Add --cache-rr to enable caching of arbitrary RR types.
authorSimon Kelley <simon@thekelleys.org.uk>
Thu, 23 Mar 2023 17:15:35 +0000 (17:15 +0000)
committerSimon Kelley <simon@thekelleys.org.uk>
Thu, 23 Mar 2023 17:15:35 +0000 (17:15 +0000)
src/blockdata.c
src/cache.c
src/dnsmasq.c
src/dnsmasq.h
src/dnssec.c
src/option.c
src/rfc1035.c
src/rrfilter.c
src/util.c

index 4c26155..56698c7 100644 (file)
@@ -19,7 +19,7 @@
 static struct blockdata *keyblock_free;
 static unsigned int blockdata_count, blockdata_hwm, blockdata_alloced;
 
-static void blockdata_expand(int n)
+static void add_blocks(int n)
 {
   struct blockdata *new = whine_malloc(n * sizeof(struct blockdata));
   
@@ -47,7 +47,7 @@ void blockdata_init(void)
 
   /* Note that daemon->cachesize is enforced to have non-zero size if OPT_DNSSEC_VALID is set */  
   if (option_bool(OPT_DNSSEC_VALID))
-    blockdata_expand(daemon->cachesize);
+    add_blocks(daemon->cachesize);
 }
 
 void blockdata_report(void)
@@ -58,50 +58,61 @@ void blockdata_report(void)
            blockdata_alloced * sizeof(struct blockdata));
 } 
 
+static struct blockdata *new_block(void)
+{
+  struct blockdata *block;
+
+  if (!keyblock_free)
+    add_blocks(50);
+  
+  if (keyblock_free)
+    {
+      block = keyblock_free;
+      keyblock_free = block->next;
+      blockdata_count++;
+      if (blockdata_hwm < blockdata_count)
+       blockdata_hwm = blockdata_count;
+      block->next = NULL;
+      return block;
+    }
+  
+  return NULL;
+}
+
 static struct blockdata *blockdata_alloc_real(int fd, char *data, size_t len)
 {
   struct blockdata *block, *ret = NULL;
   struct blockdata **prev = &ret;
   size_t blen;
 
-  while (len > 0)
+  do
     {
-      if (!keyblock_free)
-       blockdata_expand(50);
-      
-      if (keyblock_free)
-       {
-         block = keyblock_free;
-         keyblock_free = block->next;
-         blockdata_count++; 
-       }
-      else
+      if (!(block = new_block()))
        {
          /* failed to alloc, free partial chain */
          blockdata_free(ret);
          return NULL;
        }
-       
-      if (blockdata_hwm < blockdata_count)
-       blockdata_hwm = blockdata_count; 
-      
-      blen = len > KEYBLOCK_LEN ? KEYBLOCK_LEN : len;
-      if (data)
-       {
-         memcpy(block->key, data, blen);
-         data += blen;
-       }
-      else if (!read_write(fd, block->key, blen, 1))
+
+      if ((blen = len > KEYBLOCK_LEN ? KEYBLOCK_LEN : len) > 0)
        {
-         /* failed read free partial chain */
-         blockdata_free(ret);
-         return NULL;
+         if (data)
+           {
+             memcpy(block->key, data, blen);
+             data += blen;
+           }
+         else if (!read_write(fd, block->key, blen, 1))
+           {
+             /* failed read free partial chain */
+             blockdata_free(ret);
+             return NULL;
+           }
        }
+      
       len -= blen;
       *prev = block;
       prev = &block->next;
-      block->next = NULL;
-    }
+    } while (len != 0);
   
   return ret;
 }
@@ -111,6 +122,58 @@ struct blockdata *blockdata_alloc(char *data, size_t len)
   return blockdata_alloc_real(0, data, len);
 }
 
