/*
 * Class and permission mappings.
 */

#include <errno.h>
#include <stdio.h>
#include <stdlib.h>
#include <stdarg.h>
#include <assert.h>
#include <selinux/selinux.h>
#include <selinux/avc.h>
#include "mapping.h"

/*
 * Class and permission mappings
 */

struct selinux_mapping {
	security_class_t value; /* real, kernel value */
	unsigned num_perms;
	access_vector_t perms[sizeof(access_vector_t) * 8];
};

static struct selinux_mapping *current_mapping = NULL;
static security_class_t current_mapping_size = 0;

/*
 * Mapping setting function
 */

int
selinux_set_mapping(struct security_class_mapping *map)
{
	size_t size = sizeof(struct selinux_mapping);
	security_class_t i, j;
	unsigned k;

	free(current_mapping);
	current_mapping = NULL;
	current_mapping_size = 0;

	if (avc_reset() < 0)
		goto err;

	/* Find number of classes in the input mapping */
	if (!map) {
		errno = EINVAL;
		goto err;
	}
	i = 0;
	while (map[i].name)
		i++;

	/* Allocate space for the class records, plus one for class zero */
	current_mapping = (struct selinux_mapping *)calloc(++i, size);
	if (!current_mapping)
		goto err;

	/* Store the raw class and permission values */
	j = 0;
	while (map[j].name) {
		struct security_class_mapping *p_in = map + (j++);
		struct selinux_mapping *p_out = current_mapping + j;

		p_out->value = string_to_security_class(p_in->name);
		if (!p_out->value)
			goto err2;

		k = 0;
		while (p_in->perms && p_in->perms[k]) {
			/* An empty permission string skips ahead */
			if (!*p_in->perms[k]) {
				k++;
				continue;
			}
			p_out->perms[k] = string_to_av_perm(p_out->value,
							    p_in->perms[k]);
			if (!p_out->perms[k])
				goto err2;
			k++;
		}
		p_out->num_perms = k;
	}

	/* Set the mapping size here so the above lookups are "raw" */
	current_mapping_size = i;
	return 0;
err2:
	free(current_mapping);
	current_mapping = NULL;
	current_mapping_size = 0;
err:
	return -1;
}

/*
 * Get real, kernel values from mapped values
 */

security_class_t
unmap_class(security_class_t tclass)
{
	if (tclass < current_mapping_size)
		return current_mapping[tclass].value;

	assert(current_mapping_size == 0);
	return tclass;
}

access_vector_t
unmap_perm(security_class_t tclass, access_vector_t tperm)
{
	if (tclass < current_mapping_size) {
		unsigned i;
		access_vector_t kperm = 0;

		for (i=0; i<current_mapping[tclass].num_perms; i++)
			if (tperm & (1<<i)) {
				assert(current_mapping[tclass].perms[i]);
				kperm |= current_mapping[tclass].perms[i];
				tperm &= ~(1<<i);
			}
		assert(tperm == 0);
		return kperm;
	}

	assert(current_mapping_size == 0);
	return tperm;
}

/*
 * Get mapped values from real, kernel values
 */

security_class_t
map_class(security_class_t kclass)
{
	security_class_t i;

	for (i=0; i<current_mapping_size; i++)
		if (current_mapping[i].value == kclass)
			return i;

	assert(current_mapping_size == 0);
	return kclass;
}

access_vector_t
map_perm(security_class_t tclass, access_vector_t kperm)
{
	if (tclass < current_mapping_size) {
		unsigned i;
		access_vector_t tperm = 0;

		for (i=0; i<current_mapping[tclass].num_perms; i++)
			if (kperm & current_mapping[tclass].perms[i]) {
				tperm |= 1<<i;
				kperm &= ~current_mapping[tclass].perms[i];
			}
		assert(kperm == 0);
		return tperm;
	}

	assert(current_mapping_size == 0);
	return kperm;
}

void
map_decision(security_class_t tclass, struct av_decision *avd)
{
	if (tclass < current_mapping_size) {
		unsigned i;
		access_vector_t result;

		for (i=0, result=0; i<current_mapping[tclass].num_perms; i++)
			if (avd->allowed & current_mapping[tclass].perms[i])
				result |= 1<<i;
		avd->allowed = result;

		for (i=0, result=0; i<current_mapping[tclass].num_perms; i++)
			if (avd->decided & current_mapping[tclass].perms[i])
				result |= 1<<i;
		avd->decided = result;

		for (i=0, result=0; i<current_mapping[tclass].num_perms; i++)
			if (avd->auditallow & current_mapping[tclass].perms[i])
				result |= 1<<i;
		avd->auditallow = result;

		for (i=0, result=0; i<current_mapping[tclass].num_perms; i++)
			if (avd->auditdeny & current_mapping[tclass].perms[i])
				result |= 1<<i;
		avd->auditdeny = result;
	}
}
