$NetBSD: patch-bk,v 1.1 2000/03/20 02:25:43 itojun Exp $ --- sshconnect.c- Wed May 12 20:19:29 1999 +++ sshconnect.c Mon Mar 20 09:20:32 2000 @@ -215,7 +215,6 @@ #include "userfile.h" #include "emulate.h" -#ifdef KERBEROS #ifdef KRB5 #include @@ -223,7 +222,19 @@ krb5_context ssh_context = 0; krb5_auth_context auth_context = 0; #endif /* KRB5 */ -#endif /* KERBEROS */ + +#ifdef KRB4 +#include +#ifdef AFS +#if defined(HAVE_SYS_IOCTL_H) && SunOS != 4 +#include +#endif +#ifdef HAVE_SYS_FILIO_H +#include +#endif +#include +#endif /* AFS */ +#endif /* KRB4 */ /* Session id for the current session. */ unsigned char session_id[16]; @@ -337,7 +348,7 @@ /* Creates a (possibly privileged) socket for use as the ssh connection. */ -int ssh_create_socket(uid_t original_real_uid, int privileged) +int ssh_create_socket(uid_t original_real_uid, int privileged, int family) { int sock; @@ -345,43 +356,62 @@ bind our own socket to a privileged port. */ if (privileged) { - struct sockaddr_in sin; + struct addrinfo hints, *ai = NULL; + int errgai; + char strport[PORTSTRLEN]; int p; for (p = 1023; p > 512; p--) { - sock = socket(AF_INET, SOCK_STREAM, 0); + sock = socket(family, SOCK_STREAM, 0); if (sock < 0) - fatal("socket: %.100s", strerror(errno)); + { + error("socket: %.100s", strerror(errno)); + continue; + } - /* Initialize the desired sockaddr_in structure. */ - memset(&sin, 0, sizeof(sin)); - sin.sin_family = AF_INET; - sin.sin_addr.s_addr = INADDR_ANY; - sin.sin_port = htons(p); + /* Initialize the desired addrinfo structure. */ + memset(&hints, 0, sizeof(hints)); + hints.ai_family = family; + hints.ai_flags = AI_PASSIVE; + hints.ai_socktype = SOCK_STREAM; + sprintf(strport, "%d", p); + if ((errgai = getaddrinfo(NULL, strport, &hints, &ai)) != 0) + { + error("getaddrinfo: %.100s", gai_strerror(errgai)); + close(sock); + continue; + } /* Try to bind the socket to the privileged port. */ #if defined(SOCKS) - if (Rbind(sock, (struct sockaddr *)&sin, sizeof(sin)) >= 0) + if (Rbind(sock, ai->ai_addr, ai->ai_addrlen) >= 0) break; /* Success. */ #else /* SOCKS */ - if (bind(sock, (struct sockaddr *)&sin, sizeof(sin)) >= 0) + if (bind(sock, ai->ai_addr, ai->ai_addrlen) >= 0) break; /* Success. */ #endif /* SOCKS */ if (errno == EADDRINUSE) { close(sock); + if (ai) + { + freeaddrinfo(ai); + ai = NULL; + } continue; } - fatal("bind: %.100s", strerror(errno)); + error("bind: %.100s", strerror(errno)); } debug("Allocated local port %d.", p); + if (ai) + freeaddrinfo(ai); } else { /* Just create an ordinary socket on arbitrary port. */ - sock = socket(AF_INET, SOCK_STREAM, 0); + sock = socket(family, SOCK_STREAM, 0); if (sock < 0) - fatal("socket: %.100s", strerror(errno)); + error("socket: %.100s", strerror(errno)); } return sock; } @@ -396,14 +426,19 @@ the daemon. */ int ssh_connect(const char *host, int port, int connection_attempts, +#ifdef ENABLE_ANOTHER_PORT_TRY + int another_port, +#endif /* ENABLE_ANOTHER_PORT_TRY */ int anonymous, uid_t original_real_uid, const char *proxy_command, RandomState *random_state) { int sock = -1, attempt, i; int on = 1; struct servent *sp; - struct hostent *hp; - struct sockaddr_in hostaddr; + struct addrinfo hints, *ai, *aitop, *aitmp; + struct sockaddr_storage hostaddr; + char ntop[ADDRSTRLEN], strport[PORTSTRLEN]; + int gaierr; #if defined(SO_LINGER) && defined(ENABLE_SO_LINGER) struct linger linger; #endif /* SO_LINGER */ @@ -421,10 +456,6 @@ port = SSH_DEFAULT_PORT; } - /* Map localhost to ip-address locally */ - if (strcmp(host, "localhost") == 0) - host = "127.0.0.1"; - /* If a proxy command is given, connect using it. */ if (proxy_command != NULL && *proxy_command) return ssh_proxy_connect(host, port, original_real_uid, proxy_command, @@ -432,9 +463,28 @@ /* No proxy command. */ - /* No host lookup made yet. */ - hp = NULL; - + memset(&hints, 0, sizeof(hints)); + hints.ai_family = IPv4or6; + hints.ai_socktype = SOCK_STREAM; + sprintf(strport, "%d", port); + if ((gaierr = getaddrinfo(host, strport, &hints, &aitop)) != 0) + fatal("Bad host name: %.100s (%s)", host, gai_strerror(gaierr)); + +#ifdef ENABLE_ANOTHER_PORT_TRY + if (another_port) + { + aitmp = aitop; + memset(&hints, 0, sizeof(hints)); + hints.ai_family = IPv4or6; + hints.ai_socktype = SOCK_STREAM; + sprintf(strport, "%d", another_port); + if ((gaierr = getaddrinfo(host, strport, &hints, &aitop)) != 0) + fatal("Bad host name: %.100s (%s)", host, gai_strerror(gaierr)); + for (ai = aitop; ai->ai_next; ai = ai->ai_next); + ai->ai_next = aitmp; + } +#endif /* ENABLE_ANOTHER_PORT_TRY */ + /* Try to connect several times. On some machines, the first time will sometimes fail. In general socket code appears to behave quite magically on many machines. */ @@ -443,103 +493,29 @@ if (attempt > 0) debug("Trying again..."); - /* Try to parse the host name as a numeric inet address. */ - memset(&hostaddr, 0, sizeof(hostaddr)); - hostaddr.sin_family = AF_INET; - hostaddr.sin_port = htons(port); -#ifdef BROKEN_INET_ADDR - hostaddr.sin_addr.s_addr = inet_network(host); -#else /* BROKEN_INET_ADDR */ - hostaddr.sin_addr.s_addr = inet_addr(host); -#endif /* BROKEN_INET_ADDR */ - if ((hostaddr.sin_addr.s_addr & 0xffffffff) != 0xffffffff) - { - /* Create a socket. */ - sock = ssh_create_socket(original_real_uid, - !anonymous && geteuid() == UID_ROOT); - - /* Valid numeric IP address */ - debug("Connecting to %.100s port %d.", - inet_ntoa(hostaddr.sin_addr), port); - - /* Connect to the host. */ -#if defined(SOCKS) - if (Rconnect(sock, (struct sockaddr *)&hostaddr, sizeof(hostaddr)) -#else /* SOCKS */ - if (connect(sock, (struct sockaddr *)&hostaddr, sizeof(hostaddr)) -#endif /* SOCKS */ - >= 0) - { - /* Successful connect. */ - break; - } - debug("connect: %.100s", strerror(errno)); - - /* Destroy the failed socket. */ - shutdown(sock, 2); - close(sock); - } - else - { - /* Not a valid numeric inet address. */ - /* Map host name to an address. */ - if (!hp) - { - struct hostent *hp_static; - -#if defined(SOCKS5) - hp_static = Rgethostbyname(host); -#else - hp_static = gethostbyname(host); -#endif - if (hp_static) - { - hp = xmalloc(sizeof(struct hostent)); - memcpy(hp, hp_static, sizeof(struct hostent)); - - /* Copy list of addresses, not just pointers. - We don't use h_name & h_aliases so leave them as is */ - for (i = 0; hp_static->h_addr_list[i]; i++) - ; /* count them */ - hp->h_addr_list = xmalloc((i + 1) * - sizeof(hp_static->h_addr_list[0])); - for (i = 0; hp_static->h_addr_list[i]; i++) - { - hp->h_addr_list[i] = xmalloc(hp->h_length); - memcpy(hp->h_addr_list[i], hp_static->h_addr_list[i], - hp->h_length); - } - hp->h_addr_list[i] = NULL; /* last one */ - } - } - if (!hp) - fatal("Bad host name: %.100s", host); - if (!hp->h_addr_list[0]) - fatal("Host does not have an IP address: %.100s", host); - /* Loop through addresses for this host, and try each one in sequence until the connection succeeds. */ - for (i = 0; hp->h_addr_list[i]; i++) + for (ai = aitop; ai; ai = ai->ai_next) { - /* Set the address to connect to. */ - hostaddr.sin_family = hp->h_addrtype; - memcpy(&hostaddr.sin_addr, hp->h_addr_list[i], - sizeof(hostaddr.sin_addr)); + getnameinfo(ai->ai_addr, ai->ai_addrlen, + ntop, sizeof(ntop), strport, sizeof(strport), + NI_NUMERICHOST|NI_NUMERICSERV); - debug("Connecting to %.200s [%.100s] port %d.", - host, inet_ntoa(hostaddr.sin_addr), port); + debug("Connecting to %.200s [%.100s] port %s.", + host, ntop, strport); /* Create a socket for connecting. */ sock = ssh_create_socket(original_real_uid, - !anonymous && geteuid() == UID_ROOT); + !anonymous && geteuid() == UID_ROOT, + ai->ai_family); + if (sock < 0) + continue; /* Connect to the host. */ #if defined(SOCKS) - if (Rconnect(sock, (struct sockaddr *)&hostaddr, - sizeof(hostaddr)) >= 0) + if (Rconnect(sock, ai->ai_addr, ai->ai_addrlen) >= 0) #else /* SOCKS */ - if (connect(sock, (struct sockaddr *)&hostaddr, - sizeof(hostaddr)) >= 0) + if (connect(sock, ai->ai_addr, ai->ai_addrlen) >= 0) #endif /* SOCKS */ { /* Successful connection. */ @@ -552,22 +528,15 @@ returned an error. */ shutdown(sock, 2); close(sock); - } - if (hp->h_addr_list[i]) + } /* for (ai = aitop; ai; ai = ai->ai_next) */ + if (ai) break; /* Successful connection. */ - } /* Sleep a moment before retrying. */ sleep(1); } - if (hp) - { - for (i = 0; hp->h_addr_list[i]; i++) - xfree(hp->h_addr_list[i]); - xfree(hp->h_addr_list); - xfree(hp); - } + freeaddrinfo(aitop); /* Return failure if we didn't get a successful connection. */ if (attempt >= connection_attempts) @@ -932,10 +901,9 @@ return 0; } -#ifdef KERBEROS +#ifdef KRB5 int try_kerberos_authentication(void) { -#ifdef KRB5 char *remotehost; krb5_data auth; krb5_error_code r; @@ -946,7 +914,7 @@ int ap_opts, ret_stat = 0; krb5_keyblock *session_key = 0; krb5_ap_rep_enc_part *repl = 0; - struct sockaddr_in local, foreign; + struct sockaddr_storage local, foreign; memset(&auth, 0 , sizeof(auth)); remotehost = (char *) get_canonical_hostname(); @@ -1084,15 +1052,118 @@ krb5_free_ap_rep_enc_part(ssh_context, repl); return(ret_stat); +} #endif /* KRB5 */ + +#ifdef KRB4 +int try_kerberos_authentication() +{ + KTEXT_ST auth; /* Kerberos data */ + char *reply; + char inst[INST_SZ]; + char *realm; + char *service; + CREDENTIALS cred; + int r, type; + Key_schedule schedule; + u_long checksum, cksum; + MSG_DAT msg_data; + struct sockaddr_in local, foreign; + struct stat st; + + /* Don't do anything if we don't have any tickets. */ + if (stat(tkt_string(), &st) < 0) return 0; + + strncpy(inst, (char *) krb_get_phost(get_canonical_hostname()), INST_SZ); + + realm = (char *)krb_realmofhost(get_canonical_hostname()); + if (!realm) { + debug("Kerberos V4: no realm for %s", get_canonical_hostname()); + return 0; + } + /* This can really be anything. */ + checksum = (u_long) getpid(); + + if (r = krb_mk_req(&auth, KRB4_SERVICE_NAME, inst, realm, checksum)) { + debug("Kerberos V4 krb_mk_req failed: %s", krb_err_txt[r]); + return 0; + } + /* Get session key to decrypt the server's reply with. */ + if (r = krb_get_cred(KRB4_SERVICE_NAME, inst, realm, &cred)) { + debug("get_cred failed: %s", krb_err_txt[r]); + return 0; + } + des_key_sched((des_cblock *)cred.session, schedule); + + /* Send authentication info to server. */ + packet_start(SSH_CMSG_AUTH_KERBEROS); + packet_put_string((char *)auth.dat, auth.length); + packet_send(); + packet_write_wait(); + + /* zero the buffer */ + (void) memset(auth.dat, 0, MAX_KTXT_LEN); + + r = sizeof(local); + memset(&local, 0, sizeof(local)); + if (getsockname(packet_get_connection_in(), + (struct sockaddr *) &local, &r) < 0) + debug("getsockname failed: %.100s", strerror(errno)); + + r = sizeof(foreign); + memset(&foreign, 0, sizeof(foreign)); + if (getpeername(packet_get_connection_in(), + (struct sockaddr *)&foreign, &r) < 0) + debug("getpeername failed: %.100s", strerror(errno)); + + /* Get server reply. */ + type = packet_read(); + switch(type) { + + case SSH_SMSG_FAILURE: /* Should really be SSH_SMSG_AUTH_KERBEROS_FAILURE */ + debug("Kerberos V4 authentication failed."); + return 0; + break; + + case SSH_SMSG_AUTH_KERBEROS_RESPONSE: /* SSH_SMSG_AUTH_KERBEROS_SUCCESS */ + debug("Kerberos V4 authentication accepted."); + + /* Get server's response. */ + reply = packet_get_string((unsigned int *)&auth.length); + memcpy(auth.dat, reply, auth.length); + xfree(reply); + + /* If his response isn't properly encrypted with the session key, + and the decrypted checksum fails to match, he's bogus. Bail out. */ + if (r = krb_rd_priv(auth.dat, auth.length, schedule, &cred.session, + &foreign, &local, &msg_data)) { + debug("Kerberos V4 krb_rd_priv failed: %s", krb_err_txt[r]); + packet_disconnect("Kerberos V4 challenge failed!"); + } + /* fetch the (incremented) checksum that we supplied in the request */ + (void)memcpy((char *)&cksum, (char *)msg_data.app_data, sizeof(cksum)); + cksum = ntohl(cksum); + + /* If it matches, we're golden. */ + if (cksum == checksum + 1) { + debug("Kerberos V4 challenge successful."); + return 1; + } + else + packet_disconnect("Kerberos V4 challenge failed!"); + break; + + default: + packet_disconnect("Protocol error on Kerberos V4 response: %d", type); + } } -#endif /* KERBEROS */ +#endif /* KRB4 */ + -#ifdef KERBEROS_TGT_PASSING /* Forward our local Kerberos tgt to the server. */ +#ifdef KRB5 int send_kerberos_tgt(void) { -#ifdef KRB5 char *remotehost; krb5_principal client; krb5_principal server; @@ -1172,22 +1243,117 @@ krb5_free_principal(ssh_context, client); krb5_free_principal(ssh_context, server); - type = packet_read(); - if (type == SSH_SMSG_SUCCESS) - { - debug("Kerberos V5 TGT passing was successful."); - return 1; - } - else - if (type != SSH_SMSG_FAILURE) - packet_disconnect("Protocol error on Kerberos tgt response: %d", type); - else - debug("Kerberos V5 TGT passing failed."); - - return 0; + return 1; +} #endif /* KRB5 */ + +#ifdef AFS +int send_kerberos_tgt() +{ + CREDENTIALS *creds; + char pname[ANAME_SZ], pinst[INST_SZ], prealm[REALM_SZ]; + int r, type; + unsigned char buffer[8192]; + struct stat st; + + /* Don't do anything if we don't have any tickets. */ + if (stat(tkt_string(), &st) < 0) return 0; + + creds = xmalloc(sizeof(CREDENTIALS)); + + if ((r=krb_get_tf_fullname(TKT_FILE,pname,pinst,prealm)) != KSUCCESS) { + debug("Kerberos V4 tf_fullname failed: %s",krb_err_txt[r]); + return 0; + } + if ((r=krb_get_cred("krbtgt", prealm, prealm, creds)) != GC_OK) { + debug("Kerberos V4 get_cred failed: %s", krb_err_txt[r]); + return 0; + } + if (time(0) > +#ifdef HAVE_KRB_LIFE_TO_TIME + (unsigned long)krb_life_to_time(creds->issue_date, creds->lifetime)) { +#else + (creds->issue_date + ((unsigned char)creds->lifetime * 5 * 60))) { +#endif /* HAVE_KRB_LIFE_TO_TIME */ + debug("Kerberos V4 ticket expired: %s", TKT_FILE); + return 0; + } + + creds_to_radix(creds, buffer); + xfree(creds); + + packet_start(SSH_CMSG_HAVE_KERBEROS_TGT); + packet_put_string((char *)buffer, strlen(buffer)); + packet_send(); + packet_write_wait(); + + return 1; +} + +/* Forwards our AFS tokens to the server. */ +void send_afs_tokens(void) +{ + CREDENTIALS creds; + struct ViceIoctl parms; + struct ClearToken ct; + int i, type; + int len; + char buf[2048], *p, *server_cell; + unsigned char buffer[8192]; + + /* Move over ktc_GetToken, here's something leaner. */ + for (i = 0; i < 100; i++) { /* just in case */ + parms.in = (char *)&i; + parms.in_size = sizeof(i); + parms.out = buf; + parms.out_size = sizeof(buf); + if (k_pioctl(0, VIOCGETTOK, &parms, 0) != 0) break; + p = buf; + + /* Get secret token. */ + memcpy(&creds.ticket_st.length, p, sizeof(unsigned int)); + if (creds.ticket_st.length > MAX_KTXT_LEN) break; + p += sizeof(unsigned int); + memcpy(creds.ticket_st.dat, p, creds.ticket_st.length); + p += creds.ticket_st.length; + + /* Get clear token. */ + memcpy(&len, p, sizeof(len)); + if (len != sizeof(struct ClearToken)) break; + p += sizeof(len); + memcpy(&ct, p, len); + p += len; + p += sizeof(len); /* primary flag */ + server_cell = p; + + /* Flesh out our credentials. */ + strcpy(creds.service, "afs"); + creds.instance[0] = '\0'; + strncpy(creds.realm, server_cell, REALM_SZ); + memcpy(creds.session, ct.HandShakeKey, DES_KEY_SZ); + creds.issue_date = ct.BeginTimestamp; + creds.lifetime = krb_time_to_life(creds.issue_date, ct.EndTimestamp); + creds.kvno = ct.AuthHandle; + snprintf(creds.pname, sizeof(creds.pname), "AFS ID %d", ct.ViceId); + creds.pinst[0] = '\0'; + + /* Encode token, ship it off. */ + if (!creds_to_radix(&creds, buffer)) break; + packet_start(SSH_CMSG_HAVE_AFS_TOKEN); + packet_put_string((char *)buffer, strlen(buffer)); + packet_send(); + packet_write_wait(); + + /* Roger, Roger. Clearance, Clarence. What's your vector, Victor? */ + type = packet_read(); + + if (type == SSH_SMSG_FAILURE) + debug("AFS token for cell %s rejected.", server_cell); + else if (type != SSH_SMSG_SUCCESS) + packet_disconnect("Protocol error on AFS token response: %d", type); + } } -#endif /* KERBEROS_TGT_PASSING */ +#endif /* AFS */ /* Waits for the server identification string, and sends our own identification string. */ @@ -1285,14 +1451,12 @@ unsigned char check_bytes[8]; unsigned int supported_ciphers, supported_authentications, protocol_flags; HostStatus host_status; -#ifdef KERBEROS #ifdef KRB5 char *kuser; krb5_ccache ccache; krb5_error_code problem; krb5_principal client; -#endif -#endif +#endif /* KRB5 */ /* Convert the user-supplied hostname into all lowercase. */ host = xstrdup(orighost); @@ -1595,7 +1759,6 @@ debug("Received encrypted confirmation."); -#ifdef KERBEROS #ifdef KRB5 if (!ssh_context) { @@ -1629,7 +1792,6 @@ debug("Kerberos V5: could not get default ccache."); } #endif /* KRB5 */ -#endif /* KERBEROS */ /* Send the name of the user to log in as on the server. */ packet_start(SSH_CMSG_USER); @@ -1647,24 +1809,39 @@ packet_disconnect("Protocol error: got %d in response to SSH_CMSG_USER", type); -#ifdef KERBEROS_TGT_PASSING +#if defined(KRB5) || defined(AFS) /* Try Kerberos tgt passing if the server supports it. */ if ((supported_authentications & (1 << SSH_PASS_KERBEROS_TGT)) && options->kerberos_tgt_passing) { if (options->cipher == SSH_CIPHER_NONE) log_msg("WARNING: Encryption is disabled! Ticket will be transmitted in the clear!"); - (void)send_kerberos_tgt(); + if (send_kerberos_tgt()) + { + type = packet_read(); + if (type == SSH_SMSG_FAILURE) + debug("Kerberos TGT passing failed."); + else if (type != SSH_SMSG_SUCCESS) + packet_disconnect("Protocol error on Kerberos tgt response: %d", type); + } } -#endif /* KERBEROS_TGT_PASSING */ +#endif /* KRB5 || AFS */ + +#ifdef AFS + /* Try AFS token passing if the server supports it. */ + if ((supported_authentications & (1 << SSH_PASS_AFS_TOKEN)) && + options->afs_token_passing && k_hasafs()) { + if (options->cipher == SSH_CIPHER_NONE) + log_msg("WARNING: Encryption is disabled! Token will be transmitted in the clear!"); + send_afs_tokens(); + } +#endif /* AFS */ -#ifdef KERBEROS -#ifdef KRB5 +#if defined(KRB4) || defined(KRB5) if ((supported_authentications & (1 << SSH_AUTH_KERBEROS)) && options->kerberos_authentication) { - debug("Trying Kerberos V5 authentication."); -#endif + debug("Trying Kerberos authentication."); if (try_kerberos_authentication()) { /* The server should respond with success or failure. */ type = packet_read(); @@ -1673,10 +1850,8 @@ if (type != SSH_SMSG_FAILURE) packet_disconnect("Protocol error: got %d in response to Kerberos auth", type); } -#ifdef KRB5 } -#endif -#endif /* KERBEROS */ +#endif /* KRB4 || KRB5 */ /* Use rhosts authentication if running in privileged socket and we do not wish to remain anonymous. */