PageRenderTime 71ms CodeModel.GetById 17ms app.highlight 48ms RepoModel.GetById 2ms app.codeStats 0ms

/contrib/bind9/lib/dns/acl.c

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