/*
** Zabbix
** Copyright (C) 2001-2023 Zabbix SIA
**
** This program is free software; you can redistribute it and/or modify
** it under the terms of the GNU General Public License as published by
** the Free Software Foundation; either version 2 of the License, or
** (at your option) any later version.
**
** This program 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 General Public License for more details.
**
** You should have received a copy of the GNU General Public License
** along with this program; if not, write to the Free Software
** Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
**/

#include "zbxip.h"

#include "zbxnum.h"

/******************************************************************************
 *                                                                            *
 * Purpose: checks if the specified character is allowed whitespace character *
 *          that can be used before or after iprange definition               *
 *                                                                            *
 * Parameters: value - [IN] the character to check                            *
 *                                                                            *
 * Return value: SUCCEED - the value is whitespace character                  *
 *               FAIL    - otherwise                                          *
 *                                                                            *
 ******************************************************************************/
static int	iprange_is_whitespace_character(unsigned char value)
{
	switch (value)
	{
		case ' ':
		case '\r':
		case '\n':
		case '\t':
			return SUCCEED;
		default:
			return FAIL;
	}
}

/******************************************************************************
 *                                                                            *
 * Purpose: calculates the length of address data without trailing whitespace *
 *                                                                            *
 ******************************************************************************/
static size_t	iprange_address_length(const char *address)
{
	size_t		len;
	const char	*ptr;

	len = strlen(address);
	ptr = address + len - 1;

	while (0 < len && SUCCEED == iprange_is_whitespace_character(*ptr))
	{
		ptr--;
		len--;
	}

	return len;
}

/******************************************************************************
 *                                                                            *
 * Purpose: applies a bit mask to the parsed v4 or v6 IP range                *
 *                                                                            *
 * Parameters: iprange - [IN] the IP range                                    *
 *             bits    - [IN] the number of bits in IP mask                   *
 *                                                                            *
 ******************************************************************************/
static void	iprange_apply_mask(zbx_iprange_t *iprange, int bits)
{
	int	i, groups, group_bits;

	switch (iprange->type)
	{
		case ZBX_IPRANGE_V4:
			groups = 4;
			group_bits = 8;
			break;
		case ZBX_IPRANGE_V6:
			groups = 8;
			group_bits = 16;
			break;
		default:
			THIS_SHOULD_NEVER_HAPPEN;
			return;
	}

	bits = groups * group_bits - bits;

	for (i = groups - 1; 0 < bits; bits -= group_bits, i--)
	{
		unsigned int	mask_empty, mask_fill;
		int		mask_bits = bits;

		if (mask_bits > group_bits)
			mask_bits = group_bits;

		mask_empty = 0xffffffff << mask_bits;
		mask_fill = 0xffffffff >> (32 - mask_bits);

		iprange->range[i].from &= mask_empty;
		iprange->range[i].to |= mask_fill;
	}
}

/******************************************************************************
 *                                                                            *
 * Purpose: parse IPv4 address into IP range structure                        *
 *                                                                            *
 * Parameters: iprange - [OUT] the IP range                                   *
 *             address - [IN]  the IP address with optional ranges or         *
 *                             network mask (see documentation for network    *
 *                             discovery rule configuration)                  *
 *                                                                            *
 * Return value: SUCCEED - the IP range was successfully parsed               *
 *               FAIL    - otherwise                                          *
 *                                                                            *
 ******************************************************************************/
static int	iprangev4_parse(zbx_iprange_t *iprange, const char *address)
{
	int		index, bits = -1;
	const char	*ptr = address, *dash, *end;
	size_t		len;

	iprange->type = ZBX_IPRANGE_V4;

	/* ignore trailing whitespace characters */
	len = iprange_address_length(address);

	if (NULL != (end = strchr(address, '/')))
	{
		if (FAIL == zbx_is_uint_n_range(end + 1, len - (end + 1 - address), &bits, sizeof(bits), 0, 30))
			return FAIL;

		iprange->mask = 1;
	}
	else
	{
		end = address + len;
		iprange->mask = 0;
	}

	/* iterate through address numbers (bit groups) */
	for (index = 0; ptr < end && index < ZBX_IPRANGE_GROUPS_V4; address = ptr + 1)
	{
		if (NULL == (ptr = strchr(address, '.')))
			ptr = end;

		if (NULL != (dash = strchr(address, '-')))
		{
			/* having range and mask together is not supported */
			if (-1 != bits)
				return FAIL;

			/* check if the range specification is used by the current group */
			if (dash > ptr)
				dash = NULL;
		}

		len = (NULL == dash ? ptr : dash) - address;

		/* extract the range start value */
		if (FAIL == zbx_is_uint_n_range(address, len, &iprange->range[index].from,
				sizeof(iprange->range[index].from), 0, 255))
		{
			return FAIL;
		}

		/* if range is specified, extract the end value, otherwise set end value equal to the start value */
		if (NULL != dash)
		{
			dash++;
			if (FAIL == zbx_is_uint_n_range(dash, ptr - dash, &iprange->range[index].to,
					sizeof(iprange->range[index].to), 0, 255))
			{
				return FAIL;
			}

			if (iprange->range[index].to < iprange->range[index].from)
				return FAIL;
		}
		else
			iprange->range[index].to = iprange->range[index].from;

		index++;
	}

	/* IPv4 address will always have 4 groups */
	if (ZBX_IPRANGE_GROUPS_V4 != index)
		return FAIL;

	if (-1 != bits)
		iprange_apply_mask(iprange, bits);

	return SUCCEED;
}

