From deadec4bb691ba030fda2d13006f0644366a3163 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Vladim=C3=ADr=20=C4=8Cun=C3=A1t?= <vladimir.cunat@nic.cz>
Date: Mon, 30 Mar 2020 17:28:26 +0200
Subject: [PATCH] fix DNAME support

---
 NEWS                    |   1 +
 daemon/lua/kres-gen.lua |   1 +
 doc/upgrading.rst       |   1 +
 lib/cache/api.c         |   4 +-
 lib/cache/peek.c        | 116 ++++++++++++++++++++++++++++++++++------
 lib/dnssec.h            |   1 +
 lib/layer/iterate.c     |  74 ++++++++++++++++---------
 lib/layer/validate.c    |  54 ++++++++++++++++++-
 lib/utils.c             |  10 ++--
 lib/utils.h             |   3 ++
 10 files changed, 215 insertions(+), 50 deletions(-)

diff --git a/NEWS b/NEWS
index 6584df92a..bc43716e0 100644
--- a/NEWS
+++ b/NEWS
@@ -14,6 +14,7 @@ Bugfixes
 - cache: missing filesystem support for pre-allocation is no longer fatal (#549)
 - lua: policy.rpz() no longer watches the file when watch is set to false (!954)
 - fix a strict aliasing problem that might've lead to "miscompilation" (!962)
+- fix handling of DNAMEs, especially signed ones (#234, !965)
 - lua resolve(): correctly include EDNS0 in the virtual packet (!963)
   Custom modules might have been confused by that.
 - do not leak bogus data into SERVFAIL answers (#396)
diff --git a/daemon/lua/kres-gen.lua b/daemon/lua/kres-gen.lua
index 5b9b69275..853d23d7a 100644
--- a/daemon/lua/kres-gen.lua
+++ b/daemon/lua/kres-gen.lua
@@ -129,6 +129,7 @@ struct ranked_rr_array_entry {
 	_Bool to_wire : 1;
 	_Bool expiring : 1;
 	_Bool in_progress : 1;
+	_Bool dont_cache : 1;
 	knot_rrset_t *rr;
 };
 typedef struct ranked_rr_array_entry ranked_rr_array_entry_t;
diff --git a/doc/upgrading.rst b/doc/upgrading.rst
index e35a264ff..b1b3a004e 100644
--- a/doc/upgrading.rst
+++ b/doc/upgrading.rst
@@ -18,6 +18,7 @@ Module changes
 
 * Modules which use :c:type:`kr_request.trace_log` handler need update to modified handler API. Example migration is `modules/watchdog/watchdog.lua <https://gitlab.labs.nic.cz/knot/knot-resolver/-/merge_requests/957/diffs#6831501329bbf9e494048fe269c6b02944fc227c>`_.
 * Modules which were using logger :c:func:`kr_log_qverbose_impl` need migration to new logger :c:func:`kr_log_q`. Example migration is `modules/rebinding/rebinding.lua <https://gitlab.labs.nic.cz/knot/knot-resolver/-/merge_requests/957/diffs#6c74dcae147221ca64286a3ed028057adb6813b9>`_.
+* Modules which were using :c:func:`kr_ranked_rrarray_add` should note that on success it no longer returns exclusively zero but index into the array (non-negative).  Error states are unchanged (negative).
 
 
 4.x to 5.x
diff --git a/lib/cache/api.c b/lib/cache/api.c
index fcf813743..17da2c106 100644
--- a/lib/cache/api.c
+++ b/lib/cache/api.c
@@ -378,9 +378,9 @@ int cache_stash(kr_layer_t *ctx, knot_pkt_t *pkt)
 		/* uncached entries are located at the end */
 		for (ssize_t i = arr->len - 1; i >= 0; --i) {
 			ranked_rr_array_entry_t *entry = arr->at[i];
-			if (entry->qry_uid != qry->uid) {
+			if (entry->qry_uid != qry->uid || entry->dont_cache) {
 				continue;
-				/* TODO: probably safe to break but maybe not worth it */
+				/* TODO: probably safe to break on uid mismatch but maybe not worth it */
 			}
 			int ret = stash_rrarray_entry(
 					arr, i, qry, cache, &unauth_cnt, nsec_pmap,
diff --git a/lib/cache/peek.c b/lib/cache/peek.c
index 16bfb9ff7..0d66bb630 100644
--- a/lib/cache/peek.c
+++ b/lib/cache/peek.c
@@ -16,6 +16,8 @@ static int closest_NS(struct kr_cache *cache, struct key *k, entry_list_t el,
 			struct kr_query *qry, bool only_NS, bool is_DS);
 static int answer_simple_hit(kr_layer_t *ctx, knot_pkt_t *pkt, uint16_t type,
 		const struct entry_h *eh, const void *eh_bound, uint32_t new_ttl);
+static int answer_dname_hit(kr_layer_t *ctx, knot_pkt_t *pkt, const knot_dname_t *dname_owner,
+		const struct entry_h *eh, const void *eh_bound, uint32_t new_ttl);
 static int try_wild(struct key *k, struct answer *ans, const knot_dname_t *clencl_name,
 		    uint16_t type, uint8_t lowest_rank,
 		    const struct kr_query *qry, struct kr_cache *cache);
@@ -161,12 +163,18 @@ int peek_nosync(kr_layer_t *ctx, knot_pkt_t *pkt)
 						KNOT_RRTYPE_CNAME, qry->timestamp.tv_sec);
 		ret = answer_simple_hit(ctx, pkt, KNOT_RRTYPE_CNAME, v.data,
 					knot_db_val_bound(v), new_ttl);
-		/* TODO: ^^ cumbersome code; we also recompute the TTL */
 		return ret == kr_ok() ? KR_STATE_DONE : ctx->state;
 		}
-	case KNOT_RRTYPE_DNAME:
-		VERBOSE_MSG(qry, "=> DNAME not supported yet\n"); // LATER
-		return ctx->state;
+	case KNOT_RRTYPE_DNAME: {
+		const knot_db_val_t v = el[EL_DNAME];
+		assert(v.data && v.len);
+		/* TTL: for simplicity, we just ask for TTL of the generated CNAME. */
+		const int32_t new_ttl = get_new_ttl(v.data, qry, qry->sname,
+						KNOT_RRTYPE_CNAME, qry->timestamp.tv_sec);
+		ret = answer_dname_hit(ctx, pkt, k->zname, v.data,
+					knot_db_val_bound(v), new_ttl);
+		return ret == kr_ok() ? KR_STATE_DONE : ctx->state;
+		}
 	}
 
 	/* We have to try proving from NSEC*. */
@@ -405,12 +413,25 @@ static int peek_encloser(
 	return -ABS(ENOENT);
 }
 
+static void answer_simple_qflags(struct kr_qflags *qf, const struct entry_h *eh,
+				 uint32_t new_ttl)
+{
+	/* Finishing touches. */
+	qf->EXPIRING = is_expiring(eh->ttl, new_ttl);
+	qf->CACHED = true;
+	qf->NO_MINIMIZE = true;
+	qf->DNSSEC_INSECURE = kr_rank_test(eh->rank, KR_RANK_INSECURE);
+	if (qf->DNSSEC_INSECURE) {
+		qf->DNSSEC_WANT = false;
+	}
+}
 
-static int answer_simple_hit(kr_layer_t *ctx, knot_pkt_t *pkt, uint16_t type,
-		const struct entry_h *eh, const void *eh_bound, uint32_t new_ttl)
 #define CHECK_RET(ret) do { \
 	if ((ret) < 0) { assert(false); return kr_error((ret)); } \
 } while (false)
+
+static int answer_simple_hit(kr_layer_t *ctx, knot_pkt_t *pkt, uint16_t type,
+		const struct entry_h *eh, const void *eh_bound, uint32_t new_ttl)
 {
 	struct kr_request *req = ctx->req;
 	struct kr_query *qry = req->current_query;
@@ -430,22 +451,71 @@ static int answer_simple_hit(kr_layer_t *ctx, knot_pkt_t *pkt, uint16_t type,
 	ret = pkt_append(pkt, &ans.rrsets[AR_ANSWER], eh->rank);
 	CHECK_RET(ret);
 
-	/* Finishing touches. */
-	struct kr_qflags * const qf = &qry->flags;
-	qf->EXPIRING = is_expiring(eh->ttl, new_ttl);
-	qf->CACHED = true;
-	qf->NO_MINIMIZE = true;
-	qf->DNSSEC_INSECURE = kr_rank_test(eh->rank, KR_RANK_INSECURE);
-	if (qf->DNSSEC_INSECURE) {
-		qf->DNSSEC_WANT = false;
-	}
+	answer_simple_qflags(&qry->flags, eh, new_ttl);
+
 	VERBOSE_MSG(qry, "=> satisfied by exact %s: rank 0%.2o, new TTL %d\n",
 			(type == KNOT_RRTYPE_CNAME ? "CNAME" : "RRset"),
 			eh->rank, new_ttl);
 	return kr_ok();
 }
-#undef CHECK_RET
 
+static int answer_dname_hit(kr_layer_t *ctx, knot_pkt_t *pkt, const knot_dname_t *dname_owner,
+		const struct entry_h *eh, const void *eh_bound, uint32_t new_ttl)
+{
+	struct kr_request *req = ctx->req;
+	struct kr_query *qry = req->current_query;
+
+	/* All OK, so start constructing the (pseudo-)packet. */
+	int ret = pkt_renew(pkt, qry->sname, qry->stype);
+	CHECK_RET(ret);
+
+	/* Materialize the DNAME for the answer in (pseudo-)packet. */
+	struct answer ans;
+	memset(&ans, 0, sizeof(ans));
+	ans.mm = &pkt->mm;
+	ret = entry2answer(&ans, AR_ANSWER, eh, eh_bound,
+			   dname_owner, KNOT_RRTYPE_DNAME, new_ttl);
+	CHECK_RET(ret);
+	/* Put link to the RRset into the pkt. */
+	ret = pkt_append(pkt, &ans.rrsets[AR_ANSWER], eh->rank);
+	CHECK_RET(ret);
+	const knot_dname_t *dname_target =
+		knot_dname_target(ans.rrsets[AR_ANSWER].set.rr->rrs.rdata);
+
+	/* Generate CNAME RRset for the answer in (pseudo-)packet. */
+	const int AR_CNAME = AR_SOA;
+	knot_rrset_t *rr = ans.rrsets[AR_CNAME].set.rr
+		= knot_rrset_new(qry->sname, KNOT_RRTYPE_CNAME, KNOT_CLASS_IN,
+				 new_ttl, ans.mm);
+	CHECK_RET(rr ? kr_ok() : -ENOMEM);
+	const knot_dname_t *cname_target = knot_dname_replace_suffix(qry->sname,
+			knot_dname_labels(dname_owner, NULL), dname_target, ans.mm);
+	CHECK_RET(cname_target ? kr_ok() : -ENOMEM);
+	const int rdata_len = knot_dname_size(cname_target);
+
+	if (rdata_len <= KNOT_DNAME_MAXLEN
+	    && knot_dname_labels(cname_target, NULL) <= KNOT_DNAME_MAXLABELS) {
+		/* Normal case: the target name fits. */
+		rr->rrs.count = 1;
+		rr->rrs.size = knot_rdata_size(rdata_len);
+		rr->rrs.rdata = mm_alloc(ans.mm, rr->rrs.size);
+		CHECK_RET(rr->rrs.rdata ? kr_ok() : -ENOMEM);
+		knot_rdata_init(rr->rrs.rdata, rdata_len, cname_target);
+		/* Put link to the RRset into the pkt. */
+		ret = pkt_append(pkt, &ans.rrsets[AR_CNAME], eh->rank);
+		CHECK_RET(ret);
+	} else {
+		/* Note that it's basically a successful answer; name just doesn't fit. */
+		knot_wire_set_rcode(pkt->wire, KNOT_RCODE_YXDOMAIN);
+	}
+
+	answer_simple_qflags(&qry->flags, eh, new_ttl);
+	VERBOSE_MSG(qry, "=> satisfied by DNAME+CNAME: rank 0%.2o, new TTL %d\n",
+			eh->rank, new_ttl);
+	return kr_ok();
+}
+
+#undef CHECK_RET
 
 /** TODO: description; see the single call site for now. */
 static int found_exact_hit(kr_layer_t *ctx, knot_pkt_t *pkt, knot_db_val_t val,
@@ -619,8 +689,22 @@ static int closest_NS(struct kr_cache *cache, struct key *k, entry_list_t el,
 		need_zero = false;
 		/* More types are possible; try in order.
 		 * For non-fatal failures just "continue;" to try the next type. */
+		/* Now a complication - we need to try EL_DNAME before NSEC*
+		 * (Unfortunately that's not easy to write very nicely.) */
+		if (!only_NS) {
+			const int i = EL_DNAME;
+			ret = check_NS_entry(k, el[i], i, exact_match, is_DS,
+						qry, timestamp);
+			if (ret < 0) goto next_label; else
+			if (!ret) {
+				/* We found our match. */
+				k->zlf_len = zlf_len;
+				return kr_ok();
+			}
+		}
 		const int el_count = only_NS ? EL_NS + 1 : EL_LENGTH;
 		for (int i = 0; i < el_count; ++i) {
+			if (i == EL_DNAME) continue;
 			ret = check_NS_entry(k, el[i], i, exact_match, is_DS,
 						qry, timestamp);
 			if (ret < 0) goto next_label; else
diff --git a/lib/dnssec.h b/lib/dnssec.h
index 170b3a87f..d9601eaa9 100644
--- a/lib/dnssec.h
+++ b/lib/dnssec.h
@@ -45,6 +45,7 @@ struct kr_rrset_validation_ctx {
 	uint32_t qry_uid;		/*!< Current query uid. */
 	uint32_t flags;			/*!< Output - Flags. */
 	uint32_t err_cnt;		/*!< Output - Number of validation failures. */
+	uint32_t cname_norrsig_cnt;	/*!< Output - Number of CNAMEs missing RRSIGs. */
 	int result;			/*!< Output - 0 or error code. */
 	struct {
 		unsigned int matching_name_type;	/*!< Name + type matches */
diff --git a/lib/layer/iterate.c b/lib/layer/iterate.c
index 6558c50b7..3d2a93cb2 100644
--- a/lib/layer/iterate.c
+++ b/lib/layer/iterate.c
@@ -374,7 +374,7 @@ static int pick_authority(knot_pkt_t *pkt, struct kr_request *req, bool to_wire)
 						qry->flags.FORWARD || referral);
 		int ret = kr_ranked_rrarray_add(&req->auth_selected, rr,
 						rank, to_wire, qry->uid, &req->pool);
-		if (ret != kr_ok()) {
+		if (ret < 0) {
 			return ret;
 		}
 	}
@@ -498,6 +498,8 @@ static int unroll_cname(knot_pkt_t *pkt, struct kr_request *req, bool referral,
 		/* CNAME was found at previous iteration, but records may not follow the correct order.
 		 * Try to find records for pending_cname owner from section start. */
 		cname = pending_cname;
+		size_t cname_answ_selected_i = -1;
+		bool cname_is_occluded = false; /* whether `cname` is in a DNAME's bailiwick */
 		pending_cname = NULL;
 		const int cname_labels = knot_dname_labels(cname, NULL);
 		for (unsigned i = 0; i < an->count; ++i) {
@@ -506,13 +508,33 @@ static int unroll_cname(knot_pkt_t *pkt, struct kr_request *req, bool referral,
 			/* Skip the RR if its owner+type doesn't interest us. */
 			const uint16_t type = kr_rrset_type_maysig(rr);
 			const bool type_OK = rr->type == query->stype || type == query->stype
-				|| type == KNOT_RRTYPE_CNAME || type == KNOT_RRTYPE_DNAME;
-				/* TODO: actually handle DNAMEs */
-			if (rr->rclass != KNOT_CLASS_IN || !type_OK
-			    || !knot_dname_is_equal(rr->owner, cname)
+						|| type == KNOT_RRTYPE_CNAME;
+			if (rr->rclass != KNOT_CLASS_IN
 			    || knot_dname_in_bailiwick(rr->owner, query->zone_cut.name) < 0) {
 				continue;
 			}
+			const bool all_OK = type_OK && knot_dname_is_equal(rr->owner, cname);
+
+			const bool to_wire = is_final && !referral;
+
+			if (!all_OK && type == KNOT_RRTYPE_DNAME
+					&& knot_dname_in_bailiwick(cname, rr->owner) >= 1) {
+				/* This DNAME (or RRSIGs) cover the current target (`cname`),
+				 * so it is interesting and will occlude its CNAME.
+				 * We rely on CNAME being sent along with DNAME
+				 * (mandatory unless YXDOMAIN). */
+				cname_is_occluded = true;
+				uint8_t rank = get_initial_rank(rr, query, true,
+						query->flags.FORWARD || referral);
+				int ret = kr_ranked_rrarray_add(&req->answ_selected, rr,
+						rank, to_wire, query->uid, &req->pool);
+				if (ret < 0) {
+					return KR_STATE_FAIL;
+				}
+			}
+			if (!all_OK) {
+				continue;
+			}
 
 			if (rr->type == KNOT_RRTYPE_RRSIG) {
 				int rrsig_labels = knot_rrsig_labels(rr->rrs.rdata);
@@ -528,39 +550,38 @@ static int unroll_cname(knot_pkt_t *pkt, struct kr_request *req, bool referral,
 			}
 
 			/* Process records matching current SNAME */
-			int state = KR_STATE_FAIL;
-			bool to_wire = false;
-			if (is_final) {
-				/* if not referral, mark record to be written to final answer */
-				to_wire = !referral;
-			} else {
+			if (!is_final) {
 				int cnt_ = 0;
-				state = update_nsaddr(rr, query->parent, &cnt_);
+				int state = update_nsaddr(rr, query->parent, &cnt_);
 				if (state & KR_STATE_FAIL) {
 					return state;
 				}
 			}
 			uint8_t rank = get_initial_rank(rr, query, true,
 					query->flags.FORWARD || referral);
-			state = kr_ranked_rrarray_add(&req->answ_selected, rr,
-						      rank, to_wire, query->uid, &req->pool);
-			if (state != kr_ok()) {
+			int ret = kr_ranked_rrarray_add(&req->answ_selected, rr,
+						rank, to_wire, query->uid, &req->pool);
+			if (ret < 0) {
 				return KR_STATE_FAIL;
 			}
-			/* Jump to next CNAME target */
-			if ((query->stype == KNOT_RRTYPE_CNAME) || (rr->type != KNOT_RRTYPE_CNAME)) {
-				continue;
-			}
-			pending_cname = knot_cname_name(rr->rrs.rdata);
-			if (!pending_cname) {
-				break;
+			cname_answ_selected_i = ret;
+
+			/* Select the next CNAME target, but don't jump immediately.
+			 * There can be records for "old" cname (RRSIGs are interesting);
+			 * more importantly there might be a DNAME for `cname_is_occluded`. */
+			if (query->stype != KNOT_RRTYPE_CNAME && rr->type == KNOT_RRTYPE_CNAME) {
+				pending_cname = knot_cname_name(rr->rrs.rdata);
+				if (!pending_cname) {
+					break;
+				}
 			}
-			/* Don't use pending_cname immediately.
-			 * There are can be records for "old" cname. */
 		}
 		if (!pending_cname) {
 			break;
 		}
+		if (cname_is_occluded) {
+			req->answ_selected.at[cname_answ_selected_i]->dont_cache = true;
+		}
 		if (++(query->cname_depth) > KR_CNAME_CHAIN_LIMIT) {
 			VERBOSE_MSG("<= error: CNAME chain exceeded max length %d\n",
 					/* people count objects from 0, no CNAME = 0 */
@@ -828,7 +849,7 @@ static int process_stub(knot_pkt_t *pkt, struct kr_request *req)
 		/* KR_RANK_AUTH: we don't have the records directly from
 		 * an authoritative source, but we do trust the server and it's
 		 * supposed to only send us authoritative records. */
-		if (err != kr_ok()) {
+		if (err < 0) {
 			return KR_STATE_FAIL;
 		}
 	}
@@ -1068,6 +1089,9 @@ static int resolve(kr_layer_t *ctx, knot_pkt_t *pkt)
 	case KNOT_RCODE_NOERROR:
 	case KNOT_RCODE_NXDOMAIN:
 		break; /* OK */
+	case KNOT_RCODE_YXDOMAIN: /* Basically a successful answer; name just doesn't fit. */
+		knot_wire_set_rcode(req->answer->wire, KNOT_RCODE_YXDOMAIN);
+		break;
 	case KNOT_RCODE_REFUSED:
 	case KNOT_RCODE_SERVFAIL:
 		if (query->flags.STUB) {
diff --git a/lib/layer/validate.c b/lib/layer/validate.c
index c60858b43..2fa89d23d 100644
--- a/lib/layer/validate.c
+++ b/lib/layer/validate.c
@@ -82,6 +82,32 @@ static void log_bogus_rrsig(kr_rrset_validation_ctx_t *vctx, const struct kr_que
 	}
 }
 
+/** Check that given CNAME could be generated by given DNAME (no DNSSEC validation). */
+static bool cname_matches_dname(const knot_rrset_t *rr_cn, const knot_rrset_t *rr_dn)
+{
+	assert(rr_cn->type == KNOT_RRTYPE_CNAME && rr_dn->type == KNOT_RRTYPE_DNAME);
+	/* When DNAME substitution happens, let's consider the "prefix"
+	 * that is carried over and the "suffix" that is replaced.
+	 * (Here we consider the label order used in wire and presentation.) */
+	const int prefix_labels = knot_dname_in_bailiwick(rr_cn->owner, rr_dn->owner);
+	if (prefix_labels < 1)
+		return false;
+	const knot_dname_t *cn_target = knot_cname_name(rr_cn->rrs.rdata);
+	const knot_dname_t *dn_target = knot_dname_target(rr_dn->rrs.rdata);
+		/* ^ We silently use the first RR in each RRset.  Could be e.g. logged. */
+	/* Check that the suffixes are correct - and even prefix label counts. */
+	if (knot_dname_in_bailiwick(cn_target, dn_target) != prefix_labels)
+		return false;
+	/* Check that prefixes match.  Find end of the first one and compare. */
+	const knot_dname_t *cn_se = rr_cn->owner;
+	for (int i = 0; i < prefix_labels; ++i)
+		cn_se += 1 + *cn_se;
+	return strncmp((const char *)rr_cn->owner, (const char *)cn_target,
+			cn_se - rr_cn->owner) == 0;
+		/* ^ We use the fact that dnames are always zero-terminated
+		 * to avoid any possible over-read in cn_target. */
+}
+
 static int validate_section(kr_rrset_validation_ctx_t *vctx, const struct kr_query *qry,
 			    knot_mm_t *pool)
 {
@@ -94,7 +120,6 @@ static int validate_section(kr_rrset_validation_ctx_t *vctx, const struct kr_que
 	 */
 	vctx->zone_name = vctx->keys ? vctx->keys->owner : NULL;
 
-	int validation_result = 0;
 	for (ssize_t i = 0; i < vctx->rrs->len; ++i) {
 		ranked_rr_array_entry_t *entry = vctx->rrs->at[i];
 		knot_rrset_t * const rr = entry->rr;
@@ -121,7 +146,26 @@ static int validate_section(kr_rrset_validation_ctx_t *vctx, const struct kr_que
 		}
 
 		uint8_t rank_orig = entry->rank;
-		validation_result = kr_rrset_validate(vctx, rr);
+		int validation_result = kr_rrset_validate(vctx, rr);
+
+		/* Handle the case of CNAMEs synthesized from DNAMEs (they don't have RRSIGs). */
+		if (rr->type == KNOT_RRTYPE_CNAME && validation_result == kr_error(ENOENT)) {
+			for (ssize_t j = 0; j < vctx->rrs->len; ++j) {
+				ranked_rr_array_entry_t *e_dname = vctx->rrs->at[j];
+				if ((e_dname->rr->type == KNOT_RRTYPE_DNAME)
+				    /* If the order is wrong, we will need two passes. */
+				    && kr_rank_test(e_dname->rank, KR_RANK_SECURE)
+				    && cname_matches_dname(rr, e_dname->rr)) {
+					/* Now we believe the CNAME is OK. */
+					validation_result = kr_ok();
+					break;
+				}
+			}
+			if (validation_result != kr_ok()) {
+				vctx->cname_norrsig_cnt += 1;
+			}
+		}
+
 		if (validation_result == kr_ok()) {
 			kr_rank_set(&entry->rank, KR_RANK_SECURE);
 
@@ -172,10 +216,16 @@ static int validate_records(struct kr_request *req, knot_pkt_t *answer, knot_mm_
 		.has_nsec3	= has_nsec3,
 		.flags		= 0,
 		.err_cnt	= 0,
+		.cname_norrsig_cnt = 0,
 		.result		= 0
 	};
 
 	int ret = validate_section(&vctx, qry, pool);
+	if (vctx.err_cnt && vctx.err_cnt == vctx.cname_norrsig_cnt) {
+		VERBOSE_MSG(qry, ">< all validation errors are missing RRSIGs on CNAMES, trying again in hope for DNAMEs\n");
+		vctx.err_cnt = vctx.cname_norrsig_cnt = vctx.result = 0;
+		ret = validate_section(&vctx, qry, pool);
+	}
 	req->answ_validated = (vctx.err_cnt == 0);
 	if (ret != kr_ok()) {
 		return ret;
diff --git a/lib/utils.c b/lib/utils.c
index 2755d65f6..2be3ca60b 100644
--- a/lib/utils.c
+++ b/lib/utils.c
@@ -755,7 +755,7 @@ int kr_ranked_rrarray_add(ranked_rr_array_t *array, const knot_rrset_t *rr,
 				abort();
 			}
 		}
-		return kr_ok();
+		return i;
 	}
 
 	/* No stashed rrset found, add */
@@ -768,6 +768,7 @@ int kr_ranked_rrarray_add(ranked_rr_array_t *array, const knot_rrset_t *rr,
 	if (!entry) {
 		return kr_error(ENOMEM);
 	}
+	memset(entry, 0, sizeof(*entry)); /* default all to zeros */
 
 	knot_rrset_t *rr_new = knot_rrset_new(rr->owner, rr->type, rr->rclass, rr->ttl, pool);
 	if (!rr_new) {
@@ -780,9 +781,6 @@ int kr_ranked_rrarray_add(ranked_rr_array_t *array, const knot_rrset_t *rr,
 	entry->qry_uid = qry_uid;
 	entry->rr = rr_new;
 	entry->rank = rank;
-	entry->revalidation_cnt = 0;
-	entry->cached = false;
-	entry->yielded = false;
 	entry->to_wire = to_wire;
 	entry->in_progress = true;
 	if (array_push(*array, entry) < 0) {
@@ -792,7 +790,9 @@ int kr_ranked_rrarray_add(ranked_rr_array_t *array, const knot_rrset_t *rr,
 		return kr_error(ENOMEM);
 	}
 
-	return to_wire_ensure_unique(array, array->len - 1);
+	ret = to_wire_ensure_unique(array, array->len - 1);
+	if (ret < 0) return ret;
+	return array->len - 1;
 }
 
 /** Comparator for qsort() on an array of knot_data_t pointers. */
diff --git a/lib/utils.h b/lib/utils.h
index 886ef8711..55e280fdf 100644
--- a/lib/utils.h
+++ b/lib/utils.h
@@ -204,6 +204,7 @@ struct ranked_rr_array_entry {
 	bool to_wire : 1; /**< whether to be put into the answer */
 	bool expiring : 1; /**< low remaining TTL; see is_expiring; only used in cache ATM */
 	bool in_progress : 1; /**< build of RRset in progress, i.e. different format of RR data */
+	bool dont_cache : 1; /**< avoid caching; useful e.g. for generated data */
 	knot_rrset_t *rr;
 };
 typedef struct ranked_rr_array_entry ranked_rr_array_entry_t;
@@ -420,6 +421,8 @@ int kr_rrkey(char *key, uint16_t class, const knot_dname_t *owner,
  *
  * To convert to standard RRs inside, you need to call _finalize() afterwards,
  * and the memory of rr->rrs.rdata has to remain until then.
+ *
+ * \return array index (>= 0) or error code (< 0)
  */
 KR_EXPORT
 int kr_ranked_rrarray_add(ranked_rr_array_t *array, const knot_rrset_t *rr,
-- 
GitLab