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