e20e41
From b09d07ee2aa891c5b8dd2469c4a73c9dd61e2384 Mon Sep 17 00:00:00 2001
e20e41
From: Simon Kelley <simon@thekelleys.org.uk>
e20e41
Date: Wed, 25 Oct 2017 17:48:19 +0100
e20e41
Subject: [PATCH 1/2] Fix caching logic for validated answers.
e20e41
e20e41
The current logic is naive in the case that there is more than
e20e41
one RRset in an answer (Typically, when a non-CNAME query is answered
e20e41
by one or more CNAME RRs, and then then an answer RRset.)
e20e41
e20e41
If all the RRsets validate, then they are cached and marked as validated,
e20e41
but if any RRset doesn't validate, then the AD flag is not set (good) and
e20e41
ALL the RRsets are cached marked as not validated.
e20e41
e20e41
This breaks when, eg, the answer contains a validated CNAME, pointing
e20e41
to a non-validated answer. A subsequent query for the CNAME without do
e20e41
will get an answer with the AD flag wrongly reset, and worse, the same
e20e41
query with do will get a cached answer without RRSIGS, rather than
e20e41
being forwarded.
e20e41
e20e41
The code now records the validation of individual RRsets and that
e20e41
is used to correctly set the "validated" bits in the cache entries.
e20e41
e20e41
(cherry picked from commit a6004d7f17687ac2455f724d0b57098c413f128d)
e20e41
---
e20e41
 src/dnsmasq.c |   2 +
e20e41
 src/dnsmasq.h |   5 +-
e20e41
 src/dnssec.c  | 174 +++++++++++++++++++++++++++++---------------------
e20e41
 src/forward.c |  19 ++++--
e20e41
 src/rfc1035.c |  58 ++++++++++++-----
e20e41
 5 files changed, 162 insertions(+), 96 deletions(-)
e20e41
e20e41
diff --git a/src/dnsmasq.c b/src/dnsmasq.c
e20e41
index 50b2029..f3d2671 100644
e20e41
--- a/src/dnsmasq.c
e20e41
+++ b/src/dnsmasq.c
e20e41
@@ -118,6 +118,8 @@ int main (int argc, char **argv)
e20e41
       daemon->namebuff = safe_malloc(MAXDNAME * 2);
e20e41
       daemon->keyname = safe_malloc(MAXDNAME * 2);
e20e41
       daemon->workspacename = safe_malloc(MAXDNAME * 2);
e20e41
+      /* one char flag per possible RR in answer section. */
e20e41
+      daemon->rr_status = safe_malloc(256);
e20e41
     }
e20e41
 #endif
e20e41
 
e20e41
diff --git a/src/dnsmasq.h b/src/dnsmasq.h
e20e41
index 5a68162..89d138a 100644
e20e41
--- a/src/dnsmasq.h
e20e41
+++ b/src/dnsmasq.h
e20e41
@@ -1006,6 +1006,7 @@ extern struct daemon {
e20e41
 #ifdef HAVE_DNSSEC
e20e41
   char *keyname; /* MAXDNAME size buffer */
e20e41
   char *workspacename; /* ditto */
e20e41
+  char *rr_status; /* 256 bytes as flags for individual RRs */
e20e41
 #endif
e20e41
   unsigned int local_answer, queries_forwarded, auth_answer;
e20e41
   struct frec *frec_list;
e20e41
@@ -1118,7 +1119,7 @@ size_t setup_reply(struct dns_header *header, size_t  qlen,
e20e41
 		   unsigned long local_ttl);
e20e41
 int extract_addresses(struct dns_header *header, size_t qlen, char *namebuff, 
e20e41
 		      time_t now, char **ipsets, int is_sign, int checkrebind,
e20e41
-		      int no_cache, int secure, int *doctored);
e20e41
+		      int no_cache_dnssec, int secure, int *doctored, char *rr_status);
e20e41
 size_t answer_request(struct dns_header *header, char *limit, size_t qlen,  
e20e41
 		      struct in_addr local_addr, struct in_addr local_netmask, 
e20e41
 		      time_t now, int ad_reqd, int do_bit, int have_pseudoheader);
