A new jail(8) with a configuration file, ultimately to replace the work

currently done by /etc/rc.d/jail.

MFC after:	3 months
This commit is contained in:
Jamie Gritton 2012-04-26 17:36:05 +00:00
commit 91b24c185b
Notes: svn2git 2020-12-20 02:59:44 +00:00
svn path=/head/; revision=234712
11 changed files with 4392 additions and 688 deletions

View File

@ -247,44 +247,6 @@ They return \-1 on failure, and set
to indicate the error.
.Pp
.Rv -std jail_attach jail_remove
.Sh PRISON?
Once a process has been put in a prison, it and its descendants cannot escape
the prison.
.Pp
Inside the prison, the concept of
.Dq superuser
is very diluted.
In general,
it can be assumed that nothing can be mangled from inside a prison which
does not exist entirely inside that prison.
For instance the directory
tree below
.Dq Li path
can be manipulated all the ways a root can normally do it, including
.Dq Li "rm -rf /*"
but new device special nodes cannot be created because they reference
shared resources (the device drivers in the kernel).
The effective
.Dq securelevel
for a process is the greater of the global
.Dq securelevel
or, if present, the per-jail
.Dq securelevel .
.Pp
All IP activity will be forced to happen to/from the IP number specified,
which should be an alias on one of the network interfaces.
All connections to/from the loopback address
.Pf ( Li 127.0.0.1
for IPv4,
.Li ::1
for IPv6) will be changed to be to/from the primary address
of the jail for the given address family.
.Pp
It is possible to identify a process as jailed by examining
.Dq Li /proc/<pid>/status :
it will show a field near the end of the line, either as
a single hyphen for a process at large, or the name currently
set for the prison for jailed processes.
.Sh ERRORS
The
.Fn jail
@ -415,7 +377,7 @@ and
.Fn jail_attach
call
.Xr chroot 2
internally, so it can fail for all the same reasons.
internally, so they can fail for all the same reasons.
Please consult the
.Xr chroot 2
manual page for details.

View File

@ -3,9 +3,14 @@
.include <bsd.own.mk>
PROG= jail
MAN= jail.8
DPADD= ${LIBJAIL} ${LIBUTIL}
LDADD= -ljail -lutil
MAN= jail.8 jail.conf.5
SRCS= jail.c command.c config.c state.c jailp.h jaillex.l jailparse.y y.tab.h
DPADD= ${LIBJAIL} ${LIBKVM} ${LIBUTIL} ${LIBL}
LDADD= -ljail -lkvm -lutil -ll
YFLAGS+=-v
CFLAGS+=-I. -I${.CURDIR}
.if ${MK_INET6_SUPPORT} != "no"
CFLAGS+= -DINET6
@ -14,4 +19,6 @@ CFLAGS+= -DINET6
CFLAGS+= -DINET
.endif
CLEANFILES= y.output
.include <bsd.prog.mk>

857
usr.sbin/jail/command.c Normal file
View File

