2
0
mirror of https://gitlab.isc.org/isc-projects/bind9 synced 2025-08-29 13:38:26 +00:00

add "zonemd" option to dnssec-policy

"zonemd <scheme> <digest>;" now causes a ZONEMD record to be
added when inline signing.

"zonemd yes;" is a synonym for "zonemd simple sha384;".
This commit is contained in:
Evan Hunt 2025-06-11 22:33:28 -07:00
parent ca10790271
commit c6ce01b038
13 changed files with 407 additions and 12 deletions

View File

@ -40,6 +40,17 @@ dnssec-policy "test" {
signatures-validity P2W; signatures-validity P2W;
signatures-validity-dnskey P14D; signatures-validity-dnskey P14D;
zone-propagation-delay PT5M; zone-propagation-delay PT5M;
zonemd yes;
};
dnssec-policy "nozm" {
zonemd no;
};
dnssec-policy "zm512" {
zonemd simple sha512;
};
dnssec-policy "zm384andzm512" {
zonemd simple sha384;
zonemd simple sha512;
}; };
key-store "hsm" { key-store "hsm" {
directory "."; directory ".";

View File

@ -237,6 +237,16 @@ zone "max-zone-ttl.kasp" {
dnssec-policy "ttl"; dnssec-policy "ttl";
}; };
/*
* Zone that uses ZONEMD.
*/
zone "zonemd.kasp" {
type primary;
file "zonemd.kasp.db";
dnssec-policy "zonemd";
};
/* /*
* Zones in different signing states. * Zones in different signing states.
*/ */

View File

@ -119,3 +119,13 @@ dnssec-policy "keystore" {
zsk key-store "zsk" lifetime unlimited algorithm @DEFAULT_ALGORITHM@; zsk key-store "zsk" lifetime unlimited algorithm @DEFAULT_ALGORITHM@;
}; };
}; };
dnssec-policy "zonemd" {
dnskey-ttl 305;
zonemd yes;
keys {
ksk key-store "ksk" lifetime unlimited algorithm @DEFAULT_ALGORITHM@;
zsk key-store "zsk" lifetime unlimited algorithm @DEFAULT_ALGORITHM@;
};
};

View File

@ -50,7 +50,7 @@ for zn in default dnssec-keygen some-keys legacy-keys pregenerated \
rumoured rsasha256 rsasha512 ecdsa256 ecdsa384 \ rumoured rsasha256 rsasha512 ecdsa256 ecdsa384 \
dynamic dynamic-inline-signing inline-signing \ dynamic dynamic-inline-signing inline-signing \
checkds-ksk checkds-doubleksk checkds-csk inherit unlimited \ checkds-ksk checkds-doubleksk checkds-csk inherit unlimited \
keystore; do keystore zonemd; do
setup "${zn}.kasp" setup "${zn}.kasp"
cp template.db.in "$zonefile" cp template.db.in "$zonefile"
done done

View File

@ -1662,3 +1662,10 @@ def test_kasp_reload_restart(ns6):
newttl = 400 newttl = 400
isctest.run.retry_with_timeout(check_soa_ttl, timeout=10) isctest.run.retry_with_timeout(check_soa_ttl, timeout=10)
def test_kasp_zonemd(ns3):
msg = isctest.query.create("zonemd.kasp", "zonemd")
res = isctest.query.tcp(msg, ns3.ip)
isctest.check.noerror(res)
isctest.check.rr_count_eq(res.answer, 2)

View File

@ -6585,6 +6585,16 @@ max-zone-ttl
zone is updated to the time when the new version is served by all of zone is updated to the time when the new version is served by all of
the parent zone's name servers. The default is ``PT1H`` (1 hour). the parent zone's name servers. The default is ``PT1H`` (1 hour).
.. namedconf:statement:: zonemd
:tags: dnssec
:short: Specifies whether a ZONEMD record should be published in an inline-signing zone.
This option controls whether to publish a ZONEMD record in the
signed version of an :any:`inline-signing` zone. If set to ``no``,
no ZONEMD is published. If set to ``simple sha384`` or ``simple sha512``,
a ZONEMD will be published using the SIMPLE scheme and either the
SHA384 and SHA512 digest type. ``yes`` is a synonym for ``simple sha384``.
Automated KSK Rollovers Automated KSK Rollovers
^^^^^^^^^^^^^^^^^^^^^^^ ^^^^^^^^^^^^^^^^^^^^^^^

