]> git.ipfire.org Git - people/ms/dnsmasq.git/blobdiff - src/cache.c
Fix new poll() code for helper pipe. Removed CPU-spin.
[people/ms/dnsmasq.git] / src / cache.c
index 43a7ce9a6b63a99b87c31d90324bb57ea94bb96d..178d654ca92e97dd4199face425eba34f7546675 100644 (file)
@@ -1,4 +1,4 @@
-/* dnsmasq is Copyright (c) 2000-2013 Simon Kelley
+/* dnsmasq is Copyright (c) 2000-2015 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
@@ -24,10 +24,6 @@ static struct crec *new_chain = NULL;
 static int cache_inserted = 0, cache_live_freed = 0, insert_error;
 static union bigname *big_free = NULL;
 static int bignames_left, hash_size;
-static int uid = 1;
-#ifdef HAVE_DNSSEC
-static struct keydata *keyblock_free = NULL;
-#endif
 
 /* type->string mapping: this is also used by the name-hash function as a mixing table. */
 static const struct {
@@ -56,7 +52,11 @@ static const struct {
   { 38,  "A6" },
   { 39,  "DNAME" },
   { 41,  "OPT" },
+  { 43,  "DS" },
+  { 46,  "RRSIG" },
+  { 47,  "NSEC" },
   { 48,  "DNSKEY" },
+  { 50,  "NSEC3" },
   { 249, "TKEY" },
   { 250, "TSIG" },
   { 251, "IXFR" },
@@ -72,6 +72,19 @@ static void cache_link(struct crec *crecp);
 static void rehash(int size);
 static void cache_hash(struct crec *crecp);
 
+static unsigned int next_uid(void)
+{
+  static unsigned int uid = 0;
+
+  uid++;
+  
+  /* uid == 0 used to indicate CNAME to interface name. */
+  if (uid == SRC_INTERFACE)
+    uid++;
+  
+  return uid;
+}
+
 void cache_init(void)
 {
   struct crec *crecp;
@@ -87,7 +100,7 @@ void cache_init(void)
        {
          cache_link(crecp);
          crecp->flags = 0;
-         crecp->uid = uid++;
+         crecp->uid = next_uid();
        }
     }
   
@@ -171,15 +184,27 @@ static void cache_hash(struct crec *crecp)
   crecp->hash_next = *up;
   *up = crecp;
 }
+
+#ifdef HAVE_DNSSEC
+static void cache_blockdata_free(struct crec *crecp)
+{
+  if (crecp->flags & F_DNSKEY)
+    {
+      if (crecp->flags & F_DS)
+       blockdata_free(crecp->addr.sig.keydata);
+      else
+       blockdata_free(crecp->addr.key.keydata);
+    }
+  else if ((crecp->flags & F_DS) && !(crecp->flags & F_NEG))
+    blockdata_free(crecp->addr.ds.keydata);
+}
+#endif
+
 static void cache_free(struct crec *crecp)
 {
   crecp->flags &= ~F_FORWARD;
   crecp->flags &= ~F_REVERSE;
-  crecp->uid = uid++; /* invalidate CNAMES pointing to this. */
-
-  if (uid == -1)
-    uid++;
+  crecp->uid = next_uid(); /* invalidate CNAMES pointing to this. */
 
   if (cache_tail)
     cache_tail->next = crecp;
@@ -196,9 +221,9 @@ static void cache_free(struct crec *crecp)
       big_free = crecp->name.bname;
       crecp->flags &= ~F_BIGNAME;
     }
+
 #ifdef HAVE_DNSSEC
-  else if (crecp->flags & (F_DNSKEY | F_DS))
-    keydata_free(crecp->addr.key.keydata);
+  cache_blockdata_free(crecp);
 #endif
 }    
 
