- djm@cvs.openbsd.org 2010/11/29 23:45:51
     [auth.c hostfile.c hostfile.h ssh.c ssh_config.5 sshconnect.c]
     [sshconnect.h sshconnect2.c]
     automatically order the hostkeys requested by the client based on
     which hostkeys are already recorded in known_hosts. This avoids
     hostkey warnings when connecting to servers with new ECDSA keys
     that are preferred by default; with markus@
diff --git a/sshconnect.c b/sshconnect.c
index 78068c6..064bb74 100644
--- a/sshconnect.c
+++ b/sshconnect.c
@@ -1,4 +1,4 @@
-/* $OpenBSD: sshconnect.c,v 1.228 2010/10/06 21:10:21 djm Exp $ */
+/* $OpenBSD: sshconnect.c,v 1.229 2010/11/29 23:45:51 djm Exp $ */
 /*
  * Author: Tatu Ylonen <ylo@cs.hut.fi>
  * Copyright (c) 1995 Tatu Ylonen <ylo@cs.hut.fi>, Espoo, Finland
@@ -75,7 +75,7 @@
 extern uid_t original_real_uid;
 extern uid_t original_effective_uid;
 
-static int show_other_keys(const char *, Key *);
+static int show_other_keys(struct hostkeys *, Key *);
 static void warn_changed_key(Key *);
 
 /*
@@ -607,6 +607,79 @@
 	return 1;
 }
 
+static int
+sockaddr_is_local(struct sockaddr *hostaddr)
+{
+	switch (hostaddr->sa_family) {
+	case AF_INET:
+		return (ntohl(((struct sockaddr_in *)hostaddr)->
+		    sin_addr.s_addr) >> 24) == IN_LOOPBACKNET;
+	case AF_INET6:
+		return IN6_IS_ADDR_LOOPBACK(
+		    &(((struct sockaddr_in6 *)hostaddr)->sin6_addr));
+	default:
+		return 0;
+	}
+}
+
+/*
+ * Prepare the hostname and ip address strings that are used to lookup
+ * host keys in known_hosts files. These may have a port number appended.
+ */
+void
+get_hostfile_hostname_ipaddr(char *hostname, struct sockaddr *hostaddr,
+    u_short port, char **hostfile_hostname, char **hostfile_ipaddr)
+{
+	char ntop[NI_MAXHOST];
+	socklen_t addrlen;
+
+	switch (hostaddr == NULL ? -1 : hostaddr->sa_family) {
+	case -1:
+		addrlen = 0;
+		break;
+	case AF_INET:
+		addrlen = sizeof(struct sockaddr_in);
+		break;
+	case AF_INET6:
+		addrlen = sizeof(struct sockaddr_in6);
+		break;
+	default:
+		addrlen = sizeof(struct sockaddr);
+		break;
+	}
+
+	/*
+	 * We don't have the remote ip-address for connections
+	 * using a proxy command
+	 */
+	if (hostfile_ipaddr != NULL) {
+		if (options.proxy_command == NULL) {
+			if (getnameinfo(hostaddr, addrlen,
+			    ntop, sizeof(ntop), NULL, 0, NI_NUMERICHOST) != 0)
+			fatal("check_host_key: getnameinfo failed");
+			*hostfile_ipaddr = put_host_port(ntop, port);
+		} else {
+			*hostfile_ipaddr = xstrdup("<no hostip for proxy "
+			    "command>");
+		}
+	}
+
+	/*
+	 * Allow the user to record the key under a different name or
+	 * differentiate a non-standard port.  This is useful for ssh
+	 * tunneling over forwarded connections or if you run multiple
+	 * sshd's on different ports on the same machine.
+	 */
+	if (hostfile_hostname != NULL) {
+		if (options.host_key_alias != NULL) {
+			*hostfile_hostname = xstrdup(options.host_key_alias);
+			debug("using hostkeyalias: %s", *hostfile_hostname);
+		} else {
+			*hostfile_hostname = put_host_port(hostname, port);
+		}
+	}
+}
+
 /*
  * 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.
@@ -616,21 +689,21 @@
 #define ROQUIET	2
 static int
 check_host_key(char *hostname, struct sockaddr *hostaddr, u_short port,
-    Key *host_key, int readonly, const char *user_hostfile,
-    const char *system_hostfile)
+    Key *host_key, int readonly, char *user_hostfile,
+    char *system_hostfile)
 {
-	Key *file_key, *raw_key = NULL;
+	Key *raw_key = NULL;
 	const char *type;
 	char *ip = NULL, *host = NULL;
 	char hostline[1000], *hostp, *fp, *ra;
 	HostStatus host_status;
 	HostStatus ip_status;
-	int r, want_cert, local = 0, host_ip_differ = 0;
-	int salen;
-	char ntop[NI_MAXHOST];
+	int r, want_cert = key_is_cert(host_key), host_ip_differ = 0;
+	int local = sockaddr_is_local(hostaddr);
 	char msg[1024];
-	int len, host_line, ip_line, cancelled_forwarding = 0;
-	const char *host_file = NULL, *ip_file = NULL;
+	int len, cancelled_forwarding = 0;
+	struct hostkeys *host_hostkeys, *ip_hostkeys;
+	const struct hostkey_entry *host_found, *ip_found;
 
 	/*
 	 * Force accepting of the host key for loopback/localhost. The
@@ -640,23 +713,6 @@
 	 * essentially disables host authentication for localhost; however,
 	 * this is probably not a real problem.
 	 */
