Blob Blame History Raw
commit 9e34e0c59ab04514f9de9934a772283f7f372afe
Author: djm@openbsd.org <djm@openbsd.org>
Date:   Fri Nov 23 05:08:07 2018 +0000

    upstream: add a ssh_config "Match final" predicate
    
    Matches in same pass as "Match canonical" but doesn't require
    hostname canonicalisation be enabled. bz#2906 ok markus
    
    OpenBSD-Commit-ID: fba1dfe9f6e0cabcd0e2b3be13f7a434199beffa

diff --git a/readconf.c b/readconf.c
index 7850f2f5..7331ef5a 100644
--- a/readconf.c
+++ b/readconf.c
@@ -133,10 +133,11 @@
 
 static int read_config_file_depth(const char *filename, struct passwd *pw,
     const char *host, const char *original_host, Options *options,
-    int flags, int *activep, int depth);
+    int flags, int *activep, int *want_final_pass, int depth);
 static int process_config_line_depth(Options *options, struct passwd *pw,
     const char *host, const char *original_host, char *line,
-    const char *filename, int linenum, int *activep, int flags, int depth);
+    const char *filename, int linenum, int *activep, int flags,
+    int *want_final_pass, int depth);
 
 /* Keyword tokens. */
 
@@ -539,8 +540,8 @@ execute_in_shell(const char *cmd)
  */
 static int
 match_cfg_line(Options *options, char **condition, struct passwd *pw,
-    const char *host_arg, const char *original_host, int post_canon,
-    const char *filename, int linenum)
+    const char *host_arg, const char *original_host, int final_pass,
+    int *want_final_pass, const char *filename, int linenum)
 {
 	char *arg, *oattrib, *attrib, *cmd, *cp = *condition, *host, *criteria;
 	const char *ruser;
@@ -554,7 +555,7 @@ match_cfg_line(Options *options, char **condition, struct passwd *pw,
 	 */
 	port = options->port <= 0 ? default_ssh_port() : options->port;
 	ruser = options->user == NULL ? pw->pw_name : options->user;
-	if (post_canon) {
+	if (final_pass) {
 		host = xstrdup(options->hostname);
 	} else if (options->hostname != NULL) {
 		/* NB. Please keep in sync with ssh.c:main() */
@@ -586,8 +587,16 @@ match_cfg_line(Options *options, char **condition, struct passwd *pw,
 			goto out;
 		}
 		attributes++;
-		if (strcasecmp(attrib, "canonical") == 0) {
-			r = !!post_canon;  /* force bitmask member to boolean */
+		if (strcasecmp(attrib, "canonical") == 0 ||
+		    strcasecmp(attrib, "final") == 0) {
+			/*
+			 * If the config requests "Match final" then remember
+			 * this so we can perform a second pass later.
+			 */
+			if (strcasecmp(attrib, "final") == 0 &&
+			    want_final_pass != NULL)
+				*want_final_pass = 1;
+			r = !!final_pass;  /* force bitmask member to boolean */
 			if (r == (negate ? 1 : 0))
 				this_result = result = 0;
 			debug3("%.200s line %d: %smatched '%s'",
@@ -824,14 +833,14 @@ process_config_line(Options *options, struct passwd *pw, const char *host,
     int linenum, int *activep, int flags)
 {
 	return process_config_line_depth(options, pw, host, original_host,
-	    line, filename, linenum, activep, flags, 0);
+	    line, filename, linenum, activep, flags, NULL, 0);
 }
 
 #define WHITESPACE " \t\r\n"
 static int
 process_config_line_depth(Options *options, struct passwd *pw, const char *host,
     const char *original_host, char *line, const char *filename,
-    int linenum, int *activep, int flags, int depth)
+    int linenum, int *activep, int flags, int *want_final_pass, int depth)
 {
 	char *s, **charptr, *endofnumber, *keyword, *arg, *arg2;
 	char **cpptr, fwdarg[256];
@@ -1339,7 +1348,8 @@ parse_keytypes:
 			fatal("Host directive not supported as a command-line "
 			    "option");
 		value = match_cfg_line(options, &s, pw, host, original_host,
-		    flags & SSHCONF_POSTCANON, filename, linenum);
+		    flags & SSHCONF_FINAL, want_final_pass,
+		    filename, linenum);
 		if (value < 0)
 			fatal("%.200s line %d: Bad Match condition", filename,
 			    linenum);
@@ -1548,7 +1558,7 @@ parse_keytypes:
 				    pw, host, original_host, options,
 				    flags | SSHCONF_CHECKPERM |
 				    (oactive ? 0 : SSHCONF_NEVERMATCH),
-				    activep, depth + 1);
+				    activep, want_final_pass, depth + 1);
 				if (r != 1 && errno != ENOENT) {
 					fatal("Can't open user config file "
 					    "%.100s: %.100s", gl.gl_pathv[i],
@@ -1751,19 +1761,20 @@ parse_keytypes:
  */
 int
 read_config_file(const char *filename, struct passwd *pw, const char *host,
-    const char *original_host, Options *options, int flags)
+    const char *original_host, Options *options, int flags,
+    int *want_final_pass)
 {
 	int active = 1;
 
 	return read_config_file_depth(filename, pw, host, original_host,
-	    options, flags, &active, 0);
+	    options, flags, &active, want_final_pass, 0);
 }
 
 #define READCONF_MAX_DEPTH	16
 static int
 read_config_file_depth(const char *filename, struct passwd *pw,
     const char *host, const char *original_host, Options *options,
-    int flags, int *activep, int depth)
+    int flags, int *activep, int *want_final_pass, int depth)
 {
 	FILE *f;
 	char *line = NULL;
@@ -1798,7 +1809,8 @@ read_config_file_depth(const char *filename, struct passwd *pw,
 		/* Update line number counter. */
 		linenum++;
 		if (process_config_line_depth(options, pw, host, original_host,
-		    line, filename, linenum, activep, flags, depth) != 0)
+		    line, filename, linenum, activep, flags, want_final_pass,
+		    depth) != 0)
 			bad_options++;
 	}
 	free(line);
diff --git a/readconf.h b/readconf.h
index fc7e3825..8e36bf32 100644
--- a/readconf.h
+++ b/readconf.h
@@ -185,7 +185,7 @@ typedef struct {
 
 #define SSHCONF_CHECKPERM	1  /* check permissions on config file */
 #define SSHCONF_USERCONF	2  /* user provided config file not system */
-#define SSHCONF_POSTCANON	4  /* After hostname canonicalisation */
+#define SSHCONF_FINAL		4  /* Final pass over config, after canon. */
 #define SSHCONF_NEVERMATCH	8  /* Match/Host never matches; internal only */
 
 #define SSH_UPDATE_HOSTKEYS_NO	0
@@ -203,7 +203,7 @@ void	 fill_default_options_for_canonicalization(Options *);
 int	 process_config_line(Options *, struct passwd *, const char *,
     const char *, char *, const char *, int, int *, int);
 int	 read_config_file(const char *, struct passwd *, const char *,
-    const char *, Options *, int);
+    const char *, Options *, int, int *);
 int	 parse_forward(struct Forward *, const char *, int, int);
 int	 parse_jump(const char *, Options *, int);
 int	 parse_ssh_uri(const char *, char **, char **, int *);
diff --git a/ssh-keysign.c b/ssh-keysign.c
index 8f487b8c..7ea5ad0e 100644
--- a/ssh-keysign.c
+++ b/ssh-keysign.c
@@ -208,7 +208,8 @@ main(int argc, char **argv)
 
 	/* verify that ssh-keysign is enabled by the admin */
 	initialize_options(&options);
-	(void)read_config_file(_PATH_HOST_CONFIG_FILE, pw, "", "", &options, 0);
+	(void)read_config_file(_PATH_HOST_CONFIG_FILE, pw, "", "",
+	    &options, 0, NULL);
 	fill_default_options(&options);
 	if (options.enable_ssh_keysign != 1)
 		fatal("ssh-keysign not enabled in %s",
diff --git a/ssh.c b/ssh.c
index 1ac903d1..c6cb7847 100644
--- a/ssh.c
+++ b/ssh.c
@@ -527,7 +527,8 @@ check_load(int r, const char *path, const char *message)
  * file if the user specifies a config file on the command line.
  */
 static void
-process_config_files(const char *host_name, struct passwd *pw, int post_canon)
+process_config_files(const char *host_name, struct passwd *pw, int final_pass,
+    int *want_final_pass)
 {
 	char buf[PATH_MAX];
 	int r;
@@ -535,7 +536,8 @@ process_config_files(const char *host_name, struct passwd *pw, int post_canon)
 	if (config != NULL) {
 		if (strcasecmp(config, "none") != 0 &&
 		    !read_config_file(config, pw, host, host_name, &options,
-		    SSHCONF_USERCONF | (post_canon ? SSHCONF_POSTCANON : 0)))
+		    SSHCONF_USERCONF | (final_pass ? SSHCONF_FINAL : 0),
+		    want_final_pass))
 			fatal("Can't open user config file %.100s: "
 			    "%.100s", config, strerror(errno));
 	} else {
@@ -544,12 +546,12 @@ process_config_files(const char *host_name, struct passwd *pw, int post_canon)
 		if (r > 0 && (size_t)r < sizeof(buf))
 			(void)read_config_file(buf, pw, host, host_name,
 			    &options, SSHCONF_CHECKPERM | SSHCONF_USERCONF |
-			    (post_canon ? SSHCONF_POSTCANON : 0));
+			    (final_pass ? SSHCONF_FINAL : 0), want_final_pass);
 
 		/* Read systemwide configuration file after user config. */
 		(void)read_config_file(_PATH_HOST_CONFIG_FILE, pw,
 		    host, host_name, &options,
-		    post_canon ? SSHCONF_POSTCANON : 0);
+		    final_pass ? SSHCONF_FINAL : 0, want_final_pass);
 	}
 }
 
