in.telnetd.c revision 3ff1fd9d4a487a2b48cbc31c71277af0f7cf4bf2
/*
* CDDL HEADER START
*
* The contents of this file are subject to the terms of the
* Common Development and Distribution License, Version 1.0 only
* (the "License"). You may not use this file except in compliance
* with the License.
*
* You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
* See the License for the specific language governing permissions
* and limitations under the License.
*
* When distributing Covered Code, include this CDDL HEADER in each
* file and include the License file at usr/src/OPENSOLARIS.LICENSE.
* If applicable, add the following below this CDDL HEADER, with the
* fields enclosed by brackets "[]" replaced with your own identifying
* information: Portions Copyright [yyyy] [name of copyright owner]
*
* CDDL HEADER END
*
* Copyright 2005 Sun Microsystems, Inc. All rights reserved.
* Use is subject to license terms.
*/
/*
* Copyright (c) 1983, 1984, 1985, 1986, 1987, 1988, 1989 AT&T
* All Rights Reserved.
*/
/*
* University Copyright- Copyright (c) 1982, 1986, 1988
* The Regents of the University of California.
* All Rights Reserved.
*
* University Acknowledgment- Portions of this document are derived from
* software developed by the University of California, Berkeley, and its
* contributors.
*/
#pragma ident "%Z%%M% %I% %E% SMI"
/*
* Telnet server.
*/
#include <unistd.h>
#define AUTHWHO_STR
#define AUTHTYPE_NAMES
#define AUTHHOW_NAMES
#define AUTHRSP_NAMES
#define ENCRYPT_NAMES
#include <stdio.h>
#include <stdarg.h>
#include <signal.h>
#include <errno.h>
#include <netdb.h>
#include <syslog.h>
#include <ctype.h>
#include <fcntl.h>
#include <sac.h> /* for SC_WILDC */
#include <utmpx.h>
#include <malloc.h>
#include <string.h>
#include <security/pam_appl.h>
#include <sys/logindmux.h>
#include <sys/telioctl.h>
#include <deflt.h>
#include <stdlib.h>
#include <string.h>
#include <stropts.h>
#include <termios.h>
#include <com_err.h>
#include <krb5.h>
#include <krb5_repository.h>
#include <rpc/des_crypt.h>
#include <sys/cryptmod.h>
#define TELNETD_OPTS "Ss:a:dEXUhR:M:"
#ifdef DEBUG
#define DEBUG_OPTS "p:e"
#else
#define DEBUG_OPTS ""
#endif /* DEBUG */
#define OPT_NO 0 /* won't do this option */
#define OPT_YES_BUT_ALWAYS_LOOK 2
#define OPT_NO_BUT_ALWAYS_LOOK 3
#define MAXOPTLEN 256
#define MAXUSERNAMELEN 256
/*
* I/O data buffers, pointers, and counters.
*/
static int netibufsize;
ncc++; \
}
static char *neturg = 0; /* one past last bye of urgent data */
/* the remote system seems to NOT be an old 4.2 */
static int not42 = 1;
static char defaultfile[] = "/etc/default/telnetd";
static char bannervar[] = "BANNER=";
static char BANNER1[] = "\r\n\r\n";
static char BANNER2[] = "\r\n\r\0\r\n\r\0";
/*
* buffer for sub-options - enlarged to 4096 to handle credentials
* from AUTH options
*/
*subpointer++ = (c); \
}
#define MAXCCACHENAMELEN 36
#define MAXERRSTRLEN 1024
#define MAXPRINCLEN 256
static boolean_t auth_debug = 0;
static boolean_t auth_negotiated = 0;
static int auth_status = 0;
static int auth_level = 0;
static char *AuthenticatingUser = NULL;
static krb5_context telnet_context = 0;
static krb5_auth_context auth_context = 0;
/* telnetd gets session key from here */
static char *telnet_srvtab = NULL;
typedef struct {
char *AuthString;
} AuthInfo;
AUTH_ENCRYPT_ON, "KRB5 MUTUAL CRYPTO"},
"KRB5 MUTUAL" },
"KRB5 1-WAY" },
{0, 0, "NONE"}
};
#define POSTAMBLE_SIZE 5
#define NO_ENCRYPTION 0x00
#define SEND_ENCRYPTED 0x01
#define RECV_ENCRYPTED 0x02
static telnet_enc_data_t encr_data;
static int encrypt_send_encrypt_is();
extern void mit_des_fixup_key_parity(Block);
extern int krb5_setenv(const char *, const char *, int);
/* need to know what FD to use to talk to the crypto module */
static int cryptmod_fd = -1;
#define LOGIN_PROGRAM "/bin/login"
/*
* State for recv fsm
*/
#define TS_DATA 0 /* base state */
static int ncc;
static int master; /* master side of pty */
static int pty; /* side of pty that gets ioctls */
static int net;
static int inter;
extern char **environ;
static char *line;
static int SYNCHing = 0; /* we are in TELNET SYNCH mode */
static char pam_svc_name[64];
static void doit(int, struct sockaddr_storage *);
static void willoption(int);
static void wontoption(int);
static void dooption(int);
static void dontoption(int);
static void fatal(int, char *);
static void fatalperror(int, char *, int);
static void mode(int, int);
static void interrupt(void);
static void drainstream(int);
static int readstream(int, char *, int);
static void local_unsetenv(const char *name);
static void suboption(void);
static void willoption(int option);
static void wontoption(int option);
static void dontoption(int option);
static void write_data(const char *, ...);
static void write_data_len(const char *, int);
static void rmut(void);
static void cleanup(int);
static void telnet(int, int);
static void telrcv(void);
static void sendbrk(void);
static void ptyflush(void);
static void netclear(void);
static void netflush(void);
static void showbanner(void);
static void map_banner(char *);
static void defbanner(void);
static void ttloop(void);
/*
* The env_list linked list is used to store the environment variables
* until the final exec of login. A malevolent client might try to
* send an environment variable intended to affect the telnet daemon's
* execution. Right now the BANNER expansion is the only instance.
* Note that it is okay to pass the environment variables to login
* because login protects itself against environment variables mischief.
*/
struct envlist {
char *name;
char *value;
int delete;
};
/*
* The following are some clocks used to decide how to interpret
* the relationship between various variables.
*/
static struct {
int
system, /* what the current time is */
echotoggle, /* last time user entered echo character */
modenegotiated, /* last time operating mode negotiated */
didnetreceive, /* last time we read data from network */
ttypesubopt, /* ttype subopt is received */
getterminal, /* time started to get terminal information */
xdisplocopt, /* xdisploc will/wont received */
xdisplocsubopt, /* xdisploc suboption received */
nawssubopt, /* window size received */
environopt, /* environment option will/wont received */
oenvironopt, /* "old" environ option will/wont received */
environsubopt, /* environment option suboption received */
oenvironsubopt, /* "old environ option suboption received */
gotDM; /* when did we last see a data mark */
int getauth;
int authopt; /* Authentication option negotiated */
int authdone;
int getencr;
int encropt;
int encr_support;
} clocks;
static int init_neg_done = 0;
static boolean_t resolve_hostname = 0;
static void send_will(int);
static void send_wont(int);
static void send_do(int);
/* ARGSUSED */
static void
{
authenticated = &NoAuth;
} else if (result != AUTH_REJECT &&
/*
* Authentication successful, so we have a session key, and
* we're willing to do ENCRYPT, so send our ENCRYPT SUPPORT.
*
* Can't have sent ENCRYPT SUPPORT yet! And if we're sending it
* ENCRYPT dance before authentication, which is ok, but not too
* dance if authentication fails, though clients typically just
* don't care.
*/
write_data("%c%c%c%c%c%c%c",
netflush();
if (enc_debug)
"SENT ENCRYPT SUPPORT\n");
(void) encrypt_send_encrypt_is();
}
}
static void
{
"krb5 auth reply length too large (%d)", len);
if (auth_debug)
"krb5 auth reply length too large (%d)\n",
len);
return;
len = 0;
*p++ = IAC;
*p++ = SB;
*p++ = TELOPT_AUTHENTICATION;
*p++ = AUTHTYPE_KERBEROS_V5;
*p++ = type; /* RESPONSE or ACCEPT */
while (len-- > 0) {
*p++ = IAC;
}
*p++ = IAC;
*p++ = SE;
/* queue the data to be sent */
#if defined(AUTHTYPE_NAMES) && defined(AUTHWHO_STR) &&\
defined(AUTHHOW_NAMES) && defined(AUTHRSP_NAMES)
if (auth_debug) {
"%s %s|%s %s\n",
AUTHRSP_NAME(type));
}
#endif /* AUTHTYPE_NAMES && AUTHWHO_NAMES && AUTHHOW_NAMES && AUTHRSP_NAMES */
netflush();
}
/* Decode, decrypt and store the forwarded creds in the local ccache. */
static krb5_error_code
char *username)
{
krb5_creds **creds;
char ccname[MAXCCACHENAMELEN];
return (retval);
goto cleanup;
goto cleanup;
goto cleanup;
goto cleanup;
/*
* This verifies that the user is valid on the local system,
* maps the username from KerberosV5 to unix,
* and moves the KRB5CCNAME file to the correct place
*
* NOTE: the user must be in the gsscred table in order to map
* from KRB5 to Unix.
*/
username);
}
if (auth_debug)
"Successfully stored forwarded creds\n");
return (retval);
}
static void
{
krb5_error_code err = 0;
krb5_keytab keytabid = 0;
char errbuf[MAXERRSTRLEN];
char *name;
if (cnt-- < 1)
return;
switch (*data++) {
case KRB_AUTH:
if (auth_context == NULL) {
if (err)
"Error getting krb5 auth "
}
if (!err) {
&rcache);
0, 0,
&server);
if (!err) {
server, 0),
&rcache);
server);
}
}
if (err)
"Error allocating krb5 replay cache: %s",
error_message(err));
else {
rcache);
if (err)
"Error creating krb5 "
"replay cache: %s",
error_message(err));
}
}
if (!err)
if (err) {
"Error reading krb5 auth information:"
goto errout;
}
/*
* Verify that the correct principal was used
*/
char princ[MAXPRINCLEN];
"incorrect service "
"name: \"%s\" != "
"\"host\"",
princ);
} else {
"incorrect service "
"name: principal != "
"\"host\"",
sizeof (errbuf));
}
goto errout;
}
} else {
sizeof (errbuf));
goto errout;
}
if (err) {
"Failed to get authenticator: %s",
error_message(err));
goto errout;
}
!authenticator->checksum) {
"authenticator is missing checksum",
sizeof (errbuf));
goto errout;
}
if (authenticator->checksum) {
char type_check[2];
auth_context, &key);
if (err) {
"Failed to get key from "
"authenticator: %s",
error_message(err));
goto errout;
}
key, 0,
&input,
&valid);
if (err) {
"Kerberos checksum "
"verification failed: "
"%s",
error_message(err));
goto errout;
}
}
/* do ap_rep stuff here */
&outbuf))) {
"Failed to make "
"Kerberos auth reply: "
"%s",
error_message(err));
goto errout;
}
}
&name))
name = 0;
if (auth_debug) {
"\tKerberos5 identifies user as ``%s''\r\n",
}
}
&newkey);
if (session_key != NULL) {
session_key = 0;
}
newkey, &session_key);
} else {
&session_key);
}
/*
* Initialize encryption stuff. Currently, we are only
* supporting 8 byte keys and blocks. Check for this later.
*/
break;
case KRB_FORWARD:
if (auth_debug)
"RCVD KRB_FORWARD data (%d bytes)\n", cnt);
if (auth_context != NULL) {
auth_context, &rcache);
0, 0, KRB5_NT_SRV_HST, &server);
if (!err) {
server, 0),
&rcache);
server);
}
}
if (err) {
"Error allocating krb5 replay cache: %s",
error_message(err));
} else {
if (err)
"Error creating krb5 replay cache:"
" %s",
error_message(err));
}
}
/*
* from the original connection. This data is used to
* verify the forwarded credentials.
*/
if (err == 0)
/*
* If all is well, store the forwarded creds in
* the users local credential cache.
*/
if (err) {
"Read forwarded creds failed: %s",
error_message(err));
if (auth_debug)
"\tCould not read "
"forwarded credentials\r\n");
} else
if (auth_debug)
"credentials obtained\r\n");
break;
default:
if (auth_debug)
"\tUnknown Kerberos option %d\r\n",
data[-1]);
break;
}
return;
if (auth_debug)
if (auth_context != NULL) {
auth_context = 0;
}
}
static int
{
int code = 0;
if (telnet_context == NULL) {
if (code != 0 && auth_debug)
"Cannot initialize Kerberos V5: %s",
}
return (code);
}
static void
{
char namebuf[MAXPRINCLEN];
if (cnt < 1) {
if (auth_debug)
"\t(auth_name) Empty NAME in auth "
"reply\n");
return;
}
if (auth_debug)
"\t(auth_name) NAME exceeds %d bytes\n",
sizeof (namebuf)-1);
return;
}
if (auth_debug)
}
static void
{
if (cnt < 2)
return;
/*
* We failed to negoiate secure authentication
*/
if (data[0] == AUTHTYPE_NULL) {
auth_finished(0, AUTH_REJECT);
return;
}
aptr++;
if (auth_debug)
}
}
static int
{
if (auth_debug)
"auth_level = %d user = %s\n",
return (level);
if (AuthenticatingUser != NULL &&
AuthenticatingUser))) {
return (AUTH_VALID);
} else {
if (!retval)
"Krb5 principal lacks permission to "
"access local account for %s",
return (AUTH_USER);
}
}
/*
*/
static int
{
static int devrandom = -1;
if (devrandom == -1 &&
errno);
return (-1);
}
errno);
return (-1);
}
return (0);
}
/*
* encrypt_init
*
* Initialize the encryption data structures
*/
static void
{
}
/*
* encrypt_send_request_start
*
* Request that the remote side automatically start sending
* encrypted output
*/
static void
{
p = buf;
*p++ = IAC;
*p++ = SB;
*p++ = TELOPT_ENCRYPT;
*p++ = ENCRYPT_REQSTART;
/*
* We are telling the remote side which
* decrypt key we will use so that it may
* encrypt in the same key.
*/
*p++ = IAC;
*p++ = SE;
netflush();
if (enc_debug)
"SENT TELOPT_ENCRYPT ENCRYPT_REQSTART\n");
}
/*
* encrypt_is
*
* When we receive the TELOPT_ENCRYPT ENCRYPT_IS ...
* message, the client is telling us that it will be sending
* encrypted data using the indicated cipher.
* We must initialize the read (decrypt) side of our connection
*/
static void
{
register int type;
register int iv_status = CFB64_IV_OK;
register int lstate = 0;
(uchar_t)0, /* placeholder: sbbuf[4] */
};
if (--cnt < 0)
return;
/*
* Steps to take:
* 1. Create the proper stream Initialization vector
* - copy the correct 'seed' to IV and output blocks
* - set the correct key schedule
* 2. Generate reply for the other side:
* IAC SB TELOPT_ENCRYPT ENCRYPT_REPLY type CFB64_IV_OK
* [ data ... ] IAC SE
* 3. Tell crypto module: method, direction, IV
*/
switch (type) {
case TELOPT_ENCTYPE_DES_CFB64:
if (enc_debug)
"\t(encrypt_is) initial state = %d\n",
lstate);
/*
* Before we extract the IV bytes, make sure we got
* enough data.
*/
if (enc_debug)
"\t(encrypt_is) Not enough "
"IV bytes\n");
} else {
data++; /* skip over the CFB64_IV byte */
sizeof (Block));
}
break;
case TELOPT_ENCTYPE_NULL:
if (enc_debug)
"\t(encrypt_is) We accept NULL encr\n");
break;
default:
if (enc_debug)
"\t(encrypt_is) Can't find type (%d) "
"for initial negotiation\r\n",
type);
break;
}
if (iv_status == CFB64_IV_OK) {
/*
* send IV to crypto module and indicate it is for
* decrypt only
*/
} else {
/* tell crypto module to disable crypto on "read" stream */
}
netflush();
#ifdef ENCRYPT_NAMES
if (enc_debug)
"SENT TELOPT_ENCRYPT ENCRYPT_REPLY %s %s\n",
"CFB64_IV_BAD"));
#endif /* ENCRYPT_NAMES */
/* Update the state of the decryption negotiation */
if (lstate == ENCR_STATE_NOT_READY)
else {
}
if (enc_debug)
"\t(encrypt_is) final DECRYPT state = %d\n",
}
/*
* encrypt_send_encrypt_is
*
* Tell the client what encryption we will use
* and what our IV will be.
*/
static int
{
register int lstate;
int i;
/*
* Haven't received ENCRYPT SUPPORT yet or we couldn't agree
* on a cipher.
*/
return (lstate);
}
/*
* - Create a random DES key
*
* - DES ECB encrypt
* encrypt the IV using itself as the key.
*
* - Send response
* IAC SB TELOPT_ENCRYPT ENCRYPT_IS CFB64 FB64_IV [ feed block ]
* IAC SE
*
*/
if (lstate == ENCR_STATE_NOT_READY)
else if ((lstate & ENCR_STATE_NO_SEND_IV) == 0) {
if (enc_debug)
"\t(encrypt_send_is) IV already sent,"
" state = %d\n", lstate);
return (lstate);
}
/*
* Invalid key, set flag so we try again later
* when we get a good one
*/
if (enc_debug)
"\t(encrypt_send_is) No Key, cannot "
"start encryption yet\n");
return (lstate);
}
if (enc_debug)
"\t(encrypt_send_is) Creating new feed\n");
/*
* Create a random feed and send it over.
*
* Use the /dev/[u]random interface to generate
* our encryption IV.
*/
if (kret) {
if (enc_debug)
"\t(encrypt_send_is) error from "
"getrandom: %d\n", kret);
} else {
}
p = sbbuf;
*p++ = IAC;
*p++ = SB;
*p++ = TELOPT_ENCRYPT;
*p++ = ENCRYPT_IS;
*p++ = CFB64_IV;
/*
* Copy the IV bytes individually so that when a
* 255 (telnet IAC) is used, it can be "escaped" by
* adding it twice (telnet RFC 854).
*/
for (i = 0; i < sizeof (Block); i++)
*p++ = IAC;
*p++ = IAC;
*p++ = SE;
netflush();
if (!kret) {
}
if (enc_debug) {
int i;
"SENT TELOPT_ENCRYPT ENCRYPT_IS %d CFB64_IV ",
for (i = 0; i < (p-sbbuf); i++)
}
return (lstate);
}
/*
* stop_stream
*
* Utility routine to send a CRIOCSTOP ioctl to the
* crypto module (cryptmod).
*/
static void
{
}
}
/*
* start_stream
*
* Utility routine to send a CRYPTIOCSTART ioctl to the
* crypto module (cryptmod). This routine may contain optional
* payload data that the cryptmod will interpret as bytes that
* need to be decrypted and sent back up to the application
* via the data stream.
*/
static void
{
}
}
/*
* encrypt_start_output
*
* Tell the other side to start encrypting its data
*/
static void
{
int lstate;
uchar_t *p;
/*
* Initialize crypto and send the ENCRYPT_IS msg
*/
if (lstate != ENCR_STATE_OK) {
if (enc_debug)
"\t(encrypt_start_output) ENCRYPT state "
"= %d\n", lstate);
return;
}
p = sbbuf;
*p++ = IAC;
*p++ = SB;
*p++ = TELOPT_ENCRYPT;
*p++ = ENCRYPT_START;
*p++ = IAC;
*p++ = SE;
/* Flush this data out before we start encrypting */
netflush();
if (enc_debug)
"(lstate = %d) data waiting = %d\n",
/*
* tell crypto module what key to use for encrypting
* Note that the ENCRYPT has not yet been enabled, but we
* need to first set the crypto key to use.
*/
} else {
if (enc_debug)
"\t(encrypt_start_output) - unknown "
"crypto_method %d\n",
return;
}
/*
* If we previously configured this crypto method, we dont want to
* overwrite the key or ivec information already given to the crypto
* module as it will cause the cipher data between the client and server
* to become out of synch and impossible to decipher.
*/
} else {
}
cki.option_mask = 0;
/* Stop encrypt side prior to setup so we dont lose data */
perror("ioctl(CRYPTIOCSETUP) [encrypt_start_output] error");
} else {
/* Setup completed OK */
}
/*
* We do not check for "stuck" data when setting up the
* outbound "encrypt" channel. Any data queued prior to
* this IOCTL will get processed correctly without our help.
*/
/*
* tell crypto module to start encrypting
*/
if (enc_debug)
"\t(encrypt_start_output) Encrypting output\n");
}
/*
* encrypt_request_start
*
* The client requests that we start encryption immediately after
* successful negotiation
*/
static void
encrypt_request_start(void)
{
if (enc_debug)
"autoencrypt = ON\n");
} else {
}
}
/*
* encrypt_end
*
* ENCRYPT END received, stop decrypting the read stream
*/
static void
encrypt_end(int direction)
{
/*
* Call this function when we wish to disable crypto in
* either direction (ENCRYPT or DECRYPT)
*/
cki.option_mask = 0;
perror("ioctl(CRYPTIOCSETUP) [encrypt_end] error");
}
}
/*
* encrypt_request_end
*
* When we receive a REQEND from the client, it means
* that we are supposed to stop encrypting
*/
static void
{
/*
* Tell the other side we are done encrypting
*/
write_data("%c%c%c%c%c%c",
netflush();
if (enc_debug)
/*
* Turn off encryption of the write stream
*/
}
/*
* encrypt_send_request_end
*
* We stop encrypting the write stream and tell the other side about it.
*/
static void
{
write_data("%c%c%c%c%c%c",
netflush();
if (enc_debug)
}
/*
* encrypt_start
*
* The client is going to start sending encrypted data
* using the previously negotiated cipher (see what we set
* when we did the REPLY in encrypt_is).
*/
static void
encrypt_start(void)
{
int bytes = 0;
if (enc_debug)
"\t(encrypt_start) No DECRYPT method "
"defined yet\n");
return;
}
} else {
if (enc_debug)
"\t(encrypt_start) - unknown "
return;
}
/*
* Don't overwrite previously configured key and ivec info
*/
} else {
}
cki.option_mask = 0;
"error: %m");
} else {
}
if (enc_debug)
"\t(encrypt_start) called CRYPTIOCSETUP for "
"decrypt side\n");
/*
* Read any data stuck between the cryptmod and the application
* so we can pass it back down to be properly decrypted after
* this operation finishes.
*/
bytes = 0;
}
/*
* Any data which was read AFTER the ENCRYPT START message
* must be sent back down to be decrypted properly.
*
* 'ncc' is the number of bytes that have been read but
* not yet processed by the telnet state machine.
*
* 'bytes' is the number of bytes waiting to be read from
* the stream.
*
* If either one is a positive value, then those bytes
* must be pulled up and sent back down to be decrypted.
*/
if (enc_debug)
"\t(encrypt_start) after drainstream, "
}
/*
* The bytes putback into the stream are no longer
* available to be read by the server, so adjust the
* counter accordingly.
*/
ncc = 0;
#ifdef ENCRYPT_NAMES
if (enc_debug) {
"\t(encrypt_start) Start DECRYPT using %s\n",
}
#endif /* ENCRYPT_NAMES */
}
/*
* encrypt_support
*
* Called when we recieve the TELOPT_ENCRYPT SUPPORT [ encr type list ]
* message from a client.
*
* Choose an agreeable method (DES_CFB64) and
* respond with TELOPT_ENCRYPT ENCRYPT_IS [ desired crypto method ]
*
* from: RFC 2946
*/
static void
{
int lstate = ENCR_STATE_NOT_READY;
#ifdef ENCRYPT_NAMES
if (enc_debug)
"RCVD ENCRYPT SUPPORT %s\n",
ENCTYPE_NAME(type));
#endif /* ENCRYPT_NAMES */
/*
* Prefer CFB64
*/
if (type == TELOPT_ENCTYPE_DES_CFB64) {
}
}
if (use_type != TELOPT_ENCTYPE_NULL &&
auth_status != AUTH_REJECT) {
/* Authenticated -> have session key -> send ENCRYPT IS */
if (lstate == ENCR_STATE_OK)
} else if (use_type == TELOPT_ENCTYPE_NULL) {
if (enc_debug)
"\t(encrypt_support) Cannot agree "
"on crypto algorithm, output encryption "
"disabled.\n");
/*
* Cannot agree on crypto algorithm
* RFC 2946 sez:
* send "IAC SB ENCRYPT IS NULL IAC SE"
* optionally, also send IAC WONT ENCRYPT
*/
write_data("%c%c%c%c%c%c%c",
netflush();
if (enc_debug)
"SENT TELOPT_ENCRYPT ENCRYPT_IS "
"[NULL]\n");
}
}
/*
* encrypt_send_keyid
*
* Sent the key id we will use to the client
*/
static void
{
p = sbbuf;
*p++ = IAC;
*p++ = SB;
*p++ = TELOPT_ENCRYPT;
if (saveit) {
if (enc_debug)
"\t(send_keyid) store %d byte %s keyid\n",
"DECRYPT"));
if (dir == TELNET_DIR_ENCRYPT) {
} else {
}
}
p += keylen;
*p++ = IAC;
*p++ = SE;
netflush();
if (enc_debug)
"DEC_KEYID"), keyid[0]);
}
/*
* encrypt_reply
*
* When we receive the TELOPT_ENCRYPT REPLY [crtype] CFB64_IV_OK IAC SE
* message, process it accordingly.
* If the vector is acceptable, tell client we are encrypting and
* enable encryption on our write stream.
*
* Negotiate the KEYID next..
* RFC 2946, 2952
*/
static void
{
int lstate;
#ifdef ENCRYPT_NAMES
if (enc_debug)
"\t(encrypt_reply) ENCRYPT REPLY %s %s [len=%d]\n",
"CFB64_IV_BAD"), len);
#endif /* ENCRYPT_NAMES */
if (enc_debug)
"\t(encrypt_reply) initial ENCRYPT state = %d\n",
lstate);
switch (result) {
case CFB64_IV_OK:
if (lstate == ENCR_STATE_NOT_READY)
/*
* The correct response here is to send the encryption key id
* RFC 2752.
*
* Send keyid 0 to indicate that we will just use default
* keys.
*/
break;
case CFB64_IV_BAD:
/*
* Clear the ivec
*/
break;
default:
if (enc_debug)
"\t(encrypt_reply) Got unknown IV value in "
"REPLY message\n");
break;
}
if (lstate == ENCR_STATE_NOT_READY) {
if (enc_debug)
"\t(encrypt_reply) encrypt.autoflag = "
"OFF\n");
} else {
}
if (enc_debug)
"\t(encrypt_reply) ENCRYPT final state = %d\n",
lstate);
}
static void
{
int lstate;
if (enc_debug)
"\t(set_keyid_state) %s initial state = %d\n",
"DECRYPT"), lstate);
/*
* Currently, we only support using the default keyid,
* so it should be an error if the len > 1 or the keyid != 0.
*/
if (enc_debug)
"\t(set_keyid_state) unexpected keyid: "
*keyidlen = 0;
"is supported", *keyid);
} else {
/*
* We move to the "IN_PROGRESS" state.
*/
if (lstate == ENCR_STATE_NOT_READY)
/*
* Clear the NO_KEYID bit because we now have a valid keyid
*/
}
if (enc_debug)
"\t(set_keyid_state) %s final state = %d\n",
"DECRYPT"), lstate);
if (dir == TELNET_DIR_ENCRYPT)
else
}
/*
* encrypt_keyid
*
* Set the keyid value in the key_info structure.
* if necessary send a response to the sender
*/
static void
{
if (len > TELNET_MAXNUMKEYS) {
if (enc_debug)
"\t(keyid) keylen too big (%d)\n", len);
return;
}
if (enc_debug) {
"DECRYPT"), len);
}
if (len == 0) {
if (*keyidlen == 0) {
if (enc_debug)
"\t(keyid) Got 0 length keyid - "
"failure\n");
return;
}
*keyidlen = 0;
if (enc_debug)
"\t(keyid) Setting new key (%d bytes)\n",
len);
} else {
if (enc_debug)
"\t(keyid) %s Key already in place,"
"state = %d autoflag=%d\n",
(dir == TELNET_DIR_ENCRYPT ?
/* key already in place */
}
return;
}
if (enc_debug)
"DECRYPT"),
(dir == TELNET_DIR_ENCRYPT ?
}
/*
* encrypt_enc_keyid
*
* We received the ENC_KEYID message from a client indicating that
* the client wishes to verify that the indicated keyid maps to a
* valid key.
*/
static void
{
/*
* Verify the decrypt keyid is valid
*/
}
/*
* encrypt_dec_keyid
*
* We received the DEC_KEYID message from a client indicating that
* the client wants to verify that the indicated keyid maps to a valid key.
*/
static void
{
}
/*
* encrypt_session_key
*
* Store the session key in the encryption data record
*/
static void
{
if (enc_debug)
"\t(session_key) Cannot set krb5 "
"session key (unknown type = %d)\n",
}
if (enc_debug)
"\t(session_key) Settting session key "
"for server\n");
/* store the key in the cipher info data struct */
/*
* Now look to see if we still need to send the key and start
* encrypting.
*
* If so, go ahead an call it now that we have the key.
*/
if (cinfo->need_start) {
if (encrypt_send_encrypt_is() == ENCR_STATE_OK) {
cinfo->need_start = 0;
}
}
}
/*
* new_env
*
* Used to add an environment variable and value to the
* linked list structure.
*/
static int
{
return (1);
return (1);
}
return (1);
}
envlist_head = env;
return (0);
}
/*
* del_env
*
* Used to delete an environment variable from the linked list
* structure. We just set a flag because we will delete the list
* anyway before we exec login.
*/
static int
{
break;
}
}
return (0);
}
static int
{
return (0);
}
/*
* audit_telnet_settid stores the terminal id while it is still
* available. Subsequent calls to adt_load_hostname() return
* the id which is stored here.
*/
static int
audit_telnet_settid(int sock) {
int rc;
ADT_NO_AUDIT, 0, ADT_NO_AUDIT,
termid, ADT_SETTID)) == 0)
(void) adt_set_proc(ah);
}
(void) adt_end_session(ah);
}
return (rc);
}
/* ARGSUSED */
int
{
struct sockaddr_storage from;
int on = 1;
int issocket;
#if defined(DEBUG)
boolean_t standalone = 0;
#endif /* defined(DEBUG) */
extern char *optarg;
char c;
int tos = -1;
switch (c) {
#if defined(DEBUG)
case 'p':
/*
* note: alternative port number only used in
* standalone mode.
*/
standalone = 1;
break;
case 'e':
enc_debug = 1;
break;
#endif /* DEBUG */
case 'a':
auth_level = 0;
auth_level = -1;
negotiate_auth_krb5 = 0;
auth_debug = 1;
} else {
"unknown authentication level specified "
"with \'-a\' option (%s)", optarg);
}
break;
case 'X':
/* disable authentication negotiation */
negotiate_auth_krb5 = 0;
break;
case 'R':
case 'M':
if (ret) {
"Unable to use Kerberos V5 as "
"requested, exiting");
exit(1);
}
"using %s as default KRB5 realm", optarg);
}
break;
case 'S':
break;
case 'E': /* disable automatic encryption */
break;
case 'U':
resolve_hostname = 1;
break;
case 's':
tos > 255) {
"%s\n", optarg);
} else {
if (tos < 0)
tos = 020;
}
break;
case 'h':
show_hostinfo = 0;
break;
default:
c);
break;
}
}
#if defined(DEBUG)
if (standalone) {
int option = 1;
if (porttouse) {
} else {
if (sp == 0) {
"unknown service\n");
}
}
if (s < 0) {
perror("telnetd: socket");
}
sizeof (option)) == -1)
perror("setsockopt SO_REUSEADDR");
perror("bind");
}
if (listen(s, 32) < 0) {
perror("listen");
}
/* automatically reap all child processes */
for (;;) {
if (ns < 0) {
perror("accept");
}
if (pid == -1) {
perror("fork");
}
if (pid == 0) {
(void) close(s);
break;
}
}
}
#endif /* defined(DEBUG) */
if (!issocket)
fatal(0, "stdin is not a socket file descriptor");
< 0) {
perror("getpeername");
}
if (audit_telnet_settid(0)) { /* set terminal ID */
perror("audit");
}
sizeof (on)) < 0) {
}
/*
* Set the TOS value
*/
if (tos != -1 &&
errno != ENOPROTOOPT) {
}
sizeof (on)) < 0) {
}
/* set the default PAM service name */
/* NOTREACHED */
return (EXIT_SUCCESS);
}
static char *terminaltype = 0;
/*
* ttloop
*
* A small subroutine to flush the network output buffer, get some data
* from the network, and pass it through the telnet state machine. We
* also flush the pty input buffer (by dropping its data) if it becomes
* too full.
*/
static void
ttloop(void)
{
netflush();
}
if (ncc < 0) {
goto read_again;
} else if (ncc == 0) {
}
telrcv(); /* state machine */
if (ncc > 0) {
telrcv();
}
}
static void
{
}
static void
{
}
static void
{
}
/*
* getauthtype
*
* Negotiate automatic authentication, is possible.
*/
static int
{
int init_status = -1;
init_status = krb5_init();
return (AUTH_REJECT);
}
if (negotiate_auth_krb5) {
/*
* Negotiate Authentication FIRST
*/
}
ttloop();
/*
* Request KRB5 Mutual authentication and if that fails,
* KRB5 1-way client authentication
*/
p = sbbuf;
*p++ = (uchar_t)TELOPT_AUTHENTICATION;
*p++ = (uchar_t)TELQUAL_SEND;
if (negotiate_auth_krb5) {
*p++ = (uchar_t)AUTHTYPE_KERBEROS_V5;
*p++ = (uchar_t)(AUTH_WHO_CLIENT |
*p++ = (uchar_t)AUTHTYPE_KERBEROS_V5;
*p++ = (uchar_t)(AUTH_WHO_CLIENT |
*p++ = (uchar_t)AUTHTYPE_KERBEROS_V5;
*p++ = (uchar_t)(AUTH_WHO_CLIENT|
} else {
*p++ = (uchar_t)AUTHTYPE_NULL;
}
write_data_len((const char *)sbbuf,
netflush();
if (auth_debug)
"SENT TELOPT_AUTHENTICATION "
"[data]\n");
/* auth_wait returns the authentication level */
/* status = auth_wait(username, len); */
ttloop();
/*
* Now check to see if the user is valid or not
*/
else {
/*
* We cant be VALID until the user status is
* checked.
*/
if (auth_status == AUTH_VALID)
if (authenticated->AuthName ==
}
}
}
return (auth_status);
}
static void
getencrtype(void)
{
if (krb5_privacy_allowed() && negotiate_encrypt) {
if (!sent_will_encrypt) {
}
if (enc_debug)
}
if (!sent_do_encrypt) {
}
if (enc_debug)
}
ttloop();
if (auth_status != AUTH_REJECT &&
if (sent_encrypt_support == B_FALSE) {
write_data("%c%c%c%c%c%c%c",
netflush();
}
/*
* Now wait for a response to these messages before
* continuing...
* Look for TELOPT_ENCRYPT suboptions
*/
ttloop();
}
} else {
/* Dont need responses to these, so dont wait for them */
}
}
/*
* getterminaltype
*
* Ask the other end to send along its terminal type.
* Output is the variable terminaltype filled in.
*/
static void
getterminaltype(void)
{
/*
* The remote side may have already sent this info, so
* dont ask for these options if the other side already
* sent the information.
*/
}
}
}
}
}
/* make sure encryption is started here */
while (auth_status != AUTH_REJECT &&
if (enc_debug)
ttloop();
}
if (enc_debug) {
}
ttloop();
}
}
}
}
}
ttloop();
}
}
ttloop();
}
}
ttloop();
}
}
ttloop();
}
}
init_neg_done = 1;
}
/*
* Get a pty, scan input lines.
*/
static void
{
char *host;
char host_name[MAXHOSTNAMELEN];
int p, t, tt;
struct sgttyb b;
int ptmfd; /* fd of logindmux connected to pty */
int netfd; /* fd of logindmux connected to netf */
struct protocol_arg telnetp;
int nsize = 0;
char abuf[INET6_ADDRSTRLEN];
struct sockaddr_in *sin;
struct sockaddr_in6 *sin6;
char username[MAXUSERNAMELEN];
int len;
char *slavename;
}
if (grantpt(p) == -1)
fatal(f, "could not grant slave pty");
if (unlockpt(p) == -1)
fatal(f, "could not unlock slave pty");
fatal(f, "could not enable slave pty");
(void) dup2(f, 0);
fatal(f, "could not open slave pty");
pty = t;
/* XXX - ispeed and ospeed must be non-zero */
wholen = sizeof (struct sockaddr_in);
fatal(f, "Cannot alloc memory for address info\n");
fatal(f, "Cannot alloc memory for port info\n");
}
wholen = sizeof (struct sockaddr_in6);
&ipv4_addr);
fatal(f, "Cannot alloc memory for address info\n");
fatal(f, "Cannot alloc memory for port info\n");
}
(const void *)&ipv4_addr,
wholen);
/*
* If we already used rsaddr.contents, free the previous
* buffer.
*/
} else {
fatal(f, "getpeername: unknown address family\n");
}
} else {
/*
* If the '-U' option was given on the cmd line, we must
* be able to lookup the hostname
*/
if (resolve_hostname) {
fatal(f, "Couldn't resolve your address into a "
"host name.\r\nPlease contact your net "
"administrator");
}
&ipv4_addr);
} else {
sizeof (abuf));
}
}
}
/*
* Note that sockmod has to be removed since readstream assumes
* a "raw" TPI endpoint (e.g. it uses getmsg).
*/
if (removemod(f, "sockmod") < 0)
encrypt_init();
/*
* Push the crypto module on the stream before 'telmod' so it
* We must push it now because many of the crypto options negotiated
* initially must be saved in the crypto module (via IOCTL calls).
*/
cryptmod_fd = f;
/*
* gotta set the encryption clock now because it is often negotiated
* immediately by the client, and if we wait till after we negotiate
* option is received.
*/
/*
* get terminal type.
*/
username[0] = '\0';
/*
* Exchange TELOPT_AUTHENTICATE options per RFC 2941/2942
*/
/*
* Exchange TELOPT_ENCRYPT options per RFC 2946
*/
getencrtype();
/*
* Make sure telmod will pass unrecognized IOCTLs to cryptmod
*/
passthru = 1;
fatal(f, "ioctl CRPASSTHRU failed\n");
if (!ncc)
/*
* readstream will do a getmsg till it receives M_PROTO type
* T_DATA_REQ from telnetmodopen(). This signals that all data
* in-flight before telmod was pushed has been received at the
* stream head.
*/
}
if (nsize < 0) {
}
/*
* open logindmux drivers and link them with network and ptm
* file descriptors.
*/
}
}
fatal(f, "ioctl I_LINK of tcp connection failed\n");
/*
* Figure out the device number of ptm's mux fd, and pass that
* to the net's mux.
*/
}
/*
* Figure out the device number of the net's mux fd, and pass that
* to the ptm's mux.
*/
}
cryptmod_fd = netfd;
/*
* Show banner that getty never gave, but
* only if the user did not automatically authenticate.
*/
showbanner();
/*
* If the user automatically authenticated with Kerberos
* we must set the service name that PAM will use. We
* need to do it BEFORE the child fork so that 'cleanup'
* in the parent can call the PAM cleanup stuff with the
* this session.
*/
}
/*
* Request to do suppress go ahead.
*
* Send this before sending the TELOPT_ECHO stuff below because
* some clients (MIT KRB5 telnet) have quirky 'kludge mode' support
* that has them turn off local echo mode if SGA is not received first.
* This also has the odd side-effect of causing the client to enable
* encryption and then immediately disable it during the ECHO option
* negotiations. Its just better to to SGA first now that we support
* encryption.
*/
if (!myopts[TELOPT_SGA]) {
}
/*
* Pretend we got a DO ECHO from the client if we have not
* yet negotiated the ECHO.
*/
if (!myopts[TELOPT_ECHO]) {
}
/*
* Is the client side a 4.2 (NOT 4.3) system? We need to know this
* because 4.2 clients are unable to deal with TCP urgent data.
*
* To find out, we send out a "DO ECHO". If the remote system
* answers "WILL ECHO" it is probably a 4.2 client, and we note
* that fact ("WILL ECHO" ==> that the client will echo what
* WE, the server, sends it; it does NOT mean that the client will
* echo the terminal input).
*/
if (pid)
/*
* The child process needs to be the session leader
* and have the pty as its controlling tty. Thus we need
* to re-open the slave side of the pty no without
* the O_NOCTTY flag that we have been careful to
* use up to this point.
*/
(void) setsid();
if (tt < 0)
(void) close(f);
(void) close(p);
(void) close(t);
if (tt != 0)
if (tt != 1)
if (tt != 2)
if (tt > 2)
if (terminaltype)
/*
* -h : pass on name of host.
* WARNING: -h is accepted by login if and only if
* getuid() == 0.
* -p : don't clobber the environment (so terminal type stays set).
*/
{
/* System V login expects a utmp entry to already be there */
}
/*
* Load in the cached environment variables and either
*/
else
}
/* If the current auth status is less than the required level, exit */
if (auth_status < auth_level) {
}
/*
* If AUTH_VALID (proper authentication REQUIRED and we have
* correct PAM service name (pam_svc_name). If possible,
* make sure the krb5 authenticated user's name (krb5_name)
* is in the PAM REPOSITORY for krb5.
*/
if (auth_level >= 0 &&
"-p",
"-d", slavename,
"-h", host,
"-u", krb5_name,
"-s", pam_svc_name,
"-R", KRB5_REPOSITORY_NAME,
AuthenticatingUser, 0);
} else if (auth_level >= 0 &&
auth_status >= AUTH_USER &&
getenv("USER"))) {
/*
* If we only know the name but not the principal,
* login will have to authenticate further.
*/
"-p",
"-d", slavename,
"-h", host,
"-s", pam_svc_name,
getenv("USER")),
0);
} else /* default, no auth. info available, login does it all */ {
getenv("USER"), 0);
}
/*NOTREACHED*/
}
static void
{
/*NOTREACHED*/
}
static void
{
/*NOTREACHED*/
}
/*
* Main loop. Select from pty and network, and
* hand data to telnet receiver finite state machine
* when it receives telnet protocol. Regular data
* flow between pty and network takes place through
* inkernel telnet streams module (telmod).
*/
static void
{
int on = 1;
char mode;
int nsize = 0;
char binary_in = 0;
char binary_out = 0;
(void) setpgrp();
/*
* Call telrcv() once to pick up anything received during
* terminal type negotiation.
*/
telrcv();
netflush();
ptyflush();
for (;;) {
int c;
if (ncc < 0)
break;
/*
* If we couldn't flush all our output to the network,
* keep checking for when we can.
*/
/*
* Never look for input if there's still
* stuff in the corresponding output buffer
*/
} else {
}
if (!SYNCHing) {
}
#define max(x, y) (((x) < (y)) ? (y) : (x))
/*
* make an ioctl to telnet module (net side) to send
* binary mode of telnet daemon. binary_in and
* binary_out are 0 if not in binary mode.
*/
mode = 0;
mode |= TEL_BINARY_IN;
mode |= TEL_BINARY_OUT;
}
"ioctl I_NREAD failed\n", errno);
if (nsize)
/*
* make an ioctl to reinsert remaining data at
* streamhead. After this, ioctl reenables the
* telnet lower put queue. This queue was
* noenabled by telnet module after sending
*/
} else {
}
failed\n");
ncc = 0;
}
} else {
/*
* state not changed to TS_DATA and hence, more to read
* send ioctl to get one more message block.
*/
}
(struct timeval *)0)) < 1) {
if (c == -1) {
continue;
}
}
(void) sleep(5);
continue;
}
/*
* Any urgent data?
*/
SYNCHing = 1;
}
/*
* Something to read from the network...
*/
ncc = 0;
else {
if (ncc <= 0) {
break;
}
}
}
netflush();
if (ncc > 0)
telrcv();
ptyflush();
}
cleanup(0);
}
static void
telrcv(void)
{
int c;
while (ncc > 0) {
return;
c = *netip & 0377;
/*
* Once we hit data, we want to transition back to
* in-kernel processing. However, this code is shared
* by getterminaltype()/ttloop() which run before the
* in-kernel plumbing is available. So if we are still
* processing the initial option negotiation, even TS_DATA
* must be processed here.
*/
break;
}
netip++;
ncc--;
switch (state) {
case TS_CR:
/* Strip off \n or \0 after a \r */
if ((c == 0) || (c == '\n')) {
break;
}
/* FALLTHRU */
case TS_DATA:
if (c == IAC) {
break;
}
if (inter > 0)
break;
/*
* We map \r\n ==> \r, since
* We now map \r\n ==> \r for pragmatic reasons.
* Many client implementations send \r\n when
* the user hits the CarriageReturn key.
*
* We USED to map \r\n ==> \n, since \r\n says
* that we want to be in column 1 of the next
* line.
*/
}
*pfrontp++ = c;
break;
case TS_IAC:
switch (c) {
/*
* Send the process on the pty side an
* interrupt. Do this with a NULL or
* interrupt char; depending on the tty mode.
*/
case IP:
interrupt();
break;
case BREAK:
sendbrk();
break;
/*
* Are You There?
*/
case AYT:
break;
/*
* Abort Output
*/
case AO: {
ptyflush(); /* half-hearted */
"ioctl TIOCGLTC: %m\n");
}
netclear(); /* clear buffer back */
netflush();
break;
}
/*
* Erase Character and
* Erase Line
*/
case EC:
case EL: {
struct sgttyb b;
char ch;
ptyflush(); /* half-hearted */
"ioctl TIOCGETP: %m\n");
if (ch != '\377') {
}
break;
}
/*
* Check for urgent data...
*/
case DM:
break;
/*
* Begin option subnegotiation...
*/
case SB:
SB_CLEAR();
continue;
case WILL:
continue;
case WONT:
continue;
case DO:
continue;
case DONT:
continue;
case IAC:
*pfrontp++ = c;
break;
}
break;
case TS_SB:
if (c == IAC) {
} else {
SB_ACCUM(c);
}
break;
case TS_SE:
if (c != SE) {
if (c != IAC) {
}
SB_ACCUM(c);
} else {
SB_TERM();
suboption(); /* handle sub-option */
}
break;
case TS_WILL:
willoption(c);
continue;
case TS_WONT:
wontoption(c);
continue;
case TS_DO:
dooption(c);
continue;
case TS_DONT:
dontoption(c);
}
continue;
default:
}
}
}
static void
willoption(int option)
{
switch (option) {
case TELOPT_BINARY:
break;
case TELOPT_ECHO:
not42 = 0; /* looks like a 4.2 system */
/*
* Now, in a 4.2 system, to break them out of ECHOing
* (to the terminal) mode, we need to send a "WILL ECHO".
* Kludge upon kludge!
*/
}
break;
case TELOPT_TTYPE:
goto common;
case TELOPT_NAWS:
goto common;
case TELOPT_XDISPLOC:
goto common;
case TELOPT_NEW_ENVIRON:
goto common;
case TELOPT_AUTHENTICATION:
negotiate_auth_krb5 == 0)
else
break;
case TELOPT_OLD_ENVIRON:
goto common;
return;
}
/*FALLTHRU*/
case TELOPT_SGA:
break;
case TELOPT_TM:
break;
case TELOPT_ENCRYPT:
if (enc_debug)
"RCVD IAC WILL TELOPT_ENCRYPT\n");
if (krb5_privacy_allowed()) {
if (sent_do_encrypt)
else
} else {
}
break;
default:
break;
}
} else {
}
if (send_reply) {
netflush();
}
}
static void
wontoption(int option)
{
int send_reply = 1;
switch (option) {
case TELOPT_ECHO:
break;
case TELOPT_BINARY:
break;
case TELOPT_TTYPE:
break;
case TELOPT_NAWS:
break;
case TELOPT_XDISPLOC:
break;
case TELOPT_NEW_ENVIRON:
break;
case TELOPT_OLD_ENVIRON:
break;
case TELOPT_AUTHENTICATION:
auth_finished(0, AUTH_REJECT);
if (auth_debug)
"RCVD WONT TELOPT_AUTHENTICATE\n");
send_reply = 0;
break;
case TELOPT_ENCRYPT:
if (enc_debug)
"RCVD IAC WONT TELOPT_ENCRYPT\n");
/*
* Remote side cannot send encryption. No reply necessary
* Treat this as if "IAC SB ENCRYPT END IAC SE" were
* received (RFC 2946) and disable crypto.
*/
send_reply = 0;
break;
}
if (send_reply) {
}
}
/*
* We received an "IAC DO ..." message from the client, change our state
* to OPT_YES.
*/
static void
{
switch (option) {
case TELOPT_TM:
break;
case TELOPT_ECHO:
break;
case TELOPT_BINARY:
break;
case TELOPT_SGA:
break;
case TELOPT_LOGOUT:
/*
* Options don't get much easier. Acknowledge the option,
* and then clean up and exit.
*/
netflush();
cleanup(0);
/*NOTREACHED*/
case TELOPT_ENCRYPT:
if (enc_debug)
/*
* We received a "DO". This indicates that the other side
* wants us to encrypt our data (pending negotiatoin).
* reply with "IAC WILL ENCRYPT" if we are able to send
* encrypted data.
*/
if (krb5_privacy_allowed() && negotiate_encrypt) {
if (sent_will_encrypt)
else
/* return if we already sent "WILL ENCRYPT" */
return;
} else {
}
break;
case TELOPT_AUTHENTICATION:
if (auth_debug) {
"RCVD DO TELOPT_AUTHENTICATION\n");
}
/*
* RFC 2941 - only the server can send
* "DO TELOPT_AUTHENTICATION".
* if a server receives this, it must respond with WONT...
*/
break;
default:
break;
}
} else {
}
if (send_reply) {
netflush();
}
}
/*
* We received an "IAC DONT ..." message from client.
* Client does not agree with the option so act accordingly.
*/
static void
dontoption(int option)
{
int send_reply = 1;
switch (option) {
case TELOPT_ECHO:
/*
* we should stop echoing, since the client side will be doing
* it, but keep mapping CR since CR-LF will be mapped to it.
*/
break;
case TELOPT_ENCRYPT:
if (enc_debug)
/*
* Remote side cannot receive any encrypted data,
* so dont send any. No reply necessary.
*/
send_reply = 0;
break;
default:
break;
}
if (send_reply) {
}
}
/*
* suboption()
*
* Look at the sub-option buffer, and try to be helpful to the other
* side.
*
*/
static void
suboption(void)
{
int subchar;
case TELOPT_TTYPE: { /* Yaaaay! */
if (SB_GET() != TELQUAL_IS) {
return; /* ??? XXX but, this is the most robust */
}
1) && !SB_EOF()) {
int c;
c = SB_GET();
if (isupper(c)) {
c = tolower(c);
}
*terminaltype++ = c; /* accumulate name */
}
*terminaltype = 0;
break;
}
case TELOPT_NAWS: {
if (SB_EOF()) {
return;
}
if (SB_EOF()) {
return;
}
if (SB_EOF()) {
return;
}
if (SB_EOF()) {
return;
}
break;
}
case TELOPT_XDISPLOC: {
return;
}
perror("malloc");
break;
}
case TELOPT_NEW_ENVIRON:
case TELOPT_OLD_ENVIRON: {
int c;
if (SB_EOF())
return;
c = SB_GET();
if (c == TELQUAL_IS) {
if (subchar == TELOPT_OLD_ENVIRON)
else
} else if (c != TELQUAL_INFO) {
return;
}
if (subchar == TELOPT_NEW_ENVIRON) {
while (!SB_EOF()) {
c = SB_GET();
if ((c == NEW_ENV_VAR) || (c == ENV_USERVAR))
break;
}
} else
{
while (!SB_EOF()) {
c = SB_GET();
if ((c == env_ovar) || (c == ENV_USERVAR))
break;
}
}
if (SB_EOF())
return;
valp = 0;
while (!SB_EOF()) {
c = SB_GET();
if (subchar == TELOPT_OLD_ENVIRON) {
if (c == env_ovar)
c = NEW_ENV_VAR;
else if (c == env_ovalue)
c = NEW_ENV_VALUE;
}
switch (c) {
case NEW_ENV_VALUE:
*cp = '\0';
break;
case NEW_ENV_VAR:
case ENV_USERVAR:
*cp = '\0';
if (valp) {
perror("malloc");
}
} else {
}
valp = 0;
break;
case ENV_ESC:
if (SB_EOF())
break;
c = SB_GET();
/* FALL THROUGH */
default:
*cp++ = c;
break;
}
}
*cp = '\0';
if (valp) {
perror("malloc");
}
} else {
}
break;
} /* end of case TELOPT_NEW_ENVIRON */
case TELOPT_AUTHENTICATION:
if (SB_EOF())
break;
switch (SB_GET()) {
case TELQUAL_SEND:
case TELQUAL_REPLY:
/*
* These are sent server only and cannot be sent by the
* client.
*/
break;
case TELQUAL_IS:
if (auth_debug)
"RCVD AUTHENTICATION IS "
"(%d bytes)\n",
SB_LEN());
if (!auth_negotiated)
break;
case TELQUAL_NAME:
if (auth_debug)
"RCVD AUTHENTICATION NAME "
"(%d bytes)\n",
SB_LEN());
if (!auth_negotiated)
break;
}
break;
case TELOPT_ENCRYPT: {
int c;
if (SB_EOF())
break;
c = SB_GET();
#ifdef ENCRYPT_NAMES
if (enc_debug)
ENCRYPT_NAME(c));
#endif /* ENCRYPT_NAMES */
switch (c) {
case ENCRYPT_SUPPORT:
break;
case ENCRYPT_IS:
break;
case ENCRYPT_REPLY:
break;
case ENCRYPT_START:
break;
case ENCRYPT_END:
break;
case ENCRYPT_REQSTART:
break;
case ENCRYPT_REQEND:
/*
* We can always send an REQEND so that we cannot
* get stuck encrypting. We should only get this
* if we have been able to get in the correct mode
* anyhow.
*/
break;
case ENCRYPT_ENC_KEYID:
break;
case ENCRYPT_DEC_KEYID:
break;
default:
break;
}
}
break;
default:
break;
}
}
static void
{
ptyflush();
}
}
}
/*
* Because "O_CRMOD" will never be set in "off" we don't have to
* handle this case here.
*/
}
/*
* Send interrupt to process on other side of pty.
* If it is in raw mode, just write NULL;
* otherwise, write intr char.
*/
static void
interrupt(void)
{
struct sgttyb b;
ptyflush(); /* half-hearted */
*pfrontp++ = '\0';
return;
}
}
/*
* Send quit to process on other side of pty.
* If it is in raw mode, just write NULL;
* otherwise, write quit char.
*/
static void
sendbrk(void)
{
struct sgttyb b;
ptyflush(); /* half-hearted */
*pfrontp++ = '\0';
return;
}
}
static void
ptyflush(void)
{
int n;
if (n < 0)
return;
pbackp += n;
}
/*
* nextitem()
*
* Return the address of the next "item" in the TELNET data
* stream. This will be the address of the next character if
* the current address is a user data character, or it will
* be the address of the character following the TELNET command
* if the current address is a TELNET IAC ("I Am a Command")
* character.
*/
static char *
{
return (current+1);
}
case DO:
case DONT:
case WILL:
case WONT:
return (current+3);
case SB: /* loop forever looking for the SE */
{
for (;;) {
return (look);
}
}
}
}
default:
return (current+2);
}
}
/*
* netclear()
*
* We are about to do a TELNET SYNCH operation. Clear
* the path to the network.
*
* Things are a bit tricky since we may have sent the first
* byte or so of a previous TELNET command into the network.
* So, we have to scan the network buffer from the beginning
* until we are up to where we want to be.
*
* A side effect of what we do, just to keep things
* simple, is to clear the urgent data pointer. The principal
* caller should be setting the urgent data pointer AFTER calling
* us in any case.
*/
static void
netclear(void)
{
char *good;
}
int length;
do {
} else {
}
}
neturg = 0;
}
/*
* netflush
* Send as much data as possible to the network,
* handling requests for urgent data.
*/
static void
netflush(void)
{
int n;
/*
* if no urgent data, or if the other side appears to be an
* old 4.2 client (and thus unable to survive TCP urgent data),
* write the entire buffer in non-OOB mode.
*/
} else {
/*
* In 4.2 (and 4.3) systems, there is some question
* about what byte in a sendOOB operation is the "OOB"
* data. To make ourselves compatible, we only send ONE
* byte out of band, the one WE THINK should be OOB
* (though we really have more the TCP philosophy of
* urgent data rather than the Unix philosophy of OOB
* data).
*/
if (n > 1) {
/* send URGENT all by itself */
} else {
/* URGENT data */
}
}
}
if (n < 0) {
if (errno == EWOULDBLOCK)
return;
/* should blow this guy away... */
return;
}
nbackp += n;
neturg = 0;
}
}
}
/* ARGSUSED */
static void
{
/*
* If the TEL_IOC_ENABLE ioctl hasn't completed, then we need to
* handle closing differently. We close "net" first and then
* "master" in that order. We do close(net) first because
* we have no other way to disconnect forwarding between the network
* and master. So by issuing the close()'s we ensure that no further
* data rises from TCP. A more complex fix would be adding proper
* support for throwing a "stop" switch for forwarding data between
* logindmux peers. It's possible to block in the close of the tty
* while the network still receives data and the telmod module is
* TEL_STOPPED. A denial-of-service attack generates this case,
* see 4102102.
*/
if (!telmod_init_done) {
}
rmut();
}
static void
rmut(void)
{
/* while cleaning up don't allow disruption */
setutxent();
/*
* Cleaned up elsewhere.
*/
break;
}
/*
* call pam_close_session if login changed
* the utmpx user entry from type LOGIN_PROCESS
* to type USER_PROCESS, which happens
* after pam_open_session is called.
*/
sizeof (user));
sizeof (ttyn));
sizeof (rhost));
PAM_SUCCESS) {
ttyn);
rhost);
(void) pam_close_session(pamh, 0);
}
}
/*
* Since modutx failed we'll
* write out the new entry
* ourselves.
*/
(void) pututxline(up);
}
break;
}
}
endutxent();
}
static int
{
union T_primitives tpi;
int ret = 0;
int flags = 0;
int bytes_avail, count;
return (-1);
}
(unsigned)netibufsize + bytes_avail);
}
}
if (ret < 0) {
errno);
return (-1);
}
}
return (0);
}
cleanup(0);
/*NOTREACHED*/
}
static void
drainstream(int size)
{
int nbytes;
int tsize;
}
}
/*
* TPI style replacement for socket send() primitive, so we don't require
* sockmod to be on the stream.
*/
static int
{
struct T_exdata_req exd_req;
int ret;
if (ret == 0) {
}
return (ret);
}
/*
* local_setenv --
* Set the value of the environmental variable "name" to be
* "value". If rewrite is set, replace any current value.
*/
static int
{
static int alloced; /* if allocated space before */
char *c;
/*
* Do not allow environment variables which begin with LD_ to be
* inserted into the environment. While normally the dynamic linker
* protects the login program, that is based on the assumption hostile
* invocation of login are from non-root users. However, since telnetd
* runs as root, this cannot be utilized. So instead we simply
* prevent LD_* from being inserted into the environment.
* This also applies to other environment variables that
* are to be ignored in setugid apps.
* Note that at this point name can contain '='!
* Also, do not allow TTYPROMPT to be passed along here.
*/
return (-1);
}
++value;
if (!rewrite)
return (0);
while (*c++ = *value++);
return (0);
}
} else { /* create new slot */
int cnt;
char **p;
if (alloced) { /* just increase size */
if (!environ)
return (-1);
} else { /* get new space */
(cnt + 2)));
if (!p)
return (-1);
environ = p;
}
}
return (-1);
for (*c++ = '='; *c++ = *value++; );
return (0);
}
/*
* local_unsetenv(name) --
* Delete environmental variable "name".
*/
static void
local_unsetenv(const char *name)
{
char **p;
int offset;
if ((*p = *(p + 1)) == 0)
break;
}
/*
* __findenv --
* Returns pointer to value associated with name, if any, else NULL.
* environmental array, for use by local_setenv() and local_unsetenv().
* Explicitly removes '=' in argument name.
*/
static char *
{
extern char **environ;
int len;
const char *np;
char **p, *c;
return (NULL);
continue;
return (c + len + 1);
}
return (NULL);
}
static void
showbanner(void)
{
char *cp;
if (defopen(defaultfile) == 0) {
int flags;
/* ignore case */
+ 1 < sizeof (evalbuf)) {
sizeof (evalbuf));
sizeof (evalbuf));
/*
* Pipe I/O atomicity guarantees we
* need only one read.
*/
sizeof (buf) - 1,
fp)) != 0) {
char *p;
if (p != NULL)
*p = '\0';
netflush();
}
}
/* close default file */
return;
}
}
}
}
defbanner();
netflush();
}
static void
map_banner(char *p)
{
char *q;
/*
* Map the banner: "\n" -> "\r\n" and "\r" -> "\r\0"
*/
if (*p == '\n') {
*q++ = '\r';
*q++ = '\n';
p++;
} else if (*p == '\r') {
*q++ = '\r';
*q++ = '\0';
p++;
} else
*q++ = *p++;
}
/*
* Show banner that getty never gave. By default, this is `uname -sr`.
*
* The banner includes some null's (for TELNET CR disambiguation),
* so we have to be somewhat complicated.
*/
static void
defbanner(void)
{
struct utsname u;
/*
* Dont show this if the '-h' option was present
*/
if (!show_hostinfo)
return;
if (uname(&u) == -1)
return;
}
/*
* Verify that the named module is at the top of the stream
* and then pop it off.
*/
static int
{
char topmodname[BUFSIZ];
return (-1);
return (-1);
}
return (-1);
return (0);
}
static void
write_data(const char *format, ...)
{
int len;
return;
}
static void
{
while (len > 0) {
/*
* If there's not enough space in netobuf then
* try to make some.
*/
netflush();
}
/* Copy as much as we can */
}
}