/*
 * 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 <stdbool.h>

#include <isc/async.h>
#include <isc/atomic.h>
#include <isc/base32.h>
#include <isc/counter.h>
#include <isc/helper.h>
#include <isc/job.h>
#include <isc/log.h>
#include <isc/md.h>
#include <isc/mem.h>
#include <isc/refcount.h>
#include <isc/result.h>
#include <isc/string.h>
#include <isc/tid.h>
#include <isc/util.h>
#include <isc/work.h>

#include <dns/client.h>
#include <dns/db.h>
#include <dns/dnssec.h>
#include <dns/ds.h>
#include <dns/ede.h>
#include <dns/keytable.h>
#include <dns/keyvalues.h>
#include <dns/message.h>
#include <dns/ncache.h>
#include <dns/nsec.h>
#include <dns/nsec3.h>
#include <dns/rdata.h>
#include <dns/rdataset.h>
#include <dns/rdatatype.h>
#include <dns/resolver.h>
#include <dns/validator.h>
#include <dns/view.h>

/*! \file
 * \brief
 * Basic processing sequences:
 *
 * \li When called with rdataset and sigrdataset:
 *     validator_start -> validate_answer -> proveunsecure
 *     validator_start -> validate_answer -> validate_nx (if secure wildcard)
 *
 * \li When called with rdataset but no sigrdataset:
 *     validator_start -> proveunsecure
 *
 * \li When called with no rdataset or sigrdataset:
 *     validator_start -> validate_nx -> proveunsecure
 *
 * validator_start:   determine what type of validation to do.
 * validate_answer:   attempt to perform a positive validation.
 * proveunsecure:     attempt to prove the answer comes from an unsecure zone.
 * validate_nx:       attempt to prove a negative response.
 */

#define VALIDATOR_MAGIC	   ISC_MAGIC('V', 'a', 'l', '?')
#define VALID_VALIDATOR(v) ISC_MAGIC_VALID(v, VALIDATOR_MAGIC)

enum valattr {
	VALATTR_CANCELED = 1 << 1,	     /*%< Canceled. */
	VALATTR_TRIEDVERIFY = 1 << 2,	     /*%< We have found a key and have
						attempted a verify. */
	VALATTR_COMPLETE = 1 << 3,	     /*%< Completion event sent. */
	VALATTR_INSECURITY = 1 << 4,	     /*%< Attempting proveunsecure. */
	VALATTR_MAXVALIDATIONS = 1 << 5,     /*%< Max validations quota */
	VALATTR_MAXVALIDATIONFAILS = 1 << 6, /*%< Max validation fails
						quota */
	VALATTR_OFFLOADED = 1 << 7, /*%< The ownership has been passed to
				       offloaded thread */

	/*!
	 * NSEC proofs to be looked for.
	 */
	VALATTR_NEEDNOQNAME = 1 << 8,
	VALATTR_NEEDNOWILDCARD = 1 << 9,
	VALATTR_NEEDNODATA = 1 << 10,

	/*!
	 * NSEC proofs that have been found.
	 */
	VALATTR_FOUNDNOQNAME = 1 << 12,
	VALATTR_FOUNDNOWILDCARD = 1 << 13,
	VALATTR_FOUNDNODATA = 1 << 14,
	VALATTR_FOUNDCLOSEST = 1 << 15,
	VALATTR_FOUNDOPTOUT = 1 << 16,
	VALATTR_FOUNDUNKNOWN = 1 << 17,
};

#define NEEDNODATA(val)	     ((val->attributes & VALATTR_NEEDNODATA) != 0)
#define NEEDNOQNAME(val)     ((val->attributes & VALATTR_NEEDNOQNAME) != 0)
#define NEEDNOWILDCARD(val)  ((val->attributes & VALATTR_NEEDNOWILDCARD) != 0)
#define FOUNDNODATA(val)     ((val->attributes & VALATTR_FOUNDNODATA) != 0)
#define FOUNDNOQNAME(val)    ((val->attributes & VALATTR_FOUNDNOQNAME) != 0)
#define FOUNDNOWILDCARD(val) ((val->attributes & VALATTR_FOUNDNOWILDCARD) != 0)
#define FOUNDCLOSEST(val)    ((val->attributes & VALATTR_FOUNDCLOSEST) != 0)
#define FOUNDOPTOUT(val)     ((val->attributes & VALATTR_FOUNDOPTOUT) != 0)

#define CANCELING(v) atomic_load(&(v)->canceling)
#define CANCELED(v)  (((v)->attributes & VALATTR_CANCELED) != 0)
#define OFFLOADED(v) (((v)->attributes & VALATTR_OFFLOADED) != 0)
#define COMPLETE(v)  (((v)->attributes & VALATTR_COMPLETE) != 0)

#define NEGATIVE(r) (((r)->attributes.negative))
#define NXDOMAIN(r) (((r)->attributes.nxdomain))

#define MAXVALIDATIONS(r) (((r)->attributes & VALATTR_MAXVALIDATIONS) != 0)
#define MAXVALIDATIONFAILS(r) \
	(((r)->attributes & VALATTR_MAXVALIDATIONFAILS) != 0)

static void
destroy_validator(dns_validator_t *val);

static isc_result_t
select_signing_key(dns_validator_t *val, dns_rdataset_t *rdataset);

static void
resume_answer(void *arg);
static void
validate_async_done(dns_validator_t *val, isc_result_t result);
static isc_result_t
validate_async_run(dns_validator_t *val, isc_job_cb cb);
static isc_result_t
validate_helper_run(dns_validator_t *val, isc_job_cb cb);

static void
validate_dnskey(void *arg);
static void
validate_dnskey_dsset_done(dns_validator_t *val, isc_result_t result);

static isc_result_t
validate_nx(dns_validator_t *val, bool resume);

static isc_result_t
proveunsecure(dns_validator_t *val, bool have_ds, bool have_dnskey,
	      bool resume);

static void
validator_logv(dns_validator_t *val, isc_logcategory_t category,
	       isc_logmodule_t module, int level, const char *fmt, va_list ap)
	ISC_FORMAT_PRINTF(5, 0);

static void
validator_log(void *val, int level, const char *fmt, ...)
	ISC_FORMAT_PRINTF(3, 4);

static void
validator_logcreate(dns_validator_t *val, dns_name_t *name,
		    dns_rdatatype_t type, const char *caller,
		    const char *operation);

static isc_result_t
create_fetch(dns_validator_t *val, dns_name_t *name, dns_rdatatype_t type,
	     isc_job_cb callback, const char *caller);

/*%
 * Ensure the validator's rdatasets are marked as expired.
 */
static void
expire_rdatasets(dns_validator_t *val) {
	if (dns_rdataset_isassociated(&val->frdataset)) {
		dns_rdataset_expire(&val->frdataset);
	}
	if (dns_rdataset_isassociated(&val->fsigrdataset)) {
		dns_rdataset_expire(&val->fsigrdataset);
	}
}

static void
validate_extendederror(dns_validator_t *val);

static void
validator_addede(dns_validator_t *val, uint16_t code, const char *extra);

/*%
 * Ensure the validator's rdatasets are disassociated.
 */
static void
disassociate_rdatasets(dns_validator_t *val) {
	dns_rdataset_cleanup(&val->fdsset);
	dns_rdataset_cleanup(&val->frdataset);
	dns_rdataset_cleanup(&val->fsigrdataset);
}

/*%
 * Mark the rdatasets in val->vstat with trust level "answer",
 * indicating that they did not validate, but could be cached as insecure.
 */
static isc_result_t
markanswer(dns_validator_t *val, const char *where) {
	validator_log(val, ISC_LOG_DEBUG(3), "marking as answer (%s)", where);
	if (val->rdataset != NULL) {
		dns_rdataset_settrust(val->rdataset, dns_trust_answer);
	}
	if (val->sigrdataset != NULL) {
		dns_rdataset_settrust(val->sigrdataset, dns_trust_answer);
	}

	return ISC_R_SUCCESS;
}

/*%
 * Mark the RRsets in val->vstat with trust level secure.
 */
static void
marksecure(dns_validator_t *val) {
	dns_rdataset_settrust(val->rdataset, dns_trust_secure);
	if (val->sigrdataset != NULL) {
		dns_rdataset_settrust(val->sigrdataset, dns_trust_secure);
	}
	val->secure = true;
}

/*
 * Validator 'val' is finished; send the completion event to the loop
 * that called dns_validator_create(), with result `result`.
 */
static void
validator_done(dns_validator_t *val, isc_result_t result) {
	if (COMPLETE(val)) {
		return;
	}

	val->attributes |= VALATTR_COMPLETE;
	val->result = result;

	dns_ede_copy(val->cb_edectx, &val->edectx);

	isc_async_run(val->loop, val->cb, val);
}

/*%
 * Look in the NSEC record returned from a DS query to see if there is
 * a NS RRset at this name.  If it is found we are at a delegation point.
 */
static bool
isdelegation(dns_name_t *name, dns_rdataset_t *rdataset,
	     isc_result_t dbresult) {
	dns_fixedname_t fixed;
	dns_label_t hashlabel;
	dns_name_t nsec3name;
	dns_rdata_nsec3_t nsec3;
	dns_rdataset_t set;
	int order;
	int scope;
	bool found;
	isc_buffer_t buffer;
	isc_result_t result;
	unsigned char hash[NSEC3_MAX_HASH_LENGTH];
	unsigned char owner[NSEC3_MAX_HASH_LENGTH];
	unsigned int length;

	REQUIRE(dbresult == DNS_R_NXRRSET || dbresult == DNS_R_NCACHENXRRSET);

	dns_rdataset_init(&set);
	if (dbresult == DNS_R_NXRRSET) {
		dns_rdataset_clone(rdataset, &set);
	} else {
		result = dns_ncache_getrdataset(rdataset, name,
						dns_rdatatype_nsec, &set);
		if (result == ISC_R_NOTFOUND) {
			goto trynsec3;
		}
		if (result != ISC_R_SUCCESS) {
			return false;
		}
	}

	INSIST(set.type == dns_rdatatype_nsec);

	found = false;
	result = dns_rdataset_first(&set);
	if (result == ISC_R_SUCCESS) {
		dns_rdata_t rdata = DNS_RDATA_INIT;
		dns_rdataset_current(&set, &rdata);
		found = dns_nsec_typepresent(&rdata, dns_rdatatype_ns);
	}
	dns_rdataset_disassociate(&set);
	return found;

trynsec3:
	/*
	 * Iterate over the ncache entry.
	 */
	found = false;
	dns_name_init(&nsec3name);
	dns_fixedname_init(&fixed);
	dns_name_downcase(name, dns_fixedname_name(&fixed));
	name = dns_fixedname_name(&fixed);
	DNS_RDATASET_FOREACH(rdataset) {
		dns_ncache_current(rdataset, &nsec3name, &set);
		if (set.type != dns_rdatatype_nsec3) {
			dns_rdataset_disassociate(&set);
			continue;
		}
		dns_name_getlabel(&nsec3name, 0, &hashlabel);
		isc_region_consume(&hashlabel, 1);
		isc_buffer_init(&buffer, owner, sizeof(owner));
		result = isc_base32hexnp_decoderegion(&hashlabel, &buffer);
		if (result != ISC_R_SUCCESS) {
			dns_rdataset_disassociate(&set);
			continue;
		}
		DNS_RDATASET_FOREACH(&set) {
			dns_rdata_t rdata = DNS_RDATA_INIT;
			dns_rdataset_current(&set, &rdata);
			(void)dns_rdata_tostruct(&rdata, &nsec3, NULL);
			if (nsec3.hash != 1) {
				continue;
			}
			length = isc_iterated_hash(
				hash, nsec3.hash, nsec3.iterations, nsec3.salt,
				nsec3.salt_length, name->ndata, name->length);
			if (length != isc_buffer_usedlength(&buffer)) {
				continue;
			}
			order = memcmp(hash, owner, length);
			if (order == 0) {
				found = dns_nsec3_typepresent(&rdata,
							      dns_rdatatype_ns);
				dns_rdataset_disassociate(&set);
				return found;
			}
			if ((nsec3.flags & DNS_NSEC3FLAG_OPTOUT) == 0) {
				continue;
			}
			/*
			 * Does this optout span cover the name?
			 */
			scope = memcmp(owner, nsec3.next, nsec3.next_length);
			if ((scope < 0 && order > 0 &&
			     memcmp(hash, nsec3.next, length) < 0) ||
			    (scope >= 0 &&
			     (order > 0 ||
			      memcmp(hash, nsec3.next, length) < 0)))
			{
				dns_rdataset_disassociate(&set);
				return true;
			}
		}
		dns_rdataset_disassociate(&set);
	}
	return found;
}

static void
resume_answer_with_key_done(void *arg);

static bool
over_max_fails(dns_validator_t *val);

static void
consume_validation_fail(dns_validator_t *val);

static void
resume_answer_with_key(void *arg) {
	dns_validator_t *val = arg;
	dns_rdataset_t *rdataset = &val->frdataset;

	isc_result_t result = select_signing_key(val, rdataset);
	if (result == ISC_R_SUCCESS) {
		val->keyset = &val->frdataset;
	} else if (result != ISC_R_NOTFOUND) {
		val->result = result;
		if (over_max_fails(val)) {
			INSIST(val->key == NULL);
			val->result = ISC_R_QUOTA;
		}
		consume_validation_fail(val);
	}

	(void)validate_async_run(val, resume_answer_with_key_done);
}

static void
resume_answer_with_key_done(void *arg) {
	dns_validator_t *val = arg;

	switch (val->result) {
	case ISC_R_CANCELED:	 /* Validation was canceled */
	case ISC_R_SHUTTINGDOWN: /* Server shutting down */
	case ISC_R_QUOTA:	 /* Validation fails quota reached */
		dns_validator_cancel(val);
		break;
	default:
		break;
	}

	resume_answer(val);
}

/*%
 * We have been asked to look for a key.
 * If found, resume the validation process.
 * If not found, fail the validation process.
 */
static void
fetch_callback_dnskey(void *arg) {
	dns_fetchresponse_t *resp = (dns_fetchresponse_t *)arg;
	dns_validator_t *val = resp->arg;
	dns_rdataset_t *rdataset = &val->frdataset;
	isc_result_t eresult = resp->result;
	isc_result_t result;
	bool trustchain;

	/*
	 * Set 'trustchain' to true if we're walking a chain of
	 * trust; false if we're attempting to prove insecurity.
	 */
	trustchain = ((val->attributes & VALATTR_INSECURITY) == 0);

	/* Free resources which are not of interest. */
	if (resp->node != NULL) {
		dns_db_detachnode(&resp->node);
	}
	if (resp->db != NULL) {
		dns_db_detach(&resp->db);
	}
	dns_rdataset_cleanup(&val->fsigrdataset);

	validator_log(val, ISC_LOG_DEBUG(3), "in fetch_callback_dnskey");

	dns_resolver_destroyfetch(&val->fetch);

	if (CANCELED(val) || CANCELING(val)) {
		CLEANUP(ISC_R_CANCELED);
	}

	if (trustchain) {
		switch (eresult) {
		case ISC_R_SUCCESS:
		case DNS_R_NCACHENXRRSET:
			/*
			 * We have an answer to our DNSKEY query.  Either the
			 * DNSKEY RRset or a NODATA response.
			 */
			validator_log(val, ISC_LOG_DEBUG(3), "%s with trust %s",
				      eresult == ISC_R_SUCCESS
					      ? "keyset"
					      : "NCACHENXRRSET",
				      dns_trust_totext(rdataset->trust));
			/*
			 * Only extract the dst key if the keyset exists and is
			 * secure.
			 */
			if (eresult == ISC_R_SUCCESS &&
			    rdataset->trust >= dns_trust_secure)
			{
				result = validate_helper_run(
					val, resume_answer_with_key);
			} else {
				result = validate_async_run(val, resume_answer);
			}
			break;
		default:
			validator_log(val, ISC_LOG_DEBUG(3),
				      "fetch_callback_dnskey: got %s",
				      isc_result_totext(eresult));
			result = DNS_R_BROKENCHAIN;
			break;
		}
	} else {
		switch (eresult) {
		case ISC_R_SUCCESS:
			/*
			 * We have a DS (val->dsrdataset) and
			 * DNSKEY (val->fdataset).
			 */
			result = proveunsecure(val, false, true, true);
			break;
		default:
			validator_log(val, ISC_LOG_DEBUG(3),
				      "fetch_callback_dnskey: got %s",
				      isc_result_totext(eresult));
			result = DNS_R_BROKENCHAIN;
			break;
		}
	}

cleanup:
	dns_resolver_freefresp(&resp);
	validate_async_done(val, result);
	dns_validator_detach(&val);
}

