diff --git a/usr.sbin/jail/config.c b/usr.sbin/jail/config.c index 52e1cbf05c28..35f40c123042 100644 --- a/usr.sbin/jail/config.c +++ b/usr.sbin/jail/config.c @@ -1,862 +1,910 @@ /*- * SPDX-License-Identifier: BSD-2-Clause * * Copyright (c) 2011 James Gritton * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF * SUCH DAMAGE. */ #include __FBSDID("$FreeBSD$"); #include #include #include #include #include #include #include +#include #include #include #include #include #include #include "jailp.h" +#define MAX_INCLUDE_DEPTH 32 + struct ipspec { const char *name; unsigned flags; }; 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); 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}, [IP_COMMAND] = {"command", PF_INTERNAL}, [IP_DEPEND] = {"depend", PF_INTERNAL}, [IP_EXEC_CLEAN] = {"exec.clean", PF_INTERNAL | PF_BOOL}, [IP_EXEC_CONSOLELOG] = {"exec.consolelog", PF_INTERNAL}, [IP_EXEC_FIB] = {"exec.fib", PF_INTERNAL | PF_INT}, [IP_EXEC_JAIL_USER] = {"exec.jail_user", PF_INTERNAL}, [IP_EXEC_POSTSTART] = {"exec.poststart", PF_INTERNAL}, [IP_EXEC_POSTSTOP] = {"exec.poststop", PF_INTERNAL}, [IP_EXEC_PREPARE] = {"exec.prepare", PF_INTERNAL}, [IP_EXEC_PRESTART] = {"exec.prestart", PF_INTERNAL}, [IP_EXEC_PRESTOP] = {"exec.prestop", PF_INTERNAL}, [IP_EXEC_RELEASE] = {"exec.release", PF_INTERNAL}, [IP_EXEC_CREATED] = {"exec.created", PF_INTERNAL}, [IP_EXEC_START] = {"exec.start", PF_INTERNAL}, [IP_EXEC_STOP] = {"exec.stop", PF_INTERNAL}, [IP_EXEC_SYSTEM_JAIL_USER]= {"exec.system_jail_user", PF_INTERNAL | PF_BOOL}, [IP_EXEC_SYSTEM_USER] = {"exec.system_user", PF_INTERNAL}, [IP_EXEC_TIMEOUT] = {"exec.timeout", PF_INTERNAL | PF_INT}, #if defined(INET) || defined(INET6) [IP_INTERFACE] = {"interface", PF_INTERNAL}, [IP_IP_HOSTNAME] = {"ip_hostname", PF_INTERNAL | PF_BOOL}, #endif [IP_MOUNT] = {"mount", PF_INTERNAL | PF_REV}, [IP_MOUNT_DEVFS] = {"mount.devfs", PF_INTERNAL | PF_BOOL}, [IP_MOUNT_FDESCFS] = {"mount.fdescfs", PF_INTERNAL | PF_BOOL}, [IP_MOUNT_PROCFS] = {"mount.procfs", PF_INTERNAL | PF_BOOL}, [IP_MOUNT_FSTAB] = {"mount.fstab", PF_INTERNAL}, [IP_STOP_TIMEOUT] = {"stop.timeout", PF_INTERNAL | PF_INT}, [IP_VNET_INTERFACE] = {"vnet.interface", PF_INTERNAL}, #ifdef INET [IP__IP4_IFADDR] = {"ip4.addr", PF_INTERNAL | PF_CONV | PF_REV}, #endif #ifdef INET6 [IP__IP6_IFADDR] = {"ip6.addr", PF_INTERNAL | PF_CONV | PF_REV}, #endif [IP__MOUNT_FROM_FSTAB] = {"mount.fstab", PF_INTERNAL | PF_CONV | PF_REV}, [IP__OP] = {NULL, PF_CONV}, [KP_ALLOW_CHFLAGS] = {"allow.chflags", 0}, [KP_ALLOW_MOUNT] = {"allow.mount", 0}, [KP_ALLOW_RAW_SOCKETS] = {"allow.raw_sockets", 0}, [KP_ALLOW_SET_HOSTNAME]= {"allow.set_hostname", 0}, [KP_ALLOW_SOCKET_AF] = {"allow.socket_af", 0}, [KP_ALLOW_SYSVIPC] = {"allow.sysvipc", 0}, [KP_DEVFS_RULESET] = {"devfs_ruleset", 0}, [KP_HOST_HOSTNAME] = {"host.hostname", 0}, #ifdef INET [KP_IP4_ADDR] = {"ip4.addr", 0}, #endif #ifdef INET6 [KP_IP6_ADDR] = {"ip6.addr", 0}, #endif [KP_JID] = {"jid", PF_IMMUTABLE}, [KP_NAME] = {"name", PF_IMMUTABLE}, [KP_PATH] = {"path", 0}, [KP_PERSIST] = {"persist", 0}, [KP_SECURELEVEL] = {"securelevel", 0}, [KP_VNET] = {"vnet", 0}, }; /* * Parse the jail configuration file. */ void load_config(const char *cfname) { struct cfjails wild; struct cfparams opp; struct cfjail *j, *tj, *wj; struct cfparam *p, *vp, *tp; struct cfstring *s, *vs, *ns; struct cfvar *v, *vv; - struct cflex cflex; char *ep; - void *scanner; int did_self, jseq, pgen; - cflex.cfname = cfname; - cflex.error = 0; - yylex_init_extra(&cflex, &scanner); - if (!strcmp(cfname, "-")) { - cflex.cfname = "STDIN"; - yyset_in(stdin, scanner); - } else { - FILE *yfp = fopen(cfname, "r"); - if (!yfp) - err(1, "%s", cfname); - yyset_in(yfp, scanner); - } - if (yyparse(scanner) || cflex.error) - exit(1); - yylex_destroy(scanner); + parse_config(cfname, !strcmp(cfname, "-")); /* Separate the wildcard jails out from the actual jails. */ jseq = 0; TAILQ_INIT(&wild); TAILQ_FOREACH_SAFE(j, &cfjails, tq, tj) { j->seq = ++jseq; if (wild_jail_name(j->name)) requeue(j, &wild); } TAILQ_FOREACH(j, &cfjails, tq) { /* Set aside the jail's parameters. */ TAILQ_INIT(&opp); TAILQ_CONCAT(&opp, &j->params, tq); /* * The jail name implies its "name" or "jid" parameter, * though they may also be explicitly set later on. */ add_param(j, NULL, strtol(j->name, &ep, 10) && !*ep ? KP_JID : KP_NAME, j->name); /* * Collect parameters for the jail, global parameters/variables, * and any matching wildcard jails. */ did_self = 0; TAILQ_FOREACH(wj, &wild, tq) { if (j->seq < wj->seq && !did_self) { TAILQ_FOREACH(p, &opp, tq) add_param(j, p, 0, NULL); did_self = 1; } if (wild_jail_match(j->name, wj->name)) TAILQ_FOREACH(p, &wj->params, tq) add_param(j, p, 0, NULL); } if (!did_self) TAILQ_FOREACH(p, &opp, tq) add_param(j, p, 0, NULL); /* Resolve any variable substitutions. */ pgen = 0; TAILQ_FOREACH(p, &j->params, tq) { p->gen = ++pgen; find_vars: TAILQ_FOREACH(s, &p->val, tq) { while ((v = STAILQ_FIRST(&s->vars))) { TAILQ_FOREACH(vp, &j->params, tq) if (!strcmp(vp->name, v->name)) break; if (!vp || TAILQ_EMPTY(&vp->val)) { jail_warnx(j, "%s: variable \"%s\" not found", p->name, v->name); bad_var: j->flags |= JF_FAILED; TAILQ_FOREACH(vp, &j->params, tq) if (vp->gen == pgen) vp->flags |= PF_BAD; goto free_var; } if (vp->flags & PF_BAD) goto bad_var; if (vp->gen == pgen) { jail_warnx(j, "%s: variable loop", v->name); goto bad_var; } TAILQ_FOREACH(vs, &vp->val, tq) if (!STAILQ_EMPTY(&vs->vars)) { vp->gen = pgen; TAILQ_REMOVE(&j->params, vp, tq); TAILQ_INSERT_BEFORE(p, vp, tq); p = vp; goto find_vars; } vs = TAILQ_FIRST(&vp->val); if (TAILQ_NEXT(vs, tq) != NULL && (s->s[0] != '\0' || STAILQ_NEXT(v, tq))) { jail_warnx(j, "%s: array cannot be " "substituted inline", p->name); goto bad_var; } s->s = erealloc(s->s, s->len + vs->len + 1); memmove(s->s + v->pos + vs->len, s->s + v->pos, s->len - v->pos + 1); memcpy(s->s + v->pos, vs->s, vs->len); vv = v; while ((vv = STAILQ_NEXT(vv, tq))) vv->pos += vs->len; s->len += vs->len; while ((vs = TAILQ_NEXT(vs, tq))) { ns = emalloc(sizeof(struct cfstring)); ns->s = estrdup(vs->s); ns->len = vs->len; STAILQ_INIT(&ns->vars); TAILQ_INSERT_AFTER(&p->val, s, ns, tq); s = ns; } free_var: free(v->name); STAILQ_REMOVE_HEAD(&s->vars, tq); free(v); } } } /* Free the jail's original parameter list and any variables. */ while ((p = TAILQ_FIRST(&opp))) free_param(&opp, p); TAILQ_FOREACH_SAFE(p, &j->params, tq, tp) if (p->flags & PF_VAR) free_param(&j->params, p); } while ((wj = TAILQ_FIRST(&wild))) { free(wj->name); while ((p = TAILQ_FIRST(&wj->params))) free_param(&wj->params, p); TAILQ_REMOVE(&wild, wj, tq); } } +void +include_config(void *scanner, const char *cfname) +{ + static unsigned int depth; + glob_t g = {0}; + const char *slash; + char *fullpath = NULL; + + /* Simple sanity check for include loops. */ + if (++depth > MAX_INCLUDE_DEPTH) + errx(1, "maximum include depth exceeded"); + /* Base relative pathnames on the current config file. */ + if (yyget_in(scanner) != stdin && cfname[0] != '/') { + const char *outer_cfname = yyget_extra(scanner)->cfname; + if ((slash = strrchr(outer_cfname, '/')) != NULL) { + size_t dirlen = (slash - outer_cfname) + 1; + + fullpath = emalloc(dirlen + strlen(cfname) + 1); + strncpy(fullpath, outer_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); + } else + parse_config(cfname, 0); + if (fullpath) + free(fullpath); + --depth; +} + +static void +parse_config(const char *cfname, int is_stdin) +{ + struct cflex cflex = {.cfname = cfname, .error = 0}; + void *scanner; + + yylex_init_extra(&cflex, &scanner); + if (is_stdin) { + cflex.cfname = "STDIN"; + yyset_in(stdin, scanner); + } else { + FILE *yfp = fopen(cfname, "r"); + if (!yfp) + err(1, "%s", cfname); + yyset_in(yfp, scanner); + } + if (yyparse(scanner) || cflex.error) + exit(1); + yylex_destroy(scanner); +} + /* * Create a new jail record. */ struct cfjail * add_jail(void) { struct cfjail *j; j = emalloc(sizeof(struct cfjail)); memset(j, 0, sizeof(struct cfjail)); TAILQ_INIT(&j->params); STAILQ_INIT(&j->dep[DEP_FROM]); STAILQ_INIT(&j->dep[DEP_TO]); j->queue = &cfjails; TAILQ_INSERT_TAIL(&cfjails, j, tq); return j; } /* * Add a parameter to a jail. */ void add_param(struct cfjail *j, const struct cfparam *p, enum intparam ipnum, const char *value) { struct cfstrings nss; struct cfparam *dp, *np; struct cfstring *s, *ns; struct cfvar *v, *nv; const char *name; char *cs, *tname; unsigned flags; if (j == NULL) { /* Create a single anonymous jail if one doesn't yet exist. */ j = TAILQ_LAST(&cfjails, cfjails); if (j == NULL) j = add_jail(); } TAILQ_INIT(&nss); if (p != NULL) { name = p->name; flags = p->flags; /* * Make a copy of the parameter's string list, * which may be freed if it's overridden later. */ TAILQ_FOREACH(s, &p->val, tq) { ns = emalloc(sizeof(struct cfstring)); ns->s = estrdup(s->s); ns->len = s->len; STAILQ_INIT(&ns->vars); STAILQ_FOREACH(v, &s->vars, tq) { nv = emalloc(sizeof(struct cfvar)); nv->name = strdup(v->name); nv->pos = v->pos; STAILQ_INSERT_TAIL(&ns->vars, nv, tq); } TAILQ_INSERT_TAIL(&nss, ns, tq); } } else { flags = PF_APPEND; if (ipnum != IP__NULL) { name = intparams[ipnum].name; flags |= intparams[ipnum].flags; } else if ((cs = strchr(value, '='))) { tname = alloca(cs - value + 1); strlcpy(tname, value, cs - value + 1); name = tname; value = cs + 1; } else { name = value; value = NULL; } if (value != NULL) { ns = emalloc(sizeof(struct cfstring)); ns->s = estrdup(value); ns->len = strlen(value); STAILQ_INIT(&ns->vars); TAILQ_INSERT_TAIL(&nss, ns, tq); } } /* See if this parameter has already been added. */ if (ipnum != IP__NULL) dp = j->intparams[ipnum]; else TAILQ_FOREACH(dp, &j->params, tq) if (!(dp->flags & PF_CONV) && equalopts(dp->name, name)) break; if (dp != NULL) { /* Found it - append or replace. */ if ((flags ^ dp->flags) & PF_VAR) { jail_warnx(j, "variable \"$%s\" cannot have the same " "name as a parameter.", name); j->flags |= JF_FAILED; return; } if (dp->flags & PF_IMMUTABLE) { jail_warnx(j, "cannot redefine parameter \"%s\".", dp->name); j->flags |= JF_FAILED; return; } if (strcmp(dp->name, name)) { free(dp->name); dp->name = estrdup(name); } if (!(flags & PF_APPEND) || TAILQ_EMPTY(&nss)) free_param_strings(dp); TAILQ_CONCAT(&dp->val, &nss, tq); dp->flags |= flags; } else { /* Not found - add it. */ np = emalloc(sizeof(struct cfparam)); np->name = estrdup(name); TAILQ_INIT(&np->val); TAILQ_CONCAT(&np->val, &nss, tq); np->flags = flags; np->gen = 0; TAILQ_INSERT_TAIL(&j->params, np, tq); if (ipnum != IP__NULL) j->intparams[ipnum] = np; else for (ipnum = IP__NULL + 1; ipnum < IP_NPARAM; ipnum++) if (!(intparams[ipnum].flags & PF_CONV) && equalopts(name, intparams[ipnum].name)) { if (flags & PF_VAR) { jail_warnx(j, "variable \"$%s\" " "cannot have the same " "name as a parameter.", name); j->flags |= JF_FAILED; return; } j->intparams[ipnum] = np; np->flags |= intparams[ipnum].flags; break; } } } /* * Return if a boolean parameter exists and is true. */ int bool_param(const struct cfparam *p) { const char *cs; if (p == NULL) return 0; cs = strrchr(p->name, '.'); return !strncmp(cs ? cs + 1 : p->name, "no", 2) ^ (TAILQ_EMPTY(&p->val) || !strcasecmp(TAILQ_LAST(&p->val, cfstrings)->s, "true") || (strtol(TAILQ_LAST(&p->val, cfstrings)->s, NULL, 10))); } /* * Set an integer if a parameter if it exists. */ int int_param(const struct cfparam *p, int *ip) { if (p == NULL || TAILQ_EMPTY(&p->val)) return 0; *ip = strtol(TAILQ_LAST(&p->val, cfstrings)->s, NULL, 10); return 1; } /* * Return the string value of a scalar parameter if it exists. */ const char * string_param(const struct cfparam *p) { return (p && !TAILQ_EMPTY(&p->val) ? TAILQ_LAST(&p->val, cfstrings)->s : NULL); } /* * Check syntax and values of internal parameters. Set some internal * parameters based on the values of others. */ int check_intparams(struct cfjail *j) { struct cfparam *p; struct cfstring *s; FILE *f; const char *val; char *cs, *ep, *ln; size_t lnlen; int error; #if defined(INET) || defined(INET6) struct addrinfo hints; struct addrinfo *ai0, *ai; const char *hostname; int gicode, defif; #endif #ifdef INET struct in_addr addr4; int ip4ok; char avalue4[INET_ADDRSTRLEN]; #endif #ifdef INET6 struct in6_addr addr6; int ip6ok; char avalue6[INET6_ADDRSTRLEN]; #endif error = 0; /* Check format of boolan and integer values. */ TAILQ_FOREACH(p, &j->params, tq) { if (!TAILQ_EMPTY(&p->val) && (p->flags & (PF_BOOL | PF_INT))) { val = TAILQ_LAST(&p->val, cfstrings)->s; if (p->flags & PF_BOOL) { if (strcasecmp(val, "false") && strcasecmp(val, "true") && ((void)strtol(val, &ep, 10), *ep)) { jail_warnx(j, "%s: unknown boolean value \"%s\"", p->name, val); error = -1; } } else { (void)strtol(val, &ep, 10); if (ep == val || *ep) { jail_warnx(j, "%s: non-integer value \"%s\"", p->name, val); error = -1; } } } } #if defined(INET) || defined(INET6) /* * The ip_hostname parameter looks up the hostname, and adds parameters * for any IP addresses it finds. */ if (((j->flags & JF_OP_MASK) != JF_STOP || j->intparams[IP_INTERFACE] != NULL) && bool_param(j->intparams[IP_IP_HOSTNAME]) && (hostname = string_param(j->intparams[KP_HOST_HOSTNAME]))) { j->intparams[IP_IP_HOSTNAME] = NULL; /* * Silently ignore unsupported address families from * DNS lookups. */ #ifdef INET ip4ok = feature_present("inet"); #endif #ifdef INET6 ip6ok = feature_present("inet6"); #endif if ( #if defined(INET) && defined(INET6) ip4ok || ip6ok #elif defined(INET) ip4ok #elif defined(INET6) ip6ok #endif ) { /* Look up the hostname (or get the address) */ memset(&hints, 0, sizeof(hints)); hints.ai_socktype = SOCK_STREAM; hints.ai_family = #if defined(INET) && defined(INET6) ip4ok ? (ip6ok ? PF_UNSPEC : PF_INET) : PF_INET6; #elif defined(INET) PF_INET; #elif defined(INET6) PF_INET6; #endif gicode = getaddrinfo(hostname, NULL, &hints, &ai0); if (gicode != 0) { jail_warnx(j, "host.hostname %s: %s", hostname, gai_strerror(gicode)); error = -1; } else { /* * Convert the addresses to ASCII so jailparam * can convert them back. Errors are not * expected here. */ for (ai = ai0; ai; ai = ai->ai_next) switch (ai->ai_family) { #ifdef INET case AF_INET: memcpy(&addr4, &((struct sockaddr_in *) (void *)ai->ai_addr)-> sin_addr, sizeof(addr4)); if (inet_ntop(AF_INET, &addr4, avalue4, INET_ADDRSTRLEN) == NULL) err(1, "inet_ntop"); add_param(j, NULL, KP_IP4_ADDR, avalue4); break; #endif #ifdef INET6 case AF_INET6: memcpy(&addr6, &((struct sockaddr_in6 *) (void *)ai->ai_addr)-> sin6_addr, sizeof(addr6)); if (inet_ntop(AF_INET6, &addr6, avalue6, INET6_ADDRSTRLEN) == NULL) err(1, "inet_ntop"); add_param(j, NULL, KP_IP6_ADDR, avalue6); break; #endif } freeaddrinfo(ai0); } } } /* * IP addresses may include an interface to set that address on, * a netmask/suffix for that address and options for ifconfig. * These are copied to an internal command parameter and then stripped * so they won't be passed on to jailparam_set. */ defif = string_param(j->intparams[IP_INTERFACE]) != NULL; #ifdef INET if (j->intparams[KP_IP4_ADDR] != NULL) { TAILQ_FOREACH(s, &j->intparams[KP_IP4_ADDR]->val, tq) { cs = strchr(s->s, '|'); if (cs || defif) add_param(j, NULL, IP__IP4_IFADDR, s->s); if (cs) { s->len -= cs + 1 - s->s; memmove(s->s, cs + 1, s->len + 1); } if ((cs = strchr(s->s, '/')) != NULL) { *cs = '\0'; s->len = cs - s->s; } if ((cs = strchr(s->s, ' ')) != NULL) { *cs = '\0'; s->len = cs - s->s; } } } #endif #ifdef INET6 if (j->intparams[KP_IP6_ADDR] != NULL) { TAILQ_FOREACH(s, &j->intparams[KP_IP6_ADDR]->val, tq) { cs = strchr(s->s, '|'); if (cs || defif) add_param(j, NULL, IP__IP6_IFADDR, s->s); if (cs) { s->len -= cs + 1 - s->s; memmove(s->s, cs + 1, s->len + 1); } if ((cs = strchr(s->s, '/')) != NULL) { *cs = '\0'; s->len = cs - s->s; } if ((cs = strchr(s->s, ' ')) != NULL) { *cs = '\0'; s->len = cs - s->s; } } } #endif #endif /* * Read mount.fstab file(s), and treat each line as its own mount * parameter. */ if (j->intparams[IP_MOUNT_FSTAB] != NULL) { TAILQ_FOREACH(s, &j->intparams[IP_MOUNT_FSTAB]->val, tq) { if (s->len == 0) continue; f = fopen(s->s, "r"); if (f == NULL) { jail_warnx(j, "mount.fstab: %s: %s", s->s, strerror(errno)); error = -1; continue; } while ((ln = fgetln(f, &lnlen))) { if ((cs = memchr(ln, '#', lnlen - 1))) lnlen = cs - ln + 1; if (ln[lnlen - 1] == '\n' || ln[lnlen - 1] == '#') ln[lnlen - 1] = '\0'; else { cs = alloca(lnlen + 1); strlcpy(cs, ln, lnlen + 1); ln = cs; } add_param(j, NULL, IP__MOUNT_FROM_FSTAB, ln); } fclose(f); } } if (error) failed(j); return error; } /* * Import parameters into libjail's binary jailparam format. */ int import_params(struct cfjail *j) { struct cfparam *p; struct cfstring *s, *ts; struct jailparam *jp; char *value, *cs; size_t vallen; int error; error = 0; j->njp = 0; TAILQ_FOREACH(p, &j->params, tq) if (!(p->flags & PF_INTERNAL)) j->njp++; j->jp = jp = emalloc(j->njp * sizeof(struct jailparam)); TAILQ_FOREACH(p, &j->params, tq) { if (p->flags & PF_INTERNAL) continue; if (jailparam_init(jp, p->name) < 0) { error = -1; jail_warnx(j, "%s", jail_errmsg); jp++; continue; } if (TAILQ_EMPTY(&p->val)) value = NULL; else if (!jp->jp_elemlen || !TAILQ_NEXT(TAILQ_FIRST(&p->val), tq)) { /* * Scalar parameters silently discard multiple (array) * values, keeping only the last value added. This * lets values added from the command line append to * arrays wthout pre-checking the type. */ value = TAILQ_LAST(&p->val, cfstrings)->s; } else { /* * Convert arrays into comma-separated strings, which * jailparam_import will then convert back into arrays. */ vallen = 0; TAILQ_FOREACH(s, &p->val, tq) vallen += s->len + 1; value = alloca(vallen); cs = value; TAILQ_FOREACH_SAFE(s, &p->val, tq, ts) { memcpy(cs, s->s, s->len); cs += s->len + 1; cs[-1] = ','; } value[vallen - 1] = '\0'; } if (jailparam_import(jp, value) < 0) { error = -1; jail_warnx(j, "%s", jail_errmsg); } jp++; } if (error) { jailparam_free(j->jp, j->njp); free(j->jp); j->jp = NULL; failed(j); } return error; } /* * Check if options are equal (with or without the "no" prefix). */ int equalopts(const char *opt1, const char *opt2) { char *p; /* "opt" vs. "opt" or "noopt" vs. "noopt" */ if (strcmp(opt1, opt2) == 0) return (1); /* "noopt" vs. "opt" */ if (strncmp(opt1, "no", 2) == 0 && strcmp(opt1 + 2, opt2) == 0) return (1); /* "opt" vs. "noopt" */ if (strncmp(opt2, "no", 2) == 0 && strcmp(opt1, opt2 + 2) == 0) return (1); while ((p = strchr(opt1, '.')) != NULL && !strncmp(opt1, opt2, ++p - opt1)) { opt2 += p - opt1; opt1 = p; /* "foo.noopt" vs. "foo.opt" */ if (strncmp(opt1, "no", 2) == 0 && strcmp(opt1 + 2, opt2) == 0) return (1); /* "foo.opt" vs. "foo.noopt" */ if (strncmp(opt2, "no", 2) == 0 && strcmp(opt1, opt2 + 2) == 0) return (1); } return (0); } /* * See if a jail name matches a wildcard. */ int wild_jail_match(const char *jname, const char *wname) { const char *jc, *jd, *wc, *wd; /* * A non-final "*" component in the wild name matches a single jail * component, and a final "*" matches one or more jail components. */ for (jc = jname, wc = wname; (jd = strchr(jc, '.')) && (wd = strchr(wc, '.')); jc = jd + 1, wc = wd + 1) if (strncmp(jc, wc, jd - jc + 1) && strncmp(wc, "*.", 2)) return 0; return (!strcmp(jc, wc) || !strcmp(wc, "*")); } /* * Return if a jail name is a wildcard. */ int wild_jail_name(const char *wname) { const char *wc; for (wc = strchr(wname, '*'); wc; wc = strchr(wc + 1, '*')) if ((wc == wname || wc[-1] == '.') && (wc[1] == '\0' || wc[1] == '.')) return 1; return 0; } /* * Free a parameter record and all its strings and variables. */ static void free_param(struct cfparams *pp, struct cfparam *p) { free(p->name); free_param_strings(p); TAILQ_REMOVE(pp, p, tq); free(p); } -static void +void free_param_strings(struct cfparam *p) { struct cfstring *s; struct cfvar *v; while ((s = TAILQ_FIRST(&p->val))) { free(s->s); while ((v = STAILQ_FIRST(&s->vars))) { free(v->name); STAILQ_REMOVE_HEAD(&s->vars, tq); free(v); } TAILQ_REMOVE(&p->val, s, tq); free(s); } } diff --git a/usr.sbin/jail/jail.conf.5 b/usr.sbin/jail/jail.conf.5 index 67277e6d78ce..a6990c081a3b 100644 --- a/usr.sbin/jail/jail.conf.5 +++ b/usr.sbin/jail/jail.conf.5 @@ -1,239 +1,254 @@ .\" Copyright (c) 2012 James Gritton .\" All rights reserved. .\" .\" Redistribution and use in source and binary forms, with or without .\" modification, are permitted provided that the following conditions .\" are met: .\" 1. Redistributions of source code must retain the above copyright .\" notice, this list of conditions and the following disclaimer. .\" 2. Redistributions in binary form must reproduce the above copyright .\" notice, this list of conditions and the following disclaimer in the .\" documentation and/or other materials provided with the distribution. .\" .\" THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND .\" ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE .\" IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE .\" ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE .\" FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL .\" DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS .\" OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) .\" HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT .\" LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY .\" OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF .\" SUCH DAMAGE. .\" .\" $FreeBSD$ .\" -.Dd July 8, 2022 +.Dd Jun 3, 2023 .Dt JAIL.CONF 5 .Os .Sh NAME .Nm jail.conf .Nd configuration file for .Xr jail 8 .Sh DESCRIPTION A .Xr jail 8 configuration file consists of one or more jail definitions statements, and parameter or variable statements within those jail definitions. A jail definition statement looks something like a C compound statement. A parameter statement looks like a C assignment, including a terminating semicolon. .Pp The general syntax of a jail definition is: .Bd -literal -offset indent jailname { parameter = "value"; parameter = "value"; ... } .Ed .Pp Each jail is required to have a .Va name at the front of its definition. This is used by .Xr jail 8 to specify a jail on the command line and report the jail status, and is also passed to the kernel when creating the jail. .Ss Parameters A jail is defined by a set of named parameters, specified inside the jail definition. .Em See .Xr jail 8 .Em for a list of jail parameters passed to the kernel, as well as internal parameters used when creating and removing jails. .Pp A typical parameter has a name and a value. Some parameters are boolean and may be specified with values of .Dq true or .Dq false , or as valueless shortcuts, with a .Dq no prefix indicating a false value. For example, these are equivalent: .Bd -literal -offset indent allow.mount = "false"; allow.nomount; .Ed .Pp Other parameters may have more than one value. A comma-separated list of values may be set in a single statement, or an existing parameter list may be appended to using .Dq += : .Bd -literal -offset indent ip4.addr = 10.1.1.1, 10.1.1.2, 10.1.1.3; ip4.addr = 10.1.1.1; ip4.addr += 10.1.1.2; ip4.addr += 10.1.1.3; .Ed .Pp Note the .Va name parameter is implicitly set to the name in the jail definition. .Ss String format Parameter values, including jail names, can be single tokens or quoted strings. A token is any sequence of characters that aren't considered special in the syntax of the configuration file (such as a semicolon or whitespace). If a value contains anything more than letters, numbers, dots, dashes and underscores, it is advisable to put quote marks around that value. Either single or double quotes may be used. .Pp Special characters may be quoted by preceding them with a backslash. Common C-style backslash character codes are also supported, including control characters and octal or hex ASCII codes. A backslash at the end of a line will ignore the subsequent newline and continue the string at the start of the next line. .Ss Variables A string may use shell-style variable substitution. A parameter or variable name preceded by a dollar sign, and possibly enclosed in braces, will be replaced with the value of that parameter or variable. For example, a jail's path may be defined in terms of its name or hostname: .Bd -literal -offset indent path = "/var/jail/$name"; path = "/var/jail/${host.hostname}"; .Ed .Pp Variable substitution occurs in unquoted tokens or in double-quoted strings, but not in single-quote strings. .Pp A variable is defined in the same way a parameter is, except that the variable name is preceded with a dollar sign: .Bd -literal -offset indent $parentdir = "/var/jail"; path = "$parentdir/$name"; .Ed .Pp The difference between parameters and variables is that variables are only used for substitution, while parameters are used both for substitution and for passing to the kernel. .Ss Wildcards A jail definition with a name of .Dq * is used to define wildcard parameters. Every defined jail will contain both the parameters from its own definition statement, as well as any parameters in a wildcard definition. .Pp Variable substitution is done on a per-jail basis, even when that substitution is for a parameter defined in a wildcard section. This is useful for wildcard parameters based on e.g. a jail's name. .Pp Later definitions in the configuration file supersede earlier ones, so a wildcard section placed before (above) a jail definition defines parameters that could be changed on a per-jail basis. Or a wildcard section placed after (below) all jails would contain parameters that always apply to every jail. Multiple wildcard statements are allowed, and wildcard parameters may also be specified outside of a jail definition statement. .Pp If hierarchical jails are defined, a partial-matching wildcard definition may be specified. For example, a definition with a name of .Dq foo.* would apply to jails with names like .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: .Bd -literal -offset indent /* This is a C style comment. * It may span multiple lines. */ // This is a C++ style comment. # This is a shell style comment. .Ed .Pp Comments are legal wherever whitespace is allowed, i.e. anywhere except in the middle of a string or a token. .Sh FILES .Bl -tag -width "indent" -compact .It Pa /etc/jail.conf .It Pa /etc/jail.*.conf .It Pa /etc/jail.conf.d/*.conf .It Pa /usr/share/examples/jails/ .El .Sh EXAMPLES .Bd -literal # Typical static defaults: # Use the rc scripts to start and stop jails. Mount jail's /dev. exec.start = "/bin/sh /etc/rc"; exec.stop = "/bin/sh /etc/rc.shutdown jail"; exec.clean; mount.devfs; # Dynamic wildcard parameter: # Base the path off the jail name. path = "/var/jail/$name"; # A typical jail. foo { host.hostname = "foo.com"; ip4.addr = 10.1.1.1, 10.1.1.2, 10.1.1.3; } # This jail overrides the defaults defined above. bar { exec.start = ''; exec.stop = ''; path = /; mount.nodevfs; persist; // Required because there are no processes } + +# Include configurations from standard locations. +\[char46]include "/etc/jail.conf.d/*.conf"; +\[char46]include "/etc/jail.*.conf"; +\[char46]include "/usr/local/etc/jail[.]conf"; +\[char46]include "/usr/local/etc/jail.conf.d/*.conf"; +\[char46]include "/usr/local/etc/jail.*.conf"; .Ed .Sh SEE ALSO .Xr jail_set 2 , .Xr rc.conf 5 , .Xr jail 8 , .Xr jls 8 .Sh HISTORY The .Xr jail 8 utility appeared in .Fx 4.0 . The .Nm file was added in .Fx 9.1 . .Sh AUTHORS .An -nosplit The jail feature was written by .An Poul-Henning Kamp for R&D Associates who contributed it to .Fx . .Pp .An James Gritton added the extensible jail parameters and configuration file. diff --git a/usr.sbin/jail/jailp.h b/usr.sbin/jail/jailp.h index 6ea4e3ec09a3..cd97063507c8 100644 --- a/usr.sbin/jail/jailp.h +++ b/usr.sbin/jail/jailp.h @@ -1,246 +1,255 @@ /*- * SPDX-License-Identifier: BSD-2-Clause * * Copyright (c) 2011 James Gritton. * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF * SUCH DAMAGE. * * $FreeBSD$ */ #include #include #include #include #include #include +#include #define CONF_FILE "/etc/jail.conf" #define DEP_FROM 0 #define DEP_TO 1 #define DF_SEEN 0x01 /* Dependency has been followed */ #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 */ #define JF_STOP 0x0004 /* -r */ #define JF_DEPEND 0x0008 /* Operation required by dependency */ #define JF_WILD 0x0010 /* Not specified on the command line */ #define JF_FAILED 0x0020 /* Operation failed */ #define JF_PARAMS 0x0040 /* Parameters checked and imported */ #define JF_RDTUN 0x0080 /* Create-only parameter check has been done */ #define JF_PERSIST 0x0100 /* Jail is temporarily persistent */ #define JF_TIMEOUT 0x0200 /* A command (or process kill) timed out */ #define JF_SLEEPQ 0x0400 /* Waiting on a command and/or timeout */ #define JF_FROM_RUNQ 0x0800 /* Has already been on the run queue */ #define JF_SHOW 0x1000 /* -e Exhibit list of configured jails */ #define JF_OP_MASK (JF_START | JF_SET | JF_STOP) #define JF_RESTART (JF_START | JF_STOP) #define JF_START_SET (JF_START | JF_SET) #define JF_SET_RESTART (JF_SET | JF_STOP) #define JF_START_SET_RESTART (JF_START | JF_SET | JF_STOP) #define JF_DO_STOP(js) (((js) & (JF_SET | JF_STOP)) == JF_STOP) enum intparam { IP__NULL = 0, /* Null command */ IP_ALLOW_DYING, /* Allow making changes to a dying jail */ IP_COMMAND, /* Command run inside jail at creation */ IP_DEPEND, /* Jail starts after (stops before) another */ IP_EXEC_CLEAN, /* Run commands in a clean environment */ IP_EXEC_CONSOLELOG, /* Redirect optput for commands run in jail */ IP_EXEC_FIB, /* Run jailed commands with this FIB */ IP_EXEC_JAIL_USER, /* Run jailed commands as this user */ IP_EXEC_POSTSTART, /* Commands run outside jail after creating */ IP_EXEC_POSTSTOP, /* Commands run outside jail after removing */ IP_EXEC_PREPARE, /* Commands run outside jail before addrs and mounting */ IP_EXEC_PRESTART, /* Commands run outside jail before creating */ IP_EXEC_PRESTOP, /* Commands run outside jail before removing */ IP_EXEC_RELEASE, /* Commands run outside jail after addrs and unmounted */ IP_EXEC_CREATED, /* Commands run outside jail right after it was started */ IP_EXEC_START, /* Commands run inside jail on creation */ IP_EXEC_STOP, /* Commands run inside jail on removal */ IP_EXEC_SYSTEM_JAIL_USER,/* Get jail_user from system passwd file */ IP_EXEC_SYSTEM_USER, /* Run non-jailed commands as this user */ IP_EXEC_TIMEOUT, /* Time to wait for a command to complete */ #if defined(INET) || defined(INET6) IP_INTERFACE, /* Add IP addresses to this interface */ IP_IP_HOSTNAME, /* Get jail IP address(es) from hostname */ #endif IP_MOUNT, /* Mount points in fstab(5) form */ IP_MOUNT_DEVFS, /* Mount /dev under prison root */ IP_MOUNT_FDESCFS, /* Mount /dev/fd under prison root */ IP_MOUNT_PROCFS, /* Mount /proc under prison root */ IP_MOUNT_FSTAB, /* A standard fstab(5) file */ IP_STOP_TIMEOUT, /* Time to wait after sending SIGTERM */ IP_VNET_INTERFACE, /* Assign interface(s) to vnet jail */ #ifdef INET IP__IP4_IFADDR, /* Copy of ip4.addr with interface/netmask */ #endif #ifdef INET6 IP__IP6_IFADDR, /* Copy of ip6.addr with interface/prefixlen */ #endif IP__MOUNT_FROM_FSTAB, /* Line from mount.fstab file */ IP__OP, /* Placeholder for requested operation */ KP_ALLOW_CHFLAGS, KP_ALLOW_MOUNT, KP_ALLOW_RAW_SOCKETS, KP_ALLOW_SET_HOSTNAME, KP_ALLOW_SOCKET_AF, KP_ALLOW_SYSVIPC, KP_DEVFS_RULESET, KP_HOST_HOSTNAME, #ifdef INET KP_IP4_ADDR, #endif #ifdef INET6 KP_IP6_ADDR, #endif KP_JID, KP_NAME, KP_PATH, KP_PERSIST, KP_SECURELEVEL, KP_VNET, IP_NPARAM }; STAILQ_HEAD(cfvars, cfvar); struct cfvar { STAILQ_ENTRY(cfvar) tq; char *name; size_t pos; }; TAILQ_HEAD(cfstrings, cfstring); struct cfstring { TAILQ_ENTRY(cfstring) tq; char *s; size_t len; struct cfvars vars; }; TAILQ_HEAD(cfparams, cfparam); struct cfparam { TAILQ_ENTRY(cfparam) tq; char *name; struct cfstrings val; unsigned flags; int gen; }; TAILQ_HEAD(cfjails, cfjail); STAILQ_HEAD(cfdepends, cfdepend); struct cfjail { TAILQ_ENTRY(cfjail) tq; char *name; char *comline; struct cfparams params; struct cfdepends dep[2]; struct cfjails *queue; struct cfjail *cfparent; struct cfparam *intparams[IP_NPARAM]; struct cfstring *comstring; struct jailparam *jp; struct timespec timeout; const enum intparam *comparam; unsigned flags; int jid; int seq; int pstatus; int ndeps; int njp; int nprocs; }; struct cfdepend { STAILQ_ENTRY(cfdepend) tq[2]; struct cfjail *j[2]; unsigned flags; }; struct cflex { const char *cfname; int error; }; extern void *emalloc(size_t); extern void *erealloc(void *, size_t); extern char *estrdup(const char *); extern int create_jail(struct cfjail *j); extern void failed(struct cfjail *j); extern void jail_note(const struct cfjail *j, const char *fmt, ...); extern void jail_warnx(const struct cfjail *j, const char *fmt, ...); extern int next_command(struct cfjail *j); extern int finish_command(struct cfjail *j); extern struct cfjail *next_proc(int nonblock); extern void load_config(const char *cfname); +extern void include_config(void *scanner, const char *cfname); extern struct cfjail *add_jail(void); extern void add_param(struct cfjail *j, const struct cfparam *p, enum intparam ipnum, const char *value); extern int bool_param(const struct cfparam *p); extern int int_param(const struct cfparam *p, int *ip); extern const char *string_param(const struct cfparam *p); extern int check_intparams(struct cfjail *j); extern int import_params(struct cfjail *j); 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); extern void dep_done(struct cfjail *j, unsigned flags); extern void dep_reset(struct cfjail *j); extern struct cfjail *next_jail(void); extern int start_state(const char *target, int docf, unsigned state, int running); extern void requeue(struct cfjail *j, struct cfjails *queue); extern void requeue_head(struct cfjail *j, struct cfjails *queue); +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); + extern struct cfjails cfjails; extern struct cfjails ready; extern struct cfjails depend; extern int iflag; extern int note_remove; extern int paralimit; extern int verbose; diff --git a/usr.sbin/jail/jailparse.y b/usr.sbin/jail/jailparse.y index ccc311a76223..e4f2310c7fb4 100644 --- a/usr.sbin/jail/jailparse.y +++ b/usr.sbin/jail/jailparse.y @@ -1,250 +1,275 @@ %{ /*- * SPDX-License-Identifier: BSD-2-Clause * * Copyright (c) 2011 James Gritton * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF * SUCH DAMAGE. */ #include __FBSDID("$FreeBSD$"); #include #include #include #include "jailp.h" #ifdef DEBUG #define YYDEBUG 1 #endif static struct cfjail *current_jail; static struct cfjail *global_jail; %} %union { struct cfparam *p; struct cfstrings *ss; struct cfstring *s; char *cs; } %token PLEQ %token STR STR1 VAR VAR1 %type

