Check that unsigned replies come from unsigned zones if --dnssec-check-unsigned set.
diff --git a/man/dnsmasq.8 b/man/dnsmasq.8
index ed71ebe..7bf1db7 100644
--- a/man/dnsmasq.8
+++ b/man/dnsmasq.8
@@ -608,6 +608,15 @@
 but trust anchors for limited domains are also possible. The current
 root-zone trust anchors may be donwloaded from https://data.iana.org/root-anchors/root-anchors.xml 
 .TP
+.B --dnssec-check-unsigned
+As a default, dnsmasq does not check that unsigned DNS replies are
+legitimate: they are assumed to be valid and passed on (without the
+"authentic data" bit set, of course). This does not protect against an
+attacker forging unsigned replies for signed DNS zones, but it is
+fast. If this flag is set, dnsmasq will check the zones of unsigned
+replies, to ensure that unsigned replies are allowed in those
+zones. The cost of this is more upstream queries and slower performance.
+.TP
 .B --proxy-dnssec
 Copy the DNSSEC Authenticated Data bit from upstream servers to downstream clients and cache it.  This is an 
 alternative to having dnsmasq validate DNSSEC, but it depends on the security of the network between 
diff --git a/src/dnsmasq.h b/src/dnsmasq.h
index f984f3b..a00d95c 100644
--- a/src/dnsmasq.h
+++ b/src/dnsmasq.h
@@ -232,7 +232,8 @@
 #define OPT_DNSSEC_VALID   45
 #define OPT_DNSSEC_PERMISS 46
 #define OPT_DNSSEC_DEBUG   47
-#define OPT_LAST           48
+#define OPT_DNSSEC_NO_SIGN 48 
+#define OPT_LAST           49
 
 /* extra flags for my_syslog, we use a couple of facilities since they are known 
    not to occupy the same bits as priorities, no matter how syslog.h is set up. */
@@ -535,6 +536,10 @@
 #define STAT_NEED_KEY           5
 #define STAT_TRUNCATED          6
 #define STAT_SECURE_WILDCARD    7
+#define STAT_NO_SIG             8
+#define STAT_NO_DS              9
+#define STAT_NEED_DS_NEG       10
+#define STAT_CHASE_CNAME       11
 
 #define FREC_NOREBIND           1
 #define FREC_CHECKING_DISABLED  2
@@ -544,6 +549,7 @@
 #define FREC_AD_QUESTION       32
 #define FREC_DO_QUESTION       64
 #define FREC_ADDED_PHEADER    128
+#define FREC_CHECK_NOSIGN     256
 
 #ifdef HAVE_DNSSEC
 #define HASH_SIZE 20 /* SHA-1 digest size */
@@ -1085,7 +1091,8 @@
 size_t dnssec_generate_query(struct dns_header *header, char *end, char *name, int class, int type, union mysockaddr *addr);
 int dnssec_validate_by_ds(time_t now, struct dns_header *header, size_t n, char *name, char *keyname, int class);
 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 dnssec_validate_reply(time_t now, struct dns_header *header, size_t plen, char *name, char *keyname, int *class, int *neganswer);
+int dnssec_chase_cname(time_t now, struct dns_header *header, size_t plen, char *name, char *keyname);
 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);
diff --git a/src/dnssec.c b/src/dnssec.c
index 1a1c0e4..3592691 100644
--- a/src/dnssec.c
+++ b/src/dnssec.c
@@ -496,6 +496,8 @@
   
   *wkspc = p;
   *sz = new_sz;
+
+  return 1;
 }
 
 /* Bubble sort the RRset into the canonical order. 
@@ -588,6 +590,7 @@
    Return code:
    STAT_SECURE   if it validates.
    STAT_SECURE_WILDCARD if it validates and is the result of wildcard expansion.
+   STAT_NO_SIG no RRsigs found.
    STAT_INSECURE can't validate (no RRSIG, bad packet).
    STAT_BOGUS    signature is wrong.
    STAT_NEED_KEY need DNSKEY to complete validation (name is returned in keyname)
@@ -670,9 +673,13 @@
 	return STAT_INSECURE;
     }
   
-  /* RRset empty, no RRSIGs */
-  if (rrsetidx == 0 || sigidx == 0)
+  /* RRset empty */
+  if (rrsetidx == 0)
     return STAT_INSECURE; 
