diff --git a/usr.sbin/jail/config.c b/usr.sbin/jail/config.c --- a/usr.sbin/jail/config.c +++ b/usr.sbin/jail/config.c @@ -38,6 +38,7 @@ #include #include +#include #include #include #include @@ -51,15 +52,16 @@ unsigned flags; }; -extern FILE *yyin; -extern int yynerrs; - -extern int yyparse(void); +extern int yylex_init_extra(struct cflex *extra, void *scanner); +extern int yylex_destroy(void *scanner); +extern int yyparse(void *scanner); +extern int yyset_in(FILE *fp, void *scanner); struct cfjails cfjails = TAILQ_HEAD_INITIALIZER(cfjails); +static void parse_config(const char *fname, int is_stdin, + struct cfjail *injail); static void free_param(struct cfparams *pp, struct cfparam *p); -static void free_param_strings(struct cfparam *p); static const struct ipspec intparams[] = { [IP_ALLOW_DYING] = {"allow.dying", PF_INTERNAL | PF_BOOL}, @@ -124,10 +126,11 @@ }; /* - * Parse the jail configuration file. + * Parse a jail config file, either initially from main, or from + * an include within another config file. */ void -load_config(void) +load_config(const char *cfname) { struct cfjails wild; struct cfparams opp; @@ -138,16 +141,7 @@ char *ep; int did_self, jseq, pgen; - if (!strcmp(cfname, "-")) { - cfname = "STDIN"; - yyin = stdin; - } else { - yyin = fopen(cfname, "r"); - if (!yyin) - err(1, "%s", cfname); - } - if (yyparse() || yynerrs) - exit(1); + parse_config(cfname, !strcmp(cfname, "-"), NULL); /* Separate the wildcard jails out from the actual jails. */ jseq = 0; @@ -274,6 +268,64 @@ } } +void +include_config(const char *cfname, struct cflex *cflex, int from_stdin) +{ + static unsigned int depth; + glob_t g; + const char *slash; + char *fullpath = NULL; + + /* Basic sanity check for include loops. */ + if (++depth > 32) + errx(1, "maximum include depth exceeded"); + /* Base relative pathnames on the current config file. */ + if (!from_stdin && cfname[0] != '/' && + (slash = strrchr(cflex->cfname, '/')) != NULL) { + size_t dirlen = (slash - cflex->cfname) + 1; + + fullpath = emalloc(dirlen + strlen(cfname) + 1); + strncpy(fullpath, cflex->cfname, dirlen); + strcpy(fullpath + dirlen, cfname); + cfname = fullpath; + } + /* + * Check if the include statement had a filename glob. + * Globbing doesn't need to catch any files, but a non-glob + * file needs to exist (enforced by parse_config). + */ + if (glob(cfname, GLOB_NOCHECK, NULL, &g) != 0) + errx(1, "%s: filename glob failed", cfname); + if (g.gl_flags & GLOB_MAGCHAR) { + for (size_t gi = 0; gi < g.gl_matchc; gi++) + parse_config(g.gl_pathv[gi], 0, cflex->injail); + } else + parse_config(cfname, 0, cflex->injail); + if (fullpath) + free(fullpath); + --depth; +} + +static void +parse_config(const char *cfname, int is_stdin, struct cfjail *injail) +{ + struct cflex cflex = {.cfname = cfname, .injail = injail, .error = 0}; + void *scanner; + + yylex_init_extra(&cflex, &scanner); + if (is_stdin) { + yyset_in(stdin, scanner); + } else { + FILE *cfp = fopen(cfname, "r"); + if (!cfp) + err(1, "%s", cfname); + yyset_in(cfp, scanner); + } + if (yyparse(scanner) || cflex.error) + exit(1); + yylex_destroy(scanner); +} + /* * Create a new jail record. */ @@ -836,7 +888,7 @@ free(p); } -static void +void free_param_strings(struct cfparam *p) { struct cfstring *s; diff --git a/usr.sbin/jail/jail.c b/usr.sbin/jail/jail.c --- a/usr.sbin/jail/jail.c +++ b/usr.sbin/jail/jail.c @@ -56,7 +56,6 @@ int rev; }; -const char *cfname; int iflag; int note_remove; int verbose; @@ -137,6 +136,7 @@ struct stat st; FILE *jfp; struct cfjail *j; + const char *cfname; char *JidFile; size_t sysvallen; unsigned op, pi; @@ -287,7 +287,7 @@ } else if (op == JF_STOP || op == JF_SHOW) { /* Just print list of all configured non-wildcard jails */ if (op == JF_SHOW) { - load_config(); + load_config(cfname); show_jails(); exit(0); } @@ -300,7 +300,7 @@ usage(); if ((docf = !Rflag && (!strcmp(cfname, "-") || stat(cfname, &st) == 0))) - load_config(); + load_config(cfname); note_remove = docf || argc > 1 || wild_jail_name(argv[0]); } else if (argc > 1 || (argc == 1 && strchr(argv[0], '='))) { /* Single jail specified on the command line */ @@ -348,7 +348,7 @@ /* From the config file, perhaps with a specified jail */ if (Rflag || !docf) usage(); - load_config(); + load_config(cfname); } /* Find out which jails will be run. */ diff --git a/usr.sbin/jail/jail.conf.5 b/usr.sbin/jail/jail.conf.5 --- a/usr.sbin/jail/jail.conf.5 +++ b/usr.sbin/jail/jail.conf.5 @@ -24,7 +24,7 @@ .\" .\" $FreeBSD$ .\" -.Dd July 8, 2022 +.Dd May 20, 2023 .Dt JAIL.CONF 5 .Os .Sh NAME @@ -163,6 +163,14 @@ .Dq foo.bar and .Dq foo.bar.baz . +.Ss Includes +A line of the form +.Bd -literal -offset ident +.include "filename"; +.Ed +.Pp +will include another file in the configuration. The filename must be +a literal string, and cannot contain variable expansions. .Ss Comments The configuration file may contain comments in the common C, C++, and shell formats: @@ -212,6 +220,13 @@ mount.nodevfs; persist; // Required because there are no processes } + +# Include configurations from standard locations. +.include "/etc/jail.conf.d/*.conf"; +.include "/etc/jail.*.conf"; +.include "/usr/local/etc/jail[.]conf"; +.include "/usr/local/etc/jail.conf.d/*.conf"; +.include "/usr/local/etc/jail.*.conf"; .Ed .Sh SEE ALSO .Xr jail_set 2 , diff --git a/usr.sbin/jail/jaillex.l b/usr.sbin/jail/jaillex.l --- a/usr.sbin/jail/jaillex.l +++ b/usr.sbin/jail/jaillex.l @@ -38,19 +38,22 @@ #include "jailp.h" #include "y.tab.h" -extern int yynerrs; +#define YY_DECL int yylex(YYSTYPE *yylval, yyscan_t yyscanner) +#define YY_EXTRA_TYPE struct cflex* -static ssize_t text2lval(size_t triml, size_t trimr, int tovar); +extern YY_DECL; static int instr; -static int lineno = 1; -#define YY_DECL int yylex(void) +static ssize_t text2lval(size_t triml, size_t trimr, int tovar, + YYSTYPE *yylval, yyscan_t scanner); %} %option noyywrap %option noinput %option nounput +%option reentrant +%option yylineno %start _ DQ @@ -60,18 +63,8 @@ <_>[ \t]+ instr = 0; <_>#.* ; <_>\/\/.* ; -<_>\/\*([^*]|(\*+([^*\/])))*\*+\/ { - const char *s; - - for (s = yytext; s < yytext + yyleng; s++) - if (*s == '\n') - lineno++; - instr = 0; - } -<_>\n { - lineno++; - instr = 0; - } +<_>\/\*([^*]|(\*+([^*\/])))*\*+\/ instr = 0; +<_>\n instr = 0; /* Reserved tokens */ <_>\+= { @@ -87,13 +80,13 @@ <_,DQ>[A-Za-z0-9_!%&()\-.:<>?@\[\]^`|~]+ | <_,DQ>\\(.|\n|[0-7]{1,3}|x[0-9A-Fa-f]{1,2}) | <_,DQ>[$*+/\\] { - (void)text2lval(0, 0, 0); + (void)text2lval(0, 0, 0, yylval, yyscanner); return instr ? STR1 : (instr = 1, STR); } /* Single and double quoted strings */ <_>'([^\'\\]|\\(.|\n))*' { - (void)text2lval(1, 1, 0); + (void)text2lval(1, 1, 0, yylval, yyscanner); return instr ? STR1 : (instr = 1, STR); } <_>\"([^"\\]|\\(.|\n))*\" | @@ -102,7 +95,8 @@ ssize_t atvar; skip = yytext[0] == '"' ? 1 : 0; - atvar = text2lval(skip, 1, 1); + atvar = text2lval(skip, 1, 1, yylval, + yyscanner); if (atvar < 0) BEGIN _; else { @@ -120,32 +114,32 @@ /* Variables, single-word or bracketed */ <_,DQ>$[A-Za-z_][A-Za-z_0-9]* { - (void)text2lval(1, 0, 0); + (void)text2lval(1, 0, 0, yylval, yyscanner); return instr ? VAR1 : (instr = 1, VAR); } <_>$\{([^\n{}]|\\(.|\n))*\} | $\{([^\n\"{}]|\\(.|\n))*\} { - (void)text2lval(2, 1, 0); + (void)text2lval(2, 1, 0, yylval, yyscanner); return instr ? VAR1 : (instr = 1, VAR); } /* Partially formed bits worth complaining about */ <_>\/\*([^*]|(\*+([^*\/])))*\** { warnx("%s line %d: unterminated comment", - cfname, lineno); - yynerrs++; + yyextra->cfname, yylineno); + yyextra->error = 1; } <_>'([^\n'\\]|\\.)* | <_>\"([^\n\"\\]|\\.)* { warnx("%s line %d: unterminated string", - cfname, lineno); - yynerrs++; + yyextra->cfname, yylineno); + yyextra->error = 1; } <_>$\{([^\n{}]|\\.)* | $\{([^\n\"{}]|\\.)* { warnx("%s line %d: unterminated variable", - cfname, lineno); - yynerrs++; + yyextra->cfname, yylineno); + yyextra->error = 1; } /* A hack because "<0>" rules aren't allowed */ @@ -157,28 +151,19 @@ %% -void -yyerror(const char *s) -{ - if (!yytext) - warnx("%s line %d: %s", cfname, lineno, s); - else if (!yytext[0]) - warnx("%s: unexpected EOF", cfname); - else - warnx("%s line %d: %s: %s", cfname, lineno, yytext, s); -} - /* * Copy string from yytext to yylval, handling backslash escapes, * and optionally stopping at the beginning of a variable. */ static ssize_t -text2lval(size_t triml, size_t trimr, int tovar) +text2lval(size_t triml, size_t trimr, int tovar, YYSTYPE *yylval, + yyscan_t scanner) { char *d; const char *s, *se; - yylval.cs = d = emalloc(yyleng - trimr - triml + 1); + struct yyguts_t *yyg = scanner; + yylval->cs = d = emalloc(yyleng - trimr - triml + 1); se = yytext + (yyleng - trimr); for (s = yytext + triml; s < se; s++, d++) { if (*s != '\\') { @@ -186,8 +171,6 @@ *d = '\0'; return s - yytext; } - if (*s == '\n') - lineno++; *d = *s; continue; } @@ -209,8 +192,8 @@ case 'r': *d = '\r'; break; case 't': *d = '\t'; break; case 'v': *d = '\v'; break; - case '\n': d--; lineno++; break; default: *d = *s; break; + case '\n': d--; break; case 'x': *d = 0; if (s + 1 >= se) diff --git a/usr.sbin/jail/jailp.h b/usr.sbin/jail/jailp.h --- a/usr.sbin/jail/jailp.h +++ b/usr.sbin/jail/jailp.h @@ -45,15 +45,16 @@ #define DF_LIGHT 0x02 /* Implied dependency on jail existence only */ #define DF_NOFAIL 0x04 /* Don't propagate failed jails */ -#define PF_VAR 0x01 /* This is a variable, not a true parameter */ -#define PF_APPEND 0x02 /* Append to existing parameter list */ -#define PF_BAD 0x04 /* Unable to resolve parameter value */ -#define PF_INTERNAL 0x08 /* Internal parameter, not passed to kernel */ -#define PF_BOOL 0x10 /* Boolean parameter */ -#define PF_INT 0x20 /* Integer parameter */ -#define PF_CONV 0x40 /* Parameter duplicated in converted form */ -#define PF_REV 0x80 /* Run commands in reverse order on stopping */ -#define PF_IMMUTABLE 0x100 /* Immutable parameter */ +#define PF_VAR 0x0001 /* This is a variable, not a true parameter */ +#define PF_APPEND 0x0002 /* Append to existing parameter list */ +#define PF_BAD 0x0004 /* Unable to resolve parameter value */ +#define PF_INTERNAL 0x0008 /* Internal parameter, not passed to kernel */ +#define PF_BOOL 0x0010 /* Boolean parameter */ +#define PF_INT 0x0020 /* Integer parameter */ +#define PF_CONV 0x0040 /* Parameter duplicated in converted form */ +#define PF_REV 0x0080 /* Run commands in reverse order on stopping */ +#define PF_IMMUTABLE 0x0100 /* Immutable parameter */ +#define PF_NAMEVAL 0x0200 /* Parameter is in "name value" form */ #define JF_START 0x0001 /* -c */ #define JF_SET 0x0002 /* -m */ @@ -176,6 +177,7 @@ struct cfparams params; struct cfdepends dep[2]; struct cfjails *queue; + struct cfjail *cfparent; struct cfparam *intparams[IP_NPARAM]; struct cfstring *comstring; struct jailparam *jp; @@ -196,6 +198,12 @@ unsigned flags; }; +struct cflex { + const char *cfname; + struct cfjail *injail; + int error; +}; + extern void *emalloc(size_t); extern void *erealloc(void *, size_t); extern char *estrdup(const char *); @@ -208,7 +216,9 @@ extern int finish_command(struct cfjail *j); extern struct cfjail *next_proc(int nonblock); -extern void load_config(void); +extern void load_config(const char *cfname); +extern void include_config(const char *cfname, struct cflex *cflex, + int from_stdin); extern struct cfjail *add_jail(void); extern void add_param(struct cfjail *j, const struct cfparam *p, enum intparam ipnum, const char *value); @@ -220,6 +230,7 @@ extern int equalopts(const char *opt1, const char *opt2); extern int wild_jail_name(const char *wname); extern int wild_jail_match(const char *jname, const char *wname); +extern void free_param_strings(struct cfparam *p); extern void dep_setup(int docf); extern int dep_check(struct cfjail *j); @@ -231,13 +242,9 @@ extern void requeue(struct cfjail *j, struct cfjails *queue); extern void requeue_head(struct cfjail *j, struct cfjails *queue); -extern void yyerror(const char *); -extern int yylex(void); - extern struct cfjails cfjails; extern struct cfjails ready; extern struct cfjails depend; -extern const char *cfname; extern int iflag; extern int note_remove; extern int paralimit; diff --git a/usr.sbin/jail/jailparse.y b/usr.sbin/jail/jailparse.y --- a/usr.sbin/jail/jailparse.y +++ b/usr.sbin/jail/jailparse.y @@ -30,6 +30,8 @@ #include __FBSDID("$FreeBSD$"); +#include +#include #include #include @@ -52,12 +54,17 @@ %token PLEQ %token STR STR1 VAR VAR1 -%type jail +%type jail jail_name %type param_l %type