@ -0,0 +1,857 @@
/*-
* Copyright (c) 2011 James Gritton
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
* OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
* HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
* LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
* OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
* SUCH DAMAGE.
*/
#include <sys/cdefs.h>
__FBSDID("$FreeBSD$");
#include <sys/types.h>
#include <sys/event.h>
#include <sys/mount.h>
#include <sys/stat.h>
#include <sys/sysctl.h>
#include <sys/user.h>
#include <sys/wait.h>
#include <err.h>
#include <errno.h>
#include <fcntl.h>
#include <kvm.h>
#include <login_cap.h>
#include <paths.h>
#include <pwd.h>
#include <signal.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include "jailp.h"
#define DEFAULT_STOP_TIMEOUT 10
#define PHASH_SIZE 256
LIST_HEAD(phhead, phash);
struct phash {
LIST_ENTRY(phash) le;
struct cfjail *j;
pid_t pid;
};
int paralimit = -1;
extern char **environ;
static int run_command(struct cfjail *j);
static void add_proc(struct cfjail *j, pid_t pid);
static void clear_procs(struct cfjail *j);
static struct cfjail *find_proc(pid_t pid);
static int term_procs(struct cfjail *j);
static int get_user_info(struct cfjail *j, const char *username,
const struct passwd **pwdp, login_cap_t **lcapp);
static int check_path(struct cfjail *j, const char *pname, const char *path,
int isfile, const char *umount_type);
static struct cfjails sleeping = TAILQ_HEAD_INITIALIZER(sleeping);
static struct cfjails runnable = TAILQ_HEAD_INITIALIZER(runnable);
static struct cfstring dummystring = { .len = 1 };
static struct phhead phash[PHASH_SIZE];
static int kq;
/*
* Run the next command associated with a jail.
*/
int
next_command(struct cfjail *j)
{
enum intparam comparam;
int create_failed;
if (paralimit == 0) {
requeue(j, &runnable);
return 1;
}
create_failed = (j->flags & (JF_STOP | JF_FAILED)) == JF_FAILED;
comparam = *j->comparam;
for (;;) {
if (j->comstring == NULL) {
j->comparam += create_failed ? -1 : 1;
switch ((comparam = *j->comparam)) {
case 0:
return 0;
case IP_MOUNT_DEVFS:
if (!bool_param(j->intparams[IP_MOUNT_DEVFS]))
continue;
/* FALLTHROUGH */
case IP__OP:
case IP_STOP_TIMEOUT:
j->comstring = &dummystring;
break;
default:
if (j->intparams[comparam] == NULL)
continue;
j->comstring = create_failed
? TAILQ_LAST(&j->intparams[comparam]->val,
cfstrings)
: TAILQ_FIRST(&j->intparams[comparam]->val);
}
} else {
j->comstring = j->comstring == &dummystring ? NULL :
create_failed
? TAILQ_PREV(j->comstring, cfstrings, tq)
: TAILQ_NEXT(j->comstring, tq);
}
if (j->comstring == NULL || j->comstring->len == 0 ||
(create_failed && (comparam == IP_EXEC_PRESTART ||
comparam == IP_EXEC_START || comparam == IP_COMMAND ||
comparam == IP_EXEC_POSTSTART)))
continue;
switch (run_command(j)) {
case -1:
failed(j);
/* FALLTHROUGH */
case 1:
return 1;
}
}
}
/*
* Check command exit status
*/
int
finish_command(struct cfjail *j)
{
int error;
if (!(j->flags & JF_SLEEPQ))
return 0;
j->flags &= ~JF_SLEEPQ;
if (*j->comparam == IP_STOP_TIMEOUT)
{
j->flags &= ~JF_TIMEOUT;
j->pstatus = 0;
return 0;
}
paralimit++;
if (!TAILQ_EMPTY(&runnable))
requeue(TAILQ_FIRST(&runnable), &ready);
error = 0;
if (j->flags & JF_TIMEOUT) {
j->flags &= ~JF_TIMEOUT;
if (*j->comparam != IP_STOP_TIMEOUT) {
jail_warnx(j, "%s: timed out", j->comline);
failed(j);
error = -1;
} else if (verbose > 0)
jail_note(j, "timed out\n");
} else if (j->pstatus != 0) {
if (WIFSIGNALED(j->pstatus))
jail_warnx(j, "%s: exited on signal %d",
j->comline, WTERMSIG(j->pstatus));
else
jail_warnx(j, "%s: failed", j->comline);
j->pstatus = 0;
failed(j);
error = -1;
}
free(j->comline);
j->comline = NULL;
return error;
}
/*
* Check for finished processes or timeouts.
*/
struct cfjail *
next_proc(int nonblock)
{
struct kevent ke;
struct timespec ts;
struct timespec *tsp;
struct cfjail *j;
if (!TAILQ_EMPTY(&sleeping)) {
again:
tsp = NULL;
if ((j = TAILQ_FIRST(&sleeping)) && j->timeout.tv_sec) {
clock_gettime(CLOCK_REALTIME, &ts);
ts.tv_sec = j->timeout.tv_sec - ts.tv_sec;
ts.tv_nsec = j->timeout.tv_nsec - ts.tv_nsec;
if (ts.tv_nsec < 0) {
ts.tv_sec--;
ts.tv_nsec += 1000000000;
}
if (ts.tv_sec < 0 ||
(ts.tv_sec == 0 && ts.tv_nsec == 0)) {
j->flags |= JF_TIMEOUT;
clear_procs(j);
return j;
}
tsp = &ts;
}
if (nonblock) {
ts.tv_sec = 0;
ts.tv_nsec = 0;
tsp = &ts;
}
switch (kevent(kq, NULL, 0, &ke, 1, tsp)) {
case -1:
if (errno != EINTR)
err(1, "kevent");
goto again;
case 0:
if (!nonblock) {
j = TAILQ_FIRST(&sleeping);
j->flags |= JF_TIMEOUT;
clear_procs(j);
return j;
}
break;
case 1:
(void)waitpid(ke.ident, NULL, WNOHANG);
if ((j = find_proc(ke.ident))) {
j->pstatus = ke.data;
return j;
}
goto again;
}
}
return NULL;
}
/*
* Run a single command for a jail, possible inside the jail.
*/
int
run_command(struct cfjail *j)
{
const struct passwd *pwd;
const struct cfstring *comstring, *s;
login_cap_t *lcap;
char **argv;
char *cs, *comcs, *devpath;
const char *jidstr, *conslog, *path, *ruleset, *term, *username;
enum intparam comparam;
size_t comlen;
pid_t pid;
int argc, bg, clean, consfd, down, fib, i, injail, sjuser, timeout;
#if defined(INET) || defined(INET6)
char *addr;
#endif
static char *cleanenv;
/* Perform some operations that aren't actually commands */
comparam = *j->comparam;
down = j->flags & (JF_STOP | JF_FAILED);
switch (comparam) {
case IP_STOP_TIMEOUT:
return term_procs(j);
case IP__OP:
if (down) {
if (jail_remove(j->jid) < 0 && errno == EPERM) {
jail_warnx(j, "jail_remove: %s",
strerror(errno));
return -1;
}
if (verbose > 0 || (verbose == 0 && (j->flags & JF_STOP
? note_remove : j->name != NULL)))
jail_note(j, "removed\n");
j->jid = -1;
if (j->flags & JF_STOP)
dep_done(j, DF_LIGHT);
else
j->flags &= ~JF_PERSIST;
} else {
if (create_jail(j) < 0)
return -1;
if (verbose >= 0 && (j->name || verbose > 0))
jail_note(j, "created\n");
dep_done(j, DF_LIGHT);
}
return 0;
default: ;
}
/*
* Collect exec arguments. Internal commands for network and
* mounting build their own argument lists.
*/
comstring = j->comstring;
bg = 0;
switch (comparam) {
#ifdef INET
case IP__IP4_IFADDR:
argv = alloca(8 * sizeof(char *));
*(const char **)&argv[0] = _PATH_IFCONFIG;
if ((cs = strchr(comstring->s, '|'))) {
argv[1] = alloca(cs - comstring->s + 1);
strlcpy(argv[1], comstring->s, cs - comstring->s + 1);
addr = cs + 1;
} else {
*(const char **)&argv[1] =
string_param(j->intparams[IP_INTERFACE]);
addr = comstring->s;
}
*(const char **)&argv[2] = "inet";
if (!(cs = strchr(addr, '/'))) {
argv[3] = addr;
*(const char **)&argv[4] = "netmask";
*(const char **)&argv[5] = "255.255.255.255";
argc = 6;
} else if (strchr(cs + 1, '.')) {
argv[3] = alloca(cs - addr + 1);
strlcpy(argv[3], addr, cs - addr + 1);
*(const char **)&argv[4] = "netmask";
*(const char **)&argv[5] = cs + 1;
argc = 6;
} else {
argv[3] = addr;
argc = 4;
}
*(const char **)&argv[argc] = down ? "-alias" : "alias";
argv[argc + 1] = NULL;
break;
#endif
#ifdef INET6
case IP__IP6_IFADDR:
argv = alloca(8 * sizeof(char *));
*(const char **)&argv[0] = _PATH_IFCONFIG;
if ((cs = strchr(comstring->s, '|'))) {
argv[1] = alloca(cs - comstring->s + 1);
strlcpy(argv[1], comstring->s, cs - comstring->s + 1);
addr = cs + 1;
} else {
*(const char **)&argv[1] =
string_param(j->intparams[IP_INTERFACE]);
addr = comstring->s;
}
*(const char **)&argv[2] = "inet6";
argv[3] = addr;
if (!(cs = strchr(addr, '/'))) {
*(const char **)&argv[4] = "prefixlen";
*(const char **)&argv[5] = "128";
argc = 6;
} else
argc = 4;
*(const char **)&argv[argc] = down ? "-alias" : "alias";
argv[argc + 1] = NULL;
break;
#endif
case IP_VNET_INTERFACE:
argv = alloca(5 * sizeof(char *));
*(const char **)&argv[0] = _PATH_IFCONFIG;
argv[1] = comstring->s;
*(const char **)&argv[2] = down ? "-vnet" : "vnet";
jidstr = string_param(j->intparams[KP_JID]);
*(const char **)&argv[3] =
jidstr ? jidstr : string_param(j->intparams[KP_NAME]);
argv[4] = NULL;
break;
case IP_MOUNT:
case IP__MOUNT_FROM_FSTAB:
argv = alloca(8 * sizeof(char *));
comcs = alloca(comstring->len + 1);
strcpy(comcs, comstring->s);
argc = 0;
for (cs = strtok(comcs, " \t\f\v\r\n"); cs && argc < 4;
cs = strtok(NULL, " \t\f\v\r\n"))
argv[argc++] = cs;
if (argc == 0)
return 0;
if (argc < 3) {
jail_warnx(j, "%s: %s: missing information",
j->intparams[comparam]->name, comstring->s);
return -1;
}
if (check_path(j, j->intparams[comparam]->name, argv[1], 0,
down ? argv[2] : NULL) < 0)
return -1;
if (down) {
argv[4] = NULL;
argv[3] = argv[1];
*(const char **)&argv[0] = "/sbin/umount";
} else {
if (argc == 4) {
argv[7] = NULL;
argv[6] = argv[1];
argv[5] = argv[0];
argv[4] = argv[3];
*(const char **)&argv[3] = "-o";
} else {
argv[5] = NULL;
argv[4] = argv[1];
argv[3] = argv[0];
}
*(const char **)&argv[0] = _PATH_MOUNT;
}
*(const char **)&argv[1] = "-t";
break;
case IP_MOUNT_DEVFS:
argv = alloca(7 * sizeof(char *));
path = string_param(j->intparams[KP_PATH]);
if (path == NULL) {
jail_warnx(j, "mount.devfs: no path");
return -1;
}
devpath = alloca(strlen(path) + 5);
sprintf(devpath, "%s/dev", path);
if (check_path(j, "mount.devfs", devpath, 0,
down ? "devfs" : NULL) < 0)
return -1;
if (down) {
*(const char **)&argv[0] = "/sbin/umount";
argv[1] = devpath;
argv[2] = NULL;
} else {
*(const char **)&argv[0] = _PATH_MOUNT;
*(const char **)&argv[1] = "-t";
*(const char **)&argv[2] = "devfs";
ruleset = string_param(j->intparams[KP_DEVFS_RULESET]);
if (!ruleset)
ruleset = "4"; /* devfsrules_jail */
argv[3] = alloca(11 + strlen(ruleset));
sprintf(argv[3], "-oruleset=%s", ruleset);
*(const char **)&argv[4] = ".";
argv[5] = devpath;
argv[6] = NULL;
}
break;
case IP_COMMAND:
if (j->name != NULL)
goto default_command;
argc = 0;
TAILQ_FOREACH(s, &j->intparams[IP_COMMAND]->val, tq)
argc++;
argv = alloca((argc + 1) * sizeof(char *));
argc = 0;
TAILQ_FOREACH(s, &j->intparams[IP_COMMAND]->val, tq)
argv[argc++] = s->s;
argv[argc] = NULL;
j->comstring = &dummystring;
break;
default:
default_command:
if ((cs = strpbrk(comstring->s, "!\"$&'()*;<>?[\\]`{|}~")) &&
!(cs[0] == '&' && cs[1] == '\0')) {
argv = alloca(4 * sizeof(char *));
*(const char **)&argv[0] = _PATH_BSHELL;
*(const char **)&argv[1] = "-c";
argv[2] = comstring->s;
argv[3] = NULL;
} else {
if (cs) {
*cs = 0;
bg = 1;
}
comcs = alloca(comstring->len + 1);
strcpy(comcs, comstring->s);
argc = 0;
for (cs = strtok(comcs, " \t\f\v\r\n"); cs;
cs = strtok(NULL, " \t\f\v\r\n"))
argc++;
argv = alloca((argc + 1) * sizeof(char *));
strcpy(comcs, comstring->s);
argc = 0;
for (cs = strtok(comcs, " \t\f\v\r\n"); cs;
cs = strtok(NULL, " \t\f\v\r\n"))
argv[argc++] = cs;
argv[argc] = NULL;
}
}
if (argv[0] == NULL)
return 0;
if (int_param(j->intparams[IP_EXEC_TIMEOUT], &timeout) &&
timeout != 0) {
clock_gettime(CLOCK_REALTIME, &j->timeout);
j->timeout.tv_sec += timeout;
} else
j->timeout.tv_sec = 0;
injail = comparam == IP_EXEC_START || comparam == IP_COMMAND ||
comparam == IP_EXEC_STOP;
clean = bool_param(j->intparams[IP_EXEC_CLEAN]);
username = string_param(j->intparams[injail
? IP_EXEC_JAIL_USER : IP_EXEC_SYSTEM_USER]);
sjuser = bool_param(j->intparams[IP_EXEC_SYSTEM_JAIL_USER]);
consfd = 0;
if (injail &&
(conslog = string_param(j->intparams[IP_EXEC_CONSOLELOG]))) {
if (check_path(j, "exec.consolelog", conslog, 1, NULL) < 0)
return -1;
consfd =
open(conslog, O_WRONLY | O_CREAT | O_APPEND, DEFFILEMODE);
if (consfd < 0) {
jail_warnx(j, "open %s: %s", conslog, strerror(errno));
return -1;
}
}
comlen = 0;
for (i = 0; argv[i]; i++)
comlen += strlen(argv[i]) + 1;
j->comline = cs = emalloc(comlen);
for (i = 0; argv[i]; i++) {
strcpy(cs, argv[i]);
if (argv[i + 1]) {
cs += strlen(argv[i]) + 1;
cs[-1] = ' ';
}
}
if (verbose > 0)
jail_note(j, "run command%s%s%s: %s\n",
injail ? " in jail" : "", username ? " as " : "",
username ? username : "", j->comline);
pid = fork();
if (pid < 0)
err(1, "fork");
if (pid > 0) {
if (bg) {
free(j->comline);
j->comline = NULL;
return 0;
} else {
paralimit--;
add_proc(j, pid);
return 1;
}
}
if (bg)
setsid();
/* Set up the environment and run the command */
pwd = NULL;
lcap = NULL;
if ((clean || username) && injail && sjuser &&
get_user_info(j, username, &pwd, &lcap) < 0)
exit(1);
if (injail) {
/* jail_attach won't chdir along with its chroot. */
path = string_param(j->intparams[KP_PATH]);
if (path && chdir(path) < 0) {
jail_warnx(j, "chdir %s: %s", path, strerror(errno));
exit(1);
}
if (int_param(j->intparams[IP_EXEC_FIB], &fib) &&
setfib(fib) < 0) {
jail_warnx(j, "setfib: %s", strerror(errno));
exit(1);
}
if (jail_attach(j->jid) < 0) {
jail_warnx(j, "jail_attach: %s", strerror(errno));
exit(1);
}
}
if (clean || username) {
if (!(injail && sjuser) &&
get_user_info(j, username, &pwd, &lcap) < 0)
exit(1);
if (clean) {
term = getenv("TERM");
environ = &cleanenv;
setenv("PATH", "/bin:/usr/bin", 0);
setenv("TERM", term, 1);
}
if (setusercontext(lcap, pwd, pwd->pw_uid, username
? LOGIN_SETALL & ~LOGIN_SETGROUP & ~LOGIN_SETLOGIN
: LOGIN_SETPATH | LOGIN_SETENV) < 0) {
jail_warnx(j, "setusercontext %s: %s", pwd->pw_name,
strerror(errno));
exit(1);
}
login_close(lcap);
setenv("USER", pwd->pw_name, 1);
setenv("HOME", pwd->pw_dir, 1);
setenv("SHELL",
*pwd->pw_shell ? pwd->pw_shell : _PATH_BSHELL, 1);
if (clean && chdir(pwd->pw_dir) < 0) {
jail_warnx(j, "chdir %s: %s",
pwd->pw_dir, strerror(errno));
exit(1);
}
endpwent();
}
if (consfd != 0 && (dup2(consfd, 1) < 0 || dup2(consfd, 2) < 0)) {
jail_warnx(j, "exec.consolelog: %s", strerror(errno));
exit(1);
}
closefrom(3);
execvp(argv[0], argv);
jail_warnx(j, "exec %s: %s", argv[0], strerror(errno));
exit(1);
}
/*
* Add a process to the hash, tied to a jail.
*/
static void
add_proc(struct cfjail *j, pid_t pid)
{
struct kevent ke;
struct cfjail *tj;
struct phash *ph;
if (!kq && (kq = kqueue()) < 0)
err(1, "kqueue");
EV_SET(&ke, pid, EVFILT_PROC, EV_ADD, NOTE_EXIT, 0, NULL);
if (kevent(kq, &ke, 1, NULL, 0, NULL) < 0)
err(1, "kevent");
ph = emalloc(sizeof(struct phash));
ph->j = j;
ph->pid = pid;
LIST_INSERT_HEAD(&phash[pid % PHASH_SIZE], ph, le);
j->nprocs++;
j->flags |= JF_SLEEPQ;
if (j->timeout.tv_sec == 0)
requeue(j, &sleeping);
else {
/* File the jail in the sleep queue acording to its timeout. */
TAILQ_REMOVE(j->queue, j, tq);
TAILQ_FOREACH(tj, &sleeping, tq) {
if (!tj->timeout.tv_sec ||
j->timeout.tv_sec < tj->timeout.tv_sec ||
(j->timeout.tv_sec == tj->timeout.tv_sec &&
j->timeout.tv_nsec <= tj->timeout.tv_nsec)) {
TAILQ_INSERT_BEFORE(tj, j, tq);
break;
}
}
if (tj == NULL)
TAILQ_INSERT_TAIL(&sleeping, j, tq);
j->queue = &sleeping;
}
}
/*
* Remove any processes from the hash that correspond to a jail.
*/
static void
clear_procs(struct cfjail *j)
{
struct kevent ke;
struct phash *ph, *tph;
int i;
j->nprocs = 0;
for (i = 0; i < PHASH_SIZE; i++)
LIST_FOREACH_SAFE(ph, &phash[i], le, tph)
if (ph->j == j) {
EV_SET(&ke, ph->pid, EVFILT_PROC, EV_DELETE,
NOTE_EXIT, 0, NULL);
(void)kevent(kq, &ke, 1, NULL, 0, NULL);
LIST_REMOVE(ph, le);
free(ph);
}
}
/*
* Find the jail that corresponds to an exited process.
*/
static struct cfjail *
find_proc(pid_t pid)
{
struct cfjail *j;
struct phash *ph;
LIST_FOREACH(ph, &phash[pid % PHASH_SIZE], le)
if (ph->pid == pid) {
j = ph->j;
LIST_REMOVE(ph, le);
free(ph);
return --j->nprocs ? NULL : j;
}
return NULL;
}
/*
* Send SIGTERM to all processes in a jail and wait for them to die.
*/
static int
term_procs(struct cfjail *j)
{
struct kinfo_proc *ki;
int i, noted, pcnt, timeout;
static kvm_t *kd;
if (!int_param(j->intparams[IP_STOP_TIMEOUT], &timeout))
timeout = DEFAULT_STOP_TIMEOUT;
else if (timeout == 0)
return 0;
if (kd == NULL) {
kd = kvm_open(NULL, NULL, NULL, O_RDONLY, NULL);
if (kd == NULL)
return 0;
}
ki = kvm_getprocs(kd, KERN_PROC_PROC, 0, &pcnt);
if (ki == NULL)
return 0;
noted = 0;
for (i = 0; i < pcnt; i++)
if (ki[i].ki_jid == j->jid &&
kill(ki[i].ki_pid, SIGTERM) == 0) {
add_proc(j, ki[i].ki_pid);
if (verbose > 0) {
if (!noted) {
noted = 1;
jail_note(j, "sent SIGTERM to:");
}
printf(" %d", ki[i].ki_pid);
}
}
if (noted)
printf("\n");
if (j->nprocs > 0) {
clock_gettime(CLOCK_REALTIME, &j->timeout);
j->timeout.tv_sec += timeout;
return 1;
}
return 0;
}
/*
* Look up a user in the passwd and login.conf files.
*/
static int
get_user_info(struct cfjail *j, const char *username,
const struct passwd **pwdp, login_cap_t **lcapp)
{
const struct passwd *pwd;
*pwdp = pwd = username ? getpwnam(username) : getpwuid(getuid());
if (pwd == NULL) {
if (errno)
jail_warnx(j, "getpwnam%s%s: %s", username ? " " : "",
username ? username : "", strerror(errno));
else if (username)
jail_warnx(j, "%s: no such user", username);
else
jail_warnx(j, "unknown uid %d", getuid());
return -1;
}
*lcapp = login_getpwclass(pwd);
if (*lcapp == NULL) {
jail_warnx(j, "getpwclass %s: %s", pwd->pw_name,
strerror(errno));
return -1;
}
/* Set the groups while the group file is still available */
if (initgroups(pwd->pw_name, pwd->pw_gid) < 0) {
jail_warnx(j, "initgroups %s: %s", pwd->pw_name,
strerror(errno));
return -1;
}
return 0;
}
/*
* Make sure a mount or consolelog path is a valid absolute pathname
* with no symlinks.
*/
static int
check_path(struct cfjail *j, const char *pname, const char *path, int isfile,
const char *umount_type)
{
struct stat st, mpst;
struct statfs stfs;
char *tpath, *p;
const char *jailpath;
size_t jplen;
if (path[0] != '/') {
jail_warnx(j, "%s: %s: not an absolute pathname",
pname, path);
return -1;
}
/*
* Only check for symlinks in components below the jail's path,
* since that's where the security risk lies.
*/
jailpath = string_param(j->intparams[KP_PATH]);
if (jailpath == NULL)
jailpath = "";
jplen = strlen(jailpath);
if (!strncmp(path, jailpath, jplen) && path[jplen] == '/') {
tpath = alloca(strlen(path) + 1);
strcpy(tpath, path);
for (p = tpath + jplen; p != NULL; ) {
p = strchr(p + 1, '/');
if (p)
*p = '\0';
if (lstat(tpath, &st) < 0) {
if (errno == ENOENT && isfile && !p)
break;
jail_warnx(j, "%s: %s: %s", pname, tpath,
strerror(errno));
return -1;
}
if (S_ISLNK(st.st_mode)) {
jail_warnx(j, "%s: %s is a symbolic link",
pname, tpath);
return -1;
}
if (p)
*p = '/';
}
}
if (umount_type != NULL) {
if (stat(path, &st) < 0 || statfs(path, &stfs) < 0) {
jail_warnx(j, "%s: %s: %s", pname, path,
strerror(errno));
return -1;
}
if (stat(stfs.f_mntonname, &mpst) < 0) {
jail_warnx(j, "%s: %s: %s", pname, stfs.f_mntonname,
strerror(errno));
return -1;
}
if (st.st_ino != mpst.st_ino) {
jail_warnx(j, "%s: %s: not a mount point",
pname, path);
return -1;
}
if (strcmp(stfs.f_fstypename, umount_type)) {
jail_warnx(j, "%s: %s: not a %s mount",
pname, path, umount_type);
return -1;
}
}
return 0;
}

