a3e803
From 93aec4d3d80a0d1cdb6553f70f35a2e2cb1fbaa8 Mon Sep 17 00:00:00 2001
a3e803
From: =?UTF-8?q?Petr=20Men=C5=A1=C3=ADk?= <pemensik@redhat.com>
a3e803
Date: Tue, 11 Apr 2017 16:19:51 +0200
a3e803
Subject: [PATCH 2/3] 4578.   [security]      Some chaining (CNAME or DNAME)
a3e803
 responses to upstream                         queries could trigger assertion
a3e803
 failures.                         (CVE-2017-3137) [RT #44734]
a3e803
a3e803
(including part of commit fea8a9d)
a3e803
---
a3e803
 bin/tests/system/dname/ans3/ans.pl    |  16 +-
a3e803
 bin/tests/system/dname/ns1/root.db    |   2 +-
a3e803
 bin/tests/system/dname/ns2/example.db |   3 +-
a3e803
 bin/tests/system/dname/tests.sh       |  17 +-
a3e803
 lib/dns/name.c                        |   2 -
a3e803
 lib/dns/resolver.c                    | 850 +++++++++++++---------------------
a3e803
 6 files changed, 349 insertions(+), 541 deletions(-)
a3e803
a3e803
diff --git a/bin/tests/system/dname/ans3/ans.pl b/bin/tests/system/dname/ans3/ans.pl
a3e803
index 271fc7d..af338fe 100644
a3e803
--- a/bin/tests/system/dname/ans3/ans.pl
a3e803
+++ b/bin/tests/system/dname/ans3/ans.pl
a3e803
@@ -1,10 +1,18 @@
a3e803
 #!/usr/bin/env perl
a3e803
 #
a3e803
-# Copyright (C) 2014-2016  Internet Systems Consortium, Inc. ("ISC")
a3e803
+# Copyright (C) 2017  Internet Systems Consortium, Inc. ("ISC")
a3e803
 #
a3e803
-# This Source Code Form is subject to the terms of the Mozilla Public
a3e803
-# License, v. 2.0. If a copy of the MPL was not distributed with this
a3e803
-# file, You can obtain one at http://mozilla.org/MPL/2.0/.
a3e803
+# Permission to use, copy, modify, and/or distribute this software for any
a3e803
+# purpose with or without fee is hereby granted, provided that the above
a3e803
+# copyright notice and this permission notice appear in all copies.
a3e803
+#
a3e803
+# THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
a3e803
+# REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
a3e803
+# AND FITNESS.  IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
a3e803
+# INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
a3e803
+# LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
a3e803
+# OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
a3e803
+# PERFORMANCE OF THIS SOFTWARE.
a3e803
 
a3e803
 use strict;
a3e803
 use warnings;
a3e803
diff --git a/bin/tests/system/dname/ns1/root.db b/bin/tests/system/dname/ns1/root.db
a3e803
index 2e84ae0..3d55ace 100644
a3e803
--- a/bin/tests/system/dname/ns1/root.db
a3e803
+++ b/bin/tests/system/dname/ns1/root.db
a3e803
@@ -1,4 +1,4 @@
a3e803
-; Copyright (C) 2011  Internet Systems Consortium, Inc. ("ISC")
a3e803
+; Copyright (C) 2011, 2017  Internet Systems Consortium, Inc. ("ISC")
a3e803
 ;
a3e803
 ; Permission to use, copy, modify, and/or distribute this software for any
a3e803
 ; purpose with or without fee is hereby granted, provided that the above
a3e803
diff --git a/bin/tests/system/dname/ns2/example.db b/bin/tests/system/dname/ns2/example.db
a3e803
index 4289134..c0193de 100644
a3e803
--- a/bin/tests/system/dname/ns2/example.db
a3e803
+++ b/bin/tests/system/dname/ns2/example.db
a3e803
@@ -1,4 +1,4 @@
a3e803
-; Copyright (C) 2011  Internet Systems Consortium, Inc. ("ISC")
a3e803
+; Copyright (C) 2011, 2017  Internet Systems Consortium, Inc. ("ISC")
a3e803
 ;
a3e803
 ; Permission to use, copy, modify, and/or distribute this software for any
a3e803
 ; purpose with or without fee is hereby granted, provided that the above
a3e803
@@ -29,6 +29,7 @@ a.short			A	10.0.0.1
a3e803
 short-dname		DNAME	short
a3e803
 a.longlonglonglonglonglonglonglonglonglonglonglonglong	A 10.0.0.2
a3e803
 long-dname		DNAME	longlonglonglonglonglonglonglonglonglonglonglonglong
a3e803
+toolong-dname		DNAME	longlonglonglonglonglonglonglonglonglonglonglonglong
a3e803
 cname			CNAME	a.cnamedname
a3e803
 cnamedname		DNAME	target
a3e803
 a.target		A	10.0.0.3
a3e803
diff --git a/bin/tests/system/dname/tests.sh b/bin/tests/system/dname/tests.sh
a3e803
index 6dc9e88..1487bd9 100644
a3e803
--- a/bin/tests/system/dname/tests.sh
a3e803
+++ b/bin/tests/system/dname/tests.sh
a3e803
@@ -1,6 +1,6 @@
a3e803
 #!/bin/sh
a3e803
 #
a3e803
-# Copyright (C) 2011, 2012  Internet Systems Consortium, Inc. ("ISC")
a3e803
+# Copyright (C) 2011, 2012, 2017  Internet Systems Consortium, Inc. ("ISC")
a3e803
 #
a3e803
 # Permission to use, copy, modify, and/or distribute this software for any
a3e803
 # purpose with or without fee is hereby granted, provided that the above
a3e803
@@ -57,10 +57,19 @@ grep "status: YXDOMAIN" dig.out.ns2.toolong > /dev/null || ret=1
a3e803
 if [ $ret != 0 ]; then echo "I:failed"; fi
a3e803
 status=`expr $status + $ret`
a3e803
 
a3e803
-echo "I:checking (too) long dname from recursive"
a3e803
+echo "I:checking (too) long dname from recursive with cached DNAME"
a3e803
 ret=0
a3e803
-$DIG 01234567890123456789012345678901234567890123456789.longlonglonglonglonglonglonglonglonglonglonglonglonglonglong.longlonglonglonglonglonglonglonglonglonglonglonglonglonglong.longlonglonglonglonglonglonglonglonglonglonglonglonglonglong.long-dname.example @10.53.0.4 a -p 5300 > dig.out.ns4.toolong || ret=1
a3e803
-grep "status: YXDOMAIN" dig.out.ns4.toolong > /dev/null || ret=1
a3e803
+$DIG 01234567890123456789012345678901234567890123456789.longlonglonglonglonglonglonglonglonglonglonglonglonglonglong.longlonglonglonglonglonglonglonglonglonglonglonglonglonglong.longlonglonglonglonglonglonglonglonglonglonglonglonglonglong.long-dname.example @10.53.0.4 a -p 5300 > dig.out.ns4.cachedtoolong || ret=1
a3e803
+grep "status: YXDOMAIN" dig.out.ns4.cachedtoolong > /dev/null || ret=1
a3e803
+grep '^long-dname\.example\..*DNAME.*long' dig.out.ns4.cachedtoolong > /dev/null || ret=1
a3e803
+if [ $ret != 0 ]; then echo "I:failed"; fi
a3e803
+status=`expr $status + $ret`
a3e803
+
a3e803
+echo "I:checking (too) long dname from recursive without cached DNAME"
a3e803
+ret=0
a3e803
+$DIG 01234567890123456789012345678901234567890123456789.longlonglonglonglonglonglonglonglonglonglonglonglonglonglong.longlonglonglonglonglonglonglonglonglonglonglonglonglonglong.longlonglonglonglonglonglonglonglonglonglonglonglonglong.toolong-dname.example @10.53.0.4 a -p 5300 > dig.out.ns4.uncachedtoolong || ret=1
a3e803
+grep "status: YXDOMAIN" dig.out.ns4.uncachedtoolong > /dev/null || ret=1
a3e803
+grep '^toolong-dname\.example\..*DNAME.*long' dig.out.ns4.uncachedtoolong > /dev/null || ret=1
a3e803
 if [ $ret != 0 ]; then echo "I:failed"; fi
a3e803
 status=`expr $status + $ret`
a3e803
 
a3e803
diff --git a/lib/dns/name.c b/lib/dns/name.c
a3e803
index 93173ee..d02e713 100644
a3e803
--- a/lib/dns/name.c
a3e803
+++ b/lib/dns/name.c
a3e803
@@ -2119,11 +2119,9 @@ dns_name_split(dns_name_t *name, unsigned int suffixlabels,
a3e803
 	REQUIRE(prefix != NULL || suffix != NULL);
a3e803
 	REQUIRE(prefix == NULL ||
a3e803
 		(VALID_NAME(prefix) &&
a3e803
-		 prefix->buffer != NULL &&
a3e803
 		 BINDABLE(prefix)));
a3e803
 	REQUIRE(suffix == NULL ||
a3e803
 		(VALID_NAME(suffix) &&
a3e803
-		 suffix->buffer != NULL &&
a3e803
 		 BINDABLE(suffix)));
a3e803
 
a3e803
 	splitlabel = name->labels - suffixlabels;
a3e803
diff --git a/lib/dns/resolver.c b/lib/dns/resolver.c
a3e803
index c3607fa..860a792 100644
a3e803
--- a/lib/dns/resolver.c
a3e803
+++ b/lib/dns/resolver.c
a3e803
@@ -3817,6 +3817,7 @@ is_lame(fetchctx_t *fctx) {
a3e803
 	isc_result_t result;
a3e803
 
a3e803
 	if (message->rcode != dns_rcode_noerror &&
a3e803
+	    message->rcode != dns_rcode_yxdomain &&
a3e803
 	    message->rcode != dns_rcode_nxdomain)
a3e803
 		return (ISC_FALSE);
a3e803
 
a3e803
@@ -5386,79 +5387,6 @@ chase_additional(fetchctx_t *fctx) {
a3e803
 		goto again;
a3e803
 }
a3e803
 
a3e803
-static inline isc_result_t
a3e803
-cname_target(dns_rdataset_t *rdataset, dns_name_t *tname) {
a3e803
-	isc_result_t result;
a3e803
-	dns_rdata_t rdata = DNS_RDATA_INIT;
a3e803
-	dns_rdata_cname_t cname;
a3e803
-
a3e803
-	result = dns_rdataset_first(rdataset);
a3e803
-	if (result != ISC_R_SUCCESS)
a3e803
-		return (result);
a3e803
-	dns_rdataset_current(rdataset, &rdata);
a3e803
-	result = dns_rdata_tostruct(&rdata, &cname, NULL);
a3e803
-	if (result != ISC_R_SUCCESS)
a3e803
-		return (result);
a3e803
-	dns_name_init(tname, NULL);
a3e803
-	dns_name_clone(&cname.cname, tname);
a3e803
-	dns_rdata_freestruct(&cname);
a3e803
-
a3e803
-	return (ISC_R_SUCCESS);
a3e803
-}
a3e803
-
a3e803
-/*%
a3e803
- * Construct the synthesised CNAME from the existing QNAME and
a3e803
- * the DNAME RR and store it in 'target'.
a3e803
- */
a3e803
-static inline isc_result_t
a3e803
-dname_target(dns_rdataset_t *rdataset, dns_name_t *qname,
a3e803
-	     unsigned int nlabels, dns_name_t *target)
a3e803
-{
a3e803
-	isc_result_t result;
a3e803
-	dns_rdata_t rdata = DNS_RDATA_INIT;
a3e803
-	dns_rdata_dname_t dname;
a3e803
-	dns_fixedname_t prefix;
a3e803
-
a3e803
-	/*
a3e803
-	 * Get the target name of the DNAME.
a3e803
-	 */
a3e803
-	result = dns_rdataset_first(rdataset);
a3e803
-	if (result != ISC_R_SUCCESS)
a3e803
-		return (result);
a3e803
-	dns_rdataset_current(rdataset, &rdata);
a3e803
-	result = dns_rdata_tostruct(&rdata, &dname, NULL);
a3e803
-	if (result != ISC_R_SUCCESS)
a3e803
-		return (result);
a3e803
-
a3e803
-	dns_fixedname_init(&prefix);
a3e803
-	dns_name_split(qname, nlabels, dns_fixedname_name(&prefix), NULL);
a3e803
-	result = dns_name_concatenate(dns_fixedname_name(&prefix),
a3e803
-				      &dname.dname, target, NULL);
a3e803
-	dns_rdata_freestruct(&dname);
a3e803
-	return (result);
a3e803
-}
a3e803
-
a3e803
-/*%
a3e803
- * Check if it was possible to construct 'qname' from 'lastcname'
a3e803
- * and 'rdataset'.
a3e803
- */
a3e803
-static inline isc_result_t
a3e803
-fromdname(dns_rdataset_t *rdataset, dns_name_t *lastcname,
a3e803
-	  unsigned int nlabels, const dns_name_t *qname)
a3e803
-{
a3e803
-	dns_fixedname_t fixed;
a3e803
-	isc_result_t result;
a3e803
-	dns_name_t *target;
a3e803
-
a3e803
-	dns_fixedname_init(&fixed);
a3e803
-	target = dns_fixedname_name(&fixed);
a3e803
-	result = dname_target(rdataset, lastcname, nlabels, target);
a3e803
-	if (result != ISC_R_SUCCESS || !dns_name_equal(qname, target))
a3e803
-		return (ISC_R_NOTFOUND);
a3e803
-
a3e803
-	return (ISC_R_SUCCESS);
a3e803
-}
a3e803
-
a3e803
 static isc_boolean_t
a3e803
 is_answeraddress_allowed(dns_view_t *view, dns_name_t *name,
a3e803
 			 dns_rdataset_t *rdataset)
a3e803
@@ -5534,9 +5462,8 @@ is_answeraddress_allowed(dns_view_t *view, dns_name_t *name,
a3e803
 }
a3e803
 
a3e803
 static isc_boolean_t
a3e803
-is_answertarget_allowed(dns_view_t *view, dns_name_t *name,
a3e803
-			dns_rdatatype_t type, dns_name_t *tname,
a3e803
-			dns_name_t *domain)
a3e803
+is_answertarget_allowed(fetchctx_t *fctx, dns_name_t *qname, dns_name_t *rname,
a3e803
+			dns_rdataset_t *rdataset, isc_boolean_t *chainingp)
a3e803
 {
a3e803
 	isc_result_t result;
a3e803
 	dns_rbtnode_t *node = NULL;
a3e803
@@ -5544,8 +5471,57 @@ is_answertarget_allowed(dns_view_t *view, dns_name_t *name,
a3e803
 	char tnamebuf[DNS_NAME_FORMATSIZE];
a3e803
 	char classbuf[64];
a3e803
 	char typebuf[64];
a3e803
+	dns_name_t *tname = NULL;
a3e803
+	dns_rdata_cname_t cname;
a3e803
+	dns_rdata_dname_t dname;
a3e803
+	dns_view_t *view = fctx->res->view;
a3e803
+	dns_rdata_t rdata = DNS_RDATA_INIT;
a3e803
+	unsigned int nlabels;
a3e803
+	dns_fixedname_t fixed;
a3e803
+	dns_name_t prefix;
a3e803
+
a3e803
+	REQUIRE(rdataset != NULL);
a3e803
+	REQUIRE(rdataset->type == dns_rdatatype_cname ||
a3e803
+		rdataset->type == dns_rdatatype_dname);
a3e803
+
a3e803
+	/*
a3e803
+	 * By default, we allow any target name.
a3e803
+	 * If newqname != NULL we also need to extract the newqname.
a3e803
+	 */
a3e803
+	if (chainingp == NULL && view->denyanswernames == NULL)
a3e803
+		return (ISC_TRUE);
a3e803
+
a3e803
+	result = dns_rdataset_first(rdataset);
a3e803
+	RUNTIME_CHECK(result == ISC_R_SUCCESS);
a3e803
+	dns_rdataset_current(rdataset, &rdata);
a3e803
+	switch (rdataset->type) {
a3e803
+	case dns_rdatatype_cname:
a3e803
+		result = dns_rdata_tostruct(&rdata, &cname, NULL);
a3e803
+		RUNTIME_CHECK(result == ISC_R_SUCCESS);
a3e803
+		tname = &cname.cname;
a3e803
+		break;
a3e803
+	case dns_rdatatype_dname:
a3e803
+		result = dns_rdata_tostruct(&rdata, &dname, NULL);
a3e803
+		RUNTIME_CHECK(result == ISC_R_SUCCESS);
a3e803
+		dns_name_init(&prefix, NULL);
a3e803
+		dns_fixedname_init(&fixed);
a3e803
+		tname = dns_fixedname_name(&fixed);
a3e803
+		nlabels = dns_name_countlabels(qname) -
a3e803
+			  dns_name_countlabels(rname);
a3e803
+		dns_name_split(qname, nlabels, &prefix, NULL);
a3e803
+		result = dns_name_concatenate(&prefix, &dname.dname, tname,
a3e803
+					      NULL);
a3e803
+		if (result == DNS_R_NAMETOOLONG)
a3e803
+			return (ISC_TRUE);
a3e803
+		RUNTIME_CHECK(result == ISC_R_SUCCESS);
a3e803
+		break;
a3e803
+	default:
a3e803
+		INSIST(0);
a3e803
+	}
a3e803
+
a3e803
+	if (chainingp != NULL)
a3e803
+		*chainingp = ISC_TRUE;
a3e803
 
a3e803
-	/* By default, we allow any target name. */
a3e803
 	if (view->denyanswernames == NULL)
a3e803
 		return (ISC_TRUE);
a3e803
 
a3e803
@@ -5554,8 +5530,8 @@ is_answertarget_allowed(dns_view_t *view, dns_name_t *name,
a3e803
 	 * or partially, allow it.
a3e803
 	 */
a3e803
 	if (view->answernames_exclude != NULL) {
a3e803
-		result = dns_rbt_findnode(view->answernames_exclude, name, NULL,
a3e803
-					  &node, NULL, 0, NULL, NULL);
a3e803
+		result = dns_rbt_findnode(view->answernames_exclude, qname,
a3e803
+					  NULL, &node, NULL, 0, NULL, NULL);
a3e803
 		if (result == ISC_R_SUCCESS || result == DNS_R_PARTIALMATCH)
a3e803
 			return (ISC_TRUE);
a3e803
 	}
a3e803
@@ -5563,7 +5539,7 @@ is_answertarget_allowed(dns_view_t *view, dns_name_t *name,
a3e803
 	/*
a3e803
 	 * If the target name is a subdomain of the search domain, allow it.
a3e803
 	 */
a3e803
-	if (dns_name_issubdomain(tname, domain))
a3e803
+	if (dns_name_issubdomain(tname, &fctx->domain))
a3e803
 		return (ISC_TRUE);
a3e803
 
a3e803
 	/*
a3e803
@@ -5572,9 +5548,9 @@ is_answertarget_allowed(dns_view_t *view, dns_name_t *name,
a3e803
 	result = dns_rbt_findnode(view->denyanswernames, tname, NULL, &node,
a3e803
 				  NULL, 0, NULL, NULL);
a3e803
 	if (result == ISC_R_SUCCESS || result == DNS_R_PARTIALMATCH) {
a3e803
-		dns_name_format(name, qnamebuf, sizeof(qnamebuf));
a3e803
+		dns_name_format(qname, qnamebuf, sizeof(qnamebuf));
a3e803
 		dns_name_format(tname, tnamebuf, sizeof(tnamebuf));
a3e803
-		dns_rdatatype_format(type, typebuf, sizeof(typebuf));
a3e803
+		dns_rdatatype_format(rdataset->type, typebuf, sizeof(typebuf));
a3e803
 		dns_rdataclass_format(view->rdclass, classbuf,
a3e803
 				      sizeof(classbuf));
a3e803
 		isc_log_write(dns_lctx, DNS_LOGCATEGORY_RESOLVER,
a3e803
@@ -6057,473 +6033,301 @@ noanswer_response(fetchctx_t *fctx, dns_name_t *oqname,
a3e803
 	return (ISC_R_SUCCESS);
a3e803
 }
a3e803
 
a3e803
+static isc_boolean_t
a3e803
+validinanswer(dns_rdataset_t *rdataset, fetchctx_t *fctx) {
a3e803
+	if (rdataset->type == dns_rdatatype_nsec3) {
a3e803
+		/*
a3e803
+		 * NSEC3 records are not allowed to
a3e803
+		 * appear in the answer section.
a3e803
+		 */
a3e803
+		log_formerr(fctx, "NSEC3 in answer");
a3e803
+		return (ISC_FALSE);
a3e803
+	}
a3e803
+	if (rdataset->type == dns_rdatatype_tkey) {
a3e803
+		/*
a3e803
+		 * TKEY is not a valid record in a
a3e803
+		 * response to any query we can make.
a3e803
+		 */
a3e803
+		log_formerr(fctx, "TKEY in answer");
a3e803
+		return (ISC_FALSE);
a3e803
+	}
a3e803
+	if (rdataset->rdclass != fctx->res->rdclass) {
a3e803
+		log_formerr(fctx, "Mismatched class in answer");
a3e803
+		return (ISC_FALSE);
a3e803
+	}
a3e803
+	return (ISC_TRUE);
a3e803
+}
a3e803
+
a3e803
 static isc_result_t
a3e803
 answer_response(fetchctx_t *fctx) {
a3e803
 	isc_result_t result;
a3e803
-	dns_message_t *message;
a3e803
-	dns_name_t *name, *dname = NULL, *qname, tname, *ns_name;
a3e803
-	dns_name_t *cname = NULL, *lastcname = NULL;
a3e803
-	dns_rdataset_t *rdataset, *ns_rdataset;
a3e803
-	isc_boolean_t done, external, aa, found, want_chaining;
a3e803
-	isc_boolean_t have_answer, found_cname, found_dname, found_type;
a3e803
-	isc_boolean_t wanted_chaining;
a3e803
-	unsigned int aflag, chaining;
a3e803
+	dns_message_t *message = NULL;
a3e803
+	dns_name_t *name = NULL, *qname = NULL, *ns_name = NULL;
a3e803
+	dns_name_t *aname = NULL, *cname = NULL, *dname = NULL;
a3e803
+	dns_rdataset_t *rdataset = NULL, *sigrdataset = NULL;
a3e803
+	dns_rdataset_t *ardataset = NULL, *crdataset = NULL;
a3e803
+	dns_rdataset_t *drdataset = NULL, *ns_rdataset = NULL;
a3e803
+	isc_boolean_t done = ISC_FALSE, aa;
a3e803
+	unsigned int dname_labels, domain_labels;
a3e803
+	isc_boolean_t chaining = ISC_FALSE;
a3e803
 	dns_rdatatype_t type;
a3e803
-	dns_fixedname_t fdname, fqname;
a3e803
-	dns_view_t *view;
a3e803
+	dns_view_t *view = NULL;
a3e803
+	dns_trust_t trust;
a3e803
+
a3e803
+	REQUIRE(VALID_FCTX(fctx));
a3e803
 
a3e803
 	FCTXTRACE("answer_response");
a3e803
 
a3e803
 	message = fctx->rmessage;
a3e803
+	qname = &fctx->name;
a3e803
+	view = fctx->res->view;
a3e803
+	type = fctx->type;
a3e803
 
a3e803
 	/*
a3e803
-	 * Examine the answer section, marking those rdatasets which are
a3e803
-	 * part of the answer and should be cached.
a3e803
+	 * There can be multiple RRSIG and SIG records at a name so
a3e803
+	 * we treat these types as a subset of ANY.
a3e803
 	 */
a3e803
+	if (type == dns_rdatatype_rrsig || type == dns_rdatatype_sig) {
a3e803
+		type = dns_rdatatype_any;
a3e803
+	}
a3e803
 
a3e803
-	done = ISC_FALSE;
a3e803
-	found_cname = ISC_FALSE;
a3e803
-	found_dname = ISC_FALSE;
a3e803
-	found_type = ISC_FALSE;
a3e803
-	have_answer = ISC_FALSE;
a3e803
-	want_chaining = ISC_FALSE;
a3e803
-	chaining = 0;
a3e803
-	POST(want_chaining);
a3e803
-	if ((message->flags & DNS_MESSAGEFLAG_AA) != 0)
a3e803
-		aa = ISC_TRUE;
a3e803
-	else
a3e803
-		aa = ISC_FALSE;
a3e803
-	qname = &fctx->name;
a3e803
-	type = fctx->type;
a3e803
-	view = fctx->res->view;
a3e803
-	result = dns_message_firstname(message, DNS_SECTION_ANSWER);
a3e803
-	while (!done && result == ISC_R_SUCCESS) {
a3e803
-		dns_namereln_t namereln, lastreln;
a3e803
-		int order, lastorder;
a3e803
-		unsigned int nlabels, lastnlabels;
a3e803
+	/*
a3e803
+	 * Bigger than any valid DNAME label count.
a3e803
+	 */
a3e803
+	dname_labels = dns_name_countlabels(qname);
a3e803
+	domain_labels = dns_name_countlabels(&fctx->domain);
a3e803
+
a3e803
+	/*
a3e803
+	 * Perform a single pass looking for the answer, cname or covering
a3e803
+	 * dname.
a3e803
+	 */
a3e803
+	for (result = dns_message_firstname(message, DNS_SECTION_ANSWER);
a3e803
+	     result == ISC_R_SUCCESS;
a3e803
+	     result = dns_message_nextname(message, DNS_SECTION_ANSWER))
a3e803
+	{
a3e803
+		int order;
a3e803
+		unsigned int nlabels;
a3e803
+		dns_namereln_t namereln;
a3e803
 
a3e803
 		name = NULL;
a3e803
 		dns_message_currentname(message, DNS_SECTION_ANSWER, &name);
a3e803
-		external = ISC_TF(!dns_name_issubdomain(name, &fctx->domain));
a3e803
 		namereln = dns_name_fullcompare(qname, name, &order, &nlabels);
a3e803
-
a3e803
-		if (namereln == dns_namereln_equal) {
a3e803
-			wanted_chaining = ISC_FALSE;
a3e803
+		switch (namereln) {
a3e803
+		case dns_namereln_equal:
a3e803
 			for (rdataset = ISC_LIST_HEAD(name->list);
a3e803
 			     rdataset != NULL;
a3e803
-			     rdataset = ISC_LIST_NEXT(rdataset, link)) {
a3e803
-				found = ISC_FALSE;
a3e803
-				want_chaining = ISC_FALSE;
a3e803
-				aflag = 0;
a3e803
-				if (rdataset->type == dns_rdatatype_nsec3) {
a3e803
-					/*
a3e803
-					 * NSEC3 records are not allowed to
a3e803
-					 * appear in the answer section.
a3e803
-					 */
a3e803
-					log_formerr(fctx, "NSEC3 in answer");
a3e803
-					return (DNS_R_FORMERR);
a3e803
-				}
a3e803
-				if (rdataset->type == dns_rdatatype_tkey) {
a3e803
-					/*
a3e803
-					 * TKEY is not a valid record in a
a3e803
-					 * response to any query we can make.
a3e803
-					 */
a3e803
-					log_formerr(fctx, "TKEY in answer");
a3e803
-					return (DNS_R_FORMERR);
a3e803
-				}
a3e803
-				if (rdataset->rdclass != fctx->res->rdclass) {
a3e803
-					log_formerr(fctx, "Mismatched class "
a3e803
-						    "in answer");
a3e803
-					return (DNS_R_FORMERR);
a3e803
-				}
a3e803
-
a3e803
-				/*
a3e803
-				 * Apply filters, if given, on answers to reject
a3e803
-				 * a malicious attempt of rebinding.
a3e803
-				 */
a3e803
-				if ((rdataset->type == dns_rdatatype_a ||
a3e803
-				     rdataset->type == dns_rdatatype_aaaa) &&
a3e803
-				    !is_answeraddress_allowed(view, name,
a3e803
-							      rdataset)) {
a3e803
-					return (DNS_R_SERVFAIL);
a3e803
-				}
a3e803
-
a3e803
-				if (rdataset->type == type && !found_cname) {
a3e803
-					/*
a3e803
-					 * We've found an ordinary answer.
a3e803
-					 */
a3e803
-					found = ISC_TRUE;
a3e803
-					found_type = ISC_TRUE;
a3e803
-					done = ISC_TRUE;
a3e803
-					aflag = DNS_RDATASETATTR_ANSWER;
a3e803
-				} else if (type == dns_rdatatype_any) {
a3e803
-					/*
a3e803
-					 * We've found an answer matching
a3e803
-					 * an ANY query.  There may be
a3e803
-					 * more.
a3e803
-					 */
a3e803
-					found = ISC_TRUE;
a3e803
-					aflag = DNS_RDATASETATTR_ANSWER;
a3e803
-				} else if (rdataset->type == dns_rdatatype_rrsig
a3e803
-					   && rdataset->covers == type
a3e803
-					   && !found_cname) {
a3e803
-					/*
a3e803
-					 * We've found a signature that
a3e803
-					 * covers the type we're looking for.
a3e803
-					 */
a3e803
-					found = ISC_TRUE;
a3e803
-					found_type = ISC_TRUE;
a3e803
-					aflag = DNS_RDATASETATTR_ANSWERSIG;
a3e803
-				} else if (rdataset->type ==
a3e803
-					   dns_rdatatype_cname
a3e803
-					   && !found_type) {
a3e803
-					/*
a3e803
-					 * We're looking for something else,
a3e803
-					 * but we found a CNAME.
a3e803
-					 *
a3e803
-					 * Getting a CNAME response for some
a3e803
-					 * query types is an error, see
a3e803
-					 * RFC 4035, Section 2.5.
a3e803
-					 */
a3e803
-					if (type == dns_rdatatype_rrsig ||
a3e803
-					    type == dns_rdatatype_key ||
a3e803
-					    type == dns_rdatatype_nsec) {
a3e803
-						char buf[DNS_RDATATYPE_FORMATSIZE];
a3e803
-						dns_rdatatype_format(fctx->type,
a3e803
-							      buf, sizeof(buf));
a3e803
-						log_formerr(fctx,
a3e803
-							    "CNAME response "
a3e803
-							    "for %s RR", buf);
a3e803
-						return (DNS_R_FORMERR);
a3e803
-					}
a3e803
-					found = ISC_TRUE;
a3e803
-					found_cname = ISC_TRUE;
a3e803
-					want_chaining = ISC_TRUE;
a3e803
-					aflag = DNS_RDATASETATTR_ANSWER;
a3e803
-					result = cname_target(rdataset,
a3e803
-							      &tname);
a3e803
-					if (result != ISC_R_SUCCESS)
a3e803
-						return (result);
a3e803
-					/* Apply filters on the target name. */
a3e803
-					if (!is_answertarget_allowed(view,
a3e803
-							name,
a3e803
-							rdataset->type,
a3e803
-							&tname,
a3e803
-							&fctx->domain)) {
a3e803
-						return (DNS_R_SERVFAIL);
a3e803
+			     rdataset = ISC_LIST_NEXT(rdataset, link))
a3e803
+			{
a3e803
+				if (rdataset->type == type ||
a3e803
+				    type == dns_rdatatype_any)
a3e803
+				{
a3e803
+					aname = name;
a3e803
+					if (type != dns_rdatatype_any) {
a3e803
+						ardataset = rdataset;
a3e803
 					}
a3e803
-					lastcname = name;
a3e803
-				} else if (rdataset->type == dns_rdatatype_rrsig
a3e803
-					   && rdataset->covers ==
a3e803
-					   dns_rdatatype_cname
a3e803
-					   && !found_type) {
a3e803
-					/*
a3e803
-					 * We're looking for something else,
a3e803
-					 * but we found a SIG CNAME.
a3e803
-					 */
a3e803
-					found = ISC_TRUE;
a3e803
-					found_cname = ISC_TRUE;
a3e803
-					aflag = DNS_RDATASETATTR_ANSWERSIG;
a3e803
+					break;
a3e803
 				}
a3e803
-
a3e803
-				if (found) {
a3e803
-					/*
a3e803
-					 * We've found an answer to our
a3e803
-					 * question.
a3e803
-					 */
a3e803
-					name->attributes |=
a3e803
-						DNS_NAMEATTR_CACHE;
a3e803
-					rdataset->attributes |=
a3e803
-						DNS_RDATASETATTR_CACHE;
a3e803
-					rdataset->trust = dns_trust_answer;
a3e803
-					if (chaining == 0) {
a3e803
-						/*
a3e803
-						 * This data is "the" answer
a3e803
-						 * to our question only if
a3e803
-						 * we're not chaining (i.e.
a3e803
-						 * if we haven't followed
a3e803
-						 * a CNAME or DNAME).
a3e803
-						 */
a3e803
-						INSIST(!external);
a3e803
-						/*
a3e803
-						 * Don't use found_cname here
a3e803
-						 * as we have just set it
a3e803
-						 * above.
a3e803
-						 */
a3e803
-						if (cname == NULL &&
a3e803
-						    !found_dname &&
a3e803
-						    aflag ==
a3e803
-						     DNS_RDATASETATTR_ANSWER)
a3e803
-						{
a3e803
-							have_answer = ISC_TRUE;
a3e803
-							if (found_cname &&
a3e803
-							    cname == NULL)
a3e803
-								cname = name;
a3e803
-							name->attributes |=
a3e803
-								DNS_NAMEATTR_ANSWER;
a3e803
-						}
a3e803
-						rdataset->attributes |= aflag;
a3e803
-						if (aa)
a3e803
-							rdataset->trust =
a3e803
-							  dns_trust_authanswer;
a3e803
-					} else if (external) {
a3e803
-						/*
a3e803
-						 * This data is outside of
a3e803
-						 * our query domain, and
a3e803
-						 * may not be cached.
a3e803
-						 */
a3e803
-						rdataset->attributes |=
a3e803
-						    DNS_RDATASETATTR_EXTERNAL;
a3e803
-					}
a3e803
-
a3e803
-					/*
a3e803
-					 * Mark any additional data related
a3e803
-					 * to this rdataset.
a3e803
-					 */
a3e803
-					(void)dns_rdataset_additionaldata(
a3e803
-							rdataset,
a3e803
-							check_related,
a3e803
-							fctx);
a3e803
-
a3e803
-					/*
a3e803
-					 * CNAME chaining.
a3e803
-					 */
a3e803
-					if (want_chaining) {
a3e803
-						wanted_chaining = ISC_TRUE;
a3e803
-						name->attributes |=
a3e803
-							DNS_NAMEATTR_CHAINING;
a3e803
-						rdataset->attributes |=
a3e803
-						    DNS_RDATASETATTR_CHAINING;
a3e803
-						qname = &tname;
a3e803
-					}
a3e803
+				if (rdataset->type == dns_rdatatype_cname) {
a3e803
+					cname = name;
a3e803
+					crdataset = rdataset;
a3e803
+					break;
a3e803
 				}
a3e803
-				/*
a3e803
-				 * We could add an "else" clause here and
a3e803
-				 * log that we're ignoring this rdataset.
a3e803
-				 */
a3e803
 			}
a3e803
+			break;
a3e803
+
a3e803
+		case dns_namereln_subdomain:
a3e803
 			/*
a3e803
-			 * If wanted_chaining is true, we've done
a3e803
-			 * some chaining as the result of processing
a3e803
-			 * this node, and thus we need to set
a3e803
-			 * chaining to true.
a3e803
-			 *
a3e803
-			 * We don't set chaining inside of the
a3e803
-			 * rdataset loop because doing that would
a3e803
-			 * cause us to ignore the signatures of
a3e803
-			 * CNAMEs.
a3e803
+			 * In-scope DNAME records must have at least
a3e803
+			 * as many labels as the domain being queried.
a3e803
+			 * They also must be less that qname's labels
a3e803
+			 * and any previously found dname.
a3e803
 			 */
a3e803
-			if (wanted_chaining && chaining < 2U)
a3e803
-				chaining++;
a3e803
-		} else {
a3e803
-			dns_rdataset_t *dnameset = NULL;
a3e803
-			isc_boolean_t synthcname = ISC_FALSE;
a3e803
-
a3e803
-			if (lastcname != NULL) {
a3e803
-				lastreln = dns_name_fullcompare(lastcname,
a3e803
-								name,
a3e803
-								&lastorder,
a3e803
-								&lastnlabels);
a3e803
-				if (lastreln == dns_namereln_subdomain &&
a3e803
-				    lastnlabels == dns_name_countlabels(name))
a3e803
-					synthcname = ISC_TRUE;
a3e803
+			if (nlabels >= dname_labels || nlabels < domain_labels)
a3e803
+			{
a3e803
+				continue;
a3e803
 			}
a3e803
 
a3e803
 			/*
a3e803
-			 * Look for a DNAME (or its SIG).  Anything else is
a3e803
-			 * ignored.
a3e803
+			 * We are looking for the shortest DNAME if there
a3e803
+			 * are multiple ones (which there shouldn't be).
a3e803
 			 */
a3e803
-			wanted_chaining = ISC_FALSE;
a3e803
 			for (rdataset = ISC_LIST_HEAD(name->list);
a3e803
 			     rdataset != NULL;
a3e803
 			     rdataset = ISC_LIST_NEXT(rdataset, link))
a3e803
 			{
a3e803
-				if (rdataset->rdclass != fctx->res->rdclass) {
a3e803
-					log_formerr(fctx, "Mismatched class "
a3e803
-						    "in answer");
a3e803
-					return (DNS_R_FORMERR);
a3e803
-				}
a3e803
-
a3e803
-				/*
a3e803
-				 * Only pass DNAME or RRSIG(DNAME).
a3e803
-				 */
a3e803
-				if (rdataset->type != dns_rdatatype_dname &&
a3e803
-				    (rdataset->type != dns_rdatatype_rrsig ||
a3e803
-				     rdataset->covers != dns_rdatatype_dname))
a3e803
+				if (rdataset->type != dns_rdatatype_dname) {
a3e803
 					continue;
a3e803
-
a3e803
-				/*
a3e803
-				 * If we're not chaining, then the DNAME and
a3e803
-				 * its signature should not be external.
a3e803
-				 */
a3e803
-				if (chaining == 0 && external) {
a3e803
-					char qbuf[DNS_NAME_FORMATSIZE];
a3e803
-					char obuf[DNS_NAME_FORMATSIZE];
a3e803
-
a3e803
-					dns_name_format(name, qbuf,
a3e803
-							sizeof(qbuf));
a3e803
-					dns_name_format(&fctx->domain, obuf,
a3e803
-							sizeof(obuf));
a3e803
-					log_formerr(fctx, "external DNAME or "
a3e803
-						    "RRSIG covering DNAME "
a3e803
-						    "in answer: %s is "
a3e803
-						    "not in %s", qbuf, obuf);
a3e803
-					return (DNS_R_FORMERR);
a3e803
-				}
a3e803
-
a3e803
-				/*
a3e803
-				 * If DNAME + synthetic CNAME then the
a3e803
-				 * namereln is dns_namereln_subdomain.
a3e803
-				 */
a3e803
-				if (namereln != dns_namereln_subdomain &&
a3e803
-				    !synthcname)
a3e803
-				{
a3e803
-					char qbuf[DNS_NAME_FORMATSIZE];
a3e803
-					char obuf[DNS_NAME_FORMATSIZE];
a3e803
-
a3e803
-					dns_name_format(qname, qbuf,
a3e803
-							sizeof(qbuf));
a3e803
-					dns_name_format(name, obuf,
a3e803
-							sizeof(obuf));
a3e803
-					log_formerr(fctx, "unrelated DNAME "
a3e803
-						    "in answer: %s is "
a3e803
-						    "not in %s", qbuf, obuf);
a3e803
-					return (DNS_R_FORMERR);
a3e803
 				}
a3e803
+				dname = name;
a3e803
+				drdataset = rdataset;
a3e803
+				dname_labels = nlabels;
a3e803
+				break;
a3e803
+			}
a3e803
+			break;
a3e803
+		default:
a3e803
+			break;
a3e803
+		}
a3e803
+	}
a3e803
 
a3e803
-				aflag = 0;
a3e803
-				if (rdataset->type == dns_rdatatype_dname) {
a3e803
-					want_chaining = ISC_TRUE;
a3e803
-					POST(want_chaining);
a3e803
-					aflag = DNS_RDATASETATTR_ANSWER;
a3e803
-					dns_fixedname_init(&fdname);
a3e803
-					dname = dns_fixedname_name(&fdname);
a3e803
-					if (synthcname) {
a3e803
-						result = fromdname(rdataset,
a3e803
-								   lastcname,
a3e803
-								   lastnlabels,
a3e803
-								   qname);
a3e803
-					} else {
a3e803
-						result = dname_target(rdataset,
a3e803
-								      qname,
a3e803
-								      nlabels,
a3e803
-								      dname);
a3e803
-					}
a3e803
-					if (result == ISC_R_NOSPACE) {
a3e803
-						/*
a3e803
-						 * We can't construct the
a3e803
-						 * DNAME target.  Do not
a3e803
-						 * try to continue.
a3e803
-						 */
a3e803
-						want_chaining = ISC_FALSE;
a3e803
-						POST(want_chaining);
a3e803
-					} else if (result != ISC_R_SUCCESS)
a3e803
-						return (result);
a3e803
-					else
a3e803
-						dnameset = rdataset;
a3e803
+	if (dname != NULL) {
a3e803
+		aname = NULL;
a3e803
+		ardataset = NULL;
a3e803
+		cname = NULL;
a3e803
+		crdataset = NULL;
a3e803
+	} else if (aname != NULL) {
a3e803
+		cname = NULL;
a3e803
+		crdataset = NULL;
a3e803
+	}
a3e803
 
a3e803
-					if (!synthcname &&
a3e803
-					    !is_answertarget_allowed(view,
a3e803
-						     qname, rdataset->type,
a3e803
-						     dname, &fctx->domain))
a3e803
-					{
a3e803
-						return (DNS_R_SERVFAIL);
a3e803
-					}
a3e803
-				} else {
a3e803
-					/*
a3e803
-					 * We've found a signature that
a3e803
-					 * covers the DNAME.
a3e803
-					 */
a3e803
-					aflag = DNS_RDATASETATTR_ANSWERSIG;
a3e803
-				}
a3e803
+	aa = ISC_TF((message->flags & DNS_MESSAGEFLAG_AA) != 0);
a3e803
+	trust = aa ? dns_trust_authanswer : dns_trust_answer;
a3e803
 
a3e803
-				/*
a3e803
-				 * We've found an answer to our
a3e803
-				 * question.
a3e803
-				 */
a3e803
-				name->attributes |= DNS_NAMEATTR_CACHE;
a3e803
-				rdataset->attributes |= DNS_RDATASETATTR_CACHE;
a3e803
-				rdataset->trust = dns_trust_answer;
a3e803
-				/*
a3e803
-				 * If we are not chaining or the first CNAME
a3e803
-				 * is a synthesised CNAME before the DNAME.
a3e803
-				 */
a3e803
-				if ((chaining == 0) ||
a3e803
-				    (chaining == 1U && synthcname))
a3e803
-				{
a3e803
-					/*
a3e803
-					 * This data is "the" answer to
a3e803
-					 * our question only if we're
a3e803
-					 * not chaining.
a3e803
-					 */
a3e803
-					INSIST(!external);
a3e803
-					if (aflag == DNS_RDATASETATTR_ANSWER) {
a3e803
-						have_answer = ISC_TRUE;
a3e803
-						found_dname = ISC_TRUE;
a3e803
-						if (cname != NULL &&
a3e803
-						    synthcname)
a3e803
-						{
a3e803
-							cname->attributes &=
a3e803
-							   ~DNS_NAMEATTR_ANSWER;
a3e803
-						}
a3e803
-						name->attributes |=
a3e803
-							DNS_NAMEATTR_ANSWER;
a3e803
-					}
a3e803
-					rdataset->attributes |= aflag;
a3e803
-					if (aa)
a3e803
-						rdataset->trust =
a3e803
-						  dns_trust_authanswer;
a3e803
-				} else if (external) {
a3e803
-					rdataset->attributes |=
a3e803
-					    DNS_RDATASETATTR_EXTERNAL;
a3e803
-				}
a3e803
+	if (aname != NULL && type == dns_rdatatype_any) {
a3e803
+		for (rdataset = ISC_LIST_HEAD(aname->list);
a3e803
+		     rdataset != NULL;
a3e803
+		     rdataset = ISC_LIST_NEXT(rdataset, link))
a3e803
+		{
a3e803
+			if (!validinanswer(rdataset, fctx)) {
a3e803
+				return (DNS_R_FORMERR);
a3e803
 			}
a3e803
-
a3e803
-			/*
a3e803
-			 * DNAME chaining.
a3e803
-			 */
a3e803
-			if (dnameset != NULL) {
a3e803
-				if (!synthcname) {
a3e803
-					/*
a3e803
-					 * Copy the dname into the qname fixed
a3e803
-					 * name.
a3e803
-					 *
a3e803
-					 * Although we check for failure of the
a3e803
-					 * copy operation, in practice it
a3e803
-					 * should never fail since we already
a3e803
-					 * know that the result fits in a
a3e803
-					 * fixedname.
a3e803
-					 */
a3e803
-					dns_fixedname_init(&fqname);
a3e803
-					qname = dns_fixedname_name(&fqname);
a3e803
-					result = dns_name_copy(dname, qname,
a3e803
-							       NULL);
a3e803
-					if (result != ISC_R_SUCCESS)
a3e803
-						return (result);
a3e803
-				}
a3e803
-				wanted_chaining = ISC_TRUE;
a3e803
-				name->attributes |= DNS_NAMEATTR_CHAINING;
a3e803
-				dnameset->attributes |=
a3e803
-					    DNS_RDATASETATTR_CHAINING;
a3e803
+			if ((fctx->type == dns_rdatatype_sig ||
a3e803
+			     fctx->type == dns_rdatatype_rrsig) &&
a3e803
+			     rdataset->type != fctx->type)
a3e803
+			{
a3e803
+				continue;
a3e803
 			}
a3e803
-			/*
a3e803
-			 * Ensure that we can't ever get chaining == 1
a3e803
-			 * above if we have processed a DNAME.
a3e803
-			 */
a3e803
-			if (wanted_chaining && chaining < 2U)
a3e803
-				chaining += 2;
a3e803
+			if ((rdataset->type == dns_rdatatype_a ||
a3e803
+			     rdataset->type == dns_rdatatype_aaaa) &&
a3e803
+			    !is_answeraddress_allowed(view, aname, rdataset))
a3e803
+			{
a3e803
+				return (DNS_R_SERVFAIL);
a3e803
+			}
a3e803
+			if ((rdataset->type == dns_rdatatype_cname ||
a3e803
+			     rdataset->type == dns_rdatatype_dname) &&
a3e803
+			     !is_answertarget_allowed(fctx, qname, aname,
a3e803
+						      rdataset, NULL))
a3e803
+			{
a3e803
+				return (DNS_R_SERVFAIL);
a3e803
+			}
a3e803
+			aname->attributes |= DNS_NAMEATTR_CACHE;
a3e803
+			aname->attributes |= DNS_NAMEATTR_ANSWER;
a3e803
+			rdataset->attributes |= DNS_RDATASETATTR_ANSWER;
a3e803
+			rdataset->attributes |= DNS_RDATASETATTR_CACHE;
a3e803
+			rdataset->trust = trust;
a3e803
+			(void)dns_rdataset_additionaldata(rdataset,
a3e803
+							  check_related,
a3e803
+							  fctx);
a3e803
 		}
a3e803
-		result = dns_message_nextname(message, DNS_SECTION_ANSWER);
a3e803
-	}
a3e803
-	if (result == ISC_R_NOMORE)
a3e803
-		result = ISC_R_SUCCESS;
a3e803
-	if (result != ISC_R_SUCCESS)
a3e803
-		return (result);
a3e803
-
a3e803
-	/*
a3e803
-	 * We should have found an answer.
a3e803
-	 */
a3e803
-	if (!have_answer) {
a3e803
+	} else if (aname != NULL) {
a3e803
+		if (!validinanswer(ardataset, fctx))
a3e803
+			return (DNS_R_FORMERR);
a3e803
+		if ((ardataset->type == dns_rdatatype_a ||
a3e803
+		     ardataset->type == dns_rdatatype_aaaa) &&
a3e803
+		    !is_answeraddress_allowed(view, aname, ardataset)) {
a3e803
+			return (DNS_R_SERVFAIL);
a3e803
+		}
a3e803
+		if ((ardataset->type == dns_rdatatype_cname ||
a3e803
+		     ardataset->type == dns_rdatatype_dname) &&
a3e803
+		     !is_answertarget_allowed(fctx, qname, aname, ardataset,
a3e803
+					      NULL))
a3e803
+		{
a3e803
+			return (DNS_R_SERVFAIL);
a3e803
+		}
a3e803
+		aname->attributes |= DNS_NAMEATTR_CACHE;
a3e803
+		aname->attributes |= DNS_NAMEATTR_ANSWER;
a3e803
+		ardataset->attributes |= DNS_RDATASETATTR_ANSWER;
a3e803
+		ardataset->attributes |= DNS_RDATASETATTR_CACHE;
a3e803
+		ardataset->trust = trust;
a3e803
+		(void)dns_rdataset_additionaldata(ardataset, check_related,
a3e803
+						  fctx);
a3e803
+		for (sigrdataset = ISC_LIST_HEAD(aname->list);
a3e803
+		     sigrdataset != NULL;
a3e803
+		     sigrdataset = ISC_LIST_NEXT(sigrdataset, link)) {
a3e803
+			if (!validinanswer(sigrdataset, fctx))
a3e803
+				return (DNS_R_FORMERR);
a3e803
+			if (sigrdataset->type != dns_rdatatype_rrsig ||
a3e803
+			    sigrdataset->covers != type)
a3e803
+				continue;
a3e803
+			sigrdataset->attributes |= DNS_RDATASETATTR_ANSWERSIG;
a3e803
+			sigrdataset->attributes |= DNS_RDATASETATTR_CACHE;
a3e803
+			sigrdataset->trust = trust;
a3e803
+			break;
a3e803
+		}
a3e803
+	} else if (cname != NULL) {
a3e803
+		if (!validinanswer(crdataset, fctx)) {
a3e803
+			return (DNS_R_FORMERR);
a3e803
+		}
a3e803
+		if (type == dns_rdatatype_rrsig || type == dns_rdatatype_key ||
a3e803
+		    type == dns_rdatatype_nsec)
a3e803
+		{
a3e803
+			char buf[DNS_RDATATYPE_FORMATSIZE];
a3e803
+			dns_rdatatype_format(type, buf, sizeof(buf));
a3e803
+			log_formerr(fctx, "CNAME response for %s RR", buf);
a3e803
+			return (DNS_R_FORMERR);
a3e803
+		}
a3e803
+		if (!is_answertarget_allowed(fctx, qname, cname, crdataset,
a3e803
+					     NULL))
a3e803
+		{
a3e803
+			return (DNS_R_SERVFAIL);
a3e803
+		}
a3e803
+		cname->attributes |= DNS_NAMEATTR_CACHE;
a3e803
+		cname->attributes |= DNS_NAMEATTR_ANSWER;
a3e803
+		cname->attributes |= DNS_NAMEATTR_CHAINING;
a3e803
+		crdataset->attributes |= DNS_RDATASETATTR_ANSWER;
a3e803
+		crdataset->attributes |= DNS_RDATASETATTR_CACHE;
a3e803
+		crdataset->attributes |= DNS_RDATASETATTR_CHAINING;
a3e803
+		crdataset->trust = trust;
a3e803
+		for (sigrdataset = ISC_LIST_HEAD(cname->list);
a3e803
+		     sigrdataset != NULL;
a3e803
+		     sigrdataset = ISC_LIST_NEXT(sigrdataset, link))
a3e803
+		{
a3e803
+			if (!validinanswer(sigrdataset, fctx)) {
a3e803
+				return (DNS_R_FORMERR);
a3e803
+			}
a3e803
+			if (sigrdataset->type != dns_rdatatype_rrsig ||
a3e803
+			    sigrdataset->covers != dns_rdatatype_cname)
a3e803
+			{
a3e803
+				continue;
a3e803
+			}
a3e803
+			sigrdataset->attributes |= DNS_RDATASETATTR_ANSWERSIG;
a3e803
+			sigrdataset->attributes |= DNS_RDATASETATTR_CACHE;
a3e803
+			sigrdataset->trust = trust;
a3e803
+			break;
a3e803
+		}
a3e803
+		chaining = ISC_TRUE;
a3e803
+	} else if (dname != NULL) {
a3e803
+		if (!validinanswer(drdataset, fctx)) {
a3e803
+			return (DNS_R_FORMERR);
a3e803
+		}
a3e803
+		if (!is_answertarget_allowed(fctx, qname, dname, drdataset,
a3e803
+					     &chaining)) {
a3e803
+			return (DNS_R_SERVFAIL);
a3e803
+		}
a3e803
+		dname->attributes |= DNS_NAMEATTR_CACHE;
a3e803
+		dname->attributes |= DNS_NAMEATTR_ANSWER;
a3e803
+		dname->attributes |= DNS_NAMEATTR_CHAINING;
a3e803
+		drdataset->attributes |= DNS_RDATASETATTR_ANSWER;
a3e803
+		drdataset->attributes |= DNS_RDATASETATTR_CACHE;
a3e803
+		drdataset->attributes |= DNS_RDATASETATTR_CHAINING;
a3e803
+		drdataset->trust = trust;
a3e803
+		for (sigrdataset = ISC_LIST_HEAD(dname->list);
a3e803
+		     sigrdataset != NULL;
a3e803
+		     sigrdataset = ISC_LIST_NEXT(sigrdataset, link))
a3e803
+		{
a3e803
+			if (!validinanswer(sigrdataset, fctx)) {
a3e803
+				return (DNS_R_FORMERR);
a3e803
+			}
a3e803
+			if (sigrdataset->type != dns_rdatatype_rrsig ||
a3e803
+			    sigrdataset->covers != dns_rdatatype_dname)
a3e803
+			{
a3e803
+				continue;
a3e803
+			}
a3e803
+			sigrdataset->attributes |= DNS_RDATASETATTR_ANSWERSIG;
a3e803
+			sigrdataset->attributes |= DNS_RDATASETATTR_CACHE;
a3e803
+			sigrdataset->trust = trust;
a3e803
+			break;
a3e803
+		}
a3e803
+	} else {
a3e803
 		log_formerr(fctx, "reply has no answer");
a3e803
 		return (DNS_R_FORMERR);
a3e803
 	}
a3e803
@@ -6536,14 +6340,8 @@ answer_response(fetchctx_t *fctx) {
a3e803
 	/*
a3e803
 	 * Did chaining end before we got the final answer?
a3e803
 	 */
a3e803
-	if (chaining != 0) {
a3e803
-		/*
a3e803
-		 * Yes.  This may be a negative reply, so hand off
a3e803
-		 * authority section processing to the noanswer code.
a3e803
-		 * If it isn't a noanswer response, no harm will be
a3e803
-		 * done.
a3e803
-		 */
a3e803
-		return (noanswer_response(fctx, qname, 0));
a3e803
+	if (chaining) {
a3e803
+		return (ISC_R_SUCCESS);
a3e803
 	}
a3e803
 
a3e803
 	/*
a3e803
@@ -6562,11 +6360,9 @@ answer_response(fetchctx_t *fctx) {
a3e803
 	 * We expect there to be only one owner name for all the rdatasets
a3e803
 	 * in this section, and we expect that it is not external.
a3e803
 	 */
a3e803
-	done = ISC_FALSE;
a3e803
-	ns_name = NULL;
a3e803
-	ns_rdataset = NULL;
a3e803
 	result = dns_message_firstname(message, DNS_SECTION_AUTHORITY);
a3e803
 	while (!done && result == ISC_R_SUCCESS) {
a3e803
+		isc_boolean_t external;
a3e803
 		name = NULL;
a3e803
 		dns_message_currentname(message, DNS_SECTION_AUTHORITY, &name);
a3e803
 		external = ISC_TF(!dns_name_issubdomain(name, &fctx->domain));
a3e803
@@ -6585,12 +6381,13 @@ answer_response(fetchctx_t *fctx) {
a3e803
 						DNS_NAMEATTR_CACHE;
a3e803
 					rdataset->attributes |=
a3e803
 						DNS_RDATASETATTR_CACHE;
a3e803
-					if (aa && chaining == 0)
a3e803
+					if (aa && !chaining) {
a3e803
 						rdataset->trust =
a3e803
 						    dns_trust_authauthority;
a3e803
-					else
a3e803
+					} else {
a3e803
 						rdataset->trust =
a3e803
 						    dns_trust_additional;
a3e803
+					}
a3e803
 
a3e803
 					if (rdataset->type == dns_rdatatype_ns)
a3e803
 					{
a3e803
@@ -7249,6 +7046,7 @@ resquery_response(isc_task_t *task, isc_event_t *event) {
a3e803
 	 * Is the remote server broken, or does it dislike us?
a3e803
 	 */
a3e803
 	if (message->rcode != dns_rcode_noerror &&
a3e803
+	    message->rcode != dns_rcode_yxdomain &&
a3e803
 	    message->rcode != dns_rcode_nxdomain) {
a3e803
 		if (((message->rcode == dns_rcode_formerr ||
a3e803
 		      message->rcode == dns_rcode_notimp) ||
a3e803
@@ -7293,13 +7091,6 @@ resquery_response(isc_task_t *task, isc_event_t *event) {
a3e803
 				log_formerr(fctx, "server sent FORMERR");
a3e803
 				result = DNS_R_FORMERR;
a3e803
 			}
a3e803
-		} else if (message->rcode == dns_rcode_yxdomain) {
a3e803
-			/*
a3e803
-			 * DNAME mapping failed because the new name
a3e803
-			 * was too long.  There's no chance of success
a3e803
-			 * for this fetch.
a3e803
-			 */
a3e803
-			result = DNS_R_YXDOMAIN;
a3e803
 		} else if (message->rcode == dns_rcode_badvers) {
a3e803
 			unsigned int flags, mask;
a3e803
 			unsigned int version;
a3e803
@@ -7404,6 +7195,7 @@ resquery_response(isc_task_t *task, isc_event_t *event) {
a3e803
 	 */
a3e803
 	if (message->counts[DNS_SECTION_ANSWER] > 0 &&
a3e803
 	    (message->rcode == dns_rcode_noerror ||
a3e803
+	     message->rcode == dns_rcode_yxdomain ||
a3e803
 	     message->rcode == dns_rcode_nxdomain)) {
a3e803
 		/*
a3e803
 		 * [normal case]
a3e803
-- 
a3e803
2.9.3
a3e803