- djm@cvs.openbsd.org 2014/02/23 20:11:36
     [readconf.c readconf.h ssh.c ssh_config.5]
     reparse ssh_config and ~/.ssh/config if hostname canonicalisation changes
     the hostname. This allows users to write configurations that always
     refer to canonical hostnames, e.g.

     CanonicalizeHostname yes
     CanonicalDomains int.example.org example.org
     CanonicalizeFallbackLocal no

     Host *.int.example.org
         Compression off
     Host *.example.org
         User djm

     ok markus@
diff --git a/ssh.c b/ssh.c
index add760c..b7dbea2 100644
--- a/ssh.c
+++ b/ssh.c
@@ -1,4 +1,4 @@
-/* $OpenBSD: ssh.c,v 1.399 2014/02/04 00:24:29 djm Exp $ */
+/* $OpenBSD: ssh.c,v 1.400 2014/02/23 20:11:36 djm Exp $ */
 /*
  * Author: Tatu Ylonen <ylo@cs.hut.fi>
  * Copyright (c) 1995 Tatu Ylonen <ylo@cs.hut.fi>, Espoo, Finland
@@ -231,16 +231,26 @@
 	}
 }
 
+/*
+ * Attempt to resolve a host name / port to a set of addresses and
+ * optionally return any CNAMEs encountered along the way.
+ * Returns NULL on failure.
+ * NB. this function must operate with a options having undefined members.
+ */
 static struct addrinfo *
