diff options
author | rsc <devnull@localhost> | 2005-12-27 04:30:05 +0000 |
---|---|---|
committer | rsc <devnull@localhost> | 2005-12-27 04:30:05 +0000 |
commit | 3e0d8fb3ea83b2b65a6425c65beda887140f9349 (patch) | |
tree | ce1579f6999c0e4adad35b75c498ddc714b40c1e /src/cmd/ndb | |
parent | cff43a06f21a674b2b16e72f8853aac5fd24f48d (diff) | |
download | plan9port-3e0d8fb3ea83b2b65a6425c65beda887140f9349.tar.gz plan9port-3e0d8fb3ea83b2b65a6425c65beda887140f9349.tar.bz2 plan9port-3e0d8fb3ea83b2b65a6425c65beda887140f9349.zip |
add dns
Diffstat (limited to 'src/cmd/ndb')
-rwxr-xr-x | src/cmd/ndb/convDNS2M.c | 380 | ||||
-rwxr-xr-x | src/cmd/ndb/convM2DNS.c | 460 | ||||
-rwxr-xr-x | src/cmd/ndb/dblookup.c | 946 | ||||
-rwxr-xr-x | src/cmd/ndb/dn.c | 1563 | ||||
-rwxr-xr-x | src/cmd/ndb/dnarea.c | 130 | ||||
-rwxr-xr-x | src/cmd/ndb/dnnotify.c | 160 | ||||
-rwxr-xr-x | src/cmd/ndb/dnresolve.c | 753 | ||||
-rwxr-xr-x | src/cmd/ndb/dns.c | 882 | ||||
-rwxr-xr-x | src/cmd/ndb/dns.h | 403 | ||||
-rwxr-xr-x | src/cmd/ndb/dnsdebug.c | 473 | ||||
-rwxr-xr-x | src/cmd/ndb/dnserver.c | 178 | ||||
-rwxr-xr-x | src/cmd/ndb/dnsquery.c | 113 | ||||
-rwxr-xr-x | src/cmd/ndb/dnstcp.c | 362 | ||||
-rwxr-xr-x | src/cmd/ndb/dnudpserver.c | 228 | ||||
-rw-r--r-- | src/cmd/ndb/mkfile | 16 |
15 files changed, 7047 insertions, 0 deletions
diff --git a/src/cmd/ndb/convDNS2M.c b/src/cmd/ndb/convDNS2M.c new file mode 100755 index 00000000..5ee6cadb --- /dev/null +++ b/src/cmd/ndb/convDNS2M.c @@ -0,0 +1,380 @@ +#include <u.h> +#include <libc.h> +#include <ip.h> +#include <bio.h> +#include <ndb.h> +#include "dns.h" + +/* + * a dictionary of domain names for packing messages + */ +enum +{ + Ndict= 64, +}; +typedef struct Dict Dict; +struct Dict +{ + struct { + ushort offset; /* pointer to packed name in message */ + char *name; /* pointer to unpacked name in buf */ + } x[Ndict]; + int n; /* size of dictionary */ + uchar *start; /* start of packed message */ + char buf[4*1024]; /* buffer for unpacked names */ + char *ep; /* first free char in buf */ +}; + +#define NAME(x) p = pname(p, ep, x, dp) +#define SYMBOL(x) p = psym(p, ep, x) +#define STRING(x) p = pstr(p, ep, x) +#define BYTES(x, n) p = pbytes(p, ep, x, n) +#define USHORT(x) p = pushort(p, ep, x) +#define UCHAR(x) p = puchar(p, ep, x) +#define ULONG(x) p = pulong(p, ep, x) +#define V4ADDR(x) p = pv4addr(p, ep, x) +#define V6ADDR(x) p = pv6addr(p, ep, x) + +static uchar* +psym(uchar *p, uchar *ep, char *np) +{ + int n; + + n = strlen(np); + if(n >= Strlen) /* DNS maximum length string */ + n = Strlen - 1; + if(ep - p < n+1) /* see if it fits in the buffer */ + return ep+1; + *p++ = n; + memcpy(p, np, n); + return p + n; +} + +static uchar* +pstr(uchar *p, uchar *ep, char *np) +{ + int n; + + n = strlen(np); + if(n >= Strlen) /* DNS maximum length string */ + n = Strlen - 1; + if(ep - p < n+1) /* see if it fits in the buffer */ + return ep+1; + *p++ = n; + memcpy(p, np, n); + return p + n; +} + +static uchar* +pbytes(uchar *p, uchar *ep, uchar *np, int n) +{ + if(ep - p < n) + return ep+1; + memcpy(p, np, n); + return p + n; +} + +static uchar* +puchar(uchar *p, uchar *ep, int val) +{ + if(ep - p < 1) + return ep+1; + *p++ = val; + return p; +} + +static uchar* +pushort(uchar *p, uchar *ep, int val) +{ + if(ep - p < 2) + return ep+1; + *p++ = val>>8; + *p++ = val; + return p; +} + +static uchar* +pulong(uchar *p, uchar *ep, int val) +{ + if(ep - p < 4) + return ep+1; + *p++ = val>>24; + *p++ = val>>16; + *p++ = val>>8; + *p++ = val; + return p; +} + +static uchar* +pv4addr(uchar *p, uchar *ep, char *name) +{ + uchar ip[IPaddrlen]; + + if(ep - p < 4) + return ep+1; + parseip(ip, name); + v6tov4(p, ip); + return p + 4; + +} + +static uchar* +pv6addr(uchar *p, uchar *ep, char *name) +{ + if(ep - p < IPaddrlen) + return ep+1; + parseip(p, name); + return p + IPaddrlen; + +} + +static uchar* +pname(uchar *p, uchar *ep, char *np, Dict *dp) +{ + char *cp; + int i; + char *last; /* last component packed */ + + if(strlen(np) >= Domlen) /* make sure we don't exceed DNS limits */ + return ep+1; + + last = 0; + while(*np){ + /* look through every component in the dictionary for a match */ + for(i = 0; i < dp->n; i++){ + if(strcmp(np, dp->x[i].name) == 0){ + if(ep - p < 2) + return ep+1; + *p++ = (dp->x[i].offset>>8) | 0xc0; + *p++ = dp->x[i].offset; + return p; + } + } + + /* if there's room, enter this name in dictionary */ + if(dp->n < Ndict){ + if(last){ + /* the whole name is already in dp->buf */ + last = strchr(last, '.') + 1; + dp->x[dp->n].name = last; + dp->x[dp->n].offset = p - dp->start; + dp->n++; + } else { + /* add to dp->buf */ + i = strlen(np); + if(dp->ep + i + 1 < &dp->buf[sizeof(dp->buf)]){ + strcpy(dp->ep, np); + dp->x[dp->n].name = dp->ep; + last = dp->ep; + dp->x[dp->n].offset = p - dp->start; + dp->ep += i + 1; + dp->n++; + } + } + } + + /* put next component into message */ + cp = strchr(np, '.'); + if(cp == 0){ + i = strlen(np); + cp = np + i; /* point to null terminator */ + } else { + i = cp - np; + cp++; /* point past '.' */ + } + if(ep-p < i+1) + return ep+1; + *p++ = i; /* count of chars in label */ + memcpy(p, np, i); + np = cp; + p += i; + } + + if(p >= ep) + return ep+1; + *p++ = 0; /* add top level domain */ + + return p; +} + +static uchar* +convRR2M(RR *rp, uchar *p, uchar *ep, Dict *dp) +{ + uchar *lp, *data; + int len, ttl; + Txt *t; + + NAME(rp->owner->name); + USHORT(rp->type); + USHORT(rp->owner->class); + + /* egregious overuse of ttl (it's absolute time in the cache) */ + if(rp->db) + ttl = rp->ttl; + else + ttl = rp->ttl - now; + if(ttl < 0) + ttl = 0; + ULONG(ttl); + + lp = p; /* leave room for the rdata length */ + p += 2; + data = p; + + if(data >= ep) + return p+1; + + switch(rp->type){ + case Thinfo: + SYMBOL(rp->cpu->name); + SYMBOL(rp->os->name); + break; + case Tcname: + case Tmb: + case Tmd: + case Tmf: + case Tns: + NAME(rp->host->name); + break; + case Tmg: + case Tmr: + NAME(rp->mb->name); + break; + case Tminfo: + NAME(rp->rmb->name); + NAME(rp->mb->name); + break; + case Tmx: + USHORT(rp->pref); + NAME(rp->host->name); + break; + case Ta: + V4ADDR(rp->ip->name); + break; + case Taaaa: + V6ADDR(rp->ip->name); + break; + case Tptr: + NAME(rp->ptr->name); + break; + case Tsoa: + NAME(rp->host->name); + NAME(rp->rmb->name); + ULONG(rp->soa->serial); + ULONG(rp->soa->refresh); + ULONG(rp->soa->retry); + ULONG(rp->soa->expire); + ULONG(rp->soa->minttl); + break; + case Ttxt: + for(t = rp->txt; t != nil; t = t->next) + STRING(t->p); + break; + case Tnull: + BYTES(rp->null->data, rp->null->dlen); + break; + case Trp: + NAME(rp->rmb->name); + NAME(rp->rp->name); + break; + case Tkey: + USHORT(rp->key->flags); + UCHAR(rp->key->proto); + UCHAR(rp->key->alg); + BYTES(rp->key->data, rp->key->dlen); + break; + case Tsig: + USHORT(rp->sig->type); + UCHAR(rp->sig->alg); + UCHAR(rp->sig->labels); + ULONG(rp->sig->ttl); + ULONG(rp->sig->exp); + ULONG(rp->sig->incep); + USHORT(rp->sig->tag); + NAME(rp->sig->signer->name); + BYTES(rp->sig->data, rp->sig->dlen); + break; + case Tcert: + USHORT(rp->cert->type); + USHORT(rp->cert->tag); + UCHAR(rp->cert->alg); + BYTES(rp->cert->data, rp->cert->dlen); + break; + } + + /* stuff in the rdata section length */ + len = p - data; + *lp++ = len >> 8; + *lp = len; + + return p; +} + +static uchar* +convQ2M(RR *rp, uchar *p, uchar *ep, Dict *dp) +{ + NAME(rp->owner->name); + USHORT(rp->type); + USHORT(rp->owner->class); + return p; +} + +static uchar* +rrloop(RR *rp, int *countp, uchar *p, uchar *ep, Dict *dp, int quest) +{ + uchar *np; + + *countp = 0; + for(; rp && p < ep; rp = rp->next){ + if(quest) + np = convQ2M(rp, p, ep, dp); + else + np = convRR2M(rp, p, ep, dp); + if(np > ep) + break; + p = np; + (*countp)++; + } + return p; +} + +/* + * convert into a message + */ +int +convDNS2M(DNSmsg *m, uchar *buf, int len) +{ + uchar *p, *ep, *np; + Dict d; + + d.n = 0; + d.start = buf; + d.ep = d.buf; + memset(buf, 0, len); + m->qdcount = m->ancount = m->nscount = m->arcount = 0; + + /* first pack in the RR's so we can get real counts */ + p = buf + 12; + ep = buf + len; + p = rrloop(m->qd, &m->qdcount, p, ep, &d, 1); + p = rrloop(m->an, &m->ancount, p, ep, &d, 0); + p = rrloop(m->ns, &m->nscount, p, ep, &d, 0); + p = rrloop(m->ar, &m->arcount, p, ep, &d, 0); + if(p > ep) + return -1; + + /* now pack the rest */ + np = p; + p = buf; + ep = buf + len; + USHORT(m->id); + USHORT(m->flags); + USHORT(m->qdcount); + USHORT(m->ancount); + USHORT(m->nscount); + USHORT(m->arcount); + if(p > ep) + return -1; + + return np - buf; +} diff --git a/src/cmd/ndb/convM2DNS.c b/src/cmd/ndb/convM2DNS.c new file mode 100755 index 00000000..47b35616 --- /dev/null +++ b/src/cmd/ndb/convM2DNS.c @@ -0,0 +1,460 @@ +#include <u.h> +#include <libc.h> +#include <ip.h> +#include <bio.h> +#include <ndb.h> +#include "dns.h" + +typedef struct Scan Scan; +struct Scan +{ + uchar *base; + uchar *p; + uchar *ep; + char *err; +}; + +#define NAME(x) gname(x, sp) +#define SYMBOL(x) (x = gsym(sp)) +#define STRING(x) (x = gstr(sp)) +#define USHORT(x) (x = gshort(sp)) +#define ULONG(x) (x = glong(sp)) +#define UCHAR(x) (x = gchar(sp)) +#define V4ADDR(x) (x = gv4addr(sp)) +#define V6ADDR(x) (x = gv6addr(sp)) +#define BYTES(x, y) (y = gbytes(sp, &x, len - (sp->p - data))) + +static char *toolong = "too long"; + +/* + * get a ushort/ulong + */ +static ushort +gchar(Scan *sp) +{ + ushort x; + + if(sp->err) + return 0; + if(sp->ep - sp->p < 1){ + sp->err = toolong; + return 0; + } + x = sp->p[0]; + sp->p += 1; + return x; +} +static ushort +gshort(Scan *sp) +{ + ushort x; + + if(sp->err) + return 0; + if(sp->ep - sp->p < 2){ + sp->err = toolong; + return 0; + } + x = (sp->p[0]<<8) | sp->p[1]; + sp->p += 2; + return x; +} +static ulong +glong(Scan *sp) +{ + ulong x; + + if(sp->err) + return 0; + if(sp->ep - sp->p < 4){ + sp->err = toolong; + return 0; + } + x = (sp->p[0]<<24) | (sp->p[1]<<16) | (sp->p[2]<<8) | sp->p[3]; + sp->p += 4; + return x; +} + +/* + * get an ip address + */ +static DN* +gv4addr(Scan *sp) +{ + char addr[32]; + + if(sp->err) + return 0; + if(sp->ep - sp->p < 4){ + sp->err = toolong; + return 0; + } + snprint(addr, sizeof(addr), "%V", sp->p); + sp->p += 4; + + return dnlookup(addr, Cin, 1); +} +static DN* +gv6addr(Scan *sp) +{ + char addr[64]; + + if(sp->err) + return 0; + if(sp->ep - sp->p < IPaddrlen){ + sp->err = toolong; + return 0; + } + snprint(addr, sizeof(addr), "%I", sp->p); + sp->p += IPaddrlen; + + return dnlookup(addr, Cin, 1); +} + +/* + * get a string. make it an internal symbol. + */ +static DN* +gsym(Scan *sp) +{ + int n; + char sym[Strlen+1]; + + if(sp->err) + return 0; + n = *(sp->p++); + if(sp->p+n > sp->ep){ + sp->err = toolong; + return 0; + } + + if(n > Strlen){ + sp->err = "illegal string"; + return 0; + } + strncpy(sym, (char*)sp->p, n); + sym[n] = 0; + sp->p += n; + + return dnlookup(sym, Csym, 1); +} + +/* + * get a string. don't make it an internal symbol. + */ +static Txt* +gstr(Scan *sp) +{ + int n; + char sym[Strlen+1]; + Txt *t; + + if(sp->err) + return 0; + n = *(sp->p++); + if(sp->p+n > sp->ep){ + sp->err = toolong; + return 0; + } + + if(n > Strlen){ + sp->err = "illegal string"; + return 0; + } + strncpy(sym, (char*)sp->p, n); + sym[n] = 0; + sp->p += n; + + t = emalloc(sizeof(*t)); + t->next = nil; + t->p = estrdup(sym); + return t; +} + +/* + * get a sequence of bytes + */ +static int +gbytes(Scan *sp, uchar **p, int n) +{ + if(sp->err) + return 0; + if(sp->p+n > sp->ep || n < 0){ + sp->err = toolong; + return 0; + } + *p = emalloc(n); + memmove(*p, sp->p, n); + sp->p += n; + + return n; +} + +/* + * get a domain name. 'to' must point to a buffer at least Domlen+1 long. + */ +static char* +gname(char *to, Scan *sp) +{ + int len, off; + int pointer; + int n; + char *tostart; + char *toend; + uchar *p; + + tostart = to; + if(sp->err) + goto err; + pointer = 0; + p = sp->p; + toend = to + Domlen; + for(len = 0; *p; len += pointer ? 0 : (n+1)){ + if((*p & 0xc0) == 0xc0){ + /* pointer to other spot in message */ + if(pointer++ > 10){ + sp->err = "pointer loop"; + goto err; + } + off = ((p[0]<<8) + p[1]) & 0x3ff; + p = sp->base + off; + if(p >= sp->ep){ + sp->err = "bad pointer"; + goto err; + } + n = 0; + continue; + } + n = *p++; + if(len + n < Domlen - 1){ + if(to + n > toend){ + sp->err = toolong; + goto err; + } + memmove(to, p, n); + to += n; + } + p += n; + if(*p){ + if(to >= toend){ + sp->err = toolong; + goto err; + } + *to++ = '.'; + } + } + *to = 0; + if(pointer) + sp->p += len + 2; /* + 2 for pointer */ + else + sp->p += len + 1; /* + 1 for the null domain */ + return tostart; +err: + *tostart = 0; + return tostart; +} + +/* + * convert the next RR from a message + */ +static RR* +convM2RR(Scan *sp) +{ + RR *rp; + int type; + int class; + uchar *data; + int len; + char dname[Domlen+1]; + Txt *t, **l; + +retry: + NAME(dname); + USHORT(type); + USHORT(class); + + rp = rralloc(type); + rp->owner = dnlookup(dname, class, 1); + rp->type = type; + + ULONG(rp->ttl); + rp->ttl += now; + USHORT(len); + data = sp->p; + + if(sp->p + len > sp->ep) + sp->err = toolong; + if(sp->err){ + rrfree(rp); + return 0; + } + + switch(type){ + default: + /* unknown type, just ignore it */ + sp->p = data + len; + rrfree(rp); + goto retry; + case Thinfo: + SYMBOL(rp->cpu); + SYMBOL(rp->os); + break; + case Tcname: + case Tmb: + case Tmd: + case Tmf: + case Tns: + rp->host = dnlookup(NAME(dname), Cin, 1); + break; + case Tmg: + case Tmr: + rp->mb = dnlookup(NAME(dname), Cin, 1); + break; + case Tminfo: + rp->rmb = dnlookup(NAME(dname), Cin, 1); + rp->mb = dnlookup(NAME(dname), Cin, 1); + break; + case Tmx: + USHORT(rp->pref); + rp->host = dnlookup(NAME(dname), Cin, 1); + break; + case Ta: + V4ADDR(rp->ip); + break; + case Taaaa: + V6ADDR(rp->ip); + break; + case Tptr: + rp->ptr = dnlookup(NAME(dname), Cin, 1); + break; + case Tsoa: + rp->host = dnlookup(NAME(dname), Cin, 1); + rp->rmb = dnlookup(NAME(dname), Cin, 1); + ULONG(rp->soa->serial); + ULONG(rp->soa->refresh); + ULONG(rp->soa->retry); + ULONG(rp->soa->expire); + ULONG(rp->soa->minttl); + break; + case Ttxt: + l = &rp->txt; + *l = nil; + while(sp->p-data < len){ + STRING(t); + *l = t; + l = &t->next; + } + break; + case Tnull: + BYTES(rp->null->data, rp->null->dlen); + break; + case Trp: + rp->rmb = dnlookup(NAME(dname), Cin, 1); + rp->rp = dnlookup(NAME(dname), Cin, 1); + break; + case Tkey: + USHORT(rp->key->flags); + UCHAR(rp->key->proto); + UCHAR(rp->key->alg); + BYTES(rp->key->data, rp->key->dlen); + break; + case Tsig: + USHORT(rp->sig->type); + UCHAR(rp->sig->alg); + UCHAR(rp->sig->labels); + ULONG(rp->sig->ttl); + ULONG(rp->sig->exp); + ULONG(rp->sig->incep); + USHORT(rp->sig->tag); + rp->sig->signer = dnlookup(NAME(dname), Cin, 1); + BYTES(rp->sig->data, rp->sig->dlen); + break; + case Tcert: + USHORT(rp->cert->type); + USHORT(rp->cert->tag); + UCHAR(rp->cert->alg); + BYTES(rp->cert->data, rp->cert->dlen); + break; + } + if(sp->p - data != len) + sp->err = "bad RR len"; + return rp; +} + +/* + * convert the next question from a message + */ +static RR* +convM2Q(Scan *sp) +{ + char dname[Domlen+1]; + int type; + int class; + RR *rp; + + NAME(dname); + USHORT(type); + USHORT(class); + if(sp->err) + return 0; + + rp = rralloc(type); + rp->owner = dnlookup(dname, class, 1); + + return rp; +} + +static RR* +rrloop(Scan *sp, int count, int quest) +{ + int i; + static char errbuf[64]; + RR *first, *rp, **l; + + if(sp->err) + return 0; + l = &first; + first = 0; + for(i = 0; i < count; i++){ + rp = quest ? convM2Q(sp) : convM2RR(sp); + if(rp == 0) + break; + if(sp->err){ + rrfree(rp); + break; + } + *l = rp; + l = &rp->next; + } + return first; +} + +/* + * convert the next DNS from a message stream + */ +char* +convM2DNS(uchar *buf, int len, DNSmsg *m) +{ + Scan scan; + Scan *sp; + char *err; + + scan.base = buf; + scan.p = buf; + scan.ep = buf + len; + scan.err = 0; + sp = &scan; + memset(m, 0, sizeof(DNSmsg)); + USHORT(m->id); + USHORT(m->flags); + USHORT(m->qdcount); + USHORT(m->ancount); + USHORT(m->nscount); + USHORT(m->arcount); + m->qd = rrloop(sp, m->qdcount, 1); + m->an = rrloop(sp, m->ancount, 0); + m->ns = rrloop(sp, m->nscount, 0); + err = scan.err; /* live with bad ar's */ + m->ar = rrloop(sp, m->arcount, 0); + return err; +} diff --git a/src/cmd/ndb/dblookup.c b/src/cmd/ndb/dblookup.c new file mode 100755 index 00000000..f81f37df --- /dev/null +++ b/src/cmd/ndb/dblookup.c @@ -0,0 +1,946 @@ +#include <u.h> +#include <libc.h> +#include <bio.h> +#include <ndb.h> +#include <ip.h> +#include "dns.h" + +static Ndb *db; + +static RR* dblookup1(char*, int, int, int); +static RR* addrrr(Ndbtuple*, Ndbtuple*); +static RR* nsrr(Ndbtuple*, Ndbtuple*); +static RR* cnamerr(Ndbtuple*, Ndbtuple*); +static RR* mxrr(Ndbtuple*, Ndbtuple*); +static RR* soarr(Ndbtuple*, Ndbtuple*); +static RR* ptrrr(Ndbtuple*, Ndbtuple*); +static Ndbtuple* look(Ndbtuple*, Ndbtuple*, char*); +static RR* doaxfr(Ndb*, char*); +static RR* nullrr(Ndbtuple *entry, Ndbtuple *pair); +static RR* txtrr(Ndbtuple *entry, Ndbtuple *pair); +static Lock dblock; +static void createptrs(void); + +static int implemented[Tall] = +{ + [Ta] 1, + [Tns] 1, + [Tsoa] 1, + [Tmx] 1, + [Tptr] 1, + [Tcname] 1, + [Tnull] 1, + [Ttxt] 1, +}; + +static void +nstrcpy(char *to, char *from, int len) +{ + strncpy(to, from, len); + to[len-1] = 0; +} + +int +opendatabase(void) +{ + char buf[256]; + Ndb *xdb; + + if(db == nil){ + snprint(buf, sizeof(buf), "%s/ndb", mntpt); + xdb = ndbopen(dbfile); + if(xdb != nil) + xdb->nohash = 1; + db = ndbcat(ndbopen(buf), xdb); + } + if(db == nil) + return -1; + else + return 0; +} + +/* + * lookup an RR in the network database, look for matches + * against both the domain name and the wildcarded domain name. + * + * the lock makes sure only one process can be accessing the data + * base at a time. This is important since there's a lot of + * shared state there. + * + * e.g. for x.research.bell-labs.com, first look for a match against + * the x.research.bell-labs.com. If nothing matches, try *.research.bell-labs.com. + */ +RR* +dblookup(char *name, int class, int type, int auth, int ttl) +{ + RR *rp, *tp; + char buf[256]; + char *wild, *cp; + DN *dp, *ndp; + int err; + static int parallel; + static int parfd[2]; + static char token[1]; + + /* so far only internet lookups are implemented */ + if(class != Cin) + return 0; + + err = Rname; + + if(type == Tall){ + rp = 0; + for (type = Ta; type < Tall; type++) + if(implemented[type]) + rrcat(&rp, dblookup(name, class, type, auth, ttl)); + return rp; + } + + lock(&dblock); + dp = dnlookup(name, class, 1); + if(opendatabase() < 0) + goto out; + if(dp->rr) + err = 0; + + /* first try the given name */ + rp = 0; + if(cachedb) + rp = rrlookup(dp, type, NOneg); + else + rp = dblookup1(name, type, auth, ttl); + if(rp) + goto out; + + /* try lower case version */ + for(cp = name; *cp; cp++) + *cp = tolower(*cp); + if(cachedb) + rp = rrlookup(dp, type, NOneg); + else + rp = dblookup1(name, type, auth, ttl); + if(rp) + goto out; + + /* walk the domain name trying the wildcard '*' at each position */ + for(wild = strchr(name, '.'); wild; wild = strchr(wild+1, '.')){ + snprint(buf, sizeof(buf), "*%s", wild); + ndp = dnlookup(buf, class, 1); + if(ndp->rr) + err = 0; + if(cachedb) + rp = rrlookup(ndp, type, NOneg); + else + rp = dblookup1(buf, type, auth, ttl); + if(rp) + break; + } +out: + /* add owner to uncached records */ + if(rp){ + for(tp = rp; tp; tp = tp->next) + tp->owner = dp; + } else { + /* don't call it non-existent if it's not ours */ + if(err == Rname && !inmyarea(name)) + err = Rserver; + dp->nonexistent = err; + } + + unlock(&dblock); + return rp; +} + +/* + * lookup an RR in the network database + */ +static RR* +dblookup1(char *name, int type, int auth, int ttl) +{ + Ndbtuple *t, *nt; + RR *rp, *list, **l; + Ndbs s; + char dname[Domlen]; + char *attr; + DN *dp; + RR *(*f)(Ndbtuple*, Ndbtuple*); + int found, x; + + dp = 0; + switch(type){ + case Tptr: + attr = "ptr"; + f = ptrrr; + break; + case Ta: + attr = "ip"; + f = addrrr; + break; + case Tnull: + attr = "nullrr"; + f = nullrr; + break; + case Tns: + attr = "ns"; + f = nsrr; + break; + case Tsoa: + attr = "soa"; + f = soarr; + break; + case Tmx: + attr = "mx"; + f = mxrr; + break; + case Tcname: + attr = "cname"; + f = cnamerr; + break; + case Taxfr: + case Tixfr: + return doaxfr(db, name); + default: + return nil; + } + + /* + * find a matching entry in the database + */ + free(ndbgetvalue(db, &s, "dom", name, attr, &t)); + + /* + * hack for local names + */ + if(t == 0 && strchr(name, '.') == 0) + free(ndbgetvalue(db, &s, "sys", name, attr, &t)); + if(t == 0) + return nil; + + /* search whole entry for default domain name */ + strncpy(dname, name, sizeof dname); + for(nt = t; nt; nt = nt->entry) + if(strcmp(nt->attr, "dom") == 0){ + nstrcpy(dname, nt->val, sizeof dname); + break; + } + + /* ttl is maximum of soa minttl and entry's ttl ala rfc883 */ + nt = look(t, s.t, "ttl"); + if(nt){ + x = atoi(nt->val); + if(x > ttl) + ttl = x; + } + + /* default ttl is one day */ + if(ttl < 0) + ttl = DEFTTL; + + /* + * The database has 2 levels of precedence; line and entry. + * Pairs on the same line bind tighter than pairs in the + * same entry, so we search the line first. + */ + found = 0; + list = 0; + l = &list; + for(nt = s.t;; ){ + if(found == 0 && strcmp(nt->attr, "dom") == 0){ + nstrcpy(dname, nt->val, sizeof dname); + found = 1; + } + if(cistrcmp(attr, nt->attr) == 0){ + rp = (*f)(t, nt); + rp->auth = auth; + rp->db = 1; + if(ttl) + rp->ttl = ttl; + if(dp == 0) + dp = dnlookup(dname, Cin, 1); + rp->owner = dp; + *l = rp; + l = &rp->next; + nt->ptr = 1; + } + nt = nt->line; + if(nt == s.t) + break; + } + + /* search whole entry */ + for(nt = t; nt; nt = nt->entry) + if(nt->ptr == 0 && cistrcmp(attr, nt->attr) == 0){ + rp = (*f)(t, nt); + rp->db = 1; + if(ttl) + rp->ttl = ttl; + rp->auth = auth; + if(dp == 0) + dp = dnlookup(dname, Cin, 1); + rp->owner = dp; + *l = rp; + l = &rp->next; + } + ndbfree(t); + + return list; +} + +/* + * make various types of resource records from a database entry + */ +static RR* +addrrr(Ndbtuple *entry, Ndbtuple *pair) +{ + RR *rp; + uchar addr[IPaddrlen]; + + USED(entry); + parseip(addr, pair->val); + if(isv4(addr)) + rp = rralloc(Ta); + else + rp = rralloc(Taaaa); + rp->ip = dnlookup(pair->val, Cin, 1); + return rp; +} +static RR* +nullrr(Ndbtuple *entry, Ndbtuple *pair) +{ + RR *rp; + + USED(entry); + rp = rralloc(Tnull); + rp->null->data = (uchar*)estrdup(pair->val); + rp->null->dlen = strlen((char*)rp->null->data); + return rp; +} +/* + * txt rr strings are at most 255 bytes long. one + * can represent longer strings by multiple concatenated + * <= 255 byte ones. + */ +static RR* +txtrr(Ndbtuple *entry, Ndbtuple *pair) +{ + RR *rp; + Txt *t, **l; + int i, len, sofar; + + USED(entry); + rp = rralloc(Ttxt); + l = &rp->txt; + rp->txt = nil; + len = strlen(pair->val); + sofar = 0; + while(len > sofar){ + t = emalloc(sizeof(*t)); + t->next = nil; + + i = len-sofar; + if(i > 255) + i = 255; + + t->p = emalloc(i+1); + memmove(t->p, pair->val+sofar, i); + t->p[i] = 0; + sofar += i; + + *l = t; + l = &t->next; + } + return rp; +} +static RR* +cnamerr(Ndbtuple *entry, Ndbtuple *pair) +{ + RR *rp; + + USED(entry); + rp = rralloc(Tcname); + rp->host = dnlookup(pair->val, Cin, 1); + return rp; +} +static RR* +mxrr(Ndbtuple *entry, Ndbtuple *pair) +{ + RR * rp; + + rp = rralloc(Tmx); + rp->host = dnlookup(pair->val, Cin, 1); + pair = look(entry, pair, "pref"); + if(pair) + rp->pref = atoi(pair->val); + else + rp->pref = 1; + return rp; +} +static RR* +nsrr(Ndbtuple *entry, Ndbtuple *pair) +{ + RR *rp; + Ndbtuple *t; + + rp = rralloc(Tns); + rp->host = dnlookup(pair->val, Cin, 1); + t = look(entry, pair, "soa"); + if(t && t->val[0] == 0) + rp->local = 1; + return rp; +} +static RR* +ptrrr(Ndbtuple *entry, Ndbtuple *pair) +{ + RR *rp; + + USED(entry); + rp = rralloc(Tns); + rp->ptr = dnlookup(pair->val, Cin, 1); + return rp; +} +static RR* +soarr(Ndbtuple *entry, Ndbtuple *pair) +{ + RR *rp; + Ndbtuple *ns, *mb, *t; + char mailbox[Domlen]; + Ndb *ndb; + char *p; + + rp = rralloc(Tsoa); + rp->soa->serial = 1; + for(ndb = db; ndb; ndb = ndb->next) + if(ndb->mtime > rp->soa->serial) + rp->soa->serial = ndb->mtime; + rp->soa->refresh = Day; + rp->soa->retry = Hour; + rp->soa->expire = Day; + rp->soa->minttl = Day; + t = look(entry, pair, "ttl"); + if(t) + rp->soa->minttl = atoi(t->val); + t = look(entry, pair, "refresh"); + if(t) + rp->soa->refresh = atoi(t->val); + t = look(entry, pair, "serial"); + if(t) + rp->soa->serial = strtoul(t->val, 0, 10); + + ns = look(entry, pair, "ns"); + if(ns == 0) + ns = look(entry, pair, "dom"); + rp->host = dnlookup(ns->val, Cin, 1); + + /* accept all of: + * mbox=person + * mbox=person@machine.dom + * mbox=person.machine.dom + */ + mb = look(entry, pair, "mbox"); + if(mb == nil) + mb = look(entry, pair, "mb"); + if(mb){ + if(strchr(mb->val, '.')) { + p = strchr(mb->val, '@'); + if(p != nil) + *p = '.'; + rp->rmb = dnlookup(mb->val, Cin, 1); + } else { + snprint(mailbox, sizeof(mailbox), "%s.%s", + mb->val, ns->val); + rp->rmb = dnlookup(mailbox, Cin, 1); + } + } else { + snprint(mailbox, sizeof(mailbox), "postmaster.%s", + ns->val); + rp->rmb = dnlookup(mailbox, Cin, 1); + } + + /* hang dns slaves off of the soa. this is + * for managing the area. + */ + for(t = entry; t != nil; t = t->entry) + if(strcmp(t->attr, "dnsslave") == 0) + addserver(&rp->soa->slaves, t->val); + + return rp; +} + +/* + * Look for a pair with the given attribute. look first on the same line, + * then in the whole entry. + */ +static Ndbtuple* +look(Ndbtuple *entry, Ndbtuple *line, char *attr) +{ + Ndbtuple *nt; + + /* first look on same line (closer binding) */ + for(nt = line;;){ + if(cistrcmp(attr, nt->attr) == 0) + return nt; + nt = nt->line; + if(nt == line) + break; + } + /* search whole tuple */ + for(nt = entry; nt; nt = nt->entry) + if(cistrcmp(attr, nt->attr) == 0) + return nt; + return 0; +} + +static RR** +linkrr(RR *rp, DN *dp, RR **l) +{ + rp->owner = dp; + rp->auth = 1; + rp->db = 1; + *l = rp; + return &rp->next; +} + +/* these are answered specially by the tcp version */ +static RR* +doaxfr(Ndb *db, char *name) +{ + USED(db); + USED(name); + return 0; +} + + +/* + * read the all the soa's from the database to determine area's. + * this is only used when we're not caching the database. + */ +static void +dbfile2area(Ndb *db) +{ + Ndbtuple *t; + + if(debug) + syslog(0, logfile, "rereading %s", db->file); + Bseek(&db->b, 0, 0); + while(t = ndbparse(db)){ + ndbfree(t); + } +} + +/* + * read the database into the cache + */ +static void +dbpair2cache(DN *dp, Ndbtuple *entry, Ndbtuple *pair) +{ + RR *rp; + Ndbtuple *t; + static ulong ord; + + rp = 0; + if(cistrcmp(pair->attr, "ip") == 0){ + dp->ordinal = ord++; + rp = addrrr(entry, pair); + } else if(cistrcmp(pair->attr, "ns") == 0){ + rp = nsrr(entry, pair); + } else if(cistrcmp(pair->attr, "soa") == 0){ + rp = soarr(entry, pair); + addarea(dp, rp, pair); + } else if(cistrcmp(pair->attr, "mx") == 0){ + rp = mxrr(entry, pair); + } else if(cistrcmp(pair->attr, "cname") == 0){ + rp = cnamerr(entry, pair); + } else if(cistrcmp(pair->attr, "nullrr") == 0){ + rp = nullrr(entry, pair); + } else if(cistrcmp(pair->attr, "txtrr") == 0){ + rp = txtrr(entry, pair); + } + + if(rp == 0) + return; + + rp->owner = dp; + rp->db = 1; + t = look(entry, pair, "ttl"); + if(t) + rp->ttl = atoi(t->val); + rrattach(rp, 0); +} +static void +dbtuple2cache(Ndbtuple *t) +{ + Ndbtuple *et, *nt; + DN *dp; + + for(et = t; et; et = et->entry){ + if(strcmp(et->attr, "dom") == 0){ + dp = dnlookup(et->val, Cin, 1); + + /* first same line */ + for(nt = et->line; nt != et; nt = nt->line){ + dbpair2cache(dp, t, nt); + nt->ptr = 1; + } + + /* then rest of entry */ + for(nt = t; nt; nt = nt->entry){ + if(nt->ptr == 0) + dbpair2cache(dp, t, nt); + nt->ptr = 0; + } + } + } +} +static void +dbfile2cache(Ndb *db) +{ + Ndbtuple *t; + + if(debug) + syslog(0, logfile, "rereading %s", db->file); + Bseek(&db->b, 0, 0); + while(t = ndbparse(db)){ + dbtuple2cache(t); + ndbfree(t); + } +} +void +db2cache(int doit) +{ + Ndb *ndb; + Dir *d; + ulong youngest, temp; + static ulong lastcheck; + static ulong lastyoungest; + + /* no faster than once every 2 minutes */ + if(now < lastcheck + 2*Min && !doit) + return; + + refresh_areas(owned); + + lock(&dblock); + + if(opendatabase() < 0){ + unlock(&dblock); + return; + } + + /* + * file may be changing as we are reading it, so loop till + * mod times are consistent. + * + * we don't use the times in the ndb records because they may + * change outside of refreshing our cached knowledge. + */ + for(;;){ + lastcheck = now; + youngest = 0; + for(ndb = db; ndb; ndb = ndb->next){ + /* the dirfstat avoids walking the mount table each time */ + if((d = dirfstat(Bfildes(&ndb->b))) != nil || + (d = dirstat(ndb->file)) != nil){ + temp = d->mtime; /* ulong vs int crap */ + if(temp > youngest) + youngest = temp; + free(d); + } + } + if(!doit && youngest == lastyoungest){ + unlock(&dblock); + return; + } + + /* forget our area definition */ + freearea(&owned); + freearea(&delegated); + + /* reopen all the files (to get oldest for time stamp) */ + for(ndb = db; ndb; ndb = ndb->next) + ndbreopen(ndb); + + if(cachedb){ + /* mark all db records as timed out */ + dnagedb(); + + /* read in new entries */ + for(ndb = db; ndb; ndb = ndb->next) + dbfile2cache(ndb); + + /* mark as authentic anything in our domain */ + dnauthdb(); + + /* remove old entries */ + dnageall(1); + } else { + /* read all the soa's to get database defaults */ + for(ndb = db; ndb; ndb = ndb->next) + dbfile2area(ndb); + } + + doit = 0; + lastyoungest = youngest; + createptrs(); + } + + unlock(&dblock); +} + +extern uchar ipaddr[IPaddrlen]; + +/* + * get all my xxx + */ +Ndbtuple* +lookupinfo(char *attr) +{ + char buf[64]; + char *a[2]; + static Ndbtuple *t; + + snprint(buf, sizeof buf, "%I", ipaddr); + a[0] = attr; + + lock(&dblock); + if(opendatabase() < 0){ + unlock(&dblock); + return nil; + } + t = ndbipinfo(db, "ip", buf, a, 1); + unlock(&dblock); + return t; +} + +char *localservers = "local#dns#servers"; +char *localserverprefix = "local#dns#server"; + +/* + * return non-zero is this is a bad delegation + */ +int +baddelegation(RR *rp, RR *nsrp, uchar *addr) +{ + Ndbtuple *nt; + static Ndbtuple *t; + + if(t == nil) + t = lookupinfo("dom"); + if(t == nil) + return 0; + + for(; rp; rp = rp->next){ + if(rp->type != Tns) + continue; + + /* see if delegation is looping */ + if(nsrp) + if(rp->owner != nsrp->owner) + if(subsume(rp->owner->name, nsrp->owner->name) && + strcmp(nsrp->owner->name, localservers) != 0){ + syslog(0, logfile, "delegation loop %R -> %R from %I", nsrp, rp, addr); + return 1; + } + + /* see if delegating to us what we don't own */ + for(nt = t; nt != nil; nt = nt->entry) + if(rp->host && cistrcmp(rp->host->name, nt->val) == 0) + break; + if(nt != nil && !inmyarea(rp->owner->name)){ + syslog(0, logfile, "bad delegation %R from %I", rp, addr); + return 1; + } + } + + return 0; +} + +static void +addlocaldnsserver(DN *dp, int class, char *ipaddr, int i) +{ + DN *nsdp; + RR *rp; + char buf[32]; + + /* ns record for name server, make up an impossible name */ + rp = rralloc(Tns); + snprint(buf, sizeof(buf), "%s%d", localserverprefix, i); + nsdp = dnlookup(buf, class, 1); + rp->host = nsdp; + rp->owner = dp; + rp->local = 1; + rp->db = 1; + rp->ttl = 10*Min; + rrattach(rp, 1); + +print("dns %s\n", ipaddr); + /* A record */ + rp = rralloc(Ta); + rp->ip = dnlookup(ipaddr, class, 1); + rp->owner = nsdp; + rp->local = 1; + rp->db = 1; + rp->ttl = 10*Min; + rrattach(rp, 1); +} + +/* + * return list of dns server addresses to use when + * acting just as a resolver. + */ +RR* +dnsservers(int class) +{ + Ndbtuple *t, *nt; + RR *nsrp; + DN *dp; + char *p; + int i, n; + char *buf, *args[5]; + + dp = dnlookup(localservers, class, 1); + nsrp = rrlookup(dp, Tns, NOneg); + if(nsrp != nil) + return nsrp; + + p = getenv("DNSSERVER"); + if(p != nil){ + buf = estrdup(p); + n = tokenize(buf, args, nelem(args)); + for(i = 0; i < n; i++) + addlocaldnsserver(dp, class, args[i], i); + free(buf); + } else { + t = lookupinfo("@dns"); + if(t == nil) + return nil; + i = 0; + for(nt = t; nt != nil; nt = nt->entry){ + addlocaldnsserver(dp, class, nt->val, i); + i++; + } + ndbfree(t); + } + + return rrlookup(dp, Tns, NOneg); +} + +static void +addlocaldnsdomain(DN *dp, int class, char *domain) +{ + RR *rp; + + /* A record */ + rp = rralloc(Tptr); + rp->ptr = dnlookup(domain, class, 1); + rp->owner = dp; + rp->db = 1; + rp->ttl = 10*Min; + rrattach(rp, 1); +} + +/* + * return list of domains to use when resolving names without '.'s + */ +RR* +domainlist(int class) +{ + Ndbtuple *t, *nt; + RR *rp; + DN *dp; + + dp = dnlookup("local#dns#domains", class, 1); + rp = rrlookup(dp, Tptr, NOneg); + if(rp != nil) + return rp; + + t = lookupinfo("dnsdomain"); + if(t == nil) + return nil; + for(nt = t; nt != nil; nt = nt->entry) + addlocaldnsdomain(dp, class, nt->val); + ndbfree(t); + + return rrlookup(dp, Tptr, NOneg); +} + +char *v4ptrdom = ".in-addr.arpa"; +char *v6ptrdom = ".ip6.arpa"; /* ip6.int deprecated, rfc 3152 */ + +char *attribs[] = { + "ipmask", + 0 +}; + +/* + * create ptrs that are in our areas + */ +static void +createptrs(void) +{ + int len, dlen, n; + Area *s; + char *f[40]; + char buf[Domlen+1]; + uchar net[IPaddrlen]; + uchar mask[IPaddrlen]; + char ipa[48]; + Ndbtuple *t, *nt; + + dlen = strlen(v4ptrdom); + for(s = owned; s; s = s->next){ + len = strlen(s->soarr->owner->name); + if(len <= dlen) + continue; + if(cistrcmp(s->soarr->owner->name+len-dlen, v4ptrdom) != 0) + continue; + + /* get mask and net value */ + strncpy(buf, s->soarr->owner->name, sizeof(buf)); + buf[sizeof(buf)-1] = 0; + n = getfields(buf, f, nelem(f), 0, "."); + memset(mask, 0xff, IPaddrlen); + ipmove(net, v4prefix); + switch(n){ + case 3: /* /8 */ + net[IPv4off] = atoi(f[0]); + mask[IPv4off+1] = 0; + mask[IPv4off+2] = 0; + mask[IPv4off+3] = 0; + break; + case 4: /* /16 */ + net[IPv4off] = atoi(f[1]); + net[IPv4off+1] = atoi(f[0]); + mask[IPv4off+2] = 0; + mask[IPv4off+3] = 0; + break; + case 5: /* /24 */ + net[IPv4off] = atoi(f[2]); + net[IPv4off+1] = atoi(f[1]); + net[IPv4off+2] = atoi(f[0]); + mask[IPv4off+3] = 0; + break; + case 6: /* rfc2317 */ + net[IPv4off] = atoi(f[3]); + net[IPv4off+1] = atoi(f[2]); + net[IPv4off+2] = atoi(f[1]); + net[IPv4off+3] = atoi(f[0]); + sprint(ipa, "%I", net); + t = ndbipinfo(db, "ip", ipa, attribs, 1); + if(t == nil) /* could be a reverse with no forward */ + continue; + nt = look(t, t, "ipmask"); + if(nt == nil){ /* we're confused */ + ndbfree(t); + continue; + } + parseipmask(mask, nt->val); + n = 5; + break; + default: + continue; + } + + /* go through all domain entries looking for RR's in this network and create ptrs */ + dnptr(net, mask, s->soarr->owner->name, 6-n, 0); + } +} diff --git a/src/cmd/ndb/dn.c b/src/cmd/ndb/dn.c new file mode 100755 index 00000000..a38d4305 --- /dev/null +++ b/src/cmd/ndb/dn.c @@ -0,0 +1,1563 @@ +#include <u.h> +#include <libc.h> +#include <ip.h> +#include <ctype.h> +#include <bio.h> +#include <ndb.h> +#include "dns.h" + +/* + * Hash table for domain names. The hash is based only on the + * first element of the domain name. + */ +DN *ht[HTLEN]; + + +static struct +{ + Lock lk; + ulong names; /* names allocated */ + ulong oldest; /* longest we'll leave a name around */ + int active; + int mutex; + int id; +} dnvars; + +/* names of RR types */ +char *rrtname[] = +{ +[Ta] "ip", +[Tns] "ns", +[Tmd] "md", +[Tmf] "mf", +[Tcname] "cname", +[Tsoa] "soa", +[Tmb] "mb", +[Tmg] "mg", +[Tmr] "mr", +[Tnull] "null", +[Twks] "wks", +[Tptr] "ptr", +[Thinfo] "hinfo", +[Tminfo] "minfo", +[Tmx] "mx", +[Ttxt] "txt", +[Trp] "rp", +[Tkey] "key", +[Tcert] "cert", +[Tsig] "sig", +[Taaaa] "ipv6", +[Tixfr] "ixfr", +[Taxfr] "axfr", +[Tall] "all", + 0, +}; + +/* names of response codes */ +char *rname[Rmask+1] = +{ +[Rok] "ok", +[Rformat] "format error", +[Rserver] "server failure", +[Rname] "bad name", +[Runimplimented] "unimplemented", +[Rrefused] "we don't like you", +}; + +/* names of op codes */ +char *opname[] = +{ +[Oquery] "query", +[Oinverse] "inverse", +[Ostatus] "status", +}; + +Lock dnlock; + +static int sencodefmt(Fmt*); + +/* + * set up a pipe to use as a lock + */ +void +dninit(void) +{ + fmtinstall('E', eipfmt); + fmtinstall('I', eipfmt); + fmtinstall('V', eipfmt); + fmtinstall('R', rrfmt); + fmtinstall('Q', rravfmt); + fmtinstall('H', sencodefmt); + + dnvars.oldest = maxage; + dnvars.names = 0; +} + +/* + * hash for a domain name + */ +static ulong +dnhash(char *name) +{ + ulong hash; + uchar *val = (uchar*)name; + + for(hash = 0; *val; val++) + hash = (hash*13) + tolower(*val)-'a'; + return hash % HTLEN; +} + +/* + * lookup a symbol. if enter is not zero and the name is + * not found, create it. + */ +DN* +dnlookup(char *name, int class, int enter) +{ + DN **l; + DN *dp; + + l = &ht[dnhash(name)]; + lock(&dnlock); + for(dp = *l; dp; dp = dp->next) { + assert(dp->magic == DNmagic); + if(dp->class == class && cistrcmp(dp->name, name) == 0){ + dp->referenced = now; + unlock(&dnlock); + return dp; + } + l = &dp->next; + } + if(enter == 0){ + unlock(&dnlock); + return 0; + } + dnvars.names++; + dp = emalloc(sizeof(*dp)); + dp->magic = DNmagic; + dp->name = estrdup(name); + assert(dp->name != 0); + dp->class = class; + dp->rr = 0; + dp->next = 0; + dp->referenced = now; + *l = dp; + unlock(&dnlock); + + return dp; +} + +/* + * dump the cache + */ +void +dndump(char *file) +{ + DN *dp; + int i, fd; + RR *rp; + + fd = open(file, OWRITE|OTRUNC); + if(fd < 0) + return; + lock(&dnlock); + for(i = 0; i < HTLEN; i++){ + for(dp = ht[i]; dp; dp = dp->next){ + fprint(fd, "%s\n", dp->name); + for(rp = dp->rr; rp; rp = rp->next) + fprint(fd, " %R %c%c %lud/%lud\n", rp, rp->auth?'A':'U', + rp->db?'D':'N', rp->expire, rp->ttl); + } + } + unlock(&dnlock); + close(fd); +} + +/* + * purge all records + */ +void +dnpurge(void) +{ + DN *dp; + RR *rp, *srp; + int i; + + lock(&dnlock); + + for(i = 0; i < HTLEN; i++) + for(dp = ht[i]; dp; dp = dp->next){ + srp = rp = dp->rr; + dp->rr = nil; + for(; rp != nil; rp = rp->next) + rp->cached = 0; + rrfreelist(srp); + } + + unlock(&dnlock); +} + +/* + * check the age of resource records, free any that have timed out + */ +void +dnage(DN *dp) +{ + RR **l; + RR *rp, *next; + ulong diff; + + diff = now - dp->referenced; + if(diff < Reserved) + return; + + l = &dp->rr; + for(rp = dp->rr; rp; rp = next){ + assert(rp->magic == RRmagic && rp->cached); + next = rp->next; + if(!rp->db) + if(rp->expire < now || diff > dnvars.oldest){ + *l = next; + rp->cached = 0; + rrfree(rp); + continue; + } + l = &rp->next; + } +} + +#define REF(x) if(x) x->refs++ + +/* + * our target is 4000 names cached, this should be larger on large servers + */ +#define TARGET 4000 + +/* + * periodicly sweep for old records and remove unreferenced domain names + * + * only called when all other threads are locked out + */ +void +dnageall(int doit) +{ + DN *dp, **l; + int i; + RR *rp; + static ulong nextage; + + if(dnvars.names < TARGET && now < nextage && !doit){ + dnvars.oldest = maxage; + return; + } + + if(dnvars.names > TARGET) + dnvars.oldest /= 2; + nextage = now + maxage; + + lock(&dnlock); + + /* time out all old entries (and set refs to 0) */ + for(i = 0; i < HTLEN; i++) + for(dp = ht[i]; dp; dp = dp->next){ + dp->refs = 0; + dnage(dp); + } + + /* mark all referenced domain names */ + for(i = 0; i < HTLEN; i++) + for(dp = ht[i]; dp; dp = dp->next) + for(rp = dp->rr; rp; rp = rp->next){ + REF(rp->owner); + if(rp->negative){ + REF(rp->negsoaowner); + continue; + } + switch(rp->type){ + case Thinfo: + REF(rp->cpu); + REF(rp->os); + break; + case Ttxt: + break; + case Tcname: + case Tmb: + case Tmd: + case Tmf: + case Tns: + REF(rp->host); + break; + case Tmg: + case Tmr: + REF(rp->mb); + break; + case Tminfo: + REF(rp->rmb); + REF(rp->mb); + break; + case Trp: + REF(rp->rmb); + REF(rp->rp); + break; + case Tmx: + REF(rp->host); + break; + case Ta: + case Taaaa: + REF(rp->ip); + break; + case Tptr: + REF(rp->ptr); + break; + case Tsoa: + REF(rp->host); + REF(rp->rmb); + break; + } + } + + /* sweep and remove unreferenced domain names */ + for(i = 0; i < HTLEN; i++){ + l = &ht[i]; + for(dp = *l; dp; dp = *l){ + if(dp->rr == 0 && dp->refs == 0){ + assert(dp->magic == DNmagic); + *l = dp->next; + if(dp->name) + free(dp->name); + dp->magic = ~dp->magic; + dnvars.names--; + free(dp); + continue; + } + l = &dp->next; + } + } + + unlock(&dnlock); +} + +/* + * timeout all database records (used when rereading db) + */ +void +dnagedb(void) +{ + DN *dp; + int i; + RR *rp; + static ulong nextage; + + lock(&dnlock); + + /* time out all database entries */ + for(i = 0; i < HTLEN; i++) + for(dp = ht[i]; dp; dp = dp->next) + for(rp = dp->rr; rp; rp = rp->next) + if(rp->db) + rp->expire = 0; + + unlock(&dnlock); +} + +/* + * mark all local db records about my area as authoritative, time out any others + */ +void +dnauthdb(void) +{ + DN *dp; + int i; + Area *area; + RR *rp; + static ulong nextage; + + lock(&dnlock); + + /* time out all database entries */ + for(i = 0; i < HTLEN; i++) + for(dp = ht[i]; dp; dp = dp->next){ + area = inmyarea(dp->name); + for(rp = dp->rr; rp; rp = rp->next) + if(rp->db){ + if(area){ + if(rp->ttl < area->soarr->soa->minttl) + rp->ttl = area->soarr->soa->minttl; + rp->auth = 1; + } + if(rp->expire == 0){ + rp->db = 0; + dp->referenced = now - Reserved - 1; + } + } + } + + unlock(&dnlock); +} + +/* + * keep track of other processes to know if we can + * garbage collect. block while garbage collecting. + */ +int +getactivity(Request *req) +{ + int rv; + + if(traceactivity) syslog(0, "dns", "get %d by %d", dnvars.active, getpid()); + lock(&dnvars.lk); + while(dnvars.mutex){ + unlock(&dnvars.lk); + sleep(200); + lock(&dnvars.lk); + } + rv = ++dnvars.active; + now = time(0); + req->id = ++dnvars.id; + unlock(&dnvars.lk); + + return rv; +} +void +putactivity(void) +{ + static ulong lastclean; + + if(traceactivity) syslog(0, "dns", "put %d by %d", dnvars.active, getpid()); + lock(&dnvars.lk); + dnvars.active--; + assert(dnvars.active >= 0); /* "dnvars.active %d", dnvars.active */; + + /* + * clean out old entries and check for new db periodicly + */ + if(dnvars.mutex || (needrefresh == 0 && dnvars.active > 0)){ + unlock(&dnvars.lk); + return; + } + + /* wait till we're alone */ + dnvars.mutex = 1; + while(dnvars.active > 0){ + unlock(&dnvars.lk); + sleep(100); + lock(&dnvars.lk); + } + unlock(&dnvars.lk); + + db2cache(needrefresh); + dnageall(0); + + /* let others back in */ + lastclean = now; + needrefresh = 0; + dnvars.mutex = 0; +} + +/* + * Attach a single resource record to a domain name. + * - Avoid duplicates with already present RR's + * - Chain all RR's of the same type adjacent to one another + * - chain authoritative RR's ahead of non-authoritative ones + */ +static void +rrattach1(RR *new, int auth) +{ + RR **l; + RR *rp; + DN *dp; + + assert(new->magic == RRmagic && !new->cached); + + if(!new->db) + new->expire = new->ttl; + else + new->expire = now + Year; + dp = new->owner; + assert(dp->magic == DNmagic); + new->auth |= auth; + new->next = 0; + + /* + * find first rr of the right type + */ + l = &dp->rr; + for(rp = *l; rp; rp = *l){ + assert(rp->magic == RRmagic && rp->cached); + if(rp->type == new->type) + break; + l = &rp->next; + } + + /* + * negative entries replace positive entries + * positive entries replace negative entries + * newer entries replace older entries with the same fields + */ + for(rp = *l; rp; rp = *l){ + assert(rp->magic == RRmagic && rp->cached); + if(rp->type != new->type) + break; + + if(rp->db == new->db && rp->auth == new->auth){ + /* negative drives out positive and vice versa */ + if(rp->negative != new->negative){ + *l = rp->next; + rp->cached = 0; + rrfree(rp); + continue; + } + + /* all things equal, pick the newer one */ + if(rp->arg0 == new->arg0 && rp->arg1 == new->arg1){ + /* new drives out old */ + if(new->ttl > rp->ttl || new->expire > rp->expire){ + *l = rp->next; + rp->cached = 0; + rrfree(rp); + continue; + } else { + rrfree(new); + return; + } + } + + /* Hack for pointer records. This makes sure + * the ordering in the list reflects the ordering + * received or read from the database + */ + if(rp->type == Tptr){ + if(!rp->negative && !new->negative + && rp->ptr->ordinal > new->ptr->ordinal) + break; + } + } + l = &rp->next; + } + + /* + * add to chain + */ + new->cached = 1; + new->next = *l; + *l = new; +} + +/* + * Attach a list of resource records to a domain name. + * - Avoid duplicates with already present RR's + * - Chain all RR's of the same type adjacent to one another + * - chain authoritative RR's ahead of non-authoritative ones + * - remove any expired RR's + */ +void +rrattach(RR *rp, int auth) +{ + RR *next; + + lock(&dnlock); + for(; rp; rp = next){ + next = rp->next; + rp->next = 0; + + /* avoid any outside spoofing */ + if(cachedb && !rp->db && inmyarea(rp->owner->name)) + rrfree(rp); + else + rrattach1(rp, auth); + } + unlock(&dnlock); +} + +/* + * allocate a resource record of a given type + */ +RR* +rralloc(int type) +{ + RR *rp; + + rp = emalloc(sizeof(*rp)); + rp->magic = RRmagic; + rp->pc = getcallerpc(&type); + rp->type = type; + switch(type){ + case Tsoa: + rp->soa = emalloc(sizeof(*rp->soa)); + rp->soa->slaves = nil; + break; + case Tkey: + rp->key = emalloc(sizeof(*rp->key)); + break; + case Tcert: + rp->cert = emalloc(sizeof(*rp->cert)); + break; + case Tsig: + rp->sig = emalloc(sizeof(*rp->sig)); + break; + case Tnull: + rp->null = emalloc(sizeof(*rp->null)); + break; + } + rp->ttl = 0; + rp->expire = 0; + rp->next = 0; + return rp; +} + +/* + * free a resource record and any related structs + */ +void +rrfree(RR *rp) +{ + DN *dp; + RR *nrp; + Txt *t; + + assert(rp->magic = RRmagic); + assert(!rp->cached); + + dp = rp->owner; + if(dp){ + assert(dp->magic == DNmagic); + for(nrp = dp->rr; nrp; nrp = nrp->next) + assert(nrp != rp); /* "rrfree of live rr" */; + } + + switch(rp->type){ + case Tsoa: + freeserverlist(rp->soa->slaves); + free(rp->soa); + break; + case Tkey: + free(rp->key->data); + free(rp->key); + break; + case Tcert: + free(rp->cert->data); + free(rp->cert); + break; + case Tsig: + free(rp->sig->data); + free(rp->sig); + break; + case Tnull: + free(rp->null->data); + free(rp->null); + break; + case Ttxt: + while(rp->txt != nil){ + t = rp->txt; + rp->txt = t->next; + free(t->p); + free(t); + } + break; + } + + rp->magic = ~rp->magic; + free(rp); +} + +/* + * free a list of resource records and any related structs + */ +void +rrfreelist(RR *rp) +{ + RR *next; + + for(; rp; rp = next){ + next = rp->next; + rrfree(rp); + } +} + +extern RR** +rrcopy(RR *rp, RR **last) +{ + RR *nrp; + SOA *soa; + Key *key; + Cert *cert; + Sig *sig; + Null *null; + Txt *t, *nt, **l; + + nrp = rralloc(rp->type); + switch(rp->type){ + case Ttxt: + *nrp = *rp; + l = &nrp->txt; + *l = nil; + for(t = rp->txt; t != nil; t = t->next){ + nt = emalloc(sizeof(*nt)); + nt->p = estrdup(t->p); + nt->next = nil; + *l = nt; + l = &nt->next; + } + break; + case Tsoa: + soa = nrp->soa; + *nrp = *rp; + nrp->soa = soa; + *nrp->soa = *rp->soa; + nrp->soa->slaves = copyserverlist(rp->soa->slaves); + break; + case Tkey: + key = nrp->key; + *nrp = *rp; + nrp->key = key; + *key = *rp->key; + key->data = emalloc(key->dlen); + memmove(key->data, rp->key->data, rp->key->dlen); + break; + case Tsig: + sig = nrp->sig; + *nrp = *rp; + nrp->sig = sig; + *sig = *rp->sig; + sig->data = emalloc(sig->dlen); + memmove(sig->data, rp->sig->data, rp->sig->dlen); + break; + case Tcert: + cert = nrp->cert; + *nrp = *rp; + nrp->cert = cert; + *cert = *rp->cert; + cert->data = emalloc(cert->dlen); + memmove(cert->data, rp->cert->data, rp->cert->dlen); + break; + case Tnull: + null = nrp->null; + *nrp = *rp; + nrp->null = null; + *null = *rp->null; + null->data = emalloc(null->dlen); + memmove(null->data, rp->null->data, rp->null->dlen); + break; + default: + *nrp = *rp; + break; + } + nrp->cached = 0; + nrp->next = 0; + *last = nrp; + return &nrp->next; +} + +/* + * lookup a resource record of a particular type and + * class attached to a domain name. Return copies. + * + * Priority ordering is: + * db authoritative + * not timed out network authoritative + * not timed out network unauthoritative + * unauthoritative db + * + * if flag NOneg is set, don't return negative cached entries. + * return nothing instead. + */ +RR* +rrlookup(DN *dp, int type, int flag) +{ + RR *rp, *first, **last; + + assert(dp->magic == DNmagic); + + first = 0; + last = &first; + lock(&dnlock); + + /* try for an authoritative db entry */ + for(rp = dp->rr; rp; rp = rp->next){ + assert(rp->magic == RRmagic && rp->cached); + if(rp->db) + if(rp->auth) + if(tsame(type, rp->type)) + last = rrcopy(rp, last); + } + if(first) + goto out; + + /* try for an living authoritative network entry */ + for(rp = dp->rr; rp; rp = rp->next){ + if(!rp->db) + if(rp->auth) + if(rp->ttl + 60 > now) + if(tsame(type, rp->type)){ + if(flag == NOneg && rp->negative) + goto out; + last = rrcopy(rp, last); + } + } + if(first) + goto out; + + /* try for an living unauthoritative network entry */ + for(rp = dp->rr; rp; rp = rp->next){ + if(!rp->db) + if(rp->ttl + 60 > now) + if(tsame(type, rp->type)){ + if(flag == NOneg && rp->negative) + goto out; + last = rrcopy(rp, last); + } + } + if(first) + goto out; + + /* try for an unauthoritative db entry */ + for(rp = dp->rr; rp; rp = rp->next){ + if(rp->db) + if(tsame(type, rp->type)) + last = rrcopy(rp, last); + } + if(first) + goto out; + + /* otherwise, settle for anything we got (except for negative caches) */ + for(rp = dp->rr; rp; rp = rp->next){ + if(tsame(type, rp->type)){ + if(rp->negative) + goto out; + last = rrcopy(rp, last); + } + } + +out: + unlock(&dnlock); + unique(first); + return first; +} + +/* + * convert an ascii RR type name to its integer representation + */ +int +rrtype(char *atype) +{ + int i; + + for(i = 0; i <= Tall; i++) + if(rrtname[i] && strcmp(rrtname[i], atype) == 0) + return i; + + // make any a synonym for all + if(strcmp(atype, "any") == 0) + return Tall; + return atoi(atype); +} + +/* + * convert an integer RR type to it's ascii name + */ +char* +rrname(int type, char *buf, int len) +{ + char *t; + + t = 0; + if(type <= Tall) + t = rrtname[type]; + if(t==0){ + snprint(buf, len, "%d", type); + t = buf; + } + return t; +} + +/* + * return 0 if not a supported rr type + */ +int +rrsupported(int type) +{ + if(type < 0 || type >Tall) + return 0; + return rrtname[type] != 0; +} + +/* + * compare 2 types + */ +int +tsame(int t1, int t2) +{ + return t1 == t2 || t1 == Tall; +} + +/* + * Add resource records to a list, duplicate them if they are cached + * RR's since these are shared. + */ +RR* +rrcat(RR **start, RR *rp) +{ + RR **last; + + last = start; + while(*last != 0) + last = &(*last)->next; + + *last = rp; + return *start; +} + +/* + * remove negative cache rr's from an rr list + */ +RR* +rrremneg(RR **l) +{ + RR **nl, *rp; + RR *first; + + first = nil; + nl = &first; + while(*l != nil){ + rp = *l; + if(rp->negative){ + *l = rp->next; + *nl = rp; + nl = &rp->next; + *nl = nil; + } else + l = &rp->next; + } + + return first; +} + +/* + * remove rr's of a particular type from an rr list + */ +RR* +rrremtype(RR **l, int type) +{ + RR **nl, *rp; + RR *first; + + first = nil; + nl = &first; + while(*l != nil){ + rp = *l; + if(rp->type == type){ + *l = rp->next; + *nl = rp; + nl = &rp->next; + *nl = nil; + } else + l = &(*l)->next; + } + + return first; +} + +/* + * print conversion for rr records + */ +int +rrfmt(Fmt *f) +{ + RR *rp; + char *strp; + Fmt fstr; + int rv; + char buf[Domlen]; + Server *s; + Txt *t; + + fmtstrinit(&fstr); + + rp = va_arg(f->args, RR*); + if(rp == 0){ + fmtprint(&fstr, "<null>"); + goto out; + } + + fmtprint(&fstr, "%s %s", rp->owner->name, + rrname(rp->type, buf, sizeof buf)); + + if(rp->negative){ + fmtprint(&fstr, "\tnegative - rcode %d", rp->negrcode); + goto out; + } + + switch(rp->type){ + case Thinfo: + fmtprint(&fstr, "\t%s %s", rp->cpu->name, rp->os->name); + break; + case Tcname: + case Tmb: + case Tmd: + case Tmf: + case Tns: + fmtprint(&fstr, "\t%s", rp->host->name); + break; + case Tmg: + case Tmr: + fmtprint(&fstr, "\t%s", rp->mb->name); + break; + case Tminfo: + fmtprint(&fstr, "\t%s %s", rp->mb->name, rp->rmb->name); + break; + case Tmx: + fmtprint(&fstr, "\t%lud %s", rp->pref, rp->host->name); + break; + case Ta: + case Taaaa: + fmtprint(&fstr, "\t%s", rp->ip->name); + break; + case Tptr: +// fmtprint(&fstr, "\t%s(%lud)", rp->ptr->name, rp->ptr->ordinal); + fmtprint(&fstr, "\t%s", rp->ptr->name); + break; + case Tsoa: + fmtprint(&fstr, "\t%s %s %lud %lud %lud %lud %lud", rp->host->name, + rp->rmb->name, rp->soa->serial, rp->soa->refresh, rp->soa->retry, + rp->soa->expire, rp->soa->minttl); + for(s = rp->soa->slaves; s != nil; s = s->next) + fmtprint(&fstr, " %s", s->name); + break; + case Tnull: + fmtprint(&fstr, "\t%.*H", rp->null->dlen, rp->null->data); + break; + case Ttxt: + fmtprint(&fstr, "\t"); + for(t = rp->txt; t != nil; t = t->next) + fmtprint(&fstr, "%s", t->p); + break; + case Trp: + fmtprint(&fstr, "\t%s %s", rp->rmb->name, rp->rp->name); + break; + case Tkey: + fmtprint(&fstr, "\t%d %d %d", rp->key->flags, rp->key->proto, + rp->key->alg); + break; + case Tsig: + fmtprint(&fstr, "\t%d %d %d %lud %lud %lud %d %s", + rp->sig->type, rp->sig->alg, rp->sig->labels, rp->sig->ttl, + rp->sig->exp, rp->sig->incep, rp->sig->tag, rp->sig->signer->name); + break; + case Tcert: + fmtprint(&fstr, "\t%d %d %d", + rp->sig->type, rp->sig->tag, rp->sig->alg); + break; + default: + break; + } +out: + strp = fmtstrflush(&fstr); + rv = fmtstrcpy(f, strp); + free(strp); + return rv; +} + +/* + * print conversion for rr records in attribute value form + */ +int +rravfmt(Fmt *f) +{ + RR *rp; + char *strp; + Fmt fstr; + int rv; + Server *s; + Txt *t; + int quote; + + fmtstrinit(&fstr); + + rp = va_arg(f->args, RR*); + if(rp == 0){ + fmtprint(&fstr, "<null>"); + goto out; + } + + if(rp->type == Tptr) + fmtprint(&fstr, "ptr=%s", rp->owner->name); + else + fmtprint(&fstr, "dom=%s", rp->owner->name); + + switch(rp->type){ + case Thinfo: + fmtprint(&fstr, " cpu=%s os=%s", rp->cpu->name, rp->os->name); + break; + case Tcname: + fmtprint(&fstr, " cname=%s", rp->host->name); + break; + case Tmb: + case Tmd: + case Tmf: + fmtprint(&fstr, " mbox=%s", rp->host->name); + break; + case Tns: + fmtprint(&fstr, " ns=%s", rp->host->name); + break; + case Tmg: + case Tmr: + fmtprint(&fstr, " mbox=%s", rp->mb->name); + break; + case Tminfo: + fmtprint(&fstr, " mbox=%s mbox=%s", rp->mb->name, rp->rmb->name); + break; + case Tmx: + fmtprint(&fstr, " pref=%lud mx=%s", rp->pref, rp->host->name); + break; + case Ta: + case Taaaa: + fmtprint(&fstr, " ip=%s", rp->ip->name); + break; + case Tptr: + fmtprint(&fstr, " dom=%s", rp->ptr->name); + break; + case Tsoa: + fmtprint(&fstr, " ns=%s mbox=%s serial=%lud refresh=%lud retry=%lud expire=%lud ttl=%lud", + rp->host->name, rp->rmb->name, rp->soa->serial, + rp->soa->refresh, rp->soa->retry, + rp->soa->expire, rp->soa->minttl); + for(s = rp->soa->slaves; s != nil; s = s->next) + fmtprint(&fstr, " dnsslave=%s", s->name); + break; + case Tnull: + fmtprint(&fstr, " null=%.*H", rp->null->dlen, rp->null->data); + break; + case Ttxt: + fmtprint(&fstr, " txt="); + quote = 0; + for(t = rp->txt; t != nil; t = t->next) + if(strchr(t->p, ' ')) + quote = 1; + if(quote) + fmtprint(&fstr, "\""); + for(t = rp->txt; t != nil; t = t->next) + fmtprint(&fstr, "%s", t->p); + if(quote) + fmtprint(&fstr, "\""); + break; + case Trp: + fmtprint(&fstr, " rp=%s txt=%s", rp->rmb->name, rp->rp->name); + break; + case Tkey: + fmtprint(&fstr, " flags=%d proto=%d alg=%d", + rp->key->flags, rp->key->proto, rp->key->alg); + break; + case Tsig: + fmtprint(&fstr, " type=%d alg=%d labels=%d ttl=%lud exp=%lud incep=%lud tag=%d signer=%s", + rp->sig->type, rp->sig->alg, rp->sig->labels, rp->sig->ttl, + rp->sig->exp, rp->sig->incep, rp->sig->tag, rp->sig->signer->name); + break; + case Tcert: + fmtprint(&fstr, " type=%d tag=%d alg=%d", + rp->sig->type, rp->sig->tag, rp->sig->alg); + break; + default: + break; + } +out: + strp = fmtstrflush(&fstr); + rv = fmtstrcpy(f, strp); + free(strp); + return rv; +} + +void +warning(char *fmt, ...) +{ + char dnserr[128]; + va_list arg; + + va_start(arg, fmt); + vseprint(dnserr, dnserr+sizeof(dnserr), fmt, arg); + va_end(arg); + syslog(1, "dns", dnserr); +} + +/* + * create a slave process to handle a request to avoid one request blocking + * another + */ +void +slave(Request *req) +{ + static int slaveid; + + if(req->isslave) + return; /* we're already a slave process */ + + /* limit parallelism */ + if(getactivity(req) > Maxactive){ + putactivity(); + return; + } + + switch(rfork(RFPROC|RFNOTEG|RFMEM|RFNOWAIT)){ + case -1: + putactivity(); + break; + case 0: + req->isslave = 1; + break; + default: + longjmp(req->mret, 1); + } +} + +/* + * chasing down double free's + */ +void +dncheck(void *p, int dolock) +{ + int i; + DN *dp; + RR *rp; + + if(p != nil){ + dp = p; + assert(dp->magic == DNmagic); + } + + if(!testing) + return; + + if(dolock) + lock(&dnlock); + for(i = 0; i < HTLEN; i++) + for(dp = ht[i]; dp; dp = dp->next){ + assert(dp != p); + assert(dp->magic == DNmagic); + for(rp = dp->rr; rp; rp = rp->next){ + assert(rp->magic == RRmagic); + assert(rp->cached); + assert(rp->owner == dp); + } + } + if(dolock) + unlock(&dnlock); +} + +static int +rrequiv(RR *r1, RR *r2) +{ + return r1->owner == r2->owner + && r1->type == r2->type + && r1->arg0 == r2->arg0 + && r1->arg1 == r2->arg1; +} + +void +unique(RR *rp) +{ + RR **l, *nrp; + + for(; rp; rp = rp->next){ + l = &rp->next; + for(nrp = *l; nrp; nrp = *l){ + if(rrequiv(rp, nrp)){ + *l = nrp->next; + rrfree(nrp); + } else + l = &nrp->next; + } + } +} + +/* + * true if second domain is subsumed by the first + */ +int +subsume(char *higher, char *lower) +{ + int hn, ln; + + ln = strlen(lower); + hn = strlen(higher); + if(ln < hn) + return 0; + + if(cistrcmp(lower + ln - hn, higher) != 0) + return 0; + + if(ln > hn && hn != 0 && lower[ln - hn - 1] != '.') + return 0; + + return 1; +} + +/* + * randomize the order we return items to provide some + * load balancing for servers. + * + * only randomize the first class of entries + */ +RR* +randomize(RR *rp) +{ + RR *first, *last, *x, *base; + ulong n; + + if(rp == nil || rp->next == nil) + return rp; + + /* just randomize addresses and mx's */ + for(x = rp; x; x = x->next) + if(x->type != Ta && x->type != Tmx && x->type != Tns) + return rp; + + base = rp; + + n = rand(); + last = first = nil; + while(rp != nil){ + /* stop randomizing if we've moved past our class */ + if(base->auth != rp->auth || base->db != rp->db){ + last->next = rp; + break; + } + + /* unchain */ + x = rp; + rp = x->next; + x->next = nil; + + if(n&1){ + /* add to tail */ + if(last == nil) + first = x; + else + last->next = x; + last = x; + } else { + /* add to head */ + if(last == nil) + last = x; + x->next = first; + first = x; + } + + /* reroll the dice */ + n >>= 1; + } + return first; +} + +static int +sencodefmt(Fmt *f) +{ + char *out; + char *buf; + int i, len; + int ilen; + int rv; + uchar *b; + char obuf[64]; // rsc optimization + + if(!(f->flags&FmtPrec) || f->prec < 1) + goto error; + + b = va_arg(f->args, uchar*); + if(b == nil) + goto error; + + /* if it's a printable, go for it */ + len = f->prec; + for(i = 0; i < len; i++) + if(!isprint(b[i])) + break; + if(i == len){ + if(len >= sizeof obuf) + len = sizeof(obuf)-1; + memmove(obuf, b, len); + obuf[len] = 0; + fmtstrcpy(f, obuf); + return 0; + } + + ilen = f->prec; + f->prec = 0; + f->flags &= ~FmtPrec; + switch(f->r){ + case '<': + len = (8*ilen+4)/5 + 3; + break; + case '[': + len = (8*ilen+5)/6 + 4; + break; + case 'H': + len = 2*ilen + 1; + break; + default: + goto error; + } + + if(len > sizeof(obuf)){ + buf = malloc(len); + if(buf == nil) + goto error; + } else + buf = obuf; + + // convert + out = buf; + switch(f->r){ + case '<': + rv = enc32(out, len, b, ilen); + break; + case '[': + rv = enc64(out, len, b, ilen); + break; + case 'H': + rv = enc16(out, len, b, ilen); + break; + default: + rv = -1; + break; + } + if(rv < 0) + goto error; + + fmtstrcpy(f, buf); + if(buf != obuf) + free(buf); + return 0; + +error: + return fmtstrcpy(f, "<encodefmt>"); + +} + +void* +emalloc(int size) +{ + char *x; + + x = mallocz(size, 1); + if(x == nil) + abort(); + setmalloctag(x, getcallerpc(&size)); + return x; +} + +char* +estrdup(char *s) +{ + int size; + char *p; + + size = strlen(s)+1; + p = mallocz(size, 0); + if(p == nil) + abort(); + memmove(p, s, size); + setmalloctag(p, getcallerpc(&s)); + return p; +} + +/* + * create a pointer record + */ +static RR* +mkptr(DN *dp, char *ptr, ulong ttl) +{ + DN *ipdp; + RR *rp; + + ipdp = dnlookup(ptr, Cin, 1); + + rp = rralloc(Tptr); + rp->ptr = dp; + rp->owner = ipdp; + rp->db = 1; + if(ttl) + rp->ttl = ttl; + return rp; +} + +/* + * look for all ip addresses in this network and make + * pointer records for them. + */ +void +dnptr(uchar *net, uchar *mask, char *dom, int bytes, int ttl) +{ + int i, j; + DN *dp; + RR *rp, *nrp, *first, **l; + uchar ip[IPaddrlen]; + uchar nnet[IPaddrlen]; + char ptr[Domlen]; + char *p, *e; + + l = &first; + first = nil; + for(i = 0; i < HTLEN; i++){ + for(dp = ht[i]; dp; dp = dp->next){ + for(rp = dp->rr; rp; rp = rp->next){ + if(rp->type != Ta || rp->negative) + continue; + parseip(ip, rp->ip->name); + maskip(ip, mask, nnet); + if(ipcmp(net, nnet) != 0) + continue; + p = ptr; + e = ptr+sizeof(ptr); + for(j = IPaddrlen-1; j >= IPaddrlen-bytes; j--) + p = seprint(p, e, "%d.", ip[j]); + seprint(p, e, "%s", dom); + nrp = mkptr(dp, ptr, ttl); + *l = nrp; + l = &nrp->next; + } + } + } + + for(rp = first; rp != nil; rp = nrp){ + nrp = rp->next; + rp->next = nil; + rrattach(rp, 1); + } +} + +void +freeserverlist(Server *s) +{ + Server *next; + + for(; s != nil; s = next){ + next = s->next; + free(s); + } +} + +void +addserver(Server **l, char *name) +{ + Server *s; + + while(*l) + l = &(*l)->next; + s = malloc(sizeof(Server)+strlen(name)+1); + if(s == nil) + return; + s->name = (char*)(s+1); + strcpy(s->name, name); + s->next = nil; + *l = s; +} + +Server* +copyserverlist(Server *s) +{ + Server *ns; + + + for(ns = nil; s != nil; s = s->next) + addserver(&ns, s->name); + return ns; +} diff --git a/src/cmd/ndb/dnarea.c b/src/cmd/ndb/dnarea.c new file mode 100755 index 00000000..15e0e849 --- /dev/null +++ b/src/cmd/ndb/dnarea.c @@ -0,0 +1,130 @@ +#include <u.h> +#include <libc.h> +#include <bio.h> +#include <ndb.h> +#include <ip.h> +#include "dns.h" + +Area *owned; +Area *delegated; + +/* + * true if a name is in our area + */ +Area* +inmyarea(char *name) +{ + int len; + Area *s, *d; + + len = strlen(name); + for(s = owned; s; s = s->next){ + if(s->len > len) + continue; + if(cistrcmp(s->soarr->owner->name, name + len - s->len) == 0) + if(len == s->len || name[len - s->len - 1] == '.') + break; + } + if(s == 0) + return 0; + + for(d = delegated; d; d = d->next){ + if(d->len > len) + continue; + if(cistrcmp(d->soarr->owner->name, name + len - d->len) == 0) + if(len == d->len || name[len - d->len - 1] == '.') + return 0; + } + + return s; +} + +/* + * our area is the part of the domain tree that + * we serve + */ +void +addarea(DN *dp, RR *rp, Ndbtuple *t) +{ + Area **l, *s; + + if(t->val[0]) + l = &delegated; + else + l = &owned; + + /* + * The area contains a copy of the soa rr that created it. + * The owner of the the soa rr should stick around as long + * as the area does. + */ + s = emalloc(sizeof(*s)); + s->len = strlen(dp->name); + rrcopy(rp, &s->soarr); + s->soarr->owner = dp; + s->soarr->db = 1; + s->soarr->ttl = Hour; + s->neednotify = 1; + s->needrefresh = 0; + +syslog(0, logfile, "new area %s", dp->name); + + s->next = *l; + *l = s; +} + +void +freearea(Area **l) +{ + Area *s; + + while(s = *l){ + *l = s->next; + rrfree(s->soarr); + free(s); + } +} + +/* + * refresh all areas that need it + * this entails running a command 'zonerefreshprogram'. This could + * copy over databases from elsewhere or just do a zone transfer. + */ +void +refresh_areas(Area *s) +{ + int pid; + Waitmsg *w; + + for(; s != nil; s = s->next){ + if(!s->needrefresh) + continue; + + if(zonerefreshprogram == nil){ + s->needrefresh = 0; + continue; + } + + switch(pid = fork()){ + case -1: + break; + case 0: + execl(zonerefreshprogram, "zonerefresh", s->soarr->owner->name, 0); + exits(0); + break; + default: + for(;;){ + w = wait(); + if(w == nil) + break; + if(w->pid == pid){ + if(w->msg == nil || *w->msg == 0) + s->needrefresh = 0; + free(w); + break; + } + free(w); + } + } + } +} diff --git a/src/cmd/ndb/dnnotify.c b/src/cmd/ndb/dnnotify.c new file mode 100755 index 00000000..a5d91005 --- /dev/null +++ b/src/cmd/ndb/dnnotify.c @@ -0,0 +1,160 @@ +#include <u.h> +#include <libc.h> +#include <ip.h> +#include <bio.h> +#include <ndb.h> +#include "dns.h" + +/* get a notification from another system of a changed zone */ +void +dnnotify(DNSmsg *reqp, DNSmsg *repp, Request *r) +{ + RR *tp; + Area *a; + + USED(r); + /* move one question from reqp to repp */ + memset(repp, 0, sizeof(*repp)); + tp = reqp->qd; + reqp->qd = tp->next; + tp->next = 0; + repp->qd = tp; + repp->id = reqp->id; + repp->flags = Fresp | Onotify | Fauth; + + /* anything to do? */ + if(zonerefreshprogram == nil) + return; + + /* make sure its the right type */ + if(repp->qd->type != Tsoa) + return; + +syslog(0, logfile, "notification for %s", repp->qd->owner->name); + + /* is it something we care about? */ + a = inmyarea(repp->qd->owner->name); + if(a == nil) + return; + +syslog(0, logfile, "serial old %lud new %lud", a->soarr->soa->serial, repp->qd->soa->serial); + + /* do nothing if it didn't change */ + if(a->soarr->soa->serial== repp->qd->soa->serial) + return; + + a->needrefresh = 1; +} + +static void +ding(void *u, char *msg) +{ + USED(u); + + if(strstr(msg, "alarm")) + noted(NCONT); + else + noted(NDFLT); +} + +/* notify a slave that an area has changed. */ +static void +send_notify(char *slave, RR *soa, Request *req) +{ + int i, len, n, reqno, status, fd; + uchar obuf[Maxudp+OUdphdrsize]; + uchar ibuf[Maxudp+OUdphdrsize]; + RR *rp; + OUdphdr *up = (OUdphdr*)obuf; + char *err; + DNSmsg repmsg; + + /* create the request */ + reqno = rand(); + n = mkreq(soa->owner, Cin, obuf, Fauth | Onotify, reqno); + + /* get an address */ + if(strcmp(ipattr(slave), "ip") == 0) { + parseip(up->raddr, slave); + } else { + rp = dnresolve(slave, Cin, Ta, req, nil, 0, 1, 1, &status); + if(rp == nil) + return; + parseip(up->raddr, rp->ip->name); + rrfree(rp); + } + + fd = udpport(); + if(fd < 0) + return; + + notify(ding); + + /* send 3 times or until we get anything back */ + for(i = 0; i < 3; i++){ +syslog(0, logfile, "sending %d byte notify to %s/%I.%d about %s", n, slave, up->raddr, nhgets(up->rport), soa->owner->name); + if(udpwrite(fd, (OUdphdr*)obuf, obuf+OUdphdrsize, n) != n) + break; + alarm(2*1000); + len = udpread(fd, (Udphdr*)ibuf, ibuf+OUdphdrsize, Maxudp); + alarm(0); + if(len <= OUdphdrsize) + continue; + err = convM2DNS(&ibuf[OUdphdrsize], len, &repmsg); + if(err != nil) + continue; + if(repmsg.id == reqno && (repmsg.flags & Omask) == Onotify) + break; + } + + close(fd); +} + +/* send notifies for any updated areas */ +static void +notify_areas(Area *a, Request *req) +{ + Server *s; + + for(; a != nil; a = a->next){ + if(!a->neednotify) + continue; + + /* send notifies to all slaves */ + for(s = a->soarr->soa->slaves; s != nil; s = s->next) + send_notify(s->name, a->soarr, req); + a->neednotify = 0; + } +} + +/* + * process to notify other servers of changes + * (also reads in new databases) + */ +void +notifyproc(void) +{ + Request req; + static int already; + + if(already) + return; + + switch(rfork(RFPROC|RFNOTEG|RFMEM|RFNOWAIT)){ + case -1: + return; + case 0: + break; + default: + return; + } + + req.isslave = 1; /* son't fork off subprocesses */ + + for(;;){ + getactivity(&req); + notify_areas(owned, &req); + putactivity(); + sleep(60*1000); + } +} diff --git a/src/cmd/ndb/dnresolve.c b/src/cmd/ndb/dnresolve.c new file mode 100755 index 00000000..253daca4 --- /dev/null +++ b/src/cmd/ndb/dnresolve.c @@ -0,0 +1,753 @@ +#include <u.h> +#include <libc.h> +#include <ip.h> +#include <bio.h> +#include <ndb.h> +#include "dns.h" + +enum +{ + Maxdest= 24, /* maximum destinations for a request message */ + Maxtrans= 3, /* maximum transmissions to a server */ +}; + +static int netquery(DN*, int, RR*, Request*, int); +static RR* dnresolve1(char*, int, int, Request*, int, int); + +char *LOG = "dns"; + +/* + * lookup 'type' info for domain name 'name'. If it doesn't exist, try + * looking it up as a canonical name. + */ +RR* +dnresolve(char *name, int class, int type, Request *req, RR **cn, int depth, int recurse, int rooted, int *status) +{ + RR *rp, *nrp, *drp; + DN *dp; + int loops; + char nname[Domlen]; + + if(status) + *status = 0; + + /* + * hack for systems that don't have resolve search + * lists. Just look up the simple name in the database. + */ + if(!rooted && strchr(name, '.') == 0){ + rp = nil; + drp = domainlist(class); + for(nrp = drp; nrp != nil; nrp = nrp->next){ + snprint(nname, sizeof(nname), "%s.%s", name, nrp->ptr->name); + rp = dnresolve(nname, class, type, req,cn, depth, recurse, rooted, status); + rrfreelist(rrremneg(&rp)); + if(rp != nil) + break; + } + if(drp != nil) + rrfree(drp); + return rp; + } + + /* + * try the name directly + */ + rp = dnresolve1(name, class, type, req, depth, recurse); + if(rp) + return randomize(rp); + + /* try it as a canonical name if we weren't told the name didn't exist */ + dp = dnlookup(name, class, 0); + if(type != Tptr && dp->nonexistent != Rname){ + for(loops=0; rp == nil && loops < 32; loops++){ + rp = dnresolve1(name, class, Tcname, req, depth, recurse); + if(rp == nil) + break; + + if(rp->negative){ + rrfreelist(rp); + rp = nil; + break; + } + + name = rp->host->name; + if(cn) + rrcat(cn, rp); + else + rrfreelist(rp); + + rp = dnresolve1(name, class, type, req, depth, recurse); + } + } + + /* distinction between not found and not good */ + if(rp == 0 && status != 0 && dp->nonexistent != 0) + *status = dp->nonexistent; + + return randomize(rp); +} + +static RR* +dnresolve1(char *name, int class, int type, Request *req, int depth, int recurse) +{ + DN *dp, *nsdp; + RR *rp, *nsrp, *dbnsrp; + char *cp; + + if(debug) + syslog(0, LOG, "dnresolve1 %s %d %d", name, type, class); + + /* only class Cin implemented so far */ + if(class != Cin) + return 0; + + dp = dnlookup(name, class, 1); + + /* + * Try the cache first + */ + rp = rrlookup(dp, type, OKneg); + if(rp){ + if(rp->db){ + /* unauthenticated db entries are hints */ + if(rp->auth) + return rp; + } else { + /* cached entry must still be valid */ + if(rp->ttl > now){ + /* but Tall entries are special */ + if(type != Tall || rp->query == Tall) + return rp; + } + } + } + rrfreelist(rp); + + /* + * try the cache for a canonical name. if found punt + * since we'll find it during the canonical name search + * in dnresolve(). + */ + if(type != Tcname){ + rp = rrlookup(dp, Tcname, NOneg); + rrfreelist(rp); + if(rp) + return 0; + } + + /* + * if we're running as just a resolver, go to our + * designated name servers + */ + if(resolver){ + nsrp = randomize(getdnsservers(class)); + if(nsrp != nil) { + if(netquery(dp, type, nsrp, req, depth+1)){ + rrfreelist(nsrp); + return rrlookup(dp, type, OKneg); + } + rrfreelist(nsrp); + } + } + + /* + * walk up the domain name looking for + * a name server for the domain. + */ + for(cp = name; cp; cp = walkup(cp)){ + /* + * if this is a local (served by us) domain, + * return answer + */ + dbnsrp = randomize(dblookup(cp, class, Tns, 0, 0)); + if(dbnsrp && dbnsrp->local){ + rp = dblookup(name, class, type, 1, dbnsrp->ttl); + rrfreelist(dbnsrp); + return rp; + } + + /* + * if recursion isn't set, just accept local + * entries + */ + if(recurse == Dontrecurse){ + if(dbnsrp) + rrfreelist(dbnsrp); + continue; + } + + /* look for ns in cache */ + nsdp = dnlookup(cp, class, 0); + nsrp = nil; + if(nsdp) + nsrp = randomize(rrlookup(nsdp, Tns, NOneg)); + + /* if the entry timed out, ignore it */ + if(nsrp && nsrp->ttl < now){ + rrfreelist(nsrp); + nsrp = nil; + } + + if(nsrp){ + rrfreelist(dbnsrp); + + /* try the name servers found in cache */ + if(netquery(dp, type, nsrp, req, depth+1)){ + rrfreelist(nsrp); + return rrlookup(dp, type, OKneg); + } + rrfreelist(nsrp); + continue; + } + + /* use ns from db */ + if(dbnsrp){ + /* try the name servers found in db */ + if(netquery(dp, type, dbnsrp, req, depth+1)){ + /* we got an answer */ + rrfreelist(dbnsrp); + return rrlookup(dp, type, NOneg); + } + rrfreelist(dbnsrp); + } + } + + /* settle for a non-authoritative answer */ + rp = rrlookup(dp, type, OKneg); + if(rp) + return rp; + + /* noone answered. try the database, we might have a chance. */ + return dblookup(name, class, type, 0, 0); +} + +/* + * walk a domain name one element to the right. return a pointer to that element. + * in other words, return a pointer to the parent domain name. + */ +char* +walkup(char *name) +{ + char *cp; + + cp = strchr(name, '.'); + if(cp) + return cp+1; + else if(*name) + return ""; + else + return 0; +} + +/* + * Get a udpport for requests and replies. + */ +int +udpport(void) +{ + int fd; + + if((fd = dial("udp!0!53", nil, nil, nil)) < 0) + warning("can't get udp port"); + return fd; +} + +int +mkreq(DN *dp, int type, uchar *buf, int flags, ushort reqno) +{ + DNSmsg m; + int len; + OUdphdr *uh = (OUdphdr*)buf; + + /* stuff port number into output buffer */ + memset(uh, 0, sizeof(*uh)); + hnputs(uh->rport, 53); + + /* make request and convert it to output format */ + memset(&m, 0, sizeof(m)); + m.flags = flags; + m.id = reqno; + m.qd = rralloc(type); + m.qd->owner = dp; + m.qd->type = type; + len = convDNS2M(&m, &buf[OUdphdrsize], Maxudp); + if(len < 0) + abort(); /* "can't convert" */; + rrfree(m.qd); + return len; +} + +/* for alarms in readreply */ +static void +ding(void *x, char *msg) +{ + USED(x); + if(strcmp(msg, "alarm") == 0) + noted(NCONT); + else + noted(NDFLT); +} + +static void +freeanswers(DNSmsg *mp) +{ + rrfreelist(mp->qd); + rrfreelist(mp->an); + rrfreelist(mp->ns); + rrfreelist(mp->ar); +} + +/* + * read replies to a request. ignore any of the wrong type. wait at most 5 seconds. + */ +static int +readreply(int fd, DN *dp, int type, ushort req, + uchar *ibuf, DNSmsg *mp, ulong endtime, Request *reqp) +{ + char *err; + int len; + ulong now; + RR *rp; + + notify(ding); + + for(; ; freeanswers(mp)){ + now = time(0); + if(now >= endtime) + return -1; /* timed out */ + + /* timed read */ + alarm((endtime - now) * 1000); + len = udpread(fd, (OUdphdr*)ibuf, ibuf+OUdphdrsize, Maxudpin); + alarm(0); + if(len < 0) + return -1; /* timed out */ + + /* convert into internal format */ + memset(mp, 0, sizeof(*mp)); + err = convM2DNS(&ibuf[OUdphdrsize], len, mp); + if(err){ + syslog(0, LOG, "input err %s: %I", err, ibuf); + continue; + } + if(debug) + logreply(reqp->id, ibuf, mp); + + /* answering the right question? */ + if(mp->id != req){ + syslog(0, LOG, "%d: id %d instead of %d: %I", reqp->id, + mp->id, req, ibuf); + continue; + } + if(mp->qd == 0){ + syslog(0, LOG, "%d: no question RR: %I", reqp->id, ibuf); + continue; + } + if(mp->qd->owner != dp){ + syslog(0, LOG, "%d: owner %s instead of %s: %I", reqp->id, + mp->qd->owner->name, dp->name, ibuf); + continue; + } + if(mp->qd->type != type){ + syslog(0, LOG, "%d: type %d instead of %d: %I", reqp->id, + mp->qd->type, type, ibuf); + continue; + } + + /* remember what request this is in answer to */ + for(rp = mp->an; rp; rp = rp->next) + rp->query = type; + + return 0; + } + + return 0; /* never reached */ +} + +/* + * return non-0 if first list includes second list + */ +int +contains(RR *rp1, RR *rp2) +{ + RR *trp1, *trp2; + + for(trp2 = rp2; trp2; trp2 = trp2->next){ + for(trp1 = rp1; trp1; trp1 = trp1->next){ + if(trp1->type == trp2->type) + if(trp1->host == trp2->host) + if(trp1->owner == trp2->owner) + break; + } + if(trp1 == 0) + return 0; + } + + return 1; +} + + +typedef struct Dest Dest; +struct Dest +{ + uchar a[IPaddrlen]; /* ip address */ + DN *s; /* name server */ + int nx; /* number of transmissions */ + int code; +}; + + +/* + * return multicast version if any + */ +int +ipisbm(uchar *ip) +{ + if(isv4(ip)){ + if(ip[IPv4off] >= 0xe0 && ip[IPv4off] < 0xf0) + return 4; + if(ipcmp(ip, IPv4bcast) == 0) + return 4; + } else { + if(ip[0] == 0xff) + return 6; + } + return 0; +} + +/* + * Get next server address + */ +static int +serveraddrs(RR *nsrp, Dest *dest, int nd, int depth, Request *reqp) +{ + RR *rp, *arp, *trp; + Dest *cur; + + if(nd >= Maxdest) + return 0; + + /* + * look for a server whose address we already know. + * if we find one, mark it so we ignore this on + * subsequent passes. + */ + arp = 0; + for(rp = nsrp; rp; rp = rp->next){ + assert(rp->magic == RRmagic); + if(rp->marker) + continue; + arp = rrlookup(rp->host, Ta, NOneg); + if(arp){ + rp->marker = 1; + break; + } + arp = dblookup(rp->host->name, Cin, Ta, 0, 0); + if(arp){ + rp->marker = 1; + break; + } + } + + /* + * if the cache and database lookup didn't find any new + * server addresses, try resolving one via the network. + * Mark any we try to resolve so we don't try a second time. + */ + if(arp == 0){ + for(rp = nsrp; rp; rp = rp->next){ + if(rp->marker) + continue; + rp->marker = 1; + + /* + * avoid loops looking up a server under itself + */ + if(subsume(rp->owner->name, rp->host->name)) + continue; + + arp = dnresolve(rp->host->name, Cin, Ta, reqp, 0, depth+1, Recurse, 1, 0); + rrfreelist(rrremneg(&arp)); + if(arp) + break; + } + } + + /* use any addresses that we found */ + for(trp = arp; trp; trp = trp->next){ + if(nd >= Maxdest) + break; + cur = &dest[nd]; + parseip(cur->a, trp->ip->name); + if(ipisbm(cur->a)) + continue; + cur->nx = 0; + cur->s = trp->owner; + cur->code = Rtimeout; + nd++; + } + rrfreelist(arp); + return nd; +} + +/* + * cache negative responses + */ +static void +cacheneg(DN *dp, int type, int rcode, RR *soarr) +{ + RR *rp; + DN *soaowner; + ulong ttl; + + /* no cache time specified, don' make anything up */ + if(soarr != nil){ + if(soarr->next != nil){ + rrfreelist(soarr->next); + soarr->next = nil; + } + soaowner = soarr->owner; + } else + soaowner = nil; + + /* the attach can cause soarr to be freed so mine it now */ + if(soarr != nil && soarr->soa != nil) + ttl = soarr->soa->minttl+now; + else + ttl = 5*Min; + + /* add soa and negative RR to the database */ + rrattach(soarr, 1); + + rp = rralloc(type); + rp->owner = dp; + rp->negative = 1; + rp->negsoaowner = soaowner; + rp->negrcode = rcode; + rp->ttl = ttl; + rrattach(rp, 1); +} + +/* + * query name servers. If the name server returns a pointer to another + * name server, recurse. + */ +static int +netquery1(int fd, DN *dp, int type, RR *nsrp, Request *reqp, int depth, uchar *ibuf, uchar *obuf) +{ + int ndest, j, len, replywaits, rv; + ushort req; + RR *tp, *soarr; + Dest *p, *l, *np; + DN *ndp; + Dest dest[Maxdest]; + DNSmsg m; + ulong endtime; + + /* pack request into a message */ + req = rand(); + len = mkreq(dp, type, obuf, Frecurse|Oquery, req); + + /* no server addresses yet */ + l = dest; + + /* + * transmit requests and wait for answers. + * at most Maxtrans attempts to each address. + * each cycle send one more message than the previous. + */ + for(ndest = 1; ndest < Maxdest; ndest++){ + p = dest; + + endtime = time(0); + if(endtime >= reqp->aborttime) + break; + + /* get a server address if we need one */ + if(ndest > l - p){ + j = serveraddrs(nsrp, dest, l - p, depth, reqp); + l = &dest[j]; + } + + /* no servers, punt */ + if(l == dest) + break; + + /* send to first 'ndest' destinations */ + j = 0; + for(; p < &dest[ndest] && p < l; p++){ + /* skip destinations we've finished with */ + if(p->nx >= Maxtrans) + continue; + + j++; + + /* exponential backoff of requests */ + if((1<<p->nx) > ndest) + continue; + + memmove(obuf, p->a, sizeof(p->a)); + if(debug) + logsend(reqp->id, depth, obuf, p->s->name, + dp->name, type); +{Udphdr *uh = (Udphdr*)obuf; +print("send %I %I %d %d\n", uh->raddr, uh->laddr, nhgets(uh->rport), nhgets(uh->lport)); +} + if(udpwrite(fd, (OUdphdr*)obuf, obuf+OUdphdrsize, len) < 0) + warning("sending udp msg %r"); + p->nx++; + } + if(j == 0) + break; /* no destinations left */ + + /* wait up to 5 seconds for replies */ + endtime = time(0) + 5; + if(endtime > reqp->aborttime) + endtime = reqp->aborttime; + + for(replywaits = 0; replywaits < ndest; replywaits++){ + if(readreply(fd, dp, type, req, ibuf, &m, endtime, reqp) < 0) + break; /* timed out */ + + /* find responder */ + for(p = dest; p < l; p++) + if(memcmp(p->a, ibuf, sizeof(p->a)) == 0) + break; + + /* remove all addrs of responding server from list */ + for(np = dest; np < l; np++) + if(np->s == p->s) + p->nx = Maxtrans; + + /* ignore any error replies */ + if((m.flags & Rmask) == Rserver){ + rrfreelist(m.qd); + rrfreelist(m.an); + rrfreelist(m.ar); + rrfreelist(m.ns); + if(p != l) + p->code = Rserver; + continue; + } + + /* ignore any bad delegations */ + if(m.ns && baddelegation(m.ns, nsrp, ibuf)){ + rrfreelist(m.ns); + m.ns = nil; + if(m.an == nil){ + rrfreelist(m.qd); + rrfreelist(m.ar); + if(p != l) + p->code = Rserver; + continue; + } + } + + + /* remove any soa's from the authority section */ + soarr = rrremtype(&m.ns, Tsoa); + + /* incorporate answers */ + if(m.an) + rrattach(m.an, (m.flags & Fauth) ? 1 : 0); + if(m.ar) + rrattach(m.ar, 0); + if(m.ns){ + ndp = m.ns->owner; + rrattach(m.ns, 0); + } else + ndp = 0; + + /* free the question */ + if(m.qd) + rrfreelist(m.qd); + + /* + * Any reply from an authoritative server, + * or a positive reply terminates the search + */ + if(m.an != nil || (m.flags & Fauth)){ + if(m.an == nil && (m.flags & Rmask) == Rname) + dp->nonexistent = Rname; + else + dp->nonexistent = 0; + + /* + * cache any negative responses, free soarr + */ + if((m.flags & Fauth) && m.an == nil) + cacheneg(dp, type, (m.flags & Rmask), soarr); + else + rrfreelist(soarr); + return 1; + } + rrfreelist(soarr); + + /* + * if we've been given better name servers + * recurse + */ + if(m.ns){ + tp = rrlookup(ndp, Tns, NOneg); + if(!contains(nsrp, tp)){ + rv = netquery(dp, type, tp, reqp, depth+1); + rrfreelist(tp); + return rv; + } else + rrfreelist(tp); + } + } + } + + /* if all servers returned failure, propogate it */ + dp->nonexistent = Rserver; + for(p = dest; p < l; p++) + if(p->code != Rserver) + dp->nonexistent = 0; + + return 0; +} + +typedef struct Qarg Qarg; +struct Qarg +{ + DN *dp; + int type; + RR *nsrp; + Request *reqp; + int depth; +}; + + +static int +netquery(DN *dp, int type, RR *nsrp, Request *reqp, int depth) +{ + uchar *obuf; + uchar *ibuf; + RR *rp; + int fd, rv; + + if(depth > 12) + return 0; + + /* use alloced buffers rather than ones from the stack */ + ibuf = emalloc(Maxudpin+OUdphdrsize); + obuf = emalloc(Maxudp+OUdphdrsize); + + slave(reqp); + + /* prepare server RR's for incremental lookup */ + for(rp = nsrp; rp; rp = rp->next) + rp->marker = 0; + + fd = udpport(); + if(fd < 0) + return 0; + rv = netquery1(fd, dp, type, nsrp, reqp, depth, ibuf, obuf); + close(fd); + free(ibuf); + free(obuf); + + return rv; +} diff --git a/src/cmd/ndb/dns.c b/src/cmd/ndb/dns.c new file mode 100755 index 00000000..5f509f24 --- /dev/null +++ b/src/cmd/ndb/dns.c @@ -0,0 +1,882 @@ +#include <u.h> +#include <libc.h> +#include <auth.h> +#include <fcall.h> +#include <bio.h> +#include <ctype.h> +#include <ip.h> +#include <ndb.h> +#include "dns.h" + +enum +{ + Maxrequest= 1024, + Ncache= 8, + Maxpath= 128, + Maxreply= 512, + Maxrrr= 16, + Maxfdata= 8192, + + Qdir= 0, + Qdns= 1, +}; + +typedef struct Mfile Mfile; +typedef struct Job Job; +typedef struct Network Network; + +int vers; /* incremented each clone/attach */ + +struct Mfile +{ + Mfile *next; /* next free mfile */ + int ref; + + char *user; + Qid qid; + int fid; + + int type; /* reply type */ + char reply[Maxreply]; + ushort rr[Maxrrr]; /* offset of rr's */ + ushort nrr; /* number of rr's */ +}; + +// +// active local requests +// +struct Job +{ + Job *next; + int flushed; + Fcall request; + Fcall reply; +}; +Lock joblock; +Job *joblist; + +struct { + Lock lk; + Mfile *inuse; /* active mfile's */ +} mfalloc; + +int haveip; +int mfd[2]; +int debug; +int traceactivity; +int cachedb; +ulong now; +int testing; +char *trace; +int needrefresh; +int resolver; +uchar ipaddr[IPaddrlen]; /* my ip address */ +int maxage; +char *zonerefreshprogram; +int sendnotifies; + +void rversion(Job*); +void rauth(Job*); +void rflush(Job*); +void rattach(Job*, Mfile*); +char* rwalk(Job*, Mfile*); +void ropen(Job*, Mfile*); +void rcreate(Job*, Mfile*); +void rread(Job*, Mfile*); +void rwrite(Job*, Mfile*, Request*); +void rclunk(Job*, Mfile*); +void rremove(Job*, Mfile*); +void rstat(Job*, Mfile*); +void rwstat(Job*, Mfile*); +void sendmsg(Job*, char*); +void mountinit(char*, char*); +void io(void); +int fillreply(Mfile*, int); +Job* newjob(void); +void freejob(Job*); +void setext(char*, int, char*); + +char *logfile = "dns"; +char *dbfile; +char mntpt[Maxpath]; +char *LOG; + +void +usage(void) +{ + fprint(2, "usage: %s [-rs] [-f ndb-file] [-x netmtpt]\n", argv0); + exits("usage"); +} + +void +main(int argc, char *argv[]) +{ + int serve; + char servefile[Maxpath]; + char ext[Maxpath]; + char *p; + + serve = 0; +// setnetmtpt(mntpt, sizeof(mntpt), nil); + ext[0] = 0; + ARGBEGIN{ + case 'd': + debug = 1; + traceactivity = 1; + break; + case 'f': + p = ARGF(); + if(p == nil) + usage(); + dbfile = p; + break; + case 'i': + haveip = 1; + parseip(ipaddr, EARGF(usage())); + break; +// case 'x': + // p = ARGF(); + // if(p == nil) + // usage(); + // setnetmtpt(mntpt, sizeof(mntpt), p); + // setext(ext, sizeof(ext), mntpt); + // break; + case 'r': + resolver = 1; + break; + case 's': + serve = 1; /* serve network */ + cachedb = 1; + break; + case 'a': + p = ARGF(); + if(p == nil) + usage(); + maxage = atoi(p); + break; + case 't': + testing = 1; + break; + case 'z': + zonerefreshprogram = ARGF(); + break; + case 'n': + sendnotifies = 1; + break; + }ARGEND + USED(argc); + USED(argv); + +//if(testing) mainmem->flags |= POOL_NOREUSE; +#define RFREND 0 + rfork(RFREND|RFNOTEG); + + /* start syslog before we fork */ + fmtinstall('F', fcallfmt); + dninit(); + if(!haveip && myipaddr(ipaddr, mntpt) < 0) + sysfatal("can't read my ip address"); + + syslog(0, logfile, "starting dns on %I", ipaddr); + + opendatabase(); + +/* + snprint(servefile, sizeof(servefile), "#s/dns%s", ext); + unmount(servefile, mntpt); + remove(servefile); +*/ + mountinit(servefile, mntpt); + + now = time(0); + srand(now*getpid()); + db2cache(1); + + if(serve) + proccreate(dnudpserver, mntpt, STACK); + if(sendnotifies) + notifyproc(); + + io(); + syslog(0, logfile, "io returned, exiting"); + exits(0); +} + +int +myipaddr(uchar *ip, char *net) +{ + ipmove(ip, ipaddr); + return 0; +} + +/* + * if a mount point is specified, set the cs extention to be the mount point + * with '_'s replacing '/'s + */ +void +setext(char *ext, int n, char *p) +{ + int i, c; + + n--; + for(i = 0; i < n; i++){ + c = p[i]; + if(c == 0) + break; + if(c == '/') + c = '_'; + ext[i] = c; + } + ext[i] = 0; +} + +void +mountinit(char *service, char *mntpt) +{ + int p[2]; + + if(pipe(p) < 0) + abort(); /* "pipe failed" */; + switch(rfork(RFFDG|RFPROC|RFNAMEG)){ + case 0: + close(p[1]); + break; + case -1: + abort(); /* "fork failed\n" */; + default: + close(p[0]); + + if(post9pservice(p[1], "dns") < 0) + fprint(2, "post9pservice dns: %r\n"); + _exits(0); + } + mfd[0] = mfd[1] = p[0]; +} + +Mfile* +newfid(int fid, int needunused) +{ + Mfile *mf; + + lock(&mfalloc.lk); + for(mf = mfalloc.inuse; mf != nil; mf = mf->next){ + if(mf->fid == fid){ + unlock(&mfalloc.lk); + if(needunused) + return nil; + return mf; + } + } + mf = emalloc(sizeof(*mf)); + if(mf == nil) + sysfatal("out of memory"); + mf->fid = fid; + mf->next = mfalloc.inuse; + mfalloc.inuse = mf; + unlock(&mfalloc.lk); + return mf; +} + +void +freefid(Mfile *mf) +{ + Mfile **l; + + lock(&mfalloc.lk); + for(l = &mfalloc.inuse; *l != nil; l = &(*l)->next){ + if(*l == mf){ + *l = mf->next; + if(mf->user) + free(mf->user); + free(mf); + unlock(&mfalloc.lk); + return; + } + } + sysfatal("freeing unused fid"); +} + +Mfile* +copyfid(Mfile *mf, int fid) +{ + Mfile *nmf; + + nmf = newfid(fid, 1); + if(nmf == nil) + return nil; + nmf->fid = fid; + nmf->user = estrdup(mf->user); + nmf->qid.type = mf->qid.type; + nmf->qid.path = mf->qid.path; + nmf->qid.vers = vers++; + return nmf; +} + +Job* +newjob(void) +{ + Job *job; + + job = emalloc(sizeof(*job)); + lock(&joblock); + job->next = joblist; + joblist = job; + job->request.tag = -1; + unlock(&joblock); + return job; +} + +void +freejob(Job *job) +{ + Job **l; + + lock(&joblock); + for(l = &joblist; *l; l = &(*l)->next){ + if((*l) == job){ + *l = job->next; + free(job); + break; + } + } + unlock(&joblock); +} + +void +flushjob(int tag) +{ + Job *job; + + lock(&joblock); + for(job = joblist; job; job = job->next){ + if(job->request.tag == tag && job->request.type != Tflush){ + job->flushed = 1; + break; + } + } + unlock(&joblock); +} + +void +io(void) +{ + long n; + Mfile *mf; + uchar mdata[IOHDRSZ + Maxfdata]; + Request req; + Job *job; + + /* + * a slave process is sometimes forked to wait for replies from other + * servers. The master process returns immediately via a longjmp + * through 'mret'. + */ + if(setjmp(req.mret)) + putactivity(); + req.isslave = 0; + for(;;){ + n = read9pmsg(mfd[0], mdata, sizeof mdata); + if(n<=0){ + syslog(0, logfile, "error reading mntpt: %r"); + exits(0); + } + job = newjob(); + if(convM2S(mdata, n, &job->request) != n){ + freejob(job); + continue; + } + mf = newfid(job->request.fid, 0); + if(debug) + syslog(0, logfile, "%F", &job->request); + + getactivity(&req); + req.aborttime = now + 60; /* don't spend more than 60 seconds */ + + switch(job->request.type){ + default: + syslog(1, logfile, "unknown request type %d", job->request.type); + break; + case Tversion: + rversion(job); + break; + case Tauth: + rauth(job); + break; + case Tflush: + rflush(job); + break; + case Tattach: + rattach(job, mf); + break; + case Twalk: + rwalk(job, mf); + break; + case Topen: + ropen(job, mf); + break; + case Tcreate: + rcreate(job, mf); + break; + case Tread: + rread(job, mf); + break; + case Twrite: + rwrite(job, mf, &req); + break; + case Tclunk: + rclunk(job, mf); + break; + case Tremove: + rremove(job, mf); + break; + case Tstat: + rstat(job, mf); + break; + case Twstat: + rwstat(job, mf); + break; + } + + freejob(job); + + /* + * slave processes die after replying + */ + if(req.isslave){ + putactivity(); + _exits(0); + } + + putactivity(); + } +} + +void +rversion(Job *job) +{ + if(job->request.msize > IOHDRSZ + Maxfdata) + job->reply.msize = IOHDRSZ + Maxfdata; + else + job->reply.msize = job->request.msize; + if(strncmp(job->request.version, "9P2000", 6) != 0) + sendmsg(job, "unknown 9P version"); + else{ + job->reply.version = "9P2000"; + sendmsg(job, 0); + } +} + +void +rauth(Job *job) +{ + sendmsg(job, "dns: authentication not required"); +} + +/* + * don't flush till all the slaves are done + */ +void +rflush(Job *job) +{ + flushjob(job->request.oldtag); + sendmsg(job, 0); +} + +void +rattach(Job *job, Mfile *mf) +{ + if(mf->user != nil) + free(mf->user); + mf->user = estrdup(job->request.uname); + mf->qid.vers = vers++; + mf->qid.type = QTDIR; + mf->qid.path = 0LL; + job->reply.qid = mf->qid; + sendmsg(job, 0); +} + +char* +rwalk(Job *job, Mfile *mf) +{ + char *err; + char **elems; + int nelems; + int i; + Mfile *nmf; + Qid qid; + + err = 0; + nmf = nil; + elems = job->request.wname; + nelems = job->request.nwname; + job->reply.nwqid = 0; + + if(job->request.newfid != job->request.fid){ + /* clone fid */ + if(job->request.newfid<0){ + err = "clone newfid out of range"; + goto send; + } + nmf = copyfid(mf, job->request.newfid); + if(nmf == nil){ + err = "clone bad newfid"; + goto send; + } + mf = nmf; + } + /* else nmf will be nil */ + + qid = mf->qid; + if(nelems > 0){ + /* walk fid */ + for(i=0; i<nelems && i<MAXWELEM; i++){ + if((qid.type & QTDIR) == 0){ + err = "not a directory"; + break; + } + if(strcmp(elems[i], "..") == 0 || strcmp(elems[i], ".") == 0){ + qid.type = QTDIR; + qid.path = Qdir; + Found: + job->reply.wqid[i] = qid; + job->reply.nwqid++; + continue; + } + if(strcmp(elems[i], "dns") == 0){ + qid.type = QTFILE; + qid.path = Qdns; + goto Found; + } + err = "file does not exist"; + break; + } + } + + send: + if(nmf != nil && (err!=nil || job->reply.nwqid<nelems)) + freefid(nmf); + if(err == nil) + mf->qid = qid; + sendmsg(job, err); + return err; +} + +void +ropen(Job *job, Mfile *mf) +{ + int mode; + char *err; + + err = 0; + mode = job->request.mode; + if(mf->qid.type & QTDIR){ + if(mode) + err = "permission denied"; + } + job->reply.qid = mf->qid; + job->reply.iounit = 0; + sendmsg(job, err); +} + +void +rcreate(Job *job, Mfile *mf) +{ + USED(mf); + sendmsg(job, "creation permission denied"); +} + +void +rread(Job *job, Mfile *mf) +{ + int i, n, cnt; + long off; + Dir dir; + uchar buf[Maxfdata]; + char *err; + long clock; + + n = 0; + err = 0; + off = job->request.offset; + cnt = job->request.count; + if(mf->qid.type & QTDIR){ + clock = time(0); + if(off == 0){ + dir.name = "dns"; + dir.qid.type = QTFILE; + dir.qid.vers = vers; + dir.qid.path = Qdns; + dir.mode = 0666; + dir.length = 0; + dir.uid = mf->user; + dir.gid = mf->user; + dir.muid = mf->user; + dir.atime = clock; /* wrong */ + dir.mtime = clock; /* wrong */ + n = convD2M(&dir, buf, sizeof buf); + } + job->reply.data = (char*)buf; + } else { + for(i = 1; i <= mf->nrr; i++) + if(mf->rr[i] > off) + break; + if(i > mf->nrr) + goto send; + if(off + cnt > mf->rr[i]) + n = mf->rr[i] - off; + else + n = cnt; + job->reply.data = mf->reply + off; + } +send: + job->reply.count = n; + sendmsg(job, err); +} + +void +rwrite(Job *job, Mfile *mf, Request *req) +{ + int cnt, rooted, status; + long n; + char *err, *p, *atype; + RR *rp, *tp, *neg; + int wantsav; + + err = 0; + cnt = job->request.count; + if(mf->qid.type & QTDIR){ + err = "can't write directory"; + goto send; + } + if(cnt >= Maxrequest){ + err = "request too long"; + goto send; + } + job->request.data[cnt] = 0; + if(cnt > 0 && job->request.data[cnt-1] == '\n') + job->request.data[cnt-1] = 0; + + /* + * special commands + */ + if(strncmp(job->request.data, "debug", 5)==0 && job->request.data[5] == 0){ + debug ^= 1; + goto send; + } else if(strncmp(job->request.data, "dump", 4)==0 && job->request.data[4] == 0){ + dndump("/lib/ndb/dnsdump"); + goto send; + } else if(strncmp(job->request.data, "refresh", 7)==0 && job->request.data[7] == 0){ + needrefresh = 1; + goto send; +// } else if(strncmp(job->request.data, "poolcheck", 9)==0 && job->request.data[9] == 0){ +// poolcheck(mainmem); +// goto send; + } + + /* + * kill previous reply + */ + mf->nrr = 0; + mf->rr[0] = 0; + + /* + * break up request (into a name and a type) + */ + atype = strchr(job->request.data, ' '); + if(atype == 0){ + err = "illegal request"; + goto send; + } else + *atype++ = 0; + + /* + * tracing request + */ + if(strcmp(atype, "trace") == 0){ + if(trace) + free(trace); + if(*job->request.data) + trace = estrdup(job->request.data); + else + trace = 0; + goto send; + } + + mf->type = rrtype(atype); + if(mf->type < 0){ + err = "unknown type"; + goto send; + } + + p = atype - 2; + if(p >= job->request.data && *p == '.'){ + rooted = 1; + *p = 0; + } else + rooted = 0; + + p = job->request.data; + if(*p == '!'){ + wantsav = 1; + p++; + } else + wantsav = 0; + dncheck(0, 1); + rp = dnresolve(p, Cin, mf->type, req, 0, 0, Recurse, rooted, &status); + dncheck(0, 1); + neg = rrremneg(&rp); + if(neg){ + status = neg->negrcode; + rrfreelist(neg); + } + if(rp == 0){ + switch(status){ + case Rname: + err = "name does not exist"; + break; + case Rserver: + err = "dns failure"; + break; + default: + err = "resource does not exist"; + break; + } + } else { + lock(&joblock); + if(!job->flushed){ + /* format data to be read later */ + n = 0; + mf->nrr = 0; + for(tp = rp; mf->nrr < Maxrrr-1 && n < Maxreply && tp && + tsame(mf->type, tp->type); tp = tp->next){ + mf->rr[mf->nrr++] = n; + if(wantsav) + n += snprint(mf->reply+n, Maxreply-n, "%Q", tp); + else + n += snprint(mf->reply+n, Maxreply-n, "%R", tp); + } + mf->rr[mf->nrr] = n; + } + unlock(&joblock); + rrfreelist(rp); + } + + send: + dncheck(0, 1); + job->reply.count = cnt; + sendmsg(job, err); +} + +void +rclunk(Job *job, Mfile *mf) +{ + freefid(mf); + sendmsg(job, 0); +} + +void +rremove(Job *job, Mfile *mf) +{ + USED(mf); + sendmsg(job, "remove permission denied"); +} + +void +rstat(Job *job, Mfile *mf) +{ + Dir dir; + uchar buf[IOHDRSZ+Maxfdata]; + + if(mf->qid.type & QTDIR){ + dir.name = "."; + dir.mode = DMDIR|0555; + } else { + dir.name = "dns"; + dir.mode = 0666; + } + dir.qid = mf->qid; + dir.length = 0; + dir.uid = mf->user; + dir.gid = mf->user; + dir.muid = mf->user; + dir.atime = dir.mtime = time(0); + job->reply.nstat = convD2M(&dir, buf, sizeof buf); + job->reply.stat = buf; + sendmsg(job, 0); +} + +void +rwstat(Job *job, Mfile *mf) +{ + USED(mf); + sendmsg(job, "wstat permission denied"); +} + +void +sendmsg(Job *job, char *err) +{ + int n; + uchar mdata[IOHDRSZ + Maxfdata]; + char ename[ERRMAX]; + + if(err){ + job->reply.type = Rerror; + snprint(ename, sizeof(ename), "dns: %s", err); + job->reply.ename = ename; + }else{ + job->reply.type = job->request.type+1; + } + job->reply.tag = job->request.tag; + n = convS2M(&job->reply, mdata, sizeof mdata); + if(n == 0){ + syslog(1, logfile, "sendmsg convS2M of %F returns 0", &job->reply); + abort(); + } + lock(&joblock); + if(job->flushed == 0) + if(write(mfd[1], mdata, n)!=n) + sysfatal("mount write"); + unlock(&joblock); + if(debug) + syslog(0, logfile, "%F %d", &job->reply, n); +} + +/* + * the following varies between dnsdebug and dns + */ +void +logreply(int id, uchar *addr, DNSmsg *mp) +{ + RR *rp; + + syslog(0, LOG, "%d: rcvd %I flags:%s%s%s%s%s", id, addr, + mp->flags & Fauth ? " auth" : "", + mp->flags & Ftrunc ? " trunc" : "", + mp->flags & Frecurse ? " rd" : "", + mp->flags & Fcanrec ? " ra" : "", + mp->flags & (Fauth|Rname) == (Fauth|Rname) ? + " nx" : ""); + for(rp = mp->qd; rp != nil; rp = rp->next) + syslog(0, LOG, "%d: rcvd %I qd %s", id, addr, rp->owner->name); + for(rp = mp->an; rp != nil; rp = rp->next) + syslog(0, LOG, "%d: rcvd %I an %R", id, addr, rp); + for(rp = mp->ns; rp != nil; rp = rp->next) + syslog(0, LOG, "%d: rcvd %I ns %R", id, addr, rp); + for(rp = mp->ar; rp != nil; rp = rp->next) + syslog(0, LOG, "%d: rcvd %I ar %R", id, addr, rp); +} + +void +logsend(int id, int subid, uchar *addr, char *sname, char *rname, int type) +{ + char buf[12]; + + syslog(0, LOG, "%d.%d: sending to %I/%s %s %s", + id, subid, addr, sname, rname, rrname(type, buf, sizeof buf)); +} + +RR* +getdnsservers(int class) +{ + return dnsservers(class); +} diff --git a/src/cmd/ndb/dns.h b/src/cmd/ndb/dns.h new file mode 100755 index 00000000..bfaaa68d --- /dev/null +++ b/src/cmd/ndb/dns.h @@ -0,0 +1,403 @@ +#define OUdphdrsize Udphdrsize +#define OUdphdr Udphdr + +enum +{ + /* RR types */ + Ta= 1, + Tns= 2, + Tmd= 3, + Tmf= 4, + Tcname= 5, + Tsoa= 6, + Tmb= 7, + Tmg= 8, + Tmr= 9, + Tnull= 10, + Twks= 11, + Tptr= 12, + Thinfo= 13, + Tminfo= 14, + Tmx= 15, + Ttxt= 16, + Trp= 17, + Tsig= 24, + Tkey= 25, + Taaaa= 28, + Tcert= 37, + + /* query types (all RR types are also queries) */ + Tixfr= 251, /* incremental zone transfer */ + Taxfr= 252, /* zone transfer */ + Tmailb= 253, /* { Tmb, Tmg, Tmr } */ + Tall= 255, /* all records */ + + /* classes */ + Csym= 0, /* internal symbols */ + Cin= 1, /* internet */ + Ccs, /* CSNET (obsolete) */ + Cch, /* Chaos net */ + Chs, /* Hesiod (?) */ + + /* class queries (all class types are also queries) */ + Call= 255, /* all classes */ + + /* opcodes */ + Oquery= 0<<11, /* normal query */ + Oinverse= 1<<11, /* inverse query */ + Ostatus= 2<<11, /* status request */ + Onotify= 4<<11, /* notify slaves of updates */ + Omask= 0xf<<11, /* mask for opcode */ + + /* response codes */ + Rok= 0, + Rformat= 1, /* format error */ + Rserver= 2, /* server failure (e.g. no answer from something) */ + Rname= 3, /* bad name */ + Runimplimented= 4, /* unimplemented */ + Rrefused= 5, /* we don't like you */ + Rmask= 0xf, /* mask for response */ + Rtimeout= 0x10, /* timeout sending (for internal use only) */ + + /* bits in flag word (other than opcode and response) */ + Fresp= 1<<15, /* message is a response */ + Fauth= 1<<10, /* true if an authoritative response */ + Ftrunc= 1<<9, /* truncated message */ + Frecurse= 1<<8, /* request recursion */ + Fcanrec= 1<<7, /* server can recurse */ + + Domlen= 256, /* max domain name length (with NULL) */ + Labellen= 256, /* max domain label length (with NULL) */ + Strlen= 256, /* max string length (with NULL) */ + Iplen= 32, /* max ascii ip address length (with NULL) */ + + /* time to live values (in seconds) */ + Min= 60, + Hour= 60*Min, /* */ + Day= 24*Hour, /* Ta, Tmx */ + Week= 7*Day, /* Tsoa, Tns */ + Year= 52*Week, + DEFTTL= Day, + + /* reserved time (can't be timed out earlier) */ + Reserved= 5*Min, + + /* packet sizes */ + Maxudp= 512, /* maximum bytes per udp message */ + Maxudpin= 2048, /* maximum bytes per udp message */ + + /* length of domain name hash table */ + HTLEN= 4*1024, + + RRmagic= 0xdeadbabe, + DNmagic= 0xa110a110, + + /* parallelism */ + Maxactive= 32, +}; + +typedef struct DN DN; +typedef struct DNSmsg DNSmsg; +typedef struct RR RR; +typedef struct SOA SOA; +typedef struct Area Area; +typedef struct Request Request; +typedef struct Key Key; +typedef struct Cert Cert; +typedef struct Sig Sig; +typedef struct Null Null; +typedef struct Server Server; +typedef struct Txt Txt; + +/* + * a structure to track a request and any slave process handling it + */ +struct Request +{ + int isslave; /* pid of slave */ + ulong aborttime; /* time at which we give up */ + jmp_buf mret; /* where master jumps to after starting a slave */ + int id; +}; + +/* + * a domain name + */ +struct DN +{ + DN *next; /* hash collision list */ + ulong magic; + char *name; /* owner */ + RR *rr; /* resource records off this name */ + ulong referenced; /* time last referenced */ + ulong lookuptime; /* last time we tried to get a better value */ + ushort class; /* RR class */ + char refs; /* for mark and sweep */ + char nonexistent; /* true if we get an authoritative nx for this domain */ + ulong ordinal; +}; + +/* + * security info + */ +struct Key +{ + int flags; + int proto; + int alg; + int dlen; + uchar *data; +}; +struct Cert +{ + int type; + int tag; + int alg; + int dlen; + uchar *data; +}; +struct Sig +{ + int type; + int alg; + int labels; + ulong ttl; + ulong exp; + ulong incep; + int tag; + DN *signer; + int dlen; + uchar *data; +}; +struct Null +{ + int dlen; + uchar *data; +}; + +/* + * text strings + */ +struct Txt +{ + Txt *next; + char *p; +}; + +/* + * an unpacked resource record + */ +struct RR +{ + RR *next; + ulong magic; + DN *owner; /* domain that owns this resource record */ + uchar negative; /* this is a cached negative response */ + ulong pc; + ulong ttl; /* time to live to be passed on */ + ulong expire; /* time this entry expires locally */ + ushort type; /* RR type */ + ushort query; /* query tyis is in response to */ + uchar auth; /* authoritative */ + uchar db; /* from database */ + uchar cached; /* rr in cache */ + ulong marker; /* used locally when scanning rrlists */ + union { + DN *negsoaowner; /* soa for cached negative response */ + DN *host; /* hostname - soa, cname, mb, md, mf, mx, ns */ + DN *cpu; /* cpu type - hinfo */ + DN *mb; /* mailbox - mg, minfo */ + DN *ip; /* ip addrss - a */ + DN *rp; /* rp arg - rp */ + int cruftlen; + ulong arg0; + }; + union { + int negrcode; /* response code for cached negative response */ + DN *rmb; /* responsible maibox - minfo, soa, rp */ + DN *ptr; /* pointer to domain name - ptr */ + DN *os; /* operating system - hinfo */ + ulong pref; /* preference value - mx */ + ulong local; /* ns served from local database - ns */ + ulong arg1; + }; + union { + SOA *soa; /* soa timers - soa */ + Key *key; + Cert *cert; + Sig *sig; + Null *null; + Txt *txt; + }; +}; + +/* + * list of servers + */ +struct Server +{ + Server *next; + char *name; +}; + +/* + * timers for a start of authenticated record + */ +struct SOA +{ + ulong serial; /* zone serial # (sec) - soa */ + ulong refresh; /* zone refresh interval (sec) - soa */ + ulong retry; /* zone retry interval (sec) - soa */ + ulong expire; /* time to expiration (sec) - soa */ + ulong minttl; /* minimum time to live for any entry (sec) - soa */ + Server *slaves; /* slave servers */ +}; + +/* + * domain messages + */ +struct DNSmsg +{ + ushort id; + int flags; + int qdcount; /* questions */ + RR *qd; + int ancount; /* answers */ + RR *an; + int nscount; /* name servers */ + RR *ns; + int arcount; /* hints */ + RR *ar; +}; + +/* + * definition of local area for dblookup + */ +struct Area +{ + Area *next; + + int len; /* strlen(area->soarr->owner->name) */ + RR *soarr; /* soa defining this area */ + int neednotify; + int needrefresh; +}; + +enum +{ + Recurse, + Dontrecurse, + NOneg, + OKneg, +}; + +/* dn.c */ +extern char *rrtname[]; +extern char *rname[]; +extern char *opname[]; +extern void db2cache(int); +extern void dninit(void); +extern DN* dnlookup(char*, int, int); +extern void dnage(DN*); +extern void dnageall(int); +extern void dnagedb(void); +extern void dnauthdb(void); +extern void dnget(void); +extern void dnpurge(void); +extern void dnput(void); +extern Area* inmyarea(char*); +extern void rrattach(RR*, int); +extern RR* rralloc(int); +extern void rrfree(RR*); +extern void rrfreelist(RR*); +extern RR* rrlookup(DN*, int, int); +extern RR* rrcat(RR**, RR*); +extern RR** rrcopy(RR*, RR**); +extern RR* rrremneg(RR**); +extern RR* rrremtype(RR**, int); +extern int rrfmt(Fmt*); +extern int rravfmt(Fmt*); +extern int rrsupported(int); +extern int rrtype(char*); +extern char* rrname(int, char*, int); +extern int tsame(int, int); +extern void dndump(char*); +extern int getactivity(Request*); +extern void putactivity(void); +extern void abort(); /* char*, ... */; +extern void warning(char*, ...); +extern void slave(Request*); +extern void dncheck(void*, int); +extern void unique(RR*); +extern int subsume(char*, char*); +extern RR* randomize(RR*); +extern void* emalloc(int); +extern char* estrdup(char*); +extern void dnptr(uchar*, uchar*, char*, int, int); +extern void addserver(Server**, char*); +extern Server* copyserverlist(Server*); +extern void freeserverlist(Server*); + +/* dnarea.c */ +extern void refresh_areas(Area*); +extern void freearea(Area**); +extern void addarea(DN *dp, RR *rp, Ndbtuple *t); + +/* dblookup.c */ +extern RR* dblookup(char*, int, int, int, int); +extern RR* dbinaddr(DN*, int); +extern int baddelegation(RR*, RR*, uchar*); +extern RR* dnsservers(int); +extern RR* domainlist(int); +extern int opendatabase(void); + +/* dns.c */ +extern char* walkup(char*); +extern RR* getdnsservers(int); +extern void logreply(int, uchar*, DNSmsg*); +extern void logsend(int, int, uchar*, char*, char*, int); + +/* dnresolve.c */ +extern RR* dnresolve(char*, int, int, Request*, RR**, int, int, int, int*); +extern int udpport(void); +extern int mkreq(DN *dp, int type, uchar *buf, int flags, ushort reqno); + +/* dnserver.c */ +extern void dnserver(DNSmsg*, DNSmsg*, Request*); +extern void dnudpserver(void*); +extern void dntcpserver(char*); + +/* dnnotify.c */ +extern void dnnotify(DNSmsg*, DNSmsg*, Request*); +extern void notifyproc(void); + +/* convDNS2M.c */ +extern int convDNS2M(DNSmsg*, uchar*, int); + +/* convM2DNS.c */ +extern char* convM2DNS(uchar*, int, DNSmsg*); + +/* malloc.c */ +extern void mallocsanity(void*); +extern void lasthist(void*, int, ulong); + +extern int debug; +extern int traceactivity; +extern char *trace; +extern int testing; /* test cache whenever removing a DN */ +extern int cachedb; +extern int needrefresh; +extern char *dbfile; +extern char mntpt[]; +extern char *logfile; +extern int resolver; +extern int maxage; /* age of oldest entry in cache (secs) */ +extern char *zonerefreshprogram; +extern int sendnotifies; +extern ulong now; /* time base */ +extern Area *owned; +extern Area *delegated; + +#pragma varargck type "R" RR* +#pragma varargck type "Q" RR* + diff --git a/src/cmd/ndb/dnsdebug.c b/src/cmd/ndb/dnsdebug.c new file mode 100755 index 00000000..b7339372 --- /dev/null +++ b/src/cmd/ndb/dnsdebug.c @@ -0,0 +1,473 @@ +#include <u.h> +#include <libc.h> +#include <bio.h> +#include <ctype.h> +#include <ip.h> +#include <ndb.h> +#include "dns.h" + +enum +{ + Maxrequest= 128, + Ncache= 8, + Maxpath= 128, + Maxreply= 512, + Maxrrr= 16, +}; + +static char *servername; +static RR *serverrr; +static RR *serveraddrs; + +int debug; +int cachedb; +ulong now; +int testing; +int traceactivity; +char *trace; +int needrefresh; +int resolver; +uchar ipaddr[IPaddrlen]; /* my ip address */ +int maxage; +char *logfile = "dns"; +char *dbfile; +char mntpt[Maxpath]; +char *zonerefreshprogram; + +int prettyrrfmt(Fmt*); +void preloadserveraddrs(void); +void squirrelserveraddrs(void); +int setserver(char*); +void doquery(char*, char*); +void docmd(int, char**); + +void +main(int argc, char *argv[]) +{ + int n; + Biobuf in; + char *p; + char *f[4]; + + strcpy(mntpt, "/net"); + + ARGBEGIN{ + case 'r': + resolver = 1; + break; + case 'x': + dbfile = "/lib/ndb/external"; + strcpy(mntpt, "/net.alt"); + break; + case 'f': + dbfile = ARGF(); + break; + }ARGEND + + now = time(0); + dninit(); + fmtinstall('R', prettyrrfmt); + if(myipaddr(ipaddr, mntpt) < 0) + sysfatal("can't read my ip address"); + opendatabase(); + + if(resolver) + squirrelserveraddrs(); + + debug = 1; + + if(argc > 0){ + docmd(argc, argv); + exits(0); + } + + Binit(&in, 0, OREAD); + for(print("> "); p = Brdline(&in, '\n'); print("> ")){ + p[Blinelen(&in)-1] = 0; + n = tokenize(p, f, 3); + if(n<1) + continue; + + /* flush the cache */ + dnpurge(); + + docmd(n, f); + + } + exits(0); +} + +static char* +longtime(long t) +{ + int d, h, m, n; + static char x[128]; + + for(d = 0; t >= 24*60*60; t -= 24*60*60) + d++; + for(h = 0; t >= 60*60; t -= 60*60) + h++; + for(m = 0; t >= 60; t -= 60) + m++; + n = 0; + if(d) + n += sprint(x, "%d day ", d); + if(h) + n += sprint(x+n, "%d hr ", h); + if(m) + n += sprint(x+n, "%d min ", m); + if(t || n == 0) + sprint(x+n, "%ld sec", t); + return x; +} + +int +prettyrrfmt(Fmt *f) +{ + RR *rp; + char buf[3*Domlen]; + char *p, *e; + Txt *t; + + rp = va_arg(f->args, RR*); + if(rp == 0){ + strcpy(buf, "<null>"); + goto out; + } + + p = buf; + e = buf + sizeof(buf); + p = seprint(p, e, "%-32.32s %-15.15s %-5.5s", rp->owner->name, + longtime(rp->db ? rp->ttl : (rp->ttl-now)), + rrname(rp->type, buf, sizeof buf)); + + if(rp->negative){ + seprint(p, e, "negative rcode %d\n", rp->negrcode); + goto out; + } + + switch(rp->type){ + case Thinfo: + seprint(p, e, "\t%s %s", rp->cpu->name, rp->os->name); + break; + case Tcname: + case Tmb: + case Tmd: + case Tmf: + case Tns: + seprint(p, e, "\t%s", rp->host->name); + break; + case Tmg: + case Tmr: + seprint(p, e, "\t%s", rp->mb->name); + break; + case Tminfo: + seprint(p, e, "\t%s %s", rp->mb->name, rp->rmb->name); + break; + case Tmx: + seprint(p, e, "\t%lud %s", rp->pref, rp->host->name); + break; + case Ta: + case Taaaa: + seprint(p, e, "\t%s", rp->ip->name); + break; + case Tptr: + seprint(p, e, "\t%s", rp->ptr->name); + break; + case Tsoa: + seprint(p, e, "\t%s %s %lud %lud %lud %lud %lud", rp->host->name, + rp->rmb->name, rp->soa->serial, rp->soa->refresh, rp->soa->retry, + rp->soa->expire, rp->soa->minttl); + break; + case Tnull: + seprint(p, e, "\t%.*H", rp->null->dlen, rp->null->data); + break; + case Ttxt: + p = seprint(p, e, "\t"); + for(t = rp->txt; t != nil; t = t->next) + p = seprint(p, e, "%s", t->p); + break; + case Trp: + seprint(p, e, "\t%s %s", rp->rmb->name, rp->rp->name); + break; + case Tkey: + seprint(p, e, "\t%d %d %d", rp->key->flags, rp->key->proto, + rp->key->alg); + break; + case Tsig: + seprint(p, e, "\t%d %d %d %lud %lud %lud %d %s", + rp->sig->type, rp->sig->alg, rp->sig->labels, rp->sig->ttl, + rp->sig->exp, rp->sig->incep, rp->sig->tag, rp->sig->signer->name); + break; + case Tcert: + seprint(p, e, "\t%d %d %d", + rp->sig->type, rp->sig->tag, rp->sig->alg); + break; + default: + break; + } +out: + return fmtstrcpy(f, buf); +} + +void +logsection(char *flag, RR *rp) +{ + if(rp == nil) + return; + print("\t%s%R\n", flag, rp); + for(rp = rp->next; rp != nil; rp = rp->next) + print("\t %R\n", rp); +} + +void +logreply(int id, uchar *addr, DNSmsg *mp) +{ + RR *rp; + char buf[12]; + char resp[32]; + + switch(mp->flags & Rmask){ + case Rok: + strcpy(resp, "OK"); + break; + case Rformat: + strcpy(resp, "Format error"); + break; + case Rserver: + strcpy(resp, "Server failed"); + break; + case Rname: + strcpy(resp, "Nonexistent"); + break; + case Runimplimented: + strcpy(resp, "Unimplemented"); + break; + case Rrefused: + strcpy(resp, "Refused"); + break; + default: + sprint(resp, "%d", mp->flags & Rmask); + break; + } + + print("%d: rcvd %s from %I (%s%s%s%s%s)\n", id, resp, addr, + mp->flags & Fauth ? "authoritative" : "", + mp->flags & Ftrunc ? " truncated" : "", + mp->flags & Frecurse ? " recurse" : "", + mp->flags & Fcanrec ? " can_recurse" : "", + mp->flags & (Fauth|Rname) == (Fauth|Rname) ? + " nx" : ""); + for(rp = mp->qd; rp != nil; rp = rp->next) + print("\tQ: %s %s\n", rp->owner->name, rrname(rp->type, buf, sizeof buf)); + logsection("Ans: ", mp->an); + logsection("Auth: ", mp->ns); + logsection("Hint: ", mp->ar); +} + +void +logsend(int id, int subid, uchar *addr, char *sname, char *rname, int type) +{ + char buf[12]; + + print("%d.%d: sending to %I/%s %s %s\n", id, subid, + addr, sname, rname, rrname(type, buf, sizeof buf)); +} + +RR* +getdnsservers(int class) +{ + RR *rr; + + if(servername == nil) + return dnsservers(class); + + rr = rralloc(Tns); + rr->owner = dnlookup("local#dns#servers", class, 1); + rr->host = dnlookup(servername, class, 1); + + return rr; +} + +void +squirrelserveraddrs(void) +{ + RR *rr, *rp, **l; + Request req; + + /* look up the resolver address first */ + resolver = 0; + debug = 0; + if(serveraddrs) + rrfreelist(serveraddrs); + serveraddrs = nil; + rr = getdnsservers(Cin); + l = &serveraddrs; + for(rp = rr; rp != nil; rp = rp->next){ + if(strcmp(ipattr(rp->host->name), "ip") == 0){ + *l = rralloc(Ta); + (*l)->owner = rp->host; + (*l)->ip = rp->host; + l = &(*l)->next; + continue; + } + req.isslave = 1; + req.aborttime = now + 60; /* don't spend more than 60 seconds */ + *l = dnresolve(rp->host->name, Cin, Ta, &req, 0, 0, Recurse, 0, 0); + while(*l != nil) + l = &(*l)->next; + } + resolver = 1; + debug = 1; +} + +void +preloadserveraddrs(void) +{ + RR *rp, **l, *first; + + l = &first; + for(rp = serveraddrs; rp != nil; rp = rp->next){ + rrcopy(rp, l); + rrattach(first, 1); + } +} + +int +setserver(char *server) +{ + if(servername != nil){ + free(servername); + servername = nil; + resolver = 0; + } + if(server == nil || *server == 0) + return 0; + servername = strdup(server); + squirrelserveraddrs(); + if(serveraddrs == nil){ + print("can't resolve %s\n", servername); + resolver = 0; + } else { + resolver = 1; + } + return resolver ? 0 : -1; +} + +void +doquery(char *name, char *tstr) +{ + Request req; + RR *rr, *rp; + int len, type; + char *p, *np; + int rooted; + char buf[1024]; + + if(resolver) + preloadserveraddrs(); + + /* default to an "ip" request if alpha, "ptr" if numeric */ + if(tstr == nil || *tstr == 0) { + if(strcmp(ipattr(name), "ip") == 0) + tstr = "ptr"; + else + tstr = "ip"; + } + + /* if name end in '.', remove it */ + len = strlen(name); + if(len > 0 && name[len-1] == '.'){ + rooted = 1; + name[len-1] = 0; + } else + rooted = 0; + + /* inverse queries may need to be permuted */ + strncpy(buf, name, sizeof buf); + if(strcmp("ptr", tstr) == 0 + && strstr(name, "IN-ADDR") == 0 + && strstr(name, "in-addr") == 0){ + for(p = name; *p; p++) + ; + *p = '.'; + np = buf; + len = 0; + while(p >= name){ + len++; + p--; + if(*p == '.'){ + memmove(np, p+1, len); + np += len; + len = 0; + } + } + memmove(np, p+1, len); + np += len; + strcpy(np, "in-addr.arpa"); + } + + /* look it up */ + type = rrtype(tstr); + if(type < 0){ + print("!unknown type %s\n", tstr); + return; + } + + getactivity(&req); + req.isslave = 1; + req.aborttime = now + 60; /* don't spend more than 60 seconds */ + rr = dnresolve(buf, Cin, type, &req, 0, 0, Recurse, rooted, 0); + if(rr){ + print("----------------------------\n"); + for(rp = rr; rp; rp = rp->next) + print("answer %R\n", rp); + print("----------------------------\n"); + } + rrfreelist(rr); + + putactivity(); +} + +void +docmd(int n, char **f) +{ + int tmpsrv; + char *name, *type; + + name = nil; + type = nil; + tmpsrv = 0; + + if(*f[0] == '@') { + if(setserver(f[0]+1) < 0) + return; + + switch(n){ + case 3: + type = f[2]; + /* fall through */ + case 2: + name = f[1]; + tmpsrv = 1; + break; + } + } else { + switch(n){ + case 2: + type = f[1]; + /* fall through */ + case 1: + name = f[0]; + break; + } + } + + if(name == nil) + return; + + doquery(name, type); + + if(tmpsrv) + setserver(""); +} diff --git a/src/cmd/ndb/dnserver.c b/src/cmd/ndb/dnserver.c new file mode 100755 index 00000000..509734b0 --- /dev/null +++ b/src/cmd/ndb/dnserver.c @@ -0,0 +1,178 @@ +#include <u.h> +#include <libc.h> +#include <ip.h> +#include <bio.h> +#include <ndb.h> +#include "dns.h" + +static RR* doextquery(DNSmsg*, Request*, int); +static void hint(RR**, RR*); + +extern char *logfile; + +/* + * answer a dns request + */ +void +dnserver(DNSmsg *reqp, DNSmsg *repp, Request *req) +{ + RR *tp, *neg; + char *cp; + DN *nsdp, *dp; + Area *myarea; + char tname[32]; + + dncheck(nil, 1); + + memset(repp, 0, sizeof(*repp)); + repp->id = reqp->id; + repp->flags = Fresp | Fcanrec | Oquery; + + /* move one question from reqp to repp */ + tp = reqp->qd; + reqp->qd = tp->next; + tp->next = 0; + repp->qd = tp; + + if(!rrsupported(repp->qd->type)){ + syslog(0, logfile, "server: request %s", rrname(repp->qd->type, tname, sizeof tname)); + repp->flags = Runimplimented | Fresp | Fcanrec | Oquery; + return; + } + + if(repp->qd->owner->class != Cin){ + syslog(0, logfile, "server: class %d", repp->qd->owner->class); + repp->flags = Runimplimented | Fresp | Fcanrec | Oquery; + return; + } + + myarea = inmyarea(repp->qd->owner->name); + if(myarea != nil && (repp->qd->type == Tixfr || repp->qd->type == Taxfr)){ + syslog(0, logfile, "server: request %s", rrname(repp->qd->type, tname, sizeof tname)); + repp->flags = Runimplimented | Fresp | Fcanrec | Oquery; + return; + } + + /* + * get the answer if we can + */ + if(reqp->flags & Frecurse) + neg = doextquery(repp, req, Recurse); + else + neg = doextquery(repp, req, Dontrecurse); + + /* authority is transitive */ + if(myarea != nil || (repp->an && repp->an->auth)) + repp->flags |= Fauth; + + /* pass on error codes */ + if(repp->an == 0){ + dp = dnlookup(repp->qd->owner->name, repp->qd->owner->class, 0); + if(dp->rr == 0) + if(reqp->flags & Frecurse) + repp->flags |= dp->nonexistent|Fauth; + } + + if(myarea == nil){ + /* + * add name server if we know + */ + for(cp = repp->qd->owner->name; cp; cp = walkup(cp)){ + nsdp = dnlookup(cp, repp->qd->owner->class, 0); + if(nsdp == 0) + continue; + + repp->ns = rrlookup(nsdp, Tns, OKneg); + if(repp->ns){ + /* don't pass on anything we know is wrong */ + if(repp->ns->negative){ + rrfreelist(repp->ns); + repp->ns = nil; + } + break; + } + + repp->ns = dblookup(cp, repp->qd->owner->class, Tns, 0, 0); + if(repp->ns) + break; + } + } + + /* + * add ip addresses as hints + */ + if(repp->qd->type != Taxfr && repp->qd->type != Tixfr){ + for(tp = repp->ns; tp; tp = tp->next) + hint(&repp->ar, tp); + for(tp = repp->an; tp; tp = tp->next) + hint(&repp->ar, tp); + } + + /* + * add an soa to the authority section to help client with negative caching + */ + if(repp->an == nil){ + if(myarea != nil){ + rrcopy(myarea->soarr, &tp); + rrcat(&repp->ns, tp); + } else if(neg != nil) { + if(neg->negsoaowner != nil) + rrcat(&repp->ns, rrlookup(neg->negsoaowner, Tsoa, NOneg)); + repp->flags |= neg->negrcode; + } + } + + /* + * get rid of duplicates + */ + unique(repp->an); + unique(repp->ns); + unique(repp->ar); + + rrfreelist(neg); + + dncheck(nil, 1); +} + +/* + * satisfy a recursive request. dnlookup will handle cnames. + */ +static RR* +doextquery(DNSmsg *mp, Request *req, int recurse) +{ + int type; + char *name; + RR *rp, *neg; + + name = mp->qd->owner->name; + type = mp->qd->type; + rp = dnresolve(name, Cin, type, req, &mp->an, 0, recurse, 1, 0); + + /* don't return soa hints as answers, it's wrong */ + if(rp && rp->db && !rp->auth && rp->type == Tsoa) + rrfreelist(rp); + + /* don't let negative cached entries escape */ + neg = rrremneg(&rp); + rrcat(&mp->an, rp); + return neg; +} + +static void +hint(RR **last, RR *rp) +{ + RR *hp; + + switch(rp->type){ + case Tns: + case Tmx: + case Tmb: + case Tmf: + case Tmd: + hp = rrlookup(rp->host, Ta, NOneg); + if(hp == nil) + hp = dblookup(rp->host->name, Cin, Ta, 0, 0); + rrcat(last, hp); + break; + } +} diff --git a/src/cmd/ndb/dnsquery.c b/src/cmd/ndb/dnsquery.c new file mode 100755 index 00000000..597cc480 --- /dev/null +++ b/src/cmd/ndb/dnsquery.c @@ -0,0 +1,113 @@ +#include <u.h> +#include <libc.h> +#include <bio.h> +#include <ndb.h> +#include "dns.h" +#include "ip.h" + +void +main(int argc, char *argv[]) +{ + int fd, n, len, domount; + Biobuf in; + char line[1024], *lp, *p, *np, *mtpt, *srv, *dns; + char buf[1024]; + + dns = "/net/dns"; + mtpt = "/net"; + srv = "/srv/dns"; + domount = 1; + ARGBEGIN { + case 'x': + dns = "/net.alt/dns"; + mtpt = "/net.alt"; + srv = "/srv/dns_net.alt"; + break; + default: + fprint(2, "usage: %s -x [dns-mount-point]\n", argv0); + exits("usage"); + } ARGEND; + + if(argc == 1){ + domount = 0; + mtpt = argv[0]; + } + + fd = open(dns, ORDWR); + if(fd < 0){ + if(domount == 0){ + fprint(2, "can't open %s: %r\n", mtpt); + exits(0); + } + fd = open(srv, ORDWR); + if(fd < 0){ + print("can't open %s: %r\n", srv); + exits(0); + } + if(mount(fd, -1, mtpt, MBEFORE, "") < 0){ + print("can't mount(%s, %s): %r\n", srv, mtpt); + exits(0); + } + fd = open(mtpt, ORDWR); + if(fd < 0){ + print("can't open %s: %r\n", mtpt); + exits(0); + } + } + Binit(&in, 0, OREAD); + for(print("> "); lp = Brdline(&in, '\n'); print("> ")){ + n = Blinelen(&in)-1; + strncpy(line, lp, n); + line[n] = 0; + if (n<=1) + continue; + /* default to an "ip" request if alpha, "ptr" if numeric */ + if (strchr(line, ' ')==0) { + if(strcmp(ipattr(line), "ip") == 0) { + strcat(line, " ptr"); + n += 4; + } else { + strcat(line, " ip"); + n += 3; + } + } + + /* inverse queries may need to be permuted */ + if(n > 4 && strcmp("ptr", &line[n-3]) == 0 + && strstr(line, "IN-ADDR") == 0 && strstr(line, "in-addr") == 0){ + for(p = line; *p; p++) + if(*p == ' '){ + *p = '.'; + break; + } + np = buf; + len = 0; + while(p >= line){ + len++; + p--; + if(*p == '.'){ + memmove(np, p+1, len); + np += len; + len = 0; + } + } + memmove(np, p+1, len); + np += len; + strcpy(np, "in-addr.arpa ptr"); + strcpy(line, buf); + n = strlen(line); + } + + seek(fd, 0, 0); + if(write(fd, line, n) < 0) { + print("!%r\n"); + continue; + } + seek(fd, 0, 0); + while((n = read(fd, buf, sizeof(buf))) > 0){ + buf[n] = 0; + print("%s\n", buf); + } + } + exits(0); +} diff --git a/src/cmd/ndb/dnstcp.c b/src/cmd/ndb/dnstcp.c new file mode 100755 index 00000000..19a7540d --- /dev/null +++ b/src/cmd/ndb/dnstcp.c @@ -0,0 +1,362 @@ +#include <u.h> +#include <libc.h> +#include <ip.h> +#include "dns.h" + +enum +{ + Maxpath= 128, +}; + +char *logfile = "dns"; +char *dbfile; +int debug; +int cachedb = 1; +int testing; +int traceactivity; +int needrefresh; +int resolver; +char mntpt[Maxpath]; +char *caller = ""; +ulong now; +int maxage; +uchar ipaddr[IPaddrlen]; /* my ip address */ +char *LOG; +char *zonerefreshprogram; + +static int readmsg(int, uchar*, int); +static void reply(int, DNSmsg*, Request*); +static void dnzone(DNSmsg*, DNSmsg*, Request*); +static void getcaller(char*); +static void refreshmain(char*); + +void +main(int argc, char *argv[]) +{ + int len; + Request req; + DNSmsg reqmsg, repmsg; + uchar buf[512]; + char tname[32]; + char *err; + char *ext = ""; + + ARGBEGIN{ + case 'd': + debug++; + break; + case 'f': + dbfile = ARGF(); + break; + case 'r': + resolver = 1; + break; + case 'x': + ext = ARGF(); + break; + }ARGEND + + if(debug < 2) + debug = 0; + + if(argc > 0) + getcaller(argv[0]); + + dninit(); + + snprint(mntpt, sizeof(mntpt), "/net%s", ext); + if(myipaddr(ipaddr, mntpt) < 0) + sysfatal("can't read my ip address"); + syslog(0, logfile, "dnstcp call from %s to %I", caller, ipaddr); + + db2cache(1); + + setjmp(req.mret); + req.isslave = 0; + + /* loop on requests */ + for(;; putactivity()){ + now = time(0); + memset(&repmsg, 0, sizeof(repmsg)); + alarm(10*60*1000); + len = readmsg(0, buf, sizeof(buf)); + alarm(0); + if(len <= 0) + break; + getactivity(&req); + req.aborttime = now + 15*Min; + err = convM2DNS(buf, len, &reqmsg); + if(err){ + syslog(0, logfile, "server: input error: %s from %I", err, buf); + break; + } + if(reqmsg.qdcount < 1){ + syslog(0, logfile, "server: no questions from %I", buf); + break; + } + if(reqmsg.flags & Fresp){ + syslog(0, logfile, "server: reply not request from %I", buf); + break; + } + if((reqmsg.flags & Omask) != Oquery){ + syslog(0, logfile, "server: op %d from %I", reqmsg.flags & Omask, buf); + break; + } + + if(debug) + syslog(0, logfile, "%d: serve (%s) %d %s %s", + req.id, caller, + reqmsg.id, + reqmsg.qd->owner->name, + rrname(reqmsg.qd->type, tname, sizeof tname)); + + /* loop through each question */ + while(reqmsg.qd){ + if(reqmsg.qd->type == Taxfr){ + dnzone(&reqmsg, &repmsg, &req); + } else { + dnserver(&reqmsg, &repmsg, &req); + reply(1, &repmsg, &req); + rrfreelist(repmsg.qd); + rrfreelist(repmsg.an); + rrfreelist(repmsg.ns); + rrfreelist(repmsg.ar); + } + } + + rrfreelist(reqmsg.qd); + rrfreelist(reqmsg.an); + rrfreelist(reqmsg.ns); + rrfreelist(reqmsg.ar); + + if(req.isslave){ + putactivity(); + _exits(0); + } + } + refreshmain(mntpt); +} + +static int +readmsg(int fd, uchar *buf, int max) +{ + int n; + uchar x[2]; + + if(readn(fd, x, 2) != 2) + return -1; + n = (x[0]<<8) | x[1]; + if(n > max) + return -1; + if(readn(fd, buf, n) != n) + return -1; + return n; +} + +static void +reply(int fd, DNSmsg *rep, Request *req) +{ + int len, rv; + char tname[32]; + uchar buf[4096]; + RR *rp; + + if(debug){ + syslog(0, logfile, "%d: reply (%s) %s %s %ux", + req->id, caller, + rep->qd->owner->name, + rrname(rep->qd->type, tname, sizeof tname), + rep->flags); + for(rp = rep->an; rp; rp = rp->next) + syslog(0, logfile, "an %R", rp); + for(rp = rep->ns; rp; rp = rp->next) + syslog(0, logfile, "ns %R", rp); + for(rp = rep->ar; rp; rp = rp->next) + syslog(0, logfile, "ar %R", rp); + } + + + len = convDNS2M(rep, buf+2, sizeof(buf) - 2); + if(len <= 0) + abort(); /* "dnserver: converting reply" */; + buf[0] = len>>8; + buf[1] = len; + rv = write(fd, buf, len+2); + if(rv != len+2){ + syslog(0, logfile, "sending reply: %d instead of %d", rv, len+2); + exits(0); + } +} + +/* + * Hash table for domain names. The hash is based only on the + * first element of the domain name. + */ +extern DN *ht[HTLEN]; + +static int +numelem(char *name) +{ + int i; + + i = 1; + for(; *name; name++) + if(*name == '.') + i++; + return i; +} + +int +inzone(DN *dp, char *name, int namelen, int depth) +{ + int n; + + if(dp->name == 0) + return 0; + if(numelem(dp->name) != depth) + return 0; + n = strlen(dp->name); + if(n < namelen) + return 0; + if(strcmp(name, dp->name + n - namelen) != 0) + return 0; + if(n > namelen && dp->name[n - namelen - 1] != '.') + return 0; + return 1; +} + +static void +dnzone(DNSmsg *reqp, DNSmsg *repp, Request *req) +{ + DN *dp, *ndp; + RR r, *rp; + int h, depth, found, nlen; + + memset(repp, 0, sizeof(*repp)); + repp->id = reqp->id; + repp->flags = Fauth | Fresp | Fcanrec | Oquery; + repp->qd = reqp->qd; + reqp->qd = reqp->qd->next; + repp->qd->next = 0; + dp = repp->qd->owner; + + /* send the soa */ + repp->an = rrlookup(dp, Tsoa, NOneg); + reply(1, repp, req); + if(repp->an == 0) + goto out; + rrfreelist(repp->an); + + nlen = strlen(dp->name); + + /* construct a breadth first search of the name space (hard with a hash) */ + repp->an = &r; + for(depth = numelem(dp->name); ; depth++){ + found = 0; + for(h = 0; h < HTLEN; h++) + for(ndp = ht[h]; ndp; ndp = ndp->next) + if(inzone(ndp, dp->name, nlen, depth)){ + for(rp = ndp->rr; rp; rp = rp->next){ + /* there shouldn't be negatives, but just in case */ + if(rp->negative) + continue; + + /* don't send an soa's, ns's are enough */ + if(rp->type == Tsoa) + continue; + + r = *rp; + r.next = 0; + reply(1, repp, req); + } + found = 1; + } + if(!found) + break; + } + + /* resend the soa */ + repp->an = rrlookup(dp, Tsoa, NOneg); + reply(1, repp, req); + rrfreelist(repp->an); +out: + rrfree(repp->qd); +} + +static void +getcaller(char *dir) +{ + int fd, n; + static char remote[128]; + + snprint(remote, sizeof(remote), "%s/remote", dir); + fd = open(remote, OREAD); + if(fd < 0) + return; + n = read(fd, remote, sizeof(remote)-1); + close(fd); + if(n <= 0) + return; + if(remote[n-1] == '\n') + n--; + remote[n] = 0; + caller = remote; +} + +static void +refreshmain(char *net) +{ + int fd; + char file[128]; + + snprint(file, sizeof(file), "%s/dns", net); + if(debug) + syslog(0, logfile, "refreshing %s", file); + fd = open(file, ORDWR); + if(fd < 0){ + syslog(0, logfile, "can't refresh %s", file); + return; + } + fprint(fd, "refresh"); + close(fd); +} + +/* + * the following varies between dnsdebug and dns + */ +void +logreply(int id, uchar *addr, DNSmsg *mp) +{ + RR *rp; + + syslog(0, LOG, "%d: rcvd %I flags:%s%s%s%s%s", id, addr, + mp->flags & Fauth ? " auth" : "", + mp->flags & Ftrunc ? " trunc" : "", + mp->flags & Frecurse ? " rd" : "", + mp->flags & Fcanrec ? " ra" : "", + mp->flags & (Fauth|Rname) == (Fauth|Rname) ? + " nx" : ""); + for(rp = mp->qd; rp != nil; rp = rp->next) + syslog(0, LOG, "%d: rcvd %I qd %s", id, addr, rp->owner->name); + for(rp = mp->an; rp != nil; rp = rp->next) + syslog(0, LOG, "%d: rcvd %I an %R", id, addr, rp); + for(rp = mp->ns; rp != nil; rp = rp->next) + syslog(0, LOG, "%d: rcvd %I ns %R", id, addr, rp); + for(rp = mp->ar; rp != nil; rp = rp->next) + syslog(0, LOG, "%d: rcvd %I ar %R", id, addr, rp); +} + +void +logsend(int id, int subid, uchar *addr, char *sname, char *rname, int type) +{ + char buf[12]; + + syslog(0, LOG, "%d.%d: sending to %I/%s %s %s", + id, subid, addr, sname, rname, rrname(type, buf, sizeof buf)); +} + +RR* +getdnsservers(int class) +{ + return dnsservers(class); +} diff --git a/src/cmd/ndb/dnudpserver.c b/src/cmd/ndb/dnudpserver.c new file mode 100755 index 00000000..f0ed37cd --- /dev/null +++ b/src/cmd/ndb/dnudpserver.c @@ -0,0 +1,228 @@ +#include <u.h> +#include <libc.h> +#include <ip.h> +#include <bio.h> +#include <ndb.h> +#include "dns.h" + +static int udpannounce(char*); +static void reply(int, uchar*, DNSmsg*, Request*); + +extern char *logfile; + +static void +ding(void *x, char *msg) +{ + USED(x); + if(strcmp(msg, "alarm") == 0) + noted(NCONT); + else + noted(NDFLT); +} + +typedef struct Inprogress Inprogress; +struct Inprogress +{ + int inuse; + OUdphdr uh; + DN *owner; + int type; + int id; +}; +Inprogress inprog[Maxactive+2]; + +/* + * record client id and ignore retransmissions. + * we're still single thread at this point. + */ +static Inprogress* +clientrxmit(DNSmsg *req, uchar *buf) +{ + Inprogress *p, *empty; + OUdphdr *uh; + + uh = (OUdphdr *)buf; + empty = 0; + for(p = inprog; p < &inprog[Maxactive]; p++){ + if(p->inuse == 0){ + if(empty == 0) + empty = p; + continue; + } + if(req->id == p->id) + if(req->qd->owner == p->owner) + if(req->qd->type == p->type) + if(memcmp(uh, &p->uh, OUdphdrsize) == 0) + return 0; + } + if(empty == 0) + return 0; /* shouldn't happen - see slave() and definition of Maxactive */ + + empty->id = req->id; + empty->owner = req->qd->owner; + empty->type = req->qd->type; + memmove(&empty->uh, uh, OUdphdrsize); + empty->inuse = 1; + return empty; +} + +/* + * a process to act as a dns server for outside reqeusts + */ +void +dnudpserver(char *mntpt) +{ + int fd, len, op; + Request req; + DNSmsg reqmsg, repmsg; + uchar buf[OUdphdrsize + Maxudp + 1024]; + char *err; + Inprogress *p; + char tname[32]; + OUdphdr *uh; + + /* fork sharing text, data, and bss with parent */ + switch(rfork(RFPROC|RFNOTEG|RFMEM|RFNOWAIT)){ + case -1: + break; + case 0: + break; + default: + return; + } + + fd = -1; + notify(ding); +restart: + if(fd >= 0) + close(fd); + while((fd = udpannounce(mntpt)) < 0) + sleep(5000); + if(setjmp(req.mret)) + putactivity(); + req.isslave = 0; + + /* loop on requests */ + for(;; putactivity()){ + memset(&repmsg, 0, sizeof(repmsg)); + memset(&reqmsg, 0, sizeof(reqmsg)); + alarm(60*1000); + len = udpread(fd, (OUdphdr*)buf, buf+OUdphdrsize, sizeof(buf)-OUdphdrsize); + alarm(0); + if(len <= 0) + goto restart; + uh = (OUdphdr*)buf; + getactivity(&req); + req.aborttime = now + 30; /* don't spend more than 30 seconds */ + err = convM2DNS(&buf[OUdphdrsize], len, &reqmsg); + if(err){ + syslog(0, logfile, "server: input error: %s from %I", err, buf); + continue; + } + if(reqmsg.qdcount < 1){ + syslog(0, logfile, "server: no questions from %I", buf); + goto freereq; + } + if(reqmsg.flags & Fresp){ + syslog(0, logfile, "server: reply not request from %I", buf); + goto freereq; + } + op = reqmsg.flags & Omask; + if(op != Oquery && op != Onotify){ + syslog(0, logfile, "server: op %d from %I", reqmsg.flags & Omask, buf); + goto freereq; + } + + if(debug || (trace && subsume(trace, reqmsg.qd->owner->name))){ + syslog(0, logfile, "%d: serve (%I/%d) %d %s %s", + req.id, buf, ((uh->rport[0])<<8)+uh->rport[1], + reqmsg.id, + reqmsg.qd->owner->name, + rrname(reqmsg.qd->type, tname, sizeof tname)); + } + + p = clientrxmit(&reqmsg, buf); + if(p == 0){ + if(debug) + syslog(0, logfile, "%d: duplicate", req.id); + goto freereq; + } + + /* loop through each question */ + while(reqmsg.qd){ + memset(&repmsg, 0, sizeof(repmsg)); + switch(op){ + case Oquery: + dnserver(&reqmsg, &repmsg, &req); + break; + case Onotify: + dnnotify(&reqmsg, &repmsg, &req); + break; + } + reply(fd, buf, &repmsg, &req); + rrfreelist(repmsg.qd); + rrfreelist(repmsg.an); + rrfreelist(repmsg.ns); + rrfreelist(repmsg.ar); + } + + p->inuse = 0; + +freereq: + rrfreelist(reqmsg.qd); + rrfreelist(reqmsg.an); + rrfreelist(reqmsg.ns); + rrfreelist(reqmsg.ar); + + if(req.isslave){ + putactivity(); + _exits(0); + } + + } +} + +/* + * announce on udp port + */ +static int +udpannounce(char *mntpt) +{ + int fd; + char buf[40]; + + USED(mntpt); + + if((fd=announce("udp!*!nameserver", buf)) < 0) + warning("can't announce on dns udp port"); + return fd; +} + +static void +reply(int fd, uchar *buf, DNSmsg *rep, Request *reqp) +{ + int len; + char tname[32]; + RR *rp; + + if(debug || (trace && subsume(trace, rep->qd->owner->name))) + syslog(0, logfile, "%d: reply (%I/%d) %d %s %s an %R ns %R ar %R", + reqp->id, buf, ((buf[4])<<8)+buf[5], + rep->id, rep->qd->owner->name, + rrname(rep->qd->type, tname, sizeof tname), rep->an, rep->ns, rep->ar); + + len = convDNS2M(rep, &buf[OUdphdrsize], Maxudp); + if(len <= 0){ + syslog(0, logfile, "error converting reply: %s %d", rep->qd->owner->name, + rep->qd->type); + for(rp = rep->an; rp; rp = rp->next) + syslog(0, logfile, "an %R", rp); + for(rp = rep->ns; rp; rp = rp->next) + syslog(0, logfile, "ns %R", rp); + for(rp = rep->ar; rp; rp = rp->next) + syslog(0, logfile, "ar %R", rp); + return; + } + if(udpwrite(fd, (OUdphdr*)buf, buf+OUdphdrsize, len) != len) + syslog(0, logfile, "error sending reply: %r"); +} diff --git a/src/cmd/ndb/mkfile b/src/cmd/ndb/mkfile index 9bbcfd31..340599a0 100644 --- a/src/cmd/ndb/mkfile +++ b/src/cmd/ndb/mkfile @@ -1,6 +1,7 @@ <$PLAN9/src/mkhdr TARG=\ +# dns\ ndbmkdb\ ndbquery\ ndbmkhash\ @@ -11,3 +12,18 @@ LIB=$PLAN9/lib/libndb.a <$PLAN9/src/mkmany +DNSOFILES=\ + convDNS2M.$O\ + convM2DNS.$O\ + dblookup.$O\ + dnarea.$O\ + dn.$O\ + dnresolve.$O\ + dnserver.$O\ + +$DNSOFILES dns.$O dnstcp.$O dnsdebug.$O: dns.h + +$O.dns: $DNSOFILES dnnotify.$O dnudpserver.$O +$O.dnstcp: $DNSOFILES +$O.dnsdebug: $DNSOFILES + |