aboutsummaryrefslogtreecommitdiff
path: root/src/cmd/ndb
diff options
context:
space:
mode:
authorrsc <devnull@localhost>2005-12-27 04:30:05 +0000
committerrsc <devnull@localhost>2005-12-27 04:30:05 +0000
commit3e0d8fb3ea83b2b65a6425c65beda887140f9349 (patch)
treece1579f6999c0e4adad35b75c498ddc714b40c1e /src/cmd/ndb
parentcff43a06f21a674b2b16e72f8853aac5fd24f48d (diff)
downloadplan9port-3e0d8fb3ea83b2b65a6425c65beda887140f9349.tar.gz
plan9port-3e0d8fb3ea83b2b65a6425c65beda887140f9349.tar.bz2
plan9port-3e0d8fb3ea83b2b65a6425c65beda887140f9349.zip
add dns
Diffstat (limited to 'src/cmd/ndb')
-rwxr-xr-xsrc/cmd/ndb/convDNS2M.c380
-rwxr-xr-xsrc/cmd/ndb/convM2DNS.c460
-rwxr-xr-xsrc/cmd/ndb/dblookup.c946
-rwxr-xr-xsrc/cmd/ndb/dn.c1563
-rwxr-xr-xsrc/cmd/ndb/dnarea.c130
-rwxr-xr-xsrc/cmd/ndb/dnnotify.c160
-rwxr-xr-xsrc/cmd/ndb/dnresolve.c753
-rwxr-xr-xsrc/cmd/ndb/dns.c882
-rwxr-xr-xsrc/cmd/ndb/dns.h403
-rwxr-xr-xsrc/cmd/ndb/dnsdebug.c473
-rwxr-xr-xsrc/cmd/ndb/dnserver.c178
-rwxr-xr-xsrc/cmd/ndb/dnsquery.c113
-rwxr-xr-xsrc/cmd/ndb/dnstcp.c362
-rwxr-xr-xsrc/cmd/ndb/dnudpserver.c228
-rw-r--r--src/cmd/ndb/mkfile16
15 files changed, 7047 insertions, 0 deletions
diff --git a/src/cmd/ndb/convDNS2M.c b/src/cmd/ndb/convDNS2M.c
new file mode 100755
index 00000000..5ee6cadb
--- /dev/null
+++ b/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;
+}
diff --git a/src/cmd/ndb/convM2DNS.c b/src/cmd/ndb/convM2DNS.c
new file mode 100755
index 00000000..47b35616
--- /dev/null
+++ b/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;
+}
diff --git a/src/cmd/ndb/dblookup.c b/src/cmd/ndb/dblookup.c
new file mode 100755
index 00000000..f81f37df
--- /dev/null
+++ b/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);
+ }
+}
diff --git a/src/cmd/ndb/dn.c b/src/cmd/ndb/dn.c
new file mode 100755
index 00000000..a38d4305
--- /dev/null
+++ b/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;
+}
diff --git a/src/cmd/ndb/dnarea.c b/src/cmd/ndb/dnarea.c
new file mode 100755
index 00000000..15e0e849
--- /dev/null
+++ b/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);
+ }
+ }
+ }
+}
diff --git a/src/cmd/ndb/dnnotify.c b/src/cmd/ndb/dnnotify.c
new file mode 100755
index 00000000..a5d91005
--- /dev/null
+++ b/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);
+ }
+}
diff --git a/src/cmd/ndb/dnresolve.c b/src/cmd/ndb/dnresolve.c
new file mode 100755
index 00000000..253daca4
--- /dev/null
+++ b/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;
+}
diff --git a/src/cmd/ndb/dns.c b/src/cmd/ndb/dns.c
new file mode 100755
index 00000000..5f509f24
--- /dev/null
+++ b/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);
+}
diff --git a/src/cmd/ndb/dns.h b/src/cmd/ndb/dns.h
new file mode 100755
index 00000000..bfaaa68d
--- /dev/null
+++ b/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*
+
diff --git a/src/cmd/ndb/dnsdebug.c b/src/cmd/ndb/dnsdebug.c
new file mode 100755
index 00000000..b7339372
--- /dev/null
+++ b/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("");
+}
diff --git a/src/cmd/ndb/dnserver.c b/src/cmd/ndb/dnserver.c
new file mode 100755
index 00000000..509734b0
--- /dev/null
+++ b/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;
+ }
+}
diff --git a/src/cmd/ndb/dnsquery.c b/src/cmd/ndb/dnsquery.c
new file mode 100755
index 00000000..597cc480
--- /dev/null
+++ b/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);
+}
diff --git a/src/cmd/ndb/dnstcp.c b/src/cmd/ndb/dnstcp.c
new file mode 100755
index 00000000..19a7540d
--- /dev/null
+++ b/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);
+}
diff --git a/src/cmd/ndb/dnudpserver.c b/src/cmd/ndb/dnudpserver.c
new file mode 100755
index 00000000..f0ed37cd
--- /dev/null
+++ b/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");
+}
diff --git a/src/cmd/ndb/mkfile b/src/cmd/ndb/mkfile
index 9bbcfd31..340599a0 100644
--- a/src/cmd/ndb/mkfile
+++ b/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
+