mirror of
https://gitlab.isc.org/isc-projects/bind9
synced 2025-08-22 01:59:26 +00:00
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"; };'
This commit is contained in:
parent
2ad9516a72
commit
60b129da25
@ -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) {
|
||||
|
@ -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);
|
||||
|
@ -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;
|
||||
|
@ -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
|
||||
|
@ -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";
|
||||
};
|
||||
|
19
bin/tests/system/masterfile/setup.sh
Normal file
19
bin/tests/system/masterfile/setup.sh
Normal file
@ -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
|
@ -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")
|
||||
|
@ -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 <type 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.
|
||||
|
@ -27,6 +27,7 @@ zone <string> [ <class> ] {
|
||||
file <quoted_string>;
|
||||
forward ( first | only );
|
||||
forwarders [ port <integer> ] [ tls <string> ] { ( <ipv4_address> | <ipv6_address> ) [ port <integer> ] [ tls <string> ]; ... };
|
||||
initial-file <quoted_string>;
|
||||
inline-signing <boolean>;
|
||||
ixfr-from-differences <boolean>;
|
||||
journal <quoted_string>;
|
||||
|
@ -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);
|
||||
|
@ -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.
|
||||
|
@ -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");
|
||||
|
||||
|
@ -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 },
|
||||
|
@ -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);
|
||||
|
@ -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();
|
||||
|
@ -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) {
|
||||
|
Loading…
x
Reference in New Issue
Block a user