Caching of DNSSEC records.
diff --git a/src/cache.c b/src/cache.c
index fbdcae7..126d259 100644
--- a/src/cache.c
+++ b/src/cache.c
@@ -316,27 +316,71 @@
   if (flags & F_FORWARD)
     {
       for (up = hash_bucket(name), crecp = *up; crecp; crecp = crecp->hash_next)
-	if (is_expired(now, crecp) || is_outdated_cname_pointer(crecp))
-	  { 
-	    *up = crecp->hash_next;
-	    if (!(crecp->flags & (F_HOSTS | F_DHCP | F_CONFIG)))
-	      {
-		cache_unlink(crecp);
-		cache_free(crecp);
-	      }
-	  } 
-	else if ((crecp->flags & F_FORWARD) && 
-		 ((flags & crecp->flags & F_TYPE) || ((crecp->flags | flags) & F_CNAME)) &&
-		 hostname_isequal(cache_get_name(crecp), name))
-	  {
-	    if (crecp->flags & (F_HOSTS | F_DHCP | F_CONFIG))
-	      return 0;
-	    *up = crecp->hash_next;
-	    cache_unlink(crecp);
-	    cache_free(crecp);
-	  }
-	else
+	{
+	  if (is_expired(now, crecp) || is_outdated_cname_pointer(crecp))
+	    { 
+	      *up = crecp->hash_next;
+	      if (!(crecp->flags & (F_HOSTS | F_DHCP | F_CONFIG)))
+		{
+		  cache_unlink(crecp);
+		  cache_free(crecp);
+		}
+	      continue;
+	    } 
+	
+	  if ((crecp->flags & F_FORWARD) && hostname_isequal(cache_get_name(crecp), name))
+	    {
+	      
+	      if ((flags & crecp->flags & (F_IPV4 | F_IPV6)) || ((crecp->flags | flags) & F_CNAME))
+		{
+		  if (crecp->flags & (F_HOSTS | F_DHCP | F_CONFIG))
+		    return 0;
+		  *up = crecp->hash_next;
+		  cache_unlink(crecp);
+		  cache_free(crecp);
+		  continue;
+		}
+	      
+#ifdef HAVE_DNSSEC
+	      /* Deletion has to be class-sensitive for DS, DNSKEY, RRSIG, also 
+		 type-covered sensitive for  RRSIG */
+	      if (flags & crecp->flags & (F_DNSKEY | F_DS))
+		{
+		  int del = 0;
+		  switch (flags & (F_DS | F_DNSKEY))
+		    {
+		    case F_DS:
+		      if (crecp->addr.ds.class == addr->addr.dnssec.class)
+			del = 1;
+		      break;
+		      
+		    case F_DNSKEY:
+		      if (crecp->addr.key.class == addr->addr.dnssec.class)
+			del = 1;
+		      break;
+
+		      /* Both set -> RRSIG */
+		    case F_DS | F_DNSKEY:
+		      if (crecp->addr.sig.class == addr->addr.dnssec.class &&
+			  crecp->addr.sig.type_covered == addr->addr.dnssec.type)
+			del = 1;
+		      break;
+		    }
+
+		  if (del)
+		    {
+		      if (crecp->flags & F_CONFIG)
+			return 0;
+		      *up = crecp->hash_next;
+		      cache_unlink(crecp);
+		      cache_free(crecp);
+		      continue;
+		    }
+		}
+#endif
+	    }
 	  up = &crecp->hash_next;
+	}
     }
   else
     {
@@ -409,8 +453,8 @@
   if (daemon->max_cache_ttl != 0 && daemon->max_cache_ttl < ttl)
     ttl = daemon->max_cache_ttl;
 
-  /* Don't log keys */
-  if (flags & (F_IPV4 | F_IPV6))
+  /* Don't log keys here, done elsewhere */
+  if (flags & (F_IPV4 | F_IPV6 | F_CNAME))
     log_query(flags | F_UPSTREAM, name, addr, NULL);
 
   /* if previous insertion failed give up now. */
@@ -554,6 +598,9 @@
 	  if (!is_expired(now, crecp) && !is_outdated_cname_pointer(crecp))
 	    {
 	      if ((crecp->flags & F_FORWARD) && 
+#ifdef HAVE_DNSSEC
+		  ((crecp->flags & (F_DNSKEY | F_DS)) == (prot & (F_DNSKEY | F_DS))) &&
+#endif
 		  (crecp->flags & prot) &&
 		  hostname_isequal(cache_get_name(crecp), name))
 		{
@@ -611,7 +658,10 @@
 
   if (ans && 
       (ans->flags & F_FORWARD) &&
-      (ans->flags & prot) &&
+#ifdef HAVE_DNSSEC
+      ((ans->flags & (F_DNSKEY | F_DS)) == (prot & (F_DNSKEY | F_DS))) &&
+#endif
+      (ans->flags & prot) &&     
       hostname_isequal(cache_get_name(ans), name))
     return ans;
   
@@ -971,7 +1021,9 @@
 	cache->name.namep = key->name;
 	cache->uid = key->keylen;
 	cache->addr.key.algo = key->algo;
+	cache->addr.key.flags = key->flags;
 	cache->addr.key.keytag = dnskey_keytag(key->algo, key->flags, (unsigned char *)key->key, key->keylen);
+	cache->addr.key.class = C_IN; /* TODO - in option? */
 	cache_hash(cache);
       }
 #endif
@@ -1228,16 +1280,17 @@
 	    if ((cache->flags & F_CNAME) && !is_outdated_cname_pointer(cache))
 	      a = cache_get_cname_target(cache);
 #ifdef HAVE_DNSSEC
-	    else if (cache->flags & F_DNSKEY)
-	      {
-		a = daemon->addrbuff;
-		sprintf(a, "%3u %u", cache->addr.key.algo, cache->uid);
-	      }
 	    else if (cache->flags & F_DS)
 	      {
 		a = daemon->addrbuff;
+		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.digest);
+			cache->addr.key.algo, cache->addr.key.flags);
 	      }
 #endif
 	    else if (!(cache->flags & F_NEG) || !(cache->flags & F_FORWARD))
