/* * CDDL HEADER START * * The contents of this file are subject to the terms of the * Common Development and Distribution License (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 2010 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. */ /* * Telnet server. */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #define AUTHWHO_STR #define AUTHTYPE_NAMES #define AUTHHOW_NAMES #define AUTHRSP_NAMES #define ENCRYPT_NAMES #include #include #include #include #include #include #include #include #include #include #include /* for SC_WILDC */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #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 MAXERRSTRLEN 1024 #define MAXPRINCLEN 256 extern uint_t kwarn_add_warning(char *, int); extern uint_t kwarn_del_warning(char *); 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 manager; /* manager 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[MAXPATHLEN]; krb5_ccache ccache = NULL; char *client_name = 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; /* Register with ktkt_warnd(8) */ if ((retval = krb5_unparse_name(context, (*creds)->client, &client_name)) != 0) goto cleanup; (void) kwarn_del_warning(client_name); if (kwarn_add_warning(client_name, (*creds)->times.endtime) != 0) { syslog(LOG_AUTH|LOG_NOTICE, "rd_and_store_forwarded_creds: kwarn_add_warning" " failed: ktkt_warnd(8) down? "); if (auth_debug) (void) fprintf(stderr, "kwarn_add_warning failed:" " ktkt_warnd(8) down?\n"); } free(client_name); client_name = NULL; 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); (void) 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) { (void) krb5_copy_keyblock(telnet_context, newkey, &session_key); krb5_free_keyblock(telnet_context, newkey); } else { (void) 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) { (void) 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 != 0 && (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 = 0; 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; cki.ivec_usage = IVEC_ONETIME; 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); } (void) 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); 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; remopts[TELOPT_ENCRYPT] = OPT_YES_BUT_ALWAYS_LOOK; } 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 *subsidname; 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 subsidiary pty"); if (unlockpt(p) == -1) fatal(f, "could not unlock subsidiary pty"); if ((subsidname = ptsname(p)) == NULL) fatal(f, "could not enable subsidiary pty"); (void) dup2(f, 0); if ((t = open(subsidname, O_RDWR | O_NOCTTY)) == -1) fatal(f, "could not open subsidiary 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 = subsidname; 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; manager = ptmfd; cryptmod_fd = netfd; /* * Show banner that getty never gave, but * only if the user did not automatically authenticate. */ if (getenv("USER") == NULL && 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, manager); /* * The child process needs to be the session leader * and have the pty as its controlling tty. Thus we need * to re-open the subsidiary 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", subsidname, "-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", subsidname, "-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", subsidname, "--", 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 manager) { 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(manager, 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(manager, &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, manager) + 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(manager, &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(manager, 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 * "manager" in that order. We do close(net) first because * we have no other way to disconnect forwarding between the network * and manager. 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(manager); } 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"); return (0); } 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); (void) 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; } }