831
usr.sbin/jail/config.c Normal file
View File

@ -0,0 +1,831 @@
/*-
* Copyright (c) 2011 James Gritton
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
* OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
* HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
* LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
* OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
* SUCH DAMAGE.
*/
#include <sys/cdefs.h>
__FBSDID("$FreeBSD$");
#include <sys/types.h>
#include <sys/errno.h>
#include <sys/socket.h>
#include <sys/sysctl.h>
#include <arpa/inet.h>
#include <netinet/in.h>
#include <err.h>
#include <netdb.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include "jailp.h"
struct ipspec {
const char *name;
unsigned flags;
};
extern FILE *yyin;
extern int yynerrs;
struct cfjails cfjails = TAILQ_HEAD_INITIALIZER(cfjails);
static void free_param(struct cfparams *pp, struct cfparam *p);
static void free_param_strings(struct cfparam *p);
static const struct ipspec intparams[] = {
[IP_ALLOW_DYING] = {"allow.dying", PF_INTERNAL | PF_BOOL},
[IP_COMMAND] = {"command", PF_INTERNAL},
[IP_DEPEND] = {"depend", PF_INTERNAL},
[IP_EXEC_CLEAN] = {"exec.clean", PF_INTERNAL | PF_BOOL},
[IP_EXEC_CONSOLELOG] = {"exec.consolelog", PF_INTERNAL},
[IP_EXEC_FIB] = {"exec.fib", PF_INTERNAL | PF_INT},
[IP_EXEC_JAIL_USER] = {"exec.jail_user", PF_INTERNAL},
[IP_EXEC_POSTSTART] = {"exec.poststart", PF_INTERNAL},
[IP_EXEC_POSTSTOP] = {"exec.poststop", PF_INTERNAL},
[IP_EXEC_PRESTART] = {"exec.prestart", PF_INTERNAL},
[IP_EXEC_PRESTOP] = {"exec.prestop", PF_INTERNAL},
[IP_EXEC_START] = {"exec.start", PF_INTERNAL},
[IP_EXEC_STOP] = {"exec.stop", PF_INTERNAL},
[IP_EXEC_SYSTEM_JAIL_USER]= {"exec.system_jail_user",
PF_INTERNAL | PF_BOOL},
[IP_EXEC_SYSTEM_USER] = {"exec.system_user", PF_INTERNAL},
[IP_EXEC_TIMEOUT] = {"exec.timeout", PF_INTERNAL | PF_INT},
#if defined(INET) || defined(INET6)
[IP_INTERFACE] = {"interface", PF_INTERNAL},
[IP_IP_HOSTNAME] = {"ip_hostname", PF_INTERNAL | PF_BOOL},
#endif
[IP_MOUNT] = {"mount", PF_INTERNAL},
[IP_MOUNT_DEVFS] = {"mount.devfs", PF_INTERNAL | PF_BOOL},
[IP_MOUNT_FSTAB] = {"mount.fstab", PF_INTERNAL},
[IP_STOP_TIMEOUT] = {"stop.timeout", PF_INTERNAL | PF_INT},
[IP_VNET_INTERFACE] = {"vnet.interface", PF_INTERNAL},
#ifdef INET
[IP__IP4_IFADDR] = {"ip4.addr", PF_INTERNAL | PF_CONV},
#endif
#ifdef INET6
[IP__IP6_IFADDR] = {"ip6.addr", PF_INTERNAL | PF_CONV},
#endif
[IP__MOUNT_FROM_FSTAB] = {"mount.fstab", PF_INTERNAL | PF_CONV},
[IP__OP] = {NULL, PF_CONV},
[KP_ALLOW_CHFLAGS] = {"allow.chflags", 0},
[KP_ALLOW_MOUNT] = {"allow.mount", 0},
[KP_ALLOW_RAW_SOCKETS] = {"allow.raw_sockets", 0},
[KP_ALLOW_SET_HOSTNAME]= {"allow.set_hostname", 0},
[KP_ALLOW_SOCKET_AF] = {"allow.socket_af", 0},
[KP_ALLOW_SYSVIPC] = {"allow.sysvipc", 0},
[KP_DEVFS_RULESET] = {"devfs_ruleset", 0},
[KP_ENFORCE_STATFS] = {"enforce_statfs", 0},
[KP_HOST_HOSTNAME] = {"host.hostname", 0},
#ifdef INET
[KP_IP4_ADDR] = {"ip4.addr", 0},
#endif
#ifdef INET6
[KP_IP6_ADDR] = {"ip6.addr", 0},
#endif
[KP_JID] = {"jid", 0},
[KP_NAME] = {"name", 0},
[KP_PATH] = {"path", 0},
[KP_PERSIST] = {"persist", 0},
[KP_SECURELEVEL] = {"securelevel", 0},
[KP_VNET] = {"vnet", 0},
};
/*
* Parse the jail configuration file.
*/
void
load_config(void)
{
struct cfjails wild;
struct cfparams opp;
struct cfjail *j, *tj, *wj;
struct cfparam *p, *vp, *tp;
struct cfstring *s, *vs, *ns;
struct cfvar *v;
char *ep;
size_t varoff;
int did_self, jseq, pgen;
if (!strcmp(cfname, "-")) {
cfname = "STDIN";
yyin = stdin;
} else {
yyin = fopen(cfname, "r");
if (!yyin)
err(1, "%s", cfname);
}
if (yyparse() || yynerrs)
exit(1);
/* Separate the wildcard jails out from the actual jails. */
jseq = 0;
TAILQ_INIT(&wild);
TAILQ_FOREACH_SAFE(j, &cfjails, tq, tj) {
j->seq = ++jseq;
if (wild_jail_name(j->name))
requeue(j, &wild);
}
TAILQ_FOREACH(j, &cfjails, tq) {
/* Set aside the jail's parameters. */
TAILQ_INIT(&opp);
TAILQ_CONCAT(&opp, &j->params, tq);
/*
* The jail name implies its "name" or "jid" parameter,
* though they may also be explicitly set later on.
*/
add_param(j, NULL,
strtol(j->name, &ep, 10) && !*ep ? KP_JID : KP_NAME,
j->name);
/*
* Collect parameters for the jail, global parameters/variables,
* and any matching wildcard jails.
*/
did_self = 0;
TAILQ_FOREACH(wj, &wild, tq) {
if (j->seq < wj->seq && !did_self) {
TAILQ_FOREACH(p, &opp, tq)
add_param(j, p, 0, NULL);
did_self = 1;
}
if (wild_jail_match(j->name, wj->name))
TAILQ_FOREACH(p, &wj->params, tq)
add_param(j, p, 0, NULL);
}
if (!did_self)
TAILQ_FOREACH(p, &opp, tq)
add_param(j, p, 0, NULL);
/* Resolve any variable substitutions. */
pgen = 0;
TAILQ_FOREACH(p, &j->params, tq) {
p->gen = ++pgen;
find_vars:
TAILQ_FOREACH(s, &p->val, tq) {
varoff = 0;
while ((v = STAILQ_FIRST(&s->vars))) {
TAILQ_FOREACH(vp, &j->params, tq)
if (!strcmp(vp->name, v->name))
break;
if (!vp) {
jail_warnx(j,
"%s: variable \"%s\" not found",
p->name, v->name);
bad_var:
j->flags |= JF_FAILED;
TAILQ_FOREACH(vp, &j->params, tq)
if (vp->gen == pgen)
vp->flags |= PF_BAD;
goto free_var;
}
if (vp->flags & PF_BAD)
goto bad_var;
if (vp->gen == pgen) {
jail_warnx(j, "%s: variable loop",
v->name);
goto bad_var;
}
TAILQ_FOREACH(vs, &vp->val, tq)
if (!STAILQ_EMPTY(&vs->vars)) {
vp->gen = pgen;
TAILQ_REMOVE(&j->params, vp,
tq);
TAILQ_INSERT_BEFORE(p, vp, tq);
p = vp;
goto find_vars;
}
vs = TAILQ_FIRST(&vp->val);
if (TAILQ_NEXT(vs, tq) != NULL &&
(s->s[0] != '\0' ||
STAILQ_NEXT(v, tq))) {
jail_warnx(j, "%s: array cannot be "
"substituted inline",
p->name);
goto bad_var;
}
s->s = erealloc(s->s, s->len + vs->len + 1);
memmove(s->s + v->pos + varoff + vs->len,
s->s + v->pos + varoff,
s->len - (v->pos + varoff) + 1);
memcpy(s->s + v->pos + varoff, vs->s, vs->len);
varoff += vs->len;
s->len += vs->len;
while ((vs = TAILQ_NEXT(vs, tq))) {
ns = emalloc(sizeof(struct cfstring));
ns->s = estrdup(vs->s);
ns->len = vs->len;
STAILQ_INIT(&ns->vars);
TAILQ_INSERT_AFTER(&p->val, s, ns, tq);
s = ns;
}
free_var:
free(v->name);
STAILQ_REMOVE_HEAD(&s->vars, tq);
free(v);
}
}
}
/* Free the jail's original parameter list and any variables. */
while ((p = TAILQ_FIRST(&opp)))
free_param(&opp, p);
TAILQ_FOREACH_SAFE(p, &j->params, tq, tp)
if (p->flags & PF_VAR)
free_param(&j->params, p);
}
while ((wj = TAILQ_FIRST(&wild))) {
free(wj->name);
while ((p = TAILQ_FIRST(&wj->params)))
free_param(&wj->params, p);
TAILQ_REMOVE(&wild, wj, tq);
}
}
/*
* Create a new jail record.
*/
struct cfjail *
add_jail(void)
{
struct cfjail *j;
j = emalloc(sizeof(struct cfjail));
memset(j, 0, sizeof(struct cfjail));
TAILQ_INIT(&j->params);
STAILQ_INIT(&j->dep[DEP_FROM]);
STAILQ_INIT(&j->dep[DEP_TO]);
j->queue = &cfjails;
TAILQ_INSERT_TAIL(&cfjails, j, tq);
return j;
}
/*
* Add a parameter to a jail.
*/
void
add_param(struct cfjail *j, const struct cfparam *p, enum intparam ipnum,
const char *value)
{
struct cfstrings nss;
struct cfparam *dp, *np;
struct cfstring *s, *ns;
struct cfvar *v, *nv;
const char *name;
char *cs, *tname;
unsigned flags;
if (j == NULL) {
/* Create a single anonymous jail if one doesn't yet exist. */
j = TAILQ_LAST(&cfjails, cfjails);
if (j == NULL)
j = add_jail();
}
TAILQ_INIT(&nss);
if (p != NULL) {
name = p->name;
flags = p->flags;
/*
* Make a copy of the parameter's string list,
* which may be freed if it's overridden later.
*/
TAILQ_FOREACH(s, &p->val, tq) {
ns = emalloc(sizeof(struct cfstring));
ns->s = estrdup(s->s);
ns->len = s->len;
STAILQ_INIT(&ns->vars);
STAILQ_FOREACH(v, &s->vars, tq) {
nv = emalloc(sizeof(struct cfvar));
nv->name = strdup(v->name);
nv->pos = v->pos;
STAILQ_INSERT_TAIL(&ns->vars, nv, tq);
}
TAILQ_INSERT_TAIL(&nss, ns, tq);
}
} else {
flags = PF_APPEND;
if (ipnum != 0) {
name = intparams[ipnum].name;
flags |= intparams[ipnum].flags;
} else if ((cs = strchr(value, '='))) {
tname = alloca(cs - value + 1);
strlcpy(tname, value, cs - value + 1);
name = tname;
value = cs + 1;
} else {
name = value;
value = NULL;
}
if (value != NULL) {
ns = emalloc(sizeof(struct cfstring));
ns->s = estrdup(value);
ns->len = strlen(value);
STAILQ_INIT(&ns->vars);
TAILQ_INSERT_TAIL(&nss, ns, tq);
}
}
/* See if this parameter has already been added. */
if (ipnum != 0)
dp = j->intparams[ipnum];
else
TAILQ_FOREACH(dp, &j->params, tq)
if (!(dp->flags & PF_CONV) && equalopts(dp->name, name))
break;
if (dp != NULL) {
/* Found it - append or replace. */
if (strcmp(dp->name, name)) {
free(dp->name);
dp->name = estrdup(name);
}
if (!(flags & PF_APPEND) || TAILQ_EMPTY(&nss))
free_param_strings(dp);
TAILQ_CONCAT(&dp->val, &nss, tq);
dp->flags |= flags;
} else {
/* Not found - add it. */
np = emalloc(sizeof(struct cfparam));
np->name = estrdup(name);
TAILQ_INIT(&np->val);
TAILQ_CONCAT(&np->val, &nss, tq);
np->flags = flags;
np->gen = 0;
TAILQ_INSERT_TAIL(&j->params, np, tq);
if (ipnum != 0)
j->intparams[ipnum] = np;
else
for (ipnum = 1; ipnum < IP_NPARAM; ipnum++)
if (!(intparams[ipnum].flags & PF_CONV) &&
equalopts(name, intparams[ipnum].name)) {
j->intparams[ipnum] = np;
np->flags |= intparams[ipnum].flags;
break;
}
}
}
/*
* Return if a boolean parameter exists and is true.
*/
int
bool_param(const struct cfparam *p)
{
const char *cs;
if (p == NULL)
return 0;
cs = strrchr(p->name, '.');
return !strncmp(cs ? cs + 1 : p->name, "no", 2) ^
(TAILQ_EMPTY(&p->val) ||
!strcasecmp(TAILQ_LAST(&p->val, cfstrings)->s, "true") ||
(strtol(TAILQ_LAST(&p->val, cfstrings)->s, NULL, 10)));
}
/*
* Set an integer if a parameter if it exists.
*/
int
int_param(const struct cfparam *p, int *ip)
{
if (p == NULL || TAILQ_EMPTY(&p->val))
return 0;
*ip = strtol(TAILQ_LAST(&p->val, cfstrings)->s, NULL, 10);
return 1;
}
/*
* Return the string value of a scalar parameter if it exists.
*/
const char *
string_param(const struct cfparam *p)
{
return (p && !TAILQ_EMPTY(&p->val)
? TAILQ_LAST(&p->val, cfstrings)->s : NULL);
}
/*
* Check syntax and values of internal parameters. Set some internal
* parameters based on the values of others.
*/
int
check_intparams(struct cfjail *j)
{
struct cfparam *p;
struct cfstring *s;
FILE *f;
const char *val;
char *cs, *ep, *ln;
size_t lnlen;
int error;
#if defined(INET) || defined(INET6)
struct addrinfo hints;
struct addrinfo *ai0, *ai;
const char *hostname;
int gicode, defif, prefix;
#endif
#ifdef INET
struct in_addr addr4;
int ip4ok;
char avalue4[INET_ADDRSTRLEN];
#endif
#ifdef INET6
struct in6_addr addr6;
int ip6ok;
char avalue6[INET6_ADDRSTRLEN];
#endif
error = 0;
/* Check format of boolan and integer values. */
TAILQ_FOREACH(p, &j->params, tq) {
if (!TAILQ_EMPTY(&p->val) && (p->flags & (PF_BOOL | PF_INT))) {
val = TAILQ_LAST(&p->val, cfstrings)->s;
if (p->flags & PF_BOOL) {
if (strcasecmp(val, "false") &&
strcasecmp(val, "true") &&
((void)strtol(val, &ep, 10), *ep)) {
jail_warnx(j,
"%s: unknown boolean value \"%s\"",
p->name, val);
error = -1;
}
} else {
(void)strtol(val, &ep, 10);
if (ep == val || *ep) {
jail_warnx(j,
"%s: non-integer value \"%s\"",
p->name, val);
error = -1;
}
}
}
}
#if defined(INET) || defined(INET6)
/*
* The ip_hostname parameter looks up the hostname, and adds parameters
* for any IP addresses it finds.
*/
if (((j->flags & JF_OP_MASK) != JF_STOP ||
j->intparams[IP_INTERFACE] != NULL) &&
bool_param(j->intparams[IP_IP_HOSTNAME]) &&
(hostname = string_param(j->intparams[KP_HOST_HOSTNAME]))) {
j->intparams[IP_IP_HOSTNAME] = NULL;
/*
* Silently ignore unsupported address families from
* DNS lookups.
*/
#ifdef INET
ip4ok = feature_present("inet");
#endif
#ifdef INET6
ip6ok = feature_present("inet6");
#endif
if (
#if defined(INET) && defined(INET6)
ip4ok || ip6ok
#elif defined(INET)
ip4ok
#elif defined(INET6)
ip6ok
#endif
) {
/* Look up the hostname (or get the address) */
memset(&hints, 0, sizeof(hints));
hints.ai_socktype = SOCK_STREAM;
hints.ai_family =
#if defined(INET) && defined(INET6)
ip4ok ? (ip6ok ? PF_UNSPEC : PF_INET) : PF_INET6;
#elif defined(INET)
PF_INET;
#elif defined(INET6)
PF_INET6;
#endif
gicode = getaddrinfo(hostname, NULL, &hints, &ai0);
if (gicode != 0) {
jail_warnx(j, "host.hostname %s: %s", hostname,
gai_strerror(gicode));
error = -1;
} else {
/*
* Convert the addresses to ASCII so jailparam
* can convert them back. Errors are not
* expected here.
*/
for (ai = ai0; ai; ai = ai->ai_next)
switch (ai->ai_family) {
#ifdef INET
case AF_INET:
memcpy(&addr4,
&((struct sockaddr_in *)
(void *)ai->ai_addr)->
sin_addr, sizeof(addr4));
if (inet_ntop(AF_INET,
&addr4, avalue4,
INET_ADDRSTRLEN) == NULL)
err(1, "inet_ntop");
add_param(j, NULL, KP_IP4_ADDR,
avalue4);
break;
#endif
#ifdef INET6
case AF_INET6:
memcpy(&addr6,
&((struct sockaddr_in6 *)
(void *)ai->ai_addr)->
sin6_addr, sizeof(addr6));
if (inet_ntop(AF_INET6,
&addr6, avalue6,
INET6_ADDRSTRLEN) == NULL)
err(1, "inet_ntop");
add_param(j, NULL, KP_IP6_ADDR,
avalue6);
break;
#endif
}
freeaddrinfo(ai0);
}
}
}
/*
* IP addresses may include an interface to set that address on,
* and a netmask/suffix for that address.
*/
defif = string_param(j->intparams[IP_INTERFACE]) != NULL;
#ifdef INET
if (j->intparams[KP_IP4_ADDR] != NULL) {
TAILQ_FOREACH(s, &j->intparams[KP_IP4_ADDR]->val, tq) {
cs = strchr(s->s, '|');
if (cs || defif)
add_param(j, NULL, IP__IP4_IFADDR, s->s);
if (cs) {
strcpy(s->s, cs + 1);
s->len -= cs + 1 - s->s;
}
if ((cs = strchr(s->s, '/'))) {
prefix = strtol(cs + 1, &ep, 10);
if (*ep == '.'
? inet_pton(AF_INET, cs + 1, &addr4) != 1
: *ep || prefix < 0 || prefix > 32) {
jail_warnx(j,
"ip4.addr: bad netmask \"%s\"", cs);
error = -1;
}
*cs = '\0';
s->len = cs - s->s + 1;
}
}
}
#endif
#ifdef INET6
if (j->intparams[KP_IP6_ADDR] != NULL) {
TAILQ_FOREACH(s, &j->intparams[KP_IP6_ADDR]->val, tq) {
cs = strchr(s->s, '|');
if (cs || defif)
add_param(j, NULL, IP__IP6_IFADDR, s->s);
if (cs) {
strcpy(s->s, cs + 1);
s->len -= cs + 1 - s->s;
}
if ((cs = strchr(s->s, '/'))) {
prefix = strtol(cs + 1, &ep, 10);
if (*ep || prefix < 0 || prefix > 128) {
jail_warnx(j,
"ip6.addr: bad prefixlen \"%s\"",
cs);
error = -1;
}
*cs = '\0';
s->len = cs - s->s + 1;
}
}
}
#endif
#endif
/*
* Read mount.fstab file(s), and treat each line as its own mount
* parameter.
*/
if (j->intparams[IP_MOUNT_FSTAB] != NULL) {
TAILQ_FOREACH(s, &j->intparams[IP_MOUNT_FSTAB]->val, tq) {
if (s->len == 0)
continue;
f = fopen(s->s, "r");
if (f == NULL) {
jail_warnx(j, "mount.fstab: %s: %s",
s->s, strerror(errno));
error = -1;
continue;
}
while ((ln = fgetln(f, &lnlen))) {
if ((cs = memchr(ln, '#', lnlen - 1)))
lnlen = cs - ln + 1;
if (ln[lnlen - 1] == '\n' ||
ln[lnlen - 1] == '#')
ln[lnlen - 1] = '\0';
else {
cs = alloca(lnlen + 1);
strlcpy(cs, ln, lnlen + 1);
ln = cs;
}
add_param(j, NULL, IP__MOUNT_FROM_FSTAB, ln);
}
fclose(f);
}
}
if (error)
failed(j);
return error;
}
/*
* Import parameters into libjail's binary jailparam format.
*/
int
import_params(struct cfjail *j)
{
struct cfparam *p;
struct cfstring *s, *ts;
struct jailparam *jp;
char *value, *cs;
size_t vallen;
int error;
error = 0;
j->njp = 0;
TAILQ_FOREACH(p, &j->params, tq)
if (!(p->flags & PF_INTERNAL))
j->njp++;
j->jp = jp = emalloc(j->njp * sizeof(struct jailparam));
TAILQ_FOREACH(p, &j->params, tq) {
if (p->flags & PF_INTERNAL)
continue;
if (jailparam_init(jp, p->name) < 0) {
error = -1;
jail_warnx(j, "%s", jail_errmsg);
continue;
}
if (TAILQ_EMPTY(&p->val))
value = NULL;
else if (!jp->jp_elemlen ||
!TAILQ_NEXT(TAILQ_FIRST(&p->val), tq)) {
/*
* Scalar parameters silently discard multiple (array)
* values, keeping only the last value added. This
* lets values added from the command line append to
* arrays wthout pre-checking the type.
*/
value = TAILQ_LAST(&p->val, cfstrings)->s;
} else {
/*
* Convert arrays into comma-separated strings, which
* jailparam_import will then convert back into arrays.
*/
vallen = 0;
TAILQ_FOREACH(s, &p->val, tq)
vallen += s->len + 1;
value = alloca(vallen);
cs = value;
TAILQ_FOREACH_SAFE(s, &p->val, tq, ts) {
strcpy(cs, s->s);
if (ts != NULL) {
cs += s->len + 1;
cs[-1] = ',';
}
}
}
if (jailparam_import(jp, value) < 0) {
error = -1;
jail_warnx(j, "%s", jail_errmsg);
}
jp++;
}
if (error) {
jailparam_free(j->jp, j->njp);
free(j->jp);
j->jp = NULL;
failed(j);
}
return error;
}
/*
* Check if options are equal (with or without the "no" prefix).
*/
int
equalopts(const char *opt1, const char *opt2)
{
char *p;
/* "opt" vs. "opt" or "noopt" vs. "noopt" */
if (strcmp(opt1, opt2) == 0)
return (1);
/* "noopt" vs. "opt" */
if (strncmp(opt1, "no", 2) == 0 && strcmp(opt1 + 2, opt2) == 0)
return (1);
/* "opt" vs. "noopt" */
if (strncmp(opt2, "no", 2) == 0 && strcmp(opt1, opt2 + 2) == 0)
return (1);
while ((p = strchr(opt1, '.')) != NULL &&
!strncmp(opt1, opt2, ++p - opt1)) {
opt2 += p - opt1;
opt1 = p;
/* "foo.noopt" vs. "foo.opt" */
if (strncmp(opt1, "no", 2) == 0 && strcmp(opt1 + 2, opt2) == 0)
return (1);
/* "foo.opt" vs. "foo.noopt" */
if (strncmp(opt2, "no", 2) == 0 && strcmp(opt1, opt2 + 2) == 0)
return (1);
}
return (0);
}
/*
* See if a jail name matches a wildcard.
*/
int
wild_jail_match(const char *jname, const char *wname)
{
const char *jc, *jd, *wc, *wd;
/*
* A non-final "*" component in the wild name matches a single jail
* component, and a final "*" matches one or more jail components.
*/
for (jc = jname, wc = wname;
(jd = strchr(jc, '.')) && (wd = strchr(wc, '.'));
jc = jd + 1, wc = wd + 1)
if (strncmp(jc, wc, jd - jc + 1) && strncmp(wc, "*.", 2))
return 0;
return (!strcmp(jc, wc) || !strcmp(wc, "*"));
}
/*
* Return if a jail name is a wildcard.
*/
int
wild_jail_name(const char *wname)
{
const char *wc;
for (wc = strchr(wname, '*'); wc; wc = strchr(wc + 1, '*'))
if ((wc == wname || wc[-1] == '.') &&
(wc[1] == '\0' || wc[1] == '.'))
return 1;
return 0;
}
/*
* Free a parameter record and all its strings and variables.
*/
static void
free_param(struct cfparams *pp, struct cfparam *p)
{
free(p->name);
free_param_strings(p);
TAILQ_REMOVE(pp, p, tq);
free(p);
}
static void
free_param_strings(struct cfparam *p)
{
struct cfstring *s;
struct cfvar *v;
while ((s = TAILQ_FIRST(&p->val))) {
free(s->s);
while ((v = STAILQ_FIRST(&s->vars))) {
free(v->name);
STAILQ_REMOVE_HEAD(&s->vars, tq);
free(v);
}
TAILQ_REMOVE(&p->val, s, tq);
free(s);
}
}

