]> git.ipfire.org Git - thirdparty/openssh-portable.git/commitdiff
upstream: Add a sshd_config "Include" directive to allow inclusion
authordjm@openbsd.org <djm@openbsd.org>
Fri, 31 Jan 2020 22:42:45 +0000 (22:42 +0000)
committerDamien Miller <djm@mindrot.org>
Fri, 31 Jan 2020 23:20:24 +0000 (10:20 +1100)
of files. This has sensible semantics wrt Match blocks and accepts glob(3)
patterns to specify the included files. Based on patch by Jakub Jelen in
bz2468; feedback and ok markus@

OpenBSD-Commit-ID: 36ed0e845b872e33f03355b936a4fff02d5794ff

auth.c
servconf.c
servconf.h
sshd.c
sshd_config.5

diff --git a/auth.c b/auth.c
index b42d7e76c2c5c058387efd7fe94b68fc6a5c22ba..086b8ebb1626eada3c02751e844a4f70b3973c12 100644 (file)
--- a/auth.c
+++ b/auth.c
@@ -1,4 +1,4 @@
-/* $OpenBSD: auth.c,v 1.145 2020/01/23 07:10:22 dtucker Exp $ */
+/* $OpenBSD: auth.c,v 1.146 2020/01/31 22:42:45 djm Exp $ */
 /*
  * Copyright (c) 2000 Markus Friedl.  All rights reserved.
  *
@@ -79,6 +79,7 @@
 
 /* import */
 extern ServerOptions options;
+extern struct include_list includes;
 extern int use_privsep;
 extern struct sshbuf *loginmsg;
 extern struct passwd *privsep_pw;
@@ -571,7 +572,7 @@ getpwnamallow(struct ssh *ssh, const char *user)
 
        ci = get_connection_info(ssh, 1, options.use_dns);
        ci->user = user;
-       parse_server_match_config(&options, ci);
+       parse_server_match_config(&options, &includes, ci);
        log_change_level(options.log_level);
        process_permitopen(ssh, &options);
 
index 1e0718139994e658278c41e988f1e43fca6cd441..70f5f73f051921f1bc80573c514144e7e8995583 100644 (file)
@@ -1,5 +1,5 @@
 
-/* $OpenBSD: servconf.c,v 1.359 2020/01/23 10:24:29 dtucker Exp $ */
+/* $OpenBSD: servconf.c,v 1.360 2020/01/31 22:42:45 djm Exp $ */
 /*
  * Copyright (c) 1995 Tatu Ylonen <ylo@cs.hut.fi>, Espoo, Finland
  *                    All rights reserved
 #ifdef HAVE_UTIL_H
 #include <util.h>
 #endif
+#ifdef USE_SYSTEM_GLOB
+# include <glob.h>
+#else
+# include "openbsd-compat/glob.h"
+#endif
 
 #include "openbsd-compat/sys-queue.h"
 #include "xmalloc.h"
@@ -69,6 +74,9 @@ static void add_listen_addr(ServerOptions *, const char *,
     const char *, int);
 static void add_one_listen_addr(ServerOptions *, const char *,
     const char *, int);
+void parse_server_config_depth(ServerOptions *options, const char *filename,
+    struct sshbuf *conf, struct include_list *includes,
+    struct connection_info *connectinfo, int flags, int *activep, int depth);
 
 /* Use of privilege separation or not */
 extern int use_privsep;
