Negative caching for DS records.
diff --git a/src/cache.c b/src/cache.c
index dd393c4..3ebb49d 100644
--- a/src/cache.c
+++ b/src/cache.c
@@ -564,7 +564,14 @@
     *cache_get_name(new) = 0;
 
   if (addr)
-    new->addr.addr = *addr;
+    {
+#ifdef HAVE_DNSSEC
+      if (flags & (F_DS | F_DNSKEY))
+	new->uid = addr->addr.dnssec.class;
+      else
+#endif
+	new->addr.addr = *addr;	
+    }
 
   new->ttd = now + (time_t)ttl;
   new->next = new_chain;
@@ -1304,25 +1311,16 @@
 	    else if (cache->flags & F_DS)
 	      {
 		if (cache->flags & F_DNSKEY)
-		  {
-		    /* RRSIG */
-		    a = daemon->addrbuff;
-		    sprintf(a, "%5u %3u %s", cache->addr.sig.keytag,
-			    cache->addr.sig.algo, querystr("", cache->addr.sig.type_covered));
-		  }
-		else
-		  {
-		    a = daemon->addrbuff;
-		    sprintf(a, "%5u %3u %3u", cache->addr.ds.keytag,
-			    cache->addr.ds.algo, cache->addr.ds.digest);
-		  }
+		  /* RRSIG */
+		  sprintf(a, "%5u %3u %s", cache->addr.sig.keytag,
+			  cache->addr.sig.algo, querystr("", cache->addr.sig.type_covered));
+		else if (!(cache->flags & F_NEG))
+		  sprintf(a, "%5u %3u %3u", cache->addr.ds.keytag,
+			  cache->addr.ds.algo, cache->addr.ds.digest);
 	      }
 	    else if (cache->flags & F_DNSKEY)
-	      {
-		a = daemon->addrbuff;
-		sprintf(a, "%5u %3u %3u", cache->addr.key.keytag,
-			cache->addr.key.algo, cache->addr.key.flags);
-	      }
+	      sprintf(a, "%5u %3u %3u", cache->addr.key.keytag,
+		      cache->addr.key.algo, cache->addr.key.flags);
 #endif
 	    else if (!(cache->flags & F_NEG) || !(cache->flags & F_FORWARD))
 	      { 
diff --git a/src/dnssec.c b/src/dnssec.c
index 8a99a26..1a1c0e4 100644
--- a/src/dnssec.c
+++ b/src/dnssec.c
@@ -855,13 +855,17 @@
   if (qtype != T_DNSKEY || qclass != class || ntohs(header->ancount) == 0)
     return STAT_BOGUS;
 
-   /* See if we have cached a DS record which validates this key */
+  /* See if we have cached a DS record which validates this key */
   if (!(crecp = cache_find_by_name(NULL, name, now, F_DS)))
     {
       strcpy(keyname, name);
       return STAT_NEED_DS;
     }
-
+  
+  /* If we've cached that DS provably doesn't exist, result must be INSECURE */
+  if (crecp->flags & F_NEG)
+    return STAT_INSECURE;
+  
   /* NOTE, we need to find ONE DNSKEY which matches the DS */
   for (valid = 0, j = ntohs(header->ancount); j != 0 && !valid; j--) 
     {
@@ -998,7 +1002,6 @@
 			  recp1->addr.key.algo = algo;
 			  recp1->addr.key.keytag = keytag;
 			  recp1->addr.key.flags = flags;
-			  recp1->uid = class;
 			}
 		    }
 		}
