zone "example" {
type primary;
- file "example.db";
+ file "$name.db";
};
zone "missing" {
type primary;
- file "missing.db";
+ file "$name.db";
};
zone "initial" {
:tags: zone
:short: Specifies the zone's filename.
- This sets the zone's filename. In :any:`primary <type primary>`, :any:`hint <type hint>`, and :any:`redirect <type redirect>`
+ This sets the zone's filename. In :any:`primary <type primary>`,
+ :any:`hint <type hint>`, and :any:`redirect <type redirect>`
zones which do not have :any:`primaries` defined, zone data is loaded from
this file. In :any:`secondary <type secondary>`, :any:`mirror <type mirror>`, :any:`stub <type stub>`, and :any:`redirect <type 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
::
$ rndc addzone example.com \
- '{ type primary; file "example.db"; initial-file "template.db"; };'
+ '{ type primary; file "$name.db"; initial-file "template.db"; };'
+
+ This creates a zone ``example.com``, with filename ``example.com.db``.
- Using "@" to reference the zone origin name within ``template.db``
- allows the same file to be used with multiple zones, as in:
+ Using "@" to reference the zone origin within the initial file
+ allows the same file to be used for multiple zones, as in:
::
*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) {
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) {
transport_test \
tsig_test \
update_test \
+ zonefile_test \
zonemgr_test \
zt_test
--- /dev/null
+/*
+ * 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 <inttypes.h>
+#include <sched.h> /* IWYU pragma: keep */
+#include <setjmp.h>
+#include <stdarg.h>
+#include <stdbool.h>
+#include <stddef.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+
+#define UNIT_TESTING
+#include <cmocka.h>
+
+#include <isc/atomic.h>
+#include <isc/lib.h>
+
+#include <dns/lib.h>
+#include <dns/view.h>
+
+#include <tests/dns.h>
+
+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