diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..43fd62d
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,61 @@
+auto_home.c
+
+direntry.h
+hasdevtcp.h
+hasshsgr.h
+haveip6.h
+haven2i.h
+iopause.h
+select.h
+sockaddr_in6.h
+uint32.h
+uint64.h
+
+socket.lib
+
+*.o
+*.a
+
+auto-str
+axfr-get
+axfrdns
+axfrdns-conf
+cachetest
+chkshsgr
+choose
+compile
+dnscache
+dnscache-conf
+dnsfilter
+dnsip
+dnsip6
+dnsip6q
+dnsipq
+dnsmx
+dnsname
+dnsq
+dnsqr
+dnstrace
+dnstracesort
+dnstxt
+install
+instcheck
+load
+makelib
+pickdns
+pickdns-conf
+pickdns-data
+random-ip
+rbldns
+rbldns-conf
+rbldns-data
+rts
+systype
+tinydns
+tinydns-conf
+tinydns-data
+tinydns-edit
+tinydns-get
+utime
+walldns
+walldns-conf
diff --git a/Makefile b/Makefile
index 9ebf4c8..aef9420 100644
--- a/Makefile
+++ b/Makefile
@@ -31,12 +31,20 @@ compile auto-str.c buffer.h exit.h
 
 auto_home.c: \
 auto-str conf-home
-	./auto-str auto_home `head -1 conf-home` > auto_home.c
+	./auto-str auto_home `head -n 1 conf-home` > auto_home.c
 
 auto_home.o: \
 compile auto_home.c
 	./compile auto_home.c
 
+auto_destdir.c: \
+auto-str conf-home
+	./auto-str auto_home `head -n 1 conf-destdir``head -n 1 conf-home` > auto_destdir.c
+
+auto_destdir.o: \
+compile auto_destdir.c
+	./compile auto_destdir.c
+
 axfr-get: \
 load axfr-get.o iopause.o timeoutread.o timeoutwrite.o dns.a libtai.a \
 alloc.a buffer.a unix.a byte.a
@@ -151,12 +159,12 @@ compile byte_zero.c byte.h
 
 cache.o: \
 compile cache.c alloc.h byte.h uint32.h exit.h tai.h uint64.h cache.h \
-uint32.h uint64.h
+uint32.h uint64.h siphash.h
 	./compile cache.c
 
 cachetest: \
-load cachetest.o cache.o libtai.a buffer.a alloc.a unix.a byte.a
-	./load cachetest cache.o libtai.a buffer.a alloc.a unix.a \
+load cachetest.o siphash.o cache.o dns_random.o libtai.a buffer.a alloc.a unix.a byte.a
+	./load cachetest siphash.o cache.o dns_random.o libtai.a buffer.a alloc.a unix.a \
 	byte.a 
 
 cachetest.o: \
@@ -207,7 +215,7 @@ compile chkshsgr.c exit.h
 choose: \
 warn-auto.sh choose.sh conf-home
 	cat warn-auto.sh choose.sh \
-	| sed s}HOME}"`head -1 conf-home`"}g \
+	| sed s}HOME}"`head -n 1 conf-home`"}g \
 	> choose
 	chmod 755 choose
 
@@ -218,7 +226,7 @@ compile clientloc.c open.h byte.h cdb.h ip6.h
 compile: \
 warn-auto.sh conf-cc
 	( cat warn-auto.sh; \
-	echo exec "`head -1 conf-cc`" '-c $${1+"$$@"}' \
+	echo exec "`head -n 1 conf-cc`" '-c $${1+"$$@"}' \
 	) > compile
 	chmod 755 compile
 
@@ -337,7 +345,7 @@ taia.h tai.h uint64.h taia.h
 dns_transmit.o: \
 compile dns_transmit.c socket.h uint16.h alloc.h error.h byte.h \
 uint16.h dns.h stralloc.h gen_alloc.h iopause.h taia.h tai.h uint64.h \
-taia.h
+taia.h clients.h
 	./compile dns_transmit.c
 
 dns_txt.o: \
@@ -346,10 +354,10 @@ stralloc.h iopause.h taia.h tai.h uint64.h taia.h
 	./compile dns_txt.c
 
 dnscache: \
-load dnscache.o droproot.o okclient.o log.o cache.o query.o \
+load dnscache.o droproot.o okclient.o log.o siphash.o cache.o query.o \
 response.o dd.o roots.o iopause.o prot.o dns.a env.a alloc.a buffer.a \
 libtai.a unix.a byte.a socket.lib
-	./load dnscache droproot.o okclient.o log.o cache.o \
+	./load dnscache droproot.o okclient.o log.o siphash.o cache.o \
 	query.o response.o dd.o roots.o iopause.o prot.o dns.a \
 	env.a alloc.a buffer.a libtai.a unix.a byte.a  `cat \
 	socket.lib`
@@ -371,7 +379,7 @@ compile dnscache.c env.h exit.h scan.h strerr.h error.h ip4.h \
 uint16.h uint64.h socket.h uint16.h dns.h stralloc.h gen_alloc.h \
 iopause.h taia.h tai.h uint64.h taia.h taia.h byte.h roots.h fmt.h \
 iopause.h query.h dns.h uint32.h alloc.h response.h uint32.h cache.h \
-uint32.h uint64.h ndelay.h log.h uint64.h okclient.h droproot.h
+uint32.h uint64.h ndelay.h log.h uint64.h okclient.h droproot.h clients.h
 	./compile dnscache.c
 
 dnsfilter: \
@@ -495,7 +503,7 @@ stralloc.h alloc.h parsetype.h dd.h dns.h stralloc.h iopause.h taia.h
 dnstracesort: \
 warn-auto.sh dnstracesort.sh conf-home
 	cat warn-auto.sh dnstracesort.sh \
-	| sed s}HOME}"`head -1 conf-home`"}g \
+	| sed s}HOME}"`head -n 1 conf-home`"}g \
 	> dnstracesort
 	chmod 755 dnstracesort
 
@@ -573,8 +581,8 @@ compile hier.c auto_home.h
 	./compile hier.c
 
 install: \
-load install.o hier.o auto_home.o buffer.a unix.a byte.a
-	./load install hier.o auto_home.o buffer.a unix.a byte.a 
+load install.o hier.o auto_destdir.o buffer.a unix.a byte.a
+	./load install hier.o auto_destdir.o buffer.a unix.a byte.a
 
 install.o: \
 compile install.c buffer.h strerr.h error.h open.h exit.h
@@ -628,7 +636,7 @@ load: \
 warn-auto.sh conf-ld
 	( cat warn-auto.sh; \
 	echo 'main="$$1"; shift'; \
-	echo exec "`head -1 conf-ld`" \
+	echo exec "`head -n 1 conf-ld`" \
 	'-o "$$main" "$$main".o $${1+"$$@"}' \
 	) > load
 	chmod 755 load
@@ -666,7 +674,7 @@ compile ndelay_on.c ndelay.h
 	./compile ndelay_on.c
 
 okclient.o: \
-compile okclient.c str.h ip4.h okclient.h
+compile okclient.c str.h ip4.h okclient.h env.h
 	./compile okclient.c
 
 open_read.o: \
@@ -816,7 +824,7 @@ openreadclose.h stralloc.h roots.h
 rts: \
 warn-auto.sh rts.sh conf-home
 	cat warn-auto.sh rts.sh \
-	| sed s}HOME}"`head -1 conf-home`"}g \
+	| sed s}HOME}"`head -n 1 conf-home`"}g \
 	> rts
 	chmod 755 rts
 
@@ -851,6 +859,10 @@ sgetopt.o: \
 compile sgetopt.c buffer.h sgetopt.h subgetopt.h subgetopt.h
 	./compile sgetopt.c
 
+siphash.o: \
+compile siphash.c uint32.h uint64.h siphash.h
+	./compile siphash.c
+
 socket.lib: \
 trylsock.c compile load
 	( ( ./compile trylsock.c && \
@@ -1004,8 +1016,8 @@ compile subgetopt.c subgetopt.h
 systype: \
 find-systype.sh conf-cc conf-ld trycpp.c x86cpuid.c
 	( cat warn-auto.sh; \
-	echo CC=\'`head -1 conf-cc`\'; \
-	echo LD=\'`head -1 conf-ld`\'; \
+	echo CC=\'`head -n 1 conf-cc`\'; \
+	echo LD=\'`head -n 1 conf-ld`\'; \
 	cat find-systype.sh; \
 	) | sh > systype
 
diff --git a/TARGETS b/TARGETS
index 8e8e457..a9a901e 100644
--- a/TARGETS
+++ b/TARGETS
@@ -235,6 +235,7 @@ socket_recv6.o
 socket_send6.o
 haveip6.h
 haven2i.h
+siphash.o
 sockaddr_in6.h
 scan_xlong.o
 socket_accept6.o
diff --git a/auto_destdir.c b/auto_destdir.c
new file mode 100644
index 0000000..789fa0b
--- /dev/null
+++ b/auto_destdir.c
@@ -0,0 +1,3 @@
+const char auto_home[] = "\
+\057\150\157\155\145\057\152\154\171\157\057\144\152\142\144\156\163\057\144\145\163\164\057\165\163\162\057\154\157\143\141\154\
+";
diff --git a/axfr-get.c b/axfr-get.c
index f6bf5bd..4cb4ad4 100644
--- a/axfr-get.c
+++ b/axfr-get.c
@@ -210,6 +210,26 @@ unsigned int doit(char *buf,unsigned int len,unsigned int pos)
     if (!stralloc_cats(&line,".:")) return 0;
     if (!stralloc_catulong0(&line,dist,0)) return 0;
   }
+  else if (byte_equal(data,2,DNS_T_SRV)) {
+    uint16 dist, weight, port;
+    if (!stralloc_copys(&line,"S")) return 0;
+    if (!dns_domain_todot_cat(&line,d1)) return 0;
+    if (!stralloc_cats(&line,"::")) return 0;
+    pos = x_copy(buf,len,pos,data,2);
+    uint16_unpack_big(data,&dist);
+    pos = x_copy(buf,len,pos,data,2);
+    uint16_unpack_big(data,&weight);
+    pos = x_copy(buf,len,pos,data,2);
+    uint16_unpack_big(data,&port);
+    x_getname(buf,len,pos,&d1);
+    if (!dns_domain_todot_cat(&line,d1)) return 0;
+    if (!stralloc_cats(&line,".:")) return 0;
+    if (!stralloc_catulong0(&line,dist,0)) return 0;
+    if (!stralloc_cats(&line,":")) return 0;
+    if (!stralloc_catulong0(&line,weight,0)) return 0;
+    if (!stralloc_cats(&line,":")) return 0;
+    if (!stralloc_catulong0(&line,port,0)) return 0;
+  }
   else if (byte_equal(data,2,DNS_T_A) && (dlen == 4)) {
     char ipstr[IP4_FMT];
     if (!stralloc_copys(&line,"+")) return 0;
@@ -218,6 +238,14 @@ unsigned int doit(char *buf,unsigned int len,unsigned int pos)
     x_copy(buf,len,pos,data,4);
     if (!stralloc_catb(&line,ipstr,ip4_fmt(ipstr,data))) return 0;
   }
+  else if (byte_equal(data,2,DNS_T_PTR)) {
+    if (!stralloc_copys(&line,"^")) return 0;
+    if (!dns_domain_todot_cat(&line,d1)) return 0;
+    if (!stralloc_cats(&line,":")) return 0;
+    x_getname(buf,len,pos,&d1);
+    if (!dns_domain_todot_cat(&line,d1)) return 0;
+    if (!stralloc_cats(&line,".")) return 0;
+  }
   else if (byte_equal(data,2,DNS_T_AAAA)) {
     char ipstr[IP6_FMT];
     if (!stralloc_copys(&line,"3")) return 0;
diff --git a/buffer_put.c b/buffer_put.c
index f875f3f..d1327df 100644
--- a/buffer_put.c
+++ b/buffer_put.c
@@ -13,7 +13,7 @@ static int allwrite(int (*op)(),int fd,const char *buf,unsigned int len)
       if (errno == error_intr) continue;
       return -1; /* note that some data may have been written */
     }
-    if (w == 0) ; /* luser's fault */
+    if (w == 0) { }; /* luser's fault */
     buf += w;
     len -= w;
   }
diff --git a/cache.c b/cache.c
index 6302428..4c409ed 100644
--- a/cache.c
+++ b/cache.c
@@ -1,12 +1,21 @@
 #include "alloc.h"
 #include "byte.h"
+#include "dns.h"
 #include "uint32.h"
+#include "uint64.h"
 #include "exit.h"
 #include "tai.h"
 #include "cache.h"
+#include "siphash.h"
 
 uint64 cache_motion = 0;
 
+/* record cache stats */
+/* James Raftery <james@now.ie> 6 Nov. 2003 */
+uint64 cache_hit = 0;
+uint64 cache_miss = 0;
+
+static unsigned char siphash_key[16];
 static char *x = 0;
 static uint32 size;
 static uint32 hsize;
@@ -64,17 +73,11 @@ static uint32 get4(uint32 pos)
 
 static unsigned int hash(const char *key,unsigned int keylen)
 {
-  unsigned int result = 5381;
+  uint64 h;    
+  siphash24((unsigned char *) &h,
+            (const unsigned char *) key, keylen, siphash_key);
 
-  while (keylen) {
-    result = (result << 5) + result;
-    result ^= (unsigned char) *key;
-    ++key;
-    --keylen;
-  }
-  result <<= 2;
-  result &= hsize - 4;
-  return result;
+  return ((uint32) h) & (hsize - 4);
 }
 
 char *cache_get(const char *key,unsigned int keylen,unsigned int *datalen,uint32 *ttl)
@@ -112,15 +115,20 @@ char *cache_get(const char *key,unsigned int keylen,unsigned int *datalen,uint32
         if (u > size - pos - 20 - keylen) cache_impossible();
         *datalen = u;
 
+        cache_hit++;
         return x + pos + 20 + keylen;
       }
     }
     nextpos = prevpos ^ get4(pos);
     prevpos = pos;
     pos = nextpos;
