/* * Copyright (c) 1997-8,2007-8 Andrew G Morgan * Copyright (c) 1997 Andrew Main * * This file deals with exchanging internal and textual * representations of capability sets. */ #define _GNU_SOURCE #include #define LIBCAP_PLEASE_INCLUDE_ARRAY #include "libcap.h" #include #include #ifdef INCLUDE_GPERF_OUTPUT /* we need to include it after #define _GNU_SOURCE is set */ #include INCLUDE_GPERF_OUTPUT #endif /* Maximum output text length (16 per cap) */ #define CAP_TEXT_SIZE (16*__CAP_MAXBITS) /* * Parse a textual representation of capabilities, returning an internal * representation. */ #define raise_cap_mask(flat, c) (flat)[CAP_TO_INDEX(c)] |= CAP_TO_MASK(c) static void setbits(cap_t a, const __u32 *b, cap_flag_t set, unsigned blks) { int n; for (n = blks; n--; ) { a->u[n].flat[set] |= b[n]; } } static void clrbits(cap_t a, const __u32 *b, cap_flag_t set, unsigned blks) { int n; for (n = blks; n--; ) a->u[n].flat[set] &= ~b[n]; } static char const *namcmp(char const *str, char const *nam) { while (*nam && tolower((unsigned char)*str) == *nam) { str++; nam++; } if (*nam || isalnum((unsigned char)*str) || *str == '_') return NULL; return str; } static void forceall(__u32 *flat, __u32 value, unsigned blks) { unsigned n; for (n = blks; n--; flat[n] = value); return; } static int lookupname(char const **strp) { union { char const *constp; char *p; } str; str.constp = *strp; if (isdigit(*str.constp)) { unsigned long n = strtoul(str.constp, &str.p, 0); if (n >= __CAP_MAXBITS) return -1; *strp = str.constp; return n; } else { int c; unsigned len; for (len=0; (c = str.constp[len]); ++len) { if (!(isalpha(c) || (c == '_'))) { break; } } #ifdef GPERF_DOWNCASE const struct __cap_token_s *token_info; token_info = __cap_lookup_name(str.constp, len); if (token_info != NULL) { *strp = str.constp + len; return token_info->index; } #else /* ie., ndef GPERF_DOWNCASE */ char const *s; unsigned n; for (n = __CAP_BITS; n--; ) if (_cap_names[n] && (s = namcmp(str.constp, _cap_names[n]))) { *strp = s; return n; } #endif /* def GPERF_DOWNCASE */ return -1; /* No definition available */ } } cap_t cap_from_text(const char *str) { cap_t res; int n; unsigned cap_blks; if (str == NULL) { _cap_debug("bad argument"); errno = EINVAL; return NULL; } if (!(res = cap_init())) return NULL; switch (res->head.version) { case _LINUX_CAPABILITY_VERSION_1: cap_blks = _LINUX_CAPABILITY_U32S_1; break; case _LINUX_CAPABILITY_VERSION_2: cap_blks = _LINUX_CAPABILITY_U32S_2; break; case _LINUX_CAPABILITY_VERSION_3: cap_blks = _LINUX_CAPABILITY_U32S_3; break; default: errno = EINVAL; return NULL; } _cap_debug("%s", str); for (;;) { __u32 list[__CAP_BLKS]; char op; int flags = 0, listed=0; forceall(list, 0, __CAP_BLKS); /* skip leading spaces */ while (isspace((unsigned char)*str)) str++; if (!*str) { _cap_debugcap("e = ", *res, CAP_EFFECTIVE); _cap_debugcap("i = ", *res, CAP_INHERITABLE); _cap_debugcap("p = ", *res, CAP_PERMITTED); return res; } /* identify caps specified by this clause */ if (isalnum((unsigned char)*str) || *str == '_') { for (;;) { if (namcmp(str, "all")) { str += 3; forceall(list, ~0, cap_blks); } else { n = lookupname(&str); if (n == -1) goto bad; raise_cap_mask(list, n); } if (*str != ',') break; if (!isalnum((unsigned char)*++str) && *str != '_') goto bad; } listed = 1; } else if (*str == '+' || *str == '-') { goto bad; /* require a list of capabilities */ } else { forceall(list, ~0, cap_blks); } /* identify first operation on list of capabilities */ op = *str++; if (op == '=' && (*str == '+' || *str == '-')) { if (!listed) goto bad; op = (*str++ == '+' ? 'P':'M'); /* skip '=' and take next op */ } else if (op != '+' && op != '-' && op != '=') goto bad; /* cycle through list of actions */ do { _cap_debug("next char = `%c'", *str); if (*str && !isspace(*str)) { switch (*str++) { /* Effective, Inheritable, Permitted */ case 'e': flags |= LIBCAP_EFF; break; case 'i': flags |= LIBCAP_INH; break; case 'p': flags |= LIBCAP_PER; break; default: goto bad; } } else if (op != '=') { _cap_debug("only '=' can be followed by space"); goto bad; } _cap_debug("how to read?"); switch (op) { /* how do we interpret the caps? */ case '=': case 'P': /* =+ */ case 'M': /* =- */ clrbits(res, list, CAP_EFFECTIVE, cap_blks); clrbits(res, list, CAP_PERMITTED, cap_blks); clrbits(res, list, CAP_INHERITABLE, cap_blks); if (op == 'M') goto minus; /* fall through */ case '+': if (flags & LIBCAP_EFF) setbits(res, list, CAP_EFFECTIVE, cap_blks); if (flags & LIBCAP_PER) setbits(res, list, CAP_PERMITTED, cap_blks); if (flags & LIBCAP_INH) setbits(res, list, CAP_INHERITABLE, cap_blks); break; case '-': minus: if (flags & LIBCAP_EFF) clrbits(res, list, CAP_EFFECTIVE, cap_blks); if (flags & LIBCAP_PER) clrbits(res, list, CAP_PERMITTED, cap_blks); if (flags & LIBCAP_INH) clrbits(res, list, CAP_INHERITABLE, cap_blks); break; } /* new directive? */ if (*str == '+' || *str == '-') { if (!listed) { _cap_debug("for + & - must list capabilities"); goto bad; } flags = 0; /* reset the flags */ op = *str++; if (!isalpha(*str)) goto bad; } } while (*str && !isspace(*str)); _cap_debug("next clause"); } bad: cap_free(res); res = NULL; errno = EINVAL; return res; } /* * lookup a capability name and return its numerical value */ int cap_from_name(const char *name, cap_value_t *value_p) { int n; if (((n = lookupname(&name)) >= 0) && (value_p != NULL)) { *value_p = (unsigned) n; } return -(n < 0); } /* * Convert a single capability index number into a string representation */ char *cap_to_name(cap_value_t cap) { if ((cap < 0) || (cap >= __CAP_BITS)) { #if UINT_MAX != 4294967295U # error Recompile with correctly sized numeric array #endif char *tmp, *result; asprintf(&tmp, "%u", cap); result = _libcap_strdup(tmp); free(tmp); return result; } else { return _libcap_strdup(_cap_names[cap]); } } /* * Convert an internal representation to a textual one. The textual * representation is stored in static memory. It will be overwritten * on the next occasion that this function is called. */ static int getstateflags(cap_t caps, int capno) { int f = 0; if (isset_cap(caps, capno, CAP_EFFECTIVE)) { f |= LIBCAP_EFF; } if (isset_cap(caps, capno, CAP_PERMITTED)) { f |= LIBCAP_PER; } if (isset_cap(caps, capno, CAP_INHERITABLE)) { f |= LIBCAP_INH; } return f; } #define CAP_TEXT_BUFFER_ZONE 100 char *cap_to_text(cap_t caps, ssize_t *length_p) { char buf[CAP_TEXT_SIZE+CAP_TEXT_BUFFER_ZONE]; char *p; int histo[8]; int m, t; unsigned n; unsigned cap_maxbits, cap_blks; /* Check arguments */ if (!good_cap_t(caps)) { errno = EINVAL; return NULL; } switch (caps->head.version) { case _LINUX_CAPABILITY_VERSION_1: cap_blks = _LINUX_CAPABILITY_U32S_1; break; case _LINUX_CAPABILITY_VERSION_2: cap_blks = _LINUX_CAPABILITY_U32S_2; break; case _LINUX_CAPABILITY_VERSION_3: cap_blks = _LINUX_CAPABILITY_U32S_3; break; default: errno = EINVAL; return NULL; } cap_maxbits = 32 * cap_blks; _cap_debugcap("e = ", *caps, CAP_EFFECTIVE); _cap_debugcap("i = ", *caps, CAP_INHERITABLE); _cap_debugcap("p = ", *caps, CAP_PERMITTED); memset(histo, 0, sizeof(histo)); /* default prevailing state to the upper - unnamed bits */ for (n = cap_maxbits-1; n > __CAP_BITS; n--) histo[getstateflags(caps, n)]++; /* find which combination of capability sets shares the most bits we bias to preferring non-set (m=0) with the >= 0 test. Failing to do this causes strange things to happen with older systems that don't know about bits 32+. */ for (m=t=7; t--; ) if (histo[t] >= histo[m]) m = t; /* capture remaining bits - selecting m from only the unnamed bits, we maximize the likelihood that we won't see numeric capability values in the text output. */ while (n--) histo[getstateflags(caps, n)]++; /* blank is not a valid capability set */ p = sprintf(buf, "=%s%s%s", (m & LIBCAP_EFF) ? "e" : "", (m & LIBCAP_INH) ? "i" : "", (m & LIBCAP_PER) ? "p" : "" ) + buf; for (t = 8; t--; ) if (t != m && histo[t]) { *p++ = ' '; for (n = 0; n < cap_maxbits; n++) if (getstateflags(caps, n) == t) { char *this_cap_name; this_cap_name = cap_to_name(n); if ((strlen(this_cap_name) + (p - buf)) > CAP_TEXT_SIZE) { cap_free(this_cap_name); errno = ERANGE; return NULL; } p += sprintf(p, "%s,", this_cap_name); cap_free(this_cap_name); } p--; n = t & ~m; if (n) p += sprintf(p, "+%s%s%s", (n & LIBCAP_EFF) ? "e" : "", (n & LIBCAP_INH) ? "i" : "", (n & LIBCAP_PER) ? "p" : ""); n = ~t & m; if (n) p += sprintf(p, "-%s%s%s", (n & LIBCAP_EFF) ? "e" : "", (n & LIBCAP_INH) ? "i" : "", (n & LIBCAP_PER) ? "p" : ""); if (p - buf > CAP_TEXT_SIZE) { errno = ERANGE; return NULL; } } _cap_debug("%s", buf); if (length_p) { *length_p = p - buf; } return (_libcap_strdup(buf)); }