commit 9e34e0c59ab04514f9de9934a772283f7f372afe Author: 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.