/******************************************************************************
 *                                                                            *
 * Purpose: parse IPv6 address into IP range structure                        *
 *                                                                            *
 * Parameters: iprange - [OUT] the IP range                                   *
 *             address - [IN]  the IP address with optional ranges or         *
 *                             network mask (see documentation for network    *
 *                             discovery rule configuration)                  *
 *                                                                            *
 * Return value: SUCCEED - the IP range was successfully parsed               *
 *               FAIL    - otherwise                                          *
 *                                                                            *
 ******************************************************************************/
static int	iprangev6_parse(zbx_iprange_t *iprange, const char *address)
{
	int		index, fill = -1, bits = -1, target;
	const char	*ptr = address, *dash, *end;
	size_t		len;

	iprange->type = ZBX_IPRANGE_V6;

	/* ignore trailing whitespace characters */
	len = iprange_address_length(address);

	if (NULL != (end = strchr(address, '/')))
	{
		if (FAIL == zbx_is_uint_n_range(end + 1, len - (end + 1 - address), &bits, sizeof(bits), 0, 128))
			return FAIL;

		iprange->mask = 1;
	}
	else
	{
		end = address + len;
		iprange->mask = 0;
	}

	/* iterate through address numbers (bit groups) */
	for (index = 0; ptr < end && index < ZBX_IPRANGE_GROUPS_V6; address = ptr + 1)
	{
		if (NULL == (ptr = strchr(address, ':')))
			ptr = end;

		if (ptr == address)
		{
			/* handle the case when address starts with :: */
			if (':' != ptr[1])
				return FAIL;

			goto check_fill;
		}

		if (NULL != (dash = strchr(address, '-')))
		{
			/* having range and mask together is not supported */
			if (-1 != bits)
				return FAIL;

			/* check if the range specification is used by the current group */
			if (dash > ptr)
				dash = NULL;
		}

		len = (NULL == dash ? ptr : dash) - address;

		/* extract the range start value */
		if (FAIL == zbx_is_hex_n_range(address, len, &iprange->range[index].from, 4, 0, (1 << 16) - 1))
			return FAIL;

		/* if range is specified, extract the end value, otherwise set end value equal to the start value */
		if (NULL != dash)
		{
			dash++;
			if (FAIL == zbx_is_hex_n_range(dash, ptr - dash, &iprange->range[index].to, 4, 0, (1 << 16) - 1))
				return FAIL;

			if (iprange->range[index].to < iprange->range[index].from)
				return FAIL;
		}
		else
			iprange->range[index].to = iprange->range[index].from;

		index++;
check_fill:
		/* check if the next group is empty */
		if ('\0' != ptr[0] && ':' == ptr[1])
		{
			/* :: construct is allowed only once in address */
			if (-1 != fill)
				return FAIL;

			iprange->range[index].from = 0;
			iprange->range[index].to = 0;
			fill = index++;
			ptr++;

			/* check if address ends with :: */
			if (ptr == end - 1)
				break;
		}
	}

	/* fail if the address contains 9+ groups */
	if (ZBX_IPRANGE_GROUPS_V6 < index)
		return FAIL;

	/* expand the :: construct to the required number of zeros */
	if (ZBX_IPRANGE_GROUPS_V6 > index)
	{
		/* fail if the address contains less than 8 groups and no :: construct was used */
		if (-1 == fill)
			return FAIL;

		target = 7;

		/* shift the second part of address to the end */
		while (--index > fill)
			iprange->range[target--] = iprange->range[index];

		/* fill the middle with zeros */
		while (target > fill)
		{
			iprange->range[target].from = 0;
			iprange->range[target].to = 0;
			target--;
		}
	}

	if (-1 != bits)
		iprange_apply_mask(iprange, bits);

	return SUCCEED;
}

/******************************************************************************
 *                                                                            *
 * Purpose: parse IP address (v4 or v6) into IP range structure               *
 *                                                                            *
 * Parameters: iprange - [OUT] the IP range                                   *
 *             address - [IN]  the IP address with optional ranges or         *
 *                             network mask (see documentation for network    *
 *                             discovery rule configuration)                  *
 *                                                                            *
 * Return value: SUCCEED - the IP range was successfully parsed               *
 *               FAIL    - otherwise                                          *
 *                                                                            *
 ******************************************************************************/
