diff options
Diffstat (limited to 'lib/bind9/check.c')
-rw-r--r-- | lib/bind9/check.c | 2043 |
1 files changed, 2043 insertions, 0 deletions
diff --git a/lib/bind9/check.c b/lib/bind9/check.c new file mode 100644 index 0000000..6cfdc93 --- /dev/null +++ b/lib/bind9/check.c @@ -0,0 +1,2043 @@ +/* + * Copyright (C) 2004-2007 Internet Systems Consortium, Inc. ("ISC") + * Copyright (C) 2001-2003 Internet Software Consortium. + * + * Permission to use, copy, modify, and/or distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH + * REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY + * AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT, + * INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM + * LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE + * OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR + * PERFORMANCE OF THIS SOFTWARE. + */ + +/* $Id: check.c,v 1.44.18.35 2007/09/13 05:04:01 each Exp $ */ + +/*! \file */ + +#include <config.h> + +#include <stdlib.h> + +#include <isc/buffer.h> +#include <isc/log.h> +#include <isc/mem.h> +#include <isc/netaddr.h> +#include <isc/parseint.h> +#include <isc/region.h> +#include <isc/result.h> +#include <isc/sockaddr.h> +#include <isc/string.h> +#include <isc/symtab.h> +#include <isc/util.h> + +#include <dns/acl.h> +#include <dns/fixedname.h> +#include <dns/rdataclass.h> +#include <dns/rdatatype.h> +#include <dns/secalg.h> + +#include <isccfg/aclconf.h> +#include <isccfg/cfg.h> + +#include <bind9/check.h> + +#ifndef DNS_RDATASET_FIXED +#define DNS_RDATASET_FIXED 1 +#endif + +static void +freekey(char *key, unsigned int type, isc_symvalue_t value, void *userarg) { + UNUSED(type); + UNUSED(value); + isc_mem_free(userarg, key); +} + +static isc_result_t +check_orderent(const cfg_obj_t *ent, isc_log_t *logctx) { + isc_result_t result = ISC_R_SUCCESS; + isc_result_t tresult; + isc_textregion_t r; + dns_fixedname_t fixed; + const cfg_obj_t *obj; + dns_rdataclass_t rdclass; + dns_rdatatype_t rdtype; + isc_buffer_t b; + const char *str; + + dns_fixedname_init(&fixed); + obj = cfg_tuple_get(ent, "class"); + if (cfg_obj_isstring(obj)) { + + DE_CONST(cfg_obj_asstring(obj), r.base); + r.length = strlen(r.base); + tresult = dns_rdataclass_fromtext(&rdclass, &r); + if (tresult != ISC_R_SUCCESS) { + cfg_obj_log(obj, logctx, ISC_LOG_ERROR, + "rrset-order: invalid class '%s'", + r.base); + result = ISC_R_FAILURE; + } + } + + obj = cfg_tuple_get(ent, "type"); + if (cfg_obj_isstring(obj)) { + + DE_CONST(cfg_obj_asstring(obj), r.base); + r.length = strlen(r.base); + tresult = dns_rdatatype_fromtext(&rdtype, &r); + if (tresult != ISC_R_SUCCESS) { + cfg_obj_log(obj, logctx, ISC_LOG_ERROR, + "rrset-order: invalid type '%s'", + r.base); + result = ISC_R_FAILURE; + } + } + + obj = cfg_tuple_get(ent, "name"); + if (cfg_obj_isstring(obj)) { + str = cfg_obj_asstring(obj); + isc_buffer_init(&b, str, strlen(str)); + isc_buffer_add(&b, strlen(str)); + tresult = dns_name_fromtext(dns_fixedname_name(&fixed), &b, + dns_rootname, ISC_FALSE, NULL); + if (tresult != ISC_R_SUCCESS) { + cfg_obj_log(obj, logctx, ISC_LOG_ERROR, + "rrset-order: invalid name '%s'", str); + result = ISC_R_FAILURE; + } + } + + obj = cfg_tuple_get(ent, "order"); + if (!cfg_obj_isstring(obj) || + strcasecmp("order", cfg_obj_asstring(obj)) != 0) { + cfg_obj_log(ent, logctx, ISC_LOG_ERROR, + "rrset-order: keyword 'order' missing"); + result = ISC_R_FAILURE; + } + + obj = cfg_tuple_get(ent, "ordering"); + if (!cfg_obj_isstring(obj)) { + cfg_obj_log(ent, logctx, ISC_LOG_ERROR, + "rrset-order: missing ordering"); + result = ISC_R_FAILURE; + } else if (strcasecmp(cfg_obj_asstring(obj), "fixed") == 0) { +#if !DNS_RDATASET_FIXED + cfg_obj_log(obj, logctx, ISC_LOG_WARNING, + "rrset-order: order 'fixed' not fully implemented"); +#endif + } else if (strcasecmp(cfg_obj_asstring(obj), "random") != 0 && + strcasecmp(cfg_obj_asstring(obj), "cyclic") != 0) { + cfg_obj_log(obj, logctx, ISC_LOG_ERROR, + "rrset-order: invalid order '%s'", + cfg_obj_asstring(obj)); + result = ISC_R_FAILURE; + } + return (result); +} + +static isc_result_t +check_order(const cfg_obj_t *options, isc_log_t *logctx) { + isc_result_t result = ISC_R_SUCCESS; + isc_result_t tresult; + const cfg_listelt_t *element; + const cfg_obj_t *obj = NULL; + + if (cfg_map_get(options, "rrset-order", &obj) != ISC_R_SUCCESS) + return (result); + + for (element = cfg_list_first(obj); + element != NULL; + element = cfg_list_next(element)) + { + tresult = check_orderent(cfg_listelt_value(element), logctx); + if (tresult != ISC_R_SUCCESS) + result = tresult; + } + return (result); +} + +static isc_result_t +check_dual_stack(const cfg_obj_t *options, isc_log_t *logctx) { + const cfg_listelt_t *element; + const cfg_obj_t *alternates = NULL; + const cfg_obj_t *value; + const cfg_obj_t *obj; + const char *str; + dns_fixedname_t fixed; + dns_name_t *name; + isc_buffer_t buffer; + isc_result_t result = ISC_R_SUCCESS; + isc_result_t tresult; + + (void)cfg_map_get(options, "dual-stack-servers", &alternates); + + if (alternates == NULL) + return (ISC_R_SUCCESS); + + obj = cfg_tuple_get(alternates, "port"); + if (cfg_obj_isuint32(obj)) { + isc_uint32_t val = cfg_obj_asuint32(obj); + if (val > ISC_UINT16_MAX) { + cfg_obj_log(obj, logctx, ISC_LOG_ERROR, + "port '%u' out of range", val); + result = ISC_R_FAILURE; + } + } + obj = cfg_tuple_get(alternates, "addresses"); + for (element = cfg_list_first(obj); + element != NULL; + element = cfg_list_next(element)) { + value = cfg_listelt_value(element); + if (cfg_obj_issockaddr(value)) + continue; + obj = cfg_tuple_get(value, "name"); + str = cfg_obj_asstring(obj); + isc_buffer_init(&buffer, str, strlen(str)); + isc_buffer_add(&buffer, strlen(str)); + dns_fixedname_init(&fixed); + name = dns_fixedname_name(&fixed); + tresult = dns_name_fromtext(name, &buffer, dns_rootname, + ISC_FALSE, NULL); + if (tresult != ISC_R_SUCCESS) { + cfg_obj_log(obj, logctx, ISC_LOG_ERROR, + "bad name '%s'", str); + result = ISC_R_FAILURE; + } + obj = cfg_tuple_get(value, "port"); + if (cfg_obj_isuint32(obj)) { + isc_uint32_t val = cfg_obj_asuint32(obj); + if (val > ISC_UINT16_MAX) { + cfg_obj_log(obj, logctx, ISC_LOG_ERROR, + "port '%u' out of range", val); + result = ISC_R_FAILURE; + } + } + } + return (result); +} + +static isc_result_t +check_forward(const cfg_obj_t *options, isc_log_t *logctx) { + const cfg_obj_t *forward = NULL; + const cfg_obj_t *forwarders = NULL; + + (void)cfg_map_get(options, "forward", &forward); + (void)cfg_map_get(options, "forwarders", &forwarders); + + if (forward != NULL && forwarders == NULL) { + cfg_obj_log(forward, logctx, ISC_LOG_ERROR, + "no matching 'forwarders' statement"); + return (ISC_R_FAILURE); + } + return (ISC_R_SUCCESS); +} + +static isc_result_t +disabled_algorithms(const cfg_obj_t *disabled, isc_log_t *logctx) { + isc_result_t result = ISC_R_SUCCESS; + isc_result_t tresult; + const cfg_listelt_t *element; + const char *str; + isc_buffer_t b; + dns_fixedname_t fixed; + dns_name_t *name; + const cfg_obj_t *obj; + + dns_fixedname_init(&fixed); + name = dns_fixedname_name(&fixed); + obj = cfg_tuple_get(disabled, "name"); + str = cfg_obj_asstring(obj); + isc_buffer_init(&b, str, strlen(str)); + isc_buffer_add(&b, strlen(str)); + tresult = dns_name_fromtext(name, &b, dns_rootname, ISC_FALSE, NULL); + if (tresult != ISC_R_SUCCESS) { + cfg_obj_log(obj, logctx, ISC_LOG_ERROR, + "bad domain name '%s'", str); + result = tresult; + } + + obj = cfg_tuple_get(disabled, "algorithms"); + + for (element = cfg_list_first(obj); + element != NULL; + element = cfg_list_next(element)) + { + isc_textregion_t r; + dns_secalg_t alg; + isc_result_t tresult; + + DE_CONST(cfg_obj_asstring(cfg_listelt_value(element)), r.base); + r.length = strlen(r.base); + + tresult = dns_secalg_fromtext(&alg, &r); + if (tresult != ISC_R_SUCCESS) { + isc_uint8_t ui; + result = isc_parse_uint8(&ui, r.base, 10); + } + if (tresult != ISC_R_SUCCESS) { + cfg_obj_log(cfg_listelt_value(element), logctx, + ISC_LOG_ERROR, "invalid algorithm '%s'", + r.base); + result = tresult; + } + } + return (result); +} + +static isc_result_t +nameexist(const cfg_obj_t *obj, const char *name, int value, + isc_symtab_t *symtab, const char *fmt, isc_log_t *logctx, + isc_mem_t *mctx) +{ + char *key; + const char *file; + unsigned int line; + isc_result_t result; + isc_symvalue_t symvalue; + + key = isc_mem_strdup(mctx, name); + if (key == NULL) + return (ISC_R_NOMEMORY); + symvalue.as_cpointer = obj; + result = isc_symtab_define(symtab, key, value, symvalue, + isc_symexists_reject); + if (result == ISC_R_EXISTS) { + RUNTIME_CHECK(isc_symtab_lookup(symtab, key, value, + &symvalue) == ISC_R_SUCCESS); + file = cfg_obj_file(symvalue.as_cpointer); + line = cfg_obj_line(symvalue.as_cpointer); + + if (file == NULL) + file = "<unknown file>"; + cfg_obj_log(obj, logctx, ISC_LOG_ERROR, fmt, key, file, line); + isc_mem_free(mctx, key); + result = ISC_R_EXISTS; + } else if (result != ISC_R_SUCCESS) { + isc_mem_free(mctx, key); + } + return (result); +} + +static isc_result_t +mustbesecure(const cfg_obj_t *secure, isc_symtab_t *symtab, isc_log_t *logctx, + isc_mem_t *mctx) +{ + const cfg_obj_t *obj; + char namebuf[DNS_NAME_FORMATSIZE]; + const char *str; + dns_fixedname_t fixed; + dns_name_t *name; + isc_buffer_t b; + isc_result_t result = ISC_R_SUCCESS; + + dns_fixedname_init(&fixed); + name = dns_fixedname_name(&fixed); + obj = cfg_tuple_get(secure, "name"); + str = cfg_obj_asstring(obj); + isc_buffer_init(&b, str, strlen(str)); + isc_buffer_add(&b, strlen(str)); + result = dns_name_fromtext(name, &b, dns_rootname, ISC_FALSE, NULL); + if (result != ISC_R_SUCCESS) { + cfg_obj_log(obj, logctx, ISC_LOG_ERROR, + "bad domain name '%s'", str); + } else { + dns_name_format(name, namebuf, sizeof(namebuf)); + result = nameexist(secure, namebuf, 1, symtab, + "dnssec-must-be-secure '%s': already " + "exists previous definition: %s:%u", + logctx, mctx); + } + return (result); +} + +static isc_result_t +checkacl(const char *aclname, cfg_aclconfctx_t *actx, const cfg_obj_t *zconfig, + const cfg_obj_t *voptions, const cfg_obj_t *config, + isc_log_t *logctx, isc_mem_t *mctx) +{ + isc_result_t result; + const cfg_obj_t *aclobj = NULL; + const cfg_obj_t *options; + dns_acl_t *acl = NULL; + + if (zconfig != NULL) { + options = cfg_tuple_get(zconfig, "options"); + cfg_map_get(options, aclname, &aclobj); + } + if (voptions != NULL && aclobj == NULL) + cfg_map_get(voptions, aclname, &aclobj); + if (config != NULL && aclobj == NULL) { + options = NULL; + cfg_map_get(config, "options", &options); + if (options != NULL) + cfg_map_get(options, aclname, &aclobj); + } + if (aclobj == NULL) + return (ISC_R_SUCCESS); + result = cfg_acl_fromconfig(aclobj, config, logctx, actx, mctx, &acl); + if (acl != NULL) + dns_acl_detach(&acl); + return (result); +} + +static isc_result_t +check_viewacls(cfg_aclconfctx_t *actx, const cfg_obj_t *voptions, + const cfg_obj_t *config, isc_log_t *logctx, isc_mem_t *mctx) +{ + isc_result_t result = ISC_R_SUCCESS, tresult; + int i = 0; + + static const char *acls[] = { "allow-query", "allow-query-cache", + "allow-recursion", "blackhole", "match-clients", + "match-destinations", "sortlist", NULL }; + + while (acls[i] != NULL) { + tresult = checkacl(acls[i++], actx, NULL, voptions, config, + logctx, mctx); + if (tresult != ISC_R_SUCCESS) + result = tresult; + } + return (result); +} + +typedef struct { + const char *name; + unsigned int scale; + unsigned int max; +} intervaltable; + +static isc_result_t +check_options(const cfg_obj_t *options, isc_log_t *logctx, isc_mem_t *mctx) { + isc_result_t result = ISC_R_SUCCESS; + isc_result_t tresult; + unsigned int i; + const cfg_obj_t *obj = NULL; + const cfg_listelt_t *element; + isc_symtab_t *symtab = NULL; + dns_fixedname_t fixed; + const char *str; + dns_name_t *name; + isc_buffer_t b; + + static intervaltable intervals[] = { + { "cleaning-interval", 60, 28 * 24 * 60 }, /* 28 days */ + { "heartbeat-interval", 60, 28 * 24 * 60 }, /* 28 days */ + { "interface-interval", 60, 28 * 24 * 60 }, /* 28 days */ + { "max-transfer-idle-in", 60, 28 * 24 * 60 }, /* 28 days */ + { "max-transfer-idle-out", 60, 28 * 24 * 60 }, /* 28 days */ + { "max-transfer-time-in", 60, 28 * 24 * 60 }, /* 28 days */ + { "max-transfer-time-out", 60, 28 * 24 * 60 }, /* 28 days */ + { "sig-validity-interval", 86400, 10 * 366 }, /* 10 years */ + { "statistics-interval", 60, 28 * 24 * 60 }, /* 28 days */ + }; + + /* + * Check that fields specified in units of time other than seconds + * have reasonable values. + */ + for (i = 0; i < sizeof(intervals) / sizeof(intervals[0]); i++) { + isc_uint32_t val; + obj = NULL; + (void)cfg_map_get(options, intervals[i].name, &obj); + if (obj == NULL) + continue; + val = cfg_obj_asuint32(obj); + if (val > intervals[i].max) { + cfg_obj_log(obj, logctx, ISC_LOG_ERROR, + "%s '%u' is out of range (0..%u)", + intervals[i].name, val, + intervals[i].max); + result = ISC_R_RANGE; + } else if (val > (ISC_UINT32_MAX / intervals[i].scale)) { + cfg_obj_log(obj, logctx, ISC_LOG_ERROR, + "%s '%d' is out of range", + intervals[i].name, val); + result = ISC_R_RANGE; + } + } + obj = NULL; + (void)cfg_map_get(options, "preferred-glue", &obj); + if (obj != NULL) { + const char *str; + str = cfg_obj_asstring(obj); + if (strcasecmp(str, "a") != 0 && + strcasecmp(str, "aaaa") != 0 && + strcasecmp(str, "none") != 0) + cfg_obj_log(obj, logctx, ISC_LOG_ERROR, + "preferred-glue unexpected value '%s'", + str); + } + obj = NULL; + (void)cfg_map_get(options, "root-delegation-only", &obj); + if (obj != NULL) { + if (!cfg_obj_isvoid(obj)) { + const cfg_listelt_t *element; + const cfg_obj_t *exclude; + const char *str; + dns_fixedname_t fixed; + dns_name_t *name; + isc_buffer_t b; + + dns_fixedname_init(&fixed); + name = dns_fixedname_name(&fixed); + for (element = cfg_list_first(obj); + element != NULL; + element = cfg_list_next(element)) { + exclude = cfg_listelt_value(element); + str = cfg_obj_asstring(exclude); + isc_buffer_init(&b, str, strlen(str)); + isc_buffer_add(&b, strlen(str)); + tresult = dns_name_fromtext(name, &b, + dns_rootname, + ISC_FALSE, NULL); + if (tresult != ISC_R_SUCCESS) { + cfg_obj_log(obj, logctx, ISC_LOG_ERROR, + "bad domain name '%s'", + str); + result = tresult; + } + } + } + } + + /* + * Set supported DNSSEC algorithms. + */ + obj = NULL; + (void)cfg_map_get(options, "disable-algorithms", &obj); + if (obj != NULL) { + for (element = cfg_list_first(obj); + element != NULL; + element = cfg_list_next(element)) + { + obj = cfg_listelt_value(element); + tresult = disabled_algorithms(obj, logctx); + if (tresult != ISC_R_SUCCESS) + result = tresult; + } + } + + dns_fixedname_init(&fixed); + name = dns_fixedname_name(&fixed); + + /* + * Check the DLV zone name. + */ + obj = NULL; + (void)cfg_map_get(options, "dnssec-lookaside", &obj); + if (obj != NULL) { + tresult = isc_symtab_create(mctx, 100, freekey, mctx, + ISC_TRUE, &symtab); + if (tresult != ISC_R_SUCCESS) + result = tresult; + for (element = cfg_list_first(obj); + element != NULL; + element = cfg_list_next(element)) + { + const char *dlv; + + obj = cfg_listelt_value(element); + + dlv = cfg_obj_asstring(cfg_tuple_get(obj, "domain")); + isc_buffer_init(&b, dlv, strlen(dlv)); + isc_buffer_add(&b, strlen(dlv)); + tresult = dns_name_fromtext(name, &b, dns_rootname, + ISC_TRUE, NULL); + if (tresult != ISC_R_SUCCESS) { + cfg_obj_log(obj, logctx, ISC_LOG_ERROR, + "bad domain name '%s'", dlv); + result = tresult; + } + if (symtab != NULL) { + tresult = nameexist(obj, dlv, 1, symtab, + "dnssec-lookaside '%s': " + "already exists previous " + "definition: %s:%u", + logctx, mctx); + if (tresult != ISC_R_SUCCESS && + result == ISC_R_SUCCESS) + result = tresult; + } + /* + * XXXMPA to be removed when multiple lookaside + * namespaces are supported. + */ + if (!dns_name_equal(dns_rootname, name)) { + cfg_obj_log(obj, logctx, ISC_LOG_ERROR, + "dnssec-lookaside '%s': " + "non-root not yet supported", dlv); + if (result == ISC_R_SUCCESS) + result = ISC_R_FAILURE; + } + dlv = cfg_obj_asstring(cfg_tuple_get(obj, + "trust-anchor")); + isc_buffer_init(&b, dlv, strlen(dlv)); + isc_buffer_add(&b, strlen(dlv)); + tresult = dns_name_fromtext(name, &b, dns_rootname, + ISC_TRUE, NULL); + if (tresult != ISC_R_SUCCESS) { + cfg_obj_log(obj, logctx, ISC_LOG_ERROR, + "bad domain name '%s'", dlv); + if (result == ISC_R_SUCCESS) + result = tresult; + } + } + if (symtab != NULL) + isc_symtab_destroy(&symtab); + } + + /* + * Check dnssec-must-be-secure. + */ + obj = NULL; + (void)cfg_map_get(options, "dnssec-must-be-secure", &obj); + if (obj != NULL) { + isc_symtab_t *symtab = NULL; + tresult = isc_symtab_create(mctx, 100, freekey, mctx, + ISC_FALSE, &symtab); + if (tresult != ISC_R_SUCCESS) + result = tresult; + for (element = cfg_list_first(obj); + element != NULL; + element = cfg_list_next(element)) + { + obj = cfg_listelt_value(element); + tresult = mustbesecure(obj, symtab, logctx, mctx); + if (tresult != ISC_R_SUCCESS) + result = tresult; + } + if (symtab != NULL) + isc_symtab_destroy(&symtab); + } + + /* + * Check empty zone configuration. + */ + obj = NULL; + (void)cfg_map_get(options, "empty-server", &obj); + if (obj != NULL) { + str = cfg_obj_asstring(obj); + isc_buffer_init(&b, str, strlen(str)); + isc_buffer_add(&b, strlen(str)); + tresult = dns_name_fromtext(dns_fixedname_name(&fixed), &b, + dns_rootname, ISC_FALSE, NULL); + if (tresult != ISC_R_SUCCESS) { + cfg_obj_log(obj, logctx, ISC_LOG_ERROR, + "empty-server: invalid name '%s'", str); + result = ISC_R_FAILURE; + } + } + + obj = NULL; + (void)cfg_map_get(options, "empty-contact", &obj); + if (obj != NULL) { + str = cfg_obj_asstring(obj); + isc_buffer_init(&b, str, strlen(str)); + isc_buffer_add(&b, strlen(str)); + tresult = dns_name_fromtext(dns_fixedname_name(&fixed), &b, + dns_rootname, ISC_FALSE, NULL); + if (tresult != ISC_R_SUCCESS) { + cfg_obj_log(obj, logctx, ISC_LOG_ERROR, + "empty-contact: invalid name '%s'", str); + result = ISC_R_FAILURE; + } + } + + obj = NULL; + (void)cfg_map_get(options, "disable-empty-zone", &obj); + for (element = cfg_list_first(obj); + element != NULL; + element = cfg_list_next(element)) + { + obj = cfg_listelt_value(element); + str = cfg_obj_asstring(obj); + isc_buffer_init(&b, str, strlen(str)); + isc_buffer_add(&b, strlen(str)); + tresult = dns_name_fromtext(dns_fixedname_name(&fixed), &b, + dns_rootname, ISC_FALSE, NULL); + if (tresult != ISC_R_SUCCESS) { + cfg_obj_log(obj, logctx, ISC_LOG_ERROR, + "disable-empty-zone: invalid name '%s'", + str); + result = ISC_R_FAILURE; + } + } + + return (result); +} + +static isc_result_t +get_masters_def(const cfg_obj_t *cctx, const char *name, const cfg_obj_t **ret) { + isc_result_t result; + const cfg_obj_t *masters = NULL; + const cfg_listelt_t *elt; + + result = cfg_map_get(cctx, "masters", &masters); + if (result != ISC_R_SUCCESS) + return (result); + for (elt = cfg_list_first(masters); + elt != NULL; + elt = cfg_list_next(elt)) { + const cfg_obj_t *list; + const char *listname; + + list = cfg_listelt_value(elt); + listname = cfg_obj_asstring(cfg_tuple_get(list, "name")); + + if (strcasecmp(listname, name) == 0) { + *ret = list; + return (ISC_R_SUCCESS); + } + } + return (ISC_R_NOTFOUND); +} + +static isc_result_t +validate_masters(const cfg_obj_t *obj, const cfg_obj_t *config, + isc_uint32_t *countp, isc_log_t *logctx, isc_mem_t *mctx) +{ + isc_result_t result = ISC_R_SUCCESS; + isc_result_t tresult; + isc_uint32_t count = 0; + isc_symtab_t *symtab = NULL; + isc_symvalue_t symvalue; + const cfg_listelt_t *element; + const cfg_listelt_t **stack = NULL; + isc_uint32_t stackcount = 0, pushed = 0; + const cfg_obj_t *list; + + REQUIRE(countp != NULL); + result = isc_symtab_create(mctx, 100, NULL, NULL, ISC_FALSE, &symtab); + if (result != ISC_R_SUCCESS) { + *countp = count; + return (result); + } + + newlist: + list = cfg_tuple_get(obj, "addresses"); + element = cfg_list_first(list); + resume: + for ( ; + element != NULL; + element = cfg_list_next(element)) + { + const char *listname; + const cfg_obj_t *addr; + const cfg_obj_t *key; + + addr = cfg_tuple_get(cfg_listelt_value(element), + "masterselement"); + key = cfg_tuple_get(cfg_listelt_value(element), "key"); + + if (cfg_obj_issockaddr(addr)) { + count++; + continue; + } + if (!cfg_obj_isvoid(key)) { + cfg_obj_log(key, logctx, ISC_LOG_ERROR, + "unexpected token '%s'", + cfg_obj_asstring(key)); + if (result == ISC_R_SUCCESS) + result = ISC_R_FAILURE; + } + listname = cfg_obj_asstring(addr); + symvalue.as_cpointer = addr; + tresult = isc_symtab_define(symtab, listname, 1, symvalue, + isc_symexists_reject); + if (tresult == ISC_R_EXISTS) + continue; + tresult = get_masters_def(config, listname, &obj); + if (tresult != ISC_R_SUCCESS) { + if (result == ISC_R_SUCCESS) + result = tresult; + cfg_obj_log(addr, logctx, ISC_LOG_ERROR, + "unable to find masters list '%s'", + listname); + continue; + } + /* Grow stack? */ + if (stackcount == pushed) { + void * new; + isc_uint32_t newlen = stackcount + 16; + size_t newsize, oldsize; + + newsize = newlen * sizeof(*stack); + oldsize = stackcount * sizeof(*stack); + new = isc_mem_get(mctx, newsize); + if (new == NULL) + goto cleanup; + if (stackcount != 0) { + memcpy(new, stack, oldsize); + isc_mem_put(mctx, stack, oldsize); + } + stack = new; + stackcount = newlen; + } + stack[pushed++] = cfg_list_next(element); + goto newlist; + } + if (pushed != 0) { + element = stack[--pushed]; + goto resume; + } + cleanup: + if (stack != NULL) + isc_mem_put(mctx, stack, stackcount * sizeof(*stack)); + isc_symtab_destroy(&symtab); + *countp = count; + return (result); +} + +static isc_result_t +check_update_policy(const cfg_obj_t *policy, isc_log_t *logctx) { + isc_result_t result = ISC_R_SUCCESS; + isc_result_t tresult; + const cfg_listelt_t *element; + const cfg_listelt_t *element2; + dns_fixedname_t fixed; + const char *str; + isc_buffer_t b; + + for (element = cfg_list_first(policy); + element != NULL; + element = cfg_list_next(element)) + { + const cfg_obj_t *stmt = cfg_listelt_value(element); + const cfg_obj_t *identity = cfg_tuple_get(stmt, "identity"); + const cfg_obj_t *matchtype = cfg_tuple_get(stmt, "matchtype"); + const cfg_obj_t *dname = cfg_tuple_get(stmt, "name"); + const cfg_obj_t *typelist = cfg_tuple_get(stmt, "types"); + + dns_fixedname_init(&fixed); + str = cfg_obj_asstring(identity); + isc_buffer_init(&b, str, strlen(str)); + isc_buffer_add(&b, strlen(str)); + tresult = dns_name_fromtext(dns_fixedname_name(&fixed), &b, + dns_rootname, ISC_FALSE, NULL); + if (tresult != ISC_R_SUCCESS) { + cfg_obj_log(identity, logctx, ISC_LOG_ERROR, + "'%s' is not a valid name", str); + result = tresult; + } + + dns_fixedname_init(&fixed); + str = cfg_obj_asstring(dname); + isc_buffer_init(&b, str, strlen(str)); + isc_buffer_add(&b, strlen(str)); + tresult = dns_name_fromtext(dns_fixedname_name(&fixed), &b, + dns_rootname, ISC_FALSE, NULL); + if (tresult != ISC_R_SUCCESS) { + cfg_obj_log(dname, logctx, ISC_LOG_ERROR, + "'%s' is not a valid name", str); + result = tresult; + } + if (tresult == ISC_R_SUCCESS && + strcasecmp(cfg_obj_asstring(matchtype), "wildcard") == 0 && + !dns_name_iswildcard(dns_fixedname_name(&fixed))) { + cfg_obj_log(identity, logctx, ISC_LOG_ERROR, + "'%s' is not a wildcard", str); + result = ISC_R_FAILURE; + } + + for (element2 = cfg_list_first(typelist); + element2 != NULL; + element2 = cfg_list_next(element2)) + { + const cfg_obj_t *typeobj; + isc_textregion_t r; + dns_rdatatype_t type; + + typeobj = cfg_listelt_value(element2); + DE_CONST(cfg_obj_asstring(typeobj), r.base); + r.length = strlen(r.base); + + tresult = dns_rdatatype_fromtext(&type, &r); + if (tresult != ISC_R_SUCCESS) { + cfg_obj_log(typeobj, logctx, ISC_LOG_ERROR, + "'%s' is not a valid type", r.base); + result = tresult; + } + } + } + return (result); +} + +#define MASTERZONE 1 +#define SLAVEZONE 2 +#define STUBZONE 4 +#define HINTZONE 8 +#define FORWARDZONE 16 +#define DELEGATIONZONE 32 +#define CHECKACL 64 + +typedef struct { + const char *name; + int allowed; +} optionstable; + +static isc_result_t +check_zoneconf(const cfg_obj_t *zconfig, const cfg_obj_t *voptions, + const cfg_obj_t *config, isc_symtab_t *symtab, + dns_rdataclass_t defclass, cfg_aclconfctx_t *actx, + isc_log_t *logctx, isc_mem_t *mctx) +{ + const char *zname; + const char *typestr; + unsigned int ztype; + const cfg_obj_t *zoptions; + const cfg_obj_t *obj = NULL; + isc_result_t result = ISC_R_SUCCESS; + isc_result_t tresult; + unsigned int i; + dns_rdataclass_t zclass; + dns_fixedname_t fixedname; + isc_buffer_t b; + + static optionstable options[] = { + { "allow-query", MASTERZONE | SLAVEZONE | STUBZONE | CHECKACL }, + { "allow-notify", SLAVEZONE | CHECKACL }, + { "allow-transfer", MASTERZONE | SLAVEZONE | CHECKACL }, + { "notify", MASTERZONE | SLAVEZONE }, + { "also-notify", MASTERZONE | SLAVEZONE }, + { "dialup", MASTERZONE | SLAVEZONE | STUBZONE }, + { "delegation-only", HINTZONE | STUBZONE }, + { "forward", MASTERZONE | SLAVEZONE | STUBZONE | FORWARDZONE}, + { "forwarders", MASTERZONE | SLAVEZONE | STUBZONE | FORWARDZONE}, + { "maintain-ixfr-base", MASTERZONE | SLAVEZONE }, + { "max-ixfr-log-size", MASTERZONE | SLAVEZONE }, + { "notify-source", MASTERZONE | SLAVEZONE }, + { "notify-source-v6", MASTERZONE | SLAVEZONE }, + { "transfer-source", SLAVEZONE | STUBZONE }, + { "transfer-source-v6", SLAVEZONE | STUBZONE }, + { "max-transfer-time-in", SLAVEZONE | STUBZONE }, + { "max-transfer-time-out", MASTERZONE | SLAVEZONE }, + { "max-transfer-idle-in", SLAVEZONE | STUBZONE }, + { "max-transfer-idle-out", MASTERZONE | SLAVEZONE }, + { "max-retry-time", SLAVEZONE | STUBZONE }, + { "min-retry-time", SLAVEZONE | STUBZONE }, + { "max-refresh-time", SLAVEZONE | STUBZONE }, + { "min-refresh-time", SLAVEZONE | STUBZONE }, + { "sig-validity-interval", MASTERZONE }, + { "zone-statistics", MASTERZONE | SLAVEZONE | STUBZONE }, + { "allow-update", MASTERZONE | CHECKACL }, + { "allow-update-forwarding", SLAVEZONE | CHECKACL }, + { "file", MASTERZONE | SLAVEZONE | STUBZONE | HINTZONE }, + { "journal", MASTERZONE | SLAVEZONE }, + { "ixfr-base", MASTERZONE | SLAVEZONE }, + { "ixfr-tmp-file", MASTERZONE | SLAVEZONE }, + { "masters", SLAVEZONE | STUBZONE }, + { "pubkey", MASTERZONE | SLAVEZONE | STUBZONE }, + { "update-policy", MASTERZONE }, + { "database", MASTERZONE | SLAVEZONE | STUBZONE }, + { "key-directory", MASTERZONE }, + { "check-wildcard", MASTERZONE }, + { "check-mx", MASTERZONE }, + { "integrity-check", MASTERZONE }, + { "check-mx-cname", MASTERZONE }, + { "check-srv-cname", MASTERZONE }, + { "masterfile-format", MASTERZONE | SLAVEZONE | STUBZONE | HINTZONE }, + { "update-check-ksk", MASTERZONE }, + }; + + static optionstable dialups[] = { + { "notify", MASTERZONE | SLAVEZONE }, + { "notify-passive", SLAVEZONE }, + { "refresh", SLAVEZONE | STUBZONE }, + { "passive", SLAVEZONE | STUBZONE }, + }; + + zname = cfg_obj_asstring(cfg_tuple_get(zconfig, "name")); + + zoptions = cfg_tuple_get(zconfig, "options"); + + obj = NULL; + (void)cfg_map_get(zoptions, "type", &obj); + if (obj == NULL) { + cfg_obj_log(zconfig, logctx, ISC_LOG_ERROR, + "zone '%s': type not present", zname); + return (ISC_R_FAILURE); + } + + typestr = cfg_obj_asstring(obj); + if (strcasecmp(typestr, "master") == 0) + ztype = MASTERZONE; + else if (strcasecmp(typestr, "slave") == 0) + ztype = SLAVEZONE; + else if (strcasecmp(typestr, "stub") == 0) + ztype = STUBZONE; + else if (strcasecmp(typestr, "forward") == 0) + ztype = FORWARDZONE; + else if (strcasecmp(typestr, "hint") == 0) + ztype = HINTZONE; + else if (strcasecmp(typestr, "delegation-only") == 0) + ztype = DELEGATIONZONE; + else { + cfg_obj_log(obj, logctx, ISC_LOG_ERROR, + "zone '%s': invalid type %s", + zname, typestr); + return (ISC_R_FAILURE); + } + + obj = cfg_tuple_get(zconfig, "class"); + if (cfg_obj_isstring(obj)) { + isc_textregion_t r; + + DE_CONST(cfg_obj_asstring(obj), r.base); + r.length = strlen(r.base); + result = dns_rdataclass_fromtext(&zclass, &r); + if (result != ISC_R_SUCCESS) { + cfg_obj_log(obj, logctx, ISC_LOG_ERROR, + "zone '%s': invalid class %s", + zname, r.base); + return (ISC_R_FAILURE); + } + if (zclass != defclass) { + cfg_obj_log(obj, logctx, ISC_LOG_ERROR, + "zone '%s': class '%s' does not " + "match view/default class", + zname, r.base); + return (ISC_R_FAILURE); + } + } + + /* + * Look for an already existing zone. + * We need to make this cannonical as isc_symtab_define() + * deals with strings. + */ + dns_fixedname_init(&fixedname); + isc_buffer_init(&b, zname, strlen(zname)); + isc_buffer_add(&b, strlen(zname)); + tresult = dns_name_fromtext(dns_fixedname_name(&fixedname), &b, + dns_rootname, ISC_TRUE, NULL); + if (result != ISC_R_SUCCESS) { + cfg_obj_log(zconfig, logctx, ISC_LOG_ERROR, + "zone '%s': is not a valid name", zname); + tresult = ISC_R_FAILURE; + } else { + char namebuf[DNS_NAME_FORMATSIZE]; + + dns_name_format(dns_fixedname_name(&fixedname), + namebuf, sizeof(namebuf)); + tresult = nameexist(zconfig, namebuf, ztype == HINTZONE ? 1 : 2, + symtab, "zone '%s': already exists " + "previous definition: %s:%u", logctx, mctx); + if (tresult != ISC_R_SUCCESS) + result = tresult; + } + + /* + * Look for inappropriate options for the given zone type. + * Check that ACLs expand correctly. + */ + for (i = 0; i < sizeof(options) / sizeof(options[0]); i++) { + obj = NULL; + if ((options[i].allowed & ztype) == 0 && + cfg_map_get(zoptions, options[i].name, &obj) == + ISC_R_SUCCESS) + { + if (strcmp(options[i].name, "allow-update") != 0 || + ztype != SLAVEZONE) { + cfg_obj_log(obj, logctx, ISC_LOG_ERROR, + "option '%s' is not allowed " + "in '%s' zone '%s'", + options[i].name, typestr, zname); + result = ISC_R_FAILURE; + } else + cfg_obj_log(obj, logctx, ISC_LOG_WARNING, + "option '%s' is not allowed " + "in '%s' zone '%s'", + options[i].name, typestr, zname); + } + obj = NULL; + if ((options[i].allowed & ztype) != 0 && + (options[i].allowed & CHECKACL) != 0) { + + tresult = checkacl(options[i].name, actx, zconfig, + voptions, config, logctx, mctx); + if (tresult != ISC_R_SUCCESS) + result = tresult; + } + + } + + /* + * Slave & stub zones must have a "masters" field. + */ + if (ztype == SLAVEZONE || ztype == STUBZONE) { + obj = NULL; + if (cfg_map_get(zoptions, "masters", &obj) != ISC_R_SUCCESS) { + cfg_obj_log(zoptions, logctx, ISC_LOG_ERROR, + "zone '%s': missing 'masters' entry", + zname); + result = ISC_R_FAILURE; + } else { + isc_uint32_t count; + tresult = validate_masters(obj, config, &count, + logctx, mctx); + if (tresult != ISC_R_SUCCESS && result == ISC_R_SUCCESS) + result = tresult; + if (tresult == ISC_R_SUCCESS && count == 0) { + cfg_obj_log(zoptions, logctx, ISC_LOG_ERROR, + "zone '%s': empty 'masters' entry", + zname); + result = ISC_R_FAILURE; + } + } + } + + /* + * Master zones can't have both "allow-update" and "update-policy". + */ + if (ztype == MASTERZONE) { + isc_result_t res1, res2; + obj = NULL; + res1 = cfg_map_get(zoptions, "allow-update", &obj); + obj = NULL; + res2 = cfg_map_get(zoptions, "update-policy", &obj); + if (res1 == ISC_R_SUCCESS && res2 == ISC_R_SUCCESS) { + cfg_obj_log(obj, logctx, ISC_LOG_ERROR, + "zone '%s': 'allow-update' is ignored " + "when 'update-policy' is present", + zname); + result = ISC_R_FAILURE; + } else if (res2 == ISC_R_SUCCESS && + check_update_policy(obj, logctx) != ISC_R_SUCCESS) + result = ISC_R_FAILURE; + } + + /* + * Check the excessively complicated "dialup" option. + */ + if (ztype == MASTERZONE || ztype == SLAVEZONE || ztype == STUBZONE) { + const cfg_obj_t *dialup = NULL; + (void)cfg_map_get(zoptions, "dialup", &dialup); + if (dialup != NULL && cfg_obj_isstring(dialup)) { + const char *str = cfg_obj_asstring(dialup); + for (i = 0; + i < sizeof(dialups) / sizeof(dialups[0]); + i++) + { + if (strcasecmp(dialups[i].name, str) != 0) + continue; + if ((dialups[i].allowed & ztype) == 0) { + cfg_obj_log(obj, logctx, + ISC_LOG_ERROR, + "dialup type '%s' is not " + "allowed in '%s' " + "zone '%s'", + str, typestr, zname); + result = ISC_R_FAILURE; + } + break; + } + if (i == sizeof(dialups) / sizeof(dialups[0])) { + cfg_obj_log(obj, logctx, ISC_LOG_ERROR, + "invalid dialup type '%s' in zone " + "'%s'", str, zname); + result = ISC_R_FAILURE; + } + } + } + + /* + * Check that forwarding is reasonable. + */ + if (check_forward(zoptions, logctx) != ISC_R_SUCCESS) + result = ISC_R_FAILURE; + + /* + * Check various options. + */ + tresult = check_options(zoptions, logctx, mctx); + if (tresult != ISC_R_SUCCESS) + result = tresult; + + /* + * If the zone type is rbt/rbt64 then master/hint zones + * require file clauses. + */ + obj = NULL; + tresult = cfg_map_get(zoptions, "database", &obj); + if (tresult == ISC_R_NOTFOUND || + (tresult == ISC_R_SUCCESS && + (strcmp("rbt", cfg_obj_asstring(obj)) == 0 || + strcmp("rbt64", cfg_obj_asstring(obj)) == 0))) { + obj = NULL; + tresult = cfg_map_get(zoptions, "file", &obj); + if (tresult != ISC_R_SUCCESS && + (ztype == MASTERZONE || ztype == HINTZONE)) { + cfg_obj_log(zconfig, logctx, ISC_LOG_ERROR, + "zone '%s': missing 'file' entry", + zname); + result = tresult; + } + } + + return (result); +} + + +typedef struct keyalgorithms { + const char *name; + isc_uint16_t size; +} algorithmtable; + +isc_result_t +bind9_check_key(const cfg_obj_t *key, isc_log_t *logctx) { + const cfg_obj_t *algobj = NULL; + const cfg_obj_t *secretobj = NULL; + const char *keyname = cfg_obj_asstring(cfg_map_getname(key)); + const char *algorithm; + int i; + size_t len = 0; + static const algorithmtable algorithms[] = { + { "hmac-md5", 128 }, + { "hmac-md5.sig-alg.reg.int", 0 }, + { "hmac-md5.sig-alg.reg.int.", 0 }, + { "hmac-sha1", 160 }, + { "hmac-sha224", 224 }, + { "hmac-sha256", 256 }, + { "hmac-sha384", 384 }, + { "hmac-sha512", 512 }, + { NULL, 0 } + }; + + (void)cfg_map_get(key, "algorithm", &algobj); + (void)cfg_map_get(key, "secret", &secretobj); + if (secretobj == NULL || algobj == NULL) { + cfg_obj_log(key, logctx, ISC_LOG_ERROR, + "key '%s' must have both 'secret' and " + "'algorithm' defined", + keyname); + return (ISC_R_FAILURE); + } + + algorithm = cfg_obj_asstring(algobj); + for (i = 0; algorithms[i].name != NULL; i++) { + len = strlen(algorithms[i].name); + if (strncasecmp(algorithms[i].name, algorithm, len) == 0 && + (algorithm[len] == '\0' || + (algorithms[i].size != 0 && algorithm[len] == '-'))) + break; + } + if (algorithms[i].name == NULL) { + cfg_obj_log(algobj, logctx, ISC_LOG_ERROR, + "unknown algorithm '%s'", algorithm); + return (ISC_R_NOTFOUND); + } + if (algorithm[len] == '-') { + isc_uint16_t digestbits; + isc_result_t result; + result = isc_parse_uint16(&digestbits, algorithm + len + 1, 10); + if (result == ISC_R_SUCCESS || result == ISC_R_RANGE) { + if (result == ISC_R_RANGE || + digestbits > algorithms[i].size) { + cfg_obj_log(algobj, logctx, ISC_LOG_ERROR, + "key '%s' digest-bits too large " + "[%u..%u]", keyname, + algorithms[i].size / 2, + algorithms[i].size); + return (ISC_R_RANGE); + } + if ((digestbits % 8) != 0) { + cfg_obj_log(algobj, logctx, ISC_LOG_ERROR, + "key '%s' digest-bits not multiple" + " of 8", keyname); + return (ISC_R_RANGE); + } + /* + * Recommended minima for hmac algorithms. + */ + if ((digestbits < (algorithms[i].size / 2U) || + (digestbits < 80U))) + cfg_obj_log(algobj, logctx, ISC_LOG_WARNING, + "key '%s' digest-bits too small " + "[<%u]", keyname, + algorithms[i].size/2); + } else { + cfg_obj_log(algobj, logctx, ISC_LOG_ERROR, + "key '%s': unable to parse digest-bits", + keyname); + return (result); + } + } + return (ISC_R_SUCCESS); +} + +static isc_result_t +check_keylist(const cfg_obj_t *keys, isc_symtab_t *symtab, isc_log_t *logctx) { + isc_result_t result = ISC_R_SUCCESS; + isc_result_t tresult; + const cfg_listelt_t *element; + + for (element = cfg_list_first(keys); + element != NULL; + element = cfg_list_next(element)) + { + const cfg_obj_t *key = cfg_listelt_value(element); + const char *keyname = cfg_obj_asstring(cfg_map_getname(key)); + isc_symvalue_t symvalue; + + tresult = bind9_check_key(key, logctx); + if (tresult != ISC_R_SUCCESS) + return (tresult); + + symvalue.as_cpointer = key; + tresult = isc_symtab_define(symtab, keyname, 1, + symvalue, isc_symexists_reject); + if (tresult == ISC_R_EXISTS) { + const char *file; + unsigned int line; + + RUNTIME_CHECK(isc_symtab_lookup(symtab, keyname, + 1, &symvalue) == ISC_R_SUCCESS); + file = cfg_obj_file(symvalue.as_cpointer); + line = cfg_obj_line(symvalue.as_cpointer); + + if (file == NULL) + file = "<unknown file>"; + cfg_obj_log(key, logctx, ISC_LOG_ERROR, + "key '%s': already exists " + "previous definition: %s:%u", + keyname, file, line); + result = tresult; + } else if (tresult != ISC_R_SUCCESS) + return (tresult); + } + return (result); +} + +static struct { + const char *v4; + const char *v6; +} sources[] = { + { "transfer-source", "transfer-source-v6" }, + { "notify-source", "notify-source-v6" }, + { "query-source", "query-source-v6" }, + { NULL, NULL } +}; + +static isc_result_t +check_servers(const cfg_obj_t *servers, isc_log_t *logctx) { + isc_result_t result = ISC_R_SUCCESS; + isc_result_t tresult; + const cfg_listelt_t *e1, *e2; + const cfg_obj_t *v1, *v2; + isc_netaddr_t n1, n2; + unsigned int p1, p2; + const cfg_obj_t *obj; + char buf[ISC_NETADDR_FORMATSIZE]; + const char *xfr; + int source; + + for (e1 = cfg_list_first(servers); e1 != NULL; e1 = cfg_list_next(e1)) { + v1 = cfg_listelt_value(e1); + cfg_obj_asnetprefix(cfg_map_getname(v1), &n1, &p1); + /* + * Check that unused bits are zero. + */ + tresult = isc_netaddr_prefixok(&n1, p1); + if (tresult != ISC_R_SUCCESS) { + INSIST(tresult == ISC_R_FAILURE); + isc_netaddr_format(&n1, buf, sizeof(buf)); + cfg_obj_log(v1, logctx, ISC_LOG_ERROR, + "server '%s/%u': invalid prefix " + "(extra bits specified)", buf, p1); + result = tresult; + } + source = 0; + do { + obj = NULL; + if (n1.family == AF_INET) + xfr = sources[source].v6; + else + xfr = sources[source].v4; + (void)cfg_map_get(v1, xfr, &obj); + if (obj != NULL) { + isc_netaddr_format(&n1, buf, sizeof(buf)); + cfg_obj_log(v1, logctx, ISC_LOG_ERROR, + "server '%s': %s not legal", + buf, xfr); + result = ISC_R_FAILURE; + } + } while (sources[++source].v4 != NULL); + e2 = e1; + while ((e2 = cfg_list_next(e2)) != NULL) { + v2 = cfg_listelt_value(e2); + cfg_obj_asnetprefix(cfg_map_getname(v2), &n2, &p2); + if (p1 == p2 && isc_netaddr_equal(&n1, &n2)) { + const char *file = cfg_obj_file(v1); + unsigned int line = cfg_obj_line(v1); + + if (file == NULL) + file = "<unknown file>"; + + isc_netaddr_format(&n2, buf, sizeof(buf)); + cfg_obj_log(v2, logctx, ISC_LOG_ERROR, + "server '%s/%u': already exists " + "previous definition: %s:%u", + buf, p2, file, line); + result = ISC_R_FAILURE; + } + } + } + return (result); +} + +static isc_result_t +check_viewconf(const cfg_obj_t *config, const cfg_obj_t *voptions, + dns_rdataclass_t vclass, isc_log_t *logctx, isc_mem_t *mctx) +{ + const cfg_obj_t *servers = NULL; + const cfg_obj_t *zones = NULL; + const cfg_obj_t *keys = NULL; + const cfg_listelt_t *element; + isc_symtab_t *symtab = NULL; + isc_result_t result = ISC_R_SUCCESS; + isc_result_t tresult = ISC_R_SUCCESS; + cfg_aclconfctx_t actx; + const cfg_obj_t *obj; + isc_boolean_t enablednssec, enablevalidation; + + /* + * Check that all zone statements are syntactically correct and + * there are no duplicate zones. + */ + tresult = isc_symtab_create(mctx, 100, freekey, mctx, + ISC_FALSE, &symtab); + if (tresult != ISC_R_SUCCESS) + return (ISC_R_NOMEMORY); + + cfg_aclconfctx_init(&actx); + + if (voptions != NULL) + (void)cfg_map_get(voptions, "zone", &zones); + else + (void)cfg_map_get(config, "zone", &zones); + + for (element = cfg_list_first(zones); + element != NULL; + element = cfg_list_next(element)) + { + isc_result_t tresult; + const cfg_obj_t *zone = cfg_listelt_value(element); + + tresult = check_zoneconf(zone, voptions, config, symtab, + vclass, &actx, logctx, mctx); + if (tresult != ISC_R_SUCCESS) + result = ISC_R_FAILURE; + } + + isc_symtab_destroy(&symtab); + + /* + * Check that all key statements are syntactically correct and + * there are no duplicate keys. + */ + tresult = isc_symtab_create(mctx, 100, NULL, NULL, ISC_TRUE, &symtab); + if (tresult != ISC_R_SUCCESS) + return (ISC_R_NOMEMORY); + + (void)cfg_map_get(config, "key", &keys); + tresult = check_keylist(keys, symtab, logctx); + if (tresult == ISC_R_EXISTS) + result = ISC_R_FAILURE; + else if (tresult != ISC_R_SUCCESS) { + isc_symtab_destroy(&symtab); + return (tresult); + } + + if (voptions != NULL) { + keys = NULL; + (void)cfg_map_get(voptions, "key", &keys); + tresult = check_keylist(keys, symtab, logctx); + if (tresult == ISC_R_EXISTS) + result = ISC_R_FAILURE; + else if (tresult != ISC_R_SUCCESS) { + isc_symtab_destroy(&symtab); + return (tresult); + } + } + + isc_symtab_destroy(&symtab); + + /* + * Check that forwarding is reasonable. + */ + if (voptions == NULL) { + const cfg_obj_t *options = NULL; + (void)cfg_map_get(config, "options", &options); + if (options != NULL) + if (check_forward(options, logctx) != ISC_R_SUCCESS) + result = ISC_R_FAILURE; + } else { + if (check_forward(voptions, logctx) != ISC_R_SUCCESS) + result = ISC_R_FAILURE; + } + /* + * Check that dual-stack-servers is reasonable. + */ + if (voptions == NULL) { + const cfg_obj_t *options = NULL; + (void)cfg_map_get(config, "options", &options); + if (options != NULL) + if (check_dual_stack(options, logctx) != ISC_R_SUCCESS) + result = ISC_R_FAILURE; + } else { + if (check_dual_stack(voptions, logctx) != ISC_R_SUCCESS) + result = ISC_R_FAILURE; + } + + /* + * Check that rrset-order is reasonable. + */ + if (voptions != NULL) { + if (check_order(voptions, logctx) != ISC_R_SUCCESS) + result = ISC_R_FAILURE; + } + + if (voptions != NULL) { + (void)cfg_map_get(voptions, "server", &servers); + if (servers != NULL && + check_servers(servers, logctx) != ISC_R_SUCCESS) + result = ISC_R_FAILURE; + } + + /* + * Check that dnssec-enable/dnssec-validation are sensible. + */ + obj = NULL; + if (voptions != NULL) + (void)cfg_map_get(voptions, "dnssec-enable", &obj); + if (obj == NULL) + (void)cfg_map_get(config, "dnssec-enable", &obj); + if (obj == NULL) + enablednssec = ISC_TRUE; + else + enablednssec = cfg_obj_asboolean(obj); + + obj = NULL; + if (voptions != NULL) + (void)cfg_map_get(voptions, "dnssec-validation", &obj); + if (obj == NULL) + (void)cfg_map_get(config, "dnssec-validation", &obj); + if (obj == NULL) + enablevalidation = ISC_FALSE; /* XXXMPA Change for 9.5. */ + else + enablevalidation = cfg_obj_asboolean(obj); + + if (enablevalidation && !enablednssec) + cfg_obj_log(obj, logctx, ISC_LOG_WARNING, + "'dnssec-validation yes;' and 'dnssec-enable no;'"); + + if (voptions != NULL) + tresult = check_options(voptions, logctx, mctx); + else + tresult = check_options(config, logctx, mctx); + if (tresult != ISC_R_SUCCESS) + result = tresult; + + tresult = check_viewacls(&actx, voptions, config, logctx, mctx); + if (tresult != ISC_R_SUCCESS) + result = tresult; + + cfg_aclconfctx_destroy(&actx); + + return (result); +} + +static const char * +default_channels[] = { + "default_syslog", + "default_stderr", + "default_debug", + "null", + NULL +}; + +static isc_result_t +bind9_check_logging(const cfg_obj_t *config, isc_log_t *logctx, + isc_mem_t *mctx) +{ + const cfg_obj_t *categories = NULL; + const cfg_obj_t *category; + const cfg_obj_t *channels = NULL; + const cfg_obj_t *channel; + const cfg_listelt_t *element; + const cfg_listelt_t *delement; + const char *channelname; + const char *catname; + const cfg_obj_t *fileobj = NULL; + const cfg_obj_t *syslogobj = NULL; + const cfg_obj_t *nullobj = NULL; + const cfg_obj_t *stderrobj = NULL; + const cfg_obj_t *logobj = NULL; + isc_result_t result = ISC_R_SUCCESS; + isc_result_t tresult; + isc_symtab_t *symtab = NULL; + isc_symvalue_t symvalue; + int i; + + (void)cfg_map_get(config, "logging", &logobj); + if (logobj == NULL) + return (ISC_R_SUCCESS); + + result = isc_symtab_create(mctx, 100, NULL, NULL, ISC_FALSE, &symtab); + if (result != ISC_R_SUCCESS) + return (result); + + symvalue.as_cpointer = NULL; + for (i = 0; default_channels[i] != NULL; i++) { + tresult = isc_symtab_define(symtab, default_channels[i], 1, + symvalue, isc_symexists_replace); + if (tresult != ISC_R_SUCCESS) + result = tresult; + } + + cfg_map_get(logobj, "channel", &channels); + + for (element = cfg_list_first(channels); + element != NULL; + element = cfg_list_next(element)) + { + channel = cfg_listelt_value(element); + channelname = cfg_obj_asstring(cfg_map_getname(channel)); + fileobj = syslogobj = nullobj = stderrobj = NULL; + (void)cfg_map_get(channel, "file", &fileobj); + (void)cfg_map_get(channel, "syslog", &syslogobj); + (void)cfg_map_get(channel, "null", &nullobj); + (void)cfg_map_get(channel, "stderr", &stderrobj); + i = 0; + if (fileobj != NULL) + i++; + if (syslogobj != NULL) + i++; + if (nullobj != NULL) + i++; + if (stderrobj != NULL) + i++; + if (i != 1) { + cfg_obj_log(channel, logctx, ISC_LOG_ERROR, + "channel '%s': exactly one of file, syslog, " + "null, and stderr must be present", + channelname); + result = ISC_R_FAILURE; + } + tresult = isc_symtab_define(symtab, channelname, 1, + symvalue, isc_symexists_replace); + if (tresult != ISC_R_SUCCESS) + result = tresult; + } + + cfg_map_get(logobj, "category", &categories); + + for (element = cfg_list_first(categories); + element != NULL; + element = cfg_list_next(element)) + { + category = cfg_listelt_value(element); + catname = cfg_obj_asstring(cfg_tuple_get(category, "name")); + if (isc_log_categorybyname(logctx, catname) == NULL) { + cfg_obj_log(category, logctx, ISC_LOG_ERROR, + "undefined category: '%s'", catname); + result = ISC_R_FAILURE; + } + channels = cfg_tuple_get(category, "destinations"); + for (delement = cfg_list_first(channels); + delement != NULL; + delement = cfg_list_next(delement)) + { + channel = cfg_listelt_value(delement); + channelname = cfg_obj_asstring(channel); + tresult = isc_symtab_lookup(symtab, channelname, 1, + &symvalue); + if (tresult != ISC_R_SUCCESS) { + cfg_obj_log(channel, logctx, ISC_LOG_ERROR, + "undefined channel: '%s'", + channelname); + result = tresult; + } + } + } + isc_symtab_destroy(&symtab); + return (result); +} + +static isc_result_t +key_exists(const cfg_obj_t *keylist, const char *keyname) { + const cfg_listelt_t *element; + const char *str; + const cfg_obj_t *obj; + + if (keylist == NULL) + return (ISC_R_NOTFOUND); + for (element = cfg_list_first(keylist); + element != NULL; + element = cfg_list_next(element)) + { + obj = cfg_listelt_value(element); + str = cfg_obj_asstring(cfg_map_getname(obj)); + if (strcasecmp(str, keyname) == 0) + return (ISC_R_SUCCESS); + } + return (ISC_R_NOTFOUND); +} + +static isc_result_t +bind9_check_controlskeys(const cfg_obj_t *control, const cfg_obj_t *keylist, + isc_log_t *logctx) +{ + isc_result_t result = ISC_R_SUCCESS, tresult; + const cfg_obj_t *control_keylist; + const cfg_listelt_t *element; + const cfg_obj_t *key; + + control_keylist = cfg_tuple_get(control, "keys"); + if (cfg_obj_isvoid(control_keylist)) + return (ISC_R_SUCCESS); + + for (element = cfg_list_first(control_keylist); + element != NULL; + element = cfg_list_next(element)) + { + key = cfg_listelt_value(element); + tresult = key_exists(keylist, cfg_obj_asstring(key)); + if (tresult != ISC_R_SUCCESS) { + cfg_obj_log(key, logctx, ISC_LOG_ERROR, + "unknown key '%s'", cfg_obj_asstring(key)); + result = tresult; + } + } + return (result); +} + +static isc_result_t +bind9_check_controls(const cfg_obj_t *config, isc_log_t *logctx, + isc_mem_t *mctx) +{ + isc_result_t result = ISC_R_SUCCESS, tresult; + cfg_aclconfctx_t actx; + const cfg_listelt_t *element, *element2; + const cfg_obj_t *allow; + const cfg_obj_t *control; + const cfg_obj_t *controls; + const cfg_obj_t *controlslist = NULL; + const cfg_obj_t *inetcontrols; + const cfg_obj_t *unixcontrols; + const cfg_obj_t *keylist = NULL; + const char *path; + isc_uint32_t perm, mask; + dns_acl_t *acl = NULL; + isc_sockaddr_t addr; + int i; + + (void)cfg_map_get(config, "controls", &controlslist); + if (controlslist == NULL) + return (ISC_R_SUCCESS); + + (void)cfg_map_get(config, "key", &keylist); + + cfg_aclconfctx_init(&actx); + + /* + * INET: Check allow clause. + * UNIX: Check "perm" for sanity, check path length. + */ + for (element = cfg_list_first(controlslist); + element != NULL; + element = cfg_list_next(element)) { + controls = cfg_listelt_value(element); + unixcontrols = NULL; + inetcontrols = NULL; + (void)cfg_map_get(controls, "unix", &unixcontrols); + (void)cfg_map_get(controls, "inet", &inetcontrols); + for (element2 = cfg_list_first(inetcontrols); + element2 != NULL; + element2 = cfg_list_next(element2)) { + control = cfg_listelt_value(element2); + allow = cfg_tuple_get(control, "allow"); + tresult = cfg_acl_fromconfig(allow, config, logctx, + &actx, mctx, &acl); + if (acl != NULL) + dns_acl_detach(&acl); + if (tresult != ISC_R_SUCCESS) + result = tresult; + tresult = bind9_check_controlskeys(control, keylist, + logctx); + if (tresult != ISC_R_SUCCESS) + result = tresult; + } + for (element2 = cfg_list_first(unixcontrols); + element2 != NULL; + element2 = cfg_list_next(element2)) { + control = cfg_listelt_value(element2); + path = cfg_obj_asstring(cfg_tuple_get(control, "path")); + tresult = isc_sockaddr_frompath(&addr, path); + if (tresult == ISC_R_NOSPACE) { + cfg_obj_log(control, logctx, ISC_LOG_ERROR, + "unix control '%s': path too long", + path); + result = ISC_R_NOSPACE; + } + perm = cfg_obj_asuint32(cfg_tuple_get(control, "perm")); + for (i = 0; i < 3; i++) { +#ifdef NEED_SECURE_DIRECTORY + mask = (0x1 << (i*3)); /* SEARCH */ +#else + mask = (0x6 << (i*3)); /* READ + WRITE */ +#endif + if ((perm & mask) == mask) + break; + } + if (i == 0) { + cfg_obj_log(control, logctx, ISC_LOG_WARNING, + "unix control '%s' allows access " + "to everyone", path); + } else if (i == 3) { + cfg_obj_log(control, logctx, ISC_LOG_WARNING, + "unix control '%s' allows access " + "to nobody", path); + } + tresult = bind9_check_controlskeys(control, keylist, + logctx); + if (tresult != ISC_R_SUCCESS) + result = tresult; + } + } + cfg_aclconfctx_destroy(&actx); + return (result); +} + +isc_result_t +bind9_check_namedconf(const cfg_obj_t *config, isc_log_t *logctx, + isc_mem_t *mctx) +{ + const cfg_obj_t *options = NULL; + const cfg_obj_t *servers = NULL; + const cfg_obj_t *views = NULL; + const cfg_obj_t *acls = NULL; + const cfg_obj_t *kals = NULL; + const cfg_obj_t *obj; + const cfg_listelt_t *velement; + isc_result_t result = ISC_R_SUCCESS; + isc_result_t tresult; + isc_symtab_t *symtab = NULL; + + static const char *builtin[] = { "localhost", "localnets", + "any", "none"}; + + (void)cfg_map_get(config, "options", &options); + + if (options != NULL && + check_options(options, logctx, mctx) != ISC_R_SUCCESS) + result = ISC_R_FAILURE; + + (void)cfg_map_get(config, "server", &servers); + if (servers != NULL && + check_servers(servers, logctx) != ISC_R_SUCCESS) + result = ISC_R_FAILURE; + + if (bind9_check_logging(config, logctx, mctx) != ISC_R_SUCCESS) + result = ISC_R_FAILURE; + + if (bind9_check_controls(config, logctx, mctx) != ISC_R_SUCCESS) + result = ISC_R_FAILURE; + + if (options != NULL && + check_order(options, logctx) != ISC_R_SUCCESS) + result = ISC_R_FAILURE; + + (void)cfg_map_get(config, "view", &views); + + if (views != NULL && options != NULL) + if (check_dual_stack(options, logctx) != ISC_R_SUCCESS) + result = ISC_R_FAILURE; + + if (views == NULL) { + if (check_viewconf(config, NULL, dns_rdataclass_in, + logctx, mctx) != ISC_R_SUCCESS) + result = ISC_R_FAILURE; + } else { + const cfg_obj_t *zones = NULL; + + (void)cfg_map_get(config, "zone", &zones); + if (zones != NULL) { + cfg_obj_log(zones, logctx, ISC_LOG_ERROR, + "when using 'view' statements, " + "all zones must be in views"); + result = ISC_R_FAILURE; + } + } + + tresult = isc_symtab_create(mctx, 100, NULL, NULL, ISC_TRUE, &symtab); + if (tresult != ISC_R_SUCCESS) + result = tresult; + for (velement = cfg_list_first(views); + velement != NULL; + velement = cfg_list_next(velement)) + { + const cfg_obj_t *view = cfg_listelt_value(velement); + const cfg_obj_t *vname = cfg_tuple_get(view, "name"); + const cfg_obj_t *voptions = cfg_tuple_get(view, "options"); + const cfg_obj_t *vclassobj = cfg_tuple_get(view, "class"); + dns_rdataclass_t vclass = dns_rdataclass_in; + isc_result_t tresult = ISC_R_SUCCESS; + const char *key = cfg_obj_asstring(vname); + isc_symvalue_t symvalue; + + if (cfg_obj_isstring(vclassobj)) { + isc_textregion_t r; + + DE_CONST(cfg_obj_asstring(vclassobj), r.base); + r.length = strlen(r.base); + tresult = dns_rdataclass_fromtext(&vclass, &r); + if (tresult != ISC_R_SUCCESS) + cfg_obj_log(vclassobj, logctx, ISC_LOG_ERROR, + "view '%s': invalid class %s", + cfg_obj_asstring(vname), r.base); + } + if (tresult == ISC_R_SUCCESS && symtab != NULL) { + symvalue.as_cpointer = view; + tresult = isc_symtab_define(symtab, key, vclass, + symvalue, + isc_symexists_reject); + if (tresult == ISC_R_EXISTS) { + const char *file; + unsigned int line; + RUNTIME_CHECK(isc_symtab_lookup(symtab, key, + vclass, &symvalue) == ISC_R_SUCCESS); + file = cfg_obj_file(symvalue.as_cpointer); + line = cfg_obj_line(symvalue.as_cpointer); + cfg_obj_log(view, logctx, ISC_LOG_ERROR, + "view '%s': already exists " + "previous definition: %s:%u", + key, file, line); + result = tresult; + } else if (result != ISC_R_SUCCESS) { + result = tresult; + } else if ((strcasecmp(key, "_bind") == 0 && + vclass == dns_rdataclass_ch) || + (strcasecmp(key, "_default") == 0 && + vclass == dns_rdataclass_in)) { + cfg_obj_log(view, logctx, ISC_LOG_ERROR, + "attempt to redefine builtin view " + "'%s'", key); + result = ISC_R_EXISTS; + } + } + if (tresult == ISC_R_SUCCESS) + tresult = check_viewconf(config, voptions, + vclass, logctx, mctx); + if (tresult != ISC_R_SUCCESS) + result = ISC_R_FAILURE; + } + if (symtab != NULL) + isc_symtab_destroy(&symtab); + + if (views != NULL && options != NULL) { + obj = NULL; + tresult = cfg_map_get(options, "cache-file", &obj); + if (tresult == ISC_R_SUCCESS) { + cfg_obj_log(obj, logctx, ISC_LOG_ERROR, + "'cache-file' cannot be a global " + "option if views are present"); + result = ISC_R_FAILURE; + } + } + + tresult = cfg_map_get(config, "acl", &acls); + if (tresult == ISC_R_SUCCESS) { + const cfg_listelt_t *elt; + const cfg_listelt_t *elt2; + const char *aclname; + + for (elt = cfg_list_first(acls); + elt != NULL; + elt = cfg_list_next(elt)) { + const cfg_obj_t *acl = cfg_listelt_value(elt); + unsigned int i; + + aclname = cfg_obj_asstring(cfg_tuple_get(acl, "name")); + for (i = 0; + i < sizeof(builtin) / sizeof(builtin[0]); + i++) + if (strcasecmp(aclname, builtin[i]) == 0) { + cfg_obj_log(acl, logctx, ISC_LOG_ERROR, + "attempt to redefine " + "builtin acl '%s'", + aclname); + result = ISC_R_FAILURE; + break; + } + + for (elt2 = cfg_list_next(elt); + elt2 != NULL; + elt2 = cfg_list_next(elt2)) { + const cfg_obj_t *acl2 = cfg_listelt_value(elt2); + const char *name; + name = cfg_obj_asstring(cfg_tuple_get(acl2, + "name")); + if (strcasecmp(aclname, name) == 0) { + const char *file = cfg_obj_file(acl); + unsigned int line = cfg_obj_line(acl); + + if (file == NULL) + file = "<unknown file>"; + + cfg_obj_log(acl2, logctx, ISC_LOG_ERROR, + "attempt to redefine " + "acl '%s' previous " + "definition: %s:%u", + name, file, line); + result = ISC_R_FAILURE; + } + } + } + } + + tresult = cfg_map_get(config, "kal", &kals); + if (tresult == ISC_R_SUCCESS) { + const cfg_listelt_t *elt; + const cfg_listelt_t *elt2; + const char *aclname; + + for (elt = cfg_list_first(kals); + elt != NULL; + elt = cfg_list_next(elt)) { + const cfg_obj_t *acl = cfg_listelt_value(elt); + + aclname = cfg_obj_asstring(cfg_tuple_get(acl, "name")); + + for (elt2 = cfg_list_next(elt); + elt2 != NULL; + elt2 = cfg_list_next(elt2)) { + const cfg_obj_t *acl2 = cfg_listelt_value(elt2); + const char *name; + name = cfg_obj_asstring(cfg_tuple_get(acl2, + "name")); + if (strcasecmp(aclname, name) == 0) { + const char *file = cfg_obj_file(acl); + unsigned int line = cfg_obj_line(acl); + + if (file == NULL) + file = "<unknown file>"; + + cfg_obj_log(acl2, logctx, ISC_LOG_ERROR, + "attempt to redefine " + "kal '%s' previous " + "definition: %s:%u", + name, file, line); + result = ISC_R_FAILURE; + } + } + } + } + + return (result); +} |