View File

@ -29,6 +29,7 @@ dnssec-policy <string> {
signatures-validity <duration>; signatures-validity <duration>;
signatures-validity-dnskey <duration>; signatures-validity-dnskey <duration>;
zone-propagation-delay <duration>; zone-propagation-delay <duration>;
zonemd ( simple | <boolean> ) [ ( sha384 | sha512 ) ]; // may occur multiple times
}; // may occur multiple times }; // may occur multiple times
dyndb <string> <quoted_string> { <unspecified-text> }; // may occur multiple times dyndb <string> <quoted_string> { <unspecified-text> }; // may occur multiple times

View File

@ -31,7 +31,9 @@
#include <dns/dnssec.h> #include <dns/dnssec.h>
#include <dns/keystore.h> #include <dns/keystore.h>
#include <dns/rdatastruct.h>
#include <dns/types.h> #include <dns/types.h>
#include <dns/zonemd.h>
/* For storing a list of digest types */ /* For storing a list of digest types */
struct dns_kasp_digest { struct dns_kasp_digest {
@ -109,6 +111,10 @@ struct dns_kasp {
uint32_t zone_propagation_delay; uint32_t zone_propagation_delay;
bool inline_signing; bool inline_signing;
/* ZONEMD settings */
uint8_t zonemd_scheme[DNS_ZONEMD_MAX];
uint8_t zonemd_digest[DNS_ZONEMD_MAX];
/* Parent settings */ /* Parent settings */
dns_ttl_t parent_ds_ttl; dns_ttl_t parent_ds_ttl;
uint32_t parent_propagation_delay; uint32_t parent_propagation_delay;
@ -875,3 +881,30 @@ dns_kasp_adddigest(dns_kasp_t *kasp, dns_dsdigest_t alg);
* *
*\li 'kasp' is a valid, thawed kasp. *\li 'kasp' is a valid, thawed kasp.
*/ */
void
dns_kasp_setzonemd(dns_kasp_t *kasp, uint8_t scheme, uint8_t digest);
/*%<
* Set the scheme and digest type for a ZONEMD record. If 'scheme' and
* 'digest' are nonzero, then if no ZONEMD already exists in the zone,
* one will be added after signing.
*
* Requires:
*
*\li 'kasp' is a valid, thawed kasp.
*
*\li 'scheme' and 'digest' are both 0, or else 'scheme' and 'digest'
* are valid ZONEMD scheme/digest values.
*/
uint8_t *
dns_kasp_zonemd_scheme(dns_kasp_t *kasp);
uint8_t *
dns_kasp_zonemd_digest(dns_kasp_t *kasp);
/*%<
* Get the ZONEMD scheme/digest settings for the zone.
*
* Requires:
*
*\li 'kasp' is a valid, frozen kasp.
*/

View File

@ -665,3 +665,64 @@ dns_kasp_adddigest(dns_kasp_t *kasp, dns_dsdigest_t alg) {
ISC_LINK_INIT(digest, link); ISC_LINK_INIT(digest, link);
ISC_LIST_APPEND(kasp->digests, digest, link); ISC_LIST_APPEND(kasp->digests, digest, link);
} }
void
dns_kasp_setzonemd(dns_kasp_t *kasp, uint8_t scheme, uint8_t digest) {
REQUIRE(kasp != NULL);
REQUIRE(!kasp->frozen);
REQUIRE((scheme == 0 && digest == 0) ||
(scheme == DNS_ZONEMD_SCHEME_MAX &&
digest == DNS_ZONEMD_DIGEST_MAX) ||
(scheme != 0 && digest != 0 && scheme < DNS_ZONEMD_SCHEME_MAX &&
digest < DNS_ZONEMD_DIGEST_MAX));
/*
* Delete zonemds.
*/
if (scheme == DNS_ZONEMD_SCHEME_MAX) {
kasp->zonemd_scheme[0] = scheme;
kasp->zonemd_digest[0] = digest;
return;
}
/*
* Maintain zonemds.
*/
if (scheme == 0) {
kasp->zonemd_scheme[0] = scheme;
kasp->zonemd_digest[0] = digest;
return;
}
/*
* Set to this set.
*/
for (size_t i = 0; i < ARRAY_SIZE(kasp->zonemd_scheme) - 1; i++) {
if (kasp->zonemd_scheme[i] == scheme &&
kasp->zonemd_digest[i] == digest)
{
return;
}
if (kasp->zonemd_scheme[i] == 0) {
kasp->zonemd_scheme[i] = scheme;
kasp->zonemd_digest[i] = digest;
return;
}
}
}
uint8_t *
dns_kasp_zonemd_scheme(dns_kasp_t *kasp) {
REQUIRE(kasp != NULL);
REQUIRE(kasp->frozen);
return kasp->zonemd_scheme;
}
uint8_t *
dns_kasp_zonemd_digest(dns_kasp_t *kasp) {
REQUIRE(kasp != NULL);
REQUIRE(kasp->frozen);
return kasp->zonemd_digest;
}

View File

@ -14,11 +14,25 @@
#pragma once #pragma once
/* Known scheme type(s). */ /* Known scheme type(s). */
#define DNS_ZONEMD_SCHEME_SIMPLE (1) enum {
DNS_ZONEMD_SCHEME_SIMPLE = 1,
DNS_ZONEMD_SCHEME_MAX = 2,
};
/* Known digest type(s). */ /* Known digest type(s). */
#define DNS_ZONEMD_DIGEST_SHA384 (1) enum {
#define DNS_ZONEMD_DIGEST_SHA512 (2) DNS_ZONEMD_DIGEST_SHA384 = 1,
DNS_ZONEMD_DIGEST_SHA512 = 2,
DNS_ZONEMD_DIGEST_MAX = 3,
};
/*
* Array size that can hold all possible combinations of schemes and digests
* with a sentinal (0, 0) entry.
*/
#define DNS_ZONEMD_MAX \
(((int)DNS_ZONEMD_SCHEME_MAX - 1) * ((int)DNS_ZONEMD_DIGEST_MAX - 1) + \
1)
/* /*
* \brief per RFC 8976 * \brief per RFC 8976

View File

@ -10242,6 +10242,132 @@ failure:
return false; return false;
} }
static isc_result_t
setup_zonemd(dns_zone_t *zone, dns_db_t *db, dns_dbversion_t *version,
dns_diff_t *diff) {
isc_result_t result;
dns_dbnode_t *node = NULL;
dns_rdata_zonemd_t zonemd;
dns_rdataset_t zmset = DNS_RDATASET_INIT;
unsigned char digest[64] = { 0 };
unsigned char zmbuf[6 + sizeof(digest)];
uint8_t *scheme, *digest_type;
dns_ttl_t ttl = 0;
isc_buffer_t b;
scheme = dns_kasp_zonemd_scheme(zone->kasp);
digest_type = dns_kasp_zonemd_digest(zone->kasp);
/*
* No zonemd processing specified.
*/
if (scheme[0] == 0) {
return ISC_R_SUCCESS;
}
CHECK(dns_db_getoriginnode(db, &node));
/* Is there already a ZONEMD? */
result = dns_db_findrdataset(db, node, version, dns_rdatatype_zonemd, 0,
0, &zmset, NULL);
if (result == ISC_R_SUCCESS) {
ttl = zmset.ttl;
}
/*
* Delete existing ZONEMD records that don't meet policy.
*/
if (result == ISC_R_SUCCESS && scheme[0] != 0) {
DNS_RDATASET_FOREACH(&zmset) {
dns_rdata_t zr = DNS_RDATA_INIT;
bool found = false;
/*
* DNS_ZONEMD_SCHEME_MAX => delete everything.
*/
if (scheme[0] != DNS_ZONEMD_SCHEME_MAX) {
dns_rdataset_current(&zmset, &zr);
(void)dns_rdata_tostruct(&zr, &zonemd, NULL);
for (size_t i = 0; scheme[i] != 0; i++) {
if (zonemd.scheme == scheme[i] &&
zonemd.digest_type == digest[i])
{
found = true;
}
}
}
/*
* Is there a scheme/digest_type mismatch, or ZONEMD is
* being removed?
*/
if (!found) {
CHECK(update_one_rr(db, version, diff,
DNS_DIFFOP_DEL,
&zone->origin, ttl, &zr));
}
}
} else if (result == ISC_R_NOTFOUND) {
result = ISC_R_SUCCESS;
}
if (result != ISC_R_SUCCESS || scheme[0] == DNS_ZONEMD_SCHEME_MAX) {
goto failure;
}
if (!dns_rdataset_isassociated(&zmset)) {
/* Get the SOA TTL */
dns_rdataset_t soaset = DNS_RDATASET_INIT;
CHECK(dns_db_findrdataset(db, node, version, dns_rdatatype_soa,
0, 0, &soaset, NULL));
ttl = soaset.ttl;
dns_rdataset_disassociate(&soaset);
}
/* ZONEMD was either not present or has been deleted: add a dummy */
zonemd.common.rdclass = zone->rdclass;
zonemd.common.rdtype = dns_rdatatype_zonemd;
zonemd.mctx = NULL;
zonemd.serial = 0;
zonemd.digest = digest;
for (size_t i = 0; scheme[i] != 0; i++) {
dns_rdata_t rdata = DNS_RDATA_INIT;
zonemd.scheme = scheme[i];
zonemd.digest_type = digest_type[i];
switch (zonemd.digest_type) {
case DNS_ZONEMD_DIGEST_SHA384:
zonemd.length = ISC_SHA384_DIGESTLENGTH;
break;
case DNS_ZONEMD_DIGEST_SHA512:
zonemd.length = ISC_SHA512_DIGESTLENGTH;
break;
default:
UNREACHABLE();
}
isc_buffer_init(&b, zmbuf, sizeof(zmbuf));
CHECK(dns_rdata_fromstruct(&rdata, zone->rdclass,
dns_rdatatype_zonemd, &zonemd, &b));
CHECK(update_one_rr(db, version, diff, DNS_DIFFOP_ADD,
&zone->origin, ttl, &rdata));
}
failure:
if (dns_rdataset_isassociated(&zmset)) {
dns_rdataset_disassociate(&zmset);
}
if (node != NULL) {
dns_db_detachnode(&node);
}
return result;
}
/* /*
* Incrementally sign the zone using the keys requested. * Incrementally sign the zone using the keys requested.
* Builds the NSEC chain if required. * Builds the NSEC chain if required.
@ -10324,6 +10450,23 @@ zone_sign(dns_zone_t *zone) {
goto cleanup; goto cleanup;
} }
if (zone->kasp != NULL) {
result = setup_zonemd(zone, db, version, &_sig_diff);
if (result != ISC_R_SUCCESS) {
dnssec_log(zone, ISC_LOG_ERROR,
"zone_sign:setup_zonemd -> %s",
isc_result_totext(result));
goto cleanup;
}
}
result = dns_update_zonemd(db, version, &_sig_diff);
if (result != ISC_R_SUCCESS) {
dnssec_log(zone, ISC_LOG_ERROR,
"zone_sign:dns_update_zonemd -> %s",
isc_result_totext(result));
goto cleanup;
}
kasp = zone->kasp; kasp = zone->kasp;
calculate_rrsig_validity(zone, now, &inception, &soaexpire, NULL, calculate_rrsig_validity(zone, now, &inception, &soaexpire, NULL,
@ -17321,7 +17464,8 @@ sync_secure_journal(dns_zone_t *zone, dns_zone_t *raw, dns_journal_t *journal,
if (rdata->type == dns_rdatatype_nsec || if (rdata->type == dns_rdatatype_nsec ||
rdata->type == dns_rdatatype_rrsig || rdata->type == dns_rdatatype_rrsig ||
rdata->type == dns_rdatatype_nsec3 || rdata->type == dns_rdatatype_nsec3 ||
rdata->type == dns_rdatatype_nsec3param) rdata->type == dns_rdatatype_nsec3param ||
rdata->type == dns_rdatatype_zonemd)
{ {
continue; continue;
} }
@ -17510,7 +17654,8 @@ sync_secure_db(dns_zone_t *seczone, dns_zone_t *raw, dns_db_t *secdb,
if (tuple->rdata.type == dns_rdatatype_nsec || if (tuple->rdata.type == dns_rdatatype_nsec ||
tuple->rdata.type == dns_rdatatype_rrsig || tuple->rdata.type == dns_rdatatype_rrsig ||
tuple->rdata.type == dns_rdatatype_nsec3 || tuple->rdata.type == dns_rdatatype_nsec3 ||
tuple->rdata.type == dns_rdatatype_nsec3param) tuple->rdata.type == dns_rdatatype_nsec3param ||
tuple->rdata.type == dns_rdatatype_zonemd)
{ {
ISC_LIST_UNLINK(diff->tuples, tuple, link); ISC_LIST_UNLINK(diff->tuples, tuple, link);
dns_difftuple_free(&tuple); dns_difftuple_free(&tuple);
@ -17732,6 +17877,14 @@ receive_secure_serial(void *arg) {
dns_db_currentversion(zone->rss_db, &zone->rss_oldver); dns_db_currentversion(zone->rss_db, &zone->rss_oldver);
CHECK(dns_db_newversion(zone->rss_db, &zone->rss_newver)); CHECK(dns_db_newversion(zone->rss_db, &zone->rss_newver));
/*
* Set up the ZONEMD as specified in the policy.
*/
CHECK(setup_zonemd(zone, zone->rss_db, zone->rss_newver,
&zone->rss_diff));
CHECK(dns_update_zonemd(zone->rss_db, zone->rss_newver,
&zone->rss_diff));
/* /*
* Try to apply diffs from the raw zone's journal to the secure * Try to apply diffs from the raw zone's journal to the secure
* zone. If that fails, we recover by syncing up the databases * zone. If that fails, we recover by syncing up the databases
@ -18174,7 +18327,8 @@ copy_non_dnssec_records(dns_db_t *db, dns_dbversion_t *version, dns_db_t *rawdb,
if (rdataset.type == dns_rdatatype_nsec || if (rdataset.type == dns_rdatatype_nsec ||
rdataset.type == dns_rdatatype_rrsig || rdataset.type == dns_rdatatype_rrsig ||
rdataset.type == dns_rdatatype_nsec3 || rdataset.type == dns_rdatatype_nsec3 ||
rdataset.type == dns_rdatatype_nsec3param) rdataset.type == dns_rdatatype_nsec3param ||
rdataset.type == dns_rdatatype_zonemd)
{ {
dns_rdataset_disassociate(&rdataset); dns_rdataset_disassociate(&rdataset);
continue; continue;
@ -18274,11 +18428,8 @@ receive_secure_db(void *arg) {
} }
DNS_DBITERATOR_FOREACH(dbiterator) { DNS_DBITERATOR_FOREACH(dbiterator) {
result = copy_non_dnssec_records(db, version, rawdb, dbiterator, CHECK(copy_non_dnssec_records(db, version, rawdb, dbiterator,
oldserialp); oldserialp));
if (result != ISC_R_SUCCESS) {
goto failure;
}
} }
dns_dbiterator_destroy(&dbiterator); dns_dbiterator_destroy(&dbiterator);

View File

@ -30,6 +30,7 @@
#include <dns/nsec3.h> #include <dns/nsec3.h>
#include <dns/secalg.h> #include <dns/secalg.h>
#include <dns/ttl.h> #include <dns/ttl.h>
#include <dns/zonemd.h>
#include <dst/dst.h> #include <dst/dst.h>
@ -464,6 +465,7 @@ cfg_kasp_fromconfig(const cfg_obj_t *config, dns_kasp_t *default_kasp,
const cfg_obj_t *inlinesigning = NULL; const cfg_obj_t *inlinesigning = NULL;
const cfg_obj_t *cds = NULL; const cfg_obj_t *cds = NULL;
const cfg_obj_t *obj = NULL; const cfg_obj_t *obj = NULL;
const cfg_obj_t *zonemd = NULL;
const char *kaspname = NULL; const char *kaspname = NULL;
dns_kasp_t *kasp = NULL; dns_kasp_t *kasp = NULL;
size_t i = 0; size_t i = 0;
@ -593,6 +595,58 @@ cfg_kasp_fromconfig(const cfg_obj_t *config, dns_kasp_t *default_kasp,
DNS_KASP_PARENT_PROPDELAY); DNS_KASP_PARENT_PROPDELAY);
dns_kasp_setparentpropagationdelay(kasp, parentpropdelay); dns_kasp_setparentpropagationdelay(kasp, parentpropdelay);
(void)confget(maps, "zonemd", &zonemd);
if (zonemd != NULL) {
CFG_LIST_FOREACH(zonemd, element) {
obj = cfg_listelt_value(element);
const cfg_obj_t *sobj = cfg_tuple_get(obj, "scheme");
if (cfg_obj_isboolean(sobj)) {
if (!cfg_obj_asboolean(sobj)) {
dns_kasp_setzonemd(
kasp, DNS_ZONEMD_SCHEME_MAX,
DNS_ZONEMD_DIGEST_MAX);
} else {
dns_kasp_setzonemd(
kasp, DNS_ZONEMD_SCHEME_SIMPLE,
DNS_ZONEMD_DIGEST_SHA384);
}
} else if (strcasecmp(cfg_obj_asstring(sobj),
"simple") != 0)
{
cfg_obj_log(
config, ISC_LOG_ERROR,
"dnssec-policy: policy '%s' unknown "
"ZONEMD scheme %s",
kaspname, cfg_obj_asstring(sobj));
result = ISC_R_FAILURE;
} else {
const cfg_obj_t *dobj = cfg_tuple_get(obj,
"digest");
if (strcasecmp(cfg_obj_asstring(dobj),
"sha384"))
{
dns_kasp_setzonemd(
kasp, DNS_ZONEMD_SCHEME_SIMPLE,
DNS_ZONEMD_DIGEST_SHA384);
} else if (strcasecmp(cfg_obj_asstring(dobj),
"sha512"))
{
dns_kasp_setzonemd(
kasp, DNS_ZONEMD_SCHEME_SIMPLE,
DNS_ZONEMD_DIGEST_SHA512);
} else {
cfg_obj_log(config, ISC_LOG_ERROR,
"dnssec-policy: policy "
"'%s' unknown "
"ZONEMD digest %s",
kaspname,
cfg_obj_asstring(dobj));
result = ISC_R_FAILURE;
}
}
}
}
/* Configuration: Keys */ /* Configuration: Keys */
obj = NULL; obj = NULL;
(void)confget(maps, "offline-ksk", &obj); (void)confget(maps, "offline-ksk", &obj);

