1/* $NetBSD: acl.c,v 1.3.4.1 2012/06/05 21:14:59 bouyer Exp $ */ 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