summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--auth.c5
-rw-r--r--servconf.c167
-rw-r--r--servconf.h20
-rw-r--r--sshd.c65
-rw-r--r--sshd_config.519
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@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@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@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);
}
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