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

#include "context_internal.h"
#include "debug.h"
#include "private.h"

struct sepol_context {

	/* Selinux user */
	char *user;

	/* Selinux role */
	char *role;

	/* Selinux type */
	char *type;

	/* MLS */
	char *mls;
};

/* User */
const char *sepol_context_get_user(const sepol_context_t * con)
{

	return con->user;
}

hidden_def(sepol_context_get_user)

int sepol_context_set_user(sepol_handle_t * handle,
			   sepol_context_t * con, const char *user)
{

	char *tmp_user = strdup(user);
	if (!tmp_user) {
		ERR(handle, "out of memory, could not set "
		    "context user to %s", user);
		return STATUS_ERR;
	}

	free(con->user);
	con->user = tmp_user;
	return STATUS_SUCCESS;
}

hidden_def(sepol_context_set_user)

/* Role */
const char *sepol_context_get_role(const sepol_context_t * con)
{

	return con->role;
}

hidden_def(sepol_context_get_role)

int sepol_context_set_role(sepol_handle_t * handle,
			   sepol_context_t * con, const char *role)
{

	char *tmp_role = strdup(role);
	if (!tmp_role) {
		ERR(handle, "out of memory, could not set "
		    "context role to %s", role);
		return STATUS_ERR;
	}
	free(con->role);
	con->role = tmp_role;
	return STATUS_SUCCESS;
}

hidden_def(sepol_context_set_role)

/* Type */
const char *sepol_context_get_type(const sepol_context_t * con)
{

	return con->type;
}

hidden_def(sepol_context_get_type)

int sepol_context_set_type(sepol_handle_t * handle,
			   sepol_context_t * con, const char *type)
{

	char *tmp_type = strdup(type);
	if (!tmp_type) {
		ERR(handle, "out of memory, could not set "
		    "context type to %s", type);
		return STATUS_ERR;
	}
	free(con->type);
	con->type = tmp_type;
	return STATUS_SUCCESS;
}

hidden_def(sepol_context_set_type)

/* MLS */
const char *sepol_context_get_mls(const sepol_context_t * con)
{

	return con->mls;
}

hidden_def(sepol_context_get_mls)

int sepol_context_set_mls(sepol_handle_t * handle,
			  sepol_context_t * con, const char *mls)
{

	char *tmp_mls = strdup(mls);
	if (!tmp_mls) {
		ERR(handle, "out of memory, could not set "
		    "MLS fields to %s", mls);
		return STATUS_ERR;
	}
	free(con->mls);
	con->mls = tmp_mls;
	return STATUS_SUCCESS;
}

hidden_def(sepol_context_set_mls)

/* Create */
int sepol_context_create(sepol_handle_t * handle, sepol_context_t ** con_ptr)
{

	sepol_context_t *con =
	    (sepol_context_t *) malloc(sizeof(sepol_context_t));

	if (!con) {
		ERR(handle, "out of memory, could not " "create context\n");
		return STATUS_ERR;
	}

	con->user = NULL;
	con->role = NULL;
	con->type = NULL;
	con->mls = NULL;
	*con_ptr = con;
	return STATUS_SUCCESS;
}

hidden_def(sepol_context_create)

/* Deep copy clone */
int sepol_context_clone(sepol_handle_t * handle,
			const sepol_context_t * con, sepol_context_t ** con_ptr)
{

	sepol_context_t *new_con = NULL;

	if (!con) {
		*con_ptr = NULL;
		return 0;
	}
	  
	if (sepol_context_create(handle, &new_con) < 0)
		goto err;

	if (!(new_con->user = strdup(con->user)))
		goto omem;

	if (!(new_con->role = strdup(con->role)))
		goto omem;

	if (!(new_con->type = strdup(con->type)))
		goto omem;

	if (con->mls && !(new_con->mls = strdup(con->mls)))
		goto omem;

	*con_ptr = new_con;
	return STATUS_SUCCESS;

      omem:
	ERR(handle, "out of memory");

      err:
	ERR(handle, "could not clone context record");
	sepol_context_free(new_con);
	return STATUS_ERR;
}

