#include <netinet/in.h>
#include <arpa/inet.h>
#include <stdlib.h>

#include "debug.h"
#include "context.h"
#include "handle.h"
#include "log.h"

#include <sepol/policydb/policydb.h>
#include "node_internal.h"

/* Create a low level node structure from
 * a high level representation */
static int node_from_record(sepol_handle_t * handle,
			    const policydb_t * policydb,
			    ocontext_t ** node, const sepol_node_t * data)
{

	ocontext_t *tmp_node = NULL;
	context_struct_t *tmp_con = NULL;
	char *addr_buf = NULL, *mask_buf = NULL;

	tmp_node = (ocontext_t *) calloc(1, sizeof(ocontext_t));
	if (!tmp_node)
		goto omem;

	size_t addr_bsize, mask_bsize;

	/* Address and netmask */
	if (sepol_node_get_addr_bytes(handle, data, &addr_buf, &addr_bsize) < 0)
		goto err;
	if (sepol_node_get_mask_bytes(handle, data, &mask_buf, &mask_bsize) < 0)
		goto err;

	int proto = sepol_node_get_proto(data);

	switch (proto) {
	case SEPOL_PROTO_IP4:
		memcpy(&tmp_node->u.node.addr, addr_buf, addr_bsize);
		memcpy(&tmp_node->u.node.mask, mask_buf, mask_bsize);
		break;
	case SEPOL_PROTO_IP6:
		memcpy(tmp_node->u.node6.addr, addr_buf, addr_bsize);
		memcpy(tmp_node->u.node6.mask, mask_buf, mask_bsize);
		break;
	default:
		LOGD( "unsupported protocol %u", proto);
		goto err;
	}
	free(addr_buf);
	free(mask_buf);
	addr_buf = NULL;
	mask_buf = NULL;

	/* Context */
	if (context_from_record(handle, policydb, &tmp_con,
				sepol_node_get_con(data)) < 0)
		goto err;
	context_cpy(&tmp_node->context[0], tmp_con);
	context_destroy(tmp_con);
	free(tmp_con);
	tmp_con = NULL;

	*node = tmp_node;
	return STATUS_SUCCESS;

      omem:
	LOGD( "out of memory");

      err:
	if (tmp_node != NULL) {
		context_destroy(&tmp_node->context[0]);
		free(tmp_node);
	}
	context_destroy(tmp_con);
	free(tmp_con);
	free(addr_buf);
	free(mask_buf);
	LOGD( "could not create node structure");
	return STATUS_ERR;
}

static int node_to_record(sepol_handle_t * handle,
			  const policydb_t * policydb,
			  ocontext_t * node, int proto, sepol_node_t ** record)
{

	context_struct_t *con = &node->context[0];

	sepol_context_t *tmp_con = NULL;
	sepol_node_t *tmp_record = NULL;

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

	sepol_node_set_proto(tmp_record, proto);

	switch (proto) {

	case SEPOL_PROTO_IP4:
		if (sepol_node_set_addr_bytes(handle, tmp_record,
					      (const char *)&node->u.node.addr,
					      4) < 0)
			goto err;

		if (sepol_node_set_mask_bytes(handle, tmp_record,
					      (const char *)&node->u.node.mask,
					      4) < 0)
			goto err;
		break;

	case SEPOL_PROTO_IP6:
		if (sepol_node_set_addr_bytes(handle, tmp_record,
					      (const char *)&node->u.node6.addr,
					      16) < 0)
			goto err;

		if (sepol_node_set_mask_bytes(handle, tmp_record,
					      (const char *)&node->u.node6.mask,
					      16) < 0)
			goto err;
		break;

	default:
		LOGD( "unsupported protocol %u", proto);
		goto err;
	}

	if (context_to_record(handle, policydb, con, &tmp_con) < 0)
		goto err;

	if (sepol_node_set_con(handle, tmp_record, tmp_con) < 0)
		goto err;

	sepol_context_free(tmp_con);
	*record = tmp_record;
	return STATUS_SUCCESS;

      err:
	LOGD( "could not convert node to record");
	sepol_context_free(tmp_con);
	sepol_node_free(tmp_record);
	return STATUS_ERR;
}

/* Return the number of nodes */
extern int sepol_node_count(sepol_handle_t * handle __attribute__ ((unused)),
			    const sepol_policydb_t * p, unsigned int *response)
{

	unsigned int count = 0;
	ocontext_t *c, *head;
	const policydb_t *policydb = &p->p;

	head = policydb->ocontexts[OCON_NODE];
	for (c = head; c != NULL; c = c->next)
		count++;

	head = policydb->ocontexts[OCON_NODE6];
	for (c = head; c != NULL; c = c->next)
		count++;

	*response = count;

	handle = NULL;
	return STATUS_SUCCESS;
}