-	/**  hostaddr == 0! */
-	switch (hostaddr->sa_family) {
-	case AF_INET:
-		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));
-		salen = sizeof(struct sockaddr_in6);
-		break;
-	default:
-		local = 0;
-		salen = sizeof(struct sockaddr_storage);
-		break;
-	}
 	if (options.no_host_authentication_for_localhost == 1 && local &&
 	    options.host_key_alias == NULL) {
 		debug("Forcing accepting of host key for "
@@ -665,17 +721,10 @@
 	}
 
 	/*
-	 * We don't have the remote ip-address for connections
-	 * using a proxy command
+	 * Prepare the hostname and address strings used for hostkey lookup.
+	 * In some cases, these will have a port number appended.
 	 */
-	if (options.proxy_command == NULL) {
-		if (getnameinfo(hostaddr, salen, ntop, sizeof(ntop),
-		    NULL, 0, NI_NUMERICHOST) != 0)
-			fatal("check_host_key: getnameinfo failed");
-		ip = put_host_port(ntop, port);
-	} else {
-		ip = xstrdup("<no hostip for proxy command>");
-	}
+	get_hostfile_hostname_ipaddr(hostname, hostaddr, port, &host, &ip);
 
 	/*
 	 * Turn off check_host_ip if the connection is to localhost, via proxy
@@ -685,74 +734,52 @@
 	    strcmp(hostname, ip) == 0 || options.proxy_command != NULL))
 		options.check_host_ip = 0;
 
-	/*
-	 * Allow the user to record the key under a different name or
-	 * differentiate a non-standard port.  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 = xstrdup(options.host_key_alias);
-		debug("using hostkeyalias: %s", host);
-	} else {
-		host = put_host_port(hostname, port);
+	host_hostkeys = init_hostkeys();
+	load_hostkeys(host_hostkeys, host, user_hostfile);
+	load_hostkeys(host_hostkeys, host, system_hostfile);
+
+	ip_hostkeys = NULL;
+	if (!want_cert && options.check_host_ip) {
+		ip_hostkeys = init_hostkeys();
+		load_hostkeys(ip_hostkeys, ip, user_hostfile);
+		load_hostkeys(ip_hostkeys, ip, system_hostfile);
 	}
 
  retry:
+	/* Reload these as they may have changed on cert->key downgrade */
 	want_cert = key_is_cert(host_key);
 	type = key_type(host_key);
 
 	/*
-	 * Store the host key from the known host file in here so that we can
-	 * compare it with the key for the IP address.
-	 */
-	file_key = key_new(key_is_cert(host_key) ? KEY_UNSPEC : host_key->type);
-
-	/*
 	 * Check if the host key is present in the user's list of known
 	 * hosts or in the systemwide list.
 	 */
-	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);
-	}
+	host_status = check_key_in_hostkeys(host_hostkeys, host_key,
+	    &host_found);
+
 	/*
 	 * Also perform check for the ip address, skip the check if we are
 	 * localhost, looking for a certificate, or the hostname was an ip
 	 * address to begin with.
 	 */
-	if (!want_cert && options.check_host_ip) {
-		Key *ip_key = key_new(host_key->type);
-
-		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 (!want_cert && ip_hostkeys != NULL) {
+		ip_status = check_key_in_hostkeys(ip_hostkeys, host_key,
+		    &ip_found);
 		if (host_status == HOST_CHANGED &&
-		    (ip_status != HOST_CHANGED || !key_equal(ip_key, file_key)))
+		    (ip_status != HOST_CHANGED || 
+		    (ip_found != NULL &&
+		    !key_equal(ip_found->key, host_found->key))))
 			host_ip_differ = 1;
-
-		key_free(ip_key);
 	} else
 		ip_status = host_status;
 
