Blame SOURCES/openssh-8.0p1-sshd_include.patch

d7ff6e
diff -up openssh-8.0p1/auth.c.sshdinclude openssh-8.0p1/auth.c
d7ff6e
--- openssh-8.0p1/auth.c.sshdinclude	2021-10-20 15:18:49.740331098 +0200
d7ff6e
+++ openssh-8.0p1/auth.c	2021-10-20 15:19:41.324781344 +0200
d7ff6e
@@ -80,6 +80,7 @@
d7ff6e
 
d7ff6e
 /* import */
d7ff6e
 extern ServerOptions options;
d7ff6e
+extern struct include_list includes;
d7ff6e
 extern int use_privsep;
d7ff6e
 extern struct sshbuf *loginmsg;
d7ff6e
 extern struct passwd *privsep_pw;
d7ff6e
@@ -573,7 +574,7 @@ getpwnamallow(struct ssh *ssh, const cha
d7ff6e
 
d7ff6e
 	ci = get_connection_info(ssh, 1, options.use_dns);
d7ff6e
 	ci->user = user;
d7ff6e
-	parse_server_match_config(&options, ci);
d7ff6e
+	parse_server_match_config(&options, &includes, ci);
d7ff6e
 	log_change_level(options.log_level);
d7ff6e
 	process_permitopen(ssh, &options);
d7ff6e
 
d7ff6e
diff -up openssh-8.0p1/readconf.c.sshdinclude openssh-8.0p1/readconf.c
d7ff6e
--- openssh-8.0p1/readconf.c.sshdinclude	2021-10-20 15:21:43.541848103 +0200
d7ff6e
+++ openssh-8.0p1/readconf.c	2021-10-20 15:22:06.302046768 +0200
d7ff6e
@@ -711,7 +711,7 @@ match_cfg_line(Options *options, char **
d7ff6e
 static void
d7ff6e
 rm_env(Options *options, const char *arg, const char *filename, int linenum)
d7ff6e
 {
d7ff6e
-	int i, j;
d7ff6e
+	int i, j, onum_send_env = options->num_send_env;
d7ff6e
 	char *cp;
d7ff6e
 
d7ff6e
 	/* Remove an environment variable */
d7ff6e
@@ -734,6 +734,11 @@ rm_env(Options *options, const char *arg
d7ff6e
 		options->num_send_env--;
d7ff6e
 		/* NB. don't increment i */
d7ff6e
 	}
d7ff6e
+	if (onum_send_env != options->num_send_env) {
d7ff6e
+		options->send_env = xrecallocarray(options->send_env,
d7ff6e
+		    onum_send_env, options->num_send_env,
d7ff6e
+		    sizeof(*options->send_env));
d7ff6e
+	}
d7ff6e
 }
d7ff6e
 
d7ff6e
 /*
d7ff6e
diff -up openssh-8.0p1/regress/Makefile.sshdinclude openssh-8.0p1/regress/Makefile
d7ff6e
--- openssh-8.0p1/regress/Makefile.sshdinclude	2021-10-20 15:18:49.742331115 +0200
d7ff6e
+++ openssh-8.0p1/regress/Makefile	2021-10-20 15:19:41.324781344 +0200
d7ff6e
@@ -82,6 +82,7 @@ LTESTS= 	connect \
d7ff6e
 		principals-command \
d7ff6e
 		cert-file \
d7ff6e
 		cfginclude \
d7ff6e
+		servcfginclude \
d7ff6e
 		allow-deny-users \
d7ff6e
 		authinfo
d7ff6e
 
d7ff6e
@@ -118,7 +119,7 @@ CLEANFILES=	*.core actual agent-key.* au
d7ff6e
 		sftp-server.sh sftp.log ssh-log-wrapper.sh ssh.log \
d7ff6e
 		ssh_config ssh_config.* ssh_proxy ssh_proxy_bak \
d7ff6e
 		ssh_proxy_envpass sshd.log sshd_config sshd_config_minimal \
d7ff6e
-		sshd_config.orig sshd_proxy sshd_proxy.* sshd_proxy_bak \
d7ff6e
+		sshd_config.* sshd_proxy sshd_proxy.* sshd_proxy_bak \
d7ff6e
 		sshd_proxy_orig t10.out t10.out.pub t12.out t12.out.pub \
d7ff6e
 		t2.out t3.out t6.out1 t6.out2 t7.out t7.out.pub \
d7ff6e
 		t8.out t8.out.pub t9.out t9.out.pub testdata \
d7ff6e
diff -up openssh-8.0p1/regress/servcfginclude.sh.sshdinclude openssh-8.0p1/regress/servcfginclude.sh
d7ff6e
--- openssh-8.0p1/regress/servcfginclude.sh.sshdinclude	2021-10-20 15:18:49.744331132 +0200
d7ff6e
+++ openssh-8.0p1/regress/servcfginclude.sh	2021-10-20 15:22:06.303046777 +0200
d7ff6e
@@ -0,0 +1,188 @@
d7ff6e
+#	Placed in the Public Domain.
d7ff6e
+
d7ff6e
+tid="server config include"
d7ff6e
+
d7ff6e
+cat > $OBJ/sshd_config.i << _EOF
d7ff6e
+HostKey $OBJ/host.ssh-ed25519
d7ff6e
+Match host a
d7ff6e
+	Banner /aa
d7ff6e
+
d7ff6e
+Match host b
d7ff6e
+	Banner /bb
d7ff6e
+	Include $OBJ/sshd_config.i.*
d7ff6e
+
d7ff6e
+Match host c
d7ff6e
+	Include $OBJ/sshd_config.i.*
d7ff6e
+	Banner /cc
d7ff6e
+
d7ff6e
+Match host m
d7ff6e
+	Include $OBJ/sshd_config.i.*
d7ff6e
+
d7ff6e
+Match Host d
d7ff6e
+	Banner /dd
d7ff6e
+
d7ff6e
+Match Host e
d7ff6e
+	Banner /ee
d7ff6e
+	Include $OBJ/sshd_config.i.*
d7ff6e
+
d7ff6e
+Match Host f
d7ff6e
+	Include $OBJ/sshd_config.i.*
d7ff6e
+	Banner /ff
d7ff6e
+
d7ff6e
+Match Host n
d7ff6e
+	Include $OBJ/sshd_config.i.*
d7ff6e
+_EOF
d7ff6e
+
d7ff6e
+cat > $OBJ/sshd_config.i.0 << _EOF
d7ff6e
+Match host xxxxxx
d7ff6e
+_EOF
d7ff6e
+
d7ff6e
+cat > $OBJ/sshd_config.i.1 << _EOF
d7ff6e
+Match host a
d7ff6e
+	Banner /aaa
d7ff6e
+
d7ff6e
+Match host b
d7ff6e
+	Banner /bbb
d7ff6e
+
d7ff6e
+Match host c
d7ff6e
+	Banner /ccc
d7ff6e
+
d7ff6e
+Match Host d
d7ff6e
+	Banner /ddd
d7ff6e
+
d7ff6e
+Match Host e
d7ff6e
+	Banner /eee
d7ff6e
+
d7ff6e
+Match Host f
d7ff6e
+	Banner /fff
d7ff6e
+_EOF
d7ff6e
+
d7ff6e
+cat > $OBJ/sshd_config.i.2 << _EOF
d7ff6e
+Match host a
d7ff6e
+	Banner /aaaa
d7ff6e
+
d7ff6e
+Match host b
d7ff6e
+	Banner /bbbb
d7ff6e
+
d7ff6e
+Match host c
d7ff6e
+	Banner /cccc
d7ff6e
+
d7ff6e
+Match Host d
d7ff6e
+	Banner /dddd
d7ff6e
+
d7ff6e
+Match Host e
d7ff6e
+	Banner /eeee
d7ff6e
+
d7ff6e
+Match Host f
d7ff6e
+	Banner /ffff
d7ff6e
+
d7ff6e
+Match all
d7ff6e
+	Banner /xxxx
d7ff6e
+_EOF
d7ff6e
+
d7ff6e
+trial() {
d7ff6e
+	_host="$1"
d7ff6e
+	_exp="$2"
d7ff6e
+	_desc="$3"
d7ff6e
+	test -z "$_desc" && _desc="test match"
d7ff6e
+	trace "$_desc host=$_host expect=$_exp"
d7ff6e
+	${SUDO} ${REAL_SSHD} -f $OBJ/sshd_config.i -T \
d7ff6e
+	    -C "host=$_host,user=test,addr=127.0.0.1" > $OBJ/sshd_config.out ||
d7ff6e
+		fatal "ssh config parse failed: $_desc host=$_host expect=$_exp"
d7ff6e
+	_got=`grep -i '^banner ' $OBJ/sshd_config.out | awk '{print $2}'`
d7ff6e
+	if test "x$_exp" != "x$_got" ; then
d7ff6e
+		fail "$desc_ host $_host include fail: expected $_exp got $_got"
d7ff6e
+	fi
d7ff6e
+}
d7ff6e
+
d7ff6e
+trial a /aa
d7ff6e
+trial b /bb
d7ff6e
+trial c /ccc
d7ff6e
+trial d /dd
d7ff6e
+trial e /ee
d7ff6e
+trial f /fff
d7ff6e
+trial m /xxxx
d7ff6e
+trial n /xxxx
d7ff6e
+trial x none
d7ff6e
+
d7ff6e
+# Prepare an included config with an error.
d7ff6e
+
d7ff6e
+cat > $OBJ/sshd_config.i.3 << _EOF
d7ff6e
+Banner xxxx
d7ff6e
+	Junk
d7ff6e
+_EOF
d7ff6e
+
d7ff6e
+trace "disallow invalid config host=a"
d7ff6e
+${SUDO} ${REAL_SSHD} -f $OBJ/sshd_config.i \
d7ff6e
+    -C "host=a,user=test,addr=127.0.0.1" 2>/dev/null && \
d7ff6e
+	fail "sshd include allowed invalid config"
d7ff6e
+
d7ff6e
+trace "disallow invalid config host=x"
d7ff6e
+${SUDO} ${REAL_SSHD} -f $OBJ/sshd_config.i \
d7ff6e
+    -C "host=x,user=test,addr=127.0.0.1" 2>/dev/null && \
d7ff6e
+	fail "sshd include allowed invalid config"
d7ff6e
+
d7ff6e
+rm -f $OBJ/sshd_config.i.*
d7ff6e
+
d7ff6e
+# Ensure that a missing include is not fatal.
d7ff6e
+cat > $OBJ/sshd_config.i << _EOF
d7ff6e
+HostKey $OBJ/host.ssh-ed25519
d7ff6e
+Include $OBJ/sshd_config.i.*
d7ff6e
+Banner /aa
d7ff6e
+_EOF
d7ff6e
+
d7ff6e
+trial a /aa "missing include non-fatal"
d7ff6e
+
d7ff6e
+# Ensure that Match/Host in an included config does not affect parent.
d7ff6e
+cat > $OBJ/sshd_config.i.x << _EOF
d7ff6e
+Match host x
d7ff6e
+_EOF
d7ff6e
+
d7ff6e
+trial a /aa "included file does not affect match state"
d7ff6e
+
d7ff6e
+# Ensure the empty include directive is not accepted
d7ff6e
+cat > $OBJ/sshd_config.i.x << _EOF
d7ff6e
+Include
d7ff6e
+_EOF
d7ff6e
+
d7ff6e
+trace "disallow invalid with no argument"
d7ff6e
+${SUDO} ${REAL_SSHD} -f $OBJ/sshd_config.i.x -T \
d7ff6e
+    -C "host=x,user=test,addr=127.0.0.1" 2>/dev/null && \
d7ff6e
+	fail "sshd allowed Include with no argument"
d7ff6e
+
d7ff6e
+# Ensure the Include before any Match block works as expected (bug #3122)
d7ff6e
+cat > $OBJ/sshd_config.i << _EOF
d7ff6e
+Banner /xx
d7ff6e
+HostKey $OBJ/host.ssh-ed25519
d7ff6e
+Include $OBJ/sshd_config.i.2
d7ff6e
+Match host a
d7ff6e
+	Banner /aaaa
d7ff6e
+_EOF
d7ff6e
+cat > $OBJ/sshd_config.i.2 << _EOF
d7ff6e
+Match host a
d7ff6e
+	Banner /aa
d7ff6e
+_EOF
d7ff6e
+
d7ff6e
+trace "Include before match blocks"
d7ff6e
+trial a /aa "included file before match blocks is properly evaluated"
d7ff6e
+
d7ff6e
+# Port in included file is correctly interpretted (bug #3169)
d7ff6e
+cat > $OBJ/sshd_config.i << _EOF
d7ff6e
+Include $OBJ/sshd_config.i.2
d7ff6e
+Port 7722
d7ff6e
+_EOF
d7ff6e
+cat > $OBJ/sshd_config.i.2 << _EOF
d7ff6e
+HostKey $OBJ/host.ssh-ed25519
d7ff6e
+_EOF
d7ff6e
+
d7ff6e
+trace "Port after included files"
d7ff6e
+${SUDO} ${REAL_SSHD} -f $OBJ/sshd_config.i -T \
d7ff6e
+    -C "host=x,user=test,addr=127.0.0.1" > $OBJ/sshd_config.out || \
d7ff6e
+	fail "failed to parse Port after included files"
d7ff6e
+_port=`grep -i '^port ' $OBJ/sshd_config.out | awk '{print $2}'`
d7ff6e
+if test "x7722" != "x$_port" ; then
d7ff6e
+	fail "The Port in included file was intertepretted wrongly. Expected 7722, got $_port"
d7ff6e
+fi
d7ff6e
+
d7ff6e
+# cleanup
d7ff6e
+rm -f $OBJ/sshd_config.i $OBJ/sshd_config.i.* $OBJ/sshd_config.out
d7ff6e
diff -up openssh-8.0p1/regress/test-exec.sh.sshdinclude openssh-8.0p1/regress/test-exec.sh
d7ff6e
--- openssh-8.0p1/regress/test-exec.sh.sshdinclude	2021-10-20 15:18:49.746331150 +0200
d7ff6e
+++ openssh-8.0p1/regress/test-exec.sh	2021-10-20 15:19:41.324781344 +0200
d7ff6e
@@ -220,6 +220,7 @@ echo "exec ${SSH} -E${TEST_SSH_LOGFILE}
d7ff6e
 
d7ff6e
 chmod a+rx $OBJ/ssh-log-wrapper.sh
d7ff6e
 REAL_SSH="$SSH"
d7ff6e
+REAL_SSHD="$SSHD"
d7ff6e
 SSH="$SSHLOGWRAP"
d7ff6e
 
d7ff6e
 # Some test data.  We make a copy because some tests will overwrite it.
d7ff6e
diff -up openssh-8.0p1/servconf.c.sshdinclude openssh-8.0p1/servconf.c
d7ff6e
--- openssh-8.0p1/servconf.c.sshdinclude	2021-10-20 15:18:49.748331167 +0200
d7ff6e
+++ openssh-8.0p1/servconf.c	2021-10-20 15:22:06.303046777 +0200
d7ff6e
@@ -40,6 +40,11 @@
d7ff6e
 #ifdef HAVE_UTIL_H
d7ff6e
 #include <util.h>
d7ff6e
 #endif
d7ff6e
+#ifdef USE_SYSTEM_GLOB
d7ff6e
+# include <glob.h>
d7ff6e
+#else
d7ff6e
+# include "openbsd-compat/glob.h"
d7ff6e
+#endif
d7ff6e
 
d7ff6e
 #include "openbsd-compat/sys-queue.h"
d7ff6e
 #include "xmalloc.h"
d7ff6e
@@ -70,6 +75,9 @@ static void add_listen_addr(ServerOption
d7ff6e
     const char *, int);
d7ff6e
 static void add_one_listen_addr(ServerOptions *, const char *,
d7ff6e
     const char *, int);
d7ff6e
+static void parse_server_config_depth(ServerOptions *options,
d7ff6e
+    const char *filename, struct sshbuf *conf, struct include_list *includes,
d7ff6e
+    struct connection_info *connectinfo, int flags, int *activep, int depth);
d7ff6e
 
d7ff6e
 /* Use of privilege separation or not */
d7ff6e
 extern int use_privsep;
d7ff6e
@@ -528,7 +536,7 @@ typedef enum {
d7ff6e
 	sAcceptEnv, sSetEnv, sPermitTunnel,
d7ff6e
 	sMatch, sPermitOpen, sPermitListen, sForceCommand, sChrootDirectory,
d7ff6e
 	sUsePrivilegeSeparation, sAllowAgentForwarding,
d7ff6e
-	sHostCertificate,
d7ff6e
+	sHostCertificate, sInclude,
d7ff6e
 	sRevokedKeys, sTrustedUserCAKeys, sAuthorizedPrincipalsFile,
d7ff6e
 	sAuthorizedPrincipalsCommand, sAuthorizedPrincipalsCommandUser,
d7ff6e
 	sKexAlgorithms, sCASignatureAlgorithms, sIPQoS, sVersionAddendum,
d7ff6e
@@ -540,9 +548,11 @@ typedef enum {
d7ff6e
 	sDeprecated, sIgnore, sUnsupported
d7ff6e
 } ServerOpCodes;
d7ff6e
 
d7ff6e
-#define SSHCFG_GLOBAL	0x01	/* allowed in main section of sshd_config */
d7ff6e
-#define SSHCFG_MATCH	0x02	/* allowed inside a Match section */
d7ff6e
-#define SSHCFG_ALL	(SSHCFG_GLOBAL|SSHCFG_MATCH)
d7ff6e
+#define SSHCFG_GLOBAL		0x01	/* allowed in main section of config */
d7ff6e
+#define SSHCFG_MATCH		0x02	/* allowed inside a Match section */
d7ff6e
+#define SSHCFG_ALL		(SSHCFG_GLOBAL|SSHCFG_MATCH)
d7ff6e
+#define SSHCFG_NEVERMATCH	0x04  /* Match never matches; internal only */
d7ff6e
+#define SSHCFG_MATCH_ONLY	0x08  /* Match only in conditional blocks; internal only */
d7ff6e
 
d7ff6e
 /* Textual representation of the tokens. */
d7ff6e
 static struct {
d7ff6e
@@ -687,6 +697,7 @@ static struct {
d7ff6e
 	{ "trustedusercakeys", sTrustedUserCAKeys, SSHCFG_ALL },
d7ff6e
 	{ "authorizedprincipalsfile", sAuthorizedPrincipalsFile, SSHCFG_ALL },
d7ff6e
 	{ "kexalgorithms", sKexAlgorithms, SSHCFG_GLOBAL },
d7ff6e
+	{ "include", sInclude, SSHCFG_ALL },
d7ff6e
 	{ "ipqos", sIPQoS, SSHCFG_ALL },
d7ff6e
 	{ "authorizedkeyscommand", sAuthorizedKeysCommand, SSHCFG_ALL },
d7ff6e
 	{ "authorizedkeyscommanduser", sAuthorizedKeysCommandUser, SSHCFG_ALL },
d7ff6e
@@ -1259,13 +1270,14 @@ static const struct multistate multistat
d7ff6e
 	{ NULL, -1 }
d7ff6e
 };
d7ff6e
 
d7ff6e
-int
d7ff6e
-process_server_config_line(ServerOptions *options, char *line,
d7ff6e
+static int
d7ff6e
+process_server_config_line_depth(ServerOptions *options, char *line,
d7ff6e
     const char *filename, int linenum, int *activep,
d7ff6e
-    struct connection_info *connectinfo)
d7ff6e
+    struct connection_info *connectinfo, int *inc_flags, int depth,
d7ff6e
+    struct include_list *includes)
d7ff6e
 {
d7ff6e
 	char ch, *cp, ***chararrayptr, **charptr, *arg, *arg2, *p;
d7ff6e
-	int cmdline = 0, *intptr, value, value2, n, port;
d7ff6e
+	int cmdline = 0, *intptr, value, value2, n, port, oactive, r, found;
d7ff6e
 	SyslogFacility *log_facility_ptr;
d7ff6e
 	LogLevel *log_level_ptr;
d7ff6e
 	ServerOpCodes opcode;
d7ff6e
@@ -1274,6 +1286,8 @@ process_server_config_line(ServerOptions
d7ff6e
 	long long val64;
d7ff6e
 	const struct multistate *multistate_ptr;
d7ff6e
 	const char *errstr;
d7ff6e
+	struct include_item *item;
d7ff6e
+	glob_t gbuf;
d7ff6e
 
d7ff6e
 	/* Strip trailing whitespace. Allow \f (form feed) at EOL only */
d7ff6e
 	if ((len = strlen(line)) == 0)
d7ff6e
@@ -1300,7 +1314,7 @@ process_server_config_line(ServerOptions
d7ff6e
 		cmdline = 1;
d7ff6e
 		activep = &cmdline;
d7ff6e
 	}
d7ff6e
-	if (*activep && opcode != sMatch)
d7ff6e
+	if (*activep && opcode != sMatch && opcode != sInclude)
d7ff6e
 		debug3("%s:%d setting %s %s", filename, linenum, arg, cp);
d7ff6e
 	if (*activep == 0 && !(flags & SSHCFG_MATCH)) {
d7ff6e
 		if (connectinfo == NULL) {
d7ff6e
@@ -1980,15 +1994,112 @@ process_server_config_line(ServerOptions
d7ff6e
 			*intptr = value;
d7ff6e
 		break;
d7ff6e
 
d7ff6e
+	case sInclude:
d7ff6e
+		if (cmdline) {
d7ff6e
+			fatal("Include directive not supported as a "
d7ff6e
+			    "command-line option");
d7ff6e
+		}
d7ff6e
+		value = 0;
d7ff6e
+		while ((arg2 = strdelim(&cp)) != NULL && *arg2 != '\0') {
d7ff6e
+			value++;
d7ff6e
+			found = 0;
d7ff6e
+			if (*arg2 != '/' && *arg2 != '~') {
d7ff6e
+				xasprintf(&arg, "%s/%s", SSHDIR, arg2);
d7ff6e
+			} else
d7ff6e
+				arg = xstrdup(arg2);
d7ff6e
+
d7ff6e
+			/*
d7ff6e
+			 * Don't let included files clobber the containing
d7ff6e
+			 * file's Match state.
d7ff6e
+			 */
d7ff6e
+			oactive = *activep;
d7ff6e
+
d7ff6e
+			/* consult cache of include files */
d7ff6e
+			TAILQ_FOREACH(item, includes, entry) {
d7ff6e
+				if (strcmp(item->selector, arg) != 0)
d7ff6e
+					continue;
d7ff6e
+				if (item->filename != NULL) {
d7ff6e
+					parse_server_config_depth(options,
d7ff6e
+					    item->filename, item->contents,
d7ff6e
+					    includes, connectinfo,
d7ff6e
+					    (*inc_flags & SSHCFG_MATCH_ONLY
d7ff6e
+					        ? SSHCFG_MATCH_ONLY : (oactive
d7ff6e
+					            ? 0 : SSHCFG_NEVERMATCH)),
d7ff6e
+					    activep, depth + 1);
d7ff6e
+				}
d7ff6e
+				found = 1;
d7ff6e
+				*activep = oactive;
d7ff6e
+			}
d7ff6e
+			if (found != 0) {
d7ff6e
+				free(arg);
d7ff6e
+				continue;
d7ff6e
+			}
d7ff6e
+
d7ff6e
+			/* requested glob was not in cache */
d7ff6e
+			debug2("%s line %d: new include %s",
d7ff6e
+			    filename, linenum, arg);
d7ff6e
+			if ((r = glob(arg, 0, NULL, &gbuf)) != 0) {
d7ff6e
+				if (r != GLOB_NOMATCH) {
d7ff6e
+					fatal("%s line %d: include \"%s\" "
d7ff6e
+					    "glob failed", filename,
d7ff6e
+					    linenum, arg);
d7ff6e
+				}
d7ff6e
+				/*
d7ff6e
+				 * If no entry matched then record a
d7ff6e
+				 * placeholder to skip later glob calls.
d7ff6e
+				 */
d7ff6e
+				debug2("%s line %d: no match for %s",
d7ff6e
+				    filename, linenum, arg);
d7ff6e
+				item = xcalloc(1, sizeof(*item));
d7ff6e
+				item->selector = strdup(arg);
d7ff6e
+				TAILQ_INSERT_TAIL(includes,
d7ff6e
+				    item, entry);
d7ff6e
+			}
d7ff6e
+			if (gbuf.gl_pathc > INT_MAX)
d7ff6e
+				fatal("%s: too many glob results", __func__);
d7ff6e
+			for (n = 0; n < (int)gbuf.gl_pathc; n++) {
d7ff6e
+				debug2("%s line %d: including %s",
d7ff6e
+				    filename, linenum, gbuf.gl_pathv[n]);
d7ff6e
+				item = xcalloc(1, sizeof(*item));
d7ff6e
+				item->selector = strdup(arg);
d7ff6e
+				item->filename = strdup(gbuf.gl_pathv[n]);
d7ff6e
+				if ((item->contents = sshbuf_new()) == NULL) {
d7ff6e
+					fatal("%s: sshbuf_new failed",
d7ff6e
+					    __func__);
d7ff6e
+				}
d7ff6e
+				load_server_config(item->filename,
d7ff6e
+				    item->contents);
d7ff6e
+				parse_server_config_depth(options,
d7ff6e
+				    item->filename, item->contents,
d7ff6e
+				    includes, connectinfo,
d7ff6e
+				    (*inc_flags & SSHCFG_MATCH_ONLY
d7ff6e
+				        ? SSHCFG_MATCH_ONLY : (oactive
d7ff6e
+				            ? 0 : SSHCFG_NEVERMATCH)),
d7ff6e
+				    activep, depth + 1);
d7ff6e
+				*activep = oactive;
d7ff6e
+				TAILQ_INSERT_TAIL(includes, item, entry);
d7ff6e
+			}
d7ff6e
+			globfree(&gbuf);
d7ff6e
+			free(arg);
d7ff6e
+		}
d7ff6e
+		if (value == 0) {
d7ff6e
+			fatal("%s line %d: Include missing filename argument",
d7ff6e
+			    filename, linenum);
d7ff6e
+		}
d7ff6e
+		break;
d7ff6e
+
d7ff6e
 	case sMatch:
d7ff6e
 		if (cmdline)
d7ff6e
 			fatal("Match directive not supported as a command-line "
d7ff6e
 			   "option");
d7ff6e
-		value = match_cfg_line(&cp, linenum, connectinfo);
d7ff6e
+		value = match_cfg_line(&cp, linenum,
d7ff6e
+		    (*inc_flags & SSHCFG_NEVERMATCH ? NULL : connectinfo));
d7ff6e
 		if (value < 0)
d7ff6e
 			fatal("%s line %d: Bad Match condition", filename,
d7ff6e
 			    linenum);
d7ff6e
-		*activep = value;
d7ff6e
+		*activep = (*inc_flags & SSHCFG_NEVERMATCH) ? 0 : value;
d7ff6e
+		/* The MATCH_ONLY is applicable only until the first match block */
d7ff6e
+		*inc_flags &= ~SSHCFG_MATCH_ONLY;
d7ff6e
 		break;
d7ff6e
 
d7ff6e
 	case sKerberosUseKuserok:
d7ff6e
@@ -2275,6 +2386,18 @@ process_server_config_line(ServerOptions
d7ff6e
 	return 0;
d7ff6e
 }
d7ff6e
 
d7ff6e
+int
d7ff6e
+process_server_config_line(ServerOptions *options, char *line,
d7ff6e
+    const char *filename, int linenum, int *activep,
d7ff6e
+    struct connection_info *connectinfo, struct include_list *includes)
d7ff6e
+{
d7ff6e
+	int inc_flags = 0;
d7ff6e
+
d7ff6e
+	return process_server_config_line_depth(options, line, filename,
d7ff6e
+	    linenum, activep, connectinfo, &inc_flags, 0, includes);
d7ff6e
+}
d7ff6e
+
d7ff6e
+
d7ff6e
 /* Reads the server configuration file. */
d7ff6e
 
d7ff6e
 void
d7ff6e
@@ -2313,12 +2436,13 @@ load_server_config(const char *filename,
d7ff6e
 
d7ff6e
 void
d7ff6e
 parse_server_match_config(ServerOptions *options,
d7ff6e
-   struct connection_info *connectinfo)
d7ff6e
+   struct include_list *includes, struct connection_info *connectinfo)
d7ff6e
 {
d7ff6e
 	ServerOptions mo;
d7ff6e
 
d7ff6e
 	initialize_server_options(&mo);
d7ff6e
-	parse_server_config(&mo, "reprocess config", cfg, connectinfo);
d7ff6e
+	parse_server_config(&mo, "reprocess config", cfg, includes,
d7ff6e
+	    connectinfo);
d7ff6e
 	copy_set_server_options(options, &mo, 0);
d7ff6e
 }
d7ff6e
 
d7ff6e
@@ -2464,28 +2588,44 @@ copy_set_server_options(ServerOptions *d
d7ff6e
 #undef M_CP_STROPT
d7ff6e
 #undef M_CP_STRARRAYOPT
d7ff6e
 
d7ff6e
-void
d7ff6e
-parse_server_config(ServerOptions *options, const char *filename,
d7ff6e
-    struct sshbuf *conf, struct connection_info *connectinfo)
d7ff6e
+#define SERVCONF_MAX_DEPTH	16
d7ff6e
+static void
d7ff6e
+parse_server_config_depth(ServerOptions *options, const char *filename,
d7ff6e
+    struct sshbuf *conf, struct include_list *includes,
d7ff6e
+    struct connection_info *connectinfo, int flags, int *activep, int depth)
d7ff6e
 {
d7ff6e
-	int active, linenum, bad_options = 0;
d7ff6e
+	int linenum, bad_options = 0;
d7ff6e
 	char *cp, *obuf, *cbuf;
d7ff6e
 
d7ff6e
-	debug2("%s: config %s len %zu", __func__, filename, sshbuf_len(conf));
d7ff6e
+	if (depth < 0 || depth > SERVCONF_MAX_DEPTH)
d7ff6e
+		fatal("Too many recursive configuration includes");
d7ff6e
+
d7ff6e
+	debug2("%s: config %s len %zu%s", __func__, filename, sshbuf_len(conf),
d7ff6e
+	    (flags & SSHCFG_NEVERMATCH ? " [checking syntax only]" : ""));
d7ff6e
 
d7ff6e
 	if ((obuf = cbuf = sshbuf_dup_string(conf)) == NULL)
d7ff6e
 		fatal("%s: sshbuf_dup_string failed", __func__);
d7ff6e
-	active = connectinfo ? 0 : 1;
d7ff6e
 	linenum = 1;
d7ff6e
 	while ((cp = strsep(&cbuf, "\n")) != NULL) {
d7ff6e
-		if (process_server_config_line(options, cp, filename,
d7ff6e
-		    linenum++, &active, connectinfo) != 0)
d7ff6e
+		if (process_server_config_line_depth(options, cp,
d7ff6e
+		    filename, linenum++, activep, connectinfo, &flags,
d7ff6e
+		    depth, includes) != 0)
d7ff6e
 			bad_options++;
d7ff6e
 	}
d7ff6e
 	free(obuf);
d7ff6e
 	if (bad_options > 0)
d7ff6e
 		fatal("%s: terminating, %d bad configuration options",
d7ff6e
 		    filename, bad_options);
d7ff6e
+}
d7ff6e
+
d7ff6e
+void
d7ff6e
+parse_server_config(ServerOptions *options, const char *filename,
d7ff6e
+    struct sshbuf *conf, struct include_list *includes,
d7ff6e
+    struct connection_info *connectinfo)
d7ff6e
+{
d7ff6e
+	int active = connectinfo ? 0 : 1;
d7ff6e
+	parse_server_config_depth(options, filename, conf, includes,
d7ff6e
+	    connectinfo, (connectinfo ? SSHCFG_MATCH_ONLY : 0), &active, 0);
d7ff6e
 	process_queued_listen_addrs(options);
d7ff6e
 }
d7ff6e
 
d7ff6e
diff -up openssh-8.0p1/servconf.h.sshdinclude openssh-8.0p1/servconf.h
d7ff6e
--- openssh-8.0p1/servconf.h.sshdinclude	2021-10-20 15:18:49.750331185 +0200
d7ff6e
+++ openssh-8.0p1/servconf.h	2021-10-20 15:19:41.325781353 +0200
d7ff6e
@@ -16,6 +16,8 @@
d7ff6e
 #ifndef SERVCONF_H
d7ff6e
 #define SERVCONF_H
d7ff6e
 
d7ff6e
+#include <sys/queue.h>
d7ff6e
+
d7ff6e
 #define MAX_PORTS		256	/* Max # ports. */
d7ff6e
 
d7ff6e
 #define MAX_SUBSYSTEMS		256	/* Max # subsystems. */
d7ff6e
@@ -234,6 +236,15 @@ struct connection_info {
d7ff6e
 				 * unspecified */
d7ff6e
 };
d7ff6e
 
d7ff6e
+/* List of included files for re-exec from the parsed configuration */
d7ff6e
+struct include_item {
d7ff6e
+	char *selector;
d7ff6e
+	char *filename;
d7ff6e
+	struct sshbuf *contents;
d7ff6e
+	TAILQ_ENTRY(include_item) entry;
d7ff6e
+};
d7ff6e
+TAILQ_HEAD(include_list, include_item);
d7ff6e
+
d7ff6e
 
d7ff6e
 /*
d7ff6e
  * These are string config options that must be copied between the
d7ff6e
@@ -273,12 +284,13 @@ struct connection_info *get_connection_i
d7ff6e
 void	 initialize_server_options(ServerOptions *);
d7ff6e
 void	 fill_default_server_options(ServerOptions *);
d7ff6e
 int	 process_server_config_line(ServerOptions *, char *, const char *, int,
d7ff6e
-	     int *, struct connection_info *);
d7ff6e
+	     int *, struct connection_info *, struct include_list *includes);
d7ff6e
 void	 process_permitopen(struct ssh *ssh, ServerOptions *options);
d7ff6e
 void	 load_server_config(const char *, struct sshbuf *);
d7ff6e
 void	 parse_server_config(ServerOptions *, const char *, struct sshbuf *,
d7ff6e
-	     struct connection_info *);
d7ff6e
-void	 parse_server_match_config(ServerOptions *, struct connection_info *);
d7ff6e
+	     struct include_list *includes, struct connection_info *);
d7ff6e
+void	 parse_server_match_config(ServerOptions *,
d7ff6e
+	     struct include_list *includes, struct connection_info *);
d7ff6e
 int	 parse_server_match_testspec(struct connection_info *, char *);
d7ff6e
 int	 server_match_spec_complete(struct connection_info *);
d7ff6e
 void	 copy_set_server_options(ServerOptions *, ServerOptions *, int);
d7ff6e
diff -up openssh-8.0p1/sshd_config.5.sshdinclude openssh-8.0p1/sshd_config.5
d7ff6e
--- openssh-8.0p1/sshd_config.5.sshdinclude	2021-10-20 15:18:49.754331220 +0200
d7ff6e
+++ openssh-8.0p1/sshd_config.5	2021-10-20 15:19:41.325781353 +0200
d7ff6e
@@ -825,7 +825,20 @@ during
d7ff6e
 and use only the system-wide known hosts file
d7ff6e
 .Pa /etc/ssh/known_hosts .
d7ff6e
 The default is
d7ff6e
-.Cm no .
d7ff6e
+.Dq no .
d7ff6e
+.It Cm Include
d7ff6e
+Include the specified configuration file(s).
d7ff6e
+Multiple path names may be specified and each pathname may contain
d7ff6e
+.Xr glob 7
d7ff6e
+wildcards.
d7ff6e
+Files without absolute paths are assumed to be in
d7ff6e
+.Pa /etc/ssh .
d7ff6e
+A
d7ff6e
+.Cm Include
d7ff6e
+directive may appear inside a
d7ff6e
+.Cm Match
d7ff6e
+block
d7ff6e
+to perform conditional inclusion.
d7ff6e
 .It Cm IPQoS
d7ff6e
 Specifies the IPv4 type-of-service or DSCP class for the connection.
d7ff6e
 Accepted values are
d7ff6e
diff -up openssh-8.0p1/sshd.c.sshdinclude openssh-8.0p1/sshd.c
d7ff6e
--- openssh-8.0p1/sshd.c.sshdinclude	2021-10-20 15:18:49.752331202 +0200
d7ff6e
+++ openssh-8.0p1/sshd.c	2021-10-20 15:19:41.325781353 +0200
d7ff6e
@@ -257,6 +257,9 @@ struct sshauthopt *auth_opts = NULL;
d7ff6e
 /* sshd_config buffer */
d7ff6e
 struct sshbuf *cfg;
d7ff6e
 
d7ff6e
+/* Included files from the configuration file */
d7ff6e
+struct include_list includes = TAILQ_HEAD_INITIALIZER(includes);
d7ff6e
+
d7ff6e
 /* message to be displayed after login */
d7ff6e
 struct sshbuf *loginmsg;
d7ff6e
 
d7ff6e
@@ -927,30 +930,45 @@ usage(void)
d7ff6e
 static void
d7ff6e
 send_rexec_state(int fd, struct sshbuf *conf)
d7ff6e
 {
d7ff6e
-	struct sshbuf *m;
d7ff6e
+	struct sshbuf *m = NULL, *inc = NULL;
d7ff6e
+	struct include_item *item = NULL;
d7ff6e
 	int r;
d7ff6e
 
d7ff6e
 	debug3("%s: entering fd = %d config len %zu", __func__, fd,
d7ff6e
 	    sshbuf_len(conf));
d7ff6e
 
d7ff6e
+	if ((m = sshbuf_new()) == NULL || (inc = sshbuf_new()) == NULL)
d7ff6e
+		fatal("%s: sshbuf_new failed", __func__);
d7ff6e
+
d7ff6e
+	/* pack includes into a string */
d7ff6e
+	TAILQ_FOREACH(item, &includes, entry) {
d7ff6e
+		if ((r = sshbuf_put_cstring(inc, item->selector)) != 0 ||
d7ff6e
+		    (r = sshbuf_put_cstring(inc, item->filename)) != 0 ||
d7ff6e
+		    (r = sshbuf_put_stringb(inc, item->contents)) != 0)
d7ff6e
+			fatal("%s: buffer error: %s", __func__, ssh_err(r));
d7ff6e
+	}
d7ff6e
+
d7ff6e
 	/*
d7ff6e
 	 * Protocol from reexec master to child:
d7ff6e
 	 *	string	configuration
d7ff6e
-	 *	string rngseed		(only if OpenSSL is not self-seeded)
d7ff6e
+	 *	string	included_files[] {
d7ff6e
+	 *		string	selector
d7ff6e
+	 *		string	filename
d7ff6e
+	 *		string	contents
d7ff6e
+	 *	}
d7ff6e
+	 *	string	rng_seed (if required)
d7ff6e
 	 */
d7ff6e
-	if ((m = sshbuf_new()) == NULL)
d7ff6e
-		fatal("%s: sshbuf_new failed", __func__);
d7ff6e
-	if ((r = sshbuf_put_stringb(m, conf)) != 0)
d7ff6e
+	if ((r = sshbuf_put_stringb(m, conf)) != 0 ||
d7ff6e
+	    (r = sshbuf_put_stringb(m, inc)) != 0)
d7ff6e
 		fatal("%s: buffer error: %s", __func__, ssh_err(r));
d7ff6e
-
d7ff6e
 #if defined(WITH_OPENSSL) && !defined(OPENSSL_PRNG_ONLY)
d7ff6e
 	rexec_send_rng_seed(m);
d7ff6e
 #endif
d7ff6e
-
d7ff6e
 	if (ssh_msg_send(fd, 0, m) == -1)
d7ff6e
 		fatal("%s: ssh_msg_send failed", __func__);
d7ff6e
 
d7ff6e
 	sshbuf_free(m);
d7ff6e
+	sshbuf_free(inc);
d7ff6e
 
d7ff6e
 	debug3("%s: done", __func__);
d7ff6e
 }
d7ff6e
@@ -958,14 +976,15 @@ send_rexec_state(int fd, struct sshbuf *
d7ff6e
 static void
d7ff6e
 recv_rexec_state(int fd, struct sshbuf *conf)
d7ff6e
 {
d7ff6e
-	struct sshbuf *m;
d7ff6e
+	struct sshbuf *m, *inc;
d7ff6e
 	u_char *cp, ver;
d7ff6e
 	size_t len;
d7ff6e
 	int r;
d7ff6e
+	struct include_item *item;
d7ff6e
 
d7ff6e
 	debug3("%s: entering fd = %d", __func__, fd);
d7ff6e
 
d7ff6e
-	if ((m = sshbuf_new()) == NULL)
d7ff6e
+	if ((m = sshbuf_new()) == NULL || (inc = sshbuf_new()) == NULL)
d7ff6e
 		fatal("%s: sshbuf_new failed", __func__);
d7ff6e
 	if (ssh_msg_recv(fd, m) == -1)
d7ff6e
 		fatal("%s: ssh_msg_recv failed", __func__);
d7ff6e
@@ -973,14 +992,28 @@ recv_rexec_state(int fd, struct sshbuf *
d7ff6e
 		fatal("%s: buffer error: %s", __func__, ssh_err(r));
d7ff6e
 	if (ver != 0)
d7ff6e
 		fatal("%s: rexec version mismatch", __func__);
d7ff6e
-	if ((r = sshbuf_get_string(m, &cp, &len)) != 0)
d7ff6e
-		fatal("%s: buffer error: %s", __func__, ssh_err(r));
d7ff6e
-	if (conf != NULL && (r = sshbuf_put(conf, cp, len)))
d7ff6e
+	if ((r = sshbuf_get_string(m, &cp, &len)) != 0 ||
d7ff6e
+	    (r = sshbuf_get_stringb(m, inc)) != 0)
d7ff6e
 		fatal("%s: buffer error: %s", __func__, ssh_err(r));
d7ff6e
+
d7ff6e
 #if defined(WITH_OPENSSL) && !defined(OPENSSL_PRNG_ONLY)
d7ff6e
 	rexec_recv_rng_seed(m);
d7ff6e
 #endif
d7ff6e
 
d7ff6e
+	if (conf != NULL && (r = sshbuf_put(conf, cp, len)))
d7ff6e
+		fatal("%s: buffer error: %s", __func__, ssh_err(r));
d7ff6e
+
d7ff6e
+	while (sshbuf_len(inc) != 0) {
d7ff6e
+		item = xcalloc(1, sizeof(*item));
d7ff6e
+		if ((item->contents = sshbuf_new()) == NULL)
d7ff6e
+			fatal("%s: sshbuf_new failed", __func__);
d7ff6e
+		if ((r = sshbuf_get_cstring(inc, &item->selector, NULL)) != 0 ||
d7ff6e
+		    (r = sshbuf_get_cstring(inc, &item->filename, NULL)) != 0 ||
d7ff6e
+		    (r = sshbuf_get_stringb(inc, item->contents)) != 0)
d7ff6e
+			fatal("%s: buffer error: %s", __func__, ssh_err(r));
d7ff6e
+		TAILQ_INSERT_TAIL(&includes, item, entry);
d7ff6e
+	}
d7ff6e
+
d7ff6e
 	free(cp);
d7ff6e
 	sshbuf_free(m);
d7ff6e
 
d7ff6e
@@ -1661,7 +1694,7 @@ main(int ac, char **av)
d7ff6e
 		case 'o':
d7ff6e
 			line = xstrdup(optarg);
d7ff6e
 			if (process_server_config_line(&options, line,
d7ff6e
-			    "command-line", 0, NULL, NULL) != 0)
d7ff6e
+			    "command-line", 0, NULL, NULL, &includes) != 0)
d7ff6e
 				exit(1);
d7ff6e
 			free(line);
d7ff6e
 			break;
d7ff6e
@@ -1692,7 +1725,7 @@ main(int ac, char **av)
d7ff6e
 	    SYSLOG_LEVEL_INFO : options.log_level,
d7ff6e
 	    options.log_facility == SYSLOG_FACILITY_NOT_SET ?
d7ff6e
 	    SYSLOG_FACILITY_AUTH : options.log_facility,
d7ff6e
-	    log_stderr || !inetd_flag);
d7ff6e
+	    log_stderr || !inetd_flag || debug_flag);
d7ff6e
 
d7ff6e
 	/*
d7ff6e
 	 * Unset KRB5CCNAME, otherwise the user's session may inherit it from
d7ff6e
@@ -1725,12 +1758,11 @@ main(int ac, char **av)
d7ff6e
 			 */
d7ff6e
 			(void)atomicio(vwrite, startup_pipe, "\0", 1);
d7ff6e
 		}
d7ff6e
-	}
d7ff6e
-	else if (strcasecmp(config_file_name, "none") != 0)
d7ff6e
+	} else if (strcasecmp(config_file_name, "none") != 0)
d7ff6e
 		load_server_config(config_file_name, cfg);
d7ff6e
 
d7ff6e
 	parse_server_config(&options, rexeced_flag ? "rexec" : config_file_name,
d7ff6e
-	    cfg, NULL);
d7ff6e
+	    cfg, &includes, NULL);
d7ff6e
 
58886c
 	/* 'UsePAM no' is not supported in RHEL */
d7ff6e
 	if (! options.use_pam)
d7ff6e
@@ -1946,7 +1978,7 @@ main(int ac, char **av)
d7ff6e
 		if (connection_info == NULL)
d7ff6e
 			connection_info = get_connection_info(ssh, 0, 0);
d7ff6e
 		connection_info->test = 1;
d7ff6e
-		parse_server_match_config(&options, connection_info);
d7ff6e
+		parse_server_match_config(&options, &includes, connection_info);
d7ff6e
 		dump_config(&options);
d7ff6e
 	}
d7ff6e