View File

@ -2204,6 +2204,38 @@ static cfg_type_t cfg_type_checkdstype = {
doc_checkds_type, &cfg_rep_string, checkds_enums, doc_checkds_type, &cfg_rep_string, checkds_enums,
}; };
/*%
* Zonemd type.
*/
static const char *zonemd_schemes[] = { "simple", NULL };
static const char *zonemd_digests[] = { "sha384", "sha512", NULL };
static isc_result_t
parse_zscheme(cfg_parser_t *pctx, const cfg_type_t *type, cfg_obj_t **ret) {
return cfg_parse_enum_or_other(pctx, type, &cfg_type_boolean, ret);
}
static void
doc_zscheme(cfg_printer_t *pctx, const cfg_type_t *type) {
cfg_doc_enum_or_other(pctx, type, &cfg_type_boolean);
}
static cfg_type_t cfg_type_zscheme = { "zscheme", parse_zscheme,
cfg_print_ustring, doc_zscheme,
&cfg_rep_string, zonemd_schemes };
static cfg_type_t cfg_type_zdigest = { "zdigest", parse_optional_enum,
cfg_print_ustring, doc_optional_enum,
&cfg_rep_string, zonemd_digests };
static cfg_tuplefielddef_t zonemd_fields[] = {
{ "scheme", &cfg_type_zscheme, 0 },
{ "digest", &cfg_type_zdigest, 0 },
{ NULL, NULL, 0 }
};
static cfg_type_t cfg_type_zonemd = { "zonemd", cfg_parse_tuple,
cfg_print_tuple, cfg_doc_tuple,
&cfg_rep_tuple, zonemd_fields };
/*% /*%
* Clauses that can be found in a 'dnssec-policy' statement. * Clauses that can be found in a 'dnssec-policy' statement.
*/ */
@ -2227,6 +2259,7 @@ static cfg_clausedef_t dnssecpolicy_clauses[] = {
{ "signatures-validity", &cfg_type_duration, 0 }, { "signatures-validity", &cfg_type_duration, 0 },
{ "signatures-validity-dnskey", &cfg_type_duration, 0 }, { "signatures-validity-dnskey", &cfg_type_duration, 0 },
{ "zone-propagation-delay", &cfg_type_duration, 0 }, { "zone-propagation-delay", &cfg_type_duration, 0 },
{ "zonemd", &cfg_type_zonemd, CFG_CLAUSEFLAG_MULTI },
{ NULL, NULL, 0 } { NULL, NULL, 0 }
}; };