/*%
 * We have been asked to look for a DS. This may be part of
 * walking a trust chain, or an insecurity proof.
 */
static void
fetch_callback_ds(void *arg) {
	dns_fetchresponse_t *resp = (dns_fetchresponse_t *)arg;
	dns_validator_t *val = resp->arg;
	dns_rdataset_t *rdataset = &val->frdataset;
	isc_result_t eresult = resp->result;
	isc_result_t result;
	bool trustchain;

	/*
	 * Set 'trustchain' to true if we're walking a chain of
	 * trust; false if we're attempting to prove insecurity.
	 */
	trustchain = ((val->attributes & VALATTR_INSECURITY) == 0);

	/* Free resources which are not of interest. */
	if (resp->node != NULL) {
		dns_db_detachnode(&resp->node);
	}
	if (resp->db != NULL) {
		dns_db_detach(&resp->db);
	}
	dns_rdataset_cleanup(&val->fsigrdataset);

	validator_log(val, ISC_LOG_DEBUG(3), "in fetch_callback_ds");

	dns_resolver_destroyfetch(&val->fetch);

	if (CANCELED(val) || CANCELING(val)) {
		CLEANUP(ISC_R_CANCELED);
	}

	if (trustchain) {
		switch (eresult) {
		case ISC_R_SUCCESS:
			/*
			 * We looked for a DS record as part of
			 * following a key chain upwards; resume following
			 * the chain.
			 */
			validator_log(val, ISC_LOG_DEBUG(3),
				      "dsset with trust %s",
				      dns_trust_totext(rdataset->trust));
			val->dsset = &val->frdataset;
			result = validate_async_run(val, validate_dnskey);
			break;
		case DNS_R_CNAME:
		case DNS_R_NXRRSET:
		case DNS_R_NCACHENXRRSET:
		case DNS_R_SERVFAIL: /* RFC 1034 parent? */
			/*
			 * Failed to find a DS while following the
			 * chain of trust; now we need to prove insecurity.
			 */
			validator_log(val, ISC_LOG_DEBUG(3),
				      "falling back to insecurity proof (%s)",
				      isc_result_totext(eresult));
			result = proveunsecure(val, false, false, false);
			break;
		default:
			validator_log(val, ISC_LOG_DEBUG(3),
				      "fetch_callback_ds: got %s",
				      isc_result_totext(eresult));
			result = DNS_R_BROKENCHAIN;
			break;
		}
	} else {
		switch (eresult) {
		case DNS_R_NXDOMAIN:
		case DNS_R_NCACHENXDOMAIN:
			/*
			 * These results only make sense if we're attempting
			 * an insecurity proof, not when walking a chain of
			 * trust.
			 */

			result = proveunsecure(val, false, false, true);
			break;
		case ISC_R_SUCCESS:
			/*
			 * There is a DS which may or may not be a zone cut.
			 * In either case we are still in a secure zone,
			 * so keep looking for the break in the chain
			 * of trust.
			 */
			result = proveunsecure(val, true, false, true);
			break;
		case DNS_R_NXRRSET:
		case DNS_R_NCACHENXRRSET:
			if (isdelegation(resp->foundname, &val->frdataset,
					 eresult))
			{
				/*
				 * Failed to find a DS while trying to prove
				 * insecurity. If this is a zone cut, that
				 * means we're insecure.
				 */
				result = markanswer(val, "fetch_callback_ds");
				break;
			}
			FALLTHROUGH;
		case DNS_R_CNAME:
			/*
			 * Not a zone cut, so we have to keep looking for
			 * the break point in the chain of trust.
			 */
			result = proveunsecure(val, false, false, true);
			break;
		default:
			validator_log(val, ISC_LOG_DEBUG(3),
				      "fetch_callback_ds: got %s",
				      isc_result_totext(eresult));
			result = DNS_R_BROKENCHAIN;
			break;
		}
	}

cleanup:
	dns_resolver_freefresp(&resp);
	validate_async_done(val, result);
	dns_validator_detach(&val);
}

/*%
 * Callback from when a DNSKEY RRset has been validated.
 *
 * Resumes the stalled validation process.
 */
static void
validator_callback_dnskey(void *arg) {
	dns_validator_t *subvalidator = (dns_validator_t *)arg;
	dns_validator_t *val = subvalidator->parent;
	isc_result_t result = subvalidator->result;

	val->subvalidator = NULL;

	if (CANCELED(val) || CANCELING(val)) {
		CLEANUP(ISC_R_CANCELED);
	}

	validator_log(val, ISC_LOG_DEBUG(3), "in validator_callback_dnskey");
	if (result == ISC_R_SUCCESS) {
		validator_log(val, ISC_LOG_DEBUG(3), "keyset with trust %s",
			      dns_trust_totext(val->frdataset.trust));
		/*
		 * Only extract the dst key if the keyset is secure.
		 */
		if (val->frdataset.trust >= dns_trust_secure) {
			result = validate_helper_run(val,
						     resume_answer_with_key);
		} else {
			result = validate_async_run(val, resume_answer);
		}
	} else {
		validator_log(val, ISC_LOG_DEBUG(3),
			      "validator_callback_dnskey: got %s",
			      isc_result_totext(result));
		if (result != DNS_R_BROKENCHAIN) {
			expire_rdatasets(val);
			result = create_fetch(val, &val->siginfo->signer,
					      dns_rdatatype_dnskey,
					      fetch_callback_dnskey,
					      "validator_callback_dnskey");
			if (result == ISC_R_SUCCESS) {
				result = DNS_R_WAIT;
			}
		}
	}

cleanup:
	dns_validator_detach(&subvalidator->parent);
	dns_validator_shutdown(subvalidator);
	dns_validator_detach(&subvalidator);
	validate_async_done(val, result);
}

/*%
 * Callback when the DS record has been validated.
 *
 * Resumes validation of the zone key or the unsecure zone proof.
 */
static void
validator_callback_ds(void *arg) {
	dns_validator_t *subvalidator = (dns_validator_t *)arg;
	dns_validator_t *val = subvalidator->parent;
	isc_result_t result = subvalidator->result;

	val->subvalidator = NULL;

	if (CANCELED(val) || CANCELING(val)) {
		CLEANUP(ISC_R_CANCELED);
	}

	validator_log(val, ISC_LOG_DEBUG(3), "in validator_callback_ds");
	if (result == ISC_R_SUCCESS) {
		bool have_dsset;
		dns_name_t *name;
		validator_log(val, ISC_LOG_DEBUG(3), "%s with trust %s",
			      val->frdataset.type == dns_rdatatype_ds
				      ? "dsset"
				      : "ds non-existence",
			      dns_trust_totext(val->frdataset.trust));
		have_dsset = (val->frdataset.type == dns_rdatatype_ds);
		name = dns_fixedname_name(&val->fname);
		if ((val->attributes & VALATTR_INSECURITY) != 0 &&
		    val->frdataset.covers == dns_rdatatype_ds &&
		    NEGATIVE(&val->frdataset) &&
		    isdelegation(name, &val->frdataset, DNS_R_NCACHENXRRSET))
		{
			result = markanswer(val, "validator_callback_ds");
		} else if ((val->attributes & VALATTR_INSECURITY) != 0) {
			result = proveunsecure(val, have_dsset, false, true);
		} else {
			result = validate_async_run(val, validate_dnskey);
		}
	} else {
		validator_log(val, ISC_LOG_DEBUG(3),
			      "validator_callback_ds: got %s",
			      isc_result_totext(result));
		if (result != DNS_R_BROKENCHAIN) {
			expire_rdatasets(val);
			result = create_fetch(val, val->name, dns_rdatatype_ds,
					      fetch_callback_ds,
					      "validator_callback_ds");
			if (result == ISC_R_SUCCESS) {
				result = DNS_R_WAIT;
			}
		}
	}

cleanup:
	dns_validator_detach(&subvalidator->parent);
	dns_validator_shutdown(subvalidator);
	dns_validator_detach(&subvalidator);
	validate_async_done(val, result);
}

/*%
 * Callback when the CNAME record has been validated.
 *
 * Resumes validation of the unsecure zone proof.
 */
static void
validator_callback_cname(void *arg) {
	dns_validator_t *subvalidator = (dns_validator_t *)arg;
	dns_validator_t *val = subvalidator->parent;
	isc_result_t result;
	isc_result_t eresult = subvalidator->result;

	INSIST((val->attributes & VALATTR_INSECURITY) != 0);

	val->subvalidator = NULL;

	if (CANCELED(val) || CANCELING(val)) {
		CLEANUP(ISC_R_CANCELED);
	}

	validator_log(val, ISC_LOG_DEBUG(3), "in validator_callback_cname");
	if (eresult == ISC_R_SUCCESS) {
		validator_log(val, ISC_LOG_DEBUG(3), "cname with trust %s",
			      dns_trust_totext(val->frdataset.trust));
		result = proveunsecure(val, false, false, true);
	} else {
		if (eresult != DNS_R_BROKENCHAIN) {
			expire_rdatasets(val);
		}
		validator_log(val, ISC_LOG_DEBUG(3),
			      "validator_callback_cname: got %s",
			      isc_result_totext(eresult));
		result = DNS_R_BROKENCHAIN;
	}

cleanup:
	dns_validator_detach(&subvalidator->parent);
	dns_validator_shutdown(subvalidator);
	dns_validator_detach(&subvalidator);
	validate_async_done(val, result);
}

/*%
 * Callback for when NSEC records have been validated.
 *
 * Looks for NOQNAME, NODATA and OPTOUT proofs.
 *
 * Resumes the negative response validation by calling validate_nx().
 */
static void
validator_callback_nsec(void *arg) {
	dns_validator_t *subvalidator = (dns_validator_t *)arg;
	dns_validator_t *val = subvalidator->parent;
	dns_rdataset_t *rdataset = subvalidator->rdataset;
	isc_result_t result;
	isc_result_t eresult = subvalidator->result;
	bool exists, data;

	val->subvalidator = NULL;

	if (CANCELED(val) || CANCELING(val)) {
		CLEANUP(ISC_R_CANCELED);
	}

	validator_log(val, ISC_LOG_DEBUG(3), "in validator_callback_nsec");
	if (eresult == ISC_R_SUCCESS) {
		dns_name_t **proofs = val->proofs;
		dns_name_t *wild = dns_fixedname_name(&val->wild);

		if (rdataset->type == dns_rdatatype_nsec &&
		    rdataset->trust == dns_trust_secure &&
		    (NEEDNODATA(val) || NEEDNOQNAME(val)) &&
		    !FOUNDNODATA(val) && !FOUNDNOQNAME(val) &&
		    dns_nsec_noexistnodata(val->type, val->name,
					   subvalidator->name, rdataset,
					   &exists, &data, wild, validator_log,
					   val) == ISC_R_SUCCESS)
		{
			if (exists && !data) {
				val->attributes |= VALATTR_FOUNDNODATA;
				if (NEEDNODATA(val)) {
					proofs[DNS_VALIDATOR_NODATAPROOF] =
						subvalidator->name;
				}
			}
			if (!exists) {
				dns_name_t *closest = NULL;
				unsigned int clabels;

				val->attributes |= VALATTR_FOUNDNOQNAME;

				closest = dns_fixedname_name(&val->closest);
				clabels = dns_name_countlabels(closest);
				/*
				 * If we are validating a wildcard response
				 * clabels will not be zero.  We then need
				 * to check if the generated wildcard from
				 * dns_nsec_noexistnodata is consistent with
				 * the wildcard used to generate the response.
				 */
				if (clabels == 0 ||
				    dns_name_countlabels(wild) == clabels + 1)
				{
					val->attributes |= VALATTR_FOUNDCLOSEST;
				}
				/*
				 * The NSEC noqname proof also contains
				 * the closest encloser.
				 */
				if (NEEDNOQNAME(val)) {
					proofs[DNS_VALIDATOR_NOQNAMEPROOF] =
						subvalidator->name;
				}
			}
		}

		result = validate_nx(val, true);
	} else {
		validator_log(val, ISC_LOG_DEBUG(3),
			      "validator_callback_nsec: got %s",
			      isc_result_totext(eresult));
		switch (eresult) {
		case ISC_R_CANCELED:
		case ISC_R_SHUTTINGDOWN:
			result = eresult;
			break;
		case DNS_R_BROKENCHAIN:
			val->authfail++;
			FALLTHROUGH;
		default:
			result = validate_nx(val, true);
			break;
		}
	}

cleanup:
	dns_validator_detach(&subvalidator->parent);
	dns_validator_shutdown(subvalidator);
	dns_validator_detach(&subvalidator);
	validate_async_done(val, result);
}

/*%
 * Looks for the requested name and type in the view (zones and cache).
 *
 * Returns:
 * \li	ISC_R_SUCCESS
 * \li	ISC_R_NOTFOUND
 * \li	DNS_R_NCACHENXDOMAIN
 * \li	DNS_R_NCACHENXRRSET
 * \li	DNS_R_NXRRSET
 * \li	DNS_R_NXDOMAIN
 * \li	DNS_R_BROKENCHAIN
 */
static isc_result_t
view_find(dns_validator_t *val, dns_name_t *name, dns_rdatatype_t type) {
	dns_fixedname_t fixedname;
	dns_name_t *foundname;
	isc_result_t result;
	unsigned int options;

	disassociate_rdatasets(val);

	options = DNS_DBFIND_PENDINGOK;
	foundname = dns_fixedname_initname(&fixedname);
	result = dns_view_find(val->view, name, type, 0, options, false, false,
			       NULL, NULL, foundname, &val->frdataset,
			       &val->fsigrdataset);

	if (result == DNS_R_NXDOMAIN) {
		goto notfound;
	} else if (result != ISC_R_SUCCESS && result != DNS_R_NCACHENXDOMAIN &&
		   result != DNS_R_NCACHENXRRSET && result != DNS_R_EMPTYNAME &&
		   result != DNS_R_NXRRSET && result != ISC_R_NOTFOUND)
	{
		result = ISC_R_NOTFOUND;
		goto notfound;
	}

	return result;

notfound:
	disassociate_rdatasets(val);

	return result;
}

/*%
 * Checks to make sure we are not going to loop.  As we use a SHARED fetch
 * the validation process will stall if looping was to occur.
 */
static bool
check_deadlock(dns_validator_t *val, dns_name_t *name, dns_rdatatype_t type,
	       dns_rdataset_t *rdataset, dns_rdataset_t *sigrdataset) {
	dns_validator_t *parent;

	for (parent = val; parent != NULL; parent = parent->parent) {
		if (parent->type == type &&
		    dns_name_equal(parent->name, name) &&
		    /*
		     * As NSEC3 records are meta data you sometimes
		     * need to prove a NSEC3 record which says that
		     * itself doesn't exist.
		     */
		    (parent->type != dns_rdatatype_nsec3 || rdataset == NULL ||
		     sigrdataset == NULL || parent->message == NULL ||
		     parent->rdataset != NULL || parent->sigrdataset != NULL))
		{
			validator_log(val, ISC_LOG_DEBUG(3),
				      "continuing validation would lead to "
				      "deadlock: aborting validation");
			return true;
		}
	}
	return false;
}

