URI: 
       timap-based new upas/fs - plan9port - [fork] Plan 9 from user space
  HTML git clone git://src.adamsgaard.dk/plan9port
   DIR Log
   DIR Files
   DIR Refs
   DIR README
   DIR LICENSE
       ---
   DIR commit 941e17134e92de7f12977f1899860b57bbf83330
   DIR parent 1ea614ffaf9378df45410995d0a8c13042bba123
  HTML Author: rsc <devnull@localhost>
       Date:   Wed, 15 Feb 2006 12:39:09 +0000
       
       imap-based new upas/fs
       
       Diffstat:
         A src/cmd/upas/nfs/box.c              |     318 +++++++++++++++++++++++++++++++
         A src/cmd/upas/nfs/box.h              |     133 +++++++++++++++++++++++++++++++
         A src/cmd/upas/nfs/decode.c           |     257 +++++++++++++++++++++++++++++++
         A src/cmd/upas/nfs/fs.c               |    1259 +++++++++++++++++++++++++++++++
         A src/cmd/upas/nfs/fs.h               |       2 ++
         A src/cmd/upas/nfs/imap.c             |    1715 +++++++++++++++++++++++++++++++
         A src/cmd/upas/nfs/imap.h             |      23 +++++++++++++++++++++++
         A src/cmd/upas/nfs/main.c             |      68 +++++++++++++++++++++++++++++++
         A src/cmd/upas/nfs/mbox.c             |      68 +++++++++++++++++++++++++++++++
         A src/cmd/upas/nfs/mkfile             |      18 ++++++++++++++++++
         A src/cmd/upas/nfs/msg.c              |       9 +++++++++
         A src/cmd/upas/nfs/sx.c               |     217 +++++++++++++++++++++++++++++++
         A src/cmd/upas/nfs/sx.h               |      31 +++++++++++++++++++++++++++++++
         A src/cmd/upas/nfs/thread.c           |      37 +++++++++++++++++++++++++++++++
         A src/cmd/upas/nfs/util.c             |      13 +++++++++++++
       
       15 files changed, 4168 insertions(+), 0 deletions(-)
       ---
   DIR diff --git a/src/cmd/upas/nfs/box.c b/src/cmd/upas/nfs/box.c
       t@@ -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);
       +}
       +
   DIR diff --git a/src/cmd/upas/nfs/box.h b/src/cmd/upas/nfs/box.h
       t@@ -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;
       +
   DIR diff --git a/src/cmd/upas/nfs/decode.c b/src/cmd/upas/nfs/decode.c
       t@@ -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
   DIR diff --git a/src/cmd/upas/nfs/fs.c b/src/cmd/upas/nfs/fs.c
       t@@ -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);
       +}
       +
   DIR diff --git a/src/cmd/upas/nfs/fs.h b/src/cmd/upas/nfs/fs.h
       t@@ -0,0 +1,2 @@
       +extern Srv fs;
       +void fsinit0(void);
   DIR diff --git a/src/cmd/upas/nfs/imap.c b/src/cmd/upas/nfs/imap.c
       t@@ -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;
       +}
       +
   DIR diff --git a/src/cmd/upas/nfs/imap.h b/src/cmd/upas/nfs/imap.h
       t@@ -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
       +};
   DIR diff --git a/src/cmd/upas/nfs/main.c b/src/cmd/upas/nfs/main.c
       t@@ -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);
       +}
       +
   DIR diff --git a/src/cmd/upas/nfs/mbox.c b/src/cmd/upas/nfs/mbox.c
       t@@ -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();
       +}
   DIR diff --git a/src/cmd/upas/nfs/mkfile b/src/cmd/upas/nfs/mkfile
       t@@ -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
       +
   DIR diff --git a/src/cmd/upas/nfs/msg.c b/src/cmd/upas/nfs/msg.c
       t@@ -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
       +
   DIR diff --git a/src/cmd/upas/nfs/sx.c b/src/cmd/upas/nfs/sx.c
       t@@ -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;
       +}
   DIR diff --git a/src/cmd/upas/nfs/sx.h b/src/cmd/upas/nfs/sx.h
       t@@ -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*);
       +
   DIR diff --git a/src/cmd/upas/nfs/thread.c b/src/cmd/upas/nfs/thread.c
       t@@ -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);
       +}
       +
   DIR diff --git a/src/cmd/upas/nfs/util.c b/src/cmd/upas/nfs/util.c
       t@@ -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);
       +}