Skip site navigation (1)Skip section navigation (2)
Date:      Sun, 03 Jul 2011 23:24:57 -0600
From:      Jamie Gritton <jamie@FreeBSD.org>
To:        freebsd-jail@FreeBSD.org
Subject:   New jail(8) with configuration files, not yet in head
Message-ID:  <4E114EA9.4000605@FreeBSD.org>

next in thread | raw e-mail | index | archive | help
This is a multi-part message in MIME format.
--------------050909020909040607030101
Content-Type: text/plain; charset=ISO-8859-1; format=flowed
Content-Transfer-Encoding: 7bit

I'm hoping to get the latest version of jail(8) in before the door slams 
shut on 9.0.  If anyone wants to take a look at the new code and give it 
a spin, it may help to ease RE's mind about my tardiness.  The included 
diff applies to the current usr.sbin/jail directory.  In addition to the 
new program, it adds a jail.conf(5) man page that explains the config 
file format (hint: it's a typical C-style block config).

For anyone that caught my EuroBSDCon presentation last year, it's pretty 
much what I presented there.  Unfortunately it doesn't have the 
suggestions that were given then, which remain on the to-do list.  What 
little time I've devoted to this project since then has gone into 
cleaning things up and fixing the error handling.  Other bits will still 
go in later, but first I'd like to move this from "project" to "real" 
status.

The new program does two things.  First, it works the same way the 
current jail(8) does, to add jails from the command line - or change 
their parameters, or remove them.  Then it adds the ability to to the 
same thing from a config file, including running the start-up commands 
that are currently done by the rc.d/jail script.  Details of that are in 
the new jail(8) and jail.conf(5) man pages.

Bugs: there shouldn't be any, of course :-).  But please let me know if 
you see any!

- Jamie

--------------050909020909040607030101
Content-Type: text/plain;
 name="jail.diff"
Content-Transfer-Encoding: 7bit
Content-Disposition: attachment;
 filename="jail.diff"


Property changes on: .
___________________________________________________________________
Added: svn:mergeinfo
   Merged /projects/jailconf/usr.sbin/jail:r214084-223747
   Merged /vendor/resolver/dist/usr.sbin/jail:r1540-186085
   Merged /projects/largeSMP/usr.sbin/jail:r221273-222812
   Merged /projects/quota64/usr.sbin/jail:r184125-207707

Index: jail.c
===================================================================
--- jail.c	(revision 223747)
+++ jail.c	(working copy)
@@ -1,6 +1,6 @@
 /*-
  * Copyright (c) 1999 Poul-Henning Kamp.
- * Copyright (c) 2009 James Gritton
+ * Copyright (c) 2009-2011 James Gritton
  * All rights reserved.
  *
  * Redistribution and use in source and binary forms, with or without
@@ -28,155 +28,187 @@
 #include <sys/cdefs.h>
 __FBSDID("$FreeBSD$");
 
-#include <sys/param.h>
-#include <sys/jail.h>
+#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 <ctype.h>
 #include <err.h>
 #include <errno.h>
-#include <grp.h>
-#include <jail.h>
-#include <login_cap.h>
-#include <netdb.h>
-#include <paths.h>
-#include <pwd.h>
+#include <stdarg.h>
 #include <stdio.h>
 #include <stdlib.h>
 #include <string.h>
 #include <unistd.h>
 
-static struct jailparam *params;
-static char **param_values;
-static int nparams;
+#include "jailp.h"
 
-#ifdef INET6
-static int ip6_ok;
-static char *ip6_addr;
-#endif
-#ifdef INET
-static int ip4_ok;
-static char *ip4_addr;
-#endif
+#define JP_RDTUN(jp)	(((jp)->jp_ctltype & CTLFLAG_RDTUN) == CTLFLAG_RDTUN)
 
-#if defined(INET6) || defined(INET)
-static void add_ip_addr(char **addrp, char *newaddr);
-#endif
-#ifdef INET6
-static void add_ip_addr46(char *newaddr);
-#endif
-static void add_ip_addrinfo(int ai_flags, char *value);
+struct permspec {
+	const char	*name;
+	enum intparam	ipnum;
+	int		rev;
+};
+
+const char *cfname;
+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 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 set_param(const char *name, char *value);
 static void usage(void);
 
-static const char *perm_sysctl[][3] = {
-	{ "security.jail.set_hostname_allowed",
-	  "allow.noset_hostname", "allow.set_hostname" },
-	{ "security.jail.sysvipc_allowed",
-	  "allow.nosysvipc", "allow.sysvipc" },
-	{ "security.jail.allow_raw_sockets",
-	  "allow.noraw_sockets", "allow.raw_sockets" },
-	{ "security.jail.chflags_allowed",
-	  "allow.nochflags", "allow.chflags" },
-	{ "security.jail.mount_allowed",
-	  "allow.nomount", "allow.mount" },
-	{ "security.jail.socket_unixiproute_only",
-	  "allow.socket_af", "allow.nosocket_af" },
+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 },
 };
 
-extern char **environ;
+static const enum intparam startcommands[] = {
+    0,
+#ifdef INET
+    IP__IP4_IFADDR,
+#endif
+#ifdef INET6
+    IP__IP6_IFADDR,
+#endif
+    IP_MOUNT,
+    IP__MOUNT_FROM_FSTAB,
+    IP_MOUNT_DEVFS,
+    IP_EXEC_PRESTART, 
+    IP__OP,
+    IP_VNET_INTERFACE,
+    IP_EXEC_START,
+    IP_COMMAND,
+    IP_EXEC_POSTSTART,
+    0
+};
 
-#define GET_USER_INFO do {						\
-	pwd = getpwnam(username);					\
-	if (pwd == NULL) {						\
-		if (errno)						\
-			err(1, "getpwnam: %s", username);		\
-		else							\
-			errx(1, "%s: no such user", username);		\
-	}								\
-	lcap = login_getpwclass(pwd);					\
-	if (lcap == NULL)						\
-		err(1, "getpwclass: %s", username);			\
-	ngroups = ngroups_max;						\
-	if (getgrouplist(username, pwd->pw_gid, groups, &ngroups) != 0)	\
-		err(1, "getgrouplist: %s", username);			\
-} while (0)
+static const enum intparam stopcommands[] = {
+    0,
+    IP_EXEC_PRESTOP,
+    IP_EXEC_STOP,
+    IP_STOP_TIMEOUT,
+    IP__OP,
+    IP_EXEC_POSTSTOP,
+    IP_MOUNT_DEVFS,
+    IP__MOUNT_FROM_FSTAB,
+    IP_MOUNT,
+#ifdef INET6
+    IP__IP6_IFADDR,
+#endif
+#ifdef INET
+    IP__IP4_IFADDR,
+#endif
+    0
+};
 
 int
 main(int argc, char **argv)
 {
-	login_cap_t *lcap = NULL;
-	struct passwd *pwd = NULL;
-	gid_t *groups;
+	struct stat st;
+	FILE *jfp;
+	struct cfjail *j;
+	char *JidFile;
 	size_t sysvallen;
-	int ch, cmdarg, i, jail_set_flags, jid, ngroups, sysval;
-	int hflag, iflag, Jflag, lflag, rflag, uflag, Uflag;
-	long ngroups_max;
-	unsigned pi;
-	char *jailname, *securelevel, *username, *JidFile;
+	unsigned op, pi;
+	int ch, docf, error, i, oldcl, sysval;
+	int dflag, iflag, Rflag;
 	char enforce_statfs[4];
-	static char *cleanenv;
-	const char *shell, *p = NULL;
-	FILE *fp;
-
-	hflag = iflag = Jflag = lflag = rflag = uflag = Uflag =
-	    jail_set_flags = 0;
-	cmdarg = jid = -1;
-	jailname = securelevel = username = JidFile = cleanenv = NULL;
-	fp = NULL;
+#if defined(INET) || defined(INET6)
+	char *cs, *ncs;
+#endif
+#if defined(INET) && defined(INET6)
+	struct in6_addr addr6;
+#endif
 
-	ngroups_max = sysconf(_SC_NGROUPS_MAX) + 1;	
-	if ((groups = malloc(sizeof(gid_t) * ngroups_max)) == NULL)
-		err(1, "malloc");
+	op = 0;
+	dflag = iflag = Rflag = 0;
+	docf = 1;
+	cfname = CONF_FILE;
+	JidFile = NULL;
 
-	while ((ch = getopt(argc, argv, "cdhilmn:r:s:u:U:J:")) != -1) {
+	while ((ch = getopt(argc, argv, "cdf:hiJ:lmn:p:qrRs:U:v")) != -1) {
 		switch (ch) {
+		case 'c':
+			op |= JF_START;
+			break;
 		case 'd':
-			jail_set_flags |= JAIL_DYING;
+			dflag = 1;
+			break;
+		case 'f':
+			cfname = optarg;
 			break;
 		case 'h':
-			hflag = 1;
+#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;
-			Jflag = 1;
+			break;
+		case 'l':
+			add_param(NULL, NULL, IP_EXEC_CLEAN, NULL);
+			docf = 0;
+			break;
+		case 'm':
+			op |= JF_SET;
 			break;
 		case 'n':
-			jailname = optarg;
+			add_param(NULL, NULL, KP_NAME, optarg);
+			docf = 0;
 			break;
-		case 's':
-			securelevel = optarg;
+		case 'p':
+			paralimit = strtol(optarg, NULL, 10);
+			if (paralimit == 0)
+				paralimit = -1;
 			break;
-		case 'u':
-			username = optarg;
-			uflag = 1;
+		case 'q':
+			verbose = -1;
 			break;
-		case 'U':
-			username = optarg;
-			Uflag = 1;
+		case 'r':
+			op |= JF_STOP;
 			break;
-		case 'l':
-			lflag = 1;
+		case 'R':
+			op |= JF_STOP;
+			Rflag = 1;
 			break;
-		case 'c':
-			jail_set_flags |= JAIL_CREATE;
+		case 's':
+			add_param(NULL, NULL, KP_SECURELEVEL, optarg);
+			docf = 0;
 			break;
-		case 'm':
-			jail_set_flags |= JAIL_UPDATE;
+		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 'r':
-			jid = jail_getid(optarg);
-			if (jid < 0)
-				errx(1, "%s", jail_errmsg);
-			rflag = 1;
+		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();
@@ -184,312 +216,731 @@ main(int argc, char **argv)
 	}
 	argc -= optind;
 	argv += optind;
-	if (rflag) {
-		if (argc > 0 || iflag || Jflag || lflag || uflag || Uflag)
-			usage();
-		if (jail_remove(jid) < 0)
-			err(1, "jail_remove");
-		exit (0);
-	}
-	if (argc == 0)
-		usage();
-	if (uflag && Uflag)
-		usage();
-	if (lflag && username == NULL)
-		usage();
-	if (uflag)
-		GET_USER_INFO;
-
-#ifdef INET6
-	ip6_ok = feature_present("inet6");
-#endif
-#ifdef INET
-	ip4_ok = feature_present("inet");
-#endif
 
-	if (jailname)
-		set_param("name", jailname);
-	if (securelevel)
-		set_param("securelevel", securelevel);
-	if (jail_set_flags) {
-		for (i = 0; i < argc; i++) {
-			if (!strncmp(argv[i], "command=", 8)) {
-				cmdarg = i;
-				argv[cmdarg] += 8;
-				jail_set_flags |= JAIL_ATTACH;
-				break;
-			}
-			if (hflag) {
-#ifdef INET
-				if (!strncmp(argv[i], "ip4.addr=", 9)) {
-					add_ip_addr(&ip4_addr, argv[i] + 9);
-					break;
-				}
+	/* 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
-#ifdef INET6
-				if (!strncmp(argv[i], "ip6.addr=", 9)) {
-					add_ip_addr(&ip6_addr, argv[i] + 9);
+				    cs);
+				if (!ncs)
 					break;
-				}
-#endif
-				if (!strncmp(argv[i], "host.hostname=", 14))
-					add_ip_addrinfo(0, argv[i] + 14);
 			}
-			set_param(NULL, argv[i]);
 		}
-	} else {
-		if (argc < 4 || argv[0][0] != '/')
-			errx(1, "%s\n%s",
-			   "no -c or -m, so this must be an old-style command.",
-			   "But it doesn't look like one.");
-		set_param("path", argv[0]);
-		set_param("host.hostname", argv[1]);
-		if (hflag)
-			add_ip_addrinfo(0, argv[1]);
-#if defined(INET6) || defined(INET)
-		if (argv[2][0] != '\0')
-#ifdef INET6
-			add_ip_addr46(argv[2]);
-#else
-			add_ip_addr(&ip4_addr, argv[2]);
 #endif
-#endif
-		cmdarg = 3;
-		/* Emulate the defaults from security.jail.* sysctls */
+		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][0],
+				if (sysctlbyname(perm_sysctl[pi].name,
 				    &sysval, &sysvallen, NULL, 0) == 0)
