/* * Mail file system. * * Serve the bulk of requests out of memory, so they can * be in the main loop (will never see their flushes). * Some requests do block and they get handled in * separate threads. They're all okay to give up on * early, though, so we just respond saying interrupted * and then when they finish, silently discard the request. TO DO: decode subject, etc. decode body digest disposition filename ctl messages fetch mail on demand */ #include "a.h" enum { /* directories */ Qroot, Qbox, Qmsg, /* control files */ Qctl, Qboxctl, Qsearch, /* message header - same order as struct Hdr */ Qdate, Qsubject, Qfrom, Qsender, Qreplyto, Qto, Qcc, Qbcc, Qinreplyto, Qmessageid, /* part data - same order as stuct Part */ Qtype, Qidstr, Qdesc, Qencoding, /* only here temporarily! */ Qcharset, Qraw, Qrawheader, Qrawbody, Qmimeheader, /* part numbers - same order as struct Part */ Qsize, Qlines, /* other message files */ Qbody, Qheader, Qdigest, Qdisposition, Qfilename, Qflags, Qinfo, Qrawunix, Qunixdate, Qunixheader, Qfile0 = Qbody, Qnfile = Qunixheader+1-Qfile0, }; static char Egreg[] = "gone postal"; static char Enobox[] = "no such mailbox"; static char Enomsg[] = "no such message"; static char Eboxgone[] = "mailbox not available"; static char Emsggone[] = "message not available"; static char Eperm[] = "permission denied"; static char Ebadctl[] = "bad control message"; Channel *fsreqchan; Srv fs; Qid rootqid; ulong t0; void responderror(Req *r) { char e[ERRMAX]; rerrstr(e, sizeof e); respond(r, e); } int qtype(Qid q) { return q.path&0x3F; } int qboxid(Qid q) { return (q.path>>40)&0xFFFF; } int qmsgid(Qid q) { return ((q.path>>32)&0xFF000000) | ((q.path>>16)&0xFFFFFF); } int qpartid(Qid q) { return ((q.path>>6)&0x3FF); } Qid qid(int ctl, Box *box, Msg *msg, Part *part) { Qid q; q.type = 0; if(ctl == Qroot || ctl == Qbox || ctl == Qmsg) q.type = QTDIR; q.path = (vlong)((msg ? msg->id : 0)&0xFF000000)<<32; q.path |= (vlong)((msg ? msg->id : 0)&0xFFFFFF)<<16; q.path |= (vlong)((box ? box->id : 0)&0xFFFF)<<40; q.path |= ((part ? part->ix : 0)&0x3FF)<<6; q.path |= ctl&0x3F; q.vers = box ? box->validity : 0; return q; } int parseqid(Qid q, Box **box, Msg **msg, Part **part) { *msg = nil; *part = nil; *box = boxbyid(qboxid(q)); if(*box){ *msg = msgbyid(*box, qmsgid(q)); } if(*msg) *part = partbyid(*msg, qpartid(q)); return qtype(q); } static struct { int type; char *name; } typenames[] = { Qbody, "body", Qbcc, "bcc", Qcc, "cc", Qdate, "date", Qfilename, "filename", Qflags, "flags", Qfrom, "from", Qheader, "header", Qinfo, "info", Qinreplyto, "inreplyto", Qlines, "lines", Qmimeheader, "mimeheader", Qmessageid, "messageid", Qraw, "raw", Qrawunix, "rawunix", Qrawbody, "rawbody", Qrawheader, "rawheader", Qreplyto, "replyto", Qsender, "sender", Qsubject, "subject", Qto, "to", Qtype, "type", Qunixdate, "unixdate", Qunixheader, "unixheader", Qidstr, "idstr", Qdesc, "desc", Qencoding, "encoding", Qcharset, "charset", }; char* nameoftype(int t) { int i; for(i=0; ifid->qid = rootqid; r->ofcall.qid = rootqid; respond(r, nil); } static int isnumber(char *s) { int n; if(*s < '1' || *s > '9') return 0; n = strtol(s, &s, 10); if(*s != 0) return 0; return n; } static char* fswalk1(Fid *fid, char *name, void *arg) { int a, type; Box *b, *box; Msg *msg; Part *p, *part; USED(arg); switch(type = parseqid(fid->qid, &box, &msg, &part)){ case Qroot: if(strcmp(name, "..") == 0) return nil; if(strcmp(name, "ctl") == 0){ fid->qid = qid(Qctl, nil, nil, nil); return nil; } if((box = boxbyname(name)) != nil){ fid->qid = qid(Qbox, box, nil, nil); return nil; } break; case Qbox: /* * Would be nice if .. could work even if the box is gone, * but we don't know how deep the directory was. */ if(box == nil) return Eboxgone; if(strcmp(name, "..") == 0){ if((box = box->parent) == nil){ fid->qid = rootqid; return nil; } fid->qid = qid(Qbox, box, nil, nil); return nil; } if(strcmp(name, "ctl") == 0){ fid->qid = qid(Qboxctl, box, nil, nil); return nil; } if(strcmp(name, "search") == 0){ fid->qid = qid(Qsearch, box, nil, nil); return nil; } if((b = subbox(box, name)) != nil){ fid->qid = qid(Qbox, b, nil, nil); return nil; } if((a = isnumber(name)) != 0){ if((msg = msgbyid(box, a)) == nil){ return Enomsg; } fid->qid = qid(Qmsg, box, msg, nil); return nil; } break; case Qmsg: if(strcmp(name, "..") == 0){ if(part == msg->part[0]){ fid->qid = qid(Qbox, box, nil, nil); return nil; } fid->qid = qid(Qmsg, box, msg, part->parent); return nil; } if((type = typeofname(name)) > 0){ /* XXX - should check that type makes sense (see msggen) */ fid->qid = qid(type, box, msg, part); return nil; } if((a = isnumber(name)) != 0){ if((p = subpart(part, a-1)) != nil){ fid->qid = qid(Qmsg, box, msg, p); return nil; } } break; } return "not found"; } static void fswalk(Req *r) { walkandclone(r, fswalk1, nil, nil); } static struct { int flag; char *name; } flagtab[] = { FlagJunk, "junk", FlagNonJunk, "notjunk", FlagReplied, "replied", FlagFlagged, "flagged", // FlagDeleted, "deleted", FlagDraft, "draft", FlagSeen, "seen", }; static void addaddrs(Fmt *fmt, char *prefix, char *addrs) { char **f; int i, nf, inquote; char *p, *sep; if(addrs == nil) return; addrs = estrdup(addrs); nf = 0; inquote = 0; for(p=addrs; *p; p++){ if(*p == ' ' && !inquote) nf++; if(*p == '\'') inquote = !inquote; } nf += 10; f = emalloc(nf*sizeof f[0]); nf = tokenize(addrs, f, nf); fmtprint(fmt, "%s:", prefix); sep = " "; for(i=0; i+1", sep, f[i], f[i+1]); else fmtprint(fmt, "%s%s", sep, f[i+1]); sep = ", "; } fmtprint(fmt, "\n"); free(addrs); } static void mkbody(Part *p, Qid q) { char *t; int len; if(p->msg->part[0] == p) t = p->rawbody; else t = p->raw; if(t == nil) return; len = -1; if(p->encoding && cistrcmp(p->encoding, "quoted-printable") == 0) t = decode(QuotedPrintable, t, &len); else if(p->encoding && cistrcmp(p->encoding, "base64") == 0) t = decode(Base64, t, &len); else t = estrdup(t); if(p->charset){ t = tcs(p->charset, t); len = -1; } p->body = t; if(len == -1) p->nbody = strlen(t); else p->nbody = len; } static Qid ZQ; static int filedata(int type, Box *box, Msg *msg, Part *part, char **pp, int *len, int *freeme, int force, Qid q) { int i, inquote, n, t; char *from, *s; static char buf[256]; Fmt fmt; *pp = nil; *freeme = 0; if(len) *len = -1; if(msg == nil || part == nil){ werrstr(Emsggone); return -1; } switch(type){ case Qdate: case Qsubject: case Qfrom: case Qsender: case Qreplyto: case Qto: case Qcc: case Qbcc: case Qinreplyto: case Qmessageid: if(part->hdr == nil){ werrstr(Emsggone); return -1; } *pp = ((char**)&part->hdr->date)[type-Qdate]; return 0; case Qunixdate: strcpy(buf, ctime(msg->date)); *pp = buf; return 0; case Qunixheader: if(part->hdr == nil){ werrstr(Emsggone); return -1; } from = part->hdr->from; if(from == nil) from = "???"; else{ inquote = 0; for(; *from; from++){ if(*from == '\'') inquote = !inquote; if(!inquote && *from == ' '){ from++; break; } } if(*from == 0) from = part->hdr->from; } n = snprint(buf, sizeof buf, "From %s %s", from, ctime(msg->date)); if(n+1 < sizeof buf){ *pp = buf; return 0; } fmtstrinit(&fmt); fmtprint(&fmt, "From %s %s", from, ctime(msg->date)); s = fmtstrflush(&fmt); if(s){ *pp = s; *freeme = 1; }else *pp = buf; return 0; case Qtype: case Qidstr: case Qdesc: case Qencoding: case Qcharset: case Qraw: case Qrawheader: case Qrawbody: case Qmimeheader: *pp = ((char**)&part->type)[type-Qtype]; if(*pp == nil && force){ switch(type){ case Qraw: imapfetchraw(imap, part); break; case Qrawheader: imapfetchrawheader(imap, part); break; case Qrawbody: imapfetchrawbody(imap, part); break; case Qmimeheader: imapfetchrawmime(imap, part); break; default: return 0; } /* * We ran fetchsomething, which might have changed * the mailbox contents. Msg might even be gone. */ t = parseqid(q, &box, &msg, &part); if(t != type || msg == nil || part == nil) return 0; *pp = ((char**)&part->type)[type-Qtype]; } return 0; case Qbody: if(part->body){ *pp = part->body; if(len) *len = part->nbody; return 0; } if(!force) return 0; if(part->rawbody == nil){ if(part->msg->part[0] == part) imapfetchrawbody(imap, part); else imapfetchraw(imap, part); t = parseqid(q, &box, &msg, &part); if(t != type || msg == nil || part == nil) return 0; } mkbody(part, q); *pp = part->body; if(len) *len = part->nbody; return 0; case Qsize: case Qlines: n = ((uint*)&part->size)[type-Qsize]; snprint(buf, sizeof buf, "%d", n); *pp = buf; return 0; case Qflags: s = buf; *s = 0; for(i=0; iflags&flagtab[i].flag){ if(s > buf) *s++ = ' '; strcpy(s, flagtab[i].name); s += strlen(s); } } *pp = buf; return 0; case Qinfo: fmtstrinit(&fmt); if(part == msg->part[0]){ if(msg->date) fmtprint(&fmt, "unixdate %lud %s", msg->date, ctime(msg->date)); if(msg->flags){ filedata(Qflags, box, msg, part, pp, nil, freeme, 0, ZQ); fmtprint(&fmt, "flags %s\n", buf); } } if(part->hdr){ if(part->hdr->digest) fmtprint(&fmt, "digest %s\n", part->hdr->digest); if(part->hdr->from) fmtprint(&fmt, "from %s\n", part->hdr->from); if(part->hdr->to) fmtprint(&fmt, "to %s\n", part->hdr->to); if(part->hdr->cc) fmtprint(&fmt, "cc %s\n", part->hdr->cc); if(part->hdr->replyto) fmtprint(&fmt, "replyto %s\n", part->hdr->replyto); if(part->hdr->bcc) fmtprint(&fmt, "bcc %s\n", part->hdr->bcc); if(part->hdr->inreplyto) fmtprint(&fmt, "inreplyto %s\n", part->hdr->inreplyto); if(part->hdr->date) fmtprint(&fmt, "date %s\n", part->hdr->date); if(part->hdr->sender) fmtprint(&fmt, "sender %s\n", part->hdr->sender); if(part->hdr->messageid) fmtprint(&fmt, "messageid %s\n", part->hdr->messageid); if(part->hdr->subject) fmtprint(&fmt, "subject %s\n", part->hdr->subject); } if(part->type) fmtprint(&fmt, "type %s\n", part->type); if(part->lines) fmtprint(&fmt, "lines %d\n", part->lines); // fmtprint(&fmt, "disposition %s\n", "" /* disposition */); // fmtprint(&fmt, "filename %s\n", "" /* filename */); // fmtprint(&fmt, "digest %s\n", "" /* digest */); s = fmtstrflush(&fmt); if(s == nil) s = estrdup(""); *freeme = 1; *pp = s; return 0; case Qheader: if(part->hdr == nil) return 0; fmtstrinit(&fmt); if(part == msg->part[0]) fmtprint(&fmt, "Date: %s", ctime(msg->date)); else fmtprint(&fmt, "Date: %s\n", part->hdr->date); addaddrs(&fmt, "To", part->hdr->to); addaddrs(&fmt, "From", part->hdr->from); if(part->hdr->from==nil || (part->hdr->sender && strcmp(part->hdr->sender, part->hdr->from) != 0)) addaddrs(&fmt, "Sender", part->hdr->sender); if(part->hdr->from==nil || (part->hdr->replyto && strcmp(part->hdr->replyto, part->hdr->from) != 0)) addaddrs(&fmt, "Reply-To", part->hdr->replyto); addaddrs(&fmt, "Subject", part->hdr->subject); s = fmtstrflush(&fmt); if(s == nil) s = estrdup(""); *freeme = 1; *pp = s; return 0; default: werrstr(Egreg); return -1; } } int filldir(Dir *d, int type, Box *box, Msg *msg, Part *part) { int freeme, len; char *s; memset(d, 0, sizeof *d); if(box){ d->atime = box->time; d->mtime = box->time; }else{ d->atime = t0; d->mtime = t0; } d->uid = estrdup9p("upas"); d->gid = estrdup9p("upas"); d->muid = estrdup9p("upas"); d->qid = qid(type, box, msg, part); switch(type){ case Qroot: case Qbox: case Qmsg: d->mode = 0555|DMDIR; if(box && !(box->flags&FlagNoInferiors)) d->mode = 0775|DMDIR; break; case Qctl: case Qboxctl: d->mode = 0222; break; case Qsearch: d->mode = 0666; break; case Qflags: d->mode = 0666; goto msgfile; default: d->mode = 0444; msgfile: if(filedata(type, box, msg, part, &s, &len, &freeme, 0, ZQ) >= 0){ if(s){ if(len == -1) d->length = strlen(s); else d->length = len; if(freeme) free(s); } }else if(type == Qraw && msg && part == msg->part[0]) d->length = msg->size; break; } switch(type){ case Qroot: d->name = estrdup9p("/"); break; case Qbox: if(box == nil){ werrstr(Enobox); return -1; } d->name = estrdup9p(box->elem); break; case Qmsg: if(msg == nil){ werrstr(Enomsg); return -1; } if(part == nil || part == msg->part[0]) d->name = esmprint("%d", msg->id); else d->name = esmprint("%d", part->pix+1); break; case Qctl: case Qboxctl: d->name = estrdup9p("ctl"); break; case Qsearch: d->name = estrdup9p("search"); break; default: d->name = estrdup9p(nameoftype(type)); break; } return 0; } static void fsstat(Req *r) { int type; Box *box; Msg *msg; Part *part; type = parseqid(r->fid->qid, &box, &msg, &part); if(filldir(&r->d, type, box, msg, part) < 0) responderror(r); else respond(r, nil); } int rootgen(int i, Dir *d, void *aux) { USED(aux); if(i == 0) return filldir(d, Qctl, nil, nil, nil); i--; if(i < rootbox->nsub) return filldir(d, Qbox, rootbox->sub[i], nil, nil); return -1; } int boxgen(int i, Dir *d, void *aux) { Box *box; box = aux; if(i == 0) return filldir(d, Qboxctl, box, nil, nil); i--; if(i == 0) return filldir(d, Qsearch, box, nil, nil); if(i < box->nsub) return filldir(d, Qbox, box->sub[i], nil, nil); i -= box->nsub; if(i < box->nmsg) return filldir(d, Qmsg, box, box->msg[i], nil); return -1; } static int msgdir[] = { Qtype, Qbody, Qbcc, Qcc, Qdate, Qflags, Qfrom, Qheader, Qinfo, Qinreplyto, Qlines, Qmimeheader, Qmessageid, Qraw, Qrawunix, Qrawbody, Qrawheader, Qreplyto, Qsender, Qsubject, Qto, Qunixdate, Qunixheader }; static int mimemsgdir[] = { Qtype, Qbody, Qbcc, Qcc, Qdate, Qfrom, Qheader, Qinfo, Qinreplyto, Qlines, Qmimeheader, Qmessageid, Qraw, Qrawunix, Qrawbody, Qrawheader, Qreplyto, Qsender, Qsubject, Qto, }; static int mimedir[] = { Qtype, Qbody, Qmimeheader, Qraw, }; int msggen(int i, Dir *d, void *aux) { Box *box; Msg *msg; Part *part; part = aux; msg = part->msg; box = msg->box; if(part->ix == 0){ if(i < nelem(msgdir)) return filldir(d, msgdir[i], box, msg, part); i -= nelem(msgdir); }else if(part->type && strcmp(part->type, "message/rfc822") == 0){ if(i < nelem(mimemsgdir)) return filldir(d, mimemsgdir[i], box, msg, part); i -= nelem(mimemsgdir); }else{ if(i < nelem(mimedir)) return filldir(d, mimedir[i], box, msg, part); i -= nelem(mimedir); } if(i < part->nsub) return filldir(d, Qmsg, box, msg, part->sub[i]); return -1; } enum { CMhangup, }; static Cmdtab ctltab[] = { CMhangup, "hangup", 2, }; enum { CMdelete, CMrefresh, CMreplied, CMread, CMsave, CMjunk, CMnonjunk, }; static Cmdtab boxctltab[] = { CMdelete, "delete", 0, CMrefresh, "refresh", 1, CMreplied, "replied", 0, CMread, "read", 0, CMsave, "save", 0, CMjunk, "junk", 0, CMnonjunk, "nonjunk", 0, }; static void fsread(Req *r) { char *s; int type, len, freeme; Box *box; Msg *msg; Part *part; switch(type = parseqid(r->fid->qid, &box, &msg, &part)){ case Qroot: dirread9p(r, rootgen, nil); respond(r, nil); return; case Qbox: if(box == nil){ respond(r, Eboxgone); return; } if(box->nmsg == 0) imapcheckbox(imap, box); parseqid(r->fid->qid, &box, &msg, &part); if(box == nil){ respond(r, Eboxgone); return; } dirread9p(r, boxgen, box); respond(r, nil); return; case Qmsg: if(msg == nil || part == nil){ respond(r, Emsggone); return; } dirread9p(r, msggen, part); respond(r, nil); return; case Qctl: case Qboxctl: respond(r, Egreg); return; case Qsearch: readstr(r, r->fid->aux); respond(r, nil); return; default: if(filedata(type, box, msg, part, &s, &len, &freeme, 1, r->fid->qid) < 0){ responderror(r); return; } if(s && len == -1) len = strlen(s); readbuf(r, s, len); if(freeme) free(s); respond(r, nil); return; } } int mkmsglist(Box *box, char **f, int nf, Msg ***mm) { int i, nm; Msg **m; m = emalloc(nf*sizeof m[0]); nm = 0; for(i=0; iofcall.count = r->ifcall.count; switch(type = parseqid(r->fid->qid, &box, &msg, &part)){ default: respond(r, Egreg); break; case Qctl: cb = parsecmd(r->ifcall.data, r->ifcall.count); if((ct = lookupcmd(cb, ctltab, nelem(ctltab))) == nil){ respondcmderror(r, cb, "unknown message"); free(cb); return; } r->ofcall.count = r->ifcall.count; switch(ct->index){ case CMhangup: imaphangup(imap, atoi(cb->f[1])); respond(r, nil); break; default: respond(r, Egreg); break; } free(cb); return; case Qboxctl: cb = parsecmd(r->ifcall.data, r->ifcall.count); if((ct = lookupcmd(cb, boxctltab, nelem(boxctltab))) == nil){ respondcmderror(r, cb, "bad message"); free(cb); return; } r->ofcall.count = r->ifcall.count; switch(ct->index){ case CMsave: if(cb->nf <= 2){ respondcmderror(r, cb, Ebadctl); break; } nm = mkmsglist(box, cb->f+2, cb->nf-2, &m); if(nm != cb->nf-2){ // free(m); respond(r, Enomsg); break; } if(nm > 0 && imapcopylist(imap, cb->f[1], m, nm) < 0) responderror(r); else respond(r, nil); free(m); break; case CMjunk: flag = FlagJunk; goto flagit; case CMnonjunk: flag = FlagNonJunk; goto flagit; case CMreplied: flag = FlagReplied; goto flagit; case CMread: flag = FlagSeen; flagit: if(cb->nf <= 1){ respondcmderror(r, cb, Ebadctl); break; } nm = mkmsglist(box, cb->f+1, cb->nf-1, &m); if(nm != cb->nf-1){ free(m); respond(r, Enomsg); break; } if(nm > 0 && imapflaglist(imap, +1, flag, m, nm) < 0) responderror(r); else respond(r, nil); free(m); break; case CMrefresh: imapcheckbox(imap, box); respond(r, nil); break; case CMdelete: if(cb->nf <= 1){ respondcmderror(r, cb, Ebadctl); break; } nm = mkmsglist(box, cb->f+1, cb->nf-1, &m); if(nm > 0 && imapremovelist(imap, m, nm) < 0) responderror(r); else respond(r, nil); free(m); break; default: respond(r, Egreg); break; } free(cb); return; case Qflags: if(msg == nil){ respond(r, Enomsg); return; } cb = parsecmd(r->ifcall.data, r->ifcall.count); flag = 0; unflag = 0; flagset = 0; reset = 0; for(i=0; inf; i++){ f = 0; c = cb->f[i][0]; if(c == '+' || c == '-') cb->f[i]++; for(j=0; jf[i]) == 0){ f = flagtab[j].flag; break; } } if(f == 0){ respondcmderror(r, cb, "unknown flag %s", cb->f[i]); free(cb); return; } if(c == '+') flag |= f; else if(c == '-') unflag |= f; else flagset |= f; } free(cb); if((flagset!=0)+(unflag!=0)+(flag!=0) != 1){ respondcmderror(r, cb, Ebadctl); return; } if(flag) i = 1; else if(unflag){ i = -1; flag = unflag; }else{ i = 0; flag = flagset; } if(imapflaglist(imap, i, flag, &msg, 1) < 0) responderror(r); else respond(r, nil); return; case Qsearch: if(box == nil){ respond(r, Eboxgone); return; } fmtstrinit(&fmt); nm = imapsearchbox(imap, box, r->ifcall.data, &m); for(i=0; i0) fmtrune(&fmt, ' '); fmtprint(&fmt, "%d", m[i]->id); } free(r->fid->aux); r->fid->aux = fmtstrflush(&fmt); respond(r, nil); return; } } static void fsopen(Req *r) { switch(qtype(r->fid->qid)){ case Qctl: case Qboxctl: if((r->ifcall.mode&~OTRUNC) != OWRITE){ respond(r, Eperm); return; } respond(r, nil); return; case Qflags: case Qsearch: if((r->ifcall.mode&~OTRUNC) > ORDWR){ respond(r, Eperm); return; } respond(r, nil); return; default: if(r->ifcall.mode != OREAD){ respond(r, Eperm); return; } respond(r, nil); return; } } static void fsflush(Req *r) { /* * We only handle reads and writes outside the main loop, * so we must be flushing one of those. In both cases it's * okay to just ignore the results of the request, whenever * they're ready. */ incref(&r->oldreq->ref); respond(r->oldreq, "interrupted"); respond(r, nil); } static void fsthread(void *v) { Req *r; r = v; switch(r->ifcall.type){ case Tread: fsread(r); break; case Twrite: fswrite(r); break; } } static void fsrecv(void *v) { Req *r; while((r = recvp(fsreqchan)) != nil){ switch(r->ifcall.type){ case Tattach: fsattach(r); break; case Tflush: fsflush(r); break; case Topen: fsopen(r); break; case Twalk: fswalk(r); break; case Tstat: fsstat(r); break; default: threadcreate(fsthread, r, STACK); break; } } } static void fssend(Req *r) { sendp(fsreqchan, r); } static void fsdestroyfid(Fid *f) { free(f->aux); } void fsinit0(void) /* bad planning - clash with lib9pclient */ { t0 = time(0); fs.attach = fssend; fs.flush = fssend; fs.open = fssend; fs.walk = fssend; fs.read = fssend; fs.write = fssend; fs.stat = fssend; fs.destroyfid = fsdestroyfid; rootqid = qid(Qroot, nil, nil, nil); fsreqchan = chancreate(sizeof(void*), 0); mailthread(fsrecv, nil); }