URI:
       timap.c - 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
       ---
       timap.c (32743B)
       ---
            1 /*
            2  * Locking here is not quite right.
            3  * Calling qlock(&z->lk) can block the proc,
            4  * and when it comes back, boxes and msgs might have been freed
            5  * (if the refresh proc was holding the lock and in the middle of a
            6  * redial).  I've tried to be careful about not assuming boxes continue
            7  * to exist across imap commands, but maybe this isn't really tenable.
            8  * Maybe instead we should ref count the boxes and messages.
            9  */
           10 
           11 #include "a.h"
           12 #include <libsec.h>
           13 
           14 struct Imap
           15 {
           16         int                connected;
           17         int                autoreconnect;
           18         int                ticks;        /* until boom! */
           19         char*        server;
           20         char*        root;
           21         char*        user;
           22         int                mode;
           23         int                fd;
           24         Biobuf        b;
           25         Ioproc*        io;
           26         QLock        lk;
           27         QLock        rlk;
           28         Rendez        r;
           29 
           30         Box*                inbox;
           31         Box*                box;
           32         Box*                nextbox;
           33 
           34         /* SEARCH results */
           35         uint                *uid;
           36         uint                nuid;
           37 
           38         uint                reply;
           39 };
           40 
           41 static struct {
           42         char *name;
           43         int flag;
           44 } flagstab[] =
           45 {
           46         "Junk",        FlagJunk,
           47         "NonJunk",        FlagNonJunk,
           48         "\\Answered",        FlagReplied,
           49         "\\Flagged",        FlagFlagged,
           50         "\\Deleted",        FlagDeleted,
           51         "\\Draft",                FlagDraft,
           52         "\\Recent",        FlagRecent,
           53         "\\Seen",                FlagSeen,
           54         "\\NoInferiors",        FlagNoInferiors,
           55         "\\NoSelect",        FlagNoSelect,
           56         "\\Marked",        FlagMarked,
           57         "\\UnMarked",        FlagUnMarked
           58 };
           59 
           60 int                        chattyimap;
           61 
           62 static char        *tag = "#";
           63 
           64 static void        checkbox(Imap*, Box*);
           65 static char*        copyaddrs(Sx*);
           66 static void        freeup(UserPasswd*);
           67 static int                getbox(Imap*, Box*);
           68 static int                getboxes(Imap*);
           69 static char*        gsub(char*, char*, char*);
           70 static int                imapcmd(Imap*, Box*, char*, ...);
           71 static Sx*                imapcmdsxlit(Imap*, Box*, char*, ...);
           72 static Sx*                imapcmdsx(Imap*, Box*, char*, ...);
           73 static Sx*                imapcmdsx0(Imap*, char*, ...);
           74 static Sx*                imapvcmdsx(Imap*, Box*, char*, va_list, int);
           75 static Sx*                imapvcmdsx0(Imap*, char*, va_list, int);
           76 static int                imapdial(char*, int);
           77 static int                imaplogin(Imap*);
           78 static int                imapquote(Fmt*);
           79 static int                imapreconnect(Imap*);
           80 static void        imaprefreshthread(void*);
           81 static void        imaptimerproc(void*);
           82 static Sx*                imapwaitsx(Imap*);
           83 static int                isatom(Sx *v, char *name);
           84 static int                islist(Sx *v);
           85 static int                isnil(Sx *v);
           86 static int                isnumber(Sx *sx);
           87 static int                isstring(Sx *sx);
           88 static int                ioimapdial(Ioproc*, char*, int);
           89 static char*        nstring(Sx*);
           90 static void        unexpected(Imap*, Sx*);
           91 static Sx*                zBrdsx(Imap*);
           92 
           93 /*
           94  * Imap connection maintenance and login.
           95  */
           96 
           97 Imap*
           98 imapconnect(char *server, int mode, char *root, char *user)
           99 {
          100         Imap *z;
          101 
          102         fmtinstall('H', encodefmt);
          103         fmtinstall('Z', imapquote);
          104 
          105         z = emalloc(sizeof *z);
          106         z->server = estrdup(server);
          107         z->mode = mode;
          108         z->user = user;
          109         if(root)
          110                 if(root[0] != 0 && root[strlen(root)-1] != '/')
          111                         z->root = smprint("%s/", root);
          112                 else
          113                         z->root = root;
          114         else
          115                 z->root = "";
          116         z->fd = -1;
          117         z->autoreconnect = 0;
          118         z->io = ioproc();
          119 
          120         qlock(&z->lk);
          121         if(imapreconnect(z) < 0){
          122                 free(z);
          123                 return nil;
          124         }
          125 
          126         z->r.l = &z->rlk;
          127         z->autoreconnect = 1;
          128         qunlock(&z->lk);
          129 
          130         proccreate(imaptimerproc, z, STACK);
          131         mailthread(imaprefreshthread, z);
          132 
          133         return z;
          134 }
          135 
          136 void
          137 imaphangup(Imap *z, int ticks)
          138 {
          139         z->ticks = ticks;
          140         if(ticks == 0){
          141                 close(z->fd);
          142                 z->fd = -1;
          143         }
          144 }
          145 
          146 static int
          147 imapreconnect(Imap *z)
          148 {
          149         Sx *sx;
          150 
          151         z->autoreconnect = 0;
          152         z->box = nil;
          153         z->inbox = nil;
          154 
          155         if(z->fd >= 0){
          156                 close(z->fd);
          157                 z->fd = -1;
          158         }
          159 
          160         if(chattyimap)
          161                 fprint(2, "dial %s...\n", z->server);
          162         if((z->fd = ioimapdial(z->io, z->server, z->mode)) < 0)
          163                 return -1;
          164         z->connected = 1;
          165         Binit(&z->b, z->fd, OREAD);
          166         if((sx = zBrdsx(z)) == nil){
          167                 werrstr("no greeting");
          168                 goto err;
          169         }
          170         if(chattyimap)
          171                 fprint(2, "<I %#$\n", sx);
          172         if(sx->nsx >= 2 && isatom(sx->sx[0], "*") && isatom(sx->sx[1], "PREAUTH")){
          173                 freesx(sx);
          174                 goto preauth;
          175         }
          176         if(!oksx(sx)){
          177                 werrstr("bad greeting - %#$", sx);
          178                 goto err;
          179         }
          180         freesx(sx);
          181         sx = nil;
          182         if(imaplogin(z) < 0)
          183                 goto err;
          184 preauth:
          185         if(getboxes(z) < 0 || getbox(z, z->inbox) < 0)
          186                 goto err;
          187         z->autoreconnect = 1;
          188         return 0;
          189 
          190 err:
          191         if(z->fd >= 0){
          192                 close(z->fd);
          193                 z->fd = -1;
          194         }
          195         if(sx)
          196                 freesx(sx);
          197         z->autoreconnect = 1;
          198         z->connected = 0;
          199         return -1;
          200 }
          201 
          202 static int
          203 imaplogin(Imap *z)
          204 {
          205         Sx *sx;
          206         UserPasswd *up;
          207 
          208         if(z->user != nil)
          209                 up = auth_getuserpasswd(auth_getkey, "proto=pass role=client service=imap server=%q user=%q", z->server, z->user);
          210         else
          211                 up = auth_getuserpasswd(auth_getkey, "proto=pass role=client service=imap server=%q", z->server);
          212         if(up == nil){
          213                 werrstr("getuserpasswd - %r");
          214                 return -1;
          215         }
          216 
          217         sx = imapcmdsx(z, nil, "LOGIN %#Z %#Z", up->user, up->passwd);
          218         freeup(up);
          219         if(sx == nil)
          220                 return -1;
          221         if(!oksx(sx)){
          222                 freesx(sx);
          223                 werrstr("login rejected - %#$", sx);
          224                 return -1;
          225         }
          226         return 0;
          227 }
          228 
          229 static int
          230 getboxes(Imap *z)
          231 {
          232         int i;
          233         Box **r, **w, **e;
          234 
          235         for(i=0; i<nboxes; i++){
          236                 boxes[i]->mark = 1;
          237                 boxes[i]->exists = 0;
          238                 boxes[i]->maxseen = 0;
          239         }
          240         if(imapcmd(z, nil, "LIST %Z *", z->root) < 0)
          241                 return -1;
          242         if(z->root != nil && imapcmd(z, nil, "LIST %Z INBOX", "") < 0)
          243                 return -1;
          244         if(z->nextbox && z->nextbox->mark)
          245                 z->nextbox = nil;
          246         for(r=boxes, w=boxes, e=boxes+nboxes; r<e; r++){
          247                 if((*r)->mark)
          248 {fprint(2, "*** free box %s %s\n", (*r)->name, (*r)->imapname);
          249                         boxfree(*r);
          250 }
          251                 else
          252                         *w++ = *r;
          253         }
          254         nboxes = w - boxes;
          255         return 0;
          256 }
          257 
          258 static int
          259 getbox(Imap *z, Box *b)
          260 {
          261         int i;
          262         Msg **r, **w, **e;
          263 
          264         if(b == nil)
          265                 return 0;
          266 
          267         for(i=0; i<b->nmsg; i++)
          268                 b->msg[i]->imapid = 0;
          269         if(imapcmd(z, b, "UID FETCH 1:* FLAGS") < 0)
          270                 return -1;
          271         for(r=b->msg, w=b->msg, e=b->msg+b->nmsg; r<e; r++){
          272                 if((*r)->imapid == 0)
          273                         msgfree(*r);
          274                 else{
          275                         (*r)->ix = w-b->msg;
          276                         *w++ = *r;
          277                 }
          278         }
          279         b->nmsg = w - b->msg;
          280         b->imapinit = 1;
          281         checkbox(z, b);
          282         return 0;
          283 }
          284 
          285 static void
          286 freeup(UserPasswd *up)
          287 {
          288         memset(up->user, 0, strlen(up->user));
          289         memset(up->passwd, 0, strlen(up->passwd));
          290         free(up);
          291 }
          292 
          293 static void
          294 imaptimerproc(void *v)
          295 {
          296         Imap *z;
          297 
          298         z = v;
          299         for(;;){
          300                 sleep(60*1000);
          301                 qlock(z->r.l);
          302                 rwakeup(&z->r);
          303                 qunlock(z->r.l);
          304         }
          305 }
          306 
          307 static void
          308 checkbox(Imap *z, Box *b)
          309 {
          310         if(imapcmd(z, b, "NOOP") >= 0){
          311                 if(!b->imapinit)
          312                         getbox(z, b);
          313                 if(!b->imapinit)
          314                         return;
          315                 if(b==z->box && b->exists > b->maxseen){
          316                         imapcmd(z, b, "UID FETCH %d:* FULL",
          317                                 b->uidnext);
          318                 }
          319         }
          320 }
          321 
          322 static void
          323 imaprefreshthread(void *v)
          324 {
          325         Imap *z;
          326 
          327         z = v;
          328         for(;;){
          329                 qlock(z->r.l);
          330                 rsleep(&z->r);
          331                 qunlock(z->r.l);
          332 
          333                 qlock(&z->lk);
          334                 if(z->inbox)
          335                         checkbox(z, z->inbox);
          336                 qunlock(&z->lk);
          337         }
          338 }
          339 
          340 /*
          341  * Run a single command and return the Sx.  Does NOT redial.
          342  */
          343 static Sx*
          344 imapvcmdsx0(Imap *z, char *fmt, va_list arg, int dotag)
          345 {
          346         char *s;
          347         Fmt f;
          348         int len;
          349         Sx *sx;
          350 
          351         if(canqlock(&z->lk))
          352                 abort();
          353 
          354         if(z->fd < 0 || !z->connected)
          355                 return nil;
          356 
          357         fmtstrinit(&f);
          358         if(dotag)
          359                 fmtprint(&f, "%s ", tag);
          360         fmtvprint(&f, fmt, arg);
          361         fmtprint(&f, "\r\n");
          362         s = fmtstrflush(&f);
          363         len = strlen(s);
          364         s[len-2] = 0;
          365         if(chattyimap)
          366                 fprint(2, "I> %s\n", s);
          367         s[len-2] = '\r';
          368         if(iowrite(z->io, z->fd, s, len) < 0){
          369                 z->connected = 0;
          370                 free(s);
          371                 return nil;
          372         }
          373         sx = imapwaitsx(z);
          374         free(s);
          375         return sx;
          376 }
          377 
          378 static Sx*
          379 imapcmdsx0(Imap *z, char *fmt, ...)
          380 {
          381         va_list arg;
          382         Sx *sx;
          383 
          384         va_start(arg, fmt);
          385         sx = imapvcmdsx0(z, fmt, arg, 1);
          386         va_end(arg);
          387         return sx;
          388 }
          389 
          390 /*
          391  * Run a single command on box b.  Does redial.
          392  */
          393 static Sx*
          394 imapvcmdsx(Imap *z, Box *b, char *fmt, va_list arg, int dotag)
          395 {
          396         int tries;
          397         Sx *sx;
          398 
          399         tries = 0;
          400         z->nextbox = b;
          401 
          402         if(z->fd < 0 || !z->connected){
          403 reconnect:
          404                 if(!z->autoreconnect)
          405                         return nil;
          406                 if(imapreconnect(z) < 0)
          407                         return nil;
          408                 if(b && z->nextbox == nil)        /* box disappeared on reconnect */
          409                         return nil;
          410         }
          411 
          412         if(b && b != z->box){
          413                 if(z->box)
          414                         z->box->imapinit = 0;
          415                 z->box = b;
          416                 if((sx=imapcmdsx0(z, "SELECT %Z", b->imapname)) == nil){
          417                         z->box = nil;
          418                         if(tries++ == 0 && (z->fd < 0 || !z->connected))
          419                                 goto reconnect;
          420                         return nil;
          421                 }
          422                 freesx(sx);
          423         }
          424 
          425         if((sx=imapvcmdsx0(z, fmt, arg, dotag)) == nil){
          426                 if(tries++ == 0 && (z->fd < 0 || !z->connected))
          427                         goto reconnect;
          428                 return nil;
          429         }
          430         return sx;
          431 }
          432 
          433 static int
          434 imapcmd(Imap *z, Box *b, char *fmt, ...)
          435 {
          436         Sx *sx;
          437         va_list arg;
          438 
          439         va_start(arg, fmt);
          440         sx = imapvcmdsx(z, b, fmt, arg, 1);
          441         va_end(arg);
          442         if(sx == nil)
          443                 return -1;
          444         if(sx->nsx < 2 || !isatom(sx->sx[1], "OK")){
          445                 werrstr("%$", sx);
          446                 freesx(sx);
          447                 return -1;
          448         }
          449         freesx(sx);
          450         return 0;
          451 }
          452 
          453 static Sx*
          454 imapcmdsx(Imap *z, Box *b, char *fmt, ...)
          455 {
          456         Sx *sx;
          457         va_list arg;
          458 
          459         va_start(arg, fmt);
          460         sx = imapvcmdsx(z, b, fmt, arg, 1);
          461         va_end(arg);
          462         return sx;
          463 }
          464 
          465 static Sx*
          466 imapcmdsxlit(Imap *z, Box *b, char *fmt, ...)
          467 {
          468         Sx *sx;
          469         va_list arg;
          470 
          471         va_start(arg, fmt);
          472         sx = imapvcmdsx(z, b, fmt, arg, 0);
          473         va_end(arg);
          474         return sx;
          475 }
          476 
          477 static Sx*
          478 imapwaitsx(Imap *z)
          479 {
          480         Sx *sx;
          481 
          482         while((sx = zBrdsx(z)) != nil){
          483                 if(chattyimap)
          484                         fprint(2, "<| %#$\n", sx);
          485                 if(sx->nsx >= 1 && sx->sx[0]->type == SxAtom && cistrcmp(sx->sx[0]->data, tag) == 0)
          486                         return sx;
          487                 if(sx->nsx >= 1 && sx->sx[0]->type == SxAtom && cistrcmp(sx->sx[0]->data, "+") == 0){
          488                         z->reply = 1;
          489                         return sx;
          490                 }
          491                 if(sx->nsx >= 1 && sx->sx[0]->type == SxAtom && strcmp(sx->sx[0]->data, "*") == 0)
          492                         unexpected(z, sx);
          493                 if(sx->type == SxList && sx->nsx == 0){
          494                         freesx(sx);
          495                         break;
          496                 }
          497                 freesx(sx);
          498         }
          499         z->connected = 0;
          500         return nil;
          501 }
          502 
          503 /*
          504  * Imap interface to mail file system.
          505  */
          506 
          507 static void
          508 _bodyname(char *buf, char *ebuf, Part *p, char *extra)
          509 {
          510         if(buf >= ebuf){
          511                 fprint(2, "***** BUFFER TOO SMALL\n");
          512                 return;
          513         }
          514         *buf = 0;
          515         if(p->parent){
          516                 _bodyname(buf, ebuf, p->parent, "");
          517                 buf += strlen(buf);
          518                 seprint(buf, ebuf, ".%d", p->pix+1);
          519         }
          520         buf += strlen(buf);
          521         seprint(buf, ebuf, "%s", extra);
          522 }
          523 
          524 static char*
          525 bodyname(Part *p, char *extra)
          526 {
          527         static char buf[256];
          528         memset(buf, 0, sizeof buf);        /* can't see why this is necessary, but it is */
          529         _bodyname(buf, buf+sizeof buf, p, extra);
          530         return buf+1;        /* buf[0] == '.' */
          531 }
          532 
          533 static void
          534 fetch1(Imap *z, Part *p, char *s)
          535 {
          536         qlock(&z->lk);
          537         imapcmd(z, p->msg->box, "UID FETCH %d BODY[%s]",
          538                 p->msg->imapuid, bodyname(p, s));
          539         qunlock(&z->lk);
          540 }
          541 
          542 void
          543 imapfetchrawheader(Imap *z, Part *p)
          544 {
          545         fetch1(z, p, ".HEADER");
          546 }
          547 
          548 void
          549 imapfetchrawmime(Imap *z, Part *p)
          550 {
          551         fetch1(z, p, ".MIME");
          552 }
          553 
          554 void
          555 imapfetchrawbody(Imap *z, Part *p)
          556 {
          557         fetch1(z, p, ".TEXT");
          558 }
          559 
          560 void
          561 imapfetchraw(Imap *z, Part *p)
          562 {
          563         fetch1(z, p, "");
          564 }
          565 
          566 static int
          567 imaplistcmd(Imap *z, Box *box, char *before, Msg **m, uint nm, char *after)
          568 {
          569         int i, r;
          570         char *cmd;
          571         Fmt fmt;
          572 
          573         if(nm == 0)
          574                 return 0;
          575 
          576         fmtstrinit(&fmt);
          577         fmtprint(&fmt, "%s ", before);
          578         for(i=0; i<nm; i++){
          579                 if(i > 0)
          580                         fmtrune(&fmt, ',');
          581                 fmtprint(&fmt, "%ud", m[i]->imapuid);
          582         }
          583         fmtprint(&fmt, " %s", after);
          584         cmd = fmtstrflush(&fmt);
          585 
          586         r = 0;
          587         if(imapcmd(z, box, "%s", cmd) < 0)
          588                  r = -1;
          589         free(cmd);
          590         return r;
          591 }
          592 
          593 int
          594 imapcopylist(Imap *z, char *nbox, Msg **m, uint nm)
          595 {
          596         int rv;
          597         char *name, *p;
          598 
          599         if(nm == 0)
          600                 return 0;
          601 
          602         qlock(&z->lk);
          603         if(strcmp(nbox, "mbox") == 0)
          604                 name = estrdup("INBOX");
          605         else{
          606                 p = esmprint("%s%s", z->root, nbox);
          607                 name = esmprint("%Z", p);
          608                 free(p);
          609         }
          610         rv = imaplistcmd(z, m[0]->box, "UID COPY", m, nm, name);
          611         free(name);
          612         qunlock(&z->lk);
          613         return rv;
          614 }
          615 
          616 int
          617 imapremovelist(Imap *z, Msg **m, uint nm)
          618 {
          619         int rv;
          620 
          621         if(nm == 0)
          622                 return 0;
          623 
          624         qlock(&z->lk);
          625         rv = imaplistcmd(z, m[0]->box, "UID STORE", m, nm, "+FLAGS.SILENT (\\Deleted)");
          626         /* careful - box might be gone; use z->box instead */
          627         if(rv == 0 && z->box)
          628                 rv = imapcmd(z, z->box, "EXPUNGE");
          629         qunlock(&z->lk);
          630         return rv;
          631 }
          632 
          633 int
          634 imapflaglist(Imap *z, int op, int flag, Msg **m, uint nm)
          635 {
          636         char *mod, *s, *sep;
          637         int i, rv;
          638         Fmt fmt;
          639 
          640         if(op > 0)
          641                 mod = "+";
          642         else if(op == 0)
          643                 mod = "";
          644         else
          645                 mod = "-";
          646 
          647         fmtstrinit(&fmt);
          648         fmtprint(&fmt, "%sFLAGS (", mod);
          649         sep = "";
          650         for(i=0; i<nelem(flagstab); i++){
          651                 if(flagstab[i].flag & flag){
          652                         fmtprint(&fmt, "%s%s", sep, flagstab[i].name);
          653                         sep = " ";
          654                 }
          655         }
          656         fmtprint(&fmt, ")");
          657         s = fmtstrflush(&fmt);
          658 
          659         qlock(&z->lk);
          660         rv = imaplistcmd(z, m[0]->box, "UID STORE", m, nm, s);
          661         qunlock(&z->lk);
          662         free(s);
          663         return rv;
          664 }
          665 
          666 int
          667 imapsearchbox(Imap *z, Box *b, char *search, Msg ***mm)
          668 {
          669         uint *uid;
          670         int i, nuid;
          671         Msg **m;
          672         int nm;
          673         Sx *sx;
          674 
          675         qlock(&z->lk);
          676         sx = imapcmdsx(z, b, "UID SEARCH CHARSET UTF-8 TEXT {%d}", strlen(search));
          677         freesx(sx);
          678         if(!z->reply){
          679                 qunlock(&z->lk);
          680                 return -1;
          681         }
          682         if((sx = imapcmdsxlit(z, b, "%s", search)) == nil){
          683                 qunlock(&z->lk);
          684                 return -1;
          685         }
          686         if(sx->nsx < 2 || !isatom(sx->sx[1], "OK")){
          687                 werrstr("%$", sx);
          688                 freesx(sx);
          689                 qunlock(&z->lk);
          690                 return -1;
          691         }
          692         freesx(sx);
          693 
          694         uid = z->uid;
          695         nuid = z->nuid;
          696         z->uid = nil;
          697         z->nuid = 0;
          698         z->reply = 0;
          699         qunlock(&z->lk);
          700 
          701         m = emalloc(nuid*sizeof m[0]);
          702         nm = 0;
          703         for(i=0; i<nuid; i++)
          704                 if((m[nm] = msgbyimapuid(b, uid[i], 0)) != nil)
          705                         nm++;
          706         *mm = m;
          707         free(uid);
          708         return nm;
          709 }
          710 
          711 void
          712 imapcheckbox(Imap *z, Box *b)
          713 {
          714         if(b == nil)
          715                 return;
          716         qlock(&z->lk);
          717         checkbox(z, b);
          718         qunlock(&z->lk);
          719 }
          720 
          721 /*
          722  * Imap utility routines
          723  */
          724 static long
          725 _ioimapdial(va_list *arg)
          726 {
          727         char *server;
          728         int mode;
          729 
          730         server = va_arg(*arg, char*);
          731         mode = va_arg(*arg, int);
          732         return imapdial(server, mode);
          733 }
          734 static int
          735 ioimapdial(Ioproc *io, char *server, int mode)
          736 {
          737         return iocall(io, _ioimapdial, server, mode);
          738 }
          739 
          740 static long
          741 _ioBrdsx(va_list *arg)
          742 {
          743         Biobuf *b;
          744         Sx **sx;
          745 
          746         b = va_arg(*arg, Biobuf*);
          747         sx = va_arg(*arg, Sx**);
          748         *sx = Brdsx(b);
          749         if((*sx) && (*sx)->type == SxList && (*sx)->nsx == 0){
          750                 freesx(*sx);
          751                 *sx = nil;
          752         }
          753         return 0;
          754 }
          755 static Sx*
          756 ioBrdsx(Ioproc *io, Biobuf *b)
          757 {
          758         Sx *sx;
          759 
          760         iocall(io, _ioBrdsx, b, &sx);
          761         return sx;
          762 }
          763 
          764 static Sx*
          765 zBrdsx(Imap *z)
          766 {
          767         if(z->ticks && --z->ticks==0){
          768                 close(z->fd);
          769                 z->fd = -1;
          770                 return nil;
          771         }
          772         return ioBrdsx(z->io, &z->b);
          773 }
          774 
          775 static int
          776 imapdial(char *server, int mode)
          777 {
          778         int p[2];
          779         int fd[3];
          780         char *tmp;
          781         char *fpath;
          782 
          783         switch(mode){
          784         default:
          785         case Unencrypted:
          786                 return dial(netmkaddr(server, "tcp", "143"), nil, nil, nil);
          787 
          788         case Starttls:
          789                 werrstr("starttls not supported");
          790                 return -1;
          791 
          792         case Tls:
          793                 if(pipe(p) < 0)
          794                         return -1;
          795                 fd[0] = dup(p[0], -1);
          796                 fd[1] = dup(p[0], -1);
          797                 fd[2] = dup(2, -1);
          798 #ifdef PLAN9PORT
          799                 tmp = esmprint("%s:993", server);
          800                 fpath = searchpath("stunnel3");
          801                 if (!fpath) {
          802                         werrstr("stunnel not found. it is required for tls support.");
          803                         return -1;
          804                 }
          805                 if(threadspawnl(fd, fpath, "stunnel", "-c", "-r", tmp, nil) < 0) {
          806 #else
          807                 tmp = esmprint("tcp!%s!993", server);
          808                 if(threadspawnl(fd, "/bin/tlsclient", "tlsclient", tmp, nil) < 0){
          809 #endif
          810                         free(tmp);
          811                         close(p[0]);
          812                         close(p[1]);
          813                         close(fd[0]);
          814                         close(fd[1]);
          815                         close(fd[2]);
          816                         return -1;
          817                 }
          818                 free(tmp);
          819                 close(p[0]);
          820                 return p[1];
          821 
          822         case Cmd:
          823                 if(pipe(p) < 0)
          824                         return -1;
          825                 fd[0] = dup(p[0], -1);
          826                 fd[1] = dup(p[0], -1);
          827                 fd[2] = dup(2, -1);
          828                 if(threadspawnl(fd, "/usr/local/plan9/bin/rc", "rc", "-c", server, nil) < 0){
          829                         close(p[0]);
          830                         close(p[1]);
          831                         close(fd[0]);
          832                         close(fd[1]);
          833                         close(fd[2]);
          834                         return -1;
          835                 }
          836                 close(p[0]);
          837                 return p[1];
          838         }
          839 }
          840 
          841 enum
          842 {
          843         Qok = 0,
          844         Qquote,
          845         Qbackslash
          846 };
          847 
          848 static int
          849 needtoquote(Rune r)
          850 {
          851         if(r >= Runeself)
          852                 return Qquote;
          853         if(r <= ' ')
          854                 return Qquote;
          855         if(r=='\\' || r=='"')
          856                 return Qbackslash;
          857         return Qok;
          858 }
          859 
          860 static int
          861 imapquote(Fmt *f)
          862 {
          863         char *s, *t;
          864         int w, quotes;
          865         Rune r;
          866 
          867         s = va_arg(f->args, char*);
          868         if(s == nil || *s == '\0')
          869                 return fmtstrcpy(f, "\"\"");
          870 
          871         quotes = 0;
          872         if(f->flags&FmtSharp)
          873                 quotes = 1;
          874         for(t=s; *t; t+=w){
          875                 w = chartorune(&r, t);
          876                 quotes |= needtoquote(r);
          877         }
          878         if(quotes == 0)
          879                 return fmtstrcpy(f, s);
          880 
          881         fmtrune(f, '"');
          882         for(t=s; *t; t+=w){
          883                 w = chartorune(&r, t);
          884                 if(needtoquote(r) == Qbackslash)
          885                         fmtrune(f, '\\');
          886                 fmtrune(f, r);
          887         }
          888         return fmtrune(f, '"');
          889 }
          890 
          891 static int
          892 fmttype(char c)
          893 {
          894         switch(c){
          895         case 'A':
          896                 return SxAtom;
          897         case 'L':
          898                 return SxList;
          899         case 'N':
          900                 return SxNumber;
          901         case 'S':
          902                 return SxString;
          903         default:
          904                 return -1;
          905         }
          906 }
          907 
          908 /*
          909  * Check S expression against format string.
          910  */
          911 static int
          912 sxmatch(Sx *sx, char *fmt)
          913 {
          914         int i;
          915 
          916         for(i=0; fmt[i]; i++){
          917                 if(fmt[i] == '*')
          918                         fmt--;        /* like i-- but better */
          919                 if(i == sx->nsx && fmt[i+1] == '*')
          920                         return 1;
          921                 if(i >= sx->nsx)
          922                         return 0;
          923                 if(sx->sx[i] == nil)
          924                         return 0;
          925                 if(sx->sx[i]->type == SxAtom && strcmp(sx->sx[i]->data, "NIL") == 0){
          926                         if(fmt[i] == 'L'){
          927                                 free(sx->sx[i]->data);
          928                                 sx->sx[i]->data = nil;
          929                                 sx->sx[i]->type = SxList;
          930                                 sx->sx[i]->sx = nil;
          931                                 sx->sx[i]->nsx = 0;
          932                         }
          933                         else if(fmt[i] == 'S'){
          934                                 free(sx->sx[i]->data);
          935                                 sx->sx[i]->data = nil;
          936                                 sx->sx[i]->type = SxString;
          937                         }
          938                 }
          939                 if(sx->sx[i]->type == SxAtom && fmt[i]=='S')
          940                         sx->sx[i]->type = SxString;
          941                 if(sx->sx[i]->type != fmttype(fmt[i])){
          942                         fprint(2, "sxmatch: %$ not %c\n", sx->sx[i], fmt[i]);
          943                         return 0;
          944                 }
          945         }
          946         if(i != sx->nsx)
          947                 return 0;
          948         return 1;
          949 }
          950 
          951 /*
          952  * Check string against format string.
          953  */
          954 static int
          955 stringmatch(char *fmt, char *s)
          956 {
          957         for(; *fmt && *s; fmt++, s++){
          958                 switch(*fmt){
          959                 case '0':
          960                         if(*s == ' ')
          961                                 break;
          962                         /* fall through */
          963                 case '1':
          964                         if(*s < '0' || *s > '9')
          965                                 return 0;
          966                         break;
          967                 case 'A':
          968                         if(*s < 'A' || *s > 'Z')
          969                                 return 0;
          970                         break;
          971                 case 'a':
          972                         if(*s < 'a' || *s > 'z')
          973                                 return 0;
          974                         break;
          975                 case '+':
          976                         if(*s != '-' && *s != '+')
          977                                 return 0;
          978                         break;
          979                 default:
          980                         if(*s != *fmt)
          981                                 return 0;
          982                         break;
          983                 }
          984         }
          985         if(*fmt || *s)
          986                 return 0;
          987         return 1;
          988 }
          989 
          990 /*
          991  * Parse simple S expressions and IMAP elements.
          992  */
          993 static int
          994 isatom(Sx *v, char *name)
          995 {
          996         int n;
          997 
          998         if(v == nil || v->type != SxAtom)
          999                 return 0;
         1000         n = strlen(name);
         1001         if(cistrncmp(v->data, name, n) == 0)
         1002                 if(v->data[n] == 0 || (n>0 && v->data[n-1] == '['))
         1003                         return 1;
         1004         return 0;
         1005 }
         1006 
         1007 static int
         1008 isstring(Sx *sx)
         1009 {
         1010         if(sx->type == SxAtom)
         1011                 sx->type = SxString;
         1012         return sx->type == SxString;
         1013 }
         1014 
         1015 static int
         1016 isnumber(Sx *sx)
         1017 {
         1018         return sx->type == SxNumber;
         1019 }
         1020 
         1021 static int
         1022 isnil(Sx *v)
         1023 {
         1024         return v == nil ||
         1025                 (v->type==SxList && v->nsx == 0) ||
         1026                 (v->type==SxAtom && strcmp(v->data, "NIL") == 0);
         1027 }
         1028 
         1029 static int
         1030 islist(Sx *v)
         1031 {
         1032         return isnil(v) || v->type==SxList;
         1033 }
         1034 
         1035 static uint
         1036 parseflags(Sx *v)
         1037 {
         1038         int f, i, j;
         1039 
         1040         if(v->type != SxList){
         1041                 warn("malformed flags: %$", v);
         1042                 return 0;
         1043         }
         1044         f = 0;
         1045         for(i=0; i<v->nsx; i++){
         1046                 if(v->sx[i]->type != SxAtom)
         1047                         continue;
         1048                 for(j=0; j<nelem(flagstab); j++)
         1049                         if(cistrcmp(v->sx[i]->data, flagstab[j].name) == 0)
         1050                                 f |= flagstab[j].flag;
         1051         }
         1052         return f;
         1053 }
         1054 
         1055 static char months[] = "JanFebMarAprMayJunJulAugSepOctNovDec";
         1056 static int
         1057 parsemon(char *s)
         1058 {
         1059         int i;
         1060 
         1061         for(i=0; months[i]; i+=3)
         1062                 if(memcmp(s, months+i, 3) == 0)
         1063                         return i/3;
         1064         return -1;
         1065 }
         1066 
         1067 static uint
         1068 parsedate(Sx *v)
         1069 {
         1070         Tm tm;
         1071         uint t;
         1072         int delta;
         1073         char *p;
         1074 
         1075         if(v->type != SxString || !stringmatch("01-Aaa-1111 01:11:11 +1111", v->data)){
         1076         bad:
         1077                 warn("bad date: %$", v);
         1078                 return 0;
         1079         }
         1080 
         1081         /* cannot use atoi because 09 is malformed octal! */
         1082         memset(&tm, 0, sizeof tm);
         1083         p = v->data;
         1084         tm.mday = strtol(p, 0, 10);
         1085         tm.mon = parsemon(p+3);
         1086         if(tm.mon == -1)
         1087                 goto bad;
         1088         tm.year = strtol(p+7, 0, 10) - 1900;
         1089         tm.hour = strtol(p+12, 0, 10);
         1090         tm.min = strtol(p+15, 0, 10);
         1091         tm.sec = strtol(p+18, 0, 10);
         1092         strcpy(tm.zone, "GMT");
         1093 
         1094         t = tm2sec(&tm);
         1095         delta = ((p[22]-'0')*10+p[23]-'0')*3600 + ((p[24]-'0')*10+p[25]-'0')*60;
         1096         if(p[21] == '-')
         1097                 delta = -delta;
         1098 
         1099         t -= delta;
         1100         return t;
         1101 }
         1102 
         1103 static uint
         1104 parsenumber(Sx *v)
         1105 {
         1106         if(v->type != SxNumber)
         1107                 return 0;
         1108         return v->number;
         1109 }
         1110 
         1111 static void
         1112 hash(DigestState *ds, char *tag, char *val)
         1113 {
         1114         if(val == nil)
         1115                 val = "";
         1116         md5((uchar*)tag, strlen(tag)+1, nil, ds);
         1117         md5((uchar*)val, strlen(val)+1, nil, ds);
         1118 }
         1119 
         1120 static Hdr*
         1121 parseenvelope(Sx *v)
         1122 {
         1123         Hdr *hdr;
         1124         uchar digest[16];
         1125         DigestState ds;
         1126 
         1127         if(v->type != SxList || !sxmatch(v, "SSLLLLLLSS")){
         1128                 warn("bad envelope: %$", v);
         1129                 return nil;
         1130         }
         1131 
         1132         hdr = emalloc(sizeof *hdr);
         1133         hdr->date = nstring(v->sx[0]);
         1134         hdr->subject = unrfc2047(nstring(v->sx[1]));
         1135         hdr->from = copyaddrs(v->sx[2]);
         1136         hdr->sender = copyaddrs(v->sx[3]);
         1137         hdr->replyto = copyaddrs(v->sx[4]);
         1138         hdr->to = copyaddrs(v->sx[5]);
         1139         hdr->cc = copyaddrs(v->sx[6]);
         1140         hdr->bcc = copyaddrs(v->sx[7]);
         1141         hdr->inreplyto = unrfc2047(nstring(v->sx[8]));
         1142         hdr->messageid = unrfc2047(nstring(v->sx[9]));
         1143 
         1144         memset(&ds, 0, sizeof ds);
         1145         hash(&ds, "date", hdr->date);
         1146         hash(&ds, "subject", hdr->subject);
         1147         hash(&ds, "from", hdr->from);
         1148         hash(&ds, "sender", hdr->sender);
         1149         hash(&ds, "replyto", hdr->replyto);
         1150         hash(&ds, "to", hdr->to);
         1151         hash(&ds, "cc", hdr->cc);
         1152         hash(&ds, "bcc", hdr->bcc);
         1153         hash(&ds, "inreplyto", hdr->inreplyto);
         1154         hash(&ds, "messageid", hdr->messageid);
         1155         md5(0, 0, digest, &ds);
         1156         hdr->digest = esmprint("%.16H", digest);
         1157 
         1158         return hdr;
         1159 }
         1160 
         1161 static void
         1162 strlwr(char *s)
         1163 {
         1164         char *t;
         1165 
         1166         if(s == nil)
         1167                 return;
         1168         for(t=s; *t; t++)
         1169                 if('A' <= *t && *t <= 'Z')
         1170                         *t += 'a' - 'A';
         1171 }
         1172 
         1173 static void
         1174 nocr(char *s)
         1175 {
         1176         char *r, *w;
         1177 
         1178         if(s == nil)
         1179                 return;
         1180         for(r=w=s; *r; r++)
         1181                 if(*r != '\r')
         1182                         *w++ = *r;
         1183         *w = 0;
         1184 }
         1185 
         1186 /*
         1187  * substitute all occurrences of a with b in s.
         1188  */
         1189 static char*
         1190 gsub(char *s, char *a, char *b)
         1191 {
         1192         char *p, *t, *w, *last;
         1193         int n;
         1194 
         1195         n = 0;
         1196         for(p=s; (p=strstr(p, a)) != nil; p+=strlen(a))
         1197                 n++;
         1198         if(n == 0)
         1199                 return s;
         1200         t = emalloc(strlen(s)+n*strlen(b)+1);
         1201         w = t;
         1202         for(p=s; last=p, (p=strstr(p, a)) != nil; p+=strlen(a)){
         1203                 memmove(w, last, p-last);
         1204                 w += p-last;
         1205                 memmove(w, b, strlen(b));
         1206                 w += strlen(b);
         1207         }
         1208         strcpy(w, last);
         1209         free(s);
         1210         return t;
         1211 }
         1212 
         1213 /*
         1214  * Table-driven IMAP "unexpected response" parser.
         1215  * All the interesting data is in the unexpected responses.
         1216  */
         1217 static void xlist(Imap*, Sx*);
         1218 static void xrecent(Imap*, Sx*);
         1219 static void xexists(Imap*, Sx*);
         1220 static void xok(Imap*, Sx*);
         1221 static void xflags(Imap*, Sx*);
         1222 static void xfetch(Imap*, Sx*);
         1223 static void xexpunge(Imap*, Sx*);
         1224 static void xbye(Imap*, Sx*);
         1225 static void xsearch(Imap*, Sx*);
         1226 
         1227 static struct {
         1228         int                num;
         1229         char                *name;
         1230         char                *fmt;
         1231         void                (*fn)(Imap*, Sx*);
         1232 } unextab[] = {
         1233         0,        "BYE",                nil,                        xbye,
         1234         0,        "FLAGS",                "AAL",                xflags,
         1235         0,        "LIST",                "AALSS",                xlist,
         1236         0,        "OK",                nil,                        xok,
         1237         0,        "SEARCH",        "AAN*",                xsearch,
         1238 
         1239         1,        "EXISTS",                "ANA",                xexists,
         1240         1,        "EXPUNGE",        "ANA",                xexpunge,
         1241         1,        "FETCH",                "ANAL",                xfetch,
         1242         1,        "RECENT",        "ANA",                xrecent
         1243 };
         1244 
         1245 static void
         1246 unexpected(Imap *z, Sx *sx)
         1247 {
         1248         int i, num;
         1249         char *name;
         1250 
         1251         if(sx->nsx >= 3 && sx->sx[1]->type == SxNumber && sx->sx[2]->type == SxAtom){
         1252                 num = 1;
         1253                 name = sx->sx[2]->data;
         1254         }else if(sx->nsx >= 2 && sx->sx[1]->type == SxAtom){
         1255                 num = 0;
         1256                 name = sx->sx[1]->data;
         1257         }else
         1258                 return;
         1259 
         1260         for(i=0; i<nelem(unextab); i++){
         1261                 if(unextab[i].num == num && cistrcmp(unextab[i].name, name) == 0){
         1262                         if(unextab[i].fmt && !sxmatch(sx, unextab[i].fmt)){
         1263                                 warn("malformed %s: %$", name, sx);
         1264                                 continue;
         1265                         }
         1266                         unextab[i].fn(z, sx);
         1267                 }
         1268         }
         1269 }
         1270 
         1271 static int
         1272 alldollars(char *s)
         1273 {
         1274         for(; *s; s++)
         1275                 if(*s != '$')
         1276                         return 0;
         1277         return 1;
         1278 }
         1279 
         1280 static void
         1281 xlist(Imap *z, Sx *sx)
         1282 {
         1283         int inbox;
         1284         char *s, *t;
         1285         Box *box;
         1286 
         1287         s = estrdup(sx->sx[4]->data);
         1288         if(sx->sx[3]->data && strcmp(sx->sx[3]->data, "/") != 0){
         1289                 s = gsub(s, "/", "_");
         1290                 s = gsub(s, sx->sx[3]->data, "/");
         1291         }
         1292 
         1293         /*
         1294          * INBOX is the special imap name for the main mailbox.
         1295          * All other mailbox names have the root prefix removed, if applicable.
         1296          */
         1297         inbox = 0;
         1298         if(cistrcmp(s, "INBOX") == 0){
         1299                 inbox = 1;
         1300                 free(s);
         1301                 s = estrdup("mbox");
         1302         } else if(z->root && strstr(s, z->root) == s) {
         1303                 t = estrdup(s+strlen(z->root));
         1304                 free(s);
         1305                 s = t;
         1306         }
         1307 
         1308         /*
         1309          * Plan 9 calls the main mailbox mbox.
         1310          * Rename any existing mbox by appending a $.
         1311          */
         1312         if(!inbox && strncmp(s, "mbox", 4) == 0 && alldollars(s+4)){
         1313                 t = emalloc(strlen(s)+2);
         1314                 strcpy(t, s);
         1315                 strcat(t, "$");
         1316                 free(s);
         1317                 s = t;
         1318         }
         1319 
         1320         box = boxcreate(s);
         1321         if(box == nil)
         1322                 return;
         1323         box->imapname = estrdup(sx->sx[4]->data);
         1324         if(inbox)
         1325                 z->inbox = box;
         1326         box->mark = 0;
         1327         box->flags = parseflags(sx->sx[2]);
         1328 }
         1329 
         1330 static void
         1331 xrecent(Imap *z, Sx *sx)
         1332 {
         1333         if(z->box)
         1334                 z->box->recent = sx->sx[1]->number;
         1335 }
         1336 
         1337 static void
         1338 xexists(Imap *z, Sx *sx)
         1339 {
         1340         if(z->box){
         1341                 z->box->exists = sx->sx[1]->number;
         1342                 if(z->box->exists < z->box->maxseen)
         1343                         z->box->maxseen = z->box->exists;
         1344         }
         1345 }
         1346 
         1347 static void
         1348 xflags(Imap *z, Sx *sx)
         1349 {
         1350         USED(z);
         1351         USED(sx);
         1352         /*
         1353          * This response contains in sx->sx[2] the list of flags
         1354          * that can be validly attached to messages in z->box.
         1355          * We don't have any use for this list, since we
         1356          * use only the standard flags.
         1357          */
         1358 }
         1359 
         1360 static void
         1361 xbye(Imap *z, Sx *sx)
         1362 {
         1363         USED(sx);
         1364         close(z->fd);
         1365         z->fd = -1;
         1366         z->connected = 0;
         1367 }
         1368 
         1369 static void
         1370 xexpunge(Imap *z, Sx *sx)
         1371 {
         1372         int i, n;
         1373         Box *b;
         1374 
         1375         if((b=z->box) == nil)
         1376                 return;
         1377         n = sx->sx[1]->number;
         1378         for(i=0; i<b->nmsg; i++){
         1379                 if(b->msg[i]->imapid == n){
         1380                         msgplumb(b->msg[i], 1);
         1381                         msgfree(b->msg[i]);
         1382                         b->nmsg--;
         1383                         memmove(b->msg+i, b->msg+i+1, (b->nmsg-i)*sizeof b->msg[0]);
         1384                         i--;
         1385                         b->maxseen--;
         1386                         b->exists--;
         1387                         continue;
         1388                 }
         1389                 if(b->msg[i]->imapid > n)
         1390                         b->msg[i]->imapid--;
         1391                 b->msg[i]->ix = i;
         1392         }
         1393 }
         1394 
         1395 static void
         1396 xsearch(Imap *z, Sx *sx)
         1397 {
         1398         int i;
         1399 
         1400         free(z->uid);
         1401         z->uid = emalloc((sx->nsx-2)*sizeof z->uid[0]);
         1402         z->nuid = sx->nsx-2;
         1403         for(i=0; i<z->nuid; i++)
         1404                 z->uid[i] = sx->sx[i+2]->number;
         1405 }
         1406 
         1407 /*
         1408  * Table-driven FETCH message info parser.
         1409  */
         1410 static void xmsgflags(Msg*, Sx*, Sx*);
         1411 static void xmsgdate(Msg*, Sx*, Sx*);
         1412 static void xmsgrfc822size(Msg*, Sx*, Sx*);
         1413 static void xmsgenvelope(Msg*, Sx*, Sx*);
         1414 static void xmsgbody(Msg*, Sx*, Sx*);
         1415 static void xmsgbodydata(Msg*, Sx*, Sx*);
         1416 
         1417 static struct {
         1418         char *name;
         1419         void (*fn)(Msg*, Sx*, Sx*);
         1420 } msgtab[] = {
         1421         "FLAGS", xmsgflags,
         1422         "INTERNALDATE", xmsgdate,
         1423         "RFC822.SIZE", xmsgrfc822size,
         1424         "ENVELOPE", xmsgenvelope,
         1425         "BODY", xmsgbody,
         1426         "BODY[", xmsgbodydata
         1427 };
         1428 
         1429 static void
         1430 xfetch(Imap *z, Sx *sx)
         1431 {
         1432         int i, j, n, uid;
         1433         Msg *msg;
         1434 
         1435         if(z->box == nil){
         1436                 warn("FETCH but no open box: %$", sx);
         1437                 return;
         1438         }
         1439 
         1440         /* * 152 FETCH (UID 185 FLAGS () ...) */
         1441         if(sx->sx[3]->nsx%2){
         1442                 warn("malformed FETCH: %$", sx);
         1443                 return;
         1444         }
         1445 
         1446         n = sx->sx[1]->number;
         1447         sx = sx->sx[3];
         1448         for(i=0; i<sx->nsx; i+=2){
         1449                 if(isatom(sx->sx[i], "UID")){
         1450                         if(sx->sx[i+1]->type == SxNumber){
         1451                                 uid = sx->sx[i+1]->number;
         1452                                 goto haveuid;
         1453                         }
         1454                 }
         1455         }
         1456 /* This happens: too bad.
         1457         warn("FETCH without UID: %$", sx);
         1458 */
         1459         return;
         1460 
         1461 haveuid:
         1462         msg = msgbyimapuid(z->box, uid, 1);
         1463         if(msg->imapid && msg->imapid != n)
         1464                 warn("msg id mismatch: want %d have %d", msg->id, n);
         1465         msg->imapid = n;
         1466         for(i=0; i<sx->nsx; i+=2){
         1467                 for(j=0; j<nelem(msgtab); j++)
         1468                         if(isatom(sx->sx[i], msgtab[j].name))
         1469                                 msgtab[j].fn(msg, sx->sx[i], sx->sx[i+1]);
         1470         }
         1471         msgplumb(msg, 0);
         1472 }
         1473 
         1474 static void
         1475 xmsgflags(Msg *msg, Sx *k, Sx *v)
         1476 {
         1477         USED(k);
         1478         msg->flags = parseflags(v);
         1479 }
         1480 
         1481 static void
         1482 xmsgdate(Msg *msg, Sx *k, Sx *v)
         1483 {
         1484         USED(k);
         1485         msg->date = parsedate(v);
         1486 }
         1487 
         1488 static void
         1489 xmsgrfc822size(Msg *msg, Sx *k, Sx *v)
         1490 {
         1491         USED(k);
         1492         msg->size = parsenumber(v);
         1493 }
         1494 
         1495 static char*
         1496 nstring(Sx *v)
         1497 {
         1498         char *p;
         1499 
         1500         if(isnil(v))
         1501                 return estrdup("");
         1502         p = v->data;
         1503         v->data = nil;
         1504         return p;
         1505 }
         1506 
         1507 static char*
         1508 copyaddrs(Sx *v)
         1509 {
         1510         char *s, *sep;
         1511         char *name, *email, *host, *mbox;
         1512         int i;
         1513         Fmt fmt;
         1514 
         1515         if(v->nsx == 0)
         1516                 return nil;
         1517 
         1518         fmtstrinit(&fmt);
         1519         sep = "";
         1520         for(i=0; i<v->nsx; i++){
         1521                 if(!sxmatch(v->sx[i], "SSSS"))
         1522                         warn("bad address: %$", v->sx[i]);
         1523                 name = unrfc2047(nstring(v->sx[i]->sx[0]));
         1524                 /* ignore sx[1] - route */
         1525                 mbox = unrfc2047(nstring(v->sx[i]->sx[2]));
         1526                 host = unrfc2047(nstring(v->sx[i]->sx[3]));
         1527                 if(mbox == nil || host == nil){        /* rfc822 group syntax */
         1528                         free(name);
         1529                         free(mbox);
         1530                         free(host);
         1531                         continue;
         1532                 }
         1533                 email = esmprint("%s@%s", mbox, host);
         1534                 free(mbox);
         1535                 free(host);
         1536                 fmtprint(&fmt, "%s%q %q", sep, name ? name : "", email ? email : "");
         1537                 free(name);
         1538                 free(email);
         1539                 sep = " ";
         1540         }
         1541         s = fmtstrflush(&fmt);
         1542         if(s == nil)
         1543                 sysfatal("out of memory");
         1544         return s;
         1545 }
         1546 
         1547 static void
         1548 xmsgenvelope(Msg *msg, Sx *k, Sx *v)
         1549 {
         1550         USED(k);
         1551         hdrfree(msg->part[0]->hdr);
         1552         msg->part[0]->hdr = parseenvelope(v);
         1553 }
         1554 
         1555 static struct {
         1556         char *name;
         1557         int offset;
         1558 } paramtab[] = {
         1559         "charset",        offsetof(Part, charset),
         1560         "name",                offsetof(Part, filename)
         1561 };
         1562 
         1563 static void
         1564 parseparams(Part *part, Sx *v)
         1565 {
         1566         int i, j;
         1567         char *s, *t, **p;
         1568 
         1569         if(isnil(v))
         1570                 return;
         1571         if(v->nsx%2){
         1572                 warn("bad message params: %$", v);
         1573                 return;
         1574         }
         1575         for(i=0; i<v->nsx; i+=2){
         1576                 s = nstring(v->sx[i]);
         1577                 t = nstring(v->sx[i+1]);
         1578                 for(j=0; j<nelem(paramtab); j++){
         1579                         if(cistrcmp(paramtab[j].name, s) == 0){
         1580                                 p = (char**)((char*)part+paramtab[j].offset);
         1581                                 free(*p);
         1582                                 *p = t;
         1583                                 t = nil;
         1584                                 break;
         1585                         }
         1586                 }
         1587                 free(s);
         1588                 free(t);
         1589         }
         1590 }
         1591 
         1592 static void
         1593 parsestructure(Part *part, Sx *v)
         1594 {
         1595         int i;
         1596         char *s, *t;
         1597 
         1598         if(isnil(v))
         1599                 return;
         1600         if(v->type != SxList){
         1601         bad:
         1602                 warn("bad structure: %$", v);
         1603                 return;
         1604         }
         1605         if(islist(v->sx[0])){
         1606                 /* multipart */
         1607                 for(i=0; i<v->nsx && islist(v->sx[i]); i++)
         1608                         parsestructure(partcreate(part->msg, part), v->sx[i]);
         1609                 free(part->type);
         1610                 if(i != v->nsx-1 || !isstring(v->sx[i])){
         1611                         warn("bad multipart structure: %$", v);
         1612                         part->type = estrdup("multipart/mixed");
         1613                         return;
         1614                 }
         1615                 s = nstring(v->sx[i]);
         1616                 strlwr(s);
         1617                 part->type = esmprint("multipart/%s", s);
         1618                 free(s);
         1619                 return;
         1620         }
         1621         /* single part */
         1622         if(!isstring(v->sx[0]) || v->nsx < 2)
         1623                 goto bad;
         1624         s = nstring(v->sx[0]);
         1625         t = nstring(v->sx[1]);
         1626         strlwr(s);
         1627         strlwr(t);
         1628         free(part->type);
         1629         part->type = esmprint("%s/%s", s, t);
         1630         if(v->nsx < 7 || !islist(v->sx[2]) || !isstring(v->sx[3])
         1631         || !isstring(v->sx[4]) || !isstring(v->sx[5]) || !isnumber(v->sx[6]))
         1632                 goto bad;
         1633         parseparams(part, v->sx[2]);
         1634         part->idstr = nstring(v->sx[3]);
         1635         part->desc = nstring(v->sx[4]);
         1636         part->encoding = nstring(v->sx[5]);
         1637         part->size = v->sx[6]->number;
         1638         if(strcmp(s, "message") == 0 && strcmp(t, "rfc822") == 0){
         1639                 if(v->nsx < 10 || !islist(v->sx[7]) || !islist(v->sx[8]) || !isnumber(v->sx[9]))
         1640                         goto bad;
         1641                 part->hdr = parseenvelope(v->sx[7]);
         1642                 parsestructure(partcreate(part->msg, part), v->sx[8]);
         1643                 part->lines = v->sx[9]->number;
         1644         }
         1645         if(strcmp(s, "text") == 0){
         1646                 if(v->nsx < 8 || !isnumber(v->sx[7]))
         1647                         goto bad;
         1648                 part->lines = v->sx[7]->number;
         1649         }
         1650 }
         1651 
         1652 static void
         1653 xmsgbody(Msg *msg, Sx *k, Sx *v)
         1654 {
         1655         USED(k);
         1656         if(v->type != SxList){
         1657                 warn("bad body: %$", v);
         1658                 return;
         1659         }
         1660         /*
         1661          * To follow the structure exactly we should
         1662          * be doing this to partcreate(msg, msg->part[0]),
         1663          * and we should leave msg->part[0] with type message/rfc822,
         1664          * but the extra layer is redundant - what else would be in a mailbox?
         1665          */
         1666         parsestructure(msg->part[0], v);
         1667         if(msg->box->maxseen < msg->imapid)
         1668                 msg->box->maxseen = msg->imapid;
         1669         if(msg->imapuid >= msg->box->uidnext)
         1670                 msg->box->uidnext = msg->imapuid+1;
         1671 }
         1672 
         1673 static void
         1674 xmsgbodydata(Msg *msg, Sx *k, Sx *v)
         1675 {
         1676         int i;
         1677         char *name, *p;
         1678         Part *part;
         1679 
         1680         name = k->data;
         1681         name += 5;        /* body[ */
         1682         p = strchr(name, ']');
         1683         if(p)
         1684                 *p = 0;
         1685 
         1686         /* now name is something like 1 or 3.2.MIME - walk down parts from root */
         1687         part = msg->part[0];
         1688 
         1689 
         1690         while('1' <= name[0] && name[0] <= '9'){
         1691                 i = strtol(name, &p, 10);
         1692                 if(*p == '.')
         1693                         p++;
         1694                 else if(*p != 0){
         1695                         warn("bad body name: %$", k);
         1696                         return;
         1697                 }
         1698                 if((part = subpart(part, i-1)) == nil){
         1699                         warn("unknown body part: %$", k);
         1700                         return;
         1701                 }
         1702                 name = p;
         1703         }
         1704 
         1705 
         1706         if(cistrcmp(name, "") == 0){
         1707                 free(part->raw);
         1708                 part->raw = nstring(v);
         1709                 nocr(part->raw);
         1710         }else if(cistrcmp(name, "HEADER") == 0){
         1711                 free(part->rawheader);
         1712                 part->rawheader = nstring(v);
         1713                 nocr(part->rawheader);
         1714         }else if(cistrcmp(name, "MIME") == 0){
         1715                 free(part->mimeheader);
         1716                 part->mimeheader = nstring(v);
         1717                 nocr(part->mimeheader);
         1718         }else if(cistrcmp(name, "TEXT") == 0){
         1719                 free(part->rawbody);
         1720                 part->rawbody = nstring(v);
         1721                 nocr(part->rawbody);
         1722         }
         1723 }
         1724 
         1725 /*
         1726  * Table-driven OK info parser.
         1727  */
         1728 static void xokuidvalidity(Imap*, Sx*);
         1729 static void xokpermflags(Imap*, Sx*);
         1730 static void xokunseen(Imap*, Sx*);
         1731 static void xokreadwrite(Imap*, Sx*);
         1732 static void xokreadonly(Imap*, Sx*);
         1733 
         1734 struct {
         1735         char *name;
         1736         char fmt;
         1737         void (*fn)(Imap*, Sx*);
         1738 } oktab[] = {
         1739         "UIDVALIDITY", 'N',        xokuidvalidity,
         1740         "PERMANENTFLAGS", 'L',        xokpermflags,
         1741         "UNSEEN", 'N',        xokunseen,
         1742         "READ-WRITE", 0,        xokreadwrite,
         1743         "READ-ONLY",        0, xokreadonly
         1744 };
         1745 
         1746 static void
         1747 xok(Imap *z, Sx *sx)
         1748 {
         1749         int i;
         1750         char *name;
         1751         Sx *arg;
         1752 
         1753         if(sx->nsx >= 4 && sx->sx[2]->type == SxAtom && sx->sx[2]->data[0] == '['){
         1754                 if(sx->sx[3]->type == SxAtom && sx->sx[3]->data[0] == ']')
         1755                         arg = nil;
         1756                 else if(sx->sx[4]->type == SxAtom && sx->sx[4]->data[0] == ']')
         1757                         arg = sx->sx[3];
         1758                 else{
         1759                         warn("cannot parse OK: %$", sx);
         1760                         return;
         1761                 }
         1762                 name = sx->sx[2]->data+1;
         1763                 for(i=0; i<nelem(oktab); i++){
         1764                         if(cistrcmp(name, oktab[i].name) == 0){
         1765                                 if(oktab[i].fmt && (arg==nil || arg->type != fmttype(oktab[i].fmt))){
         1766                                         warn("malformed %s: %$", name, arg);
         1767                                         continue;
         1768                                 }
         1769                                 oktab[i].fn(z, arg);
         1770                         }
         1771                 }
         1772         }
         1773 }
         1774 
         1775 static void
         1776 xokuidvalidity(Imap *z, Sx *sx)
         1777 {
         1778         int i;
         1779         Box *b;
         1780 
         1781         if((b=z->box) == nil)
         1782                 return;
         1783         if(b->validity != sx->number){
         1784                 b->validity = sx->number;
         1785                 b->uidnext = 1;
         1786                 for(i=0; i<b->nmsg; i++)
         1787                         msgfree(b->msg[i]);
         1788                 free(b->msg);
         1789                 b->msg = nil;
         1790                 b->nmsg = 0;
         1791         }
         1792 }
         1793 
         1794 static void
         1795 xokpermflags(Imap *z, Sx *sx)
         1796 {
         1797         USED(z);
         1798         USED(sx);
         1799 /*        z->permflags = parseflags(sx); */
         1800 }
         1801 
         1802 static void
         1803 xokunseen(Imap *z, Sx *sx)
         1804 {
         1805         USED(z);
         1806         USED(sx);
         1807 /*        z->unseen = sx->number; */
         1808 }
         1809 
         1810 static void
         1811 xokreadwrite(Imap *z, Sx *sx)
         1812 {
         1813         USED(z);
         1814         USED(sx);
         1815 /*        z->boxmode = ORDWR; */
         1816 }
         1817 
         1818 static void
         1819 xokreadonly(Imap *z, Sx *sx)
         1820 {
         1821         USED(z);
         1822         USED(sx);
         1823 /*        z->boxmode = OREAD; */
         1824 }