-					set_param(perm_sysctl[pi]
-					    [sysval ? 2 : 1], NULL);
+					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);
-				set_param("enforce_statfs", enforce_statfs);
+				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]);
+				break;
 			}
+			add_param(NULL, NULL, 0, argv[i]);
 		}
+	} else {
+		/* From the config file, perhaps with a specified jail */
+		if (Rflag || !docf)
+			usage();
+		load_config();
 	}
-#ifdef INET
-	if (ip4_addr != NULL)
-		set_param("ip4.addr", ip4_addr);
-#endif
-#ifdef INET6
-	if (ip6_addr != NULL)
-		set_param("ip6.addr", ip6_addr);
-#endif
 
-	if (Jflag) {
-		fp = fopen(JidFile, "w");
-		if (fp == NULL)
-			errx(1, "Could not create JidFile: %s", JidFile);
-	}
-	jid = jailparam_set(params, nparams, 
-	    jail_set_flags ? jail_set_flags : JAIL_CREATE | JAIL_ATTACH);
-	if (jid < 0)
-		errx(1, "%s", jail_errmsg);
-	if (iflag) {
-		printf("%d\n", jid);
-		fflush(stdout);
-	}
-	if (Jflag) {
-		if (jail_set_flags) {
-			fprintf(fp, "jid=%d", jid);
-			for (i = 0; i < nparams; i++)
-				if (strcmp(params[i].jp_name, "jid")) {
-					fprintf(fp, " %s",
-					    (char *)params[i].jp_name);
-					if (param_values[i]) {
-						putc('=', fp);
-						quoted_print(fp,
-						    param_values[i]);
-					}
+	/* 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], op, Rflag) < 0)
+				error = 1;
+	} else {
+		if (start_state(docf ? argv[0] : NULL, 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) {
+				warnx("\"%s\" not found", j->name);
+				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))) {
+					warnx("\"%s\" already exists", j->name);
+					failed(j);
+					continue;
 				}
-			fprintf(fp, "\n");
-		} else {
-			for (i = 0; i < nparams; i++)
-				if (!strcmp(params[i].jp_name, "path"))
-					break;
-#if defined(INET6) && defined(INET)
-			fprintf(fp, "%d\t%s\t%s\t%s%s%s\t%s\n",
-			    jid, i < nparams
-			    ? (char *)params[i].jp_value : argv[0],
-			    argv[1], ip4_addr ? ip4_addr : "",
-			    ip4_addr && ip4_addr[0] && ip6_addr && ip6_addr[0]
-			    ? "," : "", ip6_addr ? ip6_addr : "", argv[3]);
-#elif defined(INET6)
-			fprintf(fp, "%d\t%s\t%s\t%s\t%s\n",
-			    jid, i < nparams
-			    ?  (char *)params[i].jp_value : argv[0],
-			    argv[1], ip6_addr ? ip6_addr : "", argv[3]);
-#elif defined(INET)
-			fprintf(fp, "%d\t%s\t%s\t%s\t%s\n",
-			    jid, i < nparams
-			    ? (char *)params[i].jp_value : argv[0],
-			    argv[1], ip4_addr ? ip4_addr : "", argv[3]);
-#endif
-		}
-		(void)fclose(fp);
-	}
-	if (cmdarg < 0)
-		exit(0);
-	if (username != NULL) {
-		if (Uflag)
-			GET_USER_INFO;
-		if (lflag) {
-			p = getenv("TERM");
-			environ = &cleanenv;
-		}
-		if (setgroups(ngroups, groups) != 0)
-			err(1, "setgroups");
-		if (setgid(pwd->pw_gid) != 0)
-			err(1, "setgid");
-		if (setusercontext(lcap, pwd, pwd->pw_uid,
-		    LOGIN_SETALL & ~LOGIN_SETGROUP & ~LOGIN_SETLOGIN) != 0)
-			err(1, "setusercontext");
-		login_close(lcap);
-	}
-	if (lflag) {
-		if (*pwd->pw_shell)
-			shell = pwd->pw_shell;
-		else
-			shell = _PATH_BSHELL;
-		if (chdir(pwd->pw_dir) < 0)
-			errx(1, "no home directory");
-		setenv("HOME", pwd->pw_dir, 1);
-		setenv("SHELL", shell, 1);
-		setenv("USER", pwd->pw_name, 1);
-		if (p)
-			setenv("TERM", p, 1);
+				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 (iflag)
+				printf("%d\n", j->jid);
+			if (jfp != NULL)
+				print_jail(jfp, j, oldcl);
+			dep_done(j, 0);
+			break;
+
+		case JF_SET:
+			if (j->jid < 0 && !(j->flags & JF_DEPEND)) {
+				warnx("\"%s\" not found", j->name);
+				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))
+					    && verbose >= 0)
+						warnx("\"%s\" not found",
+						    j->name);
+					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;
+		}
 	}
-	execvp(argv[cmdarg], argv + cmdarg);
-	err(1, "execvp: %s", argv[cmdarg]);
+
+	if (jfp != NULL)
+		fclose(jfp);
+	exit(error);
 }
 
-#if defined(INET6) || defined(INET)
+/*
+ * 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 (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.
+		 */
+		*(const void **)&jiov[0].iov_base = "jid";
+		jiov[0].iov_len = sizeof("jid");
+		jiov[1].iov_base = &jid;
+		jiov[1].iov_len = sizeof(jid);
+		*(const void **)&jiov[2].iov_base = "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
-add_ip_addr(char **addrp, char *value)
+clear_persist(struct cfjail *j)
+{
+	struct iovec jiov[4];
+	int jid;
+
+	if (!(j->flags & JF_PERSIST))
+		return;
+	j->flags &= ~JF_PERSIST;
+	*(const void **)&jiov[0].iov_base = "jid";
+	jiov[0].iov_len = sizeof("jid");
+	jiov[1].iov_base = &j->jid;
+	jiov[1].iov_len = sizeof(j->jid);
+	*(const void **)&jiov[2].iov_base = "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)
 {
-	int addrlen;
-	char *addr;
+	struct jailparam *jp, *setparams, *sjp;
+	int ns, jid;
 
-	if (!*addrp) {
-		*addrp = strdup(value);
-		if (!*addrp)
-			err(1, "malloc");
-	} else if (value[0]) {
-		addrlen = strlen(*addrp) + strlen(value) + 2;
-		addr = malloc(addrlen);
-		if (!addr)
-			err(1, "malloc");
-		snprintf(addr, addrlen, "%s,%s", *addrp, value);
-		free(*addrp);
-		*addrp = addr;
+	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;
 }
-#endif
 
-#ifdef INET6
+/*
+ * 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
-add_ip_addr46(char *value)
+running_jid(struct cfjail *j, int dflag)
 {
-	char *p, *np;
+	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;
+		}
+		*(const void **)&jiov[0].iov_base = "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]))) {
+		*(const void **)&jiov[0].iov_base = "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);
+}
 
-	for (p = value;; p = np + 1)
-	{
-		np = strchr(p, ',');
-		if (np)
-			*np = '\0';
-		add_ip_addrinfo(AI_NUMERICHOST, p);
-		if (!np)
-			break;
+/*
+ * Set jail parameters and possible 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;
 }
-#endif
 
+/*
+ * Print a jail record.
+ */
 static void
-add_ip_addrinfo(int ai_flags, char *value)
+print_jail(FILE *fp, struct cfjail *j, int oldcl)
 {
-	struct addrinfo hints, *ai0, *ai;
-	int error;
+	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
-	char avalue4[INET_ADDRSTRLEN];
-	struct in_addr addr4;
-#endif
+		print_param(fp, j->intparams[KP_IP4_ADDR], ',', 0);
 #ifdef INET6
-	char avalue6[INET6_ADDRSTRLEN];
-	struct in6_addr addr6;
-#endif
-
-	/* Look up the hostname (or get the address) */
-	memset(&hints, 0, sizeof(hints));
-	hints.ai_socktype = SOCK_STREAM;
-#if defined(INET6) && defined(INET)
-	hints.ai_family = PF_UNSPEC;
-#elif defined(INET6)
-	hints.ai_family = PF_INET6;
-#elif defined(INET)
-	hints.ai_family = PF_INET;
+		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
-	hints.ai_flags = ai_flags;
-	error = getaddrinfo(value, NULL, &hints, &ai0);
-	if (error != 0)
-		errx(1, "hostname %s: %s", value, gai_strerror(error));
-
-	/* Convert the addresses to ASCII so set_param can convert them back. */
-	for (ai = ai0; ai; ai = ai->ai_next)
-		switch (ai->ai_family) {
-#ifdef INET
-		case AF_INET:
-			if (!ip4_ok && (ai_flags & AI_NUMERICHOST) == 0)
-				break;
-			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_ip_addr(&ip4_addr, avalue4);
-			break;
 #endif
 #ifdef INET6
-		case AF_INET6:
-			if (!ip6_ok && (ai_flags & AI_NUMERICHOST) == 0)
-				break;
-			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_ip_addr(&ip6_addr, avalue6);
-			break;
+		print_param(fp, j->intparams[KP_IP6_ADDR], ',', 0);
 #endif
-		}
-	freeaddrinfo(ai0);
+		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
-quoted_print(FILE *fp, char *str)
+print_param(FILE *fp, const struct cfparam *p, int sep, int doname)
 {
-	int c, qc;
-	char *p = str;
+	const struct cfstring *s, *ts;
 
-	/* An empty string needs quoting. */
-	if (!*p) {
-		fputs("\"\"", fp);
+	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);
 	}
+}
 
-	/*
-	 * The value will be surrounded by quotes if it contains spaces
-	 * or quotes.
-	 */
-	qc = strchr(p, '\'') ? '"'
+/*
+ * 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;
@@ -505,65 +956,16 @@ quoted_print(FILE *fp, char *str)
 }
 
 static void
-set_param(const char *name, char *value)
-{
-	struct jailparam *param;
-	int i;
-
-	static int paramlistsize;
-
-	/* Separate the name from the value, if not done already. */
-	if (name == NULL) {
-		name = value;
-		if ((value = strchr(value, '=')))
-			*value++ = '\0';
-	}
-
-	/* jail_set won't chdir along with its chroot, so do it here. */
-	if (!strcmp(name, "path") && chdir(value) < 0)
-		err(1, "chdir: %s", value);
-
-	/* Check for repeat parameters */
-	for (i = 0; i < nparams; i++)
-		if (!strcmp(name, params[i].jp_name)) {
-			jailparam_free(params + i, 1);
-			memcpy(params + i, params + i + 1,
-			    (--nparams - i) * sizeof(struct jailparam));
-			break;
-		}
-
-	/* Make sure there is room for the new param record. */
-	if (!nparams) {
-		paramlistsize = 32;
-		params = malloc(paramlistsize * sizeof(*params));
-		param_values = malloc(paramlistsize * sizeof(*param_values));
-		if (params == NULL || param_values == NULL)
-			err(1, "malloc");
-	} else if (nparams >= paramlistsize) {
-		paramlistsize *= 2;
-		params = realloc(params, paramlistsize * sizeof(*params));
-		param_values = realloc(param_values,
-		    paramlistsize * sizeof(*param_values));
-		if (params == NULL)
-			err(1, "realloc");
-	}
-
-	/* Look up the paramter. */
-	param_values[nparams] = value;
-	param = params + nparams++;
-	if (jailparam_init(param, name) < 0 ||
-	    jailparam_import(param, value) < 0)
-		errx(1, "%s", jail_errmsg);
-}
-
-static void
 usage(void)
 {
 
 	(void)fprintf(stderr,
-	    "usage: jail [-d] [-h] [-i] [-J jid_file] "
-			"[-l -u username | -U username]\n"
-	    "            [-c | -m] param=value ... [command=command ...]\n"
-	    "       jail [-r jail]\n");
+	    "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);
 }
Index: Makefile
===================================================================
--- Makefile	(revision 223747)
+++ Makefile	(working copy)
@@ -3,9 +3,14 @@
 .include <bsd.own.mk>
 
 PROG=	jail
-MAN=	jail.8
-DPADD=	${LIBJAIL} ${LIBUTIL}
-LDADD=	-ljail -lutil
+MAN=	jail.8 jail.conf.5
+SRCS=	jail.c command.c config.c state.c jailp.h jaillex.l jailparse.y y.tab.h
+
+DPADD=	${LIBJAIL} ${LIBKVM} ${LIBUTIL} ${LIBL}
+LDADD=	-ljail -lkvm -lutil -ll
+
+YFLAGS+=-v
+CFLAGS+=-I. -I${.CURDIR}
 
 .if ${MK_INET6_SUPPORT} != "no"
 CFLAGS+= -DINET6
@@ -14,4 +19,6 @@ CFLAGS+= -DINET6
 CFLAGS+= -DINET
 .endif
 
+CLEANFILES= y.output
+
 .include <bsd.prog.mk>
Index: command.c
===================================================================
--- command.c	(revision 223747)
+++ command.c	(working copy)
@@ -0,0 +1,849 @@
+/*-
+ * 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: projects/jailconf/usr.sbin/jail/command.c 223442 2011-06-22 21:18:37Z jamie $");
+
+#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 void 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;
+
+	if (paralimit == 0) {
+		requeue(j, &runnable);
+		return 1;
+	}
+	create_failed = (j->flags & (JF_STOP | JF_FAILED)) == JF_FAILED;
+	comparam = *j->comparam;
+	for (;;) {
+		if (j->comstring == NULL) {
+			j->comparam += create_failed ? -1 : 1;
+			switch ((comparam = *j->comparam)) {
+			case 0:
+				return 0;
+			case IP_MOUNT_DEVFS:
+				if (!bool_param(j->intparams[IP_MOUNT_DEVFS]))
+					continue;
+				/* FALLTHROUGH */
+			case IP__OP:
+			case IP_STOP_TIMEOUT:
+				j->comstring = &dummystring;
+				break;
+			default:
+				if (j->intparams[comparam] == NULL)
+					continue;
+				j->comstring = create_failed
+				    ? TAILQ_LAST(&j->intparams[comparam]->val,
+					cfstrings)
+				    : TAILQ_FIRST(&j->intparams[comparam]->val);
+			}
+		} else {
+			j->comstring = j->comstring == &dummystring ? NULL :
+			    create_failed
+			    ? 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) {
+		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.
+ */
+int
+run_command(struct cfjail *j)
+{
+	const struct passwd *pwd;
+	const struct cfstring *comstring, *s;
+	login_cap_t *lcap;
+	char **argv;
+	char *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;
+#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 && 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 (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:
+		argv = alloca(8 * sizeof(char *));
+		*(const char **)&argv[0] = _PATH_IFCONFIG;
+		if ((cs = strchr(comstring->s, '|'))) {
+			argv[1] = alloca(cs - comstring->s + 1);
+			strlcpy(argv[1], comstring->s, cs - comstring->s + 1);
+			addr = cs + 1;
+		} else {
+			*(const char **)&argv[1] =
+			    string_param(j->intparams[IP_INTERFACE]);
+			addr = comstring->s;
+		}
+		*(const char **)&argv[2] = "inet";
+		if (!(cs = strchr(addr, '/'))) {
+			argv[3] = addr;
+			*(const char **)&argv[4] = "netmask";
+			*(const char **)&argv[5] = "255.255.255.255";
+			argc = 6;
+		} else if (strchr(cs + 1, '.')) {
+			argv[3] = alloca(cs - addr + 1);
+			strlcpy(argv[3], addr, cs - addr + 1);
+			*(const char **)&argv[4] = "netmask";
+			*(const char **)&argv[5] = cs + 1;
+			argc = 6;
+		} else {
+			argv[3] = addr;
+			argc = 4;
+		}
+		*(const char **)&argv[argc] = down ? "-alias" : "alias";
+		argv[argc + 1] = NULL;
+		break;
+#endif
+
+#ifdef INET6
+	case IP__IP6_IFADDR:
+		argv = alloca(8 * sizeof(char *));
+		*(const char **)&argv[0] = _PATH_IFCONFIG;
+		if ((cs = strchr(comstring->s, '|'))) {
+			argv[1] = alloca(cs - comstring->s + 1);
+			strlcpy(argv[1], comstring->s, cs - comstring->s + 1);
+			addr = cs + 1;
+		} else {
+			*(const char **)&argv[1] =
+			    string_param(j->intparams[IP_INTERFACE]);
+			addr = comstring->s;
+		}
+		*(const char **)&argv[2] = "inet6";
+		argv[3] = addr;
+		if (!(cs = strchr(addr, '/'))) {
+			*(const char **)&argv[4] = "prefixlen";
+			*(const char **)&argv[5] = "128";
+			argc = 6;
+		} else
+			argc = 4;
+		*(const char **)&argv[argc] = down ? "-alias" : "alias";
+		argv[argc + 1] = NULL;
+		break;	
+#endif
+
+	case IP_VNET_INTERFACE:
+		argv = alloca(5 * sizeof(char *));
+		*(const char **)&argv[0] = _PATH_IFCONFIG;
+		argv[1] = comstring->s;
+		*(const char **)&argv[2] = down ? "-vnet" : "vnet";
+		jidstr = string_param(j->intparams[KP_JID]);
+		*(const char **)&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];
+			*(const char **)&argv[0] = "/sbin/umount";
+		} else {
+			if (argc == 4) {
+				argv[7] = NULL;
+				argv[6] = argv[1];
+				argv[5] = argv[0];
+				argv[4] = argv[3];
+				*(const char **)&argv[3] = "-o";
+			} else {
+				argv[5] = NULL;
+				argv[4] = argv[1];
+				argv[3] = argv[0];
+			}
+			*(const char **)&argv[0] = _PATH_MOUNT;
+		}
+		*(const char **)&argv[1] = "-t";
+		break;
+
+	case IP_MOUNT_DEVFS:
+		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 = alloca(3 * sizeof(char *));
+			*(const char **)&argv[0] = "/sbin/umount";
+			argv[1] = devpath;
+			argv[2] = NULL;
+		} else {
+			argv = alloca(4 * sizeof(char *));
+			*(const char **)&argv[0] = _PATH_BSHELL;
+			*(const char **)&argv[1] = "-c";
+			ruleset = string_param(j->intparams
+			    [IP_MOUNT_DEVFS_RULESET]);
+			argv[2] = alloca(strlen(path) +
+			    (ruleset ? strlen(ruleset) + 1 : 0) + 56);
+			sprintf(argv[2], ". /etc/rc.subr; load_rc_config .; "
+			    "devfs_mount_jail %s/dev%s%s", path,
+			    ruleset ? " " : "", ruleset ? ruleset : "");
+			argv[3] = NULL;
+		}
+		break;
+
+	case IP_COMMAND:
+		if (j->name != NULL)
+			goto default_command;
+		argc = 0;
+		TAILQ_FOREACH(s, &j->intparams[IP_COMMAND]->val, tq)
+			argc++;
+		argv = alloca((argc + 1) * sizeof(char *));
+		argc = 0;
+		TAILQ_FOREACH(s, &j->intparams[IP_COMMAND]->val, tq)
+			argv[argc++] = s->s;
+		argv[argc] = NULL;
+		j->comstring = &dummystring;
+		break;
+
+	default:
+	default_command:
+		if ((cs = strpbrk(comstring->s, "!\"$&'()*;<>?[\\]`{|}~")) &&
+		    !(cs[0] == '&' && cs[1] == '\0')) {
+			argv = alloca(4 * sizeof(char *));
+			*(const char **)&argv[0] = _PATH_BSHELL;
+			*(const char **)&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) {
+			free(j->comline);
+			j->comline = NULL;
+			return 0;
+		} else {
+			paralimit--;
+			add_proc(j, pid);
+			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);
+			setenv("TERM", term, 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], argv);
+	jail_warnx(j, "exec %s: %s", argv[0], strerror(errno));
+	exit(1);
+}
+
+/*
+ * Add a process to the hash, tied to a jail.
+ */
+static void
+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)
+		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 acording 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;
+	}
+}
+
+/*
+ * 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, "jail");
+		if (kd == NULL)
+			exit(1);
+	}
+
+	ki = kvm_getprocs(kd, KERN_PROC_PROC, 0, &pcnt);
+	if (ki == NULL)
+		exit(1);
+	noted = 0;
+	for (i = 0; i < pcnt; i++)
+		if (ki[i].ki_jid == j->jid &&
+		    kill(ki[i].ki_pid, SIGTERM) == 0) {
+			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;
+}
Index: config.c
===================================================================
--- config.c	(revision 223747)
+++ config.c	(working copy)
@@ -0,0 +1,831 @@
+/*-
+ * 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: projects/jailconf/usr.sbin/jail/config.c 223351 2011-06-20 23:04:13Z jamie $");
+
+#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;
+
+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},
+    [IP_MOUNT_DEVFS] =		{"mount.devfs",		PF_INTERNAL | PF_BOOL},
+    [IP_MOUNT_DEVFS_RULESET]=	{"mount.devfs.ruleset",	PF_INTERNAL},
+    [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},
+#endif
+#ifdef INET6
+    [IP__IP6_IFADDR] =		{"ip6.addr",		PF_INTERNAL | PF_CONV},
+#endif
+    [IP__MOUNT_FROM_FSTAB] =	{"mount.fstab",		PF_INTERNAL | PF_CONV},
+    [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_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",			0},
+    [KP_NAME] =			{"name",		0},
+    [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;
+	char *ep;
+	size_t varoff;
+	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) {
+			varoff = 0;
+			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 + varoff + vs->len,
+				    s->s + v->pos + varoff,
+				    s->len - (v->pos + varoff) + 1);
+				memcpy(s->s + v->pos + varoff, vs->s, vs->len);
+				varoff += 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 != 0) {
+			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 != 0)
+		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 (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 != 0)
+			j->intparams[ipnum] = np;
+		else
+			for (ipnum = 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,
+	 * and a netmask/suffix for that address.
+	 */
+	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 + 1;
+			}
+		}
+	}
+#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 + 1;
+			}
+		}
+	}
+#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);
+			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) {
+				strcpy(cs, s->s);
+				if (ts != NULL) {
+					cs += s->len + 1;
+					cs[-1] = ',';
+				}
+			}
+		}
+		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);
+	}
+}
Index: jail.conf.5
===================================================================
--- jail.conf.5	(revision 223747)
+++ jail.conf.5	(working copy)
@@ -0,0 +1,231 @@
+.\" 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: projects/jailconf/usr.sbin/jail/jail.conf.5 223190 2011-06-17 16:21:03Z jamie $
+.\"
+.Dd October 20, 2010
+.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 assigment,
+including a terminating semicolon.
+.Pp
+The general syntax of a jail definition is:
+.Pp
+.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 undescores, 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 preceeding 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 preceeded 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 substition 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 preceeded 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
+}
+.Sh SEE ALSO
+.Xr jail_set 2
+.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.0 .
+.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.
Index: jaillex.l
===================================================================
--- jaillex.l	(revision 223747)
+++ jaillex.l	(working copy)
@@ -0,0 +1,232 @@
+%{
+/*-
+ * 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: projects/jailconf/usr.sbin/jail/jaillex.l 223190 2011-06-17 16:21:03Z jamie $");
+
+#include <err.h>
+#include <stddef.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include "jailp.h"
+#include "y.tab.h"
+
+#define YY_NO_UNPUT
+
+extern int yynerrs;
+
+static ssize_t text2lval(size_t triml, size_t trimr, int tovar);
+
+static int instr;
+static int lineno = 1;
+%}
+
+%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;
+}
Index: jailp.h
===================================================================
--- jailp.h	(revision 223747)
+++ jailp.h	(working copy)
@@ -0,0 +1,231 @@
+/*-
+ * 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: projects/jailconf/usr.sbin/jail/jailp.h 223351 2011-06-20 23:04:13Z jamie $
+ */
+
+#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 propigate 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 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_ALLOW_DYING = 1,	/* 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_DEVFS_RULESET,	/* Ruleset for the devfs mount */
+	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_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, unsigned state, int running);
+extern void requeue(struct cfjail *j, struct cfjails *queue);
+
+extern void yyerror(const char *);
+extern int yylex(void);
+extern int yyparse(void);
+
+extern struct cfjails cfjails;
+extern struct cfjails ready;
+extern struct cfjails depend;
+extern const char *cfname;
+extern int note_remove;
+extern int paralimit;
+extern int verbose;
Index: jailparse.y
===================================================================
--- jailparse.y	(revision 223747)
+++ jailparse.y	(working copy)
@@ -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: projects/jailconf/usr.sbin/jail/jailparse.y 223190 2011-06-17 16:21:03Z jamie $");
+
+#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 "=" preceeding 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);
+	}
+	;
+
+%%
Index: state.c
===================================================================
--- state.c	(revision 223747)
+++ state.c	(working copy)
@@ -0,0 +1,466 @@
+/*-
+ * 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: projects/jailconf/usr.sbin/jail/state.c 223190 2011-06-17 16:21:03Z jamie $");
+
+#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 error, 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);
+	error = 0;
+	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);
+		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, unsigned state, int running)
+{
+	struct iovec jiov[6];
+	struct cfjail *j, *tj;
+	int jid;
+	char namebuf[MAXHOSTNAMELEN];
+
+	if (!target || (!running && !strcmp(target, "*"))) {
+		/*
+		 * If there's 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 | JF_WILD;
+			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.
+			 */
+			*(const void **)&jiov[0].iov_base = "lastjid";
+			jiov[0].iov_len = sizeof("lastjid");
+			jiov[1].iov_base = &jid;
+			jiov[1].iov_len = sizeof(jid);
+			*(const void **)&jiov[2].iov_base = "jid";
+			jiov[2].iov_len = sizeof("jid");
+			jiov[3].iov_base = &jid;
+			jiov[3].iov_len = sizeof(jid);
+			*(const void **)&jiov[4].iov_base = "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) {
+		*(const void **)&jiov[0].iov_base = "jid";
+		jiov[0].iov_len = sizeof("jid");
+		jiov[1].iov_base = &jid;
+		jiov[1].iov_len = sizeof(jid);
+	} else {
+		*(const void **)&jiov[0].iov_base = "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);
+}
Index: jail.8
===================================================================
--- jail.8	(revision 223747)
+++ jail.8	(working copy)
@@ -1,6 +1,5 @@
-.\"
 .\" Copyright (c) 2000, 2003 Robert N. M. Watson
