- 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/ChangeLog b/ChangeLog
index e05b869..48d49de 100644
--- a/ChangeLog
+++ b/ChangeLog
@@ -16,6 +16,22 @@
      [ssh-ed25519.c]
      check for unsigned overflow; not reachable in OpenSSH but others might
      copy our code...
+   - 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@
 
 20140213
  - (dtucker) [configure.ac openbsd-compat/openssl-compat.{c,h}]  Add compat
diff --git a/readconf.c b/readconf.c
index 94e6459..dc884c9 100644
--- a/readconf.c
+++ b/readconf.c
@@ -1,4 +1,4 @@
-/* $OpenBSD: readconf.c,v 1.217 2014/02/22 01:32:19 djm Exp $ */
+/* $OpenBSD: readconf.c,v 1.218 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
@@ -1467,6 +1467,13 @@
 	return 1;
 }
 
+/* Returns 1 if a string option is unset or set to "none" or 0 otherwise. */
+int
+option_clear_or_none(const char *o)
+{
+	return o == NULL || strcasecmp(o, "none") == 0;
+}
+
 /*
  * Initializes options to special values that indicate that they have not yet
  * been set.  Read_config_file will only set options with this value. Options
@@ -1564,10 +1571,24 @@
 }
 
 /*
+ * A petite version of fill_default_options() that just fills the options
+ * needed for hostname canonicalization to proceed.
+ */
+void
+fill_default_options_for_canonicalization(Options *options)
+{
+	if (options->canonicalize_max_dots == -1)
+		options->canonicalize_max_dots = 1;
+	if (options->canonicalize_fallback_local == -1)
+		options->canonicalize_fallback_local = 1;
+	if (options->canonicalize_hostname == -1)
+		options->canonicalize_hostname = SSH_CANONICALISE_NO;
+}
+
+/*
  * Called after processing other sources of option data, this fills those
  * options for which no value has been specified with their default values.
  */
-
 void
 fill_default_options(Options * options)
 {
@@ -1722,7 +1743,7 @@
 		options->canonicalize_hostname = SSH_CANONICALISE_NO;
 #define CLEAR_ON_NONE(v) \
 	do { \
-		if (v != NULL && strcasecmp(v, "none") == 0) { \
+		if (option_clear_or_none(v)) { \
 			free(v); \
 			v = NULL; \
 		} \
diff --git a/readconf.h b/readconf.h
index 9723da0..75e3f8f 100644
--- a/readconf.h
+++ b/readconf.h
@@ -1,4 +1,4 @@
-/* $OpenBSD: readconf.h,v 1.100 2014/01/29 06:18:35 djm Exp $ */
+/* $OpenBSD: readconf.h,v 1.101 2014/02/23 20:11:36 djm Exp $ */
 
 /*
  * Author: Tatu Ylonen <ylo@cs.hut.fi>
@@ -176,12 +176,14 @@
 
 void     initialize_options(Options *);
 void     fill_default_options(Options *);
+void	 fill_default_options_for_canonicalization(Options *);
 int	 process_config_line(Options *, struct passwd *, const char *, char *,
     const char *, int, int *, int);
 int	 read_config_file(const char *, struct passwd *, const char *,
     Options *, int);
 int	 parse_forward(Forward *, const char *, int, int);
 int	 default_ssh_port(void);
+int	 option_clear_or_none(const char *);
 
 void	 add_local_forward(Options *, const Forward *);
 void	 add_remote_forward(Options *, const Forward *);
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));
diff --git a/ssh_config.5 b/ssh_config.5
index 3cadcd7..b580392 100644
--- a/ssh_config.5
+++ b/ssh_config.5
@@ -33,8 +33,8 @@
 .\" (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
 .\" THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 .\"
-.\" $OpenBSD: ssh_config.5,v 1.184 2014/01/19 04:48:08 djm Exp $
-.Dd $Mdocdate: January 19 2014 $
+.\" $OpenBSD: ssh_config.5,v 1.185 2014/02/23 20:11:36 djm Exp $
+.Dd $Mdocdate: February 23 2014 $
 .Dt SSH_CONFIG 5
 .Os
 .Sh NAME
@@ -263,6 +263,12 @@
 is set to
 .Dq always ,
 then canonicalization is applied to proxied connections too.
+.Pp
+If this option is enabled and canonicalisation results in the target hostname
+changing, then the configuration files are processed again using the new
+target name to pick up any new configuration in matching
+.Cm Host
+stanzas.
 .It Cm CanonicalizeMaxDots
 Specifies the maximum number of dot characters in a hostname before
 canonicalization is disabled.