diff options
Diffstat (limited to 'addrfilt.c')
-rw-r--r-- | addrfilt.c | 405 |
1 files changed, 405 insertions, 0 deletions
diff --git a/addrfilt.c b/addrfilt.c new file mode 100644 index 0000000..6208b46 --- /dev/null +++ b/addrfilt.c @@ -0,0 +1,405 @@ +/* + chronyd/chronyc - Programs for keeping computer clocks accurate. + + ********************************************************************** + * Copyright (C) Richard P. Curnow 1997,1998,1999,2000,2001,2002,2005 + * Copyright (C) Miroslav Lichvar 2009, 2015 + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of version 2 of the GNU General Public License as + * published by the Free Software Foundation. + * + * 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. + * + ********************************************************************** + + ======================================================================= + + This module provides a set of routines for checking IP addresses + against a set of rules and deciding whether they are allowed or + disallowed. + + */ + +#include "config.h" + +#include "sysincl.h" + +#include "addrfilt.h" +#include "memory.h" + +/* Define the number of bits which are stripped off per level of + indirection in the tables */ +#define NBITS 4 + +/* Define the table size */ +#define TABLE_SIZE (1UL<<NBITS) + +typedef enum {DENY, ALLOW, AS_PARENT} State; + +typedef struct _TableNode { + State state; + struct _TableNode *extended; +} TableNode; + +struct ADF_AuthTableInst { + TableNode base4; /* IPv4 node */ + TableNode base6; /* IPv6 node */ +}; + +/* ================================================== */ + +static void +split_ip6(IPAddr *ip, uint32_t *dst) +{ + int i; + + for (i = 0; i < 4; i++) + dst[i] = (uint32_t)ip->addr.in6[i * 4 + 0] << 24 | + ip->addr.in6[i * 4 + 1] << 16 | + ip->addr.in6[i * 4 + 2] << 8 | + ip->addr.in6[i * 4 + 3]; +} + +/* ================================================== */ + +inline static uint32_t +get_subnet(uint32_t *addr, unsigned int where) +{ + int off; + + off = where / 32; + where %= 32; + + return (addr[off] >> (32 - NBITS - where)) & ((1UL << NBITS) - 1); +} + +/* ================================================== */ + +ADF_AuthTable +ADF_CreateTable(void) +{ + ADF_AuthTable result; + result = MallocNew(struct ADF_AuthTableInst); + + /* Default is that nothing is allowed */ + result->base4.state = DENY; + result->base4.extended = NULL; + result->base6.state = DENY; + result->base6.extended = NULL; + + return result; +} + +/* ================================================== */ +/* This function deletes all definitions of child nodes, in effect + pruning a whole subnet definition back to a single parent + record. */ +static void +close_node(TableNode *node) +{ + int i; + TableNode *child_node; + + if (node->extended != NULL) { + for (i=0; i<TABLE_SIZE; i++) { + child_node = &(node->extended[i]); + close_node(child_node); + } + Free(node->extended); + node->extended = NULL; + } +} + + +/* ================================================== */ +/* Allocate the extension field in a node, and set all the children's + states to default to that of the node being extended */ + +static void +open_node(TableNode *node) +{ + int i; + TableNode *child_node; + + if (node->extended == NULL) { + + node->extended = MallocArray(struct _TableNode, TABLE_SIZE); + + for (i=0; i<TABLE_SIZE; i++) { + child_node = &(node->extended[i]); + child_node->state = AS_PARENT; + child_node->extended = NULL; + } + } +} + +/* ================================================== */ + +static ADF_Status +set_subnet(TableNode *start_node, + uint32_t *ip, + int ip_len, + int subnet_bits, + State new_state, + int delete_children) +{ + int bits_to_go, bits_consumed; + uint32_t subnet; + TableNode *node; + + bits_consumed = 0; + bits_to_go = subnet_bits; + node = start_node; + + if ((subnet_bits < 0) || + (subnet_bits > 32 * ip_len)) { + + return ADF_BADSUBNET; + + } else { + + if ((bits_to_go & (NBITS-1)) == 0) { + + while (bits_to_go > 0) { + subnet = get_subnet(ip, bits_consumed); + if (!(node->extended)) { + open_node(node); + } + node = &(node->extended[subnet]); + bits_to_go -= NBITS; + bits_consumed += NBITS; + } + + if (delete_children) { + close_node(node); + } + node->state = new_state; + + } else { /* Have to set multiple entries */ + int N, i, j; + TableNode *this_node; + + while (bits_to_go >= NBITS) { + subnet = get_subnet(ip, bits_consumed); + if (!(node->extended)) { + open_node(node); + } + node = &(node->extended[subnet]); + bits_to_go -= NBITS; + bits_consumed += NBITS; + } + + /* How many subnet entries to set : 1->8, 2->4, 3->2 */ + N = 1 << (NBITS-bits_to_go); + + subnet = get_subnet(ip, bits_consumed) & ~(N - 1); + assert(subnet + N <= TABLE_SIZE); + + if (!(node->extended)) { + open_node(node); + } + + for (i=subnet, j=0; j<N; i++, j++) { + this_node = &(node->extended[i]); + if (delete_children) { + close_node(this_node); + } + this_node->state = new_state; + } + } + + return ADF_SUCCESS; + } + +} + +/* ================================================== */ + +static ADF_Status +set_subnet_(ADF_AuthTable table, + IPAddr *ip_addr, + int subnet_bits, + State new_state, + int delete_children) +{ + uint32_t ip6[4]; + + switch (ip_addr->family) { + case IPADDR_INET4: + return set_subnet(&table->base4, &ip_addr->addr.in4, 1, subnet_bits, new_state, delete_children); + case IPADDR_INET6: + split_ip6(ip_addr, ip6); + return set_subnet(&table->base6, ip6, 4, subnet_bits, new_state, delete_children); + case IPADDR_UNSPEC: + /* Apply to both, subnet_bits has to be 0 */ + if (subnet_bits != 0) + return ADF_BADSUBNET; + memset(ip6, 0, sizeof (ip6)); + if (set_subnet(&table->base4, ip6, 1, 0, new_state, delete_children) == ADF_SUCCESS && + set_subnet(&table->base6, ip6, 4, 0, new_state, delete_children) == ADF_SUCCESS) + return ADF_SUCCESS; + break; + default: + break; + } + + return ADF_BADSUBNET; +} + +ADF_Status +ADF_Allow(ADF_AuthTable table, + IPAddr *ip, + int subnet_bits) +{ + return set_subnet_(table, ip, subnet_bits, ALLOW, 0); +} + +/* ================================================== */ + + +ADF_Status +ADF_AllowAll(ADF_AuthTable table, + IPAddr *ip, + int subnet_bits) +{ + return set_subnet_(table, ip, subnet_bits, ALLOW, 1); +} + +/* ================================================== */ + +ADF_Status +ADF_Deny(ADF_AuthTable table, + IPAddr *ip, + int subnet_bits) +{ + return set_subnet_(table, ip, subnet_bits, DENY, 0); +} + +/* ================================================== */ + +ADF_Status +ADF_DenyAll(ADF_AuthTable table, + IPAddr *ip, + int subnet_bits) +{ + return set_subnet_(table, ip, subnet_bits, DENY, 1); +} + +/* ================================================== */ + +void +ADF_DestroyTable(ADF_AuthTable table) +{ + close_node(&table->base4); + close_node(&table->base6); + Free(table); +} + +/* ================================================== */ + +static int +check_ip_in_node(TableNode *start_node, uint32_t *ip) +{ + uint32_t subnet; + int bits_consumed = 0; + int result = 0; + int finished = 0; + TableNode *node; + State state=DENY; + + node = start_node; + + do { + if (node->state != AS_PARENT) { + state = node->state; + } + if (node->extended) { + subnet = get_subnet(ip, bits_consumed); + node = &(node->extended[subnet]); + bits_consumed += NBITS; + } else { + /* Make decision on this node */ + finished = 1; + } + } while (!finished); + + switch (state) { + case ALLOW: + result = 1; + break; + case DENY: + result = 0; + break; + case AS_PARENT: + assert(0); + break; + } + + return result; +} + + +/* ================================================== */ + +int +ADF_IsAllowed(ADF_AuthTable table, + IPAddr *ip_addr) +{ + uint32_t ip6[4]; + + switch (ip_addr->family) { + case IPADDR_INET4: + return check_ip_in_node(&table->base4, &ip_addr->addr.in4); + case IPADDR_INET6: + split_ip6(ip_addr, ip6); + return check_ip_in_node(&table->base6, ip6); + default: + return 0; + } +} + +/* ================================================== */ + +static int +is_any_allowed(TableNode *node, State parent) +{ + State state; + int i; + + state = node->state != AS_PARENT ? node->state : parent; + assert(state != AS_PARENT); + + if (node->extended) { + for (i = 0; i < TABLE_SIZE; i++) { + if (is_any_allowed(&node->extended[i], state)) + return 1; + } + } else if (state == ALLOW) { + return 1; + } + + return 0; +} + +/* ================================================== */ + +int +ADF_IsAnyAllowed(ADF_AuthTable table, int family) +{ + switch (family) { + case IPADDR_INET4: + return is_any_allowed(&table->base4, AS_PARENT); + case IPADDR_INET6: + return is_any_allowed(&table->base6, AS_PARENT); + default: + return 0; + } +} |