X-Git-Url: http://andersk.mit.edu/gitweb/openssh.git/blobdiff_plain/c750d869a18eb5a8f8b78cb8e0ff6080ce97d4c0..29798ed00610835f71b5bfa8d8d14ca876cb2968:/sshconnect.c diff --git a/sshconnect.c b/sshconnect.c index 9f8458dc..0bd351f6 100644 --- a/sshconnect.c +++ b/sshconnect.c @@ -13,7 +13,7 @@ */ #include "includes.h" -RCSID("$OpenBSD: sshconnect.c,v 1.131 2002/07/12 13:29:09 itojun Exp $"); +RCSID("$OpenBSD: sshconnect.c,v 1.164 2005/06/06 11:20:36 djm Exp $"); #include @@ -31,62 +31,57 @@ RCSID("$OpenBSD: sshconnect.c,v 1.131 2002/07/12 13:29:09 itojun Exp $"); #include "readconf.h" #include "atomicio.h" #include "misc.h" -#include "readpass.h" + +#include "dns.h" char *client_version_string = NULL; char *server_version_string = NULL; +int matching_host_key_dns = 0; + /* import */ extern Options options; extern char *__progname; extern uid_t original_real_uid; extern uid_t original_effective_uid; +extern pid_t proxy_command_pid; #ifndef INET6_ADDRSTRLEN /* for non IPv6 machines */ #define INET6_ADDRSTRLEN 46 #endif +static int show_other_keys(const char *, Key *); +static void warn_changed_key(Key *); + /* * Connect to the given ssh server using a proxy command. */ static int ssh_proxy_connect(const char *host, u_short port, const char *proxy_command) { - Buffer command; - const char *cp; - char *command_string; + char *command_string, *tmp; int pin[2], pout[2]; pid_t pid; char strport[NI_MAXSERV]; + size_t len; /* Convert the port number into a string. */ snprintf(strport, sizeof strport, "%hu", 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, strport, strlen(strport)); - cp++; - continue; - } - buffer_append(&command, cp, 1); - } - buffer_append(&command, "\0", 1); - - /* Get the final command string. */ - command_string = buffer_ptr(&command); + /* + * Build the final command string in the buffer by making the + * appropriate substitutions to the given proxy command. + * + * Use "exec" to avoid "sh -c" processes on some platforms + * (e.g. Solaris) + */ + len = strlen(proxy_command) + 6; + tmp = xmalloc(len); + strlcpy(tmp, "exec ", len); + strlcat(tmp, proxy_command, len); + command_string = percent_expand(tmp, "h", host, + "p", strport, (char *)NULL); + xfree(tmp); /* Create pipes for communicating with the proxy. */ if (pipe(pin) < 0 || pipe(pout) < 0) @@ -132,13 +127,15 @@ ssh_proxy_connect(const char *host, u_short port, const char *proxy_command) /* Parent. */ if (pid < 0) fatal("fork failed: %.100s", strerror(errno)); + else + proxy_command_pid = pid; /* save pid to clean up later */ /* Close child side of the descriptors. */ close(pin[0]); close(pout[1]); /* Free the command name. */ - buffer_free(&command); + xfree(command_string); /* Set the connection file descriptors. */ packet_set_connection(pout[0], pin[1]); @@ -151,7 +148,7 @@ ssh_proxy_connect(const char *host, u_short port, const char *proxy_command) * Creates a (possibly privileged) socket for use as the ssh connection. */ static int -ssh_create_socket(int privileged, int family) +ssh_create_socket(int privileged, struct addrinfo *ai) { int sock, gaierr; struct addrinfo hints, *res; @@ -163,15 +160,16 @@ ssh_create_socket(int privileged, int family) if (privileged) { int p = IPPORT_RESERVED - 1; PRIV_START; - sock = rresvport_af(&p, family); + sock = rresvport_af(&p, ai->ai_family); PRIV_END; if (sock < 0) - error("rresvport: af=%d %.100s", family, strerror(errno)); + error("rresvport: af=%d %.100s", ai->ai_family, + strerror(errno)); else debug("Allocated local port %d.", p); return sock; } - sock = socket(family, SOCK_STREAM, 0); + sock = socket(ai->ai_family, ai->ai_socktype, ai->ai_protocol); if (sock < 0) error("socket: %.100s", strerror(errno)); @@ -180,8 +178,9 @@ ssh_create_socket(int privileged, int family) return sock; memset(&hints, 0, sizeof(hints)); - hints.ai_family = family; - hints.ai_socktype = SOCK_STREAM; + hints.ai_family = ai->ai_family; + hints.ai_socktype = ai->ai_socktype; + hints.ai_protocol = ai->ai_protocol; hints.ai_flags = AI_PASSIVE; gaierr = getaddrinfo(options.bind_address, "0", &hints, &res); if (gaierr) { @@ -200,6 +199,75 @@ ssh_create_socket(int privileged, int family) return sock; } +static int +timeout_connect(int sockfd, const struct sockaddr *serv_addr, + socklen_t addrlen, int timeout) +{ + fd_set *fdset; + struct timeval tv; + socklen_t optlen; + int fdsetsz, optval, rc, result = -1; + + if (timeout <= 0) + return (connect(sockfd, serv_addr, addrlen)); + + set_nonblock(sockfd); + rc = connect(sockfd, serv_addr, addrlen); + if (rc == 0) { + unset_nonblock(sockfd); + return (0); + } + if (errno != EINPROGRESS) + return (-1); + + fdsetsz = howmany(sockfd + 1, NFDBITS) * sizeof(fd_mask); + fdset = (fd_set *)xmalloc(fdsetsz); + + memset(fdset, 0, fdsetsz); + FD_SET(sockfd, fdset); + tv.tv_sec = timeout; + tv.tv_usec = 0; + + for (;;) { + rc = select(sockfd + 1, NULL, fdset, NULL, &tv); + if (rc != -1 || errno != EINTR) + break; + } + + switch (rc) { + case 0: + /* Timed out */ + errno = ETIMEDOUT; + break; + case -1: + /* Select error */ + debug("select: %s", strerror(errno)); + break; + case 1: + /* Completed or failed */ + optval = 0; + optlen = sizeof(optval); + if (getsockopt(sockfd, SOL_SOCKET, SO_ERROR, &optval, + &optlen) == -1) { + debug("getsockopt: %s", strerror(errno)); + break; + } + if (optval != 0) { + errno = optval; + break; + } + result = 0; + unset_nonblock(sockfd); + break; + default: + /* Should not occur */ + fatal("Bogus return (%d) from select()", rc); + } + + xfree(fdset); + return (result); +} + /* * Opens a TCP/IP connection to the remote server on the given host. * The address of the remote host will be returned in hostaddr. @@ -210,12 +278,6 @@ ssh_create_socket(int privileged, int family) * 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, @@ -227,16 +289,9 @@ ssh_connect(const char *host, struct sockaddr_storage * hostaddr, int sock = -1, attempt; char ntop[NI_MAXHOST], strport[NI_MAXSERV]; 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: needpriv %d", needpriv); + debug2("ssh_connect: needpriv %d", needpriv); /* Get default port if port has not been set. */ if (port == 0) { @@ -284,18 +339,17 @@ ssh_connect(const char *host, struct sockaddr_storage * hostaddr, host, ntop, strport); /* Create a socket for connecting. */ - sock = ssh_create_socket(needpriv, ai->ai_family); + sock = ssh_create_socket(needpriv, ai); if (sock < 0) /* Any error is already output */ continue; - if (connect(sock, ai->ai_addr, ai->ai_addrlen) >= 0) { + if (timeout_connect(sock, ai->ai_addr, ai->ai_addrlen, + options.connection_timeout) >= 0) { /* Successful connection. */ memcpy(hostaddr, ai->ai_addr, ai->ai_addrlen); break; } else { - if (errno == ECONNREFUSED) - full_failure = 0; debug("connect to address %s port %s: %s", ntop, strport, strerror(errno)); /* @@ -321,24 +375,15 @@ ssh_connect(const char *host, struct sockaddr_storage * hostaddr, /* Return failure if we didn't get a successful connection. */ if (attempt >= connection_attempts) { - log("ssh: connect to host %s port %s: %s", + error("ssh: connect to host %s port %s: %s", host, strport, strerror(errno)); - return full_failure ? ECONNABORTED : ECONNREFUSED; + return (-1); } 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)); */ - linger.l_onoff = 1; - linger.l_linger = 5; - setsockopt(sock, SOL_SOCKET, SO_LINGER, (void *)&linger, sizeof(linger)); - - /* Set keepalives if requested. */ - if (options.keepalives && + /* Set SO_KEEPALIVE if requested. */ + if (options.tcp_keep_alive && setsockopt(sock, SOL_SOCKET, SO_KEEPALIVE, (void *)&on, sizeof(on)) < 0) error("setsockopt SO_KEEPALIVE: %.100s", strerror(errno)); @@ -362,14 +407,15 @@ ssh_exchange_identification(void) int connection_out = packet_get_connection_out(); int minor1 = PROTOCOL_MINOR_1; - /* Read other side\'s version identification. */ + /* Read other side's version identification. */ for (;;) { for (i = 0; i < sizeof(buf) - 1; i++) { - int len = atomicio(read, connection_in, &buf[i], 1); - if (len < 0) - fatal("ssh_exchange_identification: read: %.100s", strerror(errno)); - if (len != 1) + size_t len = atomicio(read, connection_in, &buf[i], 1); + + if (len != 1 && errno == EPIPE) fatal("ssh_exchange_identification: Connection closed by remote host"); + else if (len != 1) + fatal("ssh_exchange_identification: read: %.100s", strerror(errno)); if (buf[i] == '\r') { buf[i] = '\n'; buf[i + 1] = 0; @@ -419,7 +465,7 @@ ssh_exchange_identification(void) enable_compat13(); minor1 = 3; if (options.forward_agent) { - log("Agent forwarding disabled for protocol 1.3"); + logit("Agent forwarding disabled for protocol 1.3"); options.forward_agent = 0; } } @@ -443,7 +489,7 @@ ssh_exchange_identification(void) compat20 ? PROTOCOL_MAJOR_2 : PROTOCOL_MAJOR_1, compat20 ? PROTOCOL_MINOR_2 : minor1, SSH_VERSION); - if (atomicio(write, connection_out, buf, strlen(buf)) != strlen(buf)) + if (atomicio(vwrite, connection_out, buf, strlen(buf)) != strlen(buf)) fatal("write: %.100s", strerror(errno)); client_version_string = xstrdup(buf); chop(client_version_string); @@ -485,12 +531,12 @@ check_host_key(char *host, struct sockaddr *hostaddr, Key *host_key, int readonly, const char *user_hostfile, const char *system_hostfile) { Key *file_key; - char *type = key_type(host_key); + const char *type = key_type(host_key); char *ip = NULL; char hostline[1000], *hostp, *fp; HostStatus host_status; HostStatus ip_status; - int local = 0, host_ip_differ = 0; + int r, local = 0, host_ip_differ = 0; int salen; char ntop[NI_MAXHOST]; char msg[1024]; @@ -610,16 +656,16 @@ check_host_key(char *host, struct sockaddr *hostaddr, Key *host_key, 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 " + logit("%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 " + host_key, options.hash_known_hosts)) + logit("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 " + logit("Warning: Permanently added the %s host " "key for IP address '%.128s' to the list " "of known hosts.", type, ip); } @@ -638,70 +684,96 @@ check_host_key(char *host, struct sockaddr *hostaddr, Key *host_key, "have requested strict checking.", type, host); goto fail; } else if (options.strict_host_key_checking == 2) { + char msg1[1024], msg2[1024]; + + if (show_other_keys(host, host_key)) + snprintf(msg1, sizeof(msg1), + "\nbut keys of different type are already" + " known for this host."); + else + snprintf(msg1, sizeof(msg1), "."); /* The default */ fp = key_fingerprint(host_key, SSH_FP_MD5, SSH_FP_HEX); + msg2[0] = '\0'; + if (options.verify_host_key_dns) { + if (matching_host_key_dns) + snprintf(msg2, sizeof(msg2), + "Matching host key fingerprint" + " found in DNS.\n"); + else + snprintf(msg2, sizeof(msg2), + "No matching host key fingerprint" + " found in DNS.\n"); + } snprintf(msg, sizeof(msg), "The authenticity of host '%.200s (%s)' can't be " - "established.\n" - "%s key fingerprint is %s.\n" + "established%s\n" + "%s key fingerprint is %s.\n%s" "Are you sure you want to continue connecting " - "(yes/no)? ", host, ip, type, fp); + "(yes/no)? ", + host, ip, msg1, type, fp, msg2); xfree(fp); if (!confirm(msg)) goto fail; } - 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 (!add_host_to_hostfile(user_hostfile, hostp, host_key)) - log("Failed to add the host to the list of known " + if (options.check_host_ip && ip_status == HOST_NEW) { + snprintf(hostline, sizeof(hostline), "%s,%s", + host, ip); + hostp = hostline; + if (options.hash_known_hosts) { + /* Add hash of host and IP separately */ + r = add_host_to_hostfile(user_hostfile, host, + host_key, options.hash_known_hosts) && + add_host_to_hostfile(user_hostfile, ip, + host_key, options.hash_known_hosts); + } else { + /* Add unhashed "host,ip" */ + r = add_host_to_hostfile(user_hostfile, + hostline, host_key, + options.hash_known_hosts); + } + } else { + r = add_host_to_hostfile(user_hostfile, host, host_key, + options.hash_known_hosts); + hostp = host; + } + + if (!r) + logit("Failed to add the host to the list of known " "hosts (%.500s).", user_hostfile); else - log("Warning: Permanently added '%.200s' (%s) to the " + logit("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) { - char *msg; + char *key_msg; if (ip_status == HOST_NEW) - msg = "is unknown"; + key_msg = "is unknown"; else if (ip_status == HOST_OK) - msg = "is unchanged"; + key_msg = "is unchanged"; else - msg = "has a different value"; + key_msg = "has a different value"; error("@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@"); error("@ WARNING: POSSIBLE DNS SPOOFING DETECTED! @"); error("@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@"); error("The %s host key for %s has changed,", type, host); error("and the key for the according IP address %s", ip); - error("%s. This could either mean that", msg); + error("%s. This could either mean that", key_msg); error("DNS SPOOFING is happening or the IP address for the host"); 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."); + warn_changed_key(host_key); error("Add correct host key in %.100s to get rid of this message.", 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 @@ -715,7 +787,7 @@ check_host_key(char *host, struct sockaddr *hostaddr, Key *host_key, /* * If strict host key checking has not been requested, allow - * the connection but without password authentication or + * the connection but without MITM-able authentication or * agent forwarding. */ if (options.password_authentication) { @@ -723,6 +795,17 @@ check_host_key(char *host, struct sockaddr *hostaddr, Key *host_key, "man-in-the-middle attacks."); options.password_authentication = 0; } + if (options.kbd_interactive_authentication) { + error("Keyboard-interactive authentication is disabled" + " to avoid man-in-the-middle attacks."); + options.kbd_interactive_authentication = 0; + options.challenge_response_authentication = 0; + } + if (options.challenge_response_authentication) { + error("Challenge/response authentication is disabled" + " to avoid man-in-the-middle attacks."); + options.challenge_response_authentication = 0; + } if (options.forward_agent) { error("Agent forwarding is disabled to avoid " "man-in-the-middle attacks."); @@ -748,6 +831,9 @@ 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 && @@ -764,7 +850,7 @@ check_host_key(char *host, struct sockaddr *hostaddr, Key *host_key, host_file, host_line); } if (options.strict_host_key_checking == 1) { - log(msg); + logit("%s", msg); error("Exiting, you have requested strict checking."); goto fail; } else if (options.strict_host_key_checking == 2) { @@ -773,7 +859,7 @@ check_host_key(char *host, struct sockaddr *hostaddr, Key *host_key, if (!confirm(msg)) goto fail; } else { - log(msg); + logit("%s", msg); } } @@ -785,10 +871,32 @@ fail: return -1; } +/* returns 0 if key verifies or -1 if key does NOT verify */ int verify_host_key(char *host, struct sockaddr *hostaddr, Key *host_key) { struct stat st; + int flags = 0; + + if (options.verify_host_key_dns && + verify_host_key_dns(host, hostaddr, host_key, &flags) == 0) { + + if (flags & DNS_VERIFY_FOUND) { + + if (options.verify_host_key_dns == 1 && + flags & DNS_VERIFY_MATCH && + flags & DNS_VERIFY_SECURE) + return 0; + + if (flags & DNS_VERIFY_MATCH) { + matching_host_key_dns = 1; + } else { + warn_changed_key(host_key); + error("Update the SSHFP RR in DNS with the new " + "host key to get rid of this message."); + } + } + } /* return ok if the key can be found in an old keyfile */ if (stat(options.system_hostfile2, &st) == 0 || @@ -859,3 +967,79 @@ ssh_put_password(char *password) 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); + logit("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); +} + +static void +warn_changed_key(Key *host_key) +{ + char *fp; + const char *type = key_type(host_key); + + 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."); + + xfree(fp); +}