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

#include "node_internal.h"
#include "context_internal.h"
#include "debug.h"
#include "log.h"

struct sepol_node {

	/* Network address and mask */
	char *addr;
	size_t addr_sz;

	char *mask;
	size_t mask_sz;

	/* Protocol */
	int proto;

	/* Context */
	sepol_context_t *con;
};

struct sepol_node_key {

	/* Network address and mask */
	char *addr;
	size_t addr_sz;

	char *mask;
	size_t mask_sz;

	/* Protocol */
	int proto;
};

/* Converts a string represtation (addr_str)
 * to a numeric representation (addr_bytes) */

static int node_parse_addr(sepol_handle_t * handle,
			   const char *addr_str, int proto, char *addr_bytes)
{

	switch (proto) {

	case SEPOL_PROTO_IP4:
		{
			struct in_addr in_addr;

			if (inet_pton(AF_INET, addr_str, &in_addr) <= 0) {
				LOGD( "could not parse IPv4 address "
				    "%s: %s", addr_str, strerror(errno));
				return STATUS_ERR;
			}

			memcpy(addr_bytes, &in_addr.s_addr, 4);
			break;
		}
	case SEPOL_PROTO_IP6:
		{
			struct in6_addr in_addr;

			if (inet_pton(AF_INET6, addr_str, &in_addr) <= 0) {
				LOGD( "could not parse IPv6 address "
				    "%s: %s", addr_str, strerror(errno));
				return STATUS_ERR;
			}

#ifdef DARWIN
			memcpy(addr_bytes, in_addr.s6_addr, 16);
#else
			memcpy(addr_bytes, in_addr.s6_addr32, 16);
#endif
			break;
		}
	default:
		LOGD( "unsupported protocol %u, could not "
		    "parse address", proto);
		return STATUS_ERR;
	}

	return STATUS_SUCCESS;
}

/* Allocates a sufficiently large buffer (addr, addr_sz)
 * according the the protocol */

