]> git.ipfire.org Git - people/ms/dnsmasq.git/commitdiff
First functional DNSSEC - highly alpha.
authorSimon Kelley <simon@thekelleys.org.uk>
Wed, 8 Jan 2014 10:26:58 +0000 (10:26 +0000)
committerSimon Kelley <simon@thekelleys.org.uk>
Wed, 8 Jan 2014 10:26:58 +0000 (10:26 +0000)
src/cache.c
src/config.h
src/dnsmasq.h
src/dnssec-crypto.h
src/dnssec-openssl.c
src/dnssec.c
src/forward.c
src/option.c
src/rfc1035.c
src/util.c

index cfbeae3fb38a78f50dcc254a61e5ba36fb8096b5..ee27e4e4ef57698f04e1813c4c004fbb9687675f 100644 (file)
@@ -56,6 +56,8 @@ static const struct {
   { 38,  "A6" },
   { 39,  "DNAME" },
   { 41,  "OPT" },
+  { 43,  "DS" },
+  { 46,  "RRSIG" },
   { 48,  "DNSKEY" },
   { 249, "TKEY" },
   { 250, "TSIG" },
@@ -916,12 +918,19 @@ void cache_reload(void)
   struct name_list *nl;
   struct cname *a;
   struct interface_name *intr;
+#ifdef HAVE_DNSSEC
+  struct dnskey *key;
+#endif
 
   cache_inserted = cache_live_freed = 0;
   
   for (i=0; i<hash_size; i++)
     for (cache = hash_table[i], up = &hash_table[i]; cache; cache = tmp)
       {
+#ifdef HAVE_DNSSEC
+       if (cache->flags & (F_DNSKEY | F_DS))
+         blockdata_free(cache->addr.key.keydata);
+#endif
        tmp = cache->hash_next;
        if (cache->flags & (F_HOSTS | F_CONFIG))
          {
@@ -948,13 +957,27 @@ void cache_reload(void)
       if (hostname_isequal(a->target, intr->name) &&
          ((cache = whine_malloc(sizeof(struct crec)))))
        {
-         cache->flags = F_FORWARD | F_NAMEP | F_CNAME | F_IMMORTAL | F_CONFIG;
+         cache->flags = F_FORWARD | F_NAMEP | F_CNAME | F_IMMORTAL | F_CONFIG | F_DNSSECOK;
          cache->name.namep = a->alias;
          cache->addr.cname.target.int_name = intr;
          cache->addr.cname.uid = -1;
          cache_hash(cache);
          add_hosts_cname(cache); /* handle chains */
        }
+
+#ifdef HAVE_DNSSEC
+  for (key = daemon->dnskeys; key; key = key->next)
+    if ((cache = whine_malloc(sizeof(struct crec))) &&
+       (cache->addr.key.keydata = blockdata_alloc(key->key, key->keylen)))
+      {
+       cache->flags = F_FORWARD | F_IMMORTAL | F_DNSKEY | F_CONFIG | F_NAMEP;
+       cache->name.namep = key->name;
+       cache->uid = key->keylen;
+       cache->addr.key.algo = key->algo;
+       cache->addr.key.keytag = dnskey_keytag(key->algo, key->flags, (unsigned char *)key->key, key->keylen);
+       cache_hash(cache);
+      }
+#endif
   
   /* borrow the packet buffer for a temporary by-address hash */
   memset(daemon->packet, 0, daemon->packet_buff_sz);
@@ -1197,16 +1220,13 @@ void dump_cache(time_t now)
       for (i=0; i<hash_size; i++)
        for (cache = hash_table[i]; cache; cache = cache->hash_next)
          {
-           char *a, *p = daemon->namebuff;
-           p += sprintf(p, "%-40.40s ", cache_get_name(cache));
-           if ((cache->flags & F_NEG) && (cache->flags & F_FORWARD))
-             a = ""; 
-           else if (cache->flags & F_CNAME) 
-             {
-               a = "";
-               if (!is_outdated_cname_pointer(cache))
-                 a = cache_get_cname_target(cache);
-             }
+           char *a = daemon->addrbuff, *p = daemon->namebuff, *n = cache_get_name(cache);
+           *a = 0;
+           if (strlen(n) == 0)
+             n = "<Root>";
+           p += sprintf(p, "%-40.40s ", n);
+           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)
              {
@@ -1216,11 +1236,11 @@ void dump_cache(time_t now)
            else if (cache->flags & F_DS)
              {
                a = daemon->addrbuff;
-               sprintf(a, "%5u %3u %3u %u", cache->addr.key.keytag,
-                       cache->addr.key.algo, cache->addr.key.digest, cache->uid);
+               sprintf(a, "%5u %3u %3u", cache->addr.key.keytag,
+                       cache->addr.key.algo, cache->addr.key.digest);
              }
 #endif
-           else 
+           else if (!(cache->flags & F_NEG) || !(cache->flags & F_FORWARD))
              { 
                a = daemon->addrbuff;
                if (cache->flags & F_IPV4)
@@ -1291,13 +1311,20 @@ void log_query(unsigned int flags, char *name, struct all_addr *addr, char *arg)
 
   if (addr)
     {
+      if (flags & F_KEYTAG)
+       sprintf(daemon->addrbuff, arg, addr->addr.keytag);
+      else
+       {
 #ifdef HAVE_IPV6
-      inet_ntop(flags & F_IPV4 ? AF_INET : AF_INET6,
-               addr, daemon->addrbuff, ADDRSTRLEN);
+         inet_ntop(flags & F_IPV4 ? AF_INET : AF_INET6,
+                   addr, daemon->addrbuff, ADDRSTRLEN);
 #else
-      strncpy(daemon->addrbuff, inet_ntoa(addr->addr.addr4), ADDRSTRLEN);  
+         strncpy(daemon->addrbuff, inet_ntoa(addr->addr.addr4), ADDRSTRLEN);  
 #endif
+       }
     }
+  else
+    dest = arg;
 
   if (flags & F_REVERSE)
     {
@@ -1339,6 +1366,8 @@ void log_query(unsigned int flags, char *name, struct all_addr *addr, char *arg)
     source = arg;
   else if (flags & F_UPSTREAM)
     source = "reply";
+  else if (flags & F_SECSTAT)
+    source = "validation";
   else if (flags & F_AUTH)
     source = "auth";
   else if (flags & F_SERVER)
@@ -1351,6 +1380,11 @@ void log_query(unsigned int flags, char *name, struct all_addr *addr, char *arg)
       source = arg;
       verb = "from";
     }
+  else if (flags & F_DNSSEC)
+    {
+      source = arg;
+      verb = "to";
+    }
   else
     source = "cached";
   
@@ -1422,6 +1456,21 @@ void blockdata_free(struct blockdata *blocks)
       keyblock_free = blocks;
     }
 }
+
+void  blockdata_retrieve(struct blockdata *block, size_t len, void *data)
+{
+  size_t blen;
+  struct  blockdata *b;
+  
+  for (b = block; len > 0 && b;  b = b->next)
+    {
+      blen = len > KEYBLOCK_LEN ? KEYBLOCK_LEN : len;
+      memcpy(data, b->key, blen);
+      data += blen;
+      len -= blen;
+    }
+}
+  
 #endif
 
       
index 079c3d936885c36b756619373367e9a950eef462..969c2b55d1264456cd4b3c0ff8f723bc8932d346 100644 (file)
@@ -139,8 +139,8 @@ RESOLVFILE
 /* #define HAVE_DBUS */
 /* #define HAVE_IDN */
 /* #define HAVE_CONNTRACK */
-#define HAVE_DNSSEC
-#define HAVE_OPENSSL
+#define HAVE_DNSSEC 
+#define HAVE_OPENSSL 
 
 /* Default locations for important system files. */
 
@@ -385,7 +385,12 @@ static char *compile_opts =
 #ifndef HAVE_AUTH
 "no-"
 #endif
-  "auth";
+"auth "
+#ifndef HAVE_DNSSEC
+"no-"
+#endif
+"DNSSEC";
+
 
 #endif
 
index 8a3541a974a764cd07cf78784ffd359726f724fa..044c865780d9cd745e51a0507637c2eca5ac9004 100644 (file)
@@ -242,6 +242,7 @@ struct all_addr {
 #ifdef HAVE_IPV6
     struct in6_addr addr6;
 #endif
+    unsigned int keytag;
   } addr;
 };
 
@@ -286,6 +287,12 @@ struct cname {
   struct cname *next;
 }; 
 
+struct dnskey {
+  char *name, *key;
+  int keylen, algo, flags;
+  struct dnskey *next;
+};
+
 #define ADDRLIST_LITERAL 1
 #define ADDRLIST_IPV6    2
 
@@ -360,7 +367,7 @@ struct crec {
     } key;
   } addr;
   time_t ttd; /* time to die */
-  /* used as keylen if F_DS or F_DNSKEY, index to source for F_HOSTS */
+  /* used as keylen ifF_DNSKEY, index to source for F_HOSTS */
   int uid; 
   unsigned short flags;
   union {
@@ -395,6 +402,9 @@ struct crec {
 #define F_QUERY     (1u<<19)
 #define F_NOERR     (1u<<20)
 #define F_AUTH      (1u<<21)
+#define F_DNSSEC    (1u<<22)
+#define F_KEYTAG    (1u<<23)
+#define F_SECSTAT   (1u<<24)
 
 /* composites */
 #define F_TYPE      (F_IPV4 | F_IPV6 | F_DNSKEY | F_DS) /* Only one may be set */
@@ -896,6 +906,9 @@ extern struct daemon {
 #ifdef OPTION6_PREFIX_CLASS 
   struct prefix_class *prefix_classes;
 #endif
+#ifdef HAVE_DNSSEC
+  struct dnskey *dnskeys;
+#endif
 
   /* globally used stuff for DNS */
   char *packet; /* packet buffer */
@@ -977,6 +990,7 @@ struct crec *cache_enumerate(int init);
 #ifdef HAVE_DNSSEC
 struct blockdata *blockdata_alloc(char *data, size_t len);
 size_t blockdata_walk(struct blockdata **key, unsigned char **p, size_t cnt);
+void  blockdata_retrieve(struct blockdata *block, size_t len, void *data);
 void blockdata_free(struct blockdata *blocks);
 #endif
 
@@ -1000,7 +1014,7 @@ size_t setup_reply(struct dns_header *header, size_t  qlen,
                   unsigned long local_ttl);
 int extract_addresses(struct dns_header *header, size_t qlen, char *namebuff, 
                      time_t now, char **ipsets, int is_sign, int checkrebind,
-                     int checking_disabled);
+                     int no_cache, int secure);
 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 check_for_bogus_wildcard(struct dns_header *header, size_t qlen, char *name, 
@@ -1034,11 +1048,13 @@ int in_zone(struct auth_zone *zone, char *name, char **cut);
 #endif
 
 /* dnssec.c */
-size_t dnssec_generate_query(struct dns_header *header, char *name, int class, int type);
+size_t dnssec_generate_query(struct dns_header *header, 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 validate_rrset(time_t now, struct dns_header *header, size_t plen, int class, int type, char *name, char *keyname);
-int dnssec_validate_reply(struct dns_header *header, size_t plen, char *name, char *keyname, int *class);
+int validate_rrset(time_t now, struct dns_header *header, size_t plen, int class, 
+                  int type, char *name, char *keyname, struct blockdata *key, int keylen, int algo, int keytag);
+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);
 
 /* util.c */
 void rand_init(void);
@@ -1065,6 +1081,9 @@ void prettyprint_time(char *buf, unsigned int t);
 int prettyprint_addr(union mysockaddr *addr, char *buf);
 int parse_hex(char *in, unsigned char *out, int maxlen, 
              unsigned int *wildcard_mask, int *mac_type);
+#ifdef HAVE_DNSSEC
+int parse_base64(char *in, char *out);
+#endif
 int memcmp_masked(unsigned char *a, unsigned char *b, int len, 
                  unsigned int mask);
 int expand_buf(struct iovec *iov, size_t size);
index 1717db6579c71a7a4bb7f867f2e34bbf74978e3f..77c5bc5cfd14779d03b2a33b1d3d0458bc359e58 100644 (file)
@@ -17,7 +17,7 @@
 #ifndef DNSSEC_CRYPTO_H
 #define DNSSEC_CRYPTO_H
 
-struct keydata;
+struct blockdata;
 
 /* 
  * vtable for a signature verification algorithm.
@@ -49,7 +49,7 @@ typedef struct VerifyAlgCtx VerifyAlgCtx;
 typedef struct
 {
   int digest_algo;
-  int (*verify)(VerifyAlgCtx *ctx, struct keydata *key, unsigned key_len);
+  int (*verify)(VerifyAlgCtx *ctx, struct blockdata *key, unsigned key_len);
 } VerifyAlg;
 
 struct VerifyAlgCtx
@@ -74,9 +74,9 @@ int verifyalg_algonum(VerifyAlgCtx *a);
 #define DIGESTALG_SHA512   257
 
 int digestalg_supported(int algo);
-int digestalg_begin(int algo);
+void digestalg_begin(int algo);
 void digestalg_add_data(void *data, unsigned len);
-void digestalg_add_keydata(struct keydata *key, size_t len);
+void digestalg_add_keydata(struct blockdata *key, size_t len);
 unsigned char *digestalg_final(void);
 int digestalg_len(void);
 
index 4bf7e73c2fdd35ebca6edeeab824dea02a7b375d..2e25f82c9c097083cc8ee49134225332a3b6a64b 100644 (file)
    along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */
 
-#include <string.h>
 #include "dnsmasq.h"
+
+#ifdef HAVE_DNSSEC
+
 #include "dnssec-crypto.h"
 #include <openssl/evp.h>
 #include <openssl/rsa.h>
 #include <openssl/dsa.h>
 #include <openssl/err.h>
+#include <string.h>
 
 #define POOL_SIZE 1
 static union _Pool
@@ -39,20 +42,20 @@ static void print_hex(unsigned char *data, unsigned len)
   printf("\n");
 }
 
-static int keydata_to_bn(BIGNUM *ret, struct keydata **key_data, unsigned char **p, unsigned len)
+static int keydata_to_bn(BIGNUM *ret, struct blockdata **key_data, unsigned char **p, unsigned len)
 {
   size_t cnt;
   BIGNUM temp;
 
   BN_init(ret);
 
-  cnt = keydata_walk(key_data, p, len);
+  cnt = blockdata_walk(key_data, p, len);
   BN_bin2bn(*p, cnt, ret);
   len -= cnt;
   *p += cnt;
   while (len > 0)
     {
-      if (!(cnt = keydata_walk(key_data, p, len)))
+      if (!(cnt = blockdata_walk(key_data, p, len)))
         return 0;
       BN_lshift(ret, ret, cnt*8);
       BN_init(&temp);
@@ -64,7 +67,7 @@ static int keydata_to_bn(BIGNUM *ret, struct keydata **key_data, unsigned char *
   return 1;
 }
 
-static int rsasha1_parse_key(BIGNUM *exp, BIGNUM *mod, struct keydata *key_data, unsigned key_len)
+static int rsasha1_parse_key(BIGNUM *exp, BIGNUM *mod, struct blockdata *key_data, unsigned key_len)
 {
   unsigned char *p = key_data->key;
   size_t exp_len, mod_len;
@@ -80,7 +83,7 @@ static int rsasha1_parse_key(BIGNUM *exp, BIGNUM *mod, struct keydata *key_data,
       keydata_to_bn(mod, &key_data, &p, mod_len);
 }
 
-static int dsasha1_parse_key(BIGNUM *Q, BIGNUM *P, BIGNUM *G, BIGNUM *Y, struct keydata *key_data, unsigned key_len)
+static int dsasha1_parse_key(BIGNUM *Q, BIGNUM *P, BIGNUM *G, BIGNUM *Y, struct blockdata *key_data, unsigned key_len)
 {
   unsigned char *p = key_data->key;
   int T;
@@ -93,7 +96,7 @@ static int dsasha1_parse_key(BIGNUM *Q, BIGNUM *P, BIGNUM *G, BIGNUM *Y, struct
       keydata_to_bn(Y, &key_data, &p, 64+T*8);
 }
 
-static int rsa_verify(VerifyAlgCtx *ctx, struct keydata *key_data, unsigned key_len, int nid, int dlen)
+static int rsa_verify(VerifyAlgCtx *ctx, struct blockdata *key_data, unsigned key_len, int nid, int dlen)
 {
   int validated = 0;
 
@@ -108,27 +111,27 @@ static int rsa_verify(VerifyAlgCtx *ctx, struct keydata *key_data, unsigned key_
   return validated;
 }
 
-static int rsamd5_verify(VerifyAlgCtx *ctx, struct keydata *key_data, unsigned key_len)
+static int rsamd5_verify(VerifyAlgCtx *ctx, struct blockdata *key_data, unsigned key_len)
 {
   return rsa_verify(ctx, key_data, key_len, NID_md5, 16);
 }
 
-static int rsasha1_verify(VerifyAlgCtx *ctx, struct keydata *key_data, unsigned key_len)
+static int rsasha1_verify(VerifyAlgCtx *ctx, struct blockdata *key_data, unsigned key_len)
 {
   return rsa_verify(ctx, key_data, key_len, NID_sha1, 20);
 }
 
-static int rsasha256_verify(VerifyAlgCtx *ctx, struct keydata *key_data, unsigned key_len)
+static int rsasha256_verify(VerifyAlgCtx *ctx, struct blockdata *key_data, unsigned key_len)
 {
   return rsa_verify(ctx, key_data, key_len, NID_sha256, 32);
 }
 
-static int rsasha512_verify(VerifyAlgCtx *ctx, struct keydata *key_data, unsigned key_len)
+static int rsasha512_verify(VerifyAlgCtx *ctx, struct blockdata *key_data, unsigned key_len)
 {
   return rsa_verify(ctx, key_data, key_len, NID_sha512, 64);
 }
 
-static int dsasha1_verify(VerifyAlgCtx *ctx, struct keydata *key_data, unsigned key_len)
+static int dsasha1_verify(VerifyAlgCtx *ctx, struct blockdata *key_data, unsigned key_len)
 {
   static unsigned char asn1_signature[] =
   {
@@ -222,9 +225,6 @@ VerifyAlgCtx* verifyalg_alloc(int algo)
   int i;
   VerifyAlgCtx *ret = 0;
 
-  if (!verifyalg_supported(algo))
-    return 0;
-
   if (pool_used == (1<<POOL_SIZE)-1)
       ret = whine_malloc(valgctx_size[algo]);
   else
@@ -271,7 +271,7 @@ int digestalg_supported(int algo)
           algo == DIGESTALG_SHA512);
 }
 
-int digestalg_begin(int algo)
+void digestalg_begin(int algo)
 {
   EVP_MD_CTX_init(&digctx);
   if (algo == DIGESTALG_SHA1)
@@ -282,9 +282,6 @@ int digestalg_begin(int algo)
     EVP_DigestInit_ex(&digctx, EVP_sha512(), NULL);
   else if (algo == DIGESTALG_MD5)
     EVP_DigestInit_ex(&digctx, EVP_md5(), NULL);
-  else
-    return 0;
-  return 1;
 }
 
 int digestalg_len()
@@ -297,12 +294,12 @@ void digestalg_add_data(void *data, unsigned len)
   EVP_DigestUpdate(&digctx, data, len);
 }
 
-void digestalg_add_keydata(struct keydata *key, size_t len)
+void digestalg_add_keydata(struct blockdata *key, size_t len)
 {
   size_t cnt; unsigned char *p = NULL;
   while (len)
     {
-      cnt = keydata_walk(&key, &p, len);
+      cnt = blockdata_walk(&key, &p, len);
       EVP_DigestUpdate(&digctx, p, cnt);
       p += cnt;
       len -= cnt;
@@ -316,3 +313,4 @@ unsigned char* digestalg_final(void)
   return digest;
 }
 
+#endif /* HAVE_DNSSEC */
index 4a911375270c13b90c137e94e931bc5966c45cca..2e82b16197ac05b755545a1f01b317126ff9e8c3 100644 (file)
@@ -1,4 +1,5 @@
 /* dnssec.c is Copyright (c) 2012 Giovanni Bajo <rasky@develer.com>
+           and Copyright (c) 2012-2014 Simon Kelley
 
    This program is free software; you can redistribute it and/or modify
    it under the terms of the GNU General Public License as published by
 */
 
 #include "dnsmasq.h"
+
+#ifdef HAVE_DNSSEC
+
 #include "dnssec-crypto.h"
 #include <assert.h>
 
 /* Maximum length in octects of a domain name, in wire format */
-#define MAXCDNAME  256
+#define MAXCDNAME  256 
 
 #define MAXRRSET 16
 
@@ -28,8 +32,6 @@
 #define SERIAL_LT       -1
 #define SERIAL_GT        1
 
-static int dnskey_keytag(int alg, unsigned char *rdata, int rdlen);
-
 /* Implement RFC1982 wrapped compare for 32-bit numbers */
 static int serial_compare_32(unsigned long s1, unsigned long s2)
 {
@@ -45,36 +47,6 @@ static int serial_compare_32(unsigned long s1, unsigned long s2)
   return SERIAL_UNDEF;
 }
 
-/* Extract a DNS name from wire format, without handling compression. This is
-   faster than extract_name() and does not require access to the full dns
-   packet. */
-static int extract_name_no_compression(unsigned char *rr, int maxlen, char *buf)
-{
-  unsigned char *start=rr, *end = rr+maxlen;
-  int count;
-  
-  while (rr < end && *rr != 0)
-    {
-      count = *rr++;
-      while (count-- > 0 && rr < end)
-        {
-          *buf = *rr++;
-          if (!isascii(*buf) || iscntrl(*buf) || *buf == '.')
-            return 0;
-          if (*buf >= 'A' && *buf <= 'Z')
-            *buf += 'a' - 'A';
-          buf++;
-        }
-      *buf++ = '.';
-    }
-  /* Remove trailing dot (if any) */
-  if (rr != start)
-    *(--buf) = 0;
-  if (rr == end)
-    return 0;
-  /* Trailing \0 in source data must be consumed */
-  return rr-start+1;
-}
 
 /* process_domain_name() - do operations with domain names in canonicalized wire format.
  *
@@ -419,46 +391,55 @@ typedef struct PendingRRSIGValidation
   int keytag;
 } PendingRRSIGValidation;
 
-/* strchrnul - like strchr, but when character is not found, returns a pointer to the terminating \0.
 
-   This is an existing C GNU extension, but it's easier to reimplement it,
-   rather than tweaking with configure. */
-static char *my_strchrnul(char *str, char ch)
+/* Convert from presentation format to wire format, in place.
+   Also map UC -> LC.
+   Note that using extract_name to get presentation format
+   then calling to_wire() removes compression and maps case,
+   thus generating names in canonical form.
+   Calling to_wire followed by from_wire is almost an identity,
+   except that the UC remains mapped to LC. 
+*/
+static int to_wire(char *name)
 {
-  while (*str && *str != ch)
-    str++;
-  return str;
+  unsigned char *l, *p, term;
+  int len;
+
+  for (l = (unsigned char*)name; *l != 0; l = p)
+    {
+      for (p = l; *p != '.' && *p != 0; p++)
+       if (*p >= 'A' && *p <= 'Z')
+         *p = *p - 'A' + 'a';
+      
+      term = *p;
+      
+      if ((len = p - l) != 0)
+       memmove(l+1, l, len);
+      *l = len;
+      
+      p++;
+      
+      if (term == 0)
+       *p = 0;
+    }
+  
+  return l + 1 - (unsigned char *)name;
 }
 
-/* Convert a domain name to wire format */
-static int convert_domain_to_wire(char *name, unsigned char* out)
+/* Note: no compression  allowed in input. */
+static void from_wire(char *name)
 {
-  unsigned char len;
-  unsigned char *start = out;
-  char *p;
+  unsigned char *l;
+  int len;
 
-  do
+  for (l = (unsigned char *)name; *l != 0; l += len+1)
     {
-      p = my_strchrnul(name, '.');
-      if ((len = p-name))
-        {
-          *out++ = len;
-          while (len--)
-            {
-              char ch = *name++;
-              /* TODO: this will not be required anymore once we
-                 remove all usages of extract_name() from DNSSEC code */
-              if (ch >= 'A' && ch <= 'Z')
-                ch = ch - 'A' + 'a';
-              *out++ = ch;
-            }
-        }
-      name = p+1;
+      len = *l;
+      memmove(l, l+1, len);
+      l[len] = '.';
     }
-  while (*p);
 
-  *out++ = '\0';
-  return out-start;
+  *(l-1) = 0;
 }
 
 
@@ -498,43 +479,56 @@ static int digestalg_add_rdata(int sigtype, struct dns_header *header, size_t pk
   return 1;
 }
 
-size_t dnssec_generate_query(struct dns_header *header, char *name, int class, int type)
+size_t dnssec_generate_query(struct dns_header *header, char *name, int class, int type, union mysockaddr *addr)
 {
   unsigned char *p;
+  char types[20];
+  
+  querystr("dnssec", types, type);
 
+  if (addr->sa.sa_family == AF_INET) 
+    log_query(F_DNSSEC | F_IPV4, name, (struct all_addr *)&addr->in.sin_addr, types);
+#ifdef HAVE_IPV6
+  else
+    log_query(F_DNSSEC | F_IPV6, name, (struct all_addr *)&addr->in6.sin6_addr, types);
+#endif
+  
   header->qdcount = htons(1);
   header->ancount = htons(0);
   header->nscount = htons(0);
   header->arcount = htons(0);
 
-  header->hb3 =  HB3_RD; 
+  header->hb3 = HB3_RD; 
   SET_OPCODE(header, QUERY);
-  header->hb4 = 0;
+  header->hb4 = HB4_CD;
 
   /* ID filled in later */
 
   p = (unsigned char *)(header+1);
        
   p = do_rfc1035_name(p, name);
+  *p++ = 0;
   PUTSHORT(type, p);
   PUTSHORT(class, p);
 
   return add_do_bit(header, p - (unsigned char *)header, ((char *) header) + PACKETSZ);
 }
   
-/* The DNS packet is expected to contain the answer to a DNSKEY query
+/* The DNS packet is expected to contain the answer to a DNSKEY query.
+   Leave name of qury in name.
    Put all DNSKEYs in the answer which are valid into the cache.
    return codes:
          STAT_INSECURE bad packet, no DNSKEYs in reply.
         STAT_SECURE   At least one valid DNSKEY found and in cache.
-        STAT_BOGUS    At least one DNSKEY found, which fails validation.
-        STAT_NEED_DS  DS records to validate a key not found, name in namebuff 
+        STAT_BOGUS    No DNSKEYs found, which  can be validated with DS,
+                      or self-sign for DNSKEY RRset is not valid.
+        STAT_NEED_DS  DS records to validate a key not found, name in keyname 
 */
 int dnssec_validate_by_ds(time_t now, struct dns_header *header, size_t plen, char *name, char *keyname, int class)
 {
-  unsigned char *p;
+  unsigned char *psave, *p = (unsigned char *)(header+1);
   struct crec *crecp, *recp1;
-  int j, qtype, qclass, ttl, rdlen, flags, protocol, algo, gotone;
+  int rc, j, qtype, qclass, ttl, rdlen, flags, algo, valid, keytag;
   struct blockdata *key;
 
   if (ntohs(header->qdcount) != 1)
@@ -546,15 +540,23 @@ int dnssec_validate_by_ds(time_t now, struct dns_header *header, size_t plen, ch
   GETSHORT(qtype, p);
   GETSHORT(qclass, p);
   
-  if (qtype != T_DNSKEY || qclass != class)
+  if (qtype != T_DNSKEY || qclass != class || ntohs(header->ancount) == 0)
     return STAT_INSECURE;
 
+   /* 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;
+    }
+
   cache_start_insert();
 
-  for (gotone = 0, j = ntohs(header->ancount); j != 0; j--) 
+  /* NOTE, we need to find ONE DNSKEY which matches the DS */
+  for (valid = 0, j = ntohs(header->ancount); j != 0; j--) 
     {
       /* Ensure we have type, class  TTL and length */
-      if (!extract_name(header, plen, &p, name, 1, 10))
+      if (!(rc = extract_name(header, plen, &p, name, 0, 10)))
        return STAT_INSECURE; /* bad packet */
   
       GETSHORT(qtype, p); 
@@ -562,64 +564,89 @@ int dnssec_validate_by_ds(time_t now, struct dns_header *header, size_t plen, ch
       GETLONG(ttl, p);
       GETSHORT(rdlen, p);
 
-      if (qclass != class || qtype != T_DNSKEY || rdlen < 4)
+      if (qclass != class || qtype != T_DNSKEY || rc == 2)
        {
-         /* skip all records other than DNSKEY */
-         p += rdlen;
-         continue;
+         if (ADD_RDLEN(header, p, plen, rdlen))
+           continue;
+
+         return STAT_INSECURE; /* bad packet */
        }
       
-      crecp = cache_find_by_name(NULL, name, now, F_DS);
+      if (!CHECK_LEN(header, p, plen, rdlen) || rdlen < 4)
+       return STAT_INSECURE; /* bad packet */
+      
+      psave = p;
       
       /* length at least covers flags, protocol and algo now. */
       GETSHORT(flags, p);
-      protocol = *p++;
+      if (*p++ != 3)
+       return STAT_INSECURE;
       algo = *p++;
-
-      /* See if we have cached a DS record which validates this key */
-      for (recp1 = crecp; recp1; recp1 = cache_find_by_name(recp1, name, now, F_DS))
-       if (recp1->addr.key.algo == algo && is_supported_digest(recp1->addr.key.digest))
-         break;
+      keytag = dnskey_keytag(algo, flags, p, rdlen - 4);
       
-      /* DS record needed to validate key is missing, return name of DS in namebuff */
-      if (!recp1)
-       return STAT_NEED_DS;
-      else
+      /* 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)))
        {
-         int valid = 1;
-         /* calculate digest of canonicalised DNSKEY data using digest in  (recp1->addr.key.digest) 
-            and see if it equals digest stored in recp1
-         */
-         
-         if (!valid)
-           return STAT_BOGUS;
+         recp1->uid = rdlen - 4;
+         recp1->addr.key.keydata = key;
+         recp1->addr.key.algo = algo;
+         recp1->addr.key.keytag = keytag;
        }
       
-      if ((key = blockdata_alloc((char*)p, rdlen)))
-       {
-         
-         /* We've proved that the KEY is OK, store it in the cache */
-         if ((crecp = cache_insert(name, NULL, now, ttl, F_FORWARD | F_DNSKEY)))
-           {
-             crecp->uid = rdlen;
-             crecp->addr.key.keydata = key;
-             crecp->addr.key.algo = algo;
-             crecp->addr.key.keytag = dnskey_keytag(algo, (char*)p, rdlen);
-             gotone = 1;
-           }
-       }
+      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)
+       continue;
+      
+      for (recp1 = crecp; recp1; recp1 = cache_find_by_name(recp1, name, now, F_DS))
+       if (recp1->addr.key.algo == algo && 
+           recp1->addr.key.keytag == keytag &&
+           (flags & 0x100) && /* zone key flag */
+           digestalg_supported(recp1->addr.key.digest))
+         {
+           int wire_len = to_wire(name);
+           
+           digestalg_begin(recp1->addr.key.digest);
+           digestalg_add_data(name, wire_len);
+           digestalg_add_data((char *)psave, rdlen);
+           
+           from_wire(name);
+
+           /* TODO fragented digest */
+           if (memcmp(digestalg_final(), recp1->addr.key.keydata->key, digestalg_len()) == 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;
+             }
+         }
     }
-  
-  cache_end_insert();  
 
-      
-  return gotone ? STAT_SECURE : STAT_INSECURE;
+  if (valid)
+    {
+      /* commit cache insert. */
+      cache_end_insert();
+      return STAT_SECURE;
+    }
+
+  log_query(F_UPSTREAM, name, NULL, "BOGUS DNSKEY");
+  return STAT_BOGUS;
 }
+
+
 /* The DNS packet is expected to contain the answer to a DS query
+   Leave name of DS query in name.
    Put all DSs in the answer which are valid into the cache.
    return codes:
-   STAT_INSECURE    bad packet, no DNSKEYs in reply.
+   STAT_INSECURE    bad packet, 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
@@ -627,8 +654,8 @@ int dnssec_validate_by_ds(time_t now, struct dns_header *header, size_t plen, ch
 
 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);
-  struct crec *crecp, *recp1;
+  unsigned char *psave, *p = (unsigned char *)(header+1);
+  struct crec *crecp;
   int qtype, qclass, val, j, gotone;
   struct blockdata *key;
 
@@ -641,10 +668,13 @@ int dnssec_validate_ds(time_t now, struct dns_header *header, size_t plen, char
   GETSHORT(qtype, p);
   GETSHORT(qclass, p);
 
-  if (qtype != T_DS || qclass != class)
+  if (qtype != T_DS || qclass != class || ntohs(header->ancount) == 0)
     return STAT_INSECURE;
-
-  val = validate_rrset(header, plen, class, T_DS, name, keyname);
+  
+  val = validate_rrset(now, header, plen, class, T_DS, name, keyname, NULL, 0, 0, 0);
+  if (val == STAT_BOGUS)
+    log_query(F_UPSTREAM, name, NULL, "BOGUS DS");
 
   /* failed to validate or missing key. */
   if (val != STAT_SECURE)
@@ -654,7 +684,7 @@ int dnssec_validate_ds(time_t now, struct dns_header *header, size_t plen, char
 
   for (gotone = 0, j = ntohs(header->ancount); j != 0; j--) 
     {
-      int ttl, rdlen, rc, algo;
+      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)))
@@ -666,32 +696,42 @@ int dnssec_validate_ds(time_t now, struct dns_header *header, size_t plen, char
       GETSHORT(rdlen, p);
       
       /* check type, class and name, skip if not in DS rrset */
-      if (qclass != class || qtype != T_DS || rc == 2)
-       {
-         p += rdlen;
-         continue;
-       }
-      
-      if ((key = blockdata_alloc((char*)p, rdlen)))
+      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 ((crecp = cache_insert(name, NULL, now, ttl, F_FORWARD | F_DS)))
+         if ((key = blockdata_alloc((char*)p, rdlen - 4)) &&
+             (crecp = cache_insert(name, NULL, now, ttl, F_FORWARD | F_DS)))
            {
-             crecp->uid = rdlen;
+             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 = dnskey_keytag(algo, (char*)p, rdlen);
-             gotone = 1;
+             crecp->addr.key.keytag = keytag;
            }
+         else
+           return STAT_INSECURE; /* cache problem */
+         
+         p = psave;
        }
-      
+
+      if (!ADD_RDLEN(header, p, plen, rdlen))
+       return STAT_INSECURE; /* bad packet */
+     
     }
   
   cache_end_insert();  
   
-  
-  return gotone ? STAT_SECURE : STAT_INSECURE;
+  return STAT_SECURE;
 }
 
 
@@ -702,528 +742,249 @@ int dnssec_validate_ds(time_t now, struct dns_header *header, size_t plen, char
    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)
+
+   if key is non-NULL, use that key, which has the algo and tag given in the params of those names,
+   otherwise find the key in the cache.
 */
-int validate_rrset(time_t now, struct dns_header *header, size_t plen, int class, int type, char *name, char *keyname)
+int validate_rrset(time_t now, struct dns_header *header, size_t plen, int class, 
+                  int type, char *name, char *keyname, struct blockdata *key, int keylen, int algo_in, int keytag_in)
 {
-  unsigned char *p, *psav, *sig;
-  int rrsetidx, res, sigttl, sig_data_len, j;
-  struct crec *crecp;
-  void *rrset[MAXRRSET];  /* TODO: max RRset size? */
+  unsigned char *p;
+  int rrsetidx, sigidx, res, rdlen, j;
+  struct crec *crecp = NULL;
+  void *rrset[MAXRRSET], *sigs[MAXRRSET];  /* TODO: max RRset size? */
   int type_covered, algo, labels, orig_ttl, sig_expiration, sig_inception, key_tag;
 
   if (!(p = skip_questions(header, plen)))
     return STAT_INSECURE;
 
   /* look for an RRSIG record for this RRset and get pointers to each record */
-  for (rrsetidx = 0, sig = NULL, j = ntohs(header->ancount) + ntohs(header->nscount); 
+  for (rrsetidx = 0, sigidx = 0, j = ntohs(header->ancount) + ntohs(header->nscount); 
        j != 0; j--) 
     {
-      unsigned char *pstart = p;
-      int stype, sclass, sttl, rdlen;
+      unsigned char *pstart;
+      int stype, sclass, sttl;
 
       if (!(res = extract_name(header, plen, &p, name, 0, 10)))
        return STAT_INSECURE; /* bad packet */
       
+      pstart = p;
+      
       GETSHORT(stype, p);
       GETSHORT(sclass, p);
       GETLONG(sttl, p);
       GETSHORT(rdlen, p);
+      
+      (void)sttl;
         
       if (!CHECK_LEN(header, p, plen, rdlen))
         return STAT_INSECURE; /* bad packet */
 
-      if (res == 2 || htons(stype) != T_RRSIG || htons(sclass) != class)
-       continue;
-
-      if (htons(stype) == type)
-       {
-         rrset[rrsetidx++] = pstart;
-          if (rrsetidx == MAXRRSET)
-           return STAT_INSECURE; /* RRSET too big TODO */
-       }
-
-      if (htons(stype) == T_RRSIG)
+      if (res == 1 && sclass == class)
        {
-         /* name matches, RRSIG for correct class */
-         /* enough data? */
-         if (rdlen < 18)
-           return STAT_INSECURE; 
-         
-         GETSHORT(type_covered, p);
-         algo = *p++;
-         labels = *p++;
-         GETLONG(orig_ttl, p);
-         GETLONG(sig_expiration, p);
-         GETLONG(sig_inception, p);
-         GETSHORT(key_tag, p);
-         
-         if (type_covered != type ||
-             !check_date_range(sig_inception, sig_expiration))
+         if (stype == type)
            {
-             /* covers wrong type or out of date - skip */
-             p = psav;
-             if (!ADD_RDLEN(header, p, plen, rdlen))
-               return STAT_INSECURE;
-             continue;
+             rrset[rrsetidx++] = pstart;
+             if (rrsetidx == MAXRRSET)
+               return STAT_INSECURE; /* RRSET too big TODO */
            }
          
-         if (!extract_name(header, plen, &p, keyname, 1, 0))
-           return STAT_INSECURE;
-
-         /* OK, we have the signature record, see if the 
-            relevant DNSKEY is in the cache. */
-         for (crecp = cache_find_by_name(NULL, keyname, now, F_DNSKEY);
-              crecp;
-              crecp = cache_find_by_name(crecp, keyname, now, F_DNSKEY))
-           if (crecp->addr.key.algo == algo && crecp->addr.key.keytag == key_tag)
-             break;
-         
-         /* No, abort for now whilst we get it */
-         if (!crecp)
-           return STAT_NEED_KEY;
-
-         /* Save point to signature data */
-         sig = p;
-         sig_data_len = rdlen - (p - psav);
-         sigttl = sttl;
-
-         /* next record */
-         p = psav;
-         if (!ADD_RDLEN(header, p, plen, rdlen))
-           return STAT_INSECURE;
-       }    
+         if (stype == T_RRSIG)
+           {
+             sigs[sigidx++] = pstart;
+             if (sigidx == MAXRRSET)
+               return STAT_INSECURE; /* RRSET too big TODO */
+           }
+       }
+     
+      if (!ADD_RDLEN(header, p, plen, rdlen))
+       return STAT_INSECURE;
     }
   
-  /* Didn't find RRSIG or RRset is empty */
-  if (!sig || rrsetidx == 0)
+  /* RRset empty, no RRSIGs */
+  if (rrsetidx == 0 || sigidx == 0)
     return STAT_INSECURE;
-  
-  /* OK, we have an RRSIG and an RRset and we have a the DNSKEY that validates them. */
-  
-  /* Sort RRset records in canonical order. */
-  rrset_canonical_order_ctx.header = header;
-  rrset_canonical_order_ctx.pktlen = plen;
-  qsort(rrset, rrsetidx, sizeof(void*), rrset_canonical_order);
-
-  /* Now initialize the signature verification algorithm and process the whole
-     RRset */
-  VerifyAlgCtx *alg = verifyalg_alloc(algo);
-  if (!alg)
-    return STAT_INSECURE;
-  alg->sig = sig;
-  alg->siglen = sig_data_len;
-  
-  u16 ntype = htons(type);
-  u16 nclass = htons(class);
-  u32 nsigttl = htonl(sigttl);
-
-  /* TODO: we shouldn't need to convert this to wire here. Best solution would be:
-     - Use process_name() instead of extract_name() everywhere in dnssec code
-     - Convert from wire format to representation format only for querying/storing cache
-  */
-  unsigned char owner_wire[MAXCDNAME];
-  int owner_wire_len = convert_domain_to_wire(name, owner_wire);
-  
-  digestalg_begin(alg->vtbl->digest_algo);
-  digestalg_add_data(sigrdata, 18+signer_name_rdlen);
-  for (i = 0; i < rrsetidx; ++i)
+     
+  /* Now try all the sigs to try and find one which validates */
+  for (j = 0; j <sigidx; j++)
     {
-      p = (unsigned char*)(rrset[i]);
+      unsigned char *psav;
+      int i, wire_len;
+      VerifyAlgCtx *alg;
+      u16 ntype, nclass;
+      u32 nsigttl;
+      
+      p = sigs[j] + 8; /* skip type, class and ttl */
       
-      digestalg_add_data(owner_wire, owner_wire_len);
-      digestalg_add_data(&ntype, 2);
-      digestalg_add_data(&nclass, 2);
-      digestalg_add_data(&nsigttl, 4);
-    
-      p += 8;
-      if (!digestalg_add_rdata(ntohs(sigtype), header, pktlen, p))
-        return 0;
-    }
-  int digest_len = digestalg_len();
-  memcpy(alg->digest, digestalg_final(), digest_len);
-
-  if (alg->vtbl->verify(alg,  crecp->addr.key.keydata, crecp_uid))
-    return STAT_SECURE;
-  
-  return STAT_INSECURE;
-}
-
-
-#if 0
-static int begin_rrsig_validation(struct dns_header *header, size_t pktlen,
-                                  unsigned char *reply, int count, char *owner,
-                                  int sigclass, int sigrdlen, unsigned char *sig,
-                                  PendingRRSIGValidation *out)
-{
-  int i, res;
-  int sigtype, sigalg, siglbl;
-  unsigned char *sigrdata = sig;
-  unsigned long sigttl, date_end, date_start;
-  unsigned char* p = reply;
-  char* signer_name = daemon->namebuff;
-  int signer_name_rdlen;
-  int keytag;
-  void *rrset[16];  /* TODO: max RRset size? */
-  int rrsetidx = 0;
-  
-  if (sigrdlen < 18)
-    return 0;
-  GETSHORT(sigtype, sig);
-  sigalg = *sig++;
-  siglbl = *sig++;
-  GETLONG(sigttl, sig);
-  GETLONG(date_end, sig);
-  GETLONG(date_start, sig);
-  GETSHORT(keytag, sig);
-  sigrdlen -= 18;
-  
-  if (!verifyalg_supported(sigalg))
-    {
-      printf("ERROR: RRSIG algorithm not supported: %d\n", sigalg);
-      return 0;
-    }
-
-  if (!check_date_range(date_start, date_end))
-    {
-      printf("ERROR: RRSIG outside date range\n");
-      return 0;
-    }
-
-  /* Iterate within the answer and find the RRsets matching the current RRsig */
-  for (i = 0; i < count; ++i)
-    {    
-      int qtype, qclass, rdlen;
-      if (!(res = extract_name(header, pktlen, &p, owner, 0, 10)))
-        return 0;
-      rrset[rrsetidx] = p;
-      GETSHORT(qtype, p);
-      GETSHORT(qclass, p);
-      p += 4; /* skip ttl */
       GETSHORT(rdlen, p);
-      if (res == 1 && qtype == sigtype && qclass == sigclass)
-        {
-          ++rrsetidx;
-          if (rrsetidx == countof(rrset))
-            {
-              /* Internal buffer too small */
-              printf("internal buffer too small for this RRset\n");
-              return 0;
-            }
-        }
-      p += rdlen;
-    }
-  
-  /* Sort RRset records in canonical order. */
-  rrset_canonical_order_ctx.header = header;
-  rrset_canonical_order_ctx.pktlen = pktlen;
-  qsort(rrset, rrsetidx, sizeof(void*), rrset_canonical_order);
-  
-  /* Skip through the signer name; we don't extract it right now because
-     we don't want to overwrite the single daemon->namebuff which contains
-     the owner name. We'll get to this later. */
-  if (!(p = skip_name(sig, header, pktlen, 0)))
-    return 0;
-  signer_name_rdlen = p - sig;
-  sig = p; sigrdlen -= signer_name_rdlen;
-
-  /* Now initialize the signature verification algorithm and process the whole
-     RRset */
-  VerifyAlgCtx *alg = verifyalg_alloc(sigalg);
-  if (!alg)
-    return 0;
-  alg->sig = sig;
-  alg->siglen = sigrdlen;
-  
-  sigtype = htons(sigtype);
-  sigclass = htons(sigclass);
-  sigttl = htonl(sigttl);
-
-  /* TODO: we shouldn't need to convert this to wire here. Best solution would be:
-     - Use process_name() instead of extract_name() everywhere in dnssec code
-     - Convert from wire format to representation format only for querying/storing cache
-   */
-  unsigned char owner_wire[MAXCDNAME];
-  int owner_wire_len = convert_domain_to_wire(owner, owner_wire);
-
-  digestalg_begin(alg->vtbl->digest_algo);
-  digestalg_add_data(sigrdata, 18+signer_name_rdlen);
-  for (i = 0; i < rrsetidx; ++i)
-    {
-      p = (unsigned char*)(rrset[i]);
-
-      digestalg_add_data(owner_wire, owner_wire_len);
-      digestalg_add_data(&sigtype, 2);
-      digestalg_add_data(&sigclass, 2);
-      digestalg_add_data(&sigttl, 4);
+      
+      if (rdlen < 18)
+       return STAT_INSECURE; /* bad packet */ 
+      
+      psav = p;
+      
+      GETSHORT(type_covered, p);
+      algo = *p++;
+      labels = *p++;
+      GETLONG(orig_ttl, p);
+      GETLONG(sig_expiration, p);
+      GETLONG(sig_inception, p);
+      GETSHORT(key_tag, p);
+      
+      if (type_covered != type ||
+         !check_date_range(sig_inception, sig_expiration) ||
+         !verifyalg_supported(algo))
+       {
+         /* covers wrong type or out of date - skip */
+         p = psav;
+         if (!ADD_RDLEN(header, p, plen, rdlen))
+           return STAT_INSECURE;
+         continue;
+       }
+      
+      if (!extract_name(header, plen, &p, keyname, 1, 0))
+       return STAT_INSECURE;
+      
+      /* OK, we have the signature record, see if the relevant DNSKEY is in the cache. */
+      if (!key && !(crecp = cache_find_by_name(NULL, keyname, now, F_DNSKEY)))
+       return STAT_NEED_KEY;
+      
+      /* Sort RRset records in canonical order. */
+      rrset_canonical_order_ctx.header = header;
+      rrset_canonical_order_ctx.pktlen = plen;
+      qsort(rrset, rrsetidx, sizeof(void*), rrset_canonical_order);
+      
+      alg = verifyalg_alloc(algo);
+      alg->sig = p;
+      alg->siglen = rdlen - (p - psav);
+       
+      ntype = htons(type);
+      nclass = htons(class);
+      nsigttl = htonl(orig_ttl);
+      
+      digestalg_begin(alg->vtbl->digest_algo);
+      digestalg_add_data(psav, 18);
+      wire_len = to_wire(keyname);
+      digestalg_add_data(keyname, wire_len);
+      from_wire(keyname);
+      
+      /* TODO wildcard rules : 4035 5.3.2 */
+      for (i = 0; i < rrsetidx; ++i)
+       {
+         p = (unsigned char*)(rrset[i]);
+         
+         wire_len = to_wire(name);
+         digestalg_add_data(name, wire_len);
+         from_wire(name);
+         digestalg_add_data(&ntype, 2);
+         digestalg_add_data(&nclass, 2);
+         digestalg_add_data(&nsigttl, 4);
+         
+         p += 8;
+         if (!digestalg_add_rdata(type, header, plen, p))
+           return STAT_INSECURE;
+       }
     
-      p += 8;
-      if (!digestalg_add_rdata(ntohs(sigtype), header, pktlen, p))
-        return 0;
-    }
-  int digest_len = digestalg_len();
-  memcpy(alg->digest, digestalg_final(), digest_len);
-
-  /* We don't need the owner name anymore; now extract the signer name */
-  if (!extract_name_no_compression(sigrdata+18, signer_name_rdlen, signer_name))
-    return 0;
+      memcpy(alg->digest, digestalg_final(),  digestalg_len());
 
-  out->alg = alg;
-  out->keytag = keytag;
-  out->signer_name = signer_name;
-  return 1;
-}
+      if (key)
+       {
+         if (algo_in == algo && keytag_in == key_tag &&
+             alg->vtbl->verify(alg, key, keylen))
+           return STAT_SECURE;
+       }
+      else
+       {
+         /* 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 &&
+               alg->vtbl->verify(alg, crecp->addr.key.keydata, crecp->uid))
+             return STAT_SECURE;
+       }
+    }
 
-static int end_rrsig_validation(PendingRRSIGValidation *val, struct crec *crec_dnskey)
-{
-  /* FIXME: keydata is non-contiguous */
-  return val->alg->vtbl->verify(val->alg, crec_dnskey->addr.key.keydata, crec_dnskey->uid);
+  return STAT_BOGUS;
 }
 
-
-static void dnssec_parserrsig(struct dns_header *header, size_t pktlen,
-                              unsigned char *reply, int count, char *owner,
-                              int sigclass, int sigrdlen, unsigned char *sig)
+/* Validate all the RRsets in the answer and authority sections of the reply (4035:3.2.3) */
+int dnssec_validate_reply(time_t now, struct dns_header *header, size_t plen, char *name, char *keyname, int *class)
 {
-  PendingRRSIGValidation val;
-
-  /* Initiate the RRSIG validation process. The pending state is returned into val. */
-  if (!begin_rrsig_validation(header, pktlen, reply, count, owner, sigclass, sigrdlen, sig, &val))
-    return;
+  unsigned char *ans_start, *p1, *p2;
+  int type1, class1, rdlen1, type2, class2, rdlen2;
+  int i, j, rc;
 
-  printf("RRSIG: querying cache for DNSKEY %s (keytag: %d)\n", val.signer_name, val.keytag);
-
-  /* Look in the cache for *all* the DNSKEYs with matching signer_name and keytag */
-  char onekey = 0;
-  struct crec *crecp = NULL;
-  while ((crecp = cache_find_by_name(crecp, val.signer_name, time(0), F_DNSKEY)))  /* TODO: time(0) */
+  if (!(ans_start = skip_questions(header, plen)))
+    return STAT_INSECURE;
+   
+  for (p1 = ans_start, i = 0; i < ntohs(header->ancount) + ntohs(header->nscount); i++)
     {
-      onekey = 1;
-
-      if (crecp->addr.key.keytag == val.keytag
-          && crecp->addr.key.algo == verifyalg_algonum(val.alg))
-        {
-          printf("RRSIG: found DNSKEY %d in cache, attempting validation\n", val.keytag);
+      if (!extract_name(header, plen, &p1, name, 1, 10))
+       return STAT_INSECURE; /* bad packet */
+      
+      GETSHORT(type1, p1);
+      GETSHORT(class1, p1);
+      p1 += 4; /* TTL */
+      GETSHORT(rdlen1, p1);
+      
+      /* Don't try and validate RRSIGs! */
+      if (type1 != T_RRSIG)
+       {
+         /* Check if we've done this RRset already */
+         for (p2 = ans_start, j = 0; j < i; j++)
+           {
+             if (!(rc = extract_name(header, plen, &p2, name, 0, 10)))
+               return STAT_INSECURE; /* bad packet */
+             
+             GETSHORT(type2, p2);
+             GETSHORT(class2, p2);
+             p2 += 4; /* TTL */
+             GETSHORT(rdlen2, p2);
+             
+             if (type2 == type1 && class2 == class1 && rc == 1)
+               break; /* Done it before: name, type, class all match. */
+             
+             if (!ADD_RDLEN(header, p2, plen, rdlen2))
+               return STAT_INSECURE;
+           }
+         
+         /* Not done, validate now */
+         if (j == i && (rc = validate_rrset(now, header, plen, class1, type1, name, keyname, NULL, 0, 0, 0)) != STAT_SECURE)
+           {
+             *class = class1; /* Class for DS or DNSKEY */
+             return rc;
+           }
+       }
 
-          if (end_rrsig_validation(&val, crecp))
-            printf("Validation OK\n");
-          else
-            printf("ERROR: Validation FAILED (%s, keytag:%d, algo:%d)\n", owner, val.keytag, verifyalg_algonum(val.alg));
-        }
+      if (!ADD_RDLEN(header, p1, plen, rdlen1))
+       return STAT_INSECURE;
     }
 
-  if (!onekey)
-    {
-      printf("DNSKEY not found, need to fetch it\n");
-      /* TODO: store PendingRRSIGValidation in routing table,
-         fetch key (and make it go through dnssec_parskey), then complete validation. */
-    }
+  return STAT_SECURE;
 }
 
-#endif /* comment out */
 
 /* Compute keytag (checksum to quickly index a key). See RFC4034 */
-static int dnskey_keytag(int alg, unsigned char *rdata, int rdlen)
+int dnskey_keytag(int alg, int flags, unsigned char *key, int keylen)
 {
   if (alg == 1)
     {
       /* Algorithm 1 (RSAMD5) has a different (older) keytag calculation algorithm.
          See RFC4034, Appendix B.1 */
-      return rdata[rdlen-3] * 256 + rdata[rdlen-2];
+      return key[keylen-4] * 256 + key[keylen-3];
     }
   else
     {
       unsigned long ac;
       int i;
 
-      ac = 0;
-      for (i = 0; i < rdlen; ++i)
-        ac += (i & 1) ? rdata[i] : rdata[i] << 8;
-      ac += (ac >> 16) & 0xFFFF;
-      return ac & 0xFFFF;
-    }
-}
-
-/* Check if the DS record (from cache) points to the DNSKEY record (from cache) */
-static int dnskey_ds_match(struct crec *dnskey, struct crec *ds)
-{
-  if (dnskey->addr.key.keytag != ds->addr.key.keytag)
-    return 0;
-  if (dnskey->addr.key.algo != ds->addr.key.algo)
-    return 0;
-
-  unsigned char owner[MAXCDNAME];  /* TODO: user part of daemon->namebuff */
-  int owner_len = convert_domain_to_wire(cache_get_name(ds), owner);
-  size_t keylen = dnskey->uid;
-  int dig = ds->uid;
-  int digsize;
-
-  if (!digestalg_begin(dig))
-    return 0;
-  digsize = digestalg_len();
-  digestalg_add_data(owner, owner_len);
-  digestalg_add_data("\x01\x01\x03", 3);
-  digestalg_add_data(&ds->addr.key.algo, 1);
-  digestalg_add_keydata(dnskey->addr.key.keydata, keylen);
-  return (memcmp(digestalg_final(), ds->addr.key.keydata->key, digsize) == 0);
-}
-
-int dnssec_parsekey(struct dns_header *header, size_t pktlen, char *owner, unsigned long ttl,
-                    int rdlen, unsigned char *rdata)
-{
-  int flags, proto, alg;
-  struct blockdata *key; struct crec *crecp;
-  unsigned char *ordata = rdata; int ordlen = rdlen;
-
-  CHECKED_GETSHORT(flags, rdata, rdlen);
-  CHECKED_GETCHAR(proto, rdata, rdlen);
-  CHECKED_GETCHAR(alg, rdata, rdlen);
-
-  if (proto != 3)
-    return 0;
-  /* Skip non-signing keys (as specified in RFC4034 */
-  if (!(flags & 0x100))
-    return 0;
-
-  key = blockdata_alloc((char*)rdata, rdlen);
-
-  /* TODO: time(0) is correct here? */
-  crecp = cache_insert(owner, NULL, time(0), ttl, F_FORWARD | F_DNSKEY);
-  if (crecp)
-    {
-      /* TODO: improve union not to name "uid" this field */
-      crecp->uid = rdlen;
-      crecp->addr.key.keydata = key;
-      crecp->addr.key.algo = alg;
-      crecp->addr.key.keytag = dnskey_keytag(alg, ordata, ordlen);
-      printf("DNSKEY: storing key for %s (keytag: %d)\n", owner, crecp->addr.key.keytag);
-    }
-  else
-    {
-      blockdata_free(key);
-      /* TODO: if insertion really might fail, verify we don't depend on cache
-         insertion success for validation workflow correctness */
-      printf("DNSKEY: cache insertion failure\n");
-      return 0;
-    }
-  return 1;
-}
-
-int dnssec_parseds(struct dns_header *header, size_t pktlen, char *owner, unsigned long ttl,
-                   int rdlen, unsigned char *rdata)
-{
-  int keytag, algo, dig;
-  struct blockdata *key; struct crec *crec_ds, *crec_key;
-
-  CHECKED_GETSHORT(keytag, rdata, rdlen);
-  CHECKED_GETCHAR(algo, rdata, rdlen);
-  CHECKED_GETCHAR(dig, rdata, rdlen);
-
-  if (!digestalg_supported(dig))
-    return 0;
-
-  key = blockdata_alloc((char*)rdata, rdlen);
-
-  /* TODO: time(0) is correct here? */
-  crec_ds = cache_insert(owner, NULL, time(0), ttl, F_FORWARD | F_DS);
-  if (!crec_ds)
-    {
-      blockdata_free(key);
-      /* TODO: if insertion really might fail, verify we don't depend on cache
-         insertion success for validation workflow correctness */
-      printf("DS: cache insertion failure\n");
-      return 0;
+      ac = ((htons(flags) >> 8) | ((htons(flags) << 8) & 0xff00)) + 0x300 + alg;
+      for (i = 0; i < keylen; ++i)
+        ac += (i & 1) ? key[i] : key[i] << 8;
+      ac += (ac >> 16) & 0xffff;
+      return ac & 0xffff;
     }
-
-  /* TODO: improve union not to name "uid" this field */
-  crec_ds->uid = dig;
-  crec_ds->addr.key.keydata = key;
-  crec_ds->addr.key.algo = algo;
-  crec_ds->addr.key.keytag = keytag;
-  printf("DS: storing key for %s (digest: %d)\n", owner, dig);
-
-  /* Now try to find a DNSKEY which matches this DS digest. */
-  printf("Looking for a DNSKEY matching DS %d...\n", keytag);
-  crec_key = NULL;
-  while ((crec_key = cache_find_by_name(crec_key, owner, time(0), F_DNSKEY)))  /* TODO: time(0) */
-    {
-      if (dnskey_ds_match(crec_key, crec_ds))
-        {
-          /* TODO: create a link within the cache: ds => dnskey */
-          printf("MATCH FOUND for keytag %d\n", keytag);
-          return 1;
-        }
-    }
-
-  printf("ERROR: match not found for DS %d (owner: %s)\n", keytag, owner);
-  return 0;
 }
 
-int dnssec1_validate(struct dns_header *header, size_t pktlen)
-{
-  unsigned char *p, *reply;
-  char *owner = daemon->namebuff;
-  int i, s, qtype, qclass, rdlen;
-  unsigned long ttl;
-  int slen[3] = { ntohs(header->ancount), ntohs(header->nscount), ntohs(header->arcount) };
 
-  if (slen[0] + slen[1] + slen[2] == 0)
-    return 0;
-  if (!(reply = p = skip_questions(header, pktlen)))
-    return 0;
-
-  /* First, process DNSKEY/DS records and add them to the cache. */
-  cache_start_insert();
-  for (i = 0; i < slen[0]; i++)
-    {
-      if (!extract_name(header, pktlen, &p, owner, 1, 10))
-       return 0;
-      GETSHORT(qtype, p);
-      GETSHORT(qclass, p);
-      GETLONG(ttl, p);
-      GETSHORT(rdlen, p);
-      if (qtype == T_DS)
-        {
-          printf("DS found\n");
-          dnssec_parseds(header, pktlen, owner, ttl, rdlen, p);
-        }
-      else if (qtype == T_DNSKEY)
-        {
-          printf("DNSKEY found\n");
-          dnssec_parsekey(header, pktlen, owner, ttl, rdlen, p);
-        }
-      p += rdlen;
-    }
-  cache_end_insert();
-
-  /* After we have cached DNSKEY/DS records, start looking for RRSIGs.
-     We want to do this in a separate step because we want the cache
-     to be already populated with DNSKEYs before parsing signatures. */
-  p = reply;
-  for (s = 0; s < 3; ++s)
-    {
-      reply = p;
-      for (i = 0; i < slen[s]; i++)
-        {
-          if (!extract_name(header, pktlen, &p, owner, 1, 10))
-            return 0;
-          GETSHORT(qtype, p);
-          GETSHORT(qclass, p);
-          GETLONG(ttl, p);
-          GETSHORT(rdlen, p);
-          if (qtype == T_RRSIG)
-            {
-              printf("RRSIG found (owner: %s)\n", owner);
-              /* TODO: missing logic. We should only validate RRSIGs for which we
-                 have a valid DNSKEY that is referenced by a DS record upstream.
-                 There is a memory vs CPU conflict here; should we validate everything
-                 to save memory and thus waste CPU, or better first acquire all information
-                 (wasting memory) and then doing the minimum CPU computations required? */
-              dnssec_parserrsig(header, pktlen, reply, slen[s], owner, qclass, rdlen, p);
-            }
-          p += rdlen;
-        }
-    }
-
-  return 1;
-}
+#endif /* HAVE_DNSSEC */
index 7e422e39f8405af30eb694af07ef5a8184f1d7c0..64f9bac3caa8b90c6a5356d2b2f50cf88eb1c1f2 100644 (file)
@@ -344,7 +344,10 @@ static int forward_query(int udpfd, union mysockaddr *udpaddr,
 
 #ifdef HAVE_DNSSEC
       if (option_bool(OPT_DNSSEC_VALID))
-       plen = add_do_bit(header, plen, ((char *) header) + PACKETSZ);
+       {
+         plen = add_do_bit(header, plen, ((char *) header) + PACKETSZ);
+         header->hb4 |= HB4_CD;
+       }
 #endif
 
       while (1)
@@ -550,7 +553,7 @@ static size_t process_reply(struct dns_header *header, time_t now, struct server
          SET_RCODE(header, NOERROR);
        }
       
-      if (extract_addresses(header, n, daemon->namebuff, now, sets, is_sign, check_rebind, no_cache))
+      if (extract_addresses(header, n, daemon->namebuff, now, sets, is_sign, check_rebind, no_cache, cache_secure))
        {
          my_syslog(LOG_WARNING, _("possible DNS-rebind attack detected: %s"), daemon->namebuff);
          munged = 1;
@@ -678,41 +681,51 @@ void reply_query(int fd, int family, time_t now)
       if (option_bool(OPT_DNSSEC_VALID) && !(forward->flags & FREC_CHECKING_DISABLED))
        {
          int status;
-         int class; 
+
+         /* We've had a reply already, which we're validating. Ignore this duplicate */
+         if (forward->stash)
+           return;
 
          if (forward->flags & FREC_DNSKEY_QUERY)
-           status = dnssec_validate_by_ds(now, header, n, daemon->namebuff, daemon->keyname, forward->class);
+           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);
          else
-           status = dnssec_validate_reply(header, n, daemon->namebuff, daemon->keyname, &forward->class);
+           status = dnssec_validate_reply(now, header, n, daemon->namebuff, daemon->keyname, &forward->class);
          
          /* 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)
            {
              struct frec *new;
-             if ((forward->stash = blockdata_alloc((char *)header, n)))
+             
+             if ((new = get_new_frec(now, NULL, 1)))
                {
-                 forward->stash_len = n;
+                 struct frec *next = new->next;
+                 *new = *forward; /* copy everything, then overwrite */
+                 new->next = next;
+                 new->stash = NULL;
+                 new->blocking_query = NULL;
+                 new->flags &= ~(FREC_DNSKEY_QUERY | FREC_DS_QUERY);
                  
-                 if ((new = get_new_frec(now, NULL, 1)))
+                 if ((forward->stash = blockdata_alloc((char *)header, n)))
                    {
                      int fd;
                      
-                     new = forward; /* copy everything, then overwrite */
+                     forward->stash_len = n;
+                     
                      new->dependent = forward; /* to find query awaiting new one. */
                      forward->blocking_query = new; /* for garbage cleaning */
-                     /* validate routines leave name of required record in daemon->namebuff */
+                     /* validate routines leave name of required record in daemon->keyname */
                      if (status == STAT_NEED_KEY)
                        {
                          new->flags |= FREC_DNSKEY_QUERY; 
-                         nn = dnssec_generate_query(header, daemon->namebuff, class, T_DNSKEY);
+                         nn = dnssec_generate_query(header, daemon->keyname, forward->class, T_DNSKEY, &server->addr);
                        }
                      else if (status == STAT_NEED_DS)
                        {
                          new->flags |= FREC_DS_QUERY;
-                         nn = dnssec_generate_query(header, daemon->namebuff, class, T_DS);
+                         nn = dnssec_generate_query(header, daemon->keyname, forward->class, T_DS, &server->addr);
                        }
                      new->crc = questions_crc(header, nn, daemon->namebuff);
                      new->new_id = get_id(new->crc);
@@ -739,9 +752,11 @@ void reply_query(int fd, int family, time_t now)
                          }
                      
                      /* Send DNSSEC query to same server as original query */
-                     while (sendto(fd, (char *)header, nn, 0, &server->addr.sa, sa_len(&server->addr)) == -1 && retry_send());
+                     while (sendto(fd, (char *)header, nn, 0, &server->addr.sa, sa_len(&server->addr)) == -1 && retry_send()); 
+                     server->queries++;
                    }
                }
+            
              return;
            }
          
@@ -750,35 +765,56 @@ void reply_query(int fd, int family, time_t now)
             and validate them with the new data. Failure to find needed data here is an internal error.
             Once we get to the original answer (FREC_DNSSEC_QUERY not set) and it validates,
             return it to the original requestor. */
-         while (forward->dependent)
+         if (forward->flags & (FREC_DNSKEY_QUERY | FREC_DS_QUERY))
            {
-             struct frec *prev = forward->dependent;
-             free_frec(forward);
-             forward = prev;
-             blockdata_retrieve_and_free(forward->stash, forward->stash_len, (void *)header);
-             n = forward->stash_len;
-             if (status == STAT_SECURE)
+             while (forward->dependent)
                {
-                  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_dnskey(now, header, n, daemon->namebuff, daemon->keyname, forward->class);
+                 struct frec *prev;
                  
-                  if (status == STAT_NEED_DS || status == STAT_NEED_KEY)
-                    my_syslog(LOG_ERR, _("Unexpected missing data for DNSSEC validation"));
+                 if (status == STAT_SECURE)
+                   {
+                     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);
+                   }
+                 
+                 prev = forward->dependent;
+                 free_frec(forward);
+                 forward = prev;
+                 forward->blocking_query = NULL; /* already gone */
+                 blockdata_retrieve(forward->stash, forward->stash_len, (void *)header);
+                  n = forward->stash_len;
+               }
+             
+             /* All DNSKEY and DS records done and in cache, now finally validate original 
+                answer, provided last DNSKEY is OK. */
+             if (status == STAT_SECURE)
+               status = dnssec_validate_reply(now, header, n, daemon->namebuff, daemon->keyname, &forward->class);
+             
+             if (status == STAT_NEED_DS || status == STAT_NEED_KEY)
+               {
+                 my_syslog(LOG_ERR, _("Unexpected missing data for DNSSEC validation"));
+                 status = STAT_INSECURE;
                }
            }
-         
-         /* All DNSKEY and DS records done and in cache, now finally validate original 
-            answer, provided last DNSKEY is OK. */
-         if (status == STAT_SECURE)
-           status = dnssec_validate_reply(header, n, daemon->namebuff, daemon->keyname, &forward->class);
+
+         log_query(F_KEYTAG | F_SECSTAT, "result", NULL, 
+                   status == STAT_SECURE ? "SECURE" : (status == STAT_INSECURE ? "INSECURE" : "BOGUS"));
+             
+         no_cache_dnssec = 0;
 
          if (status == STAT_SECURE)
            cache_secure = 1;
          /* TODO return SERVFAIL here */
          else if (status == STAT_BOGUS)
            no_cache_dnssec = 1;
+         
+         /* restore CD bit to the value in the query */
+         if (forward->flags & FREC_CHECKING_DISABLED)
+           header->hb4 |= HB4_CD;
+         else
+           header->hb4 &= ~HB4_CD;
        }
 #endif
       
@@ -1342,7 +1378,6 @@ static struct randfd *allocate_rfd(int family)
 
   return NULL; /* doom */
 }
-
 static void free_frec(struct frec *f)
 {
   if (f->rfd4 && --(f->rfd4->refcount) == 0)
@@ -1361,7 +1396,10 @@ static void free_frec(struct frec *f)
 
 #ifdef HAVE_DNSSEC
   if (f->stash)
-    blockdata_free(f->stash);
+    {
+      blockdata_free(f->stash);
+      f->stash = NULL;
+    }
 
   /* Anything we're waiting on is pointless now, too */
   if (f->blocking_query)
index cadabd5430bdd406ffbd9cf3b699d73c2c45b1bb..2933e9f7ae47e9c1ada0e48ba34594c15b7df6cf 100644 (file)
@@ -139,6 +139,7 @@ struct myoption {
 #define LOPT_QUIET_DHCP6  327
 #define LOPT_QUIET_RA     328
 #define LOPT_SEC_VALID    329
+#define LOPT_DNSKEY       330
 
 
 #ifdef HAVE_GETOPT_LONG
@@ -276,6 +277,7 @@ static const struct myoption opts[] =
     { "ipset", 1, 0, LOPT_IPSET },
     { "synth-domain", 1, 0, LOPT_SYNTH },
     { "dnssec", 0, 0, LOPT_SEC_VALID },
+    { "dnskey", 1, 0, LOPT_DNSKEY },
 #ifdef OPTION6_PREFIX_CLASS 
     { "dhcp-prefix-class", 1, 0, LOPT_PREF_CLSS },
 #endif
@@ -428,6 +430,7 @@ static struct {
   { LOPT_SYNTH, ARG_DUP, "<domain>,<range>,[<prefix>]", gettext_noop("Specify a domain and address range for synthesised names"), NULL },
 #ifdef HAVE_DNSSEC
   { LOPT_SEC_VALID, OPT_DNSSEC_VALID, NULL, gettext_noop("Activate DNSSEC validation"), NULL },
+  { LOPT_DNSKEY, ARG_DUP, "<domain>,<algo>,<key>", gettext_noop("Specify trust anchor DNSKEY"), NULL },
 #endif
 #ifdef OPTION6_PREFIX_CLASS 
   { LOPT_PREF_CLSS, ARG_DUP, "set:tag,<class>", gettext_noop("Specify DHCPv6 prefix class"), NULL },
@@ -3670,9 +3673,34 @@ static int one_opt(int option, char *arg, char *errstr, char *gen_err, int comma
        daemon->host_records_tail = new;
        break;
       }
-      
+
+#ifdef HAVE_DNSSEC
+    case LOPT_DNSKEY:
+      {
+       struct dnskey *new = opt_malloc(sizeof(struct dnskey));
+       char *key64, *algo;
+               if (!(comma = split(arg)) || !(algo = split(comma)) || !(key64 = split(algo)) ||
+             !atoi_check16(comma, &new->flags) || !atoi_check16(algo, &new->algo) ||
+             !(new->name = canonicalise_opt(arg)))
+         ret_err(_("bad DNSKEY"));
+       
+
+       /* Upper bound on length */
+       new->key = opt_malloc((3*strlen(key64)/4));
+       unhide_metas(key64);
+       if ((new->keylen = parse_base64(key64, new->key)) == -1)
+         ret_err(_("bad base64 in DNSKEY"));
+       
+       new->next = daemon->dnskeys;
+       daemon->dnskeys = new;
+
+       break;
+      }
+#endif
+               
     default:
-      ret_err(_("unsupported option (check that dnsmasq was compiled with DHCP/TFTP/DBus support)"));
+      ret_err(_("unsupported option (check that dnsmasq was compiled with DHCP/TFTP/DNSSEC/DBus support)"));
       
     }
   
index e5477829b095213dca83d3089b8e8ca076d8a822..a05060a1a25e4c174ac7542a640bbf239e269696 100644 (file)
@@ -577,10 +577,13 @@ static size_t add_pseudoheader(struct dns_header *header, size_t plen, unsigned
        return plen; /* Too big */
     }
   
-  PUTSHORT(optno, p);
-  PUTSHORT(optlen, p);
-  memcpy(p, opt, optlen);
-  p += optlen;  
+  if (optno != 0)
+    {
+      PUTSHORT(optno, p);
+      PUTSHORT(optlen, p);
+      memcpy(p, opt, optlen);
+      p += optlen;  
+    }
 
   PUTSHORT(p - datap, lenp);
   return p - (unsigned char *)header;
@@ -889,7 +892,7 @@ static int find_soa(struct dns_header *header, size_t qlen, char *name)
    expired and cleaned out that way. 
    Return 1 if we reject an address because it look like part of dns-rebinding attack. */
 int extract_addresses(struct dns_header *header, size_t qlen, char *name, time_t now, 
-                     char **ipsets, int is_sign, int check_rebind, int no_cache_dnssec)
+                     char **ipsets, int is_sign, int check_rebind, int no_cache_dnssec, int secure)
 {
   unsigned char *p, *p1, *endrr, *namep;
   int i, j, qtype, qclass, aqtype, aqclass, ardlen, res, searched_soa = 0;
@@ -919,6 +922,12 @@ int extract_addresses(struct dns_header *header, size_t qlen, char *name, time_t
       struct crec *cpp = NULL;
       int flags = RCODE(header) == NXDOMAIN ? F_NXDOMAIN : 0;
       unsigned long cttl = ULONG_MAX, attl;
+
+      if (RCODE(header) == NXDOMAIN)
+       flags |= F_NXDOMAIN;
+
+      if (secure)
+       flags |= F_DNSSECOK; 
       
       namep = p;
       if (!extract_name(header, qlen, &p, name, 1, 4))
@@ -1446,7 +1455,7 @@ size_t answer_request(struct dns_header *header, char *limit, size_t qlen,
   int dryrun = 0, sec_reqd = 0;
   int is_sign;
   struct crec *crecp;
-  int nxdomain = 0, auth = 1, trunc = 0;
+  int nxdomain = 0, auth = 1, trunc = 0, sec_data = 1;
   struct mx_srv_record *rec;
  
   /* If there is an RFC2671 pseudoheader then it will be overwritten by
@@ -1621,6 +1630,9 @@ size_t answer_request(struct dns_header *header, char *limit, size_t qlen,
                    if (qtype == T_ANY && !(crecp->flags & (F_HOSTS | F_DHCP)))
                      continue;
                    
+                   if (!(crecp->flags & F_DNSSECOK))
+                     sec_data = 0;
+                   
                    if (crecp->flags & F_NEG)
                      {
                        ans = 1;
@@ -1794,6 +1806,9 @@ size_t answer_request(struct dns_header *header, char *limit, size_t qlen,
                      if (qtype == T_ANY && !(crecp->flags & (F_HOSTS | F_DHCP | F_CONFIG)))
                        break;
                      
+                     if (!(crecp->flags & F_DNSSECOK))
+                       sec_data = 0;
+                     
                      if (crecp->flags & F_CNAME)
                        {
                          char *cname_target = cache_get_cname_target(crecp);
@@ -1868,6 +1883,9 @@ size_t answer_request(struct dns_header *header, char *limit, size_t qlen,
              if ((crecp = cache_find_by_name(NULL, name, now, F_CNAME)) &&
                  (qtype == T_CNAME || (crecp->flags & (F_HOSTS | F_DHCP | F_CONFIG))))
                {
+                 if (!(crecp->flags & F_DNSSECOK))
+                   sec_data = 0;
+                 
                  ans = 1;
                  if (!dryrun)
                    {
@@ -2046,7 +2064,13 @@ size_t answer_request(struct dns_header *header, char *limit, size_t qlen,
   /* truncation */
   if (trunc)
     header->hb3 |= HB3_TC;
-
+  
+  header->hb4 &= ~HB4_AD;
+  
+  if (option_bool(OPT_DNSSEC_VALID) || option_bool(OPT_DNSSEC_PROXY))
+    if (sec_data)
+      header->hb4 |= HB4_AD;
+  
   if (nxdomain)
     SET_RCODE(header, NXDOMAIN);
   else
index 096297d7b518229c2fe7ad91260d2ff19939eca0..c210add2b1b3d5caff9e5e474470ac55ca5b6a0a 100644 (file)
@@ -109,10 +109,10 @@ static int check_name(char *in)
   
   if (in[l-1] == '.')
     {
-      if (l == 1) return 0;
       in[l-1] = 0;
+      nowhite = 1;
     }
-  
+
   for (; (c = *in); in++)
     {
       if (c == '.')
@@ -482,6 +482,66 @@ int parse_hex(char *in, unsigned char *out, int maxlen,
   return i;
 }
 
+#ifdef HAVE_DNSSEC
+static int charval(char c)
+{
+  if (c >= 'A' && c <= 'Z')
+    return c - 'A';
+  
+  if (c >= 'a' && c <= 'z')
+    return c - 'a' + 26;
+
+  if (c >= '0' && c <= '9')
+    return c - '0' + 52;
+
+  if (c == '+')
+    return 62;
+  
+  if (c == '/')
+    return 63;
+
+  if (c == '=')
+    return -1;
+
+  return -2;
+}
+
+int parse_base64(char *in, char *out)
+{
+  char *p = out;
+  int i, val[4];
+
+  while (*in)
+    {
+      for (i = 0; i < 4; i++)
+       { 
+         while (*in == ' ') 
+           in++;
+         if (*in == 0)
+           return -1;
+         if ((val[i] = charval(*in++)) == -2)
+           return -1;
+       }
+          
+      while (*in == ' ') 
+       in++;
+      
+      if (val[1] == -1)
+       return -1; /* too much padding */
+      
+      *p++ = (val[0] << 2) | (val[1] >> 4);
+      
+      if (val[2] != -1)
+       *p++ = (val[1] << 4) | ( val[2] >> 2);
+
+      if (val[3] != -1)
+       *p++ = (val[2] << 6) | val[3];
+    }
+
+  return p - out;
+}
+#endif
+
 /* return 0 for no match, or (no matched octets) + 1 */
 int memcmp_masked(unsigned char *a, unsigned char *b, int len, unsigned int mask)
 {