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
This commit is contained in:
djm@openbsd.org 2020-01-31 22:42:45 +00:00 committed by Damien Miller
parent ba261a1dd3
commit c2bd7f74b0
5 changed files with 232 additions and 44 deletions

5
auth.c
View File

@ -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);

View File

@ -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)
{

View File

@ -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);

65
sshd.c
View File

@ -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);
}

View File

@ -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