diff options
author | djm@openbsd.org <djm@openbsd.org> | 2020-01-31 22:42:45 +0000 |
---|---|---|
committer | Damien Miller <djm@mindrot.org> | 2020-02-01 10:20:24 +1100 |
commit | c2bd7f74b0e0f3a3ee9d19ac549e6ba89013abaf (patch) | |
tree | f90d36f2501a863ff0c3d1041d93a2ef827c54d1 /servconf.c | |
parent | ba261a1dd33266168ead4f8f40446dcece4d1600 (diff) |
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
Diffstat (limited to 'servconf.c')
-rw-r--r-- | servconf.c | 167 |
1 files changed, 148 insertions, 19 deletions
diff --git a/servconf.c b/servconf.c index 1e0718139..70f5f73f0 100644 --- a/servconf.c +++ b/servconf.c | |||
@@ -1,5 +1,5 @@ | |||
1 | 1 | ||
2 | /* $OpenBSD: servconf.c,v 1.359 2020/01/23 10:24:29 dtucker Exp $ */ | 2 | /* $OpenBSD: servconf.c,v 1.360 2020/01/31 22:42:45 djm Exp $ */ |
3 | /* | 3 | /* |
4 | * Copyright (c) 1995 Tatu Ylonen <ylo@cs.hut.fi>, Espoo, Finland | 4 | * Copyright (c) 1995 Tatu Ylonen <ylo@cs.hut.fi>, Espoo, Finland |
5 | * All rights reserved | 5 | * All rights reserved |
@@ -40,6 +40,11 @@ | |||
40 | #ifdef HAVE_UTIL_H | 40 | #ifdef HAVE_UTIL_H |
41 | #include <util.h> | 41 | #include <util.h> |
42 | #endif | 42 | #endif |
43 | #ifdef USE_SYSTEM_GLOB | ||
44 | # include <glob.h> | ||
45 | #else | ||
46 | # include "openbsd-compat/glob.h" | ||
47 | #endif | ||
43 | 48 | ||
44 | #include "openbsd-compat/sys-queue.h" | 49 | #include "openbsd-compat/sys-queue.h" |
45 | #include "xmalloc.h" | 50 | #include "xmalloc.h" |
@@ -69,6 +74,9 @@ static void add_listen_addr(ServerOptions *, const char *, | |||
69 | const char *, int); | 74 | const char *, int); |
70 | static void add_one_listen_addr(ServerOptions *, const char *, | 75 | static void add_one_listen_addr(ServerOptions *, const char *, |
71 | const char *, int); | 76 | const char *, int); |
77 | void parse_server_config_depth(ServerOptions *options, const char *filename, | ||
78 | struct sshbuf *conf, struct include_list *includes, | ||
79 | struct connection_info *connectinfo, int flags, int *activep, int depth); | ||
72 | 80 | ||
73 | /* Use of privilege separation or not */ | 81 | /* Use of privilege separation or not */ |
74 | extern int use_privsep; | 82 | extern int use_privsep; |
@@ -526,7 +534,7 @@ typedef enum { | |||
526 | sAcceptEnv, sSetEnv, sPermitTunnel, | 534 | sAcceptEnv, sSetEnv, sPermitTunnel, |
527 | sMatch, sPermitOpen, sPermitListen, sForceCommand, sChrootDirectory, | 535 | sMatch, sPermitOpen, sPermitListen, sForceCommand, sChrootDirectory, |
528 | sUsePrivilegeSeparation, sAllowAgentForwarding, | 536 | sUsePrivilegeSeparation, sAllowAgentForwarding, |
529 | sHostCertificate, | 537 | sHostCertificate, sInclude, |
530 | sRevokedKeys, sTrustedUserCAKeys, sAuthorizedPrincipalsFile, | 538 | sRevokedKeys, sTrustedUserCAKeys, sAuthorizedPrincipalsFile, |
531 | sAuthorizedPrincipalsCommand, sAuthorizedPrincipalsCommandUser, | 539 | sAuthorizedPrincipalsCommand, sAuthorizedPrincipalsCommandUser, |
532 | sKexAlgorithms, sCASignatureAlgorithms, sIPQoS, sVersionAddendum, | 540 | sKexAlgorithms, sCASignatureAlgorithms, sIPQoS, sVersionAddendum, |
@@ -538,9 +546,10 @@ typedef enum { | |||
538 | sDeprecated, sIgnore, sUnsupported | 546 | sDeprecated, sIgnore, sUnsupported |
539 | } ServerOpCodes; | 547 | } ServerOpCodes; |
540 | 548 | ||
541 | #define SSHCFG_GLOBAL 0x01 /* allowed in main section of sshd_config */ | 549 | #define SSHCFG_GLOBAL 0x01 /* allowed in main section of config */ |
542 | #define SSHCFG_MATCH 0x02 /* allowed inside a Match section */ | 550 | #define SSHCFG_MATCH 0x02 /* allowed inside a Match section */ |
543 | #define SSHCFG_ALL (SSHCFG_GLOBAL|SSHCFG_MATCH) | 551 | #define SSHCFG_ALL (SSHCFG_GLOBAL|SSHCFG_MATCH) |
552 | #define SSHCFG_NEVERMATCH 0x04 /* Match never matches; internal only */ | ||
544 | 553 | ||
545 | /* Textual representation of the tokens. */ | 554 | /* Textual representation of the tokens. */ |
546 | static struct { | 555 | static struct { |
@@ -669,6 +678,7 @@ static struct { | |||
669 | { "trustedusercakeys", sTrustedUserCAKeys, SSHCFG_ALL }, | 678 | { "trustedusercakeys", sTrustedUserCAKeys, SSHCFG_ALL }, |
670 | { "authorizedprincipalsfile", sAuthorizedPrincipalsFile, SSHCFG_ALL }, | 679 | { "authorizedprincipalsfile", sAuthorizedPrincipalsFile, SSHCFG_ALL }, |
671 | { "kexalgorithms", sKexAlgorithms, SSHCFG_GLOBAL }, | 680 | { "kexalgorithms", sKexAlgorithms, SSHCFG_GLOBAL }, |
681 | { "include", sInclude, SSHCFG_ALL }, | ||
672 | { "ipqos", sIPQoS, SSHCFG_ALL }, | 682 | { "ipqos", sIPQoS, SSHCFG_ALL }, |
673 | { "authorizedkeyscommand", sAuthorizedKeysCommand, SSHCFG_ALL }, | 683 | { "authorizedkeyscommand", sAuthorizedKeysCommand, SSHCFG_ALL }, |
674 | { "authorizedkeyscommanduser", sAuthorizedKeysCommandUser, SSHCFG_ALL }, | 684 | { "authorizedkeyscommanduser", sAuthorizedKeysCommandUser, SSHCFG_ALL }, |
@@ -1240,13 +1250,14 @@ static const struct multistate multistate_tcpfwd[] = { | |||
1240 | { NULL, -1 } | 1250 | { NULL, -1 } |
1241 | }; | 1251 | }; |
1242 | 1252 | ||
1243 | int | 1253 | static int |
1244 | process_server_config_line(ServerOptions *options, char *line, | 1254 | process_server_config_line_depth(ServerOptions *options, char *line, |
1245 | const char *filename, int linenum, int *activep, | 1255 | const char *filename, int linenum, int *activep, |
1246 | struct connection_info *connectinfo) | 1256 | struct connection_info *connectinfo, int inc_flags, int depth, |
1257 | struct include_list *includes) | ||
1247 | { | 1258 | { |
1248 | char ch, *cp, ***chararrayptr, **charptr, *arg, *arg2, *p; | 1259 | char ch, *cp, ***chararrayptr, **charptr, *arg, *arg2, *p; |
1249 | int cmdline = 0, *intptr, value, value2, n, port; | 1260 | int cmdline = 0, *intptr, value, value2, n, port, oactive, r, found; |
1250 | SyslogFacility *log_facility_ptr; | 1261 | SyslogFacility *log_facility_ptr; |
1251 | LogLevel *log_level_ptr; | 1262 | LogLevel *log_level_ptr; |
1252 | ServerOpCodes opcode; | 1263 | ServerOpCodes opcode; |
@@ -1255,6 +1266,8 @@ process_server_config_line(ServerOptions *options, char *line, | |||
1255 | long long val64; | 1266 | long long val64; |
1256 | const struct multistate *multistate_ptr; | 1267 | const struct multistate *multistate_ptr; |
1257 | const char *errstr; | 1268 | const char *errstr; |
1269 | struct include_item *item; | ||
1270 | glob_t gbuf; | ||
1258 | 1271 | ||
1259 | /* Strip trailing whitespace. Allow \f (form feed) at EOL only */ | 1272 | /* Strip trailing whitespace. Allow \f (form feed) at EOL only */ |
1260 | if ((len = strlen(line)) == 0) | 1273 | if ((len = strlen(line)) == 0) |
@@ -1281,7 +1294,7 @@ process_server_config_line(ServerOptions *options, char *line, | |||
1281 | cmdline = 1; | 1294 | cmdline = 1; |
1282 | activep = &cmdline; | 1295 | activep = &cmdline; |
1283 | } | 1296 | } |
1284 | if (*activep && opcode != sMatch) | 1297 | if (*activep && opcode != sMatch && opcode != sInclude) |
1285 | debug3("%s:%d setting %s %s", filename, linenum, arg, cp); | 1298 | debug3("%s:%d setting %s %s", filename, linenum, arg, cp); |
1286 | if (*activep == 0 && !(flags & SSHCFG_MATCH)) { | 1299 | if (*activep == 0 && !(flags & SSHCFG_MATCH)) { |
1287 | if (connectinfo == NULL) { | 1300 | if (connectinfo == NULL) { |
@@ -1954,6 +1967,96 @@ process_server_config_line(ServerOptions *options, char *line, | |||
1954 | *intptr = value; | 1967 | *intptr = value; |
1955 | break; | 1968 | break; |
1956 | 1969 | ||
1970 | case sInclude: | ||
1971 | if (cmdline) { | ||
1972 | fatal("Include directive not supported as a " | ||
1973 | "command-line option"); | ||
1974 | } | ||
1975 | value = 0; | ||
1976 | while ((arg2 = strdelim(&cp)) != NULL && *arg2 != '\0') { | ||
1977 | value++; | ||
1978 | found = 0; | ||
1979 | if (*arg2 != '/' && *arg2 != '~') { | ||
1980 | xasprintf(&arg, "%s/%s", SSHDIR, arg); | ||
1981 | } else | ||
1982 | arg = xstrdup(arg2); | ||
1983 | |||
1984 | /* | ||
1985 | * Don't let included files clobber the containing | ||
1986 | * file's Match state. | ||
1987 | */ | ||
1988 | oactive = *activep; | ||
1989 | |||
1990 | /* consult cache of include files */ | ||
1991 | TAILQ_FOREACH(item, includes, entry) { | ||
1992 | if (strcmp(item->selector, arg) != 0) | ||
1993 | continue; | ||
1994 | if (item->filename != NULL) { | ||
1995 | parse_server_config_depth(options, | ||
1996 | item->filename, item->contents, | ||
1997 | includes, connectinfo, | ||
1998 | (oactive ? 0 : SSHCFG_NEVERMATCH), | ||
1999 | activep, depth + 1); | ||
2000 | } | ||
2001 | found = 1; | ||
2002 | *activep = oactive; | ||
2003 | } | ||
2004 | if (found != 0) { | ||
2005 | free(arg); | ||
2006 | continue; | ||
2007 | } | ||
2008 | |||
2009 | /* requested glob was not in cache */ | ||
2010 | debug2("%s line %d: new include %s", | ||
2011 | filename, linenum, arg); | ||
2012 | if ((r = glob(arg, 0, NULL, &gbuf)) != 0) { | ||
2013 | if (r != GLOB_NOMATCH) { | ||
2014 | fatal("%s line %d: include \"%s\" " | ||
2015 | "glob failed", filename, | ||
2016 | linenum, arg); | ||
2017 | } | ||
2018 | /* | ||
2019 | * If no entry matched then record a | ||
2020 | * placeholder to skip later glob calls. | ||
2021 | */ | ||
2022 | debug2("%s line %d: no match for %s", | ||
2023 | filename, linenum, arg); | ||
2024 | item = xcalloc(1, sizeof(*item)); | ||
2025 | item->selector = strdup(arg); | ||
2026 | TAILQ_INSERT_TAIL(includes, | ||
2027 | item, entry); | ||
2028 | } | ||
2029 | if (gbuf.gl_pathc > INT_MAX) | ||
2030 | fatal("%s: too many glob results", __func__); | ||
2031 | for (n = 0; n < (int)gbuf.gl_pathc; n++) { | ||
2032 | debug2("%s line %d: including %s", | ||
2033 | filename, linenum, gbuf.gl_pathv[n]); | ||
2034 | item = xcalloc(1, sizeof(*item)); | ||
2035 | item->selector = strdup(arg); | ||
2036 | item->filename = strdup(gbuf.gl_pathv[n]); | ||
2037 | if ((item->contents = sshbuf_new()) == NULL) { | ||
2038 | fatal("%s: sshbuf_new failed", | ||
2039 | __func__); | ||
2040 | } | ||
2041 | load_server_config(item->filename, | ||
2042 | item->contents); | ||
2043 | parse_server_config_depth(options, | ||
2044 | item->filename, item->contents, | ||
2045 | includes, connectinfo, | ||
2046 | (oactive ? 0 : SSHCFG_NEVERMATCH), | ||
2047 | activep, depth + 1); | ||
2048 | *activep = oactive; | ||
2049 | TAILQ_INSERT_TAIL(includes, item, entry); | ||
2050 | } | ||
2051 | globfree(&gbuf); | ||
2052 | free(arg); | ||
2053 | } | ||
2054 | if (value == 0) { | ||
2055 | fatal("%s line %d: Include missing filename argument", | ||
2056 | filename, linenum); | ||
2057 | } | ||
2058 | break; | ||
2059 | |||
1957 | case sMatch: | 2060 | case sMatch: |
1958 | if (cmdline) | 2061 | if (cmdline) |
1959 | fatal("Match directive not supported as a command-line " | 2062 | fatal("Match directive not supported as a command-line " |
@@ -1962,7 +2065,7 @@ process_server_config_line(ServerOptions *options, char *line, | |||
1962 | if (value < 0) | 2065 | if (value < 0) |
1963 | fatal("%s line %d: Bad Match condition", filename, | 2066 | fatal("%s line %d: Bad Match condition", filename, |
1964 | linenum); | 2067 | linenum); |
1965 | *activep = value; | 2068 | *activep = (inc_flags & SSHCFG_NEVERMATCH) ? 0 : value; |
1966 | break; | 2069 | break; |
1967 | 2070 | ||
1968 | case sPermitListen: | 2071 | case sPermitListen: |
@@ -2256,6 +2359,16 @@ process_server_config_line(ServerOptions *options, char *line, | |||
2256 | return 0; | 2359 | return 0; |
2257 | } | 2360 | } |
2258 | 2361 | ||
2362 | int | ||
2363 | process_server_config_line(ServerOptions *options, char *line, | ||
2364 | const char *filename, int linenum, int *activep, | ||
2365 | struct connection_info *connectinfo, struct include_list *includes) | ||
2366 | { | ||
2367 | return process_server_config_line_depth(options, line, filename, | ||
2368 | linenum, activep, connectinfo, 0, 0, includes); | ||
2369 | } | ||
2370 | |||
2371 | |||
2259 | /* Reads the server configuration file. */ | 2372 | /* Reads the server configuration file. */ |
2260 | 2373 | ||
2261 | void | 2374 | void |
@@ -2294,12 +2407,13 @@ load_server_config(const char *filename, struct sshbuf *conf) | |||
2294 | 2407 | ||
2295 | void | 2408 | void |
2296 | parse_server_match_config(ServerOptions *options, | 2409 | parse_server_match_config(ServerOptions *options, |
2297 | struct connection_info *connectinfo) | 2410 | struct include_list *includes, struct connection_info *connectinfo) |
2298 | { | 2411 | { |
2299 | ServerOptions mo; | 2412 | ServerOptions mo; |
2300 | 2413 | ||
2301 | initialize_server_options(&mo); | 2414 | initialize_server_options(&mo); |
2302 | parse_server_config(&mo, "reprocess config", cfg, connectinfo); | 2415 | parse_server_config(&mo, "reprocess config", cfg, includes, |
2416 | connectinfo); | ||
2303 | copy_set_server_options(options, &mo, 0); | 2417 | copy_set_server_options(options, &mo, 0); |
2304 | } | 2418 | } |
2305 | 2419 | ||
@@ -2443,22 +2557,27 @@ copy_set_server_options(ServerOptions *dst, ServerOptions *src, int preauth) | |||
2443 | #undef M_CP_STROPT | 2557 | #undef M_CP_STROPT |
2444 | #undef M_CP_STRARRAYOPT | 2558 | #undef M_CP_STRARRAYOPT |
2445 | 2559 | ||
2560 | #define SERVCONF_MAX_DEPTH 16 | ||
2446 | void | 2561 | void |
2447 | parse_server_config(ServerOptions *options, const char *filename, | 2562 | parse_server_config_depth(ServerOptions *options, const char *filename, |
2448 | struct sshbuf *conf, struct connection_info *connectinfo) | 2563 | struct sshbuf *conf, struct include_list *includes, |
2564 | struct connection_info *connectinfo, int flags, int *activep, int depth) | ||
2449 | { | 2565 | { |
2450 | int active, linenum, bad_options = 0; | 2566 | int linenum, bad_options = 0; |
2451 | char *cp, *obuf, *cbuf; | 2567 | char *cp, *obuf, *cbuf; |
2452 | 2568 | ||
2569 | if (depth < 0 || depth > SERVCONF_MAX_DEPTH) | ||
2570 | fatal("Too many recursive configuration includes"); | ||
2571 | |||
2453 | debug2("%s: config %s len %zu", __func__, filename, sshbuf_len(conf)); | 2572 | debug2("%s: config %s len %zu", __func__, filename, sshbuf_len(conf)); |
2454 | 2573 | ||
2455 | if ((obuf = cbuf = sshbuf_dup_string(conf)) == NULL) | 2574 | if ((obuf = cbuf = sshbuf_dup_string(conf)) == NULL) |
2456 | fatal("%s: sshbuf_dup_string failed", __func__); | 2575 | fatal("%s: sshbuf_dup_string failed", __func__); |
2457 | active = connectinfo ? 0 : 1; | ||
2458 | linenum = 1; | 2576 | linenum = 1; |
2459 | while ((cp = strsep(&cbuf, "\n")) != NULL) { | 2577 | while ((cp = strsep(&cbuf, "\n")) != NULL) { |
2460 | if (process_server_config_line(options, cp, filename, | 2578 | if (process_server_config_line_depth(options, cp, |
2461 | linenum++, &active, connectinfo) != 0) | 2579 | filename, linenum++, activep, connectinfo, flags, |
2580 | depth, includes) != 0) | ||
2462 | bad_options++; | 2581 | bad_options++; |
2463 | } | 2582 | } |
2464 | free(obuf); | 2583 | free(obuf); |
@@ -2468,6 +2587,16 @@ parse_server_config(ServerOptions *options, const char *filename, | |||
2468 | process_queued_listen_addrs(options); | 2587 | process_queued_listen_addrs(options); |
2469 | } | 2588 | } |
2470 | 2589 | ||
2590 | void | ||
2591 | parse_server_config(ServerOptions *options, const char *filename, | ||
2592 | struct sshbuf *conf, struct include_list *includes, | ||
2593 | struct connection_info *connectinfo) | ||
2594 | { | ||
2595 | int active = connectinfo ? 0 : 1; | ||
2596 | parse_server_config_depth(options, filename, conf, includes, | ||
2597 | connectinfo, 0, &active, 0); | ||
2598 | } | ||
2599 | |||
2471 | static const char * | 2600 | static const char * |
2472 | fmt_multistate_int(int val, const struct multistate *m) | 2601 | fmt_multistate_int(int val, const struct multistate *m) |
2473 | { | 2602 | { |