/* sshconnect.c Author: Tatu Ylonen Copyright (c) 1995 Tatu Ylonen , Espoo, Finland All rights reserved Created: Sat Mar 18 22:15:47 1995 ylo Code to connect to a remote host, and to perform the client side of the login (authentication) dialog. */ #include "config.h" #include "includes.h" RCSID("$Id: sshconnect.c,v 1.2 1999/10/28 03:25:17 damien Exp $"); #ifdef HAVE_OPENSSL #include #include #endif #ifdef HAVE_SSL #include #include #endif #include "xmalloc.h" #include "rsa.h" #include "ssh.h" #include "packet.h" #include "authfd.h" #include "cipher.h" #include "mpaux.h" #include "uidswap.h" #include "compat.h" /* Session id for the current session. */ unsigned char session_id[16]; /* Connect to the given ssh server using a proxy command. */ int ssh_proxy_connect(const char *host, int port, uid_t original_real_uid, const char *proxy_command) { Buffer command; const char *cp; char *command_string; int pin[2], pout[2]; int pid; char portstring[100]; /* Convert the port number into a string. */ snprintf(portstring, sizeof portstring, "%d", port); /* Build the final command string in the buffer by making the appropriate substitutions to the given proxy command. */ buffer_init(&command); for (cp = proxy_command; *cp; cp++) { if (cp[0] == '%' && cp[1] == '%') { buffer_append(&command, "%", 1); cp++; continue; } if (cp[0] == '%' && cp[1] == 'h') { buffer_append(&command, host, strlen(host)); cp++; continue; } if (cp[0] == '%' && cp[1] == 'p') { buffer_append(&command, portstring, strlen(portstring)); cp++; continue; } buffer_append(&command, cp, 1); } buffer_append(&command, "\0", 1); /* Get the final command string. */ command_string = buffer_ptr(&command); /* Create pipes for communicating with the proxy. */ if (pipe(pin) < 0 || pipe(pout) < 0) fatal("Could not create pipes to communicate with the proxy: %.100s", strerror(errno)); debug("Executing proxy command: %.500s", command_string); /* Fork and execute the proxy command. */ if ((pid = fork()) == 0) { char *argv[10]; /* Child. Permanently give up superuser privileges. */ permanently_set_uid(original_real_uid); /* Redirect stdin and stdout. */ close(pin[1]); if (pin[0] != 0) { if (dup2(pin[0], 0) < 0) perror("dup2 stdin"); close(pin[0]); } close(pout[0]); if (dup2(pout[1], 1) < 0) perror("dup2 stdout"); close(pout[1]); /* Cannot be 1 because pin allocated two descriptors. */ /* Stderr is left as it is so that error messages get printed on the user's terminal. */ argv[0] = "/bin/sh"; argv[1] = "-c"; argv[2] = command_string; argv[3] = NULL; /* Execute the proxy command. Note that we gave up any extra privileges above. */ execv("/bin/sh", argv); perror("/bin/sh"); exit(1); } /* Parent. */ if (pid < 0) fatal("fork failed: %.100s", strerror(errno)); /* Close child side of the descriptors. */ close(pin[0]); close(pout[1]); /* Free the command name. */ buffer_free(&command); /* Set the connection file descriptors. */ packet_set_connection(pout[0], pin[1]); return 1; } /* Creates a (possibly privileged) socket for use as the ssh connection. */ int ssh_create_socket(uid_t original_real_uid, int privileged) { int sock; /* If we are running as root and want to connect to a privileged port, bind our own socket to a privileged port. */ if (privileged) { int p = IPPORT_RESERVED - 1; sock = rresvport(&p); if (sock < 0) fatal("rresvport: %.100s", strerror(errno)); debug("Allocated local port %d.", p); } else { /* Just create an ordinary socket on arbitrary port. We use the user's uid to create the socket. */ temporarily_use_uid(original_real_uid); sock = socket(AF_INET, SOCK_STREAM, 0); if (sock < 0) fatal("socket: %.100s", strerror(errno)); restore_uid(); } return sock; } /* Opens a TCP/IP connection to the remote server on the given host. If port is 0, the default port will be used. If anonymous is zero, a privileged port will be allocated to make the connection. This requires super-user privileges if anonymous is false. Connection_attempts specifies the maximum number of tries (one per second). If proxy_command is non-NULL, it specifies the command (with %h and %p substituted for host and port, respectively) to use to contact the daemon. */ int ssh_connect(const char *host, struct sockaddr_in *hostaddr, int port, int connection_attempts, int anonymous, uid_t original_real_uid, const char *proxy_command) { int sock = -1, attempt, i; int on = 1; struct servent *sp; struct hostent *hp; struct linger linger; debug("ssh_connect: getuid %d geteuid %d anon %d", (int)getuid(), (int)geteuid(), anonymous); /* Get default port if port has not been set. */ if (port == 0) { sp = getservbyname(SSH_SERVICE_NAME, "tcp"); if (sp) port = ntohs(sp->s_port); else port = SSH_DEFAULT_PORT; } /* If a proxy command is given, connect using it. */ if (proxy_command != NULL) return ssh_proxy_connect(host, port, original_real_uid, proxy_command); /* No proxy command. */ /* No host lookup made yet. */ hp = NULL; /* 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. */ for (attempt = 0; attempt < connection_attempts; attempt++) { 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); hostaddr->sin_addr.s_addr = inet_addr(host); if ((hostaddr->sin_addr.s_addr & 0xffffffff) != 0xffffffff) { /* Valid numeric IP address */ debug("Connecting to %.100s port %d.", inet_ntoa(hostaddr->sin_addr), port); /* Create a socket. */ sock = ssh_create_socket(original_real_uid, !anonymous && geteuid() == 0 && port < IPPORT_RESERVED); /* Connect to the host. We use the user's uid in the hope that it will help with the problems of tcp_wrappers showing the remote uid as root. */ temporarily_use_uid(original_real_uid); if (connect(sock, (struct sockaddr *)hostaddr, sizeof(*hostaddr)) >= 0) { /* Successful connect. */ restore_uid(); break; } debug("connect: %.100s", strerror(errno)); restore_uid(); /* Destroy the failed socket. */ shutdown(sock, SHUT_RDWR); close(sock); } else { /* Not a valid numeric inet address. */ /* Map host name to an address. */ if (!hp) hp = gethostbyname(host); 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++) { /* 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)); debug("Connecting to %.200s [%.100s] port %d.", host, inet_ntoa(hostaddr->sin_addr), port); /* Create a socket for connecting. */ sock = ssh_create_socket(original_real_uid, !anonymous && geteuid() == 0 && port < IPPORT_RESERVED); /* Connect to the host. We use the user's uid in the hope that it will help with tcp_wrappers showing the remote uid as root. */ temporarily_use_uid(original_real_uid); if (connect(sock, (struct sockaddr *)hostaddr, sizeof(*hostaddr)) >= 0) { /* Successful connection. */ restore_uid(); break; } debug("connect: %.100s", strerror(errno)); restore_uid(); /* Close the failed socket; there appear to be some problems when reusing a socket for which connect() has already returned an error. */ shutdown(sock, SHUT_RDWR); close(sock); } if (hp->h_addr_list[i]) break; /* Successful connection. */ } /* Sleep a moment before retrying. */ sleep(1); } /* Return failure if we didn't get a successful connection. */ if (attempt >= connection_attempts) return 0; debug("Connection established."); /* Set socket options. We would like the socket to disappear as soon as it has been closed for whatever reason. */ /* setsockopt(sock, SOL_SOCKET, SO_REUSEADDR, (void *)&on, sizeof(on)); */ setsockopt(sock, IPPROTO_TCP, TCP_NODELAY, (void *)&on, sizeof(on)); linger.l_onoff = 1; linger.l_linger = 5; setsockopt(sock, SOL_SOCKET, SO_LINGER, (void *)&linger, sizeof(linger)); /* Set the connection. */ packet_set_connection(sock, sock); return 1; } /* Checks if the user has an authentication agent, and if so, tries to authenticate using the agent. */ int try_agent_authentication() { int status, type, bits; char *comment; AuthenticationConnection *auth; unsigned char response[16]; unsigned int i; BIGNUM *e, *n, *challenge; /* Get connection to the agent. */ auth = ssh_get_authentication_connection(); if (!auth) return 0; e = BN_new(); n = BN_new(); challenge = BN_new(); /* Loop through identities served by the agent. */ for (status = ssh_get_first_identity(auth, &bits, e, n, &comment); status; status = ssh_get_next_identity(auth, &bits, e, n, &comment)) { int plen, clen; /* Try this identity. */ debug("Trying RSA authentication via agent with '%.100s'", comment); xfree(comment); /* Tell the server that we are willing to authenticate using this key. */ packet_start(SSH_CMSG_AUTH_RSA); packet_put_bignum(n); packet_send(); packet_write_wait(); /* Wait for server's response. */ type = packet_read(&plen); /* The server sends failure if it doesn\'t like our key or does not support RSA authentication. */ if (type == SSH_SMSG_FAILURE) { debug("Server refused our key."); continue; } /* Otherwise it should have sent a challenge. */ if (type != SSH_SMSG_AUTH_RSA_CHALLENGE) packet_disconnect("Protocol error during RSA authentication: %d", type); packet_get_bignum(challenge, &clen); packet_integrity_check(plen, clen, type); debug("Received RSA challenge from server."); /* Ask the agent to decrypt the challenge. */ if (!ssh_decrypt_challenge(auth, bits, e, n, challenge, session_id, 1, response)) { /* The agent failed to authenticate this identifier although it advertised it supports this. Just return a wrong value. */ log("Authentication agent failed to decrypt challenge."); memset(response, 0, sizeof(response)); } debug("Sending response to RSA challenge."); /* Send the decrypted challenge back to the server. */ packet_start(SSH_CMSG_AUTH_RSA_RESPONSE); for (i = 0; i < 16; i++) packet_put_char(response[i]); packet_send(); packet_write_wait(); /* Wait for response from the server. */ type = packet_read(&plen); /* The server returns success if it accepted the authentication. */ if (type == SSH_SMSG_SUCCESS) { debug("RSA authentication accepted by server."); BN_clear_free(e); BN_clear_free(n); BN_clear_free(challenge); return 1; } /* Otherwise it should return failure. */ if (type != SSH_SMSG_FAILURE) packet_disconnect("Protocol error waiting RSA auth response: %d", type); } BN_clear_free(e); BN_clear_free(n); BN_clear_free(challenge); debug("RSA authentication using agent refused."); return 0; } /* Computes the proper response to a RSA challenge, and sends the response to the server. */ void respond_to_rsa_challenge(BIGNUM *challenge, RSA *prv) { unsigned char buf[32], response[16]; MD5_CTX md; int i, len; /* Decrypt the challenge using the private key. */ rsa_private_decrypt(challenge, challenge, prv); /* Compute the response. */ /* The response is MD5 of decrypted challenge plus session id. */ len = BN_num_bytes(challenge); assert(len <= sizeof(buf) && len); memset(buf, 0, sizeof(buf)); BN_bn2bin(challenge, buf + sizeof(buf) - len); MD5_Init(&md); MD5_Update(&md, buf, 32); MD5_Update(&md, session_id, 16); MD5_Final(response, &md); debug("Sending response to host key RSA challenge."); /* Send the response back to the server. */ packet_start(SSH_CMSG_AUTH_RSA_RESPONSE); for (i = 0; i < 16; i++) packet_put_char(response[i]); packet_send(); packet_write_wait(); memset(buf, 0, sizeof(buf)); memset(response, 0, sizeof(response)); memset(&md, 0, sizeof(md)); } /* Checks if the user has authentication file, and if so, tries to authenticate the user using it. */ int try_rsa_authentication(struct passwd *pw, const char *authfile, int may_ask_passphrase) { BIGNUM *challenge; RSA *private_key; RSA *public_key; char *passphrase, *comment; int type, i; int plen, clen; /* Try to load identification for the authentication key. */ public_key = RSA_new(); if (!load_public_key(authfile, public_key, &comment)) { RSA_free(public_key); return 0; /* Could not load it. Fail. */ } debug("Trying RSA authentication with key '%.100s'", comment); /* Tell the server that we are willing to authenticate using this key. */ packet_start(SSH_CMSG_AUTH_RSA); packet_put_bignum(public_key->n); packet_send(); packet_write_wait(); /* We no longer need the public key. */ RSA_free(public_key); /* Wait for server's response. */ type = packet_read(&plen); /* The server responds with failure if it doesn\'t like our key or doesn\'t support RSA authentication. */ if (type == SSH_SMSG_FAILURE) { debug("Server refused our key."); xfree(comment); return 0; /* Server refuses to authenticate with this key. */ } /* Otherwise, the server should respond with a challenge. */ if (type != SSH_SMSG_AUTH_RSA_CHALLENGE) packet_disconnect("Protocol error during RSA authentication: %d", type); /* Get the challenge from the packet. */ challenge = BN_new(); packet_get_bignum(challenge, &clen); packet_integrity_check(plen, clen, type); debug("Received RSA challenge from server."); private_key = RSA_new(); /* Load the private key. Try first with empty passphrase; if it fails, ask for a passphrase. */ if (!load_private_key(authfile, "", private_key, NULL)) { char buf[300]; /* Request passphrase from the user. We read from /dev/tty to make this work even if stdin has been redirected. If running in batch mode, we just use the empty passphrase, which will fail and return. */ snprintf(buf, sizeof buf, "Enter passphrase for RSA key '%.100s': ", comment); if (may_ask_passphrase) passphrase = read_passphrase(buf, 0); else { debug("Will not query passphrase for %.100s in batch mode.", comment); passphrase = xstrdup(""); } /* Load the authentication file using the pasphrase. */ if (!load_private_key(authfile, passphrase, private_key, NULL)) { memset(passphrase, 0, strlen(passphrase)); xfree(passphrase); error("Bad passphrase."); /* Send a dummy response packet to avoid protocol error. */ packet_start(SSH_CMSG_AUTH_RSA_RESPONSE); for (i = 0; i < 16; i++) packet_put_char(0); packet_send(); packet_write_wait(); /* Expect the server to reject it... */ packet_read_expect(&plen, SSH_SMSG_FAILURE); xfree(comment); return 0; } /* Destroy the passphrase. */ memset(passphrase, 0, strlen(passphrase)); xfree(passphrase); } /* We no longer need the comment. */ xfree(comment); /* Compute and send a response to the challenge. */ respond_to_rsa_challenge(challenge, private_key); /* Destroy the private key. */ RSA_free(private_key); /* We no longer need the challenge. */ BN_clear_free(challenge); /* Wait for response from the server. */ type = packet_read(&plen); if (type == SSH_SMSG_SUCCESS) { debug("RSA authentication accepted by server."); return 1; } if (type != SSH_SMSG_FAILURE) packet_disconnect("Protocol error waiting RSA auth response: %d", type); debug("RSA authentication refused."); return 0; } /* Tries to authenticate the user using combined rhosts or /etc/hosts.equiv authentication and RSA host authentication. */ int try_rhosts_rsa_authentication(const char *local_user, RSA *host_key) { int type; BIGNUM *challenge; int plen, clen; debug("Trying rhosts or /etc/hosts.equiv with RSA host authentication."); /* Tell the server that we are willing to authenticate using this key. */ packet_start(SSH_CMSG_AUTH_RHOSTS_RSA); packet_put_string(local_user, strlen(local_user)); packet_put_int(BN_num_bits(host_key->n)); packet_put_bignum(host_key->e); packet_put_bignum(host_key->n); packet_send(); packet_write_wait(); /* Wait for server's response. */ type = packet_read(&plen); /* The server responds with failure if it doesn't admit our .rhosts authentication or doesn't know our host key. */ if (type == SSH_SMSG_FAILURE) { debug("Server refused our rhosts authentication or host key."); return 0; /* Server refuses to authenticate us with this method. */ } /* Otherwise, the server should respond with a challenge. */ if (type != SSH_SMSG_AUTH_RSA_CHALLENGE) packet_disconnect("Protocol error during RSA authentication: %d", type); /* Get the challenge from the packet. */ challenge = BN_new(); packet_get_bignum(challenge, &clen); packet_integrity_check(plen, clen, type); debug("Received RSA challenge for host key from server."); /* Compute a response to the challenge. */ respond_to_rsa_challenge(challenge, host_key); /* We no longer need the challenge. */ BN_clear_free(challenge); /* Wait for response from the server. */ type = packet_read(&plen); if (type == SSH_SMSG_SUCCESS) { debug("Rhosts or /etc/hosts.equiv with RSA host authentication accepted by server."); return 1; } if (type != SSH_SMSG_FAILURE) packet_disconnect("Protocol error waiting RSA auth response: %d", type); debug("Rhosts or /etc/hosts.equiv with RSA host authentication refused."); return 0; } #ifdef KRB4 int try_kerberos_authentication() { KTEXT_ST auth; /* Kerberos data */ char *reply; char inst[INST_SZ]; char *realm; CREDENTIALS cred; int r, type, plen; 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(); r = krb_mk_req(&auth, KRB4_SERVICE_NAME, inst, realm, checksum); if (r != KSUCCESS) { debug("Kerberos V4 krb_mk_req failed: %s", krb_err_txt[r]); return 0; } /* Get session key to decrypt the server's reply with. */ r = krb_get_cred(KRB4_SERVICE_NAME, inst, realm, &cred); if (r != KSUCCESS) { 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: %s", strerror(errno)); r = sizeof(foreign); memset(&foreign, 0, sizeof(foreign)); if (getpeername(packet_get_connection_in(), (struct sockaddr *)&foreign, &r) < 0) debug("getpeername failed: %s", strerror(errno)); /* Get server reply. */ type = packet_read(&plen); 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); packet_integrity_check(plen, 4 + auth.length, type); /* If his response isn't properly encrypted with the session key, and the decrypted checksum fails to match, he's bogus. Bail out. */ r = krb_rd_priv(auth.dat, auth.length, schedule, &cred.session, &foreign, &local, &msg_data); if (r != KSUCCESS) { 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); } return 0; } #endif /* KRB4 */ #ifdef AFS int send_kerberos_tgt() { CREDENTIALS *creds; char pname[ANAME_SZ], pinst[INST_SZ], prealm[REALM_SZ]; int r, type, plen; 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(*creds)); 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) > krb_life_to_time(creds->issue_date, creds->lifetime)) { 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(); type = packet_read(&plen); if (type == SSH_SMSG_FAILURE) debug("Kerberos TGT for realm %s rejected.", prealm); else if (type != SSH_SMSG_SUCCESS) packet_disconnect("Protocol error on Kerberos TGT response: %d", type); return 1; } void send_afs_tokens(void) { CREDENTIALS creds; struct ViceIoctl parms; struct ClearToken ct; int i, type, len, plen; 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. */ strlcpy(creds.service, "afs", sizeof creds.service); creds.instance[0] = '\0'; strlcpy(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(&plen); 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 /* AFS */ /* Waits for the server identification string, and sends our own identification string. */ void ssh_exchange_identification() { char buf[256], remote_version[256]; /* must be same size! */ int remote_major, remote_minor, i; int connection_in = packet_get_connection_in(); int connection_out = packet_get_connection_out(); extern Options options; /* Read other side\'s version identification. */ for (i = 0; i < sizeof(buf) - 1; i++) { if (read(connection_in, &buf[i], 1) != 1) fatal("read: %.100s", strerror(errno)); if (buf[i] == '\r') { buf[i] = '\n'; buf[i + 1] = 0; break; } if (buf[i] == '\n') { buf[i + 1] = 0; break; } } buf[sizeof(buf) - 1] = 0; /* Check that the versions match. In future this might accept several versions and set appropriate flags to handle them. */ if (sscanf(buf, "SSH-%d.%d-%[^\n]\n", &remote_major, &remote_minor, remote_version) != 3) fatal("Bad remote protocol version identification: '%.100s'", buf); debug("Remote protocol version %d.%d, remote software version %.100s", remote_major, remote_minor, remote_version); /* Check if the remote protocol version is too old. */ if (remote_major == 1 && remote_minor < 3) fatal("Remote machine has too old SSH software version."); /* We speak 1.3, too. */ if (remote_major == 1 && remote_minor == 3) { enable_compat13(); if (options.forward_agent && strcmp(remote_version, SSH_VERSION) != 0) { log("Agent forwarding disabled, remote version '%s' is not compatible.", remote_version); options.forward_agent = 0; } } #if 0 /* Removed for now, to permit compatibility with latter versions. The server will reject our version and disconnect if it doesn't support it. */ if (remote_major != PROTOCOL_MAJOR) fatal("Protocol major versions differ: %d vs. %d", PROTOCOL_MAJOR, remote_major); #endif /* Send our own protocol version identification. */ snprintf(buf, sizeof buf, "SSH-%d.%d-%.100s\n", PROTOCOL_MAJOR, PROTOCOL_MINOR, SSH_VERSION); if (write(connection_out, buf, strlen(buf)) != strlen(buf)) fatal("write: %.100s", strerror(errno)); } int ssh_cipher_default = SSH_CIPHER_3DES; int read_yes_or_no(const char *prompt, int defval) { char buf[1024]; FILE *f; int retval = -1; if (isatty(0)) f = stdin; else f = fopen("/dev/tty", "rw"); if (f == NULL) return 0; fflush(stdout); while (1) { fprintf(stderr, "%s", prompt); if (fgets(buf, sizeof(buf), f) == NULL) { /* Print a newline (the prompt probably didn\'t have one). */ fprintf(stderr, "\n"); strlcpy(buf, "no", sizeof buf); } /* Remove newline from response. */ if (strchr(buf, '\n')) *strchr(buf, '\n') = 0; if (buf[0] == 0) retval = defval; if (strcmp(buf, "yes") == 0) retval = 1; if (strcmp(buf, "no") == 0) retval = 0; if (retval != -1) { if (f != stdin) fclose(f); return retval; } } } /* Starts a dialog with the server, and authenticates the current user on the server. This does not need any extra privileges. The basic connection to the server must already have been established before this is called. User is the remote user; if it is NULL, the current local user name will be used. Anonymous indicates that no rhosts authentication will be used. If login fails, this function prints an error and never returns. This function does not require super-user privileges. */ void ssh_login(int host_key_valid, RSA *own_host_key, const char *orighost, struct sockaddr_in *hostaddr, Options *options, uid_t original_real_uid) { int i, type; char *password; struct passwd *pw; BIGNUM *key; RSA *host_key, *file_key; RSA *public_key; unsigned char session_key[SSH_SESSION_KEY_LENGTH]; const char *server_user, *local_user; char *cp, *host, *ip = NULL; unsigned char check_bytes[8]; unsigned int supported_ciphers, supported_authentications, protocol_flags; HostStatus host_status; HostStatus ip_status; int host_ip_differ = 0; int local = (ntohl(hostaddr->sin_addr.s_addr) >> 24) == IN_LOOPBACKNET; int payload_len, clen, sum_len = 0; u_int32_t rand = 0; if (options->check_host_ip) ip = xstrdup(inet_ntoa(hostaddr->sin_addr)); /* Convert the user-supplied hostname into all lowercase. */ host = xstrdup(orighost); for (cp = host; *cp; cp++) if (isupper(*cp)) *cp = tolower(*cp); /* Exchange protocol version identification strings with the server. */ ssh_exchange_identification(); /* Put the connection into non-blocking mode. */ packet_set_nonblocking(); /* Get local user name. Use it as server user if no user name was given. */ pw = getpwuid(original_real_uid); if (!pw) fatal("User id %d not found from user database.", original_real_uid); local_user = xstrdup(pw->pw_name); server_user = options->user ? options->user : local_user; debug("Waiting for server public key."); /* Wait for a public key packet from the server. */ packet_read_expect(&payload_len, SSH_SMSG_PUBLIC_KEY); /* Get check bytes from the packet. */ for (i = 0; i < 8; i++) check_bytes[i] = packet_get_char(); /* Get the public key. */ public_key = RSA_new(); packet_get_int(); /* bits */ public_key->e = BN_new(); packet_get_bignum(public_key->e, &clen); sum_len += clen; public_key->n = BN_new(); packet_get_bignum(public_key->n, &clen); sum_len += clen; /* Get the host key. */ host_key = RSA_new(); packet_get_int(); /* bits */ host_key->e = BN_new(); packet_get_bignum(host_key->e, &clen); sum_len += clen; host_key->n = BN_new(); packet_get_bignum(host_key->n, &clen); sum_len += clen; /* Store the host key from the known host file in here * so that we can compare it with the key for the IP * address. */ file_key = RSA_new(); file_key->n = BN_new(); file_key->e = BN_new(); /* Get protocol flags. */ protocol_flags = packet_get_int(); packet_set_protocol_flags(protocol_flags); /* Get supported cipher types. */ supported_ciphers = packet_get_int(); /* Get supported authentication types. */ supported_authentications = packet_get_int(); debug("Received server public key (%d bits) and host key (%d bits).", BN_num_bits(public_key->n), BN_num_bits(host_key->n)); packet_integrity_check(payload_len, 8 + 4 + sum_len + 0 + 4 + 0 + 0 + 4 + 4 + 4, SSH_SMSG_PUBLIC_KEY); /* Compute the session id. */ compute_session_id(session_id, check_bytes, BN_num_bits(host_key->n), host_key->n, BN_num_bits(public_key->n), public_key->n); /* Check if the host key is present in the user\'s list of known hosts or in the systemwide list. */ host_status = check_host_in_hostfile(options->user_hostfile, host, BN_num_bits(host_key->n), host_key->e, host_key->n, file_key->e, file_key->n); if (host_status == HOST_NEW) host_status = check_host_in_hostfile(options->system_hostfile, host, BN_num_bits(host_key->n), host_key->e, host_key->n, file_key->e, file_key->n); /* Force accepting of the host key for localhost and 127.0.0.1. The problem is that if the home directory is NFS-mounted to multiple machines, localhost will refer to a different machine in each of them, and the user will get bogus HOST_CHANGED warnings. This essentially disables host authentication for localhost; however, this is probably not a real problem. */ if (local) { debug("Forcing accepting of host key for localhost."); host_status = HOST_OK; } /* Also perform check for the ip address, skip the check if we are localhost or the hostname was an ip address to begin with */ if (options->check_host_ip && !local && strcmp(host, ip)) { RSA *ip_key = RSA_new(); ip_key->n = BN_new(); ip_key->e = BN_new(); ip_status = check_host_in_hostfile(options->user_hostfile, ip, BN_num_bits(host_key->n), host_key->e, host_key->n, ip_key->e, ip_key->n); if (ip_status == HOST_NEW) ip_status = check_host_in_hostfile(options->system_hostfile, ip, BN_num_bits(host_key->n), host_key->e, host_key->n, ip_key->e, ip_key->n); if (host_status == HOST_CHANGED && (ip_status != HOST_CHANGED || (BN_cmp(ip_key->e, file_key->e) || BN_cmp(ip_key->n, file_key->n)))) host_ip_differ = 1; RSA_free(ip_key); } else ip_status = host_status; RSA_free(file_key); switch (host_status) { case HOST_OK: /* The host is known and the key matches. */ debug("Host '%.200s' is known and matches the host key.", host); if (options->check_host_ip) { if (ip_status == HOST_NEW) { if (!add_host_to_hostfile(options->user_hostfile, ip, BN_num_bits(host_key->n), host_key->e, host_key->n)) log("Failed to add the host ip to the list of known hosts (%.30s).", options->user_hostfile); else log("Warning: Permanently added host ip '%.30s' to the list of known hosts.", ip); } else if (ip_status != HOST_OK) log("Warning: the host key differ from the key of the ip address '%.30s' differs", ip); } break; case HOST_NEW: { char hostline[1000], *hostp = hostline; /* The host is new. */ if (options->strict_host_key_checking == 1) { /* User has requested strict host key checking. We will not add the host key automatically. The only alternative left is to abort. */ fatal("No host key is known for %.200s and you have requested strict checking.", host); } else if (options->strict_host_key_checking == 2) { /* The default */ char prompt[1024]; snprintf(prompt, sizeof(prompt), "The authenticity of host '%.200s' can't be established.\n" "Are you sure you want to continue connecting (yes/no)? ", host); if (!read_yes_or_no(prompt, -1)) fatal("Aborted by user!\n"); } if (options->check_host_ip && ip_status == HOST_NEW && strcmp(host, ip)) snprintf(hostline, sizeof(hostline), "%s,%s", host, ip); else hostp = host; /* If not in strict mode, add the key automatically to the local known_hosts file. */ if (!add_host_to_hostfile(options->user_hostfile, hostp, BN_num_bits(host_key->n), host_key->e, host_key->n)) log("Failed to add the host to the list of known hosts (%.500s).", options->user_hostfile); else log("Warning: Permanently added '%.200s' to the list of known hosts.", hostp); break; } case HOST_CHANGED: if (options->check_host_ip) { if (host_ip_differ) { error("@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@"); error("@ WARNING: POSSIBLE DNS SPOOFING DETECTED! @"); error("@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@"); error("The host key for %s has changed,", host); error("but the key for the according IP address %s has", ip); error("a different status. This could either mean that DNS"); error("SPOOFING is happening or the IP address for the host"); error("and its host key have changed at the same time"); } } /* The host key has changed. */ error("@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@"); error("@ WARNING: HOST IDENTIFICATION HAS CHANGED! @"); error("@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@"); error("IT IS POSSIBLE THAT SOMEONE IS DOING SOMETHING NASTY!"); error("Someone could be eavesdropping on you right now (man-in-the-middle attack)!"); error("It is also possible that the host key has just been changed."); error("Please contact your system administrator."); error("Add correct host key in %.100s to get rid of this message.", options->user_hostfile); /* If strict host key checking is in use, the user will have to edit the key manually and we can only abort. */ if (options->strict_host_key_checking) fatal("Host key for %.200s has changed and you have requested strict checking.", host); /* If strict host key checking has not been requested, allow the connection but without password authentication or agent forwarding. */ if (options->password_authentication) { error("Password authentication is disabled to avoid trojan horses."); options->password_authentication = 0; } if (options->forward_agent) { error("Agent forwarding is disabled to avoid trojan horses."); options->forward_agent = 0; } /* XXX Should permit the user to change to use the new id. This could be done by converting the host key to an identifying sentence, tell that the host identifies itself by that sentence, and ask the user if he/she whishes to accept the authentication. */ break; } if (options->check_host_ip) xfree(ip); /* Generate a session key. */ arc4random_stir(); /* Generate an encryption key for the session. The key is a 256 bit random number, interpreted as a 32-byte key, with the least significant 8 bits being the first byte of the key. */ for (i = 0; i < 32; i++) { if (i % 4 == 0) rand = arc4random(); session_key[i] = rand & 0xff; rand >>= 8; } /* According to the protocol spec, the first byte of the session key is the highest byte of the integer. The session key is xored with the first 16 bytes of the session id. */ key = BN_new(); BN_set_word(key, 0); for (i = 0; i < SSH_SESSION_KEY_LENGTH; i++) { BN_lshift(key, key, 8); if (i < 16) BN_add_word(key, session_key[i] ^ session_id[i]); else BN_add_word(key, session_key[i]); } /* Encrypt the integer using the public key and host key of the server (key with smaller modulus first). */ if (BN_cmp(public_key->n, host_key->n) < 0) { /* Public key has smaller modulus. */ assert(BN_num_bits(host_key->n) >= BN_num_bits(public_key->n) + SSH_KEY_BITS_RESERVED); rsa_public_encrypt(key, key, public_key); rsa_public_encrypt(key, key, host_key); } else { /* Host key has smaller modulus (or they are equal). */ assert(BN_num_bits(public_key->n) >= BN_num_bits(host_key->n) + SSH_KEY_BITS_RESERVED); rsa_public_encrypt(key, key, host_key); rsa_public_encrypt(key, key, public_key); } if (options->cipher == SSH_CIPHER_NOT_SET) { if (cipher_mask() & supported_ciphers & (1 << ssh_cipher_default)) options->cipher = ssh_cipher_default; else { debug("Cipher %d not supported, using %.100s instead.", cipher_name(ssh_cipher_default), cipher_name(SSH_FALLBACK_CIPHER)); options->cipher = SSH_FALLBACK_CIPHER; } } /* Check that the selected cipher is supported. */ if (!(supported_ciphers & (1 << options->cipher))) fatal("Selected cipher type %.100s not supported by server.", cipher_name(options->cipher)); debug("Encryption type: %.100s", cipher_name(options->cipher)); /* Send the encrypted session key to the server. */ packet_start(SSH_CMSG_SESSION_KEY); packet_put_char(options->cipher); /* Send the check bytes back to the server. */ for (i = 0; i < 8; i++) packet_put_char(check_bytes[i]); /* Send the encrypted encryption key. */ packet_put_bignum(key); /* Send protocol flags. */ packet_put_int(SSH_PROTOFLAG_SCREEN_NUMBER | SSH_PROTOFLAG_HOST_IN_FWD_OPEN); /* Send the packet now. */ packet_send(); packet_write_wait(); /* Destroy the session key integer and the public keys since we no longer need them. */ BN_clear_free(key); RSA_free(public_key); RSA_free(host_key); debug("Sent encrypted session key."); /* Set the encryption key. */ packet_set_encryption_key(session_key, SSH_SESSION_KEY_LENGTH, options->cipher, 1); /* We will no longer need the session key here. Destroy any extra copies. */ memset(session_key, 0, sizeof(session_key)); /* Expect a success message from the server. Note that this message will be received in encrypted form. */ packet_read_expect(&payload_len, SSH_SMSG_SUCCESS); debug("Received encrypted confirmation."); /* Send the name of the user to log in as on the server. */ packet_start(SSH_CMSG_USER); packet_put_string(server_user, strlen(server_user)); packet_send(); packet_write_wait(); /* The server should respond with success if no authentication is needed (the user has no password). Otherwise the server responds with failure. */ type = packet_read(&payload_len); if (type == SSH_SMSG_SUCCESS) return; /* Connection was accepted without authentication. */ if (type != SSH_SMSG_FAILURE) packet_disconnect("Protocol error: got %d in response to SSH_CMSG_USER", type); #ifdef 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("WARNING: Encryption is disabled! Ticket will be transmitted in the clear!"); (void)send_kerberos_tgt(); } /* 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("WARNING: Encryption is disabled! Token will be transmitted in the clear!"); send_afs_tokens(); } #endif /* AFS */ #ifdef KRB4 if ((supported_authentications & (1 << SSH_AUTH_KERBEROS)) && options->kerberos_authentication) { debug("Trying Kerberos authentication."); if (try_kerberos_authentication()) { /* The server should respond with success or failure. */ type = packet_read(&payload_len); if (type == SSH_SMSG_SUCCESS) return; /* Successful connection. */ if (type != SSH_SMSG_FAILURE) packet_disconnect("Protocol error: got %d in response to Kerberos auth", type); } } #endif /* KRB4 */ /* Use rhosts authentication if running in privileged socket and we do not wish to remain anonymous. */ if ((supported_authentications & (1 << SSH_AUTH_RHOSTS)) && options->rhosts_authentication) { debug("Trying rhosts authentication."); packet_start(SSH_CMSG_AUTH_RHOSTS); packet_put_string(local_user, strlen(local_user)); packet_send(); packet_write_wait(); /* The server should respond with success or failure. */ type = packet_read(&payload_len); if (type == SSH_SMSG_SUCCESS) return; /* Successful connection. */ if (type != SSH_SMSG_FAILURE) packet_disconnect("Protocol error: got %d in response to rhosts auth", type); } /* Try .rhosts or /etc/hosts.equiv authentication with RSA host authentication. */ if ((supported_authentications & (1 << SSH_AUTH_RHOSTS_RSA)) && options->rhosts_rsa_authentication && host_key_valid) { if (try_rhosts_rsa_authentication(local_user, own_host_key)) return; /* Successful authentication. */ } /* Try RSA authentication if the server supports it. */ if ((supported_authentications & (1 << SSH_AUTH_RSA)) && options->rsa_authentication) { /* Try RSA authentication using the authentication agent. The agent is tried first because no passphrase is needed for it, whereas identity files may require passphrases. */ if (try_agent_authentication()) return; /* Successful connection. */ /* Try RSA authentication for each identity. */ for (i = 0; i < options->num_identity_files; i++) if (try_rsa_authentication(pw, options->identity_files[i], !options->batch_mode)) return; /* Successful connection. */ } /* Try password authentication if the server supports it. */ if ((supported_authentications & (1 << SSH_AUTH_PASSWORD)) && options->password_authentication && !options->batch_mode) { char prompt[80]; snprintf(prompt, sizeof(prompt), "%.30s@%.30s's password: ", server_user, host); debug("Doing password authentication."); if (options->cipher == SSH_CIPHER_NONE) log("WARNING: Encryption is disabled! Password will be transmitted in clear text."); for (i = 0; i < options->number_of_password_prompts; i++) { if (i != 0) error("Permission denied, please try again."); password = read_passphrase(prompt, 0); packet_start(SSH_CMSG_AUTH_PASSWORD); packet_put_string(password, strlen(password)); memset(password, 0, strlen(password)); xfree(password); packet_send(); packet_write_wait(); type = packet_read(&payload_len); if (type == SSH_SMSG_SUCCESS) return; /* Successful connection. */ if (type != SSH_SMSG_FAILURE) packet_disconnect("Protocol error: got %d in response to passwd auth", type); } } /* All authentication methods have failed. Exit with an error message. */ fatal("Permission denied."); /*NOTREACHED*/ }