]> git.ipfire.org Git - thirdparty/bind9.git/commitdiff
Add support for zone templates
authorEvan Hunt <each@isc.org>
Tue, 15 Apr 2025 20:39:57 +0000 (13:39 -0700)
committerEvan Hunt <each@isc.org>
Tue, 3 Jun 2025 19:03:07 +0000 (12:03 -0700)
A "template" statement can contain the same configuration clauses
as a "zone" statement.  A "zone" statement can now reference a
template, and all the clauses in that template will be used as
default values for the zone. For example:

    template primary {
        type primary;
        file "$name.db";
        initial-file "primary.db";
    };

    zone example.com {
        template primary;
        file "different-name.db"; // overrides the template
    };

30 files changed:
bin/named/config.c
bin/named/include/named/config.h
bin/named/include/named/zoneconf.h
bin/named/server.c
bin/named/zoneconf.c
bin/tests/system/addzone/ns2/added.db
bin/tests/system/addzone/ns2/named1.conf.in
bin/tests/system/addzone/ns2/named2.conf.in
bin/tests/system/addzone/ns2/named3.conf.in
bin/tests/system/addzone/tests.sh
bin/tests/system/addzone/tests_sh_addzone.py
bin/tests/system/checkconf/bad-template-1.conf [new file with mode: 0644]
bin/tests/system/checkconf/bad-template-2.conf [new file with mode: 0644]
bin/tests/system/checkconf/bad-template-3.conf [new file with mode: 0644]
bin/tests/system/checkconf/bad-template-4.conf [new file with mode: 0644]
bin/tests/system/checkconf/good-template-1.conf [new file with mode: 0644]
bin/tests/system/masterfile/ns2/named.conf.j2
bin/tests/system/masterfile/tests_masterfile.py
doc/arm/reference.rst
doc/misc/forward.zoneopt
doc/misc/hint.zoneopt
doc/misc/mirror.zoneopt
doc/misc/options
doc/misc/primary.zoneopt
doc/misc/redirect.zoneopt
doc/misc/secondary.zoneopt
doc/misc/static-stub.zoneopt
doc/misc/stub.zoneopt
lib/isccfg/check.c
lib/isccfg/namedconf.c

index babf61ad8f1500aa66a923a4319520897e036f5c..cc36edad98af0b0dbce6184c23265d0ff533d56b 100644 (file)
@@ -387,6 +387,23 @@ named_config_get(cfg_obj_t const *const *maps, const char *name,
        return ISC_R_NOTFOUND;
 }
 