/*%
 * Start a fetch for the requested name and type.
 */
static isc_result_t
create_fetch(dns_validator_t *val, dns_name_t *name, dns_rdatatype_t type,
	     isc_job_cb callback, const char *caller) {
	unsigned int fopts = 0;
	isc_result_t result;

	disassociate_rdatasets(val);

	if (check_deadlock(val, name, type, NULL, NULL)) {
		validator_log(val, ISC_LOG_DEBUG(3),
			      "deadlock found (create_fetch)");
		return DNS_R_NOVALIDSIG;
	}

	if ((val->options & DNS_VALIDATOR_NOCDFLAG) != 0) {
		fopts |= DNS_FETCHOPT_NOCDFLAG;
	}

	if ((val->options & DNS_VALIDATOR_NONTA) != 0) {
		fopts |= DNS_FETCHOPT_NONTA;
	}

	validator_logcreate(val, name, type, caller, "fetch");

	dns_validator_ref(val);
	result = dns_resolver_createfetch(
		val->view->resolver, name, type, NULL, NULL, NULL, NULL, 0,
		fopts, 0, val->qc, val->gqc, val->parent_fetch, val->loop,
		callback, val, &val->edectx, &val->frdataset,
		&val->fsigrdataset, &val->fetch);
	if (result != ISC_R_SUCCESS) {
		dns_validator_detach(&val);
	}

	return result;
}

/*%
 * Start a subvalidation process.
 */
static isc_result_t
create_validator(dns_validator_t *val, dns_name_t *name, dns_rdatatype_t type,
		 dns_rdataset_t *rdataset, dns_rdataset_t *sigrdataset,
		 isc_job_cb cb, const char *caller) {
	isc_result_t result;
	unsigned int vopts = 0;
	dns_rdataset_t *sig = NULL;

	if (sigrdataset != NULL && dns_rdataset_isassociated(sigrdataset)) {
		sig = sigrdataset;
	}

	if (check_deadlock(val, name, type, rdataset, sig)) {
		validator_log(val, ISC_LOG_DEBUG(3),
			      "deadlock found (create_validator)");
		return DNS_R_NOVALIDSIG;
	}

	/* OK to clear other options, but preserve NOCDFLAG and NONTA. */
	vopts |= (val->options &
		  (DNS_VALIDATOR_NOCDFLAG | DNS_VALIDATOR_NONTA));

	validator_logcreate(val, name, type, caller, "validator");
	result = dns_validator_create(
		val->view, name, type, rdataset, sig, NULL, vopts, val->loop,
		cb, val, val->nvalidations, val->nfails, val->qc, val->gqc,
		val->parent_fetch, &val->edectx, &val->subvalidator);
	if (result == ISC_R_SUCCESS) {
		dns_validator_attach(val, &val->subvalidator->parent);
		val->subvalidator->depth = val->depth + 1;
	}
	return result;
}

/*%
 * Try to find a key that could have signed val->siginfo among those in
 * 'rdataset'.  If found, build a dst_key_t for it and point val->key at
 * it.
 *
 * If val->key is already non-NULL, locate it in the rdataset and then
 * search past it for the *next* key that could have signed 'siginfo', then
 * set val->key to that.
 *
 * Returns ISC_R_SUCCESS if a possible matching key has been found,
 * ISC_R_NOTFOUND if not. Any other value indicates error.
 */
static isc_result_t
select_signing_key(dns_validator_t *val, dns_rdataset_t *rdataset) {
	isc_result_t result;

	if (val->key == NULL) {
		result = dns_rdataset_first(rdataset);
	} else {
		dst_key_free(&val->key);
		val->key = NULL;
		result = dns_rdataset_next(rdataset);
	}

	for (; result == ISC_R_SUCCESS; result = dns_rdataset_next(rdataset)) {
		dns_rdata_rrsig_t *siginfo = val->siginfo;
		dns_rdata_t rdata = DNS_RDATA_INIT;
		dns_rdata_dnskey_t key;
		isc_region_t r;

		dns_rdataset_current(rdataset, &rdata);
		dns_rdata_tostruct(&rdata, &key, NULL); /* can't fail */

		if (key.algorithm != siginfo->algorithm ||
		    (key.flags & DNS_KEYFLAG_REVOKE) != 0 ||
		    !dns_dnssec_iszonekey(&key))
		{
			continue;
		}

		dns_rdata_toregion(&rdata, &r);
		if (dst_region_computeid(&r) != siginfo->keyid) {
			continue;
		}

		result = dns_dnssec_keyfromrdata(&siginfo->signer, &rdata,
						 val->view->mctx, &val->key);
		/* Don't count unsupported algorithm towards max fails */
		if (result == DST_R_UNSUPPORTEDALG) {
			/* Continue with the next key */
			continue;
		}
		return result;
	}

	return ISC_R_NOTFOUND;
}

/*%
 * Get the key that generated the signature in val->siginfo.
 */
static isc_result_t
seek_dnskey(dns_validator_t *val) {
	isc_result_t result;
	dns_rdata_rrsig_t *siginfo = val->siginfo;
	unsigned int nlabels;
	int order;
	dns_namereln_t namereln;

	/*
	 * Is the signer name appropriate for this signature?
	 *
	 * The signer name must be at the same level as the owner name
	 * or closer to the DNS root.
	 */
	namereln = dns_name_fullcompare(val->name, &siginfo->signer, &order,
					&nlabels);
	if (namereln != dns_namereln_subdomain &&
	    namereln != dns_namereln_equal)
	{
		return DNS_R_CONTINUE;
	}

	if (namereln == dns_namereln_equal) {
		/*
		 * If this is a self-signed keyset, it must not be a zone key
		 * (since seek_dnskey is not called from validate_dnskey).
		 */
		if (val->rdataset->type == dns_rdatatype_dnskey) {
			return DNS_R_CONTINUE;
		}

		/*
		 * Records appearing in the parent zone at delegation
		 * points cannot be self-signed.
		 */
		if (dns_rdatatype_atparent(val->rdataset->type)) {
			return DNS_R_CONTINUE;
		}
	} else {
		/*
		 * SOA and NS RRsets can only be signed by a key with
		 * the same name.
		 */
		if (val->rdataset->type == dns_rdatatype_soa ||
		    val->rdataset->type == dns_rdatatype_ns)
		{
			const char *type;

			if (val->rdataset->type == dns_rdatatype_soa) {
				type = "SOA";
			} else {
				type = "NS";
			}
			validator_log(val, ISC_LOG_DEBUG(3),
				      "%s signer mismatch", type);
			return DNS_R_CONTINUE;
		}
	}

	/*
	 * Do we know about this key?
	 */
	result = view_find(val, &siginfo->signer, dns_rdatatype_dnskey);
	switch (result) {
	case ISC_R_SUCCESS:
		/*
		 * We have an rrset for the given keyname.
		 */
		val->keyset = &val->frdataset;
		if (DNS_TRUST_PENDING(val->frdataset.trust) ||
		    DNS_TRUST_ANSWER(val->frdataset.trust))
		{
			/*
			 * We know the key but haven't validated it yet, or
			 * we had a key with trust level "answer" and
			 * a DS record for the zone has now been added.
			 */
			RETERR(create_validator(
				val, &siginfo->signer, dns_rdatatype_dnskey,
				&val->frdataset, &val->fsigrdataset,
				validator_callback_dnskey, "seek_dnskey"));
			return DNS_R_WAIT;
		} else if (val->frdataset.trust < dns_trust_secure) {
			/*
			 * The key is legitimately insecure.  There's no
			 * point in even attempting verification.
			 */
			val->key = NULL;
			result = ISC_R_SUCCESS;
		} else {
			/*
			 * See if we've got the key used in the signature.
			 */
			validator_log(val, ISC_LOG_DEBUG(3),
				      "keyset with trust %s",
				      dns_trust_totext(val->frdataset.trust));

			/*
			 * Cleanup before passing control to the offload thread
			 */
			if (dns_rdataset_isassociated(&val->frdataset) &&
			    val->keyset != &val->frdataset)
			{
				dns_rdataset_disassociate(&val->frdataset);
			}
			dns_rdataset_cleanup(&val->fsigrdataset);

			return validate_helper_run(val, resume_answer_with_key);
		}
		break;

	case ISC_R_NOTFOUND:
		/*
		 * We don't know anything about this key.
		 */
		RETERR(create_fetch(val, &siginfo->signer, dns_rdatatype_dnskey,
				    fetch_callback_dnskey, "seek_dnskey"));
		return DNS_R_WAIT;

	case DNS_R_NCACHENXDOMAIN:
	case DNS_R_NCACHENXRRSET:
	case DNS_R_EMPTYNAME:
	case DNS_R_NXDOMAIN:
	case DNS_R_NXRRSET:
		/*
		 * This key doesn't exist.
		 */
		result = DNS_R_CONTINUE;
		break;

	case DNS_R_BROKENCHAIN:
		return result;

	default:
		break;
	}

	if (val->keyset != &val->frdataset) {
		dns_rdataset_cleanup(&val->frdataset);
	}
	dns_rdataset_cleanup(&val->fsigrdataset);

	return result;
}

/*
 * Compute the tag for a key represented in a DNSKEY rdata.
 */
static dns_keytag_t
compute_keytag(dns_rdata_t *rdata) {
	isc_region_t r;

	dns_rdata_toregion(rdata, &r);
	return dst_region_computeid(&r);
}

static bool
over_max_validations(dns_validator_t *val) {
	if (val->nvalidations == NULL ||
	    isc_counter_used(val->nvalidations) <
		    isc_counter_getlimit(val->nvalidations))
	{
		return false;
	}

	/* The attribute is set only on failure */
	val->attributes |= VALATTR_MAXVALIDATIONS;
	return true;
}

static void
consume_validation(dns_validator_t *val) {
	if (val->nvalidations == NULL) {
		return;
	}
	(void)isc_counter_increment(val->nvalidations);
}

static bool
over_max_fails(dns_validator_t *val) {
	if (val->nfails == NULL ||
	    isc_counter_used(val->nfails) < isc_counter_getlimit(val->nfails))
	{
		return false;
	}

	/* The attribute is set only on failure */
	val->attributes |= VALATTR_MAXVALIDATIONFAILS;
	return true;
}

static void
consume_validation_fail(dns_validator_t *val) {
	if (val->nfails == NULL) {
		return;
	}
	(void)isc_counter_increment(val->nfails);
}

/*%
 * Is the DNSKEY rrset in val->rdataset self-signed?
 */
static isc_result_t
selfsigned_dnskey(dns_validator_t *val) {
	dns_rdataset_t *rdataset = val->rdataset;
	dns_rdataset_t *sigrdataset = val->sigrdataset;
	dns_name_t *name = val->name;
	isc_result_t result;
	isc_mem_t *mctx = val->view->mctx;
	bool match = false;

	if (rdataset->type != dns_rdatatype_dnskey) {
		return DNS_R_NOKEYMATCH;
	}

	DNS_RDATASET_FOREACH(rdataset) {
		dns_rdata_t keyrdata = DNS_RDATA_INIT;
		dns_rdata_dnskey_t key;
		dns_rdata_rrsig_t sig;
		dns_keytag_t keytag;

		dns_rdataset_current(rdataset, &keyrdata);
		result = dns_rdata_tostruct(&keyrdata, &key, NULL);
		RUNTIME_CHECK(result == ISC_R_SUCCESS);
		keytag = compute_keytag(&keyrdata);

		DNS_RDATASET_FOREACH(sigrdataset) {
			dns_rdata_t sigrdata = DNS_RDATA_INIT;
			dst_key_t *dstkey = NULL;

			dns_rdataset_current(sigrdataset, &sigrdata);
			result = dns_rdata_tostruct(&sigrdata, &sig, NULL);
			RUNTIME_CHECK(result == ISC_R_SUCCESS);

			if (sig.algorithm != key.algorithm ||
			    sig.keyid != keytag ||
			    !dns_name_equal(name, &sig.signer))
			{
				continue;
			}

			/*
			 * If the REVOKE bit is not set we have a
			 * theoretically self-signed DNSKEY RRset;
			 * this will be verified later.
			 *
			 * We don't return the answer yet, though,
			 * because we need to check the remaining keys
			 * and possbly remove them if they're revoked.
			 */
			if ((key.flags & DNS_KEYFLAG_REVOKE) == 0) {
				match = true;
				break;
			}

			/*
			 * If this RRset is pending and it is trusted,
			 * see if it was self signed by this DNSKEY.
			 */
			if (DNS_TRUST_PENDING(rdataset->trust) &&
			    dns_view_istrusted(val->view, name, &key))
			{
				result = dns_dnssec_keyfromrdata(
					name, &keyrdata, mctx, &dstkey);
				if (result == DST_R_UNSUPPORTEDALG) {
					/* don't count towards max fails */
					break; /* continue with next key */
				} else if (result != ISC_R_SUCCESS) {
					consume_validation(val);
					if (over_max_fails(val)) {
						return ISC_R_QUOTA;
					}
					consume_validation_fail(val);
					break; /* continue with next key */
				}

				if (over_max_validations(val)) {
					dst_key_free(&dstkey);
					return ISC_R_QUOTA;
				}
				result = dns_dnssec_verify(name, rdataset,
							   dstkey, true, mctx,
							   &sigrdata, NULL);
				switch (result) {
				case DNS_R_SIGFUTURE:
				case DNS_R_SIGEXPIRED:
					/*
					 * Temporal errors don't count towards
					 * max validations nor max fails.
					 */
					break;
				case ISC_R_SUCCESS:
					consume_validation(val);
					/*
					 * The key with the REVOKE flag has
					 * self signed the RRset so it is no
					 * good.
					 */
					dns_view_untrust(val->view, name, &key);
					break;
				default:
					consume_validation(val);
					if (over_max_fails(val)) {
						dst_key_free(&dstkey);
						return ISC_R_QUOTA;
					}
					consume_validation_fail(val);
					break;
				}

				dst_key_free(&dstkey);
			} else if (rdataset->trust >= dns_trust_secure) {
				/*
				 * We trust this RRset so if the key is
				 * marked revoked remove it.
				 */
				dns_view_untrust(val->view, name, &key);
			}
		}
	}

	if (!match) {
		return DNS_R_NOKEYMATCH;
	}

	return ISC_R_SUCCESS;
}

/*%
 * Attempt to verify the rdataset using the given key and rdata (RRSIG).
 * The signature was good and from a wildcard record and the QNAME does
 * not match the wildcard we need to look for a NOQNAME proof.
 *
 * Returns:
 * \li	ISC_R_SUCCESS if the verification succeeds.
 * \li	Others if the verification fails.
 */
