aboutsummaryrefslogtreecommitdiff
path: root/src/cmd/upas/nfs
diff options
context:
space:
mode:
authorrsc <devnull@localhost>2006-02-15 12:39:09 +0000
committerrsc <devnull@localhost>2006-02-15 12:39:09 +0000
commit941e17134e92de7f12977f1899860b57bbf83330 (patch)
treee0c7dc5b23b603b24c2ec0cb8d3544f91bb36101 /src/cmd/upas/nfs
parent1ea614ffaf9378df45410995d0a8c13042bba123 (diff)
downloadplan9port-941e17134e92de7f12977f1899860b57bbf83330.tar.gz
plan9port-941e17134e92de7f12977f1899860b57bbf83330.tar.bz2
plan9port-941e17134e92de7f12977f1899860b57bbf83330.zip
imap-based new upas/fs
Diffstat (limited to 'src/cmd/upas/nfs')
-rw-r--r--src/cmd/upas/nfs/box.c318
-rw-r--r--src/cmd/upas/nfs/box.h133
-rw-r--r--src/cmd/upas/nfs/decode.c257
-rw-r--r--src/cmd/upas/nfs/fs.c1259
-rw-r--r--src/cmd/upas/nfs/fs.h2
-rw-r--r--src/cmd/upas/nfs/imap.c1715
-rw-r--r--src/cmd/upas/nfs/imap.h23
-rw-r--r--src/cmd/upas/nfs/main.c68
-rw-r--r--src/cmd/upas/nfs/mbox.c68
-rw-r--r--src/cmd/upas/nfs/mkfile18
-rw-r--r--src/cmd/upas/nfs/msg.c9
-rw-r--r--src/cmd/upas/nfs/sx.c217
-rw-r--r--src/cmd/upas/nfs/sx.h31
-rw-r--r--src/cmd/upas/nfs/thread.c37
-rw-r--r--src/cmd/upas/nfs/util.c13
15 files changed, 4168 insertions, 0 deletions
diff --git a/src/cmd/upas/nfs/box.c b/src/cmd/upas/nfs/box.c
new file mode 100644
index 00000000..044edf20
--- /dev/null
+++ b/src/cmd/upas/nfs/box.c
@@ -0,0 +1,318 @@
+#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; i<nboxes; i++)
+ if(boxes[i] && strcmp(boxes[i]->name, name) == 0)
+ return boxes[i];
+ return nil;
+}
+
+Box*
+subbox(Box *b, char *elem)
+{
+ int i;
+
+ for(i=0; i<b->nsub; 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; i<nboxes; i++)
+ if(boxes[i] && boxes[i]->id == 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; i<b->nmsg; 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; i<p->nsub; 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; i<m->npart; 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;
+ snprint(buf, sizeof buf, "Mail/%s/%ud", m->box->name, m->id);
+ 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; i<box->nmsg; 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; i<box->nmsg; 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 --git a/src/cmd/upas/nfs/box.h b/src/cmd/upas/nfs/box.h
new file mode 100644
index 00000000..2eeb93e9
--- /dev/null
+++ b/src/cmd/upas/nfs/box.h
@@ -0,0 +1,133 @@
+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,
+};
+
+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* 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 --git a/src/cmd/upas/nfs/decode.c b/src/cmd/upas/nfs/decode.c
new file mode 100644
index 00000000..0e46dbff
--- /dev/null
+++ b/src/cmd/upas/nfs/decode.c
@@ -0,0 +1,257 @@
+/* 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)
+{
+ char *p, *ep;
+ uchar *eout, *out0;
+
+ out0 = out;
+ eout = out+lim;
+ for(p=in, ep=in+n; p<ep && out<eout; ){
+ if(*p == '_'){
+ *out++ = ' ';
+ p++;
+ }
+ else if(*p == '='){
+ if(p+1 >= ep)
+ break;
+ if(*(p+1) == '\n'){
+ p += 2;
+ continue;
+ }
+ if(p+3 > ep)
+ break;
+ *out++ = unhex(p+1);
+ p += 3;
+ }else
+ *out++ = *p++;
+ }
+ return out-out0;
+}
+
+char*
+decode(int kind, char *s, int *len)
+{
+ char *t;
+ int l;
+
+ if(s == nil)
+ return s;
+ switch(kind){
+ case QuotedPrintable:
+ l = strlen(s)+1;
+ t = emalloc(l);
+ l = decqp((uchar*)t, l, s, l-1);
+ *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-1251", "cp1251",
+ "koi8-r", "koi8",
+};
+
+char*
+tcs(char *charset, char *s)
+{
+ static char buf[4096];
+ int i, n;
+ int fd[3], p[2], pp[2];
+ uchar *us;
+ char *t, *u;
+ char *argv[4];
+ Rune r;
+
+ 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; i<nelem(tcstab); i++)
+ if(cistrcmp(charset, tcstab[i].mime) == 0)
+ goto tcs;
+ goto latin1;
+
+tcs:
+ argv[0] = "tcs";
+ argv[1] = "-f";
+ argv[2] = charset;
+ argv[3] = nil;
+
+ if(pipe(p) < 0 || pipe(pp) < 0)
+ sysfatal("pipe: %r");
+ fd[0] = p[0];
+ fd[1] = pp[0];
+ fd[2] = dup(2, -1);
+ if(threadspawnl(fd, "tcs", "tcs", "-f", tcstab[i].tcs, nil) < 0){
+ close(p[0]);
+ close(p[1]);
+ close(pp[0]);
+ close(pp[1]);
+ close(fd[2]);
+ goto latin1;
+ }
+ close(p[0]);
+ close(pp[0]);
+ write(p[1], s, strlen(s));
+ close(p[1]);
+ n = readn(pp[1], buf, sizeof buf-1);
+ close(pp[1]);
+ if(n <= 0)
+ goto latin1;
+ free(s);
+ buf[n] = 0;
+ return estrdup(buf);
+}
+
+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(QuotedPrintable, 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<nelem(test); i++)
+ print("%s\n\t%s\n", test[i], unrfc2047(estrdup(test[i])));
+ threadexitsall(0);
+}
+
+#endif
diff --git a/src/cmd/upas/nfs/fs.c b/src/cmd/upas/nfs/fs.c
new file mode 100644
index 00000000..eda3d666
--- /dev/null
+++ b/src/cmd/upas/nfs/fs.c
@@ -0,0 +1,1259 @@
+/*
+ * Mail file system.
+ *
+ * Serve the bulk of requests out of memory, so they can
+ * be in the main loop (will never see their flushes).
+ * Some requests do block and they get handled in
+ * separate threads. They're all okay to give up on
+ * early, though, so we just respond saying interrupted
+ * and then when they finish, silently discard the request.
+
+TO DO:
+
+ decode subject, etc.
+ decode body
+
+ digest
+ disposition
+ filename
+
+ ctl messages
+
+ fetch mail on demand
+
+ */
+
+#include "a.h"
+
+enum
+{
+ /* directories */
+ Qroot,
+ Qbox,
+ Qmsg,
+
+ /* control files */
+ Qctl,
+ Qboxctl,
+ Qsearch,
+
+ /* message header - same order as struct Hdr */
+ Qdate,
+ Qsubject,
+ Qfrom,
+ Qsender,
+ Qreplyto,
+ Qto,
+ Qcc,
+ Qbcc,
+ Qinreplyto,
+ Qmessageid,
+
+ /* part data - same order as stuct Part */
+ Qtype,
+ Qidstr,
+ Qdesc,
+ Qencoding, /* only here temporarily! */
+ Qcharset,
+ Qraw,
+ Qrawheader,
+ Qrawbody,
+ Qmimeheader,
+
+ /* part numbers - same order as struct Part */
+ Qsize,
+ Qlines,
+
+ /* other message files */
+ Qbody,
+ Qheader,
+ Qdigest,
+ Qdisposition,
+ Qfilename,
+ Qflags,
+ Qinfo,
+ Qrawunix,
+ Qunixdate,
+ Qunixheader,
+
+ Qfile0 = Qbody,
+ Qnfile = Qunixheader+1-Qfile0,
+};
+
+static char Egreg[] = "gone postal";
+static char Enobox[] = "no such mailbox";
+static char Enomsg[] = "no such message";
+static char Eboxgone[] = "mailbox not available";
+static char Emsggone[] = "message not available";
+static char Eperm[] = "permission denied";
+static char Ebadctl[] = "bad control message";
+
+Channel *fsreqchan;
+Srv fs;
+Qid rootqid;
+ulong t0;
+
+void
+responderror(Req *r)
+{
+ char e[ERRMAX];
+
+ rerrstr(e, sizeof e);
+ respond(r, e);
+}
+
+int
+qtype(Qid q)
+{
+ return q.path&0x3F;
+}
+
+int
+qboxid(Qid q)
+{
+ return (q.path>>40)&0xFFFF;
+}
+
+int
+qmsgid(Qid q)
+{
+ return ((q.path>>32)&0xFF000000) | ((q.path>>16)&0xFFFFFF);
+}
+
+int
+qpartid(Qid q)
+{
+ return ((q.path>>6)&0x3FF);
+}
+
+Qid
+qid(int ctl, Box *box, Msg *msg, Part *part)
+{
+ Qid q;
+
+ q.type = 0;
+ if(ctl == Qroot || ctl == Qbox || ctl == Qmsg)
+ q.type = QTDIR;
+ q.path = (vlong)((msg ? msg->id : 0)&0xFF000000)<<32;
+ q.path |= (vlong)((msg ? msg->id : 0)&0xFFFFFF)<<16;
+ q.path |= (vlong)((box ? box->id : 0)&0xFFFF)<<40;
+ q.path |= ((part ? part->ix : 0)&0x3FF)<<6;
+ q.path |= ctl&0x3F;
+ q.vers = box ? box->validity : 0;
+ return q;
+}
+
+int
+parseqid(Qid q, Box **box, Msg **msg, Part **part)
+{
+ *msg = nil;
+ *part = nil;
+
+ *box = boxbyid(qboxid(q));
+ if(*box){
+ *msg = msgbyid(*box, qmsgid(q));
+ }
+ if(*msg)
+ *part = partbyid(*msg, qpartid(q));
+ return qtype(q);
+}
+
+static struct {
+ int type;
+ char *name;
+} typenames[] = {
+ Qbody, "body",
+ Qbcc, "bcc",
+ Qcc, "cc",
+ Qdate, "date",
+ Qfilename, "filename",
+ Qflags, "flags",
+ Qfrom, "from",
+ Qheader, "header",
+ Qinfo, "info",
+ Qinreplyto, "inreplyto",
+ Qlines, "lines",
+ Qmimeheader, "mimeheader",
+ Qmessageid, "messageid",
+ Qraw, "raw",
+ Qrawunix, "rawunix",
+ Qrawbody, "rawbody",
+ Qrawheader, "rawheader",
+ Qreplyto, "replyto",
+ Qsender, "sender",
+ Qsubject, "subject",
+ Qto, "to",
+ Qtype, "type",
+ Qunixdate, "unixdate",
+ Qunixheader, "unixheader",
+ Qidstr, "idstr",
+ Qdesc, "desc",
+ Qencoding, "encoding",
+ Qcharset, "charset",
+};
+
+char*
+nameoftype(int t)
+{
+ int i;
+
+ for(i=0; 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);
+}
+
+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;
+
+ if(p->msg->part[0] == p)
+ t = p->rawbody;
+ else
+ t = p->raw;
+ if(t == nil)
+ return;
+
+ len = -1;
+ if(p->encoding && cistrcmp(p->encoding, "quoted-printable") == 0)
+ t = decode(QuotedPrintable, t, &len);
+ else if(p->encoding && cistrcmp(p->encoding, "base64") == 0)
+ t = decode(Base64, t, &len);
+ else
+ t = estrdup(t);
+
+ if(p->charset){
+ t = tcs(p->charset, t);
+ len = -1;
+ }
+ p->body = t;
+ if(len == -1)
+ p->nbody = strlen(t);
+ else
+ p->nbody = len;
+}
+
+static Qid ZQ;
+
+static int
+filedata(int type, Box *box, Msg *msg, Part *part, char **pp, int *len, int *freeme, int force, Qid q)
+{
+ int i, inquote, n, t;
+ char *from, *s;
+ static char buf[256];
+ Fmt fmt;
+
+ *pp = nil;
+ *freeme = 0;
+ if(len)
+ *len = -1;
+
+ if(msg == nil || part == nil){
+ werrstr(Emsggone);
+ return -1;
+ }
+ switch(type){
+ case Qdate:
+ case Qsubject:
+ case Qfrom:
+ case Qsender:
+ case Qreplyto:
+ case Qto:
+ case Qcc:
+ case Qbcc:
+ case Qinreplyto:
+ case Qmessageid:
+ if(part->hdr == nil){
+ werrstr(Emsggone);
+ return -1;
+ }
+ *pp = ((char**)&part->hdr->date)[type-Qdate];
+ return 0;
+
+ case Qunixdate:
+ strcpy(buf, ctime(msg->date));
+ *pp = buf;
+ return 0;
+
+ case Qunixheader:
+ if(part->hdr == nil){
+ werrstr(Emsggone);
+ return -1;
+ }
+ from = part->hdr->from;
+ if(from == nil)
+ from = "???";
+ else{
+ inquote = 0;
+ for(; *from; from++){
+ if(*from == '\'')
+ inquote = !inquote;
+ if(!inquote && *from == ' '){
+ from++;
+ break;
+ }
+ }
+ if(*from == 0)
+ from = part->hdr->from;
+ }
+ n = snprint(buf, sizeof buf, "From %s %s", from, ctime(msg->date));
+ if(n+1 < sizeof buf){
+ *pp = buf;
+ return 0;
+ }
+ fmtstrinit(&fmt);
+ fmtprint(&fmt, "From %s %s", from, ctime(msg->date));
+ s = fmtstrflush(&fmt);
+ if(s){
+ *pp = s;
+ *freeme = 1;
+ }else
+ *pp = buf;
+ return 0;
+
+ case Qtype:
+ case Qidstr:
+ case Qdesc:
+ case Qencoding:
+ case Qcharset:
+ case Qraw:
+ case Qrawheader:
+ case Qrawbody:
+ case Qmimeheader:
+ *pp = ((char**)&part->type)[type-Qtype];
+ if(*pp == nil && force){
+ switch(type){
+ case Qraw:
+ imapfetchraw(imap, part);
+ break;
+ case Qrawheader:
+ imapfetchrawheader(imap, part);
+ break;
+ case Qrawbody:
+ imapfetchrawbody(imap, part);
+ break;
+ case Qmimeheader:
+ imapfetchrawmime(imap, part);
+ break;
+ default:
+ return 0;
+ }
+ /*
+ * We ran fetchsomething, which might have changed
+ * the mailbox contents. Msg might even be gone.
+ */
+ t = parseqid(q, &box, &msg, &part);
+ if(t != type || msg == nil || part == nil)
+ return 0;
+ *pp = ((char**)&part->type)[type-Qtype];
+ }
+ return 0;
+
+ case Qbody:
+ if(part->body){
+ *pp = part->body;
+ if(len)
+ *len = part->nbody;
+ return 0;
+ }
+ if(!force)
+ return 0;
+ if(part->rawbody == nil){
+ if(part->msg->part[0] == part)
+ imapfetchrawbody(imap, part);
+ else
+ imapfetchraw(imap, part);
+ t = parseqid(q, &box, &msg, &part);
+ if(t != type || msg == nil || part == nil)
+ return 0;
+ }
+ mkbody(part, q);
+ *pp = part->body;
+ if(len)
+ *len = part->nbody;
+ return 0;
+
+ case Qsize:
+ case Qlines:
+ n = ((uint*)&part->size)[type-Qsize];
+ snprint(buf, sizeof buf, "%d", n);
+ *pp = buf;
+ return 0;
+
+ case Qflags:
+ s = buf;
+ *s = 0;
+ for(i=0; 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 %lud %s", msg->date, ctime(msg->date));
+ if(msg->flags){
+ filedata(Qflags, box, msg, part, pp, nil, freeme, 0, ZQ);
+ fmtprint(&fmt, "flags %s\n", buf);
+ }
+ }
+ if(part->hdr){
+ if(part->hdr->digest)
+ fmtprint(&fmt, "digest %s\n", part->hdr->digest);
+ if(part->hdr->from)
+ fmtprint(&fmt, "from %s\n", part->hdr->from);
+ if(part->hdr->to)
+ fmtprint(&fmt, "to %s\n", part->hdr->to);
+ if(part->hdr->cc)
+ fmtprint(&fmt, "cc %s\n", part->hdr->cc);
+ if(part->hdr->replyto)
+ fmtprint(&fmt, "replyto %s\n", part->hdr->replyto);
+ if(part->hdr->bcc)
+ fmtprint(&fmt, "bcc %s\n", part->hdr->bcc);
+ if(part->hdr->inreplyto)
+ fmtprint(&fmt, "inreplyto %s\n", part->hdr->inreplyto);
+ if(part->hdr->date)
+ fmtprint(&fmt, "date %s\n", part->hdr->date);
+ if(part->hdr->sender)
+ fmtprint(&fmt, "sender %s\n", part->hdr->sender);
+ if(part->hdr->messageid)
+ fmtprint(&fmt, "messageid %s\n", part->hdr->messageid);
+ if(part->hdr->subject)
+ fmtprint(&fmt, "subject %s\n", part->hdr->subject);
+ }
+ if(part->type)
+ fmtprint(&fmt, "type %s\n", part->type);
+ if(part->lines)
+ fmtprint(&fmt, "lines %d\n", part->lines);
+ // fmtprint(&fmt, "disposition %s\n", "" /* disposition */);
+ // fmtprint(&fmt, "filename %s\n", "" /* filename */);
+ // fmtprint(&fmt, "digest %s\n", "" /* digest */);
+ s = fmtstrflush(&fmt);
+ if(s == nil)
+ s = estrdup("");
+ *freeme = 1;
+ *pp = s;
+ return 0;
+
+ case Qheader:
+ if(part->hdr == nil)
+ return 0;
+ fmtstrinit(&fmt);
+ if(part == msg->part[0])
+ fmtprint(&fmt, "Date: %s", ctime(msg->date));
+ else
+ fmtprint(&fmt, "Date: %s\n", part->hdr->date);
+ addaddrs(&fmt, "To", part->hdr->to);
+ addaddrs(&fmt, "From", part->hdr->from);
+ if(part->hdr->from==nil
+ || (part->hdr->sender && strcmp(part->hdr->sender, part->hdr->from) != 0))
+ addaddrs(&fmt, "Sender", part->hdr->sender);
+ if(part->hdr->from==nil
+ || (part->hdr->replyto && strcmp(part->hdr->replyto, part->hdr->from) != 0))
+ addaddrs(&fmt, "Reply-To", part->hdr->replyto);
+ addaddrs(&fmt, "Subject", part->hdr->subject);
+ s = fmtstrflush(&fmt);
+ if(s == nil)
+ s = estrdup("");
+ *freeme = 1;
+ *pp = s;
+ return 0;
+
+ default:
+ werrstr(Egreg);
+ return -1;
+ }
+}
+
+int
+filldir(Dir *d, int type, Box *box, Msg *msg, Part *part)
+{
+ int freeme, len;
+ char *s;
+
+ memset(d, 0, sizeof *d);
+ if(box){
+ d->atime = box->time;
+ d->mtime = box->time;
+ }else{
+ d->atime = t0;
+ d->mtime = t0;
+ }
+ d->uid = estrdup9p("upas");
+ d->gid = estrdup9p("upas");
+ d->muid = estrdup9p("upas");
+ d->qid = qid(type, box, msg, part);
+
+ switch(type){
+ case Qroot:
+ case Qbox:
+ case Qmsg:
+ d->mode = 0555|DMDIR;
+ if(box && !(box->flags&FlagNoInferiors))
+ d->mode = 0775|DMDIR;
+ break;
+ case Qctl:
+ case Qboxctl:
+ d->mode = 0222;
+ break;
+ case Qsearch:
+ d->mode = 0666;
+ break;
+
+ case Qflags:
+ d->mode = 0666;
+ goto msgfile;
+ default:
+ d->mode = 0444;
+ msgfile:
+ if(filedata(type, box, msg, part, &s, &len, &freeme, 0, ZQ) >= 0){
+ if(s){
+ if(len == -1)
+ d->length = strlen(s);
+ else
+ d->length = len;
+ if(freeme)
+ free(s);
+ }
+ }else if(type == Qraw && msg && part == msg->part[0])
+ d->length = msg->size;
+ break;
+ }
+
+ switch(type){
+ case Qroot:
+ d->name = estrdup9p("/");
+ break;
+ case Qbox:
+ if(box == nil){
+ werrstr(Enobox);
+ return -1;
+ }
+ d->name = estrdup9p(box->elem);
+ break;
+ case Qmsg:
+ if(msg == nil){
+ werrstr(Enomsg);
+ return -1;
+ }
+ if(part == nil || part == msg->part[0])
+ d->name = esmprint("%d", msg->id);
+ else
+ d->name = esmprint("%d", part->pix+1);
+ break;
+ case Qctl:
+ case Qboxctl:
+ d->name = estrdup9p("ctl");
+ break;
+ case Qsearch:
+ d->name = estrdup9p("search");
+ break;
+ default:
+ d->name = estrdup9p(nameoftype(type));
+ break;
+ }
+ return 0;
+}
+
+static void
+fsstat(Req *r)
+{
+ int type;
+ Box *box;
+ Msg *msg;
+ Part *part;
+
+ type = parseqid(r->fid->qid, &box, &msg, &part);
+ if(filldir(&r->d, type, box, msg, part) < 0)
+ responderror(r);
+ else
+ respond(r, nil);
+}
+
+int
+rootgen(int i, Dir *d, void *aux)
+{
+ USED(aux);
+
+ if(i == 0)
+ return filldir(d, Qctl, nil, nil, nil);
+ i--;
+ if(i < rootbox->nsub)
+ return filldir(d, Qbox, rootbox->sub[i], nil, nil);
+ return -1;
+}
+
+int
+boxgen(int i, Dir *d, void *aux)
+{
+ Box *box;
+
+ box = aux;
+if(i==0) fprint(2, "boxgen %s %d nsub=%d nmsg=%d\n", box->name, i, box->nsub, box->nmsg);
+ if(i == 0)
+ return filldir(d, Qboxctl, box, nil, nil);
+ i--;
+ if(i == 0)
+ return filldir(d, Qsearch, box, nil, nil);
+ if(i < box->nsub)
+ return filldir(d, Qbox, box->sub[i], nil, nil);
+ i -= box->nsub;
+ if(i < box->nmsg)
+ return filldir(d, Qmsg, box, box->msg[i], nil);
+ return -1;
+}
+
+static int msgdir[] = {
+ Qtype,
+ Qbody, Qbcc, Qcc, Qdate, Qflags, Qfrom, Qheader, Qinfo,
+ Qinreplyto, Qlines, Qmimeheader, Qmessageid,
+ Qraw, Qrawunix, Qrawbody, Qrawheader,
+ Qreplyto, Qsender, Qsubject, Qto,
+ Qunixdate, Qunixheader
+};
+static int mimemsgdir[] = {
+ Qtype,
+ Qbody, Qbcc, Qcc, Qdate, Qfrom, Qheader, Qinfo,
+ Qinreplyto, Qlines, Qmimeheader, Qmessageid,
+ Qraw, Qrawunix, Qrawbody, Qrawheader,
+ Qreplyto, Qsender, Qsubject, Qto,
+};
+static int mimedir[] = {
+ Qtype,
+ Qbody,
+ Qmimeheader,
+ Qraw,
+};
+
+int
+msggen(int i, Dir *d, void *aux)
+{
+ Box *box;
+ Msg *msg;
+ Part *part;
+
+ part = aux;
+ msg = part->msg;
+ box = msg->box;
+ if(part->ix == 0){
+ if(i < nelem(msgdir))
+ return filldir(d, msgdir[i], box, msg, part);
+ i -= nelem(msgdir);
+ }else if(part->type && strcmp(part->type, "message/rfc822") == 0){
+ if(i < nelem(mimemsgdir))
+ return filldir(d, mimemsgdir[i], box, msg, part);
+ i -= nelem(mimemsgdir);
+ }else{
+ if(i < nelem(mimedir))
+ return filldir(d, mimedir[i], box, msg, part);
+ i -= nelem(mimedir);
+ }
+ if(i < part->nsub)
+ return filldir(d, Qmsg, box, msg, part->sub[i]);
+ return -1;
+}
+
+enum
+{
+ CMhangup,
+};
+static Cmdtab ctltab[] =
+{
+ CMhangup, "hangup", 2,
+};
+
+enum
+{
+ CMdelete,
+ CMrefresh,
+ CMreplied,
+ CMread,
+ CMsave,
+ CMjunk,
+ CMnonjunk,
+};
+static Cmdtab boxctltab[] =
+{
+ CMdelete, "delete", 0,
+ CMrefresh, "refresh", 1,
+ CMreplied, "replied", 0,
+ CMread, "read", 0,
+ CMsave, "save", 0,
+ CMjunk, "junk", 0,
+ CMnonjunk, "nonjunk", 0,
+};
+
+static void
+fsread(Req *r)
+{
+ char *s;
+ int type, len, freeme;
+ Box *box;
+ Msg *msg;
+ Part *part;
+
+ switch(type = parseqid(r->fid->qid, &box, &msg, &part)){
+ case Qroot:
+ dirread9p(r, rootgen, nil);
+ respond(r, nil);
+ return;
+
+ case Qbox:
+ if(box == nil){
+ respond(r, Eboxgone);
+ return;
+ }
+ if(box->nmsg == 0)
+ imapcheckbox(imap, box);
+ parseqid(r->fid->qid, &box, &msg, &part);
+ if(box == nil){
+ respond(r, Eboxgone);
+ return;
+ }
+ dirread9p(r, boxgen, box);
+ respond(r, nil);
+ return;
+
+ case Qmsg:
+ if(msg == nil || part == nil){
+ respond(r, Emsggone);
+ return;
+ }
+ dirread9p(r, msggen, part);
+ respond(r, nil);
+ return;
+
+ case Qctl:
+ case Qboxctl:
+ respond(r, Egreg);
+ return;
+
+ case Qsearch:
+ readstr(r, r->fid->aux);
+ respond(r, nil);
+ return;
+
+ default:
+ if(filedata(type, box, msg, part, &s, &len, &freeme, 1, r->fid->qid) < 0){
+ responderror(r);
+ return;
+ }
+ if(s && len == -1)
+ len = strlen(s);
+ readbuf(r, s, len);
+ if(freeme)
+ free(s);
+ respond(r, nil);
+ return;
+ }
+}
+
+int
+mkmsglist(Box *box, char **f, int nf, Msg ***mm)
+{
+ int i, nm;
+ Msg **m;
+
+ m = emalloc(nf*sizeof m[0]);
+ nm = 0;
+ for(i=0; 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;
+
+ 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 --git a/src/cmd/upas/nfs/fs.h b/src/cmd/upas/nfs/fs.h
new file mode 100644
index 00000000..83ff8ba2
--- /dev/null
+++ b/src/cmd/upas/nfs/fs.h
@@ -0,0 +1,2 @@
+extern Srv fs;
+void fsinit0(void);
diff --git a/src/cmd/upas/nfs/imap.c b/src/cmd/upas/nfs/imap.c
new file mode 100644
index 00000000..80ca59cb
--- /dev/null
+++ b/src/cmd/upas/nfs/imap.c
@@ -0,0 +1,1715 @@
+/*
+ * 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 <libsec.h>
+
+struct Imap
+{
+ int connected;
+ int autoreconnect;
+ int ticks; /* until boom! */
+ char* server;
+ 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,
+ "\\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)
+{
+ Imap *z;
+
+ fmtinstall('H', encodefmt);
+ fmtinstall('Z', imapquote);
+
+ z = emalloc(sizeof *z);
+ z->server = estrdup(server);
+ z->mode = mode;
+ 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, "<I %#$\n", sx);
+ if(sx->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((up = auth_getuserpasswd(auth_getkey, "proto=pass role=client service=imap server=%q", z->server)) == 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; i<nboxes; i++){
+ boxes[i]->mark = 1;
+ boxes[i]->exists = 0;
+ boxes[i]->maxseen = 0;
+ }
+ if(imapcmd(z, nil, "LIST %Z *", "") < 0)
+ return -1;
+ if(z->nextbox && z->nextbox->mark)
+ z->nextbox = nil;
+ for(r=boxes, w=boxes, e=boxes+nboxes; r<e; r++){
+ if((*r)->mark)
+{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; i<b->nmsg; 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; r<e; r++){
+ if((*r)->imapid == 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<nm; i++){
+ if(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;
+
+ if(nm == 0)
+ return 0;
+
+ qlock(&z->lk);
+ name = esmprint("%Z", nbox);
+ 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; i<nelem(flagstab); i++){
+ if(flagstab[i].flag & flag){
+ fmtprint(&fmt, "%s%s", sep, flagstab[i].name);
+ sep = " ";
+ }
+ }
+ fmtprint(&fmt, ")");
+ s = fmtstrflush(&fmt);
+
+ qlock(&z->lk);
+ 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; i<nuid; i++)
+ if((m[nm] = msgbyimapuid(b, uid[i], 0)) != nil)
+ nm++;
+ *mm = m;
+ free(uid);
+ return nm;
+}
+
+void
+imapcheckbox(Imap *z, Box *b)
+{
+ if(b == nil)
+ return;
+ qlock(&z->lk);
+ 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);
+ tmp = esmprint("%s:993", server);
+ if(threadspawnl(fd, "/usr/sbin/stunnel", "stunnel", "-c", "-r", tmp, nil) < 0){
+ 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; i<v->nsx; i++){
+ if(v->sx[i]->type != SxAtom)
+ continue;
+ for(j=0; j<nelem(flagstab); j++)
+ if(cistrcmp(v->sx[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;
+ }
+ memset(&tm, 0, sizeof tm);
+ p = v->data;
+ tm.mday = atoi(p);
+ tm.mon = parsemon(p+3);
+ if(tm.mon == -1)
+ goto bad;
+ tm.year = atoi(p+7) - 1900;
+ tm.hour = atoi(p+12);
+ tm.min = atoi(p+15);
+ tm.sec = atoi(p+18);
+ 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; i<nelem(unextab); i++){
+ if(unextab[i].num == num && cistrcmp(unextab[i].name, name) == 0){
+ if(unextab[i].fmt && !sxmatch(sx, unextab[i].fmt)){
+ warn("malformed %s: %$", name, sx);
+ continue;
+ }
+ unextab[i].fn(z, sx);
+ }
+ }
+}
+
+
+static void
+xlist(Imap *z, Sx *sx)
+{
+ int inbox;
+ char *s, *t;
+ Box *box;
+
+ if(sx->sx[3]->data && strcmp(sx->sx[3]->data, "/") != 0)
+ warn("box separator %q not / - need to implement translation", sx->sx[3]->data);
+ s = estrdup(sx->sx[4]->data);
+ if(sx->sx[3]->data && strcmp(sx->sx[3]->data, "/") != 0){
+ s = gsub(s, "/", "_");
+ s = gsub(s, sx->sx[3]->data, "/");
+ }
+
+ /*
+ * Plan 9 calls the main mailbox mbox.
+ * Rename any existing mbox by appending a $.
+ */
+ inbox = 0;
+ if(strncmp(s, "mbox", 4) == 0){
+ t = emalloc(strlen(s)+2);
+ strcpy(t, s);
+ strcat(t, "$");
+ free(s);
+ s = t;
+ }else if(cistrcmp(s, "INBOX") == 0){
+ inbox = 1;
+ free(s);
+ s = estrdup("mbox");
+ }
+
+ 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)
+{
+ if(z->box)
+ z->box->flags = parseflags(sx->sx[2]);
+}
+
+static void
+xbye(Imap *z, Sx *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; i<b->nmsg; 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; i<z->nuid; 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; i<sx->nsx; i+=2){
+ if(isatom(sx->sx[i], "UID")){
+ if(sx->sx[i+1]->type == SxNumber){
+ uid = sx->sx[i+1]->number;
+ goto haveuid;
+ }
+ }
+ }
+ 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; i<sx->nsx; i+=2){
+ for(j=0; j<nelem(msgtab); j++)
+ if(isatom(sx->sx[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;
+
+ 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; i<v->nsx; 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)
+{
+ hdrfree(msg->part[0]->hdr);
+ msg->part[0]->hdr = parseenvelope(v);
+}
+
+static struct {
+ char *name;
+ int offset;
+} paramtab[] = {
+ "charset", offsetof(Part, charset),
+};
+
+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; i<v->nsx; i+=2){
+ s = nstring(v->sx[i]);
+ t = nstring(v->sx[i+1]);
+ for(j=0; j<nelem(paramtab); j++){
+ if(cistrcmp(paramtab[j].name, s) == 0){
+ p = (char**)((char*)part+paramtab[j].offset);
+ free(*p);
+ *p = t;
+ t = nil;
+ break;
+ }
+ }
+ free(s);
+ free(t);
+ }
+}
+
+static void
+parsestructure(Part *part, Sx *v)
+{
+ int i;
+ char *s, *t;
+
+ if(isnil(v))
+ return;
+ if(v->type != SxList){
+ bad:
+ warn("bad structure: %$", v);
+ return;
+ }
+ if(islist(v->sx[0])){
+ /* multipart */
+ for(i=0; i<v->nsx && 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)
+{
+ 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;
+ msgplumb(msg, 0);
+}
+
+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; i<nelem(oktab); i++){
+ if(cistrcmp(name, oktab[i].name) == 0){
+ if(oktab[i].fmt && (arg==nil || arg->type != 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; i<b->nmsg; i++)
+ msgfree(b->msg[i]);
+ free(b->msg);
+ b->msg = nil;
+ b->nmsg = 0;
+ }
+}
+
+static void
+xokpermflags(Imap *z, Sx *sx)
+{
+// z->permflags = parseflags(sx);
+}
+
+static void
+xokunseen(Imap *z, Sx *sx)
+{
+// z->unseen = sx->number;
+}
+
+static void
+xokreadwrite(Imap *z, Sx *sx)
+{
+// z->boxmode = ORDWR;
+}
+
+static void
+xokreadonly(Imap *z, Sx *sx)
+{
+// z->boxmode = OREAD;
+}
+
diff --git a/src/cmd/upas/nfs/imap.h b/src/cmd/upas/nfs/imap.h
new file mode 100644
index 00000000..c4677a97
--- /dev/null
+++ b/src/cmd/upas/nfs/imap.h
@@ -0,0 +1,23 @@
+typedef struct Imap Imap;
+
+void imapcheckbox(Imap *z, Box *b);
+Imap* imapconnect(char *server, int mode);
+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 --git a/src/cmd/upas/nfs/main.c b/src/cmd/upas/nfs/main.c
new file mode 100644
index 00000000..aa37d8bd
--- /dev/null
+++ b/src/cmd/upas/nfs/main.c
@@ -0,0 +1,68 @@
+/*
+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 [-t] server\n");
+ threadexitsall("usage");
+}
+
+void
+threadmain(int argc, char **argv)
+{
+ char *server;
+ int mode;
+
+ mode = Unencrypted;
+ ARGBEGIN{
+ default:
+ usage();
+ case 'D':
+ chatty9p++;
+ break;
+ case 'V':
+ chattyimap++;
+ break;
+ case 't':
+ mode = Tls;
+ break;
+ case 'x':
+ mode = Cmd;
+ break;
+ }ARGEND
+
+ quotefmtinstall();
+ fmtinstall('$', sxfmt);
+
+ if(argc != 1)
+ usage();
+ server = argv[0];
+
+ mailthreadinit();
+ boxinit();
+ fsinit0();
+
+ if((imap = imapconnect(server, mode)) == nil)
+ sysfatal("imapconnect: %r");
+ threadpostmountsrv(&fs, "mail", nil, 0);
+}
+
diff --git a/src/cmd/upas/nfs/mbox.c b/src/cmd/upas/nfs/mbox.c
new file mode 100644
index 00000000..5f90f0a2
--- /dev/null
+++ b/src/cmd/upas/nfs/mbox.c
@@ -0,0 +1,68 @@
+#include "a.h"
+
+Mailbox *hash[123];
+Mailbox **box;
+uint nbox;
+
+static void
+markboxes(int mark)
+{
+ Mailbox *b;
+
+ for(i=0; i<nbox; i++)
+ if(box[i])
+ box[i]->mark = mark;
+}
+
+static void
+sweepboxes(void)
+{
+ Mailbox *b;
+
+ for(i=0; i<nbox; i++)
+ if(box[i] && box[i]->mark){
+ freembox(box[i]);
+ box[i] = nil;
+ }
+}
+
+static Mailbox*
+mboxbyname(char *name)
+{
+ int i;
+
+ for(i=0; i<nbox; i++)
+ if(box[i] && strcmp(box[i]->name, 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 --git a/src/cmd/upas/nfs/mkfile b/src/cmd/upas/nfs/mkfile
new file mode 100644
index 00000000..7716ab58
--- /dev/null
+++ b/src/cmd/upas/nfs/mkfile
@@ -0,0 +1,18 @@
+<$PLAN9/src/mkhdr
+
+TARG=mailfs
+
+OFILES=\
+ box.$O\
+ decode.$O\
+ fs.$O\
+ imap.$O\
+ main.$O\
+ sx.$O\
+ thread.$O\
+ util.$O\
+
+HFILES=a.h box.h imap.h sx.h
+
+<$PLAN9/src/mkone
+
diff --git a/src/cmd/upas/nfs/msg.c b/src/cmd/upas/nfs/msg.c
new file mode 100644
index 00000000..f4a26862
--- /dev/null
+++ b/src/cmd/upas/nfs/msg.c
@@ -0,0 +1,9 @@
+#include "a.h"
+
+something about a cache of msgs here
+
+cache flushes optionally to disk
+ before being tossed out
+
+reload from disk, then from server
+
diff --git a/src/cmd/upas/nfs/sx.c b/src/cmd/upas/nfs/sx.c
new file mode 100644
index 00000000..65d338c2
--- /dev/null
+++ b/src/cmd/upas/nfs/sx.c
@@ -0,0 +1,217 @@
+#include "a.h"
+
+Sx *Brdsx1(Biobuf*);
+
+Sx*
+Brdsx(Biobuf *b)
+{
+ Sx **sx, *x;
+ int nsx;
+
+ nsx = 0;
+ sx = nil;
+ while((x = Brdsx1(b)) != nil){
+ sx = erealloc(sx, (nsx+1)*sizeof sx[0]);
+ sx[nsx++] = x;
+ }
+ x = emalloc(sizeof *x);
+ x->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; i<sx->nsx; 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; i<sx->nsx; 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; i<sx->nsx; 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 --git a/src/cmd/upas/nfs/sx.h b/src/cmd/upas/nfs/sx.h
new file mode 100644
index 00000000..910f002e
--- /dev/null
+++ b/src/cmd/upas/nfs/sx.h
@@ -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 --git a/src/cmd/upas/nfs/thread.c b/src/cmd/upas/nfs/thread.c
new file mode 100644
index 00000000..710a33d7
--- /dev/null
+++ b/src/cmd/upas/nfs/thread.c
@@ -0,0 +1,37 @@
+#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;
+
+ while(recv(mailthreadchan, &n) == 1)
+ threadcreate(n.fn, n.arg, STACK);
+}
+
+void
+mailthreadinit(void)
+{
+ mailthreadchan = chancreate(sizeof(New), 0);
+ proccreate(mailproc, nil, STACK);
+}
+
diff --git a/src/cmd/upas/nfs/util.c b/src/cmd/upas/nfs/util.c
new file mode 100644
index 00000000..ac9deba8
--- /dev/null
+++ b/src/cmd/upas/nfs/util.c
@@ -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);
+}