X-Git-Url: http://andersk.mit.edu/gitweb/openssh.git/blobdiff_plain/3c62e7eb32fc0daff64bb13c41a3bdb84d8a71f4..567a05bf444fa057f6320f6c47ace609d6f44cb8:/sshconnect.c diff --git a/sshconnect.c b/sshconnect.c index 7c47ebe9..6004bf5e 100644 --- a/sshconnect.c +++ b/sshconnect.c @@ -2,46 +2,57 @@ * 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. + * + * As far as I am concerned, the code I have written for this software + * can be used freely for any purpose. Any derived versions of this + * software must be clearly marked as such, and if the derived work is + * incompatible with the protocol description in the RFC file, it must be + * called by a name other than "ssh" or "Secure Shell". */ #include "includes.h" -RCSID("$OpenBSD: sshconnect.c,v 1.77 2000/08/28 03:50:54 deraadt Exp $"); +RCSID("$OpenBSD: sshconnect.c,v 1.133 2002/07/29 18:57:30 markus Exp $"); #include -#include -#include +#include "ssh.h" #include "xmalloc.h" #include "rsa.h" -#include "ssh.h" #include "buffer.h" #include "packet.h" #include "uidswap.h" #include "compat.h" -#include "readconf.h" #include "key.h" #include "sshconnect.h" #include "hostfile.h" +#include "log.h" +#include "readconf.h" +#include "atomicio.h" +#include "misc.h" +#include "readpass.h" char *client_version_string = NULL; char *server_version_string = NULL; +/* import */ extern Options options; -#ifdef HAVE___PROGNAME extern char *__progname; -#else /* HAVE___PROGNAME */ -static const char *__progname = "ssh"; -#endif /* HAVE___PROGNAME */ +extern uid_t original_real_uid; +extern uid_t original_effective_uid; + +#ifndef INET6_ADDRSTRLEN /* for non IPv6 machines */ +#define INET6_ADDRSTRLEN 46 +#endif + +static int show_other_keys(const char *, Key *); /* * Connect to the given ssh server using a proxy command. */ -int -ssh_proxy_connect(const char *host, u_short port, uid_t original_real_uid, - const char *proxy_command) +static int +ssh_proxy_connect(const char *host, u_short port, const char *proxy_command) { Buffer command; const char *cp; @@ -82,7 +93,7 @@ ssh_proxy_connect(const char *host, u_short port, uid_t original_real_uid, /* 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)); + strerror(errno)); debug("Executing proxy command: %.500s", command_string); @@ -91,7 +102,8 @@ ssh_proxy_connect(const char *host, u_short port, uid_t original_real_uid, char *argv[10]; /* Child. Permanently give up superuser privileges. */ - permanently_set_uid(original_real_uid); + seteuid(original_real_uid); + setuid(original_real_uid); /* Redirect stdin and stdout. */ close(pin[1]); @@ -115,8 +127,8 @@ ssh_proxy_connect(const char *host, u_short port, uid_t original_real_uid, /* Execute the proxy command. Note that we gave up any extra privileges above. */ - execv(_PATH_BSHELL, argv); - perror(_PATH_BSHELL); + execv(argv[0], argv); + perror(argv[0]); exit(1); } /* Parent. */ @@ -133,16 +145,18 @@ ssh_proxy_connect(const char *host, u_short port, uid_t original_real_uid, /* Set the connection file descriptors. */ packet_set_connection(pout[0], pin[1]); - return 1; + /* Indicate OK return */ + return 0; } /* * Creates a (possibly privileged) socket for use as the ssh connection. */ -int -ssh_create_socket(uid_t original_real_uid, int privileged, int family) +static int +ssh_create_socket(int privileged, int family) { - int sock; + int sock, gaierr; + struct addrinfo hints, *res; /* * If we are running as root and want to connect to a privileged @@ -150,51 +164,81 @@ ssh_create_socket(uid_t original_real_uid, int privileged, int family) */ if (privileged) { int p = IPPORT_RESERVED - 1; + PRIV_START; sock = rresvport_af(&p, family); + PRIV_END; if (sock < 0) error("rresvport: af=%d %.100s", family, strerror(errno)); else 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(family, SOCK_STREAM, 0); - if (sock < 0) - error("socket: %.100s", strerror(errno)); - restore_uid(); + return sock; } + sock = socket(family, SOCK_STREAM, 0); + if (sock < 0) + error("socket: %.100s", strerror(errno)); + + /* Bind the socket to an alternative local IP address */ + if (options.bind_address == NULL) + return sock; + + memset(&hints, 0, sizeof(hints)); + hints.ai_family = family; + hints.ai_socktype = SOCK_STREAM; + hints.ai_flags = AI_PASSIVE; + gaierr = getaddrinfo(options.bind_address, "0", &hints, &res); + if (gaierr) { + error("getaddrinfo: %s: %s", options.bind_address, + gai_strerror(gaierr)); + close(sock); + return -1; + } + if (bind(sock, res->ai_addr, res->ai_addrlen) < 0) { + error("bind: %s: %s", options.bind_address, strerror(errno)); + close(sock); + freeaddrinfo(res); + return -1; + } + freeaddrinfo(res); return sock; } /* * Opens a TCP/IP connection to the remote server on the given host. * The address of the remote host will be returned in hostaddr. - * If port is 0, the default port will be used. If anonymous is zero, + * If port is 0, the default port will be used. If needpriv is true, * a privileged port will be allocated to make the connection. - * This requires super-user privileges if anonymous is false. + * This requires super-user privileges if needpriv is true. * 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. + * Return values: + * 0 for OK + * ECONNREFUSED if we got a "Connection Refused" by the peer on any address + * ECONNABORTED if we failed without a "Connection refused" + * Suitable error messages for the connection failure will already have been + * printed. */ int ssh_connect(const char *host, struct sockaddr_storage * hostaddr, - u_short port, int connection_attempts, - int anonymous, uid_t original_real_uid, - const char *proxy_command) + u_short port, int family, int connection_attempts, + int needpriv, const char *proxy_command) { + int gaierr; + int on = 1; int sock = -1, attempt; - struct servent *sp; - struct addrinfo hints, *ai, *aitop; char ntop[NI_MAXHOST], strport[NI_MAXSERV]; - int gaierr; + struct addrinfo hints, *ai, *aitop; struct linger linger; + struct servent *sp; + /* + * Did we get only other errors than "Connection refused" (which + * should block fallback to rsh and similar), or did we get at least + * one "Connection refused"? + */ + int full_failure = 1; - debug("ssh_connect: getuid %u geteuid %u anon %d", - (u_int) getuid(), (u_int) geteuid(), anonymous); + debug("ssh_connect: needpriv %d", needpriv); /* Get default port if port has not been set. */ if (port == 0) { @@ -206,14 +250,14 @@ ssh_connect(const char *host, struct sockaddr_storage * hostaddr, } /* If a proxy command is given, connect using it. */ if (proxy_command != NULL) - return ssh_proxy_connect(host, port, original_real_uid, proxy_command); + return ssh_proxy_connect(host, port, proxy_command); /* No proxy command. */ memset(&hints, 0, sizeof(hints)); - hints.ai_family = IPv4or6; + hints.ai_family = family; hints.ai_socktype = SOCK_STREAM; - snprintf(strport, sizeof strport, "%d", port); + snprintf(strport, sizeof strport, "%u", port); if ((gaierr = getaddrinfo(host, strport, &hints, &aitop)) != 0) fatal("%s: %.100s: %s", __progname, host, gai_strerror(gaierr)); @@ -222,8 +266,8 @@ ssh_connect(const char *host, struct sockaddr_storage * hostaddr, * 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++) { + */ + for (attempt = 0; ;) { if (attempt > 0) debug("Trying again..."); @@ -242,42 +286,35 @@ ssh_connect(const char *host, struct sockaddr_storage * hostaddr, host, ntop, strport); /* Create a socket for connecting. */ - sock = ssh_create_socket(original_real_uid, -#ifdef HAVE_CYGWIN - !anonymous && port < IPPORT_RESERVED, -#else - !anonymous && geteuid() == 0 && port < IPPORT_RESERVED, -#endif - ai->ai_family); + sock = ssh_create_socket(needpriv, ai->ai_family); if (sock < 0) + /* Any error is already output */ continue; - /* 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, ai->ai_addr, ai->ai_addrlen) >= 0) { /* Successful connection. */ memcpy(hostaddr, ai->ai_addr, ai->ai_addrlen); - restore_uid(); break; } else { - debug("connect: %.100s", strerror(errno)); - restore_uid(); + if (errno == ECONNREFUSED) + full_failure = 0; + debug("connect to address %s port %s: %s", + ntop, strport, strerror(errno)); /* * 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 (ai) break; /* Successful connection. */ + attempt++; + if (attempt >= connection_attempts) + break; /* Sleep a moment before retrying. */ sleep(1); } @@ -285,8 +322,11 @@ ssh_connect(const char *host, struct sockaddr_storage * hostaddr, freeaddrinfo(aitop); /* Return failure if we didn't get a successful connection. */ - if (attempt >= connection_attempts) - return 0; + if (attempt >= connection_attempts) { + log("ssh: connect to host %s port %s: %s", + host, strport, strerror(errno)); + return full_failure ? ECONNABORTED : ECONNREFUSED; + } debug("Connection established."); @@ -297,25 +337,32 @@ ssh_connect(const char *host, struct sockaddr_storage * hostaddr, /* setsockopt(sock, SOL_SOCKET, SO_REUSEADDR, (void *)&on, sizeof(on)); */ linger.l_onoff = 1; linger.l_linger = 5; - setsockopt(sock, SOL_SOCKET, SO_LINGER, (void *) &linger, sizeof(linger)); + setsockopt(sock, SOL_SOCKET, SO_LINGER, (void *)&linger, sizeof(linger)); + + /* Set keepalives if requested. */ + if (options.keepalives && + setsockopt(sock, SOL_SOCKET, SO_KEEPALIVE, (void *)&on, + sizeof(on)) < 0) + error("setsockopt SO_KEEPALIVE: %.100s", strerror(errno)); /* Set the connection. */ packet_set_connection(sock, sock); - return 1; + return 0; } /* * Waits for the server identification string, and sends our own * identification string. */ -void -ssh_exchange_identification() +static void +ssh_exchange_identification(void) { char buf[256], remote_version[256]; /* must be same size! */ int remote_major, remote_minor, i, mismatch; int connection_in = packet_get_connection_in(); int connection_out = packet_get_connection_out(); + int minor1 = PROTOCOL_MINOR_1; /* Read other side\'s version identification. */ for (;;) { @@ -350,12 +397,12 @@ ssh_exchange_identification() &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); + remote_major, remote_minor, remote_version); compat_datafellows(remote_version); mismatch = 0; - switch(remote_major) { + switch (remote_major) { case 1: if (remote_minor == 99 && (options.protocol & SSH_PROTO_2) && @@ -369,9 +416,10 @@ ssh_exchange_identification() } if (remote_minor < 3) { fatal("Remote machine has too old SSH software version."); - } else if (remote_minor == 3) { + } else if (remote_minor == 3 || remote_minor == 4) { /* We speak 1.3, too. */ enable_compat13(); + minor1 = 3; if (options.forward_agent) { log("Agent forwarding disabled for protocol 1.3"); options.forward_agent = 0; @@ -392,12 +440,10 @@ ssh_exchange_identification() fatal("Protocol major versions differ: %d vs. %d", (options.protocol & SSH_PROTO_2) ? PROTOCOL_MAJOR_2 : PROTOCOL_MAJOR_1, remote_major); - if (compat20) - packet_set_ssh2_format(); /* Send our own protocol version identification. */ snprintf(buf, sizeof buf, "SSH-%d.%d-%.100s\n", compat20 ? PROTOCOL_MAJOR_2 : PROTOCOL_MAJOR_1, - compat20 ? PROTOCOL_MINOR_2 : PROTOCOL_MINOR_1, + compat20 ? PROTOCOL_MINOR_2 : minor1, SSH_VERSION); if (atomicio(write, connection_out, buf, strlen(buf)) != strlen(buf)) fatal("write: %.100s", strerror(errno)); @@ -407,66 +453,51 @@ ssh_exchange_identification() debug("Local version string %.100s", client_version_string); } -int -read_yes_or_no(const char *prompt, int defval) +/* defaults to 'no' */ +static int +confirm(const char *prompt) { - char buf[1024]; - FILE *f; - int retval = -1; + const char *msg, *again = "Please type 'yes' or 'no': "; + char *p; + int ret = -1; - if (isatty(0)) - f = stdin; - else - f = fopen("/dev/tty", "rw"); - - if (f == NULL) + if (options.batch_mode) 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; - } + for (msg = prompt;;msg = again) { + p = read_passphrase(msg, RP_ECHO); + if (p == NULL || + (p[0] == '\0') || (p[0] == '\n') || + strncasecmp(p, "no", 2) == 0) + ret = 0; + if (p && strncasecmp(p, "yes", 3) == 0) + ret = 1; + if (p) + xfree(p); + if (ret != -1) + return ret; } } /* - * check whether the supplied host key is valid, return only if ok. + * check whether the supplied host key is valid, return -1 if the key + * is not valid. the user_hostfile will not be updated if 'readonly' is true. */ - -void +static int check_host_key(char *host, struct sockaddr *hostaddr, Key *host_key, - const char *user_hostfile, const char *system_hostfile) + int readonly, const char *user_hostfile, const char *system_hostfile) { Key *file_key; char *type = key_type(host_key); char *ip = NULL; - char hostline[1000], *hostp; + char hostline[1000], *hostp, *fp; HostStatus host_status; HostStatus ip_status; int local = 0, host_ip_differ = 0; int salen; char ntop[NI_MAXHOST]; + char msg[1024]; + int len, host_line, ip_line, has_keys; + const char *host_file = NULL, *ip_file = NULL; /* * Force accepting of the host key for loopback/localhost. The @@ -479,11 +510,13 @@ check_host_key(char *host, struct sockaddr *hostaddr, Key *host_key, /** hostaddr == 0! */ switch (hostaddr->sa_family) { case AF_INET: - local = (ntohl(((struct sockaddr_in *)hostaddr)->sin_addr.s_addr) >> 24) == IN_LOOPBACKNET; + local = (ntohl(((struct sockaddr_in *)hostaddr)-> + sin_addr.s_addr) >> 24) == IN_LOOPBACKNET; salen = sizeof(struct sockaddr_in); break; case AF_INET6: - local = IN6_IS_ADDR_LOOPBACK(&(((struct sockaddr_in6 *)hostaddr)->sin6_addr)); + local = IN6_IS_ADDR_LOOPBACK( + &(((struct sockaddr_in6 *)hostaddr)->sin6_addr)); salen = sizeof(struct sockaddr_in6); break; default: @@ -491,23 +524,41 @@ check_host_key(char *host, struct sockaddr *hostaddr, Key *host_key, salen = sizeof(struct sockaddr_storage); break; } - if (local) { - debug("Forcing accepting of host key for loopback/localhost."); - return; + if (options.no_host_authentication_for_localhost == 1 && local && + options.host_key_alias == NULL) { + debug("Forcing accepting of host key for " + "loopback/localhost."); + return 0; } /* - * Turn off check_host_ip for proxy connects, since - * we don't have the remote ip-address + * We don't have the remote ip-address for connections + * using a proxy command */ - if (options.proxy_command != NULL && options.check_host_ip) - options.check_host_ip = 0; - - if (options.check_host_ip) { + if (options.proxy_command == NULL) { if (getnameinfo(hostaddr, salen, ntop, sizeof(ntop), NULL, 0, NI_NUMERICHOST) != 0) fatal("check_host_key: getnameinfo failed"); ip = xstrdup(ntop); + } else { + ip = xstrdup(""); + } + /* + * Turn off check_host_ip if the connection is to localhost, via proxy + * command or if we don't have a hostname to compare with + */ + if (options.check_host_ip && + (local || strcmp(host, ip) == 0 || options.proxy_command != NULL)) + options.check_host_ip = 0; + + /* + * Allow the user to record the key under a different name. This is + * useful for ssh tunneling over forwarded connections or if you run + * multiple sshd's on different ports on the same machine. + */ + if (options.host_key_alias != NULL) { + host = options.host_key_alias; + debug("using hostkeyalias: %s", host); } /* @@ -520,19 +571,29 @@ check_host_key(char *host, struct sockaddr *hostaddr, Key *host_key, * 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(user_hostfile, host, host_key, file_key); - if (host_status == HOST_NEW) - host_status = check_host_in_hostfile(system_hostfile, host, host_key, file_key); + host_file = user_hostfile; + host_status = check_host_in_hostfile(host_file, host, host_key, + file_key, &host_line); + if (host_status == HOST_NEW) { + host_file = system_hostfile; + host_status = check_host_in_hostfile(host_file, host, host_key, + file_key, &host_line); + } /* * 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)) { + if (options.check_host_ip) { Key *ip_key = key_new(host_key->type); - ip_status = check_host_in_hostfile(user_hostfile, ip, host_key, ip_key); - if (ip_status == HOST_NEW) - ip_status = check_host_in_hostfile(system_hostfile, ip, host_key, ip_key); + ip_file = user_hostfile; + ip_status = check_host_in_hostfile(ip_file, ip, host_key, + ip_key, &ip_line); + if (ip_status == HOST_NEW) { + ip_file = system_hostfile; + ip_status = check_host_in_hostfile(ip_file, ip, + host_key, ip_key, &ip_line); + } if (host_status == HOST_CHANGED && (ip_status != HOST_CHANGED || !key_equal(ip_key, file_key))) host_ip_differ = 1; @@ -548,50 +609,70 @@ check_host_key(char *host, struct sockaddr *hostaddr, Key *host_key, /* The host is known and the key matches. */ debug("Host '%.200s' is known and matches the %s host key.", host, type); - if (options.check_host_ip) { - if (ip_status == HOST_NEW) { - if (!add_host_to_hostfile(user_hostfile, ip, host_key)) - log("Failed to add the %s host key for IP address '%.30s' to the list of known hosts (%.30s).", - type, ip, user_hostfile); - else - log("Warning: Permanently added the %s host key for IP address '%.30s' to the list of known hosts.", - type, ip); - } else if (ip_status != HOST_OK) - log("Warning: the %s host key for '%.200s' differs from the key for the IP address '%.30s'", - type, host, ip); + debug("Found key in %s:%d", host_file, host_line); + if (options.check_host_ip && ip_status == HOST_NEW) { + if (readonly) + log("%s host key for IP address " + "'%.128s' not in list of known hosts.", + type, ip); + else if (!add_host_to_hostfile(user_hostfile, ip, + host_key)) + log("Failed to add the %s host key for IP " + "address '%.128s' to the list of known " + "hosts (%.30s).", type, ip, user_hostfile); + else + log("Warning: Permanently added the %s host " + "key for IP address '%.128s' to the list " + "of known hosts.", type, ip); } break; case HOST_NEW: + if (readonly) + goto fail; /* 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 %s host key is known for %.200s and you have requested strict checking.", type, host); + /* + * User has requested strict host key checking. We + * will not add the host key automatically. The only + * alternative left is to abort. + */ + error("No %s host key is known for %.200s and you " + "have requested strict checking.", type, host); + goto fail; } else if (options.strict_host_key_checking == 2) { + has_keys = show_other_keys(host, host_key); /* The default */ - char prompt[1024]; - char *fp = key_fingerprint(host_key); - snprintf(prompt, sizeof(prompt), - "The authenticity of host '%.200s' can't be established.\n" + fp = key_fingerprint(host_key, SSH_FP_MD5, SSH_FP_HEX); + snprintf(msg, sizeof(msg), + "The authenticity of host '%.200s (%s)' can't be " + "established%s\n" "%s key fingerprint is %s.\n" - "Are you sure you want to continue connecting (yes/no)? ", - host, type, fp); - if (!read_yes_or_no(prompt, -1)) - fatal("Aborted by user!\n"); + "Are you sure you want to continue connecting " + "(yes/no)? ", + host, ip, + has_keys ? ",\nbut keys of different type are already " + "known for this host." : ".", + type, fp); + xfree(fp); + if (!confirm(msg)) + goto fail; } - if (options.check_host_ip && ip_status == HOST_NEW && strcmp(host, ip)) { + if (options.check_host_ip && ip_status == HOST_NEW) { snprintf(hostline, sizeof(hostline), "%s,%s", host, ip); hostp = hostline; } else hostp = host; - /* If not in strict mode, add the key automatically to the local known_hosts file. */ + /* + * If not in strict mode, add the key automatically to the + * local known_hosts file. + */ if (!add_host_to_hostfile(user_hostfile, hostp, host_key)) - log("Failed to add the host to the list of known hosts (%.500s).", - user_hostfile); + log("Failed to add the host to the list of known " + "hosts (%.500s).", user_hostfile); else - log("Warning: Permanently added '%.200s' (%s) to the list of known hosts.", - hostp, type); + log("Warning: Permanently added '%.200s' (%s) to the " + "list of known hosts.", hostp, type); break; case HOST_CHANGED: if (options.check_host_ip && host_ip_differ) { @@ -609,25 +690,35 @@ check_host_key(char *host, struct sockaddr *hostaddr, Key *host_key, error("and the key for the according IP address %s", ip); error("%s. This could either mean that", msg); error("DNS SPOOFING is happening or the IP address for the host"); - error("and its host key have changed at the same time"); + error("and its host key have changed at the same time."); + if (ip_status != HOST_NEW) + error("Offending key for IP in %s:%d", ip_file, ip_line); } /* The host key has changed. */ + fp = key_fingerprint(host_key, SSH_FP_MD5, SSH_FP_HEX); error("@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@"); error("@ WARNING: REMOTE 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 %s host key has just been changed.", type); + error("The fingerprint for the %s key sent by the remote host is\n%s.", + type, fp); error("Please contact your system administrator."); error("Add correct host key in %.100s to get rid of this message.", - user_hostfile); + user_hostfile); + error("Offending key in %s:%d", host_file, host_line); + xfree(fp); /* * 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("%s host key for %.200s has changed and you have requested strict checking.", type, host); + if (options.strict_host_key_checking) { + error("%s host key for %.200s has changed and you have " + "requested strict checking.", type, host); + goto fail; + } /* * If strict host key checking has not been requested, allow @@ -635,13 +726,27 @@ check_host_key(char *host, struct sockaddr *hostaddr, Key *host_key, * agent forwarding. */ if (options.password_authentication) { - error("Password authentication is disabled to avoid trojan horses."); + error("Password authentication is disabled to avoid " + "man-in-the-middle attacks."); options.password_authentication = 0; } if (options.forward_agent) { - error("Agent forwarding is disabled to avoid trojan horses."); + error("Agent forwarding is disabled to avoid " + "man-in-the-middle attacks."); options.forward_agent = 0; } + if (options.forward_x11) { + error("X11 forwarding is disabled to avoid " + "man-in-the-middle attacks."); + options.forward_x11 = 0; + } + if (options.num_local_forwards > 0 || + options.num_remote_forwards > 0) { + error("Port forwarding is disabled to avoid " + "man-in-the-middle attacks."); + options.num_local_forwards = + options.num_remote_forwards = 0; + } /* * XXX Should permit the user to change to use the new id. * This could be done by converting the host key to an @@ -650,9 +755,60 @@ check_host_key(char *host, struct sockaddr *hostaddr, Key *host_key, * accept the authentication. */ break; + case HOST_FOUND: + fatal("internal error"); + break; + } + + if (options.check_host_ip && host_status != HOST_CHANGED && + ip_status == HOST_CHANGED) { + snprintf(msg, sizeof(msg), + "Warning: the %s host key for '%.200s' " + "differs from the key for the IP address '%.128s'" + "\nOffending key for IP in %s:%d", + type, host, ip, ip_file, ip_line); + if (host_status == HOST_OK) { + len = strlen(msg); + snprintf(msg + len, sizeof(msg) - len, + "\nMatching host key in %s:%d", + host_file, host_line); + } + if (options.strict_host_key_checking == 1) { + log(msg); + error("Exiting, you have requested strict checking."); + goto fail; + } else if (options.strict_host_key_checking == 2) { + strlcat(msg, "\nAre you sure you want " + "to continue connecting (yes/no)? ", sizeof(msg)); + if (!confirm(msg)) + goto fail; + } else { + log(msg); + } } - if (options.check_host_ip) - xfree(ip); + + xfree(ip); + return 0; + +fail: + xfree(ip); + return -1; +} + +int +verify_host_key(char *host, struct sockaddr *hostaddr, Key *host_key) +{ + struct stat st; + + /* return ok if the key can be found in an old keyfile */ + if (stat(options.system_hostfile2, &st) == 0 || + stat(options.user_hostfile2, &st) == 0) { + if (check_host_key(host, hostaddr, host_key, /*readonly*/ 1, + options.user_hostfile2, options.system_hostfile2) == 0) + return 0; + } + return check_host_key(host, hostaddr, host_key, /*readonly*/ 0, + options.user_hostfile, options.system_hostfile); } /* @@ -663,17 +819,12 @@ check_host_key(char *host, struct sockaddr *hostaddr, Key *host_key, * This function does not require super-user privileges. */ void -ssh_login(int host_key_valid, RSA *own_host_key, const char *orighost, - struct sockaddr *hostaddr, uid_t original_real_uid) +ssh_login(Sensitive *sensitive, const char *orighost, + struct sockaddr *hostaddr, struct passwd *pw) { - struct passwd *pw; char *host, *cp; char *server_user, *local_user; - /* 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 %u not found from user database.", original_real_uid); local_user = xstrdup(pw->pw_name); server_user = options.user ? options.user : local_user; @@ -693,9 +844,83 @@ ssh_login(int host_key_valid, RSA *own_host_key, const char *orighost, /* authenticate user */ if (compat20) { ssh_kex2(host, hostaddr); - ssh_userauth2(server_user, host); + ssh_userauth2(local_user, server_user, host, sensitive); } else { ssh_kex(host, hostaddr); - ssh_userauth(local_user, server_user, host, host_key_valid, own_host_key); + ssh_userauth1(local_user, server_user, host, sensitive); + } +} + +void +ssh_put_password(char *password) +{ + int size; + char *padded; + + if (datafellows & SSH_BUG_PASSWORDPAD) { + packet_put_cstring(password); + return; + } + size = roundup(strlen(password) + 1, 32); + padded = xmalloc(size); + memset(padded, 0, size); + strlcpy(padded, password, size); + packet_put_string(padded, size); + memset(padded, 0, size); + xfree(padded); +} + +static int +show_key_from_file(const char *file, const char *host, int keytype) +{ + Key *found; + char *fp; + int line, ret; + + found = key_new(keytype); + if ((ret = lookup_key_in_hostfile_by_type(file, host, + keytype, found, &line))) { + fp = key_fingerprint(found, SSH_FP_MD5, SSH_FP_HEX); + log("WARNING: %s key found for host %s\n" + "in %s:%d\n" + "%s key fingerprint %s.", + key_type(found), host, file, line, + key_type(found), fp); + xfree(fp); + } + key_free(found); + return (ret); +} + +/* print all known host keys for a given host, but skip keys of given type */ +static int +show_other_keys(const char *host, Key *key) +{ + int type[] = { KEY_RSA1, KEY_RSA, KEY_DSA, -1}; + int i, found = 0; + + for (i = 0; type[i] != -1; i++) { + if (type[i] == key->type) + continue; + if (type[i] != KEY_RSA1 && + show_key_from_file(options.user_hostfile2, host, type[i])) { + found = 1; + continue; + } + if (type[i] != KEY_RSA1 && + show_key_from_file(options.system_hostfile2, host, type[i])) { + found = 1; + continue; + } + if (show_key_from_file(options.user_hostfile, host, type[i])) { + found = 1; + continue; + } + if (show_key_from_file(options.system_hostfile, host, type[i])) { + found = 1; + continue; + } + debug2("no key of type %d for host %s", type[i], host); } + return (found); }