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