-	key_free(file_key);
-
 	switch (host_status) {
 	case HOST_OK:
 		/* The host is known and the key matches. */
 		debug("Host '%.200s' is known and matches the %s host %s.",
 		    host, type, want_cert ? "certificate" : "key");
-		debug("Found %s in %s:%d",
-		    want_cert ? "CA key" : "key", host_file, host_line);
+		debug("Found %s in %s:%lu", want_cert ? "CA key" : "key",
+		    host_found->file, host_found->line);
 		if (want_cert && !check_host_cert(hostname, host_key))
 			goto fail;
 		if (options.check_host_ip && ip_status == HOST_NEW) {
@@ -803,7 +830,7 @@
 		} else if (options.strict_host_key_checking == 2) {
 			char msg1[1024], msg2[1024];
 
-			if (show_other_keys(host, host_key))
+			if (show_other_keys(host_hostkeys, host_key))
 				snprintf(msg1, sizeof(msg1),
 				    "\nbut keys of different type are already"
 				    " known for this host.");
@@ -844,8 +871,7 @@
 		 * local known_hosts file.
 		 */
 		if (options.check_host_ip && ip_status == HOST_NEW) {
-			snprintf(hostline, sizeof(hostline), "%s,%s",
-			    host, ip);
+			snprintf(hostline, sizeof(hostline), "%s,%s", host, ip);
 			hostp = hostline;
 			if (options.hash_known_hosts) {
 				/* Add hash of host and IP separately */
@@ -899,8 +925,8 @@
 			 * all hosts that one might visit.
 			 */
 			debug("Host certificate authority does not "
-			    "match %s in %s:%d", CA_MARKER,
-			    host_file, host_line);
+			    "match %s in %s:%lu", CA_MARKER,
+			    host_found->file, host_found->line);
 			goto fail;
 		}
 		if (readonly == ROQUIET)
@@ -922,13 +948,15 @@
 			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);
+				error("Offending key for IP in %s:%lu",
+				    ip_found->file, ip_found->line);
 		}
 		/* The host key has changed. */
 		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);
+		error("Offending %s key in %s:%lu", key_type(host_found->key),
+		    host_found->file, host_found->line);
 
 		/*
 		 * If strict host key checking is in use, the user will have
@@ -1013,13 +1041,13 @@
 		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);
+		    "\nOffending key for IP in %s:%lu",
+		    type, host, ip, ip_found->file, ip_found->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);
+			    "\nMatching host key in %s:%lu",
+			    host_found->file, host_found->line);
 		}
 		if (options.strict_host_key_checking == 1) {
 			logit("%s", msg);
@@ -1037,6 +1065,10 @@
 
 	xfree(ip);
 	xfree(host);
+	if (host_hostkeys != NULL)
+		free_hostkeys(host_hostkeys);
+	if (ip_hostkeys != NULL)
+		free_hostkeys(ip_hostkeys);
 	return 0;
 
 fail:
@@ -1056,6 +1088,10 @@
 		key_free(raw_key);
 	xfree(ip);
 	xfree(host);
+	if (host_hostkeys != NULL)
+		free_hostkeys(host_hostkeys);
+	if (ip_hostkeys != NULL)
+		free_hostkeys(ip_hostkeys);
 	return -1;
 }
 
@@ -1065,6 +1101,11 @@
 {
 	struct stat st;
 	int flags = 0;
+	char *fp;
+
+	fp = key_fingerprint(host_key, SSH_FP_MD5, SSH_FP_HEX);
+	debug("Server host key: %s %s", key_type(host_key), fp);
+	xfree(fp);
 
 	/* XXX certs are not yet supported for DNS */
 	if (!key_is_cert(host_key) && options.verify_host_key_dns &&
@@ -1108,7 +1149,7 @@
  */
 void
 ssh_login(Sensitive *sensitive, const char *orighost,
-    struct sockaddr *hostaddr, struct passwd *pw, int timeout_ms)
+    struct sockaddr *hostaddr, u_short port, struct passwd *pw, int timeout_ms)
 {
 	char *host, *cp;
 	char *server_user, *local_user;
@@ -1131,7 +1172,7 @@
 	/* key exchange */
 	/* authenticate user */
 	if (compat20) {
-		ssh_kex2(host, hostaddr);
+		ssh_kex2(host, hostaddr, port);
 		ssh_userauth2(local_user, server_user, host, sensitive);
 	} else {
 		ssh_kex(host, hostaddr);
@@ -1158,61 +1199,35 @@
 	xfree(padded);
 }
 
-static int
-show_key_from_file(const char *file, const char *host, int keytype)
-{
-	Key *found;
-	char *fp, *ra;
-	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);
-		ra = key_fingerprint(found, SSH_FP_MD5, SSH_FP_RANDOMART);
-		logit("WARNING: %s key found for host %s\n"
-		    "in %s:%d\n"
-		    "%s key fingerprint %s.\n%s\n",
-		    key_type(found), host, file, line,
-		    key_type(found), fp, ra);
-		xfree(ra);
-		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)
+show_other_keys(struct hostkeys *hostkeys, Key *key)
 {
 	int type[] = { KEY_RSA1, KEY_RSA, KEY_DSA, KEY_ECDSA, -1};
-	int i, found = 0;
+	int i, ret = 0;
+	char *fp, *ra;
+	const struct hostkey_entry *found;
 
 	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;
+		if (!lookup_key_in_hostkeys_by_type(hostkeys, type[i], &found))
 			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);
+		fp = key_fingerprint(found->key, SSH_FP_MD5, SSH_FP_HEX);
+		ra = key_fingerprint(found->key, SSH_FP_MD5, SSH_FP_RANDOMART);
+		logit("WARNING: %s key found for host %s\n"
+		    "in %s:%lu\n"
+		    "%s key fingerprint %s.",
+		    key_type(found->key),
+		    found->host, found->file, found->line,
+		    key_type(found->key), fp);
+		if (options.visual_host_key)
+			logit("%s", ra);
+		xfree(ra);
+		xfree(fp);
+		ret = 1;
 	}
-	return (found);
+	return ret;
 }
 
 static void