acl.c 4.49 KB
Newer Older
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
/*  Copyright (C) 2011 CZ.NIC, z.s.p.o. <knot-dns@labs.nic.cz>

    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 3 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, see <http://www.gnu.org/licenses/>.
 */

17 18
#include <string.h>
#include <stdlib.h>
19
#include <assert.h>
20
#include <sys/types.h>
Marek Vavrusa's avatar
Marek Vavrusa committed
21
#include <sys/socket.h>
22
#include <limits.h>
23
#include <stdbool.h>
24

25
#include "common/errcode.h"
26
#include "knot/updates/acl.h"
27
#include "libknot/util/endian.h"
28
#include "libknot/tsig.h"
29

30
static inline uint32_t ipv4_chunk(const struct sockaddr_in *ipv4)
31 32
{
	/* Stored as big end first. */
33
	return ipv4->sin_addr.s_addr;
34 35
}

36
static inline uint32_t ipv6_chunk(const struct sockaddr_in6 *ipv6, uint8_t idx)
37 38
{
	/* Is byte array, 4x 32bit value. */
39
	return ((uint32_t *)&ipv6->sin6_addr)[idx];
40 41
}

42
static inline uint32_t ip_chunk(const struct sockaddr_storage *ss, uint8_t idx)
43
{
44 45 46 47 48
	if (ss->ss_family == AF_INET6) {
		return ipv6_chunk((const struct sockaddr_in6 *)ss, idx);
	} else {
		return ipv4_chunk((const struct sockaddr_in *)ss);
	}
49 50 51
}

/*! \brief Compare chunks using given mask. */
52
static int cmp_chunk(const netblock_t *a1, const struct sockaddr_storage *a2,
53
                     uint8_t idx, uint32_t mask)
54
{
55
	const uint32_t c1 = ip_chunk(&a1->ss, idx) & mask;
56
	const uint32_t c2 = ip_chunk(a2, idx) & mask;
57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78

	if (c1 > c2)
		return  1;
	if (c1 < c2)
		return -1;
	return 0;
}

/*!
 * \brief Calculate bitmask for byte array from the MSB.
 *
 * \note i.e. 8 means top 8 bits set, 11111111000000000000000000000000
 *
 * \param nbits number of bits set to 1
 * \return mask
 */
static uint32_t acl_fill_mask32(short nbits)
{
	assert(nbits >= 0 && nbits <= 32);
	uint32_t r = 0;
	for (char i = 0; i < nbits; ++i) {
		r |= 1 << (31 - i);
79
	}
80

Ondřej Surý's avatar
Ondřej Surý committed
81
	/* Make sure the mask is in network byte order. */
82
	return htonl(r);
83 84
}

85
static int addr_match(const netblock_t *a1, const struct sockaddr_storage *a2)
86
{
87 88 89 90
	int ret = 0;
	uint32_t mask = 0xffffffff;
	short mask_bits = a1->prefix;
	const short chunk_bits = sizeof(mask) * CHAR_BIT;
91 92

	/* Check different length, IPv4 goes first. */
93 94
	if (a1->ss.ss_family != a2->ss_family) {
		if (a1->ss.ss_family < a2->ss_family) {
95
			return -1;
96
		} else {
97
			return 1;
98
		}
99 100
	}

101 102 103 104 105 106 107 108 109
	/* At most 4xchunk_bits for IPv6 */
	unsigned i = 0;
	while (ret == 0 && mask_bits > 0) {
		/* Compute mask for current chunk. */
		if (mask_bits <= chunk_bits) {
			mask = acl_fill_mask32(mask_bits);
			mask_bits = 0; /* Last chunk */
		} else {
			mask_bits -= chunk_bits;
110 111
		}

112
		/* Empty mask - shortcut, we're done. */
113
		if (mask > 0) {
114
			ret = cmp_chunk(a1, a2, i, mask);
115
		}
116
		++i;
117 118
	}

119
	return ret;
120 121
}

122
acl_t *acl_new()
123
{
124 125 126
	acl_t *acl = malloc(sizeof(acl_t));
	if (acl == NULL) {
		return NULL;
127
	}
128

129 130
	memset(acl, 0, sizeof(acl_t));
	init_list(acl);
131 132 133 134 135
	return acl;
}

void acl_delete(acl_t **acl)
{
136
	if (acl == NULL || *acl == NULL) {
137 138 139
		return;
	}

140
	acl_truncate(*acl);
141 142 143 144 145 146

	/* Free ACL. */
	free(*acl);
	*acl = 0;
}

147
int acl_insert(acl_t *acl, const struct sockaddr_storage *addr, uint8_t prefix, knot_tsig_key_t *key)
148
{
149 150
	if (acl == NULL || addr == NULL) {
		return KNOT_EINVAL;
151 152
	}

153
	/* Create new match. */
154 155
	acl_match_t *match = malloc(sizeof(acl_match_t));
	if (match == NULL) {
156
		return KNOT_ENOMEM;
157
	}
Jan Včelák's avatar
Jan Včelák committed
158

159 160
	match->netblock.prefix = prefix;
	memcpy(&match->netblock.ss, addr, sizeof(struct sockaddr_storage));
161
	match->key = key;
162

163
	add_tail(acl, &match->n);
164

165
	return KNOT_EOK;
166 167
}

168
acl_match_t* acl_find(acl_t *acl, const struct sockaddr_storage *addr, const knot_dname_t *key_name)
169
{
170 171
	if (acl == NULL || addr == NULL) {
		return NULL;
172 173
	}

174 175
	acl_match_t *cur = NULL;
	WALK_LIST(cur, *acl) {
176
		if (addr_match(&cur->netblock, addr) == 0) {
177 178 179 180 181 182 183 184
			/* NOKEY entry. */
			if (cur->key == NULL) {
				if (key_name == NULL) {
					return cur;
				}
				/* NOKEY entry, but key provided. */
				continue;
			}
185

Marek Vavrusa's avatar
Marek Vavrusa committed
186 187 188 189 190
			/* NOKEY provided, but key required. */
			if (key_name == NULL) {
				continue;
			}

191 192 193 194
			/* Key name match. */
			if (knot_dname_is_equal(cur->key->name, key_name)) {
				return cur;
			}
195
		}
196 197
	}

198
	return NULL;
199 200
}

201
void acl_truncate(acl_t *acl)
202
{
203
	if (acl == NULL) {
204
		return;
205
	}
206

207
	WALK_LIST_FREE(*acl);
208
}