hidden_def(sepol_context_clone)

/* Destroy */
void sepol_context_free(sepol_context_t * con)
{

	if (!con)
		return;

	free(con->user);
	free(con->role);
	free(con->type);
	free(con->mls);
	free(con);
}

hidden_def(sepol_context_free)

int sepol_context_from_string(sepol_handle_t * handle,
			      const char *str, sepol_context_t ** con)
{

	char *tmp = NULL, *low, *high;
	sepol_context_t *tmp_con = NULL;

	if (!strcmp(str, "<<none>>")) {
		*con = NULL;
		return STATUS_SUCCESS;
	}

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

	/* Working copy context */
	tmp = strdup(str);
	if (!tmp) {
		ERR(handle, "out of memory");
		goto err;
	}
	low = tmp;

	/* Then, break it into its components */

	/* User */
	if (!(high = strchr(low, ':')))
		goto mcontext;
	else
		*high++ = '\0';
	if (sepol_context_set_user(handle, tmp_con, low) < 0)
		goto err;
	low = high;

	/* Role */
	if (!(high = strchr(low, ':')))
		goto mcontext;
	else
		*high++ = '\0';
	if (sepol_context_set_role(handle, tmp_con, low) < 0)
		goto err;
	low = high;

	/* Type, and possibly MLS */
	if (!(high = strchr(low, ':'))) {
		if (sepol_context_set_type(handle, tmp_con, low) < 0)
			goto err;
	} else {
		*high++ = '\0';
		if (sepol_context_set_type(handle, tmp_con, low) < 0)
			goto err;
		low = high;
		if (sepol_context_set_mls(handle, tmp_con, low) < 0)
			goto err;
	}

	free(tmp);
	*con = tmp_con;

	return STATUS_SUCCESS;

      mcontext:
	errno = EINVAL;
	ERR(handle, "malformed context \"%s\"", str);

      err:
	ERR(handle, "could not construct context from string");
	free(tmp);
	sepol_context_free(tmp_con);
	return STATUS_ERR;
}

hidden_def(sepol_context_from_string)

static inline int safe_sum(size_t *sum, const size_t augends[], const size_t cnt) {

	size_t a, i;

	*sum = 0;
	for(i=0; i < cnt; i++) {
		/* sum should not be smaller than the addend */
		a = augends[i];
		*sum += a;
		if (*sum < a) {
			return i;
		}
	}

	return 0;
}

int sepol_context_to_string(sepol_handle_t * handle,
			    const sepol_context_t * con, char **str_ptr)
{

	int rc;
	char *str = NULL;
	size_t total_sz, err;
	const size_t sizes[] = {
			strlen(con->user),                 /* user length */
			strlen(con->role),                 /* role length */
			strlen(con->type),                 /* type length */
			(con->mls) ? strlen(con->mls) : 0, /* mls length */
			((con->mls) ? 3 : 2) + 1           /* mls has extra ":" also null byte */
	};

	err = safe_sum(&total_sz, sizes, ARRAY_SIZE(sizes));
	if (err) {
		ERR(handle, "invalid size, overflow at position: %zu", err);
		goto err;
	}

	str = (char *)malloc(total_sz);
	if (!str) {
		ERR(handle, "out of memory");
		goto err;
	}
	if (con->mls) {
		rc = snprintf(str, total_sz, "%s:%s:%s:%s",
			      con->user, con->role, con->type, con->mls);
	} else {
		rc = snprintf(str, total_sz, "%s:%s:%s",
			      con->user, con->role, con->type);
	}

	/*
	 * rc is >= 0 on the size_t cast and is safe to promote
	 * to an unsigned value.
	 */
	if (rc < 0 || (size_t)rc >= total_sz) {
		ERR(handle, "print error");
		goto err;
	}

	*str_ptr = str;
	return STATUS_SUCCESS;

      err:
	ERR(handle, "could not convert context to string");
	free(str);
	return STATUS_ERR;
}
