summaryrefslogtreecommitdiffstats
path: root/usr.sbin/jail
diff options
context:
space:
mode:
Diffstat (limited to 'usr.sbin/jail')
-rw-r--r--usr.sbin/jail/Makefile25
-rw-r--r--usr.sbin/jail/Makefile.depend31
-rw-r--r--usr.sbin/jail/command.c979
-rw-r--r--usr.sbin/jail/config.c850
-rw-r--r--usr.sbin/jail/jail.81305
-rw-r--r--usr.sbin/jail/jail.c1018
-rw-r--r--usr.sbin/jail/jail.conf.5232
-rw-r--r--usr.sbin/jail/jaillex.l235
-rw-r--r--usr.sbin/jail/jailp.h237
-rw-r--r--usr.sbin/jail/jailparse.y216
-rw-r--r--usr.sbin/jail/state.c468
11 files changed, 5596 insertions, 0 deletions
diff --git a/usr.sbin/jail/Makefile b/usr.sbin/jail/Makefile
new file mode 100644
index 0000000..9dfdee5
--- /dev/null
+++ b/usr.sbin/jail/Makefile
@@ -0,0 +1,25 @@
+# $FreeBSD$
+
+.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 l
+
+NO_WMISSING_VARIABLE_DECLARATIONS=
+
+YFLAGS+=-v
+CFLAGS+=-I. -I${.CURDIR}
+
+.if ${MK_INET6_SUPPORT} != "no"
+CFLAGS+= -DINET6
+.endif
+.if ${MK_INET_SUPPORT} != "no"
+CFLAGS+= -DINET
+.endif
+
+CLEANFILES= y.output
+
+.include <bsd.prog.mk>
diff --git a/usr.sbin/jail/Makefile.depend b/usr.sbin/jail/Makefile.depend
new file mode 100644
index 0000000..b24d6ea4
--- /dev/null
+++ b/usr.sbin/jail/Makefile.depend
@@ -0,0 +1,31 @@
+# $FreeBSD$
+# Autogenerated - do NOT edit!
+
+DIRDEPS = \
+ gnu/lib/csu \
+ gnu/lib/libgcc \
+ include \
+ include/arpa \
+ include/xlocale \
+ lib/${CSU_DIR} \
+ lib/libc \
+ lib/libcompiler_rt \
+ lib/libelf \
+ lib/libjail \
+ lib/libkvm \
+ lib/libutil \
+ usr.bin/lex/lib \
+ usr.bin/yacc.host \
+
+
+.include <dirdeps.mk>
+
+.if ${DEP_RELDIR} == ${_DEP_RELDIR}
+# local dependencies - needed for -jN in clean tree
+jaillex.o: jaillex.c
+jaillex.o: y.tab.h
+jaillex.po: jaillex.c
+jaillex.po: y.tab.h
+jailparse.o: jailparse.c
+jailparse.po: jailparse.c
+.endif
diff --git a/usr.sbin/jail/command.c b/usr.sbin/jail/command.c
new file mode 100644
index 0000000..f162c3c
--- /dev/null
+++ b/usr.sbin/jail/command.c
@@ -0,0 +1,979 @@
+/*-
+ * 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>
+__FBSDID("$FreeBSD$");
+
+#include <sys/types.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 "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;
+
+/*
+ * Run the next command associated with a jail.
+ */
+int
+next_command(struct cfjail *j)
+{
+ enum intparam comparam;
+ int create_failed, stopping;
+
+ if (paralimit == 0) {
+ requeue(j, &runnable);
+ return 1;
+ }
+ 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_START || comparam == IP_COMMAND ||
+ comparam == IP_EXEC_POSTSTART)))
+ continue;
+ switch (run_command(j)) {
+ case -1:
+ failed(j);
+ /* FALLTHROUGH */
+ case 1:
+ return 1;
+ }
+ }
+}
+
+/*
+ * Check command exit status
+ */
+int
+finish_command(struct cfjail *j)
+{
+ 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))
+ requeue(TAILQ_FIRST(&runnable), &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, possible 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, *path, *ruleset, *term, *username;
+ enum intparam comparam;
+ size_t comlen;
+ pid_t pid;
+ 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");
+ 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) {
+ 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) {
+ 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";
+ jidstr = string_param(j->intparams[KP_JID]);
+ argv[3] = jidstr ? jidstr : string_param(j->intparams[KP_NAME]);
+ 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"))
+ 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 path");
+ 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 path");
+ 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 path");
+ 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_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;
+ 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);
+ }
+ 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 && chdir(pwd->pw_dir) < 0) {
+ jail_warnx(j, "chdir %s: %s",
+ pwd->pw_dir, strerror(errno));
+ exit(1);
+ }
+ endpwent();
+ }
+
+ 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;
+
+ *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 0000000..87b8ef9
--- /dev/null
+++ b/usr.sbin/jail/config.c
@@ -0,0 +1,850 @@
+/*-
+ * 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>
+__FBSDID("$FreeBSD$");
+
+#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 <netdb.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+
+#include "jailp.h"
+
+struct ipspec {
+ const char *name;
+ unsigned flags;
+};
+
+extern FILE *yyin;
+extern int yynerrs;
+
+extern int yyparse(void);
+
+struct cfjails cfjails = TAILQ_HEAD_INITIALIZER(cfjails);
+
+static void free_param(struct cfparams *pp, struct cfparam *p);
+static void free_param_strings(struct cfparam *p);
+
+static const struct ipspec intparams[] = {
+ [IP_ALLOW_DYING] = {"allow.dying", PF_INTERNAL | PF_BOOL},
+ [IP_COMMAND] = {"command", PF_INTERNAL},
+ [IP_DEPEND] = {"depend", PF_INTERNAL},
+ [IP_EXEC_CLEAN] = {"exec.clean", PF_INTERNAL | PF_BOOL},
+ [IP_EXEC_CONSOLELOG] = {"exec.consolelog", PF_INTERNAL},
+ [IP_EXEC_FIB] = {"exec.fib", PF_INTERNAL | PF_INT},
+ [IP_EXEC_JAIL_USER] = {"exec.jail_user", PF_INTERNAL},
+ [IP_EXEC_POSTSTART] = {"exec.poststart", PF_INTERNAL},
+ [IP_EXEC_POSTSTOP] = {"exec.poststop", PF_INTERNAL},
+ [IP_EXEC_PRESTART] = {"exec.prestart", PF_INTERNAL},
+ [IP_EXEC_PRESTOP] = {"exec.prestop", PF_INTERNAL},
+ [IP_EXEC_START] = {"exec.start", PF_INTERNAL},
+ [IP_EXEC_STOP] = {"exec.stop", PF_INTERNAL},
+ [IP_EXEC_SYSTEM_JAIL_USER]= {"exec.system_jail_user",
+ PF_INTERNAL | PF_BOOL},
+ [IP_EXEC_SYSTEM_USER] = {"exec.system_user", PF_INTERNAL},
+ [IP_EXEC_TIMEOUT] = {"exec.timeout", PF_INTERNAL | PF_INT},
+#if defined(INET) || defined(INET6)
+ [IP_INTERFACE] = {"interface", PF_INTERNAL},
+ [IP_IP_HOSTNAME] = {"ip_hostname", PF_INTERNAL | PF_BOOL},
+#endif
+ [IP_MOUNT] = {"mount", PF_INTERNAL | PF_REV},
+ [IP_MOUNT_DEVFS] = {"mount.devfs", PF_INTERNAL | PF_BOOL},
+ [IP_MOUNT_FDESCFS] = {"mount.fdescfs", PF_INTERNAL | PF_BOOL},
+ [IP_MOUNT_PROCFS] = {"mount.procfs", PF_INTERNAL | PF_BOOL},
+ [IP_MOUNT_FSTAB] = {"mount.fstab", PF_INTERNAL},
+ [IP_STOP_TIMEOUT] = {"stop.timeout", PF_INTERNAL | PF_INT},
+ [IP_VNET_INTERFACE] = {"vnet.interface", PF_INTERNAL},
+#ifdef INET
+ [IP__IP4_IFADDR] = {"ip4.addr", PF_INTERNAL | PF_CONV | PF_REV},
+#endif
+#ifdef INET6
+ [IP__IP6_IFADDR] = {"ip6.addr", PF_INTERNAL | PF_CONV | PF_REV},
+#endif
+ [IP__MOUNT_FROM_FSTAB] = {"mount.fstab", PF_INTERNAL | PF_CONV | PF_REV},
+ [IP__OP] = {NULL, PF_CONV},
+ [KP_ALLOW_CHFLAGS] = {"allow.chflags", 0},
+ [KP_ALLOW_MOUNT] = {"allow.mount", 0},
+ [KP_ALLOW_RAW_SOCKETS] = {"allow.raw_sockets", 0},
+ [KP_ALLOW_SET_HOSTNAME]= {"allow.set_hostname", 0},
+ [KP_ALLOW_SOCKET_AF] = {"allow.socket_af", 0},
+ [KP_ALLOW_SYSVIPC] = {"allow.sysvipc", 0},
+ [KP_DEVFS_RULESET] = {"devfs_ruleset", 0},
+ [KP_ENFORCE_STATFS] = {"enforce_statfs", 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(void)
+{
+ 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;
+
+ if (!strcmp(cfname, "-")) {
+ cfname = "STDIN";
+ yyin = stdin;
+ } else {
+ yyin = fopen(cfname, "r");
+ if (!yyin)
+ err(1, "%s", cfname);
+ }
+ if (yyparse() || yynerrs)
+ exit(1);
+
+ /* Separate the wildcard jails out from the actual jails. */
+ jseq = 0;
+ TAILQ_INIT(&wild);
+ TAILQ_FOREACH_SAFE(j, &cfjails, tq, tj) {
+ j->seq = ++jseq;
+ if (wild_jail_name(j->name))
+ requeue(j, &wild);
+ }
+
+ TAILQ_FOREACH(j, &cfjails, tq) {
+ /* Set aside the jail's parameters. */
+ TAILQ_INIT(&opp);
+ TAILQ_CONCAT(&opp, &j->params, tq);
+ /*
+ * The jail name implies its "name" or "jid" parameter,
+ * though they may also be explicitly set later on.
+ */
+ add_param(j, NULL,
+ strtol(j->name, &ep, 10) && !*ep ? KP_JID : KP_NAME,
+ j->name);
+ /*
+ * Collect parameters for the jail, global parameters/variables,
+ * and any matching wildcard jails.
+ */
+ did_self = 0;
+ TAILQ_FOREACH(wj, &wild, tq) {
+ if (j->seq < wj->seq && !did_self) {
+ TAILQ_FOREACH(p, &opp, tq)
+ add_param(j, p, 0, NULL);
+ did_self = 1;
+ }
+ if (wild_jail_match(j->name, wj->name))
+ TAILQ_FOREACH(p, &wj->params, tq)
+ add_param(j, p, 0, NULL);
+ }
+ if (!did_self)
+ TAILQ_FOREACH(p, &opp, tq)
+ add_param(j, p, 0, NULL);
+
+ /* Resolve any variable substitutions. */
+ pgen = 0;
+ TAILQ_FOREACH(p, &j->params, tq) {
+ p->gen = ++pgen;
+ find_vars:
+ TAILQ_FOREACH(s, &p->val, tq) {
+ while ((v = STAILQ_FIRST(&s->vars))) {
+ TAILQ_FOREACH(vp, &j->params, tq)
+ if (!strcmp(vp->name, v->name))
+ break;
+ if (!vp) {
+ 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);
+ }
+}
+
+/*
+ * 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 (dp->flags & PF_IMMUTABLE) {
+ jail_warnx(j, "cannot redefine variable \"%s\".",
+ dp->name);
+ 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)) {
+ 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, prefix;
+#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) {
+ strcpy(s->s, cs + 1);
+ s->len -= cs + 1 - s->s;
+ }
+ if ((cs = strchr(s->s, '/'))) {
+ prefix = strtol(cs + 1, &ep, 10);
+ if (*ep == '.'
+ ? inet_pton(AF_INET, cs + 1, &addr4) != 1
+ : *ep || prefix < 0 || prefix > 32) {
+ jail_warnx(j,
+ "ip4.addr: bad netmask \"%s\"", cs);
+ error = -1;
+ }
+ *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) {
+ strcpy(s->s, cs + 1);
+ s->len -= cs + 1 - s->s;
+ }
+ if ((cs = strchr(s->s, '/'))) {
+ prefix = strtol(cs + 1, &ep, 10);
+ if (*ep || prefix < 0 || prefix > 128) {
+ jail_warnx(j,
+ "ip6.addr: bad prefixlen \"%s\"",
+ cs);
+ error = -1;
+ }
+ *cs = '\0';
+ s->len = cs - s->s;
+ }
+ if ((cs = strchr(s->s, ' ')) != NULL) {
+ *cs = '\0';
+ s->len = cs - s->s;
+ }
+ }
+ }
+#endif
+#endif
+
+ /*
+ * Read mount.fstab file(s), and treat each line as its own mount
+ * parameter.
+ */
+ if (j->intparams[IP_MOUNT_FSTAB] != NULL) {
+ TAILQ_FOREACH(s, &j->intparams[IP_MOUNT_FSTAB]->val, tq) {
+ if (s->len == 0)
+ continue;
+ f = fopen(s->s, "r");
+ if (f == NULL) {
+ jail_warnx(j, "mount.fstab: %s: %s",
+ s->s, strerror(errno));
+ error = -1;
+ continue;
+ }
+ while ((ln = fgetln(f, &lnlen))) {
+ if ((cs = memchr(ln, '#', lnlen - 1)))
+ lnlen = cs - ln + 1;
+ if (ln[lnlen - 1] == '\n' ||
+ ln[lnlen - 1] == '#')
+ ln[lnlen - 1] = '\0';
+ else {
+ cs = alloca(lnlen + 1);
+ strlcpy(cs, ln, lnlen + 1);
+ ln = cs;
+ }
+ add_param(j, NULL, IP__MOUNT_FROM_FSTAB, ln);
+ }
+ fclose(f);
+ }
+ }
+ if (error)
+ failed(j);
+ return error;
+}
+
+/*
+ * Import parameters into libjail's binary jailparam format.
+ */
+int
+import_params(struct cfjail *j)
+{
+ struct cfparam *p;
+ struct cfstring *s, *ts;
+ struct jailparam *jp;
+ char *value, *cs;
+ size_t vallen;
+ int error;
+
+ error = 0;
+ j->njp = 0;
+ TAILQ_FOREACH(p, &j->params, tq)
+ if (!(p->flags & PF_INTERNAL))
+ j->njp++;
+ j->jp = jp = emalloc(j->njp * sizeof(struct jailparam));
+ TAILQ_FOREACH(p, &j->params, tq) {
+ if (p->flags & PF_INTERNAL)
+ continue;
+ if (jailparam_init(jp, p->name) < 0) {
+ error = -1;
+ jail_warnx(j, "%s", jail_errmsg);
+ jp++;
+ continue;
+ }
+ if (TAILQ_EMPTY(&p->val))
+ value = NULL;
+ else if (!jp->jp_elemlen ||
+ !TAILQ_NEXT(TAILQ_FIRST(&p->val), tq)) {
+ /*
+ * Scalar parameters silently discard multiple (array)
+ * values, keeping only the last value added. This
+ * lets values added from the command line append to
+ * arrays wthout pre-checking the type.
+ */
+ value = TAILQ_LAST(&p->val, cfstrings)->s;
+ } else {
+ /*
+ * Convert arrays into comma-separated strings, which
+ * jailparam_import will then convert back into arrays.
+ */
+ vallen = 0;
+ TAILQ_FOREACH(s, &p->val, tq)
+ vallen += s->len + 1;
+ value = alloca(vallen);
+ cs = value;
+ TAILQ_FOREACH_SAFE(s, &p->val, tq, ts) {
+ memcpy(cs, s->s, s->len);
+ cs += s->len + 1;
+ cs[-1] = ',';
+ }
+ value[vallen - 1] = '\0';
+ }
+ if (jailparam_import(jp, value) < 0) {
+ error = -1;
+ jail_warnx(j, "%s", jail_errmsg);
+ }
+ jp++;
+ }
+ if (error) {
+ jailparam_free(j->jp, j->njp);
+ free(j->jp);
+ j->jp = NULL;
+ failed(j);
+ }
+ return error;
+}
+
+/*
+ * Check if options are equal (with or without the "no" prefix).
+ */
+int
+equalopts(const char *opt1, const char *opt2)
+{
+ char *p;
+
+ /* "opt" vs. "opt" or "noopt" vs. "noopt" */
+ if (strcmp(opt1, opt2) == 0)
+ return (1);
+ /* "noopt" vs. "opt" */
+ if (strncmp(opt1, "no", 2) == 0 && strcmp(opt1 + 2, opt2) == 0)
+ return (1);
+ /* "opt" vs. "noopt" */
+ if (strncmp(opt2, "no", 2) == 0 && strcmp(opt1, opt2 + 2) == 0)
+ return (1);
+ while ((p = strchr(opt1, '.')) != NULL &&
+ !strncmp(opt1, opt2, ++p - opt1)) {
+ opt2 += p - opt1;
+ opt1 = p;
+ /* "foo.noopt" vs. "foo.opt" */
+ if (strncmp(opt1, "no", 2) == 0 && strcmp(opt1 + 2, opt2) == 0)
+ return (1);
+ /* "foo.opt" vs. "foo.noopt" */
+ if (strncmp(opt2, "no", 2) == 0 && strcmp(opt1, opt2 + 2) == 0)
+ return (1);
+ }
+ return (0);
+}
+
+/*
+ * See if a jail name matches a wildcard.
+ */
+int
+wild_jail_match(const char *jname, const char *wname)
+{
+ const char *jc, *jd, *wc, *wd;
+
+ /*
+ * A non-final "*" component in the wild name matches a single jail
+ * component, and a final "*" matches one or more jail components.
+ */
+ for (jc = jname, wc = wname;
+ (jd = strchr(jc, '.')) && (wd = strchr(wc, '.'));
+ jc = jd + 1, wc = wd + 1)
+ if (strncmp(jc, wc, jd - jc + 1) && strncmp(wc, "*.", 2))
+ return 0;
+ return (!strcmp(jc, wc) || !strcmp(wc, "*"));
+}
+
+/*
+ * Return if a jail name is a wildcard.
+ */
+int
+wild_jail_name(const char *wname)
+{
+ const char *wc;
+
+ for (wc = strchr(wname, '*'); wc; wc = strchr(wc + 1, '*'))
+ if ((wc == wname || wc[-1] == '.') &&
+ (wc[1] == '\0' || wc[1] == '.'))
+ return 1;
+ return 0;
+}
+
+/*
+ * Free a parameter record and all its strings and variables.
+ */
+static void
+free_param(struct cfparams *pp, struct cfparam *p)
+{
+ free(p->name);
+ free_param_strings(p);
+ TAILQ_REMOVE(pp, p, tq);
+ free(p);
+}
+
+static void
+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 0000000..c9b79c4
--- /dev/null
+++ b/usr.sbin/jail/jail.8
@@ -0,0 +1,1305 @@
+.\" 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.
+.\"
+.\" $FreeBSD$
+.\"
+.Dd July 20, 2015
+.Dt JAIL 8
+.Os
+.Sh NAME
+.Nm jail
+.Nd "manage system jails"
+.Sh SYNOPSIS
+.Nm
+.Op Fl dhilqv
+.Op Fl J Ar jid_file
+.Op Fl u Ar username
+.Op Fl U Ar username
+.Op Fl cmr
+.Ar param Ns = Ns Ar value ...
+.Op Cm command Ns = Ns Ar command ...
+.Nm
+.Op Fl dqv
+.Op Fl f Ar conf_file
+.Op Fl p Ar limit
+.Op Fl cmr
+.Op Ar jail
+.Nm
+.Op Fl qv
+.Op Fl f Ar conf_file
+.Op Fl rR
+.Op Cm * | Ar jail ...
+.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
+.Op Ar path hostname [ Ar ip Ns [ Ns Ar ,... Ns ]] Ar command ...
+.Sh DESCRIPTION
+The
+.Nm
+utility creates new jails, or modifies or removes existing jails.
+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 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 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 d
+Allow making changes to a dying jail, equivalent to the
+.Va allow.dying
+parameter.
+.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.
+.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 be specified with parameters directly on the command line.
+In this case, the
+.Xr jail.conf 5
+file will not be used.
+For backward compatibility, the command line may also have four fixed
+parameters, without names:
+.Ar path ,
+.Ar hostname ,
+.Ar ip ,
+and
+.Ar command .
+This mode will always create a new jail, and the
+.Fl c
+and
+.Fl m
+options do not apply (and must not be present).
+.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).
+.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 allow.*
+Some restrictions of the jail environment may be set on a per-jail
+basis.
+With the exception of
+.Va allow.set_hostname ,
+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.
+In the current jail implementation, System V primitives share a single
+namespace across the host and jail environments, meaning that processes
+within a jail would be able to communicate with (and potentially interfere
+with) processes outside of the jail, and in other jails.
+.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.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.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 8
+for information on how to configure the ZFS filesystem to operate from
+within a jail.
+.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.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.
+.El
+.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 pseudo-parameters are:
+.Bl -tag -width indent
+.It Va exec.prestart
+Command(s) to run in the system environment before a jail is created.
+.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 .
+.It Va exec.poststop
+Command(s) to run in the system environment after a jail is removed.
+.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.
+The environment variables from the login class capability database for the
+target login are also set.
+.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 Ns )
+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 Ns )
+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 network interface to give to a vnet-enabled jail after is it created.
+The interface will automatically be released when the jail is removed.
+.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 5
+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 5
+filesystem on the chrooted
+.Pa /dev/fd
+directory.
+.It Va mount.procfs
+Mount a
+.Xr procfs 5
+filesystem on the chrooted
+.Pa /proc
+directory.
+.It Va allow.dying
+Allow making changes to a
+.Va dying
+jail.
+.It Va depend
+Specify a jail (or jails) that this jail depends on.
+Any such jails must be fully created, up to 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 must be fully removed, up to the last
+.Va exec.poststop
+command, before the 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
+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
+For
+.Xr named 8 ,
+it is necessary to modify
+.Pa /etc/namedb/named.conf .
+.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
+.Pa /usr/sbin/sysinstall
+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 = ed0;
+ exec.start = "/bin/sh /etc/rc";
+ exec.stop = "/bin/sh /etc/rc.shutdown";
+}
+.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
+.Nm
+.Ar -r ,
+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.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 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 killall 1 ,
+.Xr lsvfs 1 ,
+.Xr newaliases 1 ,
+.Xr pgrep 1 ,
+.Xr pkill 1 ,
+.Xr ps 1 ,
+.Xr quota 1 ,
+.Xr jail_set 2 ,
+.Xr devfs 5 ,
+.Xr fdescfs 5 ,
+.Xr jail.conf 5 ,
+.Xr linprocfs 5 ,
+.Xr linsysfs 5 ,
+.Xr procfs 5 ,
+.Xr rc.conf 5 ,
+.Xr sysctl.conf 5 ,
+.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 named 8 ,
+.Xr reboot 8 ,
+.Xr rpcbind 8 ,
+.Xr sendmail 8 ,
+.Xr shutdown 8 ,
+.Xr sysctl 8 ,
+.Xr syslogd 8 ,
+.Xr umount 8
+.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
+.Pa http://www.rndassociates.com/
+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 0000000..e42afa4
--- /dev/null
+++ b/usr.sbin/jail/jail.c
@@ -0,0 +1,1018 @@
+/*-
+ * 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/cdefs.h>
+__FBSDID("$FreeBSD$");
+
+#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;
+};
+
+const char *cfname;
+int iflag;
+int note_remove;
+int verbose;
+
+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, int dflag);
+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);
+static void print_param(FILE *fp, const struct cfparam *p, int sep, int doname);
+static void quoted_print(FILE *fp, char *str);
+static void usage(void);
+
+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,
+#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_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__NULL
+};
+
+int
+main(int argc, char **argv)
+{
+ struct stat st;
+ FILE *jfp;
+ struct cfjail *j;
+ char *JidFile;
+ size_t sysvallen;
+ unsigned op, pi;
+ int ch, docf, error, i, oldcl, sysval;
+ int dflag, Rflag;
+ char enforce_statfs[4];
+#if defined(INET) || defined(INET6)
+ char *cs, *ncs;
+#endif
+#if defined(INET) && defined(INET6)
+ struct in6_addr addr6;
+#endif
+
+ op = 0;
+ dflag = Rflag = 0;
+ docf = 1;
+ cfname = CONF_FILE;
+ JidFile = NULL;
+
+ while ((ch = getopt(argc, argv, "cdf:hiJ:lmn:p:qrRs:u:U:v")) != -1) {
+ switch (ch) {
+ case 'c':
+ op |= JF_START;
+ break;
+ case 'd':
+ dflag = 1;
+ 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;
+
+ /* Find out which of the four 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");
+ }
+ sysvallen = sizeof(sysval);
+ if (sysctlbyname("security.jail.enforce_statfs",
+ &sysval, &sysvallen, NULL, 0) == 0) {
+ snprintf(enforce_statfs,
+ sizeof(enforce_statfs), "%d", sysval);
+ add_param(NULL, NULL, KP_ENFORCE_STATFS,
+ enforce_statfs);
+ }
+ }
+ } 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();
+ 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)
+ usage();
+ docf = 0;
+ for (i = 0; i < argc; i++) {
+ 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]);
+ }
+#ifdef INET
+ else if (!strncmp(argv[i], "ip4.addr=", 9)) {
+ for (cs = argv[i] + 9;; cs = ncs + 1) {
+ ncs = strchr(cs, ',');
+ if (ncs)
+ *ncs = '\0';
+ add_param(NULL, NULL, KP_IP4_ADDR, cs);
+ if (!ncs)
+ break;
+ }
+ }
+#endif
+#ifdef INET6
+ else if (!strncmp(argv[i], "ip6.addr=", 9)) {
+ for (cs = argv[i] + 9;; cs = ncs + 1) {
+ ncs = strchr(cs, ',');
+ if (ncs)
+ *ncs = '\0';
+ add_param(NULL, NULL, KP_IP6_ADDR, cs);
+ if (!ncs)
+ break;
+ }
+ }
+#endif
+ else
+ add_param(NULL, NULL, 0, argv[i]);
+ }
+ } else {
+ /* From the config file, perhaps with a specified jail */
+ if (Rflag || !docf)
+ usage();
+ load_config();
+ }
+
+ /* Find out which jails will be run. */
+ dep_setup(docf);
+ error = 0;
+ if (op == 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.
+ */
+ 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->jid)
+ running_jid(j,
+ (j->flags & (JF_SET | JF_DEPEND)) == JF_SET
+ ? dflag || bool_param(j->intparams[IP_ALLOW_DYING])
+ : 0);
+ 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 ? JF_START : JF_SET;
+ break;
+ case JF_SET_RESTART:
+ if (j->jid < 0) {
+ jail_quoted_warnx(j, "not found",
+ "no jail specified");
+ failed(j);
+ continue;
+ }
+ j->flags = rdtun_params(j, 0) ? 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) ? 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;
+ j->comparam = startcommands;
+ j->comstring = NULL;
+ }
+ if (next_command(j))
+ continue;
+ jail_create_done:
+ clear_persist(j);
+ if (jfp != NULL)
+ print_jail(jfp, j, oldcl);
+ 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->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;
+ }
+ 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;
+ 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 iovec jiov[4];
+ struct stat st;
+ struct jailparam *jp, *setparams, *setparams2, *sjp;
+ const char *path;
+ int dopersist, ns, jid, dying, didfail;
+
+ /*
+ * 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;
+
+ didfail = 0;
+ j->jid = jailparam_set_note(j, setparams, ns, JAIL_CREATE);
+ if (j->jid < 0 && errno == EEXIST &&
+ bool_param(j->intparams[IP_ALLOW_DYING]) &&
+ int_param(j->intparams[KP_JID], &jid) && jid != 0) {
+ /*
+ * The jail already exists, but may be dying.
+ * Make sure it is, in which case an update is appropriate.
+ */
+ 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 *, "dying");
+ jiov[2].iov_len = sizeof("dying");
+ jiov[3].iov_base = &dying;
+ jiov[3].iov_len = sizeof(dying);
+ if (jail_get(jiov, 4, JAIL_DYING) < 0) {
+ /*
+ * It could be that the jail just barely finished
+ * dying, or it could be that the jid never existed
+ * but the name does. In either case, another try
+ * at creating the jail should do the right thing.
+ */
+ if (errno == ENOENT)
+ j->jid = jailparam_set_note(j, setparams, ns,
+ JAIL_CREATE);
+ } else if (dying) {
+ j->jid = jid;
+ if (rdtun_params(j, 1) < 0) {
+ j->jid = -1;
+ didfail = 1;
+ } else {
+ sjp = setparams2 = alloca((j->njp + dopersist) *
+ sizeof(struct jailparam));
+ for (jp = setparams; jp < setparams + ns; jp++)
+ if (!JP_RDTUN(jp) ||
+ !strcmp(jp->jp_name, "jid"))
+ *sjp++ = *jp;
+ j->jid = jailparam_set_note(j, setparams2,
+ sjp - setparams2, JAIL_UPDATE | JAIL_DYING);
+ /*
+ * Again, perhaps the jail just finished dying.
+ */
+ if (j->jid < 0 && errno == ENOENT)
+ j->jid = jailparam_set_note(j,
+ setparams, ns, JAIL_CREATE);
+ }
+ }
+ }
+ if (j->jid < 0 && !didfail) {
+ 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,
+ bool_param(j->intparams[IP_ALLOW_DYING])
+ ? JAIL_UPDATE | JAIL_DYING : 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;
+ int nrt, rval;
+
+ 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;
+ rval = 0;
+ if (jailparam_get(rtparams, nrt,
+ bool_param(j->intparams[IP_ALLOW_DYING]) ? JAIL_DYING : 0) > 0) {
+ rtjp = rtparams + 1;
+ for (jp = j->jp, rtjp = rtparams + 1; rtjp < rtparams + nrt;
+ jp++) {
+ if (JP_RDTUN(jp) && strcmp(jp->jp_name, "jid")) {
+ if (!((jp->jp_flags & (JP_BOOL | JP_NOBOOL)) &&
+ jp->jp_valuelen == 0 &&
+ *(int *)jp->jp_value) &&
+ !(rtjp->jp_valuelen == jp->jp_valuelen &&
+ !memcmp(rtjp->jp_value, jp->jp_value,
+ jp->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, int dflag)
+{
+ 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, dflag ? JAIL_DYING : 0);
+}
+
+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%s)",
+ (flags & (JAIL_CREATE | JAIL_UPDATE)) == JAIL_CREATE
+ ? "JAIL_CREATE" : "JAIL_UPDATE",
+ (flags & JAIL_DYING) ? " | JAIL_DYING" : "");
+ 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)
+{
+ struct cfparam *p;
+
+ if (oldcl) {
+ fprintf(fp, "%d\t", j->jid);
+ print_param(fp, j->intparams[KP_PATH], ',', 0);
+ putc('\t', fp);
+ print_param(fp, j->intparams[KP_HOST_HOSTNAME], ',', 0);
+ putc('\t', 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
+ putc('\t', fp);
+ print_param(fp, j->intparams[IP_COMMAND], ' ', 0);
+ } else {
+ fprintf(fp, "jid=%d", j->jid);
+ TAILQ_FOREACH(p, &j->params, tq)
+ if (strcmp(p->name, "jid")) {
+ putc(' ', fp);
+ print_param(fp, p, ',', 1);
+ }
+ }
+ putc('\n', fp);
+}
+
+/*
+ * 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");
+ exit(1);
+}
diff --git a/usr.sbin/jail/jail.conf.5 b/usr.sbin/jail/jail.conf.5
new file mode 100644
index 0000000..d83bf61
--- /dev/null
+++ b/usr.sbin/jail/jail.conf.5
@@ -0,0 +1,232 @@
+.\" Copyright (c) 2012 James Gritton
+.\" All rights reserved.
+.\"
+.\" Redistribution and use in source and binary forms, with or without
+.\" modification, are permitted provided that the following conditions
+.\" are met:
+.\" 1. Redistributions of source code must retain the above copyright
+.\" notice, this list of conditions and the following disclaimer.
+.\" 2. Redistributions in binary form must reproduce the above copyright
+.\" notice, this list of conditions and the following disclaimer in the
+.\" documentation and/or other materials provided with the distribution.
+.\"
+.\" THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
+.\" ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+.\" IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+.\" ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
+.\" FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+.\" DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+.\" OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+.\" HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+.\" LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+.\" OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+.\" SUCH DAMAGE.
+.\"
+.\" $FreeBSD$
+.\"
+.Dd February 13, 2014
+.Dt JAIL.CONF 5
+.Os
+.Sh NAME
+.Nm jail.conf
+.Nd configuration file for
+.Xr jail 8
+.Sh DESCRIPTION
+A
+.Xr jail 8
+configuration file consists of one or more jail definitions statements,
+and parameter or variable statements within those jail definitions.
+A jail definition statement looks something like a C compound statement.
+A parameter statement looks like a C assignment,
+including a terminating semicolon.
+.Pp
+The general syntax of a jail definition is:
+.Bd -literal -offset indent
+jailname {
+ parameter = "value";
+ parameter = "value";
+ ...
+}
+.Ed
+.Pp
+Each jail is required to have a
+.Va name
+at the front of its definition.
+This is used by
+.Xr jail 8
+to specify a jail on the command line and report the jail status,
+and is also passed to the kernel when creating the jail.
+.Ss Parameters
+A jail is defined by a set of named parameters, specified inside the
+jail definition.
+See
+.Xr jail 8
+for a list of jail parameters passed to the kernel,
+as well as internal parameters used when creating and removing jails.
+.Pp
+A typical parameter has a name and a value.
+Some parameters are boolean and may be specified with values of
+.Dq true
+or
+.Dq false ,
+or as valueless shortcuts, with a
+.Dq no
+prefix indicating a false value.
+For example, these are equivalent:
+.Bd -literal -offset indent
+allow.mount = "false";
+allow.nomount;
+.Ed
+.Pp
+Other parameters may have more than one value.
+A comma-separated list of values may be set in a single statement,
+or an existing parameter list may be appended to using
+.Dq += :
+.Bd -literal -offset indent
+ip4.addr = 10.1.1.1, 10.1.1.2, 10.1.1.3;
+
+ip4.addr = 10.1.1.1;
+ip4.addr += 10.1.1.2;
+ip4.addr += 10.1.1.3;
+.Ed
+.Pp
+Note the
+.Va name
+parameter is implicitly set to the name in the jail definition.
+.Ss String format
+Parameter values, including jail names, can be single tokens or quoted
+strings.
+A token is any sequence of characters that aren't considered special in
+the syntax of the configuration file (such as a semicolon or
+whitespace).
+If a value contains anything more than letters, numbers, dots, dashes
+and underscores, it is advisable to put quote marks around that value.
+Either single or double quotes may be used.
+.Pp
+Special characters may be quoted by preceding them with a backslash.
+Common C-style backslash character codes are also supported, including
+control characters and octal or hex ASCII codes.
+A backslash at the end of a line will ignore the subsequent newline and
+continue the string at the start of the next line.
+.Ss Variables
+A string may use shell-style variable substitution.
+A parameter or variable name preceded by a dollar sign, and possibly
+enclosed in braces, will be replaced with the value of that parameter or
+variable.
+For example, a jail's path may be defined in terms of its name or
+hostname:
+.Bd -literal -offset indent
+path = "/var/jail/$name";
+
+path = "/var/jail/${host.hostname}";
+.Ed
+.Pp
+Variable substitution occurs in unquoted tokens or in double-quoted
+strings, but not in single-quote strings.
+.Pp
+A variable is defined in the same way a parameter is, except that the
+variable name is preceded with a dollar sign:
+.Bd -literal -offset indent
+$parentdir = "/var/jail";
+path = "$parentdir/$name";
+.Ed
+.Pp
+The difference between parameters and variables is that variables are
+only used for substitution, while parameters are used both for
+substitution and for passing to the kernel.
+.Ss Wildcards
+A jail definition with a name of
+.Dq *
+is used to define wildcard parameters.
+Every defined jail will contain both the parameters from its own
+definition statement, as well as any parameters in a wildcard
+definition.
+.Pp
+Variable substitution is done on a per-jail basis, even when that
+substitution is for a parameter defined in a wildcard section.
+This is useful for wildcard parameters based on e.g. a jail's name.
+.Pp
+Later definitions in the configuration file supersede earlier ones, so a
+wildcard section placed before (above) a jail definition defines
+parameters that could be changed on a per-jail basis.
+Or a wildcard section placed after (below) all jails would contain
+parameters that always apply to every jail.
+Multiple wildcard statements are allowed, and wildcard parameters may
+also be specified outside of a jail definition statement.
+.Pp
+If hierarchical jails are defined, a partial-matching wildcard
+definition may be specified.
+For example, a definition with a name of
+.Dq foo.*
+would apply to jails with names like
+.Dq foo.bar
+and
+.Dq foo.bar.baz .
+.Ss 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 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";
+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
+}
+.Ed
+.Sh SEE ALSO
+.Xr jail_set 2 ,
+.Xr rc.conf 5 ,
+.Xr jail 8 ,
+.Xr jls 8
+.Sh HISTORY
+The
+.Xr jail 8
+utility appeared in
+.Fx 4.0 .
+The
+.Nm
+file was added in
+.Fx 9.1 .
+.Sh AUTHORS
+.An -nosplit
+The jail feature was written by
+.An Poul-Henning Kamp
+for R&D Associates
+.Pa http://www.rndassociates.com/
+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 0000000..04de85f
--- /dev/null
+++ b/usr.sbin/jail/jaillex.l
@@ -0,0 +1,235 @@
+%{
+/*-
+ * 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>
+__FBSDID("$FreeBSD$");
+
+#include <err.h>
+#include <stddef.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include "jailp.h"
+#include "y.tab.h"
+
+extern int yynerrs;
+
+static ssize_t text2lval(size_t triml, size_t trimr, int tovar);
+
+static int instr;
+static int lineno = 1;
+
+#define YY_DECL int yylex(void)
+%}
+
+%option noinput
+%option nounput
+
+%start _ DQ
+
+%%
+
+ /* Whitespace or equivalent */
+<_>[ \t]+ instr = 0;
+<_>#.* ;
+<_>\/\/.* ;
+<_>\/\*([^*]|(\*+([^*\/])))*\*+\/ {
+ const char *s;
+
+ for (s = yytext; s < yytext + yyleng; s++)
+ if (*s == '\n')
+ lineno++;
+ instr = 0;
+ }
+<_>\n {
+ lineno++;
+ 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);
+ return instr ? STR1 : (instr = 1, STR);
+ }
+
+ /* Single and double quoted strings */
+<_>'([^\'\\]|\\(.|\n))*' {
+ (void)text2lval(1, 1, 0);
+ 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);
+ 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);
+ return instr ? VAR1 : (instr = 1, VAR);
+ }
+<_>$\{([^\n{}]|\\(.|\n))*\} |
+<DQ>$\{([^\n\"{}]|\\(.|\n))*\} {
+ (void)text2lval(2, 1, 0);
+ return instr ? VAR1 : (instr = 1, VAR);
+ }
+
+ /* Partially formed bits worth complaining about */
+<_>\/\*([^*]|(\*+([^*\/])))*\** {
+ warnx("%s line %d: unterminated comment",
+ cfname, lineno);
+ yynerrs++;
+ }
+<_>'([^\n'\\]|\\.)* |
+<_>\"([^\n\"\\]|\\.)* {
+ warnx("%s line %d: unterminated string",
+ cfname, lineno);
+ yynerrs++;
+ }
+<_>$\{([^\n{}]|\\.)* |
+<DQ>$\{([^\n\"{}]|\\.)* {
+ warnx("%s line %d: unterminated variable",
+ cfname, lineno);
+ yynerrs++;
+ }
+
+ /* A hack because "<0>" rules aren't allowed */
+<_>. return yytext[0];
+.|\n {
+ BEGIN _;
+ yyless(0);
+ }
+
+%%
+
+void
+yyerror(const char *s)
+{
+ if (!yytext)
+ warnx("%s line %d: %s", cfname, lineno, s);
+ else if (!yytext[0])
+ warnx("%s: unexpected EOF", cfname);
+ else
+ warnx("%s line %d: %s: %s", cfname, lineno, yytext, s);
+}
+
+/*
+ * Copy string from yytext to yylval, handling backslash escapes,
+ * and optionally stopping at the beginning of a variable.
+ */
+static ssize_t
+text2lval(size_t triml, size_t trimr, int tovar)
+{
+ char *d;
+ const char *s, *se;
+
+ 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;
+ }
+ if (*s == '\n')
+ lineno++;
+ *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;
+ case '\n': d--; lineno++; break;
+ default: *d = *s; 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] <= 'a')
+ *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] <= 'a')
+ *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 0000000..4498f4b
--- /dev/null
+++ b/usr.sbin/jail/jailp.h
@@ -0,0 +1,237 @@
+/*-
+ * Copyright (c) 2011 James Gritton.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ *
+ * $FreeBSD$
+ */
+
+#include <sys/param.h>
+#include <sys/types.h>
+#include <sys/jail.h>
+#include <sys/queue.h>
+#include <sys/time.h>
+
+#include <jail.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 0x01 /* This is a variable, not a true parameter */
+#define PF_APPEND 0x02 /* Append to existing parameter list */
+#define PF_BAD 0x04 /* Unable to resolve parameter value */
+#define PF_INTERNAL 0x08 /* Internal parameter, not passed to kernel */
+#define PF_BOOL 0x10 /* Boolean parameter */
+#define PF_INT 0x20 /* Integer parameter */
+#define PF_CONV 0x40 /* Parameter duplicated in converted form */
+#define PF_REV 0x80 /* Run commands in reverse order on stopping */
+#define PF_IMMUTABLE 0x100 /* Immutable parameter */
+
+#define 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_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_PRESTART, /* Commands run outside jail before creating */
+ IP_EXEC_PRESTOP, /* Commands run outside jail before removing */
+ IP_EXEC_START, /* Commands run inside jail on creation */
+ IP_EXEC_STOP, /* Commands run inside jail on removal */
+ IP_EXEC_SYSTEM_JAIL_USER,/* Get jail_user from system passwd file */
+ IP_EXEC_SYSTEM_USER, /* Run non-jailed commands as this user */
+ IP_EXEC_TIMEOUT, /* Time to wait for a command to complete */
+#if defined(INET) || defined(INET6)
+ IP_INTERFACE, /* Add IP addresses to this interface */
+ IP_IP_HOSTNAME, /* Get jail IP address(es) from hostname */
+#endif
+ IP_MOUNT, /* Mount points in fstab(5) form */
+ IP_MOUNT_DEVFS, /* Mount /dev under prison root */
+ IP_MOUNT_FDESCFS, /* Mount /dev/fd under prison root */
+ IP_MOUNT_PROCFS, /* Mount /proc under prison root */
+ IP_MOUNT_FSTAB, /* A standard fstab(5) file */
+ IP_STOP_TIMEOUT, /* Time to wait after sending SIGTERM */
+ IP_VNET_INTERFACE, /* Assign interface(s) to vnet jail */
+#ifdef INET
+ IP__IP4_IFADDR, /* Copy of ip4.addr with interface/netmask */
+#endif
+#ifdef INET6
+ IP__IP6_IFADDR, /* Copy of ip6.addr with interface/prefixlen */
+#endif
+ IP__MOUNT_FROM_FSTAB, /* Line from mount.fstab file */
+ IP__OP, /* Placeholder for requested operation */
+ KP_ALLOW_CHFLAGS,
+ KP_ALLOW_MOUNT,
+ KP_ALLOW_RAW_SOCKETS,
+ KP_ALLOW_SET_HOSTNAME,
+ KP_ALLOW_SOCKET_AF,
+ KP_ALLOW_SYSVIPC,
+ KP_DEVFS_RULESET,
+ KP_ENFORCE_STATFS,
+ 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 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;
+};
+
+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(void);
+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 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 yyerror(const char *);
+extern int yylex(void);
+
+extern struct cfjails cfjails;
+extern struct cfjails ready;
+extern struct cfjails depend;
+extern const char *cfname;
+extern int iflag;
+extern int note_remove;
+extern int paralimit;
+extern int verbose;
diff --git a/usr.sbin/jail/jailparse.y b/usr.sbin/jail/jailparse.y
new file mode 100644
index 0000000..d085eb8
--- /dev/null
+++ b/usr.sbin/jail/jailparse.y
@@ -0,0 +1,216 @@
+%{
+/*-
+ * 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>
+__FBSDID("$FreeBSD$");
+
+#include <stdlib.h>
+#include <string.h>
+
+#include "jailp.h"
+
+#ifdef DEBUG
+#define YYDEBUG 1
+#endif
+%}
+
+%union {
+ struct cfjail *j;
+ struct cfparams *pp;
+ struct cfparam *p;
+ struct cfstrings *ss;
+ struct cfstring *s;
+ char *cs;
+}
+
+%token PLEQ
+%token <cs> STR STR1 VAR VAR1
+
+%type <j> jail
+%type <pp> param_l
+%type <p> param name
+%type <ss> value
+%type <s> string
+
+%%
+
+/*
+ * A config file is a series of jails (containing parameters) and jail-less
+ * parameters which realy belong to a global pseudo-jail.
+ */
+conf :
+ ;
+ | conf jail
+ ;
+ | conf param ';'
+ {
+ struct cfjail *j;
+
+ j = TAILQ_LAST(&cfjails, cfjails);
+ if (!j || strcmp(j->name, "*")) {
+ j = add_jail();
+ j->name = estrdup("*");
+ }
+ TAILQ_INSERT_TAIL(&j->params, $2, tq);
+ }
+ | conf ';'
+
+jail : STR '{' param_l '}'
+ {
+ $$ = add_jail();
+ $$->name = $1;
+ TAILQ_CONCAT(&$$->params, $3, tq);
+ free($3);
+ }
+ ;
+
+param_l :
+ {
+ $$ = emalloc(sizeof(struct cfparams));
+ TAILQ_INIT($$);
+ }
+ | param_l param ';'
+ {
+ $$ = $1;
+ TAILQ_INSERT_TAIL($$, $2, tq);
+ }
+ | param_l ';'
+ ;
+
+/*
+ * 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);
+ 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);
+ }
+ ;
+
+%%
diff --git a/usr.sbin/jail/state.c b/usr.sbin/jail/state.c
new file mode 100644
index 0000000..b3eb942
--- /dev/null
+++ b/usr.sbin/jail/state.c
@@ -0,0 +1,468 @@
+/*-
+ * 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>
+__FBSDID("$FreeBSD$");
+
+#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 int running_jid(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_jid(s->s, 0) < 0) {
+ 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_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_STOP) {
+ /* Allow -[rR] to specify a currently running jail. */
+ if ((jid = running_jid(target, JAIL_DYING)) > 0) {
+ j = add_jail();
+ j->name = estrdup(target);
+ j->jid = jid;
+ }
+ }
+ 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;
+ }
+}
+
+/*
+ * 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;
+
+ jp = bsearch(name, jails_byname, njails, sizeof(struct cfjail *),
+ cmp_jailptr_name);
+ return jp ? *jp : NULL;
+}
+
+/*
+ * Return the named jail's jid if it is running, and -1 if it isn't.
+ */
+static int
+running_jid(const char *name, int flags)
+{
+ struct iovec jiov[2];
+ char *ep;
+ int jid;
+
+ if ((jid = strtol(name, &ep, 10)) && !*ep) {
+ 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 {
+ jiov[0].iov_base = __DECONST(char *, "name");
+ jiov[0].iov_len = sizeof("name");
+ jiov[1].iov_len = strlen(name) + 1;
+ jiov[1].iov_base = alloca(jiov[1].iov_len);
+ strcpy(jiov[1].iov_base, name);
+ }
+ return jail_get(jiov, 2, flags);
+}
OpenPOWER on IntegriCloud