fix: usr: Fix parsing bug in remote-servers with key or tls

The :any:`remote-servers` clause enable the following pattern using a named ``server-list``:

	remote-servers a { 1.2.3.4; ... };
	remote-servers b { a key foo; };

However, such configuration was wrongly rejected, with an "unexpected token 'foo'" error. Such configuration is now accepted.

Closes #5646

Merge branch '5646-fix-named-remote-servers-key-tls' into 'main'

See merge request isc-projects/bind9!11252
This commit is contained in:
Colin Vidal 2025-11-28 09:51:24 +01:00
commit 51af07cdee
13 changed files with 759 additions and 297 deletions

View file

@ -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;
}

View file

@ -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; };

View file

@ -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; };

View file

@ -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; };

View file

@ -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; };
};

View file

@ -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

View file

@ -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; };
};

View file

@ -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; };
};

View file

@ -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; };
};

View file

@ -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)

View file

@ -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);
/*%<

View file

@ -509,11 +509,6 @@ notify_queue(dns_notify_t *notify, bool startup, bool dequeue) {
&notify->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);

View file

@ -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;
}