-.\" Copyright (c) 2008 James Gritton
+.\" Copyright (c) 2008-2011 James Gritton
 .\" All rights reserved.
 .\"
 .\" Redistribution and use in source and binary forms, with or without
@@ -24,186 +23,261 @@
 .\" OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
 .\" SUCH DAMAGE.
 .\"
+.\" $FreeBSD: projects/jailconf/usr.sbin/jail/jail.8 223190 2011-06-17 16:21:03Z jamie $
 .\"
-.\" ----------------------------------------------------------------------------
-.\" "THE BEER-WARE LICENSE" (Revision 42):
-.\" <phk@FreeBSD.org> wrote this file.  As long as you retain this notice you
-.\" can do whatever you want with this stuff. If we meet some day, and you think
-.\" this stuff is worth it, you can buy me a beer in return.   Poul-Henning Kamp
-.\" ----------------------------------------------------------------------------
-.\"
-.\" $FreeBSD: head/usr.sbin/jail/jail.8 221665 2011-05-08 14:57:01Z bcr $
-.\"
-.Dd January 17, 2010
+.Dd October 20, 2010
 .Dt JAIL 8
 .Os
 .Sh NAME
 .Nm jail
-.Nd "create or modify a system jail"
+.Nd "manage system jails"
 .Sh SYNOPSIS
 .Nm
