blob: ca73184aab9594550df8ee8499bf3b8af4d6a211 [file] [log] [blame]
#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] =
{
0,
/* Ta */ 1,
/* Tns */ 1,
0,
0,
/* Tcname */ 1,
/* Tsoa */ 1,
0,
0,
0,
/* Tnull */ 1,
0,
/* Tptr */ 1,
0,
0,
/* Tmx */ 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;
/* 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);
rp = nil;
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((uchar)*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;
}
/* 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);
}
}