param name %type value %type string +%pure-parser + +%lex-param { void *scanner } +%parse-param { void *scanner } + %% /* @@ -70,35 +77,63 @@ ; | conf param ';' { - struct cfjail *j; - - j = TAILQ_LAST(&cfjails, cfjails); - if (!j || strcmp(j->name, "*")) { - j = add_jail(); - j->name = estrdup("*"); + if (!special_param($2, scanner)) { + struct cfjail *j = yyget_extra(scanner)->injail; + if (!j) + { + j = TAILQ_LAST(&cfjails, cfjails); + if (!j || strcmp(j->name, "*")) { + j = add_jail(); + j->name = estrdup("*"); + } + } + TAILQ_INSERT_TAIL(&j->params, $2, tq); } - TAILQ_INSERT_TAIL(&j->params, $2, tq); } | conf ';' -jail : STR '{' param_l '}' +/* + * Allow nested jail definitions by prepending the outer jail name to + * the inner one. + */ +jail : jail_name '{' param_l '}' { - $$ = add_jail(); - $$->name = $1; + $$ = $1; TAILQ_CONCAT(&$$->params, $3, tq); free($3); + yyget_extra(scanner)->injail = $1->cfparent; } ; +jail_name : STR + { + $$ = add_jail(); + $$->cfparent = yyget_extra(scanner)->injail; + yyget_extra(scanner)->injail = $$; + if ($$->cfparent != NULL) { + size_t parentlen = strlen($$->cfparent->name); + $$->name = emalloc(parentlen + strlen($1) + 2); + strcpy($$->name, $$->cfparent->name); + $$->name[parentlen++] = '.'; + strcpy($$->name + parentlen, $1); + free($1); + } else + $$->name = $1; + } + param_l : { $$ = emalloc(sizeof(struct cfparams)); TAILQ_INIT($$); } + | param_l jail + ; | param_l param ';' { - $$ = $1; - TAILQ_INSERT_TAIL($$, $2, tq); + if (!special_param($2, scanner)) { + $$ = $1; + TAILQ_INSERT_TAIL($$, $2, tq); + } } | param_l ';' ; @@ -128,10 +163,12 @@ { $$ = $1; TAILQ_CONCAT(&$$->val, $2, tq); + $$->flags |= PF_NAMEVAL; free($2); } | error { + yyget_extra(scanner)->error = 1; } ; @@ -216,3 +253,52 @@ ; %% + +extern int YYLEX_DECL(); + +extern struct cflex *yyget_extra(void *scanner); +extern FILE *yyget_in(void *scanner); +extern int yyget_lineno(void *scanner); +extern char *yyget_text(void *scanner); + +static void +YYERROR_DECL() +{ + if (!yyget_text(scanner)) + warnx("%s line %d: %s", + yyget_extra(scanner)->cfname, yyget_lineno(scanner), s); + else if (!yyget_text(scanner)[0]) + warnx("%s: unexpected EOF", + yyget_extra(scanner)->cfname); + else + warnx("%s line %d: %s: %s", + yyget_extra(scanner)->cfname, yyget_lineno(scanner), + yyget_text(scanner), s); +} + +/* Handle special parameters (i.e. the include directive). + * Return true if the parameter was specially handled. + */ +static int +special_param(struct cfparam *p, void *scanner) +{ + if ((p->flags & (PF_VAR | PF_NAMEVAL)) != PF_NAMEVAL + || strcmp(p->name, ".include")) + return 0; + struct cfstring *s; + TAILQ_FOREACH(s, &p->val, tq) { + if (STAILQ_EMPTY(&s->vars)) + include_config(s->s, yyget_extra(scanner), + yyget_in(scanner) == stdin); + else { + warnx("%s line %d: " + "variables not permitted in '.include' filename", + yyget_extra(scanner)->cfname, + yyget_lineno(scanner)); + yyget_extra(scanner)->error = 1; + } + } + free_param_strings(p); + free(p); + return 1; +}