Strip DNSSEC RRs when query doesn't have DO bit set.
diff --git a/src/dnsmasq.h b/src/dnsmasq.h
index d52adfa..f984f3b 100644
--- a/src/dnsmasq.h
+++ b/src/dnsmasq.h
@@ -542,6 +542,8 @@
 #define FREC_DNSKEY_QUERY       8
 #define FREC_DS_QUERY          16
 #define FREC_AD_QUESTION       32
+#define FREC_DO_QUESTION       64
+#define FREC_ADDED_PHEADER    128
 
 #ifdef HAVE_DNSSEC
 #define HASH_SIZE 20 /* SHA-1 digest size */
@@ -1048,7 +1050,7 @@
 		      int no_cache, int secure, int *doctored);
 size_t answer_request(struct dns_header *header, char *limit, size_t qlen,  
 		      struct in_addr local_addr, struct in_addr local_netmask, 
-		      time_t now, int *ad_reqd);
+		      time_t now, int *ad_reqd, int *do_bit);
 int check_for_bogus_wildcard(struct dns_header *header, size_t qlen, char *name, 
 			     struct bogus_addr *addr, time_t now);
 unsigned char *find_pseudoheader(struct dns_header *header, size_t plen,
@@ -1085,6 +1087,7 @@
 int dnssec_validate_ds(time_t now, struct dns_header *header, size_t plen, char *name, char *keyname, int class);
 int dnssec_validate_reply(time_t now, struct dns_header *header, size_t plen, char *name, char *keyname, int *class);
 int dnskey_keytag(int alg, int flags, unsigned char *rdata, int rdlen);
+size_t filter_rrsigs(struct dns_header *header, size_t plen);
 unsigned char* hash_questions(struct dns_header *header, size_t plen, char *name);
 
 /* util.c */
diff --git a/src/dnssec.c b/src/dnssec.c
index be1b101..a902ded 100644
--- a/src/dnssec.c
+++ b/src/dnssec.c
@@ -472,6 +472,32 @@
     }
 }
 
+static int expand_workspace(unsigned char ***wkspc, int *sz, int new)
+{
+  unsigned char **p;
+  int new_sz = *sz;
+  
+  if (new_sz > new)
+    return 1;
+
+  if (new >= 100)
+    return 0;
+
+  new_sz += 5;
+  
+  if (!(p = whine_malloc((new_sz) * sizeof(unsigned char **))))
+    return 0;  
+  
+  if (*wkspc)
+    {
+      memcpy(p, *wkspc, *sz * sizeof(unsigned char **));
+      free(*wkspc);
+    }
+  
+  *wkspc = p;
+  *sz = new_sz;
+}
+
 /* Bubble sort the RRset into the canonical order. 
    Note that the byte-streams from two RRs may get unsynced: consider 
    RRs which have two domain-names at the start and then other data.
@@ -615,64 +641,31 @@
 	{
 	  if (stype == type)
 	    {
-	      if (rrsetidx == rrset_sz)
-		{
-		  unsigned char **new;
-
-		  /* Protect against insane/maliciuos queries which bloat the workspace
-		     and eat CPU in the sort */
-		  if (rrsetidx >= 100)
-		    return STAT_INSECURE; 
-
-		  /* expand */
-		  if (!(new = whine_malloc((rrset_sz + 5) * sizeof(unsigned char **))))
-		    return STAT_INSECURE;
-		  
-		  if (rrset)
-		    {
-		      memcpy(new, rrset, rrset_sz * sizeof(unsigned char **));
-		      free(rrset);
-		    }
-		  
-		  rrset = new;
-		  rrset_sz += 5;
-		}
+	      if (!expand_workspace(&rrset, &rrset_sz, rrsetidx))
+		return STAT_INSECURE; 
+	      
 	      rrset[rrsetidx++] = pstart;
 	    }
 	  
 	  if (stype == T_RRSIG)
 	    {
-	       if (rdlen < 18)
-		 return STAT_INSECURE; /* bad packet */ 
-	         
-	       GETSHORT(type_covered, p);
-	       
-	       if (type_covered == type)
-		 {
-		   if (sigidx == sig_sz)
-		     {
-		       unsigned char **new;
-		       
-		       /* expand */
-		       if (!(new = whine_malloc((sig_sz + 5) * sizeof(unsigned char **))))
-			 return STAT_INSECURE;
-		       
-		       if (sigs)
-			 {
-			   memcpy(new, sigs, sig_sz * sizeof(unsigned char **));
-			   free(sigs);
-			 }
-		       
-		       sigs = new;
-		       sig_sz += 5;
-		     }
-		   
-		   sigs[sigidx++] = pdata;
-		 } 
-	       p = pdata + 2; /* restore for ADD_RDLEN */
+	      if (rdlen < 18)
+		return STAT_INSECURE; /* bad packet */ 
+	      
+	      GETSHORT(type_covered, p);
+	      
+	      if (type_covered == type)
+		{
+		  if (!expand_workspace(&sigs, &sig_sz, sigidx))
+		    return STAT_INSECURE; 
+		  
+		  sigs[sigidx++] = pdata;
+		} 
+	      
+	      p = pdata + 2; /* restore for ADD_RDLEN */
 	    }
 	}