@@ -1024,7 +1027,6 @@
 			    blockdata_free(key);
 			  else
 			    {
-			      crecp->uid = class;
 			      crecp->addr.sig.keydata = key;
 			      crecp->addr.sig.keylen = rdlen;
 			      crecp->addr.sig.keytag = keytag;
@@ -1054,7 +1056,7 @@
 /* The DNS packet is expected to contain the answer to a DS query
    Put all DSs in the answer which are valid into the cache.
    return codes:
-   STAT_INSECURE    bad packet, no DS in reply.
+   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_BOGUS       At least one DS found, which fails validation.
    STAT_NEED_DNSKEY DNSKEY records to validate a DS not found, name in keyname
@@ -1063,10 +1065,10 @@
 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;
+  int qtype, qclass, val, i;
 
   if (ntohs(header->qdcount) != 1 ||
-      !extract_name(header, plen, &p, name, 1, 4))
+      !(p = skip_name(p, header, plen, 4)))
     return STAT_INSECURE;
   
   GETSHORT(qtype, p);
@@ -1077,17 +1079,65 @@
   else
     val = dnssec_validate_reply(now, header, plen, name, keyname, NULL);
   
+  p = (unsigned char *)(header+1);
+  extract_name(header, plen, &p, name, 1, 4);
+  p += 4; /* qtype, qclass */
+  
   if (val == STAT_BOGUS)
-    {
-      p = (unsigned char *)(header+1);
-      extract_name(header, plen, &p, name, 1, 4);
-      log_query(F_UPSTREAM, name, NULL, "BOGUS DS");
-    }
+    log_query(F_UPSTREAM, name, NULL, "BOGUS DS");
   
-  /* proved that no DS exists, can't validate */
+  /* proved that no DS exists, cache neg answer, can't validate */
   if (val == STAT_SECURE && ntohs(header->ancount) == 0)
-    return STAT_INSECURE;
-  
+    {
+      int  rdlen, rc;
+      unsigned long ttl, minttl = ULONG_MAX;
+      struct all_addr a;
+      
+      for (i = ntohs(header->nscount); i != 0; i--)
+	{
+	  if (!(rc = extract_name(header, plen, &p, name, 0, 10)))
+	    return STAT_INSECURE;
+	  
+	  GETSHORT(qtype, p); 
+	  GETSHORT(qclass, p);
+	  GETLONG(ttl, p);
+	  GETSHORT(rdlen, p);
+
+	  if (!CHECK_LEN(header, p, plen, rdlen) || rdlen < 4)
+	    return STAT_INSECURE; /* bad packet */
+	  
+	  if (qclass != class || qtype != T_SOA || rc ==2)
+	    {
+	      p += rdlen;
+	      continue;
+	    }
+           
+	  if (ttl < minttl)
+	    minttl = ttl;
+	  
+	  /* MNAME */
+	  if (!(p = skip_name(p, header, plen, 0)))
+	    return STAT_INSECURE;
+	  /* RNAME */
+	  if (!(p = skip_name(p, header, plen, 20)))
+	    return STAT_INSECURE;
+	  p += 16; /* SERIAL REFRESH RETRY EXPIRE */
+	  
+	  GETLONG(ttl, p); /* minTTL */
+	  if (ttl < minttl)
+	    minttl = ttl;
+	}
+      
+      cache_start_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;
 }
 
@@ -1707,7 +1757,6 @@
 				  crecp->addr.ds.keydata = key;
 				  crecp->addr.ds.algo = algo;
 				  crecp->addr.ds.keytag = keytag;
-				  crecp->uid = class2;
 				  crecp->addr.ds.keylen = rdlen2 - 4; 
 				} 
 			    }
@@ -1737,7 +1786,6 @@
 				    blockdata_free(key);
 				  else
 				    {
-				      crecp->uid = class1;
 				      crecp->addr.sig.keydata = key;
 				      crecp->addr.sig.keylen = rdlen2;
 				      crecp->addr.sig.keytag = keytag;
diff --git a/src/rfc1035.c b/src/rfc1035.c
index 5693ef9..77156e4 100644
--- a/src/rfc1035.c
+++ b/src/rfc1035.c
@@ -1580,19 +1580,29 @@
 		  while ((crecp = cache_find_by_name(crecp, name, now, F_DS)))
 		    if (crecp->uid == qclass)
 		      {
-			gotone = 1;
-			if (!dryrun && (keydata = blockdata_retrieve(crecp->addr.ds.keydata, crecp->addr.ds.keylen, NULL)))
-			  {			     			      
-			    struct all_addr a;
-			    a.addr.keytag =  crecp->addr.ds.keytag;
-			    log_query(F_KEYTAG | (crecp->flags & F_CONFIG), name, &a, "DS keytag %u");
-			    if (add_resource_record(header, limit, &trunc, nameoffset, &ansp, 
-						    crec_ttl(crecp, now), &nameoffset,
-						    T_DS, qclass, "sbbt", 
-						    crecp->addr.ds.keytag, crecp->addr.ds.algo, crecp->addr.ds.digest, crecp->addr.ds.keylen, keydata))
-			      anscount++;
-			    
-			  } 
+			gotone = 1; 
+			if (!dryrun)
+			  {
+			    if (crecp->flags & F_NEG)
+			      {
+				if (crecp->flags & F_NXDOMAIN)
+				  nxdomain = 1;
+				log_query(F_UPSTREAM, name, NULL, "secure no DS");	
+			      }
+			    else if ((keydata = blockdata_retrieve(crecp->addr.ds.keydata, crecp->addr.ds.keylen, NULL)))
+			      {			     			      
+				struct all_addr a;
+				a.addr.keytag =  crecp->addr.ds.keytag;
+				log_query(F_KEYTAG | (crecp->flags & F_CONFIG), name, &a, "DS keytag %u");
+				if (add_resource_record(header, limit, &trunc, nameoffset, &ansp, 
+							crec_ttl(crecp, now), &nameoffset,
+							T_DS, qclass, "sbbt", 
+							crecp->addr.ds.keytag, crecp->addr.ds.algo, 
+							crecp->addr.ds.digest, crecp->addr.ds.keylen, keydata))
+				  anscount++;
+				
+			      } 
+			  }
 		      }
 		}
 	      else /* DNSKEY */