plan9port

fork of plan9port with libvec, libstr and libsdb
Log | Files | Refs | README | LICENSE

imap.c (32794B)


      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 		/* could do better - use get9root for rc(1) path */
    829 		if(threadspawnl(fd, PLAN9_TARGET "/bin/rc", "rc", "-c", server, nil) < 0){
    830 			close(p[0]);
    831 			close(p[1]);
    832 			close(fd[0]);
    833 			close(fd[1]);
    834 			close(fd[2]);
    835 			return -1;
    836 		}
    837 		close(p[0]);
    838 		return p[1];
    839 	}
    840 }
    841 
    842 enum
    843 {
    844 	Qok = 0,
    845 	Qquote,
    846 	Qbackslash
    847 };
    848 
    849 static int
    850 needtoquote(Rune r)
    851 {
    852 	if(r >= Runeself)
    853 		return Qquote;
    854 	if(r <= ' ')
    855 		return Qquote;
    856 	if(r=='\\' || r=='"')
    857 		return Qbackslash;
    858 	return Qok;
    859 }
    860 
    861 static int
    862 imapquote(Fmt *f)
    863 {
    864 	char *s, *t;
    865 	int w, quotes;
    866 	Rune r;
    867 
    868 	s = va_arg(f->args, char*);
    869 	if(s == nil || *s == '\0')
    870 		return fmtstrcpy(f, "\"\"");
    871 
    872 	quotes = 0;
    873 	if(f->flags&FmtSharp)
    874 		quotes = 1;
    875 	for(t=s; *t; t+=w){
    876 		w = chartorune(&r, t);
    877 		quotes |= needtoquote(r);
    878 	}
    879 	if(quotes == 0)
    880 		return fmtstrcpy(f, s);
    881 
    882 	fmtrune(f, '"');
    883 	for(t=s; *t; t+=w){
    884 		w = chartorune(&r, t);
    885 		if(needtoquote(r) == Qbackslash)
    886 			fmtrune(f, '\\');
    887 		fmtrune(f, r);
    888 	}
    889 	return fmtrune(f, '"');
    890 }
    891 
    892 static int
    893 fmttype(char c)
    894 {
    895 	switch(c){
    896 	case 'A':
    897 		return SxAtom;
    898 	case 'L':
    899 		return SxList;
    900 	case 'N':
    901 		return SxNumber;
    902 	case 'S':
    903 		return SxString;
    904 	default:
    905 		return -1;
    906 	}
    907 }
    908 
    909 /*
    910  * Check S expression against format string.
    911  */
    912 static int
    913 sxmatch(Sx *sx, char *fmt)
    914 {
    915 	int i;
    916 
    917 	for(i=0; fmt[i]; i++){
    918 		if(fmt[i] == '*')
    919 			fmt--;	/* like i-- but better */
    920 		if(i == sx->nsx && fmt[i+1] == '*')
    921 			return 1;
    922 		if(i >= sx->nsx)
    923 			return 0;
    924 		if(sx->sx[i] == nil)
    925 			return 0;
    926 		if(sx->sx[i]->type == SxAtom && strcmp(sx->sx[i]->data, "NIL") == 0){
    927 			if(fmt[i] == 'L'){
    928 				free(sx->sx[i]->data);
    929 				sx->sx[i]->data = nil;
    930 				sx->sx[i]->type = SxList;
    931 				sx->sx[i]->sx = nil;
    932 				sx->sx[i]->nsx = 0;
    933 			}
    934 			else if(fmt[i] == 'S'){
    935 				free(sx->sx[i]->data);
    936 				sx->sx[i]->data = nil;
    937 				sx->sx[i]->type = SxString;
    938 			}
    939 		}
    940 		if(sx->sx[i]->type == SxAtom && fmt[i]=='S')
    941 			sx->sx[i]->type = SxString;
    942 		if(sx->sx[i]->type != fmttype(fmt[i])){
    943 			fprint(2, "sxmatch: %$ not %c\n", sx->sx[i], fmt[i]);
    944 			return 0;
    945 		}
    946 	}
    947 	if(i != sx->nsx)
    948 		return 0;
    949 	return 1;
    950 }
    951 
    952 /*
    953  * Check string against format string.
    954  */
    955 static int
    956 stringmatch(char *fmt, char *s)
    957 {
    958 	for(; *fmt && *s; fmt++, s++){
    959 		switch(*fmt){
    960 		case '0':
    961 			if(*s == ' ')
    962 				break;
    963 			/* fall through */
    964 		case '1':
    965 			if(*s < '0' || *s > '9')
    966 				return 0;
    967 			break;
    968 		case 'A':
    969 			if(*s < 'A' || *s > 'Z')
    970 				return 0;
    971 			break;
    972 		case 'a':
    973 			if(*s < 'a' || *s > 'z')
    974 				return 0;
    975 			break;
    976 		case '+':
    977 			if(*s != '-' && *s != '+')
    978 				return 0;
    979 			break;
    980 		default:
    981 			if(*s != *fmt)
    982 				return 0;
    983 			break;
    984 		}
    985 	}
    986 	if(*fmt || *s)
    987 		return 0;
    988 	return 1;
    989 }
    990 
    991 /*
    992  * Parse simple S expressions and IMAP elements.
    993  */
    994 static int
    995 isatom(Sx *v, char *name)
    996 {
    997 	int n;
    998 
    999 	if(v == nil || v->type != SxAtom)
   1000 		return 0;
   1001 	n = strlen(name);
   1002 	if(cistrncmp(v->data, name, n) == 0)
   1003 		if(v->data[n] == 0 || (n>0 && v->data[n-1] == '['))
   1004 			return 1;
   1005 	return 0;
   1006 }
   1007 
   1008 static int
   1009 isstring(Sx *sx)
   1010 {
   1011 	if(sx->type == SxAtom)
   1012 		sx->type = SxString;
   1013 	return sx->type == SxString;
   1014 }
   1015 
   1016 static int
   1017 isnumber(Sx *sx)
   1018 {
   1019 	return sx->type == SxNumber;
   1020 }
   1021 
   1022 static int
   1023 isnil(Sx *v)
   1024 {
   1025 	return v == nil ||
   1026 		(v->type==SxList && v->nsx == 0) ||
   1027 		(v->type==SxAtom && strcmp(v->data, "NIL") == 0);
   1028 }
   1029 
   1030 static int
   1031 islist(Sx *v)
   1032 {
   1033 	return isnil(v) || v->type==SxList;
   1034 }
   1035 
   1036 static uint
   1037 parseflags(Sx *v)
   1038 {
   1039 	int f, i, j;
   1040 
   1041 	if(v->type != SxList){
   1042 		warn("malformed flags: %$", v);
   1043 		return 0;
   1044 	}
   1045 	f = 0;
   1046 	for(i=0; i<v->nsx; i++){
   1047 		if(v->sx[i]->type != SxAtom)
   1048 			continue;
   1049 		for(j=0; j<nelem(flagstab); j++)
   1050 			if(cistrcmp(v->sx[i]->data, flagstab[j].name) == 0)
   1051 				f |= flagstab[j].flag;
   1052 	}
   1053 	return f;
   1054 }
   1055 
   1056 static char months[] = "JanFebMarAprMayJunJulAugSepOctNovDec";
   1057 static int
   1058 parsemon(char *s)
   1059 {
   1060 	int i;
   1061 
   1062 	for(i=0; months[i]; i+=3)
   1063 		if(memcmp(s, months+i, 3) == 0)
   1064 			return i/3;
   1065 	return -1;
   1066 }
   1067 
   1068 static uint
   1069 parsedate(Sx *v)
   1070 {
   1071 	Tm tm;
   1072 	uint t;
   1073 	int delta;
   1074 	char *p;
   1075 
   1076 	if(v->type != SxString || !stringmatch("01-Aaa-1111 01:11:11 +1111", v->data)){
   1077 	bad:
   1078 		warn("bad date: %$", v);
   1079 		return 0;
   1080 	}
   1081 
   1082 	/* cannot use atoi because 09 is malformed octal! */
   1083 	memset(&tm, 0, sizeof tm);
   1084 	p = v->data;
   1085 	tm.mday = strtol(p, 0, 10);
   1086 	tm.mon = parsemon(p+3);
   1087 	if(tm.mon == -1)
   1088 		goto bad;
   1089 	tm.year = strtol(p+7, 0, 10) - 1900;
   1090 	tm.hour = strtol(p+12, 0, 10);
   1091 	tm.min = strtol(p+15, 0, 10);
   1092 	tm.sec = strtol(p+18, 0, 10);
   1093 	strcpy(tm.zone, "GMT");
   1094 
   1095 	t = tm2sec(&tm);
   1096 	delta = ((p[22]-'0')*10+p[23]-'0')*3600 + ((p[24]-'0')*10+p[25]-'0')*60;
   1097 	if(p[21] == '-')
   1098 		delta = -delta;
   1099 
   1100 	t -= delta;
   1101 	return t;
   1102 }
   1103 
   1104 static uint
   1105 parsenumber(Sx *v)
   1106 {
   1107 	if(v->type != SxNumber)
   1108 		return 0;
   1109 	return v->number;
   1110 }
   1111 
   1112 static void
   1113 hash(DigestState *ds, char *tag, char *val)
   1114 {
   1115 	if(val == nil)
   1116 		val = "";
   1117 	md5((uchar*)tag, strlen(tag)+1, nil, ds);
   1118 	md5((uchar*)val, strlen(val)+1, nil, ds);
   1119 }
   1120 
   1121 static Hdr*
   1122 parseenvelope(Sx *v)
   1123 {
   1124 	Hdr *hdr;
   1125 	uchar digest[16];
   1126 	DigestState ds;
   1127 
   1128 	if(v->type != SxList || !sxmatch(v, "SSLLLLLLSS")){
   1129 		warn("bad envelope: %$", v);
   1130 		return nil;
   1131 	}
   1132 
   1133 	hdr = emalloc(sizeof *hdr);
   1134 	hdr->date = nstring(v->sx[0]);
   1135 	hdr->subject = unrfc2047(nstring(v->sx[1]));
   1136 	hdr->from = copyaddrs(v->sx[2]);
   1137 	hdr->sender = copyaddrs(v->sx[3]);
   1138 	hdr->replyto = copyaddrs(v->sx[4]);
   1139 	hdr->to = copyaddrs(v->sx[5]);
   1140 	hdr->cc = copyaddrs(v->sx[6]);
   1141 	hdr->bcc = copyaddrs(v->sx[7]);
   1142 	hdr->inreplyto = unrfc2047(nstring(v->sx[8]));
   1143 	hdr->messageid = unrfc2047(nstring(v->sx[9]));
   1144 
   1145 	memset(&ds, 0, sizeof ds);
   1146 	hash(&ds, "date", hdr->date);
   1147 	hash(&ds, "subject", hdr->subject);
   1148 	hash(&ds, "from", hdr->from);
   1149 	hash(&ds, "sender", hdr->sender);
   1150 	hash(&ds, "replyto", hdr->replyto);
   1151 	hash(&ds, "to", hdr->to);
   1152 	hash(&ds, "cc", hdr->cc);
   1153 	hash(&ds, "bcc", hdr->bcc);
   1154 	hash(&ds, "inreplyto", hdr->inreplyto);
   1155 	hash(&ds, "messageid", hdr->messageid);
   1156 	md5(0, 0, digest, &ds);
   1157 	hdr->digest = esmprint("%.16H", digest);
   1158 
   1159 	return hdr;
   1160 }
   1161 
   1162 static void
   1163 strlwr(char *s)
   1164 {
   1165 	char *t;
   1166 
   1167 	if(s == nil)
   1168 		return;
   1169 	for(t=s; *t; t++)
   1170 		if('A' <= *t && *t <= 'Z')
   1171 			*t += 'a' - 'A';
   1172 }
   1173 
   1174 static void
   1175 nocr(char *s)
   1176 {
   1177 	char *r, *w;
   1178 
   1179 	if(s == nil)
   1180 		return;
   1181 	for(r=w=s; *r; r++)
   1182 		if(*r != '\r')
   1183 			*w++ = *r;
   1184 	*w = 0;
   1185 }
   1186 
   1187 /*
   1188  * substitute all occurrences of a with b in s.
   1189  */
   1190 static char*
   1191 gsub(char *s, char *a, char *b)
   1192 {
   1193 	char *p, *t, *w, *last;
   1194 	int n;
   1195 
   1196 	n = 0;
   1197 	for(p=s; (p=strstr(p, a)) != nil; p+=strlen(a))
   1198 		n++;
   1199 	if(n == 0)
   1200 		return s;
   1201 	t = emalloc(strlen(s)+n*strlen(b)+1);
   1202 	w = t;
   1203 	for(p=s; last=p, (p=strstr(p, a)) != nil; p+=strlen(a)){
   1204 		memmove(w, last, p-last);
   1205 		w += p-last;
   1206 		memmove(w, b, strlen(b));
   1207 		w += strlen(b);
   1208 	}
   1209 	strcpy(w, last);
   1210 	free(s);
   1211 	return t;
   1212 }
   1213 
   1214 /*
   1215  * Table-driven IMAP "unexpected response" parser.
   1216  * All the interesting data is in the unexpected responses.
   1217  */
   1218 static void xlist(Imap*, Sx*);
   1219 static void xrecent(Imap*, Sx*);
   1220 static void xexists(Imap*, Sx*);
   1221 static void xok(Imap*, Sx*);
   1222 static void xflags(Imap*, Sx*);
   1223 static void xfetch(Imap*, Sx*);
   1224 static void xexpunge(Imap*, Sx*);
   1225 static void xbye(Imap*, Sx*);
   1226 static void xsearch(Imap*, Sx*);
   1227 
   1228 static struct {
   1229 	int		num;
   1230 	char		*name;
   1231 	char		*fmt;
   1232 	void		(*fn)(Imap*, Sx*);
   1233 } unextab[] = {
   1234 	0,	"BYE",		nil,			xbye,
   1235 	0,	"FLAGS",		"AAL",		xflags,
   1236 	0,	"LIST",		"AALSS",		xlist,
   1237 	0,	"OK",		nil,			xok,
   1238 	0,	"SEARCH",	"AAN*",		xsearch,
   1239 
   1240 	1,	"EXISTS",		"ANA",		xexists,
   1241 	1,	"EXPUNGE",	"ANA",		xexpunge,
   1242 	1,	"FETCH",		"ANAL",		xfetch,
   1243 	1,	"RECENT",	"ANA",		xrecent
   1244 };
   1245 
   1246 static void
   1247 unexpected(Imap *z, Sx *sx)
   1248 {
   1249 	int i, num;
   1250 	char *name;
   1251 
   1252 	if(sx->nsx >= 3 && sx->sx[1]->type == SxNumber && sx->sx[2]->type == SxAtom){
   1253 		num = 1;
   1254 		name = sx->sx[2]->data;
   1255 	}else if(sx->nsx >= 2 && sx->sx[1]->type == SxAtom){
   1256 		num = 0;
   1257 		name = sx->sx[1]->data;
   1258 	}else
   1259 		return;
   1260 
   1261 	for(i=0; i<nelem(unextab); i++){
   1262 		if(unextab[i].num == num && cistrcmp(unextab[i].name, name) == 0){
   1263 			if(unextab[i].fmt && !sxmatch(sx, unextab[i].fmt)){
   1264 				warn("malformed %s: %$", name, sx);
   1265 				continue;
   1266 			}
   1267 			unextab[i].fn(z, sx);
   1268 		}
   1269 	}
   1270 }
   1271 
   1272 static int
   1273 alldollars(char *s)
   1274 {
   1275 	for(; *s; s++)
   1276 		if(*s != '$')
   1277 			return 0;
   1278 	return 1;
   1279 }
   1280 
   1281 static void
   1282 xlist(Imap *z, Sx *sx)
   1283 {
   1284 	int inbox;
   1285 	char *s, *t;
   1286 	Box *box;
   1287 
   1288 	s = estrdup(sx->sx[4]->data);
   1289 	if(sx->sx[3]->data && strcmp(sx->sx[3]->data, "/") != 0){
   1290 		s = gsub(s, "/", "_");
   1291 		s = gsub(s, sx->sx[3]->data, "/");
   1292 	}
   1293 
   1294 	/*
   1295 	 * INBOX is the special imap name for the main mailbox.
   1296 	 * All other mailbox names have the root prefix removed, if applicable.
   1297 	 */
   1298 	inbox = 0;
   1299 	if(cistrcmp(s, "INBOX") == 0){
   1300 		inbox = 1;
   1301 		free(s);
   1302 		s = estrdup("mbox");
   1303 	} else if(z->root && strstr(s, z->root) == s) {
   1304 		t = estrdup(s+strlen(z->root));
   1305 		free(s);
   1306 		s = t;
   1307 	}
   1308 
   1309 	/*
   1310 	 * Plan 9 calls the main mailbox mbox.
   1311 	 * Rename any existing mbox by appending a $.
   1312 	 */
   1313 	if(!inbox && strncmp(s, "mbox", 4) == 0 && alldollars(s+4)){
   1314 		t = emalloc(strlen(s)+2);
   1315 		strcpy(t, s);
   1316 		strcat(t, "$");
   1317 		free(s);
   1318 		s = t;
   1319 	}
   1320 
   1321 	box = boxcreate(s);
   1322 	if(box == nil)
   1323 		return;
   1324 	box->imapname = estrdup(sx->sx[4]->data);
   1325 	if(inbox)
   1326 		z->inbox = box;
   1327 	box->mark = 0;
   1328 	box->flags = parseflags(sx->sx[2]);
   1329 }
   1330 
   1331 static void
   1332 xrecent(Imap *z, Sx *sx)
   1333 {
   1334 	if(z->box)
   1335 		z->box->recent = sx->sx[1]->number;
   1336 }
   1337 
   1338 static void
   1339 xexists(Imap *z, Sx *sx)
   1340 {
   1341 	if(z->box){
   1342 		z->box->exists = sx->sx[1]->number;
   1343 		if(z->box->exists < z->box->maxseen)
   1344 			z->box->maxseen = z->box->exists;
   1345 	}
   1346 }
   1347 
   1348 static void
   1349 xflags(Imap *z, Sx *sx)
   1350 {
   1351 	USED(z);
   1352 	USED(sx);
   1353 	/*
   1354 	 * This response contains in sx->sx[2] the list of flags
   1355 	 * that can be validly attached to messages in z->box.
   1356 	 * We don't have any use for this list, since we
   1357 	 * use only the standard flags.
   1358 	 */
   1359 }
   1360 
   1361 static void
   1362 xbye(Imap *z, Sx *sx)
   1363 {
   1364 	USED(sx);
   1365 	close(z->fd);
   1366 	z->fd = -1;
   1367 	z->connected = 0;
   1368 }
   1369 
   1370 static void
   1371 xexpunge(Imap *z, Sx *sx)
   1372 {
   1373 	int i, n;
   1374 	Box *b;
   1375 
   1376 	if((b=z->box) == nil)
   1377 		return;
   1378 	n = sx->sx[1]->number;
   1379 	for(i=0; i<b->nmsg; i++){
   1380 		if(b->msg[i]->imapid == n){
   1381 			msgplumb(b->msg[i], 1);
   1382 			msgfree(b->msg[i]);
   1383 			b->nmsg--;
   1384 			memmove(b->msg+i, b->msg+i+1, (b->nmsg-i)*sizeof b->msg[0]);
   1385 			i--;
   1386 			b->maxseen--;
   1387 			b->exists--;
   1388 			continue;
   1389 		}
   1390 		if(b->msg[i]->imapid > n)
   1391 			b->msg[i]->imapid--;
   1392 		b->msg[i]->ix = i;
   1393 	}
   1394 }
   1395 
   1396 static void
   1397 xsearch(Imap *z, Sx *sx)
   1398 {
   1399 	int i;
   1400 
   1401 	free(z->uid);
   1402 	z->uid = emalloc((sx->nsx-2)*sizeof z->uid[0]);
   1403 	z->nuid = sx->nsx-2;
   1404 	for(i=0; i<z->nuid; i++)
   1405 		z->uid[i] = sx->sx[i+2]->number;
   1406 }
   1407 
   1408 /*
   1409  * Table-driven FETCH message info parser.
   1410  */
   1411 static void xmsgflags(Msg*, Sx*, Sx*);
   1412 static void xmsgdate(Msg*, Sx*, Sx*);
   1413 static void xmsgrfc822size(Msg*, Sx*, Sx*);
   1414 static void xmsgenvelope(Msg*, Sx*, Sx*);
   1415 static void xmsgbody(Msg*, Sx*, Sx*);
   1416 static void xmsgbodydata(Msg*, Sx*, Sx*);
   1417 
   1418 static struct {
   1419 	char *name;
   1420 	void (*fn)(Msg*, Sx*, Sx*);
   1421 } msgtab[] = {
   1422 	"FLAGS", xmsgflags,
   1423 	"INTERNALDATE", xmsgdate,
   1424 	"RFC822.SIZE", xmsgrfc822size,
   1425 	"ENVELOPE", xmsgenvelope,
   1426 	"BODY", xmsgbody,
   1427 	"BODY[", xmsgbodydata
   1428 };
   1429 
   1430 static void
   1431 xfetch(Imap *z, Sx *sx)
   1432 {
   1433 	int i, j, n, uid;
   1434 	Msg *msg;
   1435 
   1436 	if(z->box == nil){
   1437 		warn("FETCH but no open box: %$", sx);
   1438 		return;
   1439 	}
   1440 
   1441 	/* * 152 FETCH (UID 185 FLAGS () ...) */
   1442 	if(sx->sx[3]->nsx%2){
   1443 		warn("malformed FETCH: %$", sx);
   1444 		return;
   1445 	}
   1446 
   1447 	n = sx->sx[1]->number;
   1448 	sx = sx->sx[3];
   1449 	for(i=0; i<sx->nsx; i+=2){
   1450 		if(isatom(sx->sx[i], "UID")){
   1451 			if(sx->sx[i+1]->type == SxNumber){
   1452 				uid = sx->sx[i+1]->number;
   1453 				goto haveuid;
   1454 			}
   1455 		}
   1456 	}
   1457 /* This happens: too bad.
   1458 	warn("FETCH without UID: %$", sx);
   1459 */
   1460 	return;
   1461 
   1462 haveuid:
   1463 	msg = msgbyimapuid(z->box, uid, 1);
   1464 	if(msg->imapid && msg->imapid != n)
   1465 		warn("msg id mismatch: want %d have %d", msg->id, n);
   1466 	msg->imapid = n;
   1467 	for(i=0; i<sx->nsx; i+=2){
   1468 		for(j=0; j<nelem(msgtab); j++)
   1469 			if(isatom(sx->sx[i], msgtab[j].name))
   1470 				msgtab[j].fn(msg, sx->sx[i], sx->sx[i+1]);
   1471 	}
   1472 	msgplumb(msg, 0);
   1473 }
   1474 
   1475 static void
   1476 xmsgflags(Msg *msg, Sx *k, Sx *v)
   1477 {
   1478 	USED(k);
   1479 	msg->flags = parseflags(v);
   1480 }
   1481 
   1482 static void
   1483 xmsgdate(Msg *msg, Sx *k, Sx *v)
   1484 {
   1485 	USED(k);
   1486 	msg->date = parsedate(v);
   1487 }
   1488 
   1489 static void
   1490 xmsgrfc822size(Msg *msg, Sx *k, Sx *v)
   1491 {
   1492 	USED(k);
   1493 	msg->size = parsenumber(v);
   1494 }
   1495 
   1496 static char*
   1497 nstring(Sx *v)
   1498 {
   1499 	char *p;
   1500 
   1501 	if(isnil(v))
   1502 		return estrdup("");
   1503 	p = v->data;
   1504 	v->data = nil;
   1505 	return p;
   1506 }
   1507 
   1508 static char*
   1509 copyaddrs(Sx *v)
   1510 {
   1511 	char *s, *sep;
   1512 	char *name, *email, *host, *mbox;
   1513 	int i;
   1514 	Fmt fmt;
   1515 
   1516 	if(v->nsx == 0)
   1517 		return nil;
   1518 
   1519 	fmtstrinit(&fmt);
   1520 	sep = "";
   1521 	for(i=0; i<v->nsx; i++){
   1522 		if(!sxmatch(v->sx[i], "SSSS"))
   1523 			warn("bad address: %$", v->sx[i]);
   1524 		name = unrfc2047(nstring(v->sx[i]->sx[0]));
   1525 		/* ignore sx[1] - route */
   1526 		mbox = unrfc2047(nstring(v->sx[i]->sx[2]));
   1527 		host = unrfc2047(nstring(v->sx[i]->sx[3]));
   1528 		if(mbox == nil || host == nil){	/* rfc822 group syntax */
   1529 			free(name);
   1530 			free(mbox);
   1531 			free(host);
   1532 			continue;
   1533 		}
   1534 		email = esmprint("%s@%s", mbox, host);
   1535 		free(mbox);
   1536 		free(host);
   1537 		fmtprint(&fmt, "%s%q %q", sep, name ? name : "", email ? email : "");
   1538 		free(name);
   1539 		free(email);
   1540 		sep = " ";
   1541 	}
   1542 	s = fmtstrflush(&fmt);
   1543 	if(s == nil)
   1544 		sysfatal("out of memory");
   1545 	return s;
   1546 }
   1547 
   1548 static void
   1549 xmsgenvelope(Msg *msg, Sx *k, Sx *v)
   1550 {
   1551 	USED(k);
   1552 	hdrfree(msg->part[0]->hdr);
   1553 	msg->part[0]->hdr = parseenvelope(v);
   1554 }
   1555 
   1556 static struct {
   1557 	char *name;
   1558 	int offset;
   1559 } paramtab[] = {
   1560 	"charset",	offsetof(Part, charset),
   1561 	"name",		offsetof(Part, filename)
   1562 };
   1563 
   1564 static void
   1565 parseparams(Part *part, Sx *v)
   1566 {
   1567 	int i, j;
   1568 	char *s, *t, **p;
   1569 
   1570 	if(isnil(v))
   1571 		return;
   1572 	if(v->nsx%2){
   1573 		warn("bad message params: %$", v);
   1574 		return;
   1575 	}
   1576 	for(i=0; i<v->nsx; i+=2){
   1577 		s = nstring(v->sx[i]);
   1578 		t = nstring(v->sx[i+1]);
   1579 		for(j=0; j<nelem(paramtab); j++){
   1580 			if(cistrcmp(paramtab[j].name, s) == 0){
   1581 				p = (char**)((char*)part+paramtab[j].offset);
   1582 				free(*p);
   1583 				*p = t;
   1584 				t = nil;
   1585 				break;
   1586 			}
   1587 		}
   1588 		free(s);
   1589 		free(t);
   1590 	}
   1591 }
   1592 
   1593 static void
   1594 parsestructure(Part *part, Sx *v)
   1595 {
   1596 	int i;
   1597 	char *s, *t;
   1598 
   1599 	if(isnil(v))
   1600 		return;
   1601 	if(v->type != SxList){
   1602 	bad:
   1603 		warn("bad structure: %$", v);
   1604 		return;
   1605 	}
   1606 	if(islist(v->sx[0])){
   1607 		/* multipart */
   1608 		for(i=0; i<v->nsx && islist(v->sx[i]); i++)
   1609 			parsestructure(partcreate(part->msg, part), v->sx[i]);
   1610 		free(part->type);
   1611 		if(i != v->nsx-1 || !isstring(v->sx[i])){
   1612 			warn("bad multipart structure: %$", v);
   1613 			part->type = estrdup("multipart/mixed");
   1614 			return;
   1615 		}
   1616 		s = nstring(v->sx[i]);
   1617 		strlwr(s);
   1618 		part->type = esmprint("multipart/%s", s);
   1619 		free(s);
   1620 		return;
   1621 	}
   1622 	/* single part */
   1623 	if(!isstring(v->sx[0]) || v->nsx < 2)
   1624 		goto bad;
   1625 	s = nstring(v->sx[0]);
   1626 	t = nstring(v->sx[1]);
   1627 	strlwr(s);
   1628 	strlwr(t);
   1629 	free(part->type);
   1630 	part->type = esmprint("%s/%s", s, t);
   1631 	if(v->nsx < 7 || !islist(v->sx[2]) || !isstring(v->sx[3])
   1632 	|| !isstring(v->sx[4]) || !isstring(v->sx[5]) || !isnumber(v->sx[6]))
   1633 		goto bad;
   1634 	parseparams(part, v->sx[2]);
   1635 	part->idstr = nstring(v->sx[3]);
   1636 	part->desc = nstring(v->sx[4]);
   1637 	part->encoding = nstring(v->sx[5]);
   1638 	part->size = v->sx[6]->number;
   1639 	if(strcmp(s, "message") == 0 && strcmp(t, "rfc822") == 0){
   1640 		if(v->nsx < 10 || !islist(v->sx[7]) || !islist(v->sx[8]) || !isnumber(v->sx[9]))
   1641 			goto bad;
   1642 		part->hdr = parseenvelope(v->sx[7]);
   1643 		parsestructure(partcreate(part->msg, part), v->sx[8]);
   1644 		part->lines = v->sx[9]->number;
   1645 	}
   1646 	if(strcmp(s, "text") == 0){
   1647 		if(v->nsx < 8 || !isnumber(v->sx[7]))
   1648 			goto bad;
   1649 		part->lines = v->sx[7]->number;
   1650 	}
   1651 }
   1652 
   1653 static void
   1654 xmsgbody(Msg *msg, Sx *k, Sx *v)
   1655 {
   1656 	USED(k);
   1657 	if(v->type != SxList){
   1658 		warn("bad body: %$", v);
   1659 		return;
   1660 	}
   1661 	/*
   1662 	 * To follow the structure exactly we should
   1663 	 * be doing this to partcreate(msg, msg->part[0]),
   1664 	 * and we should leave msg->part[0] with type message/rfc822,
   1665 	 * but the extra layer is redundant - what else would be in a mailbox?
   1666 	 */
   1667 	parsestructure(msg->part[0], v);
   1668 	if(msg->box->maxseen < msg->imapid)
   1669 		msg->box->maxseen = msg->imapid;
   1670 	if(msg->imapuid >= msg->box->uidnext)
   1671 		msg->box->uidnext = msg->imapuid+1;
   1672 }
   1673 
   1674 static void
   1675 xmsgbodydata(Msg *msg, Sx *k, Sx *v)
   1676 {
   1677 	int i;
   1678 	char *name, *p;
   1679 	Part *part;
   1680 
   1681 	name = k->data;
   1682 	name += 5;	/* body[ */
   1683 	p = strchr(name, ']');
   1684 	if(p)
   1685 		*p = 0;
   1686 
   1687 	/* now name is something like 1 or 3.2.MIME - walk down parts from root */
   1688 	part = msg->part[0];
   1689 
   1690 
   1691 	while('1' <= name[0] && name[0] <= '9'){
   1692 		i = strtol(name, &p, 10);
   1693 		if(*p == '.')
   1694 			p++;
   1695 		else if(*p != 0){
   1696 			warn("bad body name: %$", k);
   1697 			return;
   1698 		}
   1699 		if((part = subpart(part, i-1)) == nil){
   1700 			warn("unknown body part: %$", k);
   1701 			return;
   1702 		}
   1703 		name = p;
   1704 	}
   1705 
   1706 
   1707 	if(cistrcmp(name, "") == 0){
   1708 		free(part->raw);
   1709 		part->raw = nstring(v);
   1710 		nocr(part->raw);
   1711 	}else if(cistrcmp(name, "HEADER") == 0){
   1712 		free(part->rawheader);
   1713 		part->rawheader = nstring(v);
   1714 		nocr(part->rawheader);
   1715 	}else if(cistrcmp(name, "MIME") == 0){
   1716 		free(part->mimeheader);
   1717 		part->mimeheader = nstring(v);
   1718 		nocr(part->mimeheader);
   1719 	}else if(cistrcmp(name, "TEXT") == 0){
   1720 		free(part->rawbody);
   1721 		part->rawbody = nstring(v);
   1722 		nocr(part->rawbody);
   1723 	}
   1724 }
   1725 
   1726 /*
   1727  * Table-driven OK info parser.
   1728  */
   1729 static void xokuidvalidity(Imap*, Sx*);
   1730 static void xokpermflags(Imap*, Sx*);
   1731 static void xokunseen(Imap*, Sx*);
   1732 static void xokreadwrite(Imap*, Sx*);
   1733 static void xokreadonly(Imap*, Sx*);
   1734 
   1735 struct {
   1736 	char *name;
   1737 	char fmt;
   1738 	void (*fn)(Imap*, Sx*);
   1739 } oktab[] = {
   1740 	"UIDVALIDITY", 'N',	xokuidvalidity,
   1741 	"PERMANENTFLAGS", 'L',	xokpermflags,
   1742 	"UNSEEN", 'N',	xokunseen,
   1743 	"READ-WRITE", 0,	xokreadwrite,
   1744 	"READ-ONLY",	0, xokreadonly
   1745 };
   1746 
   1747 static void
   1748 xok(Imap *z, Sx *sx)
   1749 {
   1750 	int i;
   1751 	char *name;
   1752 	Sx *arg;
   1753 
   1754 	if(sx->nsx >= 4 && sx->sx[2]->type == SxAtom && sx->sx[2]->data[0] == '['){
   1755 		if(sx->sx[3]->type == SxAtom && sx->sx[3]->data[0] == ']')
   1756 			arg = nil;
   1757 		else if(sx->sx[4]->type == SxAtom && sx->sx[4]->data[0] == ']')
   1758 			arg = sx->sx[3];
   1759 		else{
   1760 			warn("cannot parse OK: %$", sx);
   1761 			return;
   1762 		}
   1763 		name = sx->sx[2]->data+1;
   1764 		for(i=0; i<nelem(oktab); i++){
   1765 			if(cistrcmp(name, oktab[i].name) == 0){
   1766 				if(oktab[i].fmt && (arg==nil || arg->type != fmttype(oktab[i].fmt))){
   1767 					warn("malformed %s: %$", name, arg);
   1768 					continue;
   1769 				}
   1770 				oktab[i].fn(z, arg);
   1771 			}
   1772 		}
   1773 	}
   1774 }
   1775 
   1776 static void
   1777 xokuidvalidity(Imap *z, Sx *sx)
   1778 {
   1779 	int i;
   1780 	Box *b;
   1781 
   1782 	if((b=z->box) == nil)
   1783 		return;
   1784 	if(b->validity != sx->number){
   1785 		b->validity = sx->number;
   1786 		b->uidnext = 1;
   1787 		for(i=0; i<b->nmsg; i++)
   1788 			msgfree(b->msg[i]);
   1789 		free(b->msg);
   1790 		b->msg = nil;
   1791 		b->nmsg = 0;
   1792 	}
   1793 }
   1794 
   1795 static void
   1796 xokpermflags(Imap *z, Sx *sx)
   1797 {
   1798 	USED(z);
   1799 	USED(sx);
   1800 /*	z->permflags = parseflags(sx); */
   1801 }
   1802 
   1803 static void
   1804 xokunseen(Imap *z, Sx *sx)
   1805 {
   1806 	USED(z);
   1807 	USED(sx);
   1808 /*	z->unseen = sx->number; */
   1809 }
   1810 
   1811 static void
   1812 xokreadwrite(Imap *z, Sx *sx)
   1813 {
   1814 	USED(z);
   1815 	USED(sx);
   1816 /*	z->boxmode = ORDWR; */
   1817 }
   1818 
   1819 static void
   1820 xokreadonly(Imap *z, Sx *sx)
   1821 {
   1822 	USED(z);
   1823 	USED(sx);
   1824 /*	z->boxmode = OREAD; */
   1825 }