allow the "Include" directive to expand the same set of %-tokens
authordjm <djm@openbsd.org>
Tue, 3 Sep 2024 05:29:55 +0000 (05:29 +0000)
committerdjm <djm@openbsd.org>
Tue, 3 Sep 2024 05:29:55 +0000 (05:29 +0000)
that "Match Exec" and environment variables.

ok dtucker@

usr.bin/ssh/readconf.c
usr.bin/ssh/ssh_config.5

index dfb4556..d8bd498 100644 (file)
@@ -1,4 +1,4 @@
-/* $OpenBSD: readconf.c,v 1.388 2024/08/23 04:51:00 deraadt Exp $ */
+/* $OpenBSD: readconf.c,v 1.389 2024/09/03 05:29:55 djm Exp $ */
 /*
  * Author: Tatu Ylonen <ylo@cs.hut.fi>
  * Copyright (c) 1995 Tatu Ylonen <ylo@cs.hut.fi>, Espoo, Finland
@@ -622,6 +622,63 @@ check_match_ifaddrs(const char *addrlist)
        return found;
 }
 
+/*
+ * Expand a "match exec" command or an Include path, caller must free returned
+ * value.
+ */
+static char *
+expand_match_exec_or_include_path(const char *path, Options *options,
+    struct passwd *pw, const char *host_arg, const char *original_host,
+    int final_pass, int is_include_path)
+{
+       char thishost[NI_MAXHOST], shorthost[NI_MAXHOST], portstr[NI_MAXSERV];
+       char uidstr[32], *conn_hash_hex, *keyalias, *jmphost, *ruser;
+       char *host, *ret;
+       int port;
+
+       port = options->port <= 0 ? default_ssh_port() : options->port;
+       ruser = options->user == NULL ? pw->pw_name : options->user;
+       if (final_pass) {
+               host = xstrdup(options->hostname);
+       } else if (options->hostname != NULL) {
+               /* NB. Please keep in sync with ssh.c:main() */
+               host = percent_expand(options->hostname,
+                   "h", host_arg, (char *)NULL);
+       } else {
+               host = xstrdup(host_arg);
+       }
+       if (gethostname(thishost, sizeof(thishost)) == -1)
+               fatal("gethostname: %s", strerror(errno));
+       jmphost = option_clear_or_none(options->jump_host) ?
+           "" : options->jump_host;
+       strlcpy(shorthost, thishost, sizeof(shorthost));
+       shorthost[strcspn(thishost, ".")] = '\0';
+       snprintf(portstr, sizeof(portstr), "%d", port);
+       snprintf(uidstr, sizeof(uidstr), "%llu",
+           (unsigned long long)pw->pw_uid);
+       conn_hash_hex = ssh_connection_hash(thishost, host,
+           portstr, ruser, jmphost);
+       keyalias = options->host_key_alias ?  options->host_key_alias : host;
+
+       ret = (is_include_path ? percent_dollar_expand : percent_expand)(path,
+           "C", conn_hash_hex,
+           "L", shorthost,
+           "d", pw->pw_dir,
+           "h", host,
+           "k", keyalias,
+           "l", thishost,
+           "n", original_host,
+           "p", portstr,
+           "r", ruser,
+           "u", pw->pw_name,
+           "i", uidstr,
+           "j", jmphost,
+           (char *)NULL);
+       free(host);
+       free(conn_hash_hex);
+       return ret;
+}
+
 /*
  * Parse and execute a Match directive.
  */