static isc_result_t
verify(dns_validator_t *val, dst_key_t *key, dns_rdata_t *rdata,
       uint16_t keyid) {
	isc_result_t result;
	dns_fixedname_t fixed;
	bool ignore = false;
	dns_name_t *wild;

	val->attributes |= VALATTR_TRIEDVERIFY;
	wild = dns_fixedname_initname(&fixed);
	if (over_max_validations(val)) {
		return ISC_R_QUOTA;
	}
again:
	result = dns_dnssec_verify(val->name, val->rdataset, key, ignore,
				   val->view->mctx, rdata, wild);
	if ((result == DNS_R_SIGEXPIRED || result == DNS_R_SIGFUTURE) &&
	    val->view->acceptexpired)
	{
		ignore = true;
		goto again;
	}

	if (ignore && (result == ISC_R_SUCCESS || result == DNS_R_FROMWILDCARD))
	{
		validator_log(val, ISC_LOG_INFO,
			      "accepted expired %sRRSIG (keyid=%u)",
			      (result == DNS_R_FROMWILDCARD) ? "wildcard " : "",
			      keyid);
	} else if (result == DNS_R_SIGEXPIRED || result == DNS_R_SIGFUTURE) {
		validator_log(val, ISC_LOG_INFO,
			      "verify failed due to bad signature (keyid=%u): "
			      "%s",
			      keyid, isc_result_totext(result));
	} else {
		validator_log(val, ISC_LOG_DEBUG(3),
			      "verify rdataset (keyid=%u): %s", keyid,
			      isc_result_totext(result));
	}
	if (result == DNS_R_FROMWILDCARD) {
		if (!dns_name_equal(val->name, wild)) {
			dns_name_t *closest;
			unsigned int labels;

			/*
			 * Compute the closest encloser in case we need it
			 * for the NSEC3 NOQNAME proof.
			 */
			closest = dns_fixedname_name(&val->closest);
			dns_name_copy(wild, closest);
			labels = dns_name_countlabels(closest) - 1;
			dns_name_getlabelsequence(closest, 1, labels, closest);
			val->attributes |= VALATTR_NEEDNOQNAME;
		}
		result = ISC_R_SUCCESS;
	}

	switch (result) {
	case DNS_R_SIGFUTURE:
	case DNS_R_SIGEXPIRED:
		/*
		 * Temporal errors don't count towards max validations nor max
		 * fails.
		 */
		validator_addede(val,
				 result == DNS_R_SIGEXPIRED
					 ? DNS_EDE_SIGNATUREEXPIRED
					 : DNS_EDE_SIGNATURENOTYETVALID,
				 NULL);
		break;
	case ISC_R_SUCCESS:
		consume_validation(val);
		break;
	default:
		consume_validation(val);
		if (over_max_fails(val)) {
			result = ISC_R_QUOTA;
			break;
		}
		consume_validation_fail(val);
		break;
	}
	return result;
}

/*%
 * Attempts positive response validation of a normal RRset.
 *
 * Returns:
 * \li	ISC_R_SUCCESS	Validation completed successfully
 * \li	DNS_R_WAIT	Validation has started but is waiting
 *			for an event.
 * \li	Other return codes are possible and all indicate failure.
 */

static void
validate_answer_iter_next(void *arg);
static void
validate_answer_process(void *arg);
static void
validate_answer_iter_done(dns_validator_t *val, isc_result_t result);

static void
validator_cancel_finish(dns_validator_t *validator);

static void
validate_answer_iter_start(dns_validator_t *val) {
	isc_result_t result = ISC_R_SUCCESS;

	/*
	 * Caller must be holding the validator lock.
	 */

	val->attributes &= ~VALATTR_OFFLOADED;
	if (CANCELING(val)) {
		validator_cancel_finish(val);
		CLEANUP(ISC_R_CANCELED);
	}

	if (val->resume) {
		/* We already have a sigrdataset. */
		result = ISC_R_SUCCESS;
		validator_log(val, ISC_LOG_DEBUG(3), "resuming validate");
	} else {
		result = dns_rdataset_first(val->sigrdataset);
	}

cleanup:
	if (result != ISC_R_SUCCESS) {
		validate_answer_iter_done(val, result);
		return;
	}

	val->unsupported_algorithm = 0;
	val->unsupported_digest = 0;
	result = validate_async_run(val, validate_answer_process);
	INSIST(result == DNS_R_WAIT);
}

static void
validate_answer_iter_next(void *arg) {
	dns_validator_t *val = arg;
	isc_result_t result;

	val->attributes &= ~VALATTR_OFFLOADED;
	if (CANCELING(val)) {
		validator_cancel_finish(val);
		CLEANUP(ISC_R_CANCELED);
	}

	val->resume = false;
	result = dns_rdataset_next(val->sigrdataset);

cleanup:
	if (result != ISC_R_SUCCESS) {
		validate_answer_iter_done(val, result);
		return;
	}

	(void)validate_async_run(val, validate_answer_process);
}

static void
validate_answer_finish(void *arg);

static void
validate_answer_signing_key_done(void *arg);

static void
validate_answer_signing_key(void *arg) {
	dns_validator_t *val = arg;
	isc_result_t result;

	if (CANCELED(val) || CANCELING(val)) {
		val->result = ISC_R_CANCELED;
	} else {
		val->result = verify(val, val->key, &val->rdata,
				     val->siginfo->keyid);
	}

	switch (val->result) {
	case ISC_R_CANCELED:	 /* Validation was canceled */
	case ISC_R_SHUTTINGDOWN: /* Server shutting down */
	case ISC_R_QUOTA:	 /* Validation fails quota reached */
	case ISC_R_SUCCESS: /* We found our valid signature, we are done! */
		if (val->key != NULL) {
			dst_key_free(&val->key);
			val->key = NULL;
		}

		break;
	default:
		/* Select next signing key */
		result = select_signing_key(val, val->keyset);
		if (result == ISC_R_SUCCESS) {
			INSIST(val->key != NULL);
		} else if (result == ISC_R_NOTFOUND) {
			INSIST(val->key == NULL);
		} else {
			val->result = result;
			if (over_max_fails(val)) {
				INSIST(val->key == NULL);
				val->result = ISC_R_QUOTA;
			}
			consume_validation_fail(val);
		}
		break;
	}

	(void)validate_async_run(val, validate_answer_signing_key_done);
}

static void
validate_answer_signing_key_done(void *arg) {
	dns_validator_t *val = arg;

	val->attributes &= ~VALATTR_OFFLOADED;
	if (CANCELING(val)) {
		validator_cancel_finish(val);
		val->result = ISC_R_CANCELED;
	} else if (val->key != NULL) {
		/* Process with next key if we selected one */
		(void)validate_helper_run(val, validate_answer_signing_key);
		return;
	}

	validate_answer_finish(val);
}

static void
validate_answer_process(void *arg) {
	dns_validator_t *val = arg;
	isc_result_t result;

	val->attributes &= ~VALATTR_OFFLOADED;
	if (CANCELING(val)) {
		validator_cancel_finish(val);
		CLEANUP(ISC_R_CANCELED);
	}

	dns_rdata_reset(&val->rdata);

	dns_rdataset_current(val->sigrdataset, &val->rdata);
	if (val->siginfo == NULL) {
		val->siginfo = isc_mem_get(val->view->mctx,
					   sizeof(*val->siginfo));
	}
	CHECK(dns_rdata_tostruct(&val->rdata, val->siginfo, NULL));

	/*
	 * At this point we could check that the signature algorithm
	 * was known and "sufficiently good".
	 */
	if (!dns_resolver_algorithm_supported(
		    val->view->resolver, &val->siginfo->signer,
		    val->siginfo->algorithm, val->siginfo->signature,
		    val->siginfo->siglen))
	{
		if (val->unsupported_algorithm == 0) {
			val->unsupported_algorithm = val->siginfo->algorithm;
			/*
			 * XXXMPA save PRIVATEOID/PRIVATEDNS identifier here
			 */
		}
		goto next_key;
	}

	if (!val->resume) {
		result = seek_dnskey(val);
		switch (result) {
		case ISC_R_SUCCESS:
			break;
		case DNS_R_CONTINUE:
			goto next_key;
		case DNS_R_WAIT:
			goto cleanup;
		default:
			goto cleanup;
		}
	}

	/*
	 * There isn't a secure DNSKEY for this signature so move
	 * onto the next RRSIG.
	 */
	if (val->key == NULL) {
		val->resume = false;
		goto next_key;
	}

	(void)validate_helper_run(val, validate_answer_signing_key);
	return;

next_key:
	result = validate_async_run(val, validate_answer_iter_next);

cleanup:
	validate_async_done(val, result);
}

static void
validate_answer_finish(void *arg) {
	dns_validator_t *val = arg;
	isc_result_t result = ISC_R_UNSET;

	if (val->result == ISC_R_SUCCESS) {
		dns_rdataset_trimttl(val->rdataset, val->sigrdataset,
				     val->siginfo, val->start,
				     val->view->acceptexpired);
	}

	if (val->key != NULL) {
		dst_key_free(&val->key);
		val->key = NULL;
	}
	if (val->keyset != NULL) {
		dns_rdataset_disassociate(val->keyset);
		val->keyset = NULL;
	}

	switch (val->result) {
	case ISC_R_CANCELED:
		validator_log(val, ISC_LOG_DEBUG(3), "validation was canceled");
		validate_async_done(val, val->result);
		return;
	case ISC_R_SHUTTINGDOWN:
		validator_log(val, ISC_LOG_DEBUG(3), "server is shutting down");
		validate_async_done(val, val->result);
		return;
	case ISC_R_QUOTA:
		if (MAXVALIDATIONS(val)) {
			validator_log(val, ISC_LOG_DEBUG(3),
				      "maximum number of validations exceeded");
		} else if (MAXVALIDATIONFAILS(val)) {
			validator_log(val, ISC_LOG_DEBUG(3),
				      "maximum number of validation failures "
				      "exceeded");
		} else {
			validator_log(
				val, ISC_LOG_DEBUG(3),
				"unknown error: validation quota exceeded");
		}
		validate_async_done(val, val->result);
		return;
	default:
		break;
	}

	if (NEEDNOQNAME(val)) {
		if (val->message == NULL) {
			validator_log(val, ISC_LOG_DEBUG(3),
				      "no message available for noqname proof");
			validate_async_done(val, DNS_R_NOVALIDSIG);
			return;
		}

		validator_log(val, ISC_LOG_DEBUG(3),
			      "looking for noqname proof");
		result = validate_nx(val, false);
		validate_async_done(val, result);
		return;
	}

	if (val->result == ISC_R_SUCCESS) {
		marksecure(val);
		validator_log(val, ISC_LOG_DEBUG(3),
			      "marking as secure, noqname proof not needed");
		validate_async_done(val, val->result);
		return;
	}

	validator_log(val, ISC_LOG_DEBUG(3), "verify failure: %s",
		      isc_result_totext(val->result));
	(void)validate_async_run(val, validate_answer_iter_next);
}

static void
validate_answer_iter_done(dns_validator_t *val, isc_result_t result) {
	if (result != ISC_R_NOMORE) {
		validator_log(val, ISC_LOG_DEBUG(3),
			      "failed to iterate signatures: %s",
			      isc_result_totext(result));
		validate_async_done(val, result);
		return;
	}

	if (result != ISC_R_SUCCESS && result != DNS_R_WAIT) {
		validate_extendederror(val);
	}

	validator_log(val, ISC_LOG_INFO, "no valid signature found");
	validate_async_done(val, val->result);
}

static void
resume_answer(void *arg) {
	dns_validator_t *val = arg;

	val->resume = true;
	validate_answer_iter_start(val);
}

static void
validate_answer(void *arg) {
	dns_validator_t *val = arg;
	val->resume = false;
	validate_answer_iter_start(val);
}

static isc_result_t
validate_async_run(dns_validator_t *val, isc_job_cb cb) {
	isc_async_run(val->loop, cb, val);
	return DNS_R_WAIT;
}

static isc_result_t
validate_helper_run(dns_validator_t *val, isc_job_cb cb) {
	val->attributes |= VALATTR_OFFLOADED;
	isc_helper_run(val->loop, cb, val);
	return DNS_R_WAIT;
}

static void
validate_async_done(dns_validator_t *val, isc_result_t result) {
	if (result == DNS_R_NOVALIDSIG &&
	    (val->attributes & VALATTR_TRIEDVERIFY) == 0)
	{
		isc_result_t saved_result = result;
		validator_log(val, ISC_LOG_DEBUG(3),
			      "falling back to insecurity proof");
		result = proveunsecure(val, false, false, false);
		if (result == DNS_R_NOTINSECURE) {
			result = saved_result;
		}
	}

	if (result != DNS_R_WAIT) {
		/* We are still continuing */
		validator_done(val, result);
		dns_validator_detach(&val);
	}
}

/*%
 * Check whether this DNSKEY (keyrdata) signed the DNSKEY RRset
 * (val->rdataset).
 */
static isc_result_t
check_signer(dns_validator_t *val, dns_rdata_t *keyrdata, uint16_t keyid,
	     dns_secalg_t algorithm) {
	isc_result_t result;
	dns_rdata_rrsig_t sig;
	dst_key_t *dstkey = NULL;
	dns_rdataset_t rdataset = DNS_RDATASET_INIT;

	RETERR(dns_dnssec_keyfromrdata(val->name, keyrdata, val->view->mctx,
				       &dstkey));

	dns_rdataset_clone(val->sigrdataset, &rdataset);
	DNS_RDATASET_FOREACH(&rdataset) {
		dns_rdata_t rdata = DNS_RDATA_INIT;

		dns_rdataset_current(&rdataset, &rdata);
		result = dns_rdata_tostruct(&rdata, &sig, NULL);
		RUNTIME_CHECK(result == ISC_R_SUCCESS);
		if (keyid != sig.keyid || algorithm != sig.algorithm) {
			continue;
		}
		result = verify(val, dstkey, &rdata, sig.keyid);
		if (result == ISC_R_SUCCESS || result == ISC_R_QUOTA) {
			dns_rdataset_disassociate(&rdataset);
			dst_key_free(&dstkey);
			return result;
		}
	}

	dns_rdataset_disassociate(&rdataset);
	dst_key_free(&dstkey);

	return ISC_R_NOMORE;
}

/*
 * get_dsset() is called to look up a DS RRset corresponding to the name
 * of a DNSKEY record, either in the cache or, if necessary, by starting a
 * fetch. This is done in the context of validating a zone key to build a
 * trust chain.
 *
 * Returns:
 * \li	ISC_R_COMPLETE		a DS has not been found; the caller should
 *				stop trying to validate the zone key and
 *				return the result code in '*resp'.
 * \li	DNS_R_CONTINUE		a DS has been found and the caller may
 * 				continue the zone key validation.
 */
static isc_result_t
get_dsset(dns_validator_t *val, dns_name_t *tname, isc_result_t *resp) {
	isc_result_t result;

	result = view_find(val, tname, dns_rdatatype_ds);
	switch (result) {
	case ISC_R_SUCCESS:
		/*
		 * We have a DS RRset.
		 */
		val->dsset = &val->frdataset;
		if (DNS_TRUST_PENDING(val->frdataset.trust) ||
		    DNS_TRUST_ANSWER(val->frdataset.trust))
		{
			/*
			 * ... which is signed but not yet validated.
			 */
			result = create_validator(
				val, tname, dns_rdatatype_ds, &val->frdataset,
				&val->fsigrdataset, validator_callback_ds,
				"get_dsset");
			*resp = DNS_R_WAIT;
			if (result != ISC_R_SUCCESS) {
				*resp = result;
			}
			return ISC_R_COMPLETE;
		}
		break;

	case ISC_R_NOTFOUND:
		/*
		 * We don't have the DS.  Find it.
		 */
		result = create_fetch(val, tname, dns_rdatatype_ds,
				      fetch_callback_ds, "validate_dnskey");
		*resp = DNS_R_WAIT;
		if (result != ISC_R_SUCCESS) {
			*resp = result;
		}
		return ISC_R_COMPLETE;

	case DNS_R_NCACHENXDOMAIN:
	case DNS_R_NCACHENXRRSET:
	case DNS_R_EMPTYNAME:
	case DNS_R_NXDOMAIN:
	case DNS_R_NXRRSET:
	case DNS_R_CNAME:
		/*
		 * The DS does not exist.
		 */
		disassociate_rdatasets(val);
		validator_log(val, ISC_LOG_DEBUG(2), "no DS record");
		*resp = DNS_R_NOVALIDSIG;
		return ISC_R_COMPLETE;

	case DNS_R_BROKENCHAIN:
		*resp = result;
		return ISC_R_COMPLETE;

	default:
		break;
	}

	return DNS_R_CONTINUE;
}

