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) {
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);
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;
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 {
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;
import difflib
import shutil
+import os
from typing import Optional
import dns.rcode
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
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";
+};
--- /dev/null
+#!/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
import dns.zone
import isctest
+import pytest
+
+pytestmark = pytest.mark.extra_artifacts(["ns2/copied.db", "ns2/present.db"])
def test_masterfile_include_semantics():
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")
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.
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>;
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);
*/
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.
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 */
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);
}
}
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;
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
}
}
+ 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");
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 },
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);
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);
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();
/*
* 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) {