1/*	$NetBSD$	*/
2
3/*
4 * Copyright (C) 2004-2009, 2011  Internet Systems Consortium, Inc. ("ISC")
5 * Copyright (C) 1999-2002  Internet Software Consortium.
6 *
7 * Permission to use, copy, modify, and/or distribute this software for any
8 * purpose with or without fee is hereby granted, provided that the above
9 * copyright notice and this permission notice appear in all copies.
10 *
11 * THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
12 * REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
13 * AND FITNESS.  IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
14 * INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
15 * LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
16 * OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
17 * PERFORMANCE OF THIS SOFTWARE.
18 */
19
20/* Id: acl.c,v 1.55 2011/06/17 23:47:49 tbox Exp  */
21
22/*! \file */
23
24#include <config.h>
25
26#include <isc/mem.h>
27#include <isc/once.h>
28#include <isc/string.h>
29#include <isc/util.h>
30
31#include <dns/acl.h>
32#include <dns/iptable.h>
33
34/*
35 * Create a new ACL, including an IP table and an array with room
36 * for 'n' ACL elements.  The elements are uninitialized and the
37 * length is 0.
38 */
39isc_result_t
40dns_acl_create(isc_mem_t *mctx, int n, dns_acl_t **target) {
41	isc_result_t result;
42	dns_acl_t *acl;
43
44	/*
45	 * Work around silly limitation of isc_mem_get().
46	 */
47	if (n == 0)
48		n = 1;
49
50	acl = isc_mem_get(mctx, sizeof(*acl));
51	if (acl == NULL)
52		return (ISC_R_NOMEMORY);
53	acl->mctx = mctx;
54	acl->name = NULL;
55
56	result = isc_refcount_init(&acl->refcount, 1);
57	if (result != ISC_R_SUCCESS) {
58		isc_mem_put(mctx, acl, sizeof(*acl));
59		return (result);
60	}
61
62	result = dns_iptable_create(mctx, &acl->iptable);
63	if (result != ISC_R_SUCCESS) {
64		isc_mem_put(mctx, acl, sizeof(*acl));
65		return (result);
66	}
67
68	acl->elements = NULL;
69	acl->alloc = 0;
70	acl->length = 0;
71	acl->has_negatives = ISC_FALSE;
72
73	ISC_LINK_INIT(acl, nextincache);
74	/*
75	 * Must set magic early because we use dns_acl_detach() to clean up.
76	 */
77	acl->magic = DNS_ACL_MAGIC;
78
79	acl->elements = isc_mem_get(mctx, n * sizeof(dns_aclelement_t));
80	if (acl->elements == NULL) {
81		result = ISC_R_NOMEMORY;
82		goto cleanup;
83	}
84	acl->alloc = n;
85	memset(acl->elements, 0, n * sizeof(dns_aclelement_t));
86	*target = acl;
87	return (ISC_R_SUCCESS);
88
89 cleanup:
90	dns_acl_detach(&acl);
91	return (result);
92}
93
94/*
95 * Create a new ACL and initialize it with the value "any" or "none",
96 * depending on the value of the "neg" parameter.
97 * "any" is a positive iptable entry with bit length 0.
98 * "none" is the same as "!any".
99 */
100static isc_result_t
101dns_acl_anyornone(isc_mem_t *mctx, isc_boolean_t neg, dns_acl_t **target) {
102	isc_result_t result;
103	dns_acl_t *acl = NULL;
104
105	result = dns_acl_create(mctx, 0, &acl);
106	if (result != ISC_R_SUCCESS)
107		return (result);
108
109	result = dns_iptable_addprefix(acl->iptable, NULL, 0, ISC_TF(!neg));
110	if (result != ISC_R_SUCCESS) {
111		dns_acl_detach(&acl);
112		return (result);
113	}
114
115	*target = acl;
116	return (result);
117}
118
119/*
120 * Create a new ACL that matches everything.
121 */
122isc_result_t
123dns_acl_any(isc_mem_t *mctx, dns_acl_t **target) {
124	return (dns_acl_anyornone(mctx, ISC_FALSE, target));
125}
126
127/*
128 * Create a new ACL that matches nothing.
129 */
130isc_result_t
131dns_acl_none(isc_mem_t *mctx, dns_acl_t **target) {
132	return (dns_acl_anyornone(mctx, ISC_TRUE, target));
133}
134
135/*
136 * If pos is ISC_TRUE, test whether acl is set to "{ any; }"
137 * If pos is ISC_FALSE, test whether acl is set to "{ none; }"
138 */
139static isc_boolean_t
140dns_acl_isanyornone(dns_acl_t *acl, isc_boolean_t pos)
141{
142	/* Should never happen but let's be safe */
143	if (acl == NULL ||
144	    acl->iptable == NULL ||
145	    acl->iptable->radix == NULL ||
146	    acl->iptable->radix->head == NULL ||
147	    acl->iptable->radix->head->prefix == NULL)
148		return (ISC_FALSE);
149
150	if (acl->length != 0 || acl->node_count != 1)
151		return (ISC_FALSE);
152
153	if (acl->iptable->radix->head->prefix->bitlen == 0 &&
154	    acl->iptable->radix->head->data[0] != NULL &&
155	    acl->iptable->radix->head->data[0] ==
156		    acl->iptable->radix->head->data[1] &&
157	    *(isc_boolean_t *) (acl->iptable->radix->head->data[0]) == pos)
158		return (ISC_TRUE);
159
160	return (ISC_FALSE); /* All others */
161}
162
163/*
164 * Test whether acl is set to "{ any; }"
165 */
166isc_boolean_t
167dns_acl_isany(dns_acl_t *acl)
168{
169	return (dns_acl_isanyornone(acl, ISC_TRUE));
170}
171
172/*
173 * Test whether acl is set to "{ none; }"
174 */
175isc_boolean_t
176dns_acl_isnone(dns_acl_t *acl)
177{
178	return (dns_acl_isanyornone(acl, ISC_FALSE));
179}
180
181/*
182 * Determine whether a given address or signer matches a given ACL.
183 * For a match with a positive ACL element or iptable radix entry,
184 * return with a positive value in match; for a match with a negated ACL
185 * element or radix entry, return with a negative value in match.
186 */
187isc_result_t
188dns_acl_match(const isc_netaddr_t *reqaddr,
189	      const dns_name_t *reqsigner,
190	      const dns_acl_t *acl,
191	      const dns_aclenv_t *env,
192	      int *match,
193	      const dns_aclelement_t **matchelt)
194{
195	isc_uint16_t bitlen, family;
196	isc_prefix_t pfx;
197	isc_radix_node_t *node = NULL;
198	const isc_netaddr_t *addr;
199	isc_netaddr_t v4addr;
200	isc_result_t result;
201	int match_num = -1;
202	unsigned int i;
203
204	REQUIRE(reqaddr != NULL);
205	REQUIRE(matchelt == NULL || *matchelt == NULL);
206
207	if (env == NULL || env->match_mapped == ISC_FALSE ||
208	    reqaddr->family != AF_INET6 ||
209	    !IN6_IS_ADDR_V4MAPPED(&reqaddr->type.in6))
210		addr = reqaddr;
211	else {
212		isc_netaddr_fromv4mapped(&v4addr, reqaddr);
213		addr = &v4addr;
214	}
215
216	/* Always match with host addresses. */
217	family = addr->family;
218	bitlen = family == AF_INET6 ? 128 : 32;
219	NETADDR_TO_PREFIX_T(addr, pfx, bitlen);
220
221	/* Assume no match. */
222	*match = 0;
223
224	/* Search radix. */
225	result = isc_radix_search(acl->iptable->radix, &node, &pfx);
226
227	/* Found a match. */
228	if (result == ISC_R_SUCCESS && node != NULL) {
229		match_num = node->node_num[ISC_IS6(family)];
230		if (*(isc_boolean_t *) node->data[ISC_IS6(family)] == ISC_TRUE)
231			*match = match_num;
232		else
233			*match = -match_num;
234	}
235
236	/* Now search non-radix elements for a match with a lower node_num. */
237	for (i = 0; i < acl->length; i++) {
238		dns_aclelement_t *e = &acl->elements[i];
239
240		/* Already found a better match? */
241		if (match_num != -1 && match_num < e->node_num) {
242			isc_refcount_destroy(&pfx.refcount);
243			return (ISC_R_SUCCESS);
244		}
245
246		if (dns_aclelement_match(reqaddr, reqsigner,
247					 e, env, matchelt)) {
248			if (match_num == -1 || e->node_num < match_num) {
249				if (e->negative == ISC_TRUE)
250					*match = -e->node_num;
251				else
252					*match = e->node_num;
253			}
254			isc_refcount_destroy(&pfx.refcount);
255			return (ISC_R_SUCCESS);
256		}
257	}
258
259	isc_refcount_destroy(&pfx.refcount);
260	return (ISC_R_SUCCESS);
261}
262
263/*
264 * Merge the contents of one ACL into another.  Call dns_iptable_merge()
265 * for the IP tables, then concatenate the element arrays.
266 *
267 * If pos is set to false, then the nested ACL is to be negated.  This
268 * means reverse the sense of each *positive* element or IP table node,
269 * but leave negatives alone, so as to prevent a double-negative causing
270 * an unexpected positive match in the parent ACL.
271 */
272isc_result_t
273dns_acl_merge(dns_acl_t *dest, dns_acl_t *source, isc_boolean_t pos)
274{
275	isc_result_t result;
276	unsigned int newalloc, nelem, i;
277	int max_node = 0, nodes;
278
279	/* Resize the element array if needed. */
280	if (dest->length + source->length > dest->alloc) {
281		void *newmem;
282
283		newalloc = dest->alloc + source->alloc;
284		if (newalloc < 4)
285			newalloc = 4;
286
287		newmem = isc_mem_get(dest->mctx,
288				     newalloc * sizeof(dns_aclelement_t));
289		if (newmem == NULL)
290			return (ISC_R_NOMEMORY);
291
292		/* Copy in the original elements */
293		memcpy(newmem, dest->elements,
294		       dest->length * sizeof(dns_aclelement_t));
295
296		/* Release the memory for the old elements array */
297		isc_mem_put(dest->mctx, dest->elements,
298			    dest->alloc * sizeof(dns_aclelement_t));
299		dest->elements = newmem;
300		dest->alloc = newalloc;
301	}
302
303	/*
304	 * Now copy in the new elements, increasing their node_num
305	 * values so as to keep the new ACL consistent.  If we're
306	 * negating, then negate positive elements, but keep negative
307	 * elements the same for security reasons.
308	 */
309	nelem = dest->length;
310	dest->length += source->length;
311	for (i = 0; i < source->length; i++) {
312		if (source->elements[i].node_num > max_node)
313			max_node = source->elements[i].node_num;
314
315		/* Copy type. */
316		dest->elements[nelem + i].type = source->elements[i].type;
317
318		/* Adjust node numbering. */
319		dest->elements[nelem + i].node_num =
320			source->elements[i].node_num + dest->node_count;
321
322		/* Duplicate nested acl. */
323		if (source->elements[i].type == dns_aclelementtype_nestedacl &&
324		   source->elements[i].nestedacl != NULL)
325			dns_acl_attach(source->elements[i].nestedacl,
326				       &dest->elements[nelem + i].nestedacl);
327
328		/* Duplicate key name. */
329		if (source->elements[i].type == dns_aclelementtype_keyname) {
330			dns_name_init(&dest->elements[nelem+i].keyname, NULL);
331			result = dns_name_dup(&source->elements[i].keyname,
332					      dest->mctx,
333					      &dest->elements[nelem+i].keyname);
334			if (result != ISC_R_SUCCESS)
335				return result;
336		}
337
338		/* reverse sense of positives if this is a negative acl */
339		if (!pos && source->elements[i].negative == ISC_FALSE) {
340			dest->elements[nelem + i].negative = ISC_TRUE;
341		} else {
342			dest->elements[nelem + i].negative =
343				source->elements[i].negative;
344		}
345	}
346
347	/*
348	 * Merge the iptables.  Make sure the destination ACL's
349	 * node_count value is set correctly afterward.
350	 */
351	nodes = max_node + dest->node_count;
352	result = dns_iptable_merge(dest->iptable, source->iptable, pos);
353	if (result != ISC_R_SUCCESS)
354		return (result);
355	if (nodes > dest->node_count)
356		dest->node_count = nodes;
357
358	return (ISC_R_SUCCESS);
359}
360
361/*
362 * Like dns_acl_match, but matches against the single ACL element 'e'
363 * rather than a complete ACL, and returns ISC_TRUE iff it matched.
364 *
365 * To determine whether the match was positive or negative, the
366 * caller should examine e->negative.  Since the element 'e' may be
367 * a reference to a named ACL or a nested ACL, a matching element
368 * returned through 'matchelt' is not necessarily 'e' itself.
369 */
370isc_boolean_t
371dns_aclelement_match(const isc_netaddr_t *reqaddr,
372		     const dns_name_t *reqsigner,
373		     const dns_aclelement_t *e,
374		     const dns_aclenv_t *env,
375		     const dns_aclelement_t **matchelt)
376{
377	dns_acl_t *inner = NULL;
378	int indirectmatch;
379	isc_result_t result;
380
381	switch (e->type) {
382	case dns_aclelementtype_keyname:
383		if (reqsigner != NULL &&
384		    dns_name_equal(reqsigner, &e->keyname)) {
385			if (matchelt != NULL)
386				*matchelt = e;
387			return (ISC_TRUE);
388		} else {
389			return (ISC_FALSE);
390		}
391
392	case dns_aclelementtype_nestedacl:
393		inner = e->nestedacl;
394		break;
395
396	case dns_aclelementtype_localhost:
397		if (env == NULL || env->localhost == NULL)
398			return (ISC_FALSE);
399		inner = env->localhost;
400		break;
401
402	case dns_aclelementtype_localnets:
403		if (env == NULL || env->localnets == NULL)
404			return (ISC_FALSE);
405		inner = env->localnets;
406		break;
407
408	default:
409		/* Should be impossible. */
410		INSIST(0);
411	}
412
413	result = dns_acl_match(reqaddr, reqsigner, inner, env,
414			       &indirectmatch, matchelt);
415	INSIST(result == ISC_R_SUCCESS);
416
417	/*
418	 * Treat negative matches in indirect ACLs as "no match".
419	 * That way, a negated indirect ACL will never become a
420	 * surprise positive match through double negation.
421	 * XXXDCL this should be documented.
422	 */
423
424	if (indirectmatch > 0) {
425		if (matchelt != NULL)
426			*matchelt = e;
427		return (ISC_TRUE);
428	}
429
430	/*
431	 * A negative indirect match may have set *matchelt, but we don't
432	 * want it set when we return.
433	 */
434
435	if (matchelt != NULL)
436		*matchelt = NULL;
437
438	return (ISC_FALSE);
439}
440
441void
442dns_acl_attach(dns_acl_t *source, dns_acl_t **target) {
443	REQUIRE(DNS_ACL_VALID(source));
444
445	isc_refcount_increment(&source->refcount, NULL);
446	*target = source;
447}
448
449static void
450destroy(dns_acl_t *dacl) {
451	unsigned int i;
452
453	INSIST(!ISC_LINK_LINKED(dacl, nextincache));
454
455	for (i = 0; i < dacl->length; i++) {
456		dns_aclelement_t *de = &dacl->elements[i];
457		if (de->type == dns_aclelementtype_keyname) {
458			dns_name_free(&de->keyname, dacl->mctx);
459		} else if (de->type == dns_aclelementtype_nestedacl) {
460			dns_acl_detach(&de->nestedacl);
461		}
462	}
463	if (dacl->elements != NULL)
464		isc_mem_put(dacl->mctx, dacl->elements,
465			    dacl->alloc * sizeof(dns_aclelement_t));
466	if (dacl->name != NULL)
467		isc_mem_free(dacl->mctx, dacl->name);
468	if (dacl->iptable != NULL)
469		dns_iptable_detach(&dacl->iptable);
470	isc_refcount_destroy(&dacl->refcount);
471	dacl->magic = 0;
472	isc_mem_put(dacl->mctx, dacl, sizeof(*dacl));
473}
474
475void
476dns_acl_detach(dns_acl_t **aclp) {
477	dns_acl_t *acl = *aclp;
478	unsigned int refs;
479
480	REQUIRE(DNS_ACL_VALID(acl));
481
482	isc_refcount_decrement(&acl->refcount, &refs);
483	if (refs == 0)
484		destroy(acl);
485	*aclp = NULL;
486}
487
488
489static isc_once_t	insecure_prefix_once = ISC_ONCE_INIT;
490static isc_mutex_t	insecure_prefix_lock;
491static isc_boolean_t	insecure_prefix_found;
492
493static void
494initialize_action(void) {
495	RUNTIME_CHECK(isc_mutex_init(&insecure_prefix_lock) == ISC_R_SUCCESS);
496}
497
498/*
499 * Called via isc_radix_walk() to find IP table nodes that are
500 * insecure.
501 */
502static void
503is_insecure(isc_prefix_t *prefix, void **data) {
504	isc_boolean_t secure;
505	int bitlen, family;
506
507	bitlen = prefix->bitlen;
508	family = prefix->family;
509
510	/* Negated entries are always secure. */
511	secure = * (isc_boolean_t *)data[ISC_IS6(family)];
512	if (!secure) {
513		return;
514	}
515
516	/* If loopback prefix found, return */
517	switch (family) {
518	case AF_INET:
519		if (bitlen == 32 &&
520		    htonl(prefix->add.sin.s_addr) == INADDR_LOOPBACK)
521			return;
522		break;
523	case AF_INET6:
524		if (bitlen == 128 && IN6_IS_ADDR_LOOPBACK(&prefix->add.sin6))
525			return;
526		break;
527	default:
528		break;
529	}
530
531	/* Non-negated, non-loopback */
532	insecure_prefix_found = ISC_TRUE;	/* LOCKED */
533	return;
534}
535
536/*
537 * Return ISC_TRUE iff the acl 'a' is considered insecure, that is,
538 * if it contains IP addresses other than those of the local host.
539 * This is intended for applications such as printing warning
540 * messages for suspect ACLs; it is not intended for making access
541 * control decisions.  We make no guarantee that an ACL for which
542 * this function returns ISC_FALSE is safe.
543 */
544isc_boolean_t
545dns_acl_isinsecure(const dns_acl_t *a) {
546	unsigned int i;
547	isc_boolean_t insecure;
548
549	RUNTIME_CHECK(isc_once_do(&insecure_prefix_once,
550				  initialize_action) == ISC_R_SUCCESS);
551
552	/*
553	 * Walk radix tree to find out if there are any non-negated,
554	 * non-loopback prefixes.
555	 */
556	LOCK(&insecure_prefix_lock);
557	insecure_prefix_found = ISC_FALSE;
558	isc_radix_process(a->iptable->radix, is_insecure);
559	insecure = insecure_prefix_found;
560	UNLOCK(&insecure_prefix_lock);
561	if (insecure)
562		return(ISC_TRUE);
563
564	/* Now check non-radix elements */
565	for (i = 0; i < a->length; i++) {
566		dns_aclelement_t *e = &a->elements[i];
567
568		/* A negated match can never be insecure. */
569		if (e->negative)
570			continue;
571
572		switch (e->type) {
573		case dns_aclelementtype_keyname:
574		case dns_aclelementtype_localhost:
575			continue;
576
577		case dns_aclelementtype_nestedacl:
578			if (dns_acl_isinsecure(e->nestedacl))
579				return (ISC_TRUE);
580			continue;
581
582		case dns_aclelementtype_localnets:
583			return (ISC_TRUE);
584
585		default:
586			INSIST(0);
587			return (ISC_TRUE);
588		}
589	}
590
591	/* No insecure elements were found. */
592	return (ISC_FALSE);
593}
594
595/*
596 * Initialize ACL environment, setting up localhost and localnets ACLs
597 */
598isc_result_t
599dns_aclenv_init(isc_mem_t *mctx, dns_aclenv_t *env) {
600	isc_result_t result;
601
602	env->localhost = NULL;
603	env->localnets = NULL;
604	result = dns_acl_create(mctx, 0, &env->localhost);
605	if (result != ISC_R_SUCCESS)
606		goto cleanup_nothing;
607	result = dns_acl_create(mctx, 0, &env->localnets);
608	if (result != ISC_R_SUCCESS)
609		goto cleanup_localhost;
610	env->match_mapped = ISC_FALSE;
611	return (ISC_R_SUCCESS);
612
613 cleanup_localhost:
614	dns_acl_detach(&env->localhost);
615 cleanup_nothing:
616	return (result);
617}
618
619void
620dns_aclenv_copy(dns_aclenv_t *t, dns_aclenv_t *s) {
621	dns_acl_detach(&t->localhost);
622	dns_acl_attach(s->localhost, &t->localhost);
623	dns_acl_detach(&t->localnets);
624	dns_acl_attach(s->localnets, &t->localnets);
625	t->match_mapped = s->match_mapped;
626}
627
628void
629dns_aclenv_destroy(dns_aclenv_t *env) {
630	dns_acl_detach(&env->localhost);
631	dns_acl_detach(&env->localnets);
632}
633