diff options
author | jamie <jamie@FreeBSD.org> | 2010-10-20 20:42:33 +0000 |
---|---|---|
committer | jamie <jamie@FreeBSD.org> | 2010-10-20 20:42:33 +0000 |
commit | 3b31921eb1179730750d3f91afe80cd48a49aa95 (patch) | |
tree | b85cf3b1386aa9169f30b2a77e955268676e2a61 /usr.sbin/jail | |
parent | 09f9c897d33c41618ada06fbbcf1a9b3812dee53 (diff) | |
download | FreeBSD-src-3b31921eb1179730750d3f91afe80cd48a49aa95.zip FreeBSD-src-3b31921eb1179730750d3f91afe80cd48a49aa95.tar.gz |
Initial work on the new jail(8). There are more features to add, and some
cleaning up to do on existing features, but this is pretty much what the
final product will look like.
Diffstat (limited to 'usr.sbin/jail')
-rw-r--r-- | usr.sbin/jail/Makefile | 13 | ||||
-rw-r--r-- | usr.sbin/jail/command.c | 675 | ||||
-rw-r--r-- | usr.sbin/jail/config.c | 786 | ||||
-rw-r--r-- | usr.sbin/jail/jail.8 | 685 | ||||
-rw-r--r-- | usr.sbin/jail/jail.c | 1255 | ||||
-rw-r--r-- | usr.sbin/jail/jail.conf.5 | 231 | ||||
-rw-r--r-- | usr.sbin/jail/jaillex.l | 232 | ||||
-rw-r--r-- | usr.sbin/jail/jailp.h | 218 | ||||
-rw-r--r-- | usr.sbin/jail/jailparse.y | 216 | ||||
-rw-r--r-- | usr.sbin/jail/state.c | 466 |
10 files changed, 4169 insertions, 608 deletions
diff --git a/usr.sbin/jail/Makefile b/usr.sbin/jail/Makefile index de35dcf..3e9e5f7 100644 --- a/usr.sbin/jail/Makefile +++ b/usr.sbin/jail/Makefile @@ -3,12 +3,19 @@ .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 .endif +CLEANFILES= y.output + .include <bsd.prog.mk> diff --git a/usr.sbin/jail/command.c b/usr.sbin/jail/command.c new file mode 100644 index 0000000..437096a --- /dev/null +++ b/usr.sbin/jail/command.c @@ -0,0 +1,675 @@ +/*- + * Copyright (c) 2010 James Gritton + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +#include <sys/cdefs.h> +__FBSDID("$FreeBSD$"); + +#include <sys/types.h> +#include <sys/event.h> +#include <sys/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 COMSTRING_DUMMY ((struct cfstring *)1) +#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; +}; + +extern char **environ; + +static int get_user_info(struct cfjail *j, const char *username, + const struct passwd **pwdp, login_cap_t **lcapp); +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 struct cfjails sleeping = TAILQ_HEAD_INITIALIZER(sleeping); +static struct cfjails runnable = TAILQ_HEAD_INITIALIZER(runnable); +static struct phhead phash[PHASH_SIZE]; +static int kq; + +/* + * Run a command associated with a jail, possibly inside the jail. + */ +int +run_command(struct cfjail *j, int *plimit, enum intparam comparam) +{ + const struct passwd *pwd; + struct cfstring *comstring, *s; + login_cap_t *lcap; + char **argv; + char *cs, *addr, *comcs; + const char *jidstr, *conslog, *path, *ruleset, *term, *username; + size_t comlen; + pid_t pid; + int argc, bg, clean, consfd, down, fib, i, injail, sjuser, timeout; + + static char *cleanenv; + + if (comparam) { + if (comparam == IP_MOUNT_DEVFS + ? !bool_param(j->intparams[IP_MOUNT_DEVFS]) + : j->intparams[comparam] == NULL) + return 0; + j->comparam = comparam; + j->comstring = comparam == IP_MOUNT_DEVFS ? COMSTRING_DUMMY + : STAILQ_FIRST(&j->intparams[comparam]->val); + } else { + comparam = j->comparam; + if (!(j->flags & JF_RUNQ)) + j->comstring = j->comstring == COMSTRING_DUMMY + ? NULL : STAILQ_NEXT(j->comstring, tq); + } + comstring = j->comstring; + if (comstring == NULL || + (comstring != COMSTRING_DUMMY && comstring->len == 0)) + return 0; + if (plimit && *plimit == 0) { + j->flags |= JF_RUNQ; + requeue(j, &runnable); + return 1; + } + j->flags &= ~(JF_RUNQ | JF_BACKGROUND); + /* + * Collect exec arguments. Internal commands for network and + * mounting build their own argument lists (XXX they should be + * truly internal). + */ + bg = j->flags & JF_FAILED; + down = j->flags & (JF_STOP | JF_FAILED); + if (comparam == 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; + j->flags |= JF_IFUP; +#ifdef INET6 + } else if (comparam == 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; + j->flags |= JF_IFUP; +#endif + } else if (comparam == 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; + j->flags |= JF_IFUP; + } else if (comparam == IP_MOUNT) { + 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 < 3) { + jail_warnx(j, "mount: %s: missing information", + comstring->s); + failed(j); + 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"; + j->flags |= JF_MOUNTED; + } else if (comparam == IP_MOUNT_FSTAB) { + argv = alloca(4 * sizeof(char *)); + *(const char **)&argv[0] = down ? "/sbin/umount" : _PATH_MOUNT; + *(const char **)&argv[1] = "-aF"; + argv[2] = comstring->s; + argv[3] = NULL; + j->flags |= JF_MOUNTED; + } else if (comparam == IP_MOUNT_DEVFS) { + path = string_param(j->intparams[KP_PATH]); + if (path == NULL) { + jail_warnx(j, "mount.devfs: no path"); + failed(j); + return -1; + } + if (down) { + argv = alloca(3 * sizeof(char *)); + *(const char **)&argv[0] = "/sbin/umount"; + argv[1] = alloca(strlen(path) + 5); + sprintf(argv[1], "%s/dev", path); + 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; + } + j->flags |= JF_MOUNTED; + } else if (comparam == IP_COMMAND && j->name == NULL) { + argc = 0; + STAILQ_FOREACH(s, &j->intparams[IP_COMMAND]->val, tq) + argc++; + argv = alloca((argc + 1) * sizeof(char *)); + argc = 0; + STAILQ_FOREACH(s, &j->intparams[IP_COMMAND]->val, tq) + argv[argc++] = s->s; + argv[argc] = NULL; + j->comstring = NULL; + } else 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; + + j->pstatus = 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]))) { + consfd = + open(conslog, O_WRONLY | O_CREAT | O_APPEND, DEFFILEMODE); + if (consfd < 0) { + jail_warnx(j, "open %s: %s", conslog, strerror(errno)); + failed(j); + 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) { + j->flags |= JF_BACKGROUND; + requeue(j, &ready); + } else { + --*plimit; + add_proc(j, pid); + } + return 1; + } + if (bg) + setsid(); + + 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); +} + +/* + * Check command exit status + */ +int +finish_command(struct cfjail *j, int *plimit) +{ + int error; + + if (j->flags & (JF_RUNQ | JF_BACKGROUND)) + return 0; + ++*plimit; + 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); + failed(j); + error = -1; + } + free(j->comline); + return error; +} + +/* + * Check for finished processed 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; +} + +/* + * Send SIGTERM to all processes in a jail and wait for them to die. + */ +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; +} + +/* + * 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++; + if (j->timeout.tv_sec) { + 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; + } else + requeue(j, &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; +} + +/* + * 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; +} diff --git a/usr.sbin/jail/config.c b/usr.sbin/jail/config.c new file mode 100644 index 0000000..f7f5b92 --- /dev/null +++ b/usr.sbin/jail/config.c @@ -0,0 +1,786 @@ +/*- + * Copyright (c) 2010 James Gritton + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +#include <sys/cdefs.h> +__FBSDID("$FreeBSD$"); + +#include <sys/types.h> +#include <sys/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 "jailp.h" + +struct ipspec { + const char *name; + enum intparam ipnum; + unsigned flags; +}; + +extern FILE *yyin; +extern int yynerrs; + +struct cfjails cfjails = TAILQ_HEAD_INITIALIZER(cfjails); + +static int cmp_intparam(const void *a, const void *b); +static void free_param(struct cfparams *pp, struct cfparam *p); +static void free_param_strings(struct cfparam *p); + +/* Note these must be in sort order */ +static const struct ipspec intparams[] = { + {"allow.dying", IP_ALLOW_DYING, PF_INTERNAL | PF_BOOL }, + {"allow.nodying", IP_ALLOW_DYING, PF_INTERNAL | PF_BOOL }, + {"command", IP_COMMAND, PF_INTERNAL }, + {"depend", IP_DEPEND, PF_INTERNAL }, + {"exec.clean", IP_EXEC_CLEAN, PF_INTERNAL | PF_BOOL }, + {"exec.consolelog", IP_EXEC_CONSOLELOG, PF_INTERNAL }, + {"exec.fib", IP_EXEC_FIB, PF_INTERNAL | PF_INT }, + {"exec.jail_user", IP_EXEC_JAIL_USER, PF_INTERNAL }, + {"exec.noclean", IP_EXEC_CLEAN, PF_INTERNAL | PF_BOOL }, + {"exec.nosystem_jail_user",IP_EXEC_SYSTEM_JAIL_USER,PF_INTERNAL | PF_BOOL }, + {"exec.poststart", IP_EXEC_POSTSTART, PF_INTERNAL }, + {"exec.poststop", IP_EXEC_POSTSTOP, PF_INTERNAL }, + {"exec.prestart", IP_EXEC_PRESTART, PF_INTERNAL }, + {"exec.prestop", IP_EXEC_PRESTOP, PF_INTERNAL }, + {"exec.start", IP_EXEC_START, PF_INTERNAL }, + {"exec.stop", IP_EXEC_STOP, PF_INTERNAL }, + {"exec.system_jail_user", IP_EXEC_SYSTEM_JAIL_USER, PF_INTERNAL | PF_BOOL }, + {"exec.system_user", IP_EXEC_SYSTEM_USER, PF_INTERNAL }, + {"exec.timeout", IP_EXEC_TIMEOUT, PF_INTERNAL | PF_INT }, + {"host.hostname", KP_HOSTNAME, 0 }, + {"interface", IP_INTERFACE, PF_INTERNAL }, + {"ip4.addr", KP_IP4_ADDR, 0 }, +#ifdef INET6 + {"ip6.addr", KP_IP6_ADDR, 0 }, +#endif + {"ip_hostname", IP_IP_HOSTNAME, PF_INTERNAL | PF_BOOL }, + {"jid", KP_JID, PF_INT }, + {"mount", IP_MOUNT, PF_INTERNAL }, + {"mount.devfs", IP_MOUNT_DEVFS, PF_INTERNAL | PF_BOOL }, + {"mount.devfs.ruleset", IP_MOUNT_DEVFS_RULESET, PF_INTERNAL }, + {"mount.fstab", IP_MOUNT_FSTAB, PF_INTERNAL }, + {"mount.nodevfs", IP_MOUNT_DEVFS, PF_INTERNAL | PF_BOOL }, + {"name", KP_NAME, 0 }, + {"noip_hostname", IP_IP_HOSTNAME, PF_INTERNAL | PF_BOOL }, + {"nopersist", KP_PERSIST, PF_BOOL }, + {"path", KP_PATH, 0 }, + {"persist", KP_PERSIST, PF_BOOL }, + {"stop.timeout", IP_STOP_TIMEOUT, PF_INTERNAL | PF_INT }, + {"vnet", KP_VNET, 0 }, + {"vnet.interface", IP_VNET_INTERFACE, PF_INTERNAL }, +}; + +/* + * 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 ? "jid" : "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, NULL, NULL); + did_self = 1; + } + if (wild_jail_match(j->name, wj->name)) + TAILQ_FOREACH(p, &wj->params, tq) + add_param(j, p, NULL, NULL); + } + if (!did_self) + TAILQ_FOREACH(p, &opp, tq) + add_param(j, p, NULL, NULL); + + /* Resolve any variable substitutions. */ + pgen = 0; + TAILQ_FOREACH(p, &j->params, tq) { + p->gen = ++pgen; + find_vars: + STAILQ_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; + } + STAILQ_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 = STAILQ_FIRST(&vp->val); + if (STAILQ_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 = STAILQ_NEXT(vs, tq))) { + ns = emalloc(sizeof(struct cfstring)); + ns->s = estrdup(vs->s); + ns->len = vs->len; + STAILQ_INIT(&ns->vars); + STAILQ_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, const char *name, + const char *value) +{ + struct cfstrings nss; + struct cfparam *dp, *np; + struct cfstring *s, *ns; + struct cfvar *v, *nv; + 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(); + } + STAILQ_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. + */ + STAILQ_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); + } + STAILQ_INSERT_TAIL(&nss, ns, tq); + } + } else { + flags = PF_APPEND; + if (value != NULL) { + ns = emalloc(sizeof(struct cfstring)); + ns->s = estrdup(value); + ns->len = strlen(value); + STAILQ_INIT(&ns->vars); + STAILQ_INSERT_TAIL(&nss, ns, tq); + } + } + + /* See if this parameter has already been added. */ + TAILQ_FOREACH(dp, &j->params, tq) { + if (equalopts(dp->name, name)) { + /* Found it - append or replace. */ + if (strcmp(dp->name, name)) { + free(dp->name); + dp->name = estrdup(name); + } + if (!(flags & PF_APPEND) || STAILQ_EMPTY(&nss)) + free_param_strings(dp); + STAILQ_CONCAT(&dp->val, &nss); + dp->flags |= flags; + break; + } + } + if (dp == NULL) { + /* Not found - add it. */ + np = emalloc(sizeof(struct cfparam)); + np->name = estrdup(name); + STAILQ_INIT(&np->val); + STAILQ_CONCAT(&np->val, &nss); + np->flags = flags; + np->gen = 0; + TAILQ_INSERT_TAIL(&j->params, np, tq); + } +} + +/* + * Find internal or known parameters. + */ +void +find_intparams(void) +{ + struct cfjail *j; + struct cfparam *p; + struct ipspec *ip; + + TAILQ_FOREACH(j, &cfjails, tq) { + TAILQ_FOREACH(p, &j->params, tq) { + ip = bsearch(p->name, intparams, + sizeof(intparams) / sizeof(intparams[0]), + sizeof(struct ipspec), cmp_intparam); + if (ip != NULL) { + j->intparams[ip->ipnum] = p; + p->flags |= ip->flags; + } + } + } +} + +/* + * Check syntax of internal parameters. + */ +int +check_intparams(struct cfjail *j) +{ + struct cfparam *p; + const char *val; + char *ep; + int error; + + error = 0; + TAILQ_FOREACH(p, &j->params, tq) { + if (!STAILQ_EMPTY(&p->val) && + (p->flags & (PF_BOOL | PF_INT))) { + val = STAILQ_LAST(&p->val, cfstring, tq)->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; + } + } + } + } + return error; +} + +/* + * 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) ^ + (STAILQ_EMPTY(&p->val) || + !strcasecmp(STAILQ_LAST(&p->val, cfstring, tq)->s, "true") || + (strtol(STAILQ_LAST(&p->val, cfstring, tq)->s, NULL, 10))); +} + +/* + * Set an integer if a parameter if it exists. + */ +int +int_param(const struct cfparam *p, int *ip) +{ + if (p == NULL || STAILQ_EMPTY(&p->val)) + return 0; + *ip = strtol(STAILQ_LAST(&p->val, cfstring, tq)->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 && !STAILQ_EMPTY(&p->val) + ? STAILQ_LAST(&p->val, cfstring, tq)->s : NULL); +} + +/* + * Look up extra IP addresses from the hostname and save interface and netmask. + */ +int +ip_params(struct cfjail *j) +{ + struct in_addr addr4; + struct addrinfo hints, *ai0, *ai; + struct cfparam *np; + struct cfstring *s, *ns; + char *cs, *ep; + const char *hostname; + size_t size; + int error, ip4ok, defif, prefix; + int mib[4]; + char avalue4[INET_ADDRSTRLEN]; +#ifdef INET6 + struct in6_addr addr6; + int ip6ok, isip6; + char avalue6[INET6_ADDRSTRLEN]; +#endif + + error = 0; + /* + * The ip_hostname parameter looks up the hostname, and adds parameters + * for any IP addresses it finds. + */ + if (bool_param(j->intparams[IP_IP_HOSTNAME]) && + (hostname = string_param(j->intparams[KP_HOSTNAME]))) { + j->intparams[IP_IP_HOSTNAME] = NULL; + /* + * Silently ignore unsupported address families from + * DNS lookups. + */ + size = 4; + ip4ok = sysctlnametomib("security.jail.param.ip4", mib, &size) + == 0; +#ifdef INET6 + size = 4; + ip6ok = sysctlnametomib("security.jail.param.ip6", mib, &size) + == 0; +#endif + if (ip4ok +#ifdef INET6 + || ip6ok +#endif + ) { + /* Look up the hostname (or get the address) */ + memset(&hints, 0, sizeof(hints)); + hints.ai_socktype = SOCK_STREAM; + hints.ai_family = +#ifdef INET6 + ip6ok ? (ip4ok ? PF_UNSPEC : PF_INET6) : +#endif + PF_INET; + error = getaddrinfo(hostname, NULL, &hints, &ai0); + if (error != 0) { + jail_warnx(j, "host.hostname %s: %s", hostname, + gai_strerror(error)); + 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) { + 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, "ip4.addr", + avalue4); + break; +#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, "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 INET6 + for (isip6 = 0; isip6 <= 1; isip6++) +#else +#define isip6 0 + do +#endif + { + if (j->intparams[KP_IP4_ADDR + isip6] == NULL) + continue; + np = j->intparams[IP__IP4_IFADDR + isip6]; + STAILQ_FOREACH(s, &j->intparams[KP_IP4_ADDR + isip6]->val, tq) { + cs = strchr(s->s, '|'); + if (cs || defif) { + if (np == NULL) { + np = j->intparams[IP__IP4_IFADDR + + isip6] = + emalloc(sizeof(struct cfparam)); + np->name = estrdup(j->intparams + [KP_IP4_ADDR + isip6]->name); + STAILQ_INIT(&np->val); + np->flags = PF_INTERNAL; + } + ns = emalloc(sizeof(struct cfstring)); + ns->s = estrdup(s->s); + ns->len = s->len; + STAILQ_INIT(&ns->vars); + STAILQ_INSERT_TAIL(&np->val, ns, tq); + if (cs != NULL) { + strcpy(s->s, cs + 1); + s->len -= cs - s->s + 1; + } + } + if ((cs = strchr(s->s, '/'))) { + prefix = strtol(cs + 1, &ep, 10); + if (!isip6 && *ep == '.' + ? inet_pton(AF_INET, cs + 1, &addr4) != 1 + : *ep || prefix < 0 || prefix > + (isip6 ? 128 : 32)) { + jail_warnx(j, isip6 + ? "ip6.addr: bad prefixlen \"%s\"" + : "ip4.addr: bad netmask \"%s\"", + cs); + error = -1; + } + *cs = '\0'; + s->len = cs - s->s + 1; + } + } + } +#ifndef INET6 + while (0); +#endif + 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 (STAILQ_EMPTY(&p->val)) + value = NULL; + else if (!jp->jp_elemlen || + !STAILQ_NEXT(STAILQ_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 = STAILQ_LAST(&p->val, cfstring, tq)->s; + } else { + /* + * Convert arrays into comma-separated strings, which + * jailparam_import will then convert back into arrays. + */ + vallen = 0; + STAILQ_FOREACH(s, &p->val, tq) + vallen += s->len + 1; + value = alloca(vallen); + cs = value; + STAILQ_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; +} + +/* + * Compare strings and intparams for bsearch. + */ + +static int +cmp_intparam(const void *a, const void *b) +{ + return strcmp((const char *)a, ((const struct ipspec *)b)->name); +} + +/* + * 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 = STAILQ_FIRST(&p->val))) { + free(s->s); + while ((v = STAILQ_FIRST(&s->vars))) { + free(v->name); + STAILQ_REMOVE_HEAD(&s->vars, tq); + free(v); + } + STAILQ_REMOVE_HEAD(&p->val, tq); + free(s); + } +} diff --git a/usr.sbin/jail/jail.8 b/usr.sbin/jail/jail.8 index 893efd84..ab66c6f 100644 --- a/usr.sbin/jail/jail.8 +++ b/usr.sbin/jail/jail.8 @@ -1,6 +1,5 @@ -.\" .\" Copyright (c) 2000, 2003 Robert N. M. Watson -.\" Copyright (c) 2008 James Gritton +.\" Copyright (c) 2008, 2010 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. .\" -.\" -.\" ---------------------------------------------------------------------------- -.\" "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$ .\" -.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 @@ If no .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 @@ assigned to itself. 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 @@ A set of IPv6 options for the prison, the counterparts to 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 @@ other jails under this jail). 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 @@ where the jail's chroot directory is located. .It Va persist Setting this boolean parameter allows a jail to exist without any processes. -Normally, a 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 @@ have not had jail functionality added to them. .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 @@ process. 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 @@ cd /usr/src 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 @@ Do what was described in 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 @@ inside the jail; others apply both for constraining a particular application 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 @@ etc. .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 @@ to quell .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 @@ If you are not using a virtual server, you may wish to modify .Xr syslogd 8 in the host environment to listen on the syslog socket in the jail environment; in this example, the syslog socket would be stored in -.Pa /data/jail/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 @@ Please refer to the 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 @@ Normal machine shutdown commands, such as 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 @@ This will send the .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 @@ unique jid. .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 @@ environment of the first jail. .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 @@ originally done by 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 diff --git a/usr.sbin/jail/jail.c b/usr.sbin/jail/jail.c index 0722bfd..0da39d9 100644 --- a/usr.sbin/jail/jail.c +++ b/usr.sbin/jail/jail.c @@ -1,6 +1,6 @@ /*- * Copyright (c) 1999 Poul-Henning Kamp. - * Copyright (c) 2009 James Gritton + * Copyright (c) 2009-2010 James Gritton * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -28,44 +28,39 @@ #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" -static char *ip4_addr; -#ifdef INET6 -static char *ip6_addr; -#endif +#define JP_RDTUN(jp) (((jp)->jp_ctltype & CTLFLAG_RDTUN) == CTLFLAG_RDTUN) -static void add_ip_addr(char **addrp, char *newaddr); -#ifdef INET6 -static void add_ip_addr46(char *newaddr); -#endif -static void add_ip_addrinfo(int ai_flags, char *value); +const char *cfname; +int verbose; + +static int create_jail(struct cfjail *j); +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] = { @@ -83,94 +78,93 @@ static const char *perm_sysctl[][3] = { "allow.socket_af", "allow.nosocket_af" }, }; -extern char **environ; - -#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) - int main(int argc, char **argv) { - login_cap_t *lcap = NULL; - struct passwd *pwd = NULL; - gid_t *groups; +#ifdef INET6 + struct in6_addr addr6; +#endif + struct stat st; + FILE *jfp; + struct cfjail *j; + char *cs, *ncs, *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, plimit, 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; - - ngroups_max = sysconf(_SC_NGROUPS_MAX) + 1; - if ((groups = malloc(sizeof(gid_t) * ngroups_max)) == NULL) - err(1, "malloc"); - while ((ch = getopt(argc, argv, "cdhilmn:r:s:u:U:J:")) != -1) { + op = 0; + plimit = -1; + dflag = iflag = Rflag = 0; + docf = 1; + cfname = CONF_FILE; + JidFile = NULL; + + 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; + add_param(NULL, NULL, "ip_hostname", NULL); + docf = 0; break; case 'i': iflag = 1; + verbose = -1; break; case 'J': JidFile = optarg; - Jflag = 1; + break; + case 'l': + add_param(NULL, NULL, "exec.clean", NULL); + docf = 0; + break; + case 'm': + op |= JF_SET; break; case 'n': - jailname = optarg; + add_param(NULL, NULL, "name", optarg); + docf = 0; break; - case 's': - securelevel = optarg; + case 'p': + plimit = strtol(optarg, NULL, 10); + if (plimit == 0) + plimit = -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, "securelevel", optarg); + docf = 0; break; - case 'm': - jail_set_flags |= JAIL_UPDATE; + case 'u': + add_param(NULL, NULL, "exec.jail_user", optarg); + add_param(NULL, NULL, "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, "exec.jail_user", optarg); + add_param(NULL, NULL, "exec.nosystem_jail_user", NULL); + docf = 0; + break; + case 'v': + verbose = 1; break; default: usage(); @@ -178,67 +172,36 @@ main(int argc, char **argv) } argc -= optind; argv += optind; - if (rflag) { - if (argc > 0 || iflag || Jflag || lflag || uflag || Uflag) + + /* 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(); - 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; - - 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) { - if (!strncmp(argv[i], "ip4.addr=", 9)) { - add_ip_addr(&ip4_addr, argv[i] + 9); - break; - } + op = JF_START; + docf = 0; + oldcl = 1; + add_param(NULL, NULL, "path", argv[0]); + add_param(NULL, NULL, "host.hostname", argv[1]); + if (argv[2][0] != '\0') { + for (cs = argv[2];; cs = ncs + 1) { + ncs = strchr(cs, ','); + if (ncs) + *ncs = '\0'; + add_param(NULL, NULL, #ifdef INET6 - if (!strncmp(argv[i], "ip6.addr=", 9)) { - add_ip_addr(&ip6_addr, argv[i] + 9); - break; - } + inet_pton(AF_INET6, cs, &addr6) == 1 + ? "ip6.addr" : #endif - if (!strncmp(argv[i], "host.hostname=", 14)) - add_ip_addrinfo(0, argv[i] + 14); + "ip4.addr", cs); + if (!ncs) + break; } - 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 (argv[2][0] != '\0') -#ifdef INET6 - add_ip_addr46(argv[2]); -#else - add_ip_addr(&ip4_addr, argv[2]); -#endif - cmdarg = 3; - /* Emulate the defaults from security.jail.* sysctls */ + for (i = 3; i < argc; i++) + add_param(NULL, NULL, "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) { @@ -247,241 +210,812 @@ main(int argc, char **argv) sysvallen = sizeof(sysval); if (sysctlbyname(perm_sysctl[pi][0], &sysval, &sysvallen, NULL, 0) == 0) - set_param(perm_sysctl[pi] - [sysval ? 2 : 1], NULL); + add_param(NULL, NULL, + perm_sysctl[pi][sysval ? 2 : 1], + NULL); } 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, "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(); + } 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, "command", + argv[i] + 8); + for (i++; i < argc; i++) + add_param(NULL, NULL, "command", + argv[i]); + break; + } + if ((cs = strchr(argv[i], '='))) + *cs++ = '\0'; + add_param(NULL, NULL, argv[i], cs); + } + } else { + /* From the config file, perhaps with a specified jail */ + if (Rflag || !docf) + usage(); + load_config(); } - if (ip4_addr != NULL) - set_param("ip4.addr", ip4_addr); -#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); + /* Find out which jails will be run. */ + find_intparams(); + 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); } - 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); + + jfp = NULL; + if (JidFile != NULL) { + jfp = fopen(JidFile, "w"); + if (jfp == NULL) + err(1, "open %s", JidFile); + setlinebuf(jfp); } - 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]); - } + 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) { + clear_persist(j); + if (j->flags & JF_MOUNTED) { + (void)run_command(j, NULL, IP_MOUNT_DEVFS); + if (run_command(j, NULL, IP_MOUNT_FSTAB)) + while (run_command(j, NULL, 0)) ; + if (run_command(j, NULL, IP_MOUNT)) + while (run_command(j, NULL, 0)) ; + } + if (j->flags & JF_IFUP) { + if (run_command(j, NULL, IP__IP4_IFADDR)) + while (run_command(j, NULL, 0)) ; +#ifdef INET6 + if (run_command(j, NULL, IP__IP6_IFADDR)) + while (run_command(j, NULL, 0)) ; +#endif + } + error = 1; + dep_done(j, 0); + continue; + } + if (!(j->flags & JF_CHECKINT)) + { + j->flags |= JF_CHECKINT; + if (dflag) + add_param(j, NULL, "allow.dying", NULL); + if (check_intparams(j) < 0) + continue; + } + if (!(j->flags & JF_IPPARAMS) && (!JF_DO_STOP(j->flags) || + j->intparams[IP_INTERFACE] != NULL)) { + j->flags |= JF_IPPARAMS; + if (ip_params(j) < 0) + continue; + } + if (j->jp == NULL && (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 (j->comstring != NULL && + (finish_command(j, &plimit) || run_command(j, &plimit, 0))) + 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: + /* + * 1: check existence and dependencies + * 2: configure IP addresses + * 3: run any exec.prestart commands + * 4: create the jail + * 5: configure vnet interfaces + * 6: run any exec.start or "command" commands + * 7: run any exec.poststart commands + */ + switch (j->comparam) { + default: + 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 (dep_check(j)) + continue; + if (j->jid > 0) + goto jail_create_done; + if (run_command(j, &plimit, IP__IP4_IFADDR)) + continue; + /* FALLTHROUGH */ + case IP__IP4_IFADDR: +#ifdef INET6 + if (run_command(j, &plimit, IP__IP6_IFADDR)) + continue; + /* FALLTHROUGH */ + case IP__IP6_IFADDR: +#endif + if (run_command(j, &plimit, IP_MOUNT)) + continue; + /* FALLTHROUGH */ + case IP_MOUNT: + if (run_command(j, &plimit, IP_MOUNT_FSTAB)) + continue; + /* FALLTHROUGH */ + case IP_MOUNT_FSTAB: + if (run_command(j, &plimit, IP_MOUNT_DEVFS)) + continue; + /* FALLTHROUGH */ + case IP_MOUNT_DEVFS: + if (run_command(j, &plimit, IP_EXEC_PRESTART)) + continue; + /* FALLTHROUGH */ + case IP_EXEC_PRESTART: + if (create_jail(j) < 0) + continue; + if (iflag) + printf("%d\n", j->jid); + if (jfp != NULL) + print_jail(jfp, j, oldcl); + if (verbose >= 0 && (j->name || verbose > 0)) + jail_note(j, "created\n"); + dep_done(j, DF_LIGHT); + if (bool_param(j->intparams[KP_VNET]) && + run_command(j, &plimit, IP_VNET_INTERFACE)) + continue; + /* FALLTHROUGH */ + case IP_VNET_INTERFACE: + if (run_command(j, &plimit, IP_EXEC_START)) + continue; + /* FALLTHROUGH */ + case IP_EXEC_START: + if (run_command(j, &plimit, IP_COMMAND)) + continue; + /* FALLTHROUGH */ + case IP_COMMAND: + if (run_command(j, &plimit, IP_EXEC_POSTSTART)) + continue; + /* FALLTHROUGH */ + case IP_EXEC_POSTSTART: + jail_create_done: + clear_persist(j); + dep_done(j, 0); + } + break; + + case JF_SET: + /* + * 1: check existence and dependencies + * 2: update the jail + */ + 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: + /* + * 1: check dependencies and existence (note order) + * 2: run any exec.prestop commands + * 3: run any exec.stop commands + * 4: send SIGTERM to all jail processes + * 5: remove the jail + * 6: run any exec.poststop commands + * 7: take down IP addresses + */ + switch (j->comparam) { + default: + 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; + } + if (run_command(j, &plimit, IP_EXEC_PRESTOP)) + continue; + /* FALLTHROUGH */ + case IP_EXEC_PRESTOP: + if (run_command(j, &plimit, IP_EXEC_STOP)) + continue; + /* FALLTHROUGH */ + case IP_EXEC_STOP: + j->comparam = IP_STOP_TIMEOUT; + if (term_procs(j)) + continue; + /* FALLTHROUGH */ + case IP_STOP_TIMEOUT: + (void)jail_remove(j->jid); + j->jid = -1; + if (verbose >= 0 && + (docf || argc > 1 || + wild_jail_name(argv[0]) || verbose > 0)) + jail_note(j, "removed\n"); + dep_done(j, DF_LIGHT); + if (run_command(j, &plimit, IP_EXEC_POSTSTOP)) + continue; + /* FALLTHROUGH */ + case IP_EXEC_POSTSTOP: + if (run_command(j, &plimit, IP_MOUNT_DEVFS)) + continue; + /* FALLTHROUGH */ + case IP_MOUNT_DEVFS: + if (run_command(j, &plimit, IP_MOUNT_FSTAB)) + continue; + /* FALLTHROUGH */ + case IP_MOUNT_FSTAB: + if (run_command(j, &plimit, IP_MOUNT)) + continue; + /* FALLTHROUGH */ + case IP_MOUNT: + if (run_command(j, &plimit, IP__IP4_IFADDR)) + continue; + /* FALLTHROUGH */ + case IP__IP4_IFADDR: #ifdef INET6 - 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]); -#else - 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]); + if (run_command(j, &plimit, IP__IP6_IFADDR)) + continue; + /* FALLTHROUGH */ + case IP__IP6_IFADDR: #endif + jail_remove_done: + dep_done(j, 0); + if (j->flags & JF_START) { + j->comparam = 0; + j->flags &= ~JF_STOP; + dep_reset(j); + requeue(j, + j->ndeps ? &waiting : &ready); + } + } + break; + } + } + + if (jfp != NULL) + fclose(jfp); + exit(error); +} + +/* + * Mark a jail's failure for future handling. + */ +void +failed(struct cfjail *j) +{ + j->flags |= JF_FAILED; + TAILQ_REMOVE(j->queue, j, tq); + TAILQ_INSERT_HEAD(&ready, j, tq); + j->queue = &ready; +} + +/* + * Exit slightly more gracefully when out of memory. + */ +void * +emalloc(size_t size) +{ + void *p; + + p = malloc(size); + if (!p) + err(1, "malloc"); + return p; +} + +void * +erealloc(void *ptr, size_t size) +{ + void *p; + + p = realloc(ptr, size); + if (!p) + err(1, "malloc"); + return p; +} + +char * +estrdup(const char *str) +{ + char *ns; + + ns = strdup(str); + if (!ns) + err(1, "malloc"); + return ns; +} + +/* + * Print a message including an optional jail name. + */ +void +jail_note(const struct cfjail *j, const char *fmt, ...) +{ + va_list ap, tap; + char *cs; + size_t len; + + va_start(ap, fmt); + va_copy(tap, ap); + len = vsnprintf(NULL, 0, fmt, tap); + va_end(tap); + cs = alloca(len + 1); + (void)vsnprintf(cs, len + 1, fmt, ap); + va_end(ap); + if (j->name) + printf("%s: %s", j->name, cs); + else + printf("%s", cs); +} + +/* + * Print a warning message including an optional jail name. + */ +void +jail_warnx(const struct cfjail *j, const char *fmt, ...) +{ + va_list ap, tap; + char *cs; + size_t len; + + va_start(ap, fmt); + va_copy(tap, ap); + len = vsnprintf(NULL, 0, fmt, tap); + va_end(tap); + cs = alloca(len + 1); + (void)vsnprintf(cs, len + 1, fmt, ap); + va_end(ap); + if (j->name) + warnx("%s: %s", j->name, cs); + else + warnx("%s", cs); +} + +/* + * Create a new jail. + */ +static 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 (stat(path, &st) < 0) { + jail_warnx(j, "path %s: %s", path, strerror(errno)); + failed(j); + return -1; + } + if (!S_ISDIR(st.st_mode)) { + jail_warnx(j, "path %s: %s", path, strerror(ENOTDIR)); + failed(j); + return -1; } - (void)fclose(fp); } - if (cmdarg < 0) - exit(0); - if (username != NULL) { - if (Uflag) - GET_USER_INFO; - if (lflag) { - p = getenv("TERM"); - environ = &cleanenv; + + /* + * 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); + failed(j); + 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 (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 (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; } - execvp(argv[cmdarg], argv + cmdarg); - err(1, "execvp: %s", argv[cmdarg]); + return j->jid; } +/* + * Remove a temporarily set "persist" parameter. + */ static void -add_ip_addr(char **addrp, char *value) +clear_persist(struct cfjail *j) { - int addrlen; - char *addr; - - 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; + 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) +{ + struct jailparam *jp, *setparams, *sjp; + int ns, jid; + + ns = 0; + for (jp = j->jp; jp < j->jp + j->njp; jp++) + if (!JP_RDTUN(jp)) + ns++; + if (ns == 0) + return 0; + sjp = setparams = alloca(++ns * sizeof(struct jailparam)); + if (jailparam_init(sjp, "jid") < 0 || + jailparam_import_raw(sjp, &j->jid, sizeof j->jid) < 0) { + jail_warnx(j, "%s", jail_errmsg); + failed(j); + return -1; } + for (jp = j->jp; jp < j->jp + j->njp; jp++) + if (!JP_RDTUN(jp)) + *++sjp = *jp; + + jid = jailparam_set_note(j, setparams, ns, + bool_param(j->intparams[IP_ALLOW_DYING]) + ? JAIL_UPDATE | JAIL_DYING : JAIL_UPDATE); + if (jid < 0) { + jail_warnx(j, "%s", jail_errmsg); + failed(j); + } + jailparam_free(setparams, 1); + return jid; } -#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; - for (p = value;; p = np + 1) - { - np = strchr(p, ','); - if (np) - *np = '\0'; - add_ip_addrinfo(AI_NUMERICHOST, p); - if (!np) - break; + pval = string_param(j->intparams[KP_JID]); + if (pval != NULL) { + 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 { + 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); } + j->jid = jail_get(jiov, 2, dflag ? JAIL_DYING : 0); } -#endif +/* + * 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; +} + +/* + * 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; - struct in_addr addr4; - size_t size; - int error, ip4ok; - int mib[4]; - char avalue4[INET_ADDRSTRLEN]; -#ifdef INET6 - struct in6_addr addr6; - int ip6ok; - char avalue6[INET6_ADDRSTRLEN]; -#endif + struct cfparam *p; - /* Look up the hostname (or get the address) */ - memset(&hints, 0, sizeof(hints)); - hints.ai_socktype = SOCK_STREAM; + 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_HOSTNAME], ',', 0); + putc('\t', fp); + print_param(fp, j->intparams[KP_IP4_ADDR], ',', 0); #ifdef INET6 - hints.ai_family = PF_UNSPEC; -#else - hints.ai_family = PF_INET; + if (j->intparams[KP_IP6_ADDR] && + !STAILQ_EMPTY(&j->intparams[KP_IP6_ADDR]->val)) { + if (j->intparams[KP_IP4_ADDR] && + !STAILQ_EMPTY(&j->intparams[KP_IP4_ADDR]->val)) + putc(',', fp); + print_param(fp, j->intparams[KP_IP6_ADDR], ',', 0); + } #endif - hints.ai_flags = ai_flags; - error = getaddrinfo(value, NULL, &hints, &ai0); - if (error != 0) - errx(1, "hostname %s: %s", value, gai_strerror(error)); + 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); +} - /* - * Silently ignore unsupported address families from DNS lookups. - * But if this is a numeric address, let the kernel give the error. - */ - if (ai_flags & AI_NUMERICHOST) - ip4ok = -#ifdef INET6 - ip6ok = -#endif - 1; - else { - size = 4; - ip4ok = (sysctlnametomib("security.jail.param.ip4", mib, - &size) == 0); -#ifdef INET6 - size = 4; - ip6ok = (sysctlnametomib("security.jail.param.ip6", mib, - &size) == 0); -#endif +/* + * Print a parameter value, or a name=value pair. + */ +static void +print_param(FILE *fp, const struct cfparam *p, int sep, int doname) +{ + const struct cfstring *s, *ts; + + if (doname) + fputs(p->name, fp); + if (p == NULL || STAILQ_EMPTY(&p->val)) + return; + if (doname) + putc('=', fp); + STAILQ_FOREACH_SAFE(s, &p->val, tq, ts) { + quoted_print(fp, s->s); + if (ts != NULL) + putc(sep, fp); } - - /* Convert the addresses to ASCII so set_param can convert them back. */ - for (ai = ai0; ai; ai = ai->ai_next) - switch (ai->ai_family) { - case AF_INET: - if (!ip4ok) - 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; -#ifdef INET6 - case AF_INET6: - if (!ip6ok) - 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; -#endif - } - freeaddrinfo(ai0); } +/* + * Print a string with quotes around spaces. + */ static void quoted_print(FILE *fp, char *str) { int c, qc; char *p = str; - /* An empty string needs quoting. */ - if (!*p) { - fputs("\"\"", fp); - return; - } - - /* - * The value will be surrounded by quotes if it contains spaces - * or quotes. - */ - qc = strchr(p, '\'') ? '"' + qc = !*p ? '"' + : strchr(p, '\'') ? '"' : strchr(p, '"') ? '\'' : strchr(p, ' ') || strchr(p, '\t') ? '"' : 0; @@ -497,65 +1031,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); } diff --git a/usr.sbin/jail/jail.conf.5 b/usr.sbin/jail/jail.conf.5 new file mode 100644 index 0000000..b09d3fe --- /dev/null +++ b/usr.sbin/jail/jail.conf.5 @@ -0,0 +1,231 @@ +.\" Copyright (c) 2010 James Gritton +.\" All rights reserved. +.\" +.\" Redistribution and use in source and binary forms, with or without +.\" modification, are permitted provided that the following conditions +.\" are met: +.\" 1. Redistributions of source code must retain the above copyright +.\" notice, this list of conditions and the following disclaimer. +.\" 2. Redistributions in binary form must reproduce the above copyright +.\" notice, this list of conditions and the following disclaimer in the +.\" documentation and/or other materials provided with the distribution. +.\" +.\" THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND +.\" ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +.\" IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +.\" ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE +.\" FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +.\" DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS +.\" OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) +.\" HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT +.\" LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY +.\" OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF +.\" SUCH DAMAGE. +.\" +.\" $FreeBSD$ +.\" +.Dd 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. diff --git a/usr.sbin/jail/jaillex.l b/usr.sbin/jail/jaillex.l new file mode 100644 index 0000000..93bd7c4 --- /dev/null +++ b/usr.sbin/jail/jaillex.l @@ -0,0 +1,232 @@ +%{ +/*- + * Copyright (c) 2010 James Gritton + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +#include <sys/cdefs.h> +__FBSDID("$FreeBSD$"); + +#include <err.h> +#include <stddef.h> +#include <stdlib.h> +#include <string.h> + +#include "jailp.h" +#include "y.tab.h" + +#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; +} diff --git a/usr.sbin/jail/jailp.h b/usr.sbin/jail/jailp.h new file mode 100644 index 0000000..227ad38 --- /dev/null +++ b/usr.sbin/jail/jailp.h @@ -0,0 +1,218 @@ +/*- + * Copyright (c) 2010 James Gritton. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * + * $FreeBSD$ + */ + +#include <sys/param.h> +#include <sys/types.h> +#include <sys/jail.h> +#include <sys/queue.h> +#include <sys/time.h> + +#include <jail.h> + +#define CONF_FILE "/etc/jail.conf" + +#define DEP_FROM 0 +#define DEP_TO 1 + +#define DF_SEEN 0x01 /* Dependency has been followed */ +#define DF_LIGHT 0x02 /* Implied dependency on jail existence only */ +#define DF_NOFAIL 0x04 /* Don't 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 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_CHECKINT 0x0040 /* Checked internal parameters */ +#define JF_IPPARAMS 0x0080 /* Looked up jail hostname for IP_HOSTNAME */ +#define JF_RDTUN 0x0100 /* Create-only parameter check has been done */ +#define JF_IFUP 0x0200 /* IP addresses have been configured */ +#define JF_MOUNTED 0x0400 /* Filesystems have been mounted */ +#define JF_PERSIST 0x0800 /* Jail is temporarily persistent */ +#define JF_TIMEOUT 0x1000 /* A command (or process kill) timed out */ +#define JF_RUNQ 0x2000 /* Jail was in the run qeueue */ +#define JF_BACKGROUND 0x4000 /* Command was run in the background */ + +#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 */ + IP_INTERFACE, /* Add IP addresses to this interface */ + IP_IP_HOSTNAME, /* Get jail IP address(es) from hostname */ + IP_MOUNT, /* Mount points in fstab(5) form */ + IP_MOUNT_FSTAB, /* A standard fstab(5) file */ + IP_MOUNT_DEVFS, /* Mount /dev under prison root */ + IP_MOUNT_DEVFS_RULESET, /* Ruleset for the devfs mount */ + IP_STOP_TIMEOUT, /* Time to wait after sending SIGTERM */ + IP__IP4_IFADDR, /* Copy of ip4.addr with interface/netmask */ +#ifdef INET6 + IP__IP6_IFADDR, /* Copy of ip6.addr with interface/prefixlen */ +#endif + IP_VNET_INTERFACE, /* Assign interface(s) to vnet jail */ + KP_HOSTNAME, + KP_IP4_ADDR, +#ifdef INET6 + KP_IP6_ADDR, +#endif + KP_JID, + KP_NAME, + KP_PATH, + KP_PERSIST, + KP_VNET, + IP_NPARAM +}; + +STAILQ_HEAD(cfvars, cfvar); + +struct cfvar { + STAILQ_ENTRY(cfvar) tq; + char *name; + size_t pos; +}; + +STAILQ_HEAD(cfstrings, cfstring); + +struct cfstring { + STAILQ_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; + 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 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 run_command(struct cfjail *j, int *plimit, enum intparam comparam); +extern int finish_command(struct cfjail *j, int *plimit); +extern struct cfjail *next_proc(int nonblock); +extern int term_procs(struct cfjail *j); + +extern void load_config(void); +extern struct cfjail *add_jail(void); +extern void add_param(struct cfjail *j, const struct cfparam *p, + const char *name, const char *value); +extern void find_intparams(void); +extern int check_intparams(struct cfjail *j); +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 ip_params(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 waiting; +extern const char *cfname; +extern int verbose; diff --git a/usr.sbin/jail/jailparse.y b/usr.sbin/jail/jailparse.y new file mode 100644 index 0000000..d9a258e --- /dev/null +++ b/usr.sbin/jail/jailparse.y @@ -0,0 +1,216 @@ +%{ +/*- + * Copyright (c) 2010 James Gritton + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +#include <sys/cdefs.h> +__FBSDID("$FreeBSD$"); + +#include <stdlib.h> +#include <string.h> + +#include "jailp.h" + +#ifdef DEBUG +#define YYDEBUG 1 +#endif +%} + +%union { + struct cfjail *j; + struct cfparams *pp; + struct cfparam *p; + struct cfstrings *ss; + struct cfstring *s; + char *cs; +} + +%token PLEQ +%token <cs> STR STR1 VAR VAR1 + +%type <j> jail +%type <pp> param_l +%type <p> param name +%type <ss> value +%type <s> string + +%% + +/* + * A config file is a series of jails (containing parameters) and jail-less + * parameters which realy belong to a global pseudo-jail. + */ +conf : + ; + | conf jail + ; + | conf param ';' + { + struct cfjail *j; + + j = TAILQ_LAST(&cfjails, cfjails); + if (!j || strcmp(j->name, "*")) { + j = add_jail(); + j->name = estrdup("*"); + } + TAILQ_INSERT_TAIL(&j->params, $2, tq); + } + | conf ';' + +jail : STR '{' param_l '}' + { + $$ = add_jail(); + $$->name = $1; + TAILQ_CONCAT(&$$->params, $3, tq); + free($3); + } + ; + +param_l : + { + $$ = emalloc(sizeof(struct cfparams)); + TAILQ_INIT($$); + } + | param_l param ';' + { + $$ = $1; + TAILQ_INSERT_TAIL($$, $2, tq); + } + | param_l ';' + ; + +/* + * Parameters have a name and an optional list of value strings, + * which may have "+=" or "=" preceeding them. + */ +param : name + { + $$ = $1; + } + | name '=' value + { + $$ = $1; + STAILQ_CONCAT(&$$->val, $3); + free($3); + } + | name PLEQ value + { + $$ = $1; + STAILQ_CONCAT(&$$->val, $3); + $$->flags |= PF_APPEND; + free($3); + } + | name value + { + $$ = $1; + STAILQ_CONCAT(&$$->val, $2); + 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; + STAILQ_INIT(&$$->val); + $$->flags = 0; + } + | VAR + { + $$ = emalloc(sizeof(struct cfparam)); + $$->name = $1; + STAILQ_INIT(&$$->val); + $$->flags = PF_VAR; + } + ; + +value : string + { + $$ = emalloc(sizeof(struct cfstrings)); + STAILQ_INIT($$); + STAILQ_INSERT_TAIL($$, $1, tq); + } + | value ',' string + { + $$ = $1; + STAILQ_INSERT_TAIL($$, $3, tq); + } + ; + +/* + * Strings may be passed in pieces, because of quoting and/or variable + * interpolation. Reassemble them into a single string. + */ +string : STR + { + $$ = emalloc(sizeof(struct cfstring)); + $$->s = $1; + $$->len = strlen($1); + STAILQ_INIT(&$$->vars); + } + | VAR + { + struct cfvar *v; + + $$ = emalloc(sizeof(struct cfstring)); + $$->s = estrdup(""); + $$->len = 0; + STAILQ_INIT(&$$->vars); + v = emalloc(sizeof(struct cfvar)); + v->name = $1; + v->pos = 0; + STAILQ_INSERT_TAIL(&$$->vars, v, tq); + } + | string STR1 + { + size_t len1; + + $$ = $1; + len1 = strlen($2); + $$->s = erealloc($$->s, $$->len + len1 + 1); + strcpy($$->s + $$->len, $2); + free($2); + $$->len += len1; + } + | string VAR1 + { + struct cfvar *v; + + $$ = $1; + v = emalloc(sizeof(struct cfvar)); + v->name = $2; + v->pos = $$->len; + STAILQ_INSERT_TAIL(&$$->vars, v, tq); + } + ; + +%% diff --git a/usr.sbin/jail/state.c b/usr.sbin/jail/state.c new file mode 100644 index 0000000..159afbc --- /dev/null +++ b/usr.sbin/jail/state.c @@ -0,0 +1,466 @@ +/*- + * Copyright (c) 2010 James Gritton + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +#include <sys/cdefs.h> +__FBSDID("$FreeBSD$"); + +#include <sys/uio.h> + +#include <err.h> +#include <stdlib.h> +#include <string.h> + +#include "jailp.h" + +struct cfjails ready = TAILQ_HEAD_INITIALIZER(ready); +struct cfjails waiting = TAILQ_HEAD_INITIALIZER(waiting); + +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])) { + STAILQ_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])) { + STAILQ_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(&waiting)) != 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, &waiting); + 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 == &waiting) + 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 ? &waiting : &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); +} |