/**
 * @file oval_resultTest.c
 * \brief Open Vulnerability and Assessment Language
 *
 * See more details at http://oval.mitre.org/
 */

/*
 * Copyright 2009--2013 Red Hat Inc., Durham, North Carolina.
 * All Rights Reserved.
 *
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public
 * License as published by the Free Software Foundation; either
 * version 2.1 of the License, or (at your option) any later version.
 *
 * This library is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this library; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
 *
 * Authors:
 *     Tomas Heinrich <theinric@redhat.com>
 *     Šimon Lukašík
 */

#ifdef HAVE_CONFIG_H
#include <config.h>
#endif

#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <stdint.h>
#if defined(OS_WINDOWS)
#include <winsock2.h>
#include <ws2tcpip.h>
#elif defined(OS_FREEBSD)
#include <arpa/inet.h>
#include <netinet/in.h>
#include <sys/socket.h>
#else
#include <arpa/inet.h>
#include <sys/socket.h>
#endif

#include "oval_definitions.h"
#include "oval_types.h"

#include "common/util.h"
#include "common/debug_priv.h"
#include "oval_cmp_ip_address_impl.h"

static inline void ipv4addr_mask(struct in_addr *ip_addr, uint32_t netmask);
static inline void ipv6addr_mask(struct in6_addr *addr, int prefix_len);
static inline int ipv4addr_parse(const char *oval_ipv4_string, uint32_t *netmask_out, struct in_addr *ip_out);
static inline int ipv6addr_parse(const char *oval_ipv6_string, uint32_t *len_out, struct in6_addr *ip_out);


static inline int ipaddr_cmp(int af, const void *addr1, const void *addr2)
{
	if (af == AF_INET)
		return memcmp(addr1, addr2, sizeof(struct in_addr));
	else {
		assert(af == AF_INET6);
		return memcmp(addr1, addr2, sizeof(struct in6_addr));
	}
}

static inline void ipaddr_mask(int af, const void *ip_addr, uint32_t mask)
{
	if (af == AF_INET)
		ipv4addr_mask((struct in_addr *) ip_addr, mask);
	else if (af == AF_INET6)
		ipv6addr_mask((struct in6_addr *) ip_addr, mask);
	else
		assert(false);

}

static inline int ipaddr_parse(int af, const char *oval_ip_string, uint32_t *mask_out, void * ip_out)
{
	if (af == AF_INET)
		return ipv4addr_parse(oval_ip_string, mask_out, ip_out);
	assert (af == AF_INET6);
	return ipv6addr_parse(oval_ip_string, mask_out, ip_out);
}

