From 22e82765f99ccf4060f1c15a1a316d0699df7bbf Mon Sep 17 00:00:00 2001 From: "W.C.A. Wijngaards" Date: Wed, 14 Oct 2020 13:34:50 +0200 Subject: [PATCH] zonemd, dnssec verification routines. --- services/authzone.c | 555 +++++++++++++++++++++++++++++++++++++++++--- services/authzone.h | 18 ++ util/fptr_wlist.c | 1 + 3 files changed, 541 insertions(+), 33 deletions(-) diff --git a/services/authzone.c b/services/authzone.c index 143350665..df77b12a2 100644 --- a/services/authzone.c +++ b/services/authzone.c @@ -69,6 +69,8 @@ #include "validator/val_nsec3.h" #include "validator/val_secalgo.h" #include "validator/val_sigcrypt.h" +#include "validator/val_anchor.h" +#include "validator/val_utils.h" #include /** bytes to use for NSEC3 hash buffer. 20 for sha1 */ @@ -1789,46 +1791,39 @@ static int zonemd_fetch_parameters(struct auth_rrset* zonemd_rrset, size_t i, * it can warn or fail on that. Checks the hash of the ZONEMD. * @param z: auth zone to check for. * caller must hold lock on zone. - * @param cfg: config file options + * @param env: module env for temp buffers. + * @param reason: returned on failure. + * @return false on failure, true if hash checks out. */ -void auth_zone_zonemd_check_hash(struct auth_zone* z /*, struct config_file* cfg*/) +static int auth_zone_zonemd_check_hash(struct auth_zone* z, + struct module_env* env, char** reason) { /* loop over ZONEMDs and see which one is valid. if not print * failure (depending on config) */ struct auth_data* apex; struct auth_rrset* zonemd_rrset; size_t i; - char* reason = NULL; struct regional* region = NULL; struct sldns_buffer* buf = NULL; char zstr[255+1]; uint32_t soa_serial = 0; - region = regional_create(); - if(!region) { - return; - } - buf = sldns_buffer_new(65535); - if(!buf) { - regional_destroy(region); - return; - } + region = env->scratch; + regional_free_all(region); + buf = env->scratch_buffer; if(!auth_zone_get_serial(z, &soa_serial)) { - regional_destroy(region); - sldns_buffer_free(buf); - return; + *reason = "zone has no SOA serial"; + return 0; } apex = az_find_name(z, z->name, z->namelen); if(!apex) { - regional_destroy(region); - sldns_buffer_free(buf); - return; + *reason = "zone has no apex"; + return 0; } zonemd_rrset = az_domain_rrset(apex, LDNS_RR_TYPE_ZONEMD); if(!zonemd_rrset || zonemd_rrset->data->count==0) { - regional_destroy(region); - sldns_buffer_free(buf); - return; /* no RRset or no RRs in rrset */ + *reason = "zone has no ZONEMD"; + return 0; /* no RRset or no RRs in rrset */ } /* we have a ZONEMD, check if it is correct */ @@ -1840,34 +1835,31 @@ void auth_zone_zonemd_check_hash(struct auth_zone* z /*, struct config_file* cfg if(!zonemd_fetch_parameters(zonemd_rrset, i, &serial, &scheme, &hashalgo, &hash, &hashlen)) { /* malformed RR */ - reason = "ZONEMD rdata malformed"; + *reason = "ZONEMD rdata malformed"; continue; } + regional_free_all(region); if(serial != soa_serial) { - reason = "ZONEMD serial is wrong"; + *reason = "ZONEMD serial is wrong"; continue; } if(auth_zone_generate_zonemd_check(z, scheme, hashalgo, - hash, hashlen, region, buf, &reason)) { + hash, hashlen, region, buf, reason)) { /* success */ if(verbosity >= VERB_ALGO) { dname_str(z->name, zstr); verbose(VERB_ALGO, "auth-zone %s ZONEMD hash is correct", zstr); } - regional_destroy(region); - sldns_buffer_free(buf); - return; + return 1; } /* try next one */ } /* fail, we may have reason */ - if(!reason) - reason = "no ZONEMD records found"; + if(!*reason) + *reason = "no ZONEMD records found"; dname_str(z->name, zstr); - log_warn("auth-zone %s ZONEMD failed: %s", zstr, reason); - - regional_destroy(region); - sldns_buffer_free(buf); + log_warn("auth-zone %s ZONEMD failed: %s", zstr, *reason); + return 0; } /** find serial number of zone or false if none */ @@ -7501,12 +7493,509 @@ int auth_zone_generate_zonemd_check(struct auth_zone* z, int scheme, /* check digest length */ if(hashlen != genlen) { *reason = "incorrect digest length"; + if(verbosity >= VERB_ALGO) { + verbose(VERB_ALGO, "zonemd scheme=%d hashalgo=%d", + scheme, hashalgo); + log_hex("ZONEMD should be ", gen, genlen); + log_hex("ZONEMD to check is", hash, hashlen); + } return 0; } /* check digest */ if(memcmp(hash, gen, genlen) != 0) { *reason = "incorrect digest"; + if(verbosity >= VERB_ALGO) { + verbose(VERB_ALGO, "zonemd scheme=%d hashalgo=%d", + scheme, hashalgo); + log_hex("ZONEMD should be ", gen, genlen); + log_hex("ZONEMD to check is", hash, hashlen); + } + return 0; + } + return 1; +} + +/** log auth zone message with zone name in front. */ +static void auth_zone_log(uint8_t* name, enum verbosity_value level, const char* format, ...) ATTR_FORMAT(printf, 3, 4); +static void auth_zone_log(uint8_t* name, enum verbosity_value level, const char* format, ...) +{ + va_list args; + va_start(args, format); + if(verbosity >= level) { + char str[255+1]; + char msg[MAXSYSLOGMSGLEN]; + dname_str(name, str); + vsnprintf(msg, sizeof(msg), format, args); + verbose(level, "auth zone %s %s", str, msg); + } + va_end(args); +} + +/** ZONEMD, dnssec verify the rrset with the dnskey */ +static int zonemd_dnssec_verify(struct auth_zone* z, struct module_env* env, + struct ub_packed_rrset_key* dnskey, struct auth_rrset* rrset) +{ + struct ub_packed_rrset_key pk; + enum sec_status sec; + struct val_env* ve; + int m; + char* why_bogus = NULL; + m = modstack_find(&env->mesh->mods, "validator"); + if(m == -1) { + auth_zone_log(z->name, VERB_ALGO, "zonemd dnssec verify: have " + "trust anchor, but no validator module"); + return 0; + } + ve = (struct val_env*)env->modinfo[m]; + + memset(&pk, 0, sizeof(pk)); + pk.entry.key = &pk; + pk.entry.data = rrset->data; + pk.rk.dname = z->name; + pk.rk.dname_len = z->namelen; + pk.rk.type = htons(rrset->type); + pk.rk.rrset_class = htons(z->dclass); + auth_zone_log(z->name, VERB_ALGO, + "zonemd: verify RRset with DNSKEY"); + /* pass NULL for qstate, it is only used for qtype NSEC to realloc + * a new name in the qstate region */ + sec = dnskeyset_verify_rrset(env, ve, &pk, dnskey, NULL, &why_bogus, + LDNS_SECTION_ANSWER, NULL); + regional_free_all(env->scratch); + if(sec == sec_status_secure) { + return 1; + } + if(why_bogus) + auth_zone_log(z->name, VERB_ALGO, "DNSSEC verify was bogus: %s", why_bogus); + return 0; +} + +/** Verify the absence of ZONEMD with DNSSEC by checking NSEC, NSEC3 type flag. + * return false on failure, reason contains description of failure. */ +static int zonemd_check_dnssec_absence(struct auth_zone* z, + struct module_env* env, struct ub_packed_rrset_key* dnskey, + struct auth_data* apex, char** reason) +{ + struct auth_rrset* nsec = NULL; + if(!apex) { + *reason = "zone has no apex domain but ZONEMD missing"; + return 0; + } + nsec = az_domain_rrset(apex, LDNS_RR_TYPE_NSEC); + if(nsec) { + /* dnssec verify the NSEC */ + if(!zonemd_dnssec_verify(z, env, dnskey, nsec)) { + *reason = "DNSSEC verify failed for NSEC RRset"; + return 0; + } + /* check type bitmap */ + } else { + /* NSEC3 perhaps ? */ + int algo; + size_t iter, saltlen; + uint8_t* salt; + struct auth_rrset* nsec3param = az_domain_rrset(apex, + LDNS_RR_TYPE_NSEC3PARAM); + struct auth_data* match; + struct auth_rrset* nsec3; + if(!nsec3param) { + *reason = "zone has no NSEC information but ZONEMD missing"; + return 0; + } + if(!az_nsec3_param(z, &algo, &iter, &salt, &saltlen)) { + *reason = "zone has no NSEC information but ZONEMD missing"; + return 0; + } + /* find the NSEC3 record */ + match = az_nsec3_find_exact(z, z->name, z->namelen, algo, + iter, salt, saltlen); + if(!match) { + *reason = "zone has no NSEC3 domain for the apex but ZONEMD missing"; + return 0; + } + nsec3 = az_domain_rrset(match, LDNS_RR_TYPE_NSEC3); + if(!nsec3) { + *reason = "zone has no NSEC3 RRset for the apex but ZONEMD missing"; + return 0; + } + /* dnssec verify the NSEC3 */ + if(!zonemd_dnssec_verify(z, env, dnskey, nsec3)) { + *reason = "DNSSEC verify failed for NSEC3 RRset"; + return 0; + } + /* check type bitmap */ + } + + return 1; +} + +/** Verify the SOA and ZONEMD DNSSEC signatures. + * return false on failure, reason contains description of failure. */ +static int zonemd_check_dnssec_soazonemd(struct auth_zone* z, + struct module_env* env, struct ub_packed_rrset_key* dnskey, + struct auth_data* apex, struct auth_rrset* zonemd_rrset, + char** reason) +{ + struct auth_rrset* soa; + if(!apex) { + *reason = "zone has no apex domain"; + return 0; + } + soa = az_domain_rrset(apex, LDNS_RR_TYPE_SOA); + if(!soa) { + *reason = "zone has no SOA RRset"; return 0; } + if(!zonemd_dnssec_verify(z, env, dnskey, soa)) { + *reason = "DNSSEC verify failed for SOA RRset"; + return 0; + } + if(!zonemd_dnssec_verify(z, env, dnskey, zonemd_rrset)) { + *reason = "DNSSEC verify failed for ZONEMD RRset"; + return 0; + } + auth_zone_log(z->name, VERB_ALGO, "zonemd DNSSEC verification of SOA and ZONEMD RRsets secure"); return 1; } + +/** + * Fail the ZONEMD verification. + * @param z: auth zone that fails. + * @param env: environment with config, to ignore failure or not. + * @param reason: failure string description. + */ +static void auth_zone_zonemd_fail(struct auth_zone* z, struct module_env* env, + char* reason) +{ + char zstr[255+1]; + /* if fail: log reason, and depending on config also take action + * and drop the zone, eg. it is gone from memory, set zone_expired */ + dname_str(z->name, zstr); + if(!reason) reason = "verification failed"; + log_warn("auth zone %s: ZONEMD verification failed: %s", zstr, reason); + + /* expired means the zone gives servfail and is not used by + * lookup if fallback_enabled*/ + z->zone_expired = 1; +} + +/** + * Verify the zonemd with DNSSEC and hash check, with given key. + * @param z: auth zone. + * @param env: environment with config and temp buffers. + * @param dnskey: dnskey that we can use, or NULL. If nonnull, the key + * has been verified and is the start of the chain of trust. + * @param is_insecure: if true, the dnskey is not used, the zone is insecure. + * And dnssec is not used. It is DNSSEC secure insecure or not under + * a trust anchor. + */ +static void +auth_zone_verify_zonemd_with_key(struct auth_zone* z, struct module_env* env, + struct ub_packed_rrset_key* dnskey, int is_insecure) +{ + char* reason = NULL; + struct auth_data* apex = NULL; + struct auth_rrset* zonemd_rrset = NULL; + int zonemd_absent = 0; + + /* see if ZONEMD is present or absent. */ + apex = az_find_name(z, z->name, z->namelen); + if(!apex) { + zonemd_absent = 1; + } else { + zonemd_rrset = az_domain_rrset(apex, LDNS_RR_TYPE_ZONEMD); + if(!zonemd_rrset || zonemd_rrset->data->count==0) { + zonemd_absent = 1; + zonemd_rrset = NULL; + } + } + + /* if no ZONEMD, and no DNSSEC, done. */ + /* if no ZONEMD, and DNSSEC, use DNSKEY to verify NSEC or NSEC3 for + * zone apex. Check ZONEMD bit is turned off or else fail */ + /* if ZONEMD, and DNSSEC, check DNSSEC signature on SOA and ZONEMD, + * or else fail */ + if(!zonemd_rrset && is_insecure) { + /* success, zonemd is absent */ + } else if(!zonemd_rrset) { + /* fetch, DNSSEC verify, and check NSEC/NSEC3 */ + if(!zonemd_check_dnssec_absence(z, env, dnskey, apex, + &reason)) { + auth_zone_zonemd_fail(z, env, reason); + return; + } + } else if(zonemd_rrset && dnskey) { + /* check DNSSEC verify of SOA and ZONEMD */ + if(!zonemd_check_dnssec_soazonemd(z, env, dnskey, apex, + zonemd_rrset, &reason)) { + auth_zone_zonemd_fail(z, env, reason); + return; + } + } + + /* check ZONEMD checksum and report or else fail. */ + if(!auth_zone_zonemd_check_hash(z, env, &reason)) { + auth_zone_zonemd_fail(z, env, reason); + return; + } + + if(zonemd_absent) + auth_zone_zonemd_fail(z, env, "ZONEMD absent and that is not allowed by config"); + /* success! log the success */ + auth_zone_log(z->name, VERB_ALGO, "ZONEMD verification successful"); +} + +/** + * verify the zone DNSKEY rrset from the trust anchor + * This is possible because the anchor is for the zone itself, and can + * thus apply straight to the zone DNSKEY set. + * @param z: the auth zone. + * @param env: environment with time and temp buffers. + * @param anchor: trust anchor to use + * @param is_insecure: returned, true if the zone is securely insecure. + * @param reason: if the routine fails, returns the failure reason. + * @param keystorage: where to store the ub_packed_rrset_key that is created + * on success. A pointer to it is returned on success. + * @return the dnskey RRset, reference to zone data and keystorage, or + * NULL on failure. + */ +static struct ub_packed_rrset_key* +zonemd_get_dnskey_from_anchor(struct auth_zone* z, struct module_env* env, + struct trust_anchor* anchor, int* is_insecure, char** reason, + struct ub_packed_rrset_key* keystorage) +{ + struct auth_data* apex; + struct auth_rrset* dnskey_rrset; + enum sec_status sec; + struct val_env* ve; + int m; + + apex = az_find_name(z, z->name, z->namelen); + if(!apex) { + *reason = "have trust anchor, but zone has no apex domain for DNSKEY"; + return 0; + } + dnskey_rrset = az_domain_rrset(apex, LDNS_RR_TYPE_DNSKEY); + if(!dnskey_rrset || dnskey_rrset->data->count==0) { + *reason = "have trust anchor, but zone has no DNSKEY"; + return 0; + } + + m = modstack_find(&env->mesh->mods, "validator"); + if(m == -1) { + *reason = "have trust anchor, but no validator module"; + return 0; + } + ve = (struct val_env*)env->modinfo[m]; + + memset(keystorage, 0, sizeof(*keystorage)); + keystorage->entry.key = keystorage; + keystorage->entry.data = dnskey_rrset->data; + keystorage->rk.dname = apex->name; + keystorage->rk.dname_len = apex->namelen; + keystorage->rk.type = htons(LDNS_RR_TYPE_DNSKEY); + keystorage->rk.rrset_class = htons(z->dclass); + auth_zone_log(z->name, VERB_QUERY, + "zonemd: verify DNSKEY RRset with trust anchor"); + /* pass NULL for qstate, it is only used when type NSEC needs a + * name reallocated to get the qstate region for that */ + sec = val_verify_DNSKEY_with_TA(env, ve, keystorage, anchor->ds_rrset, + anchor->dnskey_rrset, NULL, reason, NULL); + regional_free_all(env->scratch); + if(sec == sec_status_secure) { + /* success */ + *is_insecure = 0; + return keystorage; + } else if(sec == sec_status_insecure) { + /* insecure */ + *is_insecure = 1; + } else { + /* bogus */ + *is_insecure = 0; + } + return NULL; +} + +/** callback for ZONEMD lookup of DNSKEY */ +void auth_zonemd_dnskey_lookup_callback(void* arg, int rcode, sldns_buffer* buf, + enum sec_status sec, char* why_bogus, int ATTR_UNUSED(was_ratelimited)) +{ + struct auth_zone* z = (struct auth_zone*)arg; + struct module_env* env; + char* reason = NULL; + struct ub_packed_rrset_key* dnskey = NULL; + int is_insecure = 0; + + lock_rw_wrlock(&z->lock); + env = z->zonemd_callback_env; + /* release the env variable so another worker can pick up the + * ZONEMD verification task if it wants to */ + z->zonemd_callback_env = NULL; + if(!env || env->outnet->want_to_quit) { + lock_rw_unlock(&z->lock); + return; /* stop on quit */ + } + + /* process result */ + if(sec == sec_status_bogus) { + reason = why_bogus; + if(!reason) + reason = "lookup of DNSKEY was bogus"; + auth_zone_log(z->name, VERB_ALGO, + "zonemd lookup of DNSKEY was bogus: %s", why_bogus); + } else if(rcode == LDNS_RCODE_NOERROR) { + uint16_t wanted_qtype = LDNS_RR_TYPE_DNSKEY; + struct regional* temp = env->scratch; + struct query_info rq; + struct reply_info* rep; + memset(&rq, 0, sizeof(rq)); + rep = parse_reply_in_temp_region(buf, temp, &rq); + if(rep && rq.qtype == wanted_qtype && + FLAGS_GET_RCODE(rep->flags) == LDNS_RCODE_NOERROR) { + /* parsed successfully */ + struct ub_packed_rrset_key* answer = + reply_find_answer_rrset(&rq, rep); + if(answer && sec == sec_status_secure) { + dnskey = answer; + auth_zone_log(z->name, VERB_ALGO, + "zonemd lookup of DNSKEY was secure"); + } else if(sec == sec_status_secure && !answer) { + is_insecure = 1; + auth_zone_log(z->name, VERB_ALGO, + "zonemd lookup of DNSKEY has no content, but is secure, treat as insecure"); + } else if(sec == sec_status_insecure) { + is_insecure = 1; + auth_zone_log(z->name, VERB_ALGO, + "zonemd lookup of DNSKEY was insecure"); + } else if(sec == sec_status_indeterminate) { + is_insecure = 1; + auth_zone_log(z->name, VERB_ALGO, + "zonemd lookup of DNSKEY was indeterminate, treat as insecure"); + } else { + auth_zone_log(z->name, VERB_ALGO, + "zonemd lookup of DNSKEY has nodata"); + reason = "lookup of DNSKEY has nodata"; + } + } else { + auth_zone_log(z->name, VERB_ALGO, + "zonemd lookup of DNSKEY has no answer"); + reason = "lookup of DNSKEY has no answer"; + } + } else { + auth_zone_log(z->name, VERB_ALGO, + "zonemd lookup of DNSKEY failed"); + reason = "lookup of DNSKEY failed"; + } + + if(reason) { + auth_zone_zonemd_fail(z, env, reason); + lock_rw_unlock(&z->lock); + return; + } + + auth_zone_verify_zonemd_with_key(z, env, dnskey, is_insecure); + lock_rw_unlock(&z->lock); +} + +/** lookup DNSKEY for ZONEMD verification */ +static int +zonemd_lookup_dnskey(struct auth_zone* z, struct module_env* env) +{ + struct query_info qinfo; + uint16_t qflags = BIT_RD; + struct edns_data edns; + sldns_buffer* buf = env->scratch_buffer; + + if(z->zonemd_callback_env) { + /* another worker is already working on the callback + * for the DNSKEY lookup for ZONEMD verification. + * We do not also have to do ZONEMD verification, let that + * worker do it */ + auth_zone_log(z->name, VERB_ALGO, + "zonemd needs lookup of DNSKEY and that already worked on by another worker"); + return 1; + } + + /* use mesh_new_callback to lookup the DNSKEY, + * and then wait for them to be looked up (in cache, or query) */ + qinfo.qname_len = z->namelen; + qinfo.qname = z->name; + qinfo.qclass = z->dclass; + qinfo.qtype = LDNS_RR_TYPE_DNSKEY; + qinfo.local_alias = NULL; + if(verbosity >= VERB_ALGO) { + char buf1[512]; + char buf2[LDNS_MAX_DOMAINLEN+1]; + dname_str(z->name, buf2); + snprintf(buf1, sizeof(buf1), "auth zone %s: lookup DNSKEY " + "for zonemd verification", buf2); + log_query_info(VERB_ALGO, buf1, &qinfo); + } + edns.edns_present = 1; + edns.ext_rcode = 0; + edns.edns_version = 0; + edns.bits = EDNS_DO; + edns.opt_list = NULL; + if(sldns_buffer_capacity(buf) < 65535) + edns.udp_size = (uint16_t)sldns_buffer_capacity(buf); + else edns.udp_size = 65535; + + /* store the worker-specific module env for the callback. + * We can then reference this when the callback executes */ + z->zonemd_callback_env = env; + /* the callback can be called straight away */ + lock_rw_unlock(&z->lock); + if(!mesh_new_callback(env->mesh, &qinfo, qflags, &edns, buf, 0, + &auth_zonemd_dnskey_lookup_callback, z)) { + lock_rw_wrlock(&z->lock); + log_err("out of memory lookup up dnskey for zonemd"); + return 0; + } + lock_rw_wrlock(&z->lock); + return 1; +} + +void auth_zone_verify_zonemd(struct auth_zone* z, struct module_env* env) +{ + char* reason = NULL; + struct trust_anchor* anchor = NULL; + struct ub_packed_rrset_key* dnskey = NULL; + struct ub_packed_rrset_key keystorage; + int is_insecure = 0; + /* verify the ZONEMD if present. + * If not present check if absence is allowed by DNSSEC */ + + /* if zone is under a trustanchor */ + /* is it equal to trustanchor - get dnskey's verified */ + /* else, find chain of trust by fetching DNSKEYs lookup for zone */ + /* result if that, if insecure, means no DNSSEC for the ZONEMD, + * otherwise we have the zone DNSKEY for the DNSSEC verification. */ + anchor = anchors_lookup(env->anchors, z->name, z->namelen, z->dclass); + if(anchor && query_dname_compare(z->name, anchor->name) == 0) { + /* equal to trustanchor, no need for online lookups */ + dnskey = zonemd_get_dnskey_from_anchor(z, env, anchor, + &is_insecure, &reason, &keystorage); + if(!dnskey && !reason) { + reason = "dnskey verify with anchor failed"; + } + } else if(anchor) { + /* perform online lookups */ + /* setup online lookups, and wait for them */ + if(zonemd_lookup_dnskey(z, env)) { + /* wait for the lookup */ + return; + } + reason = "could not lookup DNSKEY for chain of trust"; + } else { + /* the zone is not under a trust anchor */ + dnskey = NULL; + is_insecure = 1; + } + + if(reason) { + auth_zone_zonemd_fail(z, env, reason); + return; + } + + auth_zone_verify_zonemd_with_key(z, env, dnskey, is_insecure); +} diff --git a/services/authzone.h b/services/authzone.h index 6a0a79da4..69ec7ed99 100644 --- a/services/authzone.h +++ b/services/authzone.h @@ -134,6 +134,11 @@ struct auth_zone { int for_upstream; /** RPZ zones */ struct rpz* rpz; + /** store the env (worker thread specific) for the zonemd callbacks + * from the mesh with the results of the lookup, if nonNULL, some + * worker has already picked up the zonemd verification task and + * this worked does not have to do it as well. */ + struct module_env* zonemd_callback_env; /** zone has been deleted */ int zone_deleted; /** deletelist pointer, unused normally except during delete */ @@ -733,4 +738,17 @@ int auth_zone_generate_zonemd_check(struct auth_zone* z, int scheme, int hashalgo, uint8_t* hash, size_t hashlen, struct regional* region, struct sldns_buffer* buf, char** reason); +/** + * Perform ZONEMD checks and verification for the auth zone. + * This includes DNSSEC verification if applicable. + * @param z: auth zone to check. Caller holds lock. wrlock. + * @param env: with temp region, buffer and config. + */ +void auth_zone_verify_zonemd(struct auth_zone* z, struct module_env* env); + +/** mesh callback for zonemd on lookup of dnskey */ +void auth_zonemd_dnskey_lookup_callback(void* arg, int rcode, + struct sldns_buffer* buf, enum sec_status sec, char* why_bogus, + int was_ratelimited); + #endif /* SERVICES_AUTHZONE_H */ diff --git a/util/fptr_wlist.c b/util/fptr_wlist.c index 7d15d1075..1469e8213 100644 --- a/util/fptr_wlist.c +++ b/util/fptr_wlist.c @@ -581,6 +581,7 @@ int fptr_whitelist_mesh_cb(mesh_cb_func_type fptr) else if(fptr == &probe_answer_cb) return 1; else if(fptr == &auth_xfer_probe_lookup_callback) return 1; else if(fptr == &auth_xfer_transfer_lookup_callback) return 1; + else if(fptr == &auth_zonemd_dnskey_lookup_callback) return 1; return 0; } -- 2.47.2