#include <stdlib.h>
#include <string.h>
#include <errno.h>

#include <sepol/policydb/policydb.h>
#include <sepol/policydb/services.h>
#include "context_internal.h"

#include "debug.h"
#include "context.h"
#include "handle.h"
#include "mls.h"
#include "private.h"

/* ----- Compatibility ---- */
int policydb_context_isvalid(const policydb_t * p, const context_struct_t * c)
{

	return context_is_valid(p, c);
}

int sepol_check_context(const char *context)
{

	return sepol_context_to_sid((const sepol_security_context_t)context,
				    strlen(context) + 1, NULL);
}

/* ---- End compatibility --- */

/*
 * Return 1 if the fields in the security context
 * structure `c' are valid.  Return 0 otherwise.
 */
int context_is_valid(const policydb_t * p, const context_struct_t * c)
{

	role_datum_t *role;
	user_datum_t *usrdatum;
	ebitmap_t types, roles;
	int ret = 1;

	ebitmap_init(&types);
	ebitmap_init(&roles);
	if (!c->role || c->role > p->p_roles.nprim)
		return 0;

	if (!c->user || c->user > p->p_users.nprim)
		return 0;

	if (!c->type || c->type > p->p_types.nprim)
		return 0;

	if (c->role != OBJECT_R_VAL) {
		/*
		 * Role must be authorized for the type.
		 */
		role = p->role_val_to_struct[c->role - 1];
		if (!role || !ebitmap_get_bit(&role->cache, c->type - 1))
			/* role may not be associated with type */
			return 0;

		/*
		 * User must be authorized for the role.
		 */
		usrdatum = p->user_val_to_struct[c->user - 1];
		if (!usrdatum)
			return 0;

		if (!ebitmap_get_bit(&usrdatum->cache, c->role - 1))
			/* user may not be associated with role */
			return 0;
	}

	if (!mls_context_isvalid(p, c))
		return 0;

	return ret;
}

/*
 * Write the security context string representation of
 * the context structure `context' into a dynamically
 * allocated string of the correct size.  Set `*scontext'
 * to point to this string and set `*scontext_len' to
 * the length of the string.
 */
int context_to_string(sepol_handle_t * handle,
		      const policydb_t * policydb,
		      const context_struct_t * context,
		      char **result, size_t * result_len)
{

	char *scontext = NULL;
	size_t scontext_len = 0;
	char *ptr;

	/* Compute the size of the context. */
	scontext_len +=
	    strlen(policydb->p_user_val_to_name[context->user - 1]) + 1;
	scontext_len +=
	    strlen(policydb->p_role_val_to_name[context->role - 1]) + 1;
	scontext_len += strlen(policydb->p_type_val_to_name[context->type - 1]);
	scontext_len += mls_compute_context_len(policydb, context);

	/* We must null terminate the string */
	scontext_len += 1;

	/* Allocate space for the context; caller must free this space. */
	scontext = malloc(scontext_len);
	if (!scontext)
		goto omem;
	scontext[scontext_len - 1] = '\0';

	/*
	 * Copy the user name, role name and type name into the context.
	 */
	ptr = scontext;
	sprintf(ptr, "%s:%s:%s",
		policydb->p_user_val_to_name[context->user - 1],
		policydb->p_role_val_to_name[context->role - 1],
		policydb->p_type_val_to_name[context->type - 1]);

	ptr +=
	    strlen(policydb->p_user_val_to_name[context->user - 1]) + 1 +
	    strlen(policydb->p_role_val_to_name[context->role - 1]) + 1 +
	    strlen(policydb->p_type_val_to_name[context->type - 1]);

	mls_sid_to_context(policydb, context, &ptr);

	*result = scontext;
	*result_len = scontext_len;
	return STATUS_SUCCESS;

      omem:
	ERR(handle, "out of memory, could not convert " "context to string");
	free(scontext);
	return STATUS_ERR;
}

/*
 * Create a context structure from the given record
 */
