diff options
Diffstat (limited to 'usr.sbin/jail/jail.c')
-rw-r--r-- | usr.sbin/jail/jail.c | 1238 |
1 files changed, 826 insertions, 412 deletions
diff --git a/usr.sbin/jail/jail.c b/usr.sbin/jail/jail.c index 69c5e53..d6de5ca 100644 --- a/usr.sbin/jail/jail.c +++ b/usr.sbin/jail/jail.c @@ -1,6 +1,6 @@ /*- * Copyright (c) 1999 Poul-Henning Kamp. - * Copyright (c) 2009 James Gritton + * Copyright (c) 2009-2012 James Gritton * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -28,155 +28,189 @@ #include <sys/cdefs.h> __FBSDID("$FreeBSD$"); -#include <sys/param.h> -#include <sys/jail.h> +#include <sys/types.h> +#include <sys/stat.h> #include <sys/socket.h> #include <sys/sysctl.h> #include <arpa/inet.h> #include <netinet/in.h> -#include <ctype.h> #include <err.h> #include <errno.h> -#include <grp.h> -#include <jail.h> -#include <login_cap.h> -#include <netdb.h> -#include <paths.h> -#include <pwd.h> +#include <stdarg.h> #include <stdio.h> #include <stdlib.h> #include <string.h> #include <unistd.h> -static struct jailparam *params; -static char **param_values; -static int nparams; +#include "jailp.h" -#ifdef INET6 -static int ip6_ok; -static char *ip6_addr; -#endif -#ifdef INET -static int ip4_ok; -static char *ip4_addr; -#endif +#define JP_RDTUN(jp) (((jp)->jp_ctltype & CTLFLAG_RDTUN) == CTLFLAG_RDTUN) -#if defined(INET6) || defined(INET) -static void add_ip_addr(char **addrp, char *newaddr); -#endif -#ifdef INET6 -static void add_ip_addr46(char *newaddr); -#endif -static void add_ip_addrinfo(int ai_flags, char *value); +struct permspec { + const char *name; + enum intparam ipnum; + int rev; +}; + +const char *cfname; +int note_remove; +int verbose; + +static void clear_persist(struct cfjail *j); +static int update_jail(struct cfjail *j); +static int rdtun_params(struct cfjail *j, int dofail); +static void running_jid(struct cfjail *j, int dflag); +static 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 set_param(const char *name, char *value); static void usage(void); -static const char *perm_sysctl[][3] = { - { "security.jail.set_hostname_allowed", - "allow.noset_hostname", "allow.set_hostname" }, - { "security.jail.sysvipc_allowed", - "allow.nosysvipc", "allow.sysvipc" }, - { "security.jail.allow_raw_sockets", - "allow.noraw_sockets", "allow.raw_sockets" }, - { "security.jail.chflags_allowed", - "allow.nochflags", "allow.chflags" }, - { "security.jail.mount_allowed", - "allow.nomount", "allow.mount" }, - { "security.jail.socket_unixiproute_only", - "allow.socket_af", "allow.nosocket_af" }, +static struct permspec perm_sysctl[] = { + { "security.jail.set_hostname_allowed", KP_ALLOW_SET_HOSTNAME, 0 }, + { "security.jail.sysvipc_allowed", KP_ALLOW_SYSVIPC, 0 }, + { "security.jail.allow_raw_sockets", KP_ALLOW_RAW_SOCKETS, 0 }, + { "security.jail.chflags_allowed", KP_ALLOW_CHFLAGS, 0 }, + { "security.jail.mount_allowed", KP_ALLOW_MOUNT, 0 }, + { "security.jail.socket_unixiproute_only", KP_ALLOW_SOCKET_AF, 1 }, +}; + +static const enum intparam startcommands[] = { + 0, +#ifdef INET + IP__IP4_IFADDR, +#endif +#ifdef INET6 + IP__IP6_IFADDR, +#endif + IP_MOUNT, + IP__MOUNT_FROM_FSTAB, + IP_MOUNT_DEVFS, + IP_EXEC_PRESTART, + IP__OP, + IP_VNET_INTERFACE, + IP_EXEC_START, + IP_COMMAND, + IP_EXEC_POSTSTART, + 0 }; -extern char **environ; - -#define GET_USER_INFO do { \ - pwd = getpwnam(username); \ - if (pwd == NULL) { \ - if (errno) \ - err(1, "getpwnam: %s", username); \ - else \ - errx(1, "%s: no such user", username); \ - } \ - lcap = login_getpwclass(pwd); \ - if (lcap == NULL) \ - err(1, "getpwclass: %s", username); \ - ngroups = ngroups_max; \ - if (getgrouplist(username, pwd->pw_gid, groups, &ngroups) != 0) \ - err(1, "getgrouplist: %s", username); \ -} while (0) +static const enum intparam stopcommands[] = { + 0, + IP_EXEC_PRESTOP, + IP_EXEC_STOP, + IP_STOP_TIMEOUT, + IP__OP, + IP_EXEC_POSTSTOP, + IP_MOUNT_DEVFS, + IP__MOUNT_FROM_FSTAB, + IP_MOUNT, +#ifdef INET6 + IP__IP6_IFADDR, +#endif +#ifdef INET + IP__IP4_IFADDR, +#endif + 0 +}; int main(int argc, char **argv) { - login_cap_t *lcap = NULL; - struct passwd *pwd = NULL; - gid_t *groups; + struct stat st; + FILE *jfp; + struct cfjail *j; + char *JidFile; size_t sysvallen; - int ch, cmdarg, i, jail_set_flags, jid, ngroups, sysval; - int hflag, iflag, Jflag, lflag, rflag, uflag, Uflag; - long ngroups_max; - unsigned pi; - char *jailname, *securelevel, *username, *JidFile; + unsigned op, pi; + int ch, docf, error, i, oldcl, sysval; + int dflag, iflag, Rflag; char enforce_statfs[4]; - static char *cleanenv; - const char *shell, *p = NULL; - FILE *fp; - - hflag = iflag = Jflag = lflag = rflag = uflag = Uflag = - jail_set_flags = 0; - cmdarg = jid = -1; - jailname = securelevel = username = JidFile = cleanenv = NULL; - fp = NULL; - - ngroups_max = sysconf(_SC_NGROUPS_MAX) + 1; - if ((groups = malloc(sizeof(gid_t) * ngroups_max)) == NULL) - err(1, "malloc"); +#if defined(INET) || defined(INET6) + char *cs, *ncs; +#endif +#if defined(INET) && defined(INET6) + struct in6_addr addr6; +#endif + + op = 0; + dflag = iflag = Rflag = 0; + docf = 1; + cfname = CONF_FILE; + JidFile = NULL; - while ((ch = getopt(argc, argv, "cdhilmn:r:s:u:U:J:")) != -1) { + while ((ch = getopt(argc, argv, "cdf:hiJ:lmn:p:qrRs:U:v")) != -1) { switch (ch) { + case 'c': + op |= JF_START; + break; case 'd': - jail_set_flags |= JAIL_DYING; + dflag = 1; + break; + case 'f': + cfname = optarg; break; case 'h': - hflag = 1; +#if defined(INET) || defined(INET6) + add_param(NULL, NULL, IP_IP_HOSTNAME, NULL); +#endif + docf = 0; break; case 'i': iflag = 1; + verbose = -1; break; case 'J': JidFile = optarg; - Jflag = 1; + break; + case 'l': + add_param(NULL, NULL, IP_EXEC_CLEAN, NULL); + docf = 0; + break; + case 'm': + op |= JF_SET; break; case 'n': - jailname = optarg; + add_param(NULL, NULL, KP_NAME, optarg); + docf = 0; break; - case 's': - securelevel = optarg; + case 'p': + paralimit = strtol(optarg, NULL, 10); + if (paralimit == 0) + paralimit = -1; break; - case 'u': - username = optarg; - uflag = 1; + case 'q': + verbose = -1; break; - case 'U': - username = optarg; - Uflag = 1; + case 'r': + op |= JF_STOP; break; - case 'l': - lflag = 1; + case 'R': + op |= JF_STOP; + Rflag = 1; break; - case 'c': - jail_set_flags |= JAIL_CREATE; + case 's': + add_param(NULL, NULL, KP_SECURELEVEL, optarg); + docf = 0; break; - case 'm': - jail_set_flags |= JAIL_UPDATE; + case 'u': + add_param(NULL, NULL, IP_EXEC_JAIL_USER, optarg); + add_param(NULL, NULL, IP_EXEC_SYSTEM_JAIL_USER, NULL); + docf = 0; break; - case 'r': - jid = jail_getid(optarg); - if (jid < 0) - errx(1, "%s", jail_errmsg); - rflag = 1; + case 'U': + add_param(NULL, NULL, IP_EXEC_JAIL_USER, optarg); + add_param(NULL, NULL, IP_EXEC_SYSTEM_JAIL_USER, + "false"); + docf = 0; + break; + case 'v': + verbose = 1; break; default: usage(); @@ -184,312 +218,747 @@ main(int argc, char **argv) } argc -= optind; argv += optind; - if (rflag) { - if (argc > 0 || iflag || Jflag || lflag || uflag || Uflag) - usage(); - if (jail_remove(jid) < 0) - err(1, "jail_remove"); - exit (0); - } - if (argc == 0) - usage(); - if (uflag && Uflag) - usage(); - if (lflag && username == NULL) - usage(); - if (uflag) - GET_USER_INFO; - -#ifdef INET6 - ip6_ok = feature_present("inet6"); -#endif -#ifdef INET - ip4_ok = feature_present("inet"); -#endif - if (jailname) - set_param("name", jailname); - if (securelevel) - set_param("securelevel", securelevel); - if (jail_set_flags) { - for (i = 0; i < argc; i++) { - if (!strncmp(argv[i], "command=", 8)) { - cmdarg = i; - argv[cmdarg] += 8; - jail_set_flags |= JAIL_ATTACH; - break; - } - if (hflag) { -#ifdef INET - if (!strncmp(argv[i], "ip4.addr=", 9)) { - add_ip_addr(&ip4_addr, argv[i] + 9); - break; - } + /* Find out which of the four command line styles this is. */ + oldcl = 0; + if (!op) { + /* Old-style command line with four fixed parameters */ + if (argc < 4 || argv[0][0] != '/') + usage(); + op = JF_START; + docf = 0; + oldcl = 1; + add_param(NULL, NULL, KP_PATH, argv[0]); + add_param(NULL, NULL, KP_HOST_HOSTNAME, argv[1]); +#if defined(INET) || defined(INET6) + if (argv[2][0] != '\0') { + for (cs = argv[2];; cs = ncs + 1) { + ncs = strchr(cs, ','); + if (ncs) + *ncs = '\0'; + add_param(NULL, NULL, +#if defined(INET) && defined(INET6) + inet_pton(AF_INET6, cs, &addr6) == 1 + ? KP_IP6_ADDR : KP_IP4_ADDR, +#elif defined(INET) + KP_IP4_ADDR, +#elif defined(INET6) + KP_IP6_ADDR, #endif -#ifdef INET6 - if (!strncmp(argv[i], "ip6.addr=", 9)) { - add_ip_addr(&ip6_addr, argv[i] + 9); + cs); + if (!ncs) break; - } -#endif - if (!strncmp(argv[i], "host.hostname=", 14)) - add_ip_addrinfo(0, argv[i] + 14); } - set_param(NULL, argv[i]); } - } else { - if (argc < 4 || argv[0][0] != '/') - errx(1, "%s\n%s", - "no -c or -m, so this must be an old-style command.", - "But it doesn't look like one."); - set_param("path", argv[0]); - set_param("host.hostname", argv[1]); - if (hflag) - add_ip_addrinfo(0, argv[1]); -#if defined(INET6) || defined(INET) - if (argv[2][0] != '\0') -#ifdef INET6 - add_ip_addr46(argv[2]); -#else - add_ip_addr(&ip4_addr, argv[2]); #endif -#endif - cmdarg = 3; - /* Emulate the defaults from security.jail.* sysctls */ + for (i = 3; i < argc; i++) + add_param(NULL, NULL, IP_COMMAND, argv[i]); + /* Emulate the defaults from security.jail.* sysctls. */ sysvallen = sizeof(sysval); if (sysctlbyname("security.jail.jailed", &sysval, &sysvallen, NULL, 0) == 0 && sysval == 0) { for (pi = 0; pi < sizeof(perm_sysctl) / sizeof(perm_sysctl[0]); pi++) { sysvallen = sizeof(sysval); - if (sysctlbyname(perm_sysctl[pi][0], + if (sysctlbyname(perm_sysctl[pi].name, &sysval, &sysvallen, NULL, 0) == 0) - set_param(perm_sysctl[pi] - [sysval ? 2 : 1], NULL); + add_param(NULL, NULL, + perm_sysctl[pi].ipnum, + (sysval ? 1 : 0) ^ + perm_sysctl[pi].rev + ? NULL : "false"); } sysvallen = sizeof(sysval); if (sysctlbyname("security.jail.enforce_statfs", &sysval, &sysvallen, NULL, 0) == 0) { snprintf(enforce_statfs, sizeof(enforce_statfs), "%d", sysval); - set_param("enforce_statfs", enforce_statfs); + add_param(NULL, NULL, KP_ENFORCE_STATFS, + enforce_statfs); + } + } + } else if (op == JF_STOP) { + /* Jail remove, perhaps using the config file */ + if (!docf || argc == 0) + usage(); + if (!Rflag) + for (i = 0; i < argc; i++) + if (strchr(argv[i], '=')) + usage(); + if ((docf = !Rflag && + (!strcmp(cfname, "-") || stat(cfname, &st) == 0))) + load_config(); + note_remove = docf || argc > 1 || wild_jail_name(argv[0]); + } else if (argc > 1 || (argc == 1 && strchr(argv[0], '='))) { + /* Single jail specified on the command line */ + if (Rflag) + usage(); + docf = 0; + for (i = 0; i < argc; i++) { + if (!strncmp(argv[i], "command", 7) && + (argv[i][7] == '\0' || argv[i][7] == '=')) { + if (argv[i][7] == '=') + add_param(NULL, NULL, IP_COMMAND, + argv[i] + 8); + for (i++; i < argc; i++) + add_param(NULL, NULL, IP_COMMAND, + argv[i]); + break; } + add_param(NULL, NULL, 0, argv[i]); } + } else { + /* From the config file, perhaps with a specified jail */ + if (Rflag || !docf) + usage(); + load_config(); } -#ifdef INET - if (ip4_addr != NULL) - set_param("ip4.addr", ip4_addr); -#endif -#ifdef INET6 - if (ip6_addr != NULL) - set_param("ip6.addr", ip6_addr); -#endif - if (Jflag) { - fp = fopen(JidFile, "w"); - if (fp == NULL) - errx(1, "Could not create JidFile: %s", JidFile); + /* 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); } - jid = jailparam_set(params, nparams, - jail_set_flags ? jail_set_flags : JAIL_CREATE | JAIL_ATTACH); - if (jid < 0) - errx(1, "%s", jail_errmsg); - if (iflag) { - printf("%d\n", jid); - fflush(stdout); + + jfp = NULL; + if (JidFile != NULL) { + jfp = fopen(JidFile, "w"); + if (jfp == NULL) + err(1, "open %s", JidFile); + setlinebuf(jfp); } - if (Jflag) { - if (jail_set_flags) { - fprintf(fp, "jid=%d", jid); - for (i = 0; i < nparams; i++) - if (strcmp(params[i].jp_name, "jid")) { - fprintf(fp, " %s", - (char *)params[i].jp_name); - if (param_values[i]) { - putc('=', fp); - quoted_print(fp, - param_values[i]); - } + setlinebuf(stdout); + + /* + * The main loop: Get an available jail and perform the required + * operation on it. When that is done, the jail may be finished, + * or it may go back for the next step. + */ + while ((j = next_jail())) + { + if (j->flags & JF_FAILED) { + 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; } - fprintf(fp, "\n"); - } else { - for (i = 0; i < nparams; i++) - if (!strcmp(params[i].jp_name, "path")) - break; -#if defined(INET6) && defined(INET) - fprintf(fp, "%d\t%s\t%s\t%s%s%s\t%s\n", - jid, i < nparams - ? (char *)params[i].jp_value : argv[0], - argv[1], ip4_addr ? ip4_addr : "", - ip4_addr && ip4_addr[0] && ip6_addr && ip6_addr[0] - ? "," : "", ip6_addr ? ip6_addr : "", argv[3]); -#elif defined(INET6) - fprintf(fp, "%d\t%s\t%s\t%s\t%s\n", - jid, i < nparams - ? (char *)params[i].jp_value : argv[0], - argv[1], ip6_addr ? ip6_addr : "", argv[3]); -#elif defined(INET) - fprintf(fp, "%d\t%s\t%s\t%s\t%s\n", - jid, i < nparams - ? (char *)params[i].jp_value : argv[0], - argv[1], ip4_addr ? ip4_addr : "", argv[3]); -#endif + if (dep_check(j)) + continue; + if (j->jid > 0) + goto jail_create_done; + j->comparam = startcommands; + j->comstring = NULL; + } + if (next_command(j)) + continue; + jail_create_done: + clear_persist(j); + if (iflag) + printf("%d\n", j->jid); + if (jfp != NULL) + print_jail(jfp, j, oldcl); + dep_done(j, 0); + break; + + case JF_SET: + if (j->jid < 0 && !(j->flags & JF_DEPEND)) { + 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; } - (void)fclose(fp); + 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; } - if (cmdarg < 0) - exit(0); - if (username != NULL) { - if (Uflag) - GET_USER_INFO; - if (lflag) { - p = getenv("TERM"); - environ = &cleanenv; + for (jp = j->jp; jp < j->jp + j->njp; jp++) + if (!dopersist || !equalopts(jp->jp_name, "persist")) + *sjp++ = *jp; + ns = sjp - setparams; + + didfail = 0; + j->jid = jailparam_set_note(j, setparams, ns, JAIL_CREATE); + if (j->jid < 0 && errno == EEXIST && + bool_param(j->intparams[IP_ALLOW_DYING]) && + int_param(j->intparams[KP_JID], &jid) && jid != 0) { + /* + * The jail already exists, but may be dying. + * Make sure it is, in which case an update is appropriate. + */ + *(const void **)&jiov[0].iov_base = "jid"; + jiov[0].iov_len = sizeof("jid"); + jiov[1].iov_base = &jid; + jiov[1].iov_len = sizeof(jid); + *(const void **)&jiov[2].iov_base = "dying"; + jiov[2].iov_len = sizeof("dying"); + jiov[3].iov_base = &dying; + jiov[3].iov_len = sizeof(dying); + if (jail_get(jiov, 4, JAIL_DYING) < 0) { + /* + * It could be that the jail just barely finished + * dying, or it could be that the jid never existed + * but the name does. In either case, another try + * at creating the jail should do the right thing. + */ + if (errno == ENOENT) + j->jid = jailparam_set_note(j, setparams, ns, + JAIL_CREATE); + } else if (dying) { + j->jid = jid; + if (rdtun_params(j, 1) < 0) { + j->jid = -1; + didfail = 1; + } else { + sjp = setparams2 = alloca((j->njp + dopersist) * + sizeof(struct jailparam)); + for (jp = setparams; jp < setparams + ns; jp++) + if (!JP_RDTUN(jp) || + !strcmp(jp->jp_name, "jid")) + *sjp++ = *jp; + j->jid = jailparam_set_note(j, setparams2, + sjp - setparams2, JAIL_UPDATE | JAIL_DYING); + /* + * Again, perhaps the jail just finished dying. + */ + if (j->jid < 0 && errno == ENOENT) + j->jid = jailparam_set_note(j, + setparams, ns, JAIL_CREATE); + } } - if (setgroups(ngroups, groups) != 0) - err(1, "setgroups"); - if (setgid(pwd->pw_gid) != 0) - err(1, "setgid"); - if (setusercontext(lcap, pwd, pwd->pw_uid, - LOGIN_SETALL & ~LOGIN_SETGROUP & ~LOGIN_SETLOGIN) != 0) - err(1, "setusercontext"); - login_close(lcap); } - if (lflag) { - if (*pwd->pw_shell) - shell = pwd->pw_shell; - else - shell = _PATH_BSHELL; - if (chdir(pwd->pw_dir) < 0) - errx(1, "no home directory"); - setenv("HOME", pwd->pw_dir, 1); - setenv("SHELL", shell, 1); - setenv("USER", pwd->pw_name, 1); - if (p) - setenv("TERM", p, 1); + if (j->jid < 0 && !didfail) { + jail_warnx(j, "%s", jail_errmsg); + failed(j); } - execvp(argv[cmdarg], argv + cmdarg); - err(1, "execvp: %s", argv[cmdarg]); + if (dopersist) { + jailparam_free(setparams, 1); + if (j->jid > 0) + j->flags |= JF_PERSIST; + } + return j->jid; } -#if defined(INET6) || defined(INET) +/* + * Remove a temporarily set "persist" parameter. + */ static void -add_ip_addr(char **addrp, char *value) +clear_persist(struct cfjail *j) +{ + struct iovec jiov[4]; + int jid; + + if (!(j->flags & JF_PERSIST)) + return; + j->flags &= ~JF_PERSIST; + *(const void **)&jiov[0].iov_base = "jid"; + jiov[0].iov_len = sizeof("jid"); + jiov[1].iov_base = &j->jid; + jiov[1].iov_len = sizeof(j->jid); + *(const void **)&jiov[2].iov_base = "nopersist"; + jiov[2].iov_len = sizeof("nopersist"); + jiov[3].iov_base = NULL; + jiov[3].iov_len = 0; + jid = jail_set(jiov, 4, JAIL_UPDATE); + if (verbose > 0) + jail_note(j, "jail_set(JAIL_UPDATE) jid=%d nopersist%s%s\n", + j->jid, jid < 0 ? ": " : "", + jid < 0 ? strerror(errno) : ""); +} + +/* + * Set a jail's parameters. + */ +static int +update_jail(struct cfjail *j) +{ + 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) { - int addrlen; - char *addr; - - if (!*addrp) { - *addrp = strdup(value); - if (!*addrp) - err(1, "malloc"); - } else if (value[0]) { - addrlen = strlen(*addrp) + strlen(value) + 2; - addr = malloc(addrlen); - if (!addr) - err(1, "malloc"); - snprintf(addr, addrlen, "%s,%s", *addrp, value); - free(*addrp); - *addrp = addr; + struct 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; } -#endif -#ifdef INET6 +/* + * Get the jail's jid if it is running. + */ static void -add_ip_addr46(char *value) +running_jid(struct cfjail *j, int dflag) { - char *p, *np; + struct iovec jiov[2]; + const char *pval; + char *ep; + int jid; + + if ((pval = string_param(j->intparams[KP_JID]))) { + if (!(jid = strtol(pval, &ep, 10)) || *ep) { + j->jid = -1; + return; + } + *(const void **)&jiov[0].iov_base = "jid"; + jiov[0].iov_len = sizeof("jid"); + jiov[1].iov_base = &jid; + jiov[1].iov_len = sizeof(jid); + } else if ((pval = string_param(j->intparams[KP_NAME]))) { + *(const void **)&jiov[0].iov_base = "name"; + jiov[0].iov_len = sizeof("name"); + jiov[1].iov_len = strlen(pval) + 1; + jiov[1].iov_base = alloca(jiov[1].iov_len); + strcpy(jiov[1].iov_base, pval); + } else { + j->jid = -1; + return; + } + j->jid = jail_get(jiov, 2, dflag ? JAIL_DYING : 0); +} - for (p = value;; p = np + 1) - { - np = strchr(p, ','); - if (np) - *np = '\0'; - add_ip_addrinfo(AI_NUMERICHOST, p); - if (!np) - break; +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; } -#endif +/* + * Print a jail record. + */ static void -add_ip_addrinfo(int ai_flags, char *value) +print_jail(FILE *fp, struct cfjail *j, int oldcl) { - struct addrinfo hints, *ai0, *ai; - int error; + struct cfparam *p; + + if (oldcl) { + fprintf(fp, "%d\t", j->jid); + print_param(fp, j->intparams[KP_PATH], ',', 0); + putc('\t', fp); + print_param(fp, j->intparams[KP_HOST_HOSTNAME], ',', 0); + putc('\t', fp); #ifdef INET - char avalue4[INET_ADDRSTRLEN]; - struct in_addr addr4; -#endif + print_param(fp, j->intparams[KP_IP4_ADDR], ',', 0); #ifdef INET6 - char avalue6[INET6_ADDRSTRLEN]; - struct in6_addr addr6; + 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 - - /* Look up the hostname (or get the address) */ - memset(&hints, 0, sizeof(hints)); - hints.ai_socktype = SOCK_STREAM; -#if defined(INET6) && defined(INET) - hints.ai_family = PF_UNSPEC; -#elif defined(INET6) - hints.ai_family = PF_INET6; -#elif defined(INET) - hints.ai_family = PF_INET; -#endif - hints.ai_flags = ai_flags; - error = getaddrinfo(value, NULL, &hints, &ai0); - if (error != 0) - errx(1, "hostname %s: %s", value, gai_strerror(error)); - - /* Convert the addresses to ASCII so set_param can convert them back. */ - for (ai = ai0; ai; ai = ai->ai_next) - switch (ai->ai_family) { -#ifdef INET - case AF_INET: - if (!ip4_ok && (ai_flags & AI_NUMERICHOST) == 0) - break; - memcpy(&addr4, &((struct sockaddr_in *) - (void *)ai->ai_addr)->sin_addr, sizeof(addr4)); - if (inet_ntop(AF_INET, &addr4, avalue4, - INET_ADDRSTRLEN) == NULL) - err(1, "inet_ntop"); - add_ip_addr(&ip4_addr, avalue4); - break; #endif #ifdef INET6 - case AF_INET6: - if (!ip6_ok && (ai_flags & AI_NUMERICHOST) == 0) - break; - memcpy(&addr6, &((struct sockaddr_in6 *) - (void *)ai->ai_addr)->sin6_addr, sizeof(addr6)); - if (inet_ntop(AF_INET6, &addr6, avalue6, - INET6_ADDRSTRLEN) == NULL) - err(1, "inet_ntop"); - add_ip_addr(&ip6_addr, avalue6); - break; + print_param(fp, j->intparams[KP_IP6_ADDR], ',', 0); #endif - } - freeaddrinfo(ai0); + putc('\t', fp); + print_param(fp, j->intparams[IP_COMMAND], ' ', 0); + } else { + fprintf(fp, "jid=%d", j->jid); + TAILQ_FOREACH(p, &j->params, tq) + if (strcmp(p->name, "jid")) { + putc(' ', fp); + print_param(fp, p, ',', 1); + } + } + putc('\n', fp); } +/* + * Print a parameter value, or a name=value pair. + */ static void -quoted_print(FILE *fp, char *str) +print_param(FILE *fp, const struct cfparam *p, int sep, int doname) { - int c, qc; - char *p = str; + const struct cfstring *s, *ts; - /* An empty string needs quoting. */ - if (!*p) { - fputs("\"\"", fp); + if (doname) + fputs(p->name, fp); + if (p == NULL || TAILQ_EMPTY(&p->val)) return; + if (doname) + putc('=', fp); + TAILQ_FOREACH_SAFE(s, &p->val, tq, ts) { + quoted_print(fp, s->s); + if (ts != NULL) + putc(sep, fp); } +} - /* - * The value will be surrounded by quotes if it contains spaces - * or quotes. - */ - qc = strchr(p, '\'') ? '"' +/* + * Print a string with quotes around spaces. + */ +static void +quoted_print(FILE *fp, char *str) +{ + int c, qc; + char *p = str; + + qc = !*p ? '"' + : strchr(p, '\'') ? '"' : strchr(p, '"') ? '\'' : strchr(p, ' ') || strchr(p, '\t') ? '"' : 0; @@ -505,71 +974,16 @@ quoted_print(FILE *fp, char *str) } static void -set_param(const char *name, char *value) -{ - struct jailparam *param; - char path[PATH_MAX]; - int i; - - static int paramlistsize; - - /* Separate the name from the value, if not done already. */ - if (name == NULL) { - name = value; - if ((value = strchr(value, '='))) - *value++ = '\0'; - } - - /* jail_set won't chdir along with its chroot, so do it here. */ - if (!strcmp(name, "path")) { - /* resolve the path with realpath(3) */ - if (realpath(value, path) != NULL) - value = path; - if (chdir(value) < 0) - err(1, "chdir: %s", value); - } - - /* Check for repeat parameters */ - for (i = 0; i < nparams; i++) - if (!strcmp(name, params[i].jp_name)) { - jailparam_free(params + i, 1); - memcpy(params + i, params + i + 1, - (--nparams - i) * sizeof(struct jailparam)); - break; - } - - /* Make sure there is room for the new param record. */ - if (!nparams) { - paramlistsize = 32; - params = malloc(paramlistsize * sizeof(*params)); - param_values = malloc(paramlistsize * sizeof(*param_values)); - if (params == NULL || param_values == NULL) - err(1, "malloc"); - } else if (nparams >= paramlistsize) { - paramlistsize *= 2; - params = realloc(params, paramlistsize * sizeof(*params)); - param_values = realloc(param_values, - paramlistsize * sizeof(*param_values)); - if (params == NULL) - err(1, "realloc"); - } - - /* Look up the paramter. */ - param_values[nparams] = value; - param = params + nparams++; - if (jailparam_init(param, name) < 0 || - jailparam_import(param, value) < 0) - errx(1, "%s", jail_errmsg); -} - -static void usage(void) { (void)fprintf(stderr, - "usage: jail [-d] [-h] [-i] [-J jid_file] " - "[-l -u username | -U username]\n" - " [-c | -m] param=value ... [command=command ...]\n" - " jail [-r jail]\n"); + "usage: jail [-dhilqv] [-J jid_file] [-u username] [-U username]\n" + " -[cmr] param=value ... [command=command ...]\n" + " jail [-dqv] [-f file] -[cmr] [jail]\n" + " jail [-qv] [-f file] -[rR] ['*' | jail ...]\n" + " jail [-dhilqv] [-J jid_file] [-u username] [-U username]\n" + " [-n jailname] [-s securelevel]\n" + " path hostname [ip[,...]] command ...\n"); exit(1); } |