]> git.ipfire.org Git - thirdparty/bind9.git/commitdiff
Make zone filename expansion accessible from outside dns_zone
authorMark Andrews <marka@isc.org>
Thu, 2 Apr 2026 04:25:09 +0000 (15:25 +1100)
committerEvan Hunt <each@isc.org>
Wed, 15 Apr 2026 04:49:59 +0000 (21:49 -0700)
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().

lib/dns/include/dns/zoneproperties.h
lib/dns/zoneproperties.c
tests/dns/zonefile_test.c

index ed7a8b188188a0bc7ac2860de880f9b47cc953dd..524ae33132b730838b91f0a4ee441a18b33ba413 100644 (file)
@@ -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.
+ */
index 06c0c57c271f532bb710e8d32f8179439c37a427..96f9d8eef40dde132ba5ce88fdcf0d2fde27d543 100644 (file)
@@ -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);
 }
 
index b3bbb9568e060ca32f58579fad049d8a5e37cc0d..6858dfacc3adf8742e3e2d4b679f9eea59e7a010 100644 (file)
@@ -34,7 +34,7 @@
 #include <tests/dns.h>
 
 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();
 }