-     
+      
       if (!ADD_RDLEN(header, p, plen, rdlen))
 	return STAT_INSECURE;
     }
@@ -1195,32 +1188,12 @@
 
 	  type_found = type;
 
-	  if (nsecs_found == nsecset_sz)
-	    {
-	      unsigned char **new;
-	      
-	      /* Protect against insane/malicious queries which bloat the workspace
-		 and eat CPU in the sort */
-	      if (nsecs_found >= 100)
-		return 0; 
-
-	      /* expand */
-	      if (!(new = whine_malloc((nsecset_sz + 5) * sizeof(unsigned char **))))
-		return 0;
-		  
-	      if (nsecset)
-		{
-		  memcpy(new, nsecset, nsecset_sz * sizeof(unsigned char **));
-		  free(nsecset);
-		}
-	      
-	      nsecset = new;
-	      nsecset_sz += 5;
-	    }
-
+	  if (!expand_workspace(&nsecset, &nsecset_sz, nsecs_found))
+	    return 0; 
+	  
 	  nsecset[nsecs_found++] = pstart;
 	}
-     
+      
       if (!ADD_RDLEN(header, p, plen, rdlen))
 	return 0;
     }
@@ -1908,6 +1881,235 @@
   return add_do_bit(header, p - (unsigned char *)header, end);
 }
 