-.Op Fl dhi
+.Op Fl dhilqv
 .Op Fl J Ar jid_file
-.Op Fl l u Ar username | Fl U Ar username
-.Op Fl c | m
-.Op Ar parameter=value ...
+.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 hi
-.Op Fl n Ar jailname
+.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 Fl l u Ar username | Fl U Ar username
-.Op Ar path hostname [ip[,..]] command ...
-.Nm
-.Op Fl r Ar jail
+.Op Ar path hostname [ Ar ip Ns [ Ns Ar ,... Ns ]] Ar command ...
 .Sh DESCRIPTION
 The
 .Nm
-utility creates a new jail or modifies an existing jail, optionally
-imprisoning the current process (and future descendants) inside it.
+utility creates new jails, or modifies or removes existing jails.
+A jail is specified via parameters on the command line, or in the
+.Xr jail.conf 5
+file.
 .Pp
-The options are as follows:
+At least one of the options
+.Fl c ,
+.Fl m
+or
+.Fl r
+must be specified.
+These options are used alone or in combination 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,
+or any jails 
+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 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 c
+and
+.Dq Nm Fl r
+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.
+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
-.Va ip
-addresses for this prison.
-This may affect default address selection for outgoing IPv4 connections
-of prisons.
-The address first returned by the resolver for each address family
-will be used as primary address.
-See the
-.Va ip4.addr
-and
-.Va ip6.addr
-parameters further down for details.
-.It Fl i
-Output the jail identifier of the newly created jail.
-.It Fl n Ar jailname
-Set the jail's name.
-This is deprecated and is equivalent to setting the
-.Va name
+to the list of addresses for this prison.
+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 jail identifier, path, hostname, IP and
-command used to start the jail.
+file, containing parameters used to start the jail.
 .It Fl l
