This port is the work of David du Colombier with contributions from Justin Bedo. diff -r 92fb0773aa33 sys/src/cmd/upas/mkfile --- a/sys/src/cmd/upas/mkfile Sun Apr 08 00:00:00 2012 +0200 +++ b/sys/src/cmd/upas/mkfile Sun Apr 08 00:00:00 2012 +0200 @@ -1,7 +1,7 @@ +#include +#include +#include +#include +#include +#include +#include <9p.h> +#include + +enum +{ + STACK = 8192 +}; + +#include "box.h" +#include "sx.h" +#include "imap.h" +#include "fs.h" + +void mailthreadinit(void); +void mailthread(void (*fn)(void*), void*); + +void warn(char*, ...); + +enum +{ + NoEncoding, + QuotedPrintable, + QuotedPrintableU, + Base64 +}; + +char* decode(int, char*, int*); +char* tcs(char*, char*); +char* unrfc2047(char*); + +extern Imap *imap; + +#undef isnumber +#define isnumber upas_isnumber + +#define esmprint smprint +#define emalloc(n) mallocz(n, 1) +#define erealloc realloc +#define estrdup strdup + +#pragma varargck type "$" Sx* +#pragma varargck type "Z" char* diff -r 92fb0773aa33 sys/src/cmd/upas/nfs/box.c --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/sys/src/cmd/upas/nfs/box.c Sun Apr 08 00:00:00 2012 +0200 @@ -0,0 +1,322 @@ +#include "a.h" + +enum +{ + BoxSubChunk = 16, + BoxChunk = 64, + MsgChunk = 256, + PartChunk = 4, + PartSubChunk = 4 +}; + +Box **boxes; +uint nboxes; +Box *rootbox; +int boxid; + +Box* +boxbyname(char *name) +{ + int i; + + /* LATER: replace with hash table */ + for(i=0; iname, name) == 0) + return boxes[i]; + return nil; +} + +Box* +subbox(Box *b, char *elem) +{ + int i; + + for(i=0; insub; i++) + if(b->sub[i] && strcmp(b->sub[i]->elem, elem) == 0) + return b->sub[i]; + return nil; +} + +Box* +boxbyid(uint id) +{ + int i; + + /* LATER: replace with binary search */ + for(i=0; iid == id) + return boxes[i]; + return nil; +} + +Box* +boxcreate(char *name) +{ + char *p; + Box *b, *bb; + + if((b = boxbyname(name)) != nil) + return b; + + b = emalloc(sizeof *b); + b->id = ++boxid; + b->time = time(0); + b->name = estrdup(name); + b->uidnext = 1; + p = strrchr(b->name, '/'); + if(p){ + *p = 0; + bb = boxcreate(b->name); + *p = '/'; + b->elem = p+1; + }else{ + bb = rootbox; + b->elem = b->name; + } + if(nboxes%BoxChunk == 0) + boxes = erealloc(boxes, (nboxes+BoxChunk)*sizeof boxes[0]); + boxes[nboxes++] = b; + if(bb->nsub%BoxSubChunk == 0) + bb->sub = erealloc(bb->sub, (bb->nsub+BoxSubChunk)*sizeof bb->sub[0]); + bb->sub[bb->nsub++] = b; + b->parent = bb; + return b; +} + +void +boxfree(Box *b) +{ + int i; + + if(b == nil) + return; + for(i=0; inmsg; i++) + msgfree(b->msg[i]); + free(b->msg); + free(b); +} + +Part* +partcreate(Msg *m, Part *pp) +{ + Part *p; + + if(m->npart%PartChunk == 0) + m->part = erealloc(m->part, (m->npart+PartChunk)*sizeof m->part[0]); + p = emalloc(sizeof *p); + p->msg = m; + p->ix = m->npart; + m->part[m->npart++] = p; + if(pp){ + if(pp->nsub%PartSubChunk == 0) + pp->sub = erealloc(pp->sub, (pp->nsub+PartSubChunk)*sizeof pp->sub[0]); + p->pix = pp->nsub; + p->parent = pp; + pp->sub[pp->nsub++] = p; + } + return p; +} + +void +partfree(Part *p) +{ + int i; + + if(p == nil) + return; + for(i=0; insub; i++) + partfree(p->sub[i]); + free(p->sub); + hdrfree(p->hdr); + free(p->type); + free(p->idstr); + free(p->desc); + free(p->encoding); + free(p->charset); + free(p->raw); + free(p->rawheader); + free(p->rawbody); + free(p->mimeheader); + free(p->body); + free(p); +} + +void +msgfree(Msg *m) +{ + int i; + + if(m == nil) + return; + for(i=0; inpart; i++) + free(m->part[i]); + free(m->part); + free(m); +} + +void +msgplumb(Msg *m, int delete) +{ + static int fd = -1; + Plumbmsg p; + Plumbattr a[10]; + char buf[256], date[40]; + int ai; + + if(m == nil || m->npart < 1 || m->part[0]->hdr == nil) + return; + if(m->box && strcmp(m->box->name, "mbox") != 0) + return; + + p.src = "mailfs"; + p.dst = "seemail"; + p.wdir = "/"; + p.type = "text"; + + ai = 0; + a[ai].name = "filetype"; + a[ai].value = "mail"; + + a[++ai].name = "mailtype"; + a[ai].value = delete?"delete":"new"; + a[ai-1].next = &a[ai]; + + if(m->part[0]->hdr->from){ + a[++ai].name = "sender"; + a[ai].value = m->part[0]->hdr->from; + a[ai-1].next = &a[ai]; + } + + if(m->part[0]->hdr->subject){ + a[++ai].name = "subject"; + a[ai].value = m->part[0]->hdr->subject; + a[ai-1].next = &a[ai]; + } + + if(m->part[0]->hdr->digest){ + a[++ai].name = "digest"; + a[ai].value = m->part[0]->hdr->digest; + a[ai-1].next = &a[ai]; + } + + strcpy(date, ctime(m->date)); + date[strlen(date)-1] = 0; /* newline */ + a[++ai].name = "date"; + a[ai].value = date; + a[ai-1].next = &a[ai]; + + a[ai].next = nil; + + p.attr = a; +#ifdef PLAN9PORT + snprint(buf, sizeof buf, "Mail/%s/%ud", m->box->name, m->id); +#else + snprint(buf, sizeof buf, "/mail/fs/%s/%ud", m->box->name, m->id); +#endif + p.ndata = strlen(buf); + p.data = buf; + + if(fd < 0) + fd = plumbopen("send", OWRITE); + if(fd < 0) + return; + + plumbsend(fd, &p); +} + + +Msg* +msgcreate(Box *box) +{ + Msg *m; + + m = emalloc(sizeof *m); + m->box = box; + partcreate(m, nil); + m->part[0]->type = estrdup("message/rfc822"); + if(box->nmsg%MsgChunk == 0) + box->msg = erealloc(box->msg, (box->nmsg+MsgChunk)*sizeof box->msg[0]); + m->ix = box->nmsg++; + box->msg[m->ix] = m; + m->id = ++box->msgid; + return m; +} + +Msg* +msgbyimapuid(Box *box, uint uid, int docreate) +{ + int i; + Msg *msg; + + if(box == nil) + return nil; + /* LATER: binary search or something */ + for(i=0; inmsg; i++) + if(box->msg[i]->imapuid == uid) + return box->msg[i]; + if(!docreate) + return nil; + msg = msgcreate(box); + msg->imapuid = uid; + return msg; +} + +Msg* +msgbyid(Box *box, uint id) +{ + int i; + + if(box == nil) + return nil; + /* LATER: binary search or something */ + for(i=0; inmsg; i++) + if(box->msg[i]->id == id) + return box->msg[i]; + return nil; +} + +Part* +partbyid(Msg *m, uint id) +{ + if(m == nil) + return nil; + if(id >= m->npart) + return nil; + return m->part[id]; +} + +Part* +subpart(Part *p, uint a) +{ + if(p == nil || a >= p->nsub) + return nil; + return p->sub[a]; +} + +void +hdrfree(Hdr *h) +{ + if(h == nil) + return; + free(h->date); + free(h->subject); + free(h->from); + free(h->sender); + free(h->replyto); + free(h->to); + free(h->cc); + free(h->bcc); + free(h->inreplyto); + free(h->messageid); + free(h->digest); + free(h); +} + +void +boxinit(void) +{ + rootbox = emalloc(sizeof *rootbox); + rootbox->name = estrdup(""); + rootbox->time = time(0); +} + diff -r 92fb0773aa33 sys/src/cmd/upas/nfs/box.h --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/sys/src/cmd/upas/nfs/box.h Sun Apr 08 00:00:00 2012 +0200 @@ -0,0 +1,135 @@ +enum +{ + FlagJunk = 1<<0, + FlagNonJunk = 1<<1, + FlagReplied = 1<<2, + FlagFlagged = 1<<3, + FlagDeleted = 1<<4, + FlagDraft = 1<<5, + FlagSeen = 1<<6, + FlagNoInferiors = 1<<7, + FlagMarked = 1<<8, + FlagNoSelect = 1<<9, + FlagUnMarked = 1<<10, + FlagRecent = 1<<11 +}; + +typedef struct Box Box; +typedef struct Hdr Hdr; +typedef struct Msg Msg; +typedef struct Part Part; + +struct Box +{ + char* name; /* name of mailbox */ + char* elem; /* last element in name */ + uint ix; /* index in box[] array */ + uint id; /* id shown in file system */ + uint flags; /* FlagNoInferiors, etc. */ + uint time; /* last update time */ + uint msgid; /* last message id used */ + + Msg** msg; /* array of messages (can have nils) */ + uint nmsg; + + char* imapname; /* name on IMAP server */ + u32int validity; /* IMAP validity number */ + uint uidnext; /* IMAP expected next uid */ + uint recent; /* IMAP first recent message */ + uint exists; /* IMAP last message in box */ + uint maxseen; /* maximum IMAP uid seen */ + int mark; + uint imapinit; /* up-to-date w.r.t. IMAP */ + + Box* parent; /* in tree */ + Box** sub; + uint nsub; +}; + +struct Hdr +{ + /* LATER: store date as int, reformat for programs */ + /* order known by fs.c */ + char* date; + char* subject; + char* from; + char* sender; + char* replyto; + char* to; + char* cc; + char* bcc; + char* inreplyto; + char* messageid; + char* digest; +}; + +struct Msg +{ + Box* box; /* mailbox containing msg */ + uint ix; /* index in box->msg[] array */ + uint id; /* id shown in file system */ + uint imapuid; /* IMAP uid */ + uint imapid; /* IMAP id */ + uint flags; /* FlagDeleted etc. */ + uint date; /* smtp envelope date */ + uint size; + + Part** part; /* message subparts - part[0] is root */ + uint npart; +}; + +struct Part +{ + Msg* msg; /* msg containing part */ + uint ix; /* index in msg->part[] */ + uint pix; /* id in parent->sub[] */ + Part* parent; /* parent in structure */ + Part** sub; /* children in structure */ + uint nsub; + + /* order known by fs.c */ + char* type; /* e.g., "text/plain" */ + char* idstr; + char* desc; + char* encoding; + char* charset; + char* filename; + char* raw; + char* rawheader; + char* rawbody; + char* mimeheader; + + /* order known by fs.c */ + uint size; + uint lines; + + char* body; + uint nbody; + Hdr* hdr; /* RFC822 envelope for message/rfc822 */ +}; + +void boxinit(void); +Box* boxbyname(char*); +Box* boxbyid(uint); +Box* boxcreate(char*); +void boxfree(Box*); +Box* subbox(Box*, char*); +Msg* msgcreate(Box*); +Part* partcreate(Msg*, Part*); + +void hdrfree(Hdr*); + +Msg* msgbyid(Box*, uint); +Msg* msgbyimapuid(Box*, uint, int); +void msgfree(Msg*); +void msgplumb(Msg*, int); + +Part* partbyid(Msg*, uint); +Part* subpart(Part*, uint); +void partfree(Part*); + +extern Box** boxes; +extern uint nboxes; + +extern Box* rootbox; + diff -r 92fb0773aa33 sys/src/cmd/upas/nfs/decode.c --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/sys/src/cmd/upas/nfs/decode.c Sun Apr 08 00:00:00 2012 +0200 @@ -0,0 +1,303 @@ +/* Quick and dirty RFC 2047 */ + +#include "a.h" + +static int +unhex1(char c) +{ + if('0' <= c && c <= '9') + return c-'0'; + if('a' <= c && c <= 'f') + return c-'a'+10; + if('A' <= c && c <= 'F') + return c-'A'+10; + return 15; +} + +static int +unhex(char *s) +{ + return unhex1(s[0])*16+unhex1(s[1]); +} + +int +_decqp(uchar *out, int lim, char *in, int n, int underscores) +{ + char *p, *ep; + uchar *eout, *out0; + + out0 = out; + eout = out+lim; + for(p=in, ep=in+n; p ep) + break; + *out++ = unhex(p+1); + p += 3; + }else + *out++ = *p++; + } + return out-out0; +} + +int +decqp(uchar *out, int lim, char *in, int n) +{ + return _decqp(out, lim, in, n, 0); +} + +char* +decode(int kind, char *s, int *len) +{ + char *t; + int l; + + if(s == nil) + return s; + switch(kind){ + case QuotedPrintable: + case QuotedPrintableU: + l = strlen(s)+1; + t = emalloc(l); + l = _decqp((uchar*)t, l, s, l-1, kind==QuotedPrintableU); + *len = l; + t[l] = 0; + return t; + + case Base64: + l = strlen(s)+1; + t = emalloc(l); + l = dec64((uchar*)t, l, s, l-1); + *len = l; + t[l] = 0; + return t; + + default: + *len = strlen(s); + return estrdup(s); + } +} + +struct { + char *mime; + char *tcs; +} tcstab[] = { + "iso-8859-2", "8859-2", + "iso-8859-3", "8859-3", + "iso-8859-4", "8859-4", + "iso-8859-5", "8859-5", + "iso-8859-6", "8859-6", + "iso-8859-7", "8859-7", + "iso-8859-8", "8859-8", + "iso-8859-9", "8859-9", + "iso-8859-10", "8859-10", + "iso-8859-15", "8859-15", + "big5", "big5", + "iso-2022-jp", "jis-kanji", + "windows-1250", "windows-1250", + "windows-1251", "windows-1251", + "windows-1252", "windows-1252", + "windows-1253", "windows-1253", + "windows-1254", "windows-1254", + "windows-1255", "windows-1255", + "windows-1256", "windows-1256", + "windows-1257", "windows-1257", + "windows-1258", "windows-1258", + "koi8-r", "koi8" +}; + +typedef struct Writeargs Writeargs; +struct Writeargs +{ + int fd; + char *s; +}; + +static void +twriter(void *v) +{ + Writeargs *w; + + w = v; + write(w->fd, w->s, strlen(w->s)); + close(w->fd); + free(w->s); + free(w); +} + +char* +tcs(char *charset, char *s) +{ + char *buf; + int i, n, nbuf; + int fd[3], p[2], pp[2]; + uchar *us; + char *t, *u; + char *argv[4]; + Rune r; + Writeargs *w; + + if(s == nil || charset == nil || *s == 0) + return s; + + if(cistrcmp(charset, "utf-8") == 0) + return s; + if(cistrcmp(charset, "iso-8859-1") == 0 || cistrcmp(charset, "us-ascii") == 0){ +latin1: + n = 0; + for(us=(uchar*)s; *us; us++) + n += runelen(*us); + n++; + t = emalloc(n); + for(us=(uchar*)s, u=t; *us; us++){ + r = *us; + u += runetochar(u, &r); + } + *u = 0; + free(s); + return t; + } + for(i=0; ifd = p[1]; + w->s = estrdup(s); + proccreate(twriter, w, STACK); + + n = readn(pp[1], buf, nbuf-1); + close(pp[1]); + if(n <= 0){ + free(buf); + goto latin1; + } + buf[n] = 0; + free(s); + s = estrdup(buf); + free(buf); + return s; +} + +char* +unrfc2047(char *s) +{ + char *p, *q, *t, *u, *v; + int len; + Rune r; + Fmt fmt; + + if(s == nil) + return nil; + + if(strstr(s, "=?") == nil) + return s; + + fmtstrinit(&fmt); + for(p=s; *p; ){ + /* =?charset?e?text?= */ + if(*p=='=' && *(p+1)=='?'){ + p += 2; + q = strchr(p, '?'); + if(q == nil) + goto emit; + q++; + if(*q == '?' || *(q+1) != '?') + goto emit; + t = q+2; + u = strchr(t, '?'); + if(u == nil || *(u+1) != '=') + goto emit; + switch(*q){ + case 'q': + case 'Q': + *u = 0; + v = decode(QuotedPrintableU, t, &len); + break; + case 'b': + case 'B': + *u = 0; + v = decode(Base64, t, &len); + break; + default: + goto emit; + } + *(q-1) = 0; + v = tcs(p, v); + fmtstrcpy(&fmt, v); + free(v); + p = u+2; + } + emit: + p += chartorune(&r, p); + fmtrune(&fmt, r); + } + p = fmtstrflush(&fmt); + if(p == nil) + sysfatal("out of memory"); + free(s); + return p; +} + +#ifdef TEST +char *test[] = +{ + "hello world", + "hello =?iso-8859-1?q?this is some text?=", + "=?US-ASCII?Q?Keith_Moore?=", + "=?ISO-8859-1?Q?Keld_J=F8rn_Simonsen?=", + "=?ISO-8859-1?Q?Andr=E9?= Pirard", + "=?ISO-8859-1?B?SWYgeW91IGNhbiByZWFkIHRoaXMgeW8=?=", + "=?ISO-8859-2?B?dSB1bmRlcnN0YW5kIHRoZSBleGFtcGxlLg==?=", + "=?ISO-8859-1?Q?Olle_J=E4rnefors?=", + "=?iso-2022-jp?B?GyRCTTVKISRKP006SiRyS34kPyQ3JEZKcz03JCIkahsoQg==?=", + "=?UTF-8?B?Ik5pbHMgTy4gU2Vsw6VzZGFsIg==?=" +}; + +void +threadmain(int argc, char **argv) +{ + int i; + + for(i=0; i>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; + + 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; 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 %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; 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; + + 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); +} + diff -r 92fb0773aa33 sys/src/cmd/upas/nfs/fs.h --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/sys/src/cmd/upas/nfs/fs.h Sun Apr 08 00:00:00 2012 +0200 @@ -0,0 +1,2 @@ +extern Srv fs; +void fsinit0(void); diff -r 92fb0773aa33 sys/src/cmd/upas/nfs/imap.c --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/sys/src/cmd/upas/nfs/imap.c Sun Apr 08 00:00:00 2012 +0200 @@ -0,0 +1,1788 @@ +/* + * Locking here is not quite right. + * Calling qlock(&z->lk) can block the proc, + * and when it comes back, boxes and msgs might have been freed + * (if the refresh proc was holding the lock and in the middle of a + * redial). I've tried to be careful about not assuming boxes continue + * to exist across imap commands, but maybe this isn't really tenable. + * Maybe instead we should ref count the boxes and messages. + */ + +#include "a.h" +#include + +struct Imap +{ + int connected; + int autoreconnect; + int ticks; /* until boom! */ + char* server; + char* root; + char* user; + int mode; + int fd; + Biobuf b; + Ioproc* io; + QLock lk; + QLock rlk; + Rendez r; + + Box* inbox; + Box* box; + Box* nextbox; + + /* SEARCH results */ + uint *uid; + uint nuid; +}; + +static struct { + char *name; + int flag; +} flagstab[] = +{ + "Junk", FlagJunk, + "NonJunk", FlagNonJunk, + "\\Answered", FlagReplied, + "\\Flagged", FlagFlagged, + "\\Deleted", FlagDeleted, + "\\Draft", FlagDraft, + "\\Recent", FlagRecent, + "\\Seen", FlagSeen, + "\\NoInferiors", FlagNoInferiors, + "\\NoSelect", FlagNoSelect, + "\\Marked", FlagMarked, + "\\UnMarked", FlagUnMarked +}; + +int chattyimap; + +static char *tag = "#"; + +static void checkbox(Imap*, Box*); +static char* copyaddrs(Sx*); +static void freeup(UserPasswd*); +static int getbox(Imap*, Box*); +static int getboxes(Imap*); +static char* gsub(char*, char*, char*); +static int imapcmd(Imap*, Box*, char*, ...); +static Sx* imapcmdsx(Imap*, Box*, char*, ...); +static Sx* imapcmdsx0(Imap*, char*, ...); +static Sx* imapvcmdsx(Imap*, Box*, char*, va_list); +static Sx* imapvcmdsx0(Imap*, char*, va_list); +static int imapdial(char*, int); +static int imaplogin(Imap*); +static int imapquote(Fmt*); +static int imapreconnect(Imap*); +static void imaprefreshthread(void*); +static void imaptimerproc(void*); +static Sx* imapwaitsx(Imap*); +static int isatom(Sx *v, char *name); +static int islist(Sx *v); +static int isnil(Sx *v); +static int isnumber(Sx *sx); +static int isstring(Sx *sx); +static int ioimapdial(Ioproc*, char*, int); +static char* nstring(Sx*); +static void unexpected(Imap*, Sx*); +static Sx* zBrdsx(Imap*); + +/* + * Imap connection maintenance and login. + */ + +Imap* +imapconnect(char *server, int mode, char *root, char *user) +{ + Imap *z; + + fmtinstall('H', encodefmt); + fmtinstall('Z', imapquote); + + z = emalloc(sizeof *z); + z->server = estrdup(server); + z->mode = mode; + z->user = user; + if(root) + if(root[0] != 0 && root[strlen(root)-1] != '/') + z->root = smprint("%s/", root); + else + z->root = root; + else + z->root = ""; + z->fd = -1; + z->autoreconnect = 0; + z->io = ioproc(); + + qlock(&z->lk); + if(imapreconnect(z) < 0){ + free(z); + return nil; + } + + z->r.l = &z->rlk; + z->autoreconnect = 1; + qunlock(&z->lk); + + proccreate(imaptimerproc, z, STACK); + mailthread(imaprefreshthread, z); + + return z; +} + +void +imaphangup(Imap *z, int ticks) +{ + z->ticks = ticks; + if(ticks == 0){ + close(z->fd); + z->fd = -1; + } +} + +static int +imapreconnect(Imap *z) +{ + Sx *sx; + + z->autoreconnect = 0; + z->box = nil; + z->inbox = nil; + + if(z->fd >= 0){ + close(z->fd); + z->fd = -1; + } + + if(chattyimap) + fprint(2, "dial %s...\n", z->server); + if((z->fd = ioimapdial(z->io, z->server, z->mode)) < 0) + return -1; + z->connected = 1; + Binit(&z->b, z->fd, OREAD); + if((sx = zBrdsx(z)) == nil){ + werrstr("no greeting"); + goto err; + } + if(chattyimap) + fprint(2, "nsx >= 2 && isatom(sx->sx[0], "*") && isatom(sx->sx[1], "PREAUTH")){ + freesx(sx); + goto preauth; + } + if(!oksx(sx)){ + werrstr("bad greeting - %#$", sx); + goto err; + } + freesx(sx); + sx = nil; + if(imaplogin(z) < 0) + goto err; +preauth: + if(getboxes(z) < 0 || getbox(z, z->inbox) < 0) + goto err; + z->autoreconnect = 1; + return 0; + +err: + if(z->fd >= 0){ + close(z->fd); + z->fd = -1; + } + if(sx) + freesx(sx); + z->autoreconnect = 1; + z->connected = 0; + return -1; +} + +static int +imaplogin(Imap *z) +{ + Sx *sx; + UserPasswd *up; + + if(z->user != nil) + up = auth_getuserpasswd(auth_getkey, "proto=pass role=client service=imap server=%q user=%q", z->server, z->user); + else + up = auth_getuserpasswd(auth_getkey, "proto=pass role=client service=imap server=%q", z->server); + if(up == nil){ + werrstr("getuserpasswd - %r"); + return -1; + } + + sx = imapcmdsx(z, nil, "LOGIN %Z %Z", up->user, up->passwd); + freeup(up); + if(sx == nil) + return -1; + if(!oksx(sx)){ + freesx(sx); + werrstr("login rejected - %#$", sx); + return -1; + } + return 0; +} + +static int +getboxes(Imap *z) +{ + int i; + Box **r, **w, **e; + + for(i=0; imark = 1; + boxes[i]->exists = 0; + boxes[i]->maxseen = 0; + } + if(imapcmd(z, nil, "LIST %Z *", z->root) < 0) + return -1; + if(z->root != nil && imapcmd(z, nil, "LIST %Z INBOX", "") < 0) + return -1; + if(z->nextbox && z->nextbox->mark) + z->nextbox = nil; + for(r=boxes, w=boxes, e=boxes+nboxes; rmark) +{fprint(2, "*** free box %s %s\n", (*r)->name, (*r)->imapname); + boxfree(*r); +} + else + *w++ = *r; + } + nboxes = w - boxes; + return 0; +} + +static int +getbox(Imap *z, Box *b) +{ + int i; + Msg **r, **w, **e; + + if(b == nil) + return 0; + + for(i=0; inmsg; i++) + b->msg[i]->imapid = 0; + if(imapcmd(z, b, "UID FETCH 1:* FLAGS") < 0) + return -1; + for(r=b->msg, w=b->msg, e=b->msg+b->nmsg; rimapid == 0) + msgfree(*r); + else{ + (*r)->ix = w-b->msg; + *w++ = *r; + } + } + b->nmsg = w - b->msg; + b->imapinit = 1; + checkbox(z, b); + return 0; +} + +static void +freeup(UserPasswd *up) +{ + memset(up->user, 0, strlen(up->user)); + memset(up->passwd, 0, strlen(up->passwd)); + free(up); +} + +static void +imaptimerproc(void *v) +{ + Imap *z; + + z = v; + for(;;){ + sleep(60*1000); + qlock(z->r.l); + rwakeup(&z->r); + qunlock(z->r.l); + } +} + +static void +checkbox(Imap *z, Box *b) +{ + if(imapcmd(z, b, "NOOP") >= 0){ + if(!b->imapinit) + getbox(z, b); + if(!b->imapinit) + return; + if(b==z->box && b->exists > b->maxseen){ + imapcmd(z, b, "UID FETCH %d:* FULL", + b->uidnext); + } + } +} + +static void +imaprefreshthread(void *v) +{ + Imap *z; + + z = v; + for(;;){ + qlock(z->r.l); + rsleep(&z->r); + qunlock(z->r.l); + + qlock(&z->lk); + if(z->inbox) + checkbox(z, z->inbox); + qunlock(&z->lk); + } +} + +/* + * Run a single command and return the Sx. Does NOT redial. + */ +static Sx* +imapvcmdsx0(Imap *z, char *fmt, va_list arg) +{ + char *s; + Fmt f; + int prefix, len; + Sx *sx; + + if(canqlock(&z->lk)) + abort(); + + if(z->fd < 0 || !z->connected) + return nil; + + prefix = strlen(tag)+1; + fmtstrinit(&f); + fmtprint(&f, "%s ", tag); + fmtvprint(&f, fmt, arg); + fmtprint(&f, "\r\n"); + s = fmtstrflush(&f); + len = strlen(s); + s[len-2] = 0; + if(chattyimap) + fprint(2, "I> %s\n", s); + s[len-2] = '\r'; + if(iowrite(z->io, z->fd, s, len) < 0){ + z->connected = 0; + free(s); + return nil; + } + sx = imapwaitsx(z); + free(s); + return sx; +} + +static Sx* +imapcmdsx0(Imap *z, char *fmt, ...) +{ + va_list arg; + Sx *sx; + + va_start(arg, fmt); + sx = imapvcmdsx0(z, fmt, arg); + va_end(arg); + return sx; +} + +/* + * Run a single command on box b. Does redial. + */ +static Sx* +imapvcmdsx(Imap *z, Box *b, char *fmt, va_list arg) +{ + int tries; + Sx *sx; + + tries = 0; + z->nextbox = b; + + if(z->fd < 0 || !z->connected){ +reconnect: + if(!z->autoreconnect) + return nil; + if(imapreconnect(z) < 0) + return nil; + if(b && z->nextbox == nil) /* box disappeared on reconnect */ + return nil; + } + + if(b && b != z->box){ + if(z->box) + z->box->imapinit = 0; + z->box = b; + if((sx=imapcmdsx0(z, "SELECT %Z", b->imapname)) == nil){ + z->box = nil; + if(tries++ == 0 && (z->fd < 0 || !z->connected)) + goto reconnect; + return nil; + } + freesx(sx); + } + + if((sx=imapvcmdsx0(z, fmt, arg)) == nil){ + if(tries++ == 0 && (z->fd < 0 || !z->connected)) + goto reconnect; + return nil; + } + return sx; +} + +static int +imapcmd(Imap *z, Box *b, char *fmt, ...) +{ + Sx *sx; + va_list arg; + + va_start(arg, fmt); + sx = imapvcmdsx(z, b, fmt, arg); + va_end(arg); + if(sx == nil) + return -1; + if(sx->nsx < 2 || !isatom(sx->sx[1], "OK")){ + werrstr("%$", sx); + freesx(sx); + return -1; + } + freesx(sx); + return 0; +} + +static Sx* +imapcmdsx(Imap *z, Box *b, char *fmt, ...) +{ + Sx *sx; + va_list arg; + + va_start(arg, fmt); + sx = imapvcmdsx(z, b, fmt, arg); + va_end(arg); + return sx; +} + +static Sx* +imapwaitsx(Imap *z) +{ + Sx *sx; + + while((sx = zBrdsx(z)) != nil){ + if(chattyimap) + fprint(2, "<| %#$\n", sx); + if(sx->nsx >= 1 && sx->sx[0]->type == SxAtom && cistrcmp(sx->sx[0]->data, tag) == 0) + return sx; + if(sx->nsx >= 1 && sx->sx[0]->type == SxAtom && strcmp(sx->sx[0]->data, "*") == 0) + unexpected(z, sx); + if(sx->type == SxList && sx->nsx == 0){ + freesx(sx); + break; + } + freesx(sx); + } + z->connected = 0; + return nil; +} + +/* + * Imap interface to mail file system. + */ + +static void +_bodyname(char *buf, char *ebuf, Part *p, char *extra) +{ + if(buf >= ebuf){ + fprint(2, "***** BUFFER TOO SMALL\n"); + return; + } + *buf = 0; + if(p->parent){ + _bodyname(buf, ebuf, p->parent, ""); + buf += strlen(buf); + seprint(buf, ebuf, ".%d", p->pix+1); + } + buf += strlen(buf); + seprint(buf, ebuf, "%s", extra); +} + +static char* +bodyname(Part *p, char *extra) +{ + static char buf[256]; + memset(buf, 0, sizeof buf); /* can't see why this is necessary, but it is */ + _bodyname(buf, buf+sizeof buf, p, extra); + return buf+1; /* buf[0] == '.' */ +} + +static void +fetch1(Imap *z, Part *p, char *s) +{ + qlock(&z->lk); + imapcmd(z, p->msg->box, "UID FETCH %d BODY[%s]", + p->msg->imapuid, bodyname(p, s)); + qunlock(&z->lk); +} + +void +imapfetchrawheader(Imap *z, Part *p) +{ + fetch1(z, p, ".HEADER"); +} + +void +imapfetchrawmime(Imap *z, Part *p) +{ + fetch1(z, p, ".MIME"); +} + +void +imapfetchrawbody(Imap *z, Part *p) +{ + fetch1(z, p, ".TEXT"); +} + +void +imapfetchraw(Imap *z, Part *p) +{ + fetch1(z, p, ""); +} + +static int +imaplistcmd(Imap *z, Box *box, char *before, Msg **m, uint nm, char *after) +{ + int i, r; + char *cmd; + Fmt fmt; + + if(nm == 0) + return 0; + + fmtstrinit(&fmt); + fmtprint(&fmt, "%s ", before); + for(i=0; i 0) + fmtrune(&fmt, ','); + fmtprint(&fmt, "%ud", m[i]->imapuid); + } + fmtprint(&fmt, " %s", after); + cmd = fmtstrflush(&fmt); + + r = 0; + if(imapcmd(z, box, "%s", cmd) < 0) + r = -1; + free(cmd); + return r; +} + +int +imapcopylist(Imap *z, char *nbox, Msg **m, uint nm) +{ + int rv; + char *name, *p; + + if(nm == 0) + return 0; + + qlock(&z->lk); + if(strcmp(nbox, "mbox") == 0) + name = estrdup("INBOX"); + else{ + p = esmprint("%s%s", z->root, nbox); + name = esmprint("%Z", p); + free(p); + } + rv = imaplistcmd(z, m[0]->box, "UID COPY", m, nm, name); + free(name); + qunlock(&z->lk); + return rv; +} + +int +imapremovelist(Imap *z, Msg **m, uint nm) +{ + int rv; + + if(nm == 0) + return 0; + + qlock(&z->lk); + rv = imaplistcmd(z, m[0]->box, "UID STORE", m, nm, "+FLAGS.SILENT (\\Deleted)"); + /* careful - box might be gone; use z->box instead */ + if(rv == 0 && z->box) + rv = imapcmd(z, z->box, "EXPUNGE"); + qunlock(&z->lk); + return rv; +} + +int +imapflaglist(Imap *z, int op, int flag, Msg **m, uint nm) +{ + char *mod, *s, *sep; + int i, rv; + Fmt fmt; + + if(op > 0) + mod = "+"; + else if(op == 0) + mod = ""; + else + mod = "-"; + + fmtstrinit(&fmt); + fmtprint(&fmt, "%sFLAGS (", mod); + sep = ""; + for(i=0; ilk); + rv = imaplistcmd(z, m[0]->box, "UID STORE", m, nm, s); + qunlock(&z->lk); + free(s); + return rv; +} + +int +imapsearchbox(Imap *z, Box *b, char *search, Msg ***mm) +{ + uint *uid; + int i, nuid; + Msg **m; + int nm; + + qlock(&z->lk); + if(imapcmd(z, b, "UID SEARCH CHARSET UTF-8 TEXT %Z", search) < 0){ + qunlock(&z->lk); + return -1; + } + + uid = z->uid; + nuid = z->nuid; + z->uid = nil; + z->nuid = 0; + qunlock(&z->lk); + + m = emalloc(nuid*sizeof m[0]); + nm = 0; + for(i=0; ilk); + checkbox(z, b); + qunlock(&z->lk); +} + +/* + * Imap utility routines + */ +static long +_ioimapdial(va_list *arg) +{ + char *server; + int mode; + + server = va_arg(*arg, char*); + mode = va_arg(*arg, int); + return imapdial(server, mode); +} +static int +ioimapdial(Ioproc *io, char *server, int mode) +{ + return iocall(io, _ioimapdial, server, mode); +} + +static long +_ioBrdsx(va_list *arg) +{ + Biobuf *b; + Sx **sx; + + b = va_arg(*arg, Biobuf*); + sx = va_arg(*arg, Sx**); + *sx = Brdsx(b); + if((*sx) && (*sx)->type == SxList && (*sx)->nsx == 0){ + freesx(*sx); + *sx = nil; + } + return 0; +} +static Sx* +ioBrdsx(Ioproc *io, Biobuf *b) +{ + Sx *sx; + + iocall(io, _ioBrdsx, b, &sx); + return sx; +} + +static Sx* +zBrdsx(Imap *z) +{ + if(z->ticks && --z->ticks==0){ + close(z->fd); + z->fd = -1; + return nil; + } + return ioBrdsx(z->io, &z->b); +} + +static int +imapdial(char *server, int mode) +{ + int p[2]; + int fd[3]; + char *tmp; + + switch(mode){ + default: + case Unencrypted: + return dial(netmkaddr(server, "tcp", "143"), nil, nil, nil); + + case Starttls: + werrstr("starttls not supported"); + return -1; + + case Tls: + if(pipe(p) < 0) + return -1; + fd[0] = dup(p[0], -1); + fd[1] = dup(p[0], -1); + fd[2] = dup(2, -1); +#ifdef PLAN9PORT + tmp = esmprint("%s:993", server); + if(threadspawnl(fd, "/usr/sbin/stunnel3", "stunnel3", "-c", "-r", tmp, nil) < 0 + && threadspawnl(fd, "/usr/bin/stunnel3", "stunnel3", "-c", "-r", tmp, nil) < 0 + && threadspawnl(fd, "/usr/sbin/stunnel", "stunnel", "-c", "-r", tmp, nil) < 0 + && threadspawnl(fd, "/usr/bin/stunnel", "stunnel", "-c", "-r", tmp, nil) < 0){ +#else + tmp = esmprint("tcp!%s!993", server); + if(threadspawnl(fd, "/bin/tlsclient", "tlsclient", tmp, nil) < 0){ +#endif + free(tmp); + close(p[0]); + close(p[1]); + close(fd[0]); + close(fd[1]); + close(fd[2]); + return -1; + } + free(tmp); + close(p[0]); + return p[1]; + + case Cmd: + if(pipe(p) < 0) + return -1; + fd[0] = dup(p[0], -1); + fd[1] = dup(p[0], -1); + fd[2] = dup(2, -1); + if(threadspawnl(fd, "/usr/local/plan9/bin/rc", "rc", "-c", server, nil) < 0){ + close(p[0]); + close(p[1]); + close(fd[0]); + close(fd[1]); + close(fd[2]); + return -1; + } + close(p[0]); + return p[1]; + } +} + +enum +{ + Qok = 0, + Qquote, + Qbackslash +}; + +static int +needtoquote(Rune r) +{ + if(r >= Runeself) + return Qquote; + if(r <= ' ') + return Qquote; + if(r=='\\' || r=='"') + return Qbackslash; + return Qok; +} + +static int +imapquote(Fmt *f) +{ + char *s, *t; + int w, quotes; + Rune r; + + s = va_arg(f->args, char*); + if(s == nil || *s == '\0') + return fmtstrcpy(f, "\"\""); + + quotes = 0; + if(f->flags&FmtSharp) + quotes = 1; + for(t=s; *t; t+=w){ + w = chartorune(&r, t); + quotes |= needtoquote(r); + } + if(quotes == 0) + return fmtstrcpy(f, s); + + fmtrune(f, '"'); + for(t=s; *t; t+=w){ + w = chartorune(&r, t); + if(needtoquote(r) == Qbackslash) + fmtrune(f, '\\'); + fmtrune(f, r); + } + return fmtrune(f, '"'); +} + +static int +fmttype(char c) +{ + switch(c){ + case 'A': + return SxAtom; + case 'L': + return SxList; + case 'N': + return SxNumber; + case 'S': + return SxString; + default: + return -1; + } +} + +/* + * Check S expression against format string. + */ +static int +sxmatch(Sx *sx, char *fmt) +{ + int i; + + for(i=0; fmt[i]; i++){ + if(fmt[i] == '*') + fmt--; /* like i-- but better */ + if(i == sx->nsx && fmt[i+1] == '*') + return 1; + if(i >= sx->nsx) + return 0; + if(sx->sx[i] == nil) + return 0; + if(sx->sx[i]->type == SxAtom && strcmp(sx->sx[i]->data, "NIL") == 0){ + if(fmt[i] == 'L'){ + free(sx->sx[i]->data); + sx->sx[i]->data = nil; + sx->sx[i]->type = SxList; + sx->sx[i]->sx = nil; + sx->sx[i]->nsx = 0; + } + else if(fmt[i] == 'S'){ + free(sx->sx[i]->data); + sx->sx[i]->data = nil; + sx->sx[i]->type = SxString; + } + } + if(sx->sx[i]->type == SxAtom && fmt[i]=='S') + sx->sx[i]->type = SxString; + if(sx->sx[i]->type != fmttype(fmt[i])){ + fprint(2, "sxmatch: %$ not %c\n", sx->sx[i], fmt[i]); + return 0; + } + } + if(i != sx->nsx) + return 0; + return 1; +} + +/* + * Check string against format string. + */ +static int +stringmatch(char *fmt, char *s) +{ + for(; *fmt && *s; fmt++, s++){ + switch(*fmt){ + case '0': + if(*s == ' ') + break; + /* fall through */ + case '1': + if(*s < '0' || *s > '9') + return 0; + break; + case 'A': + if(*s < 'A' || *s > 'Z') + return 0; + break; + case 'a': + if(*s < 'a' || *s > 'z') + return 0; + break; + case '+': + if(*s != '-' && *s != '+') + return 0; + break; + default: + if(*s != *fmt) + return 0; + break; + } + } + if(*fmt || *s) + return 0; + return 1; +} + +/* + * Parse simple S expressions and IMAP elements. + */ +static int +isatom(Sx *v, char *name) +{ + int n; + + if(v == nil || v->type != SxAtom) + return 0; + n = strlen(name); + if(cistrncmp(v->data, name, n) == 0) + if(v->data[n] == 0 || (n>0 && v->data[n-1] == '[')) + return 1; + return 0; +} + +static int +isstring(Sx *sx) +{ + if(sx->type == SxAtom) + sx->type = SxString; + return sx->type == SxString; +} + +static int +isnumber(Sx *sx) +{ + return sx->type == SxNumber; +} + +static int +isnil(Sx *v) +{ + return v == nil || + (v->type==SxList && v->nsx == 0) || + (v->type==SxAtom && strcmp(v->data, "NIL") == 0); +} + +static int +islist(Sx *v) +{ + return isnil(v) || v->type==SxList; +} + +static uint +parseflags(Sx *v) +{ + int f, i, j; + + if(v->type != SxList){ + warn("malformed flags: %$", v); + return 0; + } + f = 0; + for(i=0; insx; i++){ + if(v->sx[i]->type != SxAtom) + continue; + for(j=0; jsx[i]->data, flagstab[j].name) == 0) + f |= flagstab[j].flag; + } + return f; +} + +static char months[] = "JanFebMarAprMayJunJulAugSepOctNovDec"; +static int +parsemon(char *s) +{ + int i; + + for(i=0; months[i]; i+=3) + if(memcmp(s, months+i, 3) == 0) + return i/3; + return -1; +} + +static uint +parsedate(Sx *v) +{ + Tm tm; + uint t; + int delta; + char *p; + + if(v->type != SxString || !stringmatch("01-Aaa-1111 01:11:11 +1111", v->data)){ + bad: + warn("bad date: %$", v); + return 0; + } + + /* cannot use atoi because 09 is malformed octal! */ + memset(&tm, 0, sizeof tm); + p = v->data; + tm.mday = strtol(p, 0, 10); + tm.mon = parsemon(p+3); + if(tm.mon == -1) + goto bad; + tm.year = strtol(p+7, 0, 10) - 1900; + tm.hour = strtol(p+12, 0, 10); + tm.min = strtol(p+15, 0, 10); + tm.sec = strtol(p+18, 0, 10); + strcpy(tm.zone, "GMT"); + + t = tm2sec(&tm); + delta = ((p[22]-'0')*10+p[23]-'0')*3600 + ((p[24]-'0')*10+p[25]-'0')*60; + if(p[21] == '-') + delta = -delta; + + t -= delta; + return t; +} + +static uint +parsenumber(Sx *v) +{ + if(v->type != SxNumber) + return 0; + return v->number; +} + +static void +hash(DigestState *ds, char *tag, char *val) +{ + if(val == nil) + val = ""; + md5((uchar*)tag, strlen(tag)+1, nil, ds); + md5((uchar*)val, strlen(val)+1, nil, ds); +} + +static Hdr* +parseenvelope(Sx *v) +{ + Hdr *hdr; + uchar digest[16]; + DigestState ds; + + if(v->type != SxList || !sxmatch(v, "SSLLLLLLSS")){ + warn("bad envelope: %$", v); + return nil; + } + + hdr = emalloc(sizeof *hdr); + hdr->date = nstring(v->sx[0]); + hdr->subject = unrfc2047(nstring(v->sx[1])); + hdr->from = copyaddrs(v->sx[2]); + hdr->sender = copyaddrs(v->sx[3]); + hdr->replyto = copyaddrs(v->sx[4]); + hdr->to = copyaddrs(v->sx[5]); + hdr->cc = copyaddrs(v->sx[6]); + hdr->bcc = copyaddrs(v->sx[7]); + hdr->inreplyto = unrfc2047(nstring(v->sx[8])); + hdr->messageid = unrfc2047(nstring(v->sx[9])); + + memset(&ds, 0, sizeof ds); + hash(&ds, "date", hdr->date); + hash(&ds, "subject", hdr->subject); + hash(&ds, "from", hdr->from); + hash(&ds, "sender", hdr->sender); + hash(&ds, "replyto", hdr->replyto); + hash(&ds, "to", hdr->to); + hash(&ds, "cc", hdr->cc); + hash(&ds, "bcc", hdr->bcc); + hash(&ds, "inreplyto", hdr->inreplyto); + hash(&ds, "messageid", hdr->messageid); + md5(0, 0, digest, &ds); + hdr->digest = esmprint("%.16H", digest); + + return hdr; +} + +static void +strlwr(char *s) +{ + char *t; + + if(s == nil) + return; + for(t=s; *t; t++) + if('A' <= *t && *t <= 'Z') + *t += 'a' - 'A'; +} + +static void +nocr(char *s) +{ + char *r, *w; + + if(s == nil) + return; + for(r=w=s; *r; r++) + if(*r != '\r') + *w++ = *r; + *w = 0; +} + +/* + * substitute all occurrences of a with b in s. + */ +static char* +gsub(char *s, char *a, char *b) +{ + char *p, *t, *w, *last; + int n; + + n = 0; + for(p=s; (p=strstr(p, a)) != nil; p+=strlen(a)) + n++; + if(n == 0) + return s; + t = emalloc(strlen(s)+n*strlen(b)+1); + w = t; + for(p=s; last=p, (p=strstr(p, a)) != nil; p+=strlen(a)){ + memmove(w, last, p-last); + w += p-last; + memmove(w, b, strlen(b)); + w += strlen(b); + } + strcpy(w, last); + free(s); + return t; +} + +/* + * Table-driven IMAP "unexpected response" parser. + * All the interesting data is in the unexpected responses. + */ +static void xlist(Imap*, Sx*); +static void xrecent(Imap*, Sx*); +static void xexists(Imap*, Sx*); +static void xok(Imap*, Sx*); +static void xflags(Imap*, Sx*); +static void xfetch(Imap*, Sx*); +static void xexpunge(Imap*, Sx*); +static void xbye(Imap*, Sx*); +static void xsearch(Imap*, Sx*); + +static struct { + int num; + char *name; + char *fmt; + void (*fn)(Imap*, Sx*); +} unextab[] = { + 0, "BYE", nil, xbye, + 0, "FLAGS", "AAL", xflags, + 0, "LIST", "AALSS", xlist, + 0, "OK", nil, xok, + 0, "SEARCH", "AAN*", xsearch, + + 1, "EXISTS", "ANA", xexists, + 1, "EXPUNGE", "ANA", xexpunge, + 1, "FETCH", "ANAL", xfetch, + 1, "RECENT", "ANA", xrecent +}; + +static void +unexpected(Imap *z, Sx *sx) +{ + int i, num; + char *name; + + if(sx->nsx >= 3 && sx->sx[1]->type == SxNumber && sx->sx[2]->type == SxAtom){ + num = 1; + name = sx->sx[2]->data; + }else if(sx->nsx >= 2 && sx->sx[1]->type == SxAtom){ + num = 0; + name = sx->sx[1]->data; + }else + return; + + for(i=0; isx[4]->data); + if(sx->sx[3]->data && strcmp(sx->sx[3]->data, "/") != 0){ + s = gsub(s, "/", "_"); + s = gsub(s, sx->sx[3]->data, "/"); + } + + /* + * INBOX is the special imap name for the main mailbox. + * All other mailbox names have the root prefix removed, if applicable. + */ + inbox = 0; + if(cistrcmp(s, "INBOX") == 0){ + inbox = 1; + free(s); + s = estrdup("mbox"); + } else if(z->root && strstr(s, z->root) == s) { + t = estrdup(s+strlen(z->root)); + free(s); + s = t; + } + + /* + * Plan 9 calls the main mailbox mbox. + * Rename any existing mbox by appending a $. + */ + if(!inbox && strncmp(s, "mbox", 4) == 0 && alldollars(s+4)){ + t = emalloc(strlen(s)+2); + strcpy(t, s); + strcat(t, "$"); + free(s); + s = t; + } + + box = boxcreate(s); + if(box == nil) + return; + box->imapname = estrdup(sx->sx[4]->data); + if(inbox) + z->inbox = box; + box->mark = 0; + box->flags = parseflags(sx->sx[2]); +} + +static void +xrecent(Imap *z, Sx *sx) +{ + if(z->box) + z->box->recent = sx->sx[1]->number; +} + +static void +xexists(Imap *z, Sx *sx) +{ + if(z->box){ + z->box->exists = sx->sx[1]->number; + if(z->box->exists < z->box->maxseen) + z->box->maxseen = z->box->exists; + } +} + +static void +xflags(Imap *z, Sx *sx) +{ + USED(z); + USED(sx); + /* + * This response contains in sx->sx[2] the list of flags + * that can be validly attached to messages in z->box. + * We don't have any use for this list, since we + * use only the standard flags. + */ +} + +static void +xbye(Imap *z, Sx *sx) +{ + USED(sx); + close(z->fd); + z->fd = -1; + z->connected = 0; +} + +static void +xexpunge(Imap *z, Sx *sx) +{ + int i, n; + Box *b; + + if((b=z->box) == nil) + return; + n = sx->sx[1]->number; + for(i=0; inmsg; i++){ + if(b->msg[i]->imapid == n){ + msgplumb(b->msg[i], 1); + msgfree(b->msg[i]); + b->nmsg--; + memmove(b->msg+i, b->msg+i+1, (b->nmsg-i)*sizeof b->msg[0]); + i--; + b->maxseen--; + b->exists--; + continue; + } + if(b->msg[i]->imapid > n) + b->msg[i]->imapid--; + b->msg[i]->ix = i; + } +} + +static void +xsearch(Imap *z, Sx *sx) +{ + int i; + + free(z->uid); + z->uid = emalloc((sx->nsx-2)*sizeof z->uid[0]); + z->nuid = sx->nsx-2; + for(i=0; inuid; i++) + z->uid[i] = sx->sx[i+2]->number; +} + +/* + * Table-driven FETCH message info parser. + */ +static void xmsgflags(Msg*, Sx*, Sx*); +static void xmsgdate(Msg*, Sx*, Sx*); +static void xmsgrfc822size(Msg*, Sx*, Sx*); +static void xmsgenvelope(Msg*, Sx*, Sx*); +static void xmsgbody(Msg*, Sx*, Sx*); +static void xmsgbodydata(Msg*, Sx*, Sx*); + +static struct { + char *name; + void (*fn)(Msg*, Sx*, Sx*); +} msgtab[] = { + "FLAGS", xmsgflags, + "INTERNALDATE", xmsgdate, + "RFC822.SIZE", xmsgrfc822size, + "ENVELOPE", xmsgenvelope, + "BODY", xmsgbody, + "BODY[", xmsgbodydata +}; + +static void +xfetch(Imap *z, Sx *sx) +{ + int i, j, n, uid; + Msg *msg; + + if(z->box == nil){ + warn("FETCH but no open box: %$", sx); + return; + } + + /* * 152 FETCH (UID 185 FLAGS () ...) */ + if(sx->sx[3]->nsx%2){ + warn("malformed FETCH: %$", sx); + return; + } + + n = sx->sx[1]->number; + sx = sx->sx[3]; + for(i=0; insx; i+=2){ + if(isatom(sx->sx[i], "UID")){ + if(sx->sx[i+1]->type == SxNumber){ + uid = sx->sx[i+1]->number; + goto haveuid; + } + } + } +/* This happens: too bad. + warn("FETCH without UID: %$", sx); +*/ + return; + +haveuid: + msg = msgbyimapuid(z->box, uid, 1); + if(msg->imapid && msg->imapid != n) + warn("msg id mismatch: want %d have %d", msg->id, n); + msg->imapid = n; + for(i=0; insx; i+=2){ + for(j=0; jsx[i], msgtab[j].name)) + msgtab[j].fn(msg, sx->sx[i], sx->sx[i+1]); + } +} + +static void +xmsgflags(Msg *msg, Sx *k, Sx *v) +{ + USED(k); + msg->flags = parseflags(v); +} + +static void +xmsgdate(Msg *msg, Sx *k, Sx *v) +{ + USED(k); + msg->date = parsedate(v); +} + +static void +xmsgrfc822size(Msg *msg, Sx *k, Sx *v) +{ + USED(k); + msg->size = parsenumber(v); +} + +static char* +nstring(Sx *v) +{ + char *p; + + if(isnil(v)) + return estrdup(""); + p = v->data; + v->data = nil; + return p; +} + +static char* +copyaddrs(Sx *v) +{ + char *s, *sep; + char *name, *email, *host, *mbox; + int i; + Fmt fmt; + + if(v->nsx == 0) + return nil; + + fmtstrinit(&fmt); + sep = ""; + for(i=0; insx; i++){ + if(!sxmatch(v->sx[i], "SSSS")) + warn("bad address: %$", v->sx[i]); + name = unrfc2047(nstring(v->sx[i]->sx[0])); + /* ignore sx[1] - route */ + mbox = unrfc2047(nstring(v->sx[i]->sx[2])); + host = unrfc2047(nstring(v->sx[i]->sx[3])); + if(mbox == nil || host == nil){ /* rfc822 group syntax */ + free(name); + free(mbox); + free(host); + continue; + } + email = esmprint("%s@%s", mbox, host); + free(mbox); + free(host); + fmtprint(&fmt, "%s%q %q", sep, name ? name : "", email ? email : ""); + free(name); + free(email); + sep = " "; + } + s = fmtstrflush(&fmt); + if(s == nil) + sysfatal("out of memory"); + return s; +} + +static void +xmsgenvelope(Msg *msg, Sx *k, Sx *v) +{ + USED(k); + hdrfree(msg->part[0]->hdr); + msg->part[0]->hdr = parseenvelope(v); + msgplumb(msg, 0); +} + +static struct { + char *name; + int offset; +} paramtab[] = { + "charset", offsetof(Part, charset), + "name", offsetof(Part, filename) +}; + +static void +parseparams(Part *part, Sx *v) +{ + int i, j; + char *s, *t, **p; + + if(isnil(v)) + return; + if(v->nsx%2){ + warn("bad message params: %$", v); + return; + } + for(i=0; insx; i+=2){ + s = nstring(v->sx[i]); + t = nstring(v->sx[i+1]); + for(j=0; jtype != SxList){ + bad: + warn("bad structure: %$", v); + return; + } + if(islist(v->sx[0])){ + /* multipart */ + for(i=0; insx && islist(v->sx[i]); i++) + parsestructure(partcreate(part->msg, part), v->sx[i]); + free(part->type); + if(i != v->nsx-1 || !isstring(v->sx[i])){ + warn("bad multipart structure: %$", v); + part->type = estrdup("multipart/mixed"); + return; + } + s = nstring(v->sx[i]); + strlwr(s); + part->type = esmprint("multipart/%s", s); + free(s); + return; + } + /* single part */ + if(!isstring(v->sx[0]) || v->nsx < 2) + goto bad; + s = nstring(v->sx[0]); + t = nstring(v->sx[1]); + strlwr(s); + strlwr(t); + free(part->type); + part->type = esmprint("%s/%s", s, t); + if(v->nsx < 7 || !islist(v->sx[2]) || !isstring(v->sx[3]) + || !isstring(v->sx[4]) || !isstring(v->sx[5]) || !isnumber(v->sx[6])) + goto bad; + parseparams(part, v->sx[2]); + part->idstr = nstring(v->sx[3]); + part->desc = nstring(v->sx[4]); + part->encoding = nstring(v->sx[5]); + part->size = v->sx[6]->number; + if(strcmp(s, "message") == 0 && strcmp(t, "rfc822") == 0){ + if(v->nsx < 10 || !islist(v->sx[7]) || !islist(v->sx[8]) || !isnumber(v->sx[9])) + goto bad; + part->hdr = parseenvelope(v->sx[7]); + parsestructure(partcreate(part->msg, part), v->sx[8]); + part->lines = v->sx[9]->number; + } + if(strcmp(s, "text") == 0){ + if(v->nsx < 8 || !isnumber(v->sx[7])) + goto bad; + part->lines = v->sx[7]->number; + } +} + +static void +xmsgbody(Msg *msg, Sx *k, Sx *v) +{ + USED(k); + if(v->type != SxList){ + warn("bad body: %$", v); + return; + } + /* + * To follow the structure exactly we should + * be doing this to partcreate(msg, msg->part[0]), + * and we should leave msg->part[0] with type message/rfc822, + * but the extra layer is redundant - what else would be in a mailbox? + */ + parsestructure(msg->part[0], v); + if(msg->box->maxseen < msg->imapid) + msg->box->maxseen = msg->imapid; + if(msg->imapuid >= msg->box->uidnext) + msg->box->uidnext = msg->imapuid+1; +} + +static void +xmsgbodydata(Msg *msg, Sx *k, Sx *v) +{ + int i; + char *name, *p; + Part *part; + + name = k->data; + name += 5; /* body[ */ + p = strchr(name, ']'); + if(p) + *p = 0; + + /* now name is something like 1 or 3.2.MIME - walk down parts from root */ + part = msg->part[0]; + + + while('1' <= name[0] && name[0] <= '9'){ + i = strtol(name, &p, 10); + if(*p == '.') + p++; + else if(*p != 0){ + warn("bad body name: %$", k); + return; + } + if((part = subpart(part, i-1)) == nil){ + warn("unknown body part: %$", k); + return; + } + name = p; + } + + + if(cistrcmp(name, "") == 0){ + free(part->raw); + part->raw = nstring(v); + nocr(part->raw); + }else if(cistrcmp(name, "HEADER") == 0){ + free(part->rawheader); + part->rawheader = nstring(v); + nocr(part->rawheader); + }else if(cistrcmp(name, "MIME") == 0){ + free(part->mimeheader); + part->mimeheader = nstring(v); + nocr(part->mimeheader); + }else if(cistrcmp(name, "TEXT") == 0){ + free(part->rawbody); + part->rawbody = nstring(v); + nocr(part->rawbody); + } +} + +/* + * Table-driven OK info parser. + */ +static void xokuidvalidity(Imap*, Sx*); +static void xokpermflags(Imap*, Sx*); +static void xokunseen(Imap*, Sx*); +static void xokreadwrite(Imap*, Sx*); +static void xokreadonly(Imap*, Sx*); + +struct { + char *name; + char fmt; + void (*fn)(Imap*, Sx*); +} oktab[] = { + "UIDVALIDITY", 'N', xokuidvalidity, + "PERMANENTFLAGS", 'L', xokpermflags, + "UNSEEN", 'N', xokunseen, + "READ-WRITE", 0, xokreadwrite, + "READ-ONLY", 0, xokreadonly +}; + +static void +xok(Imap *z, Sx *sx) +{ + int i; + char *name; + Sx *arg; + + if(sx->nsx >= 4 && sx->sx[2]->type == SxAtom && sx->sx[2]->data[0] == '['){ + if(sx->sx[3]->type == SxAtom && sx->sx[3]->data[0] == ']') + arg = nil; + else if(sx->sx[4]->type == SxAtom && sx->sx[4]->data[0] == ']') + arg = sx->sx[3]; + else{ + warn("cannot parse OK: %$", sx); + return; + } + name = sx->sx[2]->data+1; + for(i=0; itype != fmttype(oktab[i].fmt))){ + warn("malformed %s: %$", name, arg); + continue; + } + oktab[i].fn(z, arg); + } + } + } +} + +static void +xokuidvalidity(Imap *z, Sx *sx) +{ + int i; + Box *b; + + if((b=z->box) == nil) + return; + if(b->validity != sx->number){ + b->validity = sx->number; + b->uidnext = 1; + for(i=0; inmsg; i++) + msgfree(b->msg[i]); + free(b->msg); + b->msg = nil; + b->nmsg = 0; + } +} + +static void +xokpermflags(Imap *z, Sx *sx) +{ + USED(z); + USED(sx); +/* z->permflags = parseflags(sx); */ +} + +static void +xokunseen(Imap *z, Sx *sx) +{ + USED(z); + USED(sx); +/* z->unseen = sx->number; */ +} + +static void +xokreadwrite(Imap *z, Sx *sx) +{ + USED(z); + USED(sx); +/* z->boxmode = ORDWR; */ +} + +static void +xokreadonly(Imap *z, Sx *sx) +{ + USED(z); + USED(sx); +/* z->boxmode = OREAD; */ +} + diff -r 92fb0773aa33 sys/src/cmd/upas/nfs/imap.h --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/sys/src/cmd/upas/nfs/imap.h Sun Apr 08 00:00:00 2012 +0200 @@ -0,0 +1,24 @@ +typedef struct Imap Imap; +#pragma incomplete Imap + +void imapcheckbox(Imap *z, Box *b); +Imap* imapconnect(char *server, int mode, char *root, char *user); +int imapcopylist(Imap *z, char *nbox, Msg **m, uint nm); +void imapfetchraw(Imap *z, Part *p); +void imapfetchrawbody(Imap *z, Part *p); +void imapfetchrawheader(Imap *z, Part *p); +void imapfetchrawmime(Imap *z, Part *p); +int imapflaglist(Imap *z, int op, int flag, Msg **m, uint nm); +void imaphangup(Imap *z, int ticks); +int imapremovelist(Imap *z, Msg **m, uint nm); +int imapsearchbox(Imap *z, Box *b, char *search, Msg ***mm); + +extern int chattyimap; + +enum +{ + Unencrypted, + Starttls, + Tls, + Cmd +}; diff -r 92fb0773aa33 sys/src/cmd/upas/nfs/main.c --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/sys/src/cmd/upas/nfs/main.c Sun Apr 08 00:00:00 2012 +0200 @@ -0,0 +1,85 @@ +/* +TO DO + +can get disposition info out of imap extended structure if needed +sizes in stat/ls ? +translate character sets in =? subjects + +fetch headers, bodies on demand + +cache headers, bodies on disk + +cache message information on disk across runs + +body.jpg + +*/ + +#include "a.h" + +Imap *imap; + +void +usage(void) +{ + fprint(2, "usage: mailfs [-DVtx] [-m mtpt] [-s srvname] [-r root] [-u user] server\n"); + threadexitsall("usage"); +} + +void +threadmain(int argc, char **argv) +{ + char *server, *srvname, *root, *user; + int mode; + char *mtpt; + + srvname = nil; + root = ""; + mode = Unencrypted; + mtpt = "/mail/fs"; + user = nil; + ARGBEGIN{ + default: + usage(); + case 'D': + chatty9p++; + break; + case 'V': + chattyimap++; + break; + case 'm': + mtpt = EARGF(usage()); + break; + case 's': + srvname = EARGF(usage()); + break; + case 't': + mode = Tls; + break; + case 'u': + user = EARGF(usage()); + break; + case 'x': + mode = Cmd; + break; + case 'r': + root = EARGF(usage()); + break; + }ARGEND + + quotefmtinstall(); + fmtinstall('$', sxfmt); + + if(argc != 1) + usage(); + server = argv[0]; + + mailthreadinit(); + boxinit(); + fsinit0(); + + if((imap = imapconnect(server, mode, root, user)) == nil) + sysfatal("imapconnect: %r"); + threadpostmountsrv(&fs, srvname, mtpt, 0); +} + diff -r 92fb0773aa33 sys/src/cmd/upas/nfs/mbox.c --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/sys/src/cmd/upas/nfs/mbox.c Sun Apr 08 00:00:00 2012 +0200 @@ -0,0 +1,68 @@ +#include "a.h" + +Mailbox *hash[123]; +Mailbox **box; +uint nbox; + +static void +markboxes(int mark) +{ + Mailbox *b; + + for(i=0; imark = mark; +} + +static void +sweepboxes(void) +{ + Mailbox *b; + + for(i=0; imark){ + freembox(box[i]); + box[i] = nil; + } +} + +static Mailbox* +mboxbyname(char *name) +{ + int i; + + for(i=0; iname, name) == 0) + return box[i]; + return nil; +} + +static Mailbox* +mboxbyid(int id) +{ + if(id < 0 || id >= nbox) + return nil; + return box[id]; +} + +static Mailbox* +mboxcreate(char *name) +{ + Mailbox *b; + + b = emalloc(sizeof *b); + b->name = estrdup(name); + if(nbox%64 == 0) + box = erealloc(box, (nbox+64)*sizeof box[0]); + box[nbox++] = b; + return b; +} + +void +mboxupdate(void) +{ + markboxes(); + if(imapcmd("LIST \"\" *") < 0) + return; + sweepboxes(); +} diff -r 92fb0773aa33 sys/src/cmd/upas/nfs/mkfile --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/sys/src/cmd/upas/nfs/mkfile Sun Apr 08 00:00:00 2012 +0200 @@ -0,0 +1,20 @@ +sx = sx; + x->nsx = nsx; + x->type = SxList; + return x; +} + +int +sxwalk(Sx *sx) +{ + int i, n; + + if(sx == nil) + return 1; + switch(sx->type){ + default: + case SxAtom: + case SxString: + case SxNumber: + return 1; + case SxList: + n = 0; + for(i=0; insx; i++) + n += sxwalk(sx->sx[i]); + return n; + } +} + +void +freesx(Sx *sx) +{ + int i; + + if(sx == nil) + return; + switch(sx->type){ + case SxAtom: + case SxString: + free(sx->data); + break; + case SxList: + for(i=0; insx; i++) + freesx(sx->sx[i]); + free(sx->sx); + break; + } + free(sx); +} + +Sx* +Brdsx1(Biobuf *b) +{ + int c, len, nbr; + char *s; + vlong n; + Sx *x; + + c = Bgetc(b); + if(c == ' ') + c = Bgetc(b); + if(c < 0) + return nil; + if(c == '\r') + c = Bgetc(b); + if(c == '\n') + return nil; + if(c == ')'){ /* end of list */ + Bungetc(b); + return nil; + } + if(c == '('){ /* parenthesized list */ + x = Brdsx(b); + c = Bgetc(b); + if(c != ')') /* oops! not good */ + Bungetc(b); + return x; + } + if(c == '{'){ /* length-prefixed string */ + len = 0; + while((c = Bgetc(b)) >= 0 && isdigit(c)) + len = len*10 + c-'0'; + if(c != '}') /* oops! not good */ + Bungetc(b); + c = Bgetc(b); + if(c != '\r') /* oops! not good */ + ; + c = Bgetc(b); + if(c != '\n') /* oops! not good */ + ; + x = emalloc(sizeof *x); + x->data = emalloc(len+1); + if(Bread(b, x->data, len) != len) + ; /* oops! */ + x->data[len] = 0; + x->ndata = len; + x->type = SxString; + return x; + } + if(c == '"'){ /* quoted string */ + s = nil; + len = 0; + while((c = Bgetc(b)) >= 0 && c != '"'){ + if(c == '\\') + c = Bgetc(b); + s = erealloc(s, len+1); + s[len++] = c; + } + s = erealloc(s, len+1); + s[len] = 0; + x = emalloc(sizeof *x); + x->data = s; + x->ndata = len; + x->type = SxString; + return x; + } + if(isdigit(c)){ /* number */ + n = c-'0';; + while((c = Bgetc(b)) >= 0 && isdigit(c)) + n = n*10 + c-'0'; + Bungetc(b); + x = emalloc(sizeof *x); + x->number = n; + x->type = SxNumber; + return x; + } + /* atom */ + len = 1; + s = emalloc(1); + s[0] = c; + nbr = 0; + while((c = Bgetc(b)) >= 0 && c > ' ' && !strchr("(){}", c)){ + /* allow embedded brackets as in BODY[] */ + if(c == '['){ + if(s[0] == '[') + break; + else + nbr++; + } + if(c == ']'){ + if(nbr > 0) + nbr--; + else + break; + } + s = erealloc(s, len+1); + s[len++] = c; + } + if(c != ' ') + Bungetc(b); + s = erealloc(s, len+1); + s[len] = 0; + x = emalloc(sizeof *x); + x->type = SxAtom; + x->data = s; + x->ndata = len; + return x; +} + +int +sxfmt(Fmt *fmt) +{ + int i, paren; + Sx *sx; + + sx = va_arg(fmt->args, Sx*); + if(sx == nil) + return 0; + + switch(sx->type){ + case SxAtom: + case SxString: + return fmtprint(fmt, "%q", sx->data); + + case SxNumber: + return fmtprint(fmt, "%lld", sx->number); + + case SxList: + paren = !(fmt->flags&FmtSharp); + if(paren) + fmtrune(fmt, '('); + for(i=0; insx; i++){ + if(i) + fmtrune(fmt, ' '); + fmtprint(fmt, "%$", sx->sx[i]); + } + if(paren) + return fmtrune(fmt, ')'); + return 0; + + default: + return fmtstrcpy(fmt, "?"); + } +} + +int +oksx(Sx *sx) +{ + return sx->nsx >= 2 + && sx->sx[1]->type == SxAtom + && cistrcmp(sx->sx[1]->data, "OK") == 0; +} diff -r 92fb0773aa33 sys/src/cmd/upas/nfs/sx.h --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/sys/src/cmd/upas/nfs/sx.h Sun Apr 08 00:00:00 2012 +0200 @@ -0,0 +1,31 @@ +/* + * S-expressions as used by IMAP. + */ + +enum +{ + SxUnknown = 0, + SxAtom, + SxString, + SxNumber, + SxList +}; + +typedef struct Sx Sx; +struct Sx +{ + int type; + char *data; + int ndata; + vlong number; + Sx **sx; + int nsx; +}; + +Sx* Brdsx(Biobuf*); +Sx* Brdsx1(Biobuf*); +void freesx(Sx*); +int oksx(Sx*); +int sxfmt(Fmt*); +int sxwalk(Sx*); + diff -r 92fb0773aa33 sys/src/cmd/upas/nfs/thread.c --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/sys/src/cmd/upas/nfs/thread.c Sun Apr 08 00:00:00 2012 +0200 @@ -0,0 +1,38 @@ +#include "a.h" + +typedef struct New New; +struct New +{ + void (*fn)(void*); + void *arg; +}; + +Channel *mailthreadchan; + +void +mailthread(void (*fn)(void*), void *arg) +{ + New n; + + n.fn = fn; + n.arg = arg; + send(mailthreadchan, &n); +} + +void +mailproc(void *v) +{ + New n; + + USED(v); + while(recv(mailthreadchan, &n) == 1) + threadcreate(n.fn, n.arg, STACK); +} + +void +mailthreadinit(void) +{ + mailthreadchan = chancreate(sizeof(New), 0); + proccreate(mailproc, nil, STACK); +} + diff -r 92fb0773aa33 sys/src/cmd/upas/nfs/util.c --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/sys/src/cmd/upas/nfs/util.c Sun Apr 08 00:00:00 2012 +0200 @@ -0,0 +1,13 @@ +#include "a.h" + +void +warn(char *fmt, ...) +{ + va_list arg; + + va_start(arg, fmt); + fprint(2, "warning: "); + vfprint(2, fmt, arg); + fprint(2, "\n"); + va_end(arg); +}