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

#include "context_internal.h"
#include "debug.h"
#include "log.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) {
		LOGD("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) {
		LOGD("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) {
		LOGD("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) {
		LOGD("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) {
		LOGD("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:
	LOGD("out of memory");

      err:
	LOGD("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) {
		LOGD("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;
	LOGD("malformed context \"%s\"", str);

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

hidden_def(sepol_context_from_string)

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

	int rc;
	const int user_sz = strlen(con->user);
	const int role_sz = strlen(con->role);
	const int type_sz = strlen(con->type);
	const int mls_sz = (con->mls) ? strlen(con->mls) : 0;
	const int total_sz = user_sz + role_sz + type_sz +
	    mls_sz + ((con->mls) ? 3 : 2);

	char *str = (char *)malloc(total_sz + 1);
	if (!str)
		goto omem;

	if (con->mls) {
		rc = snprintf(str, total_sz + 1, "%s:%s:%s:%s",
			      con->user, con->role, con->type, con->mls);
		if (rc < 0 || (rc >= total_sz + 1)) {
			LOGD("print error");
			goto err;
		}
	} else {
		rc = snprintf(str, total_sz + 1, "%s:%s:%s",
			      con->user, con->role, con->type);
		if (rc < 0 || (rc >= total_sz + 1)) {
			LOGD("print error");
			goto err;
		}
	}

	*str_ptr = str;
	return STATUS_SUCCESS;

      omem:
	LOGD("out of memory");

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