]> git.ipfire.org Git - people/ms/dnsmasq.git/blobdiff - src/cache.c
Fix boilerplate code for re-running system calls on EINTR and EAGAIN etc.
[people/ms/dnsmasq.git] / src / cache.c
index d7877d71543f19dbbfe9024696420b3989508cb1..c95624c42b1c68aae94d9a1165eecb155785a247 100644 (file)
@@ -1,4 +1,4 @@
-/* dnsmasq is Copyright (c) 2000-2011 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,7 +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 = 0;
 
 /* type->string mapping: this is also used by the name-hash function as a mixing table. */
 static const struct {
@@ -53,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" },
@@ -69,11 +72,24 @@ 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;
   int i;
-
   bignames_left = daemon->cachesize/10;
   
   if (daemon->cachesize > 0)
@@ -84,7 +100,7 @@ void cache_init(void)
        {
          cache_link(crecp);
          crecp->flags = 0;
-         crecp->uid = uid++;
+         crecp->uid = next_uid();
        }
     }
   
@@ -168,13 +184,28 @@ 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. */
-  
+  crecp->uid = next_uid(); /* invalidate CNAMES pointing to this. */
+
   if (cache_tail)
     cache_tail->next = crecp;
   else
@@ -190,6 +221,10 @@ static void cache_free(struct crec *crecp)
       big_free = crecp->name.bname;
       crecp->flags &= ~F_BIGNAME;
     }
+
+#ifdef HAVE_DNSSEC
+  cache_blockdata_free(crecp);
+#endif
 }    
 
 /* insert a new cache entry at the head of the list (youngest entry) */
@@ -228,12 +263,49 @@ char *cache_get_name(struct crec *crecp)
   return crecp->name.sname;
 }
 