oval_result_t oval_ipaddr_cmp(int af, const char *s1, const char *s2, oval_operation_t op)
{
	oval_result_t result = OVAL_RESULT_ERROR;
	uint32_t mask1 = 0, mask2 = 0;
	char addr1[INET6_ADDRSTRLEN];
	char addr2[INET6_ADDRSTRLEN];

	if (ipaddr_parse(af, s1, &mask1, &addr1) || ipaddr_parse(af, s2, &mask2, &addr2)) {
		return result;
	}

	switch (op) {
	case OVAL_OPERATION_EQUALS:
		if (!ipaddr_cmp(af, &addr1, &addr2) && mask1 == mask2)
			result = OVAL_RESULT_TRUE;
		else
			result = OVAL_RESULT_FALSE;
		break;
	case OVAL_OPERATION_NOT_EQUAL:
		if (ipaddr_cmp(af, &addr1, &addr2) || mask1 != mask2)
			result = OVAL_RESULT_TRUE;
		else
			result = OVAL_RESULT_FALSE;
		break;
	case OVAL_OPERATION_SUBSET_OF:
		/* This asserts that every IP address in the set of IP addresses
		 * on the system (add2, mask2) must be present in the set of IP
		 * addresses defined in the stated entity (addr1, mask1). */
		if (mask1 > mask2) {
			/* The bigger the netmask (IPv4) or prefix-length (IPv6) is
			 * the less IP addresses there are in the range. */
			result = OVAL_RESULT_FALSE;
			break;
		}

		/* Otherwise, compare the first bits defined by mask1 */
		ipaddr_mask(af, &addr1, mask1);
		ipaddr_mask(af, &addr2, mask1);
		if (ipaddr_cmp(af, &addr1, &addr2) == 0)
			result = OVAL_RESULT_TRUE;
		else
			result = OVAL_RESULT_FALSE;
		break;
	case OVAL_OPERATION_GREATER_THAN:
		if (mask1 != mask2) {
			return OVAL_RESULT_ERROR;
		}
		ipaddr_mask(af, &addr1, mask1);
		ipaddr_mask(af, &addr2, mask2);
		if (ipaddr_cmp(af, &addr1, &addr2) < 0)
			result = OVAL_RESULT_TRUE;
		else
			result = OVAL_RESULT_FALSE;
		break;
	case OVAL_OPERATION_GREATER_THAN_OR_EQUAL:
		if (mask1 != mask2) {
			return OVAL_RESULT_ERROR;
		}
		ipaddr_mask(af, &addr1, mask1);
		ipaddr_mask(af, &addr2, mask2);
		if (ipaddr_cmp(af, &addr1, &addr2) <= 0)
			result = OVAL_RESULT_TRUE;
		else
			result = OVAL_RESULT_FALSE;
		break;
	case OVAL_OPERATION_SUPERSET_OF:
		/* This asserts that every IP address in the set of IP addresses defined in
		 * the stated entity (addr1, mask1) is present in the set of IP addresses
		 * on the system. (addr2, mask2). */
		if (mask1 < mask2) {
			/* The smaller the netmask (IPv4) or prefix-length (IPv6) is
			 * the more IP addresses there are in the range */
			result = OVAL_RESULT_FALSE;
			break;
		}

		/* Otherwise, compare the first bits defined by mask2 */
		ipaddr_mask(af, &addr1, mask2);
		ipaddr_mask(af, &addr2, mask2);
		if (ipaddr_cmp(af, &addr1, &addr2) == 0)
			result = OVAL_RESULT_TRUE;
		else
			result = OVAL_RESULT_FALSE;
		break;
	case OVAL_OPERATION_LESS_THAN:
		if (mask1 != mask2) {
			return OVAL_RESULT_ERROR;
		}
		ipaddr_mask(af, &addr1, mask1);
		ipaddr_mask(af, &addr2, mask2);
		if (ipaddr_cmp(af, &addr1, &addr2) > 0)
			result = OVAL_RESULT_TRUE;
		else
			result = OVAL_RESULT_FALSE;
		break;
	case OVAL_OPERATION_LESS_THAN_OR_EQUAL:
		if (mask1 != mask2) {
			return OVAL_RESULT_ERROR;
		}
		ipaddr_mask(af, &addr1, mask1);
		ipaddr_mask(af, &addr2, mask2);
		if (ipaddr_cmp(af, &addr1, &addr2) >= 0)
			result = OVAL_RESULT_TRUE;
		else
			result = OVAL_RESULT_FALSE;
		break;
	default:
		dE("Unexpected compare operation: %d.", op);
		assert(false);
	}

	return result;
}

static inline int ipv4addr_parse(const char *oval_ipv4_string, uint32_t *netmask_out, struct in_addr *ip_out)
{
	char *s, *pfx;
	int result = -1;

	s = strdup(oval_ipv4_string);
	pfx = strchr(s, '/');
	if (pfx) {
		int cnt;
		unsigned char nm[4];

		*pfx++ = '\0';
		cnt = sscanf(pfx, "%hhu.%hhu.%hhu.%hhu", &nm[0], &nm[1], &nm[2], &nm[3]);
		if (cnt == 4) { /* netmask */
			*netmask_out = (nm[0] << 24) + (nm[1] << 16) + (nm[2] << 8) + nm[3];
		} else if (cnt == 1 && nm[0] <= 32) { /* prefix */
			*netmask_out = (~0u) << (32u - nm[0]);
		} else {
			dW("Invalid prefix or netmask.");
			free(s);
			return -1;
		}
	} else {
		*netmask_out = ~0;
	}

	if (inet_pton(AF_INET, s, ip_out) <= 0)
		dW("inet_pton() failed.");
	else
		result = 0;

	free(s);
	return result;
}

static inline void ipv4addr_mask(struct in_addr *ip_addr, uint32_t netmask)
{
	ip_addr->s_addr &= htonl(netmask);
}

static inline int ipv6addr_parse(const char *oval_ipv6_string, uint32_t *len_out, struct in6_addr *ip_out)
{
	char *s, *pfx;
	int result = -1;

	s = strdup(oval_ipv6_string);
	pfx = strchr(s, '/');
	if (pfx) {
		*pfx++ = '\0';
		*len_out = strtol(pfx, NULL, 10);
	} else {
		*len_out = 128;
	}

	if (inet_pton(AF_INET6, s, ip_out) <= 0)
		dW("inet_pton() failed.");
	else
		result = 0;

	free(s);
	return result;
}

static inline void ipv6addr_mask(struct in6_addr *addr, int prefix_len)
{
	assert(128 >= prefix_len);

	uint8_t mask = (~0u) << (8u - (prefix_len % 8));

	/* First n (prefix_len/8 - 1) bytes are left untouched. */
	for (int i = prefix_len/8; i < 128/8; i++)
	{
		/* The (n+1) byte is masked according to the prefix_len */
		addr->s6_addr[i] &= mask;
		/* The rest will be zeroed. */
		mask = 0;
	}
}
