From: Mark Andrews Date: Thu, 2 Apr 2026 04:25:09 +0000 (+1100) Subject: Make zone filename expansion accessible from outside dns_zone X-Git-Url: http://git.ipfire.org/?a=commitdiff_plain;h=20f8e9eb56900a9df321fe1e091762897cb8ce36;p=thirdparty%2Fbind9.git Make zone filename expansion accessible from outside dns_zone This adds a new API call dns_zone_expandzonefie(), which will enable named-checkconf to expand filenames the same way the server does in dns_zone_setfile(). --- diff --git a/lib/dns/include/dns/zoneproperties.h b/lib/dns/include/dns/zoneproperties.h index ed7a8b18818..524ae33132b 100644 --- a/lib/dns/include/dns/zoneproperties.h +++ b/lib/dns/include/dns/zoneproperties.h @@ -1557,3 +1557,36 @@ dns_zone_getkeystores(dns_zone_t *zone); * Get the keystores pointer, it should never be NULL once the server is * initialized. */ + +void +dns_zone_expandzonefile(isc_buffer_t *b, const char *filename, + const dns_name_t *zonename, const char *viewname, + const char *typename); +/*%< + * Expands the zone file name ('filename') using the inputs + * 'zonename', 'viewname' and 'typename'. The expanded file name + * is stored in the buffer 'b'. The follow expansions are available: + * + * - $name or "%s" to the zone name, in lowercase + * - $type or "%t" to the zone type + * - $view or "%v" to the view name + * - $char1 or "%1" to the first character of the zone name + * - $char2 or "%2" to the second character of the zone name (or a dot if + * there is no second character) + * - $char3 or "%3" to the third character of the zone name (or a dot if + * there is no third character) + * - $label1 or "%z" to the toplevel domain of the zone (or a dot if it is + * the TLD) + * - $label2 or "%y" to the next label under the toplevel domain (or a dot if + * there is no next label) + * - $label2 or "%x" to the next-next label under the toplevel domain (or a + * dot if there is no next-next label) + * + * If 'viewname' is NULL, it is treated as an empty string. + * + * Requires: + * \li 'b' to be non NULL. + * \li 'filename' to be non NULL. + * \li 'zonename' to be a valid name. + * \li 'typename' to be non NULL. + */ diff --git a/lib/dns/zoneproperties.c b/lib/dns/zoneproperties.c index 06c0c57c271..96f9d8eef40 100644 --- a/lib/dns/zoneproperties.c +++ b/lib/dns/zoneproperties.c @@ -273,15 +273,23 @@ setstring(dns_zone_t *zone, char **field, const char *value) { } typedef struct foundtoken foundtoken_t; -typedef isc_result_t (*tokenparse_t)(const dns_zone_t *zone, +typedef struct token_names token_names_t; +typedef isc_result_t (*tokenparse_t)(const token_names_t *names, const foundtoken_t *token, isc_buffer_t *b); + struct foundtoken { const char *pos; size_t len; tokenparse_t parse; }; +struct token_names { + dns_name_t *zonename; + const char *viewname; + const char *typename; +}; + static int foundtoken_order(const void *a, const void *b) { /* sort char pointers in order of which occurs first in memory */ @@ -301,80 +309,62 @@ putmem(isc_buffer_t *b, const char *base, size_t length) { } static isc_result_t -tokenparse_type(const dns_zone_t *zone, const foundtoken_t *token, +tokenparse_type(const token_names_t *names, const foundtoken_t *token, isc_buffer_t *b) { - const char *typename = dns_zonetype_name(zone->type); - UNUSED(token); - return putmem(b, typename, strlen(typename)); + return putmem(b, names->typename, strlen(names->typename)); } static isc_result_t -tokenparse_name(const dns_zone_t *zone, const foundtoken_t *token, - isc_buffer_t *b) { - isc_result_t result; - char buf[DNS_NAME_FORMATSIZE]; - dns_fixedname_t fn; - dns_name_t *name = dns_fixedname_initname(&fn); +tokenparse_name(const token_names_t *names, + const foundtoken_t *token ISC_ATTR_UNUSED, isc_buffer_t *b) { + char name[DNS_NAME_FORMATSIZE]; - UNUSED(token); - - result = dns_name_downcase(&zone->origin, name); - RUNTIME_CHECK(result == ISC_R_SUCCESS); - dns_name_format(name, buf, sizeof(buf)); - result = putmem(b, buf, strlen(buf)); - - return result; + dns_name_format(names->zonename, name, sizeof(name)); + return putmem(b, name, strlen(name)); } static isc_result_t -tokenparse_view(const dns_zone_t *zone, const foundtoken_t *token, +tokenparse_view(const token_names_t *names, const foundtoken_t *token, isc_buffer_t *b) { UNUSED(token); - return putmem(b, zone->view->name, strlen(zone->view->name)); + return putmem(b, names->viewname, strlen(names->viewname)); } static isc_result_t -tokenparse_char(const dns_zone_t *zone, const foundtoken_t *token, +tokenparse_char(const token_names_t *names, const foundtoken_t *token, isc_buffer_t *b) { - isc_result_t result; - char buf[DNS_NAME_FORMATSIZE]; - dns_fixedname_t fn; - dns_name_t *name = dns_fixedname_initname(&fn); + char name[DNS_NAME_FORMATSIZE]; size_t chartokidx; char c; - result = dns_name_downcase(&zone->origin, name); - RUNTIME_CHECK(result == ISC_R_SUCCESS); - dns_name_format(name, buf, sizeof(buf)); + dns_name_format(names->zonename, name, sizeof(name)); chartokidx = token->pos[token->len - 1] - '1'; INSIST(chartokidx <= 2); - if (chartokidx < strlen(buf)) { - c = buf[chartokidx]; + if (chartokidx < strlen(name)) { + c = name[chartokidx]; } else { c = '.'; } - result = putmem(b, &c, 1); - - return result; + return putmem(b, &c, 1); } static isc_result_t -tokenparse_label(const dns_zone_t *zone, const foundtoken_t *token, +tokenparse_label(const token_names_t *names, const foundtoken_t *token, isc_buffer_t *b) { isc_result_t result; char buf[DNS_NAME_FORMATSIZE]; - dns_fixedname_t fn; - dns_name_t *name = dns_fixedname_initname(&fn); - unsigned int labels = dns_name_countlabels(&zone->origin); + dns_fixedname_t ft; + dns_name_t *target = dns_fixedname_initname(&ft); + unsigned int labels; char labeltokidx; int ilabel = -1; - result = dns_name_fromstring(name, ".", NULL, 0, NULL); - INSIST(result == ISC_R_SUCCESS); + dns_name_copy(dns_rootname, target); + labels = dns_name_countlabels(names->zonename); labeltokidx = token->pos[token->len - 1]; if (token->len == 2) { @@ -389,23 +379,18 @@ tokenparse_label(const dns_zone_t *zone, const foundtoken_t *token, INSIST(labeltokidx >= '1' && labeltokidx <= '3'); } - /* - * Even if implicit, the root label counts as one label. - */ if (labeltokidx == '1' || labeltokidx == 'z') { - ilabel = labels - 2; + ilabel = labels - 1; } else if (labeltokidx == '2' || labeltokidx == 'y') { - ilabel = labels - 3; + ilabel = labels - 2; } else if (labeltokidx == '3' || labeltokidx == 'x') { - ilabel = labels - 4; + ilabel = labels - 3; } if (ilabel >= 0) { - dns_name_getlabelsequence(&zone->origin, ilabel, 1, name); - result = dns_name_downcase(name, name); - RUNTIME_CHECK(result == ISC_R_SUCCESS); + dns_name_getlabelsequence(names->zonename, ilabel, 1, target); } - dns_name_format(name, buf, sizeof(buf)); + dns_name_format(target, buf, sizeof(buf)); result = putmem(b, buf, strlen(buf)); return result; @@ -447,22 +432,33 @@ static const token_t tokens[] = { * * Cap the length at PATH_MAX. */ -static void -setfilename(dns_zone_t *zone, char **field, const char *value) { +void +dns_zone_expandzonefile(isc_buffer_t *b, const char *filename, + const dns_name_t *zonename, const char *viewname, + const char *typename) { isc_result_t result; foundtoken_t founds[ARRAY_SIZE(tokens)]; - char filename[PATH_MAX]; - isc_buffer_t b; + dns_fixedname_t fz; size_t tags = 0; + token_names_t names = { .zonename = dns_fixedname_initname(&fz), + .viewname = viewname, + .typename = typename }; - if (value == NULL) { - *field = NULL; - return; + REQUIRE(zonename != NULL); + REQUIRE(filename != NULL); + REQUIRE(typename != NULL); + + if (viewname == NULL) { + names.viewname = ""; } + /* Normalize the name by converting to lower case */ + result = dns_name_downcase(zonename, names.zonename); + INSIST(result == ISC_R_SUCCESS); + for (size_t i = 0; i < ARRAY_SIZE(tokens); i++) { const token_t *token = &tokens[i]; - const char *p = strcasestr(value, token->name); + const char *p = strcasestr(filename, token->name); if (p != NULL) { founds[tags++] = @@ -473,34 +469,48 @@ setfilename(dns_zone_t *zone, char **field, const char *value) { } if (tags == 0) { - setstring(zone, field, value); - return; + putmem(b, filename, strlen(filename)); + goto cleanup; } - isc_buffer_init(&b, filename, sizeof(filename)); - /* sort the tag offsets in order of occurrence */ qsort(founds, tags, sizeof(foundtoken_t), foundtoken_order); - const char *p = value; + const char *p = filename; for (size_t i = 0; i < tags; i++) { foundtoken_t *token = &founds[i]; - CHECK(putmem(&b, p, token->pos - p)); + CHECK(putmem(b, p, token->pos - p)); p = token->pos; INSIST(p != NULL); - CHECK(token->parse(zone, token, &b)); + CHECK(token->parse(&names, token, b)); /* Advance the input pointer past the token */ p += founds[i].len; } - const char *end = value + strlen(value); - putmem(&b, p, end - p); + const char *end = filename + strlen(filename); + putmem(b, p, end - p); cleanup: - isc_buffer_putuint8(&b, 0); + isc_buffer_putuint8(b, 0); +} + +static void +setfilename(dns_zone_t *zone, char **field, const char *value) { + char filename[PATH_MAX]; + isc_buffer_t b; + + if (value == NULL) { + *field = NULL; + return; + } + + isc_buffer_init(&b, filename, sizeof(filename)); + dns_zone_expandzonefile(&b, value, &zone->origin, + zone->view != NULL ? zone->view->name : NULL, + dns_zonetype_name(zone->type)); setstring(zone, field, filename); } diff --git a/tests/dns/zonefile_test.c b/tests/dns/zonefile_test.c index b3bbb9568e0..6858dfacc3a 100644 --- a/tests/dns/zonefile_test.c +++ b/tests/dns/zonefile_test.c @@ -34,7 +34,7 @@ #include typedef struct { - const char *name, *input, *expected; + const char *name, *view, *type, *input, *expected; } zonefile_test_params_t; static int @@ -50,103 +50,106 @@ teardown_test(void **state) { } ISC_LOOP_TEST_IMPL(filename) { - isc_result_t result; - dns_zone_t *zone = NULL; + isc_buffer_t b; + dns_fixedname_t of; + dns_name_t *origin = dns_fixedname_initname(&of); + char buf[PATH_MAX]; const zonefile_test_params_t tests[] = { - { "example.COM", "$name", "example.com" }, - { "example.COM", "$name.db", "example.com.db" }, - { "example.COM", "./dir/$name.db", "./dir/example.com.db" }, - { "example.COM", "%s", "example.com" }, - { "example.COM", "%s.db", "example.com.db" }, - { "example.COM", "./dir/%s.db", "./dir/example.com.db" }, - { "example.COM", "$type", "primary" }, - { "example.COM", "$type-file", "primary-file" }, - { "example.COM", "./dir/$type", "./dir/primary" }, - { "example.COM", "./$type/$name.db", + { "example.COM", "local", "primary", "$name", "example.com" }, + { "example.COM", "local", "primary", "$name.db", + "example.com.db" }, + { "example.COM", "local", "primary", "./dir/$name.db", + "./dir/example.com.db" }, + { "example.COM", "local", "primary", "%s", "example.com" }, + { "example.COM", "local", "primary", "%s.db", + "example.com.db" }, + { "example.COM", "local", "primary", "./dir/%s.db", + "./dir/example.com.db" }, + { "example.COM", "local", "primary", "$type", "primary" }, + { "example.COM", "local", "primary", "$type-file", + "primary-file" }, + { "example.COM", "local", "primary", "./dir/$type", + "./dir/primary" }, + { "example.COM", "local", "secondary", "./dir/$type", + "./dir/secondary" }, + { "example.COM", "local", "primary", "./$type/$name.db", "./primary/example.com.db" }, - { "example.COM", "%t", "primary" }, - { "example.COM", "%t-file", "primary-file" }, - { "example.COM", "./dir/%t", "./dir/primary" }, - { "example.COM", "./%t/%s.db", "./primary/example.com.db" }, - { "example.COM", "./$TyPe/$NAmE.db", + { "example.COM", "local", "primary", "%t", "primary" }, + { "example.COM", "local", "primary", "%t-file", + "primary-file" }, + { "example.COM", "local", "primary", "./dir/%t", + "./dir/primary" }, + { "example.COM", "local", "primary", "./%t/%s.db", "./primary/example.com.db" }, - { "example.COM", "./$name/$type", "./example.com/primary" }, - { "example.COM", "$name.$type", "example.com.primary" }, - { "example.COM", "$type$name", "primaryexample.com" }, - { "example.COM", "$type$type", "primary$type" }, - { "example.COM", "$name$name", "example.com$name" }, - { "example.COM", "typename", "typename" }, - { "example.COM", "$view", "local" }, - { "example.COM", "%v", "local" }, - { "example.COM", "./$type/$view-$name.db", + { "example.COM", "local", "secondary", "./%t/%s.db", + "./secondary/example.com.db" }, + { "example.COM", "local", "primary", "./$TyPe/$NAmE.db", + "./primary/example.com.db" }, + { "example.COM", "local", "primary", "./$name/$type", + "./example.com/primary" }, + { "example.COM", "local", "primary", "$name.$type", + "example.com.primary" }, + { "example.COM", "local", "primary", "$type$name", + "primaryexample.com" }, + { "example.COM", "local", "primary", "$type$type", + "primary$type" }, + { "example.COM", "local", "primary", "$name$name", + "example.com$name" }, + { "example.COM", "local", "primary", "typename", "typename" }, + { "example.COM", "local", "primary", "$view", "local" }, + { "example.COM", NULL, "primary", "$view", "" }, + { "example.COM", "local", "primary", "%v", "local" }, + { "example.COM", "local", "primary", "./$type/$view-$name.db", "./primary/local-example.com.db" }, - { "example.COM", "./$view/$type-$name.db", + { "example.COM", "local", "primary", "./$view/$type-$name.db", "./local/primary-example.com.db" }, - { "example.COM", "./$name/$view-$type.db", + { "example.COM", "local", "primary", "./$name/$view-$type.db", "./example.com/local-primary.db" }, - { "example.COM", "./%s/%v-%t.db", + { "example.COM", "local", "primary", "./%s/%v-%t.db", "./example.com/local-primary.db" }, - { "example.COM", "", "" }, - { "example.COM", "$char1", "e" }, - { "example.COM", "$char2", "x" }, - { "example.COM", "$char3", "a" }, - { "example.COM", "%1", "e" }, - { "example.COM", "%2", "x" }, - { "example.COM", "%3", "a" }, - { "example.COM", "$label1", "com" }, - { "example.COM", "$label2", "example" }, - { "example.COM", "$label3", "." }, - { "example.COM", "%z", "com" }, - { "example.COM", "%y", "example" }, - { "example.COM", "%x", "." }, - { "example", "$label1", "example" }, - { "example", "$label2", "." }, - { "example", "$label3", "." }, - { "a.b.c.d.e", "$label1", "e" }, - { "a.b.c.d.e", "$label2", "d" }, - { "a.b.c.d.e", "$label3", "c" }, - { "a.b.c", "$char1", "a" }, - { "a.b.c", "$char2", "." }, - { "a.b.c", "$char3", "b" }, - { "a.b.c", "%1", "a" }, - { "a.b.c", "%2", "." }, - { "a.b.c", "%3", "b" }, - { "a", "%1", "a" }, - { "a", "%2", "." }, - { "a", "%3", "." }, - { "a.b.c.d", "%1$char2%3$label1%x", "a.bdb" } + { "example.COM", "local", "primary", "", "" }, + { "example.COM", "local", "primary", "$char1", "e" }, + { "example.COM", "local", "primary", "$char2", "x" }, + { "example.COM", "local", "primary", "$char3", "a" }, + { "example.COM", "local", "primary", "%1", "e" }, + { "example.COM", "local", "primary", "%2", "x" }, + { "example.COM", "local", "primary", "%3", "a" }, + { "example.COM", "local", "primary", "$label1", "com" }, + { "example.COM", "local", "primary", "$label2", "example" }, + { "example.COM", "local", "primary", "$label3", "." }, + { "example.COM", "local", "primary", "%z", "com" }, + { "example.COM", "local", "primary", "%y", "example" }, + { "example.COM", "local", "primary", "%x", "." }, + { "example", "local", "primary", "$label1", "example" }, + { "example", "local", "primary", "$label2", "." }, + { "example", "local", "primary", "$label3", "." }, + { "a.b.c.d.e", "local", "primary", "$label1", "e" }, + { "a.b.c.d.e", "local", "primary", "$label2", "d" }, + { "a.b.c.d.e", "local", "primary", "$label3", "c" }, + { "a.b.c", "local", "primary", "$char1", "a" }, + { "a.b.c", "local", "primary", "$char2", "." }, + { "a.b.c", "local", "primary", "$char3", "b" }, + { "a.b.c", "local", "primary", "%1", "a" }, + { "a.b.c", "local", "primary", "%2", "." }, + { "a.b.c", "local", "primary", "%3", "b" }, + { "a", "local", "primary", "%1", "a" }, + { "a", "local", "primary", "%2", "." }, + { "a", "local", "primary", "%3", "." }, + { "a.b.c.d", "local", "primary", "%1$char2%3$label1%x", + "a.bdb" } }; - dns_view_t *view = NULL; - result = dns_test_makeview("local", false, false, &view); - assert_int_equal(result, ISC_R_SUCCESS); - for (size_t i = 0; i < ARRAY_SIZE(tests); i++) { - result = dns_test_makezone(tests[i].name, &zone, view, false); - assert_int_equal(result, ISC_R_SUCCESS); - - dns_zone_setview(zone, view); - 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); - - dns_zone_detach(&zone); + isc_buffer_init(&b, buf, sizeof(buf)); + dns_test_namefromstring(tests[i].name, &of); + dns_zone_expandzonefile(&b, tests[i].input, origin, + tests[i].view, tests[i].type); + assert_string_equal(buf, tests[i].expected); } - /* 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); - /* 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 @@ -154,13 +157,14 @@ ISC_LOOP_TEST_IMPL(filename) { * 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); + + isc_buffer_init(&b, buf, sizeof(buf)); + dns_test_namefromstring("example.COM", &of); + dns_zone_expandzonefile(&b, longname, origin, "local", "primary"); memmove(longname, "example.com", 11); - assert_string_equal(dns_zone_getfile(zone), longname); + assert_string_equal(buf, longname); - dns_zone_detach(&zone); isc_loopmgr_shutdown(); }