@@ -581,7 +583,7 @@ main(int ac, char **av)
 {
 	struct ssh *ssh = NULL;
 	int i, r, opt, exit_status, use_syslog, direct, timeout_ms;
-	int was_addr, config_test = 0, opt_terminated = 0;
+	int was_addr, config_test = 0, opt_terminated = 0, want_final_pass = 0;
 	char *p, *cp, *line, *argv0, buf[PATH_MAX], *logfile;
 	char cname[NI_MAXHOST];
 	struct stat st;
@@ -1089,7 +1091,9 @@ main(int ac, char **av)
 		);
 
 	/* Parse the configuration files */
-	process_config_files(host_arg, pw, 0);
+	process_config_files(host_arg, pw, 0, &want_final_pass);
+	if (want_final_pass)
+		debug("configuration requests final Match pass");
 
 	/* Hostname canonicalisation needs a few options filled. */
 	fill_default_options_for_canonicalization(&options);
@@ -1146,12 +1150,17 @@ main(int ac, char **av)
 	 * If canonicalisation is enabled then re-parse the configuration
 	 * files as new stanzas may match.
 	 */
-	if (options.canonicalize_hostname != 0) {
-		debug("Re-reading configuration after hostname "
-		    "canonicalisation");
+	if (options.canonicalize_hostname != 0 && !want_final_pass) {
+		debug("hostname canonicalisation enabled, "
+		    "will re-parse configuration");
+		want_final_pass = 1;
+	}
+
+	if (want_final_pass) {
+		debug("re-parsing configuration");
 		free(options.hostname);
 		options.hostname = xstrdup(host);
-		process_config_files(host_arg, pw, 1);
+		process_config_files(host_arg, pw, 1, NULL);
 		/*
 		 * Address resolution happens early with canonicalisation
 		 * enabled and the port number may have changed since, so
diff --git a/ssh_config.5 b/ssh_config.5
index 4d5b01d3..58a5fa1c 100644
--- a/ssh_config.5
+++ b/ssh_config.5
@@ -139,6 +139,7 @@ or the single token
 which always matches.
 The available criteria keywords are:
 .Cm canonical ,
+.Cm final ,
 .Cm exec ,
 .Cm host ,
 .Cm originalhost ,
@@ -148,12 +149,15 @@ and
 The
 .Cm all
 criteria must appear alone or immediately after
-.Cm canonical .
+.Cm canonical
+or
+.Cm final .
 Other criteria may be combined arbitrarily.
 All criteria but
 .Cm all
-and
 .Cm canonical
+and
+.Cm final
 require an argument.
 Criteria may be negated by prepending an exclamation mark
 .Pq Sq !\& .
@@ -166,6 +170,20 @@ after hostname canonicalization (see the
 option.)
 This may be useful to specify conditions that work with canonical host
 names only.
+.Pp
+The
+.Cm final
+keyword requests that the configuration be re-parsed (regardless of whether
+.Cm CanonicalizeHostname
+is enabled), and matches only during this final pass.
+If
+.Cm CanonicalizeHostname
+is enabled, then
+.Cm canonical
+and
+.Cm final
+match during the same pass.
+.Pp
 The
 .Cm exec
 keyword executes the specified command under the user's shell.