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

new: usr: Add support for zone templates

To simplify the configuration of multiple similar zones, BIND now supports a zone template mechanism. `template` blocks containing zone options can be defined at the top level of the configuration file; they can then be referenced in `zone` statements. A zone referencing a template will use the options in the specified `template` block as defaults. (Options locally defined in the `zone` statement override the template.)

The filename for a zone can now be generated parametrically from a format specified in the `file` option. The first occurrences of `$name`, `$type` and `$view` in `file` are replaced with the zone origin, the zone type (i.e., primary, secondary, etc), and the view name, respectively.

Primary zones can now take an `initial-file` option, specifying the path to a generic zone file that will be copied into the zone's `file` path when the zone is first loaded, if the `file` does not already exist.

For example, the following template can be used for primary zones:
```
        template primary {
                type primary;
                file "$name.db";
                initial-file "generic.db";
        };
```

With this template in place, a new primary zone could be added using a single `rndc addzone` command:

```
        $ rndc addzone example.com '{ template primary; };'
```

The zone would be created using the filename `example.com.db`, which would be copied into place from `generic.db`.

Closes #2964

Merge branch '2964-zone-templates' into 'main'

See merge request isc-projects/bind9!10407
This commit is contained in:
Evan Hunt 2025-06-03 19:45:06 +00:00
commit 93c44ba551
41 changed files with 1149 additions and 224 deletions

View File

