aprsc/src/acl.c

353 lines
7.0 KiB
C

/*
* ACL matching
*
* (c) Tomi Manninen <tomi.manninen@hut.fi>
* (c) Heikki Hannikainen
*
*/
#include <string.h>
#include <netdb.h>
#include <stdio.h>
#include <errno.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include "hmalloc.h"
#include "acl.h"
#include "worker.h"
#include "hlog.h"
#include "cfgfile.h"
/*
* allocate an empty acl structure
*/
struct acl_t *acl_new(void)
{
struct acl_t *a;
a = hmalloc(sizeof(struct acl_t));
a->entries4 = NULL;
a->entries6 = NULL;
return a;
}
void acl_free(struct acl_t *acl)
{
struct acl_e4_t *e4;
struct acl_e6_t *e6;
while (acl->entries4) {
e4 = acl->entries4->next;
hfree(acl->entries4);
acl->entries4 = e4;
}
while (acl->entries6) {
e6 = acl->entries6->next;
hfree(acl->entries6);
acl->entries6 = e6;
}
hfree(acl);
}
struct acl_t *acl_dup(struct acl_t *acl)
{
struct acl_e4_t *e4, *ne4;
struct acl_e6_t *e6, *ne6;
struct acl_t *a = acl_new();
for (e4 = acl->entries4; (e4); e4 = e4->next) {
ne4 = hmalloc(sizeof(*ne4));
memcpy(ne4, e4, sizeof(*ne4));
ne4->next = a->entries4;
a->entries4 = ne4;
}
for (e6 = acl->entries6; (e6); e6 = e6->next) {
ne6 = hmalloc(sizeof(*ne6));
memcpy(ne6, e6, sizeof(*ne6));
ne6->next = a->entries6;
a->entries6 = ne6;
}
return a;
}
/*
* Add an entry to the ACL
*/
int acl_add(struct acl_t *acl, char *netspec, int allow)
{
int prefixlen = 128;
char *prefixls;
struct addrinfo req, *ai, *nextai;
int i;
char dummyservice[] = "12345";
struct acl_e4_t *e4;
struct acl_e6_t *e6;
char *addr_s;
union sockaddr_u *sup;
prefixls = strchr(netspec, '/');
if (prefixls) {
// has netmask or prefix length
*prefixls = 0;
prefixls++;
}
// prepare for getaddrinfo
memset(&req, 0, sizeof(req));
req.ai_family = 0;
req.ai_socktype = SOCK_STREAM;
req.ai_protocol = IPPROTO_TCP;
req.ai_flags = 0;
ai = NULL;
// resolve the name
i = getaddrinfo(netspec, dummyservice, &req, &ai);
if (i != 0) {
hlog(LOG_ERR, "ACL: address parse failure of '%s': %s", netspec, gai_strerror(i));
return -1;
}
// parse the prefix length
if (prefixls) {
prefixlen = atoi(prefixls);
if (prefixlen < 0 || prefixlen > 128) {
hlog(LOG_ERR, "ACL: invalid prefix len '%s' for '%s'", prefixls, netspec);
return -1;
}
}
while (ai) {
nextai = ai->ai_next;
addr_s = strsockaddr( ai->ai_addr, ai->ai_addrlen );
sup = (union sockaddr_u *)ai->ai_addr;
if (ai->ai_family == AF_INET6) {
//hlog(LOG_DEBUG, "ACL: Adding IPv6: %s/%d: %s/%d", netspec, prefixlen, addr_s, prefixlen);
e6 = hmalloc(sizeof(*e6));
e6->next = acl->entries6;
acl->entries6 = e6;
e6->allow = allow;
memcpy(e6->addr, sup->si6.sin6_addr.s6_addr, sizeof(e6->addr));
e6->prefixlen = prefixlen;
if (prefixlen == 128) {
memset(e6->mask, 0xff, 16);
} else {
memset(e6->mask, 0, 16);
uint32_t shift = prefixlen;
uint32_t s;
for (i = 0; i < 4; i++) {
s = (shift > 32) ? 32 : shift;
shift -= s;
e6->mask[i] = htonl(0UL - (1UL << (32UL - s)));
/* mask the address wih the mask so that no host bits are set */
e6->addr[i] = e6->addr[i] & e6->mask[i];
}
}
} else if (ai->ai_family == AF_INET) {
if (prefixlen > 32)
prefixlen = 32;
//hlog(LOG_DEBUG, "ACL: Adding IPv4: %s/%d", netspec, prefixlen);
e4 = hmalloc(sizeof(*e4));
e4->next = acl->entries4;
acl->entries4 = e4;
e4->allow = allow;
e4->addr = ntohl(sup->si.sin_addr.s_addr);
e4->mask = 0;
for (i = 0; i < prefixlen; i++) {
e4->mask = e4->mask >> 1;
e4->mask |= 1<<31;
}
e4->addr = e4->addr & e4->mask;
}
hfree(addr_s);
hfree(ai);
ai = nextai;
}
return 0;
}
#define ACL_LINEBUF 1024
struct acl_t *acl_load(char *s)
{
struct acl_t *acl;
FILE *fp;
char buf[ACL_LINEBUF];
char *eol;
char *argv[256];
int argc;
int failed = 0;
int line = 0;
//hlog(LOG_DEBUG, "ACL: Loading ACL file \"%s\"", s);
fp = fopen(s, "r");
if (!fp) {
hlog(LOG_ERR, "ACL load failed: Could not open \"%s\" for reading: %s", s, strerror(errno));
return NULL;
}
acl = acl_new();
while (fgets(buf, ACL_LINEBUF, fp)) {
line++;
// strip newlines
eol = strchr(buf, '\r');
if (eol)
*eol = 0;
eol = strchr(buf, '\n');
if (eol)
*eol = 0;
// strip comments
eol = strchr(buf, '#');
if (eol)
*eol = 0;
// skip empty lines
if (strlen(buf) == 0)
continue;
argc = parse_args(argv, buf);
if (argc != 2) {
hlog(LOG_ERR, "ACL load failed: %s: invalid number of arguments on line %d", s, line);
failed = 1;
}
int allow = 0;
if (strcasecmp(argv[0], "allow") == 0)
allow = 1;
else if (strcasecmp(argv[0], "deny") == 0)
allow = 0;
else {
hlog(LOG_ERR, "ACL load failed: %s: invalid command on line %d: %s", s, line, argv[0]);
failed = 1;
continue;
}
if (acl_add(acl, argv[1], allow)) {
hlog(LOG_ERR, "ACL load failed: %s: invalid netspec on line %d: %s", s, line, argv[1]);
failed = 1;
}
}
if (fclose(fp)) {
hlog(LOG_ERR, "ACL load failed: close(%s): %s", s, strerror(errno));
failed = 1;
}
if (failed) {
acl_free(acl);
return NULL;
}
return acl;
}
/*
* Match an IPv4 address agaist the ACL list
*/
static int acl_check_4(struct acl_t *acl, uint32_t addr)
{
struct acl_e4_t *a = acl->entries4;
while (a) {
if ((addr & a->mask) == a->addr)
return a->allow;
a = a->next;
}
return 0;
}
/*
* Match an IPv6 address agaist the ACL list
*/
static int acl_check_6(struct acl_t *acl, uint32_t addr[4])
{
struct acl_e6_t *a = acl->entries6;
int i;
//hlog(LOG_DEBUG, "acl_check_6");
while (a) {
int fail = 0;
for (i = 0; i < 4 && !fail; i++) {
if ((addr[i] & a->mask[i]) != a->addr[i]) {
fail = 1;
}
}
if (!fail) {
return a->allow;
}
a = a->next;
}
// Disallow by default
return 0;
}
/*
* Match a sockaddr against the ACL
*/
int acl_check(struct acl_t *acl, struct sockaddr *sa, int addr_len)
{
union sockaddr_u su, *sup;
sup = (union sockaddr_u *)sa;
/* When we're listening on [::] address, IPv4 connections will be
* accepted too. The IPv4 source address will be given to mapped
* to an IPv6 address. Here, we map that back to an IPv4 address.
*/
#ifdef IN6_IS_ADDR_V4MAPPED
if ( sa->sa_family == AF_INET6 &&
( IN6_IS_ADDR_V4MAPPED(&(sup->si6.sin6_addr)) ||
IN6_IS_ADDR_V4COMPAT(&(sup->si6.sin6_addr)) ) ) {
memset(&su, 0, sizeof(su));
su.si.sin_family = AF_INET;
su.si.sin_port = sup->si6.sin6_port;
memcpy(& su.si.sin_addr, &((uint32_t*)(&(sup->si6.sin6_addr)))[3], 4);
sa = &su.sa;
sup = (union sockaddr_u *)sa;
// hlog(LOG_DEBUG, "Translating v4 mapped/compat address..");
}
#endif
if (sup->sa.sa_family == AF_INET)
return acl_check_4(acl, ntohl(sup->si.sin_addr.s_addr));
if (sup->sa.sa_family == AF_INET6)
return acl_check_6(acl, (uint32_t *)sup->si6.sin6_addr.s6_addr);
hlog(LOG_ERR, "acl_check failed: unknown address family or address length (family %d, length %d)", sup->sa.sa_family, addr_len);
return 0; // Disallow by default: unknown address family
}