acl.c 3.77 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 "libknot/libknot.h"
26
#include "knot/updates/acl.h"
27
#include "knot/conf/conf.h"
28
#include "libknot/internal/endian.h"
29
#include "libknot/rrtype/tsig.h"
30

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

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

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

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

	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);
80
	}
81

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

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

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

102 103 104 105 106 107 108 109 110
	/* 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;
111 112
		}

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

120
	return ret;
121 122
}

123
struct conf_iface* acl_find(list_t *acl, const struct sockaddr_storage *addr,
124
                              const knot_dname_t *key_name)
125
{
126 127
	if (acl == NULL || addr == NULL) {
		return NULL;
128 129
	}

130 131 132 133
	conf_remote_t *remote = NULL;
	WALK_LIST(remote, *acl) {
		conf_iface_t *cur = remote->remote;
		if (netblock_match(cur, addr) == 0) {
134 135 136 137 138 139 140 141
			/* NOKEY entry. */
			if (cur->key == NULL) {
				if (key_name == NULL) {
					return cur;
				}
				/* NOKEY entry, but key provided. */
				continue;
			}
142

Marek Vavrusa's avatar
Marek Vavrusa committed
143 144 145 146 147
			/* NOKEY provided, but key required. */
			if (key_name == NULL) {
				continue;
			}

148 149 150 151
			/* Key name match. */
			if (knot_dname_is_equal(cur->key->name, key_name)) {
				return cur;
			}
152
		}
153 154
	}

155
	return NULL;
156
}