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