View File

@ -1,6 +1,5 @@
.\"
.\" Copyright (c) 2000, 2003 Robert N. M. Watson
.\" Copyright (c) 2008 James Gritton
.\" Copyright (c) 2008-2012 James Gritton
.\" All rights reserved.
.\"
.\" Redistribution and use in source and binary forms, with or without
@ -24,116 +23,68 @@
.\" 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 February 29, 2012
.Dd April 26, 2012
.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 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 J Ar jid_file
.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 d
Allow making changes to a dying jail.
.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
parameter.
.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.
.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.
.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
.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
The jail
.Va jid
and
.Va name
parameters (if specified) must not refer to an existing jail.
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
@ -141,27 +92,158 @@ One of the
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.
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
At least one of the
.Fl c ,
.Fl m
or
Other available options are:
.Bl -tag -width indent
.It Fl d
Allow making changes to a dying jail, equivalent to the
.Va allow.dying
parameter.
.It Fl f Ar conf_file
Use configuration file
.Ar conf_file
instead of the default
.Pa /etc/jail.conf .
.It Fl h
Resolve the
.Va host.hostname
parameter (or
.Va hostname )
and add all IP addresses returned by the resolver
to the list of addresses for this 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 parameters used to start the jail.
.It Fl l
Run commands in a clean environment.
This is deprecated and is equivalent to the exec.clean parameter.
.It Fl n Ar jailname
Set the jail's name.
This is deprecated and is equivalent to the
.Va name
parameter.
.It Fl p Ar limit
Limit the number of commands from
.Va exec.*
that can run simultaneously.
.It Fl q
Suppress the message printed whenever a jail is created, modified or removed.
Only error messages will be printed.
.It Fl R
A variation of the
.Fl r
options must be specified.
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 the
.Va securelevel
parameter.
.It Fl u Ar username
The user name from host environment as whom jailed commands should run.
This is deprecated and is equivalent to the
.Va exec.jail_user
and
.Va exec.system_jail_user
parameters.
.It Fl U Ar username
The user name from 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
.Ar Parameters
are listed in
If no arguments are given after the options, the operation (except
remove) will be performed on all jails specified in the
.Xr jail.conf 5
file.
A single argument of a jail name will operate only on the specified jail.
The
.Fl r
and
.Fl R
options can also remove running jails that aren't in the
.Xr jail.conf 5
file, specified by name or jid.
.P
An argument of
.Dq *
is a wildcard that will operate on all jails, regardless of whether
they appear in
.Xr jail.conf 5 ;
this is the surest way for
.Fl r
to remove all jails.
If hierarchical jails exist, a partial-matching wildcard definition may
be specified.
For example, an argument of
.Dq foo.*
would apply to jails with names like
.Dq foo.bar
and
.Dq foo.bar.baz .
.Pp
A jail may be specified with parameters directly on the command line.
In this case, the
.Xr jail.conf 5
file will not be used.
For backward compatibility, the command line may also have four fixed
parameters, without names:
.Ar path ,
.Ar hostname ,
.Ar ip ,
and
.Ar command .
This mode will always create a new jail, and the
.Fl c
and
.Fl m
options 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, following the options.
form.
Some parameters are boolean, and do not have a value but are set by the
name alone with or without a
.Dq no
@ -169,41 +251,35 @@ prefix, e.g.
.Va persist
or
.Va nopersist .
Any parameters not set will be given default values, often based on the
current environment.
.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:
.Ar path ,
.Ar hostname ,
.Ar ip ,
They can also be given the values
.Dq true
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
.Fl c
and
.Fl m
options don't apply (and must not exist).
.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 +307,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 +335,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 +360,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
@ -314,13 +409,24 @@ permissions are effective and
.Va enforce_statfs
is set to a value lower than 2.
Devfs rules and rulesets cannot be viewed or modified from inside a jail.
.Pp
NOTE: It is important that only appropriate device nodes in devfs be
exposed to a jail; access to disk devices in the jail may permit processes
in the jail to bypass the jail sandboxing by modifying files outside of
the jail.
See
.Xr devfs 8
for information on how to use devfs rules to limit access to entries
in the per-jail devfs.
A simple devfs ruleset for jails is available as ruleset #4 in
.Pa /etc/defaults/devfs.rules .
.It Va children.max
The number of child jails allowed to be created by this jail (or by
other jails under this jail).
This limit is zero by default, indicating the jail is not allowed to
create child jails.
See the
.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
@ -345,10 +451,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
@ -459,6 +568,171 @@ 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 apply the ruleset in the
.Va devfs_ruleset
parameter (or a default of ruleset 4: devfsrules_jail)
to restrict the devices visible inside the prison.
.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
@ -476,7 +750,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
@ -489,20 +762,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.
@ -520,8 +781,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"
@ -619,7 +881,7 @@ or for running a virtual server.
.Pp
Start a shell in the jail:
.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
@ -633,15 +895,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
@ -652,11 +905,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
@ -674,36 +922,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.
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.
Create an entry for the jail in
.Pa /etc/jail.conf :
.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
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
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.
To start a virtual server environment,
.Pa /etc/rc
is run to launch various daemons and services, and
.Pa /etc/rc.shutdown
is run to shut them down when the jail is removed.
If you are running a single application in the jail,
substitute the command used to start the application for
.Dq /bin/sh /etc/rc ;
there may be some script available to cleanly shut down the application,
or it may be sufficient to go without a stop command, and have
.Nm
send
.Dv SIGTERM
to the application.
.Pp
Start the jail by running:
.Bd -literal -offset indent
jail -c testjail
.Ed
.Pp
A few warnings may be produced; however, it should all work properly.
You should be able to see
.Xr inetd 8 ,
.Xr syslogd 8 ,
@ -726,15 +985,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 ,
@ -742,9 +992,8 @@ 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:
.Bd -literal -offset indent
kill -TERM -1
kill -KILL -1
@ -754,21 +1003,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
@ -888,7 +1143,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.
@ -900,12 +1155,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 ,
@ -927,6 +1182,8 @@ utility appeared in
.Fx 4.0 .
Hierarchical/extensible jails were introduced in
.Fx 8.0 .
The configuration file was introduced in
.Fx 10.0 .
.Sh AUTHORS
.An -nosplit
The jail feature was written by
@ -947,14 +1204,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