int	zbx_iprange_parse(zbx_iprange_t *iprange, const char *address)
{
	/* ignore leading whitespace characters */
	while (SUCCEED == iprange_is_whitespace_character(*address))
		address++;

	if (NULL != strchr(address, '.'))
		return iprangev4_parse(iprange, address);

	return iprangev6_parse(iprange, address);
}

/******************************************************************************
 *                                                                            *
 * Purpose: gets the first IP address from the specified range                *
 *                                                                            *
 * Parameters: iprange - [IN] the IP range                                    *
 *             address - [OUT] the first address of the specified range       *
 *                             (with at least 8 items to support IPv6)        *
 *                                                                            *
 * Comments: The IP address is returned as a number array.                    *
 *                                                                            *
 ******************************************************************************/
void	zbx_iprange_first(const zbx_iprange_t *iprange, int *address)
{
	int	i, groups;

	groups = (ZBX_IPRANGE_V4 == iprange->type ? 4 : 8);

	for (i = 0; i < groups; i++)
		address[i] = iprange->range[i].from;

	/* exclude network address if the IPv4 range was specified with network mask */
	if (ZBX_IPRANGE_V4 == iprange->type && 0 != iprange->mask)
		address[groups - 1]++;
}

/******************************************************************************
 *                                                                            *
 * Purpose: gets the next IP address from the specified range                 *
 *                                                                            *
 * Parameters: iprange - [IN] the IP range                                    *
 *             address - [IN/OUT] IN - the current address from IP range      *
 *                                OUT - the next address from IP range        *
 *                                (with at least 8 items to support IPv6)     *
 *                                                                            *
 * Return value: SUCCEED - the next IP address was returned successfully      *
 *               FAIL    - no more addresses in the specified range           *
 *                                                                            *
 * Comments: The IP address is returned as a number array.                    *
 *                                                                            *
 ******************************************************************************/
int	zbx_iprange_next(const zbx_iprange_t *iprange, int *address)
{
	int	i, groups;

	groups = (ZBX_IPRANGE_V4 == iprange->type ? 4 : 8);

	for (i = groups - 1; i >= 0; i--)
	{
		if (address[i] < iprange->range[i].to)
		{
			address[i]++;

			/* exclude broadcast address if the IPv4 range was specified with network mask */
			if (ZBX_IPRANGE_V4 == iprange->type && 0 != iprange->mask)
			{
				for (i = groups - 1; i >= 0; i--)
				{
					if (address[i] != iprange->range[i].to)
						return SUCCEED;
				}

				return FAIL;
			}

			return SUCCEED;
		}

		if (iprange->range[i].from < iprange->range[i].to)
			address[i] = iprange->range[i].from;
	}

	return FAIL;
}

/******************************************************************************
 *                                                                            *
 * Purpose: checks if the IP address is in specified range                    *
 *                                                                            *
 * Parameters: iprange - [IN] the IP range                                    *
 *             address - [IN] the IP address to check                         *
 *                            (with at least 8 items to support IPv6)         *
 *                                                                            *
 * Return value: SUCCEED - the IP address was in the specified range          *
 *               FAIL    - otherwise                                          *
 *                                                                            *
 ******************************************************************************/
int	zbx_iprange_validate(const zbx_iprange_t *iprange, const int *address)
{
	int	i, groups;

	groups = (ZBX_IPRANGE_V4 == iprange->type ? 4 : 8);

	for (i = 0; i < groups; i++)
	{
		if (address[i] < iprange->range[i].from || address[i] > iprange->range[i].to)
			return FAIL;
	}

	return SUCCEED;
}

/******************************************************************************
 *                                                                            *
 * Purpose: get the number of addresses covered by the specified IP range     *
 *                                                                            *
 * Parameters: iprange - [IN] the IP range                                    *
 *                                                                            *
 * Return value: The number of addresses covered by the range or              *
 *               ZBX_MAX_UINT64 if this number exceeds 64 bit unsigned        *
 *               integer.                                                     *
 *                                                                            *
 ******************************************************************************/
zbx_uint64_t	zbx_iprange_volume(const zbx_iprange_t *iprange)
{
	int		i, groups;
	zbx_uint64_t	n, volume = 1;

	groups = (ZBX_IPRANGE_V4 == iprange->type ? 4 : 8);

	for (i = 0; i < groups; i++)
	{
		n = iprange->range[i].to - iprange->range[i].from + 1;

		if (ZBX_MAX_UINT64 / n < volume)
			return ZBX_MAX_UINT64;

		volume *= n;
	}

	/* exclude network and broadcast addresses if the IPv4 range was specified with network mask */
	if (ZBX_IPRANGE_V4 == iprange->type && 0 != iprange->mask)
		volume -= 2;

	return volume;
}