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