aboutsummaryrefslogtreecommitdiff
path: root/usr.sbin/jail
diff options
context:
space:
mode:
Diffstat (limited to 'usr.sbin/jail')
-rw-r--r--usr.sbin/jail/Makefile36
-rw-r--r--usr.sbin/jail/Makefile.depend20
-rw-r--r--usr.sbin/jail/command.c1076
-rw-r--r--usr.sbin/jail/config.c919
-rw-r--r--usr.sbin/jail/jail.81617
-rw-r--r--usr.sbin/jail/jail.c1071
-rw-r--r--usr.sbin/jail/jail.conf.5275
-rw-r--r--usr.sbin/jail/jaillex.l218
-rw-r--r--usr.sbin/jail/jailp.h254
-rw-r--r--usr.sbin/jail/jailparse.y276
-rw-r--r--usr.sbin/jail/state.c492
-rw-r--r--usr.sbin/jail/tests/Makefile9
-rw-r--r--usr.sbin/jail/tests/Makefile.depend10
-rw-r--r--usr.sbin/jail/tests/commands.jail.conf7
-rwxr-xr-xusr.sbin/jail/tests/jail_basic_test.sh337
15 files changed, 6617 insertions, 0 deletions
diff --git a/usr.sbin/jail/Makefile b/usr.sbin/jail/Makefile
new file mode 100644
index 000000000000..babe7b9459c0
--- /dev/null
+++ b/usr.sbin/jail/Makefile
@@ -0,0 +1,36 @@
+.include <src.opts.mk>
+
+PROG= jail
+MAN= jail.8 jail.conf.5
+SRCS= jail.c command.c config.c state.c jailp.h jaillex.l jailparse.y y.tab.h
+
+LIBADD= jail kvm util
+
+PACKAGE=jail
+
+NO_WMISSING_VARIABLE_DECLARATIONS=
+CWARNFLAGS.jailparse.c= ${NO_WUNUSED_BUT_SET_VARIABLE}
+
+YFLAGS+=-v
+CFLAGS+=-I. -I${.CURDIR}
+
+# workaround for GNU ld (GNU Binutils) 2.33.1:
+# relocation truncated to fit: R_RISCV_GPREL_I against `.LANCHOR2'
+# https://bugs.freebsd.org/242109
+.if defined(LINKER_TYPE) && ${LINKER_TYPE} == "bfd" && ${MACHINE} == "riscv"
+CFLAGS+=-Wl,--no-relax
+.endif
+
+.if ${MK_INET6_SUPPORT} != "no"
+CFLAGS+= -DINET6
+.endif
+.if ${MK_INET_SUPPORT} != "no"
+CFLAGS+= -DINET
+.endif
+
+CLEANFILES= y.output
+
+HAS_TESTS=
+SUBDIR.${MK_TESTS}+= tests
+
+.include <bsd.prog.mk>
diff --git a/usr.sbin/jail/Makefile.depend b/usr.sbin/jail/Makefile.depend
new file mode 100644
index 000000000000..ef2f6c1424ca
--- /dev/null
+++ b/usr.sbin/jail/Makefile.depend
@@ -0,0 +1,20 @@
+# Autogenerated - do NOT edit!
+
+DIRDEPS = \
+ include \
+ include/arpa \
+ include/xlocale \
+ lib/${CSU_DIR} \
+ lib/libc \
+ lib/libcompiler_rt \
+ lib/libjail \
+ lib/libkvm \
+ lib/libutil \
+ usr.bin/yacc.host \
+
+
+.include <dirdeps.mk>
+
+.if ${DEP_RELDIR} == ${_DEP_RELDIR}
+# local dependencies - needed for -jN in clean tree
+.endif
diff --git a/usr.sbin/jail/command.c b/usr.sbin/jail/command.c
new file mode 100644
index 000000000000..9da4fe51673a
--- /dev/null
+++ b/usr.sbin/jail/command.c
@@ -0,0 +1,1076 @@
+/*-
+ * 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 <sys/types.h>
+#include <sys/cpuset.h>
+#include <sys/event.h>
+#include <sys/mount.h>
+#include <sys/stat.h>
+#include <sys/sysctl.h>
+#include <sys/user.h>
+#include <sys/wait.h>
+
+#include <err.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <kvm.h>
+#include <login_cap.h>
+#include <paths.h>
+#include <pwd.h>
+#include <signal.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+#include <vis.h>
+
+#include "jailp.h"
+
+#define DEFAULT_STOP_TIMEOUT 10
+#define PHASH_SIZE 256
+
+LIST_HEAD(phhead, phash);
+
+struct phash {
+ LIST_ENTRY(phash) le;
+ struct cfjail *j;
+ pid_t pid;
+};
+
+int paralimit = -1;
+
+extern char **environ;
+
+static int run_command(struct cfjail *j);
+static int add_proc(struct cfjail *j, pid_t pid);
+static void clear_procs(struct cfjail *j);
+static struct cfjail *find_proc(pid_t pid);
+static int term_procs(struct cfjail *j);
+static int get_user_info(struct cfjail *j, const char *username,
+ const struct passwd **pwdp, login_cap_t **lcapp);
+static int check_path(struct cfjail *j, const char *pname, const char *path,
+ int isfile, const char *umount_type);
+
+static struct cfjails sleeping = TAILQ_HEAD_INITIALIZER(sleeping);
+static struct cfjails runnable = TAILQ_HEAD_INITIALIZER(runnable);
+static struct cfstring dummystring = { .len = 1 };
+static struct phhead phash[PHASH_SIZE];
+static int kq;
+
+static cpusetid_t
+root_cpuset_id(void)
+{
+ static cpusetid_t setid = CPUSET_INVALID;
+ static int error;
+
+ /* Only try to get the cpuset once. */
+ if (error == 0 && setid == CPUSET_INVALID)
+ error = cpuset_getid(CPU_LEVEL_ROOT, CPU_WHICH_PID, -1, &setid);
+ if (error != 0)
+ return (CPUSET_INVALID);
+ return (setid);
+}
+
+/*
+ * Run the next command associated with a jail.
+ */
+int
+next_command(struct cfjail *j)
+{
+ enum intparam comparam;
+ int create_failed, stopping;
+
+ if (paralimit == 0) {
+ if (j->flags & JF_FROM_RUNQ)
+ requeue_head(j, &runnable);
+ else
+ requeue(j, &runnable);
+ return 1;
+ }
+ j->flags &= ~JF_FROM_RUNQ;
+ create_failed = (j->flags & (JF_STOP | JF_FAILED)) == JF_FAILED;
+ stopping = (j->flags & JF_STOP) != 0;
+ comparam = *j->comparam;
+ for (;;) {
+ if (j->comstring == NULL) {
+ j->comparam += create_failed ? -1 : 1;
+ switch ((comparam = *j->comparam)) {
+ case IP__NULL:
+ return 0;
+ case IP_MOUNT_DEVFS:
+ if (!bool_param(j->intparams[IP_MOUNT_DEVFS]))
+ continue;
+ j->comstring = &dummystring;
+ break;
+ case IP_MOUNT_FDESCFS:
+ if (!bool_param(j->intparams[IP_MOUNT_FDESCFS]))
+ continue;
+ j->comstring = &dummystring;
+ break;
+ case IP_MOUNT_PROCFS:
+ if (!bool_param(j->intparams[IP_MOUNT_PROCFS]))
+ continue;
+ j->comstring = &dummystring;
+ break;
+ case IP__OP:
+ case IP_STOP_TIMEOUT:
+ j->comstring = &dummystring;
+ break;
+ default:
+ if (j->intparams[comparam] == NULL)
+ continue;
+ j->comstring = create_failed || (stopping &&
+ (j->intparams[comparam]->flags & PF_REV))
+ ? TAILQ_LAST(&j->intparams[comparam]->val,
+ cfstrings)
+ : TAILQ_FIRST(&j->intparams[comparam]->val);
+ }
+ } else {
+ j->comstring = j->comstring == &dummystring ? NULL :
+ create_failed || (stopping &&
+ (j->intparams[comparam]->flags & PF_REV))
+ ? TAILQ_PREV(j->comstring, cfstrings, tq)
+ : TAILQ_NEXT(j->comstring, tq);
+ }
+ if (j->comstring == NULL || j->comstring->len == 0 ||
+ (create_failed && (comparam == IP_EXEC_PRESTART ||
+ comparam == IP_EXEC_CREATED || comparam == IP_EXEC_START ||
+ comparam == IP_COMMAND || comparam == IP_EXEC_POSTSTART ||
+ comparam == IP_EXEC_PREPARE)))
+ continue;
+ switch (run_command(j)) {
+ case -1:
+ failed(j);
+ /* FALLTHROUGH */
+ case 1:
+ return 1;
+ }
+ }
+}
+
+/*
+ * Check command exit status
+ */
+int
+finish_command(struct cfjail *j)
+{
+ struct cfjail *rj;
+ int error;
+
+ if (!(j->flags & JF_SLEEPQ))
+ return 0;
+ j->flags &= ~JF_SLEEPQ;
+ if (*j->comparam == IP_STOP_TIMEOUT) {
+ j->flags &= ~JF_TIMEOUT;
+ j->pstatus = 0;
+ return 0;
+ }
+ paralimit++;
+ if (!TAILQ_EMPTY(&runnable)) {
+ rj = TAILQ_FIRST(&runnable);
+ rj->flags |= JF_FROM_RUNQ;
+ requeue(rj, &ready);
+ }
+ error = 0;
+ if (j->flags & JF_TIMEOUT) {
+ j->flags &= ~JF_TIMEOUT;
+ if (*j->comparam != IP_STOP_TIMEOUT) {
+ jail_warnx(j, "%s: timed out", j->comline);
+ failed(j);
+ error = -1;
+ } else if (verbose > 0)
+ jail_note(j, "timed out\n");
+ } else if (j->pstatus != 0) {
+ if (WIFSIGNALED(j->pstatus))
+ jail_warnx(j, "%s: exited on signal %d",
+ j->comline, WTERMSIG(j->pstatus));
+ else
+ jail_warnx(j, "%s: failed", j->comline);
+ j->pstatus = 0;
+ failed(j);
+ error = -1;
+ }
+ free(j->comline);
+ j->comline = NULL;
+ return error;
+}
+
+/*
+ * Check for finished processes or timeouts.
+ */
+struct cfjail *
+next_proc(int nonblock)
+{
+ struct kevent ke;
+ struct timespec ts;
+ struct timespec *tsp;
+ struct cfjail *j;
+
+ if (!TAILQ_EMPTY(&sleeping)) {
+ again:
+ tsp = NULL;
+ if ((j = TAILQ_FIRST(&sleeping)) && j->timeout.tv_sec) {
+ clock_gettime(CLOCK_REALTIME, &ts);
+ ts.tv_sec = j->timeout.tv_sec - ts.tv_sec;
+ ts.tv_nsec = j->timeout.tv_nsec - ts.tv_nsec;
+ if (ts.tv_nsec < 0) {
+ ts.tv_sec--;
+ ts.tv_nsec += 1000000000;
+ }
+ if (ts.tv_sec < 0 ||
+ (ts.tv_sec == 0 && ts.tv_nsec == 0)) {
+ j->flags |= JF_TIMEOUT;
+ clear_procs(j);
+ return j;
+ }
+ tsp = &ts;
+ }
+ if (nonblock) {
+ ts.tv_sec = 0;
+ ts.tv_nsec = 0;
+ tsp = &ts;
+ }
+ switch (kevent(kq, NULL, 0, &ke, 1, tsp)) {
+ case -1:
+ if (errno != EINTR)
+ err(1, "kevent");
+ goto again;
+ case 0:
+ if (!nonblock) {
+ j = TAILQ_FIRST(&sleeping);
+ j->flags |= JF_TIMEOUT;
+ clear_procs(j);
+ return j;
+ }
+ break;
+ case 1:
+ (void)waitpid(ke.ident, NULL, WNOHANG);
+ if ((j = find_proc(ke.ident))) {
+ j->pstatus = ke.data;
+ return j;
+ }
+ goto again;
+ }
+ }
+ return NULL;
+}
+
+/*
+ * Run a single command for a jail, possibly inside the jail.
+ */
+static int
+run_command(struct cfjail *j)
+{
+ const struct passwd *pwd;
+ const struct cfstring *comstring, *s;
+ login_cap_t *lcap;
+ const char **argv;
+ char *acs, *cs, *comcs, *devpath;
+ const char *jidstr, *conslog, *fmt, *path, *ruleset, *term, *username;
+ enum intparam comparam;
+ size_t comlen, ret;
+ pid_t pid;
+ cpusetid_t setid;
+ int argc, bg, clean, consfd, down, fib, i, injail, sjuser, timeout;
+#if defined(INET) || defined(INET6)
+ char *addr, *extrap, *p, *val;
+#endif
+
+ static char *cleanenv;
+
+ /* Perform some operations that aren't actually commands */
+ comparam = *j->comparam;
+ down = j->flags & (JF_STOP | JF_FAILED);
+ switch (comparam) {
+ case IP_STOP_TIMEOUT:
+ return term_procs(j);
+
+ case IP__OP:
+ if (down) {
+ if (jail_remove(j->jid) < 0 && errno == EPERM) {
+ jail_warnx(j, "jail_remove: %s",
+ strerror(errno));
+ return -1;
+ }
+ if (verbose > 0 || (verbose == 0 && (j->flags & JF_STOP
+ ? note_remove : j->name != NULL)))
+ jail_note(j, "removed\n");
+ j->jid = -1;
+ if (j->flags & JF_STOP)
+ dep_done(j, DF_LIGHT);
+ else
+ j->flags &= ~JF_PERSIST;
+ } else {
+ if (create_jail(j) < 0)
+ return -1;
+ if (iflag)
+ printf("%d\n", j->jid);
+ if (verbose >= 0 && (j->name || verbose > 0))
+ jail_note(j, "created\n");
+
+ /*
+ * Populate our jid and name parameters if they were not
+ * provided. This simplifies later logic that wants to
+ * use the jid or name to be able to do so reliably.
+ */
+ if (j->intparams[KP_JID] == NULL) {
+ char ljidstr[16];
+
+ (void)snprintf(ljidstr, sizeof(ljidstr), "%d",
+ j->jid);
+ add_param(j, NULL, KP_JID, ljidstr);
+ }
+
+ /* This matches the kernel behavior. */
+ if (j->intparams[KP_NAME] == NULL)
+ add_param(j, j->intparams[KP_JID], KP_NAME,
+ NULL);
+
+ dep_done(j, DF_LIGHT);
+ }
+ return 0;
+
+ default: ;
+ }
+ /*
+ * Collect exec arguments. Internal commands for network and
+ * mounting build their own argument lists.
+ */
+ comstring = j->comstring;
+ bg = 0;
+ switch (comparam) {
+#ifdef INET
+ case IP__IP4_IFADDR:
+ argc = 0;
+ val = alloca(strlen(comstring->s) + 1);
+ strcpy(val, comstring->s);
+ cs = val;
+ extrap = NULL;
+ while ((p = strchr(cs, ' ')) != NULL && strlen(p) > 1) {
+ if (extrap == NULL) {
+ *p = '\0';
+ extrap = p + 1;
+ }
+ cs = p + 1;
+ argc++;
+ }
+
+ argv = alloca((8 + argc) * sizeof(char *));
+ argv[0] = _PATH_IFCONFIG;
+ if ((cs = strchr(val, '|'))) {
+ argv[1] = acs = alloca(cs - val + 1);
+ strlcpy(acs, val, cs - val + 1);
+ addr = cs + 1;
+ } else {
+ argv[1] = string_param(j->intparams[IP_INTERFACE]);
+ addr = val;
+ }
+ argv[2] = "inet";
+ if (!(cs = strchr(addr, '/'))) {
+ argv[3] = addr;
+ argv[4] = "netmask";
+ argv[5] = "255.255.255.255";
+ argc = 6;
+ } else if (strchr(cs + 1, '.')) {
+ argv[3] = acs = alloca(cs - addr + 1);
+ strlcpy(acs, addr, cs - addr + 1);
+ argv[4] = "netmask";
+ argv[5] = cs + 1;
+ argc = 6;
+ } else {
+ argv[3] = addr;
+ argc = 4;
+ }
+
+ if (!down && extrap != NULL) {
+ for (cs = strtok(extrap, " "); cs;
+ cs = strtok(NULL, " ")) {
+ size_t len = strlen(cs) + 1;
+ argv[argc++] = acs = alloca(len);
+ strlcpy(acs, cs, len);
+ }
+ }
+
+ argv[argc] = down ? "-alias" : "alias";
+ argv[argc + 1] = NULL;
+ break;
+#endif
+
+#ifdef INET6
+ case IP__IP6_IFADDR:
+ argc = 0;
+ val = alloca(strlen(comstring->s) + 1);
+ strcpy(val, comstring->s);
+ cs = val;
+ extrap = NULL;
+ while ((p = strchr(cs, ' ')) != NULL && strlen(p) > 1) {
+ if (extrap == NULL) {
+ *p = '\0';
+ extrap = p + 1;
+ }
+ cs = p + 1;
+ argc++;
+ }
+
+ argv = alloca((8 + argc) * sizeof(char *));
+ argv[0] = _PATH_IFCONFIG;
+ if ((cs = strchr(val, '|'))) {
+ argv[1] = acs = alloca(cs - val + 1);
+ strlcpy(acs, val, cs - val + 1);
+ addr = cs + 1;
+ } else {
+ argv[1] = string_param(j->intparams[IP_INTERFACE]);
+ addr = val;
+ }
+ argv[2] = "inet6";
+ argv[3] = addr;
+ if (!(cs = strchr(addr, '/'))) {
+ argv[4] = "prefixlen";
+ argv[5] = "128";
+ argc = 6;
+ } else
+ argc = 4;
+
+ if (!down && extrap != NULL) {
+ for (cs = strtok(extrap, " "); cs;
+ cs = strtok(NULL, " ")) {
+ size_t len = strlen(cs) + 1;
+ argv[argc++] = acs = alloca(len);
+ strlcpy(acs, cs, len);
+ }
+ }
+
+ argv[argc] = down ? "-alias" : "alias";
+ argv[argc + 1] = NULL;
+ break;
+#endif
+
+ case IP_VNET_INTERFACE:
+ argv = alloca(5 * sizeof(char *));
+ argv[0] = _PATH_IFCONFIG;
+ argv[1] = comstring->s;
+ argv[2] = down ? "-vnet" : "vnet";
+ argv[3] = string_param(j->intparams[KP_JID]);
+ argv[4] = NULL;
+ break;
+
+ case IP_MOUNT:
+ case IP__MOUNT_FROM_FSTAB:
+ argv = alloca(8 * sizeof(char *));
+ comcs = alloca(comstring->len + 1);
+ strcpy(comcs, comstring->s);
+ argc = 0;
+ for (cs = strtok(comcs, " \t\f\v\r\n"); cs && argc < 4;
+ cs = strtok(NULL, " \t\f\v\r\n")) {
+ if (argc <= 1 && strunvis(cs, cs) < 0) {
+ jail_warnx(j, "%s: %s: fstab parse error",
+ j->intparams[comparam]->name, comstring->s);
+ return -1;
+ }
+ argv[argc++] = cs;
+ }
+ if (argc == 0)
+ return 0;
+ if (argc < 3) {
+ jail_warnx(j, "%s: %s: missing information",
+ j->intparams[comparam]->name, comstring->s);
+ return -1;
+ }
+ if (check_path(j, j->intparams[comparam]->name, argv[1], 0,
+ down ? argv[2] : NULL) < 0)
+ return -1;
+ if (down) {
+ argv[4] = NULL;
+ argv[3] = argv[1];
+ argv[0] = "/sbin/umount";
+ } else {
+ if (argc == 4) {
+ argv[7] = NULL;
+ argv[6] = argv[1];
+ argv[5] = argv[0];
+ argv[4] = argv[3];
+ argv[3] = "-o";
+ } else {
+ argv[5] = NULL;
+ argv[4] = argv[1];
+ argv[3] = argv[0];
+ }
+ argv[0] = _PATH_MOUNT;
+ }
+ argv[1] = "-t";
+ break;
+
+ case IP_MOUNT_DEVFS:
+ argv = alloca(7 * sizeof(char *));
+ path = string_param(j->intparams[KP_PATH]);
+ if (path == NULL) {
+ jail_warnx(j, "mount.devfs: no jail root path defined");
+ return -1;
+ }
+ devpath = alloca(strlen(path) + 5);
+ sprintf(devpath, "%s/dev", path);
+ if (check_path(j, "mount.devfs", devpath, 0,
+ down ? "devfs" : NULL) < 0)
+ return -1;
+ if (down) {
+ argv[0] = "/sbin/umount";
+ argv[1] = devpath;
+ argv[2] = NULL;
+ } else {
+ argv[0] = _PATH_MOUNT;
+ argv[1] = "-t";
+ argv[2] = "devfs";
+ ruleset = string_param(j->intparams[KP_DEVFS_RULESET]);
+ if (!ruleset)
+ ruleset = "4"; /* devfsrules_jail */
+ argv[3] = acs = alloca(11 + strlen(ruleset));
+ sprintf(acs, "-oruleset=%s", ruleset);
+ argv[4] = ".";
+ argv[5] = devpath;
+ argv[6] = NULL;
+ }
+ break;
+
+ case IP_MOUNT_FDESCFS:
+ argv = alloca(7 * sizeof(char *));
+ path = string_param(j->intparams[KP_PATH]);
+ if (path == NULL) {
+ jail_warnx(j, "mount.fdescfs: no jail root path defined");
+ return -1;
+ }
+ devpath = alloca(strlen(path) + 8);
+ sprintf(devpath, "%s/dev/fd", path);
+ if (check_path(j, "mount.fdescfs", devpath, 0,
+ down ? "fdescfs" : NULL) < 0)
+ return -1;
+ if (down) {
+ argv[0] = "/sbin/umount";
+ argv[1] = devpath;
+ argv[2] = NULL;
+ } else {
+ argv[0] = _PATH_MOUNT;
+ argv[1] = "-t";
+ argv[2] = "fdescfs";
+ argv[3] = ".";
+ argv[4] = devpath;
+ argv[5] = NULL;
+ }
+ break;
+
+ case IP_MOUNT_PROCFS:
+ argv = alloca(7 * sizeof(char *));
+ path = string_param(j->intparams[KP_PATH]);
+ if (path == NULL) {
+ jail_warnx(j, "mount.procfs: no jail root path defined");
+ return -1;
+ }
+ devpath = alloca(strlen(path) + 6);
+ sprintf(devpath, "%s/proc", path);
+ if (check_path(j, "mount.procfs", devpath, 0,
+ down ? "procfs" : NULL) < 0)
+ return -1;
+ if (down) {
+ argv[0] = "/sbin/umount";
+ argv[1] = devpath;
+ argv[2] = NULL;
+ } else {
+ argv[0] = _PATH_MOUNT;
+ argv[1] = "-t";
+ argv[2] = "procfs";
+ argv[3] = ".";
+ argv[4] = devpath;
+ argv[5] = NULL;
+ }
+ break;
+
+ case IP_ZFS_DATASET:
+ argv = alloca(4 * sizeof(char *));
+ jidstr = string_param(j->intparams[KP_JID]);
+ fmt = "if [ $(/sbin/zfs get -H -o value jailed %s) = on ]; then /sbin/zfs jail %s %s || echo error, attaching %s to jail %s failed; else echo error, you need to set jailed=on for dataset %s; fi";
+ comlen = strlen(fmt)
+ + 2 * strlen(jidstr)
+ + 4 * comstring->len
+ - 6 * 2 /* 6 * "%s" */
+ + 1;
+ comcs = alloca(comlen);
+ ret = snprintf(comcs, comlen, fmt, comstring->s,
+ jidstr, comstring->s, comstring->s, jidstr,
+ comstring->s);
+ if (ret >= comlen) {
+ jail_warnx(j, "internal error in ZFS dataset handling");
+ exit(1);
+ }
+ argv[0] = _PATH_BSHELL;
+ argv[1] = "-c";
+ argv[2] = comcs;
+ argv[3] = NULL;
+ break;
+
+ case IP_COMMAND:
+ if (j->name != NULL)
+ goto default_command;
+ argc = 0;
+ TAILQ_FOREACH(s, &j->intparams[IP_COMMAND]->val, tq)
+ argc++;
+ argv = alloca((argc + 1) * sizeof(char *));
+ argc = 0;
+ TAILQ_FOREACH(s, &j->intparams[IP_COMMAND]->val, tq)
+ argv[argc++] = s->s;
+ argv[argc] = NULL;
+ j->comstring = &dummystring;
+ break;
+
+ default:
+ default_command:
+ if ((cs = strpbrk(comstring->s, "!\"$&'()*;<>?[\\]`{|}~")) &&
+ !(cs[0] == '&' && cs[1] == '\0')) {
+ argv = alloca(4 * sizeof(char *));
+ argv[0] = _PATH_BSHELL;
+ argv[1] = "-c";
+ argv[2] = comstring->s;
+ argv[3] = NULL;
+ } else {
+ if (cs) {
+ *cs = 0;
+ bg = 1;
+ }
+ comcs = alloca(comstring->len + 1);
+ strcpy(comcs, comstring->s);
+ argc = 0;
+ for (cs = strtok(comcs, " \t\f\v\r\n"); cs;
+ cs = strtok(NULL, " \t\f\v\r\n"))
+ argc++;
+ argv = alloca((argc + 1) * sizeof(char *));
+ strcpy(comcs, comstring->s);
+ argc = 0;
+ for (cs = strtok(comcs, " \t\f\v\r\n"); cs;
+ cs = strtok(NULL, " \t\f\v\r\n"))
+ argv[argc++] = cs;
+ argv[argc] = NULL;
+ }
+ }
+ if (argv[0] == NULL)
+ return 0;
+
+ if (int_param(j->intparams[IP_EXEC_TIMEOUT], &timeout) &&
+ timeout != 0) {
+ clock_gettime(CLOCK_REALTIME, &j->timeout);
+ j->timeout.tv_sec += timeout;
+ } else
+ j->timeout.tv_sec = 0;
+
+ injail = comparam == IP_EXEC_START || comparam == IP_COMMAND ||
+ comparam == IP_EXEC_STOP;
+ if (injail)
+ setid = root_cpuset_id();
+ else
+ setid = CPUSET_INVALID;
+ clean = bool_param(j->intparams[IP_EXEC_CLEAN]);
+ username = string_param(j->intparams[injail
+ ? IP_EXEC_JAIL_USER : IP_EXEC_SYSTEM_USER]);
+ sjuser = bool_param(j->intparams[IP_EXEC_SYSTEM_JAIL_USER]);
+
+ consfd = 0;
+ if (injail &&
+ (conslog = string_param(j->intparams[IP_EXEC_CONSOLELOG]))) {
+ if (check_path(j, "exec.consolelog", conslog, 1, NULL) < 0)
+ return -1;
+ consfd =
+ open(conslog, O_WRONLY | O_CREAT | O_APPEND, DEFFILEMODE);
+ if (consfd < 0) {
+ jail_warnx(j, "open %s: %s", conslog, strerror(errno));
+ return -1;
+ }
+ }
+
+ comlen = 0;
+ for (i = 0; argv[i]; i++)
+ comlen += strlen(argv[i]) + 1;
+ j->comline = cs = emalloc(comlen);
+ for (i = 0; argv[i]; i++) {
+ strcpy(cs, argv[i]);
+ if (argv[i + 1]) {
+ cs += strlen(argv[i]) + 1;
+ cs[-1] = ' ';
+ }
+ }
+ if (verbose > 0)
+ jail_note(j, "run command%s%s%s: %s\n",
+ injail ? " in jail" : "", username ? " as " : "",
+ username ? username : "", j->comline);
+
+ pid = fork();
+ if (pid < 0)
+ err(1, "fork");
+ if (pid > 0) {
+ if (bg || !add_proc(j, pid)) {
+ free(j->comline);
+ j->comline = NULL;
+ return 0;
+ } else {
+ paralimit--;
+ return 1;
+ }
+ }
+ if (bg)
+ setsid();
+
+ /* Set up the environment and run the command */
+ pwd = NULL;
+ lcap = NULL;
+ if ((clean || username) && injail && sjuser &&
+ get_user_info(j, username, &pwd, &lcap) < 0)
+ exit(1);
+ if (injail) {
+ /* jail_attach won't chdir along with its chroot. */
+ path = string_param(j->intparams[KP_PATH]);
+ if (path && chdir(path) < 0) {
+ jail_warnx(j, "chdir %s: %s", path, strerror(errno));
+ exit(1);
+ }
+ if (int_param(j->intparams[IP_EXEC_FIB], &fib) &&
+ setfib(fib) < 0) {
+ jail_warnx(j, "setfib: %s", strerror(errno));
+ exit(1);
+ }
+
+ /*
+ * We wouldn't have specialized our affinity, so just setid to
+ * root. We do this prior to attaching to avoid the kernel
+ * having to create a transient cpuset that we'll promptly
+ * free up with a reset to the jail's cpuset.
+ *
+ * This is just a best-effort to use as wide of mask as
+ * possible.
+ */
+ if (setid != CPUSET_INVALID)
+ (void)cpuset_setid(CPU_WHICH_PID, -1, setid);
+
+ if (jail_attach(j->jid) < 0) {
+ jail_warnx(j, "jail_attach: %s", strerror(errno));
+ exit(1);
+ }
+ }
+ if (clean || username) {
+ if (!(injail && sjuser) &&
+ get_user_info(j, username, &pwd, &lcap) < 0)
+ exit(1);
+ if (clean) {
+ term = getenv("TERM");
+ environ = &cleanenv;
+ setenv("PATH", "/bin:/usr/bin", 0);
+ if (term != NULL)
+ setenv("TERM", term, 1);
+ }
+ if (setgid(pwd->pw_gid) < 0) {
+ jail_warnx(j, "setgid %d: %s", pwd->pw_gid,
+ strerror(errno));
+ exit(1);
+ }
+ if (setusercontext(lcap, pwd, pwd->pw_uid, username
+ ? LOGIN_SETALL & ~LOGIN_SETGROUP & ~LOGIN_SETLOGIN
+ : LOGIN_SETPATH | LOGIN_SETENV) < 0) {
+ jail_warnx(j, "setusercontext %s: %s", pwd->pw_name,
+ strerror(errno));
+ exit(1);
+ }
+ login_close(lcap);
+ setenv("USER", pwd->pw_name, 1);
+ setenv("HOME", pwd->pw_dir, 1);
+ setenv("SHELL",
+ *pwd->pw_shell ? pwd->pw_shell : _PATH_BSHELL, 1);
+ if (clean && username && chdir(pwd->pw_dir) < 0) {
+ jail_warnx(j, "chdir %s: %s",
+ pwd->pw_dir, strerror(errno));
+ exit(1);
+ }
+ endpwent();
+ }
+ if (!injail) {
+ if (string_param(j->intparams[KP_JID]))
+ setenv("JID", string_param(j->intparams[KP_JID]), 1);
+ setenv("JNAME", string_param(j->intparams[KP_NAME]), 1);
+
+ path = string_param(j->intparams[KP_PATH]);
+ setenv("JPATH", path ? path : "", 1);
+ }
+
+ if (consfd != 0 && (dup2(consfd, 1) < 0 || dup2(consfd, 2) < 0)) {
+ jail_warnx(j, "exec.consolelog: %s", strerror(errno));
+ exit(1);
+ }
+ closefrom(3);
+ execvp(argv[0], __DECONST(char *const*, argv));
+ jail_warnx(j, "exec %s: %s", argv[0], strerror(errno));
+ exit(1);
+}
+
+/*
+ * Add a process to the hash, tied to a jail.
+ */
+static int
+add_proc(struct cfjail *j, pid_t pid)
+{
+ struct kevent ke;
+ struct cfjail *tj;
+ struct phash *ph;
+
+ if (!kq && (kq = kqueue()) < 0)
+ err(1, "kqueue");
+ EV_SET(&ke, pid, EVFILT_PROC, EV_ADD, NOTE_EXIT, 0, NULL);
+ if (kevent(kq, &ke, 1, NULL, 0, NULL) < 0) {
+ if (errno == ESRCH)
+ return 0;
+ err(1, "kevent");
+ }
+ ph = emalloc(sizeof(struct phash));
+ ph->j = j;
+ ph->pid = pid;
+ LIST_INSERT_HEAD(&phash[pid % PHASH_SIZE], ph, le);
+ j->nprocs++;
+ j->flags |= JF_SLEEPQ;
+ if (j->timeout.tv_sec == 0)
+ requeue(j, &sleeping);
+ else {
+ /* File the jail in the sleep queue according to its timeout. */
+ TAILQ_REMOVE(j->queue, j, tq);
+ TAILQ_FOREACH(tj, &sleeping, tq) {
+ if (!tj->timeout.tv_sec ||
+ j->timeout.tv_sec < tj->timeout.tv_sec ||
+ (j->timeout.tv_sec == tj->timeout.tv_sec &&
+ j->timeout.tv_nsec <= tj->timeout.tv_nsec)) {
+ TAILQ_INSERT_BEFORE(tj, j, tq);
+ break;
+ }
+ }
+ if (tj == NULL)
+ TAILQ_INSERT_TAIL(&sleeping, j, tq);
+ j->queue = &sleeping;
+ }
+ return 1;
+}
+
+/*
+ * Remove any processes from the hash that correspond to a jail.
+ */
+static void
+clear_procs(struct cfjail *j)
+{
+ struct kevent ke;
+ struct phash *ph, *tph;
+ int i;
+
+ j->nprocs = 0;
+ for (i = 0; i < PHASH_SIZE; i++)
+ LIST_FOREACH_SAFE(ph, &phash[i], le, tph)
+ if (ph->j == j) {
+ EV_SET(&ke, ph->pid, EVFILT_PROC, EV_DELETE,
+ NOTE_EXIT, 0, NULL);
+ (void)kevent(kq, &ke, 1, NULL, 0, NULL);
+ LIST_REMOVE(ph, le);
+ free(ph);
+ }
+}
+
+/*
+ * Find the jail that corresponds to an exited process.
+ */
+static struct cfjail *
+find_proc(pid_t pid)
+{
+ struct cfjail *j;
+ struct phash *ph;
+
+ LIST_FOREACH(ph, &phash[pid % PHASH_SIZE], le)
+ if (ph->pid == pid) {
+ j = ph->j;
+ LIST_REMOVE(ph, le);
+ free(ph);
+ return --j->nprocs ? NULL : j;
+ }
+ return NULL;
+}
+
+/*
+ * Send SIGTERM to all processes in a jail and wait for them to die.
+ */
+static int
+term_procs(struct cfjail *j)
+{
+ struct kinfo_proc *ki;
+ int i, noted, pcnt, timeout;
+
+ static kvm_t *kd;
+
+ if (!int_param(j->intparams[IP_STOP_TIMEOUT], &timeout))
+ timeout = DEFAULT_STOP_TIMEOUT;
+ else if (timeout == 0)
+ return 0;
+
+ if (kd == NULL) {
+ kd = kvm_open(NULL, NULL, NULL, O_RDONLY, NULL);
+ if (kd == NULL)
+ return 0;
+ }
+
+ ki = kvm_getprocs(kd, KERN_PROC_PROC, 0, &pcnt);
+ if (ki == NULL)
+ return 0;
+ noted = 0;
+ for (i = 0; i < pcnt; i++)
+ if (ki[i].ki_jid == j->jid &&
+ kill(ki[i].ki_pid, SIGTERM) == 0) {
+ (void)add_proc(j, ki[i].ki_pid);
+ if (verbose > 0) {
+ if (!noted) {
+ noted = 1;
+ jail_note(j, "sent SIGTERM to:");
+ }
+ printf(" %d", ki[i].ki_pid);
+ }
+ }
+ if (noted)
+ printf("\n");
+ if (j->nprocs > 0) {
+ clock_gettime(CLOCK_REALTIME, &j->timeout);
+ j->timeout.tv_sec += timeout;
+ return 1;
+ }
+ return 0;
+}
+
+/*
+ * Look up a user in the passwd and login.conf files.
+ */
+static int
+get_user_info(struct cfjail *j, const char *username,
+ const struct passwd **pwdp, login_cap_t **lcapp)
+{
+ const struct passwd *pwd;
+
+ errno = 0;
+ *pwdp = pwd = username ? getpwnam(username) : getpwuid(getuid());
+ if (pwd == NULL) {
+ if (errno)
+ jail_warnx(j, "getpwnam%s%s: %s", username ? " " : "",
+ username ? username : "", strerror(errno));
+ else if (username)
+ jail_warnx(j, "%s: no such user", username);
+ else
+ jail_warnx(j, "unknown uid %d", getuid());
+ return -1;
+ }
+ *lcapp = login_getpwclass(pwd);
+ if (*lcapp == NULL) {
+ jail_warnx(j, "getpwclass %s: %s", pwd->pw_name,
+ strerror(errno));
+ return -1;
+ }
+ /* Set the groups while the group file is still available */
+ if (initgroups(pwd->pw_name, pwd->pw_gid) < 0) {
+ jail_warnx(j, "initgroups %s: %s", pwd->pw_name,
+ strerror(errno));
+ return -1;
+ }
+ return 0;
+}
+
+/*
+ * Make sure a mount or consolelog path is a valid absolute pathname
+ * with no symlinks.
+ */
+static int
+check_path(struct cfjail *j, const char *pname, const char *path, int isfile,
+ const char *umount_type)
+{
+ struct stat st, mpst;
+ struct statfs stfs;
+ char *tpath, *p;
+ const char *jailpath;
+ size_t jplen;
+
+ if (path[0] != '/') {
+ jail_warnx(j, "%s: %s: not an absolute pathname",
+ pname, path);
+ return -1;
+ }
+ /*
+ * Only check for symlinks in components below the jail's path,
+ * since that's where the security risk lies.
+ */
+ jailpath = string_param(j->intparams[KP_PATH]);
+ if (jailpath == NULL)
+ jailpath = "";
+ jplen = strlen(jailpath);
+ if (!strncmp(path, jailpath, jplen) && path[jplen] == '/') {
+ tpath = alloca(strlen(path) + 1);
+ strcpy(tpath, path);
+ for (p = tpath + jplen; p != NULL; ) {
+ p = strchr(p + 1, '/');
+ if (p)
+ *p = '\0';
+ if (lstat(tpath, &st) < 0) {
+ if (errno == ENOENT && isfile && !p)
+ break;
+ jail_warnx(j, "%s: %s: %s", pname, tpath,
+ strerror(errno));
+ return -1;
+ }
+ if (S_ISLNK(st.st_mode)) {
+ jail_warnx(j, "%s: %s is a symbolic link",
+ pname, tpath);
+ return -1;
+ }
+ if (p)
+ *p = '/';
+ }
+ }
+ if (umount_type != NULL) {
+ if (stat(path, &st) < 0 || statfs(path, &stfs) < 0) {
+ jail_warnx(j, "%s: %s: %s", pname, path,
+ strerror(errno));
+ return -1;
+ }
+ if (stat(stfs.f_mntonname, &mpst) < 0) {
+ jail_warnx(j, "%s: %s: %s", pname, stfs.f_mntonname,
+ strerror(errno));
+ return -1;
+ }
+ if (st.st_ino != mpst.st_ino) {
+ jail_warnx(j, "%s: %s: not a mount point",
+ pname, path);
+ return -1;
+ }
+ if (strcmp(stfs.f_fstypename, umount_type)) {
+ jail_warnx(j, "%s: %s: not a %s mount",
+ pname, path, umount_type);
+ return -1;
+ }
+ }
+ return 0;
+}
diff --git a/usr.sbin/jail/config.c b/usr.sbin/jail/config.c
new file mode 100644
index 000000000000..1bad04ccde68
--- /dev/null
+++ b/usr.sbin/jail/config.c
@@ -0,0 +1,919 @@
+/*-
+ * 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 <sys/types.h>
+#include <sys/errno.h>
+#include <sys/socket.h>
+#include <sys/sysctl.h>
+
+#include <arpa/inet.h>
+#include <netinet/in.h>
+
+#include <err.h>
+#include <glob.h>
+#include <netdb.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+
+#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 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},
+ [IP_ZFS_DATASET] = {"zfs.dataset", 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;
+ char *ep;
+ int did_self, jseq, pgen;
+
+ 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. After we
+ * collect other parameters, we'll go back and ensure they're
+ * both set if we need to do so here.
+ */
+ 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);
+
+ /*
+ * We only backfill if it's the name that wasn't set; if it was
+ * the jid, we can assume that will be populated later when the
+ * jail is created or found.
+ */
+ if (j->intparams[KP_NAME] == NULL)
+ add_param(j, NULL, KP_NAME, j->name);
+
+ /* 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);
+}
+
+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.8 b/usr.sbin/jail/jail.8
new file mode 100644
index 000000000000..9aed9b671b9e
--- /dev/null
+++ b/usr.sbin/jail/jail.8
@@ -0,0 +1,1617 @@
+.\" Copyright (c) 2000, 2003 Robert N. M. Watson
+.\" Copyright (c) 2008-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.
+.\"
+.Dd October 8, 2025
+.Dt JAIL 8
+.Os
+.Sh NAME
+.Nm jail
+.Nd "manage system jails"
+.Sh SYNOPSIS
+.Ss From Configuration File
+.Nm
+.Op Fl cm
+.Op Fl Cdqv
+.Op Fl f Ar conf_file
+.Op Fl p Ar limit
+.Op Ar jail
+.Nm
+.Op Fl r
+.Op Fl Cqv
+.Op Fl f Ar conf_file
+.Op Fl p Ar limit
+.Op Cm * | Ar jail ...
+.Ss Without Configuration File
+.Nm
+.Op Fl cm
+.Op Fl dhilqv
+.Op Fl J Ar jid_file
+.Op Fl u Ar username
+.Op Fl U Ar username
+.Ar param Ns = Ns Ar value ...
+.Op Cm command Ns = Ns Ar command ...
+.Nm
+.Op Fl rR
+.Op Fl qv
+.Op Cm * | Ar jail ...
+.Ss Show Parameters
+.Nm
+.Op Fl f Ar conf_file
+.Fl e
+.Ar separator
+.Ss Backward Compatibility
+.Nm
+.Op Fl dhilqv
+.Op Fl J Ar jid_file
+.Op Fl u Ar username
+.Op Fl U Ar username
+.Op Fl n Ar jailname
+.Op Fl s Ar securelevel
+.Ar path hostname ip Ns Op Cm \&, Ns Ar ...
+.Ar command ...
+.Sh DESCRIPTION
+The
+.Nm
+utility creates new jails, or modifies or removes existing jails.
+It can also print a list of configured jails and their parameters.
+A jail
+.Pq or Dq prison
+is specified via parameters on the command line, or in the
+.Xr jail.conf 5
+file.
+.Pp
+At least one of the options
+.Fl c ,
+.Fl e ,
+.Fl m
+or
+.Fl r
+must be specified.
+These options are used alone or in combination to describe the operation to
+perform:
+.Bl -tag -width indent
+.It Fl c
+Create a new jail.
+The jail
+.Va jid
+and
+.Va name
+parameters (if specified on the command line)
+must not refer to an existing jail.
+.It Fl e Ar separator
+Exhibit a list of all configured non-wildcard jails and their parameters.
+No jail creation, modification or removal performed if this option is used.
+The
+.Ar separator
+string is used to separate parameters.
+Use
+.Xr jls 8
+utility to list running jails.
+.It Fl m
+Modify an existing jail.
+One of the
+.Va jid
+or
+.Va name
+parameters must exist and refer to an existing jail.
+Some parameters may not be changed on a running jail.
+.It Fl r
+Remove the
+.Ar jail
+specified by jid or name.
+All jailed processes are killed, and all jails that are
+children of this jail are also
+removed.
+.It Fl rc
+Restart an existing jail.
+The jail is first removed and then re-created, as if
+.Dq Nm Fl r
+and
+.Dq Nm Fl c
+were run in succession.
+.It Fl cm
+Create a jail if it does not exist, or modify the jail if it does exist.
+.It Fl mr
+Modify an existing jail.
+The jail may be restarted if necessary to modify parameters than could
+not otherwise be changed.
+.It Fl cmr
+Create a jail if it doesn't exist, or modify (and possibly restart) the
+jail if it does exist.
+.El
+.Pp
+Other available options are:
+.Bl -tag -width indent
+.It Fl C
+Clean up after an already-removed jail, running commands and operations
+that are typically run following jail removal.
+.It Fl f Ar conf_file
+Use configuration file
+.Ar conf_file
+instead of the default
+.Pa /etc/jail.conf .
+.It Fl h
+Resolve the
+.Va host.hostname
+parameter (or
+.Va hostname )
+and add all IP addresses returned by the resolver
+to the list of addresses for this jail.
+This is equivalent to the
+.Va ip_hostname
+parameter.
+.It Fl i
+Output (only) the jail identifier of the newly created jail(s).
+This implies the
+.Fl q
+option.
+.It Fl J Ar jid_file
+Write a
+.Ar jid_file
+file, containing the parameters used to start the jail.
+.It Fl l
+Run commands in a clean environment.
+This is deprecated and is equivalent to the exec.clean parameter.
+.It Fl n Ar jailname
+Set the jail's name.
+This is deprecated and is equivalent to the
+.Va name
+parameter.
+.It Fl p Ar limit
+Limit the number of commands from
+.Va exec.*
+that can run simultaneously.
+.It Fl q
+Suppress the message printed whenever a jail is created, modified or removed.
+Only error messages will be printed.
+.It Fl R
+A variation of the
+.Fl r
+option that removes an existing jail without using the configuration file.
+No removal-related parameters for this jail will be used \(em the jail will
+simply be removed.
+.It Fl s Ar securelevel
+Set the
+.Va kern.securelevel
+MIB entry to the specified value inside the newly created jail.
+This is deprecated and is equivalent to the
+.Va securelevel
+parameter.
+.It Fl u Ar username
+The user name from host environment as whom jailed commands should run.
+This is deprecated and is equivalent to the
+.Va exec.jail_user
+and
+.Va exec.system_jail_user
+parameters.
+.It Fl U Ar username
+The user name from the jailed environment as whom jailed commands should run.
+This is deprecated and is equivalent to the
+.Va exec.jail_user
+parameter.
+.It Fl v
+Print a message on every operation, such as running commands and
+mounting filesystems.
+.It Fl d
+This is deprecated and is equivalent to the
+.Va allow.dying
+parameter, which is also deprecated.
+It used to allow making changes to a
+.Va dying
+jail.
+Now such jails are always replaced when a new jail is created with the same
+.Va jid
+or
+.Va name .
+.El
+.Pp
+If no arguments are given after the options, the operation (except
+remove) will be performed on all jails specified in the
+.Xr jail.conf 5
+file.
+A single argument of a jail name will operate only on the specified jail.
+The
+.Fl r
+and
+.Fl R
+options can also remove running jails that aren't in the
+.Xr jail.conf 5
+file, specified by name or jid.
+.Pp
+An argument of
+.Dq *
+is a wildcard that will operate on all jails, regardless of whether
+they appear in
+.Xr jail.conf 5 ;
+this is the surest way for
+.Fl r
+to remove all jails.
+If hierarchical jails exist, a partial-matching wildcard definition may
+be specified.
+For example, an argument of
+.Dq foo.*
+would apply to jails with names like
+.Dq foo.bar
+and
+.Dq foo.bar.baz .
+.Pp
+A jail may also be specified via parameters directly on the command line in
+.Dq name=value
+form, ignoring the contents of
+.Xr jail.conf 5 .
+For backward compatibility, the command line may also have four fixed
+parameters, without names:
+.Ar path ,
+.Ar hostname ,
+.Ar ip ,
+and
+.Ar command .
+.Ss Jail Parameters
+Parameters in the
+.Xr jail.conf 5
+file, or on the command line, are generally of the form
+.Dq name=value .
+Some parameters are boolean, and do not have a value but are set by the
+name alone with or without a
+.Dq no
+prefix, e.g.
+.Va persist
+or
+.Va nopersist .
+They can also be given the values
+.Dq true
+and
+.Dq false .
+Other parameters may have more than one value, specified as a
+comma-separated list, or with
+.Dq +=
+in the configuration file (see
+.Xr jail.conf 5
+for details).
+List-based parameters may also be specified multiple times on the command
+line, i.e.,
+.Dq name=value1,value2
+and
+.Dq name=value1 name=value2
+are equivalent for such parameters.
+.Pp
+The
+.Nm
+utility recognizes two classes of parameters.
+There are the true jail
+parameters that are passed to the kernel when the jail is created,
+which can be seen with
+.Xr jls 8 ,
+and can (usually) be changed with
+.Dq Nm Fl m .
+Then there are pseudo-parameters that are only used by
+.Nm
+itself.
+.Pp
+Jails have a set of core parameters, and kernel modules can add their own
+jail parameters.
+The current set of available parameters can be retrieved via
+.Dq Nm sysctl Fl d Va security.jail.param .
+Any parameters not set will be given default values, often based on the
+current environment.
+The core parameters are:
+.Bl -tag -width indent
+.It Va jid
+The jail identifier.
+This will be assigned automatically to a new jail (or can be explicitly
+set), and can be used to identify the jail for later modification, or
+for such commands as
+.Xr jls 8
+or
+.Xr jexec 8 .
+.It Va name
+The jail name.
+This is an arbitrary string that identifies a jail (except it may not
+contain a
+.Sq \&. ) .
+Like the
+.Va jid ,
+it can be passed to later
+.Nm
+commands, or to
+.Xr jls 8
+or
+.Xr jexec 8 .
+If no
+.Va name
+is supplied, a default is assumed that is the same as the
+.Va jid .
+The
+.Va name
+parameter is implied by the
+.Xr jail.conf 5
+file format, and need not be explicitly set when using the configuration
+file.
+.It Va path
+The directory which is to be the root of the jail.
+Any commands run inside the jail, either by
+.Nm
+or from
+.Xr jexec 8 ,
+are run from this directory.
+.It Va ip4.addr
+A list of IPv4 addresses assigned to the jail.
+If this is set, the jail is restricted to using only these addresses.
+Any attempts to use other addresses fail, and attempts to use wildcard
+addresses silently use the jailed address instead.
+For IPv4 the first address given will be used as the source address
+when source address selection on unbound sockets cannot find a better
+match.
+It is only possible to start multiple jails with the same IP address
+if none of the jails has more than this single overlapping IP address
+assigned to itself.
+.It Va ip4.saddrsel
+A boolean option to change the formerly mentioned behaviour and disable
+IPv4 source address selection for the jail in favour of the primary
+IPv4 address of the jail.
+Source address selection is enabled by default for all jails and the
+.Va ip4.nosaddrsel
+setting of a parent jail is not inherited for any child jails.
+.It Va ip4
+Control the availability of IPv4 addresses.
+Possible values are
+.Dq inherit
+to allow unrestricted access to all system addresses,
+.Dq new
+to restrict addresses via
+.Va ip4.addr ,
+and
+.Dq disable
+to stop the jail from using IPv4 entirely.
+Setting the
+.Va ip4.addr
+parameter implies a value of
+.Dq new .
+.It Va ip6.addr , Va ip6.saddrsel , Va ip6
+A set of IPv6 options for the jail, the counterparts to
+.Va ip4.addr ,
+.Va ip4.saddrsel
+and
+.Va ip4
+above.
+.It Va vnet
+Create the jail with its own virtual network stack,
+with its own network interfaces, addresses, routing table, etc.
+The kernel must have been compiled with the
+.Sy VIMAGE option
+for this to be available.
+Possible values are
+.Dq inherit
+to use the system network stack, possibly with restricted IP addresses,
+and
+.Dq new
+to create a new network stack.
+.It Va host.hostname
+The hostname of the jail.
+Other similar parameters are
+.Va host.domainname ,
+.Va host.hostuuid
+and
+.Va host.hostid .
+.It Va host
+Set the origin of hostname and related information.
+Possible values are
+.Dq inherit
+to use the system information and
+.Dq new
+for the jail to use the information from the above fields.
+Setting any of the above fields implies a value of
+.Dq new .
+.It Va securelevel
+The value of the jail's
+.Va kern.securelevel
+sysctl.
+A jail never has a lower securelevel than its parent system, but by
+setting this parameter it may have a higher one.
+If the system securelevel is changed, any jail securelevels will be at
+least as secure.
+.It Va devfs_ruleset
+The number of the devfs ruleset that is enforced for mounting devfs in
+this jail.
+A value of zero (default) means no ruleset is enforced.
+Descendant jails inherit the parent jail's devfs ruleset enforcement.
+Mounting devfs inside a jail is possible only if the
+.Va allow.mount
+and
+.Va allow.mount.devfs
+permissions are effective and
+.Va enforce_statfs
+is set to a value lower than 2.
+Devfs rules and rulesets cannot be viewed or modified from inside a jail.
+.Pp
+NOTE: It is important that only appropriate device nodes in devfs be
+exposed to a jail; access to disk devices in the jail may permit processes
+in the jail to bypass the jail sandboxing by modifying files outside of
+the jail.
+See
+.Xr devfs 8
+for information on how to use devfs rules to limit access to entries
+in the per-jail devfs.
+A simple devfs ruleset for jails is available as ruleset #4 in
+.Pa /etc/defaults/devfs.rules .
+.It Va children.max
+The number of child jails allowed to be created by this jail (or by
+other jails under this jail).
+This limit is zero by default, indicating the jail is not allowed to
+create child jails.
+See the
+.Sx "Hierarchical Jails"
+section for more information.
+.It Va children.cur
+The number of descendants of this jail, including its own child jails
+and any jails created under them.
+.It Va enforce_statfs
+This determines what information processes in a jail are able to get
+about mount points.
+It affects the behaviour of the following syscalls:
+.Xr statfs 2 ,
+.Xr fstatfs 2 ,
+.Xr getfsstat 2 ,
+and
+.Xr fhstatfs 2
+(as well as similar compatibility syscalls).
+When set to 0, all mount points are available without any restrictions.
+When set to 1, only mount points below the jail's chroot directory are
+visible.
+In addition to that, the path to the jail's chroot directory is removed
+from the front of their pathnames.
+When set to 2 (default), above syscalls can operate only on a mount-point
+where the jail's chroot directory is located.
+.It Va persist
+Setting this boolean parameter allows a jail to exist without any
+processes.
+Normally, a command is run as part of jail creation, and then the jail
+is destroyed as its last process exits.
+A new jail must have either the
+.Va persist
+parameter or
+.Va exec.start
+or
+.Va command
+pseudo-parameter set.
+.It Va cpuset.id
+The ID of the cpuset associated with this jail (read-only).
+.It Va dying
+This is true if the jail is in the process of shutting down (read-only).
+.It Va parent
+The
+.Va jid
+of the parent of this jail, or zero if this is a top-level jail
+(read-only).
+.It Va osrelease
+The string for the jail's
+.Va kern.osrelease
+sysctl and uname -r.
+.It Va osreldate
+The number for the jail's
+.Va kern.osreldate
+and uname -K.
+.It Va meta , Va env
+An arbitrary string associated with the jail.
+Its maximum buffer size is controlled by the global
+.Va security.jail.meta_maxbufsize
+sysctl, which can only be adjusted by the non-jailed root user.
+While the
+.Va meta
+is hidden from the jail, the
+.Va env
+is readable through the
+.Va security.jail.env
+sysctl.
+.Pp
+Each buffer can be treated as a set of key=value\\n strings.
+In order to add or replace a specific key the
+.Va meta.keyname=value
+or
+.Va env.keyname=value
+parameter notations must be used.
+While
+.Va meta.keyname=
+or
+.Va env.keyname=
+reset the value to an empty string, the
+.Va meta.keyname
+or
+.Va env.keyname
+notations, without the equal sign, remove the given key.
+Respectively, the same
+.Va meta.keyname
+or
+.Va env.keyname
+notations are used to query a specific key while reading jail parameters
+using such commands as
+.Xr jls 8 .
+Multiple keys can be queried or modified with a single command.
+.It Va allow.*
+Some restrictions of the jail environment may be set on a per-jail
+basis.
+With the exception of
+.Va allow.set_hostname
+and
+.Va allow.reserved_ports ,
+these boolean parameters are off by default.
+.Bl -tag -width indent
+.It Va allow.set_hostname
+The jail's hostname may be changed via
+.Xr hostname 1
+or
+.Xr sethostname 3 .
+.It Va allow.sysvipc
+A process within the jail has access to System V IPC primitives.
+This is deprecated in favor of the per-module parameters (see below).
+When this parameter is set, it is equivalent to setting
+.Va sysvmsg ,
+.Va sysvsem ,
+and
+.Va sysvshm
+all to
+.Dq inherit .
+.It Va allow.raw_sockets
+The jail root is allowed to create raw sockets.
+Setting this parameter allows utilities like
+.Xr ping 8
+and
+.Xr traceroute 8
+to operate inside the jail.
+If this is set, the source IP addresses are enforced to comply
+with the IP address bound to the jail, regardless of whether or not
+the
+.Dv IP_HDRINCL
+flag has been set on the socket.
+Since raw sockets can be used to configure and interact with various
+network subsystems, extra caution should be used where privileged access
+to jails is given out to untrusted parties.
+.It Va allow.chflags
+Normally, privileged users inside a jail are treated as unprivileged by
+.Xr chflags 2 .
+When this parameter is set, such users are treated as privileged, and
+may manipulate system file flags subject to the usual constraints on
+.Va kern.securelevel .
+.It Va allow.mount
+privileged users inside the jail will be able to mount and unmount file
+system types marked as jail-friendly.
+The
+.Xr lsvfs 1
+command can be used to find file system types available for mount from
+within a jail.
+This permission is effective only if
+.Va enforce_statfs
+is set to a value lower than 2.
+.It Va allow.mount.devfs
+privileged users inside the jail will be able to mount and unmount the
+devfs file system.
+This permission is effective only together with
+.Va allow.mount
+and only when
+.Va enforce_statfs
+is set to a value lower than 2.
+The devfs ruleset should be restricted from the default by using the
+.Va devfs_ruleset
+option.
+.It Va allow.quotas
+The jail root may administer quotas on the jail's filesystem(s).
+This includes filesystems that the jail may share with other jails or
+with non-jailed parts of the system.
+.It Va allow.read_msgbuf
+Jailed users may read the kernel message buffer.
+If the
+.Va security.bsd.unprivileged_read_msgbuf
+MIB entry is zero, this will be restricted to the root user.
+.It Va allow.socket_af
+Sockets within a jail are normally restricted to IPv4, IPv6, local
+(UNIX), and route.
+This allows access to other protocol stacks that have not had jail
+functionality added to them.
+.It Va allow.mlock
+Locking or unlocking physical pages in memory are normally not available
+within a jail.
+When this parameter is set, users may
+.Xr mlock 2
+or
+.Xr munlock 2
+memory subject to
+.Va security.bsd.unprivileged_mlock
+and resource limits.
+.It Va allow.nfsd
+The
+.Xr mountd 8 ,
+.Xr nfsd 8 ,
+.Xr nfsuserd 8 ,
+.Xr gssd 8
+and
+.Xr rpc.tlsservd 8
+daemons are permitted to run inside a properly configured vnet-enabled jail.
+The jail's root must be a file system mount point and
+.Va enforce_statfs
+must not be set to 0, so that
+.Xr mountd 8
+can export file systems visible within the jail.
+.Va enforce_statfs
+must be set to 1 if file systems mounted under the
+jail's file system need to be exported by
+.Xr mount 8 .
+For exporting only the jail's file system, a setting of 2
+is sufficient.
+If the kernel configuration does not include the
+.Sy NFSD
+option,
+.Pa nfsd.ko
+must be loaded outside of the jails.
+This is normally done by adding
+.Dq nfsd
+to
+.Va kld_list
+in the
+.Xr rc.conf 5
+file outside of the jails.
+Similarily, if the
+.Xr gssd 8
+is to be run in a jail, either the kernel
+.Sy KGSSAPI
+option needs to be specified or
+.Dq kgssapi
+and
+.Dq kgssapi_krb5
+need to be in
+.Va kld_list
+in the
+.Xr rc.conf 5
+file outside of the jails.
+.It Va allow.reserved_ports
+The jail root may bind to ports lower than 1024.
+.It Va allow.unprivileged_parent_tampering
+Unprivileged processes in the jail's parent may tamper with processes of the
+same UID in the jail.
+This includes the ability to signal, debug, and
+.Xr cpuset 1
+processes that belong to the jail.
+.It Va allow.unprivileged_proc_debug
+Unprivileged processes in the jail may use debugging facilities.
+.It Va allow.suser
+The value of the jail's
+.Va security.bsd.suser_enabled
+sysctl.
+The super-user will be disabled automatically if its parent system has it
+disabled.
+The super-user is enabled by default.
+.It Va allow.extattr
+Allow privileged processes in the jail to manipulate filesystem extended
+attributes in the system namespace.
+.It Va allow.adjtime
+Allow privileged processes in the jail to slowly adjusting global operating
+system time.
+For example through utilities like
+.Xr ntpd 8 .
+.It Va allow.settime
+Allow privileged processes in the jail to set global operating system data
+and time.
+For example through utilities like
+.Xr date 1 .
+This permission includes also
+.Va allow.adjtime .
+.It Va allow.routing
+Allow privileged process in the non-VNET jail to modify the system routing
+table.
+.It Va allow.setaudit
+Allow privileged processes in the jail to set
+.Xr audit 4
+session state using
+.Xr setaudit 2
+and related system calls.
+This is useful, for example, for allowing a jailed
+.Xr sshd 8
+to set the audit user ID for an authenticated session.
+However, it gives jailed processes the ability to modify or disable audit
+session state, so should be configured with care.
+.El
+.El
+.Pp
+Kernel modules may add their own parameters, which only exist when the
+module is loaded.
+These are typically headed under a parameter named after the module,
+with values of
+.Dq inherit
+to give the jail full use of the module,
+.Dq new
+to encapsulate the jail in some module-specific way,
+and
+.Dq disable
+to make the module unavailable to the jail.
+There also may be other parameters to define jail behavior within the module.
+Module-specific parameters include:
+.Bl -tag -width indent
+.It Va allow.mount.fdescfs
+privileged users inside the jail will be able to mount and unmount the
+fdescfs file system.
+This permission is effective only together with
+.Va allow.mount
+and only when
+.Va enforce_statfs
+is set to a value lower than 2.
+.It Va allow.mount.fusefs
+privileged users inside the jail will be able to mount and unmount
+fuse-based file systems.
+This permission is effective only together with
+.Va allow.mount
+and only when
+.Va enforce_statfs
+is set to a value lower than 2.
+.It Va allow.mount.nullfs
+privileged users inside the jail will be able to mount and unmount the
+nullfs file system.
+This permission is effective only together with
+.Va allow.mount
+and only when
+.Va enforce_statfs
+is set to a value lower than 2.
+.It Va allow.mount.procfs
+privileged users inside the jail will be able to mount and unmount the
+procfs file system.
+This permission is effective only together with
+.Va allow.mount
+and only when
+.Va enforce_statfs
+is set to a value lower than 2.
+.It Va allow.mount.linprocfs
+privileged users inside the jail will be able to mount and unmount the
+linprocfs file system.
+This permission is effective only together with
+.Va allow.mount
+and only when
+.Va enforce_statfs
+is set to a value lower than 2.
+.It Va allow.mount.linsysfs
+privileged users inside the jail will be able to mount and unmount the
+linsysfs file system.
+This permission is effective only together with
+.Va allow.mount
+and only when
+.Va enforce_statfs
+is set to a value lower than 2.
+.It Va allow.mount.tmpfs
+privileged users inside the jail will be able to mount and unmount the
+tmpfs file system.
+This permission is effective only together with
+.Va allow.mount
+and only when
+.Va enforce_statfs
+is set to a value lower than 2.
+.It Va allow.mount.zfs
+privileged users inside the jail will be able to mount and unmount the
+ZFS file system.
+This permission is effective only together with
+.Va allow.mount
+and only when
+.Va enforce_statfs
+is set to a value lower than 2.
+See
+.Xr zfs-jail 8
+for information on how to configure the ZFS filesystem to operate from
+within a jail.
+.It Va allow.vmm
+The jail may access
+.Xr vmm 4 .
+This flag is only available when the
+.Xr vmm 4
+kernel module is loaded.
+.It Va linux
+Determine how a jail's Linux emulation environment appears.
+A value of
+.Dq inherit
+will keep the same environment, and
+.Dq new
+will give the jail its own environment (still originally inherited when
+the jail is created).
+.It Va linux.osname , linux.osrelease , linux.oss_version
+The Linux OS name, OS release, and OSS version associated with this jail.
+.It Va sysvmsg
+Allow access to SYSV IPC message primitives.
+If set to
+.Dq inherit ,
+all IPC objects on the system are visible to this jail, whether they
+were created by the jail itself, the base system, or other jails.
+If set to
+.Dq new ,
+the jail will have its own key namespace, and can only see the objects
+that it has created;
+the system (or parent jail) has access to the jail's objects, but not to
+its keys.
+If set to
+.Dq disable ,
+the jail cannot perform any sysvmsg-related system calls.
+.It Va sysvsem, sysvshm
+Allow access to SYSV IPC semaphore and shared memory primitives, in the
+same manner as
+.Va sysvmsg .
+.It Va zfs.mount_snapshot
+When set to 1, jailed users may access the contents of ZFS snapshots
+under the filesystem's
+.Pa .zfs
+directory.
+If
+.Va allow.mount.zfs
+is set, the snapshots may also be mounted.
+.El
+.Pp
+There are pseudo-parameters that are not passed to the kernel, but are
+used by
+.Nm
+to set up the jail environment, often by running specified commands
+when jails are created or removed.
+The
+.Va exec.*
+command parameters are
+.Xr sh 1
+command lines that are run in either the system or jail environment.
+They may be given multiple values, which would run the specified
+commands in sequence.
+All commands must succeed (return a zero exit status), or the jail will
+not be created or removed, as appropriate.
+.Pp
+The following variables are added to the environment:
+.Bl -tag -width indent -offset indent
+.It Ev JID
+The
+.Va jid ,
+or jail identifier.
+.It Ev JNAME
+The
+.Va name
+of the jail.
+.It Ev JPATH
+The
+.Va path
+of the jail.
+.El
+.Pp
+The pseudo-parameters are:
+.Bl -tag -width indent
+.It Va exec.prepare
+Command(s) to run in the system environment to prepare a jail for creation.
+These commands are executed before assigning IP addresses and mounting
+filesystems, so they may be used to create a new jail filesystem if it does
+not already exist.
+.It Va exec.prestart
+Command(s) to run in the system environment before a jail is created.
+.It Va exec.created
+Command(s) to run in the system environment right after a jail has been
+created, but before commands (or services) get executed in the jail.
+.It Va exec.start
+Command(s) to run in the jail environment when a jail is created.
+A typical command to run is
+.Dq sh /etc/rc .
+.It Va command
+A synonym for
+.Va exec.start
+for use when specifying a jail directly on the command line.
+Unlike other parameters whose value is a single string,
+.Va command
+uses the remainder of the
+.Nm
+command line as its own arguments.
+.It Va exec.poststart
+Command(s) to run in the system environment after a jail is created,
+and after any
+.Va exec.start
+commands have completed.
+.It Va exec.prestop
+Command(s) to run in the system environment before a jail is removed.
+.It Va exec.stop
+Command(s) to run in the jail environment before a jail is removed,
+and after any
+.Va exec.prestop
+commands have completed.
+A typical command to run is
+.Dq sh /etc/rc.shutdown jail .
+.It Va exec.poststop
+Command(s) to run in the system environment after a jail is removed.
+.It Va exec.release
+Command(s) to run in the system environment after all other actions are done.
+These commands are executed after unmounting filesystems and removing IP
+addresses, so they may be used to remove a jail filesystem if it is no longer
+needed.
+.It Va exec.clean
+Run commands in a clean environment.
+The environment is discarded except for
+.Ev HOME , SHELL , TERM
+and
+.Ev USER .
+.Ev HOME
+and
+.Ev SHELL
+are set to the target login's default values.
+.Ev USER
+is set to the target login.
+.Ev TERM
+is imported from the current environment.
+.Ev PATH
+is set to "/bin:/usr/bin".
+The environment variables from the login class capability database for the
+target login are also set.
+.Ev JID ,
+.Ev JNAME ,
+and
+.Ev JPATH
+are not set.
+If a user is specified (as with
+.Va exec.jail_user ) ,
+commands are run from that (possibly jailed) user's directory.
+.It Va exec.jail_user
+The user to run commands as, when running in the jail environment.
+The default is to run the commands as the current user.
+.It Va exec.system_jail_user
+This boolean option looks for the
+.Va exec.jail_user
+in the system
+.Xr passwd 5
+file, instead of in the jail's file.
+.It Va exec.system_user
+The user to run commands as, when running in the system environment.
+The default is to run the commands as the current user.
+.It Va exec.timeout
+The maximum amount of time to wait for a command to complete, in
+seconds.
+If a command is still running after this timeout has passed,
+the jail will not be created or removed, as appropriate.
+.It Va exec.consolelog
+A file to direct command output (stdout and stderr) to.
+.It Va exec.fib
+The FIB (routing table) to set when running commands inside the jail.
+.It Va stop.timeout
+The maximum amount of time to wait for a jail's processes to exit
+after sending them a
+.Dv SIGTERM
+signal (which happens after the
+.Va exec.stop
+commands have completed).
+After this many seconds have passed, the jail will be removed, which
+will kill any remaining processes.
+If this is set to zero, no
+.Dv SIGTERM
+is sent and the jail is immediately removed.
+The default is 10 seconds.
+.It Va interface
+A network interface to add the jail's IP addresses
+.Va ( ip4.addr
+and
+.Va ip6.addr )
+to.
+An alias for each address will be added to the interface before the
+jail is created, and will be removed from the interface after the
+jail is removed.
+.It Va ip4.addr
+In addition to the IP addresses that are passed to the kernel, an
+interface, netmask and additional parameters (as supported by
+.Xr ifconfig 8 )
+may also be specified, in the form
+.Dq Ar interface Ns | Ns Ar ip-address Ns / Ns Ar netmask param ... .
+If an interface is given before the IP address, an alias for the address
+will be added to that interface, as it is with the
+.Va interface
+parameter.
+If a netmask in either dotted-quad or CIDR form is given
+after an IP address, it will be used when adding the IP alias.
+If additional parameters are specified then they will also be used when
+adding the IP alias.
+.It Va ip6.addr
+In addition to the IP addresses that are passed to the kernel,
+an interface, prefix and additional parameters (as supported by
+.Xr ifconfig 8 )
+may also be specified, in the form
+.Dq Ar interface Ns | Ns Ar ip-address Ns / Ns Ar prefix param ... .
+.It Va vnet.interface
+A comma separated list of network interfaces to give to a vnet-enabled jail
+after is it created.
+The interfaces will automatically be released when the jail is removed.
+.It Va zfs.dataset
+A list of ZFS datasets to be attached to the jail.
+This requires
+.Va allow.mount.zfs
+to be set.
+See
+.Xr zfs-jail 8
+for information on how to configure a ZFS dataset to be operated from
+within a jail.
+.It Va ip_hostname
+Resolve the
+.Va host.hostname
+parameter and add all IP addresses returned by the resolver
+to the list of addresses
+.Po Va ip4.addr
+or
+.Va ip6.addr Pc
+for this jail.
+This may affect default address selection for outgoing IPv4 connections
+from jails.
+The address first returned by the resolver for each address family
+will be used as the primary address.
+.It Va mount
+A filesystem to mount before creating the jail (and to unmount after
+removing it), given as a single
+.Xr fstab 5
+line.
+.It Va mount.fstab
+An
+.Xr fstab 5
+format file containing filesystems to mount before creating a jail.
+.It Va mount.devfs
+Mount a
+.Xr devfs 4
+filesystem on the chrooted
+.Pa /dev
+directory, and apply the ruleset in the
+.Va devfs_ruleset
+parameter (or a default of ruleset 4: devfsrules_jail)
+to restrict the devices visible inside the jail.
+.It Va mount.fdescfs
+Mount a
+.Xr fdescfs 4
+filesystem on the chrooted
+.Pa /dev/fd
+directory.
+.It Va mount.procfs
+Mount a
+.Xr procfs 4
+filesystem on the chrooted
+.Pa /proc
+directory.
+.It Va allow.dying
+This is deprecated and has no effect.
+It used to allow making changes to a
+.Va dying
+jail.
+Now such jails are always replaced when a new jail is created with the same
+.Va jid
+or
+.Va name .
+.It Va depend
+Specify a jail (or jails) that this jail depends on.
+When this jail is to be created, any jail(s) it depends on must already exist.
+If not, they will be created automatically, up to the completion of the last
+.Va exec.poststart
+command, before any action will taken to create this jail.
+When jails are removed the opposite is true:
+this jail will be removed, up to the last
+.Va exec.poststop
+command, before any jail(s) it depends on are stopped.
+.El
+.Sh EXAMPLES
+Jails are typically set up using one of two philosophies: either to
+constrain a specific application (possibly running with privilege), or
+to create a
+.Dq "virtual system image"
+running a variety of daemons and services.
+In both cases, a fairly complete file system install of
+.Fx
+is
+required, so as to provide the necessary command line tools, daemons,
+libraries, application configuration files, etc.
+However, for a virtual server configuration, a fair amount of
+additional work is required so as to replace the
+.Dq boot
+process.
+This manual page documents the configuration steps necessary to support
+either of these steps, although the configuration steps may need to be
+refined based on local requirements.
+.Ss "Setting up a Jail Directory Tree"
+To set up a jail directory tree containing an entire
+.Fx
+distribution, the following
+.Xr sh 1
+command script can be used:
+.Bd -literal -offset indent
+D=/here/is/the/jail
+cd /usr/src
+mkdir -p $D
+make world DESTDIR=$D
+make distribution DESTDIR=$D
+.Ed
+.Pp
+In many cases this example would put far more in the jail than needed.
+In the other extreme case a jail might contain only one file:
+the executable to be run in the jail.
+.Pp
+We recommend experimentation, and caution that it is a lot easier to
+start with a
+.Dq fat
+jail and remove things until it stops working,
+than it is to start with a
+.Dq thin
+jail and add things until it works.
+.Ss "Setting Up a Jail"
+Do what was described in
+.Sx "Setting Up a Jail Directory Tree"
+to build the jail directory tree.
+For the sake of this example, we will
+assume you built it in
+.Pa /data/jail/testjail ,
+for a jail named
+.Dq testjail .
+Substitute below as needed with your
+own directory, IP address, and hostname.
+.Ss "Setting up the Host Environment"
+First, set up the real system's environment to be
+.Dq jail-friendly .
+For consistency, we will refer to the parent box as the
+.Dq "host environment" ,
+and to the jailed virtual machine as the
+.Dq "jail environment" .
+Since jails are implemented using IP aliases, one of the first things to do
+is to disable IP services on the host system that listen on all local
+IP addresses for a service.
+If a network service is present in the host environment that binds all
+available IP addresses rather than specific IP addresses, it may service
+requests sent to jail IP addresses if the jail did not bind the port.
+This means changing
+.Xr inetd 8
+to only listen on the
+appropriate IP address, and so forth.
+Add the following to
+.Pa /etc/rc.conf
+in the host environment:
+.Bd -literal -offset indent
+sendmail_enable="NO"
+inetd_flags="-wW -a 192.0.2.23"
+rpcbind_enable="NO"
+.Ed
+.Pp
+.Li 192.0.2.23
+is the native IP address for the host system, in this example.
+Daemons that run out of
+.Xr inetd 8
+can be easily configured to use only the specified host IP address.
+Other daemons
+will need to be manually configured \(em for some this is possible through
+.Xr rc.conf 5
+flags entries; for others it is necessary to modify per-application
+configuration files, or to recompile the application.
+The following frequently deployed services must have their individual
+configuration files modified to limit the application to listening
+to a specific IP address:
+.Pp
+To configure
+.Xr sshd 8 ,
+it is necessary to modify
+.Pa /etc/ssh/sshd_config .
+.Pp
+To configure
+.Xr sendmail 8 ,
+it is necessary to modify
+.Pa /etc/mail/sendmail.cf .
+.Pp
+In addition, a number of services must be recompiled in order to run
+them in the host environment.
+This includes most applications providing services using
+.Xr rpc 3 ,
+such as
+.Xr rpcbind 8 ,
+.Xr nfsd 8 ,
+and
+.Xr mountd 8 .
+In general, applications for which it is not possible to specify which
+IP address to bind should not be run in the host environment unless they
+should also service requests sent to jail IP addresses.
+Attempting to serve
+NFS from the host environment may also cause confusion, and cannot be
+easily reconfigured to use only specific IPs, as some NFS services are
+hosted directly from the kernel.
+Any third-party network software running
+in the host environment should also be checked and configured so that it
+does not bind all IP addresses, which would result in those services also
+appearing to be offered by the jail environments.
+.Pp
+Once
+these daemons have been disabled or fixed in the host environment, it is
+best to reboot so that all daemons are in a known state, to reduce the
+potential for confusion later (such as finding that when you send mail
+to a jail, and its sendmail is down, the mail is delivered to the host,
+etc.).
+.Ss "Configuring the Jail"
+Start any jail for the first time without configuring the network
+interface so that you can clean it up a little and set up accounts.
+As
+with any machine (virtual or not), you will need to set a root password, time
+zone, etc.
+Some of these steps apply only if you intend to run a full virtual server
+inside the jail; others apply both for constraining a particular application
+or for running a virtual server.
+.Pp
+Start a shell in the jail:
+.Bd -literal -offset indent
+jail -c path=/data/jail/testjail mount.devfs \\
+ host.hostname=testhostname ip4.addr=192.0.2.100 \\
+ command=/bin/sh
+.Ed
+.Pp
+Assuming no errors, you will end up with a shell prompt within the jail.
+You can now run
+.Xr bsdconfig 8
+and do the post-install configuration to set various configuration options,
+or perform these actions manually by editing
+.Pa /etc/rc.conf ,
+etc.
+.Pp
+.Bl -bullet -offset indent -compact
+.It
+Configure
+.Pa /etc/resolv.conf
+so that name resolution within the jail will work correctly.
+.It
+Run
+.Xr newaliases 1
+to quell
+.Xr sendmail 8
+warnings.
+.It
+Set a root password, probably different from the real host system.
+.It
+Set the timezone.
+.It
+Add accounts for users in the jail environment.
+.It
+Install any packages the environment requires.
+.El
+.Pp
+You may also want to perform any package-specific configuration (web servers,
+SSH servers, etc), patch up
+.Pa /etc/syslog.conf
+so it logs as you would like, etc.
+If you are not using a virtual server, you may wish to modify
+.Xr syslogd 8
+in the host environment to listen on the syslog socket in the jail
+environment; in this example, the syslog socket would be stored in
+.Pa /data/jail/testjail/var/run/log .
+.Pp
+Exit from the shell, and the jail will be shut down.
+.Ss "Starting the Jail"
+You are now ready to restart the jail and bring up the environment with
+all of its daemons and other programs.
+Create an entry for the jail in
+.Pa /etc/jail.conf :
+.Bd -literal -offset indent
+testjail {
+ path = /tmp/jail/testjail;
+ mount.devfs;
+ host.hostname = testhostname;
+ ip4.addr = 192.0.2.100;
+ interface = em0;
+ exec.start = "/bin/sh /etc/rc";
+ exec.stop = "/bin/sh /etc/rc.shutdown jail";
+}
+.Ed
+.Pp
+To start a virtual server environment,
+.Pa /etc/rc
+is run to launch various daemons and services, and
+.Pa /etc/rc.shutdown
+is run to shut them down when the jail is removed.
+If you are running a single application in the jail,
+substitute the command used to start the application for
+.Dq /bin/sh /etc/rc ;
+there may be some script available to cleanly shut down the application,
+or it may be sufficient to go without a stop command, and have
+.Nm
+send
+.Dv SIGTERM
+to the application.
+.Pp
+Start the jail by running:
+.Bd -literal -offset indent
+jail -c testjail
+.Ed
+.Pp
+A few warnings may be produced; however, it should all work properly.
+You should be able to see
+.Xr inetd 8 ,
+.Xr syslogd 8 ,
+and other processes running within the jail using
+.Xr ps 1 ,
+with the
+.Ql J
+flag appearing beside jailed processes.
+To see an active list of jails, use
+.Xr jls 8 .
+If
+.Xr sshd 8
+is enabled in the jail environment, you should be able to
+.Xr ssh 1
+to the hostname or IP address of the jailed environment, and log
+in using the accounts you created previously.
+.Pp
+It is possible to have jails started at boot time.
+Please refer to the
+.Dq jail_*
+variables in
+.Xr rc.conf 5
+for more information.
+.Ss "Managing the Jail"
+Normal machine shutdown commands, such as
+.Xr halt 8 ,
+.Xr reboot 8 ,
+and
+.Xr shutdown 8 ,
+cannot be used successfully within the jail.
+To kill all processes from within a jail, you may use one of the
+following commands, depending on what you want to accomplish:
+.Bd -literal -offset indent
+kill -TERM -1
+kill -KILL -1
+.Ed
+.Pp
+This will send the
+.Dv SIGTERM
+or
+.Dv SIGKILL
+signals to all processes in the jail \(em be careful not to run this from
+the host environment!
+Once all of the jail's processes have died, unless the jail was created
+with the
+.Va persist
+parameter, the jail will be removed.
+Depending on
+the intended use of the jail, you may also want to run
+.Pa /etc/rc.shutdown
+from within the jail.
+.Pp
+To shut down the jail from the outside, simply remove it with:
+.Bd -literal -offset indent
+jail -r
+.Ed
+.Pp
+which will run any commands specified by
+.Va exec.stop ,
+and then send
+.Dv SIGTERM
+and eventually
+.Dv SIGKILL
+to any remaining jailed processes.
+.Pp
+The
+.Pa /proc/ Ns Ar pid Ns Pa /status
+file contains, as its last field, the name of the jail in which the
+process runs, or
+.Dq Li -
+to indicate that the process is not running within a jail.
+The
+.Xr ps 1
+command also shows a
+.Ql J
+flag for processes in a jail.
+.Pp
+You can also list/kill processes based on their jail ID.
+To show processes and their jail ID, use the following command:
+.Pp
+.Dl "ps ax -o pid,jid,args"
+.Pp
+To show and then kill processes in jail number 3 use the following commands:
+.Bd -literal -offset indent
+pgrep -lfj 3
+pkill -j 3
+.Ed
+or:
+.Pp
+.Dl "killall -j 3"
+.Ss "Jails and File Systems"
+It is not possible to
+.Xr mount 8
+or
+.Xr umount 8
+any file system inside a jail unless the file system is marked
+jail-friendly, the jail's
+.Va allow.mount
+parameter is set, and the jail's
+.Va enforce_statfs
+parameter is lower than 2.
+.Pp
+Multiple jails sharing the same file system can influence each other.
+For example, a user in one jail can fill the file system,
+leaving no space for processes in the other jail.
+Trying to use
+.Xr quota 1
+to prevent this will not work either, as the file system quotas
+are not aware of jails but only look at the user and group IDs.
+This means the same user ID in two jails share a single file
+system quota.
+One would need to use one file system per jail to make this work.
+.Ss "Sysctl MIB Entries"
+The read-only entry
+.Va security.jail.jailed
+can be used to determine if a process is running inside a jail (value
+is one) or not (value is zero).
+.Pp
+The variable
+.Va security.jail.jail_max_af_ips
+determines how may address per address family a jail may have.
+The default is 255.
+.Pp
+Some MIB variables have per-jail settings.
+Changes to these variables by a jailed process do not affect the host
+environment, only the jail environment.
+These variables are
+.Va kern.securelevel ,
+.Va security.bsd.suser_enabled ,
+.Va kern.hostname ,
+.Va kern.domainname ,
+.Va kern.hostid ,
+and
+.Va kern.hostuuid .
+.Ss "Hierarchical Jails"
+By setting a jail's
+.Va children.max
+parameter, processes within a jail may be able to create jails of their own.
+These child jails are kept in a hierarchy, with jails only able to see and/or
+modify the jails they created (or those jails' children).
+Each jail has a read-only
+.Va parent
+parameter, containing the
+.Va jid
+of the jail that created it; a
+.Va jid
+of 0 indicates the jail is a child of the current jail (or is a top-level
+jail if the current process isn't jailed).
+.Pp
+Jailed processes are not allowed to confer greater permissions than they
+themselves are given, e.g., if a jail is created with
+.Va allow.nomount ,
+it is not able to create a jail with
+.Va allow.mount
+set.
+Similarly, such restrictions as
+.Va ip4.addr
+and
+.Va securelevel
+may not be bypassed in child jails.
+.Pp
+A child jail may in turn create its own child jails if its own
+.Va children.max
+parameter is set (remember it is zero by default).
+These jails are visible to and can be modified by their parent and all
+ancestors.
+.Pp
+Jail names reflect this hierarchy, with a full name being an MIB-type string
+separated by dots.
+For example, if a base system process creates a jail
+.Dq foo ,
+and a process under that jail creates another jail
+.Dq bar ,
+then the second jail will be seen as
+.Dq foo.bar
+in the base system (though it is only seen as
+.Dq bar
+to any processes inside jail
+.Dq foo ) .
+Jids on the other hand exist in a single space, and each jail must have a
+unique jid.
+.Pp
+Like the names, a child jail's
+.Va path
+appears relative to its creator's own
+.Va path .
+This is by virtue of the child jail being created in the chrooted
+environment of the first jail.
+.Sh SEE ALSO
+.Xr date 1 ,
+.Xr killall 1 ,
+.Xr lsvfs 1 ,
+.Xr newaliases 1 ,
+.Xr pgrep 1 ,
+.Xr pkill 1 ,
+.Xr ps 1 ,
+.Xr quota 1 ,
+.Xr adjtime 2 ,
+.Xr clock_settime 2 ,
+.Xr jail_set 2 ,
+.Xr ntp_adjtime 2 ,
+.Xr devfs 4 ,
+.Xr fdescfs 4 ,
+.Xr linprocfs 4 ,
+.Xr linsysfs 4 ,
+.Xr procfs 4 ,
+.Xr vmm 4 ,
+.Xr jail.conf 5 ,
+.Xr rc.conf 5 ,
+.Xr sysctl.conf 5 ,
+.Xr bsdconfig 8 ,
+.Xr chroot 8 ,
+.Xr devfs 8 ,
+.Xr halt 8 ,
+.Xr ifconfig 8 ,
+.Xr inetd 8 ,
+.Xr jexec 8 ,
+.Xr jls 8 ,
+.Xr mount 8 ,
+.Xr mountd 8 ,
+.Xr nfsd 8 ,
+.Xr ntpd 8 ,
+.Xr reboot 8 ,
+.Xr rpcbind 8 ,
+.Xr sendmail 8 ,
+.Xr shutdown 8 ,
+.Xr sysctl 8 ,
+.Xr syslogd 8 ,
+.Xr umount 8 ,
+.Xr zfs-jail 8 ,
+.Xr extattr 9
+.Sh HISTORY
+The
+.Nm
+utility appeared in
+.Fx 4.0 .
+Hierarchical/extensible jails were introduced in
+.Fx 8.0 .
+The configuration file was introduced 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 Robert Watson
+wrote the extended documentation, found a few bugs, added
+a few new features, and cleaned up the userland jail environment.
+.Pp
+.An Bjoern A. Zeeb
+added multi-IP jail support for IPv4 and IPv6 based on a patch
+originally done by
+.An Pawel Jakub Dawidek
+for IPv4.
+.Pp
+.An James Gritton
+added the extensible jail parameters, hierarchical jails,
+and the configuration file.
+.Sh BUGS
+It might be a good idea to add an
+address alias flag such that daemons listening on all IPs
+.Pq Dv INADDR_ANY
+will not bind on that address, which would facilitate building a safe
+host environment such that host daemons do not impose on services offered
+from within jails.
+Currently, the simplest answer is to minimize services
+offered on the host, possibly limiting it to services offered from
+.Xr inetd 8
+which is easily configurable.
+.Sh NOTES
+Great care should be taken when managing directories visible within the jail.
+For example, if a jailed process has its current working directory set to a
+directory that is moved out of the jail's chroot, then the process may gain
+access to the file space outside of the jail.
+It is recommended that directories always be copied, rather than moved, out
+of a jail.
+.Pp
+In addition, there are several ways in which an unprivileged user
+outside the jail can cooperate with a privileged user inside the jail
+and thereby obtain elevated privileges in the host environment.
+Most of these attacks can be mitigated by ensuring that the jail root
+is not accessible to unprivileged users in the host environment.
+Regardless, as a general rule, untrusted users with privileged access
+to a jail should not be given access to the host environment.
diff --git a/usr.sbin/jail/jail.c b/usr.sbin/jail/jail.c
new file mode 100644
index 000000000000..46cabf76ae11
--- /dev/null
+++ b/usr.sbin/jail/jail.c
@@ -0,0 +1,1071 @@
+/*-
+ * SPDX-License-Identifier: BSD-2-Clause
+ *
+ * Copyright (c) 1999 Poul-Henning Kamp.
+ * Copyright (c) 2009-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.
+ */
+
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <sys/socket.h>
+#include <sys/sysctl.h>
+
+#include <arpa/inet.h>
+#include <netinet/in.h>
+
+#include <err.h>
+#include <errno.h>
+#include <stdarg.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+
+#include "jailp.h"
+
+#define JP_RDTUN(jp) (((jp)->jp_ctltype & CTLFLAG_RDTUN) == CTLFLAG_RDTUN)
+
+struct permspec {
+ const char *name;
+ enum intparam ipnum;
+ int rev;
+};
+
+int iflag;
+int note_remove;
+int verbose;
+const char *separator = "\t";
+
+static void clear_persist(struct cfjail *j);
+static int update_jail(struct cfjail *j);
+static int rdtun_params(struct cfjail *j, int dofail);
+static void running_jid(struct cfjail *j);
+static void jail_quoted_warnx(const struct cfjail *j, const char *name_msg,
+ const char *noname_msg);
+static int jailparam_set_note(const struct cfjail *j, struct jailparam *jp,
+ unsigned njp, int flags);
+static void print_jail(FILE *fp, struct cfjail *j, int oldcl, int running);
+static void print_param(FILE *fp, const struct cfparam *p, int sep, int doname);
+static void show_jails(void);
+static void quoted_print(FILE *fp, char *str);
+static void usage(void) __dead2;
+
+static struct permspec perm_sysctl[] = {
+ { "security.jail.set_hostname_allowed", KP_ALLOW_SET_HOSTNAME, 0 },
+ { "security.jail.sysvipc_allowed", KP_ALLOW_SYSVIPC, 0 },
+ { "security.jail.allow_raw_sockets", KP_ALLOW_RAW_SOCKETS, 0 },
+ { "security.jail.chflags_allowed", KP_ALLOW_CHFLAGS, 0 },
+ { "security.jail.mount_allowed", KP_ALLOW_MOUNT, 0 },
+ { "security.jail.socket_unixiproute_only", KP_ALLOW_SOCKET_AF, 1 },
+};
+
+static const enum intparam startcommands[] = {
+ IP__NULL,
+ IP_EXEC_PREPARE,
+#ifdef INET
+ IP__IP4_IFADDR,
+#endif
+#ifdef INET6
+ IP__IP6_IFADDR,
+#endif
+ IP_MOUNT,
+ IP__MOUNT_FROM_FSTAB,
+ IP_MOUNT_DEVFS,
+ IP_MOUNT_FDESCFS,
+ IP_MOUNT_PROCFS,
+ IP_EXEC_PRESTART,
+ IP__OP,
+ IP_EXEC_CREATED,
+ IP_ZFS_DATASET,
+ IP_VNET_INTERFACE,
+ IP_EXEC_START,
+ IP_COMMAND,
+ IP_EXEC_POSTSTART,
+ IP__NULL
+};
+
+static const enum intparam stopcommands[] = {
+ IP__NULL,
+ IP_EXEC_PRESTOP,
+ IP_EXEC_STOP,
+ IP_STOP_TIMEOUT,
+ IP__OP,
+ IP_EXEC_POSTSTOP,
+ IP_MOUNT_PROCFS,
+ IP_MOUNT_FDESCFS,
+ IP_MOUNT_DEVFS,
+ IP__MOUNT_FROM_FSTAB,
+ IP_MOUNT,
+#ifdef INET6
+ IP__IP6_IFADDR,
+#endif
+#ifdef INET
+ IP__IP4_IFADDR,
+#endif
+ IP_EXEC_RELEASE,
+ IP__NULL
+};
+
+static const enum intparam cleancommands[] = {
+ IP__NULL,
+ IP_EXEC_POSTSTOP,
+ IP_MOUNT_PROCFS,
+ IP_MOUNT_FDESCFS,
+ IP_MOUNT_DEVFS,
+ IP__MOUNT_FROM_FSTAB,
+ IP_MOUNT,
+#ifdef INET6
+ IP__IP6_IFADDR,
+#endif
+#ifdef INET
+ IP__IP4_IFADDR,
+#endif
+ IP_EXEC_RELEASE,
+ IP__NULL
+};
+
+static const struct {
+ const char *name;
+ enum intparam param;
+} listparams[] = {
+#ifdef INET
+ { "ip4.addr", KP_IP4_ADDR },
+#endif
+#ifdef INET6
+ { "ip6.addr", KP_IP6_ADDR },
+#endif
+ { "vnet.interface", IP_VNET_INTERFACE },
+ { "zfs.dataset", IP_ZFS_DATASET },
+};
+
+int
+main(int argc, char **argv)
+{
+ struct stat st;
+ FILE *jfp;
+ struct cfjail *j;
+ char *JidFile;
+ const char *cfname;
+ size_t sysvallen;
+ unsigned op, pi;
+ int ch, docf, dying_warned, error, i, oldcl, sysval;
+ int dflag, eflag, Rflag;
+#if defined(INET) || defined(INET6)
+ char *cs, *ncs;
+#endif
+#if defined(INET) && defined(INET6)
+ struct in6_addr addr6;
+#endif
+
+ op = 0;
+ dflag = eflag = Rflag = 0;
+ docf = 1;
+ cfname = CONF_FILE;
+ JidFile = NULL;
+
+ while ((ch = getopt(argc, argv, "cCde:f:hiJ:lmn:p:qrRs:u:U:v")) != -1) {
+ switch (ch) {
+ case 'c':
+ op |= JF_START;
+ break;
+ case 'C':
+ op |= JF_CLEANUP;
+ break;
+ case 'd':
+ dflag = 1;
+ break;
+ case 'e':
+ eflag = 1;
+ separator = optarg;
+ break;
+ case 'f':
+ cfname = optarg;
+ break;
+ case 'h':
+#if defined(INET) || defined(INET6)
+ add_param(NULL, NULL, IP_IP_HOSTNAME, NULL);
+#endif
+ docf = 0;
+ break;
+ case 'i':
+ iflag = 1;
+ verbose = -1;
+ break;
+ case 'J':
+ JidFile = optarg;
+ break;
+ case 'l':
+ add_param(NULL, NULL, IP_EXEC_CLEAN, NULL);
+ docf = 0;
+ break;
+ case 'm':
+ op |= JF_SET;
+ break;
+ case 'n':
+ add_param(NULL, NULL, KP_NAME, optarg);
+ docf = 0;
+ break;
+ case 'p':
+ paralimit = strtol(optarg, NULL, 10);
+ if (paralimit == 0)
+ paralimit = -1;
+ break;
+ case 'q':
+ verbose = -1;
+ break;
+ case 'r':
+ op |= JF_STOP;
+ break;
+ case 'R':
+ op |= JF_STOP;
+ Rflag = 1;
+ break;
+ case 's':
+ add_param(NULL, NULL, KP_SECURELEVEL, optarg);
+ docf = 0;
+ break;
+ case 'u':
+ add_param(NULL, NULL, IP_EXEC_JAIL_USER, optarg);
+ add_param(NULL, NULL, IP_EXEC_SYSTEM_JAIL_USER, NULL);
+ docf = 0;
+ break;
+ case 'U':
+ add_param(NULL, NULL, IP_EXEC_JAIL_USER, optarg);
+ add_param(NULL, NULL, IP_EXEC_SYSTEM_JAIL_USER,
+ "false");
+ docf = 0;
+ break;
+ case 'v':
+ verbose = 1;
+ break;
+ default:
+ usage();
+ }
+ }
+ argc -= optind;
+ argv += optind;
+
+ if (eflag) {
+ /* Just print list of all configured non-wildcard jails */
+ if (op || argc > 0)
+ usage();
+ load_config(cfname);
+ show_jails();
+ exit(0);
+ }
+
+ /* Find out which of the command line styles this is. */
+ oldcl = 0;
+ if (!op) {
+ /* Old-style command line with four fixed parameters */
+ if (argc < 4 || argv[0][0] != '/')
+ usage();
+ op = JF_START;
+ docf = 0;
+ oldcl = 1;
+ add_param(NULL, NULL, KP_PATH, argv[0]);
+ add_param(NULL, NULL, KP_HOST_HOSTNAME, argv[1]);
+#if defined(INET) || defined(INET6)
+ if (argv[2][0] != '\0') {
+ for (cs = argv[2];; cs = ncs + 1) {
+ ncs = strchr(cs, ',');
+ if (ncs)
+ *ncs = '\0';
+ add_param(NULL, NULL,
+#if defined(INET) && defined(INET6)
+ inet_pton(AF_INET6, cs, &addr6) == 1
+ ? KP_IP6_ADDR : KP_IP4_ADDR,
+#elif defined(INET)
+ KP_IP4_ADDR,
+#elif defined(INET6)
+ KP_IP6_ADDR,
+#endif
+ cs);
+ if (!ncs)
+ break;
+ }
+ }
+#endif
+ for (i = 3; i < argc; i++)
+ add_param(NULL, NULL, IP_COMMAND, argv[i]);
+ /* Emulate the defaults from security.jail.* sysctls. */
+ sysvallen = sizeof(sysval);
+ if (sysctlbyname("security.jail.jailed", &sysval, &sysvallen,
+ NULL, 0) == 0 && sysval == 0) {
+ for (pi = 0; pi < sizeof(perm_sysctl) /
+ sizeof(perm_sysctl[0]); pi++) {
+ sysvallen = sizeof(sysval);
+ if (sysctlbyname(perm_sysctl[pi].name,
+ &sysval, &sysvallen, NULL, 0) == 0)
+ add_param(NULL, NULL,
+ perm_sysctl[pi].ipnum,
+ (sysval ? 1 : 0) ^
+ perm_sysctl[pi].rev
+ ? NULL : "false");
+ }
+ }
+ } else if (op == JF_STOP) {
+ /* Jail remove, perhaps using the config file */
+ if (!docf || argc == 0)
+ usage();
+ if (!Rflag)
+ for (i = 0; i < argc; i++)
+ if (strchr(argv[i], '='))
+ usage();
+ if ((docf = !Rflag &&
+ (!strcmp(cfname, "-") || stat(cfname, &st) == 0)))
+ 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 */
+ if (Rflag || (op & JF_CLEANUP))
+ usage();
+ docf = 0;
+ for (i = 0; i < argc; i++) {
+ size_t l;
+
+ if (!strncmp(argv[i], "command", 7) &&
+ (argv[i][7] == '\0' || argv[i][7] == '=')) {
+ if (argv[i][7] == '=')
+ add_param(NULL, NULL, IP_COMMAND,
+ argv[i] + 8);
+ for (i++; i < argc; i++)
+ add_param(NULL, NULL, IP_COMMAND,
+ argv[i]);
+ continue;
+ }
+
+ /*
+ * Is this parameter a list?
+ */
+ for (l = 0; l < nitems(listparams); l++) {
+ size_t len;
+
+ len = strlen(listparams[l].name);
+ if (strncmp(argv[i], listparams[l].name, len) ||
+ argv[i][len] != '=')
+ continue;
+
+ for (cs = argv[i] + len + 1;; cs = ncs + 1) {
+ ncs = strchr(cs, ',');
+ if (ncs)
+ *ncs = '\0';
+ add_param(NULL, NULL,
+ listparams[l].param, cs);
+ if (!ncs)
+ break;
+ }
+ break;
+ }
+ if (l == nitems(listparams))
+ add_param(NULL, NULL, 0, argv[i]);
+ }
+ } else {
+ /* From the config file, perhaps with a specified jail */
+ if (Rflag || !docf)
+ usage();
+ load_config(cfname);
+ }
+
+ /* Find out which jails will be run. */
+ dep_setup(docf);
+ error = 0;
+ if ((op & JF_OP_MASK) == JF_STOP) {
+ for (i = 0; i < argc; i++)
+ if (start_state(argv[i], docf, op, Rflag) < 0)
+ error = 1;
+ } else {
+ if (start_state(argv[0], docf, op, 0) < 0)
+ exit(1);
+ }
+
+ jfp = NULL;
+ if (JidFile != NULL) {
+ jfp = fopen(JidFile, "w");
+ if (jfp == NULL)
+ err(1, "open %s", JidFile);
+ setlinebuf(jfp);
+ }
+ setlinebuf(stdout);
+
+ /*
+ * The main loop: Get an available jail and perform the required
+ * operation on it. When that is done, the jail may be finished,
+ * or it may go back for the next step.
+ */
+ dying_warned = 0;
+ while ((j = next_jail()))
+ {
+ if (j->flags & JF_FAILED) {
+ error = 1;
+ if (j->comparam == NULL) {
+ dep_done(j, 0);
+ continue;
+ }
+ }
+ if (!(j->flags & JF_PARAMS))
+ {
+ j->flags |= JF_PARAMS;
+ if (dflag)
+ add_param(j, NULL, IP_ALLOW_DYING, NULL);
+ if (check_intparams(j) < 0)
+ continue;
+ if ((j->flags & (JF_START | JF_SET)) &&
+ import_params(j) < 0)
+ continue;
+ }
+ if (j->intparams[IP_ALLOW_DYING] && !dying_warned) {
+ warnx("%s", "the 'allow.dying' parameter and '-d' flag "
+ "are deprecated and have no effect.");
+ dying_warned = 1;
+ }
+ if (!j->jid)
+ running_jid(j);
+ if (finish_command(j))
+ continue;
+
+ switch (j->flags & JF_OP_MASK) {
+ /*
+ * These operations just turn into a different op
+ * depending on the jail's current status.
+ */
+ case JF_START_SET:
+ j->flags = j->jid < 0
+ ? (j->flags & JF_CLEANUP) | JF_START : JF_SET;
+ break;
+ case JF_SET_RESTART:
+ if (j->jid < 0 && !(j->flags & JF_CLEANUP)) {
+ jail_quoted_warnx(j, "not found",
+ "no jail specified");
+ failed(j);
+ continue;
+ }
+ j->flags = rdtun_params(j, 0)
+ ? (j->flags & JF_CLEANUP) | JF_RESTART : JF_SET;
+ if (j->flags == JF_RESTART)
+ dep_reset(j);
+ break;
+ case JF_START_SET_RESTART:
+ j->flags = j->jid < 0 ? JF_START : rdtun_params(j, 0)
+ ? (j->flags & JF_CLEANUP) | JF_RESTART : JF_SET;
+ if (j->flags == JF_RESTART)
+ dep_reset(j);
+ }
+
+ switch (j->flags & JF_OP_MASK) {
+ case JF_START:
+ if (j->comparam == NULL) {
+ if (j->jid > 0 &&
+ !(j->flags & (JF_DEPEND | JF_WILD))) {
+ jail_quoted_warnx(j, "already exists",
+ NULL);
+ failed(j);
+ continue;
+ }
+ if (dep_check(j))
+ continue;
+ if (j->jid > 0)
+ goto jail_create_done;
+ if (j->flags & JF_CLEANUP) {
+ j->flags |= JF_STOP;
+ j->comparam = cleancommands;
+ } else
+ j->comparam = startcommands;
+ j->comparam = startcommands;
+ j->comstring = NULL;
+ }
+ if (next_command(j))
+ continue;
+ if (j->flags & JF_STOP)
+ goto jail_remove_done;
+ jail_create_done:
+ clear_persist(j);
+ if (jfp != NULL)
+ print_jail(jfp, j, oldcl, 1);
+ dep_done(j, 0);
+ break;
+
+ case JF_SET:
+ if (j->jid < 0 && !(j->flags & JF_DEPEND)) {
+ jail_quoted_warnx(j, "not found",
+ "no jail specified");
+ failed(j);
+ continue;
+ }
+ if (dep_check(j))
+ continue;
+ if (!(j->flags & JF_DEPEND)) {
+ if (rdtun_params(j, 1) < 0 ||
+ update_jail(j) < 0)
+ continue;
+ if (verbose >= 0 && (j->name || verbose > 0))
+ jail_note(j, "updated\n");
+ }
+ dep_done(j, 0);
+ break;
+
+ case JF_STOP:
+ case JF_RESTART:
+ if (j->comparam == NULL) {
+ if (dep_check(j))
+ continue;
+ if (j->flags & JF_CLEANUP) {
+ j->comparam = j->jid < 0
+ ? cleancommands : stopcommands;
+ } else if (j->jid < 0) {
+ if (!(j->flags & (JF_DEPEND|JF_WILD))) {
+ if (verbose >= 0)
+ jail_quoted_warnx(j,
+ "not found", NULL);
+ failed(j);
+ }
+ goto jail_remove_done;
+ }
+ else
+ j->comparam = stopcommands;
+ j->comstring = NULL;
+ } else if ((j->flags & JF_FAILED) && j->jid > 0)
+ goto jail_remove_done;
+ if (next_command(j))
+ continue;
+ jail_remove_done:
+ dep_done(j, 0);
+ if ((j->flags & (JF_START | JF_FAILED)) == JF_START) {
+ j->comparam = NULL;
+ j->flags &= ~(JF_STOP | JF_CLEANUP);
+ dep_reset(j);
+ requeue(j, j->ndeps ? &depend : &ready);
+ }
+ break;
+ }
+ }
+
+ if (jfp != NULL)
+ fclose(jfp);
+ exit(error);
+}
+
+/*
+ * Mark a jail's failure for future handling.
+ */
+void
+failed(struct cfjail *j)
+{
+ j->flags |= JF_FAILED;
+ TAILQ_REMOVE(j->queue, j, tq);
+ TAILQ_INSERT_HEAD(&ready, j, tq);
+ j->queue = &ready;
+}
+
+/*
+ * Exit slightly more gracefully when out of memory.
+ */
+void *
+emalloc(size_t size)
+{
+ void *p;
+
+ p = malloc(size);
+ if (!p)
+ err(1, "malloc");
+ return p;
+}
+
+void *
+erealloc(void *ptr, size_t size)
+{
+ void *p;
+
+ p = realloc(ptr, size);
+ if (!p)
+ err(1, "malloc");
+ return p;
+}
+
+char *
+estrdup(const char *str)
+{
+ char *ns;
+
+ ns = strdup(str);
+ if (!ns)
+ err(1, "malloc");
+ return ns;
+}
+
+/*
+ * Print a message including an optional jail name.
+ */
+void
+jail_note(const struct cfjail *j, const char *fmt, ...)
+{
+ va_list ap, tap;
+ char *cs;
+ size_t len;
+
+ va_start(ap, fmt);
+ va_copy(tap, ap);
+ len = vsnprintf(NULL, 0, fmt, tap);
+ va_end(tap);
+ cs = alloca(len + 1);
+ (void)vsnprintf(cs, len + 1, fmt, ap);
+ va_end(ap);
+ if (j->name)
+ printf("%s: %s", j->name, cs);
+ else
+ printf("%s", cs);
+}
+
+/*
+ * Print a warning message including an optional jail name.
+ */
+void
+jail_warnx(const struct cfjail *j, const char *fmt, ...)
+{
+ va_list ap, tap;
+ char *cs;
+ size_t len;
+
+ va_start(ap, fmt);
+ va_copy(tap, ap);
+ len = vsnprintf(NULL, 0, fmt, tap);
+ va_end(tap);
+ cs = alloca(len + 1);
+ (void)vsnprintf(cs, len + 1, fmt, ap);
+ va_end(ap);
+ if (j->name)
+ warnx("%s: %s", j->name, cs);
+ else
+ warnx("%s", cs);
+}
+
+/*
+ * Create a new jail.
+ */
+int
+create_jail(struct cfjail *j)
+{
+ struct stat st;
+ struct jailparam *jp, *setparams, *sjp;
+ const char *path;
+ int dopersist, ns;
+
+ /*
+ * Check the jail's path, with a better error message than jail_set
+ * gives.
+ */
+ if ((path = string_param(j->intparams[KP_PATH]))) {
+ if (j->name != NULL && path[0] != '/') {
+ jail_warnx(j, "path %s: not an absolute pathname",
+ path);
+ return -1;
+ }
+ if (stat(path, &st) < 0) {
+ jail_warnx(j, "path %s: %s", path, strerror(errno));
+ return -1;
+ }
+ if (!S_ISDIR(st.st_mode)) {
+ jail_warnx(j, "path %s: %s", path, strerror(ENOTDIR));
+ return -1;
+ }
+ }
+
+ /*
+ * Copy all the parameters, except that "persist" is always set when
+ * there are commands to run later.
+ */
+ dopersist = !bool_param(j->intparams[KP_PERSIST]) &&
+ (j->intparams[IP_EXEC_START] || j->intparams[IP_COMMAND] ||
+ j->intparams[IP_EXEC_POSTSTART]);
+ sjp = setparams =
+ alloca((j->njp + dopersist) * sizeof(struct jailparam));
+ if (dopersist && jailparam_init(sjp++, "persist") < 0) {
+ jail_warnx(j, "%s", jail_errmsg);
+ return -1;
+ }
+ for (jp = j->jp; jp < j->jp + j->njp; jp++)
+ if (!dopersist || !equalopts(jp->jp_name, "persist"))
+ *sjp++ = *jp;
+ ns = sjp - setparams;
+
+ j->jid = jailparam_set_note(j, setparams, ns, JAIL_CREATE);
+ if (j->jid < 0) {
+ jail_warnx(j, "%s", jail_errmsg);
+ failed(j);
+ }
+ if (dopersist) {
+ jailparam_free(setparams, 1);
+ if (j->jid > 0)
+ j->flags |= JF_PERSIST;
+ }
+ return j->jid;
+}
+
+/*
+ * Remove a temporarily set "persist" parameter.
+ */
+static void
+clear_persist(struct cfjail *j)
+{
+ struct iovec jiov[4];
+ int jid;
+
+ if (!(j->flags & JF_PERSIST))
+ return;
+ j->flags &= ~JF_PERSIST;
+ jiov[0].iov_base = __DECONST(char *, "jid");
+ jiov[0].iov_len = sizeof("jid");
+ jiov[1].iov_base = &j->jid;
+ jiov[1].iov_len = sizeof(j->jid);
+ jiov[2].iov_base = __DECONST(char *, "nopersist");
+ jiov[2].iov_len = sizeof("nopersist");
+ jiov[3].iov_base = NULL;
+ jiov[3].iov_len = 0;
+ jid = jail_set(jiov, 4, JAIL_UPDATE);
+ if (verbose > 0)
+ jail_note(j, "jail_set(JAIL_UPDATE) jid=%d nopersist%s%s\n",
+ j->jid, jid < 0 ? ": " : "",
+ jid < 0 ? strerror(errno) : "");
+}
+
+/*
+ * Set a jail's parameters.
+ */
+static int
+update_jail(struct cfjail *j)
+{
+ struct jailparam *jp, *setparams, *sjp;
+ int ns, jid;
+
+ ns = 0;
+ for (jp = j->jp; jp < j->jp + j->njp; jp++)
+ if (!JP_RDTUN(jp))
+ ns++;
+ if (ns == 0)
+ return 0;
+ sjp = setparams = alloca(++ns * sizeof(struct jailparam));
+ if (jailparam_init(sjp, "jid") < 0 ||
+ jailparam_import_raw(sjp, &j->jid, sizeof j->jid) < 0) {
+ jail_warnx(j, "%s", jail_errmsg);
+ failed(j);
+ return -1;
+ }
+ for (jp = j->jp; jp < j->jp + j->njp; jp++)
+ if (!JP_RDTUN(jp))
+ *++sjp = *jp;
+
+ jid = jailparam_set_note(j, setparams, ns, JAIL_UPDATE);
+ if (jid < 0) {
+ jail_warnx(j, "%s", jail_errmsg);
+ failed(j);
+ }
+ jailparam_free(setparams, 1);
+ return jid;
+}
+
+/*
+ * Return if a jail set would change any create-only parameters.
+ */
+static int
+rdtun_params(struct cfjail *j, int dofail)
+{
+ struct jailparam *jp, *rtparams, *rtjp;
+ const void *jp_value;
+ size_t jp_valuelen;
+ int nrt, rval, bool_true;
+
+ if (j->flags & JF_RDTUN)
+ return 0;
+ j->flags |= JF_RDTUN;
+ nrt = 0;
+ for (jp = j->jp; jp < j->jp + j->njp; jp++)
+ if (JP_RDTUN(jp) && strcmp(jp->jp_name, "jid"))
+ nrt++;
+ if (nrt == 0)
+ return 0;
+ rtjp = rtparams = alloca(++nrt * sizeof(struct jailparam));
+ if (jailparam_init(rtjp, "jid") < 0 ||
+ jailparam_import_raw(rtjp, &j->jid, sizeof j->jid) < 0) {
+ jail_warnx(j, "%s", jail_errmsg);
+ exit(1);
+ }
+ for (jp = j->jp; jp < j->jp + j->njp; jp++)
+ if (JP_RDTUN(jp) && strcmp(jp->jp_name, "jid")) {
+ *++rtjp = *jp;
+ rtjp->jp_value = NULL;
+ }
+ rval = 0;
+ if (jailparam_get(rtparams, nrt, 0) > 0) {
+ rtjp = rtparams + 1;
+ for (jp = j->jp; rtjp < rtparams + nrt; jp++) {
+ if (JP_RDTUN(jp) && strcmp(jp->jp_name, "jid")) {
+ jp_value = jp->jp_value;
+ jp_valuelen = jp->jp_valuelen;
+ if (jp_value == NULL && jp_valuelen > 0) {
+ if (jp->jp_flags & (JP_BOOL |
+ JP_NOBOOL | JP_JAILSYS)) {
+ bool_true = 1;
+ jp_value = &bool_true;
+ jp_valuelen = sizeof(bool_true);
+ } else if ((jp->jp_ctltype & CTLTYPE) ==
+ CTLTYPE_STRING)
+ jp_value = "";
+ else
+ jp_valuelen = 0;
+ }
+ if (rtjp->jp_valuelen != jp_valuelen ||
+ (CTLTYPE_STRING ? strncmp(rtjp->jp_value,
+ jp_value, jp_valuelen)
+ : memcmp(rtjp->jp_value, jp_value,
+ jp_valuelen))) {
+ if (dofail) {
+ jail_warnx(j, "%s cannot be "
+ "changed after creation",
+ jp->jp_name);
+ failed(j);
+ rval = -1;
+ } else
+ rval = 1;
+ break;
+ }
+ rtjp++;
+ }
+ }
+ }
+ for (rtjp = rtparams + 1; rtjp < rtparams + nrt; rtjp++)
+ rtjp->jp_name = NULL;
+ jailparam_free(rtparams, nrt);
+ return rval;
+}
+
+/*
+ * Get the jail's jid if it is running.
+ */
+static void
+running_jid(struct cfjail *j)
+{
+ struct iovec jiov[2];
+ const char *pval;
+ char *ep;
+ int jid;
+
+ if ((pval = string_param(j->intparams[KP_JID]))) {
+ if (!(jid = strtol(pval, &ep, 10)) || *ep) {
+ j->jid = -1;
+ return;
+ }
+ jiov[0].iov_base = __DECONST(char *, "jid");
+ jiov[0].iov_len = sizeof("jid");
+ jiov[1].iov_base = &jid;
+ jiov[1].iov_len = sizeof(jid);
+ } else if ((pval = string_param(j->intparams[KP_NAME]))) {
+ jiov[0].iov_base = __DECONST(char *, "name");
+ jiov[0].iov_len = sizeof("name");
+ jiov[1].iov_len = strlen(pval) + 1;
+ jiov[1].iov_base = alloca(jiov[1].iov_len);
+ strcpy(jiov[1].iov_base, pval);
+ } else {
+ j->jid = -1;
+ return;
+ }
+
+ j->jid = jail_get(jiov, 2, 0);
+ if (j->jid > 0 && j->intparams[KP_JID] == NULL) {
+ char jidstr[16];
+
+ (void)snprintf(jidstr, sizeof(jidstr), "%d", j->jid);
+ add_param(j, NULL, KP_JID, jidstr);
+ }
+}
+
+static void
+jail_quoted_warnx(const struct cfjail *j, const char *name_msg,
+ const char *noname_msg)
+{
+ const char *pval;
+
+ if ((pval = j->name) || (pval = string_param(j->intparams[KP_JID])) ||
+ (pval = string_param(j->intparams[KP_NAME])))
+ warnx("\"%s\" %s", pval, name_msg);
+ else
+ warnx("%s", noname_msg);
+}
+
+/*
+ * Set jail parameters and possibly print them out.
+ */
+static int
+jailparam_set_note(const struct cfjail *j, struct jailparam *jp, unsigned njp,
+ int flags)
+{
+ char *value;
+ int jid;
+ unsigned i;
+
+ jid = jailparam_set(jp, njp, flags);
+ if (verbose > 0) {
+ jail_note(j, "jail_set(%s)",
+ (flags & (JAIL_CREATE | JAIL_UPDATE)) == JAIL_CREATE
+ ? "JAIL_CREATE" : "JAIL_UPDATE");
+ for (i = 0; i < njp; i++) {
+ printf(" %s", jp[i].jp_name);
+ if (jp[i].jp_value == NULL)
+ continue;
+ putchar('=');
+ value = jailparam_export(jp + i);
+ if (value == NULL)
+ err(1, "jailparam_export");
+ quoted_print(stdout, value);
+ free(value);
+ }
+ if (jid < 0)
+ printf(": %s", strerror(errno));
+ printf("\n");
+ }
+ return jid;
+}
+
+/*
+ * Print a jail record.
+ */
+static void
+print_jail(FILE *fp, struct cfjail *j, int oldcl, int running)
+{
+ struct cfparam *p;
+ int printsep;
+
+ if (oldcl) {
+ if (running)
+ fprintf(fp, "%d%s", j->jid, separator);
+ print_param(fp, j->intparams[KP_PATH], ',', 0);
+ fputs(separator, fp);
+ print_param(fp, j->intparams[KP_HOST_HOSTNAME], ',', 0);
+ fputs(separator, fp);
+#ifdef INET
+ print_param(fp, j->intparams[KP_IP4_ADDR], ',', 0);
+#ifdef INET6
+ if (j->intparams[KP_IP4_ADDR] &&
+ !TAILQ_EMPTY(&j->intparams[KP_IP4_ADDR]->val) &&
+ j->intparams[KP_IP6_ADDR] &&
+ !TAILQ_EMPTY(&j->intparams[KP_IP6_ADDR]->val))
+ putc(',', fp);
+#endif
+#endif
+#ifdef INET6
+ print_param(fp, j->intparams[KP_IP6_ADDR], ',', 0);
+#endif
+ fputs(separator, fp);
+ print_param(fp, j->intparams[IP_COMMAND], ' ', 0);
+ } else {
+ printsep = 0;
+ if (running) {
+ fprintf(fp, "jid=%d", j->jid);
+ printsep = 1;
+ }
+ TAILQ_FOREACH(p, &j->params, tq)
+ if (strcmp(p->name, "jid")) {
+ if (printsep)
+ fputs(separator, fp);
+ else
+ printsep = 1;
+ print_param(fp, p, ',', 1);
+ }
+ }
+ putc('\n', fp);
+}
+
+/*
+ * Exhibit list of all configured non-wildcard jails
+ */
+static void
+show_jails(void)
+{
+ struct cfjail *j;
+
+ TAILQ_FOREACH(j, &cfjails, tq)
+ print_jail(stdout, j, 0, 0);
+}
+
+/*
+ * Print a parameter value, or a name=value pair.
+ */
+static void
+print_param(FILE *fp, const struct cfparam *p, int sep, int doname)
+{
+ const struct cfstring *s, *ts;
+
+ if (doname)
+ fputs(p->name, fp);
+ if (p == NULL || TAILQ_EMPTY(&p->val))
+ return;
+ if (doname)
+ putc('=', fp);
+ TAILQ_FOREACH_SAFE(s, &p->val, tq, ts) {
+ quoted_print(fp, s->s);
+ if (ts != NULL)
+ putc(sep, fp);
+ }
+}
+
+/*
+ * Print a string with quotes around spaces.
+ */
+static void
+quoted_print(FILE *fp, char *str)
+{
+ int c, qc;
+ char *p = str;
+
+ qc = !*p ? '"'
+ : strchr(p, '\'') ? '"'
+ : strchr(p, '"') ? '\''
+ : strchr(p, ' ') || strchr(p, '\t') ? '"'
+ : 0;
+ if (qc)
+ putc(qc, fp);
+ while ((c = *p++)) {
+ if (c == '\\' || c == qc)
+ putc('\\', fp);
+ putc(c, fp);
+ }
+ if (qc)
+ putc(qc, fp);
+}
+
+static void
+usage(void)
+{
+
+ (void)fprintf(stderr,
+ "usage: jail [-dhilqv] [-J jid_file] [-u username] [-U username]\n"
+ " -[cmr] param=value ... [command=command ...]\n"
+ " jail [-dqv] [-f file] -[cmr] [jail]\n"
+ " jail [-qv] [-f file] -[rR] ['*' | jail ...]\n"
+ " jail [-dhilqv] [-J jid_file] [-u username] [-U username]\n"
+ " [-n jailname] [-s securelevel]\n"
+ " path hostname ip[,...] command ...\n"
+ " jail [-f file] -e separator\n");
+ exit(1);
+}
diff --git a/usr.sbin/jail/jail.conf.5 b/usr.sbin/jail/jail.conf.5
new file mode 100644
index 000000000000..627c38b0f8b6
--- /dev/null
+++ b/usr.sbin/jail/jail.conf.5
@@ -0,0 +1,275 @@
+.\"-
+.\" SPDX-License-Identifier: BSD-2-Clause
+.\"
+.\" 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.
+.\"
+.Dd September 21, 2024
+.Dt JAIL.CONF 5
+.Os
+.Sh NAME
+.Nm jail.conf
+.Nd configuration file for system jails
+.Sh DESCRIPTION
+The
+.Nm
+file consists of one or more jail definitions statements for use by the
+.Xr jail 8
+management program.
+A jail definition statement consists of a single word, the name of the jail,
+an opening curly brace, a list of at least two parameter assignments,
+and a closing curly brace.
+A parameter assignment consists of a single word, the parameter name,
+an equals sign, a value enclosed in double quotes, and a terminating semicolon.
+.Pp
+The syntax of a jail definition is as follows:
+.Bd -unfilled
+. Ar jailname Cm \&{
+.Bd -unfilled -offset indent -compact
+.Ar parameter Cm = Qq Ar value ;
+\&...
+.Ed
+.Cm \&}
+.Ed
+.Pp
+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 are not 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 (or files) in the configuration.
+The filename should be either absolute, or relative to the
+configuration file's directory.
+It cannot contain variable expansions, but may contain
+.Xr glob 3
+patterns.
+.Pp
+The included file must exist, though a filename glob may match zero or
+more files.
+This allows inclusion of any/all files in a directory, such as
+.Dq Pa /etc/jail.conf.d/*.conf ,
+or conditional inclusion of a single file, such as
+.Dq Pa jail.foo[.]conf .
+.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 2 ,
+.Xr jail 3 ,
+.Xr jail 3lua ,
+.Xr rc.conf 5 ,
+.Xr jail 8 ,
+.Xr jexec 8 ,
+.Xr jls 8 ,
+.Xr zfs-jail 8
+.Pp
+The
+.Dq Jails and Containers
+chapter of the
+.%B FreeBSD Handbook .
+.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/jaillex.l b/usr.sbin/jail/jaillex.l
new file mode 100644
index 000000000000..1dc61c1e9ab0
--- /dev/null
+++ b/usr.sbin/jail/jaillex.l
@@ -0,0 +1,218 @@
+%{
+/*-
+ * 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 <sys/cdefs.h>
+#include <err.h>
+#include <stddef.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include "jailp.h"
+#include "y.tab.h"
+
+#define YY_DECL int yylex(YYSTYPE *yylval, yyscan_t yyscanner)
+#define YY_EXTRA_TYPE struct cflex*
+
+extern YY_DECL;
+
+static ssize_t text2lval(size_t triml, size_t trimr, int tovar,
+ YYSTYPE *yylval, yyscan_t scanner);
+
+static int instr;
+%}
+
+%option noyywrap
+%option noinput
+%option nounput
+%option reentrant
+%option yylineno
+
+%start _ DQ
+
+%%
+
+ /* Whitespace or equivalent */
+<_>[ \t\r\n]+ instr = 0;
+<_>#.* instr = 0;
+<_>\/\/.* instr = 0;
+<_>\/\*([^*]|(\*+([^*\/])))*\*+\/ instr = 0;
+
+ /* Reserved tokens */
+<_>\+= {
+ instr = 0;
+ return PLEQ;
+ }
+<_>[,;={}] {
+ instr = 0;
+ return yytext[0];
+ }
+
+ /* Atomic (unquoted) strings */
+<_,DQ>[A-Za-z0-9_!%&()\-.:<>?@\[\]^`|~]+ |
+<_,DQ>\\(.|\n|[0-7]{1,3}|x[0-9A-Fa-f]{1,2}) |
+<_,DQ>[$*+/\\] {
+ (void)text2lval(0, 0, 0, yylval, yyscanner);
+ return instr ? STR1 : (instr = 1, STR);
+ }
+
+ /* Single and double quoted strings */
+<_>'([^\'\\]|\\(.|\n))*' {
+ (void)text2lval(1, 1, 0, yylval, yyscanner);
+ return instr ? STR1 : (instr = 1, STR);
+ }
+<_>\"([^"\\]|\\(.|\n))*\" |
+<DQ>[^\"$\\]([^"\\]|\\(.|\n))*\" {
+ size_t skip;
+ ssize_t atvar;
+
+ skip = yytext[0] == '"' ? 1 : 0;
+ atvar = text2lval(skip, 1, 1, yylval,
+ yyscanner);
+ if (atvar < 0)
+ BEGIN _;
+ else {
+ /*
+ * The string has a variable inside it.
+ * Go into DQ mode to get the variable
+ * and then the rest of the string.
+ */
+ BEGIN DQ;
+ yyless(atvar);
+ }
+ return instr ? STR1 : (instr = 1, STR);
+ }
+<DQ>\" BEGIN _;
+
+ /* Variables, single-word or bracketed */
+<_,DQ>$[A-Za-z_][A-Za-z_0-9]* {
+ (void)text2lval(1, 0, 0, yylval, yyscanner);
+ return instr ? VAR1 : (instr = 1, VAR);
+ }
+<_>$\{([^\n{}]|\\(.|\n))*\} |
+<DQ>$\{([^\n\"{}]|\\(.|\n))*\} {
+ (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",
+ yyextra->cfname, yylineno);
+ yyextra->error = 1;
+ }
+<_>'([^\n'\\]|\\.)* |
+<_>\"([^\n\"\\]|\\.)* {
+ warnx("%s line %d: unterminated string",
+ yyextra->cfname, yylineno);
+ yyextra->error = 1;
+ }
+<_>$\{([^\n{}]|\\.)* |
+<DQ>$\{([^\n\"{}]|\\.)* {
+ warnx("%s line %d: unterminated variable",
+ yyextra->cfname, yylineno);
+ yyextra->error = 1;
+ }
+
+ /* A hack because "<0>" rules aren't allowed */
+<_>. return yytext[0];
+.|\n {
+ BEGIN _;
+ yyless(0);
+ }
+
+%%
+
+/*
+ * 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, YYSTYPE *yylval,
+ yyscan_t scanner)
+{
+ char *d;
+ const char *s, *se;
+
+ 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 != '\\') {
+ if (tovar && *s == '$') {
+ *d = '\0';
+ return s - yytext;
+ }
+ *d = *s;
+ continue;
+ }
+ s++;
+ if (*s >= '0' && *s <= '7') {
+ *d = *s - '0';
+ if (s + 1 < se && s[1] >= '0' && s[1] <= '7') {
+ *d = 010 * *d + (*++s - '0');
+ if (s + 1 < se && s[1] >= '0' && s[1] <= '7')
+ *d = 010 * *d + (*++s - '0');
+ }
+ continue;
+ }
+ switch (*s) {
+ case 'a': *d = '\a'; break;
+ case 'b': *d = '\b'; break;
+ case 'f': *d = '\f'; break;
+ case 'n': *d = '\n'; break;
+ case 'r': *d = '\r'; break;
+ case 't': *d = '\t'; break;
+ case 'v': *d = '\v'; break;
+ default: *d = *s; break;
+ case '\n': d--; break;
+ case 'x':
+ *d = 0;
+ if (s + 1 >= se)
+ break;
+ if (s[1] >= '0' && s[1] <= '9')
+ *d = *++s - '0';
+ else if (s[1] >= 'A' && s[1] <= 'F')
+ *d = *++s + (0xA - 'A');
+ else if (s[1] >= 'a' && s[1] <= 'f')
+ *d = *++s + (0xa - 'a');
+ else
+ break;
+ if (s + 1 >= se)
+ break;
+ if (s[1] >= '0' && s[1] <= '9')
+ *d = *d * 0x10 + (*++s - '0');
+ else if (s[1] >= 'A' && s[1] <= 'F')
+ *d = *d * 0x10 + (*++s + (0xA - 'A'));
+ else if (s[1] >= 'a' && s[1] <= 'f')
+ *d = *d * 0x10 + (*++s + (0xa - 'a'));
+ }
+ }
+ *d = '\0';
+ return -1;
+}
diff --git a/usr.sbin/jail/jailp.h b/usr.sbin/jail/jailp.h
new file mode 100644
index 000000000000..ccd96f5f247e
--- /dev/null
+++ b/usr.sbin/jail/jailp.h
@@ -0,0 +1,254 @@
+/*-
+ * 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 <sys/param.h>
+#include <sys/types.h>
+#include <sys/jail.h>
+#include <sys/queue.h>
+#include <sys/time.h>
+
+#include <jail.h>
+#include <stdio.h>
+
+#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 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_CLEANUP 0x1000 /* -C Run post-removal commands */
+
+#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 */
+ IP_ZFS_DATASET, /* Jail ZFS datasets */
+#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
new file mode 100644
index 000000000000..048cfcf11c2b
--- /dev/null
+++ b/usr.sbin/jail/jailparse.y
@@ -0,0 +1,276 @@
+%{
+/*-
+ * 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 <sys/cdefs.h>
+#include <err.h>
+#include <stdlib.h>
+#include <string.h>
+
+#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 <cs> STR STR1 VAR VAR1
+
+%type <p> param name
+%type <ss> value
+%type <s> 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 ';'
+ {
+ 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("*");
+ }
+ j = global_jail;
+ }
+ 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();
+
+static void
+YYERROR_DECL()
+{
+ struct cflex *cflex = yyget_extra(scanner);
+
+ if (!yyget_text(scanner))
+ warnx("%s line %d: %s",
+ cflex->cfname, yyget_lineno(scanner), s);
+ else if (!yyget_text(scanner)[0])
+ warnx("%s: unexpected EOF",
+ cflex->cfname);
+ else
+ warnx("%s line %d: %s: %s",
+ cflex->cfname, yyget_lineno(scanner),
+ yyget_text(scanner), s);
+ cflex->error = 1;
+}
+
+/* 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;
+}
diff --git a/usr.sbin/jail/state.c b/usr.sbin/jail/state.c
new file mode 100644
index 000000000000..1d200beacef9
--- /dev/null
+++ b/usr.sbin/jail/state.c
@@ -0,0 +1,492 @@
+/*-
+ * 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 <sys/cdefs.h>
+#include <sys/uio.h>
+
+#include <err.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include "jailp.h"
+
+struct cfjails ready = TAILQ_HEAD_INITIALIZER(ready);
+struct cfjails depend = TAILQ_HEAD_INITIALIZER(depend);
+
+static void dep_add(struct cfjail *from, struct cfjail *to, unsigned flags);
+static int cmp_jailptr(const void *a, const void *b);
+static int cmp_jailptr_name(const void *a, const void *b);
+static struct cfjail *find_jail(const char *name);
+static struct cfjail *running_jail(const char *name, int flags);
+
+static struct cfjail **jails_byname;
+static size_t njails;
+
+/*
+ * Set up jail dependency lists.
+ */
+void
+dep_setup(int docf)
+{
+ struct cfjail *j, *dj;
+ struct cfparam *p;
+ struct cfstring *s;
+ struct cfdepend *d;
+ const char *cs;
+ char *pname;
+ size_t plen;
+ int deps, ldeps;
+
+ if (!docf) {
+ /*
+ * With no config file, let "depend" for a single jail
+ * look at currently running jails.
+ */
+ if ((j = TAILQ_FIRST(&cfjails)) &&
+ (p = j->intparams[IP_DEPEND])) {
+ TAILQ_FOREACH(s, &p->val, tq) {
+ if (running_jail(s->s, 0) == NULL) {
+ warnx("depends on nonexistent jail "
+ "\"%s\"", s->s);
+ j->flags |= JF_FAILED;
+ }
+ }
+ }
+ return;
+ }
+
+ njails = 0;
+ TAILQ_FOREACH(j, &cfjails, tq)
+ njails++;
+ jails_byname = emalloc(njails * sizeof(struct cfjail *));
+ njails = 0;
+ TAILQ_FOREACH(j, &cfjails, tq)
+ jails_byname[njails++] = j;
+ qsort(jails_byname, njails, sizeof(struct cfjail *), cmp_jailptr);
+ deps = 0;
+ ldeps = 0;
+ plen = 0;
+ pname = NULL;
+ TAILQ_FOREACH(j, &cfjails, tq) {
+ if (j->flags & JF_FAILED)
+ continue;
+ if ((p = j->intparams[IP_DEPEND])) {
+ TAILQ_FOREACH(s, &p->val, tq) {
+ dj = find_jail(s->s);
+ if (dj != NULL) {
+ deps++;
+ dep_add(j, dj, 0);
+ } else {
+ jail_warnx(j,
+ "depends on undefined jail \"%s\"",
+ s->s);
+ j->flags |= JF_FAILED;
+ }
+ }
+ }
+ /* A jail has an implied dependency on its parent. */
+ if ((cs = strrchr(j->name, '.')))
+ {
+ if (plen < (size_t)(cs - j->name + 1)) {
+ plen = (cs - j->name) + 1;
+ pname = erealloc(pname, plen);
+ }
+ strlcpy(pname, j->name, plen);
+ dj = find_jail(pname);
+ if (dj != NULL) {
+ ldeps++;
+ dep_add(j, dj, DF_LIGHT);
+ }
+ }
+ }
+
+ /* Look for dependency loops. */
+ if (deps && (deps > 1 || ldeps)) {
+ (void)start_state(NULL, 0, 0, 0);
+ while ((j = TAILQ_FIRST(&ready))) {
+ requeue(j, &cfjails);
+ dep_done(j, DF_NOFAIL);
+ }
+ while ((j = TAILQ_FIRST(&depend)) != NULL) {
+ jail_warnx(j, "dependency loop");
+ j->flags |= JF_FAILED;
+ do {
+ requeue(j, &cfjails);
+ dep_done(j, DF_NOFAIL);
+ } while ((j = TAILQ_FIRST(&ready)));
+ }
+ TAILQ_FOREACH(j, &cfjails, tq)
+ STAILQ_FOREACH(d, &j->dep[DEP_FROM], tq[DEP_FROM])
+ d->flags &= ~DF_SEEN;
+ }
+ if (pname != NULL)
+ free(pname);
+}
+
+/*
+ * Return if a jail has dependencies.
+ */
+int
+dep_check(struct cfjail *j)
+{
+ int reset, depfrom, depto, ndeps, rev;
+ struct cfjail *dj;
+ struct cfdepend *d;
+
+ static int bits[] = { 0, 1, 1, 2, 1, 2, 2, 3 };
+
+ if (j->ndeps == 0)
+ return 0;
+ ndeps = 0;
+ if ((rev = JF_DO_STOP(j->flags))) {
+ depfrom = DEP_TO;
+ depto = DEP_FROM;
+ } else {
+ depfrom = DEP_FROM;
+ depto = DEP_TO;
+ }
+ STAILQ_FOREACH(d, &j->dep[depfrom], tq[depfrom]) {
+ if (d->flags & DF_SEEN)
+ continue;
+ dj = d->j[depto];
+ if (dj->flags & JF_FAILED) {
+ if (!(j->flags & (JF_DEPEND | JF_FAILED)) &&
+ verbose >= 0)
+ jail_warnx(j, "skipped");
+ j->flags |= JF_FAILED;
+ continue;
+ }
+ /*
+ * The dependee's state may be set (or changed) as a result of
+ * being in a dependency it wasn't in earlier.
+ */
+ reset = 0;
+ if (bits[dj->flags & JF_OP_MASK] <= 1) {
+ if (!(dj->flags & JF_OP_MASK)) {
+ reset = 1;
+ dj->flags |= JF_DEPEND;
+ requeue(dj, &ready);
+ }
+ /* Set or change the dependee's state. */
+ switch (j->flags & JF_OP_MASK) {
+ case JF_START:
+ dj->flags |= JF_START;
+ break;
+ case JF_SET:
+ if (!(dj->flags & JF_OP_MASK))
+ dj->flags |= JF_SET;
+ else if (dj->flags & JF_STOP)
+ dj->flags |= JF_START;
+ break;
+ case JF_STOP:
+ case JF_RESTART:
+ if (!(dj->flags & JF_STOP))
+ reset = 1;
+ dj->flags |= JF_STOP;
+ if (dj->flags & JF_SET)
+ dj->flags ^= (JF_START | JF_SET);
+ break;
+ }
+ }
+ if (reset)
+ dep_reset(dj);
+ if (!((d->flags & DF_LIGHT) &&
+ (rev ? dj->jid < 0 : dj->jid > 0)))
+ ndeps++;
+ }
+ if (ndeps == 0)
+ return 0;
+ requeue(j, &depend);
+ return 1;
+}
+
+/*
+ * Resolve any dependencies from a finished jail.
+ */
+void
+dep_done(struct cfjail *j, unsigned flags)
+{
+ struct cfjail *dj;
+ struct cfdepend *d;
+ int depfrom, depto;
+
+ if (JF_DO_STOP(j->flags)) {
+ depfrom = DEP_TO;
+ depto = DEP_FROM;
+ } else {
+ depfrom = DEP_FROM;
+ depto = DEP_TO;
+ }
+ STAILQ_FOREACH(d, &j->dep[depto], tq[depto]) {
+ if ((d->flags & DF_SEEN) | (flags & ~d->flags & DF_LIGHT))
+ continue;
+ d->flags |= DF_SEEN;
+ dj = d->j[depfrom];
+ if (!(flags & DF_NOFAIL) && (j->flags & JF_FAILED) &&
+ (j->flags & (JF_OP_MASK | JF_DEPEND)) !=
+ (JF_SET | JF_DEPEND)) {
+ if (!(dj->flags & (JF_DEPEND | JF_FAILED)) &&
+ verbose >= 0)
+ jail_warnx(dj, "skipped");
+ dj->flags |= JF_FAILED;
+ }
+ if (!--dj->ndeps && dj->queue == &depend)
+ requeue(dj, &ready);
+ }
+}
+
+/*
+ * Count a jail's dependencies and mark them as unseen.
+ */
+void
+dep_reset(struct cfjail *j)
+{
+ int depfrom;
+ struct cfdepend *d;
+
+ depfrom = JF_DO_STOP(j->flags) ? DEP_TO : DEP_FROM;
+ j->ndeps = 0;
+ STAILQ_FOREACH(d, &j->dep[depfrom], tq[depfrom])
+ j->ndeps++;
+}
+
+/*
+ * Find the next jail ready to do something.
+ */
+struct cfjail *
+next_jail(void)
+{
+ struct cfjail *j;
+
+ if (!(j = next_proc(!TAILQ_EMPTY(&ready))) &&
+ (j = TAILQ_FIRST(&ready)) && JF_DO_STOP(j->flags) &&
+ (j = TAILQ_LAST(&ready, cfjails)) && !JF_DO_STOP(j->flags)) {
+ TAILQ_FOREACH_REVERSE(j, &ready, cfjails, tq)
+ if (JF_DO_STOP(j->flags))
+ break;
+ }
+ if (j != NULL)
+ requeue(j, &cfjails);
+ return j;
+}
+
+/*
+ * Set jails to the proper start state.
+ */
+int
+start_state(const char *target, int docf, unsigned state, int running)
+{
+ struct iovec jiov[6];
+ struct cfjail *j, *tj;
+ int jid;
+ char namebuf[MAXHOSTNAMELEN];
+
+ if (!target || (!docf && (state & JF_OP_MASK) != JF_STOP) ||
+ (!running && !strcmp(target, "*"))) {
+ /*
+ * For a global wildcard (including no target specified),
+ * set the state on all jails and start with those that
+ * have no dependencies.
+ */
+ TAILQ_FOREACH_SAFE(j, &cfjails, tq, tj) {
+ j->flags = (j->flags & JF_FAILED) | state |
+ (docf ? JF_WILD : 0);
+ dep_reset(j);
+ requeue(j, j->ndeps ? &depend : &ready);
+ }
+ } else if (wild_jail_name(target)) {
+ /*
+ * For targets specified singly, or with a non-global wildcard,
+ * set their state and call them ready (even if there are
+ * dependencies). Leave everything else unqueued for now.
+ */
+ if (running) {
+ /*
+ * -R matches its wildcards against currently running
+ * jails, not against the config file.
+ */
+ jiov[0].iov_base = __DECONST(char *, "lastjid");
+ jiov[0].iov_len = sizeof("lastjid");
+ jiov[1].iov_base = &jid;
+ jiov[1].iov_len = sizeof(jid);
+ jiov[2].iov_base = __DECONST(char *, "jid");
+ jiov[2].iov_len = sizeof("jid");
+ jiov[3].iov_base = &jid;
+ jiov[3].iov_len = sizeof(jid);
+ jiov[4].iov_base = __DECONST(char *, "name");
+ jiov[4].iov_len = sizeof("name");
+ jiov[5].iov_base = &namebuf;
+ jiov[5].iov_len = sizeof(namebuf);
+ for (jid = 0; jail_get(jiov, 6, 0) > 0; ) {
+ if (wild_jail_match(namebuf, target)) {
+ j = add_jail();
+ j->name = estrdup(namebuf);
+ j->jid = jid;
+ j->flags = (j->flags & JF_FAILED) |
+ state | JF_WILD;
+ dep_reset(j);
+ requeue(j, &ready);
+ }
+ }
+ } else {
+ TAILQ_FOREACH_SAFE(j, &cfjails, tq, tj) {
+ if (wild_jail_match(j->name, target)) {
+ j->flags = (j->flags & JF_FAILED) |
+ state | JF_WILD;
+ dep_reset(j);
+ requeue(j, &ready);
+ }
+ }
+ }
+ } else {
+ j = find_jail(target);
+ if (j == NULL && (state & JF_OP_MASK) == JF_STOP) {
+ /* Allow -[rR] to specify a currently running jail. */
+ j = running_jail(target, JAIL_DYING);
+ }
+ if (j == NULL) {
+ warnx("\"%s\" not found", target);
+ return -1;
+ }
+ j->flags = (j->flags & JF_FAILED) | state;
+ dep_reset(j);
+ requeue(j, &ready);
+ }
+ return 0;
+}
+
+/*
+ * Move a jail to a new list.
+ */
+void
+requeue(struct cfjail *j, struct cfjails *queue)
+{
+ if (j->queue != queue) {
+ TAILQ_REMOVE(j->queue, j, tq);
+ TAILQ_INSERT_TAIL(queue, j, tq);
+ j->queue = queue;
+ }
+}
+
+void
+requeue_head(struct cfjail *j, struct cfjails *queue)
+{
+ TAILQ_REMOVE(j->queue, j, tq);
+ TAILQ_INSERT_HEAD(queue, j, tq);
+ j->queue = queue;
+}
+
+/*
+ * Add a dependency edge between two jails.
+ */
+static void
+dep_add(struct cfjail *from, struct cfjail *to, unsigned flags)
+{
+ struct cfdepend *d;
+
+ d = emalloc(sizeof(struct cfdepend));
+ d->flags = flags;
+ d->j[DEP_FROM] = from;
+ d->j[DEP_TO] = to;
+ STAILQ_INSERT_TAIL(&from->dep[DEP_FROM], d, tq[DEP_FROM]);
+ STAILQ_INSERT_TAIL(&to->dep[DEP_TO], d, tq[DEP_TO]);
+}
+
+/*
+ * Compare jail pointers for qsort/bsearch.
+ */
+static int
+cmp_jailptr(const void *a, const void *b)
+{
+ return strcmp((*((struct cfjail * const *)a))->name,
+ ((*(struct cfjail * const *)b))->name);
+}
+
+static int
+cmp_jailptr_name(const void *a, const void *b)
+{
+ return strcmp((const char *)a, ((*(struct cfjail * const *)b))->name);
+}
+
+/*
+ * Find a jail object by name.
+ */
+static struct cfjail *
+find_jail(const char *name)
+{
+ struct cfjail **jp;
+
+ if (jails_byname == NULL)
+ return NULL;
+
+ jp = bsearch(name, jails_byname, njails, sizeof(struct cfjail *),
+ cmp_jailptr_name);
+ return jp ? *jp : NULL;
+}
+
+/*
+ * Return jail if it is running, and NULL if it isn't.
+ */
+static struct cfjail *
+running_jail(const char *name, int flags)
+{
+ struct iovec jiov[4];
+ struct cfjail *jail;
+ char *ep;
+ char jailname[MAXHOSTNAMELEN];
+ int jid, ret, len;
+
+ if ((jid = strtol(name, &ep, 10)) && !*ep) {
+ memset(jailname,0,sizeof(jailname));
+ len = sizeof(jailname);
+ } else {
+ strncpy(jailname, name,sizeof(jailname));
+ len = strlen(name) + 1;
+ jid = 0;
+ }
+
+ jiov[0].iov_base = __DECONST(char *, "jid");
+ jiov[0].iov_len = sizeof("jid");
+ jiov[1].iov_base = &jid;
+ jiov[1].iov_len = sizeof(jid);
+ jiov[2].iov_base = __DECONST(char *, "name");
+ jiov[2].iov_len = sizeof("name");
+ jiov[3].iov_base = &jailname;
+ jiov[3].iov_len = len;
+
+ if ((ret = jail_get(jiov, 4, flags)) < 0)
+ return (NULL);
+
+ if ((jail = find_jail(jailname)) == NULL) {
+ jail = add_jail();
+ jail->name = estrdup(jailname);
+ jail->jid = ret;
+ }
+
+ return (jail);
+}
diff --git a/usr.sbin/jail/tests/Makefile b/usr.sbin/jail/tests/Makefile
new file mode 100644
index 000000000000..6a64557cafa8
--- /dev/null
+++ b/usr.sbin/jail/tests/Makefile
@@ -0,0 +1,9 @@
+PACKAGE= tests
+
+ATF_TESTS_SH+= jail_basic_test
+
+${PACKAGE}FILES+= commands.jail.conf
+# The different test cases create jails with the same name.
+TEST_METADATA+= is_exclusive="true"
+
+.include <bsd.test.mk>
diff --git a/usr.sbin/jail/tests/Makefile.depend b/usr.sbin/jail/tests/Makefile.depend
new file mode 100644
index 000000000000..11aba52f82cf
--- /dev/null
+++ b/usr.sbin/jail/tests/Makefile.depend
@@ -0,0 +1,10 @@
+# Autogenerated - do NOT edit!
+
+DIRDEPS = \
+
+
+.include <dirdeps.mk>
+
+.if ${DEP_RELDIR} == ${_DEP_RELDIR}
+# local dependencies - needed for -jN in clean tree
+.endif
diff --git a/usr.sbin/jail/tests/commands.jail.conf b/usr.sbin/jail/tests/commands.jail.conf
new file mode 100644
index 000000000000..ad152a28b7fe
--- /dev/null
+++ b/usr.sbin/jail/tests/commands.jail.conf
@@ -0,0 +1,7 @@
+
+exec.prestop = "echo STOP";
+exec.prestart = "echo START";
+exec.poststart = "env";
+persist;
+
+basejail {}
diff --git a/usr.sbin/jail/tests/jail_basic_test.sh b/usr.sbin/jail/tests/jail_basic_test.sh
new file mode 100755
index 000000000000..c781eed78756
--- /dev/null
+++ b/usr.sbin/jail/tests/jail_basic_test.sh
@@ -0,0 +1,337 @@
+#
+# SPDX-License-Identifier: BSD-2-Clause
+#
+# Copyright (c) 2019 Michael Zhilin
+#
+# 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.
+
+atf_test_case "basic" "cleanup"
+basic_head()
+{
+ atf_set descr 'Basic jail test'
+ atf_set require.user root
+}
+
+basic_body()
+{
+ # Create the jail
+ atf_check -s exit:0 -o ignore jail -c name=basejail persist ip4.addr=192.0.1.1
+ # Check output of jls
+ atf_check -s exit:0 -o ignore jls
+ atf_check -s exit:0 -o ignore jls -v
+ atf_check -s exit:0 -o ignore jls -n
+ # Stop jail
+ atf_check -s exit:0 -o ignore jail -r basejail
+ jail -c name=basejail persist ip4.addr=192.0.1.1
+ # Stop jail by jid
+ atf_check -s exit:0 -o ignore jail -r `jls -j basejail jid`
+ # Recreate
+ atf_check -s exit:0 -o ignore jail -cm name=basejail persist ip4.addr=192.0.1.1
+ # Restart
+ atf_check -s exit:0 -o ignore jail -rc name=basejail persist ip4.addr=192.0.1.1
+}
+
+basic_cleanup()
+{
+ jail -r basejail
+}
+
+atf_test_case "list" "cleanup"
+list_head()
+{
+ atf_set descr 'Specify some jail parameters as lists'
+ atf_set require.user root
+}
+
+list_body()
+{
+ if [ "$(sysctl -qn kern.features.vimage)" -ne 1 ]; then
+ atf_skip "cannot create VNET jails"
+ fi
+ atf_check -o save:epair ifconfig epair create
+
+ epair=$(cat epair)
+ atf_check jail -c name=basejail vnet persist vnet.interface=${epair},${epair%a}b
+
+ atf_check -o ignore jexec basejail ifconfig ${epair}
+ atf_check -o ignore jexec basejail ifconfig ${epair%a}b
+}
+
+list_cleanup()
+{
+ jail -r basejail
+ if [ -f epair ]; then
+ ifconfig $(cat epair) destroy
+ fi
+}
+
+atf_test_case "nested" "cleanup"
+nested_head()
+{
+ atf_set descr 'Hierarchical jails test'
+ atf_set require.user root
+}
+
+nested_body()
+{
+ # Create the first jail
+ jail -c name=basejail persist ip4.addr=192.0.1.1 children.max=1
+ atf_check -s exit:0 -o empty \
+ jexec basejail \
+ jail -c name=nestedjail persist ip4.addr=192.0.1.1
+
+ atf_check -s exit:1 -o empty -e inline:"jail: prison limit exceeded\n"\
+ jexec basejail \
+ jail -c name=secondnestedjail persist ip4.addr=192.0.1.1
+ # Check output of jls
+ atf_check -s exit:0 -o ignore \
+ jexec basejail jls
+ atf_check -s exit:0 -o ignore \
+ jexec basejail jls -v
+ atf_check -s exit:0 -o ignore \
+ jexec basejail jls -n
+ # Create jail with no child - children.max should be 0 by default
+ jail -c name=basejail_nochild persist ip4.addr=192.0.1.1
+ atf_check -s exit:1 -o empty \
+ -e inline:"jail: jail_set: Operation not permitted\n" \
+ jexec basejail_nochild \
+ jail -c name=nestedjail persist ip4.addr=192.0.1.1
+}
+
+nested_cleanup()
+{
+ jail -r nestedjail
+ jail -r basejail
+ jail -r basejail_nochild
+}
+
+atf_test_case "commands" "cleanup"
+commands_head()
+{
+ atf_set descr 'Commands jail test'
+ atf_set require.user root
+}
+
+commands_body()
+{
+ cp "$(atf_get_srcdir)/commands.jail.conf" jail.conf
+ echo "path = \"$PWD\";" >> jail.conf
+
+ # exec.prestart (START) and exec.poststart (env)
+ atf_check -o save:stdout -e empty \
+ jail -f jail.conf -qc basejail
+
+ # exec.prestart output is missing
+ atf_check grep -qE '^START$' stdout
+ # JID was not set in the exec.poststart env
+ atf_check grep -qE '^JID=[0-9]+' stdout
+ # JNAME was not set in the exec.poststart env
+ atf_check grep -qE '^JNAME=basejail$' stdout
+ # JPATH was not set in the exec.poststart env
+ atf_check grep -qE "^JPATH=$PWD$" stdout
+
+ # exec.prestop by jailname
+ atf_check -s exit:0 -o inline:"STOP\n" \
+ jail -f jail.conf -qr basejail
+ # exec.prestop by jid
+ jail -f jail.conf -qc basejail
+ atf_check -s exit:0 -o inline:"STOP\n" \
+ jail -f jail.conf -qr `jls -j basejail jid`
+}
+
+commands_cleanup()
+{
+ if jls -j basejail > /dev/null 2>&1; then
+ jail -r basejail
+ fi
+}
+
+atf_test_case "jid_name_set" "cleanup"
+jid_name_set_head()
+{
+ atf_set descr 'Test that one can set both the jid and name in a config file'
+ atf_set require.user root
+}
+
+find_unused_jid()
+{
+ : ${JAIL_MAX=999999}
+
+ # We'll start at a higher jid number and roll through the space until
+ # we find one that isn't taken. We start high to avoid racing parallel
+ # activity for the 'next available', though ideally we don't have a lot
+ # of parallel jail activity like that.
+ jid=5309
+ while jls -cj "$jid"; do
+ if [ "$jid" -eq "$JAIL_MAX" ]; then
+ atf_skip "System has too many jail, cannot find free slot"
+ fi
+
+ jid=$((jid + 1))
+ done
+
+ echo "$jid" | tee -a jails.lst
+}
+clean_jails()
+{
+ if [ ! -s jails.lst ]; then
+ return 0
+ fi
+
+ while read jail; do
+ if jls -c -j "$jail"; then
+ jail -r "$jail"
+ fi
+ done < jails.lst
+}
+
+jid_name_set_body()
+{
+ local jid=$(find_unused_jid)
+
+ echo "basejail" >> jails.lst
+ echo "$jid { name = basejail; persist; }" > jail.conf
+ atf_check -o match:"$jid: created" jail -f jail.conf -c "$jid"
+ # Confirm that we didn't override the explicitly-set name with the jid
+ # as the name.
+ atf_check -o match:"basejail" jls -j "$jid" name
+ atf_check -o match:"$jid: removed" jail -f jail.conf -r "$jid"
+
+ echo "$jid { host.hostname = \"\${name}\"; persist; }" > jail.conf
+ atf_check -o match:"$jid: created" jail -f jail.conf -c "$jid"
+ # Confirm that ${name} expanded and expanded correctly to the
+ # jid-implied name.
+ atf_check -o match:"$jid" jls -j "$jid" host.hostname
+ atf_check -o match:"$jid: removed" jail -f jail.conf -r "$jid"
+
+ echo "basejail { jid = $jid; persist; }" > jail.conf
+ atf_check -o match:"basejail: created" jail -f jail.conf -c basejail
+ # Confirm that our jid assigment in the definition worked out and we
+ # did in-fact create the jail there.
+ atf_check -o match:"$jid" jls -j "basejail" jid
+ atf_check -o match:"basejail: removed" jail -f jail.conf -r basejail
+}
+
+jid_name_set_cleanup()
+{
+ clean_jails
+}
+
+atf_test_case "param_consistency" "cleanup"
+param_consistency_head()
+{
+ atf_set descr 'Test for consistency in jid/name params being set implicitly'
+ atf_set require.user root
+}
+
+param_consistency_body()
+{
+ local iface jid
+
+ echo "basejail" >> jails.lst
+
+ # Most basic test: exec.poststart running a command without a jail
+ # config. This would previously crash as we only had the jid and name
+ # as populated at creation time.
+ atf_check jail -c path=/ exec.poststart="true" command=/usr/bin/true
+
+ iface=$(ifconfig lo create)
+ atf_check test -n "$iface"
+ echo "$iface" >> interfaces.lst
+
+ # Now do it again but exercising IP_VNET_INTERFACE, which is an
+ # implied command that wants to use the jid or name. This would crash
+ # as neither KP_JID or KP_NAME are populated when a jail is created,
+ # just as above- just at a different spot.
+ atf_check jail -c \
+ path=/ vnet=new vnet.interface="$iface" command=/usr/bin/true
+
+ # Test that a jail that we only know by name will have its jid resolved
+ # and added to its param set.
+ echo "basejail {path = /; exec.prestop = 'echo STOP'; persist; }" > jail.conf
+
+ atf_check -o ignore jail -f jail.conf -c basejail
+ atf_check -o match:"STOP" jail -f jail.conf -r basejail
+
+ # Do the same sequence as above, but use a jail with a jid-ish name.
+ jid=$(find_unused_jid)
+ echo "$jid {path = /; exec.prestop = 'echo STOP'; persist; }" > jail.conf
+
+ atf_check -o ignore jail -f jail.conf -c "$jid"
+ atf_check -o match:"STOP" jail -f jail.conf -r "$jid"
+
+ # Ditto, but now we set a name for that jid-jail.
+ echo "$jid {name = basejail; path = /; exec.prestop = 'echo STOP'; persist; }" > jail.conf
+
+ atf_check -o ignore jail -f jail.conf -c "$jid"
+ atf_check -o match:"STOP" jail -f jail.conf -r "$jid"
+
+ # Confirm that we have a valid jid available in exec.poststop. It's
+ # probably debatable whether we should or not.
+ echo "basejail {path = /; exec.poststop = 'echo JID=\$JID'; persist; }" > jail.conf
+ atf_check -o ignore jail -f jail.conf -c basejail
+ jid=$(jls -j basejail jid)
+
+ atf_check -o match:"JID=$jid" jail -f jail.conf -r basejail
+
+}
+
+param_consistency_cleanup()
+{
+ clean_jails
+
+ if [ -f "interfaces.lst" ]; then
+ while read iface; do
+ ifconfig "$iface" destroy
+ done < interfaces.lst
+ fi
+}
+
+atf_test_case "setaudit"
+setaudit_head()
+{
+ atf_set descr 'Test that setaudit works in a jail when configured with allow.setaudit'
+ atf_set require.user root
+ atf_set require.progs setaudit
+}
+
+setaudit_body()
+{
+ # Try to modify the audit mask within a jail without
+ # allow.setaudit configured.
+ atf_check -s not-exit:0 -o empty -e not-empty jail -c name=setaudit_jail \
+ command=setaudit -m fr ls /
+ # The command should succeed if allow.setaudit is configured.
+ atf_check -s exit:0 -o ignore -e empty jail -c name=setaudit_jail \
+ allow.setaudit command=setaudit -m fr ls /
+}
+
+atf_init_test_cases()
+{
+ atf_add_test_case "basic"
+ atf_add_test_case "list"
+ atf_add_test_case "nested"
+ atf_add_test_case "commands"
+ atf_add_test_case "jid_name_set"
+ atf_add_test_case "param_consistency"
+ atf_add_test_case "setaudit"
+}