391 lines · 7.8 KB · C
RawDownload
/*  * This file is part of the UCB release of Plan 9. It is subject to the license * terms in the LICENSE file found in the top-level directory of this * distribution and at http://akaros.cs.berkeley.edu/files/Plan9License. No * part of the UCB release of Plan 9, including this file, may be copied, * modified, propagated, or distributed except according to the terms contained * in the LICENSE file. */ /* * dnstcp - serve dns via tcp */#include <u.h>#include <libc.h>#include <ip.h>#include "dns.h" Cfg cfg; char	*caller = "";char	*dbfile;int	debug;uchar	ipaddr[IPaddrlen];	/* my ip address */char	*logfile = "dns";int	maxage = 60*60;char	mntpt[Maxpath];int	needrefresh;ulong	now;vlong	nowns;int	testing;int	traceactivity;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*); voidusage(void){	fprint(2, "usage: %s [-rR] [-f ndb-file] [-x netmtpt] [conndir]\n", argv0);	exits("usage");} voidmain(int argc, char *argv[]){	volatile int len, rcode;	volatile char tname[32];	char *volatile err, *volatile ext = "";	volatile uchar buf[64*1024], callip[IPaddrlen];	volatile DNSmsg reqmsg, repmsg;	volatile Request req; 	alarm(2*60*1000);	cfg.cachedb = 1;	ARGBEGIN{	case 'd':		debug++;		break;	case 'f':		dbfile = EARGF(usage());		break;	case 'r':		cfg.resolver = 1;		break;	case 'R':		norecursion = 1;		break;	case 'x':		ext = EARGF(usage());		break;	default:		usage();		break;	}ARGEND 	if(debug < 2)		debug = 0; 	if(argc > 0)		getcaller(argv[0]); 	cfg.inside = 1;	dninit(); 	snprint(mntpt, sizeof mntpt, "/net%s", ext);	if(myipaddr(ipaddr, mntpt) < 0)		sysfatal("can't read my ip address");	dnslog("dnstcp call from %s to %I", caller, ipaddr);	memset(callip, 0, sizeof callip);	parseip(callip, caller); 	db2cache(1); 	memset(&req, 0, sizeof req);	setjmp(req.mret);	req.isslave = 0;	procsetname("main loop"); 	/* loop on requests */	for(;; putactivity(0)){		now = time(nil);		memset(&repmsg, 0, sizeof repmsg);		len = readmsg(0, buf, sizeof buf);		if(len <= 0)			break; 		getactivity(&req, 0);		req.aborttime = timems() + S2MS(15*Min);		rcode = 0;		memset(&reqmsg, 0, sizeof reqmsg);		err = convM2DNS(buf, len, &reqmsg, &rcode);		if(err){			dnslog("server: input error: %s from %s", err, caller);			free(err);			break;		}		if (rcode == 0)			if(reqmsg.qdcount < 1){				dnslog("server: no questions from %s", caller);				break;			} else if(reqmsg.flags & Fresp){				dnslog("server: reply not request from %s",					caller);				break;			} else if((reqmsg.flags & Omask) != Oquery){				dnslog("server: op %d from %s",					reqmsg.flags & Omask, caller);				break;			}		if(debug)			dnslog("[%d] %d: serve (%s) %d %s %s",				getpid(), req.id, caller,				reqmsg.id, reqmsg.qd->owner->name,				rrname(reqmsg.qd->type, tname, sizeof tname)); 		/* loop through each question */		while(reqmsg.qd)			if(reqmsg.qd->type == Taxfr)				dnzone(&reqmsg, &repmsg, &req);			else {				dnserver(&reqmsg, &repmsg, &req, callip, rcode);				reply(1, &repmsg, &req);				rrfreelist(repmsg.qd);				rrfreelist(repmsg.an);				rrfreelist(repmsg.ns);				rrfreelist(repmsg.ar);			}		rrfreelist(reqmsg.qd);		/* qd will be nil */		rrfreelist(reqmsg.an);		rrfreelist(reqmsg.ns);		rrfreelist(reqmsg.ar); 		if(req.isslave){			putactivity(0);			_exits(0);		}	}	refreshmain(mntpt);} static intreadmsg(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 voidreply(int fd, DNSmsg *rep, Request *req){	int len, rv;	char tname[32];	uchar buf[64*1024];	RR *rp; 	if(debug){		dnslog("%d: reply (%s) %s %s %ux",			req->id, caller,			rep->qd->owner->name,			rrname(rep->qd->type, tname, sizeof tname),			rep->flags);		for(rp = rep->an; rp; rp = rp->next)			dnslog("an %R", rp);		for(rp = rep->ns; rp; rp = rp->next)			dnslog("ns %R", rp);		for(rp = rep->ar; rp; rp = rp->next)			dnslog("ar %R", rp);	}  	len = convDNS2M(rep, buf+2, sizeof(buf) - 2);	buf[0] = len>>8;	buf[1] = len;	rv = write(fd, buf, len+2);	if(rv != len+2){		dnslog("[%d] sending reply: %d instead of %d", getpid(), rv,			len+2);		exits(0);	}} /* *  Hash table for domain names.  The hash is based only on the *  first element of the domain name. */extern DN	*ht[HTLEN]; static intnumelem(char *name){	int i; 	i = 1;	for(; *name; name++)		if(*name == '.')			i++;	return i;} intinzone(DN *dp, char *name, int namelen, int depth){	int n; 	if(dp->name == nil)		return 0;	if(numelem(dp->name) != depth)		return 0;	n = strlen(dp->name);	if(n < namelen)		return 0;	if(strcmp(name, dp->name + n - namelen) != 0)		return 0;	if(n > namelen && dp->name[n - namelen - 1] != '.')		return 0;	return 1;} static voiddnzone(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->qd = reqp->qd;	reqp->qd = reqp->qd->next;	repp->qd->next = 0;	repp->flags = Fauth | Fresp | Oquery;	if(!norecursion)		repp->flags |= Fcanrec;	dp = repp->qd->owner; 	/* send the soa */	repp->an = rrlookup(dp, Tsoa, NOneg);	reply(1, repp, req);	if(repp->an == 0)		goto out;	rrfreelist(repp->an);	repp->an = nil; 	nlen = strlen(dp->name); 	/* construct a breadth-first search of the name space (hard with a hash) */	repp->an = &r;	for(depth = numelem(dp->name); ; depth++){		found = 0;		for(h = 0; h < HTLEN; h++)			for(ndp = ht[h]; ndp; ndp = ndp->next)				if(inzone(ndp, dp->name, nlen, depth)){					for(rp = ndp->rr; rp; rp = rp->next){						/*						 * there shouldn't be negatives,						 * but just in case.						 * don't send any soa's,						 * ns's are enough.						 */						if (rp->negative ||						    rp->type == Tsoa)							continue;						r = *rp;						r.next = 0;						reply(1, repp, req);					}					found = 1;				}		if(!found)			break;	} 	/* resend the soa */	repp->an = rrlookup(dp, Tsoa, NOneg);	reply(1, repp, req);	rrfreelist(repp->an);	repp->an = nil;out:	rrfree(repp->qd);	repp->qd = nil;} static voidgetcaller(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 voidrefreshmain(char *net){	int fd;	char file[128]; 	snprint(file, sizeof(file), "%s/dns", net);	if(debug)		dnslog("refreshing %s", file);	fd = open(file, ORDWR);	if(fd < 0)		dnslog("can't refresh %s", file);	else {		fprint(fd, "refresh");		close(fd);	}} /* *  the following varies between dnsdebug and dns */voidlogreply(int id, uchar *addr, DNSmsg *mp){	RR *rp; 	dnslog("%d: rcvd %I flags:%s%s%s%s%s", id, addr,		mp->flags & Fauth? " auth": "",		mp->flags & Ftrunc? " trunc": "",		mp->flags & Frecurse? " rd": "",		mp->flags & Fcanrec? " ra": "",		(mp->flags & (Fauth|Rmask)) == (Fauth|Rname)? " nx": "");	for(rp = mp->qd; rp != nil; rp = rp->next)		dnslog("%d: rcvd %I qd %s", id, addr, rp->owner->name);	for(rp = mp->an; rp != nil; rp = rp->next)		dnslog("%d: rcvd %I an %R", id, addr, rp);	for(rp = mp->ns; rp != nil; rp = rp->next)		dnslog("%d: rcvd %I ns %R", id, addr, rp);	for(rp = mp->ar; rp != nil; rp = rp->next)		dnslog("%d: rcvd %I ar %R", id, addr, rp);} voidlogsend(int id, int subid, uchar *addr, char *sname, char *rname, int type){	char buf[12]; 	dnslog("%d.%d: sending to %I/%s %s %s",		id, subid, addr, sname, rname, rrname(type, buf, sizeof buf));} RR*getdnsservers(int class){	return dnsservers(class);}