/* Check if a node exists */
int sepol_node_exists(sepol_handle_t * handle,
		      const sepol_policydb_t * p,
		      const sepol_node_key_t * key, int *response)
{

	const policydb_t *policydb = &p->p;
	ocontext_t *c, *head;

	int proto;
	const char *addr, *mask;
	sepol_node_key_unpack(key, &addr, &mask, &proto);

	switch (proto) {

	case SEPOL_PROTO_IP4:
		{
			head = policydb->ocontexts[OCON_NODE];
			for (c = head; c; c = c->next) {
				unsigned int *addr2 = &c->u.node.addr;
				unsigned int *mask2 = &c->u.node.mask;

				if (!memcmp(addr, addr2, 4) &&
				    !memcmp(mask, mask2, 4)) {

					*response = 1;
					return STATUS_SUCCESS;
				}
			}
			break;
		}
	case SEPOL_PROTO_IP6:
		{
			head = policydb->ocontexts[OCON_NODE6];
			for (c = head; c; c = c->next) {
				unsigned int *addr2 = c->u.node6.addr;
				unsigned int *mask2 = c->u.node6.mask;

				if (!memcmp(addr, addr2, 16) &&
				    !memcmp(mask, mask2, 16)) {
					*response = 1;
					return STATUS_SUCCESS;
				}
			}
			break;
		}
	default:
		LOGD( "unsupported protocol %u", proto);
		goto err;
	}

	*response = 0;
	return STATUS_SUCCESS;

      err:
	LOGD( "could not check if node %s/%s (%s) exists",
	    addr, mask, sepol_node_get_proto_str(proto));
	return STATUS_ERR;
}

/* Query a node */
int sepol_node_query(sepol_handle_t * handle,
		     const sepol_policydb_t * p,
		     const sepol_node_key_t * key, sepol_node_t ** response)
{

	const policydb_t *policydb = &p->p;
	ocontext_t *c, *head;

	int proto;
	const char *addr, *mask;
	sepol_node_key_unpack(key, &addr, &mask, &proto);

	switch (proto) {

	case SEPOL_PROTO_IP4:
		{
			head = policydb->ocontexts[OCON_NODE];
			for (c = head; c; c = c->next) {
				unsigned int *addr2 = &c->u.node.addr;
				unsigned int *mask2 = &c->u.node.mask;

				if (!memcmp(addr, addr2, 4) &&
				    !memcmp(mask, mask2, 4)) {

					if (node_to_record(handle, policydb,
							   c, SEPOL_PROTO_IP4,
							   response) < 0)
						goto err;
					return STATUS_SUCCESS;
				}
			}
			break;
		}
	case SEPOL_PROTO_IP6:
		{
			head = policydb->ocontexts[OCON_NODE6];
			for (c = head; c; c = c->next) {
				unsigned int *addr2 = c->u.node6.addr;
				unsigned int *mask2 = c->u.node6.mask;

				if (!memcmp(addr, addr2, 16) &&
				    !memcmp(mask, mask2, 16)) {

					if (node_to_record(handle, policydb,
							   c, SEPOL_PROTO_IP6,
							   response) < 0)
						goto err;
				}
			}
			break;
		}
	default:
		LOGD( "unsupported protocol %u", proto);
		goto err;
	}
	*response = NULL;
	return STATUS_SUCCESS;

      err:
	LOGD( "could not query node %s/%s (%s)",
	    addr, mask, sepol_node_get_proto_str(proto));
	return STATUS_ERR;

}

/* Load a node into policy */
int sepol_node_modify(sepol_handle_t * handle,
		      sepol_policydb_t * p,
		      const sepol_node_key_t * key, const sepol_node_t * data)
{

	policydb_t *policydb = &p->p;
	ocontext_t *node = NULL;

	int proto;
	const char *addr, *mask;

	sepol_node_key_unpack(key, &addr, &mask, &proto);

	if (node_from_record(handle, policydb, &node, data) < 0)
		goto err;

	switch (proto) {

	case SEPOL_PROTO_IP4:
		{
			/* Attach to context list */
			node->next = policydb->ocontexts[OCON_NODE];
			policydb->ocontexts[OCON_NODE] = node;
			break;
		}
	case SEPOL_PROTO_IP6:
		{
			/* Attach to context list */
			node->next = policydb->ocontexts[OCON_NODE6];
			policydb->ocontexts[OCON_NODE6] = node;
			break;
		}
	default:
		LOGD( "unsupported protocol %u", proto);
		goto err;
	}

	return STATUS_SUCCESS;

      err:
	LOGD( "could not load node %s/%s (%s)",
	    addr, mask, sepol_node_get_proto_str(proto));
	if (node != NULL) {
		context_destroy(&node->context[0]);
		free(node);
	}
	return STATUS_ERR;
}

int sepol_node_iterate(sepol_handle_t * handle,
		       const sepol_policydb_t * p,
		       int (*fn) (const sepol_node_t * node,
				  void *fn_arg), void *arg)
{

	const policydb_t *policydb = &p->p;
	ocontext_t *c, *head;
	sepol_node_t *node = NULL;
	int status;

	head = policydb->ocontexts[OCON_NODE];
	for (c = head; c; c = c->next) {
		if (node_to_record(handle, policydb, c, SEPOL_PROTO_IP4, &node)
		    < 0)
			goto err;

		/* Invoke handler */
		status = fn(node, arg);
		if (status < 0)
			goto err;

		sepol_node_free(node);
		node = NULL;

		/* Handler requested exit */
		if (status > 0)
			break;
	}

	head = policydb->ocontexts[OCON_NODE6];
	for (c = head; c; c = c->next) {
		if (node_to_record(handle, policydb, c, SEPOL_PROTO_IP6, &node)
		    < 0)
			goto err;

		/* Invoke handler */
		status = fn(node, arg);
		if (status < 0)
			goto err;

		sepol_node_free(node);
		node = NULL;

		/* Handler requested exit */
		if (status > 0)
			break;
	}

	return STATUS_SUCCESS;

      err:
	LOGD( "could not iterate over nodes");
	sepol_node_free(node);
	return STATUS_ERR;
}
