ref: 4040cb4739388c445ed428adb630d96784a85531
author: halfwit <michaelmisch1985@gmail.com>
date: Fri Sep 11 15:34:57 PDT 2020
Initial stash
--- /dev/null
+++ b/convDNS2M.c
@@ -1,0 +1,380 @@
+#include <u.h>
+#include <libc.h>
+#include <ip.h>
+#include "dns.h"
+
+/*
+ * a dictionary of domain names for packing messages
+ */
+enum
+{
+ Ndict= 64,
+};
+typedef struct Dict Dict;
+struct Dict
+{
+ struct {
+ ushort offset; /* pointer to packed name in message */
+ char *name; /* pointer to unpacked name in buf */
+ } x[Ndict];
+ int n; /* size of dictionary */
+ uchar *start; /* start of packed message */
+ char buf[16*1024]; /* buffer for unpacked names (was 4k) */
+ char *ep; /* first free char in buf */
+};
+
+#define NAME(x) p = pname(p, ep, x, dp)
+#define LABEL(x) p = pname(p, ep, x, nil)
+#define SYMBOL(x) p = psym(p, ep, x)
+#define STRING(x) p = pstr(p, ep, x)
+#define BYTES(x, n) p = pbytes(p, ep, x, n)
+#define USHORT(x) p = pushort(p, ep, x)
+#define UCHAR(x) p = puchar(p, ep, x)
+#define ULONG(x) p = pulong(p, ep, x)
+#define V4ADDR(x) p = pv4addr(p, ep, x)
+#define V6ADDR(x) p = pv6addr(p, ep, x)
+
+static uchar*
+psym(uchar *p, uchar *ep, char *np)
+{
+ int n;
+
+ n = strlen(np);
+ if(n >= Strlen) /* DNS maximum length string */
+ n = Strlen - 1;
+ if(ep - p < n+1) /* see if it fits in the buffer */
+ return ep+1;
+ *p++ = n;
+ memmove(p, np, n);
+ return p + n;
+}
+
+static uchar*
+pstr(uchar *p, uchar *ep, char *np)
+{
+ return psym(p, ep, np);
+}
+
+static uchar*
+pbytes(uchar *p, uchar *ep, uchar *np, int n)
+{
+ if(ep - p < n)
+ return ep+1;
+ memmove(p, np, n);
+ return p + n;
+}
+
+static uchar*
+puchar(uchar *p, uchar *ep, int val)
+{
+ if(ep - p < 1)
+ return ep+1;
+ *p++ = val;
+ return p;
+}
+
+static uchar*
+pushort(uchar *p, uchar *ep, int val)
+{
+ if(ep - p < 2)
+ return ep+1;
+ *p++ = val>>8;
+ *p++ = val;
+ return p;
+}
+
+static uchar*
+pulong(uchar *p, uchar *ep, int val)
+{
+ if(ep - p < 4)
+ return ep+1;
+ *p++ = val>>24;
+ *p++ = val>>16;
+ *p++ = val>>8;
+ *p++ = val;
+ return p;
+}
+
+static uchar*
+pv4addr(uchar *p, uchar *ep, char *name)
+{
+ uchar ip[IPaddrlen];
+
+ if(ep - p < 4)
+ return ep+1;
+ parseip(ip, name);
+ v6tov4(p, ip);
+ return p + 4;
+}
+
+static uchar*
+pv6addr(uchar *p, uchar *ep, char *name)
+{
+ if(ep - p < IPaddrlen)
+ return ep+1;
+ parseip(p, name);
+ return p + IPaddrlen;
+}
+
+static uchar*
+pname(uchar *p, uchar *ep, char *np, Dict *dp)
+{
+ int i;
+ char *cp;
+ char *last; /* last component packed */
+
+ if(strlen(np) >= Domlen) /* make sure we don't exceed DNS limits */
+ return ep+1;
+
+ last = nil;
+ while(*np){
+ if(dp != nil){
+ /* look through every component in the dictionary for a match */
+ for(i = 0; i < dp->n; i++){
+ if(strcmp(np, dp->x[i].name) == 0){
+ if(ep - p < 2)
+ return ep+1;
+ if ((dp->x[i].offset>>8) & 0xc0)
+ break;
+ *p++ = dp->x[i].offset>>8 | 0xc0;
+ *p++ = dp->x[i].offset;
+ return p;
+ }
+ }
+ /* if there's room, enter this name in dictionary */
+ if(dp->n < Ndict){
+ if(last != nil){
+ /* the whole name is already in dp->buf */
+ last = strchr(last, '.') + 1;
+ dp->x[dp->n].name = last;
+ dp->x[dp->n].offset = p - dp->start;
+ dp->n++;
+ } else {
+ /* add to dp->buf */
+ i = strlen(np);
+ if(dp->ep + i + 1 < &dp->buf[sizeof dp->buf]){
+ memmove(dp->ep, np, i);
+ dp->ep[i] = 0;
+ dp->x[dp->n].name = dp->ep;
+ last = dp->ep;
+ dp->x[dp->n].offset = p - dp->start;
+ dp->ep += i + 1;
+ dp->n++;
+ }
+ }
+ }
+ }
+
+ /* put next component into message */
+ cp = strchr(np, '.');
+ if(cp == nil){
+ i = strlen(np);
+ cp = np + i; /* point to null terminator */
+ } else {
+ i = cp - np;
+ cp++; /* point past '.' */
+ }
+ if(ep-p < i+1)
+ return ep+1;
+ if (i > Labellen)
+ return ep+1;
+ *p++ = i; /* count of chars in label */
+ memmove(p, np, i);
+ np = cp;
+ p += i;
+ }
+
+ if(p >= ep)
+ return ep+1;
+ *p++ = 0; /* add top level domain */
+
+ return p;
+}
+
+static uchar*
+convRR2M(RR *rp, uchar *p, uchar *ep, Dict *dp)
+{
+ uchar *lp, *data;
+ long ttl;
+ int len;
+ Txt *t;
+
+ NAME(rp->owner->name);
+ USHORT(rp->type);
+ USHORT(rp->owner->class);
+
+ if(rp->db || (ttl = (long)(rp->expire - now)) > rp->ttl)
+ ttl = rp->ttl;
+ if(ttl < 0)
+ ttl = 0;
+ ULONG(ttl);
+
+ lp = p; /* leave room for the rdata length */
+ p += 2;
+ data = p;
+
+ if(data >= ep)
+ return p+1;
+
+ switch(rp->type){
+ case Thinfo:
+ SYMBOL(rp->cpu->name);
+ SYMBOL(rp->os->name);
+ break;
+ case Tcname:
+ case Tmb:
+ case Tmd:
+ case Tmf:
+ case Tns:
+ NAME(rp->host->name);
+ break;
+ case Tmg:
+ case Tmr:
+ NAME(rp->mb->name);
+ break;
+ case Tminfo:
+ NAME(rp->rmb->name);
+ NAME(rp->mb->name);
+ break;
+ case Tmx:
+ USHORT(rp->pref);
+ NAME(rp->host->name);
+ break;
+ case Ta:
+ V4ADDR(rp->ip->name);
+ break;
+ case Taaaa:
+ V6ADDR(rp->ip->name);
+ break;
+ case Tptr:
+ NAME(rp->ptr->name);
+ break;
+ case Tsoa:
+ NAME(rp->host->name);
+ NAME(rp->rmb->name);
+ ULONG(rp->soa->serial);
+ ULONG(rp->soa->refresh);
+ ULONG(rp->soa->retry);
+ ULONG(rp->soa->expire);
+ ULONG(rp->soa->minttl);
+ break;
+ case Tsrv:
+ USHORT(rp->srv->pri);
+ USHORT(rp->srv->weight);
+ USHORT(rp->port);
+ LABEL(rp->host->name); /* rfc2782 sez no name compression */
+ break;
+ case Ttxt:
+ for(t = rp->txt; t != nil; t = t->next)
+ STRING(t->p);
+ break;
+ case Tnull:
+ BYTES(rp->null->data, rp->null->dlen);
+ break;
+ case Trp:
+ NAME(rp->rmb->name);
+ NAME(rp->rp->name);
+ break;
+ case Tkey:
+ USHORT(rp->key->flags);
+ UCHAR(rp->key->proto);
+ UCHAR(rp->key->alg);
+ BYTES(rp->key->data, rp->key->dlen);
+ break;
+ case Tsig:
+ USHORT(rp->sig->type);
+ UCHAR(rp->sig->alg);
+ UCHAR(rp->sig->labels);
+ ULONG(rp->sig->ttl);
+ ULONG(rp->sig->exp);
+ ULONG(rp->sig->incep);
+ USHORT(rp->sig->tag);
+ NAME(rp->sig->signer->name);
+ BYTES(rp->sig->data, rp->sig->dlen);
+ break;
+ case Tcert:
+ USHORT(rp->cert->type);
+ USHORT(rp->cert->tag);
+ UCHAR(rp->cert->alg);
+ BYTES(rp->cert->data, rp->cert->dlen);
+ break;
+ }
+
+ /* stuff in the rdata section length */
+ len = p - data;
+ *lp++ = len >> 8;
+ *lp = len;
+
+ return p;
+}
+
+static uchar*
+convQ2M(RR *rp, uchar *p, uchar *ep, Dict *dp)
+{
+ NAME(rp->owner->name);
+ USHORT(rp->type);
+ USHORT(rp->owner->class);
+ return p;
+}
+
+static uchar*
+rrloop(RR *rp, int *countp, uchar *p, uchar *ep, Dict *dp, int quest)
+{
+ uchar *np;
+
+ *countp = 0;
+ for(; rp && p < ep; rp = rp->next){
+ if(quest)
+ np = convQ2M(rp, p, ep, dp);
+ else
+ np = convRR2M(rp, p, ep, dp);
+ if(np > ep)
+ break;
+ p = np;
+ (*countp)++;
+ }
+ return p;
+}
+
+/*
+ * convert into a message
+ */
+int
+convDNS2M(DNSmsg *m, uchar *buf, int len)
+{
+ ulong trunc = 0;
+ uchar *p, *ep, *np;
+ Dict d;
+
+ d.n = 0;
+ d.start = buf;
+ d.ep = d.buf;
+ memset(buf, 0, len);
+ m->qdcount = m->ancount = m->nscount = m->arcount = 0;
+
+ /* first pack in the RR's so we can get real counts */
+ p = buf + 12;
+ ep = buf + len;
+ p = rrloop(m->qd, &m->qdcount, p, ep, &d, 1);
+ p = rrloop(m->an, &m->ancount, p, ep, &d, 0);
+ p = rrloop(m->ns, &m->nscount, p, ep, &d, 0);
+ p = rrloop(m->ar, &m->arcount, p, ep, &d, 0);
+ if(p > ep) {
+ trunc = Ftrunc;
+ dnslog("udp packet full; truncating my reply");
+ p = ep;
+ }
+
+ /* now pack the rest */
+ np = p;
+ p = buf;
+ ep = buf + len;
+ USHORT(m->id);
+ USHORT(m->flags | trunc);
+ USHORT(m->qdcount);
+ USHORT(m->ancount);
+ USHORT(m->nscount);
+ USHORT(m->arcount);
+ USED(p);
+ return np - buf;
+}
--- /dev/null
+++ b/convM2DNS.c
@@ -1,0 +1,603 @@
+#include <u.h>
+#include <libc.h>
+#include <ip.h>
+#include "dns.h"
+
+typedef struct Scan Scan;
+struct Scan
+{
+ uchar *base; /* input buffer */
+ uchar *p; /* current position */
+ uchar *ep; /* byte after the end */
+
+ char *err;
+ char errbuf[256]; /* hold a formatted error sometimes */
+ int rcode; /* outgoing response codes (reply flags) */
+ int stop; /* flag: stop processing */
+ int trunc; /* flag: input truncated */
+};
+
+static int
+errneg(RR *rp, Scan *sp, int actual)
+{
+ snprint(sp->errbuf, sizeof sp->errbuf, "negative len %d: %R",
+ actual, rp);
+ sp->err = sp->errbuf;
+ return 0;
+}
+
+static int
+errtoolong(RR *rp, Scan *sp, int remain, int need, char *where)
+{
+ char *p, *ep;
+ char ptype[64];
+
+ p = sp->errbuf;
+ ep = sp->errbuf + sizeof sp->errbuf - 1;
+ if (where)
+ p = seprint(p, ep, "%s: ", where);
+ if (rp)
+ p = seprint(p, ep, "type %s RR: ",
+ rrname(rp->type, ptype, sizeof ptype));
+ p = seprint(p, ep, "%d bytes needed; %d remain", need, remain);
+ if (rp)
+ seprint(p, ep, ": %R", rp);
+ sp->err = sp->errbuf;
+ /* hack to cope with servers that don't set Ftrunc when they should */
+ if (remain < Maxudp && need > Maxudp)
+ sp->trunc = 1;
+ if (debug && rp)
+ dnslog("malformed rr: %R", rp);
+ return 0;
+}
+
+/*
+ * get a ushort/ulong
+ */
+static ushort
+gchar(RR *rp, Scan *sp)
+{
+ ushort x;
+
+ if(sp->err)
+ return 0;
+ if(sp->ep - sp->p < 1)
+ return errtoolong(rp, sp, sp->ep - sp->p, 1, "gchar");
+ x = sp->p[0];
+ sp->p += 1;
+ return x;
+}
+static ushort
+gshort(RR *rp, Scan *sp)
+{
+ ushort x;
+
+ if(sp->err)
+ return 0;
+ if(sp->ep - sp->p < 2)
+ return errtoolong(rp, sp, sp->ep - sp->p, 2, "gshort");
+ x = sp->p[0]<<8 | sp->p[1];
+ sp->p += 2;
+ return x;
+}
+static ulong
+glong(RR *rp, Scan *sp)
+{
+ ulong x;
+
+ if(sp->err)
+ return 0;
+ if(sp->ep - sp->p < 4)
+ return errtoolong(rp, sp, sp->ep - sp->p, 4, "glong");
+ x = sp->p[0]<<24 | sp->p[1]<<16 | sp->p[2]<<8 | sp->p[3];
+ sp->p += 4;
+ return x;
+}
+
+/*
+ * get an ip address
+ */
+static DN*
+gv4addr(RR *rp, Scan *sp)
+{
+ char addr[32];
+
+ if(sp->err)
+ return 0;
+ if(sp->ep - sp->p < 4)
+ return (DN*)errtoolong(rp, sp, sp->ep - sp->p, 4, "gv4addr");
+ snprint(addr, sizeof addr, "%V", sp->p);
+ sp->p += 4;
+
+ return dnlookup(addr, Cin, 1);
+}
+static DN*
+gv6addr(RR *rp, Scan *sp)
+{
+ char addr[64];
+
+ if(sp->err)
+ return 0;
+ if(sp->ep - sp->p < IPaddrlen)
+ return (DN*)errtoolong(rp, sp, sp->ep - sp->p, IPaddrlen,
+ "gv6addr");
+ snprint(addr, sizeof addr, "%I", sp->p);
+ sp->p += IPaddrlen;
+
+ return dnlookup(addr, Cin, 1);
+}
+
+/*
+ * get a string. make it an internal symbol.
+ */
+static DN*
+gsym(RR *rp, Scan *sp)
+{
+ int n;
+ char sym[Strlen+1];
+
+ if(sp->err)
+ return 0;
+ n = 0;
+ if (sp->p < sp->ep)
+ n = *(sp->p++);
+ if(sp->ep - sp->p < n)
+ return (DN*)errtoolong(rp, sp, sp->ep - sp->p, n, "gsym");
+
+ if(n > Strlen){
+ sp->err = "illegal string (symbol)";
+ return 0;
+ }
+ strncpy(sym, (char*)sp->p, n);
+ sym[n] = 0;
+ if (strlen(sym) != n)
+ sp->err = "symbol shorter than declared length";
+ sp->p += n;
+
+ return dnlookup(sym, Csym, 1);
+}
+
+/*
+ * get a string. don't make it an internal symbol.
+ */
+static Txt*
+gstr(RR *rp, Scan *sp)
+{
+ int n;
+ char sym[Strlen+1];
+ Txt *t;
+
+ if(sp->err)
+ return 0;
+ n = 0;
+ if (sp->p < sp->ep)
+ n = *(sp->p++);
+ if(sp->ep - sp->p < n)
+ return (Txt*)errtoolong(rp, sp, sp->ep - sp->p, n, "gstr");
+
+ if(n > Strlen){
+ sp->err = "illegal string";
+ return 0;
+ }
+ strncpy(sym, (char*)sp->p, n);
+ sym[n] = 0;
+ if (strlen(sym) != n)
+ sp->err = "string shorter than declared length";
+ sp->p += n;
+
+ t = emalloc(sizeof(*t));
+ t->next = nil;
+ t->p = estrdup(sym);
+ return t;
+}
+
+/*
+ * get a sequence of bytes
+ */
+static int
+gbytes(RR *rp, Scan *sp, uchar **p, int n)
+{
+ *p = nil; /* i think this is a good idea */
+ if(sp->err)
+ return 0;
+ if(n < 0)
+ return errneg(rp, sp, n);
+ if(sp->ep - sp->p < n)
+ return errtoolong(rp, sp, sp->ep - sp->p, n, "gbytes");
+ *p = emalloc(n);
+ memmove(*p, sp->p, n);
+ sp->p += n;
+
+ return n;
+}
+
+/*
+ * get a domain name. 'to' must point to a buffer at least Domlen+1 long.
+ */
+static char*
+gname(char *to, RR *rp, Scan *sp)
+{
+ int len, off, pointer, n;
+ char *tostart, *toend;
+ uchar *p;
+
+ tostart = to;
+ if(sp->err || sp->stop)
+ goto err;
+ pointer = 0;
+ p = sp->p;
+ if(p == nil) {
+ dnslog("gname: %R: nil sp->p", rp);
+ goto err;
+ }
+ toend = to + Domlen;
+ for(len = 0; *p && p < sp->ep; len += (pointer? 0: n+1)) {
+ n = 0;
+ switch(*p & 0300) {
+ case 0: /* normal label */
+ if(p < sp->ep)
+ n = *p++ & 077; /* pick up length */
+ if(sp->ep - p <= n){
+ sp->err = "bad name length";
+ goto err;
+ }
+ if(len + n < Domlen - 1){
+ if(n > toend - to){
+ errtoolong(rp, sp, toend - to, n,
+ "name too long");
+ goto err;
+ }
+ memmove(to, p, n);
+ to += n;
+ }
+ p += n;
+ if(*p){
+ if(to >= toend){
+ errtoolong(rp, sp, toend - to, 2,
+ "more name components but no bytes left");
+ goto err;
+ }
+ *to++ = '.';
+ }
+ break;
+ case 0100: /* edns extended label type, rfc 2671 */
+ /*
+ * treat it like an EOF for now; it seems to be at
+ * the end of a long tcp reply.
+ */
+ dnslog("edns label; first byte 0%o = '%c'", *p, *p);
+ sp->stop = 1;
+ goto err;
+ case 0200: /* reserved */
+ sp->err = "reserved-use label present";
+ goto err;
+ case 0300: /* pointer to other spot in message */
+ if(pointer++ > 10){
+ sp->err = "pointer loop";
+ goto err;
+ }
+ off = (p[0] & 077)<<8 | p[1];
+ p = sp->base + off;
+ if(p >= sp->ep){
+ sp->err = "bad pointer";
+ goto err;
+ }
+ n = 0;
+ break;
+ }
+ }
+ *to = 0;
+ if(pointer)
+ sp->p += len + 2; /* + 2 for pointer */
+ else
+ sp->p += len + 1; /* + 1 for the null domain */
+ return tostart;
+err:
+ *tostart = 0;
+ return tostart;
+}
+
+/*
+ * ms windows 2000 seems to get the bytes backward in the type field
+ * of ptr records, so return a format error as feedback.
+ */
+static ushort
+mstypehack(Scan *sp, ushort type, char *where)
+{
+ if ((uchar)type == 0 && (type>>8) != 0) {
+ USED(where);
+// dnslog("%s: byte-swapped type field in ptr rr from win2k",
+// where);
+ if (sp->rcode == Rok)
+ sp->rcode = Rformat;
+ type >>= 8;
+ }
+ return type;
+}
+
+#define NAME(x) gname(x, rp, sp)
+#define SYMBOL(x) ((x) = gsym(rp, sp))
+#define STRING(x) ((x) = gstr(rp, sp))
+#define USHORT(x) ((x) = gshort(rp, sp))
+#define ULONG(x) ((x) = glong(rp, sp))
+#define UCHAR(x) ((x) = gchar(rp, sp))
+#define V4ADDR(x) ((x) = gv4addr(rp, sp))
+#define V6ADDR(x) ((x) = gv6addr(rp, sp))
+#define BYTES(x, y) ((y) = gbytes(rp, sp, &(x), len - (sp->p - data)))
+
+/*
+ * convert the next RR from a message
+ */
+static RR*
+convM2RR(Scan *sp, char *what)
+{
+ int type, class, len, left;
+ char dname[Domlen+1];
+ uchar *data;
+ RR *rp;
+ Txt *t, **l;
+
+retry:
+ rp = nil;
+ NAME(dname);
+ USHORT(type);
+ USHORT(class);
+
+ type = mstypehack(sp, type, "convM2RR");
+ rp = rralloc(type);
+ rp->owner = dnlookup(dname, class, 1);
+ rp->type = type;
+
+ ULONG(rp->ttl);
+ USHORT(len); /* length of data following */
+ data = sp->p;
+ assert(data != nil);
+ left = sp->ep - sp->p;
+
+ /*
+ * ms windows generates a lot of badly-formatted hints.
+ * hints are only advisory, so don't log complaints about them.
+ * it also generates answers in which p overshoots ep by exactly
+ * one byte; this seems to be harmless, so don't log them either.
+ */
+ if (len > left &&
+ !(strcmp(what, "hints") == 0 ||
+ sp->p == sp->ep + 1 && strcmp(what, "answers") == 0))
+ errtoolong(rp, sp, left, len, "convM2RR");
+ if(sp->err || sp->rcode || sp->stop){
+ rrfree(rp);
+ return nil;
+ }
+ /* even if we don't log an error message, truncate length to fit data */
+ if (len > left)
+ len = left;
+
+ switch(type){
+ default:
+ /* unknown type, just ignore it */
+ sp->p = data + len;
+ rrfree(rp);
+ goto retry;
+ case Thinfo:
+ SYMBOL(rp->cpu);
+ SYMBOL(rp->os);
+ break;
+ case Tcname:
+ case Tmb:
+ case Tmd:
+ case Tmf:
+ case Tns:
+ rp->host = dnlookup(NAME(dname), Cin, 1);
+ break;
+ case Tmg:
+ case Tmr:
+ rp->mb = dnlookup(NAME(dname), Cin, 1);
+ break;
+ case Tminfo:
+ rp->rmb = dnlookup(NAME(dname), Cin, 1);
+ rp->mb = dnlookup(NAME(dname), Cin, 1);
+ break;
+ case Tmx:
+ USHORT(rp->pref);
+ rp->host = dnlookup(NAME(dname), Cin, 1);
+ break;
+ case Ta:
+ V4ADDR(rp->ip);
+ break;
+ case Taaaa:
+ V6ADDR(rp->ip);
+ break;
+ case Tptr:
+ rp->ptr = dnlookup(NAME(dname), Cin, 1);
+ break;
+ case Tsoa:
+ rp->host = dnlookup(NAME(dname), Cin, 1);
+ rp->rmb = dnlookup(NAME(dname), Cin, 1);
+ ULONG(rp->soa->serial);
+ ULONG(rp->soa->refresh);
+ ULONG(rp->soa->retry);
+ ULONG(rp->soa->expire);
+ ULONG(rp->soa->minttl);
+ break;
+ case Tsrv:
+ USHORT(rp->srv->pri);
+ USHORT(rp->srv->weight);
+ USHORT(rp->port);
+ /*
+ * rfc2782 sez no name compression but to be
+ * backward-compatible with rfc2052, we try to expand the name.
+ * if the length is under 64 bytes, either interpretation is
+ * fine; if it's longer, we'll assume it's compressed,
+ * as recommended by rfc3597.
+ */
+ rp->host = dnlookup(NAME(dname), Cin, 1);
+ break;
+ case Ttxt:
+ l = &rp->txt;
+ *l = nil;
+ while(sp->p - data < len){
+ STRING(t);
+ *l = t;
+ l = &t->next;
+ }
+ break;
+ case Tnull:
+ BYTES(rp->null->data, rp->null->dlen);
+ break;
+ case Trp:
+ rp->rmb = dnlookup(NAME(dname), Cin, 1);
+ rp->rp = dnlookup(NAME(dname), Cin, 1);
+ break;
+ case Tkey:
+ USHORT(rp->key->flags);
+ UCHAR(rp->key->proto);
+ UCHAR(rp->key->alg);
+ BYTES(rp->key->data, rp->key->dlen);
+ break;
+ case Tsig:
+ USHORT(rp->sig->type);
+ UCHAR(rp->sig->alg);
+ UCHAR(rp->sig->labels);
+ ULONG(rp->sig->ttl);
+ ULONG(rp->sig->exp);
+ ULONG(rp->sig->incep);
+ USHORT(rp->sig->tag);
+ rp->sig->signer = dnlookup(NAME(dname), Cin, 1);
+ BYTES(rp->sig->data, rp->sig->dlen);
+ break;
+ case Tcert:
+ USHORT(rp->cert->type);
+ USHORT(rp->cert->tag);
+ UCHAR(rp->cert->alg);
+ BYTES(rp->cert->data, rp->cert->dlen);
+ break;
+ }
+ if(sp->p - data != len) {
+ char ptype[64];
+
+ /*
+ * ms windows 2000 generates cname queries for reverse lookups
+ * with this particular error. don't bother logging it.
+ *
+ * server: input error: bad cname RR len (actual 2 != len 0):
+ * 235.9.104.135.in-addr.arpa cname
+ * 235.9.104.135.in-addr.arpa from 135.104.9.235
+ */
+ if (type == Tcname && sp->p - data == 2 && len == 0)
+ return rp;
+ if (len > sp->p - data){
+ dnslog("bad %s RR len (%d bytes nominal, %zud actual): %R",
+ rrname(type, ptype, sizeof ptype), len,
+ sp->p - data, rp);
+ rrfree(rp);
+ rp = nil;
+ }
+ }
+ // if(rp) dnslog("convM2RR: got %R", rp);
+ return rp;
+}
+
+/*
+ * convert the next question from a message
+ */
+static RR*
+convM2Q(Scan *sp)
+{
+ char dname[Domlen+1];
+ int type, class;
+ RR *rp;
+
+ rp = nil;
+ NAME(dname);
+ USHORT(type);
+ USHORT(class);
+ if(sp->err || sp->rcode || sp->stop)
+ return nil;
+
+ type = mstypehack(sp, type, "convM2Q");
+ rp = rralloc(type);
+ rp->owner = dnlookup(dname, class, 1);
+
+ return rp;
+}
+
+static RR*
+rrloop(Scan *sp, char *what, int count, int quest)
+{
+ int i;
+ RR *first, *rp, **l;
+
+ if(sp->err || sp->rcode || sp->stop)
+ return nil;
+ l = &first;
+ first = nil;
+ for(i = 0; i < count; i++){
+ rp = quest? convM2Q(sp): convM2RR(sp, what);
+ if(rp == nil)
+ break;
+ setmalloctag(rp, getcallerpc(&sp));
+ /*
+ * it might be better to ignore the bad rr, possibly break out,
+ * but return the previous rrs, if any. that way our callers
+ * would know that they had got a response, however ill-formed.
+ */
+ if(sp->err || sp->rcode || sp->stop){
+ rrfree(rp);
+ break;
+ }
+ *l = rp;
+ l = &rp->next;
+ }
+// if(first)
+// setmalloctag(first, getcallerpc(&sp));
+ return first;
+}
+
+/*
+ * convert the next DNS from a message stream.
+ * if there are formatting errors or the like during parsing of the message,
+ * set *codep to the outgoing response code (e.g., Rformat), which will
+ * abort processing and reply immediately with the outgoing response code.
+ */
+char*
+convM2DNS(uchar *buf, int len, DNSmsg *m, int *codep)
+{
+ char *err = nil;
+ RR *rp = nil;
+ Scan scan;
+ Scan *sp;
+
+ assert(len >= 0);
+ assert(buf != nil);
+ sp = &scan;
+ memset(sp, 0, sizeof *sp);
+ sp->base = sp->p = buf;
+ sp->ep = buf + len;
+ sp->err = nil;
+ sp->errbuf[0] = '\0';
+ sp->rcode = Rok;
+
+ memset(m, 0, sizeof *m);
+ USHORT(m->id);
+ USHORT(m->flags);
+ USHORT(m->qdcount);
+ USHORT(m->ancount);
+ USHORT(m->nscount);
+ USHORT(m->arcount);
+
+ m->qd = rrloop(sp, "questions", m->qdcount, 1);
+ m->an = rrloop(sp, "answers", m->ancount, 0);
+ m->ns = rrloop(sp, "nameservers",m->nscount, 0);
+ if (sp->stop)
+ sp->err = nil;
+ if (sp->err)
+ err = strdup(sp->err); /* live with bad ar's */
+ m->ar = rrloop(sp, "hints", m->arcount, 0);
+ if (sp->trunc)
+ m->flags |= Ftrunc;
+ if (sp->stop)
+ sp->rcode = Rok;
+ if (codep)
+ *codep = sp->rcode;
+ return err;
+}
--- /dev/null
+++ b/cs.c
@@ -1,0 +1,2100 @@
+#include <u.h>
+#include <libc.h>
+#include <auth.h>
+#include <fcall.h>
+#include <bio.h>
+#include <ctype.h>
+#include <ndb.h>
+#include <ip.h>
+#include <String.h>
+
+enum
+{
+ Nreply= 20,
+ Maxreply= 256,
+ Maxrequest= 128,
+ Maxpath= 128,
+ Maxfdata= 8192,
+ Maxhost= 64, /* maximum host name size */
+ Maxservice= 64, /* maximum service name size */
+ Maxactive= 200, /* maximum number of active slave procs */
+
+ Qdir= 0,
+ Qcs= 1,
+};
+
+typedef struct Mfile Mfile;
+typedef struct Mlist Mlist;
+typedef struct Network Network;
+typedef struct Flushreq Flushreq;
+typedef struct Job Job;
+
+int vers; /* incremented each clone/attach */
+
+struct Mfile
+{
+ int busy; /* fid in use */
+ int ref; /* cleanup when drops to zero */
+
+ char *user;
+ Qid qid;
+ int fid;
+
+ /*
+ * current request
+ */
+ char *net;
+ char *host;
+ char *serv;
+ char *rem;
+
+ /*
+ * result of the last lookup
+ */
+ Network *nextnet;
+ int nreply;
+ char *reply[Nreply];
+ int replylen[Nreply];
+};
+
+struct Mlist
+{
+ Mlist *next;
+ Mfile mf;
+};
+
+
+/*
+ * active requests
+ */
+struct Job
+{
+ Job *next;
+ int flushed;
+ Fcall request;
+ Fcall reply;
+};
+QLock joblock;
+Job *joblist;
+
+Mlist *mlist;
+int mfd[2];
+int debug;
+
+jmp_buf masterjmp; /* return through here after a slave process has been created */
+int *isslave; /* *isslave non-zero means this is a slave process */
+long active; /* number of active slaves */
+char *dbfile;
+Ndb *db, *netdb;
+char *csuser;
+
+void rversion(Job*);
+void rflush(Job*);
+void rattach(Job*, Mfile*);
+char* rwalk(Job*, Mfile*);
+void ropen(Job*, Mfile*);
+void rcreate(Job*, Mfile*);
+void rread(Job*, Mfile*);
+void rwrite(Job*, Mfile*);
+void rclunk(Job*, Mfile*);
+void rremove(Job*, Mfile*);
+void rstat(Job*, Mfile*);
+void rwstat(Job*, Mfile*);
+void rauth(Job*);
+void sendmsg(Job*, char*);
+void error(char*);
+void mountinit(char*, char*);
+void io(void);
+void ndbinit(void);
+void netinit(int);
+void netadd(char*);
+char *genquery(Mfile*, char*);
+char* ipinfoquery(Mfile*, char**, int);
+int needproto(Network*, Ndbtuple*);
+int lookup(Mfile*);
+void ipid(void);
+void readipinterfaces(void);
+void* emalloc(int);
+char* estrdup(char*);
+Job* newjob(void);
+void freejob(Job*);
+void setext(char*, int, char*);
+void cleanmf(Mfile*);
+
+QLock dblock; /* mutex on database operations */
+QLock netlock; /* mutex for netinit() */
+
+char *logfile = "cs";
+
+char mntpt[Maxpath];
+char netndb[Maxpath];
+
+/*
+ * Network specific translators
+ */
+Ndbtuple* iplookup(Network*, char*, char*);
+char* iptrans(Ndbtuple*, Network*, char*, char*, int);
+Ndbtuple* svclookup(Network*, char*, char*);
+char* svctrans(Ndbtuple*, Network*, char*, char*, int);
+Ndbtuple* telcolookup(Network*, char*, char*);
+char* telcotrans(Ndbtuple*, Network*, char*, char*, int);
+
+Ndbtuple* dnsiplookup(char*, Ndbs*, int);
+Ndbtuple* myipinfo(Ndb *db, char **list, int n);
+
+struct Network
+{
+ char *net;
+ Ndbtuple *(*lookup)(Network*, char*, char*);
+ char *(*trans)(Ndbtuple*, Network*, char*, char*, int);
+
+ char considered; /* flag: ignored for "net!"? */
+ char fasttimeout; /* flag. was for IL */
+ char ipvers; /* flag: V4, V6 */
+
+ Network *next;
+};
+
+enum {
+ Ntcp = 1,
+
+ V4 = 1,
+ V6 = 2,
+};
+
+/*
+ * net doesn't apply to (r)udp, icmp(v6), or telco (for speed).
+ */
+Network network[] = {
+ { "il", iplookup, iptrans, 0, 1, V4, },
+ { "tcp", iplookup, iptrans, 0, 0, V4|V6, },
+ { "il", iplookup, iptrans, 0, 0, V4, },
+ { "udp", iplookup, iptrans, 1, 0, V4|V6, },
+ { "icmp", iplookup, iptrans, 1, 0, V4, },
+ { "icmpv6", iplookup, iptrans, 1, 0, V6, },
+ { "rudp", iplookup, iptrans, 1, 0, V4, },
+ { "ssh", iplookup, iptrans, 1, 0, V4|V6, },
+ { "telco", telcolookup, telcotrans, 1, 0, 0, },
+ { "svc", svclookup, svctrans, 1, 0, 0, },
+ { 0 },
+};
+
+QLock ipifclock;
+Ipifc *ipifcs;
+int confipvers;
+int dnsipvers;
+int lookipvers = V4|V6;
+
+char *mysysname;
+
+Network *netlist; /* networks ordered by preference */
+Network *last;
+
+static void
+nstrcpy(char *to, char *from, int len)
+{
+ strncpy(to, from, len);
+ to[len-1] = 0;
+}
+
+void
+usage(void)
+{
+ fprint(2, "usage: %s [-46dn] [-f ndb-file] [-x netmtpt]\n", argv0);
+ exits("usage");
+}
+
+void
+main(int argc, char *argv[])
+{
+ int justsetname;
+ char ext[Maxpath], servefile[Maxpath];
+
+ justsetname = 0;
+ setnetmtpt(mntpt, sizeof(mntpt), nil);
+ ext[0] = 0;
+ ARGBEGIN{
+ case '4':
+ lookipvers = V4;
+ break;
+ case '6':
+ lookipvers = V6;
+ break;
+ case 'd':
+ debug = 1;
+ break;
+ case 'f':
+ dbfile = EARGF(usage());
+ break;
+ case 'n':
+ justsetname = 1;
+ break;
+ case 'x':
+ setnetmtpt(mntpt, sizeof(mntpt), EARGF(usage()));
+ setext(ext, sizeof(ext), mntpt);
+ break;
+ }ARGEND
+ USED(argc);
+ USED(argv);
+
+ snprint(netndb, sizeof(netndb), "%s/ndb", mntpt);
+
+ fmtinstall('E', eipfmt);
+ fmtinstall('I', eipfmt);
+ fmtinstall('M', eipfmt);
+ fmtinstall('F', fcallfmt);
+
+ ndbinit();
+ netinit(0);
+
+ if(!justsetname){
+ snprint(servefile, sizeof(servefile), "#s/cs%s", ext);
+ unmount(servefile, mntpt);
+ remove(servefile);
+
+ rfork(RFREND|RFNOTEG);
+ csuser = estrdup(getuser());
+ mountinit(servefile, mntpt);
+ io();
+ }
+ exits(0);
+}
+
+/*
+ * if a mount point is specified, set the cs extention to be the mount point
+ * with '_'s replacing '/'s
+ */
+void
+setext(char *ext, int n, char *p)
+{
+ int i, c;
+
+ n--;
+ for(i = 0; i < n; i++){
+ c = p[i];
+ if(c == 0)
+ break;
+ if(c == '/')
+ c = '_';
+ ext[i] = c;
+ }
+ ext[i] = 0;
+}
+
+void
+mountinit(char *service, char *mntpt)
+{
+ int f;
+ int p[2];
+ char buf[32];
+
+ if(pipe(p) < 0)
+ error("pipe failed");
+
+ /*
+ * make a /srv/cs
+ */
+ f = create(service, OWRITE|ORCLOSE, 0666);
+ if(f < 0)
+ error(service);
+ snprint(buf, sizeof(buf), "%d", p[1]);
+ if(write(f, buf, strlen(buf)) != strlen(buf))
+ error("write /srv/cs");
+
+ switch(rfork(RFFDG|RFPROC|RFNAMEG)){
+ case 0:
+ close(p[1]);
+ procsetname("%s", mntpt);
+ break;
+ case -1:
+ error("fork failed");
+ default:
+ /*
+ * put ourselves into the file system
+ */
+ close(p[0]);
+ if(mount(p[1], -1, mntpt, MAFTER, "") == -1)
+ error("mount failed");
+ _exits(0);
+ }
+ mfd[0] = mfd[1] = p[0];
+}
+
+void
+ndbinit(void)
+{
+ db = ndbopen(dbfile);
+ if(db == nil)
+ error("can't open network database");
+
+ for(netdb = db; netdb != nil; netdb = netdb->next)
+ if(strcmp(netdb->file, netndb) == 0)
+ return;
+
+ netdb = ndbopen(netndb);
+ if(netdb != nil){
+ netdb->nohash = 1;
+ db = ndbcat(netdb, db);
+ }
+}
+
+Mfile*
+newfid(int fid)
+{
+ Mlist *f, *ff;
+ Mfile *mf;
+
+ ff = nil;
+ for(f = mlist; f != nil; f = f->next)
+ if(f->mf.busy && f->mf.fid == fid)
+ return &f->mf;
+ else if(ff == nil && !f->mf.busy && !f->mf.ref)
+ ff = f;
+ if(ff == nil){
+ ff = emalloc(sizeof *f);
+ ff->next = mlist;
+ mlist = ff;
+ }
+ mf = &ff->mf;
+ memset(mf, 0, sizeof *mf);
+ mf->fid = fid;
+ return mf;
+}
+
+Job*
+newjob(void)
+{
+ Job *job;
+
+ job = emalloc(sizeof *job);
+ qlock(&joblock);
+ job->next = joblist;
+ joblist = job;
+ job->request.tag = -1;
+ qunlock(&joblock);
+ return job;
+}
+
+void
+freejob(Job *job)
+{
+ Job **l;
+
+ qlock(&joblock);
+ for(l = &joblist; *l != nil; l = &(*l)->next){
+ if((*l) == job){
+ *l = job->next;
+ break;
+ }
+ }
+ qunlock(&joblock);
+ free(job);
+}
+
+void
+flushjob(int tag)
+{
+ Job *job;
+
+ qlock(&joblock);
+ for(job = joblist; job != nil; job = job->next){
+ if(job->request.tag == tag && job->request.type != Tflush){
+ job->flushed = 1;
+ break;
+ }
+ }
+ qunlock(&joblock);
+}
+
+void
+io(void)
+{
+ long n;
+ Mfile *mf;
+ int slaveflag;
+ uchar mdata[IOHDRSZ + Maxfdata];
+ Job *job;
+
+ /*
+ * if we ask dns to fulfill requests,
+ * a slave process is created to wait for replies. The
+ * master process returns immediately via a longjmp
+ * through 'masterjmp'.
+ *
+ * *isslave is a pointer into the call stack to a variable
+ * that tells whether or not the current process is a slave.
+ */
+ slaveflag = 0; /* init slave variable */
+ isslave = &slaveflag;
+ setjmp(masterjmp);
+
+ for(;;){
+ n = read9pmsg(mfd[0], mdata, sizeof mdata);
+ if(n < 0)
+ error("mount read");
+ if(n == 0)
+ break;
+ job = newjob();
+ if(convM2S(mdata, n, &job->request) != n){
+ syslog(1, logfile, "format error %ux %ux %ux %ux %ux",
+ mdata[0], mdata[1], mdata[2], mdata[3], mdata[4]);
+ freejob(job);
+ break;
+ }
+ qlock(&dblock);
+ mf = newfid(job->request.fid);
+ if(debug)
+ syslog(0, logfile, "%F", &job->request);
+
+ switch(job->request.type){
+ default:
+ syslog(1, logfile, "unknown request type %d", job->request.type);
+ break;
+ case Tversion:
+ rversion(job);
+ break;
+ case Tauth:
+ rauth(job);
+ break;
+ case Tflush:
+ rflush(job);
+ break;
+ case Tattach:
+ rattach(job, mf);
+ break;
+ case Twalk:
+ rwalk(job, mf);
+ break;
+ case Topen:
+ ropen(job, mf);
+ break;
+ case Tcreate:
+ rcreate(job, mf);
+ break;
+ case Tread:
+ rread(job, mf);
+ break;
+ case Twrite:
+ rwrite(job, mf);
+ break;
+ case Tclunk:
+ rclunk(job, mf);
+ break;
+ case Tremove:
+ rremove(job, mf);
+ break;
+ case Tstat:
+ rstat(job, mf);
+ break;
+ case Twstat:
+ rwstat(job, mf);
+ break;
+ }
+ qunlock(&dblock);
+
+ freejob(job);
+
+ /*
+ * slave processes die after replying
+ */
+ if(*isslave){
+ if(debug)
+ syslog(0, logfile, "slave death %d", getpid());
+ adec(&active);
+ _exits(0);
+ }
+ }
+}
+
+void
+rversion(Job *job)
+{
+ if(job->request.msize > IOHDRSZ + Maxfdata)
+ job->reply.msize = IOHDRSZ + Maxfdata;
+ else
+ job->reply.msize = job->request.msize;
+ job->reply.version = "9P2000";
+ if(strncmp(job->request.version, "9P", 2) != 0)
+ job->reply.version = "unknown";
+ sendmsg(job, nil);
+}
+
+void
+rauth(Job *job)
+{
+ sendmsg(job, "cs: authentication not required");
+}
+
+/*
+ * don't flush till all the slaves are done
+ */
+void
+rflush(Job *job)
+{
+ flushjob(job->request.oldtag);
+ sendmsg(job, nil);
+}
+
+void
+rattach(Job *job, Mfile *mf)
+{
+ if(mf->busy == 0){
+ mf->busy = 1;
+ mf->user = estrdup(job->request.uname);
+ }
+ mf->qid.vers = vers++;
+ mf->qid.type = QTDIR;
+ mf->qid.path = 0LL;
+ job->reply.qid = mf->qid;
+ sendmsg(job, nil);
+}
+
+
+char*
+rwalk(Job *job, Mfile *mf)
+{
+ char *err;
+ char **elems;
+ int nelems;
+ int i;
+ Mfile *nmf;
+ Qid qid;
+
+ err = nil;
+ nmf = nil;
+ elems = job->request.wname;
+ nelems = job->request.nwname;
+ job->reply.nwqid = 0;
+
+ if(job->request.newfid != job->request.fid){
+ /* clone fid */
+ nmf = newfid(job->request.newfid);
+ if(nmf->busy){
+ nmf = nil;
+ err = "clone to used channel";
+ goto send;
+ }
+ *nmf = *mf;
+ nmf->user = estrdup(mf->user);
+ nmf->fid = job->request.newfid;
+ nmf->qid.vers = vers++;
+ mf = nmf;
+ }
+ /* else nmf will be nil */
+
+ qid = mf->qid;
+ if(nelems > 0){
+ /* walk fid */
+ for(i=0; i<nelems && i<MAXWELEM; i++){
+ if((qid.type & QTDIR) == 0){
+ err = "not a directory";
+ break;
+ }
+ if(strcmp(elems[i], "..") == 0 || strcmp(elems[i], ".") == 0){
+ qid.type = QTDIR;
+ qid.path = Qdir;
+ Found:
+ job->reply.wqid[i] = qid;
+ job->reply.nwqid++;
+ continue;
+ }
+ if(strcmp(elems[i], "cs") == 0){
+ qid.type = QTFILE;
+ qid.path = Qcs;
+ goto Found;
+ }
+ err = "file does not exist";
+ break;
+ }
+ }
+
+ send:
+ if(nmf != nil && (err!=nil || job->reply.nwqid<nelems)){
+ cleanmf(nmf);
+ free(nmf->user);
+ nmf->user = nil;
+ nmf->busy = 0;
+ nmf->fid = 0;
+ }
+ if(err == nil)
+ mf->qid = qid;
+ sendmsg(job, err);
+ return err;
+}
+
+void
+ropen(Job *job, Mfile *mf)
+{
+ int mode;
+ char *err;
+
+ err = nil;
+ mode = job->request.mode;
+ if(mf->qid.type & QTDIR){
+ if(mode)
+ err = "permission denied";
+ }
+ job->reply.qid = mf->qid;
+ job->reply.iounit = 0;
+ sendmsg(job, err);
+}
+
+void
+rcreate(Job *job, Mfile *mf)
+{
+ USED(mf);
+ sendmsg(job, "creation permission denied");
+}
+
+void
+rread(Job *job, Mfile *mf)
+{
+ int i, n, cnt;
+ long off, toff, clock;
+ Dir dir;
+ uchar buf[Maxfdata];
+ char *err;
+
+ n = 0;
+ err = nil;
+ off = job->request.offset;
+ cnt = job->request.count;
+ mf->ref++;
+
+ if(mf->qid.type & QTDIR){
+ clock = time(0);
+ if(off == 0){
+ memset(&dir, 0, sizeof dir);
+ dir.name = "cs";
+ dir.qid.type = QTFILE;
+ dir.qid.vers = vers;
+ dir.qid.path = Qcs;
+ dir.mode = 0666;
+ dir.length = 0;
+ dir.uid = mf->user;
+ dir.gid = mf->user;
+ dir.muid = mf->user;
+ dir.atime = clock; /* wrong */
+ dir.mtime = clock; /* wrong */
+ n = convD2M(&dir, buf, sizeof buf);
+ }
+ job->reply.data = (char*)buf;
+ goto send;
+ }
+
+ for(;;){
+ /* look for an answer at the right offset */
+ toff = 0;
+ for(i = 0; mf->reply[i] != nil && i < mf->nreply; i++){
+ n = mf->replylen[i];
+ if(off < toff + n)
+ break;
+ toff += n;
+ }
+ if(i < mf->nreply)
+ break; /* got something to return */
+
+ /* try looking up more answers */
+ if(lookup(mf) == 0 || job->flushed){
+ /* no more */
+ n = 0;
+ goto send;
+ }
+ }
+
+ /* give back a single reply (or part of one) */
+ job->reply.data = mf->reply[i] + (off - toff);
+ if(cnt > toff - off + n)
+ n = toff - off + n;
+ else
+ n = cnt;
+
+send:
+ job->reply.count = n;
+ sendmsg(job, err);
+
+ if(--mf->ref == 0 && mf->busy == 0)
+ cleanmf(mf);
+}
+
+void
+cleanmf(Mfile *mf)
+{
+ int i;
+
+ if(mf->net != nil){
+ free(mf->net);
+ mf->net = nil;
+ }
+ if(mf->host != nil){
+ free(mf->host);
+ mf->host = nil;
+ }
+ if(mf->serv != nil){
+ free(mf->serv);
+ mf->serv = nil;
+ }
+ if(mf->rem != nil){
+ free(mf->rem);
+ mf->rem = nil;
+ }
+ for(i = 0; i < mf->nreply; i++){
+ free(mf->reply[i]);
+ mf->reply[i] = nil;
+ mf->replylen[i] = 0;
+ }
+ mf->nreply = 0;
+ mf->nextnet = netlist;
+}
+
+void
+rwrite(Job *job, Mfile *mf)
+{
+ int cnt, n;
+ char *err;
+ char *field[4];
+ char curerr[64];
+
+ err = nil;
+ cnt = job->request.count;
+ if(mf->qid.type & QTDIR){
+ err = "can't write directory";
+ goto send;
+ }
+ if(cnt >= Maxrequest){
+ err = "request too long";
+ goto send;
+ }
+ job->request.data[cnt] = 0;
+
+ if(strcmp(mf->user, "none") == 0 || strcmp(mf->user, csuser) != 0)
+ goto query; /* skip special commands if not owner */
+
+ /*
+ * toggle debugging
+ */
+ if(strncmp(job->request.data, "debug", 5)==0){
+ debug ^= 1;
+ syslog(1, logfile, "debug %d", debug);
+ goto send;
+ }
+
+ /*
+ * toggle ipv4 lookups
+ */
+ if(strncmp(job->request.data, "ipv4", 4)==0){
+ lookipvers ^= V4;
+ syslog(1, logfile, "ipv4lookups %d", (lookipvers & V4) != 0);
+ goto send;
+ }
+
+ /*
+ * toggle ipv6 lookups
+ */
+ if(strncmp(job->request.data, "ipv6", 4)==0){
+ lookipvers ^= V6;
+ syslog(1, logfile, "ipv6lookups %d", (lookipvers & V6) != 0);
+ goto send;
+ }
+
+ /*
+ * add networks to the default list
+ */
+ if(strncmp(job->request.data, "add ", 4)==0){
+ if(job->request.data[cnt-1] == '\n')
+ job->request.data[cnt-1] = 0;
+ netadd(job->request.data+4);
+ readipinterfaces();
+ goto send;
+ }
+
+ /*
+ * refresh all state
+ */
+ if(strncmp(job->request.data, "refresh", 7)==0){
+ netinit(1);
+ goto send;
+ }
+
+query:
+ if(mf->ref){
+ err = "query already in progress";
+ goto send;
+ }
+ mf->ref++;
+
+ /* start transaction with a clean slate */
+ cleanmf(mf);
+
+ /*
+ * look for a general query
+ */
+ if(*job->request.data == '!'){
+ err = genquery(mf, job->request.data+1);
+ goto done;
+ }
+
+ if(debug)
+ syslog(0, logfile, "write %s", job->request.data);
+ /*
+ * break up name
+ */
+ n = getfields(job->request.data, field, 4, 1, "!");
+ switch(n){
+ case 1:
+ mf->net = estrdup("net");
+ mf->host = estrdup(field[0]);
+ break;
+ case 4:
+ mf->rem = estrdup(field[3]);
+ /* fall through */
+ case 3:
+ mf->serv = estrdup(field[2]);
+ /* fall through */
+ case 2:
+ mf->host = estrdup(field[1]);
+ mf->net = estrdup(field[0]);
+ break;
+ }
+
+ /*
+ * do the first net worth of lookup
+ */
+ if(lookup(mf) == 0){
+ rerrstr(curerr, sizeof curerr);
+ err = curerr;
+ }
+
+done:
+ if(--mf->ref == 0 && mf->busy == 0)
+ cleanmf(mf);
+
+send:
+ job->reply.count = cnt;
+ sendmsg(job, err);
+}
+
+void
+rclunk(Job *job, Mfile *mf)
+{
+ if(mf->ref == 0)
+ cleanmf(mf);
+ free(mf->user);
+ mf->user = nil;
+ mf->fid = 0;
+ mf->busy = 0;
+ sendmsg(job, nil);
+}
+
+void
+rremove(Job *job, Mfile *mf)
+{
+ USED(mf);
+ sendmsg(job, "remove permission denied");
+}
+
+void
+rstat(Job *job, Mfile *mf)
+{
+ Dir dir;
+ uchar buf[IOHDRSZ+Maxfdata];
+
+ memset(&dir, 0, sizeof dir);
+ if(mf->qid.type & QTDIR){
+ dir.name = ".";
+ dir.mode = DMDIR|0555;
+ } else {
+ dir.name = "cs";
+ dir.mode = 0666;
+ }
+ dir.qid = mf->qid;
+ dir.length = 0;
+ dir.uid = mf->user;
+ dir.gid = mf->user;
+ dir.muid = mf->user;
+ dir.atime = dir.mtime = time(0);
+ job->reply.nstat = convD2M(&dir, buf, sizeof buf);
+ job->reply.stat = buf;
+ sendmsg(job, nil);
+}
+
+void
+rwstat(Job *job, Mfile *mf)
+{
+ USED(mf);
+ sendmsg(job, "wstat permission denied");
+}
+
+void
+sendmsg(Job *job, char *err)
+{
+ int n;
+ uchar mdata[IOHDRSZ + Maxfdata];
+ char ename[ERRMAX];
+
+ if(err){
+ job->reply.type = Rerror;
+ snprint(ename, sizeof(ename), "cs: %s", err);
+ job->reply.ename = ename;
+ }else{
+ job->reply.type = job->request.type+1;
+ }
+ job->reply.tag = job->request.tag;
+ n = convS2M(&job->reply, mdata, sizeof mdata);
+ if(n == 0){
+ syslog(1, logfile, "sendmsg convS2M of %F returns 0", &job->reply);
+ abort();
+ }
+ qlock(&joblock);
+ if(job->flushed == 0)
+ if(write(mfd[1], mdata, n)!=n)
+ error("mount write");
+ qunlock(&joblock);
+ if(debug)
+ syslog(0, logfile, "%F %d", &job->reply, n);
+}
+
+void
+error(char *s)
+{
+ syslog(1, logfile, "%s: %r", s);
+ _exits(0);
+}
+
+static uchar loopbacknet[IPaddrlen] = {
+ 0, 0, 0, 0,
+ 0, 0, 0, 0,
+ 0, 0, 0xff, 0xff,
+ 127, 0, 0, 0
+};
+static uchar loopbackmask[IPaddrlen] = {
+ 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0, 0, 0
+};
+static uchar loopback6[IPaddrlen] = {
+ 0, 0, 0, 0,
+ 0, 0, 0, 0,
+ 0, 0, 0, 0,
+ 0, 0, 0, 1
+};
+
+void
+readipinterfaces(void)
+{
+ uchar mynet[IPaddrlen];
+ Ipifc *ifc;
+ Iplifc *lifc;
+ int conf, dns;
+
+ conf = dns = 0;
+ qlock(&ipifclock);
+ ipifcs = readipifc(mntpt, ipifcs, -1);
+ for(ifc = ipifcs; ifc != nil; ifc = ifc->next){
+ for(lifc = ifc->lifc; lifc != nil; lifc = lifc->next){
+ if(ipcmp(lifc->ip, IPnoaddr) == 0)
+ continue;
+ if(isv4(lifc->ip)){
+ conf |= V4;
+ maskip(lifc->ip, loopbackmask, mynet);
+ if(ipcmp(mynet, loopbacknet) == 0)
+ continue;
+ dns |= V4;
+ } else {
+ conf |= V6;
+ if(ISIPV6LINKLOCAL(lifc->ip))
+ continue;
+ if(ipcmp(lifc->ip, loopback6) == 0)
+ continue;
+ dns |= V6;
+ }
+ }
+ }
+ qunlock(&ipifclock);
+ confipvers = conf;
+ dnsipvers = dns;
+}
+
+/*
+ * get the system name
+ */
+void
+ipid(void)
+{
+ char eaddr[16], buf[Maxpath];
+ uchar addr[6];
+ Ndbtuple *t, *tt;
+ char *p, *attr;
+ Ndbs s;
+ int f, n;
+ Dir *d;
+
+ if(mysysname != nil)
+ return;
+
+ /*
+ * environment has priority.
+ *
+ * on the sgi power the default system name
+ * is the ip address. ignore that.
+ *
+ */
+ p = getenv("sysname");
+ if(p != nil && *p != 0){
+ attr = ipattr(p);
+ if(strcmp(attr, "ip") != 0) {
+ mysysname = p;
+ goto setsys;
+ }
+ free(p);
+ }
+
+ /* try configured interfaces */
+ attr = "sys";
+ t = s.t = myipinfo(db, &attr, 1);
+ if(t != nil)
+ goto found;
+
+ /* try ethernet interfaces */
+ n = 0;
+ d = nil;
+ f = open(mntpt, OREAD);
+ if(f >= 0){
+ n = dirreadall(f, &d);
+ close(f);
+ }
+ for(f = 0; f < n; f++){
+ if((d[f].mode & DMDIR) == 0 || strncmp(d[f].name, "ether", 5) != 0)
+ continue;
+ snprint(buf, sizeof buf, "%s/%s", mntpt, d[f].name);
+ if(myetheraddr(addr, buf) >= 0){
+ snprint(eaddr, sizeof(eaddr), "%E", addr);
+ free(ndbgetvalue(db, &s, "ether", eaddr, "sys", &t));
+ if(t != nil){
+ free(d);
+ goto found;
+ }
+ }
+ }
+ free(d);
+
+ /* nothing else worked, use ip address */
+ attr = "ip";
+ t = s.t = myipinfo(db, &attr, 1);
+ if(t == nil)
+ return;
+
+found:
+ /* found in database */
+ if((tt = ndbfindattr(t, s.t, "sys")) != nil)
+ mysysname = estrdup(tt->val);
+ else if((tt = ndbfindattr(t, s.t, "ip")) != nil)
+ mysysname = estrdup(tt->val);
+ ndbfree(t);
+
+ if(mysysname == nil)
+ return;
+
+setsys:
+ /* set /dev/sysname if we now know it */
+ f = open("/dev/sysname", OWRITE);
+ if(f >= 0){
+ write(f, mysysname, strlen(mysysname));
+ close(f);
+ }
+}
+
+/*
+ * Set up a list of default networks by looking for
+ * /net/^*^/clone.
+ */
+void
+netinit(int background)
+{
+ char clone[Maxpath];
+ Network *np;
+
+ if(background){
+ if(rfork(RFPROC|RFNOTEG|RFMEM|RFNOWAIT) != 0)
+ return;
+ qlock(&netlock);
+ }
+
+ /* add the mounted networks to the default list */
+ for(np = network; np->net != nil; np++){
+ if(np->considered)
+ continue;
+ snprint(clone, sizeof(clone), "%s/%s/clone", mntpt, np->net);
+ if(access(clone, AEXIST) < 0)
+ continue;
+ if(netlist != nil)
+ last->next = np;
+ else
+ netlist = np;
+ last = np;
+ np->next = nil;
+ np->considered = 1;
+ }
+
+ /* find out what our ip addresses are */
+ readipinterfaces();
+
+ /* set the system name if we need to, these days ip is all we have */
+ ipid();
+
+ if(debug)
+ syslog(0, logfile, "mysysname %s", mysysname?mysysname:"???");
+
+ if(background){
+ qunlock(&netlock);
+ _exits(0);
+ }
+}
+
+/*
+ * add networks to the standard list
+ */
+void
+netadd(char *p)
+{
+ Network *np;
+ char *field[12];
+ int i, n;
+
+ n = getfields(p, field, 12, 1, " ");
+ for(i = 0; i < n; i++){
+ for(np = network; np->net != nil; np++){
+ if(strcmp(field[i], np->net) != 0)
+ continue;
+ if(np->considered)
+ break;
+ if(netlist != nil)
+ last->next = np;
+ else
+ netlist = np;
+ last = np;
+ np->next = nil;
+ np->considered = 1;
+ }
+ }
+}
+
+int
+lookforproto(Ndbtuple *t, char *proto)
+{
+ for(; t != nil; t = t->entry)
+ if(strcmp(t->attr, "proto") == 0 && strcmp(t->val, proto) == 0)
+ return 1;
+ return 0;
+}
+
+/*
+ * lookup a request. the network "net" means we should pick the
+ * best network to get there.
+ */
+int
+lookup(Mfile *mf)
+{
+ Network *np;
+ char *cp;
+ Ndbtuple *nt, *t;
+ char reply[Maxreply];
+ int i, rv, fasttimeout;
+
+ /* open up the standard db files */
+ if(db == nil)
+ ndbinit();
+ if(db == nil)
+ error("can't open mf->network database\n");
+
+ if(mf->net == nil)
+ return 0; /* must have been a genquery */
+
+ rv = 0;
+ if(strcmp(mf->net, "net") == 0){
+ /*
+ * go through set of default nets
+ */
+ for(np = mf->nextnet; np != nil && rv == 0; np = np->next){
+ nt = (*np->lookup)(np, mf->host, mf->serv);
+ if(nt == nil)
+ continue;
+ fasttimeout = np->fasttimeout && !lookforproto(nt, np->net);
+ for(t = nt; mf->nreply < Nreply && t != nil; t = t->entry){
+ cp = (*np->trans)(t, np, mf->serv, mf->rem, fasttimeout);
+ if(cp != nil){
+ /* avoid duplicates */
+ for(i = 0; i < mf->nreply; i++)
+ if(strcmp(mf->reply[i], cp) == 0)
+ break;
+ if(i == mf->nreply){
+ /* save the reply */
+ mf->replylen[mf->nreply] = strlen(cp);
+ mf->reply[mf->nreply++] = cp;
+ rv++;
+ } else
+ free(cp);
+ }
+ }
+ ndbfree(nt);
+ }
+ mf->nextnet = np;
+ return rv;
+ }
+
+ /*
+ * if not /net, we only get one lookup
+ */
+ if(mf->nreply != 0)
+ return 0;
+
+ /*
+ * look for a specific network
+ */
+ for(np = network; np->net != nil; np++){
+ if(np->fasttimeout)
+ continue;
+ if(strcmp(np->net, mf->net) == 0)
+ break;
+ }
+
+ if(np->net != nil){
+ /*
+ * known network
+ */
+ nt = (*np->lookup)(np, mf->host, mf->serv);
+ for(t = nt; mf->nreply < Nreply && t != nil; t = t->entry){
+ cp = (*np->trans)(t, np, mf->serv, mf->rem, 0);
+ if(cp != nil){
+ mf->replylen[mf->nreply] = strlen(cp);
+ mf->reply[mf->nreply++] = cp;
+ rv++;
+ }
+ }
+ ndbfree(nt);
+ return rv;
+ } else {
+ /*
+ * not a known network, don't translate host or service
+ */
+ if(mf->serv != nil)
+ snprint(reply, sizeof(reply), "%s/%s/clone %s!%s",
+ mntpt, mf->net, mf->host, mf->serv);
+ else
+ snprint(reply, sizeof(reply), "%s/%s/clone %s",
+ mntpt, mf->net, mf->host);
+ mf->reply[0] = estrdup(reply);
+ mf->replylen[0] = strlen(reply);
+ mf->nreply = 1;
+ return 1;
+ }
+}
+
+/*
+ * translate an ip service name into a port number. If it's a numeric port
+ * number, look for restricted access.
+ *
+ * the service '*' needs no translation.
+ */
+char*
+ipserv(Network *np, char *name, char *buf, int blen)
+{
+ char *p;
+ int alpha = 0;
+ int restr = 0;
+ Ndbtuple *t, *nt;
+ Ndbs s;
+
+ /* '*' means any service */
+ if(strcmp(name, "*") == 0){
+ nstrcpy(buf, name, blen);
+ return buf;
+ }
+
+ /* see if it's numeric or symbolic */
+ for(p = name; *p; p++){
+ if(isdigit(*p))
+ {}
+ else if(isalpha(*p) || *p == '-' || *p == '$')
+ alpha = 1;
+ else
+ return nil;
+ }
+ t = nil;
+ p = nil;
+ if(alpha){
+ p = ndbgetvalue(db, &s, np->net, name, "port", &t);
+ if(p == nil)
+ return nil;
+ } else {
+ /* look up only for tcp ports < 1024 to get the restricted
+ * attribute
+ */
+ if(atoi(name) < 1024 && strcmp(np->net, "tcp") == 0)
+ p = ndbgetvalue(db, &s, "port", name, "port", &t);
+ if(p == nil)
+ p = estrdup(name);
+ }
+
+ if(t){
+ for(nt = t; nt != nil; nt = nt->entry)
+ if(strcmp(nt->attr, "restricted") == 0)
+ restr = 1;
+ ndbfree(t);
+ }
+ snprint(buf, blen, "%s%s", p, restr ? "!r" : "");
+ free(p);
+
+ return buf;
+}
+
+Ndbtuple*
+myipinfo(Ndb *db, char **list, int n)
+{
+ Ndbtuple *t, *nt;
+ char ip[64];
+ Ipifc *ifc;
+ Iplifc *lifc;
+
+ t = nil;
+ qlock(&ipifclock);
+ for(ifc = ipifcs; ifc != nil; ifc = ifc->next){
+ for(lifc = ifc->lifc; lifc != nil; lifc = lifc->next){
+ snprint(ip, sizeof(ip), "%I", lifc->ip);
+ nt = ndbipinfo(db, "ip", ip, list, n);
+ t = ndbconcatenate(t, nt);
+ }
+ }
+ qunlock(&ipifclock);
+
+ return ndbdedup(t);
+}
+
+/*
+ * reorder according to our interfaces
+ */
+static Ndbtuple*
+ipreorder(Ndbtuple *t)
+{
+ Ndbtuple *nt;
+ uchar ip[IPaddrlen];
+ uchar net[IPaddrlen];
+ uchar tnet[IPaddrlen];
+ Ipifc *ifc;
+ Iplifc *lifc;
+
+ if(t == nil)
+ return nil;
+
+ qlock(&ipifclock);
+ for(ifc = ipifcs; ifc != nil; ifc = ifc->next){
+ for(lifc = ifc->lifc; lifc != nil; lifc = lifc->next){
+ maskip(lifc->ip, lifc->mask, net);
+ for(nt = t; nt != nil; nt = nt->entry){
+ if(strcmp(nt->attr, "ip") != 0)
+ continue;
+ if(parseip(ip, nt->val) == -1)
+ continue;
+ maskip(ip, lifc->mask, tnet);
+ if(memcmp(net, tnet, IPaddrlen) == 0){
+ qunlock(&ipifclock);
+ return ndbreorder(t, nt);
+ }
+ }
+ }
+ }
+ qunlock(&ipifclock);
+
+ return t;
+}
+
+static Ndbtuple*
+ndbline(Ndbtuple *t)
+{
+ Ndbtuple *nt;
+
+ for(nt = t; nt != nil; nt = nt->entry){
+ if(nt->entry == nil)
+ nt->line = t;
+ else
+ nt->line = nt->entry;
+ }
+ return t;
+}
+
+/*
+ * lookup an ip destination
+ */
+static Ndbtuple*
+iplookuphost(Network *np, char *host)
+{
+ char *attr, *dnsname;
+ Ndbtuple *t, *nt;
+ Ndbs s;
+
+ /*
+ * turn '[ip address]' into just 'ip address'
+ */
+ if(*host == '['){
+ char tmp[Maxhost], *x;
+
+ nstrcpy(tmp, host, sizeof tmp);
+ host = tmp;
+ if((x = strchr(++host, ']')) != nil)
+ *x = 0;
+ }
+
+ /* for dial strings with no host */
+ if(strcmp(host, "*") == 0)
+ return ndbline(ndbnew("ip", "*"));
+
+ /*
+ * hack till we go v6 :: = 0.0.0.0
+ */
+ if(strcmp("::", host) == 0)
+ return ndbline(ndbnew("ip", "*"));
+
+ /*
+ * just accept addresses
+ */
+ attr = ipattr(host);
+ if(strcmp(attr, "ip") == 0)
+ return ndbline(ndbnew("ip", host));
+
+ /*
+ * give the domain name server the first opportunity to
+ * resolve domain names. if that fails try the database.
+ */
+ t = nil;
+ if(strcmp(attr, "dom") == 0)
+ t = dnsiplookup(host, &s, np->ipvers);
+ if(t == nil){
+ for(nt = ndbsearch(db, &s, attr, host); nt != nil; nt = ndbsnext(&s, attr, host)){
+ if(ndbfindattr(nt, s.t, "ip") == nil){
+ ndbfree(nt);
+ continue;
+ }
+ t = ndbconcatenate(t, ndbreorder(nt, s.t));
+ }
+ s.t = t;
+ }
+ if(t == nil){
+ if(strcmp(attr, "dom") != 0){
+ dnsname = ndbgetvalue(db, &s, attr, host, "dom", nil);
+ if(dnsname != nil){
+ t = dnsiplookup(dnsname, &s, np->ipvers);
+ free(dnsname);
+ }
+ }
+ if(t == nil)
+ t = dnsiplookup(host, &s, np->ipvers);
+ }
+ if(t == nil)
+ return nil;
+
+ /*
+ * reorder the tuple to have the matched line first and
+ * save that in the request structure.
+ */
+ return ndbreorder(t, s.t);
+}
+
+
+Ndbtuple*
+iplookup(Network *np, char *host, char *serv)
+{
+ Ndbtuple *l, *t, *nt;
+ char ts[Maxservice], *attr;
+
+ /*
+ * start with the service since it's the most likely to fail
+ * and costs the least
+ */
+ if(serv == nil || ipserv(np, serv, ts, sizeof ts) == nil){
+ werrstr("can't translate service");
+ return nil;
+ }
+
+ /*
+ * '$' means the rest of the name is an attribute that we
+ * need to search for
+ */
+ werrstr("can't translate address");
+ if(*host == '$'){
+ t = nil;
+ attr = host+1;
+ l = myipinfo(db, &attr, 1);
+ for(nt = l; nt != nil; nt = nt->entry){
+ if(strcmp(nt->attr, attr) == 0)
+ t = ndbconcatenate(t, iplookuphost(np, nt->val));
+ }
+ ndbfree(l);
+ } else
+ t = iplookuphost(np, host);
+
+ return ipreorder(t);
+}
+
+
+/*
+ * translate an ip address
+ */
+char*
+iptrans(Ndbtuple *t, Network *np, char *serv, char *rem, int fasttimeout)
+{
+ char ts[Maxservice];
+ char reply[Maxreply];
+ char x[Maxservice];
+ uchar ip[IPaddrlen];
+
+ if(strcmp(t->attr, "ip") != 0)
+ return nil;
+
+ if(serv == nil || ipserv(np, serv, ts, sizeof ts) == nil){
+ werrstr("can't translate service");
+ return nil;
+ }
+
+ if(rem != nil)
+ snprint(x, sizeof(x), "!%s", rem);
+ else
+ *x = 0;
+
+ if(*t->val == '*')
+ snprint(reply, sizeof(reply), "%s/%s/clone %s%s",
+ mntpt, np->net, ts, x);
+ else {
+ if(parseip(ip, t->val) == -1)
+ return nil;
+ if((np->ipvers & confipvers & (isv4(ip) ? V4 : V6)) == 0)
+ return nil;
+ snprint(reply, sizeof(reply), "%s/%s/clone %I!%s%s%s",
+ mntpt, np->net, ip, ts, x, fasttimeout? "!fasttimeout": "");
+ }
+
+ return estrdup(reply);
+}
+
+/*
+ * lookup a registry entry
+ */
+Ndbtuple*
+svclookup(Network *np, char *host, char *serv)
+{
+ USED(serv);
+
+ Ndbtuple *t, *st;
+
+ int fd, n;
+ char *args[3];
+ char line[Maxreply];
+ char buf[8192+1];
+
+ fd = open("/net/registry", ORDWR);
+ if(fd < 0){
+ werrstr("unable to open registry");
+ return nil;
+ }
+
+ seprint(line, line+Maxreply, "%s svc", host);
+
+ seek(fd, 0, 0);
+ write(fd, line, sizeof(line));
+
+ werrstr("can't translate address");
+ seek(fd, 0, 0);
+
+ t = st = nil;
+
+ /* We take the first match */
+ while((read(fd, buf, sizeof(buf)-1)) > 0){
+ n = getfields(buf, args, 3, 1, "!");
+ switch(n){
+ case 1:
+ np->net = estrdup("net");
+ t = ndbline(ndbnew("svc", args[1]));
+ break;
+ case 3:
+ st = ndbnew("serv", args[2]);
+ /* fall through */
+ case 2:
+ t = ndbline(ndbnew("svc", args[1]));
+ np->net = estrdup(args[0]);
+ break;
+ }
+
+ t->line = st;
+ close(fd);
+ return t;
+ }
+
+ close(fd);
+ return nil;
+}
+
+/*
+ * translate a service listing
+ */
+char*
+svctrans(Ndbtuple *t, Network *np, char *serv, char *rem, int)
+{
+ USED(serv);
+
+ Ndbtuple *st;
+
+ char reply[Maxreply];
+ char x[Maxservice];
+
+ if(strcmp(t->attr, "svc") != 0)
+ return nil;
+
+ if(rem != nil)
+ snprint(x, sizeof(x), "!%s", rem);
+ else
+ *x = 0;
+
+ if(t->line != nil){
+ st = t->line;
+ snprint(reply, sizeof(reply), "%s/%s/clone %s!%s%s", mntpt, np->net,
+ t->val, st->val, x);
+ } else
+ snprint(reply, sizeof(reply), "%s/%s/clone %s%s", mntpt, np->net,
+ t->val, x);
+ return estrdup(reply);
+}
+
+/*
+ * lookup a telephone number
+ */
+Ndbtuple*
+telcolookup(Network *np, char *host, char *serv)
+{
+ Ndbtuple *t;
+ Ndbs s;
+
+ USED(np, serv);
+
+ werrstr("can't translate address");
+ free(ndbgetvalue(db, &s, "sys", host, "telco", &t));
+ if(t == nil)
+ return ndbline(ndbnew("telco", host));
+
+ return ndbreorder(t, s.t);
+}
+
+/*
+ * translate a telephone address
+ */
+char*
+telcotrans(Ndbtuple *t, Network *np, char *serv, char *rem, int)
+{
+ char reply[Maxreply];
+ char x[Maxservice];
+
+ if(strcmp(t->attr, "telco") != 0)
+ return nil;
+
+ if(rem != nil)
+ snprint(x, sizeof(x), "!%s", rem);
+ else
+ *x = 0;
+ if(serv != nil)
+ snprint(reply, sizeof(reply), "%s/%s/clone %s!%s%s", mntpt, np->net,
+ t->val, serv, x);
+ else
+ snprint(reply, sizeof(reply), "%s/%s/clone %s%s", mntpt, np->net,
+ t->val, x);
+ return estrdup(reply);
+}
+
+/*
+ * create a slave process to handle a request to avoid one request blocking
+ * another. parent returns to job loop.
+ */
+void
+slave(char *host)
+{
+ if(*isslave)
+ return; /* we're already a slave process */
+ if(ainc(&active) >= Maxactive){
+ adec(&active);
+ return;
+ }
+ switch(rfork(RFPROC|RFNOTEG|RFMEM|RFNOWAIT)){
+ case -1:
+ adec(&active);
+ break;
+ case 0:
+ *isslave = 1;
+ if(debug)
+ syslog(0, logfile, "slave %d", getpid());
+ procsetname("%s", host);
+ break;
+ default:
+ longjmp(masterjmp, 1);
+ }
+
+}
+
+static int
+mountdns(void)
+{
+ static QLock mountlock;
+ static int mounted;
+ char buf[128], *p;
+ int fd;
+
+ if(mounted)
+ return 0;
+
+ qlock(&mountlock);
+ snprint(buf, sizeof(buf), "%s/dns", mntpt);
+ if(access(buf, AEXIST) == 0)
+ goto done;
+ if(strcmp(mntpt, "/net") == 0)
+ snprint(buf, sizeof(buf), "/srv/dns");
+ else {
+ snprint(buf, sizeof(buf), "/srv/dns%s", mntpt);
+ while((p = strchr(buf+8, '/')) != nil)
+ *p = '_';
+ }
+ if((fd = open(buf, ORDWR)) < 0){
+err:
+ qunlock(&mountlock);
+ return -1;
+ }
+ if(mount(fd, -1, mntpt, MAFTER, "") == -1){
+ close(fd);
+ goto err;
+ }
+done:
+ mounted = 1;
+ qunlock(&mountlock);
+ return 0;
+}
+
+static Ndbtuple*
+dnsip6lookup(char *mntpt, char *buf, Ndbtuple *t)
+{
+ Ndbtuple *t6, *tt;
+
+ t6 = dnsquery(mntpt, buf, "ipv6"); /* lookup AAAA dns RRs */
+ if (t6 == nil)
+ return t;
+
+ /* convert ipv6 attr to ip */
+ for (tt = t6; tt != nil; tt = tt->entry)
+ if (strcmp(tt->attr, "ipv6") == 0)
+ strcpy(tt->attr, "ip");
+
+ /* append t6 list to t list */
+ return ndbconcatenate(t, t6);
+}
+
+/*
+ * call the dns process and have it try to translate a name
+ */
+Ndbtuple*
+dnsiplookup(char *host, Ndbs *s, int ipvers)
+{
+ char buf[Maxreply];
+ Ndbtuple *t;
+
+ ipvers &= dnsipvers & lookipvers;
+ if(ipvers == 0){
+ werrstr("no ip address");
+ return nil;
+ }
+ qunlock(&dblock);
+ slave(host);
+ if(*isslave == 0){
+ qlock(&dblock);
+ werrstr("too much activity");
+ return nil;
+ }
+
+ if(mountdns() < 0){
+ qlock(&dblock);
+ return nil;
+ }
+
+ if(strcmp(ipattr(host), "ip") == 0)
+ t = dnsquery(mntpt, host, "ptr");
+ else {
+ t = nil;
+ if(ipvers & V4)
+ t = dnsquery(mntpt, host, "ip");
+ if(ipvers & V6)
+ t = dnsip6lookup(mntpt, host, t);
+ }
+ s->t = t;
+
+ if(t == nil){
+ rerrstr(buf, sizeof buf);
+ if(strstr(buf, "exist") != nil)
+ werrstr("can't translate address: %s", buf);
+ else if(strstr(buf, "dns failure") != nil)
+ werrstr("temporary problem: %s", buf);
+ }
+
+ qlock(&dblock);
+ return t;
+}
+
+int
+qmatch(Ndbtuple *t, char **attr, char **val, int n)
+{
+ int i, found;
+ Ndbtuple *nt;
+
+ for(i = 1; i < n; i++){
+ found = 0;
+ for(nt = t; nt != nil; nt = nt->entry)
+ if(strcmp(attr[i], nt->attr) == 0)
+ if(strcmp(val[i], "*") == 0
+ || strcmp(val[i], nt->val) == 0){
+ found = 1;
+ break;
+ }
+ if(found == 0)
+ break;
+ }
+ return i == n;
+}
+
+void
+qreply(Mfile *mf, Ndbtuple *t)
+{
+ Ndbtuple *nt;
+ String *s;
+
+ s = s_new();
+ for(nt = t; mf->nreply < Nreply && nt != nil; nt = nt->entry){
+ s_append(s, nt->attr);
+ s_append(s, "=");
+ s_append(s, nt->val);
+
+ if(nt->line != nt->entry){
+ mf->replylen[mf->nreply] = s_len(s);
+ mf->reply[mf->nreply++] = estrdup(s_to_c(s));
+ s_restart(s);
+ } else
+ s_append(s, " ");
+ }
+ s_free(s);
+}
+
+enum
+{
+ Maxattr= 32,
+};
+
+/*
+ * generic query lookup. The query is of one of the following
+ * forms:
+ *
+ * attr1=val1 attr2=val2 attr3=val3 ...
+ *
+ * returns the matching tuple
+ *
+ * ipinfo attr=val attr1 attr2 attr3 ...
+ *
+ * is like ipinfo and returns the attr{1-n}
+ * associated with the ip address.
+ */
+char*
+genquery(Mfile *mf, char *query)
+{
+ int i, n;
+ char *p;
+ char *attr[Maxattr];
+ char *val[Maxattr];
+ Ndbtuple *t;
+ Ndbs s;
+
+ n = getfields(query, attr, nelem(attr), 1, " ");
+ if(n == 0)
+ return "bad query";
+
+ if(strcmp(attr[0], "ipinfo") == 0)
+ return ipinfoquery(mf, attr, n);
+
+ /* parse pairs */
+ for(i = 0; i < n; i++){
+ p = strchr(attr[i], '=');
+ if(p == nil)
+ return "bad query";
+ *p++ = 0;
+ val[i] = p;
+ }
+
+ /* give dns a chance */
+ if((strcmp(attr[0], "dom") == 0 || strcmp(attr[0], "ip") == 0) && val[0]){
+ t = dnsiplookup(val[0], &s, lookipvers);
+ if(t != nil){
+ if(qmatch(t, attr, val, n)){
+ qreply(mf, t);
+ ndbfree(t);
+ return nil;
+ }
+ ndbfree(t);
+ }
+ }
+
+ /* first pair is always the key. It can't be a '*' */
+ t = ndbsearch(db, &s, attr[0], val[0]);
+
+ /* search is the and of all the pairs */
+ while(t != nil){
+ if(qmatch(t, attr, val, n)){
+ qreply(mf, t);
+ ndbfree(t);
+ return nil;
+ }
+
+ ndbfree(t);
+ t = ndbsnext(&s, attr[0], val[0]);
+ }
+
+ return "no match";
+}
+
+/*
+ * resolve an ip address
+ */
+static Ndbtuple*
+ipresolve(char *attr, char *host)
+{
+ Ndbtuple *t, *nt, **l;
+
+ t = iplookup(&network[Ntcp], host, "*");
+ for(l = &t; *l != nil; ){
+ nt = *l;
+ if(strcmp(nt->attr, "ip") != 0){
+ *l = nt->entry;
+ nt->entry = nil;
+ ndbfree(nt);
+ continue;
+ }
+ nstrcpy(nt->attr, attr, sizeof(nt->attr));
+ l = &nt->entry;
+ }
+ return t;
+}
+
+char*
+ipinfoquery(Mfile *mf, char **list, int n)
+{
+ int i, nresolve;
+ uchar resolve[Maxattr];
+ Ndbtuple *t, *nt, **l;
+ char *attr, *val;
+
+ /* skip 'ipinfo' */
+ list++; n--;
+
+ if(n < 1)
+ return "bad query";
+
+ /* get search attribute=value, or assume myip */
+ attr = *list;
+ if((val = strchr(attr, '=')) != nil){
+ *val++ = 0;
+ list++;
+ n--;
+ }else{
+ attr = nil;
+ val = nil;
+ }
+ if(n < 1)
+ return "bad query";
+
+
+ /*
+ * don't let ndbipinfo resolve the addresses, we're
+ * better at it.
+ */
+ nresolve = 0;
+ for(i = 0; i < n; i++)
+ if(*list[i] == '@'){ /* @attr=val ? */
+ list[i]++;
+ resolve[i] = 1; /* we'll resolve it */
+ nresolve++;
+ } else
+ resolve[i] = 0;
+
+ if(attr == nil)
+ t = myipinfo(db, list, n);
+ else
+ t = ndbipinfo(db, attr, val, list, n);
+
+ if(t == nil)
+ return "no match";
+
+ if(nresolve != 0){
+ for(l = &t; *l != nil;){
+ nt = *l;
+
+ /* already an address? */
+ if(strcmp(ipattr(nt->val), "ip") == 0){
+ l = &(*l)->entry;
+ continue;
+ }
+
+ /* user wants it resolved? */
+ for(i = 0; i < n; i++)
+ if(strcmp(list[i], nt->attr) == 0)
+ break;
+ if(i >= n || resolve[i] == 0){
+ l = &(*l)->entry;
+ continue;
+ }
+
+ /* resolve address and replace entry */
+ *l = ipresolve(nt->attr, nt->val);
+ while(*l != nil)
+ l = &(*l)->entry;
+ *l = nt->entry;
+
+ nt->entry = nil;
+ ndbfree(nt);
+ }
+
+ t = ndbdedup(t);
+ }
+
+ /* make it all one line */
+ t = ndbline(t);
+
+ qreply(mf, t);
+
+ return nil;
+}
+
+void*
+emalloc(int size)
+{
+ void *x;
+
+ x = malloc(size);
+ if(x == nil)
+ error("out of memory");
+ memset(x, 0, size);
+ return x;
+}
+
+char*
+estrdup(char *s)
+{
+ int size;
+ char *p;
+
+ size = strlen(s);
+ p = malloc(size+1);
+ if(p == nil)
+ error("out of memory");
+ memmove(p, s, size);
+ p[size] = 0;
+ return p;
+}
--- /dev/null
+++ b/csquery.c
@@ -1,0 +1,79 @@
+#include <u.h>
+#include <libc.h>
+#include <bio.h>
+
+char *server;
+char *status;
+int statusonly;
+
+void
+usage(void)
+{
+ fprint(2, "usage: ndb/csquery [/net/cs [addr...]]\n");
+ exits("usage");
+}
+
+void
+query(char *addr)
+{
+ char buf[8192+1];
+ int fd, n;
+
+ fd = open(server, ORDWR);
+ if(fd < 0)
+ sysfatal("cannot open %s: %r", server);
+ if(write(fd, addr, strlen(addr)) != strlen(addr)){
+ if(!statusonly)
+ fprint(2, "translating %s: %r\n", addr);
+ status = "errors";
+ close(fd);
+ return;
+ }
+ if(!statusonly){
+ seek(fd, 0, 0);
+ while((n = read(fd, buf, sizeof(buf)-1)) > 0){
+ buf[n++] = '\n';
+ write(1, buf, n);
+ }
+ write(1, "\n", 1);
+ }
+ close(fd);
+}
+
+void
+main(int argc, char **argv)
+{
+ char *p;
+ int i;
+ Biobuf in;
+
+ ARGBEGIN{
+ case 's':
+ statusonly = 1;
+ break;
+ default:
+ usage();
+ }ARGEND
+
+ if(argc > 0)
+ server = argv[0];
+ else
+ server = "/net/cs";
+
+ if(argc > 1){
+ for(i=1; i<argc; i++)
+ query(argv[i]);
+ exits(status);
+ }
+
+ Binit(&in, 0, OREAD);
+ for(;;){
+ fprint(2, "> ");
+ p = Brdline(&in, '\n');
+ if(p == 0)
+ break;
+ p[Blinelen(&in)-1] = 0;
+ query(p);
+ }
+ exits(nil);
+}
--- /dev/null
+++ b/dblookup.c
@@ -1,0 +1,1244 @@
+#include <u.h>
+#include <libc.h>
+#include <bio.h>
+#include <ndb.h>
+#include <ip.h>
+#include <ctype.h>
+#include "dns.h"
+
+enum {
+ Nibwidth = 4,
+ Nibmask = (1<<Nibwidth) - 1,
+ V6maxrevdomdepth = 128 / Nibwidth, /* bits / bits-per-nibble */
+
+ /*
+ * ttl for generated ptr records. it was zero, which might seem
+ * like a good idea, but some dns implementations seem to be
+ * confused by a zero ttl, and instead of using the data and then
+ * discarding the RR, they conclude that they don't have valid data.
+ */
+ Ptrttl = 2*Min,
+};
+
+static Ndb *db;
+static QLock dblock;
+
+static Ipifc *ipifcs;
+static QLock ipifclock;
+
+static RR* addrrr(Ndbtuple*, Ndbtuple*);
+static RR* cnamerr(Ndbtuple*, Ndbtuple*);
+static void createptrs(void);
+static RR* dblookup1(char*, int, int, int);
+static RR* doaxfr(Ndb*, char*);
+static Ndbtuple*look(Ndbtuple*, Ndbtuple*, char*);
+static RR* mxrr(Ndbtuple*, Ndbtuple*);
+static RR* nsrr(Ndbtuple*, Ndbtuple*);
+static RR* nullrr(Ndbtuple*, Ndbtuple*);
+static RR* ptrrr(Ndbtuple*, Ndbtuple*);
+static RR* soarr(Ndbtuple*, Ndbtuple*);
+static RR* srvrr(Ndbtuple*, Ndbtuple*);
+static RR* txtrr(Ndbtuple*, Ndbtuple*);
+
+static int implemented[Tall] =
+{
+ [Ta] 1,
+ [Taaaa] 1,
+ [Tcname] 1,
+ [Tmx] 1,
+ [Tns] 1,
+ [Tnull] 1,
+ [Tptr] 1,
+ [Tsoa] 1,
+ [Tsrv] 1,
+ [Ttxt] 1,
+};
+
+/* straddle server configuration */
+static Ndbtuple *indoms, *innmsrvs, *outnmsrvs;
+
+static void
+nstrcpy(char *to, char *from, int len)
+{
+ strncpy(to, from, len);
+ to[len-1] = 0;
+}
+
+int
+opendatabase(void)
+{
+ char netdbnm[256];
+ Ndb *xdb, *netdb;
+
+ if(db != nil)
+ return 0;
+
+ xdb = ndbopen(dbfile); /* /lib/ndb */
+
+ snprint(netdbnm, sizeof netdbnm, "%s/ndb", mntpt);
+ for(netdb = xdb; netdb; netdb = netdb->next)
+ if(strcmp(netdb->file, netdbnm) == 0){
+ db = xdb;
+ return 0;
+ }
+
+ netdb = ndbopen(netdbnm); /* /net/ndb */
+ if(netdb)
+ netdb->nohash = 1;
+
+ db = ndbcat(netdb, xdb); /* both */
+ return db!=nil ? 0: -1;
+}
+
+/*
+ * lookup an RR in the network database, look for matches
+ * against both the domain name and the wildcarded domain name.
+ *
+ * the lock makes sure only one process can be accessing the data
+ * base at a time. This is important since there's a lot of
+ * shared state there.
+ *
+ * e.g. for x.research.bell-labs.com, first look for a match against
+ * the x.research.bell-labs.com. If nothing matches,
+ * try *.research.bell-labs.com.
+ */
+RR*
+dblookup(char *name, int class, int type, int auth, int ttl)
+{
+ int err;
+ char buf[Domlen], *wild;
+ RR *rp, *tp;
+ DN *dp, *ndp;
+
+ /* so far only internet lookups are implemented */
+ if(class != Cin)
+ return 0;
+
+ err = Rname;
+ rp = nil;
+
+ if(type == Tall){
+ for (type = Ta; type < Tall; type++)
+ if(implemented[type])
+ rrcat(&rp, dblookup(name, class, type, auth, ttl));
+
+ return rp;
+ }
+
+ qlock(&dblock);
+ dp = idnlookup(name, class, 1);
+
+ if(opendatabase() < 0)
+ goto out;
+ if(dp->rr)
+ err = 0;
+
+ /* first try the given name */
+ if(cfg.cachedb)
+ rp = rrlookup(dp, type, NOneg);
+ else
+ rp = dblookup1(name, type, auth, ttl);
+ if(rp)
+ goto out;
+
+ /* walk the domain name trying the wildcard '*' at each position */
+ for(wild = strchr(name, '.'); wild; wild = strchr(wild+1, '.')){
+ snprint(buf, sizeof buf, "*%s", wild);
+ ndp = idnlookup(buf, class, 1);
+ if(ndp->rr)
+ err = 0;
+ if(cfg.cachedb)
+ rp = rrlookup(ndp, type, NOneg);
+ else
+ rp = dblookup1(buf, type, auth, ttl);
+ if(rp)
+ break;
+ }
+out:
+ /* add owner to uncached records */
+ if(rp)
+ for(tp = rp; tp; tp = tp->next)
+ tp->owner = dp;
+ else {
+ /*
+ * don't call it non-existent if it's not ours
+ * (unless we're a resolver).
+ */
+ if(err == Rname && (!inmyarea(dp->name) || cfg.resolver))
+ err = Rserver;
+ dp->respcode = err;
+ }
+
+ qunlock(&dblock);
+ return rp;
+}
+
+static ulong
+intval(Ndbtuple *entry, Ndbtuple *pair, char *attr, ulong def)
+{
+ Ndbtuple *t = look(entry, pair, attr);
+
+ return (t? strtoul(t->val, 0, 10): def);
+}
+
+static void
+mklowcase(char *cp)
+{
+ Rune r;
+
+ while(*cp != 0){
+ chartorune(&r, cp);
+ r = tolowerrune(r);
+ cp += runetochar(cp, &r);
+ }
+}
+
+/*
+ * lookup an RR in the network database
+ */
+static RR*
+dblookup1(char *name, int type, int auth, int ttl)
+{
+ Ndbtuple *t, *nt;
+ RR *rp, *list, **l;
+ Ndbs s;
+ char dname[Domlen];
+ char *attr;
+ DN *dp;
+ RR *(*f)(Ndbtuple*, Ndbtuple*);
+ int found, x;
+
+ dp = nil;
+ switch(type){
+ case Tptr:
+ attr = "ptr";
+ f = ptrrr;
+ break;
+ case Ta:
+ attr = "ip";
+ f = addrrr;
+ break;
+ case Taaaa:
+ attr = "ipv6";
+ f = addrrr;
+ break;
+ case Tnull:
+ attr = "nullrr";
+ f = nullrr;
+ break;
+ case Tns:
+ attr = "ns";
+ f = nsrr;
+ break;
+ case Tsoa:
+ attr = "soa";
+ f = soarr;
+ break;
+ case Tsrv:
+ attr = "srv";
+ f = srvrr;
+ break;
+ case Ttxt:
+ attr = "txt";
+ f = txtrr;
+ break;
+ case Tmx:
+ attr = "mx";
+ f = mxrr;
+ break;
+ case Tcname:
+ attr = "cname";
+ f = cnamerr;
+ break;
+ case Taxfr:
+ case Tixfr:
+ return doaxfr(db, name);
+ default:
+// dnslog("dblookup1(%s) bad type", name);
+ return nil;
+ }
+
+ /*
+ * find a matching entry in the database
+ */
+ t = nil;
+ nstrcpy(dname, name, sizeof dname);
+ for(x=0; x<4; x++){
+ switch(x){
+ case 1: /* try unicode */
+ if(idn2utf(name, dname, sizeof dname) < 0){
+ nstrcpy(dname, name, sizeof dname);
+ continue;
+ }
+ if(strcmp(name, dname) == 0)
+ continue;
+ break;
+ case 3: /* try ascii (lower case) */
+ if(utf2idn(name, dname, sizeof dname) < 0)
+ continue;
+ case 2:
+ mklowcase(dname);
+ if(strcmp(name, dname) == 0)
+ continue;
+ break;
+ }
+ for(nt = ndbsearch(db, &s, "dom", dname); nt != nil; nt = ndbsnext(&s, "dom", dname)) {
+ if(ndbfindattr(nt, s.t, attr) == nil) {
+ ndbfree(nt);
+ continue;
+ }
+ t = ndbconcatenate(t, ndbreorder(nt, s.t));
+ }
+ if(t == nil && strchr(dname, '.') == nil) {
+ for(nt = ndbsearch(db, &s, "sys", dname); nt != nil; nt = ndbsnext(&s, "sys", dname)) {
+ if(ndbfindattr(nt, s.t, attr) == nil) {
+ ndbfree(nt);
+ continue;
+ }
+ t = ndbconcatenate(t, ndbreorder(nt, s.t));
+ }
+ }
+ s.t = t;
+ if(t != nil)
+ break;
+ }
+
+ if(t == nil) {
+// dnslog("dblookup1(%s) name not found", name);
+ return nil;
+ }
+
+
+ /* search whole entry for default domain name */
+ for(nt = t; nt; nt = nt->entry)
+ if(strcmp(nt->attr, "dom") == 0){
+ nstrcpy(dname, nt->val, sizeof dname);
+ break;
+ }
+
+ /* ttl is maximum of soa minttl and entry's ttl ala rfc883 */
+ x = intval(t, s.t, "ttl", 0);
+ if(x > ttl)
+ ttl = x;
+
+ /* default ttl is one day */
+ if(ttl < 0)
+ ttl = DEFTTL;
+
+ /*
+ * The database has 2 levels of precedence; line and entry.
+ * Pairs on the same line bind tighter than pairs in the
+ * same entry, so we search the line first.
+ */
+ found = 0;
+ list = 0;
+ l = &list;
+ for(nt = s.t;; ){
+ if(found == 0 && strcmp(nt->attr, "dom") == 0){
+ nstrcpy(dname, nt->val, sizeof dname);
+ found = 1;
+ }
+ if(strcmp(attr, nt->attr) == 0 && (rp = (*f)(t, nt)) != nil){
+ rp->auth = auth;
+ rp->db = 1;
+ if(ttl)
+ rp->ttl = ttl;
+ if(dp == nil)
+ dp = idnlookup(dname, Cin, 1);
+ rp->owner = dp;
+ *l = rp;
+ l = &rp->next;
+ nt->ptr = 1;
+ }
+ nt = nt->line;
+ if(nt == s.t)
+ break;
+ }
+
+ /* search whole entry */
+ for(nt = t; nt; nt = nt->entry)
+ if(nt->ptr == 0 && strcmp(attr, nt->attr) == 0 && (rp = (*f)(t, nt)) != nil){
+ rp->auth = auth;
+ rp->db = 1;
+ if(ttl)
+ rp->ttl = ttl;
+ if(dp == nil)
+ dp = idnlookup(dname, Cin, 1);
+ rp->owner = dp;
+ *l = rp;
+ l = &rp->next;
+ }
+ ndbfree(t);
+
+ unique(list);
+
+// dnslog("dblookup1(%s) -> %#p", name, list);
+ return list;
+}
+
+/*
+ * make various types of resource records from a database entry
+ */
+static RR*
+addrrr(Ndbtuple*, Ndbtuple *pair)
+{
+ RR *rp;
+ uchar ip[IPaddrlen];
+
+ if(parseip(ip, pair->val) == -1)
+ return nil;
+ rp = rralloc(isv4(ip) ? Ta : Taaaa);
+ rp->ip = ipalookup(ip, Cin, 1);
+ return rp;
+}
+static RR*
+nullrr(Ndbtuple*, Ndbtuple *pair)
+{
+ RR *rp;
+
+ rp = rralloc(Tnull);
+ rp->null->data = (uchar*)estrdup(pair->val);
+ rp->null->dlen = strlen((char*)rp->null->data);
+ return rp;
+}
+/*
+ * txt rr strings are at most 255 bytes long. one
+ * can represent longer strings by multiple concatenated
+ * <= 255 byte ones.
+ */
+static RR*
+txtrr(Ndbtuple*, Ndbtuple *pair)
+{
+ RR *rp;
+ Txt *t, **l;
+ int i, len, sofar;
+
+ rp = rralloc(Ttxt);
+ l = &rp->txt;
+ rp->txt = nil;
+ len = strlen(pair->val);
+ sofar = 0;
+ while(len > sofar){
+ t = emalloc(sizeof(*t));
+ t->next = nil;
+
+ i = len-sofar;
+ if(i > 255)
+ i = 255;
+
+ t->p = emalloc(i+1);
+ memmove(t->p, pair->val+sofar, i);
+ t->p[i] = 0;
+ sofar += i;
+
+ *l = t;
+ l = &t->next;
+ }
+ return rp;
+}
+static RR*
+cnamerr(Ndbtuple*, Ndbtuple *pair)
+{
+ RR *rp;
+
+ rp = rralloc(Tcname);
+ rp->host = idnlookup(pair->val, Cin, 1);
+ return rp;
+}
+static RR*
+mxrr(Ndbtuple *entry, Ndbtuple *pair)
+{
+ RR *rp;
+
+ rp = rralloc(Tmx);
+ rp->host = idnlookup(pair->val, Cin, 1);
+ rp->pref = intval(entry, pair, "pref", 1);
+ return rp;
+}
+static RR*
+nsrr(Ndbtuple *entry, Ndbtuple *pair)
+{
+ RR *rp;
+ Ndbtuple *t;
+
+ rp = rralloc(Tns);
+ rp->host = idnlookup(pair->val, Cin, 1);
+ t = look(entry, pair, "soa");
+ if(t && t->val[0] == 0)
+ rp->local = 1;
+ return rp;
+}
+static RR*
+ptrrr(Ndbtuple*, Ndbtuple *pair)
+{
+ RR *rp;
+
+ rp = rralloc(Tns);
+ rp->ptr = dnlookup(pair->val, Cin, 1);
+ return rp;
+}
+static RR*
+soarr(Ndbtuple *entry, Ndbtuple *pair)
+{
+ RR *rp;
+ Ndbtuple *ns, *mb, *t;
+ char mailbox[Domlen];
+ Ndb *ndb;
+ char *p;
+
+ rp = rralloc(Tsoa);
+ rp->soa->serial = 1;
+ for(ndb = db; ndb; ndb = ndb->next)
+ if(ndb->mtime > rp->soa->serial)
+ rp->soa->serial = ndb->mtime;
+
+ rp->soa->retry = intval(entry, pair, "retry", Hour);
+ rp->soa->expire = intval(entry, pair, "expire", Day);
+ rp->soa->minttl = intval(entry, pair, "ttl", Day);
+ rp->soa->refresh = intval(entry, pair, "refresh", Day);
+ rp->soa->serial = intval(entry, pair, "serial", rp->soa->serial);
+
+ ns = look(entry, pair, "ns");
+ if(ns == nil)
+ ns = look(entry, pair, "dom");
+ rp->host = idnlookup(ns->val, Cin, 1);
+
+ /* accept all of:
+ * mbox=person
+ * mbox=person@machine.dom
+ * mbox=person.machine.dom
+ */
+ mb = look(entry, pair, "mbox");
+ if(mb == nil)
+ mb = look(entry, pair, "mb");
+ if(mb)
+ if(strchr(mb->val, '.')) {
+ p = strchr(mb->val, '@');
+ if(p != nil)
+ *p = '.';
+ rp->rmb = idnlookup(mb->val, Cin, 1);
+ } else {
+ snprint(mailbox, sizeof mailbox, "%s.%s",
+ mb->val, ns->val);
+ rp->rmb = idnlookup(mailbox, Cin, 1);
+ }
+ else {
+ snprint(mailbox, sizeof mailbox, "postmaster.%s", ns->val);
+ rp->rmb = idnlookup(mailbox, Cin, 1);
+ }
+
+ /*
+ * hang dns slaves off of the soa. this is
+ * for managing the area.
+ */
+ for(t = entry; t != nil; t = t->entry)
+ if(strcmp(t->attr, "dnsslave") == 0)
+ addserver(&rp->soa->slaves, t->val);
+
+ return rp;
+}
+
+static RR*
+srvrr(Ndbtuple *entry, Ndbtuple *pair)
+{
+ RR *rp;
+
+ rp = rralloc(Tsrv);
+ rp->host = idnlookup(pair->val, Cin, 1);
+ rp->srv->pri = intval(entry, pair, "pri", 0);
+ rp->srv->weight = intval(entry, pair, "weight", 0);
+ /* TODO: translate service name to port # */
+ rp->port = intval(entry, pair, "port", 0);
+ return rp;
+}
+
+/*
+ * Look for a pair with the given attribute. look first on the same line,
+ * then in the whole entry.
+ */
+static Ndbtuple*
+look(Ndbtuple *entry, Ndbtuple *line, char *attr)
+{
+ Ndbtuple *nt;
+
+ /* first look on same line (closer binding) */
+ for(nt = line;;){
+ if(strcmp(attr, nt->attr) == 0)
+ return nt;
+ nt = nt->line;
+ if(nt == line)
+ break;
+ }
+ /* search whole tuple */
+ for(nt = entry; nt; nt = nt->entry)
+ if(strcmp(attr, nt->attr) == 0)
+ return nt;
+ return 0;
+}
+
+/* these are answered specially by the tcp version */
+static RR*
+doaxfr(Ndb *db, char *name)
+{
+ USED(db, name);
+ return nil;
+}
+
+/*
+ * read the database into the cache
+ */
+static void
+dbpair2cache(DN *dp, Ndbtuple *entry, Ndbtuple *pair)
+{
+ RR *rp;
+ static ulong ord;
+
+ rp = 0;
+ if(strcmp(pair->attr, "ip") == 0 ||
+ strcmp(pair->attr, "ipv6") == 0) {
+ dp->ordinal = ord++;
+ rp = addrrr(entry, pair);
+ }
+ else if(strcmp(pair->attr, "ns") == 0)
+ rp = nsrr(entry, pair);
+ else if(strcmp(pair->attr, "soa") == 0) {
+ rp = soarr(entry, pair);
+ addarea(dp, rp, pair);
+ }
+ else if(strcmp(pair->attr, "mx") == 0)
+ rp = mxrr(entry, pair);
+ else if(strcmp(pair->attr, "srv") == 0)
+ rp = srvrr(entry, pair);
+ else if(strcmp(pair->attr, "cname") == 0)
+ rp = cnamerr(entry, pair);
+ else if(strcmp(pair->attr, "nullrr") == 0)
+ rp = nullrr(entry, pair);
+ else if(strcmp(pair->attr, "txtrr") == 0)
+ rp = txtrr(entry, pair);
+ if(rp == nil)
+ return;
+
+ rp->owner = dp;
+ rp->db = 1;
+ rp->ttl = intval(entry, pair, "ttl", rp->ttl);
+ rrattach(rp, Notauthoritative);
+ dnagenever(dp);
+}
+static void
+dbtuple2cache(Ndbtuple *t)
+{
+ Ndbtuple *et, *nt;
+ DN *dp;
+
+ for(et = t; et; et = et->entry)
+ if(strcmp(et->attr, "dom") == 0){
+ dp = idnlookup(et->val, Cin, 1);
+
+ /* first same line */
+ for(nt = et->line; nt != et; nt = nt->line){
+ dbpair2cache(dp, t, nt);
+ nt->ptr = 1;
+ }
+
+ /* then rest of entry */
+ for(nt = t; nt; nt = nt->entry){
+ if(nt->ptr == 0)
+ dbpair2cache(dp, t, nt);
+ nt->ptr = 0;
+ }
+ }
+}
+static void
+dbfile2cache(Ndb *db)
+{
+ Ndbtuple *t;
+
+ if(debug)
+ dnslog("rereading %s", db->file);
+ Bseek(&db->b, 0, 0);
+ while(t = ndbparse(db)){
+ dbtuple2cache(t);
+ ndbfree(t);
+ }
+}
+
+/* called with dblock held */
+static void
+loaddomsrvs(void)
+{
+ Ndbs s;
+
+ if (!cfg.inside || !cfg.straddle || !cfg.serve)
+ return;
+ if (indoms) {
+ ndbfree(indoms);
+ ndbfree(innmsrvs);
+ ndbfree(outnmsrvs);
+ indoms = innmsrvs = outnmsrvs = nil;
+ }
+ if (db == nil)
+ opendatabase();
+ free(ndbgetvalue(db, &s, "sys", "inside-dom", "dom", &indoms));
+ free(ndbgetvalue(db, &s, "sys", "inside-ns", "ip", &innmsrvs));
+ free(ndbgetvalue(db, &s, "sys", "outside-ns", "ip", &outnmsrvs));
+ dnslog("[%d] ndb changed: reloaded inside-dom, inside-ns, outside-ns",
+ getpid());
+}
+
+void
+db2cache(int doit)
+{
+ ulong youngest;
+ Ndb *ndb;
+ Dir *d;
+ static ulong lastcheck, lastyoungest;
+
+ /* no faster than once every 2 minutes */
+ if(now < lastcheck + 2*Min && !doit)
+ return;
+
+ refresh_areas(owned);
+
+ qlock(&dblock);
+ if(opendatabase() < 0){
+ qunlock(&dblock);
+ return;
+ }
+
+ qlock(&ipifclock);
+ ipifcs = readipifc(mntpt, ipifcs, -1);
+ qunlock(&ipifclock);
+
+ /*
+ * file may be changing as we are reading it, so loop till
+ * mod times are consistent.
+ *
+ * we don't use the times in the ndb records because they may
+ * change outside of refreshing our cached knowledge.
+ */
+ for(;;){
+ lastcheck = now;
+ youngest = 0;
+ for(ndb = db; ndb; ndb = ndb->next)
+ /* dirfstat avoids walking the mount table each time */
+ if((d = dirfstat(Bfildes(&ndb->b))) != nil ||
+ (d = dirstat(ndb->file)) != nil){
+ if(d->mtime > youngest)
+ youngest = d->mtime;
+ free(d);
+ }
+ if(!doit && youngest == lastyoungest)
+ break;
+
+ /* forget our area definition */
+ freearea(&owned);
+ freearea(&delegated);
+
+ /* reopen all the files (to get oldest for time stamp) */
+ for(ndb = db; ndb; ndb = ndb->next)
+ ndbreopen(ndb);
+
+ /* reload straddle-server configuration */
+ loaddomsrvs();
+
+ /* mark all db records as timed out */
+ dnagedb();
+
+ if(cfg.cachedb){
+ /* read in new entries */
+ for(ndb = db; ndb; ndb = ndb->next)
+ dbfile2cache(ndb);
+ }
+
+ /*
+ * mark as authoritative anything in our domain,
+ * delete timed out db records
+ */
+ dnauthdb();
+
+ /* remove old entries */
+ dnageall(1);
+
+ doit = 0;
+ lastyoungest = youngest;
+ createptrs();
+ }
+
+ qunlock(&dblock);
+}
+
+extern char mntpt[Maxpath]; /* net mountpoint */
+
+/*
+ * get all my xxx
+ * caller ndbfrees the result
+ */
+Ndbtuple*
+lookupinfo(char *attr)
+{
+ Ndbtuple *t, *nt;
+ char ip[64];
+ Ipifc *ifc;
+ Iplifc *lifc;
+
+ t = nil;
+ qlock(&dblock);
+ if(opendatabase() < 0){
+ qunlock(&dblock);
+ return nil;
+ }
+ qlock(&ipifclock);
+ if(ipifcs == nil)
+ ipifcs = readipifc(mntpt, ipifcs, -1);
+ for(ifc = ipifcs; ifc != nil; ifc = ifc->next){
+ for(lifc = ifc->lifc; lifc != nil; lifc = lifc->next){
+ snprint(ip, sizeof(ip), "%I", lifc->ip);
+ nt = ndbipinfo(db, "ip", ip, &attr, 1);
+ t = ndbconcatenate(t, nt);
+ }
+ }
+ qunlock(&ipifclock);
+ qunlock(&dblock);
+
+ return ndbdedup(t);
+}
+
+/*
+ * return non-zero if this is a bad delegation
+ */
+int
+baddelegation(RR *rp, RR *nsrp, uchar *addr)
+{
+ static int whined;
+ static Ndbtuple *t;
+ Ndbtuple *nt;
+
+ if(rp->type != Tns)
+ return 0;
+
+ if(t == nil)
+ t = lookupinfo("dom");
+ if(t != nil){
+ /* see if delegating to us what we don't own */
+ for(nt = t; nt != nil; nt = nt->entry)
+ if(rp->host && cistrcmp(rp->host->name, nt->val) == 0)
+ break;
+
+ if(nt != nil && !inmyarea(rp->owner->name)){
+ if (!whined) {
+ whined = 1;
+ dnslog("bad delegation %R from %I/%s; "
+ "no further logging of them",
+ rp, addr, nsrp->host->name);
+ }
+ return 1;
+ }
+ }
+ return 0;
+}
+
+int
+myip(uchar *ip)
+{
+ Ipifc *ifc;
+ Iplifc *lifc;
+
+ qlock(&ipifclock);
+ for(ifc = ipifcs; ifc != nil; ifc = ifc->next){
+ for(lifc = ifc->lifc; lifc != nil; lifc = lifc->next){
+ if(ipcmp(ip, lifc->ip) == 0){
+ qunlock(&ipifclock);
+ return 1;
+ }
+ }
+ }
+ qunlock(&ipifclock);
+
+ return 0;
+}
+
+static void
+addlocaldnsserver(DN *dp, int class, char *addr, int i)
+{
+ uchar ip[IPaddrlen];
+ DN *nsdp, *ipdp;
+ RR *rp, *tp;
+ int type, n;
+ char buf[32];
+
+ if(parseip(ip, addr) == -1 || ipcmp(ip, IPnoaddr) == 0){
+ dnslog("rejecting bad ip %s as local dns server", addr);
+ return;
+ }
+
+ /* reject our own ip addresses so we don't query ourselves via udp */
+ if(myip(ip)){
+ dnslog("rejecting my ip %I as local dns server", ip);
+ return;
+ }
+
+ /* A or AAAA record */
+ type = isv4(ip) ? Ta : Taaaa;
+ ipdp = ipalookup(ip, class, 1);
+
+ /* check duplicate ip */
+ for(n = 0; n < i; n++){
+ snprint(buf, sizeof buf, "local#dns#server%d", n);
+ nsdp = dnlookup(buf, class, 0);
+ if(nsdp == nil)
+ continue;
+ rp = rrlookup(nsdp, type, NOneg);
+ for(tp = rp; tp != nil; tp = tp->next){
+ if(tp->ip == ipdp){
+ dnslog("rejecting duplicate local dns server ip %I", ip);
+ rrfreelist(rp);
+ return;
+ }
+ }
+ rrfreelist(rp);
+ }
+
+ snprint(buf, sizeof buf, "local#dns#server%d", i);
+ nsdp = dnlookup(buf, class, 1);
+
+ /* ns record for name server, make up an impossible name */
+ rp = rralloc(Tns);
+ rp->host = nsdp;
+ rp->owner = dp; /* e.g., local#dns#servers */
+ rp->local = 1;
+ rp->db = 1;
+ rp->ttl = 10*Min;
+ rrattach(rp, Authoritative); /* will not attach rrs in my area */
+ dnagenever(dp);
+
+ rp = rralloc(type);
+ rp->ip = ipdp;
+ rp->owner = nsdp;
+ rp->local = 1;
+ rp->db = 1;
+ rp->ttl = 10*Min;
+ rrattach(rp, Authoritative); /* will not attach rrs in my area */
+ dnagenever(nsdp);
+
+ dnslog("added local dns server %s at %I", buf, ip);
+}
+
+/*
+ * return list of dns server addresses to use when
+ * acting just as a resolver.
+ */
+RR*
+dnsservers(int class)
+{
+ int i, n;
+ char *p;
+ char *args[16];
+ Ndbtuple *t, *nt;
+ RR *nsrp;
+ DN *dp;
+
+ dp = dnlookup("local#dns#servers", class, 1);
+ nsrp = rrlookup(dp, Tns, NOneg);
+ if(nsrp != nil)
+ return nsrp;
+
+ p = getenv("DNSSERVER"); /* list of ip addresses */
+ if(p != nil && (n = tokenize(p, args, nelem(args))) > 0){
+ for(i = 0; i < n; i++)
+ addlocaldnsserver(dp, class, args[i], i);
+ } else {
+ t = lookupinfo("@dns"); /* @dns=ip1 @dns=ip2 ... */
+ if(t == nil)
+ return nil;
+ i = 0;
+ for(nt = t; nt != nil; nt = nt->entry){
+ addlocaldnsserver(dp, class, nt->val, i);
+ i++;
+ }
+ ndbfree(t);
+ }
+ free(p);
+
+ return rrlookup(dp, Tns, NOneg);
+}
+
+static void
+addlocaldnsdomain(DN *dp, int class, char *domain)
+{
+ RR *rp;
+
+ /* ptr record */
+ rp = rralloc(Tptr);
+ rp->ptr = dnlookup(domain, class, 1);
+ rp->owner = dp;
+ rp->db = 1;
+ rp->ttl = 10*Min;
+ rrattach(rp, Authoritative);
+ dnagenever(dp);
+}
+
+/*
+ * return list of domains to use when resolving names without '.'s
+ */
+RR*
+domainlist(int class)
+{
+ Ndbtuple *t, *nt;
+ RR *rp;
+ DN *dp;
+
+ dp = dnlookup("local#dns#domains", class, 1);
+ rp = rrlookup(dp, Tptr, NOneg);
+ if(rp != nil)
+ return rp;
+
+ t = lookupinfo("dnsdomain");
+ if(t == nil)
+ return nil;
+ for(nt = t; nt != nil; nt = nt->entry)
+ addlocaldnsdomain(dp, class, nt->val);
+ ndbfree(t);
+
+ return rrlookup(dp, Tptr, NOneg);
+}
+
+char *v4ptrdom = ".in-addr.arpa";
+char *v6ptrdom = ".ip6.arpa"; /* ip6.int deprecated, rfc 3152 */
+
+char *attribs[] = {
+ "ipmask",
+ 0
+};
+
+/*
+ * create ptrs that are in our v4 areas
+ */
+static void
+createv4ptrs(void)
+{
+ int len, dlen, n;
+ char *dom;
+ char buf[Domlen], ipa[64];
+ char *f[40];
+ uchar net[IPaddrlen], mask[IPaddrlen];
+ Area *s;
+ Ndbtuple *t, *nt;
+
+ dlen = strlen(v4ptrdom);
+ for(s = owned; s; s = s->next){
+ dom = s->soarr->owner->name;
+ len = strlen(dom);
+ if((len <= dlen || cistrcmp(dom+len-dlen, v4ptrdom) != 0) &&
+ cistrcmp(dom, v4ptrdom+1) != 0)
+ continue;
+
+ /* get mask and net value */
+ nstrcpy(buf, dom, sizeof buf);
+ /* buf contains something like 178.204.in-addr.arpa (n==4) */
+ n = getfields(buf, f, nelem(f), 0, ".");
+ memset(mask, 0xff, IPaddrlen);
+ ipmove(net, v4prefix);
+ switch(n){
+ case 3: /* /8 */
+ net[IPv4off] = atoi(f[0]);
+ mask[IPv4off+1] = 0;
+ mask[IPv4off+2] = 0;
+ mask[IPv4off+3] = 0;
+ break;
+ case 4: /* /16 */
+ net[IPv4off] = atoi(f[1]);
+ net[IPv4off+1] = atoi(f[0]);
+ mask[IPv4off+2] = 0;
+ mask[IPv4off+3] = 0;
+ break;
+ case 5: /* /24 */
+ net[IPv4off] = atoi(f[2]);
+ net[IPv4off+1] = atoi(f[1]);
+ net[IPv4off+2] = atoi(f[0]);
+ mask[IPv4off+3] = 0;
+ break;
+ case 6: /* rfc2317: classless in-addr.arpa delegation */
+ net[IPv4off] = atoi(f[3]);
+ net[IPv4off+1] = atoi(f[2]);
+ net[IPv4off+2] = atoi(f[1]);
+ net[IPv4off+3] = atoi(f[0]);
+ snprint(ipa, sizeof(ipa), "%I", net);
+ t = ndbipinfo(db, "ip", ipa, attribs, 1);
+ if(t == nil) /* could be a reverse with no forward */
+ continue;
+ nt = look(t, t, "ipmask");
+ if(nt == nil || parseipmask(mask, nt->val, 1) == -1){
+ ndbfree(t);
+ continue;
+ }
+ ndbfree(t);
+ n = 5;
+ break;
+ default:
+ continue;
+ }
+
+ /*
+ * go through all domain entries looking for RR's
+ * in this network and create ptrs.
+ * +2 for ".in-addr.arpa".
+ */
+ dnptr(net, mask, dom, Ta, 4+2-n, Ptrttl);
+ }
+}
+
+/* convert bytes to nibbles, big-endian */
+void
+bytes2nibbles(uchar *nibbles, uchar *bytes, int nbytes)
+{
+ while (nbytes-- > 0) {
+ *nibbles++ = *bytes >> Nibwidth;
+ *nibbles++ = *bytes++ & Nibmask;
+ }
+}
+
+void
+nibbles2bytes(uchar *bytes, uchar *nibbles, int nnibs)
+{
+ for (; nnibs >= 2; nnibs -= 2) {
+ *bytes++ = nibbles[0] << Nibwidth | (nibbles[1]&Nibmask);
+ nibbles += 2;
+ }
+ if (nnibs > 0)
+ *bytes = nibbles[0] << Nibwidth;
+}
+
+/*
+ * create ptrs that are in our v6 areas. see rfc3596
+ */
+static void
+createv6ptrs(void)
+{
+ int len, dlen, i, n, pfxnibs;
+ char *dom;
+ char buf[Domlen];
+ char *f[40];
+ uchar net[IPaddrlen], mask[IPaddrlen];
+ uchar nibnet[IPaddrlen*2], nibmask[IPaddrlen*2];
+ Area *s;
+
+ dlen = strlen(v6ptrdom);
+ for(s = owned; s; s = s->next){
+ dom = s->soarr->owner->name;
+ len = strlen(dom);
+ if((len <= dlen || cistrcmp(dom+len-dlen, v6ptrdom) != 0) &&
+ cistrcmp(dom, v6ptrdom+1) != 0)
+ continue;
+
+ /* get mask and net value */
+ nstrcpy(buf, dom, sizeof buf);
+ /* buf contains something like 2.0.0.2.ip6.arpa (n==6) */
+ n = getfields(buf, f, nelem(f), 0, ".");
+ pfxnibs = n - 2; /* 2 for .ip6.arpa */
+ if (pfxnibs < 0 || pfxnibs > V6maxrevdomdepth)
+ continue;
+
+ memset(net, 0, IPaddrlen);
+ memset(mask, 0xff, IPaddrlen);
+ bytes2nibbles(nibnet, net, IPaddrlen);
+ bytes2nibbles(nibmask, mask, IPaddrlen);
+
+ /* copy prefix of f, in reverse order, to start of net. */
+ for (i = 0; i < pfxnibs; i++)
+ nibnet[i] = strtol(f[pfxnibs - 1 - i], nil, 16);
+ /* zero nibbles of mask after prefix in net */
+ memset(nibmask + pfxnibs, 0, V6maxrevdomdepth - pfxnibs);
+
+ nibbles2bytes(net, nibnet, 2*IPaddrlen);
+ nibbles2bytes(mask, nibmask, 2*IPaddrlen);
+
+ /*
+ * go through all domain entries looking for RR's
+ * in this network and create ptrs.
+ */
+ dnptr(net, mask, dom, Taaaa, V6maxrevdomdepth - pfxnibs, Ptrttl);
+ }
+}
+
+/*
+ * create ptrs that are in our areas
+ */
+static void
+createptrs(void)
+{
+ createv4ptrs();
+ createv6ptrs();
+}
+
+/*
+ * is this domain (or DOMAIN or Domain or dOMAIN)
+ * internal to our organisation (behind our firewall)?
+ * only inside straddling servers care, everybody else gets told `yes',
+ * so they'll use mntpt for their queries.
+ */
+int
+insideaddr(char *dom)
+{
+ int domlen, vallen, rv;
+ Ndbtuple *t;
+
+ if (!cfg.inside || !cfg.straddle || !cfg.serve)
+ return 1;
+ if (dom[0] == '\0' || strcmp(dom, ".") == 0) /* dns root? */
+ return 1; /* hack for initialisation */
+
+ qlock(&dblock);
+ if (indoms == nil)
+ loaddomsrvs();
+ if (indoms == nil) {
+ qunlock(&dblock);
+ return 1; /* no "inside-dom" sys, try inside nameservers */
+ }
+
+ rv = 0;
+ domlen = strlen(dom);
+ for (t = indoms; t != nil; t = t->entry) {
+ if (strcmp(t->attr, "dom") != 0)
+ continue;
+ vallen = strlen(t->val);
+ if (cistrcmp(dom, t->val) == 0 ||
+ domlen > vallen &&
+ cistrcmp(dom + domlen - vallen, t->val) == 0 &&
+ dom[domlen - vallen - 1] == '.') {
+ rv = 1;
+ break;
+ }
+ }
+ qunlock(&dblock);
+ return rv;
+}
+
+int
+insidens(uchar *ip)
+{
+ uchar ipa[IPaddrlen];
+ Ndbtuple *t;
+
+ for (t = innmsrvs; t != nil; t = t->entry)
+ if (strcmp(t->attr, "ip") == 0) {
+ if (parseip(ipa, t->val) != -1 && ipcmp(ipa, ip) == 0)
+ return 1;
+ }
+ return 0;
+}
+
+int
+outsidensip(int n, uchar *ip)
+{
+ int i;
+ Ndbtuple *t;
+
+ i = 0;
+ for (t = outnmsrvs; t != nil; t = t->entry)
+ if (strcmp(t->attr, "ip") == 0 && i++ == n) {
+ if (parseip(ip, t->val) == -1)
+ return -1;
+ return 0;
+ }
+ return -1;
+}
--- /dev/null
+++ b/dn.c
@@ -1,0 +1,2029 @@
+#include <u.h>
+#include <libc.h>
+#include <ip.h>
+#include <ctype.h>
+#include "dns.h"
+
+/*
+ * this comment used to say `our target is 4000 names cached, this should
+ * be larger on large servers'. dns at Bell Labs starts off with
+ * about 1780 names.
+ *
+ * aging seems to corrupt the cache, so raise the trigger from 4000 until we
+ * figure it out.
+ */
+enum {
+ /* these settings will trigger frequent aging */
+ Deftarget = 4000,
+ Minage = 5*Min,
+ Defagefreq = 15*Min, /* age names this often (seconds) */
+};
+
+/*
+ * Hash table for domain names. The hash is based only on the
+ * first element of the domain name.
+ */
+DN *ht[HTLEN];
+
+static struct {
+ Lock;
+ ulong names; /* names allocated */
+ ulong oldest; /* longest we'll leave a name around */
+ int active;
+ int mutex;
+ ushort id; /* same size as in packet */
+} dnvars;
+
+/* names of RR types */
+char *rrtname[] =
+{
+[Ta] "ip",
+[Tns] "ns",
+[Tmd] "md",
+[Tmf] "mf",
+[Tcname] "cname",
+[Tsoa] "soa",
+[Tmb] "mb",
+[Tmg] "mg",
+[Tmr] "mr",
+[Tnull] "null",
+[Twks] "wks",
+[Tptr] "ptr",
+[Thinfo] "hinfo",
+[Tminfo] "minfo",
+[Tmx] "mx",
+[Ttxt] "txt",
+[Trp] "rp",
+[Tafsdb] "afsdb",
+[Tx25] "x.25",
+[Tisdn] "isdn",
+[Trt] "rt",
+[Tnsap] "nsap",
+[Tnsapptr] "nsap-ptr",
+[Tsig] "sig",
+[Tkey] "key",
+[Tpx] "px",
+[Tgpos] "gpos",
+[Taaaa] "ipv6",
+[Tloc] "loc",
+[Tnxt] "nxt",
+[Teid] "eid",
+[Tnimloc] "nimrod",
+[Tsrv] "srv",
+[Tatma] "atma",
+[Tnaptr] "naptr",
+[Tkx] "kx",
+[Tcert] "cert",
+[Ta6] "a6",
+[Tdname] "dname",
+[Tsink] "sink",
+[Topt] "opt",
+[Tapl] "apl",
+[Tds] "ds",
+[Tsshfp] "sshfp",
+[Tipseckey] "ipseckey",
+[Trrsig] "rrsig",
+[Tnsec] "nsec",
+[Tdnskey] "dnskey",
+[Tspf] "spf",
+[Tuinfo] "uinfo",
+[Tuid] "uid",
+[Tgid] "gid",
+[Tunspec] "unspec",
+[Ttkey] "tkey",
+[Ttsig] "tsig",
+[Tixfr] "ixfr",
+[Taxfr] "axfr",
+[Tmailb] "mailb",
+[Tmaila] "maila",
+[Tall] "all",
+ 0,
+};
+
+/* names of response codes */
+char *rname[Rmask+1] =
+{
+[Rok] "ok",
+[Rformat] "format error",
+[Rserver] "server failure",
+[Rname] "bad name",
+[Runimplimented] "unimplemented",
+[Rrefused] "we don't like you",
+[Ryxdomain] "name should not exist",
+[Ryxrrset] "rr set should not exist",
+[Rnxrrset] "rr set should exist",
+[Rnotauth] "not authorative",
+[Rnotzone] "not in zone",
+[Rbadvers] "bad opt version",
+/* [Rbadsig] "bad signature", */
+[Rbadkey] "bad key",
+[Rbadtime] "bad signature time",
+[Rbadmode] "bad mode",
+[Rbadname] "duplicate key name",
+[Rbadalg] "bad algorithm",
+};
+unsigned nrname = nelem(rname);
+
+/* names of op codes */
+char *opname[] =
+{
+[Oquery] "query",
+[Oinverse] "inverse query (retired)",
+[Ostatus] "status",
+[Oupdate] "update",
+};
+
+ulong target = Deftarget;
+Lock dnlock;
+
+static ulong agefreq = Defagefreq;
+
+static int rrequiv(RR *r1, RR *r2);
+static int sencodefmt(Fmt*);
+
+static void
+ding(void*, char *msg)
+{
+ if(strstr(msg, "alarm") != nil) {
+ stats.alarms++;
+ noted(NCONT); /* resume with system call error */
+ } else
+ noted(NDFLT); /* die */
+}
+
+void
+dninit(void)
+{
+ fmtinstall('E', eipfmt);
+ fmtinstall('I', eipfmt);
+ fmtinstall('V', eipfmt);
+ fmtinstall('R', rrfmt);
+ fmtinstall('Q', rravfmt);
+ fmtinstall('H', sencodefmt);
+
+ dnvars.oldest = maxage;
+ dnvars.names = 0;
+ dnvars.id = truerand(); /* don't start with same id every time */
+
+ notify(ding);
+}
+
+/*
+ * hash for a domain name
+ */
+static ulong
+dnhash(char *name)
+{
+ ulong hash;
+ uchar *val = (uchar*)name;
+
+ for(hash = 0; *val; val++)
+ hash = hash*13 + tolower(*val)-'a';
+ return hash % HTLEN;
+}
+
+/*
+ * lookup a symbol. if enter is not zero and the name is
+ * not found, create it.
+ */
+DN*
+dnlookup(char *name, int class, int enter)
+{
+ DN **l;
+ DN *dp;
+
+ l = &ht[dnhash(name)];
+ lock(&dnlock);
+ for(dp = *l; dp; dp = dp->next) {
+ assert(dp->magic == DNmagic);
+ if(dp->class == class && cistrcmp(dp->name, name) == 0){
+ dp->referenced = now;
+ unlock(&dnlock);
+ return dp;
+ }
+ l = &dp->next;
+ }
+
+ if(!enter){
+ unlock(&dnlock);
+ return 0;
+ }
+ dnvars.names++;
+ dp = emalloc(sizeof(*dp));
+ dp->magic = DNmagic;
+ dp->name = estrdup(name);
+ dp->class = class;
+ dp->rr = nil;
+ dp->referenced = now;
+ /* add new DN to tail of the hash list. *l points to last next ptr. */
+ dp->next = nil;
+ *l = dp;
+ unlock(&dnlock);
+
+ return dp;
+}
+
+DN*
+idnlookup(char *name, int class, int enter)
+{
+ char dom[Domlen];
+
+ if(utf2idn(name, dom, sizeof dom) >= 0)
+ name = dom;
+ return dnlookup(name, class, enter);
+}
+
+DN*
+ipalookup(uchar *ip, int class, int enter)
+{
+ char addr[64];
+
+ snprint(addr, sizeof(addr), "%I", ip);
+ return dnlookup(addr, class, enter);
+}
+
+static int
+rrsame(RR *rr1, RR *rr2)
+{
+ return rr1 == rr2 ||
+ rr1 != nil && rr2 != nil &&
+ rr1->db == rr2->db &&
+ rr1->auth == rr2->auth &&
+ rrequiv(rr1, rr2);
+}
+
+static int
+rronlist(RR *rp, RR *lp)
+{
+ for(; lp; lp = lp->next)
+ if (rrsame(lp, rp))
+ return 1;
+ return 0;
+}
+
+/*
+ * dump the stats
+ */
+void
+dnstats(char *file)
+{
+ int i, fd;
+
+ fd = create(file, OWRITE, 0666);
+ if(fd < 0)
+ return;
+
+ qlock(&stats);
+ fprint(fd, "# system %s\n", sysname());
+ fprint(fd, "# slave procs high-water mark\t%lud\n", stats.slavehiwat);
+ fprint(fd, "# queries received by 9p\t%lud\n", stats.qrecvd9p);
+ fprint(fd, "# queries received by udp\t%lud\n", stats.qrecvdudp);
+ fprint(fd, "# queries answered from memory\t%lud\n", stats.answinmem);
+ fprint(fd, "# queries sent by udp\t%lud\n", stats.qsent);
+ for (i = 0; i < nelem(stats.under10ths); i++)
+ if (stats.under10ths[i] || i == nelem(stats.under10ths) - 1)
+ fprint(fd, "# responses arriving within %.1f s.\t%lud\n",
+ (double)(i+1)/10, stats.under10ths[i]);
+ fprint(fd, "\n# queries sent & timed-out\t%lud\n", stats.tmout);
+ fprint(fd, "# cname queries timed-out\t%lud\n", stats.tmoutcname);
+ fprint(fd, "# ipv6 queries timed-out\t%lud\n", stats.tmoutv6);
+ fprint(fd, "\n# negative answers received\t%lud\n", stats.negans);
+ fprint(fd, "# negative answers w Rserver set\t%lud\n", stats.negserver);
+ fprint(fd, "# negative answers w bad delegation\t%lud\n",
+ stats.negbaddeleg);
+ fprint(fd, "# negative answers w bad delegation & no answers\t%lud\n",
+ stats.negbdnoans);
+ fprint(fd, "# negative answers w no Rname set\t%lud\n", stats.negnorname);
+ fprint(fd, "# negative answers cached\t%lud\n", stats.negcached);
+ qunlock(&stats);
+
+ lock(&dnlock);
+ fprint(fd, "\n# domain names %lud target %lud\n", dnvars.names, target);
+ unlock(&dnlock);
+ close(fd);
+}
+
+/*
+ * dump the cache
+ */
+void
+dndump(char *file)
+{
+ int i, fd;
+ DN *dp;
+ RR *rp;
+
+ fd = create(file, OWRITE, 0666);
+ if(fd < 0)
+ return;
+
+ lock(&dnlock);
+ for(i = 0; i < HTLEN; i++)
+ for(dp = ht[i]; dp; dp = dp->next){
+ fprint(fd, "%s\n", dp->name);
+ for(rp = dp->rr; rp; rp = rp->next) {
+ fprint(fd, "\t%R %c%c %ld/%lud\n",
+ rp, rp->auth? 'A': 'U',
+ rp->db? 'D': 'N', (long)(rp->expire - now), rp->ttl);
+ if (rronlist(rp, rp->next))
+ fprint(fd, "*** duplicate:\n");
+ }
+ }
+ unlock(&dnlock);
+ close(fd);
+}
+
+/*
+ * purge all records
+ */
+void
+dnpurge(void)
+{
+ DN *dp;
+ RR *rp, *srp;
+ int i;
+
+ lock(&dnlock);
+
+ for(i = 0; i < HTLEN; i++)
+ for(dp = ht[i]; dp; dp = dp->next){
+ srp = rp = dp->rr;
+ dp->rr = nil;
+ for(; rp != nil; rp = rp->next)
+ rp->cached = 0;
+ rrfreelist(srp);
+ }
+
+ unlock(&dnlock);
+}
+
+/*
+ * delete head of *l and free the old head.
+ * call with dnlock held.
+ */
+static void
+rrdelhead(RR **l)
+{
+ RR *rp;
+
+ if (canlock(&dnlock))
+ abort(); /* rrdelhead called with dnlock not held */
+ rp = *l;
+ if(rp == nil)
+ return;
+ *l = rp->next; /* unlink head */
+ rp->cached = 0; /* avoid blowing an assertion in rrfree */
+ rrfree(rp);
+}
+
+/*
+ * check the age of resource records, free any that have timed out.
+ * call with dnlock held.
+ */
+void
+dnage(DN *dp)
+{
+ RR **l, *rp;
+ ulong diff;
+
+ if (canlock(&dnlock))
+ abort(); /* dnage called with dnlock not held */
+ diff = now - dp->referenced;
+ if(diff < Reserved || dp->mark != 0)
+ return;
+
+ l = &dp->rr;
+ while ((rp = *l) != nil){
+ assert(rp->magic == RRmagic && rp->cached);
+ if(!rp->db && ((long)(rp->expire - now) <= 0 || diff > dnvars.oldest))
+ rrdelhead(l); /* rp == *l before; *l == rp->next after */
+ else
+ l = &rp->next;
+ }
+}
+
+#define MARK(dp) { if (dp) (dp)->mark |= 2; }
+
+/* mark a domain name and those in its RRs as never to be aged */
+void
+dnagenever(DN *dp)
+{
+ RR *rp;
+
+ lock(&dnlock);
+
+ /* mark all referenced domain names */
+ MARK(dp);
+ for(rp = dp->rr; rp; rp = rp->next){
+ MARK(rp->owner);
+ if(rp->negative){
+ MARK(rp->negsoaowner);
+ continue;
+ }
+ switch(rp->type){
+ case Thinfo:
+ MARK(rp->cpu);
+ MARK(rp->os);
+ break;
+ case Ttxt:
+ break;
+ case Tcname:
+ case Tmb:
+ case Tmd:
+ case Tmf:
+ case Tns:
+ case Tmx:
+ case Tsrv:
+ MARK(rp->host);
+ break;
+ case Tmg:
+ case Tmr:
+ MARK(rp->mb);
+ break;
+ case Tminfo:
+ MARK(rp->rmb);
+ MARK(rp->mb);
+ break;
+ case Trp:
+ MARK(rp->rmb);
+ MARK(rp->rp);
+ break;
+ case Ta:
+ case Taaaa:
+ MARK(rp->ip);
+ break;
+ case Tptr:
+ MARK(rp->ptr);
+ break;
+ case Tsoa:
+ MARK(rp->host);
+ MARK(rp->rmb);
+ break;
+ case Tsig:
+ MARK(rp->sig->signer);
+ break;
+ }
+ }
+
+ unlock(&dnlock);
+}
+
+#define REF(dp) { if (dp) (dp)->mark |= 1; }
+
+/*
+ * periodicly sweep for old records and remove unreferenced domain names
+ *
+ * only called when all other threads are locked out
+ */
+void
+dnageall(int doit)
+{
+ DN *dp, **l;
+ int i;
+ RR *rp;
+ static ulong nextage;
+
+ if(dnvars.names < target || ((long)(nextage - now) > 0 && !doit)){
+ dnvars.oldest = maxage;
+ return;
+ }
+
+ if(dnvars.names >= target) {
+ dnslog("more names (%lud) than target (%lud)", dnvars.names,
+ target);
+ dnvars.oldest /= 2;
+ if (dnvars.oldest < Minage)
+ dnvars.oldest = Minage; /* don't be silly */
+ }
+ if (agefreq > dnvars.oldest / 2)
+ nextage = now + dnvars.oldest / 2;
+ else
+ nextage = now + (ulong)agefreq;
+
+ lock(&dnlock);
+
+ /* time out all old entries (and set refs to 0) */
+ for(i = 0; i < HTLEN; i++)
+ for(dp = ht[i]; dp; dp = dp->next){
+ dp->mark &= ~1;
+ dnage(dp);
+ }
+
+ /* mark all referenced domain names */
+ for(i = 0; i < HTLEN; i++)
+ for(dp = ht[i]; dp; dp = dp->next)
+ for(rp = dp->rr; rp; rp = rp->next){
+ REF(rp->owner);
+ if(rp->negative){
+ REF(rp->negsoaowner);
+ continue;
+ }
+ switch(rp->type){
+ case Thinfo:
+ REF(rp->cpu);
+ REF(rp->os);
+ break;
+ case Ttxt:
+ break;
+ case Tcname:
+ case Tmb:
+ case Tmd:
+ case Tmf:
+ case Tns:
+ case Tmx:
+ case Tsrv:
+ REF(rp->host);
+ break;
+ case Tmg:
+ case Tmr:
+ REF(rp->mb);
+ break;
+ case Tminfo:
+ REF(rp->rmb);
+ REF(rp->mb);
+ break;
+ case Trp:
+ REF(rp->rmb);
+ REF(rp->rp);
+ break;
+ case Ta:
+ case Taaaa:
+ REF(rp->ip);
+ break;
+ case Tptr:
+ REF(rp->ptr);
+ break;
+ case Tsoa:
+ REF(rp->host);
+ REF(rp->rmb);
+ break;
+ case Tsig:
+ REF(rp->sig->signer);
+ break;
+ }
+ }
+
+ /* sweep and remove unreferenced domain names */
+ for(i = 0; i < HTLEN; i++){
+ l = &ht[i];
+ for(dp = *l; dp; dp = *l){
+ if(dp->rr == nil && dp->mark == 0){
+ assert(dp->magic == DNmagic);
+ *l = dp->next;
+
+ free(dp->name);
+ memset(dp, 0, sizeof *dp); /* cause trouble */
+ dp->magic = ~DNmagic;
+ free(dp);
+
+ dnvars.names--;
+ continue;
+ }
+ l = &dp->next;
+ }
+ }
+
+ unlock(&dnlock);
+}
+
+/*
+ * timeout all database records (used when rereading db)
+ */
+void
+dnagedb(void)
+{
+ DN *dp;
+ int i;
+ RR *rp;
+
+ lock(&dnlock);
+
+ /* time out all database entries */
+ for(i = 0; i < HTLEN; i++)
+ for(dp = ht[i]; dp; dp = dp->next) {
+ dp->mark = 0;
+ for(rp = dp->rr; rp; rp = rp->next)
+ if(rp->db)
+ rp->expire = 0;
+ }
+
+ unlock(&dnlock);
+}
+
+/*
+ * mark all local db records about my area as authoritative,
+ * delete timed out ones
+ */
+void
+dnauthdb(void)
+{
+ int i;
+ ulong minttl;
+ Area *area;
+ DN *dp;
+ RR *rp, **l;
+
+ lock(&dnlock);
+
+ /* time out all database entries */
+ for(i = 0; i < HTLEN; i++)
+ for(dp = ht[i]; dp; dp = dp->next){
+ area = inmyarea(dp->name);
+ l = &dp->rr;
+ for(rp = *l; rp; rp = *l){
+ if(rp->db){
+ if(rp->expire == 0){
+ rrdelhead(l);
+ continue;
+ }
+ if(area){
+ minttl = area->soarr->soa->minttl;
+ if(rp->ttl < minttl)
+ rp->ttl = minttl;
+ rp->auth = 1;
+ }
+ }
+ l = &rp->next;
+ }
+ }
+
+ unlock(&dnlock);
+}
+
+/*
+ * keep track of other processes to know if we can
+ * garbage collect. block while garbage collecting.
+ */
+int
+getactivity(Request *req, int recursive)
+{
+ int rv;
+
+ if(traceactivity)
+ dnslog("get: %d active by pid %d from %p",
+ dnvars.active, getpid(), getcallerpc(&req));
+ lock(&dnvars);
+ /*
+ * can't block here if we're already holding one
+ * of the dnvars.active (recursive). will deadlock.
+ */
+ while(!recursive && dnvars.mutex){
+ unlock(&dnvars);
+ sleep(100); /* tune; was 200 */
+ lock(&dnvars);
+ }
+ rv = ++dnvars.active;
+ now = time(nil);
+ nowns = nsec();
+ req->id = ++dnvars.id;
+ req->aux = nil;
+ unlock(&dnvars);
+
+ return rv;
+}
+void
+putactivity(int recursive)
+{
+ if(traceactivity)
+ dnslog("put: %d active by pid %d",
+ dnvars.active, getpid());
+ lock(&dnvars);
+ dnvars.active--;
+ assert(dnvars.active >= 0); /* "dnvars.active %d", dnvars.active */
+
+ /*
+ * clean out old entries and check for new db periodicly
+ * can't block here if being called to let go a "recursive" lock
+ * or we'll deadlock waiting for ourselves to give up the dnvars.active.
+ */
+ if (recursive || dnvars.mutex ||
+ (needrefresh == 0 && dnvars.active > 0)){
+ unlock(&dnvars);
+ return;
+ }
+
+ /* wait till we're alone */
+ dnvars.mutex = 1;
+ while(dnvars.active > 0){
+ unlock(&dnvars);
+ sleep(100); /* tune; was 100 */
+ lock(&dnvars);
+ }
+ unlock(&dnvars);
+
+ db2cache(needrefresh);
+
+ dnageall(0);
+
+ /* let others back in */
+ needrefresh = 0;
+ dnvars.mutex = 0;
+}
+
+int
+rrlistlen(RR *rp)
+{
+ int n;
+
+ n = 0;
+ for(; rp; rp = rp->next)
+ ++n;
+ return n;
+}
+
+/*
+ * Attach a single resource record to a domain name (new->owner).
+ * - Avoid duplicates with already present RR's
+ * - Chain all RR's of the same type adjacent to one another
+ * - chain authoritative RR's ahead of non-authoritative ones
+ * - remove any expired RR's
+ * If new is a stale duplicate, rrfree it.
+ * Must be called with dnlock held.
+ */
+static void
+rrattach1(RR *new, int auth)
+{
+ RR **l;
+ RR *rp;
+ DN *dp;
+ ulong ttl;
+
+ assert(new->magic == RRmagic && !new->cached);
+
+ dp = new->owner;
+ assert(dp != nil && dp->magic == DNmagic);
+ new->auth |= auth;
+ new->next = 0;
+
+ /*
+ * try not to let responses expire before we
+ * can use them to complete this query, by extending
+ * past (or nearly past) expiration time.
+ */
+ if(new->db)
+ ttl = Year;
+ else
+ ttl = new->ttl;
+ if(ttl <= Min)
+ ttl = 10*Min;
+ new->expire = now + ttl;
+
+ /*
+ * find first rr of the right type
+ */
+ l = &dp->rr;
+ for(rp = *l; rp; rp = *l){
+ assert(rp->magic == RRmagic && rp->cached);
+ if(rp->type == new->type)
+ break;
+ l = &rp->next;
+ }
+
+ /*
+ * negative entries replace positive entries
+ * positive entries replace negative entries
+ * newer entries replace older entries with the same fields
+ *
+ * look farther ahead than just the next entry when looking
+ * for duplicates; RRs of a given type can have different rdata
+ * fields (e.g. multiple NS servers).
+ */
+ while ((rp = *l) != nil){
+ assert(rp->magic == RRmagic && rp->cached);
+ if(rp->type != new->type)
+ break;
+
+ if(rp->db == new->db && rp->auth == new->auth){
+ /* negative drives out positive and vice versa */
+ if(rp->negative != new->negative) {
+ /* rp == *l before; *l == rp->next after */
+ rrdelhead(l);
+ continue;
+ }
+ /* all things equal, pick the newer one */
+ else if(rrequiv(rp, new)){
+ /* old drives out new */
+ if((long)(rp->expire - new->expire) > 0) {
+ rrfree(new);
+ return;
+ }
+ /* rp == *l before; *l == rp->next after */
+ rrdelhead(l);
+ continue;
+ }
+ /*
+ * Hack for pointer records. This makes sure
+ * the ordering in the list reflects the ordering
+ * received or read from the database
+ */
+ else if(rp->type == Tptr &&
+ !rp->negative && !new->negative &&
+ rp->ptr->ordinal > new->ptr->ordinal)
+ break;
+ }
+ l = &rp->next;
+ }
+
+ if (rronlist(new, rp)) {
+ /* should not happen; duplicates were processed above */
+ dnslog("adding duplicate %R to list of %R; aborting", new, rp);
+ abort();
+ }
+ /*
+ * add to chain
+ */
+ new->cached = 1;
+ new->next = rp;
+ *l = new;
+}
+
+/*
+ * Attach a list of resource records to a domain name.
+ * May rrfree any stale duplicate RRs; dismembers the list.
+ * Upon return, every RR in the list will have been rrfree-d
+ * or attached to its domain name.
+ * See rrattach1 for properties preserved.
+ */
+void
+rrattach(RR *rp, int auth)
+{
+ RR *next;
+ DN *dp;
+
+ lock(&dnlock);
+ for(; rp; rp = next){
+ next = rp->next;
+ rp->next = nil;
+ dp = rp->owner;
+ /* avoid any outside spoofing */
+ if(cfg.cachedb && !rp->db && inmyarea(dp->name))
+ rrfree(rp);
+ else
+ rrattach1(rp, auth);
+ }
+ unlock(&dnlock);
+}
+
+RR**
+rrcopy(RR *rp, RR **last)
+{
+ RR *nrp;
+ SOA *soa;
+ Srv *srv;
+ Key *key;
+ Cert *cert;
+ Sig *sig;
+ Null *null;
+ Txt *t, *nt, **l;
+
+ assert(rp->magic == RRmagic);
+ nrp = rralloc(rp->type);
+ switch(rp->type){
+ case Tsoa:
+ soa = nrp->soa;
+ *nrp = *rp;
+ nrp->soa = soa;
+ *soa = *rp->soa;
+ soa->slaves = copyserverlist(rp->soa->slaves);
+ break;
+ case Tsrv:
+ srv = nrp->srv;
+ *nrp = *rp;
+ nrp->srv = srv;
+ *srv = *rp->srv;
+ break;
+ case Tkey:
+ key = nrp->key;
+ *nrp = *rp;
+ nrp->key = key;
+ *key = *rp->key;
+ key->data = emalloc(key->dlen);
+ memmove(key->data, rp->key->data, rp->key->dlen);
+ break;
+ case Tcert:
+ cert = nrp->cert;
+ *nrp = *rp;
+ nrp->cert = cert;
+ *cert = *rp->cert;
+ cert->data = emalloc(cert->dlen);
+ memmove(cert->data, rp->cert->data, rp->cert->dlen);
+ break;
+ case Tsig:
+ sig = nrp->sig;
+ *nrp = *rp;
+ nrp->sig = sig;
+ *sig = *rp->sig;
+ sig->data = emalloc(sig->dlen);
+ memmove(sig->data, rp->sig->data, rp->sig->dlen);
+ break;
+ case Tnull:
+ null = nrp->null;
+ *nrp = *rp;
+ nrp->null = null;
+ *null = *rp->null;
+ null->data = emalloc(null->dlen);
+ memmove(null->data, rp->null->data, rp->null->dlen);
+ break;
+ case Ttxt:
+ *nrp = *rp;
+ l = &nrp->txt;
+ *l = nil;
+ for(t = rp->txt; t != nil; t = t->next){
+ nt = emalloc(sizeof(*nt));
+ nt->p = estrdup(t->p);
+ nt->next = nil;
+ *l = nt;
+ l = &nt->next;
+ }
+ break;
+ default:
+ *nrp = *rp;
+ break;
+ }
+ nrp->pc = getcallerpc(&rp);
+ setmalloctag(nrp, nrp->pc);
+ nrp->cached = 0;
+ nrp->next = nil;
+ *last = nrp;
+ return &nrp->next;
+}
+
+/*
+ * lookup a resource record of a particular type and
+ * class attached to a domain name. Return copies.
+ *
+ * Priority ordering is:
+ * db authoritative
+ * not timed out network authoritative
+ * not timed out network unauthoritative
+ * unauthoritative db
+ *
+ * if flag NOneg is set, don't return negative cached entries.
+ * return nothing instead.
+ */
+RR*
+rrlookup(DN *dp, int type, int flag)
+{
+ RR *rp, *first, **last;
+
+ assert(dp->magic == DNmagic);
+
+ first = nil;
+ last = &first;
+ lock(&dnlock);
+
+ /* try for an authoritative db entry */
+ for(rp = dp->rr; rp; rp = rp->next){
+ assert(rp->magic == RRmagic && rp->cached);
+ if(rp->db)
+ if(rp->auth)
+ if(tsame(type, rp->type))
+ last = rrcopy(rp, last);
+ }
+ if(first)
+ goto out;
+
+ /* try for a living authoritative network entry */
+ for(rp = dp->rr; rp; rp = rp->next){
+ if(!rp->db)
+ if(rp->auth)
+ if((long)(rp->expire - now) > 0)
+ if(tsame(type, rp->type)){
+ if(flag == NOneg && rp->negative)
+ goto out;
+ last = rrcopy(rp, last);
+ }
+ }
+ if(first)
+ goto out;
+
+ /* try for a living unauthoritative network entry */
+ for(rp = dp->rr; rp; rp = rp->next){
+ if(!rp->db)
+ if((long)(rp->expire - now) > 0)
+ if(tsame(type, rp->type)){
+ if(flag == NOneg && rp->negative)
+ goto out;
+ last = rrcopy(rp, last);
+ }
+ }
+ if(first)
+ goto out;
+
+ /* try for an unauthoritative db entry */
+ for(rp = dp->rr; rp; rp = rp->next){
+ if(rp->db)
+ if(tsame(type, rp->type))
+ last = rrcopy(rp, last);
+ }
+ if(first)
+ goto out;
+
+ /* otherwise, settle for anything we got (except for negative caches) */
+ for(rp = dp->rr; rp; rp = rp->next)
+ if(tsame(type, rp->type)){
+ if(rp->negative)
+ goto out;
+ last = rrcopy(rp, last);
+ }
+
+out:
+ unlock(&dnlock);
+ unique(first);
+ return first;
+}
+
+/*
+ * convert an ascii RR type name to its integer representation
+ */
+int
+rrtype(char *atype)
+{
+ int i;
+
+ for(i = 0; i <= Tall; i++)
+ if(rrtname[i] && strcmp(rrtname[i], atype) == 0)
+ return i;
+
+ /* make any a synonym for all */
+ if(strcmp(atype, "any") == 0)
+ return Tall;
+ else if(isascii(atype[0]) && isdigit(atype[0]))
+ return atoi(atype);
+ else
+ return -1;
+}
+
+/*
+ * return 0 if not a supported rr type
+ */
+int
+rrsupported(int type)
+{
+ if(type < 0 || type >Tall)
+ return 0;
+ return rrtname[type] != nil;
+}
+
+/*
+ * compare 2 types
+ */
+int
+tsame(int t1, int t2)
+{
+ return t1 == t2 || t1 == Tall;
+}
+
+/*
+ * Add resource records to a list.
+ */
+RR*
+rrcat(RR **start, RR *rp)
+{
+ RR *olp, *nlp;
+ RR **last;
+
+ /* check for duplicates */
+ for (olp = *start; 0 && olp; olp = olp->next)
+ for (nlp = rp; nlp; nlp = nlp->next)
+ if (rrsame(nlp, olp))
+ dnslog("rrcat: duplicate RR: %R", nlp);
+ USED(olp);
+
+ last = start;
+ while(*last != nil)
+ last = &(*last)->next;
+
+ *last = rp;
+ return *start;
+}
+
+RR*
+rrremfilter(RR **l, int (*filter)(RR*, void*), void *arg)
+{
+ RR *first, *rp;
+ RR **nl;
+
+ first = nil;
+ nl = &first;
+ while(*l != nil){
+ rp = *l;
+ if((*filter)(rp, arg)){
+ *l = rp->next;
+ *nl = rp;
+ nl = &rp->next;
+ *nl = nil;
+ } else
+ l = &(*l)->next;
+ }
+
+ return first;
+}
+
+static int
+filterneg(RR *rp, void*)
+{
+ return rp->negative;
+}
+static int
+filtertype(RR *rp, void *arg)
+{
+ return rp->type == *((int*)arg);
+}
+static int
+filterowner(RR *rp, void *arg)
+{
+ return rp->owner == (DN*)arg;
+}
+
+/*
+ * remove negative cache rr's from an rr list
+ */
+RR*
+rrremneg(RR **l)
+{
+ return rrremfilter(l, filterneg, nil);
+}
+
+/*
+ * remove rr's of a particular type from an rr list
+ */
+RR*
+rrremtype(RR **l, int type)
+{
+ return rrremfilter(l, filtertype, &type);
+}
+
+/*
+ * remove rr's of a particular owner from an rr list
+ */
+RR*
+rrremowner(RR **l, DN *owner)
+{
+ return rrremfilter(l, filterowner, owner);
+}
+
+static char *
+dnname(DN *dn)
+{
+ return dn? dn->name: "<null>";
+}
+
+static char *
+idnname(DN *dn, char *buf, int nbuf)
+{
+ char *name;
+
+ name = dnname(dn);
+ if(idn2utf(name, buf, nbuf) >= 0)
+ return buf;
+ return name;
+}
+
+/*
+ * print conversion for rr records
+ */
+int
+rrfmt(Fmt *f)
+{
+ int rv;
+ char *strp;
+ char buf[Domlen];
+ Fmt fstr;
+ RR *rp;
+ Server *s;
+ SOA *soa;
+ Srv *srv;
+ Txt *t;
+
+ fmtstrinit(&fstr);
+
+ rp = va_arg(f->args, RR*);
+ if(rp == nil){
+ fmtprint(&fstr, "<null>");
+ goto out;
+ }
+
+ fmtprint(&fstr, "%s %s", dnname(rp->owner),
+ rrname(rp->type, buf, sizeof buf));
+
+ if(rp->negative){
+ fmtprint(&fstr, "\tnegative - rcode %d", rp->negrcode);
+ goto out;
+ }
+
+ switch(rp->type){
+ case Thinfo:
+ fmtprint(&fstr, "\t%s %s", dnname(rp->cpu), dnname(rp->os));
+ break;
+ case Tcname:
+ case Tmb:
+ case Tmd:
+ case Tmf:
+ case Tns:
+ fmtprint(&fstr, "\t%s", dnname(rp->host));
+ break;
+ case Tmg:
+ case Tmr:
+ fmtprint(&fstr, "\t%s", dnname(rp->mb));
+ break;
+ case Tminfo:
+ fmtprint(&fstr, "\t%s %s", dnname(rp->mb), dnname(rp->rmb));
+ break;
+ case Tmx:
+ fmtprint(&fstr, "\t%lud %s", rp->pref, dnname(rp->host));
+ break;
+ case Ta:
+ case Taaaa:
+ fmtprint(&fstr, "\t%s", dnname(rp->ip));
+ break;
+ case Tptr:
+ fmtprint(&fstr, "\t%s", dnname(rp->ptr));
+ break;
+ case Tsoa:
+ soa = rp->soa;
+ fmtprint(&fstr, "\t%s %s %lud %lud %lud %lud %lud",
+ dnname(rp->host), dnname(rp->rmb),
+ (soa? soa->serial: 0),
+ (soa? soa->refresh: 0), (soa? soa->retry: 0),
+ (soa? soa->expire: 0), (soa? soa->minttl: 0));
+ if (soa)
+ for(s = soa->slaves; s != nil; s = s->next)
+ fmtprint(&fstr, " %s", s->name);
+ break;
+ case Tsrv:
+ srv = rp->srv;
+ fmtprint(&fstr, "\t%ud %ud %ud %s",
+ (srv? srv->pri: 0), (srv? srv->weight: 0),
+ rp->port, dnname(rp->host));
+ break;
+ case Tnull:
+ if (rp->null == nil)
+ fmtprint(&fstr, "\t<null>");
+ else
+ fmtprint(&fstr, "\t%.*H", rp->null->dlen,
+ rp->null->data);
+ break;
+ case Ttxt:
+ fmtprint(&fstr, "\t");
+ for(t = rp->txt; t != nil; t = t->next)
+ fmtprint(&fstr, "%s", t->p);
+ break;
+ case Trp:
+ fmtprint(&fstr, "\t%s %s", dnname(rp->rmb), dnname(rp->rp));
+ break;
+ case Tkey:
+ if (rp->key == nil)
+ fmtprint(&fstr, "\t<null> <null> <null>");
+ else
+ fmtprint(&fstr, "\t%d %d %d", rp->key->flags,
+ rp->key->proto, rp->key->alg);
+ break;
+ case Tsig:
+ if (rp->sig == nil)
+ fmtprint(&fstr,
+ "\t<null> <null> <null> <null> <null> <null> <null> <null>");
+ else
+ fmtprint(&fstr, "\t%d %d %d %lud %lud %lud %d %s",
+ rp->sig->type, rp->sig->alg, rp->sig->labels,
+ rp->sig->ttl, rp->sig->exp, rp->sig->incep,
+ rp->sig->tag, dnname(rp->sig->signer));
+ break;
+ case Tcert:
+ if (rp->cert == nil)
+ fmtprint(&fstr, "\t<null> <null> <null>");
+ else
+ fmtprint(&fstr, "\t%d %d %d",
+ rp->cert->type, rp->cert->tag, rp->cert->alg);
+ break;
+ }
+out:
+ strp = fmtstrflush(&fstr);
+ rv = fmtstrcpy(f, strp);
+ free(strp);
+ return rv;
+}
+
+/*
+ * print conversion for rr records in attribute value form
+ */
+int
+rravfmt(Fmt *f)
+{
+ int rv, quote;
+ char buf[Domlen], *strp;
+ Fmt fstr;
+ RR *rp;
+ Server *s;
+ SOA *soa;
+ Srv *srv;
+ Txt *t;
+
+ fmtstrinit(&fstr);
+
+ rp = va_arg(f->args, RR*);
+ if(rp == nil){
+ fmtprint(&fstr, "<null>");
+ goto out;
+ }
+
+ if(rp->type == Tptr)
+ fmtprint(&fstr, "ptr=%s", dnname(rp->owner));
+ else
+ fmtprint(&fstr, "dom=%s", idnname(rp->owner, buf, sizeof(buf)));
+
+ switch(rp->type){
+ case Thinfo:
+ fmtprint(&fstr, " cpu=%s os=%s",
+ idnname(rp->cpu, buf, sizeof(buf)),
+ idnname(rp->os, buf, sizeof(buf)));
+ break;
+ case Tcname:
+ fmtprint(&fstr, " cname=%s", idnname(rp->host, buf, sizeof(buf)));
+ break;
+ case Tmb:
+ case Tmd:
+ case Tmf:
+ fmtprint(&fstr, " mbox=%s", idnname(rp->host, buf, sizeof(buf)));
+ break;
+ case Tns:
+ fmtprint(&fstr, " ns=%s", idnname(rp->host, buf, sizeof(buf)));
+ break;
+ case Tmg:
+ case Tmr:
+ fmtprint(&fstr, " mbox=%s", idnname(rp->mb, buf, sizeof(buf)));
+ break;
+ case Tminfo:
+ fmtprint(&fstr, " mbox=%s mbox=%s",
+ idnname(rp->mb, buf, sizeof(buf)),
+ idnname(rp->rmb, buf, sizeof(buf)));
+ break;
+ case Tmx:
+ fmtprint(&fstr, " pref=%lud mx=%s", rp->pref,
+ idnname(rp->host, buf, sizeof(buf)));
+ break;
+ case Ta:
+ case Taaaa:
+ fmtprint(&fstr, " ip=%s", dnname(rp->ip));
+ break;
+ case Tptr:
+ fmtprint(&fstr, " dom=%s", dnname(rp->ptr));
+ break;
+ case Tsoa:
+ soa = rp->soa;
+ fmtprint(&fstr,
+" ns=%s mbox=%s serial=%lud refresh=%lud retry=%lud expire=%lud ttl=%lud",
+ idnname(rp->host, buf, sizeof(buf)),
+ idnname(rp->rmb, buf, sizeof(buf)),
+ (soa? soa->serial: 0),
+ (soa? soa->refresh: 0), (soa? soa->retry: 0),
+ (soa? soa->expire: 0), (soa? soa->minttl: 0));
+ for(s = soa->slaves; s != nil; s = s->next)
+ fmtprint(&fstr, " dnsslave=%s", s->name);
+ break;
+ case Tsrv:
+ srv = rp->srv;
+ fmtprint(&fstr, " pri=%ud weight=%ud port=%ud target=%s",
+ (srv? srv->pri: 0), (srv? srv->weight: 0),
+ rp->port, idnname(rp->host, buf, sizeof(buf)));
+ break;
+ case Tnull:
+ if (rp->null == nil)
+ fmtprint(&fstr, " null=<null>");
+ else
+ fmtprint(&fstr, " null=%.*H", rp->null->dlen,
+ rp->null->data);
+ break;
+ case Ttxt:
+ fmtprint(&fstr, " txt=");
+ quote = 0;
+ for(t = rp->txt; t != nil; t = t->next)
+ if(strchr(t->p, ' '))
+ quote = 1;
+ if(quote)
+ fmtprint(&fstr, "\"");
+ for(t = rp->txt; t != nil; t = t->next)
+ fmtprint(&fstr, "%s", t->p);
+ if(quote)
+ fmtprint(&fstr, "\"");
+ break;
+ case Trp:
+ fmtprint(&fstr, " rp=%s txt=%s",
+ idnname(rp->rmb, buf, sizeof(buf)),
+ idnname(rp->rp, buf, sizeof(buf)));
+ break;
+ case Tkey:
+ if (rp->key == nil)
+ fmtprint(&fstr, " flags=<null> proto=<null> alg=<null>");
+ else
+ fmtprint(&fstr, " flags=%d proto=%d alg=%d",
+ rp->key->flags, rp->key->proto, rp->key->alg);
+ break;
+ case Tsig:
+ if (rp->sig == nil)
+ fmtprint(&fstr,
+" type=<null> alg=<null> labels=<null> ttl=<null> exp=<null> incep=<null> tag=<null> signer=<null>");
+ else
+ fmtprint(&fstr,
+" type=%d alg=%d labels=%d ttl=%lud exp=%lud incep=%lud tag=%d signer=%s",
+ rp->sig->type, rp->sig->alg, rp->sig->labels,
+ rp->sig->ttl, rp->sig->exp, rp->sig->incep,
+ rp->sig->tag, idnname(rp->sig->signer, buf, sizeof(buf)));
+ break;
+ case Tcert:
+ if (rp->cert == nil)
+ fmtprint(&fstr, " type=<null> tag=<null> alg=<null>");
+ else
+ fmtprint(&fstr, " type=%d tag=%d alg=%d",
+ rp->cert->type, rp->cert->tag, rp->cert->alg);
+ break;
+ }
+out:
+ strp = fmtstrflush(&fstr);
+ rv = fmtstrcpy(f, strp);
+ free(strp);
+ return rv;
+}
+
+void
+warning(char *fmt, ...)
+{
+ char dnserr[256];
+ va_list arg;
+
+ va_start(arg, fmt);
+ vseprint(dnserr, dnserr+sizeof(dnserr), fmt, arg);
+ va_end(arg);
+ syslog(1, logfile, dnserr); /* on console too */
+}
+
+void
+dnslog(char *fmt, ...)
+{
+ char dnserr[256];
+ va_list arg;
+
+ va_start(arg, fmt);
+ vseprint(dnserr, dnserr+sizeof(dnserr), fmt, arg);
+ va_end(arg);
+ syslog(0, logfile, dnserr);
+}
+
+/*
+ * create a slave process to handle a request to avoid one request blocking
+ * another
+ */
+void
+slave(Request *req)
+{
+ int ppid, procs;
+
+ if(req->isslave)
+ return; /* we're already a slave process */
+
+ /*
+ * These calls to putactivity cannot block.
+ * After getactivity(), the current process is counted
+ * twice in dnvars.active (one will pass to the child).
+ * If putactivity tries to wait for dnvars.active == 0,
+ * it will never happen.
+ */
+
+ /* limit parallelism */
+ procs = getactivity(req, 1);
+ if(procs > stats.slavehiwat)
+ stats.slavehiwat = procs;
+ if(procs > Maxactive){
+ if(traceactivity)
+ dnslog("[%d] too much activity", getpid());
+ putactivity(1);
+ return;
+ }
+
+ /*
+ * parent returns to main loop, child does the work.
+ * don't change note group.
+ */
+ ppid = getpid();
+ switch(rfork(RFPROC|RFMEM|RFNOWAIT)){
+ case -1:
+ putactivity(1);
+ break;
+ case 0:
+ procsetname("request slave of pid %d", ppid);
+ if(traceactivity)
+ dnslog("[%d] take activity from %d", getpid(), ppid);
+ req->isslave = 1; /* why not `= getpid()'? */
+ break;
+ default:
+ /*
+ * this relies on rfork producing separate, initially-identical
+ * stacks, thus giving us two copies of `req', one in each
+ * process.
+ */
+ alarm(0);
+ longjmp(req->mret, 1);
+ }
+}
+
+static int
+blockequiv(Block *a, Block *b)
+{
+ return a->dlen == b->dlen &&
+ memcmp(a->data, b->data, a->dlen) == 0;
+}
+
+static int
+keyequiv(Key *a, Key *b)
+{
+ return a->flags == b->flags &&
+ a->proto == b->proto &&
+ a->alg == b->alg &&
+ blockequiv(a, b);
+}
+
+static int
+certequiv(Cert *a, Cert *b)
+{
+ return a->type == a->type &&
+ a->tag == a->tag &&
+ a->alg == a->alg &&
+ blockequiv(a, b);
+}
+
+static int
+txtequiv(Txt *a, Txt *b)
+{
+ char *ap, *ae, *bp, *be;
+ int n;
+
+ for(ap = ae = bp = be = nil;;ap += n, bp += n){
+ while(a != nil && (ap == nil || (ap == ae && (a = a->next) != nil)))
+ ap = a->p, ae = ap + strlen(ap);
+ while(b != nil && (bp == nil || (bp == be && (b = b->next) != nil)))
+ bp = b->p, be = bp + strlen(bp);
+ if(a == b || a == nil || b == nil)
+ break;
+ n = ae - ap;
+ if(be - bp < n)
+ n = be - bp;
+ if(memcmp(ap, bp, n) != 0)
+ return 0;
+ }
+ return a == b;
+}
+
+static int
+rrequiv(RR *r1, RR *r2)
+{
+ if(r1->owner != r2->owner
+ || r1->type != r2->type
+ || r1->arg0 != r2->arg0
+ || r1->arg1 != r2->arg1)
+ return 0;
+ switch(r1->type){
+ case Tkey:
+ return keyequiv(r1->key, r2->key);
+ case Tcert:
+ return certequiv(r1->cert, r2->cert);
+ case Tsig:
+ return r1->sig->signer == r2->sig->signer && certequiv(r1->sig, r2->sig);
+ case Tnull:
+ return blockequiv(r1->null, r2->null);
+ case Ttxt:
+ return txtequiv(r1->txt, r2->txt);
+ }
+ return 1;
+}
+
+void
+unique(RR *rp)
+{
+ RR **l, *nrp;
+
+ for(; rp; rp = rp->next){
+ l = &rp->next;
+ for(nrp = *l; nrp; nrp = *l)
+ if(rrequiv(rp, nrp)){
+ *l = nrp->next;
+ rrfree(nrp);
+ } else
+ l = &nrp->next;
+ }
+}
+
+/*
+ * true if second domain is subsumed by the first
+ */
+int
+subsume(char *higher, char *lower)
+{
+ int hn, ln;
+
+ ln = strlen(lower);
+ hn = strlen(higher);
+ if (ln < hn || cistrcmp(lower + ln - hn, higher) != 0 ||
+ ln > hn && hn != 0 && lower[ln - hn - 1] != '.')
+ return 0;
+ return 1;
+}
+
+/*
+ * randomize the order we return items to provide some
+ * load balancing for servers.
+ *
+ * only randomize the first class of entries
+ */
+RR*
+randomize(RR *rp)
+{
+ RR *first, *last, *x, *base;
+ ulong n;
+
+ if(rp == nil || rp->next == nil)
+ return rp;
+
+ /* just randomize addresses, mx's and ns's */
+ for(x = rp; x; x = x->next)
+ if(x->type != Ta && x->type != Taaaa &&
+ x->type != Tmx && x->type != Tns)
+ return rp;
+
+ base = rp;
+
+ n = rand();
+ last = first = nil;
+ while(rp != nil){
+ /* stop randomizing if we've moved past our class */
+ if(base->auth != rp->auth || base->db != rp->db){
+ last->next = rp;
+ break;
+ }
+
+ /* unchain */
+ x = rp;
+ rp = x->next;
+ x->next = nil;
+
+ if(n&1){
+ /* add to tail */
+ if(last == nil)
+ first = x;
+ else
+ last->next = x;
+ last = x;
+ } else {
+ /* add to head */
+ if(last == nil)
+ last = x;
+ x->next = first;
+ first = x;
+ }
+
+ /* reroll the dice */
+ n >>= 1;
+ }
+
+ return first;
+}
+
+static int
+sencodefmt(Fmt *f)
+{
+ int i, len, ilen, rv;
+ char *out, *buf;
+ uchar *b;
+ char obuf[64]; /* rsc optimization */
+
+ if(!(f->flags&FmtPrec) || f->prec < 1)
+ goto error;
+
+ b = va_arg(f->args, uchar*);
+ if(b == nil)
+ goto error;
+
+ /* if it's a printable, go for it */
+ len = f->prec;
+ for(i = 0; i < len; i++)
+ if(!isprint(b[i]))
+ break;
+ if(i == len){
+ if(len >= sizeof obuf)
+ len = sizeof(obuf)-1;
+ memmove(obuf, b, len);
+ obuf[len] = 0;
+ fmtstrcpy(f, obuf);
+ return 0;
+ }
+
+ ilen = f->prec;
+ f->prec = 0;
+ f->flags &= ~FmtPrec;
+ len = 2*ilen + 1;
+ if(len > sizeof(obuf)){
+ buf = malloc(len);
+ if(buf == nil)
+ goto error;
+ } else
+ buf = obuf;
+
+ /* convert */
+ out = buf;
+ rv = enc16(out, len, b, ilen);
+ if(rv < 0)
+ goto error;
+
+ fmtstrcpy(f, buf);
+ if(buf != obuf)
+ free(buf);
+ return 0;
+
+error:
+ return fmtstrcpy(f, "<encodefmt>");
+}
+
+void*
+emalloc(int size)
+{
+ char *x;
+
+ x = mallocz(size, 1);
+ if(x == nil)
+ abort();
+ setmalloctag(x, getcallerpc(&size));
+ return x;
+}
+
+char*
+estrdup(char *s)
+{
+ int size;
+ char *p;
+
+ size = strlen(s);
+ p = mallocz(size+1, 0);
+ if(p == nil)
+ abort();
+ memmove(p, s, size);
+ p[size] = 0;
+ setmalloctag(p, getcallerpc(&s));
+ return p;
+}
+
+/*
+ * create a pointer record
+ */
+static RR*
+mkptr(DN *dp, char *ptr, ulong ttl)
+{
+ DN *ipdp;
+ RR *rp;
+
+ ipdp = dnlookup(ptr, Cin, 1);
+
+ rp = rralloc(Tptr);
+ rp->ptr = dp;
+ rp->owner = ipdp;
+ rp->db = 1;
+ if(ttl)
+ rp->ttl = ttl;
+ return rp;
+}
+
+void bytes2nibbles(uchar *nibbles, uchar *bytes, int nbytes);
+
+/*
+ * look for all ip addresses in this network and make
+ * pointer records for them.
+ */
+void
+dnptr(uchar *net, uchar *mask, char *dom, int forwtype, int subdoms, int ttl)
+{
+ int i, j, len;
+ char *p, *e;
+ char ptr[Domlen];
+ uchar *ipp;
+ uchar ip[IPaddrlen], nnet[IPaddrlen];
+ uchar nibip[IPaddrlen*2];
+ DN *dp;
+ RR *rp, *nrp, *first, **l;
+
+ l = &first;
+ first = nil;
+ for(i = 0; i < HTLEN; i++)
+ for(dp = ht[i]; dp; dp = dp->next)
+ for(rp = dp->rr; rp; rp = rp->next){
+ if(rp->type != forwtype || rp->negative)
+ continue;
+ parseip(ip, rp->ip->name);
+ maskip(ip, mask, nnet);
+ if(ipcmp(net, nnet) != 0)
+ continue;
+
+ ipp = ip;
+ len = IPaddrlen;
+ if (forwtype == Taaaa) {
+ bytes2nibbles(nibip, ip, IPaddrlen);
+ ipp = nibip;
+ len = 2*IPaddrlen;
+ }
+
+ p = ptr;
+ e = ptr+sizeof(ptr);
+ for(j = len - 1; j >= len - subdoms; j--)
+ p = seprint(p, e, (forwtype == Ta?
+ "%d.": "%x."), ipp[j]);
+ seprint(p, e, "%s", dom);
+
+ nrp = mkptr(dp, ptr, ttl);
+ *l = nrp;
+ l = &nrp->next;
+ }
+
+ for(rp = first; rp != nil; rp = nrp){
+ nrp = rp->next;
+ rp->next = nil;
+ dp = rp->owner;
+ rrattach(rp, Authoritative);
+ dnagenever(dp);
+ }
+}
+
+void
+addserver(Server **l, char *name)
+{
+ Server *s;
+ int n;
+
+ while(*l)
+ l = &(*l)->next;
+ n = strlen(name);
+ s = malloc(sizeof(Server)+n+1);
+ if(s == nil)
+ return;
+ s->name = (char*)(s+1);
+ memmove(s->name, name, n);
+ s->name[n] = 0;
+ s->next = nil;
+ *l = s;
+}
+
+Server*
+copyserverlist(Server *s)
+{
+ Server *ns;
+
+ for(ns = nil; s != nil; s = s->next)
+ addserver(&ns, s->name);
+ return ns;
+}
+
+
+/* from here down is copied to ip/snoopy/dns.c periodically to update it */
+
+/*
+ * convert an integer RR type to it's ascii name
+ */
+char*
+rrname(int type, char *buf, int len)
+{
+ char *t;
+
+ t = nil;
+ if(type >= 0 && type <= Tall)
+ t = rrtname[type];
+ if(t==nil){
+ snprint(buf, len, "%d", type);
+ t = buf;
+ }
+ return t;
+}
+
+/*
+ * free a list of resource records and any related structs
+ */
+void
+rrfreelist(RR *rp)
+{
+ RR *next;
+
+ for(; rp; rp = next){
+ next = rp->next;
+ rrfree(rp);
+ }
+}
+
+void
+freeserverlist(Server *s)
+{
+ Server *next;
+
+ for(; s != nil; s = next){
+ next = s->next;
+ memset(s, 0, sizeof *s); /* cause trouble */
+ free(s);
+ }
+}
+
+/*
+ * allocate a resource record of a given type
+ */
+RR*
+rralloc(int type)
+{
+ RR *rp;
+
+ rp = emalloc(sizeof(*rp));
+ rp->magic = RRmagic;
+ rp->pc = getcallerpc(&type);
+ rp->type = type;
+ if (rp->type != type)
+ dnslog("rralloc: bogus type %d", type);
+ setmalloctag(rp, rp->pc);
+ switch(type){
+ case Tsoa:
+ rp->soa = emalloc(sizeof(*rp->soa));
+ rp->soa->slaves = nil;
+ setmalloctag(rp->soa, rp->pc);
+ break;
+ case Tsrv:
+ rp->srv = emalloc(sizeof(*rp->srv));
+ setmalloctag(rp->srv, rp->pc);
+ break;
+ case Tkey:
+ rp->key = emalloc(sizeof(*rp->key));
+ setmalloctag(rp->key, rp->pc);
+ break;
+ case Tcert:
+ rp->cert = emalloc(sizeof(*rp->cert));
+ setmalloctag(rp->cert, rp->pc);
+ break;
+ case Tsig:
+ rp->sig = emalloc(sizeof(*rp->sig));
+ setmalloctag(rp->sig, rp->pc);
+ break;
+ case Tnull:
+ rp->null = emalloc(sizeof(*rp->null));
+ setmalloctag(rp->null, rp->pc);
+ break;
+ }
+ rp->ttl = 0;
+ rp->expire = 0;
+ rp->next = 0;
+ return rp;
+}
+
+/*
+ * free a resource record and any related structs
+ */
+void
+rrfree(RR *rp)
+{
+ Txt *t;
+
+ assert(rp->magic == RRmagic && !rp->cached);
+
+ switch(rp->type){
+ case Tsoa:
+ freeserverlist(rp->soa->slaves);
+ memset(rp->soa, 0, sizeof *rp->soa); /* cause trouble */
+ free(rp->soa);
+ break;
+ case Tsrv:
+ memset(rp->srv, 0, sizeof *rp->srv); /* cause trouble */
+ free(rp->srv);
+ break;
+ case Tkey:
+ free(rp->key->data);
+ memset(rp->key, 0, sizeof *rp->key); /* cause trouble */
+ free(rp->key);
+ break;
+ case Tcert:
+ free(rp->cert->data);
+ memset(rp->cert, 0, sizeof *rp->cert); /* cause trouble */
+ free(rp->cert);
+ break;
+ case Tsig:
+ free(rp->sig->data);
+ memset(rp->sig, 0, sizeof *rp->sig); /* cause trouble */
+ free(rp->sig);
+ break;
+ case Tnull:
+ free(rp->null->data);
+ memset(rp->null, 0, sizeof *rp->null); /* cause trouble */
+ free(rp->null);
+ break;
+ case Ttxt:
+ while(t = rp->txt){
+ rp->txt = t->next;
+ free(t->p);
+ memset(t, 0, sizeof *t); /* cause trouble */
+ free(t);
+ }
+ break;
+ }
+
+ memset(rp, 0, sizeof *rp); /* cause trouble */
+ rp->magic = ~RRmagic;
+ free(rp);
+}
--- /dev/null
+++ b/dnarea.c
@@ -1,0 +1,139 @@
+#include <u.h>
+#include <libc.h>
+#include <bio.h>
+#include <ndb.h>
+#include <ip.h>
+#include "dns.h"
+
+Area *owned, *delegated;
+
+/*
+ * true if a name is in our area
+ */
+Area*
+inmyarea(char *name)
+{
+ int len;
+ Area *s, *d;
+
+ len = strlen(name);
+ for(s = owned; s; s = s->next){
+ if(s->len > len)
+ continue;
+ if(cistrcmp(s->soarr->owner->name, name + len - s->len) == 0)
+ if(len == s->len || name[len - s->len - 1] == '.')
+ break;
+ }
+ if(s == nil)
+ return nil;
+
+ /* name is in area `s' */
+ for(d = delegated; d; d = d->next){
+ if(d->len > len)
+ continue;
+ if(cistrcmp(d->soarr->owner->name, name + len - d->len) == 0)
+ if(len == d->len || name[len - d->len - 1] == '.')
+ return nil; /* name is in a delegated subarea */
+ }
+
+ return s; /* name is in area `s' and not in a delegated subarea */
+}
+
+/*
+ * our area is the part of the domain tree that
+ * we serve
+ */
+void
+addarea(DN *dp, RR *rp, Ndbtuple *t)
+{
+ Area *s;
+ Area **l;
+
+ lock(&dnlock);
+ if(t->val[0])
+ l = &delegated;
+ else
+ l = &owned;
+
+ for (s = *l; s != nil; s = s->next)
+ if(s->soarr->owner == dp) {
+ unlock(&dnlock);
+ return; /* we've already got one */
+ }
+
+ /*
+ * The area contains a copy of the soa rr that created it.
+ * The owner of the the soa rr should stick around as long
+ * as the area does.
+ */
+ s = emalloc(sizeof(*s));
+ s->len = strlen(dp->name);
+ rrcopy(rp, &s->soarr);
+ s->soarr->owner = dp;
+ s->soarr->db = 1;
+ s->soarr->ttl = Hour;
+ s->neednotify = 1;
+ s->needrefresh = 0;
+
+ if (debug)
+ dnslog("new area %s %s", dp->name,
+ l == &delegated? "delegated": "owned");
+
+ s->next = *l;
+ *l = s;
+ unlock(&dnlock);
+}
+
+void
+freearea(Area **l)
+{
+ Area *s;
+
+ lock(&dnlock);
+ while(s = *l){
+ *l = s->next;
+ rrfree(s->soarr);
+ memset(s, 0, sizeof *s); /* cause trouble */
+ free(s);
+ }
+ unlock(&dnlock);
+}
+
+/*
+ * refresh all areas that need it
+ * this entails running a command 'zonerefreshprogram'. This could
+ * copy over databases from elsewhere or just do a zone transfer.
+ */
+void
+refresh_areas(Area *s)
+{
+ int pid;
+ Waitmsg *w;
+
+ for(; s != nil; s = s->next){
+ if(!s->needrefresh)
+ continue;
+
+ if(zonerefreshprogram == nil){
+ s->needrefresh = 0;
+ continue;
+ }
+
+ pid = fork();
+ if (pid == -1) {
+ sleep(1000); /* don't fork again immediately */
+ continue;
+ }
+ if (pid == 0){
+ execl(zonerefreshprogram, "zonerefresh",
+ s->soarr->owner->name, nil);
+ exits("exec zonerefresh failed");
+ }
+ while ((w = wait()) != nil && w->pid != pid)
+ free(w);
+ if (w && w->pid == pid)
+ if(w->msg == nil || *w->msg == '\0')
+ s->needrefresh = 0;
+ free(w);
+ }
+}
--- /dev/null
+++ b/dnnotify.c
@@ -1,0 +1,176 @@
+#include <u.h>
+#include <libc.h>
+#include <ip.h>
+#include <bio.h>
+#include <ndb.h>
+#include "dns.h"
+
+/* get a notification from another system of a changed zone */
+void
+dnnotify(DNSmsg *reqp, DNSmsg *repp, Request *)
+{
+ RR *tp;
+ Area *a;
+
+ /* move one question from reqp to repp */
+ memset(repp, 0, sizeof(*repp));
+ tp = reqp->qd;
+ reqp->qd = tp->next;
+ tp->next = 0;
+ repp->qd = tp;
+ repp->id = reqp->id;
+ repp->flags = Fresp | Onotify | Fauth;
+
+ /* anything to do? */
+ if(zonerefreshprogram == nil)
+ return;
+
+ /* make sure its the right type */
+ if(repp->qd->type != Tsoa)
+ return;
+
+ dnslog("notification for %s", repp->qd->owner->name);
+
+ /* is it something we care about? */
+ a = inmyarea(repp->qd->owner->name);
+ if(a == nil)
+ return;
+
+ dnslog("serial old %lud new %lud", a->soarr->soa->serial,
+ repp->qd->soa->serial);
+
+ /* do nothing if it didn't change */
+ if(a->soarr->soa->serial != repp->qd->soa->serial)
+ a->needrefresh = 1;
+}
+
+static int
+getips(char *name, uchar *ips, int maxips, Request *req)
+{
+ RR *list, *rp;
+ int nips;
+
+ nips = 0;
+ if(nips <= maxips)
+ return nips;
+ if(strcmp(ipattr(name), "ip") == 0) {
+ if(parseip(ips, name) != -1 && !myip(ips))
+ nips++;
+ return nips;
+ }
+ list = dnresolve(name, Cin, Ta, req, nil, 0, Recurse, 0, nil);
+ rrcat(&list, dnresolve(name, Cin, Taaaa, req, nil, 0, Recurse, 0, nil));
+ rp = list = randomize(list);
+ while(rp != nil && nips < maxips){
+ uchar *ip = ips + nips*IPaddrlen;
+ if(parseip(ip, rp->ip->name) != -1 && !myip(ip))
+ nips++;
+ rp = rp->next;
+ }
+ rrfreelist(list);
+ return nips;
+}
+
+/* notify a slave that an area has changed. */
+static void
+send_notify(char *slave, RR *soa, Request *req)
+{
+ int i, j, len, n, reqno, fd, nips, send;
+ uchar ips[8*IPaddrlen], ibuf[Maxudp+Udphdrsize], obuf[Maxudp+Udphdrsize];
+ Udphdr *up = (Udphdr*)obuf;
+ DNSmsg repmsg;
+ char *err;
+
+ nips = getips(slave, ips, sizeof(ips)/IPaddrlen, req);
+ if(nips <= 0){
+ dnslog("no address %s to notify", slave);
+ return;
+ }
+
+ /* create the request */
+ reqno = rand();
+ n = mkreq(soa->owner, Cin, obuf, Fauth | Onotify, reqno);
+
+ fd = udpport(nil);
+ if(fd < 0)
+ return;
+
+ /* send 3 times or until we get anything back */
+ n += Udphdrsize;
+ for(i = 0; i < 3; i++, freeanswers(&repmsg)){
+ memset(&repmsg, 0, sizeof repmsg);
+ send = 0;
+ for(j = 0; j < nips; j++){
+ ipmove(up->raddr, ips + j*IPaddrlen);
+ if(write(fd, obuf, n) == n){
+ dnslog("send %d bytes notify to %s/%I.%d about %s", n, slave,
+ up->raddr, nhgets(up->rport), soa->owner->name);
+ send++;
+ }
+ }
+ if(send == 0)
+ break;
+ alarm(2*1000);
+ len = read(fd, ibuf, sizeof ibuf);
+ alarm(0);
+ if(len <= Udphdrsize)
+ continue;
+ err = convM2DNS(&ibuf[Udphdrsize], len, &repmsg, nil);
+ if(err != nil) {
+ free(err);
+ continue;
+ }
+ if(repmsg.id == reqno && (repmsg.flags & Omask) == Onotify){
+ freeanswers(&repmsg);
+ break;
+ }
+ }
+ close(fd);
+}
+
+/* send notifies for any updated areas */
+static void
+notify_areas(Area *a, Request *req)
+{
+ Server *s;
+
+ for(; a != nil; a = a->next){
+ if(!a->neednotify)
+ continue;
+
+ /* send notifies to all slaves */
+ for(s = a->soarr->soa->slaves; s != nil; s = s->next)
+ send_notify(s->name, a->soarr, req);
+ a->neednotify = 0;
+ }
+}
+
+/*
+ * process to notify other servers of changes
+ * (also reads in new databases)
+ */
+void
+notifyproc(void)
+{
+ Request req;
+
+ switch(rfork(RFPROC|RFNOTEG|RFMEM|RFNOWAIT)){
+ case -1:
+ return;
+ case 0:
+ break;
+ default:
+ return;
+ }
+
+ procsetname("notify slaves");
+ memset(&req, 0, sizeof req);
+ req.isslave = 1; /* don't fork off subprocesses */
+
+ for(;;){
+ getactivity(&req, 0);
+ notify_areas(owned, &req);
+ putactivity(0);
+ sleep(60*1000);
+ }
+}
--- /dev/null
+++ b/dnresolve.c
@@ -1,0 +1,1521 @@
+/*
+ * domain name resolvers, see rfcs 1035 and 1123
+ */
+#include <u.h>
+#include <libc.h>
+#include <ip.h>
+#include <bio.h>
+#include <ndb.h>
+#include "dns.h"
+
+typedef struct Dest Dest;
+typedef struct Query Query;
+
+enum
+{
+ Udp, Tcp,
+
+ Answerr= -1,
+ Answnone,
+
+ Maxdest= 32, /* maximum destinations for a request message */
+ Maxoutstanding= 15, /* max. outstanding queries per domain name */
+
+ /*
+ * these are the old values; we're trying longer timeouts now
+ * primarily for the benefit of remote nameservers querying us
+ * during times of bad connectivity.
+ */
+ Maxtrans= 5, /* maximum transmissions to a server */
+ Maxretries= 10, /* cname+actual resends: was 32; have pity on user */
+};
+enum { Hurry, Patient, };
+enum { Outns, Inns, };
+
+struct Dest
+{
+ uchar a[IPaddrlen]; /* ip address */
+ DN *s; /* name server name */
+ RR *n; /* name server rr */
+ int nx; /* number of transmissions */
+ int code; /* response code; used to clear dp->respcode */
+};
+
+struct Query {
+ DN *dp; /* domain */
+ ushort type; /* and type to look up */
+ Request *req;
+ Query *prev; /* previous query */
+
+ RR *nsrp; /* name servers to consult */
+
+ Dest *dest; /* array of destinations */
+ Dest *curdest; /* pointer to next to fill */
+ int ndest; /* transmit to this many on this round */
+
+ int udpfd;
+
+ int tcpset;
+ int tcpfd; /* if Tcp, read replies from here */
+ int tcpctlfd;
+ uchar tcpip[IPaddrlen];
+};
+
+static RR* dnresolve1(char*, int, int, Request*, int, int);
+static int netquery(Query *, int);
+
+/*
+ * reading /proc/pid/args yields either "name args" or "name [display args]",
+ * so return only display args, if any.
+ */
+static char *
+procgetname(void)
+{
+ int fd, n;
+ char *lp, *rp;
+ char buf[256];
+
+ snprint(buf, sizeof buf, "#p/%d/args", getpid());
+ if((fd = open(buf, OREAD)) < 0)
+ return strdup("");
+ *buf = '\0';
+ n = read(fd, buf, sizeof buf-1);
+ close(fd);
+ if (n >= 0)
+ buf[n] = '\0';
+ if ((lp = strchr(buf, '[')) == nil ||
+ (rp = strrchr(buf, ']')) == nil)
+ return strdup("");
+ *rp = '\0';
+ return strdup(lp+1);
+}
+
+void
+rrfreelistptr(RR **rpp)
+{
+ RR *rp;
+
+ if (rpp == nil || *rpp == nil)
+ return;
+ rp = *rpp;
+ *rpp = nil; /* update pointer in memory before freeing list */
+ rrfreelist(rp);
+}
+
+/*
+ * lookup 'type' info for domain name 'name'. If it doesn't exist, try
+ * looking it up as a canonical name.
+ *
+ * this process can be quite slow if time-outs are set too high when querying
+ * nameservers that just don't respond to certain query types. in that case,
+ * there will be multiple udp retries, multiple nameservers will be queried,
+ * and this will be repeated for a cname query. the whole thing will be
+ * retried several times until we get an answer or a time-out.
+ */
+RR*
+dnresolve(char *name, int class, int type, Request *req, RR **cn, int depth,
+ int recurse, int rooted, int *status)
+{
+ RR *rp, *nrp, *drp;
+ DN *dp;
+ int loops;
+ char *procname;
+ char nname[Domlen];
+
+ if(status)
+ *status = 0;
+
+ if(depth > 12) /* in a recursive loop? */
+ return nil;
+
+ procname = procgetname();
+ /*
+ * hack for systems that don't have resolve search
+ * lists. Just look up the simple name in the database.
+ */
+ if(!rooted && strchr(name, '.') == nil){
+ rp = nil;
+ drp = domainlist(class);
+ for(nrp = drp; rp == nil && nrp != nil; nrp = nrp->next){
+ snprint(nname, sizeof nname, "%s.%s", name,
+ nrp->ptr->name);
+ rp = dnresolve(nname, class, type, req, cn, depth+1,
+ recurse, rooted, status);
+ rrfreelist(rrremneg(&rp));
+ }
+ if(drp != nil)
+ rrfreelist(drp);
+ procsetname("%s", procname);
+ free(procname);
+ return rp;
+ }
+
+ /*
+ * try the name directly
+ */
+ rp = dnresolve1(name, class, type, req, depth, recurse);
+ if(rp == nil && (dp = idnlookup(name, class, 0)) != nil) {
+ /*
+ * try it as a canonical name if we weren't told
+ * that the name didn't exist
+ */
+ if(type != Tptr && dp->respcode != Rname)
+ for(loops = 0; rp == nil && loops < Maxretries; loops++){
+ /* retry cname, then the actual type */
+ rp = dnresolve1(name, class, Tcname, req,
+ depth, recurse);
+ if(rp == nil)
+ break;
+
+ /* rp->host == nil shouldn't happen, but does */
+ if(rp->negative || rp->host == nil){
+ rrfreelist(rp);
+ rp = nil;
+ break;
+ }
+
+ name = rp->host->name;
+ if(cn)
+ rrcat(cn, rp);
+ else
+ rrfreelist(rp);
+
+ rp = dnresolve1(name, class, type, req,
+ depth, recurse);
+ }
+
+ /* distinction between not found and not good */
+ if(rp == nil && status != nil && dp->respcode != Rok)
+ *status = dp->respcode;
+ }
+ procsetname("%s", procname);
+ free(procname);
+ return randomize(rp);
+}
+
+static void
+queryinit(Query *qp, DN *dp, int type, Request *req)
+{
+ assert(dp && dp->magic == DNmagic);
+
+ memset(qp, 0, sizeof *qp);
+ qp->udpfd = qp->tcpfd = qp->tcpctlfd = -1;
+ qp->dp = dp;
+ qp->type = type;
+ if (qp->type != type)
+ dnslog("queryinit: bogus type %d", type);
+ qp->nsrp = nil;
+ qp->dest = qp->curdest = nil;
+ qp->prev = req->aux;
+ qp->req = req;
+ req->aux = qp;
+}
+
+static void
+querydestroy(Query *qp)
+{
+ if(qp->req->aux == qp)
+ qp->req->aux = qp->prev;
+ /* leave udpfd open */
+ if (qp->tcpfd >= 0)
+ close(qp->tcpfd);
+ if (qp->tcpctlfd >= 0) {
+ hangup(qp->tcpctlfd);
+ close(qp->tcpctlfd);
+ }
+ memset(qp, 0, sizeof *qp); /* prevent accidents */
+ qp->udpfd = qp->tcpfd = qp->tcpctlfd = -1;
+}
+
+/*
+ * if the response to a query hasn't arrived within 100 ms.,
+ * it's unlikely to arrive at all. after 1 s., it's really unlikely.
+ * queries for missing RRs are likely to produce time-outs rather than
+ * negative responses, so cname and aaaa queries are likely to time out,
+ * thus we don't wait very long for them.
+ */
+static void
+notestats(vlong start, int tmout, int type)
+{
+ qlock(&stats);
+ if (tmout) {
+ stats.tmout++;
+ if (type == Taaaa)
+ stats.tmoutv6++;
+ else if (type == Tcname)
+ stats.tmoutcname++;
+ } else {
+ long wait10ths = NS2MS(nsec() - start) / 100;
+
+ if (wait10ths <= 0)
+ stats.under10ths[0]++;
+ else if (wait10ths >= nelem(stats.under10ths))
+ stats.under10ths[nelem(stats.under10ths) - 1]++;
+ else
+ stats.under10ths[wait10ths]++;
+ }
+ qunlock(&stats);
+}
+
+static void
+noteinmem(void)
+{
+ qlock(&stats);
+ stats.answinmem++;
+ qunlock(&stats);
+}
+
+/* netquery with given name servers, free ns rrs when done */
+static int
+netqueryns(Query *qp, int depth, RR *nsrp)
+{
+ int rv;
+
+ if(nsrp == nil)
+ return Answnone;
+ qp->nsrp = nsrp;
+ rv = netquery(qp, depth);
+ qp->nsrp = nil; /* prevent accidents */
+ rrfreelist(nsrp);
+ return rv;
+}
+
+static RR*
+issuequery(Query *qp, char *name, int class, int depth, int recurse)
+{
+ char *cp;
+ DN *nsdp;
+ RR *rp, *nsrp, *dbnsrp;
+
+ /*
+ * if we're running as just a resolver, query our
+ * designated name servers
+ */
+ if(cfg.resolver){
+ nsrp = randomize(getdnsservers(class));
+ if(nsrp != nil)
+ if(netqueryns(qp, depth+1, nsrp) > Answnone)
+ return rrlookup(qp->dp, qp->type, OKneg);
+ }
+
+ /*
+ * walk up the domain name looking for
+ * a name server for the domain.
+ */
+ for(cp = name; cp; cp = walkup(cp)){
+ /*
+ * if this is a local (served by us) domain,
+ * return answer
+ */
+ dbnsrp = randomize(dblookup(cp, class, Tns, 0, 0));
+ if(dbnsrp && dbnsrp->local){
+ rp = dblookup(name, class, qp->type, 1, dbnsrp->ttl);
+ rrfreelist(dbnsrp);
+ return rp;
+ }
+
+ /*
+ * if recursion isn't set, just accept local
+ * entries
+ */
+ if(recurse == Dontrecurse){
+ if(dbnsrp)
+ rrfreelist(dbnsrp);
+ continue;
+ }
+
+ /* look for ns in cache */
+ nsdp = idnlookup(cp, class, 0);
+ nsrp = nil;
+ if(nsdp)
+ nsrp = randomize(rrlookup(nsdp, Tns, NOneg));
+
+ /* if the entry timed out, ignore it */
+ if(nsrp && !nsrp->db && (long)(nsrp->expire - now) <= 0)
+ rrfreelistptr(&nsrp);
+
+ if(nsrp){
+ rrfreelistptr(&dbnsrp);
+
+ /* query the name servers found in cache */
+ if(netqueryns(qp, depth+1, nsrp) > Answnone)
+ return rrlookup(qp->dp, qp->type, OKneg);
+ } else if(dbnsrp)
+ /* try the name servers found in db */
+ if(netqueryns(qp, depth+1, dbnsrp) > Answnone)
+ return rrlookup(qp->dp, qp->type, NOneg);
+ }
+ return nil;
+}
+
+static RR*
+dnresolve1(char *name, int class, int type, Request *req, int depth,
+ int recurse)
+{
+ Area *area;
+ DN *dp;
+ RR *rp;
+ Query q;
+
+ if(debug)
+ dnslog("[%d] dnresolve1 %s %d %d", getpid(), name, type, class);
+
+ /* only class Cin implemented so far */
+ if(class != Cin)
+ return nil;
+
+ dp = idnlookup(name, class, 1);
+
+ /*
+ * Try the cache first
+ */
+ rp = rrlookup(dp, type, OKneg);
+ if(rp)
+ if(rp->db){
+ /* unauthoritative db entries are hints */
+ if(rp->auth) {
+ noteinmem();
+ if(debug)
+ dnslog("[%d] dnresolve1 %s %d %d: auth rr in db",
+ getpid(), name, type, class);
+ return rp;
+ }
+ } else
+ /* cached entry must still be valid */
+ if((long)(rp->expire - now) > 0)
+ /* but Tall entries are special */
+ if(type != Tall || rp->query == Tall) {
+ noteinmem();
+ if(debug)
+ dnslog("[%d] dnresolve1 %s %d %d: rr not in db",
+ getpid(), name, type, class);
+ return rp;
+ }
+ rrfreelist(rp);
+ rp = nil; /* accident prevention */
+ USED(rp);
+
+ /*
+ * try the cache for a canonical name. if found punt
+ * since we'll find it during the canonical name search
+ * in dnresolve().
+ */
+ if(type != Tcname){
+ rp = rrlookup(dp, Tcname, NOneg);
+ rrfreelist(rp);
+ if(rp){
+ if(debug)
+ dnslog("[%d] dnresolve1 %s %d %d: rr from rrlookup for non-cname",
+ getpid(), name, type, class);
+ return nil;
+ }
+ }
+
+ /*
+ * if the domain name is within an area of ours,
+ * we should have found its data in memory by now.
+ */
+ area = inmyarea(dp->name);
+ if (area || strncmp(dp->name, "local#", 6) == 0)
+ return nil;
+
+ queryinit(&q, dp, type, req);
+ rp = issuequery(&q, name, class, depth, recurse);
+ querydestroy(&q);
+
+ if(rp){
+ if(debug)
+ dnslog("[%d] dnresolve1 %s %d %d: rr from query",
+ getpid(), name, type, class);
+ return rp;
+ }
+
+ /* settle for a non-authoritative answer */
+ rp = rrlookup(dp, type, OKneg);
+ if(rp){
+ if(debug)
+ dnslog("[%d] dnresolve1 %s %d %d: rr from rrlookup",
+ getpid(), name, type, class);
+ return rp;
+ }
+
+ /* noone answered. try the database, we might have a chance. */
+ rp = dblookup(name, class, type, 0, 0);
+ if (rp) {
+ if(debug)
+ dnslog("[%d] dnresolve1 %s %d %d: rr from dblookup",
+ getpid(), name, type, class);
+ }else{
+ if(debug)
+ dnslog("[%d] dnresolve1 %s %d %d: no rr from dblookup; crapped out",
+ getpid(), name, type, class);
+ }
+ return rp;
+}
+
+/*
+ * walk a domain name one element to the right.
+ * return a pointer to that element.
+ * in other words, return a pointer to the parent domain name.
+ */
+char*
+walkup(char *name)
+{
+ char *cp;
+
+ cp = strchr(name, '.');
+ if(cp)
+ return cp+1;
+ else if(*name)
+ return "";
+ else
+ return 0;
+}
+
+/*
+ * Get a udp port for sending requests and reading replies. Put the port
+ * into "headers" mode.
+ */
+static char *hmsg = "headers";
+
+int
+udpport(char *mtpt)
+{
+ int fd, ctl;
+ char ds[64], adir[64];
+
+ /* get a udp port */
+ snprint(ds, sizeof ds, "%s/udp!*!0", (mtpt && *mtpt) ? mtpt : "/net");
+ ctl = announce(ds, adir);
+ if(ctl < 0){
+ /* warning("can't get udp port"); */
+ return -1;
+ }
+
+ /* turn on header style interface */
+ if(write(ctl, hmsg, strlen(hmsg)) != strlen(hmsg)){
+ close(ctl);
+ warning(hmsg);
+ return -1;
+ }
+
+ /* grab the data file */
+ snprint(ds, sizeof ds, "%s/data", adir);
+ fd = open(ds, ORDWR);
+ close(ctl);
+ if(fd < 0)
+ warning("can't open udp port %s: %r", ds);
+ return fd;
+}
+
+void
+initdnsmsg(DNSmsg *mp, RR *rp, int flags, ushort reqno)
+{
+ memset(mp, 0, sizeof *mp);
+ mp->flags = flags;
+ mp->id = reqno;
+ mp->qd = rp;
+ if(rp != nil)
+ mp->qdcount = 1;
+}
+
+/* generate a DNS UDP query packet */
+int
+mkreq(DN *dp, int type, uchar *buf, int flags, ushort reqno)
+{
+ DNSmsg m;
+ int len;
+ Udphdr *uh = (Udphdr*)buf;
+ RR *rp;
+
+ /* stuff port number into output buffer */
+ memset(uh, 0, sizeof *uh);
+ hnputs(uh->rport, 53);
+
+ /* make request and convert it to output format */
+ rp = rralloc(type);
+ rp->owner = dp;
+ initdnsmsg(&m, rp, flags, reqno);
+ len = convDNS2M(&m, &buf[Udphdrsize], Maxudp);
+ rrfreelist(rp);
+ return len;
+}
+
+void
+freeanswers(DNSmsg *mp)
+{
+ rrfreelistptr(&mp->qd);
+ rrfreelistptr(&mp->an);
+ rrfreelistptr(&mp->ns);
+ rrfreelistptr(&mp->ar);
+ mp->qdcount = mp->ancount = mp->nscount = mp->arcount = 0;
+}
+
+/* timed read of reply. sets srcip. ibuf must be 64K to handle tcp answers. */
+static int
+readnet(Query *qp, int medium, uchar *ibuf, uvlong endms, uchar **replyp,
+ uchar *srcip)
+{
+ int len, fd;
+ long ms;
+ vlong startns = nsec();
+ uchar *reply;
+ uchar lenbuf[2];
+
+ len = -1; /* pessimism */
+ *replyp = nil;
+ memset(srcip, 0, IPaddrlen);
+ ms = endms - NS2MS(startns);
+ if (ms <= 0)
+ return -1; /* taking too long */
+
+ reply = ibuf;
+ alarm(ms);
+ if (medium == Udp)
+ if (qp->udpfd < 0)
+ dnslog("readnet: qp->udpfd closed");
+ else {
+ len = read(qp->udpfd, ibuf, Udphdrsize+Maxudpin);
+ alarm(0);
+ notestats(startns, len < 0, qp->type);
+ if (len >= IPaddrlen)
+ memmove(srcip, ibuf, IPaddrlen);
+ if (len >= Udphdrsize) {
+ len -= Udphdrsize;
+ reply += Udphdrsize;
+ }
+ }
+ else {
+ if (!qp->tcpset)
+ dnslog("readnet: tcp params not set");
+ fd = qp->tcpfd;
+ if (fd < 0)
+ dnslog("readnet: %s: tcp fd unset for dest %I",
+ qp->dp->name, qp->tcpip);
+ else if (readn(fd, lenbuf, 2) != 2) {
+ dnslog("readnet: short read of 2-byte tcp msg size from %I",
+ qp->tcpip);
+ /* probably a time-out */
+ notestats(startns, 1, qp->type);
+ } else {
+ len = lenbuf[0]<<8 | lenbuf[1];
+ if (readn(fd, ibuf, len) != len) {
+ dnslog("readnet: short read of tcp data from %I",
+ qp->tcpip);
+ /* probably a time-out */
+ notestats(startns, 1, qp->type);
+ len = -1;
+ }
+ }
+ memmove(srcip, qp->tcpip, IPaddrlen);
+ }
+ alarm(0);
+ *replyp = reply;
+ return len;
+}
+
+/*
+ * read replies to a request and remember the rrs in the answer(s).
+ * ignore any of the wrong type.
+ * wait at most until endms.
+ */
+static int
+readreply(Query *qp, int medium, ushort req, uchar *ibuf, DNSmsg *mp,
+ uvlong endms)
+{
+ int len;
+ char *err;
+ char tbuf[32];
+ uchar *reply;
+ uchar srcip[IPaddrlen];
+ RR *rp;
+
+ for (; timems() < endms &&
+ (len = readnet(qp, medium, ibuf, endms, &reply, srcip)) >= 0;
+ freeanswers(mp)){
+ /* convert into internal format */
+ memset(mp, 0, sizeof *mp);
+ err = convM2DNS(reply, len, mp, nil);
+ if (mp->flags & Ftrunc) {
+ free(err);
+ freeanswers(mp);
+ /* notify our caller to retry the query via tcp. */
+ return -1;
+ } else if(err){
+ dnslog("readreply: %s: input err, len %d: %s: %I",
+ qp->dp->name, len, err, srcip);
+ free(err);
+ continue;
+ }
+ if(debug)
+ logreply(qp->req->id, srcip, mp);
+
+ /* answering the right question? */
+ if(mp->id != req)
+ dnslog("%d: id %d instead of %d: %I", qp->req->id,
+ mp->id, req, srcip);
+ else if(mp->qd == 0)
+ dnslog("%d: no question RR: %I", qp->req->id, srcip);
+ else if(mp->qd->owner != qp->dp)
+ dnslog("%d: owner %s instead of %s: %I", qp->req->id,
+ mp->qd->owner->name, qp->dp->name, srcip);
+ else if(mp->qd->type != qp->type)
+ dnslog("%d: qp->type %d instead of %d: %I",
+ qp->req->id, mp->qd->type, qp->type, srcip);
+ else {
+ /* remember what request this is in answer to */
+ for(rp = mp->an; rp; rp = rp->next)
+ rp->query = qp->type;
+ return 0;
+ }
+ }
+ if (timems() >= endms) {
+ ; /* query expired */
+ } else if (0) {
+ /* this happens routinely when a read times out */
+ dnslog("readreply: %s type %s: ns %I read error or eof "
+ "(returned %d): %r", qp->dp->name, rrname(qp->type,
+ tbuf, sizeof tbuf), srcip, len);
+ if (medium == Udp)
+ for (rp = qp->nsrp; rp != nil; rp = rp->next)
+ if (rp->type == Tns)
+ dnslog("readreply: %s: query sent to "
+ "ns %s", qp->dp->name,
+ rp->host->name);
+ }
+ memset(mp, 0, sizeof *mp);
+ return -1;
+}
+
+/*
+ * return non-0 if first list includes second list
+ */
+int
+contains(RR *rp1, RR *rp2)
+{
+ RR *trp1, *trp2;
+
+ for(trp2 = rp2; trp2; trp2 = trp2->next){
+ for(trp1 = rp1; trp1; trp1 = trp1->next)
+ if(trp1->type == trp2->type)
+ if(trp1->host == trp2->host)
+ if(trp1->owner == trp2->owner)
+ break;
+ if(trp1 == nil)
+ return 0;
+ }
+ return 1;
+}
+
+
+/*
+ * return multicast version if any
+ */
+int
+ipisbm(uchar *ip)
+{
+ if(isv4(ip)){
+ if (ip[IPv4off] >= 0xe0 && ip[IPv4off] < 0xf0 ||
+ ipcmp(ip, IPv4bcast) == 0)
+ return 4;
+ } else
+ if(ip[0] == 0xff)
+ return 6;
+ return 0;
+}
+
+static int
+queryloops(Query *qp, RR *rp)
+{
+ DN *ns = rp->host;
+
+ /*
+ * looking up a server under itself
+ */
+ if(subsume(rp->owner->name, ns->name))
+ return 1;
+
+ /*
+ * cycle on name servers refering
+ * to each another.
+ */
+ for(; qp; qp = qp->prev)
+ if(qp->dp == ns)
+ return 1;
+
+ return 0;
+}
+
+/*
+ * Get next server type address(es) into qp->dest[nd] and beyond
+ */
+static int
+serveraddrs(Query *qp, int nd, int depth, int type)
+{
+ RR *rp, *arp, *trp;
+ ulong mark;
+ Dest *p;
+
+ if(nd >= Maxdest) /* dest array is full? */
+ return Maxdest;
+
+ /*
+ * look for a server whose address we already know.
+ * if we find one, mark it so we ignore this on
+ * subsequent passes.
+ */
+ mark = 1UL<<type;
+ arp = nil;
+ for(rp = qp->nsrp; rp; rp = rp->next){
+ assert(rp->magic == RRmagic);
+ if(rp->marker & mark)
+ continue;
+ arp = rrlookup(rp->host, type, NOneg);
+ if(arp){
+ rp->marker |= mark;
+ break;
+ }
+ arp = dblookup(rp->host->name, Cin, type, 0, 0);
+ if(arp){
+ rp->marker |= mark;
+ break;
+ }
+ }
+
+ /*
+ * if the cache and database lookup didn't find any new
+ * server addresses, try resolving one via the network.
+ * Mark any we try to resolve so we don't try a second time.
+ */
+ if(arp == nil){
+ for(rp = qp->nsrp; rp; rp = rp->next)
+ if((rp->marker & mark) == 0)
+ if(queryloops(qp, rp))
+ /*
+ * give up as we should have got the address
+ * by higher up nameserver when recursing
+ * down, or will be queried when recursing up.
+ */
+ return nd;
+
+ for(rp = qp->nsrp; rp; rp = rp->next){
+ if(rp->marker & mark)
+ continue;
+ rp->marker |= mark;
+ arp = dnresolve(rp->host->name, Cin, type, qp->req, 0,
+ depth+1, Recurse, 1, 0);
+ rrfreelist(rrremneg(&arp));
+ if(arp)
+ break;
+ }
+ }
+
+ /* use any addresses that we found */
+ for(trp = arp; trp && nd < Maxdest; trp = trp->next){
+ p = &qp->dest[nd];
+ memset(p, 0, sizeof *p);
+ if(parseip(p->a, trp->ip->name) == -1)
+ continue;
+
+ /*
+ * straddling servers can reject all nameservers if they are all
+ * inside, so be sure to list at least one outside ns at
+ * the end of the ns list in /lib/ndb for `dom='.
+ */
+ if (ipisbm(p->a) ||
+ cfg.straddle && !insideaddr(qp->dp->name) && insidens(p->a))
+ continue;
+ p->nx = 0;
+ p->n = nil;
+ p->s = trp->owner;
+ for(rp = qp->nsrp; rp; rp = rp->next){
+ if(rp->host == p->s){
+ p->n = rp;
+ break;
+ }
+ }
+ p->code = Rtimeout;
+ nd++;
+ }
+ rrfreelist(arp);
+ return nd;
+}
+
+/*
+ * cache negative responses
+ */
+static void
+cacheneg(DN *dp, int type, int rcode, RR *soarr)
+{
+ RR *rp;
+ DN *soaowner;
+ ulong ttl;
+
+ qlock(&stats);
+ stats.negcached++;
+ qunlock(&stats);
+
+ /* no cache time specified, don't make anything up */
+ if(soarr != nil){
+ if(soarr->next != nil)
+ rrfreelistptr(&soarr->next);
+ soaowner = soarr->owner;
+ } else
+ soaowner = nil;
+
+ /* the attach can cause soarr to be freed so mine it now */
+ if(soarr != nil && soarr->soa != nil)
+ ttl = soarr->soa->minttl;
+ else
+ ttl = 5*Min;
+
+ /* add soa and negative RR to the database */
+ rrattach(soarr, Authoritative);
+
+ rp = rralloc(type);
+ rp->owner = dp;
+ rp->negative = 1;
+ rp->negsoaowner = soaowner;
+ rp->negrcode = rcode;
+ rp->ttl = ttl;
+ rrattach(rp, Authoritative);
+}
+
+static int
+setdestoutns(Dest *p, int n)
+{
+ memset(p, 0, sizeof *p);
+ if (outsidensip(n, p->a) < 0){
+ if (n == 0)
+ dnslog("[%d] no outside-ns in ndb", getpid());
+ return -1;
+ }
+ p->s = dnlookup("outside-ns-ips", Cin, 1);
+ return 0;
+}
+
+/*
+ * issue query via UDP or TCP as appropriate.
+ * for TCP, returns with qp->tcpip set from udppkt header.
+ */
+static int
+mydnsquery(Query *qp, int medium, uchar *udppkt, int len)
+{
+ int rv, nfd;
+ char conndir[40], addr[128];
+ uchar belen[2];
+ NetConnInfo *nci;
+
+ rv = -1;
+ if (myip(udppkt))
+ return rv;
+ switch (medium) {
+ case Udp:
+ nfd = dup(qp->udpfd, -1);
+ if (nfd < 0) {
+ warning("mydnsquery: qp->udpfd %d: %r", qp->udpfd);
+ close(qp->udpfd); /* ensure it's closed */
+ qp->udpfd = -1; /* poison it */
+ break;
+ }
+ close(nfd);
+
+ if (qp->udpfd < 0)
+ dnslog("mydnsquery: qp->udpfd %d closed", qp->udpfd);
+ else {
+ if (write(qp->udpfd, udppkt, len+Udphdrsize) !=
+ len+Udphdrsize)
+ warning("sending udp msg: %r");
+ else {
+ qlock(&stats);
+ stats.qsent++;
+ qunlock(&stats);
+ rv = 0;
+ }
+ }
+ break;
+ case Tcp:
+ /* send via TCP & keep fd around for reply */
+ memmove(qp->tcpip, udppkt, sizeof qp->tcpip);
+ snprint(addr, sizeof addr, "%s/tcp!%I!dns",
+ (mntpt && *mntpt) ? mntpt : "/net", udppkt);
+ alarm(10*1000);
+ qp->tcpfd = dial(addr, nil, conndir, &qp->tcpctlfd);
+ alarm(0);
+ if (qp->tcpfd < 0) {
+ dnslog("can't dial %s: %r", addr);
+ break;
+ }
+ nci = getnetconninfo(conndir, qp->tcpfd);
+ if (nci) {
+ parseip(qp->tcpip, nci->rsys);
+ freenetconninfo(nci);
+ } else
+ dnslog("mydnsquery: getnetconninfo failed");
+ qp->tcpset = 1;
+
+ belen[0] = len >> 8;
+ belen[1] = len;
+ if (write(qp->tcpfd, belen, 2) != 2 ||
+ write(qp->tcpfd, udppkt + Udphdrsize, len) != len)
+ warning("sending tcp msg: %r");
+ else
+ rv = 0;
+ break;
+ }
+ return rv;
+}
+
+/*
+ * send query to all UDP destinations or one TCP destination,
+ * taken from obuf (udp packet) header
+ */
+static int
+xmitquery(Query *qp, int medium, int depth, uchar *obuf, int inns, int len)
+{
+ int n;
+ char buf[32];
+ Dest *p;
+
+ if(timems() >= qp->req->aborttime)
+ return -1;
+
+ /*
+ * if we send tcp query, we just take the dest ip address from
+ * the udp header placed there by tcpquery().
+ */
+ if (medium == Tcp) {
+ procsetname("tcp %sside query for %s %s", (inns? "in": "out"),
+ qp->dp->name, rrname(qp->type, buf, sizeof buf));
+ if(mydnsquery(qp, medium, obuf, len) < 0) /* sets qp->tcpip from obuf */
+ return -1;
+ if(debug)
+ logsend(qp->req->id, depth, qp->tcpip, "", qp->dp->name,
+ qp->type);
+ return 0;
+ }
+
+ /*
+ * get a nameserver address if we need one.
+ * we're to transmit to more destinations than we currently have,
+ * so get another.
+ */
+ p = qp->dest;
+ n = qp->curdest - p;
+ if (qp->ndest > n) {
+ /* populates qp->dest with v4 and v6 addresses. */
+ n = serveraddrs(qp, n, depth, Ta);
+ n = serveraddrs(qp, n, depth, Taaaa);
+ if (n == 0 && cfg.straddle && cfg.inside) {
+ /* get ips of "outside-ns-ips" */
+ while(n < Maxdest){
+ if (setdestoutns(&qp->dest[n], n) < 0)
+ break;
+ n++;
+ }
+ if(n == 0)
+ dnslog("xmitquery: %s: no outside-ns nameservers",
+ qp->dp->name);
+ }
+ qp->curdest = &qp->dest[n];
+ }
+
+ for(n = 0; p < &qp->dest[qp->ndest] && p < qp->curdest; p++){
+ /* skip destinations we've finished with */
+ if(p->nx >= Maxtrans)
+ continue;
+ /* exponential backoff of requests */
+ if((1<<p->nx) > qp->ndest)
+ continue;
+
+ if(ipcmp(p->a, IPnoaddr) == 0)
+ continue; /* mistake */
+
+ procsetname("udp %sside query to %I/%s %s %s",
+ (inns? "in": "out"), p->a, p->s->name,
+ qp->dp->name, rrname(qp->type, buf, sizeof buf));
+ if(debug)
+ logsend(qp->req->id, depth, p->a, p->s->name,
+ qp->dp->name, qp->type);
+
+ /* fill in UDP destination addr & send it */
+ memmove(obuf, p->a, sizeof p->a);
+ if(mydnsquery(qp, medium, obuf, len) == 0)
+ n++;
+ p->nx++;
+ }
+
+ return n == 0 ? -1 : 0;
+}
+
+/* is mp a cachable negative response (with Rname set)? */
+static int
+isnegrname(DNSmsg *mp)
+{
+ /* TODO: could add || cfg.justforw to RHS of && */
+ return mp->an == nil && (mp->flags & Rmask) == Rname;
+}
+
+static int
+filterhints(RR *rp, void *arg)
+{
+ RR *nsrp;
+
+ if(rp->type != Ta && rp->type != Taaaa)
+ return 0;
+
+ for(nsrp = arg; nsrp; nsrp = nsrp->next)
+ if(nsrp->type == Tns && rp->owner == nsrp->host)
+ return 1;
+
+ return 0;
+}
+
+static int
+filterauth(RR *rp, void *arg)
+{
+ Dest *dest;
+ RR *nsrp;
+
+ dest = arg;
+ nsrp = dest->n;
+ if(nsrp == nil)
+ return 0;
+
+ if(rp->type == Tsoa && rp->owner != nsrp->owner
+ && !subsume(nsrp->owner->name, rp->owner->name)
+ && strncmp(nsrp->owner->name, "local#", 6) != 0)
+ return 1;
+
+ if(rp->type != Tns)
+ return 0;
+
+ if(rp->owner != nsrp->owner
+ && !subsume(nsrp->owner->name, rp->owner->name)
+ && strncmp(nsrp->owner->name, "local#", 6) != 0)
+ return 1;
+
+ return baddelegation(rp, nsrp, dest->a);
+}
+
+static void
+reportandfree(RR *l, char *note, Dest *p)
+{
+ RR *rp;
+
+ while(rp = l){
+ l = l->next;
+ rp->next = nil;
+ if(debug)
+ dnslog("ignoring %s from %I/%s: %R",
+ note, p->a, p->s->name, rp);
+ rrfree(rp);
+ }
+}
+
+/* returns Answerr (-1) on errors, else number of answers, which can be zero. */
+static int
+procansw(Query *qp, DNSmsg *mp, int depth, Dest *p)
+{
+ int rv;
+ char buf[32];
+ DN *ndp;
+ Query nq;
+ RR *tp, *soarr;
+
+ if(mp->an == nil)
+ stats.negans++;
+
+ /* ignore any error replies */
+ switch(mp->flags & Rmask){
+ case Rrefused:
+ case Rserver:
+ stats.negserver++;
+ freeanswers(mp);
+ p->code = Rserver;
+ return Answerr;
+ }
+
+ /* ignore any bad delegations */
+ if((tp = rrremfilter(&mp->ns, filterauth, p)) != 0)
+ reportandfree(tp, "bad delegation", p);
+
+ /* remove any soa's from the authority section */
+ soarr = rrremtype(&mp->ns, Tsoa);
+
+ /* only nameservers remaining */
+ if((tp = rrremtype(&mp->ns, Tns)) != 0){
+ reportandfree(mp->ns, "non-nameserver", p);
+ mp->ns = tp;
+ }
+
+ /* remove answers not related to the question. */
+ if((tp = rrremowner(&mp->an, qp->dp)) != 0){
+ reportandfree(mp->an, "wrong subject answer", p);
+ mp->an = tp;
+ }
+ if(qp->type != Tall){
+ if((tp = rrremtype(&mp->an, qp->type)) != 0){
+ reportandfree(mp->an, "wrong type answer", p);
+ mp->an = tp;
+ }
+ }
+
+ /* incorporate answers */
+ unique(mp->an);
+ unique(mp->ns);
+ unique(mp->ar);
+
+ if(mp->an){
+ /*
+ * only use cname answer when returned. some dns servers
+ * attach (potential) spam hint address records which poisons
+ * the cache.
+ */
+ if((tp = rrremtype(&mp->an, Tcname)) != 0){
+ reportandfree(mp->an, "ip in cname answer", p);
+ mp->an = tp;
+ }
+ rrattach(mp->an, (mp->flags & Fauth) != 0);
+ }
+ if(mp->ar){
+ /* restrict hints to address rr's for nameservers only */
+ if((tp = rrremfilter(&mp->ar, filterhints, mp->ns)) != 0){
+ reportandfree(mp->ar, "hint", p);
+ mp->ar = tp;
+ }
+ rrattach(mp->ar, Notauthoritative);
+ }
+ if(mp->ns && !cfg.justforw){
+ ndp = mp->ns->owner;
+ rrattach(mp->ns, Notauthoritative);
+ } else {
+ ndp = nil;
+ rrfreelistptr(&mp->ns);
+ mp->nscount = 0;
+ }
+
+ /* free the question */
+ if(mp->qd) {
+ rrfreelistptr(&mp->qd);
+ mp->qdcount = 0;
+ }
+
+ /*
+ * Any reply from an authoritative server
+ * that does not provide more nameservers,
+ * or a positive reply terminates the search.
+ * A negative response now also terminates the search.
+ */
+ if(mp->an || (mp->flags & Fauth) && mp->ns == nil){
+ if(isnegrname(mp))
+ qp->dp->respcode = Rname;
+ else
+ qp->dp->respcode = Rok;
+
+ /*
+ * cache any negative responses, free soarr.
+ * negative responses need not be authoritative:
+ * they can legitimately come from a cache.
+ */
+ if( /* (mp->flags & Fauth) && */ mp->an == nil)
+ cacheneg(qp->dp, qp->type, (mp->flags & Rmask), soarr);
+ else
+ rrfreelist(soarr);
+ return 1;
+ } else if (isnegrname(mp)) {
+ qp->dp->respcode = Rname;
+ /*
+ * cache negative response.
+ * negative responses need not be authoritative:
+ * they can legitimately come from a cache.
+ */
+ cacheneg(qp->dp, qp->type, (mp->flags & Rmask), soarr);
+ return 1;
+ }
+ stats.negnorname++;
+ rrfreelist(soarr);
+
+ /*
+ * if we've been given better name servers, recurse.
+ * if we're a pure resolver, don't recurse, we have
+ * to forward to a fixed set of named servers.
+ */
+ if(mp->ns == nil || cfg.resolver && cfg.justforw)
+ return Answnone;
+ tp = rrlookup(ndp, Tns, NOneg);
+ if(contains(qp->nsrp, tp)){
+ rrfreelist(tp);
+ return Answnone;
+ }
+ procsetname("recursive query for %s %s", qp->dp->name,
+ rrname(qp->type, buf, sizeof buf));
+
+ queryinit(&nq, qp->dp, qp->type, qp->req);
+ rv = netqueryns(&nq, depth+1, tp);
+ querydestroy(&nq);
+
+ return rv;
+}
+
+/*
+ * send a query via tcp to a single address (from ibuf's udp header)
+ * and read the answer(s) into mp->an.
+ */
+static int
+tcpquery(Query *qp, DNSmsg *mp, int depth, uchar *ibuf, uchar *obuf, int len,
+ ulong waitms, int inns, ushort req)
+{
+ int rv = 0;
+ uvlong endms;
+
+ endms = timems() + waitms;
+ if(endms > qp->req->aborttime)
+ endms = qp->req->aborttime;
+
+ if (0)
+ dnslog("%s: udp reply truncated; retrying query via tcp to %I",
+ qp->dp->name, qp->tcpip);
+
+ memmove(obuf, ibuf, IPaddrlen); /* send back to respondent */
+ memset(mp, 0, sizeof *mp);
+ if (xmitquery(qp, Tcp, depth, obuf, inns, len) < 0 ||
+ readreply(qp, Tcp, req, ibuf, mp, endms) < 0)
+ rv = -1;
+ if (qp->tcpfd >= 0) {
+ hangup(qp->tcpctlfd);
+ close(qp->tcpctlfd);
+ close(qp->tcpfd);
+ }
+ qp->tcpfd = qp->tcpctlfd = -1;
+
+ return rv;
+}
+
+/*
+ * query name servers. fill in obuf with on-the-wire representation of a
+ * DNSmsg derived from qp. if the name server returns a pointer to another
+ * name server, recurse.
+ */
+static int
+queryns(Query *qp, int depth, uchar *ibuf, uchar *obuf, ulong waitms, int inns)
+{
+ int ndest, len, replywaits, rv, flag;
+ ushort req;
+ uvlong endms;
+ char buf[32];
+ uchar srcip[IPaddrlen];
+ Dest *p, *np, dest[Maxdest];
+
+ req = rand();
+
+ /* request recursion only for local dns servers */
+ flag = Oquery;
+ if(strncmp(qp->nsrp->owner->name, "local#", 6) == 0)
+ flag |= Frecurse;
+
+ /* pack request into a udp message */
+ len = mkreq(qp->dp, qp->type, obuf, flag, req);
+
+ /* no server addresses yet */
+ memset(dest, 0, sizeof dest);
+ qp->curdest = qp->dest = dest;
+
+ /*
+ * transmit udp requests and wait for answers.
+ * at most Maxtrans attempts to each address.
+ * each cycle send one more message than the previous.
+ * retry a query via tcp if its response is truncated.
+ */
+ for(ndest = 2; ndest < Maxdest; ndest += 2){
+ qp->ndest = ndest;
+ qp->tcpset = 0;
+ if (xmitquery(qp, Udp, depth, obuf, inns, len) < 0)
+ break;
+
+ endms = timems() + waitms;
+ if(endms > qp->req->aborttime)
+ endms = qp->req->aborttime;
+
+ for(replywaits = 0; replywaits < ndest; replywaits++){
+ DNSmsg m;
+
+ procsetname("reading %sside reply from %I: %s %s from %s",
+ (inns? "in": "out"), obuf, qp->dp->name,
+ rrname(qp->type, buf, sizeof buf), qp->req->from);
+
+ /* read udp answer into m */
+ if (readreply(qp, Udp, req, ibuf, &m, endms) >= 0)
+ memmove(srcip, ibuf, IPaddrlen);
+ else if (!(m.flags & Ftrunc)) {
+ freeanswers(&m);
+ break; /* timed out on this dest */
+ } else {
+ /* whoops, it was truncated! ask again via tcp */
+ freeanswers(&m);
+ rv = tcpquery(qp, &m, depth, ibuf, obuf, len,
+ waitms, inns, req); /* answer in m */
+ if (rv < 0) {
+ freeanswers(&m);
+ break; /* failed via tcp too */
+ }
+ memmove(srcip, qp->tcpip, IPaddrlen);
+ }
+
+ /* find responder */
+ if(debug)
+ dnslog("queryns got reply from %I", srcip);
+ for(p = qp->dest; p < qp->curdest; p++)
+ if(ipcmp(p->a, srcip) == 0)
+ break;
+ if(p >= qp->curdest){
+ dnslog("response from %I but no destination", srcip);
+ continue;
+ }
+
+ /* remove all addrs of responding server from list */
+ for(np = qp->dest; np < qp->curdest; np++)
+ if(np->s == p->s)
+ np->nx = Maxtrans;
+
+ /* free or incorporate RRs in m */
+ rv = procansw(qp, &m, depth, p);
+ if (rv > Answnone) {
+ qp->dest = qp->curdest = nil; /* prevent accidents */
+ return rv;
+ }
+ }
+ }
+
+ /* if all servers returned failure, propagate it */
+ qp->dp->respcode = Rserver;
+ for(p = dest; p < qp->curdest; p++)
+ if(p->code != Rserver)
+ qp->dp->respcode = Rok;
+
+// if (qp->dp->respcode)
+// dnslog("queryns setting Rserver for %s", qp->dp->name);
+
+ qp->dest = qp->curdest = nil; /* prevent accidents */
+ return Answnone;
+}
+
+/*
+ * in principle we could use a single descriptor for a udp port
+ * to send all queries and receive all the answers to them,
+ * but we'd have to sort out the answers by dns-query id.
+ */
+static int
+udpquery(Query *qp, char *mntpt, int depth, int patient, int inns)
+{
+ int fd, rv;
+ uchar *obuf, *ibuf;
+
+ /* use alloced buffers rather than ones from the stack */
+ ibuf = emalloc(64*1024); /* max. tcp reply size */
+ obuf = emalloc(Maxudp+Udphdrsize);
+
+ fd = udpport(mntpt);
+ if (fd < 0) {
+ dnslog("can't get udpport for %s query of name %s: %r",
+ mntpt, qp->dp->name);
+ rv = -1;
+ goto Out;
+ }
+ qp->udpfd = fd;
+ rv = queryns(qp, depth, ibuf, obuf, 500UL<<(patient != 0), inns);
+ qp->udpfd = -1;
+ close(fd);
+
+Out:
+ free(obuf);
+ free(ibuf);
+ return rv;
+}
+
+/*
+ * look up (qp->dp->name, qp->type) rr in dns,
+ * using nameservers in qp->nsrp.
+ */
+static int
+netquery(Query *qp, int depth)
+{
+ int rv, triedin, inname;
+ RR *rp;
+
+ rv = Answnone; /* pessimism */
+ if(depth > 12) /* in a recursive loop? */
+ return Answnone;
+
+ slave(qp->req);
+
+ /*
+ * slave might have forked. if so, the parent process longjmped to
+ * req->mret; we're usually the child slave, but if there are too
+ * many children already, we're still the same process. under no
+ * circumstances block the 9p loop.
+ */
+ if(!qp->req->isslave && strcmp(qp->req->from, "9p") == 0)
+ return Answnone;
+
+ procsetname("netquery: %s", qp->dp->name);
+
+ /* prepare server RR's for incremental lookup */
+ for(rp = qp->nsrp; rp; rp = rp->next)
+ rp->marker = 0;
+
+ triedin = 0;
+
+ /*
+ * normal resolvers and servers will just use mntpt for all addresses,
+ * even on the outside. straddling servers will use mntpt (/net)
+ * for inside addresses and /net.alt for outside addresses,
+ * thus bypassing other inside nameservers.
+ */
+ inname = insideaddr(qp->dp->name);
+ if (!cfg.straddle || inname) {
+ rv = udpquery(qp, mntpt, depth, Hurry, (cfg.inside? Inns: Outns));
+ triedin = 1;
+ }
+
+ /*
+ * if we're still looking, are inside, and have an outside domain,
+ * try it on our outside interface, if any.
+ */
+ if (rv == Answnone && cfg.inside && !inname) {
+ if (triedin)
+ dnslog(
+ "[%d] netquery: internal nameservers failed for %s; trying external",
+ getpid(), qp->dp->name);
+
+ /* prepare server RR's for incremental lookup */
+ for(rp = qp->nsrp; rp; rp = rp->next)
+ rp->marker = 0;
+
+ rv = udpquery(qp, "/net.alt", depth, Patient, Outns);
+ }
+
+ return rv;
+}
+
+int
+seerootns(void)
+{
+ int rv;
+ char root[] = "";
+ Request req;
+ RR *rr, *nsrp;
+ Query q;
+
+ memset(&req, 0, sizeof req);
+ req.isslave = 1;
+ req.aborttime = timems() + Maxreqtm;
+ req.from = "internal";
+ queryinit(&q, dnlookup(root, Cin, 1), Tns, &req);
+ nsrp = randomize(dblookup(root, Cin, Tns, 0, 0));
+ for (rr = nsrp; rr != nil; rr = rr->next)
+ dnslog("seerootns query nsrp: %R", rr);
+ rv = netqueryns(&q, 0, nsrp); /* lookup ". ns" using nsrp */
+ querydestroy(&q);
+ return rv;
+}
--- /dev/null
+++ b/dns.c
@@ -1,0 +1,950 @@
+#include <u.h>
+#include <libc.h>
+#include <auth.h>
+#include <fcall.h>
+#include <bio.h>
+#include <ip.h>
+#include "dns.h"
+
+enum
+{
+ Maxrequest= 1024,
+ Maxreply= 8192, /* was 512 */
+ Maxrrr= 32, /* was 16 */
+ Maxfdata= 8192,
+
+ Defmaxage= 60*60, /* default domain name max. age */
+
+ Qdir= 0,
+ Qdns= 1,
+};
+
+typedef struct Mfile Mfile;
+typedef struct Job Job;
+typedef struct Network Network;
+
+int vers; /* incremented each clone/attach */
+
+/* holds data to be returned via read of /net/dns, perhaps multiple reads */
+struct Mfile
+{
+ Mfile *next; /* next free mfile */
+
+ char *user;
+ Qid qid;
+ int fid;
+
+ int type; /* reply type */
+ char reply[Maxreply];
+ ushort rr[Maxrrr]; /* offset of rr's */
+ ushort nrr; /* number of rr's */
+};
+
+/*
+ * active local requests
+ */
+struct Job
+{
+ Job *next;
+ int flushed;
+ Fcall request;
+ Fcall reply;
+};
+Lock joblock;
+Job *joblist;
+
+struct {
+ Lock;
+ Mfile *inuse; /* active mfile's */
+} mfalloc;
+
+Cfg cfg;
+int debug;
+int maxage = Defmaxage;
+int mfd[2];
+int needrefresh;
+ulong now;
+vlong nowns;
+int sendnotifies;
+char *trace;
+int traceactivity;
+char *zonerefreshprogram;
+
+char *logfile = "dns"; /* or "dns.test" */
+char *dbfile;
+char *dnsuser;
+char mntpt[Maxpath];
+
+int addforwtarg(char *);
+int fillreply(Mfile*, int);
+void freejob(Job*);
+void io(void);
+void mountinit(char*, char*);
+Job* newjob(void);
+void rattach(Job*, Mfile*);
+void rauth(Job*);
+void rclunk(Job*, Mfile*);
+void rcreate(Job*, Mfile*);
+void rflush(Job*);
+void ropen(Job*, Mfile*);
+void rread(Job*, Mfile*);
+void rremove(Job*, Mfile*);
+void rstat(Job*, Mfile*);
+void rversion(Job*);
+char* rwalk(Job*, Mfile*);
+void rwrite(Job*, Mfile*, Request*);
+void rwstat(Job*, Mfile*);
+void sendmsg(Job*, char*);
+void setext(char*, int, char*);
+
+static char *lookupquery(Job*, Mfile*, Request*, char*, char*, int, int);
+static char *respond(Job*, Mfile*, RR*, char*, int, int);
+
+void
+usage(void)
+{
+ fprint(2, "usage: %s [-FnorRs] [-a maxage] [-f ndb-file] [-N target] "
+ "[-T forwip] [-x netmtpt] [-z refreshprog]\n", argv0);
+ exits("usage");
+}
+
+void
+main(int argc, char *argv[])
+{
+ char servefile[Maxpath], ext[Maxpath];
+ Dir *dir;
+
+ setnetmtpt(mntpt, sizeof mntpt, nil);
+ ext[0] = 0;
+ ARGBEGIN{
+ case 'a':
+ maxage = atol(EARGF(usage()));
+ if (maxage <= 0)
+ maxage = Defmaxage;
+ break;
+ case 'd':
+ debug = 1;
+ traceactivity = 1;
+ break;
+ case 'f':
+ dbfile = EARGF(usage());
+ break;
+ case 'F':
+ cfg.justforw = cfg.resolver = 1;
+ break;
+ case 'n':
+ sendnotifies = 1;
+ break;
+ case 'N':
+ target = atol(EARGF(usage()));
+ if (target < 1000)
+ target = 1000;
+ break;
+ case 'o':
+ cfg.straddle = 1; /* straddle inside & outside networks */
+ break;
+ case 'r':
+ cfg.resolver = 1;
+ break;
+ case 'R':
+ norecursion = 1;
+ break;
+ case 's':
+ cfg.serve = 1; /* serve network */
+ cfg.cachedb = 1;
+ break;
+ case 'T':
+ addforwtarg(EARGF(usage()));
+ break;
+ case 'x':
+ setnetmtpt(mntpt, sizeof mntpt, EARGF(usage()));
+ setext(ext, sizeof ext, mntpt);
+ break;
+ case 'z':
+ zonerefreshprogram = EARGF(usage());
+ break;
+ default:
+ usage();
+ break;
+ }ARGEND
+ if(argc != 0)
+ usage();
+
+ rfork(RFREND|RFNOTEG);
+
+ cfg.inside = (*mntpt == '\0' || strcmp(mntpt, "/net") == 0);
+
+ /* start syslog before we fork */
+ fmtinstall('F', fcallfmt);
+ dninit();
+ dnslog("starting %s%sdns %s%s%son %s",
+ (cfg.straddle? "straddling ": ""),
+ (cfg.cachedb? "caching ": ""),
+ (cfg.serve? "udp server ": ""),
+ (cfg.justforw? "forwarding-only ": ""),
+ (cfg.resolver? "resolver ": ""), mntpt);
+
+ opendatabase();
+ now = time(nil); /* open time files before we fork */
+ nowns = nsec();
+ dnsuser = estrdup(getuser());
+
+ snprint(servefile, sizeof servefile, "#s/dns%s", ext);
+ dir = dirstat(servefile);
+ if (dir)
+ sysfatal("%s exists; another dns instance is running",
+ servefile);
+ free(dir);
+ mountinit(servefile, mntpt); /* forks, parent exits */
+
+ srand(truerand());
+ db2cache(1);
+
+ if (cfg.straddle && !seerootns())
+ dnslog("straddle server misconfigured; can't see root name servers");
+
+ if(cfg.serve)
+ dnudpserver(mntpt);
+ if(sendnotifies)
+ notifyproc();
+
+ io();
+ _exits(0);
+}
+
+/*
+ * if a mount point is specified, set the cs extension to be the mount point
+ * with '_'s replacing '/'s
+ */
+void
+setext(char *ext, int n, char *p)
+{
+ int i, c;
+
+ n--;
+ for(i = 0; i < n; i++){
+ c = p[i];
+ if(c == 0)
+ break;
+ if(c == '/')
+ c = '_';
+ ext[i] = c;
+ }
+ ext[i] = 0;
+}
+
+void
+mountinit(char *service, char *mntpt)
+{
+ int f;
+ int p[2];
+ char buf[32];
+
+ if(pipe(p) < 0)
+ sysfatal("pipe failed: %r");
+
+ /*
+ * make a /srv/dns
+ */
+ if((f = create(service, OWRITE|ORCLOSE, 0666)) < 0)
+ sysfatal("create %s failed: %r", service);
+ snprint(buf, sizeof buf, "%d", p[1]);
+ if(write(f, buf, strlen(buf)) != strlen(buf))
+ sysfatal("write %s failed: %r", service);
+
+ /* copy namespace to avoid a deadlock */
+ switch(rfork(RFFDG|RFPROC|RFNAMEG)){
+ case 0: /* child: start main proc */
+ close(p[1]);
+ procsetname("%s", mntpt);
+ break;
+ case -1:
+ sysfatal("fork failed: %r");
+ default: /* parent: make /srv/dns, mount it, exit */
+ close(p[0]);
+
+ /*
+ * put ourselves into the file system
+ */
+ if(mount(p[1], -1, mntpt, MAFTER, "") == -1)
+ fprint(2, "dns mount failed: %r\n");
+ _exits(0);
+ }
+ mfd[0] = mfd[1] = p[0];
+}
+
+Mfile*
+newfid(int fid, int needunused)
+{
+ Mfile *mf;
+
+ lock(&mfalloc);
+ for(mf = mfalloc.inuse; mf != nil; mf = mf->next)
+ if(mf->fid == fid){
+ unlock(&mfalloc);
+ if(needunused)
+ return nil;
+ return mf;
+ }
+ mf = emalloc(sizeof(*mf));
+ mf->fid = fid;
+ mf->qid.vers = vers;
+ mf->qid.type = QTDIR;
+ mf->qid.path = 0LL;
+ mf->user = estrdup("none");
+ mf->next = mfalloc.inuse;
+ mfalloc.inuse = mf;
+ unlock(&mfalloc);
+ return mf;
+}
+
+void
+freefid(Mfile *mf)
+{
+ Mfile **l;
+
+ lock(&mfalloc);
+ for(l = &mfalloc.inuse; *l != nil; l = &(*l)->next)
+ if(*l == mf){
+ *l = mf->next;
+ free(mf->user);
+ memset(mf, 0, sizeof *mf); /* cause trouble */
+ free(mf);
+ unlock(&mfalloc);
+ return;
+ }
+ unlock(&mfalloc);
+ sysfatal("freeing unused fid");
+}
+
+Mfile*
+copyfid(Mfile *mf, int fid)
+{
+ Mfile *nmf;
+
+ nmf = newfid(fid, 1);
+ if(nmf == nil)
+ return nil;
+ nmf->fid = fid;
+ free(nmf->user); /* estrdup("none") */
+ nmf->user = estrdup(mf->user);
+ nmf->qid.type = mf->qid.type;
+ nmf->qid.path = mf->qid.path;
+ nmf->qid.vers = vers++;
+ return nmf;
+}
+
+Job*
+newjob(void)
+{
+ Job *job;
+
+ job = emalloc(sizeof *job);
+ lock(&joblock);
+ job->next = joblist;
+ joblist = job;
+ job->request.tag = -1;
+ unlock(&joblock);
+ return job;
+}
+
+void
+freejob(Job *job)
+{
+ Job **l;
+
+ lock(&joblock);
+ for(l = &joblist; *l; l = &(*l)->next)
+ if(*l == job){
+ *l = job->next;
+ memset(job, 0, sizeof *job); /* cause trouble */
+ free(job);
+ break;
+ }
+ unlock(&joblock);
+}
+
+void
+flushjob(int tag)
+{
+ Job *job;
+
+ lock(&joblock);
+ for(job = joblist; job; job = job->next)
+ if(job->request.tag == tag && job->request.type != Tflush){
+ job->flushed = 1;
+ break;
+ }
+ unlock(&joblock);
+}
+
+void
+io(void)
+{
+ volatile long n;
+ volatile uchar mdata[IOHDRSZ + Maxfdata];
+ Job *volatile job;
+ Mfile *volatile mf;
+ volatile Request req;
+
+ memset(&req, 0, sizeof req);
+ /*
+ * a slave process is sometimes forked to wait for replies from other
+ * servers. The master process returns immediately via a longjmp
+ * through 'mret'.
+ */
+ if(setjmp(req.mret))
+ putactivity(0);
+ req.isslave = 0;
+ while((n = read9pmsg(mfd[0], mdata, sizeof mdata)) != 0){
+ if(n < 0){
+ dnslog("error reading 9P from %s: %r", mntpt);
+ break;
+ }
+
+ stats.qrecvd9prpc++;
+ job = newjob();
+ if(convM2S(mdata, n, &job->request) != n){
+ dnslog("format error %ux %ux %ux %ux %ux",
+ mdata[0], mdata[1], mdata[2], mdata[3], mdata[4]);
+ freejob(job);
+ break;
+ }
+ mf = newfid(job->request.fid, 0);
+ if(debug)
+ dnslog("%F", &job->request);
+
+ getactivity(&req, 0);
+ req.aborttime = timems() + Maxreqtm;
+ req.from = "9p";
+
+ switch(job->request.type){
+ default:
+ warning("unknown request type %d", job->request.type);
+ break;
+ case Tversion:
+ rversion(job);
+ break;
+ case Tauth:
+ rauth(job);
+ break;
+ case Tflush:
+ rflush(job);
+ break;
+ case Tattach:
+ rattach(job, mf);
+ break;
+ case Twalk:
+ rwalk(job, mf);
+ break;
+ case Topen:
+ ropen(job, mf);
+ break;
+ case Tcreate:
+ rcreate(job, mf);
+ break;
+ case Tread:
+ rread(job, mf);
+ break;
+ case Twrite:
+ /* &req is handed to dnresolve() */
+ rwrite(job, mf, &req);
+ break;
+ case Tclunk:
+ rclunk(job, mf);
+ break;
+ case Tremove:
+ rremove(job, mf);
+ break;
+ case Tstat:
+ rstat(job, mf);
+ break;
+ case Twstat:
+ rwstat(job, mf);
+ break;
+ }
+
+ freejob(job);
+
+ /*
+ * slave processes die after replying
+ */
+ if(req.isslave){
+ putactivity(0);
+ _exits(0);
+ }
+
+ putactivity(0);
+ }
+}
+
+void
+rversion(Job *job)
+{
+ if(job->request.msize > IOHDRSZ + Maxfdata)
+ job->reply.msize = IOHDRSZ + Maxfdata;
+ else
+ job->reply.msize = job->request.msize;
+ job->reply.version = "9P2000";
+ if(strncmp(job->request.version, "9P", 2) != 0)
+ job->reply.version = "unknown";
+ sendmsg(job, nil);
+}
+
+void
+rauth(Job *job)
+{
+ sendmsg(job, "dns: authentication not required");
+}
+
+/*
+ * don't flush till all the slaves are done
+ */
+void
+rflush(Job *job)
+{
+ flushjob(job->request.oldtag);
+ sendmsg(job, 0);
+}
+
+void
+rattach(Job *job, Mfile *mf)
+{
+ if(mf->user != nil)
+ free(mf->user);
+ mf->user = estrdup(job->request.uname);
+ mf->qid.vers = vers++;
+ mf->qid.type = QTDIR;
+ mf->qid.path = 0LL;
+ job->reply.qid = mf->qid;
+ sendmsg(job, 0);
+}
+
+char*
+rwalk(Job *job, Mfile *mf)
+{
+ int i, nelems;
+ char *err;
+ char **elems;
+ Mfile *nmf;
+ Qid qid;
+
+ err = 0;
+ nmf = nil;
+ elems = job->request.wname;
+ nelems = job->request.nwname;
+ job->reply.nwqid = 0;
+
+ if(job->request.newfid != job->request.fid){
+ /* clone fid */
+ nmf = copyfid(mf, job->request.newfid);
+ if(nmf == nil){
+ err = "clone bad newfid";
+ goto send;
+ }
+ mf = nmf;
+ }
+ /* else nmf will be nil */
+
+ qid = mf->qid;
+ if(nelems > 0)
+ /* walk fid */
+ for(i=0; i<nelems && i<MAXWELEM; i++){
+ if((qid.type & QTDIR) == 0){
+ err = "not a directory";
+ break;
+ }
+ if (strcmp(elems[i], "..") == 0 ||
+ strcmp(elems[i], ".") == 0){
+ qid.type = QTDIR;
+ qid.path = Qdir;
+Found:
+ job->reply.wqid[i] = qid;
+ job->reply.nwqid++;
+ continue;
+ }
+ if(strcmp(elems[i], "dns") == 0){
+ qid.type = QTFILE;
+ qid.path = Qdns;
+ goto Found;
+ }
+ err = "file does not exist";
+ break;
+ }
+
+send:
+ if(nmf != nil && (err!=nil || job->reply.nwqid<nelems))
+ freefid(nmf);
+ if(err == nil)
+ mf->qid = qid;
+ sendmsg(job, err);
+ return err;
+}
+
+void
+ropen(Job *job, Mfile *mf)
+{
+ int mode;
+ char *err;
+
+ err = 0;
+ mode = job->request.mode;
+ if(mf->qid.type & QTDIR)
+ if(mode)
+ err = "permission denied";
+ job->reply.qid = mf->qid;
+ job->reply.iounit = 0;
+ sendmsg(job, err);
+}
+
+void
+rcreate(Job *job, Mfile *mf)
+{
+ USED(mf);
+ sendmsg(job, "creation permission denied");
+}
+
+void
+rread(Job *job, Mfile *mf)
+{
+ int i, n;
+ long clock;
+ ulong cnt;
+ vlong off;
+ char *err;
+ uchar buf[Maxfdata];
+ Dir dir;
+
+ n = 0;
+ err = nil;
+ off = job->request.offset;
+ cnt = job->request.count;
+ *buf = '\0';
+ job->reply.data = (char*)buf;
+ if(mf->qid.type & QTDIR){
+ clock = time(nil);
+ if(off == 0){
+ memset(&dir, 0, sizeof dir);
+ dir.name = "dns";
+ dir.qid.type = QTFILE;
+ dir.qid.vers = vers;
+ dir.qid.path = Qdns;
+ dir.mode = 0666;
+ dir.length = 0;
+ dir.uid = dir.gid = dir.muid = mf->user;
+ dir.atime = dir.mtime = clock; /* wrong */
+ n = convD2M(&dir, buf, sizeof buf);
+ }
+ } else if (off < 0)
+ err = "negative read offset";
+ else {
+ /* first offset will always be zero */
+ for(i = 1; i <= mf->nrr; i++)
+ if(mf->rr[i] > off)
+ break;
+ if(i <= mf->nrr) {
+ if(off + cnt > mf->rr[i])
+ n = mf->rr[i] - off;
+ else
+ n = cnt;
+ assert(n >= 0);
+ job->reply.data = mf->reply + off;
+ }
+ }
+ job->reply.count = n;
+ sendmsg(job, err);
+}
+
+void
+rwrite(Job *job, Mfile *mf, Request *req)
+{
+ int rooted, wantsav, send;
+ ulong cnt;
+ char *err, *p, *atype;
+ char errbuf[ERRMAX];
+
+ err = nil;
+ cnt = job->request.count;
+ send = 1;
+ if(mf->qid.type & QTDIR)
+ err = "can't write directory";
+ else if (job->request.offset != 0)
+ err = "writing at non-zero offset";
+ else if(cnt >= Maxrequest)
+ err = "request too long";
+ else
+ send = 0;
+ if (send)
+ goto send;
+
+ job->request.data[cnt] = 0;
+ if(cnt > 0 && job->request.data[cnt-1] == '\n')
+ job->request.data[cnt-1] = 0;
+
+ if(strcmp(mf->user, "none") == 0 || strcmp(mf->user, dnsuser) != 0)
+ goto query; /* skip special commands if not owner */
+
+ /*
+ * special commands
+ */
+ if(debug)
+ dnslog("rwrite got: %s", job->request.data);
+ send = 1;
+ if(strcmp(job->request.data, "debug")==0)
+ debug ^= 1;
+ else if(strcmp(job->request.data, "dump")==0)
+ dndump("/lib/ndb/dnsdump");
+ else if(strcmp(job->request.data, "refresh")==0)
+ needrefresh = 1;
+ else if(strcmp(job->request.data, "stats")==0)
+ dnstats("/lib/ndb/dnsstats");
+ else if(strncmp(job->request.data, "target ", 7)==0){
+ target = atol(job->request.data + 7);
+ dnslog("target set to %ld", target);
+ } else
+ send = 0;
+ if (send)
+ goto send;
+
+query:
+ /*
+ * kill previous reply
+ */
+ mf->nrr = 0;
+ mf->rr[0] = 0;
+
+ /*
+ * break up request (into a name and a type)
+ */
+ atype = strchr(job->request.data, ' ');
+ if(atype == 0){
+ snprint(errbuf, sizeof errbuf, "illegal request %s",
+ job->request.data);
+ err = errbuf;
+ goto send;
+ } else
+ *atype++ = 0;
+
+ /*
+ * tracing request
+ */
+ if(strcmp(atype, "trace") == 0){
+ if(trace)
+ free(trace);
+ if(*job->request.data)
+ trace = estrdup(job->request.data);
+ else
+ trace = 0;
+ goto send;
+ }
+
+ /* normal request: domain [type] */
+ stats.qrecvd9p++;
+ mf->type = rrtype(atype);
+ if(mf->type < 0){
+ snprint(errbuf, sizeof errbuf, "unknown type %s", atype);
+ err = errbuf;
+ goto send;
+ }
+
+ p = atype - 2;
+ if(p >= job->request.data && *p == '.'){
+ rooted = 1;
+ *p = 0;
+ } else
+ rooted = 0;
+
+ p = job->request.data;
+ if(*p == '!'){
+ wantsav = 1;
+ p++;
+ } else
+ wantsav = 0;
+
+ err = lookupquery(job, mf, req, errbuf, p, wantsav, rooted);
+send:
+ job->reply.count = cnt;
+ sendmsg(job, err);
+}
+
+/*
+ * dnsdebug calls
+ * rr = dnresolve(buf, Cin, type, &req, nil, 0, Recurse, rooted, nil);
+ * which generates a UDP query, which eventually calls
+ * dnserver(&reqmsg, &repmsg, &req, buf, rcode);
+ * which calls
+ * rp = dnresolve(name, Cin, type, req, &mp->an, 0, recurse, 1, nil);
+ *
+ * but here we just call dnresolve directly.
+ */
+static char *
+lookupquery(Job *job, Mfile *mf, Request *req, char *errbuf, char *p,
+ int wantsav, int rooted)
+{
+ int status;
+ RR *rp, *neg;
+
+ status = Rok;
+ rp = dnresolve(p, Cin, mf->type, req, nil, 0, Recurse, rooted, &status);
+
+ neg = rrremneg(&rp);
+ if(neg){
+ status = neg->negrcode;
+ rrfreelist(neg);
+ }
+
+ return respond(job, mf, rp, errbuf, status, wantsav);
+}
+
+static char *
+respond(Job *job, Mfile *mf, RR *rp, char *errbuf, int status, int wantsav)
+{
+ long n;
+ RR *tp;
+
+ if(rp == nil)
+ switch(status){
+ case Rname:
+ return "name does not exist";
+ case Rserver:
+ return "dns failure";
+ case Rok:
+ default:
+ snprint(errbuf, ERRMAX,
+ "resource does not exist; negrcode %d", status);
+ return errbuf;
+ }
+
+ lock(&joblock);
+ if(!job->flushed){
+ /* format data to be read later */
+ n = 0;
+ mf->nrr = 0;
+ for(tp = rp; mf->nrr < Maxrrr-1 && n < Maxreply && tp &&
+ tsame(mf->type, tp->type); tp = tp->next){
+ mf->rr[mf->nrr++] = n;
+ if(wantsav)
+ n += snprint(mf->reply+n, Maxreply-n, "%Q", tp);
+ else
+ n += snprint(mf->reply+n, Maxreply-n, "%R", tp);
+ }
+ mf->rr[mf->nrr] = n;
+ }
+ unlock(&joblock);
+
+ rrfreelist(rp);
+
+ return nil;
+}
+
+void
+rclunk(Job *job, Mfile *mf)
+{
+ freefid(mf);
+ sendmsg(job, 0);
+}
+
+void
+rremove(Job *job, Mfile *mf)
+{
+ USED(mf);
+ sendmsg(job, "remove permission denied");
+}
+
+void
+rstat(Job *job, Mfile *mf)
+{
+ Dir dir;
+ uchar buf[IOHDRSZ+Maxfdata];
+
+ memset(&dir, 0, sizeof dir);
+ if(mf->qid.type & QTDIR){
+ dir.name = ".";
+ dir.mode = DMDIR|0555;
+ } else {
+ dir.name = "dns";
+ dir.mode = 0666;
+ }
+ dir.qid = mf->qid;
+ dir.length = 0;
+ dir.uid = dir.gid = dir.muid = mf->user;
+ dir.atime = dir.mtime = time(nil);
+ job->reply.nstat = convD2M(&dir, buf, sizeof buf);
+ job->reply.stat = buf;
+ sendmsg(job, 0);
+}
+
+void
+rwstat(Job *job, Mfile *mf)
+{
+ USED(mf);
+ sendmsg(job, "wstat permission denied");
+}
+
+void
+sendmsg(Job *job, char *err)
+{
+ int n;
+ uchar mdata[IOHDRSZ + Maxfdata];
+ char ename[ERRMAX];
+
+ if(err){
+ job->reply.type = Rerror;
+ snprint(ename, sizeof ename, "dns: %s", err);
+ job->reply.ename = ename;
+ }else
+ job->reply.type = job->request.type+1;
+ job->reply.tag = job->request.tag;
+ n = convS2M(&job->reply, mdata, sizeof mdata);
+ if(n == 0){
+ warning("sendmsg convS2M of %F returns 0", &job->reply);
+ abort();
+ }
+ lock(&joblock);
+ if(job->flushed == 0)
+ if(write(mfd[1], mdata, n)!=n)
+ sysfatal("mount write");
+ unlock(&joblock);
+ if(debug)
+ dnslog("%F %d", &job->reply, n);
+}
+
+/*
+ * the following varies between dnsdebug and dns
+ */
+void
+logreply(int id, uchar *addr, DNSmsg *mp)
+{
+ RR *rp;
+
+ dnslog("%d: rcvd %I flags:%s%s%s%s%s", id, addr,
+ mp->flags & Fauth? " auth": "",
+ mp->flags & Ftrunc? " trunc": "",
+ mp->flags & Frecurse? " rd": "",
+ mp->flags & Fcanrec? " ra": "",
+ (mp->flags & (Fauth|Rmask)) == (Fauth|Rname)? " nx": "");
+ for(rp = mp->qd; rp != nil; rp = rp->next)
+ dnslog("%d: rcvd %I qd %s", id, addr, rp->owner->name);
+ for(rp = mp->an; rp != nil; rp = rp->next)
+ dnslog("%d: rcvd %I an %R", id, addr, rp);
+ for(rp = mp->ns; rp != nil; rp = rp->next)
+ dnslog("%d: rcvd %I ns %R", id, addr, rp);
+ for(rp = mp->ar; rp != nil; rp = rp->next)
+ dnslog("%d: rcvd %I ar %R", id, addr, rp);
+}
+
+void
+logsend(int id, int subid, uchar *addr, char *sname, char *rname, int type)
+{
+ char buf[12];
+
+ dnslog("[%d] %d.%d: sending to %I/%s %s %s",
+ getpid(), id, subid, addr, sname, rname,
+ rrname(type, buf, sizeof buf));
+}
+
+RR*
+getdnsservers(int class)
+{
+ return dnsservers(class);
+}
--- /dev/null
+++ b/dns.h
@@ -1,0 +1,568 @@
+#define NS2MS(ns) ((ns) / 1000000L)
+#define S2MS(s) ((s) * 1000LL)
+
+#define timems() NS2MS(nsec())
+
+typedef struct Ndbtuple Ndbtuple;
+
+enum
+{
+ /* RR types; see: http://www.iana.org/assignments/dns-parameters */
+ Ta= 1,
+ Tns= 2,
+ Tmd= 3,
+ Tmf= 4,
+ Tcname= 5,
+ Tsoa= 6,
+ Tmb= 7,
+ Tmg= 8,
+ Tmr= 9,
+ Tnull= 10,
+ Twks= 11,
+ Tptr= 12,
+ Thinfo= 13,
+ Tminfo= 14,
+ Tmx= 15,
+ Ttxt= 16,
+ Trp= 17,
+ Tafsdb= 18,
+ Tx25= 19,
+ Tisdn= 20,
+ Trt= 21,
+ Tnsap= 22,
+ Tnsapptr= 23,
+ Tsig= 24,
+ Tkey= 25,
+ Tpx= 26,
+ Tgpos= 27,
+ Taaaa= 28,
+ Tloc= 29,
+ Tnxt= 30,
+ Teid= 31,
+ Tnimloc= 32,
+ Tsrv= 33,
+ Tatma= 34,
+ Tnaptr= 35,
+ Tkx= 36,
+ Tcert= 37,
+ Ta6= 38,
+ Tdname= 39,
+ Tsink= 40,
+ Topt= 41,
+ Tapl= 42,
+ Tds= 43,
+ Tsshfp= 44,
+ Tipseckey= 45,
+ Trrsig= 46,
+ Tnsec= 47,
+ Tdnskey= 48,
+
+ Tspf= 99,
+ Tuinfo= 100,
+ Tuid= 101,
+ Tgid= 102,
+ Tunspec= 103,
+
+ /* query types (all RR types are also queries) */
+ Ttkey= 249, /* transaction key */
+ Ttsig= 250, /* transaction signature */
+ Tixfr= 251, /* incremental zone transfer */
+ Taxfr= 252, /* zone transfer */
+ Tmailb= 253, /* { Tmb, Tmg, Tmr } */
+ Tmaila= 254, /* obsolete */
+ Tall= 255, /* all records */
+
+ /* classes */
+ Csym= 0, /* internal symbols */
+ Cin= 1, /* internet */
+ Ccs, /* CSNET (obsolete) */
+ Cch, /* Chaos net */
+ Chs, /* Hesiod (?) */
+
+ /* class queries (all class types are also queries) */
+ Call= 255, /* all classes */
+
+ /* opcodes */
+ Oquery= 0<<11, /* normal query */
+ Oinverse= 1<<11, /* inverse query (retired) */
+ Ostatus= 2<<11, /* status request */
+ Onotify= 4<<11, /* notify slaves of updates */
+ Oupdate= 5<<11,
+ Omask= 0xf<<11, /* mask for opcode */
+
+ /* response codes */
+ Rok= 0,
+ Rformat= 1, /* format error */
+ Rserver= 2, /* server failure (e.g. no answer from something) */
+ Rname= 3, /* bad name */
+ Runimplimented= 4, /* unimplemented */
+ Rrefused= 5, /* we don't like you */
+ Ryxdomain= 6, /* name exists when it should not */
+ Ryxrrset= 7, /* rr set exists when it should not */
+ Rnxrrset= 8, /* rr set that should exist does not */
+ Rnotauth= 9, /* not authoritative */
+ Rnotzone= 10, /* name not in zone */
+ Rbadvers= 16, /* bad opt version */
+/* Rbadsig= 16, */ /* also tsig signature failure */
+ Rbadkey= 17, /* key not recognized */
+ Rbadtime= 18, /* signature out of time window */
+ Rbadmode= 19, /* bad tkey mode */
+ Rbadname= 20, /* duplicate key name */
+ Rbadalg= 21, /* algorithm not supported */
+ Rmask= 0x1f, /* mask for response */
+ Rtimeout= 1<<5, /* timeout sending (for internal use only) */
+
+ /* bits in flag word (other than opcode and response) */
+ Fresp= 1<<15, /* message is a response */
+ Fauth= 1<<10, /* true if an authoritative response */
+ Ftrunc= 1<<9, /* truncated message */
+ Frecurse= 1<<8, /* request recursion */
+ Fcanrec= 1<<7, /* server can recurse */
+
+ Domlen= 256, /* max domain name length (with NULL) */
+ Labellen= 64, /* max domain label length (with NULL) */
+ Strlen= 256, /* max string length (with NULL) */
+
+ /* time to live values (in seconds) */
+ Min= 60,
+ Hour= 60*Min, /* */
+ Day= 24*Hour, /* Ta, Tmx */
+ Week= 7*Day, /* Tsoa, Tns */
+ Year= 52*Week,
+ DEFTTL= Day,
+
+ /* reserved time (can't be timed out earlier) */
+ Reserved= 5*Min,
+
+ /* packet sizes */
+ Maxudp= 512, /* maximum bytes per udp message sent */
+ Maxudpin= 2048, /* maximum bytes per udp message rcv'd */
+
+ /* length of domain name hash table */
+ HTLEN= 4*1024,
+
+ Maxpath= 128, /* size of mntpt */
+ Maxlcks= 10, /* max. query-type locks per domain name */
+ Maxauth= 16, /* proto for auth */
+ Maxremote= 256, /* For registry, if you're more popular than this... */
+ Maxmdns= 255, /* Limits for mDNS messages */
+ Maxdial= 271, /* port + trns + !! + host */
+
+ RRmagic= 0xdeadbabe,
+ DNmagic= 0xa110a110,
+
+ /* parallelism: tune; was 32; allow lots */
+ Maxactive= 250,
+
+ /* tune; was 60*1000; keep it short */
+ Maxreqtm= 8*1000, /* max. ms to process a request */
+
+ Notauthoritative = 0,
+ Authoritative,
+};
+
+typedef struct Area Area;
+typedef struct Block Block;
+typedef struct Cert Cert;
+typedef struct DN DN;
+typedef struct DNSmsg DNSmsg;
+typedef struct Key Key;
+typedef struct Null Null;
+typedef struct RR RR;
+typedef struct Request Request;
+typedef struct SOA SOA;
+typedef struct Server Server;
+typedef struct Svc Svc;
+typedef struct Sig Sig;
+typedef struct Srv Srv;
+typedef struct Txt Txt;
+
+/*
+ * a structure to track a request and any slave process handling it
+ */
+struct Request
+{
+ int isslave; /* pid of slave */
+ uvlong aborttime; /* time in ms at which we give up */
+ jmp_buf mret; /* where master jumps to after starting a slave */
+ int id;
+ char *from; /* who asked us? */
+ void *aux;
+};
+
+/*
+ * a domain name
+ */
+struct DN
+{
+ DN *next; /* hash collision list */
+ ulong magic;
+ char *name; /* owner */
+ RR *rr; /* resource records off this name */
+ ulong referenced; /* time last referenced */
+ ulong ordinal;
+ ushort class; /* RR class */
+ uchar respcode; /* response code */
+ uchar mark; /* for mark and sweep */
+};
+
+/*
+ * security info
+ */
+struct Block
+{
+ int dlen;
+ uchar *data;
+};
+struct Key
+{
+ int flags;
+ int proto;
+ int alg;
+ Block;
+};
+struct Cert
+{
+ int type;
+ int tag;
+ int alg;
+ Block;
+};
+struct Sig
+{
+ Cert;
+ int labels;
+ ulong ttl;
+ ulong exp;
+ ulong incep;
+ DN *signer;
+};
+struct Null
+{
+ Block;
+};
+
+/*
+ * text strings
+ */
+struct Txt
+{
+ Txt *next;
+ char *p;
+};
+
+/*
+ * registry service
+ */
+struct Svc
+{
+ int id; /* for server tracking */
+ Svc *next;
+ char labl[Maxmdns]; /* Use mDNS limits */
+ char host[Maxmdns]; /* host ip */
+ char mtpt[Maxpath];
+ char auth[Maxauth];
+ char trns[16]; /* transport tcp/ssh/gopher/carrierpidgeon */
+ char port[8]; /* 9fs, numerical, etc - switch to #? */
+ uchar perm; /* flag: from db */
+};
+
+/*
+ * an unpacked resource record
+ */
+struct RR
+{
+ RR *next;
+ ulong magic;
+ DN *owner; /* domain that owns this resource record */
+ uintptr pc; /* for tracking memory allocation */
+ ulong ttl; /* time to live to be passed on */
+ ulong expire; /* time this entry expires locally */
+ ulong marker; /* used locally when scanning rrlists */
+ ushort type; /* RR type */
+ ushort query; /* query type is in response to */
+ uchar auth; /* flag: authoritative */
+ uchar db; /* flag: from database */
+ uchar cached; /* flag: rr in cache */
+ uchar negative; /* flag: this is a cached negative response */
+
+ union { /* discriminated by negative & type */
+ DN *negsoaowner; /* soa for cached negative response */
+ DN *host; /* hostname - soa, cname, mb, md, mf, mx, ns, srv */
+ DN *cpu; /* cpu type - hinfo */
+ DN *mb; /* mailbox - mg, minfo */
+ DN *ip; /* ip address - a, aaaa */
+ DN *rp; /* rp arg - rp */
+ uintptr arg0; /* arg[01] are compared to find dups in dn.c */
+ };
+ union { /* discriminated by negative & type */
+ int negrcode; /* response code for cached negative resp. */
+ DN *rmb; /* responsible maibox - minfo, soa, rp */
+ DN *ptr; /* pointer to domain name - ptr */
+ DN *os; /* operating system - hinfo */
+ ulong pref; /* preference value - mx */
+ ulong local; /* ns served from local database - ns */
+ ushort port; /* - srv */
+ uintptr arg1; /* arg[01] are compared to find dups in dn.c */
+ };
+ union { /* discriminated by type */
+ SOA *soa; /* soa timers - soa */
+ Srv *srv;
+ Key *key;
+ Cert *cert;
+ Sig *sig;
+ Null *null;
+ Txt *txt;
+ };
+};
+
+/*
+ * list of servers
+ */
+struct Server
+{
+ Server *next;
+ char *name;
+};
+
+/*
+ * timers for a start-of-authority record. all ulongs are in seconds.
+ */
+struct SOA
+{
+ ulong serial; /* zone serial # */
+ ulong refresh; /* zone refresh interval */
+ ulong retry; /* zone retry interval */
+ ulong expire; /* time to expiration */
+ ulong minttl; /* min. time to live for any entry */
+
+ Server *slaves; /* slave servers */
+};
+
+/*
+ * srv (service location) record (rfc2782):
+ * _service._proto.name ttl class(IN) 'SRV' priority weight port target
+ */
+struct Srv
+{
+ ushort pri;
+ ushort weight;
+};
+
+typedef struct Rrlist Rrlist;
+struct Rrlist
+{
+ int count;
+ RR *rrs;
+};
+
+/*
+ * domain messages
+ */
+struct DNSmsg
+{
+ ushort id;
+ int flags;
+ int qdcount; /* questions */
+ RR *qd;
+ int ancount; /* answers */
+ RR *an;
+ int nscount; /* name servers */
+ RR *ns;
+ int arcount; /* hints */
+ RR *ar;
+};
+
+/*
+ * definition of local area for dblookup
+ */
+struct Area
+{
+ Area *next;
+
+ int len; /* strlen(area->soarr->owner->name) */
+ RR *soarr; /* soa defining this area */
+ int neednotify;
+ int needrefresh;
+};
+
+typedef struct Cfg Cfg;
+struct Cfg {
+ int cachedb;
+ int resolver;
+ int justforw; /* flag: pure resolver, just forward queries */
+ int serve; /* flag: serve udp queries */
+ int inside;
+ int straddle;
+};
+
+/* (udp) query stats */
+typedef struct {
+ QLock;
+ ulong slavehiwat; /* procs */
+ ulong qrecvd9p; /* query counts */
+ ulong qrecvdudp;
+ ulong qsent;
+ ulong qrecvd9prpc; /* packet count */
+ ulong alarms;
+ /* reply times by count */
+ ulong under10ths[3*10+2]; /* under n*0.1 seconds, n is index */
+ ulong tmout;
+ ulong tmoutcname;
+ ulong tmoutv6;
+
+ ulong answinmem; /* answers in memory */
+ ulong negans; /* negative answers received */
+ ulong negserver; /* neg ans with Rserver set */
+ ulong negbaddeleg; /* neg ans with bad delegations */
+ ulong negbdnoans; /* ⋯ and no answers */
+ ulong negnorname; /* neg ans with no Rname set */
+ ulong negcached; /* neg ans cached */
+} Stats;
+
+Stats stats;
+
+enum
+{
+ Recurse,
+ Dontrecurse,
+ NOneg,
+ OKneg,
+};
+
+extern Cfg cfg;
+extern char *dbfile;
+extern int debug;
+extern Area *delegated;
+extern char *logfile;
+extern int maxage; /* age of oldest entry in cache (secs) */
+extern char mntpt[];
+extern int needrefresh;
+extern int norecursion;
+extern ulong now; /* time base */
+extern vlong nowns;
+extern Area *owned;
+extern int sendnotifies;
+extern ulong target;
+extern char *trace;
+extern int traceactivity;
+extern char *zonerefreshprogram;
+extern Svc *registry;
+extern int rfd[Maxremote];
+
+#pragma varargck type "R" RR*
+#pragma varargck type "Q" RR*
+#pragma varargck type "G" Svc*
+#pragma varargck type "D" Svc*
+#pragma varargck type "N" Svc*
+
+/* dn.c */
+extern char *rrtname[];
+extern char *rname[];
+extern unsigned nrname;
+extern char *opname[];
+extern Lock dnlock;
+
+void abort(); /* char*, ... */;
+void addserver(Server**, char*);
+Server* copyserverlist(Server*);
+void db2cache(int);
+void dnage(DN*);
+void dnageall(int);
+void dnagedb(void);
+void dnagenever(DN *);
+void dnauthdb(void);
+void dndump(char*);
+void dninit(void);
+DN* dnlookup(char*, int, int);
+DN* idnlookup(char*, int, int);
+DN* ipalookup(uchar*, int, int);
+void dnptr(uchar*, uchar*, char*, int, int, int);
+void dnpurge(void);
+void dnslog(char*, ...);
+void dnstats(char *file);
+void* emalloc(int);
+char* estrdup(char*);
+void freeanswers(DNSmsg *mp);
+void freeserverlist(Server*);
+int getactivity(Request*, int);
+Area* inmyarea(char*);
+void putactivity(int);
+void reg2cache(void);
+void regconnect(void);
+char* rstr2cache(char*, int);
+Svc* rstr2svc(char*);
+char* rstrdtch(char*);
+char* rstrupdt(char*);
+RR* randomize(RR*);
+void reglog(char*, ...);
+RR* rralloc(int);
+void rrattach(RR*, int);
+int rravfmt(Fmt*);
+RR* rrcat(RR**, RR*);
+RR** rrcopy(RR*, RR**);
+int rrfmt(Fmt*);
+void rrfree(RR*);
+void rrfreelist(RR*);
+RR* rrlookup(DN*, int, int);
+char* rrname(int, char*, int);
+RR* rrremneg(RR**);
+RR* rrremtype(RR**, int);
+RR* rrremowner(RR**, DN*);
+RR* rrremfilter(RR**, int (*)(RR*, void*), void*);
+int rrsupported(int);
+int rrtype(char*);
+void slave(Request*);
+int subsume(char*, char*);
+int tsame(int, int);
+void unique(RR*);
+void warning(char*, ...);
+
+
+/* dnarea.c */
+void refresh_areas(Area*);
+void freearea(Area**);
+void addarea(DN *dp, RR *rp, Ndbtuple *t);
+
+/* dblookup.c */
+int baddelegation(RR*, RR*, uchar*);
+RR* dblookup(char*, int, int, int, int);
+RR* dnsservers(int);
+RR* domainlist(int);
+int insideaddr(char *dom);
+int insidens(uchar *ip);
+int myip(uchar *ip);
+int opendatabase(void);
+int outsidensip(int, uchar *ip);
+
+/* dns.c */
+char* walkup(char*);
+RR* getdnsservers(int);
+void logreply(int, uchar*, DNSmsg*);
+void logsend(int, int, uchar*, char*, char*, int);
+
+/* dnresolve.c */
+RR* dnresolve(char*, int, int, Request*, RR**, int, int, int, int*);
+int udpport(char *);
+int mkreq(DN *dp, int type, uchar *buf, int flags, ushort reqno);
+int seerootns(void);
+void initdnsmsg(DNSmsg *mp, RR *rp, int flags, ushort reqno);
+
+/* dnserver.c */
+void dnserver(DNSmsg*, DNSmsg*, Request*, uchar *, int);
+void dnudpserver(char*);
+
+/* dnnotify.c */
+void dnnotify(DNSmsg*, DNSmsg*, Request*);
+void notifyproc(void);
+
+/* reglookup.c */
+int openregistry(void);
+
+/* convDNS2M.c */
+int convDNS2M(DNSmsg*, uchar*, int);
+
+/* convM2DNS.c */
+char* convM2DNS(uchar*, int, DNSmsg*, int*);
+
+#pragma varargck argpos dnslog 1
--- /dev/null
+++ b/dnsdebug.c
@@ -1,0 +1,490 @@
+#include <u.h>
+#include <libc.h>
+#include <bio.h>
+#include <ctype.h>
+#include <ip.h>
+#include <ndb.h>
+#include "dns.h"
+
+enum {
+ Maxrequest= 128,
+};
+
+Cfg cfg;
+
+static char *servername;
+static RR *serveraddrs;
+
+char *dbfile;
+int debug;
+char *logfile = "dnsdebug";
+int maxage = 60*60;
+char mntpt[Maxpath];
+int needrefresh;
+ulong now;
+vlong nowns;
+char *trace;
+int traceactivity;
+char *zonerefreshprogram;
+
+void docmd(int, char**);
+void doquery(char*, char*);
+void preloadserveraddrs(void);
+int prettyrrfmt(Fmt*);
+int setserver(char*);
+void squirrelserveraddrs(void);
+
+void
+usage(void)
+{
+ fprint(2, "%s: [-rx] [-f db-file] [[@server] domain [type]]\n", argv0);
+ exits("usage");
+}
+
+void
+main(int argc, char *argv[])
+{
+ int n;
+ Biobuf in;
+ char *p;
+ char *f[4];
+
+ strcpy(mntpt, "/net");
+ cfg.inside = 1;
+
+ ARGBEGIN{
+ case 'f':
+ dbfile = EARGF(usage());
+ break;
+ case 'r':
+ cfg.resolver = 1;
+ break;
+ case 'x':
+ dbfile = "/lib/ndb/external";
+ strcpy(mntpt, "/net.alt");
+ break;
+ default:
+ usage();
+ }ARGEND
+
+ now = time(nil);
+ nowns = nsec();
+ dninit();
+ fmtinstall('R', prettyrrfmt);
+ opendatabase();
+ srand(truerand());
+
+ if(cfg.resolver)
+ squirrelserveraddrs();
+
+ debug = 1;
+
+ if(argc > 0){
+ docmd(argc, argv);
+ exits(0);
+ }
+
+ Binit(&in, 0, OREAD);
+ for(print("> "); p = Brdline(&in, '\n'); print("> ")){
+ p[Blinelen(&in)-1] = 0;
+ n = tokenize(p, f, 3);
+ if(n>=1) {
+ dnpurge(); /* flush the cache */
+ docmd(n, f);
+ }
+ }
+ exits(0);
+}
+
+static char*
+longtime(long t)
+{
+ int d, h, m, n;
+ static char x[128];
+
+ for(d = 0; t >= 24*60*60; t -= 24*60*60)
+ d++;
+ for(h = 0; t >= 60*60; t -= 60*60)
+ h++;
+ for(m = 0; t >= 60; t -= 60)
+ m++;
+ n = 0;
+ if(d)
+ n += sprint(x, "%d day ", d);
+ if(h)
+ n += sprint(x+n, "%d hr ", h);
+ if(m)
+ n += sprint(x+n, "%d min ", m);
+ if(t || n == 0)
+ sprint(x+n, "%ld sec", t);
+ return x;
+}
+
+/*
+ * convert address into a reverse lookup address
+ */
+static void
+mkptrname(char *ip, char *rip, int rlen)
+{
+ uchar a[IPaddrlen];
+ char *p, *e;
+ int i;
+
+ if(cistrstr(ip, "in-addr.arpa") || cistrstr(ip, "ip6.arpa") || parseip(a, ip) == -1)
+ snprint(rip, rlen, "%s", ip);
+ else if(isv4(a))
+ snprint(rip, rlen, "%ud.%ud.%ud.%ud.in-addr.arpa",
+ a[15], a[14], a[13], a[12]);
+ else{
+ p = rip;
+ e = rip + rlen;
+ for(i = 15; i >= 0; i--){
+ p = seprint(p, e, "%ux.", a[i]&0xf);
+ p = seprint(p, e, "%ux.", a[i]>>4);
+ }
+ seprint(p, e, "ip6.arpa");
+ }
+}
+
+int
+prettyrrfmt(Fmt *f)
+{
+ RR *rp;
+ char buf[3*Domlen];
+ char *p, *e;
+ Txt *t;
+
+ rp = va_arg(f->args, RR*);
+ if(rp == 0){
+ strcpy(buf, "<null>");
+ goto out;
+ }
+
+ p = buf;
+ e = buf + sizeof(buf);
+ p = seprint(p, e, "%-32.32s %-15.15s %-5.5s", rp->owner->name,
+ longtime(rp->ttl),
+ rrname(rp->type, buf, sizeof buf));
+
+ if(rp->negative){
+ seprint(p, e, "negative rcode %d", rp->negrcode);
+ goto out;
+ }
+
+ switch(rp->type){
+ case Thinfo:
+ seprint(p, e, "\t%s %s", rp->cpu->name, rp->os->name);
+ break;
+ case Tcname:
+ case Tmb:
+ case Tmd:
+ case Tmf:
+ case Tns:
+ seprint(p, e, "\t%s", (rp->host? rp->host->name: ""));
+ break;
+ case Tmg:
+ case Tmr:
+ seprint(p, e, "\t%s", (rp->mb? rp->mb->name: ""));
+ break;
+ case Tminfo:
+ seprint(p, e, "\t%s %s", (rp->mb? rp->mb->name: ""),
+ (rp->rmb? rp->rmb->name: ""));
+ break;
+ case Tmx:
+ seprint(p, e, "\t%lud %s", rp->pref,
+ (rp->host? rp->host->name: ""));
+ break;
+ case Ta:
+ case Taaaa:
+ seprint(p, e, "\t%s", (rp->ip? rp->ip->name: ""));
+ break;
+ case Tptr:
+ seprint(p, e, "\t%s", (rp->ptr? rp->ptr->name: ""));
+ break;
+ case Tsoa:
+ seprint(p, e, "\t%s %s %lud %lud %lud %lud %lud",
+ rp->host->name, rp->rmb->name, rp->soa->serial,
+ rp->soa->refresh, rp->soa->retry,
+ rp->soa->expire, rp->soa->minttl);
+ break;
+ case Tsrv:
+ seprint(p, e, "\t%ud %ud %ud %s",
+ rp->srv->pri, rp->srv->weight, rp->port, rp->host->name);
+ break;
+ case Tnull:
+ seprint(p, e, "\t%.*H", rp->null->dlen, rp->null->data);
+ break;
+ case Ttxt:
+ p = seprint(p, e, "\t");
+ for(t = rp->txt; t != nil; t = t->next)
+ p = seprint(p, e, "%s", t->p);
+ break;
+ case Trp:
+ seprint(p, e, "\t%s %s", rp->rmb->name, rp->rp->name);
+ break;
+ case Tkey:
+ seprint(p, e, "\t%d %d %d", rp->key->flags, rp->key->proto,
+ rp->key->alg);
+ break;
+ case Tsig:
+ seprint(p, e, "\t%d %d %d %lud %lud %lud %d %s",
+ rp->sig->type, rp->sig->alg, rp->sig->labels,
+ rp->sig->ttl, rp->sig->exp, rp->sig->incep,
+ rp->sig->tag, rp->sig->signer->name);
+ break;
+ case Tcert:
+ seprint(p, e, "\t%d %d %d",
+ rp->sig->type, rp->sig->tag, rp->sig->alg);
+ break;
+ }
+out:
+ return fmtstrcpy(f, buf);
+}
+
+void
+logsection(char *flag, RR *rp)
+{
+ if(rp == nil)
+ return;
+ print("\t%s%R\n", flag, rp);
+ for(rp = rp->next; rp != nil; rp = rp->next)
+ print("\t %R\n", rp);
+}
+
+void
+logreply(int id, uchar *addr, DNSmsg *mp)
+{
+ RR *rp;
+ char buf[12], resp[32];
+
+ switch(mp->flags & Rmask){
+ case Rok:
+ strcpy(resp, "OK");
+ break;
+ case Rformat:
+ strcpy(resp, "Format error");
+ break;
+ case Rserver:
+ strcpy(resp, "Server failed");
+ break;
+ case Rname:
+ strcpy(resp, "Nonexistent");
+ break;
+ case Runimplimented:
+ strcpy(resp, "Unimplemented");
+ break;
+ case Rrefused:
+ strcpy(resp, "Refused");
+ break;
+ default:
+ sprint(resp, "%d", mp->flags & Rmask);
+ break;
+ }
+
+ print("%d: rcvd %s from %I (%s%s%s%s%s)\n", id, resp, addr,
+ mp->flags & Fauth? "authoritative": "",
+ mp->flags & Ftrunc? " truncated": "",
+ mp->flags & Frecurse? " recurse": "",
+ mp->flags & Fcanrec? " can_recurse": "",
+ (mp->flags & (Fauth|Rmask)) == (Fauth|Rname)? " nx": "");
+ for(rp = mp->qd; rp != nil; rp = rp->next)
+ print("\tQ: %s %s\n", rp->owner->name,
+ rrname(rp->type, buf, sizeof buf));
+ logsection("Ans: ", mp->an);
+ logsection("Auth: ", mp->ns);
+ logsection("Hint: ", mp->ar);
+}
+
+void
+logsend(int id, int subid, uchar *addr, char *sname, char *rname, int type)
+{
+ char buf[12];
+
+ print("%d.%d: sending to %I/%s %s %s\n", id, subid,
+ addr, sname, rname, rrname(type, buf, sizeof buf));
+}
+
+RR*
+getdnsservers(int class)
+{
+ RR *rr;
+
+ if(servername == nil)
+ return dnsservers(class);
+
+ rr = rralloc(Tns);
+ rr->owner = dnlookup("local#dns#servers", class, 1);
+ rr->host = idnlookup(servername, class, 1);
+
+ return rr;
+}
+
+void
+squirrelserveraddrs(void)
+{
+ int v4;
+ char *attr;
+ RR *rr, *rp, **l;
+ Request req;
+
+ /* look up the resolver address first */
+ cfg.resolver = 0;
+ debug = 0;
+ if(serveraddrs){
+ rrfreelist(serveraddrs);
+ serveraddrs = nil;
+ }
+ rr = getdnsservers(Cin);
+ l = &serveraddrs;
+ for(rp = rr; rp != nil; rp = rp->next){
+ attr = ipattr(rp->host->name);
+ v4 = strcmp(attr, "ip") == 0;
+ if(v4 || strcmp(attr, "ipv6") == 0){
+ *l = rralloc(v4? Ta: Taaaa);
+ (*l)->owner = rp->host;
+ (*l)->ip = rp->host;
+ l = &(*l)->next;
+ continue;
+ }
+ memset(&req, 0, sizeof req);
+ req.isslave = 1;
+ req.aborttime = NS2MS(nowns) + Maxreqtm;
+ *l = dnresolve(rp->host->name, Cin, Ta, &req, nil, 0, Recurse, 0, nil);
+ if(*l == nil)
+ *l = dnresolve(rp->host->name, Cin, Taaaa, &req,
+ nil, 0, Recurse, 0, nil);
+ while(*l != nil)
+ l = &(*l)->next;
+ }
+ cfg.resolver = 1;
+ debug = 1;
+}
+
+void
+preloadserveraddrs(void)
+{
+ RR *rp, **l, *first;
+
+ first = nil;
+ l = &first;
+ for(rp = serveraddrs; rp != nil; rp = rp->next){
+ rrcopy(rp, l);
+ rrattach(first, Authoritative);
+ }
+}
+
+int
+setserver(char *server)
+{
+ if(servername != nil){
+ free(servername);
+ servername = nil;
+ cfg.resolver = 0;
+ }
+ if(server == nil || *server == 0)
+ return 0;
+ servername = strdup(server);
+ squirrelserveraddrs();
+ if(serveraddrs == nil){
+ print("can't resolve %s\n", servername);
+ cfg.resolver = 0;
+ } else
+ cfg.resolver = 1;
+ return cfg.resolver? 0: -1;
+}
+
+void
+doquery(char *name, char *tstr)
+{
+ int len, type, rooted;
+ char buf[1024];
+ RR *rr, *rp;
+ Request req;
+
+ if(cfg.resolver)
+ preloadserveraddrs();
+
+ /* default to an "ip" request if alpha, "ptr" if numeric */
+ if(tstr == nil || *tstr == 0)
+ if(strcmp(ipattr(name), "ip") == 0)
+ tstr = "ptr";
+ else
+ tstr = "ip";
+
+ /* look it up */
+ type = rrtype(tstr);
+ if(type < 0){
+ print("!unknown type %s\n", tstr);
+ return;
+ }
+
+ /* if name end in '.', remove it */
+ len = strlen(name);
+ if(len > 0 && name[len-1] == '.'){
+ rooted = 1;
+ name[len-1] = 0;
+ } else
+ rooted = 0;
+
+ /* inverse queries may need to be permuted */
+ if(type == Tptr)
+ mkptrname(name, buf, sizeof buf);
+ else
+ strncpy(buf, name, sizeof buf);
+
+ memset(&req, 0, sizeof req);
+ getactivity(&req, 0);
+ req.isslave = 1;
+ req.aborttime = NS2MS(nowns) + Maxreqtm;
+ rr = dnresolve(buf, Cin, type, &req, nil, 0, Recurse, rooted, nil);
+ if(rr){
+ print("----------------------------\n");
+ for(rp = rr; rp; rp = rp->next)
+ print("answer %R\n", rp);
+ print("----------------------------\n");
+ }
+ rrfreelist(rr);
+
+ putactivity(0);
+}
+
+void
+docmd(int n, char **f)
+{
+ int tmpsrv;
+ char *name, *type;
+
+ name = type = nil;
+ tmpsrv = 0;
+
+ if(*f[0] == '@') {
+ if(setserver(f[0]+1) < 0)
+ return;
+
+ switch(n){
+ case 3:
+ type = f[2];
+ /* fall through */
+ case 2:
+ name = f[1];
+ tmpsrv = 1;
+ break;
+ }
+ } else
+ switch(n){
+ case 2:
+ type = f[1];
+ /* fall through */
+ case 1:
+ name = f[0];
+ break;
+ }
+
+ if(name == nil)
+ return;
+
+ doquery(name, type);
+
+ if(tmpsrv)
+ setserver("");
+}
--- /dev/null
+++ b/dnserver.c
@@ -1,0 +1,212 @@
+#include <u.h>
+#include <libc.h>
+#include <ip.h>
+#include "dns.h"
+
+static RR* doextquery(DNSmsg*, Request*, int);
+static void hint(RR**, RR*);
+
+/* set in dns.c */
+int norecursion; /* don't allow recursive requests */
+
+/*
+ * answer a dns request
+ */
+void
+dnserver(DNSmsg *reqp, DNSmsg *repp, Request *req, uchar *srcip, int rcode)
+{
+ int recursionflag;
+ char *cp, *errmsg;
+ char tname[32];
+ DN *nsdp, *dp;
+ Area *myarea;
+ RR *tp, *neg, *rp;
+
+ recursionflag = norecursion? 0: Fcanrec;
+ memset(repp, 0, sizeof(*repp));
+ repp->id = reqp->id;
+ repp->flags = Fresp | recursionflag | Oquery;
+
+ /* move one question from reqp to repp */
+ tp = reqp->qd;
+ reqp->qd = tp->next;
+ tp->next = nil;
+ repp->qd = tp;
+
+ if (rcode) {
+ errmsg = "";
+ if (rcode >= 0 && rcode < nrname)
+ errmsg = rname[rcode];
+ dnslog("server: response code 0%o (%s), req from %I",
+ rcode, errmsg, srcip);
+ /* provide feedback to clients who send us trash */
+ repp->flags = (rcode&Rmask) | Fresp | Fcanrec | Oquery;
+ return;
+ }
+ if(!rrsupported(repp->qd->type)){
+ dnslog("server: unsupported request %s from %I",
+ rrname(repp->qd->type, tname, sizeof tname), srcip);
+ repp->flags = Runimplimented | Fresp | Fcanrec | Oquery;
+ return;
+ }
+
+ if(repp->qd->owner->class != Cin){
+ dnslog("server: unsupported class %d from %I",
+ repp->qd->owner->class, srcip);
+ repp->flags = Runimplimented | Fresp | Fcanrec | Oquery;
+ return;
+ }
+
+ myarea = inmyarea(repp->qd->owner->name);
+ if(myarea != nil) {
+ if(repp->qd->type == Tixfr || repp->qd->type == Taxfr){
+ dnslog("server: unsupported xfr request %s for %s from %I",
+ rrname(repp->qd->type, tname, sizeof tname),
+ repp->qd->owner->name, srcip);
+ repp->flags = Runimplimented | Fresp | recursionflag |
+ Oquery;
+ return;
+ }
+ }
+ if(myarea == nil && norecursion) {
+ /* we don't recurse and we're not authoritative */
+ repp->flags = Rok | Fresp | Oquery;
+ neg = nil;
+ } else {
+ /*
+ * get the answer if we can, in *repp
+ */
+ if(reqp->flags & Frecurse)
+ neg = doextquery(repp, req, Recurse);
+ else
+ neg = doextquery(repp, req, Dontrecurse);
+
+ /* authority is transitive */
+ if(myarea != nil || (repp->an && repp->an->auth))
+ repp->flags |= Fauth;
+
+ /* pass on error codes */
+ if(repp->an == nil){
+ dp = dnlookup(repp->qd->owner->name, repp->qd->owner->class, 0);
+ if(dp->rr == nil)
+ if(reqp->flags & Frecurse)
+ repp->flags |= dp->respcode | Fauth;
+ }
+ }
+
+ if(myarea == nil){
+ /*
+ * add name server if we know
+ */
+ for(cp = repp->qd->owner->name; cp; cp = walkup(cp)){
+ nsdp = dnlookup(cp, repp->qd->owner->class, 0);
+ if(nsdp == nil)
+ continue;
+
+ repp->ns = rrlookup(nsdp, Tns, OKneg);
+ if(repp->ns){
+ /* don't pass on anything we know is wrong */
+ if(repp->ns->negative){
+ rp = repp->ns;
+ repp->ns = nil;
+ rrfreelist(rp);
+ }
+ break;
+ }
+
+ if (strncmp(nsdp->name, "local#", 6) == 0)
+ dnslog("returning %s as nameserver", nsdp->name);
+ repp->ns = dblookup(cp, repp->qd->owner->class, Tns, 0, 0);
+ if(repp->ns)
+ break;
+ }
+ }
+
+ /*
+ * add ip addresses as hints
+ */
+ if(repp->qd->type != Taxfr && repp->qd->type != Tixfr){
+ for(tp = repp->ns; tp; tp = tp->next)
+ hint(&repp->ar, tp);
+ for(tp = repp->an; tp; tp = tp->next)
+ hint(&repp->ar, tp);
+ }
+
+ /*
+ * add an soa to the authority section to help client
+ * with negative caching
+ */
+ if(repp->an == nil){
+ if(myarea != nil){
+ rrcopy(myarea->soarr, &tp);
+ rrcat(&repp->ns, tp);
+ } else if(neg != nil) {
+ if(neg->negsoaowner != nil) {
+ tp = rrlookup(neg->negsoaowner, Tsoa, NOneg);
+ rrcat(&repp->ns, tp);
+ }
+ repp->flags |= neg->negrcode;
+ }
+ }
+
+ /*
+ * get rid of duplicates
+ */
+ unique(repp->an);
+ unique(repp->ns);
+ unique(repp->ar);
+
+ rrfreelist(neg);
+}
+
+/*
+ * satisfy a recursive request. dnlookup will handle cnames.
+ */
+static RR*
+doextquery(DNSmsg *mp, Request *req, int recurse)
+{
+ ushort type;
+ char *name;
+ RR *rp, *neg;
+
+ name = mp->qd->owner->name;
+ type = mp->qd->type;
+ rp = dnresolve(name, Cin, type, req, &mp->an, 0, recurse, 1, nil);
+
+ /* don't return soa hints as answers, it's wrong */
+ if(rp && rp->db && !rp->auth && rp->type == Tsoa) {
+ rrfreelist(rp);
+ rp = nil;
+ }
+
+ /* don't let negative cached entries escape */
+ neg = rrremneg(&rp);
+ rrcat(&mp->an, rp);
+
+ return neg;
+}
+
+static void
+hint(RR **last, RR *rp)
+{
+ RR *hp;
+
+ switch(rp->type){
+ case Tns:
+ case Tmx:
+ case Tmb:
+ case Tmf:
+ case Tmd:
+ hp = rrlookup(rp->host, Ta, NOneg);
+ if(hp == nil)
+ hp = dblookup(rp->host->name, Cin, Ta, 0, 0);
+ if(hp == nil)
+ hp = rrlookup(rp->host, Taaaa, NOneg);
+ if(hp == nil)
+ hp = dblookup(rp->host->name, Cin, Taaaa, 0, 0);
+ if (hp && strncmp(hp->owner->name, "local#", 6) == 0)
+ dnslog("returning %s as hint", hp->owner->name);
+ rrcat(last, hp);
+ break;
+ }
+}
--- /dev/null
+++ b/dnsgetip.c
@@ -1,0 +1,124 @@
+/* one-shot resolver */
+#include <u.h>
+#include <libc.h>
+#include <bio.h>
+#include <ndb.h>
+#include "dns.h"
+
+Cfg cfg;
+char *dbfile;
+int debug = 0;
+char *logfile = "dnsgetip";
+int maxage = 60*60;
+char mntpt[Maxpath];
+int needrefresh = 0;
+ulong now = 0;
+vlong nowns = 0;
+int traceactivity = 0;
+char *zonerefreshprogram;
+
+int aflag = 0;
+int addresses = 0;
+
+char Ebotch[] = "dns botch";
+
+char*
+resolve(char *name, int type)
+{
+ int status;
+ char *errmsg;
+ Request req;
+ RR *rr, *rp, *neg;
+
+ status = Rok;
+ errmsg = nil;
+
+ memset(&req, 0, sizeof req);
+ getactivity(&req, 0);
+ req.isslave = 1;
+ req.aborttime = NS2MS(nowns) + Maxreqtm;
+
+ rr = dnresolve(name, Cin, type, &req, nil, 0, Recurse, 0, &status);
+ neg = rrremneg(&rr);
+ if(rr == nil || neg != nil){
+ if(neg != nil)
+ status = neg->negrcode;
+ errmsg = Ebotch;
+ if(status > 0 && status < nrname)
+ errmsg = rname[status];
+ }
+
+ rrfreelist(neg);
+
+ for(rp = rr; rp != nil; rp = rp->next){
+ print("%s\n", rp->ip->name);
+ addresses++;
+ if(!aflag)
+ exits(nil);
+ }
+
+ rrfreelist(rr);
+
+ return errmsg;
+}
+
+void
+usage(void)
+{
+ fprint(2, "%s: [-adx] domain\n", argv0);
+ exits("usage");
+}
+
+void
+main(int argc, char **argv)
+{
+ char *e4, *e6;
+
+ strcpy(mntpt, "/net");
+ cfg.inside = 1;
+ cfg.resolver = 1;
+
+ ARGBEGIN{
+ case 'a':
+ aflag = 1;
+ break;
+ case 'd':
+ debug++;
+ break;
+ case 'x':
+ strcpy(mntpt, "/net.alt");
+ break;
+ default:
+ usage();
+ }ARGEND
+
+ if(argc != 1)
+ usage();
+
+ if(strcmp(ipattr(*argv), "ip") == 0)
+ print("%s\n", *argv);
+ else {
+ dninit();
+ e4 = resolve(*argv, Ta);
+ e6 = resolve(*argv, Taaaa);
+
+ if(addresses == 0){
+ if(e4 == e6)
+ sysfatal("%s: dns failure: %s", *argv, e4);
+
+ sysfatal("%s: dns failure: v4: %s: v6: %s", *argv, e4, e6);
+ }
+ }
+ exits(nil);
+}
+
+RR*
+getdnsservers(int class)
+{
+ return dnsservers(class);
+}
+
+/* stubs */
+void syslog(int, char*, char*, ...){}
+void logreply(int, uchar*, DNSmsg*){}
+void logsend(int, int, uchar*, char*, char*, int){}
--- /dev/null
+++ b/dnsquery.c
@@ -1,0 +1,119 @@
+#include <u.h>
+#include <libc.h>
+#include <bio.h>
+#include <ctype.h>
+#include <ndb.h>
+#include "dns.h"
+#include "ip.h"
+
+static void
+querydns(int fd, char *line, int n)
+{
+ char buf[8192+1];
+
+ seek(fd, 0, 0);
+ if(write(fd, line, n) != n) {
+ print("!%r\n");
+ return;
+ }
+ seek(fd, 0, 0);
+ while((n = read(fd, buf, sizeof(buf)-1)) > 0){
+ buf[n++] = '\n';
+ write(1, buf, n);
+ }
+}
+
+/*
+ * convert address into a reverse lookup address
+ */
+static void
+mkptrname(char *ip, char *rip, int rlen)
+{
+ uchar a[IPaddrlen];
+ char *p, *e;
+ int i;
+
+ if(cistrstr(ip, "in-addr.arpa") || cistrstr(ip, "ip6.arpa") || parseip(a, ip) == -1)
+ snprint(rip, rlen, "%s", ip);
+ else if(isv4(a))
+ snprint(rip, rlen, "%ud.%ud.%ud.%ud.in-addr.arpa",
+ a[15], a[14], a[13], a[12]);
+ else{
+ p = rip;
+ e = rip + rlen;
+ for(i = 15; i >= 0; i--){
+ p = seprint(p, e, "%ux.", a[i]&0xf);
+ p = seprint(p, e, "%ux.", a[i]>>4);
+ }
+ seprint(p, e, "ip6.arpa");
+ }
+}
+
+static void
+query(int fd)
+{
+ int n;
+ char *lp;
+ char buf[1024], line[1024];
+ Biobuf in;
+
+ Binit(&in, 0, OREAD);
+ for(fprint(2, "> "); lp = Brdline(&in, '\n'); fprint(2, "> ")){
+ n = Blinelen(&in) -1;
+ while(isspace(lp[n]))
+ lp[n--] = 0;
+ n++;
+ while(isspace(*lp)){
+ lp++;
+ n--;
+ }
+ if(!*lp)
+ continue;
+ strcpy(line, lp);
+
+ /* default to an "ip" request if alpha, "ptr" if numeric */
+ if(strchr(line, ' ') == nil)
+ if(strcmp(ipattr(line), "ip") == 0) {
+ strcat(line, " ptr");
+ n += 4;
+ } else {
+ strcat(line, " ip");
+ n += 3;
+ }
+
+ if(n > 4 && strcmp(" ptr", &line[n-4]) == 0){
+ line[n-4] = 0;
+ mkptrname(line, buf, sizeof buf);
+ n = snprint(line, sizeof line, "%s ptr", buf);
+ }
+
+ querydns(fd, line, n);
+ }
+ Bterm(&in);
+}
+
+void
+main(int argc, char *argv[])
+{
+ char *dns = "/net/dns";
+ int fd;
+
+ ARGBEGIN {
+ case 'x':
+ dns = "/net.alt/dns";
+ break;
+ default:
+ fprint(2, "usage: %s [-x] [/net/dns]\n", argv0);
+ exits("usage");
+ } ARGEND;
+
+ if(argc > 0)
+ dns = argv[0];
+
+ fd = open(dns, ORDWR);
+ if(fd < 0)
+ sysfatal("can't open %s: %r", dns);
+
+ query(fd);
+ exits(0);
+}
--- /dev/null
+++ b/dnstcp.c
@@ -1,0 +1,434 @@
+/*
+ * dnstcp - serve dns via tcp
+ */
+#include <u.h>
+#include <libc.h>
+#include <bio.h>
+#include <ndb.h>
+#include <ip.h>
+#include "dns.h"
+
+Cfg cfg;
+
+char *caller = "";
+char *dbfile;
+int anyone;
+int debug;
+char *logfile = "dns";
+int maxage = 60*60;
+char mntpt[Maxpath];
+int needrefresh;
+ulong now;
+vlong nowns;
+int traceactivity;
+char *zonerefreshprogram;
+
+static int readmsg(int, uchar*, int);
+static void reply(int, DNSmsg*, Request*);
+static void dnzone(DNSmsg*, DNSmsg*, Request*, uchar*);
+static void getcaller(char*);
+static void refreshmain(char*);
+
+void
+usage(void)
+{
+ fprint(2, "usage: %s [-adrR] [-f ndbfile] [-x netmtpt] [conndir]\n", argv0);
+ exits("usage");
+}
+
+void
+main(int argc, char *argv[])
+{
+ volatile int len, rcode;
+ volatile char tname[32];
+ char *volatile err, *volatile ext = "";
+ volatile uchar buf[64*1024], callip[IPaddrlen];
+ volatile DNSmsg reqmsg, repmsg;
+ volatile Request req;
+
+ cfg.cachedb = 1;
+ ARGBEGIN{
+ case 'a':
+ anyone++;
+ break;
+ case 'd':
+ debug++;
+ break;
+ case 'f':
+ dbfile = EARGF(usage());
+ break;
+ case 'r':
+ cfg.resolver = 1;
+ break;
+ case 'R':
+ norecursion = 1;
+ break;
+ case 'x':
+ ext = EARGF(usage());
+ break;
+ default:
+ usage();
+ break;
+ }ARGEND
+
+ if(argc > 0)
+ getcaller(argv[0]);
+
+ cfg.inside = 1;
+ dninit();
+
+ if(*ext == '/')
+ snprint(mntpt, sizeof mntpt, "%s", ext);
+ else
+ snprint(mntpt, sizeof mntpt, "/net%s", ext);
+
+ dnslog("dnstcp call from %s", caller);
+ memset(callip, 0, sizeof callip);
+ parseip(callip, caller);
+
+ srand(truerand());
+ db2cache(1);
+
+ memset(&req, 0, sizeof req);
+ setjmp(req.mret);
+ req.isslave = 0;
+ procsetname("main loop");
+
+ alarm(10*1000);
+
+ /* loop on requests */
+ for(;; putactivity(0)){
+ now = time(nil);
+ memset(&repmsg, 0, sizeof repmsg);
+ len = readmsg(0, buf, sizeof buf);
+ if(len <= 0)
+ break;
+
+ getactivity(&req, 0);
+ req.aborttime = timems() + S2MS(15*Min);
+ rcode = 0;
+ memset(&reqmsg, 0, sizeof reqmsg);
+ err = convM2DNS(buf, len, &reqmsg, &rcode);
+ if(err){
+ dnslog("server: input error: %s from %s", err, caller);
+ free(err);
+ break;
+ }
+ if (rcode == 0)
+ if(reqmsg.qdcount < 1){
+ dnslog("server: no questions from %s", caller);
+ break;
+ } else if(reqmsg.flags & Fresp){
+ dnslog("server: reply not request from %s",
+ caller);
+ break;
+ } else if((reqmsg.flags & Omask) != Oquery){
+ dnslog("server: op %d from %s",
+ reqmsg.flags & Omask, caller);
+ break;
+ }
+
+ if(reqmsg.qd == nil){
+ dnslog("server: no question RR from %s", caller);
+ break;
+ }
+
+ if(debug)
+ dnslog("[%d] %d: serve (%s) %d %s %s",
+ getpid(), req.id, caller,
+ reqmsg.id, reqmsg.qd->owner->name,
+ rrname(reqmsg.qd->type, tname, sizeof tname));
+
+ /* loop through each question */
+ while(reqmsg.qd)
+ if(reqmsg.qd->type == Taxfr)
+ dnzone(&reqmsg, &repmsg, &req, callip);
+ else {
+ dnserver(&reqmsg, &repmsg, &req, callip, rcode);
+ reply(1, &repmsg, &req);
+ rrfreelist(repmsg.qd);
+ rrfreelist(repmsg.an);
+ rrfreelist(repmsg.ns);
+ rrfreelist(repmsg.ar);
+ }
+ rrfreelist(reqmsg.qd); /* qd will be nil */
+ rrfreelist(reqmsg.an);
+ rrfreelist(reqmsg.ns);
+ rrfreelist(reqmsg.ar);
+
+ if(req.isslave){
+ putactivity(0);
+ _exits(0);
+ }
+ }
+ refreshmain(mntpt);
+}
+
+static int
+readmsg(int fd, uchar *buf, int max)
+{
+ int n;
+ uchar x[2];
+
+ if(readn(fd, x, 2) != 2)
+ return -1;
+ n = x[0]<<8 | x[1];
+ if(n > max)
+ return -1;
+ if(readn(fd, buf, n) != n)
+ return -1;
+ return n;
+}
+
+static void
+reply(int fd, DNSmsg *rep, Request *req)
+{
+ int len, rv;
+ char tname[32];
+ uchar buf[64*1024];
+ RR *rp;
+
+ if(debug){
+ dnslog("%d: reply (%s) %s %s %ux",
+ req->id, caller,
+ rep->qd->owner->name,
+ rrname(rep->qd->type, tname, sizeof tname),
+ rep->flags);
+ for(rp = rep->an; rp; rp = rp->next)
+ dnslog("an %R", rp);
+ for(rp = rep->ns; rp; rp = rp->next)
+ dnslog("ns %R", rp);
+ for(rp = rep->ar; rp; rp = rp->next)
+ dnslog("ar %R", rp);
+ }
+
+
+ len = convDNS2M(rep, buf+2, sizeof(buf) - 2);
+ buf[0] = len>>8;
+ buf[1] = len;
+ rv = write(fd, buf, len+2);
+ if(rv != len+2){
+ dnslog("[%d] sending reply: %d instead of %d", getpid(), rv,
+ len+2);
+ exits(0);
+ }
+}
+
+/*
+ * Hash table for domain names. The hash is based only on the
+ * first element of the domain name.
+ */
+extern DN *ht[HTLEN];
+
+static int
+numelem(char *name)
+{
+ int i;
+
+ i = 1;
+ for(; *name; name++)
+ if(*name == '.')
+ i++;
+ return i;
+}
+
+int
+inzone(DN *dp, char *name, int namelen, int depth)
+{
+ int n;
+
+ if(dp->name == nil)
+ return 0;
+ if(numelem(dp->name) != depth)
+ return 0;
+ n = strlen(dp->name);
+ if(n < namelen)
+ return 0;
+ if(cistrcmp(name, dp->name + n - namelen) != 0)
+ return 0;
+ if(n > namelen && dp->name[n - namelen - 1] != '.')
+ return 0;
+ return 1;
+}
+
+static Server*
+findserver(uchar *srcip, Server *servers, Request *req)
+{
+ uchar ip[IPaddrlen];
+ RR *list, *rp;
+ int tmp;
+
+ for(; servers != nil; servers = servers->next){
+ if(strcmp(ipattr(servers->name), "ip") == 0){
+ if(parseip(ip, servers->name) == -1)
+ continue;
+ if(ipcmp(srcip, ip) == 0)
+ return servers;
+ continue;
+ }
+
+ tmp = cfg.resolver;
+ cfg.resolver = 1;
+ list = dnresolve(servers->name, Cin, isv4(srcip)? Ta: Taaaa,
+ req, nil, 0, Recurse, 0, nil);
+ cfg.resolver = tmp;
+
+ for(rp = list; rp != nil; rp = rp->next){
+ if(parseip(ip, rp->ip->name) == -1)
+ continue;
+ if(ipcmp(srcip, ip) == 0)
+ break;
+ }
+ rrfreelist(list);
+ if(rp != nil)
+ return servers;
+ }
+ return nil;
+}
+
+static void
+dnzone(DNSmsg *reqp, DNSmsg *repp, Request *req, uchar *srcip)
+{
+ DN *dp, *ndp;
+ RR r, *rp;
+ int h, depth, found, nlen;
+
+ memset(repp, 0, sizeof(*repp));
+ repp->id = reqp->id;
+ repp->qd = reqp->qd;
+ reqp->qd = reqp->qd->next;
+ repp->qd->next = 0;
+ repp->flags = Fauth | Fresp | Oquery;
+ if(!norecursion)
+ repp->flags |= Fcanrec;
+ dp = repp->qd->owner;
+
+ /* send the soa */
+ repp->an = rrlookup(dp, Tsoa, NOneg);
+ if(repp->an != nil && !anyone && !myip(srcip)
+ && findserver(srcip, repp->an->soa->slaves, req) == nil){
+ dnslog("dnstcp: %I axfr %s - not a dnsslave", srcip, dp->name);
+ rrfreelist(repp->an);
+ repp->an = nil;
+ }
+ reply(1, repp, req);
+ if(repp->an == nil)
+ goto out;
+ rrfreelist(repp->an);
+ repp->an = nil;
+
+ nlen = strlen(dp->name);
+
+ /* construct a breadth-first search of the name space (hard with a hash) */
+ repp->an = &r;
+ for(depth = numelem(dp->name); ; depth++){
+ found = 0;
+ for(h = 0; h < HTLEN; h++)
+ for(ndp = ht[h]; ndp; ndp = ndp->next)
+ if(inzone(ndp, dp->name, nlen, depth)){
+ for(rp = ndp->rr; rp; rp = rp->next){
+ /*
+ * there shouldn't be negatives,
+ * but just in case.
+ * don't send any soa's,
+ * ns's are enough.
+ */
+ if (rp->negative ||
+ rp->type == Tsoa)
+ continue;
+ r = *rp;
+ r.next = 0;
+ reply(1, repp, req);
+ }
+ found = 1;
+ }
+ if(!found)
+ break;
+ }
+
+ /* resend the soa */
+ repp->an = rrlookup(dp, Tsoa, NOneg);
+ reply(1, repp, req);
+ rrfreelist(repp->an);
+ repp->an = nil;
+out:
+ rrfree(repp->qd);
+ repp->qd = nil;
+}
+
+static void
+getcaller(char *dir)
+{
+ int fd, n;
+ static char remote[128];
+
+ snprint(remote, sizeof(remote), "%s/remote", dir);
+ fd = open(remote, OREAD);
+ if(fd < 0)
+ return;
+ n = read(fd, remote, sizeof remote - 1);
+ close(fd);
+ if(n <= 0)
+ return;
+ if(remote[n-1] == '\n')
+ n--;
+ remote[n] = 0;
+ caller = remote;
+}
+
+static void
+refreshmain(char *net)
+{
+ int fd;
+ char file[128];
+
+ snprint(file, sizeof(file), "%s/dns", net);
+ if(debug)
+ dnslog("refreshing %s", file);
+ fd = open(file, ORDWR);
+ if(fd < 0)
+ dnslog("can't refresh %s", file);
+ else {
+ fprint(fd, "refresh");
+ close(fd);
+ }
+}
+
+/*
+ * the following varies between dnsdebug and dns
+ */
+void
+logreply(int id, uchar *addr, DNSmsg *mp)
+{
+ RR *rp;
+
+ dnslog("%d: rcvd %I flags:%s%s%s%s%s", id, addr,
+ mp->flags & Fauth? " auth": "",
+ mp->flags & Ftrunc? " trunc": "",
+ mp->flags & Frecurse? " rd": "",
+ mp->flags & Fcanrec? " ra": "",
+ (mp->flags & (Fauth|Rmask)) == (Fauth|Rname)? " nx": "");
+ for(rp = mp->qd; rp != nil; rp = rp->next)
+ dnslog("%d: rcvd %I qd %s", id, addr, rp->owner->name);
+ for(rp = mp->an; rp != nil; rp = rp->next)
+ dnslog("%d: rcvd %I an %R", id, addr, rp);
+ for(rp = mp->ns; rp != nil; rp = rp->next)
+ dnslog("%d: rcvd %I ns %R", id, addr, rp);
+ for(rp = mp->ar; rp != nil; rp = rp->next)
+ dnslog("%d: rcvd %I ar %R", id, addr, rp);
+}
+
+void
+logsend(int id, int subid, uchar *addr, char *sname, char *rname, int type)
+{
+ char buf[12];
+
+ dnslog("%d.%d: sending to %I/%s %s %s",
+ id, subid, addr, sname, rname, rrname(type, buf, sizeof buf));
+}
+
+RR*
+getdnsservers(int class)
+{
+ return dnsservers(class);
+}
--- /dev/null
+++ b/dnudpserver.c
@@ -1,0 +1,336 @@
+#include <u.h>
+#include <libc.h>
+#include <ip.h>
+#include "dns.h"
+
+enum {
+ Logqueries = 0,
+};
+
+static int udpannounce(char*);
+static void reply(int, uchar*, DNSmsg*, Request*);
+
+typedef struct Inprogress Inprogress;
+struct Inprogress
+{
+ int inuse;
+ Udphdr uh;
+ DN *owner;
+ ushort type;
+ int id;
+};
+Inprogress inprog[Maxactive+2];
+
+typedef struct Forwtarg Forwtarg;
+struct Forwtarg {
+ char *host;
+ uchar addr[IPaddrlen];
+ int fd;
+ ulong lastdial;
+};
+Forwtarg forwtarg[10];
+int forwtcount;
+
+static char *hmsg = "headers";
+
+/*
+ * record client id and ignore retransmissions.
+ * we're still single thread at this point.
+ */
+static Inprogress*
+clientrxmit(DNSmsg *req, uchar *buf)
+{
+ Inprogress *p, *empty;
+ Udphdr *uh;
+
+ uh = (Udphdr *)buf;
+ empty = nil;
+ for(p = inprog; p < &inprog[Maxactive]; p++){
+ if(p->inuse == 0){
+ if(empty == nil)
+ empty = p;
+ continue;
+ }
+ if(req->id == p->id)
+ if(req->qd->owner == p->owner)
+ if(req->qd->type == p->type)
+ if(memcmp(uh, &p->uh, Udphdrsize) == 0)
+ return nil;
+ }
+ if(empty == nil)
+ return nil; /* shouldn't happen: see slave() & Maxactive def'n */
+
+ empty->id = req->id;
+ empty->owner = req->qd->owner;
+ empty->type = req->qd->type;
+ if (empty->type != req->qd->type)
+ dnslog("clientrxmit: bogus req->qd->type %d", req->qd->type);
+ memmove(&empty->uh, uh, Udphdrsize);
+ empty->inuse = 1;
+ return empty;
+}
+
+int
+addforwtarg(char *host)
+{
+ Forwtarg *tp;
+
+ if (forwtcount >= nelem(forwtarg)) {
+ dnslog("too many forwarding targets");
+ return -1;
+ }
+ tp = forwtarg + forwtcount;
+ if(parseip(tp->addr, host) == -1) {
+ dnslog("can't parse ip %s", host);
+ return -1;
+ }
+ tp->lastdial = time(nil);
+ tp->fd = udpport(mntpt);
+ if (tp->fd < 0)
+ return -1;
+
+ free(tp->host);
+ tp->host = estrdup(host);
+ forwtcount++;
+ return 0;
+}
+
+/*
+ * fast forwarding of incoming queries to other dns servers.
+ * intended primarily for debugging.
+ */
+static void
+redistrib(uchar *buf, int len)
+{
+ uchar save[Udphdrsize];
+ Forwtarg *tp;
+ Udphdr *uh;
+
+ memmove(save, buf, Udphdrsize);
+
+ uh = (Udphdr *)buf;
+ for (tp = forwtarg; tp < forwtarg + forwtcount; tp++)
+ if (tp->fd >= 0) {
+ memmove(uh->raddr, tp->addr, sizeof tp->addr);
+ hnputs(uh->rport, 53); /* dns port */
+ if (write(tp->fd, buf, len) != len) {
+ close(tp->fd);
+ tp->fd = -1;
+ }
+ } else if (tp->host && time(nil) - tp->lastdial > 60) {
+ tp->lastdial = time(nil);
+ tp->fd = udpport(mntpt);
+ }
+
+ memmove(buf, save, Udphdrsize);
+}
+
+/*
+ * a process to act as a dns server for outside requests
+ */
+void
+dnudpserver(char *mntpt)
+{
+ volatile int fd, len, op, rcode;
+ char *volatile err;
+ volatile char tname[32];
+ volatile uchar buf[Udphdrsize + Maxudp + 1024];
+ volatile DNSmsg reqmsg, repmsg;
+ Inprogress *volatile p;
+ volatile Request req;
+ Udphdr *volatile uh;
+
+ /*
+ * fork sharing text, data, and bss with parent.
+ * stay in the same note group.
+ */
+ switch(rfork(RFPROC|RFMEM|RFNOWAIT)){
+ case -1:
+ break;
+ case 0:
+ break;
+ default:
+ return;
+ }
+
+ fd = -1;
+restart:
+ procsetname("udp server announcing");
+ if(fd >= 0)
+ close(fd);
+ while((fd = udpannounce(mntpt)) < 0)
+ sleep(5000);
+
+// procsetname("udp server");
+ memset(&req, 0, sizeof req);
+ if(setjmp(req.mret))
+ putactivity(0);
+ req.isslave = 0;
+ req.id = 0;
+ req.aborttime = 0;
+
+ /* loop on requests */
+ for(;; putactivity(0)){
+ procsetname("served %lud udp; %lud alarms",
+ stats.qrecvdudp, stats.alarms);
+ memset(&repmsg, 0, sizeof repmsg);
+ memset(&reqmsg, 0, sizeof reqmsg);
+
+ alarm(60*1000);
+ len = read(fd, buf, sizeof buf);
+ alarm(0);
+ if(len <= Udphdrsize)
+ goto restart;
+
+ if(forwtcount > 0)
+ redistrib(buf, len);
+
+ uh = (Udphdr*)buf;
+ len -= Udphdrsize;
+
+ // dnslog("read received UDP from %I to %I",
+ // ((Udphdr*)buf)->raddr, ((Udphdr*)buf)->laddr);
+ getactivity(&req, 0);
+ req.aborttime = timems() + Maxreqtm;
+ req.from = smprint("%I", buf);
+ rcode = 0;
+ stats.qrecvdudp++;
+
+ err = convM2DNS(&buf[Udphdrsize], len, &reqmsg, &rcode);
+ if(err){
+ /* first bytes in buf are source IP addr */
+ dnslog("server: input error: %s from %I", err, buf);
+ free(err);
+ goto freereq;
+ }
+ if (rcode == 0)
+ if(reqmsg.qdcount < 1){
+ dnslog("server: no questions from %I", buf);
+ goto freereq;
+ } else if(reqmsg.flags & Fresp){
+ dnslog("server: reply not request from %I", buf);
+ goto freereq;
+ }
+ op = reqmsg.flags & Omask;
+ if(op != Oquery && op != Onotify){
+ dnslog("server: op %d from %I", reqmsg.flags & Omask,
+ buf);
+ goto freereq;
+ }
+
+ if(reqmsg.qd == nil){
+ dnslog("server: no question RR from %I", buf);
+ goto freereq;
+ }
+
+ if(debug || (trace && subsume(trace, reqmsg.qd->owner->name)))
+ dnslog("%d: serve (%I/%d) %d %s %s",
+ req.id, buf, uh->rport[0]<<8 | uh->rport[1],
+ reqmsg.id, reqmsg.qd->owner->name,
+ rrname(reqmsg.qd->type, tname, sizeof tname));
+
+ p = clientrxmit(&reqmsg, buf);
+ if(p == nil){
+ if(debug)
+ dnslog("%d: duplicate", req.id);
+ goto freereq;
+ }
+
+ if (Logqueries) {
+ RR *rr;
+
+ for (rr = reqmsg.qd; rr; rr = rr->next)
+ syslog(0, "dnsq", "id %d: (%I/%d) %d %s %s",
+ req.id, buf, uh->rport[0]<<8 |
+ uh->rport[1], reqmsg.id,
+ reqmsg.qd->owner->name,
+ rrname(reqmsg.qd->type, tname,
+ sizeof tname));
+ }
+ /* loop through each question */
+ while(reqmsg.qd){
+ memset(&repmsg, 0, sizeof repmsg);
+ switch(op){
+ case Oquery:
+ dnserver(&reqmsg, &repmsg, &req, buf, rcode);
+ break;
+ case Onotify:
+ dnnotify(&reqmsg, &repmsg, &req);
+ break;
+ }
+ /* send reply on fd to address in buf's udp hdr */
+ reply(fd, buf, &repmsg, &req);
+ freeanswers(&repmsg);
+ }
+
+ p->inuse = 0;
+freereq:
+ free(req.from);
+ req.from = nil;
+ freeanswers(&reqmsg);
+ if(req.isslave){
+ putactivity(0);
+ _exits(0);
+ }
+ }
+}
+
+/*
+ * announce on well-known dns udp port and set message style interface
+ */
+static int
+udpannounce(char *mntpt)
+{
+ int data, ctl;
+ char dir[64], datafile[64+6];
+ static int whined;
+
+ /* get a udp port */
+ sprint(datafile, "%s/udp!*!dns", mntpt);
+ ctl = announce(datafile, dir);
+ if(ctl < 0){
+ if(!whined++)
+ warning("can't announce on %s", datafile);
+ return -1;
+ }
+
+ /* turn on header style interface */
+ if(write(ctl, hmsg, strlen(hmsg)) != strlen(hmsg)){
+ close(ctl);
+ if(!whined++)
+ warning("can't enable headers on %s", datafile);
+ return -1;
+ }
+
+ snprint(datafile, sizeof(datafile), "%s/data", dir);
+ data = open(datafile, ORDWR);
+ if(data < 0){
+ close(ctl);
+ if(!whined++)
+ warning("can't open %s to announce on dns udp port",
+ datafile);
+ return -1;
+ }
+
+ close(ctl);
+ return data;
+}
+
+static void
+reply(int fd, uchar *buf, DNSmsg *rep, Request *reqp)
+{
+ int len;
+ char tname[32];
+
+ if(debug || (trace && subsume(trace, rep->qd->owner->name)))
+ dnslog("%d: reply (%I/%d) %d %s %s qd %R an %R ns %R ar %R",
+ reqp->id, buf, buf[4]<<8 | buf[5],
+ rep->id, rep->qd->owner->name,
+ rrname(rep->qd->type, tname, sizeof tname),
+ rep->qd, rep->an, rep->ns, rep->ar);
+
+ len = convDNS2M(rep, &buf[Udphdrsize], Maxudp);
+ len += Udphdrsize;
+ if(write(fd, buf, len) != len)
+ dnslog("error sending reply: %r");
+}
--- /dev/null
+++ b/inform.c
@@ -1,0 +1,272 @@
+/* RFC2136 DNS inform - necessary for Win2k3 DNS servers */
+#include <u.h>
+#include <libc.h>
+#include <bio.h>
+#include <ndb.h>
+#include <ip.h>
+#include "dns.h"
+
+enum {
+ FQDNMAX = 255,
+};
+
+char *errmsgs[] = {
+ [0] "ok",
+ [1] "request format error",
+ [2] "internal server error",
+ [3] "domain name does not exist",
+ [4] "request not supported",
+ [5] "permission denied",
+ [6] "domain name already exists",
+ [7] "resource record already exists",
+ [8] "resource record does not exist",
+ [9] "server not authoritative",
+ [10] "domain name not in zone",
+};
+
+static uchar loopbacknet[IPaddrlen] = {
+ 0, 0, 0, 0,
+ 0, 0, 0, 0,
+ 0, 0, 0xff, 0xff,
+ 127, 0, 0, 0
+};
+static uchar loopbackmask[IPaddrlen] = {
+ 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0, 0, 0
+};
+static uchar loopback6[IPaddrlen] = {
+ 0, 0, 0, 0,
+ 0, 0, 0, 0,
+ 0, 0, 0, 0,
+ 0, 0, 0, 1
+};
+
+void
+usage(void)
+{
+ fprint(2, "usage: %s [-x netmtpt]\n", argv0);
+ exits("usage");
+}
+
+void
+ding(void *, char *msg)
+{
+ if(strstr(msg, "alarm") != nil)
+ noted(NCONT);
+ noted(NDFLT);
+}
+
+int
+g16(uchar **p)
+{
+ int n;
+
+ n = *(*p)++ << 8;
+ n |= *(*p)++;
+ return n;
+}
+
+void
+p16(uchar **p, int n)
+{
+ *(*p)++ = n >> 8;
+ *(*p)++ = n;
+}
+
+void
+p32(uchar **p, int n)
+{
+ *(*p)++ = n >> 24;
+ *(*p)++ = n >> 16;
+ *(*p)++ = n >> 8;
+ *(*p)++ = n;
+}
+
+void
+pmem(uchar **p, void *v, int len)
+{
+ memmove(*p, v, len);
+ *p += len;
+}
+
+void
+pname(uchar **p, char *s)
+{
+ uchar *len;
+
+ while (*s){
+ len = (*p)++;
+ while(*s && *s != '.')
+ *(*p)++ = *s++;
+ *len = *p - len - 1;
+ if(*s == '.')
+ s++;
+ }
+ *(*p)++ = 0;
+}
+
+void
+main(int argc, char *argv[])
+{
+ static char *query[] = { "dom", "dnsdomain", "ns", "inform" };
+ char *sysname, *dnsdomain, *dom, *inform, *ns, net[32], dn[256], ds[256];
+ int debug, len, fd;
+ uint err;
+ uchar *p, buf[4096], mynet[IPaddrlen];
+ ushort txid;
+ Ndb *db;
+ Ndbtuple *t, *tt;
+ Ipifc *ifc;
+ Iplifc *lifc;
+
+ fmtinstall('I', eipfmt);
+ fmtinstall('V', eipfmt);
+ setnetmtpt(net, sizeof net, nil);
+
+ debug = 0;
+ ns = nil;
+ dom = nil;
+ inform = nil;
+ dnsdomain = nil;
+ ARGBEGIN{
+ case 'd':
+ debug = 1;
+ break;
+ case 'x':
+ setnetmtpt(net, sizeof net, EARGF(usage()));
+ break;
+ default:
+ usage();
+ }ARGEND;
+
+ if(argc != 0)
+ usage();
+
+ if((sysname = getenv("sysname")) == nil)
+ sysfatal("$sysname not set");
+
+ if((db = ndbopen(nil)) == nil)
+ sysfatal("can't open ndb: %r");
+ tt = ndbipinfo(db, "sys", sysname, query, nelem(query));
+ for(t = tt; t; t = t->entry){
+ if(strcmp(t->attr, "ns") == 0)
+ ns = t->val;
+ else if(strcmp(t->attr, "dom") == 0)
+ dom = t->val;
+ else if(strcmp(t->attr, "dnsdomain") == 0)
+ dnsdomain = t->val;
+ else if(strcmp(t->attr, "inform") == 0)
+ inform = t->val;
+ }
+
+ ndbfree(tt);
+ ndbclose(db);
+
+ if(inform)
+ dom = inform;
+ if(!ns)
+ sysfatal("no relevant ns=");
+ if(!dom)
+ sysfatal("no relevant dom=");
+ if(!dnsdomain)
+ sysfatal("no relevant dnsdomain=");
+
+
+ if(utf2idn(dom, dn, sizeof(dn)) <= 0)
+ sysfatal("cannot convert dom");
+
+ if(utf2idn(dnsdomain, ds, sizeof(ds)) <= 0)
+ sysfatal("cannot convert dnsdomain");
+
+ if(debug){
+ print("ns=%s\n", ns);
+ print("dnsdomain=%s\n", dnsdomain);
+ print("dom=%s\n", dom);
+ }
+
+ if((fd = dial(netmkaddr(ns, "udp", "dns"), 0, 0, 0)) < 0)
+ sysfatal("can't dial %s: %r", ns);
+
+ txid = time(nil) + getpid();
+
+ p = buf;
+ p16(&p, txid); /* ID */
+ p16(&p, 5<<11); /* flags */
+ p16(&p, 1); /* # Zones */
+ p16(&p, 0); /* # prerequisites */
+ p16(&p, 2); /* # updates */
+ p16(&p, 0); /* # additionals */
+
+ pname(&p, ds); /* zone */
+ p16(&p, Tsoa); /* zone type */
+ p16(&p, Cin); /* zone class */
+
+ /* delete old name */
+ pname(&p, dn); /* name */
+ p16(&p, Ta); /* type: v4 addr */
+ p16(&p, Call); /* class */
+ p32(&p, 0); /* TTL */
+ p16(&p, 0); /* data len */
+
+ pname(&p, dn); /* name */
+ p16(&p, Taaaa); /* type: v6 addr */
+ p16(&p, Call); /* class */
+ p32(&p, 0); /* TTL */
+ p16(&p, 0); /* data len */
+
+ for(ifc = readipifc(net, nil, -1); ifc != nil; ifc = ifc->next){
+ for(lifc = ifc->lifc; lifc != nil; lifc = lifc->next){
+ /* unspecified */
+ if(ipcmp(lifc->ip, IPnoaddr) == 0)
+ continue;
+
+ /* ipv6 loopback */
+ if(ipcmp(lifc->ip, loopback6) == 0)
+ continue;
+
+ /* ipv4 loopback */
+ maskip(lifc->ip, loopbackmask, mynet);
+ if(ipcmp(mynet, loopbacknet) == 0)
+ continue;
+
+ if(debug)
+ print("ip=%I\n", lifc->ip);
+
+ /* add new A record */
+ pname(&p, dn); /* name */
+ p16(&p, isv4(lifc->ip)?Ta:Taaaa);
+ p16(&p, Cin); /* class */
+ p32(&p, 60*60*25); /* TTL (25 hours) */
+ if(isv4(lifc->ip)){
+ p16(&p, IPv4addrlen);
+ pmem(&p, lifc->ip+IPv4off, IPv4addrlen);
+ } else {
+ p16(&p, IPaddrlen);
+ pmem(&p, lifc->ip, IPaddrlen);
+ }
+ }
+ }
+
+ len = p - buf;
+ if(write(fd, buf, len) != len)
+ sysfatal("write failed: %r");
+
+ notify(ding);
+ alarm(3000);
+ do{
+ if(read(fd, buf, sizeof buf) < 0)
+ sysfatal("timeout");
+ p = buf;
+ }while(g16(&p) != txid);
+ alarm(0);
+
+ err = g16(&p) & 7;
+ if(err != 0 && err != 7) /* err==7 is just a "yes, I know" warning */
+ if(err < nelem(errmsgs))
+ sysfatal("%s", errmsgs[err]);
+ else
+ sysfatal("unknown dns server error %d", err);
+ exits(nil);
+}
--- /dev/null
+++ b/ipquery.c
@@ -1,0 +1,54 @@
+#include <u.h>
+#include <libc.h>
+#include <bio.h>
+#include <ndb.h>
+#include <ip.h>
+
+/*
+ * search the database for matches
+ */
+
+void
+usage(void)
+{
+ fprint(2, "usage: ipquery attr value rattribute\n");
+ exits("usage");
+}
+
+void
+search(Ndb *db, char *attr, char *val, char **rattr, int nrattr)
+{
+ Ndbtuple *t, *tt;
+
+ tt = ndbipinfo(db, attr, val, rattr, nrattr);
+ for(t = tt; t; t = t->entry)
+ print("%s=%s ", t->attr, t->val);
+ print("\n");
+ ndbfree(tt);
+}
+
+void
+main(int argc, char **argv)
+{
+ Ndb *db;
+ char *dbfile = 0;
+
+ ARGBEGIN{
+ case 'f':
+ dbfile = ARGF();
+ break;
+ }ARGEND;
+
+ if(argc < 3)
+ usage();
+
+ db = ndbopen(dbfile);
+ if(db == 0){
+ fprint(2, "no db files\n");
+ exits("no db");
+ }
+ search(db, argv[0], argv[1], argv+2, argc-2);
+ ndbclose(db);
+
+ exits(0);
+}
--- /dev/null
+++ b/mkdb.c
@@ -1,0 +1,203 @@
+#include <u.h>
+#include <libc.h>
+#include <bio.h>
+#include <ctype.h>
+
+Biobuf in;
+Biobuf out;
+
+enum
+{
+ Empty,
+ Sys,
+ Dk,
+ Ip,
+ Domain,
+};
+
+int
+iscomment(char *name)
+{
+ return *name == '#';
+}
+
+/*
+ * is this a fully specified datakit name?
+ */
+int
+isdk(char *name)
+{
+ int slash;
+
+ slash = 0;
+ for(; *name; name++){
+ if(isalnum(*name))
+ continue;
+ if(*name == '/'){
+ slash = 1;
+ continue;
+ }
+ return 0;
+ }
+ return slash;
+}
+
+/*
+ * Is this an internet domain name?
+ */
+int
+isdomain(char *name)
+{
+ int dot = 0;
+ int alpha = 0;
+
+ for(; *name; name++){
+ if(isalpha(*name) || *name == '-'){
+ alpha = 1;
+ continue;
+ }
+ if(*name == '.'){
+ dot = 1;
+ continue;
+ }
+ if(isdigit(*name))
+ continue;
+ return 0;
+ }
+ return dot && alpha;
+}
+
+/*
+ * is this an ip address?
+ */
+int
+isip(char *name)
+{
+ int dot = 0;
+
+ for(; *name; name++){
+ if(*name == '.'){
+ dot = 1;
+ continue;
+ }
+ if(isdigit(*name))
+ continue;
+ return 0;
+ }
+ return dot;
+}
+
+char tup[64][64];
+int ttype[64];
+int ntup;
+
+void
+tprint(void)
+{
+ int i, tab;
+ char *p;
+
+ tab = 0;
+ for(i = 0; i < ntup; i++){
+ if(ttype[i] == Sys){
+ Bprint(&out, "sys = %s\n", tup[i]);
+ tab = 1;
+ ttype[i] = Empty;
+ break;
+ }
+ }
+ for(i = 0; i < ntup; i++){
+ if(ttype[i] == Empty)
+ continue;
+ if(tab)
+ Bprint(&out, "\t");
+ tab = 1;
+
+ switch(ttype[i]){
+ case Domain:
+ Bprint(&out, "dom=%s\n", tup[i]);
+ break;
+ case Ip:
+ Bprint(&out, "ip=%s\n", tup[i]);
+ break;
+ case Dk:
+ p = strrchr(tup[i], '/');
+ if(p){
+ p++;
+ if((*p == 'C' || *p == 'R')
+ && strncmp(tup[i], "nj/astro/", p-tup[i]) == 0)
+ Bprint(&out, "flavor=console ");
+ }
+ Bprint(&out, "dk=%s\n", tup[i]);
+ break;
+ case Sys:
+ Bprint(&out, "sys=%s\n", tup[i]);
+ break;
+ }
+ }
+}
+
+#define NFIELDS 64
+
+/*
+ * make a database file from a merged uucp/inet database
+ */
+void
+main(void)
+{
+ int n, i, j;
+ char *l;
+ char *fields[NFIELDS];
+ int ftype[NFIELDS];
+ int same, match;
+
+ Binit(&in, 0, OREAD);
+ Binit(&out, 1, OWRITE);
+ ntup = 0;
+ while(l = Brdline(&in, '\n')){
+ l[Blinelen(&in)-1] = 0;
+ n = getfields(l, fields, NFIELDS, 1, " \t");
+ same = 0;
+ for(i = 0; i < n; i++){
+ if(iscomment(fields[i])){
+ n = i;
+ break;
+ }
+ if(isdomain(fields[i])){
+ ftype[i] = Domain;
+ for(j = 0; j < ntup; j++)
+ if(ttype[j] == Domain && strcmp(fields[i], tup[j]) == 0){
+ same = 1;
+ ftype[i] = Empty;
+ break;
+ }
+ } else if(isip(fields[i]))
+ ftype[i] = Ip;
+ else if(isdk(fields[i]))
+ ftype[i] = Dk;
+ else
+ ftype[i] = Sys;
+ }
+ if(!same && ntup){
+ tprint();
+ ntup = 0;
+ }
+ for(i = 0; i < n; i++){
+ match = 0;
+ for(j = 0; j < ntup; j++){
+ if(ftype[i] == ttype[j] && strcmp(fields[i], tup[j]) == 0){
+ match = 1;
+ break;
+ }
+ }
+ if(!match){
+ ttype[ntup] = ftype[i];
+ strcpy(tup[ntup], fields[i]);
+ ntup++;
+ }
+ }
+ }
+ if(ntup)
+ tprint();
+ exits(0);
+}
--- /dev/null
+++ b/mkfile
@@ -1,0 +1,108 @@
+# cs & dns mkfile
+</$objtype/mkfile
+
+TARG = \
+ mkdb\
+ query\
+ mkhash\
+ mkhosts\
+ cs\
+ csquery\
+ dns\
+ dnsquery\
+ dnstcp\
+ dnsdebug\
+ dnsgetip\
+ registry\
+ regquery\
+ ipquery\
+ inform\
+
+DNSOBJ = dns.$O dnudpserver.$O dn.$O dnresolve.$O dblookup.$O dnserver.$O dnnotify.$O\
+ dnarea.$O convM2DNS.$O convDNS2M.$O
+
+DNSTCPOBJ = dnstcp.$O dn.$O dnresolve.$O dblookup.$O dnserver.$O\
+ dnarea.$O convM2DNS.$O convDNS2M.$O
+
+DNSDEBUGOBJ = dnsdebug.$O dn.$O dnresolve.$O dblookup.$O dnserver.$O\
+ dnarea.$O convM2DNS.$O convDNS2M.$O
+
+DNSGETIPOBJ = dnsgetip.$O dn.$O dnresolve.$O dblookup.$O dnserver.$O\
+ dnarea.$O convM2DNS.$O convDNS2M.$O
+
+HFILES = dns.h /$objtype/lib/libndb.a
+
+BIN=/$objtype/bin/ndb
+
+</sys/src/cmd/mkmany
+
+$O.dns: $DNSOBJ
+ $LD -o $target $prereq
+
+$O.dnstcp: $DNSTCPOBJ
+ $LD -o $target $prereq
+
+$O.dnsdebug: $DNSDEBUGOBJ
+ $LD -o $target $prereq
+
+$O.dnsgetip: $DNSGETIPOBJ
+ $LD -o $target $prereq
+
+$O.cs: cs.$O
+ $LD -o $target $prereq
+
+$O.testipinfo: testipinfo.$O ipinfo.$O
+ $LD -o $target $prereq
+
+$O.registry: registry.$O reglookup.$O
+ $LD -o $target $prereq
+
+push: $O.dns $O.dnsdebug $O.dnstcp
+ import lookout / /n/lookout
+ cp $O.dns /n/lookout/$objtype/bin/ndb/dns
+ cp $O.dnsdebug /n/lookout/$objtype/bin/ndb/dnsdebug
+ cp $O.dnstcp /n/lookout/$objtype/bin/ndb/dnstcp
+ unmount /n/lookout
+ import boundary / /n/boundary
+ cp $O.dns /n/boundary/$objtype/bin/ndb/dns
+ cp $O.dnsdebug /n/boundary/$objtype/bin/ndb/dnsdebug
+ cp $O.dnstcp /n/boundary/$objtype/bin/ndb/dnstcp
+ unmount /n/boundary
+
+cs.safeinstall:V: $O.cs
+ if(test -e $BIN/ooocs)
+ mv $BIN/ooocs $BIN/oooocs
+ if(test -e $BIN/oocs)
+ mv $BIN/oocs $BIN/ooocs
+ if(test -e $BIN/ocs)
+ mv $BIN/ocs $BIN/oocs
+ mv $BIN/cs $BIN/ocs
+ cp $O.cs $BIN/cs
+
+dns.safeinstall:V: $O.dns
+ if(test -e $BIN/ooodns)
+ mv $BIN/ooodns $BIN/oooodns
+ if(test -e $BIN/oodns)
+ mv $BIN/oodns $BIN/ooodns
+ if(test -e $BIN/odns)
+ mv $BIN/odns $BIN/oodns
+ mv $BIN/dns $BIN/odns
+ cp $O.dns $BIN/dns
+
+%.safeinstallall:V:
+ for (objtype in $CPUS)
+ mk $stem.safeinstall
+
+dns.update:V:
+ SRC=`{echo $DNSOBJ|sed 's/\.'$O'/.c/g'}
+ update $UPDATEFLAGS dns.h $SRC
+
+%.update:V:
+ update $UPDATEFLAGS $stem.c
+
+update:V:
+ mk clean
+ T=`{echo $TARG|sed 's/( |$)/.update /g'}
+ mk 'UPDATEFLAGS='$"UPDATEFLAGS $T
+ update $UPDATEFLAGS mkfile
+
--- /dev/null
+++ b/mkhash.c
@@ -1,0 +1,155 @@
+#include <u.h>
+#include <libc.h>
+#include <bio.h>
+#include <ndb.h>
+
+/*
+ * make the hash table completely in memory and then write as a file
+ */
+
+uchar *ht;
+ulong hlen;
+Ndb *db;
+ulong nextchain;
+
+char*
+syserr(void)
+{
+ static char buf[ERRMAX];
+
+ errstr(buf, sizeof buf);
+ return buf;
+}
+
+void
+enter(char *val, ulong dboff)
+{
+ ulong h;
+ uchar *last;
+ ulong ptr;
+
+ h = ndbhash(val, hlen);
+ h *= NDBPLEN;
+ last = &ht[h];
+ ptr = NDBGETP(last);
+ if(ptr == NDBNAP){
+ NDBPUTP(dboff, last);
+ return;
+ }
+
+ if(ptr & NDBCHAIN){
+ /* walk the chain to the last entry */
+ for(;;){
+ ptr &= ~NDBCHAIN;
+ last = &ht[ptr+NDBPLEN];
+ ptr = NDBGETP(last);
+ if(ptr == NDBNAP){
+ NDBPUTP(dboff, last);
+ return;
+ }
+ if(!(ptr & NDBCHAIN)){
+ NDBPUTP(nextchain|NDBCHAIN, last);
+ break;
+ }
+ }
+ } else
+ NDBPUTP(nextchain|NDBCHAIN, last);
+
+ /* add a chained entry */
+ NDBPUTP(ptr, &ht[nextchain]);
+ NDBPUTP(dboff, &ht[nextchain + NDBPLEN]);
+ nextchain += 2*NDBPLEN;
+}
+
+uchar nbuf[16*1024];
+
+void
+main(int argc, char **argv)
+{
+ Ndbtuple *t, *nt;
+ int n;
+ Dir *d;
+ uchar buf[8];
+ char file[128];
+ int fd;
+ ulong off;
+ uchar *p;
+
+ if(argc != 3){
+ fprint(2, "usage: mkhash file attribute\n");
+ exits("usage");
+ }
+ db = ndbopen(argv[1]);
+ if(db == 0){
+ fprint(2, "mkhash: can't open %s\n", argv[1]);
+ exits(syserr());
+ }
+
+ /* try a bigger than normal buffer */
+ Binits(&db->b, Bfildes(&db->b), OREAD, nbuf, sizeof(nbuf));
+
+ /* count entries to calculate hash size */
+ n = 0;
+
+ while(nt = ndbparse(db)){
+ for(t = nt; t; t = t->entry){
+ if(strcmp(t->attr, argv[2]) == 0)
+ n++;
+ }
+ ndbfree(nt);
+ }
+
+ /* allocate an array large enough for worst case */
+ hlen = 2*n+1;
+ n = hlen*NDBPLEN + hlen*2*NDBPLEN;
+ ht = mallocz(n, 1);
+ if(ht == 0){
+ fprint(2, "mkhash: not enough memory\n");
+ exits(syserr());
+ }
+ for(p = ht; p < &ht[n]; p += NDBPLEN)
+ NDBPUTP(NDBNAP, p);
+ nextchain = hlen*NDBPLEN;
+
+ /* create the in core hash table */
+ Bseek(&db->b, 0, 0);
+ off = 0;
+ while(nt = ndbparse(db)){
+ for(t = nt; t; t = t->entry){
+ if(strcmp(t->attr, argv[2]) == 0)
+ enter(t->val, off);
+ }
+ ndbfree(nt);
+ off = Boffset(&db->b);
+ }
+
+ /* create the hash file */
+ snprint(file, sizeof(file), "%s.%s", argv[1], argv[2]);
+ fd = create(file, ORDWR, 0664);
+ if(fd < 0){
+ fprint(2, "mkhash: can't create %s\n", file);
+ exits(syserr());
+ }
+ NDBPUTUL(db->mtime, buf);
+ NDBPUTUL(hlen, buf+NDBULLEN);
+ if(write(fd, buf, NDBHLEN) != NDBHLEN){
+ fprint(2, "mkhash: writing %s\n", file);
+ exits(syserr());
+ }
+ if(write(fd, ht, nextchain) != nextchain){
+ fprint(2, "mkhash: writing %s\n", file);
+ exits(syserr());
+ }
+ close(fd);
+
+ /* make sure file didn't change while we were making the hash */
+ d = dirstat(argv[1]);
+ if(d == nil || d->qid.path != db->qid.path
+ || d->qid.vers != db->qid.vers){
+ fprint(2, "mkhash: %s changed underfoot\n", argv[1]);
+ remove(file);
+ exits("changed");
+ }
+
+ exits(0);
+}
--- /dev/null
+++ b/mkhosts.c
@@ -1,0 +1,233 @@
+#include <u.h>
+#include <libc.h>
+#include <bio.h>
+#include <ndb.h>
+#include <ip.h>
+
+typedef struct x
+{
+ Ndbtuple *t;
+ Ndbtuple *it;
+ Ndbtuple *nt;
+} X;
+
+X x[4096];
+int nx;
+char *domname = "research.att.com";
+int domnamlen;
+
+char*
+upper(char *x)
+{
+ char *p;
+ int c;
+
+ for(p = x; c = *p; p++)
+ *p = toupper(c);
+ return x;
+}
+
+void
+printArecord(int fd, X *p)
+{
+ Ndbtuple *nt;
+ char *c;
+ char *dom = 0;
+ char *curdom = 0;
+ int i, cdlen = 0;
+ int mxweight = 0;
+
+ if(p->nt) {
+ return;
+ }
+ for(nt=p->t; nt; nt = nt->entry) {
+ /* we are only going to handle things in the specified domain */
+ c = strchr(nt->val, '.');
+ if (c==0 || strcmp(++c, domname)!=0)
+ continue;
+ i = c - nt->val - 1;
+ if(strcmp(nt->attr, "dom") == 0) {
+ curdom = nt->val;
+ cdlen = i;
+ if (dom == 0) {
+ dom = curdom;
+ fprint(fd, "%-.*s%.*s IN A %s\n", i, nt->val, 15-i, " ", p->it->val);
+ } else
+ fprint(fd, "%-.*s%.*s IN CNAME %s.\n", i, nt->val, 15-i, " ", dom);
+ } else if(strcmp(nt->attr, "mx") == 0) {
+ if (curdom != 0)
+ fprint(fd, "%-.*s%.*s MX %d %s.\n", cdlen, curdom, 15-cdlen, " ", mxweight++, nt->val);
+ }
+ }
+}
+
+void
+printentry(int fd, X *p)
+{
+ Ndbtuple *nt;
+
+ if(p->nt)
+ return;
+ fprint(fd, "%s ", p->it->val);
+ for(nt = p->t; nt; nt = nt->entry)
+ if(strcmp(nt->attr, "dom") == 0)
+ fprint(fd, " %s", nt->val);
+ for(nt = p->t; nt; nt = nt->entry)
+ if(strcmp(nt->attr, "sys") == 0)
+ fprint(fd, " %s", nt->val);
+ fprint(fd, "\n");
+}
+
+void
+printsys(int fd, X *p)
+{
+ Ndbtuple *nt;
+
+ for(nt = p->t; nt; nt = nt->entry)
+ if(strcmp(nt->attr, "dom") == 0)
+ fprint(fd, "%s\n", nt->val);
+}
+
+void
+printtxt(int fd, X *p)
+{
+ int i;
+ Ndbtuple *nt;
+
+ if(p->nt){
+ for(;;){
+ i = strlen(p->it->val);
+ if(strcmp(p->it->val+i-2, ".0") == 0)
+ p->it->val[i-2] = 0;
+ else
+ break;
+ }
+ fprint(fd, "\nNET : %s : %s\n", p->it->val, upper(p->nt->val));
+ return;
+ }
+ fprint(fd, "HOST : %s :", p->it->val);
+ i = 0;
+ for(nt = p->t; nt; nt = nt->entry)
+ if(strcmp(nt->attr, "dom") == 0){
+ if(i++ == 0)
+ fprint(fd, " %s", upper(nt->val));
+ else
+ fprint(fd, ", %s", upper(nt->val));
+ }
+ fprint(fd, "\n");
+}
+
+void
+parse(char *file)
+{
+ int i;
+ Ndb *db;
+ Ndbtuple *t, *nt, *tt, *ipnett;
+ char *p;
+
+ db = ndbopen(file);
+ if(db == 0)
+ exits("no database");
+ while(t = ndbparse(db)){
+ for(nt = t; nt; nt = nt->entry){
+ if(strcmp(nt->attr, "ip") == 0)
+ break;
+ if(strcmp(nt->attr, "flavor") == 0
+ && strcmp(nt->val, "console") == 0)
+ return;
+ }
+ if(nt == 0){
+ ndbfree(t);
+ continue;
+ }
+
+ /* dump anything not on our nets */
+ ipnett = 0;
+ for(tt = t; tt; tt = tt->entry){
+ if(strcmp(tt->attr, "ipnet") == 0){
+ ipnett = tt;
+ break;
+ }
+ if(strcmp(tt->attr, "dom") == 0){
+ i = strlen(tt->val);
+ p = tt->val+i-domnamlen;
+ if(p >= tt->val && strcmp(p, domname) == 0)
+ break;
+ }
+ }
+ if(tt == 0){
+ ndbfree(t);
+ continue;
+ }
+
+ for(; nt; nt = nt->entry){
+ if(strcmp(nt->attr, "ip") != 0)
+ continue;
+ x[nx].it = nt;
+ x[nx].nt = ipnett;
+ x[nx++].t = t;
+ }
+ }
+}
+
+void
+main(int argc, char *argv[])
+{
+ int i, fd;
+ char fn[128];
+
+ if (argc>1)
+ domname = argv[1];
+ domnamlen = strlen(domname);
+ if(argc > 2){
+ for(i = 2; i < argc; i++)
+ parse(argv[i]);
+ } else {
+ parse("/lib/ndb/local");
+ parse("/lib/ndb/friends");
+ }
+
+// sprint(fn, "/lib/ndb/hosts.%-.21s", domname);
+// fd = create(fn, OWRITE, 0664);
+// if(fd < 0){
+// fprint(2, "can't create %s: %r\n", fn);
+// exits("boom");
+// }
+// for(i = 0; i < nx; i++)
+// printentry(fd, &x[i]);
+// close(fd);
+//
+
+ sprint(fn, "/lib/ndb/db.%-.24s", domname);
+ fd = create(fn, OWRITE, 0664);
+ if(fd < 0){
+ fprint(2, "can't create %s: %r\n", fn);
+ exits("boom");
+ }
+ fprint(fd, "; This file is generated automatically, do not edit!\n");
+ for(i = 0; i < nx; i++)
+ printArecord(fd, &x[i]);
+ close(fd);
+
+ sprint(fn, "/lib/ndb/equiv.%-.21s", domname);
+ fd = create(fn, OWRITE, 0664);
+ if(fd < 0){
+ fprint(2, "can't create %s: %r\n", fn);
+ exits("boom");
+ }
+ for(i = 0; i < nx; i++)
+ printsys(fd, &x[i]);
+ close(fd);
+
+ sprint(fn, "/lib/ndb/txt.%-.23s", domname);
+ fd = create(fn, OWRITE, 0664);
+ if(fd < 0){
+ fprint(2, "can't create %s: %r\n", fn);
+ exits("boom");
+ }
+ for(i = 0; i < nx; i++)
+ printtxt(fd, &x[i]);
+ close(fd);
+
+ exits(0);
+}
--- /dev/null
+++ b/query.c
@@ -1,0 +1,114 @@
+/*
+ * search the network database for matches
+ */
+#include <u.h>
+#include <libc.h>
+#include <bio.h>
+#include <ndb.h>
+
+static int all, multiple;
+static Biobuf bout;
+
+void
+usage(void)
+{
+ fprint(2, "usage: query [-am] [-f ndbfile] attr value "
+ "[returned-attr [reps]]\n");
+ exits("usage");
+}
+
+/* print values of nt's attributes matching rattr */
+static void
+prmatch(Ndbtuple *nt, char *rattr)
+{
+ for(; nt; nt = nt->entry)
+ if (strcmp(nt->attr, rattr) == 0)
+ Bprint(&bout, "%s\n", nt->val);
+}
+
+void
+search(Ndb *db, char *attr, char *val, char *rattr)
+{
+ char *p;
+ Ndbs s;
+ Ndbtuple *t, *nt;
+
+ /* first entry with a matching rattr */
+ if(rattr && !all){
+ p = ndbgetvalue(db, &s, attr, val, rattr, &t);
+ if (multiple)
+ prmatch(t, rattr);
+ else if(p)
+ Bprint(&bout, "%s\n", p);
+ ndbfree(t);
+ free(p);
+ return;
+ }
+
+ /* all entries with matching rattrs */
+ if(rattr) {
+ for(t = ndbsearch(db, &s, attr, val); t != nil;
+ t = ndbsnext(&s, attr, val)){
+ prmatch(t, rattr);
+ ndbfree(t);
+ }
+ return;
+ }
+
+ /* all entries */
+ for(t = ndbsearch(db, &s, attr, val); t; t = ndbsnext(&s, attr, val)){
+ for(nt = t; nt; nt = nt->entry)
+ Bprint(&bout, "%s=%s ", nt->attr, nt->val);
+ Bprint(&bout, "\n");
+ ndbfree(t);
+ }
+}
+
+void
+main(int argc, char **argv)
+{
+ int reps = 1;
+ char *rattr = nil, *dbfile = nil;
+ Ndb *db;
+
+ ARGBEGIN{
+ case 'a':
+ all++;
+ break;
+ case 'm':
+ multiple++;
+ break;
+ case 'f':
+ dbfile = EARGF(usage());
+ break;
+ default:
+ usage();
+ }ARGEND;
+
+ switch(argc){
+ case 4:
+ reps = atoi(argv[3]); /* wtf use is this? */
+ /* fall through */
+ case 3:
+ rattr = argv[2];
+ break;
+ case 2:
+ rattr = nil;
+ break;
+ default:
+ usage();
+ }
+
+ if(Binit(&bout, 1, OWRITE) == -1)
+ sysfatal("Binit: %r");
+ db = ndbopen(dbfile);
+ if(db == nil){
+ fprint(2, "%s: no db files\n", argv0);
+ exits("no db");
+ }
+ while(reps--)
+ search(db, argv[0], argv[1], rattr);
+ ndbclose(db);
+
+ exits(0);
+}
--- /dev/null
+++ b/registry.c
@@ -1,0 +1,995 @@
+#include <u.h>
+#include <libc.h>
+#include <auth.h>
+#include <fcall.h>
+#include <bio.h>
+#include <ip.h>
+#include "dns.h"
+
+enum
+{
+ Maxrequest= 1024,
+ Maxreply= 8192,
+ Maxrecords= 192,
+ Maxfdata= 8192,
+
+ Qdir= 0,
+ Qreg= 1,
+};
+
+typedef struct Mfile Mfile;
+typedef struct Job Job;
+typedef struct Records Records;
+
+struct Mfile
+{
+ Mfile *next;
+
+ char *user;
+ Qid qid;
+ int fid;
+ int bare;
+
+ char reply[Maxreply];
+ ushort rr[Maxrecords]; /* offset of record */
+ ushort nrr; /* number of records */
+};
+
+/*
+ * active requests
+ */
+struct Job
+{
+ Job *next;
+ int flushed;
+ Fcall request;
+ Fcall reply;
+};
+Lock joblock;
+Job *joblist;
+
+struct {
+ Lock;
+ Mfile *inuse; /* active mfile's */
+} mfalloc;
+
+Svc *registry;
+int vers;
+int resolver;
+int debug;
+char *dbfile = "/lib/ndb/registry";
+char *reguser;
+char mtpt[Maxpath];
+int rfd[Maxremote];
+int mfd[2];
+char *logfile = "registry";
+
+void rversion(Job*);
+void rflush(Job*);
+void rattach(Job*, Mfile*);
+char* rwalk(Job*, Mfile*);
+void ropen(Job*, Mfile*);
+void rcreate(Job*, Mfile*);
+void rread(Job*, Mfile*);
+void rwrite(Job*, Mfile*);
+void rclunk(Job*, Mfile*);
+void rremove(Job*, Mfile*);
+void rstat(Job*, Mfile*);
+void rwstat(Job*, Mfile*);
+void rauth(Job*);
+void mountinit(char*, char*);
+void setext(char*, int, char*);
+void io(void);
+
+static char* resolve(char*, ...);
+static char* addsvc(char*);
+static char* rmsvc(char*);
+static char* updatesvc(char*);
+static void refresh(void);
+static void regdump(char*);
+static void sendmsg(Job*, char*);
+
+static int scanfmt(Fmt*);
+static int srvfmt(Fmt*);
+static int dumpfmt(Fmt*);
+
+static char* query(Job*, Mfile*, char*, int);
+static char* resolvequery(Job*, Mfile*, char*, int);
+
+void
+usage(void)
+{
+ fprint(2, "usage: %s [-xrd] [-f ndb-file]\n", argv0);
+ exits("usage");
+}
+
+void
+main(int argc, char* argv[])
+{
+ char servefile[Maxpath], ext[Maxpath];
+ Dir *dir;
+ ext[0] = 0;
+
+ setnetmtpt(mtpt, sizeof mtpt, nil);
+
+ ARGBEGIN{
+ case 'r':
+ resolver = 1;
+ break;
+ case 'd':
+ debug = 1;
+ break;
+ case 'f':
+ dbfile = EARGF(usage());
+ break;
+ case 'x':
+ setnetmtpt(mtpt, sizeof mtpt, EARGF(usage()));
+ setext(ext, sizeof ext, mtpt);
+ break;
+ } ARGEND;
+ if(argc != 0)
+ usage();
+
+
+
+ rfork(RFREND|RFNOTEG);
+
+ fmtinstall('F', fcallfmt);
+ fmtinstall('G', srvfmt);
+ fmtinstall('N', scanfmt);
+ fmtinstall('D', dumpfmt);
+
+ reglog("starting registry on %s", mtpt);
+
+ if(! resolver && openregistry())
+ sysfatal("unable to open db file");
+
+ reguser = estrdup(getuser());
+ seprint(servefile, servefile+Maxpath, "#s/registry%s", ext);
+
+ dir = dirstat(servefile);
+ if (dir)
+ sysfatal("%s exists; another registry instance is running", servefile);
+ free(dir);
+
+
+ mountinit(servefile, mtpt);
+
+ if(resolver)
+ regconnect();
+ else
+ reg2cache();
+ io();
+
+ _exits(0);
+}
+
+void
+setext(char *ext, int n, char *p)
+{
+ int i, c;
+
+ n--;
+ for(i = 0; i < n; i++){
+ c = p[i];
+ if(c == 0)
+ break;
+ if(c == '/')
+ c = '_';
+ ext[i] = c;
+ }
+ ext[i] = 0;
+}
+
+void
+mountinit(char *service, char *mtpt)
+{
+ int f;
+ int p[2];
+ char buf[32];
+
+ if(pipe(p) < 0)
+ sysfatal("pipe failed: %r");
+
+ /*
+ * make a /srv/registry
+ */
+ if((f = create(service, OWRITE|ORCLOSE, 0666)) < 0)
+ sysfatal("create %s failed: %r", service);
+ snprint(buf, sizeof buf, "%d", p[1]);
+ if(write(f, buf, strlen(buf)) != strlen(buf))
+ sysfatal("write %s failed: %r", service);
+
+ /* copy namespace to avoid a deadlock */
+ switch(rfork(RFFDG|RFPROC|RFNAMEG)){
+ case 0: /* child: start main proc */
+ close(p[1]);
+ procsetname("%s", mtpt);
+ break;
+ case -1:
+ sysfatal("fork failed: %r");
+ default: /* parent: make /srv/registry, mount it, exit */
+ close(p[0]);
+
+ /*
+ * put ourselves into the file system
+ */
+ if(mount(p[1], -1, mtpt, MAFTER, "") < 0)
+ fprint(2, "registry mount failed: %r\n");
+ _exits(0);
+ }
+ mfd[0] = mfd[1] = p[0];
+}
+
+Mfile*
+newfid(int fid, int needunused)
+{
+ Mfile *mf;
+
+ lock(&mfalloc);
+ for(mf = mfalloc.inuse; mf != nil; mf = mf->next)
+ if(mf->fid == fid){
+ unlock(&mfalloc);
+ if(needunused)
+ return nil;
+ return mf;
+ }
+ mf = emalloc(sizeof(*mf));
+ mf->fid = fid;
+ mf->qid.vers = vers;
+ mf->qid.type = QTDIR;
+ mf->qid.path = 0LL;
+ mf->user = estrdup(reguser);
+ mf->next = mfalloc.inuse;
+ mfalloc.inuse = mf;
+ mf->bare = 1;
+ unlock(&mfalloc);
+ return mf;
+}
+
+void
+freefid(Mfile *mf)
+{
+ Mfile **l;
+
+ lock(&mfalloc);
+ for(l = &mfalloc.inuse; *l != nil; l = &(*l)->next)
+ if(*l == mf){
+ *l = mf->next;
+ free(mf->user);
+ memset(mf, 0, sizeof *mf); /* cause trouble */
+ free(mf);
+ unlock(&mfalloc);
+ return;
+ }
+ unlock(&mfalloc);
+ sysfatal("freeing unused fid");
+}
+
+Mfile*
+copyfid(Mfile *mf, int fid)
+{
+ Mfile *nmf;
+
+ nmf = newfid(fid, 1);
+ if(nmf == nil)
+ return nil;
+ nmf->fid = fid;
+ free(nmf->user);
+ nmf->user = estrdup(mf->user);
+ nmf->qid.type = mf->qid.type;
+ nmf->qid.path = mf->qid.path;
+ nmf->qid.vers = vers++;
+ return nmf;
+}
+
+Job*
+newjob(void)
+{
+ Job *job;
+
+ job = emalloc(sizeof *job);
+ lock(&joblock);
+ job->next = joblist;
+ joblist = job;
+ job->request.tag = -1;
+ unlock(&joblock);
+ return job;
+}
+
+void
+freejob(Job *job)
+{
+ Job **l;
+
+ lock(&joblock);
+ for(l = &joblist; *l; l = &(*l)->next)
+ if(*l == job){
+ *l = job->next;
+ memset(job, 0, sizeof *job); /* cause trouble */
+ free(job);
+ break;
+ }
+ unlock(&joblock);
+}
+
+void
+flushjob(int tag)
+{
+ Job *job;
+
+ lock(&joblock);
+ for(job = joblist; job; job = job->next)
+ if(job->request.tag == tag && job->request.type != Tflush){
+ job->flushed = 1;
+ break;
+ }
+ unlock(&joblock);
+}
+
+void
+io(void)
+{
+ long n;
+ Mfile *mf;
+ uchar mdata[IOHDRSZ + Maxfdata];
+ Job *job;
+
+ while((n = read9pmsg(mfd[0], mdata, sizeof mdata)) != 0){
+ if(n < 0){
+ syslog(1, logfile, "error reading 9P from %s: %r", mtpt);
+ break;
+ }
+
+ job = newjob();
+ if(convM2S(mdata, n, &job->request) != n){
+ reglog("format error %ux %ux %ux %ux %ux",
+ mdata[0], mdata[1], mdata[2], mdata[3], mdata[4]);
+ freejob(job);
+ break;
+ }
+ mf = newfid(job->request.fid, 0);
+ if(debug)
+ reglog("%F", &job->request);
+
+ switch(job->request.type){
+ default:
+ warning("unknown request type %d", job->request.type);
+ break;
+ case Tversion:
+ rversion(job);
+ break;
+ case Tauth:
+ rauth(job);
+ break;
+ case Tflush:
+ rflush(job);
+ break;
+ case Tattach:
+ rattach(job, mf);
+ break;
+ case Twalk:
+ rwalk(job, mf);
+ break;
+ case Topen:
+ ropen(job, mf);
+ break;
+ case Tcreate:
+ rcreate(job, mf);
+ break;
+ case Tread:
+ rread(job, mf);
+ break;
+ case Twrite:
+ rwrite(job, mf);
+ break;
+ case Tclunk:
+ rclunk(job, mf);
+ break;
+ case Tremove:
+ rremove(job, mf);
+ break;
+ case Tstat:
+ rstat(job, mf);
+ break;
+ case Twstat:
+ rwstat(job, mf);
+ break;
+ }
+
+ freejob(job);
+ }
+}
+
+void
+rversion(Job *job)
+{
+ if(job->request.msize > IOHDRSZ + Maxfdata)
+ job->reply.msize = IOHDRSZ + Maxfdata;
+ else
+ job->reply.msize = job->request.msize;
+ job->reply.version = "9P2000";
+ if(strncmp(job->request.version, "9P", 2) != 0)
+ job->reply.version = "unknown";
+ sendmsg(job, nil);
+}
+
+void
+rauth(Job *job)
+{
+ sendmsg(job, "registry: authentication not required");
+}
+
+void
+rflush(Job *job)
+{
+ flushjob(job->request.oldtag);
+ sendmsg(job, 0);
+}
+
+void
+rattach(Job *job, Mfile *mf)
+{
+ if(mf->user != nil)
+ free(mf->user);
+ mf->user = estrdup(job->request.uname);
+ mf->qid.vers = vers++;
+ mf->qid.type = QTDIR;
+ mf->qid.path = 0LL;
+ job->reply.qid = mf->qid;
+ sendmsg(job, 0);
+}
+
+char*
+rwalk(Job *job, Mfile *mf)
+{
+ int i, nelems;
+ char *err;
+ char **elems;
+ Mfile *nmf;
+ Qid qid;
+
+ err = 0;
+ nmf = nil;
+ elems = job->request.wname;
+ nelems = job->request.nwname;
+ job->reply.nwqid = 0;
+
+ if(job->request.newfid != job->request.fid){
+ /* clone fid */
+ nmf = copyfid(mf, job->request.newfid);
+ if(nmf == nil){
+ err = "clone bad newfid";
+ goto send;
+ }
+ mf = nmf;
+ }
+ /* else nmf will be nil */
+
+ qid = mf->qid;
+ if(nelems > 0){
+ /* walk fid */
+ for(i=0; i<nelems && i<MAXWELEM; i++){
+ if((qid.type & QTDIR) == 0){
+ err = "not a directory";
+ break;
+ }
+ if(strcmp(elems[i], "..") == 0 || strcmp(elems[i], ".") == 0){
+ qid.type = QTDIR;
+ qid.path = Qdir;
+ Found:
+ job->reply.wqid[i] = qid;
+ job->reply.nwqid++;
+ continue;
+ }
+ if(strcmp(elems[i], "registry") == 0){
+ qid.type = QTFILE;
+ qid.path = Qreg;
+ goto Found;
+ }
+ err = "file does not exist";
+ break;
+ }
+ }
+
+ send:
+ if(nmf != nil && (err!=nil || job->reply.nwqid<nelems))
+ freefid(nmf);
+ if(err == nil)
+ mf->qid = qid;
+ sendmsg(job, err);
+ return err;
+}
+
+void
+ropen(Job *job, Mfile *mf)
+{
+ int mode;
+ char *err;
+
+ err = 0;
+ mode = job->request.mode;
+ if(mf->qid.type & QTDIR)
+ if(mode)
+ err = "permission denied";
+ job->reply.qid = mf->qid;
+ job->reply.iounit = 0;
+ sendmsg(job, err);
+}
+
+void
+rcreate(Job *job, Mfile *mf)
+{
+ USED(mf);
+ sendmsg(job, "creation permission denied");
+}
+
+void rclunk(Job *job, Mfile *mf)
+{
+ freefid(mf);
+ sendmsg(job, 0);
+}
+
+void
+rremove(Job *job, Mfile *mf)
+{
+ USED(mf);
+ sendmsg(job, "remove permission denied");
+}
+
+void
+rread(Job *job, Mfile *mf)
+{
+ int i, n;
+ long clock;
+ ulong cnt;
+ vlong off;
+ char *err;
+ uchar buf[Maxfdata];
+ Dir dir;
+
+ n = 0;
+ err = nil;
+ off = job->request.offset;
+ cnt = job->request.count;
+ *buf = '\0';
+ job->reply.data = (char*)buf;
+ if(mf->qid.type & QTDIR){
+ clock = time(nil);
+ if(off == 0){
+ memset(&dir, 0, sizeof dir);
+ dir.name = "registry";
+ dir.qid.type = QTFILE;
+ dir.qid.vers = vers;
+ dir.qid.path = Qreg;
+ dir.mode = 0666;
+ dir.length = 0;
+ dir.uid = dir.gid = dir.muid = mf->user;
+ dir.atime = dir.mtime = clock;
+ n = convD2M(&dir, buf, sizeof buf);
+ }
+ } else if (off < 0)
+ err = "negative read offset";
+ else {
+ if(mf->bare)
+ query(job, mf, "all", 0);
+ for(i = 1; i < mf->nrr; i++)
+ if(mf->rr[i] > off)
+ break;
+ if(i <= mf->nrr){
+ if(off + cnt > mf->rr[i])
+ n = mf->rr[i] - off;
+ else
+ n = cnt;
+ assert(n >= 0);
+ job->reply.data = mf->reply + off;
+ }
+ }
+ job->reply.count = n;
+ sendmsg(job, err);
+}
+
+void
+rwrite(Job *job, Mfile *mf)
+{
+ int send, pipe2rc;
+ ulong cnt;
+ char *err, *atype;
+ char errbuf[ERRMAX];
+
+ err = nil;
+ cnt = job->request.count;
+ send = 1;
+ if(mf->qid.type & QTDIR)
+ err = "can't write directory";
+ else if (job->request.offset != 0)
+ err = "writing at non-zero offset";
+ else if (cnt >= Maxrequest)
+ err = "request too long";
+ else
+ send = 0;
+ if(send)
+ goto send;
+
+ job->request.data[cnt] = 0;
+ if(cnt > 0 && job->request.data[cnt-1] == '\n')
+ job->request.data[cnt-1] = 0;
+
+ if(strcmp(mf->user, "none") == 0 || strcmp(mf->user, reguser) != 0)
+ goto query; /* We don't want remote clients to modify our local */
+
+ /*
+ * special commands
+ */
+ send = 1;
+ if(strcmp(job->request.data, "debug")==0)
+ debug ^= 1;
+ else if(strcmp(job->request.data, "dump")==0)
+ regdump("/lib/ndb/regdump");
+ else if (strcmp(job->request.data, "refresh")==0)
+ refresh();
+ else if (strncmp(job->request.data, "add ", 4)==0)
+ err = addsvc(job->request.data + 4);
+ else if (strncmp(job->request.data, "rm ", 3)==0)
+ err = rmsvc(job->request.data + 3);
+ else if (strncmp(job->request.data, "update ", 7)==0)
+ err = updatesvc(job->request.data + 7);
+ else
+ send = 0;
+ if (send)
+ goto send;
+
+query:
+ /*
+ * kill previous reply
+ */
+ mf->nrr = 0;
+ mf->rr[0] = 0;
+ pipe2rc = 0;
+
+ atype = strchr(job->request.data, ' ');
+ if(atype == 0){
+ snprint(errbuf, sizeof errbuf, "illegal request %s", job->request.data);
+ err = errbuf;
+ goto send;
+ } else
+ *atype++ = 0;
+
+ if(strcmp(atype, "svc") == 0)
+ pipe2rc++;
+ else if(strcmp(atype, "scan") != 0){
+ snprint(errbuf, sizeof errbuf, "unknown query %s", atype);
+ err = errbuf;
+ goto send;
+ }
+
+ if(resolver){
+ resolvequery(job, mf, job->request.data, pipe2rc);
+ goto send;
+ }
+ err = query(job, mf,job->request.data, pipe2rc);
+send:
+ mf->bare = 0;
+ job->reply.count = cnt;
+ sendmsg(job, err);
+}
+
+void
+rstat(Job *job, Mfile *mf)
+{
+ Dir dir;
+ uchar buf[IOHDRSZ+Maxfdata];
+
+ memset(&dir, 0, sizeof dir);
+ if(mf->qid.type & QTDIR){
+ dir.name = ".";
+ dir.mode = DMDIR|0555;
+ }else{
+ dir.name = "registry";
+ dir.mode = 0666;
+ }
+ dir.qid = mf->qid;
+ dir.length = 0;
+ dir.uid = dir.gid = dir.muid = mf->user;
+ dir.atime = dir.mtime = time(nil);
+ job->reply.nstat = convD2M(&dir, buf, sizeof buf);
+ job->reply.stat = buf;
+ sendmsg(job, 0);
+}
+
+void
+rwstat(Job *job, Mfile *mf)
+{
+ USED(mf);
+ sendmsg(job, "wstat permission denied");
+}
+
+static char *
+resolvequery(Job *job, Mfile *mf, char *p, int pipe2rc)
+{
+ int match, i;
+ int n;
+ Svc *c;
+
+ char cmd[256];
+ char buf[8192+1];
+
+ lock(&joblock);
+ if(!job->flushed){
+ match = n = 0;
+ mf->nrr = 0;
+
+ snprint(cmd, sizeof(cmd), "%s %s", p, ((pipe2rc)?"svc":"scan"));
+
+ for(i = 0; i < Maxremote && !match && rfd[i] > 1; i++){
+ seek(rfd[i], 0, 0);
+ write(rfd[i], cmd, sizeof cmd);
+
+ seek(rfd[i], 0, 0);
+ while(read(rfd[i], buf, sizeof(buf)-1) > 0){
+ match = 1;
+ c = rstr2svc(buf);
+ mf->rr[mf->nrr++] = n;
+ if(pipe2rc)
+ n += snprint(mf->reply+n, Maxreply-n, "%G", c);
+ else
+ n += snprint(mf->reply+n, Maxreply-n, "%N", c);
+ free(c);
+ }
+ }
+ }
+ unlock(&joblock);
+
+ return 0;
+}
+
+static char *
+query(Job *job, Mfile *mf, char *p, int pipe2rc)
+{
+ int n;
+
+ Svc *c;
+ lock(&joblock);
+ if(!job->flushed){
+ n = 0;
+ mf->nrr = 0;
+ for(c = registry; c && n < Maxreply; c = c->next)
+ if((strncmp(p, c->labl, strlen(p))==0) || (strcmp(p, "all")==0)){
+ mf->rr[mf->nrr++] = n;
+ if(pipe2rc)
+ n += snprint(mf->reply+n, Maxreply-n, "%G", c);
+ else
+ n += snprint(mf->reply+n, Maxreply-n, "%N", c);
+ }
+ mf->rr[mf->nrr] = n;
+ }
+ unlock(&joblock);
+ return nil;
+}
+
+static void
+sendmsg(Job *job, char *err)
+{
+ int n;
+ uchar mdata[IOHDRSZ+Maxfdata];
+ char ename[ERRMAX];
+
+ if(err){
+ job->reply.type = Rerror;
+ snprint(ename, sizeof ename, "registry: %s", err);
+ job->reply.ename = ename;
+ }else
+ job->reply.type = job->request.type+1;
+ job->reply.tag = job->request.tag;
+ n = convS2M(&job->reply, mdata, sizeof mdata);
+ if(n == 0){
+ warning("sendmsg convS2M of %F returns 0", &job->reply);
+ abort();
+ }
+ lock(&joblock);
+ if(job->flushed == 0)
+ if(write(mfd[1], mdata, n)!=n)
+ sysfatal("mount write");
+ unlock(&joblock);
+ if(debug)
+ reglog("%F %d", &job->reply, n);
+}
+
+static void
+regdump(char *file)
+{
+ Svc *rp;
+ int fd;
+
+ if(resolver){
+ resolve("dump");
+ return;
+ }
+
+ fd = create(file, OWRITE, 0666);
+ if(fd < 0)
+ return;
+ lock(&mfalloc);
+ for(rp = registry; rp; rp = rp->next)
+ fprint(fd, "%D\n\n", rp);
+ unlock(&mfalloc);
+ close(fd);
+}
+
+static void
+refresh(void)
+{
+ Svc *c;
+ char dial[Maxdial];
+
+ if(resolver){
+ resolve("refresh");
+ return;
+ }
+
+ for(c = registry; c; c = c->next){
+ /* Don't remove the ones we've added since startup */
+ if(!c->perm)
+ continue;
+ snprint(dial, Maxdial, "%s!%s!%s", c->trns, c->host, c->port);
+ rmsvc(dial);
+ /* Reset so we don't have messy loops */
+ c = registry;
+ }
+ reg2cache();
+}
+
+static char *
+resolve(char *cmd, ...)
+{
+ int n;
+ char fullcmd[256];
+ char buf[8192+1];
+ va_list arg;
+
+ va_start(arg, cmd);
+ vseprint(fullcmd, fullcmd+sizeof(fullcmd), cmd, arg);
+ va_end(arg);
+
+ /* We only operate on our local rfd */
+ seek(rfd[0], 0, 0);
+ write(rfd[0], fullcmd, sizeof fullcmd);
+
+ seek(rfd[0], 0, 0);
+ while((n = read(rfd[0], buf, sizeof(buf)-1)) > 0){
+ buf[n++] = '\n';
+ write(1, buf, n);
+ }
+ return buf;
+}
+
+static char *
+addsvc(char *args)
+{
+ if(debug)
+ reglog("Adding entry: %s", args);
+ if(resolver)
+ return resolve("add", args);
+ return rstr2cache(args, 0);
+}
+
+static char *
+rmsvc(char *args)
+{
+ if(debug)
+ reglog("Removing entry: %s", args);
+ if(resolver)
+ return resolve("rm", args);
+ return rstrdtch(args);
+}
+
+static char *
+updatesvc(char *args)
+{
+ if(debug)
+ reglog("Updating entry: %s", args);
+ if(resolver)
+ return resolve("update", args);
+ return rstrupdt(args);
+}
+
+void
+warning(char *fmt, ...)
+{
+ char regerr[256];
+ va_list arg;
+
+ va_start(arg, fmt);
+ vseprint(regerr, regerr+sizeof(regerr), fmt, arg);
+ va_end(arg);
+ syslog(1, logfile, regerr);
+}
+
+void
+reglog(char *fmt, ...)
+{
+ char regerr[256];
+ va_list arg;
+
+ va_start(arg, fmt);
+ vseprint(regerr, regerr+sizeof(regerr), fmt, arg);
+ va_end(arg);
+ syslog(0, logfile, regerr);
+}
+
+void*
+emalloc(int size)
+{
+ void *x;
+
+ x = malloc(size);
+ if(x == nil)
+ sysfatal("out of memory");
+ memset(x, 0, size);
+ return x;
+}
+
+char*
+estrdup(char *s)
+{
+ int size;
+ char *p;
+
+ size = strlen(s);
+ p = malloc(size+1);
+ if(p == nil)
+ sysfatal("out of memory");
+ memmove(p, s, size);
+ p[size] = 0;
+ return p;
+}
+
+static int
+srvfmt(Fmt *f)
+{
+ Svc *r;
+ char mf[Maxpath+1], auth[7];
+
+ r = va_arg(f->args, Svc*);
+ mf[0] = 0;
+ auth[0] = 0;
+
+ if(strcmp(r->mtpt, "")!= 0)
+ snprint(mf, sizeof(r->mtpt)+1, " %s", r->mtpt);
+
+ if(strcmp(r->auth, "none")==0)
+ snprint(auth, 4, "srv");
+ else
+ snprint(auth, 7, "srvtls");
+
+ return fmtprint(f, "%s!%s!%s\n",
+ r->trns, r->host, r->port);
+}
+
+static int
+scanfmt(Fmt *f)
+{
+ Svc *r;
+ char mf[Maxpath+6]; /* pad for our tuple attrs */
+
+ mf[0] = 0;
+ r = va_arg(f->args, Svc*);
+ if(strcmp(r->mtpt, "")!=0)
+ snprint(mf, sizeof(r->mtpt)+6, " mtpt=%s", r->mtpt);
+ return fmtprint(f, "service=%s!%s!%s label='%s' auth=%s%s\n",
+ r->trns, r->host, r->port, r->labl, r->auth, mf);
+}
+
+static int
+dumpfmt(Fmt *f)
+{
+ Svc *r;
+ char mf[Maxpath+7]; /* pad for our tuple attrs */
+
+ r = va_arg(f->args, Svc*);
+ if(r->mtpt != 0)
+ snprint(mf, sizeof(r->mtpt) + 7, "\n\tmtpt=%s", r->mtpt);
+ return fmtprint(f, "service=%s!%s!%s\n\tlabel=%s\n\tauth=%s%s",
+ r->trns, r->host, r->port, r->labl, r->auth, mf);
+}
+
--- /dev/null
+++ b/reglookup.c
@@ -1,0 +1,251 @@
+#include <u.h>
+#include <libc.h>
+#include <bio.h>
+#include <ndb.h>
+#include "dns.h"
+
+static Ndb *db;
+static QLock dblock;
+
+int
+openregistry(void)
+{
+ if(db != nil)
+ return 0;
+
+ db = ndbopen(dbfile);
+ return db!=nil ? 0: -1;
+}
+
+static void
+attach(Svc* svc, int persist)
+{
+ svc->perm = !!persist;
+
+ if(registry != nil){
+ svc->next = registry;
+ }
+
+ svc->next = registry;
+ registry = svc;
+}
+
+static char*
+detach(char *dial)
+{
+ Svc *c, *last = 0;
+ char buf[Maxdial]; /* trns is capped at 16, port 8 */
+
+ for(c = registry; c; c = c->next){
+ snprint(buf, Maxdial, "%s!%s!%s", c->trns, c->host, c->port);
+ if(strcmp(buf, dial)==0){
+ if(last == 0)
+ registry = c->next;
+ else
+ last->next = c->next;
+ free(c);
+ return 0;
+ }
+ last = c;
+ }
+
+ return "found no matching service";
+}
+
+static void
+host2svc(Svc *svc, char *dial)
+{
+ int n;
+
+ /*
+ * entry host=tcp!mything!9fs
+ * for now, tokenize but we should allow short strings
+ */
+ n = strcspn(dial, "!");
+ if(n < 1)
+ strcpy(svc->trns, "tcp");
+ else
+ strecpy(svc->trns, svc->trns+n+1, dial);
+ dial = dial + n + 1;
+
+ n = strcspn(dial, "!");
+ strecpy(svc->host, svc->host+n+1, dial);
+
+ dial = dial + n + 1;
+ if(sizeof(dial) < 1)
+ strcpy(svc->port, "9fs");
+ else if(sizeof(dial) > 8)
+ /* If this starts happening, we should bump the number */
+ strecpy(svc->port, svc->port + 8, dial);
+ else
+ strcpy(svc->port, dial);
+}
+
+static void
+dbtuple2cache(Ndbtuple *t, int persist)
+{
+ Ndbtuple *et, *nt;
+ Svc *svc;
+
+
+ for(et = t; et; et = et->entry)
+ if(strncmp(et->attr, "serv", 4)==0){
+ svc = emalloc(sizeof(*svc));
+ host2svc(svc, et->val);
+ for(nt = et->entry; nt; nt = nt->entry)
+ if(strcmp(nt->attr, "label")==0)
+ strecpy(svc->labl, svc->labl+Maxmdns, nt->val);
+ else if(strcmp(nt->attr, "auth")==0)
+ strecpy(svc->auth, svc->auth+Maxauth, nt->val);
+ else if(strcmp(nt->attr, "mtpt")==0)
+ strecpy(svc->mtpt, svc->mtpt+Maxpath, nt->val);
+ attach(svc, persist);
+ };
+}
+
+static void
+dbfile2cache(Ndb *db)
+{
+ Ndbtuple *t;
+
+ if(debug)
+ reglog("reading %s", db->file);
+ Bseek(&db->b, 0, 0);
+ while(t = ndbparse(db)){
+ dbtuple2cache(t, 1);
+ ndbfree(t);
+ }
+
+
+}
+
+Svc*
+rstr2svc(char *entry)
+{
+ Svc *svc;
+ char *args[7];
+
+ int i, n;
+
+ n = tokenize(entry, args, 7);
+
+ svc = emalloc(sizeof(*svc));
+ host2svc(svc, estrdup(args[0]));
+
+ for(i = 1; i < n - 1; i++)
+ if(strcmp(args[i], "label")==0)
+ strecpy(svc->labl, svc->labl+Maxmdns, args[++i]);
+ else if(strcmp(args[i], "auth")==0)
+ strecpy(svc->auth, svc->auth+Maxauth, args[++i]);
+ else if(strcmp(args[i], "mtpt")==0)
+ strecpy(svc->mtpt, svc->mtpt+Maxpath, args[++i]);
+
+ return svc;
+}
+
+char*
+rstr2cache(char *entry, int persist)
+{
+ Svc *svc;
+
+ svc = rstr2svc(entry);
+ attach(svc, persist);
+ return 0;
+}
+
+char*
+rstrdtch(char *svc)
+{
+ return detach(svc);
+}
+
+/* e.g. update tcp!foo!9fs label newlabel */
+char*
+rstrupdt(char *entry)
+{
+ Svc *c, *svc = 0;
+ char *args[7], buf[Maxdial];
+ int i, n;
+
+ n = tokenize(entry, args, 7);
+
+ /* Find our service */
+ for(c = registry; c; c = c->next){
+ snprint(buf, Maxdial, "%s!%s!%s", c->trns, c->host, c->port);
+ if(strcmp(buf, args[0])==0){
+ svc = c;
+ break;
+ }
+ }
+
+ if(svc == 0)
+ return "found no matching service";
+
+ for(i = 1; i < n - 1; i++)
+ if(strcmp(args[i], "label")==0)
+ strecpy(svc->labl, svc->labl+Maxmdns, args[++i]);
+ else if(strcmp(args[i], "auth")==0)
+ strecpy(svc->auth, svc->auth+Maxauth, args[++i]);
+ else if(strcmp(args[i], "mtpt")==0)
+ strecpy(svc->mtpt, svc->mtpt+Maxpath, args[++i]);
+
+ return 0;
+}
+
+void
+regconnect(void)
+{
+ Ndb *adb;
+ Ndbtuple *t;
+ char *host, *list[1];
+ int rfdn, fd;
+
+ rfdn = 0;
+ adb = ndbopen(0);
+
+ /* Start with us */
+ host = getenv("sysname");
+ list[0] = "registry";
+ t = ndbipinfo(adb, "sys", host, list, 1);
+ if(t->val != nil){
+ fd = dial(t->val, 0, 0, 0);
+ if(fd >= 0)
+ rfd[rfdn++] = fd;
+ }
+ ndbfree(t);
+
+ /* Here we want to find our other registry= tuples in the ndb
+ * I don't claim to know the best way to do this. registry= entries
+ * in an ipnet= is the solution i see best fitting, but for now
+ * this will be marked as TODO and left alone
+ * the goal will be, however, to iterate through upstream resolvers
+ * which act as lists for services one may add, like the 9ants grid
+ * registry, as a good example; and when a local resolve misses, it
+ * bubbles up to the next resolver, etc, until we exhaust the list
+ * or find a match
+ */
+
+ if(rfdn < 1)
+ sysfatal("unable to dial any registry server");
+ rfd[rfdn] = 0;
+}
+
+void
+reg2cache(void)
+{
+ Ndb *ndb;
+
+ qlock(&dblock);
+ if(openregistry() < 0){
+ qunlock(&dblock);
+ return;
+ }
+
+ if(debug)
+ syslog(0, logfile, "building cache from db");
+
+ for(ndb = db; ndb; ndb = ndb->next)
+ dbfile2cache(ndb);
+
+ qunlock(&dblock);
+}
--- /dev/null
+++ b/regquery.c
@@ -1,0 +1,67 @@
+#include <u.h>
+#include <libc.h>
+#include <bio.h>
+#include <ctype.h>
+#include <ndb.h>
+#include "dns.h"
+#include "ip.h"
+
+void
+usage(void)
+{
+ fprint(2, "usage: regquery [-s] [-f registry] query\n");
+ exits("usage");
+}
+
+static void
+queryregistry(int fd, char *line, int n)
+{
+ char buf[8192+1];
+
+ seek(fd, 0, 0);
+ write(fd, line, n);
+
+ seek(fd, 0, 0);
+ while((n = read(fd, buf, sizeof(buf)-1)) > 0)
+ write(1, buf, n);
+}
+
+static void
+query(int fd, char *q, int pipe2rc)
+{
+ char arg[260];
+
+ if(strlen(q) > 255)
+ sysfatal("query too long");
+
+ sprint(arg, "%s %s", q, (pipe2rc) ? "svc":"scan");
+ queryregistry(fd, arg, sizeof(arg));
+}
+
+void
+main(int argc, char *argv[])
+{
+ int fd, pipe2rc = 0;
+ char *rst = "/net/registry";
+
+ ARGBEGIN {
+ case 's':
+ pipe2rc++;
+ break;
+ case 'f':
+ rst = EARGF(usage());
+ break;
+ default:
+ usage();
+ } ARGEND;
+
+ if(argc != 1)
+ usage();
+
+ fd = open(rst, ORDWR);
+ if(fd < 0)
+ sysfatal("can't open %s: %r", rst);
+
+ query(fd, argv[0], pipe2rc);
+ exits(0);
+}