+char *cache_get_cname_target(struct crec *crecp)
+{
+  if (crecp->addr.cname.uid != SRC_INTERFACE)
+    return cache_get_name(crecp->addr.cname.target.cache);
+
+  return crecp->addr.cname.target.int_name->name;
+}
+
+
+
+struct crec *cache_enumerate(int init)
+{
+  static int bucket;
+  static struct crec *cache;
+
+  if (init)
+    {
+      bucket = 0;
+      cache = NULL;
+    }
+  else if (cache && cache->hash_next)
+    cache = cache->hash_next;
+  else
+    {
+       cache = NULL; 
+       while (bucket < hash_size)
+        if ((cache = hash_table[bucket++]))
+          break;
+    }
+  
+  return cache;
+}
+
 static int is_outdated_cname_pointer(struct crec *crecp)
 {
-  if (!(crecp->flags & F_CNAME))
+  if (!(crecp->flags & F_CNAME) || crecp->addr.cname.uid == SRC_INTERFACE)
     return 0;
   
-  if (crecp->addr.cname.cache && crecp->addr.cname.uid == crecp->addr.cname.cache->uid)
+  /* NB. record may be reused as DS or DNSKEY, where uid is 
+     overloaded for something completely different */
+  if (crecp->addr.cname.target.cache && 
+      (crecp->addr.cname.target.cache->flags & (F_IPV4 | F_IPV6 | F_CNAME)) &&
+      crecp->addr.cname.uid == crecp->addr.cname.target.cache->uid)
     return 0;
   
   return 1;
@@ -250,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
@@ -259,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. */
@@ -270,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)))
-             {
-               cache_unlink(crecp);
-               cache_free(crecp);
-             }
-         } 
-       else if ((crecp->flags & F_FORWARD) && 
-                ((flags & crecp->flags & (F_IPV4 | F_IPV6)) || ((crecp->flags | flags) & F_CNAME)) &&
-                hostname_isequal(cache_get_name(crecp), name))
-         {
-           if (crecp->flags & (F_HOSTS | F_DHCP))
-             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
     {
@@ -307,13 +404,13 @@ static int cache_scan_free(char *name, struct all_addr *addr, time_t now, unsign
          if (is_expired(now, crecp))
            {
              *up = crecp->hash_next;
-             if (!(crecp->flags & (F_HOSTS | F_DHCP)))
+             if (!(crecp->flags & (F_HOSTS | F_DHCP | F_CONFIG)))
                { 
                  cache_unlink(crecp);
                  cache_free(crecp);
                }
            }
-         else if (!(crecp->flags & (F_HOSTS | F_DHCP)) &&
+         else if (!(crecp->flags & (F_HOSTS | F_DHCP | F_CONFIG)) &&
                   (flags & crecp->flags & F_REVERSE) && 
                   (flags & crecp->flags & (F_IPV4 | F_IPV6)) &&
                   memcmp(&crecp->addr.addr, addr, addrlen) == 0)
@@ -326,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
@@ -360,17 +457,42 @@ struct crec *cache_insert(char *name, struct all_addr *addr,
   int freed_all = flags & F_REVERSE;
   int free_avail = 0;
 
-  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;
     }
@@ -395,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
@@ -414,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)
@@ -422,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--;
        
       }
@@ -451,10 +591,15 @@ struct crec *cache_insert(char *name, struct all_addr *addr,
     *cache_get_name(new) = 0;
 
   if (addr)
-    new->addr.addr = *addr;
-  else
-    new->addr.cname.cache = NULL;
-  
+    {
+#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;
   new_chain = new;
@@ -485,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
@@ -505,10 +653,13 @@ 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))
                {
-                 if (crecp->flags & (F_HOSTS | F_DHCP))
+                 if (crecp->flags & (F_HOSTS | F_DHCP | F_CONFIG))
                    {
                      *chainp = crecp;
                      chainp = &crecp->next;
@@ -533,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);
@@ -549,7 +700,7 @@ struct crec *cache_find_by_name(struct crec *crecp, char *name, time_t now, unsi
            {
              /* expired entry, free it */
              *up = crecp->hash_next;
-             if (!(crecp->flags & (F_HOSTS | F_DHCP)))
+             if (!(crecp->flags & (F_HOSTS | F_DHCP | F_CONFIG)))
                { 
                  cache_unlink(crecp);
                  cache_free(crecp);
@@ -562,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;
   
@@ -570,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
@@ -599,7 +753,7 @@ struct crec *cache_find_by_addr(struct crec *crecp, struct all_addr *addr,
               if ((crecp->flags & prot) &&
                   memcmp(&crecp->addr.addr, addr, addrlen) == 0)
                 {          
-                  if (crecp->flags & (F_HOSTS | F_DHCP))
+                  if (crecp->flags & (F_HOSTS | F_DHCP | F_CONFIG))
                     {
                       *chainp = crecp;
                       chainp = &crecp->next;
@@ -615,7 +769,7 @@ struct crec *cache_find_by_addr(struct crec *crecp, struct all_addr *addr,
           else
             {
               *up = crecp->hash_next;
-              if (!(crecp->flags & (F_HOSTS | F_DHCP)))
+              if (!(crecp->flags & (F_HOSTS | F_DHCP | F_CONFIG)))
                 {
                   cache_unlink(crecp);
                   cache_free(crecp);
@@ -634,12 +788,31 @@ struct crec *cache_find_by_addr(struct crec *crecp, struct all_addr *addr,
   return NULL;
 }
 
+static void add_hosts_cname(struct crec *target)
+{
+  struct crec *crec;
+  struct cname *a;
+  
+  for (a = daemon->cnames; a; a = a->next)
+    if (hostname_isequal(cache_get_name(target), a->target) &&
+       (crec = whine_malloc(sizeof(struct crec))))
+      {
+       crec->flags = F_FORWARD | F_IMMORTAL | F_NAMEP | F_CONFIG | F_CNAME;
+       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, 
-                           unsigned short flags, int index, int addr_dup)
+                           unsigned int index, struct crec **rhash, int hashsz)
 {
-  struct crec *lookup = cache_find_by_name(NULL, cache->name.sname, 0, flags & (F_IPV4 | F_IPV6));
+  struct crec *lookup = cache_find_by_name(NULL, cache_get_name(cache), 0, cache->flags & (F_IPV4 | F_IPV6));
   int i, nameexists = 0;
-  struct cname *a;
+  unsigned int j; 
 
   /* Remove duplicates in hosts files. */
   if (lookup && (lookup->flags & F_HOSTS))
@@ -653,47 +826,58 @@ static void add_hosts_entry(struct crec *cache, struct all_addr *addr, int addrl
     }
   
   /* Ensure there is only one address -> name mapping (first one trumps) 
-     We do this by steam here, first we see if the address is the same as
-     the last one we saw, which eliminates most in the case of an ad-block 
-     file with thousands of entries for the same address.
-     Then we search and bail at the first matching address that came from
-     a HOSTS file. Since the first host entry gets reverse, we know 
-     then that it must exist without searching exhaustively for it. */
+     We do this by steam here, The entries are kept in hash chains, linked
+     by ->next (which is unused at this point) held in hash buckets in
+     the array rhash, hashed on address. Note that rhash and the values
+     in ->next are only valid  whilst reading hosts files: the buckets are
+     then freed, and the ->next pointer used for other things. 
+
+     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. 
+
+     Note that we only do this process when bulk-reading hosts files, 
+     for incremental reads, rhash is NULL, and we use cache lookups
+     instead.
+  */
   
-  if (addr_dup)
-    flags &= ~F_REVERSE;
+  if (rhash)
+    {
+      /* 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
-    for (i=0; i<hash_size; i++)
-      {
-       for (lookup = hash_table[i]; lookup; lookup = lookup->hash_next)
-         if ((lookup->flags & F_HOSTS) && 
-             (lookup->flags & flags & (F_IPV4 | F_IPV6)) &&
-             memcmp(&lookup->addr.addr, addr, addrlen) == 0)
-           {
-             flags &= ~F_REVERSE;
-             break;
-           }
-       if (lookup)
-         break;
-      }
-  
-  cache->flags = flags;
+    {
+      /* 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);
+  memcpy(&cache->addr.addr, addr, addrlen);  
   cache_hash(cache);
   
   /* don't need to do alias stuff for second and subsequent addresses. */
   if (!nameexists)
-    for (a = daemon->cnames; a; a = a->next)
-      if (hostname_isequal(cache->name.sname, a->target) &&
-         (lookup = whine_malloc(sizeof(struct crec))))
-       {
-         lookup->flags = F_FORWARD | F_IMMORTAL | F_NAMEP | F_HOSTS | F_CNAME;
-         lookup->name.namep = a->alias;
-         lookup->addr.cname.cache = cache;
-         lookup->addr.cname.uid = index;
-         cache_hash(lookup);
-       }
+    add_hosts_cname(cache);
 }
 
 static int eatspace(FILE *f)
@@ -743,14 +927,14 @@ static int gettok(FILE *f, char *token)
     }
 }
 
-static int read_hostsfile(char *filename, int index, int cache_size)
+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;
   int addr_count = 0, name_count = cache_size, lineno = 0;
-  unsigned short flags = 0, saved_flags = 0;
-  struct all_addr addr, saved_addr;
-  int atnl, addrlen = 0, addr_dup;
+  unsigned short flags = 0;
+  struct all_addr addr;
+  int atnl, addrlen = 0;
 
   if (!f)
     {
@@ -762,28 +946,20 @@ static int read_hostsfile(char *filename, int index, int cache_size)
   
   while ((atnl = gettok(f, token)) != EOF)
     {
-      addr_dup = 0;
       lineno++;
       
-#ifdef HAVE_IPV6      
       if (inet_pton(AF_INET, token, &addr) > 0)
        {
          flags = F_HOSTS | F_IMMORTAL | F_FORWARD | F_REVERSE | F_IPV4;
          addrlen = INADDRSZ;
          domain_suffix = get_domain(addr.addr.addr4);
        }
+#ifdef HAVE_IPV6
       else if (inet_pton(AF_INET6, token, &addr) > 0)
        {
          flags = F_HOSTS | F_IMMORTAL | F_FORWARD | F_REVERSE | F_IPV6;
          addrlen = IN6ADDRSZ;
-         domain_suffix = daemon->domain_suffix;
-       }
-#else 
-      if ((addr.addr.addr4.s_addr = inet_addr(token)) != (in_addr_t) -1)
-       {
-         flags = F_HOSTS | F_IMMORTAL | F_FORWARD | F_REVERSE | F_IPV4;
-         addrlen = INADDRSZ;
-         domain_suffix = get_domain(addr.addr.addr4);
+         domain_suffix = get_domain6(&addr.addr.addr6);
        }
 #endif
       else
@@ -794,18 +970,10 @@ static int read_hostsfile(char *filename, int index, int cache_size)
          continue;
        }
       
-      if (saved_flags == flags && memcmp(&addr, &saved_addr, addrlen) == 0)
-       addr_dup = 1;
-      else
-       {
-         saved_flags = flags;
-         saved_addr = addr;
-       }
-      
       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;
@@ -832,14 +1000,15 @@ static int read_hostsfile(char *filename, int index, int cache_size)
                  strcpy(cache->name.sname, canon);
                  strcat(cache->name.sname, ".");
                  strcat(cache->name.sname, domain_suffix);
-                 add_hosts_entry(cache, &addr, addrlen, flags, index, addr_dup);
-                 addr_dup = 1;
+                 cache->flags = flags;
+                 add_hosts_entry(cache, &addr, addrlen, index, rhash, hashsz);
                  name_count++;
                }
              if ((cache = whine_malloc(sizeof(struct crec) + strlen(canon)+1-SMALLDNAME)))
                {
                  strcpy(cache->name.sname, canon);
-                 add_hosts_entry(cache, &addr, addrlen, flags, index, addr_dup);
+                 cache->flags = flags;
+                 add_hosts_entry(cache, &addr, addrlen, index, rhash, hashsz);
                  name_count++;
                }
              free(canon);
@@ -851,7 +1020,9 @@ static int read_hostsfile(char *filename, int index, int cache_size)
     } 
 
   fclose(f);
-  rehash(name_count);
+  
+  if (rhash)
+    rehash(name_count); 
   
   my_syslog(LOG_INFO, _("read %s - %d addresses"), filename, addr_count);
   
@@ -861,16 +1032,26 @@ static int read_hostsfile(char *filename, int index, int cache_size)
 void cache_reload(void)
 {
   struct crec *cache, **up, *tmp;
-  int i, total_size = daemon->cachesize;
+  int revhashsz, i, total_size = daemon->cachesize;
   struct hostsfile *ah;
+  struct host_record *hr;
+  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)
+       if (cache->flags & (F_HOSTS | F_CONFIG))
          {
            *up = cache->hash_next;
            free(cache);
@@ -889,34 +1070,87 @@ void cache_reload(void)
          up = &cache->hash_next;
       }
   
+  /* Add CNAMEs to interface_names to the cache */
+  for (a = daemon->cnames; a; a = a->next)
+    for (intr = daemon->int_names; intr; intr = intr->next)
+      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->name.namep = a->alias;
+         cache->addr.cname.target.int_name = intr;
+         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);
+  revhashsz = daemon->packet_buff_sz / sizeof(struct crec *);
+  /* we overwrote the buffer... */
+  daemon->srv_save = NULL;
+
+  /* Do host_records in config. */
+  for (hr = daemon->host_records; hr; hr = hr->next)
+    for (nl = hr->names; nl; nl = nl->next)
+      {
+       if (hr->addr.s_addr != 0 &&
+           (cache = whine_malloc(sizeof(struct crec))))
+         {
+           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, SRC_CONFIG, (struct crec **)daemon->packet, revhashsz);
+         }
+#ifdef HAVE_IPV6
+       if (!IN6_IS_ADDR_UNSPECIFIED(&hr->addr6) &&
+           (cache = whine_malloc(sizeof(struct crec))))
+         {
+           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, SRC_CONFIG, (struct crec **)daemon->packet, revhashsz);
+         }
+#endif
+      }
+       
   if (option_bool(OPT_NO_HOSTS) && !daemon->addn_hosts)
     {
       if (daemon->cachesize > 0)
        my_syslog(LOG_INFO, _("cleared cache"));
-      return;
+    }
+  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);
     }
 
-  if (!option_bool(OPT_NO_HOSTS))
-    total_size = read_hostsfile(HOSTSFILE, 0, total_size);
-          
-  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);
+#ifdef HAVE_INOTIFY
+  set_dynamic_inotify(AH_HOSTS, total_size, (struct crec **)daemon->packet, revhashsz);
+#endif
+  
 } 
 
-char *get_domain(struct in_addr addr)
-{
-  struct cond_domain *c;
-
-  for (c = daemon->cond_domain; c; c = c->next)
-    if (ntohl(addr.s_addr) >= ntohl(c->start.s_addr) &&
-        ntohl(addr.s_addr) <= ntohl(c->end.s_addr))
-      return c->domain;
-
-  return daemon->domain_suffix;
-}
-
 #ifdef HAVE_DHCP
 struct in_addr a_record_from_hosts(char *name, time_t now)
 {
@@ -950,108 +1184,237 @@ void cache_unhash_dhcp(void)
        up = &cache->hash_next;
 }
 
-void cache_add_dhcp_entry(char *host_name, 
-                         struct in_addr *host_address, time_t ttd) 
+static void add_dhcp_cname(struct crec *target, time_t ttd)
 {
-  struct crec *crec = NULL, *aliasc;
-  unsigned short flags =  F_NAMEP | F_DHCP | F_FORWARD | F_IPV4 | F_REVERSE;
-  int in_hosts = 0;
+  struct crec *aliasc;
   struct cname *a;
   
-  while ((crec = cache_find_by_name(crec, host_name, 0, F_IPV4 | F_CNAME)))
+  for (a = daemon->cnames; a; a = a->next)
+    if (hostname_isequal(cache_get_name(target), a->target))
+      {
+       if ((aliasc = dhcp_spare))
+         dhcp_spare = dhcp_spare->next;
+       else /* need new one */
+         aliasc = whine_malloc(sizeof(struct crec));
+       
+       if (aliasc)
+         {
+           aliasc->flags = F_FORWARD | F_NAMEP | F_DHCP | F_CNAME | F_CONFIG;
+           if (ttd == 0)
+             aliasc->flags |= F_IMMORTAL;
+           else
+             aliasc->ttd = 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);
+         }
+      }
+}
+
+void cache_add_dhcp_entry(char *host_name, int prot,
+                         struct all_addr *host_address, time_t ttd) 
+{
+  struct crec *crec = NULL, *fail_crec = NULL;
+  unsigned short flags = F_IPV4;
+  int in_hosts = 0;
+  size_t addrlen = sizeof(struct in_addr);
+
+#ifdef HAVE_IPV6
+  if (prot == AF_INET6)
+    {
+      flags = F_IPV6;
+      addrlen = sizeof(struct in6_addr);
+    }
+#endif
+  
+  inet_ntop(prot, host_address, daemon->addrbuff, ADDRSTRLEN);
+  
+  while ((crec = cache_find_by_name(crec, host_name, 0, flags | F_CNAME)))
     {
       /* check all addresses associated with name */
-      if (crec->flags & F_HOSTS)
+      if (crec->flags & (F_HOSTS | F_CONFIG))
        {
-         /* if in hosts, don't need DHCP record */
-         in_hosts = 1;
-         
          if (crec->flags & F_CNAME)
            my_syslog(MS_DHCP | LOG_WARNING, 
                      _("%s is a CNAME, not giving it to the DHCP lease of %s"),
-                     host_name, inet_ntoa(*host_address));
-         else if (crec->addr.addr.addr.addr4.s_addr != host_address->s_addr)
-           {
-             strcpy(daemon->namebuff, inet_ntoa(crec->addr.addr.addr.addr4));
-             my_syslog(MS_DHCP | LOG_WARNING, 
-                       _("not giving name %s to the DHCP lease of %s because "
-                         "the name exists in %s with address %s"), 
-                       host_name, inet_ntoa(*host_address),
-                       record_source(crec->uid), daemon->namebuff);
-           }     
+                     host_name, daemon->addrbuff);
+         else if (memcmp(&crec->addr.addr, host_address, addrlen) == 0)
+           in_hosts = 1;
+         else
+           fail_crec = crec;
        }
       else if (!(crec->flags & F_DHCP))
        {
-         cache_scan_free(host_name, NULL, 0, crec->flags & (F_IPV4 | F_CNAME | F_FORWARD));
+         cache_scan_free(host_name, NULL, 0, crec->flags & (flags | F_CNAME | F_FORWARD));
          /* scan_free deletes all addresses associated with name */
          break;
        }
     }
   
-   if (in_hosts)
+  /* if in hosts, don't need DHCP record */
+  if (in_hosts)
     return;
-
-   if ((crec = cache_find_by_addr(NULL, (struct all_addr *)host_address, 0, F_IPV4)))
-     {
-       if (crec->flags & F_NEG)
-        cache_scan_free(NULL, (struct all_addr *)host_address, 0, F_IPV4 | F_REVERSE);
-       else
-        /* avoid multiple reverse mappings */
-        flags &= ~F_REVERSE;
-     }
-   
-   if ((crec = dhcp_spare))
+  
+  /* Name in hosts, address doesn't match */
+  if (fail_crec)
+    {
+      inet_ntop(prot, &fail_crec->addr.addr, daemon->namebuff, MAXDNAME);
+      my_syslog(MS_DHCP | LOG_WARNING, 
+               _("not giving name %s to the DHCP lease of %s because "
+                 "the name exists in %s with address %s"), 
+               host_name, daemon->addrbuff,
+               record_source(fail_crec->uid), daemon->namebuff);
+      return;
+    }    
+  
+  if ((crec = cache_find_by_addr(NULL, (struct all_addr *)host_address, 0, flags)))
+    {
+      if (crec->flags & F_NEG)
+       {
+         flags |= F_REVERSE;
+         cache_scan_free(NULL, (struct all_addr *)host_address, 0, flags);
+       }
+    }
+  else
+    flags |= F_REVERSE;
+  
+  if ((crec = dhcp_spare))
     dhcp_spare = dhcp_spare->next;
   else /* need new one */
     crec = whine_malloc(sizeof(struct crec));
   
   if (crec) /* malloc may fail */
     {
-      crec->flags = flags;
+      crec->flags = flags | F_NAMEP | F_DHCP | F_FORWARD;
       if (ttd == 0)
        crec->flags |= F_IMMORTAL;
       else
        crec->ttd = ttd;
-      crec->addr.addr.addr.addr4 = *host_address;
+      crec->addr.addr = *host_address;
       crec->name.namep = host_name;
-      crec->uid = uid++;
+      crec->uid = next_uid();
       cache_hash(crec);
 
-      for (a = daemon->cnames; a; a = a->next)
-       if (hostname_isequal(host_name, a->target))
+      add_dhcp_cname(crec, ttd);
+    }
+}
+#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)))
          {
-           if ((aliasc = dhcp_spare))
-             dhcp_spare = dhcp_spare->next;
-           else /* need new one */
-             aliasc = whine_malloc(sizeof(struct crec));
-           
-           if (aliasc)
+           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 = (p - buff) + bufflen;
+           bytes_needed = snprintf(p, bytes_avail, "%s#%d %u %u", daemon->addrbuff, port, queries, failed_queries);
+           if (bytes_needed >= bytes_avail)
              {
-               aliasc->flags = F_FORWARD | F_NAMEP | F_DHCP | F_CNAME;
-               if (ttd == 0)
-                 aliasc->flags |= F_IMMORTAL;
-               else
-                 aliasc->ttd = ttd;
-               aliasc->name.namep = a->alias;
-               aliasc->addr.cname.cache = crec;
-               aliasc->addr.cname.uid = crec->uid;
-               cache_hash(aliasc);
+               /* 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 = (p - buff) + bufflen;
+               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;
 }
-#endif
-
 
 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."), 
            daemon->cachesize, cache_live_freed, cache_inserted);
   my_syslog(LOG_INFO, _("queries forwarded %u, queries answered locally %u"), 
            daemon->queries_forwarded, daemon->local_answer);
+#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)
@@ -1080,45 +1443,67 @@ 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) 
+           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 ", 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_DS)
              {
-               a = "";
-               if (!is_outdated_cname_pointer(cache))
-                 a = cache_get_name(cache->addr.cname.cache);
+               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);
              }
-#ifdef HAVE_IPV6
-           else 
+           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 if (!(cache->flags & F_NEG) || !(cache->flags & F_FORWARD))
              { 
                a = daemon->addrbuff;
                if (cache->flags & F_IPV4)
                  inet_ntop(AF_INET, &cache->addr.addr, a, ADDRSTRLEN);
+#ifdef HAVE_IPV6
                else if (cache->flags & F_IPV6)
                  inet_ntop(AF_INET6, &cache->addr.addr, a, ADDRSTRLEN);
+#endif
              }
-#else
-            else 
-             a = inet_ntoa(cache->addr.addr.addr.addr4);
+
+           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, "%-30.30s %s%s%s%s%s%s%s%s%s%s  ", a, 
-                        cache->flags & F_IPV4 ? "4" : "",
-                        cache->flags & F_IPV6 ? "6" : "",
-                        cache->flags & F_CNAME ? "C" : "",
+           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" : " ",
                         cache->flags & F_DHCP ? "D" : " ",
                         cache->flags & F_NEG ? "N" : " ",
                         cache->flags & F_NXDOMAIN ? "X" : " ",
-                        cache->flags & F_HOSTS ? "H" : " ");
+                        cache->flags & F_HOSTS ? "H" : " ",
+                        cache->flags & F_DNSSECOK ? "V" : " ");
 #ifdef HAVE_BROKEN_RTC
            p += sprintf(p, "%lu", cache->flags & F_IMMORTAL ? 0: (unsigned long)(cache->ttd - now));
 #else
@@ -1131,28 +1516,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 *str, unsigned short type)
+char *querystr(char *desc, unsigned short type)
 {
   unsigned int i;
-  
-  sprintf(str, "query[type=%d]", 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,"query[%s]", 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)
@@ -1165,13 +1589,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)
     {
@@ -1182,14 +1613,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)
@@ -1213,6 +1637,10 @@ 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)
     {
       source = "forwarded";
@@ -1223,12 +1651,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);
+  if (option_bool(OPT_EXTRALOG))
+    {
+      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
+       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);
 }