/* $NetBSD: check.c,v 1.14.2.2 2024/02/25 15:46:46 martin Exp $ */ /* * Copyright (C) Internet Systems Consortium, Inc. ("ISC") * * SPDX-License-Identifier: MPL-2.0 * * 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 https://mozilla.org/MPL/2.0/. * * See the COPYRIGHT file distributed with this work for additional * information regarding copyright ownership. */ /*! \file */ #include #include #include #include #include #ifdef HAVE_DNSTAP #include #endif #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include static in_port_t dnsport = 53; static isc_result_t fileexist(const cfg_obj_t *obj, isc_symtab_t *symtab, bool writeable, isc_log_t *logctxlogc); static isc_result_t keydirexist(const cfg_obj_t *zcgf, const char *dir, const char *kaspnamestr, isc_symtab_t *symtab, isc_log_t *logctx, isc_mem_t *mctx); 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); if (result == ISC_R_SUCCESS) { 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); if (result == ISC_R_SUCCESS) { result = ISC_R_FAILURE; } } } obj = cfg_tuple_get(ent, "name"); if (cfg_obj_isstring(obj)) { str = cfg_obj_asstring(obj); isc_buffer_constinit(&b, str, strlen(str)); isc_buffer_add(&b, strlen(str)); tresult = dns_name_fromtext(dns_fixedname_name(&fixed), &b, dns_rootname, 0, NULL); if (tresult != ISC_R_SUCCESS) { cfg_obj_log(obj, logctx, ISC_LOG_ERROR, "rrset-order: invalid name '%s'", str); if (result == ISC_R_SUCCESS) { 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"); if (result == ISC_R_SUCCESS) { 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"); if (result == ISC_R_SUCCESS) { 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' was disabled at " "compilation time"); #endif /* if !DNS_RDATASET_FIXED */ } else if (strcasecmp(cfg_obj_asstring(obj), "random") != 0 && strcasecmp(cfg_obj_asstring(obj), "cyclic") != 0 && strcasecmp(cfg_obj_asstring(obj), "none") != 0) { cfg_obj_log(obj, logctx, ISC_LOG_ERROR, "rrset-order: invalid order '%s'", cfg_obj_asstring(obj)); if (result == ISC_R_SUCCESS) { 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 (result == ISC_R_SUCCESS && 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)) { uint32_t val = cfg_obj_asuint32(obj); if (val > UINT16_MAX) { cfg_obj_log(obj, logctx, ISC_LOG_ERROR, "port '%u' out of range", val); if (result == ISC_R_SUCCESS) { result = ISC_R_RANGE; } } } 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_constinit(&buffer, str, strlen(str)); isc_buffer_add(&buffer, strlen(str)); name = dns_fixedname_initname(&fixed); tresult = dns_name_fromtext(name, &buffer, dns_rootname, 0, NULL); if (tresult != ISC_R_SUCCESS) { cfg_obj_log(obj, logctx, ISC_LOG_ERROR, "bad name '%s'", str); if (result == ISC_R_SUCCESS) { result = tresult; } } obj = cfg_tuple_get(value, "port"); if (cfg_obj_isuint32(obj)) { uint32_t val = cfg_obj_asuint32(obj); if (val > UINT16_MAX) { cfg_obj_log(obj, logctx, ISC_LOG_ERROR, "port '%u' out of range", val); if (result == ISC_R_SUCCESS) { result = ISC_R_RANGE; } } } } return (result); } static isc_result_t check_forward(const cfg_obj_t *options, const cfg_obj_t *global, 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 (forwarders != NULL && global != NULL) { const char *file = cfg_obj_file(global); unsigned int line = cfg_obj_line(global); cfg_obj_log(forwarders, logctx, ISC_LOG_ERROR, "forwarders declared in root zone and " "in general configuration: %s:%u", file, line); return (ISC_R_FAILURE); } 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; name = dns_fixedname_initname(&fixed); obj = cfg_tuple_get(disabled, "name"); str = cfg_obj_asstring(obj); isc_buffer_constinit(&b, str, strlen(str)); isc_buffer_add(&b, strlen(str)); tresult = dns_name_fromtext(name, &b, dns_rootname, 0, 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; 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) { cfg_obj_log(cfg_listelt_value(element), logctx, ISC_LOG_ERROR, "invalid algorithm '%s'", r.base); result = tresult; } } return (result); } static isc_result_t disabled_ds_digests(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; name = dns_fixedname_initname(&fixed); obj = cfg_tuple_get(disabled, "name"); str = cfg_obj_asstring(obj); isc_buffer_constinit(&b, str, strlen(str)); isc_buffer_add(&b, strlen(str)); tresult = dns_name_fromtext(name, &b, dns_rootname, 0, 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, "digests"); for (element = cfg_list_first(obj); element != NULL; element = cfg_list_next(element)) { isc_textregion_t r; dns_dsdigest_t digest; DE_CONST(cfg_obj_asstring(cfg_listelt_value(element)), r.base); r.length = strlen(r.base); /* works with a numeric argument too */ tresult = dns_dsdigest_fromtext(&digest, &r); if (tresult != ISC_R_SUCCESS) { cfg_obj_log(cfg_listelt_value(element), logctx, ISC_LOG_ERROR, "invalid digest type '%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); 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 = ""; } 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; name = dns_fixedname_initname(&fixed); obj = cfg_tuple_get(secure, "name"); str = cfg_obj_asstring(obj); isc_buffer_constinit(&b, str, strlen(str)); isc_buffer_add(&b, strlen(str)); result = dns_name_fromtext(name, &b, dns_rootname, 0, 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, 0, &acl); if (acl != NULL) { dns_acl_detach(&acl); } if (strcasecmp(aclname, "allow-transfer") == 0 && cfg_obj_istuple(aclobj)) { const cfg_obj_t *obj_port = cfg_tuple_get( cfg_tuple_get(aclobj, "port-transport"), "port"); const cfg_obj_t *obj_proto = cfg_tuple_get( cfg_tuple_get(aclobj, "port-transport"), "transport"); if (cfg_obj_isuint32(obj_port) && cfg_obj_asuint32(obj_port) >= UINT16_MAX) { cfg_obj_log(obj_port, logctx, ISC_LOG_ERROR, "port value '%u' is out of range", cfg_obj_asuint32(obj_port)); if (result == ISC_R_SUCCESS) { result = ISC_R_RANGE; } } if (cfg_obj_isstring(obj_proto)) { const char *allowed[] = { "tcp", "tls" }; const char *transport = cfg_obj_asstring(obj_proto); bool found = false; for (size_t i = 0; i < ARRAY_SIZE(allowed); i++) { if (strcasecmp(transport, allowed[i]) == 0) { found = true; } } if (!found) { cfg_obj_log(obj_proto, logctx, ISC_LOG_ERROR, "'%s' is not a valid transport " "protocol for " "zone " "transfers. Please specify either " "'tcp' or 'tls'", transport); result = ISC_R_FAILURE; } } } 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-on", "allow-query-cache", "allow-query-cache-on", "blackhole", "keep-response-order", "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); } static void dns64_error(const cfg_obj_t *obj, isc_log_t *logctx, isc_netaddr_t *netaddr, unsigned int prefixlen, const char *message) { char buf[ISC_NETADDR_FORMATSIZE + 1]; isc_netaddr_format(netaddr, buf, sizeof(buf)); cfg_obj_log(obj, logctx, ISC_LOG_ERROR, "dns64 prefix %s/%u %s", buf, prefixlen, message); } static isc_result_t check_dns64(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; const cfg_obj_t *dns64 = NULL; const cfg_obj_t *options; const cfg_listelt_t *element; const cfg_obj_t *map, *obj; isc_netaddr_t na, sa; unsigned int prefixlen; int nbytes; int i; static const char *acls[] = { "clients", "exclude", "mapped", NULL }; if (voptions != NULL) { cfg_map_get(voptions, "dns64", &dns64); } if (config != NULL && dns64 == NULL) { options = NULL; cfg_map_get(config, "options", &options); if (options != NULL) { cfg_map_get(options, "dns64", &dns64); } } if (dns64 == NULL) { return (ISC_R_SUCCESS); } for (element = cfg_list_first(dns64); element != NULL; element = cfg_list_next(element)) { map = cfg_listelt_value(element); obj = cfg_map_getname(map); cfg_obj_asnetprefix(obj, &na, &prefixlen); if (na.family != AF_INET6) { dns64_error(map, logctx, &na, prefixlen, "must be IPv6"); result = ISC_R_FAILURE; continue; } if (na.type.in6.s6_addr[8] != 0) { dns64_error(map, logctx, &na, prefixlen, "bits [64..71] must be zero"); result = ISC_R_FAILURE; continue; } if (prefixlen != 32 && prefixlen != 40 && prefixlen != 48 && prefixlen != 56 && prefixlen != 64 && prefixlen != 96) { dns64_error(map, logctx, &na, prefixlen, "length is not 32/40/48/56/64/96"); result = ISC_R_FAILURE; continue; } for (i = 0; acls[i] != NULL; i++) { obj = NULL; (void)cfg_map_get(map, acls[i], &obj); if (obj != NULL) { dns_acl_t *acl = NULL; isc_result_t tresult; tresult = cfg_acl_fromconfig(obj, config, logctx, actx, mctx, 0, &acl); if (acl != NULL) { dns_acl_detach(&acl); } if (tresult != ISC_R_SUCCESS) { result = tresult; } } } obj = NULL; (void)cfg_map_get(map, "suffix", &obj); if (obj != NULL) { static const unsigned char zeros[16]; isc_netaddr_fromsockaddr(&sa, cfg_obj_assockaddr(obj)); if (sa.family != AF_INET6) { cfg_obj_log(map, logctx, ISC_LOG_ERROR, "dns64 requires a IPv6 suffix"); result = ISC_R_FAILURE; continue; } nbytes = prefixlen / 8 + 4; if (prefixlen <= 64) { nbytes++; } if (memcmp(sa.type.in6.s6_addr, zeros, nbytes) != 0) { char netaddrbuf[ISC_NETADDR_FORMATSIZE]; isc_netaddr_format(&sa, netaddrbuf, sizeof(netaddrbuf)); cfg_obj_log(obj, logctx, ISC_LOG_ERROR, "bad suffix '%s' leading " "%u octets not zeros", netaddrbuf, nbytes); result = ISC_R_FAILURE; } } } return (result); } #define CHECK_RRL(cond, pat, val1, val2) \ do { \ if (!(cond)) { \ cfg_obj_log(obj, logctx, ISC_LOG_ERROR, pat, val1, \ val2); \ if (result == ISC_R_SUCCESS) \ result = ISC_R_RANGE; \ } \ } while (0) #define CHECK_RRL_RATE(rate, def, max_rate, name) \ do { \ obj = NULL; \ mresult = cfg_map_get(map, name, &obj); \ if (mresult == ISC_R_SUCCESS) { \ rate = cfg_obj_asuint32(obj); \ CHECK_RRL(rate <= max_rate, name " %d > %d", rate, \ max_rate); \ } \ } while (0) static isc_result_t check_ratelimit(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; isc_result_t mresult; const cfg_obj_t *map = NULL; const cfg_obj_t *options; const cfg_obj_t *obj; int min_entries, i; int all_per_second; int errors_per_second; int nodata_per_second; int nxdomains_per_second; int referrals_per_second; int responses_per_second; int slip; if (voptions != NULL) { cfg_map_get(voptions, "rate-limit", &map); } if (config != NULL && map == NULL) { options = NULL; cfg_map_get(config, "options", &options); if (options != NULL) { cfg_map_get(options, "rate-limit", &map); } } if (map == NULL) { return (ISC_R_SUCCESS); } min_entries = 500; obj = NULL; mresult = cfg_map_get(map, "min-table-size", &obj); if (mresult == ISC_R_SUCCESS) { min_entries = cfg_obj_asuint32(obj); if (min_entries < 1) { min_entries = 1; } } obj = NULL; mresult = cfg_map_get(map, "max-table-size", &obj); if (mresult == ISC_R_SUCCESS) { i = cfg_obj_asuint32(obj); CHECK_RRL(i >= min_entries, "max-table-size %d < min-table-size %d", i, min_entries); } CHECK_RRL_RATE(responses_per_second, 0, DNS_RRL_MAX_RATE, "responses-per-second"); CHECK_RRL_RATE(referrals_per_second, responses_per_second, DNS_RRL_MAX_RATE, "referrals-per-second"); CHECK_RRL_RATE(nodata_per_second, responses_per_second, DNS_RRL_MAX_RATE, "nodata-per-second"); CHECK_RRL_RATE(nxdomains_per_second, responses_per_second, DNS_RRL_MAX_RATE, "nxdomains-per-second"); CHECK_RRL_RATE(errors_per_second, responses_per_second, DNS_RRL_MAX_RATE, "errors-per-second"); CHECK_RRL_RATE(all_per_second, 0, DNS_RRL_MAX_RATE, "all-per-second"); CHECK_RRL_RATE(slip, 2, DNS_RRL_MAX_SLIP, "slip"); obj = NULL; mresult = cfg_map_get(map, "window", &obj); if (mresult == ISC_R_SUCCESS) { i = cfg_obj_asuint32(obj); CHECK_RRL(i >= 1 && i <= DNS_RRL_MAX_WINDOW, "window %d < 1 or > %d", i, DNS_RRL_MAX_WINDOW); } obj = NULL; mresult = cfg_map_get(map, "qps-scale", &obj); if (mresult == ISC_R_SUCCESS) { i = cfg_obj_asuint32(obj); CHECK_RRL(i >= 1, "invalid 'qps-scale %d'%s", i, ""); } obj = NULL; mresult = cfg_map_get(map, "ipv4-prefix-length", &obj); if (mresult == ISC_R_SUCCESS) { i = cfg_obj_asuint32(obj); CHECK_RRL(i >= 8 && i <= 32, "invalid 'ipv4-prefix-length %d'%s", i, ""); } obj = NULL; mresult = cfg_map_get(map, "ipv6-prefix-length", &obj); if (mresult == ISC_R_SUCCESS) { i = cfg_obj_asuint32(obj); CHECK_RRL(i >= 16 && i <= DNS_RRL_MAX_PREFIX, "ipv6-prefix-length %d < 16 or > %d", i, DNS_RRL_MAX_PREFIX); } obj = NULL; (void)cfg_map_get(map, "exempt-clients", &obj); if (obj != NULL) { dns_acl_t *acl = NULL; isc_result_t tresult; tresult = cfg_acl_fromconfig(obj, config, logctx, actx, mctx, 0, &acl); if (acl != NULL) { dns_acl_detach(&acl); } if (result == ISC_R_SUCCESS) { result = tresult; } } return (result); } /* * Check allow-recursion and allow-recursion-on acls, and also log a * warning if they're inconsistent with the "recursion" option. */ static isc_result_t check_recursionacls(cfg_aclconfctx_t *actx, const cfg_obj_t *voptions, const char *viewname, const cfg_obj_t *config, isc_log_t *logctx, isc_mem_t *mctx) { const cfg_obj_t *options, *aclobj, *obj = NULL; dns_acl_t *acl = NULL; isc_result_t result = ISC_R_SUCCESS, tresult; bool recursion; const char *forview = " for view "; int i = 0; static const char *acls[] = { "allow-recursion", "allow-recursion-on", NULL }; if (voptions != NULL) { cfg_map_get(voptions, "recursion", &obj); } if (obj == NULL && config != NULL) { options = NULL; cfg_map_get(config, "options", &options); if (options != NULL) { cfg_map_get(options, "recursion", &obj); } } if (obj == NULL) { recursion = true; } else { recursion = cfg_obj_asboolean(obj); } if (viewname == NULL) { viewname = ""; forview = ""; } for (i = 0; acls[i] != NULL; i++) { aclobj = options = NULL; acl = NULL; if (voptions != NULL) { cfg_map_get(voptions, acls[i], &aclobj); } if (config != NULL && aclobj == NULL) { options = NULL; cfg_map_get(config, "options", &options); if (options != NULL) { cfg_map_get(options, acls[i], &aclobj); } } if (aclobj == NULL) { continue; } tresult = cfg_acl_fromconfig(aclobj, config, logctx, actx, mctx, 0, &acl); if (tresult != ISC_R_SUCCESS) { result = tresult; } if (acl == NULL) { continue; } if (!recursion && !dns_acl_isnone(acl)) { cfg_obj_log(aclobj, logctx, ISC_LOG_WARNING, "both \"recursion no;\" and " "\"%s\" active%s%s", acls[i], forview, viewname); } if (acl != NULL) { dns_acl_detach(&acl); } } return (result); } typedef struct { const char *name; unsigned int scale; unsigned int max; } intervaltable; #ifdef HAVE_DNSTAP typedef struct { const char *name; unsigned int min; unsigned int max; } fstrmtable; #endif /* ifdef HAVE_DNSTAP */ typedef enum { optlevel_config, optlevel_options, optlevel_view, optlevel_zone } optlevel_t; static isc_result_t check_name(const char *str) { dns_fixedname_t fixed; dns_fixedname_init(&fixed); return (dns_name_fromstring(dns_fixedname_name(&fixed), str, 0, NULL)); } static bool kasp_name_allowed(const cfg_listelt_t *element) { const char *name = cfg_obj_asstring( cfg_tuple_get(cfg_listelt_value(element), "name")); if (strcmp("none", name) == 0) { return (false); } if (strcmp("default", name) == 0) { return (false); } if (strcmp("insecure", name) == 0) { return (false); } return (true); } static const cfg_obj_t * find_maplist(const cfg_obj_t *config, const char *listname, const char *name) { isc_result_t result; const cfg_obj_t *maplist = NULL; const cfg_listelt_t *elt = NULL; REQUIRE(config != NULL); REQUIRE(name != NULL); result = cfg_map_get(config, listname, &maplist); if (result != ISC_R_SUCCESS) { return (NULL); } for (elt = cfg_list_first(maplist); elt != NULL; elt = cfg_list_next(elt)) { const cfg_obj_t *map = cfg_listelt_value(elt); if (strcasecmp(cfg_obj_asstring(cfg_map_getname(map)), name) == 0) { return (map); } } return (NULL); } static isc_result_t check_listener(const cfg_obj_t *listener, const cfg_obj_t *config, cfg_aclconfctx_t *actx, isc_log_t *logctx, isc_mem_t *mctx) { isc_result_t tresult, result = ISC_R_SUCCESS; const cfg_obj_t *ltup = NULL; const cfg_obj_t *tlsobj = NULL, *httpobj = NULL; const cfg_obj_t *portobj = NULL; const cfg_obj_t *http_server = NULL; bool do_tls = false, no_tls = false; dns_acl_t *acl = NULL; ltup = cfg_tuple_get(listener, "tuple"); RUNTIME_CHECK(ltup != NULL); tlsobj = cfg_tuple_get(ltup, "tls"); if (tlsobj != NULL && cfg_obj_isstring(tlsobj)) { const char *tlsname = cfg_obj_asstring(tlsobj); if (strcasecmp(tlsname, "none") == 0) { no_tls = true; } else if (strcasecmp(tlsname, "ephemeral") == 0) { do_tls = true; } else { const cfg_obj_t *tlsmap = NULL; do_tls = true; tlsmap = find_maplist(config, "tls", tlsname); if (tlsmap == NULL) { cfg_obj_log(tlsobj, logctx, ISC_LOG_ERROR, "tls '%s' is not defined", cfg_obj_asstring(tlsobj)); result = ISC_R_FAILURE; } } } httpobj = cfg_tuple_get(ltup, "http"); if (httpobj != NULL && cfg_obj_isstring(httpobj)) { const char *httpname = cfg_obj_asstring(httpobj); if (!do_tls && !no_tls) { cfg_obj_log(httpobj, logctx, ISC_LOG_ERROR, "http must specify a 'tls' " "statement, 'tls ephemeral', or " "'tls none'"); result = ISC_R_FAILURE; } http_server = find_maplist(config, "http", httpname); if (http_server == NULL && strcasecmp(httpname, "default") != 0) { cfg_obj_log(httpobj, logctx, ISC_LOG_ERROR, "http '%s' is not defined", cfg_obj_asstring(httpobj)); result = ISC_R_FAILURE; } } portobj = cfg_tuple_get(ltup, "port"); if (cfg_obj_isuint32(portobj) && cfg_obj_asuint32(portobj) >= UINT16_MAX) { cfg_obj_log(portobj, logctx, ISC_LOG_ERROR, "port value '%u' is out of range", cfg_obj_asuint32(portobj)); if (result == ISC_R_SUCCESS) { result = ISC_R_RANGE; } } tresult = cfg_acl_fromconfig(cfg_tuple_get(listener, "acl"), config, logctx, actx, mctx, 0, &acl); if (result == ISC_R_SUCCESS) { result = tresult; } if (acl != NULL) { dns_acl_detach(&acl); } return (result); } static isc_result_t check_listeners(const cfg_obj_t *list, const cfg_obj_t *config, cfg_aclconfctx_t *actx, isc_log_t *logctx, isc_mem_t *mctx) { isc_result_t tresult, result = ISC_R_SUCCESS; const cfg_listelt_t *elt = NULL; for (elt = cfg_list_first(list); elt != NULL; elt = cfg_list_next(elt)) { const cfg_obj_t *obj = cfg_listelt_value(elt); tresult = check_listener(obj, config, actx, logctx, mctx); if (result == ISC_R_SUCCESS) { result = tresult; } } return (result); } static isc_result_t check_port(const cfg_obj_t *options, isc_log_t *logctx, const char *type, in_port_t *portp) { const cfg_obj_t *portobj = NULL; isc_result_t result; result = cfg_map_get(options, type, &portobj); if (result != ISC_R_SUCCESS) { return (ISC_R_SUCCESS); } if (cfg_obj_asuint32(portobj) >= UINT16_MAX) { cfg_obj_log(portobj, logctx, ISC_LOG_ERROR, "port '%u' out of range", cfg_obj_asuint32(portobj)); return (ISC_R_RANGE); } if (portp != NULL) { *portp = (in_port_t)cfg_obj_asuint32(portobj); } return (ISC_R_SUCCESS); } static isc_result_t check_options(const cfg_obj_t *options, const cfg_obj_t *config, isc_log_t *logctx, isc_mem_t *mctx, optlevel_t optlevel) { isc_result_t result = ISC_R_SUCCESS; isc_result_t tresult; unsigned int i; const cfg_obj_t *obj = NULL; const cfg_obj_t *resignobj = NULL; const cfg_listelt_t *element; isc_symtab_t *symtab = NULL; const char *str; isc_buffer_t b; uint32_t lifetime = 3600; bool has_dnssecpolicy = false; const char *ccalg = "siphash24"; cfg_aclconfctx_t *actx = NULL; static const char *sources[] = { "query-source", "query-source-v6", }; /* * { "name", scale, value } * (scale * value) <= UINT32_MAX */ static intervaltable intervals[] = { { "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 */ /* minimum and maximum cache and negative cache TTLs */ { "min-cache-ttl", 1, MAX_MIN_CACHE_TTL }, /* 90 secs */ { "max-cache-ttl", 1, UINT32_MAX }, /* no limit */ { "min-ncache-ttl", 1, MAX_MIN_NCACHE_TTL }, /* 90 secs */ { "max-ncache-ttl", 1, MAX_MAX_NCACHE_TTL }, /* 7 days */ }; static const char *server_contact[] = { "empty-server", "empty-contact", "dns64-server", "dns64-contact", NULL }; #ifdef HAVE_DNSTAP static fstrmtable fstrm[] = { { "fstrm-set-buffer-hint", FSTRM_IOTHR_BUFFER_HINT_MIN, FSTRM_IOTHR_BUFFER_HINT_MAX }, { "fstrm-set-flush-timeout", FSTRM_IOTHR_FLUSH_TIMEOUT_MIN, FSTRM_IOTHR_FLUSH_TIMEOUT_MAX }, { "fstrm-set-input-queue-size", FSTRM_IOTHR_INPUT_QUEUE_SIZE_MIN, FSTRM_IOTHR_INPUT_QUEUE_SIZE_MAX }, { "fstrm-set-output-notify-threshold", FSTRM_IOTHR_QUEUE_NOTIFY_THRESHOLD_MIN, 0 }, { "fstrm-set-output-queue-size", FSTRM_IOTHR_OUTPUT_QUEUE_SIZE_MIN, FSTRM_IOTHR_OUTPUT_QUEUE_SIZE_MAX }, { "fstrm-set-reopen-interval", FSTRM_IOTHR_REOPEN_INTERVAL_MIN, FSTRM_IOTHR_REOPEN_INTERVAL_MAX } }; #endif /* ifdef HAVE_DNSTAP */ if (optlevel == optlevel_options) { /* * Check port values, and record "port" for later use. */ tresult = check_port(options, logctx, "port", &dnsport); if (tresult != ISC_R_SUCCESS) { result = tresult; } tresult = check_port(options, logctx, "tls-port", NULL); if (tresult != ISC_R_SUCCESS) { result = tresult; } tresult = check_port(options, logctx, "http-port", NULL); if (tresult != ISC_R_SUCCESS) { result = tresult; } tresult = check_port(options, logctx, "https-port", NULL); if (tresult != ISC_R_SUCCESS) { result = tresult; } } if (optlevel == optlevel_options || optlevel == optlevel_view) { /* * Warn if query-source or query-source-v6 options specify * a port, and fail if they specify the DNS port. */ for (i = 0; i < ARRAY_SIZE(sources); i++) { obj = NULL; (void)cfg_map_get(options, sources[i], &obj); if (obj != NULL) { const isc_sockaddr_t *sa = cfg_obj_assockaddr(obj); in_port_t port = isc_sockaddr_getport(sa); if (port == dnsport) { cfg_obj_log(obj, logctx, ISC_LOG_ERROR, "'%s' cannot specify the " "DNS listener port (%d)", sources[i], port); result = ISC_R_FAILURE; } else if (port != 0) { cfg_obj_log(obj, logctx, ISC_LOG_WARNING, "'%s': specifying a port " "is not recommended", sources[i]); } } } } /* * Check that fields specified in units of time other than seconds * have reasonable values. */ for (i = 0; i < sizeof(intervals) / sizeof(intervals[0]); i++) { uint32_t val; obj = NULL; (void)cfg_map_get(options, intervals[i].name, &obj); if (obj == NULL) { continue; } if (cfg_obj_isduration(obj)) { val = cfg_obj_asduration(obj); } else { 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 > (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; } } /* * Check dnssec-policy. */ obj = NULL; (void)cfg_map_get(options, "dnssec-policy", &obj); if (obj != NULL) { bool bad_kasp = false; bool bad_name = false; if (optlevel != optlevel_config && !cfg_obj_isstring(obj)) { bad_kasp = true; } else if (optlevel == optlevel_config) { dns_kasplist_t list; dns_kasp_t *kasp = NULL, *kasp_next = NULL; ISC_LIST_INIT(list); if (cfg_obj_islist(obj)) { for (element = cfg_list_first(obj); element != NULL; element = cfg_list_next(element)) { isc_result_t ret; cfg_obj_t *kconfig = cfg_listelt_value(element); if (!cfg_obj_istuple(kconfig)) { bad_kasp = true; continue; } if (!kasp_name_allowed(element)) { bad_name = true; continue; } ret = cfg_kasp_fromconfig(kconfig, NULL, mctx, logctx, &list, &kasp); if (ret != ISC_R_SUCCESS) { if (result == ISC_R_SUCCESS) { result = ret; } } if (kasp != NULL) { dns_kasp_detach(&kasp); } } } for (kasp = ISC_LIST_HEAD(list); kasp != NULL; kasp = kasp_next) { kasp_next = ISC_LIST_NEXT(kasp, link); ISC_LIST_UNLINK(list, kasp, link); dns_kasp_detach(&kasp); } } if (bad_kasp) { cfg_obj_log(obj, logctx, ISC_LOG_ERROR, "dnssec-policy may only be configured at " "the top level, please use name reference " "at the zone level"); if (result == ISC_R_SUCCESS) { result = ISC_R_FAILURE; } } else if (bad_name) { cfg_obj_log(obj, logctx, ISC_LOG_ERROR, "dnssec-policy name may not be 'insecure', " "'none', or 'default' (which are built-in " "policies)"); if (result == ISC_R_SUCCESS) { result = ISC_R_FAILURE; } } else { has_dnssecpolicy = true; } } obj = NULL; cfg_map_get(options, "max-rsa-exponent-size", &obj); if (obj != NULL) { uint32_t val; val = cfg_obj_asuint32(obj); if (val != 0 && (val < 35 || val > 4096)) { cfg_obj_log(obj, logctx, ISC_LOG_ERROR, "max-rsa-exponent-size '%u' is out of " "range (35..4096)", val); result = ISC_R_RANGE; } } obj = NULL; cfg_map_get(options, "sig-validity-interval", &obj); if (obj != NULL) { uint32_t validity, resign = 0; validity = cfg_obj_asuint32(cfg_tuple_get(obj, "validity")); resignobj = cfg_tuple_get(obj, "re-sign"); if (!cfg_obj_isvoid(resignobj)) { resign = cfg_obj_asuint32(resignobj); } if (validity > 3660 || validity == 0) { /* 10 years */ cfg_obj_log(obj, logctx, ISC_LOG_ERROR, "%s '%u' is out of range (1..3660)", "sig-validity-interval", validity); result = ISC_R_RANGE; } if (!cfg_obj_isvoid(resignobj)) { if (resign > 3660 || resign == 0) { /* 10 years */ cfg_obj_log(obj, logctx, ISC_LOG_ERROR, "%s '%u' is out of range (1..3660)", "sig-validity-interval (re-sign)", validity); result = ISC_R_RANGE; } else if ((validity > 7 && validity < resign) || (validity <= 7 && validity * 24 < resign)) { cfg_obj_log(obj, logctx, ISC_LOG_ERROR, "validity interval (%u days) " "less than re-signing interval " "(%u %s)", validity, resign, (validity > 7) ? "days" : "hours"); result = ISC_R_RANGE; } } if (has_dnssecpolicy) { cfg_obj_log(obj, logctx, ISC_LOG_ERROR, "sig-validity-interval: cannot be " "configured if dnssec-policy is also set"); result = ISC_R_FAILURE; } } obj = NULL; cfg_map_get(options, "dnskey-sig-validity", &obj); if (obj != NULL) { uint32_t keyvalidity; keyvalidity = cfg_obj_asuint32(obj); if (keyvalidity > 3660) { /* 10 years */ cfg_obj_log(obj, logctx, ISC_LOG_ERROR, "%s '%u' is out of range (0..3660)", "dnskey-sig-validity", keyvalidity); result = ISC_R_RANGE; } if (has_dnssecpolicy) { cfg_obj_log(obj, logctx, ISC_LOG_ERROR, "dnskey-sig-validity: cannot be " "configured if dnssec-policy is also set"); result = ISC_R_FAILURE; } } obj = NULL; (void)cfg_map_get(options, "preferred-glue", &obj); if (obj != NULL) { 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)) { for (element = cfg_list_first(obj); element != NULL; element = cfg_list_next(element)) { const cfg_obj_t *exclude; exclude = cfg_listelt_value(element); str = cfg_obj_asstring(exclude); tresult = check_name(str); 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; } } } /* * Set supported DS digest types. */ obj = NULL; (void)cfg_map_get(options, "disable-ds-digests", &obj); if (obj != NULL) { for (element = cfg_list_first(obj); element != NULL; element = cfg_list_next(element)) { obj = cfg_listelt_value(element); tresult = disabled_ds_digests(obj, logctx); if (tresult != ISC_R_SUCCESS) { result = tresult; } } } /* * Check auto-dnssec at the view/options level */ obj = NULL; (void)cfg_map_get(options, "auto-dnssec", &obj); if (obj != NULL) { const char *arg = cfg_obj_asstring(obj); if (optlevel != optlevel_zone && strcasecmp(arg, "off") != 0) { cfg_obj_log(obj, logctx, ISC_LOG_ERROR, "auto-dnssec may only be activated at the " "zone level"); if (result == ISC_R_SUCCESS) { result = ISC_R_FAILURE; } } } /* * Check dnssec-must-be-secure. */ obj = NULL; (void)cfg_map_get(options, "dnssec-must-be-secure", &obj); if (obj != NULL) { tresult = isc_symtab_create(mctx, 100, freekey, mctx, false, &symtab); if (tresult != ISC_R_SUCCESS) { result = tresult; } else { 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 (result == ISC_R_SUCCESS && tresult != ISC_R_SUCCESS) { result = tresult; } } } if (symtab != NULL) { isc_symtab_destroy(&symtab); } } /* * Check server/contacts for syntactic validity. */ for (i = 0; server_contact[i] != NULL; i++) { obj = NULL; (void)cfg_map_get(options, server_contact[i], &obj); if (obj != NULL) { str = cfg_obj_asstring(obj); if (check_name(str) != ISC_R_SUCCESS) { cfg_obj_log(obj, logctx, ISC_LOG_ERROR, "%s: invalid name '%s'", server_contact[i], str); if (result == ISC_R_SUCCESS) { result = ISC_R_FAILURE; } } } } /* * Check empty zone configuration. */ 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); if (check_name(str) != ISC_R_SUCCESS) { cfg_obj_log(obj, logctx, ISC_LOG_ERROR, "disable-empty-zone: invalid name '%s'", str); if (result == ISC_R_SUCCESS) { result = ISC_R_FAILURE; } } } /* * Check that server-id is not too long. * 1024 bytes should be big enough. */ obj = NULL; (void)cfg_map_get(options, "server-id", &obj); if (obj != NULL && cfg_obj_isstring(obj) && strlen(cfg_obj_asstring(obj)) > 1024U) { cfg_obj_log(obj, logctx, ISC_LOG_ERROR, "'server-id' too big (>1024 bytes)"); if (result == ISC_R_SUCCESS) { result = ISC_R_FAILURE; } } obj = NULL; (void)cfg_map_get(options, "nta-lifetime", &obj); if (obj != NULL) { lifetime = cfg_obj_asduration(obj); if (lifetime > 604800) { /* 7 days */ cfg_obj_log(obj, logctx, ISC_LOG_ERROR, "'nta-lifetime' cannot exceed one week"); if (result == ISC_R_SUCCESS) { result = ISC_R_RANGE; } } else if (lifetime == 0) { cfg_obj_log(obj, logctx, ISC_LOG_ERROR, "'nta-lifetime' may not be zero"); if (result == ISC_R_SUCCESS) { result = ISC_R_RANGE; } } } obj = NULL; (void)cfg_map_get(options, "nta-recheck", &obj); if (obj != NULL) { uint32_t recheck = cfg_obj_asduration(obj); if (recheck > 604800) { /* 7 days */ cfg_obj_log(obj, logctx, ISC_LOG_ERROR, "'nta-recheck' cannot exceed one week"); if (result == ISC_R_SUCCESS) { result = ISC_R_RANGE; } } if (recheck > lifetime) { cfg_obj_log(obj, logctx, ISC_LOG_WARNING, "'nta-recheck' (%d seconds) is " "greater than 'nta-lifetime' " "(%d seconds)", recheck, lifetime); } } obj = NULL; (void)cfg_map_get(options, "cookie-algorithm", &obj); if (obj != NULL) { ccalg = cfg_obj_asstring(obj); if (strcasecmp(ccalg, "aes") == 0) { cfg_obj_log(obj, logctx, ISC_LOG_WARNING, "cookie-algorithm 'aes' is deprecated"); } } obj = NULL; (void)cfg_map_get(options, "cookie-secret", &obj); if (obj != NULL) { unsigned char secret[32]; for (element = cfg_list_first(obj); element != NULL; element = cfg_list_next(element)) { unsigned int usedlength; obj = cfg_listelt_value(element); str = cfg_obj_asstring(obj); memset(secret, 0, sizeof(secret)); isc_buffer_init(&b, secret, sizeof(secret)); tresult = isc_hex_decodestring(str, &b); if (tresult == ISC_R_NOSPACE) { cfg_obj_log(obj, logctx, ISC_LOG_ERROR, "cookie-secret: too long"); } else if (tresult != ISC_R_SUCCESS) { cfg_obj_log(obj, logctx, ISC_LOG_ERROR, "cookie-secret: invalid hex " "string"); } if (tresult != ISC_R_SUCCESS) { if (result == ISC_R_SUCCESS) { result = tresult; } continue; } usedlength = isc_buffer_usedlength(&b); if (strcasecmp(ccalg, "aes") == 0 && usedlength != ISC_AES128_KEYLENGTH) { cfg_obj_log(obj, logctx, ISC_LOG_ERROR, "AES cookie-secret must be 128 " "bits"); if (result == ISC_R_SUCCESS) { result = ISC_R_RANGE; } } if (strcasecmp(ccalg, "siphash24") == 0 && usedlength != ISC_SIPHASH24_KEY_LENGTH) { cfg_obj_log(obj, logctx, ISC_LOG_ERROR, "SipHash-2-4 cookie-secret must be " "128 bits"); if (result == ISC_R_SUCCESS) { result = ISC_R_RANGE; } } } } #ifdef HAVE_DNSTAP for (i = 0; i < sizeof(fstrm) / sizeof(fstrm[0]); i++) { uint32_t value; obj = NULL; (void)cfg_map_get(options, fstrm[i].name, &obj); if (obj == NULL) { continue; } if (cfg_obj_isduration(obj)) { value = cfg_obj_asduration(obj); } else { value = cfg_obj_asuint32(obj); } if (value < fstrm[i].min || (fstrm[i].max != 0U && value > fstrm[i].max)) { if (fstrm[i].max != 0U) { cfg_obj_log(obj, logctx, ISC_LOG_ERROR, "%s '%u' out of range (%u..%u)", fstrm[i].name, value, fstrm[i].min, fstrm[i].max); } else { cfg_obj_log(obj, logctx, ISC_LOG_ERROR, "%s out of range (%u < %u)", fstrm[i].name, value, fstrm[i].min); } if (result == ISC_R_SUCCESS) { result = ISC_R_RANGE; } } if (strcmp(fstrm[i].name, "fstrm-set-input-queue-size") == 0) { int bits = 0; do { bits += value & 0x1; value >>= 1; } while (value != 0U); if (bits != 1) { cfg_obj_log(obj, logctx, ISC_LOG_ERROR, "%s '%u' not a power-of-2", fstrm[i].name, cfg_obj_asuint32(obj)); if (result == ISC_R_SUCCESS) { result = ISC_R_RANGE; } } } } /* Check that dnstap-ouput values are consistent */ obj = NULL; (void)cfg_map_get(options, "dnstap-output", &obj); if (obj != NULL) { const cfg_obj_t *obj2; dns_dtmode_t dmode; obj2 = cfg_tuple_get(obj, "mode"); if (obj2 == NULL) { cfg_obj_log(obj, logctx, ISC_LOG_ERROR, "dnstap-output mode not found"); if (result == ISC_R_SUCCESS) { result = ISC_R_FAILURE; } } else { if (strcasecmp(cfg_obj_asstring(obj2), "file") == 0) { dmode = dns_dtmode_file; } else { dmode = dns_dtmode_unix; } obj2 = cfg_tuple_get(obj, "size"); if (obj2 != NULL && !cfg_obj_isvoid(obj2) && dmode == dns_dtmode_unix) { cfg_obj_log(obj, logctx, ISC_LOG_ERROR, "dnstap-output size " "cannot be set with mode unix"); if (result == ISC_R_SUCCESS) { result = ISC_R_FAILURE; } } obj2 = cfg_tuple_get(obj, "versions"); if (obj2 != NULL && !cfg_obj_isvoid(obj2) && dmode == dns_dtmode_unix) { cfg_obj_log(obj, logctx, ISC_LOG_ERROR, "dnstap-output versions " "cannot be set with mode unix"); if (result == ISC_R_SUCCESS) { result = ISC_R_FAILURE; } } obj2 = cfg_tuple_get(obj, "suffix"); if (obj2 != NULL && !cfg_obj_isvoid(obj2) && dmode == dns_dtmode_unix) { cfg_obj_log(obj, logctx, ISC_LOG_ERROR, "dnstap-output suffix " "cannot be set with mode unix"); if (result == ISC_R_SUCCESS) { result = ISC_R_FAILURE; } } } } #endif /* ifdef HAVE_DNSTAP */ obj = NULL; (void)cfg_map_get(options, "lmdb-mapsize", &obj); if (obj != NULL) { uint64_t mapsize = cfg_obj_asuint64(obj); if (mapsize < (1ULL << 20)) { /* 1 megabyte */ cfg_obj_log(obj, logctx, ISC_LOG_ERROR, "'lmdb-mapsize " "%" PRId64 "' " "is too small", mapsize); if (result == ISC_R_SUCCESS) { result = ISC_R_RANGE; } } else if (mapsize > (1ULL << 40)) { /* 1 terabyte */ cfg_obj_log(obj, logctx, ISC_LOG_ERROR, "'lmdb-mapsize " "%" PRId64 "' " "is too large", mapsize); if (result == ISC_R_SUCCESS) { result = ISC_R_RANGE; } } } obj = NULL; (void)cfg_map_get(options, "resolver-nonbackoff-tries", &obj); if (obj != NULL && cfg_obj_asuint32(obj) == 0U) { cfg_obj_log(obj, logctx, ISC_LOG_ERROR, "'resolver-nonbackoff-tries' must be >= 1"); if (result == ISC_R_SUCCESS) { result = ISC_R_RANGE; } } obj = NULL; (void)cfg_map_get(options, "max-ixfr-ratio", &obj); if (obj != NULL && cfg_obj_ispercentage(obj)) { uint32_t percent = cfg_obj_aspercentage(obj); if (percent == 0) { cfg_obj_log(obj, logctx, ISC_LOG_ERROR, "'ixfr-max-ratio' must be a nonzero " "percentage or 'unlimited')"); if (result == ISC_R_SUCCESS) { result = ISC_R_RANGE; } } else if (percent > 100) { cfg_obj_log(obj, logctx, ISC_LOG_WARNING, "'ixfr-max-ratio %d%%' exceeds 100%%", percent); } } obj = NULL; (void)cfg_map_get(options, "check-names", &obj); if (obj != NULL && !cfg_obj_islist(obj)) { obj = NULL; } if (obj != NULL) { /* Note: SEC is defined in on some platforms. */ enum { MAS = 1, PRI = 2, SLA = 4, SCN = 8 } values = 0; for (const cfg_listelt_t *el = cfg_list_first(obj); el != NULL; el = cfg_list_next(el)) { const cfg_obj_t *tuple = cfg_listelt_value(el); const cfg_obj_t *type = cfg_tuple_get(tuple, "type"); const char *keyword = cfg_obj_asstring(type); if (strcasecmp(keyword, "primary") == 0) { if ((values & PRI) == PRI) { cfg_obj_log(obj, logctx, ISC_LOG_ERROR, "'check-names primary' " "duplicated"); if (result == ISC_R_SUCCESS) { result = ISC_R_FAILURE; } } values |= PRI; } else if (strcasecmp(keyword, "master") == 0) { if ((values & MAS) == MAS) { cfg_obj_log(obj, logctx, ISC_LOG_ERROR, "'check-names master' " "duplicated"); if (result == ISC_R_SUCCESS) { result = ISC_R_FAILURE; } } values |= MAS; } else if (strcasecmp(keyword, "secondary") == 0) { if ((values & SCN) == SCN) { cfg_obj_log(obj, logctx, ISC_LOG_ERROR, "'check-names secondary' " "duplicated"); if (result == ISC_R_SUCCESS) { result = ISC_R_FAILURE; } } values |= SCN; } else if (strcasecmp(keyword, "slave") == 0) { if ((values & SLA) == SLA) { cfg_obj_log(obj, logctx, ISC_LOG_ERROR, "'check-names slave' " "duplicated"); if (result == ISC_R_SUCCESS) { result = ISC_R_FAILURE; } } values |= SLA; } } if ((values & (PRI | MAS)) == (PRI | MAS)) { cfg_obj_log(obj, logctx, ISC_LOG_ERROR, "'check-names' cannot take both " "'primary' and 'master'"); if (result == ISC_R_SUCCESS) { result = ISC_R_FAILURE; } } if ((values & (SCN | SLA)) == (SCN | SLA)) { cfg_obj_log(obj, logctx, ISC_LOG_ERROR, "'check-names' cannot take both " "'secondary' and 'slave'"); if (result == ISC_R_SUCCESS) { result = ISC_R_FAILURE; } } } obj = NULL; (void)cfg_map_get(options, "stale-refresh-time", &obj); if (obj != NULL) { uint32_t refresh_time = cfg_obj_asduration(obj); if (refresh_time > 0 && refresh_time < 30) { cfg_obj_log(obj, logctx, ISC_LOG_WARNING, "'stale-refresh-time' should either be 0 " "or otherwise 30 seconds or higher"); } } cfg_aclconfctx_create(mctx, &actx); obj = NULL; (void)cfg_map_get(options, "listen-on", &obj); if (obj != NULL) { INSIST(config != NULL); tresult = check_listeners(obj, config, actx, logctx, mctx); if (result == ISC_R_SUCCESS) { result = tresult; } } obj = NULL; (void)cfg_map_get(options, "listen-on-v6", &obj); if (obj != NULL) { INSIST(config != NULL); tresult = check_listeners(obj, config, actx, logctx, mctx); if (result == ISC_R_SUCCESS) { result = tresult; } } if (actx != NULL) { cfg_aclconfctx_detach(&actx); } return (result); } /* * Check "remote-servers" style list. */ static isc_result_t bind9_check_remoteserverlist(const cfg_obj_t *cctx, const char *list, isc_log_t *logctx, isc_symtab_t *symtab, isc_mem_t *mctx) { isc_symvalue_t symvalue; isc_result_t result, tresult; const cfg_obj_t *obj = NULL; const cfg_listelt_t *elt; result = cfg_map_get(cctx, list, &obj); if (result != ISC_R_SUCCESS) { return (ISC_R_SUCCESS); } elt = cfg_list_first(obj); while (elt != NULL) { char *tmp; const char *name; obj = cfg_listelt_value(elt); name = cfg_obj_asstring(cfg_tuple_get(obj, "name")); tmp = isc_mem_strdup(mctx, name); symvalue.as_cpointer = obj; tresult = isc_symtab_define(symtab, tmp, 1, symvalue, isc_symexists_reject); if (tresult == ISC_R_EXISTS) { const char *file = NULL; unsigned int line; RUNTIME_CHECK( isc_symtab_lookup(symtab, tmp, 1, &symvalue) == ISC_R_SUCCESS); file = cfg_obj_file(symvalue.as_cpointer); line = cfg_obj_line(symvalue.as_cpointer); if (file == NULL) { file = ""; } cfg_obj_log(obj, logctx, ISC_LOG_ERROR, "%s list '%s' is duplicated: " "also defined at %s:%u", list, name, file, line); isc_mem_free(mctx, tmp); result = tresult; break; } else if (tresult != ISC_R_SUCCESS) { isc_mem_free(mctx, tmp); result = tresult; break; } elt = cfg_list_next(elt); } return (result); } /* * Check primaries lists for duplicates. */ static isc_result_t bind9_check_primarylists(const cfg_obj_t *cctx, isc_log_t *logctx, isc_mem_t *mctx) { isc_result_t result, tresult; isc_symtab_t *symtab = NULL; result = isc_symtab_create(mctx, 100, freekey, mctx, false, &symtab); if (result != ISC_R_SUCCESS) { return (result); } tresult = bind9_check_remoteserverlist(cctx, "primaries", logctx, symtab, mctx); if (tresult != ISC_R_SUCCESS) { result = tresult; } tresult = bind9_check_remoteserverlist(cctx, "masters", logctx, symtab, mctx); if (tresult != ISC_R_SUCCESS) { result = tresult; } isc_symtab_destroy(&symtab); return (result); } /* * Check parental-agents lists for duplicates. */ static isc_result_t bind9_check_parentalagentlists(const cfg_obj_t *cctx, isc_log_t *logctx, isc_mem_t *mctx) { isc_result_t result, tresult; isc_symtab_t *symtab = NULL; result = isc_symtab_create(mctx, 100, freekey, mctx, false, &symtab); if (result != ISC_R_SUCCESS) { return (result); } tresult = bind9_check_remoteserverlist(cctx, "parental-agents", logctx, symtab, mctx); if (tresult != ISC_R_SUCCESS) { result = tresult; } isc_symtab_destroy(&symtab); return (result); } #if HAVE_LIBNGHTTP2 static isc_result_t bind9_check_httpserver(const cfg_obj_t *http, isc_log_t *logctx, isc_symtab_t *symtab) { isc_result_t result, tresult; const char *name = cfg_obj_asstring(cfg_map_getname(http)); const cfg_obj_t *eps = NULL; const cfg_listelt_t *elt = NULL; isc_symvalue_t symvalue; if (strcasecmp(name, "default") == 0) { cfg_obj_log(http, logctx, ISC_LOG_ERROR, "'http' name cannot be '%s' (which is a " "built-in configuration)", name); result = ISC_R_FAILURE; } else { /* Check for duplicates */ symvalue.as_cpointer = http; result = isc_symtab_define(symtab, name, 1, symvalue, isc_symexists_reject); if (result == ISC_R_EXISTS) { const char *file = NULL; unsigned int line; tresult = isc_symtab_lookup(symtab, name, 1, &symvalue); RUNTIME_CHECK(tresult == ISC_R_SUCCESS); line = cfg_obj_line(symvalue.as_cpointer); file = cfg_obj_file(symvalue.as_cpointer); if (file == NULL) { file = ""; } cfg_obj_log(http, logctx, ISC_LOG_ERROR, "http '%s' is duplicated: " "also defined at %s:%u", name, file, line); } } /* Check endpoints are valid */ tresult = cfg_map_get(http, "endpoints", &eps); if (tresult == ISC_R_SUCCESS) { for (elt = cfg_list_first(eps); elt != NULL; elt = cfg_list_next(elt)) { const cfg_obj_t *ep = cfg_listelt_value(elt); const char *path = cfg_obj_asstring(ep); if (!isc_nm_http_path_isvalid(path)) { cfg_obj_log(eps, logctx, ISC_LOG_ERROR, "endpoint '%s' is not a " "valid absolute HTTP path", path); if (result == ISC_R_SUCCESS) { result = ISC_R_FAILURE; } } } } return (result); } static isc_result_t bind9_check_httpservers(const cfg_obj_t *config, isc_log_t *logctx, isc_mem_t *mctx) { isc_result_t result, tresult; const cfg_obj_t *obj = NULL; const cfg_listelt_t *elt = NULL; isc_symtab_t *symtab = NULL; result = isc_symtab_create(mctx, 100, NULL, NULL, false, &symtab); if (result != ISC_R_SUCCESS) { return (result); } result = cfg_map_get(config, "http", &obj); if (result != ISC_R_SUCCESS) { result = ISC_R_SUCCESS; goto done; } for (elt = cfg_list_first(obj); elt != NULL; elt = cfg_list_next(elt)) { obj = cfg_listelt_value(elt); tresult = bind9_check_httpserver(obj, logctx, symtab); if (result == ISC_R_SUCCESS) { result = tresult; } } done: isc_symtab_destroy(&symtab); return (result); } #endif /* HAVE_LIBNGHTTP2 */ static isc_result_t bind9_check_tls_defintion(const cfg_obj_t *tlsobj, const char *name, isc_log_t *logctx, isc_symtab_t *symtab) { isc_result_t result, tresult; const cfg_obj_t *tls_proto_list = NULL, *tls_key = NULL, *tls_cert = NULL, *tls_ciphers = NULL; uint32_t tls_protos = 0; isc_symvalue_t symvalue; if (strcasecmp(name, "ephemeral") == 0 || strcasecmp(name, "none") == 0) { cfg_obj_log(tlsobj, logctx, ISC_LOG_ERROR, "tls clause name '%s' is reserved for internal use", name); result = ISC_R_FAILURE; } else { /* Check for duplicates */ symvalue.as_cpointer = tlsobj; result = isc_symtab_define(symtab, name, 1, symvalue, isc_symexists_reject); if (result == ISC_R_EXISTS) { const char *file = NULL; unsigned int line; tresult = isc_symtab_lookup(symtab, name, 1, &symvalue); RUNTIME_CHECK(tresult == ISC_R_SUCCESS); line = cfg_obj_line(symvalue.as_cpointer); file = cfg_obj_file(symvalue.as_cpointer); if (file == NULL) { file = ""; } cfg_obj_log(tlsobj, logctx, ISC_LOG_ERROR, "tls clause '%s' is duplicated: " "also defined at %s:%u", name, file, line); } } (void)cfg_map_get(tlsobj, "key-file", &tls_key); (void)cfg_map_get(tlsobj, "cert-file", &tls_cert); if ((tls_key == NULL && tls_cert != NULL) || (tls_cert == NULL && tls_key != NULL)) { cfg_obj_log(tlsobj, logctx, ISC_LOG_ERROR, "tls '%s': 'cert-file' and 'key-file' must " "both be specified, or both omitted", name); result = ISC_R_FAILURE; } /* Check protocols are valid */ tresult = cfg_map_get(tlsobj, "protocols", &tls_proto_list); if (tresult == ISC_R_SUCCESS) { const cfg_listelt_t *proto = NULL; INSIST(tls_proto_list != NULL); for (proto = cfg_list_first(tls_proto_list); proto != 0; proto = cfg_list_next(proto)) { const cfg_obj_t *tls_proto_obj = cfg_listelt_value(proto); const char *tls_sver = cfg_obj_asstring(tls_proto_obj); const isc_tls_protocol_version_t ver = isc_tls_protocol_name_to_version(tls_sver); if (ver == ISC_TLS_PROTO_VER_UNDEFINED) { cfg_obj_log(tls_proto_obj, logctx, ISC_LOG_ERROR, "'%s' is not a valid " "TLS protocol version", tls_sver); result = ISC_R_FAILURE; continue; } else if (!isc_tls_protocol_supported(ver)) { cfg_obj_log(tls_proto_obj, logctx, ISC_LOG_ERROR, "'%s' is not " "supported by the " "cryptographic library version in " "use (%s)", tls_sver, OPENSSL_VERSION_TEXT); result = ISC_R_FAILURE; } if ((tls_protos & ver) != 0) { cfg_obj_log(tls_proto_obj, logctx, ISC_LOG_WARNING, "'%s' is specified more than once " "in '%s'", tls_sver, name); result = ISC_R_FAILURE; } tls_protos |= ver; } if (tls_protos == 0) { cfg_obj_log(tlsobj, logctx, ISC_LOG_ERROR, "tls '%s' does not contain any valid " "TLS protocol versions definitions", name); result = ISC_R_FAILURE; } } /* Check cipher list string is valid */ tresult = cfg_map_get(tlsobj, "ciphers", &tls_ciphers); if (tresult == ISC_R_SUCCESS) { const char *ciphers = cfg_obj_asstring(tls_ciphers); if (!isc_tls_cipherlist_valid(ciphers)) { cfg_obj_log(tls_ciphers, logctx, ISC_LOG_ERROR, "'ciphers' in the 'tls' clause '%s' is " "not a " "valid cipher list string", name); result = ISC_R_FAILURE; } } return (result); } static isc_result_t bind9_check_tls_definitions(const cfg_obj_t *config, isc_log_t *logctx, isc_mem_t *mctx) { isc_result_t result, tresult; const cfg_obj_t *obj = NULL; const cfg_listelt_t *elt = NULL; isc_symtab_t *symtab = NULL; result = cfg_map_get(config, "tls", &obj); if (result != ISC_R_SUCCESS) { result = ISC_R_SUCCESS; return (result); } result = isc_symtab_create(mctx, 100, NULL, NULL, false, &symtab); if (result != ISC_R_SUCCESS) { return (result); } for (elt = cfg_list_first(obj); elt != NULL; elt = cfg_list_next(elt)) { const char *name; obj = cfg_listelt_value(elt); name = cfg_obj_asstring(cfg_map_getname(obj)); tresult = bind9_check_tls_defintion(obj, name, logctx, symtab); if (result == ISC_R_SUCCESS) { result = tresult; } } isc_symtab_destroy(&symtab); return (result); } static isc_result_t get_remotes(const cfg_obj_t *cctx, const char *list, const char *name, const cfg_obj_t **ret) { isc_result_t result; const cfg_obj_t *obj = NULL; const cfg_listelt_t *elt = NULL; result = cfg_map_get(cctx, list, &obj); if (result != ISC_R_SUCCESS) { return (result); } elt = cfg_list_first(obj); while (elt != NULL) { const char *listname; obj = cfg_listelt_value(elt); listname = cfg_obj_asstring(cfg_tuple_get(obj, "name")); if (strcasecmp(listname, name) == 0) { *ret = obj; return (ISC_R_SUCCESS); } elt = cfg_list_next(elt); } return (ISC_R_NOTFOUND); } static isc_result_t get_remoteservers_def(const char *list, const char *name, const cfg_obj_t *cctx, const cfg_obj_t **ret) { isc_result_t result = ISC_R_NOTFOUND; if (strcmp(list, "primaries") == 0) { result = get_remotes(cctx, "primaries", name, ret); if (result != ISC_R_SUCCESS) { result = get_remotes(cctx, "masters", name, ret); } } else if (strcmp(list, "parental-agents") == 0) { result = get_remotes(cctx, "parental-agents", name, ret); } return (result); } static isc_result_t validate_remotes(const char *list, const cfg_obj_t *obj, const cfg_obj_t *config, uint32_t *countp, isc_log_t *logctx, isc_mem_t *mctx) { isc_result_t result = ISC_R_SUCCESS; isc_result_t tresult; uint32_t count = 0; isc_symtab_t *symtab = NULL; isc_symvalue_t symvalue; const cfg_listelt_t *element; const cfg_listelt_t **stack = NULL; uint32_t stackcount = 0, pushed = 0; const cfg_obj_t *listobj; REQUIRE(countp != NULL); result = isc_symtab_create(mctx, 100, NULL, NULL, false, &symtab); if (result != ISC_R_SUCCESS) { *countp = count; return (result); } newlist: listobj = cfg_tuple_get(obj, "addresses"); element = cfg_list_first(listobj); resume: for (; element != NULL; element = cfg_list_next(element)) { const char *listname; const cfg_obj_t *addr; const cfg_obj_t *key; const cfg_obj_t *tls; addr = cfg_tuple_get(cfg_listelt_value(element), "remoteselement"); key = cfg_tuple_get(cfg_listelt_value(element), "key"); tls = cfg_tuple_get(cfg_listelt_value(element), "tls"); if (cfg_obj_issockaddr(addr)) { count++; if (cfg_obj_isstring(key)) { const char *str = cfg_obj_asstring(key); dns_fixedname_t fname; dns_name_t *nm = dns_fixedname_initname(&fname); tresult = dns_name_fromstring(nm, str, 0, NULL); if (tresult != ISC_R_SUCCESS) { cfg_obj_log(key, logctx, ISC_LOG_ERROR, "'%s' is not a valid name", str); if (result == ISC_R_SUCCESS) { result = tresult; } } } if (cfg_obj_isstring(tls)) { const char *str = cfg_obj_asstring(tls); dns_fixedname_t fname; dns_name_t *nm = dns_fixedname_initname(&fname); tresult = dns_name_fromstring(nm, str, 0, NULL); if (tresult != ISC_R_SUCCESS) { cfg_obj_log(tls, logctx, ISC_LOG_ERROR, "'%s' is not a valid name", str); if (result == ISC_R_SUCCESS) { result = tresult; } } if (strcasecmp(str, "ephemeral") != 0) { const cfg_obj_t *tlsmap = NULL; tlsmap = find_maplist(config, "tls", str); if (tlsmap == NULL) { cfg_obj_log( tls, logctx, ISC_LOG_ERROR, "tls '%s' is not " "defined", cfg_obj_asstring(tls)); result = ISC_R_FAILURE; } } } 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; } } if (!cfg_obj_isvoid(tls)) { cfg_obj_log(key, logctx, ISC_LOG_ERROR, "unexpected token '%s'", cfg_obj_asstring(tls)); 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_remoteservers_def(list, listname, config, &obj); if (tresult != ISC_R_SUCCESS) { if (result == ISC_R_SUCCESS) { result = tresult; } cfg_obj_log(addr, logctx, ISC_LOG_ERROR, "unable to find %s list '%s'", list, listname); continue; } /* Grow stack? */ if (stackcount == pushed) { void *newstack; uint32_t newlen = stackcount + 16; size_t newsize, oldsize; newsize = newlen * sizeof(*stack); oldsize = stackcount * sizeof(*stack); newstack = isc_mem_get(mctx, newsize); if (stackcount != 0) { void *ptr; DE_CONST(stack, ptr); memmove(newstack, stack, oldsize); isc_mem_put(mctx, ptr, oldsize); } stack = newstack; stackcount = newlen; } stack[pushed++] = cfg_list_next(element); goto newlist; } if (pushed != 0) { element = stack[--pushed]; goto resume; } if (stack != NULL) { void *ptr; DE_CONST(stack, ptr); isc_mem_put(mctx, ptr, 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_id, fixed_name; dns_name_t *id, *name; const char *str; isc_textregion_t r; dns_rdatatype_t type; /* Check for "update-policy local;" */ if (cfg_obj_isstring(policy) && strcmp("local", cfg_obj_asstring(policy)) == 0) { return (ISC_R_SUCCESS); } /* Now check the grant policy */ 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_ssumatchtype_t mtype; id = dns_fixedname_initname(&fixed_id); name = dns_fixedname_initname(&fixed_name); tresult = dns_ssu_mtypefromstring(cfg_obj_asstring(matchtype), &mtype); if (tresult != ISC_R_SUCCESS) { cfg_obj_log(identity, logctx, ISC_LOG_ERROR, "has a bad match-type"); } str = cfg_obj_asstring(identity); tresult = dns_name_fromstring(id, str, 1, NULL); if (tresult != ISC_R_SUCCESS) { cfg_obj_log(identity, logctx, ISC_LOG_ERROR, "'%s' is not a valid name", str); result = tresult; } /* * There is no name field for subzone and dname is void */ if (mtype == dns_ssumatchtype_subdomain && cfg_obj_isvoid(dname)) { str = "."; /* Use "." as a replacement. */ } else { str = cfg_obj_asstring(dname); } if (tresult == ISC_R_SUCCESS) { tresult = dns_name_fromstring(name, str, 0, 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 && mtype == dns_ssumatchtype_wildcard && !dns_name_iswildcard(name)) { cfg_obj_log(identity, logctx, ISC_LOG_ERROR, "'%s' is not a wildcard", str); result = ISC_R_FAILURE; } /* * For some match types, the name should be a placeholder * value, either "." or the same as identity. */ switch (mtype) { case dns_ssumatchtype_self: case dns_ssumatchtype_selfsub: case dns_ssumatchtype_selfwild: if (tresult == ISC_R_SUCCESS && (!dns_name_equal(id, name) && !dns_name_equal(dns_rootname, name))) { cfg_obj_log(identity, logctx, ISC_LOG_ERROR, "identity and name fields are not " "the same"); result = ISC_R_FAILURE; } break; case dns_ssumatchtype_selfkrb5: case dns_ssumatchtype_selfms: case dns_ssumatchtype_selfsubkrb5: case dns_ssumatchtype_selfsubms: case dns_ssumatchtype_tcpself: case dns_ssumatchtype_6to4self: if (tresult == ISC_R_SUCCESS && !dns_name_equal(dns_rootname, name)) { cfg_obj_log(identity, logctx, ISC_LOG_ERROR, "name field not set to " "placeholder value '.'"); result = ISC_R_FAILURE; } break; case dns_ssumatchtype_name: case dns_ssumatchtype_subdomain: /* also zonesub */ case dns_ssumatchtype_subdomainms: case dns_ssumatchtype_subdomainselfmsrhs: case dns_ssumatchtype_subdomainkrb5: case dns_ssumatchtype_subdomainselfkrb5rhs: case dns_ssumatchtype_wildcard: case dns_ssumatchtype_external: case dns_ssumatchtype_local: if (tresult == ISC_R_SUCCESS) { DE_CONST(str, r.base); r.length = strlen(str); tresult = dns_rdatatype_fromtext(&type, &r); } if (tresult == ISC_R_SUCCESS) { cfg_obj_log(identity, logctx, ISC_LOG_ERROR, "missing name field type '%s' " "found", str); result = ISC_R_FAILURE; break; } break; default: UNREACHABLE(); } for (element2 = cfg_list_first(typelist); element2 != NULL; element2 = cfg_list_next(element2)) { const cfg_obj_t *typeobj; const char *bracket; typeobj = cfg_listelt_value(element2); DE_CONST(cfg_obj_asstring(typeobj), r.base); bracket = strchr(r.base, '(' /*)*/); if (bracket != NULL) { char *end = NULL; unsigned long max; r.length = bracket - r.base; max = strtoul(bracket + 1, &end, 10); if (max > 0xffff || end[0] != /*(*/ ')' || end[1] != 0) { cfg_obj_log(typeobj, logctx, ISC_LOG_ERROR, "'%s' is not a valid count", bracket); result = DNS_R_SYNTAX; } } else { 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", (int)r.length, r.base); result = tresult; } } } return (result); } typedef struct { const char *name; unsigned int allowed; } optionstable; static isc_result_t check_nonzero(const cfg_obj_t *options, isc_log_t *logctx) { isc_result_t result = ISC_R_SUCCESS; const cfg_obj_t *obj = NULL; unsigned int i; static const char *nonzero[] = { "max-retry-time", "min-retry-time", "max-refresh-time", "min-refresh-time" }; /* * Check if value is zero. */ for (i = 0; i < sizeof(nonzero) / sizeof(nonzero[0]); i++) { obj = NULL; if (cfg_map_get(options, nonzero[i], &obj) == ISC_R_SUCCESS && cfg_obj_asuint32(obj) == 0) { cfg_obj_log(obj, logctx, ISC_LOG_ERROR, "'%s' must not be zero", nonzero[i]); result = ISC_R_FAILURE; } } return (result); } /*% * Check whether NOTIFY configuration at the zone level is acceptable for a * mirror zone. Return true if it is; return false otherwise. */ static bool check_mirror_zone_notify(const cfg_obj_t *zoptions, const char *znamestr, isc_log_t *logctx) { bool notify_configuration_ok = true; const cfg_obj_t *obj = NULL; (void)cfg_map_get(zoptions, "notify", &obj); if (obj == NULL) { /* * "notify" not set at zone level. This is fine. */ return (true); } if (cfg_obj_isboolean(obj)) { if (cfg_obj_asboolean(obj)) { /* * "notify yes;" set at zone level. This is an error. */ notify_configuration_ok = false; } } else { const char *notifystr = cfg_obj_asstring(obj); if (strcasecmp(notifystr, "explicit") != 0) { /* * Something else than "notify explicit;" set at zone * level. This is an error. */ notify_configuration_ok = false; } } if (!notify_configuration_ok) { cfg_obj_log(zoptions, logctx, ISC_LOG_ERROR, "zone '%s': mirror zones can only be used with " "'notify no;' or 'notify explicit;'", znamestr); } return (notify_configuration_ok); } /*% * Try to determine whether recursion is available in a view without resorting * to extraordinary measures: just check the "recursion" and "allow-recursion" * settings. The point is to prevent accidental mirror zone misuse rather than * to enforce some sort of policy. Recursion is assumed to be allowed by * default if it is not explicitly disabled. */ static bool check_recursion(const cfg_obj_t *config, const cfg_obj_t *voptions, const cfg_obj_t *goptions, isc_log_t *logctx, cfg_aclconfctx_t *actx, isc_mem_t *mctx) { dns_acl_t *acl = NULL; const cfg_obj_t *obj; isc_result_t result; bool retval = true; /* * Check the "recursion" option first. */ obj = NULL; result = ISC_R_NOTFOUND; if (voptions != NULL) { result = cfg_map_get(voptions, "recursion", &obj); } if (result != ISC_R_SUCCESS && goptions != NULL) { result = cfg_map_get(goptions, "recursion", &obj); } if (result == ISC_R_SUCCESS && !cfg_obj_asboolean(obj)) { retval = false; goto cleanup; } /* * If recursion is not disabled by the "recursion" option, check * whether it is disabled by the "allow-recursion" ACL. */ obj = NULL; result = ISC_R_NOTFOUND; if (voptions != NULL) { result = cfg_map_get(voptions, "allow-recursion", &obj); } if (result != ISC_R_SUCCESS && goptions != NULL) { result = cfg_map_get(goptions, "allow-recursion", &obj); } if (result == ISC_R_SUCCESS) { result = cfg_acl_fromconfig(obj, config, logctx, actx, mctx, 0, &acl); if (result != ISC_R_SUCCESS) { goto cleanup; } retval = !dns_acl_isnone(acl); } cleanup: if (acl != NULL) { dns_acl_detach(&acl); } return (retval); } 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, isc_symtab_t *files, isc_symtab_t *keydirs, isc_symtab_t *inview, const char *viewname, dns_rdataclass_t defclass, bool nodeprecate, cfg_aclconfctx_t *actx, isc_log_t *logctx, isc_mem_t *mctx) { const char *znamestr; const char *typestr = NULL; const char *target = NULL; unsigned int ztype; const cfg_obj_t *zoptions, *goptions = NULL; const cfg_obj_t *obj = NULL, *kasp = NULL; const cfg_obj_t *inviewobj = NULL; isc_result_t result = ISC_R_SUCCESS; isc_result_t tresult; unsigned int i; dns_rdataclass_t zclass; dns_fixedname_t fixedname; dns_name_t *zname = NULL; /* NULL if parsing of zone name fails. */ isc_buffer_t b; bool root = false; bool rfc1918 = false; bool ula = false; const cfg_listelt_t *element; bool dlz; bool ddns = false; bool has_dnssecpolicy = false; const void *clauses = NULL; const char *option = NULL; const char *kaspname = NULL; const char *dir = NULL; static const char *acls[] = { "allow-notify", "allow-transfer", "allow-update", "allow-update-forwarding", }; static optionstable dialups[] = { { "notify", CFG_ZONE_PRIMARY | CFG_ZONE_SECONDARY }, { "notify-passive", CFG_ZONE_SECONDARY }, { "passive", CFG_ZONE_SECONDARY | CFG_ZONE_STUB }, { "refresh", CFG_ZONE_SECONDARY | CFG_ZONE_STUB }, }; static const char *sources[] = { "transfer-source", "transfer-source-v6", "notify-source", "notify-source-v6", "parental-source", "parental-source-v6", }; znamestr = cfg_obj_asstring(cfg_tuple_get(zconfig, "name")); zoptions = cfg_tuple_get(zconfig, "options"); if (config != NULL) { cfg_map_get(config, "options", &goptions); } inviewobj = NULL; (void)cfg_map_get(zoptions, "in-view", &inviewobj); if (inviewobj != NULL) { target = cfg_obj_asstring(inviewobj); ztype = CFG_ZONE_INVIEW; } else { 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", znamestr); return (ISC_R_FAILURE); } typestr = cfg_obj_asstring(obj); if (strcasecmp(typestr, "master") == 0 || strcasecmp(typestr, "primary") == 0) { ztype = CFG_ZONE_PRIMARY; } else if (strcasecmp(typestr, "slave") == 0 || strcasecmp(typestr, "secondary") == 0) { ztype = CFG_ZONE_SECONDARY; } else if (strcasecmp(typestr, "mirror") == 0) { ztype = CFG_ZONE_MIRROR; } else if (strcasecmp(typestr, "stub") == 0) { ztype = CFG_ZONE_STUB; } else if (strcasecmp(typestr, "static-stub") == 0) { ztype = CFG_ZONE_STATICSTUB; } else if (strcasecmp(typestr, "forward") == 0) { ztype = CFG_ZONE_FORWARD; } else if (strcasecmp(typestr, "hint") == 0) { ztype = CFG_ZONE_HINT; } else if (strcasecmp(typestr, "delegation-only") == 0) { ztype = CFG_ZONE_DELEGATION; if (!nodeprecate) { cfg_obj_log(obj, logctx, ISC_LOG_WARNING, "'type delegation-only' is " "deprecated"); } } else if (strcasecmp(typestr, "redirect") == 0) { ztype = CFG_ZONE_REDIRECT; } else { cfg_obj_log(obj, logctx, ISC_LOG_ERROR, "zone '%s': invalid type %s", znamestr, typestr); return (ISC_R_FAILURE); } if (ztype == CFG_ZONE_REDIRECT && strcmp(znamestr, ".") != 0) { cfg_obj_log(zconfig, logctx, ISC_LOG_ERROR, "redirect zones must be called \".\""); 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", znamestr, 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", znamestr, r.base); return (ISC_R_FAILURE); } } else { zclass = defclass; } /* * Look for an already existing zone. * We need to make this canonical as isc_symtab_define() * deals with strings. */ dns_fixedname_init(&fixedname); isc_buffer_constinit(&b, znamestr, strlen(znamestr)); isc_buffer_add(&b, strlen(znamestr)); tresult = dns_name_fromtext(dns_fixedname_name(&fixedname), &b, dns_rootname, DNS_NAME_DOWNCASE, NULL); if (tresult != ISC_R_SUCCESS) { cfg_obj_log(zconfig, logctx, ISC_LOG_ERROR, "zone '%s': is not a valid name", znamestr); result = ISC_R_FAILURE; } else { char namebuf[DNS_NAME_FORMATSIZE + 128]; char *tmp = namebuf; size_t len = sizeof(namebuf); zname = dns_fixedname_name(&fixedname); dns_name_format(zname, namebuf, sizeof(namebuf)); tresult = nameexist(zconfig, namebuf, ztype == CFG_ZONE_HINT ? 1 : ztype == CFG_ZONE_REDIRECT ? 2 : 3, symtab, "zone '%s': already exists " "previous definition: %s:%u", logctx, mctx); if (tresult != ISC_R_SUCCESS) { result = tresult; } if (dns_name_equal(zname, dns_rootname)) { root = true; } else if (dns_name_isrfc1918(zname)) { rfc1918 = true; } else if (dns_name_isula(zname)) { ula = true; } len -= strlen(tmp); tmp += strlen(tmp); (void)snprintf(tmp, len, "%u/%s", zclass, (ztype == CFG_ZONE_INVIEW) ? target : (viewname != NULL) ? viewname : "_default"); switch (ztype) { case CFG_ZONE_INVIEW: tresult = isc_symtab_lookup(inview, namebuf, 0, NULL); if (tresult != ISC_R_SUCCESS) { cfg_obj_log(inviewobj, logctx, ISC_LOG_ERROR, "'in-view' zone '%s' " "does not exist in view '%s', " "or view '%s' is not yet defined", znamestr, target, target); if (result == ISC_R_SUCCESS) { result = tresult; } } break; case CFG_ZONE_FORWARD: case CFG_ZONE_REDIRECT: case CFG_ZONE_DELEGATION: break; case CFG_ZONE_PRIMARY: case CFG_ZONE_SECONDARY: case CFG_ZONE_MIRROR: case CFG_ZONE_HINT: case CFG_ZONE_STUB: case CFG_ZONE_STATICSTUB: tmp = isc_mem_strdup(mctx, namebuf); { isc_symvalue_t symvalue; symvalue.as_cpointer = NULL; tresult = isc_symtab_define( inview, tmp, 1, symvalue, isc_symexists_replace); if (tresult == ISC_R_NOMEMORY) { isc_mem_free(mctx, tmp); } if (result == ISC_R_SUCCESS && tresult != ISC_R_SUCCESS) { result = tresult; } } break; default: UNREACHABLE(); } } if (ztype == CFG_ZONE_INVIEW) { const cfg_obj_t *fwd = NULL; unsigned int maxopts = 1; (void)cfg_map_get(zoptions, "forward", &fwd); if (fwd != NULL) { maxopts++; } fwd = NULL; (void)cfg_map_get(zoptions, "forwarders", &fwd); if (fwd != NULL) { maxopts++; } if (cfg_map_count(zoptions) > maxopts) { cfg_obj_log(zconfig, logctx, ISC_LOG_ERROR, "zone '%s': 'in-view' used " "with incompatible zone options", znamestr); if (result == ISC_R_SUCCESS) { result = ISC_R_FAILURE; } } return (result); } /* * Check if value is zero. */ if (check_nonzero(zoptions, logctx) != ISC_R_SUCCESS) { result = ISC_R_FAILURE; } /* * Check if a dnssec-policy is set. */ obj = NULL; (void)cfg_map_get(zoptions, "dnssec-policy", &obj); if (obj == NULL && voptions != NULL) { (void)cfg_map_get(voptions, "dnssec-policy", &obj); } if (obj == NULL && goptions != NULL) { (void)cfg_map_get(goptions, "dnssec-policy", &obj); } if (obj != NULL) { const cfg_obj_t *kasps = NULL; kaspname = cfg_obj_asstring(obj); if (strcmp(kaspname, "default") == 0) { has_dnssecpolicy = true; } else if (strcmp(kaspname, "insecure") == 0) { has_dnssecpolicy = true; } else if (strcmp(kaspname, "none") == 0) { has_dnssecpolicy = false; } else { (void)cfg_map_get(config, "dnssec-policy", &kasps); for (element = cfg_list_first(kasps); element != NULL; element = cfg_list_next(element)) { const cfg_obj_t *kobj = cfg_tuple_get( cfg_listelt_value(element), "name"); if (strcmp(kaspname, cfg_obj_asstring(kobj)) == 0) { has_dnssecpolicy = true; } } if (!has_dnssecpolicy) { cfg_obj_log(zconfig, logctx, ISC_LOG_ERROR, "zone '%s': option " "'dnssec-policy %s' has no " "matching dnssec-policy config", znamestr, kaspname); if (result == ISC_R_SUCCESS) { result = ISC_R_FAILURE; } } } if (has_dnssecpolicy) { kasp = obj; } } /* * Warn about zones with both dnssec-policy and max-zone-ttl */ if (has_dnssecpolicy) { obj = NULL; (void)cfg_map_get(zoptions, "max-zone-ttl", &obj); if (obj == NULL && voptions != NULL) { (void)cfg_map_get(voptions, "max-zone-ttl", &obj); } if (obj == NULL && goptions != NULL) { (void)cfg_map_get(goptions, "max-zone-ttl", &obj); } if (obj != NULL) { cfg_obj_log(obj, logctx, ISC_LOG_WARNING, "zone '%s': option 'max-zone-ttl' " "is ignored when used together with " "'dnssec-policy'", znamestr); } } /* * Check validity of the zone options. */ option = cfg_map_firstclause(&cfg_type_zoneopts, &clauses, &i); while (option != NULL) { obj = NULL; if (cfg_map_get(zoptions, option, &obj) == ISC_R_SUCCESS && obj != NULL && !cfg_clause_validforzone(option, ztype)) { cfg_obj_log(obj, logctx, ISC_LOG_WARNING, "option '%s' is not allowed " "in '%s' zone '%s'", option, typestr, znamestr); result = ISC_R_FAILURE; } option = cfg_map_nextclause(&cfg_type_zoneopts, &clauses, &i); } /* * Check that ACLs expand correctly. */ for (i = 0; i < ARRAY_SIZE(acls); i++) { tresult = checkacl(acls[i], actx, zconfig, voptions, config, logctx, mctx); if (tresult != ISC_R_SUCCESS) { result = tresult; } } /* * Only a limited subset of all possible "notify" settings can be used * at the zone level for mirror zones. */ if (ztype == CFG_ZONE_MIRROR && !check_mirror_zone_notify(zoptions, znamestr, logctx)) { result = ISC_R_FAILURE; } /* * Primary, secondary, and mirror zones may have an "also-notify" * field, but shouldn't if notify is disabled. */ if (ztype == CFG_ZONE_PRIMARY || ztype == CFG_ZONE_SECONDARY || ztype == CFG_ZONE_MIRROR) { bool donotify = true; obj = NULL; tresult = cfg_map_get(zoptions, "notify", &obj); if (tresult != ISC_R_SUCCESS && voptions != NULL) { tresult = cfg_map_get(voptions, "notify", &obj); } if (tresult != ISC_R_SUCCESS && goptions != NULL) { tresult = cfg_map_get(goptions, "notify", &obj); } if (tresult == ISC_R_SUCCESS) { if (cfg_obj_isboolean(obj)) { donotify = cfg_obj_asboolean(obj); } else { const char *str = cfg_obj_asstring(obj); if (ztype != CFG_ZONE_PRIMARY && (strcasecmp(str, "master-only") == 0 || strcasecmp(str, "primary-only") == 0)) { donotify = false; } } } obj = NULL; tresult = cfg_map_get(zoptions, "also-notify", &obj); if (tresult == ISC_R_SUCCESS && !donotify) { cfg_obj_log(zoptions, logctx, ISC_LOG_WARNING, "zone '%s': 'also-notify' set but " "'notify' is disabled", znamestr); } if (tresult != ISC_R_SUCCESS && voptions != NULL) { tresult = cfg_map_get(voptions, "also-notify", &obj); } if (tresult != ISC_R_SUCCESS && goptions != NULL) { tresult = cfg_map_get(goptions, "also-notify", &obj); } if (tresult == ISC_R_SUCCESS && donotify) { uint32_t count; tresult = validate_remotes("primaries", obj, config, &count, logctx, mctx); if (tresult != ISC_R_SUCCESS && result == ISC_R_SUCCESS) { result = tresult; } } } /* * Secondary, mirror, and stub zones must have a "primaries" field, * with one exception: when mirroring the root zone, a default, * built-in primary server list is used in the absence of one * explicitly specified. */ if (ztype == CFG_ZONE_SECONDARY || ztype == CFG_ZONE_STUB || (ztype == CFG_ZONE_MIRROR && zname != NULL && !dns_name_equal(zname, dns_rootname))) { obj = NULL; (void)cfg_map_get(zoptions, "primaries", &obj); if (obj == NULL) { /* If "primaries" was unset, check for "masters" */ (void)cfg_map_get(zoptions, "masters", &obj); } else { const cfg_obj_t *obj2 = NULL; /* ...bug if it was set, "masters" must not be. */ (void)cfg_map_get(zoptions, "masters", &obj2); if (obj2 != NULL) { cfg_obj_log(obj, logctx, ISC_LOG_ERROR, "'primaries' and 'masters' cannot " "both be used in the same zone"); result = ISC_R_FAILURE; } } if (obj == NULL) { cfg_obj_log(zoptions, logctx, ISC_LOG_ERROR, "zone '%s': missing 'primaries' entry", znamestr); result = ISC_R_FAILURE; } else { uint32_t count; tresult = validate_remotes("primaries", 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 'primaries' entry", znamestr); result = ISC_R_FAILURE; } } } /* * Warn if *-source and *-source-v6 options specify a port, * and fail if they specify the default listener port. */ for (i = 0; i < ARRAY_SIZE(sources); i++) { obj = NULL; (void)cfg_map_get(zoptions, sources[i], &obj); if (obj == NULL && goptions != NULL) { (void)cfg_map_get(goptions, sources[i], &obj); } if (obj != NULL) { in_port_t port = isc_sockaddr_getport(cfg_obj_assockaddr(obj)); if (port == dnsport) { cfg_obj_log(obj, logctx, ISC_LOG_ERROR, "'%s' cannot specify the " "DNS listener port (%d)", sources[i], port); result = ISC_R_FAILURE; } else if (port != 0) { cfg_obj_log(obj, logctx, ISC_LOG_WARNING, "'%s': specifying a port is " "not recommended", sources[i]); } } } /* * Primary and secondary zones that have a "parental-agents" field, * must have a corresponding "parental-agents" clause. */ if (ztype == CFG_ZONE_PRIMARY || ztype == CFG_ZONE_SECONDARY) { obj = NULL; (void)cfg_map_get(zoptions, "parental-agents", &obj); if (obj != NULL) { uint32_t count; tresult = validate_remotes("parental-agents", 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 'parental-agents' entry", znamestr); result = ISC_R_FAILURE; } } } /* * Configuring a mirror zone and disabling recursion at the same time * contradicts the purpose of the former. */ if (ztype == CFG_ZONE_MIRROR && !check_recursion(config, voptions, goptions, logctx, actx, mctx)) { cfg_obj_log(zoptions, logctx, ISC_LOG_ERROR, "zone '%s': mirror zones cannot be used if " "recursion is disabled", znamestr); result = ISC_R_FAILURE; } /* * Primary zones can't have both "allow-update" and "update-policy". */ if (ztype == CFG_ZONE_PRIMARY || ztype == CFG_ZONE_SECONDARY) { bool signing = false; isc_result_t res1, res2, res3; const cfg_obj_t *au = NULL; const char *arg; obj = NULL; res1 = cfg_map_get(zoptions, "allow-update", &au); 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", znamestr); result = ISC_R_FAILURE; } else if (res2 == ISC_R_SUCCESS) { res3 = check_update_policy(obj, logctx); if (res3 != ISC_R_SUCCESS) { result = ISC_R_FAILURE; } } /* * To determine whether auto-dnssec is allowed, * we should also check for allow-update at the * view and options levels. */ if (res1 != ISC_R_SUCCESS && voptions != NULL) { res1 = cfg_map_get(voptions, "allow-update", &au); } if (res1 != ISC_R_SUCCESS && goptions != NULL) { res1 = cfg_map_get(goptions, "allow-update", &au); } if (res2 == ISC_R_SUCCESS) { ddns = true; } else if (res1 == ISC_R_SUCCESS) { dns_acl_t *acl = NULL; res1 = cfg_acl_fromconfig(au, config, logctx, actx, mctx, 0, &acl); if (res1 != ISC_R_SUCCESS) { cfg_obj_log(au, logctx, ISC_LOG_ERROR, "acl expansion failed: %s", isc_result_totext(result)); result = ISC_R_FAILURE; } else if (acl != NULL) { if (!dns_acl_isnone(acl)) { ddns = true; } dns_acl_detach(&acl); } } obj = NULL; res1 = cfg_map_get(zoptions, "inline-signing", &obj); if (res1 == ISC_R_SUCCESS) { signing = cfg_obj_asboolean(obj); } if (has_dnssecpolicy) { if (!ddns && !signing) { cfg_obj_log(kasp, logctx, ISC_LOG_ERROR, "'inline-signing yes;' must also " "be configured explicitly for " "zones using dnssec-policy%s. See " "https://kb.isc.org/docs/" "dnssec-policy-requires-dynamic-" "dns-or-inline-signing", (ztype == CFG_ZONE_PRIMARY) ? " without a configured " "'allow-update' or " "'update-policy'" : ""); result = ISC_R_FAILURE; } } obj = NULL; arg = "off"; res3 = cfg_map_get(zoptions, "auto-dnssec", &obj); if (res3 == ISC_R_SUCCESS) { arg = cfg_obj_asstring(obj); cfg_obj_log(obj, logctx, ISC_LOG_WARNING, "'auto-dnssec' option is deprecated and " "will be removed in BIND 9.19. Please " "migrate to dnssec-policy"); } if (strcasecmp(arg, "off") != 0) { if (!ddns && !signing && !has_dnssecpolicy) { cfg_obj_log(obj, logctx, ISC_LOG_ERROR, "'auto-dnssec %s;' requires%s " "inline-signing to be configured " "for the zone", arg, (ztype == CFG_ZONE_PRIMARY) ? " dynamic DNS or" : ""); result = ISC_R_FAILURE; } if (has_dnssecpolicy) { cfg_obj_log(obj, logctx, ISC_LOG_ERROR, "'auto-dnssec %s;' cannot be " "configured if dnssec-policy is " "also set", arg); result = ISC_R_FAILURE; } } obj = NULL; res1 = cfg_map_get(zoptions, "sig-signing-type", &obj); if (res1 == ISC_R_SUCCESS) { uint32_t type = cfg_obj_asuint32(obj); if (type < 0xff00U || type > 0xffffU) { cfg_obj_log(obj, logctx, ISC_LOG_ERROR, "sig-signing-type: %u out of " "range [%u..%u]", type, 0xff00U, 0xffffU); result = ISC_R_FAILURE; } } obj = NULL; res1 = cfg_map_get(zoptions, "dnssec-dnskey-kskonly", &obj); if (res1 == ISC_R_SUCCESS && ztype == CFG_ZONE_SECONDARY && !signing) { cfg_obj_log(obj, logctx, ISC_LOG_ERROR, "dnssec-dnskey-kskonly: requires " "inline-signing when used in secondary " "zone"); result = ISC_R_FAILURE; } if (res1 == ISC_R_SUCCESS && has_dnssecpolicy) { cfg_obj_log(obj, logctx, ISC_LOG_ERROR, "dnssec-dnskey-kskonly: cannot be " "configured if dnssec-policy is also set"); result = ISC_R_FAILURE; } obj = NULL; res1 = cfg_map_get(zoptions, "dnssec-secure-to-insecure", &obj); if (res1 == ISC_R_SUCCESS && has_dnssecpolicy) { cfg_obj_log(obj, logctx, ISC_LOG_ERROR, "dnssec-secure-to-insecure: cannot be " "configured if dnssec-policy is also set"); result = ISC_R_FAILURE; } obj = NULL; res1 = cfg_map_get(zoptions, "dnssec-loadkeys-interval", &obj); if (res1 == ISC_R_SUCCESS && ztype == CFG_ZONE_SECONDARY && !signing) { cfg_obj_log(obj, logctx, ISC_LOG_ERROR, "dnssec-loadkeys-interval: requires " "inline-signing when used in secondary " "zone"); result = ISC_R_FAILURE; } obj = NULL; res1 = cfg_map_get(zoptions, "update-check-ksk", &obj); if (res1 == ISC_R_SUCCESS && ztype == CFG_ZONE_SECONDARY && !signing) { cfg_obj_log(obj, logctx, ISC_LOG_ERROR, "update-check-ksk: requires " "inline-signing when used in secondary " "zone"); result = ISC_R_FAILURE; } if (res1 == ISC_R_SUCCESS && has_dnssecpolicy) { cfg_obj_log(obj, logctx, ISC_LOG_ERROR, "update-check-ksk: cannot be configured " "if dnssec-policy is also set"); result = ISC_R_FAILURE; } obj = NULL; res1 = cfg_map_get(zoptions, "dnssec-update-mode", &obj); if (res1 == ISC_R_SUCCESS && has_dnssecpolicy) { cfg_obj_log(obj, logctx, ISC_LOG_ERROR, "dnssec-update-mode: cannot be configured " "if dnssec-policy is also set"); result = ISC_R_FAILURE; } } /* * Check the excessively complicated "dialup" option. */ if (ztype == CFG_ZONE_PRIMARY || ztype == CFG_ZONE_SECONDARY || ztype == CFG_ZONE_STUB) { obj = NULL; (void)cfg_map_get(zoptions, "dialup", &obj); if (obj != NULL && cfg_obj_isstring(obj)) { const char *str = cfg_obj_asstring(obj); 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, znamestr); 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, znamestr); result = ISC_R_FAILURE; } } } /* * Check that forwarding is reasonable. */ obj = NULL; if (root) { if (voptions != NULL) { (void)cfg_map_get(voptions, "forwarders", &obj); } if (obj == NULL && goptions != NULL) { (void)cfg_map_get(goptions, "forwarders", &obj); } } if (check_forward(zoptions, obj, logctx) != ISC_R_SUCCESS) { result = ISC_R_FAILURE; } /* * Check that a RFC 1918 / ULA reverse zone is not forward first * unless explicitly configured to be so. */ if (ztype == CFG_ZONE_FORWARD && (rfc1918 || ula)) { obj = NULL; (void)cfg_map_get(zoptions, "forward", &obj); if (obj == NULL) { /* * Forward mode not explicitly configured. */ if (voptions != NULL) { cfg_map_get(voptions, "forward", &obj); } if (obj == NULL && goptions != NULL) { cfg_map_get(goptions, "forward", &obj); } if (obj == NULL || strcasecmp(cfg_obj_asstring(obj), "first") == 0) { cfg_obj_log(zconfig, logctx, ISC_LOG_WARNING, "inherited 'forward first;' for " "%s zone '%s' - did you want " "'forward only;'?", rfc1918 ? "rfc1918" : "ula", znamestr); } } } /* * Check validity of static stub server addresses. */ obj = NULL; (void)cfg_map_get(zoptions, "server-addresses", &obj); if (ztype == CFG_ZONE_STATICSTUB && obj != NULL) { for (element = cfg_list_first(obj); element != NULL; element = cfg_list_next(element)) { isc_sockaddr_t sa; isc_netaddr_t na; obj = cfg_listelt_value(element); sa = *cfg_obj_assockaddr(obj); isc_netaddr_fromsockaddr(&na, &sa); if (isc_netaddr_getzone(&na) != 0) { result = ISC_R_FAILURE; cfg_obj_log(obj, logctx, ISC_LOG_ERROR, "scoped address is not allowed " "for static stub " "server-addresses"); } } } /* * Check validity of static stub server names. */ obj = NULL; (void)cfg_map_get(zoptions, "server-names", &obj); if (zname != NULL && ztype == CFG_ZONE_STATICSTUB && obj != NULL) { for (element = cfg_list_first(obj); element != NULL; element = cfg_list_next(element)) { const char *snamestr; dns_fixedname_t fixed_sname; isc_buffer_t b2; dns_name_t *sname; obj = cfg_listelt_value(element); snamestr = cfg_obj_asstring(obj); isc_buffer_constinit(&b2, snamestr, strlen(snamestr)); isc_buffer_add(&b2, strlen(snamestr)); sname = dns_fixedname_initname(&fixed_sname); tresult = dns_name_fromtext(sname, &b2, dns_rootname, 0, NULL); if (tresult != ISC_R_SUCCESS) { cfg_obj_log(zconfig, logctx, ISC_LOG_ERROR, "server-name '%s' is not a valid " "name", snamestr); result = ISC_R_FAILURE; } else if (dns_name_issubdomain(sname, zname)) { cfg_obj_log(zconfig, logctx, ISC_LOG_ERROR, "server-name '%s' must not be a " "subdomain of zone name '%s'", snamestr, znamestr); result = ISC_R_FAILURE; } } } /* * Warn if key-directory doesn't exist */ obj = NULL; (void)cfg_map_get(zoptions, "key-directory", &obj); if (obj == NULL && voptions != NULL) { (void)cfg_map_get(voptions, "key-directory", &obj); } if (obj == NULL && goptions != NULL) { (void)cfg_map_get(goptions, "key-directory", &obj); } if (obj != NULL) { dir = cfg_obj_asstring(obj); tresult = isc_file_isdirectory(dir); switch (tresult) { case ISC_R_SUCCESS: break; case ISC_R_FILENOTFOUND: cfg_obj_log(obj, logctx, ISC_LOG_WARNING, "key-directory: '%s' does not exist", dir); break; case ISC_R_INVALIDFILE: cfg_obj_log(obj, logctx, ISC_LOG_WARNING, "key-directory: '%s' is not a directory", dir); break; default: cfg_obj_log(obj, logctx, ISC_LOG_WARNING, "key-directory: '%s' %s", dir, isc_result_totext(tresult)); result = tresult; } } /* * Make sure there is no other zone with the same * key-directory and a different dnssec-policy. */ if (zname != NULL) { char keydirbuf[DNS_NAME_FORMATSIZE + 128]; char *tmp = keydirbuf; size_t len = sizeof(keydirbuf); dns_name_format(zname, keydirbuf, sizeof(keydirbuf)); len -= strlen(tmp); tmp += strlen(tmp); (void)snprintf(tmp, len, "/%s", (dir == NULL) ? "(null)" : dir); tresult = keydirexist(zconfig, (const char *)keydirbuf, kaspname, keydirs, logctx, mctx); if (tresult != ISC_R_SUCCESS) { result = tresult; } } /* * Check various options. */ tresult = check_options(zoptions, config, logctx, mctx, optlevel_zone); if (tresult != ISC_R_SUCCESS) { result = tresult; } /* * If the zone type is rbt/rbt64 then primary/hint zones require file * clauses. If inline-signing is used, then secondary zones require a * file clause as well. */ obj = NULL; dlz = false; tresult = cfg_map_get(zoptions, "dlz", &obj); if (tresult == ISC_R_SUCCESS) { dlz = true; } obj = NULL; tresult = cfg_map_get(zoptions, "database", &obj); if (dlz && tresult == ISC_R_SUCCESS) { cfg_obj_log(zconfig, logctx, ISC_LOG_ERROR, "zone '%s': cannot specify both 'dlz' " "and 'database'", znamestr); result = ISC_R_FAILURE; } else if (!dlz && (tresult == ISC_R_NOTFOUND || (tresult == ISC_R_SUCCESS && (strcmp("rbt", cfg_obj_asstring(obj)) == 0 || strcmp("rbt64", cfg_obj_asstring(obj)) == 0)))) { isc_result_t res1; const cfg_obj_t *fileobj = NULL; tresult = cfg_map_get(zoptions, "file", &fileobj); obj = NULL; res1 = cfg_map_get(zoptions, "inline-signing", &obj); if ((tresult != ISC_R_SUCCESS && (ztype == CFG_ZONE_PRIMARY || ztype == CFG_ZONE_HINT || (ztype == CFG_ZONE_SECONDARY && res1 == ISC_R_SUCCESS && cfg_obj_asboolean(obj))))) { cfg_obj_log(zconfig, logctx, ISC_LOG_ERROR, "zone '%s': missing 'file' entry", znamestr); result = tresult; } else if (tresult == ISC_R_SUCCESS && (ztype == CFG_ZONE_SECONDARY || ztype == CFG_ZONE_MIRROR || ddns || has_dnssecpolicy)) { tresult = fileexist(fileobj, files, true, logctx); if (tresult != ISC_R_SUCCESS) { result = tresult; } } else if (tresult == ISC_R_SUCCESS && (ztype == CFG_ZONE_PRIMARY || ztype == CFG_ZONE_HINT)) { tresult = fileexist(fileobj, files, false, logctx); if (tresult != ISC_R_SUCCESS) { result = tresult; } } } return (result); } typedef struct keyalgorithms { const char *name; 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; isc_result_t result; isc_buffer_t buf; unsigned char secretbuf[1024]; 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); } isc_buffer_init(&buf, secretbuf, sizeof(secretbuf)); result = isc_base64_decodestring(cfg_obj_asstring(secretobj), &buf); if (result != ISC_R_SUCCESS) { cfg_obj_log(secretobj, logctx, ISC_LOG_ERROR, "bad secret '%s'", isc_result_totext(result)); return (result); } 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] == '-') { uint16_t digestbits; 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 fileexist(const cfg_obj_t *obj, isc_symtab_t *symtab, bool writeable, isc_log_t *logctx) { isc_result_t result; isc_symvalue_t symvalue; unsigned int line; const char *file; result = isc_symtab_lookup(symtab, cfg_obj_asstring(obj), 0, &symvalue); if (result == ISC_R_SUCCESS) { if (writeable) { file = cfg_obj_file(symvalue.as_cpointer); line = cfg_obj_line(symvalue.as_cpointer); cfg_obj_log(obj, logctx, ISC_LOG_ERROR, "writeable file '%s': already in use: " "%s:%u", cfg_obj_asstring(obj), file, line); return (ISC_R_EXISTS); } result = isc_symtab_lookup(symtab, cfg_obj_asstring(obj), 2, &symvalue); if (result == ISC_R_SUCCESS) { file = cfg_obj_file(symvalue.as_cpointer); line = cfg_obj_line(symvalue.as_cpointer); cfg_obj_log(obj, logctx, ISC_LOG_ERROR, "writeable file '%s': already in use: " "%s:%u", cfg_obj_asstring(obj), file, line); return (ISC_R_EXISTS); } return (ISC_R_SUCCESS); } symvalue.as_cpointer = obj; result = isc_symtab_define(symtab, cfg_obj_asstring(obj), writeable ? 2 : 1, symvalue, isc_symexists_reject); return (result); } static isc_result_t keydirexist(const cfg_obj_t *zcfg, const char *keydir, const char *kaspnamestr, isc_symtab_t *symtab, isc_log_t *logctx, isc_mem_t *mctx) { isc_result_t result; isc_symvalue_t symvalue; char *symkey; if (kaspnamestr == NULL || strcmp(kaspnamestr, "none") == 0) { return (ISC_R_SUCCESS); } result = isc_symtab_lookup(symtab, keydir, 0, &symvalue); if (result == ISC_R_SUCCESS) { const cfg_obj_t *kasp = NULL; const cfg_obj_t *exist = symvalue.as_cpointer; const char *file = cfg_obj_file(exist); unsigned int line = cfg_obj_line(exist); /* * Having the same key-directory for the same zone is fine * iff the zone is using the same policy, or has no policy. */ (void)cfg_map_get(cfg_tuple_get(exist, "options"), "dnssec-policy", &kasp); if (kasp == NULL || strcmp(cfg_obj_asstring(kasp), "none") == 0 || strcmp(cfg_obj_asstring(kasp), kaspnamestr) == 0) { return (ISC_R_SUCCESS); } cfg_obj_log(zcfg, logctx, ISC_LOG_ERROR, "key-directory '%s' already in use by zone %s with " "policy %s: %s:%u", keydir, cfg_obj_asstring(cfg_tuple_get(exist, "name")), cfg_obj_asstring(kasp), file, line); return (ISC_R_EXISTS); } /* * Add the new zone plus key-directory. */ symkey = isc_mem_strdup(mctx, keydir); symvalue.as_cpointer = zcfg; result = isc_symtab_define(symtab, symkey, 2, symvalue, isc_symexists_reject); return (result); } /* * Check key list for duplicates key names and that the key names * are valid domain names as these keys are used for TSIG. * * Check the key contents for validity. */ static isc_result_t check_keylist(const cfg_obj_t *keys, isc_symtab_t *symtab, isc_mem_t *mctx, isc_log_t *logctx) { char namebuf[DNS_NAME_FORMATSIZE]; dns_fixedname_t fname; dns_name_t *name; isc_result_t result = ISC_R_SUCCESS; isc_result_t tresult; const cfg_listelt_t *element; name = dns_fixedname_initname(&fname); for (element = cfg_list_first(keys); element != NULL; element = cfg_list_next(element)) { const cfg_obj_t *key = cfg_listelt_value(element); const char *keyid = cfg_obj_asstring(cfg_map_getname(key)); isc_symvalue_t symvalue; isc_buffer_t b; char *keyname; isc_buffer_constinit(&b, keyid, strlen(keyid)); isc_buffer_add(&b, strlen(keyid)); tresult = dns_name_fromtext(name, &b, dns_rootname, 0, NULL); if (tresult != ISC_R_SUCCESS) { cfg_obj_log(key, logctx, ISC_LOG_ERROR, "key '%s': bad key name", keyid); result = tresult; continue; } tresult = bind9_check_key(key, logctx); if (tresult != ISC_R_SUCCESS) { return (tresult); } dns_name_format(name, namebuf, sizeof(namebuf)); keyname = isc_mem_strdup(mctx, namebuf); 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 = ""; } cfg_obj_log(key, logctx, ISC_LOG_ERROR, "key '%s': already exists " "previous definition: %s:%u", keyid, file, line); isc_mem_free(mctx, keyname); result = tresult; } else if (tresult != ISC_R_SUCCESS) { isc_mem_free(mctx, keyname); return (tresult); } } return (result); } /* * RNDC keys are not normalised unlike TSIG keys. * * "foo." is different to "foo". */ static bool rndckey_exists(const cfg_obj_t *keylist, const char *keyname) { const cfg_listelt_t *element; const cfg_obj_t *obj; const char *str; if (keylist == NULL) { return (false); } 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)) { return (true); } } return (false); } static struct { const char *v4; const char *v6; } sources[] = { { "transfer-source", "transfer-source-v6" }, { "notify-source", "notify-source-v6" }, { "parental-source", "parental-source-v6" }, { "query-source", "query-source-v6" }, { NULL, NULL } }; static struct { const char *name; isc_result_t (*set)(dns_peer_t *peer, bool newval); } bools[] = { { "bogus", dns_peer_setbogus }, { "edns", dns_peer_setsupportedns }, { "provide-ixfr", dns_peer_setprovideixfr }, { "request-expire", dns_peer_setrequestexpire }, { "request-ixfr", dns_peer_setrequestixfr }, { "request-nsid", dns_peer_setrequestnsid }, { "send-cookie", dns_peer_setsendcookie }, { "tcp-keepalive", dns_peer_settcpkeepalive }, { "tcp-only", dns_peer_setforcetcp }, }; static isc_result_t check_servers(const cfg_obj_t *config, const cfg_obj_t *voptions, isc_symtab_t *symtab, isc_mem_t *mctx, isc_log_t *logctx) { dns_fixedname_t fname; isc_result_t result = ISC_R_SUCCESS; isc_result_t tresult; const cfg_listelt_t *e1, *e2; const cfg_obj_t *v1, *v2, *keys; const cfg_obj_t *servers; isc_netaddr_t n1, n2; unsigned int p1, p2; const cfg_obj_t *obj; char buf[ISC_NETADDR_FORMATSIZE]; char namebuf[DNS_NAME_FORMATSIZE]; const char *xfr; const char *keyval; isc_buffer_t b; int source; dns_name_t *keyname; servers = NULL; if (voptions != NULL) { (void)cfg_map_get(voptions, "server", &servers); } if (servers == NULL) { (void)cfg_map_get(config, "server", &servers); } if (servers == NULL) { return (ISC_R_SUCCESS); } for (e1 = cfg_list_first(servers); e1 != NULL; e1 = cfg_list_next(e1)) { dns_peer_t *peer = NULL; size_t i; 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 { /* * For a v6 server we can't specify a v4 source, * and vice versa. */ 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/%u': %s not legal", buf, p1, xfr); result = ISC_R_FAILURE; } /* * Check that we aren't using the DNS * listener port (i.e. 53, or whatever was set * as "port" in options) as a source port. */ obj = NULL; if (n1.family == AF_INET) { xfr = sources[source].v4; } else { xfr = sources[source].v6; } (void)cfg_map_get(v1, xfr, &obj); if (obj != NULL) { const isc_sockaddr_t *sa = cfg_obj_assockaddr(obj); in_port_t port = isc_sockaddr_getport(sa); if (port == dnsport) { cfg_obj_log(obj, logctx, ISC_LOG_ERROR, "'%s' cannot specify the " "DNS listener port (%d)", xfr, port); 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 = ""; } 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; } } keys = NULL; cfg_map_get(v1, "keys", &keys); if (keys != NULL) { /* * Normalize key name. */ keyval = cfg_obj_asstring(keys); isc_buffer_constinit(&b, keyval, strlen(keyval)); isc_buffer_add(&b, strlen(keyval)); keyname = dns_fixedname_initname(&fname); tresult = dns_name_fromtext(keyname, &b, dns_rootname, 0, NULL); if (tresult != ISC_R_SUCCESS) { cfg_obj_log(keys, logctx, ISC_LOG_ERROR, "bad key name '%s'", keyval); result = ISC_R_FAILURE; continue; } dns_name_format(keyname, namebuf, sizeof(namebuf)); tresult = isc_symtab_lookup(symtab, namebuf, 1, NULL); if (tresult != ISC_R_SUCCESS) { cfg_obj_log(keys, logctx, ISC_LOG_ERROR, "unknown key '%s'", keyval); result = ISC_R_FAILURE; } } (void)dns_peer_newprefix(mctx, &n1, p1, &peer); for (i = 0; i < ARRAY_SIZE(bools); i++) { const cfg_obj_t *opt = NULL; cfg_map_get(v1, bools[i].name, &opt); if (opt != NULL) { tresult = (bools[i].set)( peer, cfg_obj_asboolean(opt)); if (tresult != ISC_R_SUCCESS) { cfg_obj_log(opt, logctx, ISC_LOG_ERROR, "setting server option " "'%s' failed: %s", bools[i].name, isc_result_totext(tresult)); result = ISC_R_FAILURE; } } } dns_peer_detach(&peer); } return (result); } #define ROOT_KSK_STATIC 0x01 #define ROOT_KSK_MANAGED 0x02 #define ROOT_KSK_ANY 0x03 #define ROOT_KSK_2010 0x04 #define ROOT_KSK_2017 0x08 static isc_result_t check_trust_anchor(const cfg_obj_t *key, bool managed, unsigned int *flagsp, isc_log_t *logctx) { const char *str = NULL, *namestr = NULL; dns_fixedname_t fkeyname; dns_name_t *keyname = NULL; isc_buffer_t b; isc_region_t r; isc_result_t result = ISC_R_SUCCESS; isc_result_t tresult; uint32_t rdata1, rdata2, rdata3; unsigned char data[4096]; const char *atstr = NULL; enum { INIT_DNSKEY, STATIC_DNSKEY, INIT_DS, STATIC_DS, TRUSTED } anchortype; /* * The 2010 and 2017 IANA root keys - these are used below * to check the contents of trusted, initial and * static trust anchor configurations. */ static const unsigned char root_ksk_2010[] = { 0x03, 0x01, 0x00, 0x01, 0xa8, 0x00, 0x20, 0xa9, 0x55, 0x66, 0xba, 0x42, 0xe8, 0x86, 0xbb, 0x80, 0x4c, 0xda, 0x84, 0xe4, 0x7e, 0xf5, 0x6d, 0xbd, 0x7a, 0xec, 0x61, 0x26, 0x15, 0x55, 0x2c, 0xec, 0x90, 0x6d, 0x21, 0x16, 0xd0, 0xef, 0x20, 0x70, 0x28, 0xc5, 0x15, 0x54, 0x14, 0x4d, 0xfe, 0xaf, 0xe7, 0xc7, 0xcb, 0x8f, 0x00, 0x5d, 0xd1, 0x82, 0x34, 0x13, 0x3a, 0xc0, 0x71, 0x0a, 0x81, 0x18, 0x2c, 0xe1, 0xfd, 0x14, 0xad, 0x22, 0x83, 0xbc, 0x83, 0x43, 0x5f, 0x9d, 0xf2, 0xf6, 0x31, 0x32, 0x51, 0x93, 0x1a, 0x17, 0x6d, 0xf0, 0xda, 0x51, 0xe5, 0x4f, 0x42, 0xe6, 0x04, 0x86, 0x0d, 0xfb, 0x35, 0x95, 0x80, 0x25, 0x0f, 0x55, 0x9c, 0xc5, 0x43, 0xc4, 0xff, 0xd5, 0x1c, 0xbe, 0x3d, 0xe8, 0xcf, 0xd0, 0x67, 0x19, 0x23, 0x7f, 0x9f, 0xc4, 0x7e, 0xe7, 0x29, 0xda, 0x06, 0x83, 0x5f, 0xa4, 0x52, 0xe8, 0x25, 0xe9, 0xa1, 0x8e, 0xbc, 0x2e, 0xcb, 0xcf, 0x56, 0x34, 0x74, 0x65, 0x2c, 0x33, 0xcf, 0x56, 0xa9, 0x03, 0x3b, 0xcd, 0xf5, 0xd9, 0x73, 0x12, 0x17, 0x97, 0xec, 0x80, 0x89, 0x04, 0x1b, 0x6e, 0x03, 0xa1, 0xb7, 0x2d, 0x0a, 0x73, 0x5b, 0x98, 0x4e, 0x03, 0x68, 0x73, 0x09, 0x33, 0x23, 0x24, 0xf2, 0x7c, 0x2d, 0xba, 0x85, 0xe9, 0xdb, 0x15, 0xe8, 0x3a, 0x01, 0x43, 0x38, 0x2e, 0x97, 0x4b, 0x06, 0x21, 0xc1, 0x8e, 0x62, 0x5e, 0xce, 0xc9, 0x07, 0x57, 0x7d, 0x9e, 0x7b, 0xad, 0xe9, 0x52, 0x41, 0xa8, 0x1e, 0xbb, 0xe8, 0xa9, 0x01, 0xd4, 0xd3, 0x27, 0x6e, 0x40, 0xb1, 0x14, 0xc0, 0xa2, 0xe6, 0xfc, 0x38, 0xd1, 0x9c, 0x2e, 0x6a, 0xab, 0x02, 0x64, 0x4b, 0x28, 0x13, 0xf5, 0x75, 0xfc, 0x21, 0x60, 0x1e, 0x0d, 0xee, 0x49, 0xcd, 0x9e, 0xe9, 0x6a, 0x43, 0x10, 0x3e, 0x52, 0x4d, 0x62, 0x87, 0x3d }; static const unsigned char root_ksk_2017[] = { 0x03, 0x01, 0x00, 0x01, 0xac, 0xff, 0xb4, 0x09, 0xbc, 0xc9, 0x39, 0xf8, 0x31, 0xf7, 0xa1, 0xe5, 0xec, 0x88, 0xf7, 0xa5, 0x92, 0x55, 0xec, 0x53, 0x04, 0x0b, 0xe4, 0x32, 0x02, 0x73, 0x90, 0xa4, 0xce, 0x89, 0x6d, 0x6f, 0x90, 0x86, 0xf3, 0xc5, 0xe1, 0x77, 0xfb, 0xfe, 0x11, 0x81, 0x63, 0xaa, 0xec, 0x7a, 0xf1, 0x46, 0x2c, 0x47, 0x94, 0x59, 0x44, 0xc4, 0xe2, 0xc0, 0x26, 0xbe, 0x5e, 0x98, 0xbb, 0xcd, 0xed, 0x25, 0x97, 0x82, 0x72, 0xe1, 0xe3, 0xe0, 0x79, 0xc5, 0x09, 0x4d, 0x57, 0x3f, 0x0e, 0x83, 0xc9, 0x2f, 0x02, 0xb3, 0x2d, 0x35, 0x13, 0xb1, 0x55, 0x0b, 0x82, 0x69, 0x29, 0xc8, 0x0d, 0xd0, 0xf9, 0x2c, 0xac, 0x96, 0x6d, 0x17, 0x76, 0x9f, 0xd5, 0x86, 0x7b, 0x64, 0x7c, 0x3f, 0x38, 0x02, 0x9a, 0xbd, 0xc4, 0x81, 0x52, 0xeb, 0x8f, 0x20, 0x71, 0x59, 0xec, 0xc5, 0xd2, 0x32, 0xc7, 0xc1, 0x53, 0x7c, 0x79, 0xf4, 0xb7, 0xac, 0x28, 0xff, 0x11, 0x68, 0x2f, 0x21, 0x68, 0x1b, 0xf6, 0xd6, 0xab, 0xa5, 0x55, 0x03, 0x2b, 0xf6, 0xf9, 0xf0, 0x36, 0xbe, 0xb2, 0xaa, 0xa5, 0xb3, 0x77, 0x8d, 0x6e, 0xeb, 0xfb, 0xa6, 0xbf, 0x9e, 0xa1, 0x91, 0xbe, 0x4a, 0xb0, 0xca, 0xea, 0x75, 0x9e, 0x2f, 0x77, 0x3a, 0x1f, 0x90, 0x29, 0xc7, 0x3e, 0xcb, 0x8d, 0x57, 0x35, 0xb9, 0x32, 0x1d, 0xb0, 0x85, 0xf1, 0xb8, 0xe2, 0xd8, 0x03, 0x8f, 0xe2, 0x94, 0x19, 0x92, 0x54, 0x8c, 0xee, 0x0d, 0x67, 0xdd, 0x45, 0x47, 0xe1, 0x1d, 0xd6, 0x3a, 0xf9, 0xc9, 0xfc, 0x1c, 0x54, 0x66, 0xfb, 0x68, 0x4c, 0xf0, 0x09, 0xd7, 0x19, 0x7c, 0x2c, 0xf7, 0x9e, 0x79, 0x2a, 0xb5, 0x01, 0xe6, 0xa8, 0xa1, 0xca, 0x51, 0x9a, 0xf2, 0xcb, 0x9b, 0x5f, 0x63, 0x67, 0xe9, 0x4c, 0x0d, 0x47, 0x50, 0x24, 0x51, 0x35, 0x7b, 0xe1, 0xb5 }; static const unsigned char root_ds_1_2017[] = { 0xae, 0x1e, 0xa5, 0xb9, 0x74, 0xd4, 0xc8, 0x58, 0xb7, 0x40, 0xbd, 0x03, 0xe3, 0xce, 0xd7, 0xeb, 0xfc, 0xbd, 0x17, 0x24 }; static const unsigned char root_ds_2_2017[] = { 0xe0, 0x6d, 0x44, 0xb8, 0x0b, 0x8f, 0x1d, 0x39, 0xa9, 0x5c, 0x0b, 0x0d, 0x7c, 0x65, 0xd0, 0x84, 0x58, 0xe8, 0x80, 0x40, 0x9b, 0xbc, 0x68, 0x34, 0x57, 0x10, 0x42, 0x37, 0xc7, 0xf8, 0xec, 0x8D }; /* if DNSKEY, flags; if DS, key tag */ rdata1 = cfg_obj_asuint32(cfg_tuple_get(key, "rdata1")); /* if DNSKEY, protocol; if DS, algorithm */ rdata2 = cfg_obj_asuint32(cfg_tuple_get(key, "rdata2")); /* if DNSKEY, algorithm; if DS, digest type */ rdata3 = cfg_obj_asuint32(cfg_tuple_get(key, "rdata3")); namestr = cfg_obj_asstring(cfg_tuple_get(key, "name")); keyname = dns_fixedname_initname(&fkeyname); isc_buffer_constinit(&b, namestr, strlen(namestr)); isc_buffer_add(&b, strlen(namestr)); result = dns_name_fromtext(keyname, &b, dns_rootname, 0, NULL); if (result != ISC_R_SUCCESS) { cfg_obj_log(key, logctx, ISC_LOG_WARNING, "bad key name: %s\n", isc_result_totext(result)); result = ISC_R_FAILURE; } if (managed) { atstr = cfg_obj_asstring(cfg_tuple_get(key, "anchortype")); if (strcasecmp(atstr, "static-key") == 0) { managed = false; anchortype = STATIC_DNSKEY; } else if (strcasecmp(atstr, "static-ds") == 0) { managed = false; anchortype = STATIC_DS; } else if (strcasecmp(atstr, "initial-key") == 0) { anchortype = INIT_DNSKEY; } else if (strcasecmp(atstr, "initial-ds") == 0) { anchortype = INIT_DS; } else { cfg_obj_log(key, logctx, ISC_LOG_ERROR, "key '%s': " "invalid initialization method '%s'", namestr, atstr); result = ISC_R_FAILURE; /* * We can't interpret the trust anchor, so * we skip all other checks. */ goto cleanup; } } else { atstr = "trusted-key"; anchortype = TRUSTED; } switch (anchortype) { case INIT_DNSKEY: case STATIC_DNSKEY: case TRUSTED: if (rdata1 > 0xffff) { cfg_obj_log(key, logctx, ISC_LOG_ERROR, "flags too big: %u", rdata1); result = ISC_R_RANGE; } if (rdata1 & DNS_KEYFLAG_REVOKE) { cfg_obj_log(key, logctx, ISC_LOG_WARNING, "key flags revoke bit set"); } if (rdata2 > 0xff) { cfg_obj_log(key, logctx, ISC_LOG_ERROR, "protocol too big: %u", rdata2); result = ISC_R_RANGE; } if (rdata3 > 0xff) { cfg_obj_log(key, logctx, ISC_LOG_ERROR, "algorithm too big: %u\n", rdata3); result = ISC_R_RANGE; } isc_buffer_init(&b, data, sizeof(data)); str = cfg_obj_asstring(cfg_tuple_get(key, "data")); tresult = isc_base64_decodestring(str, &b); if (tresult != ISC_R_SUCCESS) { cfg_obj_log(key, logctx, ISC_LOG_ERROR, "%s", isc_result_totext(tresult)); result = ISC_R_FAILURE; } else { isc_buffer_usedregion(&b, &r); if ((rdata3 == DST_ALG_RSASHA1) && r.length > 1 && r.base[0] == 1 && r.base[1] == 3) { cfg_obj_log(key, logctx, ISC_LOG_WARNING, "%s '%s' has a weak exponent", atstr, namestr); } } if (result == ISC_R_SUCCESS && dns_name_equal(keyname, dns_rootname)) { /* * Flag any use of a root key, regardless of content. */ *flagsp |= (managed ? ROOT_KSK_MANAGED : ROOT_KSK_STATIC); if (rdata1 == 257 && rdata2 == 3 && rdata3 == 8 && (isc_buffer_usedlength(&b) == sizeof(root_ksk_2010)) && memcmp(data, root_ksk_2010, sizeof(root_ksk_2010)) == 0) { *flagsp |= ROOT_KSK_2010; } if (rdata1 == 257 && rdata2 == 3 && rdata3 == 8 && (isc_buffer_usedlength(&b) == sizeof(root_ksk_2017)) && memcmp(data, root_ksk_2017, sizeof(root_ksk_2017)) == 0) { *flagsp |= ROOT_KSK_2017; } } break; case INIT_DS: case STATIC_DS: if (rdata1 > 0xffff) { cfg_obj_log(key, logctx, ISC_LOG_ERROR, "key tag too big: %u", rdata1); result = ISC_R_RANGE; } if (rdata2 > 0xff) { cfg_obj_log(key, logctx, ISC_LOG_ERROR, "algorithm too big: %u\n", rdata2); result = ISC_R_RANGE; } if (rdata3 > 0xff) { cfg_obj_log(key, logctx, ISC_LOG_ERROR, "digest type too big: %u", rdata3); result = ISC_R_RANGE; } isc_buffer_init(&b, data, sizeof(data)); str = cfg_obj_asstring(cfg_tuple_get(key, "data")); tresult = isc_hex_decodestring(str, &b); if (tresult != ISC_R_SUCCESS) { cfg_obj_log(key, logctx, ISC_LOG_ERROR, "%s", isc_result_totext(tresult)); result = ISC_R_FAILURE; } if (result == ISC_R_SUCCESS && dns_name_equal(keyname, dns_rootname)) { /* * Flag any use of a root key, regardless of content. */ *flagsp |= (managed ? ROOT_KSK_MANAGED : ROOT_KSK_STATIC); if (rdata1 == 20326 && rdata2 == 8 && rdata3 == 1 && (isc_buffer_usedlength(&b) == sizeof(root_ds_1_2017)) && memcmp(data, root_ds_1_2017, sizeof(root_ds_1_2017)) == 0) { *flagsp |= ROOT_KSK_2017; } if (rdata1 == 20326 && rdata2 == 8 && rdata3 == 2 && (isc_buffer_usedlength(&b) == sizeof(root_ds_2_2017)) && memcmp(data, root_ds_2_2017, sizeof(root_ds_2_2017)) == 0) { *flagsp |= ROOT_KSK_2017; } } break; } cleanup: return (result); } static isc_result_t record_static_keys(isc_symtab_t *symtab, isc_mem_t *mctx, const cfg_obj_t *keylist, isc_log_t *logctx, bool autovalidation) { isc_result_t result, ret = ISC_R_SUCCESS; const cfg_listelt_t *elt; dns_fixedname_t fixed; dns_name_t *name; char namebuf[DNS_NAME_FORMATSIZE], *p = NULL; name = dns_fixedname_initname(&fixed); for (elt = cfg_list_first(keylist); elt != NULL; elt = cfg_list_next(elt)) { const char *initmethod; const cfg_obj_t *init = NULL; const cfg_obj_t *obj = cfg_listelt_value(elt); const char *str = cfg_obj_asstring(cfg_tuple_get(obj, "name")); isc_symvalue_t symvalue; result = dns_name_fromstring(name, str, 0, NULL); if (result != ISC_R_SUCCESS) { continue; } init = cfg_tuple_get(obj, "anchortype"); if (!cfg_obj_isvoid(init)) { initmethod = cfg_obj_asstring(init); if (strcasecmp(initmethod, "initial-key") == 0) { /* initializing key, skip it */ continue; } if (strcasecmp(initmethod, "initial-ds") == 0) { /* initializing key, skip it */ continue; } } dns_name_format(name, namebuf, sizeof(namebuf)); symvalue.as_cpointer = obj; p = isc_mem_strdup(mctx, namebuf); result = isc_symtab_define(symtab, p, 1, symvalue, isc_symexists_reject); if (result == ISC_R_EXISTS) { isc_mem_free(mctx, p); } else if (result != ISC_R_SUCCESS) { isc_mem_free(mctx, p); ret = result; continue; } if (autovalidation && dns_name_equal(name, dns_rootname)) { cfg_obj_log(obj, logctx, ISC_LOG_ERROR, "static trust anchor for root zone " "cannot be used with " "'dnssec-validation auto'."); ret = ISC_R_FAILURE; continue; } } return (ret); } static isc_result_t check_initializing_keys(isc_symtab_t *symtab, const cfg_obj_t *keylist, isc_log_t *logctx) { isc_result_t result, ret = ISC_R_SUCCESS; const cfg_listelt_t *elt; dns_fixedname_t fixed; dns_name_t *name; char namebuf[DNS_NAME_FORMATSIZE]; name = dns_fixedname_initname(&fixed); for (elt = cfg_list_first(keylist); elt != NULL; elt = cfg_list_next(elt)) { const cfg_obj_t *obj = cfg_listelt_value(elt); const cfg_obj_t *init = NULL; const char *str; isc_symvalue_t symvalue; init = cfg_tuple_get(obj, "anchortype"); if (cfg_obj_isvoid(init) || strcasecmp(cfg_obj_asstring(init), "static-key") == 0 || strcasecmp(cfg_obj_asstring(init), "static-ds") == 0) { /* static key, skip it */ continue; } str = cfg_obj_asstring(cfg_tuple_get(obj, "name")); result = dns_name_fromstring(name, str, 0, NULL); if (result != ISC_R_SUCCESS) { continue; } dns_name_format(name, namebuf, sizeof(namebuf)); result = isc_symtab_lookup(symtab, namebuf, 1, &symvalue); if (result == ISC_R_SUCCESS) { const char *file = cfg_obj_file(symvalue.as_cpointer); unsigned int line = cfg_obj_line(symvalue.as_cpointer); if (file == NULL) { file = ""; } cfg_obj_log(obj, logctx, ISC_LOG_ERROR, "static and initializing keys " "cannot be used for the " "same domain. " "static key defined at " "%s:%u", file, line); ret = ISC_R_FAILURE; } } return (ret); } static isc_result_t record_ds_keys(isc_symtab_t *symtab, isc_mem_t *mctx, const cfg_obj_t *keylist) { isc_result_t result, ret = ISC_R_SUCCESS; const cfg_listelt_t *elt; dns_fixedname_t fixed; dns_name_t *name; char namebuf[DNS_NAME_FORMATSIZE], *p = NULL; name = dns_fixedname_initname(&fixed); for (elt = cfg_list_first(keylist); elt != NULL; elt = cfg_list_next(elt)) { const char *initmethod; const cfg_obj_t *init = NULL; const cfg_obj_t *obj = cfg_listelt_value(elt); const char *str = cfg_obj_asstring(cfg_tuple_get(obj, "name")); isc_symvalue_t symvalue; result = dns_name_fromstring(name, str, 0, NULL); if (result != ISC_R_SUCCESS) { continue; } init = cfg_tuple_get(obj, "anchortype"); if (!cfg_obj_isvoid(init)) { initmethod = cfg_obj_asstring(init); if (strcasecmp(initmethod, "initial-key") == 0 || strcasecmp(initmethod, "static-key") == 0) { /* Key-style key, skip it */ continue; } } dns_name_format(name, namebuf, sizeof(namebuf)); symvalue.as_cpointer = obj; p = isc_mem_strdup(mctx, namebuf); result = isc_symtab_define(symtab, p, 1, symvalue, isc_symexists_reject); if (result == ISC_R_EXISTS) { isc_mem_free(mctx, p); } else if (result != ISC_R_SUCCESS) { isc_mem_free(mctx, p); ret = result; continue; } } return (ret); } /* * Check for conflicts between static and initialiizing keys. */ static isc_result_t check_ta_conflicts(const cfg_obj_t *global_ta, const cfg_obj_t *view_ta, const cfg_obj_t *global_tkeys, const cfg_obj_t *view_tkeys, bool autovalidation, isc_mem_t *mctx, isc_log_t *logctx) { isc_result_t result, tresult; const cfg_listelt_t *elt = NULL; const cfg_obj_t *keylist = NULL; isc_symtab_t *statictab = NULL, *dstab = NULL; result = isc_symtab_create(mctx, 100, freekey, mctx, false, &statictab); if (result != ISC_R_SUCCESS) { goto cleanup; } result = isc_symtab_create(mctx, 100, freekey, mctx, false, &dstab); if (result != ISC_R_SUCCESS) { goto cleanup; } /* * First we record all the static keys (i.e., old-style * trusted-keys and trust-anchors configured with "static-key"), * and all the DS-style trust anchors. */ for (elt = cfg_list_first(global_ta); elt != NULL; elt = cfg_list_next(elt)) { keylist = cfg_listelt_value(elt); tresult = record_static_keys(statictab, mctx, keylist, logctx, autovalidation); if (result == ISC_R_SUCCESS) { result = tresult; } tresult = record_ds_keys(dstab, mctx, keylist); if (result == ISC_R_SUCCESS) { result = tresult; } } for (elt = cfg_list_first(view_ta); elt != NULL; elt = cfg_list_next(elt)) { keylist = cfg_listelt_value(elt); tresult = record_static_keys(statictab, mctx, keylist, logctx, autovalidation); if (result == ISC_R_SUCCESS) { result = tresult; } tresult = record_ds_keys(dstab, mctx, keylist); if (result == ISC_R_SUCCESS) { result = tresult; } } for (elt = cfg_list_first(global_tkeys); elt != NULL; elt = cfg_list_next(elt)) { keylist = cfg_listelt_value(elt); tresult = record_static_keys(statictab, mctx, keylist, logctx, autovalidation); if (result == ISC_R_SUCCESS) { result = tresult; } } for (elt = cfg_list_first(view_tkeys); elt != NULL; elt = cfg_list_next(elt)) { keylist = cfg_listelt_value(elt); tresult = record_static_keys(statictab, mctx, keylist, logctx, autovalidation); if (result == ISC_R_SUCCESS) { result = tresult; } } /* * Next, ensure that there's no conflict between the * static keys and the trust-anchors configured with "initial-key". */ for (elt = cfg_list_first(global_ta); elt != NULL; elt = cfg_list_next(elt)) { keylist = cfg_listelt_value(elt); tresult = check_initializing_keys(statictab, keylist, logctx); if (result == ISC_R_SUCCESS) { result = tresult; } } for (elt = cfg_list_first(view_ta); elt != NULL; elt = cfg_list_next(elt)) { keylist = cfg_listelt_value(elt); tresult = check_initializing_keys(statictab, keylist, logctx); if (result == ISC_R_SUCCESS) { result = tresult; } } cleanup: if (statictab != NULL) { isc_symtab_destroy(&statictab); } if (dstab != NULL) { isc_symtab_destroy(&dstab); } return (result); } typedef enum { special_zonetype_rpz, special_zonetype_catz } special_zonetype_t; static isc_result_t check_rpz_catz(const char *rpz_catz, const cfg_obj_t *rpz_obj, const char *viewname, isc_symtab_t *symtab, isc_log_t *logctx, special_zonetype_t specialzonetype) { const cfg_listelt_t *element; const cfg_obj_t *obj, *nameobj, *zoneobj; const char *zonename, *zonetype; const char *forview = " for view "; isc_symvalue_t value; isc_result_t result, tresult; dns_fixedname_t fixed; dns_name_t *name; char namebuf[DNS_NAME_FORMATSIZE]; unsigned int num_zones = 0; if (viewname == NULL) { viewname = ""; forview = ""; } result = ISC_R_SUCCESS; name = dns_fixedname_initname(&fixed); obj = cfg_tuple_get(rpz_obj, "zone list"); for (element = cfg_list_first(obj); element != NULL; element = cfg_list_next(element)) { obj = cfg_listelt_value(element); nameobj = cfg_tuple_get(obj, "zone name"); zonename = cfg_obj_asstring(nameobj); zonetype = ""; if (specialzonetype == special_zonetype_rpz) { if (++num_zones > 64) { cfg_obj_log(nameobj, logctx, ISC_LOG_ERROR, "more than 64 response policy " "zones in view '%s'", viewname); return (ISC_R_FAILURE); } } tresult = dns_name_fromstring(name, zonename, 0, NULL); if (tresult != ISC_R_SUCCESS) { cfg_obj_log(nameobj, logctx, ISC_LOG_ERROR, "bad domain name '%s'", zonename); if (result == ISC_R_SUCCESS) { result = tresult; } continue; } dns_name_format(name, namebuf, sizeof(namebuf)); tresult = isc_symtab_lookup(symtab, namebuf, 3, &value); if (tresult == ISC_R_SUCCESS) { obj = NULL; zoneobj = value.as_cpointer; if (zoneobj != NULL && cfg_obj_istuple(zoneobj)) { zoneobj = cfg_tuple_get(zoneobj, "options"); } if (zoneobj != NULL && cfg_obj_ismap(zoneobj)) { (void)cfg_map_get(zoneobj, "type", &obj); } if (obj != NULL) { zonetype = cfg_obj_asstring(obj); } } if (strcasecmp(zonetype, "primary") != 0 && strcasecmp(zonetype, "master") != 0 && strcasecmp(zonetype, "secondary") != 0 && strcasecmp(zonetype, "slave") != 0) { cfg_obj_log(nameobj, logctx, ISC_LOG_ERROR, "%s '%s'%s%s is not a primary or secondary " "zone", rpz_catz, zonename, forview, viewname); if (result == ISC_R_SUCCESS) { result = ISC_R_FAILURE; } } } return (result); } static isc_result_t check_catz(const cfg_obj_t *catz_obj, const char *viewname, isc_mem_t *mctx, isc_log_t *logctx) { const cfg_listelt_t *element; const cfg_obj_t *obj, *nameobj, *primariesobj; const char *zonename; const char *forview = " for view "; isc_result_t result, tresult; isc_symtab_t *symtab = NULL; dns_fixedname_t fixed; dns_name_t *name = dns_fixedname_initname(&fixed); if (viewname == NULL) { viewname = ""; forview = ""; } result = isc_symtab_create(mctx, 100, freekey, mctx, false, &symtab); if (result != ISC_R_SUCCESS) { return (result); } obj = cfg_tuple_get(catz_obj, "zone list"); for (element = cfg_list_first(obj); element != NULL; element = cfg_list_next(element)) { char namebuf[DNS_NAME_FORMATSIZE]; obj = cfg_listelt_value(element); nameobj = cfg_tuple_get(obj, "zone name"); zonename = cfg_obj_asstring(nameobj); tresult = dns_name_fromstring(name, zonename, 0, NULL); if (tresult != ISC_R_SUCCESS) { cfg_obj_log(obj, logctx, ISC_LOG_ERROR, "bad domain name '%s'", zonename); if (result == ISC_R_SUCCESS) { result = tresult; continue; } } dns_name_format(name, namebuf, sizeof(namebuf)); tresult = nameexist(nameobj, namebuf, 1, symtab, "catalog zone '%s': already added here %s:%u", logctx, mctx); if (tresult != ISC_R_SUCCESS) { result = tresult; continue; } primariesobj = cfg_tuple_get(obj, "default-primaries"); if (primariesobj != NULL && cfg_obj_istuple(primariesobj)) { primariesobj = cfg_tuple_get(obj, "default-masters"); if (primariesobj != NULL && cfg_obj_istuple(primariesobj)) { cfg_obj_log(nameobj, logctx, ISC_LOG_ERROR, "catalog zone '%s'%s%s: " "'default-primaries' and " "'default-masters' can not be both " "defined", zonename, forview, viewname); result = ISC_R_FAILURE; break; } } } if (symtab != NULL) { isc_symtab_destroy(&symtab); } return (result); } /*% * Data structure used for the 'callback_data' argument to check_one_plugin(). */ struct check_one_plugin_data { isc_mem_t *mctx; isc_log_t *lctx; cfg_aclconfctx_t *actx; isc_result_t *check_result; }; /*% * A callback for the cfg_pluginlist_foreach() call in check_viewconf() below. * Since the point is to check configuration of all plugins even when * processing some of them fails, always return ISC_R_SUCCESS and indicate any * check failures through the 'check_result' variable passed in via the * 'callback_data' structure. */ static isc_result_t check_one_plugin(const cfg_obj_t *config, const cfg_obj_t *obj, const char *plugin_path, const char *parameters, void *callback_data) { struct check_one_plugin_data *data = callback_data; char full_path[PATH_MAX]; isc_result_t result; result = ns_plugin_expandpath(plugin_path, full_path, sizeof(full_path)); if (result != ISC_R_SUCCESS) { cfg_obj_log(obj, data->lctx, ISC_LOG_ERROR, "%s: plugin check failed: " "unable to get full plugin path: %s", plugin_path, isc_result_totext(result)); return (result); } result = ns_plugin_check(full_path, parameters, config, cfg_obj_file(obj), cfg_obj_line(obj), data->mctx, data->lctx, data->actx); if (result != ISC_R_SUCCESS) { cfg_obj_log(obj, data->lctx, ISC_LOG_ERROR, "%s: plugin check failed: %s", full_path, isc_result_totext(result)); *data->check_result = result; } return (ISC_R_SUCCESS); } static isc_result_t check_dnstap(const cfg_obj_t *voptions, const cfg_obj_t *config, isc_log_t *logctx) { #ifdef HAVE_DNSTAP const cfg_obj_t *options = NULL; const cfg_obj_t *obj = NULL; if (config != NULL) { (void)cfg_map_get(config, "options", &options); } if (options != NULL) { (void)cfg_map_get(options, "dnstap-output", &obj); } if (obj == NULL) { if (voptions != NULL) { (void)cfg_map_get(voptions, "dnstap", &obj); } if (options != NULL && obj == NULL) { (void)cfg_map_get(options, "dnstap", &obj); } if (obj != NULL) { cfg_obj_log(obj, logctx, ISC_LOG_ERROR, "'dnstap-output' must be set if 'dnstap' " "is set"); return (ISC_R_FAILURE); } } return (ISC_R_SUCCESS); #else /* ifdef HAVE_DNSTAP */ UNUSED(voptions); UNUSED(config); UNUSED(logctx); return (ISC_R_SUCCESS); #endif /* ifdef HAVE_DNSTAP */ } static isc_result_t check_viewconf(const cfg_obj_t *config, const cfg_obj_t *voptions, const char *viewname, dns_rdataclass_t vclass, isc_symtab_t *files, isc_symtab_t *keydirs, bool check_plugins, bool nodeprecate, isc_symtab_t *inview, isc_log_t *logctx, isc_mem_t *mctx) { const cfg_obj_t *zones = NULL; const cfg_obj_t *view_tkeys = NULL, *global_tkeys = NULL; const cfg_obj_t *view_mkeys = NULL, *global_mkeys = NULL; const cfg_obj_t *view_ta = NULL, *global_ta = NULL; const cfg_obj_t *check_keys[2] = { NULL, NULL }; const cfg_obj_t *keys = NULL; const cfg_listelt_t *element, *element2; isc_symtab_t *symtab = NULL; isc_result_t result = ISC_R_SUCCESS; isc_result_t tresult = ISC_R_SUCCESS; cfg_aclconfctx_t *actx = NULL; const cfg_obj_t *obj; const cfg_obj_t *options = NULL; const cfg_obj_t *opts = NULL; const cfg_obj_t *plugin_list = NULL; bool autovalidation = false; unsigned int tflags = 0, dflags = 0; int i; /* * Get global options block */ (void)cfg_map_get(config, "options", &options); /* * The most relevant options for this view */ if (voptions != NULL) { opts = voptions; } else { opts = options; } /* * Check that all zone statements are syntactically correct and * there are no duplicate zones. */ tresult = isc_symtab_create(mctx, 1000, freekey, mctx, false, &symtab); if (tresult != ISC_R_SUCCESS) { return (ISC_R_NOMEMORY); } cfg_aclconfctx_create(mctx, &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)) { const cfg_obj_t *zone = cfg_listelt_value(element); tresult = check_zoneconf(zone, voptions, config, symtab, files, keydirs, inview, viewname, vclass, nodeprecate, actx, logctx, mctx); if (tresult != ISC_R_SUCCESS) { result = ISC_R_FAILURE; } } /* * Check that the response-policy and catalog-zones options * refer to zones that exist. */ if (opts != NULL) { obj = NULL; if ((cfg_map_get(opts, "response-policy", &obj) == ISC_R_SUCCESS) && (check_rpz_catz("response-policy zone", obj, viewname, symtab, logctx, special_zonetype_rpz) != ISC_R_SUCCESS)) { result = ISC_R_FAILURE; } obj = NULL; if ((cfg_map_get(opts, "catalog-zones", &obj) == ISC_R_SUCCESS) && (check_rpz_catz("catalog zone", obj, viewname, symtab, logctx, special_zonetype_catz) != ISC_R_SUCCESS)) { result = ISC_R_FAILURE; } } /* * Check catalog-zones configuration. */ if (opts != NULL) { obj = NULL; if ((cfg_map_get(opts, "catalog-zones", &obj) == ISC_R_SUCCESS) && (check_catz(obj, viewname, mctx, logctx) != ISC_R_SUCCESS)) { result = ISC_R_FAILURE; } } isc_symtab_destroy(&symtab); /* * Check that forwarding is reasonable. */ if (opts != NULL && check_forward(opts, NULL, logctx) != ISC_R_SUCCESS) { result = ISC_R_FAILURE; } /* * Check non-zero options at the global and view levels. */ if (options != NULL && check_nonzero(options, logctx) != ISC_R_SUCCESS) { result = ISC_R_FAILURE; } if (voptions != NULL && check_nonzero(voptions, logctx) != ISC_R_SUCCESS) { result = ISC_R_FAILURE; } /* * Check that dual-stack-servers is reasonable. */ if (opts != NULL && check_dual_stack(opts, logctx) != ISC_R_SUCCESS) { result = ISC_R_FAILURE; } /* * Check that rrset-order is reasonable. */ if (opts != NULL && check_order(opts, logctx) != ISC_R_SUCCESS) { result = ISC_R_FAILURE; } /* * Check that all key statements are syntactically correct and * there are no duplicate keys. */ tresult = isc_symtab_create(mctx, 1000, freekey, mctx, false, &symtab); if (tresult != ISC_R_SUCCESS) { goto cleanup; } (void)cfg_map_get(config, "key", &keys); tresult = check_keylist(keys, symtab, mctx, logctx); if (tresult == ISC_R_EXISTS) { result = ISC_R_FAILURE; } else if (tresult != ISC_R_SUCCESS) { result = tresult; goto cleanup; } if (voptions != NULL) { keys = NULL; (void)cfg_map_get(voptions, "key", &keys); tresult = check_keylist(keys, symtab, mctx, logctx); if (tresult == ISC_R_EXISTS) { result = ISC_R_FAILURE; } else if (tresult != ISC_R_SUCCESS) { result = tresult; goto cleanup; } } /* * Global servers can refer to keys in views. */ if (check_servers(config, voptions, symtab, mctx, logctx) != ISC_R_SUCCESS) { result = ISC_R_FAILURE; } isc_symtab_destroy(&symtab); /* * Load all DNSSEC keys. */ if (voptions != NULL) { (void)cfg_map_get(voptions, "trusted-keys", &view_tkeys); (void)cfg_map_get(voptions, "trust-anchors", &view_ta); (void)cfg_map_get(voptions, "managed-keys", &view_mkeys); } (void)cfg_map_get(config, "trusted-keys", &global_tkeys); (void)cfg_map_get(config, "trust-anchors", &global_ta); (void)cfg_map_get(config, "managed-keys", &global_mkeys); /* * Check trusted-keys. */ check_keys[0] = view_tkeys; check_keys[1] = global_tkeys; for (i = 0; i < 2; i++) { if (check_keys[i] != NULL) { unsigned int flags = 0; for (element = cfg_list_first(check_keys[i]); element != NULL; element = cfg_list_next(element)) { const cfg_obj_t *keylist = cfg_listelt_value(element); for (element2 = cfg_list_first(keylist); element2 != NULL; element2 = cfg_list_next(element2)) { obj = cfg_listelt_value(element2); tresult = check_trust_anchor( obj, false, &flags, logctx); if (tresult != ISC_R_SUCCESS) { result = tresult; } } } if ((flags & ROOT_KSK_STATIC) != 0) { cfg_obj_log(check_keys[i], logctx, ISC_LOG_WARNING, "trusted-keys entry for the root " "zone WILL FAIL after key " "rollover - use trust-anchors " "with initial-key " "or initial-ds instead."); } tflags |= flags; } } /* * Check dnssec/managed-keys. (Only one or the other can be used.) */ if ((view_mkeys != NULL || global_mkeys != NULL) && (view_ta != NULL || global_ta != NULL)) { keys = (view_mkeys != NULL) ? view_mkeys : global_mkeys; cfg_obj_log(keys, logctx, ISC_LOG_ERROR, "use of managed-keys is not allowed when " "trust-anchors is also in use"); result = ISC_R_FAILURE; } if (view_ta == NULL && global_ta == NULL) { view_ta = view_mkeys; global_ta = global_mkeys; } check_keys[0] = view_ta; check_keys[1] = global_ta; for (i = 0; i < 2; i++) { if (check_keys[i] != NULL) { unsigned int flags = 0; for (element = cfg_list_first(check_keys[i]); element != NULL; element = cfg_list_next(element)) { const cfg_obj_t *keylist = cfg_listelt_value(element); for (element2 = cfg_list_first(keylist); element2 != NULL; element2 = cfg_list_next(element2)) { obj = cfg_listelt_value(element2); tresult = check_trust_anchor( obj, true, &flags, logctx); if (tresult != ISC_R_SUCCESS) { result = tresult; } } } if ((flags & ROOT_KSK_STATIC) != 0) { cfg_obj_log(check_keys[i], logctx, ISC_LOG_WARNING, "static entry for the root " "zone WILL FAIL after key " "rollover - use trust-anchors " "with initial-key " "or initial-ds instead."); } if ((flags & ROOT_KSK_2010) != 0 && (flags & ROOT_KSK_2017) == 0) { cfg_obj_log(check_keys[i], logctx, ISC_LOG_WARNING, "initial-key entry for the root " "zone uses the 2010 key without " "the updated 2017 key"); } dflags |= flags; } } if ((tflags & ROOT_KSK_ANY) != 0 && (dflags & ROOT_KSK_ANY) != 0) { keys = (view_ta != NULL) ? view_ta : global_ta; cfg_obj_log(keys, logctx, ISC_LOG_WARNING, "both trusted-keys and trust-anchors " "for the root zone are present"); } if ((dflags & ROOT_KSK_ANY) == ROOT_KSK_ANY) { keys = (view_ta != NULL) ? view_ta : global_ta; cfg_obj_log(keys, logctx, ISC_LOG_WARNING, "both initial and static entries for the " "root zone are present"); } obj = NULL; if (voptions != NULL) { (void)cfg_map_get(voptions, "dnssec-validation", &obj); } if (obj == NULL && options != NULL) { (void)cfg_map_get(options, "dnssec-validation", &obj); } if (obj != NULL && !cfg_obj_isboolean(obj)) { autovalidation = true; } tresult = check_ta_conflicts(global_ta, view_ta, global_tkeys, view_tkeys, autovalidation, mctx, logctx); if (tresult != ISC_R_SUCCESS) { result = tresult; } /* * Check options. */ if (voptions != NULL) { tresult = check_options(voptions, NULL, logctx, mctx, optlevel_view); } else { tresult = check_options(config, config, logctx, mctx, optlevel_config); } if (tresult != ISC_R_SUCCESS) { result = tresult; } tresult = check_dnstap(voptions, config, logctx); if (tresult != ISC_R_SUCCESS) { result = tresult; } tresult = check_viewacls(actx, voptions, config, logctx, mctx); if (tresult != ISC_R_SUCCESS) { result = tresult; } tresult = check_recursionacls(actx, voptions, viewname, config, logctx, mctx); if (tresult != ISC_R_SUCCESS) { result = tresult; } tresult = check_dns64(actx, voptions, config, logctx, mctx); if (tresult != ISC_R_SUCCESS) { result = tresult; } tresult = check_ratelimit(actx, voptions, config, logctx, mctx); if (tresult != ISC_R_SUCCESS) { result = tresult; } /* * Load plugins. */ if (check_plugins) { if (voptions != NULL) { (void)cfg_map_get(voptions, "plugin", &plugin_list); } else { (void)cfg_map_get(config, "plugin", &plugin_list); } } { struct check_one_plugin_data check_one_plugin_data = { .mctx = mctx, .lctx = logctx, .actx = actx, .check_result = &tresult, }; (void)cfg_pluginlist_foreach(config, plugin_list, logctx, check_one_plugin, &check_one_plugin_data); if (tresult != ISC_R_SUCCESS) { result = tresult; } } cleanup: if (symtab != NULL) { isc_symtab_destroy(&symtab); } if (actx != NULL) { cfg_aclconfctx_detach(&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, 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 bind9_check_controlskeys(const cfg_obj_t *control, const cfg_obj_t *keylist, isc_log_t *logctx) { isc_result_t result = ISC_R_SUCCESS; const cfg_obj_t *control_keylist; const cfg_listelt_t *element; const cfg_obj_t *key; const char *keyval; 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); keyval = cfg_obj_asstring(key); if (!rndckey_exists(keylist, keyval)) { cfg_obj_log(key, logctx, ISC_LOG_ERROR, "unknown key '%s'", keyval); result = ISC_R_NOTFOUND; } } 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 = NULL; 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; 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_create(mctx, &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, 0, &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 /* ifdef NEED_SECURE_DIRECTORY */ mask = (0x6 << (i * 3)); /* READ + WRITE */ #endif /* ifdef NEED_SECURE_DIRECTORY */ 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_detach(&actx); return (result); } isc_result_t bind9_check_namedconf(const cfg_obj_t *config, bool check_plugins, bool nodeprecate, isc_log_t *logctx, isc_mem_t *mctx) { const cfg_obj_t *options = NULL; const cfg_obj_t *views = NULL; const cfg_obj_t *acls = NULL; const cfg_listelt_t *velement; isc_result_t result = ISC_R_SUCCESS; isc_result_t tresult; isc_symtab_t *symtab = NULL; isc_symtab_t *files = NULL; isc_symtab_t *keydirs = NULL; isc_symtab_t *inview = NULL; static const char *builtin[] = { "localhost", "localnets", "any", "none" }; (void)cfg_map_get(config, "options", &options); if (options != NULL && check_options(options, config, logctx, mctx, optlevel_options) != 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 (bind9_check_primarylists(config, logctx, mctx) != ISC_R_SUCCESS) { result = ISC_R_FAILURE; } if (bind9_check_parentalagentlists(config, logctx, mctx) != ISC_R_SUCCESS) { result = ISC_R_FAILURE; } #if HAVE_LIBNGHTTP2 if (bind9_check_httpservers(config, logctx, mctx) != ISC_R_SUCCESS) { result = ISC_R_FAILURE; } #endif /* HAVE_LIBNGHTTP2 */ if (bind9_check_tls_definitions(config, logctx, mctx) != 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; /* * Use case insensitive comparison as not all file * systems are case sensitive. This will prevent people * using FOO.DB and foo.db on case sensitive file * systems but that shouldn't be a major issue. */ } } /* * Use case insensitive comparison as not all file systems are * case sensitive. This will prevent people using FOO.DB and foo.db * on case sensitive file systems but that shouldn't be a major issue. */ tresult = isc_symtab_create(mctx, 100, NULL, NULL, false, &files); if (tresult != ISC_R_SUCCESS) { result = tresult; goto cleanup; } tresult = isc_symtab_create(mctx, 100, freekey, mctx, false, &keydirs); if (tresult != ISC_R_SUCCESS) { result = tresult; goto cleanup; } tresult = isc_symtab_create(mctx, 100, freekey, mctx, true, &inview); if (tresult != ISC_R_SUCCESS) { result = tresult; goto cleanup; } if (views == NULL) { tresult = check_viewconf(config, NULL, NULL, dns_rdataclass_in, files, keydirs, check_plugins, nodeprecate, inview, logctx, mctx); if (result == ISC_R_SUCCESS && tresult != ISC_R_SUCCESS) { result = ISC_R_FAILURE; } } else { const cfg_obj_t *zones = NULL; const cfg_obj_t *plugins = 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; } (void)cfg_map_get(config, "plugin", &plugins); if (plugins != NULL) { cfg_obj_log(plugins, logctx, ISC_LOG_ERROR, "when using 'view' statements, " "all plugins must be defined in views"); result = ISC_R_FAILURE; } } tresult = isc_symtab_create(mctx, 100, NULL, NULL, true, &symtab); if (tresult != ISC_R_SUCCESS) { result = tresult; goto cleanup; } 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; const char *key = cfg_obj_asstring(vname); isc_symvalue_t symvalue; unsigned int symtype; tresult = ISC_R_SUCCESS; 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); } } symtype = vclass + 1; if (tresult == ISC_R_SUCCESS && symtab != NULL) { symvalue.as_cpointer = view; tresult = isc_symtab_define(symtab, key, symtype, symvalue, isc_symexists_reject); if (tresult == ISC_R_EXISTS) { const char *file; unsigned int line; RUNTIME_CHECK(isc_symtab_lookup(symtab, key, symtype, &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 (tresult != 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, key, vclass, files, keydirs, check_plugins, nodeprecate, inview, logctx, mctx); } if (tresult != ISC_R_SUCCESS) { result = ISC_R_FAILURE; } } cfg_map_get(config, "acl", &acls); if (acls != NULL) { 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 line = cfg_obj_line(acl); 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); if (file == NULL) { 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; } } } } cleanup: if (symtab != NULL) { isc_symtab_destroy(&symtab); } if (inview != NULL) { isc_symtab_destroy(&inview); } if (files != NULL) { isc_symtab_destroy(&files); } if (keydirs != NULL) { isc_symtab_destroy(&keydirs); } return (result); }