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