/* * Copyright 2008 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 */ /* * Copyright (c) 1983 The Regents of the University of California. * All rights reserved. * * Redistribution and use in source and binary forms are permitted * provided that the above copyright notice and this paragraph are * duplicated in all such forms and that any documentation, * advertising materials, and other materials related to such * distribution and use acknowledge that the software was developed * by the University of California, Berkeley. The name of the * University may not be used to endorse or promote products derived * from this software without specific prior written permission. * THIS SOFTWARE IS PROVIDED ``AS IS'' AND WITHOUT ANY EXPRESS OR * IMPLIED WARRANTIES, INCLUDING, WITHOUT LIMITATION, THE IMPLIED * WARRANTIES OF MERCHANTIBILITY AND FITNESS FOR A PARTICULAR PURPOSE. */ /* * remote login server: * remuser\0 * locuser\0 * terminal info\0 * data */ #include #include #include #include #include #include #include #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 #define KRB5_RECVAUTH_V5 5 #define UT_NAMESIZE sizeof (((struct utmpx *)0)->ut_name) static char lusername[UT_NAMESIZE+1]; static char rusername[UT_NAMESIZE+1]; static char *krusername = NULL; static char term[64]; static krb5_ccache ccache = NULL; static krb5_keyblock *session_key = NULL; static int chksum_flag = 0; static int use_auth = 0; static enum kcmd_proto kcmd_protocol; #ifdef ALLOW_KCMD_V2 static krb5_data encr_iv = { NULL, 0 }; static krb5_data decr_iv = { NULL, 0 }; #endif /* ALLOW_KCMD_V2 */ #define CHKSUM_REQUIRED 0x01 #define CHKSUM_IGNORED 0x02 #define VALID_CHKSUM(x) ((x) == 0 || (x) == CHKSUM_REQUIRED ||\ (x) == CHKSUM_IGNORED) #define PWD_IF_FAIL 0x01 #define PWD_REQUIRED 0x02 #define AUTH_NONE 0x00 #define ARGSTR "k5exEXciM:s:S:D:" #define DEFAULT_TOS 16 #define KRB5_PROG_NAME "krlogin" #define SECURE_MSG "This rlogin session is using encryption " \ "for all data transmissions.\r\n" #define KRB_V5_SENDAUTH_VERS "KRB5_SENDAUTH_V1.0" #define KRB5_RECVAUTH_V5 5 static krb5_error_code krb5_compat_recvauth(krb5_context context, krb5_auth_context *auth_context, krb5_pointer fdp, krb5_principal server, krb5_int32 flags, krb5_keytab keytab, krb5_ticket **ticket, krb5_int32 *auth_sys, krb5_data *version); static void do_krb_login(int, char *, char *, krb5_context, int, krb5_keytab); static int configure_stream(int, krb5_keyblock *, int, krb5_data *, uint_t); extern krb5_error_code krb5_read_message(krb5_context, krb5_pointer, krb5_data *); extern krb5_error_code krb5_net_read(krb5_context, int, char *, int); #define LOGIN_PROGRAM "/bin/login" #define DEFAULT_PROG_NAME "rlogin" static const char *pam_prog_name = DEFAULT_PROG_NAME; static void rmut(void); static void doit(int, struct sockaddr_storage *, krb5_context, int, krb5_keytab); static void protocol(int, int, int); static int readstream(int, char *, int); static void fatal(int, const char *); static void fatalperror(int, const char *); static int send_oob(int fd, void *ptr, size_t count); static int removemod(int f, char *modname); static int issock(int fd) { struct stat stats; if (fstat(fd, &stats) == -1) return (0); return (S_ISSOCK(stats.st_mode)); } /* * audit_rlogin_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_rlogin_settid(int fd) { 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(fd, &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[]) { int on = 1; socklen_t fromlen; struct sockaddr_storage from; int fd = -1; extern char *optarg; char c; int tos = -1; krb5_context krb_context; krb5_keytab keytab = NULL; krb5_error_code status; char *realm = NULL; char *keytab_file = NULL; int encr_flag = 0; struct sockaddr_storage ouraddr; socklen_t ourlen; #ifdef DEBUG int debug_port = 0; #endif /* DEBUG */ openlog("rlogind", LOG_PID | LOG_ODELAY, LOG_DAEMON); while ((c = getopt(argc, argv, ARGSTR)) != -1) { switch (c) { case 'k': case '5': use_auth = KRB5_RECVAUTH_V5; break; case 'e': case 'E': case 'x': case 'X': encr_flag = 1; break; case 'M': realm = (char *)strdup(optarg); break; case 'S': keytab_file = (char *)strdup(optarg); break; case 'c': chksum_flag |= CHKSUM_REQUIRED; break; case 'i': chksum_flag |= CHKSUM_IGNORED; break; case 's': if (optarg == NULL || (tos = atoi(optarg)) < 0 || tos > 255) { syslog(LOG_ERR, "%s: illegal tos value: " "%s\n", argv[0], optarg); } else { if (tos < 0) tos = DEFAULT_TOS; } break; #ifdef DEBUG case 'D': debug_port = atoi(optarg); break; #endif /* DEBUG */ default: syslog(LOG_ERR, "Unrecognized command line option " "(-%c), exiting", optopt); exit(EXIT_FAILURE); } } if (use_auth == KRB5_RECVAUTH_V5) { status = krb5_init_context(&krb_context); if (status) { syslog(LOG_ERR, "Error initializing krb5: %s", error_message(status)); exit(EXIT_FAILURE); } if (realm != NULL) krb5_set_default_realm(krb_context, realm); if (keytab_file != NULL) { if ((status = krb5_kt_resolve(krb_context, keytab_file, &keytab))) { com_err(argv[0], status, "while resolving srvtab file %s", keytab_file); exit(EXIT_FAILURE); } } } #ifdef DEBUG if (debug_port) { int s; struct sockaddr_in sin; if ((s = socket(AF_INET, SOCK_STREAM, PF_UNSPEC)) < 0) { fatalperror(STDERR_FILENO, "Error in socket"); } (void) memset((char *)&sin, 0, sizeof (sin)); sin.sin_family = AF_INET; sin.sin_port = htons(debug_port); sin.sin_addr.s_addr = INADDR_ANY; (void) setsockopt(s, SOL_SOCKET, SO_REUSEADDR, (char *)&on, sizeof (on)); if ((bind(s, (struct sockaddr *)&sin, sizeof (sin))) < 0) { fatalperror(STDERR_FILENO, "bind error"); } if ((listen(s, 5)) < 0) { fatalperror(STDERR_FILENO, "listen error"); } fromlen = sizeof (from); if ((fd = accept(s, (struct sockaddr *)&from, &fromlen)) < 0) { fatalperror(STDERR_FILENO, "accept error"); } (void) close(s); } else #endif /* DEBUG */ { if (!issock(STDIN_FILENO)) fatal(STDIN_FILENO, "stdin is not a socket file descriptor"); fd = STDIN_FILENO; } fromlen = sizeof (from); if (getpeername(fd, (struct sockaddr *)&from, &fromlen) < 0) fatalperror(STDERR_FILENO, "getpeername"); if (audit_rlogin_settid(fd)) /* set terminal ID */ fatalperror(STDERR_FILENO, "audit"); if (setsockopt(fd, SOL_SOCKET, SO_KEEPALIVE, (char *)&on, sizeof (on)) < 0) syslog(LOG_WARNING, "setsockopt(SO_KEEPALIVE): %m"); if (!VALID_CHKSUM(chksum_flag)) { syslog(LOG_ERR, "Configuration error: mutually exclusive " "options specified (-c and -i)"); fatal(fd, "Checksums are required and ignored (-c and -i);" "these options are mutually exclusive - check " "the documentation."); } ourlen = sizeof (ouraddr); if (getsockname(fd, (struct sockaddr *)&ouraddr, &ourlen) == -1) { syslog(LOG_ERR, "getsockname error: %m"); exit(EXIT_FAILURE); } if (tos != -1 && ouraddr.ss_family != AF_INET6 && setsockopt(fd, IPPROTO_IP, IP_TOS, (char *)&tos, sizeof (tos)) < 0 && errno != ENOPROTOOPT) { syslog(LOG_ERR, "setsockopt(IP_TOS %d): %m", tos); } doit(fd, &from, krb_context, encr_flag, keytab); return (0); } static void cleanup(int); static int nsize = 0; /* bytes read prior to pushing rlmod */ static char *rlbuf; /* buffer where nbytes are read to */ static char *line; static struct winsize win = { 0, 0, 0, 0 }; static pid_t pid; static char hostname[MAXHOSTNAMELEN + 1]; static void getstr(int f, char *buf, int cnt, char *err) { char c; do { if (read(f, &c, 1) != 1 || (--cnt < 0)) { syslog(LOG_ERR, "Error reading \'%s\' field", err); exit(EXIT_FAILURE); } *buf++ = c; } while (c != '\0'); } static krb5_error_code recvauth(int f, krb5_context krb_context, unsigned int *valid_checksum, krb5_ticket **ticket, int *auth_type, krb5_principal *client, int encr_flag, krb5_keytab keytab) { krb5_error_code status = 0; krb5_auth_context auth_context = NULL; krb5_rcache rcache; krb5_authenticator *authenticator; krb5_data inbuf; krb5_data auth_version; *valid_checksum = 0; if ((status = krb5_auth_con_init(krb_context, &auth_context))) return (status); /* Only need remote address for rd_cred() to verify client */ if ((status = krb5_auth_con_genaddrs(krb_context, auth_context, f, KRB5_AUTH_CONTEXT_GENERATE_REMOTE_FULL_ADDR))) return (status); status = krb5_auth_con_getrcache(krb_context, auth_context, &rcache); if (status) return (status); if (!rcache) { krb5_principal server; status = krb5_sname_to_principal(krb_context, 0, 0, KRB5_NT_SRV_HST, &server); if (status) return (status); status = krb5_get_server_rcache(krb_context, krb5_princ_component(krb_context, server, 0), &rcache); krb5_free_principal(krb_context, server); if (status) return (status); status = krb5_auth_con_setrcache(krb_context, auth_context, rcache); if (status) return (status); } if ((status = krb5_compat_recvauth(krb_context, &auth_context, &f, NULL, /* Specify daemon principal */ 0, /* no flags */ keytab, /* NULL to use v5srvtab */ ticket, /* return ticket */ auth_type, /* authentication system */ &auth_version))) { if (*auth_type == KRB5_RECVAUTH_V5) { /* * clean up before exiting */ getstr(f, rusername, sizeof (rusername), "remuser"); getstr(f, lusername, sizeof (lusername), "locuser"); getstr(f, term, sizeof (term), "Terminal type"); } return (status); } getstr(f, lusername, sizeof (lusername), "locuser"); getstr(f, term, sizeof (term), "Terminal type"); kcmd_protocol = KCMD_UNKNOWN_PROTOCOL; if (auth_version.length != 9 || auth_version.data == NULL) { syslog(LOG_ERR, "Bad application protocol version length in " "KRB5 exchange, exiting"); fatal(f, "Bad application version length, exiting."); } /* * Determine which Kerberos CMD protocol was used. */ if (strncmp(auth_version.data, "KCMDV0.1", 9) == 0) { kcmd_protocol = KCMD_OLD_PROTOCOL; } else if (strncmp(auth_version.data, "KCMDV0.2", 9) == 0) { kcmd_protocol = KCMD_NEW_PROTOCOL; } else { syslog(LOG_ERR, "Unrecognized KCMD protocol (%s), exiting", (char *)auth_version.data); fatal(f, "Unrecognized KCMD protocol, exiting"); } if ((*auth_type == KRB5_RECVAUTH_V5) && chksum_flag && kcmd_protocol == KCMD_OLD_PROTOCOL) { if ((status = krb5_auth_con_getauthenticator(krb_context, auth_context, &authenticator))) return (status); if (authenticator->checksum) { struct sockaddr_storage adr; int adr_length = sizeof (adr); int buflen; krb5_data input; krb5_keyblock key; char *chksumbuf; /* * Define the lenght of the chksum buffer. * chksum string = "[portnum]:termstr:username" * The extra 32 is to hold a integer string for * the portnumber. */ buflen = strlen(term) + strlen(lusername) + 32; chksumbuf = (char *)malloc(buflen); if (chksumbuf == 0) { krb5_free_authenticator(krb_context, authenticator); fatal(f, "Out of memory error"); } if (getsockname(f, (struct sockaddr *)&adr, &adr_length) != 0) { krb5_free_authenticator(krb_context, authenticator); fatal(f, "getsockname error"); } (void) snprintf(chksumbuf, buflen, "%u:%s%s", ntohs(SOCK_PORT(adr)), term, lusername); input.data = chksumbuf; input.length = strlen(chksumbuf); key.contents = (*ticket)->enc_part2->session->contents; key.length = (*ticket)->enc_part2->session->length; status = krb5_c_verify_checksum(krb_context, &key, 0, &input, authenticator->checksum, valid_checksum); if (status == 0 && *valid_checksum == 0) status = KRB5KRB_AP_ERR_BAD_INTEGRITY; if (chksumbuf) krb5_xfree(chksumbuf); if (status) { krb5_free_authenticator(krb_context, authenticator); return (status); } } krb5_free_authenticator(krb_context, authenticator); } if ((status = krb5_copy_principal(krb_context, (*ticket)->enc_part2->client, client))) return (status); /* Get the Unix username of the remote user */ getstr(f, rusername, sizeof (rusername), "remuser"); /* Get the Kerberos principal name string of the remote user */ if ((status = krb5_unparse_name(krb_context, *client, &krusername))) return (status); #ifdef DEBUG syslog(LOG_DEBUG | LOG_AUTH, "rlogind: got krb5 credentials for %s", (krusername != NULL ? krusername : "")); #endif if (encr_flag) { status = krb5_auth_con_getremotesubkey(krb_context, auth_context, &session_key); if (status) { syslog(LOG_ERR, "Error getting KRB5 session " "subkey, exiting"); fatal(f, "Error getting KRB5 session subkey, exiting"); } /* * The "new" protocol requires that a subkey be sent. */ if (session_key == NULL && kcmd_protocol == KCMD_NEW_PROTOCOL) { syslog(LOG_ERR, "No KRB5 session subkey sent, exiting"); fatal(f, "No KRB5 session subkey sent, exiting"); } /* * The "old" protocol does not permit an authenticator subkey. * The key is taken from the ticket instead (see below). */ if (session_key != NULL && kcmd_protocol == KCMD_OLD_PROTOCOL) { syslog(LOG_ERR, "KRB5 session subkey not permitted " "with old KCMD protocol, exiting"); fatal(f, "KRB5 session subkey not permitted " "with old KCMD protocol, exiting"); } /* * If no key at this point, use the session key from * the ticket. */ if (session_key == NULL) { /* * Save the session key so we can configure the crypto * module later. */ status = krb5_copy_keyblock(krb_context, (*ticket)->enc_part2->session, &session_key); if (status) { syslog(LOG_ERR, "krb5_copy_keyblock failed"); fatal(f, "krb5_copy_keyblock failed"); } } /* * If session key still cannot be found, we must * exit because encryption is required here * when encr_flag (-x) is set. */ if (session_key == NULL) { syslog(LOG_ERR, "Could not find an encryption key," "exiting"); fatal(f, "Encryption required but key not found, " "exiting"); } } /* * Use krb5_read_message to read the principal stuff. */ if ((status = krb5_read_message(krb_context, (krb5_pointer)&f, &inbuf))) fatal(f, "Error reading krb5 message"); if (inbuf.length) { /* Forwarding being done, read creds */ krb5_creds **creds = NULL; if (status = krb5_rd_cred(krb_context, auth_context, &inbuf, &creds, NULL)) { if (rcache) (void) krb5_rc_close(krb_context, rcache); krb5_free_creds(krb_context, *creds); fatal(f, "Can't get forwarded credentials"); } /* Store the forwarded creds in the ccache */ if (status = store_forw_creds(krb_context, creds, *ticket, lusername, &ccache)) { if (rcache) (void) krb5_rc_close(krb_context, rcache); krb5_free_creds(krb_context, *creds); fatal(f, "Can't store forwarded credentials"); } krb5_free_creds(krb_context, *creds); } if (rcache) (void) krb5_rc_close(krb_context, rcache); return (status); } static void do_krb_login(int f, char *host_addr, char *hostname, krb5_context krb_context, int encr_flag, krb5_keytab keytab) { krb5_error_code status; uint_t valid_checksum; krb5_ticket *ticket = NULL; int auth_sys = 0; int auth_sent = 0; krb5_principal client = NULL; if (getuid()) fatal(f, "Error authorizing KRB5 connection, " "server lacks privilege"); status = recvauth(f, krb_context, &valid_checksum, &ticket, &auth_sys, &client, encr_flag, keytab); if (status) { if (ticket) krb5_free_ticket(krb_context, ticket); if (status != 255) syslog(LOG_ERR, "Authentication failed from %s(%s): %s\n", host_addr, hostname, error_message(status)); fatal(f, "Kerberos authentication failed, exiting"); } if (auth_sys != KRB5_RECVAUTH_V5) { fatal(f, "This server only supports Kerberos V5"); } else { /* * Authenticated OK, now check authorization. */ if (client && krb5_kuserok(krb_context, client, lusername)) auth_sent = KRB5_RECVAUTH_V5; } if (auth_sent == KRB5_RECVAUTH_V5 && kcmd_protocol == KCMD_OLD_PROTOCOL && chksum_flag == CHKSUM_REQUIRED && !valid_checksum) { syslog(LOG_ERR, "Client did not supply required checksum, " "connection rejected."); fatal(f, "Client did not supply required checksum, " "connection rejected."); } if (auth_sys != auth_sent) { char *msg_fail = NULL; int msgsize = 0; if (ticket) krb5_free_ticket(krb_context, ticket); if (krusername != NULL) { /* * msgsize must be enough to hold * krusername, lusername and a brief * message describing the failure. */ msgsize = strlen(krusername) + strlen(lusername) + 80; msg_fail = (char *)malloc(msgsize); } if (msg_fail == NULL) { syslog(LOG_ERR, "User is not authorized to login to " "specified account"); fatal(f, "User is not authorized to login to " "specified account"); } if (auth_sent != 0) (void) snprintf(msg_fail, msgsize, "Access denied because of improper " "KRB5 credentials"); else (void) snprintf(msg_fail, msgsize, "User %s is not authorized to login " "to account %s", krusername, lusername); syslog(LOG_ERR, "%s", msg_fail); fatal(f, msg_fail); } } /* * stop_stream * * Utility routine to send a CRYPTIOCSTOP 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) { struct strioctl crioc; uint32_t iocval; size_t datalen = 0; char *data = NULL; if (dir == CRYPT_DECRYPT) { iocval = CRYPTIOCSTARTDEC; /* Look for data not yet processed */ if (ioctl(fd, I_NREAD, &datalen) < 0) { syslog(LOG_ERR, "I_NREAD returned error %m"); datalen = 0; } else { if (datalen > 0) { data = malloc(datalen); if (data != NULL) { int nbytes = read(fd, data, datalen); datalen = nbytes; } else { syslog(LOG_ERR, "malloc error (%d bytes)", datalen); datalen = 0; } } else { datalen = 0; } } } else { iocval = CRYPTIOCSTARTENC; } crioc.ic_cmd = iocval; 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"); if (data != NULL) free(data); } static int configure_stream(int fd, krb5_keyblock *skey, int dir, krb5_data *ivec, uint_t iv_usage) { struct cr_info_t setup_info; struct strioctl crioc; int retval = 0; switch (skey->enctype) { case ENCTYPE_DES_CBC_CRC: setup_info.crypto_method = CRYPT_METHOD_DES_CBC_CRC; break; case ENCTYPE_DES_CBC_MD5: setup_info.crypto_method = CRYPT_METHOD_DES_CBC_MD5; break; case ENCTYPE_DES_CBC_RAW: setup_info.crypto_method = CRYPT_METHOD_DES_CBC_NULL; break; case ENCTYPE_DES3_CBC_SHA1: setup_info.crypto_method = CRYPT_METHOD_DES3_CBC_SHA1; break; case ENCTYPE_ARCFOUR_HMAC: setup_info.crypto_method = CRYPT_METHOD_ARCFOUR_HMAC_MD5; break; case ENCTYPE_ARCFOUR_HMAC_EXP: setup_info.crypto_method = CRYPT_METHOD_ARCFOUR_HMAC_MD5_EXP; break; case ENCTYPE_AES128_CTS_HMAC_SHA1_96: setup_info.crypto_method = CRYPT_METHOD_AES128; break; case ENCTYPE_AES256_CTS_HMAC_SHA1_96: setup_info.crypto_method = CRYPT_METHOD_AES256; break; default: syslog(LOG_ERR, "Enctype in kerberos session key " "is not supported by crypto module(%d)", skey->enctype); return (-1); } if (ivec == NULL || ivec->length == 0) { (void) memset(&setup_info.ivec, 0, sizeof (setup_info.ivec)); if (skey->enctype != ENCTYPE_ARCFOUR_HMAC && skey->enctype != ENCTYPE_ARCFOUR_HMAC_EXP) /* Kerberos IVs are 8 bytes long for DES keys */ setup_info.iveclen = KRB5_MIT_DES_KEYSIZE; else setup_info.iveclen = 0; } else { (void) memcpy(&setup_info.ivec, ivec->data, ivec->length); setup_info.iveclen = ivec->length; } setup_info.ivec_usage = iv_usage; (void) memcpy(&setup_info.key, skey->contents, skey->length); setup_info.keylen = skey->length; setup_info.direction_mask = dir; /* * R* commands get special handling by crypto module - * 4 byte length field is used before each encrypted block * of data. */ setup_info.option_mask = (kcmd_protocol == KCMD_OLD_PROTOCOL ? CRYPTOPT_RCMD_MODE_V1 : CRYPTOPT_RCMD_MODE_V2); crioc.ic_cmd = CRYPTIOCSETUP; crioc.ic_timout = -1; crioc.ic_len = sizeof (setup_info); crioc.ic_dp = (char *)&setup_info; if (ioctl(fd, I_STR, &crioc)) { syslog(LOG_ERR, "Error sending CRYPTIOCSETUP ioctl: %m"); retval = -1; } return (retval); } static krb5_error_code krb5_compat_recvauth(krb5_context context, krb5_auth_context *auth_context, krb5_pointer fdp, /* IN */ krb5_principal server, /* IN */ krb5_int32 flags, /* IN */ krb5_keytab keytab, /* IN */ krb5_ticket **ticket, /* OUT */ krb5_int32 *auth_sys, /* OUT */ krb5_data *version) /* OUT */ { krb5_int32 vlen; char *buf; int len, length; krb5_int32 retval; int fd = *((int *)fdp); if ((retval = krb5_net_read(context, fd, (char *)&vlen, 4)) != 4) return ((retval < 0) ? errno : ECONNABORTED); /* * Assume that we're talking to a V5 recvauth; read in the * the version string, and make sure it matches. */ len = (int)ntohl(vlen); if (len < 0 || len > 255) return (KRB5_SENDAUTH_BADAUTHVERS); buf = malloc(len); if (buf == NULL) return (ENOMEM); length = krb5_net_read(context, fd, buf, len); if (len != length) { krb5_xfree(buf); return ((len < 0) ? errno : ECONNABORTED); } if (strcmp(buf, KRB_V5_SENDAUTH_VERS) != 0) { krb5_xfree(buf); return (KRB5_SENDAUTH_BADAUTHVERS); } krb5_xfree(buf); *auth_sys = KRB5_RECVAUTH_V5; retval = krb5_recvauth_version(context, auth_context, fdp, server, flags | KRB5_RECVAUTH_SKIP_VERSION, keytab, ticket, version); return (retval); } static void doit(int f, struct sockaddr_storage *fromp, krb5_context krb_context, int encr_flag, krb5_keytab keytab) { int p, t, on = 1; char c; char abuf[INET6_ADDRSTRLEN]; struct sockaddr_in *sin; struct sockaddr_in6 *sin6; int fromplen; in_port_t port; struct termios tp; boolean_t bad_port; boolean_t no_name; char rhost_addra[INET6_ADDRSTRLEN]; if (!(rlbuf = malloc(BUFSIZ))) { syslog(LOG_ERR, "rlbuf malloc failed\n"); exit(EXIT_FAILURE); } (void) alarm(60); if (read(f, &c, 1) != 1 || c != 0) { syslog(LOG_ERR, "failed to receive protocol zero byte\n"); exit(EXIT_FAILURE); } (void) alarm(0); if (fromp->ss_family == AF_INET) { sin = (struct sockaddr_in *)fromp; port = sin->sin_port = ntohs((ushort_t)sin->sin_port); fromplen = sizeof (struct sockaddr_in); if (!inet_ntop(AF_INET, &sin->sin_addr, rhost_addra, sizeof (rhost_addra))) goto badconversion; } else if (fromp->ss_family == AF_INET6) { sin6 = (struct sockaddr_in6 *)fromp; port = sin6->sin6_port = ntohs((ushort_t)sin6->sin6_port); fromplen = sizeof (struct sockaddr_in6); if (IN6_IS_ADDR_V4MAPPED(&sin6->sin6_addr)) { struct in_addr ipv4_addr; IN6_V4MAPPED_TO_INADDR(&sin6->sin6_addr, &ipv4_addr); if (!inet_ntop(AF_INET, &ipv4_addr, rhost_addra, sizeof (rhost_addra))) goto badconversion; } else { if (!inet_ntop(AF_INET6, &sin6->sin6_addr, rhost_addra, sizeof (rhost_addra))) goto badconversion; } } else { syslog(LOG_ERR, "unknown address family %d\n", fromp->ss_family); fatal(f, "Permission denied"); } /* * Allow connections only from the "ephemeral" reserved * ports(ports 512 - 1023) by checking the remote port * because other utilities(e.g. in.ftpd) can be used to * allow a unprivileged user to originate a connection * from a privileged port and provide untrustworthy * authentication. */ bad_port = (use_auth != KRB5_RECVAUTH_V5 && (port >= (in_port_t)IPPORT_RESERVED) || (port < (in_port_t)(IPPORT_RESERVED/2))); no_name = getnameinfo((const struct sockaddr *) fromp, fromplen, hostname, sizeof (hostname), NULL, 0, 0) != 0; if (no_name || bad_port) { (void) strlcpy(abuf, rhost_addra, sizeof (abuf)); /* If no host name, use IP address for name later on. */ if (no_name) (void) strlcpy(hostname, abuf, sizeof (hostname)); } if (!no_name) { /* * Even if getnameinfo() succeeded, we still have to check * for spoofing. */ check_address("rlogind", fromp, sin, sin6, rhost_addra, hostname, sizeof (hostname)); } if (bad_port) { if (no_name) syslog(LOG_NOTICE, "connection from %s - bad port\n", abuf); else syslog(LOG_NOTICE, "connection from %s(%s) - bad port\n", hostname, abuf); fatal(f, "Permission denied"); } if (use_auth == KRB5_RECVAUTH_V5) { do_krb_login(f, rhost_addra, hostname, krb_context, encr_flag, keytab); if (krusername != NULL && strlen(krusername)) { /* * Kerberos Authentication succeeded, * so set the proper program name to use * with pam (important during 'cleanup' * routine later). */ pam_prog_name = KRB5_PROG_NAME; } } if (write(f, "", 1) != 1) { syslog(LOG_NOTICE, "send of the zero byte(to %s) failed:" " cannot start data transfer mode\n", (no_name ? abuf : hostname)); exit(EXIT_FAILURE); } if ((p = open("/dev/ptmx", O_RDWR)) == -1) fatalperror(f, "cannot open /dev/ptmx"); if (grantpt(p) == -1) fatal(f, "could not grant slave pty"); if (unlockpt(p) == -1) fatal(f, "could not unlock slave pty"); if ((line = ptsname(p)) == NULL) fatal(f, "could not enable slave pty"); if ((t = open(line, O_RDWR)) == -1) fatal(f, "could not open slave pty"); if (ioctl(t, I_PUSH, "ptem") == -1) fatalperror(f, "ioctl I_PUSH ptem"); if (ioctl(t, I_PUSH, "ldterm") == -1) fatalperror(f, "ioctl I_PUSH ldterm"); if (ioctl(t, I_PUSH, "ttcompat") == -1) fatalperror(f, "ioctl I_PUSH ttcompat"); /* * POP the sockmod and push the rlmod module. * * 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"); if (encr_flag) { if (ioctl(f, I_PUSH, "cryptmod") < 0) fatalperror(f, "ioctl I_PUSH rlmod"); } if (ioctl(f, I_PUSH, "rlmod") < 0) fatalperror(f, "ioctl I_PUSH rlmod"); if (encr_flag) { /* * Make sure rlmod will pass unrecognized IOCTLs to cryptmod */ uchar_t passthru = 1; struct strioctl rlmodctl; rlmodctl.ic_cmd = CRYPTPASSTHRU; rlmodctl.ic_timout = -1; rlmodctl.ic_len = sizeof (uchar_t); rlmodctl.ic_dp = (char *)&passthru; if (ioctl(f, I_STR, &rlmodctl) < 0) fatal(f, "ioctl CRYPTPASSTHRU failed\n"); } /* * readstream will do a getmsg till it receives * M_PROTO type T_DATA_REQ from rloginmodopen() * indicating all data on the stream prior to pushing rlmod has * been drained at the stream head. */ if ((nsize = readstream(f, rlbuf, BUFSIZ)) < 0) fatalperror(f, "readstream failed"); /* * Make sure the pty doesn't modify the strings passed * to login as part of the "rlogin protocol." The login * program should set these flags to apropriate values * after it has read the strings. */ if (ioctl(t, TCGETS, &tp) == -1) fatalperror(f, "ioctl TCGETS"); tp.c_lflag &= ~(ECHO|ICANON); tp.c_oflag &= ~(XTABS|OCRNL); tp.c_iflag &= ~(IGNPAR|ICRNL); if (ioctl(t, TCSETS, &tp) == -1) fatalperror(f, "ioctl TCSETS"); /* * System V ptys allow the TIOC{SG}WINSZ ioctl to be * issued on the master side of the pty. Luckily, that's * the only tty ioctl we need to do do, so we can close the * slave side in the parent process after the fork. */ (void) ioctl(p, TIOCSWINSZ, &win); pid = fork(); if (pid < 0) fatalperror(f, "fork"); if (pid == 0) { int tt; struct utmpx ut; /* System V login expects a utmp entry to already be there */ (void) memset(&ut, 0, sizeof (ut)); (void) strncpy(ut.ut_user, ".rlogin", sizeof (ut.ut_user)); (void) strncpy(ut.ut_line, line, sizeof (ut.ut_line)); ut.ut_pid = getpid(); ut.ut_id[0] = 'r'; 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.rlogind:\tmakeutx failed"); /* controlling tty */ if (setsid() == -1) fatalperror(f, "setsid"); if ((tt = open(line, O_RDWR)) == -1) fatalperror(f, "could not re-open slave pty"); if (close(p) == -1) fatalperror(f, "error closing pty master"); if (close(t) == -1) fatalperror(f, "error closing pty slave" " opened before session established"); /* * If this fails we may or may not be able to output an * error message. */ if (close(f) == -1) fatalperror(f, "error closing deamon stdout"); if (dup2(tt, STDIN_FILENO) == -1 || dup2(tt, STDOUT_FILENO) == -1 || dup2(tt, STDERR_FILENO) == -1) exit(EXIT_FAILURE); /* Disaster! No stderr! */ (void) close(tt); if (use_auth == KRB5_RECVAUTH_V5 && krusername != NULL && strlen(krusername)) { (void) execl(LOGIN_PROGRAM, "login", "-d", line, "-r", hostname, "-u", krusername, /* KRB5 principal name */ "-s", pam_prog_name, "-t", term, /* Remote Terminal */ "-U", rusername, /* Remote User */ "-R", KRB5_REPOSITORY_NAME, lusername, /* local user */ NULL); } else { (void) execl(LOGIN_PROGRAM, "login", "-d", line, "-r", hostname, NULL); } fatalperror(STDERR_FILENO, "/bin/login"); /*NOTREACHED*/ } (void) close(t); (void) ioctl(f, FIONBIO, &on); (void) ioctl(p, FIONBIO, &on); /* * Must ignore SIGTTOU, otherwise we'll stop * when we try and set slave pty's window shape * (our controlling tty is the master pty). * Likewise, we don't want any of the tty-generated * signals from chars passing through. */ (void) sigset(SIGTSTP, SIG_IGN); (void) sigset(SIGINT, SIG_IGN); (void) sigset(SIGQUIT, SIG_IGN); (void) sigset(SIGTTOU, SIG_IGN); (void) sigset(SIGTTIN, SIG_IGN); (void) sigset(SIGCHLD, cleanup); (void) setpgrp(); if (encr_flag) { krb5_data ivec, *ivptr; uint_t ivec_usage; stop_stream(f, CRYPT_ENCRYPT|CRYPT_DECRYPT); /* * Configure the STREAMS crypto module. For now, * don't use any IV parameter. KCMDV0.2 support * will require the use of Initialization Vectors * for both encrypt and decrypt modes. */ if (kcmd_protocol == KCMD_OLD_PROTOCOL) { if (session_key->enctype == ENCTYPE_DES_CBC_CRC) { /* * This is gross but necessary for MIT compat. */ ivec.length = session_key->length; ivec.data = (char *)session_key->contents; ivec_usage = IVEC_REUSE; ivptr = &ivec; } else { ivptr = NULL; /* defaults to all 0's */ ivec_usage = IVEC_NEVER; } /* * configure both sides of stream together * since they share the same IV. * This is what makes the OLD KCMD protocol * less secure than the newer one - Bad ivecs. */ if (configure_stream(f, session_key, CRYPT_ENCRYPT|CRYPT_DECRYPT, ivptr, ivec_usage) != 0) fatal(f, "Cannot initialize encryption -" " exiting.\n"); } else { size_t blocksize; if (session_key->enctype == ENCTYPE_ARCFOUR_HMAC || session_key->enctype == ENCTYPE_ARCFOUR_HMAC_EXP) { if (configure_stream(f, session_key, CRYPT_ENCRYPT|CRYPT_DECRYPT, NULL, IVEC_NEVER) != 0) fatal(f, "Cannot initialize encryption -" " exiting.\n"); goto startcrypto; } if (krb5_c_block_size(krb_context, session_key->enctype, &blocksize)) { syslog(LOG_ERR, "Cannot determine blocksize " "for encryption type %d", session_key->enctype); fatal(f, "Cannot determine blocksize " "for encryption - exiting.\n"); } ivec.data = (char *)malloc(blocksize); ivec.length = blocksize; if (ivec.data == NULL) fatal(f, "memory error - exiting\n"); /* * Following MIT convention - * encrypt IV = 0x01 x blocksize * decrypt IV = 0x00 x blocksize * ivec_usage = IVEC_ONETIME * * configure_stream separately for encrypt and * decrypt because there are 2 different IVs. * * AES uses 0's for IV. */ if (session_key->enctype == ENCTYPE_AES128_CTS_HMAC_SHA1_96 || session_key->enctype == ENCTYPE_AES256_CTS_HMAC_SHA1_96) (void) memset(ivec.data, 0x00, blocksize); else (void) memset(ivec.data, 0x01, blocksize); if (configure_stream(f, session_key, CRYPT_ENCRYPT, &ivec, IVEC_ONETIME) != 0) fatal(f, "Cannot initialize encryption -" " exiting.\n"); (void) memset(ivec.data, 0x00, blocksize); if (configure_stream(f, session_key, CRYPT_DECRYPT, &ivec, IVEC_ONETIME) != 0) fatal(f, "Cannot initialize encryption -" " exiting.\n"); (void) free(ivec.data); } startcrypto: start_stream(f, CRYPT_ENCRYPT); start_stream(f, CRYPT_DECRYPT); } protocol(f, p, encr_flag); cleanup(0); /*NOTREACHED*/ badconversion: fatalperror(f, "address conversion"); /*NOTREACHED*/ } /* * rlogin "protocol" machine. */ static void protocol(int f, int p, int encr_flag) { struct stat buf; struct protocol_arg rloginp; struct strioctl rloginmod; int ptmfd; /* fd of logindmux coneected to ptmx */ int netfd; /* fd of logindmux connected to netf */ static uchar_t oobdata[] = {TIOCPKT_WINDOW}; /* indicate new rlogin */ if (send_oob(f, oobdata, 1) < 0) fatalperror(f, "send_oob"); /* * We cannot send the SECURE_MSG until after the * client has been signaled with the oobdata (above). */ if (encr_flag) { if (write(f, SECURE_MSG, strlen(SECURE_MSG)) < 0) fatalperror(f, "Error writing SECURE message"); } /* * Open logindmux driver and link netf and ptmx * underneath logindmux. */ if ((ptmfd = open("/dev/logindmux", O_RDWR)) == -1) fatalperror(f, "open /dev/logindmux"); if ((netfd = open("/dev/logindmux", O_RDWR)) == -1) fatalperror(f, "open /dev/logindmux"); 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 the ptm's mux fd, and pass that * to the net's mux. */ if (fstat(ptmfd, &buf) < 0) fatalperror(f, "cannot determine device number" " of pty side of /dev/logindmux"); rloginp.dev = buf.st_rdev; rloginp.flag = 0; rloginmod.ic_cmd = LOGDMX_IOC_QEXCHANGE; rloginmod.ic_timout = -1; rloginmod.ic_len = sizeof (struct protocol_arg); rloginmod.ic_dp = (char *)&rloginp; if (ioctl(netfd, I_STR, &rloginmod) < 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)) fatalperror(f, "cannot determine device number" " of network side of /dev/logindmux"); rloginp.dev = buf.st_rdev; rloginp.flag = 1; rloginmod.ic_cmd = LOGDMX_IOC_QEXCHANGE; rloginmod.ic_timout = -1; rloginmod.ic_len = sizeof (struct protocol_arg); rloginmod.ic_dp = (char *)&rloginp; if (ioctl(ptmfd, I_STR, &rloginmod) < 0) fatal(netfd, "ioctl LOGDMXZ_IOC_QEXCHANGE of ptmfd failed\n"); /* * Send an ioctl type RL_IOC_ENABLE to reenable the * message queue and reinsert the data read from streamhead * at the time of pushing rloginmod module. * We need to send this ioctl even if no data was read earlier * since we need to reenable the message queue of rloginmod module. */ rloginmod.ic_cmd = RL_IOC_ENABLE; rloginmod.ic_timout = -1; if (nsize) { rloginmod.ic_len = nsize; rloginmod.ic_dp = rlbuf; } else { rloginmod.ic_len = 0; rloginmod.ic_dp = NULL; } if (ioctl(netfd, I_STR, &rloginmod) < 0) fatal(netfd, "ioctl RL_IOC_ENABLE of netfd failed\n"); /* * User level daemon now pauses till the shell exits. */ (void) pause(); } /* This is a signal handler, hence the dummy argument */ /*ARGSUSED*/ static void cleanup(int dummy) { rmut(); exit(EXIT_FAILURE); /*NOTREACHED*/ } /* * TPI style replacement for socket send() primitive, so we don't require * sockmod to be on the stream. */ static int send_oob(int fd, void *ptr, size_t 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); } static void fatal(int fd, const char *msg) { char *bufp; size_t len = strlen(msg) + 16; /* enough for our wrapper */ bufp = alloca(len); /* ASCII 001 is the error indicator */ len = snprintf(bufp, len, "\01rlogind: %s.\r\n", msg); (void) write(fd, bufp, len); exit(EXIT_FAILURE); /*NOTREACHED*/ } /*PRINTFLIKE2*/ static void fatalperror(int fd, const char *msg) { char *bufp; const char *errstr; int save_errno = errno; size_t len = strlen(msg); if ((errstr = strerror(save_errno))) { len += strlen(errstr) + 3; /* 3 for ": " and \0 below */ bufp = alloca(len); (void) snprintf(bufp, len, "%s: %s", msg, errstr); } else { const char fmt[] = "%s: Error %d"; /* -4 for %s & %d. "*8/3" is bytes->decimal, pessimistically */ len += sizeof (fmt) -4 + (sizeof (save_errno) *8 /3); bufp = alloca(len); (void) snprintf(bufp, len, fmt, msg, save_errno); } fatal(fd, bufp); /*NOTREACHED*/ } 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 dont allow disruption */ (void) sigset(SIGCHLD, SIG_IGN); setutxent(); while (up = getutxent()) { if (up->ut_pid == pid) { if (up->ut_type == DEAD_PROCESS) break; /* Cleaned up elsewhere. */ /* * 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)); /* * Use the same pam_prog_name that * 'login' used. */ if ((pam_start(pam_prog_name, 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) sigset(SIGCHLD, cleanup); } static int readstream(int fd, char *buf, int size) { struct strbuf ctlbuf, datbuf; union T_primitives tpi; int nbytes = 0; int ret = 0; int flags = 0; int bufsize = size; int nread; (void) memset(&ctlbuf, 0, sizeof (ctlbuf)); (void) memset(&datbuf, 0, sizeof (datbuf)); ctlbuf.buf = (char *)&tpi; ctlbuf.maxlen = sizeof (tpi); datbuf.buf = buf; datbuf.maxlen = size; for (;;) { if (ioctl(fd, I_NREAD, &nread) < 0) { syslog(LOG_ERR, "I_NREAD returned error %m"); return (-1); } if (nread + nbytes > bufsize) { buf = (char *)realloc(buf, (unsigned)(bufsize + nread)); if (buf == NULL) { syslog(LOG_WARNING, "buffer allocation failed\n"); return (-1); } bufsize += nread; rlbuf = buf; datbuf.buf = buf + nbytes; } datbuf.maxlen = bufsize - nbytes; ret = getmsg(fd, &ctlbuf, &datbuf, &flags); if (ret < 0) { syslog(LOG_ERR, "getmsg failed error %m"); return (-1); } if ((ctlbuf.len == 0) && (datbuf.len == 0)) { /* * getmsg() returned no data - this indicates * that the connection is closing down. */ cleanup(0); } if (ctlbuf.len <= 0) { nbytes += datbuf.len; datbuf.buf += datbuf.len; continue; } if (tpi.type == T_DATA_REQ) { return (nbytes); } if ((tpi.type == T_ORDREL_IND) || (tpi.type == T_DISCON_IND)) cleanup(0); } } /* * 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); }