mirror of
https://gitlab.isc.org/isc-projects/bind9
synced 2025-08-29 05:28:00 +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:
parent
60b129da25
commit
598ae3f63c
@ -36,12 +36,12 @@ zone "." {
|
|||||||
|
|
||||||
zone "example" {
|
zone "example" {
|
||||||
type primary;
|
type primary;
|
||||||
file "example.db";
|
file "$name.db";
|
||||||
};
|
};
|
||||||
|
|
||||||
zone "missing" {
|
zone "missing" {
|
||||||
type primary;
|
type primary;
|
||||||
file "missing.db";
|
file "$name.db";
|
||||||
};
|
};
|
||||||
|
|
||||||
zone "initial" {
|
zone "initial" {
|
||||||
|
@ -7132,13 +7132,21 @@ Zone Options
|
|||||||
:tags: zone
|
:tags: zone
|
||||||
:short: Specifies the zone's filename.
|
: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
|
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
|
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
|
which do have :any:`primaries` defined, zone data is retrieved from
|
||||||
another server and saved in this file. This option is not applicable
|
another server and saved in this file. This option is not applicable
|
||||||
to other zone types.
|
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`
|
:any:`forward`
|
||||||
This option is only meaningful if the zone has a forwarders list. The ``only`` value
|
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
|
causes the lookup to fail after trying the forwarders and getting no
|
||||||
@ -7163,10 +7171,12 @@ Zone Options
|
|||||||
::
|
::
|
||||||
|
|
||||||
$ rndc addzone example.com \
|
$ 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``
|
This creates a zone ``example.com``, with filename ``example.com.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:
|
||||||
|
|
||||||
::
|
::
|
||||||
|
|
||||||
|
104
lib/dns/zone.c
104
lib/dns/zone.c
@ -1797,6 +1797,108 @@ setstring(dns_zone_t *zone, char **field, const char *value) {
|
|||||||
*field = copy;
|
*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
|
void
|
||||||
dns_zone_setfile(dns_zone_t *zone, const char *file, const char *initial_file,
|
dns_zone_setfile(dns_zone_t *zone, const char *file, const char *initial_file,
|
||||||
dns_masterformat_t format, const dns_master_style_t *style) {
|
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);
|
REQUIRE(zone->stream == NULL);
|
||||||
|
|
||||||
LOCK_ZONE(zone);
|
LOCK_ZONE(zone);
|
||||||
setstring(zone, &zone->masterfile, file);
|
setfilename(zone, &zone->masterfile, file);
|
||||||
setstring(zone, &zone->initfile, initial_file);
|
setstring(zone, &zone->initfile, initial_file);
|
||||||
zone->masterformat = format;
|
zone->masterformat = format;
|
||||||
if (format == dns_masterformat_text) {
|
if (format == dns_masterformat_text) {
|
||||||
|
@ -51,6 +51,7 @@ check_PROGRAMS = \
|
|||||||
transport_test \
|
transport_test \
|
||||||
tsig_test \
|
tsig_test \
|
||||||
update_test \
|
update_test \
|
||||||
|
zonefile_test \
|
||||||
zonemgr_test \
|
zonemgr_test \
|
||||||
zt_test
|
zt_test
|
||||||
|
|
||||||
|
121
tests/dns/zonefile_test.c
Normal file
121
tests/dns/zonefile_test.c
Normal 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
|
Loading…
x
Reference in New Issue
Block a user