in.telnetd.c revision 7c478bd95313f5f23a4c958a745db2134aa03244
/*
* 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
* or http://www.opensolaris.org/os/licensing.
* 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 2004 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 <sys/types.h>
#include <sys/param.h>
#include <sys/socket.h>
#include <sys/wait.h>
#include <sys/file.h>
#include <sys/stat.h>
#include <sys/filio.h>
#include <sys/time.h>
#include <sys/stropts.h>
#include <sys/stream.h>
#include <sys/tihdr.h>
#include <sys/utsname.h>
#include <unistd.h>
#include <netinet/in.h>
#define AUTHWHO_STR
#define AUTHTYPE_NAMES
#define AUTHHOW_NAMES
#define AUTHRSP_NAMES
#define ENCRYPT_NAMES
#include <arpa/telnet.h>
#include <arpa/inet.h>
#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 <sys/ttold.h>
#include <malloc.h>
#include <string.h>
#include <security/pam_appl.h>
#include <sys/tihdr.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 <des/des.h>
#include <rpc/des_crypt.h>
#include <sys/cryptmod.h>
#include <bsm/adt.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 1 /* will do this option */
#define OPT_YES_BUT_ALWAYS_LOOK 2
#define OPT_NO_BUT_ALWAYS_LOOK 3
#define MAXOPTLEN 256
#define MAXUSERNAMELEN 256
static char remopts[MAXOPTLEN];
static char myopts[MAXOPTLEN];
static uchar_t doopt[] = { (uchar_t)IAC, (uchar_t)DO, '%', 'c', 0 };
static uchar_t dont[] = { (uchar_t)IAC, (uchar_t)DONT, '%', 'c', 0 };
static uchar_t will[] = { (uchar_t)IAC, (uchar_t)WILL, '%', 'c', 0 };
static uchar_t wont[] = { (uchar_t)IAC, (uchar_t)WONT, '%', 'c', 0 };
/*
* I/O data buffers, pointers, and counters.
*/
static char ptyobuf[BUFSIZ], *pfrontp = ptyobuf, *pbackp = ptyobuf;
static char *netibuf, *netip;
static int netibufsize;
#define NIACCUM(c) { *netip++ = c; \
ncc++; \
}
static char netobuf[BUFSIZ], *nfrontp = netobuf, *nbackp = netobuf;
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
*/
static char subbuffer[4096], *subpointer = subbuffer, *subend = subbuffer;
#define SB_CLEAR() subpointer = subbuffer;
#define SB_TERM() { subend = subpointer; SB_CLEAR(); }
#define SB_ACCUM(c) if (subpointer < (subbuffer+sizeof (subbuffer))) { \
*subpointer++ = (c); \
}
#define SB_GET() ((*subpointer++)&0xff)
#define SB_EOF() (subpointer >= subend)
#define SB_LEN() (subend - subpointer)
#define MAXCCACHENAMELEN 36
#define MAXERRSTRLEN 1024
#define MAXPRINCLEN 256
static boolean_t auth_debug = 0;
static boolean_t negotiate_auth_krb5 = 1;
static boolean_t auth_negotiated = 0;
static int auth_status = 0;
static int auth_level = 0;
static char *AuthenticatingUser = NULL;
static char *krb5_name = NULL;
static krb5_address rsaddr = { 0, 0, 0, NULL };
static krb5_address rsport = { 0, 0, 0, NULL };
static krb5_context telnet_context = 0;
static krb5_auth_context auth_context = 0;
/* telnetd gets session key from here */
static krb5_ticket *ticket = NULL;
static krb5_keyblock *session_key = NULL;
static char *telnet_srvtab = NULL;
typedef struct {
uchar_t AuthName;
uchar_t AuthHow;
char *AuthString;
} AuthInfo;
static AuthInfo auth_list[] = {
{AUTHTYPE_KERBEROS_V5, AUTH_WHO_CLIENT | AUTH_HOW_MUTUAL |
AUTH_ENCRYPT_ON, "KRB5 MUTUAL CRYPTO"},
{AUTHTYPE_KERBEROS_V5, AUTH_WHO_CLIENT | AUTH_HOW_MUTUAL,
"KRB5 MUTUAL" },
{AUTHTYPE_KERBEROS_V5, AUTH_WHO_CLIENT | AUTH_HOW_ONE_WAY,
"KRB5 1-WAY" },
{0, 0, "NONE"}
};
static AuthInfo NoAuth = {0, 0, NULL};
static AuthInfo *authenticated = NULL;
#define PREAMBLE_SIZE 5 /* for auth_reply_str allocation */
#define POSTAMBLE_SIZE 5
#define STR_DATA_LEN(len) ((len) * 2 + PREAMBLE_SIZE + POSTAMBLE_SIZE)
static void auth_name(uchar_t *, int);
static void auth_is(uchar_t *, int);
#define NO_ENCRYPTION 0x00
#define SEND_ENCRYPTED 0x01
#define RECV_ENCRYPTED 0x02
#define ENCRYPT_BOTH_WAYS (SEND_ENCRYPTED | RECV_ENCRYPTED)
static telnet_enc_data_t encr_data;
static boolean_t negotiate_encrypt = B_TRUE;
static boolean_t sent_encrypt_support = B_FALSE;
static boolean_t sent_will_encrypt = B_FALSE;
static boolean_t sent_do_encrypt = B_FALSE;
static boolean_t enc_debug = 0;
static void encrypt_session_key(Session_Key *key, cipher_info_t *cinfo);
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 */
#define TS_IAC 1 /* look for double IAC's */
#define TS_CR 2 /* CR-LF ->'s CR */
#define TS_SB 3 /* throw away begin's... */
#define TS_SE 4 /* ...end's (suboption negotiation) */
#define TS_WILL 5 /* will option negotiation */
#define TS_WONT 6 /* wont " */
#define TS_DO 7 /* do " */
#define TS_DONT 8 /* dont " */
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 int state = TS_DATA;
static int env_ovar = -1; /* XXX.sparker */
static int env_ovalue = -1; /* XXX.sparker */
static char pam_svc_name[64];
static boolean_t telmod_init_done = B_FALSE;
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 int send_oob(int fd, char *ptr, int count);
static int local_setenv(const char *name, const char *value, int rewrite);
static void local_unsetenv(const char *name);
static void suboption(void);
static int removemod(int f, char *modname);
static void willoption(int option);
static void wontoption(int option);
static void dooption(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 {
struct envlist *next;
char *name;
char *value;
int delete;
};
static struct envlist *envlist_head = NULL;
/*
* 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 */
ttypeopt, /* ttype will/won't received */
ttypesubopt, /* ttype subopt is received */
getterminal, /* time started to get terminal information */
xdisplocopt, /* xdisploc will/wont received */
xdisplocsubopt, /* xdisploc suboption received */
nawsopt, /* window size will/wont 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 boolean_t show_hostinfo = 1;
#define settimer(x) (clocks.x = ++clocks.system)
#define sequenceIs(x, y) (clocks.x < clocks.y)
static void send_will(int);
static void send_wont(int);
static void send_do(int);
static char *__findenv(const char *name, int *offset);
/* ARGSUSED */
static void
auth_finished(AuthInfo *ap, int result)
{
if ((authenticated = ap) == NULL) {
authenticated = &NoAuth;
if (myopts[TELOPT_ENCRYPT] == OPT_YES)
send_wont(TELOPT_ENCRYPT);
myopts[TELOPT_ENCRYPT] = remopts[TELOPT_ENCRYPT] = OPT_NO;
encr_data.encrypt.autoflag = 0;
} else if (result != AUTH_REJECT &&
myopts[TELOPT_ENCRYPT] == OPT_YES &&
remopts[TELOPT_ENCRYPT] == OPT_YES) {
/*
* 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
* now it's really only because we did the DO ENCRYPT/WILL
* ENCRYPT dance before authentication, which is ok, but not too
* bright since we have to do the DONT ENCRYPT/WONT ENCRYPT
* dance if authentication fails, though clients typically just
* don't care.
*/
write_data("%c%c%c%c%c%c%c",
(uchar_t)IAC,
(uchar_t)SB,
(uchar_t)TELOPT_ENCRYPT,
(uchar_t)ENCRYPT_SUPPORT,
(uchar_t)TELOPT_ENCTYPE_DES_CFB64,
(uchar_t)IAC,
(uchar_t)SE);
netflush();
sent_encrypt_support = B_TRUE;
if (enc_debug)
(void) fprintf(stderr,
"SENT ENCRYPT SUPPORT\n");
(void) encrypt_send_encrypt_is();
}
auth_status = result;
settimer(authdone);
}
static void
reply_to_client(AuthInfo *ap, int type, void *data, int len)
{
uchar_t reply[BUFSIZ];
uchar_t *p = reply;
uchar_t *cd = (uchar_t *)data;
if (len == -1 && data != NULL)
len = strlen((char *)data);
else if (len > (sizeof (reply) - 9)) {
syslog(LOG_ERR,
"krb5 auth reply length too large (%d)", len);
if (auth_debug)
(void) fprintf(stderr,
"krb5 auth reply length too large (%d)\n",
len);
return;
} else if (data == NULL)
len = 0;
*p++ = IAC;
*p++ = SB;
*p++ = TELOPT_AUTHENTICATION;
*p++ = AUTHTYPE_KERBEROS_V5;
*p++ = ap->AuthName;
*p++ = ap->AuthHow; /* MUTUAL, ONE-WAY, etc */
*p++ = type; /* RESPONSE or ACCEPT */
while (len-- > 0) {
if ((*p++ = *cd++) == IAC)
*p++ = IAC;
}
*p++ = IAC;
*p++ = SE;
/* queue the data to be sent */
write_data_len((const char *)reply, p-reply);
#if defined(AUTHTYPE_NAMES) && defined(AUTHWHO_STR) &&\
defined(AUTHHOW_NAMES) && defined(AUTHRSP_NAMES)
if (auth_debug) {
(void) fprintf(stderr, "SENT TELOPT_AUTHENTICATION REPLY "
"%s %s|%s %s\n",
AUTHTYPE_NAME(ap->AuthName),
AUTHWHO_NAME(ap->AuthHow & AUTH_WHO_MASK),
AUTHHOW_NAME(ap->AuthHow & AUTH_HOW_MASK),
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
rd_and_store_forwarded_creds(krb5_context context,
krb5_auth_context auth_context,
krb5_data *inbuf, krb5_ticket *ticket,
char *username)
{
krb5_creds **creds;
krb5_error_code retval;
char ccname[MAXCCACHENAMELEN];
krb5_ccache ccache = NULL;
if (retval = krb5_rd_cred(context, auth_context, inbuf, &creds, NULL))
return (retval);
(void) sprintf(ccname, "FILE:/tmp/krb5cc_p%ld", getpid());
(void) krb5_setenv("KRB5CCNAME", ccname, 1);
if ((retval = krb5_cc_default(context, &ccache)))
goto cleanup;
if ((retval = krb5_cc_initialize(context, ccache,
ticket->enc_part2->client)) != 0)
goto cleanup;
if ((retval = krb5_cc_store_cred(context, ccache, *creds)) != 0)
goto cleanup;
if ((retval = krb5_cc_close(context, ccache)) != 0)
goto cleanup;
if (username != NULL) {
/*
* 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
* /tmp/krb5cc_[uid] with correct ownership (0600 uid gid).
*
* NOTE: the user must be in the gsscred table in order to map
* from KRB5 to Unix.
*/
(void) krb5_kuserok(context, ticket->enc_part2->client,
username);
}
if (auth_debug)
(void) fprintf(stderr,
"Successfully stored forwarded creds\n");
cleanup:
krb5_free_creds(context, *creds);
return (retval);
}
static void
kerberos5_is(AuthInfo *ap, uchar_t *data, int cnt)
{
krb5_error_code err = 0;
krb5_principal server;
krb5_keyblock *newkey = NULL;
krb5_keytab keytabid = 0;
krb5_data outbuf;
krb5_data inbuf;
krb5_authenticator *authenticator;
char errbuf[MAXERRSTRLEN];
char *name;
krb5_data auth;
Session_Key skey;
if (cnt-- < 1)
return;
switch (*data++) {
case KRB_AUTH:
auth.data = (char *)data;
auth.length = cnt;
if (auth_context == NULL) {
err = krb5_auth_con_init(telnet_context, &auth_context);
if (err)
syslog(LOG_ERR,
"Error getting krb5 auth "
"context: %s", error_message(err));
}
if (!err) {
krb5_rcache rcache;
err = krb5_auth_con_getrcache(telnet_context,
auth_context,
&rcache);
if (!err && !rcache) {
err = krb5_sname_to_principal(telnet_context,
0, 0,
KRB5_NT_SRV_HST,
&server);
if (!err) {
err = krb5_get_server_rcache(
telnet_context,
krb5_princ_component(
telnet_context,
server, 0),
&rcache);
krb5_free_principal(telnet_context,
server);
}
}
if (err)
syslog(LOG_ERR,
"Error allocating krb5 replay cache: %s",
error_message(err));
else {
err = krb5_auth_con_setrcache(telnet_context,
auth_context,
rcache);
if (err)
syslog(LOG_ERR,
"Error creating krb5 "
"replay cache: %s",
error_message(err));
}
}
if (!err && telnet_srvtab != NULL)
err = krb5_kt_resolve(telnet_context,
telnet_srvtab, &keytabid);
if (!err)
err = krb5_rd_req(telnet_context, &auth_context, &auth,
NULL, keytabid, NULL, &ticket);
if (err) {
(void) snprintf(errbuf, sizeof (errbuf),
"Error reading krb5 auth information:"
" %s", error_message(err));
goto errout;
}
/*
* Verify that the correct principal was used
*/
if (krb5_princ_component(telnet_context,
ticket->server, 0)->length < MAXPRINCLEN) {
char princ[MAXPRINCLEN];
(void) strncpy(princ,
krb5_princ_component(telnet_context,
ticket->server, 0)->data,
krb5_princ_component(telnet_context,
ticket->server, 0)->length);
princ[krb5_princ_component(telnet_context,
ticket->server, 0)->length] = '\0';
if (strcmp("host", princ)) {
if (strlen(princ) < sizeof (errbuf) - 39) {
(void) snprintf(errbuf, sizeof (errbuf),
"incorrect service "
"name: \"%s\" != "
"\"host\"",
princ);
} else {
(void) strncpy(errbuf,
"incorrect service "
"name: principal != "
"\"host\"",
sizeof (errbuf));
}
goto errout;
}
} else {
(void) strlcpy(errbuf, "service name too long",
sizeof (errbuf));
goto errout;
}
err = krb5_auth_con_getauthenticator(telnet_context,
auth_context,
&authenticator);
if (err) {
(void) snprintf(errbuf, sizeof (errbuf),
"Failed to get authenticator: %s",
error_message(err));
goto errout;
}
if ((ap->AuthHow & AUTH_ENCRYPT_MASK) == AUTH_ENCRYPT_ON &&
!authenticator->checksum) {
(void) strlcpy(errbuf,
"authenticator is missing checksum",
sizeof (errbuf));
goto errout;
}
if (authenticator->checksum) {
char type_check[2];
krb5_checksum *cksum = authenticator->checksum;
krb5_keyblock *key;
krb5_data input;
krb5_boolean valid;
type_check[0] = ap->AuthName;
type_check[1] = ap->AuthHow;
err = krb5_auth_con_getkey(telnet_context,
auth_context, &key);
if (err) {
(void) snprintf(errbuf, sizeof (errbuf),
"Failed to get key from "
"authenticator: %s",
error_message(err));
goto errout;
}
input.data = type_check;
input.length = 2;
err = krb5_c_verify_checksum(telnet_context,
key, 0,
&input,
cksum,
&valid);
if (!err && !valid)
err = KRB5KRB_AP_ERR_BAD_INTEGRITY;
if (err) {
(void) snprintf(errbuf, sizeof (errbuf),
"Kerberos checksum "
"verification failed: "
"%s",
error_message(err));
goto errout;
}
krb5_free_keyblock(telnet_context, key);
}
krb5_free_authenticator(telnet_context, authenticator);
if ((ap->AuthHow & AUTH_HOW_MASK) == AUTH_HOW_MUTUAL) {
/* do ap_rep stuff here */
if ((err = krb5_mk_rep(telnet_context, auth_context,
&outbuf))) {
(void) snprintf(errbuf, sizeof (errbuf),
"Failed to make "
"Kerberos auth reply: "
"%s",
error_message(err));
goto errout;
}
reply_to_client(ap, KRB_RESPONSE, outbuf.data,
outbuf.length);
}
if (krb5_unparse_name(telnet_context,
ticket->enc_part2->client,
&name))
name = 0;
reply_to_client(ap, KRB_ACCEPT, name, name ? -1 : 0);
if (auth_debug) {
syslog(LOG_NOTICE,
"\tKerberos5 identifies user as ``%s''\r\n",
name ? name : "");
}
if (name != NULL) {
krb5_name = (char *)strdup(name);
}
auth_finished(ap, AUTH_USER);
if (name != NULL)
free(name);
krb5_auth_con_getremotesubkey(telnet_context, auth_context,
&newkey);
if (session_key != NULL) {
krb5_free_keyblock(telnet_context, session_key);
session_key = 0;
}
if (newkey != NULL) {
krb5_copy_keyblock(telnet_context,
newkey, &session_key);
krb5_free_keyblock(telnet_context, newkey);
} else {
krb5_copy_keyblock(telnet_context,
ticket->enc_part2->session,
&session_key);
}
/*
* Initialize encryption stuff. Currently, we are only
* supporting 8 byte keys and blocks. Check for this later.
*/
skey.type = SK_DES;
skey.length = DES_BLOCKSIZE;
skey.data = session_key->contents;
encrypt_session_key(&skey, &encr_data.encrypt);
encrypt_session_key(&skey, &encr_data.decrypt);
break;
case KRB_FORWARD:
inbuf.length = cnt;
inbuf.data = (char *)data;
if (auth_debug)
(void) fprintf(stderr,
"RCVD KRB_FORWARD data (%d bytes)\n", cnt);
if (auth_context != NULL) {
krb5_rcache rcache;
err = krb5_auth_con_getrcache(telnet_context,
auth_context, &rcache);
if (!err && !rcache) {
err = krb5_sname_to_principal(telnet_context,
0, 0, KRB5_NT_SRV_HST, &server);
if (!err) {
err = krb5_get_server_rcache(
telnet_context,
krb5_princ_component(
telnet_context,
server, 0),
&rcache);
krb5_free_principal(telnet_context,
server);
}
}
if (err) {
syslog(LOG_ERR,
"Error allocating krb5 replay cache: %s",
error_message(err));
} else {
err = krb5_auth_con_setrcache(telnet_context,
auth_context, rcache);
if (err)
syslog(LOG_ERR,
"Error creating krb5 replay cache:"
" %s",
error_message(err));
}
}
/*
* Use the 'rsaddr' and 'rsport' (remote service addr/port)
* from the original connection. This data is used to
* verify the forwarded credentials.
*/
if (!(err = krb5_auth_con_setaddrs(telnet_context, auth_context,
NULL, &rsaddr)))
err = krb5_auth_con_setports(telnet_context,
auth_context, NULL, &rsport);
if (err == 0)
/*
* If all is well, store the forwarded creds in
* the users local credential cache.
*/
err = rd_and_store_forwarded_creds(telnet_context,
auth_context, &inbuf,
ticket,
AuthenticatingUser);
if (err) {
(void) snprintf(errbuf, sizeof (errbuf),
"Read forwarded creds failed: %s",
error_message(err));
syslog(LOG_ERR, "%s", errbuf);
reply_to_client(ap, KRB_FORWARD_REJECT, errbuf, -1);
if (auth_debug)
(void) fprintf(stderr,
"\tCould not read "
"forwarded credentials\r\n");
} else
reply_to_client(ap, KRB_FORWARD_ACCEPT, (void *) 0, 0);
if (rsaddr.contents != NULL)
free(rsaddr.contents);
if (rsport.contents != NULL)
free(rsport.contents);
if (auth_debug)
(void) fprintf(stderr, "\tForwarded "
"credentials obtained\r\n");
break;
default:
if (auth_debug)
(void) fprintf(stderr,
"\tUnknown Kerberos option %d\r\n",
data[-1]);
reply_to_client(ap, KRB_REJECT, (void *) 0, 0);
break;
}
return;
errout:
reply_to_client(ap, KRB_REJECT, errbuf, -1);
if (auth_debug)
(void) fprintf(stderr, "\tKerberos V5 error: %s\r\n", errbuf);
syslog(LOG_ERR, "%s", errbuf);
if (auth_context != NULL) {
krb5_auth_con_free(telnet_context, auth_context);
auth_context = 0;
}
}
static int
krb5_init()
{
int code = 0;
if (telnet_context == NULL) {
code = krb5_init_context(&telnet_context);
if (code != 0 && auth_debug)
syslog(LOG_NOTICE,
"Cannot initialize Kerberos V5: %s",
error_message(code));
}
return (code);
}
static void
auth_name(uchar_t *data, int cnt)
{
char namebuf[MAXPRINCLEN];
if (cnt < 1) {
if (auth_debug)
(void) fprintf(stderr,
"\t(auth_name) Empty NAME in auth "
"reply\n");
return;
}
if (cnt > sizeof (namebuf)-1) {
if (auth_debug)
(void) fprintf(stderr,
"\t(auth_name) NAME exceeds %d bytes\n",
sizeof (namebuf)-1);
return;
}
(void) memcpy((void *)namebuf, (void *)data, cnt);
namebuf[cnt] = 0;
if (auth_debug)
(void) fprintf(stderr, "\t(auth_name) name [%s]\n", namebuf);
AuthenticatingUser = (char *)strdup(namebuf);
}
static void
auth_is(uchar_t *data, int cnt)
{
AuthInfo *aptr = auth_list;
if (cnt < 2)
return;
/*
* We failed to negoiate secure authentication
*/
if (data[0] == AUTHTYPE_NULL) {
auth_finished(0, AUTH_REJECT);
return;
}
while (aptr->AuthName != NULL &&
(aptr->AuthName != data[0] || aptr->AuthHow != data[1]))
aptr++;
if (aptr != NULL) {
if (auth_debug)
(void) fprintf(stderr, "\t(auth_is) auth type is %s "
"(%d bytes)\n", aptr->AuthString, cnt);
if (aptr->AuthName == AUTHTYPE_KERBEROS_V5)
kerberos5_is(aptr, data+2, cnt-2);
}
}
static int
krb5_user_status(char *name, int namelen, int level)
{
int retval = AUTH_USER;
if (auth_debug)
(void) fprintf(stderr, "\t(krb5_user_status) level = %d "
"auth_level = %d user = %s\n",
level, auth_level,
(AuthenticatingUser != NULL ? AuthenticatingUser : ""));
if (level < AUTH_USER)
return (level);
if (AuthenticatingUser != NULL &&
(retval = krb5_kuserok(telnet_context, ticket->enc_part2->client,
AuthenticatingUser))) {
(void) strncpy(name, AuthenticatingUser, namelen);
return (AUTH_VALID);
} else {
if (!retval)
syslog(LOG_ERR,
"Krb5 principal lacks permission to "
"access local account for %s",
AuthenticatingUser);
return (AUTH_USER);
}
}
/*
* Wrapper around /dev/urandom
*/
static int
getrandom(char *buf, int buflen)
{
static int devrandom = -1;
if (devrandom == -1 &&
(devrandom = open("/dev/urandom", O_RDONLY)) == -1) {
fatalperror(net, "Unable to open /dev/urandom: ",
errno);
return (-1);
}
if (read(devrandom, buf, buflen) == -1) {
fatalperror(net, "Unable to read from /dev/urandom: ",
errno);
return (-1);
}
return (0);
}
/*
* encrypt_init
*
* Initialize the encryption data structures
*/
static void
encrypt_init()
{
(void) memset(&encr_data.encrypt, 0, sizeof (cipher_info_t));
(void) memset(&encr_data.decrypt, 0, sizeof (cipher_info_t));
encr_data.encrypt.state = ENCR_STATE_NOT_READY;
encr_data.decrypt.state = ENCR_STATE_NOT_READY;
}
/*
* encrypt_send_request_start
*
* Request that the remote side automatically start sending
* encrypted output
*/
static void
encrypt_send_request_start()
{
uchar_t buf[6+TELNET_MAXKEYIDLEN], *p;
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.
*/
(void) memcpy(p, encr_data.decrypt.keyid, encr_data.decrypt.keyidlen);
p += encr_data.decrypt.keyidlen;
*p++ = IAC;
*p++ = SE;
write_data_len((const char *)buf, p-buf);
netflush();
if (enc_debug)
(void) fprintf(stderr,
"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
encrypt_is(uchar_t *data, int cnt)
{
register int type;
register int iv_status = CFB64_IV_OK;
register int lstate = 0;
uchar_t sbbuf[] = {
(uchar_t)IAC,
(uchar_t)SB,
(uchar_t)TELOPT_ENCRYPT,
(uchar_t)ENCRYPT_REPLY,
(uchar_t)0, /* placeholder: sbbuf[4] */
(uchar_t)CFB64_IV_OK, /* placeholder: sbbuf[5] */
(uchar_t)IAC,
(uchar_t)SE,
};
if (--cnt < 0)
return;
type = sbbuf[4] = *data++;
/*
* 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:
encr_data.decrypt.type = type;
lstate = encr_data.decrypt.state;
if (enc_debug)
(void) fprintf(stderr,
"\t(encrypt_is) initial state = %d\n",
lstate);
/*
* Before we extract the IV bytes, make sure we got
* enough data.
*/
if (cnt < sizeof (Block)) {
iv_status = CFB64_IV_BAD;
if (enc_debug)
(void) fprintf(stderr,
"\t(encrypt_is) Not enough "
"IV bytes\n");
lstate = ENCR_STATE_NOT_READY;
} else {
data++; /* skip over the CFB64_IV byte */
(void) memcpy(encr_data.decrypt.ivec, data,
sizeof (Block));
lstate = ENCR_STATE_IN_PROGRESS;
}
break;
case TELOPT_ENCTYPE_NULL:
encr_data.decrypt.type = type;
lstate &= ~ENCR_STATE_NO_RECV_IV;
lstate &= ~ENCR_STATE_NO_SEND_IV;
if (enc_debug)
(void) fprintf(stderr,
"\t(encrypt_is) We accept NULL encr\n");
break;
default:
iv_status = CFB64_IV_BAD;
encr_data.decrypt.type = NULL;
if (enc_debug)
(void) fprintf(stderr,
"\t(encrypt_is) Can't find type (%d) "
"for initial negotiation\r\n",
type);
lstate = ENCR_STATE_NOT_READY;
break;
}
sbbuf[5] = (uchar_t)iv_status; /* either CFB64_IV_OK or BAD */
if (iv_status == CFB64_IV_OK) {
/*
* send IV to crypto module and indicate it is for
* decrypt only
*/
lstate &= ~ENCR_STATE_NO_RECV_IV; /* we received an OK IV */
lstate &= ~ENCR_STATE_NO_SEND_IV; /* we dont send an IV */
} else {
/* tell crypto module to disable crypto on "read" stream */
lstate = ENCR_STATE_NOT_READY;
}
write_data_len((const char *)sbbuf, sizeof (sbbuf));
netflush();
#ifdef ENCRYPT_NAMES
if (enc_debug)
(void) fprintf(stderr,
"SENT TELOPT_ENCRYPT ENCRYPT_REPLY %s %s\n",
ENCTYPE_NAME(type),
(iv_status == CFB64_IV_OK ? "CFB64_IV_OK" :
"CFB64_IV_BAD"));
#endif /* ENCRYPT_NAMES */
/* Update the state of the decryption negotiation */
encr_data.decrypt.state = lstate;
if (lstate == ENCR_STATE_NOT_READY)
encr_data.decrypt.autoflag = 0;
else {
if (lstate == ENCR_STATE_OK && encr_data.decrypt.autoflag)
encrypt_send_request_start();
}
if (enc_debug)
(void) fprintf(stderr,
"\t(encrypt_is) final DECRYPT state = %d\n",
encr_data.decrypt.state);
}
/*
* encrypt_send_encrypt_is
*
* Tell the client what encryption we will use
* and what our IV will be.
*/
static int
encrypt_send_encrypt_is()
{
register int lstate;
krb5_error_code kret;
uchar_t sbbuf[MAXOPTLEN], *p;
int i;
lstate = encr_data.encrypt.state;
if (encr_data.encrypt.type == ENCTYPE_NULL) {
/*
* 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)
lstate = ENCR_STATE_IN_PROGRESS;
else if ((lstate & ENCR_STATE_NO_SEND_IV) == 0) {
if (enc_debug)
(void) fprintf(stderr,
"\t(encrypt_send_is) IV already sent,"
" state = %d\n", lstate);
return (lstate);
}
if (!VALIDKEY(encr_data.encrypt.krbdes_key)) {
/*
* Invalid key, set flag so we try again later
* when we get a good one
*/
encr_data.encrypt.need_start = 1;
if (enc_debug)
(void) fprintf(stderr,
"\t(encrypt_send_is) No Key, cannot "
"start encryption yet\n");
return (lstate);
}
if (enc_debug)
(void) fprintf(stderr,
"\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.
*/
kret = getrandom((char *)encr_data.encrypt.ivec, sizeof (Block));
if (kret) {
if (enc_debug)
(void) fprintf(stderr,
"\t(encrypt_send_is) error from "
"getrandom: %d\n", kret);
syslog(LOG_ERR, "Failed to create encryption key (err %d)\n");
encr_data.encrypt.type = ENCTYPE_NULL;
} else {
mit_des_fixup_key_parity(encr_data.encrypt.ivec);
}
p = sbbuf;
*p++ = IAC;
*p++ = SB;
*p++ = TELOPT_ENCRYPT;
*p++ = ENCRYPT_IS;
*p++ = encr_data.encrypt.type;
*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++)
if ((*p++ = encr_data.encrypt.ivec[i]) == IAC)
*p++ = IAC;
*p++ = IAC;
*p++ = SE;
write_data_len((const char *)sbbuf, (size_t)(p-sbbuf));
netflush();
if (!kret) {
lstate &= ~ENCR_STATE_NO_SEND_IV; /* we sent our IV */
lstate &= ~ENCR_STATE_NO_SEND_IV; /* dont need decrypt IV */
}
encr_data.encrypt.state = lstate;
if (enc_debug) {
int i;
(void) fprintf(stderr,
"SENT TELOPT_ENCRYPT ENCRYPT_IS %d CFB64_IV ",
encr_data.encrypt.type);
for (i = 0; i < (p-sbbuf); i++)
(void) fprintf(stderr, "%d ", (int)sbbuf[i]);
(void) fprintf(stderr, "\n");
}
return (lstate);
}
/*
* stop_stream
*
* Utility routine to send a CRIOCSTOP ioctl to the
* crypto module (cryptmod).
*/
static void
stop_stream(int fd, int dir)
{
struct strioctl crioc;
uint32_t stopdir = dir;
crioc.ic_cmd = CRYPTIOCSTOP;
crioc.ic_timout = -1;
crioc.ic_len = sizeof (stopdir);
crioc.ic_dp = (char *)&stopdir;
if (ioctl(fd, I_STR, &crioc)) {
syslog(LOG_ERR, "Error sending CRYPTIOCSTOP ioctl: %m");
}
}
/*
* 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
start_stream(int fd, int dir, int datalen, char *data)
{
struct strioctl crioc;
crioc.ic_cmd = (dir == CRYPT_ENCRYPT ? CRYPTIOCSTARTENC :
CRYPTIOCSTARTDEC);
crioc.ic_timout = -1;
crioc.ic_len = datalen;
crioc.ic_dp = data;
if (ioctl(fd, I_STR, &crioc)) {
syslog(LOG_ERR, "Error sending CRYPTIOCSTART ioctl: %m");
}
}
/*
* encrypt_start_output
*
* Tell the other side to start encrypting its data
*/
static void
encrypt_start_output()
{
int lstate;
uchar_t *p;
uchar_t sbbuf[MAXOPTLEN];
struct strioctl crioc;
struct cr_info_t cki;
/*
* Initialize crypto and send the ENCRYPT_IS msg
*/
lstate = encrypt_send_encrypt_is();
if (lstate != ENCR_STATE_OK) {
if (enc_debug)
(void) fprintf(stderr,
"\t(encrypt_start_output) ENCRYPT state "
"= %d\n", lstate);
return;
}
p = sbbuf;
*p++ = IAC;
*p++ = SB;
*p++ = TELOPT_ENCRYPT;
*p++ = ENCRYPT_START;
(void) memcpy(p, encr_data.encrypt.keyid, encr_data.encrypt.keyidlen);
p += encr_data.encrypt.keyidlen;
*p++ = IAC;
*p++ = SE;
/* Flush this data out before we start encrypting */
write_data_len((const char *)sbbuf, (int)(p-sbbuf));
netflush();
if (enc_debug)
(void) fprintf(stderr, "SENT TELOPT_ENCRYPT ENCRYPT_START %d "
"(lstate = %d) data waiting = %d\n",
(int)encr_data.encrypt.keyid[0],
lstate, nfrontp-nbackp);
encr_data.encrypt.state = lstate;
/*
* 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.
*/
cki.direction_mask = CRYPT_ENCRYPT;
if (encr_data.encrypt.type == TELOPT_ENCTYPE_DES_CFB64) {
cki.crypto_method = CRYPT_METHOD_DES_CFB;
} else {
if (enc_debug)
(void) fprintf(stderr,
"\t(encrypt_start_output) - unknown "
"crypto_method %d\n",
encr_data.encrypt.type);
syslog(LOG_ERR, "unrecognized crypto encrypt method: %d",
encr_data.encrypt.type);
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.
*/
if (encr_data.encrypt.setup == cki.crypto_method) {
cki.keylen = 0;
cki.iveclen = 0;
} else {
cki.keylen = DES_BLOCKSIZE;
(void) memcpy(cki.key, (void *)encr_data.encrypt.krbdes_key,
DES_BLOCKSIZE);
cki.iveclen = DES_BLOCKSIZE;
(void) memcpy(cki.ivec, (void *)encr_data.encrypt.ivec,
DES_BLOCKSIZE);
cki.ivec_usage = IVEC_ONETIME;
}
cki.option_mask = 0;
/* Stop encrypt side prior to setup so we dont lose data */
stop_stream(cryptmod_fd, CRYPT_ENCRYPT);
crioc.ic_cmd = CRYPTIOCSETUP;
crioc.ic_timout = -1;
crioc.ic_len = sizeof (struct cr_info_t);
crioc.ic_dp = (char *)&cki;
if (ioctl(cryptmod_fd, I_STR, &crioc)) {
perror("ioctl(CRYPTIOCSETUP) [encrypt_start_output] error");
} else {
/* Setup completed OK */
encr_data.encrypt.setup = cki.crypto_method;
}
/*
* 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.
*/
start_stream(cryptmod_fd, CRYPT_ENCRYPT, 0, NULL);
/*
* tell crypto module to start encrypting
*/
if (enc_debug)
(void) fprintf(stderr,
"\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 (encr_data.encrypt.type == ENCTYPE_NULL) {
encr_data.encrypt.autoflag = 1;
if (enc_debug)
(void) fprintf(stderr, "\t(encrypt_request_start) "
"autoencrypt = ON\n");
} else {
encrypt_start_output();
}
}
/*
* encrypt_end
*
* ENCRYPT END received, stop decrypting the read stream
*/
static void
encrypt_end(int direction)
{
struct cr_info_t cki;
struct strioctl crioc;
uint32_t stopdir;
stopdir = (direction == TELNET_DIR_DECRYPT ? CRYPT_DECRYPT :
CRYPT_ENCRYPT);
stop_stream(cryptmod_fd, stopdir);
/*
* Call this function when we wish to disable crypto in
* either direction (ENCRYPT or DECRYPT)
*/
cki.direction_mask = (direction == TELNET_DIR_DECRYPT ? CRYPT_DECRYPT :
CRYPT_ENCRYPT);
cki.crypto_method = CRYPT_METHOD_NONE;
cki.option_mask = 0;
cki.keylen = 0;
cki.iveclen = 0;
crioc.ic_cmd = CRYPTIOCSETUP;
crioc.ic_timout = -1;
crioc.ic_len = sizeof (cki);
crioc.ic_dp = (char *)&cki;
if (ioctl(cryptmod_fd, I_STR, &crioc)) {
perror("ioctl(CRYPTIOCSETUP) [encrypt_end] error");
}
start_stream(cryptmod_fd, stopdir, 0, NULL);
}
/*
* encrypt_request_end
*
* When we receive a REQEND from the client, it means
* that we are supposed to stop encrypting
*/
static void
encrypt_request_end()
{
/*
* Tell the other side we are done encrypting
*/
write_data("%c%c%c%c%c%c",
(uchar_t)IAC,
(uchar_t)SB,
(uchar_t)TELOPT_ENCRYPT,
(uchar_t)ENCRYPT_END,
(uchar_t)IAC,
(uchar_t)SE);
netflush();
if (enc_debug)
(void) fprintf(stderr, "SENT TELOPT_ENCRYPT ENCRYPT_END\n");
/*
* Turn off encryption of the write stream
*/
encrypt_end(TELNET_DIR_ENCRYPT);
}
/*
* encrypt_send_request_end
*
* We stop encrypting the write stream and tell the other side about it.
*/
static void
encrypt_send_request_end()
{
write_data("%c%c%c%c%c%c",
(uchar_t)IAC,
(uchar_t)SB,
(uchar_t)TELOPT_ENCRYPT,
(uchar_t)ENCRYPT_REQEND,
(uchar_t)IAC,
(uchar_t)SE);
netflush();
if (enc_debug)
(void) fprintf(stderr, "SENT TELOPT_ENCRYPT ENCRYPT_REQEND\n");
}
/*
* 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)
{
struct cr_info_t cki;
struct strioctl crioc;
int bytes = 0;
char *dataptr = NULL;
if (encr_data.decrypt.type == ENCTYPE_NULL) {
if (enc_debug)
(void) fprintf(stderr,
"\t(encrypt_start) No DECRYPT method "
"defined yet\n");
encrypt_send_request_end();
return;
}
cki.direction_mask = CRYPT_DECRYPT;
if (encr_data.decrypt.type == TELOPT_ENCTYPE_DES_CFB64) {
cki.crypto_method = CRYPT_METHOD_DES_CFB;
} else {
if (enc_debug)
(void) fprintf(stderr,
"\t(encrypt_start) - unknown "
"crypto_method %d\n", encr_data.decrypt.type);
syslog(LOG_ERR, "unrecognized crypto decrypt method: %d",
encr_data.decrypt.type);
return;
}
/*
* Don't overwrite previously configured key and ivec info
*/
if (encr_data.decrypt.setup != cki.crypto_method) {
(void) memcpy(cki.key, (void *)encr_data.decrypt.krbdes_key,
DES_BLOCKSIZE);
(void) memcpy(cki.ivec, (void *)encr_data.decrypt.ivec,
DES_BLOCKSIZE);
cki.keylen = DES_BLOCKSIZE;
cki.iveclen = DES_BLOCKSIZE;
cki.ivec_usage = IVEC_ONETIME;
} else {
cki.keylen = 0;
cki.iveclen = 0;
}
cki.option_mask = 0;
stop_stream(cryptmod_fd, CRYPT_DECRYPT);
crioc.ic_cmd = CRYPTIOCSETUP;
crioc.ic_timout = -1;
crioc.ic_len = sizeof (struct cr_info_t);
crioc.ic_dp = (char *)&cki;
if (ioctl(cryptmod_fd, I_STR, &crioc)) {
syslog(LOG_ERR, "ioctl(CRYPTIOCSETUP) [encrypt_start] "
"error: %m");
} else {
encr_data.decrypt.setup = cki.crypto_method;
}
if (enc_debug)
(void) fprintf(stderr,
"\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.
*/
if (ioctl(cryptmod_fd, I_NREAD, &bytes) < 0) {
syslog(LOG_ERR, "I_NREAD returned error %m");
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 (ncc || bytes) {
drainstream(bytes);
if (enc_debug)
(void) fprintf(stderr,
"\t(encrypt_start) after drainstream, "
"ncc=%d bytes = %d\n", ncc, bytes);
bytes += ncc;
dataptr = netip;
}
start_stream(cryptmod_fd, CRYPT_DECRYPT, bytes, dataptr);
/*
* The bytes putback into the stream are no longer
* available to be read by the server, so adjust the
* counter accordingly.
*/
ncc = 0;
netip = netibuf;
(void) memset(netip, 0, netibufsize);
#ifdef ENCRYPT_NAMES
if (enc_debug) {
(void) fprintf(stderr,
"\t(encrypt_start) Start DECRYPT using %s\n",
ENCTYPE_NAME(encr_data.decrypt.type));
}
#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
encrypt_support(char *data, int cnt)
{
int lstate = ENCR_STATE_NOT_READY;
int type, use_type = 0;
while (cnt-- > 0 && use_type == 0) {
type = *data++;
#ifdef ENCRYPT_NAMES
if (enc_debug)
(void) fprintf(stderr,
"RCVD ENCRYPT SUPPORT %s\n",
ENCTYPE_NAME(type));
#endif /* ENCRYPT_NAMES */
/*
* Prefer CFB64
*/
if (type == TELOPT_ENCTYPE_DES_CFB64) {
use_type = type;
}
}
encr_data.encrypt.type = use_type;
if (use_type != TELOPT_ENCTYPE_NULL &&
authenticated != NULL && authenticated != &NoAuth &&
auth_status != AUTH_REJECT) {
/* Authenticated -> have session key -> send ENCRYPT IS */
lstate = encrypt_send_encrypt_is();
if (lstate == ENCR_STATE_OK)
encrypt_start_output();
} else if (use_type == TELOPT_ENCTYPE_NULL) {
if (enc_debug)
(void) fprintf(stderr,
"\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",
(uchar_t)IAC,
(uchar_t)SB,
(uchar_t)TELOPT_ENCRYPT,
(uchar_t)ENCRYPT_IS,
(uchar_t)TELOPT_ENCTYPE_NULL,
(uchar_t)IAC,
(uchar_t)SE);
send_wont(TELOPT_ENCRYPT);
netflush();
if (enc_debug)
(void) fprintf(stderr,
"SENT TELOPT_ENCRYPT ENCRYPT_IS "
"[NULL]\n");
remopts[TELOPT_ENCRYPT] = OPT_NO;
}
settimer(encr_support);
}
/*
* encrypt_send_keyid
*
* Sent the key id we will use to the client
*/
static void
encrypt_send_keyid(int dir, uchar_t *keyid, int keylen, boolean_t saveit)
{
uchar_t sbbuf[128], *p;
p = sbbuf;
*p++ = IAC;
*p++ = SB;
*p++ = TELOPT_ENCRYPT;
*p++ = (dir == TELNET_DIR_ENCRYPT ? ENCRYPT_ENC_KEYID :
ENCRYPT_DEC_KEYID);
if (saveit) {
if (enc_debug)
(void) fprintf(stderr,
"\t(send_keyid) store %d byte %s keyid\n",
keylen,
(dir == TELNET_DIR_ENCRYPT ? "ENCRYPT" :
"DECRYPT"));
if (dir == TELNET_DIR_ENCRYPT) {
(void) memcpy(encr_data.encrypt.keyid, keyid, keylen);
encr_data.encrypt.keyidlen = keylen;
} else {
(void) memcpy(encr_data.decrypt.keyid, keyid, keylen);
encr_data.decrypt.keyidlen = keylen;
}
}
(void) memcpy(p, keyid, keylen);
p += keylen;
*p++ = IAC;
*p++ = SE;
write_data_len((const char *)sbbuf, (size_t)(p-sbbuf));
netflush();
if (enc_debug)
(void) fprintf(stderr, "SENT TELOPT_ENCRYPT %s %d\n",
(dir == TELNET_DIR_ENCRYPT ? "ENC_KEYID" :
"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
encrypt_reply(char *data, int len)
{
uchar_t type = (uchar_t)(*data++);
uchar_t result = (uchar_t)(*data);
int lstate;
#ifdef ENCRYPT_NAMES
if (enc_debug)
(void) fprintf(stderr,
"\t(encrypt_reply) ENCRYPT REPLY %s %s [len=%d]\n",
ENCRYPT_NAME(type),
(result == CFB64_IV_OK ? "CFB64_IV_OK" :
"CFB64_IV_BAD"), len);
#endif /* ENCRYPT_NAMES */
lstate = encr_data.encrypt.state;
if (enc_debug)
(void) fprintf(stderr,
"\t(encrypt_reply) initial ENCRYPT state = %d\n",
lstate);
switch (result) {
case CFB64_IV_OK:
if (lstate == ENCR_STATE_NOT_READY)
lstate = ENCR_STATE_IN_PROGRESS;
lstate &= ~ENCR_STATE_NO_RECV_IV; /* we got the IV */
lstate &= ~ENCR_STATE_NO_SEND_IV; /* we dont need to send IV */
/*
* 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.
*/
encrypt_send_keyid(TELNET_DIR_ENCRYPT, (uchar_t *)"\0", 1, 1);
break;
case CFB64_IV_BAD:
/*
* Clear the ivec
*/
(void) memset(encr_data.encrypt.ivec, 0, sizeof (Block));
lstate = ENCR_STATE_NOT_READY;
break;
default:
if (enc_debug)
(void) fprintf(stderr,
"\t(encrypt_reply) Got unknown IV value in "
"REPLY message\n");
lstate = ENCR_STATE_NOT_READY;
break;
}
encr_data.encrypt.state = lstate;
if (lstate == ENCR_STATE_NOT_READY) {
encr_data.encrypt.autoflag = 0;
encr_data.encrypt.type = ENCTYPE_NULL;
if (enc_debug)
(void) fprintf(stderr,
"\t(encrypt_reply) encrypt.autoflag = "
"OFF\n");
} else {
encr_data.encrypt.type = type;
if ((lstate == ENCR_STATE_OK) && encr_data.encrypt.autoflag)
encrypt_start_output();
}
if (enc_debug)
(void) fprintf(stderr,
"\t(encrypt_reply) ENCRYPT final state = %d\n",
lstate);
}
static void
encrypt_set_keyid_state(uchar_t *keyid, int *keyidlen, int dir)
{
int lstate;
lstate = (dir == TELNET_DIR_ENCRYPT ? encr_data.encrypt.state :
encr_data.decrypt.state);
if (enc_debug)
(void) fprintf(stderr,
"\t(set_keyid_state) %s initial state = %d\n",
(dir == TELNET_DIR_ENCRYPT ? "ENCRYPT" :
"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 (*keyidlen != 1 || (*keyid != '\0')) {
if (enc_debug)
(void) fprintf(stderr,
"\t(set_keyid_state) unexpected keyid: "
"len=%d value=%d\n", *keyidlen, *keyid);
*keyidlen = 0;
syslog(LOG_ERR, "rcvd unexpected keyid %d - only keyid of 0 "
"is supported", *keyid);
} else {
/*
* We move to the "IN_PROGRESS" state.
*/
if (lstate == ENCR_STATE_NOT_READY)
lstate = ENCR_STATE_IN_PROGRESS;
/*
* Clear the NO_KEYID bit because we now have a valid keyid
*/
lstate &= ~ENCR_STATE_NO_KEYID;
}
if (enc_debug)
(void) fprintf(stderr,
"\t(set_keyid_state) %s final state = %d\n",
(dir == TELNET_DIR_ENCRYPT ? "ENCRYPT" :
"DECRYPT"), lstate);
if (dir == TELNET_DIR_ENCRYPT)
encr_data.encrypt.state = lstate;
else
encr_data.decrypt.state = lstate;
}
/*
* encrypt_keyid
*
* Set the keyid value in the key_info structure.
* if necessary send a response to the sender
*/
static void
encrypt_keyid(uchar_t *newkeyid, int *keyidlen, uchar_t *keyid,
int len, int dir)
{
if (len > TELNET_MAXNUMKEYS) {
if (enc_debug)
(void) fprintf(stderr,
"\t(keyid) keylen too big (%d)\n", len);
return;
}
if (enc_debug) {
(void) fprintf(stderr, "\t(keyid) set KEYID for %s len = %d\n",
(dir == TELNET_DIR_ENCRYPT ? "ENCRYPT" :
"DECRYPT"), len);
}
if (len == 0) {
if (*keyidlen == 0) {
if (enc_debug)
(void) fprintf(stderr,
"\t(keyid) Got 0 length keyid - "
"failure\n");
return;
}
*keyidlen = 0;
encrypt_set_keyid_state(newkeyid, keyidlen, dir);
} else if (len != *keyidlen || memcmp(keyid, newkeyid, len)) {
if (enc_debug)
(void) fprintf(stderr,
"\t(keyid) Setting new key (%d bytes)\n",
len);
*keyidlen = len;
(void) memcpy(newkeyid, keyid, len);
encrypt_set_keyid_state(newkeyid, keyidlen, dir);
} else {
encrypt_set_keyid_state(newkeyid, keyidlen, dir);
if (enc_debug)
(void) fprintf(stderr,
"\t(keyid) %s Key already in place,"
"state = %d autoflag=%d\n",
(dir == TELNET_DIR_ENCRYPT ? "ENCRYPT" : "DECRYPT"),
(dir == TELNET_DIR_ENCRYPT ? encr_data.encrypt.state:
encr_data.decrypt.state),
(dir == TELNET_DIR_ENCRYPT ?
encr_data.encrypt.autoflag:
encr_data.decrypt.autoflag));
/* key already in place */
if ((encr_data.encrypt.state == ENCR_STATE_OK) &&
dir == TELNET_DIR_ENCRYPT && encr_data.encrypt.autoflag) {
encrypt_start_output();
}
return;
}
if (enc_debug)
(void) fprintf(stderr, "\t(keyid) %s final state = %d\n",
(dir == TELNET_DIR_ENCRYPT ? "ENCRYPT" :
"DECRYPT"),
(dir == TELNET_DIR_ENCRYPT ?
encr_data.encrypt.state :
encr_data.decrypt.state));
encrypt_send_keyid(dir, newkeyid, *keyidlen, 0);
}
/*
* 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
encrypt_enc_keyid(char *data, int cnt)
{
/*
* Verify the decrypt keyid is valid
*/
encrypt_keyid(encr_data.decrypt.keyid, &encr_data.decrypt.keyidlen,
(uchar_t *)data, cnt, TELNET_DIR_DECRYPT);
}
/*
* 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_dec_keyid(char *data, int cnt)
{
encrypt_keyid(encr_data.encrypt.keyid, &encr_data.encrypt.keyidlen,
(uchar_t *)data, cnt, TELNET_DIR_ENCRYPT);
}
/*
* encrypt_session_key
*
* Store the session key in the encryption data record
*/
static void
encrypt_session_key(Session_Key *key, cipher_info_t *cinfo)
{
if (key == NULL || key->type != SK_DES) {
if (enc_debug)
(void) fprintf(stderr,
"\t(session_key) Cannot set krb5 "
"session key (unknown type = %d)\n",
key ? key->type : -1);
}
if (enc_debug)
(void) fprintf(stderr,
"\t(session_key) Settting session key "
"for server\n");
/* store the key in the cipher info data struct */
(void) memcpy(cinfo->krbdes_key, (void *)key->data, sizeof (Block));
/*
* 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
new_env(const char *name, const char *value)
{
struct envlist *env;
env = malloc(sizeof (struct envlist));
if (env == NULL)
return (1);
if ((env->name = strdup(name)) == NULL) {
free(env);
return (1);
}
if ((env->value = strdup(value)) == NULL) {
free(env->name);
free(env);
return (1);
}
env->delete = 0;
env->next = envlist_head;
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
del_env(const char *name)
{
struct envlist *env;
for (env = envlist_head; env; env = env->next) {
if (strcmp(env->name, name) == 0) {
env->delete = 1;
break;
}
}
return (0);
}
static int
issock(int fd)
{
struct stat stats;
if (fstat(fd, &stats) == -1)
return (0);
return (S_ISSOCK(stats.st_mode));
}
/*
* 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) {
adt_session_data_t *ah;
adt_termid_t *termid;
int rc;
if ((rc = adt_start_session(&ah, NULL, 0)) == 0) {
if ((rc = adt_load_termid(sock, &termid)) == 0) {
if ((rc = adt_set_user(ah, ADT_NO_AUDIT,
ADT_NO_AUDIT, 0, ADT_NO_AUDIT,
termid, ADT_SETTID)) == 0)
(void) adt_set_proc(ah);
free(termid);
}
(void) adt_end_session(ah);
}
return (rc);
}
/* ARGSUSED */
int
main(int argc, char *argv[])
{
struct sockaddr_storage from;
int on = 1;
socklen_t fromlen;
int issocket;
#if defined(DEBUG)
ushort_t porttouse = 0;
boolean_t standalone = 0;
#endif /* defined(DEBUG) */
extern char *optarg;
char c;
int tos = -1;
while ((c = getopt(argc, argv, TELNETD_OPTS DEBUG_OPTS)) != -1) {
switch (c) {
#if defined(DEBUG)
case 'p':
/*
* note: alternative port number only used in
* standalone mode.
*/
porttouse = atoi(optarg);
standalone = 1;
break;
case 'e':
enc_debug = 1;
break;
#endif /* DEBUG */
case 'a':
if (strcasecmp(optarg, "none") == 0) {
auth_level = 0;
} else if (strcasecmp(optarg, "user") == 0) {
auth_level = AUTH_USER;
} else if (strcasecmp(optarg, "valid") == 0) {
auth_level = AUTH_VALID;
} else if (strcasecmp(optarg, "off") == 0) {
auth_level = -1;
negotiate_auth_krb5 = 0;
} else if (strcasecmp(optarg, "debug") == 0) {
auth_debug = 1;
} else {
syslog(LOG_ERR,
"unknown authentication level specified "
"with \'-a\' option (%s)", optarg);
auth_level = AUTH_USER;
}
break;
case 'X':
/* disable authentication negotiation */
negotiate_auth_krb5 = 0;
break;
case 'R':
case 'M':
if (optarg != NULL) {
int ret = krb5_init();
if (ret) {
syslog(LOG_ERR,
"Unable to use Kerberos V5 as "
"requested, exiting");
exit(1);
}
krb5_set_default_realm(telnet_context, optarg);
syslog(LOG_NOTICE,
"using %s as default KRB5 realm", optarg);
}
break;
case 'S':
telnet_srvtab = (char *)strdup(optarg);
break;
case 'E': /* disable automatic encryption */
negotiate_encrypt = B_FALSE;
break;
case 'U':
resolve_hostname = 1;
break;
case 's':
if (optarg == NULL || (tos = atoi(optarg)) < 0 ||
tos > 255) {
syslog(LOG_ERR, "telnetd: illegal tos value: "
"%s\n", optarg);
} else {
if (tos < 0)
tos = 020;
}
break;
case 'h':
show_hostinfo = 0;
break;
default:
syslog(LOG_ERR, "telnetd: illegal cmd line option %c",
c);
break;
}
}
netibufsize = BUFSIZ;
if (!(netibuf = (char *)malloc(netibufsize)))
syslog(LOG_ERR, "netibuf malloc failed\n");
(void) memset(netibuf, 0, netibufsize);
netip = netibuf;
#if defined(DEBUG)
if (standalone) {
int s, ns, foo;
struct servent *sp;
static struct sockaddr_in6 sin6 = { AF_INET6 };
int option = 1;
if (porttouse) {
sin6.sin6_port = htons(porttouse);
} else {
sp = getservbyname("telnet", "tcp");
if (sp == 0) {
(void) fprintf(stderr,
"telnetd: tcp/telnet: "
"unknown service\n");
exit(EXIT_FAILURE);
}
sin6.sin6_port = sp->s_port;
}
s = socket(AF_INET6, SOCK_STREAM, IPPROTO_TCP);
if (s < 0) {
perror("telnetd: socket");
exit(EXIT_FAILURE);
}
if (setsockopt(s, SOL_SOCKET, SO_REUSEADDR, (char *)&option,
sizeof (option)) == -1)
perror("setsockopt SO_REUSEADDR");
if (bind(s, (struct sockaddr *)&sin6, sizeof (sin6)) < 0) {
perror("bind");
exit(EXIT_FAILURE);
}
if (listen(s, 32) < 0) {
perror("listen");
exit(EXIT_FAILURE);
}
/* automatically reap all child processes */
(void) signal(SIGCHLD, SIG_IGN);
for (;;) {
pid_t pid;
foo = sizeof (sin6);
ns = accept(s, (struct sockaddr *)&sin6, &foo);
if (ns < 0) {
perror("accept");
exit(EXIT_FAILURE);
}
pid = fork();
if (pid == -1) {
perror("fork");
exit(EXIT_FAILURE);
}
if (pid == 0) {
(void) dup2(ns, 0);
(void) close(s);
(void) signal(SIGCHLD, SIG_DFL);
break;
}
(void) close(ns);
}
}
#endif /* defined(DEBUG) */
openlog("telnetd", LOG_PID | LOG_ODELAY, LOG_DAEMON);
issocket = issock(0);
if (!issocket)
fatal(0, "stdin is not a socket file descriptor");
fromlen = (socklen_t)sizeof (from);
(void) memset((char *)&from, 0, sizeof (from));
if (getpeername(0, (struct sockaddr *)&from, &fromlen)
< 0) {
(void) fprintf(stderr, "%s: ", argv[0]);
perror("getpeername");
_exit(EXIT_FAILURE);
}
if (audit_telnet_settid(0)) { /* set terminal ID */
(void) fprintf(stderr, "%s: ", argv[0]);
perror("audit");
exit(EXIT_FAILURE);
}
if (setsockopt(0, SOL_SOCKET, SO_KEEPALIVE, (const char *)&on,
sizeof (on)) < 0) {
syslog(LOG_WARNING, "setsockopt (SO_KEEPALIVE): %m");
}
/*
* Set the TOS value
*/
if (tos != -1 &&
setsockopt(0, IPPROTO_IP, IP_TOS,
(char *)&tos, sizeof (tos)) < 0 &&
errno != ENOPROTOOPT) {
syslog(LOG_ERR, "setsockopt (IP_TOS %d): %m", tos);
}
if (setsockopt(net, SOL_SOCKET, SO_OOBINLINE, (char *)&on,
sizeof (on)) < 0) {
syslog(LOG_WARNING, "setsockopt (SO_OOBINLINE): %m");
}
/* set the default PAM service name */
(void) strcpy(pam_svc_name, "telnet");
doit(0, &from);
/* 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)
{
if (nfrontp-nbackp) {
netflush();
}
read_again:
ncc = read(net, netibuf, netibufsize);
if (ncc < 0) {
if (errno == EINTR)
goto read_again;
syslog(LOG_INFO, "ttloop: read: %m");
exit(EXIT_FAILURE);
} else if (ncc == 0) {
syslog(LOG_INFO, "ttloop: peer closed connection\n");
exit(EXIT_FAILURE);
}
netip = netibuf;
telrcv(); /* state machine */
if (ncc > 0) {
pfrontp = pbackp = ptyobuf;
telrcv();
}
}
static void
send_do(int option)
{
write_data("%c%c%c", (uchar_t)IAC, (uchar_t)DO, (uchar_t)option);
}
static void
send_will(int option)
{
write_data("%c%c%c", (uchar_t)IAC, (uchar_t)WILL, (uchar_t)option);
}
static void
send_wont(int option)
{
write_data("%c%c%c", (uchar_t)IAC, (uchar_t)WONT, (uchar_t)option);
}
/*
* getauthtype
*
* Negotiate automatic authentication, is possible.
*/
static int
getauthtype(char *username, int *len)
{
int init_status = -1;
init_status = krb5_init();
if (auth_level == -1 || init_status != 0) {
remopts[TELOPT_AUTHENTICATION] = OPT_NO;
myopts[TELOPT_AUTHENTICATION] = OPT_NO;
negotiate_auth_krb5 = B_FALSE;
negotiate_encrypt = B_FALSE;
return (AUTH_REJECT);
}
if (init_status == 0 && auth_level != -1) {
if (negotiate_auth_krb5) {
/*
* Negotiate Authentication FIRST
*/
send_do(TELOPT_AUTHENTICATION);
remopts[TELOPT_AUTHENTICATION] =
OPT_YES_BUT_ALWAYS_LOOK;
}
while (sequenceIs(authopt, getauth))
ttloop();
if (remopts[TELOPT_AUTHENTICATION] == OPT_YES) {
/*
* Request KRB5 Mutual authentication and if that fails,
* KRB5 1-way client authentication
*/
uchar_t sbbuf[MAXOPTLEN], *p;
p = sbbuf;
*p++ = (uchar_t)IAC;
*p++ = (uchar_t)SB;
*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 |
AUTH_HOW_MUTUAL |
AUTH_ENCRYPT_ON);
*p++ = (uchar_t)AUTHTYPE_KERBEROS_V5;
*p++ = (uchar_t)(AUTH_WHO_CLIENT |
AUTH_HOW_MUTUAL);
*p++ = (uchar_t)AUTHTYPE_KERBEROS_V5;
*p++ = (uchar_t)(AUTH_WHO_CLIENT|
AUTH_HOW_ONE_WAY);
} else {
*p++ = (uchar_t)AUTHTYPE_NULL;
}
*p++ = (uchar_t)IAC;
*p++ = (uchar_t)SE;
write_data_len((const char *)sbbuf,
(size_t)(p - sbbuf));
netflush();
if (auth_debug)
(void) fprintf(stderr,
"SENT TELOPT_AUTHENTICATION "
"[data]\n");
/* auth_wait returns the authentication level */
/* status = auth_wait(username, len); */
while (sequenceIs(authdone, getauth))
ttloop();
/*
* Now check to see if the user is valid or not
*/
if (authenticated == NULL || authenticated == &NoAuth)
auth_status = AUTH_REJECT;
else {
/*
* We cant be VALID until the user status is
* checked.
*/
if (auth_status == AUTH_VALID)
auth_status = AUTH_USER;
if (authenticated->AuthName ==
AUTHTYPE_KERBEROS_V5)
auth_status = krb5_user_status(
username, *len, auth_status);
}
}
}
return (auth_status);
}
static void
getencrtype(void)
{
if (krb5_privacy_allowed() && negotiate_encrypt) {
if (myopts[TELOPT_ENCRYPT] != OPT_YES) {
if (!sent_will_encrypt) {
send_will(TELOPT_ENCRYPT);
sent_will_encrypt = B_TRUE;
}
if (enc_debug)
(void) fprintf(stderr, "SENT WILL ENCRYPT\n");
}
if (remopts[TELOPT_ENCRYPT] != OPT_YES) {
if (!sent_do_encrypt) {
send_do(TELOPT_ENCRYPT);
sent_do_encrypt = B_TRUE;
}
if (enc_debug)
(void) fprintf(stderr, "SENT DO ENCRYPT\n");
}
myopts[TELOPT_ENCRYPT] = OPT_YES;
while (sequenceIs(encropt, getencr))
ttloop();
if (auth_status != AUTH_REJECT &&
remopts[TELOPT_ENCRYPT] == OPT_YES &&
myopts[TELOPT_ENCRYPT] == OPT_YES) {
if (sent_encrypt_support == B_FALSE) {
write_data("%c%c%c%c%c%c%c",
(uchar_t)IAC,
(uchar_t)SB,
(uchar_t)TELOPT_ENCRYPT,
(uchar_t)ENCRYPT_SUPPORT,
(uchar_t)TELOPT_ENCTYPE_DES_CFB64,
(uchar_t)IAC,
(uchar_t)SE);
netflush();
}
/*
* Now wait for a response to these messages before
* continuing...
* Look for TELOPT_ENCRYPT suboptions
*/
while (sequenceIs(encr_support, getencr))
ttloop();
}
} else {
/* Dont need responses to these, so dont wait for them */
settimer(encropt);
remopts[TELOPT_ENCRYPT] = OPT_NO;
myopts[TELOPT_ENCRYPT] = OPT_NO;
}
}
/*
* 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.
*/
if (sequenceIs(ttypeopt, getterminal)) {
send_do(TELOPT_TTYPE);
remopts[TELOPT_TTYPE] = OPT_YES_BUT_ALWAYS_LOOK;
}
if (sequenceIs(nawsopt, getterminal)) {
send_do(TELOPT_NAWS);
remopts[TELOPT_NAWS] = OPT_YES_BUT_ALWAYS_LOOK;
}
if (sequenceIs(xdisplocopt, getterminal)) {
send_do(TELOPT_XDISPLOC);
remopts[TELOPT_XDISPLOC] = OPT_YES_BUT_ALWAYS_LOOK;
}
if (sequenceIs(environopt, getterminal)) {
send_do(TELOPT_NEW_ENVIRON);
remopts[TELOPT_NEW_ENVIRON] = OPT_YES_BUT_ALWAYS_LOOK;
}
if (sequenceIs(oenvironopt, getterminal)) {
send_do(TELOPT_OLD_ENVIRON);
remopts[TELOPT_OLD_ENVIRON] = OPT_YES_BUT_ALWAYS_LOOK;
}
/* make sure encryption is started here */
while (auth_status != AUTH_REJECT &&
authenticated != &NoAuth && authenticated != NULL &&
remopts[TELOPT_ENCRYPT] == OPT_YES &&
encr_data.encrypt.autoflag &&
encr_data.encrypt.state != ENCR_STATE_OK) {
if (enc_debug)
(void) fprintf(stderr, "getterminaltype() forcing encrypt\n");
ttloop();
}
if (enc_debug) {
(void) fprintf(stderr, "getterminaltype() encryption %sstarted\n",
encr_data.encrypt.state == ENCR_STATE_OK ? "" : "not ");
}
while (sequenceIs(ttypeopt, getterminal) ||
sequenceIs(nawsopt, getterminal) ||
sequenceIs(xdisplocopt, getterminal) ||
sequenceIs(environopt, getterminal) ||
sequenceIs(oenvironopt, getterminal)) {
ttloop();
}
if (remopts[TELOPT_TTYPE] == OPT_YES) {
static uchar_t sbbuf[] = { (uchar_t)IAC, (uchar_t)SB,
(uchar_t)TELOPT_TTYPE, (uchar_t)TELQUAL_SEND,
(uchar_t)IAC, (uchar_t)SE };
write_data_len((const char *)sbbuf, sizeof (sbbuf));
}
if (remopts[TELOPT_XDISPLOC] == OPT_YES) {
static uchar_t sbbuf[] = { (uchar_t)IAC, (uchar_t)SB,
(uchar_t)TELOPT_XDISPLOC, (uchar_t)TELQUAL_SEND,
(uchar_t)IAC, (uchar_t)SE };
write_data_len((const char *)sbbuf, sizeof (sbbuf));
}
if (remopts[TELOPT_NEW_ENVIRON] == OPT_YES) {
static uchar_t sbbuf[] = { (uchar_t)IAC, (uchar_t)SB,
(uchar_t)TELOPT_NEW_ENVIRON, (uchar_t)TELQUAL_SEND,
(uchar_t)IAC, (uchar_t)SE };
write_data_len((const char *)sbbuf, sizeof (sbbuf));
}
if (remopts[TELOPT_OLD_ENVIRON] == OPT_YES) {
static uchar_t sbbuf[] = { (uchar_t)IAC, (uchar_t)SB,
(uchar_t)TELOPT_OLD_ENVIRON, (uchar_t)TELQUAL_SEND,
(uchar_t)IAC, (uchar_t)SE };
write_data_len((const char *)sbbuf, sizeof (sbbuf));
}
if (remopts[TELOPT_TTYPE] == OPT_YES) {
while (sequenceIs(ttypesubopt, getterminal)) {
ttloop();
}
}
if (remopts[TELOPT_XDISPLOC] == OPT_YES) {
while (sequenceIs(xdisplocsubopt, getterminal)) {
ttloop();
}
}
if (remopts[TELOPT_NEW_ENVIRON] == OPT_YES) {
while (sequenceIs(environsubopt, getterminal)) {
ttloop();
}
}
if (remopts[TELOPT_OLD_ENVIRON] == OPT_YES) {
while (sequenceIs(oenvironsubopt, getterminal)) {
ttloop();
}
}
init_neg_done = 1;
}
pid_t pid;
/*
* Get a pty, scan input lines.
*/
static void
doit(int f, struct sockaddr_storage *who)
{
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 stat buf;
struct protocol_arg telnetp;
struct strioctl telnetmod;
struct envlist *env, *next;
int nsize = 0;
char abuf[INET6_ADDRSTRLEN];
struct sockaddr_in *sin;
struct sockaddr_in6 *sin6;
socklen_t wholen;
char username[MAXUSERNAMELEN];
int len;
uchar_t passthru;
char *slavename;
if ((p = open("/dev/ptmx", O_RDWR | O_NOCTTY)) == -1) {
fatalperror(f, "open /dev/ptmx", errno);
}
if (grantpt(p) == -1)
fatal(f, "could not grant slave pty");
if (unlockpt(p) == -1)
fatal(f, "could not unlock slave pty");
if ((slavename = ptsname(p)) == NULL)
fatal(f, "could not enable slave pty");
(void) dup2(f, 0);
if ((t = open(slavename, O_RDWR | O_NOCTTY)) == -1)
fatal(f, "could not open slave pty");
if (ioctl(t, I_PUSH, "ptem") == -1)
fatalperror(f, "ioctl I_PUSH ptem", errno);
if (ioctl(t, I_PUSH, "ldterm") == -1)
fatalperror(f, "ioctl I_PUSH ldterm", errno);
if (ioctl(t, I_PUSH, "ttcompat") == -1)
fatalperror(f, "ioctl I_PUSH ttcompat", errno);
line = slavename;
pty = t;
if (ioctl(t, TIOCGETP, &b) == -1)
syslog(LOG_INFO, "ioctl TIOCGETP pty t: %m\n");
b.sg_flags = O_CRMOD|O_XTABS|O_ANYP;
/* XXX - ispeed and ospeed must be non-zero */
b.sg_ispeed = B38400;
b.sg_ospeed = B38400;
if (ioctl(t, TIOCSETN, &b) == -1)
syslog(LOG_INFO, "ioctl TIOCSETN pty t: %m\n");
if (ioctl(pty, TIOCGETP, &b) == -1)
syslog(LOG_INFO, "ioctl TIOCGETP pty pty: %m\n");
b.sg_flags &= ~O_ECHO;
if (ioctl(pty, TIOCSETN, &b) == -1)
syslog(LOG_INFO, "ioctl TIOCSETN pty pty: %m\n");
if (who->ss_family == AF_INET) {
char *addrbuf = NULL;
char *portbuf = NULL;
sin = (struct sockaddr_in *)who;
wholen = sizeof (struct sockaddr_in);
addrbuf = (char *)malloc(wholen);
if (addrbuf == NULL)
fatal(f, "Cannot alloc memory for address info\n");
portbuf = (char *)malloc(sizeof (sin->sin_port));
if (portbuf == NULL) {
free(addrbuf);
fatal(f, "Cannot alloc memory for port info\n");
}
(void) memcpy(addrbuf, (const void *)&sin->sin_addr, wholen);
(void) memcpy(portbuf, (const void *)&sin->sin_port,
sizeof (sin->sin_port));
if (rsaddr.contents != NULL)
free(rsaddr.contents);
rsaddr.contents = (krb5_octet *)addrbuf;
rsaddr.length = wholen;
rsaddr.addrtype = ADDRTYPE_INET;
if (rsport.contents != NULL)
free(rsport.contents);
rsport.contents = (krb5_octet *)portbuf;
rsport.length = sizeof (sin->sin_port);
rsport.addrtype = ADDRTYPE_IPPORT;
} else if (who->ss_family == AF_INET6) {
struct in_addr ipv4_addr;
char *addrbuf = NULL;
char *portbuf = NULL;
sin6 = (struct sockaddr_in6 *)who;
wholen = sizeof (struct sockaddr_in6);
IN6_V4MAPPED_TO_INADDR(&sin6->sin6_addr,
&ipv4_addr);
addrbuf = (char *)malloc(wholen);
if (addrbuf == NULL)
fatal(f, "Cannot alloc memory for address info\n");
portbuf = (char *)malloc(sizeof (sin6->sin6_port));
if (portbuf == NULL) {
free(addrbuf);
fatal(f, "Cannot alloc memory for port info\n");
}
(void) memcpy((void *) addrbuf,
(const void *)&ipv4_addr,
wholen);
/*
* If we already used rsaddr.contents, free the previous
* buffer.
*/
if (rsaddr.contents != NULL)
free(rsaddr.contents);
rsaddr.contents = (krb5_octet *)addrbuf;
rsaddr.length = sizeof (ipv4_addr);
rsaddr.addrtype = ADDRTYPE_INET;
(void) memcpy((void *) portbuf, (const void *)&sin6->sin6_port,
sizeof (sin6->sin6_port));
if (rsport.contents != NULL)
free(rsport.contents);
rsport.contents = (krb5_octet *)portbuf;
rsport.length = sizeof (sin6->sin6_port);
rsport.addrtype = ADDRTYPE_IPPORT;
} else {
syslog(LOG_ERR, "unknown address family %d\n",
who->ss_family);
fatal(f, "getpeername: unknown address family\n");
}
if (getnameinfo((const struct sockaddr *) who, wholen, host_name,
sizeof (host_name), NULL, 0, 0) == 0) {
host = host_name;
} 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");
}
if (who->ss_family == AF_INET6) {
if (IN6_IS_ADDR_V4MAPPED(&sin6->sin6_addr)) {
struct in_addr ipv4_addr;
IN6_V4MAPPED_TO_INADDR(&sin6->sin6_addr,
&ipv4_addr);
host = (char *)inet_ntop(AF_INET,
&ipv4_addr, abuf, sizeof (abuf));
} else {
host = (char *)inet_ntop(AF_INET6,
&sin6->sin6_addr, abuf,
sizeof (abuf));
}
} else if (who->ss_family == AF_INET) {
host = (char *)inet_ntop(AF_INET,
&sin->sin_addr, abuf, 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)
fatalperror(f, "couldn't remove sockmod", errno);
encrypt_init();
/*
* Push the crypto module on the stream before 'telmod' so it
* can encrypt/decrypt without interfering with telmod functionality
* We must push it now because many of the crypto options negotiated
* initially must be saved in the crypto module (via IOCTL calls).
*/
if (ioctl(f, I_PUSH, "cryptmod") < 0)
fatalperror(f, "ioctl I_PUSH cryptmod", errno);
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
* auth, it will be out of whack with when the WILL/WONT ENCRYPT
* option is received.
*/
settimer(getencr);
/*
* get terminal type.
*/
username[0] = '\0';
len = sizeof (username);
settimer(getterminal);
settimer(getauth);
/*
* Exchange TELOPT_AUTHENTICATE options per RFC 2941/2942
*/
auth_status = getauthtype(username, &len);
/*
* Exchange TELOPT_ENCRYPT options per RFC 2946
*/
getencrtype();
getterminaltype();
if (ioctl(f, I_PUSH, "telmod") < 0)
fatalperror(f, "ioctl I_PUSH telmod", errno);
/*
* Make sure telmod will pass unrecognized IOCTLs to cryptmod
*/
passthru = 1;
telnetmod.ic_cmd = CRYPTPASSTHRU;
telnetmod.ic_timout = -1;
telnetmod.ic_len = sizeof (uchar_t);
telnetmod.ic_dp = (char *)&passthru;
if (ioctl(f, I_STR, &telnetmod) < 0)
fatal(f, "ioctl CRPASSTHRU failed\n");
if (!ncc)
netip = netibuf;
/*
* 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.
*/
while ((nsize = readstream(f, netibuf, ncc + netip - netibuf)) > 0) {
ncc += nsize;
}
if (nsize < 0) {
fatalperror(f, "readstream failed\n", errno);
}
/*
* open logindmux drivers and link them with network and ptm
* file descriptors.
*/
if ((ptmfd = open("/dev/logindmux", O_RDWR)) == -1) {
fatalperror(f, "open /dev/logindmux", errno);
}
if ((netfd = open("/dev/logindmux", O_RDWR)) == -1) {
fatalperror(f, "open /dev/logindmux", errno);
}
if (ioctl(ptmfd, I_LINK, p) < 0)
fatal(f, "ioctl I_LINK of /dev/ptmx failed\n");
if (ioctl(netfd, I_LINK, f) < 0)
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.
*/
if (fstat(ptmfd, &buf) < 0) {
fatalperror(f, "fstat ptmfd failed", errno);
}
telnetp.dev = buf.st_rdev;
telnetp.flag = 0;
telnetmod.ic_cmd = LOGDMX_IOC_QEXCHANGE;
telnetmod.ic_timout = -1;
telnetmod.ic_len = sizeof (struct protocol_arg);
telnetmod.ic_dp = (char *)&telnetp;
if (ioctl(netfd, I_STR, &telnetmod) < 0)
fatal(netfd, "ioctl LOGDMX_IOC_QEXCHANGE of netfd failed\n");
/*
* Figure out the device number of the net's mux fd, and pass that
* to the ptm's mux.
*/
if (fstat(netfd, &buf) < 0) {
fatalperror(f, "fstat netfd failed", errno);
}
telnetp.dev = buf.st_rdev;
telnetp.flag = 1;
telnetmod.ic_cmd = LOGDMX_IOC_QEXCHANGE;
telnetmod.ic_timout = -1;
telnetmod.ic_len = sizeof (struct protocol_arg);
telnetmod.ic_dp = (char *)&telnetp;
if (ioctl(ptmfd, I_STR, &telnetmod) < 0)
fatal(netfd, "ioctl LOGDMX_IOC_QEXCHANGE of ptmfd failed\n");
net = netfd;
master = ptmfd;
cryptmod_fd = netfd;
/*
* Show banner that getty never gave, but
* only if the user did not automatically authenticate.
*/
if (getenv("USER") == '\0' && auth_status < AUTH_USER)
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
* same PAM service that /bin/login will use to authenticate
* this session.
*/
if (auth_level >= 0 && auth_status >= AUTH_USER &&
(AuthenticatingUser != NULL) && strlen(AuthenticatingUser)) {
(void) strcpy(pam_svc_name, "ktelnet");
}
/*
* 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]) {
dooption(TELOPT_SGA);
}
/*
* Pretend we got a DO ECHO from the client if we have not
* yet negotiated the ECHO.
*/
if (!myopts[TELOPT_ECHO]) {
dooption(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).
*/
send_do(TELOPT_ECHO);
remopts[TELOPT_ECHO] = OPT_YES_BUT_ALWAYS_LOOK;
if ((pid = fork()) < 0)
fatalperror(netfd, "fork", errno);
if (pid)
telnet(net, master);
/*
* 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();
tt = open(line, O_RDWR);
if (tt < 0)
fatalperror(netfd, line, errno);
(void) close(netfd);
(void) close(ptmfd);
(void) close(f);
(void) close(p);
(void) close(t);
if (tt != 0)
(void) dup2(tt, 0);
if (tt != 1)
(void) dup2(tt, 1);
if (tt != 2)
(void) dup2(tt, 2);
if (tt > 2)
(void) close(tt);
if (terminaltype)
(void) local_setenv("TERM", terminaltype+5, 1);
/*
* -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 */
struct utmpx ut;
(void) memset((char *)&ut, 0, sizeof (ut));
(void) strncpy(ut.ut_user, ".telnet", sizeof (ut.ut_user));
(void) strncpy(ut.ut_line, line, sizeof (ut.ut_line));
ut.ut_pid = getpid();
ut.ut_id[0] = 't';
ut.ut_id[1] = (char)SC_WILDC;
ut.ut_id[2] = (char)SC_WILDC;
ut.ut_id[3] = (char)SC_WILDC;
ut.ut_type = LOGIN_PROCESS;
ut.ut_exit.e_termination = 0;
ut.ut_exit.e_exit = 0;
(void) time(&ut.ut_tv.tv_sec);
if (makeutx(&ut) == NULL)
syslog(LOG_INFO, "in.telnetd:\tmakeutx failed");
}
/*
* Load in the cached environment variables and either
* set/unset them in the environment.
*/
for (next = envlist_head; next; ) {
env = next;
if (env->delete)
(void) local_unsetenv(env->name);
else
(void) local_setenv(env->name, env->value, 1);
free(env->name);
free(env->value);
next = env->next;
free(env);
}
if (!username || !username[0])
auth_status = AUTH_REJECT; /* we dont know who this is */
/* If the current auth status is less than the required level, exit */
if (auth_status < auth_level) {
fatal(net, "Authentication failed\n");
exit(EXIT_FAILURE);
}
/*
* If AUTH_VALID (proper authentication REQUIRED and we have
* a krb5_name), exec '/bin/login', make sure it uses the
* 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 &&
(auth_status == AUTH_VALID || auth_status == AUTH_USER) &&
((krb5_name != NULL) && strlen(krb5_name)) &&
((AuthenticatingUser != NULL) && strlen(AuthenticatingUser))) {
(void) execl(LOGIN_PROGRAM, "login",
"-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 &&
(((AuthenticatingUser != NULL) && strlen(AuthenticatingUser)) ||
getenv("USER"))) {
/*
* If we only know the name but not the principal,
* login will have to authenticate further.
*/
(void) execl(LOGIN_PROGRAM, "login",
"-p",
"-d", slavename,
"-h", host,
"-s", pam_svc_name,
(AuthenticatingUser != NULL ? AuthenticatingUser :
getenv("USER")),
0);
} else /* default, no auth. info available, login does it all */ {
(void) execl(LOGIN_PROGRAM, "login",
"-p", "-h", host, "-d", slavename,
getenv("USER"), 0);
}
fatalperror(netfd, LOGIN_PROGRAM, errno);
/*NOTREACHED*/
}
static void
fatal(int f, char *msg)
{
char buf[BUFSIZ];
(void) snprintf(buf, sizeof (buf), "telnetd: %s.\r\n", msg);
(void) write(f, buf, strlen(buf));
exit(EXIT_FAILURE);
/*NOTREACHED*/
}
static void
fatalperror(int f, char *msg, int errnum)
{
char buf[BUFSIZ];
(void) snprintf(buf, sizeof (buf),
"%s: %s\r\n", msg, strerror(errnum));
fatal(f, buf);
/*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
telnet(int net, int master)
{
int on = 1;
char mode;
struct strioctl telnetmod;
int nsize = 0;
char binary_in = 0;
char binary_out = 0;
if (ioctl(net, FIONBIO, &on) == -1)
syslog(LOG_INFO, "ioctl FIONBIO net: %m\n");
if (ioctl(master, FIONBIO, &on) == -1)
syslog(LOG_INFO, "ioctl FIONBIO pty p: %m\n");
(void) signal(SIGTSTP, SIG_IGN);
(void) signal(SIGCHLD, (void (*)())cleanup);
(void) setpgrp();
/*
* Call telrcv() once to pick up anything received during
* terminal type negotiation.
*/
telrcv();
netflush();
ptyflush();
for (;;) {
fd_set ibits, obits, xbits;
int c;
if (ncc < 0)
break;
FD_ZERO(&ibits);
FD_ZERO(&obits);
FD_ZERO(&xbits);
/*
* If we couldn't flush all our output to the network,
* keep checking for when we can.
*/
if (nfrontp - nbackp)
FD_SET(net, &obits);
/*
* Never look for input if there's still
* stuff in the corresponding output buffer
*/
if (pfrontp - pbackp) {
FD_SET(master, &obits);
} else {
FD_SET(net, &ibits);
}
if (!SYNCHing) {
FD_SET(net, &xbits);
}
#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.
*/
if (binary_in != myopts[TELOPT_BINARY] ||
binary_out != remopts[TELOPT_BINARY]) {
mode = 0;
if (myopts[TELOPT_BINARY] != OPT_NO)
mode |= TEL_BINARY_IN;
if (remopts[TELOPT_BINARY] != OPT_NO)
mode |= TEL_BINARY_OUT;
telnetmod.ic_cmd = TEL_IOC_MODE;
telnetmod.ic_timout = -1;
telnetmod.ic_len = 1;
telnetmod.ic_dp = &mode;
syslog(LOG_DEBUG, "TEL_IOC_MODE binary has changed\n");
if (ioctl(net, I_STR, &telnetmod) < 0)
fatal(net, "ioctl TEL_IOC_MODE failed\n");
binary_in = myopts[TELOPT_BINARY];
binary_out = remopts[TELOPT_BINARY];
}
if (state == TS_DATA) {
if ((nfrontp == nbackp) &&
(pfrontp == pbackp)) {
if (ioctl(net, I_NREAD, &nsize) < 0)
fatalperror(net,
"ioctl I_NREAD failed\n", errno);
if (nsize)
drainstream(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
* protocol/urgent data to telnetd.
*/
telnetmod.ic_cmd = TEL_IOC_ENABLE;
telnetmod.ic_timout = -1;
if (ncc || nsize) {
telnetmod.ic_len = ncc + nsize;
telnetmod.ic_dp = netip;
} else {
telnetmod.ic_len = 0;
telnetmod.ic_dp = NULL;
}
if (ioctl(net, I_STR, &telnetmod) < 0)
fatal(net, "ioctl TEL_IOC_ENABLE \
failed\n");
telmod_init_done = B_TRUE;
netip = netibuf;
(void) memset(netibuf, 0, netibufsize);
ncc = 0;
}
} else {
/*
* state not changed to TS_DATA and hence, more to read
* send ioctl to get one more message block.
*/
telnetmod.ic_cmd = TEL_IOC_GETBLK;
telnetmod.ic_timout = -1;
telnetmod.ic_len = 0;
telnetmod.ic_dp = NULL;
if (ioctl(net, I_STR, &telnetmod) < 0)
fatal(net, "ioctl TEL_IOC_GETBLK failed\n");
}
if ((c = select(max(net, master) + 1, &ibits, &obits, &xbits,
(struct timeval *)0)) < 1) {
if (c == -1) {
if (errno == EINTR) {
continue;
}
}
(void) sleep(5);
continue;
}
/*
* Any urgent data?
*/
if (FD_ISSET(net, &xbits)) {
SYNCHing = 1;
}
/*
* Something to read from the network...
*/
if (FD_ISSET(net, &ibits)) {
ncc = read(net, netibuf, netibufsize);
if (ncc < 0 && errno == EWOULDBLOCK)
ncc = 0;
else {
if (ncc <= 0) {
break;
}
netip = netibuf;
}
}
if (FD_ISSET(net, &obits) && (nfrontp - nbackp) > 0)
netflush();
if (ncc > 0)
telrcv();
if (FD_ISSET(master, &obits) && (pfrontp - pbackp) > 0)
ptyflush();
}
cleanup(0);
}
static void
telrcv(void)
{
int c;
while (ncc > 0) {
if ((&ptyobuf[BUFSIZ] - pfrontp) < 2)
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.
*/
if (c != IAC && state == TS_DATA && init_neg_done) {
break;
}
netip++;
ncc--;
switch (state) {
case TS_CR:
state = TS_DATA;
/* Strip off \n or \0 after a \r */
if ((c == 0) || (c == '\n')) {
break;
}
/* FALLTHRU */
case TS_DATA:
if (c == IAC) {
state = TS_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.
*/
if (c == '\r' && (myopts[TELOPT_BINARY] == OPT_NO)) {
state = TS_CR;
}
*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:
write_data_len("\r\n[Yes]\r\n", 9);
break;
/*
* Abort Output
*/
case AO: {
struct ltchars tmpltc;
ptyflush(); /* half-hearted */
if (ioctl(pty, TIOCGLTC, &tmpltc) == -1)
syslog(LOG_INFO,
"ioctl TIOCGLTC: %m\n");
if (tmpltc.t_flushc != '\377') {
*pfrontp++ = tmpltc.t_flushc;
}
netclear(); /* clear buffer back */
write_data("%c%c", (uchar_t)IAC,
(uchar_t)DM);
neturg = nfrontp-1; /* off by one XXX */
netflush();
netflush(); /* XXX.sparker */
break;
}
/*
* Erase Character and
* Erase Line
*/
case EC:
case EL: {
struct sgttyb b;
char ch;
ptyflush(); /* half-hearted */
if (ioctl(pty, TIOCGETP, &b) == -1)
syslog(LOG_INFO,
"ioctl TIOCGETP: %m\n");
ch = (c == EC) ?
b.sg_erase : b.sg_kill;
if (ch != '\377') {
*pfrontp++ = ch;
}
break;
}
/*
* Check for urgent data...
*/
case DM:
break;
/*
* Begin option subnegotiation...
*/
case SB:
state = TS_SB;
SB_CLEAR();
continue;
case WILL:
state = TS_WILL;
continue;
case WONT:
state = TS_WONT;
continue;
case DO:
state = TS_DO;
continue;
case DONT:
state = TS_DONT;
continue;
case IAC:
*pfrontp++ = c;
break;
}
state = TS_DATA;
break;
case TS_SB:
if (c == IAC) {
state = TS_SE;
} else {
SB_ACCUM(c);
}
break;
case TS_SE:
if (c != SE) {
if (c != IAC) {
SB_ACCUM((uchar_t)IAC);
}
SB_ACCUM(c);
state = TS_SB;
} else {
SB_TERM();
suboption(); /* handle sub-option */
state = TS_DATA;
}
break;
case TS_WILL:
if (remopts[c] != OPT_YES)
willoption(c);
state = TS_DATA;
continue;
case TS_WONT:
if (remopts[c] != OPT_NO)
wontoption(c);
state = TS_DATA;
continue;
case TS_DO:
if (myopts[c] != OPT_YES)
dooption(c);
state = TS_DATA;
continue;
case TS_DONT:
if (myopts[c] != OPT_NO) {
dontoption(c);
}
state = TS_DATA;
continue;
default:
syslog(LOG_ERR, "telnetd: panic state=%d\n", state);
(void) printf("telnetd: panic state=%d\n", state);
exit(EXIT_FAILURE);
}
}
}
static void
willoption(int option)
{
uchar_t *fmt;
boolean_t send_reply = B_TRUE;
switch (option) {
case TELOPT_BINARY:
mode(O_RAW, 0);
fmt = doopt;
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!
*/
if (myopts[TELOPT_ECHO] == OPT_YES) {
dooption(TELOPT_ECHO);
}
fmt = dont;
break;
case TELOPT_TTYPE:
settimer(ttypeopt);
goto common;
case TELOPT_NAWS:
settimer(nawsopt);
goto common;
case TELOPT_XDISPLOC:
settimer(xdisplocopt);
goto common;
case TELOPT_NEW_ENVIRON:
settimer(environopt);
goto common;
case TELOPT_AUTHENTICATION:
settimer(authopt);
if (remopts[option] == OPT_NO ||
negotiate_auth_krb5 == 0)
fmt = dont;
else
fmt = doopt;
break;
case TELOPT_OLD_ENVIRON:
settimer(oenvironopt);
goto common;
common:
if (remopts[option] == OPT_YES_BUT_ALWAYS_LOOK) {
remopts[option] = OPT_YES;
return;
}
/*FALLTHRU*/
case TELOPT_SGA:
fmt = doopt;
break;
case TELOPT_TM:
fmt = dont;
break;
case TELOPT_ENCRYPT:
settimer(encropt); /* got response to do/dont */
if (enc_debug)
(void) fprintf(stderr,
"RCVD IAC WILL TELOPT_ENCRYPT\n");
if (krb5_privacy_allowed()) {
fmt = doopt;
if (sent_do_encrypt)
send_reply = B_FALSE;
else
sent_do_encrypt = B_TRUE;
} else {
fmt = dont;
}
break;
default:
fmt = dont;
break;
}
if (fmt == doopt) {
remopts[option] = OPT_YES;
} else {
remopts[option] = OPT_NO;
}
if (send_reply) {
write_data((const char *)fmt, option);
netflush();
}
}
static void
wontoption(int option)
{
uchar_t *fmt;
int send_reply = 1;
switch (option) {
case TELOPT_ECHO:
not42 = 1; /* doesn't seem to be a 4.2 system */
break;
case TELOPT_BINARY:
mode(0, O_RAW);
break;
case TELOPT_TTYPE:
settimer(ttypeopt);
break;
case TELOPT_NAWS:
settimer(nawsopt);
break;
case TELOPT_XDISPLOC:
settimer(xdisplocopt);
break;
case TELOPT_NEW_ENVIRON:
settimer(environopt);
break;
case TELOPT_OLD_ENVIRON:
settimer(oenvironopt);
break;
case TELOPT_AUTHENTICATION:
settimer(authopt);
auth_finished(0, AUTH_REJECT);
if (auth_debug)
(void) fprintf(stderr,
"RCVD WONT TELOPT_AUTHENTICATE\n");
remopts[option] = OPT_NO;
send_reply = 0;
break;
case TELOPT_ENCRYPT:
if (enc_debug)
(void) fprintf(stderr,
"RCVD IAC WONT TELOPT_ENCRYPT\n");
settimer(encropt); /* got response to will/wont */
/*
* 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.
*/
encrypt_end(TELNET_DIR_DECRYPT);
send_reply = 0;
break;
}
fmt = dont;
remopts[option] = OPT_NO;
if (send_reply) {
write_data((const char *)fmt, option);
}
}
/*
* We received an "IAC DO ..." message from the client, change our state
* to OPT_YES.
*/
static void
dooption(int option)
{
uchar_t *fmt;
boolean_t send_reply = B_TRUE;
switch (option) {
case TELOPT_TM:
fmt = wont;
break;
case TELOPT_ECHO:
mode(O_ECHO|O_CRMOD, 0);
fmt = will;
break;
case TELOPT_BINARY:
mode(O_RAW, 0);
fmt = will;
break;
case TELOPT_SGA:
fmt = will;
break;
case TELOPT_LOGOUT:
/*
* Options don't get much easier. Acknowledge the option,
* and then clean up and exit.
*/
write_data((const char *)will, option);
netflush();
cleanup(0);
/*NOTREACHED*/
case TELOPT_ENCRYPT:
if (enc_debug)
(void) fprintf(stderr, "RCVD DO TELOPT_ENCRYPT\n");
settimer(encropt);
/*
* 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) {
fmt = will;
if (sent_will_encrypt)
send_reply = B_FALSE;
else
sent_will_encrypt = B_TRUE;
/* return if we already sent "WILL ENCRYPT" */
if (myopts[option] == OPT_YES)
return;
} else {
fmt = wont;
}
break;
case TELOPT_AUTHENTICATION:
if (auth_debug) {
(void) fprintf(stderr,
"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...
*/
fmt = wont;
break;
default:
fmt = wont;
break;
}
if (fmt == will) {
myopts[option] = OPT_YES;
} else {
myopts[option] = OPT_NO;
}
if (send_reply) {
write_data((const char *)fmt, option);
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.
*/
mode(0, O_ECHO);
break;
case TELOPT_ENCRYPT:
if (enc_debug)
(void) fprintf(stderr, "RCVD IAC DONT ENCRYPT\n");
settimer(encropt);
/*
* Remote side cannot receive any encrypted data,
* so dont send any. No reply necessary.
*/
send_reply = 0;
break;
default:
break;
}
myopts[option] = OPT_NO;
if (send_reply) {
write_data((const char *)wont, option);
}
}
/*
* suboption()
*
* Look at the sub-option buffer, and try to be helpful to the other
* side.
*
*/
static void
suboption(void)
{
int subchar;
switch (subchar = SB_GET()) {
case TELOPT_TTYPE: { /* Yaaaay! */
static char terminalname[5+41] = "TERM=";
settimer(ttypesubopt);
if (SB_GET() != TELQUAL_IS) {
return; /* ??? XXX but, this is the most robust */
}
terminaltype = terminalname+strlen(terminalname);
while (terminaltype < (terminalname + sizeof (terminalname) -
1) && !SB_EOF()) {
int c;
c = SB_GET();
if (isupper(c)) {
c = tolower(c);
}
*terminaltype++ = c; /* accumulate name */
}
*terminaltype = 0;
terminaltype = terminalname;
break;
}
case TELOPT_NAWS: {
struct winsize ws;
if (SB_EOF()) {
return;
}
ws.ws_col = SB_GET() << 8;
if (SB_EOF()) {
return;
}
ws.ws_col |= SB_GET();
if (SB_EOF()) {
return;
}
ws.ws_row = SB_GET() << 8;
if (SB_EOF()) {
return;
}
ws.ws_row |= SB_GET();
ws.ws_xpixel = 0; ws.ws_ypixel = 0;
(void) ioctl(pty, TIOCSWINSZ, &ws);
settimer(nawsopt);
break;
}
case TELOPT_XDISPLOC: {
if (SB_EOF() || SB_GET() != TELQUAL_IS) {
return;
}
settimer(xdisplocsubopt);
subpointer[SB_LEN()] = '\0';
if ((new_env("DISPLAY", subpointer)) == 1)
perror("malloc");
break;
}
case TELOPT_NEW_ENVIRON:
case TELOPT_OLD_ENVIRON: {
int c;
char *cp, *varp, *valp;
if (SB_EOF())
return;
c = SB_GET();
if (c == TELQUAL_IS) {
if (subchar == TELOPT_OLD_ENVIRON)
settimer(oenvironsubopt);
else
settimer(environsubopt);
} 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;
cp = varp = (char *)subpointer;
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';
cp = valp = (char *)subpointer;
break;
case NEW_ENV_VAR:
case ENV_USERVAR:
*cp = '\0';
if (valp) {
if ((new_env(varp, valp)) == 1) {
perror("malloc");
}
} else {
(void) del_env(varp);
}
cp = varp = (char *)subpointer;
valp = 0;
break;
case ENV_ESC:
if (SB_EOF())
break;
c = SB_GET();
/* FALL THROUGH */
default:
*cp++ = c;
break;
}
}
*cp = '\0';
if (valp) {
if ((new_env(varp, valp)) == 1) {
perror("malloc");
}
} else {
(void) del_env(varp);
}
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)
(void) fprintf(stderr,
"RCVD AUTHENTICATION IS "
"(%d bytes)\n",
SB_LEN());
if (!auth_negotiated)
auth_is((uchar_t *)subpointer, SB_LEN());
break;
case TELQUAL_NAME:
if (auth_debug)
(void) fprintf(stderr,
"RCVD AUTHENTICATION NAME "
"(%d bytes)\n",
SB_LEN());
if (!auth_negotiated)
auth_name((uchar_t *)subpointer, SB_LEN());
break;
}
break;
case TELOPT_ENCRYPT: {
int c;
if (SB_EOF())
break;
c = SB_GET();
#ifdef ENCRYPT_NAMES
if (enc_debug)
(void) fprintf(stderr, "RCVD ENCRYPT %s\n",
ENCRYPT_NAME(c));
#endif /* ENCRYPT_NAMES */
switch (c) {
case ENCRYPT_SUPPORT:
encrypt_support(subpointer, SB_LEN());
break;
case ENCRYPT_IS:
encrypt_is((uchar_t *)subpointer, SB_LEN());
break;
case ENCRYPT_REPLY:
(void) encrypt_reply(subpointer, SB_LEN());
break;
case ENCRYPT_START:
encrypt_start();
break;
case ENCRYPT_END:
encrypt_end(TELNET_DIR_DECRYPT);
break;
case ENCRYPT_REQSTART:
encrypt_request_start();
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.
*/
encrypt_request_end();
break;
case ENCRYPT_ENC_KEYID:
encrypt_enc_keyid(subpointer, SB_LEN());
break;
case ENCRYPT_DEC_KEYID:
encrypt_dec_keyid(subpointer, SB_LEN());
break;
default:
break;
}
}
break;
default:
break;
}
}
static void
mode(int on, int off)
{
struct termios tios;
ptyflush();
if (tcgetattr(pty, &tios) < 0)
syslog(LOG_INFO, "tcgetattr: %m\n");
if (on & O_RAW) {
tios.c_cflag |= CS8;
tios.c_iflag &= ~IUCLC;
tios.c_lflag &= ~(XCASE|IEXTEN);
}
if (off & O_RAW) {
if ((tios.c_cflag & PARENB) != 0)
tios.c_cflag &= ~CS8;
tios.c_lflag |= IEXTEN;
}
if (on & O_ECHO)
tios.c_lflag |= ECHO;
if (off & O_ECHO)
tios.c_lflag &= ~ECHO;
if (on & O_CRMOD) {
tios.c_iflag |= ICRNL;
tios.c_oflag |= ONLCR;
}
/*
* Because "O_CRMOD" will never be set in "off" we don't have to
* handle this case here.
*/
if (tcsetattr(pty, TCSANOW, &tios) < 0)
syslog(LOG_INFO, "tcsetattr: %m\n");
}
/*
* 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;
struct tchars tchars;
ptyflush(); /* half-hearted */
if (ioctl(pty, TIOCGETP, &b) == -1)
syslog(LOG_INFO, "ioctl TIOCGETP: %m\n");
if (b.sg_flags & O_RAW) {
*pfrontp++ = '\0';
return;
}
*pfrontp++ = ioctl(pty, TIOCGETC, &tchars) < 0 ?
'\177' : tchars.t_intrc;
}
/*
* 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;
struct tchars tchars;
ptyflush(); /* half-hearted */
(void) ioctl(pty, TIOCGETP, &b);
if (b.sg_flags & O_RAW) {
*pfrontp++ = '\0';
return;
}
*pfrontp++ = ioctl(pty, TIOCGETC, &tchars) < 0 ?
'\034' : tchars.t_quitc;
}
static void
ptyflush(void)
{
int n;
if ((n = pfrontp - pbackp) > 0)
n = write(master, pbackp, n);
if (n < 0)
return;
pbackp += n;
if (pbackp == pfrontp)
pbackp = pfrontp = ptyobuf;
}
/*
* 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 *
nextitem(char *current)
{
if ((*current&0xff) != IAC) {
return (current+1);
}
switch (*(current+1)&0xff) {
case DO:
case DONT:
case WILL:
case WONT:
return (current+3);
case SB: /* loop forever looking for the SE */
{
char *look = current+2;
for (;;) {
if ((*look++&0xff) == IAC) {
if ((*look++&0xff) == SE) {
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 *thisitem, *next;
char *good;
#define wewant(p) ((nfrontp > p) && ((*p&0xff) == IAC) && \
((*(p+1)&0xff) != EC) && ((*(p+1)&0xff) != EL))
thisitem = netobuf;
while ((next = nextitem(thisitem)) <= nbackp) {
thisitem = next;
}
/* Now, thisitem is first before/at boundary. */
good = netobuf; /* where the good bytes go */
while (nfrontp > thisitem) {
if (wewant(thisitem)) {
int length;
next = thisitem;
do {
next = nextitem(next);
} while (wewant(next) && (nfrontp > next));
length = next-thisitem;
(void) memmove(good, thisitem, length);
good += length;
thisitem = next;
} else {
thisitem = nextitem(thisitem);
}
}
nbackp = netobuf;
nfrontp = good; /* next byte to be sent */
neturg = 0;
}
/*
* netflush
* Send as much data as possible to the network,
* handling requests for urgent data.
*/
static void
netflush(void)
{
int n;
if ((n = nfrontp - nbackp) > 0) {
/*
* 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.
*/
if ((neturg == 0) || (not42 == 0)) {
n = write(net, nbackp, n); /* normal write */
} else {
n = neturg - nbackp;
/*
* 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 */
n = write(net, nbackp, n-1);
} else {
/* URGENT data */
n = send_oob(net, nbackp, n);
}
}
}
if (n < 0) {
if (errno == EWOULDBLOCK)
return;
/* should blow this guy away... */
return;
}
nbackp += n;
if (nbackp >= neturg) {
neturg = 0;
}
if (nbackp == nfrontp) {
nbackp = nfrontp = netobuf;
}
}
/* ARGSUSED */
static void
cleanup(int signum)
{
/*
* 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) {
(void) close(net);
(void) close(master);
}
rmut();
exit(EXIT_FAILURE);
}
static void
rmut(void)
{
pam_handle_t *pamh;
struct utmpx *up;
char user[sizeof (up->ut_user) + 1];
char ttyn[sizeof (up->ut_line) + 1];
char rhost[sizeof (up->ut_host) + 1];
/* while cleaning up don't allow disruption */
(void) signal(SIGCHLD, SIG_IGN);
setutxent();
while (up = getutxent()) {
if (up->ut_pid == pid) {
if (up->ut_type == DEAD_PROCESS) {
/*
* 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.
*/
if (up->ut_type == USER_PROCESS) {
(void) strlcpy(user, up->ut_user,
sizeof (user));
(void) strlcpy(ttyn, up->ut_line,
sizeof (ttyn));
(void) strlcpy(rhost, up->ut_host,
sizeof (rhost));
if ((pam_start("telnet", user, NULL, &pamh)) ==
PAM_SUCCESS) {
(void) pam_set_item(pamh, PAM_TTY,
ttyn);
(void) pam_set_item(pamh, PAM_RHOST,
rhost);
(void) pam_close_session(pamh, 0);
(void) pam_end(pamh, PAM_SUCCESS);
}
}
up->ut_type = DEAD_PROCESS;
up->ut_exit.e_termination = WTERMSIG(0);
up->ut_exit.e_exit = WEXITSTATUS(0);
(void) time(&up->ut_tv.tv_sec);
if (modutx(up) == NULL) {
/*
* Since modutx failed we'll
* write out the new entry
* ourselves.
*/
(void) pututxline(up);
updwtmpx("wtmpx", up);
}
break;
}
}
endutxent();
(void) signal(SIGCHLD, (void (*)())cleanup);
}
static int
readstream(int fd, char *buf, int offset)
{
struct strbuf ctlbuf, datbuf;
union T_primitives tpi;
int ret = 0;
int flags = 0;
int bytes_avail, count;
(void) memset((char *)&ctlbuf, 0, sizeof (ctlbuf));
(void) memset((char *)&datbuf, 0, sizeof (datbuf));
ctlbuf.buf = (char *)&tpi;
ctlbuf.maxlen = sizeof (tpi);
if (ioctl(fd, I_NREAD, &bytes_avail) < 0) {
syslog(LOG_ERR, "I_NREAD returned error %m");
return (-1);
}
if (bytes_avail > netibufsize - offset) {
count = netip - netibuf;
netibuf = (char *)realloc(netibuf,
(unsigned)netibufsize + bytes_avail);
if (netibuf == NULL) {
fatal(net, "netibuf realloc failed\n");
}
netibufsize += bytes_avail;
netip = netibuf + count;
buf = netibuf;
}
datbuf.buf = buf + offset;
datbuf.maxlen = netibufsize;
ret = getmsg(fd, &ctlbuf, &datbuf, &flags);
if (ret < 0) {
syslog(LOG_ERR, "getmsg returned -1, errno %d\n",
errno);
return (-1);
}
if (ctlbuf.len <= 0) {
return (datbuf.len);
}
if (tpi.type == T_DATA_REQ) {
return (0);
}
if ((tpi.type == T_ORDREL_IND) || (tpi.type == T_DISCON_IND))
cleanup(0);
fatal(fd, "no data or protocol element recognized");
/*NOTREACHED*/
}
static void
drainstream(int size)
{
int nbytes;
int tsize;
tsize = netip - netibuf;
if ((tsize + ncc + size) > netibufsize) {
if (!(netibuf = (char *)realloc(netibuf,
(unsigned)tsize + ncc + size)))
fatalperror(net, "netibuf realloc failed\n", errno);
netibufsize = tsize + ncc + size;
netip = netibuf + tsize;
}
if ((nbytes = read(net, (char *)netip + ncc, size)) != size)
syslog(LOG_ERR, "read %d bytes\n", nbytes);
}
/*
* TPI style replacement for socket send() primitive, so we don't require
* sockmod to be on the stream.
*/
static int
send_oob(int fd, char *ptr, int count)
{
struct T_exdata_req exd_req;
struct strbuf hdr, dat;
int ret;
exd_req.PRIM_type = T_EXDATA_REQ;
exd_req.MORE_flag = 0;
hdr.buf = (char *)&exd_req;
hdr.len = sizeof (exd_req);
dat.buf = ptr;
dat.len = count;
ret = putmsg(fd, &hdr, &dat, 0);
if (ret == 0) {
ret = count;
}
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
local_setenv(const char *name, const char *value, int rewrite)
{
static int alloced; /* if allocated space before */
char *c;
int l_value, offset;
/*
* 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.
*/
if (strncmp(name, "LD_", 3) == 0 ||
strncmp(name, "NLSPATH", 7) == 0 ||
(strncmp(name, "TTYPROMPT", 9) == 0 &&
(name[9] == '\0' || name[9] == '='))) {
return (-1);
}
if (*value == '=') /* no `=' in value */
++value;
l_value = strlen(value);
if ((c = __findenv(name, &offset))) { /* find if already exists */
if (!rewrite)
return (0);
if ((int)strlen(c) >= l_value) { /* old larger; copy over */
while (*c++ = *value++);
return (0);
}
} else { /* create new slot */
int cnt;
char **p;
for (p = environ, cnt = 0; *p; ++p, ++cnt);
if (alloced) { /* just increase size */
environ = (char **)realloc((char *)environ,
(size_t)(sizeof (char *) * (cnt + 2)));
if (!environ)
return (-1);
} else { /* get new space */
alloced = 1; /* copy old entries into it */
p = (char **)malloc((size_t)(sizeof (char *)*
(cnt + 2)));
if (!p)
return (-1);
(void) memcpy(p, environ, cnt * sizeof (char *));
environ = p;
}
environ[cnt + 1] = NULL;
offset = cnt;
}
for (c = (char *)name; *c && *c != '='; ++c); /* no `=' in name */
if (!(environ[offset] = /* name + `=' + value */
malloc((size_t)((int)(c - name) + l_value + 2))))
return (-1);
for (c = environ[offset]; ((*c = *name++) != 0) && (*c != '='); ++c);
for (*c++ = '='; *c++ = *value++; );
return (0);
}
/*
* local_unsetenv(name) --
* Delete environmental variable "name".
*/
static void
local_unsetenv(const char *name)
{
char **p;
int offset;
while (__findenv(name, &offset)) /* if set multiple times */
for (p = &environ[offset]; ; ++p)
if ((*p = *(p + 1)) == 0)
break;
}
/*
* __findenv --
* Returns pointer to value associated with name, if any, else NULL.
* Sets offset to be the offset of the name/value combination in the
* environmental array, for use by local_setenv() and local_unsetenv().
* Explicitly removes '=' in argument name.
*/
static char *
__findenv(const char *name, int *offset)
{
extern char **environ;
int len;
const char *np;
char **p, *c;
if (name == NULL || environ == NULL)
return (NULL);
for (np = name; *np && *np != '='; ++np)
continue;
len = np - name;
for (p = environ; (c = *p) != NULL; ++p)
if (strncmp(c, name, len) == 0 && c[len] == '=') {
*offset = p - environ;
return (c + len + 1);
}
return (NULL);
}
static void
showbanner(void)
{
char *cp;
char evalbuf[BUFSIZ];
if (defopen(defaultfile) == 0) {
int flags;
/* ignore case */
flags = defcntl(DC_GETFLAGS, 0);
TURNOFF(flags, DC_CASE);
defcntl(DC_SETFLAGS, flags);
if (cp = defread(bannervar)) {
FILE *fp;
if (strlen(cp) + strlen("eval echo '") + strlen("'\n")
+ 1 < sizeof (evalbuf)) {
(void) strlcpy(evalbuf, "eval echo '",
sizeof (evalbuf));
(void) strlcat(evalbuf, cp, sizeof (evalbuf));
(void) strlcat(evalbuf, "'\n",
sizeof (evalbuf));
if (fp = popen(evalbuf, "r")) {
char buf[BUFSIZ];
size_t size;
/*
* Pipe I/O atomicity guarantees we
* need only one read.
*/
if ((size = fread(buf, 1,
sizeof (buf) - 1,
fp)) != 0) {
char *p;
buf[size] = '\0';
p = strrchr(buf, '\n');
if (p != NULL)
*p = '\0';
if (strlen(buf)) {
map_banner(buf);
netflush();
}
}
(void) pclose(fp);
/* close default file */
(void) defopen(NULL);
return;
}
}
}
(void) defopen(NULL); /* close default file */
}
defbanner();
netflush();
}
static void
map_banner(char *p)
{
char *q;
/*
* Map the banner: "\n" -> "\r\n" and "\r" -> "\r\0"
*/
for (q = nfrontp; p && *p && q < nfrontp + sizeof (netobuf) - 1; )
if (*p == '\n') {
*q++ = '\r';
*q++ = '\n';
p++;
} else if (*p == '\r') {
*q++ = '\r';
*q++ = '\0';
p++;
} else
*q++ = *p++;
nfrontp += q - netobuf;
}
/*
* 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;
write_data_len((const char *) BANNER1, sizeof (BANNER1) - 1);
write_data_len(u.sysname, strlen(u.sysname));
write_data_len(" ", 1);
write_data_len(u.release, strlen(u.release));
write_data_len((const char *)BANNER2, sizeof (BANNER2) - 1);
}
/*
* Verify that the named module is at the top of the stream
* and then pop it off.
*/
static int
removemod(int f, char *modname)
{
char topmodname[BUFSIZ];
if (ioctl(f, I_LOOK, topmodname) < 0)
return (-1);
if (strcmp(modname, topmodname) != 0) {
errno = ENXIO;
return (-1);
}
if (ioctl(f, I_POP, 0) < 0)
return (-1);
return (0);
}
static void
write_data(const char *format, ...)
{
va_list args;
int len;
char argp[BUFSIZ];
va_start(args, format);
if ((len = vsnprintf(argp, sizeof (argp), format, args)) == -1)
return;
write_data_len(argp, len);
va_end(args);
}
static void
write_data_len(const char *buf, int len)
{
int remaining, copied;
remaining = BUFSIZ - (nfrontp - netobuf);
while (len > 0) {
/*
* If there's not enough space in netobuf then
* try to make some.
*/
if ((len > BUFSIZ ? BUFSIZ : len) > remaining) {
netflush();
remaining = BUFSIZ - (nfrontp - netobuf);
}
/* Copy as much as we can */
copied = remaining > len ? len : remaining;
(void) memmove(nfrontp, buf, copied);
nfrontp += copied;
len -= copied;
remaining -= copied;
buf += copied;
}
}