]> git.lizzy.rs Git - plan9front.git/blob - sys/src/cmd/upas/fs/fs.c
upas/fs: catch alarm note (used in pop3 code)
[plan9front.git] / sys / src / cmd / upas / fs / fs.c
1 #include "common.h"
2 #include <auth.h>
3 #include <fcall.h>
4 #include <libsec.h>
5 #include <ctype.h>
6 #include "dat.h"
7
8 enum
9 {
10         OPERM   = 0x3,          // mask of all permission types in open mode
11 };
12
13 typedef struct Fid Fid;
14
15 struct Fid
16 {
17         Qid     qid;
18         short   busy;
19         short   open;
20         int     fid;
21         Fid     *next;
22         Mailbox *mb;
23         Message *m;
24         Message *mtop;          // top level message
25
26         //finger pointers to speed up reads of large directories
27         long    foff;   // offset/DIRLEN of finger
28         Message *fptr;  // pointer to message at off
29         int     fvers;  // mailbox version when finger was saved
30 };
31
32 ulong   path;           // incremented for each new file
33 Fid     *fids;
34 int     mfd[2];
35 char    user[Elemlen];
36 int     messagesize = 4*1024+IOHDRSZ;
37 uchar   mdata[8*1024+IOHDRSZ];
38 uchar   mbuf[8*1024+IOHDRSZ];
39 Fcall   thdr;
40 Fcall   rhdr;
41 int     fflg;
42 char    *mntpt;
43 int     biffing;
44 int     plumbing = 1;
45
46 QLock   mbllock;
47 Mailbox *mbl;
48
49 Fid             *newfid(int);
50 void            error(char*);
51 void            io(void);
52 void            *erealloc(void*, ulong);
53 void            *emalloc(ulong);
54 void            usage(void);
55 void            reader(void);
56 int             readheader(Message*, char*, int, int);
57 int             cistrncmp(char*, char*, int);
58 int             tokenconvert(String*, char*, int);
59 String*         stringconvert(String*, char*, int);
60 void            post(char*, char*, int);
61
62 char    *rflush(Fid*), *rauth(Fid*),
63         *rattach(Fid*), *rwalk(Fid*),
64         *ropen(Fid*), *rcreate(Fid*),
65         *rread(Fid*), *rwrite(Fid*), *rclunk(Fid*),
66         *rremove(Fid*), *rstat(Fid*), *rwstat(Fid*),
67         *rversion(Fid*);
68
69 char    *(*fcalls[])(Fid*) = {
70         [Tflush]        rflush,
71         [Tversion]      rversion,
72         [Tauth] rauth,
73         [Tattach]       rattach,
74         [Twalk]         rwalk,
75         [Topen]         ropen,
76         [Tcreate]       rcreate,
77         [Tread]         rread,
78         [Twrite]        rwrite,
79         [Tclunk]        rclunk,
80         [Tremove]       rremove,
81         [Tstat]         rstat,
82         [Twstat]        rwstat,
83 };
84
85 char    Eperm[] =       "permission denied";
86 char    Enotdir[] =     "not a directory";
87 char    Enoauth[] =     "upas/fs: authentication not required";
88 char    Enotexist[] =   "file does not exist";
89 char    Einuse[] =      "file in use";
90 char    Eexist[] =      "file exists";
91 char    Enotowner[] =   "not owner";
92 char    Eisopen[] =     "file already open for I/O";
93 char    Excl[] =        "exclusive use file already open";
94 char    Ename[] =       "illegal name";
95 char    Ebadctl[] =     "unknown control message";
96
97 char *dirtab[] =
98 {
99 [Qdir]          ".",
100 [Qbody]         "body",
101 [Qbcc]          "bcc",
102 [Qcc]           "cc",
103 [Qdate]         "date",
104 [Qdigest]       "digest",
105 [Qdisposition]  "disposition",
106 [Qfilename]     "filename",
107 [Qfrom]         "from",
108 [Qheader]       "header",
109 [Qinfo]         "info",
110 [Qinreplyto]    "inreplyto",
111 [Qlines]        "lines",
112 [Qmimeheader]   "mimeheader",
113 [Qmessageid]    "messageid",
114 [Qraw]          "raw",
115 [Qrawunix]      "rawunix",
116 [Qrawbody]      "rawbody",
117 [Qrawheader]    "rawheader",
118 [Qreplyto]      "replyto",
119 [Qsender]       "sender",
120 [Qsubject]      "subject",
121 [Qto]           "to",
122 [Qtype]         "type",
123 [Qunixdate]     "unixdate",
124 [Qunixheader]   "unixheader",
125 [Qctl]          "ctl",
126 [Qmboxctl]      "ctl",
127 };
128
129 enum
130 {
131         Hsize=  1277,
132 };
133
134 Hash    *htab[Hsize];
135
136 int     debug;
137 int     fflag;
138 int     logging;
139
140 void
141 usage(void)
142 {
143         fprint(2, "usage: upas/fs [-bdlnps] [-f mboxfile] [-m mountpoint]\n");
144         exits("usage");
145 }
146
147 void
148 notifyf(void *, char *s)
149 {
150         if(strstr(s, "alarm") || strstr(s, "interrupt"))
151                 noted(NCONT);
152         noted(NDFLT);
153 }
154
155 void
156 main(int argc, char *argv[])
157 {
158         int p[2], std, nodflt;
159         char maildir[128];
160         char mbox[128];
161         char *mboxfile, *err;
162         char srvfile[64];
163         int srvpost;
164
165         rfork(RFNOTEG);
166         mntpt = nil;
167         fflag = 0;
168         mboxfile = nil;
169         std = 0;
170         nodflt = 0;
171         srvpost = 0;
172
173         ARGBEGIN{
174         case 'b':
175                 biffing = 1;
176                 break;
177         case 'f':
178                 fflag = 1;
179                 mboxfile = EARGF(usage());
180                 break;
181         case 'm':
182                 mntpt = EARGF(usage());
183                 break;
184         case 'd':
185                 debug = 1;
186                 break;
187         case 'p':
188                 plumbing = 0;
189                 break;
190         case 's':
191                 srvpost = 1;
192                 break;
193         case 'l':
194                 logging = 1;
195                 break;
196         case 'n':
197                 nodflt = 1;
198                 break;
199         default:
200                 usage();
201         }ARGEND
202
203         if(argc)
204                 usage();
205         if(pipe(p) < 0)
206                 error("pipe failed");
207         mfd[0] = p[0];
208         mfd[1] = p[0];
209
210         notify(notifyf);
211         strcpy(user, getuser());
212         if(mntpt == nil){
213                 snprint(maildir, sizeof(maildir), "/mail/fs");
214                 mntpt = maildir;
215         }
216         if(mboxfile == nil && !nodflt){
217                 snprint(mbox, sizeof(mbox), "/mail/box/%s/mbox", user);
218                 mboxfile = mbox;
219                 std = 1;
220         }
221
222         if(debug)
223                 fmtinstall('F', fcallfmt);
224
225         if(mboxfile != nil){
226                 err = newmbox(mboxfile, "mbox", std);
227                 if(err != nil)
228                         sysfatal("opening %s: %s", mboxfile, err);
229         }
230
231         switch(rfork(RFFDG|RFPROC|RFNAMEG|RFNOTEG|RFREND)){
232         case -1:
233                 error("fork");
234         case 0:
235                 henter(PATH(0, Qtop), dirtab[Qctl],
236                         (Qid){PATH(0, Qctl), 0, QTFILE}, nil, nil);
237                 close(p[1]);
238                 io();
239                 postnote(PNGROUP, getpid(), "die yankee pig dog");
240                 break;
241         default:
242                 close(p[0]);    /* don't deadlock if child fails */
243                 if(srvpost){
244                         sprint(srvfile, "/srv/upasfs.%s", user);
245                         post(srvfile, "upasfs", p[1]);
246                 } else {
247                         if(mount(p[1], -1, mntpt, MREPL, "") < 0)
248                                 error("mount failed");
249                 }
250         }
251         exits(0);
252 }
253
254 static int
255 fileinfo(Message *m, int t, char **pp)
256 {
257         char *p;
258         int len;
259
260         p = "";
261         len = 0;
262         switch(t){
263         case Qbody:
264                 p = m->body;
265                 len = m->bend - m->body;
266                 break;
267         case Qbcc:
268                 if(m->bcc822){
269                         p = s_to_c(m->bcc822);
270                         len = strlen(p);
271                 }
272                 break;
273         case Qcc:
274                 if(m->cc822){
275                         p = s_to_c(m->cc822);
276                         len = strlen(p);
277                 }
278                 break;
279         case Qdisposition:
280                 switch(m->disposition){
281                 case Dinline:
282                         p = "inline";
283                         break;
284                 case Dfile:
285                         p = "file";
286                         break;
287                 }
288                 len = strlen(p);
289                 break;
290         case Qdate:
291                 if(m->date822){
292                         p = s_to_c(m->date822);
293                         len = strlen(p);
294                 } else if(m->unixdate != nil){
295                         p = s_to_c(m->unixdate);
296                         len = strlen(p);
297                 }
298                 break;
299         case Qfilename:
300                 if(m->filename){
301                         p = s_to_c(m->filename);
302                         len = strlen(p);
303                 }
304                 break;
305         case Qinreplyto:
306                 if(m->inreplyto822){
307                         p = s_to_c(m->inreplyto822);
308                         len = strlen(p);
309                 }
310                 break;
311         case Qmessageid:
312                 if(m->messageid822){
313                         p = s_to_c(m->messageid822);
314                         len = strlen(p);
315                 }
316                 break;
317         case Qfrom:
318                 if(m->from822){
319                         p = s_to_c(m->from822);
320                         len = strlen(p);
321                 } else if(m->unixfrom != nil){
322                         p = s_to_c(m->unixfrom);
323                         len = strlen(p);
324                 }
325                 break;
326         case Qheader:
327                 p = m->header;
328                 len = headerlen(m);
329                 break;
330         case Qlines:
331                 p = m->lines;
332                 if(*p == 0)
333                         countlines(m);
334                 len = strlen(m->lines);
335                 break;
336         case Qraw:
337                 p = m->start;
338                 if(strncmp(m->start, "From ", 5) == 0){
339                         p = strchr(p, '\n');
340                         if(p == nil)
341                                 p = m->start;
342                         else
343                                 p++;
344                 }
345                 len = m->end - p;
346                 break;
347         case Qrawunix:
348                 p = m->start;
349                 len = m->end - p;
350                 break;
351         case Qrawbody:
352                 p = m->rbody;
353                 len = m->rbend - p;
354                 break;
355         case Qrawheader:
356                 p = m->header;
357                 len = m->hend - p;
358                 break;
359         case Qmimeheader:
360                 p = m->mheader;
361                 len = m->mhend - p;
362                 break;
363         case Qreplyto:
364                 p = nil;
365                 if(m->replyto822 != nil){
366                         p = s_to_c(m->replyto822);
367                         len = strlen(p);
368                 } else if(m->from822 != nil){
369                         p = s_to_c(m->from822);
370                         len = strlen(p);
371                 } else if(m->sender822 != nil){
372                         p = s_to_c(m->sender822);
373                         len = strlen(p);
374                 } else if(m->unixfrom != nil){
375                         p = s_to_c(m->unixfrom);
376                         len = strlen(p);
377                 }
378                 break;
379         case Qsender:
380                 if(m->sender822){
381                         p = s_to_c(m->sender822);
382                         len = strlen(p);
383                 }
384                 break;
385         case Qsubject:
386                 p = nil;
387                 if(m->subject822){
388                         p = s_to_c(m->subject822);
389                         len = strlen(p);
390                 }
391                 break;
392         case Qto:
393                 if(m->to822){
394                         p = s_to_c(m->to822);
395                         len = strlen(p);
396                 }
397                 break;
398         case Qtype:
399                 if(m->type){
400                         p = s_to_c(m->type);
401                         len = strlen(p);
402                 }
403                 break;
404         case Qunixdate:
405                 if(m->unixdate){
406                         p = s_to_c(m->unixdate);
407                         len = strlen(p);
408                 }
409                 break;
410         case Qunixheader:
411                 if(m->unixheader){
412                         p = s_to_c(m->unixheader);
413                         len = s_len(m->unixheader);
414                 }
415                 break;
416         case Qdigest:
417                 if(m->sdigest){
418                         p = s_to_c(m->sdigest);
419                         len = strlen(p);
420                 }
421                 break;
422         }
423         *pp = p;
424         return len;
425 }
426
427 int infofields[] = {
428         Qfrom,
429         Qto,
430         Qcc,
431         Qreplyto,
432         Qunixdate,
433         Qsubject,
434         Qtype,
435         Qdisposition,
436         Qfilename,
437         Qdigest,
438         Qbcc,
439         Qinreplyto,
440         Qdate,
441         Qsender,
442         Qmessageid,
443         Qlines,
444         -1,
445 };
446
447 static int
448 readinfo(Message *m, char *buf, long off, int count)
449 {
450         char *p;
451         int len, i, n;
452         String *s;
453
454         s = s_new();
455         len = 0;
456         for(i = 0; len < count && infofields[i] >= 0; i++){
457                 n = fileinfo(m, infofields[i], &p);
458                 s = stringconvert(s, p, n);
459                 s_append(s, "\n");
460                 p = s_to_c(s);
461                 n = strlen(p);
462                 if(off > 0){
463                         if(off >= n){
464                                 off -= n;
465                                 continue;
466                         }
467                         p += off;
468                         n -= off;
469                         off = 0;
470                 }
471                 if(n > count - len)
472                         n = count - len;
473                 if(buf)
474                         memmove(buf+len, p, n);
475                 len += n;
476         }
477         s_free(s);
478         return len;
479 }
480
481 static void
482 mkstat(Dir *d, Mailbox *mb, Message *m, int t)
483 {
484         char *p;
485
486         d->uid = user;
487         d->gid = user;
488         d->muid = user;
489         d->mode = 0444;
490         d->qid.vers = 0;
491         d->qid.type = QTFILE;
492         d->type = 0;
493         d->dev = 0;
494         if(mb != nil && mb->d != nil){
495                 d->atime = mb->d->atime;
496                 d->mtime = mb->d->mtime;
497         } else {
498                 d->atime = time(0);
499                 d->mtime = d->atime;
500         }
501
502         switch(t){
503         case Qtop:
504                 d->name = ".";
505                 d->mode = DMDIR|0555;
506                 d->atime = d->mtime = time(0);
507                 d->length = 0;
508                 d->qid.path = PATH(0, Qtop);
509                 d->qid.type = QTDIR;
510                 break;
511         case Qmbox:
512                 d->name = mb->name;
513                 d->mode = DMDIR|0555;
514                 d->length = 0;
515                 d->qid.path = PATH(mb->id, Qmbox);
516                 d->qid.type = QTDIR;
517                 d->qid.vers = mb->vers;
518                 break;
519         case Qdir:
520                 d->name = m->name;
521                 d->mode = DMDIR|0555;
522                 d->length = 0;
523                 d->qid.path = PATH(m->id, Qdir);
524                 d->qid.type = QTDIR;
525                 break;
526         case Qctl:
527                 d->name = dirtab[t];
528                 d->mode = 0666;
529                 d->atime = d->mtime = time(0);
530                 d->length = 0;
531                 d->qid.path = PATH(0, Qctl);
532                 break;
533         case Qmboxctl:
534                 d->name = dirtab[t];
535                 d->mode = 0222;
536                 d->atime = d->mtime = time(0);
537                 d->length = 0;
538                 d->qid.path = PATH(mb->id, Qmboxctl);
539                 break;
540         case Qinfo:
541                 d->name = dirtab[t];
542                 d->length = readinfo(m, nil, 0, 1<<30);
543                 d->qid.path = PATH(m->id, t);
544                 break;
545         default:
546                 d->name = dirtab[t];
547                 d->length = fileinfo(m, t, &p);
548                 d->qid.path = PATH(m->id, t);
549                 break;
550         }
551 }
552
553 char*
554 rversion(Fid*)
555 {
556         Fid *f;
557
558         if(thdr.msize < 256)
559                 return "max messagesize too small";
560         if(thdr.msize < messagesize)
561                 messagesize = thdr.msize;
562         rhdr.msize = messagesize;
563         if(strncmp(thdr.version, "9P2000", 6) != 0)
564                 return "unknown 9P version";
565         else
566                 rhdr.version = "9P2000";
567         for(f = fids; f; f = f->next)
568                 if(f->busy)
569                         rclunk(f);
570         return nil;
571 }
572
573 char*
574 rauth(Fid*)
575 {
576         return Enoauth;
577 }
578
579 char*
580 rflush(Fid *f)
581 {
582         USED(f);
583         return 0;
584 }
585
586 char*
587 rattach(Fid *f)
588 {
589         f->busy = 1;
590         f->m = nil;
591         f->mb = nil;
592         f->qid.path = PATH(0, Qtop);
593         f->qid.type = QTDIR;
594         f->qid.vers = 0;
595         rhdr.qid = f->qid;
596         if(strcmp(thdr.uname, user) != 0)
597                 return Eperm;
598         return 0;
599 }
600
601 static Fid*
602 doclone(Fid *f, int nfid)
603 {
604         Fid *nf;
605
606         nf = newfid(nfid);
607         if(nf->busy)
608                 return nil;
609         nf->busy = 1;
610         nf->open = 0;
611         nf->m = f->m;
612         nf->mtop = f->mtop;
613         nf->mb = f->mb;
614         if(f->mb != nil)
615                 mboxincref(f->mb);
616         if(f->mtop != nil){
617                 qlock(f->mb);
618                 msgincref(f->mtop);
619                 qunlock(f->mb);
620         }
621         nf->qid = f->qid;
622         return nf;
623 }
624
625 char*
626 dowalk(Fid *f, char *name)
627 {
628         int t;
629         Mailbox *omb, *mb;
630         char *rv, *p;
631         Hash *h;
632
633         t = FILE(f->qid.path);
634
635         rv = Enotexist;
636
637         omb = f->mb;
638         if(omb)
639                 qlock(omb);
640         else
641                 qlock(&mbllock);
642
643         // this must catch everything except . and ..
644 retry:
645         h = hlook(f->qid.path, name);
646         if(h != nil){
647                 f->mb = h->mb;
648                 f->m = h->m;
649                 switch(t){
650                 case Qtop:
651                         if(f->mb != nil)
652                                 mboxincref(f->mb);
653                         break;
654                 case Qmbox:
655                         if(f->m){
656                                 msgincref(f->m);
657                                 f->mtop = f->m;
658                         }
659                         break;
660                 }
661                 f->qid = h->qid;
662                 rv = nil;
663         } else if((p = strchr(name, '.')) != nil && *name != '.'){
664                 *p = 0;
665                 goto retry;
666         }
667
668         if(omb)
669                 qunlock(omb);
670         else
671                 qunlock(&mbllock);
672         if(rv == nil)
673                 return rv;
674
675         if(strcmp(name, ".") == 0)
676                 return nil;
677
678         if(f->qid.type != QTDIR)
679                 return Enotdir;
680
681         if(strcmp(name, "..") == 0){
682                 switch(t){
683                 case Qtop:
684                         f->qid.path = PATH(0, Qtop);
685                         f->qid.type = QTDIR;
686                         f->qid.vers = 0;
687                         break;
688                 case Qmbox:
689                         f->qid.path = PATH(0, Qtop);
690                         f->qid.type = QTDIR;
691                         f->qid.vers = 0;
692                         qlock(&mbllock);
693                         mb = f->mb;
694                         f->mb = nil;
695                         mboxdecref(mb);
696                         qunlock(&mbllock);
697                         break;
698                 case Qdir:
699                         qlock(f->mb);
700                         if(f->m->whole == f->mb->root){
701                                 f->qid.path = PATH(f->mb->id, Qmbox);
702                                 f->qid.type = QTDIR;
703                                 f->qid.vers = f->mb->d->qid.vers;
704                                 msgdecref(f->mb, f->mtop);
705                                 f->m = f->mtop = nil;
706                         } else {
707                                 f->m = f->m->whole;
708                                 f->qid.path = PATH(f->m->id, Qdir);
709                                 f->qid.type = QTDIR;
710                         }
711                         qunlock(f->mb);
712                         break;
713                 }
714                 rv = nil;
715         }
716         return rv;
717 }
718
719 char*
720 rwalk(Fid *f)
721 {
722         Fid *nf;
723         char *rv;
724         int i;
725
726         if(f->open)
727                 return Eisopen;
728
729         rhdr.nwqid = 0;
730         nf = nil;
731
732         /* clone if requested */
733         if(thdr.newfid != thdr.fid){
734                 nf = doclone(f, thdr.newfid);
735                 if(nf == nil)
736                         return "new fid in use";
737                 f = nf;
738         }
739
740         /* if it's just a clone, return */
741         if(thdr.nwname == 0 && nf != nil)
742                 return nil;
743
744         /* walk each element */
745         rv = nil;
746         for(i = 0; i < thdr.nwname; i++){
747                 rv = dowalk(f, thdr.wname[i]);
748                 if(rv != nil){
749                         if(nf != nil)   
750                                 rclunk(nf);
751                         break;
752                 }
753                 rhdr.wqid[i] = f->qid;
754         }
755         rhdr.nwqid = i;
756
757         /* we only error out if no walk  */
758         if(i > 0)
759                 rv = nil;
760
761         return rv;
762 }
763
764 char *
765 ropen(Fid *f)
766 {
767         int file;
768
769         if(f->open)
770                 return Eisopen;
771
772         file = FILE(f->qid.path);
773         if(thdr.mode != OREAD)
774                 if(file != Qctl && file != Qmboxctl)
775                         return Eperm;
776
777         // make sure we've decoded
778         if(file == Qbody){
779                 if(f->m->decoded == 0)
780                         decode(f->m);
781                 if(f->m->converted == 0)
782                         convert(f->m);
783         }
784
785         rhdr.iounit = 0;
786         rhdr.qid = f->qid;
787         f->open = 1;
788         return 0;
789 }
790
791 char *
792 rcreate(Fid*)
793 {
794         return Eperm;
795 }
796
797 int
798 readtopdir(Fid*, uchar *buf, long off, int cnt, int blen)
799 {
800         Dir d;
801         int m, n;
802         long pos;
803         Mailbox *mb;
804
805         n = 0;
806         pos = 0;
807         mkstat(&d, nil, nil, Qctl);
808         m = convD2M(&d, &buf[n], blen);
809         if(off <= pos){
810                 if(m <= BIT16SZ || m > cnt)
811                         return 0;
812                 n += m;
813                 cnt -= m;
814         }
815         pos += m;
816                 
817         for(mb = mbl; mb != nil; mb = mb->next){
818                 mkstat(&d, mb, nil, Qmbox);
819                 m = convD2M(&d, &buf[n], blen-n);
820                 if(off <= pos){
821                         if(m <= BIT16SZ || m > cnt)
822                                 break;
823                         n += m;
824                         cnt -= m;
825                 }
826                 pos += m;
827         }
828         return n;
829 }
830
831 int
832 readmboxdir(Fid *f, uchar *buf, long off, int cnt, int blen)
833 {
834         Dir d;
835         int n, m;
836         long pos;
837         Message *msg;
838
839         n = 0;
840         if(f->mb->ctl){
841                 mkstat(&d, f->mb, nil, Qmboxctl);
842                 m = convD2M(&d, &buf[n], blen);
843                 if(off == 0){
844                         if(m <= BIT16SZ || m > cnt){
845                                 f->fptr = nil;
846                                 return 0;
847                         }
848                         n += m;
849                         cnt -= m;
850                 } else
851                         off -= m;
852         }
853
854         // to avoid n**2 reads of the directory, use a saved finger pointer
855         if(f->mb->vers == f->fvers && off >= f->foff && f->fptr != nil){
856                 msg = f->fptr;
857                 pos = f->foff;
858         } else {
859                 msg = f->mb->root->part;
860                 pos = 0;
861         } 
862
863         for(; cnt > 0 && msg != nil; msg = msg->next){
864                 // act like deleted files aren't there
865                 if(msg->deleted)
866                         continue;
867
868                 mkstat(&d, f->mb, msg, Qdir);
869                 m = convD2M(&d, &buf[n], blen-n);
870                 if(off <= pos){
871                         if(m <= BIT16SZ || m > cnt)
872                                 break;
873                         n += m;
874                         cnt -= m;
875                 }
876                 pos += m;
877         }
878
879         // save a finger pointer for next read of the mbox directory
880         f->foff = pos;
881         f->fptr = msg;
882         f->fvers = f->mb->vers;
883
884         return n;
885 }
886
887 int
888 readmsgdir(Fid *f, uchar *buf, long off, int cnt, int blen)
889 {
890         Dir d;
891         int i, n, m;
892         long pos;
893         Message *msg;
894
895         n = 0;
896         pos = 0;
897         for(i = 0; i < Qmax; i++){
898                 mkstat(&d, f->mb, f->m, i);
899                 m = convD2M(&d, &buf[n], blen-n);
900                 if(off <= pos){
901                         if(m <= BIT16SZ || m > cnt)
902                                 return n;
903                         n += m;
904                         cnt -= m;
905                 }
906                 pos += m;
907         }
908         for(msg = f->m->part; msg != nil; msg = msg->next){
909                 mkstat(&d, f->mb, msg, Qdir);
910                 m = convD2M(&d, &buf[n], blen-n);
911                 if(off <= pos){
912                         if(m <= BIT16SZ || m > cnt)
913                                 break;
914                         n += m;
915                         cnt -= m;
916                 }
917                 pos += m;
918         }
919
920         return n;
921 }
922
923 char*
924 rread(Fid *f)
925 {
926         long off;
927         int t, i, n, cnt;
928         char *p;
929
930         rhdr.count = 0;
931         off = thdr.offset;
932         cnt = thdr.count;
933
934         if(cnt > messagesize - IOHDRSZ)
935                 cnt = messagesize - IOHDRSZ;
936
937         rhdr.data = (char*)mbuf;
938
939         t = FILE(f->qid.path);
940         if(f->qid.type & QTDIR){
941                 if(t == Qtop) {
942                         qlock(&mbllock);
943                         n = readtopdir(f, mbuf, off, cnt, messagesize - IOHDRSZ);
944                         qunlock(&mbllock);
945                 } else if(t == Qmbox) {
946                         qlock(f->mb);
947                         if(off == 0)
948                                 syncmbox(f->mb, 1);
949                         n = readmboxdir(f, mbuf, off, cnt, messagesize - IOHDRSZ);
950                         qunlock(f->mb);
951                 } else if(t == Qmboxctl) {
952                         n = 0;
953                 } else {
954                         n = readmsgdir(f, mbuf, off, cnt, messagesize - IOHDRSZ);
955                 }
956
957                 rhdr.count = n;
958                 return nil;
959         }
960
961         if(FILE(f->qid.path) == Qheader){
962                 rhdr.count = readheader(f->m, (char*)mbuf, off, cnt);
963                 return nil;
964         }
965
966         if(FILE(f->qid.path) == Qinfo){
967                 rhdr.count = readinfo(f->m, (char*)mbuf, off, cnt);
968                 return nil;
969         }
970
971         i = fileinfo(f->m, FILE(f->qid.path), &p);
972         if(off < i){
973                 if((off + cnt) > i)
974                         cnt = i - off;
975                 memmove(mbuf, p + off, cnt);
976                 rhdr.count = cnt;
977         }
978         return nil;
979 }
980
981 char*
982 rwrite(Fid *f)
983 {
984         char *err;
985         char *token[1024];
986         int t, n;
987         String *file;
988
989         t = FILE(f->qid.path);
990         rhdr.count = thdr.count;
991         switch(t){
992         case Qctl:
993                 if(thdr.count == 0)
994                         return Ebadctl;
995                 if(thdr.data[thdr.count-1] == '\n')
996                         thdr.data[thdr.count-1] = 0;
997                 else
998                         thdr.data[thdr.count] = 0;
999                 n = tokenize(thdr.data, token, nelem(token));
1000                 if(n == 0)
1001                         return Ebadctl;
1002                 if(strcmp(token[0], "open") == 0){
1003                         file = s_new();
1004                         switch(n){
1005                         case 1:
1006                                 err = Ebadctl;
1007                                 break;
1008                         case 2:
1009                                 mboxpath(token[1], getlog(), file, 0);
1010                                 err = newmbox(s_to_c(file), nil, 0);
1011                                 break;
1012                         default:
1013                                 mboxpath(token[1], getlog(), file, 0);
1014                                 if(strchr(token[2], '/') != nil)
1015                                         err = "/ not allowed in mailbox name";
1016                                 else
1017                                         err = newmbox(s_to_c(file), token[2], 0);
1018                                 break;
1019                         }
1020                         s_free(file);
1021                         return err;
1022                 }
1023                 if(strcmp(token[0], "close") == 0){
1024                         if(n < 2)
1025                                 return nil;
1026                         freembox(token[1]);
1027                         return nil;
1028                 }
1029                 if(strcmp(token[0], "delete") == 0){
1030                         if(n < 3)
1031                                 return nil;
1032                         delmessages(n-1, &token[1]);
1033                         return nil;
1034                 }
1035                 return Ebadctl;
1036         case Qmboxctl:
1037                 if(f->mb && f->mb->ctl){
1038                         if(thdr.count == 0)
1039                                 return Ebadctl;
1040                         if(thdr.data[thdr.count-1] == '\n')
1041                                 thdr.data[thdr.count-1] = 0;
1042                         else
1043                                 thdr.data[thdr.count] = 0;
1044                         n = tokenize(thdr.data, token, nelem(token));
1045                         if(n == 0)
1046                                 return Ebadctl;
1047                         return (*f->mb->ctl)(f->mb, n, token);
1048                 }
1049         }
1050         return Eperm;
1051 }
1052
1053 char *
1054 rclunk(Fid *f)
1055 {
1056         Mailbox *mb;
1057
1058         f->busy = 0;
1059         f->open = 0;
1060         if(f->mtop != nil){
1061                 qlock(f->mb);
1062                 msgdecref(f->mb, f->mtop);
1063                 qunlock(f->mb);
1064         }
1065         f->m = f->mtop = nil;
1066         mb = f->mb;
1067         if(mb != nil){
1068                 f->mb = nil;
1069                 assert(mb->refs > 0);
1070                 qlock(&mbllock);
1071                 mboxdecref(mb);
1072                 qunlock(&mbllock);
1073         }
1074         f->fid = -1;
1075         return 0;
1076 }
1077
1078 char *
1079 rremove(Fid *f)
1080 {
1081         if(f->m != nil){
1082                 if(f->m->deleted == 0)
1083                         mailplumb(f->mb, f->m, 1);
1084                 f->m->deleted = 1;
1085         }
1086         return rclunk(f);
1087 }
1088
1089 char *
1090 rstat(Fid *f)
1091 {
1092         Dir d;
1093
1094         if(FILE(f->qid.path) == Qmbox){
1095                 qlock(f->mb);
1096                 syncmbox(f->mb, 1);
1097                 qunlock(f->mb);
1098         }
1099         mkstat(&d, f->mb, f->m, FILE(f->qid.path));
1100         rhdr.nstat = convD2M(&d, mbuf, messagesize - IOHDRSZ);
1101         rhdr.stat = mbuf;
1102         return 0;
1103 }
1104
1105 char *
1106 rwstat(Fid*)
1107 {
1108         return Eperm;
1109 }
1110
1111 Fid *
1112 newfid(int fid)
1113 {
1114         Fid *f, *ff;
1115
1116         ff = 0;
1117         for(f = fids; f; f = f->next)
1118                 if(f->fid == fid)
1119                         return f;
1120                 else if(!ff && !f->busy)
1121                         ff = f;
1122         if(ff){
1123                 ff->fid = fid;
1124                 ff->fptr = nil;
1125                 return ff;
1126         }
1127         f = emalloc(sizeof *f);
1128         f->fid = fid;
1129         f->fptr = nil;
1130         f->next = fids;
1131         fids = f;
1132         return f;
1133 }
1134
1135 int
1136 fidmboxrefs(Mailbox *mb)
1137 {
1138         Fid *f;
1139         int refs = 0;
1140
1141         for(f = fids; f; f = f->next){
1142                 if(f->mb == mb)
1143                         refs++;
1144         }
1145         return refs;
1146 }
1147
1148 void
1149 io(void)
1150 {
1151         char *err;
1152         int n;
1153
1154         /* start a process to watch the mailboxes*/
1155         if(plumbing){
1156                 switch(rfork(RFPROC|RFMEM)){
1157                 case -1:
1158                         /* oh well */
1159                         break;
1160                 case 0:
1161                         reader();
1162                         exits(nil);
1163                 default:
1164                         break;
1165                 }
1166         }
1167
1168         for(;;){
1169                 /*
1170                  * reading from a pipe or a network device
1171                  * will give an error after a few eof reads
1172                  * however, we cannot tell the difference
1173                  * between a zero-length read and an interrupt
1174                  * on the processes writing to us,
1175                  * so we wait for the error
1176                  */
1177                 checkmboxrefs();
1178                 n = read9pmsg(mfd[0], mdata, messagesize);
1179                 if(n == 0)
1180                         continue;
1181                 if(n < 0)
1182                         return;
1183                 if(convM2S(mdata, n, &thdr) == 0)
1184                         continue;
1185
1186                 if(debug)
1187                         fprint(2, "%s:<-%F\n", argv0, &thdr);
1188
1189                 rhdr.data = (char*)mdata + messagesize;
1190                 if(!fcalls[thdr.type])
1191                         err = "bad fcall type";
1192                 else
1193                         err = (*fcalls[thdr.type])(newfid(thdr.fid));
1194                 if(err){
1195                         rhdr.type = Rerror;
1196                         rhdr.ename = err;
1197                 }else{
1198                         rhdr.type = thdr.type + 1;
1199                         rhdr.fid = thdr.fid;
1200                 }
1201                 rhdr.tag = thdr.tag;
1202                 if(debug)
1203                         fprint(2, "%s:->%F\n", argv0, &rhdr);/**/
1204                 n = convS2M(&rhdr, mdata, messagesize);
1205                 if(write(mfd[1], mdata, n) != n)
1206                         error("mount write");
1207         }
1208 }
1209
1210 void
1211 reader(void)
1212 {
1213         ulong t;
1214         Dir *d;
1215         Mailbox *mb;
1216
1217         sleep(15*1000);
1218         for(;;){
1219                 t = time(0);
1220                 qlock(&mbllock);
1221                 for(mb = mbl; mb != nil; mb = mb->next){
1222                         assert(mb->refs > 0);
1223                         if(mb->waketime != 0 && t > mb->waketime){
1224                                 qlock(mb);
1225                                 mb->waketime = 0;
1226                                 break;
1227                         }
1228
1229                         if(mb->d == nil || mb->d->name == nil)
1230                                 continue;
1231                         d = dirstat(mb->path);
1232                         if(d == nil)
1233                                 continue;
1234
1235                         qlock(mb);
1236                         if(mb->d)
1237                         if(d->qid.path != mb->d->qid.path
1238                            || d->qid.vers != mb->d->qid.vers){
1239                                 free(d);
1240                                 break;
1241                         }
1242                         qunlock(mb);
1243                         free(d);
1244                 }
1245                 qunlock(&mbllock);
1246                 if(mb != nil){
1247                         syncmbox(mb, 1);
1248                         qunlock(mb);
1249                 } else
1250                         sleep(15*1000);
1251         }
1252 }
1253
1254 int
1255 newid(void)
1256 {
1257         int rv;
1258         static int id;
1259         static Lock idlock;
1260
1261         lock(&idlock);
1262         rv = ++id;
1263         unlock(&idlock);
1264
1265         return rv;
1266 }
1267
1268 void
1269 error(char *s)
1270 {
1271         postnote(PNGROUP, getpid(), "die yankee pig dog");
1272         fprint(2, "%s: %s: %r\n", argv0, s);
1273         exits(s);
1274 }
1275
1276
1277 typedef struct Ignorance Ignorance;
1278 struct Ignorance
1279 {
1280         Ignorance *next;
1281         char    *str;           /* string */
1282         int     partial;        /* true if not exact match */
1283 };
1284 Ignorance *ignorance;
1285
1286 /*
1287  *  read the file of headers to ignore 
1288  */
1289 void
1290 readignore(void)
1291 {
1292         char *p;
1293         Ignorance *i;
1294         Biobuf *b;
1295
1296         if(ignorance != nil)
1297                 return;
1298
1299         b = Bopen("/mail/lib/ignore", OREAD);
1300         if(b == 0)
1301                 return;
1302         while(p = Brdline(b, '\n')){
1303                 p[Blinelen(b)-1] = 0;
1304                 while(*p && (*p == ' ' || *p == '\t'))
1305                         p++;
1306                 if(*p == '#')
1307                         continue;
1308                 i = malloc(sizeof(Ignorance));
1309                 if(i == 0)
1310                         break;
1311                 i->partial = strlen(p);
1312                 i->str = strdup(p);
1313                 if(i->str == 0){
1314                         free(i);
1315                         break;
1316                 }
1317                 i->next = ignorance;
1318                 ignorance = i;
1319         }
1320         Bterm(b);
1321 }
1322
1323 int
1324 ignore(char *p)
1325 {
1326         Ignorance *i;
1327
1328         readignore();
1329         for(i = ignorance; i != nil; i = i->next)
1330                 if(cistrncmp(i->str, p, i->partial) == 0)
1331                         return 1;
1332         return 0;
1333 }
1334
1335 int
1336 hdrlen(char *p, char *e)
1337 {
1338         char *ep;
1339
1340         ep = p;
1341         do {
1342                 ep = strchr(ep, '\n');
1343                 if(ep == nil){
1344                         ep = e;
1345                         break;
1346                 }
1347                 ep++;
1348                 if(ep >= e){
1349                         ep = e;
1350                         break;
1351                 }
1352         } while(*ep == ' ' || *ep == '\t');
1353         return ep - p;
1354 }
1355
1356 // rfc2047 non-ascii: =?charset?q?encoded-text?=
1357 int
1358 rfc2047convert(String *s, char *token, int len)
1359 {
1360         char charset[100], decoded[1024], *e, *x;
1361         int l;
1362
1363         if(len == 0)
1364                 return -1;
1365
1366         e = token+len-2;
1367         token += 2;
1368
1369         x = memchr(token, '?', e-token);
1370         if(x == nil || (l=x-token) >= sizeof charset)
1371                 return -1;
1372         memmove(charset, token, l);
1373         charset[l] = 0;
1374
1375         token = x+1;
1376
1377         // bail if it doesn't fit 
1378         if(e-token > sizeof(decoded)-1)
1379                 return -1;
1380
1381         // bail if we don't understand the encoding
1382         if(cistrncmp(token, "b?", 2) == 0){
1383                 token += 2;
1384                 len = dec64((uchar*)decoded, sizeof(decoded), token, e-token);
1385                 decoded[len] = 0;
1386         } else if(cistrncmp(token, "q?", 2) == 0){
1387                 token += 2;
1388                 len = decquoted(decoded, token, e, 1);
1389                 if(len > 0 && decoded[len-1] == '\n')
1390                         len--;
1391                 decoded[len] = 0;
1392         } else
1393                 return -1;
1394
1395         if(xtoutf(charset, &x, decoded, decoded+len) <= 0)
1396                 s_append(s, decoded);
1397         else {
1398                 s_append(s, x);
1399                 free(x);
1400         }
1401         return 0;
1402 }
1403
1404 char*
1405 rfc2047start(char *start, char *end)
1406 {
1407         int quests;
1408
1409         if(*--end != '=')
1410                 return nil;
1411         if(*--end != '?')
1412                 return nil;
1413
1414         quests = 0;
1415         for(end--; end >= start; end--){
1416                 switch(*end){
1417                 case '=':
1418                         if(quests == 3 && *(end+1) == '?')
1419                                 return end;
1420                         break;
1421                 case '?':
1422                         ++quests;
1423                         break;
1424                 case ' ':
1425                 case '\t':
1426                 case '\n':
1427                 case '\r':
1428                         /* can't have white space in a token */
1429                         return nil;
1430                 }
1431         }
1432         return nil;
1433 }
1434
1435 // convert a header line
1436 String*
1437 stringconvert(String *s, char *uneaten, int len)
1438 {
1439         char *token, *p, *e;
1440
1441         s = s_reset(s);
1442         p = uneaten;
1443         for(e = p+len; p < e; ){
1444                 while(*p++ == '=' && (token = rfc2047start(uneaten, p))){
1445                         s_nappend(s, uneaten, token-uneaten);
1446                         if(rfc2047convert(s, token, p - token) < 0)
1447                                 s_nappend(s, token, p - token);
1448                         uneaten = p;
1449                         for(; p<e && isspace(*p);)
1450                                 p++;
1451                         if(p+2 < e && p[0] == '=' && p[1] == '?')
1452                                 uneaten = p;    // paste
1453                 }
1454         }
1455         if(p > uneaten)
1456                 s_nappend(s, uneaten, p-uneaten);
1457         return s;
1458 }
1459
1460 int
1461 readheader(Message *m, char *buf, int off, int cnt)
1462 {
1463         char *p, *e;
1464         int n, ns;
1465         char *to = buf;
1466         String *s;
1467
1468         p = m->header;
1469         e = m->hend;
1470         s = nil;
1471
1472         // copy in good headers
1473         while(cnt > 0 && p < e){
1474                 n = hdrlen(p, e);
1475                 if(ignore(p)){
1476                         p += n;
1477                         continue;
1478                 }
1479
1480                 // rfc2047 processing
1481                 s = stringconvert(s, p, n);
1482                 ns = s_len(s);
1483                 if(off > 0){
1484                         if(ns <= off){
1485                                 off -= ns;
1486                                 p += n;
1487                                 continue;
1488                         }
1489                         ns -= off;
1490                 }
1491                 if(ns > cnt)
1492                         ns = cnt;
1493                 memmove(to, s_to_c(s)+off, ns);
1494                 to += ns;
1495                 p += n;
1496                 cnt -= ns;
1497                 off = 0;
1498         }
1499
1500         s_free(s);
1501         return to - buf;
1502 }
1503
1504 int
1505 headerlen(Message *m)
1506 {
1507         char buf[1024];
1508         int i, n;
1509
1510         if(m->hlen >= 0)
1511                 return m->hlen;
1512         for(n = 0; ; n += i){
1513                 i = readheader(m, buf, n, sizeof(buf));
1514                 if(i <= 0)
1515                         break;
1516         }
1517         m->hlen = n;
1518         return n;
1519 }
1520
1521 QLock hashlock;
1522
1523 uint
1524 hash(ulong ppath, char *name)
1525 {
1526         uchar *p;
1527         uint h;
1528
1529         h = 0;
1530         for(p = (uchar*)name; *p; p++)
1531                 h = h*7 + *p;
1532         h += ppath;
1533
1534         return h % Hsize;
1535 }
1536
1537 Hash*
1538 hlook(ulong ppath, char *name)
1539 {
1540         int h;
1541         Hash *hp;
1542
1543         qlock(&hashlock);
1544         h = hash(ppath, name);
1545         for(hp = htab[h]; hp != nil; hp = hp->next)
1546                 if(ppath == hp->ppath && strcmp(name, hp->name) == 0){
1547                         qunlock(&hashlock);
1548                         return hp;
1549                 }
1550         qunlock(&hashlock);
1551         return nil;
1552 }
1553
1554 void
1555 henter(ulong ppath, char *name, Qid qid, Message *m, Mailbox *mb)
1556 {
1557         int h;
1558         Hash *hp, **l;
1559
1560         qlock(&hashlock);
1561         h = hash(ppath, name);
1562         for(l = &htab[h]; *l != nil; l = &(*l)->next){
1563                 hp = *l;
1564                 if(ppath == hp->ppath && strcmp(name, hp->name) == 0){
1565                         hp->m = m;
1566                         hp->mb = mb;
1567                         hp->qid = qid;
1568                         qunlock(&hashlock);
1569                         return;
1570                 }
1571         }
1572
1573         *l = hp = emalloc(sizeof(*hp));
1574         hp->m = m;
1575         hp->mb = mb;
1576         hp->qid = qid;
1577         hp->name = name;
1578         hp->ppath = ppath;
1579         qunlock(&hashlock);
1580 }
1581
1582 void
1583 hfree(ulong ppath, char *name)
1584 {
1585         int h;
1586         Hash *hp, **l;
1587
1588         qlock(&hashlock);
1589         h = hash(ppath, name);
1590         for(l = &htab[h]; *l != nil; l = &(*l)->next){
1591                 hp = *l;
1592                 if(ppath == hp->ppath && strcmp(name, hp->name) == 0){
1593                         hp->mb = nil;
1594                         *l = hp->next;
1595                         free(hp);
1596                         break;
1597                 }
1598         }
1599         qunlock(&hashlock);
1600 }
1601
1602 int
1603 hashmboxrefs(Mailbox *mb)
1604 {
1605         int h;
1606         Hash *hp;
1607         int refs = 0;
1608
1609         qlock(&hashlock);
1610         for(h = 0; h < Hsize; h++){
1611                 for(hp = htab[h]; hp != nil; hp = hp->next)
1612                         if(hp->mb == mb)
1613                                 refs++;
1614         }
1615         qunlock(&hashlock);
1616         return refs;
1617 }
1618
1619 void
1620 checkmboxrefs(void)
1621 {
1622         int f, refs;
1623         Mailbox *mb;
1624
1625         qlock(&mbllock);
1626         for(mb=mbl; mb; mb=mb->next){
1627                 qlock(mb);
1628                 refs = (f=fidmboxrefs(mb))+1;
1629                 if(refs != mb->refs){
1630                         fprint(2, "mbox %s %s ref mismatch actual %d (%d+1) expected %d\n", mb->name, mb->path, refs, f, mb->refs);
1631                         abort();
1632                 }
1633                 qunlock(mb);
1634         }
1635         qunlock(&mbllock);
1636 }
1637
1638 void
1639 post(char *name, char *envname, int srvfd)
1640 {
1641         int fd;
1642         char buf[32];
1643
1644         fd = create(name, OWRITE, 0600);
1645         if(fd < 0)
1646                 error("post failed");
1647         sprint(buf, "%d",srvfd);
1648         if(write(fd, buf, strlen(buf)) != strlen(buf))
1649                 error("srv write");
1650         close(fd);
1651         putenv(envname, name);
1652 }