From 8c922cd518cd36a281ce7cb57136ab3daff4af36 Mon Sep 17 00:00:00 2001 From: Theo de Raadt Date: Sun, 26 Sep 1999 20:53:39 +0000 Subject: i bet a lot of people didn't know what ssh 1.2.16 had a nice license. well, except for the patent issues. someone in sweden (forget their name at the moment) cleaned out most of the patented code, and now this code removes rsa code. when this is done, it will link against libssl, but the work isn't completely done yet. then we need to bring this up to modern days, featurewise. --- usr.bin/ssh/sshconnect.c | 1434 ++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 1434 insertions(+) create mode 100644 usr.bin/ssh/sshconnect.c (limited to 'usr.bin/ssh/sshconnect.c') diff --git a/usr.bin/ssh/sshconnect.c b/usr.bin/ssh/sshconnect.c new file mode 100644 index 00000000000..1ce4b01047f --- /dev/null +++ b/usr.bin/ssh/sshconnect.c @@ -0,0 +1,1434 @@ +/* + +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 "includes.h" +RCSID("$Id: sshconnect.c,v 1.1 1999/09/26 20:53:38 deraadt Exp $"); + +#include +#include "xmalloc.h" +#include "randoms.h" +#include "rsa.h" +#include "ssh.h" +#include "packet.h" +#include "authfd.h" +#include "cipher.h" +#include "ssh_md5.h" +#include "mpaux.h" +#include "uidswap.h" + +#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]; + +/* 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, RandomState *random_state) +{ + 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. */ + sprintf(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], random_state); + + 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) + { + struct sockaddr_in sin; + int p; + for (p = 1023; p > 512; p--) + { + sock = socket(AF_INET, SOCK_STREAM, 0); + if (sock < 0) + fatal("socket: %.100s", strerror(errno)); + + /* 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); + + /* Try to bind the socket to the privileged port. */ + if (bind(sock, (struct sockaddr *)&sin, sizeof(sin)) >= 0) + break; /* Success. */ + if (errno == EADDRINUSE) + { + close(sock); + continue; + } + fatal("bind: %.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, int port, int connection_attempts, + 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; +#ifdef SO_LINGER + struct linger linger; +#endif /* SO_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, + random_state); + + /* 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); +#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) + { + /* 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 < 1024); + + /* 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, 2); + 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 < 1024); + + /* 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, 2); + 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)); */ +#ifdef TCP_NODELAY + setsockopt(sock, IPPROTO_TCP, TCP_NODELAY, (void *)&on, sizeof(on)); +#endif /* TCP_NODELAY */ +#ifdef SO_LINGER + linger.l_onoff = 1; + linger.l_linger = 5; + setsockopt(sock, SOL_SOCKET, SO_LINGER, (void *)&linger, sizeof(linger)); +#endif /* SO_LINGER */ + + /* Set the connection. */ + packet_set_connection(sock, sock, random_state); + + 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; + MP_INT e, n, challenge; + char *comment; + AuthenticationConnection *auth; + unsigned char response[16]; + unsigned int i; + + /* Get connection to the agent. */ + auth = ssh_get_authentication_connection(); + if (!auth) + return 0; + + mpz_init(&e); + mpz_init(&n); + mpz_init(&challenge); + + /* 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_mp_int(&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_mp_int(&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."); + mpz_clear(&e); + mpz_clear(&n); + mpz_clear(&challenge); + return 1; + } + + /* Otherwise it should return failure. */ + if (type != SSH_SMSG_FAILURE) + packet_disconnect("Protocol error waiting RSA auth response: %d", + type); + } + + mpz_clear(&e); + mpz_clear(&n); + mpz_clear(&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(MP_INT *challenge, RSAPrivateKey *prv) +{ + unsigned char buf[32], response[16]; + struct MD5Context md; + int i; + + /* 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. */ + mp_linearize_msb_first(buf, 32, challenge); + MD5Init(&md); + MD5Update(&md, buf, 32); + MD5Update(&md, session_id, 16); + MD5Final(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) +{ + MP_INT challenge; + RSAPrivateKey private_key; + RSAPublicKey public_key; + char *passphrase, *comment; + int type, i; + int plen, clen; + + /* Try to load identification for the authentication key. */ + if (!load_public_key(authfile, &public_key, &comment)) + 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_mp_int(&public_key.n); + packet_send(); + packet_write_wait(); + + /* We no longer need the public key. */ + rsa_clear_public_key(&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. */ + mpz_init(&challenge); + packet_get_mp_int(&challenge, &clen); + + packet_integrity_check(plen, clen, type); + + debug("Received RSA challenge from server."); + + /* 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. */ + sprintf(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_clear_private_key(&private_key); + + /* We no longer need the challenge. */ + mpz_clear(&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, + RSAPrivateKey *host_key) +{ + int type; + MP_INT 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(host_key->bits); + packet_put_mp_int(&host_key->e); + packet_put_mp_int(&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. */ + mpz_init(&challenge); + packet_get_mp_int(&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. */ + mpz_clear(&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; + Key_schedule schedule; + u_long checksum, cksum; + MSG_DAT msg_data; + struct sockaddr_in local, foreign; + struct stat st; + int plen; + + /* Don't do anything if we don't have any tickets. */ + if (stat(tkt_string(), &st) < 0) return 0; + + debug("Trying Kerberos authentication."); + 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 %.100s", 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: %.100s", 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: %.100s", 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(&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: %.100s", 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 + +#ifdef KERBEROS_TGT_PASSING +int send_kerberos_tgt() +{ + CREDENTIALS *creds; + char pname[ANAME_SZ], pinst[INST_SZ], prealm[REALM_SZ]; + int r, plen, 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: %.100s",krb_err_txt[r]); + return 0; +} + if ((r=krb_get_cred("krbtgt", prealm, prealm, creds)) != GC_OK) { + debug("Kerberos V4 get_cred failed: %.100s", krb_err_txt[r]); + return 0; + } + if (time(0) > krb_life_to_time(creds->issue_date, creds->lifetime)) { + debug("Kerberos V4 ticket expired: %.100s", 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 %.100s rejected.", prealm); + else if (type != SSH_SMSG_SUCCESS) + packet_disconnect("Protocol error on Kerberos TGT response: %d", type); + + return 1; +} +#endif /* KERBEROS_TGT_PASSING */ + +/* 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, 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. */ + 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; + sprintf(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 %.100s 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(); + + /* 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); +#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 + + /* Check if the remote protocol version is too old. */ + if (remote_major == 1 && remote_minor == 0) + fatal("Remote machine has too old SSH software version."); + + /* Send our own protocol version identification. */ + sprintf(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"); + strcpy(buf, "no"); + } + /* 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(RandomState *state, int host_key_valid, + RSAPrivateKey *own_host_key, + const char *orighost, + Options *options, uid_t original_real_uid) +{ + int i, type; + char buf[1024]; + char *password; + struct passwd *pw; + MP_INT key; + RSAPublicKey host_key; + RSAPublicKey public_key; + unsigned char session_key[SSH_SESSION_KEY_LENGTH]; + const char *server_user, *local_user; + char *cp, *host; + struct stat st; + unsigned char check_bytes[8]; + unsigned int supported_ciphers, supported_authentications, protocol_flags; + HostStatus host_status; + int payload_len, clen, sum_len = 0; + + /* 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.bits = packet_get_int(); + mpz_init(&public_key.e); + packet_get_mp_int(&public_key.e, &clen); + sum_len += clen; + mpz_init(&public_key.n); + packet_get_mp_int(&public_key.n, &clen); + sum_len += clen; + + /* Get the host key. */ + host_key.bits = packet_get_int(); + mpz_init(&host_key.e); + packet_get_mp_int(&host_key.e, &clen); + sum_len += clen; + mpz_init(&host_key.n); + packet_get_mp_int(&host_key.n, &clen); + sum_len += clen; + + /* 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).", + public_key.bits, host_key.bits); + + 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, host_key.bits, &host_key.n, + public_key.bits, &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, host_key.bits, + &host_key.e, &host_key.n); + if (host_status == HOST_NEW) + host_status = check_host_in_hostfile(options->system_hostfile, host, + host_key.bits, &host_key.e, + &host_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 (strcmp(host, "localhost") == 0 || + strcmp(host, "127.0.0.1") == 0) + { + debug("Forcing accepting of host key for localhost."); + host_status = HOST_OK; + } + + 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); + break; + case HOST_NEW: + /* 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]; + sprintf(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 not in strict mode, add the key automatically to the local + known_hosts file. */ + if (!add_host_to_hostfile(options->user_hostfile, host, host_key.bits, + &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 host '%.200s' to the list of known hosts.", host); + break; + case HOST_CHANGED: + /* 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. */ + error("Password authentication is disabled to avoid trojan horses."); + options->password_authentication = 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; + } + + /* Generate a session key. */ + + /* Initialize the random number generator. */ + sprintf(buf, "%.500s/%.200s", pw->pw_dir, SSH_CLIENT_SEEDFILE); + if (stat(buf, &st) < 0) + log("Creating random seed file ~/%.900s. This may take a while.", + SSH_CLIENT_SEEDFILE); + else + debug("Initializing random; seed file %.900s", buf); + random_initialize(state, buf); + + /* 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++) + session_key[i] = random_get_byte(state); + + /* Save the new random state. */ + random_save(state, buf); + random_stir(state); /* This is supposed to be irreversible. */ + + /* 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. */ + mpz_init_set_ui(&key, 0); + for (i = 0; i < SSH_SESSION_KEY_LENGTH; i++) + { + mpz_mul_2exp(&key, &key, 8); + if (i < 16) + mpz_add_ui(&key, &key, session_key[i] ^ session_id[i]); + else + mpz_add_ui(&key, &key, session_key[i]); + } + + /* Encrypt the integer using the public key and host key of the server + (key with smaller modulus first). */ + if (mpz_cmp(&public_key.n, &host_key.n) < 0) + { + /* Public key has smaller modulus. */ + assert(host_key.bits >= public_key.bits + SSH_KEY_BITS_RESERVED); + + rsa_public_encrypt(&key, &key, &public_key, state); + rsa_public_encrypt(&key, &key, &host_key, state); + } + else + { + /* Host key has smaller modulus (or they are equal). */ + assert(public_key.bits >= host_key.bits + SSH_KEY_BITS_RESERVED); + + rsa_public_encrypt(&key, &key, &host_key, state); + rsa_public_encrypt(&key, &key, &public_key, state); + } + + 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_mp_int(&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. */ + mpz_clear(&key); + rsa_clear_public_key(&public_key); + rsa_clear_public_key(&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 KERBEROS_TGT_PASSING + /* 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(); + } +#endif /* KERBEROS_TGT_PASSING */ + +#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("WARNING: Encryption is disabled! Token will be transmitted in the clear!"); + send_afs_tokens(); + } +#endif /* AFS */ + +#if defined(KRB4) + if ((supported_authentications & (1 << SSH_AUTH_KERBEROS)) && + options->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) + { + debug("Doing password authentication."); + if (options->cipher == SSH_CIPHER_NONE) + log("WARNING: Encryption is disabled! Password will be transmitted in clear text."); + password = read_passphrase("Password: ", 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*/ +} -- cgit v1.2.3