plan9port

fork of plan9port with libvec, libstr and libsdb
Log | Files | Refs | README | LICENSE

dns.c (15204B)


      1 #include <u.h>
      2 #include <libc.h>
      3 #include <auth.h>
      4 #include <fcall.h>
      5 #include <bio.h>
      6 #include <ctype.h>
      7 #include <ip.h>
      8 #include <ndb.h>
      9 #include <thread.h>
     10 #include "dns.h"
     11 
     12 enum
     13 {
     14 	Maxrequest=		1024,
     15 	Ncache=			8,
     16 	Maxpath=		128,
     17 	Maxreply=		512,
     18 	Maxrrr=			16,
     19 	Maxfdata=		8192,
     20 
     21 	Qdir=			0,
     22 	Qdns=			1
     23 };
     24 
     25 typedef struct Mfile	Mfile;
     26 typedef struct Job	Job;
     27 typedef struct Network	Network;
     28 
     29 int vers;		/* incremented each clone/attach */
     30 
     31 struct Mfile
     32 {
     33 	Mfile		*next;		/* next free mfile */
     34 	int		ref;
     35 
     36 	char		*user;
     37 	Qid		qid;
     38 	int		fid;
     39 
     40 	int		type;		/* reply type */
     41 	char		reply[Maxreply];
     42 	ushort		rr[Maxrrr];	/* offset of rr's */
     43 	ushort		nrr;		/* number of rr's */
     44 };
     45 
     46 /*
     47  * active local requests
     48  */
     49 struct Job
     50 {
     51 	Job	*next;
     52 	int	flushed;
     53 	Fcall	request;
     54 	Fcall	reply;
     55 };
     56 Lock	joblock;
     57 Job	*joblist;
     58 
     59 struct {
     60 	Lock	lk;
     61 	Mfile	*inuse;		/* active mfile's */
     62 } mfalloc;
     63 
     64 int	mfd[2];
     65 int	debug;
     66 int traceactivity;
     67 int	cachedb;
     68 ulong	now;
     69 int	testing;
     70 char	*trace;
     71 int	needrefresh;
     72 int	resolver;
     73 uchar	ipaddr[IPaddrlen];	/* my ip address */
     74 int	maxage;
     75 char	*zonerefreshprogram;
     76 int	sendnotifies;
     77 
     78 void	rversion(Job*);
     79 void	rauth(Job*);
     80 void	rflush(Job*);
     81 void	rattach(Job*, Mfile*);
     82 char*	rwalk(Job*, Mfile*);
     83 void	ropen(Job*, Mfile*);
     84 void	rcreate(Job*, Mfile*);
     85 void	rread(Job*, Mfile*);
     86 void	rwrite(Job*, Mfile*, Request*);
     87 void	rclunk(Job*, Mfile*);
     88 void	rremove(Job*, Mfile*);
     89 void	rstat(Job*, Mfile*);
     90 void	rwstat(Job*, Mfile*);
     91 void	sendmsg(Job*, char*);
     92 void	mountinit(char*);
     93 void	io(void);
     94 int	fillreply(Mfile*, int);
     95 Job*	newjob(void);
     96 void	freejob(Job*);
     97 void	setext(char*, int, char*);
     98 
     99 char *tcpaddr = "tcp!*!domain";
    100 char *udpaddr = "udp!*!domain";
    101 char	*logfile = "dns";
    102 char	*dbfile;
    103 char	mntpt[Maxpath];
    104 char	*LOG;
    105 
    106 void
    107 usage(void)
    108 {
    109 	fprint(2, "usage: dns [-dnrst] [-a maxage] [-f ndb-file] [-T tcpaddr] [-U udpaddr] [-x service] [-z zoneprog]\n");
    110 	threadexitsall("usage");
    111 }
    112 
    113 void
    114 checkaddress(void)
    115 {
    116 	char *u, *t;
    117 
    118 	u = strchr(udpaddr, '!');
    119 	t = strchr(tcpaddr, '!');
    120 	if(u && t && strcmp(u, t) != 0)
    121 		fprint(2, "warning: announce mismatch %s %s\n", udpaddr, tcpaddr);
    122 }
    123 
    124 int
    125 threadmaybackground(void)
    126 {
    127 	return 1;
    128 }
    129 
    130 void
    131 threadmain(int argc, char *argv[])
    132 {
    133 	int serveudp, servetcp;
    134 	char *service;
    135 
    136 	serveudp = 0;
    137 	servetcp = 0;
    138 	service = "dns";
    139 	ARGBEGIN{
    140 	case 'd':
    141 		debug = 1;
    142 		traceactivity = 1;
    143 		break;
    144 	case 'f':
    145 		dbfile = EARGF(usage());
    146 		break;
    147 	case 'x':
    148 		service = EARGF(usage());
    149 		break;
    150 	case 'r':
    151 		resolver = 1;
    152 		break;
    153 	case 's':
    154 		serveudp = 1;
    155 		cachedb = 1;
    156 		break;
    157 	case 't':
    158 		servetcp = 1;
    159 		cachedb = 1;
    160 		break;
    161 	case 'a':
    162 		maxage = atoi(EARGF(usage()));
    163 		break;
    164 	case 'z':
    165 		zonerefreshprogram = EARGF(usage());
    166 		break;
    167 	case 'n':
    168 		sendnotifies = 1;
    169 		break;
    170 	case 'U':
    171 		udpaddr = estrdup(netmkaddr(EARGF(usage()), "udp", "domain"));
    172 		break;
    173 	case 'T':
    174 		tcpaddr = estrdup(netmkaddr(EARGF(usage()), "tcp", "domain"));
    175 		break;
    176 	default:
    177 		usage();
    178 	}ARGEND
    179 
    180 	if(argc)
    181 		usage();
    182 	if(serveudp && servetcp)
    183 		checkaddress();
    184 
    185 	rfork(RFNOTEG);
    186 
    187 	/* start syslog before we fork */
    188 	fmtinstall('F', fcallfmt);
    189 	dninit();
    190 	if(myipaddr(ipaddr, mntpt) < 0)
    191 		sysfatal("can't read my ip address");
    192 
    193 	syslog(0, logfile, "starting dns on %I", ipaddr);
    194 
    195 	opendatabase();
    196 
    197 	mountinit(service);
    198 
    199 	now = time(0);
    200 	srand(now*getpid());
    201 	db2cache(1);
    202 
    203 	if(serveudp)
    204 		proccreate(dnudpserver, nil, STACK);
    205 	if(servetcp)
    206 		proccreate(dntcpserver, nil, STACK);
    207 	if(sendnotifies)
    208 		proccreate(notifyproc, nil, STACK);
    209 
    210 	io();
    211 }
    212 
    213 /*
    214  *  if a mount point is specified, set the cs extention to be the mount point
    215  *  with '_'s replacing '/'s
    216  */
    217 void
    218 setext(char *ext, int n, char *p)
    219 {
    220 	int i, c;
    221 
    222 	n--;
    223 	for(i = 0; i < n; i++){
    224 		c = p[i];
    225 		if(c == 0)
    226 			break;
    227 		if(c == '/')
    228 			c = '_';
    229 		ext[i] = c;
    230 	}
    231 	ext[i] = 0;
    232 }
    233 
    234 void
    235 mountinit(char *service)
    236 {
    237 	int p[2];
    238 
    239 	if(pipe(p) < 0)
    240 		abort(); /* "pipe failed" */;
    241 	if(post9pservice(p[1], service, nil) < 0)
    242 		fprint(2, "post9pservice dns: %r\n");
    243 	close(p[1]);
    244 	mfd[0] = mfd[1] = p[0];
    245 }
    246 
    247 Mfile*
    248 newfid(int fid, int needunused)
    249 {
    250 	Mfile *mf;
    251 
    252 	lock(&mfalloc.lk);
    253 	for(mf = mfalloc.inuse; mf != nil; mf = mf->next){
    254 		if(mf->fid == fid){
    255 			unlock(&mfalloc.lk);
    256 			if(needunused)
    257 				return nil;
    258 			return mf;
    259 		}
    260 	}
    261 	if(!needunused){
    262 		unlock(&mfalloc.lk);
    263 		return nil;
    264 	}
    265 	mf = emalloc(sizeof(*mf));
    266 	if(mf == nil)
    267 		sysfatal("out of memory");
    268 	mf->fid = fid;
    269 	mf->next = mfalloc.inuse;
    270 	mfalloc.inuse = mf;
    271 	unlock(&mfalloc.lk);
    272 	return mf;
    273 }
    274 
    275 void
    276 freefid(Mfile *mf)
    277 {
    278 	Mfile **l;
    279 
    280 	lock(&mfalloc.lk);
    281 	for(l = &mfalloc.inuse; *l != nil; l = &(*l)->next){
    282 		if(*l == mf){
    283 			*l = mf->next;
    284 			if(mf->user)
    285 				free(mf->user);
    286 			free(mf);
    287 			unlock(&mfalloc.lk);
    288 			return;
    289 		}
    290 	}
    291 	sysfatal("freeing unused fid");
    292 }
    293 
    294 Mfile*
    295 copyfid(Mfile *mf, int fid)
    296 {
    297 	Mfile *nmf;
    298 
    299 	nmf = newfid(fid, 1);
    300 	if(nmf == nil)
    301 		return nil;
    302 	nmf->fid = fid;
    303 	nmf->user = estrdup(mf->user);
    304 	nmf->qid.type = mf->qid.type;
    305 	nmf->qid.path = mf->qid.path;
    306 	nmf->qid.vers = vers++;
    307 	return nmf;
    308 }
    309 
    310 Job*
    311 newjob(void)
    312 {
    313 	Job *job;
    314 
    315 	job = emalloc(sizeof(*job));
    316 	lock(&joblock);
    317 	job->next = joblist;
    318 	joblist = job;
    319 	job->request.tag = -1;
    320 	unlock(&joblock);
    321 	return job;
    322 }
    323 
    324 void
    325 freejob(Job *job)
    326 {
    327 	Job **l;
    328 
    329 	lock(&joblock);
    330 	for(l = &joblist; *l; l = &(*l)->next){
    331 		if((*l) == job){
    332 			*l = job->next;
    333 			free(job);
    334 			break;
    335 		}
    336 	}
    337 	unlock(&joblock);
    338 }
    339 
    340 void
    341 flushjob(int tag)
    342 {
    343 	Job *job;
    344 
    345 	lock(&joblock);
    346 	for(job = joblist; job; job = job->next){
    347 		if(job->request.tag == tag && job->request.type != Tflush){
    348 			job->flushed = 1;
    349 			break;
    350 		}
    351 	}
    352 	unlock(&joblock);
    353 }
    354 
    355 void
    356 ioproc0(void *v)
    357 {
    358 	long n;
    359 	Mfile *mf;
    360 	uchar mdata[IOHDRSZ + Maxfdata];
    361 	Request req;
    362 	Job *job;
    363 
    364 	USED(v);
    365 
    366 	for(;;){
    367 		n = read9pmsg(mfd[0], mdata, sizeof mdata);
    368 		if(n <= 0){
    369 			syslog(0, logfile, "error reading mntpt: %r");
    370 			break;
    371 		}
    372 		job = newjob();
    373 		if(convM2S(mdata, n, &job->request) != n){
    374 			freejob(job);
    375 			continue;
    376 		}
    377 		if(debug)
    378 			syslog(0, logfile, "%F", &job->request);
    379 
    380 		getactivity(&req);
    381 		req.aborttime = now + 60;	/* don't spend more than 60 seconds */
    382 
    383 		mf = nil;
    384 		switch(job->request.type){
    385 		case Tversion:
    386 		case Tauth:
    387 		case Tflush:
    388 			break;
    389 		case Tattach:
    390 			mf = newfid(job->request.fid, 1);
    391 			if(mf == nil){
    392 				sendmsg(job, "fid in use");
    393 				goto skip;
    394 			}
    395 			break;
    396 		default:
    397 			mf = newfid(job->request.fid, 0);
    398 			if(mf == nil){
    399 				sendmsg(job, "unknown fid");
    400 				goto skip;
    401 			}
    402 			break;
    403 		}
    404 
    405 		switch(job->request.type){
    406 		default:
    407 			syslog(1, logfile, "unknown request type %d", job->request.type);
    408 			break;
    409 		case Tversion:
    410 			rversion(job);
    411 			break;
    412 		case Tauth:
    413 			rauth(job);
    414 			break;
    415 		case Tflush:
    416 			rflush(job);
    417 			break;
    418 		case Tattach:
    419 			rattach(job, mf);
    420 			break;
    421 		case Twalk:
    422 			rwalk(job, mf);
    423 			break;
    424 		case Topen:
    425 			ropen(job, mf);
    426 			break;
    427 		case Tcreate:
    428 			rcreate(job, mf);
    429 			break;
    430 		case Tread:
    431 			rread(job, mf);
    432 			break;
    433 		case Twrite:
    434 			rwrite(job, mf, &req);
    435 			break;
    436 		case Tclunk:
    437 			rclunk(job, mf);
    438 			break;
    439 		case Tremove:
    440 			rremove(job, mf);
    441 			break;
    442 		case Tstat:
    443 			rstat(job, mf);
    444 			break;
    445 		case Twstat:
    446 			rwstat(job, mf);
    447 			break;
    448 		}
    449 skip:
    450 		freejob(job);
    451 		putactivity();
    452 	}
    453 }
    454 
    455 void
    456 io(void)
    457 {
    458 	int i;
    459 
    460 	for(i=0; i<Maxactive; i++)
    461 		proccreate(ioproc0, 0, STACK);
    462 }
    463 
    464 void
    465 rversion(Job *job)
    466 {
    467 	if(job->request.msize > IOHDRSZ + Maxfdata)
    468 		job->reply.msize = IOHDRSZ + Maxfdata;
    469 	else
    470 		job->reply.msize = job->request.msize;
    471 	if(strncmp(job->request.version, "9P2000", 6) != 0)
    472 		sendmsg(job, "unknown 9P version");
    473 	else{
    474 		job->reply.version = "9P2000";
    475 		sendmsg(job, 0);
    476 	}
    477 }
    478 
    479 void
    480 rauth(Job *job)
    481 {
    482 	sendmsg(job, "dns: authentication not required");
    483 }
    484 
    485 /*
    486  *  don't flush till all the slaves are done
    487  */
    488 void
    489 rflush(Job *job)
    490 {
    491 	flushjob(job->request.oldtag);
    492 	sendmsg(job, 0);
    493 }
    494 
    495 void
    496 rattach(Job *job, Mfile *mf)
    497 {
    498 	if(mf->user != nil)
    499 		free(mf->user);
    500 	mf->user = estrdup(job->request.uname);
    501 	mf->qid.vers = vers++;
    502 	mf->qid.type = QTDIR;
    503 	mf->qid.path = 0LL;
    504 	job->reply.qid = mf->qid;
    505 	sendmsg(job, 0);
    506 }
    507 
    508 char*
    509 rwalk(Job *job, Mfile *mf)
    510 {
    511 	char *err;
    512 	char **elems;
    513 	int nelems;
    514 	int i;
    515 	Mfile *nmf;
    516 	Qid qid;
    517 
    518 	err = 0;
    519 	nmf = nil;
    520 	elems = job->request.wname;
    521 	nelems = job->request.nwname;
    522 	job->reply.nwqid = 0;
    523 
    524 	if(job->request.newfid != job->request.fid){
    525 		/* clone fid */
    526 		nmf = copyfid(mf, job->request.newfid);
    527 		if(nmf == nil){
    528 			err = "clone bad newfid";
    529 			goto send;
    530 		}
    531 		mf = nmf;
    532 	}
    533 	/* else nmf will be nil */
    534 
    535 	qid = mf->qid;
    536 	if(nelems > 0){
    537 		/* walk fid */
    538 		for(i=0; i<nelems && i<MAXWELEM; i++){
    539 			if((qid.type & QTDIR) == 0){
    540 				err = "not a directory";
    541 				break;
    542 			}
    543 			if(strcmp(elems[i], "..") == 0 || strcmp(elems[i], ".") == 0){
    544 				qid.type = QTDIR;
    545 				qid.path = Qdir;
    546     Found:
    547 				job->reply.wqid[i] = qid;
    548 				job->reply.nwqid++;
    549 				continue;
    550 			}
    551 			if(strcmp(elems[i], "dns") == 0){
    552 				qid.type = QTFILE;
    553 				qid.path = Qdns;
    554 				goto Found;
    555 			}
    556 			err = "file does not exist";
    557 			break;
    558 		}
    559 	}
    560 
    561     send:
    562 	if(nmf != nil && (err!=nil || job->reply.nwqid<nelems))
    563 		freefid(nmf);
    564 	if(err == nil)
    565 		mf->qid = qid;
    566 	sendmsg(job, err);
    567 	return err;
    568 }
    569 
    570 void
    571 ropen(Job *job, Mfile *mf)
    572 {
    573 	int mode;
    574 	char *err;
    575 
    576 	err = 0;
    577 	mode = job->request.mode;
    578 	if(mf->qid.type & QTDIR){
    579 		if(mode)
    580 			err = "permission denied";
    581 	}
    582 	job->reply.qid = mf->qid;
    583 	job->reply.iounit = 0;
    584 	sendmsg(job, err);
    585 }
    586 
    587 void
    588 rcreate(Job *job, Mfile *mf)
    589 {
    590 	USED(mf);
    591 	sendmsg(job, "creation permission denied");
    592 }
    593 
    594 void
    595 rread(Job *job, Mfile *mf)
    596 {
    597 	int i, n, cnt;
    598 	long off;
    599 	Dir dir;
    600 	uchar buf[Maxfdata];
    601 	char *err;
    602 	long clock;
    603 
    604 	n = 0;
    605 	err = 0;
    606 	off = job->request.offset;
    607 	cnt = job->request.count;
    608 	if(mf->qid.type & QTDIR){
    609 		clock = time(0);
    610 		if(off == 0){
    611 			dir.name = "dns";
    612 			dir.qid.type = QTFILE;
    613 			dir.qid.vers = vers;
    614 			dir.qid.path = Qdns;
    615 			dir.mode = 0666;
    616 			dir.length = 0;
    617 			dir.uid = mf->user;
    618 			dir.gid = mf->user;
    619 			dir.muid = mf->user;
    620 			dir.atime = clock;	/* wrong */
    621 			dir.mtime = clock;	/* wrong */
    622 			n = convD2M(&dir, buf, sizeof buf);
    623 		}
    624 		job->reply.data = (char*)buf;
    625 	} else {
    626 		for(i = 1; i <= mf->nrr; i++)
    627 			if(mf->rr[i] > off)
    628 				break;
    629 		if(i > mf->nrr)
    630 			goto send;
    631 		if(off + cnt > mf->rr[i])
    632 			n = mf->rr[i] - off;
    633 		else
    634 			n = cnt;
    635 		job->reply.data = mf->reply + off;
    636 	}
    637 send:
    638 	job->reply.count = n;
    639 	sendmsg(job, err);
    640 }
    641 
    642 void
    643 rwrite(Job *job, Mfile *mf, Request *req)
    644 {
    645 	int cnt, rooted, status;
    646 	long n;
    647 	char *err, *p, *atype;
    648 	RR *rp, *tp, *neg;
    649 	int wantsav;
    650 	static char *dumpfile;
    651 
    652 	err = 0;
    653 	cnt = job->request.count;
    654 	if(mf->qid.type & QTDIR){
    655 		err = "can't write directory";
    656 		goto send;
    657 	}
    658 	if(cnt >= Maxrequest){
    659 		err = "request too long";
    660 		goto send;
    661 	}
    662 	job->request.data[cnt] = 0;
    663 	if(cnt > 0 && job->request.data[cnt-1] == '\n')
    664 		job->request.data[cnt-1] = 0;
    665 
    666 	/*
    667 	 *  special commands
    668 	 */
    669 	p = job->request.data;
    670 	if(strcmp(p, "debug")==0){
    671 		debug ^= 1;
    672 		goto send;
    673 	} else if(strcmp(p, "dump")==0){
    674 		if(dumpfile == nil)
    675 			dumpfile = unsharp("#9/ndb/dnsdump");
    676 		dndump(dumpfile);
    677 		goto send;
    678 	} else if(strncmp(p, "dump ", 5) == 0){
    679 		if(*(p+5))
    680 			dndump(p+5);
    681 		else
    682 			err = "bad filename";
    683 		goto send;
    684 	} else if(strcmp(p, "refresh")==0){
    685 		needrefresh = 1;
    686 		goto send;
    687 	}
    688 
    689 	/*
    690 	 *  kill previous reply
    691 	 */
    692 	mf->nrr = 0;
    693 	mf->rr[0] = 0;
    694 
    695 	/*
    696 	 *  break up request (into a name and a type)
    697 	 */
    698 	atype = strchr(job->request.data, ' ');
    699 	if(atype == 0){
    700 		err = "illegal request";
    701 		goto send;
    702 	} else
    703 		*atype++ = 0;
    704 
    705 	/*
    706 	 *  tracing request
    707 	 */
    708 	if(strcmp(atype, "trace") == 0){
    709 		if(trace)
    710 			free(trace);
    711 		if(*job->request.data)
    712 			trace = estrdup(job->request.data);
    713 		else
    714 			trace = 0;
    715 		goto send;
    716 	}
    717 
    718 	mf->type = rrtype(atype);
    719 	if(mf->type < 0){
    720 		err = "unknown type";
    721 		goto send;
    722 	}
    723 
    724 	p = atype - 2;
    725 	if(p >= job->request.data && *p == '.'){
    726 		rooted = 1;
    727 		*p = 0;
    728 	} else
    729 		rooted = 0;
    730 
    731 	p = job->request.data;
    732 	if(*p == '!'){
    733 		wantsav = 1;
    734 		p++;
    735 	} else
    736 		wantsav = 0;
    737 	dncheck(0, 1);
    738 	rp = dnresolve(p, Cin, mf->type, req, 0, 0, Recurse, rooted, &status);
    739 	dncheck(0, 1);
    740 	neg = rrremneg(&rp);
    741 	if(neg){
    742 		status = neg->negrcode;
    743 		rrfreelist(neg);
    744 	}
    745 	if(rp == 0){
    746 		switch(status){
    747 		case Rname:
    748 			err = "name does not exist";
    749 			break;
    750 		case Rserver:
    751 			err = "dns failure";
    752 			break;
    753 		default:
    754 			err = "resource does not exist";
    755 			break;
    756 		}
    757 	} else {
    758 		lock(&joblock);
    759 		if(!job->flushed){
    760 			/* format data to be read later */
    761 			n = 0;
    762 			mf->nrr = 0;
    763 			for(tp = rp; mf->nrr < Maxrrr-1 && n < Maxreply && tp &&
    764 					tsame(mf->type, tp->type); tp = tp->next){
    765 				mf->rr[mf->nrr++] = n;
    766 				if(wantsav)
    767 					n += snprint(mf->reply+n, Maxreply-n, "%Q", tp);
    768 				else
    769 					n += snprint(mf->reply+n, Maxreply-n, "%R", tp);
    770 			}
    771 			mf->rr[mf->nrr] = n;
    772 		}
    773 		unlock(&joblock);
    774 		rrfreelist(rp);
    775 	}
    776 
    777     send:
    778 	dncheck(0, 1);
    779 	job->reply.count = cnt;
    780 	sendmsg(job, err);
    781 }
    782 
    783 void
    784 rclunk(Job *job, Mfile *mf)
    785 {
    786 	freefid(mf);
    787 	sendmsg(job, 0);
    788 }
    789 
    790 void
    791 rremove(Job *job, Mfile *mf)
    792 {
    793 	USED(mf);
    794 	sendmsg(job, "remove permission denied");
    795 }
    796 
    797 void
    798 rstat(Job *job, Mfile *mf)
    799 {
    800 	Dir dir;
    801 	uchar buf[IOHDRSZ+Maxfdata];
    802 
    803 	if(mf->qid.type & QTDIR){
    804 		dir.name = ".";
    805 		dir.mode = DMDIR|0555;
    806 	} else {
    807 		dir.name = "dns";
    808 		dir.mode = 0666;
    809 	}
    810 	dir.qid = mf->qid;
    811 	dir.length = 0;
    812 	dir.uid = mf->user;
    813 	dir.gid = mf->user;
    814 	dir.muid = mf->user;
    815 	dir.atime = dir.mtime = time(0);
    816 	job->reply.nstat = convD2M(&dir, buf, sizeof buf);
    817 	job->reply.stat = buf;
    818 	sendmsg(job, 0);
    819 }
    820 
    821 void
    822 rwstat(Job *job, Mfile *mf)
    823 {
    824 	USED(mf);
    825 	sendmsg(job, "wstat permission denied");
    826 }
    827 
    828 void
    829 sendmsg(Job *job, char *err)
    830 {
    831 	int n;
    832 	uchar mdata[IOHDRSZ + Maxfdata];
    833 	char ename[ERRMAX];
    834 
    835 	if(err){
    836 		job->reply.type = Rerror;
    837 		snprint(ename, sizeof(ename), "dns: %s", err);
    838 		job->reply.ename = ename;
    839 	}else{
    840 		job->reply.type = job->request.type+1;
    841 	}
    842 	job->reply.tag = job->request.tag;
    843 	n = convS2M(&job->reply, mdata, sizeof mdata);
    844 	if(n == 0){
    845 		syslog(1, logfile, "sendmsg convS2M of %F returns 0", &job->reply);
    846 		abort();
    847 	}
    848 	lock(&joblock);
    849 	if(job->flushed == 0)
    850 		if(write(mfd[1], mdata, n)!=n)
    851 			sysfatal("mount write");
    852 	unlock(&joblock);
    853 	if(debug)
    854 		syslog(0, logfile, "%F %d", &job->reply, n);
    855 }
    856 
    857 /*
    858  *  the following varies between dnsdebug and dns
    859  */
    860 void
    861 logreply(int id, uchar *addr, DNSmsg *mp)
    862 {
    863 	RR *rp;
    864 
    865 	syslog(0, LOG, "%d: rcvd %I flags:%s%s%s%s%s", id, addr,
    866 		mp->flags & Fauth ? " auth" : "",
    867 		mp->flags & Ftrunc ? " trunc" : "",
    868 		mp->flags & Frecurse ? " rd" : "",
    869 		mp->flags & Fcanrec ? " ra" : "",
    870 		mp->flags & (Fauth|Rname) == (Fauth|Rname) ?
    871 		" nx" : "");
    872 	for(rp = mp->qd; rp != nil; rp = rp->next)
    873 		syslog(0, LOG, "%d: rcvd %I qd %s", id, addr, rp->owner->name);
    874 	for(rp = mp->an; rp != nil; rp = rp->next)
    875 		syslog(0, LOG, "%d: rcvd %I an %R", id, addr, rp);
    876 	for(rp = mp->ns; rp != nil; rp = rp->next)
    877 		syslog(0, LOG, "%d: rcvd %I ns %R", id, addr, rp);
    878 	for(rp = mp->ar; rp != nil; rp = rp->next)
    879 		syslog(0, LOG, "%d: rcvd %I ar %R", id, addr, rp);
    880 }
    881 
    882 void
    883 logsend(int id, int subid, uchar *addr, char *sname, char *rname, int type)
    884 {
    885 	char buf[12];
    886 
    887 	syslog(0, LOG, "%d.%d: sending to %I/%s %s %s",
    888 		id, subid, addr, sname, rname, rrname(type, buf, sizeof buf));
    889 }
    890 
    891 RR*
    892 getdnsservers(int class)
    893 {
    894 	return dnsservers(class);
    895 }