static void
validate_dnskey_dsset_done(dns_validator_t *val, isc_result_t result) {
	switch (result) {
	case ISC_R_CANCELED:
	case ISC_R_SHUTTINGDOWN:
		/* Abort, abort, abort! */
		break;
	case ISC_R_SUCCESS:
		marksecure(val);
		validator_log(val, ISC_LOG_DEBUG(3), "marking as secure (DS)");
		break;
	case ISC_R_NOMORE:
		if (val->unsupported_algorithm != 0 ||
		    val->unsupported_digest != 0)
		{
			validator_log(val, ISC_LOG_DEBUG(3),
				      "no supported algorithm/digest (DS)");
			result = markanswer(val, "validate_dnskey (3)");
			validate_extendederror(val);
			break;
		}
		FALLTHROUGH;
	default:
		validator_log(val, ISC_LOG_INFO,
			      "no valid signature found (DS)");
		result = DNS_R_NOVALIDSIG;
		break;
	}

	if (val->dsset == &val->fdsset) {
		val->dsset = NULL;
		dns_rdataset_disassociate(&val->fdsset);
	}

	validate_async_done(val, result);
}

static isc_result_t
validate_dnskey_dsset(dns_validator_t *val) {
	dns_rdata_t dsrdata = DNS_RDATA_INIT;
	dns_rdata_t keyrdata = DNS_RDATA_INIT;
	isc_result_t result;
	dns_rdata_ds_t ds;
	dns_rdata_dnskey_t key;
	unsigned char *data = 0;
	unsigned int datalen = 0;

	dns_rdata_reset(&dsrdata);
	dns_rdataset_current(val->dsset, &dsrdata);
	result = dns_rdata_tostruct(&dsrdata, &ds, NULL);
	RUNTIME_CHECK(result == ISC_R_SUCCESS);

	if (ds.digest_type == DNS_DSDIGEST_SHA1 && val->digest_sha1 == false) {
		return DNS_R_BADALG;
	}

	if (!dns_resolver_ds_digest_supported(val->view->resolver, val->name,
					      ds.digest_type))
	{
		if (val->unsupported_digest == 0) {
			val->unsupported_digest = ds.digest_type;
		}
		return DNS_R_BADALG;
	}

	switch (ds.algorithm) {
	case DNS_KEYALG_PRIVATEDNS:
	case DNS_KEYALG_PRIVATEOID:
		switch (ds.digest_type) {
#if defined(DNS_DSDIGEST_SHA256PRIVATE) && defined(DNS_DSDIGEST_SHA384PRIVATE)
		case DNS_DSDIGEST_SHA256PRIVATE:
		case DNS_DSDIGEST_SHA384PRIVATE:
			data = ds.digest;
			datalen = ds.length;
			break;
#endif
		default:
			break;
		}
		break;
	default:
		break;
	}

	if (data != NULL || (ds.algorithm != DNS_KEYALG_PRIVATEDNS &&
			     ds.algorithm != DNS_KEYALG_PRIVATEOID))
	{
		if (!dns_resolver_algorithm_supported(val->view->resolver,
						      val->name, ds.algorithm,
						      data, datalen))
		{
			if (val->unsupported_algorithm == 0) {
				val->unsupported_algorithm = ds.algorithm;
			}
			return DNS_R_BADALG;
		}
	}

	/*
	 * Find the DNSKEY matching the DS...
	 */
	result = dns_dnssec_matchdskey(val->name, &dsrdata, val->rdataset,
				       &keyrdata);
	if (result != ISC_R_SUCCESS) {
		validator_log(val, ISC_LOG_DEBUG(3), "no DNSKEY matching DS");
		validator_addede(val, DNS_EDE_DNSKEYMISSING,
				 "DNSKEY found but not matching DS");
		return DNS_R_NOKEYMATCH;
	}

	/*
	 * Figure out if the private algorithm is supported now that we have
	 * found a matching dnskey.
	 */
	dns_rdata_tostruct(&keyrdata, &key, NULL);
	if (data == NULL && (ds.algorithm == DNS_KEYALG_PRIVATEDNS ||
			     ds.algorithm == DNS_KEYALG_PRIVATEOID))
	{
		if (!dns_resolver_algorithm_supported(val->view->resolver,
						      val->name, key.algorithm,
						      key.data, key.datalen))
		{
			if (val->unsupported_algorithm == 0) {
				val->unsupported_algorithm = key.algorithm;
				/*
				 * XXXMPA Save PRIVATEOID / PRIVATEDNS here.
				 */
			}
			return DNS_R_BADALG;
		}
	}

	/*
	 * ... and check that it signed the DNSKEY RRset.
	 */
	result = check_signer(val, &keyrdata, ds.key_tag, ds.algorithm);
	if (result != ISC_R_SUCCESS) {
		validator_log(val, ISC_LOG_DEBUG(3),
			      "no RRSIG matching DS key");

		return DNS_R_NOVALIDSIG;
	}

	return ISC_R_SUCCESS;
}

static void
validate_dnskey_dsset_next_done(void *arg);

static void
validate_dnskey_dsset_next(void *arg) {
	dns_validator_t *val = arg;

	if (CANCELED(val) || CANCELING(val)) {
		val->result = ISC_R_CANCELED;
	} else {
		val->result = dns_rdataset_next(val->dsset);
	}

	if (val->result == ISC_R_SUCCESS) {
		/* continue async run */
		val->result = validate_dnskey_dsset(val);
	}

	validate_async_run(val, validate_dnskey_dsset_next_done);
}

static void
validate_dnskey_dsset_next_done(void *arg) {
	dns_validator_t *val = arg;
	isc_result_t result = val->result;

	val->attributes &= ~VALATTR_OFFLOADED;
	if (CANCELING(val)) {
		validator_cancel_finish(val);
		result = ISC_R_CANCELED;
	}

	switch (result) {
	case ISC_R_CANCELED:
	case ISC_R_SHUTTINGDOWN:
		/* Abort, abort, abort! */
		break;
	case ISC_R_SUCCESS:
	case ISC_R_NOMORE:
		/* We are done */
		break;
	default:
		/* Continue validation until we have success or no more data */
		(void)validate_helper_run(val, validate_dnskey_dsset_next);
		return;
	}

	validate_dnskey_dsset_done(val, result);
	return;
}

static void
validate_dnskey_dsset_first(dns_validator_t *val) {
	isc_result_t result;

	if (CANCELED(val) || CANCELING(val)) {
		result = ISC_R_CANCELED;
	} else {
		result = dns_rdataset_first(val->dsset);
	}

	if (result == ISC_R_SUCCESS) {
		/* continue async run */
		result = validate_dnskey_dsset(val);
		if (result != ISC_R_SUCCESS) {
			(void)validate_helper_run(val,
						  validate_dnskey_dsset_next);
			return;
		}
	}

	validate_dnskey_dsset_done(val, result);
}

static void
validate_dnskey(void *arg) {
	dns_validator_t *val = arg;
	isc_result_t result = ISC_R_SUCCESS;
	dns_keynode_t *keynode = NULL;
	dns_rdata_ds_t ds;

	if (CANCELED(val) || CANCELING(val)) {
		CLEANUP(ISC_R_CANCELED);
	}

	/*
	 * If we don't already have a DS RRset, check to see if there's
	 * a DS style trust anchor configured for this key.
	 */
	if (val->dsset == NULL) {
		result = dns_keytable_find(val->keytable, val->name, &keynode);
		if (result == ISC_R_SUCCESS) {
			if (dns_keynode_dsset(keynode, &val->fdsset)) {
				val->dsset = &val->fdsset;
			}
			dns_keynode_detach(&keynode);
		}
	}

	/*
	 * No trust anchor for this name, so we look up the DS at the parent.
	 */
	if (val->dsset == NULL) {
		isc_result_t tresult = ISC_R_SUCCESS;

		/*
		 * If this is the root name and there was no trust anchor,
		 * we can give up now, since there's no DS at the root.
		 */
		if (dns_name_equal(val->name, dns_rootname)) {
			if ((val->attributes & VALATTR_TRIEDVERIFY) != 0) {
				validator_log(val, ISC_LOG_DEBUG(3),
					      "root key failed to validate");
			} else {
				validator_log(val, ISC_LOG_DEBUG(3),
					      "no trusted root key");
			}
			CLEANUP(DNS_R_NOVALIDSIG);
		}

		/*
		 * Look up the DS RRset for this name.
		 */
		result = get_dsset(val, val->name, &tresult);
		if (result == ISC_R_COMPLETE) {
			result = tresult;
			goto cleanup;
		}
	}

	/*
	 * We have a DS set.
	 */
	INSIST(val->dsset != NULL);

	if (val->dsset->trust < dns_trust_secure) {
		result = markanswer(val, "validate_dnskey (2)");
		goto cleanup;
	}

	/*
	 * Look through the DS record and find the keys that can sign the
	 * key set and the matching signature.  For each such key, attempt
	 * verification.
	 */
	val->unsupported_algorithm = 0;
	val->unsupported_digest = 0;

	/*
	 * If DNS_DSDIGEST_SHA256 or DNS_DSDIGEST_SHA384 is present we
	 * are required to prefer it over DNS_DSDIGEST_SHA1.  This in
	 * practice means that we need to ignore DNS_DSDIGEST_SHA1 if a
	 * DNS_DSDIGEST_SHA256 or DNS_DSDIGEST_SHA384 is present.
	 */
	val->digest_sha1 = true;
	DNS_RDATASET_FOREACH(val->dsset) {
		dns_rdata_t dsrdata = DNS_RDATA_INIT;
		dns_rdataset_current(val->dsset, &dsrdata);
		result = dns_rdata_tostruct(&dsrdata, &ds, NULL);
		RUNTIME_CHECK(result == ISC_R_SUCCESS);

		if (!dns_resolver_ds_digest_supported(
			    val->view->resolver, val->name, ds.digest_type))
		{
			continue;
		}

		if (!dns_resolver_algorithm_supported(val->view->resolver,
						      val->name, ds.algorithm,
						      NULL, 0))
		{
			continue;
		}

		if ((ds.digest_type == DNS_DSDIGEST_SHA256 &&
		     ds.length == ISC_SHA256_DIGESTLENGTH) ||
		    (ds.digest_type == DNS_DSDIGEST_SHA384 &&
		     ds.length == ISC_SHA384_DIGESTLENGTH))
		{
			val->digest_sha1 = false;
			break;
		}
	}

	validate_dnskey_dsset_first(val);
	return;

cleanup:
	if (val->dsset == &val->fdsset) {
		val->dsset = NULL;
		dns_rdataset_disassociate(&val->fdsset);
	}
	validate_async_done(val, result);
}

/*%
 * val_rdataset_first and val_rdataset_next provide iteration methods
 * that hide whether we are iterating across the AUTHORITY section of
 * a message, or a negative cache rdataset.
 */
static isc_result_t
val_rdataset_first(dns_validator_t *val, dns_name_t **namep,
		   dns_rdataset_t **rdatasetp) {
	dns_message_t *message = val->message;
	isc_result_t result = ISC_R_SUCCESS;

	REQUIRE(rdatasetp != NULL);
	REQUIRE(namep != NULL);
	if (message == NULL) {
		REQUIRE(*rdatasetp != NULL);
		REQUIRE(*namep != NULL);
	} else {
		REQUIRE(*rdatasetp == NULL);
		REQUIRE(*namep == NULL);
	}

	if (message != NULL) {
		RETERR(dns_message_firstname(message, DNS_SECTION_AUTHORITY));
		dns_message_currentname(message, DNS_SECTION_AUTHORITY, namep);
		*rdatasetp = ISC_LIST_HEAD((*namep)->list);
		INSIST(*rdatasetp != NULL);
	} else {
		result = dns_rdataset_first(val->rdataset);
		if (result == ISC_R_SUCCESS) {
			dns_ncache_current(val->rdataset, *namep, *rdatasetp);
		}
	}

	return result;
}

static isc_result_t
val_rdataset_next(dns_validator_t *val, dns_name_t **namep,
		  dns_rdataset_t **rdatasetp) {
	dns_message_t *message = val->message;
	isc_result_t result = ISC_R_SUCCESS;

	REQUIRE(rdatasetp != NULL && *rdatasetp != NULL);
	REQUIRE(namep != NULL && *namep != NULL);

	if (message != NULL) {
		dns_rdataset_t *rdataset = *rdatasetp;
		rdataset = ISC_LIST_NEXT(rdataset, link);
		if (rdataset == NULL) {
			*namep = NULL;
			result = dns_message_nextname(message,
						      DNS_SECTION_AUTHORITY);
			if (result == ISC_R_SUCCESS) {
				dns_message_currentname(
					message, DNS_SECTION_AUTHORITY, namep);
				rdataset = ISC_LIST_HEAD((*namep)->list);
				INSIST(rdataset != NULL);
			}
		}
		*rdatasetp = rdataset;
	} else {
		dns_rdataset_disassociate(*rdatasetp);
		result = dns_rdataset_next(val->rdataset);
		if (result == ISC_R_SUCCESS) {
			dns_ncache_current(val->rdataset, *namep, *rdatasetp);
		}
	}
	return result;
}

/*%
 * Look for NODATA at the wildcard and NOWILDCARD proofs in the
 * previously validated NSEC records.  As these proofs are mutually
 * exclusive we stop when one is found.
 *
 * Returns
 * \li	ISC_R_SUCCESS
 */
static isc_result_t
checkwildcard(dns_validator_t *val, dns_rdatatype_t type,
	      dns_name_t *zonename) {
	dns_name_t *name, *wild, tname;
	isc_result_t result;
	bool exists, data;
	char namebuf[DNS_NAME_FORMATSIZE];
	dns_rdataset_t *rdataset, trdataset;

	dns_name_init(&tname);
	dns_rdataset_init(&trdataset);
	wild = dns_fixedname_name(&val->wild);

	if (dns_name_countlabels(wild) == 0) {
		validator_log(val, ISC_LOG_DEBUG(3),
			      "in checkwildcard: no wildcard to check");
		return ISC_R_SUCCESS;
	}

	dns_name_format(wild, namebuf, sizeof(namebuf));
	validator_log(val, ISC_LOG_DEBUG(3), "in checkwildcard: %s", namebuf);

	if (val->message == NULL) {
		name = &tname;
		rdataset = &trdataset;
	} else {
		name = NULL;
		rdataset = NULL;
	}

	for (result = val_rdataset_first(val, &name, &rdataset);
	     result == ISC_R_SUCCESS;
	     result = val_rdataset_next(val, &name, &rdataset))
	{
		if (rdataset->type != type ||
		    rdataset->trust != dns_trust_secure)
		{
			continue;
		}

		if (rdataset->type == dns_rdatatype_nsec &&
		    (NEEDNODATA(val) || NEEDNOWILDCARD(val)) &&
		    !FOUNDNODATA(val) && !FOUNDNOWILDCARD(val) &&
		    dns_nsec_noexistnodata(val->type, wild, name, rdataset,
					   &exists, &data, NULL, validator_log,
					   val) == ISC_R_SUCCESS)
		{
			dns_name_t **proofs = val->proofs;
			if (exists && !data) {
				val->attributes |= VALATTR_FOUNDNODATA;
			}
			if (exists && !data && NEEDNODATA(val)) {
				proofs[DNS_VALIDATOR_NODATAPROOF] = name;
			}
			if (!exists) {
				val->attributes |= VALATTR_FOUNDNOWILDCARD;
			}
			if (!exists && NEEDNOQNAME(val)) {
				proofs[DNS_VALIDATOR_NOWILDCARDPROOF] = name;
			}
			dns_rdataset_cleanup(&trdataset);
			return ISC_R_SUCCESS;
		}

		if (rdataset->type == dns_rdatatype_nsec3 &&
		    (NEEDNODATA(val) || NEEDNOWILDCARD(val)) &&
		    !FOUNDNODATA(val) && !FOUNDNOWILDCARD(val) &&
		    dns_nsec3_noexistnodata(
			    val->type, wild, name, rdataset, zonename, &exists,
			    &data, NULL, NULL, NULL, NULL, NULL, NULL,
			    validator_log, val) == ISC_R_SUCCESS)
		{
			dns_name_t **proofs = val->proofs;
			if (exists && !data) {
				val->attributes |= VALATTR_FOUNDNODATA;
			}
			if (exists && !data && NEEDNODATA(val)) {
				proofs[DNS_VALIDATOR_NODATAPROOF] = name;
			}
			if (!exists) {
				val->attributes |= VALATTR_FOUNDNOWILDCARD;
			}
			if (!exists && NEEDNOQNAME(val)) {
				proofs[DNS_VALIDATOR_NOWILDCARDPROOF] = name;
			}
			dns_rdataset_cleanup(&trdataset);
			return ISC_R_SUCCESS;
		}
	}
	if (result == ISC_R_NOMORE) {
		result = ISC_R_SUCCESS;
	}
	dns_rdataset_cleanup(&trdataset);
	return result;
}

