2
0
mirror of https://gitlab.isc.org/isc-projects/bind9 synced 2025-08-22 01:59:26 +00:00

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 <zonename> \
     { type primary; file "$name.db"; initial-file "template.db"
This commit is contained in:
Evan Hunt 2025-04-15 21:17:44 -07:00
parent 60b129da25
commit 598ae3f63c
5 changed files with 241 additions and 7 deletions

View File

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

View File

@ -7132,13 +7132,21 @@ Zone Options
: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
@ -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:
::

View File

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

View File

@ -51,6 +51,7 @@ check_PROGRAMS = \
transport_test \
tsig_test \
update_test \
zonefile_test \
zonemgr_test \
zt_test

121
tests/dns/zonefile_test.c Normal file
View File

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