1/*
2 * Copyright (C) 2004-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#include <config.h>
21
22#include <isc/mem.h>
23#include <isc/string.h>		/* Required for HP/UX (and others?) */
24#include <isc/util.h>
25
26#include <isccfg/namedconf.h>
27#include <isccfg/aclconf.h>
28
29#include <dns/acl.h>
30#include <dns/iptable.h>
31#include <dns/fixedname.h>
32#include <dns/log.h>
33
34#define LOOP_MAGIC ISC_MAGIC('L','O','O','P')
35
36isc_result_t
37cfg_aclconfctx_create(isc_mem_t *mctx, cfg_aclconfctx_t **ret) {
38	isc_result_t result;
39	cfg_aclconfctx_t *actx;
40
41	REQUIRE(mctx != NULL);
42	REQUIRE(ret != NULL && *ret == NULL);
43
44	actx = isc_mem_get(mctx, sizeof(*actx));
45	if (actx == NULL)
46		return (ISC_R_NOMEMORY);
47
48	result = isc_refcount_init(&actx->references, 1);
49	if (result != ISC_R_SUCCESS)
50		goto cleanup;
51
52	actx->mctx = NULL;
53	isc_mem_attach(mctx, &actx->mctx);
54	ISC_LIST_INIT(actx->named_acl_cache);
55
56	*ret = actx;
57	return (ISC_R_SUCCESS);
58
59 cleanup:
60	isc_mem_put(mctx, actx, sizeof(*actx));
61	return (result);
62}
63
64void
65cfg_aclconfctx_attach(cfg_aclconfctx_t *src, cfg_aclconfctx_t **dest) {
66	REQUIRE(src != NULL);
67	REQUIRE(dest != NULL && *dest == NULL);
68
69	isc_refcount_increment(&src->references, NULL);
70	*dest = src;
71}
72
73void
74cfg_aclconfctx_detach(cfg_aclconfctx_t **actxp) {
75	cfg_aclconfctx_t *actx;
76	dns_acl_t *dacl, *next;
77	unsigned int refs;
78
79	REQUIRE(actxp != NULL && *actxp != NULL);
80
81	actx = *actxp;
82
83	isc_refcount_decrement(&actx->references, &refs);
84	if (refs == 0) {
85		for (dacl = ISC_LIST_HEAD(actx->named_acl_cache);
86		     dacl != NULL;
87		     dacl = next)
88		{
89			next = ISC_LIST_NEXT(dacl, nextincache);
90			ISC_LIST_UNLINK(actx->named_acl_cache, dacl,
91					nextincache);
92			dns_acl_detach(&dacl);
93		}
94		isc_mem_putanddetach(&actx->mctx, actx, sizeof(*actx));
95	}
96
97	*actxp = NULL;
98}
99
100/*
101 * Find the definition of the named acl whose name is "name".
102 */
103static isc_result_t
104get_acl_def(const cfg_obj_t *cctx, const char *name, const cfg_obj_t **ret) {
105	isc_result_t result;
106	const cfg_obj_t *acls = NULL;
107	const cfg_listelt_t *elt;
108
109	result = cfg_map_get(cctx, "acl", &acls);
110	if (result != ISC_R_SUCCESS)
111		return (result);
112	for (elt = cfg_list_first(acls);
113	     elt != NULL;
114	     elt = cfg_list_next(elt)) {
115		const cfg_obj_t *acl = cfg_listelt_value(elt);
116		const char *aclname = cfg_obj_asstring(cfg_tuple_get(acl, "name"));
117		if (strcasecmp(aclname, name) == 0) {
118			if (ret != NULL) {
119				*ret = cfg_tuple_get(acl, "value");
120			}
121			return (ISC_R_SUCCESS);
122		}
123	}
124	return (ISC_R_NOTFOUND);
125}
126
127static isc_result_t
128convert_named_acl(const cfg_obj_t *nameobj, const cfg_obj_t *cctx,
129		  isc_log_t *lctx, cfg_aclconfctx_t *ctx,
130		  isc_mem_t *mctx, unsigned int nest_level,
131		  dns_acl_t **target)
132{
133	isc_result_t result;
134	const cfg_obj_t *cacl = NULL;
135	dns_acl_t *dacl;
136	dns_acl_t loop;
137	const char *aclname = cfg_obj_asstring(nameobj);
138
139	/* Look for an already-converted version. */
140	for (dacl = ISC_LIST_HEAD(ctx->named_acl_cache);
141	     dacl != NULL;
142	     dacl = ISC_LIST_NEXT(dacl, nextincache))
143	{
144		if (strcasecmp(aclname, dacl->name) == 0) {
145			if (ISC_MAGIC_VALID(dacl, LOOP_MAGIC)) {
146				cfg_obj_log(nameobj, lctx, ISC_LOG_ERROR,
147					    "acl loop detected: %s", aclname);
148				return (ISC_R_FAILURE);
149			}
150			dns_acl_attach(dacl, target);
151			return (ISC_R_SUCCESS);
152		}
153	}
154	/* Not yet converted.  Convert now. */
155	result = get_acl_def(cctx, aclname, &cacl);
156	if (result != ISC_R_SUCCESS) {
157		cfg_obj_log(nameobj, lctx, ISC_LOG_WARNING,
158			    "undefined ACL '%s'", aclname);
159		return (result);
160	}
161	/*
162	 * Add a loop detection element.
163	 */
164	memset(&loop, 0, sizeof(loop));
165	ISC_LINK_INIT(&loop, nextincache);
166	DE_CONST(aclname, loop.name);
167	loop.magic = LOOP_MAGIC;
168	ISC_LIST_APPEND(ctx->named_acl_cache, &loop, nextincache);
169	result = cfg_acl_fromconfig(cacl, cctx, lctx, ctx, mctx,
170				    nest_level, &dacl);
171	ISC_LIST_UNLINK(ctx->named_acl_cache, &loop, nextincache);
172	loop.magic = 0;
173	loop.name = NULL;
174	if (result != ISC_R_SUCCESS)
175		return (result);
176	dacl->name = isc_mem_strdup(dacl->mctx, aclname);
177	if (dacl->name == NULL)
178		return (ISC_R_NOMEMORY);
179	ISC_LIST_APPEND(ctx->named_acl_cache, dacl, nextincache);
180	dns_acl_attach(dacl, target);
181	return (ISC_R_SUCCESS);
182}
183
184static isc_result_t
185convert_keyname(const cfg_obj_t *keyobj, isc_log_t *lctx, isc_mem_t *mctx,
186		dns_name_t *dnsname)
187{
188	isc_result_t result;
189	isc_buffer_t buf;
190	dns_fixedname_t fixname;
191	unsigned int keylen;
192	const char *txtname = cfg_obj_asstring(keyobj);
193
194	keylen = strlen(txtname);
195	isc_buffer_init(&buf, txtname, keylen);
196	isc_buffer_add(&buf, keylen);
197	dns_fixedname_init(&fixname);
198	result = dns_name_fromtext(dns_fixedname_name(&fixname), &buf,
199				   dns_rootname, 0, NULL);
200	if (result != ISC_R_SUCCESS) {
201		cfg_obj_log(keyobj, lctx, ISC_LOG_WARNING,
202			    "key name '%s' is not a valid domain name",
203			    txtname);
204		return (result);
205	}
206	return (dns_name_dup(dns_fixedname_name(&fixname), mctx, dnsname));
207}
208
209/*
210 * Recursively pre-parse an ACL definition to find the total number
211 * of non-IP-prefix elements (localhost, localnets, key) in all nested
212 * ACLs, so that the parent will have enough space allocated for the
213 * elements table after all the nested ACLs have been merged in to the
214 * parent.
215 */
216static int
217count_acl_elements(const cfg_obj_t *caml, const cfg_obj_t *cctx,
218		   isc_boolean_t *has_negative)
219{
220	const cfg_listelt_t *elt;
221	const cfg_obj_t *cacl = NULL;
222	isc_result_t result;
223	int n = 0;
224
225	if (has_negative != NULL)
226		*has_negative = ISC_FALSE;
227
228	for (elt = cfg_list_first(caml);
229	     elt != NULL;
230	     elt = cfg_list_next(elt)) {
231		const cfg_obj_t *ce = cfg_listelt_value(elt);
232
233		/* negated element; just get the value. */
234		if (cfg_obj_istuple(ce)) {
235			ce = cfg_tuple_get(ce, "value");
236			if (has_negative != NULL)
237				*has_negative = ISC_TRUE;
238		}
239
240		if (cfg_obj_istype(ce, &cfg_type_keyref)) {
241			n++;
242		} else if (cfg_obj_islist(ce)) {
243			isc_boolean_t negative;
244			n += count_acl_elements(ce, cctx, &negative);
245			if (negative)
246				n++;
247		} else if (cfg_obj_isstring(ce)) {
248			const char *name = cfg_obj_asstring(ce);
249			if (strcasecmp(name, "localhost") == 0 ||
250			    strcasecmp(name, "localnets") == 0) {
251				n++;
252			} else if (strcasecmp(name, "any") != 0 &&
253				   strcasecmp(name, "none") != 0) {
254				result = get_acl_def(cctx, name, &cacl);
255				if (result == ISC_R_SUCCESS)
256					n += count_acl_elements(cacl, cctx,
257								NULL) + 1;
258			}
259		}
260	}
261
262	return n;
263}
264
265isc_result_t
266cfg_acl_fromconfig(const cfg_obj_t *caml,
267		   const cfg_obj_t *cctx,
268		   isc_log_t *lctx,
269		   cfg_aclconfctx_t *ctx,
270		   isc_mem_t *mctx,
271		   unsigned int nest_level,
272		   dns_acl_t **target)
273{
274	isc_result_t result;
275	dns_acl_t *dacl = NULL, *inneracl = NULL;
276	dns_aclelement_t *de;
277	const cfg_listelt_t *elt;
278	dns_iptable_t *iptab;
279	int new_nest_level = 0;
280
281	if (nest_level != 0)
282		new_nest_level = nest_level - 1;
283
284	REQUIRE(target != NULL);
285	REQUIRE(*target == NULL || DNS_ACL_VALID(*target));
286
287	if (*target != NULL) {
288		/*
289		 * If target already points to an ACL, then we're being
290		 * called recursively to configure a nested ACL.  The
291		 * nested ACL's contents should just be absorbed into its
292		 * parent ACL.
293		 */
294		dns_acl_attach(*target, &dacl);
295		dns_acl_detach(target);
296	} else {
297		/*
298		 * Need to allocate a new ACL structure.  Count the items
299		 * in the ACL definition that will require space in the
300		 * elements table.  (Note that if nest_level is nonzero,
301		 * *everything* goes in the elements table.)
302		 */
303		int nelem;
304
305		if (nest_level == 0)
306			nelem = count_acl_elements(caml, cctx, NULL);
307		else
308			nelem = cfg_list_length(caml, ISC_FALSE);
309
310		result = dns_acl_create(mctx, nelem, &dacl);
311		if (result != ISC_R_SUCCESS)
312			return (result);
313	}
314
315	de = dacl->elements;
316	for (elt = cfg_list_first(caml);
317	     elt != NULL;
318	     elt = cfg_list_next(elt)) {
319		const cfg_obj_t *ce = cfg_listelt_value(elt);
320		isc_boolean_t	neg;
321
322		if (cfg_obj_istuple(ce)) {
323			/* This must be a negated element. */
324			ce = cfg_tuple_get(ce, "value");
325			neg = ISC_TRUE;
326			dacl->has_negatives = ISC_TRUE;
327		} else
328			neg = ISC_FALSE;
329
330		/*
331		 * If nest_level is nonzero, then every element is
332		 * to be stored as a separate, nested ACL rather than
333		 * merged into the main iptable.
334		 */
335		iptab = dacl->iptable;
336
337		if (nest_level != 0) {
338			result = dns_acl_create(mctx,
339						cfg_list_length(ce, ISC_FALSE),
340						&de->nestedacl);
341			if (result != ISC_R_SUCCESS)
342				goto cleanup;
343			iptab = de->nestedacl->iptable;
344		}
345
346		if (cfg_obj_isnetprefix(ce)) {
347			/* Network prefix */
348			isc_netaddr_t	addr;
349			unsigned int	bitlen;
350
351			cfg_obj_asnetprefix(ce, &addr, &bitlen);
352
353			/*
354			 * If nesting ACLs (nest_level != 0), we negate
355			 * the nestedacl element, not the iptable entry.
356			 */
357			result = dns_iptable_addprefix(iptab, &addr, bitlen,
358					      ISC_TF(nest_level != 0 || !neg));
359			if (result != ISC_R_SUCCESS)
360				goto cleanup;
361
362			if (nest_level > 0) {
363				de->type = dns_aclelementtype_nestedacl;
364				de->negative = neg;
365			} else
366				continue;
367		} else if (cfg_obj_islist(ce)) {
368			/*
369			 * If we're nesting ACLs, put the nested
370			 * ACL onto the elements list; otherwise
371			 * merge it into *this* ACL.  We nest ACLs
372			 * in two cases: 1) sortlist, 2) if the
373			 * nested ACL contains negated members.
374			 */
375			if (inneracl != NULL)
376				dns_acl_detach(&inneracl);
377			result = cfg_acl_fromconfig(ce, cctx, lctx,
378						    ctx, mctx, new_nest_level,
379						    &inneracl);
380			if (result != ISC_R_SUCCESS)
381				goto cleanup;
382nested_acl:
383			if (nest_level > 0 || inneracl->has_negatives) {
384				de->type = dns_aclelementtype_nestedacl;
385				de->negative = neg;
386				if (de->nestedacl != NULL)
387					dns_acl_detach(&de->nestedacl);
388				dns_acl_attach(inneracl,
389					       &de->nestedacl);
390				dns_acl_detach(&inneracl);
391				/* Fall through. */
392			} else {
393				dns_acl_merge(dacl, inneracl,
394					      ISC_TF(!neg));
395				de += inneracl->length;  /* elements added */
396				dns_acl_detach(&inneracl);
397				continue;
398			}
399		} else if (cfg_obj_istype(ce, &cfg_type_keyref)) {
400			/* Key name. */
401			de->type = dns_aclelementtype_keyname;
402			de->negative = neg;
403			dns_name_init(&de->keyname, NULL);
404			result = convert_keyname(ce, lctx, mctx,
405						 &de->keyname);
406			if (result != ISC_R_SUCCESS)
407				goto cleanup;
408		} else if (cfg_obj_isstring(ce)) {
409			/* ACL name. */
410			const char *name = cfg_obj_asstring(ce);
411			if (strcasecmp(name, "any") == 0) {
412				/* Iptable entry with zero bit length. */
413				result = dns_iptable_addprefix(iptab, NULL, 0,
414					      ISC_TF(nest_level != 0 || !neg));
415				if (result != ISC_R_SUCCESS)
416					goto cleanup;
417
418				if (nest_level != 0) {
419					de->type = dns_aclelementtype_nestedacl;
420					de->negative = neg;
421				} else
422					continue;
423			} else if (strcasecmp(name, "none") == 0) {
424				/* none == !any */
425				/*
426				 * We don't unconditional set
427				 * dacl->has_negatives and
428				 * de->negative to true so we can handle
429				 * "!none;".
430				 */
431				result = dns_iptable_addprefix(iptab, NULL, 0,
432					      ISC_TF(nest_level != 0 || neg));
433				if (result != ISC_R_SUCCESS)
434					goto cleanup;
435
436				if (!neg)
437					dacl->has_negatives = !neg;
438
439				if (nest_level != 0) {
440					de->type = dns_aclelementtype_nestedacl;
441					de->negative = !neg;
442				} else
443					continue;
444			} else if (strcasecmp(name, "localhost") == 0) {
445				de->type = dns_aclelementtype_localhost;
446				de->negative = neg;
447			} else if (strcasecmp(name, "localnets") == 0) {
448				de->type = dns_aclelementtype_localnets;
449				de->negative = neg;
450			} else {
451				if (inneracl != NULL)
452					dns_acl_detach(&inneracl);
453				result = convert_named_acl(ce, cctx, lctx, ctx,
454							   mctx, new_nest_level,
455							   &inneracl);
456				if (result != ISC_R_SUCCESS)
457					goto cleanup;
458
459				goto nested_acl;
460			}
461		} else {
462			cfg_obj_log(ce, lctx, ISC_LOG_WARNING,
463				    "address match list contains "
464				    "unsupported element type");
465			result = ISC_R_FAILURE;
466			goto cleanup;
467		}
468
469		/*
470		 * This should only be reached for localhost, localnets
471		 * and keyname elements, and nested ACLs if nest_level is
472		 * nonzero (i.e., in sortlists).
473		 */
474		if (de->nestedacl != NULL &&
475		    de->type != dns_aclelementtype_nestedacl)
476			dns_acl_detach(&de->nestedacl);
477
478		dacl->node_count++;
479		de->node_num = dacl->node_count;
480
481		dacl->length++;
482		de++;
483		INSIST(dacl->length <= dacl->alloc);
484	}
485
486	dns_acl_attach(dacl, target);
487	result = ISC_R_SUCCESS;
488
489 cleanup:
490	if (inneracl != NULL)
491		dns_acl_detach(&inneracl);
492	dns_acl_detach(&dacl);
493	return (result);
494}
495