param name %type value %type string %pure-parser %lex-param { void *scanner } %parse-param { void *scanner } %% /* * A config file is a list of jails and parameters. Parameters are * added to the current jail, otherwise to a global pesudo-jail. */ conf : | conf jail | conf param ';' { - struct cfjail *j = current_jail; + if (!special_param($2, scanner)) { + struct cfjail *j = current_jail; - if (j == NULL) { - if (global_jail == NULL) { - global_jail = add_jail(); - global_jail->name = estrdup("*"); + if (j == NULL) { + if (global_jail == NULL) { + global_jail = add_jail(); + global_jail->name = estrdup("*"); + } + j = global_jail; } - j = global_jail; + TAILQ_INSERT_TAIL(&j->params, $2, tq); } - TAILQ_INSERT_TAIL(&j->params, $2, tq); } | conf ';' ; jail : jail_name '{' conf '}' { current_jail = current_jail->cfparent; } ; jail_name : STR { struct cfjail *j = add_jail(); if (current_jail == NULL) j->name = $1; else { /* * A nested jail definition becomes * a hierarchically-named sub-jail. */ size_t parentlen = strlen(current_jail->name); j->name = emalloc(parentlen + strlen($1) + 2); strcpy(j->name, current_jail->name); j->name[parentlen++] = '.'; strcpy(j->name + parentlen, $1); free($1); } j->cfparent = current_jail; current_jail = j; } ; /* * Parameters have a name and an optional list of value strings, * which may have "+=" or "=" preceding them. */ param : name { $$ = $1; } | name '=' value { $$ = $1; TAILQ_CONCAT(&$$->val, $3, tq); free($3); } | name PLEQ value { $$ = $1; TAILQ_CONCAT(&$$->val, $3, tq); $$->flags |= PF_APPEND; free($3); } | name value { $$ = $1; TAILQ_CONCAT(&$$->val, $2, tq); + $$->flags |= PF_NAMEVAL; free($2); } | error ; /* * A parameter has a fixed name. A variable definition looks just like a * parameter except that the name is a variable. */ name : STR { $$ = emalloc(sizeof(struct cfparam)); $$->name = $1; TAILQ_INIT(&$$->val); $$->flags = 0; } | VAR { $$ = emalloc(sizeof(struct cfparam)); $$->name = $1; TAILQ_INIT(&$$->val); $$->flags = PF_VAR; } ; value : string { $$ = emalloc(sizeof(struct cfstrings)); TAILQ_INIT($$); TAILQ_INSERT_TAIL($$, $1, tq); } | value ',' string { $$ = $1; TAILQ_INSERT_TAIL($$, $3, tq); } ; /* * Strings may be passed in pieces, because of quoting and/or variable * interpolation. Reassemble them into a single string. */ string : STR { $$ = emalloc(sizeof(struct cfstring)); $$->s = $1; $$->len = strlen($1); STAILQ_INIT(&$$->vars); } | VAR { struct cfvar *v; $$ = emalloc(sizeof(struct cfstring)); $$->s = estrdup(""); $$->len = 0; STAILQ_INIT(&$$->vars); v = emalloc(sizeof(struct cfvar)); v->name = $1; v->pos = 0; STAILQ_INSERT_TAIL(&$$->vars, v, tq); } | string STR1 { size_t len1; $$ = $1; len1 = strlen($2); $$->s = erealloc($$->s, $$->len + len1 + 1); strcpy($$->s + $$->len, $2); free($2); $$->len += len1; } | string VAR1 { struct cfvar *v; $$ = $1; v = emalloc(sizeof(struct cfvar)); v->name = $2; v->pos = $$->len; STAILQ_INSERT_TAIL(&$$->vars, v, tq); } ; %% extern int YYLEX_DECL(); -extern struct cflex *yyget_extra(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_APPEND | 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(scanner, s->s); + 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; +}