cd38a86c63
This commit fixes bug: command "jail -r" didn't trigger pre/post stop commands (and others) defined in config file if jid is specified insted of name. Also it adds basic tests for usr.sbin/jail to avoid regression. Reviewed by: jamie, kevans, ray MFC after: 5 days Differential Revision: https://reviews.freebsd.org/D21328
495 lines
12 KiB
C
495 lines
12 KiB
C
/*-
|
|
* SPDX-License-Identifier: BSD-2-Clause-FreeBSD
|
|
*
|
|
* 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 struct cfjail *running_jail(const char *name, int flags);
|
|
|
|
static struct cfjail **jails_byname;
|
|
static size_t njails;
|
|
|
|
/*
|
|
* Set up jail dependency lists.
|
|
*/
|
|
void
|
|
dep_setup(int docf)
|
|
{
|
|
struct cfjail *j, *dj;
|
|
struct cfparam *p;
|
|
struct cfstring *s;
|
|
struct cfdepend *d;
|
|
const char *cs;
|
|
char *pname;
|
|
size_t plen;
|
|
int deps, ldeps;
|
|
|
|
if (!docf) {
|
|
/*
|
|
* With no config file, let "depend" for a single jail
|
|
* look at currently running jails.
|
|
*/
|
|
if ((j = TAILQ_FIRST(&cfjails)) &&
|
|
(p = j->intparams[IP_DEPEND])) {
|
|
TAILQ_FOREACH(s, &p->val, tq) {
|
|
if (running_jail(s->s, 0) == NULL) {
|
|
warnx("depends on nonexistent jail "
|
|
"\"%s\"", s->s);
|
|
j->flags |= JF_FAILED;
|
|
}
|
|
}
|
|
}
|
|
return;
|
|
}
|
|
|
|
njails = 0;
|
|
TAILQ_FOREACH(j, &cfjails, tq)
|
|
njails++;
|
|
jails_byname = emalloc(njails * sizeof(struct cfjail *));
|
|
njails = 0;
|
|
TAILQ_FOREACH(j, &cfjails, tq)
|
|
jails_byname[njails++] = j;
|
|
qsort(jails_byname, njails, sizeof(struct cfjail *), cmp_jailptr);
|
|
deps = 0;
|
|
ldeps = 0;
|
|
plen = 0;
|
|
pname = NULL;
|
|
TAILQ_FOREACH(j, &cfjails, tq) {
|
|
if (j->flags & JF_FAILED)
|
|
continue;
|
|
if ((p = j->intparams[IP_DEPEND])) {
|
|
TAILQ_FOREACH(s, &p->val, tq) {
|
|
dj = find_jail(s->s);
|
|
if (dj != NULL) {
|
|
deps++;
|
|
dep_add(j, dj, 0);
|
|
} else {
|
|
jail_warnx(j,
|
|
"depends on undefined jail \"%s\"",
|
|
s->s);
|
|
j->flags |= JF_FAILED;
|
|
}
|
|
}
|
|
}
|
|
/* A jail has an implied dependency on its parent. */
|
|
if ((cs = strrchr(j->name, '.')))
|
|
{
|
|
if (plen < (size_t)(cs - j->name + 1)) {
|
|
plen = (cs - j->name) + 1;
|
|
pname = erealloc(pname, plen);
|
|
}
|
|
strlcpy(pname, j->name, plen);
|
|
dj = find_jail(pname);
|
|
if (dj != NULL) {
|
|
ldeps++;
|
|
dep_add(j, dj, DF_LIGHT);
|
|
}
|
|
}
|
|
}
|
|
|
|
/* Look for dependency loops. */
|
|
if (deps && (deps > 1 || ldeps)) {
|
|
(void)start_state(NULL, 0, 0, 0);
|
|
while ((j = TAILQ_FIRST(&ready))) {
|
|
requeue(j, &cfjails);
|
|
dep_done(j, DF_NOFAIL);
|
|
}
|
|
while ((j = TAILQ_FIRST(&depend)) != NULL) {
|
|
jail_warnx(j, "dependency loop");
|
|
j->flags |= JF_FAILED;
|
|
do {
|
|
requeue(j, &cfjails);
|
|
dep_done(j, DF_NOFAIL);
|
|
} while ((j = TAILQ_FIRST(&ready)));
|
|
}
|
|
TAILQ_FOREACH(j, &cfjails, tq)
|
|
STAILQ_FOREACH(d, &j->dep[DEP_FROM], tq[DEP_FROM])
|
|
d->flags &= ~DF_SEEN;
|
|
}
|
|
if (pname != NULL)
|
|
free(pname);
|
|
}
|
|
|
|
/*
|
|
* Return if a jail has dependencies.
|
|
*/
|
|
int
|
|
dep_check(struct cfjail *j)
|
|
{
|
|
int reset, depfrom, depto, ndeps, rev;
|
|
struct cfjail *dj;
|
|
struct cfdepend *d;
|
|
|
|
static int bits[] = { 0, 1, 1, 2, 1, 2, 2, 3 };
|
|
|
|
if (j->ndeps == 0)
|
|
return 0;
|
|
ndeps = 0;
|
|
if ((rev = JF_DO_STOP(j->flags))) {
|
|
depfrom = DEP_TO;
|
|
depto = DEP_FROM;
|
|
} else {
|
|
depfrom = DEP_FROM;
|
|
depto = DEP_TO;
|
|
}
|
|
STAILQ_FOREACH(d, &j->dep[depfrom], tq[depfrom]) {
|
|
if (d->flags & DF_SEEN)
|
|
continue;
|
|
dj = d->j[depto];
|
|
if (dj->flags & JF_FAILED) {
|
|
if (!(j->flags & (JF_DEPEND | JF_FAILED)) &&
|
|
verbose >= 0)
|
|
jail_warnx(j, "skipped");
|
|
j->flags |= JF_FAILED;
|
|
continue;
|
|
}
|
|
/*
|
|
* The dependee's state may be set (or changed) as a result of
|
|
* being in a dependency it wasn't in earlier.
|
|
*/
|
|
reset = 0;
|
|
if (bits[dj->flags & JF_OP_MASK] <= 1) {
|
|
if (!(dj->flags & JF_OP_MASK)) {
|
|
reset = 1;
|
|
dj->flags |= JF_DEPEND;
|
|
requeue(dj, &ready);
|
|
}
|
|
/* Set or change the dependee's state. */
|
|
switch (j->flags & JF_OP_MASK) {
|
|
case JF_START:
|
|
dj->flags |= JF_START;
|
|
break;
|
|
case JF_SET:
|
|
if (!(dj->flags & JF_OP_MASK))
|
|
dj->flags |= JF_SET;
|
|
else if (dj->flags & JF_STOP)
|
|
dj->flags |= JF_START;
|
|
break;
|
|
case JF_STOP:
|
|
case JF_RESTART:
|
|
if (!(dj->flags & JF_STOP))
|
|
reset = 1;
|
|
dj->flags |= JF_STOP;
|
|
if (dj->flags & JF_SET)
|
|
dj->flags ^= (JF_START | JF_SET);
|
|
break;
|
|
}
|
|
}
|
|
if (reset)
|
|
dep_reset(dj);
|
|
if (!((d->flags & DF_LIGHT) &&
|
|
(rev ? dj->jid < 0 : dj->jid > 0)))
|
|
ndeps++;
|
|
}
|
|
if (ndeps == 0)
|
|
return 0;
|
|
requeue(j, &depend);
|
|
return 1;
|
|
}
|
|
|
|
/*
|
|
* Resolve any dependencies from a finished jail.
|
|
*/
|
|
void
|
|
dep_done(struct cfjail *j, unsigned flags)
|
|
{
|
|
struct cfjail *dj;
|
|
struct cfdepend *d;
|
|
int depfrom, depto;
|
|
|
|
if (JF_DO_STOP(j->flags)) {
|
|
depfrom = DEP_TO;
|
|
depto = DEP_FROM;
|
|
} else {
|
|
depfrom = DEP_FROM;
|
|
depto = DEP_TO;
|
|
}
|
|
STAILQ_FOREACH(d, &j->dep[depto], tq[depto]) {
|
|
if ((d->flags & DF_SEEN) | (flags & ~d->flags & DF_LIGHT))
|
|
continue;
|
|
d->flags |= DF_SEEN;
|
|
dj = d->j[depfrom];
|
|
if (!(flags & DF_NOFAIL) && (j->flags & JF_FAILED) &&
|
|
(j->flags & (JF_OP_MASK | JF_DEPEND)) !=
|
|
(JF_SET | JF_DEPEND)) {
|
|
if (!(dj->flags & (JF_DEPEND | JF_FAILED)) &&
|
|
verbose >= 0)
|
|
jail_warnx(dj, "skipped");
|
|
dj->flags |= JF_FAILED;
|
|
}
|
|
if (!--dj->ndeps && dj->queue == &depend)
|
|
requeue(dj, &ready);
|
|
}
|
|
}
|
|
|
|
/*
|
|
* Count a jail's dependencies and mark them as unseen.
|
|
*/
|
|
void
|
|
dep_reset(struct cfjail *j)
|
|
{
|
|
int depfrom;
|
|
struct cfdepend *d;
|
|
|
|
depfrom = JF_DO_STOP(j->flags) ? DEP_TO : DEP_FROM;
|
|
j->ndeps = 0;
|
|
STAILQ_FOREACH(d, &j->dep[depfrom], tq[depfrom])
|
|
j->ndeps++;
|
|
}
|
|
|
|
/*
|
|
* Find the next jail ready to do something.
|
|
*/
|
|
struct cfjail *
|
|
next_jail(void)
|
|
{
|
|
struct cfjail *j;
|
|
|
|
if (!(j = next_proc(!TAILQ_EMPTY(&ready))) &&
|
|
(j = TAILQ_FIRST(&ready)) && JF_DO_STOP(j->flags) &&
|
|
(j = TAILQ_LAST(&ready, cfjails)) && !JF_DO_STOP(j->flags)) {
|
|
TAILQ_FOREACH_REVERSE(j, &ready, cfjails, tq)
|
|
if (JF_DO_STOP(j->flags))
|
|
break;
|
|
}
|
|
if (j != NULL)
|
|
requeue(j, &cfjails);
|
|
return j;
|
|
}
|
|
|
|
/*
|
|
* Set jails to the proper start state.
|
|
*/
|
|
int
|
|
start_state(const char *target, int docf, unsigned state, int running)
|
|
{
|
|
struct iovec jiov[6];
|
|
struct cfjail *j, *tj;
|
|
int jid;
|
|
char namebuf[MAXHOSTNAMELEN];
|
|
|
|
if (!target || (!docf && state != JF_STOP) ||
|
|
(!running && !strcmp(target, "*"))) {
|
|
/*
|
|
* For a global wildcard (including no target specified),
|
|
* set the state on all jails and start with those that
|
|
* have no dependencies.
|
|
*/
|
|
TAILQ_FOREACH_SAFE(j, &cfjails, tq, tj) {
|
|
j->flags = (j->flags & JF_FAILED) | state |
|
|
(docf ? JF_WILD : 0);
|
|
dep_reset(j);
|
|
requeue(j, j->ndeps ? &depend : &ready);
|
|
}
|
|
} else if (wild_jail_name(target)) {
|
|
/*
|
|
* For targets specified singly, or with a non-global wildcard,
|
|
* set their state and call them ready (even if there are
|
|
* dependencies). Leave everything else unqueued for now.
|
|
*/
|
|
if (running) {
|
|
/*
|
|
* -R matches its wildcards against currently running
|
|
* jails, not against the config file.
|
|
*/
|
|
jiov[0].iov_base = __DECONST(char *, "lastjid");
|
|
jiov[0].iov_len = sizeof("lastjid");
|
|
jiov[1].iov_base = &jid;
|
|
jiov[1].iov_len = sizeof(jid);
|
|
jiov[2].iov_base = __DECONST(char *, "jid");
|
|
jiov[2].iov_len = sizeof("jid");
|
|
jiov[3].iov_base = &jid;
|
|
jiov[3].iov_len = sizeof(jid);
|
|
jiov[4].iov_base = __DECONST(char *, "name");
|
|
jiov[4].iov_len = sizeof("name");
|
|
jiov[5].iov_base = &namebuf;
|
|
jiov[5].iov_len = sizeof(namebuf);
|
|
for (jid = 0; jail_get(jiov, 6, 0) > 0; ) {
|
|
if (wild_jail_match(namebuf, target)) {
|
|
j = add_jail();
|
|
j->name = estrdup(namebuf);
|
|
j->jid = jid;
|
|
j->flags = (j->flags & JF_FAILED) |
|
|
state | JF_WILD;
|
|
dep_reset(j);
|
|
requeue(j, &ready);
|
|
}
|
|
}
|
|
} else {
|
|
TAILQ_FOREACH_SAFE(j, &cfjails, tq, tj) {
|
|
if (wild_jail_match(j->name, target)) {
|
|
j->flags = (j->flags & JF_FAILED) |
|
|
state | JF_WILD;
|
|
dep_reset(j);
|
|
requeue(j, &ready);
|
|
}
|
|
}
|
|
}
|
|
} else {
|
|
j = find_jail(target);
|
|
if (j == NULL && state == JF_STOP) {
|
|
/* Allow -[rR] to specify a currently running jail. */
|
|
j = running_jail(target, JAIL_DYING);
|
|
}
|
|
if (j == NULL) {
|
|
warnx("\"%s\" not found", target);
|
|
return -1;
|
|
}
|
|
j->flags = (j->flags & JF_FAILED) | state;
|
|
dep_reset(j);
|
|
requeue(j, &ready);
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
/*
|
|
* Move a jail to a new list.
|
|
*/
|
|
void
|
|
requeue(struct cfjail *j, struct cfjails *queue)
|
|
{
|
|
if (j->queue != queue) {
|
|
TAILQ_REMOVE(j->queue, j, tq);
|
|
TAILQ_INSERT_TAIL(queue, j, tq);
|
|
j->queue = queue;
|
|
}
|
|
}
|
|
|
|
void
|
|
requeue_head(struct cfjail *j, struct cfjails *queue)
|
|
{
|
|
TAILQ_REMOVE(j->queue, j, tq);
|
|
TAILQ_INSERT_HEAD(queue, j, tq);
|
|
j->queue = queue;
|
|
}
|
|
|
|
/*
|
|
* Add a dependency edge between two jails.
|
|
*/
|
|
static void
|
|
dep_add(struct cfjail *from, struct cfjail *to, unsigned flags)
|
|
{
|
|
struct cfdepend *d;
|
|
|
|
d = emalloc(sizeof(struct cfdepend));
|
|
d->flags = flags;
|
|
d->j[DEP_FROM] = from;
|
|
d->j[DEP_TO] = to;
|
|
STAILQ_INSERT_TAIL(&from->dep[DEP_FROM], d, tq[DEP_FROM]);
|
|
STAILQ_INSERT_TAIL(&to->dep[DEP_TO], d, tq[DEP_TO]);
|
|
}
|
|
|
|
/*
|
|
* Compare jail pointers for qsort/bsearch.
|
|
*/
|
|
static int
|
|
cmp_jailptr(const void *a, const void *b)
|
|
{
|
|
return strcmp((*((struct cfjail * const *)a))->name,
|
|
((*(struct cfjail * const *)b))->name);
|
|
}
|
|
|
|
static int
|
|
cmp_jailptr_name(const void *a, const void *b)
|
|
{
|
|
return strcmp((const char *)a, ((*(struct cfjail * const *)b))->name);
|
|
}
|
|
|
|
/*
|
|
* Find a jail object by name.
|
|
*/
|
|
static struct cfjail *
|
|
find_jail(const char *name)
|
|
{
|
|
struct cfjail **jp;
|
|
|
|
if (jails_byname == NULL)
|
|
return NULL;
|
|
|
|
jp = bsearch(name, jails_byname, njails, sizeof(struct cfjail *),
|
|
cmp_jailptr_name);
|
|
return jp ? *jp : NULL;
|
|
}
|
|
|
|
/*
|
|
* Return jail if it is running, and NULL if it isn't.
|
|
*/
|
|
static struct cfjail *
|
|
running_jail(const char *name, int flags)
|
|
{
|
|
struct iovec jiov[4];
|
|
struct cfjail *jail;
|
|
char *ep;
|
|
char jailname[MAXHOSTNAMELEN];
|
|
int jid, ret, len;
|
|
|
|
if ((jid = strtol(name, &ep, 10)) && !*ep) {
|
|
memset(jailname,0,sizeof(jailname));
|
|
len = sizeof(jailname);
|
|
} else {
|
|
strncpy(jailname, name,sizeof(jailname));
|
|
len = strlen(name) + 1;
|
|
jid = 0;
|
|
}
|
|
|
|
jiov[0].iov_base = __DECONST(char *, "jid");
|
|
jiov[0].iov_len = sizeof("jid");
|
|
jiov[1].iov_base = &jid;
|
|
jiov[1].iov_len = sizeof(jid);
|
|
jiov[2].iov_base = __DECONST(char *, "name");
|
|
jiov[2].iov_len = sizeof("name");
|
|
jiov[3].iov_base = &jailname;
|
|
jiov[3].iov_len = len;
|
|
|
|
if ((ret = jail_get(jiov, 4, flags)) < 0)
|
|
return (NULL);
|
|
|
|
if ((jail = find_jail(jailname)) == NULL) {
|
|
jail = add_jail();
|
|
jail->name = estrdup(jailname);
|
|
jail->jid = ret;
|
|
}
|
|
|
|
return (jail);
|
|
}
|