hlfw.ca

registry

Download patch

ref: 4040cb4739388c445ed428adb630d96784a85531
author: halfwit <michaelmisch1985@gmail.com>
date: Fri Sep 11 15:34:57 PDT 2020

Initial stash

--- /dev/null
+++ b/convDNS2M.c
@@ -1,0 +1,380 @@
+#include <u.h>
+#include <libc.h>
+#include <ip.h>
+#include "dns.h"
+
+/*
+ *  a dictionary of domain names for packing messages
+ */
+enum
+{
+	Ndict=	64,
+};
+typedef struct Dict	Dict;
+struct Dict
+{
+	struct {
+		ushort	offset;		/* pointer to packed name in message */
+		char	*name;		/* pointer to unpacked name in buf */
+	} x[Ndict];
+	int	n;		/* size of dictionary */
+	uchar	*start;		/* start of packed message */
+	char	buf[16*1024];	/* buffer for unpacked names (was 4k) */
+	char	*ep;		/* first free char in buf */
+};
+
+#define NAME(x)		p = pname(p, ep, x, dp)
+#define LABEL(x)	p = pname(p, ep, x, nil)
+#define SYMBOL(x)	p = psym(p, ep, x)
+#define STRING(x)	p = pstr(p, ep, x)
+#define BYTES(x, n)	p = pbytes(p, ep, x, n)
+#define USHORT(x)	p = pushort(p, ep, x)
+#define UCHAR(x)	p = puchar(p, ep, x)
+#define ULONG(x)	p = pulong(p, ep, x)
+#define V4ADDR(x)	p = pv4addr(p, ep, x)
+#define V6ADDR(x)	p = pv6addr(p, ep, x)
+
+static uchar*
+psym(uchar *p, uchar *ep, char *np)
+{
+	int n;
+
+	n = strlen(np);
+	if(n >= Strlen)			/* DNS maximum length string */
+		n = Strlen - 1;
+	if(ep - p < n+1)		/* see if it fits in the buffer */
+		return ep+1;
+	*p++ = n;
+	memmove(p, np, n);
+	return p + n;
+}
+
+static uchar*
+pstr(uchar *p, uchar *ep, char *np)
+{
+	return psym(p, ep, np);
+}
+
+static uchar*
+pbytes(uchar *p, uchar *ep, uchar *np, int n)
+{
+	if(ep - p < n)
+		return ep+1;
+	memmove(p, np, n);
+	return p + n;
+}
+
+static uchar*
+puchar(uchar *p, uchar *ep, int val)
+{
+	if(ep - p < 1)
+		return ep+1;
+	*p++ = val;
+	return p;
+}
+
+static uchar*
+pushort(uchar *p, uchar *ep, int val)
+{
+	if(ep - p < 2)
+		return ep+1;
+	*p++ = val>>8;
+	*p++ = val;
+	return p;
+}
+
+static uchar*
+pulong(uchar *p, uchar *ep, int val)
+{
+	if(ep - p < 4)
+		return ep+1;
+	*p++ = val>>24;
+	*p++ = val>>16;
+	*p++ = val>>8;
+	*p++ = val;
+	return p;
+}
+
+static uchar*
+pv4addr(uchar *p, uchar *ep, char *name)
+{
+	uchar ip[IPaddrlen];
+
+	if(ep - p < 4)
+		return ep+1;
+	parseip(ip, name);
+	v6tov4(p, ip);
+	return p + 4;
+}
+
+static uchar*
+pv6addr(uchar *p, uchar *ep, char *name)
+{
+	if(ep - p < IPaddrlen)
+		return ep+1;
+	parseip(p, name);
+	return p + IPaddrlen;
+}
+
+static uchar*
+pname(uchar *p, uchar *ep, char *np, Dict *dp)
+{
+	int i;
+	char *cp;
+	char *last;		/* last component packed */
+
+	if(strlen(np) >= Domlen) /* make sure we don't exceed DNS limits */
+		return ep+1;
+
+	last = nil;
+	while(*np){
+		if(dp != nil){
+			/* look through every component in the dictionary for a match */
+			for(i = 0; i < dp->n; i++){
+				if(strcmp(np, dp->x[i].name) == 0){
+					if(ep - p < 2)
+						return ep+1;
+					if ((dp->x[i].offset>>8) & 0xc0)
+						break;
+					*p++ = dp->x[i].offset>>8 | 0xc0;
+					*p++ = dp->x[i].offset;
+					return p;
+				}
+			}
+			/* if there's room, enter this name in dictionary */
+			if(dp->n < Ndict){
+				if(last != nil){
+					/* the whole name is already in dp->buf */
+					last = strchr(last, '.') + 1;
+					dp->x[dp->n].name = last;
+					dp->x[dp->n].offset = p - dp->start;
+					dp->n++;
+				} else {
+					/* add to dp->buf */
+					i = strlen(np);
+					if(dp->ep + i + 1 < &dp->buf[sizeof dp->buf]){
+						memmove(dp->ep, np, i);
+						dp->ep[i] = 0;
+						dp->x[dp->n].name = dp->ep;
+						last = dp->ep;
+						dp->x[dp->n].offset = p - dp->start;
+						dp->ep += i + 1;
+						dp->n++;
+					}
+				}
+			}
+		}
+
+		/* put next component into message */
+		cp = strchr(np, '.');
+		if(cp == nil){
+			i = strlen(np);
+			cp = np + i;	/* point to null terminator */
+		} else {
+			i = cp - np;
+			cp++;		/* point past '.' */
+		}
+		if(ep-p < i+1)
+			return ep+1;
+		if (i > Labellen)
+			return ep+1;
+		*p++ = i;		/* count of chars in label */
+		memmove(p, np, i);
+		np = cp;
+		p += i;
+	}
+
+	if(p >= ep)
+		return ep+1;
+	*p++ = 0;	/* add top level domain */
+
+	return p;
+}
+
+static uchar*
+convRR2M(RR *rp, uchar *p, uchar *ep, Dict *dp)
+{
+	uchar *lp, *data;
+	long ttl;
+	int len;
+	Txt *t;
+
+	NAME(rp->owner->name);
+	USHORT(rp->type);
+	USHORT(rp->owner->class);
+
+	if(rp->db || (ttl = (long)(rp->expire - now)) > rp->ttl)
+		ttl = rp->ttl;
+	if(ttl < 0)
+		ttl = 0;
+	ULONG(ttl);
+
+	lp = p;			/* leave room for the rdata length */
+	p += 2;
+	data = p;
+
+	if(data >= ep)
+		return p+1;
+
+	switch(rp->type){
+	case Thinfo:
+		SYMBOL(rp->cpu->name);
+		SYMBOL(rp->os->name);
+		break;
+	case Tcname:
+	case Tmb:
+	case Tmd:
+	case Tmf:
+	case Tns:
+		NAME(rp->host->name);
+		break;
+	case Tmg:
+	case Tmr:
+		NAME(rp->mb->name);
+		break;
+	case Tminfo:
+		NAME(rp->rmb->name);
+		NAME(rp->mb->name);
+		break;
+	case Tmx:
+		USHORT(rp->pref);
+		NAME(rp->host->name);
+		break;
+	case Ta:
+		V4ADDR(rp->ip->name);
+		break;
+	case Taaaa:
+		V6ADDR(rp->ip->name);
+		break;
+	case Tptr:
+		NAME(rp->ptr->name);
+		break;
+	case Tsoa:
+		NAME(rp->host->name);
+		NAME(rp->rmb->name);
+		ULONG(rp->soa->serial);
+		ULONG(rp->soa->refresh);
+		ULONG(rp->soa->retry);
+		ULONG(rp->soa->expire);
+		ULONG(rp->soa->minttl);
+		break;
+	case Tsrv:
+		USHORT(rp->srv->pri);
+		USHORT(rp->srv->weight);
+		USHORT(rp->port);
+		LABEL(rp->host->name);	/* rfc2782 sez no name compression */
+		break;
+	case Ttxt:
+		for(t = rp->txt; t != nil; t = t->next)
+			STRING(t->p);
+		break;
+	case Tnull:
+		BYTES(rp->null->data, rp->null->dlen);
+		break;
+	case Trp:
+		NAME(rp->rmb->name);
+		NAME(rp->rp->name);
+		break;
+	case Tkey:
+		USHORT(rp->key->flags);
+		UCHAR(rp->key->proto);
+		UCHAR(rp->key->alg);
+		BYTES(rp->key->data, rp->key->dlen);
+		break;
+	case Tsig:
+		USHORT(rp->sig->type);
+		UCHAR(rp->sig->alg);
+		UCHAR(rp->sig->labels);
+		ULONG(rp->sig->ttl);
+		ULONG(rp->sig->exp);
+		ULONG(rp->sig->incep);
+		USHORT(rp->sig->tag);
+		NAME(rp->sig->signer->name);
+		BYTES(rp->sig->data, rp->sig->dlen);
+		break;
+	case Tcert:
+		USHORT(rp->cert->type);
+		USHORT(rp->cert->tag);
+		UCHAR(rp->cert->alg);
+		BYTES(rp->cert->data, rp->cert->dlen);
+		break;
+	}
+
+	/* stuff in the rdata section length */
+	len = p - data;
+	*lp++ = len >> 8;
+	*lp = len;
+
+	return p;
+}
+
+static uchar*
+convQ2M(RR *rp, uchar *p, uchar *ep, Dict *dp)
+{
+	NAME(rp->owner->name);
+	USHORT(rp->type);
+	USHORT(rp->owner->class);
+	return p;
+}
+
+static uchar*
+rrloop(RR *rp, int *countp, uchar *p, uchar *ep, Dict *dp, int quest)
+{
+	uchar *np;
+
+	*countp = 0;
+	for(; rp && p < ep; rp = rp->next){
+		if(quest)
+			np = convQ2M(rp, p, ep, dp);
+		else
+			np = convRR2M(rp, p, ep, dp);
+		if(np > ep)
+			break;
+		p = np;
+		(*countp)++;
+	}
+	return p;
+}
+
+/*
+ *  convert into a message
+ */
+int
+convDNS2M(DNSmsg *m, uchar *buf, int len)
+{
+	ulong trunc = 0;
+	uchar *p, *ep, *np;
+	Dict d;
+
+	d.n = 0;
+	d.start = buf;
+	d.ep = d.buf;
+	memset(buf, 0, len);
+	m->qdcount = m->ancount = m->nscount = m->arcount = 0;
+
+	/* first pack in the RR's so we can get real counts */
+	p = buf + 12;
+	ep = buf + len;
+	p = rrloop(m->qd, &m->qdcount, p, ep, &d, 1);
+	p = rrloop(m->an, &m->ancount, p, ep, &d, 0);
+	p = rrloop(m->ns, &m->nscount, p, ep, &d, 0);
+	p = rrloop(m->ar, &m->arcount, p, ep, &d, 0);
+	if(p > ep) {
+		trunc = Ftrunc;
+		dnslog("udp packet full; truncating my reply");
+		p = ep;
+	}
+
+	/* now pack the rest */
+	np = p;
+	p = buf;
+	ep = buf + len;
+	USHORT(m->id);
+	USHORT(m->flags | trunc);
+	USHORT(m->qdcount);
+	USHORT(m->ancount);
+	USHORT(m->nscount);
+	USHORT(m->arcount);
+	USED(p);
+	return np - buf;
+}
--- /dev/null
+++ b/convM2DNS.c
@@ -1,0 +1,603 @@
+#include <u.h>
+#include <libc.h>
+#include <ip.h>
+#include "dns.h"
+
+typedef struct Scan	Scan;
+struct Scan
+{
+	uchar	*base;		/* input buffer */
+	uchar	*p;		/* current position */
+	uchar	*ep;		/* byte after the end */
+
+	char	*err;
+	char	errbuf[256];	/* hold a formatted error sometimes */
+	int	rcode;		/* outgoing response codes (reply flags) */
+	int	stop;		/* flag: stop processing */
+	int	trunc;		/* flag: input truncated */
+};
+
+static int
+errneg(RR *rp, Scan *sp, int actual)
+{
+	snprint(sp->errbuf, sizeof sp->errbuf, "negative len %d: %R",
+		actual, rp);
+	sp->err = sp->errbuf;
+	return 0;
+}
+
+static int
+errtoolong(RR *rp, Scan *sp, int remain, int need, char *where)
+{
+	char *p, *ep;
+	char ptype[64];
+
+	p =  sp->errbuf;
+	ep = sp->errbuf + sizeof sp->errbuf - 1;
+	if (where)
+		p = seprint(p, ep, "%s: ", where);
+	if (rp)
+		p = seprint(p, ep, "type %s RR: ",
+			rrname(rp->type, ptype, sizeof ptype));
+	p = seprint(p, ep, "%d bytes needed; %d remain", need, remain);
+	if (rp)
+		seprint(p, ep, ": %R", rp);
+	sp->err = sp->errbuf;
+	/* hack to cope with servers that don't set Ftrunc when they should */
+	if (remain < Maxudp && need > Maxudp)
+		sp->trunc = 1;
+	if (debug && rp)
+		dnslog("malformed rr: %R", rp);
+	return 0;
+}
+
+/*
+ *  get a ushort/ulong
+ */
+static ushort
+gchar(RR *rp, Scan *sp)
+{
+	ushort x;
+
+	if(sp->err)
+		return 0;
+	if(sp->ep - sp->p < 1)
+		return errtoolong(rp, sp, sp->ep - sp->p, 1, "gchar");
+	x = sp->p[0];
+	sp->p += 1;
+	return x;
+}
+static ushort
+gshort(RR *rp, Scan *sp)
+{
+	ushort x;
+
+	if(sp->err)
+		return 0;
+	if(sp->ep - sp->p < 2)
+		return errtoolong(rp, sp, sp->ep - sp->p, 2, "gshort");
+	x = sp->p[0]<<8 | sp->p[1];
+	sp->p += 2;
+	return x;
+}
+static ulong
+glong(RR *rp, Scan *sp)
+{
+	ulong x;
+
+	if(sp->err)
+		return 0;
+	if(sp->ep - sp->p < 4)
+		return errtoolong(rp, sp, sp->ep - sp->p, 4, "glong");
+	x = sp->p[0]<<24 | sp->p[1]<<16 | sp->p[2]<<8 | sp->p[3];
+	sp->p += 4;
+	return x;
+}
+
+/*
+ *  get an ip address
+ */
+static DN*
+gv4addr(RR *rp, Scan *sp)
+{
+	char addr[32];
+
+	if(sp->err)
+		return 0;
+	if(sp->ep - sp->p < 4)
+		return (DN*)errtoolong(rp, sp, sp->ep - sp->p, 4, "gv4addr");
+	snprint(addr, sizeof addr, "%V", sp->p);
+	sp->p += 4;
+
+	return dnlookup(addr, Cin, 1);
+}
+static DN*
+gv6addr(RR *rp, Scan *sp)
+{
+	char addr[64];
+
+	if(sp->err)
+		return 0;
+	if(sp->ep - sp->p < IPaddrlen)
+		return (DN*)errtoolong(rp, sp, sp->ep - sp->p, IPaddrlen,
+			"gv6addr");
+	snprint(addr, sizeof addr, "%I", sp->p);
+	sp->p += IPaddrlen;
+
+	return dnlookup(addr, Cin, 1);
+}
+
+/*
+ *  get a string.  make it an internal symbol.
+ */
+static DN*
+gsym(RR *rp, Scan *sp)
+{
+	int n;
+	char sym[Strlen+1];
+
+	if(sp->err)
+		return 0;
+	n = 0;
+	if (sp->p < sp->ep)
+		n = *(sp->p++);
+	if(sp->ep - sp->p < n)
+		return (DN*)errtoolong(rp, sp, sp->ep - sp->p, n, "gsym");
+
+	if(n > Strlen){
+		sp->err = "illegal string (symbol)";
+		return 0;
+	}
+	strncpy(sym, (char*)sp->p, n);
+	sym[n] = 0;
+	if (strlen(sym) != n)
+		sp->err = "symbol shorter than declared length";
+	sp->p += n;
+
+	return dnlookup(sym, Csym, 1);
+}
+
+/*
+ *  get a string.  don't make it an internal symbol.
+ */
+static Txt*
+gstr(RR *rp, Scan *sp)
+{
+	int n;
+	char sym[Strlen+1];
+	Txt *t;
+
+	if(sp->err)
+		return 0;
+	n = 0;
+	if (sp->p < sp->ep)
+		n = *(sp->p++);
+	if(sp->ep - sp->p < n)
+		return (Txt*)errtoolong(rp, sp, sp->ep - sp->p, n, "gstr");
+
+	if(n > Strlen){
+		sp->err = "illegal string";
+		return 0;
+	}
+	strncpy(sym, (char*)sp->p, n);
+	sym[n] = 0;
+	if (strlen(sym) != n)
+		sp->err = "string shorter than declared length";
+	sp->p += n;
+
+	t = emalloc(sizeof(*t));
+	t->next = nil;
+	t->p = estrdup(sym);
+	return t;
+}
+
+/*
+ *  get a sequence of bytes
+ */
+static int
+gbytes(RR *rp, Scan *sp, uchar **p, int n)
+{
+	*p = nil;			/* i think this is a good idea */
+	if(sp->err)
+		return 0;
+	if(n < 0)
+		return errneg(rp, sp, n);
+	if(sp->ep - sp->p < n)
+		return errtoolong(rp, sp, sp->ep - sp->p, n, "gbytes");
+	*p = emalloc(n);
+	memmove(*p, sp->p, n);
+	sp->p += n;
+
+	return n;
+}
+
+/*
+ *  get a domain name.  'to' must point to a buffer at least Domlen+1 long.
+ */
+static char*
+gname(char *to, RR *rp, Scan *sp)
+{
+	int len, off, pointer, n;
+	char *tostart, *toend;
+	uchar *p;
+
+	tostart = to;
+	if(sp->err || sp->stop)
+		goto err;
+	pointer = 0;
+	p = sp->p;
+	if(p == nil) {
+		dnslog("gname: %R: nil sp->p", rp);
+		goto err;
+	}
+	toend = to + Domlen;
+	for(len = 0; *p && p < sp->ep; len += (pointer? 0: n+1)) {
+		n = 0;
+		switch(*p & 0300) {
+		case 0:			/* normal label */
+			if(p < sp->ep)
+				n = *p++ & 077;		/* pick up length */
+			if(sp->ep - p <= n){
+				sp->err = "bad name length";
+				goto err;
+			}
+			if(len + n < Domlen - 1){
+				if(n > toend - to){
+					errtoolong(rp, sp, toend - to, n,
+						"name too long");
+					goto err;
+				}
+				memmove(to, p, n);
+				to += n;
+			}
+			p += n;
+			if(*p){
+				if(to >= toend){
+					errtoolong(rp, sp, toend - to, 2,
+				     "more name components but no bytes left");
+					goto err;
+				}
+				*to++ = '.';
+			}
+			break;
+		case 0100:		/* edns extended label type, rfc 2671 */
+			/*
+			 * treat it like an EOF for now; it seems to be at
+			 * the end of a long tcp reply.
+			 */
+			dnslog("edns label; first byte 0%o = '%c'", *p, *p);
+			sp->stop = 1;
+			goto err;
+		case 0200:		/* reserved */
+			sp->err = "reserved-use label present";
+			goto err;
+		case 0300:		/* pointer to other spot in message */
+			if(pointer++ > 10){
+				sp->err = "pointer loop";
+				goto err;
+			}
+			off = (p[0] & 077)<<8 | p[1];
+			p = sp->base + off;
+			if(p >= sp->ep){
+				sp->err = "bad pointer";
+				goto err;
+			}
+			n = 0;
+			break;
+		}
+	}
+	*to = 0;
+	if(pointer)
+		sp->p += len + 2;	/* + 2 for pointer */
+	else
+		sp->p += len + 1;	/* + 1 for the null domain */
+	return tostart;
+err:
+	*tostart = 0;
+	return tostart;
+}
+
+/*
+ * ms windows 2000 seems to get the bytes backward in the type field
+ * of ptr records, so return a format error as feedback.
+ */
+static ushort
+mstypehack(Scan *sp, ushort type, char *where)
+{
+	if ((uchar)type == 0 && (type>>8) != 0) {
+		USED(where);
+//		dnslog("%s: byte-swapped type field in ptr rr from win2k",
+//			where);
+		if (sp->rcode == Rok)
+			sp->rcode = Rformat;
+		type >>= 8;
+	}
+	return type;
+}
+
+#define NAME(x)		gname(x, rp, sp)
+#define SYMBOL(x)	((x) = gsym(rp, sp))
+#define STRING(x)	((x) = gstr(rp, sp))
+#define USHORT(x)	((x) = gshort(rp, sp))
+#define ULONG(x)	((x) = glong(rp, sp))
+#define UCHAR(x)	((x) = gchar(rp, sp))
+#define V4ADDR(x)	((x) = gv4addr(rp, sp))
+#define V6ADDR(x)	((x) = gv6addr(rp, sp))
+#define BYTES(x, y)	((y) = gbytes(rp, sp, &(x), len - (sp->p - data)))
+
+/*
+ *  convert the next RR from a message
+ */
+static RR*
+convM2RR(Scan *sp, char *what)
+{
+	int type, class, len, left;
+	char dname[Domlen+1];
+	uchar *data;
+	RR *rp;
+	Txt *t, **l;
+
+retry:
+	rp = nil;
+	NAME(dname);
+	USHORT(type);
+	USHORT(class);
+
+	type = mstypehack(sp, type, "convM2RR");
+	rp = rralloc(type);
+	rp->owner = dnlookup(dname, class, 1);
+	rp->type = type;
+
+	ULONG(rp->ttl);
+	USHORT(len);			/* length of data following */
+	data = sp->p;
+	assert(data != nil);
+	left = sp->ep - sp->p;
+
+	/*
+	 * ms windows generates a lot of badly-formatted hints.
+	 * hints are only advisory, so don't log complaints about them.
+	 * it also generates answers in which p overshoots ep by exactly
+	 * one byte; this seems to be harmless, so don't log them either.
+	 */
+	if (len > left &&
+	   !(strcmp(what, "hints") == 0 ||
+	     sp->p == sp->ep + 1 && strcmp(what, "answers") == 0))
+		errtoolong(rp, sp, left, len, "convM2RR");
+	if(sp->err || sp->rcode || sp->stop){
+		rrfree(rp);
+		return nil;
+	}
+	/* even if we don't log an error message, truncate length to fit data */
+	if (len > left)
+		len = left;
+
+	switch(type){
+	default:
+		/* unknown type, just ignore it */
+		sp->p = data + len;
+		rrfree(rp);
+		goto retry;
+	case Thinfo:
+		SYMBOL(rp->cpu);
+		SYMBOL(rp->os);
+		break;
+	case Tcname:
+	case Tmb:
+	case Tmd:
+	case Tmf:
+	case Tns:
+		rp->host = dnlookup(NAME(dname), Cin, 1);
+		break;
+	case Tmg:
+	case Tmr:
+		rp->mb  = dnlookup(NAME(dname), Cin, 1);
+		break;
+	case Tminfo:
+		rp->rmb = dnlookup(NAME(dname), Cin, 1);
+		rp->mb  = dnlookup(NAME(dname), Cin, 1);
+		break;
+	case Tmx:
+		USHORT(rp->pref);
+		rp->host = dnlookup(NAME(dname), Cin, 1);
+		break;
+	case Ta:
+		V4ADDR(rp->ip);
+		break;
+	case Taaaa:
+		V6ADDR(rp->ip);
+		break;
+	case Tptr:
+		rp->ptr = dnlookup(NAME(dname), Cin, 1);
+		break;
+	case Tsoa:
+		rp->host = dnlookup(NAME(dname), Cin, 1);
+		rp->rmb  = dnlookup(NAME(dname), Cin, 1);
+		ULONG(rp->soa->serial);
+		ULONG(rp->soa->refresh);
+		ULONG(rp->soa->retry);
+		ULONG(rp->soa->expire);
+		ULONG(rp->soa->minttl);
+		break;
+	case Tsrv:
+		USHORT(rp->srv->pri);
+		USHORT(rp->srv->weight);
+		USHORT(rp->port);
+		/*
+		 * rfc2782 sez no name compression but to be
+		 * backward-compatible with rfc2052, we try to expand the name. 
+		 * if the length is under 64 bytes, either interpretation is
+		 * fine; if it's longer, we'll assume it's compressed,
+		 * as recommended by rfc3597.
+		 */
+		rp->host = dnlookup(NAME(dname), Cin, 1);
+		break;
+	case Ttxt:
+		l = &rp->txt;
+		*l = nil;
+		while(sp->p - data < len){
+			STRING(t);
+			*l = t;
+			l = &t->next;
+		}
+		break;
+	case Tnull:
+		BYTES(rp->null->data, rp->null->dlen);
+		break;
+	case Trp:
+		rp->rmb = dnlookup(NAME(dname), Cin, 1);
+		rp->rp  = dnlookup(NAME(dname), Cin, 1);
+		break;
+	case Tkey:
+		USHORT(rp->key->flags);
+		UCHAR(rp->key->proto);
+		UCHAR(rp->key->alg);
+		BYTES(rp->key->data, rp->key->dlen);
+		break;
+	case Tsig:
+		USHORT(rp->sig->type);
+		UCHAR(rp->sig->alg);
+		UCHAR(rp->sig->labels);
+		ULONG(rp->sig->ttl);
+		ULONG(rp->sig->exp);
+		ULONG(rp->sig->incep);
+		USHORT(rp->sig->tag);
+		rp->sig->signer = dnlookup(NAME(dname), Cin, 1);
+		BYTES(rp->sig->data, rp->sig->dlen);
+		break;
+	case Tcert:
+		USHORT(rp->cert->type);
+		USHORT(rp->cert->tag);
+		UCHAR(rp->cert->alg);
+		BYTES(rp->cert->data, rp->cert->dlen);
+		break;
+	}
+	if(sp->p - data != len) {
+		char ptype[64];
+
+		/*
+		 * ms windows 2000 generates cname queries for reverse lookups
+		 * with this particular error.  don't bother logging it.
+		 *
+		 * server: input error: bad cname RR len (actual 2 != len 0):
+		 * 235.9.104.135.in-addr.arpa cname
+		 *	235.9.104.135.in-addr.arpa from 135.104.9.235
+		 */
+		if (type == Tcname && sp->p - data == 2 && len == 0)
+			return rp;
+		if (len > sp->p - data){
+			dnslog("bad %s RR len (%d bytes nominal, %zud actual): %R",
+				rrname(type, ptype, sizeof ptype), len,
+				sp->p - data, rp);
+			rrfree(rp);
+			rp = nil;
+		}
+	}
+	// if(rp) dnslog("convM2RR: got %R", rp);
+	return rp;
+}
+
+/*
+ *  convert the next question from a message
+ */
+static RR*
+convM2Q(Scan *sp)
+{
+	char dname[Domlen+1];
+	int type, class;
+	RR *rp;
+
+	rp = nil;
+	NAME(dname);
+	USHORT(type);
+	USHORT(class);
+	if(sp->err || sp->rcode || sp->stop)
+		return nil;
+
+	type = mstypehack(sp, type, "convM2Q");
+	rp = rralloc(type);
+	rp->owner = dnlookup(dname, class, 1);
+
+	return rp;
+}
+
+static RR*
+rrloop(Scan *sp, char *what, int count, int quest)
+{
+	int i;
+	RR *first, *rp, **l;
+
+	if(sp->err || sp->rcode || sp->stop)
+		return nil;
+	l = &first;
+	first = nil;
+	for(i = 0; i < count; i++){
+		rp = quest? convM2Q(sp): convM2RR(sp, what);
+		if(rp == nil)
+			break;
+		setmalloctag(rp, getcallerpc(&sp));
+		/*
+		 * it might be better to ignore the bad rr, possibly break out,
+		 * but return the previous rrs, if any.  that way our callers
+		 * would know that they had got a response, however ill-formed.
+		 */
+		if(sp->err || sp->rcode || sp->stop){
+			rrfree(rp);
+			break;
+		}
+		*l = rp;
+		l = &rp->next;
+	}
+//	if(first)
+//		setmalloctag(first, getcallerpc(&sp));
+	return first;
+}
+
+/*
+ *  convert the next DNS from a message stream.
+ *  if there are formatting errors or the like during parsing of the message,
+ *  set *codep to the outgoing response code (e.g., Rformat), which will
+ *  abort processing and reply immediately with the outgoing response code.
+ */
+char*
+convM2DNS(uchar *buf, int len, DNSmsg *m, int *codep)
+{
+	char *err = nil;
+	RR *rp = nil;
+	Scan scan;
+	Scan *sp;
+
+	assert(len >= 0);
+	assert(buf != nil);
+	sp = &scan;
+	memset(sp, 0, sizeof *sp);
+	sp->base = sp->p = buf;
+	sp->ep = buf + len;
+	sp->err = nil;
+	sp->errbuf[0] = '\0';
+	sp->rcode = Rok;
+
+	memset(m, 0, sizeof *m);
+	USHORT(m->id);
+	USHORT(m->flags);
+	USHORT(m->qdcount);
+	USHORT(m->ancount);
+	USHORT(m->nscount);
+	USHORT(m->arcount);
+
+	m->qd = rrloop(sp, "questions",	m->qdcount, 1);
+	m->an = rrloop(sp, "answers",	m->ancount, 0);
+	m->ns = rrloop(sp, "nameservers",m->nscount, 0);
+	if (sp->stop)
+		sp->err = nil;
+	if (sp->err)
+		err = strdup(sp->err);		/* live with bad ar's */
+	m->ar = rrloop(sp, "hints",	m->arcount, 0);
+	if (sp->trunc)
+		m->flags |= Ftrunc;
+	if (sp->stop)
+		sp->rcode = Rok;
+	if (codep)
+		*codep = sp->rcode;
+	return err;
+}
--- /dev/null
+++ b/cs.c
@@ -1,0 +1,2100 @@
+#include <u.h>
+#include <libc.h>
+#include <auth.h>
+#include <fcall.h>
+#include <bio.h>
+#include <ctype.h>
+#include <ndb.h>
+#include <ip.h>
+#include <String.h>
+
+enum
+{
+	Nreply=			20,
+	Maxreply=		256,
+	Maxrequest=		128,
+	Maxpath=		128,
+	Maxfdata=		8192,
+	Maxhost=		64,		/* maximum host name size */
+	Maxservice=		64,		/* maximum service name size */
+	Maxactive=		200,		/* maximum number of active slave procs */
+
+	Qdir=			0,
+	Qcs=			1,
+};
+
+typedef struct Mfile	Mfile;
+typedef struct Mlist	Mlist;
+typedef struct Network	Network;
+typedef struct Flushreq	Flushreq;
+typedef struct Job	Job;
+
+int vers;		/* incremented each clone/attach */
+
+struct Mfile
+{
+	int		busy;	/* fid in use */
+	int		ref;	/* cleanup when drops to zero */
+
+	char		*user;
+	Qid		qid;
+	int		fid;
+
+	/*
+	 *  current request
+	 */
+	char		*net;
+	char		*host;
+	char		*serv;
+	char		*rem;
+
+	/*
+	 *  result of the last lookup
+	 */
+	Network		*nextnet;
+	int		nreply;
+	char		*reply[Nreply];
+	int		replylen[Nreply];
+};
+
+struct Mlist
+{
+	Mlist	*next;
+	Mfile	mf;
+};
+
+
+/*
+ *  active requests
+ */
+struct Job
+{
+	Job	*next;
+	int	flushed;
+	Fcall	request;
+	Fcall	reply;
+};
+QLock	joblock;
+Job	*joblist;
+
+Mlist	*mlist;
+int	mfd[2];
+int	debug;
+
+jmp_buf	masterjmp;	/* return through here after a slave process has been created */
+int	*isslave;	/* *isslave non-zero means this is a slave process */
+long	active;		/* number of active slaves */
+char	*dbfile;
+Ndb	*db, *netdb;
+char	*csuser;
+
+void	rversion(Job*);
+void	rflush(Job*);
+void	rattach(Job*, Mfile*);
+char*	rwalk(Job*, Mfile*);
+void	ropen(Job*, Mfile*);
+void	rcreate(Job*, Mfile*);
+void	rread(Job*, Mfile*);
+void	rwrite(Job*, Mfile*);
+void	rclunk(Job*, Mfile*);
+void	rremove(Job*, Mfile*);
+void	rstat(Job*, Mfile*);
+void	rwstat(Job*, Mfile*);
+void	rauth(Job*);
+void	sendmsg(Job*, char*);
+void	error(char*);
+void	mountinit(char*, char*);
+void	io(void);
+void	ndbinit(void);
+void	netinit(int);
+void	netadd(char*);
+char	*genquery(Mfile*, char*);
+char*	ipinfoquery(Mfile*, char**, int);
+int	needproto(Network*, Ndbtuple*);
+int	lookup(Mfile*);
+void	ipid(void);
+void	readipinterfaces(void);
+void*	emalloc(int);
+char*	estrdup(char*);
+Job*	newjob(void);
+void	freejob(Job*);
+void	setext(char*, int, char*);
+void	cleanmf(Mfile*);
+
+QLock	dblock;		/* mutex on database operations */
+QLock	netlock;	/* mutex for netinit() */
+
+char	*logfile = "cs";
+
+char	mntpt[Maxpath];
+char	netndb[Maxpath];
+
+/*
+ *  Network specific translators
+ */
+Ndbtuple*	iplookup(Network*, char*, char*);
+char*		iptrans(Ndbtuple*, Network*, char*, char*, int);
+Ndbtuple*	svclookup(Network*, char*, char*);
+char*		svctrans(Ndbtuple*, Network*, char*, char*, int);
+Ndbtuple*	telcolookup(Network*, char*, char*);
+char*		telcotrans(Ndbtuple*, Network*, char*, char*, int);
+
+Ndbtuple*	dnsiplookup(char*, Ndbs*, int);
+Ndbtuple*	myipinfo(Ndb *db, char **list, int n);
+
+struct Network
+{
+	char		*net;
+	Ndbtuple	*(*lookup)(Network*, char*, char*);
+	char		*(*trans)(Ndbtuple*, Network*, char*, char*, int);
+
+	char		considered;		/* flag: ignored for "net!"? */
+	char		fasttimeout;		/* flag. was for IL */
+	char		ipvers;			/* flag: V4, V6 */
+
+	Network		*next;
+};
+
+enum {
+	Ntcp = 1,
+
+	V4 = 1,
+	V6 = 2,
+};
+
+/*
+ *  net doesn't apply to (r)udp, icmp(v6), or telco (for speed).
+ */
+Network network[] = {
+	{ "il",		iplookup,	iptrans,	0, 1, V4,	},
+	{ "tcp",	iplookup,	iptrans,	0, 0, V4|V6,	},
+	{ "il", 	iplookup,	iptrans,	0, 0, V4,	},
+	{ "udp",	iplookup,	iptrans,	1, 0, V4|V6,	},
+	{ "icmp",	iplookup,	iptrans,	1, 0, V4,	},
+	{ "icmpv6",	iplookup,	iptrans,	1, 0, V6,	},
+	{ "rudp",	iplookup,	iptrans,	1, 0, V4,	},
+	{ "ssh",	iplookup,	iptrans,	1, 0, V4|V6,	},
+	{ "telco",	telcolookup,	telcotrans,	1, 0, 0,	},
+	{ "svc",	svclookup,	svctrans,	1, 0, 0,	},
+	{ 0 },
+};
+
+QLock ipifclock;
+Ipifc *ipifcs;
+int confipvers;
+int dnsipvers;
+int lookipvers = V4|V6;
+
+char *mysysname;
+
+Network *netlist;		/* networks ordered by preference */
+Network *last;
+
+static void
+nstrcpy(char *to, char *from, int len)
+{
+	strncpy(to, from, len);
+	to[len-1] = 0;
+}
+
+void
+usage(void)
+{
+	fprint(2, "usage: %s [-46dn] [-f ndb-file] [-x netmtpt]\n", argv0);
+	exits("usage");
+}
+
+void
+main(int argc, char *argv[])
+{
+	int justsetname;
+	char ext[Maxpath], servefile[Maxpath];
+
+	justsetname = 0;
+	setnetmtpt(mntpt, sizeof(mntpt), nil);
+	ext[0] = 0;
+	ARGBEGIN{
+	case '4':
+		lookipvers = V4;
+		break;
+	case '6':
+		lookipvers = V6;
+		break;
+	case 'd':
+		debug = 1;
+		break;
+	case 'f':
+		dbfile = EARGF(usage());
+		break;
+	case 'n':
+		justsetname = 1;
+		break;
+	case 'x':
+		setnetmtpt(mntpt, sizeof(mntpt), EARGF(usage()));
+		setext(ext, sizeof(ext), mntpt);
+		break;
+	}ARGEND
+	USED(argc);
+	USED(argv);
+
+	snprint(netndb, sizeof(netndb), "%s/ndb", mntpt);
+
+	fmtinstall('E', eipfmt);
+	fmtinstall('I', eipfmt);
+	fmtinstall('M', eipfmt);
+	fmtinstall('F', fcallfmt);
+
+	ndbinit();
+	netinit(0);
+
+	if(!justsetname){
+		snprint(servefile, sizeof(servefile), "#s/cs%s", ext);
+		unmount(servefile, mntpt);
+		remove(servefile);
+
+		rfork(RFREND|RFNOTEG);
+		csuser = estrdup(getuser());
+		mountinit(servefile, mntpt);
+		io();
+	}
+	exits(0);
+}
+
+/*
+ *  if a mount point is specified, set the cs extention to be the mount point
+ *  with '_'s replacing '/'s
+ */
+void
+setext(char *ext, int n, char *p)
+{
+	int i, c;
+
+	n--;
+	for(i = 0; i < n; i++){
+		c = p[i];
+		if(c == 0)
+			break;
+		if(c == '/')
+			c = '_';
+		ext[i] = c;
+	}
+	ext[i] = 0;
+}
+
+void
+mountinit(char *service, char *mntpt)
+{
+	int f;
+	int p[2];
+	char buf[32];
+
+	if(pipe(p) < 0)
+		error("pipe failed");
+
+	/*
+	 *  make a /srv/cs
+	 */
+	f = create(service, OWRITE|ORCLOSE, 0666);
+	if(f < 0)
+		error(service);
+	snprint(buf, sizeof(buf), "%d", p[1]);
+	if(write(f, buf, strlen(buf)) != strlen(buf))
+		error("write /srv/cs");
+
+	switch(rfork(RFFDG|RFPROC|RFNAMEG)){
+	case 0:
+		close(p[1]);
+		procsetname("%s", mntpt);
+		break;
+	case -1:
+		error("fork failed");
+	default:
+		/*
+		 *  put ourselves into the file system
+		 */
+		close(p[0]);
+		if(mount(p[1], -1, mntpt, MAFTER, "") == -1)
+			error("mount failed");
+		_exits(0);
+	}
+	mfd[0] = mfd[1] = p[0];
+}
+
+void
+ndbinit(void)
+{
+	db = ndbopen(dbfile);
+	if(db == nil)
+		error("can't open network database");
+
+	for(netdb = db; netdb != nil; netdb = netdb->next)
+		if(strcmp(netdb->file, netndb) == 0)
+			return;
+
+	netdb = ndbopen(netndb);
+	if(netdb != nil){
+		netdb->nohash = 1;
+		db = ndbcat(netdb, db);
+	}
+}
+
+Mfile*
+newfid(int fid)
+{
+	Mlist *f, *ff;
+	Mfile *mf;
+
+	ff = nil;
+	for(f = mlist; f != nil; f = f->next)
+		if(f->mf.busy && f->mf.fid == fid)
+			return &f->mf;
+		else if(ff == nil && !f->mf.busy && !f->mf.ref)
+			ff = f;
+	if(ff == nil){
+		ff = emalloc(sizeof *f);
+		ff->next = mlist;
+		mlist = ff;
+	}
+	mf = &ff->mf;
+	memset(mf, 0, sizeof *mf);
+	mf->fid = fid;
+	return mf;
+}
+
+Job*
+newjob(void)
+{
+	Job *job;
+
+	job = emalloc(sizeof *job);
+	qlock(&joblock);
+	job->next = joblist;
+	joblist = job;
+	job->request.tag = -1;
+	qunlock(&joblock);
+	return job;
+}
+
+void
+freejob(Job *job)
+{
+	Job **l;
+
+	qlock(&joblock);
+	for(l = &joblist; *l != nil; l = &(*l)->next){
+		if((*l) == job){
+			*l = job->next;
+			break;
+		}
+	}
+	qunlock(&joblock);
+	free(job);
+}
+
+void
+flushjob(int tag)
+{
+	Job *job;
+
+	qlock(&joblock);
+	for(job = joblist; job != nil; job = job->next){
+		if(job->request.tag == tag && job->request.type != Tflush){
+			job->flushed = 1;
+			break;
+		}
+	}
+	qunlock(&joblock);
+}
+
+void
+io(void)
+{
+	long n;
+	Mfile *mf;
+	int slaveflag;
+	uchar mdata[IOHDRSZ + Maxfdata];
+	Job *job;
+
+	/*
+	 *  if we ask dns to fulfill requests,
+	 *  a slave process is created to wait for replies.  The
+	 *  master process returns immediately via a longjmp
+	 *  through 'masterjmp'.
+	 *
+	 *  *isslave is a pointer into the call stack to a variable
+	 *  that tells whether or not the current process is a slave.
+	 */
+	slaveflag = 0;		/* init slave variable */
+	isslave = &slaveflag;
+	setjmp(masterjmp);
+
+	for(;;){
+		n = read9pmsg(mfd[0], mdata, sizeof mdata);
+		if(n < 0)
+			error("mount read");
+		if(n == 0)
+			break;
+		job = newjob();
+		if(convM2S(mdata, n, &job->request) != n){
+			syslog(1, logfile, "format error %ux %ux %ux %ux %ux",
+				mdata[0], mdata[1], mdata[2], mdata[3], mdata[4]);
+			freejob(job);
+			break;
+		}
+		qlock(&dblock);
+		mf = newfid(job->request.fid);
+		if(debug)
+			syslog(0, logfile, "%F", &job->request);
+
+		switch(job->request.type){
+		default:
+			syslog(1, logfile, "unknown request type %d", job->request.type);
+			break;
+		case Tversion:
+			rversion(job);
+			break;
+		case Tauth:
+			rauth(job);
+			break;
+		case Tflush:
+			rflush(job);
+			break;
+		case Tattach:
+			rattach(job, mf);
+			break;
+		case Twalk:
+			rwalk(job, mf);
+			break;
+		case Topen:
+			ropen(job, mf);
+			break;
+		case Tcreate:
+			rcreate(job, mf);
+			break;
+		case Tread:
+			rread(job, mf);
+			break;
+		case Twrite:
+			rwrite(job, mf);
+			break;
+		case Tclunk:
+			rclunk(job, mf);
+			break;
+		case Tremove:
+			rremove(job, mf);
+			break;
+		case Tstat:
+			rstat(job, mf);
+			break;
+		case Twstat:
+			rwstat(job, mf);
+			break;
+		}
+		qunlock(&dblock);
+
+		freejob(job);
+
+		/*
+		 *  slave processes die after replying
+		 */
+		if(*isslave){
+			if(debug)
+				syslog(0, logfile, "slave death %d", getpid());
+			adec(&active);
+			_exits(0);
+		}
+	}
+}
+
+void
+rversion(Job *job)
+{
+	if(job->request.msize > IOHDRSZ + Maxfdata)
+		job->reply.msize = IOHDRSZ + Maxfdata;
+	else
+		job->reply.msize = job->request.msize;
+	job->reply.version = "9P2000";
+	if(strncmp(job->request.version, "9P", 2) != 0)
+		job->reply.version = "unknown";
+	sendmsg(job, nil);
+}
+
+void
+rauth(Job *job)
+{
+	sendmsg(job, "cs: authentication not required");
+}
+
+/*
+ *  don't flush till all the slaves are done
+ */
+void
+rflush(Job *job)
+{
+	flushjob(job->request.oldtag);
+	sendmsg(job, nil);
+}
+
+void
+rattach(Job *job, Mfile *mf)
+{
+	if(mf->busy == 0){
+		mf->busy = 1;
+		mf->user = estrdup(job->request.uname);
+	}
+	mf->qid.vers = vers++;
+	mf->qid.type = QTDIR;
+	mf->qid.path = 0LL;
+	job->reply.qid = mf->qid;
+	sendmsg(job, nil);
+}
+
+
+char*
+rwalk(Job *job, Mfile *mf)
+{
+	char *err;
+	char **elems;
+	int nelems;
+	int i;
+	Mfile *nmf;
+	Qid qid;
+
+	err = nil;
+	nmf = nil;
+	elems = job->request.wname;
+	nelems = job->request.nwname;
+	job->reply.nwqid = 0;
+
+	if(job->request.newfid != job->request.fid){
+		/* clone fid */
+		nmf = newfid(job->request.newfid);
+		if(nmf->busy){
+			nmf = nil;
+			err = "clone to used channel";
+			goto send;
+		}
+		*nmf = *mf;
+		nmf->user = estrdup(mf->user);
+		nmf->fid = job->request.newfid;
+		nmf->qid.vers = vers++;
+		mf = nmf;
+	}
+	/* else nmf will be nil */
+
+	qid = mf->qid;
+	if(nelems > 0){
+		/* walk fid */
+		for(i=0; i<nelems && i<MAXWELEM; i++){
+			if((qid.type & QTDIR) == 0){
+				err = "not a directory";
+				break;
+			}
+			if(strcmp(elems[i], "..") == 0 || strcmp(elems[i], ".") == 0){
+				qid.type = QTDIR;
+				qid.path = Qdir;
+    Found:
+				job->reply.wqid[i] = qid;
+				job->reply.nwqid++;
+				continue;
+			}
+			if(strcmp(elems[i], "cs") == 0){
+				qid.type = QTFILE;
+				qid.path = Qcs;
+				goto Found;
+			}
+			err = "file does not exist";
+			break;
+		}
+	}
+
+    send:
+	if(nmf != nil && (err!=nil || job->reply.nwqid<nelems)){
+		cleanmf(nmf);
+		free(nmf->user);
+		nmf->user = nil;
+		nmf->busy = 0;
+		nmf->fid = 0;
+	}
+	if(err == nil)
+		mf->qid = qid;
+	sendmsg(job, err);
+	return err;
+}
+
+void
+ropen(Job *job, Mfile *mf)
+{
+	int mode;
+	char *err;
+
+	err = nil;
+	mode = job->request.mode;
+	if(mf->qid.type & QTDIR){
+		if(mode)
+			err = "permission denied";
+	}
+	job->reply.qid = mf->qid;
+	job->reply.iounit = 0;
+	sendmsg(job, err);
+}
+
+void
+rcreate(Job *job, Mfile *mf)
+{
+	USED(mf);
+	sendmsg(job, "creation permission denied");
+}
+
+void
+rread(Job *job, Mfile *mf)
+{
+	int i, n, cnt;
+	long off, toff, clock;
+	Dir dir;
+	uchar buf[Maxfdata];
+	char *err;
+
+	n = 0;
+	err = nil;
+	off = job->request.offset;
+	cnt = job->request.count;
+	mf->ref++;
+
+	if(mf->qid.type & QTDIR){
+		clock = time(0);
+		if(off == 0){
+			memset(&dir, 0, sizeof dir);
+			dir.name = "cs";
+			dir.qid.type = QTFILE;
+			dir.qid.vers = vers;
+			dir.qid.path = Qcs;
+			dir.mode = 0666;
+			dir.length = 0;
+			dir.uid = mf->user;
+			dir.gid = mf->user;
+			dir.muid = mf->user;
+			dir.atime = clock;	/* wrong */
+			dir.mtime = clock;	/* wrong */
+			n = convD2M(&dir, buf, sizeof buf);
+		}
+		job->reply.data = (char*)buf;
+		goto send;
+	}
+
+	for(;;){
+		/* look for an answer at the right offset */
+		toff = 0;
+		for(i = 0; mf->reply[i] != nil && i < mf->nreply; i++){
+			n = mf->replylen[i];
+			if(off < toff + n)
+				break;
+			toff += n;
+		}
+		if(i < mf->nreply)
+			break;		/* got something to return */
+
+		/* try looking up more answers */
+		if(lookup(mf) == 0 || job->flushed){
+			/* no more */
+			n = 0;
+			goto send;
+		}
+	}
+
+	/* give back a single reply (or part of one) */
+	job->reply.data = mf->reply[i] + (off - toff);
+	if(cnt > toff - off + n)
+		n = toff - off + n;
+	else
+		n = cnt;
+
+send:
+	job->reply.count = n;
+	sendmsg(job, err);
+
+	if(--mf->ref == 0 && mf->busy == 0)
+		cleanmf(mf);
+}
+
+void
+cleanmf(Mfile *mf)
+{
+	int i;
+
+	if(mf->net != nil){
+		free(mf->net);
+		mf->net = nil;
+	}
+	if(mf->host != nil){
+		free(mf->host);
+		mf->host = nil;
+	}
+	if(mf->serv != nil){
+		free(mf->serv);
+		mf->serv = nil;
+	}
+	if(mf->rem != nil){
+		free(mf->rem);
+		mf->rem = nil;
+	}
+	for(i = 0; i < mf->nreply; i++){
+		free(mf->reply[i]);
+		mf->reply[i] = nil;
+		mf->replylen[i] = 0;
+	}
+	mf->nreply = 0;
+	mf->nextnet = netlist;
+}
+
+void
+rwrite(Job *job, Mfile *mf)
+{
+	int cnt, n;
+	char *err;
+	char *field[4];
+	char curerr[64];
+
+	err = nil;
+	cnt = job->request.count;
+	if(mf->qid.type & QTDIR){
+		err = "can't write directory";
+		goto send;
+	}
+	if(cnt >= Maxrequest){
+		err = "request too long";
+		goto send;
+	}
+	job->request.data[cnt] = 0;
+
+	if(strcmp(mf->user, "none") == 0 || strcmp(mf->user, csuser) != 0)
+		goto query;	/* skip special commands if not owner */
+
+	/*
+	 *  toggle debugging
+	 */
+	if(strncmp(job->request.data, "debug", 5)==0){
+		debug ^= 1;
+		syslog(1, logfile, "debug %d", debug);
+		goto send;
+	}
+
+	/*
+	 *  toggle ipv4 lookups
+	 */
+	if(strncmp(job->request.data, "ipv4", 4)==0){
+		lookipvers ^= V4;
+		syslog(1, logfile, "ipv4lookups %d", (lookipvers & V4) != 0);
+		goto send;
+	}
+
+	/*
+	 *  toggle ipv6 lookups
+	 */
+	if(strncmp(job->request.data, "ipv6", 4)==0){
+		lookipvers ^= V6;
+		syslog(1, logfile, "ipv6lookups %d", (lookipvers & V6) != 0);
+		goto send;
+	}
+
+	/*
+	 *  add networks to the default list
+	 */
+	if(strncmp(job->request.data, "add ", 4)==0){
+		if(job->request.data[cnt-1] == '\n')
+			job->request.data[cnt-1] = 0;
+		netadd(job->request.data+4);
+		readipinterfaces();
+		goto send;
+	}
+
+	/*
+	 *  refresh all state
+	 */
+	if(strncmp(job->request.data, "refresh", 7)==0){
+		netinit(1);
+		goto send;
+	}
+
+query:
+	if(mf->ref){
+		err = "query already in progress";
+		goto send;
+	}
+	mf->ref++;
+
+	/* start transaction with a clean slate */
+	cleanmf(mf);
+
+	/*
+	 *  look for a general query
+	 */
+	if(*job->request.data == '!'){
+		err = genquery(mf, job->request.data+1);
+		goto done;
+	}
+
+	if(debug)
+		syslog(0, logfile, "write %s", job->request.data);
+	/*
+	 *  break up name
+	 */
+	n = getfields(job->request.data, field, 4, 1, "!");
+	switch(n){
+	case 1:
+		mf->net = estrdup("net");
+		mf->host = estrdup(field[0]);
+		break;
+	case 4:
+		mf->rem = estrdup(field[3]);
+		/* fall through */
+	case 3:
+		mf->serv = estrdup(field[2]);
+		/* fall through */
+	case 2:
+		mf->host = estrdup(field[1]);
+		mf->net = estrdup(field[0]);
+		break;
+	}
+
+	/*
+	 *  do the first net worth of lookup
+	 */
+	if(lookup(mf) == 0){
+		rerrstr(curerr, sizeof curerr);
+		err = curerr;
+	}
+
+done:
+	if(--mf->ref == 0 && mf->busy == 0)
+		cleanmf(mf);
+
+send:
+	job->reply.count = cnt;
+	sendmsg(job, err);
+}
+
+void
+rclunk(Job *job, Mfile *mf)
+{
+	if(mf->ref == 0)
+		cleanmf(mf);
+	free(mf->user);
+	mf->user = nil;
+	mf->fid = 0;
+	mf->busy = 0;
+	sendmsg(job, nil);
+}
+
+void
+rremove(Job *job, Mfile *mf)
+{
+	USED(mf);
+	sendmsg(job, "remove permission denied");
+}
+
+void
+rstat(Job *job, Mfile *mf)
+{
+	Dir dir;
+	uchar buf[IOHDRSZ+Maxfdata];
+
+	memset(&dir, 0, sizeof dir);
+	if(mf->qid.type & QTDIR){
+		dir.name = ".";
+		dir.mode = DMDIR|0555;
+	} else {
+		dir.name = "cs";
+		dir.mode = 0666;
+	}
+	dir.qid = mf->qid;
+	dir.length = 0;
+	dir.uid = mf->user;
+	dir.gid = mf->user;
+	dir.muid = mf->user;
+	dir.atime = dir.mtime = time(0);
+	job->reply.nstat = convD2M(&dir, buf, sizeof buf);
+	job->reply.stat = buf;
+	sendmsg(job, nil);
+}
+
+void
+rwstat(Job *job, Mfile *mf)
+{
+	USED(mf);
+	sendmsg(job, "wstat permission denied");
+}
+
+void
+sendmsg(Job *job, char *err)
+{
+	int n;
+	uchar mdata[IOHDRSZ + Maxfdata];
+	char ename[ERRMAX];
+
+	if(err){
+		job->reply.type = Rerror;
+		snprint(ename, sizeof(ename), "cs: %s", err);
+		job->reply.ename = ename;
+	}else{
+		job->reply.type = job->request.type+1;
+	}
+	job->reply.tag = job->request.tag;
+	n = convS2M(&job->reply, mdata, sizeof mdata);
+	if(n == 0){
+		syslog(1, logfile, "sendmsg convS2M of %F returns 0", &job->reply);
+		abort();
+	}
+	qlock(&joblock);
+	if(job->flushed == 0)
+		if(write(mfd[1], mdata, n)!=n)
+			error("mount write");
+	qunlock(&joblock);
+	if(debug)
+		syslog(0, logfile, "%F %d", &job->reply, n);
+}
+
+void
+error(char *s)
+{
+	syslog(1, logfile, "%s: %r", s);
+	_exits(0);
+}
+
+static uchar loopbacknet[IPaddrlen] = {
+	0, 0, 0, 0,
+	0, 0, 0, 0,
+	0, 0, 0xff, 0xff,
+	127, 0, 0, 0
+};
+static uchar loopbackmask[IPaddrlen] = {
+	0xff, 0xff, 0xff, 0xff,
+	0xff, 0xff, 0xff, 0xff,
+	0xff, 0xff, 0xff, 0xff,
+	0xff, 0, 0, 0
+};
+static uchar loopback6[IPaddrlen] = {
+	0, 0, 0, 0,
+	0, 0, 0, 0,
+	0, 0, 0, 0,
+	0, 0, 0, 1
+};
+
+void
+readipinterfaces(void)
+{
+	uchar mynet[IPaddrlen];
+	Ipifc *ifc;
+	Iplifc *lifc;
+	int conf, dns;
+
+	conf = dns = 0;
+	qlock(&ipifclock);
+	ipifcs = readipifc(mntpt, ipifcs, -1);
+	for(ifc = ipifcs; ifc != nil; ifc = ifc->next){
+		for(lifc = ifc->lifc; lifc != nil; lifc = lifc->next){
+			if(ipcmp(lifc->ip, IPnoaddr) == 0)
+				continue;
+			if(isv4(lifc->ip)){
+				conf |= V4;
+				maskip(lifc->ip, loopbackmask, mynet);
+				if(ipcmp(mynet, loopbacknet) == 0)
+					continue;
+				dns |= V4;
+			} else {
+				conf |= V6;
+				if(ISIPV6LINKLOCAL(lifc->ip))
+					continue;
+				if(ipcmp(lifc->ip, loopback6) == 0)
+					continue;
+				dns |= V6;
+			}
+		}
+	}
+	qunlock(&ipifclock);
+	confipvers = conf;
+	dnsipvers = dns;
+}
+
+/*
+ *  get the system name
+ */
+void
+ipid(void)
+{
+	char eaddr[16], buf[Maxpath];
+	uchar addr[6];
+	Ndbtuple *t, *tt;
+	char *p, *attr;
+	Ndbs s;
+	int f, n;
+	Dir *d;
+
+	if(mysysname != nil)
+		return;
+
+	/*
+	 *  environment has priority.
+	 *
+	 *  on the sgi power the default system name
+	 *  is the ip address.  ignore that.
+	 *
+	 */
+	p = getenv("sysname");
+	if(p != nil && *p != 0){
+		attr = ipattr(p);
+		if(strcmp(attr, "ip") != 0) {
+			mysysname = p;
+			goto setsys;
+		}
+		free(p);
+	}
+
+	/* try configured interfaces */
+	attr = "sys";
+	t = s.t = myipinfo(db, &attr, 1);
+	if(t != nil)
+		goto found;
+
+	/* try ethernet interfaces */
+	n = 0;
+	d = nil;
+	f = open(mntpt, OREAD);
+	if(f >= 0){
+		n = dirreadall(f, &d);
+		close(f);
+	}
+	for(f = 0; f < n; f++){
+		if((d[f].mode & DMDIR) == 0 || strncmp(d[f].name, "ether", 5) != 0)
+			continue;
+		snprint(buf, sizeof buf, "%s/%s", mntpt, d[f].name);
+		if(myetheraddr(addr, buf) >= 0){
+			snprint(eaddr, sizeof(eaddr), "%E", addr);
+			free(ndbgetvalue(db, &s, "ether", eaddr, "sys", &t));
+			if(t != nil){
+				free(d);
+				goto found;
+			}
+		}
+	}
+	free(d);
+
+	/* nothing else worked, use ip address */
+	attr = "ip";
+	t = s.t = myipinfo(db, &attr, 1);
+	if(t == nil)
+		return;
+	
+found:
+	/* found in database */
+	if((tt = ndbfindattr(t, s.t, "sys")) != nil)
+		mysysname = estrdup(tt->val);
+	else if((tt = ndbfindattr(t, s.t, "ip")) != nil)
+		mysysname = estrdup(tt->val);
+	ndbfree(t);
+
+	if(mysysname == nil)
+		return;
+
+setsys:
+	/* set /dev/sysname if we now know it */
+	f = open("/dev/sysname", OWRITE);
+	if(f >= 0){
+		write(f, mysysname, strlen(mysysname));
+		close(f);
+	}
+}
+
+/*
+ *  Set up a list of default networks by looking for
+ *  /net/^*^/clone.
+ */
+void
+netinit(int background)
+{
+	char clone[Maxpath];
+	Network *np;
+
+	if(background){
+		if(rfork(RFPROC|RFNOTEG|RFMEM|RFNOWAIT) != 0)
+			return;
+		qlock(&netlock);
+	}
+
+	/* add the mounted networks to the default list */
+	for(np = network; np->net != nil; np++){
+		if(np->considered)
+			continue;
+		snprint(clone, sizeof(clone), "%s/%s/clone", mntpt, np->net);
+		if(access(clone, AEXIST) < 0)
+			continue;
+		if(netlist != nil)
+			last->next = np;
+		else
+			netlist = np;
+		last = np;
+		np->next = nil;
+		np->considered = 1;
+	}
+
+	/* find out what our ip addresses are */
+	readipinterfaces();
+
+	/* set the system name if we need to, these days ip is all we have */
+	ipid();
+
+	if(debug)
+		syslog(0, logfile, "mysysname %s", mysysname?mysysname:"???");
+
+	if(background){
+		qunlock(&netlock);
+		_exits(0);
+	}
+}
+
+/*
+ *  add networks to the standard list
+ */
+void
+netadd(char *p)
+{
+	Network *np;
+	char *field[12];
+	int i, n;
+
+	n = getfields(p, field, 12, 1, " ");
+	for(i = 0; i < n; i++){
+		for(np = network; np->net != nil; np++){
+			if(strcmp(field[i], np->net) != 0)
+				continue;
+			if(np->considered)
+				break;
+			if(netlist != nil)
+				last->next = np;
+			else
+				netlist = np;
+			last = np;
+			np->next = nil;
+			np->considered = 1;
+		}
+	}
+}
+
+int
+lookforproto(Ndbtuple *t, char *proto)
+{
+	for(; t != nil; t = t->entry)
+		if(strcmp(t->attr, "proto") == 0 && strcmp(t->val, proto) == 0)
+			return 1;
+	return 0;
+}
+
+/*
+ *  lookup a request.  the network "net" means we should pick the
+ *  best network to get there.
+ */
+int
+lookup(Mfile *mf)
+{
+	Network *np;
+	char *cp;
+	Ndbtuple *nt, *t;
+	char reply[Maxreply];
+	int i, rv, fasttimeout;
+
+	/* open up the standard db files */
+	if(db == nil)
+		ndbinit();
+	if(db == nil)
+		error("can't open mf->network database\n");
+
+	if(mf->net == nil)
+		return 0;	/* must have been a genquery */
+
+	rv = 0;
+	if(strcmp(mf->net, "net") == 0){
+		/*
+		 *  go through set of default nets
+		 */
+		for(np = mf->nextnet; np != nil && rv == 0; np = np->next){
+			nt = (*np->lookup)(np, mf->host, mf->serv);
+			if(nt == nil)
+				continue;
+			fasttimeout = np->fasttimeout && !lookforproto(nt, np->net);
+			for(t = nt; mf->nreply < Nreply && t != nil; t = t->entry){
+				cp = (*np->trans)(t, np, mf->serv, mf->rem, fasttimeout);
+				if(cp != nil){
+					/* avoid duplicates */
+					for(i = 0; i < mf->nreply; i++)
+						if(strcmp(mf->reply[i], cp) == 0)
+							break;
+					if(i == mf->nreply){
+						/* save the reply */
+						mf->replylen[mf->nreply] = strlen(cp);
+						mf->reply[mf->nreply++] = cp;
+						rv++;
+					} else
+						free(cp);
+				}
+			}
+			ndbfree(nt);
+		}
+		mf->nextnet = np;
+		return rv;
+	}
+
+	/*
+	 *  if not /net, we only get one lookup
+	 */
+	if(mf->nreply != 0)
+		return 0;
+
+	/*
+	 *  look for a specific network
+	 */
+	for(np = network; np->net != nil; np++){
+		if(np->fasttimeout)
+			continue;
+		if(strcmp(np->net, mf->net) == 0)
+			break;
+	}
+
+	if(np->net != nil){
+		/*
+		 *  known network
+		 */
+		nt = (*np->lookup)(np, mf->host, mf->serv);
+		for(t = nt; mf->nreply < Nreply && t != nil; t = t->entry){
+			cp = (*np->trans)(t, np, mf->serv, mf->rem, 0);
+			if(cp != nil){
+				mf->replylen[mf->nreply] = strlen(cp);
+				mf->reply[mf->nreply++] = cp;
+				rv++;
+			}
+		}
+		ndbfree(nt);
+		return rv;
+	} else {
+		/*
+		 *  not a known network, don't translate host or service
+		 */
+		if(mf->serv != nil)
+			snprint(reply, sizeof(reply), "%s/%s/clone %s!%s",
+				mntpt, mf->net, mf->host, mf->serv);
+		else
+			snprint(reply, sizeof(reply), "%s/%s/clone %s",
+				mntpt, mf->net, mf->host);
+		mf->reply[0] = estrdup(reply);
+		mf->replylen[0] = strlen(reply);
+		mf->nreply = 1;
+		return 1;
+	}
+}
+
+/*
+ *  translate an ip service name into a port number.  If it's a numeric port
+ *  number, look for restricted access.
+ *
+ *  the service '*' needs no translation.
+ */
+char*
+ipserv(Network *np, char *name, char *buf, int blen)
+{
+	char *p;
+	int alpha = 0;
+	int restr = 0;
+	Ndbtuple *t, *nt;
+	Ndbs s;
+
+	/* '*' means any service */
+	if(strcmp(name, "*") == 0){
+		nstrcpy(buf, name, blen);
+		return buf;
+	}
+
+	/*  see if it's numeric or symbolic */
+	for(p = name; *p; p++){
+		if(isdigit(*p))
+			{}
+		else if(isalpha(*p) || *p == '-' || *p == '$')
+			alpha = 1;
+		else
+			return nil;
+	}
+	t = nil;
+	p = nil;
+	if(alpha){
+		p = ndbgetvalue(db, &s, np->net, name, "port", &t);
+		if(p == nil)
+			return nil;
+	} else {
+		/* look up only for tcp ports < 1024 to get the restricted
+		 * attribute
+		 */
+		if(atoi(name) < 1024 && strcmp(np->net, "tcp") == 0)
+			p = ndbgetvalue(db, &s, "port", name, "port", &t);
+		if(p == nil)
+			p = estrdup(name);
+	}
+
+	if(t){
+		for(nt = t; nt != nil; nt = nt->entry)
+			if(strcmp(nt->attr, "restricted") == 0)
+				restr = 1;
+		ndbfree(t);
+	}
+	snprint(buf, blen, "%s%s", p, restr ? "!r" : "");
+	free(p);
+
+	return buf;
+}
+
+Ndbtuple*
+myipinfo(Ndb *db, char **list, int n)
+{
+	Ndbtuple *t, *nt;
+	char ip[64];
+	Ipifc *ifc;
+	Iplifc *lifc;
+
+	t = nil;
+	qlock(&ipifclock);
+	for(ifc = ipifcs; ifc != nil; ifc = ifc->next){
+		for(lifc = ifc->lifc; lifc != nil; lifc = lifc->next){
+			snprint(ip, sizeof(ip), "%I", lifc->ip);
+			nt = ndbipinfo(db, "ip", ip, list, n);
+			t = ndbconcatenate(t, nt);
+		}
+	}
+	qunlock(&ipifclock);
+
+	return ndbdedup(t);
+}
+
+/*
+ * reorder according to our interfaces
+ */
+static Ndbtuple*
+ipreorder(Ndbtuple *t)
+{
+	Ndbtuple *nt;
+	uchar ip[IPaddrlen];
+	uchar net[IPaddrlen];
+	uchar tnet[IPaddrlen];
+	Ipifc *ifc;
+	Iplifc *lifc;
+
+	if(t == nil)
+		return nil;
+
+	qlock(&ipifclock);
+	for(ifc = ipifcs; ifc != nil; ifc = ifc->next){
+		for(lifc = ifc->lifc; lifc != nil; lifc = lifc->next){
+			maskip(lifc->ip, lifc->mask, net);
+			for(nt = t; nt != nil; nt = nt->entry){
+				if(strcmp(nt->attr, "ip") != 0)
+					continue;
+				if(parseip(ip, nt->val) == -1)
+					continue;
+				maskip(ip, lifc->mask, tnet);
+				if(memcmp(net, tnet, IPaddrlen) == 0){
+					qunlock(&ipifclock);
+					return ndbreorder(t, nt);
+				}
+			}
+		}
+	}
+	qunlock(&ipifclock);
+
+	return t;
+}
+
+static Ndbtuple*
+ndbline(Ndbtuple *t)
+{
+	Ndbtuple *nt;
+
+	for(nt = t; nt != nil; nt = nt->entry){
+		if(nt->entry == nil)
+			nt->line = t;
+		else
+			nt->line = nt->entry;
+	}
+	return t;
+}
+
+/*
+ *  lookup an ip destination
+ */
+static Ndbtuple*
+iplookuphost(Network *np, char *host)
+{
+	char *attr, *dnsname;
+	Ndbtuple *t, *nt;
+	Ndbs s;
+
+	/*
+	 *  turn '[ip address]' into just 'ip address'
+	 */
+	if(*host == '['){
+		char tmp[Maxhost], *x;
+
+		nstrcpy(tmp, host, sizeof tmp);
+		host = tmp;
+		if((x = strchr(++host, ']')) != nil)
+			*x = 0;
+	}
+
+	/* for dial strings with no host */
+	if(strcmp(host, "*") == 0)
+		return ndbline(ndbnew("ip", "*"));
+
+	/*
+	 *  hack till we go v6 :: = 0.0.0.0
+	 */
+	if(strcmp("::", host) == 0)
+		return ndbline(ndbnew("ip", "*"));
+
+	/*
+	 *  just accept addresses
+	 */
+	attr = ipattr(host);
+	if(strcmp(attr, "ip") == 0)
+		return ndbline(ndbnew("ip", host));
+
+	/*
+	 *  give the domain name server the first opportunity to
+	 *  resolve domain names.  if that fails try the database.
+	 */
+	t = nil;
+	if(strcmp(attr, "dom") == 0)
+		t = dnsiplookup(host, &s, np->ipvers);
+	if(t == nil){
+		for(nt = ndbsearch(db, &s, attr, host); nt != nil; nt = ndbsnext(&s, attr, host)){
+			if(ndbfindattr(nt, s.t, "ip") == nil){
+				ndbfree(nt);
+				continue;
+			}
+			t = ndbconcatenate(t, ndbreorder(nt, s.t));
+		}
+		s.t = t;
+	}
+	if(t == nil){
+		if(strcmp(attr, "dom") != 0){
+			dnsname = ndbgetvalue(db, &s, attr, host, "dom", nil);
+			if(dnsname != nil){
+				t = dnsiplookup(dnsname, &s, np->ipvers);
+				free(dnsname);
+			}
+		}
+		if(t == nil)
+			t = dnsiplookup(host, &s, np->ipvers);
+	}
+	if(t == nil)
+		return nil;
+
+	/*
+	 *  reorder the tuple to have the matched line first and
+	 *  save that in the request structure.
+	 */
+	return ndbreorder(t, s.t);
+}
+
+
+Ndbtuple*
+iplookup(Network *np, char *host, char *serv)
+{
+	Ndbtuple *l, *t, *nt;
+	char ts[Maxservice], *attr;
+
+	/*
+	 *  start with the service since it's the most likely to fail
+	 *  and costs the least
+	 */
+	if(serv == nil || ipserv(np, serv, ts, sizeof ts) == nil){
+		werrstr("can't translate service");
+		return nil;
+	}
+
+	/*
+	 *  '$' means the rest of the name is an attribute that we
+	 *  need to search for
+	 */
+ 	werrstr("can't translate address");
+	if(*host == '$'){
+		t = nil;
+		attr = host+1;
+		l = myipinfo(db, &attr, 1);
+		for(nt = l; nt != nil; nt = nt->entry){
+			if(strcmp(nt->attr, attr) == 0)
+				t = ndbconcatenate(t, iplookuphost(np, nt->val));
+		}
+		ndbfree(l);
+	} else
+		t = iplookuphost(np, host);
+
+	return ipreorder(t);
+}
+
+
+/*
+ *  translate an ip address
+ */
+char*
+iptrans(Ndbtuple *t, Network *np, char *serv, char *rem, int fasttimeout)
+{
+	char ts[Maxservice];
+	char reply[Maxreply];
+	char x[Maxservice];
+	uchar ip[IPaddrlen];
+
+	if(strcmp(t->attr, "ip") != 0)
+		return nil;
+
+	if(serv == nil || ipserv(np, serv, ts, sizeof ts) == nil){
+		werrstr("can't translate service");
+		return nil;
+	}
+
+	if(rem != nil)
+		snprint(x, sizeof(x), "!%s", rem);
+	else
+		*x = 0;
+
+	if(*t->val == '*')
+		snprint(reply, sizeof(reply), "%s/%s/clone %s%s",
+			mntpt, np->net, ts, x);
+	else {
+		if(parseip(ip, t->val) == -1)
+			return nil;
+		if((np->ipvers & confipvers & (isv4(ip) ? V4 : V6)) == 0)
+			return nil;
+		snprint(reply, sizeof(reply), "%s/%s/clone %I!%s%s%s",
+			mntpt, np->net, ip, ts, x, fasttimeout? "!fasttimeout": "");
+	}
+
+	return estrdup(reply);
+}
+
+/* 
+ * lookup a registry entry
+ */
+Ndbtuple*
+svclookup(Network *np, char *host, char *serv)
+{	
+	USED(serv);
+
+	Ndbtuple *t, *st;
+
+	int fd, n;
+	char *args[3];
+	char line[Maxreply];
+	char buf[8192+1];
+
+	fd = open("/net/registry", ORDWR);
+	if(fd < 0){
+		werrstr("unable to open registry");
+		return nil;
+	}
+
+	seprint(line, line+Maxreply, "%s svc", host);
+
+	seek(fd, 0, 0);
+	write(fd, line, sizeof(line));
+
+	werrstr("can't translate address");
+	seek(fd, 0, 0);
+
+	t = st = nil;
+
+	/* We take the first match */
+	while((read(fd, buf, sizeof(buf)-1)) > 0){
+		n = getfields(buf, args, 3, 1, "!");
+		switch(n){
+		case 1:
+			np->net = estrdup("net");
+			t = ndbline(ndbnew("svc", args[1]));
+			break;
+		case 3:
+			st = ndbnew("serv", args[2]);
+			/* fall through */
+		case 2:
+			t = ndbline(ndbnew("svc", args[1]));
+			np->net = estrdup(args[0]);
+			break;
+		}
+		
+		t->line = st;
+		close(fd);
+		return t;
+	}
+	
+	close(fd);
+	return nil;
+}
+
+/*
+ *  translate a service listing
+ */
+char*
+svctrans(Ndbtuple *t, Network *np, char *serv, char *rem, int)
+{
+	USED(serv);
+
+	Ndbtuple *st;
+
+	char reply[Maxreply];
+	char x[Maxservice];
+
+	if(strcmp(t->attr, "svc") != 0)
+		return nil;
+
+	if(rem != nil)
+		snprint(x, sizeof(x), "!%s", rem);
+	else
+		*x = 0;
+
+	if(t->line != nil){
+		st = t->line;
+		snprint(reply, sizeof(reply), "%s/%s/clone %s!%s%s", mntpt, np->net,
+			t->val, st->val, x);
+	} else
+		snprint(reply, sizeof(reply), "%s/%s/clone %s%s", mntpt, np->net,
+			t->val, x);
+	return estrdup(reply);
+}
+
+/*
+ *  lookup a telephone number
+ */
+Ndbtuple*
+telcolookup(Network *np, char *host, char *serv)
+{
+	Ndbtuple *t;
+	Ndbs s;
+
+	USED(np, serv);
+
+	werrstr("can't translate address");
+	free(ndbgetvalue(db, &s, "sys", host, "telco", &t));
+	if(t == nil)
+		return ndbline(ndbnew("telco", host));
+
+	return ndbreorder(t, s.t);
+}
+
+/*
+ *  translate a telephone address
+ */
+char*
+telcotrans(Ndbtuple *t, Network *np, char *serv, char *rem, int)
+{
+	char reply[Maxreply];
+	char x[Maxservice];
+
+	if(strcmp(t->attr, "telco") != 0)
+		return nil;
+
+	if(rem != nil)
+		snprint(x, sizeof(x), "!%s", rem);
+	else
+		*x = 0;
+	if(serv != nil)
+		snprint(reply, sizeof(reply), "%s/%s/clone %s!%s%s", mntpt, np->net,
+			t->val, serv, x);
+	else
+		snprint(reply, sizeof(reply), "%s/%s/clone %s%s", mntpt, np->net,
+			t->val, x);
+	return estrdup(reply);
+}
+
+/*
+ *  create a slave process to handle a request to avoid one request blocking
+ *  another.  parent returns to job loop.
+ */
+void
+slave(char *host)
+{
+	if(*isslave)
+		return;		/* we're already a slave process */
+	if(ainc(&active) >= Maxactive){
+		adec(&active);
+		return;
+	}
+	switch(rfork(RFPROC|RFNOTEG|RFMEM|RFNOWAIT)){
+	case -1:
+		adec(&active);
+		break;
+	case 0:
+		*isslave = 1;
+		if(debug)
+			syslog(0, logfile, "slave %d", getpid());
+		procsetname("%s", host);
+		break;
+	default:
+		longjmp(masterjmp, 1);
+	}
+
+}
+
+static int
+mountdns(void)
+{
+	static QLock mountlock;
+	static int mounted;
+	char buf[128], *p;
+	int fd;
+
+	if(mounted)
+		return 0;
+
+	qlock(&mountlock);
+	snprint(buf, sizeof(buf), "%s/dns", mntpt);
+	if(access(buf, AEXIST) == 0)
+		goto done;
+	if(strcmp(mntpt, "/net") == 0)
+		snprint(buf, sizeof(buf), "/srv/dns");
+	else {
+		snprint(buf, sizeof(buf), "/srv/dns%s", mntpt);
+		while((p = strchr(buf+8, '/')) != nil)
+			*p = '_';
+	}
+	if((fd = open(buf, ORDWR)) < 0){
+err:
+		qunlock(&mountlock);
+		return -1;	
+	}
+	if(mount(fd, -1, mntpt, MAFTER, "") == -1){
+		close(fd);
+		goto err;
+	}
+done:
+	mounted = 1;
+	qunlock(&mountlock);
+	return 0;
+}
+
+static Ndbtuple*
+dnsip6lookup(char *mntpt, char *buf, Ndbtuple *t)
+{
+	Ndbtuple *t6, *tt;
+
+	t6 = dnsquery(mntpt, buf, "ipv6");	/* lookup AAAA dns RRs */
+	if (t6 == nil)
+		return t;
+
+	/* convert ipv6 attr to ip */
+	for (tt = t6; tt != nil; tt = tt->entry)
+		if (strcmp(tt->attr, "ipv6") == 0)
+			strcpy(tt->attr, "ip");
+
+	/* append t6 list to t list */
+	return ndbconcatenate(t, t6);
+}
+
+/*
+ *  call the dns process and have it try to translate a name
+ */
+Ndbtuple*
+dnsiplookup(char *host, Ndbs *s, int ipvers)
+{
+	char buf[Maxreply];
+	Ndbtuple *t;
+
+	ipvers &= dnsipvers & lookipvers;
+	if(ipvers == 0){
+		werrstr("no ip address");
+		return nil;
+	}
+	qunlock(&dblock);
+	slave(host);
+	if(*isslave == 0){
+		qlock(&dblock);
+		werrstr("too much activity");
+		return nil;
+	}
+
+	if(mountdns() < 0){
+		qlock(&dblock);
+		return nil;
+	}
+
+	if(strcmp(ipattr(host), "ip") == 0)
+		t = dnsquery(mntpt, host, "ptr");
+	else {
+		t = nil;
+		if(ipvers & V4)
+			t = dnsquery(mntpt, host, "ip");
+		if(ipvers & V6)
+			t = dnsip6lookup(mntpt, host, t);
+	}
+	s->t = t;
+
+	if(t == nil){
+		rerrstr(buf, sizeof buf);
+		if(strstr(buf, "exist") != nil)
+			werrstr("can't translate address: %s", buf);
+		else if(strstr(buf, "dns failure") != nil)
+			werrstr("temporary problem: %s", buf);
+	}
+
+	qlock(&dblock);
+	return t;
+}
+
+int
+qmatch(Ndbtuple *t, char **attr, char **val, int n)
+{
+	int i, found;
+	Ndbtuple *nt;
+
+	for(i = 1; i < n; i++){
+		found = 0;
+		for(nt = t; nt != nil; nt = nt->entry)
+			if(strcmp(attr[i], nt->attr) == 0)
+				if(strcmp(val[i], "*") == 0
+				|| strcmp(val[i], nt->val) == 0){
+					found = 1;
+					break;
+				}
+		if(found == 0)
+			break;
+	}
+	return i == n;
+}
+
+void
+qreply(Mfile *mf, Ndbtuple *t)
+{
+	Ndbtuple *nt;
+	String *s;
+
+	s = s_new();
+	for(nt = t; mf->nreply < Nreply && nt != nil; nt = nt->entry){
+		s_append(s, nt->attr);
+		s_append(s, "=");
+		s_append(s, nt->val);
+
+		if(nt->line != nt->entry){
+			mf->replylen[mf->nreply] = s_len(s);
+			mf->reply[mf->nreply++] = estrdup(s_to_c(s));
+			s_restart(s);
+		} else
+			s_append(s, " ");
+	}
+	s_free(s);
+}
+
+enum
+{
+	Maxattr=	32,
+};
+
+/*
+ *  generic query lookup.  The query is of one of the following
+ *  forms:
+ *
+ *  attr1=val1 attr2=val2 attr3=val3 ...
+ *
+ *  returns the matching tuple
+ *
+ *  ipinfo attr=val attr1 attr2 attr3 ...
+ *
+ *  is like ipinfo and returns the attr{1-n}
+ *  associated with the ip address.
+ */
+char*
+genquery(Mfile *mf, char *query)
+{
+	int i, n;
+	char *p;
+	char *attr[Maxattr];
+	char *val[Maxattr];
+	Ndbtuple *t;
+	Ndbs s;
+
+	n = getfields(query, attr, nelem(attr), 1, " ");
+	if(n == 0)
+		return "bad query";
+
+	if(strcmp(attr[0], "ipinfo") == 0)
+		return ipinfoquery(mf, attr, n);
+
+	/* parse pairs */
+	for(i = 0; i < n; i++){
+		p = strchr(attr[i], '=');
+		if(p == nil)
+			return "bad query";
+		*p++ = 0;
+		val[i] = p;
+	}
+
+	/* give dns a chance */
+	if((strcmp(attr[0], "dom") == 0 || strcmp(attr[0], "ip") == 0) && val[0]){
+		t = dnsiplookup(val[0], &s, lookipvers);
+		if(t != nil){
+			if(qmatch(t, attr, val, n)){
+				qreply(mf, t);
+				ndbfree(t);
+				return nil;
+			}
+			ndbfree(t);
+		}
+	}
+
+	/* first pair is always the key.  It can't be a '*' */
+	t = ndbsearch(db, &s, attr[0], val[0]);
+
+	/* search is the and of all the pairs */
+	while(t != nil){
+		if(qmatch(t, attr, val, n)){
+			qreply(mf, t);
+			ndbfree(t);
+			return nil;
+		}
+
+		ndbfree(t);
+		t = ndbsnext(&s, attr[0], val[0]);
+	}
+
+	return "no match";
+}
+
+/*
+ *  resolve an ip address
+ */
+static Ndbtuple*
+ipresolve(char *attr, char *host)
+{
+	Ndbtuple *t, *nt, **l;
+
+	t = iplookup(&network[Ntcp], host, "*");
+	for(l = &t; *l != nil; ){
+		nt = *l;
+		if(strcmp(nt->attr, "ip") != 0){
+			*l = nt->entry;
+			nt->entry = nil;
+			ndbfree(nt);
+			continue;
+		}
+		nstrcpy(nt->attr, attr, sizeof(nt->attr));
+		l = &nt->entry;
+	}
+	return t;
+}
+
+char*
+ipinfoquery(Mfile *mf, char **list, int n)
+{
+	int i, nresolve;
+	uchar resolve[Maxattr];
+	Ndbtuple *t, *nt, **l;
+	char *attr, *val;
+
+	/* skip 'ipinfo' */
+	list++; n--;
+
+	if(n < 1)
+		return "bad query";
+
+	/* get search attribute=value, or assume myip */
+	attr = *list;
+	if((val = strchr(attr, '=')) != nil){
+		*val++ = 0;
+		list++;
+		n--;
+	}else{
+		attr = nil;
+		val = nil;
+	}
+	if(n < 1)
+		return "bad query";
+
+
+	/*
+	 *  don't let ndbipinfo resolve the addresses, we're
+	 *  better at it.
+	 */
+	nresolve = 0;
+	for(i = 0; i < n; i++)
+		if(*list[i] == '@'){		/* @attr=val ? */
+			list[i]++;
+			resolve[i] = 1;		/* we'll resolve it */
+			nresolve++;
+		} else
+			resolve[i] = 0;
+
+	if(attr == nil)
+		t = myipinfo(db, list, n);
+	else
+		t = ndbipinfo(db, attr, val, list, n);
+
+	if(t == nil)
+		return "no match";
+
+	if(nresolve != 0){
+		for(l = &t; *l != nil;){
+			nt = *l;
+
+			/* already an address? */
+			if(strcmp(ipattr(nt->val), "ip") == 0){
+				l = &(*l)->entry;
+				continue;
+			}
+
+			/* user wants it resolved? */
+			for(i = 0; i < n; i++)
+				if(strcmp(list[i], nt->attr) == 0)
+					break;
+			if(i >= n || resolve[i] == 0){
+				l = &(*l)->entry;
+				continue;
+			}
+
+			/* resolve address and replace entry */
+			*l = ipresolve(nt->attr, nt->val);
+			while(*l != nil)
+				l = &(*l)->entry;
+			*l = nt->entry;
+
+			nt->entry = nil;
+			ndbfree(nt);
+		}
+
+		t = ndbdedup(t);
+	}
+
+	/* make it all one line */
+	t = ndbline(t);
+
+	qreply(mf, t);
+
+	return nil;
+}
+
+void*
+emalloc(int size)
+{
+	void *x;
+
+	x = malloc(size);
+	if(x == nil)
+		error("out of memory");
+	memset(x, 0, size);
+	return x;
+}
+
+char*
+estrdup(char *s)
+{
+	int size;
+	char *p;
+
+	size = strlen(s);
+	p = malloc(size+1);
+	if(p == nil)
+		error("out of memory");
+	memmove(p, s, size);
+	p[size] = 0;
+	return p;
+}
--- /dev/null
+++ b/csquery.c
@@ -1,0 +1,79 @@
+#include <u.h>
+#include <libc.h>
+#include <bio.h>
+
+char *server;
+char *status;
+int statusonly;
+
+void
+usage(void)
+{
+	fprint(2, "usage: ndb/csquery [/net/cs [addr...]]\n");
+	exits("usage");
+}
+
+void
+query(char *addr)
+{
+	char buf[8192+1];
+	int fd, n;
+
+	fd = open(server, ORDWR);
+	if(fd < 0)
+		sysfatal("cannot open %s: %r", server);
+	if(write(fd, addr, strlen(addr)) != strlen(addr)){
+		if(!statusonly)
+			fprint(2, "translating %s: %r\n", addr);
+		status = "errors";
+		close(fd);
+		return;
+	}
+	if(!statusonly){
+		seek(fd, 0, 0);
+		while((n = read(fd, buf, sizeof(buf)-1)) > 0){
+			buf[n++] = '\n';
+			write(1, buf, n);
+		}
+		write(1, "\n", 1);
+	}
+	close(fd);
+}
+
+void
+main(int argc, char **argv)
+{
+	char *p;
+	int i;
+	Biobuf in;
+
+	ARGBEGIN{
+	case 's':
+		statusonly = 1;
+		break;
+	default:
+		usage();
+	}ARGEND
+
+	if(argc > 0)
+		server = argv[0];
+	else
+		server = "/net/cs";
+
+	if(argc > 1){
+		for(i=1; i<argc; i++)
+			query(argv[i]);
+		exits(status);
+	}
+
+	Binit(&in, 0, OREAD);
+	for(;;){
+		fprint(2, "> ");
+		p = Brdline(&in, '\n');
+		if(p == 0)
+			break;
+		p[Blinelen(&in)-1] = 0;
+		query(p);
+	}
+	exits(nil);
+}
--- /dev/null
+++ b/dblookup.c
@@ -1,0 +1,1244 @@
+#include <u.h>
+#include <libc.h>
+#include <bio.h>
+#include <ndb.h>
+#include <ip.h>
+#include <ctype.h>
+#include "dns.h"
+
+enum {
+	Nibwidth = 4,
+	Nibmask = (1<<Nibwidth) - 1,
+	V6maxrevdomdepth = 128 / Nibwidth,	/* bits / bits-per-nibble */
+
+	/*
+	 * ttl for generated ptr records.  it was zero, which might seem
+	 * like a good idea, but some dns implementations seem to be
+	 * confused by a zero ttl, and instead of using the data and then
+	 * discarding the RR, they conclude that they don't have valid data.
+	 */
+	Ptrttl = 2*Min,
+};
+
+static Ndb	*db;
+static QLock	dblock;
+
+static Ipifc	*ipifcs;
+static QLock	ipifclock;
+
+static RR*	addrrr(Ndbtuple*, Ndbtuple*);
+static RR*	cnamerr(Ndbtuple*, Ndbtuple*);
+static void	createptrs(void);
+static RR*	dblookup1(char*, int, int, int);
+static RR*	doaxfr(Ndb*, char*);
+static Ndbtuple*look(Ndbtuple*, Ndbtuple*, char*);
+static RR*	mxrr(Ndbtuple*, Ndbtuple*);
+static RR*	nsrr(Ndbtuple*, Ndbtuple*);
+static RR*	nullrr(Ndbtuple*, Ndbtuple*);
+static RR*	ptrrr(Ndbtuple*, Ndbtuple*);
+static RR*	soarr(Ndbtuple*, Ndbtuple*);
+static RR*	srvrr(Ndbtuple*, Ndbtuple*);
+static RR*	txtrr(Ndbtuple*, Ndbtuple*);
+
+static int	implemented[Tall] =
+{
+	[Ta]		1,
+	[Taaaa]		1,
+	[Tcname]	1,
+	[Tmx]		1,
+	[Tns]		1,
+	[Tnull]		1,
+	[Tptr]		1,
+	[Tsoa]		1,
+	[Tsrv]		1,
+	[Ttxt]		1,
+};
+
+/* straddle server configuration */
+static Ndbtuple *indoms, *innmsrvs, *outnmsrvs;
+
+static void
+nstrcpy(char *to, char *from, int len)
+{
+	strncpy(to, from, len);
+	to[len-1] = 0;
+}
+
+int
+opendatabase(void)
+{
+	char netdbnm[256];
+	Ndb *xdb, *netdb;
+
+	if(db != nil)
+		return 0;
+
+	xdb = ndbopen(dbfile);		/* /lib/ndb */
+
+	snprint(netdbnm, sizeof netdbnm, "%s/ndb", mntpt);
+	for(netdb = xdb; netdb; netdb = netdb->next)
+		if(strcmp(netdb->file, netdbnm) == 0){
+			db = xdb;
+			return 0;
+		}
+
+	netdb = ndbopen(netdbnm);	/* /net/ndb */
+	if(netdb)
+		netdb->nohash = 1;
+
+	db = ndbcat(netdb, xdb);	/* both */
+	return db!=nil ? 0: -1;
+}
+
+/*
+ *  lookup an RR in the network database, look for matches
+ *  against both the domain name and the wildcarded domain name.
+ *
+ *  the lock makes sure only one process can be accessing the data
+ *  base at a time.  This is important since there's a lot of
+ *  shared state there.
+ *
+ *  e.g. for x.research.bell-labs.com, first look for a match against
+ *       the x.research.bell-labs.com.  If nothing matches,
+ *	 try *.research.bell-labs.com.
+ */
+RR*
+dblookup(char *name, int class, int type, int auth, int ttl)
+{
+	int err;
+	char buf[Domlen], *wild;
+	RR *rp, *tp;
+	DN *dp, *ndp;
+
+	/* so far only internet lookups are implemented */
+	if(class != Cin)
+		return 0;
+
+	err = Rname;
+	rp = nil;
+
+	if(type == Tall){
+		for (type = Ta; type < Tall; type++)
+			if(implemented[type])
+				rrcat(&rp, dblookup(name, class, type, auth, ttl));
+
+		return rp;
+	}
+
+	qlock(&dblock);
+	dp = idnlookup(name, class, 1);
+
+	if(opendatabase() < 0)
+		goto out;
+	if(dp->rr)
+		err = 0;
+
+	/* first try the given name */
+	if(cfg.cachedb)
+		rp = rrlookup(dp, type, NOneg);
+	else
+		rp = dblookup1(name, type, auth, ttl);
+	if(rp)
+		goto out;
+
+	/* walk the domain name trying the wildcard '*' at each position */
+	for(wild = strchr(name, '.'); wild; wild = strchr(wild+1, '.')){
+		snprint(buf, sizeof buf, "*%s", wild);
+		ndp = idnlookup(buf, class, 1);
+		if(ndp->rr)
+			err = 0;
+		if(cfg.cachedb)
+			rp = rrlookup(ndp, type, NOneg);
+		else
+			rp = dblookup1(buf, type, auth, ttl);
+		if(rp)
+			break;
+	}
+out:
+	/* add owner to uncached records */
+	if(rp)
+		for(tp = rp; tp; tp = tp->next)
+			tp->owner = dp;
+	else {
+		/*
+		 * don't call it non-existent if it's not ours
+		 * (unless we're a resolver).
+		 */
+		if(err == Rname && (!inmyarea(dp->name) || cfg.resolver))
+			err = Rserver;
+		dp->respcode = err;
+	}
+
+	qunlock(&dblock);
+	return rp;
+}
+
+static ulong
+intval(Ndbtuple *entry, Ndbtuple *pair, char *attr, ulong def)
+{
+	Ndbtuple *t = look(entry, pair, attr);
+
+	return (t? strtoul(t->val, 0, 10): def);
+}
+
+static void
+mklowcase(char *cp)
+{
+	Rune r;
+
+	while(*cp != 0){
+		chartorune(&r, cp);
+		r = tolowerrune(r);
+		cp += runetochar(cp, &r);
+	}
+}
+
+/*
+ *  lookup an RR in the network database
+ */
+static RR*
+dblookup1(char *name, int type, int auth, int ttl)
+{
+	Ndbtuple *t, *nt;
+	RR *rp, *list, **l;
+	Ndbs s;
+	char dname[Domlen];
+	char *attr;
+	DN *dp;
+	RR *(*f)(Ndbtuple*, Ndbtuple*);
+	int found, x;
+
+	dp = nil;
+	switch(type){
+	case Tptr:
+		attr = "ptr";
+		f = ptrrr;
+		break;
+	case Ta:
+		attr = "ip";
+		f = addrrr;
+		break;
+	case Taaaa:
+		attr = "ipv6";
+		f = addrrr;
+		break;
+	case Tnull:
+		attr = "nullrr";
+		f = nullrr;
+		break;
+	case Tns:
+		attr = "ns";
+		f = nsrr;
+		break;
+	case Tsoa:
+		attr = "soa";
+		f = soarr;
+		break;
+	case Tsrv:
+		attr = "srv";
+		f = srvrr;
+		break;
+	case Ttxt:
+		attr = "txt";
+		f = txtrr;
+		break;
+	case Tmx:
+		attr = "mx";
+		f = mxrr;
+		break;
+	case Tcname:
+		attr = "cname";
+		f = cnamerr;
+		break;
+	case Taxfr:
+	case Tixfr:
+		return doaxfr(db, name);
+	default:
+//		dnslog("dblookup1(%s) bad type", name);
+		return nil;
+	}
+
+	/*
+	 *  find a matching entry in the database
+	 */
+	t = nil;
+	nstrcpy(dname, name, sizeof dname);
+	for(x=0; x<4; x++){
+		switch(x){
+		case 1:	/* try unicode */
+			if(idn2utf(name, dname, sizeof dname) < 0){
+				nstrcpy(dname, name, sizeof dname);
+				continue;
+			}
+			if(strcmp(name, dname) == 0)
+				continue;
+			break;
+		case 3:	/* try ascii (lower case) */
+			if(utf2idn(name, dname, sizeof dname) < 0)
+				continue;
+		case 2:
+			mklowcase(dname);
+			if(strcmp(name, dname) == 0)
+				continue;
+			break;
+		}
+		for(nt = ndbsearch(db, &s, "dom", dname); nt != nil; nt = ndbsnext(&s, "dom", dname)) {
+			if(ndbfindattr(nt, s.t, attr) == nil) {
+				ndbfree(nt);
+				continue;
+			}
+			t = ndbconcatenate(t, ndbreorder(nt, s.t));
+		}
+		if(t == nil && strchr(dname, '.') == nil) {
+			for(nt = ndbsearch(db, &s, "sys", dname); nt != nil; nt = ndbsnext(&s, "sys", dname)) {
+				if(ndbfindattr(nt, s.t, attr) == nil) {
+					ndbfree(nt);
+					continue;
+				}
+				t = ndbconcatenate(t, ndbreorder(nt, s.t));
+			}
+		}
+		s.t = t;
+		if(t != nil)
+			break;
+	}
+
+	if(t == nil) {
+//		dnslog("dblookup1(%s) name not found", name);
+		return nil;
+	}
+
+
+	/* search whole entry for default domain name */
+	for(nt = t; nt; nt = nt->entry)
+		if(strcmp(nt->attr, "dom") == 0){
+			nstrcpy(dname, nt->val, sizeof dname);
+			break;
+		}
+
+	/* ttl is maximum of soa minttl and entry's ttl ala rfc883 */
+	x = intval(t, s.t, "ttl", 0);
+	if(x > ttl)
+		ttl = x;
+
+	/* default ttl is one day */
+	if(ttl < 0)
+		ttl = DEFTTL;
+
+	/*
+	 *  The database has 2 levels of precedence; line and entry.
+	 *  Pairs on the same line bind tighter than pairs in the
+	 *  same entry, so we search the line first.
+	 */
+	found = 0;
+	list = 0;
+	l = &list;
+	for(nt = s.t;; ){
+		if(found == 0 && strcmp(nt->attr, "dom") == 0){
+			nstrcpy(dname, nt->val, sizeof dname);
+			found = 1;
+		}
+		if(strcmp(attr, nt->attr) == 0 && (rp = (*f)(t, nt)) != nil){
+			rp->auth = auth;
+			rp->db = 1;
+			if(ttl)
+				rp->ttl = ttl;
+			if(dp == nil)
+				dp = idnlookup(dname, Cin, 1);
+			rp->owner = dp;
+			*l = rp;
+			l = &rp->next;
+			nt->ptr = 1;
+		}
+		nt = nt->line;
+		if(nt == s.t)
+			break;
+	}
+
+	/* search whole entry */
+	for(nt = t; nt; nt = nt->entry)
+		if(nt->ptr == 0 && strcmp(attr, nt->attr) == 0 && (rp = (*f)(t, nt)) != nil){
+			rp->auth = auth;
+			rp->db = 1;
+			if(ttl)
+				rp->ttl = ttl;
+			if(dp == nil)
+				dp = idnlookup(dname, Cin, 1);
+			rp->owner = dp;
+			*l = rp;
+			l = &rp->next;
+		}
+	ndbfree(t);
+
+	unique(list);
+
+//	dnslog("dblookup1(%s) -> %#p", name, list);
+	return list;
+}
+
+/*
+ *  make various types of resource records from a database entry
+ */
+static RR*
+addrrr(Ndbtuple*, Ndbtuple *pair)
+{
+	RR *rp;
+	uchar ip[IPaddrlen];
+
+	if(parseip(ip, pair->val) == -1)
+		return nil;
+	rp = rralloc(isv4(ip) ? Ta : Taaaa);
+	rp->ip = ipalookup(ip, Cin, 1);
+	return rp;
+}
+static RR*
+nullrr(Ndbtuple*, Ndbtuple *pair)
+{
+	RR *rp;
+
+	rp = rralloc(Tnull);
+	rp->null->data = (uchar*)estrdup(pair->val);
+	rp->null->dlen = strlen((char*)rp->null->data);
+	return rp;
+}
+/*
+ *  txt rr strings are at most 255 bytes long.  one
+ *  can represent longer strings by multiple concatenated
+ *  <= 255 byte ones.
+ */
+static RR*
+txtrr(Ndbtuple*, Ndbtuple *pair)
+{
+	RR *rp;
+	Txt *t, **l;
+	int i, len, sofar;
+
+	rp = rralloc(Ttxt);
+	l = &rp->txt;
+	rp->txt = nil;
+	len = strlen(pair->val);
+	sofar = 0;
+	while(len > sofar){
+		t = emalloc(sizeof(*t));
+		t->next = nil;
+
+		i = len-sofar;
+		if(i > 255)
+			i = 255;
+
+		t->p = emalloc(i+1);
+		memmove(t->p, pair->val+sofar, i);
+		t->p[i] = 0;
+		sofar += i;
+
+		*l = t;
+		l = &t->next;
+	}
+	return rp;
+}
+static RR*
+cnamerr(Ndbtuple*, Ndbtuple *pair)
+{
+	RR *rp;
+
+	rp = rralloc(Tcname);
+	rp->host = idnlookup(pair->val, Cin, 1);
+	return rp;
+}
+static RR*
+mxrr(Ndbtuple *entry, Ndbtuple *pair)
+{
+	RR *rp;
+
+	rp = rralloc(Tmx);
+	rp->host = idnlookup(pair->val, Cin, 1);
+	rp->pref = intval(entry, pair, "pref", 1);
+	return rp;
+}
+static RR*
+nsrr(Ndbtuple *entry, Ndbtuple *pair)
+{
+	RR *rp;
+	Ndbtuple *t;
+
+	rp = rralloc(Tns);
+	rp->host = idnlookup(pair->val, Cin, 1);
+	t = look(entry, pair, "soa");
+	if(t && t->val[0] == 0)
+		rp->local = 1;
+	return rp;
+}
+static RR*
+ptrrr(Ndbtuple*, Ndbtuple *pair)
+{
+	RR *rp;
+
+	rp = rralloc(Tns);
+	rp->ptr = dnlookup(pair->val, Cin, 1);
+	return rp;
+}
+static RR*
+soarr(Ndbtuple *entry, Ndbtuple *pair)
+{
+	RR *rp;
+	Ndbtuple *ns, *mb, *t;
+	char mailbox[Domlen];
+	Ndb *ndb;
+	char *p;
+
+	rp = rralloc(Tsoa);
+	rp->soa->serial = 1;
+	for(ndb = db; ndb; ndb = ndb->next)
+		if(ndb->mtime > rp->soa->serial)
+			rp->soa->serial = ndb->mtime;
+
+	rp->soa->retry  = intval(entry, pair, "retry", Hour);
+	rp->soa->expire = intval(entry, pair, "expire", Day);
+	rp->soa->minttl = intval(entry, pair, "ttl", Day);
+	rp->soa->refresh = intval(entry, pair, "refresh", Day);
+	rp->soa->serial = intval(entry, pair, "serial", rp->soa->serial);
+
+	ns = look(entry, pair, "ns");
+	if(ns == nil)
+		ns = look(entry, pair, "dom");
+	rp->host = idnlookup(ns->val, Cin, 1);
+
+	/* accept all of:
+	 *  mbox=person
+	 *  mbox=person@machine.dom
+	 *  mbox=person.machine.dom
+	 */
+	mb = look(entry, pair, "mbox");
+	if(mb == nil)
+		mb = look(entry, pair, "mb");
+	if(mb)
+		if(strchr(mb->val, '.')) {
+			p = strchr(mb->val, '@');
+			if(p != nil)
+				*p = '.';
+			rp->rmb = idnlookup(mb->val, Cin, 1);
+		} else {
+			snprint(mailbox, sizeof mailbox, "%s.%s",
+				mb->val, ns->val);
+			rp->rmb = idnlookup(mailbox, Cin, 1);
+		}
+	else {
+		snprint(mailbox, sizeof mailbox, "postmaster.%s", ns->val);
+		rp->rmb = idnlookup(mailbox, Cin, 1);
+	}
+
+	/*
+	 *  hang dns slaves off of the soa.  this is
+	 *  for managing the area.
+	 */
+	for(t = entry; t != nil; t = t->entry)
+		if(strcmp(t->attr, "dnsslave") == 0)
+			addserver(&rp->soa->slaves, t->val);
+
+	return rp;
+}
+
+static RR*
+srvrr(Ndbtuple *entry, Ndbtuple *pair)
+{
+	RR *rp;
+
+	rp = rralloc(Tsrv);
+	rp->host = idnlookup(pair->val, Cin, 1);
+	rp->srv->pri = intval(entry, pair, "pri", 0);
+	rp->srv->weight = intval(entry, pair, "weight", 0);
+	/* TODO: translate service name to port # */
+	rp->port = intval(entry, pair, "port", 0);
+	return rp;
+}
+
+/*
+ *  Look for a pair with the given attribute.  look first on the same line,
+ *  then in the whole entry.
+ */
+static Ndbtuple*
+look(Ndbtuple *entry, Ndbtuple *line, char *attr)
+{
+	Ndbtuple *nt;
+
+	/* first look on same line (closer binding) */
+	for(nt = line;;){
+		if(strcmp(attr, nt->attr) == 0)
+			return nt;
+		nt = nt->line;
+		if(nt == line)
+			break;
+	}
+	/* search whole tuple */
+	for(nt = entry; nt; nt = nt->entry)
+		if(strcmp(attr, nt->attr) == 0)
+			return nt;
+	return 0;
+}
+
+/* these are answered specially by the tcp version */
+static RR*
+doaxfr(Ndb *db, char *name)
+{
+	USED(db, name);
+	return nil;
+}
+
+/*
+ *  read the database into the cache
+ */
+static void
+dbpair2cache(DN *dp, Ndbtuple *entry, Ndbtuple *pair)
+{
+	RR *rp;
+	static ulong ord;
+
+	rp = 0;
+	if(strcmp(pair->attr, "ip") == 0 ||
+	   strcmp(pair->attr, "ipv6") == 0) {
+		dp->ordinal = ord++;
+		rp = addrrr(entry, pair);
+	}
+	else if(strcmp(pair->attr, "ns") == 0)
+		rp = nsrr(entry, pair);
+	else if(strcmp(pair->attr, "soa") == 0) {
+		rp = soarr(entry, pair);
+		addarea(dp, rp, pair);
+	}
+	else if(strcmp(pair->attr, "mx") == 0)
+		rp = mxrr(entry, pair);
+	else if(strcmp(pair->attr, "srv") == 0)
+		rp = srvrr(entry, pair);
+	else if(strcmp(pair->attr, "cname") == 0)
+		rp = cnamerr(entry, pair);
+	else if(strcmp(pair->attr, "nullrr") == 0)
+		rp = nullrr(entry, pair);
+	else if(strcmp(pair->attr, "txtrr") == 0)
+		rp = txtrr(entry, pair);
+	if(rp == nil)
+		return;
+
+	rp->owner = dp;
+	rp->db = 1;
+	rp->ttl = intval(entry, pair, "ttl", rp->ttl);
+	rrattach(rp, Notauthoritative);
+	dnagenever(dp);
+}
+static void
+dbtuple2cache(Ndbtuple *t)
+{
+	Ndbtuple *et, *nt;
+	DN *dp;
+
+	for(et = t; et; et = et->entry)
+		if(strcmp(et->attr, "dom") == 0){
+			dp = idnlookup(et->val, Cin, 1);
+
+			/* first same line */
+			for(nt = et->line; nt != et; nt = nt->line){
+				dbpair2cache(dp, t, nt);
+				nt->ptr = 1;
+			}
+
+			/* then rest of entry */
+			for(nt = t; nt; nt = nt->entry){
+				if(nt->ptr == 0)
+					dbpair2cache(dp, t, nt);
+				nt->ptr = 0;
+			}
+		}
+}
+static void
+dbfile2cache(Ndb *db)
+{
+	Ndbtuple *t;
+
+	if(debug)
+		dnslog("rereading %s", db->file);
+	Bseek(&db->b, 0, 0);
+	while(t = ndbparse(db)){
+		dbtuple2cache(t);
+		ndbfree(t);
+	}
+}
+
+/* called with dblock held */
+static void
+loaddomsrvs(void)
+{
+	Ndbs s;
+
+	if (!cfg.inside || !cfg.straddle || !cfg.serve)
+		return;
+	if (indoms) {
+		ndbfree(indoms);
+		ndbfree(innmsrvs);
+		ndbfree(outnmsrvs);
+		indoms = innmsrvs = outnmsrvs = nil;
+	}
+	if (db == nil)
+		opendatabase();
+	free(ndbgetvalue(db, &s, "sys", "inside-dom", "dom", &indoms));
+	free(ndbgetvalue(db, &s, "sys", "inside-ns",  "ip",  &innmsrvs));
+	free(ndbgetvalue(db, &s, "sys", "outside-ns", "ip",  &outnmsrvs));
+	dnslog("[%d] ndb changed: reloaded inside-dom, inside-ns, outside-ns",
+		getpid());
+}
+
+void
+db2cache(int doit)
+{
+	ulong youngest;
+	Ndb *ndb;
+	Dir *d;
+	static ulong lastcheck, lastyoungest;
+
+	/* no faster than once every 2 minutes */
+	if(now < lastcheck + 2*Min && !doit)
+		return;
+
+	refresh_areas(owned);
+
+	qlock(&dblock);
+	if(opendatabase() < 0){
+		qunlock(&dblock);
+		return;
+	}
+
+	qlock(&ipifclock);
+	ipifcs = readipifc(mntpt, ipifcs, -1);
+	qunlock(&ipifclock);
+
+	/*
+	 *  file may be changing as we are reading it, so loop till
+	 *  mod times are consistent.
+	 *
+	 *  we don't use the times in the ndb records because they may
+	 *  change outside of refreshing our cached knowledge.
+	 */
+	for(;;){
+		lastcheck = now;
+		youngest = 0;
+		for(ndb = db; ndb; ndb = ndb->next)
+			/* dirfstat avoids walking the mount table each time */
+			if((d = dirfstat(Bfildes(&ndb->b))) != nil ||
+			   (d = dirstat(ndb->file)) != nil){
+				if(d->mtime > youngest)
+					youngest = d->mtime;
+				free(d);
+			}
+		if(!doit && youngest == lastyoungest)
+			break;
+
+		/* forget our area definition */
+		freearea(&owned);
+		freearea(&delegated);
+
+		/* reopen all the files (to get oldest for time stamp) */
+		for(ndb = db; ndb; ndb = ndb->next)
+			ndbreopen(ndb);
+
+		/* reload straddle-server configuration */
+		loaddomsrvs();
+
+		/* mark all db records as timed out */
+		dnagedb();
+
+		if(cfg.cachedb){
+			/* read in new entries */
+			for(ndb = db; ndb; ndb = ndb->next)
+				dbfile2cache(ndb);
+		}
+
+		/*
+		 * mark as authoritative anything in our domain,
+		 * delete timed out db records
+		 */
+		dnauthdb();
+
+		/* remove old entries */
+		dnageall(1);
+
+		doit = 0;
+		lastyoungest = youngest;
+		createptrs();
+	}
+
+	qunlock(&dblock);
+}
+
+extern char	mntpt[Maxpath];		/* net mountpoint */
+
+/*
+ *  get all my xxx
+ *  caller ndbfrees the result
+ */
+Ndbtuple*
+lookupinfo(char *attr)
+{
+	Ndbtuple *t, *nt;
+	char ip[64];
+	Ipifc *ifc;
+	Iplifc *lifc;
+
+	t = nil;
+	qlock(&dblock);
+	if(opendatabase() < 0){
+		qunlock(&dblock);
+		return nil;
+	}
+	qlock(&ipifclock);
+	if(ipifcs == nil)
+		ipifcs = readipifc(mntpt, ipifcs, -1);
+	for(ifc = ipifcs; ifc != nil; ifc = ifc->next){
+		for(lifc = ifc->lifc; lifc != nil; lifc = lifc->next){
+			snprint(ip, sizeof(ip), "%I", lifc->ip);
+			nt = ndbipinfo(db, "ip", ip, &attr, 1);
+			t = ndbconcatenate(t, nt);
+		}
+	}
+	qunlock(&ipifclock);
+	qunlock(&dblock);
+
+	return ndbdedup(t);
+}
+
+/*
+ *  return non-zero if this is a bad delegation
+ */
+int
+baddelegation(RR *rp, RR *nsrp, uchar *addr)
+{
+	static int whined;
+	static Ndbtuple *t;
+	Ndbtuple *nt;
+
+	if(rp->type != Tns)
+		return 0;
+
+	if(t == nil)
+		t = lookupinfo("dom");
+	if(t != nil){
+		/* see if delegating to us what we don't own */
+		for(nt = t; nt != nil; nt = nt->entry)
+			if(rp->host && cistrcmp(rp->host->name, nt->val) == 0)
+				break;
+
+		if(nt != nil && !inmyarea(rp->owner->name)){
+			if (!whined) {
+				whined = 1;
+				dnslog("bad delegation %R from %I/%s; "
+					"no further logging of them",
+					rp, addr, nsrp->host->name);
+			}
+			return 1;
+		}
+	}
+	return 0;
+}
+
+int
+myip(uchar *ip)
+{
+	Ipifc *ifc;
+	Iplifc *lifc;
+
+	qlock(&ipifclock);
+	for(ifc = ipifcs; ifc != nil; ifc = ifc->next){
+		for(lifc = ifc->lifc; lifc != nil; lifc = lifc->next){
+			if(ipcmp(ip, lifc->ip) == 0){
+				qunlock(&ipifclock);
+				return 1;
+			}
+		}
+	}
+	qunlock(&ipifclock);
+
+	return 0;
+}
+
+static void
+addlocaldnsserver(DN *dp, int class, char *addr, int i)
+{
+	uchar ip[IPaddrlen];
+	DN *nsdp, *ipdp;
+	RR *rp, *tp;
+	int type, n;
+	char buf[32];
+
+	if(parseip(ip, addr) == -1 || ipcmp(ip, IPnoaddr) == 0){
+		dnslog("rejecting bad ip %s as local dns server", addr);
+		return;
+	}
+
+	/* reject our own ip addresses so we don't query ourselves via udp */
+	if(myip(ip)){
+		dnslog("rejecting my ip %I as local dns server", ip);
+		return;
+	}
+
+	/* A or AAAA record */
+	type = isv4(ip) ? Ta : Taaaa;
+	ipdp = ipalookup(ip, class, 1);
+
+	/* check duplicate ip */
+	for(n = 0; n < i; n++){
+		snprint(buf, sizeof buf, "local#dns#server%d", n);
+		nsdp = dnlookup(buf, class, 0);
+		if(nsdp == nil)
+			continue;
+		rp = rrlookup(nsdp, type, NOneg);
+		for(tp = rp; tp != nil; tp = tp->next){
+			if(tp->ip == ipdp){
+				dnslog("rejecting duplicate local dns server ip %I", ip);
+				rrfreelist(rp);
+				return;
+			}
+		}
+		rrfreelist(rp);
+	}
+
+	snprint(buf, sizeof buf, "local#dns#server%d", i);
+	nsdp = dnlookup(buf, class, 1);
+
+	/* ns record for name server, make up an impossible name */
+	rp = rralloc(Tns);
+	rp->host = nsdp;
+	rp->owner = dp;			/* e.g., local#dns#servers */
+	rp->local = 1;
+	rp->db = 1;
+	rp->ttl = 10*Min;
+	rrattach(rp, Authoritative);	/* will not attach rrs in my area */
+	dnagenever(dp);
+
+	rp = rralloc(type);
+	rp->ip = ipdp;
+	rp->owner = nsdp;
+	rp->local = 1;
+	rp->db = 1;
+	rp->ttl = 10*Min;
+	rrattach(rp, Authoritative);	/* will not attach rrs in my area */
+	dnagenever(nsdp);
+
+	dnslog("added local dns server %s at %I", buf, ip);
+}
+
+/*
+ *  return list of dns server addresses to use when
+ *  acting just as a resolver.
+ */
+RR*
+dnsservers(int class)
+{
+	int i, n;
+	char *p;
+	char *args[16];
+	Ndbtuple *t, *nt;
+	RR *nsrp;
+	DN *dp;
+
+	dp = dnlookup("local#dns#servers", class, 1);
+	nsrp = rrlookup(dp, Tns, NOneg);
+	if(nsrp != nil)
+		return nsrp;
+
+	p = getenv("DNSSERVER");		/* list of ip addresses */
+	if(p != nil && (n = tokenize(p, args, nelem(args))) > 0){
+		for(i = 0; i < n; i++)
+			addlocaldnsserver(dp, class, args[i], i);
+	} else {
+		t = lookupinfo("@dns");		/* @dns=ip1 @dns=ip2 ... */
+		if(t == nil)
+			return nil;
+		i = 0;
+		for(nt = t; nt != nil; nt = nt->entry){
+			addlocaldnsserver(dp, class, nt->val, i);
+			i++;
+		}
+		ndbfree(t);
+	}
+	free(p);
+
+	return rrlookup(dp, Tns, NOneg);
+}
+
+static void
+addlocaldnsdomain(DN *dp, int class, char *domain)
+{
+	RR *rp;
+
+	/* ptr record */
+	rp = rralloc(Tptr);
+	rp->ptr = dnlookup(domain, class, 1);
+	rp->owner = dp;
+	rp->db = 1;
+	rp->ttl = 10*Min;
+	rrattach(rp, Authoritative);
+	dnagenever(dp);
+}
+
+/*
+ *  return list of domains to use when resolving names without '.'s
+ */
+RR*
+domainlist(int class)
+{
+	Ndbtuple *t, *nt;
+	RR *rp;
+	DN *dp;
+
+	dp = dnlookup("local#dns#domains", class, 1);
+	rp = rrlookup(dp, Tptr, NOneg);
+	if(rp != nil)
+		return rp;
+
+	t = lookupinfo("dnsdomain");
+	if(t == nil)
+		return nil;
+	for(nt = t; nt != nil; nt = nt->entry)
+		addlocaldnsdomain(dp, class, nt->val);
+	ndbfree(t);
+
+	return rrlookup(dp, Tptr, NOneg);
+}
+
+char *v4ptrdom = ".in-addr.arpa";
+char *v6ptrdom = ".ip6.arpa";		/* ip6.int deprecated, rfc 3152 */
+
+char *attribs[] = {
+	"ipmask",
+	0
+};
+
+/*
+ *  create ptrs that are in our v4 areas
+ */
+static void
+createv4ptrs(void)
+{
+	int len, dlen, n;
+	char *dom;
+	char buf[Domlen], ipa[64];
+	char *f[40];
+	uchar net[IPaddrlen], mask[IPaddrlen];
+	Area *s;
+	Ndbtuple *t, *nt;
+
+	dlen = strlen(v4ptrdom);
+	for(s = owned; s; s = s->next){
+		dom = s->soarr->owner->name;
+		len = strlen(dom);
+		if((len <= dlen || cistrcmp(dom+len-dlen, v4ptrdom) != 0) &&
+		    cistrcmp(dom, v4ptrdom+1) != 0)
+			continue;
+
+		/* get mask and net value */
+		nstrcpy(buf, dom, sizeof buf);
+		/* buf contains something like 178.204.in-addr.arpa (n==4) */
+		n = getfields(buf, f, nelem(f), 0, ".");
+		memset(mask, 0xff, IPaddrlen);
+		ipmove(net, v4prefix);
+		switch(n){
+		case 3:			/* /8 */
+			net[IPv4off] = atoi(f[0]);
+			mask[IPv4off+1] = 0;
+			mask[IPv4off+2] = 0;
+			mask[IPv4off+3] = 0;
+			break;
+		case 4:			/* /16 */
+			net[IPv4off] = atoi(f[1]);
+			net[IPv4off+1] = atoi(f[0]);
+			mask[IPv4off+2] = 0;
+			mask[IPv4off+3] = 0;
+			break;
+		case 5:			/* /24 */
+			net[IPv4off] = atoi(f[2]);
+			net[IPv4off+1] = atoi(f[1]);
+			net[IPv4off+2] = atoi(f[0]);
+			mask[IPv4off+3] = 0;
+			break;
+		case 6:		/* rfc2317: classless in-addr.arpa delegation */
+			net[IPv4off] = atoi(f[3]);
+			net[IPv4off+1] = atoi(f[2]);
+			net[IPv4off+2] = atoi(f[1]);
+			net[IPv4off+3] = atoi(f[0]);
+			snprint(ipa, sizeof(ipa), "%I", net);
+			t = ndbipinfo(db, "ip", ipa, attribs, 1);
+			if(t == nil)	/* could be a reverse with no forward */
+				continue;
+			nt = look(t, t, "ipmask");
+			if(nt == nil || parseipmask(mask, nt->val, 1) == -1){
+				ndbfree(t);
+				continue;
+			}
+			ndbfree(t);
+			n = 5;
+			break;
+		default:
+			continue;
+		}
+
+		/*
+		 * go through all domain entries looking for RR's
+		 * in this network and create ptrs.
+		 * +2 for ".in-addr.arpa".
+		 */
+		dnptr(net, mask, dom, Ta, 4+2-n, Ptrttl);
+	}
+}
+
+/* convert bytes to nibbles, big-endian */
+void
+bytes2nibbles(uchar *nibbles, uchar *bytes, int nbytes)
+{
+	while (nbytes-- > 0) {
+		*nibbles++ = *bytes >> Nibwidth;
+		*nibbles++ = *bytes++ & Nibmask;
+	}
+}
+
+void
+nibbles2bytes(uchar *bytes, uchar *nibbles, int nnibs)
+{
+	for (; nnibs >= 2; nnibs -= 2) {
+		*bytes++ = nibbles[0] << Nibwidth | (nibbles[1]&Nibmask);
+		nibbles += 2;
+	}
+	if (nnibs > 0)
+		*bytes = nibbles[0] << Nibwidth;
+}
+
+/*
+ *  create ptrs that are in our v6 areas.  see rfc3596
+ */
+static void
+createv6ptrs(void)
+{
+	int len, dlen, i, n, pfxnibs;
+	char *dom;
+	char buf[Domlen];
+	char *f[40];
+	uchar net[IPaddrlen], mask[IPaddrlen];
+	uchar nibnet[IPaddrlen*2], nibmask[IPaddrlen*2];
+	Area *s;
+
+	dlen = strlen(v6ptrdom);
+	for(s = owned; s; s = s->next){
+		dom = s->soarr->owner->name;
+		len = strlen(dom);
+		if((len <= dlen || cistrcmp(dom+len-dlen, v6ptrdom) != 0) &&
+		    cistrcmp(dom, v6ptrdom+1) != 0)
+			continue;
+
+		/* get mask and net value */
+		nstrcpy(buf, dom, sizeof buf);
+		/* buf contains something like 2.0.0.2.ip6.arpa (n==6) */
+		n = getfields(buf, f, nelem(f), 0, ".");
+		pfxnibs = n - 2;		/* 2 for .ip6.arpa */
+		if (pfxnibs < 0 || pfxnibs > V6maxrevdomdepth)
+			continue;
+
+		memset(net, 0, IPaddrlen);
+		memset(mask, 0xff, IPaddrlen);
+		bytes2nibbles(nibnet, net, IPaddrlen);
+		bytes2nibbles(nibmask, mask, IPaddrlen);
+
+		/* copy prefix of f, in reverse order, to start of net. */
+		for (i = 0; i < pfxnibs; i++)
+			nibnet[i] = strtol(f[pfxnibs - 1 - i], nil, 16);
+		/* zero nibbles of mask after prefix in net */
+		memset(nibmask + pfxnibs, 0, V6maxrevdomdepth - pfxnibs);
+
+		nibbles2bytes(net, nibnet, 2*IPaddrlen);
+		nibbles2bytes(mask, nibmask, 2*IPaddrlen);
+
+		/*
+		 * go through all domain entries looking for RR's
+		 * in this network and create ptrs.
+		 */
+		dnptr(net, mask, dom, Taaaa, V6maxrevdomdepth - pfxnibs, Ptrttl);
+	}
+}
+
+/*
+ *  create ptrs that are in our areas
+ */
+static void
+createptrs(void)
+{
+	createv4ptrs();
+	createv6ptrs();
+}
+
+/*
+ * is this domain (or DOMAIN or Domain or dOMAIN)
+ * internal to our organisation (behind our firewall)?
+ * only inside straddling servers care, everybody else gets told `yes',
+ * so they'll use mntpt for their queries.
+ */
+int
+insideaddr(char *dom)
+{
+	int domlen, vallen, rv;
+	Ndbtuple *t;
+
+	if (!cfg.inside || !cfg.straddle || !cfg.serve)
+		return 1;
+	if (dom[0] == '\0' || strcmp(dom, ".") == 0)	/* dns root? */
+		return 1;			/* hack for initialisation */
+
+	qlock(&dblock);
+	if (indoms == nil)
+		loaddomsrvs();
+	if (indoms == nil) {
+		qunlock(&dblock);
+		return 1;  /* no "inside-dom" sys, try inside nameservers */
+	}
+
+	rv = 0;
+	domlen = strlen(dom);
+	for (t = indoms; t != nil; t = t->entry) {
+		if (strcmp(t->attr, "dom") != 0)
+			continue;
+		vallen = strlen(t->val);
+		if (cistrcmp(dom, t->val) == 0 ||
+		    domlen > vallen &&
+		     cistrcmp(dom + domlen - vallen, t->val) == 0 &&
+		     dom[domlen - vallen - 1] == '.') {
+			rv = 1;
+			break;
+		}
+	}
+	qunlock(&dblock);
+	return rv;
+}
+
+int
+insidens(uchar *ip)
+{
+	uchar ipa[IPaddrlen];
+	Ndbtuple *t;
+
+	for (t = innmsrvs; t != nil; t = t->entry)
+		if (strcmp(t->attr, "ip") == 0) {
+			if (parseip(ipa, t->val) != -1 && ipcmp(ipa, ip) == 0)
+				return 1;
+		}
+	return 0;
+}
+
+int
+outsidensip(int n, uchar *ip)
+{
+	int i;
+	Ndbtuple *t;
+
+	i = 0;
+	for (t = outnmsrvs; t != nil; t = t->entry)
+		if (strcmp(t->attr, "ip") == 0 && i++ == n) {
+			if (parseip(ip, t->val) == -1)
+				return -1;
+			return 0;
+		}
+	return -1;
+}
--- /dev/null
+++ b/dn.c
@@ -1,0 +1,2029 @@
+#include <u.h>
+#include <libc.h>
+#include <ip.h>
+#include <ctype.h>
+#include "dns.h"
+
+/*
+ *  this comment used to say `our target is 4000 names cached, this should
+ *  be larger on large servers'.  dns at Bell Labs starts off with
+ *  about 1780 names.
+ *
+ * aging seems to corrupt the cache, so raise the trigger from 4000 until we
+ * figure it out.
+ */
+enum {
+	/* these settings will trigger frequent aging */
+	Deftarget	= 4000,
+	Minage		=  5*Min,
+	Defagefreq	= 15*Min,	/* age names this often (seconds) */
+};
+
+/*
+ *  Hash table for domain names.  The hash is based only on the
+ *  first element of the domain name.
+ */
+DN *ht[HTLEN];
+
+static struct {
+	Lock;
+	ulong	names;		/* names allocated */
+	ulong	oldest;		/* longest we'll leave a name around */
+	int	active;
+	int	mutex;
+	ushort	id;		/* same size as in packet */
+} dnvars;
+
+/* names of RR types */
+char *rrtname[] =
+{
+[Ta]		"ip",
+[Tns]		"ns",
+[Tmd]		"md",
+[Tmf]		"mf",
+[Tcname]	"cname",
+[Tsoa]		"soa",
+[Tmb]		"mb",
+[Tmg]		"mg",
+[Tmr]		"mr",
+[Tnull]		"null",
+[Twks]		"wks",
+[Tptr]		"ptr",
+[Thinfo]	"hinfo",
+[Tminfo]	"minfo",
+[Tmx]		"mx",
+[Ttxt]		"txt",
+[Trp]		"rp",
+[Tafsdb]	"afsdb",
+[Tx25]		"x.25",
+[Tisdn]		"isdn",
+[Trt]		"rt",
+[Tnsap]		"nsap",
+[Tnsapptr]	"nsap-ptr",
+[Tsig]		"sig",
+[Tkey]		"key",
+[Tpx]		"px",
+[Tgpos]		"gpos",
+[Taaaa]		"ipv6",
+[Tloc]		"loc",
+[Tnxt]		"nxt",
+[Teid]		"eid",
+[Tnimloc]	"nimrod",
+[Tsrv]		"srv",
+[Tatma]		"atma",
+[Tnaptr]	"naptr",
+[Tkx]		"kx",
+[Tcert]		"cert",
+[Ta6]		"a6",
+[Tdname]	"dname",
+[Tsink]		"sink",
+[Topt]		"opt",
+[Tapl]		"apl",
+[Tds]		"ds",
+[Tsshfp]	"sshfp",
+[Tipseckey]	"ipseckey",
+[Trrsig]	"rrsig",
+[Tnsec]		"nsec",
+[Tdnskey]	"dnskey",
+[Tspf]		"spf",
+[Tuinfo]	"uinfo",
+[Tuid]		"uid",
+[Tgid]		"gid",
+[Tunspec]	"unspec",
+[Ttkey]		"tkey",
+[Ttsig]		"tsig",
+[Tixfr]		"ixfr",
+[Taxfr]		"axfr",
+[Tmailb]	"mailb",
+[Tmaila]	"maila",
+[Tall]		"all",
+		0,
+};
+
+/* names of response codes */
+char *rname[Rmask+1] =
+{
+[Rok]			"ok",
+[Rformat]		"format error",
+[Rserver]		"server failure",
+[Rname]			"bad name",
+[Runimplimented]	"unimplemented",
+[Rrefused]		"we don't like you",
+[Ryxdomain]		"name should not exist",
+[Ryxrrset]		"rr set should not exist",
+[Rnxrrset]		"rr set should exist",
+[Rnotauth]		"not authorative",
+[Rnotzone]		"not in zone",
+[Rbadvers]		"bad opt version",
+/* [Rbadsig]		"bad signature", */
+[Rbadkey]		"bad key",
+[Rbadtime]		"bad signature time",
+[Rbadmode]		"bad mode",
+[Rbadname]		"duplicate key name",
+[Rbadalg]		"bad algorithm",
+};
+unsigned nrname = nelem(rname);
+
+/* names of op codes */
+char *opname[] =
+{
+[Oquery]	"query",
+[Oinverse]	"inverse query (retired)",
+[Ostatus]	"status",
+[Oupdate]	"update",
+};
+
+ulong target = Deftarget;
+Lock	dnlock;
+
+static ulong agefreq = Defagefreq;
+
+static int rrequiv(RR *r1, RR *r2);
+static int sencodefmt(Fmt*);
+
+static void
+ding(void*, char *msg)
+{
+	if(strstr(msg, "alarm") != nil) {
+		stats.alarms++;
+		noted(NCONT);		/* resume with system call error */
+	} else
+		noted(NDFLT);		/* die */
+}
+
+void
+dninit(void)
+{
+	fmtinstall('E', eipfmt);
+	fmtinstall('I', eipfmt);
+	fmtinstall('V', eipfmt);
+	fmtinstall('R', rrfmt);
+	fmtinstall('Q', rravfmt);
+	fmtinstall('H', sencodefmt);
+
+	dnvars.oldest = maxage;
+	dnvars.names = 0;
+	dnvars.id = truerand();	/* don't start with same id every time */
+
+	notify(ding);
+}
+
+/*
+ *  hash for a domain name
+ */
+static ulong
+dnhash(char *name)
+{
+	ulong hash;
+	uchar *val = (uchar*)name;
+
+	for(hash = 0; *val; val++)
+		hash = hash*13 + tolower(*val)-'a';
+	return hash % HTLEN;
+}
+
+/*
+ *  lookup a symbol.  if enter is not zero and the name is
+ *  not found, create it.
+ */
+DN*
+dnlookup(char *name, int class, int enter)
+{
+	DN **l;
+	DN *dp;
+
+	l = &ht[dnhash(name)];
+	lock(&dnlock);
+	for(dp = *l; dp; dp = dp->next) {
+		assert(dp->magic == DNmagic);
+		if(dp->class == class && cistrcmp(dp->name, name) == 0){
+			dp->referenced = now;
+			unlock(&dnlock);
+			return dp;
+		}
+		l = &dp->next;
+	}
+
+	if(!enter){
+		unlock(&dnlock);
+		return 0;
+	}
+	dnvars.names++;
+	dp = emalloc(sizeof(*dp));
+	dp->magic = DNmagic;
+	dp->name = estrdup(name);
+	dp->class = class;
+	dp->rr = nil;
+	dp->referenced = now;
+	/* add new DN to tail of the hash list.  *l points to last next ptr. */
+	dp->next = nil;
+	*l = dp;
+	unlock(&dnlock);
+
+	return dp;
+}
+
+DN*
+idnlookup(char *name, int class, int enter)
+{
+	char dom[Domlen];
+
+	if(utf2idn(name, dom, sizeof dom) >= 0)
+		name = dom;
+	return dnlookup(name, class, enter);
+}
+
+DN*
+ipalookup(uchar *ip, int class, int enter)
+{
+	char addr[64];
+
+	snprint(addr, sizeof(addr), "%I", ip);
+	return dnlookup(addr, class, enter);
+}
+
+static int
+rrsame(RR *rr1, RR *rr2)
+{
+	return rr1 == rr2 ||
+		rr1 != nil && rr2 != nil &&
+		rr1->db == rr2->db &&
+		rr1->auth == rr2->auth &&
+		rrequiv(rr1, rr2);
+}
+
+static int
+rronlist(RR *rp, RR *lp)
+{
+	for(; lp; lp = lp->next)
+		if (rrsame(lp, rp))
+			return 1;
+	return 0;
+}
+
+/*
+ * dump the stats
+ */
+void
+dnstats(char *file)
+{
+	int i, fd;
+
+	fd = create(file, OWRITE, 0666);
+	if(fd < 0)
+		return;
+
+	qlock(&stats);
+	fprint(fd, "# system %s\n", sysname());
+	fprint(fd, "# slave procs high-water mark\t%lud\n", stats.slavehiwat);
+	fprint(fd, "# queries received by 9p\t%lud\n", stats.qrecvd9p);
+	fprint(fd, "# queries received by udp\t%lud\n", stats.qrecvdudp);
+	fprint(fd, "# queries answered from memory\t%lud\n", stats.answinmem);
+	fprint(fd, "# queries sent by udp\t%lud\n", stats.qsent);
+	for (i = 0; i < nelem(stats.under10ths); i++)
+		if (stats.under10ths[i] || i == nelem(stats.under10ths) - 1)
+			fprint(fd, "# responses arriving within %.1f s.\t%lud\n",
+				(double)(i+1)/10, stats.under10ths[i]);
+	fprint(fd, "\n# queries sent & timed-out\t%lud\n", stats.tmout);
+	fprint(fd, "# cname queries timed-out\t%lud\n", stats.tmoutcname);
+	fprint(fd, "# ipv6  queries timed-out\t%lud\n", stats.tmoutv6);
+	fprint(fd, "\n# negative answers received\t%lud\n", stats.negans);
+	fprint(fd, "# negative answers w Rserver set\t%lud\n", stats.negserver);
+	fprint(fd, "# negative answers w bad delegation\t%lud\n",
+		stats.negbaddeleg);
+	fprint(fd, "# negative answers w bad delegation & no answers\t%lud\n",
+		stats.negbdnoans);
+	fprint(fd, "# negative answers w no Rname set\t%lud\n", stats.negnorname);
+	fprint(fd, "# negative answers cached\t%lud\n", stats.negcached);
+	qunlock(&stats);
+
+	lock(&dnlock);
+	fprint(fd, "\n# domain names %lud target %lud\n", dnvars.names, target);
+	unlock(&dnlock);
+	close(fd);
+}
+
+/*
+ *  dump the cache
+ */
+void
+dndump(char *file)
+{
+	int i, fd;
+	DN *dp;
+	RR *rp;
+
+	fd = create(file, OWRITE, 0666);
+	if(fd < 0)
+		return;
+
+	lock(&dnlock);
+	for(i = 0; i < HTLEN; i++)
+		for(dp = ht[i]; dp; dp = dp->next){
+			fprint(fd, "%s\n", dp->name);
+			for(rp = dp->rr; rp; rp = rp->next) {
+				fprint(fd, "\t%R %c%c %ld/%lud\n",
+					rp, rp->auth? 'A': 'U',
+					rp->db? 'D': 'N', (long)(rp->expire - now), rp->ttl);
+				if (rronlist(rp, rp->next))
+					fprint(fd, "*** duplicate:\n");
+			}
+		}
+	unlock(&dnlock);
+	close(fd);
+}
+
+/*
+ *  purge all records
+ */
+void
+dnpurge(void)
+{
+	DN *dp;
+	RR *rp, *srp;
+	int i;
+
+	lock(&dnlock);
+
+	for(i = 0; i < HTLEN; i++)
+		for(dp = ht[i]; dp; dp = dp->next){
+			srp = rp = dp->rr;
+			dp->rr = nil;
+			for(; rp != nil; rp = rp->next)
+				rp->cached = 0;
+			rrfreelist(srp);
+		}
+
+	unlock(&dnlock);
+}
+
+/*
+ *  delete head of *l and free the old head.
+ *  call with dnlock held.
+ */
+static void
+rrdelhead(RR **l)
+{
+	RR *rp;
+
+	if (canlock(&dnlock))
+		abort();	/* rrdelhead called with dnlock not held */
+	rp = *l;
+	if(rp == nil)
+		return;
+	*l = rp->next;		/* unlink head */
+	rp->cached = 0;		/* avoid blowing an assertion in rrfree */
+	rrfree(rp);
+}
+
+/*
+ *  check the age of resource records, free any that have timed out.
+ *  call with dnlock held.
+ */
+void
+dnage(DN *dp)
+{
+	RR **l, *rp;
+	ulong diff;
+
+	if (canlock(&dnlock))
+		abort();	/* dnage called with dnlock not held */
+	diff = now - dp->referenced;
+	if(diff < Reserved || dp->mark != 0)
+		return;
+
+	l = &dp->rr;
+	while ((rp = *l) != nil){
+		assert(rp->magic == RRmagic && rp->cached);
+		if(!rp->db && ((long)(rp->expire - now) <= 0 || diff > dnvars.oldest))
+			rrdelhead(l); /* rp == *l before; *l == rp->next after */
+		else
+			l = &rp->next;
+	}
+}
+
+#define MARK(dp)	{ if (dp) (dp)->mark |= 2; }
+
+/* mark a domain name and those in its RRs as never to be aged */
+void
+dnagenever(DN *dp)
+{
+	RR *rp;
+
+	lock(&dnlock);
+
+	/* mark all referenced domain names */
+	MARK(dp);
+	for(rp = dp->rr; rp; rp = rp->next){
+		MARK(rp->owner);
+		if(rp->negative){
+			MARK(rp->negsoaowner);
+			continue;
+		}
+		switch(rp->type){
+		case Thinfo:
+			MARK(rp->cpu);
+			MARK(rp->os);
+			break;
+		case Ttxt:
+			break;
+		case Tcname:
+		case Tmb:
+		case Tmd:
+		case Tmf:
+		case Tns:
+		case Tmx:
+		case Tsrv:
+			MARK(rp->host);
+			break;
+		case Tmg:
+		case Tmr:
+			MARK(rp->mb);
+			break;
+		case Tminfo:
+			MARK(rp->rmb);
+			MARK(rp->mb);
+			break;
+		case Trp:
+			MARK(rp->rmb);
+			MARK(rp->rp);
+			break;
+		case Ta:
+		case Taaaa:
+			MARK(rp->ip);
+			break;
+		case Tptr:
+			MARK(rp->ptr);
+			break;
+		case Tsoa:
+			MARK(rp->host);
+			MARK(rp->rmb);
+			break;
+		case Tsig:
+			MARK(rp->sig->signer);
+			break;
+		}
+	}
+
+	unlock(&dnlock);
+}
+
+#define REF(dp)	{ if (dp) (dp)->mark |= 1; }
+
+/*
+ *  periodicly sweep for old records and remove unreferenced domain names
+ *
+ *  only called when all other threads are locked out
+ */
+void
+dnageall(int doit)
+{
+	DN *dp, **l;
+	int i;
+	RR *rp;
+	static ulong nextage;
+
+	if(dnvars.names < target || ((long)(nextage - now) > 0 && !doit)){
+		dnvars.oldest = maxage;
+		return;
+	}
+
+	if(dnvars.names >= target) {
+		dnslog("more names (%lud) than target (%lud)", dnvars.names,
+			target);
+		dnvars.oldest /= 2;
+		if (dnvars.oldest < Minage)
+			dnvars.oldest = Minage;		/* don't be silly */
+	}
+	if (agefreq > dnvars.oldest / 2)
+		nextage = now + dnvars.oldest / 2;
+	else
+		nextage = now + (ulong)agefreq;
+
+	lock(&dnlock);
+
+	/* time out all old entries (and set refs to 0) */
+	for(i = 0; i < HTLEN; i++)
+		for(dp = ht[i]; dp; dp = dp->next){
+			dp->mark &= ~1;
+			dnage(dp);
+		}
+
+	/* mark all referenced domain names */
+	for(i = 0; i < HTLEN; i++)
+		for(dp = ht[i]; dp; dp = dp->next)
+			for(rp = dp->rr; rp; rp = rp->next){
+				REF(rp->owner);
+				if(rp->negative){
+					REF(rp->negsoaowner);
+					continue;
+				}
+				switch(rp->type){
+				case Thinfo:
+					REF(rp->cpu);
+					REF(rp->os);
+					break;
+				case Ttxt:
+					break;
+				case Tcname:
+				case Tmb:
+				case Tmd:
+				case Tmf:
+				case Tns:
+				case Tmx:
+				case Tsrv:
+					REF(rp->host);
+					break;
+				case Tmg:
+				case Tmr:
+					REF(rp->mb);
+					break;
+				case Tminfo:
+					REF(rp->rmb);
+					REF(rp->mb);
+					break;
+				case Trp:
+					REF(rp->rmb);
+					REF(rp->rp);
+					break;
+				case Ta:
+				case Taaaa:
+					REF(rp->ip);
+					break;
+				case Tptr:
+					REF(rp->ptr);
+					break;
+				case Tsoa:
+					REF(rp->host);
+					REF(rp->rmb);
+					break;
+				case Tsig:
+					REF(rp->sig->signer);
+					break;
+				}
+			}
+
+	/* sweep and remove unreferenced domain names */
+	for(i = 0; i < HTLEN; i++){
+		l = &ht[i];
+		for(dp = *l; dp; dp = *l){
+			if(dp->rr == nil && dp->mark == 0){
+				assert(dp->magic == DNmagic);
+				*l = dp->next;
+
+				free(dp->name);
+				memset(dp, 0, sizeof *dp); /* cause trouble */
+				dp->magic = ~DNmagic;
+				free(dp);
+
+				dnvars.names--;
+				continue;
+			}
+			l = &dp->next;
+		}
+	}
+
+	unlock(&dnlock);
+}
+
+/*
+ *  timeout all database records (used when rereading db)
+ */
+void
+dnagedb(void)
+{
+	DN *dp;
+	int i;
+	RR *rp;
+
+	lock(&dnlock);
+
+	/* time out all database entries */
+	for(i = 0; i < HTLEN; i++)
+		for(dp = ht[i]; dp; dp = dp->next) {
+			dp->mark = 0;
+			for(rp = dp->rr; rp; rp = rp->next)
+				if(rp->db)
+					rp->expire = 0;
+		}
+
+	unlock(&dnlock);
+}
+
+/*
+ *  mark all local db records about my area as authoritative,
+ *  delete timed out ones
+ */
+void
+dnauthdb(void)
+{
+	int i;
+	ulong minttl;
+	Area *area;
+	DN *dp;
+	RR *rp, **l;
+
+	lock(&dnlock);
+
+	/* time out all database entries */
+	for(i = 0; i < HTLEN; i++)
+		for(dp = ht[i]; dp; dp = dp->next){
+			area = inmyarea(dp->name);
+			l = &dp->rr;
+			for(rp = *l; rp; rp = *l){
+				if(rp->db){
+					if(rp->expire == 0){
+						rrdelhead(l);
+						continue;
+					}
+					if(area){
+						minttl = area->soarr->soa->minttl;
+						if(rp->ttl < minttl)
+							rp->ttl = minttl;
+						rp->auth = 1;
+					}
+				}
+				l = &rp->next;
+			}
+		}
+
+	unlock(&dnlock);
+}
+
+/*
+ *  keep track of other processes to know if we can
+ *  garbage collect.  block while garbage collecting.
+ */
+int
+getactivity(Request *req, int recursive)
+{
+	int rv;
+
+	if(traceactivity)
+		dnslog("get: %d active by pid %d from %p",
+			dnvars.active, getpid(), getcallerpc(&req));
+	lock(&dnvars);
+	/*
+	 * can't block here if we're already holding one
+	 * of the dnvars.active (recursive).  will deadlock.
+	 */
+	while(!recursive && dnvars.mutex){
+		unlock(&dnvars);
+		sleep(100);			/* tune; was 200 */
+		lock(&dnvars);
+	}
+	rv = ++dnvars.active;
+	now = time(nil);
+	nowns = nsec();
+	req->id = ++dnvars.id;
+	req->aux = nil;
+	unlock(&dnvars);
+
+	return rv;
+}
+void
+putactivity(int recursive)
+{
+	if(traceactivity)
+		dnslog("put: %d active by pid %d",
+			dnvars.active, getpid());
+	lock(&dnvars);
+	dnvars.active--;
+	assert(dnvars.active >= 0); /* "dnvars.active %d", dnvars.active */
+
+	/*
+	 *  clean out old entries and check for new db periodicly
+	 *  can't block here if being called to let go a "recursive" lock
+	 *  or we'll deadlock waiting for ourselves to give up the dnvars.active.
+	 */
+	if (recursive || dnvars.mutex ||
+	    (needrefresh == 0 && dnvars.active > 0)){
+		unlock(&dnvars);
+		return;
+	}
+
+	/* wait till we're alone */
+	dnvars.mutex = 1;
+	while(dnvars.active > 0){
+		unlock(&dnvars);
+		sleep(100);		/* tune; was 100 */
+		lock(&dnvars);
+	}
+	unlock(&dnvars);
+
+	db2cache(needrefresh);
+
+	dnageall(0);
+
+	/* let others back in */
+	needrefresh = 0;
+	dnvars.mutex = 0;
+}
+
+int
+rrlistlen(RR *rp)
+{
+	int n;
+
+	n = 0;
+	for(; rp; rp = rp->next)
+		++n;
+	return n;
+}
+
+/*
+ *  Attach a single resource record to a domain name (new->owner).
+ *	- Avoid duplicates with already present RR's
+ *	- Chain all RR's of the same type adjacent to one another
+ *	- chain authoritative RR's ahead of non-authoritative ones
+ *	- remove any expired RR's
+ *  If new is a stale duplicate, rrfree it.
+ *  Must be called with dnlock held.
+ */
+static void
+rrattach1(RR *new, int auth)
+{
+	RR **l;
+	RR *rp;
+	DN *dp;
+	ulong ttl;
+
+	assert(new->magic == RRmagic && !new->cached);
+
+	dp = new->owner;
+	assert(dp != nil && dp->magic == DNmagic);
+	new->auth |= auth;
+	new->next = 0;
+
+	/*
+	 * try not to let responses expire before we
+	 * can use them to complete this query, by extending
+	 * past (or nearly past) expiration time.
+	 */
+	if(new->db)
+		ttl = Year;
+	else
+		ttl = new->ttl;
+	if(ttl <= Min)
+		ttl = 10*Min;
+	new->expire = now + ttl;
+
+	/*
+	 *  find first rr of the right type
+	 */
+	l = &dp->rr;
+	for(rp = *l; rp; rp = *l){
+		assert(rp->magic == RRmagic && rp->cached);
+		if(rp->type == new->type)
+			break;
+		l = &rp->next;
+	}
+
+	/*
+	 *  negative entries replace positive entries
+	 *  positive entries replace negative entries
+	 *  newer entries replace older entries with the same fields
+	 *
+	 *  look farther ahead than just the next entry when looking
+	 *  for duplicates; RRs of a given type can have different rdata
+	 *  fields (e.g. multiple NS servers).
+	 */
+	while ((rp = *l) != nil){
+		assert(rp->magic == RRmagic && rp->cached);
+		if(rp->type != new->type)
+			break;
+
+		if(rp->db == new->db && rp->auth == new->auth){
+			/* negative drives out positive and vice versa */
+			if(rp->negative != new->negative) {
+				/* rp == *l before; *l == rp->next after */
+				rrdelhead(l);
+				continue;	
+			}
+			/* all things equal, pick the newer one */
+			else if(rrequiv(rp, new)){
+				/* old drives out new */
+				if((long)(rp->expire - new->expire) > 0) {
+					rrfree(new);
+					return;
+				}
+				/* rp == *l before; *l == rp->next after */
+				rrdelhead(l);
+				continue;
+			}
+			/*
+			 *  Hack for pointer records.  This makes sure
+			 *  the ordering in the list reflects the ordering
+			 *  received or read from the database
+			 */
+			else if(rp->type == Tptr &&
+			    !rp->negative && !new->negative &&
+			    rp->ptr->ordinal > new->ptr->ordinal)
+				break;
+		}
+		l = &rp->next;
+	}
+
+	if (rronlist(new, rp)) {
+		/* should not happen; duplicates were processed above */
+		dnslog("adding duplicate %R to list of %R; aborting", new, rp);
+		abort();
+	}
+	/*
+	 *  add to chain
+	 */
+	new->cached = 1;
+	new->next = rp;
+	*l = new;
+}
+
+/*
+ *  Attach a list of resource records to a domain name.
+ *  May rrfree any stale duplicate RRs; dismembers the list.
+ *  Upon return, every RR in the list will have been rrfree-d
+ *  or attached to its domain name.
+ *  See rrattach1 for properties preserved.
+ */
+void
+rrattach(RR *rp, int auth)
+{
+	RR *next;
+	DN *dp;
+
+	lock(&dnlock);
+	for(; rp; rp = next){
+		next = rp->next;
+		rp->next = nil;
+		dp = rp->owner;
+		/* avoid any outside spoofing */
+		if(cfg.cachedb && !rp->db && inmyarea(dp->name))
+			rrfree(rp);
+		else
+			rrattach1(rp, auth);
+	}
+	unlock(&dnlock);
+}
+
+RR**
+rrcopy(RR *rp, RR **last)
+{
+	RR *nrp;
+	SOA *soa;
+	Srv *srv;
+	Key *key;
+	Cert *cert;
+	Sig *sig;
+	Null *null;
+	Txt *t, *nt, **l;
+
+	assert(rp->magic == RRmagic);
+	nrp = rralloc(rp->type);
+	switch(rp->type){
+	case Tsoa:
+		soa = nrp->soa;
+		*nrp = *rp;
+		nrp->soa = soa;
+		*soa = *rp->soa;
+		soa->slaves = copyserverlist(rp->soa->slaves);
+		break;
+	case Tsrv:
+		srv = nrp->srv;
+		*nrp = *rp;
+		nrp->srv = srv;
+		*srv = *rp->srv;
+		break;
+	case Tkey:
+		key = nrp->key;
+		*nrp = *rp;
+		nrp->key = key;
+		*key = *rp->key;
+		key->data = emalloc(key->dlen);
+		memmove(key->data, rp->key->data, rp->key->dlen);
+		break;
+	case Tcert:
+		cert = nrp->cert;
+		*nrp = *rp;
+		nrp->cert = cert;
+		*cert = *rp->cert;
+		cert->data = emalloc(cert->dlen);
+		memmove(cert->data, rp->cert->data, rp->cert->dlen);
+		break;
+	case Tsig:
+		sig = nrp->sig;
+		*nrp = *rp;
+		nrp->sig = sig;
+		*sig = *rp->sig;
+		sig->data = emalloc(sig->dlen);
+		memmove(sig->data, rp->sig->data, rp->sig->dlen);
+		break;
+	case Tnull:
+		null = nrp->null;
+		*nrp = *rp;
+		nrp->null = null;
+		*null = *rp->null;
+		null->data = emalloc(null->dlen);
+		memmove(null->data, rp->null->data, rp->null->dlen);
+		break;
+	case Ttxt:
+		*nrp = *rp;
+		l = &nrp->txt;
+		*l = nil;
+		for(t = rp->txt; t != nil; t = t->next){
+			nt = emalloc(sizeof(*nt));
+			nt->p = estrdup(t->p);
+			nt->next = nil;
+			*l = nt;
+			l = &nt->next;
+		}
+		break;
+	default:
+		*nrp = *rp;
+		break;
+	}
+	nrp->pc = getcallerpc(&rp);
+	setmalloctag(nrp, nrp->pc);
+	nrp->cached = 0;
+	nrp->next = nil;
+	*last = nrp;
+	return &nrp->next;
+}
+
+/*
+ *  lookup a resource record of a particular type and
+ *  class attached to a domain name.  Return copies.
+ *
+ *  Priority ordering is:
+ *	db authoritative
+ *	not timed out network authoritative
+ *	not timed out network unauthoritative
+ *	unauthoritative db
+ *
+ *  if flag NOneg is set, don't return negative cached entries.
+ *  return nothing instead.
+ */
+RR*
+rrlookup(DN *dp, int type, int flag)
+{
+	RR *rp, *first, **last;
+
+	assert(dp->magic == DNmagic);
+
+	first = nil;
+	last = &first;
+	lock(&dnlock);
+
+	/* try for an authoritative db entry */
+	for(rp = dp->rr; rp; rp = rp->next){
+		assert(rp->magic == RRmagic && rp->cached);
+		if(rp->db)
+		if(rp->auth)
+		if(tsame(type, rp->type))
+			last = rrcopy(rp, last);
+	}
+	if(first)
+		goto out;
+
+	/* try for a living authoritative network entry */
+	for(rp = dp->rr; rp; rp = rp->next){
+		if(!rp->db)
+		if(rp->auth)
+		if((long)(rp->expire - now) > 0)
+ 		if(tsame(type, rp->type)){
+			if(flag == NOneg && rp->negative)
+				goto out;
+			last = rrcopy(rp, last);
+		}
+	}
+	if(first)
+		goto out;
+
+	/* try for a living unauthoritative network entry */
+	for(rp = dp->rr; rp; rp = rp->next){
+		if(!rp->db)
+		if((long)(rp->expire - now) > 0)
+		if(tsame(type, rp->type)){
+			if(flag == NOneg && rp->negative)
+				goto out;
+			last = rrcopy(rp, last);
+		}
+	}
+	if(first)
+		goto out;
+
+	/* try for an unauthoritative db entry */
+	for(rp = dp->rr; rp; rp = rp->next){
+		if(rp->db)
+		if(tsame(type, rp->type))
+			last = rrcopy(rp, last);
+	}
+	if(first)
+		goto out;
+
+	/* otherwise, settle for anything we got (except for negative caches) */
+	for(rp = dp->rr; rp; rp = rp->next)
+		if(tsame(type, rp->type)){
+			if(rp->negative)
+				goto out;
+			last = rrcopy(rp, last);
+		}
+
+out:
+	unlock(&dnlock);
+	unique(first);
+	return first;
+}
+
+/*
+ *  convert an ascii RR type name to its integer representation
+ */
+int
+rrtype(char *atype)
+{
+	int i;
+
+	for(i = 0; i <= Tall; i++)
+		if(rrtname[i] && strcmp(rrtname[i], atype) == 0)
+			return i;
+
+	/* make any a synonym for all */
+	if(strcmp(atype, "any") == 0)
+		return Tall;
+	else if(isascii(atype[0]) && isdigit(atype[0]))
+		return atoi(atype);
+	else
+		return -1;
+}
+
+/*
+ *  return 0 if not a supported rr type
+ */
+int
+rrsupported(int type)
+{
+	if(type < 0 || type >Tall)
+		return 0;
+	return rrtname[type] != nil;
+}
+
+/*
+ *  compare 2 types
+ */
+int
+tsame(int t1, int t2)
+{
+	return t1 == t2 || t1 == Tall;
+}
+
+/*
+ *  Add resource records to a list.
+ */
+RR*
+rrcat(RR **start, RR *rp)
+{
+	RR *olp, *nlp;
+	RR **last;
+
+	/* check for duplicates */
+	for (olp = *start; 0 && olp; olp = olp->next)
+		for (nlp = rp; nlp; nlp = nlp->next)
+			if (rrsame(nlp, olp))
+				dnslog("rrcat: duplicate RR: %R", nlp);
+	USED(olp);
+
+	last = start;
+	while(*last != nil)
+		last = &(*last)->next;
+
+	*last = rp;
+	return *start;
+}
+
+RR*
+rrremfilter(RR **l, int (*filter)(RR*, void*), void *arg)
+{
+	RR *first, *rp;
+	RR **nl;
+
+	first = nil;
+	nl = &first;
+	while(*l != nil){
+		rp = *l;
+		if((*filter)(rp, arg)){
+			*l = rp->next;
+			*nl = rp;
+			nl = &rp->next;
+			*nl = nil;
+		} else
+			l = &(*l)->next;
+	}
+
+	return first;
+}
+
+static int
+filterneg(RR *rp, void*)
+{
+	return rp->negative;
+}
+static int
+filtertype(RR *rp, void *arg)
+{
+	return rp->type == *((int*)arg);
+}
+static int
+filterowner(RR *rp, void *arg)
+{
+	return rp->owner == (DN*)arg;
+}
+
+/*
+ *  remove negative cache rr's from an rr list
+ */
+RR*
+rrremneg(RR **l)
+{
+	return rrremfilter(l, filterneg, nil);
+}
+
+/*
+ *  remove rr's of a particular type from an rr list
+ */
+RR*
+rrremtype(RR **l, int type)
+{
+	return rrremfilter(l, filtertype, &type);
+}
+
+/*
+ *  remove rr's of a particular owner from an rr list
+ */
+RR*
+rrremowner(RR **l, DN *owner)
+{
+	return rrremfilter(l, filterowner, owner);
+}
+
+static char *
+dnname(DN *dn)
+{
+	return dn? dn->name: "<null>";
+}
+
+static char *
+idnname(DN *dn, char *buf, int nbuf)
+{
+	char *name;
+
+	name = dnname(dn);
+	if(idn2utf(name, buf, nbuf) >= 0)
+		return buf;
+	return name;
+}
+
+/*
+ *  print conversion for rr records
+ */
+int
+rrfmt(Fmt *f)
+{
+	int rv;
+	char *strp;
+	char buf[Domlen];
+	Fmt fstr;
+	RR *rp;
+	Server *s;
+	SOA *soa;
+	Srv *srv;
+	Txt *t;
+
+	fmtstrinit(&fstr);
+
+	rp = va_arg(f->args, RR*);
+	if(rp == nil){
+		fmtprint(&fstr, "<null>");
+		goto out;
+	}
+
+	fmtprint(&fstr, "%s %s", dnname(rp->owner),
+		rrname(rp->type, buf, sizeof buf));
+
+	if(rp->negative){
+		fmtprint(&fstr, "\tnegative - rcode %d", rp->negrcode);
+		goto out;
+	}
+
+	switch(rp->type){
+	case Thinfo:
+		fmtprint(&fstr, "\t%s %s", dnname(rp->cpu), dnname(rp->os));
+		break;
+	case Tcname:
+	case Tmb:
+	case Tmd:
+	case Tmf:
+	case Tns:
+		fmtprint(&fstr, "\t%s", dnname(rp->host));
+		break;
+	case Tmg:
+	case Tmr:
+		fmtprint(&fstr, "\t%s", dnname(rp->mb));
+		break;
+	case Tminfo:
+		fmtprint(&fstr, "\t%s %s", dnname(rp->mb), dnname(rp->rmb));
+		break;
+	case Tmx:
+		fmtprint(&fstr, "\t%lud %s", rp->pref, dnname(rp->host));
+		break;
+	case Ta:
+	case Taaaa:
+		fmtprint(&fstr, "\t%s", dnname(rp->ip));
+		break;
+	case Tptr:
+		fmtprint(&fstr, "\t%s", dnname(rp->ptr));
+		break;
+	case Tsoa:
+		soa = rp->soa;
+		fmtprint(&fstr, "\t%s %s %lud %lud %lud %lud %lud",
+			dnname(rp->host), dnname(rp->rmb),
+			(soa? soa->serial: 0),
+			(soa? soa->refresh: 0), (soa? soa->retry: 0),
+			(soa? soa->expire: 0), (soa? soa->minttl: 0));
+		if (soa)
+			for(s = soa->slaves; s != nil; s = s->next)
+				fmtprint(&fstr, " %s", s->name);
+		break;
+	case Tsrv:
+		srv = rp->srv;
+		fmtprint(&fstr, "\t%ud %ud %ud %s",
+			(srv? srv->pri: 0), (srv? srv->weight: 0),
+			rp->port, dnname(rp->host));
+		break;
+	case Tnull:
+		if (rp->null == nil)
+			fmtprint(&fstr, "\t<null>");
+		else
+			fmtprint(&fstr, "\t%.*H", rp->null->dlen,
+				rp->null->data);
+		break;
+	case Ttxt:
+		fmtprint(&fstr, "\t");
+		for(t = rp->txt; t != nil; t = t->next)
+			fmtprint(&fstr, "%s", t->p);
+		break;
+	case Trp:
+		fmtprint(&fstr, "\t%s %s", dnname(rp->rmb), dnname(rp->rp));
+		break;
+	case Tkey:
+		if (rp->key == nil)
+			fmtprint(&fstr, "\t<null> <null> <null>");
+		else
+			fmtprint(&fstr, "\t%d %d %d", rp->key->flags,
+				rp->key->proto, rp->key->alg);
+		break;
+	case Tsig:
+		if (rp->sig == nil)
+			fmtprint(&fstr,
+		   "\t<null> <null> <null> <null> <null> <null> <null> <null>");
+		else
+			fmtprint(&fstr, "\t%d %d %d %lud %lud %lud %d %s",
+				rp->sig->type, rp->sig->alg, rp->sig->labels,
+				rp->sig->ttl, rp->sig->exp, rp->sig->incep,
+				rp->sig->tag, dnname(rp->sig->signer));
+		break;
+	case Tcert:
+		if (rp->cert == nil)
+			fmtprint(&fstr, "\t<null> <null> <null>");
+		else
+			fmtprint(&fstr, "\t%d %d %d",
+				rp->cert->type, rp->cert->tag, rp->cert->alg);
+		break;
+	}
+out:
+	strp = fmtstrflush(&fstr);
+	rv = fmtstrcpy(f, strp);
+	free(strp);
+	return rv;
+}
+
+/*
+ *  print conversion for rr records in attribute value form
+ */
+int
+rravfmt(Fmt *f)
+{
+	int rv, quote;
+	char buf[Domlen], *strp;
+	Fmt fstr;
+	RR *rp;
+	Server *s;
+	SOA *soa;
+	Srv *srv;
+	Txt *t;
+
+	fmtstrinit(&fstr);
+
+	rp = va_arg(f->args, RR*);
+	if(rp == nil){
+		fmtprint(&fstr, "<null>");
+		goto out;
+	}
+
+	if(rp->type == Tptr)
+		fmtprint(&fstr, "ptr=%s", dnname(rp->owner));
+	else
+		fmtprint(&fstr, "dom=%s", idnname(rp->owner, buf, sizeof(buf)));
+
+	switch(rp->type){
+	case Thinfo:
+		fmtprint(&fstr, " cpu=%s os=%s",
+			idnname(rp->cpu, buf, sizeof(buf)),
+			idnname(rp->os, buf, sizeof(buf)));
+		break;
+	case Tcname:
+		fmtprint(&fstr, " cname=%s", idnname(rp->host, buf, sizeof(buf)));
+		break;
+	case Tmb:
+	case Tmd:
+	case Tmf:
+		fmtprint(&fstr, " mbox=%s", idnname(rp->host, buf, sizeof(buf)));
+		break;
+	case Tns:
+		fmtprint(&fstr,  " ns=%s", idnname(rp->host, buf, sizeof(buf)));
+		break;
+	case Tmg:
+	case Tmr:
+		fmtprint(&fstr, " mbox=%s", idnname(rp->mb, buf, sizeof(buf)));
+		break;
+	case Tminfo:
+		fmtprint(&fstr, " mbox=%s mbox=%s",
+			idnname(rp->mb, buf, sizeof(buf)),
+			idnname(rp->rmb, buf, sizeof(buf)));
+		break;
+	case Tmx:
+		fmtprint(&fstr, " pref=%lud mx=%s", rp->pref,
+			idnname(rp->host, buf, sizeof(buf)));
+		break;
+	case Ta:
+	case Taaaa:
+		fmtprint(&fstr, " ip=%s", dnname(rp->ip));
+		break;
+	case Tptr:
+		fmtprint(&fstr, " dom=%s", dnname(rp->ptr));
+		break;
+	case Tsoa:
+		soa = rp->soa;
+		fmtprint(&fstr,
+" ns=%s mbox=%s serial=%lud refresh=%lud retry=%lud expire=%lud ttl=%lud",
+			idnname(rp->host, buf, sizeof(buf)),
+			idnname(rp->rmb, buf, sizeof(buf)),
+			(soa? soa->serial: 0),
+			(soa? soa->refresh: 0), (soa? soa->retry: 0),
+			(soa? soa->expire: 0), (soa? soa->minttl: 0));
+		for(s = soa->slaves; s != nil; s = s->next)
+			fmtprint(&fstr, " dnsslave=%s", s->name);
+		break;
+	case Tsrv:
+		srv = rp->srv;
+		fmtprint(&fstr, " pri=%ud weight=%ud port=%ud target=%s",
+			(srv? srv->pri: 0), (srv? srv->weight: 0),
+			rp->port, idnname(rp->host, buf, sizeof(buf)));
+		break;
+	case Tnull:
+		if (rp->null == nil)
+			fmtprint(&fstr, " null=<null>");
+		else
+			fmtprint(&fstr, " null=%.*H", rp->null->dlen,
+				rp->null->data);
+		break;
+	case Ttxt:
+		fmtprint(&fstr, " txt=");
+		quote = 0;
+		for(t = rp->txt; t != nil; t = t->next)
+			if(strchr(t->p, ' '))
+				quote = 1;
+		if(quote)
+			fmtprint(&fstr, "\"");
+		for(t = rp->txt; t != nil; t = t->next)
+			fmtprint(&fstr, "%s", t->p);
+		if(quote)
+			fmtprint(&fstr, "\"");
+		break;
+	case Trp:
+		fmtprint(&fstr, " rp=%s txt=%s",
+			idnname(rp->rmb, buf, sizeof(buf)),
+			idnname(rp->rp, buf, sizeof(buf)));
+		break;
+	case Tkey:
+		if (rp->key == nil)
+			fmtprint(&fstr, " flags=<null> proto=<null> alg=<null>");
+		else
+			fmtprint(&fstr, " flags=%d proto=%d alg=%d",
+				rp->key->flags, rp->key->proto, rp->key->alg);
+		break;
+	case Tsig:
+		if (rp->sig == nil)
+			fmtprint(&fstr,
+" type=<null> alg=<null> labels=<null> ttl=<null> exp=<null> incep=<null> tag=<null> signer=<null>");
+		else
+			fmtprint(&fstr,
+" type=%d alg=%d labels=%d ttl=%lud exp=%lud incep=%lud tag=%d signer=%s",
+				rp->sig->type, rp->sig->alg, rp->sig->labels,
+				rp->sig->ttl, rp->sig->exp, rp->sig->incep,
+				rp->sig->tag, idnname(rp->sig->signer, buf, sizeof(buf)));
+		break;
+	case Tcert:
+		if (rp->cert == nil)
+			fmtprint(&fstr, " type=<null> tag=<null> alg=<null>");
+		else
+			fmtprint(&fstr, " type=%d tag=%d alg=%d",
+				rp->cert->type, rp->cert->tag, rp->cert->alg);
+		break;
+	}
+out:
+	strp = fmtstrflush(&fstr);
+	rv = fmtstrcpy(f, strp);
+	free(strp);
+	return rv;
+}
+
+void
+warning(char *fmt, ...)
+{
+	char dnserr[256];
+	va_list arg;
+
+	va_start(arg, fmt);
+	vseprint(dnserr, dnserr+sizeof(dnserr), fmt, arg);
+	va_end(arg);
+	syslog(1, logfile, dnserr);		/* on console too */
+}
+
+void
+dnslog(char *fmt, ...)
+{
+	char dnserr[256];
+	va_list arg;
+
+	va_start(arg, fmt);
+	vseprint(dnserr, dnserr+sizeof(dnserr), fmt, arg);
+	va_end(arg);
+	syslog(0, logfile, dnserr);
+}
+
+/*
+ *  create a slave process to handle a request to avoid one request blocking
+ *  another
+ */
+void
+slave(Request *req)
+{
+	int ppid, procs;
+
+	if(req->isslave)
+		return;		/* we're already a slave process */
+
+	/*
+	 * These calls to putactivity cannot block.
+	 * After getactivity(), the current process is counted
+	 * twice in dnvars.active (one will pass to the child).
+	 * If putactivity tries to wait for dnvars.active == 0,
+	 * it will never happen.
+	 */
+
+	/* limit parallelism */
+	procs = getactivity(req, 1);
+	if(procs > stats.slavehiwat)
+		stats.slavehiwat = procs;
+	if(procs > Maxactive){
+		if(traceactivity)
+			dnslog("[%d] too much activity", getpid());
+		putactivity(1);
+		return;
+	}
+
+	/*
+	 * parent returns to main loop, child does the work.
+	 * don't change note group.
+	 */
+	ppid = getpid();
+	switch(rfork(RFPROC|RFMEM|RFNOWAIT)){
+	case -1:
+		putactivity(1);
+		break;
+	case 0:
+		procsetname("request slave of pid %d", ppid);
+ 		if(traceactivity)
+			dnslog("[%d] take activity from %d", getpid(), ppid);
+		req->isslave = 1;	/* why not `= getpid()'? */
+		break;
+	default:
+		/*
+		 * this relies on rfork producing separate, initially-identical
+		 * stacks, thus giving us two copies of `req', one in each
+		 * process.
+		 */
+		alarm(0);
+		longjmp(req->mret, 1);
+	}
+}
+
+static int
+blockequiv(Block *a, Block *b)
+{
+	return	a->dlen == b->dlen &&
+		memcmp(a->data, b->data, a->dlen) == 0;
+}
+
+static int
+keyequiv(Key *a, Key *b)
+{
+	return	a->flags == b->flags &&
+		a->proto == b->proto &&
+		a->alg == b->alg &&
+		blockequiv(a, b);
+}
+
+static int
+certequiv(Cert *a, Cert *b)
+{
+	return	a->type == a->type &&
+		a->tag == a->tag &&
+		a->alg == a->alg &&
+		blockequiv(a, b);
+}
+
+static int
+txtequiv(Txt *a, Txt *b)
+{
+	char *ap, *ae, *bp, *be;
+	int n;
+
+	for(ap = ae = bp = be = nil;;ap += n, bp += n){
+		while(a != nil && (ap == nil || (ap == ae && (a = a->next) != nil)))
+			ap = a->p, ae = ap + strlen(ap);
+		while(b != nil && (bp == nil || (bp == be && (b = b->next) != nil)))
+			bp = b->p, be = bp + strlen(bp);
+		if(a == b || a == nil || b == nil)
+			break;
+		n = ae - ap;
+		if(be - bp < n)
+			n = be - bp;
+		if(memcmp(ap, bp, n) != 0)
+			return 0;
+	}
+	return a == b;
+}
+
+static int
+rrequiv(RR *r1, RR *r2)
+{
+	if(r1->owner != r2->owner
+	|| r1->type != r2->type
+	|| r1->arg0 != r2->arg0
+	|| r1->arg1 != r2->arg1)
+		return 0;
+	switch(r1->type){
+	case Tkey:
+		return keyequiv(r1->key, r2->key);
+	case Tcert:
+		return certequiv(r1->cert, r2->cert);
+	case Tsig:
+		return r1->sig->signer == r2->sig->signer && certequiv(r1->sig, r2->sig);
+	case Tnull:
+		return blockequiv(r1->null, r2->null);
+	case Ttxt:
+		return txtequiv(r1->txt, r2->txt);
+	}
+	return 1;
+}
+
+void
+unique(RR *rp)
+{
+	RR **l, *nrp;
+
+	for(; rp; rp = rp->next){
+		l = &rp->next;
+		for(nrp = *l; nrp; nrp = *l)
+			if(rrequiv(rp, nrp)){
+				*l = nrp->next;
+				rrfree(nrp);
+			} else
+				l = &nrp->next;
+	}
+}
+
+/*
+ *  true if second domain is subsumed by the first
+ */
+int
+subsume(char *higher, char *lower)
+{
+	int hn, ln;
+
+	ln = strlen(lower);
+	hn = strlen(higher);
+	if (ln < hn || cistrcmp(lower + ln - hn, higher) != 0 ||
+	    ln > hn && hn != 0 && lower[ln - hn - 1] != '.')
+		return 0;
+	return 1;
+}
+
+/*
+ *  randomize the order we return items to provide some
+ *  load balancing for servers.
+ *
+ *  only randomize the first class of entries
+ */
+RR*
+randomize(RR *rp)
+{
+	RR *first, *last, *x, *base;
+	ulong n;
+
+	if(rp == nil || rp->next == nil)
+		return rp;
+
+	/* just randomize addresses, mx's and ns's */
+	for(x = rp; x; x = x->next)
+		if(x->type != Ta && x->type != Taaaa &&
+		    x->type != Tmx && x->type != Tns)
+			return rp;
+
+	base = rp;
+
+	n = rand();
+	last = first = nil;
+	while(rp != nil){
+		/* stop randomizing if we've moved past our class */
+		if(base->auth != rp->auth || base->db != rp->db){
+			last->next = rp;
+			break;
+		}
+
+		/* unchain */
+		x = rp;
+		rp = x->next;
+		x->next = nil;
+
+		if(n&1){
+			/* add to tail */
+			if(last == nil)
+				first = x;
+			else
+				last->next = x;
+			last = x;
+		} else {
+			/* add to head */
+			if(last == nil)
+				last = x;
+			x->next = first;
+			first = x;
+		}
+
+		/* reroll the dice */
+		n >>= 1;
+	}
+
+	return first;
+}
+
+static int
+sencodefmt(Fmt *f)
+{
+	int i, len, ilen, rv;
+	char *out, *buf;
+	uchar *b;
+	char obuf[64];		/* rsc optimization */
+
+	if(!(f->flags&FmtPrec) || f->prec < 1)
+		goto error;
+
+	b = va_arg(f->args, uchar*);
+	if(b == nil)
+		goto error;
+
+	/* if it's a printable, go for it */
+	len = f->prec;
+	for(i = 0; i < len; i++)
+		if(!isprint(b[i]))
+			break;
+	if(i == len){
+		if(len >= sizeof obuf)
+			len = sizeof(obuf)-1;
+		memmove(obuf, b, len);
+		obuf[len] = 0;
+		fmtstrcpy(f, obuf);
+		return 0;
+	}
+
+	ilen = f->prec;
+	f->prec = 0;
+	f->flags &= ~FmtPrec;
+	len = 2*ilen + 1;
+	if(len > sizeof(obuf)){
+		buf = malloc(len);
+		if(buf == nil)
+			goto error;
+	} else
+		buf = obuf;
+
+	/* convert */
+	out = buf;
+	rv = enc16(out, len, b, ilen);
+	if(rv < 0)
+		goto error;
+
+	fmtstrcpy(f, buf);
+	if(buf != obuf)
+		free(buf);
+	return 0;
+
+error:
+	return fmtstrcpy(f, "<encodefmt>");
+}
+
+void*
+emalloc(int size)
+{
+	char *x;
+
+	x = mallocz(size, 1);
+	if(x == nil)
+		abort();
+	setmalloctag(x, getcallerpc(&size));
+	return x;
+}
+
+char*
+estrdup(char *s)
+{
+	int size;
+	char *p;
+
+	size = strlen(s);
+	p = mallocz(size+1, 0);
+	if(p == nil)
+		abort();
+	memmove(p, s, size);
+	p[size] = 0;
+	setmalloctag(p, getcallerpc(&s));
+	return p;
+}
+
+/*
+ *  create a pointer record
+ */
+static RR*
+mkptr(DN *dp, char *ptr, ulong ttl)
+{
+	DN *ipdp;
+	RR *rp;
+
+	ipdp = dnlookup(ptr, Cin, 1);
+
+	rp = rralloc(Tptr);
+	rp->ptr = dp;
+	rp->owner = ipdp;
+	rp->db = 1;
+	if(ttl)
+		rp->ttl = ttl;
+	return rp;
+}
+
+void	bytes2nibbles(uchar *nibbles, uchar *bytes, int nbytes);
+
+/*
+ *  look for all ip addresses in this network and make
+ *  pointer records for them.
+ */
+void
+dnptr(uchar *net, uchar *mask, char *dom, int forwtype, int subdoms, int ttl)
+{
+	int i, j, len;
+	char *p, *e;
+	char ptr[Domlen];
+	uchar *ipp;
+	uchar ip[IPaddrlen], nnet[IPaddrlen];
+	uchar nibip[IPaddrlen*2];
+	DN *dp;
+	RR *rp, *nrp, *first, **l;
+
+	l = &first;
+	first = nil;
+	for(i = 0; i < HTLEN; i++)
+		for(dp = ht[i]; dp; dp = dp->next)
+			for(rp = dp->rr; rp; rp = rp->next){
+				if(rp->type != forwtype || rp->negative)
+					continue;
+				parseip(ip, rp->ip->name);
+				maskip(ip, mask, nnet);
+				if(ipcmp(net, nnet) != 0)
+					continue;
+
+				ipp = ip;
+				len = IPaddrlen;
+				if (forwtype == Taaaa) {
+					bytes2nibbles(nibip, ip, IPaddrlen);
+					ipp = nibip;
+					len = 2*IPaddrlen;
+				}
+
+				p = ptr;
+				e = ptr+sizeof(ptr);
+				for(j = len - 1; j >= len - subdoms; j--)
+					p = seprint(p, e, (forwtype == Ta?
+						"%d.": "%x."), ipp[j]);
+				seprint(p, e, "%s", dom);
+
+				nrp = mkptr(dp, ptr, ttl);
+				*l = nrp;
+				l = &nrp->next;
+			}
+
+	for(rp = first; rp != nil; rp = nrp){
+		nrp = rp->next;
+		rp->next = nil;
+		dp = rp->owner;
+		rrattach(rp, Authoritative);
+		dnagenever(dp);
+	}
+}
+
+void
+addserver(Server **l, char *name)
+{
+	Server *s;
+	int n;
+
+	while(*l)
+		l = &(*l)->next;
+	n = strlen(name);
+	s = malloc(sizeof(Server)+n+1);
+	if(s == nil)
+		return;
+	s->name = (char*)(s+1);
+	memmove(s->name, name, n);
+	s->name[n] = 0;
+	s->next = nil;
+	*l = s;
+}
+
+Server*
+copyserverlist(Server *s)
+{
+	Server *ns;
+
+	for(ns = nil; s != nil; s = s->next)
+		addserver(&ns, s->name);
+	return ns;
+}
+
+
+/* from here down is copied to ip/snoopy/dns.c periodically to update it */
+
+/*
+ *  convert an integer RR type to it's ascii name
+ */
+char*
+rrname(int type, char *buf, int len)
+{
+	char *t;
+
+	t = nil;
+	if(type >= 0 && type <= Tall)
+		t = rrtname[type];
+	if(t==nil){
+		snprint(buf, len, "%d", type);
+		t = buf;
+	}
+	return t;
+}
+
+/*
+ *  free a list of resource records and any related structs
+ */
+void
+rrfreelist(RR *rp)
+{
+	RR *next;
+
+	for(; rp; rp = next){
+		next = rp->next;
+		rrfree(rp);
+	}
+}
+
+void
+freeserverlist(Server *s)
+{
+	Server *next;
+
+	for(; s != nil; s = next){
+		next = s->next;
+		memset(s, 0, sizeof *s);	/* cause trouble */
+		free(s);
+	}
+}
+
+/*
+ *  allocate a resource record of a given type
+ */
+RR*
+rralloc(int type)
+{
+	RR *rp;
+
+	rp = emalloc(sizeof(*rp));
+	rp->magic = RRmagic;
+	rp->pc = getcallerpc(&type);
+	rp->type = type;
+	if (rp->type != type)
+		dnslog("rralloc: bogus type %d", type);
+	setmalloctag(rp, rp->pc);
+	switch(type){
+	case Tsoa:
+		rp->soa = emalloc(sizeof(*rp->soa));
+		rp->soa->slaves = nil;
+		setmalloctag(rp->soa, rp->pc);
+		break;
+	case Tsrv:
+		rp->srv = emalloc(sizeof(*rp->srv));
+		setmalloctag(rp->srv, rp->pc);
+		break;
+	case Tkey:
+		rp->key = emalloc(sizeof(*rp->key));
+		setmalloctag(rp->key, rp->pc);
+		break;
+	case Tcert:
+		rp->cert = emalloc(sizeof(*rp->cert));
+		setmalloctag(rp->cert, rp->pc);
+		break;
+	case Tsig:
+		rp->sig = emalloc(sizeof(*rp->sig));
+		setmalloctag(rp->sig, rp->pc);
+		break;
+	case Tnull:
+		rp->null = emalloc(sizeof(*rp->null));
+		setmalloctag(rp->null, rp->pc);
+		break;
+	}
+	rp->ttl = 0;
+	rp->expire = 0;
+	rp->next = 0;
+	return rp;
+}
+
+/*
+ *  free a resource record and any related structs
+ */
+void
+rrfree(RR *rp)
+{
+	Txt *t;
+
+	assert(rp->magic == RRmagic && !rp->cached);
+
+	switch(rp->type){
+	case Tsoa:
+		freeserverlist(rp->soa->slaves);
+		memset(rp->soa, 0, sizeof *rp->soa);	/* cause trouble */
+		free(rp->soa);
+		break;
+	case Tsrv:
+		memset(rp->srv, 0, sizeof *rp->srv);	/* cause trouble */
+		free(rp->srv);
+		break;
+	case Tkey:
+		free(rp->key->data);
+		memset(rp->key, 0, sizeof *rp->key);	/* cause trouble */
+		free(rp->key);
+		break;
+	case Tcert:
+		free(rp->cert->data);
+		memset(rp->cert, 0, sizeof *rp->cert);	/* cause trouble */
+		free(rp->cert);
+		break;
+	case Tsig:
+		free(rp->sig->data);
+		memset(rp->sig, 0, sizeof *rp->sig);	/* cause trouble */
+		free(rp->sig);
+		break;
+	case Tnull:
+		free(rp->null->data);
+		memset(rp->null, 0, sizeof *rp->null);	/* cause trouble */
+		free(rp->null);
+		break;
+	case Ttxt:
+		while(t = rp->txt){
+			rp->txt = t->next;
+			free(t->p);
+			memset(t, 0, sizeof *t);	/* cause trouble */
+			free(t);
+		}
+		break;
+	}
+
+	memset(rp, 0, sizeof *rp);		/* cause trouble */
+	rp->magic = ~RRmagic;
+	free(rp);
+}
--- /dev/null
+++ b/dnarea.c
@@ -1,0 +1,139 @@
+#include <u.h>
+#include <libc.h>
+#include <bio.h>
+#include <ndb.h>
+#include <ip.h>
+#include "dns.h"
+
+Area *owned, *delegated;
+
+/*
+ *  true if a name is in our area
+ */
+Area*
+inmyarea(char *name)
+{
+	int len;
+	Area *s, *d;
+
+	len = strlen(name);
+	for(s = owned; s; s = s->next){
+		if(s->len > len)
+			continue;
+		if(cistrcmp(s->soarr->owner->name, name + len - s->len) == 0)
+			if(len == s->len || name[len - s->len - 1] == '.')
+				break;
+	}
+	if(s == nil)
+		return nil;
+
+	/* name is in area `s' */
+	for(d = delegated; d; d = d->next){
+		if(d->len > len)
+			continue;
+		if(cistrcmp(d->soarr->owner->name, name + len - d->len) == 0)
+			if(len == d->len || name[len - d->len - 1] == '.')
+				return nil; /* name is in a delegated subarea */
+	}
+
+	return s;	/* name is in area `s' and not in a delegated subarea */
+}
+
+/*
+ *  our area is the part of the domain tree that
+ *  we serve
+ */
+void
+addarea(DN *dp, RR *rp, Ndbtuple *t)
+{
+	Area *s;
+	Area **l;
+
+	lock(&dnlock);
+	if(t->val[0])
+		l = &delegated;
+	else
+		l = &owned;
+
+	for (s = *l; s != nil; s = s->next)
+		if(s->soarr->owner == dp) {
+			unlock(&dnlock);
+			return;		/* we've already got one */
+		}
+
+	/*
+	 *  The area contains a copy of the soa rr that created it.
+	 *  The owner of the the soa rr should stick around as long
+	 *  as the area does.
+	 */
+	s = emalloc(sizeof(*s));
+	s->len = strlen(dp->name);
+	rrcopy(rp, &s->soarr);
+	s->soarr->owner = dp;
+	s->soarr->db = 1;
+	s->soarr->ttl = Hour;
+	s->neednotify = 1;
+	s->needrefresh = 0;
+
+	if (debug)
+		dnslog("new area %s %s", dp->name,
+			l == &delegated? "delegated": "owned");
+
+	s->next = *l;
+	*l = s;
+	unlock(&dnlock);
+}
+
+void
+freearea(Area **l)
+{
+	Area *s;
+
+	lock(&dnlock);
+	while(s = *l){
+		*l = s->next;
+		rrfree(s->soarr);
+		memset(s, 0, sizeof *s);	/* cause trouble */
+		free(s);
+	}
+	unlock(&dnlock);
+}
+
+/*
+ * refresh all areas that need it
+ *  this entails running a command 'zonerefreshprogram'.  This could
+ *  copy over databases from elsewhere or just do a zone transfer.
+ */
+void
+refresh_areas(Area *s)
+{
+	int pid;
+	Waitmsg *w;
+
+	for(; s != nil; s = s->next){
+		if(!s->needrefresh)
+			continue;
+
+		if(zonerefreshprogram == nil){
+			s->needrefresh = 0;
+			continue;
+		}
+
+		pid = fork();
+		if (pid == -1) {
+			sleep(1000);	/* don't fork again immediately */
+			continue;
+		}
+		if (pid == 0){
+			execl(zonerefreshprogram, "zonerefresh",
+				s->soarr->owner->name, nil);
+			exits("exec zonerefresh failed");
+		}
+		while ((w = wait()) != nil && w->pid != pid)
+			free(w);
+		if (w && w->pid == pid)
+			if(w->msg == nil || *w->msg == '\0')
+				s->needrefresh = 0;
+		free(w);
+	}
+}
--- /dev/null
+++ b/dnnotify.c
@@ -1,0 +1,176 @@
+#include <u.h>
+#include <libc.h>
+#include <ip.h>
+#include <bio.h>
+#include <ndb.h>
+#include "dns.h"
+
+/* get a notification from another system of a changed zone */
+void
+dnnotify(DNSmsg *reqp, DNSmsg *repp, Request *)
+{
+	RR *tp;
+	Area *a;
+
+	/* move one question from reqp to repp */
+	memset(repp, 0, sizeof(*repp));
+	tp = reqp->qd;
+	reqp->qd = tp->next;
+	tp->next = 0;
+	repp->qd = tp;
+	repp->id = reqp->id;
+	repp->flags = Fresp  | Onotify | Fauth;
+
+	/* anything to do? */
+	if(zonerefreshprogram == nil)
+		return;
+
+	/* make sure its the right type */
+	if(repp->qd->type != Tsoa)
+		return;
+
+	dnslog("notification for %s", repp->qd->owner->name);
+
+	/* is it something we care about? */
+	a = inmyarea(repp->qd->owner->name);
+	if(a == nil)
+		return;
+
+	dnslog("serial old %lud new %lud", a->soarr->soa->serial,
+		repp->qd->soa->serial);
+
+	/* do nothing if it didn't change */
+	if(a->soarr->soa->serial != repp->qd->soa->serial)
+		a->needrefresh = 1;
+}
+
+static int
+getips(char *name, uchar *ips, int maxips, Request *req)
+{
+	RR *list, *rp;
+	int nips;
+
+	nips = 0;
+	if(nips <= maxips)
+		return nips;
+	if(strcmp(ipattr(name), "ip") == 0) {
+		if(parseip(ips, name) != -1 && !myip(ips))
+			nips++;
+		return nips;
+	}
+	list = dnresolve(name, Cin, Ta, req, nil, 0, Recurse, 0, nil);
+	rrcat(&list, dnresolve(name, Cin, Taaaa, req, nil, 0, Recurse, 0, nil));
+	rp = list = randomize(list);
+	while(rp != nil && nips < maxips){
+		uchar *ip = ips + nips*IPaddrlen;
+		if(parseip(ip, rp->ip->name) != -1 && !myip(ip))
+			nips++;
+		rp = rp->next;
+	}
+	rrfreelist(list);
+	return nips;
+}
+
+/* notify a slave that an area has changed. */
+static void
+send_notify(char *slave, RR *soa, Request *req)
+{
+	int i, j, len, n, reqno, fd, nips, send;
+	uchar ips[8*IPaddrlen], ibuf[Maxudp+Udphdrsize], obuf[Maxudp+Udphdrsize];
+	Udphdr *up = (Udphdr*)obuf;
+	DNSmsg repmsg;
+	char *err;
+
+	nips = getips(slave, ips, sizeof(ips)/IPaddrlen, req);
+	if(nips <= 0){
+		dnslog("no address %s to notify", slave);
+		return;
+	}
+
+	/* create the request */
+	reqno = rand();
+	n = mkreq(soa->owner, Cin, obuf, Fauth | Onotify, reqno);
+
+	fd = udpport(nil);
+	if(fd < 0)
+		return;
+
+	/* send 3 times or until we get anything back */
+	n += Udphdrsize;
+	for(i = 0; i < 3; i++, freeanswers(&repmsg)){
+		memset(&repmsg, 0, sizeof repmsg);
+		send = 0;
+		for(j = 0; j < nips; j++){
+			ipmove(up->raddr, ips + j*IPaddrlen);
+			if(write(fd, obuf, n) == n){
+				dnslog("send %d bytes notify to %s/%I.%d about %s", n, slave,
+					up->raddr, nhgets(up->rport), soa->owner->name);
+				send++;
+			}
+		}
+		if(send == 0)
+			break;
+		alarm(2*1000);
+		len = read(fd, ibuf, sizeof ibuf);
+		alarm(0);
+		if(len <= Udphdrsize)
+			continue;
+		err = convM2DNS(&ibuf[Udphdrsize], len, &repmsg, nil);
+		if(err != nil) {
+			free(err);
+			continue;
+		}
+		if(repmsg.id == reqno && (repmsg.flags & Omask) == Onotify){
+			freeanswers(&repmsg);
+			break;
+		}
+	}
+	close(fd);
+}
+
+/* send notifies for any updated areas */
+static void
+notify_areas(Area *a, Request *req)
+{
+	Server *s;
+
+	for(; a != nil; a = a->next){
+		if(!a->neednotify)
+			continue;
+
+		/* send notifies to all slaves */
+		for(s = a->soarr->soa->slaves; s != nil; s = s->next)
+			send_notify(s->name, a->soarr, req);
+		a->neednotify = 0;
+	}
+}
+
+/*
+ *  process to notify other servers of changes
+ *  (also reads in new databases)
+ */
+void
+notifyproc(void)
+{
+	Request req;
+
+	switch(rfork(RFPROC|RFNOTEG|RFMEM|RFNOWAIT)){
+	case -1:
+		return;
+	case 0:
+		break;
+	default:
+		return;
+	}
+
+	procsetname("notify slaves");
+	memset(&req, 0, sizeof req);
+	req.isslave = 1;	/* don't fork off subprocesses */
+
+	for(;;){
+		getactivity(&req, 0);
+		notify_areas(owned, &req);
+		putactivity(0);
+		sleep(60*1000);
+	}
+}
--- /dev/null
+++ b/dnresolve.c
@@ -1,0 +1,1521 @@
+/*
+ * domain name resolvers, see rfcs 1035 and 1123
+ */
+#include <u.h>
+#include <libc.h>
+#include <ip.h>
+#include <bio.h>
+#include <ndb.h>
+#include "dns.h"
+
+typedef struct Dest Dest;
+typedef struct Query Query;
+
+enum
+{
+	Udp, Tcp,
+
+	Answerr=	-1,
+	Answnone,
+
+	Maxdest=	32,	/* maximum destinations for a request message */
+	Maxoutstanding=	15,	/* max. outstanding queries per domain name */
+
+	/*
+	 * these are the old values; we're trying longer timeouts now
+	 * primarily for the benefit of remote nameservers querying us
+	 * during times of bad connectivity.
+	 */
+	Maxtrans=	5,	/* maximum transmissions to a server */
+	Maxretries=	10,	/* cname+actual resends: was 32; have pity on user */
+};
+enum { Hurry, Patient, };
+enum { Outns, Inns, };
+
+struct Dest
+{
+	uchar	a[IPaddrlen];	/* ip address */
+	DN	*s;		/* name server name */
+	RR	*n;		/* name server rr */
+	int	nx;		/* number of transmissions */
+	int	code;		/* response code; used to clear dp->respcode */
+};
+
+struct Query {
+	DN	*dp;		/* domain */
+	ushort	type;		/* and type to look up */
+	Request *req;
+	Query	*prev;		/* previous query */
+
+	RR	*nsrp;		/* name servers to consult */
+
+	Dest	*dest;		/* array of destinations */
+	Dest	*curdest;	/* pointer to next to fill */
+	int	ndest;		/* transmit to this many on this round */
+
+	int	udpfd;
+
+	int	tcpset;
+	int	tcpfd;		/* if Tcp, read replies from here */
+	int	tcpctlfd;
+	uchar	tcpip[IPaddrlen];
+};
+
+static RR*	dnresolve1(char*, int, int, Request*, int, int);
+static int	netquery(Query *, int);
+
+/*
+ * reading /proc/pid/args yields either "name args" or "name [display args]",
+ * so return only display args, if any.
+ */
+static char *
+procgetname(void)
+{
+	int fd, n;
+	char *lp, *rp;
+	char buf[256];
+
+	snprint(buf, sizeof buf, "#p/%d/args", getpid());
+	if((fd = open(buf, OREAD)) < 0)
+		return strdup("");
+	*buf = '\0';
+	n = read(fd, buf, sizeof buf-1);
+	close(fd);
+	if (n >= 0)
+		buf[n] = '\0';
+	if ((lp = strchr(buf, '[')) == nil ||
+	    (rp = strrchr(buf, ']')) == nil)
+		return strdup("");
+	*rp = '\0';
+	return strdup(lp+1);
+}
+
+void
+rrfreelistptr(RR **rpp)
+{
+	RR *rp;
+
+	if (rpp == nil || *rpp == nil)
+		return;
+	rp = *rpp;
+	*rpp = nil;	/* update pointer in memory before freeing list */
+	rrfreelist(rp);
+}
+
+/*
+ *  lookup 'type' info for domain name 'name'.  If it doesn't exist, try
+ *  looking it up as a canonical name.
+ *
+ *  this process can be quite slow if time-outs are set too high when querying
+ *  nameservers that just don't respond to certain query types.  in that case,
+ *  there will be multiple udp retries, multiple nameservers will be queried,
+ *  and this will be repeated for a cname query.  the whole thing will be
+ *  retried several times until we get an answer or a time-out.
+ */
+RR*
+dnresolve(char *name, int class, int type, Request *req, RR **cn, int depth,
+	int recurse, int rooted, int *status)
+{
+	RR *rp, *nrp, *drp;
+	DN *dp;
+	int loops;
+	char *procname;
+	char nname[Domlen];
+
+	if(status)
+		*status = 0;
+
+	if(depth > 12)			/* in a recursive loop? */
+		return nil;
+
+	procname = procgetname();
+	/*
+	 *  hack for systems that don't have resolve search
+	 *  lists.  Just look up the simple name in the database.
+	 */
+	if(!rooted && strchr(name, '.') == nil){
+		rp = nil;
+		drp = domainlist(class);
+		for(nrp = drp; rp == nil && nrp != nil; nrp = nrp->next){
+			snprint(nname, sizeof nname, "%s.%s", name,
+				nrp->ptr->name);
+			rp = dnresolve(nname, class, type, req, cn, depth+1,
+				recurse, rooted, status);
+			rrfreelist(rrremneg(&rp));
+		}
+		if(drp != nil)
+			rrfreelist(drp);
+		procsetname("%s", procname);
+		free(procname);
+		return rp;
+	}
+
+	/*
+	 *  try the name directly
+	 */
+	rp = dnresolve1(name, class, type, req, depth, recurse);
+	if(rp == nil && (dp = idnlookup(name, class, 0)) != nil) {
+		/*
+		 * try it as a canonical name if we weren't told
+		 * that the name didn't exist
+		 */
+		if(type != Tptr && dp->respcode != Rname)
+			for(loops = 0; rp == nil && loops < Maxretries; loops++){
+				/* retry cname, then the actual type */
+				rp = dnresolve1(name, class, Tcname, req,
+					depth, recurse);
+				if(rp == nil)
+					break;
+
+				/* rp->host == nil shouldn't happen, but does */
+				if(rp->negative || rp->host == nil){
+					rrfreelist(rp);
+					rp = nil;
+					break;
+				}
+
+				name = rp->host->name;
+				if(cn)
+					rrcat(cn, rp);
+				else
+					rrfreelist(rp);
+
+				rp = dnresolve1(name, class, type, req,
+					depth, recurse);
+			}
+
+		/* distinction between not found and not good */
+		if(rp == nil && status != nil && dp->respcode != Rok)
+			*status = dp->respcode;
+	}
+	procsetname("%s", procname);
+	free(procname);
+	return randomize(rp);
+}
+
+static void
+queryinit(Query *qp, DN *dp, int type, Request *req)
+{
+	assert(dp && dp->magic == DNmagic);
+
+	memset(qp, 0, sizeof *qp);
+	qp->udpfd = qp->tcpfd = qp->tcpctlfd = -1;
+	qp->dp = dp;
+	qp->type = type;
+	if (qp->type != type)
+		dnslog("queryinit: bogus type %d", type);
+	qp->nsrp = nil;
+	qp->dest = qp->curdest = nil;
+	qp->prev = req->aux;
+	qp->req = req;
+	req->aux = qp;
+}
+
+static void
+querydestroy(Query *qp)
+{
+	if(qp->req->aux == qp)
+		qp->req->aux = qp->prev;
+	/* leave udpfd open */
+	if (qp->tcpfd >= 0)
+		close(qp->tcpfd);
+	if (qp->tcpctlfd >= 0) {
+		hangup(qp->tcpctlfd);
+		close(qp->tcpctlfd);
+	}
+	memset(qp, 0, sizeof *qp);	/* prevent accidents */
+	qp->udpfd = qp->tcpfd = qp->tcpctlfd = -1;
+}
+
+/*
+ * if the response to a query hasn't arrived within 100 ms.,
+ * it's unlikely to arrive at all.  after 1 s., it's really unlikely.
+ * queries for missing RRs are likely to produce time-outs rather than
+ * negative responses, so cname and aaaa queries are likely to time out,
+ * thus we don't wait very long for them.
+ */
+static void
+notestats(vlong start, int tmout, int type)
+{
+	qlock(&stats);
+	if (tmout) {
+		stats.tmout++;
+		if (type == Taaaa)
+			stats.tmoutv6++;
+		else if (type == Tcname)
+			stats.tmoutcname++;
+	} else {
+		long wait10ths = NS2MS(nsec() - start) / 100;
+
+		if (wait10ths <= 0)
+			stats.under10ths[0]++;
+		else if (wait10ths >= nelem(stats.under10ths))
+			stats.under10ths[nelem(stats.under10ths) - 1]++;
+		else
+			stats.under10ths[wait10ths]++;
+	}
+	qunlock(&stats);
+}
+
+static void
+noteinmem(void)
+{
+	qlock(&stats);
+	stats.answinmem++;
+	qunlock(&stats);
+}
+
+/* netquery with given name servers, free ns rrs when done */
+static int
+netqueryns(Query *qp, int depth, RR *nsrp)
+{
+	int rv;
+
+	if(nsrp == nil)
+		return Answnone;
+	qp->nsrp = nsrp;
+	rv = netquery(qp, depth);
+	qp->nsrp = nil;		/* prevent accidents */
+	rrfreelist(nsrp);
+	return rv;
+}
+
+static RR*
+issuequery(Query *qp, char *name, int class, int depth, int recurse)
+{
+	char *cp;
+	DN *nsdp;
+	RR *rp, *nsrp, *dbnsrp;
+
+	/*
+	 *  if we're running as just a resolver, query our
+	 *  designated name servers
+	 */
+	if(cfg.resolver){
+		nsrp = randomize(getdnsservers(class));
+		if(nsrp != nil)
+			if(netqueryns(qp, depth+1, nsrp) > Answnone)
+				return rrlookup(qp->dp, qp->type, OKneg);
+	}
+
+	/*
+ 	 *  walk up the domain name looking for
+	 *  a name server for the domain.
+	 */
+	for(cp = name; cp; cp = walkup(cp)){
+		/*
+		 *  if this is a local (served by us) domain,
+		 *  return answer
+		 */
+		dbnsrp = randomize(dblookup(cp, class, Tns, 0, 0));
+		if(dbnsrp && dbnsrp->local){
+			rp = dblookup(name, class, qp->type, 1, dbnsrp->ttl);
+			rrfreelist(dbnsrp);
+			return rp;
+		}
+
+		/*
+		 *  if recursion isn't set, just accept local
+		 *  entries
+		 */
+		if(recurse == Dontrecurse){
+			if(dbnsrp)
+				rrfreelist(dbnsrp);
+			continue;
+		}
+
+		/* look for ns in cache */
+		nsdp = idnlookup(cp, class, 0);
+		nsrp = nil;
+		if(nsdp)
+			nsrp = randomize(rrlookup(nsdp, Tns, NOneg));
+
+		/* if the entry timed out, ignore it */
+		if(nsrp && !nsrp->db && (long)(nsrp->expire - now) <= 0)
+			rrfreelistptr(&nsrp);
+
+		if(nsrp){
+			rrfreelistptr(&dbnsrp);
+
+			/* query the name servers found in cache */
+			if(netqueryns(qp, depth+1, nsrp) > Answnone)
+				return rrlookup(qp->dp, qp->type, OKneg);
+		} else if(dbnsrp)
+			/* try the name servers found in db */
+			if(netqueryns(qp, depth+1, dbnsrp) > Answnone)
+				return rrlookup(qp->dp, qp->type, NOneg);
+	}
+	return nil;
+}
+
+static RR*
+dnresolve1(char *name, int class, int type, Request *req, int depth,
+	int recurse)
+{
+	Area *area;
+	DN *dp;
+	RR *rp;
+	Query q;
+
+	if(debug)
+		dnslog("[%d] dnresolve1 %s %d %d", getpid(), name, type, class);
+
+	/* only class Cin implemented so far */
+	if(class != Cin)
+		return nil;
+
+	dp = idnlookup(name, class, 1);
+
+	/*
+	 *  Try the cache first
+	 */
+	rp = rrlookup(dp, type, OKneg);
+	if(rp)
+		if(rp->db){
+			/* unauthoritative db entries are hints */
+			if(rp->auth) {
+				noteinmem();
+				if(debug)
+					dnslog("[%d] dnresolve1 %s %d %d: auth rr in db",
+						getpid(), name, type, class);
+				return rp;
+			}
+		} else
+			/* cached entry must still be valid */
+			if((long)(rp->expire - now) > 0)
+				/* but Tall entries are special */
+				if(type != Tall || rp->query == Tall) {
+					noteinmem();
+					if(debug)
+						dnslog("[%d] dnresolve1 %s %d %d: rr not in db",
+							getpid(), name, type, class);
+					return rp;
+				}
+	rrfreelist(rp);
+	rp = nil;		/* accident prevention */
+	USED(rp);
+
+	/*
+	 * try the cache for a canonical name. if found punt
+	 * since we'll find it during the canonical name search
+	 * in dnresolve().
+	 */
+	if(type != Tcname){
+		rp = rrlookup(dp, Tcname, NOneg);
+		rrfreelist(rp);
+		if(rp){
+			if(debug)
+				dnslog("[%d] dnresolve1 %s %d %d: rr from rrlookup for non-cname",
+					getpid(), name, type, class);
+			return nil;
+		}
+	}
+
+	/*
+	 * if the domain name is within an area of ours,
+	 * we should have found its data in memory by now.
+	 */
+	area = inmyarea(dp->name);
+	if (area || strncmp(dp->name, "local#", 6) == 0)
+		return nil;
+
+	queryinit(&q, dp, type, req);
+	rp = issuequery(&q, name, class, depth, recurse);
+	querydestroy(&q);
+
+	if(rp){
+		if(debug)
+			dnslog("[%d] dnresolve1 %s %d %d: rr from query",
+				getpid(), name, type, class);
+		return rp;
+	}
+
+	/* settle for a non-authoritative answer */
+	rp = rrlookup(dp, type, OKneg);
+	if(rp){
+		if(debug)
+			dnslog("[%d] dnresolve1 %s %d %d: rr from rrlookup",
+				getpid(), name, type, class);
+		return rp;
+	}
+
+	/* noone answered.  try the database, we might have a chance. */
+	rp = dblookup(name, class, type, 0, 0);
+	if (rp) {
+		if(debug)
+			dnslog("[%d] dnresolve1 %s %d %d: rr from dblookup",
+				getpid(), name, type, class);
+	}else{
+		if(debug)
+			dnslog("[%d] dnresolve1 %s %d %d: no rr from dblookup; crapped out",
+				getpid(), name, type, class);
+	}
+	return rp;
+}
+
+/*
+ *  walk a domain name one element to the right.
+ *  return a pointer to that element.
+ *  in other words, return a pointer to the parent domain name.
+ */
+char*
+walkup(char *name)
+{
+	char *cp;
+
+	cp = strchr(name, '.');
+	if(cp)
+		return cp+1;
+	else if(*name)
+		return "";
+	else
+		return 0;
+}
+
+/*
+ *  Get a udp port for sending requests and reading replies.  Put the port
+ *  into "headers" mode.
+ */
+static char *hmsg = "headers";
+
+int
+udpport(char *mtpt)
+{
+	int fd, ctl;
+	char ds[64], adir[64];
+
+	/* get a udp port */
+	snprint(ds, sizeof ds, "%s/udp!*!0", (mtpt && *mtpt) ? mtpt : "/net");
+	ctl = announce(ds, adir);
+	if(ctl < 0){
+		/* warning("can't get udp port"); */
+		return -1;
+	}
+
+	/* turn on header style interface */
+	if(write(ctl, hmsg, strlen(hmsg)) != strlen(hmsg)){
+		close(ctl);
+		warning(hmsg);
+		return -1;
+	}
+
+	/* grab the data file */
+	snprint(ds, sizeof ds, "%s/data", adir);
+	fd = open(ds, ORDWR);
+	close(ctl);
+	if(fd < 0)
+		warning("can't open udp port %s: %r", ds);
+	return fd;
+}
+
+void
+initdnsmsg(DNSmsg *mp, RR *rp, int flags, ushort reqno)
+{
+	memset(mp, 0, sizeof *mp);
+	mp->flags = flags;
+	mp->id = reqno;
+	mp->qd = rp;
+	if(rp != nil)
+		mp->qdcount = 1;
+}
+
+/* generate a DNS UDP query packet */
+int
+mkreq(DN *dp, int type, uchar *buf, int flags, ushort reqno)
+{
+	DNSmsg m;
+	int len;
+	Udphdr *uh = (Udphdr*)buf;
+	RR *rp;
+
+	/* stuff port number into output buffer */
+	memset(uh, 0, sizeof *uh);
+	hnputs(uh->rport, 53);
+
+	/* make request and convert it to output format */
+	rp = rralloc(type);
+	rp->owner = dp;
+	initdnsmsg(&m, rp, flags, reqno);
+	len = convDNS2M(&m, &buf[Udphdrsize], Maxudp);
+	rrfreelist(rp);
+	return len;
+}
+
+void
+freeanswers(DNSmsg *mp)
+{
+	rrfreelistptr(&mp->qd);
+	rrfreelistptr(&mp->an);
+	rrfreelistptr(&mp->ns);
+	rrfreelistptr(&mp->ar);
+	mp->qdcount = mp->ancount = mp->nscount = mp->arcount = 0;
+}
+
+/* timed read of reply.  sets srcip.  ibuf must be 64K to handle tcp answers. */
+static int
+readnet(Query *qp, int medium, uchar *ibuf, uvlong endms, uchar **replyp,
+	uchar *srcip)
+{
+	int len, fd;
+	long ms;
+	vlong startns = nsec();
+	uchar *reply;
+	uchar lenbuf[2];
+
+	len = -1;			/* pessimism */
+	*replyp = nil;
+	memset(srcip, 0, IPaddrlen);
+	ms = endms - NS2MS(startns);
+	if (ms <= 0)
+		return -1;		/* taking too long */
+
+	reply = ibuf;
+	alarm(ms);
+	if (medium == Udp)
+		if (qp->udpfd < 0)
+			dnslog("readnet: qp->udpfd closed");
+		else {
+			len = read(qp->udpfd, ibuf, Udphdrsize+Maxudpin);
+			alarm(0);
+			notestats(startns, len < 0, qp->type);
+			if (len >= IPaddrlen)
+				memmove(srcip, ibuf, IPaddrlen);
+			if (len >= Udphdrsize) {
+				len   -= Udphdrsize;
+				reply += Udphdrsize;
+			}
+		}
+	else {
+		if (!qp->tcpset)
+			dnslog("readnet: tcp params not set");
+		fd = qp->tcpfd;
+		if (fd < 0)
+			dnslog("readnet: %s: tcp fd unset for dest %I",
+				qp->dp->name, qp->tcpip);
+		else if (readn(fd, lenbuf, 2) != 2) {
+			dnslog("readnet: short read of 2-byte tcp msg size from %I",
+				qp->tcpip);
+			/* probably a time-out */
+			notestats(startns, 1, qp->type);
+		} else {
+			len = lenbuf[0]<<8 | lenbuf[1];
+			if (readn(fd, ibuf, len) != len) {
+				dnslog("readnet: short read of tcp data from %I",
+					qp->tcpip);
+				/* probably a time-out */
+				notestats(startns, 1, qp->type);
+				len = -1;
+			}
+		}
+		memmove(srcip, qp->tcpip, IPaddrlen);
+	}
+	alarm(0);
+	*replyp = reply;
+	return len;
+}
+
+/*
+ *  read replies to a request and remember the rrs in the answer(s).
+ *  ignore any of the wrong type.
+ *  wait at most until endms.
+ */
+static int
+readreply(Query *qp, int medium, ushort req, uchar *ibuf, DNSmsg *mp,
+	uvlong endms)
+{
+	int len;
+	char *err;
+	char tbuf[32];
+	uchar *reply;
+	uchar srcip[IPaddrlen];
+	RR *rp;
+
+	for (; timems() < endms &&
+	    (len = readnet(qp, medium, ibuf, endms, &reply, srcip)) >= 0;
+	    freeanswers(mp)){
+		/* convert into internal format  */
+		memset(mp, 0, sizeof *mp);
+		err = convM2DNS(reply, len, mp, nil);
+		if (mp->flags & Ftrunc) {
+			free(err);
+			freeanswers(mp);
+			/* notify our caller to retry the query via tcp. */
+			return -1;
+		} else if(err){
+			dnslog("readreply: %s: input err, len %d: %s: %I",
+				qp->dp->name, len, err, srcip);
+			free(err);
+			continue;
+		}
+		if(debug)
+			logreply(qp->req->id, srcip, mp);
+
+		/* answering the right question? */
+		if(mp->id != req)
+			dnslog("%d: id %d instead of %d: %I", qp->req->id,
+				mp->id, req, srcip);
+		else if(mp->qd == 0)
+			dnslog("%d: no question RR: %I", qp->req->id, srcip);
+		else if(mp->qd->owner != qp->dp)
+			dnslog("%d: owner %s instead of %s: %I", qp->req->id,
+				mp->qd->owner->name, qp->dp->name, srcip);
+		else if(mp->qd->type != qp->type)
+			dnslog("%d: qp->type %d instead of %d: %I",
+				qp->req->id, mp->qd->type, qp->type, srcip);
+		else {
+			/* remember what request this is in answer to */
+			for(rp = mp->an; rp; rp = rp->next)
+				rp->query = qp->type;
+			return 0;
+		}
+	}
+	if (timems() >= endms) {
+		;				/* query expired */
+	} else if (0) {
+		/* this happens routinely when a read times out */
+		dnslog("readreply: %s type %s: ns %I read error or eof "
+			"(returned %d): %r", qp->dp->name, rrname(qp->type,
+			tbuf, sizeof tbuf), srcip, len);
+		if (medium == Udp)
+			for (rp = qp->nsrp; rp != nil; rp = rp->next)
+				if (rp->type == Tns)
+					dnslog("readreply: %s: query sent to "
+						"ns %s", qp->dp->name,
+						rp->host->name);
+	}
+	memset(mp, 0, sizeof *mp);
+	return -1;
+}
+
+/*
+ *	return non-0 if first list includes second list
+ */
+int
+contains(RR *rp1, RR *rp2)
+{
+	RR *trp1, *trp2;
+
+	for(trp2 = rp2; trp2; trp2 = trp2->next){
+		for(trp1 = rp1; trp1; trp1 = trp1->next)
+			if(trp1->type == trp2->type)
+			if(trp1->host == trp2->host)
+			if(trp1->owner == trp2->owner)
+				break;
+		if(trp1 == nil)
+			return 0;
+	}
+	return 1;
+}
+
+
+/*
+ *  return multicast version if any
+ */
+int
+ipisbm(uchar *ip)
+{
+	if(isv4(ip)){
+		if (ip[IPv4off] >= 0xe0 && ip[IPv4off] < 0xf0 ||
+		    ipcmp(ip, IPv4bcast) == 0)
+			return 4;
+	} else
+		if(ip[0] == 0xff)
+			return 6;
+	return 0;
+}
+
+static int
+queryloops(Query *qp, RR *rp)
+{
+	DN *ns = rp->host;
+
+	/*
+	 *  looking up a server under itself
+	 */
+	if(subsume(rp->owner->name, ns->name))
+		return 1;
+
+	/*
+	 *  cycle on name servers refering
+	 *  to each another.
+	 */
+	for(; qp; qp = qp->prev)
+		if(qp->dp == ns)
+			return 1;
+
+	return 0;
+}
+
+/*
+ *  Get next server type address(es) into qp->dest[nd] and beyond
+ */
+static int
+serveraddrs(Query *qp, int nd, int depth, int type)
+{
+	RR *rp, *arp, *trp;
+	ulong mark;
+	Dest *p;
+
+	if(nd >= Maxdest)		/* dest array is full? */
+		return Maxdest;
+
+	/*
+	 *  look for a server whose address we already know.
+	 *  if we find one, mark it so we ignore this on
+	 *  subsequent passes.
+	 */
+	mark = 1UL<<type;
+	arp = nil;
+	for(rp = qp->nsrp; rp; rp = rp->next){
+		assert(rp->magic == RRmagic);
+		if(rp->marker & mark)
+			continue;
+		arp = rrlookup(rp->host, type, NOneg);
+		if(arp){
+			rp->marker |= mark;
+			break;
+		}
+		arp = dblookup(rp->host->name, Cin, type, 0, 0);
+		if(arp){
+			rp->marker |= mark;
+			break;
+		}
+	}
+
+	/*
+	 *  if the cache and database lookup didn't find any new
+	 *  server addresses, try resolving one via the network.
+	 *  Mark any we try to resolve so we don't try a second time.
+	 */
+	if(arp == nil){
+		for(rp = qp->nsrp; rp; rp = rp->next)
+			if((rp->marker & mark) == 0)
+			if(queryloops(qp, rp))
+				/*
+				 * give up as we should have got the address
+				 * by higher up nameserver when recursing
+				 * down, or will be queried when recursing up.
+				 */
+				return nd;
+
+		for(rp = qp->nsrp; rp; rp = rp->next){
+			if(rp->marker & mark)
+				continue;
+			rp->marker |= mark;
+			arp = dnresolve(rp->host->name, Cin, type, qp->req, 0,
+				depth+1, Recurse, 1, 0);
+			rrfreelist(rrremneg(&arp));
+			if(arp)
+				break;
+		}
+	}
+
+	/* use any addresses that we found */
+	for(trp = arp; trp && nd < Maxdest; trp = trp->next){
+		p = &qp->dest[nd];
+		memset(p, 0, sizeof *p);
+		if(parseip(p->a, trp->ip->name) == -1)
+			continue;
+
+		/*
+		 * straddling servers can reject all nameservers if they are all
+		 * inside, so be sure to list at least one outside ns at
+		 * the end of the ns list in /lib/ndb for `dom='.
+		 */
+		if (ipisbm(p->a) ||
+		    cfg.straddle && !insideaddr(qp->dp->name) && insidens(p->a))
+			continue;
+		p->nx = 0;
+		p->n = nil;
+		p->s = trp->owner;
+		for(rp = qp->nsrp; rp; rp = rp->next){
+			if(rp->host == p->s){
+				p->n = rp;
+				break;
+			}
+		}
+		p->code = Rtimeout;
+		nd++;
+	}
+	rrfreelist(arp);
+	return nd;
+}
+
+/*
+ *  cache negative responses
+ */
+static void
+cacheneg(DN *dp, int type, int rcode, RR *soarr)
+{
+	RR *rp;
+	DN *soaowner;
+	ulong ttl;
+
+	qlock(&stats);
+	stats.negcached++;
+	qunlock(&stats);
+
+	/* no cache time specified, don't make anything up */
+	if(soarr != nil){
+		if(soarr->next != nil)
+			rrfreelistptr(&soarr->next);
+		soaowner = soarr->owner;
+	} else
+		soaowner = nil;
+
+	/* the attach can cause soarr to be freed so mine it now */
+	if(soarr != nil && soarr->soa != nil)
+		ttl = soarr->soa->minttl;
+	else
+		ttl = 5*Min;
+
+	/* add soa and negative RR to the database */
+	rrattach(soarr, Authoritative);
+
+	rp = rralloc(type);
+	rp->owner = dp;
+	rp->negative = 1;
+	rp->negsoaowner = soaowner;
+	rp->negrcode = rcode;
+	rp->ttl = ttl;
+	rrattach(rp, Authoritative);
+}
+
+static int
+setdestoutns(Dest *p, int n)
+{
+	memset(p, 0, sizeof *p);
+	if (outsidensip(n, p->a) < 0){
+		if (n == 0)
+			dnslog("[%d] no outside-ns in ndb", getpid());
+		return -1;
+	}
+	p->s = dnlookup("outside-ns-ips", Cin, 1);
+	return 0;
+}
+
+/*
+ * issue query via UDP or TCP as appropriate.
+ * for TCP, returns with qp->tcpip set from udppkt header.
+ */
+static int
+mydnsquery(Query *qp, int medium, uchar *udppkt, int len)
+{
+	int rv, nfd;
+	char conndir[40], addr[128];
+	uchar belen[2];
+	NetConnInfo *nci;
+
+	rv = -1;
+	if (myip(udppkt))
+		return rv;
+	switch (medium) {
+	case Udp:
+		nfd = dup(qp->udpfd, -1);
+		if (nfd < 0) {
+			warning("mydnsquery: qp->udpfd %d: %r", qp->udpfd);
+			close(qp->udpfd);	/* ensure it's closed */
+			qp->udpfd = -1;		/* poison it */
+			break;
+		}
+		close(nfd);
+
+		if (qp->udpfd < 0)
+			dnslog("mydnsquery: qp->udpfd %d closed", qp->udpfd);
+		else {
+			if (write(qp->udpfd, udppkt, len+Udphdrsize) !=
+			    len+Udphdrsize)
+				warning("sending udp msg: %r");
+			else {
+				qlock(&stats);
+				stats.qsent++;
+				qunlock(&stats);
+				rv = 0;
+			}
+		}
+		break;
+	case Tcp:
+		/* send via TCP & keep fd around for reply */
+		memmove(qp->tcpip, udppkt, sizeof qp->tcpip);
+		snprint(addr, sizeof addr, "%s/tcp!%I!dns",
+			(mntpt && *mntpt) ? mntpt : "/net", udppkt);
+		alarm(10*1000);
+		qp->tcpfd = dial(addr, nil, conndir, &qp->tcpctlfd);
+		alarm(0);
+		if (qp->tcpfd < 0) {
+			dnslog("can't dial %s: %r", addr);
+			break;
+		}
+		nci = getnetconninfo(conndir, qp->tcpfd);
+		if (nci) {
+			parseip(qp->tcpip, nci->rsys);
+			freenetconninfo(nci);
+		} else
+			dnslog("mydnsquery: getnetconninfo failed");
+		qp->tcpset = 1;
+
+		belen[0] = len >> 8;
+		belen[1] = len;
+		if (write(qp->tcpfd, belen, 2) != 2 ||
+		    write(qp->tcpfd, udppkt + Udphdrsize, len) != len)
+			warning("sending tcp msg: %r");
+		else
+			rv = 0;
+		break;
+	}
+	return rv;
+}
+
+/*
+ * send query to all UDP destinations or one TCP destination,
+ * taken from obuf (udp packet) header
+ */
+static int
+xmitquery(Query *qp, int medium, int depth, uchar *obuf, int inns, int len)
+{
+	int n;
+	char buf[32];
+	Dest *p;
+
+	if(timems() >= qp->req->aborttime)
+		return -1;
+
+	/*
+	 * if we send tcp query, we just take the dest ip address from
+	 * the udp header placed there by tcpquery().
+	 */
+	if (medium == Tcp) {
+		procsetname("tcp %sside query for %s %s", (inns? "in": "out"),
+			qp->dp->name, rrname(qp->type, buf, sizeof buf));
+		if(mydnsquery(qp, medium, obuf, len) < 0) /* sets qp->tcpip from obuf */
+			return -1;
+		if(debug)
+			logsend(qp->req->id, depth, qp->tcpip, "", qp->dp->name,
+				qp->type);
+		return 0;
+	}
+
+	/*
+	 * get a nameserver address if we need one.
+	 * we're to transmit to more destinations than we currently have,
+	 * so get another.
+	 */
+	p = qp->dest;
+	n = qp->curdest - p;
+	if (qp->ndest > n) {
+		/* populates qp->dest with v4 and v6 addresses. */
+		n = serveraddrs(qp, n, depth, Ta);
+		n = serveraddrs(qp, n, depth, Taaaa);
+		if (n == 0 && cfg.straddle && cfg.inside) {
+			/* get ips of "outside-ns-ips" */
+			while(n < Maxdest){
+				if (setdestoutns(&qp->dest[n], n) < 0)
+					break;
+				n++;
+			}
+			if(n == 0)
+				dnslog("xmitquery: %s: no outside-ns nameservers",
+					qp->dp->name);
+		}
+		qp->curdest = &qp->dest[n];
+	}
+
+	for(n = 0; p < &qp->dest[qp->ndest] && p < qp->curdest; p++){
+		/* skip destinations we've finished with */
+		if(p->nx >= Maxtrans)
+			continue;
+		/* exponential backoff of requests */
+		if((1<<p->nx) > qp->ndest)
+			continue;
+
+		if(ipcmp(p->a, IPnoaddr) == 0)
+			continue;		/* mistake */
+
+		procsetname("udp %sside query to %I/%s %s %s",
+			(inns? "in": "out"), p->a, p->s->name,
+			qp->dp->name, rrname(qp->type, buf, sizeof buf));
+		if(debug)
+			logsend(qp->req->id, depth, p->a, p->s->name,
+				qp->dp->name, qp->type);
+
+		/* fill in UDP destination addr & send it */
+		memmove(obuf, p->a, sizeof p->a);
+		if(mydnsquery(qp, medium, obuf, len) == 0)
+			n++;
+		p->nx++;
+	}
+
+	return n == 0 ? -1 : 0;
+}
+
+/* is mp a cachable negative response (with Rname set)? */
+static int
+isnegrname(DNSmsg *mp)
+{
+	/* TODO: could add || cfg.justforw to RHS of && */
+	return mp->an == nil && (mp->flags & Rmask) == Rname;
+}
+
+static int
+filterhints(RR *rp, void *arg)
+{
+	RR *nsrp;
+
+	if(rp->type != Ta && rp->type != Taaaa)
+		return 0;
+
+	for(nsrp = arg; nsrp; nsrp = nsrp->next)
+		if(nsrp->type == Tns && rp->owner == nsrp->host)
+			return 1;
+
+	return 0;
+}
+
+static int
+filterauth(RR *rp, void *arg)
+{
+	Dest *dest;
+	RR *nsrp;
+
+	dest = arg;
+	nsrp = dest->n;
+	if(nsrp == nil)
+		return 0;
+
+	if(rp->type == Tsoa && rp->owner != nsrp->owner
+	&& !subsume(nsrp->owner->name, rp->owner->name)
+	&& strncmp(nsrp->owner->name, "local#", 6) != 0)
+		return 1;
+
+	if(rp->type != Tns)
+		return 0;
+
+	if(rp->owner != nsrp->owner
+	&& !subsume(nsrp->owner->name, rp->owner->name)
+	&& strncmp(nsrp->owner->name, "local#", 6) != 0)
+		return 1;
+
+	return baddelegation(rp, nsrp, dest->a);
+}
+
+static void
+reportandfree(RR *l, char *note, Dest *p)
+{
+	RR *rp;
+
+	while(rp = l){
+		l = l->next;
+		rp->next = nil;
+		if(debug)
+			dnslog("ignoring %s from %I/%s: %R",
+				note, p->a, p->s->name, rp);
+		rrfree(rp);
+	}
+}
+
+/* returns Answerr (-1) on errors, else number of answers, which can be zero. */
+static int
+procansw(Query *qp, DNSmsg *mp, int depth, Dest *p)
+{
+	int rv;
+	char buf[32];
+	DN *ndp;
+	Query nq;
+	RR *tp, *soarr;
+
+	if(mp->an == nil)
+		stats.negans++;
+
+	/* ignore any error replies */
+	switch(mp->flags & Rmask){
+	case Rrefused:
+	case Rserver:
+		stats.negserver++;
+		freeanswers(mp);
+		p->code = Rserver;
+		return Answerr;
+	}
+
+	/* ignore any bad delegations */
+	if((tp = rrremfilter(&mp->ns, filterauth, p)) != 0)
+		reportandfree(tp, "bad delegation", p);
+
+	/* remove any soa's from the authority section */
+	soarr = rrremtype(&mp->ns, Tsoa);
+
+	/* only nameservers remaining */
+	if((tp = rrremtype(&mp->ns, Tns)) != 0){
+		reportandfree(mp->ns, "non-nameserver", p);
+		mp->ns = tp;
+	}
+
+	/* remove answers not related to the question. */
+	if((tp = rrremowner(&mp->an, qp->dp)) != 0){
+		reportandfree(mp->an, "wrong subject answer", p);
+		mp->an = tp;
+	}
+	if(qp->type != Tall){
+		if((tp = rrremtype(&mp->an, qp->type)) != 0){
+			reportandfree(mp->an, "wrong type answer", p);
+			mp->an = tp;
+		}
+	}
+
+	/* incorporate answers */
+	unique(mp->an);
+	unique(mp->ns);
+	unique(mp->ar);
+
+	if(mp->an){
+		/*
+		 * only use cname answer when returned. some dns servers
+		 * attach (potential) spam hint address records which poisons
+		 * the cache.
+		 */
+		if((tp = rrremtype(&mp->an, Tcname)) != 0){
+			reportandfree(mp->an, "ip in cname answer", p);
+			mp->an = tp;
+		}
+		rrattach(mp->an, (mp->flags & Fauth) != 0);
+	}
+	if(mp->ar){
+		/* restrict hints to address rr's for nameservers only */
+		if((tp = rrremfilter(&mp->ar, filterhints, mp->ns)) != 0){
+			reportandfree(mp->ar, "hint", p);
+			mp->ar = tp;
+		}
+		rrattach(mp->ar, Notauthoritative);
+	}
+	if(mp->ns && !cfg.justforw){
+		ndp = mp->ns->owner;
+		rrattach(mp->ns, Notauthoritative);
+	} else {
+		ndp = nil;
+		rrfreelistptr(&mp->ns);
+		mp->nscount = 0;
+	}
+
+	/* free the question */
+	if(mp->qd) {
+		rrfreelistptr(&mp->qd);
+		mp->qdcount = 0;
+	}
+
+	/*
+	 *  Any reply from an authoritative server
+	 *  that does not provide more nameservers,
+	 *  or a positive reply terminates the search.
+	 *  A negative response now also terminates the search.
+	 */
+	if(mp->an || (mp->flags & Fauth) && mp->ns == nil){
+		if(isnegrname(mp))
+			qp->dp->respcode = Rname;
+		else
+			qp->dp->respcode = Rok;
+
+		/*
+		 *  cache any negative responses, free soarr.
+		 *  negative responses need not be authoritative:
+		 *  they can legitimately come from a cache.
+		 */
+		if( /* (mp->flags & Fauth) && */ mp->an == nil)
+			cacheneg(qp->dp, qp->type, (mp->flags & Rmask), soarr);
+		else
+			rrfreelist(soarr);
+		return 1;
+	} else if (isnegrname(mp)) {
+		qp->dp->respcode = Rname;
+		/*
+		 *  cache negative response.
+		 *  negative responses need not be authoritative:
+		 *  they can legitimately come from a cache.
+		 */
+		cacheneg(qp->dp, qp->type, (mp->flags & Rmask), soarr);
+		return 1;
+	}
+	stats.negnorname++;
+	rrfreelist(soarr);
+
+	/*
+	 *  if we've been given better name servers, recurse.
+	 *  if we're a pure resolver, don't recurse, we have
+	 *  to forward to a fixed set of named servers.
+	 */
+	if(mp->ns == nil || cfg.resolver && cfg.justforw)
+		return Answnone;
+	tp = rrlookup(ndp, Tns, NOneg);
+	if(contains(qp->nsrp, tp)){
+		rrfreelist(tp);
+		return Answnone;
+	}
+	procsetname("recursive query for %s %s", qp->dp->name,
+		rrname(qp->type, buf, sizeof buf));
+
+	queryinit(&nq, qp->dp, qp->type, qp->req);
+	rv = netqueryns(&nq, depth+1, tp);
+	querydestroy(&nq);
+
+	return rv;
+}
+
+/*
+ * send a query via tcp to a single address (from ibuf's udp header)
+ * and read the answer(s) into mp->an.
+ */
+static int
+tcpquery(Query *qp, DNSmsg *mp, int depth, uchar *ibuf, uchar *obuf, int len,
+	ulong waitms, int inns, ushort req)
+{
+	int rv = 0;
+	uvlong endms;
+
+	endms = timems() + waitms;
+	if(endms > qp->req->aborttime)
+		endms = qp->req->aborttime;
+
+	if (0)
+		dnslog("%s: udp reply truncated; retrying query via tcp to %I",
+			qp->dp->name, qp->tcpip);
+
+	memmove(obuf, ibuf, IPaddrlen);		/* send back to respondent */
+	memset(mp, 0, sizeof *mp);
+	if (xmitquery(qp, Tcp, depth, obuf, inns, len) < 0 ||
+	    readreply(qp, Tcp, req, ibuf, mp, endms) < 0)
+		rv = -1;
+	if (qp->tcpfd >= 0) {
+		hangup(qp->tcpctlfd);
+		close(qp->tcpctlfd);
+		close(qp->tcpfd);
+	}
+	qp->tcpfd = qp->tcpctlfd = -1;
+
+	return rv;
+}
+
+/*
+ *  query name servers.  fill in obuf with on-the-wire representation of a
+ *  DNSmsg derived from qp.  if the name server returns a pointer to another
+ *  name server, recurse.
+ */
+static int
+queryns(Query *qp, int depth, uchar *ibuf, uchar *obuf, ulong waitms, int inns)
+{
+	int ndest, len, replywaits, rv, flag;
+	ushort req;
+	uvlong endms;
+	char buf[32];
+	uchar srcip[IPaddrlen];
+	Dest *p, *np, dest[Maxdest];
+
+	req = rand();
+
+	/* request recursion only for local dns servers */
+	flag = Oquery;
+	if(strncmp(qp->nsrp->owner->name, "local#", 6) == 0)
+		flag |= Frecurse;
+
+	/* pack request into a udp message */
+	len = mkreq(qp->dp, qp->type, obuf, flag, req);
+
+	/* no server addresses yet */
+	memset(dest, 0, sizeof dest);
+	qp->curdest = qp->dest = dest;
+
+	/*
+	 *  transmit udp requests and wait for answers.
+	 *  at most Maxtrans attempts to each address.
+	 *  each cycle send one more message than the previous.
+	 *  retry a query via tcp if its response is truncated.
+	 */
+	for(ndest = 2; ndest < Maxdest; ndest += 2){
+		qp->ndest = ndest;
+		qp->tcpset = 0;
+		if (xmitquery(qp, Udp, depth, obuf, inns, len) < 0)
+			break;
+
+		endms = timems() + waitms;
+		if(endms > qp->req->aborttime)
+			endms = qp->req->aborttime;
+
+		for(replywaits = 0; replywaits < ndest; replywaits++){
+			DNSmsg m;
+
+			procsetname("reading %sside reply from %I: %s %s from %s",
+				(inns? "in": "out"), obuf, qp->dp->name,
+				rrname(qp->type, buf, sizeof buf), qp->req->from);
+
+			/* read udp answer into m */
+			if (readreply(qp, Udp, req, ibuf, &m, endms) >= 0)
+				memmove(srcip, ibuf, IPaddrlen);
+			else if (!(m.flags & Ftrunc)) {
+				freeanswers(&m);
+				break;		/* timed out on this dest */
+			} else {
+				/* whoops, it was truncated! ask again via tcp */
+				freeanswers(&m);
+				rv = tcpquery(qp, &m, depth, ibuf, obuf, len,
+					waitms, inns, req);  /* answer in m */
+				if (rv < 0) {
+					freeanswers(&m);
+					break;		/* failed via tcp too */
+				}
+				memmove(srcip, qp->tcpip, IPaddrlen);
+			}
+
+			/* find responder */
+			if(debug)
+				dnslog("queryns got reply from %I", srcip);
+			for(p = qp->dest; p < qp->curdest; p++)
+				if(ipcmp(p->a, srcip) == 0)
+					break;
+			if(p >= qp->curdest){
+				dnslog("response from %I but no destination", srcip);
+				continue;
+			}
+
+			/* remove all addrs of responding server from list */
+			for(np = qp->dest; np < qp->curdest; np++)
+				if(np->s == p->s)
+					np->nx = Maxtrans;
+
+			/* free or incorporate RRs in m */
+			rv = procansw(qp, &m, depth, p);
+			if (rv > Answnone) {
+				qp->dest = qp->curdest = nil; /* prevent accidents */
+				return rv;
+			}
+		}
+	}
+
+	/* if all servers returned failure, propagate it */
+	qp->dp->respcode = Rserver;
+	for(p = dest; p < qp->curdest; p++)
+		if(p->code != Rserver)
+			qp->dp->respcode = Rok;
+
+//	if (qp->dp->respcode)
+//		dnslog("queryns setting Rserver for %s", qp->dp->name);
+
+	qp->dest = qp->curdest = nil;		/* prevent accidents */
+	return Answnone;
+}
+
+/*
+ * in principle we could use a single descriptor for a udp port
+ * to send all queries and receive all the answers to them,
+ * but we'd have to sort out the answers by dns-query id.
+ */
+static int
+udpquery(Query *qp, char *mntpt, int depth, int patient, int inns)
+{
+	int fd, rv;
+	uchar *obuf, *ibuf;
+
+	/* use alloced buffers rather than ones from the stack */
+	ibuf = emalloc(64*1024);		/* max. tcp reply size */
+	obuf = emalloc(Maxudp+Udphdrsize);
+
+	fd = udpport(mntpt);
+	if (fd < 0) {
+		dnslog("can't get udpport for %s query of name %s: %r",
+			mntpt, qp->dp->name);
+		rv = -1;
+		goto Out;
+	}
+	qp->udpfd = fd;
+	rv = queryns(qp, depth, ibuf, obuf, 500UL<<(patient != 0), inns);
+	qp->udpfd = -1;
+	close(fd);
+
+Out:
+	free(obuf);
+	free(ibuf);
+	return rv;
+}
+
+/*
+ * look up (qp->dp->name, qp->type) rr in dns,
+ * using nameservers in qp->nsrp.
+ */
+static int
+netquery(Query *qp, int depth)
+{
+	int rv, triedin, inname;
+	RR *rp;
+
+	rv = Answnone;			/* pessimism */
+	if(depth > 12)			/* in a recursive loop? */
+		return Answnone;
+
+	slave(qp->req);
+
+	/*
+	 * slave might have forked.  if so, the parent process longjmped to
+	 * req->mret; we're usually the child slave, but if there are too
+	 * many children already, we're still the same process. under no
+	 * circumstances block the 9p loop.
+	 */
+	if(!qp->req->isslave && strcmp(qp->req->from, "9p") == 0)
+		return Answnone;
+
+	procsetname("netquery: %s", qp->dp->name);
+
+	/* prepare server RR's for incremental lookup */
+	for(rp = qp->nsrp; rp; rp = rp->next)
+		rp->marker = 0;
+
+	triedin = 0;
+
+	/*
+	 * normal resolvers and servers will just use mntpt for all addresses,
+	 * even on the outside.  straddling servers will use mntpt (/net)
+	 * for inside addresses and /net.alt for outside addresses,
+	 * thus bypassing other inside nameservers.
+	 */
+	inname = insideaddr(qp->dp->name);
+	if (!cfg.straddle || inname) {
+		rv = udpquery(qp, mntpt, depth, Hurry, (cfg.inside? Inns: Outns));
+		triedin = 1;
+	}
+
+	/*
+	 * if we're still looking, are inside, and have an outside domain,
+	 * try it on our outside interface, if any.
+	 */
+	if (rv == Answnone && cfg.inside && !inname) {
+		if (triedin)
+			dnslog(
+	   "[%d] netquery: internal nameservers failed for %s; trying external",
+				getpid(), qp->dp->name);
+
+		/* prepare server RR's for incremental lookup */
+		for(rp = qp->nsrp; rp; rp = rp->next)
+			rp->marker = 0;
+
+		rv = udpquery(qp, "/net.alt", depth, Patient, Outns);
+	}
+
+	return rv;
+}
+
+int
+seerootns(void)
+{
+	int rv;
+	char root[] = "";
+	Request req;
+	RR *rr, *nsrp;
+	Query q;
+
+	memset(&req, 0, sizeof req);
+	req.isslave = 1;
+	req.aborttime = timems() + Maxreqtm;
+	req.from = "internal";
+	queryinit(&q, dnlookup(root, Cin, 1), Tns, &req);
+	nsrp = randomize(dblookup(root, Cin, Tns, 0, 0));
+	for (rr = nsrp; rr != nil; rr = rr->next)
+		dnslog("seerootns query nsrp: %R", rr);
+	rv = netqueryns(&q, 0, nsrp);		/* lookup ". ns" using nsrp */
+	querydestroy(&q);
+	return rv;
+}
--- /dev/null
+++ b/dns.c
@@ -1,0 +1,950 @@
+#include <u.h>
+#include <libc.h>
+#include <auth.h>
+#include <fcall.h>
+#include <bio.h>
+#include <ip.h>
+#include "dns.h"
+
+enum
+{
+	Maxrequest=		1024,
+	Maxreply=		8192,		/* was 512 */
+	Maxrrr=			32,		/* was 16 */
+	Maxfdata=		8192,
+
+	Defmaxage=		60*60,	/* default domain name max. age */
+
+	Qdir=			0,
+	Qdns=			1,
+};
+
+typedef struct Mfile	Mfile;
+typedef struct Job	Job;
+typedef struct Network	Network;
+
+int vers;		/* incremented each clone/attach */
+
+/* holds data to be returned via read of /net/dns, perhaps multiple reads */
+struct Mfile
+{
+	Mfile		*next;		/* next free mfile */
+
+	char		*user;
+	Qid		qid;
+	int		fid;
+
+	int		type;		/* reply type */
+	char		reply[Maxreply];
+	ushort		rr[Maxrrr];	/* offset of rr's */
+	ushort		nrr;		/* number of rr's */
+};
+
+/*
+ *  active local requests
+ */
+struct Job
+{
+	Job	*next;
+	int	flushed;
+	Fcall	request;
+	Fcall	reply;
+};
+Lock	joblock;
+Job	*joblist;
+
+struct {
+	Lock;
+	Mfile	*inuse;		/* active mfile's */
+} mfalloc;
+
+Cfg	cfg;
+int	debug;
+int	maxage = Defmaxage;
+int	mfd[2];
+int	needrefresh;
+ulong	now;
+vlong	nowns;
+int	sendnotifies;
+char	*trace;
+int	traceactivity;
+char	*zonerefreshprogram;
+
+char	*logfile = "dns";	/* or "dns.test" */
+char	*dbfile;
+char	*dnsuser;
+char	mntpt[Maxpath];
+
+int	addforwtarg(char *);
+int	fillreply(Mfile*, int);
+void	freejob(Job*);
+void	io(void);
+void	mountinit(char*, char*);
+Job*	newjob(void);
+void	rattach(Job*, Mfile*);
+void	rauth(Job*);
+void	rclunk(Job*, Mfile*);
+void	rcreate(Job*, Mfile*);
+void	rflush(Job*);
+void	ropen(Job*, Mfile*);
+void	rread(Job*, Mfile*);
+void	rremove(Job*, Mfile*);
+void	rstat(Job*, Mfile*);
+void	rversion(Job*);
+char*	rwalk(Job*, Mfile*);
+void	rwrite(Job*, Mfile*, Request*);
+void	rwstat(Job*, Mfile*);
+void	sendmsg(Job*, char*);
+void	setext(char*, int, char*);
+
+static char *lookupquery(Job*, Mfile*, Request*, char*, char*, int, int);
+static char *respond(Job*, Mfile*, RR*, char*, int, int);
+
+void
+usage(void)
+{
+	fprint(2, "usage: %s [-FnorRs] [-a maxage] [-f ndb-file] [-N target] "
+		"[-T forwip] [-x netmtpt] [-z refreshprog]\n", argv0);
+	exits("usage");
+}
+
+void
+main(int argc, char *argv[])
+{
+	char servefile[Maxpath], ext[Maxpath];
+	Dir *dir;
+
+	setnetmtpt(mntpt, sizeof mntpt, nil);
+	ext[0] = 0;
+	ARGBEGIN{
+	case 'a':
+		maxage = atol(EARGF(usage()));
+		if (maxage <= 0)
+			maxage = Defmaxage;
+		break;
+	case 'd':
+		debug = 1;
+		traceactivity = 1;
+		break;
+	case 'f':
+		dbfile = EARGF(usage());
+		break;
+	case 'F':
+		cfg.justforw = cfg.resolver = 1;
+		break;
+	case 'n':
+		sendnotifies = 1;
+		break;
+	case 'N':
+		target = atol(EARGF(usage()));
+		if (target < 1000)
+			target = 1000;
+		break;
+	case 'o':
+		cfg.straddle = 1;	/* straddle inside & outside networks */
+		break;
+	case 'r':
+		cfg.resolver = 1;
+		break;
+	case 'R':
+		norecursion = 1;
+		break;
+	case 's':
+		cfg.serve = 1;		/* serve network */
+		cfg.cachedb = 1;
+		break;
+	case 'T':
+		addforwtarg(EARGF(usage()));
+		break;
+	case 'x':
+		setnetmtpt(mntpt, sizeof mntpt, EARGF(usage()));
+		setext(ext, sizeof ext, mntpt);
+		break;
+	case 'z':
+		zonerefreshprogram = EARGF(usage());
+		break;
+	default:
+		usage();
+		break;
+	}ARGEND
+	if(argc != 0)
+		usage();
+
+	rfork(RFREND|RFNOTEG);
+
+	cfg.inside = (*mntpt == '\0' || strcmp(mntpt, "/net") == 0);
+
+	/* start syslog before we fork */
+	fmtinstall('F', fcallfmt);
+	dninit();
+	dnslog("starting %s%sdns %s%s%son %s",
+		(cfg.straddle? "straddling ": ""),
+		(cfg.cachedb? "caching ": ""),
+		(cfg.serve?   "udp server ": ""),
+		(cfg.justforw? "forwarding-only ": ""),
+		(cfg.resolver? "resolver ": ""), mntpt);
+
+	opendatabase();
+	now = time(nil);		/* open time files before we fork */
+	nowns = nsec();
+	dnsuser = estrdup(getuser());
+
+	snprint(servefile, sizeof servefile, "#s/dns%s", ext);
+	dir = dirstat(servefile);
+	if (dir)
+		sysfatal("%s exists; another dns instance is running",
+			servefile);
+	free(dir);
+	mountinit(servefile, mntpt);	/* forks, parent exits */
+
+	srand(truerand());
+	db2cache(1);
+
+	if (cfg.straddle && !seerootns())
+		dnslog("straddle server misconfigured; can't see root name servers");
+
+	if(cfg.serve)
+		dnudpserver(mntpt);
+	if(sendnotifies)
+		notifyproc();
+
+	io();
+	_exits(0);
+}
+
+/*
+ *  if a mount point is specified, set the cs extension to be the mount point
+ *  with '_'s replacing '/'s
+ */
+void
+setext(char *ext, int n, char *p)
+{
+	int i, c;
+
+	n--;
+	for(i = 0; i < n; i++){
+		c = p[i];
+		if(c == 0)
+			break;
+		if(c == '/')
+			c = '_';
+		ext[i] = c;
+	}
+	ext[i] = 0;
+}
+
+void
+mountinit(char *service, char *mntpt)
+{
+	int f;
+	int p[2];
+	char buf[32];
+
+	if(pipe(p) < 0)
+		sysfatal("pipe failed: %r");
+
+	/*
+	 *  make a /srv/dns
+	 */
+	if((f = create(service, OWRITE|ORCLOSE, 0666)) < 0)
+		sysfatal("create %s failed: %r", service);
+	snprint(buf, sizeof buf, "%d", p[1]);
+	if(write(f, buf, strlen(buf)) != strlen(buf))
+		sysfatal("write %s failed: %r", service);
+
+	/* copy namespace to avoid a deadlock */
+	switch(rfork(RFFDG|RFPROC|RFNAMEG)){
+	case 0:			/* child: start main proc */
+		close(p[1]);
+		procsetname("%s", mntpt);
+		break;
+	case -1:
+		sysfatal("fork failed: %r");
+	default:		/* parent: make /srv/dns, mount it, exit */
+		close(p[0]);
+
+		/*
+		 *  put ourselves into the file system
+		 */
+		if(mount(p[1], -1, mntpt, MAFTER, "") == -1)
+			fprint(2, "dns mount failed: %r\n");
+		_exits(0);
+	}
+	mfd[0] = mfd[1] = p[0];
+}
+
+Mfile*
+newfid(int fid, int needunused)
+{
+	Mfile *mf;
+
+	lock(&mfalloc);
+	for(mf = mfalloc.inuse; mf != nil; mf = mf->next)
+		if(mf->fid == fid){
+			unlock(&mfalloc);
+			if(needunused)
+				return nil;
+			return mf;
+		}
+	mf = emalloc(sizeof(*mf));
+	mf->fid = fid;
+	mf->qid.vers = vers;
+	mf->qid.type = QTDIR;
+	mf->qid.path = 0LL;
+	mf->user = estrdup("none");
+	mf->next = mfalloc.inuse;
+	mfalloc.inuse = mf;
+	unlock(&mfalloc);
+	return mf;
+}
+
+void
+freefid(Mfile *mf)
+{
+	Mfile **l;
+
+	lock(&mfalloc);
+	for(l = &mfalloc.inuse; *l != nil; l = &(*l)->next)
+		if(*l == mf){
+			*l = mf->next;
+			free(mf->user);
+			memset(mf, 0, sizeof *mf);	/* cause trouble */
+			free(mf);
+			unlock(&mfalloc);
+			return;
+		}
+	unlock(&mfalloc);
+	sysfatal("freeing unused fid");
+}
+
+Mfile*
+copyfid(Mfile *mf, int fid)
+{
+	Mfile *nmf;
+
+	nmf = newfid(fid, 1);
+	if(nmf == nil)
+		return nil;
+	nmf->fid = fid;
+	free(nmf->user);			/* estrdup("none") */
+	nmf->user = estrdup(mf->user);
+	nmf->qid.type = mf->qid.type;
+	nmf->qid.path = mf->qid.path;
+	nmf->qid.vers = vers++;
+	return nmf;
+}
+
+Job*
+newjob(void)
+{
+	Job *job;
+
+	job = emalloc(sizeof *job);
+	lock(&joblock);
+	job->next = joblist;
+	joblist = job;
+	job->request.tag = -1;
+	unlock(&joblock);
+	return job;
+}
+
+void
+freejob(Job *job)
+{
+	Job **l;
+
+	lock(&joblock);
+	for(l = &joblist; *l; l = &(*l)->next)
+		if(*l == job){
+			*l = job->next;
+			memset(job, 0, sizeof *job);	/* cause trouble */
+			free(job);
+			break;
+		}
+	unlock(&joblock);
+}
+
+void
+flushjob(int tag)
+{
+	Job *job;
+
+	lock(&joblock);
+	for(job = joblist; job; job = job->next)
+		if(job->request.tag == tag && job->request.type != Tflush){
+			job->flushed = 1;
+			break;
+		}
+	unlock(&joblock);
+}
+
+void
+io(void)
+{
+	volatile long n;
+	volatile uchar mdata[IOHDRSZ + Maxfdata];
+	Job *volatile job;
+	Mfile *volatile mf;
+	volatile Request req;
+
+	memset(&req, 0, sizeof req);
+	/*
+	 *  a slave process is sometimes forked to wait for replies from other
+	 *  servers.  The master process returns immediately via a longjmp
+	 *  through 'mret'.
+	 */
+	if(setjmp(req.mret))
+		putactivity(0);
+	req.isslave = 0;
+	while((n = read9pmsg(mfd[0], mdata, sizeof mdata)) != 0){
+		if(n < 0){
+			dnslog("error reading 9P from %s: %r", mntpt);
+			break;
+		}
+
+		stats.qrecvd9prpc++;
+		job = newjob();
+		if(convM2S(mdata, n, &job->request) != n){
+			dnslog("format error %ux %ux %ux %ux %ux",
+				mdata[0], mdata[1], mdata[2], mdata[3], mdata[4]);
+			freejob(job);
+			break;
+		}
+		mf = newfid(job->request.fid, 0);
+		if(debug)
+			dnslog("%F", &job->request);
+
+		getactivity(&req, 0);
+		req.aborttime = timems() + Maxreqtm;
+		req.from = "9p";
+
+		switch(job->request.type){
+		default:
+			warning("unknown request type %d", job->request.type);
+			break;
+		case Tversion:
+			rversion(job);
+			break;
+		case Tauth:
+			rauth(job);
+			break;
+		case Tflush:
+			rflush(job);
+			break;
+		case Tattach:
+			rattach(job, mf);
+			break;
+		case Twalk:
+			rwalk(job, mf);
+			break;
+		case Topen:
+			ropen(job, mf);
+			break;
+		case Tcreate:
+			rcreate(job, mf);
+			break;
+		case Tread:
+			rread(job, mf);
+			break;
+		case Twrite:
+			/* &req is handed to dnresolve() */
+			rwrite(job, mf, &req);
+			break;
+		case Tclunk:
+			rclunk(job, mf);
+			break;
+		case Tremove:
+			rremove(job, mf);
+			break;
+		case Tstat:
+			rstat(job, mf);
+			break;
+		case Twstat:
+			rwstat(job, mf);
+			break;
+		}
+
+		freejob(job);
+
+		/*
+		 *  slave processes die after replying
+		 */
+		if(req.isslave){
+			putactivity(0);
+			_exits(0);
+		}
+
+		putactivity(0);
+	}
+}
+
+void
+rversion(Job *job)
+{
+	if(job->request.msize > IOHDRSZ + Maxfdata)
+		job->reply.msize = IOHDRSZ + Maxfdata;
+	else
+		job->reply.msize = job->request.msize;
+	job->reply.version = "9P2000";
+	if(strncmp(job->request.version, "9P", 2) != 0)
+		job->reply.version = "unknown";
+	sendmsg(job, nil);
+}
+
+void
+rauth(Job *job)
+{
+	sendmsg(job, "dns: authentication not required");
+}
+
+/*
+ *  don't flush till all the slaves are done
+ */
+void
+rflush(Job *job)
+{
+	flushjob(job->request.oldtag);
+	sendmsg(job, 0);
+}
+
+void
+rattach(Job *job, Mfile *mf)
+{
+	if(mf->user != nil)
+		free(mf->user);
+	mf->user = estrdup(job->request.uname);
+	mf->qid.vers = vers++;
+	mf->qid.type = QTDIR;
+	mf->qid.path = 0LL;
+	job->reply.qid = mf->qid;
+	sendmsg(job, 0);
+}
+
+char*
+rwalk(Job *job, Mfile *mf)
+{
+	int i, nelems;
+	char *err;
+	char **elems;
+	Mfile *nmf;
+	Qid qid;
+
+	err = 0;
+	nmf = nil;
+	elems  = job->request.wname;
+	nelems = job->request.nwname;
+	job->reply.nwqid = 0;
+
+	if(job->request.newfid != job->request.fid){
+		/* clone fid */
+		nmf = copyfid(mf, job->request.newfid);
+		if(nmf == nil){
+			err = "clone bad newfid";
+			goto send;
+		}
+		mf = nmf;
+	}
+	/* else nmf will be nil */
+
+	qid = mf->qid;
+	if(nelems > 0)
+		/* walk fid */
+		for(i=0; i<nelems && i<MAXWELEM; i++){
+			if((qid.type & QTDIR) == 0){
+				err = "not a directory";
+				break;
+			}
+			if (strcmp(elems[i], "..") == 0 ||
+			    strcmp(elems[i], ".") == 0){
+				qid.type = QTDIR;
+				qid.path = Qdir;
+Found:
+				job->reply.wqid[i] = qid;
+				job->reply.nwqid++;
+				continue;
+			}
+			if(strcmp(elems[i], "dns") == 0){
+				qid.type = QTFILE;
+				qid.path = Qdns;
+				goto Found;
+			}
+			err = "file does not exist";
+			break;
+		}
+
+send:
+	if(nmf != nil && (err!=nil || job->reply.nwqid<nelems))
+		freefid(nmf);
+	if(err == nil)
+		mf->qid = qid;
+	sendmsg(job, err);
+	return err;
+}
+
+void
+ropen(Job *job, Mfile *mf)
+{
+	int mode;
+	char *err;
+
+	err = 0;
+	mode = job->request.mode;
+	if(mf->qid.type & QTDIR)
+		if(mode)
+			err = "permission denied";
+	job->reply.qid = mf->qid;
+	job->reply.iounit = 0;
+	sendmsg(job, err);
+}
+
+void
+rcreate(Job *job, Mfile *mf)
+{
+	USED(mf);
+	sendmsg(job, "creation permission denied");
+}
+
+void
+rread(Job *job, Mfile *mf)
+{
+	int i, n;
+	long clock;
+	ulong cnt;
+	vlong off;
+	char *err;
+	uchar buf[Maxfdata];
+	Dir dir;
+
+	n = 0;
+	err = nil;
+	off = job->request.offset;
+	cnt = job->request.count;
+	*buf = '\0';
+	job->reply.data = (char*)buf;
+	if(mf->qid.type & QTDIR){
+		clock = time(nil);
+		if(off == 0){
+			memset(&dir, 0, sizeof dir);
+			dir.name = "dns";
+			dir.qid.type = QTFILE;
+			dir.qid.vers = vers;
+			dir.qid.path = Qdns;
+			dir.mode = 0666;
+			dir.length = 0;
+			dir.uid = dir.gid = dir.muid = mf->user;
+			dir.atime = dir.mtime = clock;		/* wrong */
+			n = convD2M(&dir, buf, sizeof buf);
+		}
+	} else if (off < 0)
+		err = "negative read offset";
+	else {
+		/* first offset will always be zero */
+		for(i = 1; i <= mf->nrr; i++)
+			if(mf->rr[i] > off)
+				break;
+		if(i <= mf->nrr) {
+			if(off + cnt > mf->rr[i])
+				n = mf->rr[i] - off;
+			else
+				n = cnt;
+			assert(n >= 0);
+			job->reply.data = mf->reply + off;
+		}
+	}
+	job->reply.count = n;
+	sendmsg(job, err);
+}
+
+void
+rwrite(Job *job, Mfile *mf, Request *req)
+{
+	int rooted, wantsav, send;
+	ulong cnt;
+	char *err, *p, *atype;
+	char errbuf[ERRMAX];
+
+	err = nil;
+	cnt = job->request.count;
+	send = 1;
+	if(mf->qid.type & QTDIR)
+		err = "can't write directory";
+	else if (job->request.offset != 0)
+		err = "writing at non-zero offset";
+	else if(cnt >= Maxrequest)
+		err = "request too long";
+	else
+		send = 0;
+	if (send)
+		goto send;
+
+	job->request.data[cnt] = 0;
+	if(cnt > 0 && job->request.data[cnt-1] == '\n')
+		job->request.data[cnt-1] = 0;
+
+	if(strcmp(mf->user, "none") == 0 || strcmp(mf->user, dnsuser) != 0)
+		goto query;	/* skip special commands if not owner */
+
+	/*
+	 *  special commands
+	 */
+	if(debug)
+		dnslog("rwrite got: %s", job->request.data);
+	send = 1;
+	if(strcmp(job->request.data, "debug")==0)
+		debug ^= 1;
+	else if(strcmp(job->request.data, "dump")==0)
+		dndump("/lib/ndb/dnsdump");
+	else if(strcmp(job->request.data, "refresh")==0)
+		needrefresh = 1;
+	else if(strcmp(job->request.data, "stats")==0)
+		dnstats("/lib/ndb/dnsstats");
+	else if(strncmp(job->request.data, "target ", 7)==0){
+		target = atol(job->request.data + 7);
+		dnslog("target set to %ld", target);
+	} else
+		send = 0;
+	if (send)
+		goto send;
+
+query:
+	/*
+	 *  kill previous reply
+	 */
+	mf->nrr = 0;
+	mf->rr[0] = 0;
+
+	/*
+	 *  break up request (into a name and a type)
+	 */
+	atype = strchr(job->request.data, ' ');
+	if(atype == 0){
+		snprint(errbuf, sizeof errbuf, "illegal request %s",
+			job->request.data);
+		err = errbuf;
+		goto send;
+	} else
+		*atype++ = 0;
+
+	/*
+	 *  tracing request
+	 */
+	if(strcmp(atype, "trace") == 0){
+		if(trace)
+			free(trace);
+		if(*job->request.data)
+			trace = estrdup(job->request.data);
+		else
+			trace = 0;
+		goto send;
+	}
+
+	/* normal request: domain [type] */
+	stats.qrecvd9p++;
+	mf->type = rrtype(atype);
+	if(mf->type < 0){
+		snprint(errbuf, sizeof errbuf, "unknown type %s", atype);
+		err = errbuf;
+		goto send;
+	}
+
+	p = atype - 2;
+	if(p >= job->request.data && *p == '.'){
+		rooted = 1;
+		*p = 0;
+	} else
+		rooted = 0;
+
+	p = job->request.data;
+	if(*p == '!'){
+		wantsav = 1;
+		p++;
+	} else
+		wantsav = 0;
+
+	err = lookupquery(job, mf, req, errbuf, p, wantsav, rooted);
+send:
+	job->reply.count = cnt;
+	sendmsg(job, err);
+}
+
+/*
+ * dnsdebug calls
+ *	rr = dnresolve(buf, Cin, type, &req, nil, 0, Recurse, rooted, nil);
+ * which generates a UDP query, which eventually calls
+ *	dnserver(&reqmsg, &repmsg, &req, buf, rcode);
+ * which calls
+ *	rp = dnresolve(name, Cin, type, req, &mp->an, 0, recurse, 1, nil);
+ *
+ * but here we just call dnresolve directly.
+ */
+static char *
+lookupquery(Job *job, Mfile *mf, Request *req, char *errbuf, char *p,
+	int wantsav, int rooted)
+{
+	int status;
+	RR *rp, *neg;
+
+	status = Rok;
+	rp = dnresolve(p, Cin, mf->type, req, nil, 0, Recurse, rooted, &status);
+
+	neg = rrremneg(&rp);
+	if(neg){
+		status = neg->negrcode;
+		rrfreelist(neg);
+	}
+
+	return respond(job, mf, rp, errbuf, status, wantsav);
+}
+
+static char *
+respond(Job *job, Mfile *mf, RR *rp, char *errbuf, int status, int wantsav)
+{
+	long n;
+	RR *tp;
+
+	if(rp == nil)
+		switch(status){
+		case Rname:
+			return "name does not exist";
+		case Rserver:
+			return "dns failure";
+		case Rok:
+		default:
+			snprint(errbuf, ERRMAX,
+				"resource does not exist; negrcode %d", status);
+			return errbuf;
+		}
+
+	lock(&joblock);
+	if(!job->flushed){
+		/* format data to be read later */
+		n = 0;
+		mf->nrr = 0;
+		for(tp = rp; mf->nrr < Maxrrr-1 && n < Maxreply && tp &&
+		    tsame(mf->type, tp->type); tp = tp->next){
+			mf->rr[mf->nrr++] = n;
+			if(wantsav)
+				n += snprint(mf->reply+n, Maxreply-n, "%Q", tp);
+			else
+				n += snprint(mf->reply+n, Maxreply-n, "%R", tp);
+		}
+		mf->rr[mf->nrr] = n;
+	}
+	unlock(&joblock);
+
+	rrfreelist(rp);
+
+	return nil;
+}
+
+void
+rclunk(Job *job, Mfile *mf)
+{
+	freefid(mf);
+	sendmsg(job, 0);
+}
+
+void
+rremove(Job *job, Mfile *mf)
+{
+	USED(mf);
+	sendmsg(job, "remove permission denied");
+}
+
+void
+rstat(Job *job, Mfile *mf)
+{
+	Dir dir;
+	uchar buf[IOHDRSZ+Maxfdata];
+
+	memset(&dir, 0, sizeof dir);
+	if(mf->qid.type & QTDIR){
+		dir.name = ".";
+		dir.mode = DMDIR|0555;
+	} else {
+		dir.name = "dns";
+		dir.mode = 0666;
+	}
+	dir.qid = mf->qid;
+	dir.length = 0;
+	dir.uid = dir.gid = dir.muid = mf->user;
+	dir.atime = dir.mtime = time(nil);
+	job->reply.nstat = convD2M(&dir, buf, sizeof buf);
+	job->reply.stat = buf;
+	sendmsg(job, 0);
+}
+
+void
+rwstat(Job *job, Mfile *mf)
+{
+	USED(mf);
+	sendmsg(job, "wstat permission denied");
+}
+
+void
+sendmsg(Job *job, char *err)
+{
+	int n;
+	uchar mdata[IOHDRSZ + Maxfdata];
+	char ename[ERRMAX];
+
+	if(err){
+		job->reply.type = Rerror;
+		snprint(ename, sizeof ename, "dns: %s", err);
+		job->reply.ename = ename;
+	}else
+		job->reply.type = job->request.type+1;
+	job->reply.tag = job->request.tag;
+	n = convS2M(&job->reply, mdata, sizeof mdata);
+	if(n == 0){
+		warning("sendmsg convS2M of %F returns 0", &job->reply);
+		abort();
+	}
+	lock(&joblock);
+	if(job->flushed == 0)
+		if(write(mfd[1], mdata, n)!=n)
+			sysfatal("mount write");
+	unlock(&joblock);
+	if(debug)
+		dnslog("%F %d", &job->reply, n);
+}
+
+/*
+ *  the following varies between dnsdebug and dns
+ */
+void
+logreply(int id, uchar *addr, DNSmsg *mp)
+{
+	RR *rp;
+
+	dnslog("%d: rcvd %I flags:%s%s%s%s%s", id, addr,
+		mp->flags & Fauth? " auth": "",
+		mp->flags & Ftrunc? " trunc": "",
+		mp->flags & Frecurse? " rd": "",
+		mp->flags & Fcanrec? " ra": "",
+		(mp->flags & (Fauth|Rmask)) == (Fauth|Rname)? " nx": "");
+	for(rp = mp->qd; rp != nil; rp = rp->next)
+		dnslog("%d: rcvd %I qd %s", id, addr, rp->owner->name);
+	for(rp = mp->an; rp != nil; rp = rp->next)
+		dnslog("%d: rcvd %I an %R", id, addr, rp);
+	for(rp = mp->ns; rp != nil; rp = rp->next)
+		dnslog("%d: rcvd %I ns %R", id, addr, rp);
+	for(rp = mp->ar; rp != nil; rp = rp->next)
+		dnslog("%d: rcvd %I ar %R", id, addr, rp);
+}
+
+void
+logsend(int id, int subid, uchar *addr, char *sname, char *rname, int type)
+{
+	char buf[12];
+
+	dnslog("[%d] %d.%d: sending to %I/%s %s %s",
+		getpid(), id, subid, addr, sname, rname,
+		rrname(type, buf, sizeof buf));
+}
+
+RR*
+getdnsservers(int class)
+{
+	return dnsservers(class);
+}
--- /dev/null
+++ b/dns.h
@@ -1,0 +1,568 @@
+#define NS2MS(ns) ((ns) / 1000000L)
+#define S2MS(s)   ((s)  * 1000LL)
+
+#define timems()	NS2MS(nsec())
+
+typedef struct Ndbtuple Ndbtuple;
+
+enum
+{
+	/* RR types; see: http://www.iana.org/assignments/dns-parameters */
+	Ta=		1,
+	Tns=		2,
+	Tmd=		3,
+	Tmf=		4,
+	Tcname=		5,
+	Tsoa=		6,
+	Tmb=		7,
+	Tmg=		8,
+	Tmr=		9,
+	Tnull=		10,
+	Twks=		11,
+	Tptr=		12,
+	Thinfo=		13,
+	Tminfo=		14,
+	Tmx=		15,
+	Ttxt=		16,
+	Trp=		17,
+	Tafsdb=		18,
+	Tx25=		19,
+	Tisdn=		20,
+	Trt=		21,
+	Tnsap=		22,
+	Tnsapptr=	23,
+	Tsig=		24,
+	Tkey=		25,
+	Tpx=		26,
+	Tgpos=		27,
+	Taaaa=		28,
+	Tloc=		29,
+	Tnxt=		30,
+	Teid=		31,
+	Tnimloc=	32,
+	Tsrv=		33,
+	Tatma=		34,
+	Tnaptr=		35,
+	Tkx=		36,
+	Tcert=		37,
+	Ta6=		38,
+	Tdname=		39,
+	Tsink=		40,
+	Topt=		41,
+	Tapl=		42,
+	Tds=		43,
+	Tsshfp=		44,
+	Tipseckey=	45,
+	Trrsig=		46,
+	Tnsec=		47,
+	Tdnskey=	48,
+
+	Tspf=		99,
+	Tuinfo=		100,
+	Tuid=		101,
+	Tgid=		102,
+	Tunspec=	103,
+
+	/* query types (all RR types are also queries) */
+	Ttkey=	249,	/* transaction key */
+	Ttsig=	250,	/* transaction signature */
+	Tixfr=	251,	/* incremental zone transfer */
+	Taxfr=	252,	/* zone transfer */
+	Tmailb=	253,	/* { Tmb, Tmg, Tmr } */
+	Tmaila= 254,	/* obsolete */
+	Tall=	255,	/* all records */
+
+	/* classes */
+	Csym=	0,	/* internal symbols */
+	Cin=	1,	/* internet */
+	Ccs,		/* CSNET (obsolete) */
+	Cch,		/* Chaos net */
+	Chs,		/* Hesiod (?) */
+
+	/* class queries (all class types are also queries) */
+	Call=	255,	/* all classes */
+
+	/* opcodes */
+	Oquery=		0<<11,		/* normal query */
+	Oinverse=	1<<11,		/* inverse query (retired) */
+	Ostatus=	2<<11,		/* status request */
+	Onotify=	4<<11,		/* notify slaves of updates */
+	Oupdate=	5<<11,
+	Omask=		0xf<<11,	/* mask for opcode */
+
+	/* response codes */
+	Rok=		0,
+	Rformat=	1,	/* format error */
+	Rserver=	2,	/* server failure (e.g. no answer from something) */
+	Rname=		3,	/* bad name */
+	Runimplimented=	4,	/* unimplemented */
+	Rrefused=	5,	/* we don't like you */
+	Ryxdomain=	6,	/* name exists when it should not */
+	Ryxrrset=	7,	/* rr set exists when it should not */
+	Rnxrrset=	8,	/* rr set that should exist does not */
+	Rnotauth=	9,	/* not authoritative */
+	Rnotzone=	10,	/* name not in zone */
+	Rbadvers=	16,	/* bad opt version */
+/*	Rbadsig=	16, */	/* also tsig signature failure */
+	Rbadkey=	17,		/* key not recognized */
+	Rbadtime=	18,		/* signature out of time window */
+	Rbadmode=	19,		/* bad tkey mode */
+	Rbadname=	20,		/* duplicate key name */
+	Rbadalg=	21,		/* algorithm not supported */
+	Rmask=		0x1f,	/* mask for response */
+	Rtimeout=	1<<5,	/* timeout sending (for internal use only) */
+
+	/* bits in flag word (other than opcode and response) */
+	Fresp=		1<<15,	/* message is a response */
+	Fauth=		1<<10,	/* true if an authoritative response */
+	Ftrunc=		1<<9,	/* truncated message */
+	Frecurse=	1<<8,	/* request recursion */
+	Fcanrec=	1<<7,	/* server can recurse */
+
+	Domlen=		256,	/* max domain name length (with NULL) */
+	Labellen=	64,	/* max domain label length (with NULL) */
+	Strlen=		256,	/* max string length (with NULL) */
+
+	/* time to live values (in seconds) */
+	Min=		60,
+	Hour=		60*Min,		/* */
+	Day=		24*Hour,	/* Ta, Tmx */
+	Week=		7*Day,		/* Tsoa, Tns */
+	Year=		52*Week,
+	DEFTTL=		Day,
+
+	/* reserved time (can't be timed out earlier) */
+	Reserved=	5*Min,
+
+	/* packet sizes */
+	Maxudp=		512,	/* maximum bytes per udp message sent */
+	Maxudpin=	2048,	/* maximum bytes per udp message rcv'd */
+
+	/* length of domain name hash table */
+	HTLEN= 		4*1024,
+
+	Maxpath=	128,	/* size of mntpt */
+	Maxlcks=	10,	/* max. query-type locks per domain name */
+	Maxauth=	16,	/* proto for auth */
+	Maxremote=	256,    /* For registry, if you're more popular than this... */
+	Maxmdns=	255,	/* Limits for mDNS messages */
+	Maxdial= 	271,	/* port + trns + !! + host */
+
+	RRmagic=	0xdeadbabe,
+	DNmagic=	0xa110a110,
+
+	/* parallelism: tune; was 32; allow lots */
+	Maxactive=	250,
+
+	/* tune; was 60*1000; keep it short */
+	Maxreqtm=	8*1000,	/* max. ms to process a request */
+
+	Notauthoritative = 0,
+	Authoritative,
+};
+
+typedef struct Area	Area;
+typedef struct Block	Block;
+typedef struct Cert	Cert;
+typedef struct DN	DN;
+typedef struct DNSmsg	DNSmsg;
+typedef struct Key	Key;
+typedef struct Null	Null;
+typedef struct RR	RR;
+typedef struct Request	Request;
+typedef struct SOA	SOA;
+typedef struct Server	Server;
+typedef struct Svc	Svc;
+typedef struct Sig	Sig;
+typedef struct Srv	Srv;
+typedef struct Txt	Txt;
+
+/*
+ *  a structure to track a request and any slave process handling it
+ */
+struct Request
+{
+	int	isslave;	/* pid of slave */
+	uvlong	aborttime;	/* time in ms at which we give up */
+	jmp_buf	mret;		/* where master jumps to after starting a slave */
+	int	id;
+	char	*from;		/* who asked us? */
+	void	*aux;
+};
+
+/*
+ *  a domain name
+ */
+struct DN
+{
+	DN	*next;		/* hash collision list */
+	ulong	magic;
+	char	*name;		/* owner */
+	RR	*rr;		/* resource records off this name */
+	ulong	referenced;	/* time last referenced */
+	ulong	ordinal;
+	ushort	class;		/* RR class */
+	uchar	respcode;	/* response code */
+	uchar	mark;		/* for mark and sweep */
+};
+
+/*
+ *  security info
+ */
+struct Block
+{
+	int	dlen;
+	uchar	*data;
+};
+struct Key
+{
+	int	flags;
+	int	proto;
+	int	alg;
+	Block;
+};
+struct Cert
+{
+	int	type;
+	int	tag;
+	int	alg;
+	Block;
+};
+struct Sig
+{
+	Cert;
+	int	labels;
+	ulong	ttl;
+	ulong	exp;
+	ulong	incep;
+	DN	*signer;
+};
+struct Null
+{
+	Block;
+};
+
+/*
+ *  text strings
+ */
+struct Txt
+{
+	Txt	*next;
+	char	*p;
+};
+
+/*
+ * registry service
+ */
+struct Svc
+{
+	int	id;		/* for server tracking */
+	Svc 	*next;
+	char	labl[Maxmdns];	/* Use mDNS limits */
+	char	host[Maxmdns];	/* host ip */
+	char	mtpt[Maxpath];
+	char	auth[Maxauth];
+	char	trns[16]; 	/* transport tcp/ssh/gopher/carrierpidgeon */
+	char	port[8];	/* 9fs, numerical, etc - switch to #? */
+	uchar	perm; 		/* flag: from db */
+};
+
+/*
+ *  an unpacked resource record
+ */
+struct RR
+{
+	RR	*next;
+	ulong	magic;
+	DN	*owner;		/* domain that owns this resource record */
+	uintptr	pc;		/* for tracking memory allocation */
+	ulong	ttl;		/* time to live to be passed on */
+	ulong	expire;		/* time this entry expires locally */
+	ulong	marker;		/* used locally when scanning rrlists */
+	ushort	type;		/* RR type */
+	ushort	query;		/* query type is in response to */
+	uchar	auth;		/* flag: authoritative */
+	uchar	db;		/* flag: from database */
+	uchar	cached;		/* flag: rr in cache */
+	uchar	negative;	/* flag: this is a cached negative response */
+
+	union {			/* discriminated by negative & type */
+		DN	*negsoaowner;	/* soa for cached negative response */
+		DN	*host;	/* hostname - soa, cname, mb, md, mf, mx, ns, srv */
+		DN	*cpu;	/* cpu type - hinfo */
+		DN	*mb;	/* mailbox - mg, minfo */
+		DN	*ip;	/* ip address - a, aaaa */
+		DN	*rp;	/* rp arg - rp */
+		uintptr	arg0;	/* arg[01] are compared to find dups in dn.c */
+	};
+	union {			/* discriminated by negative & type */
+		int	negrcode; /* response code for cached negative resp. */
+		DN	*rmb;	/* responsible maibox - minfo, soa, rp */
+		DN	*ptr;	/* pointer to domain name - ptr */
+		DN	*os;	/* operating system - hinfo */
+		ulong	pref;	/* preference value - mx */
+		ulong	local;	/* ns served from local database - ns */
+		ushort	port;	/* - srv */
+		uintptr	arg1;	/* arg[01] are compared to find dups in dn.c */
+	};
+	union {			/* discriminated by type */
+		SOA	*soa;	/* soa timers - soa */
+		Srv	*srv;
+		Key	*key;
+		Cert	*cert;
+		Sig	*sig;
+		Null	*null;
+		Txt	*txt;
+	};
+};
+
+/*
+ *  list of servers
+ */
+struct Server
+{
+	Server	*next;
+	char	*name;
+};
+
+/*
+ *  timers for a start-of-authority record.  all ulongs are in seconds.
+ */
+struct SOA
+{
+	ulong	serial;		/* zone serial # */
+	ulong	refresh;	/* zone refresh interval */
+	ulong	retry;		/* zone retry interval */
+	ulong	expire;		/* time to expiration */
+	ulong	minttl;		/* min. time to live for any entry */
+
+	Server	*slaves;	/* slave servers */
+};
+
+/*
+ * srv (service location) record (rfc2782):
+ * _service._proto.name ttl class(IN) 'SRV' priority weight port target
+ */
+struct Srv
+{
+	ushort	pri;
+	ushort	weight;
+};
+
+typedef struct Rrlist Rrlist;
+struct Rrlist
+{
+	int	count;
+	RR	*rrs;
+};
+
+/*
+ *  domain messages
+ */
+struct DNSmsg
+{
+	ushort	id;
+	int	flags;
+	int	qdcount;	/* questions */
+	RR 	*qd;
+	int	ancount;	/* answers */
+	RR	*an;
+	int	nscount;	/* name servers */
+	RR	*ns;
+	int	arcount;	/* hints */
+	RR	*ar;
+};
+
+/*
+ *  definition of local area for dblookup
+ */
+struct Area
+{
+	Area	*next;
+
+	int	len;		/* strlen(area->soarr->owner->name) */
+	RR	*soarr;		/* soa defining this area */
+	int	neednotify;
+	int	needrefresh;
+};
+
+typedef struct Cfg Cfg;
+struct Cfg {
+	int	cachedb;
+	int	resolver;
+	int	justforw;	/* flag: pure resolver, just forward queries */
+	int	serve;		/* flag: serve udp queries */
+	int	inside;
+	int	straddle;
+};
+
+/* (udp) query stats */
+typedef struct {
+	QLock;
+	ulong	slavehiwat;	/* procs */
+	ulong	qrecvd9p;	/* query counts */
+	ulong	qrecvdudp;
+	ulong	qsent;
+	ulong	qrecvd9prpc;	/* packet count */
+	ulong	alarms;
+	/* reply times by count */
+	ulong	under10ths[3*10+2];	/* under n*0.1 seconds, n is index */
+	ulong	tmout;
+	ulong	tmoutcname;
+	ulong	tmoutv6;
+
+	ulong	answinmem;	/* answers in memory */
+	ulong	negans;		/* negative answers received */
+	ulong	negserver;	/* neg ans with Rserver set */
+	ulong	negbaddeleg;	/* neg ans with bad delegations */
+	ulong	negbdnoans;	/* ⋯ and no answers */
+	ulong	negnorname;	/* neg ans with no Rname set */
+	ulong	negcached;	/* neg ans cached */
+} Stats;
+
+Stats stats;
+
+enum
+{
+	Recurse,
+	Dontrecurse,
+	NOneg,
+	OKneg,
+};
+
+extern Cfg	cfg;
+extern char	*dbfile;
+extern int	debug;
+extern Area	*delegated;
+extern char	*logfile;
+extern int	maxage;		/* age of oldest entry in cache (secs) */
+extern char	mntpt[];
+extern int	needrefresh;
+extern int	norecursion;
+extern ulong	now;		/* time base */
+extern vlong	nowns;
+extern Area	*owned;
+extern int	sendnotifies;
+extern ulong	target;
+extern char	*trace;
+extern int	traceactivity;
+extern char	*zonerefreshprogram;
+extern Svc  	*registry;
+extern int 	rfd[Maxremote];
+
+#pragma	varargck	type	"R"	RR*
+#pragma	varargck	type	"Q"	RR*
+#pragma varargck 	type 	"G" 	Svc*
+#pragma varargck	type	"D"	Svc*
+#pragma varargck	type	"N"	Svc*
+
+/* dn.c */
+extern char	*rrtname[];
+extern char	*rname[];
+extern unsigned	nrname;
+extern char	*opname[];
+extern Lock	dnlock;
+
+void	abort(); /* char*, ... */;
+void	addserver(Server**, char*);
+Server*	copyserverlist(Server*);
+void	db2cache(int);
+void	dnage(DN*);
+void	dnageall(int);
+void	dnagedb(void);
+void	dnagenever(DN *);
+void	dnauthdb(void);
+void	dndump(char*);
+void	dninit(void);
+DN*	dnlookup(char*, int, int);
+DN*	idnlookup(char*, int, int);
+DN*	ipalookup(uchar*, int, int);
+void	dnptr(uchar*, uchar*, char*, int, int, int);
+void	dnpurge(void);
+void	dnslog(char*, ...);
+void	dnstats(char *file);
+void*	emalloc(int);
+char*	estrdup(char*);
+void	freeanswers(DNSmsg *mp);
+void	freeserverlist(Server*);
+int	getactivity(Request*, int);
+Area*	inmyarea(char*);
+void	putactivity(int);
+void	reg2cache(void);
+void	regconnect(void);
+char*	rstr2cache(char*, int);
+Svc*	rstr2svc(char*);
+char*	rstrdtch(char*);
+char*	rstrupdt(char*);
+RR*	randomize(RR*);
+void	reglog(char*, ...);
+RR*	rralloc(int);
+void	rrattach(RR*, int);
+int	rravfmt(Fmt*);
+RR*	rrcat(RR**, RR*);
+RR**	rrcopy(RR*, RR**);
+int	rrfmt(Fmt*);
+void	rrfree(RR*);
+void	rrfreelist(RR*);
+RR*	rrlookup(DN*, int, int);
+char*	rrname(int, char*, int);
+RR*	rrremneg(RR**);
+RR*	rrremtype(RR**, int);
+RR*	rrremowner(RR**, DN*);
+RR*	rrremfilter(RR**, int (*)(RR*, void*), void*);
+int	rrsupported(int);
+int	rrtype(char*);
+void	slave(Request*);
+int	subsume(char*, char*);
+int	tsame(int, int);
+void	unique(RR*);
+void	warning(char*, ...);
+
+
+/* dnarea.c */
+void	refresh_areas(Area*);
+void	freearea(Area**);
+void	addarea(DN *dp, RR *rp, Ndbtuple *t);
+
+/* dblookup.c */
+int	baddelegation(RR*, RR*, uchar*);
+RR*	dblookup(char*, int, int, int, int);
+RR*	dnsservers(int);
+RR*	domainlist(int);
+int	insideaddr(char *dom);
+int	insidens(uchar *ip);
+int	myip(uchar *ip);
+int	opendatabase(void);
+int	outsidensip(int, uchar *ip);
+
+/* dns.c */
+char*	walkup(char*);
+RR*	getdnsservers(int);
+void	logreply(int, uchar*, DNSmsg*);
+void	logsend(int, int, uchar*, char*, char*, int);
+
+/* dnresolve.c */
+RR*	dnresolve(char*, int, int, Request*, RR**, int, int, int, int*);
+int	udpport(char *);
+int	mkreq(DN *dp, int type, uchar *buf, int flags, ushort reqno);
+int	seerootns(void);
+void	initdnsmsg(DNSmsg *mp, RR *rp, int flags, ushort reqno);
+
+/* dnserver.c */
+void	dnserver(DNSmsg*, DNSmsg*, Request*, uchar *, int);
+void	dnudpserver(char*);
+
+/* dnnotify.c */
+void	dnnotify(DNSmsg*, DNSmsg*, Request*);
+void	notifyproc(void);
+
+/* reglookup.c */
+int	openregistry(void);
+
+/* convDNS2M.c */
+int	convDNS2M(DNSmsg*, uchar*, int);
+
+/* convM2DNS.c */
+char*	convM2DNS(uchar*, int, DNSmsg*, int*);
+
+#pragma varargck argpos dnslog 1
--- /dev/null
+++ b/dnsdebug.c
@@ -1,0 +1,490 @@
+#include <u.h>
+#include <libc.h>
+#include <bio.h>
+#include <ctype.h>
+#include <ip.h>
+#include <ndb.h>
+#include "dns.h"
+
+enum {
+	Maxrequest=		128,
+};
+
+Cfg cfg;
+
+static char *servername;
+static RR *serveraddrs;
+
+char	*dbfile;
+int	debug;
+char	*logfile = "dnsdebug";
+int	maxage  = 60*60;
+char	mntpt[Maxpath];
+int	needrefresh;
+ulong	now;
+vlong	nowns;
+char	*trace;
+int	traceactivity;
+char	*zonerefreshprogram;
+
+void	docmd(int, char**);
+void	doquery(char*, char*);
+void	preloadserveraddrs(void);
+int	prettyrrfmt(Fmt*);
+int	setserver(char*);
+void	squirrelserveraddrs(void);
+
+void
+usage(void)
+{
+	fprint(2, "%s: [-rx] [-f db-file] [[@server] domain [type]]\n", argv0);
+	exits("usage");
+}
+
+void
+main(int argc, char *argv[])
+{
+	int n;
+	Biobuf in;
+	char *p;
+	char *f[4];
+
+	strcpy(mntpt, "/net");
+	cfg.inside = 1;
+
+	ARGBEGIN{
+	case 'f':
+		dbfile = EARGF(usage());
+		break;
+	case 'r':
+		cfg.resolver = 1;
+		break;
+	case 'x':
+		dbfile = "/lib/ndb/external";
+		strcpy(mntpt, "/net.alt");
+		break;
+	default:
+		usage();
+	}ARGEND
+
+	now = time(nil);
+	nowns = nsec();
+	dninit();
+	fmtinstall('R', prettyrrfmt);
+	opendatabase();
+	srand(truerand());
+
+	if(cfg.resolver)
+		squirrelserveraddrs();
+
+	debug = 1;
+
+	if(argc > 0){
+		docmd(argc, argv);
+		exits(0);
+	}
+
+	Binit(&in, 0, OREAD);
+	for(print("> "); p = Brdline(&in, '\n'); print("> ")){
+		p[Blinelen(&in)-1] = 0;
+		n = tokenize(p, f, 3);
+		if(n>=1) {
+			dnpurge();		/* flush the cache */
+			docmd(n, f);
+		}
+	}
+	exits(0);
+}
+
+static char*
+longtime(long t)
+{
+	int d, h, m, n;
+	static char x[128];
+
+	for(d = 0; t >= 24*60*60; t -= 24*60*60)
+		d++;
+	for(h = 0; t >= 60*60; t -= 60*60)
+		h++;
+	for(m = 0; t >= 60; t -= 60)
+		m++;
+	n = 0;
+	if(d)
+		n += sprint(x, "%d day ", d);
+	if(h)
+		n += sprint(x+n, "%d hr ", h);
+	if(m)
+		n += sprint(x+n, "%d min ", m);
+	if(t || n == 0)
+		sprint(x+n, "%ld sec", t);
+	return x;
+}
+
+/*
+ *  convert address into a reverse lookup address
+ */
+static void
+mkptrname(char *ip, char *rip, int rlen)
+{
+	uchar a[IPaddrlen];
+	char *p, *e;
+	int i;
+
+	if(cistrstr(ip, "in-addr.arpa") || cistrstr(ip, "ip6.arpa") || parseip(a, ip) == -1)
+		snprint(rip, rlen, "%s", ip);
+	else if(isv4(a))
+		snprint(rip, rlen, "%ud.%ud.%ud.%ud.in-addr.arpa",
+			a[15], a[14], a[13], a[12]);
+	else{
+		p = rip;
+		e = rip + rlen;
+		for(i = 15; i >= 0; i--){
+			p = seprint(p, e, "%ux.", a[i]&0xf);
+			p = seprint(p, e, "%ux.", a[i]>>4);
+		}
+		seprint(p, e, "ip6.arpa");
+	}
+}
+
+int
+prettyrrfmt(Fmt *f)
+{
+	RR *rp;
+	char buf[3*Domlen];
+	char *p, *e;
+	Txt *t;
+
+	rp = va_arg(f->args, RR*);
+	if(rp == 0){
+		strcpy(buf, "<null>");
+		goto out;
+	}
+
+	p = buf;
+	e = buf + sizeof(buf);
+	p = seprint(p, e, "%-32.32s %-15.15s %-5.5s", rp->owner->name,
+		longtime(rp->ttl),
+		rrname(rp->type, buf, sizeof buf));
+
+	if(rp->negative){
+		seprint(p, e, "negative rcode %d", rp->negrcode);
+		goto out;
+	}
+
+	switch(rp->type){
+	case Thinfo:
+		seprint(p, e, "\t%s %s", rp->cpu->name, rp->os->name);
+		break;
+	case Tcname:
+	case Tmb:
+	case Tmd:
+	case Tmf:
+	case Tns:
+		seprint(p, e, "\t%s", (rp->host? rp->host->name: ""));
+		break;
+	case Tmg:
+	case Tmr:
+		seprint(p, e, "\t%s", (rp->mb? rp->mb->name: ""));
+		break;
+	case Tminfo:
+		seprint(p, e, "\t%s %s", (rp->mb? rp->mb->name: ""),
+			(rp->rmb? rp->rmb->name: ""));
+		break;
+	case Tmx:
+		seprint(p, e, "\t%lud %s", rp->pref,
+			(rp->host? rp->host->name: ""));
+		break;
+	case Ta:
+	case Taaaa:
+		seprint(p, e, "\t%s", (rp->ip? rp->ip->name: ""));
+		break;
+	case Tptr:
+		seprint(p, e, "\t%s", (rp->ptr? rp->ptr->name: ""));
+		break;
+	case Tsoa:
+		seprint(p, e, "\t%s %s %lud %lud %lud %lud %lud",
+			rp->host->name, rp->rmb->name, rp->soa->serial,
+			rp->soa->refresh, rp->soa->retry,
+			rp->soa->expire, rp->soa->minttl);
+		break;
+	case Tsrv:
+		seprint(p, e, "\t%ud %ud %ud %s",
+			rp->srv->pri, rp->srv->weight, rp->port, rp->host->name);
+		break;
+	case Tnull:
+		seprint(p, e, "\t%.*H", rp->null->dlen, rp->null->data);
+		break;
+	case Ttxt:
+		p = seprint(p, e, "\t");
+		for(t = rp->txt; t != nil; t = t->next)
+			p = seprint(p, e, "%s", t->p);
+		break;
+	case Trp:
+		seprint(p, e, "\t%s %s", rp->rmb->name, rp->rp->name);
+		break;
+	case Tkey:
+		seprint(p, e, "\t%d %d %d", rp->key->flags, rp->key->proto,
+			rp->key->alg);
+		break;
+	case Tsig:
+		seprint(p, e, "\t%d %d %d %lud %lud %lud %d %s",
+			rp->sig->type, rp->sig->alg, rp->sig->labels,
+			rp->sig->ttl, rp->sig->exp, rp->sig->incep,
+			rp->sig->tag, rp->sig->signer->name);
+		break;
+	case Tcert:
+		seprint(p, e, "\t%d %d %d",
+			rp->sig->type, rp->sig->tag, rp->sig->alg);
+		break;
+	}
+out:
+	return fmtstrcpy(f, buf);
+}
+
+void
+logsection(char *flag, RR *rp)
+{
+	if(rp == nil)
+		return;
+	print("\t%s%R\n", flag, rp);
+	for(rp = rp->next; rp != nil; rp = rp->next)
+		print("\t      %R\n", rp);
+}
+
+void
+logreply(int id, uchar *addr, DNSmsg *mp)
+{
+	RR *rp;
+	char buf[12], resp[32];
+
+	switch(mp->flags & Rmask){
+	case Rok:
+		strcpy(resp, "OK");
+		break;
+	case Rformat:
+		strcpy(resp, "Format error");
+		break;
+	case Rserver:
+		strcpy(resp, "Server failed");
+		break;
+	case Rname:
+		strcpy(resp, "Nonexistent");
+		break;
+	case Runimplimented:
+		strcpy(resp, "Unimplemented");
+		break;
+	case Rrefused:
+		strcpy(resp, "Refused");
+		break;
+	default:
+		sprint(resp, "%d", mp->flags & Rmask);
+		break;
+	}
+
+	print("%d: rcvd %s from %I (%s%s%s%s%s)\n", id, resp, addr,
+		mp->flags & Fauth? "authoritative": "",
+		mp->flags & Ftrunc? " truncated": "",
+		mp->flags & Frecurse? " recurse": "",
+		mp->flags & Fcanrec? " can_recurse": "",
+		(mp->flags & (Fauth|Rmask)) == (Fauth|Rname)? " nx": "");
+	for(rp = mp->qd; rp != nil; rp = rp->next)
+		print("\tQ:    %s %s\n", rp->owner->name,
+			rrname(rp->type, buf, sizeof buf));
+	logsection("Ans:  ", mp->an);
+	logsection("Auth: ", mp->ns);
+	logsection("Hint: ", mp->ar);
+}
+
+void
+logsend(int id, int subid, uchar *addr, char *sname, char *rname, int type)
+{
+	char buf[12];
+
+	print("%d.%d: sending to %I/%s %s %s\n", id, subid,
+		addr, sname, rname, rrname(type, buf, sizeof buf));
+}
+
+RR*
+getdnsservers(int class)
+{
+	RR *rr;
+
+	if(servername == nil)
+		return dnsservers(class);
+
+	rr = rralloc(Tns);
+	rr->owner = dnlookup("local#dns#servers", class, 1);
+	rr->host = idnlookup(servername, class, 1);
+
+	return rr;
+}
+
+void
+squirrelserveraddrs(void)
+{
+	int v4;
+	char *attr;
+	RR *rr, *rp, **l;
+	Request req;
+
+	/* look up the resolver address first */
+	cfg.resolver = 0;
+	debug = 0;
+	if(serveraddrs){
+		rrfreelist(serveraddrs);
+		serveraddrs = nil;
+	}
+	rr = getdnsservers(Cin);
+	l = &serveraddrs;
+	for(rp = rr; rp != nil; rp = rp->next){
+		attr = ipattr(rp->host->name);
+		v4 = strcmp(attr, "ip") == 0;
+		if(v4 || strcmp(attr, "ipv6") == 0){
+			*l = rralloc(v4? Ta: Taaaa);
+			(*l)->owner = rp->host;
+			(*l)->ip = rp->host;
+			l = &(*l)->next;
+			continue;
+		}
+		memset(&req, 0, sizeof req);
+		req.isslave = 1;
+		req.aborttime = NS2MS(nowns) + Maxreqtm;
+		*l = dnresolve(rp->host->name, Cin, Ta, &req, nil, 0, Recurse, 0, nil);
+		if(*l == nil)
+			*l = dnresolve(rp->host->name, Cin, Taaaa, &req,
+				nil, 0, Recurse, 0, nil);
+		while(*l != nil)
+			l = &(*l)->next;
+	}
+	cfg.resolver = 1;
+	debug = 1;
+}
+
+void
+preloadserveraddrs(void)
+{
+	RR *rp, **l, *first;
+
+	first = nil;
+	l = &first;
+	for(rp = serveraddrs; rp != nil; rp = rp->next){
+		rrcopy(rp, l);
+		rrattach(first, Authoritative);
+	}
+}
+
+int
+setserver(char *server)
+{
+	if(servername != nil){
+		free(servername);
+		servername = nil;
+		cfg.resolver = 0;
+	}
+	if(server == nil || *server == 0)
+		return 0;
+	servername = strdup(server);
+	squirrelserveraddrs();
+	if(serveraddrs == nil){
+		print("can't resolve %s\n", servername);
+		cfg.resolver = 0;
+	} else
+		cfg.resolver = 1;
+	return cfg.resolver? 0: -1;
+}
+
+void
+doquery(char *name, char *tstr)
+{
+	int len, type, rooted;
+	char buf[1024];
+	RR *rr, *rp;
+	Request req;
+
+	if(cfg.resolver)
+		preloadserveraddrs();
+
+	/* default to an "ip" request if alpha, "ptr" if numeric */
+	if(tstr == nil || *tstr == 0)
+		if(strcmp(ipattr(name), "ip") == 0)
+			tstr = "ptr";
+		else
+			tstr = "ip";
+
+	/* look it up */
+	type = rrtype(tstr);
+	if(type < 0){
+		print("!unknown type %s\n", tstr);
+		return;
+	}
+
+	/* if name end in '.', remove it */
+	len = strlen(name);
+	if(len > 0 && name[len-1] == '.'){
+		rooted = 1;
+		name[len-1] = 0;
+	} else
+		rooted = 0;
+
+	/* inverse queries may need to be permuted */
+	if(type == Tptr)
+		mkptrname(name, buf, sizeof buf);
+	else
+		strncpy(buf, name, sizeof buf);
+
+	memset(&req, 0, sizeof req);
+	getactivity(&req, 0);
+	req.isslave = 1;
+	req.aborttime = NS2MS(nowns) + Maxreqtm;
+	rr = dnresolve(buf, Cin, type, &req, nil, 0, Recurse, rooted, nil);
+	if(rr){
+		print("----------------------------\n");
+		for(rp = rr; rp; rp = rp->next)
+			print("answer %R\n", rp);
+		print("----------------------------\n");
+	}
+	rrfreelist(rr);
+
+	putactivity(0);
+}
+
+void
+docmd(int n, char **f)
+{
+	int tmpsrv;
+	char *name, *type;
+
+	name = type = nil;
+	tmpsrv = 0;
+
+	if(*f[0] == '@') {
+		if(setserver(f[0]+1) < 0)
+			return;
+
+		switch(n){
+		case 3:
+			type = f[2];
+			/* fall through */
+		case 2:
+			name = f[1];
+			tmpsrv = 1;
+			break;
+		}
+	} else
+		switch(n){
+		case 2:
+			type = f[1];
+			/* fall through */
+		case 1:
+			name = f[0];
+			break;
+		}
+
+	if(name == nil)
+		return;
+
+	doquery(name, type);
+
+	if(tmpsrv)
+		setserver("");
+}
--- /dev/null
+++ b/dnserver.c
@@ -1,0 +1,212 @@
+#include <u.h>
+#include <libc.h>
+#include <ip.h>
+#include "dns.h"
+
+static RR*	doextquery(DNSmsg*, Request*, int);
+static void	hint(RR**, RR*);
+
+/* set in dns.c */
+int	norecursion;		/* don't allow recursive requests */
+
+/*
+ *  answer a dns request
+ */
+void
+dnserver(DNSmsg *reqp, DNSmsg *repp, Request *req, uchar *srcip, int rcode)
+{
+	int recursionflag;
+	char *cp, *errmsg;
+	char tname[32];
+	DN *nsdp, *dp;
+	Area *myarea;
+	RR *tp, *neg, *rp;
+
+	recursionflag = norecursion? 0: Fcanrec;
+	memset(repp, 0, sizeof(*repp));
+	repp->id = reqp->id;
+	repp->flags = Fresp | recursionflag | Oquery;
+
+	/* move one question from reqp to repp */
+	tp = reqp->qd;
+	reqp->qd = tp->next;
+	tp->next = nil;
+	repp->qd = tp;
+
+	if (rcode) {
+		errmsg = "";
+		if (rcode >= 0 && rcode < nrname)
+			errmsg = rname[rcode];
+		dnslog("server: response code 0%o (%s), req from %I",
+			rcode, errmsg, srcip);
+		/* provide feedback to clients who send us trash */
+		repp->flags = (rcode&Rmask) | Fresp | Fcanrec | Oquery;
+		return;
+	}
+	if(!rrsupported(repp->qd->type)){
+		dnslog("server: unsupported request %s from %I",
+			rrname(repp->qd->type, tname, sizeof tname), srcip);
+		repp->flags = Runimplimented | Fresp | Fcanrec | Oquery;
+		return;
+	}
+
+	if(repp->qd->owner->class != Cin){
+		dnslog("server: unsupported class %d from %I",
+			repp->qd->owner->class, srcip);
+		repp->flags = Runimplimented | Fresp | Fcanrec | Oquery;
+		return;
+	}
+
+	myarea = inmyarea(repp->qd->owner->name);
+	if(myarea != nil) {
+		if(repp->qd->type == Tixfr || repp->qd->type == Taxfr){
+			dnslog("server: unsupported xfr request %s for %s from %I",
+				rrname(repp->qd->type, tname, sizeof tname),
+				repp->qd->owner->name, srcip);
+			repp->flags = Runimplimented | Fresp | recursionflag |
+				Oquery;
+			return;
+		}
+	}
+	if(myarea == nil && norecursion) {
+		/* we don't recurse and we're not authoritative */
+		repp->flags = Rok | Fresp | Oquery;
+		neg = nil;
+	} else {
+		/*
+		 *  get the answer if we can, in *repp
+		 */
+		if(reqp->flags & Frecurse)
+			neg = doextquery(repp, req, Recurse);
+		else
+			neg = doextquery(repp, req, Dontrecurse);
+
+		/* authority is transitive */
+		if(myarea != nil || (repp->an && repp->an->auth))
+			repp->flags |= Fauth;
+
+		/* pass on error codes */
+		if(repp->an == nil){
+			dp = dnlookup(repp->qd->owner->name, repp->qd->owner->class, 0);
+			if(dp->rr == nil)
+				if(reqp->flags & Frecurse)
+					repp->flags |= dp->respcode | Fauth;
+		}
+	}
+
+	if(myarea == nil){
+		/*
+		 *  add name server if we know
+		 */
+		for(cp = repp->qd->owner->name; cp; cp = walkup(cp)){
+			nsdp = dnlookup(cp, repp->qd->owner->class, 0);
+			if(nsdp == nil)
+				continue;
+
+			repp->ns = rrlookup(nsdp, Tns, OKneg);
+			if(repp->ns){
+				/* don't pass on anything we know is wrong */
+				if(repp->ns->negative){
+					rp = repp->ns;
+					repp->ns = nil;
+					rrfreelist(rp);
+				}
+				break;
+			}
+
+			if (strncmp(nsdp->name, "local#", 6) == 0)
+				dnslog("returning %s as nameserver", nsdp->name);
+			repp->ns = dblookup(cp, repp->qd->owner->class, Tns, 0, 0);
+			if(repp->ns)
+				break;
+		}
+	}
+
+	/*
+	 *  add ip addresses as hints
+	 */
+	if(repp->qd->type != Taxfr && repp->qd->type != Tixfr){
+		for(tp = repp->ns; tp; tp = tp->next)
+			hint(&repp->ar, tp);
+		for(tp = repp->an; tp; tp = tp->next)
+			hint(&repp->ar, tp);
+	}
+
+	/*
+	 *  add an soa to the authority section to help client
+	 *  with negative caching
+	 */
+	if(repp->an == nil){
+		if(myarea != nil){
+			rrcopy(myarea->soarr, &tp);
+			rrcat(&repp->ns, tp);
+		} else if(neg != nil) {
+			if(neg->negsoaowner != nil) {
+				tp = rrlookup(neg->negsoaowner, Tsoa, NOneg);
+				rrcat(&repp->ns, tp);
+			}
+			repp->flags |= neg->negrcode;
+		}
+	}
+
+	/*
+	 *  get rid of duplicates
+	 */
+	unique(repp->an);
+	unique(repp->ns);
+	unique(repp->ar);
+
+	rrfreelist(neg);
+}
+
+/*
+ *  satisfy a recursive request.  dnlookup will handle cnames.
+ */
+static RR*
+doextquery(DNSmsg *mp, Request *req, int recurse)
+{
+	ushort type;
+	char *name;
+	RR *rp, *neg;
+
+	name = mp->qd->owner->name;
+	type = mp->qd->type;
+	rp = dnresolve(name, Cin, type, req, &mp->an, 0, recurse, 1, nil);
+
+	/* don't return soa hints as answers, it's wrong */
+	if(rp && rp->db && !rp->auth && rp->type == Tsoa) {
+		rrfreelist(rp);
+		rp = nil;
+	}
+
+	/* don't let negative cached entries escape */
+	neg = rrremneg(&rp);
+	rrcat(&mp->an, rp);
+
+	return neg;
+}
+
+static void
+hint(RR **last, RR *rp)
+{
+	RR *hp;
+
+	switch(rp->type){
+	case Tns:
+	case Tmx:
+	case Tmb:
+	case Tmf:
+	case Tmd:
+		hp = rrlookup(rp->host, Ta, NOneg);
+		if(hp == nil)
+			hp = dblookup(rp->host->name, Cin, Ta, 0, 0);
+		if(hp == nil)
+			hp = rrlookup(rp->host, Taaaa, NOneg);
+		if(hp == nil)
+			hp = dblookup(rp->host->name, Cin, Taaaa, 0, 0);
+		if (hp && strncmp(hp->owner->name, "local#", 6) == 0)
+			dnslog("returning %s as hint", hp->owner->name);
+		rrcat(last, hp);
+		break;
+	}
+}
--- /dev/null
+++ b/dnsgetip.c
@@ -1,0 +1,124 @@
+/* one-shot resolver */
+#include <u.h>
+#include <libc.h>
+#include <bio.h>
+#include <ndb.h>
+#include "dns.h"
+
+Cfg cfg;
+char *dbfile;
+int debug		= 0;
+char *logfile		= "dnsgetip";
+int	maxage		= 60*60;
+char mntpt[Maxpath];
+int	needrefresh	= 0;
+ulong	now		= 0;
+vlong	nowns		= 0;
+int	traceactivity	= 0;
+char	*zonerefreshprogram;
+
+int aflag = 0;
+int addresses = 0;
+
+char Ebotch[] = "dns botch";
+
+char*
+resolve(char *name, int type)
+{
+	int status;
+	char *errmsg;
+	Request req;
+	RR *rr, *rp, *neg;
+
+	status = Rok;
+	errmsg = nil;
+
+	memset(&req, 0, sizeof req);
+	getactivity(&req, 0);
+	req.isslave = 1;
+	req.aborttime = NS2MS(nowns) + Maxreqtm;
+
+	rr = dnresolve(name, Cin, type, &req, nil, 0, Recurse, 0, &status);
+	neg = rrremneg(&rr);
+	if(rr == nil || neg != nil){
+		if(neg != nil)
+			status = neg->negrcode;
+		errmsg = Ebotch;
+		if(status > 0 && status < nrname)
+			errmsg = rname[status];
+	}
+
+	rrfreelist(neg);
+
+	for(rp = rr; rp != nil; rp = rp->next){
+		print("%s\n", rp->ip->name);
+		addresses++;
+		if(!aflag)
+			exits(nil);
+	}
+
+	rrfreelist(rr);
+
+	return errmsg;
+}
+
+void
+usage(void)
+{
+	fprint(2, "%s: [-adx] domain\n", argv0);
+	exits("usage");
+}
+
+void
+main(int argc, char **argv)
+{
+	char *e4, *e6;
+
+	strcpy(mntpt, "/net");
+	cfg.inside = 1;
+	cfg.resolver = 1;
+
+	ARGBEGIN{
+	case 'a':
+		aflag = 1;
+		break;
+	case 'd':
+		debug++;
+		break;
+	case 'x':
+		strcpy(mntpt, "/net.alt");
+		break;
+	default:
+		usage();
+	}ARGEND
+
+	if(argc != 1)
+		usage();
+
+	if(strcmp(ipattr(*argv), "ip") == 0)
+		print("%s\n", *argv);
+	else {
+		dninit();
+		e4 = resolve(*argv, Ta);
+		e6 = resolve(*argv, Taaaa);
+
+		if(addresses == 0){
+			if(e4 == e6)
+				sysfatal("%s: dns failure: %s", *argv, e4);
+
+			sysfatal("%s: dns failure: v4: %s: v6: %s", *argv, e4, e6);
+		}
+	}
+	exits(nil);
+}
+
+RR*
+getdnsservers(int class)
+{
+	return dnsservers(class);
+}
+
+/* stubs */
+void syslog(int, char*, char*, ...){}
+void logreply(int, uchar*, DNSmsg*){}
+void logsend(int, int, uchar*, char*, char*, int){}
--- /dev/null
+++ b/dnsquery.c
@@ -1,0 +1,119 @@
+#include <u.h>
+#include <libc.h>
+#include <bio.h>
+#include <ctype.h>
+#include <ndb.h>
+#include "dns.h"
+#include "ip.h"
+
+static void
+querydns(int fd, char *line, int n)
+{
+	char buf[8192+1];
+
+	seek(fd, 0, 0);
+	if(write(fd, line, n) != n) {
+		print("!%r\n");
+		return;
+	}
+	seek(fd, 0, 0);
+	while((n = read(fd, buf, sizeof(buf)-1)) > 0){
+		buf[n++] = '\n';
+		write(1, buf, n);
+	}
+}
+
+/*
+ *  convert address into a reverse lookup address
+ */
+static void
+mkptrname(char *ip, char *rip, int rlen)
+{
+	uchar a[IPaddrlen];
+	char *p, *e;
+	int i;
+
+	if(cistrstr(ip, "in-addr.arpa") || cistrstr(ip, "ip6.arpa") || parseip(a, ip) == -1)
+		snprint(rip, rlen, "%s", ip);
+	else if(isv4(a))
+		snprint(rip, rlen, "%ud.%ud.%ud.%ud.in-addr.arpa",
+			a[15], a[14], a[13], a[12]);
+	else{
+		p = rip;
+		e = rip + rlen;
+		for(i = 15; i >= 0; i--){
+			p = seprint(p, e, "%ux.", a[i]&0xf);
+			p = seprint(p, e, "%ux.", a[i]>>4);
+		}
+		seprint(p, e, "ip6.arpa");
+	}
+}
+
+static void
+query(int fd)
+{
+	int n;
+	char *lp;
+	char buf[1024], line[1024];
+	Biobuf in;
+
+	Binit(&in, 0, OREAD);
+	for(fprint(2, "> "); lp = Brdline(&in, '\n'); fprint(2, "> ")){
+		n = Blinelen(&in) -1;
+		while(isspace(lp[n]))
+			lp[n--] = 0;
+		n++;
+		while(isspace(*lp)){
+			lp++;
+			n--;
+		}
+		if(!*lp)
+			continue;
+		strcpy(line, lp);
+
+		/* default to an "ip" request if alpha, "ptr" if numeric */
+		if(strchr(line, ' ') == nil)
+			if(strcmp(ipattr(line), "ip") == 0) {
+				strcat(line, " ptr");
+				n += 4;
+			} else {
+				strcat(line, " ip");
+				n += 3;
+			}
+
+		if(n > 4 && strcmp(" ptr", &line[n-4]) == 0){
+			line[n-4] = 0;
+			mkptrname(line, buf, sizeof buf);
+			n = snprint(line, sizeof line, "%s ptr", buf);
+		}
+
+		querydns(fd, line, n);
+	}
+	Bterm(&in);
+}
+
+void
+main(int argc, char *argv[])
+{
+	char *dns  = "/net/dns";
+	int fd;
+
+	ARGBEGIN {
+	case 'x':
+		dns = "/net.alt/dns";
+		break;
+	default:
+		fprint(2, "usage: %s [-x] [/net/dns]\n", argv0);
+		exits("usage");
+	} ARGEND;
+
+	if(argc > 0)
+		dns = argv[0];
+
+	fd = open(dns, ORDWR);
+	if(fd < 0)
+		sysfatal("can't open %s: %r", dns);
+
+	query(fd);
+	exits(0);
+}
--- /dev/null
+++ b/dnstcp.c
@@ -1,0 +1,434 @@
+/*
+ * dnstcp - serve dns via tcp
+ */
+#include <u.h>
+#include <libc.h>
+#include <bio.h>
+#include <ndb.h>
+#include <ip.h>
+#include "dns.h"
+
+Cfg cfg;
+
+char	*caller = "";
+char	*dbfile;
+int	anyone;
+int	debug;
+char	*logfile = "dns";
+int	maxage = 60*60;
+char	mntpt[Maxpath];
+int	needrefresh;
+ulong	now;
+vlong	nowns;
+int	traceactivity;
+char	*zonerefreshprogram;
+
+static int	readmsg(int, uchar*, int);
+static void	reply(int, DNSmsg*, Request*);
+static void	dnzone(DNSmsg*, DNSmsg*, Request*, uchar*);
+static void	getcaller(char*);
+static void	refreshmain(char*);
+
+void
+usage(void)
+{
+	fprint(2, "usage: %s [-adrR] [-f ndbfile] [-x netmtpt] [conndir]\n", argv0);
+	exits("usage");
+}
+
+void
+main(int argc, char *argv[])
+{
+	volatile int len, rcode;
+	volatile char tname[32];
+	char *volatile err, *volatile ext = "";
+	volatile uchar buf[64*1024], callip[IPaddrlen];
+	volatile DNSmsg reqmsg, repmsg;
+	volatile Request req;
+
+	cfg.cachedb = 1;
+	ARGBEGIN{
+	case 'a':
+		anyone++;
+		break;
+	case 'd':
+		debug++;
+		break;
+	case 'f':
+		dbfile = EARGF(usage());
+		break;
+	case 'r':
+		cfg.resolver = 1;
+		break;
+	case 'R':
+		norecursion = 1;
+		break;
+	case 'x':
+		ext = EARGF(usage());
+		break;
+	default:
+		usage();
+		break;
+	}ARGEND
+
+	if(argc > 0)
+		getcaller(argv[0]);
+
+	cfg.inside = 1;
+	dninit();
+
+	if(*ext == '/')
+		snprint(mntpt, sizeof mntpt, "%s", ext);
+	else
+		snprint(mntpt, sizeof mntpt, "/net%s", ext);
+
+	dnslog("dnstcp call from %s", caller);
+	memset(callip, 0, sizeof callip);
+	parseip(callip, caller);
+
+	srand(truerand());
+	db2cache(1);
+
+	memset(&req, 0, sizeof req);
+	setjmp(req.mret);
+	req.isslave = 0;
+	procsetname("main loop");
+
+	alarm(10*1000);
+
+	/* loop on requests */
+	for(;; putactivity(0)){
+		now = time(nil);
+		memset(&repmsg, 0, sizeof repmsg);
+		len = readmsg(0, buf, sizeof buf);
+		if(len <= 0)
+			break;
+
+		getactivity(&req, 0);
+		req.aborttime = timems() + S2MS(15*Min);
+		rcode = 0;
+		memset(&reqmsg, 0, sizeof reqmsg);
+		err = convM2DNS(buf, len, &reqmsg, &rcode);
+		if(err){
+			dnslog("server: input error: %s from %s", err, caller);
+			free(err);
+			break;
+		}
+		if (rcode == 0)
+			if(reqmsg.qdcount < 1){
+				dnslog("server: no questions from %s", caller);
+				break;
+			} else if(reqmsg.flags & Fresp){
+				dnslog("server: reply not request from %s",
+					caller);
+				break;
+			} else if((reqmsg.flags & Omask) != Oquery){
+				dnslog("server: op %d from %s",
+					reqmsg.flags & Omask, caller);
+				break;
+			}
+
+		if(reqmsg.qd == nil){
+			dnslog("server: no question RR from %s", caller);
+			break;
+		}
+
+		if(debug)
+			dnslog("[%d] %d: serve (%s) %d %s %s",
+				getpid(), req.id, caller,
+				reqmsg.id, reqmsg.qd->owner->name,
+				rrname(reqmsg.qd->type, tname, sizeof tname));
+
+		/* loop through each question */
+		while(reqmsg.qd)
+			if(reqmsg.qd->type == Taxfr)
+				dnzone(&reqmsg, &repmsg, &req, callip);
+			else {
+				dnserver(&reqmsg, &repmsg, &req, callip, rcode);
+				reply(1, &repmsg, &req);
+				rrfreelist(repmsg.qd);
+				rrfreelist(repmsg.an);
+				rrfreelist(repmsg.ns);
+				rrfreelist(repmsg.ar);
+			}
+		rrfreelist(reqmsg.qd);		/* qd will be nil */
+		rrfreelist(reqmsg.an);
+		rrfreelist(reqmsg.ns);
+		rrfreelist(reqmsg.ar);
+
+		if(req.isslave){
+			putactivity(0);
+			_exits(0);
+		}
+	}
+	refreshmain(mntpt);
+}
+
+static int
+readmsg(int fd, uchar *buf, int max)
+{
+	int n;
+	uchar x[2];
+
+	if(readn(fd, x, 2) != 2)
+		return -1;
+	n = x[0]<<8 | x[1];
+	if(n > max)
+		return -1;
+	if(readn(fd, buf, n) != n)
+		return -1;
+	return n;
+}
+
+static void
+reply(int fd, DNSmsg *rep, Request *req)
+{
+	int len, rv;
+	char tname[32];
+	uchar buf[64*1024];
+	RR *rp;
+
+	if(debug){
+		dnslog("%d: reply (%s) %s %s %ux",
+			req->id, caller,
+			rep->qd->owner->name,
+			rrname(rep->qd->type, tname, sizeof tname),
+			rep->flags);
+		for(rp = rep->an; rp; rp = rp->next)
+			dnslog("an %R", rp);
+		for(rp = rep->ns; rp; rp = rp->next)
+			dnslog("ns %R", rp);
+		for(rp = rep->ar; rp; rp = rp->next)
+			dnslog("ar %R", rp);
+	}
+
+
+	len = convDNS2M(rep, buf+2, sizeof(buf) - 2);
+	buf[0] = len>>8;
+	buf[1] = len;
+	rv = write(fd, buf, len+2);
+	if(rv != len+2){
+		dnslog("[%d] sending reply: %d instead of %d", getpid(), rv,
+			len+2);
+		exits(0);
+	}
+}
+
+/*
+ *  Hash table for domain names.  The hash is based only on the
+ *  first element of the domain name.
+ */
+extern DN	*ht[HTLEN];
+
+static int
+numelem(char *name)
+{
+	int i;
+
+	i = 1;
+	for(; *name; name++)
+		if(*name == '.')
+			i++;
+	return i;
+}
+
+int
+inzone(DN *dp, char *name, int namelen, int depth)
+{
+	int n;
+
+	if(dp->name == nil)
+		return 0;
+	if(numelem(dp->name) != depth)
+		return 0;
+	n = strlen(dp->name);
+	if(n < namelen)
+		return 0;
+	if(cistrcmp(name, dp->name + n - namelen) != 0)
+		return 0;
+	if(n > namelen && dp->name[n - namelen - 1] != '.')
+		return 0;
+	return 1;
+}
+
+static Server*
+findserver(uchar *srcip, Server *servers, Request *req)
+{
+	uchar ip[IPaddrlen];
+	RR *list, *rp;
+	int tmp;
+
+	for(; servers != nil; servers = servers->next){
+		if(strcmp(ipattr(servers->name), "ip") == 0){
+			if(parseip(ip, servers->name) == -1)
+				continue;
+			if(ipcmp(srcip, ip) == 0)
+				return servers;
+			continue;
+		}
+
+		tmp = cfg.resolver;
+		cfg.resolver = 1;
+		list = dnresolve(servers->name, Cin, isv4(srcip)? Ta: Taaaa,
+			req, nil, 0, Recurse, 0, nil);
+		cfg.resolver = tmp;
+
+		for(rp = list; rp != nil; rp = rp->next){
+			if(parseip(ip, rp->ip->name) == -1)
+				continue;
+			if(ipcmp(srcip, ip) == 0)
+				break;
+		}
+		rrfreelist(list);
+		if(rp != nil)
+			return servers;
+	}
+	return nil;
+}
+
+static void
+dnzone(DNSmsg *reqp, DNSmsg *repp, Request *req, uchar *srcip)
+{
+	DN *dp, *ndp;
+	RR r, *rp;
+	int h, depth, found, nlen;
+
+	memset(repp, 0, sizeof(*repp));
+	repp->id = reqp->id;
+	repp->qd = reqp->qd;
+	reqp->qd = reqp->qd->next;
+	repp->qd->next = 0;
+	repp->flags = Fauth | Fresp | Oquery;
+	if(!norecursion)
+		repp->flags |= Fcanrec;
+	dp = repp->qd->owner;
+
+	/* send the soa */
+	repp->an = rrlookup(dp, Tsoa, NOneg);
+	if(repp->an != nil && !anyone && !myip(srcip)
+	&& findserver(srcip, repp->an->soa->slaves, req) == nil){
+		dnslog("dnstcp: %I axfr %s - not a dnsslave", srcip, dp->name);
+		rrfreelist(repp->an);
+		repp->an = nil;
+	}
+	reply(1, repp, req);
+	if(repp->an == nil)
+		goto out;
+	rrfreelist(repp->an);
+	repp->an = nil;
+
+	nlen = strlen(dp->name);
+
+	/* construct a breadth-first search of the name space (hard with a hash) */
+	repp->an = &r;
+	for(depth = numelem(dp->name); ; depth++){
+		found = 0;
+		for(h = 0; h < HTLEN; h++)
+			for(ndp = ht[h]; ndp; ndp = ndp->next)
+				if(inzone(ndp, dp->name, nlen, depth)){
+					for(rp = ndp->rr; rp; rp = rp->next){
+						/*
+						 * there shouldn't be negatives,
+						 * but just in case.
+						 * don't send any soa's,
+						 * ns's are enough.
+						 */
+						if (rp->negative ||
+						    rp->type == Tsoa)
+							continue;
+						r = *rp;
+						r.next = 0;
+						reply(1, repp, req);
+					}
+					found = 1;
+				}
+		if(!found)
+			break;
+	}
+
+	/* resend the soa */
+	repp->an = rrlookup(dp, Tsoa, NOneg);
+	reply(1, repp, req);
+	rrfreelist(repp->an);
+	repp->an = nil;
+out:
+	rrfree(repp->qd);
+	repp->qd = nil;
+}
+
+static void
+getcaller(char *dir)
+{
+	int fd, n;
+	static char remote[128];
+
+	snprint(remote, sizeof(remote), "%s/remote", dir);
+	fd = open(remote, OREAD);
+	if(fd < 0)
+		return;
+	n = read(fd, remote, sizeof remote - 1);
+	close(fd);
+	if(n <= 0)
+		return;
+	if(remote[n-1] == '\n')
+		n--;
+	remote[n] = 0;
+	caller = remote;
+}
+
+static void
+refreshmain(char *net)
+{
+	int fd;
+	char file[128];
+
+	snprint(file, sizeof(file), "%s/dns", net);
+	if(debug)
+		dnslog("refreshing %s", file);
+	fd = open(file, ORDWR);
+	if(fd < 0)
+		dnslog("can't refresh %s", file);
+	else {
+		fprint(fd, "refresh");
+		close(fd);
+	}
+}
+
+/*
+ *  the following varies between dnsdebug and dns
+ */
+void
+logreply(int id, uchar *addr, DNSmsg *mp)
+{
+	RR *rp;
+
+	dnslog("%d: rcvd %I flags:%s%s%s%s%s", id, addr,
+		mp->flags & Fauth? " auth": "",
+		mp->flags & Ftrunc? " trunc": "",
+		mp->flags & Frecurse? " rd": "",
+		mp->flags & Fcanrec? " ra": "",
+		(mp->flags & (Fauth|Rmask)) == (Fauth|Rname)? " nx": "");
+	for(rp = mp->qd; rp != nil; rp = rp->next)
+		dnslog("%d: rcvd %I qd %s", id, addr, rp->owner->name);
+	for(rp = mp->an; rp != nil; rp = rp->next)
+		dnslog("%d: rcvd %I an %R", id, addr, rp);
+	for(rp = mp->ns; rp != nil; rp = rp->next)
+		dnslog("%d: rcvd %I ns %R", id, addr, rp);
+	for(rp = mp->ar; rp != nil; rp = rp->next)
+		dnslog("%d: rcvd %I ar %R", id, addr, rp);
+}
+
+void
+logsend(int id, int subid, uchar *addr, char *sname, char *rname, int type)
+{
+	char buf[12];
+
+	dnslog("%d.%d: sending to %I/%s %s %s",
+		id, subid, addr, sname, rname, rrname(type, buf, sizeof buf));
+}
+
+RR*
+getdnsservers(int class)
+{
+	return dnsservers(class);
+}
--- /dev/null
+++ b/dnudpserver.c
@@ -1,0 +1,336 @@
+#include <u.h>
+#include <libc.h>
+#include <ip.h>
+#include "dns.h"
+
+enum {
+	Logqueries = 0,
+};
+
+static int	udpannounce(char*);
+static void	reply(int, uchar*, DNSmsg*, Request*);
+
+typedef struct Inprogress Inprogress;
+struct Inprogress
+{
+	int	inuse;
+	Udphdr	uh;
+	DN	*owner;
+	ushort	type;
+	int	id;
+};
+Inprogress inprog[Maxactive+2];
+
+typedef struct Forwtarg Forwtarg;
+struct Forwtarg {
+	char	*host;
+	uchar	addr[IPaddrlen];
+	int	fd;
+	ulong	lastdial;
+};
+Forwtarg forwtarg[10];
+int forwtcount;
+
+static char *hmsg = "headers";
+
+/*
+ *  record client id and ignore retransmissions.
+ *  we're still single thread at this point.
+ */
+static Inprogress*
+clientrxmit(DNSmsg *req, uchar *buf)
+{
+	Inprogress *p, *empty;
+	Udphdr *uh;
+
+	uh = (Udphdr *)buf;
+	empty = nil;
+	for(p = inprog; p < &inprog[Maxactive]; p++){
+		if(p->inuse == 0){
+			if(empty == nil)
+				empty = p;
+			continue;
+		}
+		if(req->id == p->id)
+		if(req->qd->owner == p->owner)
+		if(req->qd->type == p->type)
+		if(memcmp(uh, &p->uh, Udphdrsize) == 0)
+			return nil;
+	}
+	if(empty == nil)
+		return nil; /* shouldn't happen: see slave() & Maxactive def'n */
+
+	empty->id = req->id;
+	empty->owner = req->qd->owner;
+	empty->type = req->qd->type;
+	if (empty->type != req->qd->type)
+		dnslog("clientrxmit: bogus req->qd->type %d", req->qd->type);
+	memmove(&empty->uh, uh, Udphdrsize);
+	empty->inuse = 1;
+	return empty;
+}
+
+int
+addforwtarg(char *host)
+{
+	Forwtarg *tp;
+
+	if (forwtcount >= nelem(forwtarg)) {
+		dnslog("too many forwarding targets");
+		return -1;
+	}
+	tp = forwtarg + forwtcount;
+	if(parseip(tp->addr, host) == -1) {
+		dnslog("can't parse ip %s", host);
+		return -1;
+	}
+	tp->lastdial = time(nil);
+	tp->fd = udpport(mntpt);
+	if (tp->fd < 0)
+		return -1;
+
+	free(tp->host);
+	tp->host = estrdup(host);
+	forwtcount++;
+	return 0;
+}
+
+/*
+ * fast forwarding of incoming queries to other dns servers.
+ * intended primarily for debugging.
+ */
+static void
+redistrib(uchar *buf, int len)
+{
+	uchar save[Udphdrsize];
+	Forwtarg *tp;
+	Udphdr *uh;
+
+	memmove(save, buf, Udphdrsize);
+
+	uh = (Udphdr *)buf;
+	for (tp = forwtarg; tp < forwtarg + forwtcount; tp++)
+		if (tp->fd >= 0) {
+			memmove(uh->raddr, tp->addr, sizeof tp->addr);
+			hnputs(uh->rport, 53);		/* dns port */
+			if (write(tp->fd, buf, len) != len) {
+				close(tp->fd);
+				tp->fd = -1;
+			}
+		} else if (tp->host && time(nil) - tp->lastdial > 60) {
+			tp->lastdial = time(nil);
+			tp->fd = udpport(mntpt);
+		}
+
+	memmove(buf, save, Udphdrsize);
+}
+
+/*
+ *  a process to act as a dns server for outside requests
+ */
+void
+dnudpserver(char *mntpt)
+{
+	volatile int fd, len, op, rcode;
+	char *volatile err;
+	volatile char tname[32];
+	volatile uchar buf[Udphdrsize + Maxudp + 1024];
+	volatile DNSmsg reqmsg, repmsg;
+	Inprogress *volatile p;
+	volatile Request req;
+	Udphdr *volatile uh;
+
+	/*
+	 * fork sharing text, data, and bss with parent.
+	 * stay in the same note group.
+	 */
+	switch(rfork(RFPROC|RFMEM|RFNOWAIT)){
+	case -1:
+		break;
+	case 0:
+		break;
+	default:
+		return;
+	}
+
+	fd = -1;
+restart:
+	procsetname("udp server announcing");
+	if(fd >= 0)
+		close(fd);
+	while((fd = udpannounce(mntpt)) < 0)
+		sleep(5000);
+
+//	procsetname("udp server");
+	memset(&req, 0, sizeof req);
+	if(setjmp(req.mret))
+		putactivity(0);
+	req.isslave = 0;
+	req.id = 0;
+	req.aborttime = 0;
+
+	/* loop on requests */
+	for(;; putactivity(0)){
+		procsetname("served %lud udp; %lud alarms",
+			stats.qrecvdudp, stats.alarms);
+		memset(&repmsg, 0, sizeof repmsg);
+		memset(&reqmsg, 0, sizeof reqmsg);
+
+		alarm(60*1000);
+		len = read(fd, buf, sizeof buf);
+		alarm(0);
+		if(len <= Udphdrsize)
+			goto restart;
+
+		if(forwtcount > 0)
+			redistrib(buf, len);
+
+		uh = (Udphdr*)buf;
+		len -= Udphdrsize;
+
+		// dnslog("read received UDP from %I to %I",
+		//	((Udphdr*)buf)->raddr, ((Udphdr*)buf)->laddr);
+		getactivity(&req, 0);
+		req.aborttime = timems() + Maxreqtm;
+		req.from = smprint("%I", buf);
+		rcode = 0;
+		stats.qrecvdudp++;
+
+		err = convM2DNS(&buf[Udphdrsize], len, &reqmsg, &rcode);
+		if(err){
+			/* first bytes in buf are source IP addr */
+			dnslog("server: input error: %s from %I", err, buf);
+			free(err);
+			goto freereq;
+		}
+		if (rcode == 0)
+			if(reqmsg.qdcount < 1){
+				dnslog("server: no questions from %I", buf);
+				goto freereq;
+			} else if(reqmsg.flags & Fresp){
+				dnslog("server: reply not request from %I", buf);
+				goto freereq;
+			}
+		op = reqmsg.flags & Omask;
+		if(op != Oquery && op != Onotify){
+			dnslog("server: op %d from %I", reqmsg.flags & Omask,
+				buf);
+			goto freereq;
+		}
+
+		if(reqmsg.qd == nil){
+			dnslog("server: no question RR from %I", buf);
+			goto freereq;
+		}
+
+		if(debug || (trace && subsume(trace, reqmsg.qd->owner->name)))
+			dnslog("%d: serve (%I/%d) %d %s %s",
+				req.id, buf, uh->rport[0]<<8 | uh->rport[1],
+				reqmsg.id, reqmsg.qd->owner->name,
+				rrname(reqmsg.qd->type, tname, sizeof tname));
+
+		p = clientrxmit(&reqmsg, buf);
+		if(p == nil){
+			if(debug)
+				dnslog("%d: duplicate", req.id);
+			goto freereq;
+		}
+
+		if (Logqueries) {
+			RR *rr;
+
+			for (rr = reqmsg.qd; rr; rr = rr->next)
+				syslog(0, "dnsq", "id %d: (%I/%d) %d %s %s",
+					req.id, buf, uh->rport[0]<<8 |
+					uh->rport[1], reqmsg.id,
+					reqmsg.qd->owner->name,
+					rrname(reqmsg.qd->type, tname,
+					sizeof tname));
+		}
+		/* loop through each question */
+		while(reqmsg.qd){
+			memset(&repmsg, 0, sizeof repmsg);
+			switch(op){
+			case Oquery:
+				dnserver(&reqmsg, &repmsg, &req, buf, rcode);
+				break;
+			case Onotify:
+				dnnotify(&reqmsg, &repmsg, &req);
+				break;
+			}
+			/* send reply on fd to address in buf's udp hdr */
+			reply(fd, buf, &repmsg, &req);
+			freeanswers(&repmsg);
+		}
+
+		p->inuse = 0;
+freereq:
+		free(req.from);
+		req.from = nil;
+		freeanswers(&reqmsg);
+		if(req.isslave){
+			putactivity(0);
+			_exits(0);
+		}
+	}
+}
+
+/*
+ *  announce on well-known dns udp port and set message style interface
+ */
+static int
+udpannounce(char *mntpt)
+{
+	int data, ctl;
+	char dir[64], datafile[64+6];
+	static int whined;
+
+	/* get a udp port */
+	sprint(datafile, "%s/udp!*!dns", mntpt);
+	ctl = announce(datafile, dir);
+	if(ctl < 0){
+		if(!whined++)
+			warning("can't announce on %s", datafile);
+		return -1;
+	}
+
+	/* turn on header style interface */
+	if(write(ctl, hmsg, strlen(hmsg)) != strlen(hmsg)){
+		close(ctl);
+		if(!whined++)
+			warning("can't enable headers on %s", datafile);
+		return -1;
+	}
+
+	snprint(datafile, sizeof(datafile), "%s/data", dir);
+	data = open(datafile, ORDWR);
+	if(data < 0){
+		close(ctl);
+		if(!whined++)
+			warning("can't open %s to announce on dns udp port",
+				datafile);
+		return -1;
+	}
+
+	close(ctl);
+	return data;
+}
+
+static void
+reply(int fd, uchar *buf, DNSmsg *rep, Request *reqp)
+{
+	int len;
+	char tname[32];
+
+	if(debug || (trace && subsume(trace, rep->qd->owner->name)))
+		dnslog("%d: reply (%I/%d) %d %s %s qd %R an %R ns %R ar %R",
+			reqp->id, buf, buf[4]<<8 | buf[5],
+			rep->id, rep->qd->owner->name,
+			rrname(rep->qd->type, tname, sizeof tname),
+			rep->qd, rep->an, rep->ns, rep->ar);
+
+	len = convDNS2M(rep, &buf[Udphdrsize], Maxudp);
+	len += Udphdrsize;
+	if(write(fd, buf, len) != len)
+		dnslog("error sending reply: %r");
+}
--- /dev/null
+++ b/inform.c
@@ -1,0 +1,272 @@
+/* RFC2136 DNS inform - necessary for Win2k3 DNS servers */
+#include <u.h>
+#include <libc.h>
+#include <bio.h>
+#include <ndb.h>
+#include <ip.h>
+#include "dns.h"
+
+enum {
+	FQDNMAX	= 255,
+};
+
+char *errmsgs[] = {
+	[0]  "ok",
+	[1]  "request format error",
+	[2]  "internal server error",
+	[3]  "domain name does not exist",
+	[4]  "request not supported",
+	[5]  "permission denied",
+	[6]  "domain name already exists",
+	[7]  "resource record already exists",
+	[8]  "resource record does not exist",
+	[9]  "server not authoritative",
+	[10] "domain name not in zone",
+};
+
+static uchar loopbacknet[IPaddrlen] = {
+	0, 0, 0, 0,
+	0, 0, 0, 0,
+	0, 0, 0xff, 0xff,
+	127, 0, 0, 0
+};
+static uchar loopbackmask[IPaddrlen] = {
+	0xff, 0xff, 0xff, 0xff,
+	0xff, 0xff, 0xff, 0xff,
+	0xff, 0xff, 0xff, 0xff,
+	0xff, 0, 0, 0
+};
+static uchar loopback6[IPaddrlen] = {
+	0, 0, 0, 0,
+	0, 0, 0, 0,
+	0, 0, 0, 0,
+	0, 0, 0, 1
+};
+
+void
+usage(void)
+{
+	fprint(2, "usage: %s [-x netmtpt]\n", argv0);
+	exits("usage");
+}
+
+void
+ding(void *, char *msg)
+{
+	if(strstr(msg, "alarm") != nil)
+		noted(NCONT);
+	noted(NDFLT);
+}
+
+int
+g16(uchar **p)
+{
+	int n;
+
+	n  = *(*p)++ << 8;
+	n |= *(*p)++;
+	return n;
+}
+
+void
+p16(uchar **p, int n)
+{
+	*(*p)++ = n >> 8;
+	*(*p)++ = n;
+}
+
+void
+p32(uchar **p, int n)
+{
+	*(*p)++ = n >> 24;
+	*(*p)++ = n >> 16;
+	*(*p)++ = n >> 8;
+	*(*p)++ = n;
+}
+
+void
+pmem(uchar **p, void *v, int len)
+{
+	memmove(*p, v, len);
+	*p += len;
+}
+
+void
+pname(uchar **p, char *s)
+{
+	uchar *len;
+
+	while (*s){
+		len = (*p)++;
+		while(*s && *s != '.')
+			*(*p)++ = *s++;
+		*len = *p - len - 1;
+		if(*s == '.')
+			s++;
+	}
+	*(*p)++ = 0;
+}
+
+void
+main(int argc, char *argv[])
+{
+	static char *query[] = { "dom", "dnsdomain", "ns", "inform" };
+	char *sysname, *dnsdomain, *dom, *inform, *ns, net[32], dn[256], ds[256];
+	int debug, len, fd;
+	uint err;
+	uchar *p, buf[4096], mynet[IPaddrlen];
+	ushort txid;
+	Ndb *db;
+	Ndbtuple *t, *tt;
+	Ipifc *ifc;
+	Iplifc *lifc;
+
+	fmtinstall('I', eipfmt);
+	fmtinstall('V', eipfmt);
+	setnetmtpt(net, sizeof net, nil);
+
+	debug = 0;
+	ns = nil;
+	dom = nil;
+	inform = nil;
+	dnsdomain = nil;
+	ARGBEGIN{
+	case 'd':
+		debug = 1;
+		break;
+	case 'x':
+		setnetmtpt(net, sizeof net, EARGF(usage()));
+		break;
+	default:
+		usage();
+	}ARGEND;
+
+	if(argc != 0)
+		usage();
+
+	if((sysname = getenv("sysname")) == nil)
+		sysfatal("$sysname not set");
+
+	if((db = ndbopen(nil)) == nil)
+		sysfatal("can't open ndb: %r");
+	tt = ndbipinfo(db, "sys", sysname, query, nelem(query));
+	for(t = tt; t; t = t->entry){
+		if(strcmp(t->attr, "ns") == 0)
+			ns = t->val;
+		else if(strcmp(t->attr, "dom") == 0)
+			dom = t->val;
+		else if(strcmp(t->attr, "dnsdomain") == 0)
+			dnsdomain = t->val;
+		else if(strcmp(t->attr, "inform") == 0)
+			inform = t->val;
+	}
+
+	ndbfree(tt);
+	ndbclose(db);
+
+	if(inform)
+		dom = inform;
+	if(!ns)
+		sysfatal("no relevant ns=");
+	if(!dom)
+		sysfatal("no relevant dom=");
+	if(!dnsdomain)
+		sysfatal("no relevant dnsdomain=");
+
+
+	if(utf2idn(dom, dn, sizeof(dn)) <= 0)
+		sysfatal("cannot convert dom");
+
+	if(utf2idn(dnsdomain, ds, sizeof(ds)) <= 0)
+		sysfatal("cannot convert dnsdomain");
+
+	if(debug){
+		print("ns=%s\n", ns);
+		print("dnsdomain=%s\n", dnsdomain);
+		print("dom=%s\n", dom);
+	}
+
+	if((fd = dial(netmkaddr(ns, "udp", "dns"), 0, 0, 0)) < 0)
+		sysfatal("can't dial %s: %r", ns);
+
+	txid = time(nil) + getpid();
+
+	p = buf;
+	p16(&p, txid);		/* ID */
+	p16(&p, 5<<11);		/* flags */
+	p16(&p, 1);		/* # Zones */
+	p16(&p, 0);		/* # prerequisites */
+	p16(&p, 2);		/* # updates */
+	p16(&p, 0);		/* # additionals */
+
+        pname(&p, ds);		/* zone */
+	p16(&p, Tsoa);		/* zone type */
+	p16(&p, Cin);		/* zone class */
+
+	/* delete old name */
+   	pname(&p, dn);		/* name */
+	p16(&p, Ta);		/* type: v4 addr */
+	p16(&p, Call);		/* class */
+	p32(&p, 0);		/* TTL */
+	p16(&p, 0);		/* data len */
+
+   	pname(&p, dn);		/* name */
+	p16(&p, Taaaa);		/* type: v6 addr */
+	p16(&p, Call);		/* class */
+	p32(&p, 0);		/* TTL */
+	p16(&p, 0);		/* data len */
+
+	for(ifc = readipifc(net, nil, -1); ifc != nil; ifc = ifc->next){
+		for(lifc = ifc->lifc; lifc != nil; lifc = lifc->next){
+			/* unspecified */
+			if(ipcmp(lifc->ip, IPnoaddr) == 0)
+				continue;
+
+			/* ipv6 loopback */
+			if(ipcmp(lifc->ip, loopback6) == 0)
+				continue;
+
+			/* ipv4 loopback */
+			maskip(lifc->ip, loopbackmask, mynet);
+			if(ipcmp(mynet, loopbacknet) == 0)
+				continue;
+
+			if(debug)
+				print("ip=%I\n", lifc->ip);
+
+			/* add new A record */
+			pname(&p, dn);		/* name */
+			p16(&p, isv4(lifc->ip)?Ta:Taaaa);
+			p16(&p, Cin);		/* class */
+			p32(&p, 60*60*25);	/* TTL (25 hours) */
+			if(isv4(lifc->ip)){
+				p16(&p, IPv4addrlen);
+				pmem(&p, lifc->ip+IPv4off, IPv4addrlen);
+			} else {
+				p16(&p, IPaddrlen);
+				pmem(&p, lifc->ip, IPaddrlen);
+			}
+		}
+	}
+
+	len = p - buf;
+	if(write(fd, buf, len) != len)
+		sysfatal("write failed: %r");
+
+	notify(ding);
+	alarm(3000);
+	do{
+		if(read(fd, buf, sizeof buf) < 0)
+			sysfatal("timeout");
+		p = buf;
+	}while(g16(&p) != txid);
+	alarm(0);
+
+	err = g16(&p) & 7;
+	if(err != 0 && err != 7)	/* err==7 is just a "yes, I know" warning */
+		if(err < nelem(errmsgs))
+			sysfatal("%s", errmsgs[err]);
+		else
+			sysfatal("unknown dns server error %d", err);
+	exits(nil);
+}
--- /dev/null
+++ b/ipquery.c
@@ -1,0 +1,54 @@
+#include <u.h>
+#include <libc.h>
+#include <bio.h>
+#include <ndb.h>
+#include <ip.h>
+
+/*
+ *  search the database for matches
+ */
+
+void
+usage(void)
+{
+	fprint(2, "usage: ipquery attr value rattribute\n");
+	exits("usage");
+}
+
+void
+search(Ndb *db, char *attr, char *val, char **rattr, int nrattr)
+{
+	Ndbtuple *t, *tt;
+
+	tt = ndbipinfo(db, attr, val, rattr, nrattr);
+	for(t = tt; t; t = t->entry)
+		print("%s=%s ", t->attr, t->val);
+	print("\n");
+	ndbfree(tt);
+}
+
+void
+main(int argc, char **argv)
+{
+	Ndb *db;
+	char *dbfile = 0;
+
+	ARGBEGIN{
+	case 'f':
+		dbfile = ARGF();
+		break;
+	}ARGEND;
+
+	if(argc < 3)
+		usage();
+
+	db = ndbopen(dbfile);
+	if(db == 0){
+		fprint(2, "no db files\n");
+		exits("no db");
+	}
+	search(db, argv[0], argv[1], argv+2, argc-2);
+	ndbclose(db);
+
+	exits(0);
+}
--- /dev/null
+++ b/mkdb.c
@@ -1,0 +1,203 @@
+#include <u.h>
+#include <libc.h>
+#include <bio.h>
+#include <ctype.h>
+
+Biobuf in;
+Biobuf out;
+
+enum
+{
+	Empty,
+	Sys,
+	Dk,
+	Ip,
+	Domain,
+};
+
+int
+iscomment(char *name)
+{
+	return *name == '#';
+}
+
+/*
+ *  is this a fully specified datakit name?
+ */
+int
+isdk(char *name)
+{
+	int slash;
+
+	slash = 0;
+	for(; *name; name++){
+		if(isalnum(*name))
+			continue;
+		if(*name == '/'){
+			slash = 1;
+			continue;
+		}
+		return 0;
+	}
+	return slash;
+}
+
+/*
+ *  Is this an internet domain name?
+ */
+int
+isdomain(char *name)
+{
+	int dot = 0;
+	int alpha = 0;
+
+	for(; *name; name++){
+		if(isalpha(*name) || *name == '-'){
+			alpha = 1;
+			continue;
+		}
+		if(*name == '.'){
+			dot = 1;
+			continue;
+		}
+		if(isdigit(*name))
+			continue;
+		return 0;
+	}
+	return dot && alpha;
+}
+
+/*
+ *  is this an ip address?
+ */
+int
+isip(char *name)
+{
+	int dot = 0;
+
+	for(; *name; name++){
+		if(*name == '.'){
+			dot = 1;
+			continue;
+		}
+		if(isdigit(*name))
+			continue;
+		return 0;
+	}
+	return dot;
+}
+
+char tup[64][64];
+int ttype[64];
+int ntup;
+
+void
+tprint(void)
+{
+	int i, tab;
+	char *p;
+
+	tab = 0;
+	for(i = 0; i < ntup; i++){
+		if(ttype[i] == Sys){
+			Bprint(&out, "sys = %s\n", tup[i]);
+			tab = 1;
+			ttype[i] = Empty;
+			break;
+		}
+	}
+	for(i = 0; i < ntup; i++){
+		if(ttype[i] == Empty)
+			continue;
+		if(tab)
+			Bprint(&out, "\t");
+		tab = 1;
+
+		switch(ttype[i]){
+		case Domain:
+			Bprint(&out, "dom=%s\n", tup[i]);
+			break;
+		case Ip:
+			Bprint(&out, "ip=%s\n", tup[i]);
+			break;
+		case Dk:
+			p = strrchr(tup[i], '/');
+			if(p){
+				p++;
+				if((*p == 'C' || *p == 'R')
+				&& strncmp(tup[i], "nj/astro/", p-tup[i]) == 0)
+					Bprint(&out, "flavor=console ");
+			}
+			Bprint(&out, "dk=%s\n", tup[i]);
+			break;
+		case Sys:
+			Bprint(&out, "sys=%s\n", tup[i]);
+			break;
+		}
+	}
+}
+
+#define NFIELDS 64
+
+/*
+ *  make a database file from a merged uucp/inet database
+ */
+void
+main(void)
+{
+	int n, i, j;
+	char *l;
+	char *fields[NFIELDS];
+	int ftype[NFIELDS];
+	int same, match;
+
+	Binit(&in, 0, OREAD);
+	Binit(&out, 1, OWRITE);
+	ntup = 0;
+	while(l = Brdline(&in, '\n')){
+		l[Blinelen(&in)-1] = 0;
+		n = getfields(l, fields, NFIELDS, 1, " \t");
+		same = 0;
+		for(i = 0; i < n; i++){
+			if(iscomment(fields[i])){
+				n = i;
+				break;
+			}
+			if(isdomain(fields[i])){
+				ftype[i] = Domain;
+				for(j = 0; j < ntup; j++)
+					if(ttype[j] == Domain && strcmp(fields[i], tup[j]) == 0){
+						same = 1;
+						ftype[i] = Empty;
+						break;
+					}
+			} else if(isip(fields[i]))
+				ftype[i] = Ip;
+			else if(isdk(fields[i]))
+				ftype[i] = Dk;
+			else
+				ftype[i] = Sys;
+		}
+		if(!same && ntup){
+			tprint();
+			ntup = 0;
+		}
+		for(i = 0; i < n; i++){
+			match = 0;
+			for(j = 0; j < ntup; j++){
+				if(ftype[i] == ttype[j] && strcmp(fields[i], tup[j]) == 0){
+					match = 1;
+					break;
+				}
+			}
+			if(!match){
+				ttype[ntup] = ftype[i];
+				strcpy(tup[ntup], fields[i]);
+				ntup++;
+			}
+		}
+	}
+	if(ntup)
+		tprint();
+	exits(0);
+}
--- /dev/null
+++ b/mkfile
@@ -1,0 +1,108 @@
+# cs & dns mkfile
+</$objtype/mkfile
+
+TARG =	\
+	mkdb\
+	query\
+	mkhash\
+	mkhosts\
+	cs\
+	csquery\
+	dns\
+	dnsquery\
+	dnstcp\
+	dnsdebug\
+	dnsgetip\
+	registry\
+	regquery\
+	ipquery\
+	inform\
+
+DNSOBJ = dns.$O dnudpserver.$O dn.$O dnresolve.$O dblookup.$O dnserver.$O dnnotify.$O\
+	 dnarea.$O convM2DNS.$O convDNS2M.$O
+
+DNSTCPOBJ = dnstcp.$O dn.$O dnresolve.$O dblookup.$O dnserver.$O\
+	 dnarea.$O convM2DNS.$O convDNS2M.$O
+
+DNSDEBUGOBJ = dnsdebug.$O dn.$O dnresolve.$O dblookup.$O dnserver.$O\
+	 dnarea.$O convM2DNS.$O convDNS2M.$O
+
+DNSGETIPOBJ = dnsgetip.$O dn.$O dnresolve.$O dblookup.$O dnserver.$O\
+	 dnarea.$O convM2DNS.$O convDNS2M.$O
+
+HFILES = dns.h /$objtype/lib/libndb.a
+
+BIN=/$objtype/bin/ndb
+
+</sys/src/cmd/mkmany
+
+$O.dns: $DNSOBJ
+	$LD -o $target $prereq
+
+$O.dnstcp: $DNSTCPOBJ
+	$LD -o $target $prereq
+
+$O.dnsdebug: $DNSDEBUGOBJ
+	$LD -o $target $prereq
+
+$O.dnsgetip: $DNSGETIPOBJ
+	$LD -o $target $prereq
+
+$O.cs: cs.$O
+	$LD -o $target $prereq
+
+$O.testipinfo: testipinfo.$O ipinfo.$O
+	$LD -o $target $prereq
+
+$O.registry: registry.$O reglookup.$O
+	$LD -o $target $prereq
+
+push: $O.dns $O.dnsdebug $O.dnstcp
+	import lookout / /n/lookout
+	cp $O.dns /n/lookout/$objtype/bin/ndb/dns
+	cp $O.dnsdebug /n/lookout/$objtype/bin/ndb/dnsdebug
+	cp $O.dnstcp /n/lookout/$objtype/bin/ndb/dnstcp
+	unmount /n/lookout
+	import boundary / /n/boundary
+	cp $O.dns /n/boundary/$objtype/bin/ndb/dns
+	cp $O.dnsdebug /n/boundary/$objtype/bin/ndb/dnsdebug
+	cp $O.dnstcp /n/boundary/$objtype/bin/ndb/dnstcp
+	unmount /n/boundary
+
+cs.safeinstall:V: $O.cs
+	if(test -e $BIN/ooocs)
+		mv $BIN/ooocs $BIN/oooocs
+	if(test -e $BIN/oocs)
+		mv $BIN/oocs $BIN/ooocs
+	if(test -e $BIN/ocs)
+		mv $BIN/ocs $BIN/oocs
+	mv $BIN/cs $BIN/ocs
+	cp $O.cs $BIN/cs
+
+dns.safeinstall:V: $O.dns
+	if(test -e $BIN/ooodns)
+		mv $BIN/ooodns $BIN/oooodns
+	if(test -e $BIN/oodns)
+		mv $BIN/oodns $BIN/ooodns
+	if(test -e $BIN/odns)
+		mv $BIN/odns $BIN/oodns
+	mv $BIN/dns $BIN/odns
+	cp $O.dns $BIN/dns
+
+%.safeinstallall:V:
+	for (objtype in $CPUS)
+		mk $stem.safeinstall
+
+dns.update:V:
+	SRC=`{echo $DNSOBJ|sed 's/\.'$O'/.c/g'}
+	update $UPDATEFLAGS dns.h $SRC
+
+%.update:V:
+	update $UPDATEFLAGS $stem.c
+
+update:V:
+	mk clean
+	T=`{echo $TARG|sed 's/( |$)/.update /g'}
+	mk 'UPDATEFLAGS='$"UPDATEFLAGS $T
+	update $UPDATEFLAGS mkfile
+
--- /dev/null
+++ b/mkhash.c
@@ -1,0 +1,155 @@
+#include <u.h>
+#include <libc.h>
+#include <bio.h>
+#include <ndb.h>
+
+/*
+ *  make the hash table completely in memory and then write as a file
+ */
+
+uchar *ht;
+ulong hlen;
+Ndb *db;
+ulong nextchain;
+
+char*
+syserr(void)
+{
+	static char buf[ERRMAX];
+
+	errstr(buf, sizeof buf);
+	return buf;
+}
+
+void
+enter(char *val, ulong dboff)
+{
+	ulong h;
+	uchar *last;
+	ulong ptr;
+
+	h = ndbhash(val, hlen);
+	h *= NDBPLEN;
+	last = &ht[h];
+	ptr = NDBGETP(last);
+	if(ptr == NDBNAP){
+		NDBPUTP(dboff, last);
+		return;
+	}
+
+	if(ptr & NDBCHAIN){
+		/* walk the chain to the last entry */
+		for(;;){
+			ptr &= ~NDBCHAIN;
+			last = &ht[ptr+NDBPLEN];
+			ptr = NDBGETP(last);
+			if(ptr == NDBNAP){
+				NDBPUTP(dboff, last);
+				return;
+			}
+			if(!(ptr & NDBCHAIN)){
+				NDBPUTP(nextchain|NDBCHAIN, last);
+				break;
+			}
+		}
+	} else
+		NDBPUTP(nextchain|NDBCHAIN, last);
+
+	/* add a chained entry */
+	NDBPUTP(ptr, &ht[nextchain]);
+	NDBPUTP(dboff, &ht[nextchain + NDBPLEN]);
+	nextchain += 2*NDBPLEN;
+}
+
+uchar nbuf[16*1024];
+
+void
+main(int argc, char **argv)
+{
+	Ndbtuple *t, *nt;
+	int n;
+	Dir *d;	
+	uchar buf[8];
+	char file[128];
+	int fd;
+	ulong off;
+	uchar *p;
+
+	if(argc != 3){
+		fprint(2, "usage: mkhash file attribute\n");
+		exits("usage");
+	}
+	db = ndbopen(argv[1]);
+	if(db == 0){
+		fprint(2, "mkhash: can't open %s\n", argv[1]);
+		exits(syserr());
+	}
+
+	/* try a bigger than normal buffer */
+	Binits(&db->b, Bfildes(&db->b), OREAD, nbuf, sizeof(nbuf));
+
+	/* count entries to calculate hash size */
+	n = 0;
+
+	while(nt = ndbparse(db)){
+		for(t = nt; t; t = t->entry){
+			if(strcmp(t->attr, argv[2]) == 0)
+				n++;
+		}
+		ndbfree(nt);
+	}
+
+	/* allocate an array large enough for worst case */
+	hlen = 2*n+1;
+	n = hlen*NDBPLEN + hlen*2*NDBPLEN;
+	ht = mallocz(n, 1);
+	if(ht == 0){
+		fprint(2, "mkhash: not enough memory\n");
+		exits(syserr());
+	}
+	for(p = ht; p < &ht[n]; p += NDBPLEN)
+		NDBPUTP(NDBNAP, p);
+	nextchain = hlen*NDBPLEN;
+
+	/* create the in core hash table */
+	Bseek(&db->b, 0, 0);
+	off = 0;
+	while(nt = ndbparse(db)){
+		for(t = nt; t; t = t->entry){
+			if(strcmp(t->attr, argv[2]) == 0)
+				enter(t->val, off);
+		}
+		ndbfree(nt);
+		off = Boffset(&db->b);
+	}
+
+	/* create the hash file */
+	snprint(file, sizeof(file), "%s.%s", argv[1], argv[2]);
+	fd = create(file, ORDWR, 0664);
+	if(fd < 0){
+		fprint(2, "mkhash: can't create %s\n", file);
+		exits(syserr());
+	}
+	NDBPUTUL(db->mtime, buf);
+	NDBPUTUL(hlen, buf+NDBULLEN);
+	if(write(fd, buf, NDBHLEN) != NDBHLEN){
+		fprint(2, "mkhash: writing %s\n", file);
+		exits(syserr());
+	}
+	if(write(fd, ht, nextchain) != nextchain){
+		fprint(2, "mkhash: writing %s\n", file);
+		exits(syserr());
+	}
+	close(fd);
+
+	/* make sure file didn't change while we were making the hash */
+	d = dirstat(argv[1]);
+	if(d == nil || d->qid.path != db->qid.path
+	   || d->qid.vers != db->qid.vers){
+		fprint(2, "mkhash: %s changed underfoot\n", argv[1]);
+		remove(file);
+		exits("changed");
+	}
+
+	exits(0);
+}
--- /dev/null
+++ b/mkhosts.c
@@ -1,0 +1,233 @@
+#include <u.h>
+#include <libc.h>
+#include <bio.h>
+#include <ndb.h>
+#include <ip.h>
+
+typedef struct x
+{
+	Ndbtuple *t;
+	Ndbtuple *it;
+	Ndbtuple *nt;
+} X;
+
+X x[4096];
+int nx;
+char *domname = "research.att.com";
+int domnamlen;
+
+char*
+upper(char *x)
+{
+	char *p;
+	int c;
+
+	for(p = x; c = *p; p++)
+		*p = toupper(c);
+	return x;
+}
+
+void
+printArecord(int fd, X *p)
+{
+	Ndbtuple *nt;
+	char *c;
+	char *dom = 0;
+	char *curdom = 0;
+	int i, cdlen = 0;
+	int mxweight = 0;
+
+	if(p->nt) {
+		return;
+	}
+	for(nt=p->t; nt; nt = nt->entry) {
+		/* we are only going to handle things in the specified domain */
+		c = strchr(nt->val, '.');
+		if (c==0 || strcmp(++c, domname)!=0)
+			continue;
+		i = c - nt->val - 1;
+		if(strcmp(nt->attr, "dom") == 0) {
+			curdom = nt->val;
+			cdlen = i;
+			if (dom == 0) {
+				dom = curdom;
+				fprint(fd, "%-.*s%.*s	IN A	%s\n", i, nt->val, 15-i, "               ", p->it->val);
+			} else
+				fprint(fd, "%-.*s%.*s	IN CNAME	%s.\n", i, nt->val, 15-i, "               ", dom);
+		} else if(strcmp(nt->attr, "mx") == 0) {
+			if (curdom != 0)
+				fprint(fd, "%-.*s%.*s	MX	%d	%s.\n", cdlen, curdom, 15-cdlen, "               ", mxweight++, nt->val);
+		}
+	}
+}
+
+void
+printentry(int fd, X *p)
+{
+	Ndbtuple *nt;
+
+	if(p->nt)
+		return;
+	fprint(fd, "%s	", p->it->val);
+	for(nt = p->t; nt; nt = nt->entry)
+		if(strcmp(nt->attr, "dom") == 0)
+			fprint(fd, " %s", nt->val);
+	for(nt = p->t; nt; nt = nt->entry)
+		if(strcmp(nt->attr, "sys") == 0)
+			fprint(fd, " %s", nt->val);
+	fprint(fd, "\n");
+}
+
+void
+printsys(int fd, X *p)
+{
+	Ndbtuple *nt;
+
+	for(nt = p->t; nt; nt = nt->entry)
+		if(strcmp(nt->attr, "dom") == 0)
+			fprint(fd, "%s\n", nt->val);
+}
+
+void
+printtxt(int fd, X *p)
+{
+	int i;
+	Ndbtuple *nt;
+
+	if(p->nt){
+		for(;;){
+			i = strlen(p->it->val);
+			if(strcmp(p->it->val+i-2, ".0") == 0)
+				p->it->val[i-2] = 0;
+			else
+				break;
+		}
+		fprint(fd, "\nNET : %s : %s\n", p->it->val, upper(p->nt->val));
+		return;
+	}
+	fprint(fd, "HOST : %s :", p->it->val);
+	i = 0;
+	for(nt = p->t; nt; nt = nt->entry)
+		if(strcmp(nt->attr, "dom") == 0){
+			if(i++ == 0)
+				fprint(fd, " %s", upper(nt->val));
+			else
+				fprint(fd, ", %s", upper(nt->val));
+		}
+	fprint(fd, "\n");
+}
+
+void
+parse(char *file)
+{
+	int i;
+	Ndb *db;
+	Ndbtuple *t, *nt, *tt, *ipnett;
+	char *p;
+
+	db = ndbopen(file);
+	if(db == 0)
+		exits("no database");
+	while(t = ndbparse(db)){
+		for(nt = t; nt; nt = nt->entry){
+			if(strcmp(nt->attr, "ip") == 0)
+				break;
+			if(strcmp(nt->attr, "flavor") == 0
+			&& strcmp(nt->val, "console") == 0)
+				return;
+		}
+		if(nt == 0){
+			ndbfree(t);
+			continue;
+		}
+
+		/* dump anything not on our nets */
+		ipnett = 0;
+		for(tt = t; tt; tt = tt->entry){
+			if(strcmp(tt->attr, "ipnet") == 0){
+				ipnett = tt;
+				break;
+			}
+			if(strcmp(tt->attr, "dom") == 0){
+				i = strlen(tt->val);
+				p = tt->val+i-domnamlen;
+				if(p >= tt->val && strcmp(p, domname) == 0)
+					break;
+			}
+		}
+		if(tt == 0){
+			ndbfree(t);
+			continue;
+		}
+
+		for(; nt; nt = nt->entry){
+			if(strcmp(nt->attr, "ip") != 0)
+				continue;
+			x[nx].it = nt;
+			x[nx].nt = ipnett;
+			x[nx++].t = t;
+		}
+	}
+}
+
+void
+main(int argc, char *argv[])
+{
+	int i, fd;
+	char fn[128];
+
+	if (argc>1)
+		domname = argv[1];
+	domnamlen = strlen(domname);
+	if(argc > 2){
+		for(i = 2; i < argc; i++)
+			parse(argv[i]);
+	} else {
+		parse("/lib/ndb/local");
+		parse("/lib/ndb/friends");
+	}
+	
+//	sprint(fn, "/lib/ndb/hosts.%-.21s", domname);
+//	fd = create(fn, OWRITE, 0664);
+//	if(fd < 0){
+//		fprint(2, "can't create %s: %r\n", fn);
+//		exits("boom");
+//	}
+//	for(i = 0; i < nx; i++)
+//		printentry(fd, &x[i]);
+//	close(fd);
+//
+
+	sprint(fn, "/lib/ndb/db.%-.24s", domname);
+	fd = create(fn, OWRITE, 0664);
+	if(fd < 0){
+		fprint(2, "can't create %s: %r\n", fn);
+		exits("boom");
+	}
+	fprint(fd, "; This file is generated automatically, do not edit!\n");
+	for(i = 0; i < nx; i++)
+		printArecord(fd, &x[i]);
+	close(fd);
+
+	sprint(fn, "/lib/ndb/equiv.%-.21s", domname);
+	fd = create(fn, OWRITE, 0664);
+	if(fd < 0){
+		fprint(2, "can't create %s: %r\n", fn);
+		exits("boom");
+	}
+	for(i = 0; i < nx; i++)
+		printsys(fd, &x[i]);
+	close(fd);
+
+	sprint(fn, "/lib/ndb/txt.%-.23s", domname);
+	fd = create(fn, OWRITE, 0664);
+	if(fd < 0){
+		fprint(2, "can't create %s: %r\n", fn);
+		exits("boom");
+	}
+	for(i = 0; i < nx; i++)
+		printtxt(fd, &x[i]);
+	close(fd);
+
+	exits(0);
+}
--- /dev/null
+++ b/query.c
@@ -1,0 +1,114 @@
+/*
+ *  search the network database for matches
+ */
+#include <u.h>
+#include <libc.h>
+#include <bio.h>
+#include <ndb.h>
+
+static int all, multiple;
+static Biobuf bout;
+
+void
+usage(void)
+{
+	fprint(2, "usage: query [-am] [-f ndbfile] attr value "
+		"[returned-attr [reps]]\n");
+	exits("usage");
+}
+
+/* print values of nt's attributes matching rattr */
+static void
+prmatch(Ndbtuple *nt, char *rattr)
+{
+	for(; nt; nt = nt->entry)
+		if (strcmp(nt->attr, rattr) == 0)
+			Bprint(&bout, "%s\n", nt->val);
+}
+
+void
+search(Ndb *db, char *attr, char *val, char *rattr)
+{
+	char *p;
+	Ndbs s;
+	Ndbtuple *t, *nt;
+
+	/* first entry with a matching rattr */
+	if(rattr && !all){
+		p = ndbgetvalue(db, &s, attr, val, rattr, &t);
+		if (multiple)
+			prmatch(t, rattr);
+		else if(p)
+			Bprint(&bout, "%s\n", p);
+		ndbfree(t);
+		free(p);
+		return;
+	}
+
+	/* all entries with matching rattrs */
+	if(rattr) {
+		for(t = ndbsearch(db, &s, attr, val); t != nil;
+		    t = ndbsnext(&s, attr, val)){
+			prmatch(t, rattr);
+			ndbfree(t);
+		}
+		return;
+	}
+
+	/* all entries */
+	for(t = ndbsearch(db, &s, attr, val); t; t = ndbsnext(&s, attr, val)){
+		for(nt = t; nt; nt = nt->entry)
+			Bprint(&bout, "%s=%s ", nt->attr, nt->val);
+		Bprint(&bout, "\n");
+		ndbfree(t);
+	}
+}
+
+void
+main(int argc, char **argv)
+{
+	int reps = 1;
+	char *rattr = nil, *dbfile = nil;
+	Ndb *db;
+	
+	ARGBEGIN{
+	case 'a':
+		all++;
+		break;
+	case 'm':
+		multiple++;
+		break;
+	case 'f':
+		dbfile = EARGF(usage());
+		break;
+	default:
+		usage();
+	}ARGEND;
+
+	switch(argc){
+	case 4:
+		reps = atoi(argv[3]);	/* wtf use is this? */
+		/* fall through */
+	case 3:
+		rattr = argv[2];
+		break;
+	case 2:
+		rattr = nil;
+		break;
+	default:
+		usage();
+	}
+
+	if(Binit(&bout, 1, OWRITE) == -1)
+		sysfatal("Binit: %r");
+	db = ndbopen(dbfile);
+	if(db == nil){
+		fprint(2, "%s: no db files\n", argv0);
+		exits("no db");
+	}
+	while(reps--)
+		search(db, argv[0], argv[1], rattr);
+	ndbclose(db);
+
+	exits(0);
+}
--- /dev/null
+++ b/registry.c
@@ -1,0 +1,995 @@
+#include <u.h>
+#include <libc.h>
+#include <auth.h>
+#include <fcall.h>
+#include <bio.h>
+#include <ip.h>
+#include "dns.h"
+
+enum
+{
+	Maxrequest=		1024,
+	Maxreply=		8192,
+	Maxrecords=		192,
+	Maxfdata=		8192,
+
+	Qdir=			0,
+	Qreg=			1,
+};
+
+typedef struct Mfile	Mfile;
+typedef struct Job	Job;
+typedef struct Records  Records;
+
+struct Mfile
+{
+	Mfile	*next;
+
+	char	*user;
+	Qid	qid;
+	int	fid;
+	int	bare;
+
+	char	reply[Maxreply];
+	ushort	rr[Maxrecords]; /* offset of record */
+	ushort	nrr;		/* number of records */	
+};
+
+/*
+ *  active requests
+ */
+struct Job
+{
+	Job	*next;
+	int	flushed;
+	Fcall	request;
+	Fcall	reply;
+};
+Lock	joblock;
+Job	*joblist;
+
+struct {
+	Lock;
+	Mfile	*inuse;		/* active mfile's */
+} mfalloc;
+
+Svc	*registry;
+int	vers;
+int	resolver;
+int	debug;
+char	*dbfile = "/lib/ndb/registry";
+char	*reguser;
+char	mtpt[Maxpath];
+int	rfd[Maxremote];
+int	mfd[2];
+char	*logfile = "registry";
+
+void	rversion(Job*);
+void	rflush(Job*);
+void	rattach(Job*, Mfile*);
+char*	rwalk(Job*, Mfile*);
+void	ropen(Job*, Mfile*);
+void	rcreate(Job*, Mfile*);
+void	rread(Job*, Mfile*);
+void	rwrite(Job*, Mfile*);
+void	rclunk(Job*, Mfile*);
+void	rremove(Job*, Mfile*);
+void	rstat(Job*, Mfile*);
+void	rwstat(Job*, Mfile*);
+void	rauth(Job*);
+void	mountinit(char*, char*);
+void	setext(char*, int, char*);
+void	io(void);
+
+static char*	resolve(char*, ...);
+static char*	addsvc(char*);
+static char*	rmsvc(char*);
+static char*	updatesvc(char*);
+static void	refresh(void);
+static void	regdump(char*);
+static void	sendmsg(Job*, char*);
+
+static int	scanfmt(Fmt*);
+static int	srvfmt(Fmt*);
+static int	dumpfmt(Fmt*);
+
+static char* query(Job*, Mfile*, char*, int);
+static char* resolvequery(Job*, Mfile*, char*, int);
+
+void
+usage(void)
+{
+	fprint(2, "usage: %s [-xrd] [-f ndb-file]\n", argv0);
+	exits("usage");
+}
+
+void
+main(int argc, char* argv[])
+{
+	char servefile[Maxpath], ext[Maxpath];
+	Dir *dir;
+	ext[0] = 0;
+
+	setnetmtpt(mtpt, sizeof mtpt, nil);
+
+	ARGBEGIN{
+	case 'r':
+		resolver = 1;
+		break;
+	case 'd':
+		debug = 1;
+		break;
+	case 'f':
+		dbfile = EARGF(usage());
+		break;
+	case 'x':
+		setnetmtpt(mtpt, sizeof mtpt, EARGF(usage()));
+		setext(ext, sizeof ext, mtpt);
+		break;
+	} ARGEND;
+	if(argc != 0)
+		usage();
+    
+
+
+	rfork(RFREND|RFNOTEG);
+
+	fmtinstall('F', fcallfmt);
+	fmtinstall('G', srvfmt);
+	fmtinstall('N', scanfmt);
+	fmtinstall('D', dumpfmt);
+
+	reglog("starting registry on %s", mtpt);
+
+	if(! resolver && openregistry())
+		sysfatal("unable to open db file");
+
+	reguser = estrdup(getuser());
+	seprint(servefile, servefile+Maxpath, "#s/registry%s", ext);
+	
+	dir = dirstat(servefile);
+	if (dir)
+		sysfatal("%s exists; another registry instance is running", servefile);
+	free(dir);
+
+
+	mountinit(servefile, mtpt);
+
+	if(resolver)
+		regconnect();
+	else
+		reg2cache();
+	io();
+
+	_exits(0);
+}
+
+void
+setext(char *ext, int n, char *p)
+{
+	int i, c;
+
+	n--;
+	for(i = 0; i < n; i++){
+		c = p[i];
+		if(c == 0)
+			break;
+		if(c == '/')
+			c = '_';
+		ext[i] = c;
+	}
+	ext[i] = 0;
+}
+
+void
+mountinit(char *service, char *mtpt)
+{
+	int f;
+	int p[2];
+	char buf[32];
+
+	if(pipe(p) < 0)
+		sysfatal("pipe failed: %r");
+
+	/*
+	 *  make a /srv/registry
+	 */
+	if((f = create(service, OWRITE|ORCLOSE, 0666)) < 0)
+		sysfatal("create %s failed: %r", service);
+	snprint(buf, sizeof buf, "%d", p[1]);
+	if(write(f, buf, strlen(buf)) != strlen(buf))
+		sysfatal("write %s failed: %r", service);
+
+	/* copy namespace to avoid a deadlock */
+	switch(rfork(RFFDG|RFPROC|RFNAMEG)){
+	case 0:			/* child: start main proc */
+		close(p[1]);
+		procsetname("%s", mtpt);
+		break;
+	case -1:
+		sysfatal("fork failed: %r");
+	default:		/* parent: make /srv/registry, mount it, exit */
+		close(p[0]);
+
+		/*
+		 *  put ourselves into the file system
+		 */
+		if(mount(p[1], -1, mtpt, MAFTER, "") < 0)
+			fprint(2, "registry mount failed: %r\n");
+		_exits(0);
+	}
+	mfd[0] = mfd[1] = p[0];
+}
+
+Mfile*
+newfid(int fid, int needunused)
+{
+	Mfile *mf;
+
+	lock(&mfalloc);
+	for(mf = mfalloc.inuse; mf != nil; mf = mf->next)
+		if(mf->fid == fid){
+			unlock(&mfalloc);
+			if(needunused)
+				return nil;
+			return mf;
+		}
+	mf = emalloc(sizeof(*mf));
+	mf->fid = fid;
+	mf->qid.vers = vers;
+	mf->qid.type = QTDIR;
+	mf->qid.path = 0LL;
+	mf->user = estrdup(reguser);
+	mf->next = mfalloc.inuse;
+	mfalloc.inuse = mf;
+	mf->bare = 1;
+	unlock(&mfalloc);
+	return mf;
+}
+
+void
+freefid(Mfile *mf)
+{
+	Mfile **l;
+
+	lock(&mfalloc);
+	for(l = &mfalloc.inuse; *l != nil; l = &(*l)->next)
+		if(*l == mf){
+			*l = mf->next;
+			free(mf->user);
+			memset(mf, 0, sizeof *mf);	/* cause trouble */
+			free(mf);
+			unlock(&mfalloc);
+			return;
+		}
+	unlock(&mfalloc);
+	sysfatal("freeing unused fid");
+}
+
+Mfile*
+copyfid(Mfile *mf, int fid)
+{
+	Mfile *nmf;
+
+	nmf = newfid(fid, 1);
+	if(nmf == nil)
+		return nil;
+	nmf->fid = fid;
+	free(nmf->user);
+	nmf->user = estrdup(mf->user);
+	nmf->qid.type = mf->qid.type;
+	nmf->qid.path = mf->qid.path;
+	nmf->qid.vers = vers++;
+	return nmf;
+}
+
+Job*
+newjob(void)
+{
+	Job *job;
+
+	job = emalloc(sizeof *job);
+	lock(&joblock);
+	job->next = joblist;
+	joblist = job;
+	job->request.tag = -1;
+	unlock(&joblock);
+	return job;
+}
+
+void
+freejob(Job *job)
+{
+	Job **l;
+
+	lock(&joblock);
+	for(l = &joblist; *l; l = &(*l)->next)
+		if(*l == job){
+			*l = job->next;
+			memset(job, 0, sizeof *job);	/* cause trouble */
+			free(job);
+			break;
+		}
+	unlock(&joblock);
+}
+
+void
+flushjob(int tag)
+{
+	Job *job;
+
+	lock(&joblock);
+	for(job = joblist; job; job = job->next)
+		if(job->request.tag == tag && job->request.type != Tflush){
+			job->flushed = 1;
+			break;
+		}
+	unlock(&joblock);
+}
+
+void
+io(void)
+{
+	long n;
+	Mfile *mf;
+	uchar mdata[IOHDRSZ + Maxfdata];
+	Job *job;
+
+	while((n = read9pmsg(mfd[0], mdata, sizeof mdata)) != 0){
+		if(n < 0){
+			syslog(1, logfile, "error reading 9P from %s: %r", mtpt);
+			break;
+		}
+
+		job = newjob();
+		if(convM2S(mdata, n, &job->request) != n){
+			reglog("format error %ux %ux %ux %ux %ux",
+				mdata[0], mdata[1], mdata[2], mdata[3], mdata[4]);
+			freejob(job);
+			break;
+		}
+		mf = newfid(job->request.fid, 0);
+		if(debug)
+			reglog("%F", &job->request);
+
+		switch(job->request.type){
+		default:
+			warning("unknown request type %d", job->request.type);
+			break;
+		case Tversion:
+			rversion(job);
+			break;
+		case Tauth:
+			rauth(job);
+			break;
+		case Tflush:
+			rflush(job);
+			break;
+		case Tattach:
+			rattach(job, mf);
+			break;
+		case Twalk:
+			rwalk(job, mf);
+			break;
+		case Topen:
+			ropen(job, mf);
+			break;
+		case Tcreate:
+			rcreate(job, mf);
+			break;
+		case Tread:
+			rread(job, mf);
+			break;
+		case Twrite:
+			rwrite(job, mf);
+			break;
+		case Tclunk:
+			rclunk(job, mf);
+			break;
+		case Tremove:
+			rremove(job, mf);
+			break;
+		case Tstat:
+			rstat(job, mf);
+			break;
+		case Twstat:
+			rwstat(job, mf);
+			break;
+		}
+
+		freejob(job);
+	}
+}
+
+void
+rversion(Job *job)
+{
+	if(job->request.msize > IOHDRSZ + Maxfdata)
+		job->reply.msize = IOHDRSZ + Maxfdata;
+	else
+		job->reply.msize = job->request.msize;
+	job->reply.version = "9P2000";
+	if(strncmp(job->request.version, "9P", 2) != 0)
+		job->reply.version = "unknown";
+	sendmsg(job, nil);
+}
+
+void
+rauth(Job *job)
+{
+	sendmsg(job, "registry: authentication not required");
+}
+
+void
+rflush(Job *job)
+{
+	flushjob(job->request.oldtag);
+	sendmsg(job, 0);
+}
+
+void
+rattach(Job *job, Mfile *mf)
+{
+	if(mf->user != nil)
+		free(mf->user);
+	mf->user = estrdup(job->request.uname);
+	mf->qid.vers = vers++;
+	mf->qid.type = QTDIR;
+	mf->qid.path = 0LL;
+	job->reply.qid = mf->qid;
+	sendmsg(job, 0);
+}
+
+char*
+rwalk(Job *job, Mfile *mf)
+{
+	int i, nelems;
+	char *err;
+	char **elems;
+	Mfile *nmf;
+	Qid qid;
+
+	err = 0;
+	nmf = nil;
+	elems = job->request.wname;
+	nelems = job->request.nwname;
+	job->reply.nwqid = 0;
+
+	if(job->request.newfid != job->request.fid){
+		/* clone fid */
+		nmf = copyfid(mf, job->request.newfid);
+		if(nmf == nil){
+			err = "clone bad newfid";
+			goto send;
+		}
+		mf = nmf;
+	}
+	/* else nmf will be nil */
+
+	qid = mf->qid;
+	if(nelems > 0){
+		/* walk fid */
+		for(i=0; i<nelems && i<MAXWELEM; i++){
+			if((qid.type & QTDIR) == 0){
+				err = "not a directory";
+				break;
+			}
+			if(strcmp(elems[i], "..") == 0 || strcmp(elems[i], ".") == 0){
+				qid.type = QTDIR;
+				qid.path = Qdir;
+    Found:
+				job->reply.wqid[i] = qid;
+				job->reply.nwqid++;
+				continue;
+			}
+			if(strcmp(elems[i], "registry") == 0){
+				qid.type = QTFILE;
+				qid.path = Qreg;
+				goto Found;
+			}
+			err = "file does not exist";
+			break;
+		}
+	}
+
+    send:
+	if(nmf != nil && (err!=nil || job->reply.nwqid<nelems))
+		freefid(nmf);
+	if(err == nil)
+		mf->qid = qid;
+	sendmsg(job, err);
+	return err;
+}
+
+void
+ropen(Job *job, Mfile *mf)
+{
+	int mode;
+	char *err;
+
+	err = 0;
+	mode = job->request.mode;
+	if(mf->qid.type & QTDIR)
+		if(mode)
+			err = "permission denied";
+	job->reply.qid = mf->qid;
+	job->reply.iounit = 0;
+	sendmsg(job, err);
+}
+
+void
+rcreate(Job *job, Mfile *mf)
+{
+	USED(mf);
+	sendmsg(job, "creation permission denied");
+}
+
+void rclunk(Job *job, Mfile *mf)
+{
+	freefid(mf);
+	sendmsg(job, 0);
+}
+
+void
+rremove(Job *job, Mfile *mf)
+{
+	USED(mf);
+	sendmsg(job, "remove permission denied");
+}
+
+void 
+rread(Job *job, Mfile *mf)
+{
+	int i, n;
+	long clock;
+	ulong cnt;
+	vlong off;
+	char *err;
+	uchar buf[Maxfdata];
+	Dir dir;
+
+	n = 0;
+	err = nil;
+	off = job->request.offset;
+	cnt = job->request.count;
+	*buf = '\0';
+	job->reply.data = (char*)buf;
+	if(mf->qid.type & QTDIR){
+		clock = time(nil);
+		if(off == 0){
+			memset(&dir, 0, sizeof dir);
+			dir.name = "registry";
+			dir.qid.type = QTFILE;
+			dir.qid.vers = vers;
+			dir.qid.path = Qreg;
+			dir.mode = 0666;
+			dir.length = 0;
+			dir.uid = dir.gid = dir.muid = mf->user;
+			dir.atime = dir.mtime = clock;
+			n = convD2M(&dir, buf, sizeof buf);
+		}
+	} else if (off < 0)
+		err = "negative read offset";
+	else {
+		if(mf->bare)
+			query(job, mf, "all", 0);
+		for(i = 1; i < mf->nrr; i++)
+			if(mf->rr[i] > off)
+				break;
+		if(i <= mf->nrr){
+			if(off + cnt > mf->rr[i])
+				n = mf->rr[i] - off;
+			else
+				n = cnt;
+			assert(n >= 0);
+			job->reply.data = mf->reply + off;
+		}
+	}
+	job->reply.count = n;
+	sendmsg(job, err);	
+}
+
+void 
+rwrite(Job *job, Mfile *mf)
+{
+	int send, pipe2rc;
+	ulong cnt;	
+	char *err, *atype;
+	char errbuf[ERRMAX];
+	
+	err = nil;
+	cnt = job->request.count;
+	send = 1;
+	if(mf->qid.type & QTDIR)
+		err = "can't write directory";
+	else if (job->request.offset != 0)
+		err = "writing at non-zero offset";
+	else if (cnt >= Maxrequest)
+		err = "request too long";
+	else
+		send = 0;
+	if(send)
+		goto send;
+
+	job->request.data[cnt] = 0;
+	if(cnt > 0 && job->request.data[cnt-1] == '\n')
+		job->request.data[cnt-1] = 0;
+
+	if(strcmp(mf->user, "none") == 0 || strcmp(mf->user, reguser) != 0)
+		goto query; /* We don't want remote clients to modify our local */
+
+	/*
+	 * special commands
+	 */
+	send = 1;
+	if(strcmp(job->request.data, "debug")==0)
+		debug ^= 1;
+	else if(strcmp(job->request.data, "dump")==0)
+		regdump("/lib/ndb/regdump");
+	else if (strcmp(job->request.data, "refresh")==0)
+		refresh();
+	else if (strncmp(job->request.data, "add ", 4)==0)
+		err = addsvc(job->request.data + 4);
+	else if (strncmp(job->request.data, "rm ", 3)==0)
+		err = rmsvc(job->request.data + 3);
+	else if (strncmp(job->request.data, "update ", 7)==0)
+		err = updatesvc(job->request.data + 7);
+	else
+		send = 0;
+	if (send)
+		goto send;
+
+query:
+	/*
+	 *	kill previous reply
+	 */
+	mf->nrr = 0;
+	mf->rr[0] = 0;
+	pipe2rc = 0;
+	
+	atype = strchr(job->request.data, ' ');
+	if(atype == 0){
+		snprint(errbuf, sizeof errbuf, "illegal request %s", job->request.data);
+		err = errbuf;
+		goto send;
+	} else
+		*atype++ = 0;
+
+	if(strcmp(atype, "svc") == 0)
+		pipe2rc++;
+	else if(strcmp(atype, "scan") != 0){
+		snprint(errbuf, sizeof errbuf, "unknown query %s", atype);
+		err = errbuf;
+		goto send;
+	}
+
+	if(resolver){
+		resolvequery(job, mf, job->request.data, pipe2rc);
+		goto send;
+	}
+	err = query(job, mf,job->request.data, pipe2rc);
+send:
+	mf->bare = 0;
+	job->reply.count = cnt;
+	sendmsg(job, err);
+}
+
+void
+rstat(Job *job, Mfile *mf)
+{
+	Dir dir;
+	uchar buf[IOHDRSZ+Maxfdata];
+	
+	memset(&dir, 0, sizeof dir);
+	if(mf->qid.type & QTDIR){
+		dir.name = ".";
+		dir.mode = DMDIR|0555;
+	}else{
+		dir.name = "registry";
+		dir.mode = 0666;
+	}
+	dir.qid = mf->qid;
+	dir.length = 0;
+	dir.uid = dir.gid = dir.muid = mf->user;
+	dir.atime = dir.mtime = time(nil);
+	job->reply.nstat = convD2M(&dir, buf, sizeof buf);
+	job->reply.stat = buf;
+	sendmsg(job, 0);
+}
+
+void
+rwstat(Job *job, Mfile *mf)
+{
+	USED(mf);
+	sendmsg(job, "wstat permission denied");
+}
+
+static char *
+resolvequery(Job *job, Mfile *mf, char *p, int pipe2rc)
+{
+	int match, i;
+	int n;
+	Svc *c;
+
+	char cmd[256];
+	char buf[8192+1];
+
+	lock(&joblock);
+	if(!job->flushed){
+		match = n = 0;
+		mf->nrr = 0;
+
+		snprint(cmd, sizeof(cmd), "%s %s", p, ((pipe2rc)?"svc":"scan"));
+
+		for(i = 0; i < Maxremote && !match && rfd[i] > 1; i++){
+			seek(rfd[i], 0, 0);
+			write(rfd[i], cmd, sizeof cmd);
+
+			seek(rfd[i], 0, 0);
+			while(read(rfd[i], buf, sizeof(buf)-1) > 0){	
+				match = 1;
+				c = rstr2svc(buf);
+				mf->rr[mf->nrr++] = n;
+				if(pipe2rc)
+					n += snprint(mf->reply+n, Maxreply-n, "%G", c);
+				else
+					n += snprint(mf->reply+n, Maxreply-n, "%N", c);
+				free(c);
+			}
+		}
+	}
+	unlock(&joblock);
+
+	return 0;
+}
+
+static char *
+query(Job *job, Mfile *mf, char *p, int pipe2rc)
+{
+	int n;
+
+	Svc *c;
+	lock(&joblock);
+	if(!job->flushed){
+		n = 0;
+		mf->nrr = 0;
+		for(c = registry; c && n < Maxreply; c = c->next)
+			if((strncmp(p, c->labl, strlen(p))==0) || (strcmp(p, "all")==0)){
+				mf->rr[mf->nrr++] = n;
+				if(pipe2rc)
+					n += snprint(mf->reply+n, Maxreply-n, "%G", c);
+				else
+					n += snprint(mf->reply+n, Maxreply-n, "%N", c);
+			}
+		mf->rr[mf->nrr] = n;
+	}
+	unlock(&joblock);
+	return nil;
+}
+
+static void
+sendmsg(Job *job, char *err)
+{
+	int n;
+	uchar mdata[IOHDRSZ+Maxfdata];
+	char ename[ERRMAX];
+
+	if(err){
+		job->reply.type = Rerror;
+		snprint(ename, sizeof ename, "registry: %s", err);
+		job->reply.ename = ename;
+	}else
+		job->reply.type = job->request.type+1;
+	job->reply.tag = job->request.tag;
+	n = convS2M(&job->reply, mdata, sizeof mdata);
+	if(n == 0){
+		warning("sendmsg convS2M of %F returns 0", &job->reply);
+		abort();
+	}
+	lock(&joblock);
+	if(job->flushed == 0)
+		if(write(mfd[1], mdata, n)!=n)
+			sysfatal("mount write");
+	unlock(&joblock);
+	if(debug)
+		reglog("%F %d", &job->reply, n);
+}
+
+static void
+regdump(char *file)
+{
+	Svc *rp;
+	int fd;
+
+	if(resolver){
+		resolve("dump");
+		return;
+	}
+
+	fd = create(file, OWRITE, 0666);
+	if(fd < 0)
+		return;
+	lock(&mfalloc);
+	for(rp = registry; rp; rp = rp->next)
+		fprint(fd, "%D\n\n", rp);
+	unlock(&mfalloc);
+	close(fd);
+}
+
+static void
+refresh(void)
+{	
+	Svc *c;
+	char dial[Maxdial];
+
+	if(resolver){
+		resolve("refresh");
+		return;
+	}
+
+	for(c = registry; c; c = c->next){
+		/* Don't remove the ones we've added since startup */
+		if(!c->perm)
+			continue;
+		snprint(dial, Maxdial, "%s!%s!%s", c->trns, c->host, c->port);
+		rmsvc(dial);
+		/* Reset so we don't have messy loops */
+		c = registry;
+	}
+	reg2cache();
+}
+
+static char *
+resolve(char *cmd, ...)
+{
+	int n;
+	char fullcmd[256];
+	char buf[8192+1];
+	va_list arg;
+	
+	va_start(arg, cmd);
+	vseprint(fullcmd, fullcmd+sizeof(fullcmd), cmd, arg);
+	va_end(arg);
+
+	/* We only operate on our local rfd */
+	seek(rfd[0], 0, 0);
+	write(rfd[0], fullcmd, sizeof fullcmd);
+
+	seek(rfd[0], 0, 0);
+	while((n = read(rfd[0], buf, sizeof(buf)-1)) > 0){
+		buf[n++] = '\n';
+		write(1, buf, n);
+	}
+	return buf;
+}
+
+static char *
+addsvc(char *args)
+{
+	if(debug)
+		reglog("Adding entry: %s", args);
+	if(resolver)
+		return resolve("add", args);
+	return rstr2cache(args, 0);
+}
+
+static char *
+rmsvc(char *args)
+{
+	if(debug)
+		reglog("Removing entry: %s", args);
+	if(resolver)
+		return resolve("rm", args);
+	return rstrdtch(args);
+}
+
+static char *
+updatesvc(char *args)
+{
+	if(debug)
+		reglog("Updating entry: %s", args);
+	if(resolver)
+		return resolve("update", args);
+	return rstrupdt(args);
+}
+
+void
+warning(char *fmt, ...)
+{
+	char regerr[256];
+	va_list arg;
+
+	va_start(arg, fmt);
+	vseprint(regerr, regerr+sizeof(regerr), fmt, arg);
+	va_end(arg);
+	syslog(1, logfile, regerr);
+}
+
+void
+reglog(char *fmt, ...)
+{
+	char regerr[256];
+	va_list arg;
+	
+	va_start(arg, fmt);
+	vseprint(regerr, regerr+sizeof(regerr), fmt, arg);
+	va_end(arg);
+	syslog(0, logfile, regerr);
+}
+
+void*
+emalloc(int size)
+{
+	void *x;
+
+	x = malloc(size);
+	if(x == nil)
+		sysfatal("out of memory");
+	memset(x, 0, size);
+	return x;
+}
+
+char*
+estrdup(char *s)
+{
+	int size;
+	char *p;
+
+	size = strlen(s);
+	p = malloc(size+1);
+	if(p == nil)
+		sysfatal("out of memory");
+	memmove(p, s, size);
+	p[size] = 0;
+	return p;
+}
+
+static int
+srvfmt(Fmt *f)
+{
+	Svc *r;
+	char mf[Maxpath+1], auth[7];
+	
+	r = va_arg(f->args, Svc*);
+	mf[0] = 0;
+	auth[0] = 0;
+		
+	if(strcmp(r->mtpt, "")!= 0)
+		snprint(mf, sizeof(r->mtpt)+1, " %s", r->mtpt);
+
+	if(strcmp(r->auth, "none")==0)
+		snprint(auth, 4, "srv");
+	else
+		snprint(auth, 7, "srvtls");
+
+	return fmtprint(f, "%s!%s!%s\n",
+		r->trns, r->host, r->port);
+}
+
+static int
+scanfmt(Fmt *f)
+{
+	Svc *r;
+	char mf[Maxpath+6]; /* pad for our tuple attrs */
+
+	mf[0] = 0;
+	r = va_arg(f->args, Svc*);
+	if(strcmp(r->mtpt, "")!=0)
+		snprint(mf, sizeof(r->mtpt)+6, " mtpt=%s", r->mtpt);
+	return fmtprint(f, "service=%s!%s!%s label='%s' auth=%s%s\n",
+		r->trns, r->host, r->port, r->labl, r->auth, mf);
+}
+
+static int
+dumpfmt(Fmt *f)
+{
+	Svc *r;
+	char mf[Maxpath+7]; /* pad for our tuple attrs */
+
+	r = va_arg(f->args, Svc*);
+	if(r->mtpt != 0)
+		snprint(mf, sizeof(r->mtpt) + 7, "\n\tmtpt=%s", r->mtpt);
+	return fmtprint(f, "service=%s!%s!%s\n\tlabel=%s\n\tauth=%s%s",
+		r->trns, r->host, r->port, r->labl, r->auth, mf);
+}
+
--- /dev/null
+++ b/reglookup.c
@@ -1,0 +1,251 @@
+#include <u.h>
+#include <libc.h>
+#include <bio.h>
+#include <ndb.h>
+#include "dns.h"
+
+static Ndb	*db;
+static QLock	dblock;
+
+int
+openregistry(void)
+{
+	if(db != nil)
+		return 0;
+
+	db = ndbopen(dbfile);
+	return db!=nil ? 0: -1;
+}
+
+static void
+attach(Svc* svc, int persist)
+{
+	svc->perm = !!persist;
+
+	if(registry != nil){
+		svc->next = registry;
+	}
+
+	svc->next = registry;
+	registry = svc;
+}
+
+static char*
+detach(char *dial)
+{
+	Svc *c, *last = 0;
+	char buf[Maxdial]; /* trns is capped at 16, port 8 */
+
+	for(c = registry; c; c = c->next){
+		snprint(buf, Maxdial, "%s!%s!%s", c->trns, c->host, c->port);
+		if(strcmp(buf, dial)==0){
+			if(last == 0)
+				registry = c->next;
+			else
+				last->next = c->next;
+			free(c);
+			return 0;
+		}
+		last = c;
+	}
+
+	return "found no matching service";
+}
+
+static void
+host2svc(Svc *svc, char *dial)
+{
+	int n;
+
+	/* 
+	 * entry host=tcp!mything!9fs
+	 * for now, tokenize but we should allow short strings
+         */
+	n = strcspn(dial, "!");
+	if(n < 1)
+		strcpy(svc->trns, "tcp");
+	else
+		strecpy(svc->trns, svc->trns+n+1, dial);
+	dial = dial + n + 1;
+
+	n = strcspn(dial, "!");
+	strecpy(svc->host, svc->host+n+1, dial);
+
+	dial = dial + n + 1;
+	if(sizeof(dial) < 1)
+		strcpy(svc->port, "9fs");
+	else if(sizeof(dial) > 8)
+		/* If this starts happening, we should bump the number */
+		strecpy(svc->port, svc->port + 8, dial);
+	else
+		strcpy(svc->port, dial);
+}
+
+static void
+dbtuple2cache(Ndbtuple *t, int persist)
+{
+	Ndbtuple *et, *nt;
+	Svc *svc;
+
+
+	for(et = t; et; et = et->entry)
+		if(strncmp(et->attr, "serv", 4)==0){
+			svc = emalloc(sizeof(*svc));
+			host2svc(svc, et->val);
+			for(nt = et->entry; nt; nt = nt->entry)
+				if(strcmp(nt->attr, "label")==0)
+					strecpy(svc->labl, svc->labl+Maxmdns, nt->val);
+				else if(strcmp(nt->attr, "auth")==0)
+					strecpy(svc->auth, svc->auth+Maxauth, nt->val);
+				else if(strcmp(nt->attr, "mtpt")==0)
+					strecpy(svc->mtpt, svc->mtpt+Maxpath, nt->val);
+			attach(svc, persist);
+		};
+}
+
+static void
+dbfile2cache(Ndb *db)
+{
+	Ndbtuple *t;
+
+	if(debug)
+		reglog("reading %s", db->file);
+	Bseek(&db->b, 0, 0);
+	while(t = ndbparse(db)){
+		dbtuple2cache(t, 1);
+		ndbfree(t);
+	}
+
+
+}
+
+Svc*
+rstr2svc(char *entry)
+{
+	Svc *svc;
+	char *args[7];
+	
+	int i, n;
+
+	n = tokenize(entry, args, 7);
+
+	svc = emalloc(sizeof(*svc));
+	host2svc(svc, estrdup(args[0]));
+
+	for(i = 1; i < n - 1; i++)
+		if(strcmp(args[i], "label")==0)	
+			strecpy(svc->labl, svc->labl+Maxmdns, args[++i]);
+		else if(strcmp(args[i], "auth")==0)
+			strecpy(svc->auth, svc->auth+Maxauth, args[++i]);
+		else if(strcmp(args[i], "mtpt")==0)
+			strecpy(svc->mtpt, svc->mtpt+Maxpath, args[++i]);
+
+	return svc;
+}
+
+char*
+rstr2cache(char *entry, int persist)
+{
+	Svc *svc;
+
+	svc = rstr2svc(entry);
+	attach(svc, persist);
+	return 0;
+}
+
+char*
+rstrdtch(char *svc)
+{
+	return detach(svc);
+}
+
+/* e.g. update tcp!foo!9fs label newlabel */
+char*
+rstrupdt(char *entry)
+{
+	Svc *c, *svc = 0;
+	char *args[7], buf[Maxdial];
+	int i, n;
+
+	n = tokenize(entry, args, 7);
+
+	/* Find our service */
+	for(c = registry; c; c = c->next){
+		snprint(buf, Maxdial, "%s!%s!%s", c->trns, c->host, c->port);
+		if(strcmp(buf, args[0])==0){
+			svc = c;
+			break;
+		}
+	}
+	
+	if(svc == 0)
+		return "found no matching service";
+
+	for(i = 1; i < n - 1; i++)
+		if(strcmp(args[i], "label")==0)
+			strecpy(svc->labl, svc->labl+Maxmdns, args[++i]);
+		else if(strcmp(args[i], "auth")==0)
+			strecpy(svc->auth, svc->auth+Maxauth, args[++i]);
+		else if(strcmp(args[i], "mtpt")==0)
+			strecpy(svc->mtpt, svc->mtpt+Maxpath, args[++i]);
+
+	return 0;
+}
+
+void
+regconnect(void)
+{
+	Ndb *adb;
+	Ndbtuple *t;
+	char *host, *list[1];
+	int rfdn, fd;
+
+	rfdn = 0;
+	adb = ndbopen(0);
+
+	/* Start with us */
+	host = getenv("sysname");
+	list[0] = "registry";
+	t = ndbipinfo(adb, "sys", host, list, 1);
+	if(t->val != nil){
+		fd = dial(t->val, 0, 0, 0);
+		if(fd >= 0)
+			rfd[rfdn++] = fd;
+	}
+	ndbfree(t);
+
+	/* Here we want to find our other registry= tuples in the ndb
+         * I don't claim to know the best way to do this. registry= entries 
+	 * in an ipnet= is the solution i see best fitting, but for now 
+         * this will be marked as TODO and left alone 
+	 * the goal will be, however, to iterate through upstream resolvers
+         * which act as lists for services one may add, like the 9ants grid
+         * registry, as a good example; and when a local resolve misses, it
+         * bubbles up to the next resolver, etc, until we exhaust the list 
+         * or find a match
+         */
+
+	if(rfdn < 1)
+		sysfatal("unable to dial any registry server");
+	rfd[rfdn] = 0;
+}
+
+void
+reg2cache(void)
+{
+	Ndb *ndb;
+	
+	qlock(&dblock);
+	if(openregistry() < 0){
+		qunlock(&dblock);
+		return;
+	}
+	
+	if(debug)
+		syslog(0, logfile, "building cache from db");
+			
+	for(ndb = db; ndb; ndb = ndb->next)
+		dbfile2cache(ndb);
+
+	qunlock(&dblock);
+}
--- /dev/null
+++ b/regquery.c
@@ -1,0 +1,67 @@
+#include <u.h>
+#include <libc.h>
+#include <bio.h>
+#include <ctype.h>
+#include <ndb.h>
+#include "dns.h"
+#include "ip.h"
+
+void
+usage(void)
+{
+	fprint(2, "usage: regquery [-s] [-f registry] query\n");
+	exits("usage");
+}
+
+static void
+queryregistry(int fd, char *line, int n)
+{
+	char buf[8192+1];
+
+	seek(fd, 0, 0);
+	write(fd, line, n);
+
+	seek(fd, 0, 0);
+	while((n = read(fd, buf, sizeof(buf)-1)) > 0)
+		write(1, buf, n);
+}
+
+static void
+query(int fd, char *q, int pipe2rc)
+{
+	char arg[260];
+
+	if(strlen(q) > 255)
+		sysfatal("query too long");
+
+	sprint(arg, "%s %s", q, (pipe2rc) ? "svc":"scan");
+	queryregistry(fd, arg, sizeof(arg));
+}
+
+void
+main(int argc, char *argv[])
+{
+	int fd, pipe2rc = 0;
+	char *rst  = "/net/registry";
+
+	ARGBEGIN {
+	case 's':
+		pipe2rc++;
+		break;
+	case 'f':
+		rst = EARGF(usage());
+		break;
+	default:
+		usage();
+	} ARGEND;
+
+	if(argc != 1)
+		usage();
+
+	fd = open(rst, ORDWR);
+	if(fd < 0)
+		sysfatal("can't open %s: %r", rst);
+
+	query(fd, argv[0], pipe2rc);
+	exits(0);
+}