diff options
-rw-r--r-- | auth.c | 5 | ||||
-rw-r--r-- | servconf.c | 167 | ||||
-rw-r--r-- | servconf.h | 20 | ||||
-rw-r--r-- | sshd.c | 65 | ||||
-rw-r--r-- | sshd_config.5 | 19 |
5 files changed, 232 insertions, 44 deletions
@@ -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); @@ -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) { @@ -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); @@ -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 |