diff --git a/src/dns-protocol.h b/src/dns-protocol.h
index 2149e72..6507642 100644
--- a/src/dns-protocol.h
+++ b/src/dns-protocol.h
@@ -76,7 +76,6 @@
 #define EDNS0_OPTION_MAC            65001 /* dyndns.org temporary assignment */
 #define EDNS0_OPTION_CLIENT_SUBNET  8     /* IANA */
 
-
 struct dns_header {
   u16 id;
   u8  hb3,hb4;
diff --git a/src/dnsmasq.h b/src/dnsmasq.h
index 143e500..b60e639 100644
--- a/src/dnsmasq.h
+++ b/src/dnsmasq.h
@@ -244,7 +244,12 @@
 #ifdef HAVE_IPV6
     struct in6_addr addr6;
 #endif
+    /* for log_query */
     unsigned int keytag;
+    /* for cache_insert if RRSIG, DNSKEY, DS */
+    struct {
+      unsigned short class, type;
+    } dnssec;      
   } addr;
 };
 
@@ -363,13 +368,22 @@
     } cname;
     struct {
       struct blockdata *keydata;
+      unsigned short class, flags, keytag;
       unsigned char algo;
-      unsigned char digest; /* DS only */
-      unsigned short keytag;
-    } key;
+    } key; 
+    struct {
+      struct blockdata *keydata;
+      unsigned short class, keytag;
+      unsigned char algo;
+      unsigned char digest; 
+    } ds; 
+    struct {
+      struct blockdata *keydata;
+      unsigned short class, type_covered, keytag;
+    } sig;
   } addr;
   time_t ttd; /* time to die */