+/* Go through a domain name, find "pointers" and fix them up based on how many bytes
+   we've chopped out of the packet, or check they don't point into an elided part.  */
+static int check_name(unsigned char **namep, struct dns_header *header, size_t plen, int fixup, unsigned char **rrs, int rr_count)
+{
+  unsigned char *ansp = *namep;
+
+  while(1)
+    {
+      unsigned int label_type;
+      
+      if (!CHECK_LEN(header, ansp, plen, 1))
+	return 0;
+      
+      label_type = (*ansp) & 0xc0;
+
+      if (label_type == 0xc0)
+	{
+	  /* pointer for compression. */
+	  unsigned int offset, i;
+	  unsigned char *p;
+	  
+	  if (!CHECK_LEN(header, ansp, plen, 2))
+	    return 0;
+
+	  offset = ((*ansp++) & 0x3f) << 8;
+	  offset |= *ansp++;
+
+	  p = offset + (unsigned char *)header;
+	  
+	  for (i = 0; i < rr_count; i++)
+	    if (p < rrs[i])
+	      break;
+	    else
+	      if (i & 1)
+		offset -= rrs[i] - rrs[i-1];
+
+	  /* does the pointer end up in an elided RR? */
+	  if (i & 1)
+	    return -1;
+
+	  /* No, scale the pointer */
+	  if (fixup)
+	    {
+	      ansp -= 2;
+	      *ansp++ = (offset >> 8) | 0xc0;
+	      *ansp++ = offset & 0xff;
+	    }
+	  break;
+	}
+      else if (label_type == 0x80)
+	return 0; /* reserved */
+      else if (label_type == 0x40)
+	{
+	  /* Extended label type */
+	  unsigned int count;
+	  
+	  if (!CHECK_LEN(header, ansp, plen, 2))
+	    return 0;
+	  
+	  if (((*ansp++) & 0x3f) != 1)
+	    return 0; /* we only understand bitstrings */
+	  
+	  count = *(ansp++); /* Bits in bitstring */
+	  
+	  if (count == 0) /* count == 0 means 256 bits */
+	    ansp += 32;
+	  else
+	    ansp += ((count-1)>>3)+1;
+	}
+      else
+	{ /* label type == 0 Bottom six bits is length */
+	  unsigned int len = (*ansp++) & 0x3f;
+	  
+	  if (!ADD_RDLEN(header, ansp, plen, len))
+	    return 0;
+
+	  if (len == 0)
+	    break; /* zero length label marks the end. */
+	}
+    }
+
+  *namep = ansp;
+
+  return 1;
+}
+
+/* Go through RRs and check or fixup the domain names contained within */
+static int check_rrs(unsigned char *p, struct dns_header *header, size_t plen, int fixup, unsigned char **rrs, int rr_count)
+{
+  int i, type, class, rdlen;
+  
+  for (i = 0; i < ntohs(header->ancount) + ntohs(header->nscount); i++)
+    {
+       if (type != T_NSEC && type != T_NSEC3 && type != T_RRSIG)
+	 {
+	   if (!check_name(&p, header, plen, fixup, rrs, rr_count))
+	     return 0;
+	 }
+       else
+	 {
+	   if (!(p = skip_name(p, header, plen, 10)))
+	     return 0;
+	 }
+      
+      GETSHORT(type, p); 
+      GETSHORT(class, p);
+      p += 4; /* TTL */
+      GETSHORT(rdlen, p);
+      
+      if (type != T_NSEC && type != T_NSEC3 && type != T_RRSIG)
+	{
+	  if (class == C_IN)
+	    {
+	      u16 *d;
+	      unsigned char *pp = p;
+	      
+	      for (d = get_desc(type); *d != (u16)-1; d++)
+		{
+		  if (*d != 0)
+		    pp += *d;
+		  else if (!check_name(&pp, header, plen, fixup, rrs, rr_count))
+		    return 0;
+		}
+	    }
+	}
+      
+      if (!ADD_RDLEN(header, p, plen, rdlen))
+	return 0;
+    }
+  
+  return 1;
+}
+	
+
+size_t filter_rrsigs(struct dns_header *header, size_t plen)
+{
+  static unsigned char **rrs;
+  static int rr_sz = 0;
+  
+  unsigned char *p = (unsigned char *)(header+1);
+  int i, rdlen, qtype, qclass, rr_found, chop_an, chop_ns;
+
+  if (ntohs(header->qdcount) != 1 ||
+      !(p = skip_name(p, header, plen, 4)))
+    return plen;
+  
+  GETSHORT(qtype, p);
+  GETSHORT(qclass, p);
+
+  /* First pass, find pointers to start and end of all the records we wish to elide:
+     records added for DNSSEC, unless explicity queried for */
+  for (rr_found = 0, chop_ns = 0, chop_an = 0, i = 0; i < ntohs(header->ancount) + ntohs(header->nscount); i++)
+    {
+      unsigned char *pstart = p;
+      int type, class;
+
+      if (!(p = skip_name(p, header, plen, 10)))
+	return plen;
+      
+      GETSHORT(type, p); 
+      GETSHORT(class, p);
+      p += 4; /* TTL */
+      GETSHORT(rdlen, p);
+      
+      if ((type == T_NSEC || type == T_NSEC3 || type == T_RRSIG) && 
+	  (type != qtype || class != qclass))
+	{
+	  if (!expand_workspace(&rrs, &rr_sz, rr_found + 1))
+	    return plen; 
+	  
+	  rrs[rr_found++] = pstart;
+
+	  if (!ADD_RDLEN(header, p, plen, rdlen))
+	    return plen;
+	  
+	  rrs[rr_found++] = p;
+	  
+	  if (i < ntohs(header->ancount))
+	    chop_an++;
+	  else
+	    chop_ns++;
+	}
+      else if (!ADD_RDLEN(header, p, plen, rdlen))
+	return plen;
+    }
+  
+  /* Nothing to do. */
+  if (rr_found == 0)
+    return plen;
+
+  /* Second pass, look for pointers in names in the records we're keeping and make sure they don't
+     point to records we're going to elide. This is theoretically possible, but unlikely. If
+     it happens, we give up and leave the answer unchanged. */
+  p = (unsigned char *)(header+1);
+  
+  /* question first */
+  if (!check_name(&p, header, plen, 0, rrs, rr_found))
+    return plen;
+  p += 4; /* qclass, qtype */
+  
+  /* Now answers and NS */
+  if (!check_rrs(p, header, plen, 0, rrs, rr_found))
+    return plen;
+  
+  /* Third pass, elide records */
+  for (p = rrs[0], i = 1; i < rr_found; i += 2)
+    {
+      unsigned char *start = rrs[i];
+      unsigned char *end = (i != rr_found - 1) ? rrs[i+1] : ((unsigned char *)(header+1)) + plen;
+      
+      memmove(p, start, end-start);
+      p += end-start;
+    }
+     
+  plen = p - (unsigned char *)header;
+  header->ancount = htons(ntohs(header->ancount) - chop_an);
+  header->nscount = htons(ntohs(header->nscount) - chop_ns);
+  
+  /* Fourth pass, fix up pointers in the remaining records */
+  p = (unsigned char *)(header+1);
+  
+  check_name(&p, header, plen, 1, rrs, rr_found);
+  p += 4; /* qclass, qtype */
+  
+  check_rrs(p, header, plen, 1, rrs, rr_found);
+  
+  return plen;
+}
+
 unsigned char* hash_questions(struct dns_header *header, size_t plen, char *name)
 {
   int q;
diff --git a/src/forward.c b/src/forward.c
index 5b9fa1e..c05efe4 100644
--- a/src/forward.c
+++ b/src/forward.c
@@ -235,7 +235,7 @@
 static int forward_query(int udpfd, union mysockaddr *udpaddr,
 			 struct all_addr *dst_addr, unsigned int dst_iface,
 			 struct dns_header *header, size_t plen, time_t now, 
-			 struct frec *forward, int ad_reqd)
+			 struct frec *forward, int ad_reqd, int do_bit)
 {
   char *domain = NULL;
   int type = 0, norebind = 0;
@@ -336,8 +336,10 @@
 	    forward->flags |= FREC_AD_QUESTION;
 #ifdef HAVE_DNSSEC
 	  forward->work_counter = DNSSEC_WORK;
+	  if (do_bit)
+	    forward->flags |= FREC_DO_QUESTION;
 #endif
-
+	  
 	  header->id = htons(forward->new_id);
 	  
 	  /* In strict_order mode, always try servers in the order 
@@ -393,11 +395,17 @@
 #ifdef HAVE_DNSSEC
       if (option_bool(OPT_DNSSEC_VALID))
 	{
-	  plen = add_do_bit(header, plen, ((char *) header) + daemon->packet_buff_sz);
+	  size_t new_plen = add_do_bit(header, plen, ((char *) header) + daemon->packet_buff_sz);
+	 
 	  /* For debugging, set Checking Disabled, otherwise, have the upstream check too,
 	     this allows it to select auth servers when one is returning bad data. */
 	  if (option_bool(OPT_DNSSEC_DEBUG))
 	    header->hb4 |= HB4_CD;
+
+	  if (new_plen != plen)
+	    forward->flags |= FREC_ADDED_PHEADER;
+
+	  plen = new_plen;
 	}
 #endif
 
