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