[openssh-commits] [openssh] 02/05: upstream: Add a sshd_config "Include" directive to allow inclusion

git+noreply at mindrot.org git+noreply at mindrot.org
Sat Feb 1 10:22:40 AEDT 2020


This is an automated email from the git hooks/post-receive script.

djm pushed a commit to branch master
in repository openssh.

commit c2bd7f74b0e0f3a3ee9d19ac549e6ba89013abaf
Author: djm at openbsd.org <djm at openbsd.org>
Date:   Fri Jan 31 22:42:45 2020 +0000

    upstream: Add a sshd_config "Include" directive to allow inclusion
    
    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        |   5 +-
 servconf.c    | 167 +++++++++++++++++++++++++++++++++++++++++++++++++++-------
 servconf.h    |  20 +++++--
 sshd.c        |  65 +++++++++++++++++------
 sshd_config.5 |  19 +++++--
 5 files changed, 232 insertions(+), 44 deletions(-)

diff --git a/auth.c b/auth.c
index b42d7e76..086b8ebb 100644
--- 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);
 
diff --git a/servconf.c b/servconf.c
index 1e071813..70f5f73f 100644
--- a/servconf.c
+++ b/servconf.c
@@ -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 at cs.hut.fi>, Espoo, Finland
  *                    All rights reserved
@@ -40,6 +40,11 @@
 #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)
 {
diff --git a/servconf.h b/servconf.h
index 6fc1efb2..deda09d9 100644
--- a/servconf.h
+++ b/servconf.h
@@ -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 at 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 46fdf7ee..57fab042 100644
--- 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 at cs.hut.fi>
  * Copyright (c) 1995 Tatu Ylonen <ylo at 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);
 	}
 
diff --git a/sshd_config.5 b/sshd_config.5
index 1395a5e6..6c3b5e5e 100644
--- a/sshd_config.5
+++ b/sshd_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: 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

-- 
To stop receiving notification emails like this one, please contact
djm at mindrot.org.


More information about the openssh-commits mailing list