summaryrefslogtreecommitdiffstats
path: root/lib/dns/acl.c
diff options
context:
space:
mode:
Diffstat (limited to '')
-rw-r--r--lib/dns/acl.c728
1 files changed, 728 insertions, 0 deletions
diff --git a/lib/dns/acl.c b/lib/dns/acl.c
new file mode 100644
index 0000000..c1108e8
--- /dev/null
+++ b/lib/dns/acl.c
@@ -0,0 +1,728 @@
+/*
+ * Copyright (C) Internet Systems Consortium, Inc. ("ISC")
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * See the COPYRIGHT file distributed with this work for additional
+ * information regarding copyright ownership.
+ */
+
+/*! \file */
+
+#include <config.h>
+
+#include <stdbool.h>
+
+#include <isc/mem.h>
+#include <isc/once.h>
+#include <isc/string.h>
+#include <isc/util.h>
+
+#include <dns/acl.h>
+#include <dns/iptable.h>
+
+
+/*
+ * Create a new ACL, including an IP table and an array with room
+ * for 'n' ACL elements. The elements are uninitialized and the
+ * length is 0.
+ */
+isc_result_t
+dns_acl_create(isc_mem_t *mctx, int n, dns_acl_t **target) {
+ isc_result_t result;
+ dns_acl_t *acl;
+
+ /*
+ * Work around silly limitation of isc_mem_get().
+ */
+ if (n == 0)
+ n = 1;
+
+ acl = isc_mem_get(mctx, sizeof(*acl));
+ if (acl == NULL)
+ return (ISC_R_NOMEMORY);
+
+ acl->mctx = NULL;
+ isc_mem_attach(mctx, &acl->mctx);
+
+ acl->name = NULL;
+
+ result = isc_refcount_init(&acl->refcount, 1);
+ if (result != ISC_R_SUCCESS) {
+ isc_mem_put(mctx, acl, sizeof(*acl));
+ return (result);
+ }
+
+ result = dns_iptable_create(mctx, &acl->iptable);
+ if (result != ISC_R_SUCCESS) {
+ isc_mem_put(mctx, acl, sizeof(*acl));
+ return (result);
+ }
+
+ acl->elements = NULL;
+ acl->alloc = 0;
+ acl->length = 0;
+ acl->has_negatives = false;
+
+ ISC_LINK_INIT(acl, nextincache);
+ /*
+ * Must set magic early because we use dns_acl_detach() to clean up.
+ */
+ acl->magic = DNS_ACL_MAGIC;
+
+ acl->elements = isc_mem_get(mctx, n * sizeof(dns_aclelement_t));
+ if (acl->elements == NULL) {
+ result = ISC_R_NOMEMORY;
+ goto cleanup;
+ }
+ acl->alloc = n;
+ memset(acl->elements, 0, n * sizeof(dns_aclelement_t));
+ *target = acl;
+ return (ISC_R_SUCCESS);
+
+ cleanup:
+ dns_acl_detach(&acl);
+ return (result);
+}
+
+/*
+ * Create a new ACL and initialize it with the value "any" or "none",
+ * depending on the value of the "neg" parameter.
+ * "any" is a positive iptable entry with bit length 0.
+ * "none" is the same as "!any".
+ */
+static isc_result_t
+dns_acl_anyornone(isc_mem_t *mctx, bool neg, dns_acl_t **target) {
+ isc_result_t result;
+ dns_acl_t *acl = NULL;
+
+ result = dns_acl_create(mctx, 0, &acl);
+ if (result != ISC_R_SUCCESS)
+ return (result);
+
+ result = dns_iptable_addprefix(acl->iptable, NULL, 0, !neg);
+ if (result != ISC_R_SUCCESS) {
+ dns_acl_detach(&acl);
+ return (result);
+ }
+
+ *target = acl;
+ return (result);
+}
+
+/*
+ * Create a new ACL that matches everything.
+ */
+isc_result_t
+dns_acl_any(isc_mem_t *mctx, dns_acl_t **target) {
+ return (dns_acl_anyornone(mctx, false, target));
+}
+
+/*
+ * Create a new ACL that matches nothing.
+ */
+isc_result_t
+dns_acl_none(isc_mem_t *mctx, dns_acl_t **target) {
+ return (dns_acl_anyornone(mctx, true, target));
+}
+
+/*
+ * If pos is true, test whether acl is set to "{ any; }"
+ * If pos is false, test whether acl is set to "{ none; }"
+ */
+static bool
+dns_acl_isanyornone(dns_acl_t *acl, bool pos)
+{
+ /* Should never happen but let's be safe */
+ if (acl == NULL ||
+ acl->iptable == NULL ||
+ acl->iptable->radix == NULL ||
+ acl->iptable->radix->head == NULL ||
+ acl->iptable->radix->head->prefix == NULL)
+ return (false);
+
+ if (acl->length != 0 || acl->node_count != 1)
+ return (false);
+
+ if (acl->iptable->radix->head->prefix->bitlen == 0 &&
+ acl->iptable->radix->head->data[0] != NULL &&
+ acl->iptable->radix->head->data[0] ==
+ acl->iptable->radix->head->data[1] &&
+ *(bool *) (acl->iptable->radix->head->data[0]) == pos)
+ return (true);
+
+ return (false); /* All others */
+}
+
+/*
+ * Test whether acl is set to "{ any; }"
+ */
+bool
+dns_acl_isany(dns_acl_t *acl)
+{
+ return (dns_acl_isanyornone(acl, true));
+}
+
+/*
+ * Test whether acl is set to "{ none; }"
+ */
+bool
+dns_acl_isnone(dns_acl_t *acl)
+{
+ return (dns_acl_isanyornone(acl, false));
+}
+
+/*
+ * Determine whether a given address or signer matches a given ACL.
+ * For a match with a positive ACL element or iptable radix entry,
+ * return with a positive value in match; for a match with a negated ACL
+ * element or radix entry, return with a negative value in match.
+ */
+isc_result_t
+dns_acl_match(const isc_netaddr_t *reqaddr,
+ const dns_name_t *reqsigner,
+ const dns_acl_t *acl,
+ const dns_aclenv_t *env,
+ int *match,
+ const dns_aclelement_t **matchelt)
+{
+ return (dns_acl_match2(reqaddr, reqsigner, NULL, 0, NULL, acl, env,
+ match, matchelt));
+}
+
+isc_result_t
+dns_acl_match2(const isc_netaddr_t *reqaddr,
+ const dns_name_t *reqsigner,
+ const isc_netaddr_t *ecs,
+ uint8_t ecslen,
+ uint8_t *scope,
+ const dns_acl_t *acl,
+ const dns_aclenv_t *env,
+ int *match,
+ const dns_aclelement_t **matchelt)
+{
+ uint16_t bitlen;
+ isc_prefix_t pfx;
+ isc_radix_node_t *node = NULL;
+ const isc_netaddr_t *addr = reqaddr;
+ isc_netaddr_t v4addr;
+ isc_result_t result;
+ int match_num = -1;
+ unsigned int i;
+
+ REQUIRE(reqaddr != NULL);
+ REQUIRE(matchelt == NULL || *matchelt == NULL);
+ REQUIRE(ecs != NULL || scope == NULL);
+
+ if (env != NULL && env->match_mapped &&
+ addr->family == AF_INET6 &&
+ IN6_IS_ADDR_V4MAPPED(&addr->type.in6))
+ {
+ isc_netaddr_fromv4mapped(&v4addr, addr);
+ addr = &v4addr;
+ }
+
+ /* Always match with host addresses. */
+ bitlen = (addr->family == AF_INET6) ? 128 : 32;
+ NETADDR_TO_PREFIX_T(addr, pfx, bitlen, false);
+
+ /* Assume no match. */
+ *match = 0;
+
+ /* Search radix. */
+ result = isc_radix_search(acl->iptable->radix, &node, &pfx);
+
+ /* Found a match. */
+ if (result == ISC_R_SUCCESS && node != NULL) {
+ int fam = ISC_RADIX_FAMILY(&pfx);
+ match_num = node->node_num[fam];
+ if (*(bool *) node->data[fam]) {
+ *match = match_num;
+ } else {
+ *match = -match_num;
+ }
+ }
+
+ isc_refcount_destroy(&pfx.refcount);
+
+ /*
+ * If ecs is not NULL, we search the radix tree again to
+ * see if we find a better match on an ECS node
+ */
+ if (ecs != NULL) {
+ node = NULL;
+ addr = ecs;
+
+ if (env != NULL && env->match_mapped &&
+ addr->family == AF_INET6 &&
+ IN6_IS_ADDR_V4MAPPED(&addr->type.in6))
+ {
+ isc_netaddr_fromv4mapped(&v4addr, addr);
+ addr = &v4addr;
+ }
+
+ NETADDR_TO_PREFIX_T(addr, pfx, ecslen, true);
+
+ result = isc_radix_search(acl->iptable->radix, &node, &pfx);
+ if (result == ISC_R_SUCCESS && node != NULL) {
+ int off = ISC_RADIX_FAMILY(&pfx);
+ if (match_num == -1 ||
+ node->node_num[off] < match_num)
+ {
+ match_num = node->node_num[off];
+ if (scope != NULL) {
+ *scope = node->bit;
+ }
+ if (*(bool *) node->data[off]) {
+ *match = match_num;
+ } else {
+ *match = -match_num;
+ }
+ }
+ }
+
+ isc_refcount_destroy(&pfx.refcount);
+ }
+
+ /* Now search non-radix elements for a match with a lower node_num. */
+ for (i = 0; i < acl->length; i++) {
+ dns_aclelement_t *e = &acl->elements[i];
+
+ /* Already found a better match? */
+ if (match_num != -1 && match_num < e->node_num) {
+ break;
+ }
+
+ if (dns_aclelement_match2(reqaddr, reqsigner, ecs, ecslen,
+ scope, e, env, matchelt))
+ {
+ if (match_num == -1 || e->node_num < match_num) {
+ if (e->negative)
+ *match = -e->node_num;
+ else
+ *match = e->node_num;
+ }
+ break;
+ }
+ }
+
+ return (ISC_R_SUCCESS);
+}
+
+/*
+ * Merge the contents of one ACL into another. Call dns_iptable_merge()
+ * for the IP tables, then concatenate the element arrays.
+ *
+ * If pos is set to false, then the nested ACL is to be negated. This
+ * means reverse the sense of each *positive* element or IP table node,
+ * but leave negatives alone, so as to prevent a double-negative causing
+ * an unexpected positive match in the parent ACL.
+ */
+isc_result_t
+dns_acl_merge(dns_acl_t *dest, dns_acl_t *source, bool pos)
+{
+ isc_result_t result;
+ unsigned int newalloc, nelem, i;
+ int max_node = 0, nodes;
+
+ /* Resize the element array if needed. */
+ if (dest->length + source->length > dest->alloc) {
+ void *newmem;
+
+ newalloc = dest->alloc + source->alloc;
+ if (newalloc < 4)
+ newalloc = 4;
+
+ newmem = isc_mem_get(dest->mctx,
+ newalloc * sizeof(dns_aclelement_t));
+ if (newmem == NULL)
+ return (ISC_R_NOMEMORY);
+
+ /* Zero. */
+ memset(newmem, 0, newalloc * sizeof(dns_aclelement_t));
+
+ /* Copy in the original elements */
+ memmove(newmem, dest->elements,
+ dest->length * sizeof(dns_aclelement_t));
+
+ /* Release the memory for the old elements array */
+ isc_mem_put(dest->mctx, dest->elements,
+ dest->alloc * sizeof(dns_aclelement_t));
+ dest->elements = newmem;
+ dest->alloc = newalloc;
+ }
+
+ /*
+ * Now copy in the new elements, increasing their node_num
+ * values so as to keep the new ACL consistent. If we're
+ * negating, then negate positive elements, but keep negative
+ * elements the same for security reasons.
+ */
+ nelem = dest->length;
+ dest->length += source->length;
+ for (i = 0; i < source->length; i++) {
+ if (source->elements[i].node_num > max_node)
+ max_node = source->elements[i].node_num;
+
+ /* Copy type. */
+ dest->elements[nelem + i].type = source->elements[i].type;
+
+ /* Adjust node numbering. */
+ dest->elements[nelem + i].node_num =
+ source->elements[i].node_num + dest->node_count;
+
+ /* Duplicate nested acl. */
+ if (source->elements[i].type == dns_aclelementtype_nestedacl &&
+ source->elements[i].nestedacl != NULL)
+ dns_acl_attach(source->elements[i].nestedacl,
+ &dest->elements[nelem + i].nestedacl);
+
+ /* Duplicate key name. */
+ if (source->elements[i].type == dns_aclelementtype_keyname) {
+ dns_name_init(&dest->elements[nelem+i].keyname, NULL);
+ result = dns_name_dup(&source->elements[i].keyname,
+ dest->mctx,
+ &dest->elements[nelem+i].keyname);
+ if (result != ISC_R_SUCCESS)
+ return result;
+ }
+
+#ifdef HAVE_GEOIP
+ /* Duplicate GeoIP data */
+ if (source->elements[i].type == dns_aclelementtype_geoip) {
+ dest->elements[nelem + i].geoip_elem =
+ source->elements[i].geoip_elem;
+ }
+#endif
+
+ /* reverse sense of positives if this is a negative acl */
+ if (!pos && !source->elements[i].negative) {
+ dest->elements[nelem + i].negative = true;
+ } else {
+ dest->elements[nelem + i].negative =
+ source->elements[i].negative;
+ }
+ }
+
+ /*
+ * Merge the iptables. Make sure the destination ACL's
+ * node_count value is set correctly afterward.
+ */
+ nodes = max_node + dest->node_count;
+ result = dns_iptable_merge(dest->iptable, source->iptable, pos);
+ if (result != ISC_R_SUCCESS)
+ return (result);
+ if (nodes > dest->node_count)
+ dest->node_count = nodes;
+
+ return (ISC_R_SUCCESS);
+}
+
+/*
+ * Like dns_acl_match, but matches against the single ACL element 'e'
+ * rather than a complete ACL, and returns true iff it matched.
+ *
+ * To determine whether the match was positive or negative, the
+ * caller should examine e->negative. Since the element 'e' may be
+ * a reference to a named ACL or a nested ACL, a matching element
+ * returned through 'matchelt' is not necessarily 'e' itself.
+ */
+bool
+dns_aclelement_match(const isc_netaddr_t *reqaddr,
+ const dns_name_t *reqsigner,
+ const dns_aclelement_t *e,
+ const dns_aclenv_t *env,
+ const dns_aclelement_t **matchelt)
+{
+ return (dns_aclelement_match2(reqaddr, reqsigner, NULL, 0, NULL,
+ e, env, matchelt));
+}
+
+bool
+dns_aclelement_match2(const isc_netaddr_t *reqaddr,
+ const dns_name_t *reqsigner,
+ const isc_netaddr_t *ecs,
+ uint8_t ecslen,
+ uint8_t *scope,
+ const dns_aclelement_t *e,
+ const dns_aclenv_t *env,
+ const dns_aclelement_t **matchelt)
+{
+ dns_acl_t *inner = NULL;
+ int indirectmatch;
+ isc_result_t result;
+#ifdef HAVE_GEOIP
+ const isc_netaddr_t *addr = NULL;
+#endif
+
+ REQUIRE(ecs != NULL || scope == NULL);
+
+ switch (e->type) {
+ case dns_aclelementtype_keyname:
+ if (reqsigner != NULL &&
+ dns_name_equal(reqsigner, &e->keyname)) {
+ if (matchelt != NULL)
+ *matchelt = e;
+ return (true);
+ } else
+ return (false);
+
+ case dns_aclelementtype_nestedacl:
+ inner = e->nestedacl;
+ break;
+
+ case dns_aclelementtype_localhost:
+ if (env == NULL || env->localhost == NULL)
+ return (false);
+ inner = env->localhost;
+ break;
+
+ case dns_aclelementtype_localnets:
+ if (env == NULL || env->localnets == NULL)
+ return (false);
+ inner = env->localnets;
+ break;
+
+#ifdef HAVE_GEOIP
+ case dns_aclelementtype_geoip:
+ if (env == NULL || env->geoip == NULL)
+ return (false);
+ addr = (env->geoip_use_ecs && ecs != NULL) ? ecs : reqaddr;
+ return (dns_geoip_match(addr, scope, env->geoip,
+ &e->geoip_elem));
+#endif
+ default:
+ /* Should be impossible. */
+ INSIST(0);
+ }
+
+ result = dns_acl_match2(reqaddr, reqsigner, ecs, ecslen, scope,
+ inner, env, &indirectmatch, matchelt);
+ INSIST(result == ISC_R_SUCCESS);
+
+ /*
+ * Treat negative matches in indirect ACLs as "no match".
+ * That way, a negated indirect ACL will never become a
+ * surprise positive match through double negation.
+ * XXXDCL this should be documented.
+ */
+ if (indirectmatch > 0) {
+ if (matchelt != NULL)
+ *matchelt = e;
+ return (true);
+ }
+
+ /*
+ * A negative indirect match may have set *matchelt, but we don't
+ * want it set when we return.
+ */
+ if (matchelt != NULL)
+ *matchelt = NULL;
+
+ return (false);
+}
+
+void
+dns_acl_attach(dns_acl_t *source, dns_acl_t **target) {
+ REQUIRE(DNS_ACL_VALID(source));
+
+ isc_refcount_increment(&source->refcount, NULL);
+ *target = source;
+}
+
+static void
+destroy(dns_acl_t *dacl) {
+ unsigned int i;
+
+ INSIST(!ISC_LINK_LINKED(dacl, nextincache));
+
+ for (i = 0; i < dacl->length; i++) {
+ dns_aclelement_t *de = &dacl->elements[i];
+ if (de->type == dns_aclelementtype_keyname) {
+ dns_name_free(&de->keyname, dacl->mctx);
+ } else if (de->type == dns_aclelementtype_nestedacl) {
+ dns_acl_detach(&de->nestedacl);
+ }
+ }
+ if (dacl->elements != NULL)
+ isc_mem_put(dacl->mctx, dacl->elements,
+ dacl->alloc * sizeof(dns_aclelement_t));
+ if (dacl->name != NULL)
+ isc_mem_free(dacl->mctx, dacl->name);
+ if (dacl->iptable != NULL)
+ dns_iptable_detach(&dacl->iptable);
+ isc_refcount_destroy(&dacl->refcount);
+ dacl->magic = 0;
+ isc_mem_putanddetach(&dacl->mctx, dacl, sizeof(*dacl));
+}
+
+void
+dns_acl_detach(dns_acl_t **aclp) {
+ dns_acl_t *acl = *aclp;
+ unsigned int refs;
+
+ REQUIRE(DNS_ACL_VALID(acl));
+
+ isc_refcount_decrement(&acl->refcount, &refs);
+ if (refs == 0)
+ destroy(acl);
+ *aclp = NULL;
+}
+
+
+static isc_once_t insecure_prefix_once = ISC_ONCE_INIT;
+static isc_mutex_t insecure_prefix_lock;
+static bool insecure_prefix_found;
+
+static void
+initialize_action(void) {
+ RUNTIME_CHECK(isc_mutex_init(&insecure_prefix_lock) == ISC_R_SUCCESS);
+}
+
+/*
+ * Called via isc_radix_process() to find IP table nodes that are
+ * insecure.
+ */
+static void
+is_insecure(isc_prefix_t *prefix, void **data) {
+ /*
+ * If all nonexistent or negative then this node is secure.
+ */
+ if ((data[0] == NULL || !* (bool *) data[0]) &&
+ (data[1] == NULL || !* (bool *) data[1]) &&
+ (data[2] == NULL || !* (bool *) data[2]) &&
+ (data[3] == NULL || !* (bool *) data[3]))
+ return;
+
+ /*
+ * If a loopback address found and the other family and
+ * ecs entry doesn't exist or is negative, return.
+ */
+ if (prefix->bitlen == 32 &&
+ htonl(prefix->add.sin.s_addr) == INADDR_LOOPBACK &&
+ (data[1] == NULL || !* (bool *) data[1]) &&
+ (data[2] == NULL || !* (bool *) data[2]) &&
+ (data[3] == NULL || !* (bool *) data[3]))
+ return;
+
+ if (prefix->bitlen == 128 &&
+ IN6_IS_ADDR_LOOPBACK(&prefix->add.sin6) &&
+ (data[0] == NULL || !* (bool *) data[0]) &&
+ (data[2] == NULL || !* (bool *) data[2]) &&
+ (data[3] == NULL || !* (bool *) data[3]))
+ return;
+
+ /* Non-negated, non-loopback */
+ insecure_prefix_found = true; /* LOCKED */
+ return;
+}
+
+/*
+ * Return true iff the acl 'a' is considered insecure, that is,
+ * if it contains IP addresses other than those of the local host.
+ * This is intended for applications such as printing warning
+ * messages for suspect ACLs; it is not intended for making access
+ * control decisions. We make no guarantee that an ACL for which
+ * this function returns false is safe.
+ */
+bool
+dns_acl_isinsecure(const dns_acl_t *a) {
+ unsigned int i;
+ bool insecure;
+
+ RUNTIME_CHECK(isc_once_do(&insecure_prefix_once,
+ initialize_action) == ISC_R_SUCCESS);
+
+ /*
+ * Walk radix tree to find out if there are any non-negated,
+ * non-loopback prefixes.
+ */
+ LOCK(&insecure_prefix_lock);
+ insecure_prefix_found = false;
+ isc_radix_process(a->iptable->radix, is_insecure);
+ insecure = insecure_prefix_found;
+ UNLOCK(&insecure_prefix_lock);
+ if (insecure)
+ return (true);
+
+ /* Now check non-radix elements */
+ for (i = 0; i < a->length; i++) {
+ dns_aclelement_t *e = &a->elements[i];
+
+ /* A negated match can never be insecure. */
+ if (e->negative)
+ continue;
+
+ switch (e->type) {
+ case dns_aclelementtype_keyname:
+ case dns_aclelementtype_localhost:
+ continue;
+
+ case dns_aclelementtype_nestedacl:
+ if (dns_acl_isinsecure(e->nestedacl))
+ return (true);
+ continue;
+
+#ifdef HAVE_GEOIP
+ case dns_aclelementtype_geoip:
+#endif
+ case dns_aclelementtype_localnets:
+ return (true);
+
+ default:
+ INSIST(0);
+ return (true);
+ }
+ }
+
+ /* No insecure elements were found. */
+ return (false);
+}
+
+/*
+ * Initialize ACL environment, setting up localhost and localnets ACLs
+ */
+isc_result_t
+dns_aclenv_init(isc_mem_t *mctx, dns_aclenv_t *env) {
+ isc_result_t result;
+
+ env->localhost = NULL;
+ env->localnets = NULL;
+ result = dns_acl_create(mctx, 0, &env->localhost);
+ if (result != ISC_R_SUCCESS)
+ goto cleanup_nothing;
+ result = dns_acl_create(mctx, 0, &env->localnets);
+ if (result != ISC_R_SUCCESS)
+ goto cleanup_localhost;
+ env->match_mapped = false;
+#ifdef HAVE_GEOIP
+ env->geoip = NULL;
+ env->geoip_use_ecs = false;
+#endif
+ return (ISC_R_SUCCESS);
+
+ cleanup_localhost:
+ dns_acl_detach(&env->localhost);
+ cleanup_nothing:
+ return (result);
+}
+
+void
+dns_aclenv_copy(dns_aclenv_t *t, dns_aclenv_t *s) {
+ dns_acl_detach(&t->localhost);
+ dns_acl_attach(s->localhost, &t->localhost);
+ dns_acl_detach(&t->localnets);
+ dns_acl_attach(s->localnets, &t->localnets);
+ t->match_mapped = s->match_mapped;
+#ifdef HAVE_GEOIP
+ t->geoip_use_ecs = s->geoip_use_ecs;
+#endif
+}
+
+void
+dns_aclenv_destroy(dns_aclenv_t *env) {
+ dns_acl_detach(&env->localhost);
+ dns_acl_detach(&env->localnets);
+}