From 17fb0463eea4a2f3c45d79daa41bc392edd2276d Mon Sep 17 00:00:00 2001 From: Mark Andrews Date: Thu, 24 Jun 2021 12:20:14 +1000 Subject: [PATCH] Check ZONEMD records when loading or transferring a zone --- bin/named/config.c | 1 + bin/tests/system/autosign/ns1/named.conf.in | 1 + bin/tests/system/autosign/ns3/named.conf.in | 5 + bin/tests/system/autosign/tests.sh | 2 +- bin/tests/system/camp/ns1/named.conf.in | 1 + bin/tests/system/dnssec/ns2/named.conf.in | 1 + bin/tests/system/dnssec/ns3/named.conf.in | 29 + bin/tests/system/dnssec/ns7/named.conf.in | 3 + bin/tests/system/mirror/ns1/named.conf.in | 1 + bin/tests/system/mirror/ns2/named.conf.in | 2 + bin/tests/system/mirror/ns3/named.conf.in | 1 + .../system/nsupdate/ns3/zonemd.test.db.in | 8 +- bin/tests/system/nsupdate/ns5/named.conf.in | 12 + bin/tests/system/redirect/ns5/named.conf.in | 8 +- bin/tests/system/redirect/ns6/named.conf.in | 8 +- lib/dns/include/dns/zone.h | 41 +- lib/dns/xfrin.c | 16 +- lib/dns/zone.c | 530 +++++++++++++++++- 18 files changed, 639 insertions(+), 31 deletions(-) diff --git a/bin/named/config.c b/bin/named/config.c index 934c3711ad..e707015b67 100644 --- a/bin/named/config.c +++ b/bin/named/config.c @@ -263,6 +263,7 @@ view \"_bind\" chaos {\n\ notify no;\n\ allow-new-zones no;\n\ max-cache-size 2M;\n\ + zonemd-check no;\n\ \n\ # Prevent use of this zone in DNS amplified reflection DoS attacks\n\ rate-limit {\n\ diff --git a/bin/tests/system/autosign/ns1/named.conf.in b/bin/tests/system/autosign/ns1/named.conf.in index fb76b65dc9..167dce96a2 100644 --- a/bin/tests/system/autosign/ns1/named.conf.in +++ b/bin/tests/system/autosign/ns1/named.conf.in @@ -42,6 +42,7 @@ zone "." { allow-transfer { any; }; allow-update { any; }; allow-query { any; }; + zonemd-check no; }; include "trusted.conf"; diff --git a/bin/tests/system/autosign/ns3/named.conf.in b/bin/tests/system/autosign/ns3/named.conf.in index 3707a2c7c9..66be0a95d7 100644 --- a/bin/tests/system/autosign/ns3/named.conf.in +++ b/bin/tests/system/autosign/ns3/named.conf.in @@ -284,6 +284,7 @@ zone "oldsigs.example" { dnssec-policy jitter; sig-signing-nodes 1000; sig-signing-signatures 100; + zonemd-check no; }; zone "prepub.example" { @@ -329,6 +330,7 @@ zone "ttl4.example" { zone "delay.example" { type primary; file "delay.example.db"; + zonemd-check no; }; zone "nozsk.example" { @@ -337,6 +339,7 @@ zone "nozsk.example" { allow-update { any; }; inline-signing no; dnssec-policy autosign; + zonemd-check no; }; zone "inaczsk.example" { @@ -345,6 +348,7 @@ zone "inaczsk.example" { allow-update { any; }; inline-signing no; dnssec-policy autosign; + zonemd-check no; }; zone "noksk.example" { @@ -353,6 +357,7 @@ zone "noksk.example" { allow-update { any; }; inline-signing no; dnssec-policy autosign; + zonemd-check no; }; zone "sync.example" { diff --git a/bin/tests/system/autosign/tests.sh b/bin/tests/system/autosign/tests.sh index 14064affe2..f9413eb4eb 100755 --- a/bin/tests/system/autosign/tests.sh +++ b/bin/tests/system/autosign/tests.sh @@ -260,7 +260,7 @@ now="$(TZ=UTC date +%Y%m%d%H%M%S)" check_expiry() ( $DIG $DIGOPTS AXFR oldsigs.example @10.53.0.3 >dig.out.test$n || return 1 nearest_expiration="$(awk '$4 == "RRSIG" { print $9 }' db, &xfr->axfr)); - CHECK(dns_zone_verifydb(xfr->zone, xfr->db, NULL)); + result = dns_zone_checkzonemd(xfr->zone, xfr->db, xfr->ver); + if (result == DNS_R_BADZONE) { + goto failure; + } + if (dns_zone_gettype(xfr->zone) == dns_zone_mirror) { + CHECK(dns_zone_verifydb(xfr->zone, xfr->db, NULL)); + } CHECK(axfr_finalize(xfr)); } else { (void)dns_db_endload(xfr->db, &xfr->axfr); @@ -511,7 +517,13 @@ static isc_result_t ixfr_end_transaction(dns_xfrin_t *xfr) { isc_result_t result = ISC_R_SUCCESS; - CHECK(dns_zone_verifydb(xfr->zone, xfr->db, xfr->ver)); + result = dns_zone_checkzonemd(xfr->zone, xfr->db, xfr->ver); + if (result == DNS_R_BADZONE) { + goto failure; + } + if (dns_zone_gettype(xfr->zone) == dns_zone_mirror) { + CHECK(dns_zone_verifydb(xfr->zone, xfr->db, xfr->ver)); + } /* XXX enter ready-to-commit state here */ if (xfr->ixfr.journal != NULL) { CHECK(dns_journal_commit(xfr->ixfr.journal)); diff --git a/lib/dns/zone.c b/lib/dns/zone.c index fe8eedd461..4adea3b199 100644 --- a/lib/dns/zone.c +++ b/lib/dns/zone.c @@ -89,6 +89,7 @@ #include #include #include +#include #include #include @@ -4903,6 +4904,520 @@ failure: return result; } +static bool +signed_by_dnskey(dns_zone_t *zone, dns_name_t *name, dns_rdataset_t *dnskeyset, + dns_rdataset_t *rdataset, dns_rdataset_t *sigrdataset) { + isc_result_t result = ISC_R_NOTFOUND; + bool answer; + bool ignoretime = DNS_ZONEMD_OPTION(zone, DNS_ZONEMDOPT_ACCEPTEXPIRED); + dst_key_t *key = NULL; + + /* + * If rdataset is not associated there is nothing to verify. + */ + if (!dns_rdataset_isassociated(rdataset)) { + return false; + } + + if (dns_rdataset_isassociated(dnskeyset) && + dns_rdataset_isassociated(sigrdataset)) + { + result = dns_rdataset_first(dnskeyset); + while (result == ISC_R_SUCCESS) { + dns_rdata_t rdata = DNS_RDATA_INIT; + + dns_rdataset_current(dnskeyset, &rdata); + if (dns_dnssec_signs(&rdata, name, rdataset, + sigrdataset, ignoretime, + zone->mctx)) + { + break; + } + result = dns_rdataset_next(dnskeyset); + } + } + + if (key != NULL) { + dst_key_free(&key); + } + answer = (result == ISC_R_SUCCESS); + dns_zone_log(zone, ISC_LOG_DEBUG(3), "signed_by_dnskey(%u) -> %s", + rdataset->type, answer ? "true" : "false"); + return answer; +} + +static bool +self_signed(dns_zone_t *zone, dns_name_t *name, dns_rdataset_t *dsset, + dns_rdataset_t *dnskeyset, dns_rdataset_t *sigrdataset) { + isc_result_t result = ISC_R_NOTFOUND; + dns_rdataset_t *rdataset = dnskeyset; + bool answer; + bool ignoretime = DNS_ZONEMD_OPTION(zone, DNS_ZONEMDOPT_ACCEPTEXPIRED); + dst_key_t *key = NULL; + + if (dns_rdataset_isassociated(dnskeyset) && + dns_rdataset_isassociated(sigrdataset)) + { + result = dns_rdataset_first(dnskeyset); + while (result == ISC_R_SUCCESS) { + dns_rdata_t rdata = DNS_RDATA_INIT; + + dns_rdataset_current(dnskeyset, &rdata); + if (dns_dnssec_signs(&rdata, name, rdataset, + sigrdataset, ignoretime, + zone->mctx)) + { + if (dsset == NULL) { + /* + * There isn't a trust achor to + * check against so we are done. + */ + break; + } + CHECK(dns_dnssec_keyfromrdata( + name, &rdata, zone->mctx, &key)); + + for (result = dns_rdataset_first(dsset); + result == ISC_R_SUCCESS; + result = dns_rdataset_next(dsset)) + { + dns_rdata_t dsrdata = DNS_RDATA_INIT; + dns_rdata_t newdsrdata = DNS_RDATA_INIT; + unsigned char buf[DNS_DS_BUFFERSIZE]; + dns_rdata_ds_t ds; + + dns_rdata_reset(&dsrdata); + dns_rdataset_current(dsset, &dsrdata); + CHECK(dns_rdata_tostruct(&dsrdata, &ds, + NULL)); + + if (ds.key_tag != dst_key_id(key) || + ds.algorithm != dst_key_alg(key)) + { + continue; + } + + result = dns_ds_buildrdata( + name, &rdata, ds.digest_type, + buf, &newdsrdata); + if (result != ISC_R_SUCCESS) { + continue; + } + + if (dns_rdata_compare(&dsrdata, + &newdsrdata) == 0) + { + break; + } + } + if (result == ISC_R_SUCCESS) { + break; + } + dst_key_free(&key); + } + result = dns_rdataset_next(dnskeyset); + } + } +failure: + if (key != NULL) { + dst_key_free(&key); + } + answer = (result == ISC_R_SUCCESS); + dns_zone_log(zone, ISC_LOG_DEBUG(3), "self_signed(%u) -> %s", + rdataset->type, answer ? "true" : "false"); + return answer; +} + +static bool +haszonemdbit(dns_rdataset_t *set) { + isc_result_t result; + dns_rdata_t rdata = DNS_RDATA_INIT; + + result = dns_rdataset_first(set); + if (result != ISC_R_SUCCESS) { + /* fail true */ + return true; + } + dns_rdataset_current(set, &rdata); + if (set->type == dns_rdatatype_nsec) { + return dns_nsec_typepresent(&rdata, dns_rdatatype_zonemd); + } else { + return dns_nsec3_typepresent(&rdata, dns_rdatatype_zonemd); + } +} + +static isc_result_t +find_origin_nsec3(dns_zone_t *zone, dns_db_t *db, dns_dbnode_t *node, + dns_dbversion_t *version, bool *nsec3paramflagsok, + dns_rdataset_t *nsec3paramset, + dns_rdataset_t *signsec3paramset, dns_fixedname_t *fnsec3name, + dns_dbnode_t **nsec3node, dns_rdataset_t *nsec3set, + dns_rdataset_t *signsec3set) { + unsigned char hash[NSEC3_MAX_HASH_LENGTH]; + size_t hashlen = sizeof(hash); + dns_rdata_nsec3param_t nsec3param; + isc_result_t result; + + result = dns_db_findrdataset( + db, node, version, dns_rdatatype_nsec3param, dns_rdatatype_none, + 0, nsec3paramset, signsec3paramset); + dns_zone_log(zone, ISC_LOG_DEBUG(3), "dns_db_findrdataset(%u) -> %s", + dns_rdatatype_nsec3param, isc_result_totext(result)); + if (result != ISC_R_SUCCESS) { + goto failure; + } + result = dns_rdataset_first(nsec3paramset); + while (result == ISC_R_SUCCESS) { + dns_rdata_t rdata = DNS_RDATA_INIT; + dns_rdataset_current(nsec3paramset, &rdata); + CHECK(dns_rdata_tostruct(&rdata, &nsec3param, NULL)); + if (nsec3param.flags == 0) { + *nsec3paramflagsok = true; + break; + } + result = dns_rdataset_next(nsec3paramset); + } + CHECK((result == ISC_R_NOMORE) ? ISC_R_NOTFOUND : result); + CHECK(dns_nsec3_hashname(fnsec3name, hash, &hashlen, &zone->origin, + &zone->origin, nsec3param.hash, + nsec3param.iterations, nsec3param.salt, + nsec3param.salt_length)); + CHECK(dns_db_findnsec3node(db, dns_fixedname_name(fnsec3name), false, + nsec3node)); + /* lookup NSEC3 record */ + result = dns_db_findrdataset(db, *nsec3node, version, + dns_rdatatype_nsec3, dns_rdatatype_none, 0, + nsec3set, signsec3set); + dns_zone_log(zone, ISC_LOG_DEBUG(3), "dns_db_findrdataset(%u) -> %s", + dns_rdatatype_nsec3, isc_result_totext(result)); +failure: + return result; +} + +isc_result_t +dns_zone_checkzonemd(dns_zone_t *zone, dns_db_t *db, dns_dbversion_t *version) { + enum { unknown = 0, good = 1, bad = 2 } found[256] = { unknown }; + bool nsec3paramflagsok = false; + isc_result_t result; + dns_keynode_t *keynode = NULL; + dns_keytable_t *secroots = NULL; + dns_dbnode_t *node = NULL; + dns_dbnode_t *nsec3node = NULL; + dns_fixedname_t fnsec3name; + dns_name_t *origin; + dns_name_t *nsec3name = dns_fixedname_initname(&fnsec3name); + dns_rdata_soa_t soa; + dns_rdata_t soardata = DNS_RDATA_INIT; + dns_rdataset_t dsset = DNS_RDATASET_INIT, *dssetp = NULL; + dns_rdataset_t dnskeyset = DNS_RDATASET_INIT, + sigdnskeyset = DNS_RDATASET_INIT; + dns_rdataset_t nsec3paramset = DNS_RDATASET_INIT, + signsec3paramset = DNS_RDATASET_INIT; + dns_rdataset_t nsec3set = DNS_RDATASET_INIT, + signsec3set = DNS_RDATASET_INIT; + dns_rdataset_t nsecset = DNS_RDATASET_INIT, + signsecset = DNS_RDATASET_INIT; + dns_rdataset_t soaset = DNS_RDATASET_INIT, + sigsoaset = DNS_RDATASET_INIT; + dns_rdataset_t zonemdset = DNS_RDATASET_INIT, + sigzonemdset = DNS_RDATASET_INIT; + + REQUIRE(DNS_ZONE_VALID(zone)); + REQUIRE(db != NULL); + + if (!DNS_ZONEMD_OPTION(zone, DNS_ZONEMDOPT_CHECK)) { + return ISC_R_NOTFOUND; + } + + origin = &zone->origin; + + if (zone->view != NULL) { + result = dns_view_getsecroots(zone->view, &secroots); + if (result == ISC_R_SUCCESS) { + result = dns_keytable_find(secroots, origin, &keynode); + } + if (result == ISC_R_SUCCESS) { + if (dns_keynode_dsset(keynode, &dsset)) { + dssetp = &dsset; + } + } + } + + dns_zone_log(zone, ISC_LOG_DEBUG(3), "in zone_checkzonemd"); + + CHECK(dns_db_findnode(db, &zone->origin, false, &node)); + + result = dns_db_findrdataset(db, node, version, dns_rdatatype_soa, + dns_rdatatype_none, 0, &soaset, + &sigsoaset); + dns_zone_log(zone, ISC_LOG_DEBUG(3), "dns_db_findrdataset(%u) -> %s", + dns_rdatatype_soa, isc_result_totext(result)); + CHECK((result == ISC_R_NOTFOUND) ? DNS_R_BADZONE : result); + + result = dns_db_findrdataset(db, node, version, dns_rdatatype_dnskey, + dns_rdatatype_none, 0, &dnskeyset, + &sigdnskeyset); + dns_zone_log(zone, ISC_LOG_DEBUG(3), "dns_db_findrdataset(%u) -> %s", + dns_rdatatype_dnskey, isc_result_totext(result)); + if (result != ISC_R_SUCCESS && result != ISC_R_NOTFOUND) { + goto failure; + } + + result = dns_db_findrdataset(db, node, version, dns_rdatatype_zonemd, + dns_rdatatype_none, 0, &zonemdset, + &sigzonemdset); + dns_zone_log(zone, ISC_LOG_DEBUG(3), "dns_db_findrdataset(%u) -> %s", + dns_rdatatype_zonemd, isc_result_totext(result)); + if (result != ISC_R_SUCCESS && result != ISC_R_NOTFOUND) { + goto failure; + } + + /* + * Look for proofs that the zone is fully signed (NSEC or NSEC3PARAM) + * at apex and that ZONEMD does not exist (apex NSEC/NSEC3), + */ + result = dns_db_findrdataset(db, node, version, dns_rdatatype_nsec, + dns_rdatatype_none, 0, &nsecset, + &signsecset); + dns_zone_log(zone, ISC_LOG_DEBUG(3), "dns_db_findrdataset(%u) -> %s", + dns_rdatatype_nsec, isc_result_totext(result)); + if (result != ISC_R_SUCCESS && result != ISC_R_NOTFOUND) { + goto failure; + } + if (result == ISC_R_NOTFOUND) { + result = find_origin_nsec3(zone, db, node, version, + &nsec3paramflagsok, &nsec3paramset, + &signsec3paramset, &fnsec3name, + &nsec3node, &nsec3set, &signsec3set); + if (result != ISC_R_SUCCESS && result != ISC_R_NOTFOUND) { + goto failure; + } + } + + /* + * The zone is signed, check that zonemdset is + * correctly signed, or that it doesn't exist and + * that its nonexistence is not an error. + * + * For a zone to be deemed signed, DNSKEY and one of + * NSEC or NSEC3PARAM (with 0 flags) need to exist. + */ + if (dns_rdataset_isassociated(&dnskeyset) && + (dns_rdataset_isassociated(&nsecset) || + (dns_rdataset_isassociated(&nsec3paramset) && nsec3paramflagsok))) + { + if (!self_signed(zone, origin, dssetp, &dnskeyset, + &sigdnskeyset)) + { + dns_zone_log(zone, ISC_LOG_INFO, + "ZONEMD: self validation failure DNSKEY"); + CHECK(DNS_R_BADZONE); + } + + if (!signed_by_dnskey(zone, origin, &dnskeyset, &soaset, + &sigsoaset)) + { + dns_zone_log(zone, ISC_LOG_INFO, + "ZONEMD: self validation failure SOA"); + CHECK(DNS_R_BADZONE); + } + + if (!dns_rdataset_isassociated(&zonemdset)) { + if (signed_by_dnskey(zone, origin, &dnskeyset, &nsecset, + &signsecset) && + !haszonemdbit(&nsecset)) + { + if (DNS_ZONEMD_OPTION(zone, + DNS_ZONEMDOPT_REQUIRED)) + { + dns_zone_log(zone, ISC_LOG_INFO, + "ZONEMD required but not " + "present"); + CHECK(DNS_R_BADZONE); + } + CHECK(ISC_R_NOTFOUND); + } + + if (signed_by_dnskey(zone, nsec3name, &dnskeyset, + &nsec3set, &signsec3set) && + signed_by_dnskey(zone, origin, &dnskeyset, + &nsec3paramset, + &signsec3paramset) && + !haszonemdbit(&nsec3set)) + { + if (DNS_ZONEMD_OPTION(zone, + DNS_ZONEMDOPT_REQUIRED)) + { + dns_zone_log(zone, ISC_LOG_INFO, + "ZONEMD required but not " + "present"); + CHECK(DNS_R_BADZONE); + } + CHECK(ISC_R_NOTFOUND); + } + + CHECK(DNS_R_BADZONE); + } else if (!signed_by_dnskey(zone, origin, &dnskeyset, + &zonemdset, &sigzonemdset)) + { + dns_zone_log(zone, ISC_LOG_INFO, + "ZONEMD validation failed"); + CHECK(DNS_R_BADZONE); + } + } else { + if (dssetp != NULL) { + dns_zone_log(zone, ISC_LOG_INFO, + "zone at a trust anchor should be signed " + "but isn't"); + CHECK(DNS_R_BADZONE); + } + + if (!dns_rdataset_isassociated(&zonemdset)) { + if (DNS_ZONEMD_OPTION(zone, DNS_ZONEMDOPT_REQUIRED) && + DNS_ZONEMD_OPTION(zone, DNS_ZONEMDOPT_DNSSECONLY)) + { + dns_zone_log(zone, ISC_LOG_INFO, + "ZONEMD required but not present"); + CHECK(DNS_R_BADZONE); + } + + CHECK(ISC_R_NOTFOUND); + } + } + + /* + * Check the ZONEMD records. + */ + CHECK(dns_rdataset_first(&soaset)); + dns_rdataset_current(&soaset, &soardata); + CHECK(dns_rdata_tostruct(&soardata, &soa, NULL)); + result = dns_rdataset_first(&zonemdset); + while (result == ISC_R_SUCCESS) { + dns_rdata_t rdata = DNS_RDATA_INIT; + + dns_rdataset_current(&zonemdset, &rdata); + if (dns_zonemd_supported(&rdata)) { + dns_rdata_zonemd_t zonemd; + dns_rdata_t check = DNS_RDATA_INIT; + unsigned char buf[DNS_ZONEMD_BUFFERSIZE]; + + CHECK(dns_rdata_tostruct(&rdata, &zonemd, NULL)); + if (soa.serial != zonemd.serial) { + dns_zone_log(zone, ISC_LOG_DEBUG(3), + "zonemd serial mismatch: soa=%u " + "zonemd=%u", + soa.serial, zonemd.serial); + found[zonemd.digest_type] = bad; + goto nextzonemd; + } + if (found[zonemd.digest_type] != unknown) { + if (found[zonemd.digest_type] == good) { + dns_zone_log(zone, ISC_LOG_DEBUG(3), + "zonemd duplicate: digest " + "type=%u", + zonemd.digest_type); + found[zonemd.digest_type] = bad; + } + } else { + CHECK(dns_zonemd_buildrdata( + &check, db, version, zonemd.scheme, + zonemd.digest_type, zone->mctx, buf, + sizeof(buf))); + if (dns_rdata_compare(&check, &rdata) != 0) { + char buf1[256] = { 0 }, + buf2[256] = { 0 }; + isc_buffer_t b; + dns_zone_log(zone, ISC_LOG_DEBUG(3), + "zonemd dns_rdata_compare " + "!= 0"); + isc_buffer_init(&b, buf1, sizeof(buf1)); + dns_rdata_totext(&check, NULL, &b); + isc_buffer_init(&b, buf2, sizeof(buf2)); + dns_rdata_totext(&rdata, NULL, &b); + found[zonemd.digest_type] = bad; + } else { + found[zonemd.digest_type] = good; + } + } + } + nextzonemd: + result = dns_rdataset_next(&zonemdset); + } + if (result == ISC_R_NOMORE) { + size_t i; + for (i = 0; i < ARRAY_SIZE(found); i++) { + if (found[i] == good) { + result = ISC_R_SUCCESS; + } + } + if (result != ISC_R_SUCCESS) { + if (DNS_ZONEMD_OPTION(zone, DNS_ZONEMDOPT_REQUIRED)) { + dns_zone_log(zone, ISC_LOG_INFO, + "valid ZONEMD required but not " + "present"); + result = DNS_R_BADZONE; + } else { + result = ISC_R_NOTFOUND; + } + } + } +failure: + if (dns_rdataset_isassociated(&dsset)) { + dns_rdataset_disassociate(&dsset); + } + if (dns_rdataset_isassociated(&soaset)) { + dns_rdataset_disassociate(&soaset); + } + if (dns_rdataset_isassociated(&sigsoaset)) { + dns_rdataset_disassociate(&sigsoaset); + } + if (dns_rdataset_isassociated(&nsecset)) { + dns_rdataset_disassociate(&nsecset); + } + if (dns_rdataset_isassociated(&signsecset)) { + dns_rdataset_disassociate(&signsecset); + } + if (dns_rdataset_isassociated(&nsec3set)) { + dns_rdataset_disassociate(&nsec3set); + } + if (dns_rdataset_isassociated(&signsec3set)) { + dns_rdataset_disassociate(&signsec3set); + } + if (dns_rdataset_isassociated(&dnskeyset)) { + dns_rdataset_disassociate(&dnskeyset); + } + if (dns_rdataset_isassociated(&sigdnskeyset)) { + dns_rdataset_disassociate(&sigdnskeyset); + } + if (dns_rdataset_isassociated(&zonemdset)) { + dns_rdataset_disassociate(&zonemdset); + } + if (dns_rdataset_isassociated(&sigzonemdset)) { + dns_rdataset_disassociate(&sigzonemdset); + } + if (dns_rdataset_isassociated(&nsec3paramset)) { + dns_rdataset_disassociate(&nsec3paramset); + } + if (dns_rdataset_isassociated(&signsec3paramset)) { + dns_rdataset_disassociate(&signsec3paramset); + } + if (node != NULL) { + dns_db_detachnode(db, &node); + } + if (nsec3node != NULL) { + dns_db_detachnode(db, &nsec3node); + } + if (keynode != NULL) { + dns_keynode_detach(&keynode); + } + if (secroots != NULL) { + dns_keytable_detach(&secroots); + } + dns_zone_log(zone, ISC_LOG_DEBUG(3), "dns_zone_checkzonemd -> %s", + isc_result_totext(result)); + return result; +} + /* * The zone is presumed to be locked. * If this is a inline_raw zone the secure version is also locked. @@ -5157,11 +5672,18 @@ zone_postload(dns_zone_t *zone, dns_db_t *db, isc_time_t loadtime, goto cleanup; } - result = dns_zone_verifydb(zone, db, NULL); - if (result != ISC_R_SUCCESS) { + result = dns_zone_checkzonemd(zone, db, NULL); + if (result == DNS_R_BADZONE) { goto cleanup; } + if (zone->type == dns_zone_mirror) { + result = dns_zone_verifydb(zone, db, NULL); + if (result != ISC_R_SUCCESS) { + goto cleanup; + } + } + if (zone->db != NULL) { unsigned int oldsoacount; @@ -24617,10 +25139,6 @@ dns_zone_verifydb(dns_zone_t *zone, dns_db_t *db, dns_dbversion_t *ver) { ENTER; - if (dns_zone_gettype(zone) != dns_zone_mirror) { - return ISC_R_SUCCESS; - } - if (ver == NULL) { dns_db_currentversion(db, &version); } else {