-    if (++loop > 100) return 0; /* to protect against hash flooding */
+    if (++loop > 100) { /* to protect against hash flooding */
+      cache_miss++;
+      return 0;
+    }
   }
 
+  cache_miss++;
   return 0;
 }
 
@@ -183,6 +191,10 @@ void cache_set(const char *key,unsigned int keylen,const char *data,unsigned int
 
 int cache_init(unsigned int cachesize)
 {
+  unsigned int i = 0U;
+  do {
+    siphash_key[i] = (unsigned char) dns_random(0x100);
+  } while (++i < sizeof siphash_key);
   if (x) {
     alloc_free(x);
     x = 0;
diff --git a/cache.h b/cache.h
index f5306c5..0f45e7d 100644
--- a/cache.h
+++ b/cache.h
@@ -5,6 +5,12 @@
 #include "uint64.h"
 
 extern uint64 cache_motion;
+
+/* record cache stats */
+/* James Raftery <james@now.ie> 6 Nov. 2003 */
+extern uint64 cache_hit;
+extern uint64 cache_miss;
+
 extern int cache_init(unsigned int);
 extern void cache_set(const char *,unsigned int,const char *,unsigned int,uint32);
 extern char *cache_get(const char *,unsigned int,unsigned int *,uint32 *);
diff --git a/clients.h b/clients.h
new file mode 100644
index 0000000..983a4ad
--- /dev/null
+++ b/clients.h
@@ -0,0 +1,7 @@
+#ifndef CLIENTS_H
+#define CLIENTS_H
+
+#define MAXUDP 200
+#define MAXTCP 20
+
+#endif /* CLIENTS_H */
diff --git a/conf-cc b/conf-cc
index b315ecb..d224474 100644
--- a/conf-cc
+++ b/conf-cc
@@ -1,3 +1,3 @@
-gcc -O2 -Wimplicit -Wunused -Wcomment -Wchar-subscripts -Wuninitialized -Wshadow -Wcast-qual -Wcast-align -Wwrite-strings
+gcc -O2 -Wall -Wextra -Wno-parentheses -Wno-misleading-indentation -Wno-pointer-sign -Wno-old-style-declaration -Wno-unused-parameter -Wno-sign-compare
 
 This will be used to compile .c files.
diff --git a/conf-destdir b/conf-destdir
new file mode 100644
index 0000000..e69de29
diff --git a/dns.h b/dns.h
index 5398e2b..45456ae 100644
--- a/dns.h
+++ b/dns.h
@@ -4,6 +4,7 @@
 #include "stralloc.h"
 #include "iopause.h"
 #include "taia.h"
+#include "clients.h"
 
 #define DNS_C_IN "\0\1"
 #define DNS_C_ANY "\0\377"
@@ -20,6 +21,7 @@
 #define DNS_T_SIG "\0\30"
 #define DNS_T_KEY "\0\31"
 #define DNS_T_AAAA "\0\34"
+#define DNS_T_SRV "\0\41"
 #define DNS_T_AXFR "\0\374"
 #define DNS_T_ANY "\0\377"
 
@@ -38,8 +40,14 @@ struct dns_transmit {
   char localip[16];
   unsigned int scope_id;
   char qtype[2];
+  struct dns_transmit *master;
+  struct dns_transmit *slaves[MAXUDP];
+  int nslaves;
 } ;
 
+extern void dns_enable_merge(void (*logger)(const char *, const char *,
+      const char *));
+
 extern void dns_random_init(const char *);
 extern unsigned int dns_random(unsigned int);
 
@@ -88,10 +96,7 @@ extern int dns_ip4_qualify(stralloc *,stralloc *,const stralloc *);
 extern int dns_ip6_qualify_rules(stralloc *,stralloc *,const stralloc *,const stralloc *);
 extern int dns_ip6_qualify(stralloc *,stralloc *,const stralloc *);
 
-#define DNS_IP6_INT 0
-#define DNS_IP6_ARPA 1
-
-extern int dns_name6_domain(char *,const char *,int);
+extern int dns_name6_domain(char *,const char *);
 #define DNS_NAME6_DOMAIN (4*16+11)
 
 #endif
diff --git a/dns_name.c b/dns_name.c
index 518a0c0..07b9b44 100644
--- a/dns_name.c
+++ b/dns_name.c
@@ -48,23 +48,16 @@ int dns_name4(stralloc *out,const char ip[4])
   return 0;
 }
 
-int dns_name6_inner(stralloc *out,const char ip[16],int t)
+int dns_name6(stralloc *out,const char ip[16])
 {
   char name[DNS_NAME6_DOMAIN];
 
-  dns_name6_domain(name,ip,t);
+  if (ip6_isv4mapped(ip))
+    return dns_name4(out,ip+12);
+  dns_name6_domain(name,ip);
   if (dns_resolve(name,DNS_T_PTR) == -1) return -1;
   if (dns_name_packet(out,dns_resolve_tx.packet,dns_resolve_tx.packetlen) == -1) return -1;
   dns_transmit_free(&dns_resolve_tx);
   dns_domain_free(&q);
   return 0;
 }
-
-int dns_name6(stralloc *out,const char ip[16])
-{
-  if (ip6_isv4mapped(ip))
-    return dns_name4(out,ip+12);
-  if (dns_name6_inner(out,ip,DNS_IP6_ARPA)) return -1;
-  if (!out->len) return dns_name6_inner(out,ip,DNS_IP6_INT);
-  return 0;
-}
diff --git a/dns_nd6.c b/dns_nd6.c
index 6dbeb89..0248840 100644
--- a/dns_nd6.c
+++ b/dns_nd6.c
@@ -15,7 +15,7 @@ unsigned int mkint(unsigned char a,unsigned char b) {
   return ((unsigned int)a << 8) + (unsigned int)b;
 }
 
-int dns_name6_domain(char name[DNS_NAME6_DOMAIN],const char ip[16],int t)
+int dns_name6_domain(char name[DNS_NAME6_DOMAIN],const char ip[16])
 {
   unsigned int j;
 
@@ -25,11 +25,6 @@ int dns_name6_domain(char name[DNS_NAME6_DOMAIN],const char ip[16],int t)
     name[j*4+2]=1;
     name[j*4+3]=tohex((unsigned char)ip[15-j] >> 4);
   }
-  if (t==DNS_IP6_INT)
-    byte_copy(name + 4*16,9,"\3ip6\3int\0");
-  else if (t==DNS_IP6_ARPA)
-    byte_copy(name + 4*16,10,"\3ip6\4arpa\0");
-  else return 0;
-  return 4*16+9+t;
+  byte_copy(name + 4*16,10,"\3ip6\4arpa\0");
+  return 4*16+10;
 }
-
diff --git a/dns_rcip.c b/dns_rcip.c
index efd1b21..9923183 100644
--- a/dns_rcip.c
+++ b/dns_rcip.c
@@ -8,7 +8,7 @@
 
 static stralloc data = {0};
 
-static int init(char ip[256])
+static int init(char ip[512])
 {
   int i;
   int j;
@@ -54,16 +54,16 @@ static int init(char ip[256])
     byte_copy(ip,16,"\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\1");
     iplen = 16;
   }
-  byte_zero(ip + iplen,256 - iplen);
+  byte_zero(ip + iplen,512 - iplen);
   return 0;
 }
 
 static int ok = 0;
 static unsigned int uses;
 static struct taia deadline;
-static char ip[256]; /* defined if ok */
+static char ip[512]; /* defined if ok */
 
-int dns_resolvconfip(char s[256])
+int dns_resolvconfip(char s[512])
 {
   struct taia now;
 
@@ -80,6 +80,6 @@ int dns_resolvconfip(char s[256])
   }
 
   --uses;
-  byte_copy(s,256,ip);
+  byte_copy(s,512,ip);
   return 0;
 }