-  /* used as keylen ifF_DNSKEY, index to source for F_HOSTS */
+  /* used as keylen if F_DNSKEY or F_DS, index to source for F_HOSTS */
   int uid; 
   unsigned short flags;
   union {
@@ -409,7 +423,7 @@
 #define F_SECSTAT   (1u<<24)
 
 /* composites */
-#define F_TYPE      (F_IPV4 | F_IPV6 | F_DNSKEY | F_DS) /* Only one may be set */
+#define F_TYPE      (F_IPV4 | F_IPV6 | F_DNSKEY | F_DS) /* F_DS & F_DNSKEY -> RRSIG yuck. */
 
 
 
diff --git a/src/dnssec.c b/src/dnssec.c
index 0a80c7b..a80df99 100644
--- a/src/dnssec.c
+++ b/src/dnssec.c
@@ -707,7 +707,9 @@
 	{
 	  /* iterate through all possible keys 4035 5.3.1 */
 	  for (; crecp; crecp = cache_find_by_name(crecp, keyname, now, F_DNSKEY))
-	    if (crecp->addr.key.algo == algo && crecp->addr.key.keytag == key_tag &&
+	    if (crecp->addr.key.algo == algo && 
+		crecp->addr.key.keytag == key_tag &&
+		crecp->addr.key.class == class &&
 		verify(crecp->addr.key.keydata, crecp->uid, sig, sig_len, digest, algo))
 	      return STAT_SECURE;
 	}
@@ -732,6 +734,7 @@
   struct crec *crecp, *recp1;
   int rc, j, qtype, qclass, ttl, rdlen, flags, algo, valid, keytag;
   struct blockdata *key;
+  struct all_addr a;
 
   if (ntohs(header->qdcount) != 1 ||
       !extract_name(header, plen, &p, name, 1, 4))
@@ -786,23 +789,34 @@
       algo = *p++;
       keytag = dnskey_keytag(algo, flags, p, rdlen - 4);
       
+      /* Cache needs to known class for DNSSEC stuff */
+      a.addr.dnssec.class = class;
+      
       /* Put the key into the cache. Note that if the validation fails, we won't
 	 call cache_end_insert() and this will never be committed. */
       if ((key = blockdata_alloc((char*)p, rdlen - 4)) &&
-	  (recp1 = cache_insert(name, NULL, now, ttl, F_FORWARD | F_DNSKEY)))
+	  (recp1 = cache_insert(name, &a, now, ttl, F_FORWARD | F_DNSKEY | F_DNSSECOK)))
 	{
+	  struct all_addr a;
+	  
+	  a.addr.keytag = keytag;
+	  log_query(F_KEYTAG | F_UPSTREAM, name, &a, "DNSKEY keytag %u");
+	  
 	  recp1->uid = rdlen - 4;
 	  recp1->addr.key.keydata = key;
 	  recp1->addr.key.algo = algo;
 	  recp1->addr.key.keytag = keytag;
+	  recp1->addr.key.flags = flags;
+	  recp1->addr.key.class = class;
 	}
       
       p = psave;
       if (!ADD_RDLEN(header, p, plen, rdlen))
 	return STAT_INSECURE; /* bad packet */
       
-      /* Already determined that message is OK. Just loop stuffing cache */ 
-      if (valid || !key)
+      /* Already determined that message is OK or failed to store or ineligable
+	 (ie no zone key flag) key. Don't attempt to validate, just loop stuffing cache */ 
+      if (valid || !key || !(flags & 0x100))
 	continue;
       
       for (recp1 = crecp; recp1; recp1 = cache_find_by_name(recp1, name, now, F_DS))
@@ -811,10 +825,10 @@
 	  unsigned char *digest, *ds_digest;
 	  const struct nettle_hash *hash;
 	  
-	  if (recp1->addr.key.algo == algo && 
-	      recp1->addr.key.keytag == keytag &&
-	      (flags & 0x100) && /* zone key flag */
-	      (hash = hash_find(ds_digest_name(recp1->addr.key.digest))) &&
+	  if (recp1->addr.ds.algo == algo && 
+	      recp1->addr.ds.keytag == keytag &&
+	      recp1->addr.ds.class == class &&
+	      (hash = hash_find(ds_digest_name(recp1->addr.ds.digest))) &&
 	      hash_init(hash, &ctx, &digest))
 	    
 	    {
@@ -833,10 +847,7 @@
 		  memcmp(ds_digest, digest, recp1->uid) == 0 &&
 		  validate_rrset(now, header, plen, class, T_DNSKEY, name, keyname, key, rdlen - 4, algo, keytag))
 		{
-		  struct all_addr a;
 		  valid = 1;
-		  a.addr.keytag = keytag;
-		  log_query(F_KEYTAG | F_UPSTREAM, name, &a, "DNSKEY keytag %u");
 		  break;
 		}
 	    }
@@ -866,10 +877,8 @@
 
 int dnssec_validate_ds(time_t now, struct dns_header *header, size_t plen, char *name, char *keyname, int class)
 {
-  unsigned char *psave, *p = (unsigned char *)(header+1);
-  struct crec *crecp;
-  int qtype, qclass, val, j;
-  struct blockdata *key;
+  unsigned char *p = (unsigned char *)(header+1);
+  int qtype, qclass, val;
 
   if (ntohs(header->qdcount) != 1 ||
       !extract_name(header, plen, &p, name, 1, 4))
@@ -884,68 +893,12 @@
   if (qtype != T_DS || qclass != class || ntohs(header->ancount) == 0)
     return STAT_INSECURE;
   
-  val = validate_rrset(now, header, plen, class, T_DS, name, keyname, NULL, 0, 0, 0);
- 
+  val = dnssec_validate_reply(now, header, plen, name, keyname, NULL);
+  
   if (val == STAT_BOGUS)
     log_query(F_UPSTREAM, name, NULL, "BOGUS DS");
 
-  /* failed to validate or missing key. */
-  if (val != STAT_SECURE)
-    return val;
-  
-  cache_start_insert();
-
-  for (j = ntohs(header->ancount); j != 0; j--) 
-    {
-      int ttl, rdlen, rc, algo, digest, keytag;
-      
-      /* Ensure we have type, class  TTL and length */
-      if (!(rc = extract_name(header, plen, &p, name, 0, 10)))
-	return STAT_INSECURE; /* bad packet */
-      
-      GETSHORT(qtype, p); 
-      GETSHORT(qclass, p);
-      GETLONG(ttl, p);
-      GETSHORT(rdlen, p);
-      
-      /* check type, class and name, skip if not in DS rrset */
-      if (qclass == class && qtype == T_DS && rc == 1)
-	{
-	  if (!CHECK_LEN(header, p, plen, rdlen) || rdlen < 4)
-	    return STAT_INSECURE; /* bad packet */
-	  
-	  psave = p;
-	  GETSHORT(keytag, p);
-	  algo = *p++;
-	  digest = *p++;
-	  
-	  /* We've proved that the DS is OK, store it in the cache */
-	  if ((key = blockdata_alloc((char*)p, rdlen - 4)) &&
-	      (crecp = cache_insert(name, NULL, now, ttl, F_FORWARD | F_DS)))
-	    {
-	      struct all_addr a;
-	      a.addr.keytag = keytag;
-	      log_query(F_KEYTAG | F_UPSTREAM, name, &a, "DS keytag %u");
-	      crecp->addr.key.digest = digest;
-	      crecp->addr.key.keydata = key;
-	      crecp->addr.key.algo = algo;
-	      crecp->addr.key.keytag = keytag;
-	      crecp->uid = rdlen - 4; 
-	    }
-	  else
-	    return STAT_INSECURE; /* cache problem */
-	  
-	  p = psave;
-	}
-
-      if (!ADD_RDLEN(header, p, plen, rdlen))
-	return STAT_INSECURE; /* bad packet */
-     
-    }
-  
-  cache_end_insert();  
-  
-  return STAT_SECURE;
+  return val;
 }
 
 /* 4034 6.1 */
@@ -1014,6 +967,7 @@
 
 
 /* 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)
 {
   unsigned char *ans_start, *p1, *p2;
@@ -1058,10 +1012,68 @@
 	    }
 	  
 	  /* Not done, validate now */
-	  if (j == i && (rc = validate_rrset(now, header, plen, class1, type1, name, keyname, NULL, 0, 0, 0)) != STAT_SECURE)
+	  if (j == i)
 	    {
-	      *class = class1; /* Class for DS or DNSKEY */
-	      return rc;
+	      if ((rc = validate_rrset(now, header, plen, class1, type1, name, keyname, NULL, 0, 0, 0)) != STAT_SECURE)
+		{
+		  if (class)
+		    *class = class1; /* Class for DS or DNSKEY */
+		  return rc;
+		}
+
+	      /* If we just validated a DS RRset, cache it */
+	      if (type1 == T_DS)
+		{
+		  int ttl, keytag, algo, digest;
+		  unsigned char *psave;
+		  struct all_addr a;
+		  struct blockdata *key;
+		  struct crec *crecp;
+		  
+		  cache_start_insert();
+		  
+		  for (p2 = ans_start, j = 0; j < ntohs(header->ancount) + ntohs(header->nscount); j++)
+		    {
+		      if (!(rc = extract_name(header, plen, &p2, name, 0, 10)))
+			return STAT_INSECURE; /* bad packet */
+		      
+		      GETSHORT(type2, p2);
+		      GETSHORT(class2, p2);
+		      GETLONG(ttl, p2);
+		      GETSHORT(rdlen2, p2);
+		      
+		      if (type2 == T_DS && class2 == class1 && rc == 1)
+			{
+			  psave = p2;
+			  GETSHORT(keytag, p2);
+			  algo = *p2++;
+			  digest = *p2++;
+			  
+			  /* Cache needs to known class for DNSSEC stuff */
+			  a.addr.dnssec.class = class2;
+			  
+			  if ((key = blockdata_alloc((char*)p2, rdlen2 - 4)) &&
+			      (crecp = cache_insert(name, &a, now, ttl, F_FORWARD | F_DS | F_DNSSECOK)))
+			    {
+			      a.addr.keytag = keytag;
+			      log_query(F_KEYTAG | F_UPSTREAM, name, &a, "DS keytag %u");
+			      crecp->addr.ds.digest = digest;
+			      crecp->addr.ds.keydata = key;
+			      crecp->addr.ds.algo = algo;
+			      crecp->addr.ds.keytag = keytag;
+			      crecp->addr.ds.class = class2;
+			      crecp->uid = rdlen2 - 4; 
+			    } 
+			  
+			  p2 = psave;
+			}
+		      
+		      if (!ADD_RDLEN(header, p2, plen, rdlen2))
+			return STAT_INSECURE; /* bad packet */
+		    }
+		  
+		  cache_end_insert();
+		}
 	    }
 	}
 