-resolve_host(const char *name, u_int port, int logerr, char *cname, size_t clen)
+resolve_host(const char *name, int port, int logerr, char *cname, size_t clen)
 {
 	char strport[NI_MAXSERV];
 	struct addrinfo hints, *res;
 	int gaierr, loglevel = SYSLOG_LEVEL_DEBUG1;
 
+	if (port <= 0)
+		port = default_ssh_port();
+
 	snprintf(strport, sizeof strport, "%u", port);
 	memset(&hints, 0, sizeof(hints));
-	hints.ai_family = options.address_family;
+	hints.ai_family = options.address_family == -1 ?
+	    AF_UNSPEC : options.address_family;
 	hints.ai_socktype = SOCK_STREAM;
 	if (cname != NULL)
 		hints.ai_flags = AI_CANONNAME;
@@ -265,6 +275,7 @@
 /*
  * Check whether the cname is a permitted replacement for the hostname
  * and perform the replacement if it is.
+ * NB. this function must operate with a options having undefined members.
  */
 static int
 check_follow_cname(char **namep, const char *cname)
@@ -281,7 +292,7 @@
 	 * Don't attempt to canonicalize names that will be interpreted by
 	 * a proxy unless the user specifically requests so.
 	 */
-	if (options.proxy_command != NULL &&
+	if (!option_clear_or_none(options.proxy_command) &&
 	    options.canonicalize_hostname != SSH_CANONICALISE_ALWAYS)
 		return 0;
 	debug3("%s: check \"%s\" CNAME \"%s\"", __func__, *namep, cname);
@@ -305,9 +316,10 @@
  * Attempt to resolve the supplied hostname after applying the user's
  * canonicalization rules. Returns the address list for the host or NULL
  * if no name was found after canonicalization.
+ * NB. this function must operate with a options having undefined members.
  */
 static struct addrinfo *
-resolve_canonicalize(char **hostp, u_int port)
+resolve_canonicalize(char **hostp, int port)
 {
 	int i, ndots;
 	char *cp, *fullhost, cname_target[NI_MAXHOST];
@@ -315,13 +327,15 @@
 
 	if (options.canonicalize_hostname == SSH_CANONICALISE_NO)
 		return NULL;
+
 	/*
 	 * Don't attempt to canonicalize names that will be interpreted by
 	 * a proxy unless the user specifically requests so.
 	 */
-	if (options.proxy_command != NULL &&
+	if (!option_clear_or_none(options.proxy_command) &&
 	    options.canonicalize_hostname != SSH_CANONICALISE_ALWAYS)
 		return NULL;
+
 	/* Don't apply canonicalization to sufficiently-qualified hostnames */
 	ndots = 0;
 	for (cp = *hostp; *cp != '\0'; cp++) {
@@ -338,7 +352,9 @@
 		*cname_target = '\0';
 		xasprintf(&fullhost, "%s.%s.", *hostp,
 		    options.canonical_domains[i]);
-		if ((addrs = resolve_host(fullhost, options.port, 0,
+		debug3("%s: attempting \"%s\" => \"%s\"", __func__,
+		    *hostp, fullhost);
+		if ((addrs = resolve_host(fullhost, port, 0,
 		    cname_target, sizeof(cname_target))) == NULL) {
 			free(fullhost);
 			continue;
@@ -355,11 +371,41 @@
 		return addrs;
 	}
 	if (!options.canonicalize_fallback_local)
-		fatal("%s: Could not resolve host \"%s\"", __progname, host);
+		fatal("%s: Could not resolve host \"%s\"", __progname, *hostp);
+	debug2("%s: host %s not found in any suffix", __func__, *hostp);
 	return NULL;
 }
 
 /*
+ * Read per-user configuration file.  Ignore the system wide config
+ * file if the user specifies a config file on the command line.
+ */
+static void
+process_config_files(struct passwd *pw)
+{
+	char buf[MAXPATHLEN];
+	int r;
+
+	if (config != NULL) {
+		if (strcasecmp(config, "none") != 0 &&
+		    !read_config_file(config, pw, host, &options,
+		    SSHCONF_USERCONF))
+			fatal("Can't open user config file %.100s: "
+			    "%.100s", config, strerror(errno));
+	} else {
+		r = snprintf(buf, sizeof buf, "%s/%s", pw->pw_dir,
+		    _PATH_SSH_USER_CONFFILE);
+		if (r > 0 && (size_t)r < sizeof(buf))
+			(void)read_config_file(buf, pw, host, &options,
+			     SSHCONF_CHECKPERM|SSHCONF_USERCONF);
+
+		/* Read systemwide configuration file after user config. */
+		(void)read_config_file(_PATH_HOST_CONFIG_FILE, pw, host,
+		    &options, 0);
+	}
+}
+
+/*
  * Main program for the ssh client.
  */
 int
@@ -832,31 +878,54 @@
 	if (debug_flag)
 		logit("%s, %s", SSH_VERSION, SSLeay_version(SSLEAY_VERSION));
 
-	/*
-	 * Read per-user configuration file.  Ignore the system wide config
-	 * file if the user specifies a config file on the command line.
-	 */
-	if (config != NULL) {
-		if (strcasecmp(config, "none") != 0 &&
-		    !read_config_file(config, pw, host, &options,
-		    SSHCONF_USERCONF))
-			fatal("Can't open user config file %.100s: "
-			    "%.100s", config, strerror(errno));
-	} else {
-		r = snprintf(buf, sizeof buf, "%s/%s", pw->pw_dir,
-		    _PATH_SSH_USER_CONFFILE);
-		if (r > 0 && (size_t)r < sizeof(buf))
-			(void)read_config_file(buf, pw, host, &options,
-			     SSHCONF_CHECKPERM|SSHCONF_USERCONF);
+	/* Parse the configuration files */
+	process_config_files(pw);
 
-		/* Read systemwide configuration file after user config. */
-		(void)read_config_file(_PATH_HOST_CONFIG_FILE, pw, host,
-		    &options, 0);
+	/* Hostname canonicalisation needs a few options filled. */
+	fill_default_options_for_canonicalization(&options);
+
+	/* If the user has replaced the hostname then take it into use now */
+	if (options.hostname != NULL) {
+		/* NB. Please keep in sync with readconf.c:match_cfg_line() */
+		cp = percent_expand(options.hostname,
+		    "h", host, (char *)NULL);
+		free(host);
+		host = cp;
+	}
+
+	/* If canonicalization requested then try to apply it */
+	lowercase(host);
+	if (options.canonicalize_hostname != SSH_CANONICALISE_NO)
+		addrs = resolve_canonicalize(&host, options.port);
+
+	/*
+	 * If canonicalization not requested, or if it failed then try to
+	 * resolve the bare hostname name using the system resolver's usual
+	 * search rules. Skip the lookup if a ProxyCommand is being used
+	 * unless the user has specifically requested canonicalisation.
+	 */
+	if (addrs == NULL && (option_clear_or_none(options.proxy_command) ||
+            options.canonicalize_hostname == SSH_CANONICALISE_ALWAYS)) {
+		if ((addrs = resolve_host(host, options.port, 1,
+		    cname, sizeof(cname))) == NULL)
+			cleanup_exit(255); /* resolve_host logs the error */
+		check_follow_cname(&host, cname);
+	}
+
+	/*
+	 * If the target hostname has changed as a result of canonicalisation
+	 * then re-parse the configuration files as new stanzas may match.
+	 */
+	if (strcasecmp(host_arg, host) != 0) {
+		debug("Hostname has changed; re-reading configuration");
+		process_config_files(pw);
 	}
 
 	/* Fill configuration defaults. */
 	fill_default_options(&options);
 
+	if (options.port == 0)
+		options.port = default_ssh_port();
 	channel_set_af(options.address_family);
 
 	/* Tidy and check options */
@@ -899,37 +968,6 @@
 	if (options.user == NULL)
 		options.user = xstrdup(pw->pw_name);
 
-	/* Get default port if port has not been set. */
-	if (options.port == 0)
-		options.port = default_ssh_port();
-
-	/* preserve host name given on command line for %n expansion */
-	if (options.hostname != NULL) {
-		/* NB. Please keep in sync with readconf.c:match_cfg_line() */
-		cp = percent_expand(options.hostname,
-		    "h", host, (char *)NULL);
-		free(host);
-		host = cp;
-	}
-
-	/* If canonicalization requested then try to apply it */
-	lowercase(host);
-	if (options.canonicalize_hostname != SSH_CANONICALISE_NO)
-		addrs = resolve_canonicalize(&host, options.port);
-	/*
-	 * If canonicalization not requested, or if it failed then try to
-	 * resolve the bare hostname name using the system resolver's usual
-	 * search rules. Skip the lookup if a ProxyCommand is being used
-	 * unless the user has specifically requested canonicalisation.
-	 */
-	if (addrs == NULL && (options.proxy_command == NULL ||
-            options.canonicalize_hostname == SSH_CANONICALISE_ALWAYS)) {
-		if ((addrs = resolve_host(host, options.port, 1,
-		    cname, sizeof(cname))) == NULL)
-			cleanup_exit(255); /* resolve_host logs the error */
-		check_follow_cname(&host, cname);
-	}
-
 	if (gethostname(thishost, sizeof(thishost)) == -1)
 		fatal("gethostname: %s", strerror(errno));
 	strlcpy(shorthost, thishost, sizeof(shorthost));