Blob


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 */
11 #include "a.h"
12 #include <libsec.h>
14 struct Imap
15 {
16 int connected;
17 int autoreconnect;
18 int ticks; /* until boom! */
19 char* server;
20 char* root;
21 int mode;
22 int fd;
23 Biobuf b;
24 Ioproc* io;
25 QLock lk;
26 QLock rlk;
27 Rendez r;
29 Box* inbox;
30 Box* box;
31 Box* nextbox;
33 /* SEARCH results */
34 uint *uid;
35 uint nuid;
36 };
38 static struct {
39 char *name;
40 int flag;
41 } flagstab[] =
42 {
43 "Junk", FlagJunk,
44 "NonJunk", FlagNonJunk,
45 "\\Answered", FlagReplied,
46 "\\Flagged", FlagFlagged,
47 "\\Deleted", FlagDeleted,
48 "\\Draft", FlagDraft,
49 "\\Recent", FlagRecent,
50 "\\Seen", FlagSeen,
51 "\\NoInferiors", FlagNoInferiors,
52 "\\NoSelect", FlagNoSelect,
53 "\\Marked", FlagMarked,
54 "\\UnMarked", FlagUnMarked
55 };
57 int chattyimap;
59 static char *tag = "#";
61 static void checkbox(Imap*, Box*);
62 static char* copyaddrs(Sx*);
63 static void freeup(UserPasswd*);
64 static int getbox(Imap*, Box*);
65 static int getboxes(Imap*);
66 static char* gsub(char*, char*, char*);
67 static int imapcmd(Imap*, Box*, char*, ...);
68 static Sx* imapcmdsx(Imap*, Box*, char*, ...);
69 static Sx* imapcmdsx0(Imap*, char*, ...);
70 static Sx* imapvcmdsx(Imap*, Box*, char*, va_list);
71 static Sx* imapvcmdsx0(Imap*, char*, va_list);
72 static int imapdial(char*, int);
73 static int imaplogin(Imap*);
74 static int imapquote(Fmt*);
75 static int imapreconnect(Imap*);
76 static void imaprefreshthread(void*);
77 static void imaptimerproc(void*);
78 static Sx* imapwaitsx(Imap*);
79 static int isatom(Sx *v, char *name);
80 static int islist(Sx *v);
81 static int isnil(Sx *v);
82 static int isnumber(Sx *sx);
83 static int isstring(Sx *sx);
84 static int ioimapdial(Ioproc*, char*, int);
85 static char* nstring(Sx*);
86 static void unexpected(Imap*, Sx*);
87 static Sx* zBrdsx(Imap*);
89 /*
90 * Imap connection maintenance and login.
91 */
93 Imap*
94 imapconnect(char *server, int mode, char *root)
95 {
96 Imap *z;
98 fmtinstall('H', encodefmt);
99 fmtinstall('Z', imapquote);
101 z = emalloc(sizeof *z);
102 z->server = estrdup(server);
103 z->mode = mode;
104 if(root)
105 if(root[0] != 0 && root[strlen(root)-1] != '/')
106 z->root = smprint("%s/", root);
107 else
108 z->root = root;
109 else
110 z->root = "";
111 z->fd = -1;
112 z->autoreconnect = 0;
113 z->io = ioproc();
115 qlock(&z->lk);
116 if(imapreconnect(z) < 0){
117 free(z);
118 return nil;
121 z->r.l = &z->rlk;
122 z->autoreconnect = 1;
123 qunlock(&z->lk);
125 proccreate(imaptimerproc, z, STACK);
126 mailthread(imaprefreshthread, z);
128 return z;
131 void
132 imaphangup(Imap *z, int ticks)
134 z->ticks = ticks;
135 if(ticks == 0){
136 close(z->fd);
137 z->fd = -1;
141 static int
142 imapreconnect(Imap *z)
144 Sx *sx;
146 z->autoreconnect = 0;
147 z->box = nil;
148 z->inbox = nil;
150 if(z->fd >= 0){
151 close(z->fd);
152 z->fd = -1;
155 if(chattyimap)
156 fprint(2, "dial %s...\n", z->server);
157 if((z->fd = ioimapdial(z->io, z->server, z->mode)) < 0)
158 return -1;
159 z->connected = 1;
160 Binit(&z->b, z->fd, OREAD);
161 if((sx = zBrdsx(z)) == nil){
162 werrstr("no greeting");
163 goto err;
165 if(chattyimap)
166 fprint(2, "<I %#$\n", sx);
167 if(sx->nsx >= 2 && isatom(sx->sx[0], "*") && isatom(sx->sx[1], "PREAUTH")){
168 freesx(sx);
169 goto preauth;
171 if(!oksx(sx)){
172 werrstr("bad greeting - %#$", sx);
173 goto err;
175 freesx(sx);
176 sx = nil;
177 if(imaplogin(z) < 0)
178 goto err;
179 preauth:
180 if(getboxes(z) < 0 || getbox(z, z->inbox) < 0)
181 goto err;
182 z->autoreconnect = 1;
183 return 0;
185 err:
186 if(z->fd >= 0){
187 close(z->fd);
188 z->fd = -1;
190 if(sx)
191 freesx(sx);
192 z->autoreconnect = 1;
193 z->connected = 0;
194 return -1;
197 static int
198 imaplogin(Imap *z)
200 Sx *sx;
201 UserPasswd *up;
203 if((up = auth_getuserpasswd(auth_getkey, "proto=pass role=client service=imap server=%q", z->server)) == nil){
204 werrstr("getuserpasswd - %r");
205 return -1;
208 sx = imapcmdsx(z, nil, "LOGIN %Z %Z", up->user, up->passwd);
209 freeup(up);
210 if(sx == nil)
211 return -1;
212 if(!oksx(sx)){
213 freesx(sx);
214 werrstr("login rejected - %#$", sx);
215 return -1;
217 return 0;
220 static int
221 getboxes(Imap *z)
223 int i;
224 Box **r, **w, **e;
226 for(i=0; i<nboxes; i++){
227 boxes[i]->mark = 1;
228 boxes[i]->exists = 0;
229 boxes[i]->maxseen = 0;
231 if(imapcmd(z, nil, "LIST %Z *", z->root) < 0)
232 return -1;
233 if(z->root != nil && imapcmd(z, nil, "LIST %Z INBOX", "") < 0)
234 return -1;
235 if(z->nextbox && z->nextbox->mark)
236 z->nextbox = nil;
237 for(r=boxes, w=boxes, e=boxes+nboxes; r<e; r++){
238 if((*r)->mark)
239 {fprint(2, "*** free box %s %s\n", (*r)->name, (*r)->imapname);
240 boxfree(*r);
242 else
243 *w++ = *r;
245 nboxes = w - boxes;
246 return 0;
249 static int
250 getbox(Imap *z, Box *b)
252 int i;
253 Msg **r, **w, **e;
255 if(b == nil)
256 return 0;
258 for(i=0; i<b->nmsg; i++)
259 b->msg[i]->imapid = 0;
260 if(imapcmd(z, b, "UID FETCH 1:* FLAGS") < 0)
261 return -1;
262 for(r=b->msg, w=b->msg, e=b->msg+b->nmsg; r<e; r++){
263 if((*r)->imapid == 0)
264 msgfree(*r);
265 else{
266 (*r)->ix = w-b->msg;
267 *w++ = *r;
270 b->nmsg = w - b->msg;
271 b->imapinit = 1;
272 checkbox(z, b);
273 return 0;
276 static void
277 freeup(UserPasswd *up)
279 memset(up->user, 0, strlen(up->user));
280 memset(up->passwd, 0, strlen(up->passwd));
281 free(up);
284 static void
285 imaptimerproc(void *v)
287 Imap *z;
289 z = v;
290 for(;;){
291 sleep(60*1000);
292 qlock(z->r.l);
293 rwakeup(&z->r);
294 qunlock(z->r.l);
298 static void
299 checkbox(Imap *z, Box *b)
301 if(imapcmd(z, b, "NOOP") >= 0){
302 if(!b->imapinit)
303 getbox(z, b);
304 if(!b->imapinit)
305 return;
306 if(b==z->box && b->exists > b->maxseen){
307 imapcmd(z, b, "UID FETCH %d:* FULL",
308 b->uidnext);
313 static void
314 imaprefreshthread(void *v)
316 Imap *z;
318 z = v;
319 for(;;){
320 qlock(z->r.l);
321 rsleep(&z->r);
322 qunlock(z->r.l);
324 qlock(&z->lk);
325 if(z->inbox)
326 checkbox(z, z->inbox);
327 qunlock(&z->lk);
331 /*
332 * Run a single command and return the Sx. Does NOT redial.
333 */
334 static Sx*
335 imapvcmdsx0(Imap *z, char *fmt, va_list arg)
337 char *s;
338 Fmt f;
339 int prefix, len;
340 Sx *sx;
342 if(canqlock(&z->lk))
343 abort();
345 if(z->fd < 0 || !z->connected)
346 return nil;
348 prefix = strlen(tag)+1;
349 fmtstrinit(&f);
350 fmtprint(&f, "%s ", tag);
351 fmtvprint(&f, fmt, arg);
352 fmtprint(&f, "\r\n");
353 s = fmtstrflush(&f);
354 len = strlen(s);
355 s[len-2] = 0;
356 if(chattyimap)
357 fprint(2, "I> %s\n", s);
358 s[len-2] = '\r';
359 if(iowrite(z->io, z->fd, s, len) < 0){
360 z->connected = 0;
361 free(s);
362 return nil;
364 sx = imapwaitsx(z);
365 free(s);
366 return sx;
369 static Sx*
370 imapcmdsx0(Imap *z, char *fmt, ...)
372 va_list arg;
373 Sx *sx;
375 va_start(arg, fmt);
376 sx = imapvcmdsx0(z, fmt, arg);
377 va_end(arg);
378 return sx;
381 /*
382 * Run a single command on box b. Does redial.
383 */
384 static Sx*
385 imapvcmdsx(Imap *z, Box *b, char *fmt, va_list arg)
387 int tries;
388 Sx *sx;
390 tries = 0;
391 z->nextbox = b;
393 if(z->fd < 0 || !z->connected){
394 reconnect:
395 if(!z->autoreconnect)
396 return nil;
397 if(imapreconnect(z) < 0)
398 return nil;
399 if(b && z->nextbox == nil) /* box disappeared on reconnect */
400 return nil;
403 if(b && b != z->box){
404 if(z->box)
405 z->box->imapinit = 0;
406 z->box = b;
407 if((sx=imapcmdsx0(z, "SELECT %Z", b->imapname)) == nil){
408 z->box = nil;
409 if(tries++ == 0 && (z->fd < 0 || !z->connected))
410 goto reconnect;
411 return nil;
413 freesx(sx);
416 if((sx=imapvcmdsx0(z, fmt, arg)) == nil){
417 if(tries++ == 0 && (z->fd < 0 || !z->connected))
418 goto reconnect;
419 return nil;
421 return sx;
424 static int
425 imapcmd(Imap *z, Box *b, char *fmt, ...)
427 Sx *sx;
428 va_list arg;
430 va_start(arg, fmt);
431 sx = imapvcmdsx(z, b, fmt, arg);
432 va_end(arg);
433 if(sx == nil)
434 return -1;
435 if(sx->nsx < 2 || !isatom(sx->sx[1], "OK")){
436 werrstr("%$", sx);
437 freesx(sx);
438 return -1;
440 freesx(sx);
441 return 0;
444 static Sx*
445 imapcmdsx(Imap *z, Box *b, char *fmt, ...)
447 Sx *sx;
448 va_list arg;
450 va_start(arg, fmt);
451 sx = imapvcmdsx(z, b, fmt, arg);
452 va_end(arg);
453 return sx;
456 static Sx*
457 imapwaitsx(Imap *z)
459 Sx *sx;
461 while((sx = zBrdsx(z)) != nil){
462 if(chattyimap)
463 fprint(2, "<| %#$\n", sx);
464 if(sx->nsx >= 1 && sx->sx[0]->type == SxAtom && cistrcmp(sx->sx[0]->data, tag) == 0)
465 return sx;
466 if(sx->nsx >= 1 && sx->sx[0]->type == SxAtom && strcmp(sx->sx[0]->data, "*") == 0)
467 unexpected(z, sx);
468 if(sx->type == SxList && sx->nsx == 0){
469 freesx(sx);
470 break;
472 freesx(sx);
474 z->connected = 0;
475 return nil;
478 /*
479 * Imap interface to mail file system.
480 */
482 static void
483 _bodyname(char *buf, char *ebuf, Part *p, char *extra)
485 if(buf >= ebuf){
486 fprint(2, "***** BUFFER TOO SMALL\n");
487 return;
489 *buf = 0;
490 if(p->parent){
491 _bodyname(buf, ebuf, p->parent, "");
492 buf += strlen(buf);
493 seprint(buf, ebuf, ".%d", p->pix+1);
495 buf += strlen(buf);
496 seprint(buf, ebuf, "%s", extra);
499 static char*
500 bodyname(Part *p, char *extra)
502 static char buf[256];
503 memset(buf, 0, sizeof buf); /* can't see why this is necessary, but it is */
504 _bodyname(buf, buf+sizeof buf, p, extra);
505 return buf+1; /* buf[0] == '.' */
508 static void
509 fetch1(Imap *z, Part *p, char *s)
511 qlock(&z->lk);
512 imapcmd(z, p->msg->box, "UID FETCH %d BODY[%s]",
513 p->msg->imapuid, bodyname(p, s));
514 qunlock(&z->lk);
517 void
518 imapfetchrawheader(Imap *z, Part *p)
520 fetch1(z, p, ".HEADER");
523 void
524 imapfetchrawmime(Imap *z, Part *p)
526 fetch1(z, p, ".MIME");
529 void
530 imapfetchrawbody(Imap *z, Part *p)
532 fetch1(z, p, ".TEXT");
535 void
536 imapfetchraw(Imap *z, Part *p)
538 fetch1(z, p, "");
541 static int
542 imaplistcmd(Imap *z, Box *box, char *before, Msg **m, uint nm, char *after)
544 int i, r;
545 char *cmd;
546 Fmt fmt;
548 if(nm == 0)
549 return 0;
551 fmtstrinit(&fmt);
552 fmtprint(&fmt, "%s ", before);
553 for(i=0; i<nm; i++){
554 if(i > 0)
555 fmtrune(&fmt, ',');
556 fmtprint(&fmt, "%ud", m[i]->imapuid);
558 fmtprint(&fmt, " %s", after);
559 cmd = fmtstrflush(&fmt);
561 r = 0;
562 if(imapcmd(z, box, "%s", cmd) < 0)
563 r = -1;
564 free(cmd);
565 return r;
568 int
569 imapcopylist(Imap *z, char *nbox, Msg **m, uint nm)
571 int rv;
572 char *name, *p;
574 if(nm == 0)
575 return 0;
577 qlock(&z->lk);
578 if(strcmp(nbox, "mbox") == 0)
579 name = estrdup("INBOX");
580 else{
581 p = esmprint("%s%s", z->root, nbox);
582 name = esmprint("%Z", p);
583 free(p);
585 rv = imaplistcmd(z, m[0]->box, "UID COPY", m, nm, name);
586 free(name);
587 qunlock(&z->lk);
588 return rv;
591 int
592 imapremovelist(Imap *z, Msg **m, uint nm)
594 int rv;
596 if(nm == 0)
597 return 0;
599 qlock(&z->lk);
600 rv = imaplistcmd(z, m[0]->box, "UID STORE", m, nm, "+FLAGS.SILENT (\\Deleted)");
601 /* careful - box might be gone; use z->box instead */
602 if(rv == 0 && z->box)
603 rv = imapcmd(z, z->box, "EXPUNGE");
604 qunlock(&z->lk);
605 return rv;
608 int
609 imapflaglist(Imap *z, int op, int flag, Msg **m, uint nm)
611 char *mod, *s, *sep;
612 int i, rv;
613 Fmt fmt;
615 if(op > 0)
616 mod = "+";
617 else if(op == 0)
618 mod = "";
619 else
620 mod = "-";
622 fmtstrinit(&fmt);
623 fmtprint(&fmt, "%sFLAGS (", mod);
624 sep = "";
625 for(i=0; i<nelem(flagstab); i++){
626 if(flagstab[i].flag & flag){
627 fmtprint(&fmt, "%s%s", sep, flagstab[i].name);
628 sep = " ";
631 fmtprint(&fmt, ")");
632 s = fmtstrflush(&fmt);
634 qlock(&z->lk);
635 rv = imaplistcmd(z, m[0]->box, "UID STORE", m, nm, s);
636 qunlock(&z->lk);
637 free(s);
638 return rv;
641 int
642 imapsearchbox(Imap *z, Box *b, char *search, Msg ***mm)
644 uint *uid;
645 int i, nuid;
646 Msg **m;
647 int nm;
649 qlock(&z->lk);
650 if(imapcmd(z, b, "UID SEARCH CHARSET UTF-8 TEXT %Z", search) < 0){
651 qunlock(&z->lk);
652 return -1;
655 uid = z->uid;
656 nuid = z->nuid;
657 z->uid = nil;
658 z->nuid = 0;
659 qunlock(&z->lk);
661 m = emalloc(nuid*sizeof m[0]);
662 nm = 0;
663 for(i=0; i<nuid; i++)
664 if((m[nm] = msgbyimapuid(b, uid[i], 0)) != nil)
665 nm++;
666 *mm = m;
667 free(uid);
668 return nm;
671 void
672 imapcheckbox(Imap *z, Box *b)
674 if(b == nil)
675 return;
676 qlock(&z->lk);
677 checkbox(z, b);
678 qunlock(&z->lk);
681 /*
682 * Imap utility routines
683 */
684 static long
685 _ioimapdial(va_list *arg)
687 char *server;
688 int mode;
690 server = va_arg(*arg, char*);
691 mode = va_arg(*arg, int);
692 return imapdial(server, mode);
694 static int
695 ioimapdial(Ioproc *io, char *server, int mode)
697 return iocall(io, _ioimapdial, server, mode);
700 static long
701 _ioBrdsx(va_list *arg)
703 Biobuf *b;
704 Sx **sx;
706 b = va_arg(*arg, Biobuf*);
707 sx = va_arg(*arg, Sx**);
708 *sx = Brdsx(b);
709 if((*sx) && (*sx)->type == SxList && (*sx)->nsx == 0){
710 freesx(*sx);
711 *sx = nil;
713 return 0;
715 static Sx*
716 ioBrdsx(Ioproc *io, Biobuf *b)
718 Sx *sx;
720 iocall(io, _ioBrdsx, b, &sx);
721 return sx;
724 static Sx*
725 zBrdsx(Imap *z)
727 if(z->ticks && --z->ticks==0){
728 close(z->fd);
729 z->fd = -1;
730 return nil;
732 return ioBrdsx(z->io, &z->b);
735 static int
736 imapdial(char *server, int mode)
738 int p[2];
739 int fd[3];
740 char *tmp;
742 switch(mode){
743 default:
744 case Unencrypted:
745 return dial(netmkaddr(server, "tcp", "143"), nil, nil, nil);
747 case Starttls:
748 werrstr("starttls not supported");
749 return -1;
751 case Tls:
752 if(pipe(p) < 0)
753 return -1;
754 fd[0] = dup(p[0], -1);
755 fd[1] = dup(p[0], -1);
756 fd[2] = dup(2, -1);
757 tmp = esmprint("%s:993", server);
758 if(threadspawnl(fd, "tlsclient", "tlsclient", tmp, nil) < 0
759 && threadspawnl(fd, "/usr/sbin/stunnel3", "stunnel3", "-c", "-r", tmp, nil) < 0
760 && threadspawnl(fd, "/usr/bin/stunnel3", "stunnel3", "-c", "-r", tmp, nil) < 0
761 && threadspawnl(fd, "/usr/sbin/stunnel", "stunnel", "-c", "-r", tmp, nil) < 0
762 && threadspawnl(fd, "/usr/bin/stunnel", "stunnel", "-c", "-r", tmp, nil) < 0){
763 free(tmp);
764 close(p[0]);
765 close(p[1]);
766 close(fd[0]);
767 close(fd[1]);
768 close(fd[2]);
769 return -1;
771 free(tmp);
772 close(p[0]);
773 return p[1];
775 case Cmd:
776 if(pipe(p) < 0)
777 return -1;
778 fd[0] = dup(p[0], -1);
779 fd[1] = dup(p[0], -1);
780 fd[2] = dup(2, -1);
781 if(threadspawnl(fd, "/usr/local/plan9/bin/rc", "rc", "-c", server, nil) < 0){
782 close(p[0]);
783 close(p[1]);
784 close(fd[0]);
785 close(fd[1]);
786 close(fd[2]);
787 return -1;
789 close(p[0]);
790 return p[1];
794 enum
796 Qok = 0,
797 Qquote,
798 Qbackslash
799 };
801 static int
802 needtoquote(Rune r)
804 if(r >= Runeself)
805 return Qquote;
806 if(r <= ' ')
807 return Qquote;
808 if(r=='\\' || r=='"')
809 return Qbackslash;
810 return Qok;
813 static int
814 imapquote(Fmt *f)
816 char *s, *t;
817 int w, quotes;
818 Rune r;
820 s = va_arg(f->args, char*);
821 if(s == nil || *s == '\0')
822 return fmtstrcpy(f, "\"\"");
824 quotes = 0;
825 if(f->flags&FmtSharp)
826 quotes = 1;
827 for(t=s; *t; t+=w){
828 w = chartorune(&r, t);
829 quotes |= needtoquote(r);
831 if(quotes == 0)
832 return fmtstrcpy(f, s);
834 fmtrune(f, '"');
835 for(t=s; *t; t+=w){
836 w = chartorune(&r, t);
837 if(needtoquote(r) == Qbackslash)
838 fmtrune(f, '\\');
839 fmtrune(f, r);
841 return fmtrune(f, '"');
844 static int
845 fmttype(char c)
847 switch(c){
848 case 'A':
849 return SxAtom;
850 case 'L':
851 return SxList;
852 case 'N':
853 return SxNumber;
854 case 'S':
855 return SxString;
856 default:
857 return -1;
861 /*
862 * Check S expression against format string.
863 */
864 static int
865 sxmatch(Sx *sx, char *fmt)
867 int i;
869 for(i=0; fmt[i]; i++){
870 if(fmt[i] == '*')
871 fmt--; /* like i-- but better */
872 if(i == sx->nsx && fmt[i+1] == '*')
873 return 1;
874 if(i >= sx->nsx)
875 return 0;
876 if(sx->sx[i] == nil)
877 return 0;
878 if(sx->sx[i]->type == SxAtom && strcmp(sx->sx[i]->data, "NIL") == 0){
879 if(fmt[i] == 'L'){
880 free(sx->sx[i]->data);
881 sx->sx[i]->data = nil;
882 sx->sx[i]->type = SxList;
883 sx->sx[i]->sx = nil;
884 sx->sx[i]->nsx = 0;
886 else if(fmt[i] == 'S'){
887 free(sx->sx[i]->data);
888 sx->sx[i]->data = nil;
889 sx->sx[i]->type = SxString;
892 if(sx->sx[i]->type == SxAtom && fmt[i]=='S')
893 sx->sx[i]->type = SxString;
894 if(sx->sx[i]->type != fmttype(fmt[i])){
895 fprint(2, "sxmatch: %$ not %c\n", sx->sx[i], fmt[i]);
896 return 0;
899 if(i != sx->nsx)
900 return 0;
901 return 1;
904 /*
905 * Check string against format string.
906 */
907 static int
908 stringmatch(char *fmt, char *s)
910 for(; *fmt && *s; fmt++, s++){
911 switch(*fmt){
912 case '0':
913 if(*s == ' ')
914 break;
915 /* fall through */
916 case '1':
917 if(*s < '0' || *s > '9')
918 return 0;
919 break;
920 case 'A':
921 if(*s < 'A' || *s > 'Z')
922 return 0;
923 break;
924 case 'a':
925 if(*s < 'a' || *s > 'z')
926 return 0;
927 break;
928 case '+':
929 if(*s != '-' && *s != '+')
930 return 0;
931 break;
932 default:
933 if(*s != *fmt)
934 return 0;
935 break;
938 if(*fmt || *s)
939 return 0;
940 return 1;
943 /*
944 * Parse simple S expressions and IMAP elements.
945 */
946 static int
947 isatom(Sx *v, char *name)
949 int n;
951 if(v == nil || v->type != SxAtom)
952 return 0;
953 n = strlen(name);
954 if(cistrncmp(v->data, name, n) == 0)
955 if(v->data[n] == 0 || (n>0 && v->data[n-1] == '['))
956 return 1;
957 return 0;
960 static int
961 isstring(Sx *sx)
963 if(sx->type == SxAtom)
964 sx->type = SxString;
965 return sx->type == SxString;
968 static int
969 isnumber(Sx *sx)
971 return sx->type == SxNumber;
974 static int
975 isnil(Sx *v)
977 return v == nil ||
978 (v->type==SxList && v->nsx == 0) ||
979 (v->type==SxAtom && strcmp(v->data, "NIL") == 0);
982 static int
983 islist(Sx *v)
985 return isnil(v) || v->type==SxList;
988 static uint
989 parseflags(Sx *v)
991 int f, i, j;
993 if(v->type != SxList){
994 warn("malformed flags: %$", v);
995 return 0;
997 f = 0;
998 for(i=0; i<v->nsx; i++){
999 if(v->sx[i]->type != SxAtom)
1000 continue;
1001 for(j=0; j<nelem(flagstab); j++)
1002 if(cistrcmp(v->sx[i]->data, flagstab[j].name) == 0)
1003 f |= flagstab[j].flag;
1005 return f;
1008 static char months[] = "JanFebMarAprMayJunJulAugSepOctNovDec";
1009 static int
1010 parsemon(char *s)
1012 int i;
1014 for(i=0; months[i]; i+=3)
1015 if(memcmp(s, months+i, 3) == 0)
1016 return i/3;
1017 return -1;
1020 static uint
1021 parsedate(Sx *v)
1023 Tm tm;
1024 uint t;
1025 int delta;
1026 char *p;
1028 if(v->type != SxString || !stringmatch("01-Aaa-1111 01:11:11 +1111", v->data)){
1029 bad:
1030 warn("bad date: %$", v);
1031 return 0;
1034 /* cannot use atoi because 09 is malformed octal! */
1035 memset(&tm, 0, sizeof tm);
1036 p = v->data;
1037 tm.mday = strtol(p, 0, 10);
1038 tm.mon = parsemon(p+3);
1039 if(tm.mon == -1)
1040 goto bad;
1041 tm.year = strtol(p+7, 0, 10) - 1900;
1042 tm.hour = strtol(p+12, 0, 10);
1043 tm.min = strtol(p+15, 0, 10);
1044 tm.sec = strtol(p+18, 0, 10);
1045 strcpy(tm.zone, "GMT");
1047 t = tm2sec(&tm);
1048 delta = ((p[22]-'0')*10+p[23]-'0')*3600 + ((p[24]-'0')*10+p[25]-'0')*60;
1049 if(p[21] == '-')
1050 delta = -delta;
1052 t -= delta;
1053 return t;
1056 static uint
1057 parsenumber(Sx *v)
1059 if(v->type != SxNumber)
1060 return 0;
1061 return v->number;
1064 static void
1065 hash(DigestState *ds, char *tag, char *val)
1067 if(val == nil)
1068 val = "";
1069 md5((uchar*)tag, strlen(tag)+1, nil, ds);
1070 md5((uchar*)val, strlen(val)+1, nil, ds);
1073 static Hdr*
1074 parseenvelope(Sx *v)
1076 Hdr *hdr;
1077 uchar digest[16];
1078 DigestState ds;
1080 if(v->type != SxList || !sxmatch(v, "SSLLLLLLSS")){
1081 warn("bad envelope: %$", v);
1082 return nil;
1085 hdr = emalloc(sizeof *hdr);
1086 hdr->date = nstring(v->sx[0]);
1087 hdr->subject = unrfc2047(nstring(v->sx[1]));
1088 hdr->from = copyaddrs(v->sx[2]);
1089 hdr->sender = copyaddrs(v->sx[3]);
1090 hdr->replyto = copyaddrs(v->sx[4]);
1091 hdr->to = copyaddrs(v->sx[5]);
1092 hdr->cc = copyaddrs(v->sx[6]);
1093 hdr->bcc = copyaddrs(v->sx[7]);
1094 hdr->inreplyto = unrfc2047(nstring(v->sx[8]));
1095 hdr->messageid = unrfc2047(nstring(v->sx[9]));
1097 memset(&ds, 0, sizeof ds);
1098 hash(&ds, "date", hdr->date);
1099 hash(&ds, "subject", hdr->subject);
1100 hash(&ds, "from", hdr->from);
1101 hash(&ds, "sender", hdr->sender);
1102 hash(&ds, "replyto", hdr->replyto);
1103 hash(&ds, "to", hdr->to);
1104 hash(&ds, "cc", hdr->cc);
1105 hash(&ds, "bcc", hdr->bcc);
1106 hash(&ds, "inreplyto", hdr->inreplyto);
1107 hash(&ds, "messageid", hdr->messageid);
1108 md5(0, 0, digest, &ds);
1109 hdr->digest = esmprint("%.16H", digest);
1111 return hdr;
1114 static void
1115 strlwr(char *s)
1117 char *t;
1119 if(s == nil)
1120 return;
1121 for(t=s; *t; t++)
1122 if('A' <= *t && *t <= 'Z')
1123 *t += 'a' - 'A';
1126 static void
1127 nocr(char *s)
1129 char *r, *w;
1131 if(s == nil)
1132 return;
1133 for(r=w=s; *r; r++)
1134 if(*r != '\r')
1135 *w++ = *r;
1136 *w = 0;
1140 * substitute all occurrences of a with b in s.
1142 static char*
1143 gsub(char *s, char *a, char *b)
1145 char *p, *t, *w, *last;
1146 int n;
1148 n = 0;
1149 for(p=s; (p=strstr(p, a)) != nil; p+=strlen(a))
1150 n++;
1151 if(n == 0)
1152 return s;
1153 t = emalloc(strlen(s)+n*strlen(b)+1);
1154 w = t;
1155 for(p=s; last=p, (p=strstr(p, a)) != nil; p+=strlen(a)){
1156 memmove(w, last, p-last);
1157 w += p-last;
1158 memmove(w, b, strlen(b));
1159 w += strlen(b);
1161 strcpy(w, last);
1162 free(s);
1163 return t;
1167 * Table-driven IMAP "unexpected response" parser.
1168 * All the interesting data is in the unexpected responses.
1170 static void xlist(Imap*, Sx*);
1171 static void xrecent(Imap*, Sx*);
1172 static void xexists(Imap*, Sx*);
1173 static void xok(Imap*, Sx*);
1174 static void xflags(Imap*, Sx*);
1175 static void xfetch(Imap*, Sx*);
1176 static void xexpunge(Imap*, Sx*);
1177 static void xbye(Imap*, Sx*);
1178 static void xsearch(Imap*, Sx*);
1180 static struct {
1181 int num;
1182 char *name;
1183 char *fmt;
1184 void (*fn)(Imap*, Sx*);
1185 } unextab[] = {
1186 0, "BYE", nil, xbye,
1187 0, "FLAGS", "AAL", xflags,
1188 0, "LIST", "AALSS", xlist,
1189 0, "OK", nil, xok,
1190 0, "SEARCH", "AAN*", xsearch,
1192 1, "EXISTS", "ANA", xexists,
1193 1, "EXPUNGE", "ANA", xexpunge,
1194 1, "FETCH", "ANAL", xfetch,
1195 1, "RECENT", "ANA", xrecent
1198 static void
1199 unexpected(Imap *z, Sx *sx)
1201 int i, num;
1202 char *name;
1204 if(sx->nsx >= 3 && sx->sx[1]->type == SxNumber && sx->sx[2]->type == SxAtom){
1205 num = 1;
1206 name = sx->sx[2]->data;
1207 }else if(sx->nsx >= 2 && sx->sx[1]->type == SxAtom){
1208 num = 0;
1209 name = sx->sx[1]->data;
1210 }else
1211 return;
1213 for(i=0; i<nelem(unextab); i++){
1214 if(unextab[i].num == num && cistrcmp(unextab[i].name, name) == 0){
1215 if(unextab[i].fmt && !sxmatch(sx, unextab[i].fmt)){
1216 warn("malformed %s: %$", name, sx);
1217 continue;
1219 unextab[i].fn(z, sx);
1224 static int
1225 alldollars(char *s)
1227 for(; *s; s++)
1228 if(*s != '$')
1229 return 0;
1230 return 1;
1233 static void
1234 xlist(Imap *z, Sx *sx)
1236 int inbox;
1237 char *s, *t;
1238 Box *box;
1240 s = estrdup(sx->sx[4]->data);
1241 if(sx->sx[3]->data && strcmp(sx->sx[3]->data, "/") != 0){
1242 s = gsub(s, "/", "_");
1243 s = gsub(s, sx->sx[3]->data, "/");
1247 * INBOX is the special imap name for the main mailbox.
1248 * All other mailbox names have the root prefix removed, if applicable.
1250 inbox = 0;
1251 if(cistrcmp(s, "INBOX") == 0){
1252 inbox = 1;
1253 free(s);
1254 s = estrdup("mbox");
1255 } else if(z->root && strstr(s, z->root) == s) {
1256 t = estrdup(s+strlen(z->root));
1257 free(s);
1258 s = t;
1262 * Plan 9 calls the main mailbox mbox.
1263 * Rename any existing mbox by appending a $.
1265 if(!inbox && strncmp(s, "mbox", 4) == 0 && alldollars(s+4)){
1266 t = emalloc(strlen(s)+2);
1267 strcpy(t, s);
1268 strcat(t, "$");
1269 free(s);
1270 s = t;
1273 box = boxcreate(s);
1274 if(box == nil)
1275 return;
1276 box->imapname = estrdup(sx->sx[4]->data);
1277 if(inbox)
1278 z->inbox = box;
1279 box->mark = 0;
1280 box->flags = parseflags(sx->sx[2]);
1283 static void
1284 xrecent(Imap *z, Sx *sx)
1286 if(z->box)
1287 z->box->recent = sx->sx[1]->number;
1290 static void
1291 xexists(Imap *z, Sx *sx)
1293 if(z->box){
1294 z->box->exists = sx->sx[1]->number;
1295 if(z->box->exists < z->box->maxseen)
1296 z->box->maxseen = z->box->exists;
1300 static void
1301 xflags(Imap *z, Sx *sx)
1304 * This response contains in sx->sx[2] the list of flags
1305 * that can be validly attached to messages in z->box.
1306 * We don't have any use for this list, since we
1307 * use only the standard flags.
1311 static void
1312 xbye(Imap *z, Sx *sx)
1314 close(z->fd);
1315 z->fd = -1;
1316 z->connected = 0;
1319 static void
1320 xexpunge(Imap *z, Sx *sx)
1322 int i, n;
1323 Box *b;
1325 if((b=z->box) == nil)
1326 return;
1327 n = sx->sx[1]->number;
1328 for(i=0; i<b->nmsg; i++){
1329 if(b->msg[i]->imapid == n){
1330 msgplumb(b->msg[i], 1);
1331 msgfree(b->msg[i]);
1332 b->nmsg--;
1333 memmove(b->msg+i, b->msg+i+1, (b->nmsg-i)*sizeof b->msg[0]);
1334 i--;
1335 b->maxseen--;
1336 b->exists--;
1337 continue;
1339 if(b->msg[i]->imapid > n)
1340 b->msg[i]->imapid--;
1341 b->msg[i]->ix = i;
1345 static void
1346 xsearch(Imap *z, Sx *sx)
1348 int i;
1350 free(z->uid);
1351 z->uid = emalloc((sx->nsx-2)*sizeof z->uid[0]);
1352 z->nuid = sx->nsx-2;
1353 for(i=0; i<z->nuid; i++)
1354 z->uid[i] = sx->sx[i+2]->number;
1358 * Table-driven FETCH message info parser.
1360 static void xmsgflags(Msg*, Sx*, Sx*);
1361 static void xmsgdate(Msg*, Sx*, Sx*);
1362 static void xmsgrfc822size(Msg*, Sx*, Sx*);
1363 static void xmsgenvelope(Msg*, Sx*, Sx*);
1364 static void xmsgbody(Msg*, Sx*, Sx*);
1365 static void xmsgbodydata(Msg*, Sx*, Sx*);
1367 static struct {
1368 char *name;
1369 void (*fn)(Msg*, Sx*, Sx*);
1370 } msgtab[] = {
1371 "FLAGS", xmsgflags,
1372 "INTERNALDATE", xmsgdate,
1373 "RFC822.SIZE", xmsgrfc822size,
1374 "ENVELOPE", xmsgenvelope,
1375 "BODY", xmsgbody,
1376 "BODY[", xmsgbodydata
1379 static void
1380 xfetch(Imap *z, Sx *sx)
1382 int i, j, n, uid;
1383 Msg *msg;
1385 if(z->box == nil){
1386 warn("FETCH but no open box: %$", sx);
1387 return;
1390 /* * 152 FETCH (UID 185 FLAGS () ...) */
1391 if(sx->sx[3]->nsx%2){
1392 warn("malformed FETCH: %$", sx);
1393 return;
1396 n = sx->sx[1]->number;
1397 sx = sx->sx[3];
1398 for(i=0; i<sx->nsx; i+=2){
1399 if(isatom(sx->sx[i], "UID")){
1400 if(sx->sx[i+1]->type == SxNumber){
1401 uid = sx->sx[i+1]->number;
1402 goto haveuid;
1406 /* This happens: too bad.
1407 warn("FETCH without UID: %$", sx);
1409 return;
1411 haveuid:
1412 msg = msgbyimapuid(z->box, uid, 1);
1413 if(msg->imapid && msg->imapid != n)
1414 warn("msg id mismatch: want %d have %d", msg->id, n);
1415 msg->imapid = n;
1416 for(i=0; i<sx->nsx; i+=2){
1417 for(j=0; j<nelem(msgtab); j++)
1418 if(isatom(sx->sx[i], msgtab[j].name))
1419 msgtab[j].fn(msg, sx->sx[i], sx->sx[i+1]);
1423 static void
1424 xmsgflags(Msg *msg, Sx *k, Sx *v)
1426 USED(k);
1427 msg->flags = parseflags(v);
1430 static void
1431 xmsgdate(Msg *msg, Sx *k, Sx *v)
1433 USED(k);
1434 msg->date = parsedate(v);
1437 static void
1438 xmsgrfc822size(Msg *msg, Sx *k, Sx *v)
1440 USED(k);
1441 msg->size = parsenumber(v);
1444 static char*
1445 nstring(Sx *v)
1447 char *p;
1449 if(isnil(v))
1450 return estrdup("");
1451 p = v->data;
1452 v->data = nil;
1453 return p;
1456 static char*
1457 copyaddrs(Sx *v)
1459 char *s, *sep;
1460 char *name, *email, *host, *mbox;
1461 int i;
1462 Fmt fmt;
1464 if(v->nsx == 0)
1465 return nil;
1467 fmtstrinit(&fmt);
1468 sep = "";
1469 for(i=0; i<v->nsx; i++){
1470 if(!sxmatch(v->sx[i], "SSSS"))
1471 warn("bad address: %$", v->sx[i]);
1472 name = unrfc2047(nstring(v->sx[i]->sx[0]));
1473 /* ignore sx[1] - route */
1474 mbox = unrfc2047(nstring(v->sx[i]->sx[2]));
1475 host = unrfc2047(nstring(v->sx[i]->sx[3]));
1476 if(mbox == nil || host == nil){ /* rfc822 group syntax */
1477 free(name);
1478 free(mbox);
1479 free(host);
1480 continue;
1482 email = esmprint("%s@%s", mbox, host);
1483 free(mbox);
1484 free(host);
1485 fmtprint(&fmt, "%s%q %q", sep, name ? name : "", email ? email : "");
1486 free(name);
1487 free(email);
1488 sep = " ";
1490 s = fmtstrflush(&fmt);
1491 if(s == nil)
1492 sysfatal("out of memory");
1493 return s;
1496 static void
1497 xmsgenvelope(Msg *msg, Sx *k, Sx *v)
1499 hdrfree(msg->part[0]->hdr);
1500 msg->part[0]->hdr = parseenvelope(v);
1501 msgplumb(msg, 0);
1504 static struct {
1505 char *name;
1506 int offset;
1507 } paramtab[] = {
1508 "charset", offsetof(Part, charset),
1509 "name", offsetof(Part, filename)
1512 static void
1513 parseparams(Part *part, Sx *v)
1515 int i, j;
1516 char *s, *t, **p;
1518 if(isnil(v))
1519 return;
1520 if(v->nsx%2){
1521 warn("bad message params: %$", v);
1522 return;
1524 for(i=0; i<v->nsx; i+=2){
1525 s = nstring(v->sx[i]);
1526 t = nstring(v->sx[i+1]);
1527 for(j=0; j<nelem(paramtab); j++){
1528 if(cistrcmp(paramtab[j].name, s) == 0){
1529 p = (char**)((char*)part+paramtab[j].offset);
1530 free(*p);
1531 *p = t;
1532 t = nil;
1533 break;
1536 free(s);
1537 free(t);
1541 static void
1542 parsestructure(Part *part, Sx *v)
1544 int i;
1545 char *s, *t;
1547 if(isnil(v))
1548 return;
1549 if(v->type != SxList){
1550 bad:
1551 warn("bad structure: %$", v);
1552 return;
1554 if(islist(v->sx[0])){
1555 /* multipart */
1556 for(i=0; i<v->nsx && islist(v->sx[i]); i++)
1557 parsestructure(partcreate(part->msg, part), v->sx[i]);
1558 free(part->type);
1559 if(i != v->nsx-1 || !isstring(v->sx[i])){
1560 warn("bad multipart structure: %$", v);
1561 part->type = estrdup("multipart/mixed");
1562 return;
1564 s = nstring(v->sx[i]);
1565 strlwr(s);
1566 part->type = esmprint("multipart/%s", s);
1567 free(s);
1568 return;
1570 /* single part */
1571 if(!isstring(v->sx[0]) || v->nsx < 2)
1572 goto bad;
1573 s = nstring(v->sx[0]);
1574 t = nstring(v->sx[1]);
1575 strlwr(s);
1576 strlwr(t);
1577 free(part->type);
1578 part->type = esmprint("%s/%s", s, t);
1579 if(v->nsx < 7 || !islist(v->sx[2]) || !isstring(v->sx[3])
1580 || !isstring(v->sx[4]) || !isstring(v->sx[5]) || !isnumber(v->sx[6]))
1581 goto bad;
1582 parseparams(part, v->sx[2]);
1583 part->idstr = nstring(v->sx[3]);
1584 part->desc = nstring(v->sx[4]);
1585 part->encoding = nstring(v->sx[5]);
1586 part->size = v->sx[6]->number;
1587 if(strcmp(s, "message") == 0 && strcmp(t, "rfc822") == 0){
1588 if(v->nsx < 10 || !islist(v->sx[7]) || !islist(v->sx[8]) || !isnumber(v->sx[9]))
1589 goto bad;
1590 part->hdr = parseenvelope(v->sx[7]);
1591 parsestructure(partcreate(part->msg, part), v->sx[8]);
1592 part->lines = v->sx[9]->number;
1594 if(strcmp(s, "text") == 0){
1595 if(v->nsx < 8 || !isnumber(v->sx[7]))
1596 goto bad;
1597 part->lines = v->sx[7]->number;
1601 static void
1602 xmsgbody(Msg *msg, Sx *k, Sx *v)
1604 if(v->type != SxList){
1605 warn("bad body: %$", v);
1606 return;
1609 * To follow the structure exactly we should
1610 * be doing this to partcreate(msg, msg->part[0]),
1611 * and we should leave msg->part[0] with type message/rfc822,
1612 * but the extra layer is redundant - what else would be in a mailbox?
1614 parsestructure(msg->part[0], v);
1615 if(msg->box->maxseen < msg->imapid)
1616 msg->box->maxseen = msg->imapid;
1617 if(msg->imapuid >= msg->box->uidnext)
1618 msg->box->uidnext = msg->imapuid+1;
1621 static void
1622 xmsgbodydata(Msg *msg, Sx *k, Sx *v)
1624 int i;
1625 char *name, *p;
1626 Part *part;
1628 name = k->data;
1629 name += 5; /* body[ */
1630 p = strchr(name, ']');
1631 if(p)
1632 *p = 0;
1634 /* now name is something like 1 or 3.2.MIME - walk down parts from root */
1635 part = msg->part[0];
1638 while('1' <= name[0] && name[0] <= '9'){
1639 i = strtol(name, &p, 10);
1640 if(*p == '.')
1641 p++;
1642 else if(*p != 0){
1643 warn("bad body name: %$", k);
1644 return;
1646 if((part = subpart(part, i-1)) == nil){
1647 warn("unknown body part: %$", k);
1648 return;
1650 name = p;
1654 if(cistrcmp(name, "") == 0){
1655 free(part->raw);
1656 part->raw = nstring(v);
1657 nocr(part->raw);
1658 }else if(cistrcmp(name, "HEADER") == 0){
1659 free(part->rawheader);
1660 part->rawheader = nstring(v);
1661 nocr(part->rawheader);
1662 }else if(cistrcmp(name, "MIME") == 0){
1663 free(part->mimeheader);
1664 part->mimeheader = nstring(v);
1665 nocr(part->mimeheader);
1666 }else if(cistrcmp(name, "TEXT") == 0){
1667 free(part->rawbody);
1668 part->rawbody = nstring(v);
1669 nocr(part->rawbody);
1674 * Table-driven OK info parser.
1676 static void xokuidvalidity(Imap*, Sx*);
1677 static void xokpermflags(Imap*, Sx*);
1678 static void xokunseen(Imap*, Sx*);
1679 static void xokreadwrite(Imap*, Sx*);
1680 static void xokreadonly(Imap*, Sx*);
1682 struct {
1683 char *name;
1684 char fmt;
1685 void (*fn)(Imap*, Sx*);
1686 } oktab[] = {
1687 "UIDVALIDITY", 'N', xokuidvalidity,
1688 "PERMANENTFLAGS", 'L', xokpermflags,
1689 "UNSEEN", 'N', xokunseen,
1690 "READ-WRITE", 0, xokreadwrite,
1691 "READ-ONLY", 0, xokreadonly
1694 static void
1695 xok(Imap *z, Sx *sx)
1697 int i;
1698 char *name;
1699 Sx *arg;
1701 if(sx->nsx >= 4 && sx->sx[2]->type == SxAtom && sx->sx[2]->data[0] == '['){
1702 if(sx->sx[3]->type == SxAtom && sx->sx[3]->data[0] == ']')
1703 arg = nil;
1704 else if(sx->sx[4]->type == SxAtom && sx->sx[4]->data[0] == ']')
1705 arg = sx->sx[3];
1706 else{
1707 warn("cannot parse OK: %$", sx);
1708 return;
1710 name = sx->sx[2]->data+1;
1711 for(i=0; i<nelem(oktab); i++){
1712 if(cistrcmp(name, oktab[i].name) == 0){
1713 if(oktab[i].fmt && (arg==nil || arg->type != fmttype(oktab[i].fmt))){
1714 warn("malformed %s: %$", name, arg);
1715 continue;
1717 oktab[i].fn(z, arg);
1723 static void
1724 xokuidvalidity(Imap *z, Sx *sx)
1726 int i;
1727 Box *b;
1729 if((b=z->box) == nil)
1730 return;
1731 if(b->validity != sx->number){
1732 b->validity = sx->number;
1733 b->uidnext = 1;
1734 for(i=0; i<b->nmsg; i++)
1735 msgfree(b->msg[i]);
1736 free(b->msg);
1737 b->msg = nil;
1738 b->nmsg = 0;
1742 static void
1743 xokpermflags(Imap *z, Sx *sx)
1745 /* z->permflags = parseflags(sx); */
1748 static void
1749 xokunseen(Imap *z, Sx *sx)
1751 /* z->unseen = sx->number; */
1754 static void
1755 xokreadwrite(Imap *z, Sx *sx)
1757 /* z->boxmode = ORDWR; */
1760 static void
1761 xokreadonly(Imap *z, Sx *sx)
1763 /* z->boxmode = OREAD; */