@@ -506,7 +514,7 @@
 }
 
 static size_t process_reply(struct dns_header *header, time_t now, struct server *server, size_t n, int check_rebind, 
-			    int no_cache, int cache_secure, int ad_reqd, int check_subnet, union mysockaddr *query_source)
+			    int no_cache, int cache_secure, int ad_reqd, int do_bit, int added_pheader, int check_subnet, union mysockaddr *query_source)
 {
   unsigned char *pheader, *sizep;
   char **sets = 0;
@@ -553,6 +561,12 @@
 	  my_syslog(LOG_WARNING, _("discarding DNS reply: subnet option mismatch"));
 	  return 0;
 	}
+      
+      if (added_pheader)
+	{
+	  pheader = 0; 
+	  header->arcount = htons(0);
+	}
     }
   
   /* RFC 4035 sect 4.6 para 3 */
@@ -624,6 +638,10 @@
   
   if (!(header->hb4 & HB4_CD) && ad_reqd && cache_secure)
     header->hb4 |= HB4_AD;
+
+  /* If the requestor didn't set the DO bit, don't return DNSSEC info. */
+  if (!do_bit)
+    n = filter_rrsigs(header, n);
 #endif
 
   /* do this after extract_addresses. Ensure NODATA reply and remove
@@ -708,7 +726,7 @@
 	  if ((nn = resize_packet(header, (size_t)n, pheader, plen)))
 	    {
 	      header->hb3 &= ~(HB3_QR | HB3_TC);
-	      forward_query(-1, NULL, NULL, 0, header, nn, now, forward, 0);
+	      forward_query(-1, NULL, NULL, 0, header, nn, now, forward, 0, 0);
 	      return;
 	    }
 	}
@@ -927,7 +945,8 @@
 	header->hb4 &= ~HB4_CD;
       
       if ((nn = process_reply(header, now, server, (size_t)n, check_rebind, no_cache_dnssec, cache_secure,
-			      forward->flags & FREC_AD_QUESTION, forward->flags & FREC_HAS_SUBNET, &forward->source)))
+			      forward->flags & FREC_AD_QUESTION, forward->flags & FREC_DO_QUESTION, 
+			      forward->flags & FREC_ADDED_PHEADER, forward->flags & FREC_HAS_SUBNET, &forward->source)))
 	{
 	  header->id = htons(forward->orig_id);
 	  header->hb4 |= HB4_RA; /* recursion if available */