@@ -240,7 +265,7 @@ char *cache_get_name(struct crec *crecp)
 
 char *cache_get_cname_target(struct crec *crecp)
 {
-  if (crecp->addr.cname.uid != -1)
+  if (crecp->addr.cname.uid != SRC_INTERFACE)
     return cache_get_name(crecp->addr.cname.target.cache);
 
   return crecp->addr.cname.target.int_name->name;
@@ -273,7 +298,7 @@ struct crec *cache_enumerate(int init)
 
 static int is_outdated_cname_pointer(struct crec *crecp)
 {
-  if (!(crecp->flags & F_CNAME) || crecp->addr.cname.uid == -1)
+  if (!(crecp->flags & F_CNAME) || crecp->addr.cname.uid == SRC_INTERFACE)
     return 0;
   
   /* NB. record may be reused as DS or DNSKEY, where uid is 
@@ -297,7 +322,7 @@ static int is_expired(time_t now, struct crec *crecp)
   return 1;
 }
 
-static int cache_scan_free(char *name, struct all_addr *addr, time_t now, unsigned short flags)
+static struct crec *cache_scan_free(char *name, struct all_addr *addr, time_t now, unsigned short flags)
 {
   /* Scan and remove old entries.
      If (flags & F_FORWARD) then remove any forward entries for name and any expired
@@ -306,8 +331,8 @@ static int cache_scan_free(char *name, struct all_addr *addr, time_t now, unsign
      entries in the whole cache.
      If (flags == 0) remove any expired entries in the whole cache. 
 
-     In the flags & F_FORWARD case, the return code is valid, and returns zero if the
-     name exists in the cache as a HOSTS or DHCP entry (these are never deleted)
+     In the flags & F_FORWARD case, the return code is valid, and returns a non-NULL pointer
+     to a cache entry if the name exists in the cache as a HOSTS or DHCP entry (these are never deleted)
 
      We take advantage of the fact that hash chains have stuff in the order <reverse>,<other>,<immortal>
      so that when we hit an entry which isn't reverse and is immortal, we're done. */
@@ -317,27 +342,52 @@ static int cache_scan_free(char *name, struct all_addr *addr, time_t now, unsign
   if (flags & F_FORWARD)
     {
       for (up = hash_bucket(name), crecp = *up; crecp; crecp = crecp->hash_next)
-       if (is_expired(now, crecp) || is_outdated_cname_pointer(crecp))
-         { 
-           *up = crecp->hash_next;
-           if (!(crecp->flags & (F_HOSTS | F_DHCP | F_CONFIG)))
-             {
-               cache_unlink(crecp);
-               cache_free(crecp);
-             }
-         } 
-       else if ((crecp->flags & F_FORWARD) && 
-                ((flags & crecp->flags & F_TYPE) || ((crecp->flags | flags) & F_CNAME)) &&
-                hostname_isequal(cache_get_name(crecp), name))
-         {
-           if (crecp->flags & (F_HOSTS | F_DHCP | F_CONFIG))
-             return 0;
-           *up = crecp->hash_next;
-           cache_unlink(crecp);
-           cache_free(crecp);
-         }
-       else
+       {
+         if (is_expired(now, crecp) || is_outdated_cname_pointer(crecp))
+           { 
+             *up = crecp->hash_next;
+             if (!(crecp->flags & (F_HOSTS | F_DHCP | F_CONFIG)))
+               {
+                 cache_unlink(crecp);
+                 cache_free(crecp);
+               }
+             continue;
+           } 
+       
+         if ((crecp->flags & F_FORWARD) && hostname_isequal(cache_get_name(crecp), name))
+           {
+             /* Don't delete DNSSEC in favour of a CNAME, they can co-exist */
+             if ((flags & crecp->flags & (F_IPV4 | F_IPV6)) || 
+                 (((crecp->flags | flags) & F_CNAME) && !(crecp->flags & (F_DNSKEY | F_DS))))
+               {
+                 if (crecp->flags & (F_HOSTS | F_DHCP | F_CONFIG))
+                   return crecp;
+                 *up = crecp->hash_next;
+                 cache_unlink(crecp);
+                 cache_free(crecp);
+                 continue;
+               }
+             
+#ifdef HAVE_DNSSEC
+             /* Deletion has to be class-sensitive for DS, DNSKEY, RRSIG, also 
+                type-covered sensitive for  RRSIG */
+             if ((flags & (F_DNSKEY | F_DS)) &&
+                 (flags & (F_DNSKEY | F_DS)) == (crecp->flags & (F_DNSKEY | F_DS)) &&
+                 crecp->uid == addr->addr.dnssec.class &&
+                 (!((flags & (F_DS | F_DNSKEY)) == (F_DS | F_DNSKEY)) || 
+                  crecp->addr.sig.type_covered == addr->addr.dnssec.type))
+               {
+                 if (crecp->flags & F_CONFIG)
+                   return crecp;
+                 *up = crecp->hash_next;
+                 cache_unlink(crecp);
+                 cache_free(crecp);
+                 continue;
+               }
+#endif
+           }
          up = &crecp->hash_next;
+       }
     }
   else
     {
@@ -373,7 +423,7 @@ static int cache_scan_free(char *name, struct all_addr *addr, time_t now, unsign
            up = &crecp->hash_next;
     }
   
-  return 1;
+  return NULL;
 }
 
 /* Note: The normal calling sequence is
@@ -407,22 +457,42 @@ struct crec *cache_insert(char *name, struct all_addr *addr,
   int freed_all = flags & F_REVERSE;
   int free_avail = 0;
 
-  if (daemon->max_cache_ttl != 0 && daemon->max_cache_ttl < ttl)
-    ttl = daemon->max_cache_ttl;
-
-  /* Don't log keys */
-  if (flags & (F_IPV4 | F_IPV6))
-    log_query(flags | F_UPSTREAM, name, addr, NULL);
+  /* Don't log DNSSEC records here, done elsewhere */
+  if (flags & (F_IPV4 | F_IPV6 | F_CNAME))
+    {
+      log_query(flags | F_UPSTREAM, name, addr, NULL);
+      /* Don't mess with TTL for DNSSEC records. */
+      if (daemon->max_cache_ttl != 0 && daemon->max_cache_ttl < ttl)
+       ttl = daemon->max_cache_ttl;
+      if (daemon->min_cache_ttl != 0 && daemon->min_cache_ttl > ttl)
+       ttl = daemon->min_cache_ttl;
+    }
 
   /* if previous insertion failed give up now. */
   if (insert_error)
     return NULL;
-
+  
   /* First remove any expired entries and entries for the name/address we
-     are currently inserting. Fail is we attempt to delete a name from
-     /etc/hosts or DHCP. */
-  if (!cache_scan_free(name, addr, now, flags))
+     are currently inserting. */
+  if ((new = cache_scan_free(name, addr, now, flags)))
     {
+      /* We're trying to insert a record over one from 
+        /etc/hosts or DHCP, or other config. If the 
+        existing record is for an A or AAAA and
+        the record we're trying to insert is the same, 
+        just drop the insert, but don't error the whole process. */
+      if ((flags & (F_IPV4 | F_IPV6)) && (flags & F_FORWARD))
+       {
+         if ((flags & F_IPV4) && (new->flags & F_IPV4) &&
+             new->addr.addr.addr.addr4.s_addr == addr->addr.addr4.s_addr)
+           return new;
+#ifdef HAVE_IPV6
+         else if ((flags & F_IPV6) && (new->flags & F_IPV6) &&
+                  IN6_ARE_ADDR_EQUAL(&new->addr.addr.addr.addr6, &addr->addr.addr6))
+           return new;
+#endif
+       }
+      
       insert_error = 1;
       return NULL;
     }
@@ -447,14 +517,32 @@ struct crec *cache_insert(char *name, struct all_addr *addr,
           insert. Once in this state, all inserts will probably fail. */
        if (free_avail)
          {
+           static int warned = 0;
+           if (!warned)
+             {
+               my_syslog(LOG_ERR, _("Internal error in cache."));
+               warned = 1;
+             }
            insert_error = 1;
            return NULL;
          }
                
        if (freed_all)
          {
+           struct all_addr free_addr = new->addr.addr;;
+
+#ifdef HAVE_DNSSEC
+           /* For DNSSEC records, addr holds class and type_covered for RRSIG */
+           if (new->flags & (F_DS | F_DNSKEY))
+             {
+               free_addr.addr.dnssec.class = new->uid;
+               if ((new->flags & (F_DS | F_DNSKEY)) == (F_DS | F_DNSKEY))
+                 free_addr.addr.dnssec.type = new->addr.sig.type_covered;
+             }
+#endif
+           
            free_avail = 1; /* Must be free space now. */
-           cache_scan_free(cache_get_name(new), &new->addr.addr, now, new->flags);
+           cache_scan_free(cache_get_name(new), &free_addr, now, new->flags);
            cache_live_freed++;
          }
        else
@@ -466,7 +554,7 @@ struct crec *cache_insert(char *name, struct all_addr *addr,
       }
  
     /* Check if we need to and can allocate extra memory for a long name.
-       If that fails, give up now. */
+       If that fails, give up now, always succeed for DNSSEC records. */
     if (name && (strlen(name) > SMALLDNAME-1))
       {
        if (big_free)
@@ -474,13 +562,13 @@ struct crec *cache_insert(char *name, struct all_addr *addr,
            big_name = big_free;
            big_free = big_free->next;
          }
-       else if (!bignames_left ||
+       else if ((bignames_left == 0 && !(flags & (F_DS | F_DNSKEY))) ||
                 !(big_name = (union bigname *)whine_malloc(sizeof(union bigname))))
          {
            insert_error = 1;
            return NULL;
          }
-       else
+       else if (bignames_left != 0)
          bignames_left--;
        
       }
@@ -503,7 +591,14 @@ struct crec *cache_insert(char *name, struct all_addr *addr,
     *cache_get_name(new) = 0;
 
   if (addr)
-    new->addr.addr = *addr;
+    {
+#ifdef HAVE_DNSSEC
+      if (flags & (F_DS | F_DNSKEY))
+       new->uid = addr->addr.dnssec.class;
+      else
+#endif
+       new->addr.addr = *addr; 
+    }
 
   new->ttd = now + (time_t)ttl;
   new->next = new_chain;
@@ -535,10 +630,13 @@ void cache_end_insert(void)
   new_chain = NULL;
 }
 
-struct crec *cache_find_by_name(struct crec *crecp, char *name, time_t now, unsigned short prot)
+struct crec *cache_find_by_name(struct crec *crecp, char *name, time_t now, unsigned int prot)
 {
   struct crec *ans;
+  int no_rr = prot & F_NO_RR;
 
+  prot &= ~F_NO_RR;
+  
   if (crecp) /* iterating */
     ans = crecp->next;
   else
@@ -555,6 +653,9 @@ struct crec *cache_find_by_name(struct crec *crecp, char *name, time_t now, unsi
          if (!is_expired(now, crecp) && !is_outdated_cname_pointer(crecp))
            {
              if ((crecp->flags & F_FORWARD) && 
+#ifdef HAVE_DNSSEC
+                 (((crecp->flags & (F_DNSKEY | F_DS)) == (prot & (F_DNSKEY | F_DS))) || (prot & F_NSIGMATCH)) &&
+#endif
                  (crecp->flags & prot) &&
                  hostname_isequal(cache_get_name(crecp), name))
                {
@@ -583,7 +684,7 @@ struct crec *cache_find_by_name(struct crec *crecp, char *name, time_t now, unsi
                    }
                  else
                    {
-                     if (!insert)
+                     if (!insert && !no_rr)
                        {
                          insert = up;
                          ins_flags = crecp->flags & (F_REVERSE | F_IMMORTAL);
@@ -612,7 +713,10 @@ struct crec *cache_find_by_name(struct crec *crecp, char *name, time_t now, unsi
 
   if (ans && 
       (ans->flags & F_FORWARD) &&
-      (ans->flags & prot) &&
+#ifdef HAVE_DNSSEC
+      (((ans->flags & (F_DNSKEY | F_DS)) == (prot & (F_DNSKEY | F_DS))) || (prot & F_NSIGMATCH)) &&
+#endif
+      (ans->flags & prot) &&     
       hostname_isequal(cache_get_name(ans), name))
     return ans;
   
@@ -620,7 +724,7 @@ struct crec *cache_find_by_name(struct crec *crecp, char *name, time_t now, unsi
 }
 
 struct crec *cache_find_by_addr(struct crec *crecp, struct all_addr *addr, 
-                               time_t now, unsigned short prot)
+                               time_t now, unsigned int prot)
 {
   struct crec *ans;
 #ifdef HAVE_IPV6
@@ -697,13 +801,14 @@ static void add_hosts_cname(struct crec *target)
        crec->name.namep = a->alias;
        crec->addr.cname.target.cache = target;
        crec->addr.cname.uid = target->uid;
+       crec->uid = next_uid();
        cache_hash(crec);
        add_hosts_cname(crec); /* handle chains */
       }
 }
   
 static void add_hosts_entry(struct crec *cache, struct all_addr *addr, int addrlen, 
-                           int index, struct crec **rhash, int hashsz)
+                           unsigned int index, struct crec **rhash, int hashsz)
 {
   struct crec *lookup = cache_find_by_name(NULL, cache_get_name(cache), 0, cache->flags & (F_IPV4 | F_IPV6));
   int i, nameexists = 0;
@@ -730,27 +835,42 @@ static void add_hosts_entry(struct crec *cache, struct all_addr *addr, int addrl
      Only insert each unique address once into this hashing structure.
 
      This complexity avoids O(n^2) divergent CPU use whilst reading
-     large (10000 entry) hosts files. */
-  
-  /* hash address */
-  for (j = 0, i = 0; i < addrlen; i++)
-    j = (j*2 +((unsigned char *)addr)[i]) % hashsz;
-  
-  for (lookup = rhash[j]; lookup; lookup = lookup->next)
-    if ((lookup->flags & cache->flags & (F_IPV4 | F_IPV6)) &&
-       memcmp(&lookup->addr.addr, addr, addrlen) == 0)
-      {
-       cache->flags &= ~F_REVERSE;
-       break;
-      }
+     large (10000 entry) hosts files. 
+
+     Note that we only do this process when bulk-reading hosts files, 
+     for incremental reads, rhash is NULL, and we use cache lookups
+     instead.
+  */
   
-  /* maintain address hash chain, insert new unique address */
-  if (!lookup)
+  if (rhash)
     {
-      cache->next = rhash[j];
-      rhash[j] = cache;
+      /* hash address */
+      for (j = 0, i = 0; i < addrlen; i++)
+       j = (j*2 +((unsigned char *)addr)[i]) % hashsz;
+      
+      for (lookup = rhash[j]; lookup; lookup = lookup->next)
+       if ((lookup->flags & cache->flags & (F_IPV4 | F_IPV6)) &&
+           memcmp(&lookup->addr.addr, addr, addrlen) == 0)
+         {
+           cache->flags &= ~F_REVERSE;
+           break;
+         }
+      
+      /* maintain address hash chain, insert new unique address */
+      if (!lookup)
+       {
+         cache->next = rhash[j];
+         rhash[j] = cache;
+       }
     }
-  
+  else
+    {
+      /* incremental read, lookup in cache */
+      lookup = cache_find_by_addr(NULL, addr, 0, cache->flags & (F_IPV4 | F_IPV6));
+      if (lookup && lookup->flags & F_HOSTS)
+       cache->flags &= ~F_REVERSE;
+    }
+
   cache->uid = index;
   memcpy(&cache->addr.addr, addr, addrlen);  
   cache_hash(cache);
@@ -807,7 +927,7 @@ static int gettok(FILE *f, char *token)
     }
 }
 
-static int read_hostsfile(char *filename, int index, int cache_size, struct crec **rhash, int hashsz)
+int read_hostsfile(char *filename, unsigned int index, int cache_size, struct crec **rhash, int hashsz)
 {  
   FILE *f = fopen(filename, "r");
   char *token = daemon->namebuff, *domain_suffix = NULL;
@@ -853,7 +973,7 @@ static int read_hostsfile(char *filename, int index, int cache_size, struct crec
       addr_count++;
       
       /* rehash every 1000 names. */
-      if ((name_count - cache_size) > 1000)
+      if (rhash && ((name_count - cache_size) > 1000))
        {
          rehash(name_count);
          cache_size = name_count;
@@ -900,7 +1020,9 @@ static int read_hostsfile(char *filename, int index, int cache_size, struct crec
     } 
 
   fclose(f);
-  rehash(name_count);
+  
+  if (rhash)
+    rehash(name_count); 
   
   my_syslog(LOG_INFO, _("read %s - %d addresses"), filename, addr_count);
   
@@ -916,12 +1038,18 @@ void cache_reload(void)
   struct name_list *nl;
   struct cname *a;
   struct interface_name *intr;
+#ifdef HAVE_DNSSEC
+  struct ds_config *ds;
+#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
+       cache_blockdata_free(cache);
+#endif
        tmp = cache->hash_next;
        if (cache->flags & (F_HOSTS | F_CONFIG))
          {
@@ -951,10 +1079,27 @@ void cache_reload(void)
          cache->flags = F_FORWARD | F_NAMEP | F_CNAME | F_IMMORTAL | F_CONFIG;
          cache->name.namep = a->alias;
          cache->addr.cname.target.int_name = intr;
-         cache->addr.cname.uid = -1;
+         cache->addr.cname.uid = SRC_INTERFACE;
+         cache->uid = next_uid();
          cache_hash(cache);
          add_hosts_cname(cache); /* handle chains */
        }
+
+#ifdef HAVE_DNSSEC
+  for (ds = daemon->ds; ds; ds = ds->next)
+    if ((cache = whine_malloc(sizeof(struct crec))) &&
+       (cache->addr.ds.keydata = blockdata_alloc(ds->digest, ds->digestlen)))
+      {
+       cache->flags = F_FORWARD | F_IMMORTAL | F_DS | F_CONFIG | F_NAMEP;
+       cache->name.namep = ds->name;
+       cache->addr.ds.keylen = ds->digestlen;
+       cache->addr.ds.algo = ds->algo;
+       cache->addr.ds.keytag = ds->keytag;
+       cache->addr.ds.digest = ds->digest_type;
+       cache->uid = ds->class;
+       cache_hash(cache);
+      }
+#endif
   
   /* borrow the packet buffer for a temporary by-address hash */
   memset(daemon->packet, 0, daemon->packet_buff_sz);
@@ -971,7 +1116,7 @@ void cache_reload(void)
          {
            cache->name.namep = nl->name;
            cache->flags = F_HOSTS | F_IMMORTAL | F_FORWARD | F_REVERSE | F_IPV4 | F_NAMEP | F_CONFIG;
-           add_hosts_entry(cache, (struct all_addr *)&hr->addr, INADDRSZ, 0, (struct crec **)daemon->packet, revhashsz);
+           add_hosts_entry(cache, (struct all_addr *)&hr->addr, INADDRSZ, SRC_CONFIG, (struct crec **)daemon->packet, revhashsz);
          }
 #ifdef HAVE_IPV6
        if (!IN6_IS_ADDR_UNSPECIFIED(&hr->addr6) &&
@@ -979,7 +1124,7 @@ void cache_reload(void)
          {
            cache->name.namep = nl->name;
            cache->flags = F_HOSTS | F_IMMORTAL | F_FORWARD | F_REVERSE | F_IPV6 | F_NAMEP | F_CONFIG;
-           add_hosts_entry(cache, (struct all_addr *)&hr->addr6, IN6ADDRSZ, 0, (struct crec **)daemon->packet, revhashsz);
+           add_hosts_entry(cache, (struct all_addr *)&hr->addr6, IN6ADDRSZ, SRC_CONFIG, (struct crec **)daemon->packet, revhashsz);
          }
 #endif
       }
@@ -988,16 +1133,22 @@ void cache_reload(void)
     {
       if (daemon->cachesize > 0)
        my_syslog(LOG_INFO, _("cleared cache"));
-      return;
     }
-    
-  if (!option_bool(OPT_NO_HOSTS))
-    total_size = read_hostsfile(HOSTSFILE, 0, total_size, (struct crec **)daemon->packet, revhashsz);
-          
-  daemon->addn_hosts = expand_filelist(daemon->addn_hosts);
-  for (ah = daemon->addn_hosts; ah; ah = ah->next)
-    if (!(ah->flags & AH_INACTIVE))
-      total_size = read_hostsfile(ah->fname, ah->index, total_size, (struct crec **)daemon->packet, revhashsz);
+  else
+    {
+      if (!option_bool(OPT_NO_HOSTS))
+       total_size = read_hostsfile(HOSTSFILE, SRC_HOSTS, total_size, (struct crec **)daemon->packet, revhashsz);
+      
+      daemon->addn_hosts = expand_filelist(daemon->addn_hosts);
+      for (ah = daemon->addn_hosts; ah; ah = ah->next)
+       if (!(ah->flags & AH_INACTIVE))
+         total_size = read_hostsfile(ah->fname, ah->index, total_size, (struct crec **)daemon->packet, revhashsz);
+    }
+
+#ifdef HAVE_INOTIFY
+  set_dynamic_inotify(AH_HOSTS, total_size, (struct crec **)daemon->packet, revhashsz);
+#endif
+  
 } 
 
 #ifdef HAVE_DHCP
@@ -1056,6 +1207,7 @@ static void add_dhcp_cname(struct crec *target, time_t ttd)
            aliasc->name.namep = a->alias;
            aliasc->addr.cname.target.cache = target;
            aliasc->addr.cname.uid = target->uid;
+           aliasc->uid = next_uid();
            cache_hash(aliasc);
            add_dhcp_cname(aliasc, ttd);
          }
@@ -1143,7 +1295,7 @@ void cache_add_dhcp_entry(char *host_name, int prot,
        crec->ttd = ttd;
       crec->addr.addr = *host_address;
       crec->name.namep = host_name;
-      crec->uid = uid++;
+      crec->uid = next_uid();
       cache_hash(crec);
 
       add_dhcp_cname(crec, ttd);
@@ -1151,10 +1303,120 @@ void cache_add_dhcp_entry(char *host_name, int prot,
 }
 #endif
 
+int cache_make_stat(struct txt_record *t)
+{ 
+  static char *buff = NULL;
+  static int bufflen = 60;
+  int len;
+  struct server *serv, *serv1;
+  char *p;
+
+  if (!buff && !(buff = whine_malloc(60)))
+    return 0;
+
+  p = buff;
+  
+  switch (t->stat)
+    {
+    case TXT_STAT_CACHESIZE:
+      sprintf(buff+1, "%d", daemon->cachesize);
+      break;
+
+    case TXT_STAT_INSERTS:
+      sprintf(buff+1, "%d", cache_inserted);
+      break;
+
+    case TXT_STAT_EVICTIONS:
+      sprintf(buff+1, "%d", cache_live_freed);
+      break;
+
+    case TXT_STAT_MISSES:
+      sprintf(buff+1, "%u", daemon->queries_forwarded);
+      break;
+
+    case TXT_STAT_HITS:
+      sprintf(buff+1, "%u", daemon->local_answer);
+      break;
+
+#ifdef HAVE_AUTH
+    case TXT_STAT_AUTH:
+      sprintf(buff+1, "%u", daemon->auth_answer);
+      break;
+#endif
+
+    case TXT_STAT_SERVERS:
+      /* sum counts from different records for same server */
+      for (serv = daemon->servers; serv; serv = serv->next)
+       serv->flags &= ~SERV_COUNTED;
+      
+      for (serv = daemon->servers; serv; serv = serv->next)
+       if (!(serv->flags & 
+             (SERV_NO_ADDR | SERV_LITERAL_ADDRESS | SERV_COUNTED | SERV_USE_RESOLV | SERV_NO_REBIND)))
+         {
+           char *new, *lenp;
+           int port, newlen, bytes_avail, bytes_needed;
+           unsigned int queries = 0, failed_queries = 0;
+           for (serv1 = serv; serv1; serv1 = serv1->next)
+             if (!(serv1->flags & 
+                   (SERV_NO_ADDR | SERV_LITERAL_ADDRESS | SERV_COUNTED | SERV_USE_RESOLV | SERV_NO_REBIND)) && 
+                 sockaddr_isequal(&serv->addr, &serv1->addr))
+               {
+                 serv1->flags |= SERV_COUNTED;
+                 queries += serv1->queries;
+                 failed_queries += serv1->failed_queries;
+               }
+           port = prettyprint_addr(&serv->addr, daemon->addrbuff);
+           lenp = p++; /* length */
+           bytes_avail = bufflen - (p - buff );
+           bytes_needed = snprintf(p, bytes_avail, "%s#%d %u %u", daemon->addrbuff, port, queries, failed_queries);
+           if (bytes_needed >= bytes_avail)
+             {
+               /* expand buffer if necessary */
+               newlen = bytes_needed + 1 + bufflen - bytes_avail;
+               if (!(new = whine_malloc(newlen)))
+                 return 0;
+               memcpy(new, buff, bufflen);
+               free(buff);
+               p = new + (p - buff);
+               lenp = p - 1;
+               buff = new;
+               bufflen = newlen;
+               bytes_avail =  bufflen - (p - buff );
+               bytes_needed = snprintf(p, bytes_avail, "%s#%d %u %u", daemon->addrbuff, port, queries, failed_queries);
+             }
+           *lenp = bytes_needed;
+           p += bytes_needed;
+         }
+      t->txt = (unsigned char *)buff;
+      t->len = p - buff;
+      return 1;
+    }
+  
+  len = strlen(buff+1);
+  t->txt = (unsigned char *)buff;
+  t->len = len + 1;
+  *buff = len;
+  return 1;
+}
+
+/* There can be names in the cache containing control chars, don't 
+   mess up logging or open security holes. */
+static char *sanitise(char *name)
+{
+  unsigned char *r;
+  if (name)
+    for (r = (unsigned char *)name; *r; r++)
+      if (!isprint((int)*r))
+       return "<name unprintable>";
+
+  return name;
+}
+
 
 void dump_cache(time_t now)
 {
   struct server *serv, *serv1;
+  char *t = "";
 
   my_syslog(LOG_INFO, _("time %lu"), (unsigned long)now);
   my_syslog(LOG_INFO, _("cache size %d, %d/%d cache insertions re-used unexpired cache entries."), 
@@ -1164,6 +1426,9 @@ void dump_cache(time_t now)
 #ifdef HAVE_AUTH
   my_syslog(LOG_INFO, _("queries for authoritative zones %u"), daemon->auth_answer);
 #endif
+#ifdef HAVE_DNSSEC
+  blockdata_report();
+#endif
 
   /* sum counts from different records for same server */
   for (serv = daemon->servers; serv; serv = serv->next)
@@ -1192,35 +1457,34 @@ void dump_cache(time_t now)
     {
       struct crec *cache ;
       int i;
-      my_syslog(LOG_INFO, "Host                                     Address                        Flags     Expires");
+      my_syslog(LOG_INFO, "Host                                     Address                        Flags      Expires");
     
       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 && !(cache->flags & F_REVERSE))
+             n = "<Root>";
+           p += sprintf(p, "%-30.30s ", sanitise(n));
+           if ((cache->flags & F_CNAME) && !is_outdated_cname_pointer(cache))
+             a = sanitise(cache_get_cname_target(cache));
 #ifdef HAVE_DNSSEC
-           else if (cache->flags & F_DNSKEY)
-             {
-               a = daemon->addrbuff;
-               sprintf(a, "%3u %u", cache->addr.key.algo, cache->uid);
-             }
            else if (cache->flags & F_DS)
              {
-               a = daemon->addrbuff;
-               sprintf(a, "%5u %3u %3u %u", cache->addr.key.flags_or_keyid,
-                       cache->addr.key.algo, cache->addr.key.digest, cache->uid);
+               if (cache->flags & F_DNSKEY)
+                 /* RRSIG */
+                 sprintf(a, "%5u %3u %s", cache->addr.sig.keytag,
+                         cache->addr.sig.algo, querystr("", cache->addr.sig.type_covered));
+               else if (!(cache->flags & F_NEG))
+                 sprintf(a, "%5u %3u %3u", cache->addr.ds.keytag,
+                         cache->addr.ds.algo, cache->addr.ds.digest);
              }
+           else if (cache->flags & F_DNSKEY)
+             sprintf(a, "%5u %3u %3u", cache->addr.key.keytag,
+                     cache->addr.key.algo, cache->addr.key.flags);
 #endif
-           else 
+           else if (!(cache->flags & F_NEG) || !(cache->flags & F_FORWARD))
              { 
                a = daemon->addrbuff;
                if (cache->flags & F_IPV4)
@@ -1231,12 +1495,21 @@ void dump_cache(time_t now)
 #endif
              }
 
-           p += sprintf(p, "%-30.30s %s%s%s%s%s%s%s%s%s%s%s%s%s  ", a, 
-                        cache->flags & F_IPV4 ? "4" : "",
-                        cache->flags & F_IPV6 ? "6" : "",
-                        cache->flags & F_DNSKEY ? "K" : "",
-                        cache->flags & F_DS ? "S" : "",
-                        cache->flags & F_CNAME ? "C" : "",
+           if (cache->flags & F_IPV4)
+             t = "4";
+           else if (cache->flags & F_IPV6)
+             t = "6";
+           else if (cache->flags & F_CNAME)
+             t = "C";
+#ifdef HAVE_DNSSEC
+           else if ((cache->flags & (F_DS | F_DNSKEY)) == (F_DS | F_DNSKEY))
+             t = "G"; /* DNSKEY and DS set -> RRISG */
+           else if (cache->flags & F_DS)
+             t = "S";
+           else if (cache->flags & F_DNSKEY)
+             t = "K";
+#endif
+           p += sprintf(p, "%-40.40s %s%s%s%s%s%s%s%s%s  ", a, t,
                         cache->flags & F_FORWARD ? "F" : " ",
                         cache->flags & F_REVERSE ? "R" : " ",
                         cache->flags & F_IMMORTAL ? "I" : " ",
@@ -1257,28 +1530,67 @@ void dump_cache(time_t now)
     }
 }
 
-char *record_source(int index)
+char *record_source(unsigned int index)
 {
   struct hostsfile *ah;
 
-  if (index == 0)
+  if (index == SRC_CONFIG)
+    return "config";
+  else if (index == SRC_HOSTS)
     return HOSTSFILE;
 
   for (ah = daemon->addn_hosts; ah; ah = ah->next)
     if (ah->index == index)
       return ah->fname;
-  
+
+#ifdef HAVE_INOTIFY
+  for (ah = daemon->dynamic_dirs; ah; ah = ah->next)
+     if (ah->index == index)
+       return ah->fname;
+#endif
+
   return "<unknown>";
 }
 
-void querystr(char *desc, char *str, unsigned short type)
+char *querystr(char *desc, unsigned short type)
 {
   unsigned int i;
-  
-  sprintf(str, "%s[type=%d]", desc, type); 
+  int len = 10; /* strlen("type=xxxxx") */
+  const char *types = NULL;
+  static char *buff = NULL;
+  static int bufflen = 0;
+
   for (i = 0; i < (sizeof(typestr)/sizeof(typestr[0])); i++)
     if (typestr[i].type == type)
-      sprintf(str,"%s[%s]", desc, typestr[i].name);
+      {
+       types = typestr[i].name;
+       len = strlen(types);
+       break;
+      }
+
+  len += 3; /* braces, terminator */
+  len += strlen(desc);
+
+  if (!buff || bufflen < len)
+    {
+      if (buff)
+       free(buff);
+      else if (len < 20)
+       len = 20;
+      
+      buff = whine_malloc(len);
+      bufflen = len;
+    }
+
+  if (buff)
+    {
+      if (types)
+       sprintf(buff, "%s[%s]", desc, types);
+      else
+       sprintf(buff, "%s[type=%d]", desc, type);
+    }
+
+  return buff ? buff : "";
 }
 
 void log_query(unsigned int flags, char *name, struct all_addr *addr, char *arg)
@@ -1289,15 +1601,24 @@ void log_query(unsigned int flags, char *name, struct all_addr *addr, char *arg)
   if (!option_bool(OPT_LOG))
     return;
 
+  name = sanitise(name);
+
   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)
     {
@@ -1308,14 +1629,7 @@ void log_query(unsigned int flags, char *name, struct all_addr *addr, char *arg)
   if (flags & F_NEG)
     {
       if (flags & F_NXDOMAIN)
-       {
-         if (flags & F_IPV4)
-           dest = "NXDOMAIN-IPv4";
-         else if (flags & F_IPV6)
-           dest = "NXDOMAIN-IPv6";
-         else
-           dest = "NXDOMAIN";
-       }
+       dest = "NXDOMAIN";
       else
        {      
          if (flags & F_IPV4)
@@ -1339,6 +1653,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,59 +1667,34 @@ 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 if (flags & F_IPSET)
+    {
+      source = "ipset add";
+      dest = name;
+      name = arg;
+      verb = daemon->addrbuff;
+    }
   else
     source = "cached";
   
   if (strlen(name) == 0)
     name = ".";
 
-  my_syslog(LOG_INFO, "%s %s %s %s", source, name, verb, dest);
-}
-
-#ifdef HAVE_DNSSEC
-struct keydata *keydata_alloc(char *data, size_t len)
-{
-  struct keydata *block, *ret = NULL;
-  struct keydata **prev = &ret;
-  while (len > 0)
+  if (option_bool(OPT_EXTRALOG))
     {
-      if (keyblock_free)
-       {
-         block = keyblock_free;
-         keyblock_free = block->next;
-       }
+      int port = prettyprint_addr(daemon->log_source_addr, daemon->addrbuff2);
+      if (flags & F_NOEXTRA)
+       my_syslog(LOG_INFO, "* %s/%u %s %s %s %s", daemon->addrbuff2, port, source, name, verb, dest);
       else
-       block = whine_malloc(sizeof(struct keydata));
-
-      if (!block)
-       {
-         /* failed to alloc, free partial chain */
-         keydata_free(ret);
-         return NULL;
-       }
-      
-      memcpy(block->key, data, len > KEYBLOCK_LEN ? KEYBLOCK_LEN : len);
-      data += KEYBLOCK_LEN;
-      len -= KEYBLOCK_LEN;
-      *prev = block;
-      prev = &block->next;
-      block->next = NULL;
-    }
-  
-  return ret;
-}
-
-void keydata_free(struct keydata *blocks)
-{
-  struct keydata *tmp;
-
-  if (blocks)
-    {
-      for (tmp = blocks; tmp->next; tmp = tmp->next);
-      tmp->next = keyblock_free;
-      keyblock_free = blocks;
+       my_syslog(LOG_INFO, "%u %s/%u %s %s %s %s", daemon->log_display_id, daemon->addrbuff2, port, source, name, verb, dest);
     }
+  else
+    my_syslog(LOG_INFO, "%s %s %s %s", source, name, verb, dest);
 }
-#endif
 
-