@@ -1079,6 +1091,10 @@
   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--) 
     {
diff --git a/src/rfc1035.c b/src/rfc1035.c
index 6827544..302fadd 100644
--- a/src/rfc1035.c
+++ b/src/rfc1035.c
@@ -1368,6 +1368,11 @@
 	p += INADDRSZ;
 	break;
 	
+      case 'b':
+	usval = va_arg(ap, int);
+	*p++ = usval;
+	break;
+	
       case 's':
 	usval = va_arg(ap, int);
 	PUTSHORT(usval, p);
@@ -1538,6 +1543,58 @@
 	    }
 	}
 
+#ifdef HAVE_DNSSEC
+      if ((qtype == T_DNSKEY || qtype == T_ANY) && (crecp = cache_find_by_name(NULL, name, now, F_DNSKEY)))
+	{
+	  do {
+	    char *keydata;
+	    
+	    if (crecp->addr.ds.class == qclass &&
+		(qtype == T_DNSKEY || (crecp->flags & F_CONFIG)) &&
+		(keydata = blockdata_retrieve(crecp->addr.key.keydata, crecp->uid, NULL)))
+	      {
+		ans = 1;
+		if (!dryrun)
+		  {
+		    struct all_addr a;
+		    a.addr.keytag =  crecp->addr.key.keytag;
+		    log_query(F_KEYTAG | (crecp->flags & F_CONFIG), name, &a, "DNSKEY keytag %u");
+		    if (add_resource_record(header, limit, &trunc, nameoffset, &ansp, 
+					    crec_ttl(crecp, now), &nameoffset,
+					    T_DNSKEY, qclass, "sbbt", 
+					    crecp->addr.key.flags, 3, crecp->addr.key.algo, crecp->uid, keydata))
+		      anscount++;
+		  }
+	      }
+	  } while (crecp = cache_find_by_name(crecp, name, now, F_DNSKEY));
+	}
+
+      if ((qtype == T_DS || qtype == T_ANY) && (crecp = cache_find_by_name(NULL, name, now, F_DS)))
+	{
+	  do {
+	    char *keydata;
+	    
+	    if (crecp->addr.ds.class == qclass &&
+		(qtype == T_DS || (crecp->flags & F_CONFIG)) &&
+		(keydata = blockdata_retrieve(crecp->addr.ds.keydata, crecp->uid, NULL)))
+	      {
+		ans = 1;
+		if (!dryrun)
+		  {
+		    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->uid, keydata))
+		      anscount++;
+		  }
+	      }
+	  } while (crecp = cache_find_by_name(crecp, name, now, F_DS));
+	}
+#endif	     
+      
       if (qclass == C_IN)
 	{
 	  struct txt_record *t;
@@ -1901,7 +1958,7 @@
 		    }
 		}
 	    }
-	  
+
 	  if (qtype == T_MX || qtype == T_ANY)
 	    {
 	      int found = 0;