diff options
Diffstat (limited to 'lib/libjail/jail.c')
-rw-r--r-- | lib/libjail/jail.c | 1069 |
1 files changed, 1069 insertions, 0 deletions
diff --git a/lib/libjail/jail.c b/lib/libjail/jail.c new file mode 100644 index 0000000..207b9f2 --- /dev/null +++ b/lib/libjail/jail.c @@ -0,0 +1,1069 @@ +/*- + * Copyright (c) 2009 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/param.h> +#include <sys/types.h> +#include <sys/jail.h> +#include <sys/socket.h> +#include <sys/sysctl.h> + +#include <arpa/inet.h> +#include <netinet/in.h> + +#include <errno.h> +#include <inttypes.h> +#include <stdio.h> +#include <stdarg.h> +#include <stdlib.h> +#include <string.h> + +#include "jail.h" + +#define SJPARAM "security.jail.param" + +#define JPS_IN_ADDR 1 +#define JPS_IN6_ADDR 2 + +#define ARRAY_SANITY 5 +#define ARRAY_SLOP 5 + + +static int jailparam_import_enum(const char **values, int nvalues, + const char *valstr, size_t valsize, int *value); +static int jailparam_type(struct jailparam *jp); +static char *noname(const char *name); +static char *nononame(const char *name); + +char jail_errmsg[JAIL_ERRMSGLEN]; + +static const char *bool_values[] = { "false", "true" }; +static const char *jailsys_values[] = { "disable", "new", "inherit" }; + + +/* + * Import a null-terminated parameter list and set a jail with the flags + * and parameters. + */ +int +jail_setv(int flags, ...) +{ + va_list ap, tap; + struct jailparam *jp; + const char *name, *value; + int njp, jid; + + /* Create the parameter list and import the parameters. */ + va_start(ap, flags); + va_copy(tap, ap); + for (njp = 0; va_arg(tap, char *) != NULL; njp++) + (void)va_arg(tap, char *); + va_end(tap); + jp = alloca(njp * sizeof(struct jailparam)); + for (njp = 0; (name = va_arg(ap, char *)) != NULL;) { + value = va_arg(ap, char *); + if (jailparam_init(jp + njp, name) < 0) + goto error; + if (jailparam_import(jp + njp++, value) < 0) + goto error; + } + va_end(ap); + jid = jailparam_set(jp, njp, flags); + jailparam_free(jp, njp); + return (jid); + + error: + jailparam_free(jp, njp); + va_end(ap); + return (-1); +} + +/* + * Read a null-terminated parameter list, get the referenced jail, and export + * the parameters to the list. + */ +int +jail_getv(int flags, ...) +{ + va_list ap, tap; + struct jailparam *jp, *jp_lastjid, *jp_jid, *jp_name, *jp_key; + char *valarg, *value; + const char *name, *key_value, *lastjid_value, *jid_value, *name_value; + int njp, i, jid; + + /* Create the parameter list and find the key. */ + va_start(ap, flags); + va_copy(tap, ap); + for (njp = 0; va_arg(tap, char *) != NULL; njp++) + (void)va_arg(tap, char *); + va_end(tap); + + jp = alloca(njp * sizeof(struct jailparam)); + va_copy(tap, ap); + jp_lastjid = jp_jid = jp_name = NULL; + lastjid_value = jid_value = name_value = NULL; + for (njp = 0; (name = va_arg(tap, char *)) != NULL; njp++) { + value = va_arg(tap, char *); + if (jailparam_init(jp + njp, name) < 0) { + va_end(tap); + goto error; + } + if (!strcmp(jp[njp].jp_name, "lastjid")) { + jp_lastjid = jp + njp; + lastjid_value = value; + } else if (!strcmp(jp[njp].jp_name, "jid")) { + jp_jid = jp + njp; + jid_value = value; + } if (!strcmp(jp[njp].jp_name, "name")) { + jp_name = jp + njp; + name_value = value; + } + } + va_end(tap); + /* Import the key parameter. */ + if (jp_lastjid != NULL) { + jp_key = jp_lastjid; + key_value = lastjid_value; + } else if (jp_jid != NULL && strtol(jid_value, NULL, 10) != 0) { + jp_key = jp_jid; + key_value = jid_value; + } else if (jp_name != NULL) { + jp_key = jp_name; + key_value = name_value; + } else { + strlcpy(jail_errmsg, "no jail specified", JAIL_ERRMSGLEN); + errno = ENOENT; + goto error; + } + if (jailparam_import(jp_key, key_value) < 0) + goto error; + /* Get the jail and export the parameters. */ + jid = jailparam_get(jp, njp, flags); + if (jid < 0) + goto error; + for (i = 0; i < njp; i++) { + (void)va_arg(ap, char *); + valarg = va_arg(ap, char *); + if (jp + i != jp_key) { + /* It's up to the caller to ensure there's room. */ + if ((jp[i].jp_ctltype & CTLTYPE) == CTLTYPE_STRING) + strcpy(valarg, jp[i].jp_value); + else { + value = jailparam_export(jp + i); + if (value == NULL) + goto error; + strcpy(valarg, value); + free(value); + } + } + } + jailparam_free(jp, njp); + va_end(ap); + return (jid); + + error: + jailparam_free(jp, njp); + va_end(ap); + return (-1); +} + +/* + * Return a list of all known parameters. + */ +int +jailparam_all(struct jailparam **jpp) +{ + struct jailparam *jp, *tjp; + size_t mlen1, mlen2, buflen; + int njp, nlist; + int mib1[CTL_MAXNAME], mib2[CTL_MAXNAME - 2]; + char buf[MAXPATHLEN]; + + njp = 0; + nlist = 32; + jp = malloc(nlist * sizeof(*jp)); + if (jp == NULL) { + strerror_r(errno, jail_errmsg, JAIL_ERRMSGLEN); + return (-1); + } + mib1[0] = 0; + mib1[1] = 2; + mlen1 = CTL_MAXNAME - 2; + if (sysctlnametomib(SJPARAM, mib1 + 2, &mlen1) < 0) { + snprintf(jail_errmsg, JAIL_ERRMSGLEN, + "sysctlnametomib(" SJPARAM "): %s", strerror(errno)); + goto error; + } + for (;; njp++) { + /* Get the next parameter. */ + mlen2 = sizeof(mib2); + if (sysctl(mib1, mlen1 + 2, mib2, &mlen2, NULL, 0) < 0) { + snprintf(jail_errmsg, JAIL_ERRMSGLEN, + "sysctl(0.2): %s", strerror(errno)); + goto error; + } + if (mib2[0] != mib1[2] || mib2[1] != mib1[3] || + mib2[2] != mib1[4]) + break; + /* Convert it to an ascii name. */ + memcpy(mib1 + 2, mib2, mlen2); + mlen1 = mlen2 / sizeof(int); + mib1[1] = 1; + buflen = sizeof(buf); + if (sysctl(mib1, mlen1 + 2, buf, &buflen, NULL, 0) < 0) { + snprintf(jail_errmsg, JAIL_ERRMSGLEN, + "sysctl(0.1): %s", strerror(errno)); + goto error; + } + if (buf[buflen - 2] == '.') + buf[buflen - 2] = '\0'; + /* Add the parameter to the list */ + if (njp >= nlist) { + nlist *= 2; + tjp = realloc(jp, nlist * sizeof(*jp)); + if (tjp == NULL) + goto error; + jp = tjp; + } + if (jailparam_init(jp + njp, buf + sizeof(SJPARAM)) < 0) + goto error; + mib1[1] = 2; + } + jp = realloc(jp, njp * sizeof(*jp)); + *jpp = jp; + return (njp); + + error: + jailparam_free(jp, njp); + free(jp); + return (-1); +} + +/* + * Clear a jail parameter and copy in its name. + */ +int +jailparam_init(struct jailparam *jp, const char *name) +{ + + memset(jp, 0, sizeof(*jp)); + jp->jp_name = strdup(name); + if (jp->jp_name == NULL) { + strerror_r(errno, jail_errmsg, JAIL_ERRMSGLEN); + return (-1); + } + if (jailparam_type(jp) < 0) { + jailparam_free(jp, 1); + jp->jp_name = NULL; + jp->jp_value = NULL; + return (-1); + } + return (0); +} + +/* + * Put a name and value into a jail parameter element, converting the value + * to internal form. + */ +int +jailparam_import(struct jailparam *jp, const char *value) +{ + char *p, *ep, *tvalue; + const char *avalue; + int i, nval, fw; + + if (value == NULL) + return (0); + if ((jp->jp_ctltype & CTLTYPE) == CTLTYPE_STRING) { + jp->jp_value = strdup(value); + if (jp->jp_value == NULL) { + strerror_r(errno, jail_errmsg, JAIL_ERRMSGLEN); + return (-1); + } + return (0); + } + nval = 1; + if (jp->jp_elemlen) { + if (value[0] == '\0' || (value[0] == '-' && value[1] == '\0')) { + jp->jp_value = strdup(""); + if (jp->jp_value == NULL) { + strerror_r(errno, jail_errmsg, JAIL_ERRMSGLEN); + return (-1); + } + jp->jp_valuelen = 0; + return (0); + } + for (p = strchr(value, ','); p; p = strchr(p + 1, ',')) + nval++; + jp->jp_valuelen = jp->jp_elemlen * nval; + } + jp->jp_value = malloc(jp->jp_valuelen); + if (jp->jp_value == NULL) { + strerror_r(errno, jail_errmsg, JAIL_ERRMSGLEN); + return (-1); + } + avalue = value; + for (i = 0; i < nval; i++) { + fw = nval == 1 ? strlen(avalue) : strcspn(avalue, ","); + switch (jp->jp_ctltype & CTLTYPE) { + case CTLTYPE_INT: + if (jp->jp_flags & (JP_BOOL | JP_NOBOOL)) { + if (!jailparam_import_enum(bool_values, 2, + avalue, fw, &((int *)jp->jp_value)[i])) { + snprintf(jail_errmsg, + JAIL_ERRMSGLEN, "%s: " + "unknown boolean value \"%.*s\"", + jp->jp_name, fw, avalue); + errno = EINVAL; + goto error; + } + break; + } + if (jp->jp_flags & JP_JAILSYS) { + /* + * Allow setting a jailsys parameter to "new" + * in a booleanesque fashion. + */ + if (value[0] == '\0') + ((int *)jp->jp_value)[i] = JAIL_SYS_NEW; + else if (!jailparam_import_enum(jailsys_values, + sizeof(jailsys_values) / + sizeof(jailsys_values[0]), avalue, fw, + &((int *)jp->jp_value)[i])) { + snprintf(jail_errmsg, + JAIL_ERRMSGLEN, "%s: " + "unknown jailsys value \"%.*s\"", + jp->jp_name, fw, avalue); + errno = EINVAL; + goto error; + } + break; + } + ((int *)jp->jp_value)[i] = strtol(avalue, &ep, 10); + integer_test: + if (ep != avalue + fw) { + snprintf(jail_errmsg, JAIL_ERRMSGLEN, + "%s: non-integer value \"%.*s\"", + jp->jp_name, fw, avalue); + errno = EINVAL; + goto error; + } + break; + case CTLTYPE_UINT: + ((unsigned *)jp->jp_value)[i] = + strtoul(avalue, &ep, 10); + goto integer_test; + case CTLTYPE_LONG: + ((long *)jp->jp_value)[i] = strtol(avalue, &ep, 10); + goto integer_test; + case CTLTYPE_ULONG: + ((unsigned long *)jp->jp_value)[i] = + strtoul(avalue, &ep, 10); + goto integer_test; + case CTLTYPE_S64: + ((int64_t *)jp->jp_value)[i] = + strtoimax(avalue, &ep, 10); + goto integer_test; + case CTLTYPE_U64: + ((uint64_t *)jp->jp_value)[i] = + strtoumax(avalue, &ep, 10); + goto integer_test; + case CTLTYPE_STRUCT: + tvalue = alloca(fw + 1); + strlcpy(tvalue, avalue, fw + 1); + switch (jp->jp_structtype) { + case JPS_IN_ADDR: + if (inet_pton(AF_INET, tvalue, + &((struct in_addr *)jp->jp_value)[i]) != 1) + { + snprintf(jail_errmsg, + JAIL_ERRMSGLEN, + "%s: not an IPv4 address: %s", + jp->jp_name, tvalue); + errno = EINVAL; + goto error; + } + break; + case JPS_IN6_ADDR: + if (inet_pton(AF_INET6, tvalue, + &((struct in6_addr *)jp->jp_value)[i]) != 1) + { + snprintf(jail_errmsg, + JAIL_ERRMSGLEN, + "%s: not an IPv6 address: %s", + jp->jp_name, tvalue); + errno = EINVAL; + goto error; + } + break; + default: + goto unknown_type; + } + break; + default: + unknown_type: + snprintf(jail_errmsg, JAIL_ERRMSGLEN, + "unknown type for %s", jp->jp_name); + errno = ENOENT; + goto error; + } + avalue += fw + 1; + } + return (0); + + error: + free(jp->jp_value); + jp->jp_value = NULL; + return (-1); +} + +static int +jailparam_import_enum(const char **values, int nvalues, const char *valstr, + size_t valsize, int *value) +{ + char *ep; + int i; + + for (i = 0; i < nvalues; i++) + if (valsize == strlen(values[i]) && + !strncasecmp(valstr, values[i], valsize)) { + *value = i; + return 1; + } + *value = strtol(valstr, &ep, 10); + return (ep == valstr + valsize); +} + +/* + * Put a name and value into a jail parameter element, copying the value + * but not altering it. + */ +int +jailparam_import_raw(struct jailparam *jp, void *value, size_t valuelen) +{ + + jp->jp_value = value; + jp->jp_valuelen = valuelen; + jp->jp_flags |= JP_RAWVALUE; + return (0); +} + +/* + * Run the jail_set and jail_get system calls on a parameter list. + */ +int +jailparam_set(struct jailparam *jp, unsigned njp, int flags) +{ + struct iovec *jiov; + char *nname; + int i, jid, bool0; + unsigned j; + + jiov = alloca(sizeof(struct iovec) * 2 * (njp + 1)); + bool0 = 0; + for (i = j = 0; j < njp; j++) { + jiov[i].iov_base = jp[j].jp_name; + jiov[i].iov_len = strlen(jp[j].jp_name) + 1; + i++; + if (jp[j].jp_flags & (JP_BOOL | JP_NOBOOL)) { + /* + * Set booleans without values. If one has a value of + * zero, change it to (or from) its "no" counterpart. + */ + jiov[i].iov_base = NULL; + jiov[i].iov_len = 0; + if (jp[j].jp_value != NULL && + jp[j].jp_valuelen == sizeof(int) && + !*(int *)jp[j].jp_value) { + bool0 = 1; + nname = jp[j].jp_flags & JP_BOOL + ? noname(jp[j].jp_name) + : nononame(jp[j].jp_name); + if (nname == NULL) { + njp = j; + jid = -1; + goto done; + } + jiov[i - 1].iov_base = nname; + jiov[i - 1].iov_len = strlen(nname) + 1; + + } + } else { + /* + * Try to fill in missing values with an empty string. + */ + if (jp[j].jp_value == NULL && jp[j].jp_valuelen > 0 && + jailparam_import(jp + j, "") < 0) { + njp = j; + jid = -1; + goto done; + } + jiov[i].iov_base = jp[j].jp_value; + jiov[i].iov_len = + (jp[j].jp_ctltype & CTLTYPE) == CTLTYPE_STRING + ? strlen(jp[j].jp_value) + 1 + : jp[j].jp_valuelen; + } + i++; + } + *(const void **)&jiov[i].iov_base = "errmsg"; + jiov[i].iov_len = sizeof("errmsg"); + i++; + jiov[i].iov_base = jail_errmsg; + jiov[i].iov_len = JAIL_ERRMSGLEN; + i++; + jail_errmsg[0] = 0; + jid = jail_set(jiov, i, flags); + if (jid < 0 && !jail_errmsg[0]) + snprintf(jail_errmsg, sizeof(jail_errmsg), "jail_set: %s", + strerror(errno)); + done: + if (bool0) + for (j = 0; j < njp; j++) + if ((jp[j].jp_flags & (JP_BOOL | JP_NOBOOL)) && + jp[j].jp_value != NULL && + jp[j].jp_valuelen == sizeof(int) && + !*(int *)jp[j].jp_value) + free(jiov[j * 2].iov_base); + return (jid); +} + +int +jailparam_get(struct jailparam *jp, unsigned njp, int flags) +{ + struct iovec *jiov; + struct jailparam *jp_lastjid, *jp_jid, *jp_name, *jp_key; + int i, ai, ki, jid, arrays, sanity; + unsigned j; + + /* + * Get the types for all parameters. + * Find the key and any array parameters. + */ + jiov = alloca(sizeof(struct iovec) * 2 * (njp + 1)); + jp_lastjid = jp_jid = jp_name = NULL; + arrays = 0; + for (ai = j = 0; j < njp; j++) { + if (!strcmp(jp[j].jp_name, "lastjid")) + jp_lastjid = jp + j; + else if (!strcmp(jp[j].jp_name, "jid")) + jp_jid = jp + j; + else if (!strcmp(jp[j].jp_name, "name")) + jp_name = jp + j; + else if (jp[j].jp_elemlen && !(jp[j].jp_flags & JP_RAWVALUE)) { + arrays = 1; + jiov[ai].iov_base = jp[j].jp_name; + jiov[ai].iov_len = strlen(jp[j].jp_name) + 1; + ai++; + jiov[ai].iov_base = NULL; + jiov[ai].iov_len = 0; + ai++; + } + } + jp_key = jp_lastjid ? jp_lastjid : + jp_jid && jp_jid->jp_valuelen == sizeof(int) && + jp_jid->jp_value && *(int *)jp_jid->jp_value ? jp_jid : jp_name; + if (jp_key == NULL || jp_key->jp_value == NULL) { + strlcpy(jail_errmsg, "no jail specified", JAIL_ERRMSGLEN); + errno = ENOENT; + return (-1); + } + ki = ai; + jiov[ki].iov_base = jp_key->jp_name; + jiov[ki].iov_len = strlen(jp_key->jp_name) + 1; + ki++; + jiov[ki].iov_base = jp_key->jp_value; + jiov[ki].iov_len = (jp_key->jp_ctltype & CTLTYPE) == CTLTYPE_STRING + ? strlen(jp_key->jp_value) + 1 : jp_key->jp_valuelen; + ki++; + *(const void **)&jiov[ki].iov_base = "errmsg"; + jiov[ki].iov_len = sizeof("errmsg"); + ki++; + jiov[ki].iov_base = jail_errmsg; + jiov[ki].iov_len = JAIL_ERRMSGLEN; + ki++; + jail_errmsg[0] = 0; + if (arrays && jail_get(jiov, ki, flags) < 0) { + if (!jail_errmsg[0]) + snprintf(jail_errmsg, sizeof(jail_errmsg), + "jail_get: %s", strerror(errno)); + return (-1); + } + /* Allocate storage for all parameters. */ + for (ai = j = 0, i = ki; j < njp; j++) { + if (jp[j].jp_elemlen && !(jp[j].jp_flags & JP_RAWVALUE)) { + ai++; + jiov[ai].iov_len += jp[j].jp_elemlen * ARRAY_SLOP; + if (jp[j].jp_valuelen >= jiov[ai].iov_len) + jiov[ai].iov_len = jp[j].jp_valuelen; + else { + jp[j].jp_valuelen = jiov[ai].iov_len; + if (jp[j].jp_value != NULL) + free(jp[j].jp_value); + jp[j].jp_value = malloc(jp[j].jp_valuelen); + if (jp[j].jp_value == NULL) { + strerror_r(errno, jail_errmsg, + JAIL_ERRMSGLEN); + return (-1); + } + } + jiov[ai].iov_base = jp[j].jp_value; + memset(jiov[ai].iov_base, 0, jiov[ai].iov_len); + ai++; + } else if (jp + j != jp_key) { + jiov[i].iov_base = jp[j].jp_name; + jiov[i].iov_len = strlen(jp[j].jp_name) + 1; + i++; + if (jp[j].jp_value == NULL && + !(jp[j].jp_flags & JP_RAWVALUE)) { + jp[j].jp_value = malloc(jp[j].jp_valuelen); + if (jp[j].jp_value == NULL) { + strerror_r(errno, jail_errmsg, + JAIL_ERRMSGLEN); + return (-1); + } + } + jiov[i].iov_base = jp[j].jp_value; + jiov[i].iov_len = jp[j].jp_valuelen; + memset(jiov[i].iov_base, 0, jiov[i].iov_len); + i++; + } + } + /* + * Get the prison. If there are array elements, retry a few times + * in case their sizes changed from under us. + */ + for (sanity = 0;; sanity++) { + jid = jail_get(jiov, i, flags); + if (jid >= 0 || !arrays || sanity == ARRAY_SANITY || + errno != EINVAL || jail_errmsg[0]) + break; + for (ai = j = 0; j < njp; j++) { + if (jp[j].jp_elemlen && + !(jp[j].jp_flags & JP_RAWVALUE)) { + ai++; + jiov[ai].iov_base = NULL; + jiov[ai].iov_len = 0; + ai++; + } + } + if (jail_get(jiov, ki, flags) < 0) + break; + for (ai = j = 0; j < njp; j++) { + if (jp[j].jp_elemlen && + !(jp[j].jp_flags & JP_RAWVALUE)) { + ai++; + jiov[ai].iov_len += + jp[j].jp_elemlen * ARRAY_SLOP; + if (jp[j].jp_valuelen >= jiov[ai].iov_len) + jiov[ai].iov_len = jp[j].jp_valuelen; + else { + jp[j].jp_valuelen = jiov[ai].iov_len; + if (jp[j].jp_value != NULL) + free(jp[j].jp_value); + jp[j].jp_value = + malloc(jiov[ai].iov_len); + if (jp[j].jp_value == NULL) { + strerror_r(errno, jail_errmsg, + JAIL_ERRMSGLEN); + return (-1); + } + } + jiov[ai].iov_base = jp[j].jp_value; + memset(jiov[ai].iov_base, 0, jiov[ai].iov_len); + ai++; + } + } + } + if (jid < 0 && !jail_errmsg[0]) + snprintf(jail_errmsg, sizeof(jail_errmsg), + "jail_get: %s", strerror(errno)); + for (ai = j = 0, i = ki; j < njp; j++) { + if (jp[j].jp_elemlen && !(jp[j].jp_flags & JP_RAWVALUE)) { + ai++; + jp[j].jp_valuelen = jiov[ai].iov_len; + ai++; + } else if (jp + j != jp_key) { + i++; + jp[j].jp_valuelen = jiov[i].iov_len; + i++; + } + } + return (jid); +} + +/* + * Convert a jail parameter's value to external form. + */ +char * +jailparam_export(struct jailparam *jp) +{ + size_t *valuelens; + char *value, *tvalue, **values; + size_t valuelen; + int i, nval, ival; + char valbuf[INET6_ADDRSTRLEN]; + + if ((jp->jp_ctltype & CTLTYPE) == CTLTYPE_STRING) { + value = strdup(jp->jp_value); + if (value == NULL) + strerror_r(errno, jail_errmsg, JAIL_ERRMSGLEN); + return (value); + } + nval = jp->jp_elemlen ? jp->jp_valuelen / jp->jp_elemlen : 1; + if (nval == 0) { + value = strdup(""); + if (value == NULL) + strerror_r(errno, jail_errmsg, JAIL_ERRMSGLEN); + return (value); + } + values = alloca(nval * sizeof(char *)); + valuelens = alloca(nval * sizeof(size_t)); + valuelen = 0; + for (i = 0; i < nval; i++) { + switch (jp->jp_ctltype & CTLTYPE) { + case CTLTYPE_INT: + ival = ((int *)jp->jp_value)[i]; + if ((jp->jp_flags & (JP_BOOL | JP_NOBOOL)) && + (unsigned)ival < 2) { + strlcpy(valbuf, bool_values[ival], + sizeof(valbuf)); + break; + } + if ((jp->jp_flags & JP_JAILSYS) && + (unsigned)ival < sizeof(jailsys_values) / + sizeof(jailsys_values[0])) { + strlcpy(valbuf, jailsys_values[ival], + sizeof(valbuf)); + break; + } + snprintf(valbuf, sizeof(valbuf), "%d", ival); + break; + case CTLTYPE_UINT: + snprintf(valbuf, sizeof(valbuf), "%u", + ((unsigned *)jp->jp_value)[i]); + break; + case CTLTYPE_LONG: + snprintf(valbuf, sizeof(valbuf), "%ld", + ((long *)jp->jp_value)[i]); + break; + case CTLTYPE_ULONG: + snprintf(valbuf, sizeof(valbuf), "%lu", + ((unsigned long *)jp->jp_value)[i]); + break; + case CTLTYPE_S64: + snprintf(valbuf, sizeof(valbuf), "%jd", + (intmax_t)((int64_t *)jp->jp_value)[i]); + break; + case CTLTYPE_U64: + snprintf(valbuf, sizeof(valbuf), "%ju", + (uintmax_t)((uint64_t *)jp->jp_value)[i]); + break; + case CTLTYPE_STRUCT: + switch (jp->jp_structtype) { + case JPS_IN_ADDR: + if (inet_ntop(AF_INET, + &((struct in_addr *)jp->jp_value)[i], + valbuf, sizeof(valbuf)) == NULL) { + strerror_r(errno, jail_errmsg, + JAIL_ERRMSGLEN); + return (NULL); + } + break; + case JPS_IN6_ADDR: + if (inet_ntop(AF_INET6, + &((struct in6_addr *)jp->jp_value)[i], + valbuf, sizeof(valbuf)) == NULL) { + strerror_r(errno, jail_errmsg, + JAIL_ERRMSGLEN); + return (NULL); + } + break; + default: + goto unknown_type; + } + break; + default: + unknown_type: + snprintf(jail_errmsg, JAIL_ERRMSGLEN, + "unknown type for %s", jp->jp_name); + errno = ENOENT; + return (NULL); + } + valuelens[i] = strlen(valbuf) + 1; + valuelen += valuelens[i]; + values[i] = alloca(valuelens[i]); + strcpy(values[i], valbuf); + } + value = malloc(valuelen); + if (value == NULL) + strerror_r(errno, jail_errmsg, JAIL_ERRMSGLEN); + else { + tvalue = value; + for (i = 0; i < nval; i++) { + strcpy(tvalue, values[i]); + if (i < nval - 1) { + tvalue += valuelens[i]; + tvalue[-1] = ','; + } + } + } + return (value); +} + +/* + * Free the contents of a jail parameter list (but not the list itself). + */ +void +jailparam_free(struct jailparam *jp, unsigned njp) +{ + unsigned j; + + for (j = 0; j < njp; j++) { + free(jp[j].jp_name); + if (!(jp[j].jp_flags & JP_RAWVALUE)) + free(jp[j].jp_value); + } +} + +/* + * Find a parameter's type and size from its MIB. + */ +static int +jailparam_type(struct jailparam *jp) +{ + char *p, *name, *nname; + size_t miblen, desclen; + int i, isarray; + struct { + int i; + char s[MAXPATHLEN]; + } desc; + int mib[CTL_MAXNAME]; + + /* The "lastjid" parameter isn't real. */ + name = jp->jp_name; + if (!strcmp(name, "lastjid")) { + jp->jp_valuelen = sizeof(int); + jp->jp_ctltype = CTLTYPE_INT | CTLFLAG_WR; + return (0); + } + + /* Find the sysctl that describes the parameter. */ + mib[0] = 0; + mib[1] = 3; + snprintf(desc.s, sizeof(desc.s), SJPARAM ".%s", name); + miblen = sizeof(mib) - 2 * sizeof(int); + if (sysctl(mib, 2, mib + 2, &miblen, desc.s, strlen(desc.s)) < 0) { + if (errno != ENOENT) { + snprintf(jail_errmsg, JAIL_ERRMSGLEN, + "sysctl(0.3.%s): %s", name, strerror(errno)); + return (-1); + } + /* + * The parameter probably doesn't exist. But it might be + * the "no" counterpart to a boolean. + */ + nname = nononame(name); + if (nname == NULL) { + unknown_parameter: + snprintf(jail_errmsg, JAIL_ERRMSGLEN, + "unknown parameter: %s", jp->jp_name); + errno = ENOENT; + return (-1); + } + name = alloca(strlen(nname) + 1); + strcpy(name, nname); + free(nname); + snprintf(desc.s, sizeof(desc.s), SJPARAM ".%s", name); + miblen = sizeof(mib) - 2 * sizeof(int); + if (sysctl(mib, 2, mib + 2, &miblen, desc.s, + strlen(desc.s)) < 0) + goto unknown_parameter; + jp->jp_flags |= JP_NOBOOL; + } + mib_desc: + mib[1] = 4; + desclen = sizeof(desc); + if (sysctl(mib, (miblen / sizeof(int)) + 2, &desc, &desclen, + NULL, 0) < 0) { + snprintf(jail_errmsg, JAIL_ERRMSGLEN, + "sysctl(0.4.%s): %s", name, strerror(errno)); + return (-1); + } + jp->jp_ctltype = desc.i; + /* If this came from removing a "no", it better be a boolean. */ + if (jp->jp_flags & JP_NOBOOL) { + if ((desc.i & CTLTYPE) == CTLTYPE_INT && desc.s[0] == 'B') { + jp->jp_valuelen = sizeof(int); + return (0); + } + else if ((desc.i & CTLTYPE) != CTLTYPE_NODE) + goto unknown_parameter; + } + /* See if this is an array type. */ + p = strchr(desc.s, '\0'); + isarray = 0; + if (p - 2 < desc.s || strcmp(p - 2, ",a")) + isarray = 0; + else { + isarray = 1; + p[-2] = 0; + } + /* Look for types we understand. */ + switch (desc.i & CTLTYPE) { + case CTLTYPE_INT: + if (desc.s[0] == 'B') + jp->jp_flags |= JP_BOOL; + else if (!strcmp(desc.s, "E,jailsys")) + jp->jp_flags |= JP_JAILSYS; + case CTLTYPE_UINT: + jp->jp_valuelen = sizeof(int); + break; + case CTLTYPE_LONG: + case CTLTYPE_ULONG: + jp->jp_valuelen = sizeof(long); + break; + case CTLTYPE_S64: + case CTLTYPE_U64: + jp->jp_valuelen = sizeof(int64_t); + break; + case CTLTYPE_STRING: + desc.s[0] = 0; + desclen = sizeof(desc.s); + if (sysctl(mib + 2, miblen / sizeof(int), desc.s, &desclen, + NULL, 0) < 0) { + snprintf(jail_errmsg, JAIL_ERRMSGLEN, + "sysctl(" SJPARAM ".%s): %s", name, + strerror(errno)); + return (-1); + } + jp->jp_valuelen = strtoul(desc.s, NULL, 10); + break; + case CTLTYPE_STRUCT: + if (!strcmp(desc.s, "S,in_addr")) { + jp->jp_structtype = JPS_IN_ADDR; + jp->jp_valuelen = sizeof(struct in_addr); + } else if (!strcmp(desc.s, "S,in6_addr")) { + jp->jp_structtype = JPS_IN6_ADDR; + jp->jp_valuelen = sizeof(struct in6_addr); + } else { + desclen = 0; + if (sysctl(mib + 2, miblen / sizeof(int), + NULL, &jp->jp_valuelen, NULL, 0) < 0) { + snprintf(jail_errmsg, JAIL_ERRMSGLEN, + "sysctl(" SJPARAM ".%s): %s", name, + strerror(errno)); + return (-1); + } + } + break; + case CTLTYPE_NODE: + /* + * A node might be described by an empty-named child, + * which would be immediately before or after the node itself. + */ + mib[1] = 1; + miblen += sizeof(int); + for (i = -1; i <= 1; i += 2) { + mib[(miblen / sizeof(int)) + 1] = + mib[(miblen / sizeof(int))] + i; + desclen = sizeof(desc.s); + if (sysctl(mib, (miblen / sizeof(int)) + 2, desc.s, + &desclen, NULL, 0) < 0) { + if (errno == ENOENT) + continue; + snprintf(jail_errmsg, JAIL_ERRMSGLEN, + "sysctl(0.1): %s", strerror(errno)); + return (-1); + } + if (desclen == sizeof(SJPARAM) + strlen(name) + 2 && + memcmp(SJPARAM ".", desc.s, sizeof(SJPARAM)) == 0 && + memcmp(name, desc.s + sizeof(SJPARAM), + desclen - sizeof(SJPARAM) - 2) == 0 && + desc.s[desclen - 2] == '.') + goto mib_desc; + } + goto unknown_parameter; + default: + snprintf(jail_errmsg, JAIL_ERRMSGLEN, + "unknown type for %s", jp->jp_name); + errno = ENOENT; + return (-1); + } + if (isarray) { + jp->jp_elemlen = jp->jp_valuelen; + jp->jp_valuelen = 0; + } + return (0); +} + +/* + * Change a boolean parameter name into its "no" counterpart or vice versa. + */ +static char * +noname(const char *name) +{ + char *nname, *p; + + nname = malloc(strlen(name) + 3); + if (nname == NULL) { + strerror_r(errno, jail_errmsg, JAIL_ERRMSGLEN); + return (NULL); + } + p = strrchr(name, '.'); + if (p != NULL) + sprintf(nname, "%.*s.no%s", (int)(p - name), name, p + 1); + else + sprintf(nname, "no%s", name); + return (nname); +} + +static char * +nononame(const char *name) +{ + char *p, *nname; + + p = strrchr(name, '.'); + if (strncmp(p ? p + 1 : name, "no", 2)) { + snprintf(jail_errmsg, sizeof(jail_errmsg), + "mismatched boolean: %s", name); + errno = EINVAL; + return (NULL); + } + nname = malloc(strlen(name) - 1); + if (nname == NULL) { + strerror_r(errno, jail_errmsg, JAIL_ERRMSGLEN); + return (NULL); + } + if (p != NULL) + sprintf(nname, "%.*s.%s", (int)(p - name), name, p + 3); + else + strcpy(nname, name + 2); + return (nname); +} |