/*
 * Look for the needed proofs for a negative or wildcard response
 * from a zone using NSEC3, and set flags in the validator as they
 * are found.
 */
static isc_result_t
findnsec3proofs(dns_validator_t *val) {
	dns_name_t *name, tname;
	isc_result_t result;
	bool exists, data, optout, unknown;
	bool setclosest, setnearest, *setclosestp;
	dns_fixedname_t fclosest, fnearest, fzonename;
	dns_name_t *closest, *nearest, *zonename, *closestp;
	dns_name_t **proofs = val->proofs;
	dns_rdataset_t *rdataset, trdataset;

	dns_name_init(&tname);
	dns_rdataset_init(&trdataset);
	closest = dns_fixedname_initname(&fclosest);
	nearest = dns_fixedname_initname(&fnearest);
	zonename = dns_fixedname_initname(&fzonename);

	if (val->message == NULL) {
		name = &tname;
		rdataset = &trdataset;
	} else {
		name = NULL;
		rdataset = NULL;
	}

	for (result = val_rdataset_first(val, &name, &rdataset);
	     result == ISC_R_SUCCESS;
	     result = val_rdataset_next(val, &name, &rdataset))
	{
		if (rdataset->type != dns_rdatatype_nsec3 ||
		    rdataset->trust != dns_trust_secure)
		{
			continue;
		}

		result = dns_nsec3_noexistnodata(val->type, val->name, name,
						 rdataset, zonename, NULL, NULL,
						 NULL, NULL, NULL, NULL, NULL,
						 NULL, validator_log, val);
		if (result != ISC_R_IGNORE && result != ISC_R_SUCCESS) {
			dns_rdataset_cleanup(&trdataset);
			return result;
		}
	}
	if (result != ISC_R_NOMORE) {
		result = ISC_R_SUCCESS;
	}
	POST(result);

	if (dns_name_countlabels(zonename) == 0) {
		dns_rdataset_cleanup(&trdataset);
		return ISC_R_SUCCESS;
	}

	/*
	 * If the val->closest is set then we want to use it otherwise
	 * we need to discover it.
	 */
	if (dns_name_countlabels(dns_fixedname_name(&val->closest)) != 0) {
		char namebuf[DNS_NAME_FORMATSIZE];

		dns_name_format(dns_fixedname_name(&val->closest), namebuf,
				sizeof(namebuf));
		validator_log(val, ISC_LOG_DEBUG(3),
			      "closest encloser from wildcard signature '%s'",
			      namebuf);
		dns_name_copy(dns_fixedname_name(&val->closest), closest);
		closestp = NULL;
		setclosestp = NULL;
	} else {
		closestp = closest;
		setclosestp = &setclosest;
	}

	for (result = val_rdataset_first(val, &name, &rdataset);
	     result == ISC_R_SUCCESS;
	     result = val_rdataset_next(val, &name, &rdataset))
	{
		if (rdataset->type != dns_rdatatype_nsec3 ||
		    rdataset->trust != dns_trust_secure)
		{
			continue;
		}

		/*
		 * We process all NSEC3 records to find the closest
		 * encloser and nearest name to the closest encloser.
		 */
		setclosest = setnearest = false;
		optout = false;
		unknown = false;
		result = dns_nsec3_noexistnodata(
			val->type, val->name, name, rdataset, zonename, &exists,
			&data, &optout, &unknown, setclosestp, &setnearest,
			closestp, nearest, validator_log, val);
		if (unknown) {
			val->attributes |= VALATTR_FOUNDUNKNOWN;
		}
		if (result == DNS_R_NSEC3ITERRANGE) {
			/*
			 * We don't really know which NSEC3 record provides
			 * which proof.  Just populate them.
			 */
			if (NEEDNOQNAME(val) &&
			    proofs[DNS_VALIDATOR_NOQNAMEPROOF] == NULL)
			{
				proofs[DNS_VALIDATOR_NOQNAMEPROOF] = name;
			} else if (setclosest) {
				proofs[DNS_VALIDATOR_CLOSESTENCLOSER] = name;
			} else if (NEEDNODATA(val) &&
				   proofs[DNS_VALIDATOR_NODATAPROOF] == NULL)
			{
				proofs[DNS_VALIDATOR_NODATAPROOF] = name;
			} else if (NEEDNOWILDCARD(val) &&
				   proofs[DNS_VALIDATOR_NOWILDCARDPROOF] ==
					   NULL)
			{
				proofs[DNS_VALIDATOR_NOWILDCARDPROOF] = name;
			}
			dns_rdataset_cleanup(&trdataset);
			return result;
		}
		if (result != ISC_R_SUCCESS) {
			continue;
		}
		if (setclosest) {
			proofs[DNS_VALIDATOR_CLOSESTENCLOSER] = name;
		}
		if (exists && !data && NEEDNODATA(val)) {
			val->attributes |= VALATTR_FOUNDNODATA;
			proofs[DNS_VALIDATOR_NODATAPROOF] = name;
		}
		if (!exists && setnearest) {
			val->attributes |= VALATTR_FOUNDNOQNAME;
			proofs[DNS_VALIDATOR_NOQNAMEPROOF] = name;
			if (optout) {
				val->attributes |= VALATTR_FOUNDOPTOUT;
			}
		}
	}
	if (result == ISC_R_NOMORE) {
		result = ISC_R_SUCCESS;
	}

	/*
	 * To know we have a valid noqname and optout proofs we need to also
	 * have a valid closest encloser.  Otherwise we could still be looking
	 * at proofs from the parent zone.
	 */
	if (dns_name_countlabels(closest) > 0 &&
	    dns_name_countlabels(nearest) ==
		    dns_name_countlabels(closest) + 1 &&
	    dns_name_issubdomain(nearest, closest))
	{
		val->attributes |= VALATTR_FOUNDCLOSEST;
		result = dns_name_concatenate(dns_wildcardname, closest,
					      dns_fixedname_name(&val->wild));
		RUNTIME_CHECK(result == ISC_R_SUCCESS);
	} else {
		val->attributes &= ~VALATTR_FOUNDNOQNAME;
		val->attributes &= ~VALATTR_FOUNDOPTOUT;
		proofs[DNS_VALIDATOR_NOQNAMEPROOF] = NULL;
	}

	/*
	 * Do we need to check for the wildcard?
	 */
	if (FOUNDNOQNAME(val) && FOUNDCLOSEST(val) &&
	    ((NEEDNODATA(val) && !FOUNDNODATA(val)) || NEEDNOWILDCARD(val)))
	{
		result = checkwildcard(val, dns_rdatatype_nsec3, zonename);
		if (result != ISC_R_SUCCESS) {
			dns_rdataset_cleanup(&trdataset);
			return result;
		}
	}
	dns_rdataset_cleanup(&trdataset);
	return result;
}

/*
 * Start a validator for negative response data.
 *
 * Returns:
 * \li	DNS_R_CONTINUE	Validation skipped, continue
 * \li	DNS_R_WAIT	Validation is in progress
 *
 * \li	Other return codes indicate failure.
 */
static isc_result_t
validate_neg_rrset(dns_validator_t *val, dns_name_t *name,
		   dns_rdataset_t *rdataset, dns_rdataset_t *sigrdataset) {
	/*
	 * If a signed zone is missing the zone key, bad
	 * things could happen.  A query for data in the zone
	 * would lead to a query for the zone key, which
	 * would return a negative answer, which would contain
	 * an SOA and an NSEC signed by the missing key, which
	 * would trigger another query for the DNSKEY (since
	 * the first one is still in progress), and go into an
	 * infinite loop.  Avoid that.
	 */
	if (val->type == dns_rdatatype_dnskey &&
	    rdataset->type == dns_rdatatype_nsec &&
	    dns_name_equal(name, val->name))
	{
		dns_rdata_t nsec = DNS_RDATA_INIT;

		RETERR(dns_rdataset_first(rdataset));
		dns_rdataset_current(rdataset, &nsec);
		if (dns_nsec_typepresent(&nsec, dns_rdatatype_soa)) {
			return DNS_R_CONTINUE;
		}
	}

	val->nxset = rdataset;
	RETERR(create_validator(val, name, rdataset->type, rdataset,
				sigrdataset, validator_callback_nsec,
				"validate_neg_rrset"));

	val->authcount++;
	return DNS_R_WAIT;
}

/*%
 * Validate the authority section records.
 */
static isc_result_t
validate_authority(dns_validator_t *val, bool resume) {
	dns_name_t *name;
	dns_message_t *message = val->message;
	isc_result_t result;

	if (!resume) {
		result = dns_message_firstname(message, DNS_SECTION_AUTHORITY);
	} else {
		result = ISC_R_SUCCESS;
	}

	for (; result == ISC_R_SUCCESS;
	     result = dns_message_nextname(message, DNS_SECTION_AUTHORITY))
	{
		dns_rdataset_t *rdataset = NULL, *sigrdataset = NULL;

		name = NULL;
		dns_message_currentname(message, DNS_SECTION_AUTHORITY, &name);
		if (resume) {
			rdataset = ISC_LIST_NEXT(val->nxset, link);
			val->nxset = NULL;
			resume = false;
		} else {
			rdataset = ISC_LIST_HEAD(name->list);
		}

		for (; rdataset != NULL;
		     rdataset = ISC_LIST_NEXT(rdataset, link))
		{
			if (rdataset->type == dns_rdatatype_rrsig) {
				continue;
			}

			for (sigrdataset = ISC_LIST_HEAD(name->list);
			     sigrdataset != NULL;
			     sigrdataset = ISC_LIST_NEXT(sigrdataset, link))
			{
				if (sigrdataset->type == dns_rdatatype_rrsig &&
				    sigrdataset->covers == rdataset->type)
				{
					break;
				}
			}

			result = validate_neg_rrset(val, name, rdataset,
						    sigrdataset);
			if (result != DNS_R_CONTINUE) {
				return result;
			}
		}
	}
	if (result == ISC_R_NOMORE) {
		result = ISC_R_SUCCESS;
	}
	return result;
}

/*%
 * Validate negative cache elements.
 */
static isc_result_t
validate_ncache(dns_validator_t *val, bool resume) {
	dns_name_t *name;
	isc_result_t result;

	if (!resume) {
		result = dns_rdataset_first(val->rdataset);
	} else {
		result = dns_rdataset_next(val->rdataset);
	}

	for (; result == ISC_R_SUCCESS;
	     result = dns_rdataset_next(val->rdataset))
	{
		dns_rdataset_t *rdataset, *sigrdataset = NULL;

		disassociate_rdatasets(val);

		name = dns_fixedname_initname(&val->fname);
		rdataset = &val->frdataset;
		dns_ncache_current(val->rdataset, name, rdataset);

		if (val->frdataset.type == dns_rdatatype_rrsig) {
			continue;
		}

		result = dns_ncache_getsigrdataset(val->rdataset, name,
						   rdataset->type,
						   &val->fsigrdataset);
		if (result == ISC_R_SUCCESS) {
			sigrdataset = &val->fsigrdataset;
		}

		result = validate_neg_rrset(val, name, rdataset, sigrdataset);
		if (result == DNS_R_CONTINUE) {
			continue;
		}

		return result;
	}
	if (result == ISC_R_NOMORE) {
		result = ISC_R_SUCCESS;
	}

	return result;
}

/*%
 * Prove a negative answer is good or that there is a NOQNAME when the
 * answer is from a wildcard.
 *
 * Loop through the authority section looking for NODATA, NOWILDCARD
 * and NOQNAME proofs in the NSEC records by calling
 * validator_callback_nsec().
 *
 * If the required proofs are found we are done.
 *
 * If the proofs are not found attempt to prove this is an unsecure
 * response.
 */
static isc_result_t
validate_nx(dns_validator_t *val, bool resume) {
	isc_result_t result;

	if (resume) {
		validator_log(val, ISC_LOG_DEBUG(3), "resuming validate_nx");
	}

	if (val->message == NULL) {
		result = validate_ncache(val, resume);
	} else {
		result = validate_authority(val, resume);
	}

	if (result != ISC_R_SUCCESS) {
		return result;
	}

	/*
	 * Do we only need to check for NOQNAME?  To get here we must have
	 * had a secure wildcard answer.
	 */
	if (!NEEDNODATA(val) && !NEEDNOWILDCARD(val) && NEEDNOQNAME(val)) {
		if (!FOUNDNOQNAME(val)) {
			result = findnsec3proofs(val);
			if (result == DNS_R_NSEC3ITERRANGE) {
				validator_log(val, ISC_LOG_DEBUG(3),
					      "too many iterations");
				markanswer(val, "validate_nx (3)");
				return ISC_R_SUCCESS;
			}
		}

		if (FOUNDNOQNAME(val) && FOUNDCLOSEST(val) && !FOUNDOPTOUT(val))
		{
			validator_log(val, ISC_LOG_DEBUG(3),
				      "marking as secure, noqname proof found");
			marksecure(val);
			return ISC_R_SUCCESS;
		} else if (FOUNDOPTOUT(val) &&
			   dns_name_countlabels(
				   dns_fixedname_name(&val->wild)) != 0)
		{
			validator_log(val, ISC_LOG_DEBUG(3),
				      "optout proof found");
			val->optout = true;
			markanswer(val, "validate_nx (1)");
			return ISC_R_SUCCESS;
		} else if ((val->attributes & VALATTR_FOUNDUNKNOWN) != 0) {
			validator_log(val, ISC_LOG_DEBUG(3),
				      "unknown NSEC3 hash algorithm found");
			markanswer(val, "validate_nx (2)");
			return ISC_R_SUCCESS;
		}

		validator_log(val, ISC_LOG_DEBUG(3), "noqname proof not found");
		return DNS_R_NOVALIDNSEC;
	}

	if (!FOUNDNOQNAME(val) && !FOUNDNODATA(val)) {
		result = findnsec3proofs(val);
		if (result == DNS_R_NSEC3ITERRANGE) {
			validator_log(val, ISC_LOG_DEBUG(3),
				      "too many iterations");
			markanswer(val, "validate_nx (4)");
			return ISC_R_SUCCESS;
		}
	}

	/*
	 * Do we need to check for the wildcard?
	 */
	if (FOUNDNOQNAME(val) && FOUNDCLOSEST(val) &&
	    ((NEEDNODATA(val) && !FOUNDNODATA(val)) || NEEDNOWILDCARD(val)))
	{
		RETERR(checkwildcard(val, dns_rdatatype_nsec, NULL));
	}

	if ((NEEDNODATA(val) && (FOUNDNODATA(val) || FOUNDOPTOUT(val))) ||
	    (NEEDNOQNAME(val) && FOUNDNOQNAME(val) && NEEDNOWILDCARD(val) &&
	     FOUNDNOWILDCARD(val) && FOUNDCLOSEST(val)))
	{
		if ((val->attributes & VALATTR_FOUNDOPTOUT) != 0) {
			val->optout = true;
		}
		validator_log(val, ISC_LOG_DEBUG(3),
			      "nonexistence proof(s) found");
		if (val->message == NULL) {
			marksecure(val);
		} else {
			val->secure = true;
		}
		return ISC_R_SUCCESS;
	}

	if (val->authfail != 0 && val->authcount == val->authfail) {
		return DNS_R_BROKENCHAIN;
	}

	return proveunsecure(val, false, false, false);
}

/*
 * Check if any of the DS records has a private DNSSEC algorithm.
 */