static int node_alloc_addr(sepol_handle_t * handle,
			   int proto, char **addr, size_t * addr_sz)
{

	char *tmp_addr = NULL;
	size_t tmp_addr_sz;

	switch (proto) {

	case SEPOL_PROTO_IP4:
		tmp_addr_sz = 4;
		tmp_addr = malloc(4);
		if (!tmp_addr)
			goto omem;
		break;

	case SEPOL_PROTO_IP6:
		tmp_addr_sz = 16;
		tmp_addr = malloc(16);
		if (!tmp_addr)
			goto omem;
		break;

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

	*addr = tmp_addr;
	*addr_sz = tmp_addr_sz;
	return STATUS_SUCCESS;

      omem:
	LOGD( "out of memory");

      err:
	free(tmp_addr);
	LOGD( "could not allocate address of protocol %s",
	    sepol_node_get_proto_str(proto));
	return STATUS_ERR;
}

/* Converts a numeric representation (addr_bytes)
 * to a string representation (addr_str), according to 
 * the protocol */

static int node_expand_addr(sepol_handle_t * handle,
			    char *addr_bytes, int proto, char *addr_str)
{

	switch (proto) {

	case SEPOL_PROTO_IP4:
		{
			struct in_addr addr;
			memset(&addr, 0, sizeof(struct in_addr));
			memcpy(&addr.s_addr, addr_bytes, 4);

			if (inet_ntop(AF_INET, &addr, addr_str,
				      INET_ADDRSTRLEN) == NULL) {

				LOGD(
				    "could not expand IPv4 address to string: %s",
				    strerror(errno));
				return STATUS_ERR;
			}
			break;
		}

	case SEPOL_PROTO_IP6:
		{
			struct in6_addr addr;
			memset(&addr, 0, sizeof(struct in6_addr));
#ifdef DARWIN
			memcpy(&addr.s6_addr[0], addr_bytes, 16);
#else
			memcpy(&addr.s6_addr32[0], addr_bytes, 16);
#endif
			if (inet_ntop(AF_INET6, &addr, addr_str,
				      INET6_ADDRSTRLEN) == NULL) {

				LOGD(
				    "could not expand IPv6 address to string: %s",
				    strerror(errno));
				return STATUS_ERR;
			}
			break;
		}

	default:
		LOGD( "unsupported protocol %u, could not"
		    " expand address to string", proto);
		return STATUS_ERR;
	}

	return STATUS_SUCCESS;
}

/* Allocates a sufficiently large address string (addr)
 * according to the protocol */

static int node_alloc_addr_string(sepol_handle_t * handle,
				  int proto, char **addr)
{

	char *tmp_addr = NULL;

	switch (proto) {

	case SEPOL_PROTO_IP4:
		tmp_addr = malloc(INET_ADDRSTRLEN);
		if (!tmp_addr)
			goto omem;
		break;

	case SEPOL_PROTO_IP6:
		tmp_addr = malloc(INET6_ADDRSTRLEN);
		if (!tmp_addr)
			goto omem;
		break;

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

	*addr = tmp_addr;
	return STATUS_SUCCESS;

      omem:
	LOGD( "out of memory");

      err:
	free(tmp_addr);
	LOGD( "could not allocate string buffer for "
	    "address of protocol %s", sepol_node_get_proto_str(proto));
	return STATUS_ERR;
}

/* Key */
int sepol_node_key_create(sepol_handle_t * handle,
			  const char *addr,
			  const char *mask,
			  int proto, sepol_node_key_t ** key_ptr)
{

	sepol_node_key_t *tmp_key =
	    (sepol_node_key_t *) calloc(1, sizeof(sepol_node_key_t));
	if (!tmp_key)
		goto omem;

	if (node_alloc_addr(handle, proto, &tmp_key->addr, &tmp_key->addr_sz) <
	    0)
		goto err;
	if (node_parse_addr(handle, addr, proto, tmp_key->addr) < 0)
		goto err;

	if (node_alloc_addr(handle, proto, &tmp_key->mask, &tmp_key->mask_sz) <
	    0)
		goto err;
	if (node_parse_addr(handle, mask, proto, tmp_key->mask) < 0)
		goto err;

	tmp_key->proto = proto;

	*key_ptr = tmp_key;
	return STATUS_SUCCESS;

      omem:
	LOGD( "out of memory");

      err:
	sepol_node_key_free(tmp_key);
	LOGD( "could not create node key for (%s, %s, %s)",
	    addr, mask, sepol_node_get_proto_str(proto));
	return STATUS_ERR;
}

hidden_def(sepol_node_key_create)

void sepol_node_key_unpack(const sepol_node_key_t * key,
			   const char **addr, const char **mask, int *proto)
{

	*addr = key->addr;
	*mask = key->mask;
	*proto = key->proto;
}

hidden_def(sepol_node_key_unpack)

int sepol_node_key_extract(sepol_handle_t * handle,
			   const sepol_node_t * node,
			   sepol_node_key_t ** key_ptr)
{

	sepol_node_key_t *tmp_key =
	    (sepol_node_key_t *) calloc(1, sizeof(sepol_node_key_t));
	if (!tmp_key)
		goto omem;

	tmp_key->addr = malloc(node->addr_sz);
	tmp_key->mask = malloc(node->mask_sz);

	if (!tmp_key->addr || !tmp_key->mask)
		goto omem;

	memcpy(tmp_key->addr, node->addr, node->addr_sz);
	memcpy(tmp_key->mask, node->mask, node->mask_sz);
	tmp_key->addr_sz = node->addr_sz;
	tmp_key->mask_sz = node->mask_sz;
	tmp_key->proto = node->proto;

	*key_ptr = tmp_key;
	return STATUS_SUCCESS;

      omem:
	sepol_node_key_free(tmp_key);
	LOGD( "out of memory, could not extract node key");
	return STATUS_ERR;
}

void sepol_node_key_free(sepol_node_key_t * key)
{

	if (!key)
		return;

	free(key->addr);
	free(key->mask);
	free(key);
}

hidden_def(sepol_node_key_free)

int sepol_node_compare(const sepol_node_t * node, const sepol_node_key_t * key)
{

	int rc1, rc2;

	if ((node->addr_sz < key->addr_sz) || (node->mask_sz < key->mask_sz))
		return -1;

	else if ((node->addr_sz > key->addr_sz) ||
		 (node->mask_sz > key->mask_sz))
		return 1;

	rc1 = memcmp(node->addr, key->addr, node->addr_sz);
	rc2 = memcmp(node->mask, key->mask, node->mask_sz);

	return (rc2 != 0) ? rc2 : rc1;
}

int sepol_node_compare2(const sepol_node_t * node, const sepol_node_t * node2)
{

	int rc1, rc2;

	if ((node->addr_sz < node2->addr_sz) ||
	    (node->mask_sz < node2->mask_sz))
		return -1;

	else if ((node->addr_sz > node2->addr_sz) ||
		 (node->mask_sz > node2->mask_sz))
		return 1;

	rc1 = memcmp(node->addr, node2->addr, node->addr_sz);
	rc2 = memcmp(node->mask, node2->mask, node->mask_sz);

	return (rc2 != 0) ? rc2 : rc1;
}

/* Addr */
int sepol_node_get_addr(sepol_handle_t * handle,
			const sepol_node_t * node, char **addr)
{

	char *tmp_addr = NULL;

	if (node_alloc_addr_string(handle, node->proto, &tmp_addr) < 0)
		goto err;

	if (node_expand_addr(handle, node->addr, node->proto, tmp_addr) < 0)
		goto err;

	*addr = tmp_addr;
	return STATUS_SUCCESS;

      err:
	free(tmp_addr);
	LOGD( "could not get node address");
	return STATUS_ERR;
}

hidden_def(sepol_node_get_addr)

int sepol_node_get_addr_bytes(sepol_handle_t * handle,
			      const sepol_node_t * node,
			      char **buffer, size_t * bsize)
{

	char *tmp_buf = malloc(node->addr_sz);
	if (!tmp_buf) {
		LOGD( "out of memory, could not get address bytes");
		return STATUS_ERR;
	}

	memcpy(tmp_buf, node->addr, node->addr_sz);
	*buffer = tmp_buf;
	*bsize = node->addr_sz;
	return STATUS_SUCCESS;
}

hidden_def(sepol_node_get_addr_bytes)

int sepol_node_set_addr(sepol_handle_t * handle,
			sepol_node_t * node, int proto, const char *addr)
{

	char *tmp_addr = NULL;
	size_t tmp_addr_sz;

	if (node_alloc_addr(handle, proto, &tmp_addr, &tmp_addr_sz) < 0)
		goto err;

	if (node_parse_addr(handle, addr, proto, tmp_addr) < 0)
		goto err;

	free(node->addr);
	node->addr = tmp_addr;
	node->addr_sz = tmp_addr_sz;
	return STATUS_SUCCESS;

      err:
	free(tmp_addr);
	LOGD( "could not set node address to %s", addr);
	return STATUS_ERR;
}

hidden_def(sepol_node_set_addr)

int sepol_node_set_addr_bytes(sepol_handle_t * handle,
			      sepol_node_t * node,
			      const char *addr, size_t addr_sz)
{

	char *tmp_addr = malloc(addr_sz);
	if (!tmp_addr) {
		LOGD( "out of memory, could not " "set node address");
		return STATUS_ERR;
	}

	memcpy(tmp_addr, addr, addr_sz);
	free(node->addr);
	node->addr = tmp_addr;
	node->addr_sz = addr_sz;
	return STATUS_SUCCESS;
}

hidden_def(sepol_node_set_addr_bytes)

/* Mask */
int sepol_node_get_mask(sepol_handle_t * handle,
			const sepol_node_t * node, char **mask)
{

	char *tmp_mask = NULL;

	if (node_alloc_addr_string(handle, node->proto, &tmp_mask) < 0)
		goto err;

	if (node_expand_addr(handle, node->mask, node->proto, tmp_mask) < 0)
		goto err;

	*mask = tmp_mask;
	return STATUS_SUCCESS;

      err:
	free(tmp_mask);
	LOGD( "could not get node netmask");
	return STATUS_ERR;
}

hidden_def(sepol_node_get_mask)

int sepol_node_get_mask_bytes(sepol_handle_t * handle,
			      const sepol_node_t * node,
			      char **buffer, size_t * bsize)
{

	char *tmp_buf = malloc(node->mask_sz);
	if (!tmp_buf) {
		LOGD( "out of memory, could not get netmask bytes");
		return STATUS_ERR;
	}

	memcpy(tmp_buf, node->mask, node->mask_sz);
	*buffer = tmp_buf;
	*bsize = node->mask_sz;
	return STATUS_SUCCESS;
}

hidden_def(sepol_node_get_mask_bytes)

int sepol_node_set_mask(sepol_handle_t * handle,
			sepol_node_t * node, int proto, const char *mask)
{

	char *tmp_mask = NULL;
	size_t tmp_mask_sz;

	if (node_alloc_addr(handle, proto, &tmp_mask, &tmp_mask_sz) < 0)
		goto err;

	if (node_parse_addr(handle, mask, proto, tmp_mask) < 0)
		goto err;

	free(node->mask);
	node->mask = tmp_mask;
	node->mask_sz = tmp_mask_sz;
	return STATUS_SUCCESS;

      err:
	free(tmp_mask);
	LOGD( "could not set node netmask to %s", mask);
	return STATUS_ERR;
}

hidden_def(sepol_node_set_mask)

int sepol_node_set_mask_bytes(sepol_handle_t * handle,
			      sepol_node_t * node,
			      const char *mask, size_t mask_sz)
{

	char *tmp_mask = malloc(mask_sz);
	if (!tmp_mask) {
		LOGD( "out of memory, could not " "set node netmask");
		return STATUS_ERR;
	}
	memcpy(tmp_mask, mask, mask_sz);
	free(node->mask);
	node->mask = tmp_mask;
	node->mask_sz = mask_sz;
	return STATUS_SUCCESS;
}

hidden_def(sepol_node_set_mask_bytes)

/* Protocol */
int sepol_node_get_proto(const sepol_node_t * node)
{

	return node->proto;
}

hidden_def(sepol_node_get_proto)

void sepol_node_set_proto(sepol_node_t * node, int proto)
{

	node->proto = proto;
}

hidden_def(sepol_node_set_proto)

const char *sepol_node_get_proto_str(int proto)
{

	switch (proto) {
	case SEPOL_PROTO_IP4:
		return "ipv4";
	case SEPOL_PROTO_IP6:
		return "ipv6";
	default:
		return "???";
	}
}

hidden_def(sepol_node_get_proto_str)

/* Create */
int sepol_node_create(sepol_handle_t * handle, sepol_node_t ** node)
{

	sepol_node_t *tmp_node = (sepol_node_t *) malloc(sizeof(sepol_node_t));

	if (!tmp_node) {
		LOGD( "out of memory, could not create " "node record");
		return STATUS_ERR;
	}

	tmp_node->addr = NULL;
	tmp_node->addr_sz = 0;
	tmp_node->mask = NULL;
	tmp_node->mask_sz = 0;
	tmp_node->proto = SEPOL_PROTO_IP4;
	tmp_node->con = NULL;
	*node = tmp_node;

	return STATUS_SUCCESS;
}

hidden_def(sepol_node_create)

/* Deep copy clone */
int sepol_node_clone(sepol_handle_t * handle,
		     const sepol_node_t * node, sepol_node_t ** node_ptr)
{

	sepol_node_t *new_node = NULL;
	if (sepol_node_create(handle, &new_node) < 0)
		goto err;

	/* Copy address, mask, protocol */
	new_node->addr = malloc(node->addr_sz);
	new_node->mask = malloc(node->mask_sz);
	if (!new_node->addr || !new_node->mask)
		goto omem;

	memcpy(new_node->addr, node->addr, node->addr_sz);
	memcpy(new_node->mask, node->mask, node->mask_sz);
	new_node->addr_sz = node->addr_sz;
	new_node->mask_sz = node->mask_sz;
	new_node->proto = node->proto;

	/* Copy context */
	if (node->con &&
	    (sepol_context_clone(handle, node->con, &new_node->con) < 0))
		goto err;

	*node_ptr = new_node;
	return STATUS_SUCCESS;

      omem:
	LOGD( "out of memory");

      err:
	LOGD( "could not clone node record");
	sepol_node_free(new_node);
	return STATUS_ERR;
}

/* Destroy */
void sepol_node_free(sepol_node_t * node)
{

	if (!node)
		return;

	sepol_context_free(node->con);
	free(node->addr);
	free(node->mask);
	free(node);
}

hidden_def(sepol_node_free)

/* Context */
sepol_context_t *sepol_node_get_con(const sepol_node_t * node)
{

	return node->con;
}

hidden_def(sepol_node_get_con)

int sepol_node_set_con(sepol_handle_t * handle,
		       sepol_node_t * node, sepol_context_t * con)
{

	sepol_context_t *newcon;

	if (sepol_context_clone(handle, con, &newcon) < 0) {
		LOGD( "out of memory, could not set node context");
		return STATUS_ERR;
	}

	sepol_context_free(node->con);
	node->con = newcon;
	return STATUS_SUCCESS;
}

hidden_def(sepol_node_set_con)