-Run program in the 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.
+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 - 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 setting the
+This is deprecated and is equivalent to the
 .Va securelevel
 parameter.
 .It Fl u Ar username
-The user name from host environment as whom the
-.Ar command
-should run.
-.It Fl U Ar username
-The user name from jailed environment as whom the
-.Ar command
-should run.
-.It Fl c
-Create a new jail.
-The
-.Va jid
+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 name
-parameters (if specified) 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.
-.It Fl cm
-Create a jail if it does not exist, or modify a jail if it does exist.
-.It Fl r
-Remove the
-.Ar jail
-specified by jid or name.
-All jailed processes are killed, and all children of this jail are also
-removed.
+.Va exec.system_jail_user
+parameters.
+.It Fl U Ar username
+The user name from 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
-At least one of the
-.Fl c ,
-.Fl m
-or
+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
-options must be specified.
-.Pp
-.Ar Parameters
-are listed in
-.Dq name=value
-form, following the options.
-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 .
-Any parameters not set will be given default values, often based on the
-current environment.
+and
+.Fl R
+options can also remove running jails that aren't in the
+.Xr jail.conf 5
+file, specified by name or jid.
+.P
+An argument of
+.Dq *
+is a wildcard that will operate on all jails.  To prevent errors,
+this is the only 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
-The pseudo-parameter
-.Va command
-specifies that the current process should enter the new (or modified) jail,
-and run the specified command.
-It must be the last parameter specified, because it includes not only
-the value following the
-.Sq =
-sign, but also passes the rest of the arguments to the command.
-.Pp
-Instead of supplying named
-.Ar parameters ,
-four fixed parameters may be supplied in order on the command line:
+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 .
-As the
-.Va jid
-and
-.Va name
-parameters aren't in this list, this mode will always create a new jail, and
-the
+This mode will always create a new jail, and the
 .Fl c
 and
 .Fl m
 options don't apply (and must not exist).