static bool
check_ds_private(dns_rdataset_t *rdataset) {
	dns_rdata_ds_t ds;
	isc_result_t result;

	for (result = dns_rdataset_first(rdataset); result == ISC_R_SUCCESS;
	     result = dns_rdataset_next(rdataset))
	{
		dns_rdata_t rdata = DNS_RDATA_INIT;
		dns_rdataset_current(rdataset, &rdata);
		result = dns_rdata_tostruct(&rdata, &ds, NULL);
		RUNTIME_CHECK(result == ISC_R_SUCCESS);
		if (ds.algorithm == DNS_KEYALG_PRIVATEDNS ||
		    ds.algorithm == DNS_KEYALG_PRIVATEOID)
		{
			return true;
		}
	}
	return false;
}

/*%
 * Check that DS rdataset has at least one record with
 * a supported algorithm and digest.
 */
static bool
check_ds_algs(dns_validator_t *val, dns_name_t *name,
	      dns_rdataset_t *dsrdataset, dns_rdataset_t *dnskeyset) {
	dns_rdata_ds_t ds;
	dns_rdata_dnskey_t key;
	bool seen_private = false;
	uint16_t key_tag = 0;
	uint8_t algorithm = 0;

	DNS_RDATASET_FOREACH(dsrdataset) {
		isc_result_t result;
		dns_rdata_t dsrdata = DNS_RDATA_INIT;
		dns_rdata_t keyrdata = DNS_RDATA_INIT;
		unsigned char *data = NULL;
		size_t datalen = 0;

		dns_rdataset_current(dsrdataset, &dsrdata);
		result = dns_rdata_tostruct(&dsrdata, &ds, NULL);
		RUNTIME_CHECK(result == ISC_R_SUCCESS);

		/*
		 * Look for a matching DNSKEY to find the PRIVATE
		 * DNSSEC algorithm.
		 */
		if (ds.algorithm == DNS_KEYALG_PRIVATEOID ||
		    ds.algorithm == DNS_KEYALG_PRIVATEDNS)
		{
			switch (ds.digest_type) {
#if defined(DNS_DSDIGEST_SHA256PRIVATE) && defined(DNS_DSDIGEST_SHA384PRIVATE)
			case DNS_DSDIGEST_SHA256PRIVATE:
			case DNS_DSDIGEST_SHA384PRIVATE:
				data = ds.digest;
				datalen = ds.length;
				break;
#endif
			case DNS_DSDIGEST_SHA1:
			case DNS_DSDIGEST_SHA256:
			case DNS_DSDIGEST_SHA384:
				if (dnskeyset == NULL) {
					algorithm = ds.algorithm;
					key_tag = ds.key_tag;
					seen_private = true;
					continue;
				}
				result = dns_dnssec_matchdskey(
					name, &dsrdata, dnskeyset, &keyrdata);
				if (result != ISC_R_SUCCESS) {
					algorithm = ds.algorithm;
					key_tag = ds.key_tag;
					seen_private = true;
					continue;
				}
				result = dns_rdata_tostruct(&keyrdata, &key,
							    NULL);
				RUNTIME_CHECK(result == ISC_R_SUCCESS);
				data = key.data;
				datalen = key.datalen;
				break;
			default:
				break;
			}
		}

		if (dns_resolver_ds_digest_supported(val->view->resolver, name,
						     ds.digest_type) &&
		    dns_resolver_algorithm_supported(val->view->resolver, name,
						     ds.algorithm, data,
						     datalen))
		{
			return true;
		}
	}

	/*
	 * No unsupported alg/digest EDE error is raised here because the prove
	 * unsecure flow always runs after a validate/validatenx flow. So if an
	 * unsupported alg/digest was found while building the chain of trust,
	 * it would be raised already.
	 *
	 * If we have seen a private algorithm for which we couldn't find a
	 * DNSKEY nor extract it from the digest field we must assume the child
	 * zone is secure. With PRIVATEDNS and PRIVATEOID we can make that
	 * determination if we match a DNSKEY for every DS with these algorithms
	 * or extract the algorithm from the digest field. Since we don't know
	 * whether the private algorithm is unsupported or not, we are required
	 * to treat it as supported.
	 */
	if (seen_private) {
		char namebuf[DNS_NAME_FORMATSIZE];
		dns_name_format(name, namebuf, sizeof(namebuf));
		validator_log(val, ISC_LOG_INFO,
			      "No DNSKEY for %s/DS with %s algorithm, tag %u",
			      namebuf,
			      algorithm == DNS_KEYALG_PRIVATEDNS ? "PRIVATEDNS"
								 : "PRIVATEOID",
			      key_tag);
	}
	return seen_private;
}

/*%
 * seek_ds is called to look up DS rrsets at the label of val->name
 * indicated by val->labels. This is done while building an insecurity
 * proof, and so it will attempt validation of NXDOMAIN, NXRRSET or CNAME
 * responses.
 *
 * Returns:
 * \li	ISC_R_COMPLETE		a result has been determined and copied
 * 				into `*resp`; ISC_R_SUCCESS indicates that
 * 				the name has been proven insecure and any
 * 				other result indicates failure.
 * \li	DNS_R_CONTINUE		result is indeterminate; caller should
 * 				continue walking down labels.
 */
static isc_result_t
seek_ds(dns_validator_t *val, isc_result_t *resp) {
	isc_result_t result;
	char namebuf[DNS_NAME_FORMATSIZE];
	dns_fixedname_t fixedfound;
	dns_name_t *found = dns_fixedname_initname(&fixedfound);
	dns_name_t *tname = dns_fixedname_initname(&val->fname);

	if (val->labels == dns_name_countlabels(val->name)) {
		dns_name_copy(val->name, tname);
	} else {
		dns_name_split(val->name, val->labels, NULL, tname);
	}

	dns_name_format(tname, namebuf, sizeof(namebuf));
	validator_log(val, ISC_LOG_DEBUG(3), "checking existence of DS at '%s'",
		      namebuf);

	result = view_find(val, tname, dns_rdatatype_ds);
	switch (result) {
	case ISC_R_SUCCESS:
		/*
		 * There is a DS here.  If it's already been
		 * validated, continue walking down labels.
		 */
		if (val->frdataset.trust >= dns_trust_secure) {
			dns_rdataset_t *dssetp = &val->frdataset,
				       *keysetp = NULL;

			if (check_ds_private(&val->frdataset)) {
				dns_rdataset_cleanup(&val->dsrdataset);
				dns_rdataset_clone(&val->frdataset,
						   &val->dsrdataset);
				dssetp = &val->dsrdataset;
				dns_rdataset_disassociate(&val->frdataset);
				result = view_find(val, tname,
						   dns_rdatatype_dnskey);
				switch (result) {
				case ISC_R_SUCCESS:
					keysetp = &val->frdataset;
					break;
				case ISC_R_NOTFOUND:
					/*
					 * We don't know anything about the
					 * DNSKEY.  Find it.
					 */
					*resp = DNS_R_WAIT;
					result = create_fetch(
						val, tname,
						dns_rdatatype_dnskey,
						fetch_callback_dnskey,
						"seek_ds");
					if (result != ISC_R_SUCCESS) {
						*resp = result;
					}
					return ISC_R_COMPLETE;
					break;
				default:
					validator_log(val, ISC_LOG_DEBUG(3),
						      "no DNSKEY found (%s/DS)",
						      namebuf);
					break;
				}
			}
			if (!check_ds_algs(val, tname, dssetp, keysetp)) {
				validator_log(
					val, ISC_LOG_DEBUG(3),
					"no supported algorithm/digest (%s/DS)",
					namebuf);
				*resp = markanswer(val, "seek_ds (1)");
				return ISC_R_COMPLETE;
			}

			break;
		}

		/*
		 * Otherwise, try to validate it now.
		 */
		result = create_validator(val, tname, dns_rdatatype_ds,
					  &val->frdataset, &val->fsigrdataset,
					  validator_callback_ds, "seek_ds");
		*resp = DNS_R_WAIT;
		if (result != ISC_R_SUCCESS) {
			*resp = result;
		}

		return ISC_R_COMPLETE;

	case ISC_R_NOTFOUND:
		/*
		 * We don't know anything about the DS.  Find it.
		 */
		*resp = DNS_R_WAIT;
		result = create_fetch(val, tname, dns_rdatatype_ds,
				      fetch_callback_ds, "seek_ds");
		if (result != ISC_R_SUCCESS) {
			*resp = result;
		}
		return ISC_R_COMPLETE;

	case DNS_R_NXRRSET:
	case DNS_R_NCACHENXRRSET:
		/*
		 * There is no DS.  If this is a delegation,
		 * we may be done.
		 *
		 * If we have "trust == answer" then this namespace
		 * has switched from insecure to should be secure.
		 */
		if (DNS_TRUST_PENDING(val->frdataset.trust) ||
		    DNS_TRUST_ANSWER(val->frdataset.trust))
		{
			result = create_validator(
				val, tname, dns_rdatatype_ds, &val->frdataset,
				&val->fsigrdataset, validator_callback_ds,
				"seek_ds");
			*resp = DNS_R_WAIT;
			if (result != ISC_R_SUCCESS) {
				*resp = result;
			}
			return ISC_R_COMPLETE;
		}

		/*
		 * Zones using NSEC3 don't return a NSEC RRset so
		 * we need to use dns_view_findzonecut2 to find
		 * the zone cut.
		 */
		if (result == DNS_R_NXRRSET &&
		    !dns_rdataset_isassociated(&val->frdataset) &&
		    dns_view_bestzonecut(val->view, tname, found, NULL, 0, 0,
					 false, false, NULL) == ISC_R_SUCCESS &&
		    dns_name_equal(tname, found))
		{
			*resp = markanswer(val, "seek_ds (2)");
			return ISC_R_COMPLETE;
		}

		if (val->frdataset.trust < dns_trust_secure) {
			/*
			 * This shouldn't happen, since the negative
			 * response should have been validated.  Since
			 * there's no way of validating existing
			 * negative response blobs, give up.
			 */
			validator_log(val, ISC_LOG_WARNING,
				      "can't validate existing "
				      "negative responses (no DS)");
			*resp = DNS_R_NOVALIDSIG;
			return ISC_R_COMPLETE;
		}

		if (isdelegation(tname, &val->frdataset, result)) {
			*resp = markanswer(val, "seek_ds (3)");
			return ISC_R_COMPLETE;
		}

		break;

	case DNS_R_NXDOMAIN:
	case DNS_R_NCACHENXDOMAIN:
		/*
		 * This is not a zone cut. Assuming things are
		 * as expected, continue.
		 */
		if (!dns_rdataset_isassociated(&val->frdataset)) {
			/*
			 * There should be an NSEC here, since we
			 * are still in a secure zone.
			 */
			*resp = DNS_R_NOVALIDNSEC;
			return ISC_R_COMPLETE;
		} else if (DNS_TRUST_PENDING(val->frdataset.trust) ||
			   DNS_TRUST_ANSWER(val->frdataset.trust))
		{
			/*
			 * If we have "trust == answer" then this
			 * namespace has switched from insecure to
			 * should be secure.
			 */
			*resp = DNS_R_WAIT;
			result = create_validator(
				val, tname, dns_rdatatype_ds, &val->frdataset,
				&val->fsigrdataset, validator_callback_ds,
				"seek_ds");
			if (result != ISC_R_SUCCESS) {
				*resp = result;
			}
			return ISC_R_COMPLETE;
		} else if (val->frdataset.trust < dns_trust_secure) {
			/*
			 * This shouldn't happen, since the negative
			 * response should have been validated.  Since
			 * there's no way of validating existing
			 * negative response blobs, give up.
			 */
			validator_log(val, ISC_LOG_WARNING,
				      "can't validate existing "
				      "negative responses "
				      "(not a zone cut)");
			*resp = DNS_R_NOVALIDSIG;
			return ISC_R_COMPLETE;
		}

		break;

	case DNS_R_CNAME:
		if (DNS_TRUST_PENDING(val->frdataset.trust) ||
		    DNS_TRUST_ANSWER(val->frdataset.trust))
		{
			result = create_validator(
				val, tname, dns_rdatatype_cname,
				&val->frdataset, &val->fsigrdataset,
				validator_callback_cname, "seek_ds (cname)");
			*resp = DNS_R_WAIT;
			if (result != ISC_R_SUCCESS) {
				*resp = result;
			}
			return ISC_R_COMPLETE;
		}

		break;

	default:
		*resp = result;
		return ISC_R_COMPLETE;
	}

	/*
	 * No definite answer yet; continue walking down labels.
	 */
	return DNS_R_CONTINUE;
}

/*%
 * proveunsecure walks down, label by label, from the closest enclosing
 * trust anchor to the name that is being validated, looking for an
 * endpoint in the chain of trust.  That occurs when we can prove that
 * a DS record does not exist at a delegation point, or that a DS exists
 * at a delegation point but we don't support its algorithm/digest.  If
 * no such endpoint is found, then the response should have been secure.
 *
 * Returns:
 * \li	ISC_R_SUCCESS		val->name is in an unsecure zone
 * \li	DNS_R_WAIT		validation is in progress.
 * \li	DNS_R_NOVALIDSIG
 * \li	DNS_R_NOVALIDNSEC
 * \li	DNS_R_NOTINSECURE
 * \li	DNS_R_BROKENCHAIN
 */
static isc_result_t
proveunsecure(dns_validator_t *val, bool have_ds, bool have_dnskey,
	      bool resume) {
	isc_result_t result;
	char namebuf[DNS_NAME_FORMATSIZE];
	dns_fixedname_t fixedsecroot;
	dns_name_t *secroot = dns_fixedname_initname(&fixedsecroot);
	unsigned int labels;

	INSIST(!(have_ds && have_dnskey));

	/*
	 * We're attempting to prove insecurity.
	 */
	val->attributes |= VALATTR_INSECURITY;

	dns_name_copy(val->name, secroot);

	/*
	 * If this is a response to a DS query, we need to look in
	 * the parent zone for the trust anchor.
	 */
	labels = dns_name_countlabels(secroot);
	if (val->type == dns_rdatatype_ds && labels > 1U) {
		dns_name_getlabelsequence(secroot, 1, labels - 1, secroot);
	}

	result = dns_keytable_finddeepestmatch(val->keytable, secroot, secroot);
	if (result == ISC_R_NOTFOUND) {
		validator_log(val, ISC_LOG_DEBUG(3), "not beneath secure root");
		return markanswer(val, "proveunsecure (1)");
	} else if (result != ISC_R_SUCCESS) {
		return result;
	}

	if (!resume) {
		/*
		 * We are looking for interruptions in the chain of trust.
		 * That can only happen *below* the trust anchor, so we
		 * start looking at the next label down.
		 */
		val->labels = dns_name_countlabels(secroot) + 1;
	} else {
		validator_log(val, ISC_LOG_DEBUG(3), "resuming proveunsecure");

		/*
		 * If we have a DS rdataset and it is secure, check whether
		 * it has a supported algorithm combination.  If not, this is
		 * an insecure delegation as far as this resolver is concerned.
		 */
		if (have_dnskey ||
		    (have_ds && val->frdataset.trust >= dns_trust_secure))
		{
			dns_rdataset_t *dssetp = NULL, *keysetp = NULL;
			dns_name_t *fname = dns_fixedname_name(&val->fname);
			if (have_dnskey) {
				dssetp = &val->dsrdataset;
				keysetp = &val->frdataset;
			} else {
				dssetp = &val->frdataset;
			}

			if (!have_dnskey && check_ds_private(&val->frdataset)) {
				dns_rdataset_cleanup(&val->dsrdataset);
				dns_rdataset_clone(&val->frdataset,
						   &val->dsrdataset);
				dssetp = &val->dsrdataset;
				dns_rdataset_disassociate(&val->frdataset);
				result = view_find(val, fname,
						   dns_rdatatype_dnskey);
				switch (result) {
				case ISC_R_SUCCESS:
					keysetp = &val->frdataset;
					break;
				case ISC_R_NOTFOUND:
					/*
					 * We don't know anything about the
					 * DNSKEY.  Find it.
					 */
					result = create_fetch(
						val, fname,
						dns_rdatatype_dnskey,
						fetch_callback_dnskey,
						"seek_ds");
					if (result == ISC_R_SUCCESS) {
						result = DNS_R_WAIT;
					}
					goto out;
				default:
					validator_log(val, ISC_LOG_DEBUG(3),
						      "no DNSKEY found (%s/DS)",
						      namebuf);
					break;
				}
			}
			if (!check_ds_algs(val, fname, dssetp, keysetp)) {
				dns_name_format(fname, namebuf,
						sizeof(namebuf));
				validator_log(
					val, ISC_LOG_DEBUG(3),
					"no supported algorithm/digest (%s/DS)",
					namebuf);
				result = markanswer(val, "proveunsecure (2)");
				goto out;
			}
		}
		val->labels++;
	}

	/*
	 * Walk down through each of the remaining labels in the name,
	 * looking for DS records.
	 */
	while (val->labels <= dns_name_countlabels(val->name)) {
		isc_result_t tresult;

		result = seek_ds(val, &tresult);
		if (result == ISC_R_COMPLETE) {
			result = tresult;
			goto out;
		}

		INSIST(result == DNS_R_CONTINUE);
		val->labels++;
	}

	/* Couldn't complete insecurity proof. */
	validator_log(val, ISC_LOG_DEBUG(3), "insecurity proof failed: %s",
		      isc_result_totext(result));

	if (val->type == dns_rdatatype_dnskey && val->rdataset == NULL) {
		validator_addede(val, DNS_EDE_DNSKEYMISSING, "no DNSKEY found");
	}

	return DNS_R_NOTINSECURE;

out:
	if (result != DNS_R_WAIT) {
		disassociate_rdatasets(val);
	}
	return result;
}