int context_from_record(sepol_handle_t * handle,
			const policydb_t * policydb,
			context_struct_t ** cptr,
			const sepol_context_t * record)
{

	context_struct_t *scontext = NULL;
	user_datum_t *usrdatum;
	role_datum_t *roldatum;
	type_datum_t *typdatum;

	/* Hashtab keys are not constant - suppress warnings */
	char *user = strdup(sepol_context_get_user(record));
	char *role = strdup(sepol_context_get_role(record));
	char *type = strdup(sepol_context_get_type(record));
	const char *mls = sepol_context_get_mls(record);

	scontext = (context_struct_t *) malloc(sizeof(context_struct_t));
	if (!user || !role || !type || !scontext) {
		ERR(handle, "out of memory");
		goto err;
	}
	context_init(scontext);

	/* User */
	usrdatum = (user_datum_t *) hashtab_search(policydb->p_users.table,
						   (hashtab_key_t) user);
	if (!usrdatum) {
		ERR(handle, "user %s is not defined", user);
		goto err_destroy;
	}
	scontext->user = usrdatum->s.value;

	/* Role */
	roldatum = (role_datum_t *) hashtab_search(policydb->p_roles.table,
						   (hashtab_key_t) role);
	if (!roldatum) {
		ERR(handle, "role %s is not defined", role);
		goto err_destroy;
	}
	scontext->role = roldatum->s.value;

	/* Type */
	typdatum = (type_datum_t *) hashtab_search(policydb->p_types.table,
						   (hashtab_key_t) type);
	if (!typdatum || typdatum->flavor == TYPE_ATTRIB) {
		ERR(handle, "type %s is not defined", type);
		goto err_destroy;
	}
	scontext->type = typdatum->s.value;

	/* MLS */
	if (mls && !policydb->mls) {
		ERR(handle, "MLS is disabled, but MLS context \"%s\" found",
		    mls);
		goto err_destroy;
	} else if (!mls && policydb->mls) {
		ERR(handle, "MLS is enabled, but no MLS context found");
		goto err_destroy;
	}
	if (mls && (mls_from_string(handle, policydb, mls, scontext) < 0))
		goto err_destroy;

	/* Validity check */
	if (!context_is_valid(policydb, scontext)) {
		if (mls) {
			ERR(handle,
			    "invalid security context: \"%s:%s:%s:%s\"",
			    user, role, type, mls);
		} else {
			ERR(handle,
			    "invalid security context: \"%s:%s:%s\"",
			    user, role, type);
		}
		goto err_destroy;
	}

	*cptr = scontext;
	free(user);
	free(type);
	free(role);
	return STATUS_SUCCESS;

      err_destroy:
	errno = EINVAL;
	context_destroy(scontext);

      err:
	free(scontext);
	free(user);
	free(type);
	free(role);
	ERR(handle, "could not create context structure");
	return STATUS_ERR;
}

/*
 * Create a record from the given context structure
 */
int context_to_record(sepol_handle_t * handle,
		      const policydb_t * policydb,
		      const context_struct_t * context,
		      sepol_context_t ** record)
{

	sepol_context_t *tmp_record = NULL;
	char *mls = NULL;

	if (sepol_context_create(handle, &tmp_record) < 0)
		goto err;

	if (sepol_context_set_user(handle, tmp_record,
				   policydb->p_user_val_to_name[context->user -
								1]) < 0)
		goto err;

	if (sepol_context_set_role(handle, tmp_record,
				   policydb->p_role_val_to_name[context->role -
								1]) < 0)
		goto err;

	if (sepol_context_set_type(handle, tmp_record,
				   policydb->p_type_val_to_name[context->type -
								1]) < 0)
		goto err;

	if (policydb->mls) {
		if (mls_to_string(handle, policydb, context, &mls) < 0)
			goto err;

		if (sepol_context_set_mls(handle, tmp_record, mls) < 0)
			goto err;
	}

	free(mls);
	*record = tmp_record;
	return STATUS_SUCCESS;

      err:
	ERR(handle, "could not create context record");
	sepol_context_free(tmp_record);
	free(mls);
	return STATUS_ERR;
}

/*
 * Create a context structure from the provided string.
 */
int context_from_string(sepol_handle_t * handle,
			const policydb_t * policydb,
			context_struct_t ** cptr,
			const char *con_str, size_t con_str_len)
{

	char *con_cpy = NULL;
	sepol_context_t *ctx_record = NULL;

	if (zero_or_saturated(con_str_len)) {
		ERR(handle, "Invalid context length");
		goto err;
	}

	/* sepol_context_from_string expects a NULL-terminated string */
	con_cpy = malloc(con_str_len + 1);
	if (!con_cpy) {
		ERR(handle, "out of memory");
		goto err;
	}

	memcpy(con_cpy, con_str, con_str_len);
	con_cpy[con_str_len] = '\0';

	if (sepol_context_from_string(handle, con_cpy, &ctx_record) < 0)
		goto err;

	/* Now create from the data structure */
	if (context_from_record(handle, policydb, cptr, ctx_record) < 0)
		goto err;

	free(con_cpy);
	sepol_context_free(ctx_record);
	return STATUS_SUCCESS;

      err:
	ERR(handle, "could not create context structure");
	free(con_cpy);
	sepol_context_free(ctx_record);
	return STATUS_ERR;
}

int sepol_context_check(sepol_handle_t * handle,
			const sepol_policydb_t * policydb,
			const sepol_context_t * context)
{

	context_struct_t *con = NULL;
	int ret = context_from_record(handle, &policydb->p, &con, context);
	context_destroy(con);
	free(con);
	return ret;
}