+isc_result_t
+named_config_findopt(const cfg_obj_t *opts1, const cfg_obj_t *opts2,
+                    const char *name, const cfg_obj_t **objp) {
+       isc_result_t result = ISC_R_NOTFOUND;
+
+       REQUIRE(*objp == NULL);
+
+       if (opts1 != NULL) {
+               result = cfg_map_get(opts1, name, objp);
+       }
+       if (*objp == NULL && opts2 != NULL) {
+               result = cfg_map_get(opts2, name, objp);
+       }
+
+       return result;
+}
+
 isc_result_t
 named_checknames_get(const cfg_obj_t **maps, const char *const names[],
                     const cfg_obj_t **obj) {
index 0be3b12b9bb2421c15fde4a27b6229f97337a130..62f0e3c815d3bf7fd3e6a2fbfb0074c901a80cee 100644 (file)
@@ -67,3 +67,7 @@ named_config_getport(const cfg_obj_t *config, const char *type,
 isc_result_t
 named_config_getkeyalgorithm(const char *str, unsigned int *typep,
                             uint16_t *digestbits);
+
+isc_result_t
+named_config_findopt(const cfg_obj_t *opts1, const cfg_obj_t *opts2,
+                    const char *name, const cfg_obj_t **objp);
index a10a650fc7a464bb04f35d29653dad53ccc362bc..e96be44d3e97f6739b24a2a3db5660c1c853a15a 100644 (file)
@@ -63,7 +63,7 @@ named_zone_inlinesigning(const cfg_obj_t *zconfig, const cfg_obj_t *vconfig,
 isc_result_t
 named_zone_configure_writeable_dlz(dns_dlzdb_t *dlzdatabase, dns_zone_t *zone,
                                   dns_rdataclass_t rdclass, dns_name_t *name);
-/*%>
+/*%<
  * configure a DLZ zone, setting up the database methods and calling
  * postload to load the origin values
  *
@@ -73,3 +73,11 @@ named_zone_configure_writeable_dlz(dns_dlzdb_t *dlzdatabase, dns_zone_t *zone,
  * \li 'rdclass' to be a valid rdataclass
  * \li 'name' to be a valid zone origin name
  */
+
+const cfg_obj_t *
+named_zone_templateopts(const cfg_obj_t *config, const cfg_obj_t *zoptions);
+/*%<
+ * If a zone with options `zoptions` specifies a zone template, look
+ * the template options and return them. If no such template is found,
+ * return NULL.
+ */
index 7c35cc4a51bfe61a9a0fe774ed299a9e40ed9a66..cccd19240523b2b5f84a6c6c4cd787793b271f5b 100644 (file)
@@ -3097,12 +3097,12 @@ cleanup:
 
 static isc_result_t
 create_empty_zone(dns_zone_t *pzone, dns_name_t *name, dns_view_t *view,
-                 const cfg_obj_t *zonelist, const char **empty_dbtype,
-                 int empty_dbtypec, dns_zonestat_level_t statlevel) {
+                 const cfg_obj_t *config, const cfg_obj_t *voptions,
+                 const char **empty_dbtype, int empty_dbtypec,
+                 dns_zonestat_level_t statlevel) {
        char namebuf[DNS_NAME_FORMATSIZE];
        const cfg_obj_t *obj = NULL;
-       const cfg_obj_t *zconfig = NULL;
-       const cfg_obj_t *zoptions = NULL;
+       const cfg_obj_t *zonelist = NULL;
        const char *default_dbtype[4] = { ZONEDB_DEFAULT };
        const char *sep = ": view ";
        const char *str = NULL;
@@ -3126,12 +3126,20 @@ create_empty_zone(dns_zone_t *pzone, dns_name_t *name, dns_view_t *view,
        ns = dns_fixedname_initname(&nsfixed);
        contact = dns_fixedname_initname(&cfixed);
 
+       if (voptions != NULL) {
+               (void)cfg_map_get(voptions, "zone", &zonelist);
+       } else {
+               (void)cfg_map_get(config, "zone", &zonelist);
+       }
        /*
         * Look for forward "zones" beneath this empty zone and if so
         * create a custom db for the empty zone.
         */
        CFG_LIST_FOREACH (zonelist, element) {
-               zconfig = cfg_listelt_value(element);
+               const cfg_obj_t *zconfig = cfg_listelt_value(element);
+               const cfg_obj_t *zoptions = cfg_tuple_get(zconfig, "options");
+               const cfg_obj_t *toptions = NULL;
+
                str = cfg_obj_asstring(cfg_tuple_get(zconfig, "name"));
                CHECK(dns_name_fromstring(zname, str, dns_rootname, 0, NULL));
                namereln = dns_name_fullcompare(zname, name, &order, &nlabels);
@@ -3139,15 +3147,16 @@ create_empty_zone(dns_zone_t *pzone, dns_name_t *name, dns_view_t *view,
                        continue;
                }
 
-               zoptions = cfg_tuple_get(zconfig, "options");
+               toptions = named_zone_templateopts(config, zoptions);
 
                obj = NULL;
-               (void)cfg_map_get(zoptions, "type", &obj);
+               (void)named_config_findopt(zoptions, toptions, "type", &obj);
                if (obj != NULL &&
                    strcasecmp(cfg_obj_asstring(obj), "forward") == 0)
                {
                        obj = NULL;
-                       (void)cfg_map_get(zoptions, "forward", &obj);
+                       (void)named_config_findopt(zoptions, toptions,
+                                                  "forward", &obj);
                        if (obj == NULL) {
                                continue;
                        }
@@ -5468,9 +5477,9 @@ configure_view(dns_view_t *view, dns_viewlist_t *viewlist, cfg_obj_t *config,
                                dns_view_detach(&pview);
                        }
 
-                       CHECK(create_empty_zone(zone, name, view, zonelist,
-                                               empty_dbtype, empty_dbtypec,
-                                               statlevel));
+                       CHECK(create_empty_zone(zone, name, view, config,
+                                               voptions, empty_dbtype,
+                                               empty_dbtypec, statlevel));
                        if (zone != NULL) {
                                dns_zone_detach(&zone);
                        }
@@ -6151,6 +6160,7 @@ configure_zone(const cfg_obj_t *config, const cfg_obj_t *zconfig,
        dns_zone_t *dupzone = NULL;
        const cfg_obj_t *options = NULL;
        const cfg_obj_t *zoptions = NULL;
+       const cfg_obj_t *toptions = NULL;
        const cfg_obj_t *typeobj = NULL;
        const cfg_obj_t *forwarders = NULL;
        const cfg_obj_t *forwardtype = NULL;
@@ -6160,10 +6170,10 @@ configure_zone(const cfg_obj_t *config, const cfg_obj_t *zconfig,
        isc_result_t tresult;
        isc_buffer_t buffer;
        dns_fixedname_t fixorigin;
-       dns_name_t *origin;
-       const char *zname;
+       dns_name_t *origin = NULL;
+       const char *zname = NULL;
        dns_rdataclass_t zclass;
-       const char *ztypestr;
+       const char *ztypestr = NULL;
        dns_rpz_num_t rpz_num;
        bool zone_is_catz = false;
        bool zone_maybe_inline = false;
@@ -6174,6 +6184,7 @@ configure_zone(const cfg_obj_t *config, const cfg_obj_t *zconfig,
        (void)cfg_map_get(config, "options", &options);
 
        zoptions = cfg_tuple_get(zconfig, "options");
+       toptions = named_zone_templateopts(config, zoptions);
 
        /*
         * Get the zone origin as a dns_name_t.
@@ -6258,7 +6269,7 @@ configure_zone(const cfg_obj_t *config, const cfg_obj_t *zconfig,
                goto cleanup;
        }
 
-       (void)cfg_map_get(zoptions, "type", &typeobj);
+       (void)named_config_findopt(zoptions, toptions, "type", &typeobj);
        if (typeobj == NULL) {
                cfg_obj_log(zconfig, ISC_LOG_ERROR,
                            "zone '%s' 'type' not specified", zname);
@@ -6273,7 +6284,9 @@ configure_zone(const cfg_obj_t *config, const cfg_obj_t *zconfig,
         */
        if (strcasecmp(ztypestr, "hint") == 0) {
                const cfg_obj_t *fileobj = NULL;
-               if (cfg_map_get(zoptions, "file", &fileobj) != ISC_R_SUCCESS) {
+               (void)named_config_findopt(zoptions, toptions, "file",
+                                          &fileobj);
+               if (fileobj == NULL) {
                        isc_log_write(NAMED_LOGCATEGORY_GENERAL,
                                      NAMED_LOGMODULE_SERVER, ISC_LOG_ERROR,
                                      "zone '%s': 'file' not specified", zname);
@@ -6303,8 +6316,10 @@ configure_zone(const cfg_obj_t *config, const cfg_obj_t *zconfig,
                forwardtype = NULL;
                forwarders = NULL;
 
-               (void)cfg_map_get(zoptions, "forward", &forwardtype);
-               (void)cfg_map_get(zoptions, "forwarders", &forwarders);
+               (void)named_config_findopt(zoptions, toptions, "forward",
+                                          &forwardtype);
+               (void)named_config_findopt(zoptions, toptions, "forwarders",
+                                          &forwarders);
                CHECK(configure_forward(config, view, origin, forwarders,
                                        forwardtype));
                goto cleanup;
@@ -6463,9 +6478,11 @@ configure_zone(const cfg_obj_t *config, const cfg_obj_t *zconfig,
         * selective forwarding.
         */
        forwarders = NULL;
-       if (cfg_map_get(zoptions, "forwarders", &forwarders) == ISC_R_SUCCESS) {
+       named_config_findopt(zoptions, toptions, "forwarders", &forwarders);
+       if (forwarders != NULL) {
                forwardtype = NULL;
-               (void)cfg_map_get(zoptions, "forward", &forwardtype);
+               named_config_findopt(zoptions, toptions, "forward",
+                                    &forwardtype);
                CHECK(configure_forward(config, view, origin, forwarders,
                                        forwardtype));
        }
@@ -6497,9 +6514,9 @@ configure_zone(const cfg_obj_t *config, const cfg_obj_t *zconfig,
                        dns_zone_setstats(raw, named_g_server->zonestats);
                        CHECK(dns_zone_link(zone, raw));
                }
-               if (cfg_map_get(zoptions, "ixfr-from-differences",
-                               &ixfrfromdiffs) == ISC_R_SUCCESS)
-               {
+               named_config_findopt(zoptions, toptions,
+                                    "ixfr-from-differences", &ixfrfromdiffs);
+               if (ixfrfromdiffs != NULL) {
                        isc_log_write(NAMED_LOGCATEGORY_GENERAL,
                                      NAMED_LOGMODULE_SERVER, ISC_LOG_INFO,
                                      "zone '%s': 'ixfr-from-differences' is "
@@ -12459,11 +12476,9 @@ nzd_save(MDB_txn **txnp, MDB_dbi dbi, dns_zone_t *zone,
                }
        } else {
                /* We're creating or overwriting the zone */
-               const cfg_obj_t *zoptions;
+               const cfg_obj_t *zoptions = cfg_tuple_get(zconfig, "options");
 
                isc_buffer_allocate(view->mctx, &text, 256);
-
-               zoptions = cfg_tuple_get(zconfig, "options");
                if (zoptions == NULL) {
                        isc_log_write(NAMED_LOGCATEGORY_GENERAL,
                                      NAMED_LOGMODULE_SERVER, ISC_LOG_ERROR,
@@ -12785,16 +12800,14 @@ load_nzf(dns_view_t *view, ns_cfgctx_t *nzcfg) {
        isc_buffer_allocate(view->mctx, &text, 256);
 
        CFG_LIST_FOREACH (zonelist, element) {
-               const cfg_obj_t *zconfig;
+               const cfg_obj_t *zconfig = cfg_listelt_value(element);
                const cfg_obj_t *zoptions;
                char zname[DNS_NAME_FORMATSIZE];
                dns_fixedname_t fname;
-               dns_name_t *name;
-               const char *origin;
+               dns_name_t *name = NULL;
+               const char *origin = NULL;
                isc_buffer_t b;
 
-               zconfig = cfg_listelt_value(element);
-
                origin = cfg_obj_asstring(cfg_tuple_get(zconfig, "name"));
                if (origin == NULL) {
                        result = ISC_R_FAILURE;
@@ -12943,10 +12956,15 @@ newzone_parse(named_server_t *server, char *command, dns_view_t **viewp,
                if (obj != NULL) {
                        (void)putstr(text, "'in-view' zones not supported by ");
                        (void)putstr(text, bn);
-               } else {
-                       (void)putstr(text, "zone type not specified");
+                       CHECK(ISC_R_FAILURE);
+               }
+
+               (void)cfg_map_get(zoptions, "template", &obj);
+               if (obj == NULL) {
+                       (void)putstr(text, "no zone type or "
+                                          "template specified");
+                       CHECK(ISC_R_FAILURE);
                }
-               CHECK(ISC_R_FAILURE);
        }
 
        if (strcasecmp(cfg_obj_asstring(obj), "hint") == 0 ||
@@ -13518,14 +13536,12 @@ named_server_changezone(named_server_t *server, char *command,
                (void)putstr(text, "Not allowing new zones in view '");
                (void)putstr(text, view->name);
                (void)putstr(text, "'");
-               result = ISC_R_NOPERM;
-               goto cleanup;
+               CHECK(ISC_R_NOPERM);
        }
 
        cfg = (ns_cfgctx_t *)view->new_zone_config;
        if (cfg == NULL) {
-               result = ISC_R_FAILURE;
-               goto cleanup;
+               CHECK(ISC_R_FAILURE);
        }
 
        zonename = cfg_obj_asstring(cfg_tuple_get(zoneobj, "name"));
@@ -13922,12 +13938,16 @@ find_name_in_list_from_map(const cfg_obj_t *config,
                        if (result == ISC_R_SUCCESS &&
                            dns_name_equal(name1, name2))
                        {
-                               const cfg_obj_t *zoptions;
+                               const cfg_obj_t *zoptions =
+                                       cfg_tuple_get(obj, "options");
                                const cfg_obj_t *typeobj = NULL;
-                               zoptions = cfg_tuple_get(obj, "options");
 
                                if (zoptions != NULL) {
-                                       cfg_map_get(zoptions, "type", &typeobj);
+                                       const cfg_obj_t *toptions =
+                                               named_zone_templateopts(
+                                                       config, zoptions);
+                                       named_config_findopt(zoptions, toptions,
+                                                            "type", &typeobj);
                                }
                                if (redirect && typeobj != NULL &&
                                    strcasecmp(cfg_obj_asstring(typeobj),
index ca24ec9851d4d656be5c74e1e615a66a03d89562..0e069b13098a7e8381a3d1258c2059f9ee43206d 100644 (file)
@@ -77,12 +77,12 @@ configure_zone_acl(const cfg_obj_t *zconfig, const cfg_obj_t *vconfig,
                   void (*setzacl)(dns_zone_t *, dns_acl_t *),
                   void (*clearzacl)(dns_zone_t *)) {
        isc_result_t result;
-       const cfg_obj_t *maps[5] = { NULL, NULL, NULL, NULL, NULL };
+       const cfg_obj_t *maps[6] = { 0 };
        const cfg_obj_t *aclobj = NULL;
        int i = 0;
        dns_acl_t **aclp = NULL, *acl = NULL;
        const char *aclname;
-       dns_view_t *view;
+       dns_view_t *view = NULL;
 
        view = dns_zone_getview(zone);
 
@@ -129,7 +129,7 @@ configure_zone_acl(const cfg_obj_t *zconfig, const cfg_obj_t *vconfig,
 
        /* First check to see if ACL is defined within the zone */
        if (zconfig != NULL) {
-               maps[0] = cfg_tuple_get(zconfig, "options");
+               maps[i] = cfg_tuple_get(zconfig, "options");
                (void)named_config_get(maps, aclname, &aclobj);
                if (aclobj != NULL) {
                        aclp = NULL;
@@ -137,6 +137,14 @@ configure_zone_acl(const cfg_obj_t *zconfig, const cfg_obj_t *vconfig,
                }
        }
 
+       if (config != NULL && maps[i] != NULL) {
+               const cfg_obj_t *toptions = named_zone_templateopts(config,
+                                                                   maps[i]);
+               if (toptions != NULL) {
+                       maps[i++] = toptions;
+               }
+       }
+
        /* Failing that, see if there's a default ACL already in the view */
        if (aclp != NULL && *aclp != NULL) {
                (*setzacl)(zone, *aclp);
@@ -187,8 +195,8 @@ parse_acl:
  * Parse the zone update-policy statement.
  */
 static isc_result_t
-configure_zone_ssutable(const cfg_obj_t *zconfig, dns_zone_t *zone,
-                       const char *zname) {
+configure_zone_ssutable(const cfg_obj_t *zconfig, const cfg_obj_t *tconfig,
+                       dns_zone_t *zone, const char *zname) {
        const cfg_obj_t *updatepolicy = NULL;
        dns_ssutable_t *table = NULL;
        isc_mem_t *mctx = dns_zone_getmctx(zone);
@@ -200,8 +208,8 @@ configure_zone_ssutable(const cfg_obj_t *zconfig, dns_zone_t *zone,
        isc_buffer_init(&dbuf, debug, sizeof(debug));
        isc_buffer_setmctx(&dbuf, mctx);
 
-       (void)cfg_map_get(zconfig, "update-policy", &updatepolicy);
-
+       (void)named_config_findopt(zconfig, tconfig, "update-policy",
+                                  &updatepolicy);
        if (updatepolicy == NULL) {
                dns_zone_setssutable(zone, NULL);
                return ISC_R_SUCCESS;
@@ -540,8 +548,8 @@ configure_staticstub_servernames(const cfg_obj_t *zconfig, dns_zone_t *zone,
  * Configure static-stub zone.
  */
 static isc_result_t
-configure_staticstub(const cfg_obj_t *zconfig, dns_zone_t *zone,
-                    const char *zname, const char *dbtype) {
+configure_staticstub(const cfg_obj_t *zconfig, const cfg_obj_t *tconfig,
+                    dns_zone_t *zone, const char *zname, const char *dbtype) {
        int i = 0;
        const cfg_obj_t *obj;
        isc_mem_t *mctx = dns_zone_getmctx(zone);
@@ -583,18 +591,16 @@ configure_staticstub(const cfg_obj_t *zconfig, dns_zone_t *zone,
 
        /* Prepare zone RRs from the configuration */
        obj = NULL;
-       result = cfg_map_get(zconfig, "server-addresses", &obj);
-       if (result == ISC_R_SUCCESS) {
-               INSIST(obj != NULL);
+       (void)named_config_findopt(zconfig, tconfig, "server-addresses", &obj);
+       if (obj != NULL) {
                CHECK(configure_staticstub_serveraddrs(obj, zone, &rdatalist_ns,
                                                       &rdatalist_a,
                                                       &rdatalist_aaaa));
        }
 
        obj = NULL;
-       result = cfg_map_get(zconfig, "server-names", &obj);
-       if (result == ISC_R_SUCCESS) {
-               INSIST(obj != NULL);
+       (void)named_config_findopt(zconfig, tconfig, "server-names", &obj);
+       if (obj != NULL) {
                CHECK(configure_staticstub_servernames(obj, zone, &rdatalist_ns,
                                                       zname));
        }
@@ -682,12 +688,11 @@ cleanup:
  * Convert a config file zone type into a server zone type.
  */
 static dns_zonetype_t
-zonetype_fromconfig(const cfg_obj_t *map) {
+zonetype_fromconfig(const cfg_obj_t *zmap, const cfg_obj_t *tmap) {
        const cfg_obj_t *obj = NULL;
-       isc_result_t result;
 
-       result = cfg_map_get(map, "type", &obj);
-       INSIST(result == ISC_R_SUCCESS && obj != NULL);
+       (void)named_config_findopt(zmap, tmap, "type", &obj);
+       INSIST(obj != NULL);
        return named_config_getzonetype(obj);
 }
 
@@ -872,11 +877,13 @@ named_zone_configure(const cfg_obj_t *config, const cfg_obj_t *vconfig,
        const char *zname;
        dns_rdataclass_t zclass;
        dns_rdataclass_t vclass;
-       const cfg_obj_t *maps[5];
-       const cfg_obj_t *nodefault[4];
+       const cfg_obj_t *maps[6];
+       const cfg_obj_t *nodefault[5];
+       const cfg_obj_t *nooptions[3];
        const cfg_obj_t *zoptions = NULL;
+       const cfg_obj_t *toptions = NULL;
        const cfg_obj_t *options = NULL;
-       const cfg_obj_t *obj;
+       const cfg_obj_t *obj = NULL;
        const char *filename = NULL;
        const char *initial_file = NULL;
        const char *kaspname = NULL;
@@ -910,23 +917,34 @@ named_zone_configure(const cfg_obj_t *config, const cfg_obj_t *vconfig,
        bool transferinsecs = ns_server_getoption(named_g_server->sctx,
                                                  NS_SERVER_TRANSFERINSECS);
 
+       REQUIRE(config != NULL);
+       REQUIRE(zconfig != NULL);
+
        i = 0;
-       if (zconfig != NULL) {
-               zoptions = cfg_tuple_get(zconfig, "options");
-               nodefault[i] = maps[i] = zoptions;
+
+       zoptions = cfg_tuple_get(zconfig, "options");
+       INSIST(zoptions != NULL);
+       nodefault[i] = nooptions[i] = maps[i] = zoptions;
+       i++;
+
+       toptions = named_zone_templateopts(config, zoptions);
+       if (toptions != NULL) {
+               nodefault[i] = nooptions[i] = maps[i] = toptions;
                i++;
        }
+
+       nooptions[i] = NULL;
        if (vconfig != NULL) {
                nodefault[i] = maps[i] = cfg_tuple_get(vconfig, "options");
                i++;
        }
-       if (config != NULL) {
-               (void)cfg_map_get(config, "options", &options);
-               if (options != NULL) {
-                       nodefault[i] = maps[i] = options;
-                       i++;
-               }
+
+       (void)cfg_map_get(config, "options", &options);
+       if (options != NULL) {
+               nodefault[i] = maps[i] = options;
+               i++;
        }
+
        nodefault[i] = NULL;
        maps[i++] = named_g_defaults;
        maps[i] = NULL;
@@ -951,7 +969,7 @@ named_zone_configure(const cfg_obj_t *config, const cfg_obj_t *vconfig,
                dns_zone_setclass(raw, zclass);
        }
 
-       ztype = zonetype_fromconfig(zoptions);
+       ztype = zonetype_fromconfig(zoptions, toptions);
        if (raw != NULL) {
                dns_zone_settype(raw, ztype);
                dns_zone_settype(zone, dns_zone_primary);
@@ -960,13 +978,13 @@ named_zone_configure(const cfg_obj_t *config, const cfg_obj_t *vconfig,
        }
 
        obj = NULL;
-       result = cfg_map_get(zoptions, "database", &obj);
+       result = named_config_get(nooptions, "database", &obj);
        if (result == ISC_R_SUCCESS) {
                cpval = isc_mem_strdup(mctx, cfg_obj_asstring(obj));
        }
 
        obj = NULL;
-       result = cfg_map_get(zoptions, "dlz", &obj);
+       result = named_config_get(nooptions, "dlz", &obj);
        if (result == ISC_R_SUCCESS) {
                const char *dlzname = cfg_obj_asstring(obj);
                size_t len = strlen(dlzname) + 5;
@@ -992,13 +1010,13 @@ named_zone_configure(const cfg_obj_t *config, const cfg_obj_t *vconfig,
        }
 
        obj = NULL;
-       result = cfg_map_get(zoptions, "file", &obj);
+       result = named_config_get(nooptions, "file", &obj);
        if (result == ISC_R_SUCCESS) {
                filename = cfg_obj_asstring(obj);
        }
 
        obj = NULL;
-       result = cfg_map_get(zoptions, "initial-file", &obj);
+       result = named_config_get(nooptions, "initial-file", &obj);
        if (result == ISC_R_SUCCESS) {
                initial_file = cfg_obj_asstring(obj);
        }
@@ -1074,7 +1092,7 @@ named_zone_configure(const cfg_obj_t *config, const cfg_obj_t *vconfig,
        }
 
        obj = NULL;
-       result = cfg_map_get(zoptions, "journal", &obj);
+       result = named_config_get(nooptions, "journal", &obj);
        if (result == ISC_R_SUCCESS) {
                dns_zone_setjournal(mayberaw, cfg_obj_asstring(obj));
        }
@@ -1433,7 +1451,8 @@ named_zone_configure(const cfg_obj_t *config, const cfg_obj_t *vconfig,
                                   cfg_obj_asboolean(obj));
 
                obj = NULL;
-               result = cfg_map_get(zoptions, "log-report-channel", &obj);
+               result = named_config_get(nooptions, "log-report-channel",
+                                         &obj);
                if (result == ISC_R_SUCCESS) {
                        logreports = cfg_obj_asboolean(obj);
                        dns_zone_setoption(zone, DNS_ZONEOPT_LOGREPORTS,
@@ -1533,7 +1552,8 @@ named_zone_configure(const cfg_obj_t *config, const cfg_obj_t *vconfig,
                                      zname);
                }
 
-               CHECK(configure_zone_ssutable(zoptions, mayberaw, zname));
+               CHECK(configure_zone_ssutable(zoptions, toptions, mayberaw,
+                                             zname));
        }
 
        /*
@@ -1618,7 +1638,8 @@ named_zone_configure(const cfg_obj_t *config, const cfg_obj_t *vconfig,
         */
        if (ztype == dns_zone_primary || ztype == dns_zone_secondary) {
                const cfg_obj_t *parentals = NULL;
-               (void)cfg_map_get(zoptions, "parental-agents", &parentals);
+               (void)named_config_get(nooptions, "parental-agents",
+                                      &parentals);
                if (parentals != NULL) {
                        dns_ipkeylist_t ipkl;
                        dns_ipkeylist_init(&ipkl);
@@ -1769,7 +1790,7 @@ named_zone_configure(const cfg_obj_t *config, const cfg_obj_t *vconfig,
                 * are explicitly enabled by zone configuration.
                 */
                obj = NULL;
-               (void)cfg_map_get(zoptions, "allow-transfer", &obj);
+               (void)named_config_get(nooptions, "allow-transfer", &obj);
                if (obj == NULL) {
                        dns_acl_t *none;
                        CHECK(dns_acl_none(mctx, &none));
@@ -1782,9 +1803,9 @@ named_zone_configure(const cfg_obj_t *config, const cfg_obj_t *vconfig,
        case dns_zone_redirect:
                count = 0;
                obj = NULL;
-               (void)cfg_map_get(zoptions, "primaries", &obj);
+               (void)named_config_get(nooptions, "primaries", &obj);
                if (obj == NULL) {
-                       (void)cfg_map_get(zoptions, "masters", &obj);
+                       (void)named_config_get(nooptions, "masters", &obj);
                }
 
                /*
@@ -1886,7 +1907,7 @@ named_zone_configure(const cfg_obj_t *config, const cfg_obj_t *vconfig,
                break;
 
        case dns_zone_staticstub:
-               CHECK(configure_staticstub(zoptions, zone, zname,
+               CHECK(configure_staticstub(zoptions, toptions, zone, zname,
                                           default_dbtype));
                break;
 
@@ -1927,20 +1948,22 @@ named_zone_reusable(dns_zone_t *zone, const cfg_obj_t *zconfig,
                    const cfg_obj_t *vconfig, const cfg_obj_t *config,
                    dns_kasplist_t *kasplist) {
        const cfg_obj_t *zoptions = NULL;
+       const cfg_obj_t *toptions = NULL;
        const cfg_obj_t *obj = NULL;
-       const char *cfilename;
-       const char *zfilename;
+       const char *cfilename = NULL;
+       const char *zfilename = NULL;
        dns_zone_t *raw = NULL;
        bool has_raw, inline_signing;
        dns_zonetype_t ztype;
 
        zoptions = cfg_tuple_get(zconfig, "options");
+       toptions = named_zone_templateopts(config, zoptions);
 
        /*
         * We always reconfigure a static-stub zone for simplicity, assuming
         * the amount of data to be loaded is small.
         */
-       if (zonetype_fromconfig(zoptions) == dns_zone_staticstub) {
+       if (zonetype_fromconfig(zoptions, toptions) == dns_zone_staticstub) {
                dns_zone_log(zone, ISC_LOG_DEBUG(1),
                             "not reusable: staticstub");
                return false;
@@ -1971,14 +1994,14 @@ named_zone_reusable(dns_zone_t *zone, const cfg_obj_t *zconfig,
                return false;
        }
 
-       if (zonetype_fromconfig(zoptions) != ztype) {
+       if (zonetype_fromconfig(zoptions, toptions) != ztype) {
                dns_zone_log(zone, ISC_LOG_DEBUG(1),
                             "not reusable: type mismatch");
                return false;
        }
 
        obj = NULL;
-       (void)cfg_map_get(zoptions, "file", &obj);
+       (void)named_config_findopt(zoptions, toptions, "file", &obj);
        if (obj != NULL) {
                cfilename = cfg_obj_asstring(obj);
        } else {
@@ -1999,15 +2022,27 @@ named_zone_reusable(dns_zone_t *zone, const cfg_obj_t *zconfig,
 bool
 named_zone_inlinesigning(const cfg_obj_t *zconfig, const cfg_obj_t *vconfig,
                         const cfg_obj_t *config, dns_kasplist_t *kasplist) {
-       const cfg_obj_t *maps[4];
+       const cfg_obj_t *maps[5] = { 0 }, *noopts[3] = { 0 };
        const cfg_obj_t *signing = NULL;
        const cfg_obj_t *policy = NULL;
+       const cfg_obj_t *toptions = NULL;
        dns_kasp_t *kasp = NULL;
        isc_result_t res;
        bool inline_signing = false;
        int i = 0;
 
-       maps[i++] = cfg_tuple_get(zconfig, "options");
+       noopts[i] = maps[i] = cfg_tuple_get(zconfig, "options");
+       i++;
+
+       if (config != NULL) {
+               toptions = named_zone_templateopts(config, maps[0]);
+               if (toptions != NULL) {
+                       noopts[i] = maps[i] = toptions;
+                       i++;
+               }
+       }
+
+       noopts[i] = NULL;
        if (vconfig != NULL) {
                maps[i++] = cfg_tuple_get(vconfig, "options");
        }
@@ -2041,13 +2076,35 @@ named_zone_inlinesigning(const cfg_obj_t *zconfig, const cfg_obj_t *vconfig,
 
        /*
         * The zone option 'inline-signing' may override the value in
-        * dnssec-policy. This is a zone-only option, so look in maps[0]
-        * only.
+        * dnssec-policy. This is a zone-only option, so look in the
+        * zone and template blocks only.
         */
-       res = cfg_map_get(maps[0], "inline-signing", &signing);
+       res = named_config_get(noopts, "inline-signing", &signing);
        if (res == ISC_R_SUCCESS && cfg_obj_isboolean(signing)) {
                return cfg_obj_asboolean(signing);
        }
 
        return inline_signing;
 }
+
+const cfg_obj_t *
+named_zone_templateopts(const cfg_obj_t *config, const cfg_obj_t *zoptions) {
+       const cfg_obj_t *templates = NULL;
+       const cfg_obj_t *obj = NULL;
+
+       (void)cfg_map_get(config, "template", &templates);
+       (void)cfg_map_get(zoptions, "template", &obj);
+       if (obj != NULL && templates != NULL) {
+               const char *tmplname = cfg_obj_asstring(obj);
+               CFG_LIST_FOREACH (templates, e) {
+                       const cfg_obj_t *t = cfg_tuple_get(cfg_listelt_value(e),
+                                                          "name");
+                       if (strcasecmp(cfg_obj_asstring(t), tmplname) == 0) {
+                               return cfg_tuple_get(cfg_listelt_value(e),
+                                                    "options");
+                       }
+               }
+       }
+
+       return NULL;
+}
index 286e71753228587f00c6b9d2cc62a3e51f3cccfc..849821078cac36ff26ad7721e820de4576f2da8c 100644 (file)
@@ -9,7 +9,6 @@
 ; See the COPYRIGHT file distributed with this work for additional
 ; information regarding copyright ownership.
 
-;$ORIGIN added.example.
 $TTL 300       ; 5 minutes
 @                       IN SOA mname1. . (
                                1          ; serial
index bd94f6e75108f6f2fafb588eb426680f274c454f..e85ad22054164a26f7cbb2525da79443b60822d9 100644 (file)
@@ -28,6 +28,12 @@ controls {
        inet 10.53.0.2 port @CONTROLPORT@ allow { any; } keys { rndc_key; };
 };
 
+template primary {
+       type primary;
+       file "$view-$name.db";
+       initial-file "added.db";
+};
+
 zone "." {
        type hint;
        file "../../_common/root.hint";
index ef9adeb22f9b0d0b10b952da8aa928f44ab4f6e5..74bc2659e0ad1a29f840251e92ec6c6e35b36af9 100644 (file)
@@ -27,6 +27,12 @@ options {
        dnssec-validation no;
 };
 
+template primary {
+       type primary;
+       file "$view-$name.db";
+       initial-file "added.db";
+};
+
 view internal {
        match-clients { 10.53.0.2; };
        allow-new-zones no;
index ca934c4671d9c703b184d1ce7865a9dca0bbfadf..0642ac8d5d6ba29e11307f633371572ba7a0ca11 100644 (file)
@@ -27,6 +27,12 @@ options {
        dnssec-validation no;
 };
 
+template primary {
+       type primary;
+       file "$view-$name.db";
+       initial-file "added.db";
+};
+
 view internal {
        match-clients { 10.53.0.2; };
        allow-new-zones no;
index 6d5939c89678b6356b810667ee323096028f873c..5809709656d21c766d7701d5233ad5b5eecbcc6c 100755 (executable)
@@ -745,5 +745,29 @@ if [ $ret != 0 ]; then echo_i "failed"; fi
 status=$((status + ret))
 n=$((n + 1))
 
+echo_i "checking addzone with zone template (primary) ($n)"
+ret=0
+$RNDCCMD 10.53.0.2 addzone 'template.example in external { template primary; };' 2>&1 | sed 's/^/I:ns2 /'
+$DIG +norec $DIGOPTS @10.53.0.2 -b 10.53.0.2 a.template.example a >dig.out.ns2.int.$n || ret=1
+grep 'status: NOERROR' dig.out.ns2.int.$n >/dev/null || ret=1
+grep 'ANSWER: 0' dig.out.ns2.int.$n >/dev/null || ret=1
+$DIG +norec $DIGOPTS @10.53.0.2 -b 10.53.0.4 a.template.example a >dig.out.ns2.ext.$n || ret=1
+grep 'status: NOERROR' dig.out.ns2.ext.$n >/dev/null || ret=1
+grep 'ANSWER: 1' dig.out.ns2.ext.$n >/dev/null || ret=1
+test -f ns2/external-template.example.db
+n=$((n + 1))
+if [ $ret != 0 ]; then echo_i "failed"; fi
+status=$((status + ret))
+
+echo_i "checking addzone with nonexistent template ($n)"
+ret=0
+nextpart ns2/named.run >/dev/null
+$RNDCCMD 10.53.0.2 addzone 'wrong.example in external { template nope; };' 2>&1 | grep -qF "failure" || ret=1
+nextpart ns2/named.run | grep -qF "zone 'wrong.example': template 'nope' not found" || ret=1
+test -f ns2/wrong-template.example.db && ret=1
+n=$((n + 1))
+if [ $ret != 0 ]; then echo_i "failed"; fi
+status=$((status + ret))
+
 echo_i "exit status: $status"
 [ $status -eq 0 ] || exit 1
index 4ca4440c04608b167db2b9ce644a6c8680f0eb88..af7bf4b2aafd8a459d9c12e83ecb13ceb458146f 100644 (file)
@@ -26,6 +26,7 @@ pytestmark = pytest.mark.extra_artifacts(
         "ns2/K*.private",
         "ns2/K*.state",
         "ns2/external.nzd",
+        "ns2/external-template.example.db",
         "ns2/extra.nzd",
         "ns2/inline.db.jbk",
         "ns2/inline.db.signed",
diff --git a/bin/tests/system/checkconf/bad-template-1.conf b/bin/tests/system/checkconf/bad-template-1.conf
new file mode 100644 (file)
index 0000000..1240d75
--- /dev/null
@@ -0,0 +1,23 @@
+/*
+ * 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.
+ */
+
+template a {
+       type primary;
+       file "$name.db";
+       initial-file "template.db";
+};
+
+zone example {
+       template a;
+       type secondary;
+};
diff --git a/bin/tests/system/checkconf/bad-template-2.conf b/bin/tests/system/checkconf/bad-template-2.conf
new file mode 100644 (file)
index 0000000..7502f6c
--- /dev/null
@@ -0,0 +1,22 @@
+/*
+ * 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.
+ */
+
+template a {
+       type primary;
+       file "$name.db";
+       initial-file "template.db";
+};
+
+template b {
+       template a;
+};
diff --git a/bin/tests/system/checkconf/bad-template-3.conf b/bin/tests/system/checkconf/bad-template-3.conf
new file mode 100644 (file)
index 0000000..728f4e2
--- /dev/null
@@ -0,0 +1,23 @@
+/*
+ * 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.
+ */
+
+template a {
+       type primary;
+       file "$name.db";
+       initial-file "template.db";
+};
+
+zone example.com {
+       # specify an undefined template
+       template c;
+};
diff --git a/bin/tests/system/checkconf/bad-template-4.conf b/bin/tests/system/checkconf/bad-template-4.conf
new file mode 100644 (file)
index 0000000..d98dd43
--- /dev/null
@@ -0,0 +1,17 @@
+/*
+ * 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.
+ */
+
+zone example.com {
+       # specify a template, but there are no templates
+       template c;
+};
diff --git a/bin/tests/system/checkconf/good-template-1.conf b/bin/tests/system/checkconf/good-template-1.conf
new file mode 100644 (file)
index 0000000..5637590
--- /dev/null
@@ -0,0 +1,23 @@
+/*
+ * 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.
+ */
+
+template a {
+       type primary;
+       file "$name.db";
+       initial-file "template.db";
+};
+
+zone example {
+       template a;
+       file "othername.db";
+};
index 442d273a8c762b37f787894f308009824ceb6f70..4e66b4f1c0a557ff11e96c94bf9b85d7cbf28fe0 100644 (file)
@@ -34,14 +34,23 @@ zone "." {
        file "../../_common/root.hint";
 };
 
-zone "example" {
+template primary {
        type primary;
        file "$name.db";
 };
 
+zone "example" {
+       template primary;
+};
+
 zone "missing" {
-       type primary;
-       file "$name.db";
+       template primary;
+};
+
+zone "different" {
+       template primary;
+       initial-file "example.db";
+       file "alternate.db";
 };
 
 zone "initial" {
index 8b25f6db076dea219e633c8e84ffc4f5fc6c7e13..862be26baee6bcc1fa15e43917e143a95c7a1228 100644 (file)
@@ -17,7 +17,9 @@ import dns.zone
 import isctest
 import pytest
 
-pytestmark = pytest.mark.extra_artifacts(["ns2/copied.db", "ns2/present.db"])
+pytestmark = pytest.mark.extra_artifacts(
+    ["ns2/copied.db", "ns2/present.db", "ns2/alternate.db"]
+)
 
 
 def test_masterfile_include_semantics():
@@ -91,7 +93,20 @@ example.     300     IN      SOA     mname1. . 2010042407 20 20 1814400 3600
 
 
 def test_masterfile_initial_file():
-    """Test zone configuration with initial template files"""
+    """Test zone configurations with initial template files"""
+    # example inherited its configuration from the template,
+    # make sure it works
+    msg_soa = dns.message.make_query("example.", "SOA")
+    res_soa = isctest.query.tcp(msg_soa, "10.53.0.2")
+    expected_soa_rr = """;ANSWER
+example.       300     IN      SOA     mname1. . 2010042407 20 20 1814400 3600
+"""
+    expected = dns.message.from_text(expected_soa_rr)
+    isctest.check.rrsets_equal(res_soa.answer, expected.answer)
+
+    # initial uses an initial-file option with the "file"
+    # option set to "copied.db". make sure it works and that
+    # copied.db has been populated.
     msg_soa = dns.message.make_query("initial.", "SOA")
     res_soa = isctest.query.tcp(msg_soa, "10.53.0.2")
     expected_soa_rr = """;ANSWER
@@ -101,13 +116,30 @@ initial.  300     IN      SOA     mname1. . 2010042407 20 20 1814400 3600
     isctest.check.rrsets_equal(res_soa.answer, expected.answer)
     isctest.check.file_contents_equal("ns2/example.db", "ns2/copied.db")
 
-    # the 'present.db' file already existed and shouldn't load
+    # present uses an initial-file option, but the file 'present.db'
+    # already exists and is empty, so the initial-file should not be
+    # copied into place and the zone should not load.
     msg_soa = dns.message.make_query("present.", "SOA")
     res_soa = isctest.query.tcp(msg_soa, "10.53.0.2")
     isctest.check.servfail(res_soa)
     isctest.check.file_empty("ns2/present.db")
 
 
+def test_masterfile_template_override():
+    """Test zone configurations with overridden template options"""
+    # different inherited configuration from the template, but
+    # overrides the "file" option to 'alternate.db'.
+    msg_soa = dns.message.make_query("different.", "SOA")
+    res_soa = isctest.query.tcp(msg_soa, "10.53.0.2")
+    expected_soa_rr = """;ANSWER
+different.     300     IN      SOA     mname1. . 2010042407 20 20 1814400 3600
+"""
+    expected = dns.message.from_text(expected_soa_rr)
+    isctest.check.rrsets_equal(res_soa.answer, expected.answer)
+    isctest.check.file_contents_equal("ns2/example.db", "ns2/alternate.db")
+    assert not os.path.exists("ns2/different.db")
+
+
 def test_masterfile_missing_master_file_servfail():
     """Test nameserver returning SERVFAIL for a missing master file"""
     msg_soa = dns.message.make_query("missing.", "SOA")
index 63535eddde415ef74adff8233072399cf06230da..a26db7103ab9057f3df8dc1db0edbb8d458a8aa6 100644 (file)
@@ -7038,6 +7038,13 @@ mid-1970s. Zone data for it can be specified with the ``CHAOS`` class.
 Zone Options
 ^^^^^^^^^^^^
 
+.. namedconf:statement:: template
+   :tags: zone
+   :short: Specifies a template to use for zone configuration.
+
+   This specifies a zone template from which to import other zone options.
+   See :ref:`zone_templates` for details.
+
 :any:`allow-notify`
    See the description of :any:`allow-notify` in :ref:`access_control`.
 
@@ -7377,6 +7384,52 @@ Zone Options
 :any:`send-report-channel`
    See the description of :any:`send-report-channel` in :namedconf:ref:`options`.
 
+.. _zone_templates:
+
+Zone Templates
+^^^^^^^^^^^^^^
+
+To simplify the configuration of multiple similar zones, BIND 9
+supports a zone template mechanism. ``template`` blocks can be
+defined at the top level of the configuration; these blocks can
+contain any set of options that could be set in a :any:`zone`
+statement, with the exceptions of :any:`in-view` and :any:`template`.
+
+Once a template has been defined, it can be referenced in a
+:any:`zone` statement; the zone is then configured using the
+options specified in the :any:`template` as defaults.
+Options that are locally defined within the :any:`zone` statement
+override the template.
+
+For example, the following configuration would define two primary
+and two secondary zones:
+
+   ::
+
+     template primary {
+         type primary;
+         file "$type/$name.db";
+         initial-file "initial.db";
+     };
+
+     template secondary {
+         type secondary;
+         file "$type/$name.db";
+         primaries { 192.0.2.1; };
+     };
+
+     zone example.com { template primary; };
+     zone example.org { template primary; };
+     zone example.net { template secondary; };
+     zone example.edu { template secondary; };
+
+Templates can also be used for zones that are added using
+``rndc addzone`` (see :any:`allow-new-zones`):
+
+   ::
+
+      $ rndc addzone example.biz '{ template secondary; };'
+
 .. _dynamic_update_policies:
 
 Dynamic Update Policies
index a0d26b12d8b203e77dee87099ee986a63e8a60bf..af060cf3476c3fbc7db9b993a1bdfa2b37eaa272 100644 (file)
@@ -2,4 +2,5 @@ zone <string> [ <class> ] {
        type forward;
        forward ( first | only );
        forwarders [ port <integer> ] [ tls <string> ] { ( <ipv4_address> | <ipv6_address> ) [ port <integer> ] [ tls <string> ]; ... };
+       template <string>;
 };
index 2d2c98de4dcea5d962b49f6c3247a7749076ab24..260db7fb5fcffa055355b9915991b58f98970dea 100644 (file)
@@ -2,4 +2,5 @@ zone <string> [ <class> ] {
        type hint;
        check-names ( fail | warn | ignore );
        file <quoted_string>;
+       template <string>;
 };
index 2f49a34e9cbd1a647c6cf202152bb6843282e9e5..aa193235a72f348698514aa814ed3b4ae60928de 100644 (file)
@@ -38,6 +38,7 @@ zone <string> [ <class> ] {
        request-expire <boolean>;
        request-ixfr <boolean>;
        request-ixfr-max-diffs <integer>;
+       template <string>;
        transfer-source ( <ipv4_address> | * );
        transfer-source-v6 ( <ipv6_address> | * );
        try-tcp-refresh <boolean>;
index 1cb0d96443879327da2e13dd66aa362c4fe9164b..3215fc7af7b5f5a7c69ca53e0532a8f332e13d04 100644 (file)
@@ -362,6 +362,93 @@ statistics-channels {
        inet ( <ipv4_address> | <ipv6_address> | * ) [ port ( <integer> | * ) ] [ allow { <address_match_element>; ... } ]; // may occur multiple times
 }; // optional (only available if configured), may occur multiple times
 
+template <string> {
+       allow-notify { <address_match_element>; ... };
+       allow-query { <address_match_element>; ... };
+       allow-query-on { <address_match_element>; ... };
+       allow-transfer [ port <integer> ] [ transport <string> ] { <address_match_element>; ... };
+       allow-update { <address_match_element>; ... };
+       allow-update-forwarding { <address_match_element>; ... };
+       also-notify [ port <integer> ] [ source ( <ipv4_address> | * ) ] [ source-v6 ( <ipv6_address> | * ) ] { ( <server-list> | <ipv4_address> [ port <integer> ] | <ipv6_address> [ port <integer> ] ) [ key <string> ] [ tls <string> ]; ... };
+       check-dup-records ( fail | warn | ignore );
+       check-integrity <boolean>;
+       check-mx ( fail | warn | ignore );
+       check-mx-cname ( fail | warn | ignore );
+       check-names ( fail | warn | ignore );
+       check-sibling <boolean>;
+       check-spf ( warn | ignore );
+       check-srv-cname ( fail | warn | ignore );
+       check-svcb <boolean>;
+       check-wildcard <boolean>;
+       checkds ( explicit | <boolean> );
+       database <string>;
+       dlz <string>;
+       dnskey-sig-validity <integer>; // obsolete
+       dnssec-dnskey-kskonly <boolean>; // obsolete
+       dnssec-loadkeys-interval <integer>;
+       dnssec-policy <string>;
+       dnssec-secure-to-insecure <boolean>; // obsolete
+       dnssec-update-mode ( maintain | no-resign ); // obsolete
+       file <quoted_string>;
+       forward ( first | only );
+       forwarders [ port <integer> ] [ tls <string> ] { ( <ipv4_address> | <ipv6_address> ) [ port <integer> ] [ tls <string> ]; ... };
+       initial-file <quoted_string>;
+       inline-signing <boolean>;
+       ixfr-from-differences <boolean>;
+       journal <quoted_string>;
+       key-directory <quoted_string>;
+       log-report-channel <boolean>;
+       masterfile-format ( raw | text );
+       masterfile-style ( full | relative );
+       max-ixfr-ratio ( unlimited | <percentage> );
+       max-journal-size ( default | unlimited | <sizeval> );
+       max-records <integer>;
+       max-records-per-type <integer>;
+       max-refresh-time <integer>;
+       max-retry-time <integer>;
+       max-transfer-idle-in <integer>;
+       max-transfer-idle-out <integer>;
+       max-transfer-time-in <integer>;
+       max-transfer-time-out <integer>;
+       max-types-per-name <integer>;
+       max-zone-ttl ( unlimited | <duration> ); // deprecated
+       min-refresh-time <integer>;
+       min-retry-time <integer>;
+       min-transfer-rate-in <integer> <integer>;
+       multi-master <boolean>;
+       notify ( explicit | master-only | primary-only | <boolean> );
+       notify-defer <integer>;
+       notify-delay <integer>;
+       notify-source ( <ipv4_address> | * );
+       notify-source-v6 ( <ipv6_address> | * );
+       notify-to-soa <boolean>;
+       nsec3-test-zone <boolean>; // test only
+       parental-agents [ port <integer> ] [ source ( <ipv4_address> | * ) ] [ source-v6 ( <ipv6_address> | * ) ] { ( <server-list> | <ipv4_address> [ port <integer> ] | <ipv6_address> [ port <integer> ] ) [ key <string> ] [ tls <string> ]; ... };
+       parental-source ( <ipv4_address> | * );
+       parental-source-v6 ( <ipv6_address> | * );
+       primaries [ port <integer> ] [ source ( <ipv4_address> | * ) ] [ source-v6 ( <ipv6_address> | * ) ] { ( <server-list> | <ipv4_address> [ port <integer> ] | <ipv6_address> [ port <integer> ] ) [ key <string> ] [ tls <string> ]; ... };
+       provide-zoneversion <boolean>;
+       request-expire <boolean>;
+       request-ixfr <boolean>;
+       request-ixfr-max-diffs <integer>;
+       send-report-channel <string>;
+       serial-update-method ( date | increment | unixtime );
+       server-addresses { ( <ipv4_address> | <ipv6_address> ); ... };
+       server-names { <string>; ... };
+       sig-signing-nodes <integer>;
+       sig-signing-signatures <integer>;
+       sig-signing-type <integer>;
+       sig-validity-interval <integer> [ <integer> ]; // obsolete
+       transfer-source ( <ipv4_address> | * );
+       transfer-source-v6 ( <ipv6_address> | * );
+       try-tcp-refresh <boolean>;
+       type ( primary | master | secondary | slave | mirror | forward | hint | redirect | static-stub | stub );
+       update-check-ksk <boolean>; // obsolete
+       update-policy ( local | { ( deny | grant ) <string> ( 6to4-self | external | krb5-self | krb5-selfsub | krb5-subdomain | krb5-subdomain-self-rhs | ms-self | ms-selfsub | ms-subdomain | ms-subdomain-self-rhs | name | self | selfsub | selfwild | subdomain | tcp-self | wildcard | zonesub ) [ <string> ] <rrtypelist>; ... } );
+       zero-no-soa-ttl <boolean>;
+       zone-statistics ( full | terse | none | <boolean> );
+}; // may occur multiple times
+
 tls <string> {
        ca-file <quoted_string>;
        cert-file <quoted_string>;
index 74a314999abb3e804fcc6737b137f15b4e8462f9..dd1b94756b770b631da926ff2a7c030dfff1cb78 100644 (file)
@@ -60,6 +60,7 @@ zone <string> [ <class> ] {
        sig-signing-signatures <integer>;
        sig-signing-type <integer>;
        sig-validity-interval <integer> [ <integer> ]; // obsolete
+       template <string>;
        update-check-ksk <boolean>; // obsolete
        update-policy ( local | { ( deny | grant ) <string> ( 6to4-self | external | krb5-self | krb5-selfsub | krb5-subdomain | krb5-subdomain-self-rhs | ms-self | ms-selfsub | ms-subdomain | ms-subdomain-self-rhs | name | self | selfsub | selfwild | subdomain | tcp-self | wildcard | zonesub ) [ <string> ] <rrtypelist>; ... } );
        zero-no-soa-ttl <boolean>;
index f457c807c94410a81f9bec7323eb1e2a0bdfb40c..e338b6e2316d8485d18f57bbf25294dbc8eed886 100644 (file)
@@ -11,5 +11,6 @@ zone <string> [ <class> ] {
        max-types-per-name <integer>;
        max-zone-ttl ( unlimited | <duration> ); // deprecated
        primaries [ port <integer> ] [ source ( <ipv4_address> | * ) ] [ source-v6 ( <ipv6_address> | * ) ] { ( <server-list> | <ipv4_address> [ port <integer> ] | <ipv6_address> [ port <integer> ] ) [ key <string> ] [ tls <string> ]; ... };
+       template <string>;
        zone-statistics ( full | terse | none | <boolean> );
 };
index b1dbcea978e38e02095221b63f57814de817ecb8..7529112a3342fe4d5b047359e4daf62086bf2029 100644 (file)
@@ -60,6 +60,7 @@ zone <string> [ <class> ] {
        sig-signing-signatures <integer>;
        sig-signing-type <integer>;
        sig-validity-interval <integer> [ <integer> ]; // obsolete
+       template <string>;
        transfer-source ( <ipv4_address> | * );
        transfer-source-v6 ( <ipv6_address> | * );
        try-tcp-refresh <boolean>;
index 40a340f6298123557f130576faa33d563e7ce7bd..14928922dd642035f037b72131d3c6fdd41dc193 100644 (file)
@@ -9,5 +9,6 @@ zone <string> [ <class> ] {
        max-types-per-name <integer>;
        server-addresses { ( <ipv4_address> | <ipv6_address> ); ... };
        server-names { <string>; ... };
+       template <string>;
        zone-statistics ( full | terse | none | <boolean> );
 };
index 97b9ba057862a7a6fa59860b1669e538fae0133c..4d2509548467d69edf10f0c9b53ae6f32b5abb7e 100644 (file)
@@ -21,6 +21,7 @@ zone <string> [ <class> ] {
        min-transfer-rate-in <integer> <integer>;
        multi-master <boolean>;
        primaries [ port <integer> ] [ source ( <ipv4_address> | * ) ] [ source-v6 ( <ipv6_address> | * ) ] { ( <server-list> | <ipv4_address> [ port <integer> ] | <ipv6_address> [ port <integer> ] ) [ key <string> ] [ tls <string> ]; ... };
+       template <string>;
        transfer-source ( <ipv4_address> | * );
        transfer-source-v6 ( <ipv6_address> | * );
        zone-statistics ( full | terse | none | <boolean> );
index 22cb3df899527c634964c6ad7239390ca71b1e42..a3c79085da3eea85c5e7b6a6774ad1596b0b37b9 100644 (file)
@@ -2923,14 +2923,15 @@ check:
 }
 
 /*
- * Try to find a zone option in one of up to three levels of options:
- * for example, the zone, view, and global option blocks.
+ * Try to find a zone option in one of up to four levels of options:
+ * for example, the zone, template, view, and global option blocks.
  * (Fewer levels can be specified for options that aren't defined at
- * all three levels.)
+ * all four levels.)
  */
 static isc_result_t
 get_zoneopt(const cfg_obj_t *opts1, const cfg_obj_t *opts2,
-           const cfg_obj_t *opts3, const char *name, const cfg_obj_t **objp) {
+           const cfg_obj_t *opts3, const cfg_obj_t *opts4, const char *name,
+           const cfg_obj_t **objp) {
        isc_result_t result = ISC_R_NOTFOUND;
 
        REQUIRE(*objp == NULL);
@@ -2944,6 +2945,9 @@ get_zoneopt(const cfg_obj_t *opts1, const cfg_obj_t *opts2,
        if (*objp == NULL && opts3 != NULL) {
                result = cfg_map_get(opts3, name, objp);
        }
+       if (*objp == NULL && opts4 != NULL) {
+               result = cfg_map_get(opts4, name, objp);
+       }
 
        return result;
 }
@@ -2958,13 +2962,14 @@ isccfg_check_zoneconf(const cfg_obj_t *zconfig, const cfg_obj_t *voptions,
        const char *znamestr = NULL;
        const char *typestr = NULL;
        const char *target = NULL;
+       const char *tmplname = NULL;
        int ztype;
-       const cfg_obj_t *zoptions, *goptions = NULL;
+       const cfg_obj_t *zoptions = NULL, *toptions = NULL, *goptions = NULL;
        const cfg_obj_t *obj = NULL, *kasp = NULL;
-       const cfg_obj_t *inviewobj = NULL;
+       const cfg_obj_t *templates = NULL, *inviewobj = NULL;
        isc_result_t result = ISC_R_SUCCESS;
        isc_result_t tresult;
-       unsigned int i;
+       unsigned int i = 0;
        dns_rdataclass_t zclass;
        dns_fixedname_t fixedname;
        dns_name_t *zname = NULL; /* NULL if parsing of zone name fails. */
@@ -2976,6 +2981,7 @@ isccfg_check_zoneconf(const cfg_obj_t *zconfig, const cfg_obj_t *voptions,
        bool ddns = false;
        bool has_dnssecpolicy = false;
        bool kasp_inlinesigning = false;
+       bool inline_signing = false;
        const void *clauses = NULL;
        const char *option = NULL;
        const char *kaspname = NULL;
@@ -2995,14 +3001,37 @@ isccfg_check_zoneconf(const cfg_obj_t *zconfig, const cfg_obj_t *voptions,
                cfg_map_get(config, "options", &goptions);
        }
 
-       inviewobj = NULL;
+       /* If the zone specifies a template, find it too */
+       (void)cfg_map_get(config, "template", &templates);
+       (void)cfg_map_get(zoptions, "template", &obj);
+       if (obj != NULL) {
+               tmplname = cfg_obj_asstring(obj);
+
+               CFG_LIST_FOREACH (templates, e) {
+                       const cfg_obj_t *t = cfg_tuple_get(cfg_listelt_value(e),
+                                                          "name");
+                       if (strcasecmp(cfg_obj_asstring(t), tmplname) == 0) {
+                               toptions = cfg_tuple_get(cfg_listelt_value(e),
+                                                        "options");
+                               break;
+                       }
+               }
+
+               if (toptions == NULL) {
+                       cfg_obj_log(zconfig, ISC_LOG_ERROR,
+                                   "zone '%s': template '%s' not found",
+                                   znamestr, tmplname);
+                       return ISC_R_FAILURE;
+               }
+       }
+
        (void)cfg_map_get(zoptions, "in-view", &inviewobj);
        if (inviewobj != NULL) {
                target = cfg_obj_asstring(inviewobj);
                ztype = CFG_ZONE_INVIEW;
        } else {
                obj = NULL;
-               (void)cfg_map_get(zoptions, "type", &obj);
+               (void)get_zoneopt(zoptions, toptions, NULL, NULL, "type", &obj);
                if (obj == NULL) {
                        cfg_obj_log(zconfig, ISC_LOG_ERROR,
                                    "zone '%s': type not present", znamestr);
@@ -3195,7 +3224,8 @@ isccfg_check_zoneconf(const cfg_obj_t *zconfig, const cfg_obj_t *voptions,
         * Check if a dnssec-policy is set.
         */
        obj = NULL;
-       (void)get_zoneopt(zoptions, voptions, goptions, "dnssec-policy", &obj);
+       (void)get_zoneopt(zoptions, toptions, voptions, goptions,
+                         "dnssec-policy", &obj);
        if (obj != NULL) {
                kaspname = cfg_obj_asstring(obj);
                if (strcmp(kaspname, "default") == 0) {
@@ -3258,8 +3288,8 @@ isccfg_check_zoneconf(const cfg_obj_t *zconfig, const cfg_obj_t *voptions,
         * */
        if (has_dnssecpolicy) {
                obj = NULL;
-               (void)get_zoneopt(zoptions, voptions, goptions, "max-zone-ttl",
-                                 &obj);
+               (void)get_zoneopt(zoptions, toptions, voptions, goptions,
+                                 "max-zone-ttl", &obj);
                if (obj != NULL) {
                        cfg_obj_log(obj, ISC_LOG_ERROR,
                                    "zone '%s': option 'max-zone-ttl' "
@@ -3278,13 +3308,19 @@ isccfg_check_zoneconf(const cfg_obj_t *zconfig, const cfg_obj_t *voptions,
        option = cfg_map_firstclause(&cfg_type_zoneopts, &clauses, &i);
        while (option != NULL) {
                obj = NULL;
-               if (cfg_map_get(zoptions, option, &obj) == ISC_R_SUCCESS &&
-                   obj != NULL && !cfg_clause_validforzone(option, ztype))
-               {
+               bool topt = false;
+               (void)cfg_map_get(zoptions, option, &obj);
+               if (obj == NULL && toptions != NULL) {
+                       (void)cfg_map_get(toptions, option, &obj);
+                       topt = true;
+               }
+               if (obj != NULL && !cfg_clause_validforzone(option, ztype)) {
                        cfg_obj_log(obj, ISC_LOG_WARNING,
                                    "option '%s' is not allowed "
-                                   "in '%s' zone '%s'",
-                                   option, typestr, znamestr);
+                                   "in '%s' zone '%s'%s%s%s",
+                                   option, typestr, znamestr,
+                                   topt ? " (referencing template '" : "",
+                                   topt ? tmplname : "", topt ? "')" : "");
                        result = ISC_R_FAILURE;
                }
                option = cfg_map_nextclause(&cfg_type_zoneopts, &clauses, &i);
@@ -3321,9 +3357,9 @@ isccfg_check_zoneconf(const cfg_obj_t *zconfig, const cfg_obj_t *voptions,
                bool donotify = true;
 
                obj = NULL;
-               tresult = get_zoneopt(zoptions, voptions, goptions, "notify",
-                                     &obj);
-               if (tresult == ISC_R_SUCCESS) {
+               (void)get_zoneopt(zoptions, toptions, voptions, goptions,
+                                 "notify", &obj);
+               if (obj != NULL) {
                        if (cfg_obj_isboolean(obj)) {
                                donotify = cfg_obj_asboolean(obj);
                        } else {
@@ -3338,18 +3374,19 @@ isccfg_check_zoneconf(const cfg_obj_t *zconfig, const cfg_obj_t *voptions,
                }
 
                obj = NULL;
-               tresult = cfg_map_get(zoptions, "also-notify", &obj);
-               if (tresult == ISC_R_SUCCESS && !donotify) {
+               (void)get_zoneopt(zoptions, toptions, NULL, NULL, "also-notify",
+                                 &obj);
+               if (obj != NULL && !donotify) {
                        cfg_obj_log(zoptions, ISC_LOG_WARNING,
                                    "zone '%s': 'also-notify' set but "
                                    "'notify' is disabled",
                                    znamestr);
                }
-               if (tresult != ISC_R_SUCCESS) {
-                       tresult = get_zoneopt(voptions, goptions, NULL,
-                                             "also-notify", &obj);
+               if (obj == NULL) {
+                       (void)get_zoneopt(voptions, goptions, NULL, NULL,
+                                         "also-notify", &obj);
                }
-               if (tresult == ISC_R_SUCCESS && donotify) {
+               if (obj != NULL && donotify) {
                        uint32_t count;
                        tresult = validate_remotes(obj, config, &count, mctx);
                        if (tresult != ISC_R_SUCCESS && result == ISC_R_SUCCESS)
@@ -3370,15 +3407,18 @@ isccfg_check_zoneconf(const cfg_obj_t *zconfig, const cfg_obj_t *voptions,
             !dns_name_equal(zname, dns_rootname)))
        {
                obj = NULL;
-               (void)cfg_map_get(zoptions, "primaries", &obj);
+               (void)get_zoneopt(zoptions, toptions, NULL, NULL, "primaries",
+                                 &obj);
                if (obj == NULL) {
                        /* If "primaries" was unset, check for "masters" */
-                       (void)cfg_map_get(zoptions, "masters", &obj);
+                       (void)get_zoneopt(zoptions, toptions, NULL, NULL,
+                                         "masters", &obj);
                } else {
                        const cfg_obj_t *obj2 = NULL;
 
                        /* ...bug if it was set, "masters" must not be. */
-                       (void)cfg_map_get(zoptions, "masters", &obj2);
+                       (void)get_zoneopt(zoptions, toptions, NULL, NULL,
+                                         "masters", &obj2);
                        if (obj2 != NULL) {
                                cfg_obj_log(obj, ISC_LOG_ERROR,
                                            "'primaries' and 'masters' cannot "
@@ -3414,7 +3454,8 @@ isccfg_check_zoneconf(const cfg_obj_t *zconfig, const cfg_obj_t *voptions,
         */
        if (ztype == CFG_ZONE_PRIMARY || ztype == CFG_ZONE_SECONDARY) {
                obj = NULL;
-               (void)cfg_map_get(zoptions, "parental-agents", &obj);
+               (void)get_zoneopt(zoptions, toptions, NULL, NULL,
+                                 "parental-agents", &obj);
                if (obj != NULL) {
                        uint32_t count;
                        tresult = validate_remotes(obj, config, &count, mctx);
@@ -3451,22 +3492,22 @@ isccfg_check_zoneconf(const cfg_obj_t *zconfig, const cfg_obj_t *voptions,
         */
        if (ztype == CFG_ZONE_PRIMARY || ztype == CFG_ZONE_SECONDARY) {
                bool signing = false;
-               isc_result_t res1, res2, res3;
-               const cfg_obj_t *au = NULL;
+               const cfg_obj_t *au = NULL, *up = NULL;
 
-               obj = NULL;
-               res1 = cfg_map_get(zoptions, "allow-update", &au);
-               obj = NULL;
-               res2 = cfg_map_get(zoptions, "update-policy", &obj);
-               if (res1 == ISC_R_SUCCESS && res2 == ISC_R_SUCCESS) {
-                       cfg_obj_log(obj, ISC_LOG_ERROR,
+               (void)get_zoneopt(zoptions, toptions, NULL, NULL,
+                                 "allow-update", &au);
+               (void)get_zoneopt(zoptions, toptions, NULL, NULL,
+                                 "update-policy", &up);
+
+               if (au != NULL && up != NULL) {
+                       cfg_obj_log(au, ISC_LOG_ERROR,
                                    "zone '%s': 'allow-update' is ignored "
                                    "when 'update-policy' is present",
                                    znamestr);
                        result = ISC_R_FAILURE;
-               } else if (res2 == ISC_R_SUCCESS) {
-                       res3 = check_update_policy(obj);
-                       if (res3 != ISC_R_SUCCESS) {
+               } else if (up != NULL) {
+                       tresult = check_update_policy(up);
+                       if (tresult != ISC_R_SUCCESS) {
                                result = ISC_R_FAILURE;
                        }
                }
@@ -3476,18 +3517,18 @@ isccfg_check_zoneconf(const cfg_obj_t *zconfig, const cfg_obj_t *voptions,
                 * we should also check for allow-update at the
                 * view and options levels.
                 */
-               if (res1 != ISC_R_SUCCESS) {
-                       res1 = get_zoneopt(voptions, goptions, NULL,
-                                          "allow-update", &au);
+               if (au == NULL) {
+                       (void)get_zoneopt(voptions, goptions, NULL, NULL,
+                                         "allow-update", &au);
                }
 
-               if (res2 == ISC_R_SUCCESS) {
+               if (up != NULL) {
                        ddns = true;
-               } else if (res1 == ISC_R_SUCCESS) {
+               } else if (au != NULL) {
                        dns_acl_t *acl = NULL;
-                       res1 = cfg_acl_fromconfig(au, config, actx, mctx, 0,
-                                                 &acl);
-                       if (res1 != ISC_R_SUCCESS) {
+                       tresult = cfg_acl_fromconfig(au, config, actx, mctx, 0,
+                                                    &acl);
+                       if (tresult != ISC_R_SUCCESS) {
                                cfg_obj_log(au, ISC_LOG_ERROR,
                                            "acl expansion failed: %s",
                                            isc_result_totext(result));
@@ -3501,9 +3542,10 @@ isccfg_check_zoneconf(const cfg_obj_t *zconfig, const cfg_obj_t *voptions,
                }
 
                obj = NULL;
-               res1 = cfg_map_get(zoptions, "inline-signing", &obj);
-               if (res1 == ISC_R_SUCCESS) {
-                       signing = cfg_obj_asboolean(obj);
+               (void)get_zoneopt(zoptions, toptions, NULL, NULL,
+                                 "inline-signing", &obj);
+               if (obj != NULL) {
+                       inline_signing = signing = cfg_obj_asboolean(obj);
                } else if (has_dnssecpolicy) {
                        signing = kasp_inlinesigning;
                }
@@ -3527,8 +3569,9 @@ isccfg_check_zoneconf(const cfg_obj_t *zconfig, const cfg_obj_t *voptions,
                }
 
                obj = NULL;
-               res1 = cfg_map_get(zoptions, "sig-signing-type", &obj);
-               if (res1 == ISC_R_SUCCESS) {
+               (void)get_zoneopt(zoptions, toptions, NULL, NULL,
+                                 "sig-signing-type", &obj);
+               if (obj != NULL) {
                        uint32_t type = cfg_obj_asuint32(obj);
                        if (type < 0xff00U || type > 0xffffU) {
                                cfg_obj_log(obj, ISC_LOG_ERROR,
@@ -3540,10 +3583,9 @@ isccfg_check_zoneconf(const cfg_obj_t *zconfig, const cfg_obj_t *voptions,
                }
 
                obj = NULL;
-               res1 = cfg_map_get(zoptions, "dnssec-loadkeys-interval", &obj);
-               if (res1 == ISC_R_SUCCESS && ztype == CFG_ZONE_SECONDARY &&
-                   !signing)
-               {
+               (void)get_zoneopt(zoptions, toptions, NULL, NULL,
+                                 "dnssec-loadkeys-interval", &obj);
+               if (obj != NULL && ztype == CFG_ZONE_SECONDARY && !signing) {
                        cfg_obj_log(obj, ISC_LOG_ERROR,
                                    "dnssec-loadkeys-interval: requires "
                                    "inline-signing when used in secondary "
@@ -3557,7 +3599,8 @@ isccfg_check_zoneconf(const cfg_obj_t *zconfig, const cfg_obj_t *voptions,
         */
        obj = NULL;
        if (root) {
-               (void)get_zoneopt(voptions, goptions, NULL, "forwarders", &obj);
+               (void)get_zoneopt(voptions, goptions, NULL, NULL, "forwarders",
+                                 &obj);
        }
        if (check_forward(config, zoptions, obj) != ISC_R_SUCCESS) {
                result = ISC_R_FAILURE;
@@ -3569,13 +3612,15 @@ isccfg_check_zoneconf(const cfg_obj_t *zconfig, const cfg_obj_t *voptions,
         */
        if (ztype == CFG_ZONE_FORWARD && (rfc1918 || ula)) {
                obj = NULL;
-               (void)cfg_map_get(zoptions, "forward", &obj);
+               (void)get_zoneopt(zoptions, toptions, NULL, NULL, "forward",
+                                 &obj);
                if (obj == NULL) {
                        /*
-                        * Forward mode not explicitly configured.
+                        * Forward mode not explicitly configured
+                        * at the zone or template level.
                         */
-                       (void)get_zoneopt(voptions, goptions, NULL, "forward",
-                                         &obj);
+                       (void)get_zoneopt(voptions, goptions, NULL, NULL,
+                                         "forward", &obj);
                        if (obj == NULL ||
                            strcasecmp(cfg_obj_asstring(obj), "first") == 0)
                        {
@@ -3593,7 +3638,8 @@ isccfg_check_zoneconf(const cfg_obj_t *zconfig, const cfg_obj_t *voptions,
         * Check validity of static stub server addresses.
         */
        obj = NULL;
-       (void)cfg_map_get(zoptions, "server-addresses", &obj);
+       (void)get_zoneopt(zoptions, toptions, NULL, NULL, "server-addresses",
+                         &obj);
        if (ztype == CFG_ZONE_STATICSTUB && obj != NULL) {
                CFG_LIST_FOREACH (obj, element) {
                        isc_sockaddr_t sa;
@@ -3616,7 +3662,7 @@ isccfg_check_zoneconf(const cfg_obj_t *zconfig, const cfg_obj_t *voptions,
         * Check validity of static stub server names.
         */
        obj = NULL;
-       (void)cfg_map_get(zoptions, "server-names", &obj);
+       (void)get_zoneopt(zoptions, toptions, NULL, NULL, "server-names", &obj);
        if (zname != NULL && ztype == CFG_ZONE_STATICSTUB && obj != NULL) {
                CFG_LIST_FOREACH (obj, element) {
                        const char *snamestr = NULL;
@@ -3649,7 +3695,8 @@ isccfg_check_zoneconf(const cfg_obj_t *zconfig, const cfg_obj_t *voptions,
        }
 
        obj = NULL;
-       (void)cfg_map_get(zoptions, "send-report-channel", &obj);
+       (void)get_zoneopt(zoptions, toptions, NULL, NULL, "send-report-channel",
+                         &obj);
        if (obj != NULL) {
                const char *str = cfg_obj_asstring(obj);
                dns_fixedname_t fad;
@@ -3677,7 +3724,8 @@ isccfg_check_zoneconf(const cfg_obj_t *zconfig, const cfg_obj_t *voptions,
         * Warn if key-directory doesn't exist
         */
        obj = NULL;
-       (void)get_zoneopt(zoptions, voptions, goptions, "key-directory", &obj);
+       (void)get_zoneopt(zoptions, toptions, voptions, goptions,
+                         "key-directory", &obj);
        if (obj != NULL) {
                dir = cfg_obj_asstring(obj);
 
@@ -3726,8 +3774,9 @@ isccfg_check_zoneconf(const cfg_obj_t *zconfig, const cfg_obj_t *voptions,
         */
        if (ztype == CFG_ZONE_PRIMARY || ztype == CFG_ZONE_SECONDARY) {
                obj = NULL;
-               tresult = cfg_map_get(zoptions, "log-report-channel", &obj);
-               if (tresult == ISC_R_SUCCESS && cfg_obj_asboolean(obj) &&
+               (void)get_zoneopt(zoptions, toptions, NULL, NULL,
+                                 "log-report-channel", &obj);
+               if (obj != NULL && cfg_obj_asboolean(obj) &&
                    dns_name_equal(zname, dns_rootname))
                {
                        cfg_obj_log(zconfig, ISC_LOG_ERROR,
@@ -3754,14 +3803,14 @@ isccfg_check_zoneconf(const cfg_obj_t *zconfig, const cfg_obj_t *voptions,
         */
        obj = NULL;
        dlz = false;
-       tresult = cfg_map_get(zoptions, "dlz", &obj);
-       if (tresult == ISC_R_SUCCESS) {
+       (void)get_zoneopt(zoptions, toptions, NULL, NULL, "dlz", &obj);
+       if (obj != NULL) {
                dlz = true;
        }
 
        obj = NULL;
-       tresult = cfg_map_get(zoptions, "database", &obj);
-       if (dlz && tresult == ISC_R_SUCCESS) {
+       (void)get_zoneopt(zoptions, toptions, NULL, NULL, "database", &obj);
+       if (dlz && obj != NULL) {
                cfg_obj_log(zconfig, ISC_LOG_ERROR,
                            "zone '%s': cannot specify both 'dlz' "
                            "and 'database'",
@@ -3769,28 +3818,23 @@ isccfg_check_zoneconf(const cfg_obj_t *zconfig, const cfg_obj_t *voptions,
                if (result == ISC_R_SUCCESS) {
                        result = ISC_R_FAILURE;
                }
-       } else if (!dlz &&
-                  (tresult == ISC_R_NOTFOUND ||
-                   (tresult == ISC_R_SUCCESS &&
-                    strcmp(ZONEDB_DEFAULT, cfg_obj_asstring(obj)) == 0)))
+       } else if (!dlz && (obj == NULL ||
+                           strcmp(ZONEDB_DEFAULT, cfg_obj_asstring(obj)) == 0))
        {
-               isc_result_t res1;
                const cfg_obj_t *fileobj = NULL;
-               tresult = cfg_map_get(zoptions, "file", &fileobj);
-               obj = NULL;
-               res1 = cfg_map_get(zoptions, "inline-signing", &obj);
-               if (tresult != ISC_R_SUCCESS &&
+               (void)get_zoneopt(zoptions, toptions, NULL, NULL, "file",
+                                 &fileobj);
+               if (fileobj == NULL &&
                    (ztype == CFG_ZONE_PRIMARY || ztype == CFG_ZONE_HINT ||
-                    (ztype == CFG_ZONE_SECONDARY && res1 == ISC_R_SUCCESS &&
-                     cfg_obj_asboolean(obj))))
+                    (ztype == CFG_ZONE_SECONDARY && inline_signing)))
                {
                        cfg_obj_log(zconfig, ISC_LOG_ERROR,
                                    "zone '%s': missing 'file' entry",
                                    znamestr);
                        if (result == ISC_R_SUCCESS) {
-                               result = tresult;
+                               result = ISC_R_FAILURE;
                        }
-               } else if (tresult == ISC_R_SUCCESS && files != NULL &&
+               } else if (fileobj != NULL && files != NULL &&
                           (ztype == CFG_ZONE_SECONDARY ||
                            ztype == CFG_ZONE_MIRROR || ddns ||
                            has_dnssecpolicy))
@@ -3800,7 +3844,7 @@ isccfg_check_zoneconf(const cfg_obj_t *zconfig, const cfg_obj_t *voptions,
                        {
                                result = tresult;
                        }
-               } else if (tresult == ISC_R_SUCCESS && files != NULL &&
+               } else if (fileobj != NULL && files != NULL &&
                           (ztype == CFG_ZONE_PRIMARY ||
                            ztype == CFG_ZONE_HINT))
                {
@@ -3817,13 +3861,13 @@ isccfg_check_zoneconf(const cfg_obj_t *zconfig, const cfg_obj_t *voptions,
         * consistent.
         */
        obj = NULL;
-       tresult = get_zoneopt(zoptions, voptions, goptions, "masterfile-format",
-                             &obj);
+       tresult = get_zoneopt(zoptions, toptions, voptions, goptions,
+                             "masterfile-format", &obj);
        if (tresult == ISC_R_SUCCESS &&
            strcasecmp(cfg_obj_asstring(obj), "raw") == 0)
        {
                obj = NULL;
-               tresult = get_zoneopt(zoptions, voptions, goptions,
+               tresult = get_zoneopt(zoptions, toptions, voptions, goptions,
                                      "masterfile-style", &obj);
                if (tresult == ISC_R_SUCCESS) {
                        cfg_obj_log(obj, ISC_LOG_ERROR,
@@ -3838,8 +3882,8 @@ isccfg_check_zoneconf(const cfg_obj_t *zconfig, const cfg_obj_t *voptions,
        }
 
        obj = NULL;
-       (void)get_zoneopt(zoptions, voptions, goptions, "max-journal-size",
-                         &obj);
+       (void)get_zoneopt(zoptions, toptions, voptions, goptions,
+                         "max-journal-size", &obj);
        if (obj != NULL && cfg_obj_isuint64(obj)) {
                uint64_t value = cfg_obj_asuint64(obj);
                if (value > DNS_JOURNAL_SIZE_MAX) {
@@ -3854,8 +3898,8 @@ isccfg_check_zoneconf(const cfg_obj_t *zconfig, const cfg_obj_t *voptions,
        }
 
        obj = NULL;
-       (void)get_zoneopt(zoptions, voptions, goptions, "min-transfer-rate-in",
-                         &obj);
+       (void)get_zoneopt(zoptions, toptions, voptions, goptions,
+                         "min-transfer-rate-in", &obj);
        if (obj != NULL) {
                uint32_t traffic_bytes =
                        cfg_obj_asuint32(cfg_tuple_get(obj, "traffic_bytes"));
index 82c50890f9679213486f1ef8ad390d444c25ca9d..d5c61b2402ba46bed8b6f98f4daa53ee4eace1e7 100644 (file)
@@ -143,6 +143,8 @@ static cfg_type_t cfg_type_sizeval;
 static cfg_type_t cfg_type_sockaddr4wild;
 static cfg_type_t cfg_type_sockaddr6wild;
 static cfg_type_t cfg_type_statschannels;
+static cfg_type_t cfg_type_template;
+static cfg_type_t cfg_type_templateopts;
 static cfg_type_t cfg_type_tlsconf;
 static cfg_type_t cfg_type_view;
 static cfg_type_t cfg_type_viewopts;
@@ -471,6 +473,18 @@ static cfg_type_t cfg_type_zone = { "zone",             cfg_parse_tuple,
                                    cfg_print_tuple, cfg_doc_tuple,
                                    &cfg_rep_tuple,  zone_fields };
 
+/*%
+ * A zone statement.
+ */
+static cfg_tuplefielddef_t template_fields[] = {
+       { "name", &cfg_type_astring, 0 },
+       { "options", &cfg_type_templateopts, 0 },
+       { NULL, NULL, 0 }
+};
+static cfg_type_t cfg_type_template = { "template",     cfg_parse_tuple,
+                                       cfg_print_tuple, cfg_doc_tuple,
+                                       &cfg_rep_tuple,  template_fields };
+
 /*%
  * A dnssec-policy statement.
  */
@@ -1155,6 +1169,7 @@ static cfg_clausedef_t namedconf_clauses[] = {
        { "statistics-channels", &cfg_type_statschannels,
          CFG_CLAUSEFLAG_MULTI | CFG_CLAUSEFLAG_NOTCONFIGURED },
 #endif
+       { "template", &cfg_type_template, CFG_CLAUSEFLAG_MULTI },
        { "tls", &cfg_type_tlsconf, CFG_CLAUSEFLAG_MULTI },
        { "view", &cfg_type_view, CFG_CLAUSEFLAG_MULTI },
        { NULL, NULL, 0 }
@@ -2429,7 +2444,6 @@ static cfg_clausedef_t zone_only_clauses[] = {
        { "file", &cfg_type_qstring,
          CFG_ZONE_PRIMARY | CFG_ZONE_SECONDARY | CFG_ZONE_MIRROR |
                  CFG_ZONE_STUB | CFG_ZONE_HINT | CFG_ZONE_REDIRECT },
-       { "in-view", &cfg_type_astring, CFG_ZONE_INVIEW },
        { "initial-file", &cfg_type_qstring, CFG_ZONE_PRIMARY },
        { "inline-signing", &cfg_type_boolean,
          CFG_ZONE_PRIMARY | CFG_ZONE_SECONDARY },
@@ -2457,6 +2471,15 @@ static cfg_clausedef_t zone_only_clauses[] = {
        { NULL, NULL, 0 }
 };
 
+static cfg_clausedef_t non_template_clauses[] = {
+       { "in-view", &cfg_type_astring, CFG_ZONE_INVIEW },
+       { "template", &cfg_type_astring,
+         CFG_ZONE_PRIMARY | CFG_ZONE_SECONDARY | CFG_ZONE_MIRROR |
+                 CFG_ZONE_STUB | CFG_ZONE_STATICSTUB | CFG_ZONE_DELEGATION |
+                 CFG_ZONE_HINT | CFG_ZONE_REDIRECT | CFG_ZONE_FORWARD },
+       { NULL, NULL, 0 }
+};
+
 /*% The top-level named.conf syntax. */
 
 static cfg_clausedef_t *namedconf_clausesets[] = { namedconf_clauses,
@@ -2493,11 +2516,24 @@ static cfg_type_t cfg_type_viewopts = { "view",        cfg_parse_map,
 
 /*% The "zone" statement syntax. */
 
-static cfg_clausedef_t *zone_clausesets[] = { zone_only_clauses, zone_clauses,
+static cfg_clausedef_t *zone_clausesets[] = { non_template_clauses,
+                                             zone_only_clauses, zone_clauses,
                                              NULL };
 cfg_type_t cfg_type_zoneopts = { "zoneopts",  cfg_parse_map, cfg_print_map,
                                 cfg_doc_map, &cfg_rep_map,  zone_clausesets };
 
+/*%
+ * The "template" statement syntax: any clause that "zone" can take,
+ * except that zones can have a "template" option and templates cannot.
+ */
+
+static cfg_clausedef_t *template_clausesets[] = { zone_only_clauses,
+                                                 zone_clauses, NULL };
+static cfg_type_t cfg_type_templateopts = {
+       "templateopts", cfg_parse_map, cfg_print_map,
+       cfg_doc_map,    &cfg_rep_map,  template_clausesets
+};
+
 /*% The "dnssec-policy" statement syntax. */
 static cfg_clausedef_t *dnssecpolicy_clausesets[] = { dnssecpolicy_clauses,
                                                      NULL };
@@ -3845,6 +3881,14 @@ cfg_clause_validforzone(const char *name, unsigned int ztype) {
                }
                valid = true;
        }
+       for (clause = non_template_clauses; clause->name != NULL; clause++) {
+               if ((clause->flags & ztype) == 0 ||
+                   strcmp(clause->name, name) != 0)
+               {
+                       continue;
+               }
+               valid = true;
+       }
 
        return valid;
 }
@@ -3853,23 +3897,25 @@ void
 cfg_print_zonegrammar(const unsigned int zonetype, unsigned int flags,
                      void (*f)(void *closure, const char *text, int textlen),
                      void *closure) {
-#define NCLAUSES                                               \
-       (((sizeof(zone_clauses) + sizeof(zone_only_clauses)) / \
-         sizeof(clause[0])) -                                 \
-        1)
+#define NCLAUSES                                                      \
+       ARRAY_SIZE(non_template_clauses) + ARRAY_SIZE(zone_clauses) + \
+               ARRAY_SIZE(zone_only_clauses) - 2
 
        cfg_printer_t pctx;
-       cfg_clausedef_t *clause = NULL;
        cfg_clausedef_t clauses[NCLAUSES];
+       cfg_clausedef_t *clause = clauses;
 
        pctx.f = f;
        pctx.closure = closure;
        pctx.indent = 0;
        pctx.flags = flags;
 
-       memmove(clauses, zone_clauses, sizeof(zone_clauses));
-       memmove(clauses + sizeof(zone_clauses) / sizeof(zone_clauses[0]) - 1,
-               zone_only_clauses, sizeof(zone_only_clauses));
+       memmove(clause, zone_clauses, sizeof(zone_clauses));
+       clause += ARRAY_SIZE(zone_clauses) - 1;
+       memmove(clause, zone_only_clauses, sizeof(zone_only_clauses));
+       clause += ARRAY_SIZE(zone_only_clauses) - 1;
+       memmove(clause, non_template_clauses, sizeof(non_template_clauses));
+
        qsort(clauses, NCLAUSES - 1, sizeof(clause[0]), cmp_clause);
 
        cfg_print_cstr(&pctx, "zone <string> [ <class> ] {\n");