/*%
 * Start the validation process.
 *
 * Attempt to validate the answer based on the category it appears to
 * fall in.
 * \li	1. secure positive answer.
 * \li	2. unsecure positive answer.
 * \li	3. a negative answer (secure or unsecure).
 *
 * Note an answer that appears to be a secure positive answer may actually
 * be an unsecure positive answer.
 */
static void
validator_start(void *arg) {
	dns_validator_t *val = (dns_validator_t *)arg;
	isc_result_t result = ISC_R_FAILURE;

	if (CANCELED(val) || CANCELING(val)) {
		CLEANUP(ISC_R_CANCELED);
	}

	validator_log(val, ISC_LOG_DEBUG(3), "starting");

	if (val->rdataset != NULL && val->sigrdataset != NULL) {
		/*
		 * This looks like a simple validation.  We say "looks like"
		 * because it might end up requiring an insecurity proof.
		 */
		validator_log(val, ISC_LOG_DEBUG(3),
			      "attempting positive response validation");

		INSIST(dns_rdataset_isassociated(val->rdataset));
		INSIST(dns_rdataset_isassociated(val->sigrdataset));

		result = selfsigned_dnskey(val);
		switch (result) {
		case ISC_R_QUOTA:
			goto cleanup;
		case ISC_R_SUCCESS:
			result = validate_async_run(val, validate_dnskey);
			break;
		case DNS_R_NOKEYMATCH:
			result = validate_async_run(val, validate_answer);
			break;
		default:
			UNREACHABLE();
		}
	} else if (val->rdataset != NULL && val->rdataset->type != 0) {
		/*
		 * This is either an unsecure subdomain or a response
		 * from a broken server.
		 */
		INSIST(dns_rdataset_isassociated(val->rdataset));
		validator_log(val, ISC_LOG_DEBUG(3),
			      "attempting insecurity proof");

		result = proveunsecure(val, false, false, false);
		if (result == DNS_R_NOTINSECURE) {
			validator_log(val, ISC_LOG_INFO,
				      "got insecure response; "
				      "parent indicates it should be secure");
		}
	} else if (val->rdataset == NULL && val->sigrdataset == NULL) {
		/*
		 * This is a validation of a negative response.
		 */
		validator_log(val, ISC_LOG_DEBUG(3),
			      "attempting negative response validation "
			      "from message");

		if (val->message->rcode == dns_rcode_nxdomain) {
			val->attributes |= VALATTR_NEEDNOQNAME;
			val->attributes |= VALATTR_NEEDNOWILDCARD;
		} else {
			val->attributes |= VALATTR_NEEDNODATA;
		}

		result = validate_nx(val, false);
	} else if (val->rdataset != NULL && NEGATIVE(val->rdataset)) {
		/*
		 * This is a delayed validation of a negative cache entry.
		 */
		validator_log(val, ISC_LOG_DEBUG(3),
			      "attempting negative response validation "
			      "from cache");

		if (NXDOMAIN(val->rdataset)) {
			val->attributes |= VALATTR_NEEDNOQNAME;
			val->attributes |= VALATTR_NEEDNOWILDCARD;
		} else {
			val->attributes |= VALATTR_NEEDNODATA;
		}

		result = validate_nx(val, false);
	} else {
		UNREACHABLE();
	}

cleanup:
	validate_async_done(val, result);
}

isc_result_t
dns_validator_create(dns_view_t *view, dns_name_t *name, dns_rdatatype_t type,
		     dns_rdataset_t *rdataset, dns_rdataset_t *sigrdataset,
		     dns_message_t *message, unsigned int options,
		     isc_loop_t *loop, isc_job_cb cb, void *arg,
		     isc_counter_t *nvalidations, isc_counter_t *nfails,
		     isc_counter_t *qc, isc_counter_t *gqc, fetchctx_t *parent,
		     dns_edectx_t *edectx, dns_validator_t **validatorp) {
	dns_validator_t *val = NULL;
	dns_keytable_t *kt = NULL;

	REQUIRE(name != NULL);
	REQUIRE(rdataset != NULL ||
		(rdataset == NULL && sigrdataset == NULL && message != NULL));
	REQUIRE(validatorp != NULL && *validatorp == NULL);
	REQUIRE(edectx != NULL);

	RETERR(dns_view_getsecroots(view, &kt));

	val = isc_mem_get(view->mctx, sizeof(*val));
	*val = (dns_validator_t){
		.tid = isc_tid(),
		.result = DNS_R_NOVALIDSIG,
		.rdataset = rdataset,
		.sigrdataset = sigrdataset,
		.name = name,
		.type = type,
		.options = options,
		.keytable = kt,
		.link = ISC_LINK_INITIALIZER,
		.loop = isc_loop_ref(loop),
		.cb = cb,
		.arg = arg,
		.rdata = DNS_RDATA_INIT,
		.cb_edectx = edectx,
		.parent_fetch = parent,
	};

	dns_ede_init(view->mctx, &val->edectx);

	isc_refcount_init(&val->references, 1);
	dns_view_attach(view, &val->view);
	if (message != NULL) {
		dns_message_attach(message, &val->message);
	}

	if (nfails != NULL) {
		isc_counter_attach(nfails, &val->nfails);
	}

	if (nvalidations != NULL) {
		isc_counter_attach(nvalidations, &val->nvalidations);
	}

	if (qc != NULL) {
		isc_counter_attach(qc, &val->qc);
	}
	if (gqc != NULL) {
		isc_counter_attach(gqc, &val->gqc);
	}

	dns_rdataset_init(&val->fdsset);
	dns_rdataset_init(&val->frdataset);
	dns_rdataset_init(&val->fsigrdataset);
	dns_rdataset_init(&val->dsrdataset);
	dns_fixedname_init(&val->wild);
	dns_fixedname_init(&val->closest);
	val->start = isc_stdtime_now();
	val->magic = VALIDATOR_MAGIC;

	if ((options & DNS_VALIDATOR_DEFER) == 0) {
		dns_validator_ref(val);
		(void)validate_async_run(val, validator_start);
	}

	*validatorp = val;

	return ISC_R_SUCCESS;
}

void
dns_validator_send(dns_validator_t *val) {
	REQUIRE(VALID_VALIDATOR(val));
	REQUIRE(val->tid == isc_tid());

	INSIST((val->options & DNS_VALIDATOR_DEFER) != 0);
	val->options &= ~DNS_VALIDATOR_DEFER;

	dns_validator_ref(val);
	(void)validate_async_run(val, validator_start);
}

static void
validator_cancel_finish(dns_validator_t *validator) {
	validator_log(validator, ISC_LOG_DEBUG(3), "validator_cancel_finish");

	if (CANCELING(validator) && !CANCELED(validator)) {
		if (validator->fetch != NULL) {
			dns_resolver_cancelfetch(validator->fetch);
		}
		if (validator->subvalidator != NULL) {
			dns_validator_cancel(validator->subvalidator);
		}
		if (!COMPLETE(validator)) {
			validator->options &= ~DNS_VALIDATOR_DEFER;
			validator_done(validator, ISC_R_CANCELED);
		}
		validator->attributes |= VALATTR_CANCELED;
	}
}

void
dns_validator_cancel(dns_validator_t *validator) {
	REQUIRE(VALID_VALIDATOR(validator));
	REQUIRE(validator->tid == isc_tid());

	validator_log(validator, ISC_LOG_DEBUG(3), "dns_validator_cancel");

	atomic_store(&validator->canceling, true);

	if (!OFFLOADED(validator)) {
		validator_cancel_finish(validator);
	}
}

static void
destroy_validator(dns_validator_t *val) {
	isc_mem_t *mctx = NULL;

	REQUIRE(val->fetch == NULL);
	REQUIRE(val->subvalidator == NULL);

	val->magic = 0;
	if (val->key != NULL) {
		dst_key_free(&val->key);
	}
	if (val->keytable != NULL) {
		dns_keytable_detach(&val->keytable);
	}
	disassociate_rdatasets(val);
	dns_rdataset_cleanup(&val->dsrdataset);
	mctx = val->view->mctx;
	if (val->siginfo != NULL) {
		isc_mem_put(mctx, val->siginfo, sizeof(*val->siginfo));
	}
	if (val->message != NULL) {
		dns_message_detach(&val->message);
	}
	if (val->nfails != NULL) {
		isc_counter_detach(&val->nfails);
	}
	if (val->nvalidations != NULL) {
		isc_counter_detach(&val->nvalidations);
	}
	if (val->qc != NULL) {
		isc_counter_detach(&val->qc);
	}
	if (val->gqc != NULL) {
		isc_counter_detach(&val->gqc);
	}

	dns_ede_invalidate(&val->edectx);

	dns_view_detach(&val->view);
	isc_loop_detach(&val->loop);

	isc_mem_put(mctx, val, sizeof(*val));
}

void
dns_validator_shutdown(dns_validator_t *val) {
	REQUIRE(VALID_VALIDATOR(val));
	REQUIRE(COMPLETE(val));
	REQUIRE(val->tid == isc_tid());

	validator_log(val, ISC_LOG_DEBUG(4), "dns_validator_shutdown");

	/*
	 * The validation is now complete and the owner is no longer interested
	 * in any further results. If there are still callback events queued up
	 * which hold a validator reference, they should not be allowed to use
	 * val->name during logging, because the owner may destroy it after this
	 * function is called.
	 */
	val->name = NULL;
}

static void
validator_logv(dns_validator_t *val, isc_logcategory_t category,
	       isc_logmodule_t module, int level, const char *fmt, va_list ap) {
	char msgbuf[2048];
	static const char spaces[] = "        *";
	int depth = val->depth * 2;
	const char *viewname, *sep1, *sep2;

	vsnprintf(msgbuf, sizeof(msgbuf), fmt, ap);

	if ((unsigned int)depth >= sizeof spaces) {
		depth = sizeof spaces - 1;
	}

	/*
	 * Log the view name unless it's:
	 * * "_default/IN" (which means there's only one view
	 *   configured in the server), or
	 * * "_dnsclient/IN" (which means this is being called
	 *   from an application using dns/client.c).
	 */
	if (val->view->rdclass == dns_rdataclass_in &&
	    (strcmp(val->view->name, "_default") == 0 ||
	     strcmp(val->view->name, DNS_CLIENTVIEW_NAME) == 0))
	{
		sep1 = viewname = sep2 = "";
	} else {
		sep1 = "view ";
		viewname = val->view->name;
		sep2 = ": ";
	}

	if (val->name != NULL) {
		char namebuf[DNS_NAME_FORMATSIZE];
		char typebuf[DNS_RDATATYPE_FORMATSIZE];

		dns_name_format(val->name, namebuf, sizeof(namebuf));
		dns_rdatatype_format(val->type, typebuf, sizeof(typebuf));
		isc_log_write(category, module, level,
			      "%s%s%s%.*svalidating %s/%s: %s", sep1, viewname,
			      sep2, depth, spaces, namebuf, typebuf, msgbuf);
	} else {
		isc_log_write(category, module, level,
			      "%s%s%s%.*svalidator @%p: %s", sep1, viewname,
			      sep2, depth, spaces, val, msgbuf);
	}
}

static void
validator_log(void *val, int level, const char *fmt, ...) {
	va_list ap;

	if (!isc_log_wouldlog(level)) {
		return;
	}

	va_start(ap, fmt);

	validator_logv(val, DNS_LOGCATEGORY_DNSSEC, DNS_LOGMODULE_VALIDATOR,
		       level, fmt, ap);
	va_end(ap);
}

static void
validator_logcreate(dns_validator_t *val, dns_name_t *name,
		    dns_rdatatype_t type, const char *caller,
		    const char *operation) {
	char namestr[DNS_NAME_FORMATSIZE];
	char typestr[DNS_RDATATYPE_FORMATSIZE];

	dns_name_format(name, namestr, sizeof(namestr));
	dns_rdatatype_format(type, typestr, sizeof(typestr));
	validator_log(val, ISC_LOG_DEBUG(9), "%s: creating %s for %s %s",
		      caller, operation, namestr, typestr);
}

static void
validator_addede(dns_validator_t *val, uint16_t code, const char *extra) {
	REQUIRE(VALID_VALIDATOR(val));

	char bdata[DNS_NAME_FORMATSIZE + DNS_RDATATYPE_FORMATSIZE +
		   DNS_EDE_EXTRATEXT_LEN];
	isc_buffer_t b;

	isc_buffer_init(&b, bdata, sizeof(bdata));

	if (extra != NULL) {
		isc_buffer_putstr(&b, extra);
		isc_buffer_putuint8(&b, ' ');
	}

	dns_name_totext(val->name, DNS_NAME_OMITFINALDOT, &b);
	isc_buffer_putuint8(&b, '/');
	dns_rdatatype_totext(val->type, &b);
	isc_buffer_putuint8(&b, '\0');

	dns_ede_add(&val->edectx, code, bdata);
}

static void
validate_extendederror(dns_validator_t *val) {
	dns_validator_t *edeval = val;
	char bdata[DNS_EDE_EXTRATEXT_LEN];
	isc_buffer_t b;

	REQUIRE(VALID_VALIDATOR(edeval));

	isc_buffer_init(&b, bdata, sizeof(bdata));

	while (edeval->parent != NULL) {
		edeval = edeval->parent;
	}

	if (val->unsupported_algorithm != 0) {
		isc_buffer_clear(&b);
		dns_secalg_totext(val->unsupported_algorithm, &b);
		isc_buffer_putuint8(&b, '\0');
		validator_addede(val, DNS_EDE_DNSKEYALG, bdata);
	}

	if (val->unsupported_digest != 0) {
		isc_buffer_clear(&b);
		dns_dsdigest_totext(val->unsupported_digest, &b);
		isc_buffer_putuint8(&b, '\0');
		validator_addede(val, DNS_EDE_DSDIGESTTYPE, bdata);
	}
}

#if DNS_VALIDATOR_TRACE
ISC_REFCOUNT_TRACE_IMPL(dns_validator, destroy_validator);
#else
ISC_REFCOUNT_IMPL(dns_validator, destroy_validator);
#endif