@@ -632,15 +689,12 @@ match_cfg_line(Options *options, char **condition, struct passwd *pw,
 {
        char *arg, *oattrib, *attrib, *cmd, *cp = *condition, *host, *criteria;
        const char *ruser;
-       int r, port, this_result, result = 1, attributes = 0, negate;
-       char thishost[NI_MAXHOST], shorthost[NI_MAXHOST], portstr[NI_MAXSERV];
-       char uidstr[32];
+       int r, this_result, result = 1, attributes = 0, negate;
 
        /*
         * Configuration is likely to be incomplete at this point so we
         * must be prepared to use default values.
         */
-       port = options->port <= 0 ? default_ssh_port() : options->port;
        ruser = options->user == NULL ? pw->pw_name : options->user;
        if (final_pass) {
                host = xstrdup(options->hostname);
@@ -742,37 +796,12 @@ match_cfg_line(Options *options, char **condition, struct passwd *pw,
                        if (r == (negate ? 1 : 0))
                                this_result = result = 0;
                } else if (strcasecmp(attrib, "exec") == 0) {
-                       char *conn_hash_hex, *keyalias, *jmphost;
-
-                       if (gethostname(thishost, sizeof(thishost)) == -1)
-                               fatal("gethostname: %s", strerror(errno));
-                       jmphost = option_clear_or_none(options->jump_host) ?
-                           "" : options->jump_host;
-                       strlcpy(shorthost, thishost, sizeof(shorthost));
-                       shorthost[strcspn(thishost, ".")] = '\0';
-                       snprintf(portstr, sizeof(portstr), "%d", port);
-                       snprintf(uidstr, sizeof(uidstr), "%llu",
-                           (unsigned long long)pw->pw_uid);
-                       conn_hash_hex = ssh_connection_hash(thishost, host,
-                           portstr, ruser, jmphost);
-                       keyalias = options->host_key_alias ?
-                           options->host_key_alias : host;
-
-                       cmd = percent_expand(arg,
-                           "C", conn_hash_hex,
-                           "L", shorthost,
-                           "d", pw->pw_dir,
-                           "h", host,
-                           "k", keyalias,
-                           "l", thishost,
-                           "n", original_host,
-                           "p", portstr,
-                           "r", ruser,
-                           "u", pw->pw_name,
-                           "i", uidstr,
-                           "j", jmphost,
-                           (char *)NULL);
-                       free(conn_hash_hex);
+                       if ((cmd = expand_match_exec_or_include_path(arg,
+                           options, pw, host_arg, original_host,
+                           final_pass, 0)) == NULL) {
+                               fatal("%.200s line %d: failed to expand match "
+                                   "exec '%.100s'", filename, linenum, arg);
+                       }
                        if (result != 1) {
                                /* skip execution if prior predicate failed */
                                debug3("%.200s line %d: skipped exec "
@@ -1967,6 +1996,15 @@ parse_pubkey_algos:
                                    filename, linenum, keyword);
                                goto out;
                        }
+                       /* Expand %tokens and environment variables */
+                       if ((p = expand_match_exec_or_include_path(arg,
+                           options, pw, host, original_host,
+                           flags & SSHCONF_FINAL, 1)) == NULL) {
+                               error("%.200s line %d: Unable to expand user "
+                                   "config file '%.100s'",
+                                   filename, linenum, arg);
+                               continue;
+                       }
                        /*
                         * Ensure all paths are anchored. User configuration
                         * files may begin with '~/' but system configurations
@@ -1974,17 +2012,19 @@ parse_pubkey_algos:
                         * as living in ~/.ssh for user configurations or
                         * /etc/ssh for system ones.
                         */
-                       if (*arg == '~' && (flags & SSHCONF_USERCONF) == 0) {
+                       if (*p == '~' && (flags & SSHCONF_USERCONF) == 0) {
                                error("%.200s line %d: bad include path %s.",
-                                   filename, linenum, arg);
+                                   filename, linenum, p);
                                goto out;
                        }
-                       if (!path_absolute(arg) && *arg != '~') {
+                       if (!path_absolute(p) && *p != '~') {
                                xasprintf(&arg2, "%s/%s",
                                    (flags & SSHCONF_USERCONF) ?
-                                   "~/" _PATH_SSH_USER_DIR : SSHDIR, arg);
-                       } else
-                               arg2 = xstrdup(arg);
+                                   "~/" _PATH_SSH_USER_DIR : SSHDIR, p);
+                       } else {
+                               arg2 = xstrdup(p);
+                       }
+                       free(p);
                        memset(&gl, 0, sizeof(gl));
                        r = glob(arg2, GLOB_TILDE, NULL, &gl);
                        if (r == GLOB_NOMATCH) {
@@ -2010,8 +2050,9 @@ parse_pubkey_algos:
                                    (oactive ? 0 : SSHCONF_NEVERMATCH),
                                    activep, want_final_pass, depth + 1);
                                if (r != 1 && errno != ENOENT) {
-                                       error("Can't open user config file "
-                                           "%.100s: %.100s", gl.gl_pathv[i],
+                                       error("%.200s line %d: Can't open user "
+                                           "config file %.100s: %.100s",
+                                           filename, linenum, gl.gl_pathv[i],
                                            strerror(errno));
                                        globfree(&gl);
                                        goto out;
index 81dccf8..d1ae1be 100644 (file)
@@ -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.399 2024/08/22 23:11:30 djm Exp $
-.Dd $Mdocdate: August 22 2024 $
+.\" $OpenBSD: ssh_config.5,v 1.400 2024/09/03 05:29:56 djm Exp $
+.Dd $Mdocdate: September 3 2024 $
 .Dt SSH_CONFIG 5
 .Os
 .Sh NAME
@@ -1182,7 +1182,12 @@ to unknown options that appear before it.
 Include the specified configuration file(s).
 Multiple pathnames may be specified and each pathname may contain
 .Xr glob 7
-wildcards and, for user configurations, shell-like
+wildcards,
+tokens as described in the
+.Sx TOKENS
+section, envrionment variables as described in the
+.Sx ENVIRONMENT VARIABLES
+section and, for user configurations, shell-like
 .Sq ~
 references to user home directories.
 Wildcards will be expanded and processed in lexical order.
@@ -2271,6 +2276,7 @@ The local username.
 .Cm ControlPath ,
 .Cm IdentityAgent ,
 .Cm IdentityFile ,
+.Cm Include ,
 .Cm KnownHostsCommand ,
 .Cm LocalForward ,
 .Cm Match exec ,
@@ -2319,6 +2325,7 @@ The keywords
 .Cm ControlPath ,
 .Cm IdentityAgent ,
 .Cm IdentityFile ,
+.Cm Include ,
 .Cm KnownHostsCommand ,
 and
 .Cm UserKnownHostsFile