xref: /plan9-contrib/sys/src/cmd/ndb/cs.c (revision 8deabd962e84f51c67a12f970084955d97d8a8f2)
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 <ndb.h>
8 #include <ip.h>
9 #include <String.h>
10 
11 enum
12 {
13 	Nreply=			20,
14 	Maxreply=		256,
15 	Maxrequest=		128,
16 	Maxpath=		128,
17 	Maxfdata=		8192,
18 	Maxhost=		64,		/* maximum host name size */
19 	Maxservice=		64,		/* maximum service name size */
20 
21 	Qdir=			0,
22 	Qcs=			1,
23 };
24 
25 typedef struct Mfile	Mfile;
26 typedef struct Mlist	Mlist;
27 typedef struct Network	Network;
28 typedef struct Flushreq	Flushreq;
29 typedef struct Job	Job;
30 
31 int vers;		/* incremented each clone/attach */
32 
33 struct Mfile
34 {
35 	int		busy;
36 
37 	char		*user;
38 	Qid		qid;
39 	int		fid;
40 
41 	/*
42 	 *  current request
43 	 */
44 	char		*net;
45 	char		*host;
46 	char		*serv;
47 	char		*rem;
48 
49 	/*
50 	 *  result of the last lookup
51 	 */
52 	Network		*nextnet;
53 	int		nreply;
54 	char		*reply[Nreply];
55 	int		replylen[Nreply];
56 };
57 
58 struct Mlist
59 {
60 	Mlist	*next;
61 	Mfile	mf;
62 };
63 
64 
65 /*
66  *  active requests
67  */
68 struct Job
69 {
70 	Job	*next;
71 	int	flushed;
72 	Fcall	request;
73 	Fcall	reply;
74 };
75 Lock	joblock;
76 Job	*joblist;
77 
78 Mlist	*mlist;
79 int	mfd[2];
80 int	debug;
81 int	paranoia;
82 jmp_buf	masterjmp;	/* return through here after a slave process has been created */
83 int	*isslave;	/* *isslave non-zero means this is a slave process */
84 char	*dbfile;
85 Ndb	*db, *netdb;
86 
87 void	rversion(Job*);
88 void	rflush(Job*);
89 void	rattach(Job*, Mfile*);
90 char*	rwalk(Job*, Mfile*);
91 void	ropen(Job*, Mfile*);
92 void	rcreate(Job*, Mfile*);
93 void	rread(Job*, Mfile*);
94 void	rwrite(Job*, Mfile*);
95 void	rclunk(Job*, Mfile*);
96 void	rremove(Job*, Mfile*);
97 void	rstat(Job*, Mfile*);
98 void	rwstat(Job*, Mfile*);
99 void	rauth(Job*);
100 void	sendmsg(Job*, char*);
101 void	error(char*);
102 void	mountinit(char*, char*);
103 void	io(void);
104 void	ndbinit(void);
105 void	netinit(int);
106 void	netadd(char*);
107 char	*genquery(Mfile*, char*);
108 char*	ipinfoquery(Mfile*, char**, int);
109 int	needproto(Network*, Ndbtuple*);
110 int	lookup(Mfile*);
111 Ndbtuple*	reorder(Ndbtuple*, Ndbtuple*);
112 void	ipid(void);
113 void	readipinterfaces(void);
114 void*	emalloc(int);
115 char*	estrdup(char*);
116 Job*	newjob(void);
117 void	freejob(Job*);
118 void	setext(char*, int, char*);
119 void	cleanmf(Mfile*);
120 
121 extern void	paralloc(void);
122 
123 Lock	dblock;		/* mutex on database operations */
124 Lock	netlock;	/* mutex for netinit() */
125 
126 char	*logfile = "cs";
127 char	*paranoiafile = "cs.paranoia";
128 
129 char	mntpt[Maxpath];
130 char	netndb[Maxpath];
131 
132 /*
133  *  Network specific translators
134  */
135 Ndbtuple*	iplookup(Network*, char*, char*, int);
136 char*		iptrans(Ndbtuple*, Network*, char*, char*, int);
137 Ndbtuple*	telcolookup(Network*, char*, char*, int);
138 char*		telcotrans(Ndbtuple*, Network*, char*, char*, int);
139 Ndbtuple*	dnsiplookup(char*, Ndbs*);
140 
141 struct Network
142 {
143 	char		*net;
144 	Ndbtuple	*(*lookup)(Network*, char*, char*, int);
145 	char		*(*trans)(Ndbtuple*, Network*, char*, char*, int);
146 	int		considered;
147 	int		fasttimeouthack;
148 	Network		*next;
149 };
150 
151 enum
152 {
153 	Ntcp = 0,
154 //	Nilfast,
155 
156 //	Fasttimeout = 1,
157 };
158 
159 /*
160  *  net doesn't apply to (r)udp, icmp(v6), or telco (for speed).
161  */
162 Network network[] = {
163 [Ntcp]	{ "tcp",	iplookup,	iptrans,	0, 0 },
164 // [Nilfast]{ "il",	iplookup,	iptrans,	0, Fasttimeout },
165 	{ "il",		iplookup,	iptrans,	0, 0 },
166 	{ "udp",	iplookup,	iptrans,	1, 0 },
167 	{ "icmp",	iplookup,	iptrans,	1, 0 },
168 	{ "icmpv6",	iplookup,	iptrans,	1, 0 },
169 	{ "rudp",	iplookup,	iptrans,	1, 0 },
170 	{ "telco",	telcolookup,	telcotrans,	1, 0 },
171 	{ 0 },
172 };
173 
174 Lock ipifclock;
175 Ipifc *ipifcs;
176 
177 char	eaddr[16];		/* ascii ethernet address */
178 char	ipaddr[64];		/* ascii internet address */
179 uchar	ipa[IPaddrlen];		/* binary internet address */
180 char	*mysysname;
181 
182 Network *netlist;		/* networks ordered by preference */
183 Network *last;
184 
185 static void
186 nstrcpy(char *to, char *from, int len)
187 {
188 	strncpy(to, from, len);
189 	to[len-1] = 0;
190 }
191 
192 void
193 usage(void)
194 {
195 	fprint(2, "usage: %s [-dn] [-f ndb-file] [-x netmtpt]\n", argv0);
196 	exits("usage");
197 }
198 
199 void
200 main(int argc, char *argv[])
201 {
202 	int justsetname;
203 	char ext[Maxpath], servefile[Maxpath];
204 
205 	justsetname = 0;
206 	setnetmtpt(mntpt, sizeof(mntpt), nil);
207 	ext[0] = 0;
208 	ARGBEGIN{
209 	case 'd':
210 		debug = 1;
211 		break;
212 	case 'f':
213 		dbfile = EARGF(usage());
214 		break;
215 	case 'n':
216 		justsetname = 1;
217 		break;
218 	case 'x':
219 		setnetmtpt(mntpt, sizeof(mntpt), EARGF(usage()));
220 		setext(ext, sizeof(ext), mntpt);
221 		break;
222 	}ARGEND
223 	USED(argc);
224 	USED(argv);
225 
226 	rfork(RFREND|RFNOTEG);
227 
228 	snprint(servefile, sizeof(servefile), "#s/cs%s", ext);
229 	snprint(netndb, sizeof(netndb), "%s/ndb", mntpt);
230 	unmount(servefile, mntpt);
231 	remove(servefile);
232 
233 	fmtinstall('E', eipfmt);
234 	fmtinstall('I', eipfmt);
235 	fmtinstall('M', eipfmt);
236 	fmtinstall('F', fcallfmt);
237 
238 	ndbinit();
239 	netinit(0);
240 
241 	if(!justsetname){
242 		mountinit(servefile, mntpt);
243 		io();
244 	}
245 	exits(0);
246 }
247 
248 /*
249  *  if a mount point is specified, set the cs extention to be the mount point
250  *  with '_'s replacing '/'s
251  */
252 void
253 setext(char *ext, int n, char *p)
254 {
255 	int i, c;
256 
257 	n--;
258 	for(i = 0; i < n; i++){
259 		c = p[i];
260 		if(c == 0)
261 			break;
262 		if(c == '/')
263 			c = '_';
264 		ext[i] = c;
265 	}
266 	ext[i] = 0;
267 }
268 
269 void
270 mountinit(char *service, char *mntpt)
271 {
272 	int f;
273 	int p[2];
274 	char buf[32];
275 
276 	if(pipe(p) < 0)
277 		error("pipe failed");
278 
279 	/*
280 	 *  make a /srv/cs
281 	 */
282 	f = create(service, OWRITE|ORCLOSE, 0666);
283 	if(f < 0)
284 		error(service);
285 	snprint(buf, sizeof(buf), "%d", p[1]);
286 	if(write(f, buf, strlen(buf)) != strlen(buf))
287 		error("write /srv/cs");
288 
289 	switch(rfork(RFFDG|RFPROC|RFNAMEG)){
290 	case 0:
291 		close(p[1]);
292 		break;
293 	case -1:
294 		error("fork failed\n");
295 	default:
296 		/*
297 		 *  put ourselves into the file system
298 		 */
299 		close(p[0]);
300 		if(mount(p[1], -1, mntpt, MAFTER, "") < 0)
301 			error("mount failed\n");
302 		_exits(0);
303 	}
304 	mfd[0] = mfd[1] = p[0];
305 }
306 
307 void
308 ndbinit(void)
309 {
310 	db = ndbopen(dbfile);
311 	if(db == nil)
312 		error("can't open network database");
313 
314 	netdb = ndbopen(netndb);
315 	if(netdb != nil){
316 		netdb->nohash = 1;
317 		db = ndbcat(netdb, db);
318 	}
319 }
320 
321 Mfile*
322 newfid(int fid)
323 {
324 	Mlist *f, *ff;
325 	Mfile *mf;
326 
327 	ff = 0;
328 	for(f = mlist; f; f = f->next)
329 		if(f->mf.busy && f->mf.fid == fid)
330 			return &f->mf;
331 		else if(!ff && !f->mf.busy)
332 			ff = f;
333 	if(ff == 0){
334 		ff = emalloc(sizeof *f);
335 		ff->next = mlist;
336 		mlist = ff;
337 	}
338 	mf = &ff->mf;
339 	memset(mf, 0, sizeof *mf);
340 	mf->fid = fid;
341 	return mf;
342 }
343 
344 Job*
345 newjob(void)
346 {
347 	Job *job;
348 
349 	job = mallocz(sizeof(Job), 1);
350 	lock(&joblock);
351 	job->next = joblist;
352 	joblist = job;
353 	job->request.tag = -1;
354 	unlock(&joblock);
355 	return job;
356 }
357 
358 void
359 freejob(Job *job)
360 {
361 	Job **l;
362 
363 	lock(&joblock);
364 	for(l = &joblist; *l; l = &(*l)->next){
365 		if((*l) == job){
366 			*l = job->next;
367 			free(job);
368 			break;
369 		}
370 	}
371 	unlock(&joblock);
372 }
373 
374 void
375 flushjob(int tag)
376 {
377 	Job *job;
378 
379 	lock(&joblock);
380 	for(job = joblist; job; job = job->next){
381 		if(job->request.tag == tag && job->request.type != Tflush){
382 			job->flushed = 1;
383 			break;
384 		}
385 	}
386 	unlock(&joblock);
387 }
388 
389 void
390 io(void)
391 {
392 	long n;
393 	Mfile *mf;
394 	int slaveflag;
395 	uchar mdata[IOHDRSZ + Maxfdata];
396 	Job *job;
397 
398 	/*
399 	 *  if we ask dns to fulfill requests,
400 	 *  a slave process is created to wait for replies.  The
401 	 *  master process returns immediately via a longjmp
402 	 *  through 'masterjmp'.
403 	 *
404 	 *  *isslave is a pointer into the call stack to a variable
405 	 *  that tells whether or not the current process is a slave.
406 	 */
407 	slaveflag = 0;		/* init slave variable */
408 	isslave = &slaveflag;
409 	setjmp(masterjmp);
410 
411 	for(;;){
412 		n = read9pmsg(mfd[0], mdata, sizeof mdata);
413 		if(n<=0)
414 			error("mount read");
415 		job = newjob();
416 		if(convM2S(mdata, n, &job->request) != n){
417 			syslog(1, logfile, "format error %ux %ux %ux %ux %ux",
418 				mdata[0], mdata[1], mdata[2], mdata[3], mdata[4]);
419 			freejob(job);
420 			continue;
421 		}
422 		lock(&dblock);
423 		mf = newfid(job->request.fid);
424 		if(debug)
425 			syslog(0, logfile, "%F", &job->request);
426 
427 
428 		switch(job->request.type){
429 		default:
430 			syslog(1, logfile, "unknown request type %d", job->request.type);
431 			break;
432 		case Tversion:
433 			rversion(job);
434 			break;
435 		case Tauth:
436 			rauth(job);
437 			break;
438 		case Tflush:
439 			rflush(job);
440 			break;
441 		case Tattach:
442 			rattach(job, mf);
443 			break;
444 		case Twalk:
445 			rwalk(job, mf);
446 			break;
447 		case Topen:
448 			ropen(job, mf);
449 			break;
450 		case Tcreate:
451 			rcreate(job, mf);
452 			break;
453 		case Tread:
454 			rread(job, mf);
455 			break;
456 		case Twrite:
457 			rwrite(job, mf);
458 			break;
459 		case Tclunk:
460 			rclunk(job, mf);
461 			break;
462 		case Tremove:
463 			rremove(job, mf);
464 			break;
465 		case Tstat:
466 			rstat(job, mf);
467 			break;
468 		case Twstat:
469 			rwstat(job, mf);
470 			break;
471 		}
472 		unlock(&dblock);
473 
474 		freejob(job);
475 
476 		/*
477 		 *  slave processes die after replying
478 		 */
479 		if(*isslave){
480 			if(debug)
481 				syslog(0, logfile, "slave death %d", getpid());
482 			_exits(0);
483 		}
484 	}
485 }
486 
487 void
488 rversion(Job *job)
489 {
490 	if(job->request.msize > IOHDRSZ + Maxfdata)
491 		job->reply.msize = IOHDRSZ + Maxfdata;
492 	else
493 		job->reply.msize = job->request.msize;
494 	if(strncmp(job->request.version, "9P2000", 6) != 0)
495 		sendmsg(job, "unknown 9P version");
496 	else{
497 		job->reply.version = "9P2000";
498 		sendmsg(job, 0);
499 	}
500 }
501 
502 void
503 rauth(Job *job)
504 {
505 	sendmsg(job, "cs: authentication not required");
506 }
507 
508 /*
509  *  don't flush till all the slaves are done
510  */
511 void
512 rflush(Job *job)
513 {
514 	flushjob(job->request.oldtag);
515 	sendmsg(job, 0);
516 }
517 
518 void
519 rattach(Job *job, Mfile *mf)
520 {
521 	if(mf->busy == 0){
522 		mf->busy = 1;
523 		mf->user = estrdup(job->request.uname);
524 	}
525 	mf->qid.vers = vers++;
526 	mf->qid.type = QTDIR;
527 	mf->qid.path = 0LL;
528 	job->reply.qid = mf->qid;
529 	sendmsg(job, 0);
530 }
531 
532 
533 char*
534 rwalk(Job *job, Mfile *mf)
535 {
536 	char *err;
537 	char **elems;
538 	int nelems;
539 	int i;
540 	Mfile *nmf;
541 	Qid qid;
542 
543 	err = 0;
544 	nmf = nil;
545 	elems = job->request.wname;
546 	nelems = job->request.nwname;
547 	job->reply.nwqid = 0;
548 
549 	if(job->request.newfid != job->request.fid){
550 		/* clone fid */
551 		nmf = newfid(job->request.newfid);
552 		if(nmf->busy){
553 			nmf = nil;
554 			err = "clone to used channel";
555 			goto send;
556 		}
557 		*nmf = *mf;
558 		nmf->user = estrdup(mf->user);
559 		nmf->fid = job->request.newfid;
560 		nmf->qid.vers = vers++;
561 		mf = nmf;
562 	}
563 	/* else nmf will be nil */
564 
565 	qid = mf->qid;
566 	if(nelems > 0){
567 		/* walk fid */
568 		for(i=0; i<nelems && i<MAXWELEM; i++){
569 			if((qid.type & QTDIR) == 0){
570 				err = "not a directory";
571 				break;
572 			}
573 			if(strcmp(elems[i], "..") == 0 || strcmp(elems[i], ".") == 0){
574 				qid.type = QTDIR;
575 				qid.path = Qdir;
576     Found:
577 				job->reply.wqid[i] = qid;
578 				job->reply.nwqid++;
579 				continue;
580 			}
581 			if(strcmp(elems[i], "cs") == 0){
582 				qid.type = QTFILE;
583 				qid.path = Qcs;
584 				goto Found;
585 			}
586 			err = "file does not exist";
587 			break;
588 		}
589 	}
590 
591     send:
592 	if(nmf != nil && (err!=nil || job->reply.nwqid<nelems)){
593 		cleanmf(nmf);
594 		free(nmf->user);
595 		nmf->user = 0;
596 		nmf->busy = 0;
597 		nmf->fid = 0;
598 	}
599 	if(err == nil)
600 		mf->qid = qid;
601 	sendmsg(job, err);
602 	return err;
603 }
604 
605 void
606 ropen(Job *job, Mfile *mf)
607 {
608 	int mode;
609 	char *err;
610 
611 	err = 0;
612 	mode = job->request.mode;
613 	if(mf->qid.type & QTDIR){
614 		if(mode)
615 			err = "permission denied";
616 	}
617 	job->reply.qid = mf->qid;
618 	job->reply.iounit = 0;
619 	sendmsg(job, err);
620 }
621 
622 void
623 rcreate(Job *job, Mfile *mf)
624 {
625 	USED(mf);
626 	sendmsg(job, "creation permission denied");
627 }
628 
629 void
630 rread(Job *job, Mfile *mf)
631 {
632 	int i, n, cnt;
633 	long off, toff, clock;
634 	Dir dir;
635 	uchar buf[Maxfdata];
636 	char *err;
637 
638 	n = 0;
639 	err = 0;
640 	off = job->request.offset;
641 	cnt = job->request.count;
642 	if(mf->qid.type & QTDIR){
643 		clock = time(0);
644 		if(off == 0){
645 			dir.name = "cs";
646 			dir.qid.type = QTFILE;
647 			dir.qid.vers = vers;
648 			dir.qid.path = Qcs;
649 			dir.mode = 0666;
650 			dir.length = 0;
651 			dir.uid = mf->user;
652 			dir.gid = mf->user;
653 			dir.muid = mf->user;
654 			dir.atime = clock;	/* wrong */
655 			dir.mtime = clock;	/* wrong */
656 			n = convD2M(&dir, buf, sizeof buf);
657 		}
658 		job->reply.data = (char*)buf;
659 	} else {
660 		for(;;){
661 			/* look for an answer at the right offset */
662 			toff = 0;
663 			for(i = 0; mf->reply[i] && i < mf->nreply; i++){
664 				n = mf->replylen[i];
665 				if(off < toff + n)
666 					break;
667 				toff += n;
668 			}
669 			if(i < mf->nreply)
670 				break;		/* got something to return */
671 
672 			/* try looking up more answers */
673 			if(lookup(mf) == 0){
674 				/* no more */
675 				n = 0;
676 				goto send;
677 			}
678 		}
679 
680 		/* give back a single reply (or part of one) */
681 		job->reply.data = mf->reply[i] + (off - toff);
682 		if(cnt > toff - off + n)
683 			n = toff - off + n;
684 		else
685 			n = cnt;
686 	}
687 send:
688 	job->reply.count = n;
689 	sendmsg(job, err);
690 }
691 void
692 cleanmf(Mfile *mf)
693 {
694 	int i;
695 
696 	if(mf->net != nil){
697 		free(mf->net);
698 		mf->net = nil;
699 	}
700 	if(mf->host != nil){
701 		free(mf->host);
702 		mf->host = nil;
703 	}
704 	if(mf->serv != nil){
705 		free(mf->serv);
706 		mf->serv = nil;
707 	}
708 	if(mf->rem != nil){
709 		free(mf->rem);
710 		mf->rem = nil;
711 	}
712 	for(i = 0; i < mf->nreply; i++){
713 		free(mf->reply[i]);
714 		mf->reply[i] = nil;
715 		mf->replylen[i] = 0;
716 	}
717 	mf->nreply = 0;
718 	mf->nextnet = netlist;
719 }
720 
721 void
722 rwrite(Job *job, Mfile *mf)
723 {
724 	int cnt, n;
725 	char *err;
726 	char *field[4];
727 	char curerr[64];
728 
729 	err = 0;
730 	cnt = job->request.count;
731 	if(mf->qid.type & QTDIR){
732 		err = "can't write directory";
733 		goto send;
734 	}
735 	if(cnt >= Maxrequest){
736 		err = "request too long";
737 		goto send;
738 	}
739 	job->request.data[cnt] = 0;
740 
741 	/*
742 	 *  toggle debugging
743 	 */
744 	if(strncmp(job->request.data, "debug", 5)==0){
745 		debug ^= 1;
746 		syslog(1, logfile, "debug %d", debug);
747 		goto send;
748 	}
749 
750 	/*
751 	 *  toggle debugging
752 	 */
753 	if(strncmp(job->request.data, "paranoia", 8)==0){
754 		paranoia ^= 1;
755 		syslog(1, logfile, "paranoia %d", paranoia);
756 		goto send;
757 	}
758 
759 	/*
760 	 *  add networks to the default list
761 	 */
762 	if(strncmp(job->request.data, "add ", 4)==0){
763 		if(job->request.data[cnt-1] == '\n')
764 			job->request.data[cnt-1] = 0;
765 		netadd(job->request.data+4);
766 		readipinterfaces();
767 		goto send;
768 	}
769 
770 	/*
771 	 *  refresh all state
772 	 */
773 	if(strncmp(job->request.data, "refresh", 7)==0){
774 		netinit(1);
775 		goto send;
776 	}
777 
778 	/* start transaction with a clean slate */
779 	cleanmf(mf);
780 
781 	/*
782 	 *  look for a general query
783 	 */
784 	if(*job->request.data == '!'){
785 		err = genquery(mf, job->request.data+1);
786 		goto send;
787 	}
788 
789 	if(debug)
790 		syslog(0, logfile, "write %s", job->request.data);
791 	if(paranoia)
792 		syslog(0, paranoiafile, "write %s by %s", job->request.data, mf->user);
793 
794 	/*
795 	 *  break up name
796 	 */
797 	n = getfields(job->request.data, field, 4, 1, "!");
798 	switch(n){
799 	case 1:
800 		mf->net = strdup("net");
801 		mf->host = strdup(field[0]);
802 		break;
803 	case 4:
804 		mf->rem = strdup(field[3]);
805 		/* fall through */
806 	case 3:
807 		mf->serv = strdup(field[2]);
808 		/* fall through */
809 	case 2:
810 		mf->host = strdup(field[1]);
811 		mf->net = strdup(field[0]);
812 		break;
813 	}
814 
815 	/*
816 	 *  do the first net worth of lookup
817 	 */
818 	if(lookup(mf) == 0){
819 		rerrstr(curerr, sizeof curerr);
820 		err = curerr;
821 	}
822 send:
823 	job->reply.count = cnt;
824 	sendmsg(job, err);
825 }
826 
827 void
828 rclunk(Job *job, Mfile *mf)
829 {
830 	cleanmf(mf);
831 	free(mf->user);
832 	mf->user = 0;
833 	mf->busy = 0;
834 	mf->fid = 0;
835 	sendmsg(job, 0);
836 }
837 
838 void
839 rremove(Job *job, Mfile *mf)
840 {
841 	USED(mf);
842 	sendmsg(job, "remove permission denied");
843 }
844 
845 void
846 rstat(Job *job, Mfile *mf)
847 {
848 	Dir dir;
849 	uchar buf[IOHDRSZ+Maxfdata];
850 
851 	if(mf->qid.type & QTDIR){
852 		dir.name = ".";
853 		dir.mode = DMDIR|0555;
854 	} else {
855 		dir.name = "cs";
856 		dir.mode = 0666;
857 	}
858 	dir.qid = mf->qid;
859 	dir.length = 0;
860 	dir.uid = mf->user;
861 	dir.gid = mf->user;
862 	dir.muid = mf->user;
863 	dir.atime = dir.mtime = time(0);
864 	job->reply.nstat = convD2M(&dir, buf, sizeof buf);
865 	job->reply.stat = buf;
866 	sendmsg(job, 0);
867 }
868 
869 void
870 rwstat(Job *job, Mfile *mf)
871 {
872 	USED(mf);
873 	sendmsg(job, "wstat permission denied");
874 }
875 
876 void
877 sendmsg(Job *job, char *err)
878 {
879 	int n;
880 	uchar mdata[IOHDRSZ + Maxfdata];
881 	char ename[ERRMAX];
882 
883 	if(err){
884 		job->reply.type = Rerror;
885 		snprint(ename, sizeof(ename), "cs: %s", err);
886 		job->reply.ename = ename;
887 	}else{
888 		job->reply.type = job->request.type+1;
889 	}
890 	job->reply.tag = job->request.tag;
891 	n = convS2M(&job->reply, mdata, sizeof mdata);
892 	if(n == 0){
893 		syslog(1, logfile, "sendmsg convS2M of %F returns 0", &job->reply);
894 		abort();
895 	}
896 	lock(&joblock);
897 	if(job->flushed == 0)
898 		if(write(mfd[1], mdata, n)!=n)
899 			error("mount write");
900 	unlock(&joblock);
901 	if(debug)
902 		syslog(0, logfile, "%F %d", &job->reply, n);
903 }
904 
905 void
906 error(char *s)
907 {
908 	syslog(1, "cs", "%s: %r", s);
909 	_exits(0);
910 }
911 
912 static int
913 isvalidip(uchar *ip)
914 {
915 	return ipcmp(ip, IPnoaddr) != 0 && ipcmp(ip, v4prefix) != 0;
916 }
917 
918 static uchar loopbacknet[IPaddrlen] = {
919 	0, 0, 0, 0,
920 	0, 0, 0, 0,
921 	0, 0, 0xff, 0xff,
922 	127, 0, 0, 0
923 };
924 static uchar loopbackmask[IPaddrlen] = {
925 	0xff, 0xff, 0xff, 0xff,
926 	0xff, 0xff, 0xff, 0xff,
927 	0xff, 0xff, 0xff, 0xff,
928 	0xff, 0, 0, 0
929 };
930 
931 void
932 readipinterfaces(void)
933 {
934 	if(myipaddr(ipa, mntpt) != 0)
935 		ipmove(ipa, IPnoaddr);
936 	sprint(ipaddr, "%I", ipa);
937 	if (debug)
938 		syslog(0, "dns", "ipaddr is %s\n", ipaddr);
939 }
940 
941 /*
942  *  get the system name
943  */
944 void
945 ipid(void)
946 {
947 	uchar addr[6];
948 	Ndbtuple *t, *tt;
949 	char *p, *attr;
950 	Ndbs s;
951 	int f;
952 	char buf[Maxpath];
953 
954 	/* use environment, ether addr, or ipaddr to get system name */
955 	if(mysysname == 0){
956 		/*
957 		 *  environment has priority.
958 		 *
959 		 *  on the sgi power the default system name
960 		 *  is the ip address.  ignore that.
961 		 *
962 		 */
963 		p = getenv("sysname");
964 		if(p && *p){
965 			attr = ipattr(p);
966 			if(strcmp(attr, "ip") != 0)
967 				mysysname = strdup(p);
968 		}
969 
970 		/*
971 		 *  the /net/ndb contains what the network
972 		 *  figured out from DHCP.  use that name if
973 		 *  there is one.
974 		 */
975 		if(mysysname == 0 && netdb != nil){
976 			ndbreopen(netdb);
977 			for(tt = t = ndbparse(netdb); t != nil; t = t->entry){
978 				if(strcmp(t->attr, "sys") == 0){
979 					mysysname = strdup(t->val);
980 					break;
981 				}
982 			}
983 			ndbfree(tt);
984 		}
985 
986 		/* next network database, ip address, and ether address to find a name */
987 		if(mysysname == 0){
988 			t = nil;
989 			if(isvalidip(ipa))
990 				free(ndbgetvalue(db, &s, "ip", ipaddr, "sys", &t));
991 			if(t == nil){
992 				for(f = 0; f < 3; f++){
993 					snprint(buf, sizeof buf, "%s/ether%d", mntpt, f);
994 					if(myetheraddr(addr, buf) >= 0){
995 						snprint(eaddr, sizeof(eaddr), "%E", addr);
996 						free(ndbgetvalue(db, &s, "ether", eaddr, "sys", &t));
997 						if(t != nil)
998 							break;
999 					}
1000 				}
1001 			}
1002 			for(tt = t; tt != nil; tt = tt->entry){
1003 				if(strcmp(tt->attr, "sys") == 0){
1004 					mysysname = strdup(tt->val);
1005 					break;
1006 				}
1007 			}
1008 			ndbfree(t);
1009 		}
1010 
1011 		/* nothing else worked, use the ip address */
1012 		if(mysysname == 0 && isvalidip(ipa))
1013 			mysysname = strdup(ipaddr);
1014 
1015 
1016 		/* set /dev/sysname if we now know it */
1017 		if(mysysname){
1018 			f = open("/dev/sysname", OWRITE);
1019 			if(f >= 0){
1020 				write(f, mysysname, strlen(mysysname));
1021 				close(f);
1022 			}
1023 		}
1024 	}
1025 }
1026 
1027 /*
1028  *  Set up a list of default networks by looking for
1029  *  /net/ * /clone.
1030  */
1031 void
1032 netinit(int background)
1033 {
1034 	char clone[Maxpath];
1035 	Network *np;
1036 	static int working;
1037 
1038 	if(background){
1039 		switch(rfork(RFPROC|RFNOTEG|RFMEM|RFNOWAIT)){
1040 		case 0:
1041 			break;
1042 		default:
1043 			return;
1044 		}
1045 		lock(&netlock);
1046 	}
1047 
1048 	/* add the mounted networks to the default list */
1049 	for(np = network; np->net; np++){
1050 		if(np->considered)
1051 			continue;
1052 		snprint(clone, sizeof(clone), "%s/%s/clone", mntpt, np->net);
1053 		if(access(clone, AEXIST) < 0)
1054 			continue;
1055 		if(netlist)
1056 			last->next = np;
1057 		else
1058 			netlist = np;
1059 		last = np;
1060 		np->next = 0;
1061 		np->considered = 1;
1062 	}
1063 
1064 	/* find out what our ip address is */
1065 	readipinterfaces();
1066 
1067 	/* set the system name if we need to, these says ip is all we have */
1068 	ipid();
1069 
1070 	if(debug)
1071 		syslog(0, logfile, "mysysname %s eaddr %s ipaddr %s ipa %I\n",
1072 			mysysname?mysysname:"???", eaddr, ipaddr, ipa);
1073 
1074 	if(background){
1075 		unlock(&netlock);
1076 		_exits(0);
1077 	}
1078 }
1079 
1080 /*
1081  *  add networks to the standard list
1082  */
1083 void
1084 netadd(char *p)
1085 {
1086 	Network *np;
1087 	char *field[12];
1088 	int i, n;
1089 
1090 	n = getfields(p, field, 12, 1, " ");
1091 	for(i = 0; i < n; i++){
1092 		for(np = network; np->net; np++){
1093 			if(strcmp(field[i], np->net) != 0)
1094 				continue;
1095 			if(np->considered)
1096 				break;
1097 			if(netlist)
1098 				last->next = np;
1099 			else
1100 				netlist = np;
1101 			last = np;
1102 			np->next = 0;
1103 			np->considered = 1;
1104 		}
1105 	}
1106 }
1107 
1108 int
1109 lookforproto(Ndbtuple *t, char *proto)
1110 {
1111 	for(; t != nil; t = t->entry)
1112 		if(strcmp(t->attr, "proto") == 0 && strcmp(t->val, proto) == 0)
1113 			return 1;
1114 	return 0;
1115 }
1116 
1117 /*
1118  *  lookup a request.  the network "net" means we should pick the
1119  *  best network to get there.
1120  */
1121 int
1122 lookup(Mfile *mf)
1123 {
1124 	Network *np;
1125 	char *cp;
1126 	Ndbtuple *nt, *t;
1127 	char reply[Maxreply];
1128 	int i, rv;
1129 	int hack;
1130 
1131 	/* open up the standard db files */
1132 	if(db == 0)
1133 		ndbinit();
1134 	if(db == 0)
1135 		error("can't open mf->network database\n");
1136 
1137 	rv = 0;
1138 
1139 	if(mf->net == nil)
1140 		return 0;	/* must have been a genquery */
1141 
1142 	if(strcmp(mf->net, "net") == 0){
1143 		/*
1144 		 *  go through set of default nets
1145 		 */
1146 		for(np = mf->nextnet; np; np = np->next){
1147 			nt = (*np->lookup)(np, mf->host, mf->serv, 1);
1148 			if(nt == nil)
1149 				continue;
1150 			hack = np->fasttimeouthack && !lookforproto(nt, np->net);
1151 			for(t = nt; mf->nreply < Nreply && t; t = t->entry){
1152 				cp = (*np->trans)(t, np, mf->serv, mf->rem, hack);
1153 				if(cp){
1154 					/* avoid duplicates */
1155 					for(i = 0; i < mf->nreply; i++)
1156 						if(strcmp(mf->reply[i], cp) == 0)
1157 							break;
1158 					if(i == mf->nreply){
1159 						/* save the reply */
1160 						mf->replylen[mf->nreply] = strlen(cp);
1161 						mf->reply[mf->nreply++] = cp;
1162 						rv++;
1163 					}
1164 				}
1165 			}
1166 			ndbfree(nt);
1167 			np = np->next;
1168 			break;
1169 		}
1170 		mf->nextnet = np;
1171 		return rv;
1172 	}
1173 
1174 	/*
1175 	 *  if not /net, we only get one lookup
1176 	 */
1177 	if(mf->nreply != 0)
1178 		return 0;
1179 	/*
1180 	 *  look for a specific network
1181 	 */
1182 	for(np = netlist; np && np->net != nil; np++){
1183 		if(np->fasttimeouthack)
1184 			continue;
1185 		if(strcmp(np->net, mf->net) == 0)
1186 			break;
1187 	}
1188 
1189 	if(np && np->net != nil){
1190 		/*
1191 		 *  known network
1192 		 */
1193 		nt = (*np->lookup)(np, mf->host, mf->serv, 1);
1194 		for(t = nt; mf->nreply < Nreply && t; t = t->entry){
1195 			cp = (*np->trans)(t, np, mf->serv, mf->rem, 0);
1196 			if(cp){
1197 				mf->replylen[mf->nreply] = strlen(cp);
1198 				mf->reply[mf->nreply++] = cp;
1199 				rv++;
1200 			}
1201 		}
1202 		ndbfree(nt);
1203 		return rv;
1204 	} else {
1205 		/*
1206 		 *  not a known network, don't translate host or service
1207 		 */
1208 		if(mf->serv)
1209 			snprint(reply, sizeof(reply), "%s/%s/clone %s!%s",
1210 				mntpt, mf->net, mf->host, mf->serv);
1211 		else
1212 			snprint(reply, sizeof(reply), "%s/%s/clone %s",
1213 				mntpt, mf->net, mf->host);
1214 		mf->reply[0] = strdup(reply);
1215 		mf->replylen[0] = strlen(reply);
1216 		mf->nreply = 1;
1217 		return 1;
1218 	}
1219 }
1220 
1221 /*
1222  *  translate an ip service name into a port number.  If it's a numeric port
1223  *  number, look for restricted access.
1224  *
1225  *  the service '*' needs no translation.
1226  */
1227 char*
1228 ipserv(Network *np, char *name, char *buf, int blen)
1229 {
1230 	char *p;
1231 	int alpha = 0;
1232 	int restr = 0;
1233 	char port[10];
1234 	Ndbtuple *t, *nt;
1235 	Ndbs s;
1236 
1237 	/* '*' means any service */
1238 	if(strcmp(name, "*")==0){
1239 		strcpy(buf, name);
1240 		return buf;
1241 	}
1242 
1243 	/*  see if it's numeric or symbolic */
1244 	port[0] = 0;
1245 	for(p = name; *p; p++){
1246 		if(isdigit(*p))
1247 			{}
1248 		else if(isalpha(*p) || *p == '-' || *p == '$')
1249 			alpha = 1;
1250 		else
1251 			return 0;
1252 	}
1253 	t = nil;
1254 	p = nil;
1255 	if(alpha){
1256 		p = ndbgetvalue(db, &s, np->net, name, "port", &t);
1257 		if(p == nil)
1258 			return 0;
1259 	} else {
1260 		/* look up only for tcp ports < 1024 to get the restricted
1261 		 * attribute
1262 		 */
1263 		if(atoi(name) < 1024 && strcmp(np->net, "tcp") == 0)
1264 			p = ndbgetvalue(db, &s, "port", name, "port", &t);
1265 		if(p == nil)
1266 			p = strdup(name);
1267 	}
1268 
1269 	if(t){
1270 		for(nt = t; nt; nt = nt->entry)
1271 			if(strcmp(nt->attr, "restricted") == 0)
1272 				restr = 1;
1273 		ndbfree(t);
1274 	}
1275 	snprint(buf, blen, "%s%s", p, restr ? "!r" : "");
1276 	free(p);
1277 
1278 	return buf;
1279 }
1280 
1281 /*
1282  *  lookup an ip attribute
1283  */
1284 int
1285 ipattrlookup(Ndb *db, char *ipa, char *attr, char *val, int vlen)
1286 {
1287 
1288 	Ndbtuple *t, *nt;
1289 	char *alist[2];
1290 
1291 	alist[0] = attr;
1292 	t = ndbipinfo(db, "ip", ipa, alist, 1);
1293 	if(t == nil)
1294 		return 0;
1295 	for(nt = t; nt != nil; nt = nt->entry){
1296 		if(strcmp(nt->attr, attr) == 0){
1297 			nstrcpy(val, nt->val, vlen);
1298 			ndbfree(t);
1299 			return 1;
1300 		}
1301 	}
1302 
1303 	/* we shouldn't get here */
1304 	ndbfree(t);
1305 	return 0;
1306 }
1307 
1308 /*
1309  *  lookup (and translate) an ip destination
1310  */
1311 Ndbtuple*
1312 iplookup(Network *np, char *host, char *serv, int nolookup)
1313 {
1314 	char *attr, *dnsname;
1315 	Ndbtuple *t, *nt;
1316 	Ndbs s;
1317 	char ts[Maxservice];
1318 	char dollar[Maxhost];
1319 	uchar ip[IPaddrlen];
1320 	uchar net[IPaddrlen];
1321 	uchar tnet[IPaddrlen];
1322 	Ipifc *ifc;
1323 	Iplifc *lifc;
1324 
1325 	USED(nolookup);
1326 
1327 	/*
1328 	 *  start with the service since it's the most likely to fail
1329 	 *  and costs the least
1330 	 */
1331 	werrstr("can't translate address");
1332 	if(serv==0 || ipserv(np, serv, ts, sizeof ts) == 0){
1333 		werrstr("can't translate service");
1334 		return 0;
1335 	}
1336 
1337 	/* for dial strings with no host */
1338 	if(strcmp(host, "*") == 0)
1339 		return ndbnew("ip", "*");
1340 
1341 	/*
1342 	 *  hack till we go v6 :: = 0.0.0.0
1343 	 */
1344 	if(strcmp("::", host) == 0)
1345 		return ndbnew("ip", "*");
1346 
1347 	/*
1348 	 *  '$' means the rest of the name is an attribute that we
1349 	 *  need to search for
1350 	 */
1351 	if(*host == '$'){
1352 		if(ipattrlookup(db, ipaddr, host+1, dollar, sizeof dollar))
1353 			host = dollar;
1354 	}
1355 
1356 	/*
1357 	 *  turn '[ip address]' into just 'ip address'
1358 	 */
1359 	if(*host == '[' && host[strlen(host)-1] == ']'){
1360 		host++;
1361 		host[strlen(host)-1] = 0;
1362 	}
1363 
1364 	/*
1365 	 *  just accept addresses
1366 	 */
1367 	attr = ipattr(host);
1368 	if(strcmp(attr, "ip") == 0)
1369 		return ndbnew("ip", host);
1370 
1371 	/*
1372 	 *  give the domain name server the first opportunity to
1373 	 *  resolve domain names.  if that fails try the database.
1374 	 */
1375 	t = 0;
1376 	werrstr("can't translate address");
1377 	if(strcmp(attr, "dom") == 0)
1378 		t = dnsiplookup(host, &s);
1379 	if(t == 0)
1380 		free(ndbgetvalue(db, &s, attr, host, "ip", &t));
1381 	if(t == 0){
1382 		dnsname = ndbgetvalue(db, &s, attr, host, "dom", nil);
1383 		if(dnsname){
1384 			t = dnsiplookup(dnsname, &s);
1385 			free(dnsname);
1386 		}
1387 	}
1388 	if(t == 0)
1389 		t = dnsiplookup(host, &s);
1390 	if(t == 0)
1391 		return 0;
1392 
1393 	/*
1394 	 *  reorder the tuple to have the matched line first and
1395 	 *  save that in the request structure.
1396 	 */
1397 	t = reorder(t, s.t);
1398 
1399 	/*
1400 	 * reorder according to our interfaces
1401 	 */
1402 	lock(&ipifclock);
1403 	for(ifc = ipifcs; ifc != nil; ifc = ifc->next){
1404 		for(lifc = ifc->lifc; lifc != nil; lifc = lifc->next){
1405 			maskip(lifc->ip, lifc->mask, net);
1406 			for(nt = t; nt; nt = nt->entry){
1407 				if(strcmp(nt->attr, "ip") != 0)
1408 					continue;
1409 				parseip(ip, nt->val);
1410 				maskip(ip, lifc->mask, tnet);
1411 				if(memcmp(net, tnet, IPaddrlen) == 0){
1412 					t = reorder(t, nt);
1413 					unlock(&ipifclock);
1414 					return t;
1415 				}
1416 			}
1417 		}
1418 	}
1419 	unlock(&ipifclock);
1420 
1421 	return t;
1422 }
1423 
1424 /*
1425  *  translate an ip address
1426  */
1427 char*
1428 iptrans(Ndbtuple *t, Network *np, char *serv, char *rem, int hack)
1429 {
1430 	char ts[Maxservice];
1431 	char reply[Maxreply];
1432 	char x[Maxservice];
1433 
1434 	if(strcmp(t->attr, "ip") != 0)
1435 		return 0;
1436 
1437 	if(serv == 0 || ipserv(np, serv, ts, sizeof ts) == 0){
1438 		werrstr("can't translate service");
1439 		return 0;
1440 	}
1441 	if(rem != nil)
1442 		snprint(x, sizeof(x), "!%s", rem);
1443 	else
1444 		*x = 0;
1445 
1446 	if(*t->val == '*')
1447 		snprint(reply, sizeof(reply), "%s/%s/clone %s%s",
1448 			mntpt, np->net, ts, x);
1449 	else
1450 		snprint(reply, sizeof(reply), "%s/%s/clone %s!%s%s%s",
1451 			mntpt, np->net, t->val, ts, x, hack? "!fasttimeout": "");
1452 
1453 	return strdup(reply);
1454 }
1455 
1456 /*
1457  *  lookup a telephone number
1458  */
1459 Ndbtuple*
1460 telcolookup(Network *np, char *host, char *serv, int nolookup)
1461 {
1462 	Ndbtuple *t;
1463 	Ndbs s;
1464 
1465 	USED(np, nolookup, serv);
1466 
1467 	werrstr("can't translate address");
1468 	free(ndbgetvalue(db, &s, "sys", host, "telco", &t));
1469 	if(t == 0)
1470 		return ndbnew("telco", host);
1471 
1472 	return reorder(t, s.t);
1473 }
1474 
1475 /*
1476  *  translate a telephone address
1477  */
1478 char*
1479 telcotrans(Ndbtuple *t, Network *np, char *serv, char *rem, int)
1480 {
1481 	char reply[Maxreply];
1482 	char x[Maxservice];
1483 
1484 	if(strcmp(t->attr, "telco") != 0)
1485 		return 0;
1486 
1487 	if(rem != nil)
1488 		snprint(x, sizeof(x), "!%s", rem);
1489 	else
1490 		*x = 0;
1491 	if(serv)
1492 		snprint(reply, sizeof(reply), "%s/%s/clone %s!%s%s", mntpt, np->net,
1493 			t->val, serv, x);
1494 	else
1495 		snprint(reply, sizeof(reply), "%s/%s/clone %s%s", mntpt, np->net,
1496 			t->val, x);
1497 	return strdup(reply);
1498 }
1499 
1500 /*
1501  *  reorder the tuple to put x's line first in the entry
1502  */
1503 Ndbtuple*
1504 reorder(Ndbtuple *t, Ndbtuple *x)
1505 {
1506 	Ndbtuple *nt;
1507 	Ndbtuple *line;
1508 
1509 	/* find start of this entry's line */
1510 	for(line = x; line->entry == line->line; line = line->line)
1511 		;
1512 	line = line->line;
1513 	if(line == t)
1514 		return t;	/* already the first line */
1515 
1516 	/* remove this line and everything after it from the entry */
1517 	for(nt = t; nt->entry != line; nt = nt->entry)
1518 		;
1519 	nt->entry = 0;
1520 
1521 	/* make that the start of the entry */
1522 	for(nt = line; nt->entry; nt = nt->entry)
1523 		;
1524 	nt->entry = t;
1525 	return line;
1526 }
1527 
1528 /*
1529  *  create a slave process to handle a request to avoid one request blocking
1530  *  another.  parent returns to job loop.
1531  */
1532 void
1533 slave(void)
1534 {
1535 	if(*isslave)
1536 		return;		/* we're already a slave process */
1537 
1538 	switch(rfork(RFPROC|RFNOTEG|RFMEM|RFNOWAIT)){
1539 	case -1:
1540 		break;
1541 	case 0:
1542 		if(debug)
1543 			syslog(0, logfile, "slave %d", getpid());
1544 		*isslave = 1;
1545 		break;
1546 	default:
1547 		longjmp(masterjmp, 1);
1548 	}
1549 
1550 }
1551 
1552 /*
1553  *  call the dns process and have it try to translate a name
1554  */
1555 Ndbtuple*
1556 dnsiplookup(char *host, Ndbs *s)
1557 {
1558 	char buf[Maxreply];
1559 	Ndbtuple *t;
1560 
1561 	unlock(&dblock);
1562 
1563 	/* save the name before starting a slave */
1564 	snprint(buf, sizeof(buf), "%s", host);
1565 
1566 	slave();
1567 
1568 	if(strcmp(ipattr(buf), "ip") == 0)
1569 		t = dnsquery(mntpt, buf, "ptr");
1570 	else
1571 		t = dnsquery(mntpt, buf, "ip");
1572 	s->t = t;
1573 
1574 	if(t == nil){
1575 		rerrstr(buf, sizeof buf);
1576 		if(strstr(buf, "exist"))
1577 			werrstr("can't translate address: %s", buf);
1578 		else if(strstr(buf, "dns failure"))
1579 			werrstr("temporary problem: %s", buf);
1580 	}
1581 
1582 	lock(&dblock);
1583 	return t;
1584 }
1585 
1586 int
1587 qmatch(Ndbtuple *t, char **attr, char **val, int n)
1588 {
1589 	int i, found;
1590 	Ndbtuple *nt;
1591 
1592 	for(i = 1; i < n; i++){
1593 		found = 0;
1594 		for(nt = t; nt; nt = nt->entry)
1595 			if(strcmp(attr[i], nt->attr) == 0)
1596 				if(strcmp(val[i], "*") == 0
1597 				|| strcmp(val[i], nt->val) == 0){
1598 					found = 1;
1599 					break;
1600 				}
1601 		if(found == 0)
1602 			break;
1603 	}
1604 	return i == n;
1605 }
1606 
1607 void
1608 qreply(Mfile *mf, Ndbtuple *t)
1609 {
1610 	Ndbtuple *nt;
1611 	String *s;
1612 
1613 	s = s_new();
1614 	for(nt = t; mf->nreply < Nreply && nt; nt = nt->entry){
1615 		s_append(s, nt->attr);
1616 		s_append(s, "=");
1617 		s_append(s, nt->val);
1618 
1619 		if(nt->line != nt->entry){
1620 			mf->replylen[mf->nreply] = s_len(s);
1621 			mf->reply[mf->nreply++] = strdup(s_to_c(s));
1622 			s_restart(s);
1623 		} else
1624 			s_append(s, " ");
1625 	}
1626 	s_free(s);
1627 }
1628 
1629 enum
1630 {
1631 	Maxattr=	32,
1632 };
1633 
1634 /*
1635  *  generic query lookup.  The query is of one of the following
1636  *  forms:
1637  *
1638  *  attr1=val1 attr2=val2 attr3=val3 ...
1639  *
1640  *  returns the matching tuple
1641  *
1642  *  ipinfo attr=val attr1 attr2 attr3 ...
1643  *
1644  *  is like ipinfo and returns the attr{1-n}
1645  *  associated with the ip address.
1646  */
1647 char*
1648 genquery(Mfile *mf, char *query)
1649 {
1650 	int i, n;
1651 	char *p;
1652 	char *attr[Maxattr];
1653 	char *val[Maxattr];
1654 	Ndbtuple *t;
1655 	Ndbs s;
1656 
1657 	n = getfields(query, attr, 32, 1, " ");
1658 	if(n == 0)
1659 		return "bad query";
1660 
1661 	if(strcmp(attr[0], "ipinfo") == 0)
1662 		return ipinfoquery(mf, attr, n);
1663 
1664 	/* parse pairs */
1665 	for(i = 0; i < n; i++){
1666 		p = strchr(attr[i], '=');
1667 		if(p == 0)
1668 			return "bad query";
1669 		*p++ = 0;
1670 		val[i] = p;
1671 	}
1672 
1673 	/* give dns a chance */
1674 	if((strcmp(attr[0], "dom") == 0 || strcmp(attr[0], "ip") == 0) && val[0]){
1675 		t = dnsiplookup(val[0], &s);
1676 		if(t){
1677 			if(qmatch(t, attr, val, n)){
1678 				qreply(mf, t);
1679 				ndbfree(t);
1680 				return 0;
1681 			}
1682 			ndbfree(t);
1683 		}
1684 	}
1685 
1686 	/* first pair is always the key.  It can't be a '*' */
1687 	t = ndbsearch(db, &s, attr[0], val[0]);
1688 
1689 	/* search is the and of all the pairs */
1690 	while(t){
1691 		if(qmatch(t, attr, val, n)){
1692 			qreply(mf, t);
1693 			ndbfree(t);
1694 			return 0;
1695 		}
1696 
1697 		ndbfree(t);
1698 		t = ndbsnext(&s, attr[0], val[0]);
1699 	}
1700 
1701 	return "no match";
1702 }
1703 
1704 /*
1705  *  resolve an ip address
1706  */
1707 static Ndbtuple*
1708 ipresolve(char *attr, char *host)
1709 {
1710 	Ndbtuple *t, *nt, **l;
1711 
1712 	t = iplookup(&network[Ntcp], host, "*", 0);
1713 	for(l = &t; *l != nil; ){
1714 		nt = *l;
1715 		if(strcmp(nt->attr, "ip") != 0){
1716 			*l = nt->entry;
1717 			nt->entry = nil;
1718 			ndbfree(nt);
1719 			continue;
1720 		}
1721 		strcpy(nt->attr, attr);
1722 		l = &nt->entry;
1723 	}
1724 	return t;
1725 }
1726 
1727 char*
1728 ipinfoquery(Mfile *mf, char **list, int n)
1729 {
1730 	int i, nresolve;
1731 	int resolve[Maxattr];
1732 	Ndbtuple *t, *nt, **l;
1733 	char *attr, *val;
1734 
1735 	/* skip 'ipinfo' */
1736 	list++; n--;
1737 
1738 	if(n < 1)
1739 		return "bad query";
1740 
1741 	/* get search attribute=value, or assume ip=myipaddr */
1742 	attr = *list;
1743 	if((val = strchr(attr, '=')) != nil){
1744 		*val++ = 0;
1745 		list++;
1746 		n--;
1747 	}else{
1748 		attr = "ip";
1749 		val = ipaddr;
1750 	}
1751 
1752 	if(n < 1)
1753 		return "bad query";
1754 
1755 	/*
1756 	 *  don't let ndbipinfo resolve the addresses, we're
1757 	 *  better at it.
1758 	 */
1759 	nresolve = 0;
1760 	for(i = 0; i < n; i++)
1761 		if(*list[i] == '@'){		/* @attr=val ? */
1762 			list[i]++;
1763 			resolve[i] = 1;		/* we'll resolve it */
1764 			nresolve++;
1765 		} else
1766 			resolve[i] = 0;
1767 
1768 	t = ndbipinfo(db, attr, val, list, n);
1769 	if(t == nil)
1770 		return "no match";
1771 
1772 	if(nresolve != 0){
1773 		for(l = &t; *l != nil;){
1774 			nt = *l;
1775 
1776 			/* already an address? */
1777 			if(strcmp(ipattr(nt->val), "ip") == 0){
1778 				l = &(*l)->entry;
1779 				continue;
1780 			}
1781 
1782 			/* user wants it resolved? */
1783 			for(i = 0; i < n; i++)
1784 				if(strcmp(list[i], nt->attr) == 0)
1785 					break;
1786 			if(i >= n || resolve[i] == 0){
1787 				l = &(*l)->entry;
1788 				continue;
1789 			}
1790 
1791 			/* resolve address and replace entry */
1792 			*l = ipresolve(nt->attr, nt->val);
1793 			while(*l != nil)
1794 				l = &(*l)->entry;
1795 			*l = nt->entry;
1796 
1797 			nt->entry = nil;
1798 			ndbfree(nt);
1799 		}
1800 	}
1801 
1802 	/* make it all one line */
1803 	for(nt = t; nt != nil; nt = nt->entry){
1804 		if(nt->entry == nil)
1805 			nt->line = t;
1806 		else
1807 			nt->line = nt->entry;
1808 	}
1809 
1810 	qreply(mf, t);
1811 
1812 	return nil;
1813 }
1814 
1815 void*
1816 emalloc(int size)
1817 {
1818 	void *x;
1819 
1820 	x = malloc(size);
1821 	if(x == nil)
1822 		abort();
1823 	memset(x, 0, size);
1824 	return x;
1825 }
1826 
1827 char*
1828 estrdup(char *s)
1829 {
1830 	int size;
1831 	char *p;
1832 
1833 	size = strlen(s)+1;
1834 	p = malloc(size);
1835 	if(p == nil)
1836 		abort();
1837 	memmove(p, s, size);
1838 	return p;
1839 }
1840