+
+  /* no RRSIGs */
+  if (sigidx == 0)
+    return STAT_NO_SIG; 
   
   /* Sort RRset records into canonical order. 
      Note that at this point keyname and daemon->workspacename buffs are
@@ -1058,6 +1065,7 @@
    return codes:
    STAT_INSECURE    bad packet, no DS in reply, proven no DS in reply.
    STAT_SECURE      At least one valid DS found and in cache.
+   STAT_NO_DS       It's proved there's no DS here.
    STAT_BOGUS       At least one DS found, which fails validation.
    STAT_NEED_DNSKEY DNSKEY records to validate a DS not found, name in keyname
 */
@@ -1065,7 +1073,7 @@
 int dnssec_validate_ds(time_t now, struct dns_header *header, size_t plen, char *name, char *keyname, int class)
 {
   unsigned char *p = (unsigned char *)(header+1);
-  int qtype, qclass, val, i;
+  int qtype, qclass, val, i, neganswer;
 
   if (ntohs(header->qdcount) != 1 ||
       !(p = skip_name(p, header, plen, 4)))
@@ -1077,25 +1085,36 @@
   if (qtype != T_DS || qclass != class)
     val = STAT_BOGUS;
   else
-    val = dnssec_validate_reply(now, header, plen, name, keyname, NULL);
+    val = dnssec_validate_reply(now, header, plen, name, keyname, NULL, &neganswer);
+
+  if (val == STAT_NO_SIG)
+    val = STAT_INSECURE;
   
   p = (unsigned char *)(header+1);
   extract_name(header, plen, &p, name, 1, 4);
   p += 4; /* qtype, qclass */
   
+  if (!(p = skip_section(p, ntohs(header->ancount), header, plen)))
+    return STAT_INSECURE;
+  
   if (val == STAT_BOGUS)
     log_query(F_UPSTREAM, name, NULL, "BOGUS DS");
   
-  /* proved that no DS exists, cache neg answer, can't validate */
-  if (val == STAT_SECURE && ntohs(header->ancount) == 0)
+  if ((val == STAT_SECURE || val == STAT_INSECURE) && neganswer)
     {
-      int  rdlen, rc;
+      int rdlen, flags =  F_FORWARD | F_DS | F_NEG ;
       unsigned long ttl, minttl = ULONG_MAX;
       struct all_addr a;
+
+      if (RCODE(header) == NXDOMAIN)
+	flags |= F_NXDOMAIN;
+      
+      if (val == STAT_SECURE)
+	flags |= F_DNSSECOK;
       
       for (i = ntohs(header->nscount); i != 0; i--)
 	{
-	  if (!(rc = extract_name(header, plen, &p, name, 0, 10)))
+	  if (!(p = skip_name(p, header, plen, 0)))
 	    return STAT_INSECURE;
 	  
 	  GETSHORT(qtype, p); 
@@ -1103,10 +1122,10 @@
 	  GETLONG(ttl, p);
 	  GETSHORT(rdlen, p);
 
-	  if (!CHECK_LEN(header, p, plen, rdlen) || rdlen < 4)
+	  if (!CHECK_LEN(header, p, plen, rdlen))
 	    return STAT_INSECURE; /* bad packet */
-	  
-	  if (qclass != class || qtype != T_SOA || rc ==2)
+	    
+	  if (qclass != class || qtype != T_SOA)
 	    {
 	      p += rdlen;
 	      continue;
@@ -1126,16 +1145,21 @@
 	  GETLONG(ttl, p); /* minTTL */
 	  if (ttl < minttl)
 	    minttl = ttl;
+	  
+	  break;
 	}
       
-      cache_start_insert();
+      if (i != 0)
+	{
+	  cache_start_insert();
+	  
+	  a.addr.dnssec.class = class;
+	  cache_insert(name, &a, now, ttl, flags);
+	  
+	  cache_end_insert(); 
+	}
 
-      a.addr.dnssec.class = class;
-      cache_insert(name, &a, now, ttl, F_FORWARD | F_DS | F_DNSSECOK | F_NEG | (RCODE(header) == NXDOMAIN ? F_NXDOMAIN : 0));
-	
-      cache_end_insert();
-
-      return STAT_INSECURE; 
+      return (val == STAT_SECURE) ? STAT_NO_DS : STAT_INSECURE; 
     }
 
   return val;
@@ -1624,22 +1648,76 @@
     
 /* Validate all the RRsets in the answer and authority sections of the reply (4035:3.2.3) */
 /* Returns are the same as validate_rrset, plus the class if the missing key is in *class */
-int dnssec_validate_reply(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 *neganswer)
 {
-  unsigned char *ans_start, *p1, *p2, **nsecs;
-  int type1, class1, rdlen1, type2, class2, rdlen2;
+  unsigned char *ans_start, *qname, *p1, *p2, **nsecs;
+  int type1, class1, rdlen1, type2, class2, rdlen2, qclass, qtype;
   int i, j, rc, nsec_count, cname_count = 10;
-  int nsec_type = 0;
+  int nsec_type = 0, have_answer = 0;
 
+  if (neganswer)
+    *neganswer = 0;
+  
   if (RCODE(header) == SERVFAIL)
     return STAT_BOGUS;
   
   if ((RCODE(header) != NXDOMAIN && RCODE(header) != NOERROR) || ntohs(header->qdcount) != 1)
     return STAT_INSECURE;
+
+  qname = p1 = (unsigned char *)(header+1);
   
-  if (!(ans_start = skip_questions(header, plen)))
+  if (!extract_name(header, plen, &p1, name, 1, 4))
     return STAT_INSECURE;
+
+  GETSHORT(qtype, p1);
+  GETSHORT(qclass, p1);
+  ans_start = p1;
+ 
+  /* Can't validate an RRISG query */
+  if (qtype == T_RRSIG)
+    return STAT_INSECURE;
+ 
+ cname_loop:
+  for (j = ntohs(header->ancount); j != 0; j--) 
+    {
+      /* leave pointer to missing name in qname */
+           
+      if (!(rc = extract_name(header, plen, &p1, name, 0, 10)))
+	return STAT_INSECURE; /* bad packet */
+      
+      GETSHORT(type2, p1); 
+      GETSHORT(class2, p1);
+      p1 += 4; /* TTL */
+      GETSHORT(rdlen2, p1);
+
+      if (rc == 1 && qclass == class2)
+	{
+	  /* Do we have an answer for the question? */
+	  if (type2 == qtype)
+	    {
+	      have_answer = 1;
+	      break;
+	    }
+	  else if (type2 == T_CNAME)
+	    {
+	      qname = p1;
+	      
+	      /* looped CNAMES */
+	      if (!cname_count-- || !extract_name(header, plen, &p1, name, 1, 0))
+		return STAT_INSECURE;
+	       
+	      p1 = ans_start;
+	      goto cname_loop;
+	    }
+	} 
+
+      if (!ADD_RDLEN(header, p1, plen, rdlen2))
+	return STAT_INSECURE;
+    }
    
+  if (neganswer && !have_answer)
+    *neganswer = 1;
+  
   for (p1 = ans_start, i = 0; i < ntohs(header->ancount) + ntohs(header->nscount); i++)
     {
       if (!extract_name(header, plen, &p1, name, 1, 10))
@@ -1812,70 +1890,98 @@
     }
 
   /* OK, all the RRsets validate, now see if we have a NODATA or NXDOMAIN reply */
-
-  p1 = (unsigned char *)(header+1);
-  
-  if (!extract_name(header, plen, &p1, name, 1, 4))
-    return STAT_INSECURE;
-
-  GETSHORT(type1, p1);
-  GETSHORT(class1, p1);
-
-  /* Can't validate RRSIG query */
-  if (type1 == T_RRSIG)
-    return STAT_INSECURE;
-
- cname_loop:
-  for (j = ntohs(header->ancount); j != 0; j--) 
-    {
-      if (!(rc = extract_name(header, plen, &p1, name, 0, 10)))
-	return STAT_INSECURE; /* bad packet */
-      
-      GETSHORT(type2, p1); 
-      GETSHORT(class2, p1);
-      p1 += 4; /* TTL */
-      GETSHORT(rdlen2, p1);
-
-      if (rc == 1 && class1 == class2)
-	{
-	  /* Do we have an answer for the question? */
-	  if (type1 == type2)
-	    return RCODE(header) == NXDOMAIN ? STAT_BOGUS : STAT_SECURE;
-	  else if (type2 == T_CNAME)
-	    {
-	      /* looped CNAMES */
-	      if (!cname_count-- || 
-		  !extract_name(header, plen, &p1, name, 1, 0) ||
-		  !(p1 = skip_questions(header, plen)))
-		return STAT_INSECURE;
-	      
-	      goto cname_loop;
-	    }
-	} 
-
-      if (!ADD_RDLEN(header, p1, plen, rdlen2))
-	return STAT_INSECURE;
-    }
-
+  if (have_answer)
+    return STAT_SECURE;
+     
   /* NXDOMAIN or NODATA reply, prove that (name, class1, type1) can't exist */
-  
   /* First marshall the NSEC records, if we've not done it previously */
   if (!nsec_type)
     {
-      nsec_type = find_nsec_records(header, plen, &nsecs, &nsec_count, class1);
+      nsec_type = find_nsec_records(header, plen, &nsecs, &nsec_count, qclass);
       
       if (nsec_type == 0)
 	return STAT_INSECURE; /* Bad packet */
       if (nsec_type == -1)
 	return STAT_BOGUS; /* No NSECs */
     }
+   
+  /* Get name of missing answer */
+  if (!extract_name(header, plen, &qname, name, 1, 0))
+    return STAT_INSECURE;
   
   if (nsec_type == T_NSEC)
-    return prove_non_existence_nsec(header, plen, nsecs, nsec_count, daemon->workspacename, keyname, name, type1);
+    return prove_non_existence_nsec(header, plen, nsecs, nsec_count, daemon->workspacename, keyname, name, qtype);
   else
-    return prove_non_existence_nsec3(header, plen, nsecs, nsec_count, daemon->workspacename, keyname, name, type1);
+    return prove_non_existence_nsec3(header, plen, nsecs, nsec_count, daemon->workspacename, keyname, name, qtype);
 }
 
+/* Chase the CNAME chain in the packet until the first record which _doesn't validate.
+   Needed for proving answer in unsigned space.
+   Return STAT_NEED_* 
+          STAT_BOGUS - error
+          STAT_INSECURE - name of first non-secure record in name 
+*/
+int dnssec_chase_cname(time_t now, struct dns_header *header, size_t plen, char *name, char *keyname)
+{
+  unsigned char *p = (unsigned char *)(header+1);
+  int type, class, qtype, qclass, rdlen, j, rc;
+  int cname_count = 10;
+
+  /* Get question */
+  if (!extract_name(header, plen, &p, name, 1, 4))
+    return STAT_BOGUS;
+  
+  GETSHORT(qtype, p);
+  GETSHORT(qclass, p);
+
+  while (1)
+    {
+      for (j = ntohs(header->ancount); j != 0; j--) 
+	{
+	  if (!(rc = extract_name(header, plen, &p, name, 0, 10)))
+	    return STAT_BOGUS; /* bad packet */
+	  
+	  GETSHORT(type, p); 
+	  GETSHORT(class, p);
+	  p += 4; /* TTL */
+	  GETSHORT(rdlen, p);
+
+	  /* Not target, loop */
+	  if (rc == 2 || qclass != class)
+	    {
+	      if (!ADD_RDLEN(header, p, plen, rdlen))
+		return STAT_BOGUS;
+	      continue;
+	    }
+	  
+	  /* Got to end of CNAME chain. */
+	  if (type != T_CNAME)
+	    return STAT_INSECURE;
+	  
+	  /* validate CNAME chain, return if insecure or need more data */
+	  rc = validate_rrset(now, header, plen, class, type, name, keyname, NULL, 0, 0, 0);
+	  if (rc != STAT_SECURE)
+	    {
+	      if (rc == STAT_NO_SIG)
+		rc = STAT_INSECURE;
+	      return rc;
+	    }
+
+	  /* Loop down CNAME chain/ */
+	  if (!cname_count-- || 
+	      !extract_name(header, plen, &p, name, 1, 0) ||
+	      !(p = skip_questions(header, plen)))
+	    return STAT_BOGUS;
+	  
+	  break;
+	}
+
+      /* End of CNAME chain */
+      return STAT_INSECURE;	
+    }
+}
+
+
 /* Compute keytag (checksum to quickly index a key). See RFC4034 */
 int dnskey_keytag(int alg, int flags, unsigned char *key, int keylen)
 {
@@ -1951,7 +2057,8 @@
       if (label_type == 0xc0)
 	{
 	  /* pointer for compression. */
-	  unsigned int offset, i;
+	  unsigned int offset;
+	  int i;
 	  unsigned char *p;
 	  
 	  if (!CHECK_LEN(header, ansp, plen, 2))
@@ -1971,7 +2078,7 @@
 
 	  /* does the pointer end up in an elided RR? */
 	  if (i & 1)
-	    return -1;
+	    return 0;
 
 	  /* No, scale the pointer */
 	  if (fixup)
@@ -2023,27 +2130,26 @@
 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;
+  unsigned char *pp;
   
   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;
-	 }
+      pp = p;
+
+      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)
 	{
+	  /* fixup name of RR */
+	  if (!check_name(&pp, header, plen, fixup, rrs, rr_count))
+	    return 0;
+	  
 	  if (class == C_IN)
 	    {
 	      u16 *d;
diff --git a/src/forward.c b/src/forward.c
index c05efe4..7916716 100644
--- a/src/forward.c
+++ b/src/forward.c
@@ -24,6 +24,14 @@
 static void free_frec(struct frec *f);
 static struct randfd *allocate_rfd(int family);
 
+#ifdef HAVE_DNSSEC
+static int tcp_key_recurse(time_t now, int status, struct dns_header *header, size_t n, 
+			   int class, char *name, char *keyname, struct server *server, int *keycount);
+static int do_check_sign(time_t now, struct dns_header *header, size_t plen, char *name, char *keyname, int class);
+static int send_check_sign(time_t now, struct dns_header *header, size_t plen, char *name, char *keyname);
+#endif
+
+
 /* Send a UDP packet with its source address set as "source" 
    unless nowild is true, when we just send it with the kernel default */
 int send_from(int fd, int nowild, char *packet, size_t len, 
@@ -250,6 +258,8 @@
 #endif
  unsigned int gotname = extract_request(header, plen, daemon->namebuff, NULL);
 
+ (void)do_bit;
+
   /* may be no servers available. */
   if (!daemon->servers)
     forward = NULL;
@@ -522,6 +532,7 @@
   size_t plen; 
 
   (void)ad_reqd;
+  (void) do_bit;
 
 #ifdef HAVE_IPSET
   /* Similar algorithm to search_servers. */
@@ -793,13 +804,27 @@
 	  else if (forward->flags & FREC_DNSKEY_QUERY)
 	    status = dnssec_validate_by_ds(now, header, n, daemon->namebuff, daemon->keyname, forward->class);
 	  else if (forward->flags & FREC_DS_QUERY)
-	    status = dnssec_validate_ds(now, header, n, daemon->namebuff, daemon->keyname, forward->class);
+	    {
+	      status = dnssec_validate_ds(now, header, n, daemon->namebuff, daemon->keyname, forward->class);
+	      if (status == STAT_NO_DS)
+		status = STAT_INSECURE;
+	    }
+	  else if (forward->flags & FREC_CHECK_NOSIGN)
+	    status = do_check_sign(now, header, n, daemon->namebuff, daemon->keyname, forward->class);
 	  else
-	    status = dnssec_validate_reply(now, header, n, daemon->namebuff, daemon->keyname, &forward->class);
-
+	    {
+	      status = dnssec_validate_reply(now, header, n, daemon->namebuff, daemon->keyname, &forward->class, NULL);
+	      if (status == STAT_NO_SIG)
+		{
+		  if (option_bool(OPT_DNSSEC_NO_SIGN))
+		    status = send_check_sign(now, header, n, daemon->namebuff, daemon->keyname);
+		  else
+		    status = STAT_INSECURE;
+		}
+	    }
 	  /* Can't validate, as we're missing key data. Put this
 	     answer aside, whilst we get that. */     
-	  if (status == STAT_NEED_DS || status == STAT_NEED_KEY)
+	  if (status == STAT_NEED_DS || status == STAT_NEED_DS_NEG || status == STAT_NEED_KEY)
 	    {
 	      struct frec *new, *orig;
 	      
@@ -829,7 +854,7 @@
 #ifdef HAVE_IPV6
 		  new->rfd6 = NULL;
 #endif
-		  new->flags &= ~(FREC_DNSKEY_QUERY | FREC_DS_QUERY);
+		  new->flags &= ~(FREC_DNSKEY_QUERY | FREC_DS_QUERY | FREC_CHECK_NOSIGN);
 		  
 		  new->dependent = forward; /* to find query awaiting new one. */
 		  forward->blocking_query = new; /* for garbage cleaning */
@@ -842,7 +867,10 @@
 		    }
 		  else 
 		    {
-		      new->flags |= FREC_DS_QUERY;
+		      if (status == STAT_NEED_DS_NEG)
+			new->flags |= FREC_CHECK_NOSIGN;
+		      else
+			new->flags |= FREC_DS_QUERY;
 		      nn = dnssec_generate_query(header,((char *) header) + daemon->packet_buff_sz,
 						 daemon->keyname, forward->class, T_DS, &server->addr);
 		    }
@@ -906,11 +934,26 @@
 		  if (forward->flags & FREC_DNSKEY_QUERY)
 		    status = dnssec_validate_by_ds(now, header, n, daemon->namebuff, daemon->keyname, forward->class);
 		  else if (forward->flags & FREC_DS_QUERY)
-		    status = dnssec_validate_ds(now, header, n, daemon->namebuff, daemon->keyname, forward->class);
+		    {
+		      status = dnssec_validate_ds(now, header, n, daemon->namebuff, daemon->keyname, forward->class);
+		      if (status == STAT_NO_DS)
+			status = STAT_INSECURE;
+		    }
+		  else if (forward->flags & FREC_CHECK_NOSIGN)
+		    status = do_check_sign(now, header, n, daemon->namebuff, daemon->keyname, forward->class);
 		  else
-		    status = dnssec_validate_reply(now, header, n, daemon->namebuff, daemon->keyname, &forward->class);	
-		  
-		  if (status == STAT_NEED_DS || status == STAT_NEED_KEY)
+		    {
+		      status = dnssec_validate_reply(now, header, n, daemon->namebuff, daemon->keyname, &forward->class, NULL);	
+		      if (status == STAT_NO_SIG)
+			{
+			  if (option_bool(OPT_DNSSEC_NO_SIGN))
+			    status = send_check_sign(now, header, n, daemon->namebuff, daemon->keyname);
+			  else
+			    status = STAT_INSECURE;
+			}
+		    }
+	       
+		  if (status == STAT_NEED_DS || status == STAT_NEED_DS_NEG || status == STAT_NEED_KEY)
 		    goto anotherkey;
 		}
 	    }
@@ -1207,6 +1250,164 @@
 }
 
 #ifdef HAVE_DNSSEC
+
+/* UDP: we've got an unsigned answer, return STAT_INSECURE if we can prove there's no DS
+   and therefore the answer shouldn't be signed, or STAT_BOGUS if it should be, or 
+   STAT_NEED_DS_NEG and keyname if we need to do the query. */
+static int send_check_sign(time_t now, struct dns_header *header, size_t plen, char *name, char *keyname)
+{
+  struct crec *crecp;
+  char *name_start = name;
+  int status = dnssec_chase_cname(now, header, plen, name, keyname);
+  
+  if (status != STAT_INSECURE)
+    return status;
+
+  while (1)
+    {
+      crecp = cache_find_by_name(NULL, name_start, now, F_DS);
+      
+      if (crecp && (crecp->flags & F_DNSSECOK))
+	return (crecp->flags & F_NEG) ? STAT_INSECURE : STAT_BOGUS;
+       
+      if (crecp && (crecp->flags & F_NEG) && (name_start = strchr(name_start, '.')))
+	{
+	  name_start++; /* chop a label off and try again */
+	  continue;
+	}
+
+      strcpy(keyname, name_start);
+      return STAT_NEED_DS_NEG;
+    }
+}
+
+/* Got answer to DS query from send_check_sign, check for proven non-existence, or make the next DS query to try. */
+static int do_check_sign(time_t now, struct dns_header *header, size_t plen, char *name, char *keyname, int class)
+  
+{ 
+  char *name_start;
+  unsigned char *p;
+  int status = dnssec_validate_ds(now, header, plen, name, keyname, class);
+  
+  if (status != STAT_INSECURE)
+    {
+      if (status == STAT_NO_DS)
+	status = STAT_INSECURE;
+      return status;
+    }
+  
+  p = (unsigned char *)(header+1);
+  
+  if (extract_name(header, plen, &p, name, 1, 4) &&
+      (name_start = strchr(name, '.')))
+    {
+      name_start++; /* chop a label off and try again */
+      strcpy(keyname, name_start);
+      return STAT_NEED_DS_NEG;
+    }
+  
+  return STAT_BOGUS;
+}
+
+/* Move toward the root, until we find a signed non-existance of a DS, in which case
+   an unsigned answer is OK, or we find a signed DS, in which case there should be 
+   a signature, and the answer is BOGUS */
+static int  tcp_check_for_unsigned_zone(time_t now, struct dns_header *header, size_t plen, int class, char *name, 
+					char *keyname, struct server *server, int *keycount)
+{
+  size_t m;
+  unsigned char *packet, *payload;
+  u16 *length;
+  unsigned char *p = (unsigned char *)(header+1);
+  int status;
+  char *name_start = name;
+
+  /* Get first insecure entry in CNAME chain */
+  status = tcp_key_recurse(now, STAT_CHASE_CNAME, header, plen, class, name, keyname, server, keycount);
+  if (status == STAT_BOGUS)
+    return STAT_BOGUS;
+  
+  if (!(packet = whine_malloc(65536 + MAXDNAME + RRFIXEDSZ + sizeof(u16))))
+    return STAT_BOGUS;
+  
+  payload = &packet[2];
+  header = (struct dns_header *)payload;
+  length = (u16 *)packet;
+  
+  while (1)
+    {
+      unsigned char *newhash, hash[HASH_SIZE];
+      unsigned char c1, c2;
+      struct crec *crecp = cache_find_by_name(NULL, name_start, now, F_DS);
+ 
+      if (--(*keycount) == 0)
+	return STAT_BOGUS;    
+      
+      if (crecp && (crecp->flags & F_DNSSECOK))
+	{
+	  free(packet);
+	  return (crecp->flags & F_NEG) ? STAT_INSECURE : STAT_BOGUS;
+	}
+      
+      /* If we have cached insecurely that a DS doesn't exist, 
+	 ise that is a hit for where to start looking for the secure one */
+      if (crecp && (crecp->flags & F_NEG) && (name_start = strchr(name_start, '.')))
+	{
+	  name_start++; /* chop a label off and try again */
+	  continue;
+	}
+
+      m = dnssec_generate_query(header, ((char *) header) + 65536, name_start, class, T_DS, &server->addr);
+      
+      /* We rely on the question section coming back unchanged, ensure it is with the hash. */
+      if ((newhash = hash_questions(header, (unsigned int)m, name)))
+	memcpy(hash, newhash, HASH_SIZE);
+      
+      *length = htons(m);
+      
+      if (read_write(server->tcpfd, packet, m + sizeof(u16), 0) &&
+	  read_write(server->tcpfd, &c1, 1, 1) &&
+	  read_write(server->tcpfd, &c2, 1, 1) &&
+	  read_write(server->tcpfd, payload, (c1 << 8) | c2, 1))
+	{
+	  m = (c1 << 8) | c2;
+	  
+	  newhash = hash_questions(header, (unsigned int)m, name);
+	  if (newhash && memcmp(hash, newhash, HASH_SIZE) == 0)
+	    {
+	      /* Note this trashes all three name workspaces */
+	      status = tcp_key_recurse(now, STAT_NEED_DS_NEG, header, m, class, name, keyname, server, keycount);
+	      	   
+	      /* We've found a DS which proves the bit of the DNS where the
+		 original query is, is unsigned, so the answer is OK, 
+		 if unvalidated. */
+	      if (status == STAT_NO_DS)
+		{
+		  free(packet);
+		  return STAT_INSECURE;
+		}
+	      
+	      /* No DS, not got to DNSSEC-land yet, go up. */
+	      if (status == STAT_INSECURE)
+		{
+		  p = (unsigned char *)(header+1);
+		  
+		  if (extract_name(header, plen, &p, name, 1, 4) &&
+		      (name_start = strchr(name, '.')))
+		    {
+		      name_start++; /* chop a label off and try again */
+		      continue;
+		    }
+		}
+	    }
+	}
+      
+      free(packet);
+
+      return STAT_BOGUS;
+    }
+}
+
 static int tcp_key_recurse(time_t now, int status, struct dns_header *header, size_t n, 
 			   int class, char *name, char *keyname, struct server *server, int *keycount)
 {
@@ -1219,11 +1420,27 @@
   
   if (status == STAT_NEED_KEY)
     new_status = dnssec_validate_by_ds(now, header, n, name, keyname, class);
-  else if (status == STAT_NEED_DS)
-    new_status = dnssec_validate_ds(now, header, n, name, keyname, class);
-  else
-    new_status = dnssec_validate_reply(now, header, n, name, keyname, &class);
-  
+  else if (status == STAT_NEED_DS || status == STAT_NEED_DS_NEG)
+    {
+      new_status = dnssec_validate_ds(now, header, n, name, keyname, class);
+      if (status == STAT_NEED_DS  && new_status == STAT_NO_DS)
+	new_status = STAT_INSECURE;
+    }
+  else if (status == STAT_CHASE_CNAME)
+    new_status = dnssec_chase_cname(now, header, n, name, keyname);
+  else 
+    {
+      new_status = dnssec_validate_reply(now, header, n, name, keyname, &class, NULL);
+      
+      if (new_status == STAT_NO_SIG)
+	{
+	  if (option_bool(OPT_DNSSEC_NO_SIGN))
+	    new_status = tcp_check_for_unsigned_zone(now, header, n, class, name, keyname, server, keycount);
+	  else
+	    new_status = STAT_INSECURE;
+	}
+    }
+
   /* Can't validate because we need a key/DS whose name now in keyname.
      Make query for same, and recurse to validate */
   if (new_status == STAT_NEED_DS || new_status == STAT_NEED_KEY)
@@ -1253,7 +1470,9 @@
 	{
 	  m = (c1 << 8) | c2;
 	  
-	  if (tcp_key_recurse(now, new_status, new_header, m, class, name, keyname, server, keycount) == STAT_SECURE)
+	  new_status = tcp_key_recurse(now, new_status, new_header, m, class, name, keyname, server, keycount);
+	  
+	  if (new_status == STAT_SECURE)
 	    {
 	      /* Reached a validated record, now try again at this level.
 		 Note that we may get ANOTHER NEED_* if an answer needs more than one key.
@@ -1261,11 +1480,27 @@
 	      
 	      if (status == STAT_NEED_KEY)
 		new_status = dnssec_validate_by_ds(now, header, n, name, keyname, class);
-	      else if (status == STAT_NEED_DS)
-		new_status = dnssec_validate_ds(now, header, n, name, keyname, class);
-	      else
-		new_status = dnssec_validate_reply(now, header, n, name, keyname, &class);
-
+	      else if (status == STAT_NEED_DS || status == STAT_NEED_DS_NEG)
+		{
+		  new_status = dnssec_validate_ds(now, header, n, name, keyname, class);
+		  if (status == STAT_NEED_DS && new_status == STAT_NO_DS)
+		    new_status = STAT_INSECURE; /* Validated no DS */
+		}
+	      else if (status == STAT_CHASE_CNAME)
+		new_status = dnssec_chase_cname(now, header, n, name, keyname);
+	      else 
+		{
+		  new_status = dnssec_validate_reply(now, header, n, name, keyname, &class, NULL);
+		  
+		  if (new_status == STAT_NO_SIG)
+		    {
+		      if (option_bool(OPT_DNSSEC_NO_SIGN))
+			new_status = tcp_check_for_unsigned_zone(now, header, n, class, name, keyname, server, keycount);
+		      else
+			new_status = STAT_INSECURE;
+		    }
+		}
+	      
 	      if (new_status == STAT_NEED_DS || new_status == STAT_NEED_KEY)
 		goto another_tcp_key;
 	    }
@@ -1273,7 +1508,6 @@
 
       free(packet);
     }
-  
   return new_status;
 }
 #endif
diff --git a/src/option.c b/src/option.c
index 2d860d5..b898231 100644
--- a/src/option.c
+++ b/src/option.c
@@ -143,6 +143,7 @@
 #define LOPT_DNSSEC_DEBUG 331
 #define LOPT_REV_SERV     332
 #define LOPT_SERVERS_FILE 333
+#define LOPT_DNSSEC_CHECK 334
 
 #ifdef HAVE_GETOPT_LONG
 static const struct option opts[] =  
@@ -283,6 +284,7 @@
     { "dnssec", 0, 0, LOPT_SEC_VALID },
     { "trust-anchor", 1, 0, LOPT_TRUST_ANCHOR },
     { "dnssec-debug", 0, 0, LOPT_DNSSEC_DEBUG },
+    { "dnssec-check-unsigned", 0, 0, LOPT_DNSSEC_CHECK },
 #ifdef OPTION6_PREFIX_CLASS 
     { "dhcp-prefix-class", 1, 0, LOPT_PREF_CLSS },
 #endif
@@ -438,6 +440,7 @@
   { LOPT_SEC_VALID, OPT_DNSSEC_VALID, NULL, gettext_noop("Activate DNSSEC validation"), NULL },
   { LOPT_TRUST_ANCHOR, ARG_DUP, "<domain>,[<class>],...", gettext_noop("Specify trust anchor key digest."), NULL },
   { LOPT_DNSSEC_DEBUG, OPT_DNSSEC_DEBUG, NULL, gettext_noop("Disable upstream checking for DNSSEC debugging."), NULL },
+  { LOPT_DNSSEC_CHECK, OPT_DNSSEC_NO_SIGN, NULL, gettext_noop("Ensure answers without DNSSEC are in unsigned zones."), NULL },
 #ifdef OPTION6_PREFIX_CLASS 
   { LOPT_PREF_CLSS, ARG_DUP, "set:tag,<class>", gettext_noop("Specify DHCPv6 prefix class"), NULL },
 #endif
diff --git a/src/rfc1035.c b/src/rfc1035.c
index 77156e4..2fd43e2 100644
--- a/src/rfc1035.c
+++ b/src/rfc1035.c
@@ -927,7 +927,7 @@
   
   for (i = ntohs(header->qdcount); i != 0; i--)
     {
-      int found = 0, cname_count = 5;
+      int found = 0, cname_count = 10;
       struct crec *cpp = NULL;
       int flags = RCODE(header) == NXDOMAIN ? F_NXDOMAIN : 0;
       int secflag = secure ?  F_DNSSECOK : 0;