File diff suppressed because it is too large Load Diff

231
usr.sbin/jail/jail.conf.5 Normal file
View File

@ -0,0 +1,231 @@
.\" Copyright (c) 2011 James Gritton
.\" All rights reserved.
.\"
.\" Redistribution and use in source and binary forms, with or without
.\" modification, are permitted provided that the following conditions
.\" are met:
.\" 1. Redistributions of source code must retain the above copyright
.\" notice, this list of conditions and the following disclaimer.
.\" 2. Redistributions in binary form must reproduce the above copyright
.\" notice, this list of conditions and the following disclaimer in the
.\" documentation and/or other materials provided with the distribution.
.\"
.\" THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
.\" ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
.\" IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
.\" ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
.\" FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
.\" DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
.\" OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
.\" HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
.\" LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
.\" OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
.\" SUCH DAMAGE.
.\"
.\" $FreeBSD$
.\"
.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.

232
usr.sbin/jail/jaillex.l Normal file
View File

@ -0,0 +1,232 @@
%{
/*-
* Copyright (c) 2011 James Gritton
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
* OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
* HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
* LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
* OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
* SUCH DAMAGE.
*/
#include <sys/cdefs.h>
__FBSDID("$FreeBSD$");
#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;
}

232
usr.sbin/jail/jailp.h Normal file
View File

