/* access.c - functions for access control

   Copyright (C) 2003  Russell Kroll <rkroll@exploits.org>

   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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*/

#include <stdint.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>

#include "common.h"
#include "access.h"

struct acl_t		*acl_head = NULL;
struct access_t	*access_head = NULL;

#ifndef	HAVE_IPV6
static int mask_cmp(const struct sockaddr_in *ip_addr,
		const struct sockaddr_in *net_addr, unsigned int prefix)
{
#else
static int mask_cmp (const struct sockaddr_storage *ip_addr,
		const struct sockaddr_storage *net_addr, unsigned int prefix)
{
	if (ip_addr->ss_family == AF_INET)
	{
#endif
		struct in_addr	*ip  = &((struct sockaddr_in *)ip_addr)->sin_addr;
		struct in_addr	*net = &((struct sockaddr_in *)net_addr)->sin_addr;

		return ((ip->s_addr & prefix) == net->s_addr);
#ifdef	HAVE_IPV6
	}

	if ((ip_addr->ss_family == AF_INET6) && (net_addr->ss_family == AF_INET6))
	{
		struct in6_addr	ip6;
		struct in6_addr	*net = &((struct sockaddr_in6 *)net_addr)->sin6_addr;
		unsigned char		i = (prefix >> 3);
		
		memcpy (&ip6, &((struct sockaddr_in6 *)ip_addr)->sin6_addr, sizeof (struct in6_addr));
			
		if (prefix % 8)
			ip6.s6_addr[i++] &= 0xff << (8 - (prefix % 8));

		while (i < sizeof(ip6.s6_addr))
			ip6.s6_addr[i++] = 0;

	
		return (memcmp(ip6.s6_addr, net->s6_addr, sizeof(ip6.s6_addr)) == 0);
	}

	if ((ip_addr->ss_family == AF_INET6) && (net_addr->ss_family == AF_INET))
	{
		struct in6_addr	*ip6 = &((struct sockaddr_in6 *)ip_addr)->sin6_addr;
		struct in_addr		*net = &((struct sockaddr_in *)net_addr)->sin_addr;
			
		return (IN6_IS_ADDR_V4MAPPED(ip6) &&
			((((const uint32_t *)ip6)[3] & prefix) == net->s_addr));
	}

	fatal_with_errno(EXIT_FAILURE, "mask_cmp: Unknown address family");
#endif
}

/* see if <addr> matches the acl <aclname> */
#ifndef	HAVE_IPV6
int acl_check(const char *aclname, const struct sockaddr_in *addr)
#else
int acl_check(const char *aclname, const struct sockaddr_storage *addr)
#endif
{
	struct acl_t	*tmp;

	for (tmp = acl_head; tmp != NULL; tmp = tmp->next)
	{
		if (strcmp(tmp->name, aclname))
			continue;

		if (mask_cmp(addr, &tmp->addr, tmp->mask))
			return 1;	/* match */
	}

	return 0;	/* not found */
}

/* return ACCEPT/REJECT based on source address */
#ifndef	HAVE_IPV6
int access_check(const struct sockaddr_in *addr)
#else
int access_check(const struct sockaddr_storage *addr)
#endif
{
	struct access_t	*tmp;
	int	ret;

	for (tmp = access_head; tmp != NULL; tmp = tmp->next)
	{
		ret = acl_check(tmp->aclname, addr);

		upsdebugx(3, "acl_check: %s: match %d", tmp->aclname, ret);

		if (ret == 1) {
			upsdebugx(1, "ACL [%s] matches, action=%d",
				tmp->aclname, tmp->action);
			return tmp->action;
		}
	}

	/* fail safe */
	return ACCESS_REJECT;
}

/* add to the master list of ACL names */
void acl_add(const char *aclname, char *ipblock)
{
	struct acl_t	*tmp, *last;
	char		*addr, *mask;

	/* are we sane? */
	if ((!aclname) || (!ipblock))
		return;

	/*
	 * 192.168.1.1/32			valid
	 * 192.168.1.1/255.255.255.255	valid
	 * 192.168.1.1			invalid
	 * ::FFFF:192.168.1.0/120		valid
	 * ::FFFF:192.168.1.1		invalid
	 * ::1/128				valid
	 * ::1					invalid
	 */
	if (((addr = strtok(ipblock, "/")) == NULL) || ((mask = strtok(NULL, "\0")) == NULL))
		fatalx(EXIT_FAILURE, "Can't parse ACL %s %s", aclname, ipblock);

	tmp = last = acl_head;

	while (tmp != NULL)
	{
		last = tmp;
		tmp = tmp->next;
	}

	/* memset (&saddr, 0, sizeof (struct sockaddr_storage)); */
	tmp = xmalloc(sizeof(struct acl_t));
	memset(tmp, 0, sizeof (struct acl_t));
	tmp->name = xstrdup(aclname);
	tmp->next = NULL;

#ifndef	HAVE_IPV6
	if (strstr(mask, ".") == NULL)
	{
		/* must be a /nn CIDR type block */
		tmp->mask = strtol(mask, NULL, 10);

		if (tmp->mask < 0 || tmp->mask > 32)
		{
			free (tmp);
			fatal_with_errno(EXIT_FAILURE, "Invalid CIDR type block: Must be > 0 && < 32");
		}

		tmp->mask = htonl(0xffffffff << (32 - tmp->mask));
	}
	else
	{
		/* must be a n.n.n.n dotted quad block */
		tmp->mask = inet_addr(mask);
	}

	tmp->addr.sin_addr.s_addr = inet_addr(addr) & tmp->mask;
#else
	if (strstr(addr, ":") == NULL)
	{
		struct sockaddr_in	s4;	/* IPv4 address */

		/* mask */
		if (inet_pton(AF_INET, mask, &s4.sin_addr) < 1)
		{
			/* must be a /nn CIDR type block */
			tmp->mask = strtol(mask, NULL, 10);

			if (tmp->mask < 0 || tmp->mask > 32)
			{
				free (tmp);
				fatal_with_errno(EXIT_FAILURE, "Invalid CIDR type block: Must be > 0 && < 32");
			}

			tmp->mask = htonl(0xffffffff << (32 - tmp->mask));
		}
		else
		{
			/* must be a n.n.n.n dotted quad block */
			tmp->mask = s4.sin_addr.s_addr;
		}

		/* address */
		memset(&s4, 0, sizeof (struct sockaddr_in));
		s4.sin_family = AF_INET;

		/* apply mask to address */
		if (inet_pton(AF_INET, addr, &s4.sin_addr) < 1)
		{
			free(tmp);
			fatal_with_errno(EXIT_FAILURE, "Invalid IPv4 address: \"%s\"", addr);
		}
		else
		{
			s4.sin_addr.s_addr &= tmp->mask;
		}

		memcpy(&(tmp->addr), &s4, sizeof(struct sockaddr_in));

		tmp->addr.ss_family = AF_INET;
	}
	else
	{
		struct sockaddr_in6	s6;	/* IPv6 address */

		/* prefix */
		tmp->mask = strtol(mask, NULL, 10);

		/* address */
		memset(&s6, 0, sizeof(struct sockaddr_in6));
		s6.sin6_family = AF_INET6;

		if (inet_pton(AF_INET6, addr, &s6.sin6_addr) < 1)
		{
			free(tmp);
			fatal_with_errno(EXIT_FAILURE, "Invalid IPv6 address: \"%s\"", addr);
		}

		/* apply prefix to address */
		if (tmp->mask < 0 || tmp->mask > 128)
		{
			free (tmp);
			fatal_with_errno(EXIT_FAILURE, "Invalid IPv6 prefix");
		}
		else
		{
			unsigned char	i = (tmp->mask >> 3);

			if ((tmp->mask) % 8)
				s6.sin6_addr.s6_addr[i++] &= 0xff << (8 - ((tmp->mask) % 8));

			while (i < sizeof(s6.sin6_addr.s6_addr))
				s6.sin6_addr.s6_addr[i++] = 0;
		}

		memcpy(&(tmp->addr), &s6, sizeof(struct sockaddr_in6));

		tmp->addr.ss_family = AF_INET6;
	}
#endif

	if (last == NULL)	/* first */
		acl_head = tmp;
	else
		last->next = tmp;
}

void acl_free(void)
{
	struct acl_t	*ptr, *next;

	ptr = acl_head;

	while (ptr)
	{
		next = ptr->next;
		
		free(ptr->name);
		free(ptr);

		ptr = next;
	}

	acl_head = NULL;
}

void access_free(void)
{
	struct access_t	*ptr, *next;

	ptr = access_head;

	while (ptr)
	{
		next = ptr->next;

		free(ptr->aclname);
		free(ptr);

		ptr = next;
	}

	access_head = NULL;
}	

static void access_append(int action, const char *aclname)
{
	struct access_t	*tmp, *last;

	tmp = last = access_head;

	while (tmp != NULL)
	{
		last = tmp;
		tmp = tmp->next;
	}

	tmp = xmalloc(sizeof(struct access_t));

	tmp->action = action;
	tmp->aclname = xstrdup(aclname);

	tmp->next = NULL;

	if (last)
		last->next = tmp;
	else
		access_head = tmp;
}		

void access_add(int type, int numargs, const char **arg)
{
	int	i;

	for (i = 0; i < numargs; i++)
		access_append(type, arg[i]);
}


syntax highlighted by Code2HTML, v. 0.9.1