From 60b129da253d72fd4b65d93f8590789ab92b8120 Mon Sep 17 00:00:00 2001 From: Evan Hunt Date: Sun, 13 Apr 2025 00:28:49 -0700 Subject: [PATCH 1/3] Add zone "initial-file" option When loading a primary zone for the first time, if the zonefile does not exist but an "initial-file" option has been set, then a new file will be copied into place from the path specified by "initial-file". This can be used to simplify the process of adding new zones. For instance, a template zonefile could be used by running: $ rndc addzone example.com \ '{ type primary; file "example.db"; initial-file "template.db"; };' --- bin/check/check-tool.c | 2 +- bin/named/server.c | 2 +- bin/named/zoneconf.c | 16 ++++- bin/tests/system/isctest/check.py | 5 ++ bin/tests/system/masterfile/ns2/named.conf.j2 | 12 ++++ bin/tests/system/masterfile/setup.sh | 19 ++++++ .../system/masterfile/tests_masterfile.py | 21 +++++++ doc/arm/reference.rst | 26 ++++++++ doc/misc/primary.zoneopt | 1 + fuzz/dns_message_checksig.c | 2 +- lib/dns/include/dns/zone.h | 10 +-- lib/dns/zone.c | 62 ++++++++++++++++++- lib/isccfg/namedconf.c | 1 + tests/dns/nsec3param_test.c | 3 +- tests/dns/zt_test.c | 8 +-- tests/libtest/ns.c | 2 +- 16 files changed, 174 insertions(+), 18 deletions(-) create mode 100644 bin/tests/system/masterfile/setup.sh diff --git a/bin/check/check-tool.c b/bin/check/check-tool.c index 13c5ec7593..3bd232c8be 100644 --- a/bin/check/check-tool.c +++ b/bin/check/check-tool.c @@ -656,7 +656,7 @@ load_zone(isc_mem_t *mctx, const char *zonename, const char *filename, dns_zone_setstream(zone, stdin, fileformat, &dns_master_style_default); } else { - dns_zone_setfile(zone, filename, fileformat, + dns_zone_setfile(zone, filename, NULL, fileformat, &dns_master_style_default); } if (journal != NULL) { diff --git a/bin/named/server.c b/bin/named/server.c index e539d77867..7c35cc4a51 100644 --- a/bin/named/server.c +++ b/bin/named/server.c @@ -6599,7 +6599,7 @@ add_keydata_zone(dns_view_t *view, const char *directory, isc_mem_t *mctx) { CHECK(isc_file_sanitize( directory, defaultview ? "managed-keys" : view->name, defaultview ? "bind" : "mkeys", filename, sizeof(filename))); - dns_zone_setfile(zone, filename, dns_masterformat_text, + dns_zone_setfile(zone, filename, NULL, dns_masterformat_text, &dns_master_style_default); dns_zone_setview(zone, view); diff --git a/bin/named/zoneconf.c b/bin/named/zoneconf.c index 899d83bbcd..ca24ec9851 100644 --- a/bin/named/zoneconf.c +++ b/bin/named/zoneconf.c @@ -878,6 +878,7 @@ named_zone_configure(const cfg_obj_t *config, const cfg_obj_t *vconfig, const cfg_obj_t *options = NULL; const cfg_obj_t *obj; const char *filename = NULL; + const char *initial_file = NULL; const char *kaspname = NULL; const char *dupcheck; dns_checkdstype_t checkdstype = dns_checkdstype_yes; @@ -996,6 +997,12 @@ named_zone_configure(const cfg_obj_t *config, const cfg_obj_t *vconfig, filename = cfg_obj_asstring(obj); } + obj = NULL; + result = cfg_map_get(zoptions, "initial-file", &obj); + if (result == ISC_R_SUCCESS) { + initial_file = cfg_obj_asstring(obj); + } + if (ztype == dns_zone_secondary || ztype == dns_zone_mirror) { masterformat = dns_masterformat_raw; } else { @@ -1053,14 +1060,17 @@ named_zone_configure(const cfg_obj_t *config, const cfg_obj_t *vconfig, size_t signedlen = strlen(filename) + sizeof(SIGNED); char *signedname; - dns_zone_setfile(raw, filename, masterformat, masterstyle); + dns_zone_setfile(raw, filename, initial_file, masterformat, + masterstyle); signedname = isc_mem_get(mctx, signedlen); (void)snprintf(signedname, signedlen, "%s" SIGNED, filename); - dns_zone_setfile(zone, signedname, dns_masterformat_raw, NULL); + dns_zone_setfile(zone, signedname, NULL, dns_masterformat_raw, + NULL); isc_mem_put(mctx, signedname, signedlen); } else { - dns_zone_setfile(zone, filename, masterformat, masterstyle); + dns_zone_setfile(zone, filename, initial_file, masterformat, + masterstyle); } obj = NULL; diff --git a/bin/tests/system/isctest/check.py b/bin/tests/system/isctest/check.py index b35dfe848e..5c78ba7347 100644 --- a/bin/tests/system/isctest/check.py +++ b/bin/tests/system/isctest/check.py @@ -11,6 +11,7 @@ import difflib import shutil +import os from typing import Optional import dns.rcode @@ -150,3 +151,7 @@ def file_contents_equal(file1, file2): assert not line.startswith("+ ") and not line.startswith( "- " ), f'file contents of "{file1}" and "{file2}" differ' + + +def file_empty(file): + assert os.path.getsize(file) == 0 diff --git a/bin/tests/system/masterfile/ns2/named.conf.j2 b/bin/tests/system/masterfile/ns2/named.conf.j2 index 6333b05f40..50a1d2e2d9 100644 --- a/bin/tests/system/masterfile/ns2/named.conf.j2 +++ b/bin/tests/system/masterfile/ns2/named.conf.j2 @@ -43,3 +43,15 @@ zone "missing" { type primary; file "missing.db"; }; + +zone "initial" { + type primary; + file "copied.db"; + initial-file "example.db"; +}; + +zone "present" { + type primary; + file "present.db"; + initial-file "example.db"; +}; diff --git a/bin/tests/system/masterfile/setup.sh b/bin/tests/system/masterfile/setup.sh new file mode 100644 index 0000000000..9f57f5a9b3 --- /dev/null +++ b/bin/tests/system/masterfile/setup.sh @@ -0,0 +1,19 @@ +#!/bin/sh -e + +# 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. + +# shellcheck source=conf.sh +. ../conf.sh + +set -e + +touch ns2/present.db diff --git a/bin/tests/system/masterfile/tests_masterfile.py b/bin/tests/system/masterfile/tests_masterfile.py index 9aaaa769a5..8b25f6db07 100644 --- a/bin/tests/system/masterfile/tests_masterfile.py +++ b/bin/tests/system/masterfile/tests_masterfile.py @@ -15,6 +15,9 @@ import dns.message import dns.zone import isctest +import pytest + +pytestmark = pytest.mark.extra_artifacts(["ns2/copied.db", "ns2/present.db"]) def test_masterfile_include_semantics(): @@ -87,6 +90,24 @@ example. 300 IN SOA mname1. . 2010042407 20 20 1814400 3600 isctest.check.rrsets_equal(res_soa.answer, expected.answer, compare_ttl=True) +def test_masterfile_initial_file(): + """Test zone configuration with initial template files""" + msg_soa = dns.message.make_query("initial.", "SOA") + res_soa = isctest.query.tcp(msg_soa, "10.53.0.2") + expected_soa_rr = """;ANSWER +initial. 300 IN SOA mname1. . 2010042407 20 20 1814400 3600 +""" + expected = dns.message.from_text(expected_soa_rr) + isctest.check.rrsets_equal(res_soa.answer, expected.answer) + isctest.check.file_contents_equal("ns2/example.db", "ns2/copied.db") + + # the 'present.db' file already existed and shouldn't load + msg_soa = dns.message.make_query("present.", "SOA") + res_soa = isctest.query.tcp(msg_soa, "10.53.0.2") + isctest.check.servfail(res_soa) + isctest.check.file_empty("ns2/present.db") + + def test_masterfile_missing_master_file_servfail(): """Test nameserver returning SERVFAIL for a missing master file""" msg_soa = dns.message.make_query("missing.", "SOA") diff --git a/doc/arm/reference.rst b/doc/arm/reference.rst index efdcdc5b0a..0c81a56800 100644 --- a/doc/arm/reference.rst +++ b/doc/arm/reference.rst @@ -7149,6 +7149,32 @@ Zone Options specified in a zone of type :any:`forward`, no forwarding is done for the zone and the global options are not used. +.. namedconf:statement:: initial-file + :tags: zone + :short: Specifies a file with the initial contents of a newly created zone. + + When a :any:`primary ` zone is loaded for the first time, + if the zone's :any:`file` does not exist but ``initial-file`` does, the + zone file is copied into place from the initial file before loading. + This can be used to simplify the process of adding new zones, removing + the need to create the zone file before configuring the zone. For example, + a template zonefile could be used by running: + + :: + + $ rndc addzone example.com \ + '{ type primary; file "example.db"; initial-file "template.db"; };' + + Using "@" to reference the zone origin name within ``template.db`` + allows the same file to be used with multiple zones, as in: + + :: + + $TTL 300 + @ IN SOA ns hosmaster 1 1800 1800 86400 3600 + NS ns + ns A 192.0.2.1 + .. namedconf:statement:: journal :tags: zone :short: Allows the default journal's filename to be overridden. diff --git a/doc/misc/primary.zoneopt b/doc/misc/primary.zoneopt index d43f66a55a..74a314999a 100644 --- a/doc/misc/primary.zoneopt +++ b/doc/misc/primary.zoneopt @@ -27,6 +27,7 @@ zone [ ] { file ; forward ( first | only ); forwarders [ port ] [ tls ] { ( | ) [ port ] [ tls ]; ... }; + initial-file ; inline-signing ; ixfr-from-differences ; journal ; diff --git a/fuzz/dns_message_checksig.c b/fuzz/dns_message_checksig.c index e1c2eeefb3..01ef02f585 100644 --- a/fuzz/dns_message_checksig.c +++ b/fuzz/dns_message_checksig.c @@ -212,7 +212,7 @@ LLVMFuzzerInitialize(int *argc ISC_ATTR_UNUSED, char ***argv ISC_ATTR_UNUSED) { dns_zone_setclass(zone, view->rdclass); dns_zone_settype(zone, dns_zone_primary); dns_zone_setkeydirectory(zone, wd); - dns_zone_setfile(zone, pathbuf, dns_masterformat_text, + dns_zone_setfile(zone, pathbuf, NULL, dns_masterformat_text, &dns_master_style_default); result = dns_zone_load(zone, false); diff --git a/lib/dns/include/dns/zone.h b/lib/dns/include/dns/zone.h index fc3f601028..bd015e7de9 100644 --- a/lib/dns/include/dns/zone.h +++ b/lib/dns/include/dns/zone.h @@ -290,16 +290,18 @@ dns_zone_getorigin(dns_zone_t *zone); */ void -dns_zone_setfile(dns_zone_t *zone, const char *file, dns_masterformat_t format, - const dns_master_style_t *style); +dns_zone_setfile(dns_zone_t *zone, const char *file, const char *initial_file, + dns_masterformat_t format, const dns_master_style_t *style); /*%< * Sets the name of the master file in the format of 'format' from which * the zone loads its database to 'file'. * * For zones that have no associated master file, 'file' will be NULL. + * For some zone types, e.g. secondary zones, 'file' is optional, but + * for primary zones it is mandatory. If the master file does not exist + * during loading, then it will be copied into place from 'initial_file'. * - * For zones with persistent databases, the file name - * setting is ignored. + * For zones with persistent databases, the file name setting is ignored. * * Require: *\li 'zone' to be a valid zone. diff --git a/lib/dns/zone.c b/lib/dns/zone.c index 7e61d6869c..89fa19a383 100644 --- a/lib/dns/zone.c +++ b/lib/dns/zone.c @@ -284,6 +284,7 @@ struct dns_zone { dns_name_t origin; dns_name_t rad; char *masterfile; + char *initfile; const FILE *stream; /* loading from a stream? */ ISC_LIST(dns_include_t) includes; /* Include files */ ISC_LIST(dns_include_t) newincludes; /* Loading */ @@ -1289,9 +1290,13 @@ zone_free(dns_zone_t *zone) { if (zone->masterfile != NULL) { isc_mem_free(zone->mctx, zone->masterfile); } + if (zone->initfile != NULL) { + isc_mem_free(zone->mctx, zone->initfile); + } if (zone->keydirectory != NULL) { isc_mem_free(zone->mctx, zone->keydirectory); } + if (zone->kasp != NULL) { dns_kasp_detach(&zone->kasp); } @@ -1793,13 +1798,14 @@ setstring(dns_zone_t *zone, char **field, const char *value) { } void -dns_zone_setfile(dns_zone_t *zone, const char *file, dns_masterformat_t format, - const dns_master_style_t *style) { +dns_zone_setfile(dns_zone_t *zone, const char *file, const char *initial_file, + dns_masterformat_t format, const dns_master_style_t *style) { REQUIRE(DNS_ZONE_VALID(zone)); REQUIRE(zone->stream == NULL); LOCK_ZONE(zone); setstring(zone, &zone->masterfile, file); + setstring(zone, &zone->initfile, initial_file); zone->masterformat = format; if (format == dns_masterformat_text) { zone->masterstyle = style; @@ -2105,6 +2111,42 @@ zone_touched(dns_zone_t *zone) { return false; } +static isc_result_t +copy_initfile(dns_zone_t *zone) { + isc_result_t result; + FILE *input = NULL, *output = NULL; + size_t len; + + CHECK(isc_stdio_open(zone->initfile, "r", &input)); + CHECK(isc_stdio_open(zone->masterfile, "w", &output)); + + CHECK(isc_file_getsizefd(fileno(input), (off_t *)&len)); + + do { + char buf[BUFSIZ]; + size_t rval; + + result = isc_stdio_read(buf, 1, sizeof(buf), input, &rval); + if (result != ISC_R_SUCCESS && result != ISC_R_EOF) { + goto failure; + } + CHECK(isc_stdio_write(buf, rval, 1, output, NULL)); + len -= rval; + } while (len > 0); + +failure: + if (input != NULL) { + isc_stdio_close(input); + } + if (output != NULL) { + if (result != ISC_R_SUCCESS) { + isc_file_remove(zone->masterfile); + } + isc_stdio_close(output); + } + return result; +} + /* * Note: when dealing with inline-signed zones, external callers will always * call zone_load() for the secure zone; zone_load() calls itself recursively @@ -2347,6 +2389,22 @@ zone_load(dns_zone_t *zone, unsigned int flags, bool locked) { } } + if (zone->type == dns_zone_primary && zone->masterfile != NULL && + !isc_file_exists(zone->masterfile) && zone->initfile != NULL) + { + dns_zone_logc(zone, DNS_LOGCATEGORY_ZONELOAD, ISC_LOG_INFO, + "zone file %s not found; copying initial " + "file %s", + zone->masterfile, zone->initfile); + result = copy_initfile(zone); + if (result != ISC_R_SUCCESS) { + dns_zone_logc(zone, DNS_LOGCATEGORY_ZONELOAD, + ISC_LOG_ERROR, "copy from %s failed: %s", + zone->initfile, + isc_result_totext(result)); + } + } + dns_zone_logc(zone, DNS_LOGCATEGORY_ZONELOAD, ISC_LOG_DEBUG(1), "starting load"); diff --git a/lib/isccfg/namedconf.c b/lib/isccfg/namedconf.c index 7372a60415..82c50890f9 100644 --- a/lib/isccfg/namedconf.c +++ b/lib/isccfg/namedconf.c @@ -2430,6 +2430,7 @@ static cfg_clausedef_t zone_only_clauses[] = { CFG_ZONE_PRIMARY | CFG_ZONE_SECONDARY | CFG_ZONE_MIRROR | CFG_ZONE_STUB | CFG_ZONE_HINT | CFG_ZONE_REDIRECT }, { "in-view", &cfg_type_astring, CFG_ZONE_INVIEW }, + { "initial-file", &cfg_type_qstring, CFG_ZONE_PRIMARY }, { "inline-signing", &cfg_type_boolean, CFG_ZONE_PRIMARY | CFG_ZONE_SECONDARY }, { "ixfr-base", NULL, CFG_CLAUSEFLAG_ANCIENT }, diff --git a/tests/dns/nsec3param_test.c b/tests/dns/nsec3param_test.c index 35e3ce0343..ec194f297d 100644 --- a/tests/dns/nsec3param_test.c +++ b/tests/dns/nsec3param_test.c @@ -120,7 +120,8 @@ nsec3param_change_test(const nsec3param_change_test_params_t *test) { assert_int_equal(result, ISC_R_SUCCESS); dns_zone_setfile(zone, TESTS_DIR "/testdata/nsec3param/nsec3.db.signed", - dns_masterformat_text, &dns_master_style_default); + NULL, dns_masterformat_text, + &dns_master_style_default); result = dns_zone_load(zone, false); assert_int_equal(result, ISC_R_SUCCESS); diff --git a/tests/dns/zt_test.c b/tests/dns/zt_test.c index eea991afd6..803eabc15e 100644 --- a/tests/dns/zt_test.c +++ b/tests/dns/zt_test.c @@ -184,7 +184,7 @@ ISC_LOOP_TEST_IMPL(asyncload_zone) { fwrite(buf, 1, n, zonefile); fflush(zonefile); - dns_zone_setfile(zone, "./zone.data", dns_masterformat_text, + dns_zone_setfile(zone, "./zone.data", NULL, dns_masterformat_text, &dns_master_style_default); dns_zone_asyncload(zone, false, load_done_first, zone); @@ -235,19 +235,19 @@ ISC_LOOP_TEST_IMPL(asyncload_zt) { result = dns_test_makezone("foo", &zone1, NULL, true); assert_int_equal(result, ISC_R_SUCCESS); - dns_zone_setfile(zone1, TESTS_DIR "/testdata/zt/zone1.db", + dns_zone_setfile(zone1, TESTS_DIR "/testdata/zt/zone1.db", NULL, dns_masterformat_text, &dns_master_style_default); view = dns_zone_getview(zone1); result = dns_test_makezone("bar", &zone2, view, false); assert_int_equal(result, ISC_R_SUCCESS); - dns_zone_setfile(zone2, TESTS_DIR "/testdata/zt/zone1.db", + dns_zone_setfile(zone2, TESTS_DIR "/testdata/zt/zone1.db", NULL, dns_masterformat_text, &dns_master_style_default); /* This one will fail to load */ result = dns_test_makezone("fake", &zone3, view, false); assert_int_equal(result, ISC_R_SUCCESS); - dns_zone_setfile(zone3, TESTS_DIR "/testdata/zt/nonexistent.db", + dns_zone_setfile(zone3, TESTS_DIR "/testdata/zt/nonexistent.db", NULL, dns_masterformat_text, &dns_master_style_default); rcu_read_lock(); diff --git a/tests/libtest/ns.c b/tests/libtest/ns.c index 64c94d6f05..83e5c3f2b7 100644 --- a/tests/libtest/ns.c +++ b/tests/libtest/ns.c @@ -168,7 +168,7 @@ ns_test_serve_zone(const char *zonename, const char *filename, /* * Set path to the master file for the zone and then load it. */ - dns_zone_setfile(served_zone, filename, dns_masterformat_text, + dns_zone_setfile(served_zone, filename, NULL, dns_masterformat_text, &dns_master_style_default); result = dns_zone_load(served_zone, false); if (result != ISC_R_SUCCESS) { From 598ae3f63ca973ad57ce944aae6aab9ab0480c9a Mon Sep 17 00:00:00 2001 From: Evan Hunt Date: Tue, 15 Apr 2025 21:17:44 -0700 Subject: [PATCH 2/3] Allow zone names to be generated parametrically Special tokens can now be specified in a zone "file" option in order to generate the filename parametrically. The first instead of "$name" in the "file" option is replaced with the zone origin, the first instance of "$type" is replaced with the zone type (i.e., primary, secondary, etc), and the first instance of "$view" is replaced with the view name.. This simplifies the creation of zones using initial-file templates. For example: $ rndc addzone \ { type primary; file "$name.db"; initial-file "template.db" --- bin/tests/system/masterfile/ns2/named.conf.j2 | 4 +- doc/arm/reference.rst | 18 ++- lib/dns/zone.c | 104 ++++++++++++++- tests/dns/Makefile.am | 1 + tests/dns/zonefile_test.c | 121 ++++++++++++++++++ 5 files changed, 241 insertions(+), 7 deletions(-) create mode 100644 tests/dns/zonefile_test.c diff --git a/bin/tests/system/masterfile/ns2/named.conf.j2 b/bin/tests/system/masterfile/ns2/named.conf.j2 index 50a1d2e2d9..442d273a8c 100644 --- a/bin/tests/system/masterfile/ns2/named.conf.j2 +++ b/bin/tests/system/masterfile/ns2/named.conf.j2 @@ -36,12 +36,12 @@ zone "." { zone "example" { type primary; - file "example.db"; + file "$name.db"; }; zone "missing" { type primary; - file "missing.db"; + file "$name.db"; }; zone "initial" { diff --git a/doc/arm/reference.rst b/doc/arm/reference.rst index 0c81a56800..63535eddde 100644 --- a/doc/arm/reference.rst +++ b/doc/arm/reference.rst @@ -7132,13 +7132,21 @@ Zone Options :tags: zone :short: Specifies the zone's filename. - This sets the zone's filename. In :any:`primary `, :any:`hint `, and :any:`redirect ` + This sets the zone's filename. In :any:`primary `, + :any:`hint `, and :any:`redirect ` zones which do not have :any:`primaries` defined, zone data is loaded from this file. In :any:`secondary `, :any:`mirror `, :any:`stub `, and :any:`redirect ` zones which do have :any:`primaries` defined, zone data is retrieved from another server and saved in this file. This option is not applicable to other zone types. + The filename can be generated parametrically by including special + tokens in the string: the first instance of ``$name`` in the string + is replaced with the zone name in lower case; the first instance of + ``$type`` is replaced with the zone type -- i.e., ``primary``, + ``secondary``, etc); and the first instance of ``$view`` is replaced + with the view name. These tokens are case-insensitive. + :any:`forward` This option is only meaningful if the zone has a forwarders list. The ``only`` value causes the lookup to fail after trying the forwarders and getting no @@ -7163,10 +7171,12 @@ Zone Options :: $ rndc addzone example.com \ - '{ type primary; file "example.db"; initial-file "template.db"; };' + '{ type primary; file "$name.db"; initial-file "template.db"; };' - Using "@" to reference the zone origin name within ``template.db`` - allows the same file to be used with multiple zones, as in: + This creates a zone ``example.com``, with filename ``example.com.db``. + + Using "@" to reference the zone origin within the initial file + allows the same file to be used for multiple zones, as in: :: diff --git a/lib/dns/zone.c b/lib/dns/zone.c index 89fa19a383..8edc1beb5f 100644 --- a/lib/dns/zone.c +++ b/lib/dns/zone.c @@ -1797,6 +1797,108 @@ setstring(dns_zone_t *zone, char **field, const char *value) { *field = copy; } +static int +position_order(const void *a, const void *b) { + /* sort char pointers in order of which occurs first in memory */ + return (char *)*(char **)a - (char *)*(char **)b; +} + +static isc_result_t +putmem(isc_buffer_t *b, const char *base, size_t length) { + size_t space = isc_buffer_availablelength(b) - 1; + if (space < length) { + isc_buffer_putmem(b, (const unsigned char *)base, space); + return ISC_R_NOSPACE; + } + + isc_buffer_putmem(b, (const unsigned char *)base, length); + return ISC_R_SUCCESS; +} + +/* + * Set the masterfile field, expanding $name to the zone name, + * $type to the zone type, and $view to the view name. Cap the + * length at PATH_MAX. + */ +static void +setfilename(dns_zone_t *zone, char **field, const char *value) { + isc_result_t result; + char *t = NULL, *n = NULL, *v = NULL; + char *positions[3]; + char filename[PATH_MAX]; + isc_buffer_t b; + size_t tags = 0; + + if (value == NULL) { + *field = NULL; + return; + } + + t = strcasestr(value, "$type"); + if (t != NULL) { + positions[tags++] = t; + } + + n = strcasestr(value, "$name"); + if (n != NULL) { + positions[tags++] = n; + } + + v = strcasestr(value, "$view"); + if (v != NULL) { + positions[tags++] = v; + } + + if (tags == 0) { + setstring(zone, field, value); + return; + } + + isc_buffer_init(&b, filename, sizeof(filename)); + + /* sort the tag offsets in order of occurrence */ + qsort(positions, tags, sizeof(char *), position_order); + + const char *p = value; + for (size_t i = 0; i < tags; i++) { + size_t tokenlen = 0; + + CHECK(putmem(&b, p, (positions[i] - p))); + + p = positions[i]; + INSIST(p != NULL); + if (p == n) { + dns_fixedname_t fn; + dns_name_t *name = dns_fixedname_initname(&fn); + char namebuf[DNS_NAME_FORMATSIZE]; + + result = dns_name_downcase(&zone->origin, name); + RUNTIME_CHECK(result == ISC_R_SUCCESS); + dns_name_format(name, namebuf, sizeof(namebuf)); + CHECK(putmem(&b, namebuf, strlen(namebuf))); + tokenlen = 5; /* "$name" */ + } else if (p == t) { + const char *typename = dns_zonetype_name(zone->type); + CHECK(putmem(&b, typename, strlen(typename))); + tokenlen = 5; /* "$type" */ + } else if (p == v) { + CHECK(putmem(&b, zone->view->name, + strlen(zone->view->name))); + tokenlen = 5; /* "$view" */ + } + + /* Advance the input pointer past the token */ + p += tokenlen; + } + + const char *end = value + strlen(value); + putmem(&b, p, end - p); + +failure: + isc_buffer_putuint8(&b, 0); + setstring(zone, field, filename); +} + void dns_zone_setfile(dns_zone_t *zone, const char *file, const char *initial_file, dns_masterformat_t format, const dns_master_style_t *style) { @@ -1804,7 +1906,7 @@ dns_zone_setfile(dns_zone_t *zone, const char *file, const char *initial_file, REQUIRE(zone->stream == NULL); LOCK_ZONE(zone); - setstring(zone, &zone->masterfile, file); + setfilename(zone, &zone->masterfile, file); setstring(zone, &zone->initfile, initial_file); zone->masterformat = format; if (format == dns_masterformat_text) { diff --git a/tests/dns/Makefile.am b/tests/dns/Makefile.am index 0eb30dcabf..8d4cc4c151 100644 --- a/tests/dns/Makefile.am +++ b/tests/dns/Makefile.am @@ -51,6 +51,7 @@ check_PROGRAMS = \ transport_test \ tsig_test \ update_test \ + zonefile_test \ zonemgr_test \ zt_test diff --git a/tests/dns/zonefile_test.c b/tests/dns/zonefile_test.c new file mode 100644 index 0000000000..a25bf65fd4 --- /dev/null +++ b/tests/dns/zonefile_test.c @@ -0,0 +1,121 @@ +/* + * 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. + */ + +#include +#include /* IWYU pragma: keep */ +#include +#include +#include +#include +#include +#include +#include + +#define UNIT_TESTING +#include + +#include +#include + +#include +#include + +#include + +typedef struct { + const char *input, *expected; +} zonefile_test_params_t; + +static int +setup_test(void **state) { + setup_loopmgr(state); + return 0; +} + +static int +teardown_test(void **state) { + teardown_loopmgr(state); + return 0; +} + +ISC_LOOP_TEST_IMPL(filename) { + isc_result_t result; + dns_zone_t *zone = NULL; + const zonefile_test_params_t tests[] = { + { "$name", "example.com" }, + { "$name.db", "example.com.db" }, + { "./dir/$name.db", "./dir/example.com.db" }, + { "$type", "primary" }, + { "$type-file", "primary-file" }, + { "./dir/$type", "./dir/primary" }, + { "./$type/$name.db", "./primary/example.com.db" }, + { "./$TyPe/$NAmE.db", "./primary/example.com.db" }, + { "./$name/$type", "./example.com/primary" }, + { "$name.$type", "example.com.primary" }, + { "$type$name", "primaryexample.com" }, + { "$type$type", "primary$type" }, + { "$name$name", "example.com$name" }, + { "typename", "typename" }, + { "$view", "local" }, + { "./$type/$view-$name.db", "./primary/local-example.com.db" }, + { "./$view/$type-$name.db", "./local/primary-example.com.db" }, + { "./$name/$view-$type.db", "./example.com/local-primary.db" }, + { "", "" }, + }; + + dns_view_t *view = NULL; + result = dns_test_makeview("local", false, false, &view); + assert_int_equal(result, ISC_R_SUCCESS); + + /* use .COM here to test that the name is correctly downcased */ + result = dns_test_makezone("example.COM", &zone, view, false); + assert_int_equal(result, ISC_R_SUCCESS); + + dns_zone_setview(zone, view); + dns_view_detach(&view); + + for (size_t i = 0; i < ARRAY_SIZE(tests); i++) { + dns_zone_setfile(zone, tests[i].input, NULL, + dns_masterformat_text, + &dns_master_style_default); + assert_string_equal(dns_zone_getfile(zone), tests[i].expected); + } + + /* test PATH_MAX overrun */ + char longname[PATH_MAX] = { 0 }; + memset(longname, 'x', sizeof(longname) - 1); + dns_zone_setfile(zone, longname, NULL, dns_masterformat_text, + &dns_master_style_default); + assert_string_equal(dns_zone_getfile(zone), longname); + + /* + * overwrite the beginning of the long name with $name. when + * it's expanded to the zone name, the resulting string should + * still be capped at PATH_MAX characters. + */ + memmove(longname, "$name", 5); + dns_zone_setfile(zone, longname, NULL, dns_masterformat_text, + &dns_master_style_default); + assert_int_equal(strlen(longname), PATH_MAX - 1); + memmove(longname, "example.com", 11); + assert_string_equal(dns_zone_getfile(zone), longname); + + dns_zone_detach(&zone); + isc_loopmgr_shutdown(loopmgr); +} + +ISC_TEST_LIST_START +ISC_TEST_ENTRY_CUSTOM(filename, setup_test, teardown_test) +ISC_TEST_LIST_END + +ISC_TEST_MAIN From b8f325ae01f30aa4de8ee7d632e0114b558f93fb Mon Sep 17 00:00:00 2001 From: Evan Hunt Date: Tue, 15 Apr 2025 13:39:57 -0700 Subject: [PATCH 3/3] Add support for zone templates A "template" statement can contain the same configuration clauses as a "zone" statement. A "zone" statement can now reference a template, and all the clauses in that template will be used as default values for the zone. For example: template primary { type primary; file "$name.db"; initial-file "primary.db"; }; zone example.com { template primary; file "different-name.db"; // overrides the template }; --- bin/named/config.c | 17 ++ bin/named/include/named/config.h | 4 + bin/named/include/named/zoneconf.h | 10 +- bin/named/server.c | 102 ++++---- bin/named/zoneconf.c | 165 +++++++++---- bin/tests/system/addzone/ns2/added.db | 1 - bin/tests/system/addzone/ns2/named1.conf.in | 6 + bin/tests/system/addzone/ns2/named2.conf.in | 6 + bin/tests/system/addzone/ns2/named3.conf.in | 6 + bin/tests/system/addzone/tests.sh | 24 ++ bin/tests/system/addzone/tests_sh_addzone.py | 1 + .../system/checkconf/bad-template-1.conf | 23 ++ .../system/checkconf/bad-template-2.conf | 22 ++ .../system/checkconf/bad-template-3.conf | 23 ++ .../system/checkconf/bad-template-4.conf | 17 ++ .../system/checkconf/good-template-1.conf | 23 ++ bin/tests/system/masterfile/ns2/named.conf.j2 | 15 +- .../system/masterfile/tests_masterfile.py | 38 ++- doc/arm/reference.rst | 53 ++++ doc/misc/forward.zoneopt | 1 + doc/misc/hint.zoneopt | 1 + doc/misc/mirror.zoneopt | 1 + doc/misc/options | 87 +++++++ doc/misc/primary.zoneopt | 1 + doc/misc/redirect.zoneopt | 1 + doc/misc/secondary.zoneopt | 1 + doc/misc/static-stub.zoneopt | 1 + doc/misc/stub.zoneopt | 1 + lib/isccfg/check.c | 232 +++++++++++------- lib/isccfg/namedconf.c | 66 ++++- 30 files changed, 742 insertions(+), 207 deletions(-) create mode 100644 bin/tests/system/checkconf/bad-template-1.conf create mode 100644 bin/tests/system/checkconf/bad-template-2.conf create mode 100644 bin/tests/system/checkconf/bad-template-3.conf create mode 100644 bin/tests/system/checkconf/bad-template-4.conf create mode 100644 bin/tests/system/checkconf/good-template-1.conf diff --git a/bin/named/config.c b/bin/named/config.c index babf61ad8f..cc36edad98 100644 --- a/bin/named/config.c +++ b/bin/named/config.c @@ -387,6 +387,23 @@ named_config_get(cfg_obj_t const *const *maps, const char *name, return ISC_R_NOTFOUND; } +isc_result_t +named_config_findopt(const cfg_obj_t *opts1, const cfg_obj_t *opts2, + const char *name, const cfg_obj_t **objp) { + isc_result_t result = ISC_R_NOTFOUND; + + REQUIRE(*objp == NULL); + + if (opts1 != NULL) { + result = cfg_map_get(opts1, name, objp); + } + if (*objp == NULL && opts2 != NULL) { + result = cfg_map_get(opts2, name, objp); + } + + return result; +} + isc_result_t named_checknames_get(const cfg_obj_t **maps, const char *const names[], const cfg_obj_t **obj) { diff --git a/bin/named/include/named/config.h b/bin/named/include/named/config.h index 0be3b12b9b..62f0e3c815 100644 --- a/bin/named/include/named/config.h +++ b/bin/named/include/named/config.h @@ -67,3 +67,7 @@ named_config_getport(const cfg_obj_t *config, const char *type, isc_result_t named_config_getkeyalgorithm(const char *str, unsigned int *typep, uint16_t *digestbits); + +isc_result_t +named_config_findopt(const cfg_obj_t *opts1, const cfg_obj_t *opts2, + const char *name, const cfg_obj_t **objp); diff --git a/bin/named/include/named/zoneconf.h b/bin/named/include/named/zoneconf.h index a10a650fc7..e96be44d3e 100644 --- a/bin/named/include/named/zoneconf.h +++ b/bin/named/include/named/zoneconf.h @@ -63,7 +63,7 @@ named_zone_inlinesigning(const cfg_obj_t *zconfig, const cfg_obj_t *vconfig, isc_result_t named_zone_configure_writeable_dlz(dns_dlzdb_t *dlzdatabase, dns_zone_t *zone, dns_rdataclass_t rdclass, dns_name_t *name); -/*%> +/*%< * configure a DLZ zone, setting up the database methods and calling * postload to load the origin values * @@ -73,3 +73,11 @@ named_zone_configure_writeable_dlz(dns_dlzdb_t *dlzdatabase, dns_zone_t *zone, * \li 'rdclass' to be a valid rdataclass * \li 'name' to be a valid zone origin name */ + +const cfg_obj_t * +named_zone_templateopts(const cfg_obj_t *config, const cfg_obj_t *zoptions); +/*%< + * If a zone with options `zoptions` specifies a zone template, look + * the template options and return them. If no such template is found, + * return NULL. + */ diff --git a/bin/named/server.c b/bin/named/server.c index 7c35cc4a51..cccd192405 100644 --- a/bin/named/server.c +++ b/bin/named/server.c @@ -3097,12 +3097,12 @@ cleanup: static isc_result_t create_empty_zone(dns_zone_t *pzone, dns_name_t *name, dns_view_t *view, - const cfg_obj_t *zonelist, const char **empty_dbtype, - int empty_dbtypec, dns_zonestat_level_t statlevel) { + const cfg_obj_t *config, const cfg_obj_t *voptions, + const char **empty_dbtype, int empty_dbtypec, + dns_zonestat_level_t statlevel) { char namebuf[DNS_NAME_FORMATSIZE]; const cfg_obj_t *obj = NULL; - const cfg_obj_t *zconfig = NULL; - const cfg_obj_t *zoptions = NULL; + const cfg_obj_t *zonelist = NULL; const char *default_dbtype[4] = { ZONEDB_DEFAULT }; const char *sep = ": view "; const char *str = NULL; @@ -3126,12 +3126,20 @@ create_empty_zone(dns_zone_t *pzone, dns_name_t *name, dns_view_t *view, ns = dns_fixedname_initname(&nsfixed); contact = dns_fixedname_initname(&cfixed); + if (voptions != NULL) { + (void)cfg_map_get(voptions, "zone", &zonelist); + } else { + (void)cfg_map_get(config, "zone", &zonelist); + } /* * Look for forward "zones" beneath this empty zone and if so * create a custom db for the empty zone. */ CFG_LIST_FOREACH (zonelist, element) { - zconfig = cfg_listelt_value(element); + const cfg_obj_t *zconfig = cfg_listelt_value(element); + const cfg_obj_t *zoptions = cfg_tuple_get(zconfig, "options"); + const cfg_obj_t *toptions = NULL; + str = cfg_obj_asstring(cfg_tuple_get(zconfig, "name")); CHECK(dns_name_fromstring(zname, str, dns_rootname, 0, NULL)); namereln = dns_name_fullcompare(zname, name, &order, &nlabels); @@ -3139,15 +3147,16 @@ create_empty_zone(dns_zone_t *pzone, dns_name_t *name, dns_view_t *view, continue; } - zoptions = cfg_tuple_get(zconfig, "options"); + toptions = named_zone_templateopts(config, zoptions); obj = NULL; - (void)cfg_map_get(zoptions, "type", &obj); + (void)named_config_findopt(zoptions, toptions, "type", &obj); if (obj != NULL && strcasecmp(cfg_obj_asstring(obj), "forward") == 0) { obj = NULL; - (void)cfg_map_get(zoptions, "forward", &obj); + (void)named_config_findopt(zoptions, toptions, + "forward", &obj); if (obj == NULL) { continue; } @@ -5468,9 +5477,9 @@ configure_view(dns_view_t *view, dns_viewlist_t *viewlist, cfg_obj_t *config, dns_view_detach(&pview); } - CHECK(create_empty_zone(zone, name, view, zonelist, - empty_dbtype, empty_dbtypec, - statlevel)); + CHECK(create_empty_zone(zone, name, view, config, + voptions, empty_dbtype, + empty_dbtypec, statlevel)); if (zone != NULL) { dns_zone_detach(&zone); } @@ -6151,6 +6160,7 @@ configure_zone(const cfg_obj_t *config, const cfg_obj_t *zconfig, dns_zone_t *dupzone = NULL; const cfg_obj_t *options = NULL; const cfg_obj_t *zoptions = NULL; + const cfg_obj_t *toptions = NULL; const cfg_obj_t *typeobj = NULL; const cfg_obj_t *forwarders = NULL; const cfg_obj_t *forwardtype = NULL; @@ -6160,10 +6170,10 @@ configure_zone(const cfg_obj_t *config, const cfg_obj_t *zconfig, isc_result_t tresult; isc_buffer_t buffer; dns_fixedname_t fixorigin; - dns_name_t *origin; - const char *zname; + dns_name_t *origin = NULL; + const char *zname = NULL; dns_rdataclass_t zclass; - const char *ztypestr; + const char *ztypestr = NULL; dns_rpz_num_t rpz_num; bool zone_is_catz = false; bool zone_maybe_inline = false; @@ -6174,6 +6184,7 @@ configure_zone(const cfg_obj_t *config, const cfg_obj_t *zconfig, (void)cfg_map_get(config, "options", &options); zoptions = cfg_tuple_get(zconfig, "options"); + toptions = named_zone_templateopts(config, zoptions); /* * Get the zone origin as a dns_name_t. @@ -6258,7 +6269,7 @@ configure_zone(const cfg_obj_t *config, const cfg_obj_t *zconfig, goto cleanup; } - (void)cfg_map_get(zoptions, "type", &typeobj); + (void)named_config_findopt(zoptions, toptions, "type", &typeobj); if (typeobj == NULL) { cfg_obj_log(zconfig, ISC_LOG_ERROR, "zone '%s' 'type' not specified", zname); @@ -6273,7 +6284,9 @@ configure_zone(const cfg_obj_t *config, const cfg_obj_t *zconfig, */ if (strcasecmp(ztypestr, "hint") == 0) { const cfg_obj_t *fileobj = NULL; - if (cfg_map_get(zoptions, "file", &fileobj) != ISC_R_SUCCESS) { + (void)named_config_findopt(zoptions, toptions, "file", + &fileobj); + if (fileobj == NULL) { isc_log_write(NAMED_LOGCATEGORY_GENERAL, NAMED_LOGMODULE_SERVER, ISC_LOG_ERROR, "zone '%s': 'file' not specified", zname); @@ -6303,8 +6316,10 @@ configure_zone(const cfg_obj_t *config, const cfg_obj_t *zconfig, forwardtype = NULL; forwarders = NULL; - (void)cfg_map_get(zoptions, "forward", &forwardtype); - (void)cfg_map_get(zoptions, "forwarders", &forwarders); + (void)named_config_findopt(zoptions, toptions, "forward", + &forwardtype); + (void)named_config_findopt(zoptions, toptions, "forwarders", + &forwarders); CHECK(configure_forward(config, view, origin, forwarders, forwardtype)); goto cleanup; @@ -6463,9 +6478,11 @@ configure_zone(const cfg_obj_t *config, const cfg_obj_t *zconfig, * selective forwarding. */ forwarders = NULL; - if (cfg_map_get(zoptions, "forwarders", &forwarders) == ISC_R_SUCCESS) { + named_config_findopt(zoptions, toptions, "forwarders", &forwarders); + if (forwarders != NULL) { forwardtype = NULL; - (void)cfg_map_get(zoptions, "forward", &forwardtype); + named_config_findopt(zoptions, toptions, "forward", + &forwardtype); CHECK(configure_forward(config, view, origin, forwarders, forwardtype)); } @@ -6497,9 +6514,9 @@ configure_zone(const cfg_obj_t *config, const cfg_obj_t *zconfig, dns_zone_setstats(raw, named_g_server->zonestats); CHECK(dns_zone_link(zone, raw)); } - if (cfg_map_get(zoptions, "ixfr-from-differences", - &ixfrfromdiffs) == ISC_R_SUCCESS) - { + named_config_findopt(zoptions, toptions, + "ixfr-from-differences", &ixfrfromdiffs); + if (ixfrfromdiffs != NULL) { isc_log_write(NAMED_LOGCATEGORY_GENERAL, NAMED_LOGMODULE_SERVER, ISC_LOG_INFO, "zone '%s': 'ixfr-from-differences' is " @@ -12459,11 +12476,9 @@ nzd_save(MDB_txn **txnp, MDB_dbi dbi, dns_zone_t *zone, } } else { /* We're creating or overwriting the zone */ - const cfg_obj_t *zoptions; + const cfg_obj_t *zoptions = cfg_tuple_get(zconfig, "options"); isc_buffer_allocate(view->mctx, &text, 256); - - zoptions = cfg_tuple_get(zconfig, "options"); if (zoptions == NULL) { isc_log_write(NAMED_LOGCATEGORY_GENERAL, NAMED_LOGMODULE_SERVER, ISC_LOG_ERROR, @@ -12785,16 +12800,14 @@ load_nzf(dns_view_t *view, ns_cfgctx_t *nzcfg) { isc_buffer_allocate(view->mctx, &text, 256); CFG_LIST_FOREACH (zonelist, element) { - const cfg_obj_t *zconfig; + const cfg_obj_t *zconfig = cfg_listelt_value(element); const cfg_obj_t *zoptions; char zname[DNS_NAME_FORMATSIZE]; dns_fixedname_t fname; - dns_name_t *name; - const char *origin; + dns_name_t *name = NULL; + const char *origin = NULL; isc_buffer_t b; - zconfig = cfg_listelt_value(element); - origin = cfg_obj_asstring(cfg_tuple_get(zconfig, "name")); if (origin == NULL) { result = ISC_R_FAILURE; @@ -12943,10 +12956,15 @@ newzone_parse(named_server_t *server, char *command, dns_view_t **viewp, if (obj != NULL) { (void)putstr(text, "'in-view' zones not supported by "); (void)putstr(text, bn); - } else { - (void)putstr(text, "zone type not specified"); + CHECK(ISC_R_FAILURE); + } + + (void)cfg_map_get(zoptions, "template", &obj); + if (obj == NULL) { + (void)putstr(text, "no zone type or " + "template specified"); + CHECK(ISC_R_FAILURE); } - CHECK(ISC_R_FAILURE); } if (strcasecmp(cfg_obj_asstring(obj), "hint") == 0 || @@ -13518,14 +13536,12 @@ named_server_changezone(named_server_t *server, char *command, (void)putstr(text, "Not allowing new zones in view '"); (void)putstr(text, view->name); (void)putstr(text, "'"); - result = ISC_R_NOPERM; - goto cleanup; + CHECK(ISC_R_NOPERM); } cfg = (ns_cfgctx_t *)view->new_zone_config; if (cfg == NULL) { - result = ISC_R_FAILURE; - goto cleanup; + CHECK(ISC_R_FAILURE); } zonename = cfg_obj_asstring(cfg_tuple_get(zoneobj, "name")); @@ -13922,12 +13938,16 @@ find_name_in_list_from_map(const cfg_obj_t *config, if (result == ISC_R_SUCCESS && dns_name_equal(name1, name2)) { - const cfg_obj_t *zoptions; + const cfg_obj_t *zoptions = + cfg_tuple_get(obj, "options"); const cfg_obj_t *typeobj = NULL; - zoptions = cfg_tuple_get(obj, "options"); if (zoptions != NULL) { - cfg_map_get(zoptions, "type", &typeobj); + const cfg_obj_t *toptions = + named_zone_templateopts( + config, zoptions); + named_config_findopt(zoptions, toptions, + "type", &typeobj); } if (redirect && typeobj != NULL && strcasecmp(cfg_obj_asstring(typeobj), diff --git a/bin/named/zoneconf.c b/bin/named/zoneconf.c index ca24ec9851..0e069b1309 100644 --- a/bin/named/zoneconf.c +++ b/bin/named/zoneconf.c @@ -77,12 +77,12 @@ configure_zone_acl(const cfg_obj_t *zconfig, const cfg_obj_t *vconfig, void (*setzacl)(dns_zone_t *, dns_acl_t *), void (*clearzacl)(dns_zone_t *)) { isc_result_t result; - const cfg_obj_t *maps[5] = { NULL, NULL, NULL, NULL, NULL }; + const cfg_obj_t *maps[6] = { 0 }; const cfg_obj_t *aclobj = NULL; int i = 0; dns_acl_t **aclp = NULL, *acl = NULL; const char *aclname; - dns_view_t *view; + dns_view_t *view = NULL; view = dns_zone_getview(zone); @@ -129,7 +129,7 @@ configure_zone_acl(const cfg_obj_t *zconfig, const cfg_obj_t *vconfig, /* First check to see if ACL is defined within the zone */ if (zconfig != NULL) { - maps[0] = cfg_tuple_get(zconfig, "options"); + maps[i] = cfg_tuple_get(zconfig, "options"); (void)named_config_get(maps, aclname, &aclobj); if (aclobj != NULL) { aclp = NULL; @@ -137,6 +137,14 @@ configure_zone_acl(const cfg_obj_t *zconfig, const cfg_obj_t *vconfig, } } + if (config != NULL && maps[i] != NULL) { + const cfg_obj_t *toptions = named_zone_templateopts(config, + maps[i]); + if (toptions != NULL) { + maps[i++] = toptions; + } + } + /* Failing that, see if there's a default ACL already in the view */ if (aclp != NULL && *aclp != NULL) { (*setzacl)(zone, *aclp); @@ -187,8 +195,8 @@ parse_acl: * Parse the zone update-policy statement. */ static isc_result_t -configure_zone_ssutable(const cfg_obj_t *zconfig, dns_zone_t *zone, - const char *zname) { +configure_zone_ssutable(const cfg_obj_t *zconfig, const cfg_obj_t *tconfig, + dns_zone_t *zone, const char *zname) { const cfg_obj_t *updatepolicy = NULL; dns_ssutable_t *table = NULL; isc_mem_t *mctx = dns_zone_getmctx(zone); @@ -200,8 +208,8 @@ configure_zone_ssutable(const cfg_obj_t *zconfig, dns_zone_t *zone, isc_buffer_init(&dbuf, debug, sizeof(debug)); isc_buffer_setmctx(&dbuf, mctx); - (void)cfg_map_get(zconfig, "update-policy", &updatepolicy); - + (void)named_config_findopt(zconfig, tconfig, "update-policy", + &updatepolicy); if (updatepolicy == NULL) { dns_zone_setssutable(zone, NULL); return ISC_R_SUCCESS; @@ -540,8 +548,8 @@ configure_staticstub_servernames(const cfg_obj_t *zconfig, dns_zone_t *zone, * Configure static-stub zone. */ static isc_result_t -configure_staticstub(const cfg_obj_t *zconfig, dns_zone_t *zone, - const char *zname, const char *dbtype) { +configure_staticstub(const cfg_obj_t *zconfig, const cfg_obj_t *tconfig, + dns_zone_t *zone, const char *zname, const char *dbtype) { int i = 0; const cfg_obj_t *obj; isc_mem_t *mctx = dns_zone_getmctx(zone); @@ -583,18 +591,16 @@ configure_staticstub(const cfg_obj_t *zconfig, dns_zone_t *zone, /* Prepare zone RRs from the configuration */ obj = NULL; - result = cfg_map_get(zconfig, "server-addresses", &obj); - if (result == ISC_R_SUCCESS) { - INSIST(obj != NULL); + (void)named_config_findopt(zconfig, tconfig, "server-addresses", &obj); + if (obj != NULL) { CHECK(configure_staticstub_serveraddrs(obj, zone, &rdatalist_ns, &rdatalist_a, &rdatalist_aaaa)); } obj = NULL; - result = cfg_map_get(zconfig, "server-names", &obj); - if (result == ISC_R_SUCCESS) { - INSIST(obj != NULL); + (void)named_config_findopt(zconfig, tconfig, "server-names", &obj); + if (obj != NULL) { CHECK(configure_staticstub_servernames(obj, zone, &rdatalist_ns, zname)); } @@ -682,12 +688,11 @@ cleanup: * Convert a config file zone type into a server zone type. */ static dns_zonetype_t -zonetype_fromconfig(const cfg_obj_t *map) { +zonetype_fromconfig(const cfg_obj_t *zmap, const cfg_obj_t *tmap) { const cfg_obj_t *obj = NULL; - isc_result_t result; - result = cfg_map_get(map, "type", &obj); - INSIST(result == ISC_R_SUCCESS && obj != NULL); + (void)named_config_findopt(zmap, tmap, "type", &obj); + INSIST(obj != NULL); return named_config_getzonetype(obj); } @@ -872,11 +877,13 @@ named_zone_configure(const cfg_obj_t *config, const cfg_obj_t *vconfig, const char *zname; dns_rdataclass_t zclass; dns_rdataclass_t vclass; - const cfg_obj_t *maps[5]; - const cfg_obj_t *nodefault[4]; + const cfg_obj_t *maps[6]; + const cfg_obj_t *nodefault[5]; + const cfg_obj_t *nooptions[3]; const cfg_obj_t *zoptions = NULL; + const cfg_obj_t *toptions = NULL; const cfg_obj_t *options = NULL; - const cfg_obj_t *obj; + const cfg_obj_t *obj = NULL; const char *filename = NULL; const char *initial_file = NULL; const char *kaspname = NULL; @@ -910,23 +917,34 @@ named_zone_configure(const cfg_obj_t *config, const cfg_obj_t *vconfig, bool transferinsecs = ns_server_getoption(named_g_server->sctx, NS_SERVER_TRANSFERINSECS); + REQUIRE(config != NULL); + REQUIRE(zconfig != NULL); + i = 0; - if (zconfig != NULL) { - zoptions = cfg_tuple_get(zconfig, "options"); - nodefault[i] = maps[i] = zoptions; + + zoptions = cfg_tuple_get(zconfig, "options"); + INSIST(zoptions != NULL); + nodefault[i] = nooptions[i] = maps[i] = zoptions; + i++; + + toptions = named_zone_templateopts(config, zoptions); + if (toptions != NULL) { + nodefault[i] = nooptions[i] = maps[i] = toptions; i++; } + + nooptions[i] = NULL; if (vconfig != NULL) { nodefault[i] = maps[i] = cfg_tuple_get(vconfig, "options"); i++; } - if (config != NULL) { - (void)cfg_map_get(config, "options", &options); - if (options != NULL) { - nodefault[i] = maps[i] = options; - i++; - } + + (void)cfg_map_get(config, "options", &options); + if (options != NULL) { + nodefault[i] = maps[i] = options; + i++; } + nodefault[i] = NULL; maps[i++] = named_g_defaults; maps[i] = NULL; @@ -951,7 +969,7 @@ named_zone_configure(const cfg_obj_t *config, const cfg_obj_t *vconfig, dns_zone_setclass(raw, zclass); } - ztype = zonetype_fromconfig(zoptions); + ztype = zonetype_fromconfig(zoptions, toptions); if (raw != NULL) { dns_zone_settype(raw, ztype); dns_zone_settype(zone, dns_zone_primary); @@ -960,13 +978,13 @@ named_zone_configure(const cfg_obj_t *config, const cfg_obj_t *vconfig, } obj = NULL; - result = cfg_map_get(zoptions, "database", &obj); + result = named_config_get(nooptions, "database", &obj); if (result == ISC_R_SUCCESS) { cpval = isc_mem_strdup(mctx, cfg_obj_asstring(obj)); } obj = NULL; - result = cfg_map_get(zoptions, "dlz", &obj); + result = named_config_get(nooptions, "dlz", &obj); if (result == ISC_R_SUCCESS) { const char *dlzname = cfg_obj_asstring(obj); size_t len = strlen(dlzname) + 5; @@ -992,13 +1010,13 @@ named_zone_configure(const cfg_obj_t *config, const cfg_obj_t *vconfig, } obj = NULL; - result = cfg_map_get(zoptions, "file", &obj); + result = named_config_get(nooptions, "file", &obj); if (result == ISC_R_SUCCESS) { filename = cfg_obj_asstring(obj); } obj = NULL; - result = cfg_map_get(zoptions, "initial-file", &obj); + result = named_config_get(nooptions, "initial-file", &obj); if (result == ISC_R_SUCCESS) { initial_file = cfg_obj_asstring(obj); } @@ -1074,7 +1092,7 @@ named_zone_configure(const cfg_obj_t *config, const cfg_obj_t *vconfig, } obj = NULL; - result = cfg_map_get(zoptions, "journal", &obj); + result = named_config_get(nooptions, "journal", &obj); if (result == ISC_R_SUCCESS) { dns_zone_setjournal(mayberaw, cfg_obj_asstring(obj)); } @@ -1433,7 +1451,8 @@ named_zone_configure(const cfg_obj_t *config, const cfg_obj_t *vconfig, cfg_obj_asboolean(obj)); obj = NULL; - result = cfg_map_get(zoptions, "log-report-channel", &obj); + result = named_config_get(nooptions, "log-report-channel", + &obj); if (result == ISC_R_SUCCESS) { logreports = cfg_obj_asboolean(obj); dns_zone_setoption(zone, DNS_ZONEOPT_LOGREPORTS, @@ -1533,7 +1552,8 @@ named_zone_configure(const cfg_obj_t *config, const cfg_obj_t *vconfig, zname); } - CHECK(configure_zone_ssutable(zoptions, mayberaw, zname)); + CHECK(configure_zone_ssutable(zoptions, toptions, mayberaw, + zname)); } /* @@ -1618,7 +1638,8 @@ named_zone_configure(const cfg_obj_t *config, const cfg_obj_t *vconfig, */ if (ztype == dns_zone_primary || ztype == dns_zone_secondary) { const cfg_obj_t *parentals = NULL; - (void)cfg_map_get(zoptions, "parental-agents", &parentals); + (void)named_config_get(nooptions, "parental-agents", + &parentals); if (parentals != NULL) { dns_ipkeylist_t ipkl; dns_ipkeylist_init(&ipkl); @@ -1769,7 +1790,7 @@ named_zone_configure(const cfg_obj_t *config, const cfg_obj_t *vconfig, * are explicitly enabled by zone configuration. */ obj = NULL; - (void)cfg_map_get(zoptions, "allow-transfer", &obj); + (void)named_config_get(nooptions, "allow-transfer", &obj); if (obj == NULL) { dns_acl_t *none; CHECK(dns_acl_none(mctx, &none)); @@ -1782,9 +1803,9 @@ named_zone_configure(const cfg_obj_t *config, const cfg_obj_t *vconfig, case dns_zone_redirect: count = 0; obj = NULL; - (void)cfg_map_get(zoptions, "primaries", &obj); + (void)named_config_get(nooptions, "primaries", &obj); if (obj == NULL) { - (void)cfg_map_get(zoptions, "masters", &obj); + (void)named_config_get(nooptions, "masters", &obj); } /* @@ -1886,7 +1907,7 @@ named_zone_configure(const cfg_obj_t *config, const cfg_obj_t *vconfig, break; case dns_zone_staticstub: - CHECK(configure_staticstub(zoptions, zone, zname, + CHECK(configure_staticstub(zoptions, toptions, zone, zname, default_dbtype)); break; @@ -1927,20 +1948,22 @@ named_zone_reusable(dns_zone_t *zone, const cfg_obj_t *zconfig, const cfg_obj_t *vconfig, const cfg_obj_t *config, dns_kasplist_t *kasplist) { const cfg_obj_t *zoptions = NULL; + const cfg_obj_t *toptions = NULL; const cfg_obj_t *obj = NULL; - const char *cfilename; - const char *zfilename; + const char *cfilename = NULL; + const char *zfilename = NULL; dns_zone_t *raw = NULL; bool has_raw, inline_signing; dns_zonetype_t ztype; zoptions = cfg_tuple_get(zconfig, "options"); + toptions = named_zone_templateopts(config, zoptions); /* * We always reconfigure a static-stub zone for simplicity, assuming * the amount of data to be loaded is small. */ - if (zonetype_fromconfig(zoptions) == dns_zone_staticstub) { + if (zonetype_fromconfig(zoptions, toptions) == dns_zone_staticstub) { dns_zone_log(zone, ISC_LOG_DEBUG(1), "not reusable: staticstub"); return false; @@ -1971,14 +1994,14 @@ named_zone_reusable(dns_zone_t *zone, const cfg_obj_t *zconfig, return false; } - if (zonetype_fromconfig(zoptions) != ztype) { + if (zonetype_fromconfig(zoptions, toptions) != ztype) { dns_zone_log(zone, ISC_LOG_DEBUG(1), "not reusable: type mismatch"); return false; } obj = NULL; - (void)cfg_map_get(zoptions, "file", &obj); + (void)named_config_findopt(zoptions, toptions, "file", &obj); if (obj != NULL) { cfilename = cfg_obj_asstring(obj); } else { @@ -1999,15 +2022,27 @@ named_zone_reusable(dns_zone_t *zone, const cfg_obj_t *zconfig, bool named_zone_inlinesigning(const cfg_obj_t *zconfig, const cfg_obj_t *vconfig, const cfg_obj_t *config, dns_kasplist_t *kasplist) { - const cfg_obj_t *maps[4]; + const cfg_obj_t *maps[5] = { 0 }, *noopts[3] = { 0 }; const cfg_obj_t *signing = NULL; const cfg_obj_t *policy = NULL; + const cfg_obj_t *toptions = NULL; dns_kasp_t *kasp = NULL; isc_result_t res; bool inline_signing = false; int i = 0; - maps[i++] = cfg_tuple_get(zconfig, "options"); + noopts[i] = maps[i] = cfg_tuple_get(zconfig, "options"); + i++; + + if (config != NULL) { + toptions = named_zone_templateopts(config, maps[0]); + if (toptions != NULL) { + noopts[i] = maps[i] = toptions; + i++; + } + } + + noopts[i] = NULL; if (vconfig != NULL) { maps[i++] = cfg_tuple_get(vconfig, "options"); } @@ -2041,13 +2076,35 @@ named_zone_inlinesigning(const cfg_obj_t *zconfig, const cfg_obj_t *vconfig, /* * The zone option 'inline-signing' may override the value in - * dnssec-policy. This is a zone-only option, so look in maps[0] - * only. + * dnssec-policy. This is a zone-only option, so look in the + * zone and template blocks only. */ - res = cfg_map_get(maps[0], "inline-signing", &signing); + res = named_config_get(noopts, "inline-signing", &signing); if (res == ISC_R_SUCCESS && cfg_obj_isboolean(signing)) { return cfg_obj_asboolean(signing); } return inline_signing; } + +const cfg_obj_t * +named_zone_templateopts(const cfg_obj_t *config, const cfg_obj_t *zoptions) { + const cfg_obj_t *templates = NULL; + const cfg_obj_t *obj = NULL; + + (void)cfg_map_get(config, "template", &templates); + (void)cfg_map_get(zoptions, "template", &obj); + if (obj != NULL && templates != NULL) { + const char *tmplname = cfg_obj_asstring(obj); + CFG_LIST_FOREACH (templates, e) { + const cfg_obj_t *t = cfg_tuple_get(cfg_listelt_value(e), + "name"); + if (strcasecmp(cfg_obj_asstring(t), tmplname) == 0) { + return cfg_tuple_get(cfg_listelt_value(e), + "options"); + } + } + } + + return NULL; +} diff --git a/bin/tests/system/addzone/ns2/added.db b/bin/tests/system/addzone/ns2/added.db index 286e717532..849821078c 100644 --- a/bin/tests/system/addzone/ns2/added.db +++ b/bin/tests/system/addzone/ns2/added.db @@ -9,7 +9,6 @@ ; See the COPYRIGHT file distributed with this work for additional ; information regarding copyright ownership. -;$ORIGIN added.example. $TTL 300 ; 5 minutes @ IN SOA mname1. . ( 1 ; serial diff --git a/bin/tests/system/addzone/ns2/named1.conf.in b/bin/tests/system/addzone/ns2/named1.conf.in index bd94f6e751..e85ad22054 100644 --- a/bin/tests/system/addzone/ns2/named1.conf.in +++ b/bin/tests/system/addzone/ns2/named1.conf.in @@ -28,6 +28,12 @@ controls { inet 10.53.0.2 port @CONTROLPORT@ allow { any; } keys { rndc_key; }; }; +template primary { + type primary; + file "$view-$name.db"; + initial-file "added.db"; +}; + zone "." { type hint; file "../../_common/root.hint"; diff --git a/bin/tests/system/addzone/ns2/named2.conf.in b/bin/tests/system/addzone/ns2/named2.conf.in index ef9adeb22f..74bc2659e0 100644 --- a/bin/tests/system/addzone/ns2/named2.conf.in +++ b/bin/tests/system/addzone/ns2/named2.conf.in @@ -27,6 +27,12 @@ options { dnssec-validation no; }; +template primary { + type primary; + file "$view-$name.db"; + initial-file "added.db"; +}; + view internal { match-clients { 10.53.0.2; }; allow-new-zones no; diff --git a/bin/tests/system/addzone/ns2/named3.conf.in b/bin/tests/system/addzone/ns2/named3.conf.in index ca934c4671..0642ac8d5d 100644 --- a/bin/tests/system/addzone/ns2/named3.conf.in +++ b/bin/tests/system/addzone/ns2/named3.conf.in @@ -27,6 +27,12 @@ options { dnssec-validation no; }; +template primary { + type primary; + file "$view-$name.db"; + initial-file "added.db"; +}; + view internal { match-clients { 10.53.0.2; }; allow-new-zones no; diff --git a/bin/tests/system/addzone/tests.sh b/bin/tests/system/addzone/tests.sh index 6d5939c896..5809709656 100755 --- a/bin/tests/system/addzone/tests.sh +++ b/bin/tests/system/addzone/tests.sh @@ -745,5 +745,29 @@ if [ $ret != 0 ]; then echo_i "failed"; fi status=$((status + ret)) n=$((n + 1)) +echo_i "checking addzone with zone template (primary) ($n)" +ret=0 +$RNDCCMD 10.53.0.2 addzone 'template.example in external { template primary; };' 2>&1 | sed 's/^/I:ns2 /' +$DIG +norec $DIGOPTS @10.53.0.2 -b 10.53.0.2 a.template.example a >dig.out.ns2.int.$n || ret=1 +grep 'status: NOERROR' dig.out.ns2.int.$n >/dev/null || ret=1 +grep 'ANSWER: 0' dig.out.ns2.int.$n >/dev/null || ret=1 +$DIG +norec $DIGOPTS @10.53.0.2 -b 10.53.0.4 a.template.example a >dig.out.ns2.ext.$n || ret=1 +grep 'status: NOERROR' dig.out.ns2.ext.$n >/dev/null || ret=1 +grep 'ANSWER: 1' dig.out.ns2.ext.$n >/dev/null || ret=1 +test -f ns2/external-template.example.db +n=$((n + 1)) +if [ $ret != 0 ]; then echo_i "failed"; fi +status=$((status + ret)) + +echo_i "checking addzone with nonexistent template ($n)" +ret=0 +nextpart ns2/named.run >/dev/null +$RNDCCMD 10.53.0.2 addzone 'wrong.example in external { template nope; };' 2>&1 | grep -qF "failure" || ret=1 +nextpart ns2/named.run | grep -qF "zone 'wrong.example': template 'nope' not found" || ret=1 +test -f ns2/wrong-template.example.db && ret=1 +n=$((n + 1)) +if [ $ret != 0 ]; then echo_i "failed"; fi +status=$((status + ret)) + echo_i "exit status: $status" [ $status -eq 0 ] || exit 1 diff --git a/bin/tests/system/addzone/tests_sh_addzone.py b/bin/tests/system/addzone/tests_sh_addzone.py index 4ca4440c04..af7bf4b2aa 100644 --- a/bin/tests/system/addzone/tests_sh_addzone.py +++ b/bin/tests/system/addzone/tests_sh_addzone.py @@ -26,6 +26,7 @@ pytestmark = pytest.mark.extra_artifacts( "ns2/K*.private", "ns2/K*.state", "ns2/external.nzd", + "ns2/external-template.example.db", "ns2/extra.nzd", "ns2/inline.db.jbk", "ns2/inline.db.signed", diff --git a/bin/tests/system/checkconf/bad-template-1.conf b/bin/tests/system/checkconf/bad-template-1.conf new file mode 100644 index 0000000000..1240d757ba --- /dev/null +++ b/bin/tests/system/checkconf/bad-template-1.conf @@ -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. + */ + +template a { + type primary; + file "$name.db"; + initial-file "template.db"; +}; + +zone example { + template a; + type secondary; +}; diff --git a/bin/tests/system/checkconf/bad-template-2.conf b/bin/tests/system/checkconf/bad-template-2.conf new file mode 100644 index 0000000000..7502f6c97c --- /dev/null +++ b/bin/tests/system/checkconf/bad-template-2.conf @@ -0,0 +1,22 @@ +/* + * 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. + */ + +template a { + type primary; + file "$name.db"; + initial-file "template.db"; +}; + +template b { + template a; +}; diff --git a/bin/tests/system/checkconf/bad-template-3.conf b/bin/tests/system/checkconf/bad-template-3.conf new file mode 100644 index 0000000000..728f4e2406 --- /dev/null +++ b/bin/tests/system/checkconf/bad-template-3.conf @@ -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. + */ + +template a { + type primary; + file "$name.db"; + initial-file "template.db"; +}; + +zone example.com { + # specify an undefined template + template c; +}; diff --git a/bin/tests/system/checkconf/bad-template-4.conf b/bin/tests/system/checkconf/bad-template-4.conf new file mode 100644 index 0000000000..d98dd43882 --- /dev/null +++ b/bin/tests/system/checkconf/bad-template-4.conf @@ -0,0 +1,17 @@ +/* + * 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. + */ + +zone example.com { + # specify a template, but there are no templates + template c; +}; diff --git a/bin/tests/system/checkconf/good-template-1.conf b/bin/tests/system/checkconf/good-template-1.conf new file mode 100644 index 0000000000..5637590ce7 --- /dev/null +++ b/bin/tests/system/checkconf/good-template-1.conf @@ -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. + */ + +template a { + type primary; + file "$name.db"; + initial-file "template.db"; +}; + +zone example { + template a; + file "othername.db"; +}; diff --git a/bin/tests/system/masterfile/ns2/named.conf.j2 b/bin/tests/system/masterfile/ns2/named.conf.j2 index 442d273a8c..4e66b4f1c0 100644 --- a/bin/tests/system/masterfile/ns2/named.conf.j2 +++ b/bin/tests/system/masterfile/ns2/named.conf.j2 @@ -34,14 +34,23 @@ zone "." { file "../../_common/root.hint"; }; -zone "example" { +template primary { type primary; file "$name.db"; }; +zone "example" { + template primary; +}; + zone "missing" { - type primary; - file "$name.db"; + template primary; +}; + +zone "different" { + template primary; + initial-file "example.db"; + file "alternate.db"; }; zone "initial" { diff --git a/bin/tests/system/masterfile/tests_masterfile.py b/bin/tests/system/masterfile/tests_masterfile.py index 8b25f6db07..862be26bae 100644 --- a/bin/tests/system/masterfile/tests_masterfile.py +++ b/bin/tests/system/masterfile/tests_masterfile.py @@ -17,7 +17,9 @@ import dns.zone import isctest import pytest -pytestmark = pytest.mark.extra_artifacts(["ns2/copied.db", "ns2/present.db"]) +pytestmark = pytest.mark.extra_artifacts( + ["ns2/copied.db", "ns2/present.db", "ns2/alternate.db"] +) def test_masterfile_include_semantics(): @@ -91,7 +93,20 @@ example. 300 IN SOA mname1. . 2010042407 20 20 1814400 3600 def test_masterfile_initial_file(): - """Test zone configuration with initial template files""" + """Test zone configurations with initial template files""" + # example inherited its configuration from the template, + # make sure it works + msg_soa = dns.message.make_query("example.", "SOA") + res_soa = isctest.query.tcp(msg_soa, "10.53.0.2") + expected_soa_rr = """;ANSWER +example. 300 IN SOA mname1. . 2010042407 20 20 1814400 3600 +""" + expected = dns.message.from_text(expected_soa_rr) + isctest.check.rrsets_equal(res_soa.answer, expected.answer) + + # initial uses an initial-file option with the "file" + # option set to "copied.db". make sure it works and that + # copied.db has been populated. msg_soa = dns.message.make_query("initial.", "SOA") res_soa = isctest.query.tcp(msg_soa, "10.53.0.2") expected_soa_rr = """;ANSWER @@ -101,13 +116,30 @@ initial. 300 IN SOA mname1. . 2010042407 20 20 1814400 3600 isctest.check.rrsets_equal(res_soa.answer, expected.answer) isctest.check.file_contents_equal("ns2/example.db", "ns2/copied.db") - # the 'present.db' file already existed and shouldn't load + # present uses an initial-file option, but the file 'present.db' + # already exists and is empty, so the initial-file should not be + # copied into place and the zone should not load. msg_soa = dns.message.make_query("present.", "SOA") res_soa = isctest.query.tcp(msg_soa, "10.53.0.2") isctest.check.servfail(res_soa) isctest.check.file_empty("ns2/present.db") +def test_masterfile_template_override(): + """Test zone configurations with overridden template options""" + # different inherited configuration from the template, but + # overrides the "file" option to 'alternate.db'. + msg_soa = dns.message.make_query("different.", "SOA") + res_soa = isctest.query.tcp(msg_soa, "10.53.0.2") + expected_soa_rr = """;ANSWER +different. 300 IN SOA mname1. . 2010042407 20 20 1814400 3600 +""" + expected = dns.message.from_text(expected_soa_rr) + isctest.check.rrsets_equal(res_soa.answer, expected.answer) + isctest.check.file_contents_equal("ns2/example.db", "ns2/alternate.db") + assert not os.path.exists("ns2/different.db") + + def test_masterfile_missing_master_file_servfail(): """Test nameserver returning SERVFAIL for a missing master file""" msg_soa = dns.message.make_query("missing.", "SOA") diff --git a/doc/arm/reference.rst b/doc/arm/reference.rst index 63535eddde..a26db7103a 100644 --- a/doc/arm/reference.rst +++ b/doc/arm/reference.rst @@ -7038,6 +7038,13 @@ mid-1970s. Zone data for it can be specified with the ``CHAOS`` class. Zone Options ^^^^^^^^^^^^ +.. namedconf:statement:: template + :tags: zone + :short: Specifies a template to use for zone configuration. + + This specifies a zone template from which to import other zone options. + See :ref:`zone_templates` for details. + :any:`allow-notify` See the description of :any:`allow-notify` in :ref:`access_control`. @@ -7377,6 +7384,52 @@ Zone Options :any:`send-report-channel` See the description of :any:`send-report-channel` in :namedconf:ref:`options`. +.. _zone_templates: + +Zone Templates +^^^^^^^^^^^^^^ + +To simplify the configuration of multiple similar zones, BIND 9 +supports a zone template mechanism. ``template`` blocks can be +defined at the top level of the configuration; these blocks can +contain any set of options that could be set in a :any:`zone` +statement, with the exceptions of :any:`in-view` and :any:`template`. + +Once a template has been defined, it can be referenced in a +:any:`zone` statement; the zone is then configured using the +options specified in the :any:`template` as defaults. +Options that are locally defined within the :any:`zone` statement +override the template. + +For example, the following configuration would define two primary +and two secondary zones: + + :: + + template primary { + type primary; + file "$type/$name.db"; + initial-file "initial.db"; + }; + + template secondary { + type secondary; + file "$type/$name.db"; + primaries { 192.0.2.1; }; + }; + + zone example.com { template primary; }; + zone example.org { template primary; }; + zone example.net { template secondary; }; + zone example.edu { template secondary; }; + +Templates can also be used for zones that are added using +``rndc addzone`` (see :any:`allow-new-zones`): + + :: + + $ rndc addzone example.biz '{ template secondary; };' + .. _dynamic_update_policies: Dynamic Update Policies diff --git a/doc/misc/forward.zoneopt b/doc/misc/forward.zoneopt index a0d26b12d8..af060cf347 100644 --- a/doc/misc/forward.zoneopt +++ b/doc/misc/forward.zoneopt @@ -2,4 +2,5 @@ zone [ ] { type forward; forward ( first | only ); forwarders [ port ] [ tls ] { ( | ) [ port ] [ tls ]; ... }; + template ; }; diff --git a/doc/misc/hint.zoneopt b/doc/misc/hint.zoneopt index 2d2c98de4d..260db7fb5f 100644 --- a/doc/misc/hint.zoneopt +++ b/doc/misc/hint.zoneopt @@ -2,4 +2,5 @@ zone [ ] { type hint; check-names ( fail | warn | ignore ); file ; + template ; }; diff --git a/doc/misc/mirror.zoneopt b/doc/misc/mirror.zoneopt index 2f49a34e9c..aa193235a7 100644 --- a/doc/misc/mirror.zoneopt +++ b/doc/misc/mirror.zoneopt @@ -38,6 +38,7 @@ zone [ ] { request-expire ; request-ixfr ; request-ixfr-max-diffs ; + template ; transfer-source ( | * ); transfer-source-v6 ( | * ); try-tcp-refresh ; diff --git a/doc/misc/options b/doc/misc/options index 1cb0d96443..3215fc7af7 100644 --- a/doc/misc/options +++ b/doc/misc/options @@ -362,6 +362,93 @@ statistics-channels { inet ( | | * ) [ port ( | * ) ] [ allow { ; ... } ]; // may occur multiple times }; // optional (only available if configured), may occur multiple times +template { + allow-notify { ; ... }; + allow-query { ; ... }; + allow-query-on { ; ... }; + allow-transfer [ port ] [ transport ] { ; ... }; + allow-update { ; ... }; + allow-update-forwarding { ; ... }; + also-notify [ port ] [ source ( | * ) ] [ source-v6 ( | * ) ] { ( | [ port ] | [ port ] ) [ key ] [ tls ]; ... }; + check-dup-records ( fail | warn | ignore ); + check-integrity ; + check-mx ( fail | warn | ignore ); + check-mx-cname ( fail | warn | ignore ); + check-names ( fail | warn | ignore ); + check-sibling ; + check-spf ( warn | ignore ); + check-srv-cname ( fail | warn | ignore ); + check-svcb ; + check-wildcard ; + checkds ( explicit | ); + database ; + dlz ; + dnskey-sig-validity ; // obsolete + dnssec-dnskey-kskonly ; // obsolete + dnssec-loadkeys-interval ; + dnssec-policy ; + dnssec-secure-to-insecure ; // obsolete + dnssec-update-mode ( maintain | no-resign ); // obsolete + file ; + forward ( first | only ); + forwarders [ port ] [ tls ] { ( | ) [ port ] [ tls ]; ... }; + initial-file ; + inline-signing ; + ixfr-from-differences ; + journal ; + key-directory ; + log-report-channel ; + masterfile-format ( raw | text ); + masterfile-style ( full | relative ); + max-ixfr-ratio ( unlimited | ); + max-journal-size ( default | unlimited | ); + max-records ; + max-records-per-type ; + max-refresh-time ; + max-retry-time ; + max-transfer-idle-in ; + max-transfer-idle-out ; + max-transfer-time-in ; + max-transfer-time-out ; + max-types-per-name ; + max-zone-ttl ( unlimited | ); // deprecated + min-refresh-time ; + min-retry-time ; + min-transfer-rate-in ; + multi-master ; + notify ( explicit | master-only | primary-only | ); + notify-defer ; + notify-delay ; + notify-source ( | * ); + notify-source-v6 ( | * ); + notify-to-soa ; + nsec3-test-zone ; // test only + parental-agents [ port ] [ source ( | * ) ] [ source-v6 ( | * ) ] { ( | [ port ] | [ port ] ) [ key ] [ tls ]; ... }; + parental-source ( | * ); + parental-source-v6 ( | * ); + primaries [ port ] [ source ( | * ) ] [ source-v6 ( | * ) ] { ( | [ port ] | [ port ] ) [ key ] [ tls ]; ... }; + provide-zoneversion ; + request-expire ; + request-ixfr ; + request-ixfr-max-diffs ; + send-report-channel ; + serial-update-method ( date | increment | unixtime ); + server-addresses { ( | ); ... }; + server-names { ; ... }; + sig-signing-nodes ; + sig-signing-signatures ; + sig-signing-type ; + sig-validity-interval [ ]; // obsolete + transfer-source ( | * ); + transfer-source-v6 ( | * ); + try-tcp-refresh ; + type ( primary | master | secondary | slave | mirror | forward | hint | redirect | static-stub | stub ); + update-check-ksk ; // obsolete + update-policy ( local | { ( deny | grant ) ( 6to4-self | external | krb5-self | krb5-selfsub | krb5-subdomain | krb5-subdomain-self-rhs | ms-self | ms-selfsub | ms-subdomain | ms-subdomain-self-rhs | name | self | selfsub | selfwild | subdomain | tcp-self | wildcard | zonesub ) [ ] ; ... } ); + zero-no-soa-ttl ; + zone-statistics ( full | terse | none | ); +}; // may occur multiple times + tls { ca-file ; cert-file ; diff --git a/doc/misc/primary.zoneopt b/doc/misc/primary.zoneopt index 74a314999a..dd1b94756b 100644 --- a/doc/misc/primary.zoneopt +++ b/doc/misc/primary.zoneopt @@ -60,6 +60,7 @@ zone [ ] { sig-signing-signatures ; sig-signing-type ; sig-validity-interval [ ]; // obsolete + template ; update-check-ksk ; // obsolete update-policy ( local | { ( deny | grant ) ( 6to4-self | external | krb5-self | krb5-selfsub | krb5-subdomain | krb5-subdomain-self-rhs | ms-self | ms-selfsub | ms-subdomain | ms-subdomain-self-rhs | name | self | selfsub | selfwild | subdomain | tcp-self | wildcard | zonesub ) [ ] ; ... } ); zero-no-soa-ttl ; diff --git a/doc/misc/redirect.zoneopt b/doc/misc/redirect.zoneopt index f457c807c9..e338b6e231 100644 --- a/doc/misc/redirect.zoneopt +++ b/doc/misc/redirect.zoneopt @@ -11,5 +11,6 @@ zone [ ] { max-types-per-name ; max-zone-ttl ( unlimited | ); // deprecated primaries [ port ] [ source ( | * ) ] [ source-v6 ( | * ) ] { ( | [ port ] | [ port ] ) [ key ] [ tls ]; ... }; + template ; zone-statistics ( full | terse | none | ); }; diff --git a/doc/misc/secondary.zoneopt b/doc/misc/secondary.zoneopt index b1dbcea978..7529112a33 100644 --- a/doc/misc/secondary.zoneopt +++ b/doc/misc/secondary.zoneopt @@ -60,6 +60,7 @@ zone [ ] { sig-signing-signatures ; sig-signing-type ; sig-validity-interval [ ]; // obsolete + template ; transfer-source ( | * ); transfer-source-v6 ( | * ); try-tcp-refresh ; diff --git a/doc/misc/static-stub.zoneopt b/doc/misc/static-stub.zoneopt index 40a340f629..14928922dd 100644 --- a/doc/misc/static-stub.zoneopt +++ b/doc/misc/static-stub.zoneopt @@ -9,5 +9,6 @@ zone [ ] { max-types-per-name ; server-addresses { ( | ); ... }; server-names { ; ... }; + template ; zone-statistics ( full | terse | none | ); }; diff --git a/doc/misc/stub.zoneopt b/doc/misc/stub.zoneopt index 97b9ba0578..4d25095484 100644 --- a/doc/misc/stub.zoneopt +++ b/doc/misc/stub.zoneopt @@ -21,6 +21,7 @@ zone [ ] { min-transfer-rate-in ; multi-master ; primaries [ port ] [ source ( | * ) ] [ source-v6 ( | * ) ] { ( | [ port ] | [ port ] ) [ key ] [ tls ]; ... }; + template ; transfer-source ( | * ); transfer-source-v6 ( | * ); zone-statistics ( full | terse | none | ); diff --git a/lib/isccfg/check.c b/lib/isccfg/check.c index 22cb3df899..a3c79085da 100644 --- a/lib/isccfg/check.c +++ b/lib/isccfg/check.c @@ -2923,14 +2923,15 @@ check: } /* - * Try to find a zone option in one of up to three levels of options: - * for example, the zone, view, and global option blocks. + * Try to find a zone option in one of up to four levels of options: + * for example, the zone, template, view, and global option blocks. * (Fewer levels can be specified for options that aren't defined at - * all three levels.) + * all four levels.) */ static isc_result_t get_zoneopt(const cfg_obj_t *opts1, const cfg_obj_t *opts2, - const cfg_obj_t *opts3, const char *name, const cfg_obj_t **objp) { + const cfg_obj_t *opts3, const cfg_obj_t *opts4, const char *name, + const cfg_obj_t **objp) { isc_result_t result = ISC_R_NOTFOUND; REQUIRE(*objp == NULL); @@ -2944,6 +2945,9 @@ get_zoneopt(const cfg_obj_t *opts1, const cfg_obj_t *opts2, if (*objp == NULL && opts3 != NULL) { result = cfg_map_get(opts3, name, objp); } + if (*objp == NULL && opts4 != NULL) { + result = cfg_map_get(opts4, name, objp); + } return result; } @@ -2958,13 +2962,14 @@ isccfg_check_zoneconf(const cfg_obj_t *zconfig, const cfg_obj_t *voptions, const char *znamestr = NULL; const char *typestr = NULL; const char *target = NULL; + const char *tmplname = NULL; int ztype; - const cfg_obj_t *zoptions, *goptions = NULL; + const cfg_obj_t *zoptions = NULL, *toptions = NULL, *goptions = NULL; const cfg_obj_t *obj = NULL, *kasp = NULL; - const cfg_obj_t *inviewobj = NULL; + const cfg_obj_t *templates = NULL, *inviewobj = NULL; isc_result_t result = ISC_R_SUCCESS; isc_result_t tresult; - unsigned int i; + unsigned int i = 0; dns_rdataclass_t zclass; dns_fixedname_t fixedname; dns_name_t *zname = NULL; /* NULL if parsing of zone name fails. */ @@ -2976,6 +2981,7 @@ isccfg_check_zoneconf(const cfg_obj_t *zconfig, const cfg_obj_t *voptions, bool ddns = false; bool has_dnssecpolicy = false; bool kasp_inlinesigning = false; + bool inline_signing = false; const void *clauses = NULL; const char *option = NULL; const char *kaspname = NULL; @@ -2995,14 +3001,37 @@ isccfg_check_zoneconf(const cfg_obj_t *zconfig, const cfg_obj_t *voptions, cfg_map_get(config, "options", &goptions); } - inviewobj = NULL; + /* If the zone specifies a template, find it too */ + (void)cfg_map_get(config, "template", &templates); + (void)cfg_map_get(zoptions, "template", &obj); + if (obj != NULL) { + tmplname = cfg_obj_asstring(obj); + + CFG_LIST_FOREACH (templates, e) { + const cfg_obj_t *t = cfg_tuple_get(cfg_listelt_value(e), + "name"); + if (strcasecmp(cfg_obj_asstring(t), tmplname) == 0) { + toptions = cfg_tuple_get(cfg_listelt_value(e), + "options"); + break; + } + } + + if (toptions == NULL) { + cfg_obj_log(zconfig, ISC_LOG_ERROR, + "zone '%s': template '%s' not found", + znamestr, tmplname); + return ISC_R_FAILURE; + } + } + (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); + (void)get_zoneopt(zoptions, toptions, NULL, NULL, "type", &obj); if (obj == NULL) { cfg_obj_log(zconfig, ISC_LOG_ERROR, "zone '%s': type not present", znamestr); @@ -3195,7 +3224,8 @@ isccfg_check_zoneconf(const cfg_obj_t *zconfig, const cfg_obj_t *voptions, * Check if a dnssec-policy is set. */ obj = NULL; - (void)get_zoneopt(zoptions, voptions, goptions, "dnssec-policy", &obj); + (void)get_zoneopt(zoptions, toptions, voptions, goptions, + "dnssec-policy", &obj); if (obj != NULL) { kaspname = cfg_obj_asstring(obj); if (strcmp(kaspname, "default") == 0) { @@ -3258,8 +3288,8 @@ isccfg_check_zoneconf(const cfg_obj_t *zconfig, const cfg_obj_t *voptions, * */ if (has_dnssecpolicy) { obj = NULL; - (void)get_zoneopt(zoptions, voptions, goptions, "max-zone-ttl", - &obj); + (void)get_zoneopt(zoptions, toptions, voptions, goptions, + "max-zone-ttl", &obj); if (obj != NULL) { cfg_obj_log(obj, ISC_LOG_ERROR, "zone '%s': option 'max-zone-ttl' " @@ -3278,13 +3308,19 @@ isccfg_check_zoneconf(const cfg_obj_t *zconfig, const cfg_obj_t *voptions, 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)) - { + bool topt = false; + (void)cfg_map_get(zoptions, option, &obj); + if (obj == NULL && toptions != NULL) { + (void)cfg_map_get(toptions, option, &obj); + topt = true; + } + if (obj != NULL && !cfg_clause_validforzone(option, ztype)) { cfg_obj_log(obj, ISC_LOG_WARNING, "option '%s' is not allowed " - "in '%s' zone '%s'", - option, typestr, znamestr); + "in '%s' zone '%s'%s%s%s", + option, typestr, znamestr, + topt ? " (referencing template '" : "", + topt ? tmplname : "", topt ? "')" : ""); result = ISC_R_FAILURE; } option = cfg_map_nextclause(&cfg_type_zoneopts, &clauses, &i); @@ -3321,9 +3357,9 @@ isccfg_check_zoneconf(const cfg_obj_t *zconfig, const cfg_obj_t *voptions, bool donotify = true; obj = NULL; - tresult = get_zoneopt(zoptions, voptions, goptions, "notify", - &obj); - if (tresult == ISC_R_SUCCESS) { + (void)get_zoneopt(zoptions, toptions, voptions, goptions, + "notify", &obj); + if (obj != NULL) { if (cfg_obj_isboolean(obj)) { donotify = cfg_obj_asboolean(obj); } else { @@ -3338,18 +3374,19 @@ isccfg_check_zoneconf(const cfg_obj_t *zconfig, const cfg_obj_t *voptions, } obj = NULL; - tresult = cfg_map_get(zoptions, "also-notify", &obj); - if (tresult == ISC_R_SUCCESS && !donotify) { + (void)get_zoneopt(zoptions, toptions, NULL, NULL, "also-notify", + &obj); + if (obj != NULL && !donotify) { cfg_obj_log(zoptions, ISC_LOG_WARNING, "zone '%s': 'also-notify' set but " "'notify' is disabled", znamestr); } - if (tresult != ISC_R_SUCCESS) { - tresult = get_zoneopt(voptions, goptions, NULL, - "also-notify", &obj); + if (obj == NULL) { + (void)get_zoneopt(voptions, goptions, NULL, NULL, + "also-notify", &obj); } - if (tresult == ISC_R_SUCCESS && donotify) { + if (obj != NULL && donotify) { uint32_t count; tresult = validate_remotes(obj, config, &count, mctx); if (tresult != ISC_R_SUCCESS && result == ISC_R_SUCCESS) @@ -3370,15 +3407,18 @@ isccfg_check_zoneconf(const cfg_obj_t *zconfig, const cfg_obj_t *voptions, !dns_name_equal(zname, dns_rootname))) { obj = NULL; - (void)cfg_map_get(zoptions, "primaries", &obj); + (void)get_zoneopt(zoptions, toptions, NULL, NULL, "primaries", + &obj); if (obj == NULL) { /* If "primaries" was unset, check for "masters" */ - (void)cfg_map_get(zoptions, "masters", &obj); + (void)get_zoneopt(zoptions, toptions, NULL, NULL, + "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); + (void)get_zoneopt(zoptions, toptions, NULL, NULL, + "masters", &obj2); if (obj2 != NULL) { cfg_obj_log(obj, ISC_LOG_ERROR, "'primaries' and 'masters' cannot " @@ -3414,7 +3454,8 @@ isccfg_check_zoneconf(const cfg_obj_t *zconfig, const cfg_obj_t *voptions, */ if (ztype == CFG_ZONE_PRIMARY || ztype == CFG_ZONE_SECONDARY) { obj = NULL; - (void)cfg_map_get(zoptions, "parental-agents", &obj); + (void)get_zoneopt(zoptions, toptions, NULL, NULL, + "parental-agents", &obj); if (obj != NULL) { uint32_t count; tresult = validate_remotes(obj, config, &count, mctx); @@ -3451,22 +3492,22 @@ isccfg_check_zoneconf(const cfg_obj_t *zconfig, const cfg_obj_t *voptions, */ 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 cfg_obj_t *au = NULL, *up = NULL; - 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, ISC_LOG_ERROR, + (void)get_zoneopt(zoptions, toptions, NULL, NULL, + "allow-update", &au); + (void)get_zoneopt(zoptions, toptions, NULL, NULL, + "update-policy", &up); + + if (au != NULL && up != NULL) { + cfg_obj_log(au, 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); - if (res3 != ISC_R_SUCCESS) { + } else if (up != NULL) { + tresult = check_update_policy(up); + if (tresult != ISC_R_SUCCESS) { result = ISC_R_FAILURE; } } @@ -3476,18 +3517,18 @@ isccfg_check_zoneconf(const cfg_obj_t *zconfig, const cfg_obj_t *voptions, * we should also check for allow-update at the * view and options levels. */ - if (res1 != ISC_R_SUCCESS) { - res1 = get_zoneopt(voptions, goptions, NULL, - "allow-update", &au); + if (au == NULL) { + (void)get_zoneopt(voptions, goptions, NULL, NULL, + "allow-update", &au); } - if (res2 == ISC_R_SUCCESS) { + if (up != NULL) { ddns = true; - } else if (res1 == ISC_R_SUCCESS) { + } else if (au != NULL) { dns_acl_t *acl = NULL; - res1 = cfg_acl_fromconfig(au, config, actx, mctx, 0, - &acl); - if (res1 != ISC_R_SUCCESS) { + tresult = cfg_acl_fromconfig(au, config, actx, mctx, 0, + &acl); + if (tresult != ISC_R_SUCCESS) { cfg_obj_log(au, ISC_LOG_ERROR, "acl expansion failed: %s", isc_result_totext(result)); @@ -3501,9 +3542,10 @@ isccfg_check_zoneconf(const cfg_obj_t *zconfig, const cfg_obj_t *voptions, } obj = NULL; - res1 = cfg_map_get(zoptions, "inline-signing", &obj); - if (res1 == ISC_R_SUCCESS) { - signing = cfg_obj_asboolean(obj); + (void)get_zoneopt(zoptions, toptions, NULL, NULL, + "inline-signing", &obj); + if (obj != NULL) { + inline_signing = signing = cfg_obj_asboolean(obj); } else if (has_dnssecpolicy) { signing = kasp_inlinesigning; } @@ -3527,8 +3569,9 @@ isccfg_check_zoneconf(const cfg_obj_t *zconfig, const cfg_obj_t *voptions, } obj = NULL; - res1 = cfg_map_get(zoptions, "sig-signing-type", &obj); - if (res1 == ISC_R_SUCCESS) { + (void)get_zoneopt(zoptions, toptions, NULL, NULL, + "sig-signing-type", &obj); + if (obj != NULL) { uint32_t type = cfg_obj_asuint32(obj); if (type < 0xff00U || type > 0xffffU) { cfg_obj_log(obj, ISC_LOG_ERROR, @@ -3540,10 +3583,9 @@ isccfg_check_zoneconf(const cfg_obj_t *zconfig, const cfg_obj_t *voptions, } obj = NULL; - res1 = cfg_map_get(zoptions, "dnssec-loadkeys-interval", &obj); - if (res1 == ISC_R_SUCCESS && ztype == CFG_ZONE_SECONDARY && - !signing) - { + (void)get_zoneopt(zoptions, toptions, NULL, NULL, + "dnssec-loadkeys-interval", &obj); + if (obj != NULL && ztype == CFG_ZONE_SECONDARY && !signing) { cfg_obj_log(obj, ISC_LOG_ERROR, "dnssec-loadkeys-interval: requires " "inline-signing when used in secondary " @@ -3557,7 +3599,8 @@ isccfg_check_zoneconf(const cfg_obj_t *zconfig, const cfg_obj_t *voptions, */ obj = NULL; if (root) { - (void)get_zoneopt(voptions, goptions, NULL, "forwarders", &obj); + (void)get_zoneopt(voptions, goptions, NULL, NULL, "forwarders", + &obj); } if (check_forward(config, zoptions, obj) != ISC_R_SUCCESS) { result = ISC_R_FAILURE; @@ -3569,13 +3612,15 @@ isccfg_check_zoneconf(const cfg_obj_t *zconfig, const cfg_obj_t *voptions, */ if (ztype == CFG_ZONE_FORWARD && (rfc1918 || ula)) { obj = NULL; - (void)cfg_map_get(zoptions, "forward", &obj); + (void)get_zoneopt(zoptions, toptions, NULL, NULL, "forward", + &obj); if (obj == NULL) { /* - * Forward mode not explicitly configured. + * Forward mode not explicitly configured + * at the zone or template level. */ - (void)get_zoneopt(voptions, goptions, NULL, "forward", - &obj); + (void)get_zoneopt(voptions, goptions, NULL, NULL, + "forward", &obj); if (obj == NULL || strcasecmp(cfg_obj_asstring(obj), "first") == 0) { @@ -3593,7 +3638,8 @@ isccfg_check_zoneconf(const cfg_obj_t *zconfig, const cfg_obj_t *voptions, * Check validity of static stub server addresses. */ obj = NULL; - (void)cfg_map_get(zoptions, "server-addresses", &obj); + (void)get_zoneopt(zoptions, toptions, NULL, NULL, "server-addresses", + &obj); if (ztype == CFG_ZONE_STATICSTUB && obj != NULL) { CFG_LIST_FOREACH (obj, element) { isc_sockaddr_t sa; @@ -3616,7 +3662,7 @@ isccfg_check_zoneconf(const cfg_obj_t *zconfig, const cfg_obj_t *voptions, * Check validity of static stub server names. */ obj = NULL; - (void)cfg_map_get(zoptions, "server-names", &obj); + (void)get_zoneopt(zoptions, toptions, NULL, NULL, "server-names", &obj); if (zname != NULL && ztype == CFG_ZONE_STATICSTUB && obj != NULL) { CFG_LIST_FOREACH (obj, element) { const char *snamestr = NULL; @@ -3649,7 +3695,8 @@ isccfg_check_zoneconf(const cfg_obj_t *zconfig, const cfg_obj_t *voptions, } obj = NULL; - (void)cfg_map_get(zoptions, "send-report-channel", &obj); + (void)get_zoneopt(zoptions, toptions, NULL, NULL, "send-report-channel", + &obj); if (obj != NULL) { const char *str = cfg_obj_asstring(obj); dns_fixedname_t fad; @@ -3677,7 +3724,8 @@ isccfg_check_zoneconf(const cfg_obj_t *zconfig, const cfg_obj_t *voptions, * Warn if key-directory doesn't exist */ obj = NULL; - (void)get_zoneopt(zoptions, voptions, goptions, "key-directory", &obj); + (void)get_zoneopt(zoptions, toptions, voptions, goptions, + "key-directory", &obj); if (obj != NULL) { dir = cfg_obj_asstring(obj); @@ -3726,8 +3774,9 @@ isccfg_check_zoneconf(const cfg_obj_t *zconfig, const cfg_obj_t *voptions, */ if (ztype == CFG_ZONE_PRIMARY || ztype == CFG_ZONE_SECONDARY) { obj = NULL; - tresult = cfg_map_get(zoptions, "log-report-channel", &obj); - if (tresult == ISC_R_SUCCESS && cfg_obj_asboolean(obj) && + (void)get_zoneopt(zoptions, toptions, NULL, NULL, + "log-report-channel", &obj); + if (obj != NULL && cfg_obj_asboolean(obj) && dns_name_equal(zname, dns_rootname)) { cfg_obj_log(zconfig, ISC_LOG_ERROR, @@ -3754,14 +3803,14 @@ isccfg_check_zoneconf(const cfg_obj_t *zconfig, const cfg_obj_t *voptions, */ obj = NULL; dlz = false; - tresult = cfg_map_get(zoptions, "dlz", &obj); - if (tresult == ISC_R_SUCCESS) { + (void)get_zoneopt(zoptions, toptions, NULL, NULL, "dlz", &obj); + if (obj != NULL) { dlz = true; } obj = NULL; - tresult = cfg_map_get(zoptions, "database", &obj); - if (dlz && tresult == ISC_R_SUCCESS) { + (void)get_zoneopt(zoptions, toptions, NULL, NULL, "database", &obj); + if (dlz && obj != NULL) { cfg_obj_log(zconfig, ISC_LOG_ERROR, "zone '%s': cannot specify both 'dlz' " "and 'database'", @@ -3769,28 +3818,23 @@ isccfg_check_zoneconf(const cfg_obj_t *zconfig, const cfg_obj_t *voptions, if (result == ISC_R_SUCCESS) { result = ISC_R_FAILURE; } - } else if (!dlz && - (tresult == ISC_R_NOTFOUND || - (tresult == ISC_R_SUCCESS && - strcmp(ZONEDB_DEFAULT, cfg_obj_asstring(obj)) == 0))) + } else if (!dlz && (obj == NULL || + strcmp(ZONEDB_DEFAULT, 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 && + (void)get_zoneopt(zoptions, toptions, NULL, NULL, "file", + &fileobj); + if (fileobj == NULL && (ztype == CFG_ZONE_PRIMARY || ztype == CFG_ZONE_HINT || - (ztype == CFG_ZONE_SECONDARY && res1 == ISC_R_SUCCESS && - cfg_obj_asboolean(obj)))) + (ztype == CFG_ZONE_SECONDARY && inline_signing))) { cfg_obj_log(zconfig, ISC_LOG_ERROR, "zone '%s': missing 'file' entry", znamestr); if (result == ISC_R_SUCCESS) { - result = tresult; + result = ISC_R_FAILURE; } - } else if (tresult == ISC_R_SUCCESS && files != NULL && + } else if (fileobj != NULL && files != NULL && (ztype == CFG_ZONE_SECONDARY || ztype == CFG_ZONE_MIRROR || ddns || has_dnssecpolicy)) @@ -3800,7 +3844,7 @@ isccfg_check_zoneconf(const cfg_obj_t *zconfig, const cfg_obj_t *voptions, { result = tresult; } - } else if (tresult == ISC_R_SUCCESS && files != NULL && + } else if (fileobj != NULL && files != NULL && (ztype == CFG_ZONE_PRIMARY || ztype == CFG_ZONE_HINT)) { @@ -3817,13 +3861,13 @@ isccfg_check_zoneconf(const cfg_obj_t *zconfig, const cfg_obj_t *voptions, * consistent. */ obj = NULL; - tresult = get_zoneopt(zoptions, voptions, goptions, "masterfile-format", - &obj); + tresult = get_zoneopt(zoptions, toptions, voptions, goptions, + "masterfile-format", &obj); if (tresult == ISC_R_SUCCESS && strcasecmp(cfg_obj_asstring(obj), "raw") == 0) { obj = NULL; - tresult = get_zoneopt(zoptions, voptions, goptions, + tresult = get_zoneopt(zoptions, toptions, voptions, goptions, "masterfile-style", &obj); if (tresult == ISC_R_SUCCESS) { cfg_obj_log(obj, ISC_LOG_ERROR, @@ -3838,8 +3882,8 @@ isccfg_check_zoneconf(const cfg_obj_t *zconfig, const cfg_obj_t *voptions, } obj = NULL; - (void)get_zoneopt(zoptions, voptions, goptions, "max-journal-size", - &obj); + (void)get_zoneopt(zoptions, toptions, voptions, goptions, + "max-journal-size", &obj); if (obj != NULL && cfg_obj_isuint64(obj)) { uint64_t value = cfg_obj_asuint64(obj); if (value > DNS_JOURNAL_SIZE_MAX) { @@ -3854,8 +3898,8 @@ isccfg_check_zoneconf(const cfg_obj_t *zconfig, const cfg_obj_t *voptions, } obj = NULL; - (void)get_zoneopt(zoptions, voptions, goptions, "min-transfer-rate-in", - &obj); + (void)get_zoneopt(zoptions, toptions, voptions, goptions, + "min-transfer-rate-in", &obj); if (obj != NULL) { uint32_t traffic_bytes = cfg_obj_asuint32(cfg_tuple_get(obj, "traffic_bytes")); diff --git a/lib/isccfg/namedconf.c b/lib/isccfg/namedconf.c index 82c50890f9..d5c61b2402 100644 --- a/lib/isccfg/namedconf.c +++ b/lib/isccfg/namedconf.c @@ -143,6 +143,8 @@ static cfg_type_t cfg_type_sizeval; static cfg_type_t cfg_type_sockaddr4wild; static cfg_type_t cfg_type_sockaddr6wild; static cfg_type_t cfg_type_statschannels; +static cfg_type_t cfg_type_template; +static cfg_type_t cfg_type_templateopts; static cfg_type_t cfg_type_tlsconf; static cfg_type_t cfg_type_view; static cfg_type_t cfg_type_viewopts; @@ -471,6 +473,18 @@ static cfg_type_t cfg_type_zone = { "zone", cfg_parse_tuple, cfg_print_tuple, cfg_doc_tuple, &cfg_rep_tuple, zone_fields }; +/*% + * A zone statement. + */ +static cfg_tuplefielddef_t template_fields[] = { + { "name", &cfg_type_astring, 0 }, + { "options", &cfg_type_templateopts, 0 }, + { NULL, NULL, 0 } +}; +static cfg_type_t cfg_type_template = { "template", cfg_parse_tuple, + cfg_print_tuple, cfg_doc_tuple, + &cfg_rep_tuple, template_fields }; + /*% * A dnssec-policy statement. */ @@ -1155,6 +1169,7 @@ static cfg_clausedef_t namedconf_clauses[] = { { "statistics-channels", &cfg_type_statschannels, CFG_CLAUSEFLAG_MULTI | CFG_CLAUSEFLAG_NOTCONFIGURED }, #endif + { "template", &cfg_type_template, CFG_CLAUSEFLAG_MULTI }, { "tls", &cfg_type_tlsconf, CFG_CLAUSEFLAG_MULTI }, { "view", &cfg_type_view, CFG_CLAUSEFLAG_MULTI }, { NULL, NULL, 0 } @@ -2429,7 +2444,6 @@ static cfg_clausedef_t zone_only_clauses[] = { { "file", &cfg_type_qstring, CFG_ZONE_PRIMARY | CFG_ZONE_SECONDARY | CFG_ZONE_MIRROR | CFG_ZONE_STUB | CFG_ZONE_HINT | CFG_ZONE_REDIRECT }, - { "in-view", &cfg_type_astring, CFG_ZONE_INVIEW }, { "initial-file", &cfg_type_qstring, CFG_ZONE_PRIMARY }, { "inline-signing", &cfg_type_boolean, CFG_ZONE_PRIMARY | CFG_ZONE_SECONDARY }, @@ -2457,6 +2471,15 @@ static cfg_clausedef_t zone_only_clauses[] = { { NULL, NULL, 0 } }; +static cfg_clausedef_t non_template_clauses[] = { + { "in-view", &cfg_type_astring, CFG_ZONE_INVIEW }, + { "template", &cfg_type_astring, + CFG_ZONE_PRIMARY | CFG_ZONE_SECONDARY | CFG_ZONE_MIRROR | + CFG_ZONE_STUB | CFG_ZONE_STATICSTUB | CFG_ZONE_DELEGATION | + CFG_ZONE_HINT | CFG_ZONE_REDIRECT | CFG_ZONE_FORWARD }, + { NULL, NULL, 0 } +}; + /*% The top-level named.conf syntax. */ static cfg_clausedef_t *namedconf_clausesets[] = { namedconf_clauses, @@ -2493,11 +2516,24 @@ static cfg_type_t cfg_type_viewopts = { "view", cfg_parse_map, /*% The "zone" statement syntax. */ -static cfg_clausedef_t *zone_clausesets[] = { zone_only_clauses, zone_clauses, +static cfg_clausedef_t *zone_clausesets[] = { non_template_clauses, + zone_only_clauses, zone_clauses, NULL }; cfg_type_t cfg_type_zoneopts = { "zoneopts", cfg_parse_map, cfg_print_map, cfg_doc_map, &cfg_rep_map, zone_clausesets }; +/*% + * The "template" statement syntax: any clause that "zone" can take, + * except that zones can have a "template" option and templates cannot. + */ + +static cfg_clausedef_t *template_clausesets[] = { zone_only_clauses, + zone_clauses, NULL }; +static cfg_type_t cfg_type_templateopts = { + "templateopts", cfg_parse_map, cfg_print_map, + cfg_doc_map, &cfg_rep_map, template_clausesets +}; + /*% The "dnssec-policy" statement syntax. */ static cfg_clausedef_t *dnssecpolicy_clausesets[] = { dnssecpolicy_clauses, NULL }; @@ -3845,6 +3881,14 @@ cfg_clause_validforzone(const char *name, unsigned int ztype) { } valid = true; } + for (clause = non_template_clauses; clause->name != NULL; clause++) { + if ((clause->flags & ztype) == 0 || + strcmp(clause->name, name) != 0) + { + continue; + } + valid = true; + } return valid; } @@ -3853,23 +3897,25 @@ void cfg_print_zonegrammar(const unsigned int zonetype, unsigned int flags, void (*f)(void *closure, const char *text, int textlen), void *closure) { -#define NCLAUSES \ - (((sizeof(zone_clauses) + sizeof(zone_only_clauses)) / \ - sizeof(clause[0])) - \ - 1) +#define NCLAUSES \ + ARRAY_SIZE(non_template_clauses) + ARRAY_SIZE(zone_clauses) + \ + ARRAY_SIZE(zone_only_clauses) - 2 cfg_printer_t pctx; - cfg_clausedef_t *clause = NULL; cfg_clausedef_t clauses[NCLAUSES]; + cfg_clausedef_t *clause = clauses; pctx.f = f; pctx.closure = closure; pctx.indent = 0; pctx.flags = flags; - memmove(clauses, zone_clauses, sizeof(zone_clauses)); - memmove(clauses + sizeof(zone_clauses) / sizeof(zone_clauses[0]) - 1, - zone_only_clauses, sizeof(zone_only_clauses)); + memmove(clause, zone_clauses, sizeof(zone_clauses)); + clause += ARRAY_SIZE(zone_clauses) - 1; + memmove(clause, zone_only_clauses, sizeof(zone_only_clauses)); + clause += ARRAY_SIZE(zone_only_clauses) - 1; + memmove(clause, non_template_clauses, sizeof(non_template_clauses)); + qsort(clauses, NCLAUSES - 1, sizeof(clause[0]), cmp_clause); cfg_print_cstr(&pctx, "zone [ ] {\n");