diff options
Diffstat (limited to 'usr.sbin/jail/jail.c')
-rw-r--r-- | usr.sbin/jail/jail.c | 1012 |
1 files changed, 1012 insertions, 0 deletions
diff --git a/usr.sbin/jail/jail.c b/usr.sbin/jail/jail.c new file mode 100644 index 0000000..6970ae1 --- /dev/null +++ b/usr.sbin/jail/jail.c @@ -0,0 +1,1012 @@ +/*- + * Copyright (c) 1999 Poul-Henning Kamp. + * Copyright (c) 2009-2012 James Gritton + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +#include <sys/cdefs.h> +__FBSDID("$FreeBSD$"); + +#include <sys/types.h> +#include <sys/stat.h> +#include <sys/socket.h> +#include <sys/sysctl.h> + +#include <arpa/inet.h> +#include <netinet/in.h> + +#include <err.h> +#include <errno.h> +#include <stdarg.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <unistd.h> + +#include "jailp.h" + +#define JP_RDTUN(jp) (((jp)->jp_ctltype & CTLFLAG_RDTUN) == CTLFLAG_RDTUN) + +struct permspec { + const char *name; + enum intparam ipnum; + int rev; +}; + +const char *cfname; +int iflag; +int note_remove; +int verbose; + +static void clear_persist(struct cfjail *j); +static int update_jail(struct cfjail *j); +static int rdtun_params(struct cfjail *j, int dofail); +static void running_jid(struct cfjail *j, int dflag); +static void jail_quoted_warnx(const struct cfjail *j, const char *name_msg, + const char *noname_msg); +static int jailparam_set_note(const struct cfjail *j, struct jailparam *jp, + unsigned njp, int flags); +static void print_jail(FILE *fp, struct cfjail *j, int oldcl); +static void print_param(FILE *fp, const struct cfparam *p, int sep, int doname); +static void quoted_print(FILE *fp, char *str); +static void usage(void); + +static struct permspec perm_sysctl[] = { + { "security.jail.set_hostname_allowed", KP_ALLOW_SET_HOSTNAME, 0 }, + { "security.jail.sysvipc_allowed", KP_ALLOW_SYSVIPC, 0 }, + { "security.jail.allow_raw_sockets", KP_ALLOW_RAW_SOCKETS, 0 }, + { "security.jail.chflags_allowed", KP_ALLOW_CHFLAGS, 0 }, + { "security.jail.mount_allowed", KP_ALLOW_MOUNT, 0 }, + { "security.jail.socket_unixiproute_only", KP_ALLOW_SOCKET_AF, 1 }, +}; + +static const enum intparam startcommands[] = { + IP__NULL, +#ifdef INET + IP__IP4_IFADDR, +#endif +#ifdef INET6 + IP__IP6_IFADDR, +#endif + IP_MOUNT, + IP__MOUNT_FROM_FSTAB, + IP_MOUNT_DEVFS, + IP_EXEC_PRESTART, + IP__OP, + IP_VNET_INTERFACE, + IP_EXEC_START, + IP_COMMAND, + IP_EXEC_POSTSTART, + IP__NULL +}; + +static const enum intparam stopcommands[] = { + IP__NULL, + IP_EXEC_PRESTOP, + IP_EXEC_STOP, + IP_STOP_TIMEOUT, + IP__OP, + IP_EXEC_POSTSTOP, + IP_MOUNT_DEVFS, + IP__MOUNT_FROM_FSTAB, + IP_MOUNT, +#ifdef INET6 + IP__IP6_IFADDR, +#endif +#ifdef INET + IP__IP4_IFADDR, +#endif + IP__NULL +}; + +int +main(int argc, char **argv) +{ + struct stat st; + FILE *jfp; + struct cfjail *j; + char *JidFile; + size_t sysvallen; + unsigned op, pi; + int ch, docf, error, i, oldcl, sysval; + int dflag, Rflag; + char enforce_statfs[4]; +#if defined(INET) || defined(INET6) + char *cs, *ncs; +#endif +#if defined(INET) && defined(INET6) + struct in6_addr addr6; +#endif + + op = 0; + dflag = Rflag = 0; + docf = 1; + cfname = CONF_FILE; + JidFile = NULL; + + while ((ch = getopt(argc, argv, "cdf:hiJ:lmn:p:qrRs:u:U:v")) != -1) { + switch (ch) { + case 'c': + op |= JF_START; + break; + case 'd': + dflag = 1; + break; + case 'f': + cfname = optarg; + break; + case 'h': +#if defined(INET) || defined(INET6) + add_param(NULL, NULL, IP_IP_HOSTNAME, NULL); +#endif + docf = 0; + break; + case 'i': + iflag = 1; + verbose = -1; + break; + case 'J': + JidFile = optarg; + break; + case 'l': + add_param(NULL, NULL, IP_EXEC_CLEAN, NULL); + docf = 0; + break; + case 'm': + op |= JF_SET; + break; + case 'n': + add_param(NULL, NULL, KP_NAME, optarg); + docf = 0; + break; + case 'p': + paralimit = strtol(optarg, NULL, 10); + if (paralimit == 0) + paralimit = -1; + break; + case 'q': + verbose = -1; + break; + case 'r': + op |= JF_STOP; + break; + case 'R': + op |= JF_STOP; + Rflag = 1; + break; + case 's': + add_param(NULL, NULL, KP_SECURELEVEL, optarg); + docf = 0; + break; + case 'u': + add_param(NULL, NULL, IP_EXEC_JAIL_USER, optarg); + add_param(NULL, NULL, IP_EXEC_SYSTEM_JAIL_USER, NULL); + docf = 0; + break; + case 'U': + add_param(NULL, NULL, IP_EXEC_JAIL_USER, optarg); + add_param(NULL, NULL, IP_EXEC_SYSTEM_JAIL_USER, + "false"); + docf = 0; + break; + case 'v': + verbose = 1; + break; + default: + usage(); + } + } + argc -= optind; + argv += optind; + + /* Find out which of the four command line styles this is. */ + oldcl = 0; + if (!op) { + /* Old-style command line with four fixed parameters */ + if (argc < 4 || argv[0][0] != '/') + usage(); + op = JF_START; + docf = 0; + oldcl = 1; + add_param(NULL, NULL, KP_PATH, argv[0]); + add_param(NULL, NULL, KP_HOST_HOSTNAME, argv[1]); +#if defined(INET) || defined(INET6) + if (argv[2][0] != '\0') { + for (cs = argv[2];; cs = ncs + 1) { + ncs = strchr(cs, ','); + if (ncs) + *ncs = '\0'; + add_param(NULL, NULL, +#if defined(INET) && defined(INET6) + inet_pton(AF_INET6, cs, &addr6) == 1 + ? KP_IP6_ADDR : KP_IP4_ADDR, +#elif defined(INET) + KP_IP4_ADDR, +#elif defined(INET6) + KP_IP6_ADDR, +#endif + cs); + if (!ncs) + break; + } + } +#endif + for (i = 3; i < argc; i++) + add_param(NULL, NULL, IP_COMMAND, argv[i]); + /* Emulate the defaults from security.jail.* sysctls. */ + sysvallen = sizeof(sysval); + if (sysctlbyname("security.jail.jailed", &sysval, &sysvallen, + NULL, 0) == 0 && sysval == 0) { + for (pi = 0; pi < sizeof(perm_sysctl) / + sizeof(perm_sysctl[0]); pi++) { + sysvallen = sizeof(sysval); + if (sysctlbyname(perm_sysctl[pi].name, + &sysval, &sysvallen, NULL, 0) == 0) + add_param(NULL, NULL, + perm_sysctl[pi].ipnum, + (sysval ? 1 : 0) ^ + perm_sysctl[pi].rev + ? NULL : "false"); + } + sysvallen = sizeof(sysval); + if (sysctlbyname("security.jail.enforce_statfs", + &sysval, &sysvallen, NULL, 0) == 0) { + snprintf(enforce_statfs, + sizeof(enforce_statfs), "%d", sysval); + add_param(NULL, NULL, KP_ENFORCE_STATFS, + enforce_statfs); + } + } + } else if (op == JF_STOP) { + /* Jail remove, perhaps using the config file */ + if (!docf || argc == 0) + usage(); + if (!Rflag) + for (i = 0; i < argc; i++) + if (strchr(argv[i], '=')) + usage(); + if ((docf = !Rflag && + (!strcmp(cfname, "-") || stat(cfname, &st) == 0))) + load_config(); + note_remove = docf || argc > 1 || wild_jail_name(argv[0]); + } else if (argc > 1 || (argc == 1 && strchr(argv[0], '='))) { + /* Single jail specified on the command line */ + if (Rflag) + usage(); + docf = 0; + for (i = 0; i < argc; i++) { + if (!strncmp(argv[i], "command", 7) && + (argv[i][7] == '\0' || argv[i][7] == '=')) { + if (argv[i][7] == '=') + add_param(NULL, NULL, IP_COMMAND, + argv[i] + 8); + for (i++; i < argc; i++) + add_param(NULL, NULL, IP_COMMAND, + argv[i]); + } +#ifdef INET + else if (!strncmp(argv[i], "ip4.addr=", 9)) { + for (cs = argv[i] + 9;; cs = ncs + 1) { + ncs = strchr(cs, ','); + if (ncs) + *ncs = '\0'; + add_param(NULL, NULL, KP_IP4_ADDR, cs); + if (!ncs) + break; + } + } +#endif +#ifdef INET6 + else if (!strncmp(argv[i], "ip6.addr=", 9)) { + for (cs = argv[i] + 9;; cs = ncs + 1) { + ncs = strchr(cs, ','); + if (ncs) + *ncs = '\0'; + add_param(NULL, NULL, KP_IP6_ADDR, cs); + if (!ncs) + break; + } + } +#endif + else + add_param(NULL, NULL, 0, argv[i]); + } + } else { + /* From the config file, perhaps with a specified jail */ + if (Rflag || !docf) + usage(); + load_config(); + } + + /* Find out which jails will be run. */ + dep_setup(docf); + error = 0; + if (op == JF_STOP) { + for (i = 0; i < argc; i++) + if (start_state(argv[i], docf, op, Rflag) < 0) + error = 1; + } else { + if (start_state(argv[0], docf, op, 0) < 0) + exit(1); + } + + jfp = NULL; + if (JidFile != NULL) { + jfp = fopen(JidFile, "w"); + if (jfp == NULL) + err(1, "open %s", JidFile); + setlinebuf(jfp); + } + setlinebuf(stdout); + + /* + * The main loop: Get an available jail and perform the required + * operation on it. When that is done, the jail may be finished, + * or it may go back for the next step. + */ + while ((j = next_jail())) + { + if (j->flags & JF_FAILED) { + error = 1; + if (j->comparam == NULL) { + dep_done(j, 0); + continue; + } + } + if (!(j->flags & JF_PARAMS)) + { + j->flags |= JF_PARAMS; + if (dflag) + add_param(j, NULL, IP_ALLOW_DYING, NULL); + if (check_intparams(j) < 0) + continue; + if ((j->flags & (JF_START | JF_SET)) && + import_params(j) < 0) + continue; + } + if (!j->jid) + running_jid(j, + (j->flags & (JF_SET | JF_DEPEND)) == JF_SET + ? dflag || bool_param(j->intparams[IP_ALLOW_DYING]) + : 0); + if (finish_command(j)) + continue; + + switch (j->flags & JF_OP_MASK) { + /* + * These operations just turn into a different op + * depending on the jail's current status. + */ + case JF_START_SET: + j->flags = j->jid < 0 ? JF_START : JF_SET; + break; + case JF_SET_RESTART: + if (j->jid < 0) { + jail_quoted_warnx(j, "not found", + "no jail specified"); + failed(j); + continue; + } + j->flags = rdtun_params(j, 0) ? JF_RESTART : JF_SET; + if (j->flags == JF_RESTART) + dep_reset(j); + break; + case JF_START_SET_RESTART: + j->flags = j->jid < 0 ? JF_START + : rdtun_params(j, 0) ? JF_RESTART : JF_SET; + if (j->flags == JF_RESTART) + dep_reset(j); + } + + switch (j->flags & JF_OP_MASK) { + case JF_START: + if (j->comparam == NULL) { + if (j->jid > 0 && + !(j->flags & (JF_DEPEND | JF_WILD))) { + jail_quoted_warnx(j, "already exists", + NULL); + failed(j); + continue; + } + if (dep_check(j)) + continue; + if (j->jid > 0) + goto jail_create_done; + j->comparam = startcommands; + j->comstring = NULL; + } + if (next_command(j)) + continue; + jail_create_done: + clear_persist(j); + if (jfp != NULL) + print_jail(jfp, j, oldcl); + dep_done(j, 0); + break; + + case JF_SET: + if (j->jid < 0 && !(j->flags & JF_DEPEND)) { + jail_quoted_warnx(j, "not found", + "no jail specified"); + failed(j); + continue; + } + if (dep_check(j)) + continue; + if (!(j->flags & JF_DEPEND)) { + if (rdtun_params(j, 1) < 0 || + update_jail(j) < 0) + continue; + if (verbose >= 0 && (j->name || verbose > 0)) + jail_note(j, "updated\n"); + } + dep_done(j, 0); + break; + + case JF_STOP: + case JF_RESTART: + if (j->comparam == NULL) { + if (dep_check(j)) + continue; + if (j->jid < 0) { + if (!(j->flags & (JF_DEPEND | JF_WILD)) + && verbose >= 0) + jail_quoted_warnx(j, + "not found", NULL); + goto jail_remove_done; + } + j->comparam = stopcommands; + j->comstring = NULL; + } else if ((j->flags & JF_FAILED) && j->jid > 0) + goto jail_remove_done; + if (next_command(j)) + continue; + jail_remove_done: + dep_done(j, 0); + if ((j->flags & (JF_START | JF_FAILED)) == JF_START) { + j->comparam = NULL; + j->flags &= ~JF_STOP; + dep_reset(j); + requeue(j, j->ndeps ? &depend : &ready); + } + break; + } + } + + if (jfp != NULL) + fclose(jfp); + exit(error); +} + +/* + * Mark a jail's failure for future handling. + */ +void +failed(struct cfjail *j) +{ + j->flags |= JF_FAILED; + TAILQ_REMOVE(j->queue, j, tq); + TAILQ_INSERT_HEAD(&ready, j, tq); + j->queue = &ready; +} + +/* + * Exit slightly more gracefully when out of memory. + */ +void * +emalloc(size_t size) +{ + void *p; + + p = malloc(size); + if (!p) + err(1, "malloc"); + return p; +} + +void * +erealloc(void *ptr, size_t size) +{ + void *p; + + p = realloc(ptr, size); + if (!p) + err(1, "malloc"); + return p; +} + +char * +estrdup(const char *str) +{ + char *ns; + + ns = strdup(str); + if (!ns) + err(1, "malloc"); + return ns; +} + +/* + * Print a message including an optional jail name. + */ +void +jail_note(const struct cfjail *j, const char *fmt, ...) +{ + va_list ap, tap; + char *cs; + size_t len; + + va_start(ap, fmt); + va_copy(tap, ap); + len = vsnprintf(NULL, 0, fmt, tap); + va_end(tap); + cs = alloca(len + 1); + (void)vsnprintf(cs, len + 1, fmt, ap); + va_end(ap); + if (j->name) + printf("%s: %s", j->name, cs); + else + printf("%s", cs); +} + +/* + * Print a warning message including an optional jail name. + */ +void +jail_warnx(const struct cfjail *j, const char *fmt, ...) +{ + va_list ap, tap; + char *cs; + size_t len; + + va_start(ap, fmt); + va_copy(tap, ap); + len = vsnprintf(NULL, 0, fmt, tap); + va_end(tap); + cs = alloca(len + 1); + (void)vsnprintf(cs, len + 1, fmt, ap); + va_end(ap); + if (j->name) + warnx("%s: %s", j->name, cs); + else + warnx("%s", cs); +} + +/* + * Create a new jail. + */ +int +create_jail(struct cfjail *j) +{ + struct iovec jiov[4]; + struct stat st; + struct jailparam *jp, *setparams, *setparams2, *sjp; + const char *path; + int dopersist, ns, jid, dying, didfail; + + /* + * Check the jail's path, with a better error message than jail_set + * gives. + */ + if ((path = string_param(j->intparams[KP_PATH]))) { + if (j->name != NULL && path[0] != '/') { + jail_warnx(j, "path %s: not an absolute pathname", + path); + return -1; + } + if (stat(path, &st) < 0) { + jail_warnx(j, "path %s: %s", path, strerror(errno)); + return -1; + } + if (!S_ISDIR(st.st_mode)) { + jail_warnx(j, "path %s: %s", path, strerror(ENOTDIR)); + return -1; + } + } + + /* + * Copy all the parameters, except that "persist" is always set when + * there are commands to run later. + */ + dopersist = !bool_param(j->intparams[KP_PERSIST]) && + (j->intparams[IP_EXEC_START] || j->intparams[IP_COMMAND] || + j->intparams[IP_EXEC_POSTSTART]); + sjp = setparams = + alloca((j->njp + dopersist) * sizeof(struct jailparam)); + if (dopersist && jailparam_init(sjp++, "persist") < 0) { + jail_warnx(j, "%s", jail_errmsg); + return -1; + } + for (jp = j->jp; jp < j->jp + j->njp; jp++) + if (!dopersist || !equalopts(jp->jp_name, "persist")) + *sjp++ = *jp; + ns = sjp - setparams; + + didfail = 0; + j->jid = jailparam_set_note(j, setparams, ns, JAIL_CREATE); + if (j->jid < 0 && errno == EEXIST && + bool_param(j->intparams[IP_ALLOW_DYING]) && + int_param(j->intparams[KP_JID], &jid) && jid != 0) { + /* + * The jail already exists, but may be dying. + * Make sure it is, in which case an update is appropriate. + */ + *(const void **)&jiov[0].iov_base = "jid"; + jiov[0].iov_len = sizeof("jid"); + jiov[1].iov_base = &jid; + jiov[1].iov_len = sizeof(jid); + *(const void **)&jiov[2].iov_base = "dying"; + jiov[2].iov_len = sizeof("dying"); + jiov[3].iov_base = &dying; + jiov[3].iov_len = sizeof(dying); + if (jail_get(jiov, 4, JAIL_DYING) < 0) { + /* + * It could be that the jail just barely finished + * dying, or it could be that the jid never existed + * but the name does. In either case, another try + * at creating the jail should do the right thing. + */ + if (errno == ENOENT) + j->jid = jailparam_set_note(j, setparams, ns, + JAIL_CREATE); + } else if (dying) { + j->jid = jid; + if (rdtun_params(j, 1) < 0) { + j->jid = -1; + didfail = 1; + } else { + sjp = setparams2 = alloca((j->njp + dopersist) * + sizeof(struct jailparam)); + for (jp = setparams; jp < setparams + ns; jp++) + if (!JP_RDTUN(jp) || + !strcmp(jp->jp_name, "jid")) + *sjp++ = *jp; + j->jid = jailparam_set_note(j, setparams2, + sjp - setparams2, JAIL_UPDATE | JAIL_DYING); + /* + * Again, perhaps the jail just finished dying. + */ + if (j->jid < 0 && errno == ENOENT) + j->jid = jailparam_set_note(j, + setparams, ns, JAIL_CREATE); + } + } + } + if (j->jid < 0 && !didfail) { + jail_warnx(j, "%s", jail_errmsg); + failed(j); + } + if (dopersist) { + jailparam_free(setparams, 1); + if (j->jid > 0) + j->flags |= JF_PERSIST; + } + return j->jid; +} + +/* + * Remove a temporarily set "persist" parameter. + */ +static void +clear_persist(struct cfjail *j) +{ + struct iovec jiov[4]; + int jid; + + if (!(j->flags & JF_PERSIST)) + return; + j->flags &= ~JF_PERSIST; + *(const void **)&jiov[0].iov_base = "jid"; + jiov[0].iov_len = sizeof("jid"); + jiov[1].iov_base = &j->jid; + jiov[1].iov_len = sizeof(j->jid); + *(const void **)&jiov[2].iov_base = "nopersist"; + jiov[2].iov_len = sizeof("nopersist"); + jiov[3].iov_base = NULL; + jiov[3].iov_len = 0; + jid = jail_set(jiov, 4, JAIL_UPDATE); + if (verbose > 0) + jail_note(j, "jail_set(JAIL_UPDATE) jid=%d nopersist%s%s\n", + j->jid, jid < 0 ? ": " : "", + jid < 0 ? strerror(errno) : ""); +} + +/* + * Set a jail's parameters. + */ +static int +update_jail(struct cfjail *j) +{ + struct jailparam *jp, *setparams, *sjp; + int ns, jid; + + ns = 0; + for (jp = j->jp; jp < j->jp + j->njp; jp++) + if (!JP_RDTUN(jp)) + ns++; + if (ns == 0) + return 0; + sjp = setparams = alloca(++ns * sizeof(struct jailparam)); + if (jailparam_init(sjp, "jid") < 0 || + jailparam_import_raw(sjp, &j->jid, sizeof j->jid) < 0) { + jail_warnx(j, "%s", jail_errmsg); + failed(j); + return -1; + } + for (jp = j->jp; jp < j->jp + j->njp; jp++) + if (!JP_RDTUN(jp)) + *++sjp = *jp; + + jid = jailparam_set_note(j, setparams, ns, + bool_param(j->intparams[IP_ALLOW_DYING]) + ? JAIL_UPDATE | JAIL_DYING : JAIL_UPDATE); + if (jid < 0) { + jail_warnx(j, "%s", jail_errmsg); + failed(j); + } + jailparam_free(setparams, 1); + return jid; +} + +/* + * Return if a jail set would change any create-only parameters. + */ +static int +rdtun_params(struct cfjail *j, int dofail) +{ + struct jailparam *jp, *rtparams, *rtjp; + int nrt, rval; + + if (j->flags & JF_RDTUN) + return 0; + j->flags |= JF_RDTUN; + nrt = 0; + for (jp = j->jp; jp < j->jp + j->njp; jp++) + if (JP_RDTUN(jp) && strcmp(jp->jp_name, "jid")) + nrt++; + if (nrt == 0) + return 0; + rtjp = rtparams = alloca(++nrt * sizeof(struct jailparam)); + if (jailparam_init(rtjp, "jid") < 0 || + jailparam_import_raw(rtjp, &j->jid, sizeof j->jid) < 0) { + jail_warnx(j, "%s", jail_errmsg); + exit(1); + } + for (jp = j->jp; jp < j->jp + j->njp; jp++) + if (JP_RDTUN(jp) && strcmp(jp->jp_name, "jid")) + *++rtjp = *jp; + rval = 0; + if (jailparam_get(rtparams, nrt, + bool_param(j->intparams[IP_ALLOW_DYING]) ? JAIL_DYING : 0) > 0) { + rtjp = rtparams + 1; + for (jp = j->jp, rtjp = rtparams + 1; rtjp < rtparams + nrt; + jp++) { + if (JP_RDTUN(jp) && strcmp(jp->jp_name, "jid")) { + if (!((jp->jp_flags & (JP_BOOL | JP_NOBOOL)) && + jp->jp_valuelen == 0 && + *(int *)jp->jp_value) && + !(rtjp->jp_valuelen == jp->jp_valuelen && + !memcmp(rtjp->jp_value, jp->jp_value, + jp->jp_valuelen))) { + if (dofail) { + jail_warnx(j, "%s cannot be " + "changed after creation", + jp->jp_name); + failed(j); + rval = -1; + } else + rval = 1; + break; + } + rtjp++; + } + } + } + for (rtjp = rtparams + 1; rtjp < rtparams + nrt; rtjp++) + rtjp->jp_name = NULL; + jailparam_free(rtparams, nrt); + return rval; +} + +/* + * Get the jail's jid if it is running. + */ +static void +running_jid(struct cfjail *j, int dflag) +{ + struct iovec jiov[2]; + const char *pval; + char *ep; + int jid; + + if ((pval = string_param(j->intparams[KP_JID]))) { + if (!(jid = strtol(pval, &ep, 10)) || *ep) { + j->jid = -1; + return; + } + *(const void **)&jiov[0].iov_base = "jid"; + jiov[0].iov_len = sizeof("jid"); + jiov[1].iov_base = &jid; + jiov[1].iov_len = sizeof(jid); + } else if ((pval = string_param(j->intparams[KP_NAME]))) { + *(const void **)&jiov[0].iov_base = "name"; + jiov[0].iov_len = sizeof("name"); + jiov[1].iov_len = strlen(pval) + 1; + jiov[1].iov_base = alloca(jiov[1].iov_len); + strcpy(jiov[1].iov_base, pval); + } else { + j->jid = -1; + return; + } + j->jid = jail_get(jiov, 2, dflag ? JAIL_DYING : 0); +} + +static void +jail_quoted_warnx(const struct cfjail *j, const char *name_msg, + const char *noname_msg) +{ + const char *pval; + + if ((pval = j->name) || (pval = string_param(j->intparams[KP_JID])) || + (pval = string_param(j->intparams[KP_NAME]))) + warnx("\"%s\" %s", pval, name_msg); + else + warnx("%s", noname_msg); +} + +/* + * Set jail parameters and possible print them out. + */ +static int +jailparam_set_note(const struct cfjail *j, struct jailparam *jp, unsigned njp, + int flags) +{ + char *value; + int jid; + unsigned i; + + jid = jailparam_set(jp, njp, flags); + if (verbose > 0) { + jail_note(j, "jail_set(%s%s)", + (flags & (JAIL_CREATE | JAIL_UPDATE)) == JAIL_CREATE + ? "JAIL_CREATE" : "JAIL_UPDATE", + (flags & JAIL_DYING) ? " | JAIL_DYING" : ""); + for (i = 0; i < njp; i++) { + printf(" %s", jp[i].jp_name); + if (jp[i].jp_value == NULL) + continue; + putchar('='); + value = jailparam_export(jp + i); + if (value == NULL) + err(1, "jailparam_export"); + quoted_print(stdout, value); + free(value); + } + if (jid < 0) + printf(": %s", strerror(errno)); + printf("\n"); + } + return jid; +} + +/* + * Print a jail record. + */ +static void +print_jail(FILE *fp, struct cfjail *j, int oldcl) +{ + struct cfparam *p; + + if (oldcl) { + fprintf(fp, "%d\t", j->jid); + print_param(fp, j->intparams[KP_PATH], ',', 0); + putc('\t', fp); + print_param(fp, j->intparams[KP_HOST_HOSTNAME], ',', 0); + putc('\t', fp); +#ifdef INET + print_param(fp, j->intparams[KP_IP4_ADDR], ',', 0); +#ifdef INET6 + if (j->intparams[KP_IP4_ADDR] && + !TAILQ_EMPTY(&j->intparams[KP_IP4_ADDR]->val) && + j->intparams[KP_IP6_ADDR] && + !TAILQ_EMPTY(&j->intparams[KP_IP6_ADDR]->val)) + putc(',', fp); +#endif +#endif +#ifdef INET6 + print_param(fp, j->intparams[KP_IP6_ADDR], ',', 0); +#endif + putc('\t', fp); + print_param(fp, j->intparams[IP_COMMAND], ' ', 0); + } else { + fprintf(fp, "jid=%d", j->jid); + TAILQ_FOREACH(p, &j->params, tq) + if (strcmp(p->name, "jid")) { + putc(' ', fp); + print_param(fp, p, ',', 1); + } + } + putc('\n', fp); +} + +/* + * Print a parameter value, or a name=value pair. + */ +static void +print_param(FILE *fp, const struct cfparam *p, int sep, int doname) +{ + const struct cfstring *s, *ts; + + if (doname) + fputs(p->name, fp); + if (p == NULL || TAILQ_EMPTY(&p->val)) + return; + if (doname) + putc('=', fp); + TAILQ_FOREACH_SAFE(s, &p->val, tq, ts) { + quoted_print(fp, s->s); + if (ts != NULL) + putc(sep, fp); + } +} + +/* + * Print a string with quotes around spaces. + */ +static void +quoted_print(FILE *fp, char *str) +{ + int c, qc; + char *p = str; + + qc = !*p ? '"' + : strchr(p, '\'') ? '"' + : strchr(p, '"') ? '\'' + : strchr(p, ' ') || strchr(p, '\t') ? '"' + : 0; + if (qc) + putc(qc, fp); + while ((c = *p++)) { + if (c == '\\' || c == qc) + putc('\\', fp); + putc(c, fp); + } + if (qc) + putc(qc, fp); +} + +static void +usage(void) +{ + + (void)fprintf(stderr, + "usage: jail [-dhilqv] [-J jid_file] [-u username] [-U username]\n" + " -[cmr] param=value ... [command=command ...]\n" + " jail [-dqv] [-f file] -[cmr] [jail]\n" + " jail [-qv] [-f file] -[rR] ['*' | jail ...]\n" + " jail [-dhilqv] [-J jid_file] [-u username] [-U username]\n" + " [-n jailname] [-s securelevel]\n" + " path hostname [ip[,...]] command ...\n"); + exit(1); +} |