@ -656,7 +656,7 @@ load_zone(isc_mem_t *mctx, const char *zonename, const char *filename,
dns_zone_setstream(zone, stdin, fileformat,
&dns_master_style_default);
} else {
dns_zone_setfile(zone, filename, fileformat,
dns_zone_setfile(zone, filename, NULL, fileformat,
&dns_master_style_default);
}
if (journal != NULL) {

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

View 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);

View 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.
*/

View 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 "
@ -6599,7 +6616,7 @@ add_keydata_zone(dns_view_t *view, const char *directory, isc_mem_t *mctx) {
CHECK(isc_file_sanitize(
directory, defaultview ? "managed-keys" : view->name,
defaultview ? "bind" : "mkeys", filename, sizeof(filename)));
dns_zone_setfile(zone, filename, dns_masterformat_text,
dns_zone_setfile(zone, filename, NULL, dns_masterformat_text,
&dns_master_style_default);
dns_zone_setview(zone, view);
@ -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),

View 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,12 +877,15 @@ 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;
const char *dupcheck;
dns_checkdstype_t checkdstype = dns_checkdstype_yes;
@ -909,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;
@ -950,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);
@ -959,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;
@ -991,11 +1010,17 @@ 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 = named_config_get(nooptions, "initial-file", &obj);
if (result == ISC_R_SUCCESS) {
initial_file = cfg_obj_asstring(obj);
}
if (ztype == dns_zone_secondary || ztype == dns_zone_mirror) {
masterformat = dns_masterformat_raw;
} else {
@ -1053,18 +1078,21 @@ named_zone_configure(const cfg_obj_t *config, const cfg_obj_t *vconfig,
size_t signedlen = strlen(filename) + sizeof(SIGNED);
char *signedname;
dns_zone_setfile(raw, filename, masterformat, masterstyle);
dns_zone_setfile(raw, filename, initial_file, masterformat,
masterstyle);
signedname = isc_mem_get(mctx, signedlen);
(void)snprintf(signedname, signedlen, "%s" SIGNED, filename);
dns_zone_setfile(zone, signedname, dns_masterformat_raw, NULL);
dns_zone_setfile(zone, signedname, NULL, dns_masterformat_raw,
NULL);
isc_mem_put(mctx, signedname, signedlen);
} else {
dns_zone_setfile(zone, filename, masterformat, masterstyle);
dns_zone_setfile(zone, filename, initial_file, masterformat,
masterstyle);
}
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));
}
@ -1423,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,
@ -1523,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));
}
/*
@ -1608,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);
@ -1759,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));
@ -1772,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);
}
/*
@ -1876,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;
@ -1917,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;
@ -1961,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 {
@ -1989,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");
}
@ -2031,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;
}

View 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

View 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";

View 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;

View 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;

View File

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

View 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",

View File

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

View File

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

View File

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

View File

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

View File

@ -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";
};

View File

@ -11,6 +11,7 @@
import difflib
import shutil
import os
from typing import Optional
import dns.rcode
@ -150,3 +151,7 @@ def file_contents_equal(file1, file2):
assert not line.startswith("+ ") and not line.startswith(
"- "
), f'file contents of "{file1}" and "{file2}" differ'
def file_empty(file):
assert os.path.getsize(file) == 0

View File

@ -34,12 +34,33 @@ zone "." {
file "../../_common/root.hint";
};
zone "example" {
template primary {
type primary;
file "example.db";
file "$name.db";
};
zone "example" {
template primary;
};
zone "missing" {
type primary;
file "missing.db";
template primary;
};
zone "different" {
template primary;
initial-file "example.db";
file "alternate.db";
};
zone "initial" {
type primary;
file "copied.db";
initial-file "example.db";
};
zone "present" {
type primary;
file "present.db";
initial-file "example.db";
};

View File

@ -0,0 +1,19 @@
#!/bin/sh -e
# 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.
# shellcheck source=conf.sh
. ../conf.sh
set -e
touch ns2/present.db

View File

@ -15,6 +15,11 @@ import dns.message
import dns.zone
import isctest
import pytest
pytestmark = pytest.mark.extra_artifacts(
["ns2/copied.db", "ns2/present.db", "ns2/alternate.db"]
)
def test_masterfile_include_semantics():
@ -87,6 +92,54 @@ example. 300 IN SOA mname1. . 2010042407 20 20 1814400 3600
isctest.check.rrsets_equal(res_soa.answer, expected.answer, compare_ttl=True)
def test_masterfile_initial_file():
"""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
initial. 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/copied.db")
# 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")

View 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`.
@ -7132,13 +7139,21 @@ Zone Options
:tags: zone
:short: Specifies the zone's filename.
This sets the zone's filename. In :any:`primary <type primary>`, :any:`hint <type hint>`, and :any:`redirect <type redirect>`
This sets the zone's filename. In :any:`primary <type primary>`,
:any:`hint <type hint>`, and :any:`redirect <type redirect>`
zones which do not have :any:`primaries` defined, zone data is loaded from
this file. In :any:`secondary <type secondary>`, :any:`mirror <type mirror>`, :any:`stub <type stub>`, and :any:`redirect <type redirect>` zones
which do have :any:`primaries` defined, zone data is retrieved from
another server and saved in this file. This option is not applicable
to other zone types.
The filename can be generated parametrically by including special
tokens in the string: the first instance of ``$name`` in the string
is replaced with the zone name in lower case; the first instance of
``$type`` is replaced with the zone type -- i.e., ``primary``,
``secondary``, etc); and the first instance of ``$view`` is replaced
with the view name. These tokens are case-insensitive.
:any:`forward`
This option is only meaningful if the zone has a forwarders list. The ``only`` value
causes the lookup to fail after trying the forwarders and getting no
@ -7149,6 +7164,34 @@ Zone Options
specified in a zone of type :any:`forward`, no forwarding is done for
the zone and the global options are not used.
.. namedconf:statement:: initial-file
:tags: zone
:short: Specifies a file with the initial contents of a newly created zone.
When a :any:`primary <type primary>` zone is loaded for the first time,
if the zone's :any:`file` does not exist but ``initial-file`` does, the
zone file is copied into place from the initial file before loading.
This can be used to simplify the process of adding new zones, removing
the need to create the zone file before configuring the zone. For example,
a template zonefile could be used by running:
::
$ rndc addzone example.com \
'{ type primary; file "$name.db"; initial-file "template.db"; };'
This creates a zone ``example.com``, with filename ``example.com.db``.
Using "@" to reference the zone origin within the initial file
allows the same file to be used for multiple zones, as in:
::
$TTL 300
@ IN SOA ns hosmaster 1 1800 1800 86400 3600
NS ns
ns A 192.0.2.1
.. namedconf:statement:: journal
:tags: zone
:short: Allows the default journal's filename to be overridden.
@ -7341,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

View 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>;
};

View File

@ -2,4 +2,5 @@ zone <string> [ <class> ] {
type hint;
check-names ( fail | warn | ignore );
file <quoted_string>;
template <string>;
};

View 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>;

View 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>;

View File

@ -27,6 +27,7 @@ zone <string> [ <class> ] {
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>;
@ -59,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>;

View 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> );
};

View 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>;

View 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> );
};

View 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> );

View File

@ -212,7 +212,7 @@ LLVMFuzzerInitialize(int *argc ISC_ATTR_UNUSED, char ***argv ISC_ATTR_UNUSED) {
dns_zone_setclass(zone, view->rdclass);
dns_zone_settype(zone, dns_zone_primary);
dns_zone_setkeydirectory(zone, wd);
dns_zone_setfile(zone, pathbuf, dns_masterformat_text,
dns_zone_setfile(zone, pathbuf, NULL, dns_masterformat_text,
&dns_master_style_default);
result = dns_zone_load(zone, false);

View File

@ -290,16 +290,18 @@ dns_zone_getorigin(dns_zone_t *zone);
*/
void
dns_zone_setfile(dns_zone_t *zone, const char *file, dns_masterformat_t format,
const dns_master_style_t *style);
dns_zone_setfile(dns_zone_t *zone, const char *file, const char *initial_file,
dns_masterformat_t format, const dns_master_style_t *style);
/*%<
* Sets the name of the master file in the format of 'format' from which
* the zone loads its database to 'file'.
*
* For zones that have no associated master file, 'file' will be NULL.
* For some zone types, e.g. secondary zones, 'file' is optional, but
* for primary zones it is mandatory. If the master file does not exist
* during loading, then it will be copied into place from 'initial_file'.
*
* For zones with persistent databases, the file name
* setting is ignored.
* For zones with persistent databases, the file name setting is ignored.
*
* Require:
*\li 'zone' to be a valid zone.

View File

@ -284,6 +284,7 @@ struct dns_zone {
dns_name_t origin;
dns_name_t rad;
char *masterfile;
char *initfile;
const FILE *stream; /* loading from a stream? */
ISC_LIST(dns_include_t) includes; /* Include files */
ISC_LIST(dns_include_t) newincludes; /* Loading */
@ -1289,9 +1290,13 @@ zone_free(dns_zone_t *zone) {
if (zone->masterfile != NULL) {
isc_mem_free(zone->mctx, zone->masterfile);
}
if (zone->initfile != NULL) {
isc_mem_free(zone->mctx, zone->initfile);
}
if (zone->keydirectory != NULL) {
isc_mem_free(zone->mctx, zone->keydirectory);
}
if (zone->kasp != NULL) {
dns_kasp_detach(&zone->kasp);
}
@ -1792,14 +1797,117 @@ setstring(dns_zone_t *zone, char **field, const char *value) {
*field = copy;
}
static int
position_order(const void *a, const void *b) {
/* sort char pointers in order of which occurs first in memory */
return (char *)*(char **)a - (char *)*(char **)b;
}
static isc_result_t
putmem(isc_buffer_t *b, const char *base, size_t length) {
size_t space = isc_buffer_availablelength(b) - 1;
if (space < length) {
isc_buffer_putmem(b, (const unsigned char *)base, space);
return ISC_R_NOSPACE;
}
isc_buffer_putmem(b, (const unsigned char *)base, length);
return ISC_R_SUCCESS;
}
/*
* Set the masterfile field, expanding $name to the zone name,
* $type to the zone type, and $view to the view name. Cap the
* length at PATH_MAX.
*/
static void
setfilename(dns_zone_t *zone, char **field, const char *value) {
isc_result_t result;
char *t = NULL, *n = NULL, *v = NULL;
char *positions[3];
char filename[PATH_MAX];
isc_buffer_t b;
size_t tags = 0;
if (value == NULL) {
*field = NULL;
return;
}
t = strcasestr(value, "$type");
if (t != NULL) {
positions[tags++] = t;
}
n = strcasestr(value, "$name");
if (n != NULL) {
positions[tags++] = n;
}
v = strcasestr(value, "$view");
if (v != NULL) {
positions[tags++] = v;
}
if (tags == 0) {
setstring(zone, field, value);
return;
}
isc_buffer_init(&b, filename, sizeof(filename));
/* sort the tag offsets in order of occurrence */
qsort(positions, tags, sizeof(char *), position_order);
const char *p = value;
for (size_t i = 0; i < tags; i++) {
size_t tokenlen = 0;
CHECK(putmem(&b, p, (positions[i] - p)));
p = positions[i];
INSIST(p != NULL);
if (p == n) {
dns_fixedname_t fn;
dns_name_t *name = dns_fixedname_initname(&fn);
char namebuf[DNS_NAME_FORMATSIZE];
result = dns_name_downcase(&zone->origin, name);
RUNTIME_CHECK(result == ISC_R_SUCCESS);
dns_name_format(name, namebuf, sizeof(namebuf));
CHECK(putmem(&b, namebuf, strlen(namebuf)));
tokenlen = 5; /* "$name" */
} else if (p == t) {
const char *typename = dns_zonetype_name(zone->type);
CHECK(putmem(&b, typename, strlen(typename)));
tokenlen = 5; /* "$type" */
} else if (p == v) {
CHECK(putmem(&b, zone->view->name,
strlen(zone->view->name)));
tokenlen = 5; /* "$view" */
}
/* Advance the input pointer past the token */
p += tokenlen;
}
const char *end = value + strlen(value);
putmem(&b, p, end - p);
failure:
isc_buffer_putuint8(&b, 0);
setstring(zone, field, filename);
}
void
dns_zone_setfile(dns_zone_t *zone, const char *file, dns_masterformat_t format,
const dns_master_style_t *style) {
dns_zone_setfile(dns_zone_t *zone, const char *file, const char *initial_file,
dns_masterformat_t format, const dns_master_style_t *style) {
REQUIRE(DNS_ZONE_VALID(zone));
REQUIRE(zone->stream == NULL);
LOCK_ZONE(zone);
setstring(zone, &zone->masterfile, file);
setfilename(zone, &zone->masterfile, file);
setstring(zone, &zone->initfile, initial_file);
zone->masterformat = format;
if (format == dns_masterformat_text) {
zone->masterstyle = style;
@ -2105,6 +2213,42 @@ zone_touched(dns_zone_t *zone) {
return false;
}
static isc_result_t
copy_initfile(dns_zone_t *zone) {
isc_result_t result;
FILE *input = NULL, *output = NULL;
size_t len;
CHECK(isc_stdio_open(zone->initfile, "r", &input));
CHECK(isc_stdio_open(zone->masterfile, "w", &output));
CHECK(isc_file_getsizefd(fileno(input), (off_t *)&len));
do {
char buf[BUFSIZ];
size_t rval;
result = isc_stdio_read(buf, 1, sizeof(buf), input, &rval);
if (result != ISC_R_SUCCESS && result != ISC_R_EOF) {
goto failure;
}
CHECK(isc_stdio_write(buf, rval, 1, output, NULL));
len -= rval;
} while (len > 0);
failure:
if (input != NULL) {
isc_stdio_close(input);
}
if (output != NULL) {
if (result != ISC_R_SUCCESS) {
isc_file_remove(zone->masterfile);
}
isc_stdio_close(output);
}
return result;
}
/*
* Note: when dealing with inline-signed zones, external callers will always
* call zone_load() for the secure zone; zone_load() calls itself recursively
@ -2347,6 +2491,22 @@ zone_load(dns_zone_t *zone, unsigned int flags, bool locked) {
}
}
if (zone->type == dns_zone_primary && zone->masterfile != NULL &&
!isc_file_exists(zone->masterfile) && zone->initfile != NULL)
{
dns_zone_logc(zone, DNS_LOGCATEGORY_ZONELOAD, ISC_LOG_INFO,
"zone file %s not found; copying initial "
"file %s",
zone->masterfile, zone->initfile);
result = copy_initfile(zone);
if (result != ISC_R_SUCCESS) {
dns_zone_logc(zone, DNS_LOGCATEGORY_ZONELOAD,
ISC_LOG_ERROR, "copy from %s failed: %s",
zone->initfile,
isc_result_totext(result));
}
}
dns_zone_logc(zone, DNS_LOGCATEGORY_ZONELOAD, ISC_LOG_DEBUG(1),
"starting load");

View 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"));

View 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,7 @@ 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 },
{ "ixfr-base", NULL, CFG_CLAUSEFLAG_ANCIENT },
@ -2456,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,
@ -2492,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 };
@ -3844,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;
}
@ -3852,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");

View File

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

View File

@ -120,7 +120,8 @@ nsec3param_change_test(const nsec3param_change_test_params_t *test) {
assert_int_equal(result, ISC_R_SUCCESS);
dns_zone_setfile(zone, TESTS_DIR "/testdata/nsec3param/nsec3.db.signed",
dns_masterformat_text, &dns_master_style_default);
NULL, dns_masterformat_text,
&dns_master_style_default);
result = dns_zone_load(zone, false);
assert_int_equal(result, ISC_R_SUCCESS);

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

@ -0,0 +1,121 @@
/*
* Copyright (C) Internet Systems Consortium, Inc. ("ISC")
*
* SPDX-License-Identifier: MPL-2.0
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, you can obtain one at https://mozilla.org/MPL/2.0/.
*
* See the COPYRIGHT file distributed with this work for additional
* information regarding copyright ownership.
*/
#include <inttypes.h>
#include <sched.h> /* IWYU pragma: keep */
#include <setjmp.h>
#include <stdarg.h>
#include <stdbool.h>
#include <stddef.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#define UNIT_TESTING
#include <cmocka.h>
#include <isc/atomic.h>
#include <isc/lib.h>
#include <dns/lib.h>
#include <dns/view.h>
#include <tests/dns.h>
typedef struct {
const char *input, *expected;
} zonefile_test_params_t;
static int
setup_test(void **state) {
setup_loopmgr(state);
return 0;
}
static int
teardown_test(void **state) {
teardown_loopmgr(state);
return 0;
}
ISC_LOOP_TEST_IMPL(filename) {
isc_result_t result;
dns_zone_t *zone = NULL;
const zonefile_test_params_t tests[] = {
{ "$name", "example.com" },
{ "$name.db", "example.com.db" },
{ "./dir/$name.db", "./dir/example.com.db" },
{ "$type", "primary" },
{ "$type-file", "primary-file" },
{ "./dir/$type", "./dir/primary" },
{ "./$type/$name.db", "./primary/example.com.db" },
{ "./$TyPe/$NAmE.db", "./primary/example.com.db" },
{ "./$name/$type", "./example.com/primary" },
{ "$name.$type", "example.com.primary" },
{ "$type$name", "primaryexample.com" },
{ "$type$type", "primary$type" },
{ "$name$name", "example.com$name" },
{ "typename", "typename" },
{ "$view", "local" },
{ "./$type/$view-$name.db", "./primary/local-example.com.db" },
{ "./$view/$type-$name.db", "./local/primary-example.com.db" },
{ "./$name/$view-$type.db", "./example.com/local-primary.db" },
{ "", "" },
};
dns_view_t *view = NULL;
result = dns_test_makeview("local", false, false, &view);
assert_int_equal(result, ISC_R_SUCCESS);
/* use .COM here to test that the name is correctly downcased */
result = dns_test_makezone("example.COM", &zone, view, false);
assert_int_equal(result, ISC_R_SUCCESS);
dns_zone_setview(zone, view);
dns_view_detach(&view);
for (size_t i = 0; i < ARRAY_SIZE(tests); i++) {
dns_zone_setfile(zone, tests[i].input, NULL,
dns_masterformat_text,
&dns_master_style_default);
assert_string_equal(dns_zone_getfile(zone), tests[i].expected);
}
/* test PATH_MAX overrun */
char longname[PATH_MAX] = { 0 };
memset(longname, 'x', sizeof(longname) - 1);
dns_zone_setfile(zone, longname, NULL, dns_masterformat_text,
&dns_master_style_default);
assert_string_equal(dns_zone_getfile(zone), longname);
/*
* overwrite the beginning of the long name with $name. when
* it's expanded to the zone name, the resulting string should
* still be capped at PATH_MAX characters.
*/
memmove(longname, "$name", 5);
dns_zone_setfile(zone, longname, NULL, dns_masterformat_text,
&dns_master_style_default);
assert_int_equal(strlen(longname), PATH_MAX - 1);
memmove(longname, "example.com", 11);
assert_string_equal(dns_zone_getfile(zone), longname);
dns_zone_detach(&zone);
isc_loopmgr_shutdown(loopmgr);
}
ISC_TEST_LIST_START
ISC_TEST_ENTRY_CUSTOM(filename, setup_test, teardown_test)
ISC_TEST_LIST_END
ISC_TEST_MAIN

View File

@ -184,7 +184,7 @@ ISC_LOOP_TEST_IMPL(asyncload_zone) {
fwrite(buf, 1, n, zonefile);
fflush(zonefile);
dns_zone_setfile(zone, "./zone.data", dns_masterformat_text,
dns_zone_setfile(zone, "./zone.data", NULL, dns_masterformat_text,
&dns_master_style_default);
dns_zone_asyncload(zone, false, load_done_first, zone);
@ -235,19 +235,19 @@ ISC_LOOP_TEST_IMPL(asyncload_zt) {
result = dns_test_makezone("foo", &zone1, NULL, true);
assert_int_equal(result, ISC_R_SUCCESS);
dns_zone_setfile(zone1, TESTS_DIR "/testdata/zt/zone1.db",
dns_zone_setfile(zone1, TESTS_DIR "/testdata/zt/zone1.db", NULL,
dns_masterformat_text, &dns_master_style_default);
view = dns_zone_getview(zone1);
result = dns_test_makezone("bar", &zone2, view, false);
assert_int_equal(result, ISC_R_SUCCESS);
dns_zone_setfile(zone2, TESTS_DIR "/testdata/zt/zone1.db",
dns_zone_setfile(zone2, TESTS_DIR "/testdata/zt/zone1.db", NULL,
dns_masterformat_text, &dns_master_style_default);
/* This one will fail to load */
result = dns_test_makezone("fake", &zone3, view, false);
assert_int_equal(result, ISC_R_SUCCESS);
dns_zone_setfile(zone3, TESTS_DIR "/testdata/zt/nonexistent.db",
dns_zone_setfile(zone3, TESTS_DIR "/testdata/zt/nonexistent.db", NULL,
dns_masterformat_text, &dns_master_style_default);
rcu_read_lock();

View File

@ -168,7 +168,7 @@ ns_test_serve_zone(const char *zonename, const char *filename,
/*
* Set path to the master file for the zone and then load it.
*/
dns_zone_setfile(served_zone, filename, dns_masterformat_text,
dns_zone_setfile(served_zone, filename, NULL, dns_masterformat_text,
&dns_master_style_default);
result = dns_zone_load(served_zone, false);
if (result != ISC_R_SUCCESS) {