@@ -1169,9 +1188,9 @@
   else
 #endif
     {
-      int ad_reqd;
+      int ad_reqd, do_bit;
       m = answer_request(header, ((char *) header) + daemon->packet_buff_sz, (size_t)n, 
-			 dst_addr_4, netmask, now, &ad_reqd);
+			 dst_addr_4, netmask, now, &ad_reqd, &do_bit);
       
       if (m >= 1)
 	{
@@ -1180,7 +1199,7 @@
 	  daemon->local_answer++;
 	}
       else if (forward_query(listen->fd, &source_addr, &dst_addr, if_index,
-			     header, (size_t)n, now, NULL, ad_reqd))
+			     header, (size_t)n, now, NULL, ad_reqd, do_bit))
 	daemon->queries_forwarded++;
       else
 	daemon->local_answer++;
@@ -1272,7 +1291,8 @@
 #ifdef HAVE_AUTH
   int local_auth = 0;
 #endif
-  int checking_disabled, ad_question, check_subnet, no_cache_dnssec = 0, cache_secure = 0;
+  int checking_disabled, ad_question, do_bit, added_pheader = 0;
+  int check_subnet, no_cache_dnssec = 0, cache_secure = 0;
   size_t m;
   unsigned short qtype;
   unsigned int gotname;
