/*
 * Copyright (c) 1997-8,2021 Andrew G. Morgan <morgan@kernel.org>
 *
 * This file deals with exchanging internal and external
 * representations of capability sets.
 */

#include "libcap.h"

/*
 * External representation for capabilities. (exported as a fixed
 * length)
 */
#define CAP_EXT_MAGIC "\220\302\001\121"
#define CAP_EXT_MAGIC_SIZE 4
const static __u8 external_magic[CAP_EXT_MAGIC_SIZE+1] = CAP_EXT_MAGIC;

/*
 * This is the largest size libcap can currently export.
 * cap_size() may return something smaller depending on the
 * content of its argument cap_t.
 */
struct cap_ext_struct {
    __u8 magic[CAP_EXT_MAGIC_SIZE];
    __u8 length_of_capset;
    /*
     * note, we arrange these so the caps are stacked with byte-size
     * resolution
     */
    __u8 bytes[CAP_SET_SIZE][NUMBER_OF_CAP_SETS];
};

/*
 * minimum exported flag size: libcap2 has always exported with flags
 * this size.
 */
static size_t _libcap_min_ext_flag_size = CAP_SET_SIZE < 8 ? CAP_SET_SIZE : 8;

/*
 * return size of external capability set
 */
ssize_t cap_size(cap_t cap_d)
{
    if (good_cap_t(cap_d)) {
	size_t j, used;
	for (j=used=0; j<CAP_SET_SIZE; j+=sizeof(__u32)) {
	    int i;
	    __u32 val = 0;
	    for (i=0; i<NUMBER_OF_CAP_SETS; ++i) {
		val |= cap_d->u[j/sizeof(__u32)].flat[i];
	    }
	    if (val == 0) {
		continue;
	    }
	    if (val > 0x0000ffff) {
		if (val > 0x00ffffff) {
		    used = j+4;
		} else {
		    used = j+3;
		}
	    } else if (val > 0x000000ff) {
		used = j+2;
	    } else {
		used = j+1;
	    }
	}
	if (used < _libcap_min_ext_flag_size) {
	    used = _libcap_min_ext_flag_size;
	}
	return (ssize_t)(CAP_EXT_MAGIC_SIZE + 1+ NUMBER_OF_CAP_SETS * used);
    }
    return ssizeof(struct cap_ext_struct);
}

/*
 * Copy the internal (cap_d) capability set into an external
 * representation.  The external representation is portable to other
 * Linux architectures.
 */

ssize_t cap_copy_ext(void *cap_ext, cap_t cap_d, ssize_t length)
{
    struct cap_ext_struct *result = (struct cap_ext_struct *) cap_ext;
    ssize_t csz, len_set;
    int i;

    /* valid arguments? */
    if (!good_cap_t(cap_d) || cap_ext == NULL) {
	errno = EINVAL;
	return -1;
    }

    csz = cap_size(cap_d);
    if (csz > length) {
	errno = EINVAL;
	return -1;
    }
    len_set = (csz - (CAP_EXT_MAGIC_SIZE+1))/NUMBER_OF_CAP_SETS;

    /* fill external capability set */
    memcpy(&result->magic, external_magic, CAP_EXT_MAGIC_SIZE);
    result->length_of_capset = len_set;

    for (i=0; i<NUMBER_OF_CAP_SETS; ++i) {
	size_t j;
	for (j=0; j<len_set; ) {
	    __u32 val;

	    val = cap_d->u[j/sizeof(__u32)].flat[i];

	    result->bytes[j++][i] =      val        & 0xFF;
	    if (j < len_set) {
		result->bytes[j++][i] = (val >>= 8) & 0xFF;
	    }
	    if (j < len_set) {
		result->bytes[j++][i] = (val >>= 8) & 0xFF;
	    }
	    if (j < len_set) {
		result->bytes[j++][i] = (val >> 8)  & 0xFF;
	    }
	}
    }

    /* All done: return length of external representation */
    return csz;
}

/*
 * Import an external representation to produce an internal rep.
 * the internal rep should be liberated with cap_free().
 *
 * Note, this function assumes that cap_ext has a valid length. That
 * is, feeding garbage to this function will likely crash the program.
 */
cap_t cap_copy_int(const void *cap_ext)
{
    const struct cap_ext_struct *export =
	(const struct cap_ext_struct *) cap_ext;
    cap_t cap_d;
    int set, blen;

    /* Does the external representation make sense? */
    if ((export == NULL)
	|| memcmp(export->magic, external_magic, CAP_EXT_MAGIC_SIZE)) {
	errno = EINVAL;
	return NULL;
    }

    /* Obtain a new internal capability set */
    if (!(cap_d = cap_init()))
       return NULL;

    blen = export->length_of_capset;
    for (set=0; set<NUMBER_OF_CAP_SETS; ++set) {
	unsigned blk;
	int bno = 0;
	for (blk=0; blk<(CAP_SET_SIZE/sizeof(__u32)); ++blk) {
	    __u32 val = 0;

	    if (bno != blen)
		val  = export->bytes[bno++][set];
	    if (bno != blen)
		val |= export->bytes[bno++][set] << 8;
	    if (bno != blen)
		val |= export->bytes[bno++][set] << 16;
	    if (bno != blen)
		val |= export->bytes[bno++][set] << 24;

	    cap_d->u[blk].flat[set] = val;
	}
    }

    /* all done */
    return cap_d;
}

/*
 * This function is the same as cap_copy_int() although it requires an
 * extra argument that is the length of the cap_ext data. Before
 * running cap_copy_int() the function validates that length is
 * consistent with the stated length. It returns NULL on error.
 */
cap_t cap_copy_int_check(const void *cap_ext, ssize_t length)
{
    const struct cap_ext_struct *export =
	(const struct cap_ext_struct *) cap_ext;

    if (length < 1+CAP_EXT_MAGIC_SIZE) {
	errno = EINVAL;
	return NULL;
    }
    if (length < 1+CAP_EXT_MAGIC_SIZE + export->length_of_capset * NUMBER_OF_CAP_SETS) {
	errno = EINVAL;
	return NULL;
    }
    return cap_copy_int(cap_ext);
}
