#include <config.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>
isc_result_t
dns_acl_create(isc_mem_t *mctx, int n, dns_acl_t **target) {
isc_result_t result;
dns_acl_t *acl;
if (n == 0)
n = 1;
acl = isc_mem_get(mctx, sizeof(*acl));
if (acl == NULL)
return (ISC_R_NOMEMORY);
acl->mctx = 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 = ISC_FALSE;
ISC_LINK_INIT(acl, nextincache);
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);
}
static isc_result_t
dns_acl_anyornone(isc_mem_t *mctx, isc_boolean_t 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, ISC_TF(!neg));
if (result != ISC_R_SUCCESS) {
dns_acl_detach(&acl);
return (result);
}
*target = acl;
return (result);
}
isc_result_t
dns_acl_any(isc_mem_t *mctx, dns_acl_t **target) {
return (dns_acl_anyornone(mctx, ISC_FALSE, target));
}
isc_result_t
dns_acl_none(isc_mem_t *mctx, dns_acl_t **target) {
return (dns_acl_anyornone(mctx, ISC_TRUE, target));
}
static isc_boolean_t
dns_acl_isanyornone(dns_acl_t *acl, isc_boolean_t pos)
{
if (acl == NULL ||
acl->iptable == NULL ||
acl->iptable->radix == NULL ||
acl->iptable->radix->head == NULL ||
acl->iptable->radix->head->prefix == NULL)
return (ISC_FALSE);
if (acl->length != 0 || acl->node_count != 1)
return (ISC_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] &&
*(isc_boolean_t *) (acl->iptable->radix->head->data[0]) == pos)
return (ISC_TRUE);
return (ISC_FALSE);
}
isc_boolean_t
dns_acl_isany(dns_acl_t *acl)
{
return (dns_acl_isanyornone(acl, ISC_TRUE));
}
isc_boolean_t
dns_acl_isnone(dns_acl_t *acl)
{
return (dns_acl_isanyornone(acl, ISC_FALSE));
}
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)
{
isc_uint16_t bitlen, family;
isc_prefix_t pfx;
isc_radix_node_t *node = NULL;
const isc_netaddr_t *addr;
isc_netaddr_t v4addr;
isc_result_t result;
int match_num = -1;
unsigned int i;
REQUIRE(reqaddr != NULL);
REQUIRE(matchelt == NULL || *matchelt == NULL);
if (env == NULL || env->match_mapped == ISC_FALSE ||
reqaddr->family != AF_INET6 ||
!IN6_IS_ADDR_V4MAPPED(&reqaddr->type.in6))
addr = reqaddr;
else {
isc_netaddr_fromv4mapped(&v4addr, reqaddr);
addr = &v4addr;
}
family = addr->family;
bitlen = family == AF_INET6 ? 128 : 32;
NETADDR_TO_PREFIX_T(addr, pfx, bitlen);
*match = 0;
result = isc_radix_search(acl->iptable->radix, &node, &pfx);
if (result == ISC_R_SUCCESS && node != NULL) {
match_num = node->node_num[ISC_IS6(family)];
if (*(isc_boolean_t *) node->data[ISC_IS6(family)] == ISC_TRUE)
*match = match_num;
else
*match = -match_num;
}
for (i = 0; i < acl->length; i++) {
dns_aclelement_t *e = &acl->elements[i];
if (match_num != -1 && match_num < e->node_num) {
isc_refcount_destroy(&pfx.refcount);
return (ISC_R_SUCCESS);
}
if (dns_aclelement_match(reqaddr, reqsigner,
e, env, matchelt)) {
if (match_num == -1 || e->node_num < match_num) {
if (e->negative == ISC_TRUE)
*match = -e->node_num;
else
*match = e->node_num;
}
isc_refcount_destroy(&pfx.refcount);
return (ISC_R_SUCCESS);
}
}
isc_refcount_destroy(&pfx.refcount);
return (ISC_R_SUCCESS);
}
isc_result_t
dns_acl_merge(dns_acl_t *dest, dns_acl_t *source, isc_boolean_t pos)
{
isc_result_t result;
unsigned int newalloc, nelem, i;
int max_node = 0, nodes;
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);
memcpy(newmem, dest->elements,
dest->length * sizeof(dns_aclelement_t));
isc_mem_put(dest->mctx, dest->elements,
dest->alloc * sizeof(dns_aclelement_t));
dest->elements = newmem;
dest->alloc = newalloc;
}
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;
dest->elements[nelem + i].type = source->elements[i].type;
dest->elements[nelem + i].node_num =
source->elements[i].node_num + dest->node_count;
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);
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;
}
if (!pos && source->elements[i].negative == ISC_FALSE) {
dest->elements[nelem + i].negative = ISC_TRUE;
} else {
dest->elements[nelem + i].negative =
source->elements[i].negative;
}
}
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);
}
isc_boolean_t
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)
{
dns_acl_t *inner = NULL;
int indirectmatch;
isc_result_t result;
switch (e->type) {
case dns_aclelementtype_keyname:
if (reqsigner != NULL &&
dns_name_equal(reqsigner, &e->keyname)) {
if (matchelt != NULL)
*matchelt = e;
return (ISC_TRUE);
} else {
return (ISC_FALSE);
}
case dns_aclelementtype_nestedacl:
inner = e->nestedacl;
break;
case dns_aclelementtype_localhost:
if (env == NULL || env->localhost == NULL)
return (ISC_FALSE);
inner = env->localhost;
break;
case dns_aclelementtype_localnets:
if (env == NULL || env->localnets == NULL)
return (ISC_FALSE);
inner = env->localnets;
break;
default:
INSIST(0);
}
result = dns_acl_match(reqaddr, reqsigner, inner, env,
&indirectmatch, matchelt);
INSIST(result == ISC_R_SUCCESS);
if (indirectmatch > 0) {
if (matchelt != NULL)
*matchelt = e;
return (ISC_TRUE);
}
if (matchelt != NULL)
*matchelt = NULL;
return (ISC_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_put(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 isc_boolean_t insecure_prefix_found;
static void
initialize_action(void) {
RUNTIME_CHECK(isc_mutex_init(&insecure_prefix_lock) == ISC_R_SUCCESS);
}
static void
is_insecure(isc_prefix_t *prefix, void **data) {
isc_boolean_t secure;
int bitlen, family;
bitlen = prefix->bitlen;
family = prefix->family;
secure = * (isc_boolean_t *)data[ISC_IS6(family)];
if (!secure) {
return;
}
switch (family) {
case AF_INET:
if (bitlen == 32 &&
htonl(prefix->add.sin.s_addr) == INADDR_LOOPBACK)
return;
break;
case AF_INET6:
if (bitlen == 128 && IN6_IS_ADDR_LOOPBACK(&prefix->add.sin6))
return;
break;
default:
break;
}
insecure_prefix_found = ISC_TRUE;
return;
}
isc_boolean_t
dns_acl_isinsecure(const dns_acl_t *a) {
unsigned int i;
isc_boolean_t insecure;
RUNTIME_CHECK(isc_once_do(&insecure_prefix_once,
initialize_action) == ISC_R_SUCCESS);
LOCK(&insecure_prefix_lock);
insecure_prefix_found = ISC_FALSE;
isc_radix_process(a->iptable->radix, is_insecure);
insecure = insecure_prefix_found;
UNLOCK(&insecure_prefix_lock);
if (insecure)
return(ISC_TRUE);
for (i = 0; i < a->length; i++) {
dns_aclelement_t *e = &a->elements[i];
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 (ISC_TRUE);
continue;
case dns_aclelementtype_localnets:
return (ISC_TRUE);
default:
INSIST(0);
return (ISC_TRUE);
}
}
return (ISC_FALSE);
}
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 = ISC_FALSE;
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;
}
void
dns_aclenv_destroy(dns_aclenv_t *env) {
dns_acl_detach(&env->localhost);
dns_acl_detach(&env->localnets);
}