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