@ -0,0 +1,232 @@
/*-
* Copyright (c) 2011 James Gritton.
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
* OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
* HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
* LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
* OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
* SUCH DAMAGE.
*
* $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 PF_CONV 0x40 /* Parameter duplicated in converted form */
#define JF_START 0x0001 /* -c */
#define JF_SET 0x0002 /* -m */
#define JF_STOP 0x0004 /* -r */
#define JF_DEPEND 0x0008 /* Operation required by dependency */
#define JF_WILD 0x0010 /* Not specified on the command line */
#define JF_FAILED 0x0020 /* Operation failed */
#define JF_PARAMS 0x0040 /* Parameters checked and imported */
#define JF_RDTUN 0x0080 /* Create-only parameter check has been done */
#define JF_PERSIST 0x0100 /* Jail is temporarily persistent */
#define JF_TIMEOUT 0x0200 /* A command (or process kill) timed out */
#define JF_SLEEPQ 0x0400 /* Waiting on a command and/or timeout */
#define JF_OP_MASK (JF_START | JF_SET | JF_STOP)
#define JF_RESTART (JF_START | JF_STOP)
#define JF_START_SET (JF_START | JF_SET)
#define JF_SET_RESTART (JF_SET | JF_STOP)
#define JF_START_SET_RESTART (JF_START | JF_SET | JF_STOP)
#define JF_DO_STOP(js) (((js) & (JF_SET | JF_STOP)) == JF_STOP)
enum intparam {
IP_ALLOW_DYING = 1, /* Allow making changes to a dying jail */
IP_COMMAND, /* Command run inside jail at creation */
IP_DEPEND, /* Jail starts after (stops before) another */
IP_EXEC_CLEAN, /* Run commands in a clean environment */
IP_EXEC_CONSOLELOG, /* Redirect optput for commands run in jail */
IP_EXEC_FIB, /* Run jailed commands with this FIB */
IP_EXEC_JAIL_USER, /* Run jailed commands as this user */
IP_EXEC_POSTSTART, /* Commands run outside jail after creating */
IP_EXEC_POSTSTOP, /* Commands run outside jail after removing */
IP_EXEC_PRESTART, /* Commands run outside jail before creating */
IP_EXEC_PRESTOP, /* Commands run outside jail before removing */
IP_EXEC_START, /* Commands run inside jail on creation */
IP_EXEC_STOP, /* Commands run inside jail on removal */
IP_EXEC_SYSTEM_JAIL_USER,/* Get jail_user from system passwd file */
IP_EXEC_SYSTEM_USER, /* Run non-jailed commands as this user */
IP_EXEC_TIMEOUT, /* Time to wait for a command to complete */
#if defined(INET) || defined(INET6)
IP_INTERFACE, /* Add IP addresses to this interface */
IP_IP_HOSTNAME, /* Get jail IP address(es) from hostname */
#endif
IP_MOUNT, /* Mount points in fstab(5) form */
IP_MOUNT_DEVFS, /* Mount /dev under prison root */
IP_MOUNT_FSTAB, /* A standard fstab(5) file */
IP_STOP_TIMEOUT, /* Time to wait after sending SIGTERM */
IP_VNET_INTERFACE, /* Assign interface(s) to vnet jail */
#ifdef INET
IP__IP4_IFADDR, /* Copy of ip4.addr with interface/netmask */
#endif
#ifdef INET6
IP__IP6_IFADDR, /* Copy of ip6.addr with interface/prefixlen */
#endif
IP__MOUNT_FROM_FSTAB, /* Line from mount.fstab file */
IP__OP, /* Placeholder for requested operation */
KP_ALLOW_CHFLAGS,
KP_ALLOW_MOUNT,
KP_ALLOW_RAW_SOCKETS,
KP_ALLOW_SET_HOSTNAME,
KP_ALLOW_SOCKET_AF,
KP_ALLOW_SYSVIPC,
KP_DEVFS_RULESET,
KP_ENFORCE_STATFS,
KP_HOST_HOSTNAME,
#ifdef INET
KP_IP4_ADDR,
#endif
#ifdef INET6
KP_IP6_ADDR,
#endif
KP_JID,
KP_NAME,
KP_PATH,
KP_PERSIST,
KP_SECURELEVEL,
KP_VNET,
IP_NPARAM
};
STAILQ_HEAD(cfvars, cfvar);
struct cfvar {
STAILQ_ENTRY(cfvar) tq;
char *name;
size_t pos;
};
TAILQ_HEAD(cfstrings, cfstring);
struct cfstring {
TAILQ_ENTRY(cfstring) tq;
char *s;
size_t len;
struct cfvars vars;
};
TAILQ_HEAD(cfparams, cfparam);
struct cfparam {
TAILQ_ENTRY(cfparam) tq;
char *name;
struct cfstrings val;
unsigned flags;
int gen;
};
TAILQ_HEAD(cfjails, cfjail);
STAILQ_HEAD(cfdepends, cfdepend);
struct cfjail {
TAILQ_ENTRY(cfjail) tq;
char *name;
char *comline;
struct cfparams params;
struct cfdepends dep[2];
struct cfjails *queue;
struct cfparam *intparams[IP_NPARAM];
struct cfstring *comstring;
struct jailparam *jp;
struct timespec timeout;
const enum intparam *comparam;
unsigned flags;
int jid;
int seq;
int pstatus;
int ndeps;
int njp;
int nprocs;
};
struct cfdepend {
STAILQ_ENTRY(cfdepend) tq[2];
struct cfjail *j[2];
unsigned flags;
};
extern void *emalloc(size_t);
extern void *erealloc(void *, size_t);
extern char *estrdup(const char *);
extern int create_jail(struct cfjail *j);
extern void failed(struct cfjail *j);
extern void jail_note(const struct cfjail *j, const char *fmt, ...);
extern void jail_warnx(const struct cfjail *j, const char *fmt, ...);
extern int next_command(struct cfjail *j);
extern int finish_command(struct cfjail *j);
extern struct cfjail *next_proc(int nonblock);
extern void load_config(void);
extern struct cfjail *add_jail(void);
extern void add_param(struct cfjail *j, const struct cfparam *p,
enum intparam ipnum, const char *value);
extern int bool_param(const struct cfparam *p);
extern int int_param(const struct cfparam *p, int *ip);
extern const char *string_param(const struct cfparam *p);
extern int check_intparams(struct cfjail *j);
extern int import_params(struct cfjail *j);
extern int equalopts(const char *opt1, const char *opt2);
extern int wild_jail_name(const char *wname);
extern int wild_jail_match(const char *jname, const char *wname);
extern void dep_setup(int docf);
extern int dep_check(struct cfjail *j);
extern void dep_done(struct cfjail *j, unsigned flags);
extern void dep_reset(struct cfjail *j);
extern struct cfjail *next_jail(void);
extern int start_state(const char *target, int docf, unsigned state,
int running);
extern void requeue(struct cfjail *j, struct cfjails *queue);
extern void yyerror(const char *);
extern int yylex(void);
extern int yyparse(void);
extern struct cfjails cfjails;
extern struct cfjails ready;
extern struct cfjails depend;
extern const char *cfname;
extern int note_remove;
extern int paralimit;
extern int verbose;

