Commit Diff


commit - cff43a06f21a674b2b16e72f8853aac5fd24f48d
commit + 3e0d8fb3ea83b2b65a6425c65beda887140f9349
blob - 9bbcfd319aa8c43487c22878124b4bb720ac1d82
blob + 340599a055088a72b4127d4d53c937c42399defe
--- src/cmd/ndb/mkfile
+++ src/cmd/ndb/mkfile
@@ -1,6 +1,7 @@
 <$PLAN9/src/mkhdr
 
 TARG=\
+#	dns\
 	ndbmkdb\
 	ndbquery\
 	ndbmkhash\
@@ -11,3 +12,18 @@ LIB=$PLAN9/lib/libndb.a
 
 <$PLAN9/src/mkmany
 
+DNSOFILES=\
+	convDNS2M.$O\
+	convM2DNS.$O\
+	dblookup.$O\
+	dnarea.$O\
+	dn.$O\
+	dnresolve.$O\
+	dnserver.$O\
+
+$DNSOFILES dns.$O dnstcp.$O dnsdebug.$O: dns.h
+
+$O.dns: $DNSOFILES dnnotify.$O dnudpserver.$O
+$O.dnstcp: $DNSOFILES
+$O.dnsdebug: $DNSOFILES
+
blob - /dev/null
blob + 5ee6cadbd28c640a64ba9ac223672a4ecd55b655 (mode 755)
--- /dev/null
+++ src/cmd/ndb/convDNS2M.c
@@ -0,0 +1,380 @@
+#include <u.h>
+#include <libc.h>
+#include <ip.h>
+#include <bio.h>
+#include <ndb.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[4*1024];	/* buffer for unpacked names */
+	char *ep;		/* first free char in buf */
+};
+
+#define NAME(x)		p = pname(p, ep, x, dp)
+#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;
+	memcpy(p, np, n);
+	return p + n;
+}
+
+static uchar*
+pstr(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;
+	memcpy(p, np, n);
+	return p + n;
+}
+
+static uchar*
+pbytes(uchar *p, uchar *ep, uchar *np, int n)
+{
+	if(ep - p < n)
+		return ep+1;
+	memcpy(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)
+{
+	char *cp;
+	int i;
+	char *last;		/* last component packed */
+
+	if(strlen(np) >= Domlen)	/* make sure we don't exceed DNS limits */
+		return ep+1;
+
+	last = 0;
+	while(*np){
+		/* 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;
+				*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){
+				/* 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)]){
+					strcpy(dp->ep, np);
+					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 == 0){
+			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;
+		*p++ = i;		/* count of chars in label */
+		memcpy(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;
+	int len, ttl;
+	Txt *t;
+
+	NAME(rp->owner->name);
+	USHORT(rp->type);
+	USHORT(rp->owner->class);
+
+	/* egregious overuse of ttl (it's absolute time in the cache) */
+	if(rp->db)
+		ttl = rp->ttl;
+	else
+		ttl = rp->ttl - now;
+	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 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)
+{
+	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)
+		return -1;
+
+	/* now pack the rest */
+	np = p;
+	p = buf;
+	ep = buf + len;
+	USHORT(m->id);
+	USHORT(m->flags);
+	USHORT(m->qdcount);
+	USHORT(m->ancount);
+	USHORT(m->nscount);
+	USHORT(m->arcount);
+	if(p > ep)
+		return -1;
+
+	return np - buf;
+}
blob - /dev/null
blob + 47b35616e605c800868078ae30fb6b3d043e2a17 (mode 755)
--- /dev/null
+++ src/cmd/ndb/convM2DNS.c
@@ -0,0 +1,460 @@
+#include <u.h>
+#include <libc.h>
+#include <ip.h>
+#include <bio.h>
+#include <ndb.h>
+#include "dns.h"
+
+typedef struct Scan	Scan;
+struct Scan
+{
+	uchar	*base;
+	uchar	*p;
+	uchar	*ep;
+	char	*err;
+};
+
+#define NAME(x)		gname(x, sp)
+#define SYMBOL(x)	(x = gsym(sp))
+#define STRING(x)	(x = gstr(sp))
+#define USHORT(x)	(x = gshort(sp))
+#define ULONG(x)	(x = glong(sp))
+#define UCHAR(x)	(x = gchar(sp))
+#define V4ADDR(x)	(x = gv4addr(sp))
+#define V6ADDR(x)	(x = gv6addr(sp))
+#define BYTES(x, y)	(y = gbytes(sp, &x, len - (sp->p - data)))
+
+static char *toolong = "too long";
+
+/*
+ *  get a ushort/ulong
+ */
+static ushort
+gchar(Scan *sp)
+{
+	ushort x;
+
+	if(sp->err)
+		return 0;
+	if(sp->ep - sp->p < 1){
+		sp->err = toolong;
+		return 0;
+	}
+	x = sp->p[0];
+	sp->p += 1;
+	return x;
+}
+static ushort
+gshort(Scan *sp)
+{
+	ushort x;
+
+	if(sp->err)
+		return 0;
+	if(sp->ep - sp->p < 2){
+		sp->err = toolong;
+		return 0;
+	}
+	x = (sp->p[0]<<8) | sp->p[1];
+	sp->p += 2;
+	return x;
+}
+static ulong
+glong(Scan *sp)
+{
+	ulong x;
+
+	if(sp->err)
+		return 0;
+	if(sp->ep - sp->p < 4){
+		sp->err = toolong;
+		return 0;
+	}
+	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(Scan *sp)
+{
+	char addr[32];
+
+	if(sp->err)
+		return 0;
+	if(sp->ep - sp->p < 4){
+		sp->err = toolong;
+		return 0;
+	}
+	snprint(addr, sizeof(addr), "%V", sp->p);
+	sp->p += 4;
+
+	return dnlookup(addr, Cin, 1);
+}
+static DN*
+gv6addr(Scan *sp)
+{
+	char addr[64];
+
+	if(sp->err)
+		return 0;
+	if(sp->ep - sp->p < IPaddrlen){
+		sp->err = toolong;
+		return 0;
+	}
+	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(Scan *sp)
+{
+	int n;
+	char sym[Strlen+1];
+
+	if(sp->err)
+		return 0;
+	n = *(sp->p++);
+	if(sp->p+n > sp->ep){
+		sp->err = toolong;
+		return 0;
+	}
+
+	if(n > Strlen){
+		sp->err = "illegal string";
+		return 0;
+	}
+	strncpy(sym, (char*)sp->p, n);
+	sym[n] = 0;
+	sp->p += n;
+
+	return dnlookup(sym, Csym, 1);
+}
+
+/*
+ *  get a string.  don't make it an internal symbol.
+ */
+static Txt*
+gstr(Scan *sp)
+{
+	int n;
+	char sym[Strlen+1];
+	Txt *t;
+
+	if(sp->err)
+		return 0;
+	n = *(sp->p++);
+	if(sp->p+n > sp->ep){
+		sp->err = toolong;
+		return 0;
+	}
+
+	if(n > Strlen){
+		sp->err = "illegal string";
+		return 0;
+	}
+	strncpy(sym, (char*)sp->p, n);
+	sym[n] = 0;
+	sp->p += n;
+
+	t = emalloc(sizeof(*t));
+	t->next = nil;
+	t->p = estrdup(sym);
+	return t;
+}
+
+/*
+ *  get a sequence of bytes
+ */
+static int
+gbytes(Scan *sp, uchar **p, int n)
+{
+	if(sp->err)
+		return 0;
+	if(sp->p+n > sp->ep || n < 0){
+		sp->err = toolong;
+		return 0;
+	}
+	*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, Scan *sp)
+{
+	int len, off;
+	int pointer;
+	int n;
+	char *tostart;
+	char *toend;
+	uchar *p;
+
+	tostart = to;
+	if(sp->err)
+		goto err;
+	pointer = 0;
+	p = sp->p;
+	toend = to + Domlen;
+	for(len = 0; *p; len += pointer ? 0 : (n+1)){
+		if((*p & 0xc0) == 0xc0){
+			/* pointer to other spot in message */
+			if(pointer++ > 10){
+				sp->err = "pointer loop";
+				goto err;
+			}
+			off = ((p[0]<<8) + p[1]) & 0x3ff;
+			p = sp->base + off;
+			if(p >= sp->ep){
+				sp->err = "bad pointer";
+				goto err;
+			}
+			n = 0;
+			continue;
+		}
+		n = *p++;
+		if(len + n < Domlen - 1){
+			if(to + n > toend){
+				sp->err = toolong;
+				goto err;
+			}
+			memmove(to, p, n);
+			to += n;
+		}
+		p += n;
+		if(*p){
+			if(to >= toend){
+				sp->err = toolong;
+				goto err;
+			}
+			*to++ = '.';
+		}
+	}
+	*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;
+}
+
+/*
+ *  convert the next RR from a message
+ */
+static RR*
+convM2RR(Scan *sp)
+{
+	RR *rp;
+	int type;
+	int class;
+	uchar *data;
+	int len;
+	char dname[Domlen+1];
+	Txt *t, **l;
+
+retry:
+	NAME(dname);
+	USHORT(type);
+	USHORT(class);
+
+	rp = rralloc(type);
+	rp->owner = dnlookup(dname, class, 1);
+	rp->type = type;
+
+	ULONG(rp->ttl);
+	rp->ttl += now;
+	USHORT(len);
+	data = sp->p;
+
+	if(sp->p + len > sp->ep)
+		sp->err = toolong;
+	if(sp->err){
+		rrfree(rp);
+		return 0;
+	}
+
+	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 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)
+		sp->err = "bad RR len";
+	return rp;
+}
+
+/*
+ *  convert the next question from a message
+ */
+static RR*
+convM2Q(Scan *sp)
+{
+	char dname[Domlen+1];
+	int type;
+	int class;
+	RR *rp;
+
+	NAME(dname);
+	USHORT(type);
+	USHORT(class);
+	if(sp->err)
+		return 0;
+
+	rp = rralloc(type);
+	rp->owner = dnlookup(dname, class, 1);
+
+	return rp;
+}
+
+static RR*
+rrloop(Scan *sp, int count, int quest)
+{
+	int i;
+	static char errbuf[64];
+	RR *first, *rp, **l;
+
+	if(sp->err)
+		return 0;
+	l = &first;
+	first = 0;
+	for(i = 0; i < count; i++){
+		rp = quest ? convM2Q(sp) : convM2RR(sp);
+		if(rp == 0)
+			break;
+		if(sp->err){
+			rrfree(rp);
+			break;
+		}
+		*l = rp;
+		l = &rp->next;
+	}
+	return first;
+}
+
+/*
+ *  convert the next DNS from a message stream
+ */
+char*
+convM2DNS(uchar *buf, int len, DNSmsg *m)
+{
+	Scan scan;
+	Scan *sp;
+	char *err;
+
+	scan.base = buf;
+	scan.p = buf;
+	scan.ep = buf + len;
+	scan.err = 0;
+	sp = &scan;
+	memset(m, 0, sizeof(DNSmsg));
+	USHORT(m->id);
+	USHORT(m->flags);
+	USHORT(m->qdcount);
+	USHORT(m->ancount);
+	USHORT(m->nscount);
+	USHORT(m->arcount);
+	m->qd = rrloop(sp, m->qdcount, 1);
+	m->an = rrloop(sp, m->ancount, 0);
+	m->ns = rrloop(sp, m->nscount, 0);
+	err = scan.err;				/* live with bad ar's */
+	m->ar = rrloop(sp, m->arcount, 0);
+	return err;
+}
blob - /dev/null
blob + f81f37df0a114f2abe1af665900c2b0867b7ddca (mode 755)
--- /dev/null
+++ src/cmd/ndb/dblookup.c
@@ -0,0 +1,946 @@
+#include <u.h>
+#include <libc.h>
+#include <bio.h>
+#include <ndb.h>
+#include <ip.h>
+#include "dns.h"
+
+static Ndb *db;
+
+static RR*	dblookup1(char*, int, int, int);
+static RR*	addrrr(Ndbtuple*, Ndbtuple*);
+static RR*	nsrr(Ndbtuple*, Ndbtuple*);
+static RR*	cnamerr(Ndbtuple*, Ndbtuple*);
+static RR*	mxrr(Ndbtuple*, Ndbtuple*);
+static RR*	soarr(Ndbtuple*, Ndbtuple*);
+static RR*	ptrrr(Ndbtuple*, Ndbtuple*);
+static Ndbtuple* look(Ndbtuple*, Ndbtuple*, char*);
+static RR*	doaxfr(Ndb*, char*);
+static RR*	nullrr(Ndbtuple *entry, Ndbtuple *pair);
+static RR*	txtrr(Ndbtuple *entry, Ndbtuple *pair);
+static Lock	dblock;
+static void	createptrs(void);
+
+static int	implemented[Tall] =
+{
+	[Ta]		1,
+	[Tns]		1,
+	[Tsoa]		1,
+	[Tmx]		1,
+	[Tptr]		1,
+	[Tcname]	1,
+	[Tnull]		1,
+	[Ttxt]		1,
+};
+
+static void
+nstrcpy(char *to, char *from, int len)
+{
+	strncpy(to, from, len);
+	to[len-1] = 0;
+}
+
+int
+opendatabase(void)
+{
+	char buf[256];
+	Ndb *xdb;
+
+	if(db == nil){
+		snprint(buf, sizeof(buf), "%s/ndb", mntpt);
+		xdb = ndbopen(dbfile);
+		if(xdb != nil)
+			xdb->nohash = 1;
+		db = ndbcat(ndbopen(buf), xdb);
+	}
+	if(db == nil)
+		return -1;
+	else
+		return 0;
+}
+
+/*
+ *  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)
+{
+	RR *rp, *tp;
+	char buf[256];
+	char *wild, *cp;
+	DN *dp, *ndp;
+	int err;
+	static int parallel;
+	static int parfd[2];
+	static char token[1];
+
+	/* so far only internet lookups are implemented */
+	if(class != Cin)
+		return 0;
+
+	err = Rname;
+
+	if(type == Tall){
+		rp = 0;
+		for (type = Ta; type < Tall; type++)
+			if(implemented[type])
+				rrcat(&rp, dblookup(name, class, type, auth, ttl));
+		return rp;
+	}
+
+	lock(&dblock);
+	dp = dnlookup(name, class, 1);
+	if(opendatabase() < 0)
+		goto out;
+	if(dp->rr)
+		err = 0;
+
+	/* first try the given name */
+	rp = 0;
+	if(cachedb)
+		rp = rrlookup(dp, type, NOneg);
+	else
+		rp = dblookup1(name, type, auth, ttl);
+	if(rp)
+		goto out;
+
+	/* try lower case version */
+	for(cp = name; *cp; cp++)
+		*cp = tolower(*cp);
+	if(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 = dnlookup(buf, class, 1);
+		if(ndp->rr)
+			err = 0;
+		if(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 */
+		if(err == Rname && !inmyarea(name))
+			err = Rserver;
+		dp->nonexistent = err;
+	}
+
+	unlock(&dblock);
+	return rp;
+}
+
+/*
+ *  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 = 0;
+	switch(type){
+	case Tptr:
+		attr = "ptr";
+		f = ptrrr;
+		break;
+	case Ta:
+		attr = "ip";
+		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 Tmx:
+		attr = "mx";
+		f = mxrr;
+		break;
+	case Tcname:
+		attr = "cname";
+		f = cnamerr;
+		break;
+	case Taxfr:
+	case Tixfr:
+		return doaxfr(db, name);
+	default:
+		return nil;
+	}
+
+	/*
+	 *  find a matching entry in the database
+	 */
+	free(ndbgetvalue(db, &s, "dom", name, attr, &t));
+
+	/*
+	 *  hack for local names
+	 */
+	if(t == 0 && strchr(name, '.') == 0)
+		free(ndbgetvalue(db, &s, "sys", name, attr, &t));
+	if(t == 0)
+		return nil;
+
+	/* search whole entry for default domain name */
+	strncpy(dname, name, sizeof dname);
+	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 */
+	nt = look(t, s.t, "ttl");
+	if(nt){
+		x = atoi(nt->val);
+		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(cistrcmp(attr, nt->attr) == 0){
+			rp = (*f)(t, nt);
+			rp->auth = auth;
+			rp->db = 1;
+			if(ttl)
+				rp->ttl = ttl;
+			if(dp == 0)
+				dp = dnlookup(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 && cistrcmp(attr, nt->attr) == 0){
+			rp = (*f)(t, nt);
+			rp->db = 1;
+			if(ttl)
+				rp->ttl = ttl;
+			rp->auth = auth;
+			if(dp == 0)
+				dp = dnlookup(dname, Cin, 1);
+			rp->owner = dp;
+			*l = rp;
+			l = &rp->next;
+		}
+	ndbfree(t);
+
+	return list;
+}
+
+/*
+ *  make various types of resource records from a database entry
+ */
+static RR*
+addrrr(Ndbtuple *entry, Ndbtuple *pair)
+{
+	RR *rp;
+	uchar addr[IPaddrlen];
+
+	USED(entry);
+	parseip(addr, pair->val);
+	if(isv4(addr))
+		rp = rralloc(Ta);
+	else
+		rp = rralloc(Taaaa);
+	rp->ip = dnlookup(pair->val, Cin, 1);
+	return rp;
+}
+static RR*
+nullrr(Ndbtuple *entry, Ndbtuple *pair)
+{
+	RR *rp;
+
+	USED(entry);
+	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 *entry, Ndbtuple *pair)
+{
+	RR *rp;
+	Txt *t, **l;
+	int i, len, sofar;
+
+	USED(entry);
+	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 *entry, Ndbtuple *pair)
+{
+	RR *rp;
+
+	USED(entry);
+	rp = rralloc(Tcname);
+	rp->host = dnlookup(pair->val, Cin, 1);
+	return rp;
+}
+static RR*
+mxrr(Ndbtuple *entry, Ndbtuple *pair)
+{
+	RR * rp;
+
+	rp = rralloc(Tmx);
+	rp->host = dnlookup(pair->val, Cin, 1);
+	pair = look(entry, pair, "pref");
+	if(pair)
+		rp->pref = atoi(pair->val);
+	else
+		rp->pref = 1;
+	return rp;
+}
+static RR*
+nsrr(Ndbtuple *entry, Ndbtuple *pair)
+{
+	RR *rp;
+	Ndbtuple *t;
+
+	rp = rralloc(Tns);
+	rp->host = dnlookup(pair->val, Cin, 1);
+	t = look(entry, pair, "soa");
+	if(t && t->val[0] == 0)
+		rp->local = 1;
+	return rp;
+}
+static RR*
+ptrrr(Ndbtuple *entry, Ndbtuple *pair)
+{
+	RR *rp;
+
+	USED(entry);
+	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->refresh = Day;
+	rp->soa->retry = Hour;
+	rp->soa->expire = Day;
+	rp->soa->minttl = Day;
+	t = look(entry, pair, "ttl");
+	if(t)
+		rp->soa->minttl = atoi(t->val);
+	t = look(entry, pair, "refresh");
+	if(t)
+		rp->soa->refresh = atoi(t->val);
+	t = look(entry, pair, "serial");
+	if(t)
+		rp->soa->serial = strtoul(t->val, 0, 10);
+
+	ns = look(entry, pair, "ns");
+	if(ns == 0)
+		ns = look(entry, pair, "dom");
+	rp->host = dnlookup(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 = dnlookup(mb->val, Cin, 1);
+		} else {
+			snprint(mailbox, sizeof(mailbox), "%s.%s",
+				mb->val, ns->val);
+			rp->rmb = dnlookup(mailbox, Cin, 1);
+		}
+	} else {
+		snprint(mailbox, sizeof(mailbox), "postmaster.%s",
+			ns->val);
+		rp->rmb = dnlookup(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;
+}
+
+/*
+ *  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(cistrcmp(attr, nt->attr) == 0)
+			return nt;
+		nt = nt->line;
+		if(nt == line)
+			break;
+	}
+	/* search whole tuple */
+	for(nt = entry; nt; nt = nt->entry)
+		if(cistrcmp(attr, nt->attr) == 0)
+			return nt;
+	return 0;
+}
+
+static RR**
+linkrr(RR *rp, DN *dp, RR **l)
+{
+	rp->owner = dp;
+	rp->auth = 1;
+	rp->db = 1;
+	*l = rp;
+	return &rp->next;
+}
+
+/* these are answered specially by the tcp version */
+static RR*
+doaxfr(Ndb *db, char *name)
+{
+	USED(db);
+	USED(name);
+	return 0;
+}
+
+
+/*
+ *  read the all the soa's from the database to determine area's.
+ *  this is only used when we're not caching the database.
+ */
+static void
+dbfile2area(Ndb *db)
+{
+	Ndbtuple *t;
+
+	if(debug)
+		syslog(0, logfile, "rereading %s", db->file);
+	Bseek(&db->b, 0, 0);
+	while(t = ndbparse(db)){
+		ndbfree(t);
+	}
+}
+
+/*
+ *  read the database into the cache
+ */
+static void
+dbpair2cache(DN *dp, Ndbtuple *entry, Ndbtuple *pair)
+{
+	RR *rp;
+	Ndbtuple *t;
+	static ulong ord;
+
+	rp = 0;
+	if(cistrcmp(pair->attr, "ip") == 0){
+		dp->ordinal = ord++;
+		rp = addrrr(entry, pair);
+	} else 	if(cistrcmp(pair->attr, "ns") == 0){
+		rp = nsrr(entry, pair);
+	} else if(cistrcmp(pair->attr, "soa") == 0){
+		rp = soarr(entry, pair);
+		addarea(dp, rp, pair);
+	} else if(cistrcmp(pair->attr, "mx") == 0){
+		rp = mxrr(entry, pair);
+	} else if(cistrcmp(pair->attr, "cname") == 0){
+		rp = cnamerr(entry, pair);
+	} else if(cistrcmp(pair->attr, "nullrr") == 0){
+		rp = nullrr(entry, pair);
+	} else if(cistrcmp(pair->attr, "txtrr") == 0){
+		rp = txtrr(entry, pair);
+	}
+
+	if(rp == 0)
+		return;
+
+	rp->owner = dp;
+	rp->db = 1;
+	t = look(entry, pair, "ttl");
+	if(t)
+		rp->ttl = atoi(t->val);
+	rrattach(rp, 0);
+}
+static void
+dbtuple2cache(Ndbtuple *t)
+{
+	Ndbtuple *et, *nt;
+	DN *dp;
+
+	for(et = t; et; et = et->entry){
+		if(strcmp(et->attr, "dom") == 0){
+			dp = dnlookup(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)
+		syslog(0, logfile, "rereading %s", db->file);
+	Bseek(&db->b, 0, 0);
+	while(t = ndbparse(db)){
+		dbtuple2cache(t);
+		ndbfree(t);
+	}
+}
+void
+db2cache(int doit)
+{
+	Ndb *ndb;
+	Dir *d;
+	ulong youngest, temp;
+	static ulong lastcheck;
+	static ulong lastyoungest;
+
+	/* no faster than once every 2 minutes */
+	if(now < lastcheck + 2*Min && !doit)
+		return;
+
+	refresh_areas(owned);
+
+	lock(&dblock);
+
+	if(opendatabase() < 0){
+		unlock(&dblock);
+		return;
+	}
+
+	/*
+	 *  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){
+			/* the dirfstat avoids walking the mount table each time */
+			if((d = dirfstat(Bfildes(&ndb->b))) != nil ||
+			   (d = dirstat(ndb->file)) != nil){
+				temp = d->mtime;		/* ulong vs int crap */
+				if(temp > youngest)
+					youngest = temp;
+				free(d);
+			}
+		}
+		if(!doit && youngest == lastyoungest){
+			unlock(&dblock);
+			return;
+		}
+	
+		/* 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);
+
+		if(cachedb){
+			/* mark all db records as timed out */
+			dnagedb();
+	
+			/* read in new entries */
+			for(ndb = db; ndb; ndb = ndb->next)
+				dbfile2cache(ndb);
+	
+			/* mark as authentic anything in our domain */
+			dnauthdb();
+	
+			/* remove old entries */
+			dnageall(1);
+		} else {
+			/* read all the soa's to get database defaults */
+			for(ndb = db; ndb; ndb = ndb->next)
+				dbfile2area(ndb);
+		}
+
+		doit = 0;
+		lastyoungest = youngest;
+		createptrs();
+	}
+
+	unlock(&dblock);
+}
+
+extern uchar	ipaddr[IPaddrlen];
+
+/*
+ *  get all my xxx
+ */
+Ndbtuple*
+lookupinfo(char *attr)
+{
+	char buf[64];
+	char *a[2];
+	static Ndbtuple *t;
+
+	snprint(buf, sizeof buf, "%I", ipaddr);
+	a[0] = attr;
+	
+	lock(&dblock);
+	if(opendatabase() < 0){
+		unlock(&dblock);
+		return nil;
+	}
+	t = ndbipinfo(db, "ip", buf, a, 1);
+	unlock(&dblock);
+	return t;
+}
+
+char *localservers = "local#dns#servers";
+char *localserverprefix = "local#dns#server";
+
+/*
+ *  return non-zero is this is a bad delegation
+ */
+int
+baddelegation(RR *rp, RR *nsrp, uchar *addr)
+{
+	Ndbtuple *nt;
+	static Ndbtuple *t;
+
+	if(t == nil)
+		t = lookupinfo("dom");
+	if(t == nil)
+		return 0;
+
+	for(; rp; rp = rp->next){
+		if(rp->type != Tns)
+			continue;
+
+		/* see if delegation is looping */
+		if(nsrp)
+		if(rp->owner != nsrp->owner)
+		if(subsume(rp->owner->name, nsrp->owner->name) &&
+		   strcmp(nsrp->owner->name, localservers) != 0){
+			syslog(0, logfile, "delegation loop %R -> %R from %I", nsrp, rp, addr);
+			return 1;
+		}
+
+		/* 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)){
+			syslog(0, logfile, "bad delegation %R from %I", rp, addr);
+			return 1;
+		}
+	}
+
+	return 0;
+}
+
+static void
+addlocaldnsserver(DN *dp, int class, char *ipaddr, int i)
+{
+	DN *nsdp;
+	RR *rp;
+	char buf[32];
+
+	/* ns record for name server, make up an impossible name */
+	rp = rralloc(Tns);
+	snprint(buf, sizeof(buf), "%s%d", localserverprefix, i);
+	nsdp = dnlookup(buf, class, 1);
+	rp->host = nsdp;
+	rp->owner = dp;
+	rp->local = 1;
+	rp->db = 1;
+	rp->ttl = 10*Min;
+	rrattach(rp, 1);
+
+print("dns %s\n", ipaddr);
+	/* A record */
+	rp = rralloc(Ta);
+	rp->ip = dnlookup(ipaddr, class, 1);
+	rp->owner = nsdp;
+	rp->local = 1;
+	rp->db = 1;
+	rp->ttl = 10*Min;
+	rrattach(rp, 1);
+}
+
+/*
+ *  return list of dns server addresses to use when
+ *  acting just as a resolver.
+ */
+RR*
+dnsservers(int class)
+{
+	Ndbtuple *t, *nt;
+	RR *nsrp;
+	DN *dp;
+	char *p;
+	int i, n;
+	char *buf, *args[5];
+
+	dp = dnlookup(localservers, class, 1);
+	nsrp = rrlookup(dp, Tns, NOneg);
+	if(nsrp != nil)
+		return nsrp;
+
+	p = getenv("DNSSERVER");
+	if(p != nil){
+		buf = estrdup(p);
+		n = tokenize(buf, args, nelem(args));
+		for(i = 0; i < n; i++)
+			addlocaldnsserver(dp, class, args[i], i);
+		free(buf);
+	} else {
+		t = lookupinfo("@dns");
+		if(t == nil)
+			return nil;
+		i = 0;
+		for(nt = t; nt != nil; nt = nt->entry){
+			addlocaldnsserver(dp, class, nt->val, i);
+			i++;
+		}
+		ndbfree(t);
+	}
+
+	return rrlookup(dp, Tns, NOneg);
+}
+
+static void
+addlocaldnsdomain(DN *dp, int class, char *domain)
+{
+	RR *rp;
+
+	/* A record */
+	rp = rralloc(Tptr);
+	rp->ptr = dnlookup(domain, class, 1);
+	rp->owner = dp;
+	rp->db = 1;
+	rp->ttl = 10*Min;
+	rrattach(rp, 1);
+}
+
+/*
+ *  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 areas
+ */
+static void
+createptrs(void)
+{
+	int len, dlen, n;
+	Area *s;
+	char *f[40];
+	char buf[Domlen+1];
+	uchar net[IPaddrlen];
+	uchar mask[IPaddrlen];
+	char ipa[48];
+	Ndbtuple *t, *nt;
+
+	dlen = strlen(v4ptrdom);
+	for(s = owned; s; s = s->next){
+		len = strlen(s->soarr->owner->name);
+		if(len <= dlen)
+			continue;
+		if(cistrcmp(s->soarr->owner->name+len-dlen, v4ptrdom) != 0)
+			continue;
+
+		/* get mask and net value */
+		strncpy(buf, s->soarr->owner->name, sizeof(buf));
+		buf[sizeof(buf)-1] = 0;
+		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 */
+			net[IPv4off] = atoi(f[3]);
+			net[IPv4off+1] = atoi(f[2]);
+			net[IPv4off+2] = atoi(f[1]);
+			net[IPv4off+3] = atoi(f[0]);
+			sprint(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){	/* we're confused */
+				ndbfree(t);
+				continue;
+			}
+			parseipmask(mask, nt->val);
+			n = 5;
+			break;
+		default:
+			continue;
+		}
+
+		/* go through all domain entries looking for RR's in this network and create ptrs */
+		dnptr(net, mask, s->soarr->owner->name, 6-n, 0);
+	}
+}
blob - /dev/null
blob + a38d43054fcbe4e705bdc5aee7bc1eea701968f9 (mode 755)
--- /dev/null
+++ src/cmd/ndb/dn.c
@@ -0,0 +1,1563 @@
+#include <u.h>
+#include <libc.h>
+#include <ip.h>
+#include <ctype.h>
+#include <bio.h>
+#include <ndb.h>
+#include "dns.h"
+
+/*
+ *  Hash table for domain names.  The hash is based only on the
+ *  first element of the domain name.
+ */
+DN	*ht[HTLEN];
+
+
+static struct
+{
+	Lock	lk;
+	ulong	names;	/* names allocated */
+	ulong	oldest;	/* longest we'll leave a name around */
+	int	active;
+	int	mutex;
+	int	id;
+} 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",
+[Tkey]		"key",
+[Tcert]		"cert",
+[Tsig]		"sig",
+[Taaaa]		"ipv6",
+[Tixfr]		"ixfr",
+[Taxfr]		"axfr",
+[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",
+};
+
+/* names of op codes */
+char *opname[] =
+{
+[Oquery]	"query",
+[Oinverse]	"inverse",
+[Ostatus]	"status",
+};
+
+Lock	dnlock;
+
+static int sencodefmt(Fmt*);
+
+/*
+ *  set up a pipe to use as a lock
+ */
+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;
+}
+
+/*
+ *  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 == 0){
+		unlock(&dnlock);
+		return 0;
+	}
+	dnvars.names++;
+	dp = emalloc(sizeof(*dp));
+	dp->magic = DNmagic;
+	dp->name = estrdup(name);
+	assert(dp->name != 0);
+	dp->class = class;
+	dp->rr = 0;
+	dp->next = 0;
+	dp->referenced = now;
+	*l = dp;
+	unlock(&dnlock);
+
+	return dp;
+}
+
+/*
+ *  dump the cache
+ */
+void
+dndump(char *file)
+{
+	DN *dp;
+	int i, fd;
+	RR *rp;
+
+	fd = open(file, OWRITE|OTRUNC);
+	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, "	%R %c%c %lud/%lud\n", rp, rp->auth?'A':'U',
+					rp->db?'D':'N', rp->expire, rp->ttl);
+		}
+	}
+	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);
+}
+
+/*
+ *  check the age of resource records, free any that have timed out
+ */
+void
+dnage(DN *dp)
+{
+	RR **l;
+	RR *rp, *next;
+	ulong diff;
+
+	diff = now - dp->referenced;
+	if(diff < Reserved)
+		return;
+
+	l = &dp->rr;
+	for(rp = dp->rr; rp; rp = next){
+		assert(rp->magic == RRmagic && rp->cached);
+		next = rp->next;
+		if(!rp->db)
+		if(rp->expire < now || diff > dnvars.oldest){
+			*l = next;
+			rp->cached = 0;
+			rrfree(rp);
+			continue;
+		}
+		l = &rp->next;
+	}
+}
+
+#define REF(x) if(x) x->refs++
+
+/*
+ *  our target is 4000 names cached, this should be larger on large servers
+ */
+#define TARGET 4000
+
+/*
+ *  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 && now < nextage && !doit){
+		dnvars.oldest = maxage;
+		return;
+	}
+
+	if(dnvars.names > TARGET)
+		dnvars.oldest /= 2;
+	nextage = now + maxage;
+
+	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->refs = 0;
+			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:
+					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 Tmx:
+					REF(rp->host);
+					break;
+				case Ta:
+				case Taaaa:
+					REF(rp->ip);
+					break;
+				case Tptr:
+					REF(rp->ptr);
+					break;
+				case Tsoa:
+					REF(rp->host);
+					REF(rp->rmb);
+					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 == 0 && dp->refs == 0){
+				assert(dp->magic == DNmagic);
+				*l = dp->next;
+				if(dp->name)
+					free(dp->name);
+				dp->magic = ~dp->magic;
+				dnvars.names--;
+				free(dp);
+				continue;
+			}
+			l = &dp->next;
+		}
+	}
+
+	unlock(&dnlock);
+}
+
+/*
+ *  timeout all database records (used when rereading db)
+ */
+void
+dnagedb(void)
+{
+	DN *dp;
+	int i;
+	RR *rp;
+	static ulong nextage;
+
+	lock(&dnlock);
+
+	/* time out all database entries */
+	for(i = 0; i < HTLEN; i++)
+		for(dp = ht[i]; dp; dp = dp->next)
+			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, time out any others
+ */
+void
+dnauthdb(void)
+{
+	DN *dp;
+	int i;
+	Area *area;
+	RR *rp;
+	static ulong nextage;
+
+	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);
+			for(rp = dp->rr; rp; rp = rp->next)
+				if(rp->db){
+					if(area){
+						if(rp->ttl < area->soarr->soa->minttl)
+							rp->ttl = area->soarr->soa->minttl;
+						rp->auth = 1;
+					}
+					if(rp->expire == 0){
+						rp->db = 0;
+						dp->referenced = now - Reserved - 1;
+					}
+				}
+		}
+
+	unlock(&dnlock);
+}
+
+/*
+ *  keep track of other processes to know if we can
+ *  garbage collect.  block while garbage collecting.
+ */
+int
+getactivity(Request *req)
+{
+	int rv;
+
+	if(traceactivity) syslog(0, "dns", "get %d by %d", dnvars.active, getpid());
+	lock(&dnvars.lk);
+	while(dnvars.mutex){
+		unlock(&dnvars.lk);
+		sleep(200);
+		lock(&dnvars.lk);
+	}
+	rv = ++dnvars.active;
+	now = time(0);
+	req->id = ++dnvars.id;
+	unlock(&dnvars.lk);
+
+	return rv;
+}
+void
+putactivity(void)
+{
+	static ulong lastclean;
+
+	if(traceactivity) syslog(0, "dns", "put %d by %d", dnvars.active, getpid());
+	lock(&dnvars.lk);
+	dnvars.active--;
+	assert(dnvars.active >= 0); /* "dnvars.active %d", dnvars.active */;
+
+	/*
+	 *  clean out old entries and check for new db periodicly
+	 */
+	if(dnvars.mutex || (needrefresh == 0 && dnvars.active > 0)){
+		unlock(&dnvars.lk);
+		return;
+	}
+
+	/* wait till we're alone */
+	dnvars.mutex = 1;
+	while(dnvars.active > 0){
+		unlock(&dnvars.lk);
+		sleep(100);
+		lock(&dnvars.lk);
+	}
+	unlock(&dnvars.lk);
+
+	db2cache(needrefresh);
+	dnageall(0);
+
+	/* let others back in */
+	lastclean = now;
+	needrefresh = 0;
+	dnvars.mutex = 0;
+}
+
+/*
+ *  Attach a single resource record to a domain name.
+ *	- 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
+ */
+static void
+rrattach1(RR *new, int auth)
+{
+	RR **l;
+	RR *rp;
+	DN *dp;
+
+	assert(new->magic == RRmagic && !new->cached);
+
+	if(!new->db)
+		new->expire = new->ttl;
+	else
+		new->expire = now + Year;
+	dp = new->owner;
+	assert(dp->magic == DNmagic);
+	new->auth |= auth;
+	new->next = 0;
+
+	/*
+	 *  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
+	 */
+	for(rp = *l; rp; rp = *l){
+		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){
+				*l = rp->next;
+				rp->cached = 0;
+				rrfree(rp);
+				continue;
+			}
+
+			/* all things equal, pick the newer one */
+			if(rp->arg0 == new->arg0 && rp->arg1 == new->arg1){
+				/* new drives out old */
+				if(new->ttl > rp->ttl || new->expire > rp->expire){
+					*l = rp->next;
+					rp->cached = 0;
+					rrfree(rp);
+					continue;
+				} else {
+					rrfree(new);
+					return;
+				}
+			}
+
+			/*  Hack for pointer records.  This makes sure
+			 *  the ordering in the list reflects the ordering
+			 *  received or read from the database
+			 */
+			if(rp->type == Tptr){
+				if(!rp->negative && !new->negative
+				&& rp->ptr->ordinal > new->ptr->ordinal)
+					break;
+			}
+		}
+		l = &rp->next;
+	}
+
+	/*
+	 *  add to chain
+	 */
+	new->cached = 1;
+	new->next = *l;
+	*l = new;
+}
+
+/*
+ *  Attach a list of resource records to a domain name.
+ *	- 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
+ */
+void
+rrattach(RR *rp, int auth)
+{
+	RR *next;
+
+	lock(&dnlock);
+	for(; rp; rp = next){
+		next = rp->next;
+		rp->next = 0;
+
+		/* avoid any outside spoofing */
+		if(cachedb && !rp->db && inmyarea(rp->owner->name))
+			rrfree(rp);
+		else
+			rrattach1(rp, auth);
+	}
+	unlock(&dnlock);
+}
+
+/*
+ *  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;
+	switch(type){
+	case Tsoa:
+		rp->soa = emalloc(sizeof(*rp->soa));
+		rp->soa->slaves = nil;
+		break;
+	case Tkey:
+		rp->key = emalloc(sizeof(*rp->key));
+		break;
+	case Tcert:
+		rp->cert = emalloc(sizeof(*rp->cert));
+		break;
+	case Tsig:
+		rp->sig = emalloc(sizeof(*rp->sig));
+		break;
+	case Tnull:
+		rp->null = emalloc(sizeof(*rp->null));
+		break;
+	}
+	rp->ttl = 0;
+	rp->expire = 0;
+	rp->next = 0;
+	return rp;
+}
+
+/*
+ *  free a resource record and any related structs
+ */
+void
+rrfree(RR *rp)
+{
+	DN *dp;
+	RR *nrp;
+	Txt *t;
+
+	assert(rp->magic = RRmagic);
+	assert(!rp->cached);
+
+	dp = rp->owner;
+	if(dp){
+		assert(dp->magic == DNmagic);
+		for(nrp = dp->rr; nrp; nrp = nrp->next)
+			assert(nrp != rp); /* "rrfree of live rr" */;
+	}
+
+	switch(rp->type){
+	case Tsoa:
+		freeserverlist(rp->soa->slaves);
+		free(rp->soa);
+		break;
+	case Tkey:
+		free(rp->key->data);
+		free(rp->key);
+		break;
+	case Tcert:
+		free(rp->cert->data);
+		free(rp->cert);
+		break;
+	case Tsig:
+		free(rp->sig->data);
+		free(rp->sig);
+		break;
+	case Tnull:
+		free(rp->null->data);
+		free(rp->null);
+		break;
+	case Ttxt:
+		while(rp->txt != nil){
+			t = rp->txt;
+			rp->txt = t->next;
+			free(t->p);
+			free(t);
+		}
+		break;
+	}
+
+	rp->magic = ~rp->magic;
+	free(rp);
+}
+
+/*
+ *  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);
+	}
+}
+
+extern RR**
+rrcopy(RR *rp, RR **last)
+{
+	RR *nrp;
+	SOA *soa;
+	Key *key;
+	Cert *cert;
+	Sig *sig;
+	Null *null;
+	Txt *t, *nt, **l;
+
+	nrp = rralloc(rp->type);
+	switch(rp->type){
+	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;
+	case Tsoa:
+		soa = nrp->soa;
+		*nrp = *rp;
+		nrp->soa = soa;
+		*nrp->soa = *rp->soa;
+		nrp->soa->slaves = copyserverlist(rp->soa->slaves);
+		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 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 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 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;
+	default:
+		*nrp = *rp;
+		break;
+	}
+	nrp->cached = 0;
+	nrp->next = 0;
+	*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 = 0;
+	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 an living authoritative network entry */
+	for(rp = dp->rr; rp; rp = rp->next){
+		if(!rp->db)
+		if(rp->auth)
+		if(rp->ttl + 60 > now)
+		if(tsame(type, rp->type)){
+			if(flag == NOneg && rp->negative)
+				goto out;
+			last = rrcopy(rp, last);
+		}
+	}
+	if(first)
+		goto out;
+
+	/* try for an living unauthoritative network entry */
+	for(rp = dp->rr; rp; rp = rp->next){
+		if(!rp->db)
+		if(rp->ttl + 60 > now)
+		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;
+	return atoi(atype);
+}
+
+/*
+ *  convert an integer RR type to it's ascii name
+ */
+char*
+rrname(int type, char *buf, int len)
+{
+	char *t;
+
+	t = 0;
+	if(type <= Tall)
+		t = rrtname[type];
+	if(t==0){
+		snprint(buf, len, "%d", type);
+		t = buf;
+	}
+	return t;
+}
+
+/*
+ *  return 0 if not a supported rr type
+ */
+int
+rrsupported(int type)
+{
+	if(type < 0 || type >Tall)
+		return 0;
+	return rrtname[type] != 0;
+}
+
+/*
+ *  compare 2 types
+ */
+int
+tsame(int t1, int t2)
+{
+	return t1 == t2 || t1 == Tall;
+}
+
+/*
+ *  Add resource records to a list, duplicate them if they are cached
+ *  RR's since these are shared.
+ */
+RR*
+rrcat(RR **start, RR *rp)
+{
+	RR **last;
+
+	last = start;
+	while(*last != 0)
+		last = &(*last)->next;
+
+	*last = rp;
+	return *start;
+}
+
+/*
+ *  remove negative cache rr's from an rr list
+ */
+RR*
+rrremneg(RR **l)
+{
+	RR **nl, *rp;
+	RR *first;
+
+	first = nil;
+	nl = &first;
+	while(*l != nil){
+		rp = *l;
+		if(rp->negative){
+			*l = rp->next;
+			*nl = rp;
+			nl = &rp->next;
+			*nl = nil;
+		} else
+			l = &rp->next;
+	}
+
+	return first;
+}
+
+/*
+ *  remove rr's of a particular type from an rr list
+ */
+RR*
+rrremtype(RR **l, int type)
+{
+	RR **nl, *rp;
+	RR *first;
+
+	first = nil;
+	nl = &first;
+	while(*l != nil){
+		rp = *l;
+		if(rp->type == type){
+			*l = rp->next;
+			*nl = rp;
+			nl = &rp->next;
+			*nl = nil;
+		} else
+			l = &(*l)->next;
+	}
+
+	return first;
+}
+
+/*
+ *  print conversion for rr records
+ */
+int
+rrfmt(Fmt *f)
+{
+	RR *rp;
+	char *strp;
+	Fmt fstr;
+	int rv;
+	char buf[Domlen];
+	Server *s;
+	Txt *t;
+
+	fmtstrinit(&fstr);
+
+	rp = va_arg(f->args, RR*);
+	if(rp == 0){
+		fmtprint(&fstr, "<null>");
+		goto out;
+	}
+
+	fmtprint(&fstr, "%s %s", rp->owner->name,
+		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", rp->cpu->name, rp->os->name);
+		break;
+	case Tcname:
+	case Tmb:
+	case Tmd:
+	case Tmf:
+	case Tns:
+		fmtprint(&fstr, "\t%s", rp->host->name);
+		break;
+	case Tmg:
+	case Tmr:
+		fmtprint(&fstr, "\t%s", rp->mb->name);
+		break;
+	case Tminfo:
+		fmtprint(&fstr, "\t%s %s", rp->mb->name, rp->rmb->name);
+		break;
+	case Tmx:
+		fmtprint(&fstr, "\t%lud %s", rp->pref, rp->host->name);
+		break;
+	case Ta:
+	case Taaaa:
+		fmtprint(&fstr, "\t%s", rp->ip->name);
+		break;
+	case Tptr:
+//		fmtprint(&fstr, "\t%s(%lud)", rp->ptr->name, rp->ptr->ordinal);
+		fmtprint(&fstr, "\t%s", rp->ptr->name);
+		break;
+	case Tsoa:
+		fmtprint(&fstr, "\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);
+		for(s = rp->soa->slaves; s != nil; s = s->next)
+			fmtprint(&fstr, " %s", s->name);
+		break;
+	case Tnull:
+		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", rp->rmb->name, rp->rp->name);
+		break;
+	case Tkey:
+		fmtprint(&fstr, "\t%d %d %d", rp->key->flags, rp->key->proto,
+			rp->key->alg);
+		break;
+	case Tsig:
+		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, rp->sig->signer->name);
+		break;
+	case Tcert:
+		fmtprint(&fstr, "\t%d %d %d",
+			rp->sig->type, rp->sig->tag, rp->sig->alg);
+		break;
+	default:
+		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)
+{
+	RR *rp;
+	char *strp;
+	Fmt fstr;
+	int rv;
+	Server *s;
+	Txt *t;
+	int quote;
+
+	fmtstrinit(&fstr);
+
+	rp = va_arg(f->args, RR*);
+	if(rp == 0){
+		fmtprint(&fstr, "<null>");
+		goto out;
+	}
+
+	if(rp->type == Tptr)
+		fmtprint(&fstr, "ptr=%s", rp->owner->name);
+	else
+		fmtprint(&fstr, "dom=%s", rp->owner->name);
+
+	switch(rp->type){
+	case Thinfo:
+		fmtprint(&fstr, " cpu=%s os=%s", rp->cpu->name, rp->os->name);
+		break;
+	case Tcname:
+		fmtprint(&fstr, " cname=%s", rp->host->name);
+		break;
+	case Tmb:
+	case Tmd:
+	case Tmf:
+		fmtprint(&fstr, " mbox=%s", rp->host->name);
+		break;
+	case Tns:
+		fmtprint(&fstr,  " ns=%s", rp->host->name);
+		break;
+	case Tmg:
+	case Tmr:
+		fmtprint(&fstr, " mbox=%s", rp->mb->name);
+		break;
+	case Tminfo:
+		fmtprint(&fstr, " mbox=%s mbox=%s", rp->mb->name, rp->rmb->name);
+		break;
+	case Tmx:
+		fmtprint(&fstr, " pref=%lud mx=%s", rp->pref, rp->host->name);
+		break;
+	case Ta:
+	case Taaaa:
+		fmtprint(&fstr, " ip=%s", rp->ip->name);
+		break;
+	case Tptr:
+		fmtprint(&fstr, " dom=%s", rp->ptr->name);
+		break;
+	case Tsoa:
+		fmtprint(&fstr, " ns=%s mbox=%s serial=%lud refresh=%lud retry=%lud expire=%lud ttl=%lud",
+			rp->host->name, rp->rmb->name, rp->soa->serial,
+			rp->soa->refresh, rp->soa->retry,
+			rp->soa->expire, rp->soa->minttl);
+		for(s = rp->soa->slaves; s != nil; s = s->next)
+			fmtprint(&fstr, " dnsslave=%s", s->name);
+		break;
+	case Tnull:
+		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", rp->rmb->name, rp->rp->name);
+		break;
+	case Tkey:
+		fmtprint(&fstr, " flags=%d proto=%d alg=%d",
+			rp->key->flags, rp->key->proto, rp->key->alg);
+		break;
+	case Tsig:
+		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, rp->sig->signer->name);
+		break;
+	case Tcert:
+		fmtprint(&fstr, " type=%d tag=%d alg=%d",
+			rp->sig->type, rp->sig->tag, rp->sig->alg);
+		break;
+	default:
+		break;
+	}
+out:
+	strp = fmtstrflush(&fstr);
+	rv = fmtstrcpy(f, strp);
+	free(strp);
+	return rv;
+}
+
+void
+warning(char *fmt, ...)
+{
+	char dnserr[128];
+	va_list arg;
+
+	va_start(arg, fmt);
+	vseprint(dnserr, dnserr+sizeof(dnserr), fmt, arg);
+	va_end(arg);
+	syslog(1, "dns", dnserr);
+}
+
+/*
+ *  create a slave process to handle a request to avoid one request blocking
+ *  another
+ */
+void
+slave(Request *req)
+{
+	static int slaveid;
+
+	if(req->isslave)
+		return;		/* we're already a slave process */
+
+	/* limit parallelism */
+	if(getactivity(req) > Maxactive){
+		putactivity();
+		return;
+	}
+
+	switch(rfork(RFPROC|RFNOTEG|RFMEM|RFNOWAIT)){
+	case -1:
+		putactivity();
+		break;
+	case 0:
+		req->isslave = 1;
+		break;
+	default:
+		longjmp(req->mret, 1);
+	}
+}
+
+/*
+ *  chasing down double free's
+ */
+void
+dncheck(void *p, int dolock)
+{
+	int i;
+	DN *dp;
+	RR *rp;
+
+	if(p != nil){
+		dp = p;
+		assert(dp->magic == DNmagic);
+	}
+
+	if(!testing)
+		return;
+
+	if(dolock)
+		lock(&dnlock);
+	for(i = 0; i < HTLEN; i++)
+		for(dp = ht[i]; dp; dp = dp->next){
+			assert(dp != p);
+			assert(dp->magic == DNmagic);
+			for(rp = dp->rr; rp; rp = rp->next){
+				assert(rp->magic == RRmagic);
+				assert(rp->cached);
+				assert(rp->owner == dp);
+			}
+		}
+	if(dolock)
+		unlock(&dnlock);
+}
+
+static int
+rrequiv(RR *r1, RR *r2)
+{
+	return r1->owner == r2->owner
+		&& r1->type == r2->type
+		&& r1->arg0 == r2->arg0
+		&& r1->arg1 == r2->arg1;
+}
+
+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)
+		return 0;
+
+	if(cistrcmp(lower + ln - hn, higher) != 0)
+		return 0;
+
+	if(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 and mx's */
+	for(x = rp; x; x = x->next)
+		if(x->type != Ta && 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)
+{
+	char *out;
+	char *buf;
+	int i, len;
+	int ilen;
+	int rv;
+	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;
+	switch(f->r){
+	case '<':
+		len = (8*ilen+4)/5 + 3;
+		break;
+	case '[':
+		len = (8*ilen+5)/6 + 4;
+		break;
+	case 'H':
+		len = 2*ilen + 1;
+		break;
+	default:
+		goto error;
+	}
+
+	if(len > sizeof(obuf)){
+		buf = malloc(len);
+		if(buf == nil)
+			goto error;
+	} else
+		buf = obuf;
+
+	// convert
+	out = buf;
+	switch(f->r){
+	case '<':
+		rv = enc32(out, len, b, ilen);
+		break;
+	case '[':
+		rv = enc64(out, len, b, ilen);
+		break;
+	case 'H':
+		rv = enc16(out, len, b, ilen);
+		break;
+	default:
+		rv = -1;
+		break;
+	}
+	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)+1;
+	p = mallocz(size, 0);
+	if(p == nil)
+		abort();
+	memmove(p, s, size);
+	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;
+}
+
+/*
+ *  look for all ip addresses in this network and make
+ *  pointer records for them.
+ */
+void
+dnptr(uchar *net, uchar *mask, char *dom, int bytes, int ttl)
+{
+	int i, j;
+	DN *dp;
+	RR *rp, *nrp, *first, **l;
+	uchar ip[IPaddrlen];
+	uchar nnet[IPaddrlen];
+	char ptr[Domlen];
+	char *p, *e;
+
+	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 != Ta || rp->negative)
+					continue;
+				parseip(ip, rp->ip->name);
+				maskip(ip, mask, nnet);
+				if(ipcmp(net, nnet) != 0)
+					continue;
+				p = ptr;
+				e = ptr+sizeof(ptr);
+				for(j = IPaddrlen-1; j >= IPaddrlen-bytes; j--)
+					p = seprint(p, e, "%d.", ip[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;
+		rrattach(rp, 1);
+	}
+}
+
+void
+freeserverlist(Server *s)
+{
+	Server *next;
+
+	for(; s != nil; s = next){
+		next = s->next;
+		free(s);
+	}
+}
+
+void
+addserver(Server **l, char *name)
+{
+	Server *s;
+
+	while(*l)
+		l = &(*l)->next;
+	s = malloc(sizeof(Server)+strlen(name)+1);
+	if(s == nil)
+		return;
+	s->name = (char*)(s+1);
+	strcpy(s->name, name);
+	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;
+}
blob - /dev/null
blob + 15e0e849c0b3b7f06e808036ad63f0901fc4061b (mode 755)
--- /dev/null
+++ src/cmd/ndb/dnarea.c
@@ -0,0 +1,130 @@
+#include <u.h>
+#include <libc.h>
+#include <bio.h>
+#include <ndb.h>
+#include <ip.h>
+#include "dns.h"
+
+Area *owned;
+Area *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 == 0)
+		return 0;
+
+	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 0;
+	}
+
+	return s;
+}
+
+/*
+ *  our area is the part of the domain tree that
+ *  we serve
+ */
+void
+addarea(DN *dp, RR *rp, Ndbtuple *t)
+{
+	Area **l, *s;
+
+	if(t->val[0])
+		l = &delegated;
+	else
+		l = &owned;
+
+	/*
+	 *  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;
+
+syslog(0, logfile, "new area %s", dp->name);
+
+	s->next = *l;
+	*l = s;
+}
+
+void
+freearea(Area **l)
+{
+	Area *s;
+
+	while(s = *l){
+		*l = s->next;
+		rrfree(s->soarr);
+		free(s);
+	}
+}
+
+/*
+ * 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;
+		}
+
+		switch(pid = fork()){
+		case -1:
+			break;
+		case 0:
+			execl(zonerefreshprogram, "zonerefresh", s->soarr->owner->name, 0);
+			exits(0);
+			break;
+		default:
+			for(;;){
+				w = wait();
+				if(w == nil)
+					break;
+				if(w->pid == pid){
+					if(w->msg == nil || *w->msg == 0)
+						s->needrefresh = 0;
+					free(w);
+					break;
+				}
+				free(w);
+			}
+		}
+	}
+}
blob - /dev/null
blob + a5d91005bd9c36119a3bee423d1c98343d060066 (mode 755)
--- /dev/null
+++ src/cmd/ndb/dnnotify.c
@@ -0,0 +1,160 @@
+#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 *r)
+{
+	RR *tp;
+	Area *a;
+
+	USED(r);
+	/* 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;
+
+syslog(0, logfile, "notification for %s", repp->qd->owner->name);
+
+	/* is it something we care about? */
+	a = inmyarea(repp->qd->owner->name);
+	if(a == nil)
+		return;
+
+syslog(0, logfile, "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)
+		return;
+
+	a->needrefresh = 1;
+}
+
+static void
+ding(void *u, char *msg)
+{
+	USED(u);
+
+	if(strstr(msg, "alarm"))
+		noted(NCONT);
+	else
+		noted(NDFLT);
+}
+
+/* notify a slave that an area has changed. */
+static void
+send_notify(char *slave, RR *soa, Request *req)
+{
+	int i, len, n, reqno, status, fd;
+	uchar obuf[Maxudp+OUdphdrsize];
+	uchar ibuf[Maxudp+OUdphdrsize];
+	RR *rp;
+	OUdphdr *up = (OUdphdr*)obuf;
+	char *err;
+	DNSmsg repmsg;
+
+	/* create the request */
+	reqno = rand();
+	n = mkreq(soa->owner, Cin, obuf, Fauth | Onotify, reqno);
+
+	/* get an address */
+	if(strcmp(ipattr(slave), "ip") == 0) {
+		parseip(up->raddr, slave);
+	} else {
+		rp = dnresolve(slave, Cin, Ta, req, nil, 0, 1, 1, &status);
+		if(rp == nil)
+			return;
+		parseip(up->raddr, rp->ip->name);
+		rrfree(rp);
+	}
+
+	fd = udpport();
+	if(fd < 0)
+		return;
+
+	notify(ding);
+
+	/* send 3 times or until we get anything back */
+	for(i = 0; i < 3; i++){
+syslog(0, logfile, "sending %d byte notify to %s/%I.%d about %s", n, slave, up->raddr, nhgets(up->rport), soa->owner->name);
+		if(udpwrite(fd, (OUdphdr*)obuf, obuf+OUdphdrsize, n) != n)
+			break;
+		alarm(2*1000);
+		len = udpread(fd, (Udphdr*)ibuf, ibuf+OUdphdrsize, Maxudp);
+		alarm(0);
+		if(len <= OUdphdrsize)
+			continue;
+		err = convM2DNS(&ibuf[OUdphdrsize], len, &repmsg);
+		if(err != nil)
+			continue;
+		if(repmsg.id == reqno && (repmsg.flags & Omask) == Onotify)
+			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;
+	static int already;
+
+	if(already)
+		return;
+
+	switch(rfork(RFPROC|RFNOTEG|RFMEM|RFNOWAIT)){
+	case -1:
+		return;
+	case 0:
+		break;
+	default:
+		return;
+	}
+
+	req.isslave = 1;	/* son't fork off subprocesses */
+
+	for(;;){
+		getactivity(&req);
+		notify_areas(owned, &req);
+		putactivity();
+		sleep(60*1000);
+	}
+}
blob - /dev/null
blob + 253daca4254e6a91bcc198a7db55d561ab631028 (mode 755)
--- /dev/null
+++ src/cmd/ndb/dnresolve.c
@@ -0,0 +1,753 @@
+#include <u.h>
+#include <libc.h>
+#include <ip.h>
+#include <bio.h>
+#include <ndb.h>
+#include "dns.h"
+
+enum
+{
+	Maxdest=	24,	/* maximum destinations for a request message */
+	Maxtrans=	3,	/* maximum transmissions to a server */
+};
+
+static int	netquery(DN*, int, RR*, Request*, int);
+static RR*	dnresolve1(char*, int, int, Request*, int, int);
+
+char *LOG = "dns";
+
+/*
+ *  lookup 'type' info for domain name 'name'.  If it doesn't exist, try
+ *  looking it up as a canonical name.
+ */
+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 nname[Domlen];
+
+	if(status)
+		*status = 0;
+
+	/*
+	 *  hack for systems that don't have resolve search
+	 *  lists.  Just look up the simple name in the database.
+	 */
+	if(!rooted && strchr(name, '.') == 0){
+		rp = nil;
+		drp = domainlist(class);
+		for(nrp = drp; nrp != nil; nrp = nrp->next){
+			snprint(nname, sizeof(nname), "%s.%s", name, nrp->ptr->name);
+			rp = dnresolve(nname, class, type, req,cn, depth, recurse, rooted, status);
+			rrfreelist(rrremneg(&rp));
+			if(rp != nil)
+				break;
+		}
+		if(drp != nil)
+			rrfree(drp);
+		return rp;
+	}
+
+	/*
+	 *  try the name directly
+	 */
+	rp = dnresolve1(name, class, type, req, depth, recurse);
+	if(rp)
+		return randomize(rp);
+
+	/* try it as a canonical name if we weren't told the name didn't exist */
+	dp = dnlookup(name, class, 0);
+	if(type != Tptr && dp->nonexistent != Rname){
+		for(loops=0; rp == nil && loops < 32; loops++){
+			rp = dnresolve1(name, class, Tcname, req, depth, recurse);
+			if(rp == nil)
+				break;
+
+			if(rp->negative){
+				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 == 0 && status != 0 && dp->nonexistent != 0)
+		*status = dp->nonexistent;
+
+	return randomize(rp);
+}
+
+static RR*
+dnresolve1(char *name, int class, int type, Request *req, int depth, int recurse)
+{
+	DN *dp, *nsdp;
+	RR *rp, *nsrp, *dbnsrp;
+	char *cp;
+
+	if(debug)
+		syslog(0, LOG, "dnresolve1 %s %d %d", name, type, class);
+
+	/* only class Cin implemented so far */
+	if(class != Cin)
+		return 0;
+
+	dp = dnlookup(name, class, 1);
+
+	/*
+	 *  Try the cache first
+	 */
+	rp = rrlookup(dp, type, OKneg);
+	if(rp){
+		if(rp->db){
+			/* unauthenticated db entries are hints */
+			if(rp->auth)
+				return rp;
+		} else {
+			/* cached entry must still be valid */
+			if(rp->ttl > now){
+				/* but Tall entries are special */
+				if(type != Tall || rp->query == Tall)
+					return rp;
+			}
+		}
+	}
+	rrfreelist(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)
+			return 0;
+	}
+
+	/*
+	 *  if we're running as just a resolver, go to our
+	 *  designated name servers
+	 */
+	if(resolver){
+		nsrp = randomize(getdnsservers(class));
+		if(nsrp != nil) {
+			if(netquery(dp, type, nsrp, req, depth+1)){
+				rrfreelist(nsrp);
+				return rrlookup(dp, type, OKneg);
+			}
+			rrfreelist(nsrp);
+		}
+	}
+
+	/*
+ 	 *  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, 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 = dnlookup(cp, class, 0);
+		nsrp = nil;
+		if(nsdp)
+			nsrp = randomize(rrlookup(nsdp, Tns, NOneg));
+
+		/* if the entry timed out, ignore it */
+		if(nsrp && nsrp->ttl < now){
+			rrfreelist(nsrp);
+			nsrp = nil;
+		}
+
+		if(nsrp){
+			rrfreelist(dbnsrp);
+
+			/* try the name servers found in cache */
+			if(netquery(dp, type, nsrp, req, depth+1)){
+				rrfreelist(nsrp);
+				return rrlookup(dp, type, OKneg);
+			}
+			rrfreelist(nsrp);
+			continue;
+		}
+
+		/* use ns from db */
+		if(dbnsrp){
+			/* try the name servers found in db */
+			if(netquery(dp, type, dbnsrp, req, depth+1)){
+				/* we got an answer */
+				rrfreelist(dbnsrp);
+				return rrlookup(dp, type, NOneg);
+			}
+			rrfreelist(dbnsrp);
+		}
+	}
+
+	/* settle for a non-authoritative answer */
+	rp = rrlookup(dp, type, OKneg);
+	if(rp)
+		return rp;
+
+	/* noone answered.  try the database, we might have a chance. */
+	return dblookup(name, class, type, 0, 0);
+}
+
+/*
+ *  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 udpport for requests and replies. 
+ */
+int
+udpport(void)
+{
+	int fd;
+
+	if((fd = dial("udp!0!53", nil, nil, nil)) < 0)
+		warning("can't get udp port");
+	return fd;
+}
+
+int
+mkreq(DN *dp, int type, uchar *buf, int flags, ushort reqno)
+{
+	DNSmsg m;
+	int len;
+	OUdphdr *uh = (OUdphdr*)buf;
+
+	/* stuff port number into output buffer */
+	memset(uh, 0, sizeof(*uh));
+	hnputs(uh->rport, 53);
+
+	/* make request and convert it to output format */
+	memset(&m, 0, sizeof(m));
+	m.flags = flags;
+	m.id = reqno;
+	m.qd = rralloc(type);
+	m.qd->owner = dp;
+	m.qd->type = type;
+	len = convDNS2M(&m, &buf[OUdphdrsize], Maxudp);
+	if(len < 0)
+		abort(); /* "can't convert" */;
+	rrfree(m.qd);
+	return len;
+}
+
+/* for alarms in readreply */
+static void
+ding(void *x, char *msg)
+{
+	USED(x);
+	if(strcmp(msg, "alarm") == 0)
+		noted(NCONT);
+	else
+		noted(NDFLT);
+}
+
+static void
+freeanswers(DNSmsg *mp)
+{
+	rrfreelist(mp->qd);
+	rrfreelist(mp->an);
+	rrfreelist(mp->ns);
+	rrfreelist(mp->ar);
+}
+
+/*
+ *  read replies to a request.  ignore any of the wrong type.  wait at most 5 seconds.
+ */
+static int
+readreply(int fd, DN *dp, int type, ushort req,
+	  uchar *ibuf, DNSmsg *mp, ulong endtime, Request *reqp)
+{
+	char *err;
+	int len;
+	ulong now;
+	RR *rp;
+
+	notify(ding);
+
+	for(; ; freeanswers(mp)){
+		now = time(0);
+		if(now >= endtime)
+			return -1;	/* timed out */
+
+		/* timed read */
+		alarm((endtime - now) * 1000);
+		len = udpread(fd, (OUdphdr*)ibuf, ibuf+OUdphdrsize, Maxudpin);
+		alarm(0);
+		if(len < 0)
+			return -1;	/* timed out */
+		
+		/* convert into internal format  */
+		memset(mp, 0, sizeof(*mp));
+		err = convM2DNS(&ibuf[OUdphdrsize], len, mp);
+		if(err){
+			syslog(0, LOG, "input err %s: %I", err, ibuf);
+			continue;
+		}
+		if(debug)
+			logreply(reqp->id, ibuf, mp);
+
+		/* answering the right question? */
+		if(mp->id != req){
+			syslog(0, LOG, "%d: id %d instead of %d: %I", reqp->id,
+					mp->id, req, ibuf);
+			continue;
+		}
+		if(mp->qd == 0){
+			syslog(0, LOG, "%d: no question RR: %I", reqp->id, ibuf);
+			continue;
+		}
+		if(mp->qd->owner != dp){
+			syslog(0, LOG, "%d: owner %s instead of %s: %I", reqp->id,
+				mp->qd->owner->name, dp->name, ibuf);
+			continue;
+		}
+		if(mp->qd->type != type){
+			syslog(0, LOG, "%d: type %d instead of %d: %I", reqp->id,
+				mp->qd->type, type, ibuf);
+			continue;
+		}
+
+		/* remember what request this is in answer to */
+		for(rp = mp->an; rp; rp = rp->next)
+			rp->query = type;
+
+		return 0;
+	}
+
+	return 0;	/* never reached */
+}
+
+/*
+ *	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 == 0)
+			return 0;
+	}
+
+	return 1;
+}
+
+
+typedef struct Dest	Dest;
+struct Dest
+{
+	uchar	a[IPaddrlen];	/* ip address */
+	DN	*s;		/* name server */
+	int	nx;		/* number of transmissions */
+	int	code;
+};
+
+
+/*
+ *  return multicast version if any
+ */
+int
+ipisbm(uchar *ip)
+{
+	if(isv4(ip)){
+		if(ip[IPv4off] >= 0xe0 && ip[IPv4off] < 0xf0)
+			return 4;
+		if(ipcmp(ip, IPv4bcast) == 0)
+			return 4;
+	} else {
+		if(ip[0] == 0xff)
+			return 6;
+	}
+	return 0;
+}
+
+/*
+ *  Get next server address
+ */
+static int
+serveraddrs(RR *nsrp, Dest *dest, int nd, int depth, Request *reqp)
+{
+	RR *rp, *arp, *trp;
+	Dest *cur;
+
+	if(nd >= Maxdest)
+		return 0;
+
+	/*
+	 *  look for a server whose address we already know.
+	 *  if we find one, mark it so we ignore this on
+	 *  subsequent passes.
+	 */
+	arp = 0;
+	for(rp = nsrp; rp; rp = rp->next){
+		assert(rp->magic == RRmagic);
+		if(rp->marker)
+			continue;
+		arp = rrlookup(rp->host, Ta, NOneg);
+		if(arp){
+			rp->marker = 1;
+			break;
+		}
+		arp = dblookup(rp->host->name, Cin, Ta, 0, 0);
+		if(arp){
+			rp->marker = 1;
+			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 == 0){
+		for(rp = nsrp; rp; rp = rp->next){
+			if(rp->marker)
+				continue;
+			rp->marker = 1;
+
+			/*
+			 *  avoid loops looking up a server under itself
+			 */
+			if(subsume(rp->owner->name, rp->host->name))
+				continue;
+
+			arp = dnresolve(rp->host->name, Cin, Ta, reqp, 0, depth+1, Recurse, 1, 0);
+			rrfreelist(rrremneg(&arp));
+			if(arp)
+				break;
+		}
+	}
+
+	/* use any addresses that we found */
+	for(trp = arp; trp; trp = trp->next){
+		if(nd >= Maxdest)
+			break;
+		cur = &dest[nd];
+		parseip(cur->a, trp->ip->name);
+		if(ipisbm(cur->a))
+			continue;
+		cur->nx = 0;
+		cur->s = trp->owner;
+		cur->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;
+
+	/* no cache time specified, don' make anything up */
+	if(soarr != nil){
+		if(soarr->next != nil){
+			rrfreelist(soarr->next);
+			soarr->next = nil;
+		}
+		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+now;
+	else
+		ttl = 5*Min;
+
+	/* add soa and negative RR to the database */
+	rrattach(soarr, 1);
+
+	rp = rralloc(type);
+	rp->owner = dp;
+	rp->negative = 1;
+	rp->negsoaowner = soaowner;
+	rp->negrcode = rcode;
+	rp->ttl = ttl;
+	rrattach(rp, 1);
+}
+
+/*
+ *  query name servers.  If the name server returns a pointer to another
+ *  name server, recurse.
+ */
+static int
+netquery1(int fd, DN *dp, int type, RR *nsrp, Request *reqp, int depth, uchar *ibuf, uchar *obuf)
+{
+	int ndest, j, len, replywaits, rv;
+	ushort req;
+	RR *tp, *soarr;
+	Dest *p, *l, *np;
+	DN *ndp;
+	Dest dest[Maxdest];
+	DNSmsg m;
+	ulong endtime;
+
+	/* pack request into a message */
+	req = rand();
+	len = mkreq(dp, type, obuf, Frecurse|Oquery, req);
+
+	/* no server addresses yet */
+	l = dest;
+
+	/*
+	 *  transmit requests and wait for answers.
+	 *  at most Maxtrans attempts to each address.
+	 *  each cycle send one more message than the previous.
+	 */
+	for(ndest = 1; ndest < Maxdest; ndest++){
+		p = dest;
+
+		endtime = time(0);
+		if(endtime >= reqp->aborttime)
+			break;
+
+		/* get a server address if we need one */
+		if(ndest > l - p){
+			j = serveraddrs(nsrp, dest, l - p, depth, reqp);
+			l = &dest[j];
+		}
+
+		/* no servers, punt */
+		if(l == dest)
+			break;
+
+		/* send to first 'ndest' destinations */
+		j = 0;
+		for(; p < &dest[ndest] && p < l; p++){
+			/* skip destinations we've finished with */
+			if(p->nx >= Maxtrans)
+				continue;
+
+			j++;
+
+			/* exponential backoff of requests */
+			if((1<<p->nx) > ndest)
+				continue;
+
+			memmove(obuf, p->a, sizeof(p->a));
+			if(debug)
+				logsend(reqp->id, depth, obuf, p->s->name,
+					dp->name, type);
+{Udphdr *uh = (Udphdr*)obuf;
+print("send %I %I %d %d\n", uh->raddr, uh->laddr, nhgets(uh->rport), nhgets(uh->lport));
+}
+			if(udpwrite(fd, (OUdphdr*)obuf, obuf+OUdphdrsize, len) < 0)
+				warning("sending udp msg %r");
+			p->nx++;
+		}
+		if(j == 0)
+			break;		/* no destinations left */
+
+		/* wait up to 5 seconds for replies */
+		endtime = time(0) + 5;
+		if(endtime > reqp->aborttime)
+			endtime = reqp->aborttime;
+
+		for(replywaits = 0; replywaits < ndest; replywaits++){
+			if(readreply(fd, dp, type, req, ibuf, &m, endtime, reqp) < 0)
+				break;		/* timed out */
+
+			/* find responder */
+			for(p = dest; p < l; p++)
+				if(memcmp(p->a, ibuf, sizeof(p->a)) == 0)
+					break;
+
+			/* remove all addrs of responding server from list */
+			for(np = dest; np < l; np++)
+				if(np->s == p->s)
+					p->nx = Maxtrans;
+
+			/* ignore any error replies */
+			if((m.flags & Rmask) == Rserver){
+				rrfreelist(m.qd);
+				rrfreelist(m.an);
+				rrfreelist(m.ar);
+				rrfreelist(m.ns);
+				if(p != l)
+					p->code = Rserver;
+				continue;
+			}
+
+			/* ignore any bad delegations */
+			if(m.ns && baddelegation(m.ns, nsrp, ibuf)){
+				rrfreelist(m.ns);
+				m.ns = nil;
+				if(m.an == nil){
+					rrfreelist(m.qd);
+					rrfreelist(m.ar);
+					if(p != l)
+						p->code = Rserver;
+					continue;
+				}
+			}
+
+
+			/* remove any soa's from the authority section */
+			soarr = rrremtype(&m.ns, Tsoa);
+
+			/* incorporate answers */
+			if(m.an)
+				rrattach(m.an, (m.flags & Fauth) ? 1 : 0);
+			if(m.ar)
+				rrattach(m.ar, 0);
+			if(m.ns){
+				ndp = m.ns->owner;
+				rrattach(m.ns, 0);
+			} else
+				ndp = 0;
+
+			/* free the question */
+			if(m.qd)
+				rrfreelist(m.qd);
+
+			/*
+			 *  Any reply from an authoritative server,
+			 *  or a positive reply terminates the search
+			 */
+			if(m.an != nil || (m.flags & Fauth)){
+				if(m.an == nil && (m.flags & Rmask) == Rname)
+					dp->nonexistent = Rname;
+				else
+					dp->nonexistent = 0;
+
+				/*
+				 *  cache any negative responses, free soarr
+				 */
+				if((m.flags & Fauth) && m.an == nil)
+					cacheneg(dp, type, (m.flags & Rmask), soarr);
+				else
+					rrfreelist(soarr);
+				return 1;
+			}
+			rrfreelist(soarr);
+
+			/*
+			 *  if we've been given better name servers
+			 *  recurse
+			 */
+			if(m.ns){
+				tp = rrlookup(ndp, Tns, NOneg);
+				if(!contains(nsrp, tp)){
+					rv = netquery(dp, type, tp, reqp, depth+1);
+					rrfreelist(tp);
+					return rv;
+				} else
+					rrfreelist(tp);
+			}
+		}
+	}
+
+	/* if all servers returned failure, propogate it */
+	dp->nonexistent = Rserver;
+	for(p = dest; p < l; p++)
+		if(p->code != Rserver)
+			dp->nonexistent = 0;
+
+	return 0;
+}
+
+typedef struct Qarg Qarg;
+struct Qarg
+{
+	DN *dp;
+	int type;
+	RR *nsrp;
+	Request *reqp;
+	int depth;
+};
+
+
+static int
+netquery(DN *dp, int type, RR *nsrp, Request *reqp, int depth)
+{
+	uchar *obuf;
+	uchar *ibuf;
+	RR *rp;
+	int fd, rv;
+
+	if(depth > 12)
+		return 0;
+
+	/* use alloced buffers rather than ones from the stack */
+	ibuf = emalloc(Maxudpin+OUdphdrsize);
+	obuf = emalloc(Maxudp+OUdphdrsize);
+
+	slave(reqp);
+
+	/* prepare server RR's for incremental lookup */
+	for(rp = nsrp; rp; rp = rp->next)
+		rp->marker = 0;
+
+	fd = udpport();
+	if(fd < 0)
+		return 0;
+	rv = netquery1(fd, dp, type, nsrp, reqp, depth, ibuf, obuf);
+	close(fd);
+	free(ibuf);
+	free(obuf);
+
+	return rv;
+}
blob - /dev/null
blob + 5f509f24f722f78d991f6e57b3cc20aacae8338b (mode 755)
--- /dev/null
+++ src/cmd/ndb/dns.c
@@ -0,0 +1,882 @@
+#include <u.h>
+#include <libc.h>
+#include <auth.h>
+#include <fcall.h>
+#include <bio.h>
+#include <ctype.h>
+#include <ip.h>
+#include <ndb.h>
+#include "dns.h"
+
+enum
+{
+	Maxrequest=		1024,
+	Ncache=			8,
+	Maxpath=		128,
+	Maxreply=		512,
+	Maxrrr=			16,
+	Maxfdata=		8192,
+
+	Qdir=			0,
+	Qdns=			1,
+};
+
+typedef struct Mfile	Mfile;
+typedef struct Job	Job;
+typedef struct Network	Network;
+
+int vers;		/* incremented each clone/attach */
+
+struct Mfile
+{
+	Mfile		*next;		/* next free mfile */
+	int		ref;
+
+	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	lk;
+	Mfile	*inuse;		/* active mfile's */
+} mfalloc;
+
+int	haveip;
+int	mfd[2];
+int	debug;
+int traceactivity;
+int	cachedb;
+ulong	now;
+int	testing;
+char	*trace;
+int	needrefresh;
+int	resolver;
+uchar	ipaddr[IPaddrlen];	/* my ip address */
+int	maxage;
+char	*zonerefreshprogram;
+int	sendnotifies;
+
+void	rversion(Job*);
+void	rauth(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*, Request*);
+void	rclunk(Job*, Mfile*);
+void	rremove(Job*, Mfile*);
+void	rstat(Job*, Mfile*);
+void	rwstat(Job*, Mfile*);
+void	sendmsg(Job*, char*);
+void	mountinit(char*, char*);
+void	io(void);
+int	fillreply(Mfile*, int);
+Job*	newjob(void);
+void	freejob(Job*);
+void	setext(char*, int, char*);
+
+char	*logfile = "dns";
+char	*dbfile;
+char	mntpt[Maxpath];
+char	*LOG;
+
+void
+usage(void)
+{
+	fprint(2, "usage: %s [-rs] [-f ndb-file] [-x netmtpt]\n", argv0);
+	exits("usage");
+}
+
+void
+main(int argc, char *argv[])
+{
+	int	serve;
+	char	servefile[Maxpath];
+	char	ext[Maxpath];
+	char	*p;
+
+	serve = 0;
+//	setnetmtpt(mntpt, sizeof(mntpt), nil);
+	ext[0] = 0;
+	ARGBEGIN{
+	case 'd':
+		debug = 1;
+		traceactivity = 1;
+		break;
+	case 'f':
+		p = ARGF();
+		if(p == nil)
+			usage();
+		dbfile = p;
+		break;
+	case 'i':
+		haveip = 1;
+		parseip(ipaddr, EARGF(usage()));
+		break;
+//	case 'x':
+	//	p = ARGF();
+	//	if(p == nil)
+	//		usage();
+	//	setnetmtpt(mntpt, sizeof(mntpt), p);
+	//	setext(ext, sizeof(ext), mntpt);
+	//	break;
+	case 'r':
+		resolver = 1;
+		break;
+	case 's':
+		serve = 1;	/* serve network */
+		cachedb = 1;
+		break;
+	case 'a':
+		p = ARGF();
+		if(p == nil)
+			usage();
+		maxage = atoi(p);
+		break;
+	case 't':
+		testing = 1;
+		break;
+	case 'z':
+		zonerefreshprogram = ARGF();
+		break;
+	case 'n':
+		sendnotifies = 1;
+		break;
+	}ARGEND
+	USED(argc);
+	USED(argv);
+
+//if(testing) mainmem->flags |= POOL_NOREUSE;
+#define RFREND 0
+	rfork(RFREND|RFNOTEG);
+
+	/* start syslog before we fork */
+	fmtinstall('F', fcallfmt);
+	dninit();
+	if(!haveip && myipaddr(ipaddr, mntpt) < 0)
+		sysfatal("can't read my ip address");
+
+	syslog(0, logfile, "starting dns on %I", ipaddr);
+
+	opendatabase();
+
+/*
+	snprint(servefile, sizeof(servefile), "#s/dns%s", ext);
+	unmount(servefile, mntpt);
+	remove(servefile);
+*/
+	mountinit(servefile, mntpt);
+
+	now = time(0);
+	srand(now*getpid());
+	db2cache(1);
+
+	if(serve)
+		proccreate(dnudpserver, mntpt, STACK);
+	if(sendnotifies)
+		notifyproc();
+
+	io();
+	syslog(0, logfile, "io returned, exiting");
+	exits(0);
+}
+
+int
+myipaddr(uchar *ip, char *net)
+{
+	ipmove(ip, ipaddr);
+	return 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 p[2];
+
+	if(pipe(p) < 0)
+		abort(); /* "pipe failed" */;
+	switch(rfork(RFFDG|RFPROC|RFNAMEG)){
+	case 0:
+		close(p[1]);
+		break;
+	case -1:
+		abort(); /* "fork failed\n" */;
+	default:
+		close(p[0]);
+
+		if(post9pservice(p[1], "dns") < 0)
+			fprint(2, "post9pservice dns: %r\n");
+		_exits(0);
+	}
+	mfd[0] = mfd[1] = p[0];
+}
+
+Mfile*
+newfid(int fid, int needunused)
+{
+	Mfile *mf;
+
+	lock(&mfalloc.lk);
+	for(mf = mfalloc.inuse; mf != nil; mf = mf->next){
+		if(mf->fid == fid){
+			unlock(&mfalloc.lk);
+			if(needunused)
+				return nil;
+			return mf;
+		}
+	}
+	mf = emalloc(sizeof(*mf));
+	if(mf == nil)
+		sysfatal("out of memory");
+	mf->fid = fid;
+	mf->next = mfalloc.inuse;
+	mfalloc.inuse = mf;
+	unlock(&mfalloc.lk);
+	return mf;
+}
+
+void
+freefid(Mfile *mf)
+{
+	Mfile **l;
+
+	lock(&mfalloc.lk);
+	for(l = &mfalloc.inuse; *l != nil; l = &(*l)->next){
+		if(*l == mf){
+			*l = mf->next;
+			if(mf->user)
+				free(mf->user);
+			free(mf);
+			unlock(&mfalloc.lk);
+			return;
+		}
+	}
+	sysfatal("freeing unused fid");
+}
+
+Mfile*
+copyfid(Mfile *mf, int fid)
+{
+	Mfile *nmf;
+
+	nmf = newfid(fid, 1);
+	if(nmf == nil)
+		return nil;
+	nmf->fid = fid;
+	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;
+			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];
+	Request req;
+	Job *job;
+
+	/*
+	 *  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();
+	req.isslave = 0;
+	for(;;){
+		n = read9pmsg(mfd[0], mdata, sizeof mdata);
+		if(n<=0){
+			syslog(0, logfile, "error reading mntpt: %r");
+			exits(0);
+		}
+		job = newjob();
+		if(convM2S(mdata, n, &job->request) != n){
+			freejob(job);
+			continue;
+		}
+		mf = newfid(job->request.fid, 0);
+		if(debug)
+			syslog(0, logfile, "%F", &job->request);
+
+		getactivity(&req);
+		req.aborttime = now + 60;	/* don't spend more than 60 seconds */
+
+		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, &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();
+			_exits(0);
+		}
+	
+		putactivity();
+	}
+}
+
+void
+rversion(Job *job)
+{
+	if(job->request.msize > IOHDRSZ + Maxfdata)
+		job->reply.msize = IOHDRSZ + Maxfdata;
+	else
+		job->reply.msize = job->request.msize;
+	if(strncmp(job->request.version, "9P2000", 6) != 0)
+		sendmsg(job, "unknown 9P version");
+	else{
+		job->reply.version = "9P2000";
+		sendmsg(job, 0);
+	}
+}
+
+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)
+{
+	char *err;
+	char **elems;
+	int nelems;
+	int i;
+	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 */
+		if(job->request.newfid<0){
+			err = "clone newfid out of range";
+			goto send;
+		}
+		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, cnt;
+	long off;
+	Dir dir;
+	uchar buf[Maxfdata];
+	char *err;
+	long clock;
+
+	n = 0;
+	err = 0;
+	off = job->request.offset;
+	cnt = job->request.count;
+	if(mf->qid.type & QTDIR){
+		clock = time(0);
+		if(off == 0){
+			dir.name = "dns";
+			dir.qid.type = QTFILE;
+			dir.qid.vers = vers;
+			dir.qid.path = Qdns;
+			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;
+	} else {
+		for(i = 1; i <= mf->nrr; i++)
+			if(mf->rr[i] > off)
+				break;
+		if(i > mf->nrr)
+			goto send;
+		if(off + cnt > mf->rr[i])
+			n = mf->rr[i] - off;
+		else
+			n = cnt;
+		job->reply.data = mf->reply + off;
+	}
+send:
+	job->reply.count = n;
+	sendmsg(job, err);
+}
+
+void
+rwrite(Job *job, Mfile *mf, Request *req)
+{
+	int cnt, rooted, status;
+	long n;
+	char *err, *p, *atype;
+	RR *rp, *tp, *neg;
+	int wantsav;
+
+	err = 0;
+	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(cnt > 0 && job->request.data[cnt-1] == '\n')
+		job->request.data[cnt-1] = 0;
+
+	/*
+	 *  special commands
+	 */
+	if(strncmp(job->request.data, "debug", 5)==0 && job->request.data[5] == 0){
+		debug ^= 1;
+		goto send;
+	} else if(strncmp(job->request.data, "dump", 4)==0 && job->request.data[4] == 0){
+		dndump("/lib/ndb/dnsdump");
+		goto send;
+	} else if(strncmp(job->request.data, "refresh", 7)==0 && job->request.data[7] == 0){
+		needrefresh = 1;
+		goto send;
+//	} else if(strncmp(job->request.data, "poolcheck", 9)==0 && job->request.data[9] == 0){
+//		poolcheck(mainmem);
+//		goto send;
+	}
+
+	/*
+	 *  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){
+		err = "illegal request";
+		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;
+	}
+
+	mf->type = rrtype(atype);
+	if(mf->type < 0){
+		err = "unknown type";
+		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;
+	dncheck(0, 1);
+	rp = dnresolve(p, Cin, mf->type, req, 0, 0, Recurse, rooted, &status);
+	dncheck(0, 1);
+	neg = rrremneg(&rp);
+	if(neg){
+		status = neg->negrcode;
+		rrfreelist(neg);
+	}
+	if(rp == 0){
+		switch(status){
+		case Rname:
+			err = "name does not exist";
+			break;
+		case Rserver:
+			err = "dns failure";
+			break;
+		default:
+			err = "resource does not exist";
+			break;
+		}
+	} else {
+		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);
+	}
+
+    send:
+	dncheck(0, 1);
+	job->reply.count = cnt;
+	sendmsg(job, err);
+}
+
+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];
+
+	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 = 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, 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){
+		syslog(1, logfile, "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)
+		syslog(0, logfile, "%F %d", &job->reply, n);
+}
+
+/*
+ *  the following varies between dnsdebug and dns
+ */
+void
+logreply(int id, uchar *addr, DNSmsg *mp)
+{
+	RR *rp;
+
+	syslog(0, LOG, "%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|Rname) == (Fauth|Rname) ?
+		" nx" : "");
+	for(rp = mp->qd; rp != nil; rp = rp->next)
+		syslog(0, LOG, "%d: rcvd %I qd %s", id, addr, rp->owner->name);
+	for(rp = mp->an; rp != nil; rp = rp->next)
+		syslog(0, LOG, "%d: rcvd %I an %R", id, addr, rp);
+	for(rp = mp->ns; rp != nil; rp = rp->next)
+		syslog(0, LOG, "%d: rcvd %I ns %R", id, addr, rp);
+	for(rp = mp->ar; rp != nil; rp = rp->next)
+		syslog(0, LOG, "%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];
+
+	syslog(0, LOG, "%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);
+}
blob - /dev/null
blob + bfaaa68d08dc7a7238ad6db1a2ee574adc938e4f (mode 755)
--- /dev/null
+++ src/cmd/ndb/dns.h
@@ -0,0 +1,403 @@
+#define OUdphdrsize Udphdrsize
+#define OUdphdr Udphdr
+
+enum
+{
+	/* RR types */
+	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,
+	Tsig=	24,
+	Tkey=	25,
+	Taaaa=	28,
+	Tcert=	37,
+
+	/* query types (all RR types are also queries) */
+	Tixfr=	251,	/* incremental zone transfer */
+	Taxfr=	252,	/* zone transfer */
+	Tmailb=	253,	/* { Tmb, Tmg, Tmr } */	
+	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 */
+	Ostatus=	2<<11,		/* status request */
+	Onotify=	4<<11,		/* notify slaves of updates */
+	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 */
+	Rmask=		0xf,	/* mask for response */
+	Rtimeout=	0x10,	/* 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=	256,	/* max domain label length (with NULL) */
+	Strlen=		256,	/* max string length (with NULL) */
+	Iplen=		32,	/* max ascii ip address 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 */
+	Maxudpin=	2048,	/* maximum bytes per udp message */
+
+	/* length of domain name hash table */
+	HTLEN= 		4*1024,
+
+	RRmagic=	0xdeadbabe,
+	DNmagic=	0xa110a110,
+
+	/* parallelism */
+	Maxactive=	32,
+};
+
+typedef struct DN	DN;
+typedef struct DNSmsg	DNSmsg;
+typedef struct RR	RR;
+typedef struct SOA	SOA;
+typedef struct Area	Area;
+typedef struct Request	Request;
+typedef struct Key	Key;
+typedef struct Cert	Cert;
+typedef struct Sig	Sig;
+typedef struct Null	Null;
+typedef struct Server	Server;
+typedef struct Txt	Txt;
+
+/*
+ *  a structure to track a request and any slave process handling it
+ */
+struct Request
+{
+	int	isslave;	/* pid of slave */
+	ulong	aborttime;	/* time at which we give up */
+	jmp_buf	mret;		/* where master jumps to after starting a slave */
+	int	id;
+};
+
+/*
+ *  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	lookuptime;	/* last time we tried to get a better value */
+	ushort	class;		/* RR class */
+	char	refs;		/* for mark and sweep */
+	char	nonexistent;	/* true if we get an authoritative nx for this domain */
+	ulong	ordinal;
+};
+
+/*
+ *  security info
+ */
+struct Key
+{
+	int	flags;
+	int	proto;
+	int	alg;
+	int	dlen;
+	uchar	*data;
+};
+struct Cert
+{
+	int	type;
+	int	tag;
+	int	alg;
+	int	dlen;
+	uchar	*data;
+};
+struct Sig
+{
+	int	type;
+	int	alg;
+	int	labels;
+	ulong	ttl;
+	ulong	exp;
+	ulong	incep;
+	int	tag;
+	DN	*signer;
+	int	dlen;
+	uchar	*data;
+};
+struct Null
+{
+	int	dlen;
+	uchar	*data;
+};
+
+/*
+ *  text strings
+ */
+struct Txt
+{
+	Txt	*next;
+	char	*p;
+};
+
+/*
+ *  an unpacked resource record
+ */
+struct RR
+{
+	RR	*next;
+	ulong	magic;
+	DN	*owner;		/* domain that owns this resource record */
+	uchar	negative;	/* this is a cached negative response */
+	ulong	pc;
+	ulong	ttl;		/* time to live to be passed on */
+	ulong	expire;		/* time this entry expires locally */
+	ushort	type;		/* RR type */
+	ushort	query;		/* query tyis is in response to */
+	uchar	auth;		/* authoritative */
+	uchar	db;		/* from database */
+	uchar	cached;		/* rr in cache */
+	ulong	marker;		/* used locally when scanning rrlists */
+	union {
+		DN	*negsoaowner;	/* soa for cached negative response */
+		DN	*host;	/* hostname - soa, cname, mb, md, mf, mx, ns */
+		DN	*cpu;	/* cpu type - hinfo */
+		DN	*mb;	/* mailbox - mg, minfo */
+		DN	*ip;	/* ip addrss - a */
+		DN	*rp;	/* rp arg - rp */
+		int	cruftlen;
+		ulong	arg0;
+	};
+	union {
+		int	negrcode;	/* response code for cached negative response */
+		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 */
+		ulong	arg1;
+	};
+	union {
+		SOA	*soa;	/* soa timers - soa */
+		Key	*key;
+		Cert	*cert;
+		Sig	*sig;
+		Null	*null;
+		Txt	*txt;
+	};
+};
+
+/*
+ *  list of servers
+ */
+struct Server
+{
+	Server	*next;
+	char	*name;
+};
+
+/*
+ *  timers for a start of authenticated record
+ */
+struct SOA
+{
+	ulong	serial;		/* zone serial # (sec) - soa */
+	ulong	refresh;	/* zone refresh interval (sec) - soa */
+	ulong	retry;		/* zone retry interval (sec) - soa */
+	ulong	expire;		/* time to expiration (sec) - soa */
+	ulong	minttl;		/* minimum time to live for any entry (sec) - soa */
+	Server	*slaves;	/* slave servers */
+};
+
+/*
+ *  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;
+};
+
+enum
+{
+	Recurse,
+	Dontrecurse,
+	NOneg,
+	OKneg,
+};
+
+/* dn.c */
+extern char	*rrtname[];
+extern char	*rname[];
+extern char	*opname[];
+extern void	db2cache(int);
+extern void	dninit(void);
+extern DN*	dnlookup(char*, int, int);
+extern void	dnage(DN*);
+extern void	dnageall(int);
+extern void	dnagedb(void);
+extern void	dnauthdb(void);
+extern void	dnget(void);
+extern void	dnpurge(void);
+extern void	dnput(void);
+extern Area*	inmyarea(char*);
+extern void	rrattach(RR*, int);
+extern RR*	rralloc(int);
+extern void	rrfree(RR*);
+extern void	rrfreelist(RR*);
+extern RR*	rrlookup(DN*, int, int);
+extern RR*	rrcat(RR**, RR*);
+extern RR**	rrcopy(RR*, RR**);
+extern RR*	rrremneg(RR**);
+extern RR*	rrremtype(RR**, int);
+extern int	rrfmt(Fmt*);
+extern int	rravfmt(Fmt*);
+extern int	rrsupported(int);
+extern int	rrtype(char*);
+extern char*	rrname(int, char*, int);
+extern int	tsame(int, int);
+extern void	dndump(char*);
+extern int	getactivity(Request*);
+extern void	putactivity(void);
+extern void	abort(); /* char*, ... */;
+extern void	warning(char*, ...);
+extern void	slave(Request*);
+extern void	dncheck(void*, int);
+extern void	unique(RR*);
+extern int	subsume(char*, char*);
+extern RR*	randomize(RR*);
+extern void*	emalloc(int);
+extern char*	estrdup(char*);
+extern void	dnptr(uchar*, uchar*, char*, int, int);
+extern void	addserver(Server**, char*);
+extern Server*	copyserverlist(Server*);
+extern void	freeserverlist(Server*);
+
+/* dnarea.c */
+extern void	refresh_areas(Area*);
+extern void	freearea(Area**);
+extern void	addarea(DN *dp, RR *rp, Ndbtuple *t);
+
+/* dblookup.c */
+extern RR*	dblookup(char*, int, int, int, int);
+extern RR*	dbinaddr(DN*, int);
+extern int	baddelegation(RR*, RR*, uchar*);
+extern RR*	dnsservers(int);
+extern RR*	domainlist(int);
+extern int	opendatabase(void);
+
+/* dns.c */
+extern char*	walkup(char*);
+extern RR*	getdnsservers(int);
+extern void	logreply(int, uchar*, DNSmsg*);
+extern void	logsend(int, int, uchar*, char*, char*, int);
+
+/* dnresolve.c */
+extern RR*	dnresolve(char*, int, int, Request*, RR**, int, int, int, int*);
+extern int	udpport(void);
+extern int	mkreq(DN *dp, int type, uchar *buf, int flags, ushort reqno);
+
+/* dnserver.c */
+extern void	dnserver(DNSmsg*, DNSmsg*, Request*);
+extern void	dnudpserver(void*);
+extern void	dntcpserver(char*);
+
+/* dnnotify.c */
+extern void	dnnotify(DNSmsg*, DNSmsg*, Request*);
+extern void	notifyproc(void);
+
+/* convDNS2M.c */
+extern int	convDNS2M(DNSmsg*, uchar*, int);
+
+/* convM2DNS.c */
+extern char*	convM2DNS(uchar*, int, DNSmsg*);
+
+/* malloc.c */
+extern void	mallocsanity(void*);
+extern void	lasthist(void*, int, ulong);
+
+extern int debug;
+extern int traceactivity;
+extern char	*trace;
+extern int	testing;	/* test cache whenever removing a DN */
+extern int	cachedb;
+extern int	needrefresh;
+extern char	*dbfile;
+extern char	mntpt[];
+extern char	*logfile;
+extern int	resolver;
+extern int	maxage;		/* age of oldest entry in cache (secs) */
+extern char	*zonerefreshprogram;
+extern int	sendnotifies;
+extern ulong	now;		/* time base */
+extern Area	*owned;
+extern Area	*delegated;
+
+#pragma	varargck	type	"R"	RR*
+#pragma	varargck	type	"Q"	RR*
+
blob - /dev/null
blob + b733937267c66f631c240b4150fa94857f876a07 (mode 755)
--- /dev/null
+++ src/cmd/ndb/dnsdebug.c
@@ -0,0 +1,473 @@
+#include <u.h>
+#include <libc.h>
+#include <bio.h>
+#include <ctype.h>
+#include <ip.h>
+#include <ndb.h>
+#include "dns.h"
+
+enum
+{
+	Maxrequest=		128,
+	Ncache=			8,
+	Maxpath=		128,
+	Maxreply=		512,
+	Maxrrr=			16,
+};
+
+static char *servername;
+static RR *serverrr;
+static RR *serveraddrs;
+
+int	debug;
+int	cachedb;
+ulong	now;
+int	testing;
+int traceactivity;
+char	*trace;
+int	needrefresh;
+int	resolver;
+uchar	ipaddr[IPaddrlen];	/* my ip address */
+int	maxage;
+char	*logfile = "dns";
+char	*dbfile;
+char	mntpt[Maxpath];
+char	*zonerefreshprogram;
+
+int prettyrrfmt(Fmt*);
+void preloadserveraddrs(void);
+void squirrelserveraddrs(void);
+int setserver(char*);
+void doquery(char*, char*);
+void docmd(int, char**);
+
+void
+main(int argc, char *argv[])
+{
+	int n;
+	Biobuf in;
+	char *p;
+	char *f[4];
+
+	strcpy(mntpt, "/net");
+
+	ARGBEGIN{
+	case 'r':
+		resolver = 1;
+		break;
+	case 'x':
+		dbfile = "/lib/ndb/external";
+		strcpy(mntpt, "/net.alt");
+		break;
+	case 'f':
+		dbfile = ARGF();
+		break;
+	}ARGEND
+
+	now = time(0);
+	dninit();
+	fmtinstall('R', prettyrrfmt);
+	if(myipaddr(ipaddr, mntpt) < 0)
+		sysfatal("can't read my ip address");
+	opendatabase();
+
+	if(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)
+			continue;
+
+		/* flush the cache */
+		dnpurge();
+
+		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;
+}
+
+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->db ? rp->ttl : (rp->ttl-now)),
+		rrname(rp->type, buf, sizeof buf));
+
+	if(rp->negative){
+		seprint(p, e, "negative rcode %d\n", 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->name);
+		break;
+	case Tmg:
+	case Tmr:
+		seprint(p, e, "\t%s", rp->mb->name);
+		break;
+	case Tminfo:
+		seprint(p, e, "\t%s %s", rp->mb->name, rp->rmb->name);
+		break;
+	case Tmx:
+		seprint(p, e, "\t%lud %s", rp->pref, rp->host->name);
+		break;
+	case Ta:
+	case Taaaa:
+		seprint(p, e, "\t%s", rp->ip->name);
+		break;
+	case Tptr:
+		seprint(p, e, "\t%s", 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 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;
+	default:
+		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];
+	char 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|Rname) == (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 = dnlookup(servername, class, 1);
+
+	return rr;
+}
+
+void
+squirrelserveraddrs(void)
+{
+	RR *rr, *rp, **l;
+	Request req;
+
+	/* look up the resolver address first */
+	resolver = 0;
+	debug = 0;
+	if(serveraddrs)
+		rrfreelist(serveraddrs);
+	serveraddrs = nil;
+	rr = getdnsservers(Cin);
+	l = &serveraddrs;
+	for(rp = rr; rp != nil; rp = rp->next){
+		if(strcmp(ipattr(rp->host->name), "ip") == 0){
+			*l = rralloc(Ta);
+			(*l)->owner = rp->host;
+			(*l)->ip = rp->host;
+			l = &(*l)->next;
+			continue;
+		}
+		req.isslave = 1;
+		req.aborttime = now + 60;	/* don't spend more than 60 seconds */
+		*l = dnresolve(rp->host->name, Cin, Ta, &req, 0, 0, Recurse, 0, 0);
+		while(*l != nil)
+			l = &(*l)->next;
+	}
+	resolver = 1;
+	debug = 1;
+}
+
+void
+preloadserveraddrs(void)
+{
+	RR *rp, **l, *first;
+	
+	l = &first;
+	for(rp = serveraddrs; rp != nil; rp = rp->next){
+		rrcopy(rp, l);
+		rrattach(first, 1);
+	}
+}
+
+int
+setserver(char *server)
+{
+	if(servername != nil){
+		free(servername);
+		servername = nil;
+		resolver = 0;
+	}
+	if(server == nil || *server == 0)
+		return 0;
+	servername = strdup(server);
+	squirrelserveraddrs();
+	if(serveraddrs == nil){
+		print("can't resolve %s\n", servername);
+		resolver = 0;
+	} else {
+		resolver = 1;
+	}
+	return resolver ? 0 : -1;
+}
+
+void
+doquery(char *name, char *tstr)
+{
+	Request req;
+	RR *rr, *rp;
+	int len, type;
+	char *p, *np;
+	int rooted;
+	char buf[1024];
+
+	if(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";
+	}
+
+	/* 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 */
+	strncpy(buf, name, sizeof buf);
+	if(strcmp("ptr", tstr) == 0
+	&& strstr(name, "IN-ADDR") == 0
+	&& strstr(name, "in-addr") == 0){
+		for(p = name; *p; p++)
+			;
+		*p = '.';
+		np = buf;
+		len = 0;
+		while(p >= name){
+			len++;
+			p--;
+			if(*p == '.'){
+				memmove(np, p+1, len);
+				np += len;
+				len = 0;
+			}
+		}
+		memmove(np, p+1, len);
+		np += len;
+		strcpy(np, "in-addr.arpa");
+	}
+
+	/* look it up */
+	type = rrtype(tstr);
+	if(type < 0){
+		print("!unknown type %s\n", tstr);
+		return;
+	}
+
+	getactivity(&req);
+	req.isslave = 1;
+	req.aborttime = now + 60;	/* don't spend more than 60 seconds */
+	rr = dnresolve(buf, Cin, type, &req, 0, 0, Recurse, rooted, 0);
+	if(rr){
+		print("----------------------------\n");
+		for(rp = rr; rp; rp = rp->next)
+			print("answer %R\n", rp);
+		print("----------------------------\n");
+	}
+	rrfreelist(rr);
+
+	putactivity();
+}
+
+void
+docmd(int n, char **f)
+{
+	int tmpsrv;
+	char *name, *type;
+
+	name = nil;
+	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("");
+}
blob - /dev/null
blob + 509734b033ce39c3cfd35da475d1ae237cd6a746 (mode 755)
--- /dev/null
+++ src/cmd/ndb/dnserver.c
@@ -0,0 +1,178 @@
+#include <u.h>
+#include <libc.h>
+#include <ip.h>
+#include <bio.h>
+#include <ndb.h>
+#include "dns.h"
+
+static RR*	doextquery(DNSmsg*, Request*, int);
+static void	hint(RR**, RR*);
+
+extern char *logfile;
+
+/*
+ *  answer a dns request
+ */
+void
+dnserver(DNSmsg *reqp, DNSmsg *repp, Request *req)
+{
+	RR *tp, *neg;
+	char *cp;
+	DN *nsdp, *dp;
+	Area *myarea;
+	char tname[32];
+
+	dncheck(nil, 1);
+
+	memset(repp, 0, sizeof(*repp));
+	repp->id = reqp->id;
+	repp->flags = Fresp | Fcanrec | Oquery;
+
+	/* move one question from reqp to repp */
+	tp = reqp->qd;
+	reqp->qd = tp->next;
+	tp->next = 0;
+	repp->qd = tp;
+
+	if(!rrsupported(repp->qd->type)){
+		syslog(0, logfile, "server: request %s", rrname(repp->qd->type, tname, sizeof tname));
+		repp->flags = Runimplimented | Fresp | Fcanrec | Oquery;
+		return;
+	}
+
+	if(repp->qd->owner->class != Cin){
+		syslog(0, logfile, "server: class %d", repp->qd->owner->class);
+		repp->flags = Runimplimented | Fresp | Fcanrec | Oquery;
+		return;
+	}
+
+	myarea = inmyarea(repp->qd->owner->name);
+	if(myarea != nil && (repp->qd->type == Tixfr || repp->qd->type == Taxfr)){
+		syslog(0, logfile, "server: request %s", rrname(repp->qd->type, tname, sizeof tname));
+		repp->flags = Runimplimented | Fresp | Fcanrec | Oquery;
+		return;
+	}
+
+	/*
+	 *  get the answer if we can
+	 */
+	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 == 0){
+		dp = dnlookup(repp->qd->owner->name, repp->qd->owner->class, 0);
+		if(dp->rr == 0)
+			if(reqp->flags & Frecurse)
+				repp->flags |= dp->nonexistent|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 == 0)
+				continue;
+	
+			repp->ns = rrlookup(nsdp, Tns, OKneg);
+			if(repp->ns){
+				/* don't pass on anything we know is wrong */
+				if(repp->ns->negative){
+					rrfreelist(repp->ns);
+					repp->ns = nil;
+				}
+				break;
+			}
+	
+			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)
+				rrcat(&repp->ns, rrlookup(neg->negsoaowner, Tsoa, NOneg));
+			repp->flags |= neg->negrcode;
+		}
+	}
+
+	/*
+	 *  get rid of duplicates
+	 */
+	unique(repp->an);
+	unique(repp->ns);
+	unique(repp->ar);
+
+	rrfreelist(neg);
+
+	dncheck(nil, 1);
+}
+
+/*
+ *  satisfy a recursive request.  dnlookup will handle cnames.
+ */
+static RR*
+doextquery(DNSmsg *mp, Request *req, int recurse)
+{
+	int 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, 0);
+
+	/* don't return soa hints as answers, it's wrong */
+	if(rp && rp->db && !rp->auth && rp->type == Tsoa)
+		rrfreelist(rp);
+
+	/* 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);
+		rrcat(last, hp);
+		break;
+	}
+}
blob - /dev/null
blob + 597cc480272d31803448c928bee41b1d8cee3d77 (mode 755)
--- /dev/null
+++ src/cmd/ndb/dnsquery.c
@@ -0,0 +1,113 @@
+#include <u.h>
+#include <libc.h>
+#include <bio.h>
+#include <ndb.h>
+#include "dns.h"
+#include "ip.h"
+
+void
+main(int argc, char *argv[])
+{
+	int fd, n, len, domount;
+	Biobuf in;
+	char line[1024], *lp, *p, *np, *mtpt, *srv, *dns;
+	char buf[1024];
+
+	dns = "/net/dns";
+	mtpt = "/net";
+	srv = "/srv/dns";
+	domount = 1;
+	ARGBEGIN {
+	case 'x':
+		dns = "/net.alt/dns";
+		mtpt = "/net.alt";
+		srv = "/srv/dns_net.alt";
+		break;
+	default:
+		fprint(2, "usage: %s -x [dns-mount-point]\n", argv0);
+		exits("usage");
+	} ARGEND;
+
+	if(argc == 1){
+		domount = 0;
+		mtpt = argv[0];
+	}
+
+	fd = open(dns, ORDWR);
+	if(fd < 0){
+		if(domount == 0){
+			fprint(2, "can't open %s: %r\n", mtpt);
+			exits(0);
+		}
+		fd = open(srv, ORDWR);
+		if(fd < 0){
+			print("can't open %s: %r\n", srv);
+			exits(0);
+		}
+		if(mount(fd, -1, mtpt, MBEFORE, "") < 0){
+			print("can't mount(%s, %s): %r\n", srv, mtpt);
+			exits(0);
+		}
+		fd = open(mtpt, ORDWR);
+		if(fd < 0){
+			print("can't open %s: %r\n", mtpt);
+			exits(0);
+		}
+	}
+	Binit(&in, 0, OREAD);
+	for(print("> "); lp = Brdline(&in, '\n'); print("> ")){
+		n = Blinelen(&in)-1;
+		strncpy(line, lp, n);
+		line[n] = 0;
+		if (n<=1)
+			continue;
+		/* default to an "ip" request if alpha, "ptr" if numeric */
+		if (strchr(line, ' ')==0) {
+			if(strcmp(ipattr(line), "ip") == 0) {
+				strcat(line, " ptr");
+				n += 4;
+			} else {
+				strcat(line, " ip");
+				n += 3;
+			}
+		}
+
+		/* inverse queries may need to be permuted */
+		if(n > 4 && strcmp("ptr", &line[n-3]) == 0
+		&& strstr(line, "IN-ADDR") == 0 && strstr(line, "in-addr") == 0){
+			for(p = line; *p; p++)
+				if(*p == ' '){
+					*p = '.';
+					break;
+				}
+			np = buf;
+			len = 0;
+			while(p >= line){
+				len++;
+				p--;
+				if(*p == '.'){
+					memmove(np, p+1, len);
+					np += len;
+					len = 0;
+				}
+			}
+			memmove(np, p+1, len);
+			np += len;
+			strcpy(np, "in-addr.arpa ptr");
+			strcpy(line, buf);
+			n = strlen(line);
+		}
+
+		seek(fd, 0, 0);
+		if(write(fd, line, n) < 0) {
+			print("!%r\n");
+			continue;
+		}
+		seek(fd, 0, 0);
+		while((n = read(fd, buf, sizeof(buf))) > 0){
+			buf[n] = 0;
+			print("%s\n", buf);
+		}
+	}
+	exits(0);
+}
blob - /dev/null
blob + 19a7540d5d2eee864d15856686bb284fc1eb38a1 (mode 755)
--- /dev/null
+++ src/cmd/ndb/dnstcp.c
@@ -0,0 +1,362 @@
+#include <u.h>
+#include <libc.h>
+#include <ip.h>
+#include "dns.h"
+
+enum
+{
+	Maxpath=		128,
+};
+
+char	*logfile = "dns";
+char	*dbfile;
+int	debug;
+int	cachedb = 1;
+int	testing;
+int traceactivity;
+int	needrefresh;
+int 	resolver;
+char	mntpt[Maxpath];
+char	*caller = "";
+ulong	now;
+int	maxage;
+uchar	ipaddr[IPaddrlen];	/* my ip address */
+char	*LOG;
+char	*zonerefreshprogram;
+
+static int	readmsg(int, uchar*, int);
+static void	reply(int, DNSmsg*, Request*);
+static void	dnzone(DNSmsg*, DNSmsg*, Request*);
+static void	getcaller(char*);
+static void	refreshmain(char*);
+
+void
+main(int argc, char *argv[])
+{
+	int len;
+	Request req;
+	DNSmsg reqmsg, repmsg;
+	uchar buf[512];
+	char tname[32];
+	char *err;
+	char *ext = "";
+
+	ARGBEGIN{
+	case 'd':
+		debug++;
+		break;
+	case 'f':
+		dbfile = ARGF();
+		break;
+	case 'r':
+		resolver = 1;
+		break;
+	case 'x':
+		ext = ARGF();
+		break;
+	}ARGEND
+
+	if(debug < 2)
+		debug = 0;
+
+	if(argc > 0)
+		getcaller(argv[0]);
+
+	dninit();
+
+	snprint(mntpt, sizeof(mntpt), "/net%s", ext);
+	if(myipaddr(ipaddr, mntpt) < 0)
+		sysfatal("can't read my ip address");
+	syslog(0, logfile, "dnstcp call from %s to %I", caller, ipaddr);
+
+	db2cache(1);
+
+	setjmp(req.mret);
+	req.isslave = 0;
+
+	/* loop on requests */
+	for(;; putactivity()){
+		now = time(0);
+		memset(&repmsg, 0, sizeof(repmsg));
+		alarm(10*60*1000);
+		len = readmsg(0, buf, sizeof(buf));
+		alarm(0);
+		if(len <= 0)
+			break;
+		getactivity(&req);
+		req.aborttime = now + 15*Min;
+		err = convM2DNS(buf, len, &reqmsg);
+		if(err){
+			syslog(0, logfile, "server: input error: %s from %I", err, buf);
+			break;
+		}
+		if(reqmsg.qdcount < 1){
+			syslog(0, logfile, "server: no questions from %I", buf);
+			break;
+		}
+		if(reqmsg.flags & Fresp){
+			syslog(0, logfile, "server: reply not request from %I", buf);
+			break;
+		}
+		if((reqmsg.flags & Omask) != Oquery){
+			syslog(0, logfile, "server: op %d from %I", reqmsg.flags & Omask, buf);
+			break;
+		}
+
+		if(debug)
+			syslog(0, logfile, "%d: serve (%s) %d %s %s",
+				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);
+			} else {
+				dnserver(&reqmsg, &repmsg, &req);
+				reply(1, &repmsg, &req);
+				rrfreelist(repmsg.qd);
+				rrfreelist(repmsg.an);
+				rrfreelist(repmsg.ns);
+				rrfreelist(repmsg.ar);
+			}
+		}
+
+		rrfreelist(reqmsg.qd);
+		rrfreelist(reqmsg.an);
+		rrfreelist(reqmsg.ns);
+		rrfreelist(reqmsg.ar);
+
+		if(req.isslave){
+			putactivity();
+			_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[4096];
+	RR *rp;
+
+	if(debug){
+		syslog(0, logfile, "%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)
+			syslog(0, logfile, "an %R", rp);
+		for(rp = rep->ns; rp; rp = rp->next)
+			syslog(0, logfile, "ns %R", rp);
+		for(rp = rep->ar; rp; rp = rp->next)
+			syslog(0, logfile, "ar %R", rp);
+	}
+
+
+	len = convDNS2M(rep, buf+2, sizeof(buf) - 2);
+	if(len <= 0)
+		abort(); /* "dnserver: converting reply" */;
+	buf[0] = len>>8;
+	buf[1] = len;
+	rv = write(fd, buf, len+2);
+	if(rv != len+2){
+		syslog(0, logfile, "sending reply: %d instead of %d", 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 == 0)
+		return 0;
+	if(numelem(dp->name) != depth)
+		return 0;
+	n = strlen(dp->name);
+	if(n < namelen)
+		return 0;
+	if(strcmp(name, dp->name + n - namelen) != 0)
+		return 0;
+	if(n > namelen && dp->name[n - namelen - 1] != '.')
+		return 0;
+	return 1;
+}
+
+static void
+dnzone(DNSmsg *reqp, DNSmsg *repp, Request *req)
+{
+	DN *dp, *ndp;
+	RR r, *rp;
+	int h, depth, found, nlen;
+
+	memset(repp, 0, sizeof(*repp));
+	repp->id = reqp->id;
+	repp->flags = Fauth | Fresp | Fcanrec | Oquery;
+	repp->qd = reqp->qd;
+	reqp->qd = reqp->qd->next;
+	repp->qd->next = 0;
+	dp = repp->qd->owner;
+
+	/* send the soa */
+	repp->an = rrlookup(dp, Tsoa, NOneg);
+	reply(1, repp, req);
+	if(repp->an == 0)
+		goto out;
+	rrfreelist(repp->an);
+
+	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 */
+						if(rp->negative)
+							continue;
+
+						/* don't send an soa's, ns's are enough */
+						if(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);
+out:
+	rrfree(repp->qd);
+}
+
+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)
+		syslog(0, logfile, "refreshing %s", file);
+	fd = open(file, ORDWR);
+	if(fd < 0){
+		syslog(0, logfile, "can't refresh %s", file);
+		return;
+	}
+	fprint(fd, "refresh");
+	close(fd);
+}
+
+/*
+ *  the following varies between dnsdebug and dns
+ */
+void
+logreply(int id, uchar *addr, DNSmsg *mp)
+{
+	RR *rp;
+
+	syslog(0, LOG, "%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|Rname) == (Fauth|Rname) ?
+		" nx" : "");
+	for(rp = mp->qd; rp != nil; rp = rp->next)
+		syslog(0, LOG, "%d: rcvd %I qd %s", id, addr, rp->owner->name);
+	for(rp = mp->an; rp != nil; rp = rp->next)
+		syslog(0, LOG, "%d: rcvd %I an %R", id, addr, rp);
+	for(rp = mp->ns; rp != nil; rp = rp->next)
+		syslog(0, LOG, "%d: rcvd %I ns %R", id, addr, rp);
+	for(rp = mp->ar; rp != nil; rp = rp->next)
+		syslog(0, LOG, "%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];
+
+	syslog(0, LOG, "%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);
+}
blob - /dev/null
blob + f0ed37cd4208b7731c7cad660170f08677b50a23 (mode 755)
--- /dev/null
+++ src/cmd/ndb/dnudpserver.c
@@ -0,0 +1,228 @@
+#include <u.h>
+#include <libc.h>
+#include <ip.h>
+#include <bio.h>
+#include <ndb.h>
+#include "dns.h"
+
+static int	udpannounce(char*);
+static void	reply(int, uchar*, DNSmsg*, Request*);
+
+extern char *logfile;
+
+static void
+ding(void *x, char *msg)
+{
+	USED(x);
+	if(strcmp(msg, "alarm") == 0)
+		noted(NCONT);
+	else
+		noted(NDFLT);
+}
+
+typedef struct Inprogress Inprogress;
+struct Inprogress
+{
+	int	inuse;
+	OUdphdr	uh;
+	DN	*owner;
+	int	type;
+	int	id;
+};
+Inprogress inprog[Maxactive+2];
+
+/*
+ *  record client id and ignore retransmissions.
+ *  we're still single thread at this point.
+ */
+static Inprogress*
+clientrxmit(DNSmsg *req, uchar *buf)
+{
+	Inprogress *p, *empty;
+	OUdphdr *uh;
+
+	uh = (OUdphdr *)buf;
+	empty = 0;
+	for(p = inprog; p < &inprog[Maxactive]; p++){
+		if(p->inuse == 0){
+			if(empty == 0)
+				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, OUdphdrsize) == 0)
+			return 0;
+	}
+	if(empty == 0)
+		return 0;	/* shouldn't happen - see slave() and definition of Maxactive */
+
+	empty->id = req->id;
+	empty->owner = req->qd->owner;
+	empty->type = req->qd->type;
+	memmove(&empty->uh, uh, OUdphdrsize);
+	empty->inuse = 1;
+	return empty;
+}
+
+/*
+ *  a process to act as a dns server for outside reqeusts
+ */
+void
+dnudpserver(char *mntpt)
+{
+	int fd, len, op;
+	Request req;
+	DNSmsg reqmsg, repmsg;
+	uchar buf[OUdphdrsize + Maxudp + 1024];
+	char *err;
+	Inprogress *p;
+	char tname[32];
+	OUdphdr *uh;
+
+	/* fork sharing text, data, and bss with parent */
+	switch(rfork(RFPROC|RFNOTEG|RFMEM|RFNOWAIT)){
+	case -1:
+		break;
+	case 0:
+		break;
+	default:
+		return;
+	}
+
+	fd = -1;
+	notify(ding);
+restart:
+	if(fd >= 0)
+		close(fd);
+	while((fd = udpannounce(mntpt)) < 0)
+		sleep(5000);
+	if(setjmp(req.mret))
+		putactivity();
+	req.isslave = 0;
+
+	/* loop on requests */
+	for(;; putactivity()){
+		memset(&repmsg, 0, sizeof(repmsg));
+		memset(&reqmsg, 0, sizeof(reqmsg));
+		alarm(60*1000);
+		len = udpread(fd, (OUdphdr*)buf, buf+OUdphdrsize, sizeof(buf)-OUdphdrsize);
+		alarm(0);
+		if(len <= 0)
+			goto restart;
+		uh = (OUdphdr*)buf;
+		getactivity(&req);
+		req.aborttime = now + 30;	/* don't spend more than 30 seconds */
+		err = convM2DNS(&buf[OUdphdrsize], len, &reqmsg);
+		if(err){
+			syslog(0, logfile, "server: input error: %s from %I", err, buf);
+			continue;
+		}
+		if(reqmsg.qdcount < 1){
+			syslog(0, logfile, "server: no questions from %I", buf);
+			goto freereq;
+		}
+		if(reqmsg.flags & Fresp){
+			syslog(0, logfile, "server: reply not request from %I", buf);
+			goto freereq;
+		}
+		op = reqmsg.flags & Omask;
+		if(op != Oquery && op != Onotify){
+			syslog(0, logfile, "server: op %d from %I", reqmsg.flags & Omask, buf);
+			goto freereq;
+		}
+
+		if(debug || (trace && subsume(trace, reqmsg.qd->owner->name))){
+			syslog(0, logfile, "%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 == 0){
+			if(debug)
+				syslog(0, logfile, "%d: duplicate", req.id);
+			goto freereq;
+		}
+
+		/* loop through each question */
+		while(reqmsg.qd){
+			memset(&repmsg, 0, sizeof(repmsg));
+			switch(op){
+			case Oquery:
+				dnserver(&reqmsg, &repmsg, &req);
+				break;
+			case Onotify:
+				dnnotify(&reqmsg, &repmsg, &req);
+				break;
+			}
+			reply(fd, buf, &repmsg, &req);
+			rrfreelist(repmsg.qd);
+			rrfreelist(repmsg.an);
+			rrfreelist(repmsg.ns);
+			rrfreelist(repmsg.ar);
+		}
+
+		p->inuse = 0;
+
+freereq:
+		rrfreelist(reqmsg.qd);
+		rrfreelist(reqmsg.an);
+		rrfreelist(reqmsg.ns);
+		rrfreelist(reqmsg.ar);
+
+		if(req.isslave){
+			putactivity();
+			_exits(0);
+		}
+
+	}
+}
+
+/*
+ *  announce on udp port
+ */
+static int
+udpannounce(char *mntpt)
+{
+	int fd;
+	char buf[40];
+	
+	USED(mntpt);
+	
+	if((fd=announce("udp!*!nameserver", buf)) < 0)
+		warning("can't announce on dns udp port");
+	return fd;
+}
+
+static void
+reply(int fd, uchar *buf, DNSmsg *rep, Request *reqp)
+{
+	int len;
+	char tname[32];
+	RR *rp;
+
+	if(debug || (trace && subsume(trace, rep->qd->owner->name)))
+		syslog(0, logfile, "%d: reply (%I/%d) %d %s %s 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->an, rep->ns, rep->ar);
+
+	len = convDNS2M(rep, &buf[OUdphdrsize], Maxudp);
+	if(len <= 0){
+		syslog(0, logfile, "error converting reply: %s %d", rep->qd->owner->name,
+			rep->qd->type);
+		for(rp = rep->an; rp; rp = rp->next)
+			syslog(0, logfile, "an %R", rp);
+		for(rp = rep->ns; rp; rp = rp->next)
+			syslog(0, logfile, "ns %R", rp);
+		for(rp = rep->ar; rp; rp = rp->next)
+			syslog(0, logfile, "ar %R", rp);
+		return;
+	}
+	if(udpwrite(fd, (OUdphdr*)buf, buf+OUdphdrsize, len) != len)
+		syslog(0, logfile, "error sending reply: %r");
+}