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 char* user;
22 int mode;
23 int fd;
24 Biobuf b;
25 Ioproc* io;
26 QLock lk;
27 QLock rlk;
28 Rendez r;
30 Box* inbox;
31 Box* box;
32 Box* nextbox;
34 /* SEARCH results */
35 uint *uid;
36 uint nuid;
38 uint reply;
39 };
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 };
60 int chattyimap;
62 static char *tag = "#";
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*);
93 /*
94 * Imap connection maintenance and login.
95 */
97 Imap*
98 imapconnect(char *server, int mode, char *root, char *user)
99 {
100 Imap *z;
102 fmtinstall('H', encodefmt);
103 fmtinstall('Z', imapquote);
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();
120 qlock(&z->lk);
121 if(imapreconnect(z) < 0){
122 free(z);
123 return nil;
126 z->r.l = &z->rlk;
127 z->autoreconnect = 1;
128 qunlock(&z->lk);
130 proccreate(imaptimerproc, z, STACK);
131 mailthread(imaprefreshthread, z);
133 return z;
136 void
137 imaphangup(Imap *z, int ticks)
139 z->ticks = ticks;
140 if(ticks == 0){
141 close(z->fd);
142 z->fd = -1;
146 static int
147 imapreconnect(Imap *z)
149 Sx *sx;
151 z->autoreconnect = 0;
152 z->box = nil;
153 z->inbox = nil;
155 if(z->fd >= 0){
156 close(z->fd);
157 z->fd = -1;
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;
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;
176 if(!oksx(sx)){
177 werrstr("bad greeting - %#$", sx);
178 goto err;
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;
190 err:
191 if(z->fd >= 0){
192 close(z->fd);
193 z->fd = -1;
195 if(sx)
196 freesx(sx);
197 z->autoreconnect = 1;
198 z->connected = 0;
199 return -1;
202 static int
203 imaplogin(Imap *z)
205 Sx *sx;
206 UserPasswd *up;
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;
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;
226 return 0;
229 static int
230 getboxes(Imap *z)
232 int i;
233 Box **r, **w, **e;
235 for(i=0; i<nboxes; i++){
236 boxes[i]->mark = 1;
237 boxes[i]->exists = 0;
238 boxes[i]->maxseen = 0;
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);
251 else
252 *w++ = *r;
254 nboxes = w - boxes;
255 return 0;
258 static int
259 getbox(Imap *z, Box *b)
261 int i;
262 Msg **r, **w, **e;
264 if(b == nil)
265 return 0;
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;
279 b->nmsg = w - b->msg;
280 b->imapinit = 1;
281 checkbox(z, b);
282 return 0;
285 static void
286 freeup(UserPasswd *up)
288 memset(up->user, 0, strlen(up->user));
289 memset(up->passwd, 0, strlen(up->passwd));
290 free(up);
293 static void
294 imaptimerproc(void *v)
296 Imap *z;
298 z = v;
299 for(;;){
300 sleep(60*1000);
301 qlock(z->r.l);
302 rwakeup(&z->r);
303 qunlock(z->r.l);
307 static void
308 checkbox(Imap *z, Box *b)
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);
322 static void
323 imaprefreshthread(void *v)
325 Imap *z;
327 z = v;
328 for(;;){
329 qlock(z->r.l);
330 rsleep(&z->r);
331 qunlock(z->r.l);
333 qlock(&z->lk);
334 if(z->inbox)
335 checkbox(z, z->inbox);
336 qunlock(&z->lk);
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)
346 char *s;
347 Fmt f;
348 int len;
349 Sx *sx;
351 if(canqlock(&z->lk))
352 abort();
354 if(z->fd < 0 || !z->connected)
355 return nil;
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;
373 sx = imapwaitsx(z);
374 free(s);
375 return sx;
378 static Sx*
379 imapcmdsx0(Imap *z, char *fmt, ...)
381 va_list arg;
382 Sx *sx;
384 va_start(arg, fmt);
385 sx = imapvcmdsx0(z, fmt, arg, 1);
386 va_end(arg);
387 return sx;
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)
396 int tries;
397 Sx *sx;
399 tries = 0;
400 z->nextbox = b;
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;
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;
422 freesx(sx);
425 if((sx=imapvcmdsx0(z, fmt, arg, dotag)) == nil){
426 if(tries++ == 0 && (z->fd < 0 || !z->connected))
427 goto reconnect;
428 return nil;
430 return sx;
433 static int
434 imapcmd(Imap *z, Box *b, char *fmt, ...)
436 Sx *sx;
437 va_list arg;
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;
449 freesx(sx);
450 return 0;
453 static Sx*
454 imapcmdsx(Imap *z, Box *b, char *fmt, ...)
456 Sx *sx;
457 va_list arg;
459 va_start(arg, fmt);
460 sx = imapvcmdsx(z, b, fmt, arg, 1);
461 va_end(arg);
462 return sx;
465 static Sx*
466 imapcmdsxlit(Imap *z, Box *b, char *fmt, ...)
468 Sx *sx;
469 va_list arg;
471 va_start(arg, fmt);
472 sx = imapvcmdsx(z, b, fmt, arg, 0);
473 va_end(arg);
474 return sx;
477 static Sx*
478 imapwaitsx(Imap *z)
480 Sx *sx;
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;
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;
497 freesx(sx);
499 z->connected = 0;
500 return nil;
503 /*
504 * Imap interface to mail file system.
505 */
507 static void
508 _bodyname(char *buf, char *ebuf, Part *p, char *extra)
510 if(buf >= ebuf){
511 fprint(2, "***** BUFFER TOO SMALL\n");
512 return;
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);
520 buf += strlen(buf);
521 seprint(buf, ebuf, "%s", extra);
524 static char*
525 bodyname(Part *p, char *extra)
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] == '.' */
533 static void
534 fetch1(Imap *z, Part *p, char *s)
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);
542 void
543 imapfetchrawheader(Imap *z, Part *p)
545 fetch1(z, p, ".HEADER");
548 void
549 imapfetchrawmime(Imap *z, Part *p)
551 fetch1(z, p, ".MIME");
554 void
555 imapfetchrawbody(Imap *z, Part *p)
557 fetch1(z, p, ".TEXT");
560 void
561 imapfetchraw(Imap *z, Part *p)
563 fetch1(z, p, "");
566 static int
567 imaplistcmd(Imap *z, Box *box, char *before, Msg **m, uint nm, char *after)
569 int i, r;
570 char *cmd;
571 Fmt fmt;
573 if(nm == 0)
574 return 0;
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);
583 fmtprint(&fmt, " %s", after);
584 cmd = fmtstrflush(&fmt);
586 r = 0;
587 if(imapcmd(z, box, "%s", cmd) < 0)
588 r = -1;
589 free(cmd);
590 return r;
593 int
594 imapcopylist(Imap *z, char *nbox, Msg **m, uint nm)
596 int rv;
597 char *name, *p;
599 if(nm == 0)
600 return 0;
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);
610 rv = imaplistcmd(z, m[0]->box, "UID COPY", m, nm, name);
611 free(name);
612 qunlock(&z->lk);
613 return rv;
616 int
617 imapremovelist(Imap *z, Msg **m, uint nm)
619 int rv;
621 if(nm == 0)
622 return 0;
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;
633 int
634 imapflaglist(Imap *z, int op, int flag, Msg **m, uint nm)
636 char *mod, *s, *sep;
637 int i, rv;
638 Fmt fmt;
640 if(op > 0)
641 mod = "+";
642 else if(op == 0)
643 mod = "";
644 else
645 mod = "-";
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 = " ";
656 fmtprint(&fmt, ")");
657 s = fmtstrflush(&fmt);
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;
666 int
667 imapsearchbox(Imap *z, Box *b, char *search, Msg ***mm)
669 uint *uid;
670 int i, nuid;
671 Msg **m;
672 int nm;
673 Sx *sx;
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;
682 if((sx = imapcmdsxlit(z, b, "%s", search)) == nil){
683 qunlock(&z->lk);
684 return -1;
686 if(sx->nsx < 2 || !isatom(sx->sx[1], "OK")){
687 werrstr("%$", sx);
688 freesx(sx);
689 qunlock(&z->lk);
690 return -1;
692 freesx(sx);
694 uid = z->uid;
695 nuid = z->nuid;
696 z->uid = nil;
697 z->nuid = 0;
698 z->reply = 0;
699 qunlock(&z->lk);
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;
711 void
712 imapcheckbox(Imap *z, Box *b)
714 if(b == nil)
715 return;
716 qlock(&z->lk);
717 checkbox(z, b);
718 qunlock(&z->lk);
721 /*
722 * Imap utility routines
723 */
724 static long
725 _ioimapdial(va_list *arg)
727 char *server;
728 int mode;
730 server = va_arg(*arg, char*);
731 mode = va_arg(*arg, int);
732 return imapdial(server, mode);
734 static int
735 ioimapdial(Ioproc *io, char *server, int mode)
737 return iocall(io, _ioimapdial, server, mode);
740 static long
741 _ioBrdsx(va_list *arg)
743 Biobuf *b;
744 Sx **sx;
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;
753 return 0;
755 static Sx*
756 ioBrdsx(Ioproc *io, Biobuf *b)
758 Sx *sx;
760 iocall(io, _ioBrdsx, b, &sx);
761 return sx;
764 static Sx*
765 zBrdsx(Imap *z)
767 if(z->ticks && --z->ticks==0){
768 close(z->fd);
769 z->fd = -1;
770 return nil;
772 return ioBrdsx(z->io, &z->b);
775 static int
776 imapdial(char *server, int mode)
778 int p[2];
779 int fd[3];
780 char *tmp;
781 char *fpath;
783 switch(mode){
784 default:
785 case Unencrypted:
786 return dial(netmkaddr(server, "tcp", "143"), nil, nil, nil);
788 case Starttls:
789 werrstr("starttls not supported");
790 return -1;
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;
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;
818 free(tmp);
819 close(p[0]);
820 return p[1];
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;
836 close(p[0]);
837 return p[1];
841 enum
843 Qok = 0,
844 Qquote,
845 Qbackslash
846 };
848 static int
849 needtoquote(Rune r)
851 if(r >= Runeself)
852 return Qquote;
853 if(r <= ' ')
854 return Qquote;
855 if(r=='\\' || r=='"')
856 return Qbackslash;
857 return Qok;
860 static int
861 imapquote(Fmt *f)
863 char *s, *t;
864 int w, quotes;
865 Rune r;
867 s = va_arg(f->args, char*);
868 if(s == nil || *s == '\0')
869 return fmtstrcpy(f, "\"\"");
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);
878 if(quotes == 0)
879 return fmtstrcpy(f, s);
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);
888 return fmtrune(f, '"');
891 static int
892 fmttype(char c)
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;
908 /*
909 * Check S expression against format string.
910 */
911 static int
912 sxmatch(Sx *sx, char *fmt)
914 int i;
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;
933 else if(fmt[i] == 'S'){
934 free(sx->sx[i]->data);
935 sx->sx[i]->data = nil;
936 sx->sx[i]->type = SxString;
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;
946 if(i != sx->nsx)
947 return 0;
948 return 1;
951 /*
952 * Check string against format string.
953 */
954 static int
955 stringmatch(char *fmt, char *s)
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;
985 if(*fmt || *s)
986 return 0;
987 return 1;
990 /*
991 * Parse simple S expressions and IMAP elements.
992 */
993 static int
994 isatom(Sx *v, char *name)
996 int n;
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;
1007 static int
1008 isstring(Sx *sx)
1010 if(sx->type == SxAtom)
1011 sx->type = SxString;
1012 return sx->type == SxString;
1015 static int
1016 isnumber(Sx *sx)
1018 return sx->type == SxNumber;
1021 static int
1022 isnil(Sx *v)
1024 return v == nil ||
1025 (v->type==SxList && v->nsx == 0) ||
1026 (v->type==SxAtom && strcmp(v->data, "NIL") == 0);
1029 static int
1030 islist(Sx *v)
1032 return isnil(v) || v->type==SxList;
1035 static uint
1036 parseflags(Sx *v)
1038 int f, i, j;
1040 if(v->type != SxList){
1041 warn("malformed flags: %$", v);
1042 return 0;
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;
1052 return f;
1055 static char months[] = "JanFebMarAprMayJunJulAugSepOctNovDec";
1056 static int
1057 parsemon(char *s)
1059 int i;
1061 for(i=0; months[i]; i+=3)
1062 if(memcmp(s, months+i, 3) == 0)
1063 return i/3;
1064 return -1;
1067 static uint
1068 parsedate(Sx *v)
1070 Tm tm;
1071 uint t;
1072 int delta;
1073 char *p;
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;
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");
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;
1099 t -= delta;
1100 return t;
1103 static uint
1104 parsenumber(Sx *v)
1106 if(v->type != SxNumber)
1107 return 0;
1108 return v->number;
1111 static void
1112 hash(DigestState *ds, char *tag, char *val)
1114 if(val == nil)
1115 val = "";
1116 md5((uchar*)tag, strlen(tag)+1, nil, ds);
1117 md5((uchar*)val, strlen(val)+1, nil, ds);
1120 static Hdr*
1121 parseenvelope(Sx *v)
1123 Hdr *hdr;
1124 uchar digest[16];
1125 DigestState ds;
1127 if(v->type != SxList || !sxmatch(v, "SSLLLLLLSS")){
1128 warn("bad envelope: %$", v);
1129 return nil;
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]));
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);
1158 return hdr;
1161 static void
1162 strlwr(char *s)
1164 char *t;
1166 if(s == nil)
1167 return;
1168 for(t=s; *t; t++)
1169 if('A' <= *t && *t <= 'Z')
1170 *t += 'a' - 'A';
1173 static void
1174 nocr(char *s)
1176 char *r, *w;
1178 if(s == nil)
1179 return;
1180 for(r=w=s; *r; r++)
1181 if(*r != '\r')
1182 *w++ = *r;
1183 *w = 0;
1187 * substitute all occurrences of a with b in s.
1189 static char*
1190 gsub(char *s, char *a, char *b)
1192 char *p, *t, *w, *last;
1193 int n;
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);
1208 strcpy(w, last);
1209 free(s);
1210 return t;
1214 * Table-driven IMAP "unexpected response" parser.
1215 * All the interesting data is in the unexpected responses.
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*);
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,
1239 1, "EXISTS", "ANA", xexists,
1240 1, "EXPUNGE", "ANA", xexpunge,
1241 1, "FETCH", "ANAL", xfetch,
1242 1, "RECENT", "ANA", xrecent
1245 static void
1246 unexpected(Imap *z, Sx *sx)
1248 int i, num;
1249 char *name;
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;
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;
1266 unextab[i].fn(z, sx);
1271 static int
1272 alldollars(char *s)
1274 for(; *s; s++)
1275 if(*s != '$')
1276 return 0;
1277 return 1;
1280 static void
1281 xlist(Imap *z, Sx *sx)
1283 int inbox;
1284 char *s, *t;
1285 Box *box;
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, "/");
1294 * INBOX is the special imap name for the main mailbox.
1295 * All other mailbox names have the root prefix removed, if applicable.
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;
1309 * Plan 9 calls the main mailbox mbox.
1310 * Rename any existing mbox by appending a $.
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;
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]);
1330 static void
1331 xrecent(Imap *z, Sx *sx)
1333 if(z->box)
1334 z->box->recent = sx->sx[1]->number;
1337 static void
1338 xexists(Imap *z, Sx *sx)
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;
1347 static void
1348 xflags(Imap *z, Sx *sx)
1350 USED(z);
1351 USED(sx);
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.
1360 static void
1361 xbye(Imap *z, Sx *sx)
1363 USED(sx);
1364 close(z->fd);
1365 z->fd = -1;
1366 z->connected = 0;
1369 static void
1370 xexpunge(Imap *z, Sx *sx)
1372 int i, n;
1373 Box *b;
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;
1389 if(b->msg[i]->imapid > n)
1390 b->msg[i]->imapid--;
1391 b->msg[i]->ix = i;
1395 static void
1396 xsearch(Imap *z, Sx *sx)
1398 int i;
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;
1408 * Table-driven FETCH message info parser.
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*);
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
1429 static void
1430 xfetch(Imap *z, Sx *sx)
1432 int i, j, n, uid;
1433 Msg *msg;
1435 if(z->box == nil){
1436 warn("FETCH but no open box: %$", sx);
1437 return;
1440 /* * 152 FETCH (UID 185 FLAGS () ...) */
1441 if(sx->sx[3]->nsx%2){
1442 warn("malformed FETCH: %$", sx);
1443 return;
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;
1456 /* This happens: too bad.
1457 warn("FETCH without UID: %$", sx);
1459 return;
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]);
1471 msgplumb(msg, 0);
1474 static void
1475 xmsgflags(Msg *msg, Sx *k, Sx *v)
1477 USED(k);
1478 msg->flags = parseflags(v);
1481 static void
1482 xmsgdate(Msg *msg, Sx *k, Sx *v)
1484 USED(k);
1485 msg->date = parsedate(v);
1488 static void
1489 xmsgrfc822size(Msg *msg, Sx *k, Sx *v)
1491 USED(k);
1492 msg->size = parsenumber(v);
1495 static char*
1496 nstring(Sx *v)
1498 char *p;
1500 if(isnil(v))
1501 return estrdup("");
1502 p = v->data;
1503 v->data = nil;
1504 return p;
1507 static char*
1508 copyaddrs(Sx *v)
1510 char *s, *sep;
1511 char *name, *email, *host, *mbox;
1512 int i;
1513 Fmt fmt;
1515 if(v->nsx == 0)
1516 return nil;
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;
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 = " ";
1541 s = fmtstrflush(&fmt);
1542 if(s == nil)
1543 sysfatal("out of memory");
1544 return s;
1547 static void
1548 xmsgenvelope(Msg *msg, Sx *k, Sx *v)
1550 USED(k);
1551 hdrfree(msg->part[0]->hdr);
1552 msg->part[0]->hdr = parseenvelope(v);
1555 static struct {
1556 char *name;
1557 int offset;
1558 } paramtab[] = {
1559 "charset", offsetof(Part, charset),
1560 "name", offsetof(Part, filename)
1563 static void
1564 parseparams(Part *part, Sx *v)
1566 int i, j;
1567 char *s, *t, **p;
1569 if(isnil(v))
1570 return;
1571 if(v->nsx%2){
1572 warn("bad message params: %$", v);
1573 return;
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;
1587 free(s);
1588 free(t);
1592 static void
1593 parsestructure(Part *part, Sx *v)
1595 int i;
1596 char *s, *t;
1598 if(isnil(v))
1599 return;
1600 if(v->type != SxList){
1601 bad:
1602 warn("bad structure: %$", v);
1603 return;
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;
1615 s = nstring(v->sx[i]);
1616 strlwr(s);
1617 part->type = esmprint("multipart/%s", s);
1618 free(s);
1619 return;
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;
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;
1652 static void
1653 xmsgbody(Msg *msg, Sx *k, Sx *v)
1655 USED(k);
1656 if(v->type != SxList){
1657 warn("bad body: %$", v);
1658 return;
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?
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;
1673 static void
1674 xmsgbodydata(Msg *msg, Sx *k, Sx *v)
1676 int i;
1677 char *name, *p;
1678 Part *part;
1680 name = k->data;
1681 name += 5; /* body[ */
1682 p = strchr(name, ']');
1683 if(p)
1684 *p = 0;
1686 /* now name is something like 1 or 3.2.MIME - walk down parts from root */
1687 part = msg->part[0];
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;
1698 if((part = subpart(part, i-1)) == nil){
1699 warn("unknown body part: %$", k);
1700 return;
1702 name = p;
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);
1726 * Table-driven OK info parser.
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*);
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
1746 static void
1747 xok(Imap *z, Sx *sx)
1749 int i;
1750 char *name;
1751 Sx *arg;
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;
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;
1769 oktab[i].fn(z, arg);
1775 static void
1776 xokuidvalidity(Imap *z, Sx *sx)
1778 int i;
1779 Box *b;
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;
1794 static void
1795 xokpermflags(Imap *z, Sx *sx)
1797 USED(z);
1798 USED(sx);
1799 /* z->permflags = parseflags(sx); */
1802 static void
1803 xokunseen(Imap *z, Sx *sx)
1805 USED(z);
1806 USED(sx);
1807 /* z->unseen = sx->number; */
1810 static void
1811 xokreadwrite(Imap *z, Sx *sx)
1813 USED(z);
1814 USED(sx);
1815 /* z->boxmode = ORDWR; */
1818 static void
1819 xokreadonly(Imap *z, Sx *sx)
1821 USED(z);
1822 USED(sx);
1823 /* z->boxmode = OREAD; */