@@ -526,7 +534,7 @@ typedef enum {
        sAcceptEnv, sSetEnv, sPermitTunnel,
        sMatch, sPermitOpen, sPermitListen, sForceCommand, sChrootDirectory,
        sUsePrivilegeSeparation, sAllowAgentForwarding,
-       sHostCertificate,
+       sHostCertificate, sInclude,
        sRevokedKeys, sTrustedUserCAKeys, sAuthorizedPrincipalsFile,
        sAuthorizedPrincipalsCommand, sAuthorizedPrincipalsCommandUser,
        sKexAlgorithms, sCASignatureAlgorithms, sIPQoS, sVersionAddendum,
@@ -538,9 +546,10 @@ typedef enum {
        sDeprecated, sIgnore, sUnsupported
 } ServerOpCodes;
 
-#define SSHCFG_GLOBAL  0x01    /* allowed in main section of sshd_config */
-#define SSHCFG_MATCH   0x02    /* allowed inside a Match section */
-#define SSHCFG_ALL     (SSHCFG_GLOBAL|SSHCFG_MATCH)
+#define SSHCFG_GLOBAL          0x01    /* allowed in main section of config */
+#define SSHCFG_MATCH           0x02    /* allowed inside a Match section */
+#define SSHCFG_ALL             (SSHCFG_GLOBAL|SSHCFG_MATCH)
+#define SSHCFG_NEVERMATCH      0x04  /* Match never matches; internal only */
 
 /* Textual representation of the tokens. */
 static struct {
@@ -669,6 +678,7 @@ static struct {
        { "trustedusercakeys", sTrustedUserCAKeys, SSHCFG_ALL },
        { "authorizedprincipalsfile", sAuthorizedPrincipalsFile, SSHCFG_ALL },
        { "kexalgorithms", sKexAlgorithms, SSHCFG_GLOBAL },
+       { "include", sInclude, SSHCFG_ALL },
        { "ipqos", sIPQoS, SSHCFG_ALL },
        { "authorizedkeyscommand", sAuthorizedKeysCommand, SSHCFG_ALL },
        { "authorizedkeyscommanduser", sAuthorizedKeysCommandUser, SSHCFG_ALL },
@@ -1240,13 +1250,14 @@ static const struct multistate multistate_tcpfwd[] = {
        { NULL, -1 }
 };
 
-int
-process_server_config_line(ServerOptions *options, char *line,
+static int
+process_server_config_line_depth(ServerOptions *options, char *line,
     const char *filename, int linenum, int *activep,
-    struct connection_info *connectinfo)
+    struct connection_info *connectinfo, int inc_flags, int depth,
+    struct include_list *includes)
 {
        char ch, *cp, ***chararrayptr, **charptr, *arg, *arg2, *p;
-       int cmdline = 0, *intptr, value, value2, n, port;
+       int cmdline = 0, *intptr, value, value2, n, port, oactive, r, found;
        SyslogFacility *log_facility_ptr;
        LogLevel *log_level_ptr;
        ServerOpCodes opcode;
@@ -1255,6 +1266,8 @@ process_server_config_line(ServerOptions *options, char *line,
        long long val64;
        const struct multistate *multistate_ptr;
        const char *errstr;
+       struct include_item *item;
+       glob_t gbuf;
 
        /* Strip trailing whitespace. Allow \f (form feed) at EOL only */
        if ((len = strlen(line)) == 0)
@@ -1281,7 +1294,7 @@ process_server_config_line(ServerOptions *options, char *line,
                cmdline = 1;
                activep = &cmdline;
        }
-       if (*activep && opcode != sMatch)
+       if (*activep && opcode != sMatch && opcode != sInclude)
                debug3("%s:%d setting %s %s", filename, linenum, arg, cp);
        if (*activep == 0 && !(flags & SSHCFG_MATCH)) {
                if (connectinfo == NULL) {
@@ -1954,6 +1967,96 @@ process_server_config_line(ServerOptions *options, char *line,
                        *intptr = value;
                break;
 
+       case sInclude:
+               if (cmdline) {
+                       fatal("Include directive not supported as a "
+                           "command-line option");
+               }
+               value = 0;
+               while ((arg2 = strdelim(&cp)) != NULL && *arg2 != '\0') {
+                       value++;
+                       found = 0;
+                       if (*arg2 != '/' && *arg2 != '~') {
+                               xasprintf(&arg, "%s/%s", SSHDIR, arg);
+                       } else
+                               arg = xstrdup(arg2);
+
+                       /*
+                        * Don't let included files clobber the containing
+                        * file's Match state.
+                        */
+                       oactive = *activep;
+
+                       /* consult cache of include files */
+                       TAILQ_FOREACH(item, includes, entry) {
+                               if (strcmp(item->selector, arg) != 0)
+                                       continue;
+                               if (item->filename != NULL) {
+                                       parse_server_config_depth(options,
+                                           item->filename, item->contents,
+                                           includes, connectinfo,
+                                           (oactive ? 0 : SSHCFG_NEVERMATCH),
+                                           activep, depth + 1);
+                               }
+                               found = 1;
+                               *activep = oactive;
+                       }
+                       if (found != 0) {
+                               free(arg);
+                               continue;
+                       }
+
+                       /* requested glob was not in cache */
+                       debug2("%s line %d: new include %s",
+                           filename, linenum, arg);
+                       if ((r = glob(arg, 0, NULL, &gbuf)) != 0) {
+                               if (r != GLOB_NOMATCH) {
+                                       fatal("%s line %d: include \"%s\" "
+                                           "glob failed", filename,
+                                           linenum, arg);
+                               }
+                               /*
+                                * If no entry matched then record a
+                                * placeholder to skip later glob calls.
+                                */
+                               debug2("%s line %d: no match for %s",
+                                   filename, linenum, arg);
+                               item = xcalloc(1, sizeof(*item));
+                               item->selector = strdup(arg);
+                               TAILQ_INSERT_TAIL(includes,
+                                   item, entry);
+                       }
+                       if (gbuf.gl_pathc > INT_MAX)
+                               fatal("%s: too many glob results", __func__);
+                       for (n = 0; n < (int)gbuf.gl_pathc; n++) {
+                               debug2("%s line %d: including %s",
+                                   filename, linenum, gbuf.gl_pathv[n]);
+                               item = xcalloc(1, sizeof(*item));
+                               item->selector = strdup(arg);
+                               item->filename = strdup(gbuf.gl_pathv[n]);
+                               if ((item->contents = sshbuf_new()) == NULL) {
+                                       fatal("%s: sshbuf_new failed",
+                                           __func__);
+                               }
+                               load_server_config(item->filename,
+                                   item->contents);
+                               parse_server_config_depth(options,
+                                   item->filename, item->contents,
+                                   includes, connectinfo,
+                                   (oactive ? 0 : SSHCFG_NEVERMATCH),
+                                   activep, depth + 1);
+                               *activep = oactive;
+                               TAILQ_INSERT_TAIL(includes, item, entry);
+                       }
+                       globfree(&gbuf);
+                       free(arg);
+               }
+               if (value == 0) {
+                       fatal("%s line %d: Include missing filename argument",
+                           filename, linenum);
+               }
+               break;
+
        case sMatch:
                if (cmdline)
                        fatal("Match directive not supported as a command-line "
@@ -1962,7 +2065,7 @@ process_server_config_line(ServerOptions *options, char *line,
                if (value < 0)
                        fatal("%s line %d: Bad Match condition", filename,
                            linenum);
-               *activep = value;
+               *activep = (inc_flags & SSHCFG_NEVERMATCH) ? 0 : value;
                break;
 
        case sPermitListen:
@@ -2256,6 +2359,16 @@ process_server_config_line(ServerOptions *options, char *line,
        return 0;
 }
 
+int
+process_server_config_line(ServerOptions *options, char *line,
+    const char *filename, int linenum, int *activep,
+    struct connection_info *connectinfo, struct include_list *includes)
+{
+       return process_server_config_line_depth(options, line, filename,
+           linenum, activep, connectinfo, 0, 0, includes);
+}
+
+
 /* Reads the server configuration file. */
 
 void
@@ -2294,12 +2407,13 @@ load_server_config(const char *filename, struct sshbuf *conf)
 
 void
 parse_server_match_config(ServerOptions *options,
-   struct connection_info *connectinfo)
+   struct include_list *includes, struct connection_info *connectinfo)
 {
        ServerOptions mo;
 
        initialize_server_options(&mo);
-       parse_server_config(&mo, "reprocess config", cfg, connectinfo);
+       parse_server_config(&mo, "reprocess config", cfg, includes,
+           connectinfo);
        copy_set_server_options(options, &mo, 0);
 }
 
@@ -2443,22 +2557,27 @@ copy_set_server_options(ServerOptions *dst, ServerOptions *src, int preauth)
 #undef M_CP_STROPT
 #undef M_CP_STRARRAYOPT
 
+#define SERVCONF_MAX_DEPTH     16
 void
-parse_server_config(ServerOptions *options, const char *filename,
-    struct sshbuf *conf, struct connection_info *connectinfo)
+parse_server_config_depth(ServerOptions *options, const char *filename,
+    struct sshbuf *conf, struct include_list *includes,
+    struct connection_info *connectinfo, int flags, int *activep, int depth)
 {
-       int active, linenum, bad_options = 0;
+       int linenum, bad_options = 0;
        char *cp, *obuf, *cbuf;
 
+       if (depth < 0 || depth > SERVCONF_MAX_DEPTH)
+               fatal("Too many recursive configuration includes");
+
        debug2("%s: config %s len %zu", __func__, filename, sshbuf_len(conf));
 
        if ((obuf = cbuf = sshbuf_dup_string(conf)) == NULL)
                fatal("%s: sshbuf_dup_string failed", __func__);
-       active = connectinfo ? 0 : 1;
        linenum = 1;
        while ((cp = strsep(&cbuf, "\n")) != NULL) {
-               if (process_server_config_line(options, cp, filename,
-                   linenum++, &active, connectinfo) != 0)
+               if (process_server_config_line_depth(options, cp,
+                   filename, linenum++, activep, connectinfo, flags,
+                   depth, includes) != 0)
                        bad_options++;
        }
        free(obuf);
@@ -2468,6 +2587,16 @@ parse_server_config(ServerOptions *options, const char *filename,
        process_queued_listen_addrs(options);
 }
 
+void
+parse_server_config(ServerOptions *options, const char *filename,
+    struct sshbuf *conf, struct include_list *includes,
+    struct connection_info *connectinfo)
+{
+       int active = connectinfo ? 0 : 1;
+       parse_server_config_depth(options, filename, conf, includes,
+           connectinfo, 0, &active, 0);
+}
+
 static const char *
 fmt_multistate_int(int val, const struct multistate *m)
 {
index 6fc1efb2c8a2bedd008c3e06f5c481e5c819e216..deda09d934fb53b8481b7ba15e59d28be456d349 100644 (file)
@@ -1,4 +1,4 @@
-/* $OpenBSD: servconf.h,v 1.142 2019/12/15 18:57:30 djm Exp $ */
+/* $OpenBSD: servconf.h,v 1.143 2020/01/31 22:42:45 djm Exp $ */
 
 /*
  * Author: Tatu Ylonen <ylo@cs.hut.fi>
@@ -16,6 +16,8 @@
 #ifndef SERVCONF_H
 #define SERVCONF_H
 
+#include <sys/queue.h>
+
 #define MAX_PORTS              256     /* Max # ports. */
 
 #define MAX_SUBSYSTEMS         256     /* Max # subsystems. */
@@ -230,6 +232,15 @@ struct connection_info {
                                 * unspecified */
 };
 
+/* List of included files for re-exec from the parsed configuration */
+struct include_item {
+       char *selector;
+       char *filename;
+       struct sshbuf *contents;
+       TAILQ_ENTRY(include_item) entry;
+};
+TAILQ_HEAD(include_list, include_item);
+
 
 /*
  * These are string config options that must be copied between the
@@ -269,12 +280,13 @@ struct connection_info *get_connection_info(struct ssh *, int, int);
 void    initialize_server_options(ServerOptions *);
 void    fill_default_server_options(ServerOptions *);
 int     process_server_config_line(ServerOptions *, char *, const char *, int,
-            int *, struct connection_info *);
+            int *, struct connection_info *, struct include_list *includes);
 void    process_permitopen(struct ssh *ssh, ServerOptions *options);
 void    load_server_config(const char *, struct sshbuf *);
 void    parse_server_config(ServerOptions *, const char *, struct sshbuf *,
-            struct connection_info *);
-void    parse_server_match_config(ServerOptions *, struct connection_info *);
+            struct include_list *includes, struct connection_info *);
+void    parse_server_match_config(ServerOptions *,
+            struct include_list *includes, struct connection_info *);
 int     parse_server_match_testspec(struct connection_info *, char *);
 int     server_match_spec_complete(struct connection_info *);
 void    copy_set_server_options(ServerOptions *, ServerOptions *, int);
diff --git a/sshd.c b/sshd.c
index 46fdf7ee384a17907363fcbd43a5b40daf63fde4..57fab04254aaddab670f5f5e49bf4072b10e40c9 100644 (file)
--- a/sshd.c
+++ b/sshd.c
@@ -1,4 +1,4 @@
-/* $OpenBSD: sshd.c,v 1.545 2020/01/24 23:56:01 djm Exp $ */
+/* $OpenBSD: sshd.c,v 1.546 2020/01/31 22:42:45 djm Exp $ */
 /*
  * Author: Tatu Ylonen <ylo@cs.hut.fi>
  * Copyright (c) 1995 Tatu Ylonen <ylo@cs.hut.fi>, Espoo, Finland
@@ -251,6 +251,9 @@ struct sshauthopt *auth_opts = NULL;
 /* sshd_config buffer */
 struct sshbuf *cfg;
 
+/* Included files from the configuration file */
+struct include_list includes = TAILQ_HEAD_INITIALIZER(includes);
+
 /* message to be displayed after login */
 struct sshbuf *loginmsg;
 
@@ -870,30 +873,45 @@ usage(void)
 static void
 send_rexec_state(int fd, struct sshbuf *conf)
 {
-       struct sshbuf *m;
+       struct sshbuf *m = NULL, *inc = NULL;
+       struct include_item *item = NULL;
        int r;
 
        debug3("%s: entering fd = %d config len %zu", __func__, fd,
            sshbuf_len(conf));
 
+       if ((m = sshbuf_new()) == NULL || (inc = sshbuf_new()) == NULL)
+               fatal("%s: sshbuf_new failed", __func__);
+
+       /* pack includes into a string */
+       TAILQ_FOREACH(item, &includes, entry) {
+               if ((r = sshbuf_put_cstring(inc, item->selector)) != 0 ||
+                   (r = sshbuf_put_cstring(inc, item->filename)) != 0 ||
+                   (r = sshbuf_put_stringb(inc, item->contents)) != 0)
+                       fatal("%s: buffer error: %s", __func__, ssh_err(r));
+       }
+
        /*
         * Protocol from reexec master to child:
         *      string  configuration
-        *      string rngseed          (only if OpenSSL is not self-seeded)
+        *      string  included_files[] {
+        *              string  selector
+        *              string  filename
+        *              string  contents
+        *      }
+        *      string  rng_seed (if required)
         */
-       if ((m = sshbuf_new()) == NULL)
-               fatal("%s: sshbuf_new failed", __func__);
-       if ((r = sshbuf_put_stringb(m, conf)) != 0)
+       if ((r = sshbuf_put_stringb(m, conf)) != 0 ||
+           (r = sshbuf_put_stringb(m, inc)) != 0)
                fatal("%s: buffer error: %s", __func__, ssh_err(r));
-
 #if defined(WITH_OPENSSL) && !defined(OPENSSL_PRNG_ONLY)
        rexec_send_rng_seed(m);
 #endif
-
        if (ssh_msg_send(fd, 0, m) == -1)
                fatal("%s: ssh_msg_send failed", __func__);
 
        sshbuf_free(m);
+       sshbuf_free(inc);
 
        debug3("%s: done", __func__);
 }
@@ -901,14 +919,15 @@ send_rexec_state(int fd, struct sshbuf *conf)
 static void
 recv_rexec_state(int fd, struct sshbuf *conf)
 {
-       struct sshbuf *m;
+       struct sshbuf *m, *inc;
        u_char *cp, ver;
        size_t len;
        int r;
+       struct include_item *item;
 
        debug3("%s: entering fd = %d", __func__, fd);
 
-       if ((m = sshbuf_new()) == NULL)
+       if ((m = sshbuf_new()) == NULL || (inc = sshbuf_new()) == NULL)
                fatal("%s: sshbuf_new failed", __func__);
        if (ssh_msg_recv(fd, m) == -1)
                fatal("%s: ssh_msg_recv failed", __func__);
@@ -916,14 +935,28 @@ recv_rexec_state(int fd, struct sshbuf *conf)
                fatal("%s: buffer error: %s", __func__, ssh_err(r));
        if (ver != 0)
                fatal("%s: rexec version mismatch", __func__);
-       if ((r = sshbuf_get_string(m, &cp, &len)) != 0)
-               fatal("%s: buffer error: %s", __func__, ssh_err(r));
-       if (conf != NULL && (r = sshbuf_put(conf, cp, len)))
+       if ((r = sshbuf_get_string(m, &cp, &len)) != 0 ||
+           (r = sshbuf_get_stringb(m, inc)) != 0)
                fatal("%s: buffer error: %s", __func__, ssh_err(r));
+
 #if defined(WITH_OPENSSL) && !defined(OPENSSL_PRNG_ONLY)
        rexec_recv_rng_seed(m);
 #endif
 
+       if (conf != NULL && (r = sshbuf_put(conf, cp, len)))
+               fatal("%s: buffer error: %s", __func__, ssh_err(r));
+
+       while (sshbuf_len(inc) != 0) {
+               item = xcalloc(1, sizeof(*item));
+               if ((item->contents = sshbuf_new()) == NULL)
+                       fatal("%s: sshbuf_new failed", __func__);
+               if ((r = sshbuf_get_cstring(inc, &item->selector, NULL)) != 0 ||
+                   (r = sshbuf_get_cstring(inc, &item->filename, NULL)) != 0 ||
+                   (r = sshbuf_get_stringb(inc, item->contents)) != 0)
+                       fatal("%s: buffer error: %s", __func__, ssh_err(r));
+               TAILQ_INSERT_TAIL(&includes, item, entry);
+       }
+
        free(cp);
        sshbuf_free(m);
 
@@ -1600,7 +1633,7 @@ main(int ac, char **av)
                case 'o':
                        line = xstrdup(optarg);
                        if (process_server_config_line(&options, line,
-                           "command-line", 0, NULL, NULL) != 0)
+                           "command-line", 0, NULL, NULL, &includes) != 0)
                                exit(1);
                        free(line);
                        break;
@@ -1669,7 +1702,7 @@ main(int ac, char **av)
                load_server_config(config_file_name, cfg);
 
        parse_server_config(&options, rexeced_flag ? "rexec" : config_file_name,
-           cfg, NULL);
+           cfg, &includes, NULL);
 
        /* Fill in default values for those options not explicitly set. */
        fill_default_server_options(&options);
@@ -1895,7 +1928,7 @@ main(int ac, char **av)
                if (connection_info == NULL)
                        connection_info = get_connection_info(ssh, 0, 0);
                connection_info->test = 1;
-               parse_server_match_config(&options, connection_info);
+               parse_server_match_config(&options, &includes, connection_info);
                dump_config(&options);
        }
 
index 1395a5e6db384892c59473ae9a4f8aff37b723a3..6c3b5e5e0a22bf0b20f466832207005950fd1c25 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: sshd_config.5,v 1.303 2020/01/28 01:49:36 djm Exp $
-.Dd $Mdocdate: January 28 2020 $
+.\" $OpenBSD: sshd_config.5,v 1.304 2020/01/31 22:42:45 djm Exp $
+.Dd $Mdocdate: January 31 2020 $
 .Dt SSHD_CONFIG 5
 .Os
 .Sh NAME
@@ -801,7 +801,20 @@ during
 and use only the system-wide known hosts file
 .Pa /etc/ssh/known_hosts .
 The default is
-.Cm no .
+.Dq no .
+.It Cm Include
+Include the specified configuration file(s).
+Multiple path names may be specified and each pathname may contain
+.Xr glob 7
+wildcards.
+Files without absolute paths are assumed to be in
+.Pa /etc/ssh .
+A
+.Cm Include
+directive may appear inside a
+.Cm Match
+block
+to perform conditional inclusion.
 .It Cm IPQoS
 Specifies the IPv4 type-of-service or DSCP class for the connection.
 Accepted values are