diff --git a/doc/reference.rst b/doc/reference.rst index 8538fd11b..ef34b25a7 100644 --- a/doc/reference.rst +++ b/doc/reference.rst @@ -2604,6 +2604,7 @@ master An ordered list of references :ref:`remote` and :ref:`remotes` to zone primary servers (formerly known as master servers). +Empty value is allowed for template value overriding. *Default:* not set @@ -2630,6 +2631,7 @@ notify An ordered list of references :ref:`remote` and :ref:`remotes` to secondary servers to which notify message is sent if the zone changes. +Empty value is allowed for template value overriding. *Default:* not set @@ -2933,7 +2935,7 @@ ds-push ------- Per zone configuration of :ref:`policy_ds-push`. This option overrides possible -per policy option. +per policy option. Empty value is allowed for template value overriding. *Default:* not set diff --git a/src/knot/conf/conf.c b/src/knot/conf/conf.c index 977d9d8cf..5e9ed8393 100644 --- a/src/knot/conf/conf.c +++ b/src/knot/conf/conf.c @@ -191,6 +191,10 @@ conf_val_t conf_zone_get_txn( conf_db_get(conf, txn, C_ZONE, key1_name, dname, dname_size, &val); switch (val.code) { case KNOT_EOK: + if (val.blob_len == 1 && (val.item->flags & CONF_REF_EMPTY)) { + static const conf_val_t empty = { .code = KNOT_ENOENT }; + return empty; + } return val; default: CONF_LOG_ZONE(LOG_ERR, dname, "failed to read '%s/%s' (%s)", diff --git a/src/knot/conf/conf.h b/src/knot/conf/conf.h index 91366a6bb..f79a284ef 100644 --- a/src/knot/conf/conf.h +++ b/src/knot/conf/conf.h @@ -32,6 +32,7 @@ #define CONF_IO_FRLD_MOD YP_FUSR8 /*!< Reload global modules. */ #define CONF_IO_FRLD_ZONE YP_FUSR9 /*!< Reload a specific zone. */ #define CONF_IO_FRLD_ZONES YP_FUSR10 /*!< Reload all zones. */ +#define CONF_REF_EMPTY YP_FUSR11 /*!< Allow empty reference value for zone item. */ #define CONF_IO_FRLD_ALL (CONF_IO_FRLD_SRV | CONF_IO_FRLD_LOG | \ CONF_IO_FRLD_MOD | CONF_IO_FRLD_ZONES) diff --git a/src/knot/conf/schema.c b/src/knot/conf/schema.c index b606e4040..f31b5b3ef 100644 --- a/src/knot/conf/schema.c +++ b/src/knot/conf/schema.c @@ -454,9 +454,11 @@ static const yp_item_t desc_policy[] = { #define ZONE_ITEMS(FLAGS) \ { C_STORAGE, YP_TSTR, YP_VSTR = { STORAGE_DIR }, FLAGS }, \ { C_FILE, YP_TSTR, YP_VNONE, FLAGS }, \ - { C_MASTER, YP_TREF, YP_VREF = { C_RMT, C_RMTS }, YP_FMULTI, { check_ref } }, \ + { C_MASTER, YP_TREF, YP_VREF = { C_RMT, C_RMTS }, YP_FMULTI | CONF_REF_EMPTY, \ + { check_ref } }, \ { C_DDNS_MASTER, YP_TREF, YP_VREF = { C_RMT }, YP_FNONE, { check_ref_empty } }, \ - { C_NOTIFY, YP_TREF, YP_VREF = { C_RMT, C_RMTS }, YP_FMULTI, { check_ref } }, \ + { C_NOTIFY, YP_TREF, YP_VREF = { C_RMT, C_RMTS }, YP_FMULTI | CONF_REF_EMPTY, \ + { check_ref } }, \ { C_ACL, YP_TREF, YP_VREF = { C_ACL }, YP_FMULTI, { check_ref } }, \ { C_MASTER_PIN_TOL, YP_TINT, YP_VINT = { 0, UINT32_MAX, 0, YP_STIME } }, \ { C_PROVIDE_IXFR, YP_TBOOL, YP_VBOOL = { true } }, \ @@ -474,7 +476,7 @@ static const yp_item_t desc_policy[] = { { C_DNSSEC_SIGNING, YP_TBOOL, YP_VNONE, FLAGS }, \ { C_DNSSEC_VALIDATION, YP_TBOOL, YP_VNONE, FLAGS }, \ { C_DNSSEC_POLICY, YP_TREF, YP_VREF = { C_POLICY }, FLAGS, { check_ref_dflt } }, \ - { C_DS_PUSH, YP_TREF, YP_VREF = { C_RMT, C_RMTS }, YP_FMULTI | FLAGS, \ + { C_DS_PUSH, YP_TREF, YP_VREF = { C_RMT, C_RMTS }, YP_FMULTI | CONF_REF_EMPTY | FLAGS, \ { check_ref } }, \ { C_REVERSE_GEN, YP_TDNAME,YP_VNONE, FLAGS | CONF_IO_FRLD_ZONES }, \ { C_SERIAL_POLICY, YP_TOPT, YP_VOPT = { serial_policies, SERIAL_POLICY_INCREMENT } }, \ diff --git a/src/knot/conf/tools.c b/src/knot/conf/tools.c index c822621cd..1ea8446a3 100644 --- a/src/knot/conf/tools.c +++ b/src/knot/conf/tools.c @@ -236,6 +236,13 @@ int check_ref( bool found1 = false, found2 = false; + // Check if allowed empty value for specific zone items. + if (args->data_len == 1 && (args->item->flags & CONF_REF_EMPTY) && + args->item->parent->name[0] == C_ZONE[0] && + memcmp(&args->item->parent->name[1], &C_ZONE[1], C_ZONE[0]) == 0) { + return KNOT_EOK; + } + // Try to find the id in the first section. found1 = conf_rawid_exists_txn(args->extra->conf, args->extra->txn, ref->name, args->data, args->data_len); diff --git a/tests-extra/tests/config/template/test.py b/tests-extra/tests/config/template/test.py new file mode 100644 index 000000000..d99801d02 --- /dev/null +++ b/tests-extra/tests/config/template/test.py @@ -0,0 +1,62 @@ +#!/usr/bin/env python3 + +'''Test for configuration zone templates''' + +import os + +from dnstest.libknot import libknot +from dnstest.test import Test + +t = Test() + +master = t.server("knot") +slave = t.server("knot") +zones = t.zone_rnd(2, records=5, dnssec=False) +for z in zones: + z.name = z.name.lower() + +t.link(zones, master, slave, ixfr=True) + +ctl = libknot.control.KnotCtl() + +t.start() + +serials_init = master.zones_wait(zones) +slave.zones_wait(zones) + +ctl.connect(os.path.join(master.dir, "knot.sock")) +ctl.send_block(cmd="conf-begin") +resp = ctl.receive_block() + +# Move notify setting from zones to the default template. +ctl.send_block(cmd="conf-get", section="zone", item="notify", identifier=zones[0].name) +resp = ctl.receive_block() +for val in resp['zone'][zones[0].name]['notify']: + ctl.send_block(cmd="conf-set", section="template", identifier="default", item="notify", data=val) + resp = ctl.receive_block() +ctl.send_block(cmd="conf-unset", section="zone", item="notify") +resp = ctl.receive_block() + +# Override template setting with the default (none) for the first zone. +ctl.send_block(cmd="conf-set", section="zone", item="notify", identifier=zones[0].name, data="") +resp = ctl.receive_block() + +ctl.send_block(cmd="conf-commit") +resp = ctl.receive_block() +ctl.send(libknot.control.KnotCtlType.END) +ctl.close() + +# Modify the zones and check that notify doesn't work for the first zone. +serials_prev = serials_init +serial1 = serials_init[zones[0].name] +for i in range(2): + for zone in zones: + master.update_zonefile(zone, random=True) + master.ctl('zone-reload') + + serials_prev = master.zones_wait(zones, serials_prev) + slave.zone_wait(zones[1], serials_prev[zones[1].name], equal=True, greater=False) + t.sleep(1) + slave.zone_wait(zones[0], serial1, equal=True, greater=False) + +t.end()