+.Ss Jail Parameters
+Parameters in the
+.Xr jail.conf 5
+file, or on the command line, are generally in
+.Dq name=value
+form.
+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 partameters 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
-Jails have a set a core parameters, and modules can add their own jail
-parameters.
+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,
+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 a 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
@@ -231,14 +305,21 @@
 .Va name
 is supplied, a default is assumed that is the same as the
 .Va jid .
-.It Va path
-Directory which is to be the root of the prison.
 The
-.Va command
-(if any) is run from this directory, as are commands from
-.Xr jexec 8 .
+.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 prison.
+Any commands run inside the prison, either by
+.Nm
+or from
+.Xr jexec 8 ,
+are run from this directory.
 .It Va ip4.addr
-A comma-separated list of IPv4 addresses assigned to the prison.
+A list of IPv4 addresses assigned to the prison.
 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.
@@ -252,7 +333,7 @@
 A boolean option to change the formerly mentioned behaviour and disable
 IPv4 source address selection for the prison in favour of the primary
 IPv4 address of the jail.
-Source address selection is enabled by default for all jails and a
+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
@@ -277,8 +358,20 @@
 and
 .Va ip4
 above.
+.It vnet
+Create the prison 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
-Hostname of the prison.
+The hostname of the prison.
 Other similar parameters are
 .Va host.domainname ,
 .Va host.hostuuid
