xref: /plan9/sys/src/cmd/ndb/dns.c (revision 3468a4915d661daa200976acc4f80f51aae144b2)
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 <pool.h>
9 #include "dns.h"
10 
11 enum
12 {
13 	Maxrequest=		1024,
14 	Maxreply=		8192,		/* was 512 */
15 	Maxrrr=			32,		/* was 16 */
16 	Maxfdata=		8192,
17 
18 	Defmaxage=		60*60,	/* default domain name max. age */
19 
20 	Qdir=			0,
21 	Qdns=			1,
22 };
23 
24 typedef struct Mfile	Mfile;
25 typedef struct Job	Job;
26 typedef struct Network	Network;
27 
28 int vers;		/* incremented each clone/attach */
29 
30 static volatile int stop;
31 
32 /* holds data to be returned via read of /net/dns, perhaps multiple reads */
33 struct Mfile
34 {
35 	Mfile		*next;		/* next free mfile */
36 	int		ref;
37 
38 	char		*user;
39 	Qid		qid;
40 	int		fid;
41 
42 	int		type;		/* reply type */
43 	char		reply[Maxreply];
44 	ushort		rr[Maxrrr];	/* offset of rr's */
45 	ushort		nrr;		/* number of rr's */
46 };
47 
48 /*
49  *  active local requests
50  */
51 struct Job
52 {
53 	Job	*next;
54 	int	flushed;
55 	Fcall	request;
56 	Fcall	reply;
57 };
58 Lock	joblock;
59 Job	*joblist;
60 
61 struct {
62 	Lock;
63 	Mfile	*inuse;		/* active mfile's */
64 } mfalloc;
65 
66 Cfg	cfg;
67 int	debug;
68 uchar	ipaddr[IPaddrlen];	/* my ip address */
69 int	maxage = Defmaxage;
70 int	mfd[2];
71 int	needrefresh;
72 ulong	now;
73 vlong	nowns;
74 int	sendnotifies;
75 int	testing;
76 char	*trace;
77 int	traceactivity;
78 char	*zonerefreshprogram;
79 
80 char	*logfile = "dns";	/* or "dns.test" */
81 char	*dbfile;
82 char	mntpt[Maxpath];
83 
84 int	fillreply(Mfile*, int);
85 void	freejob(Job*);
86 void	io(void);
87 void	mountinit(char*, char*);
88 Job*	newjob(void);
89 void	rattach(Job*, Mfile*);
90 void	rauth(Job*);
91 void	rclunk(Job*, Mfile*);
92 void	rcreate(Job*, Mfile*);
93 void	rflush(Job*);
94 void	ropen(Job*, Mfile*);
95 void	rread(Job*, Mfile*);
96 void	rremove(Job*, Mfile*);
97 void	rstat(Job*, Mfile*);
98 void	rversion(Job*);
99 char*	rwalk(Job*, Mfile*);
100 void	rwrite(Job*, Mfile*, Request*);
101 void	rwstat(Job*, Mfile*);
102 void	sendmsg(Job*, char*);
103 void	setext(char*, int, char*);
104 
105 void
106 usage(void)
107 {
108 	fprint(2, "usage: %s [-FnorRst] [-a maxage] [-f ndb-file] [-N target] "
109 		"[-T forwip] [-x netmtpt] [-z refreshprog]\n", argv0);
110 	exits("usage");
111 }
112 
113 int addforwtarg(char *);
114 
115 void
116 main(int argc, char *argv[])
117 {
118 	int kid, pid;
119 	char servefile[Maxpath], ext[Maxpath];
120 	Dir *dir;
121 
122 	setnetmtpt(mntpt, sizeof mntpt, nil);
123 	ext[0] = 0;
124 	ARGBEGIN{
125 	case 'a':
126 		maxage = atol(EARGF(usage()));
127 		if (maxage <= 0)
128 			maxage = Defmaxage;
129 		break;
130 	case 'd':
131 		debug = 1;
132 		traceactivity = 1;
133 		break;
134 	case 'f':
135 		dbfile = EARGF(usage());
136 		break;
137 	case 'F':
138 		cfg.justforw = cfg.resolver = 1;
139 		break;
140 	case 'n':
141 		sendnotifies = 1;
142 		break;
143 	case 'N':
144 		target = atol(EARGF(usage()));
145 		if (target < 1000)
146 			target = 1000;
147 		break;
148 	case 'o':
149 		cfg.straddle = 1;	/* straddle inside & outside networks */
150 		break;
151 	case 'r':
152 		cfg.resolver = 1;
153 		break;
154 	case 'R':
155 		norecursion = 1;
156 		break;
157 	case 's':
158 		cfg.serve = 1;		/* serve network */
159 		cfg.cachedb = 1;
160 		break;
161 	case 't':
162 		testing = 1;
163 		break;
164 	case 'T':
165 		addforwtarg(EARGF(usage()));
166 		break;
167 	case 'x':
168 		setnetmtpt(mntpt, sizeof mntpt, EARGF(usage()));
169 		setext(ext, sizeof ext, mntpt);
170 		break;
171 	case 'z':
172 		zonerefreshprogram = EARGF(usage());
173 		break;
174 	default:
175 		usage();
176 		break;
177 	}ARGEND
178 	if(argc != 0)
179 		usage();
180 
181 	if(testing)
182 		mainmem->flags |= POOL_NOREUSE | POOL_ANTAGONISM;
183 	mainmem->flags |= POOL_ANTAGONISM;
184 	rfork(RFREND|RFNOTEG);
185 
186 	cfg.inside = (*mntpt == '\0' || strcmp(mntpt, "/net") == 0);
187 
188 	/* start syslog before we fork */
189 	fmtinstall('F', fcallfmt);
190 	dninit();
191 	/* this really shouldn't be fatal */
192 	if(myipaddr(ipaddr, mntpt) < 0)
193 		sysfatal("can't read my ip address");
194 	dnslog("starting %s%sdns %s%s%son %I's %s",
195 		(cfg.straddle? "straddling ": ""),
196 		(cfg.cachedb? "caching ": ""),
197 		(cfg.serve?   "udp server ": ""),
198 		(cfg.justforw? "forwarding-only ": ""),
199 		(cfg.resolver? "resolver ": ""), ipaddr, mntpt);
200 
201 	opendatabase();
202 	now = time(nil);		/* open time files before we fork */
203 	nowns = nsec();
204 
205 	snprint(servefile, sizeof servefile, "#s/dns%s", ext);
206 	dir = dirstat(servefile);
207 	if (dir)
208 		sysfatal("%s exists; another dns instance is running",
209 			servefile);
210 	free(dir);
211 //	unmount(servefile, mntpt);
212 //	remove(servefile);
213 
214 	mountinit(servefile, mntpt);	/* forks, parent exits */
215 
216 	srand(now*getpid());
217 	db2cache(1);
218 //	dnageallnever();
219 
220 	if (cfg.straddle && !seerootns())
221 		dnslog("straddle server misconfigured; can't see root name servers");
222 	/*
223 	 * fork without sharing heap.
224 	 * parent waits around for child to die, then forks & restarts.
225 	 * child may spawn udp server, notify procs, etc.; when it gets too
226 	 * big, it kills itself and any children.
227 	 * /srv/dns and /net/dns remain open and valid.
228 	 */
229 	for (;;) {
230 		kid = rfork(RFPROC|RFFDG|RFNOTEG);
231 		switch (kid) {
232 		case -1:
233 			sysfatal("fork failed: %r");
234 		case 0:
235 			if(cfg.serve)
236 				dnudpserver(mntpt);
237 			if(sendnotifies)
238 				notifyproc();
239 			io();
240 			_exits("restart");
241 		default:
242 			while ((pid = waitpid()) != kid && pid != -1)
243 				continue;
244 			break;
245 		}
246 		dnslog("dns restarting");
247 	}
248 }
249 
250 /*
251  *  if a mount point is specified, set the cs extension to be the mount point
252  *  with '_'s replacing '/'s
253  */
254 void
255 setext(char *ext, int n, char *p)
256 {
257 	int i, c;
258 
259 	n--;
260 	for(i = 0; i < n; i++){
261 		c = p[i];
262 		if(c == 0)
263 			break;
264 		if(c == '/')
265 			c = '_';
266 		ext[i] = c;
267 	}
268 	ext[i] = 0;
269 }
270 
271 void
272 mountinit(char *service, char *mntpt)
273 {
274 	int f;
275 	int p[2];
276 	char buf[32];
277 
278 	if(pipe(p) < 0)
279 		abort(); /* "pipe failed" */;
280 	/* copy namespace to avoid a deadlock */
281 	switch(rfork(RFFDG|RFPROC|RFNAMEG)){
282 	case 0:			/* child: hang around and (re)start main proc */
283 		close(p[1]);
284 		procsetname("%s restarter", mntpt);
285 		break;
286 	case -1:
287 		abort(); /* "fork failed\n" */;
288 	default:		/* parent: make /srv/dns, mount it, exit */
289 		close(p[0]);
290 
291 		/*
292 		 *  make a /srv/dns
293 		 */
294 		f = create(service, 1, 0666);
295 		if(f < 0)
296 			abort(); /* service */;
297 		snprint(buf, sizeof buf, "%d", p[1]);
298 		if(write(f, buf, strlen(buf)) != strlen(buf))
299 			abort(); /* "write %s", service */;
300 		close(f);
301 
302 		/*
303 		 *  put ourselves into the file system
304 		 */
305 		if(mount(p[1], -1, mntpt, MAFTER, "") < 0)
306 			fprint(2, "dns mount failed: %r\n");
307 		_exits(0);
308 	}
309 	mfd[0] = mfd[1] = p[0];
310 }
311 
312 Mfile*
313 newfid(int fid, int needunused)
314 {
315 	Mfile *mf;
316 
317 	lock(&mfalloc);
318 	for(mf = mfalloc.inuse; mf != nil; mf = mf->next)
319 		if(mf->fid == fid){
320 			unlock(&mfalloc);
321 			if(needunused)
322 				return nil;
323 			return mf;
324 		}
325 	mf = emalloc(sizeof(*mf));
326 	mf->fid = fid;
327 	mf->user = estrdup("dummy");
328 	mf->next = mfalloc.inuse;
329 	mfalloc.inuse = mf;
330 	unlock(&mfalloc);
331 	return mf;
332 }
333 
334 void
335 freefid(Mfile *mf)
336 {
337 	Mfile **l;
338 
339 	lock(&mfalloc);
340 	for(l = &mfalloc.inuse; *l != nil; l = &(*l)->next)
341 		if(*l == mf){
342 			*l = mf->next;
343 			if(mf->user)
344 				free(mf->user);
345 			memset(mf, 0, sizeof *mf);	/* cause trouble */
346 			free(mf);
347 			unlock(&mfalloc);
348 			return;
349 		}
350 	unlock(&mfalloc);
351 	sysfatal("freeing unused fid");
352 }
353 
354 Mfile*
355 copyfid(Mfile *mf, int fid)
356 {
357 	Mfile *nmf;
358 
359 	nmf = newfid(fid, 1);
360 	if(nmf == nil)
361 		return nil;
362 	nmf->fid = fid;
363 	free(nmf->user);			/* estrdup("dummy") */
364 	nmf->user = estrdup(mf->user);
365 	nmf->qid.type = mf->qid.type;
366 	nmf->qid.path = mf->qid.path;
367 	nmf->qid.vers = vers++;
368 	return nmf;
369 }
370 
371 Job*
372 newjob(void)
373 {
374 	Job *job;
375 
376 	job = emalloc(sizeof *job);
377 	lock(&joblock);
378 	job->next = joblist;
379 	joblist = job;
380 	job->request.tag = -1;
381 	unlock(&joblock);
382 	return job;
383 }
384 
385 void
386 freejob(Job *job)
387 {
388 	Job **l;
389 
390 	lock(&joblock);
391 	for(l = &joblist; *l; l = &(*l)->next)
392 		if(*l == job){
393 			*l = job->next;
394 			memset(job, 0, sizeof *job);	/* cause trouble */
395 			free(job);
396 			break;
397 		}
398 	unlock(&joblock);
399 }
400 
401 void
402 flushjob(int tag)
403 {
404 	Job *job;
405 
406 	lock(&joblock);
407 	for(job = joblist; job; job = job->next)
408 		if(job->request.tag == tag && job->request.type != Tflush){
409 			job->flushed = 1;
410 			break;
411 		}
412 	unlock(&joblock);
413 }
414 
415 void
416 io(void)
417 {
418 	volatile long n;
419 	volatile uchar mdata[IOHDRSZ + Maxfdata];
420 	Job *volatile job;
421 	Mfile *volatile mf;
422 	volatile Request req;
423 
424 	memset(&req, 0, sizeof req);
425 	/*
426 	 *  a slave process is sometimes forked to wait for replies from other
427 	 *  servers.  The master process returns immediately via a longjmp
428 	 *  through 'mret'.
429 	 */
430 	if(setjmp(req.mret))
431 		putactivity(0);
432 	req.isslave = 0;
433 	stop = 0;
434 	while(!stop){
435 		procsetname("%d %s/dns Twrites of %d 9p rpcs read; %d alarms",
436 			stats.qrecvd9p, mntpt, stats.qrecvd9prpc, stats.alarms);
437 		n = read9pmsg(mfd[0], mdata, sizeof mdata);
438 		if(n<=0){
439 			dnslog("error reading 9P from %s: %r", mntpt);
440 			sleep(2000);		/* don't thrash */
441 			return;
442 		}
443 
444 		stats.qrecvd9prpc++;
445 		job = newjob();
446 		if(convM2S(mdata, n, &job->request) != n){
447 			freejob(job);
448 			continue;
449 		}
450 		mf = newfid(job->request.fid, 0);
451 		if(debug)
452 			dnslog("%F", &job->request);
453 
454 		getactivity(&req, 0);
455 		req.aborttime = time(nil) + Maxreqtm;
456 		req.from = "9p";
457 
458 		switch(job->request.type){
459 		default:
460 			warning("unknown request type %d", job->request.type);
461 			break;
462 		case Tversion:
463 			rversion(job);
464 			break;
465 		case Tauth:
466 			rauth(job);
467 			break;
468 		case Tflush:
469 			rflush(job);
470 			break;
471 		case Tattach:
472 			rattach(job, mf);
473 			break;
474 		case Twalk:
475 			rwalk(job, mf);
476 			break;
477 		case Topen:
478 			ropen(job, mf);
479 			break;
480 		case Tcreate:
481 			rcreate(job, mf);
482 			break;
483 		case Tread:
484 			rread(job, mf);
485 			break;
486 		case Twrite:
487 			/* &req is handed to dnresolve() */
488 			rwrite(job, mf, &req);
489 			break;
490 		case Tclunk:
491 			rclunk(job, mf);
492 			break;
493 		case Tremove:
494 			rremove(job, mf);
495 			break;
496 		case Tstat:
497 			rstat(job, mf);
498 			break;
499 		case Twstat:
500 			rwstat(job, mf);
501 			break;
502 		}
503 
504 		freejob(job);
505 
506 		/*
507 		 *  slave processes die after replying
508 		 */
509 		if(req.isslave){
510 			putactivity(0);
511 			_exits(0);
512 		}
513 
514 		putactivity(0);
515 	}
516 	/* kill any udp server, notifier, etc. processes */
517 	postnote(PNGROUP, getpid(), "die");
518 	sleep(1000);
519 }
520 
521 void
522 rversion(Job *job)
523 {
524 	if(job->request.msize > IOHDRSZ + Maxfdata)
525 		job->reply.msize = IOHDRSZ + Maxfdata;
526 	else
527 		job->reply.msize = job->request.msize;
528 	if(strncmp(job->request.version, "9P2000", 6) != 0)
529 		sendmsg(job, "unknown 9P version");
530 	else{
531 		job->reply.version = "9P2000";
532 		sendmsg(job, 0);
533 	}
534 }
535 
536 void
537 rauth(Job *job)
538 {
539 	sendmsg(job, "dns: authentication not required");
540 }
541 
542 /*
543  *  don't flush till all the slaves are done
544  */
545 void
546 rflush(Job *job)
547 {
548 	flushjob(job->request.oldtag);
549 	sendmsg(job, 0);
550 }
551 
552 void
553 rattach(Job *job, Mfile *mf)
554 {
555 	if(mf->user != nil)
556 		free(mf->user);
557 	mf->user = estrdup(job->request.uname);
558 	mf->qid.vers = vers++;
559 	mf->qid.type = QTDIR;
560 	mf->qid.path = 0LL;
561 	job->reply.qid = mf->qid;
562 	sendmsg(job, 0);
563 }
564 
565 char*
566 rwalk(Job *job, Mfile *mf)
567 {
568 	int i, nelems;
569 	char *err;
570 	char **elems;
571 	Mfile *nmf;
572 	Qid qid;
573 
574 	err = 0;
575 	nmf = nil;
576 	elems  = job->request.wname;
577 	nelems = job->request.nwname;
578 	job->reply.nwqid = 0;
579 
580 	if(job->request.newfid != job->request.fid){
581 		/* clone fid */
582 		nmf = copyfid(mf, job->request.newfid);
583 		if(nmf == nil){
584 			err = "clone bad newfid";
585 			goto send;
586 		}
587 		mf = nmf;
588 	}
589 	/* else nmf will be nil */
590 
591 	qid = mf->qid;
592 	if(nelems > 0)
593 		/* walk fid */
594 		for(i=0; i<nelems && i<MAXWELEM; i++){
595 			if((qid.type & QTDIR) == 0){
596 				err = "not a directory";
597 				break;
598 			}
599 			if (strcmp(elems[i], "..") == 0 ||
600 			    strcmp(elems[i], ".") == 0){
601 				qid.type = QTDIR;
602 				qid.path = Qdir;
603 Found:
604 				job->reply.wqid[i] = qid;
605 				job->reply.nwqid++;
606 				continue;
607 			}
608 			if(strcmp(elems[i], "dns") == 0){
609 				qid.type = QTFILE;
610 				qid.path = Qdns;
611 				goto Found;
612 			}
613 			err = "file does not exist";
614 			break;
615 		}
616 
617 send:
618 	if(nmf != nil && (err!=nil || job->reply.nwqid<nelems))
619 		freefid(nmf);
620 	if(err == nil)
621 		mf->qid = qid;
622 	sendmsg(job, err);
623 	return err;
624 }
625 
626 void
627 ropen(Job *job, Mfile *mf)
628 {
629 	int mode;
630 	char *err;
631 
632 	err = 0;
633 	mode = job->request.mode;
634 	if(mf->qid.type & QTDIR)
635 		if(mode)
636 			err = "permission denied";
637 	job->reply.qid = mf->qid;
638 	job->reply.iounit = 0;
639 	sendmsg(job, err);
640 }
641 
642 void
643 rcreate(Job *job, Mfile *mf)
644 {
645 	USED(mf);
646 	sendmsg(job, "creation permission denied");
647 }
648 
649 void
650 rread(Job *job, Mfile *mf)
651 {
652 	int i, n;
653 	long clock;
654 	ulong cnt;
655 	vlong off;
656 	char *err;
657 	uchar buf[Maxfdata];
658 	Dir dir;
659 
660 	n = 0;
661 	err = nil;
662 	off = job->request.offset;
663 	cnt = job->request.count;
664 	*buf = '\0';
665 	job->reply.data = (char*)buf;
666 	if(mf->qid.type & QTDIR){
667 		clock = time(nil);
668 		if(off == 0){
669 			memset(&dir, 0, sizeof dir);
670 			dir.name = "dns";
671 			dir.qid.type = QTFILE;
672 			dir.qid.vers = vers;
673 			dir.qid.path = Qdns;
674 			dir.mode = 0666;
675 			dir.length = 0;
676 			dir.uid = dir.gid = dir.muid = mf->user;
677 			dir.atime = dir.mtime = clock;		/* wrong */
678 			n = convD2M(&dir, buf, sizeof buf);
679 		}
680 	} else if (off < 0)
681 		err = "negative read offset";
682 	else {
683 		/* first offset will always be zero */
684 		for(i = 1; i <= mf->nrr; i++)
685 			if(mf->rr[i] > off)
686 				break;
687 		if(i <= mf->nrr) {
688 			if(off + cnt > mf->rr[i])
689 				n = mf->rr[i] - off;
690 			else
691 				n = cnt;
692 			assert(n >= 0);
693 			job->reply.data = mf->reply + off;
694 		}
695 	}
696 	job->reply.count = n;
697 	sendmsg(job, err);
698 }
699 
700 void
701 rwrite(Job *job, Mfile *mf, Request *req)
702 {
703 	int rooted, status, wantsav;
704 	long n;
705 	ulong cnt;
706 	char *err, *p, *atype;
707 	RR *rp, *tp, *neg;
708 
709 	err = nil;
710 	cnt = job->request.count;
711 	if(mf->qid.type & QTDIR){
712 		err = "can't write directory";
713 		goto send;
714 	}
715 	if (job->request.offset != 0) {
716 		err = "writing at non-zero offset";
717 		goto send;
718 	}
719 	if(cnt >= Maxrequest){
720 		err = "request too long";
721 		goto send;
722 	}
723 	job->request.data[cnt] = 0;
724 	if(cnt > 0 && job->request.data[cnt-1] == '\n')
725 		job->request.data[cnt-1] = 0;
726 
727 	/*
728 	 *  special commands
729 	 */
730 //	dnslog("rwrite got: %s", job->request.data);
731 	if(strcmp(job->request.data, "debug")==0){
732 		debug ^= 1;
733 		goto send;
734 	} else if(strcmp(job->request.data, "dump")==0){
735 		dndump("/lib/ndb/dnsdump");
736 		goto send;
737 	} else if(strcmp(job->request.data, "poolcheck")==0){
738 		poolcheck(mainmem);
739 		goto send;
740 	} else if(strcmp(job->request.data, "refresh")==0){
741 		needrefresh = 1;
742 		goto send;
743 	} else if(strcmp(job->request.data, "restart")==0){
744 		stop = 1;
745 		goto send;
746 	} else if(strcmp(job->request.data, "stats")==0){
747 		dnstats("/lib/ndb/dnsstats");
748 		goto send;
749 	} else if(strncmp(job->request.data, "target ", 7)==0){
750 		target = atol(job->request.data + 7);
751 		dnslog("target set to %ld", target);
752 		goto send;
753 	} else if(strcmp(job->request.data, "age")==0){
754 		dnslog("dump, age & dump forced");
755 		dndump("/lib/ndb/dnsdump1");
756 		dnforceage();
757 		dndump("/lib/ndb/dnsdump2");
758 		goto send;
759 	}
760 
761 	/*
762 	 *  kill previous reply
763 	 */
764 	mf->nrr = 0;
765 	mf->rr[0] = 0;
766 
767 	/*
768 	 *  break up request (into a name and a type)
769 	 */
770 	atype = strchr(job->request.data, ' ');
771 	if(atype == 0){
772 		err = "illegal request";
773 		goto send;
774 	} else
775 		*atype++ = 0;
776 
777 	/*
778 	 *  tracing request
779 	 */
780 	if(strcmp(atype, "trace") == 0){
781 		if(trace)
782 			free(trace);
783 		if(*job->request.data)
784 			trace = estrdup(job->request.data);
785 		else
786 			trace = 0;
787 		goto send;
788 	}
789 
790 	/* normal request: domain [type] */
791 	stats.qrecvd9p++;
792 	mf->type = rrtype(atype);
793 	if(mf->type < 0){
794 		err = "unknown type";
795 		goto send;
796 	}
797 
798 	p = atype - 2;
799 	if(p >= job->request.data && *p == '.'){
800 		rooted = 1;
801 		*p = 0;
802 	} else
803 		rooted = 0;
804 
805 	p = job->request.data;
806 	if(*p == '!'){
807 		wantsav = 1;
808 		p++;
809 	} else
810 		wantsav = 0;
811 
812 	dncheck(0, 1);
813 	status = 0;
814 	rp = dnresolve(p, Cin, mf->type, req, 0, 0, Recurse, rooted, &status);
815 
816 	dncheck(0, 1);
817 	lock(&dnlock);
818 	neg = rrremneg(&rp);
819 	if(neg){
820 		status = neg->negrcode;
821 		rrfreelist(neg);
822 	}
823 	unlock(&dnlock);
824 
825 	if(rp == nil)
826 		switch(status){
827 		case Rname:
828 			err = "name does not exist";
829 			break;
830 		case Rserver:
831 			err = "dns failure";
832 			break;
833 		default:
834 			err = "resource does not exist";
835 			break;
836 		}
837 	else {
838 		lock(&joblock);
839 		if(!job->flushed){
840 			/* format data to be read later */
841 			n = 0;
842 			mf->nrr = 0;
843 			for(tp = rp; mf->nrr < Maxrrr-1 && n < Maxreply && tp &&
844 			    tsame(mf->type, tp->type); tp = tp->next){
845 				mf->rr[mf->nrr++] = n;
846 				if(wantsav)
847 					n += snprint(mf->reply+n, Maxreply-n,
848 						"%Q", tp);
849 				else
850 					n += snprint(mf->reply+n, Maxreply-n,
851 						"%R", tp);
852 			}
853 			mf->rr[mf->nrr] = n;
854 		}
855 		unlock(&joblock);
856 		rrfreelist(rp);
857 	}
858 send:
859 	dncheck(0, 1);
860 	job->reply.count = cnt;
861 	sendmsg(job, err);
862 }
863 
864 void
865 rclunk(Job *job, Mfile *mf)
866 {
867 	freefid(mf);
868 	sendmsg(job, 0);
869 }
870 
871 void
872 rremove(Job *job, Mfile *mf)
873 {
874 	USED(mf);
875 	sendmsg(job, "remove permission denied");
876 }
877 
878 void
879 rstat(Job *job, Mfile *mf)
880 {
881 	Dir dir;
882 	uchar buf[IOHDRSZ+Maxfdata];
883 
884 	memset(&dir, 0, sizeof dir);
885 	if(mf->qid.type & QTDIR){
886 		dir.name = ".";
887 		dir.mode = DMDIR|0555;
888 	} else {
889 		dir.name = "dns";
890 		dir.mode = 0666;
891 	}
892 	dir.qid = mf->qid;
893 	dir.length = 0;
894 	dir.uid = dir.gid = dir.muid = mf->user;
895 	dir.atime = dir.mtime = time(nil);
896 	job->reply.nstat = convD2M(&dir, buf, sizeof buf);
897 	job->reply.stat = buf;
898 	sendmsg(job, 0);
899 }
900 
901 void
902 rwstat(Job *job, Mfile *mf)
903 {
904 	USED(mf);
905 	sendmsg(job, "wstat permission denied");
906 }
907 
908 void
909 sendmsg(Job *job, char *err)
910 {
911 	int n;
912 	uchar mdata[IOHDRSZ + Maxfdata];
913 	char ename[ERRMAX];
914 
915 	if(err){
916 		job->reply.type = Rerror;
917 		snprint(ename, sizeof ename, "dns: %s", err);
918 		job->reply.ename = ename;
919 	}else
920 		job->reply.type = job->request.type+1;
921 	job->reply.tag = job->request.tag;
922 	n = convS2M(&job->reply, mdata, sizeof mdata);
923 	if(n == 0){
924 		warning("sendmsg convS2M of %F returns 0", &job->reply);
925 		abort();
926 	}
927 	lock(&joblock);
928 	if(job->flushed == 0)
929 		if(write(mfd[1], mdata, n)!=n)
930 			sysfatal("mount write");
931 	unlock(&joblock);
932 	if(debug)
933 		dnslog("%F %d", &job->reply, n);
934 }
935 
936 /*
937  *  the following varies between dnsdebug and dns
938  */
939 void
940 logreply(int id, uchar *addr, DNSmsg *mp)
941 {
942 	RR *rp;
943 
944 	dnslog("%d: rcvd %I flags:%s%s%s%s%s", id, addr,
945 		mp->flags & Fauth? " auth": "",
946 		mp->flags & Ftrunc? " trunc": "",
947 		mp->flags & Frecurse? " rd": "",
948 		mp->flags & Fcanrec? " ra": "",
949 		(mp->flags & (Fauth|Rmask)) == (Fauth|Rname)? " nx": "");
950 	for(rp = mp->qd; rp != nil; rp = rp->next)
951 		dnslog("%d: rcvd %I qd %s", id, addr, rp->owner->name);
952 	for(rp = mp->an; rp != nil; rp = rp->next)
953 		dnslog("%d: rcvd %I an %R", id, addr, rp);
954 	for(rp = mp->ns; rp != nil; rp = rp->next)
955 		dnslog("%d: rcvd %I ns %R", id, addr, rp);
956 	for(rp = mp->ar; rp != nil; rp = rp->next)
957 		dnslog("%d: rcvd %I ar %R", id, addr, rp);
958 }
959 
960 void
961 logsend(int id, int subid, uchar *addr, char *sname, char *rname, int type)
962 {
963 	char buf[12];
964 
965 	dnslog("[%d] %d.%d: sending to %I/%s %s %s",
966 		getpid(), id, subid, addr, sname, rname,
967 		rrname(type, buf, sizeof buf));
968 }
969 
970 RR*
971 getdnsservers(int class)
972 {
973 	return dnsservers(class);
974 }
975