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