@@ -307,7 +400,7 @@
 This limit is zero by default, indicating the jail is not allowed to
 create child jails.
 See the
-.Va "Hierarchical Jails"
+.Sx "Hierarchical Jails"
 section for more information.
 .It Va children.cur
 The number of descendents of this jail, including its own child jails
@@ -332,10 +425,13 @@
 .It Va persist
 Setting this boolean parameter allows a jail to exist without any
 processes.
-Normally, a jail is destroyed as its last process exits.
+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
@@ -404,6 +500,176 @@
 .El
 .El
 .Pp
+There are pseudo-parameters that aren't passed to the kernel, but are
+used by
+.Nm
+to set up the prison 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 prison environment.
+They may be given multiple values, which run would the specified
+commands in sequence.
+All commands must succed (return a zero exit status), or the jail will
+not be created or removed.
+.Pp
+The pseudo-parameters are:
+.Bl -tag -width indent
+.It Va exec.prestart
+Command(s) to run in the system environment before a prison is created.
+.It Va exec.start
+Command(s) to run in the prison 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 prison 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 prison 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 prison 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 prison'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.
+If a command is still running after this many seconds have passed,
+the jail not be created or removed.
+.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 prison.
+.It Va stop.timeout
+The maximum amount of time to wait for a prison'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 prison will be removed, which
+will kill any remaining processes.
+If this is set to zero, no
+.Dv SIGTERM
+is sent and the prison is immediately removed.
+The default is 10 seconds.
+.It Va interface
+A network interface to add the prison's IP addresses
+.Va ( ip4.addr
+and
+.Va ip6.addr )
+to.
+An alias for each address will be added to the interface before the
+prison is created, and will be removed from the interface after the
+prison is removed.
+.It Op Va ip4.addr
+In addition to the IP addresses that are passed to the kernel, and
+interface and/or a netmask may also be specified, in the form
+.Dq Ar interface Ns | Ns Ar ip-address Ns / Ns Ar netmask .
+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 IP address, it will be used when adding the IP alias.
+.It Op Va ip6.addr
+In addition to the IP addresses that are passed to the kernel,
+and interface and/or a prefix may also be specified, in the form
+.Dq Ar interface Ns | Ns Ar ip-address Ns / Ns Ar prefix .
+.It Va vnet.interface
+A network interface to give to a vnet-enabled jail after is it created.
+The interface will automatically be returned 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
+.Va ( ip4.addr
+or
+.Va ip6.addr )
+for this prison.
+This may affect default address selection for outgoing IPv4 connections
+of prisons.
+The address first returned by the resolver for each address family
+will be used as 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
+ filesystem on the chrooted /dev directory, and run
+.Xr devfs 8
+to restrict the devices visible inside the prison.
+.It Va mount.devfs.ruleset
+The ruleset from
+.Xr devfs.rules 5
+to use when mounting a devfs filesystem.
+The default ruleset is
+.Dq devfsrules_jail .
+.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
@@ -421,7 +687,6 @@
 This manual page documents the configuration steps necessary to support
 either of these steps, although the configuration steps may be
 refined based on local requirements.