@@ -1350,7 +1370,7 @@
 	{
 	  /* m > 0 if answered from cache */
 	  m = answer_request(header, ((char *) header) + 65536, (size_t)size, 
-			     dst_addr_4, netmask, now, &ad_question);
+			     dst_addr_4, netmask, now, &ad_question, &do_bit);
 	  
 	  /* Do this by steam now we're not in the select() loop */
 	  check_log_writer(NULL); 
@@ -1430,11 +1450,17 @@
 #ifdef HAVE_DNSSEC
 			  if (option_bool(OPT_DNSSEC_VALID))
 			    {
-			      size = add_do_bit(header, size, ((char *) header) + 65536);
+			      size_t new_size = add_do_bit(header, size, ((char *) header) + 65536);
+			      
 			      /* For debugging, set Checking Disabled, otherwise, have the upstream check too,
 				 this allows it to select auth servers when one is returning bad data. */
 			      if (option_bool(OPT_DNSSEC_DEBUG))
 				header->hb4 |= HB4_CD;
+			      
+			      if (size != new_size)
+				added_pheader = 1;
+			      
+			      size = new_size;
 			    }
 #endif
 			  
@@ -1533,7 +1559,7 @@
 
 		      m = process_reply(header, now, last_server, (unsigned int)m, 
 					option_bool(OPT_NO_REBIND) && !norebind, no_cache_dnssec,
-					cache_secure, ad_question, check_subnet, &peer_addr); 
+					cache_secure, ad_question, do_bit, added_pheader, check_subnet, &peer_addr); 
 		      
 		      break;
 		    }
diff --git a/src/rfc1035.c b/src/rfc1035.c
index b9152e1..5693ef9 100644
--- a/src/rfc1035.c
+++ b/src/rfc1035.c
@@ -601,11 +601,11 @@
     
   if (family == parm->l3->sa.sa_family)
     {
-      if (family == AF_INET && memcmp (&parm->l3->in.sin_addr, addrp, INADDRSZ) == 0)
+      if (family == AF_INET && memcmp(&parm->l3->in.sin_addr, addrp, INADDRSZ) == 0)
 	match = 1;
 #ifdef HAVE_IPV6
       else
-	if (family == AF_INET6 && memcmp (&parm->l3->in6.sin6_addr, addrp, IN6ADDRSZ) == 0)
+	if (family == AF_INET6 && memcmp(&parm->l3->in6.sin6_addr, addrp, IN6ADDRSZ) == 0)
 	  match = 1;
 #endif
     }
@@ -1453,7 +1453,7 @@
 /* return zero if we can't answer from cache, or packet size if we can */
 size_t answer_request(struct dns_header *header, char *limit, size_t qlen,  
 		      struct in_addr local_addr, struct in_addr local_netmask, 
-		      time_t now, int *ad_reqd) 
+		      time_t now, int *ad_reqd, int *do_bit) 
 {
   char *name = daemon->namebuff;
   unsigned char *p, *ansp, *pheader;
@@ -1475,7 +1475,8 @@
   
   /* RFC 6840 5.7 */
   *ad_reqd = header->hb4 & HB4_AD;
-  
+  *do_bit = 0;
+
   /* If there is an RFC2671 pseudoheader then it will be overwritten by
      partial replies, so we have to do a dry run to see if we can answer
      the query. We check to see if the do bit is set, if so we always
@@ -1493,7 +1494,8 @@
       pheader += 2; /* ext_rcode */
       GETSHORT(flags, pheader);
       
-      sec_reqd = flags & 0x8000; /* do bit */ 
+      if ((sec_reqd = flags & 0x8000))
+	*do_bit = 1;/* do bit */ 
       *ad_reqd = 1;
 
       /* If our client is advertising a larger UDP packet size