+/* Add data to the end of the block. 
+   newlen is length of new data, NOT total new length. 
+   Use blockdata_alloc(NULL, 0) to make empty block to add to. */
+int blockdata_expand(struct blockdata *block, size_t oldlen, char *data, size_t newlen)
+{
+  struct blockdata *b;
+  
+  /* find size of current final block */
+  for (b = block; oldlen > KEYBLOCK_LEN && b;  b = b->next, oldlen -= KEYBLOCK_LEN);
+
+  /* chain to short for length, something is broken */
+  if (oldlen > KEYBLOCK_LEN)
+    {
+      blockdata_free(block);
+      return 0;
+    }
+
+  while (1)
+    {
+      struct blockdata *new;
+      size_t blocksize = KEYBLOCK_LEN - oldlen;
+      size_t size = (newlen <= blocksize) ? newlen : blocksize;
+      
+      if (size != 0)
+       {
+         memcpy(&b->key[oldlen], data, size);
+         data += size;
+         newlen -= size;
+       }
+      
+      /* full blocks from now on. */
+      oldlen = 0;
+
+      if (newlen == 0)
+       break;
+
+      if ((new = new_block()))
+       {
+         b->next = new;
+         b = new;
+       }
+      else
+       {
+         /* failed to alloc, free partial chain */
+         blockdata_free(block);
+         return 0;
+       }
+    }
+
+  return 1;
+}
+
 void blockdata_free(struct blockdata *blocks)
 {
   struct blockdata *tmp;
index 6ae6688..64fc69d 100644 (file)
@@ -29,6 +29,7 @@ static void make_non_terminals(struct crec *source);
 static struct crec *really_insert(char *name, union all_addr *addr, unsigned short class,
                                  time_t now,  unsigned long ttl, unsigned int flags);
 static void dump_cache_entry(struct crec *cache, time_t now);
+static char *querystr(char *desc, unsigned short type);
 
 /* type->string mapping: this is also used by the name-hash function as a mixing table. */
 /* taken from https://www.iana.org/assignments/dns-parameters/dns-parameters.xhtml */
@@ -133,6 +134,17 @@ static void cache_link(struct crec *crecp);
 static void rehash(int size);
 static void cache_hash(struct crec *crecp);
 
+unsigned short rrtype(char *in)
+{
+  int i;
+  
+  for (i = 0; i < (sizeof(typestr)/sizeof(typestr[0])); i++)
+    if (strcasecmp(in, typestr[i].name) == 0)
+      return typestr[i].type;
+
+  return 0;
+}
+
 void next_uid(struct crec *crecp)
 {
   static unsigned int uid = 0;
@@ -265,6 +277,8 @@ static void cache_blockdata_free(struct crec *crecp)
     {
       if (crecp->flags & F_SRV)
        blockdata_free(crecp->addr.srv.target);
+      else if (crecp->flags & F_RR)
+       blockdata_free(crecp->addr.rr.rrdata);
 #ifdef HAVE_DNSSEC
       else if (crecp->flags & F_DNSKEY)
        blockdata_free(crecp->addr.key.keydata);
@@ -459,7 +473,8 @@ static struct crec *cache_scan_free(char *name, union all_addr *addr, unsigned s
            {
              /* Don't delete DNSSEC in favour of a CNAME, they can co-exist */
              if ((flags & crecp->flags & (F_IPV4 | F_IPV6 | F_SRV | F_NXDOMAIN)) || 
-                 (((crecp->flags | flags) & F_CNAME) && !(crecp->flags & (F_DNSKEY | F_DS))))
+                 (((crecp->flags | flags) & F_CNAME) && !(crecp->flags & (F_DNSKEY | F_DS))) ||
+                 ((crecp->flags & flags & F_RR) && addr->rr.rrtype == crecp->addr.rr.rrtype))
                {
                  if (crecp->flags & (F_HOSTS | F_DHCP | F_CONFIG))
                    return crecp;
@@ -776,7 +791,7 @@ void cache_end_insert(void)
              read_write(daemon->pipe_to_parent, (unsigned char *)&new_chain->ttd, sizeof(new_chain->ttd), 0);
              read_write(daemon->pipe_to_parent, (unsigned  char *)&flags, sizeof(flags), 0);
 
-             if (flags & (F_IPV4 | F_IPV6 | F_DNSKEY | F_DS | F_SRV))
+             if (flags & (F_IPV4 | F_IPV6 | F_DNSKEY | F_DS | F_SRV | F_RR))
                read_write(daemon->pipe_to_parent, (unsigned char *)&new_chain->addr, sizeof(new_chain->addr), 0);
              if (flags & F_SRV)
                {
@@ -784,6 +799,12 @@ void cache_end_insert(void)
                  if (!(flags & F_NEG))
                    blockdata_write(new_chain->addr.srv.target, new_chain->addr.srv.targetlen, daemon->pipe_to_parent);
                }
+             if (flags & F_RR)
+               {
+                 /* A negative RR entry is possible and has no data, obviously. */
+                 if (!(flags & F_NEG))
+                   blockdata_write(new_chain->addr.rr.rrdata, new_chain->addr.rr.datalen, daemon->pipe_to_parent);
+               }
 #ifdef HAVE_DNSSEC
              if (flags & F_DNSKEY)
                {
@@ -848,16 +869,18 @@ int cache_recv_insert(time_t now, int fd)
 
       ttl = difftime(ttd, now);
       
-      if (flags & (F_IPV4 | F_IPV6 | F_DNSKEY | F_DS | F_SRV))
+      if (flags & (F_IPV4 | F_IPV6 | F_DNSKEY | F_DS | F_SRV | F_RR))
        {
          unsigned short class = C_IN;
-
+         
          if (!read_write(fd, (unsigned char *)&addr, sizeof(addr), 1))
            return 0;
-
+         
          if ((flags & F_SRV) && !(flags & F_NEG) && !(addr.srv.target = blockdata_read(fd, addr.srv.targetlen)))
            return 0;
-       
+
+         if ((flags & F_RR) && !(flags & F_NEG) && !(addr.rr.rrdata = blockdata_read(fd, addr.rr.datalen)))
+           return 0;
 #ifdef HAVE_DNSSEC
           if (flags & F_DNSKEY)
             {
@@ -1587,7 +1610,7 @@ static void make_non_terminals(struct crec *source)
       if (!is_outdated_cname_pointer(crecp) &&
          (crecp->flags & F_FORWARD) &&
          (crecp->flags & type) &&
-         !(crecp->flags & (F_IPV4 | F_IPV6 | F_CNAME | F_SRV | F_DNSKEY | F_DS)) && 
+         !(crecp->flags & (F_IPV4 | F_IPV6 | F_CNAME | F_SRV | F_DNSKEY | F_DS | F_RR)) && 
          hostname_isequal(name, cache_get_name(crecp)))
        {
          *up = crecp->hash_next;
@@ -1644,7 +1667,7 @@ static void make_non_terminals(struct crec *source)
 
       if (crecp)
        {
-         crecp->flags = (source->flags | F_NAMEP) & ~(F_IPV4 | F_IPV6 | F_CNAME | F_SRV | F_DNSKEY | F_DS | F_REVERSE);
+         crecp->flags = (source->flags | F_NAMEP) & ~(F_IPV4 | F_IPV6 | F_CNAME | F_SRV | F_RR | F_DNSKEY | F_DS | F_REVERSE);
          if (!(crecp->flags & F_IMMORTAL))
            crecp->ttd = source->ttd;
          crecp->name.namep = name;
@@ -1792,6 +1815,8 @@ static void dump_cache_entry(struct crec *cache, time_t now)
       blockdata_retrieve(cache->addr.srv.target, targetlen, a + len);
       a[len + targetlen] = 0;          
     }
+  else if (cache->flags & F_RR)
+    sprintf(a, "%s", querystr(NULL, cache->addr.rr.rrtype));
 #ifdef HAVE_DNSSEC
   else if (cache->flags & F_DS)
     {
@@ -1820,6 +1845,8 @@ static void dump_cache_entry(struct crec *cache, time_t now)
     t = "C";
   else if (cache->flags & F_SRV)
     t = "V";
+  else if (cache->flags & F_RR)
+    t = "T";
 #ifdef HAVE_DNSSEC
   else if (cache->flags & F_DS)
     t = "S";
@@ -2078,6 +2105,8 @@ void log_query(unsigned int flags, char *name, union all_addr *addr, char *arg,
              sprintf(portstring, "#%u", type);
            }
        }
+      else if (flags & F_RR)
+       dest = querystr(NULL, addr->rr.rrtype);
       else
        dest = arg;
     }
index bd3dcf5..6fb9571 100644 (file)
@@ -125,17 +125,11 @@ int main (int argc, char **argv)
     {
       /* Note that both /000 and '.' are allowed within labels. These get
         represented in presentation format using NAME_ESCAPE as an escape
-        character when in DNSSEC mode. 
-        In theory, if all the characters in a name were /000 or
+        character. In theory, if all the characters in a name were /000 or
         '.' or NAME_ESCAPE then all would have to be escaped, so the 
-        presentation format would be twice as long as the spec.
-
-        daemon->namebuff was previously allocated by the option-reading
-        code before we knew if we're in DNSSEC mode, so reallocate here. */
-      free(daemon->namebuff);
-      daemon->namebuff = safe_malloc(MAXDNAME * 2);
-      daemon->keyname = safe_malloc(MAXDNAME * 2);
-      daemon->workspacename = safe_malloc(MAXDNAME * 2);
+        presentation format would be twice as long as the spec. */
+      daemon->keyname = safe_malloc((MAXDNAME * 2) + 1);
+      daemon->workspacename = safe_malloc((MAXDNAME * 2) + 1);
       /* one char flag per possible RR in answer section (may get extended). */
       daemon->rr_status_sz = 64;
       daemon->rr_status = safe_malloc(sizeof(*daemon->rr_status) * daemon->rr_status_sz);
index 7d26460..376c630 100644 (file)
@@ -282,7 +282,8 @@ struct event_desc {
 #define OPT_STRIP_MAC      70
 #define OPT_NORR           71
 #define OPT_NO_IDENT       72
-#define OPT_LAST           73
+#define OPT_CACHE_RR       73
+#define OPT_LAST           74
 
 #define OPTION_BITS (sizeof(unsigned int)*8)
 #define OPTION_SIZE ( (OPT_LAST/OPTION_BITS)+((OPT_LAST%OPTION_BITS)!=0) )
@@ -337,7 +338,7 @@ union all_addr {
   /* for arbitrary RR record. */
   struct {
     struct blockdata *rrdata;
-    u16 rrtype;
+    unsigned short rrtype, datalen;
   } rr;
 };
 
@@ -663,6 +664,11 @@ struct iname {
   struct iname *next;
 };
 
+struct rrlist {
+  unsigned short rr;
+  struct rrlist *next;
+};
+
 /* subnet parameters from command line */
 struct mysubnet {
   union mysockaddr addr;
@@ -1128,6 +1134,7 @@ extern struct daemon {
   struct naptr *naptr;
   struct txt_record *txt, *rr;
   struct ptr_record *ptr;
+  struct rrlist *cache_rr, filter_rr;
   struct host_record *host_records, *host_records_tail;
   struct cname *cnames;
   struct auth_zone *auth_zones;
@@ -1309,6 +1316,7 @@ struct server_details {
 
 /* cache.c */
 void cache_init(void);
+unsigned short rrtype(char *in);
 void next_uid(struct crec *crecp);
 void log_query(unsigned int flags, char *name, union all_addr *addr, char *arg, unsigned short type); 
 char *record_source(unsigned int index);
@@ -1342,6 +1350,8 @@ int read_hostsfile(char *filename, unsigned int index, int cache_size,
 void blockdata_init(void);
 void blockdata_report(void);
 struct blockdata *blockdata_alloc(char *data, size_t len);
+int blockdata_expand(struct blockdata *block, size_t oldlen,
+                    char *data, size_t newlen);
 void *blockdata_retrieve(struct blockdata *block, size_t len, void *data);
 struct blockdata *blockdata_read(int fd, size_t len);
 void blockdata_write(struct blockdata *block, size_t len, int fd);
@@ -1423,6 +1433,7 @@ void rand_init(void);
 unsigned short rand16(void);
 u32 rand32(void);
 u64 rand64(void);
+int rr_on_list(struct rrlist *list, unsigned short rr);
 int legal_hostname(char *name);
 char *canonicalise(char *in, int *nomem);
 unsigned char *do_rfc1035_name(unsigned char *p, char *sval, char *limit);
@@ -1817,13 +1828,16 @@ int do_poll(int timeout);
 
 /* rrfilter.c */
 size_t rrfilter(struct dns_header *header, size_t *plen, int mode);
-u16 *rrfilter_desc(int type);
+short *rrfilter_desc(int type);
 int expand_workspace(unsigned char ***wkspc, int *szp, int new);
+int to_wire(char *name);
+void from_wire(char *name);
 /* modes. */
 #define RRFILTER_EDNS0   0
 #define RRFILTER_DNSSEC  1
 #define RRFILTER_A       2
 #define RRFILTER_AAAA    3
+
 /* edns0.c */
 unsigned char *find_pseudoheader(struct dns_header *header, size_t plen,
                                   size_t *len, unsigned char **p, int *is_sign, int *is_last);
index 219ba9a..aa196eb 100644 (file)
 #define SERIAL_LT       -1
 #define SERIAL_GT        1
 
-/* 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. 
-
-   Note that both /000 and '.' are allowed within labels. These get
-   represented in presentation format using NAME_ESCAPE as an escape
-   character. In theory, if all the characters in a name were /000 or
-   '.' or NAME_ESCAPE then all would have to be escaped, so the 
-   presentation format would be twice as long as the spec (1024). 
-   The buffers are all declared as 2049 (allowing for the trailing zero) 
-   for this reason.
-*/
-static int to_wire(char *name)
-{
-  unsigned char *l, *p, *q, 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';
-       else if (*p == NAME_ESCAPE)
-         {
-           for (q = p; *q; q++)
-             *q = *(q+1);
-           (*p)--;
-         }
-      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;
-}
-
-/* Note: no compression  allowed in input. */
-static void from_wire(char *name)
-{
-  unsigned char *l, *p, *last;
-  int len;
-  
-  for (last = (unsigned char *)name; *last != 0; last += *last+1);
-  
-  for (l = (unsigned char *)name; *l != 0; l += len+1)
-    {
-      len = *l;
-      memmove(l, l+1, len);
-      for (p = l; p < l + len; p++)
-       if (*p == '.' || *p == 0 || *p == NAME_ESCAPE)
-         {
-           memmove(p+1, p, 1 + last - p);
-           len++;
-           *p++ = NAME_ESCAPE; 
-           (*p)++;
-         }
-       
-      l[len] = '.';
-    }
-
-  if ((char *)l != name)
-    *(l-1) = 0;
-}
-
 /* Input in presentation format */
 static int count_labels(char *name)
 {
@@ -225,7 +150,7 @@ static int is_check_date(unsigned long curtime)
    On returning 0, the end has been reached.
 */
 struct rdata_state {
-  u16 *desc;
+  short *desc;
   size_t c;
   unsigned char *end, *ip, *op;
   char *buff;
@@ -246,7 +171,7 @@ static int get_rdata(struct dns_header *header, size_t plen, struct rdata_state
     {
       d = *(state->desc);
       
-      if (d == (u16)-1)
+      if (d == -1)
        {
          /* all the bytes to the end. */
          if ((state->c = state->end - state->ip) != 0)
@@ -294,7 +219,7 @@ static int get_rdata(struct dns_header *header, size_t plen, struct rdata_state
 
 /* Bubble sort the RRset into the canonical order. */
 
-static int sort_rrset(struct dns_header *header, size_t plen, u16 *rr_desc, int rrsetidx, 
+static int sort_rrset(struct dns_header *header, size_t plen, short *rr_desc, int rrsetidx, 
                      unsigned char **rrset, char *buff1, char *buff2)
 {
   int swap, i, j;
@@ -331,7 +256,7 @@ static int sort_rrset(struct dns_header *header, size_t plen, u16 *rr_desc, int
             is the identity function and we can compare
             the RRs directly. If not we compare the 
             canonicalised RRs one byte at a time. */
-         if (*rr_desc == (u16)-1)        
+         if (*rr_desc == -1)     
            {
              int rdmin = rdlen1 > rdlen2 ? rdlen2 : rdlen1;
              int cmp = memcmp(state1.ip, state2.ip, rdmin);
@@ -524,7 +449,7 @@ static int validate_rrset(time_t now, struct dns_header *header, size_t plen, in
   unsigned char *p;
   int rdlen, j, name_labels, algo, labels, key_tag;
   struct crec *crecp = NULL;
-  u16 *rr_desc = rrfilter_desc(type);
+  short *rr_desc = rrfilter_desc(type);
   u32 sig_expiration, sig_inception;
   int failflags = DNSSEC_FAIL_NOSIG | DNSSEC_FAIL_NYV | DNSSEC_FAIL_EXP | DNSSEC_FAIL_NOKEYSUP;
   
@@ -671,7 +596,7 @@ static int validate_rrset(time_t now, struct dns_header *header, size_t plen, in
             
             If canonicalisation is not needed, a simple insertion into the hash works.
          */
-         if (*rr_desc == (u16)-1)
+         if (*rr_desc == -1)
            {
              len = htons(rdlen);
              hash->update(ctx, 2, (unsigned char *)&len);
index 2e208ba..87a321e 100644 (file)
@@ -186,6 +186,7 @@ struct myoption {
 #define LOPT_STALE_CACHE   377
 #define LOPT_NORR          378
 #define LOPT_NO_IDENT      379
+#define LOPT_CACHE_RR      380
 
 #ifdef HAVE_GETOPT_LONG
 static const struct option opts[] =  
@@ -239,6 +240,7 @@ static const struct myoption opts[] =
     { "local-ttl", 1, 0, 'T' },
     { "no-negcache", 0, 0, 'N' },
     { "no-round-robin", 0, 0, LOPT_NORR },
+    { "cache-rr", 1, 0, LOPT_CACHE_RR },
     { "addn-hosts", 1, 0, 'H' },
     { "hostsdir", 1, 0, LOPT_HOST_INOTIFY },
     { "query-port", 1, 0, 'Q' },
@@ -566,13 +568,14 @@ static struct {
   { LOPT_DHCPTTL, ARG_ONE, "<ttl>", gettext_noop("Set TTL in DNS responses with DHCP-derived addresses."), NULL }, 
   { LOPT_REPLY_DELAY, ARG_ONE, "<integer>", gettext_noop("Delay DHCP replies for at least number of seconds."), NULL },
   { LOPT_RAPID_COMMIT, OPT_RAPID_COMMIT, NULL, gettext_noop("Enables DHCPv4 Rapid Commit option."), NULL },
-  { LOPT_DUMPFILE, ARG_ONE, "<path>", gettext_noop("Path to debug packet dump file"), NULL },
-  { LOPT_DUMPMASK, ARG_ONE, "<hex>", gettext_noop("Mask which packets to dump"), NULL },
+  { LOPT_DUMPFILE, ARG_ONE, "<path>", gettext_noop("Path to debug packet dump file."), NULL },
+  { LOPT_DUMPMASK, ARG_ONE, "<hex>", gettext_noop("Mask which packets to dump."), NULL },
   { LOPT_SCRIPT_TIME, OPT_LEASE_RENEW, NULL, gettext_noop("Call dhcp-script when lease expiry changes."), NULL },
   { LOPT_UMBRELLA, ARG_ONE, "[=<optspec>]", gettext_noop("Send Cisco Umbrella identifiers including remote IP."), NULL },
   { LOPT_QUIET_TFTP, OPT_QUIET_TFTP, NULL, gettext_noop("Do not log routine TFTP."), NULL },
   { LOPT_NORR, OPT_NORR, NULL, gettext_noop("Suppress round-robin ordering of DNS records."), NULL },
   { LOPT_NO_IDENT, OPT_NO_IDENT, NULL, gettext_noop("Do not add CHAOS TXT records."), NULL },
+  { LOPT_CACHE_RR, ARG_DUP, "RRtype", gettext_noop("Cache this DNS resource record type."), NULL },
   { 0, 0, NULL, NULL, NULL }
 }; 
 
@@ -3465,6 +3468,27 @@ static int one_opt(int option, char *arg, char *errstr, char *gen_err, int comma
            }
        }
       break;
+
+    case LOPT_CACHE_RR:
+      while (1) {
+       int type;
+       struct rrlist *new;
+       
+       comma = split(arg);
+       if (!atoi_check(arg, &type) && (type = rrtype(arg)) == 0)
+         ret_err(_("bad RR type"));
+
+       new = opt_malloc(sizeof(struct rrlist));
+       new->rr = type;
+
+       new->next = daemon->cache_rr;
+       daemon->cache_rr = new;
+       
+       if (!comma) break;
+       arg = comma;
+      }
+      break;
+      
             
 #ifdef HAVE_DHCP
     case 'X': /* --dhcp-lease-max */
@@ -5733,10 +5757,15 @@ void read_opts(int argc, char **argv, char *compile_opts)
 {
   size_t argbuf_size = MAXDNAME;
   char *argbuf = opt_malloc(argbuf_size);
-  char *buff = opt_malloc(MAXDNAME);
+  /* Note that both /000 and '.' are allowed within labels. These get
+     represented in presentation format using NAME_ESCAPE as an escape
+     character. In theory, if all the characters in a name were /000 or
+     '.' or NAME_ESCAPE then all would have to be escaped, so the 
+     presentation format would be twice as long as the spec. */
+  char *buff = opt_malloc((MAXDNAME * 2) + 1);
   int option, testmode = 0;
   char *arg, *conffile = NULL;
-      
+  
   opterr = 0;
 
   daemon = opt_malloc(sizeof(struct daemon));
index ea21ffa..28579c6 100644 (file)
@@ -89,23 +89,14 @@ int extract_name(struct dns_header *header, size_t plen, unsigned char **pp,
            if (isExtract)
              {
                unsigned char c = *p;
-#ifdef HAVE_DNSSEC
-               if (option_bool(OPT_DNSSEC_VALID))
+
+               if (c == 0 || c == '.' || c == NAME_ESCAPE)
                  {
-                   if (c == 0 || c == '.' || c == NAME_ESCAPE)
-                     {
-                       *cp++ = NAME_ESCAPE;
-                       *cp++ = c+1;
-                     }
-                   else
-                     *cp++ = c; 
+                   *cp++ = NAME_ESCAPE;
+                   *cp++ = c+1;
                  }
                else
-#endif
-               if (c != 0 && c != '.')
-                 *cp++ = c;
-               else
-                 return 0;
+                 *cp++ = c; 
              }
            else 
              {
@@ -118,10 +109,9 @@ int extract_name(struct dns_header *header, size_t plen, unsigned char **pp,
                    cp++;
                    if (c1 >= 'A' && c1 <= 'Z')
                      c1 += 'a' - 'A';
-#ifdef HAVE_DNSSEC
-                   if (option_bool(OPT_DNSSEC_VALID) && c1 == NAME_ESCAPE)
+
+                   if (c1 == NAME_ESCAPE)
                      c1 = (*cp++)-1;
-#endif
                    
                    if (c2 >= 'A' && c2 <= 'Z')
                      c2 += 'a' - 'A';
@@ -502,12 +492,10 @@ static int find_soa(struct dns_header *header, size_t qlen, int *doctored)
 }
 
 /* Print TXT reply to log */
-static int print_txt(struct dns_header *header, const size_t qlen, char *name,
-                    unsigned char *p, const int ardlen, int secflag)
+static int log_txt(char *name, unsigned char *p, const int ardlen, int secflag)
 {
   unsigned char *p1 = p;
-  if (!CHECK_LEN(header, p1, qlen, ardlen))
-    return 0;
   /* Loop over TXT payload */
   while ((p1 - p) < ardlen)
     {
@@ -526,7 +514,7 @@ static int print_txt(struct dns_header *header, const size_t qlen, char *name,
        }
 
       *p3 = 0;
-      log_query(secflag | F_FORWARD | F_UPSTREAM, name, NULL, (char*)p1, 0);
+      log_query(secflag | F_FORWARD, name, NULL, (char*)p1, 0);
       /* restore */
       memmove(p1 + 1, p1, i);
       *p1 = len;
@@ -719,6 +707,8 @@ int extract_addresses(struct dns_header *header, size_t qlen, char *name, time_t
        }
       else if (qtype == T_SRV)
        flags |= F_SRV;
+      else if (qtype != T_CNAME && rr_on_list(daemon->cache_rr, qtype))
+       flags |= F_RR;
       else
        insert = 0; /* NOTE: do not cache data from CNAME queries. */
       
@@ -804,7 +794,7 @@ int extract_addresses(struct dns_header *header, size_t qlen, char *name, time_t
 #ifdef HAVE_DNSSEC
              if (!option_bool(OPT_DNSSEC_VALID) || aqtype != T_RRSIG)
 #endif
-               log_query(secflag | F_FORWARD | F_UPSTREAM, name, NULL, NULL, aqtype);
+               log_query(secflag | F_FORWARD | F_UPSTREAM | F_RRNAME, name, NULL, NULL, aqtype);
            }
          else if (!(flags & F_NXDOMAIN))
            {
@@ -829,6 +819,64 @@ int extract_addresses(struct dns_header *header, size_t qlen, char *name, time_t
                  if (!extract_name(header, qlen, &tmp, name, 1, 0))
                    return 2;
                }
+             else if (flags & F_RR)
+               {
+                 short desc, *rrdesc = rrfilter_desc(aqtype);
+                 unsigned char *tmp = namep;
+                 
+                 if (!CHECK_LEN(header, p1, qlen, ardlen))
+                   return 2; /* bad packet */
+                 addr.rr.rrtype = aqtype;
+                 addr.rr.datalen = 0;
+
+                 /* The RR data may include names, and those names may include
+                    compression, which will be rendered meaningless when
+                    copied into another packet. 
+                    Here we go through a description of the packet type to
+                    find the names, and extract them to a c-string and then
+                    re-encode them to standalone DNS format without compression. */
+                 if (!(addr.rr.rrdata = blockdata_alloc(NULL, 0)))
+                   return 0;
+                 do
+                   {
+                     desc = *rrdesc++;
+                     
+                     if (desc == -1)
+                       {
+                         /* Copy the rest of the RR and end. */
+                         if (!blockdata_expand(addr.rr.rrdata, addr.rr.datalen, (char *)p1, endrr - p1))
+                           return 0;
+                         addr.rr.datalen += endrr - p1;
+                       }
+                     else if (desc == 0)
+                       {
+                         /* Name, extract it then re-encode. */
+                         int len;
+
+                         if (!extract_name(header, qlen, &p1, name, 1, 0))
+                           return 2;
+
+                         len = to_wire(name);
+                         if (!blockdata_expand(addr.rr.rrdata, addr.rr.datalen, name, len))
+                           return 0;
+                         addr.rr.datalen += len;
+                       }
+                     else
+                       {
+                         /* desc is length of a block of data to be used as-is */
+                         if (desc > endrr - p1)
+                           desc = endrr - p1;
+                         if (!blockdata_expand(addr.rr.rrdata, addr.rr.datalen, (char *)p1, desc))
+                           return 0;
+                         addr.rr.datalen += desc;
+                         p1 += desc;
+                       }
+                   } while (desc != -1);
+
+                 /* we overwrote the original name, so get it back here. */
+                 if (!extract_name(header, qlen, &tmp, name, 1, 0))
+                   return 2;
+               } 
              else if (flags & (F_IPV4 | F_IPV6))
                {
                  /* copy address into aligned storage */
@@ -876,8 +924,10 @@ int extract_addresses(struct dns_header *header, size_t qlen, char *name, time_t
              
              if (aqtype == T_TXT)
                {
-                 if (!print_txt(header, qlen, name, p1, ardlen, secflag))
-                   return 2;
+                  if (!CHECK_LEN(header, p1, qlen, ardlen))
+                    return 2;
+                  
+                  log_txt(name, p1, ardlen, secflag | F_UPSTREAM);
                }
              else
                {
@@ -903,7 +953,7 @@ int extract_addresses(struct dns_header *header, size_t qlen, char *name, time_t
        {
          if (flags & F_NXDOMAIN)
            {
-             flags &= ~(F_IPV4 | F_IPV6 | F_SRV);
+             flags &= ~(F_IPV4 | F_IPV6 | F_SRV | F_RR);
              
              /* Can store NXDOMAIN reply for any qtype. */
              insert = 1;
@@ -924,7 +974,10 @@ int extract_addresses(struct dns_header *header, size_t qlen, char *name, time_t
              if (ttl == 0)
                ttl = cttl;
              
-             newc = cache_insert(name, NULL, C_IN, now, ttl, F_FORWARD | F_NEG | flags | (secure ? F_DNSSECOK : 0));   
+             if (flags & F_RR)
+               addr.rr.rrtype = qtype;
+
+             newc = cache_insert(name, &addr, C_IN, now, ttl, F_FORWARD | F_NEG | flags | (secure ? F_DNSSECOK : 0));  
              if (newc && cpp)
                {
                  next_uid(newc);
@@ -2044,7 +2097,7 @@ size_t answer_request(struct dns_header *header, char *limit, size_t qlen,
              if (!found)
                {
                  if ((crecp = cache_find_by_name(NULL, name, now, F_SRV | F_NXDOMAIN | (dryrun ? F_NO_RR : 0))) &&
-                     rd_bit && (!do_bit || (option_bool(OPT_DNSSEC_VALID) && !(crecp->flags & F_DNSSECOK))))
+                     rd_bit && (!do_bit || cache_validated(crecp)))
                    do
                      {
                        int stale_flag = 0;
@@ -2125,8 +2178,57 @@ size_t answer_request(struct dns_header *header, char *limit, size_t qlen,
              if (!dryrun)
                log_query(F_CONFIG | F_NEG, name, &addr, NULL, 0);
            }
-       }
 
+         if (!ans && qtype != T_ANY)
+           {
+              if ((crecp = cache_find_by_name(NULL, name, now, F_RR | F_NXDOMAIN | (dryrun ? F_NO_RR : 0))) &&
+                  rd_bit && (!do_bit || cache_validated(crecp)))
+                do
+                  {
+                    int stale_flag = 0;
+
+                    if (crecp->addr.rr.rrtype == qtype)
+                      {
+                        if (crec_isstale(crecp, now))
+                          {
+                            if (stale)
+                              *stale = 1;
+                            
+                            stale_flag = F_STALE;
+                          }
+                        
+                        if (!(crecp->flags & F_DNSSECOK))
+                          sec_data = 0;
+                        
+                        auth = 0;
+                        ans = 1;
+                        
+                        if (!dryrun)
+                          {
+                            char *rrdata = NULL;
+
+                            if (!(crecp->flags & F_NEG))
+                              {
+                                rrdata = blockdata_retrieve(crecp->addr.rr.rrdata, crecp->addr.rr.datalen, NULL);
+                            
+                                if (add_resource_record(header, limit, &trunc, nameoffset, &ansp, 
+                                                        crec_ttl(crecp, now), NULL, qtype, C_IN, "t",
+                                                        crecp->addr.rr.datalen, rrdata))
+                                  anscount++;
+                              }
+                            
+                            /* log after cache insertion as log_txt mangles rrdata */
+                            if (qtype == T_TXT && !(crecp->flags & F_NEG))
+                              log_txt(name, (unsigned char *)rrdata, crecp->addr.rr.datalen, crecp->flags & F_DNSSECOK);
+                            else
+                              log_query(stale_flag | crecp->flags, name, &crecp->addr, NULL, 0);
+                          }
+                      }
+                  } while ((crecp = cache_find_by_name(crecp, name, now, F_RR)));
+           }
+       }
+      
+      
       if (!ans)
        {
          /* We may know that the domain doesn't exist for any RRtype. */
index 3a5547a..e4c56cb 100644 (file)
@@ -136,9 +136,9 @@ static int check_rrs(unsigned char *p, struct dns_header *header, size_t plen, i
          
          if (class == C_IN)
            {
-             u16 *d;
+             short *d;
  
-             for (pp = p, d = rrfilter_desc(type); *d != (u16)-1; d++)
+             for (pp = p, d = rrfilter_desc(type); *d != -1; d++)
                {
                  if (*d != 0)
                    pp += *d;
@@ -285,7 +285,7 @@ size_t rrfilter(struct dns_header *header, size_t *plen, int mode)
 }
 
 /* This is used in the DNSSEC code too, hence it's exported */
-u16 *rrfilter_desc(int type)
+short *rrfilter_desc(int type)
 {
   /* List of RRtypes which include domains in the data.
      0 -> domain
@@ -296,7 +296,7 @@ u16 *rrfilter_desc(int type)
      anything which needs no mangling.
   */
   
-  static u16 rr_desc[] = 
+  static short rr_desc[] = 
     { 
       T_NS, 0, -1, 
       T_MD, 0, -1,
@@ -321,10 +321,10 @@ u16 *rrfilter_desc(int type)
       0, -1 /* wildcard/catchall */
     }; 
   
-  u16 *p = rr_desc;
+  short *p = rr_desc;
   
   while (*p != type && *p != 0)
-    while (*p++ != (u16)-1);
+    while (*p++ != -1);
 
   return p+1;
 }
@@ -352,3 +352,78 @@ int expand_workspace(unsigned char ***wkspc, int *szp, int new)
 
   return 1;
 }
+
+/* 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. 
+
+   Note that both /000 and '.' are allowed within labels. These get
+   represented in presentation format using NAME_ESCAPE as an escape
+   character. In theory, if all the characters in a name were /000 or
+   '.' or NAME_ESCAPE then all would have to be escaped, so the 
+   presentation format would be twice as long as the spec (1024). 
+   The buffers are all declared as 2049 (allowing for the trailing zero) 
+   for this reason.
+*/
+int to_wire(char *name)
+{
+  unsigned char *l, *p, *q, 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';
+       else if (*p == NAME_ESCAPE)
+         {
+           for (q = p; *q; q++)
+             *q = *(q+1);
+           (*p)--;
+         }
+      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;
+}
+
+/* Note: no compression  allowed in input. */
+void from_wire(char *name)
+{
+  unsigned char *l, *p, *last;
+  int len;
+  
+  for (last = (unsigned char *)name; *last != 0; last += *last+1);
+  
+  for (l = (unsigned char *)name; *l != 0; l += len+1)
+    {
+      len = *l;
+      memmove(l, l+1, len);
+      for (p = l; p < l + len; p++)
+       if (*p == '.' || *p == 0 || *p == NAME_ESCAPE)
+         {
+           memmove(p+1, p, 1 + last - p);
+           len++;
+           *p++ = NAME_ESCAPE; 
+           (*p)++;
+         }
+       
+      l[len] = '.';
+    }
+
+  if ((char *)l != name)
+    *(l-1) = 0;
+}
index e0ce67d..073d7ad 100644 (file)
@@ -115,6 +115,19 @@ u64 rand64(void)
   return (u64)out[outleft+1] + (((u64)out[outleft]) << 32);
 }
 
+int rr_on_list(struct rrlist *list, unsigned short rr)
+{
+  while (list)
+    {
+      if (list->rr == rr)
+       return 1;
+
+      list = list->next;
+    }
+
+  return 0;
+}
+
 /* returns 1 if name is OK and ascii printable
  * returns 2 if name should be processed by IDN */
 static int check_name(char *in)
@@ -280,11 +293,9 @@ unsigned char *do_rfc1035_name(unsigned char *p, char *sval, char *limit)
           if (limit && p + 1 > (unsigned char*)limit)
             return NULL;
 
-#ifdef HAVE_DNSSEC
-         if (option_bool(OPT_DNSSEC_VALID) && *sval == NAME_ESCAPE)
+         if (*sval == NAME_ESCAPE)
            *p++ = (*(++sval))-1;
          else
-#endif         
            *p++ = *sval;
        }