| /* |
| * 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, |
| Qfilename, |
| Qraw, |
| Qrawheader, |
| Qrawbody, |
| Qmimeheader, |
| |
| /* part numbers - same order as struct Part */ |
| Qsize, |
| Qlines, |
| |
| /* other message files */ |
| Qbody, |
| Qheader, |
| Qdigest, |
| Qdisposition, |
| 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; |
| |
| #ifdef PLAN9PORT |
| void |
| responderror(Req *r) |
| { |
| char e[ERRMAX]; |
| |
| rerrstr(e, sizeof e); |
| respond(r, e); |
| } |
| #endif |
| |
| 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; i<nelem(typenames); i++) |
| if(typenames[i].type == t) |
| return typenames[i].name; |
| return "???"; |
| } |
| |
| int |
| typeofname(char *name) |
| { |
| int i; |
| |
| for(i=0; i<nelem(typenames); i++) |
| if(strcmp(typenames[i].name, name) == 0) |
| return typenames[i].type; |
| return 0; |
| } |
| |
| static void |
| fsattach(Req *r) |
| { |
| r->fid->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<nf; i+=2){ |
| if(f[i][0]) |
| fmtprint(fmt, "%s%s <%s>", 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; |
| |
| USED(q); |
| 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 Qfilename: |
| 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; i<nelem(flagtab); i++){ |
| if(msg->flags&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 %ud %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); |
| if(part->filename) |
| fmtprint(&fmt, "filename %s\n", part->filename); |
| 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); |
| fmtprint(&fmt, "Subject: %s\n", 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); |
| i--; |
| 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, |
| Qfilename, |
| Qcharset, |
| 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; i<nf; i++) |
| if((m[nm] = msgbyid(box, atoi(f[i]))) != nil) |
| nm++; |
| *mm = m; |
| return nm; |
| } |
| |
| static void |
| fswrite(Req *r) |
| { |
| int i, j, c, type, flag, unflag, flagset, f, reset; |
| Box *box; |
| Msg *msg; |
| Part *part; |
| Cmdbuf *cb; |
| Cmdtab *ct; |
| Msg **m; |
| int nm; |
| Fmt fmt; |
| |
| r->ofcall.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; i<cb->nf; i++){ |
| f = 0; |
| c = cb->f[i][0]; |
| if(c == '+' || c == '-') |
| cb->f[i]++; |
| for(j=0; j<nelem(flagtab); j++){ |
| if(strcmp(flagtab[j].name, cb->f[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; i<nm; i++){ |
| if(i>0) |
| 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; |
| |
| USED(v); |
| 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); |
| } |
| |