diff --git a/dns_resolve.c b/dns_resolve.c
index 82b5bbb..b588784 100644
--- a/dns_resolve.c
+++ b/dns_resolve.c
@@ -10,7 +10,7 @@ int dns_resolve(const char *q,const char qtype[2])
 {
   struct taia stamp;
   struct taia deadline;
-  char servers[256];
+  char servers[512];
   iopause_fd x[1];
   int r;
 
diff --git a/dns_transmit.c b/dns_transmit.c
index cba1fd2..1d46760 100644
--- a/dns_transmit.c
+++ b/dns_transmit.c
@@ -8,6 +8,62 @@
 #include "uint16.h"
 #include "dns.h"
 #include "ip6.h"
+#include "strerr.h"
+
+static int merge_enable;
+static void (*merge_logger)(const char *, const char *, const char *);
+void dns_enable_merge(void (*f)(const char *, const char *, const char *))
+{
+  merge_enable = 1;
+  merge_logger = f;
+}
+
+static int merge_equal(struct dns_transmit *a, struct dns_transmit *b)
+{
+  const char *ip1 = a->servers + 16 * a->curserver;
+  const char *ip2 = b->servers + 16 * b->curserver;
+  return
+    byte_equal(ip1, 16, ip2) &&
+    byte_equal(a->qtype, 2, b->qtype) &&
+    dns_domain_equal(a->query + 14, b->query + 14);
+}
+
+struct dns_transmit *inprogress[MAXUDP];
+
+static int try_merge(struct dns_transmit *d)
+{
+  int i;
+  for (i = 0; i < MAXUDP; i++) {
+    if (!inprogress[i]) continue;
+    if (!merge_equal(d, inprogress[i])) continue;
+    if (inprogress[i]->nslaves == MAXUDP) continue;
+    d->master = inprogress[i];
+    inprogress[i]->slaves[inprogress[i]->nslaves++] = d;
+    return 1;
+  }
+  return 0;
+}
+
+static void register_inprogress(struct dns_transmit *d)
+{
+  int i;
+  for (i = 0; i < MAXUDP; i++) {
+    if (!inprogress[i]) {
+      inprogress[i] = d;
+      return;
+    }
+  }
+  strerr_die1x(100, "BUG: out of inprogress slots");
+}
+
+static void unregister_inprogress(struct dns_transmit *d)
+{
+  int i;
+  for (i = 0; i < MAXUDP; i++) {
+    if (inprogress[i] == d)
+      inprogress[i] = 0;
+  }
+}
 
 static int serverwantstcp(const char *buf,unsigned int len)
 {
@@ -60,8 +116,31 @@ static void packetfree(struct dns_transmit *d)
   d->packet = 0;
 }
 
+static void mergefree(struct dns_transmit *d)
+{
+  int i;
+  if (merge_enable)
+    unregister_inprogress(d);
+  /* unregister us from our master */
+  if (d->master) {
+    for (i = 0; i < d->master->nslaves; i++)
+      if (d->master->slaves[i] == d)
+        d->master->slaves[i] = 0;
+    d->master = 0;
+  }
+  /* and unregister all of our slaves from us */
+  for (i = 0; i < d->nslaves; i++) {
+    if (d->slaves[i]) {
+      d->slaves[i]->master = NULL;
+      d->slaves[i] = 0;
+    }
+  }
+  d->nslaves = 0;
+}
+
 static void queryfree(struct dns_transmit *d)
 {
+  mergefree(d);
   if (!d->query) return;
   alloc_free(d->query);
   d->query = 0;
@@ -100,11 +179,18 @@ static int thisudp(struct dns_transmit *d)
   const char *ip;
 
   socketfree(d);
+  mergefree(d);
 
   while (d->udploop < 4) {
-    for (;d->curserver < 16;++d->curserver) {
+    for (;d->curserver < 32;++d->curserver) {
       ip = d->servers + 16 * d->curserver;
       if (byte_diff(ip,16,V6any)) {
+        if (merge_enable && try_merge(d)) {
+          if (merge_logger)
+            merge_logger(ip, d->qtype, d->query + 14);
+          return 0;
+        }
+
 	d->query[2] = dns_random(256);
 	d->query[3] = dns_random(256);
   
@@ -119,6 +205,8 @@ static int thisudp(struct dns_transmit *d)
             taia_uint(&d->deadline,timeouts[d->udploop]);
             taia_add(&d->deadline,&d->deadline,&now);
             d->tcpstate = 0;
+            if (merge_enable)
+              register_inprogress(d);
             return 0;
           }
   
@@ -153,7 +241,7 @@ static int thistcp(struct dns_transmit *d)
   socketfree(d);
   packetfree(d);
 
-  for (;d->curserver < 16;++d->curserver) {
+  for (;d->curserver < 32;++d->curserver) {
     ip = d->servers + 16 * d->curserver;
     if (byte_diff(ip,16,V6any)) {
       d->query[2] = dns_random(256);
@@ -167,6 +255,7 @@ static int thistcp(struct dns_transmit *d)
       taia_uint(&d->deadline,10);
       taia_add(&d->deadline,&d->deadline,&now);
       if (socket_connect6(d->s1 - 1,ip,53,d->scope_id) == 0) {
+        d->pos = 0;
         d->tcpstate = 2;
         return 0;
       }
@@ -194,7 +283,7 @@ static int nexttcp(struct dns_transmit *d)
   return thistcp(d);
 }
 
-int dns_transmit_start(struct dns_transmit *d,const char servers[256],int flagrecursive,const char *q,const char qtype[2],const char localip[16])
+int dns_transmit_start(struct dns_transmit *d,const char servers[512],int flagrecursive,const char *q,const char qtype[2],const char localip[16])
 {
   unsigned int len;
 
@@ -218,7 +307,7 @@ int dns_transmit_start(struct dns_transmit *d,const char servers[256],int flagre
 
   d->udploop = flagrecursive ? 1 : 0;
 
-  if (len + 16 > 512) return firsttcp(d);
+  if ((len + 16 > 512) || byte_equal(qtype,2,DNS_T_ANY)) return firsttcp(d);
   return firstudp(d);
 }
 
@@ -227,8 +316,12 @@ void dns_transmit_io(struct dns_transmit *d,iopause_fd *x,struct taia *deadline)
   x->fd = d->s1 - 1;
 
   switch(d->tcpstate) {
-    case 0: case 3: case 4: case 5:
-      x->events = IOPAUSE_READ;
+    case 0:
+      if (d->master) return;
+      if (d->packet) { taia_now(deadline); return; }
+      /* otherwise, fall through */
+    case 3: case 4: case 5:
+        x->events = IOPAUSE_READ;
       break;
     case 1: case 2:
       x->events = IOPAUSE_WRITE;
@@ -245,10 +338,14 @@ int dns_transmit_get(struct dns_transmit *d,const iopause_fd *x,const struct tai
   unsigned char ch;
   int r;
   int fd;
+  int i;
 
   errno = error_io;
   fd = d->s1 - 1;
 
+  if (d->tcpstate == 0 && d->master) return 0;
+  if (d->tcpstate == 0 && d->packet) return 1;
+
   if (!x->revents) {
     if (taia_less(when,&d->deadline)) return 0;
     errno = error_timeout;
@@ -280,6 +377,15 @@ have sent query to curserver on UDP socket s
     d->packet = alloc(d->packetlen);
     if (!d->packet) { dns_transmit_free(d); return -1; }
     byte_copy(d->packet,d->packetlen,udpbuf);
+
+    for (i = 0; i < d->nslaves; i++) {
+      if (!d->slaves[i]) continue;
+      d->slaves[i]->packetlen = d->packetlen;
+      d->slaves[i]->packet = alloc(d->packetlen);
+      if (!d->slaves[i]->packet) { dns_transmit_free(d->slaves[i]); continue; }
+      byte_copy(d->slaves[i]->packet,d->packetlen,udpbuf);
+    }
+
     queryfree(d);
     return 1;
   }
diff --git a/dnscache.c b/dnscache.c
index ebf8b20..1ba45a7 100644
--- a/dnscache.c
+++ b/dnscache.c
@@ -1,11 +1,12 @@
 #include <unistd.h>
+#include <signal.h>
 #include "env.h"
 #include "exit.h"
 #include "scan.h"
 #include "strerr.h"
 #include "error.h"
-#include "ip4.h"
 #include "ip6.h"
+#include "str.h"
 #include "uint16.h"
 #include "uint64.h"
 #include "socket.h"
@@ -53,19 +54,27 @@ static int packetquery(char *buf,unsigned int len,char **q,char qtype[2],char qc
 
 
 static char myipoutgoing[16];
-static char myipincoming[16];
-static char buf[1024];
+static char buf[65535];
 uint64 numqueries = 0;
 
+struct interf {
+  char ip[16];
+  int udp53;
+  int tcp53;
+  iopause_fd *udp53io;
+  iopause_fd *tcp53io;
+
+  struct interf *next;
+};
 
-static int udp53;
+struct interf *interhead = 0;
 
-#define MAXUDP 200
 static struct udpclient {
   struct query q;
   struct taia start;
   uint64 active; /* query number, if active; otherwise 0 */
   iopause_fd *io;
+  int fd;
   char ip[16];
   uint16 port;
   char id[2];
@@ -73,11 +82,15 @@ static struct udpclient {
 } u[MAXUDP];
 int uactive = 0;
 
+#define MAXSOA 20
+int soaactive = 0;
+
 void u_drop(int j)
 {
   if (!u[j].active) return;
   log_querydrop(&u[j].active);
   u[j].active = 0; --uactive;
+  if (byte_equal(u[j].q.type,2,DNS_T_SOA)) --soaactive;
 }
 
 void u_respond(int j)
@@ -85,12 +98,13 @@ void u_respond(int j)
   if (!u[j].active) return;
   response_id(u[j].id);
   if (response_len > 512) response_tc();
-  socket_send6(udp53,response,response_len,u[j].ip,u[j].port,u[j].scope_id);
+  socket_send6(u[j].fd,response,response_len,u[j].ip,u[j].port,u[j].scope_id);
   log_querydone(&u[j].active,response_len);
   u[j].active = 0; --uactive;
+  if (byte_equal(u[j].q.type,2,DNS_T_SOA)) --soaactive;
 }
 
-void u_new(void)
+void u_new(int fd)
 {
   int j;
   int i;
@@ -115,8 +129,9 @@ void u_new(void)
 
   x = u + j;
   taia_now(&x->start);
+  x->fd=fd;
 
-  len = socket_recv6(udp53,buf,sizeof buf,x->ip,&x->port,&x->scope_id);
+  len = socket_recv6(x->fd,buf,sizeof buf,x->ip,&x->port,&x->scope_id);
   if (len == -1) return;
   if (len >= sizeof buf) return;
   if (x->port < 1024) if (x->port != 53) return;
@@ -126,6 +141,16 @@ void u_new(void)
 
   x->active = ++numqueries; ++uactive;
   log_query(&x->active,x->ip,x->port,x->id,q,qtype);
+
+  if (byte_equal(qtype,2,DNS_T_SOA)) {
+    if (soaactive >= MAXSOA) {
+      log_querydropmaxsoa(&x->active);
+      x->active = 0; --uactive;
+      return;
+    }
+    ++soaactive;
+  }
+
   switch(query_start(&x->q,q,qtype,qclass,myipoutgoing,interface)) {
     case -1:
       u_drop(j);
@@ -135,15 +160,13 @@ void u_new(void)
   }
 }
 
-static int tcp53;
-
-#define MAXTCP 20
 struct tcpclient {
   struct query q;
   struct taia start;
   struct taia timeout;
   uint64 active; /* query number or 1, if active; otherwise 0 */
   iopause_fd *io;
+  int fd;
   char ip[16]; /* send response to this address */
   uint16 port; /* send response to this port */
   char id[2];
@@ -214,8 +237,9 @@ void t_respond(int j)
 void t_rw(int j)
 {
   struct tcpclient *x;
-  char ch;
+  char *ch;
   static char *q = 0;
+  unsigned int toread;
   char qtype[2];
   char qclass[2];
   int r;
@@ -231,20 +255,25 @@ void t_rw(int j)
     }
     return;
   }
-
-  r = read(x->tcp,&ch,1);
+  switch (x->state) {
+    case 1: toread = 2U; break;
+    case 2: toread = 1U; break;
+    case 3: toread = x->len - x->pos; break;
+    default: return; /* impossible */
+  }
+  r = read(x->tcp, buf, toread);
   if (r == 0) { errno = error_pipe; t_close(j); return; }
   if (r < 0) { t_close(j); return; }
-
+  ch = buf;
   if (x->state == 1) {
-    x->len = (unsigned char) ch;
+    x->len = (unsigned char) *ch++;
     x->len <<= 8;
     x->state = 2;
-    return;
+    if (--r <= 0) return;
   }
   if (x->state == 2) {
-    x->len += (unsigned char) ch;
-    if (!x->len) { errno = error_proto; t_close(j); return; }
+    x->len += (unsigned char) *ch;
+    if (x->len < 12) { errno = error_proto; t_close(j); return; }
     x->buf = alloc(x->len);
     if (!x->buf) { t_close(j); return; }
     x->pos = 0;
@@ -254,7 +283,8 @@ void t_rw(int j)
 
   if (x->state != 3) return; /* impossible */
 
-  x->buf[x->pos++] = ch;
+  byte_copy(&x->buf[x->pos], r, ch);
+  x->pos += r;
   if (x->pos < x->len) return;
 
   if (!packetquery(x->buf,x->len,&q,qtype,qclass,x->id)) { t_close(j); return; }
@@ -273,7 +303,7 @@ void t_rw(int j)
   x->state = 0;
 }
 
-void t_new(void)
+void t_new(int fd)
 {
   int i;
   int j;
@@ -297,8 +327,9 @@ void t_new(void)
 
   x = t + j;
   taia_now(&x->start);
+  x->fd=fd;
 
-  x->tcp = socket_accept6(tcp53,x->ip,&x->port,&x->scope_id);
+  x->tcp = socket_accept6(x->fd,x->ip,&x->port,&x->scope_id);
   if (x->tcp == -1) return;
   if (x->port < 1024) if (x->port != 53) { close(x->tcp); return; }
   if (!okclient(x->ip)) { close(x->tcp); return; }
@@ -311,19 +342,24 @@ void t_new(void)
   log_tcpopen(x->ip,x->port);
 }
 
+#define FATAL "dnscache: fatal: "
 
-iopause_fd io[3 + MAXUDP + MAXTCP];
-iopause_fd *udp53io;
-iopause_fd *tcp53io;
+iopause_fd *io = 0;
+int numio;
 
 static void doit(void)
 {
   int j;
   struct taia deadline;
   struct taia stamp;
+  struct interf *inter;
   int iolen;
   int r;
 
+  io = (iopause_fd *) alloc((numio + 1 + MAXUDP + MAXTCP) * sizeof(iopause_fd));
+  if (!io)
+    strerr_die2sys(111,FATAL,"unable to alloc io: ");
+
   for (;;) {
     taia_now(&stamp);
     taia_uint(&deadline,120);
@@ -331,13 +367,15 @@ static void doit(void)
 
     iolen = 0;
 
-    udp53io = io + iolen++;
-    udp53io->fd = udp53;
-    udp53io->events = IOPAUSE_READ;
+    for (inter = interhead; inter != 0; inter = inter->next) {
+      inter->udp53io = io + iolen++;
+      inter->udp53io->fd = inter->udp53;
+      inter->udp53io->events = IOPAUSE_READ;
 
-    tcp53io = io + iolen++;
-    tcp53io->fd = tcp53;
-    tcp53io->events = IOPAUSE_READ;
+      inter->tcp53io = io + iolen++;
+      inter->tcp53io->fd = inter->tcp53;
+      inter->tcp53io->events = IOPAUSE_READ;
+    }
 
     for (j = 0;j < MAXUDP;++j)
       if (u[j].active) {
@@ -379,23 +417,30 @@ static void doit(void)
 	    t_rw(j);
       }
 
-    if (udp53io)
-      if (udp53io->revents)
-	u_new();
-
-    if (tcp53io)
-      if (tcp53io->revents)
-	t_new();
-  }
+    for (inter = interhead; inter != 0; inter = inter->next) {
+	  if (inter->udp53io)
+	    if (inter->udp53io->revents)
+	      u_new(inter->udp53);
+	
+      if (inter->tcp53io)
+	    if (inter->tcp53io->revents)
+	      t_new(inter->tcp53);
+	    }
+	  }
 }
   
-#define FATAL "dnscache: fatal: "
-
 char seed[128];
 
 int main()
 {
   char *x;
+  int len;
+  int pos;
+  int oldpos;
+  char iptmp[16];
+  char iperr[IP6_FMT];
+  struct interf *inter;
+  struct interf *itmp;
   unsigned int i, j, k;
   unsigned long cachesize;
   static stralloc sa = {0};
@@ -403,34 +448,54 @@ int main()
   x = env_get("INTERFACE");
   if (x) scan_ulong(x,&interface);
 
+  signal(SIGPIPE, SIG_IGN);
   x = env_get("IP");
   if (!x)
     strerr_die2x(111,FATAL,"$IP not set");
-  if (!ip6_scan(x,myipincoming))
-    strerr_die3x(111,FATAL,"unable to parse IP address ",x);
+  len = str_len(x);
+  numio = pos = oldpos = 0;
+  while (pos < len) {
+    if (pos) oldpos = pos + 1;
+    pos = oldpos + str_chr(x + oldpos,',');
+    x[pos] = 0;
+    if (!str_len(x + oldpos)) continue;
+
+    if (!ip6_scan(x + oldpos,iptmp))
+      strerr_die3x(111,FATAL,"unable to parse IP address ",x + oldpos);
+
+    inter = (struct interf *) alloc(sizeof(struct interf));
+
+    if (interhead == 0) interhead = inter;
+    else if (interhead->next == 0) interhead->next = inter;
+    else {
+      for (itmp = interhead; itmp->next != 0; itmp = itmp->next);
+      itmp->next = inter;
+    }
 
-#if 0
-  /* if if IP is a mapped-IPv4 address, disable IPv6 functionality */
-  /* this is actually a bad idea */
-  if (ip6_isv4mapped(myipincoming))
-    noipv6 = 1;
-#endif
-
-  udp53 = socket_udp6();
-  if (udp53 == -1)
-    strerr_die2sys(111,FATAL,"unable to create UDP socket: ");
-  if (socket_bind6_reuse(udp53,myipincoming,53,interface) == -1)
-    strerr_die2sys(111,FATAL,"unable to bind UDP socket: ");
-
-  tcp53 = socket_tcp6();
-  if (tcp53 == -1)
-    strerr_die2sys(111,FATAL,"unable to create TCP socket: ");
-  if (socket_bind6_reuse(tcp53,myipincoming,53,interface) == -1)
-    strerr_die2sys(111,FATAL,"unable to bind TCP socket: ");
+    inter->next = 0;
+    inter->udp53 = socket_udp6();
+    if (inter->udp53 == -1)
+      strerr_die4sys(111,FATAL,"unable to create UDP socket for IP address ",x + oldpos,": ");
+    if (socket_bind6_reuse(inter->udp53,iptmp,53,interface) == -1)
+      strerr_die4sys(111,FATAL,"unable to bind UDP socket for IP address ",x + oldpos,": ");
+
+    inter->tcp53 = socket_tcp6();
+    if (inter->tcp53 == -1)
+      strerr_die4sys(111,FATAL,"unable to create TCP socket for IP address ",x + oldpos,": ");
+    if (socket_bind6_reuse(inter->tcp53,iptmp,53,interface) == -1)
+      strerr_die4sys(111,FATAL,"unable to bind TCP socket for IP address ",x + oldpos,": ");
+
+    numio++;
+    log_listen(iptmp);
+  }
+
+  if (interhead == 0)
+    strerr_die2x(111,FATAL,"no interfaces to listen on");
 
   droproot(FATAL);
 
-  socket_tryreservein(udp53,131072);
+  for (inter = interhead; inter != 0; inter = inter->next)
+    socket_tryreservein(inter->udp53,131072);
 
   byte_zero(seed,sizeof seed);
   read(0,seed,sizeof seed);
@@ -468,12 +533,17 @@ int main()
     response_hidettl();
   if (env_get("FORWARDONLY"))
     query_forwardonly();
+  if (env_get("MERGEQUERIES"))
+    dns_enable_merge(log_merge);
 
   if (!roots_init())
     strerr_die2sys(111,FATAL,"unable to read servers: ");
 
-  if (socket_listen(tcp53,20) == -1)
-    strerr_die2sys(111,FATAL,"unable to listen on TCP socket: ");
+  for (inter = interhead; inter != 0; inter = inter->next)
+    if (socket_listen(inter->tcp53,20) == -1) {
+      iperr[ip6_fmt(iperr,inter->ip)] = 0;
+      strerr_die4sys(111,FATAL,"unable to listen on TCP socket for IP ",iperr,": ");
+    }
 
   log_startup();
   doit();
diff --git a/dnsfilter.c b/dnsfilter.c
index 822ff1e..e2b303c 100644
--- a/dnsfilter.c
+++ b/dnsfilter.c
@@ -45,7 +45,7 @@ int flag0 = 1;
 iopause_fd *io;
 int iolen;
 
-char servers[256];
+char servers[512];
 char ip[4];
 char name[DNS_NAME4_DOMAIN];
 
diff --git a/dnsq.c b/dnsq.c
index e00bee2..1f0c654 100644
--- a/dnsq.c
+++ b/dnsq.c
@@ -26,7 +26,7 @@ void oops(void)
 
 static struct dns_transmit tx;
 
-int resolve(char *q,char qtype[2],char servers[256])
+int resolve(char *q,char qtype[2],char servers[512])
 {
   struct taia stamp;
   struct taia deadline;
@@ -49,7 +49,7 @@ int resolve(char *q,char qtype[2],char servers[256])
   return 0;
 }
 
-char servers[256];
+char servers[512];
 static stralloc ip;
 static stralloc fqdn;
 
@@ -76,8 +76,8 @@ int main(int argc,char **argv)
   if (!*++argv) usage();
   if (!stralloc_copys(&out,*argv)) oops();
   if (dns_ip6_qualify(&ip,&fqdn,&out) == -1) oops();
-  if (ip.len >= 256) ip.len = 256;
-  byte_zero(servers,256);
+  if (ip.len >= 512) ip.len = 512;
+  byte_zero(servers,512);
   byte_copy(servers,ip.len,ip.s);
 
   if (!stralloc_copys(&out,"")) oops();
diff --git a/dnsroots.global b/dnsroots.global
index 3b567e1..9fb41d0 100644
--- a/dnsroots.global
+++ b/dnsroots.global
@@ -1,13 +1,13 @@
 198.41.0.4
-128.9.0.107
+199.9.14.201
 192.33.4.12
-128.8.10.90
+199.7.91.13
 192.203.230.10
 192.5.5.241
 192.112.36.4
-128.63.2.53
+198.97.190.53
 192.36.148.17
-198.41.0.10
+192.58.128.30
 193.0.14.129
-198.32.64.12
+199.7.83.42
 202.12.27.33
diff --git a/dnstrace.c b/dnstrace.c
index 07fe1e9..5f1795e 100644
--- a/dnstrace.c
+++ b/dnstrace.c
@@ -48,13 +48,13 @@ int resolve(char *q,char qtype[2],char ip[16])
   struct taia start;
   struct taia stamp;
   struct taia deadline;
-  char servers[256];
+  char servers[512];
   iopause_fd x[1];
   int r;
 
   taia_now(&start);
 
-  byte_zero(servers,256);
+  byte_zero(servers,512);
   byte_copy(servers,16,ip);
 
   if (dns_transmit_start(&tx,servers,0,q,qtype,V6any) == -1) return -1;
diff --git a/dnstracesort.sh b/dnstracesort.sh
index e57359c..108ef2f 100644
--- a/dnstracesort.sh
+++ b/dnstracesort.sh
@@ -12,7 +12,7 @@ awk -F: '
     }
     print
   }
-' | sort -t: +0 -2 +4 +3 -4 +2 -3 | uniq | awk -F: '
+' | sort -t: -k 1,3 -k 5 -k 4,5 -k 3,4 | uniq | awk -F: '
   {
     type = $1
     q = $2
diff --git a/hier.c b/hier.c
index b7dd760..9cb9717 100644
--- a/hier.c
+++ b/hier.c
@@ -6,8 +6,6 @@ extern void c(const char* home,const char* subdir,const char* file,int uid,int g
 
 void hier()
 {
-  c("/","etc","dnsroots.global",-1,-1,0644);
-
   h(auto_home,-1,-1,02755);
   d(auto_home,"bin",-1,-1,02755);
 
diff --git a/log.c b/log.c
index df465e2..1b87211 100644
--- a/log.c
+++ b/log.c
@@ -118,6 +118,12 @@ void log_querydrop(uint64 *qnum)
   line();
 }
 
+void log_querydropmaxsoa(uint64 *qnum)
+{
+  string("drop "); number(*qnum); space(); string("maxsoa");
+  line();
+}
+
 void log_tcpopen(const char client[16],unsigned int port)
 {
   string("tcpopen ");
@@ -134,14 +140,14 @@ void log_tcpclose(const char client[16],unsigned int port)
   line();
 }
 
-void log_tx(const char *q,const char qtype[2],const char *control,const char servers[256],unsigned int gluelessness)
+void log_tx(const char *q,const char qtype[2],const char *control,const char servers[512],unsigned int gluelessness)
 {
   int i;
 
   string("tx "); number(gluelessness); space();
   logtype(qtype); space(); name(q); space();
   name(control);
-  for (i = 0;i < 256;i += 16)
+  for (i = 0;i < 512;i += 16)
     if (byte_diff(servers + i,16,V6any)) {
       space();
       ip(servers + i);
@@ -149,6 +155,12 @@ void log_tx(const char *q,const char qtype[2],const char *control,const char ser
   line();
 }
 
+void log_merge(const char *addr, const char qtype[2], const char *q)
+{
+  string("merge "); ip(addr); space(); logtype(qtype); space(); name(q);
+  line();
+}
+
 void log_cachedanswer(const char *q,const char type[2])
 {
   string("cached "); logtype(type); space();
@@ -195,6 +207,13 @@ void log_lame(const char server[16],const char *control,const char *referral)
   line();
 }
 
+void log_ignore_referral(const char server[16],const char * control, const char *referral)
+{
+  string("ignored referral "); ip(server); space();
+  name(control); space(); name(referral);
+  line();
+}
+
 void log_servfail(const char *dn)
 {
   const char *x = error_str(errno);
@@ -275,6 +294,12 @@ void log_stats(void)
 {
   extern uint64 numqueries;
   extern uint64 cache_motion;
+
+  /* record cache stats */
+  /* James Raftery <james@now.ie> 6 Nov. 2003 */
+  extern uint64 cache_hit;
+  extern uint64 cache_miss;
+
   extern int uactive;
   extern int tactive;
 
@@ -282,6 +307,15 @@ void log_stats(void)
   number(numqueries); space();
   number(cache_motion); space();
   number(uactive); space();
-  number(tactive);
+  number(tactive); space();
+  number(cache_hit); space();
+  number(cache_miss);
+  line();
+}
+
+void log_listen(const char addr[16])
+{
+  string("listening on ");
+  ip(addr);
   line();
 }
diff --git a/log.h b/log.h
index fe62fa3..67463a7 100644
--- a/log.h
+++ b/log.h
@@ -4,9 +4,11 @@
 #include "uint64.h"
 
 extern void log_startup(void);
+extern void log_listen(const char *);
 
 extern void log_query(uint64 *,const char *,unsigned int,const char *,const char *,const char *);
 extern void log_querydrop(uint64 *);
+extern void log_querydropmaxsoa(uint64 *) ;
 extern void log_querydone(uint64 *,unsigned int);
 
 extern void log_tcpopen(const char *,unsigned int);
@@ -18,11 +20,13 @@ extern void log_cachednxdomain(const char *);
 extern void log_cachedns(const char *,const char *);
 
 extern void log_tx(const char *,const char *,const char *,const char *,unsigned int);
+extern void log_merge(const char *, const char *, const char *);
 
 extern void log_nxdomain(const char *,const char *,unsigned int);
 extern void log_nodata(const char *,const char *,const char *,unsigned int);
 extern void log_servfail(const char *);
 extern void log_lame(const char *,const char *,const char *);
+extern void log_ignore_referral(const char *,const char *,const char *);
 
 extern void log_rr(const char *,const char *,const char *,const char *,unsigned int,unsigned int);
 extern void log_rrns(const char *,const char *,const char *,unsigned int);
diff --git a/okclient.c b/okclient.c
index 9a0d3c6..9368846 100644
--- a/okclient.c
+++ b/okclient.c
@@ -5,15 +5,26 @@
 #include "ip6.h"
 #include "byte.h"
 #include "okclient.h"
+#include "env.h"
 
 static char fn[3 + IP6_FMT];
 
 int okclient(char ip[16])
 {
+  static int init_done = 0;
   struct stat st;
   int i;
   char sep;
 
+  if (!init_done) {
+    if (env_get("OKCLIENT"))
+      init_done = 1;
+    else
+      init_done = 2;
+  }
+  if (init_done == 1)
+    return 1;
+
   fn[0] = 'i';
   fn[1] = 'p';
   fn[2] = '/';
diff --git a/pickdns-data.c b/pickdns-data.c
index 60cabb0..d013c55 100644
--- a/pickdns-data.c
+++ b/pickdns-data.c
@@ -123,7 +123,7 @@ void syntaxerror(const char *why)
 }
 void die_datatmp(void)
 {
-  strerr_die2sys(111,FATAL,"unable to create data.tmp: ");
+  strerr_die2sys(111,FATAL,"unable to create data.cdb.tmp: ");
 }
 
 int main()
@@ -142,7 +142,7 @@ int main()
   if (fd == -1) strerr_die2sys(111,FATAL,"unable to open data: ");
   buffer_init(&b,buffer_unixread,fd,bspace,sizeof bspace);
 
-  fdcdb = open_trunc("data.tmp");
+  fdcdb = open_trunc("data.cdb.tmp");
   if (fdcdb == -1) die_datatmp();
   if (cdb_make_start(&cdb,fdcdb) == -1) die_datatmp();
 
@@ -223,8 +223,8 @@ int main()
   if (cdb_make_finish(&cdb) == -1) die_datatmp();
   if (fsync(fdcdb) == -1) die_datatmp();
   if (close(fdcdb) == -1) die_datatmp(); /* NFS stupidity */
-  if (rename("data.tmp","data.cdb") == -1)
-    strerr_die2sys(111,FATAL,"unable to move data.tmp to data.cdb: ");
+  if (rename("data.cdb.tmp","data.cdb") == -1)
+    strerr_die2sys(111,FATAL,"unable to move data.cdb.tmp to data.cdb: ");
 
   _exit(0);
 }
diff --git a/query.c b/query.c
index 993dc79..6de180d 100644
--- a/query.c
+++ b/query.c
@@ -94,6 +94,21 @@ static void cleanup(struct query *z)
   }
 }
 
+static int move_name_to_alias(struct query *z,uint32 ttl)
+{
+  int j ;
+
+  if (z->alias[QUERY_MAXALIAS - 1]) return 0 ;
+  for (j = QUERY_MAXALIAS - 1;j > 0;--j)
+    z->alias[j] = z->alias[j - 1];
+  for (j = QUERY_MAXALIAS - 1;j > 0;--j)
+    z->aliasttl[j] = z->aliasttl[j - 1];
+  z->alias[0] = z->name[0];
+  z->aliasttl[0] = ttl;
+  z->name[0] = 0;
+  return 1 ;
+}
+
 static int rqa(struct query *z)
 {
   int i;
@@ -126,7 +141,6 @@ static int globalip(char *d,char ip[16])
 static char *t1 = 0;
 static char *t2 = 0;
 static char *t3 = 0;
-static char *cname = 0;
 static char *referral = 0;
 static unsigned int *records = 0;
 
@@ -181,15 +195,14 @@ static int doit(struct query *z,int state)
   uint16 datalen;
   char *control;
   char *d;
+  char *owner_name = 0 ;
   const char *dtype;
   unsigned int dlen;
   int flagout;
-  int flagcname;
   int flagreferral;
   int flagsoa;
   uint32 ttl;
   uint32 soattl;
-  uint32 cnamettl;
   int i;
   int j;
   int k;
@@ -206,14 +219,14 @@ static int doit(struct query *z,int state)
 
 
   NEWNAME:
-  if (++z->loop == 100) goto DIE;
+  if (++z->loop == 250) goto DIE;
   d = z->name[z->level];
   dtype = z->level ? (z->ipv6[z->level] ? DNS_T_AAAA : DNS_T_A) : z->type;
   dlen = dns_domain_length(d);
 
   if (globalip(d,misc)) {
     if (z->level) {
-      for (k = 0;k < 256;k += 16)
+      for (k = 0;k < 512;k += 16)
         if (byte_equal(z->servers[z->level - 1] + k,16,V6any)) {
 	  byte_copy(z->servers[z->level - 1] + k,12,V4mappedprefix);
 	  byte_copy(z->servers[z->level - 1] + k + 12,4,misc);
@@ -226,86 +239,53 @@ static int doit(struct query *z,int state)
       if (!response_rstart(d,DNS_T_A,655360)) goto DIE;
       if (!response_addbytes(misc,4)) goto DIE;
       response_rfinish(RESPONSE_ANSWER);
-    }
-    cleanup(z);
-    return 1;
-  }
-
-  if (dns_domain_equal(d,"\0011\0010\0010\0010\0010\0010\0010\0010\0010\0010\0010\0010\0010\0010\0010\0010\0010\0010\0010\0010\0010\0010\0010\0010\0010\0010\0010\0010\0010\0010\0010\0010\003ip6\003int\0")) {
-    if (z->level) goto LOWERLEVEL;
-    if (!rqa(z)) goto DIE;
-    if (typematch(DNS_T_PTR,dtype)) {
-      if (!response_rstart(d,DNS_T_PTR,655360)) goto DIE;
-      if (!response_addname("\016ipv6-localhost\0")) goto DIE;
-      if (!response_addname("\015ipv6-loopback\0")) goto DIE;
-      response_rfinish(RESPONSE_ANSWER);
-    }
-    cleanup(z);
-    return 1;
-  }
-
-  if (dns_domain_equal(d,"\0010\0010\0010\0010\0010\0010\0010\0010\0010\0010\0010\0010\0010\0010\0010\0010\0010\0010\0010\0010\0010\0010\0010\0010\0010\0010\0010\0010\0010\0010\001e\001f\003ip6\003int\0")) {
-    if (z->level) goto LOWERLEVEL;
-    if (!rqa(z)) goto DIE;
-    if (typematch(DNS_T_PTR,dtype)) {
-      if (!response_rstart(d,DNS_T_PTR,655360)) goto DIE;
-      if (!response_addname("\015ipv6-localnet\0")) goto DIE;
-      response_rfinish(RESPONSE_ANSWER);
-    }
-    cleanup(z);
-    return 1;
-  }
-
-  if (dns_domain_equal(d,"\0010\0010\0010\0010\0010\0010\0010\0010\0010\0010\0010\0010\0010\0010\0010\0010\0010\0010\0010\0010\0010\0010\0010\0010\0010\0010\0010\0010\0010\0010\001f\001f\003ip6\003int\0")) {
-    if (z->level) goto LOWERLEVEL;
-    if (!rqa(z)) goto DIE;
-    if (typematch(DNS_T_PTR,dtype)) {
-      if (!response_rstart(d,DNS_T_PTR,655360)) goto DIE;
-      if (!response_addname("\020ipv6-mcastprefix\0")) goto DIE;
+    } else if (typematch(DNS_T_AAAA,dtype)) {
+      if (!response_rstart(d,DNS_T_AAAA,655360)) goto DIE;
+      if (!response_addbytes("\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\001",16)) goto DIE;
       response_rfinish(RESPONSE_ANSWER);
     }
     cleanup(z);
     return 1;
   }
 
-  if (dns_domain_equal(d,"\0011\0010\0010\0010\0010\0010\0010\0010\0010\0010\0010\0010\0010\0010\0010\0010\0010\0010\0010\0010\0010\0010\0010\0010\0010\0010\0010\0010\0012\0010\001f\001f\003ip6\003int\0")) {
+  if (dns_domain_equal(d,"\0011\0010\0010\0010\0010\0010\0010\0010\0010\0010\0010\0010\0010\0010\0010\0010\0010\0010\0010\0010\0010\0010\0010\0010\0010\0010\0010\0010\0010\0010\0010\0010\003ip6\004arpa\0")) {
     if (z->level) goto LOWERLEVEL;
     if (!rqa(z)) goto DIE;
     if (typematch(DNS_T_PTR,dtype)) {
       if (!response_rstart(d,DNS_T_PTR,655360)) goto DIE;
-      if (!response_addname("\015ipv6-allnodes\0")) goto DIE;
+      if (!response_addname("\011localhost\0")) goto DIE;
       response_rfinish(RESPONSE_ANSWER);
     }
     cleanup(z);
     return 1;
   }
 
-  if (dns_domain_equal(d,"\0012\0010\0010\0010\0010\0010\0010\0010\0010\0010\0010\0010\0010\0010\0010\0010\0010\0010\0010\0010\0010\0010\0010\0010\0010\0010\0010\0010\0012\0010\001f\001f\003ip6\003int\0")) {
+  if (dns_domain_equal(d,"\0011\0010\0010\0010\0010\0010\0010\0010\0010\0010\0010\0010\0010\0010\0010\0010\0010\0010\0010\0010\0010\0010\0010\0010\0010\0010\0010\0010\0012\0010\001f\001f\003ip6\004arpa\0")) {
     if (z->level) goto LOWERLEVEL;
     if (!rqa(z)) goto DIE;
     if (typematch(DNS_T_PTR,dtype)) {
       if (!response_rstart(d,DNS_T_PTR,655360)) goto DIE;
-      if (!response_addname("\017ipv6-allrouters\0")) goto DIE;
+      if (!response_addname("\014ip6-allnodes\0")) goto DIE;
       response_rfinish(RESPONSE_ANSWER);
     }
     cleanup(z);
     return 1;
   }
 
-  if (dns_domain_equal(d,"\0011\0010\0010\0010\0010\0010\0010\0010\0010\0010\0010\0010\0010\0010\0010\0010\0010\0010\0010\0010\0010\0010\0010\0010\0010\0010\0010\0010\0012\0010\001f\001f\003ip6\003int\0")) {
+  if (dns_domain_equal(d,"\0012\0010\0010\0010\0010\0010\0010\0010\0010\0010\0010\0010\0010\0010\0010\0010\0010\0010\0010\0010\0010\0010\0010\0010\0010\0010\0010\0010\0012\0010\001f\001f\003ip6\004arpa\0")) {
     if (z->level) goto LOWERLEVEL;
     if (!rqa(z)) goto DIE;
     if (typematch(DNS_T_PTR,dtype)) {
       if (!response_rstart(d,DNS_T_PTR,655360)) goto DIE;
-      if (!response_addname("\015ipv6-allhosts\0")) goto DIE;
+      if (!response_addname("\016ip6-allrouters\0")) goto DIE;
       response_rfinish(RESPONSE_ANSWER);
     }
     cleanup(z);
     return 1;
   }
 
-  if (dns_domain_equal(d,"\016ipv6-localhost\0") ||
-      dns_domain_equal(d,"\015ipv6-loopback\0"))
+  if (dns_domain_equal(d,"\015ip6-localhost\0") ||
+      dns_domain_equal(d,"\014ip6-loopback\0"))
     {
       if (z->level) goto LOWERLEVEL;
       if (!rqa(z)) goto DIE;
@@ -318,33 +298,7 @@ static int doit(struct query *z,int state)
       return 1;
     }
 
-  if (dns_domain_equal(d,"\015ipv6-localnet\0"))
-    {
-      if (z->level) goto LOWERLEVEL;
-      if (!rqa(z)) goto DIE;
-      if (typematch(DNS_T_AAAA,dtype)) {
-	if (!response_rstart(d,DNS_T_AAAA,655360)) goto DIE;
-	if (!response_addbytes("\376\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000",16)) goto DIE;
-	response_rfinish(RESPONSE_ANSWER);
-      }
-      cleanup(z);
-      return 1;
-    }
-
-  if (dns_domain_equal(d,"\020ipv6-mcastprefix\0"))
-    {
-      if (z->level) goto LOWERLEVEL;
-      if (!rqa(z)) goto DIE;
-      if (typematch(DNS_T_AAAA,dtype)) {
-	if (!response_rstart(d,DNS_T_AAAA,655360)) goto DIE;
-	if (!response_addbytes("\377\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000",16)) goto DIE;
-	response_rfinish(RESPONSE_ANSWER);
-      }
-      cleanup(z);
-      return 1;
-    }
-
-  if (dns_domain_equal(d,"\15ipv6-allnodes\0"))
+  if (dns_domain_equal(d,"\14ip6-allnodes\0"))
     {
       if (z->level) goto LOWERLEVEL;
       if (!rqa(z)) goto DIE;
@@ -357,7 +311,7 @@ static int doit(struct query *z,int state)
       return 1;
     }
 
-  if (dns_domain_equal(d,"\17ipv6-allrouters\0"))
+  if (dns_domain_equal(d,"\16ip6-allrouters\0"))
     {
       if (z->level) goto LOWERLEVEL;
       if (!rqa(z)) goto DIE;
@@ -370,19 +324,6 @@ static int doit(struct query *z,int state)
       return 1;
     }
 
-  if (dns_domain_equal(d,"\15ipv6-allhosts\0"))
-    {
-      if (z->level) goto LOWERLEVEL;
-      if (!rqa(z)) goto DIE;
-      if (typematch(DNS_T_AAAA,dtype)) {
-	if (!response_rstart(d,DNS_T_AAAA,655360)) goto DIE;
-	if (!response_addbytes("\377\002\000\000\000\000\000\000\000\000\000\000\000\000\000\003",16)) goto DIE;
-	response_rfinish(RESPONSE_ANSWER);
-      }
-      cleanup(z);
-      return 1;
-    }
-
   if (dns_domain_equal(d,"\0011\0010\0010\003127\7in-addr\4arpa\0")) {
     if (z->level) goto LOWERLEVEL;
     if (!rqa(z)) goto DIE;
@@ -408,7 +349,10 @@ static int doit(struct query *z,int state)
 
     byte_copy(key,2,DNS_T_CNAME);
     cached = cache_get(key,dlen + 2,&cachedlen,&ttl);
-    if (cached) {
+    /* A previous explicit query might have caused an empty RRSet to have been
+    ** cached.  Take care to ignore such a thing. 
+    */
+    if (cached && cachedlen) {
       if (typematch(DNS_T_CNAME,dtype)) {
         log_cachedanswer(d,DNS_T_CNAME);
         if (!rqa(z)) goto DIE;
@@ -417,8 +361,11 @@ static int doit(struct query *z,int state)
 	return 1;
       }
       log_cachedcname(d,cached);
-      if (!dns_domain_copy(&cname,cached)) goto DIE;
-      goto CNAME;
+      if (!z->level) {
+	if (!move_name_to_alias(z,ttl)) goto DIE ;
+      }
+      if (!dns_domain_copy(&z->name[z->level],cached)) goto DIE;
+      goto NEWNAME;
     }
 
     if (typematch(DNS_T_NS,dtype)) {
@@ -475,6 +422,29 @@ static int doit(struct query *z,int state)
       }
     }
 
+    if (typematch(DNS_T_SOA,dtype)) {
+      byte_copy(key,2,DNS_T_SOA);
+      cached = cache_get(key,dlen + 2,&cachedlen,&ttl);
+      if (cached && (cachedlen || byte_diff(dtype,2,DNS_T_ANY))) {
+        log_cachedanswer(d,DNS_T_SOA);
+        if (!rqa(z)) goto DIE;
+        pos = 0;
+        while (pos = dns_packet_copy(cached,cachedlen,pos,misc,20)) {
+          pos = dns_packet_getname(cached,cachedlen,pos,&t2);
+          if (!pos) break;
+          pos = dns_packet_getname(cached,cachedlen,pos,&t3);
+          if (!pos) break;
+          if (!response_rstart(d,DNS_T_SOA,ttl)) goto DIE;
+          if (!response_addname(t2)) goto DIE;
+          if (!response_addname(t3)) goto DIE;
+          if (!response_addbytes(misc,20)) goto DIE;
+          response_rfinish(RESPONSE_ANSWER);
+        }
+        cleanup(z);
+        return 1;
+      }
+    }
+
     if (typematch(DNS_T_A,dtype)) {
       byte_copy(key,2,DNS_T_A);
       cached = cache_get(key,dlen + 2,&cachedlen,&ttl);
@@ -486,7 +456,7 @@ static int doit(struct query *z,int state)
 	if (z->level) {
 	  log_cachedanswer(d,DNS_T_A);
 	  while (cachedlen >= 4) {
-	    for (k = 0;k < 256;k += 16)
+	    for (k = 0;k < 512;k += 16)
 	      if (byte_equal(z->servers[z->level - 1] + k,16,V6any)) {
 		byte_copy(z->servers[z->level - 1] + k,12,V4mappedprefix);
 		byte_copy(z->servers[z->level - 1] + k + 12,4,cached);
@@ -519,7 +489,7 @@ static int doit(struct query *z,int state)
 	if (z->level) {
 	  log_cachedanswer(d,DNS_T_AAAA);
 	  while (cachedlen >= 16) {
-	    for (k = 0;k < 256;k += 16)
+	    for (k = 0;k < 512;k += 16)
 	      if (byte_equal(z->servers[z->level - 1] + k,16,V6any)) {
 		byte_copy(z->servers[z->level - 1] + k,16,cached);
 		break;
@@ -544,7 +514,7 @@ static int doit(struct query *z,int state)
       }
     }
 
-    if (!typematch(DNS_T_ANY,dtype) && !typematch(DNS_T_AXFR,dtype) && !typematch(DNS_T_CNAME,dtype) && !typematch(DNS_T_NS,dtype) && !typematch(DNS_T_PTR,dtype) && !typematch(DNS_T_A,dtype) && !typematch(DNS_T_MX,dtype) && !typematch(DNS_T_AAAA,dtype)) {
+    if (!typematch(DNS_T_ANY,dtype) && !typematch(DNS_T_AXFR,dtype) && !typematch(DNS_T_NS,dtype) && !typematch(DNS_T_PTR,dtype) && !typematch(DNS_T_A,dtype) && !typematch(DNS_T_MX,dtype) && !typematch(DNS_T_AAAA,dtype) && !typematch(DNS_T_SOA,dtype)) {
       byte_copy(key,2,dtype);
       cached = cache_get(key,dlen + 2,&cachedlen,&ttl);
       if (cached && (cachedlen || byte_diff(dtype,2,DNS_T_ANY))) {
@@ -583,7 +553,7 @@ static int doit(struct query *z,int state)
         cached = cache_get(key,dlen + 2,&cachedlen,&ttl);
         if (cached && cachedlen) {
 	  z->control[z->level] = d;
-          byte_zero(z->servers[z->level],256);
+          byte_zero(z->servers[z->level],512);
           for (j = 0;j < QUERY_MAXNS;++j)
             dns_domain_free(&z->ns[z->level][j]);
           pos = 0;
@@ -617,12 +587,12 @@ static int doit(struct query *z,int state)
       dns_domain_free(&z->ns[z->level][j]);
     }
 
-  for (j = 0;j < 256;j += 16)
+  for (j = 0;j < 512;j += 16)
     if (byte_diff(z->servers[z->level] + j,16,V6any))
       break;
-  if (j == 256) goto SERVFAIL;
+  if (j == 512) goto SERVFAIL;
 
-  dns_sortip6(z->servers[z->level],256);
+  dns_sortip6(z->servers[z->level],512);
   if (z->level) {
     dtype = z->ipv6[z->level] ? DNS_T_AAAA : DNS_T_A;
     log_tx(z->name[z->level],dtype,z->control[z->level],z->servers[z->level],z->level);
@@ -644,7 +614,7 @@ static int doit(struct query *z,int state)
 
 
   HAVEPACKET:
-  if (++z->loop == 100) goto DIE;
+  if (++z->loop == 250) goto DIE;
   buf = z->dt.packet;
   len = z->dt.packetlen;
 
@@ -667,29 +637,31 @@ static int doit(struct query *z,int state)
   if (rcode && (rcode != 3)) goto DIE; /* impossible; see irrelevant() */
 
   flagout = 0;
-  flagcname = 0;
   flagreferral = 0;
   flagsoa = 0;
   soattl = 0;
-  cnamettl = 0;
+  if (!dns_domain_copy(&owner_name,d)) goto DIE;
+  /* This code assumes that the CNAME chain is presented in the correct 
+  ** order.  The example algorithm in RFC 1034 will actually result in this
+  ** being the case, but the words do not require it to be so.
+  */
   for (j = 0;j < numanswers;++j) {
     pos = dns_packet_getname(buf,len,pos,&t1); if (!pos) goto DIE;
     pos = dns_packet_copy(buf,len,pos,header,10); if (!pos) goto DIE;
 
-    if (dns_domain_equal(t1,d))
+    if (dns_domain_equal(t1,owner_name))
       if (byte_equal(header + 2,2,DNS_C_IN)) { /* should always be true */
         if (typematch(header,dtype))
           flagout = 1;
         else if (typematch(header,DNS_T_CNAME)) {
-          if (!dns_packet_getname(buf,len,pos,&cname)) goto DIE;
-          flagcname = 1;
-	  cnamettl = ttlget(header + 4);
+          if (!dns_packet_getname(buf,len,pos,&owner_name)) goto DIE;
         }
       }
   
     uint16_unpack_big(header + 8,&datalen);
     pos += datalen;
   }
+  dns_domain_free(&owner_name) ;
   posauthority = pos;
 
   for (j = 0;j < numauthority;++j) {
@@ -710,14 +682,6 @@ static int doit(struct query *z,int state)
     pos += datalen;
   }
 
-  if (!flagcname && !rcode && !flagout && flagreferral && !flagsoa)
-    if (dns_domain_equal(referral,control) || !dns_domain_suffix(referral,control)) {
-      log_lame(whichserver,control,referral);
-      byte_zero(whichserver,16);
-      goto HAVENS;
-    }
-
-
   if (records) { alloc_free(records); records = 0; }
 
   k = numanswers + numauthority + numglue;
@@ -772,6 +736,10 @@ static int doit(struct query *z,int state)
     }
 
     if (!dns_domain_suffix(t1,control)) { i = j; continue; }
+    if (!flagforwardonly && byte_equal(type,2,DNS_T_NS) && dns_domain_equal(t1,control)) {
+	char dummy[512];
+	if (!roots(dummy,control)) { i = j; continue; }
+    }
     if (!roots_same(t1,control)) { i = j; continue; }
 
     if (byte_equal(type,2,DNS_T_ANY))
@@ -779,15 +747,24 @@ static int doit(struct query *z,int state)
     else if (byte_equal(type,2,DNS_T_AXFR))
       ;
     else if (byte_equal(type,2,DNS_T_SOA)) {
+      int non_authority = 0;
+      save_start();
       while (i < j) {
         pos = dns_packet_skipname(buf,len,records[i]); if (!pos) goto DIE;
         pos = dns_packet_getname(buf,len,pos + 10,&t2); if (!pos) goto DIE;
         pos = dns_packet_getname(buf,len,pos,&t3); if (!pos) goto DIE;
         pos = dns_packet_copy(buf,len,pos,misc,20); if (!pos) goto DIE;
-        if (records[i] < posauthority)
+        if (records[i] < posauthority) {
           log_rrsoa(whichserver,t1,t2,t3,misc,ttl);
+          save_data(misc,20);
+          save_data(t2,dns_domain_length(t2));
+          save_data(t3,dns_domain_length(t3));
+          non_authority++;
+        }
         ++i;
       }
+      if (non_authority)
+        save_finish(DNS_T_SOA,t1,ttl);
     }
     else if (byte_equal(type,2,DNS_T_CNAME)) {
       pos = dns_packet_skipname(buf,len,records[j - 1]); if (!pos) goto DIE;
@@ -886,24 +863,36 @@ static int doit(struct query *z,int state)
 
   alloc_free(records); records = 0;
 
+  if (byte_diff(DNS_T_CNAME,2,dtype)) {
+    /* This code assumes that the CNAME chain is presented in the correct 
+    ** order.  The example algorithm in RFC 1034 will actually result in this
+    ** being the case, but the words do not require it to be so.
+    */
+    pos = posanswers;
+    for (j = 0;j < numanswers;++j) {
+      pos = dns_packet_getname(buf,len,pos,&t1); if (!pos) goto DIE;
+      pos = dns_packet_copy(buf,len,pos,header,10); if (!pos) goto DIE;
+
+      if (dns_domain_equal(t1,d))
+	if (byte_equal(header + 2,2,DNS_C_IN)) { /* should always be true */
+	  if (typematch(header,DNS_T_CNAME)) {
+	    ttl = ttlget(header + 4);
+	    if (z->level == 0) {
+	      if (!move_name_to_alias(z,ttl)) goto DIE ;
+	    }
+	    if (!dns_packet_getname(buf,len,pos,&z->name[z->level])) goto DIE;
+	    d = z->name[z->level];
+	    if (!dns_domain_suffix(d,control) || !roots_same(d,control))
+	      goto NEWNAME ;  /* Cannot trust the chain further - restart using current name */
+	  }
+	}
 
-  if (flagcname) {
-    ttl = cnamettl;
-    CNAME:
-    if (!z->level) {
-      if (z->alias[QUERY_MAXALIAS - 1]) goto DIE;
-      for (j = QUERY_MAXALIAS - 1;j > 0;--j)
-        z->alias[j] = z->alias[j - 1];
-      for (j = QUERY_MAXALIAS - 1;j > 0;--j)
-        z->aliasttl[j] = z->aliasttl[j - 1];
-      z->alias[0] = z->name[0];
-      z->aliasttl[0] = ttl;
-      z->name[0] = 0;
+      uint16_unpack_big(header + 8,&datalen);
+      pos += datalen;
     }
-    if (!dns_domain_copy(&z->name[z->level],cname)) goto DIE;
-    goto NEWNAME;
   }
 
+  /* A "no such name" error applies to the end of any CNAME chain, not to the start. */
   if (rcode == 3) {
     log_nxdomain(whichserver,d,soattl);
     cachegeneric(DNS_T_ANY,d,"",0,soattl);
@@ -916,10 +905,26 @@ static int doit(struct query *z,int state)
     return 1;
   }
 
+  /* We check for a lame server _after_ we have cached any records that it
+  ** might have returned to us.  This copes better with the incorrect
+  ** behaviour of one content DNS server software that doesn't return
+  ** complete CNAME chains but instead returns only the first link in a
+  ** chain followed by a lame delegation to the same server.
+  ** Also: We check for a lame server _after_ following the CNAME chain.  The
+  ** delegation in a referral answer applies to the _end_ of the chain, not
+  ** to the beginning.
+  */
+  if (!rcode && !flagout && flagreferral && !flagsoa)
+    if (dns_domain_equal(referral,control) || !dns_domain_suffix(referral,control)) {
+      log_lame(whichserver,control,referral);
+      byte_zero(whichserver,16);
+      goto HAVENS;
+    }
+
   if (!flagout && flagsoa)
+    /* Don't save empty RRSets for those types that we use as special markers. */
     if (byte_diff(DNS_T_ANY,2,dtype))
-      if (byte_diff(DNS_T_AXFR,2,dtype))
-        if (byte_diff(DNS_T_CNAME,2,dtype)) {
+      if (byte_diff(DNS_T_AXFR,2,dtype)) {
           save_start();
           save_finish(dtype,d,soattl);
 	  log_nodata(whichserver,d,dtype,soattl);
@@ -944,7 +949,7 @@ static int doit(struct query *z,int state)
           if (typematch(header,DNS_T_A))
             if (byte_equal(header + 2,2,DNS_C_IN)) /* should always be true */
               if (datalen == 4)
-                for (k = 0;k < 256;k += 16)
+                for (k = 0;k < 512;k += 16)
                   if (byte_equal(z->servers[z->level - 1] + k,16,V6any)) {
 		    byte_copy(z->servers[z->level - 1] + k,12,V4mappedprefix);
                     if (!dns_packet_copy(buf,len,pos,z->servers[z->level - 1] + k + 12,4)) goto DIE;
@@ -953,7 +958,7 @@ static int doit(struct query *z,int state)
           if (typematch(header,DNS_T_AAAA))
             if (byte_equal(header + 2,2,DNS_C_IN)) /* should always be true */
               if (datalen == 16)
-                for (k = 0;k < 256;k += 16)
+                for (k = 0;k < 512;k += 16)
                   if (byte_equal(z->servers[z->level - 1] + k,16,V6any)) {
                     if (!dns_packet_copy(buf,len,pos,z->servers[z->level - 1] + k,16)) goto DIE;
                     break;
@@ -1011,9 +1016,21 @@ static int doit(struct query *z,int state)
 
 
   if (!dns_domain_suffix(d,referral)) goto DIE;
+
+  /* In strict "forwardonly" mode, we don't, as the manual states,
+  ** contact a chain of servers according to "NS" resource records.
+  ** We don't obey any referral responses, therefore.  Instead, we
+  ** eliminate the server from the list and try the next one.
+  */
+  if (flagforwardonly) {
+      log_ignore_referral(whichserver,control,referral);
+      byte_zero(whichserver,16);
+      goto HAVENS;
+  }
+
   control = d + dns_domain_suffixpos(d,referral);
   z->control[z->level] = control;
-  byte_zero(z->servers[z->level],256);
+  byte_zero(z->servers[z->level],512);
   for (j = 0;j < QUERY_MAXNS;++j)
     dns_domain_free(&z->ns[z->level][j]);
   k = 0;
@@ -1045,6 +1062,7 @@ static int doit(struct query *z,int state)
   DIE:
   cleanup(z);
   if (records) { alloc_free(records); records = 0; }
+  dns_domain_free(&owner_name) ;
   return -1;
 }
 
diff --git a/query.h b/query.h
index 61812aa..72e79ff 100644
--- a/query.h
+++ b/query.h
@@ -14,7 +14,7 @@ struct query {
   char *name[QUERY_MAXLEVEL];
   char *control[QUERY_MAXLEVEL]; /* pointing inside name */
   char *ns[QUERY_MAXLEVEL][QUERY_MAXNS];
-  char servers[QUERY_MAXLEVEL][256];
+  char servers[QUERY_MAXLEVEL][512];
   char *alias[QUERY_MAXALIAS];
   uint32 aliasttl[QUERY_MAXALIAS];
   char ipv6[QUERY_MAXLEVEL];
diff --git a/rbldns-data.c b/rbldns-data.c
index ed495db..0c107a9 100644
--- a/rbldns-data.c
+++ b/rbldns-data.c
@@ -42,7 +42,7 @@ void syntaxerror(const char *why)
 }
 void die_datatmp(void)
 {
-  strerr_die2sys(111,FATAL,"unable to create data.tmp: ");
+  strerr_die2sys(111,FATAL,"unable to create data.cdb.tmp: ");
 }
 
 int main()
@@ -59,7 +59,7 @@ int main()
   if (fd == -1) strerr_die2sys(111,FATAL,"unable to open data: ");
   buffer_init(&b,buffer_unixread,fd,bspace,sizeof bspace);
 
-  fdcdb = open_trunc("data.tmp");
+  fdcdb = open_trunc("data.cdb.tmp");
   if (fdcdb == -1) die_datatmp();
   if (cdb_make_start(&cdb,fdcdb) == -1) die_datatmp();
 
@@ -89,6 +89,15 @@ int main()
         if (cdb_make_add(&cdb,"",0,tmp.s,tmp.len) == -1)
           die_datatmp();
         break;
+      case '!': /* root entry */
+	j = byte_chr(line.s + 1,line.len - 1,':');
+	if (j >= line.len - 1) syntaxerror(": missing colon");
+	if (ip4_scan(line.s + 1,ip) != j) syntaxerror(": malformed IP address");
+	if (!stralloc_copyb(&tmp,ip,4)) nomem();
+	if (!stralloc_catb(&tmp,line.s + j + 2,line.len - j - 2)) nomem();
+        if (cdb_make_add(&cdb,"R",1,tmp.s,tmp.len) == -1)
+          die_datatmp();
+        break;
       case '0': case '1': case '2': case '3': case '4':
       case '5': case '6': case '7': case '8': case '9':
 	if (!stralloc_0(&line)) nomem();
@@ -121,8 +130,8 @@ int main()
   if (cdb_make_finish(&cdb) == -1) die_datatmp();
   if (fsync(fdcdb) == -1) die_datatmp();
   if (close(fdcdb) == -1) die_datatmp(); /* NFS stupidity */
-  if (rename("data.tmp","data.cdb") == -1)
-    strerr_die2sys(111,FATAL,"unable to move data.tmp to data.cdb: ");
+  if (rename("data.cdb.tmp","data.cdb") == -1)
+    strerr_die2sys(111,FATAL,"unable to move data.cdb.tmp to data.cdb: ");
 
   _exit(0);
 }
diff --git a/rbldns.c b/rbldns.c
index 2c13c27..289c8b6 100644
--- a/rbldns.c
+++ b/rbldns.c
@@ -33,7 +33,19 @@ static int doit(char *q,char qtype[2])
   if (byte_equal(qtype,2,DNS_T_ANY)) flaga = flagtxt = 1;
   if (!flaga && !flagtxt) goto REFUSE;
 
-  if (dd(q,base,reverseip) != 4) goto REFUSE;
+  i = dd(q,base,reverseip);
+  if(i == 0) { /* root entry */
+    r = cdb_find(&c,"R",1);
+    if (r == -1) return 0;
+    if (r && ((dlen = cdb_datalen(&c)) >= 4)) {
+      if (dlen > 100) dlen = 100;
+      if (cdb_read(&c,data,dlen,cdb_datapos(&c)) == -1) return 0;
+    }
+    else {
+      goto REFUSE;
+    }
+  } else {
+  if (i != 4) goto REFUSE;
   uint32_unpack(reverseip,&ipnum);
   uint32_pack_big(ip,ipnum);
 
@@ -63,7 +75,7 @@ static int doit(char *q,char qtype[2])
     --dlen;
     dlen += ip4_fmt(data + dlen,ip);
   }
-
+  }
   if (flaga) {
     if (!response_rstart(q,DNS_T_A,2048)) return 0;
     if (!response_addbytes(data,4)) return 0;
diff --git a/response.c b/response.c
index ba90c89..33b2fb1 100644
--- a/response.c
+++ b/response.c
@@ -34,7 +34,7 @@ int response_addname(const char *d)
         uint16_pack_big(buf,49152 + name_ptr[i]);
         return response_addbytes(buf,2);
       }
-    if (dlen <= 128)
+    if ((dlen <= 128) && (response_len < 16384))
       if (name_num < NAMES) {
 	byte_copy(name[name_num],dlen,d);
 	name_ptr[name_num] = response_len;
diff --git a/roots.c b/roots.c
index 4162ec5..256ac77 100644
--- a/roots.c
+++ b/roots.c
@@ -23,7 +23,7 @@ static int roots_find(char *q)
     j = dns_domain_length(data.s + i);
     if (dns_domain_equal(data.s + i,q)) return i + j;
     i += j;
-    i += 256;
+    i += 512;
   }
   return -1;
 }
@@ -41,12 +41,12 @@ static int roots_search(char *q)
   }
 }
 
-int roots(char servers[256],char *q)
+int roots(char servers[512],char *q)
 {
   int r;
   r = roots_find(q);
   if (r == -1) return 0;
-  byte_copy(servers,256,data.s + r);
+  byte_copy(servers,512,data.s + r);
   return 1;
 }
 
@@ -61,7 +61,7 @@ static int init2(DIR *dir)
   const char *fqdn;
   static char *q;
   static stralloc text;
-  char servers[256];
+  char servers[512];
   int serverslen;
   int i;
   int j;
@@ -86,15 +86,15 @@ static int init2(DIR *dir)
       j = 0;
       for (i = 0;i < text.len;++i)
 	if (text.s[i] == '\n') {
-	  if (serverslen <= 60)
+	  if (serverslen <= 512 - 16)
 	    if (ip6_scan(text.s + j,servers + serverslen))
 	      serverslen += 16;
 	  j = i + 1;
 	}
-      byte_zero(servers + serverslen,256 - serverslen);
+      byte_zero(servers + serverslen,512 - serverslen);
 
       if (!stralloc_catb(&data,q,dns_domain_length(q))) return 0;
-      if (!stralloc_catb(&data,servers,256)) return 0;
+      if (!stralloc_catb(&data,servers,512)) return 0;
     }
   }
 }
diff --git a/siphash.c b/siphash.c
new file mode 100644
index 0000000..a2007db
--- /dev/null
+++ b/siphash.c
@@ -0,0 +1,98 @@
+#include <string.h>
+#include "uint32.h"
+#include "uint64.h"
+#include "siphash.h"
+
+typedef uint64 u64;
+typedef uint32 u32;
+typedef unsigned char u8;
+
+#define ROTL(x,b) (u64)( ((x) << (b)) | ( (x) >> (64 - (b))) )
+
+#define U32TO8_LE(p, v)         \
+    (p)[0] = (u8)((v)      ); (p)[1] = (u8)((v) >>  8); \
+    (p)[2] = (u8)((v) >> 16); (p)[3] = (u8)((v) >> 24);
+
+#define U64TO8_LE(p, v)         \
+  U32TO8_LE((p),     (u32)((v)      ));   \
+  U32TO8_LE((p) + 4, (u32)((v) >> 32));
+
+#define U8TO64_LE(p) \
+  (((u64)((p)[0])      ) | \
+   ((u64)((p)[1]) <<  8) | \
+   ((u64)((p)[2]) << 16) | \
+   ((u64)((p)[3]) << 24) | \
+   ((u64)((p)[4]) << 32) | \
+   ((u64)((p)[5]) << 40) | \
+   ((u64)((p)[6]) << 48) | \
+   ((u64)((p)[7]) << 56))
+
+#define SIPROUND            \
+  do {              \
+    x0 += x1; x1=ROTL(x1,13); x1 ^= x0; x0=ROTL(x0,32); \
+    x2 += x3; x3=ROTL(x3,16); x3 ^= x2;     \
+    x0 += x3; x3=ROTL(x3,21); x3 ^= x0;     \
+    x2 += x1; x1=ROTL(x1,17); x1 ^= x2; x2=ROTL(x2,32); \
+  } while(0)
+
+/* SipHash-2-4 */
+int siphash24( unsigned char *out, const unsigned char *in, unsigned long long inlen, const unsigned char *k )
+{
+  /* "somepseudorandomlygeneratedbytes" */
+  u64 x0 = 0x736f6d6570736575ULL;
+  u64 x1 = 0x646f72616e646f6dULL;
+  u64 x2 = 0x6c7967656e657261ULL;
+  u64 x3 = 0x7465646279746573ULL;
+  u64 b;
+  u64 k0 = U8TO64_LE( k );
+  u64 k1 = U8TO64_LE( k + 8 );
+  u64 m;
+  const u8 *end = in + inlen - ( inlen % sizeof( u64 ) );
+  const int left = inlen & 7;
+  b = ( ( u64 )inlen ) << 56;
+  x3 ^= k1;
+  x2 ^= k0;
+  x1 ^= k1;
+  x0 ^= k0;
+
+  for ( ; in != end; in += 8 )
+  {
+    m = U8TO64_LE( in );
+    x3 ^= m;
+    SIPROUND;
+    SIPROUND;
+    x0 ^= m;
+  }
+
+  switch( left )
+  {
+  case 7: b |= ( ( u64 )in[ 6] )  << 48;
+
+  case 6: b |= ( ( u64 )in[ 5] )  << 40;
+
+  case 5: b |= ( ( u64 )in[ 4] )  << 32;
+
+  case 4: b |= ( ( u64 )in[ 3] )  << 24;
+
+  case 3: b |= ( ( u64 )in[ 2] )  << 16;
+
+  case 2: b |= ( ( u64 )in[ 1] )  <<  8;
+
+  case 1: b |= ( ( u64 )in[ 0] ); break;
+
+  case 0: break;
+  }
+
+  x3 ^= b;
+  SIPROUND;
+  SIPROUND;
+  x0 ^= b;
+  x2 ^= 0xff;
+  SIPROUND;
+  SIPROUND;
+  SIPROUND;
+  SIPROUND;
+  b = x0 ^ x1 ^ x2  ^ x3;
+  U64TO8_LE( out, b );
+  return 0;
+}
diff --git a/siphash.h b/siphash.h
new file mode 100644
index 0000000..bc2e6a0
--- /dev/null
+++ b/siphash.h
@@ -0,0 +1,7 @@
+
+#ifndef SIPHASH_H
+#define SIPHASH_H
+
+int siphash24( unsigned char *out, const unsigned char *in, unsigned long long inlen, const unsigned char *k );
+
+#endif
diff --git a/tdlookup.c b/tdlookup.c
index b760340..11ba745 100644
--- a/tdlookup.c
+++ b/tdlookup.c
@@ -105,12 +105,13 @@ static int doname(void)
   return response_addname(d1);
 }
 
-static int doit(char *q,char qtype[2])
+static int doit1(char **pqname,char qtype[2])
 {
   unsigned int bpos;
   unsigned int anpos;
   unsigned int aupos;
   unsigned int arpos;
+  char *q;
   char *control;
   char *wild;
   int flaggavesoa;
@@ -125,6 +126,12 @@ static int doit(char *q,char qtype[2])
   int addrnum,addr6num;
   uint32 addrttl,addr6ttl;
   int i;
+  int loop = 0 ;
+
+RESTART:
+  if (loop++ >= 100) return 0 ;
+
+  q = *pqname ;
 
   anpos = response_len;
 
@@ -139,7 +146,14 @@ static int doit(char *q,char qtype[2])
       if (byte_equal(type,2,DNS_T_NS)) flagns = 1;
     }
     if (flagns) break;
-    if (!*control) return 0; /* q is not within our bailiwick */
+    if (!*control) { /* q is not within our bailiwick */
+      if (loop <= 1)
+        return 0 ;
+      else {
+        response[2] &= ~4;
+        goto DONE; /* The administrator has issued contradictory instructions */
+      }
+    }
     control += *control;
     control += 1;
   }
@@ -186,9 +200,17 @@ static int doit(char *q,char qtype[2])
 	continue;
       }
       if (!response_rstart(q,type,ttl)) return 0;
-      if (byte_equal(type,2,DNS_T_NS) || byte_equal(type,2,DNS_T_CNAME) || byte_equal(type,2,DNS_T_PTR)) {
+      if (byte_equal(type,2,DNS_T_NS) || byte_equal(type,2,DNS_T_PTR)) {
 	if (!doname()) return 0;
       }
+      else if (byte_equal(type,2,DNS_T_CNAME)) {
+	if (!doname()) return 0;
+        if (byte_diff(type,2,qtype)) {
+	  response_rfinish(RESPONSE_ANSWER);
+	  if (!dns_domain_copy(pqname,d1)) return 0 ;
+	  goto RESTART ;
+	}
+      }
       else if (byte_equal(type,2,DNS_T_MX)) {
 	if (!dobytes(2)) return 0;
 	if (!doname()) return 0;
@@ -300,9 +322,21 @@ static int doit(char *q,char qtype[2])
     }
   }
 
+DONE:
   return 1;
 }
 
+static int doit(char *qname,char qtype[2])
+{
+  int r ;
+  char * q = 0 ;
+
+  if (!dns_domain_copy(&q, qname)) return 0 ;
+  r = doit1(&q, qtype) ;
+  dns_domain_free(&q) ;
+  return r ;
+}
+
 int respond(char *q,char qtype[2],char ip[16])
 {
   int fd;
diff --git a/tinydns-data.c b/tinydns-data.c
index a509b21..337d73b 100644
--- a/tinydns-data.c
+++ b/tinydns-data.c
@@ -26,29 +26,56 @@
 
 #define FATAL "tinydns-data: fatal: "
 
+void die_semantic2(const char * s1, const char * s2)
+{
+  strerr_die3x(111,FATAL,s1,s2) ;
+}
+void die_semantic4(const char * s1, const char * s2,const char * s3, const char * s4)
+{
+  strerr_die5x(111,FATAL,s1,s2,s3,s4) ;
+}
 void die_datatmp(void)
 {
-  strerr_die2sys(111,FATAL,"unable to create data.tmp: ");
+  strerr_die2sys(111,FATAL,"unable to create data.cdb.tmp: ");
 }
 void nomem(void)
 {
   strerr_die1sys(111,FATAL);
 }
 
+void ttlparse(stralloc *sa,unsigned long * ttl, unsigned long defttl, const char * ltype)
+{
+    int ttllen ;
+
+    if (sa->len > 0) {
+	if (!stralloc_0(sa)) nomem();
+	ttllen = scan_ulong(sa->s,ttl) ;
+	if (ttllen + 1 != sa->len)
+	    die_semantic4("unparseable TTL in ",ltype," line: ", sa->s) ;
+    } else
+	*ttl = defttl;
+}
+
 void ttdparse(stralloc *sa,char ttd[8])
 {
   unsigned int i;
   char ch;
 
   byte_zero(ttd,8);
-  for (i = 0;(i < 16) && (i < sa->len);++i) {
+  for (i = 0;i < sa->len;++i) {
+    if (i >= 16) {
+      if (!stralloc_0(sa)) nomem() ;
+      die_semantic2("timestamp is too long: ", sa->s) ;
+    }
     ch = sa->s[i];
     if ((ch >= '0') && (ch <= '9'))
       ch -= '0';
     else if ((ch >= 'a') && (ch <= 'f'))
       ch -= 'a' - 10;
-    else
-      ch = 0;
+    else {
+      if (!stralloc_0(sa)) nomem() ;
+      die_semantic2("timestamp contains an invalid character: ", sa->s) ;
+    }
     if (!(i & 1)) ch <<= 4;
     ttd[i >> 1] |= ch;
   }
@@ -56,6 +83,10 @@ void ttdparse(stralloc *sa,char ttd[8])
 
 void locparse(stralloc *sa,char loc[2])
 {
+  if (sa->len > 2) {
+    if (!stralloc_0(sa)) nomem() ;
+    die_semantic2("location code longer than two characters: ", sa->s) ;
+  }
   loc[0] = (sa->len > 0) ? sa->s[0] : 0;
   loc[1] = (sa->len > 1) ? sa->s[1] : 0;
 }
@@ -167,11 +198,7 @@ void rr_finish(const char *owner)
     die_datatmp();
 }
 
-buffer b;
-char bspace[1024];
-
 static stralloc line;
-int match = 1;
 unsigned long linenum = 0;
 
 #define NUMFIELDS 15
@@ -198,12 +225,13 @@ static unsigned int scan_u32(const char *s,uint32 *u) {
   return r;
 }
 
-int main()
+void load(const char *fname)
 {
   int fddata;
   int i;
   int j;
   int k;
+  int iplen ;
   char ch;
   unsigned long ttl;
   char ttd[8];
@@ -214,20 +242,21 @@ int main()
   char type[2];
   char soa[20];
   char buf[4];
+  char srv[6];
+  int match;
+  buffer b;
+  char bspace[1024];
 
   umask(022);
 
-  fddata = open_read("data");
+  fddata = open_read(fname);
   if (fddata == -1)
-    strerr_die2sys(111,FATAL,"unable to open data: ");
+    strerr_die4sys(111,FATAL,"unable to open ",fname,": ");
   defaultsoa_init(fddata);
 
   buffer_init(&b,buffer_unixread,fddata,bspace,sizeof bspace);
 
-  fdcdb = open_trunc("data.tmp");
-  if (fdcdb == -1) die_datatmp();
-  if (cdb_make_start(&cdb,fdcdb) == -1) die_datatmp();
-
+  match = 1;
   while (match) {
     ++linenum;
     if (getln(&b,&line,&match,'\n') == -1)
@@ -284,8 +313,7 @@ int main()
 	if (!scan_u32(f[7].s,&u)) uint32_unpack_big(defaultsoa + 16,&u);
 	uint32_pack_big(soa + 16,u);
 
-	if (!stralloc_0(&f[8])) nomem();
-	if (!scan_ulong(f[8].s,&ttl)) ttl = TTL_NEGATIVE;
+	ttlparse(&f[8],&ttl,TTL_NEGATIVE,"Z");
 	ttdparse(&f[9],ttd);
 	locparse(&f[10],loc);
 
@@ -300,8 +328,7 @@ int main()
 
       case '.': case '&':
 	if (!dns_domain_fromdot(&d1,f[0].s,f[0].len)) nomem();
-	if (!stralloc_0(&f[3])) nomem();
-	if (!scan_ulong(f[3].s,&ttl)) ttl = TTL_NS;
+	ttlparse(&f[3],&ttl,TTL_NS,". or &");
 	ttdparse(&f[4],ttd);
 	locparse(&f[5],loc);
 
@@ -326,24 +353,26 @@ int main()
 	rr_addname(d2);
 	rr_finish(d1);
 
-	if (ip4_scan(f[1].s,ip)) {
+	iplen = ip4_scan(f[1].s,ip) ;
+	if (iplen != 0 && iplen + 1 == f[1].len) {
 	  rr_start(DNS_T_A,ttl,ttd,loc);
 	  rr_add(ip,4);
 	  rr_finish(d2);
-	}
+	} else if (f[1].len > 1)
+	  die_semantic4("unparseable IP address in ","& or ."," line: ", f[1].s) ;
 
 	break;
 
       case '+': case '=':
 	if (!dns_domain_fromdot(&d1,f[0].s,f[0].len)) nomem();
-	if (!stralloc_0(&f[2])) nomem();
-	if (!scan_ulong(f[2].s,&ttl)) ttl = TTL_POSITIVE;
+	ttlparse(&f[2],&ttl,TTL_POSITIVE,"+ or =");
 	ttdparse(&f[3],ttd);
 	locparse(&f[4],loc);
 
 	if (!stralloc_0(&f[1])) nomem();
 
-	if (ip4_scan(f[1].s,ip)) {
+	iplen = ip4_scan(f[1].s,ip) ;
+	if (iplen != 0 && iplen + 1 == f[1].len) {
 	  rr_start(DNS_T_A,ttl,ttd,loc);
 	  rr_add(ip,4);
 	  rr_finish(d1);
@@ -359,8 +388,7 @@ int main()
 
       case '6': case '3':
 	if (!dns_domain_fromdot(&d1,f[0].s,f[0].len)) nomem();
-	if (!stralloc_0(&f[2])) nomem();
-	if (!scan_ulong(f[2].s,&ttl)) ttl = TTL_POSITIVE;
+	ttlparse(&f[2],&ttl,TTL_POSITIVE,"6 or 3");
 	ttdparse(&f[3],ttd);
 	locparse(&f[4],loc);
 
@@ -370,24 +398,21 @@ int main()
 	  rr_add(ip6,16);
 	  rr_finish(d1);
 
-	  if (line.s[0] == '6') {	/* emit both .ip6.arpa and .ip6.int */
-	    dns_name6_domain(d6ptr,ip6,DNS_IP6_ARPA);
-	    rr_start(DNS_T_PTR,ttl,ttd,loc);
-	    rr_addname(d1);
-	    rr_finish(d6ptr);
-
-	    dns_name6_domain(d6ptr,ip6,DNS_IP6_INT);
+	  if (line.s[0] == '6') {
+	    dns_name6_domain(d6ptr,ip6);
 	    rr_start(DNS_T_PTR,ttl,ttd,loc);
 	    rr_addname(d1);
 	    rr_finish(d6ptr);
 	  }
-	}
+	} else if (f[1].len > 1)
+	  die_semantic4("unparseable IP address in ","+ or ="," line: ", f[1].s) ;
+	else
+	  die_semantic4("missing IP address in ","+ or ="," line: ", f[1].s) ;
 	break;
 
       case '@':
 	if (!dns_domain_fromdot(&d1,f[0].s,f[0].len)) nomem();
-	if (!stralloc_0(&f[4])) nomem();
-	if (!scan_ulong(f[4].s,&ttl)) ttl = TTL_POSITIVE;
+	ttlparse(&f[4],&ttl,TTL_POSITIVE,"@");
 	ttdparse(&f[5],ttd);
 	locparse(&f[6],loc);
 
@@ -415,11 +440,48 @@ int main()
 	}
 	break;
 
+      case 'S':
+	if (!dns_domain_fromdot(&d1,f[0].s,f[0].len)) nomem();
+	ttlparse(&f[6],&ttl,TTL_POSITIVE,"S");
+	ttdparse(&f[7],ttd);
+	locparse(&f[8],loc);
+
+	if (!stralloc_0(&f[1])) nomem();
+
+	if (byte_chr(f[2].s,f[2].len,'.') >= f[2].len) {
+	  if (!stralloc_cats(&f[2],".srv.")) nomem();
+	  if (!stralloc_catb(&f[2],f[0].s,f[0].len)) nomem();
+	}
+	if (!dns_domain_fromdot(&d2,f[2].s,f[2].len)) nomem();
+
+	if (!stralloc_0(&f[4])) nomem();
+	if (!scan_u32(f[4].s,&u)) u = 0;
+	uint16_pack_big(srv,u);
+	if (!stralloc_0(&f[5])) nomem();
+	if (!scan_u32(f[5].s,&u)) u = 0;
+	uint16_pack_big(srv + 2,u);
+	if (!stralloc_0(&f[3])) nomem();
+	if (!scan_u32(f[3].s,&u)) nomem();
+	uint16_pack_big(srv + 4,u);
+
+	rr_start(DNS_T_SRV,ttl,ttd,loc);
+	rr_add(srv,6);
+	rr_addname(d2);
+	rr_finish(d1);
+
+	iplen = ip4_scan(f[1].s,ip) ;
+	if (iplen != 0 && iplen + 1 == f[1].len) {
+	  rr_start(DNS_T_A,ttl,ttd,loc);
+	  rr_add(ip,4);
+	  rr_finish(d2);
+	} else if (f[1].len > 1)
+	  die_semantic4("unparseable IP address in ","@"," line: ", f[1].s) ;
+	break;
+
       case '^': case 'C':
 	if (!dns_domain_fromdot(&d1,f[0].s,f[0].len)) nomem();
 	if (!dns_domain_fromdot(&d2,f[1].s,f[1].len)) nomem();
-	if (!stralloc_0(&f[2])) nomem();
-	if (!scan_ulong(f[2].s,&ttl)) ttl = TTL_POSITIVE;
+	ttlparse(&f[2],&ttl,TTL_POSITIVE,"^ or C");
 	ttdparse(&f[3],ttd);
 	locparse(&f[4],loc);
 
@@ -433,8 +495,7 @@ int main()
 
       case '\'':
 	if (!dns_domain_fromdot(&d1,f[0].s,f[0].len)) nomem();
-	if (!stralloc_0(&f[2])) nomem();
-	if (!scan_ulong(f[2].s,&ttl)) ttl = TTL_POSITIVE;
+	ttlparse(&f[2],&ttl,TTL_POSITIVE,"\'");
 	ttdparse(&f[3],ttd);
 	locparse(&f[4],loc);
 
@@ -444,7 +505,7 @@ int main()
 	i = 0;
 	while (i < f[1].len) {
 	  k = f[1].len - i;
-	  if (k > 127) k = 127;
+	  if (k > 255) k = 255;
 	  ch = k;
 	  rr_add(&ch,1);
 	  rr_add(f[1].s + i,k);
@@ -456,8 +517,7 @@ int main()
 
       case ':':
 	if (!dns_domain_fromdot(&d1,f[0].s,f[0].len)) nomem();
-	if (!stralloc_0(&f[3])) nomem();
-	if (!scan_ulong(f[3].s,&ttl)) ttl = TTL_POSITIVE;
+	ttlparse(&f[3],&ttl,TTL_POSITIVE,":");
 	ttdparse(&f[4],ttd);
 	locparse(&f[5],loc);
 
@@ -490,12 +550,27 @@ int main()
         syntaxerror(": unrecognized leading character");
     }
   }
+  close(fddata);
+}
+
+int main(int argc, char **argv)
+{
+  umask(022);
+  fdcdb = open_trunc("data.cdb.tmp");
+  if (fdcdb == -1) die_datatmp();
+  if (cdb_make_start(&cdb,fdcdb) == -1) die_datatmp();
+
+  if (argc == 1)
+    load("data");
+  while(--argc) {
+    load(argv[argc]);
+  }
 
   if (cdb_make_finish(&cdb) == -1) die_datatmp();
   if (fsync(fdcdb) == -1) die_datatmp();
   if (close(fdcdb) == -1) die_datatmp(); /* NFS stupidity */
-  if (rename("data.tmp","data.cdb") == -1)
-    strerr_die2sys(111,FATAL,"unable to move data.tmp to data.cdb: ");
+  if (rename("data.cdb.tmp","data.cdb") == -1)
+    strerr_die2sys(111,FATAL,"unable to move data.cdb.tmp to data.cdb: ");
 
   _exit(0);
 }
diff --git a/tinydns-edit.c b/tinydns-edit.c
index 8633220..6324917 100644
--- a/tinydns-edit.c
+++ b/tinydns-edit.c
@@ -72,6 +72,12 @@ char strnum[FMT_ULONG];
 static char *names[26];
 static int used[26];
 
+static int want_ttl(unsigned long ttl)
+{
+  int isns = (mode == '.') || (mode == '&');
+  return (isns && (ttl != TTL_NS)) || (!isns && (ttl != TTL_POSITIVE));
+}
+
 void put(const char *buf,unsigned int len)
 {
   if (buffer_putalign(&bnew,buf,len) == -1) die_write();
@@ -270,8 +276,10 @@ int main(int argc,char **argv)
         if (!stralloc_cats(&f[0],":")) nomem();
       break;
   }
-  if (!stralloc_cats(&f[0],":")) nomem();
-  if (!stralloc_catb(&f[0],strnum,fmt_ulong(strnum,ttl))) nomem();
+  if (want_ttl(ttl)) {
+    if (!stralloc_cats(&f[0],":")) nomem();
+    if (!stralloc_catb(&f[0],strnum,fmt_ulong(strnum,ttl))) nomem();
+  }
   if (!stralloc_cats(&f[0],"\n")) nomem();
   put(f[0].s,f[0].len);