e20e41
@@ -1151,7 +1152,7 @@ size_t dnssec_generate_query(struct dns_header *header, unsigned char *end, char
e20e41
 int dnssec_validate_by_ds(time_t now, struct dns_header *header, size_t n, char *name, char *keyname, int class);
e20e41
 int dnssec_validate_ds(time_t now, struct dns_header *header, size_t plen, char *name, char *keyname, int class);
e20e41
 int dnssec_validate_reply(time_t now, struct dns_header *header, size_t plen, char *name, char *keyname, int *class,
e20e41
-			  int check_unsigned, int *neganswer, int *nons);
e20e41
+			  int check_unsigned, int *neganswer, int *nons, char *rr_status);
e20e41
 int dnskey_keytag(int alg, int flags, unsigned char *rdata, int rdlen);
e20e41
 size_t filter_rrsigs(struct dns_header *header, size_t plen);
e20e41
 unsigned char* hash_questions(struct dns_header *header, size_t plen, char *name);
e20e41
diff --git a/src/dnssec.c b/src/dnssec.c
e20e41
index f45c804..3121eb1 100644
e20e41
--- a/src/dnssec.c
e20e41
+++ b/src/dnssec.c
e20e41
@@ -1177,8 +1177,7 @@ int dnssec_validate_ds(time_t now, struct dns_header *header, size_t plen, char
e20e41
   if (qtype != T_DS || qclass != class)
e20e41
     rc = STAT_BOGUS;
e20e41
   else
e20e41
-    rc = dnssec_validate_reply(now, header, plen, name, keyname, NULL, 0, &neganswer, &nons);
e20e41
-  /* Note dnssec_validate_reply() will have cached positive answers */
e20e41
+    rc = dnssec_validate_reply(now, header, plen, name, keyname, NULL, 0, &neganswer, &nons, NULL);
e20e41
   
e20e41
   if (rc == STAT_INSECURE)
e20e41
     rc = STAT_BOGUS;
e20e41
@@ -1962,18 +1961,25 @@ static int zone_status(char *name, int class, char *keyname, time_t now)
e20e41
    STAT_INSECURE at least one RRset not validated, because in unsigned zone.
e20e41
    STAT_BOGUS    signature is wrong, bad packet, no validation where there should be.
e20e41
    STAT_NEED_KEY need DNSKEY to complete validation (name is returned in keyname, class in *class)
e20e41
-   STAT_NEED_DS  need DS to complete validation (name is returned in keyname) 
e20e41
+   STAT_NEED_DS  need DS to complete validation (name is returned in keyname)
e20e41
+
e20e41
+   If non-NULL, rr_status points to a char array which corressponds to the RRs in the 
e20e41
+   answer section (only). This is set to 1 for each RR which is validated, and 0 for any which aren't.
e20e41
 */
e20e41
 int dnssec_validate_reply(time_t now, struct dns_header *header, size_t plen, char *name, char *keyname, 
e20e41
-			  int *class, int check_unsigned, int *neganswer, int *nons)
e20e41
+			  int *class, int check_unsigned, int *neganswer, int *nons, char *rr_status)
e20e41
 {
e20e41
   static unsigned char **targets = NULL;
e20e41
   static int target_sz = 0;
e20e41
 
e20e41
   unsigned char *ans_start, *p1, *p2;
e20e41
-  int type1, class1, rdlen1, type2, class2, rdlen2, qclass, qtype, targetidx;
e20e41
+  int type1, class1, rdlen1 = 0, type2, class2, rdlen2, qclass, qtype, targetidx;
e20e41
   int i, j, rc;
e20e41
+  int secure = STAT_SECURE;
e20e41
 
e20e41
+  if (rr_status)
e20e41
+    memset(rr_status, 0, ntohs(header->ancount));
e20e41
+  
e20e41
   if (neganswer)
e20e41
     *neganswer = 0;
e20e41
   
e20e41
@@ -2030,7 +2036,10 @@ int dnssec_validate_reply(time_t now, struct dns_header *header, size_t plen, ch
e20e41
   
e20e41
   for (p1 = ans_start, i = 0; i < ntohs(header->ancount) + ntohs(header->nscount); i++)
e20e41
     {
e20e41
-      if (!extract_name(header, plen, &p1, name, 1, 10))
e20e41
+       if (i != 0 && !ADD_RDLEN(header, p1, plen, rdlen1))
e20e41
+	 return STAT_BOGUS;
e20e41
+
e20e41
+       if (!extract_name(header, plen, &p1, name, 1, 10))
e20e41
 	return STAT_BOGUS; /* bad packet */
e20e41
       
e20e41
       GETSHORT(type1, p1);
e20e41
@@ -2039,106 +2048,125 @@ int dnssec_validate_reply(time_t now, struct dns_header *header, size_t plen, ch
e20e41
       GETSHORT(rdlen1, p1);
e20e41
       
e20e41
       /* Don't try and validate RRSIGs! */
e20e41
-      if (type1 != T_RRSIG)
e20e41
+      if (type1 == T_RRSIG)
e20e41
+	continue;
e20e41
+      
e20e41
+      /* Check if we've done this RRset already */
e20e41
+      for (p2 = ans_start, j = 0; j < i; j++)
e20e41
 	{
e20e41
-	  /* Check if we've done this RRset already */
e20e41
-	  for (p2 = ans_start, j = 0; j < i; j++)
e20e41
-	    {
e20e41
-	      if (!(rc = extract_name(header, plen, &p2, name, 0, 10)))
e20e41
-		return STAT_BOGUS; /* bad packet */
e20e41
-	      
e20e41
-	      GETSHORT(type2, p2);
e20e41
-	      GETSHORT(class2, p2);
e20e41
-	      p2 += 4; /* TTL */
e20e41
-	      GETSHORT(rdlen2, p2);
e20e41
-	      
e20e41
-	      if (type2 == type1 && class2 == class1 && rc == 1)
e20e41
-		break; /* Done it before: name, type, class all match. */
e20e41
-	      
e20e41
-	      if (!ADD_RDLEN(header, p2, plen, rdlen2))
e20e41
-		return STAT_BOGUS;
e20e41
-	    }
e20e41
+	  if (!(rc = extract_name(header, plen, &p2, name, 0, 10)))
e20e41
+	    return STAT_BOGUS; /* bad packet */
e20e41
+	  
e20e41
+	  GETSHORT(type2, p2);
e20e41
+	  GETSHORT(class2, p2);
e20e41
+	  p2 += 4; /* TTL */
e20e41
+	  GETSHORT(rdlen2, p2);
e20e41
+	  
e20e41
+	  if (type2 == type1 && class2 == class1 && rc == 1)
e20e41
+	    break; /* Done it before: name, type, class all match. */
e20e41
 	  
e20e41
+	  if (!ADD_RDLEN(header, p2, plen, rdlen2))
e20e41
+	    return STAT_BOGUS;
e20e41
+	}
e20e41
+      
e20e41
+      if (j != i)
e20e41
+	{
e20e41
+	  /* Done already: copy the validation status */
e20e41
+	  if (rr_status && (i < ntohs(header->ancount)))
e20e41
+	    rr_status[i] = rr_status[j];
e20e41
+	}
e20e41
+      else
e20e41
+	{
e20e41
 	  /* Not done, validate now */
e20e41
-	  if (j == i)
e20e41
+	  int sigcnt, rrcnt;
e20e41
+	  char *wildname;
e20e41
+	  
e20e41
+	  if (!explore_rrset(header, plen, class1, type1, name, keyname, &sigcnt, &rrcnt))
e20e41
+	    return STAT_BOGUS;
e20e41
+	  
e20e41
+	  /* No signatures for RRset. We can be configured to assume this is OK and return a INSECURE result. */
e20e41
+	  if (sigcnt == 0)
e20e41
 	    {
e20e41
-	      int sigcnt, rrcnt;
e20e41
-	      char *wildname;
e20e41
-	      
e20e41
-	      if (!explore_rrset(header, plen, class1, type1, name, keyname, &sigcnt, &rrcnt))
e20e41
-		return STAT_BOGUS;
e20e41
-
e20e41
-	      /* No signatures for RRset. We can be configured to assume this is OK and return a INSECURE result. */
e20e41
-	      if (sigcnt == 0)
e20e41
+	      if (check_unsigned)
e20e41
 		{
e20e41
-		  if (check_unsigned)
e20e41
-		    {
e20e41
-		      rc = zone_status(name, class1, keyname, now);
e20e41
-		      if (rc == STAT_SECURE)
e20e41
-			rc = STAT_BOGUS;
e20e41
-		       if (class)
e20e41
-			 *class = class1; /* Class for NEED_DS or NEED_KEY */
e20e41
-		    }
e20e41
-		  else 
e20e41
-		    rc = STAT_INSECURE; 
e20e41
-		  
e20e41
-		  return rc;
e20e41
+		  rc = zone_status(name, class1, keyname, now);
e20e41
+		  if (rc == STAT_SECURE)
e20e41
+		    rc = STAT_BOGUS;
e20e41
+		  if (class)
e20e41
+		    *class = class1; /* Class for NEED_DS or NEED_KEY */
e20e41
 		}
e20e41
+	      else 
e20e41
+		rc = STAT_INSECURE; 
e20e41
 	      
e20e41
+	      if (rc != STAT_INSECURE)
e20e41
+		return rc;
e20e41
+	    }
e20e41
+	  else
e20e41
+	    {
e20e41
 	      /* explore_rrset() gives us key name from sigs in keyname.
e20e41
 		 Can't overwrite name here. */
e20e41
 	      strcpy(daemon->workspacename, keyname);
e20e41
 	      rc = zone_status(daemon->workspacename, class1, keyname, now);
e20e41
-
e20e41
-	      if (rc != STAT_SECURE)
e20e41
+	      
e20e41
+	      if (rc == STAT_BOGUS || rc == STAT_NEED_KEY || rc == STAT_NEED_DS)
e20e41
 		{
e20e41
 		  /* Zone is insecure, don't need to validate RRset */
e20e41
 		  if (class)
e20e41
 		    *class = class1; /* Class for NEED_DS or NEED_KEY */
e20e41
 		  return rc;
e20e41
-		} 
e20e41
-	      
e20e41
-	      rc = validate_rrset(now, header, plen, class1, type1, sigcnt, rrcnt, name, keyname, &wildname, NULL, 0, 0, 0);
e20e41
+		}
e20e41
 	      
e20e41
-	      if (rc == STAT_BOGUS || rc == STAT_NEED_KEY || rc == STAT_NEED_DS)
e20e41
-		{
e20e41
-		  if (class)
e20e41
-		    *class = class1; /* Class for DS or DNSKEY */
e20e41
-		  return rc;
e20e41
-		} 
e20e41
-	      else 
e20e41
+	      /* Zone is insecure, don't need to validate RRset */
e20e41
+	      if (rc == STAT_SECURE)
e20e41
 		{
e20e41
+		  rc = validate_rrset(now, header, plen, class1, type1, sigcnt,
e20e41
+				      rrcnt, name, keyname, &wildname, NULL, 0, 0, 0);
e20e41
+		  
e20e41
+		  if (rc == STAT_BOGUS || rc == STAT_NEED_KEY || rc == STAT_NEED_DS)
e20e41
+		    {
e20e41
+		      if (class)
e20e41
+			*class = class1; /* Class for DS or DNSKEY */
e20e41
+		      return rc;
e20e41
+		    } 
e20e41
+		  
e20e41
 		  /* rc is now STAT_SECURE or STAT_SECURE_WILDCARD */
e20e41
-		 
e20e41
+		  
e20e41
+		  /* Note that RR is validated */
e20e41
+		   if (rr_status && (i < ntohs(header->ancount)))
e20e41
+		     rr_status[i] = 1;
e20e41
+		   
e20e41
 		  /* Note if we've validated either the answer to the question
e20e41
 		     or the target of a CNAME. Any not noted will need NSEC or
e20e41
 		     to be in unsigned space. */
e20e41
-
e20e41
 		  for (j = 0; j 
e20e41
 		    if ((p2 = targets[j]))
e20e41
 		      {
e20e41
-			if (!(rc = extract_name(header, plen, &p2, name, 0, 10)))
e20e41
+			int rc1;
e20e41
+			if (!(rc1 = extract_name(header, plen, &p2, name, 0, 10)))
e20e41
 			  return STAT_BOGUS; /* bad packet */
e20e41
 			
e20e41
-			if (class1 == qclass && rc == 1 && (type1 == T_CNAME || type1 == qtype || qtype == T_ANY ))
e20e41
+			if (class1 == qclass && rc1 == 1 && (type1 == T_CNAME || type1 == qtype || qtype == T_ANY ))
e20e41
 			  targets[j] = NULL;
e20e41
 		      }
e20e41
-			    
e20e41
-		   /* An attacker replay a wildcard answer with a different
e20e41
-		      answer and overlay a genuine RR. To prove this
e20e41
-		      hasn't happened, the answer must prove that
e20e41
-		      the gennuine record doesn't exist. Check that here. 
e20e41
-		      Note that we may not yet have validated the NSEC/NSEC3 RRsets. 
e20e41
-		      That's not a problem since if the RRsets later fail
e20e41
-		      we'll return BOGUS then. */
e20e41
-		  if (rc == STAT_SECURE_WILDCARD && !prove_non_existence(header, plen, keyname, name, type1, class1, wildname, NULL))
e20e41
+		  
e20e41
+		  /* An attacker replay a wildcard answer with a different
e20e41
+		     answer and overlay a genuine RR. To prove this
e20e41
+		     hasn't happened, the answer must prove that
e20e41
+		     the genuine record doesn't exist. Check that here. 
e20e41
+		     Note that we may not yet have validated the NSEC/NSEC3 RRsets. 
e20e41
+		     That's not a problem since if the RRsets later fail
e20e41
+		     we'll return BOGUS then. */
e20e41
+		  if (rc == STAT_SECURE_WILDCARD &&
e20e41
+		      !prove_non_existence(header, plen, keyname, name, type1, class1, wildname, NULL))
e20e41
 		    return STAT_BOGUS;
e20e41
+
e20e41
+		  rc = STAT_SECURE;
e20e41
 		}
e20e41
 	    }
e20e41
 	}
e20e41
 
e20e41
-      if (!ADD_RDLEN(header, p1, plen, rdlen1))
e20e41
-	return STAT_BOGUS;
e20e41
+      if (rc == STAT_INSECURE)
e20e41
+	secure = STAT_INSECURE;
e20e41
     }
e20e41
 
e20e41
   /* OK, all the RRsets validate, now see if we have a missing answer or CNAME target. */
e20e41
@@ -2172,7 +2200,7 @@ int dnssec_validate_reply(time_t now, struct dns_header *header, size_t plen, ch
e20e41
 	  }
e20e41
       }
e20e41
   
e20e41
-  return STAT_SECURE;
e20e41
+  return secure;
e20e41
 }
e20e41
 
e20e41
 
e20e41
diff --git a/src/forward.c b/src/forward.c
e20e41
index a729c06..245c448 100644
e20e41
--- a/src/forward.c
e20e41
+++ b/src/forward.c
e20e41
@@ -561,7 +561,8 @@ static size_t process_reply(struct dns_header *header, time_t now, struct server
e20e41
   char **sets = 0;
e20e41
   int munged = 0, is_sign;
e20e41
   size_t plen; 
e20e41
-
e20e41
+  char *rr_status = NULL;
e20e41
+  
e20e41
   (void)ad_reqd;
e20e41
   (void)do_bit;
e20e41
   (void)bogusanswer;
e20e41
@@ -651,6 +652,11 @@ static size_t process_reply(struct dns_header *header, time_t now, struct server
e20e41
 	server->flags |= SERV_WARNED_RECURSIVE;
e20e41
     }  
e20e41
 
e20e41
+#ifdef HAVE_DNSSEC
e20e41
+  if (option_bool(OPT_DNSSEC_VALID))
e20e41
+    rr_status = daemon->rr_status;
e20e41
+#endif
e20e41
+
e20e41
   if (daemon->bogus_addr && RCODE(header) != NXDOMAIN &&
e20e41
       check_for_bogus_wildcard(header, n, daemon->namebuff, daemon->bogus_addr, now))
e20e41
     {
e20e41
@@ -676,7 +682,7 @@ static size_t process_reply(struct dns_header *header, time_t now, struct server
e20e41
 	  cache_secure = 0;
e20e41
 	}
e20e41
       
e20e41
-      if (extract_addresses(header, n, daemon->namebuff, now, sets, is_sign, check_rebind, no_cache, cache_secure, &doctored))
e20e41
+      if (extract_addresses(header, n, daemon->namebuff, now, sets, is_sign, check_rebind, no_cache, cache_secure, &doctored, rr_status))
e20e41
 	{
e20e41
 	  my_syslog(LOG_WARNING, _("possible DNS-rebind attack detected: %s"), daemon->namebuff);
e20e41
 	  munged = 1;
e20e41
@@ -856,7 +862,7 @@ void reply_query(int fd, int family, time_t now)
e20e41
   if (forward->forwardall == 0 || --forward->forwardall == 1 || RCODE(header) != REFUSED)
e20e41
     {
e20e41
       int check_rebind = 0, no_cache_dnssec = 0, cache_secure = 0, bogusanswer = 0;
e20e41
-
e20e41
+      
e20e41
       if (option_bool(OPT_NO_REBIND))
e20e41
 	check_rebind = !(forward->flags & FREC_NOREBIND);
e20e41
       
e20e41
@@ -896,7 +902,8 @@ void reply_query(int fd, int family, time_t now)
e20e41
 		    status = dnssec_validate_ds(now, header, n, daemon->namebuff, daemon->keyname, forward->class);
e20e41
 		  else
e20e41
 		    status = dnssec_validate_reply(now, header, n, daemon->namebuff, daemon->keyname, &forward->class, 
e20e41
-						   option_bool(OPT_DNSSEC_NO_SIGN), NULL, NULL);
e20e41
+						   option_bool(OPT_DNSSEC_NO_SIGN) && (server->flags & SERV_DO_DNSSEC),
e20e41
+						   NULL, NULL, daemon->rr_status);
e20e41
 		}
e20e41
 	      
e20e41
 	      /* Can't validate, as we're missing key data. Put this
e20e41
@@ -1480,7 +1487,9 @@ static int tcp_key_recurse(time_t now, int status, struct dns_header *header, si
e20e41
       else if (status == STAT_NEED_DS)
e20e41
 	new_status = dnssec_validate_ds(now, header, n, name, keyname, class);
e20e41
       else 
e20e41
-	new_status = dnssec_validate_reply(now, header, n, name, keyname, &class, option_bool(OPT_DNSSEC_NO_SIGN), NULL, NULL);
e20e41
+	new_status = dnssec_validate_reply(now, header, n, name, keyname, &class,
e20e41
+					   option_bool(OPT_DNSSEC_NO_SIGN) && (server->flags & SERV_DO_DNSSEC),
e20e41
+					   NULL, NULL, daemon->rr_status);
e20e41
       
e20e41
       if (new_status != STAT_NEED_DS && new_status != STAT_NEED_KEY)
e20e41
 	break;
e20e41
diff --git a/src/rfc1035.c b/src/rfc1035.c
e20e41
index f78b5cb..607412f 100644
e20e41
--- a/src/rfc1035.c
e20e41
+++ b/src/rfc1035.c
e20e41
@@ -571,7 +571,8 @@ static int find_soa(struct dns_header *header, size_t qlen, char *name, int *doc
e20e41
    expired and cleaned out that way. 
e20e41
    Return 1 if we reject an address because it look like part of dns-rebinding attack. */
e20e41
 int extract_addresses(struct dns_header *header, size_t qlen, char *name, time_t now, 
e20e41
-		      char **ipsets, int is_sign, int check_rebind, int no_cache_dnssec, int secure, int *doctored)
e20e41
+		      char **ipsets, int is_sign, int check_rebind, int no_cache_dnssec,
e20e41
+		      int secure, int *doctored, char *rr_status)
e20e41
 {
e20e41
   unsigned char *p, *p1, *endrr, *namep;
e20e41
   int i, j, qtype, qclass, aqtype, aqclass, ardlen, res, searched_soa = 0;
e20e41
@@ -582,6 +583,7 @@ int extract_addresses(struct dns_header *header, size_t qlen, char *name, time_t
e20e41
 #else
e20e41
   (void)ipsets; /* unused */
e20e41
 #endif
e20e41
+
e20e41
   
e20e41
   cache_start_insert();
e20e41
 
e20e41
@@ -590,10 +592,16 @@ int extract_addresses(struct dns_header *header, size_t qlen, char *name, time_t
e20e41
     {
e20e41
       searched_soa = 1;
e20e41
       ttl = find_soa(header, qlen, name, doctored);
e20e41
-#ifdef HAVE_DNSSEC
e20e41
-      if (*doctored && secure)
e20e41
-	return 0;
e20e41
-#endif
e20e41
+
e20e41
+      if (*doctored)
e20e41
+	{
e20e41
+	  if (secure)
e20e41
+	    return 0;
e20e41
+	  if (rr_status)
e20e41
+	    for (i = 0; i < ntohs(header->ancount); i++)
e20e41
+	      if (rr_status[i])
e20e41
+		return 0;
e20e41
+	}
e20e41
     }
e20e41
   
e20e41
   /* go through the questions. */
e20e41
@@ -604,7 +612,7 @@ int extract_addresses(struct dns_header *header, size_t qlen, char *name, time_t
e20e41
       int found = 0, cname_count = CNAME_CHAIN;
e20e41
       struct crec *cpp = NULL;
e20e41
       int flags = RCODE(header) == NXDOMAIN ? F_NXDOMAIN : 0;
e20e41
-      int secflag = secure ?  F_DNSSECOK : 0;
e20e41
+      int cname_short = 0;
e20e41
       unsigned long cttl = ULONG_MAX, attl;
e20e41
 
e20e41
       namep = p;
e20e41
@@ -632,8 +640,9 @@ int extract_addresses(struct dns_header *header, size_t qlen, char *name, time_t
e20e41
 	      if (!(p1 = skip_questions(header, qlen)))
e20e41
 		return 0;
e20e41
 	      
e20e41
-	      for (j = ntohs(header->ancount); j != 0; j--) 
e20e41
+	      for (j = 0; j < ntohs(header->ancount); j++) 
e20e41
 		{
e20e41
+		  int secflag = 0;
e20e41
 		  unsigned char *tmp = namep;
e20e41
 		  /* the loop body overwrites the original name, so get it back here. */
e20e41
 		  if (!extract_name(header, qlen, &tmp, name, 1, 0) ||
e20e41
@@ -659,11 +668,21 @@ int extract_addresses(struct dns_header *header, size_t qlen, char *name, time_t
e20e41
 		    {
e20e41
 		      if (!extract_name(header, qlen, &p1, name, 1, 0))
e20e41
 			return 0;
e20e41
-		      
e20e41
+
e20e41
+		      if (rr_status && rr_status[j])
e20e41
+			{
e20e41
+			  /* validated RR anywhere in CNAME chain, don't cache. */
e20e41
+			  if (cname_short || aqtype == T_CNAME)
e20e41
+			    return 0;
e20e41
+
e20e41
+			  secflag = F_DNSSECOK;
e20e41
+			}
e20e41
+
e20e41
 		      if (aqtype == T_CNAME)
e20e41
 			{
e20e41
-			  if (!cname_count-- || secure)
e20e41
-			    return 0; /* looped CNAMES, or DNSSEC, which we can't cache. */
e20e41
+			  if (!cname_count--)
e20e41
+			    return 0; /* looped CNAMES, we can't cache. */
e20e41
+			  cname_short = 1;
e20e41
 			  goto cname_loop;
e20e41
 			}
e20e41
 		      
e20e41
@@ -685,7 +704,7 @@ int extract_addresses(struct dns_header *header, size_t qlen, char *name, time_t
e20e41
 		  ttl = find_soa(header, qlen, NULL, doctored);
e20e41
 		}
e20e41
 	      if (ttl)
e20e41
-		cache_insert(NULL, &addr, now, ttl, name_encoding | F_REVERSE | F_NEG | flags | secflag);	
e20e41
+		cache_insert(NULL, &addr, now, ttl, name_encoding | F_REVERSE | F_NEG | flags | (secure ?  F_DNSSECOK : 0));	
e20e41
 	    }
e20e41
 	}
e20e41
       else
e20e41
@@ -713,8 +732,10 @@ int extract_addresses(struct dns_header *header, size_t qlen, char *name, time_t
e20e41
 	  if (!(p1 = skip_questions(header, qlen)))
e20e41
 	    return 0;
e20e41
 	  
e20e41
-	  for (j = ntohs(header->ancount); j != 0; j--) 
e20e41
+	  for (j = 0; j < ntohs(header->ancount); j++) 
e20e41
 	    {
e20e41
+	      int secflag = 0;
e20e41
+	      
e20e41
 	      if (!(res = extract_name(header, qlen, &p1, name, 0, 10)))
e20e41
 		return 0; /* bad packet */
e20e41
 	      
e20e41
@@ -731,6 +752,10 @@ int extract_addresses(struct dns_header *header, size_t qlen, char *name, time_t
e20e41
 	      
e20e41
 	      if (aqclass == C_IN && res != 2 && (aqtype == T_CNAME || aqtype == qtype))
e20e41
 		{
e20e41
+#ifdef HAVE_DNSSEC
e20e41
+		  if (rr_status && rr_status[j])
e20e41
+		      secflag = F_DNSSECOK;
e20e41
+#endif		  
e20e41
 		  if (aqtype == T_CNAME)
e20e41
 		    {
e20e41
 		      if (!cname_count--)
e20e41
@@ -822,7 +847,7 @@ int extract_addresses(struct dns_header *header, size_t qlen, char *name, time_t
e20e41
 		 pointing at this, inherit its TTL */
e20e41
 	      if (ttl || cpp)
e20e41
 		{
e20e41
-		  newc = cache_insert(name, NULL, now, ttl ? ttl : cttl, F_FORWARD | F_NEG | flags | secflag);	
e20e41
+		  newc = cache_insert(name, NULL, now, ttl ? ttl : cttl, F_FORWARD | F_NEG | flags | (secure ? F_DNSSECOK : 0));	
e20e41
 		  if (newc && cpp)
e20e41
 		    {
e20e41
 		      cpp->addr.cname.target.cache = newc;
e20e41
@@ -937,7 +962,7 @@ int check_for_local_domain(char *name, time_t now)
e20e41
   /* Note: the call to cache_find_by_name is intended to find any record which matches
e20e41
      ie A, AAAA, CNAME. */
e20e41
 
e20e41
-  if ((crecp = cache_find_by_name(NULL, name, now, F_IPV4 | F_IPV6 | F_CNAME |F_NO_RR)) &&
e20e41
+  if ((crecp = cache_find_by_name(NULL, name, now, F_IPV4 | F_IPV6 | F_CNAME | F_NO_RR)) &&
e20e41
       (crecp->flags & (F_HOSTS | F_DHCP | F_CONFIG)))
e20e41
     return 1;
e20e41
   
e20e41
@@ -1689,8 +1714,9 @@ size_t answer_request(struct dns_header *header, char *limit, size_t qlen,
e20e41
 
e20e41
 	  if (qtype == T_CNAME || qtype == T_ANY)
e20e41
 	    {
e20e41
-	      if ((crecp = cache_find_by_name(NULL, name, now, F_CNAME)) &&
e20e41
-		  (qtype == T_CNAME || (crecp->flags & (F_HOSTS | F_DHCP | F_CONFIG  | (dryrun ? F_NO_RR : 0)))))
e20e41
+	      if ((crecp = cache_find_by_name(NULL, name, now, F_CNAME | (dryrun ? F_NO_RR : 0))) &&
e20e41
+		  (qtype == T_CNAME || (crecp->flags & F_CONFIG)) &&
e20e41
+		  ((crecp->flags & F_CONFIG) || !do_bit || !(crecp->flags & F_DNSSECOK)))
e20e41
 		{
e20e41
 		  if (!(crecp->flags & F_DNSSECOK))
e20e41
 		    sec_data = 0;
e20e41
-- 
e20e41
2.20.1
e20e41