| /* |
| * Copyright (c) 1997-8,2007-8 Andrew G Morgan <morgan@kernel.org> |
| * Copyright (c) 1997 Andrew Main <zefram@dcs.warwick.ac.uk> |
| * |
| * This file deals with exchanging internal and textual |
| * representations of capability sets. |
| */ |
| |
| #define _GNU_SOURCE |
| #include <stdio.h> |
| |
| #define LIBCAP_PLEASE_INCLUDE_ARRAY |
| #include "libcap.h" |
| |
| #include <ctype.h> |
| #include <limits.h> |
| |
| /* 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)); |
| } |