-.Sh EXAMPLES
 .Ss "Setting up a Jail Directory Tree"
 To set up a jail directory tree containing an entire
 .Fx
@@ -434,20 +699,8 @@
 mkdir -p $D
 make world DESTDIR=$D
 make distribution DESTDIR=$D
-mount -t devfs devfs $D/dev
 .Ed
 .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 .
-.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.
@@ -465,8 +718,9 @@
 to build the jail directory tree.
 For the sake of this example, we will
 assume you built it in
-.Pa /data/jail/192.0.2.100 ,
-named for the jailed IP address.
+.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"
@@ -563,8 +817,9 @@
 or for running a virtual server.
 .Pp
 Start a shell in the jail:
+.Pp
 .Bd -literal -offset indent
-jail -c path=/data/jail/192.0.2.100 host.hostname=testhostname \\
+jail -c path=/data/jail/testjail mount.devfs host.hostname=testhostname \\
 	ip4.addr=192.0.2.100 command=/bin/sh
 .Ed
 .Pp
@@ -578,15 +833,6 @@
 .Pp
 .Bl -bullet -offset indent -compact
 .It
-Create an empty
-.Pa /etc/fstab
-to quell startup warnings about missing fstab (virtual server only)
-.It
-Disable the port mapper
-.Pa ( /etc/rc.conf :
-.Li rpcbind_enable="NO" )
-(virtual server only)
-.It
 Configure
 .Pa /etc/resolv.conf
 so that name resolution within the jail will work correctly
@@ -597,11 +843,6 @@
 .Xr sendmail 8
 warnings.
 .It
-Disable interface configuration to quell startup warnings about
-.Xr ifconfig 8
-.Pq Li network_interfaces=""
-(virtual server only)
-.It
 Set a root password, probably different from the real host system
 .It
 Set the timezone
@@ -619,36 +860,47 @@
 .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/192.0.2.100/var/run/log .
+.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.
-If you are running a single application in the jail, substitute the
-command used to start the application for
-.Pa /etc/rc
-in the examples below.
+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.
-To do this, first bring up the
-virtual host interface, and then start the jail's
-.Pa /etc/rc
-script from within the jail.
+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
-ifconfig ed0 inet alias 192.0.2.100/32
-mount -t procfs proc /data/jail/192.0.2.100/proc
-jail -c path=/data/jail/192.0.2.100 host.hostname=testhostname \\
-	ip4.addr=192.0.2.100 command=/bin/sh /etc/rc
+jail -c testjail
 .Ed
 .Pp
-A few warnings will be produced, because most
-.Xr sysctl 8
-configuration variables cannot be set from within the jail, as they are
-global across all jails and the host environment.
-However, it should all
-work properly.
+A few warnings may be produced; however, it should all work properly.
 You should be able to see
 .Xr inetd 8 ,
 .Xr syslogd 8 ,
@@ -671,15 +923,6 @@
 variables in
 .Xr rc.conf 5
 for more information.
-The
-.Xr rc 8
-jail script provides a flexible system to start/stop jails:
-.Bd -literal
-/etc/rc.d/jail start
-/etc/rc.d/jail stop
-/etc/rc.d/jail start myjail
-/etc/rc.d/jail stop myjail
-.Ed
 .Ss "Managing the Jail"
 Normal machine shutdown commands, such as
 .Xr halt 8 ,
@@ -687,9 +930,9 @@
 and
 .Xr shutdown 8 ,
 cannot be used successfully within the jail.
-To kill all processes in a
-jail, you may log into the jail and, as root, use one of the following
-commands, depending on what you want to accomplish:
+To kill all processes from within a jail, you may use one of the
+following commands, depending on what you want to accomplish:
+.Pp
 .Bd -literal -offset indent
 kill -TERM -1
 kill -KILL -1
@@ -699,21 +942,27 @@
 .Dv SIGTERM
 or
 .Dv SIGKILL
-signals to all processes in the jail from within the jail.
+signals to all processes in the jail - 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.
-To kill processes from outside the jail, use the
-.Xr jexec 8
-utility in conjunction with the one of the
-.Xr kill 1
-commands above.
-You may also remove the jail with
+.Pp
+To shut down the jail from the outside, simply remove it with
 .Nm
 .Ar -r ,
-which will killall the jail's processes with
-.Dv SIGKILL .
+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
@@ -831,7 +1080,7 @@
 .Pp
 Like the names, a child jail's
 .Va path
-is relative to its creator's own
+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.
@@ -843,12 +1092,12 @@
 .Xr pkill 1 ,
 .Xr ps 1 ,
 .Xr quota 1 ,
-.Xr chroot 2 ,
 .Xr jail_set 2 ,
-.Xr jail_attach 2 ,
+.Xr jail.conf 5 ,
 .Xr procfs 5 ,
 .Xr rc.conf 5 ,
 .Xr sysctl.conf 5 ,
+.Xr chroot 8 ,
 .Xr devfs 8 ,
 .Xr halt 8 ,
 .Xr inetd 8 ,
@@ -890,14 +1139,10 @@
 for IPv4.
 .Pp
 .An James Gritton
-added the extensible jail parameters and hierarchical jails.
+added the extensible jail parameters, hierarchical jails,
+and the configuration file.
 .Sh BUGS
-Jail currently lacks the ability to allow access to
-specific jail information via
-.Xr ps 1
-as opposed to
-.Xr procfs 5 .
-Similarly, it might be a good idea to add an
+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

--------------050909020909040607030101--



Want to link to this message? Use this URL: <https://mail-archive.FreeBSD.org/cgi/mid.cgi?4E114EA9.4000605>