diff --git a/bin/named/config.c b/bin/named/config.c index 05c832798a..32918feba6 100644 --- a/bin/named/config.c +++ b/bin/named/config.c @@ -329,40 +329,232 @@ named_config_getname(isc_mem_t *mctx, const cfg_obj_t *obj, static const char *remotesnames[4] = { "remote-servers", "parental-agents", "primaries", "masters" }; +typedef struct { + isc_sockaddr_t *addrs; + size_t addrsallocated; + + isc_sockaddr_t *sources; + size_t sourcesallocated; + + dns_name_t **keys; + size_t keysallocated; + + dns_name_t **tlss; + size_t tlssallocated; + + size_t count; /* common to addrs, sources, keys and tlss */ + + const char **seen; + size_t seencount; + size_t seenallocated; +} getipandkeylist_state_t; + +static isc_result_t +getipandkeylist(in_port_t defport, in_port_t deftlsport, + const cfg_obj_t *config, const cfg_obj_t *list, + in_port_t listport, const cfg_obj_t *listkey, + const cfg_obj_t *listtls, isc_mem_t *mctx, + getipandkeylist_state_t *s) { + const cfg_obj_t *addrlist = cfg_tuple_get(list, "addresses"); + const cfg_obj_t *portobj = cfg_tuple_get(list, "port"); + const cfg_obj_t *src4obj = cfg_tuple_get(list, "source"); + const cfg_obj_t *src6obj = cfg_tuple_get(list, "source-v6"); + in_port_t port = (in_port_t)0; + isc_sockaddr_t src4; + isc_sockaddr_t src6; + isc_result_t result = ISC_R_SUCCESS; + + if (cfg_obj_isuint32(portobj)) { + uint32_t val = cfg_obj_asuint32(portobj); + if (val > UINT16_MAX) { + cfg_obj_log(portobj, ISC_LOG_ERROR, + "port '%u' out of range", val); + return ISC_R_RANGE; + } + port = (in_port_t)val; + } else if (listport > 0) { + /* + * No port in the current list, but it is a list named elsewhere + * where the port is defined, i.e: + * + * remote-servers bar { 10.53.0.4; }; + * remote-servers foo port 5555 { bar; 10.54.0.3; }; + * ^^^ + * + * The current list is the list `bar`, and the server + * `10.53.0.4` has the port `5555` defined. + */ + port = listport; + } + + if (src4obj != NULL && cfg_obj_issockaddr(src4obj)) { + src4 = *cfg_obj_assockaddr(src4obj); + } else { + isc_sockaddr_any(&src4); + } + + if (src6obj != NULL && cfg_obj_issockaddr(src6obj)) { + src6 = *cfg_obj_assockaddr(src6obj); + } else { + isc_sockaddr_any6(&src6); + } + + for (const cfg_listelt_t *element = cfg_list_first(addrlist); + element != NULL; element = cfg_list_next(element)) + { + const cfg_obj_t *addr; + const cfg_obj_t *key; + const cfg_obj_t *tls; + + skiplist: + 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 this is not an address, this is the name of a nested list, + * i.e. + * + * remote-servers nestedlist { 10.53.0.4; }; + * remote-servers list { nestedlist key foo; 10.54.0.6; }; + * ^^^^^^^^^^^^^^^^^^ + * + * We are currently in the list `list`, and `addr` is the name + * `nestedlist`, so we'll immediately recurse to process + * `nestedlist` before processing the next element of `list`. + */ + if (!cfg_obj_issockaddr(addr)) { + const char *listname = cfg_obj_asstring(addr); + const cfg_obj_t *nestedlist = NULL; + isc_result_t tresult; + + for (size_t i = 0; i < s->seencount; i++) { + if (strcasecmp(s->seen[i], listname) == 0) { + element = cfg_list_next(element); + goto skiplist; + } + } + + grow_array(mctx, s->seen, s->seencount, + s->seenallocated); + s->seen[s->seencount] = listname; + + for (size_t i = 0; i < ARRAY_SIZE(remotesnames); i++) { + tresult = named_config_getremotesdef( + config, remotesnames[i], listname, + &nestedlist); + if (tresult == ISC_R_SUCCESS) { + break; + } + } + + if (tresult != ISC_R_SUCCESS) { + cfg_obj_log(addr, ISC_LOG_ERROR, + "remote-servers \"%s\" not found", + listname); + return tresult; + } + + result = getipandkeylist(defport, deftlsport, config, + nestedlist, port, key, tls, + mctx, s); + if (result != ISC_R_SUCCESS) { + goto out; + } + continue; + } + + grow_array(mctx, s->addrs, s->count, s->addrsallocated); + grow_array(mctx, s->keys, s->count, s->keysallocated); + grow_array(mctx, s->tlss, s->count, s->tlssallocated); + grow_array(mctx, s->sources, s->count, s->sourcesallocated); + + s->addrs[s->count] = *cfg_obj_assockaddr(addr); + + result = named_config_getname(mctx, key, &s->keys[s->count]); + if (result != ISC_R_SUCCESS) { + goto out; + } + + /* + * The `key` is not provided for this address, so, if we're + * inside a named list, get the `key` provided at the point the + * list is used. + */ + if (s->keys[s->count] == NULL && listkey != NULL) { + result = named_config_getname(mctx, listkey, + &s->keys[s->count]); + if (result != ISC_R_SUCCESS) { + goto out; + } + } + + result = named_config_getname(mctx, tls, &s->tlss[s->count]); + if (result != ISC_R_SUCCESS) { + goto out; + } + + /* + * The `tls` is not provided for this address, so, if we're + * inside a named list, get the `tls` provided at the point the + * named list is used. + */ + if (s->tlss[s->count] == NULL && listtls != NULL) { + result = named_config_getname(mctx, listtls, + &s->tlss[s->count]); + } + + /* If the port is unset, take it from one of the upper levels */ + if (isc_sockaddr_getport(&s->addrs[s->count]) == 0) { + in_port_t addr_port = port; + + /* If unset, use the default port or tls-port */ + if (addr_port == 0) { + if (s->tlss[s->count] != NULL) { + addr_port = deftlsport; + } else { + addr_port = defport; + } + } + + isc_sockaddr_setport(&s->addrs[s->count], addr_port); + } + + switch (isc_sockaddr_pf(&s->addrs[s->count])) { + case PF_INET: + s->sources[s->count] = src4; + break; + case PF_INET6: + s->sources[s->count] = src6; + break; + default: + result = ISC_R_NOTIMPLEMENTED; + goto out; + } + + s->count++; + } + +out: + if (result != ISC_R_SUCCESS) { + /* + * Reaching this point without success means we were in the + * middle of adding a new entry, so it needs to be counted for + * correctly free `s.keys` and `s.tlss` (as they potentially + * added a new element right before something fails) + */ + s->count++; + } + return result; +} + isc_result_t named_config_getipandkeylist(const cfg_obj_t *config, const cfg_obj_t *list, isc_mem_t *mctx, dns_ipkeylist_t *ipkl) { - uint32_t addrcount = 0, srccount = 0; - uint32_t keycount = 0, tlscount = 0; - uint32_t listcount = 0, l = 0, i = 0; - uint32_t stackcount = 0, pushed = 0; isc_result_t result; - const cfg_listelt_t *element; - const cfg_obj_t *addrlist; - const cfg_obj_t *portobj; - const cfg_obj_t *src4obj; - const cfg_obj_t *src6obj; - in_port_t port = (in_port_t)0; in_port_t def_port; in_port_t def_tlsport; - isc_sockaddr_t src4; - isc_sockaddr_t src6; - isc_sockaddr_t *addrs = NULL; - isc_sockaddr_t *sources = NULL; - dns_name_t **keys = NULL; - dns_name_t **tlss = NULL; - struct { - const char *name; - in_port_t port; - isc_sockaddr_t *src4s; - isc_sockaddr_t *src6s; - } *lists = NULL; - struct { - const cfg_listelt_t *element; - in_port_t port; - isc_sockaddr_t src4; - isc_sockaddr_t src6; - } *stack = NULL; REQUIRE(ipkl != NULL); REQUIRE(ipkl->count == 0); @@ -385,222 +577,83 @@ named_config_getipandkeylist(const cfg_obj_t *config, const cfg_obj_t *list, goto cleanup; } -newlist: - addrlist = cfg_tuple_get(list, "addresses"); - portobj = cfg_tuple_get(list, "port"); - src4obj = cfg_tuple_get(list, "source"); - src6obj = cfg_tuple_get(list, "source-v6"); - - if (cfg_obj_isuint32(portobj)) { - uint32_t val = cfg_obj_asuint32(portobj); - if (val > UINT16_MAX) { - cfg_obj_log(portobj, ISC_LOG_ERROR, - "port '%u' out of range", val); - result = ISC_R_RANGE; - goto cleanup; - } - port = (in_port_t)val; + /* + * Process the (nested) list(s). + */ + getipandkeylist_state_t s = {}; + result = getipandkeylist(def_port, def_tlsport, config, list, + (in_port_t)0, NULL, NULL, mctx, &s); + if (result != ISC_R_SUCCESS) { + goto cleanup; } - if (src4obj != NULL && cfg_obj_issockaddr(src4obj)) { - src4 = *cfg_obj_assockaddr(src4obj); - } else { - isc_sockaddr_any(&src4); + shrink_array(mctx, s.addrs, s.count, s.addrsallocated); + shrink_array(mctx, s.keys, s.count, s.keysallocated); + shrink_array(mctx, s.tlss, s.count, s.tlssallocated); + shrink_array(mctx, s.sources, s.count, s.sourcesallocated); + + ipkl->addrs = s.addrs; + ipkl->keys = s.keys; + ipkl->tlss = s.tlss; + ipkl->sources = s.sources; + ipkl->count = s.count; + + INSIST(s.addrsallocated == s.keysallocated); + INSIST(s.addrsallocated == s.tlssallocated); + INSIST(s.addrsallocated == s.sourcesallocated); + ipkl->allocated = s.addrsallocated; + + if (s.seen != NULL) { + /* + * `s.seen` is not shrinked (no point, as it's deleted right + * away anyway), so we need to use `s.seenallocated` to + * correctly free the array. + */ + isc_mem_cput(mctx, s.seen, s.seenallocated, sizeof(s.seen[0])); } - if (src6obj != NULL && cfg_obj_issockaddr(src6obj)) { - src6 = *cfg_obj_assockaddr(src6obj); - } else { - isc_sockaddr_any6(&src6); - } - - element = cfg_list_first(addrlist); -resume: - for (; element != NULL; element = cfg_list_next(element)) { - 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)) { - const char *listname = cfg_obj_asstring(addr); - isc_result_t tresult; - uint32_t j; - - /* Grow lists? */ - grow_array(mctx, lists, l, listcount); - - /* Seen? */ - for (j = 0; j < l; j++) { - if (strcasecmp(lists[j].name, listname) == 0) { - break; - } - } - if (j < l) { - continue; - } - list = NULL; - tresult = ISC_R_NOTFOUND; - for (size_t n = 0; n < ARRAY_SIZE(remotesnames); n++) { - tresult = named_config_getremotesdef( - config, remotesnames[n], listname, - &list); - if (tresult == ISC_R_SUCCESS) { - break; - } - } - if (tresult == ISC_R_NOTFOUND) { - cfg_obj_log(addr, ISC_LOG_ERROR, - "remote-servers \"%s\" not found", - listname); - } - if (tresult != ISC_R_SUCCESS) { - result = tresult; - goto cleanup; - } - lists[l++].name = listname; - /* Grow stack? */ - grow_array(mctx, stack, pushed, stackcount); - /* - * We want to resume processing this list on the - * next element. - */ - stack[pushed].element = cfg_list_next(element); - stack[pushed].port = port; - stack[pushed].src4 = src4; - stack[pushed].src6 = src6; - pushed++; - goto newlist; - } - - grow_array(mctx, addrs, i, addrcount); - grow_array(mctx, keys, i, keycount); - grow_array(mctx, tlss, i, tlscount); - grow_array(mctx, sources, i, srccount); - - addrs[i] = *cfg_obj_assockaddr(addr); - - result = named_config_getname(mctx, key, &keys[i]); - if (result != ISC_R_SUCCESS) { - i++; /* Increment here so that cleanup on error works. - */ - goto cleanup; - } - - result = named_config_getname(mctx, tls, &tlss[i]); - if (result != ISC_R_SUCCESS) { - i++; /* Increment here so that cleanup on error works. - */ - goto cleanup; - } - - /* If the port is unset, take it from one of the upper levels */ - if (isc_sockaddr_getport(&addrs[i]) == 0) { - in_port_t addr_port = port; - - /* If unset, use the default port or tls-port */ - if (addr_port == 0) { - if (tlss[i] != NULL) { - addr_port = def_tlsport; - } else { - addr_port = def_port; - } - } - - isc_sockaddr_setport(&addrs[i], addr_port); - } - - switch (isc_sockaddr_pf(&addrs[i])) { - case PF_INET: - sources[i] = src4; - break; - case PF_INET6: - sources[i] = src6; - break; - default: - i++; /* Increment here so that cleanup on error works. - */ - result = ISC_R_NOTIMPLEMENTED; - goto cleanup; - } - - i++; - } - if (pushed != 0) { - pushed--; - element = stack[pushed].element; - port = stack[pushed].port; - src4 = stack[pushed].src4; - src6 = stack[pushed].src6; - goto resume; - } - - shrink_array(mctx, addrs, i, addrcount); - shrink_array(mctx, keys, i, keycount); - shrink_array(mctx, tlss, i, tlscount); - shrink_array(mctx, sources, i, srccount); - - if (lists != NULL) { - isc_mem_cput(mctx, lists, listcount, sizeof(lists[0])); - } - if (stack != NULL) { - isc_mem_cput(mctx, stack, stackcount, sizeof(stack[0])); - } - - INSIST(keycount == addrcount); - INSIST(tlscount == addrcount); - INSIST(srccount == addrcount); - - ipkl->addrs = addrs; - ipkl->keys = keys; - ipkl->tlss = tlss; - ipkl->sources = sources; - ipkl->count = addrcount; - ipkl->allocated = addrcount; - return ISC_R_SUCCESS; cleanup: - if (addrs != NULL) { - isc_mem_cput(mctx, addrs, addrcount, sizeof(addrs[0])); + /* + * Because we didn't shrinked the array back in this path, we need to + * use `s.*allocated` to correctly free the allocated arrays. + */ + if (s.addrs != NULL) { + isc_mem_cput(mctx, s.addrs, s.count, sizeof(s.addrs[0])); } - if (keys != NULL) { - for (size_t j = 0; j < i; j++) { - if (keys[j] == NULL) { + if (s.keys != NULL) { + for (size_t i = 0; i < s.count; i++) { + if (s.keys[i] == NULL) { continue; } - if (dns_name_dynamic(keys[j])) { - dns_name_free(keys[j], mctx); + if (dns_name_dynamic(s.keys[i])) { + dns_name_free(s.keys[i], mctx); } - isc_mem_put(mctx, keys[j], sizeof(*keys[j])); + isc_mem_put(mctx, s.keys[i], sizeof(*s.keys[i])); } - isc_mem_cput(mctx, keys, keycount, sizeof(keys[0])); + isc_mem_cput(mctx, s.keys, s.keysallocated, sizeof(s.keys[0])); } - if (tlss != NULL) { - for (size_t j = 0; j < i; j++) { - if (tlss[j] == NULL) { + if (s.tlss != NULL) { + for (size_t i = 0; i < s.count; i++) { + if (s.tlss[i] == NULL) { continue; } - if (dns_name_dynamic(tlss[j])) { - dns_name_free(tlss[j], mctx); + if (dns_name_dynamic(s.tlss[i])) { + dns_name_free(s.tlss[i], mctx); } - isc_mem_put(mctx, tlss[j], sizeof(*tlss[j])); + isc_mem_put(mctx, s.tlss[i], sizeof(*s.tlss[i])); } - isc_mem_cput(mctx, tlss, tlscount, sizeof(tlss[0])); + isc_mem_cput(mctx, s.tlss, s.tlssallocated, sizeof(s.tlss[0])); } - if (sources != NULL) { - isc_mem_cput(mctx, sources, srccount, sizeof(sources[0])); + if (s.sources != NULL) { + isc_mem_cput(mctx, s.sources, s.sourcesallocated, + sizeof(s.sources[0])); } - if (lists != NULL) { - isc_mem_cput(mctx, lists, listcount, sizeof(lists[0])); - } - if (stack != NULL) { - isc_mem_cput(mctx, stack, stackcount, sizeof(stack[0])); + if (s.seen != NULL) { + isc_mem_cput(mctx, s.seen, s.seenallocated, sizeof(s.seen[0])); } + return result; } diff --git a/bin/tests/system/checkconf/bad-remote-servers-key.conf b/bin/tests/system/checkconf/bad-remote-servers-key.conf new file mode 100644 index 0000000000..d4963a87f8 --- /dev/null +++ b/bin/tests/system/checkconf/bad-remote-servers-key.conf @@ -0,0 +1,15 @@ +/* + * 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. + */ + +remote-servers a { 1.2.3.4; }; +remote-servers d { a key foo; }; diff --git a/bin/tests/system/checkconf/bad-remote-servers-tls.conf b/bin/tests/system/checkconf/bad-remote-servers-tls.conf new file mode 100644 index 0000000000..461975da59 --- /dev/null +++ b/bin/tests/system/checkconf/bad-remote-servers-tls.conf @@ -0,0 +1,15 @@ +/* + * 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. + */ + +remote-servers a { 1.2.3.4; }; +remote-servers d { a tls foo; }; diff --git a/bin/tests/system/checkconf/good-remote-servers-named.conf b/bin/tests/system/checkconf/good-remote-servers-named.conf new file mode 100644 index 0000000000..f7eadc587f --- /dev/null +++ b/bin/tests/system/checkconf/good-remote-servers-named.conf @@ -0,0 +1,27 @@ +/* + * 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. + */ + +key foo { + algorithm hmac-sha256; + secret "9999abcd8765"; +}; + +tls bar { +}; + +remote-servers a { 1.2.3.4; }; +remote-servers b { 1.2.3.4; }; +remote-servers c { 1.2.3.4; 5.6.7.8; ::1; }; +remote-servers d { a key foo; b tls bar; c key foo tls bar; }; +remote-servers e { a key foo.; }; +remote-servers f { b tls bar; }; diff --git a/bin/tests/system/xfer-servers-list/ns1/named.conf.j2 b/bin/tests/system/xfer-servers-list/ns1/named.conf.j2 new file mode 100644 index 0000000000..8400ca2458 --- /dev/null +++ b/bin/tests/system/xfer-servers-list/ns1/named.conf.j2 @@ -0,0 +1,77 @@ +/* + * 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. + */ + +options { + listen-on port @PORT@ { 10.53.0.1; }; + transfer-source 10.53.0.1; + pid-file "named.pid"; + recursion no; + /* + * Notifications are sent from 10.53.1.1. This, the `notify + * explicit` and `also-notify` below, enables us to use specific TSIG + * keys for notifications for each secondary server instead of using + * the xfer TSIG key. + */ + notify-source 10.53.1.1; + /* + * The test. zone doesn't specify other NS, however this makes it clear + * this NS must only notify from also-notify list. + */ + notify explicit; +}; + +key xfrkey { + algorithm @DEFAULT_HMAC@; + secret "9999abcd8765"; +}; + +key notifykey2 { + algorithm @DEFAULT_HMAC@; + secret "2222abcd8765"; +}; + +key notifykey3 { + algorithm @DEFAULT_HMAC@; + secret "3333abcd8765"; +}; + +key notifykey4 { + algorithm @DEFAULT_HMAC@; + secret "4444abcd8765"; +}; + +remote-servers secondariesbis { + 10.53.0.4 port @PORT@; /* gets notifykey4 */ +}; + +remote-servers secondaries { + 10.53.0.3 port @PORT@ key notifykey3; + secondariesbis key notifykey4; + 10.53.0.2 port @PORT@; /* gets notifykey2 */ +}; + +zone "test" { + type primary; + allow-transfer { key xfrkey; }; + also-notify { secondaries key notifykey2; }; + file "test.db"; +}; + +key rndc_key { + secret "1234abcd8765"; + algorithm @DEFAULT_HMAC@; +}; + +controls { + inet 10.53.0.1 port @CONTROLPORT@ allow { any; } keys { rndc_key; }; +}; diff --git a/bin/tests/system/xfer-servers-list/ns1/test.db.j2 b/bin/tests/system/xfer-servers-list/ns1/test.db.j2 new file mode 100644 index 0000000000..569a54f164 --- /dev/null +++ b/bin/tests/system/xfer-servers-list/ns1/test.db.j2 @@ -0,0 +1,23 @@ +; 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. + +{% set serial = serial | default(1) %} + +$TTL 60 +test. IN SOA ns.test. op.test. ( + @serial@ ; serial + 100 ; refresh + 100 ; retry + 300 ; expire + 60 ; minimum + ) +test. NS ns.test. +ns.test. A 10.53.0.1 diff --git a/bin/tests/system/xfer-servers-list/ns2/named.conf.j2 b/bin/tests/system/xfer-servers-list/ns2/named.conf.j2 new file mode 100644 index 0000000000..22b9595932 --- /dev/null +++ b/bin/tests/system/xfer-servers-list/ns2/named.conf.j2 @@ -0,0 +1,48 @@ +/* + * 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. + */ + +options { + listen-on port @PORT@ { 10.53.0.2; }; + transfer-source 10.53.0.2; + pid-file "named.pid"; + recursion no; +}; + +key xfrkey { + algorithm @DEFAULT_HMAC@; + secret "9999abcd8765"; +}; + +key notifykey2 { + algorithm @DEFAULT_HMAC@; + secret "2222abcd8765"; +}; + +zone "test" { + /* + * Notify comes from a different IP address than the primary listening + * address, and with a different key. + */ + allow-notify { key notifykey2; }; + type secondary; + primaries { 10.53.0.1 port @PORT@ key xfrkey; }; +}; + +key rndc_key { + secret "1234abcd8765"; + algorithm @DEFAULT_HMAC@; +}; + +controls { + inet 10.53.0.2 port @CONTROLPORT@ allow { any; } keys { rndc_key; }; +}; diff --git a/bin/tests/system/xfer-servers-list/ns3/named.conf.j2 b/bin/tests/system/xfer-servers-list/ns3/named.conf.j2 new file mode 100644 index 0000000000..b17757e135 --- /dev/null +++ b/bin/tests/system/xfer-servers-list/ns3/named.conf.j2 @@ -0,0 +1,48 @@ +/* + * 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. + */ + +options { + listen-on port @PORT@ { 10.53.0.3; }; + transfer-source 10.53.0.3; + pid-file "named.pid"; + recursion no; +}; + +key xfrkey { + algorithm @DEFAULT_HMAC@; + secret "9999abcd8765"; +}; + +key notifykey3 { + algorithm @DEFAULT_HMAC@; + secret "3333abcd8765"; +}; + +zone "test" { + /* + * Notify comes from a different IP address than the primary listening + * address, and with a different key. + */ + allow-notify { key notifykey3; }; + type secondary; + primaries { 10.53.0.1 port @PORT@ key xfrkey; }; +}; + +key rndc_key { + secret "1234abcd8765"; + algorithm @DEFAULT_HMAC@; +}; + +controls { + inet 10.53.0.3 port @CONTROLPORT@ allow { any; } keys { rndc_key; }; +}; diff --git a/bin/tests/system/xfer-servers-list/ns4/named.conf.j2 b/bin/tests/system/xfer-servers-list/ns4/named.conf.j2 new file mode 100644 index 0000000000..fce6e301ba --- /dev/null +++ b/bin/tests/system/xfer-servers-list/ns4/named.conf.j2 @@ -0,0 +1,48 @@ +/* + * 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. + */ + +options { + listen-on port @PORT@ { 10.53.0.4; }; + transfer-source 10.53.0.4; + pid-file "named.pid"; + recursion no; +}; + +key xfrkey { + algorithm @DEFAULT_HMAC@; + secret "9999abcd8765"; +}; + +key notifykey4 { + algorithm @DEFAULT_HMAC@; + secret "4444abcd8765"; +}; + +zone "test" { + /* + * Notify comes from a different IP address than the primary listening + * address, and with a different key. + */ + allow-notify { key notifykey4; }; + type secondary; + primaries { 10.53.0.1 port @PORT@ key xfrkey; }; +}; + +key rndc_key { + secret "1234abcd8765"; + algorithm @DEFAULT_HMAC@; +}; + +controls { + inet 10.53.0.4 port @CONTROLPORT@ allow { any; } keys { rndc_key; }; +}; diff --git a/bin/tests/system/xfer-servers-list/tests_xfer_servers_list.py b/bin/tests/system/xfer-servers-list/tests_xfer_servers_list.py new file mode 100644 index 0000000000..612be36b1d --- /dev/null +++ b/bin/tests/system/xfer-servers-list/tests_xfer_servers_list.py @@ -0,0 +1,68 @@ +# 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. + +import re + +import isctest + + +def check_soa(ns, serial): + msg = isctest.query.create("test.", "SOA") + res = isctest.query.udp(msg, ns.ip) + isctest.check.noerror(res) + assert len(res.answer) == 1 + assert ( + res.answer[0].to_text() + == f"test. 60 IN SOA ns.test. op.test. {serial} 100 100 300 60" + ) + + +def wait_for_initial_xfrin(ns): + with ns.watch_log_from_start() as watcher: + watcher.wait_for_line("Transfer status: success") + check_soa(ns, 1) + + +def wait_for_sending_notify(ns1, ns, key_name): + pattern = re.compile( + f"zone test/IN: sending notify to {ns.ip}#[0-9]+ : TSIG \\({key_name}\\)" + ) + with ns1.watch_log_from_start() as watcher: + watcher.wait_for_line(pattern) + + +def test_xfer_servers_list(ns1, ns2, ns3, ns4, templates): + # First, wait for ns2, ns3 and ns4 to xfrin foo.fr and answer it + wait_for_initial_xfrin(ns2) + wait_for_initial_xfrin(ns3) + wait_for_initial_xfrin(ns4) + + # ns1 initially notifies the secondaries using the respectively configured keys + # - 10.53.0.2 has the key defined where `secondaries` is used + # - 10.53.0.3 has the key directly after its IP address + # - 10.53.0.4 has the key defined where `secondariesbis` is used + # (inside `secondaries`), so it uses this one instead of the one + # defined where `secondaries` is used. + # Because the order notification are sent doesn't matter here, we can't use wait_for_sequence + seq = [(ns2, "notifykey2"), (ns3, "notifykey3"), (ns4, "notifykey4")] + for ns, key_name in seq: + wait_for_sending_notify(ns1, ns, key_name) + + # Then, ns1 update foo.fr. It notifies ns2, ns3 and ns4 about it + templates.render("ns1/test.db", {"serial": 2}) + with ns2.watch_log_from_here() as ns2_watcher, ns3.watch_log_from_here() as ns3_watcher, ns4.watch_log_from_here() as ns4_watcher: + ns1.rndc("reload") + ns2_watcher.wait_for_line("Transfer status: success") + ns3_watcher.wait_for_line("Transfer status: success") + ns4_watcher.wait_for_line("Transfer status: success") + check_soa(ns2, 2) + check_soa(ns3, 2) + check_soa(ns4, 2) diff --git a/lib/dns/include/dns/notify.h b/lib/dns/include/dns/notify.h index 2db6028fad..11aaffc66a 100644 --- a/lib/dns/include/dns/notify.h +++ b/lib/dns/include/dns/notify.h @@ -110,15 +110,6 @@ dns_notify_queue(dns_notify_t *notify, bool startup); * 'notify' is a valid notify. */ -isc_result_t -dns_notify_dequeue(dns_notify_t *notify, bool startup); -/*%< - * Dequeue notify. - * - * Requires: - * 'notify' is a valid notify. - */ - void dns_notify_find_address(dns_notify_t *notify); /*%< diff --git a/lib/dns/notify.c b/lib/dns/notify.c index 371b33482e..ed47a92447 100644 --- a/lib/dns/notify.c +++ b/lib/dns/notify.c @@ -509,11 +509,6 @@ notify_queue(dns_notify_t *notify, bool startup, bool dequeue) { ¬ify->rlevent); } -isc_result_t -dns_notify_dequeue(dns_notify_t *notify, bool startup) { - return notify_queue(notify, startup, true); -} - isc_result_t dns_notify_queue(dns_notify_t *notify, bool startup) { return notify_queue(notify, startup, false); diff --git a/lib/isccfg/check.c b/lib/isccfg/check.c index f8c1697457..59d73b3030 100644 --- a/lib/isccfg/check.c +++ b/lib/isccfg/check.c @@ -84,6 +84,9 @@ keydirexist(const cfg_obj_t *zcgf, const char *optname, dns_name_t *zname, static const cfg_obj_t * find_maplist(const cfg_obj_t *config, const char *listname, const char *name); +static isc_result_t +validate_remotes(const cfg_obj_t *obj, const cfg_obj_t *config, + uint32_t *countp, isc_mem_t *mctx); static void freekey(char *key, unsigned int type, isc_symvalue_t value, void *userarg) { UNUSED(type); @@ -2084,6 +2087,12 @@ check_remoteserverlist(const cfg_obj_t *cctx, const char *list, result = tresult; break; } + + uint32_t dummy = 0; + result = validate_remotes(obj, cctx, &dummy, mctx); + if (result != ISC_R_SUCCESS) { + break; + } } return result; } @@ -2414,6 +2423,96 @@ get_remoteservers_def(const char *name, const cfg_obj_t *cctx, return get_remotes(cctx, "masters", name, ret); } +static isc_result_t +validate_remotes_key(const cfg_obj_t *config, const cfg_obj_t *key) { + isc_result_t result = ISC_R_SUCCESS; + + if (cfg_obj_isstring(key)) { + const cfg_obj_t *keys = NULL; + const char *str = cfg_obj_asstring(key); + dns_fixedname_t fname; + dns_name_t *nm = dns_fixedname_initname(&fname); + bool found = false; + + result = dns_name_fromstring(nm, str, dns_rootname, 0, NULL); + if (result != ISC_R_SUCCESS) { + cfg_obj_log(key, ISC_LOG_ERROR, + "'%s' is not a valid name", str); + } + + result = cfg_map_get(config, "key", &keys); + CFG_LIST_FOREACH(keys, elt) { + /* + * `key` are normalized TSIG which must be identified by + * a domain name, so this is needed. Otherwise, with a + * raw string comparison we could have: + * + * remote-servers { x.y.z.s key foo }; + * key foo. { + * ... + * }; + * + * This would otherwise fail, even though the key + * exists. + */ + const cfg_obj_t *foundkey = cfg_listelt_value(elt); + const char *foundkeystr = + cfg_obj_asstring(cfg_map_getname(foundkey)); + dns_fixedname_t foundfname; + dns_name_t *foundkeyname = + dns_fixedname_initname(&foundfname); + + result = dns_name_fromstring(foundkeyname, foundkeystr, + dns_rootname, 0, NULL); + + if (dns_name_equal(nm, foundkeyname)) { + found = true; + break; + } + } + + if (!found) { + cfg_obj_log(key, ISC_LOG_ERROR, + "key '%s' is not defined", + cfg_obj_asstring(key)); + result = ISC_R_FAILURE; + } + } + + return result; +} + +static isc_result_t +validate_remotes_tls(const cfg_obj_t *config, const cfg_obj_t *tls) { + isc_result_t result = ISC_R_SUCCESS; + + if (cfg_obj_isstring(tls)) { + const char *str = cfg_obj_asstring(tls); + dns_fixedname_t fname; + dns_name_t *nm = dns_fixedname_initname(&fname); + + result = dns_name_fromstring(nm, str, dns_rootname, 0, NULL); + if (result != ISC_R_SUCCESS) { + cfg_obj_log(tls, ISC_LOG_ERROR, + "'%s' is not a valid name", str); + } + + if (strcasecmp(str, "ephemeral") != 0) { + const cfg_obj_t *tlsmap = NULL; + + tlsmap = find_maplist(config, "tls", str); + if (tlsmap == NULL) { + cfg_obj_log(tls, ISC_LOG_ERROR, + "tls '%s' is not defined", + cfg_obj_asstring(tls)); + result = ISC_R_FAILURE; + } + } + } + + return result; +} + static isc_result_t validate_remotes(const cfg_obj_t *obj, const cfg_obj_t *config, uint32_t *countp, isc_mem_t *mctx) { @@ -2445,69 +2544,21 @@ resume: key = cfg_tuple_get(cfg_listelt_value(element), "key"); tls = cfg_tuple_get(cfg_listelt_value(element), "tls"); + result = validate_remotes_key(config, key); + if (result != ISC_R_SUCCESS) { + goto out; + } + + result = validate_remotes_tls(config, tls); + if (result != ISC_R_SUCCESS) { + goto out; + } + 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, dns_rootname, 0, NULL); - if (tresult != ISC_R_SUCCESS) { - cfg_obj_log(key, 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, dns_rootname, 0, NULL); - if (tresult != ISC_R_SUCCESS) { - cfg_obj_log(tls, 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, 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, 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, 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, @@ -2539,11 +2590,14 @@ resume: element = stack[--pushed]; goto resume; } + + *countp = count; + +out: if (stack != NULL) { isc_mem_cput(mctx, stack, stackcount, sizeof(*stack)); } isc_symtab_destroy(&symtab); - *countp = count; return result; }