216
usr.sbin/jail/jailparse.y Normal file
View File

@ -0,0 +1,216 @@
%{
/*-
* Copyright (c) 2011 James Gritton
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
* OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
* HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
* LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
* OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
* SUCH DAMAGE.
*/
#include <sys/cdefs.h>
__FBSDID("$FreeBSD$");
#include <stdlib.h>
#include <string.h>
#include "jailp.h"
#ifdef DEBUG
#define YYDEBUG 1
#endif
%}
%union {
struct cfjail *j;
struct cfparams *pp;
struct cfparam *p;
struct cfstrings *ss;
struct cfstring *s;
char *cs;
}
%token PLEQ
%token <cs> STR STR1 VAR VAR1
%type <j> jail
%type <pp> param_l
%type <p> param name
%type <ss> value
%type <s> string
%%
/*
* A config file is a series of jails (containing parameters) and jail-less
* parameters which realy belong to a global pseudo-jail.
*/
conf :
;
| conf jail
;
| conf param ';'
{
struct cfjail *j;
j = TAILQ_LAST(&cfjails, cfjails);
if (!j || strcmp(j->name, "*")) {
j = add_jail();
j->name = estrdup("*");
}
TAILQ_INSERT_TAIL(&j->params, $2, tq);
}
| conf ';'
jail : STR '{' param_l '}'
{
$$ = add_jail();
$$->name = $1;
TAILQ_CONCAT(&$$->params, $3, tq);
free($3);
}
;
param_l :
{
$$ = emalloc(sizeof(struct cfparams));
TAILQ_INIT($$);
}
| param_l param ';'
{
$$ = $1;
TAILQ_INSERT_TAIL($$, $2, tq);
}
| param_l ';'
;
/*
* Parameters have a name and an optional list of value strings,
* which may have "+=" or "=" preceeding them.
*/
param : name
{
$$ = $1;
}
| name '=' value
{
$$ = $1;
TAILQ_CONCAT(&$$->val, $3, tq);
free($3);
}
| name PLEQ value
{
$$ = $1;
TAILQ_CONCAT(&$$->val, $3, tq);
$$->flags |= PF_APPEND;
free($3);
}
| name value
{
$$ = $1;
TAILQ_CONCAT(&$$->val, $2, tq);
free($2);
}
| error
{
}
;
/*
* A parameter has a fixed name. A variable definition looks just like a
* parameter except that the name is a variable.
*/
name : STR
{
$$ = emalloc(sizeof(struct cfparam));
$$->name = $1;
TAILQ_INIT(&$$->val);
$$->flags = 0;
}
| VAR
{
$$ = emalloc(sizeof(struct cfparam));
$$->name = $1;
TAILQ_INIT(&$$->val);
$$->flags = PF_VAR;
}
;
value : string
{
$$ = emalloc(sizeof(struct cfstrings));
TAILQ_INIT($$);
TAILQ_INSERT_TAIL($$, $1, tq);
}
| value ',' string
{
$$ = $1;
TAILQ_INSERT_TAIL($$, $3, tq);
}
;
/*
* Strings may be passed in pieces, because of quoting and/or variable
* interpolation. Reassemble them into a single string.
*/
string : STR
{
$$ = emalloc(sizeof(struct cfstring));
$$->s = $1;
$$->len = strlen($1);
STAILQ_INIT(&$$->vars);
}
| VAR
{
struct cfvar *v;
$$ = emalloc(sizeof(struct cfstring));
$$->s = estrdup("");
$$->len = 0;
STAILQ_INIT(&$$->vars);
v = emalloc(sizeof(struct cfvar));
v->name = $1;
v->pos = 0;
STAILQ_INSERT_TAIL(&$$->vars, v, tq);
}
| string STR1
{
size_t len1;
$$ = $1;
len1 = strlen($2);
$$->s = erealloc($$->s, $$->len + len1 + 1);
strcpy($$->s + $$->len, $2);
free($2);
$$->len += len1;
}
| string VAR1
{
struct cfvar *v;
$$ = $1;
v = emalloc(sizeof(struct cfvar));
v->name = $2;
v->pos = $$->len;
STAILQ_INSERT_TAIL(&$$->vars, v, tq);
}
;
%%

