xref: /plan9/sys/src/cmd/ip/imap4d/imap4d.c (revision 86abb9fb23a9f11dbfd9e6dc2fe0c20d62417d94)
1 #include <u.h>
2 #include <libc.h>
3 #include <auth.h>
4 #include <bio.h>
5 #include "imap4d.h"
6 
7 /*
8  * these should be in libraries
9  */
10 char	*csquery(char *attr, char *val, char *rattr);
11 
12 /*
13  * /lib/rfc/rfc2060 imap4rev1
14  * /lib/rfc/rfc2683 is implementation advice
15  * /lib/rfc/rfc2342 is namespace capability
16  * /lib/rfc/rfc2222 is security protocols
17  * /lib/rfc/rfc1731 is security protocols
18  * /lib/rfc/rfc2221 is LOGIN-REFERRALS
19  * /lib/rfc/rfc2193 is MAILBOX-REFERRALS
20  * /lib/rfc/rfc2177 is IDLE capability
21  * /lib/rfc/rfc2195 is CRAM-MD5 authentication
22  * /lib/rfc/rfc2088 is LITERAL+ capability
23  * /lib/rfc/rfc1760 is S/Key authentication
24  *
25  * outlook uses "Secure Password Authentication" aka ntlm authentication
26  *
27  * capabilities from nslocum
28  * CAPABILITY IMAP4 IMAP4REV1 NAMESPACE IDLE SCAN SORT MAILBOX-REFERRALS LOGIN-REFERRALS AUTH=LOGIN THREAD=ORDEREDSUBJECT
29  */
30 
31 typedef struct	ParseCmd	ParseCmd;
32 
33 enum
34 {
35 	UlongMax	= 4294967295,
36 };
37 
38 struct ParseCmd
39 {
40 	char	*name;
41 	void	(*f)(char *tg, char *cmd);
42 };
43 
44 static	void	appendCmd(char *tg, char *cmd);
45 static	void	authenticateCmd(char *tg, char *cmd);
46 static	void	capabilityCmd(char *tg, char *cmd);
47 static	void	closeCmd(char *tg, char *cmd);
48 static	void	copyCmd(char *tg, char *cmd);
49 static	void	createCmd(char *tg, char *cmd);
50 static	void	deleteCmd(char *tg, char *cmd);
51 static	void	expungeCmd(char *tg, char *cmd);
52 static	void	fetchCmd(char *tg, char *cmd);
53 static	void	idleCmd(char *tg, char *cmd);
54 static	void	listCmd(char *tg, char *cmd);
55 static	void	loginCmd(char *tg, char *cmd);
56 static	void	logoutCmd(char *tg, char *cmd);
57 static	void	namespaceCmd(char *tg, char *cmd);
58 static	void	noopCmd(char *tg, char *cmd);
59 static	void	renameCmd(char *tg, char *cmd);
60 static	void	searchCmd(char *tg, char *cmd);
61 static	void	selectCmd(char *tg, char *cmd);
62 static	void	statusCmd(char *tg, char *cmd);
63 static	void	storeCmd(char *tg, char *cmd);
64 static	void	subscribeCmd(char *tg, char *cmd);
65 static	void	uidCmd(char *tg, char *cmd);
66 static	void	unsubscribeCmd(char *tg, char *cmd);
67 
68 static	void	copyUCmd(char *tg, char *cmd, int uids);
69 static	void	fetchUCmd(char *tg, char *cmd, int uids);
70 static	void	searchUCmd(char *tg, char *cmd, int uids);
71 static	void	storeUCmd(char *tg, char *cmd, int uids);
72 
73 static	void	imap4(int);
74 static	void	status(int expungeable, int uids);
75 static	void	cleaner(void);
76 static	void	check(void);
77 static	int	catcher(void*, char*);
78 
79 static	Search	*searchKey(int first);
80 static	Search	*searchKeys(int first, Search *tail);
81 static	char	*astring(void);
82 static	char	*atomString(char *disallowed, char *initial);
83 static	char	*atom(void);
84 static	void	badsyn(void);
85 static	void	clearcmd(void);
86 static	char	*command(void);
87 static	void	crnl(void);
88 static	Fetch	*fetchAtt(char *s, Fetch *f);
89 static	Fetch	*fetchWhat(void);
90 static	int	flagList(void);
91 static	int	flags(void);
92 static	int	getc(void);
93 static	char	*listmbox(void);
94 static	char	*literal(void);
95 static	ulong	litlen(void);
96 static	MsgSet	*msgSet(int);
97 static	void	mustBe(int c);
98 static	ulong	number(int nonzero);
99 static	int	peekc(void);
100 static	char	*quoted(void);
101 static	void	sectText(Fetch *f, int mimeOk);
102 static	ulong	seqNo(void);
103 static	Store	*storeWhat(void);
104 static	char	*tag(void);
105 static	ulong	uidNo(void);
106 static	void	ungetc(void);
107 
108 static	ParseCmd	SNonAuthed[] =
109 {
110 	{"capability",		capabilityCmd},
111 	{"logout",		logoutCmd},
112 	{"x-exit",		logoutCmd},
113 	{"noop",		noopCmd},
114 
115 	{"login",		loginCmd},
116 	{"authenticate",	authenticateCmd},
117 
118 	nil
119 };
120 
121 static	ParseCmd	SAuthed[] =
122 {
123 	{"capability",		capabilityCmd},
124 	{"logout",		logoutCmd},
125 	{"x-exit",		logoutCmd},
126 	{"noop",		noopCmd},
127 
128 	{"append",		appendCmd},
129 	{"create",		createCmd},
130 	{"delete",		deleteCmd},
131 	{"examine",		selectCmd},
132 	{"select",		selectCmd},
133 	{"idle",		idleCmd},
134 	{"list",		listCmd},
135 	{"lsub",		listCmd},
136 	{"namespace",		namespaceCmd},
137 	{"rename",		renameCmd},
138 	{"status",		statusCmd},
139 	{"subscribe",		subscribeCmd},
140 	{"unsubscribe",		unsubscribeCmd},
141 
142 	nil
143 };
144 
145 static	ParseCmd	SSelected[] =
146 {
147 	{"capability",		capabilityCmd},
148 	{"logout",		logoutCmd},
149 	{"x-exit",		logoutCmd},
150 	{"noop",		noopCmd},
151 
152 	{"append",		appendCmd},
153 	{"create",		createCmd},
154 	{"delete",		deleteCmd},
155 	{"examine",		selectCmd},
156 	{"select",		selectCmd},
157 	{"idle",		idleCmd},
158 	{"list",		listCmd},
159 	{"lsub",		listCmd},
160 	{"namespace",		namespaceCmd},
161 	{"rename",		renameCmd},
162 	{"status",		statusCmd},
163 	{"subscribe",		subscribeCmd},
164 	{"unsubscribe",		unsubscribeCmd},
165 
166 	{"check",		noopCmd},
167 	{"close",		closeCmd},
168 	{"copy",		copyCmd},
169 	{"expunge",		expungeCmd},
170 	{"fetch",		fetchCmd},
171 	{"search",		searchCmd},
172 	{"store",		storeCmd},
173 	{"uid",			uidCmd},
174 
175 	nil
176 };
177 
178 static	char		*atomStop = "(){%*\"\\";
179 static	Chalstate	*chal;
180 static	int		chaled;
181 static	ParseCmd	*imapState;
182 static	jmp_buf		parseJmp;
183 static	char		*parseMsg;
184 static	int		allowPass;
185 static	int		allowCR;
186 static	int		exiting;
187 static	QLock		imaplock;
188 static	int		idlepid = -1;
189 
190 Biobuf	bout;
191 Biobuf	bin;
192 char	username[UserNameLen];
193 char	mboxDir[MboxNameLen];
194 char	*servername;
195 char	*site;
196 char	*remote;
197 Box	*selected;
198 Bin	*parseBin;
199 int	debug;
200 
201 void
202 main(int argc, char *argv[])
203 {
204 	char *s, *t;
205 	int preauth, n;
206 
207 	Binit(&bin, 0, OREAD);
208 	Binit(&bout, 1, OWRITE);
209 
210 	preauth = 0;
211 	allowPass = 0;
212 	allowCR = 0;
213 	ARGBEGIN{
214 	case 'a':
215 		preauth = 1;
216 		break;
217 	case 'd':
218 		site = ARGF();
219 		break;
220 	case 'c':
221 		allowCR = 1;
222 		break;
223 	case 'p':
224 		allowPass = 1;
225 		break;
226 	case 'r':
227 		remote = ARGF();
228 		break;
229 	case 's':
230 		servername = ARGF();
231 		break;
232 	case 'v':
233 		debug = 1;
234 		debuglog("imap4d debugging enabled\n");
235 		break;
236 	default:
237 		fprint(2, "usage: ip/imap4d [-acpv] [-d site] [-r remotehost] [-s servername]\n");
238 		bye("usage");
239 		break;
240 	}ARGEND
241 
242 	if(allowPass && allowCR){
243 		fprint(2, "%s: -c and -p are mutually exclusive\n", argv0);
244 		bye("usage");
245 	}
246 
247 	if(preauth)
248 		setupuser(nil);
249 
250 	if(servername == nil){
251 		servername = csquery("sys", sysname(), "dom");
252 		if(servername == nil)
253 			servername = sysname();
254 		if(servername == nil){
255 			fprint(2, "ip/imap4d can't find server name: %r\n");
256 			bye("can't find system name");
257 		}
258 	}
259 	if(site == nil){
260 		t = getenv("site");
261 		if(t == nil)
262 			site = servername;
263 		else{
264 			n = strlen(t);
265 			s = strchr(servername, '.');
266 			if(s == nil)
267 				s = servername;
268 			else
269 				s++;
270 			n += strlen(s) + 2;
271 			site = emalloc(n);
272 			snprint(site, n, "%s.%s", t, s);
273 		}
274 	}
275 
276 	rfork(RFNOTEG|RFREND);
277 
278 	atnotify(catcher, 1);
279 	qlock(&imaplock);
280 	atexit(cleaner);
281 	imap4(preauth);
282 }
283 
284 static void
285 imap4(int preauth)
286 {
287 	char *volatile tg;
288 	char *volatile cmd;
289 	ParseCmd *st;
290 
291 	if(preauth){
292 		Bprint(&bout, "* preauth %s IMAP4rev1 server ready user %s authenticated\r\n", servername, username);
293 		imapState = SAuthed;
294 	}else{
295 		Bprint(&bout, "* OK %s IMAP4rev1 server ready\r\n", servername);
296 		imapState = SNonAuthed;
297 	}
298 	if(Bflush(&bout) < 0)
299 		writeErr();
300 
301 	chaled = 0;
302 
303 	tg = nil;
304 	cmd = nil;
305 	if(setjmp(parseJmp)){
306 		if(tg == nil)
307 			Bprint(&bout, "* bad empty command line: %s\r\n", parseMsg);
308 		else if(cmd == nil)
309 			Bprint(&bout, "%s BAD no command: %s\r\n", tg, parseMsg);
310 		else
311 			Bprint(&bout, "%s BAD %s %s\r\n", tg, cmd, parseMsg);
312 		clearcmd();
313 		if(Bflush(&bout) < 0)
314 			writeErr();
315 		binfree(&parseBin);
316 	}
317 	for(;;){
318 		if(mbLocked())
319 			bye("internal error: mailbox lock held");
320 		tg = nil;
321 		cmd = nil;
322 		tg = tag();
323 		mustBe(' ');
324 		cmd = atom();
325 
326 		/*
327 		 * note: outlook express is broken: it requires echoing the
328 		 * command as part of matching response
329 		 */
330 		for(st = imapState; st->name != nil; st++){
331 			if(cistrcmp(cmd, st->name) == 0){
332 				(*st->f)(tg, cmd);
333 				break;
334 			}
335 		}
336 		if(st->name == nil){
337 			clearcmd();
338 			Bprint(&bout, "%s BAD %s illegal command\r\n", tg, cmd);
339 		}
340 
341 		if(Bflush(&bout) < 0)
342 			writeErr();
343 		binfree(&parseBin);
344 	}
345 }
346 
347 void
348 bye(char *fmt, ...)
349 {
350 	va_list arg;
351 
352 	va_start(arg, fmt);
353 	Bprint(&bout, "* bye ");
354 	Bvprint(&bout, fmt, arg);
355 	Bprint(&bout, "\r\n");
356 	Bflush(&bout);
357 exits("rob2");
358 	exits(0);
359 }
360 
361 void
362 parseErr(char *msg)
363 {
364 	parseMsg = msg;
365 	longjmp(parseJmp, 1);
366 }
367 
368 /*
369  * an error occured while writing to the client
370  */
371 void
372 writeErr(void)
373 {
374 	cleaner();
375 	_exits("connection closed");
376 }
377 
378 static int
379 catcher(void *v, char *msg)
380 {
381 	USED(v);
382 	if(strstr(msg, "closed pipe") != nil)
383 		return 1;
384 	return 0;
385 }
386 
387 /*
388  * wipes out the idleCmd backgroung process if it is around.
389  * this can only be called if the current proc has qlocked imaplock.
390  * it must be the last piece of imap4d code executed.
391  */
392 static void
393 cleaner(void)
394 {
395 	int i;
396 
397 	if(idlepid < 0)
398 		return;
399 	exiting = 1;
400 	close(0);
401 	close(1);
402 	close(2);
403 
404 	/*
405 	 * the other proc is either stuck in a read, a sleep,
406 	 * or is trying to lock imap4lock.
407 	 * get him out of it so he can exit cleanly
408 	 */
409 	qunlock(&imaplock);
410 	for(i = 0; i < 4; i++)
411 		postnote(PNGROUP, getpid(), "die");
412 }
413 
414 /*
415  * send any pending status updates to the client
416  * careful: shouldn't exit, because called by idle polling proc
417  *
418  * can't always send pending info
419  * in particular, can't send expunge info
420  * in response to a fetch, store, or search command.
421  *
422  * rfc2060 5.2:	server must send mailbox size updates
423  * rfc2060 5.2:	server may send flag updates
424  * rfc2060 5.5:	servers prohibited from sending expunge while fetch, store, search in progress
425  * rfc2060 7:	in selected state, server checks mailbox for new messages as part of every command
426  * 		sends untagged EXISTS and RECENT respsonses reflecting new size of the mailbox
427  * 		should also send appropriate untagged FETCH and EXPUNGE messages if another agent
428  * 		changes the state of any message flags or expunges any messages
429  * rfc2060 7.4.1	expunge server response must not be sent when no command is in progress,
430  * 		nor while responding to a fetch, stort, or search command (uid versions are ok)
431  * 		command only "in progress" after entirely parsed.
432  *
433  * strategy for third party deletion of messages or of a mailbox
434  *
435  * deletion of a selected mailbox => act like all message are expunged
436  *	not strictly allowed by rfc2180, but close to method 3.2.
437  *
438  * renaming same as deletion
439  *
440  * copy
441  *	reject iff a deleted message is in the request
442  *
443  * search, store, fetch operations on expunged messages
444  *	ignore the expunged messages
445  *	return tagged no if referenced
446  */
447 static void
448 status(int expungeable, int uids)
449 {
450 	int tell;
451 
452 	if(!selected)
453 		return;
454 	tell = 0;
455 	if(expungeable)
456 		tell = expungeMsgs(selected, 1);
457 	if(selected->sendFlags)
458 		sendFlags(selected, uids);
459 	if(tell || selected->toldMax != selected->max){
460 		Bprint(&bout, "* %lud EXISTS\r\n", selected->max);
461 		selected->toldMax = selected->max;
462 	}
463 	if(tell || selected->toldRecent != selected->recent){
464 		Bprint(&bout, "* %lud RECENT\r\n", selected->recent);
465 		selected->toldRecent = selected->recent;
466 	}
467 	if(tell)
468 		closeImp(selected, checkBox(selected, 1));
469 }
470 
471 /*
472  * careful: can't exit, because called by idle polling proc
473  */
474 static void
475 check(void)
476 {
477 	if(!selected)
478 		return;
479 	checkBox(selected, 0);
480 	status(1, 0);
481 }
482 
483 static void
484 appendCmd(char *tg, char *cmd)
485 {
486 	char *mbox, head[128];
487 	ulong t, n, now;
488 	int flags, ok;
489 
490 	mustBe(' ');
491 	mbox = astring();
492 	mustBe(' ');
493 	flags = 0;
494 	if(peekc() == '('){
495 		flags = flagList();
496 		mustBe(' ');
497 	}
498 	now = time(nil);
499 	if(peekc() == '"'){
500 		t = imap4DateTime(quoted());
501 		if(t == ~0)
502 			parseErr("illegal date format");
503 		mustBe(' ');
504 		if(t > now)
505 			t = now;
506 	}else
507 		t = now;
508 	n = litlen();
509 
510 	mbox = mboxName(mbox);
511 	if(mbox == nil || !okMbox(mbox)){
512 		check();
513 		Bprint(&bout, "%s NO %s bad mailbox\r\n", tg, cmd);
514 		return;
515 	}
516 	if(!cdExists(mboxDir, mbox)){
517 		check();
518 		Bprint(&bout, "%s NO [TRYCREATE] %s mailbox does not exist\r\n", tg, cmd);
519 		return;
520 	}
521 
522 	snprint(head, sizeof(head), "From %s %s", username, ctime(t));
523 	ok = appendSave(mbox, flags, head, &bin, n);
524 	crnl();
525 	check();
526 	if(ok)
527 		Bprint(&bout, "%s OK %s completed\r\n", tg, cmd);
528 	else
529 		Bprint(&bout, "%s NO %s message save failed\r\n", tg, cmd);
530 }
531 
532 static void
533 authenticateCmd(char *tg, char *cmd)
534 {
535 	char *s, *t;
536 
537 	mustBe(' ');
538 	s = atom();
539 	crnl();
540 	auth_freechal(chal);
541 	chal = nil;
542 	if(cistrcmp(s, "cram-md5") == 0){
543 		t = cramauth();
544 		if(t == nil){
545 			Bprint(&bout, "%s OK %s\r\n", tg, cmd);
546 			imapState = SAuthed;
547 		}else
548 			Bprint(&bout, "%s NO %s failed %s\r\n", tg, cmd, t);
549 	}else
550 		Bprint(&bout, "%s NO %s unsupported authentication protocol\r\n", tg, cmd);
551 }
552 
553 static void
554 capabilityCmd(char *tg, char *cmd)
555 {
556 	crnl();
557 	check();
558 // nslocum's capabilities
559 //	Bprint(&bout, "* CAPABILITY IMAP4 IMAP4REV1 NAMESPACE IDLE SCAN SORT MAILBOX-REFERRALS LOGIN-REFERRALS AUTH=LOGIN THREAD=ORDEREDSUBJECT\r\n");
560 	Bprint(&bout, "* CAPABILITY IMAP4REV1 IDLE NAMESPACE AUTH=CRAM-MD5\r\n");
561 	Bprint(&bout, "%s OK %s\r\n", tg, cmd);
562 }
563 
564 static void
565 closeCmd(char *tg, char *cmd)
566 {
567 	crnl();
568 	imapState = SAuthed;
569 	closeBox(selected, 1);
570 	selected = nil;
571 	Bprint(&bout, "%s OK %s mailbox closed, now in authenticated state\r\n", tg, cmd);
572 }
573 
574 /*
575  * note: message id's are before any pending expunges
576  */
577 static void
578 copyCmd(char *tg, char *cmd)
579 {
580 	copyUCmd(tg, cmd, 0);
581 }
582 
583 static void
584 copyUCmd(char *tg, char *cmd, int uids)
585 {
586 	MsgSet *ms;
587 	char *uid, *mbox;
588 	ulong max;
589 	int ok;
590 
591 	mustBe(' ');
592 	ms = msgSet(uids);
593 	mustBe(' ');
594 	mbox = astring();
595 	crnl();
596 
597 	uid = "";
598 	if(uids)
599 		uid = "uid ";
600 
601 	mbox = mboxName(mbox);
602 	if(mbox == nil || !okMbox(mbox)){
603 		status(1, uids);
604 		Bprint(&bout, "%s NO %s%s bad mailbox\r\n", tg, uid, cmd);
605 		return;
606 	}
607 	if(cistrcmp(mbox, "inbox") == 0)
608 		mbox = "mbox";
609 	if(!cdExists(mboxDir, mbox)){
610 		check();
611 		Bprint(&bout, "%s NO [TRYCREATE] %s mailbox does not exist\r\n", tg, cmd);
612 		return;
613 	}
614 
615 	max = selected->max;
616 	checkBox(selected, 0);
617 	ok = forMsgs(selected, ms, max, uids, copyCheck, nil);
618 	if(ok)
619 		ok = forMsgs(selected, ms, max, uids, copySave, mbox);
620 
621 	status(1, uids);
622 	if(ok)
623 		Bprint(&bout, "%s OK %s%s completed\r\n", tg, uid, cmd);
624 	else
625 		Bprint(&bout, "%s NO %s%s failed\r\n", tg, uid, cmd);
626 }
627 
628 static void
629 createCmd(char *tg, char *cmd)
630 {
631 	char *mbox, *m;
632 	int fd, slash;
633 
634 	mustBe(' ');
635 	mbox = astring();
636 	crnl();
637 	check();
638 
639 	m = strchr(mbox, '\0');
640 	slash = m != mbox && m[-1] == '/';
641 	mbox = mboxName(mbox);
642 	if(mbox == nil || !okMbox(mbox)){
643 		Bprint(&bout, "%s NO %s bad mailbox\r\n", tg, cmd);
644 		return;
645 	}
646 	if(cistrcmp(mbox, "inbox") == 0){
647 		Bprint(&bout, "%s NO %s cannot remotely create INBOX\r\n", tg, cmd);
648 		return;
649 	}
650 	if(access(mbox, AEXIST) >= 0){
651 		Bprint(&bout, "%s NO %s mailbox already exists\r\n", tg, cmd);
652 		return;
653 	}
654 
655 	fd = createBox(mbox, slash);
656 	close(fd);
657 	if(fd < 0)
658 		Bprint(&bout, "%s NO %s cannot create mailbox %s\r\n", tg, cmd, mbox);
659 	else
660 		Bprint(&bout, "%s OK %s %s completed\r\n", tg, mbox, cmd);
661 }
662 
663 static void
664 deleteCmd(char *tg, char *cmd)
665 {
666 	char *mbox, *imp;
667 
668 	mustBe(' ');
669 	mbox = astring();
670 	crnl();
671 	check();
672 
673 	mbox = mboxName(mbox);
674 	if(mbox == nil || !okMbox(mbox)){
675 		Bprint(&bout, "%s NO %s bad mailbox\r\n", tg, cmd);
676 		return;
677 	}
678 
679 	imp = impName(mbox);
680 	if(cistrcmp(mbox, "inbox") == 0
681 	|| imp != nil && cdRemove(mboxDir, imp) < 0 && cdExists(mboxDir, imp)
682 	|| cdRemove(mboxDir, mbox) < 0)
683 		Bprint(&bout, "%s NO %s cannot delete mailbox %s\r\n", tg, cmd, mbox);
684 	else
685 		Bprint(&bout, "%s OK %s %s completed\r\n", tg, mbox, cmd);
686 }
687 
688 static void
689 expungeCmd(char *tg, char *cmd)
690 {
691 	int ok;
692 
693 	crnl();
694 	ok = deleteMsgs(selected);
695 	check();
696 	if(ok)
697 		Bprint(&bout, "%s OK %s messages erased\r\n", tg, cmd);
698 	else
699 		Bprint(&bout, "%s NO %s some messages not expunged\r\n", tg, cmd);
700 }
701 
702 static void
703 fetchCmd(char *tg, char *cmd)
704 {
705 	fetchUCmd(tg, cmd, 0);
706 }
707 
708 static void
709 fetchUCmd(char *tg, char *cmd, int uids)
710 {
711 	Fetch *f;
712 	MsgSet *ms;
713 	MbLock *ml;
714 	char *uid;
715 	ulong max;
716 	int ok;
717 
718 	mustBe(' ');
719 	ms = msgSet(uids);
720 	mustBe(' ');
721 	f = fetchWhat();
722 	crnl();
723 	uid = "";
724 	if(uids)
725 		uid = "uid ";
726 	max = selected->max;
727 	ml = checkBox(selected, 1);
728 	if(ml != nil)
729 		forMsgs(selected, ms, max, uids, fetchSeen, f);
730 	closeImp(selected, ml);
731 	ok = ml != nil && forMsgs(selected, ms, max, uids, fetchMsg, f);
732 	status(uids, uids);
733 	if(ok)
734 		Bprint(&bout, "%s OK %s%s completed\r\n", tg, uid, cmd);
735 	else
736 		Bprint(&bout, "%s NO %s%s failed\r\n", tg, uid, cmd);
737 }
738 
739 static void
740 idleCmd(char *tg, char *cmd)
741 {
742 	int c, pid;
743 
744 	crnl();
745 	Bprint(&bout, "+ idling, waiting for done\r\n");
746 	if(Bflush(&bout) < 0)
747 		writeErr();
748 
749 	if(idlepid < 0){
750 		pid = rfork(RFPROC|RFMEM|RFNOWAIT);
751 		if(pid == 0){
752 			for(;;){
753 				qlock(&imaplock);
754 				if(exiting)
755 					break;
756 
757 				/*
758 				 * parent may have changed curDir, but it doesn't change our .
759 				 */
760 				resetCurDir();
761 
762 				check();
763 				if(Bflush(&bout) < 0)
764 					writeErr();
765 				qunlock(&imaplock);
766 				sleep(15*1000);
767 				enableForwarding();
768 			}
769 _exits("rob3");
770 			_exits(0);
771 		}
772 		idlepid = pid;
773 	}
774 
775 	qunlock(&imaplock);
776 
777 	/*
778 	 * clear out the next line, which is supposed to contain (case-insensitive)
779 	 * done\n
780 	 * this is special code since it has to dance with the idle polling proc
781 	 * and handle exiting correctly.
782 	 */
783 	for(;;){
784 		c = getc();
785 		if(c < 0){
786 			qlock(&imaplock);
787 			if(!exiting)
788 				cleaner();
789 _exits("rob4");
790 			_exits(0);
791 		}
792 		if(c == '\n')
793 			break;
794 	}
795 
796 	qlock(&imaplock);
797 	if(exiting)
798 {_exits("rob5");
799 		_exits(0);
800 }
801 
802 	/*
803 	 * child may have changed curDir, but it doesn't change our .
804 	 */
805 	resetCurDir();
806 
807 	check();
808 	Bprint(&bout, "%s OK %s terminated\r\n", tg, cmd);
809 }
810 
811 static void
812 listCmd(char *tg, char *cmd)
813 {
814 	char *s, *t, *ss, *ref, *mbox;
815 	int n;
816 
817 	mustBe(' ');
818 	s = astring();
819 	mustBe(' ');
820 	t = listmbox();
821 	crnl();
822 	check();
823 	ref = mutf7str(s);
824 	mbox = mutf7str(t);
825 	if(ref == nil || mbox == nil){
826 		Bprint(&bout, "%s BAD %s mailbox name not in modified utf-7\r\n", tg, cmd);
827 		return;
828 	}
829 
830 	/*
831 	 * special request for hierarchy delimiter and root name
832 	 * root name appears to be name up to and including any delimiter,
833 	 * or the empty string, if there is no delimiter.
834 	 *
835 	 * this must change if the # namespace convention is supported.
836 	 */
837 	if(*mbox == '\0'){
838 		s = strchr(ref, '/');
839 		if(s == nil)
840 			ref = "";
841 		else
842 			s[1] = '\0';
843 		Bprint(&bout, "* %s (\\Noselect) \"/\" \"%s\"\r\n", cmd, ref);
844 		Bprint(&bout, "%s OK %s\r\n", tg, cmd);
845 		return;
846 	}
847 
848 
849 	/*
850 	 * massage the listing name:
851 	 * clean up the components individually,
852 	 * then rip off componenets from the ref to
853 	 * take care of leading ..'s in the mbox.
854 	 *
855 	 * the cleanup can wipe out * followed by a ..
856 	 * tough luck if such a stupid pattern is given.
857 	 */
858 	cleanname(mbox);
859 	if(strcmp(mbox, ".") == 0)
860 		*mbox = '\0';
861 	if(mbox[0] == '/')
862 		*ref = '\0';
863 	else if(*ref != '\0'){
864 		cleanname(ref);
865 		if(strcmp(ref, ".") == 0)
866 			*ref = '\0';
867 	}else
868 		*ref = '\0';
869 	while(*ref && isdotdot(mbox)){
870 		s = strrchr(ref, '/');
871 		if(s == nil)
872 			s = ref;
873 		if(isdotdot(s))
874 			break;
875 		*s = '\0';
876 		mbox += 2;
877 		if(*mbox == '/')
878 			mbox++;
879 	}
880 	if(*ref == '\0'){
881 		s = mbox;
882 		ss = s;
883 	}else{
884 		n = strlen(ref) + strlen(mbox) + 2;
885 		t = binalloc(&parseBin, n, 0);
886 		if(t == nil)
887 			parseErr("out of memory");
888 		snprint(t, n, "%s/%s", ref, mbox);
889 		s = t;
890 		ss = s + strlen(ref);
891 	}
892 
893 	/*
894 	 * only allow activity in /mail/box
895 	 */
896 	if(s[0] == '/' || isdotdot(s)){
897 		Bprint(&bout, "%s NO illegal mailbox pattern\r\n", tg);
898 		return;
899 	}
900 
901 	if(cistrcmp(cmd, "lsub") == 0)
902 		lsubBoxes(cmd, s, ss);
903 	else
904 		listBoxes(cmd, s, ss);
905 	Bprint(&bout, "%s OK %s completed\r\n", tg, cmd);
906 }
907 
908 static char*
909 passCR(char*u, char*p)
910 {
911 	static char Ebadch[] = "can't get challenge";
912 	static char nchall[64];
913 	static char response[64];
914 	static Chalstate *ch = nil;
915 	AuthInfo *ai;
916 
917 again:
918 	if (ch == nil){
919 		if(!(ch = auth_challenge("proto=p9cr role=server user=%q", u)))
920 			return Ebadch;
921 		snprint(nchall, 64, " encrypt challenge: %s", ch->chal);
922 		return nchall;
923 	} else {
924 		strncpy(response, p, 64);
925 		ch->resp = response;
926 		ch->nresp = strlen(response);
927 		ai = auth_response(ch);
928 		auth_freechal(ch);
929 		ch = nil;
930 		if (ai == nil)
931 			goto again;
932 		setupuser(ai);
933 		return nil;
934 	}
935 
936 }
937 
938 static void
939 loginCmd(char *tg, char *cmd)
940 {
941 	char *s, *t;
942 	AuthInfo *ai;
943 	char*r;
944 	mustBe(' ');
945 	s = astring();	/* uid */
946 	mustBe(' ');
947 	t = astring();	/* password */
948 	crnl();
949 	if(allowCR){
950 		if ((r = passCR(s, t)) == nil){
951 			Bprint(&bout, "%s OK %s succeeded\r\n", tg, cmd);
952 			imapState = SAuthed;
953 		} else {
954 			Bprint(&bout, "* NO [ALERT] %s\r\n", r);
955 			Bprint(&bout, "%s NO %s succeeded\r\n", tg, cmd);
956 		}
957 		return;
958 	}
959 	else if(allowPass){
960 		if(ai = passLogin(s, t)){
961 			setupuser(ai);
962 			Bprint(&bout, "%s OK %s succeeded\r\n", tg, cmd);
963 			imapState = SAuthed;
964 		}else
965 			Bprint(&bout, "%s NO %s failed check\r\n", tg, cmd);
966 		return;
967 	}
968 	Bprint(&bout, "%s NO %s plaintext passwords disallowed\r\n", tg, cmd);
969 }
970 
971 /*
972  * logout or x-exit, which doesn't expunge the mailbox
973  */
974 static void
975 logoutCmd(char *tg, char *cmd)
976 {
977 	crnl();
978 
979 	if(cmd[0] != 'x' && selected){
980 		closeBox(selected, 1);
981 		selected = nil;
982 	}
983 	Bprint(&bout, "* bye\r\n");
984 	Bprint(&bout, "%s OK %s completed\r\n", tg, cmd);
985 exits("rob6");
986 	exits(0);
987 }
988 
989 static void
990 namespaceCmd(char *tg, char *cmd)
991 {
992 	crnl();
993 	check();
994 
995 	/*
996 	 * personal, other users, shared namespaces
997 	 * send back nil or descriptions of (prefix heirarchy-delim) for each case
998 	 */
999 	Bprint(&bout, "* NAMESPACE ((\"\" \"/\")) nil nil\r\n");
1000 	Bprint(&bout, "%s OK %s completed\r\n", tg, cmd);
1001 }
1002 
1003 static void
1004 noopCmd(char *tg, char *cmd)
1005 {
1006 	crnl();
1007 	check();
1008 	Bprint(&bout, "%s OK %s completed\r\n", tg, cmd);
1009 	enableForwarding();
1010 }
1011 
1012 /*
1013  * this is only a partial implementation
1014  * should copy files to other directories,
1015  * and copy & truncate inbox
1016  */
1017 static void
1018 renameCmd(char *tg, char *cmd)
1019 {
1020 	char *from, *to;
1021 	int ok;
1022 
1023 	mustBe(' ');
1024 	from = astring();
1025 	mustBe(' ');
1026 	to = astring();
1027 	crnl();
1028 	check();
1029 
1030 	to = mboxName(to);
1031 	if(to == nil || !okMbox(to) || cistrcmp(to, "inbox") == 0){
1032 		Bprint(&bout, "%s NO %s bad mailbox destination name\r\n", tg, cmd);
1033 		return;
1034 	}
1035 	if(access(to, AEXIST) >= 0){
1036 		Bprint(&bout, "%s NO %s mailbox already exists\r\n", tg, cmd);
1037 		return;
1038 	}
1039 	from = mboxName(from);
1040 	if(from == nil || !okMbox(from)){
1041 		Bprint(&bout, "%s NO %s bad mailbox destination name\r\n", tg, cmd);
1042 		return;
1043 	}
1044 	if(cistrcmp(from, "inbox") == 0)
1045 		ok = copyBox(from, to, 0);
1046 	else
1047 		ok = moveBox(from, to);
1048 
1049 	if(ok)
1050 		Bprint(&bout, "%s OK %s completed\r\n", tg, cmd);
1051 	else
1052 		Bprint(&bout, "%s NO %s failed\r\n", tg, cmd);
1053 }
1054 
1055 static void
1056 searchCmd(char *tg, char *cmd)
1057 {
1058 	searchUCmd(tg, cmd, 0);
1059 }
1060 
1061 static void
1062 searchUCmd(char *tg, char *cmd, int uids)
1063 {
1064 	Search rock;
1065 	Msg *m;
1066 	char *uid;
1067 	ulong id;
1068 
1069 	mustBe(' ');
1070 	rock.next = nil;
1071 	searchKeys(1, &rock);
1072 	crnl();
1073 	uid = "";
1074 	if(uids)
1075 		uid = "uid ";
1076 	if(rock.next != nil && rock.next->key == SKCharset){
1077 		if(cistrstr(rock.next->s, "utf-8") != 0
1078 		&& cistrcmp(rock.next->s, "us-ascii") != 0){
1079 			Bprint(&bout, "%s NO [BADCHARSET] (\"US-ASCII\" \"UTF-8\") %s%s failed\r\n", tg, uid, cmd);
1080 			checkBox(selected, 0);
1081 			status(uids, uids);
1082 			return;
1083 		}
1084 		rock.next = rock.next->next;
1085 	}
1086 	Bprint(&bout, "* search");
1087 	for(m = selected->msgs; m != nil; m = m->next)
1088 		m->matched = searchMsg(m, rock.next);
1089 	for(m = selected->msgs; m != nil; m = m->next){
1090 		if(m->matched){
1091 			if(uids)
1092 				id = m->uid;
1093 			else
1094 				id = m->seq;
1095 			Bprint(&bout, " %lud", id);
1096 		}
1097 	}
1098 	Bprint(&bout, "\r\n");
1099 	checkBox(selected, 0);
1100 	status(uids, uids);
1101 	Bprint(&bout, "%s OK %s%s completed\r\n", tg, uid, cmd);
1102 }
1103 
1104 static void
1105 selectCmd(char *tg, char *cmd)
1106 {
1107 	Msg *m;
1108 	char *s, *mbox;
1109 
1110 	mustBe(' ');
1111 	mbox = astring();
1112 	crnl();
1113 
1114 	if(selected){
1115 		imapState = SAuthed;
1116 		closeBox(selected, 1);
1117 		selected = nil;
1118 	}
1119 
1120 	mbox = mboxName(mbox);
1121 	if(mbox == nil || !okMbox(mbox)){
1122 		Bprint(&bout, "%s NO %s bad mailbox\r\n", tg, cmd);
1123 		return;
1124 	}
1125 
1126 	selected = openBox(mbox, "imap", cistrcmp(cmd, "select") == 0);
1127 	if(selected == nil){
1128 		Bprint(&bout, "%s NO %s can't open mailbox %s: %r\r\n", tg, cmd, mbox);
1129 		return;
1130 	}
1131 
1132 	imapState = SSelected;
1133 
1134 	Bprint(&bout, "* FLAGS (\\Seen \\Answered \\Flagged \\Deleted \\Draft)\r\n");
1135 	Bprint(&bout, "* %lud EXISTS\r\n", selected->max);
1136 	selected->toldMax = selected->max;
1137 	Bprint(&bout, "* %lud RECENT\r\n", selected->recent);
1138 	selected->toldRecent = selected->recent;
1139 	for(m = selected->msgs; m != nil; m = m->next){
1140 		if(!m->expunged && (m->flags & MSeen) != MSeen){
1141 			Bprint(&bout, "* OK [UNSEEN %ld]\r\n", m->seq);
1142 			break;
1143 		}
1144 	}
1145 	Bprint(&bout, "* OK [PERMANENTFLAGS (\\Seen \\Answered \\Flagged \\Draft \\Deleted)]\r\n");
1146 	Bprint(&bout, "* OK [UIDNEXT %ld]\r\n", selected->uidnext);
1147 	Bprint(&bout, "* OK [UIDVALIDITY %ld]\r\n", selected->uidvalidity);
1148 	s = "READ-ONLY";
1149 	if(selected->writable)
1150 		s = "READ-WRITE";
1151 	Bprint(&bout, "%s OK [%s] %s %s completed\r\n", tg, s, cmd, mbox);
1152 }
1153 
1154 static NamedInt	statusItems[] =
1155 {
1156 	{"MESSAGES",	SMessages},
1157 	{"RECENT",	SRecent},
1158 	{"UIDNEXT",	SUidNext},
1159 	{"UIDVALIDITY",	SUidValidity},
1160 	{"UNSEEN",	SUnseen},
1161 	{nil,		0}
1162 };
1163 
1164 static void
1165 statusCmd(char *tg, char *cmd)
1166 {
1167 	Box *box;
1168 	Msg *m;
1169 	char *s, *mbox;
1170 	ulong v;
1171 	int si, i;
1172 
1173 	mustBe(' ');
1174 	mbox = astring();
1175 	mustBe(' ');
1176 	mustBe('(');
1177 	si = 0;
1178 	for(;;){
1179 		s = atom();
1180 		i = mapInt(statusItems, s);
1181 		if(i == 0)
1182 			parseErr("illegal status item");
1183 		si |= i;
1184 		if(peekc() == ')')
1185 			break;
1186 		mustBe(' ');
1187 	}
1188 	mustBe(')');
1189 	crnl();
1190 
1191 	mbox = mboxName(mbox);
1192 	if(mbox == nil || !okMbox(mbox)){
1193 		check();
1194 		Bprint(&bout, "%s NO %s bad mailbox\r\n", tg, cmd);
1195 		return;
1196 	}
1197 
1198 	box = openBox(mbox, "status", 1);
1199 	if(box == nil){
1200 		check();
1201 		Bprint(&bout, "%s NO [TRYCREATE] %s can't open mailbox %s: %r\r\n", tg, cmd, mbox);
1202 		return;
1203 	}
1204 
1205 	Bprint(&bout, "* STATUS %s (", mbox);
1206 	s = "";
1207 	for(i = 0; statusItems[i].name != nil; i++){
1208 		if(si & statusItems[i].v){
1209 			v = 0;
1210 			switch(statusItems[i].v){
1211 			case SMessages:
1212 				v = box->max;
1213 				break;
1214 			case SRecent:
1215 				v = box->recent;
1216 				break;
1217 			case SUidNext:
1218 				v = box->uidnext;
1219 				break;
1220 			case SUidValidity:
1221 				v = box->uidvalidity;
1222 				break;
1223 			case SUnseen:
1224 				v = 0;
1225 				for(m = box->msgs; m != nil; m = m->next)
1226 					if((m->flags & MSeen) != MSeen)
1227 						v++;
1228 				break;
1229 			default:
1230 				Bprint(&bout, ")");
1231 				bye("internal error: status item not implemented");
1232 				break;
1233 			}
1234 			Bprint(&bout, "%s%s %lud", s, statusItems[i].name, v);
1235 			s = " ";
1236 		}
1237 	}
1238 	Bprint(&bout, ")\r\n");
1239 	closeBox(box, 1);
1240 
1241 	check();
1242 	Bprint(&bout, "%s OK %s completed\r\n", tg, cmd);
1243 }
1244 
1245 static void
1246 storeCmd(char *tg, char *cmd)
1247 {
1248 	storeUCmd(tg, cmd, 0);
1249 }
1250 
1251 static void
1252 storeUCmd(char *tg, char *cmd, int uids)
1253 {
1254 	Store *st;
1255 	MsgSet *ms;
1256 	MbLock *ml;
1257 	char *uid;
1258 	ulong max;
1259 	int ok;
1260 
1261 	mustBe(' ');
1262 	ms = msgSet(uids);
1263 	mustBe(' ');
1264 	st = storeWhat();
1265 	crnl();
1266 	uid = "";
1267 	if(uids)
1268 		uid = "uid ";
1269 	max = selected->max;
1270 	ml = checkBox(selected, 1);
1271 	ok = ml != nil && forMsgs(selected, ms, max, uids, storeMsg, st);
1272 	closeImp(selected, ml);
1273 	status(uids, uids);
1274 	if(ok)
1275 		Bprint(&bout, "%s OK %s%s completed\r\n", tg, uid, cmd);
1276 	else
1277 		Bprint(&bout, "%s NO %s%s failed\r\n", tg, uid, cmd);
1278 }
1279 
1280 /*
1281  * minimal implementation of subscribe
1282  * all folders are automatically subscribed,
1283  * and can't be unsubscribed
1284  */
1285 static void
1286 subscribeCmd(char *tg, char *cmd)
1287 {
1288 	Box *box;
1289 	char *mbox;
1290 	int ok;
1291 
1292 	mustBe(' ');
1293 	mbox = astring();
1294 	crnl();
1295 	check();
1296 	mbox = mboxName(mbox);
1297 	ok = 0;
1298 	if(mbox != nil && okMbox(mbox)){
1299 		box = openBox(mbox, "subscribe", 0);
1300 		if(box != nil){
1301 			ok = subscribe(mbox, 's');
1302 			closeBox(box, 1);
1303 		}
1304 	}
1305 	if(!ok)
1306 		Bprint(&bout, "%s NO %s bad mailbox\r\n", tg, cmd);
1307 	else
1308 		Bprint(&bout, "%s OK %s completed\r\n", tg, cmd);
1309 }
1310 
1311 static void
1312 uidCmd(char *tg, char *cmd)
1313 {
1314 	char *sub;
1315 
1316 	mustBe(' ');
1317 	sub = atom();
1318 	if(cistrcmp(sub, "copy") == 0)
1319 		copyUCmd(tg, sub, 1);
1320 	else if(cistrcmp(sub, "fetch") == 0)
1321 		fetchUCmd(tg, sub, 1);
1322 	else if(cistrcmp(sub, "search") == 0)
1323 		searchUCmd(tg, sub, 1);
1324 	else if(cistrcmp(sub, "store") == 0)
1325 		storeUCmd(tg, sub, 1);
1326 	else{
1327 		clearcmd();
1328 		Bprint(&bout, "%s BAD %s illegal uid command %s\r\n", tg, cmd, sub);
1329 	}
1330 }
1331 
1332 static void
1333 unsubscribeCmd(char *tg, char *cmd)
1334 {
1335 	char *mbox;
1336 
1337 	mustBe(' ');
1338 	mbox = astring();
1339 	crnl();
1340 	check();
1341 	mbox = mboxName(mbox);
1342 	if(mbox == nil || !okMbox(mbox) || !subscribe(mbox, 'u'))
1343 		Bprint(&bout, "%s NO %s can't unsubscribe\r\n", tg, cmd);
1344 	else
1345 		Bprint(&bout, "%s OK %s completed\r\n", tg, cmd);
1346 }
1347 
1348 static void
1349 badsyn(void)
1350 {
1351 	parseErr("bad syntax");
1352 }
1353 
1354 static void
1355 clearcmd(void)
1356 {
1357 	int c;
1358 
1359 	for(;;){
1360 		c = getc();
1361 		if(c < 0)
1362 			bye("end of input");
1363 		if(c == '\n')
1364 			return;
1365 	}
1366 }
1367 
1368 static void
1369 crnl(void)
1370 {
1371 	int c;
1372 
1373 	c = getc();
1374 	if(c == '\n')
1375 		return;
1376 	if(c != '\r' || getc() != '\n')
1377 		badsyn();
1378 }
1379 
1380 static void
1381 mustBe(int c)
1382 {
1383 	if(getc() != c){
1384 		ungetc();
1385 		badsyn();
1386 	}
1387 }
1388 
1389 /*
1390  * flaglist	: '(' ')' | '(' flags ')'
1391  */
1392 static int
1393 flagList(void)
1394 {
1395 	int f;
1396 
1397 	mustBe('(');
1398 	f = 0;
1399 	if(peekc() != ')')
1400 		f = flags();
1401 
1402 	mustBe(')');
1403 	return f;
1404 }
1405 
1406 /*
1407  * flags	: flag | flags ' ' flag
1408  * flag		: '\' atom | atom
1409  */
1410 static int
1411 flags(void)
1412 {
1413 	int ff, flags;
1414 	char *s;
1415 	int c;
1416 
1417 	flags = 0;
1418 	for(;;){
1419 		c = peekc();
1420 		if(c == '\\'){
1421 			mustBe('\\');
1422 			s = atomString(atomStop, "\\");
1423 		}else if(strchr(atomStop, c) != nil)
1424 			s = atom();
1425 		else
1426 			break;
1427 		ff = mapFlag(s);
1428 		if(ff == 0)
1429 			parseErr("flag not supported");
1430 		flags |= ff;
1431 		if(peekc() != ' ')
1432 			break;
1433 		mustBe(' ');
1434 	}
1435 	if(flags == 0)
1436 		parseErr("no flags given");
1437 	return flags;
1438 }
1439 
1440 /*
1441  * storeWhat	: osign 'FLAGS' ' ' storeflags
1442  *		| osign 'FLAGS.SILENT' ' ' storeflags
1443  * osign	:
1444  *		| '+' | '-'
1445  * storeflags	: flagList | flags
1446  */
1447 static Store*
1448 storeWhat(void)
1449 {
1450 	int f;
1451 	char *s;
1452 	int c, w;
1453 
1454 	c = peekc();
1455 	if(c == '+' || c == '-')
1456 		mustBe(c);
1457 	else
1458 		c = 0;
1459 	s = atom();
1460 	w = 0;
1461 	if(cistrcmp(s, "flags") == 0)
1462 		w = STFlags;
1463 	else if(cistrcmp(s, "flags.silent") == 0)
1464 		w = STFlagsSilent;
1465 	else
1466 		parseErr("illegal store attribute");
1467 	mustBe(' ');
1468 	if(peekc() == '(')
1469 		f = flagList();
1470 	else
1471 		f = flags();
1472 	return mkStore(c, w, f);
1473 }
1474 
1475 /*
1476  * fetchWhat	: "ALL" | "FULL" | "FAST" | fetchAtt | '(' fetchAtts ')'
1477  * fetchAtts	: fetchAtt | fetchAtts ' ' fetchAtt
1478  */
1479 static char *fetchAtom	= "(){}%*\"\\[]";
1480 static Fetch*
1481 fetchWhat(void)
1482 {
1483 	Fetch *f;
1484 	char *s;
1485 
1486 	if(peekc() == '('){
1487 		getc();
1488 		f = nil;
1489 		for(;;){
1490 			s = atomString(fetchAtom, "");
1491 			f = fetchAtt(s, f);
1492 			if(peekc() == ')')
1493 				break;
1494 			mustBe(' ');
1495 		}
1496 		getc();
1497 		return revFetch(f);
1498 	}
1499 
1500 	s = atomString(fetchAtom, "");
1501 	if(cistrcmp(s, "all") == 0)
1502 		f = mkFetch(FFlags, mkFetch(FInternalDate, mkFetch(FRfc822Size, mkFetch(FEnvelope, nil))));
1503 	else if(cistrcmp(s, "fast") == 0)
1504 		f = mkFetch(FFlags, mkFetch(FInternalDate, mkFetch(FRfc822Size, nil)));
1505 	else if(cistrcmp(s, "full") == 0)
1506 		f = mkFetch(FFlags, mkFetch(FInternalDate, mkFetch(FRfc822Size, mkFetch(FEnvelope, mkFetch(FBody, nil)))));
1507 	else
1508 		f = fetchAtt(s, nil);
1509 	return f;
1510 }
1511 
1512 /*
1513  * fetchAtt	: "ENVELOPE" | "FLAGS" | "INTERNALDATE"
1514  *		| "RFC822" | "RFC822.HEADER" | "RFC822.SIZE" | "RFC822.TEXT"
1515  *		| "BODYSTRUCTURE"
1516  *		| "UID"
1517  *		| "BODY"
1518  *		| "BODY" bodysubs
1519  *		| "BODY.PEEK" bodysubs
1520  * bodysubs	: sect
1521  *		| sect '<' number '.' nz-number '>'
1522  * sect		: '[' sectSpec ']'
1523  * sectSpec	: sectMsgText
1524  *		| sectPart
1525  *		| sectPart '.' sectText
1526  * sectPart	: nz-number
1527  *		| sectPart '.' nz-number
1528  */
1529 static Fetch*
1530 fetchAtt(char *s, Fetch *f)
1531 {
1532 	NList *sect;
1533 	int c;
1534 
1535 	if(cistrcmp(s, "envelope") == 0)
1536 		return mkFetch(FEnvelope, f);
1537 	if(cistrcmp(s, "flags") == 0)
1538 		return mkFetch(FFlags, f);
1539 	if(cistrcmp(s, "internaldate") == 0)
1540 		return mkFetch(FInternalDate, f);
1541 	if(cistrcmp(s, "RFC822") == 0)
1542 		return mkFetch(FRfc822, f);
1543 	if(cistrcmp(s, "RFC822.header") == 0)
1544 		return mkFetch(FRfc822Head, f);
1545 	if(cistrcmp(s, "RFC822.size") == 0)
1546 		return mkFetch(FRfc822Size, f);
1547 	if(cistrcmp(s, "RFC822.text") == 0)
1548 		return mkFetch(FRfc822Text, f);
1549 	if(cistrcmp(s, "bodystructure") == 0)
1550 		return mkFetch(FBodyStruct, f);
1551 	if(cistrcmp(s, "uid") == 0)
1552 		return mkFetch(FUid, f);
1553 
1554 	if(cistrcmp(s, "body") == 0){
1555 		if(peekc() != '[')
1556 			return mkFetch(FBody, f);
1557 		f = mkFetch(FBodySect, f);
1558 	}else if(cistrcmp(s, "body.peek") == 0)
1559 		f = mkFetch(FBodyPeek, f);
1560 	else
1561 		parseErr("illegal fetch attribute");
1562 
1563 	mustBe('[');
1564 	c = peekc();
1565 	if(c >= '1' && c <= '9'){
1566 		sect = mkNList(number(1), nil);
1567 		while(peekc() == '.'){
1568 			getc();
1569 			c = peekc();
1570 			if(c >= '1' && c <= '9'){
1571 				sect = mkNList(number(1), sect);
1572 			}else{
1573 				break;
1574 			}
1575 		}
1576 		f->sect = revNList(sect);
1577 	}
1578 	if(peekc() != ']')
1579 		sectText(f, f->sect != nil);
1580 	mustBe(']');
1581 
1582 	if(peekc() != '<')
1583 		return f;
1584 
1585 	f->partial = 1;
1586 	mustBe('<');
1587 	f->start = number(0);
1588 	mustBe('.');
1589 	f->size = number(1);
1590 	mustBe('>');
1591 	return f;
1592 }
1593 
1594 /*
1595  * sectText	: sectMsgText | "MIME"
1596  * sectMsgText	: "HEADER"
1597  *		| "TEXT"
1598  *		| "HEADER.FIELDS" ' ' hdrList
1599  *		| "HEADER.FIELDS.NOT" ' ' hdrList
1600  * hdrList	: '(' hdrs ')'
1601  * hdrs:	: astring
1602  *		| hdrs ' ' astring
1603  */
1604 static void
1605 sectText(Fetch *f, int mimeOk)
1606 {
1607 	SList *h;
1608 	char *s;
1609 
1610 	s = atomString(fetchAtom, "");
1611 	if(cistrcmp(s, "header") == 0){
1612 		f->part = FPHead;
1613 		return;
1614 	}
1615 	if(cistrcmp(s, "text") == 0){
1616 		f->part = FPText;
1617 		return;
1618 	}
1619 	if(mimeOk && cistrcmp(s, "mime") == 0){
1620 		f->part = FPMime;
1621 		return;
1622 	}
1623 	if(cistrcmp(s, "header.fields") == 0)
1624 		f->part = FPHeadFields;
1625 	else if(cistrcmp(s, "header.fields.not") == 0)
1626 		f->part = FPHeadFieldsNot;
1627 	else
1628 		parseErr("illegal fetch section text");
1629 	mustBe(' ');
1630 	mustBe('(');
1631 	h = nil;
1632 	for(;;){
1633 		h = mkSList(astring(), h);
1634 		if(peekc() == ')')
1635 			break;
1636 		mustBe(' ');
1637 	}
1638 	mustBe(')');
1639 	f->hdrs = revSList(h);
1640 }
1641 
1642 /*
1643  * searchWhat	: "CHARSET" ' ' astring searchkeys | searchkeys
1644  * searchkeys	: searchkey | searchkeys ' ' searchkey
1645  * searchkey	: "ALL" | "ANSWERED" | "DELETED" | "FLAGGED" | "NEW" | "OLD" | "RECENT"
1646  *		| "SEEN" | "UNANSWERED" | "UNDELETED" | "UNFLAGGED" | "DRAFT" | "UNDRAFT"
1647  *		| astrkey ' ' astring
1648  *		| datekey ' ' date
1649  *		| "KEYWORD" ' ' flag | "UNKEYWORD" flag
1650  *		| "LARGER" ' ' number | "SMALLER" ' ' number
1651  * 		| "HEADER" astring ' ' astring
1652  *		| set | "UID" ' ' set
1653  *		| "NOT" ' ' searchkey
1654  *		| "OR" ' ' searchkey ' ' searchkey
1655  *		| '(' searchkeys ')'
1656  * astrkey	: "BCC" | "BODY" | "CC" | "FROM" | "SUBJECT" | "TEXT" | "TO"
1657  * datekey	: "BEFORE" | "ON" | "SINCE" | "SENTBEFORE" | "SENTON" | "SENTSINCE"
1658  */
1659 static NamedInt searchMap[] =
1660 {
1661 	{"ALL",		SKAll},
1662 	{"ANSWERED",	SKAnswered},
1663 	{"DELETED",	SKDeleted},
1664 	{"FLAGGED",	SKFlagged},
1665 	{"NEW",		SKNew},
1666 	{"OLD",		SKOld},
1667 	{"RECENT",	SKRecent},
1668 	{"SEEN",	SKSeen},
1669 	{"UNANSWERED",	SKUnanswered},
1670 	{"UNDELETED",	SKUndeleted},
1671 	{"UNFLAGGED",	SKUnflagged},
1672 	{"DRAFT",	SKDraft},
1673 	{"UNDRAFT",	SKUndraft},
1674 	{"UNSEEN",	SKUnseen},
1675 	{nil,		0}
1676 };
1677 
1678 static NamedInt searchMapStr[] =
1679 {
1680 	{"CHARSET",	SKCharset},
1681 	{"BCC",		SKBcc},
1682 	{"BODY",	SKBody},
1683 	{"CC",		SKCc},
1684 	{"FROM",	SKFrom},
1685 	{"SUBJECT",	SKSubject},
1686 	{"TEXT",	SKText},
1687 	{"TO",		SKTo},
1688 	{nil,		0}
1689 };
1690 
1691 static NamedInt searchMapDate[] =
1692 {
1693 	{"BEFORE",	SKBefore},
1694 	{"ON",		SKOn},
1695 	{"SINCE",	SKSince},
1696 	{"SENTBEFORE",	SKSentBefore},
1697 	{"SENTON",	SKSentOn},
1698 	{"SENTSINCE",	SKSentSince},
1699 	{nil,		0}
1700 };
1701 
1702 static NamedInt searchMapFlag[] =
1703 {
1704 	{"KEYWORD",	SKKeyword},
1705 	{"UNKEYWORD",	SKUnkeyword},
1706 	{nil,		0}
1707 };
1708 
1709 static NamedInt searchMapNum[] =
1710 {
1711 	{"SMALLER",	SKSmaller},
1712 	{"LARGER",	SKLarger},
1713 	{nil,		0}
1714 };
1715 
1716 static Search*
1717 searchKeys(int first, Search *tail)
1718 {
1719 	Search *s;
1720 
1721 	for(;;){
1722 		if(peekc() == '('){
1723 			getc();
1724 			tail = searchKeys(0, tail);
1725 			mustBe(')');
1726 		}else{
1727 			s = searchKey(first);
1728 			tail->next = s;
1729 			tail = s;
1730 		}
1731 		first = 0;
1732 		if(peekc() != ' ')
1733 			break;
1734 		getc();
1735 	}
1736 	return tail;
1737 }
1738 
1739 static Search*
1740 searchKey(int first)
1741 {
1742 	Search *sr, rock;
1743 	Tm tm;
1744 	char *a;
1745 	int i, c;
1746 
1747 	sr = binalloc(&parseBin, sizeof(Search), 1);
1748 	if(sr == nil)
1749 		parseErr("out of memory");
1750 
1751 	c = peekc();
1752 	if(c >= '0' && c <= '9'){
1753 		sr->key = SKSet;
1754 		sr->set = msgSet(0);
1755 		return sr;
1756 	}
1757 
1758 	a = atom();
1759 	if(i = mapInt(searchMap, a))
1760 		sr->key = i;
1761 	else if(i = mapInt(searchMapStr, a)){
1762 		if(!first && i == SKCharset)
1763 			parseErr("illegal search key");
1764 		sr->key = i;
1765 		mustBe(' ');
1766 		sr->s = astring();
1767 	}else if(i = mapInt(searchMapDate, a)){
1768 		sr->key = i;
1769 		mustBe(' ');
1770 		c = peekc();
1771 		if(c == '"')
1772 			getc();
1773 		a = atom();
1774 		if(!imap4Date(&tm, a))
1775 			parseErr("bad date format");
1776 		sr->year = tm.year;
1777 		sr->mon = tm.mon;
1778 		sr->mday = tm.mday;
1779 		if(c == '"')
1780 			mustBe('"');
1781 	}else if(i = mapInt(searchMapFlag, a)){
1782 		sr->key = i;
1783 		mustBe(' ');
1784 		c = peekc();
1785 		if(c == '\\'){
1786 			mustBe('\\');
1787 			a = atomString(atomStop, "\\");
1788 		}else
1789 			a = atom();
1790 		i = mapFlag(a);
1791 		if(i == 0)
1792 			parseErr("flag not supported");
1793 		sr->num = i;
1794 	}else if(i = mapInt(searchMapNum, a)){
1795 		sr->key = i;
1796 		mustBe(' ');
1797 		sr->num = number(0);
1798 	}else if(cistrcmp(a, "HEADER") == 0){
1799 		sr->key = SKHeader;
1800 		mustBe(' ');
1801 		sr->hdr = astring();
1802 		mustBe(' ');
1803 		sr->s = astring();
1804 	}else if(cistrcmp(a, "UID") == 0){
1805 		sr->key = SKUid;
1806 		mustBe(' ');
1807 		sr->set = msgSet(0);
1808 	}else if(cistrcmp(a, "NOT") == 0){
1809 		sr->key = SKNot;
1810 		mustBe(' ');
1811 		rock.next = nil;
1812 		searchKeys(0, &rock);
1813 		sr->left = rock.next;
1814 	}else if(cistrcmp(a, "OR") == 0){
1815 		sr->key = SKOr;
1816 		mustBe(' ');
1817 		rock.next = nil;
1818 		searchKeys(0, &rock);
1819 		sr->left = rock.next;
1820 		mustBe(' ');
1821 		rock.next = nil;
1822 		searchKeys(0, &rock);
1823 		sr->right = rock.next;
1824 	}else
1825 		parseErr("illegal search key");
1826 	return sr;
1827 }
1828 
1829 /*
1830  * set	: seqno
1831  *	| seqno ':' seqno
1832  *	| set ',' set
1833  * seqno: nz-number
1834  *	| '*'
1835  *
1836  */
1837 static MsgSet*
1838 msgSet(int uids)
1839 {
1840 	MsgSet head, *last, *ms;
1841 	ulong from, to;
1842 
1843 	last = &head;
1844 	head.next = nil;
1845 	for(;;){
1846 		from = uids ? uidNo() : seqNo();
1847 		to = from;
1848 		if(peekc() == ':'){
1849 			getc();
1850 			to = uids ? uidNo() : seqNo();
1851 		}
1852 		ms = binalloc(&parseBin, sizeof(MsgSet), 0);
1853 		if(ms == nil)
1854 			parseErr("out of memory");
1855 		ms->from = from;
1856 		ms->to = to;
1857 		ms->next = nil;
1858 		last->next = ms;
1859 		last = ms;
1860 		if(peekc() != ',')
1861 			break;
1862 		getc();
1863 	}
1864 	return head.next;
1865 }
1866 
1867 static ulong
1868 seqNo(void)
1869 {
1870 	if(peekc() == '*'){
1871 		getc();
1872 		return ~0UL;
1873 	}
1874 	return number(1);
1875 }
1876 
1877 static ulong
1878 uidNo(void)
1879 {
1880 	if(peekc() == '*'){
1881 		getc();
1882 		return ~0UL;
1883 	}
1884 	return number(0);
1885 }
1886 
1887 /*
1888  * 7 bit, non-ctl chars, no (){%*"\
1889  * NIL is special case for nstring or parenlist
1890  */
1891 static char *
1892 atom(void)
1893 {
1894 	return atomString(atomStop, "");
1895 }
1896 
1897 /*
1898  * like an atom, but no +
1899  */
1900 static char *
1901 tag(void)
1902 {
1903 	return atomString("+(){%*\"\\", "");
1904 }
1905 
1906 /*
1907  * string or atom allowing %*
1908  */
1909 static char *
1910 listmbox(void)
1911 {
1912 	int c;
1913 
1914 	c = peekc();
1915 	if(c == '{')
1916 		return literal();
1917 	if(c == '"')
1918 		return quoted();
1919 	return atomString("(){\"\\", "");
1920 }
1921 
1922 /*
1923  * string or atom
1924  */
1925 static char *
1926 astring(void)
1927 {
1928 	int c;
1929 
1930 	c = peekc();
1931 	if(c == '{')
1932 		return literal();
1933 	if(c == '"')
1934 		return quoted();
1935 	return atom();
1936 }
1937 
1938 /*
1939  * 7 bit, non-ctl chars, none from exception list
1940  */
1941 static char *
1942 atomString(char *disallowed, char *initial)
1943 {
1944 	char *s;
1945 	int c, ns, as;
1946 
1947 	ns = strlen(initial);
1948 	s = binalloc(&parseBin, ns + StrAlloc, 0);
1949 	if(s == nil)
1950 		parseErr("out of memory");
1951 	strcpy(s, initial);
1952 	as = ns + StrAlloc;
1953 	for(;;){
1954 		c = getc();
1955 		if(c <= ' ' || c >= 0x7f || strchr(disallowed, c) != nil){
1956 			ungetc();
1957 			break;
1958 		}
1959 		s[ns++] = c;
1960 		if(ns >= as){
1961 			s = bingrow(&parseBin, s, as, as + StrAlloc, 0);
1962 			if(s == nil)
1963 				parseErr("out of memory");
1964 			as += StrAlloc;
1965 		}
1966 	}
1967 	if(ns == 0)
1968 		badsyn();
1969 	s[ns] = '\0';
1970 	return s;
1971 }
1972 
1973 /*
1974  * quoted: '"' chars* '"'
1975  * chars:	1-128 except \r and \n
1976  */
1977 static char *
1978 quoted(void)
1979 {
1980 	char *s;
1981 	int c, ns, as;
1982 
1983 	mustBe('"');
1984 	s = binalloc(&parseBin, StrAlloc, 0);
1985 	if(s == nil)
1986 		parseErr("out of memory");
1987 	as = StrAlloc;
1988 	ns = 0;
1989 	for(;;){
1990 		c = getc();
1991 		if(c == '"')
1992 			break;
1993 		if(c < 1 || c > 0x7f || c == '\r' || c == '\n')
1994 			badsyn();
1995 		if(c == '\\'){
1996 			c = getc();
1997 			if(c != '\\' && c != '"')
1998 				badsyn();
1999 		}
2000 		s[ns++] = c;
2001 		if(ns >= as){
2002 			s = bingrow(&parseBin, s, as, as + StrAlloc, 0);
2003 			if(s == nil)
2004 				parseErr("out of memory");
2005 			as += StrAlloc;
2006 		}
2007 	}
2008 	s[ns] = '\0';
2009 	return s;
2010 }
2011 
2012 /*
2013  * litlen: {number}\r\n
2014  */
2015 static ulong
2016 litlen(void)
2017 {
2018 	ulong v;
2019 
2020 	mustBe('{');
2021 	v = number(0);
2022 	mustBe('}');
2023 	crnl();
2024 	return v;
2025 }
2026 
2027 /*
2028  * literal: litlen data<0:litlen>
2029  */
2030 static char *
2031 literal(void)
2032 {
2033 	char *s;
2034 	ulong v;
2035 
2036 	v = litlen();
2037 	s = binalloc(&parseBin, v+1, 0);
2038 	if(s == nil)
2039 		parseErr("out of memory");
2040 	Bprint(&bout, "+ Ready for literal data\r\n");
2041 	if(Bflush(&bout) < 0)
2042 		writeErr();
2043 	if(v != 0 && Bread(&bin, s, v) != v)
2044 		badsyn();
2045 	s[v] = '\0';
2046 	return s;
2047 }
2048 
2049 /*
2050  * digits; number is 32 bits
2051  */
2052 static ulong
2053 number(int nonzero)
2054 {
2055 	ulong v;
2056 	int c, first;
2057 
2058 	v = 0;
2059 	first = 1;
2060 	for(;;){
2061 		c = getc();
2062 		if(c < '0' || c > '9'){
2063 			ungetc();
2064 			if(first)
2065 				badsyn();
2066 			break;
2067 		}
2068 		if(nonzero && first && c == '0')
2069 			badsyn();
2070 		c -= '0';
2071 		first = 0;
2072 		if(v > UlongMax/10 || v == UlongMax/10 && c > UlongMax%10)
2073 			parseErr("number out of range\r\n");
2074 		v = v * 10 + c;
2075 	}
2076 	return v;
2077 }
2078 
2079 static int
2080 getc(void)
2081 {
2082 	return Bgetc(&bin);
2083 }
2084 
2085 static void
2086 ungetc(void)
2087 {
2088 	Bungetc(&bin);
2089 }
2090 
2091 static int
2092 peekc(void)
2093 {
2094 	int c;
2095 
2096 	c = Bgetc(&bin);
2097 	Bungetc(&bin);
2098 	return c;
2099 }
2100 
2101