469
usr.sbin/jail/state.c Normal file
View File

@ -0,0 +1,469 @@
/*-
* Copyright (c) 2011 James Gritton
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
* OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
* HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
* LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
* OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
* SUCH DAMAGE.
*/
#include <sys/cdefs.h>
__FBSDID("$FreeBSD$");
#include <sys/uio.h>
#include <err.h>
#include <stdlib.h>
#include <string.h>
#include "jailp.h"
struct cfjails ready = TAILQ_HEAD_INITIALIZER(ready);
struct cfjails depend = TAILQ_HEAD_INITIALIZER(depend);
static void dep_add(struct cfjail *from, struct cfjail *to, unsigned flags);
static int cmp_jailptr(const void *a, const void *b);
static int cmp_jailptr_name(const void *a, const void *b);
static struct cfjail *find_jail(const char *name);
static int running_jid(const char *name, int flags);
static struct cfjail **jails_byname;
static size_t njails;
/*
* Set up jail dependency lists.
*/
void
dep_setup(int docf)
{
struct cfjail *j, *dj;
struct cfparam *p;
struct cfstring *s;
struct cfdepend *d;
const char *cs;
char *pname;
size_t plen;
int error, deps, ldeps;
if (!docf) {
/*
* With no config file, let "depend" for a single jail
* look at currently running jails.
*/
if ((j = TAILQ_FIRST(&cfjails)) &&
(p = j->intparams[IP_DEPEND])) {
TAILQ_FOREACH(s, &p->val, tq) {
if (running_jid(s->s, 0) < 0) {
warnx("depends on nonexistent jail "
"\"%s\"", s->s);
j->flags |= JF_FAILED;
}
}
}
return;
}
njails = 0;
TAILQ_FOREACH(j, &cfjails, tq)
njails++;
jails_byname = emalloc(njails * sizeof(struct cfjail *));
njails = 0;
TAILQ_FOREACH(j, &cfjails, tq)
jails_byname[njails++] = j;
qsort(jails_byname, njails, sizeof(struct cfjail *), cmp_jailptr);
error = 0;
deps = 0;
ldeps = 0;
plen = 0;
pname = NULL;
TAILQ_FOREACH(j, &cfjails, tq) {
if (j->flags & JF_FAILED)
continue;
if ((p = j->intparams[IP_DEPEND])) {
TAILQ_FOREACH(s, &p->val, tq) {
dj = find_jail(s->s);
if (dj != NULL) {
deps++;
dep_add(j, dj, 0);
} else {
jail_warnx(j,
"depends on undefined jail \"%s\"",
s->s);
j->flags |= JF_FAILED;
}
}
}
/* A jail has an implied dependency on its parent. */
if ((cs = strrchr(j->name, '.')))
{
if (plen < (size_t)(cs - j->name + 1)) {
plen = (cs - j->name) + 1;
pname = erealloc(pname, plen);
}
strlcpy(pname, j->name, plen);
dj = find_jail(pname);
if (dj != NULL) {
ldeps++;
dep_add(j, dj, DF_LIGHT);
}
}
}
/* Look for dependency loops. */
if (deps && (deps > 1 || ldeps)) {
(void)start_state(NULL, 0, 0, 0);
while ((j = TAILQ_FIRST(&ready))) {
requeue(j, &cfjails);
dep_done(j, DF_NOFAIL);
}
while ((j = TAILQ_FIRST(&depend)) != NULL) {
jail_warnx(j, "dependency loop");
j->flags |= JF_FAILED;
do {
requeue(j, &cfjails);
dep_done(j, DF_NOFAIL);
} while ((j = TAILQ_FIRST(&ready)));
}
TAILQ_FOREACH(j, &cfjails, tq)
STAILQ_FOREACH(d, &j->dep[DEP_FROM], tq[DEP_FROM])
d->flags &= ~DF_SEEN;
}
if (pname != NULL)
free(pname);
}
/*
* Return if a jail has dependencies.
*/
int
dep_check(struct cfjail *j)
{
int reset, depfrom, depto, ndeps, rev;
struct cfjail *dj;
struct cfdepend *d;
static int bits[] = { 0, 1, 1, 2, 1, 2, 2, 3 };
if (j->ndeps == 0)
return 0;
ndeps = 0;
if ((rev = JF_DO_STOP(j->flags))) {
depfrom = DEP_TO;
depto = DEP_FROM;
} else {
depfrom = DEP_FROM;
depto = DEP_TO;
}
STAILQ_FOREACH(d, &j->dep[depfrom], tq[depfrom]) {
if (d->flags & DF_SEEN)
continue;
dj = d->j[depto];
if (dj->flags & JF_FAILED) {
if (!(j->flags & (JF_DEPEND | JF_FAILED)) &&
verbose >= 0)
jail_warnx(j, "skipped");
j->flags |= JF_FAILED;
continue;
}
/*
* The dependee's state may be set (or changed) as a result of
* being in a dependency it wasn't in earlier.
*/
reset = 0;
if (bits[dj->flags & JF_OP_MASK] <= 1) {
if (!(dj->flags & JF_OP_MASK)) {
reset = 1;
dj->flags |= JF_DEPEND;
requeue(dj, &ready);
}
/* Set or change the dependee's state. */
switch (j->flags & JF_OP_MASK) {
case JF_START:
dj->flags |= JF_START;
break;
case JF_SET:
if (!(dj->flags & JF_OP_MASK))
dj->flags |= JF_SET;
else if (dj->flags & JF_STOP)
dj->flags |= JF_START;
break;
case JF_STOP:
case JF_RESTART:
if (!(dj->flags & JF_STOP))
reset = 1;
dj->flags |= JF_STOP;
if (dj->flags & JF_SET)
dj->flags ^= (JF_START | JF_SET);
break;
}
}
if (reset)
dep_reset(dj);
if (!((d->flags & DF_LIGHT) &&
(rev ? dj->jid < 0 : dj->jid > 0)))
ndeps++;
}
if (ndeps == 0)
return 0;
requeue(j, &depend);
return 1;
}
/*
* Resolve any dependencies from a finished jail.
*/
void
dep_done(struct cfjail *j, unsigned flags)
{
struct cfjail *dj;
struct cfdepend *d;
int depfrom, depto;
if (JF_DO_STOP(j->flags)) {
depfrom = DEP_TO;
depto = DEP_FROM;
} else {
depfrom = DEP_FROM;
depto = DEP_TO;
}
STAILQ_FOREACH(d, &j->dep[depto], tq[depto]) {
if ((d->flags & DF_SEEN) | (flags & ~d->flags & DF_LIGHT))
continue;
d->flags |= DF_SEEN;
dj = d->j[depfrom];
if (!(flags & DF_NOFAIL) && (j->flags & JF_FAILED) &&
(j->flags & (JF_OP_MASK | JF_DEPEND)) !=
(JF_SET | JF_DEPEND)) {
if (!(dj->flags & (JF_DEPEND | JF_FAILED)) &&
verbose >= 0)
jail_warnx(dj, "skipped");
dj->flags |= JF_FAILED;
}
if (!--dj->ndeps && dj->queue == &depend)
requeue(dj, &ready);
}
}
/*
* Count a jail's dependencies and mark them as unseen.
*/
void
dep_reset(struct cfjail *j)
{
int depfrom;
struct cfdepend *d;
depfrom = JF_DO_STOP(j->flags) ? DEP_TO : DEP_FROM;
j->ndeps = 0;
STAILQ_FOREACH(d, &j->dep[depfrom], tq[depfrom])
j->ndeps++;
}
/*
* Find the next jail ready to do something.
*/
struct cfjail *
next_jail(void)
{
struct cfjail *j;
if (!(j = next_proc(!TAILQ_EMPTY(&ready))) &&
(j = TAILQ_FIRST(&ready)) && JF_DO_STOP(j->flags) &&
(j = TAILQ_LAST(&ready, cfjails)) && !JF_DO_STOP(j->flags)) {
TAILQ_FOREACH_REVERSE(j, &ready, cfjails, tq)
if (JF_DO_STOP(j->flags))
break;
}
if (j != NULL)
requeue(j, &cfjails);
return j;
}
/*
* Set jails to the proper start state.
*/
int
start_state(const char *target, int docf, unsigned state, int running)
{
struct iovec jiov[6];
struct cfjail *j, *tj;
int jid;
char namebuf[MAXHOSTNAMELEN];
if (!target || (!docf && state != JF_STOP) ||
(!running && !strcmp(target, "*"))) {
/*
* For a global wildcard (including no target specified),
* set the state on all jails and start with those that
* have no dependencies.
*/
TAILQ_FOREACH_SAFE(j, &cfjails, tq, tj) {
j->flags = (j->flags & JF_FAILED) | state |
(docf ? JF_WILD : 0);
dep_reset(j);
requeue(j, j->ndeps ? &depend : &ready);
}
} else if (wild_jail_name(target)) {
/*
* For targets specified singly, or with a non-global wildcard,
* set their state and call them ready (even if there are
* dependencies). Leave everything else unqueued for now.
*/
if (running) {
/*
* -R matches its wildcards against currently running
* jails, not against the config file.
*/
*(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);
}