xref: /plan9-contrib/sys/src/cmd/ip/imap4d/imap4d.c (revision ec59a3ddbfceee0efe34584c2c9981a5e5ff1ec4)
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(!cdExists(mboxDir, mbox)){
608 		check();
609 		Bprint(&bout, "%s NO [TRYCREATE] %s mailbox does not exist\r\n", tg, cmd);
610 		return;
611 	}
612 
613 	max = selected->max;
614 	checkBox(selected, 0);
615 	ok = forMsgs(selected, ms, max, uids, copyCheck, nil);
616 	if(ok)
617 		ok = forMsgs(selected, ms, max, uids, copySave, mbox);
618 
619 	status(1, uids);
620 	if(ok)
621 		Bprint(&bout, "%s OK %s%s completed\r\n", tg, uid, cmd);
622 	else
623 		Bprint(&bout, "%s NO %s%s failed\r\n", tg, uid, cmd);
624 }
625 
626 static void
627 createCmd(char *tg, char *cmd)
628 {
629 	char *mbox, *m;
630 	int fd, slash;
631 
632 	mustBe(' ');
633 	mbox = astring();
634 	crnl();
635 	check();
636 
637 	m = strchr(mbox, '\0');
638 	slash = m != mbox && m[-1] == '/';
639 	mbox = mboxName(mbox);
640 	if(mbox == nil || !okMbox(mbox)){
641 		Bprint(&bout, "%s NO %s bad mailbox\r\n", tg, cmd);
642 		return;
643 	}
644 	if(cistrcmp(mbox, "inbox") == 0){
645 		Bprint(&bout, "%s NO %s cannot remotely create INBOX\r\n", tg, cmd);
646 		return;
647 	}
648 	if(access(mbox, AEXIST) >= 0){
649 		Bprint(&bout, "%s NO %s mailbox already exists\r\n", tg, cmd);
650 		return;
651 	}
652 
653 	fd = createBox(mbox, slash);
654 	close(fd);
655 	if(fd < 0)
656 		Bprint(&bout, "%s NO %s cannot create mailbox %s\r\n", tg, cmd, mbox);
657 	else
658 		Bprint(&bout, "%s OK %s %s completed\r\n", tg, mbox, cmd);
659 }
660 
661 static void
662 deleteCmd(char *tg, char *cmd)
663 {
664 	char *mbox, *imp;
665 
666 	mustBe(' ');
667 	mbox = astring();
668 	crnl();
669 	check();
670 
671 	mbox = mboxName(mbox);
672 	if(mbox == nil || !okMbox(mbox)){
673 		Bprint(&bout, "%s NO %s bad mailbox\r\n", tg, cmd);
674 		return;
675 	}
676 
677 	imp = impName(mbox);
678 	if(cistrcmp(mbox, "inbox") == 0
679 	|| imp != nil && cdRemove(mboxDir, imp) < 0 && cdExists(mboxDir, imp)
680 	|| cdRemove(mboxDir, mbox) < 0)
681 		Bprint(&bout, "%s NO %s cannot delete mailbox %s\r\n", tg, cmd, mbox);
682 	else
683 		Bprint(&bout, "%s OK %s %s completed\r\n", tg, mbox, cmd);
684 }
685 
686 static void
687 expungeCmd(char *tg, char *cmd)
688 {
689 	int ok;
690 
691 	crnl();
692 	ok = deleteMsgs(selected);
693 	check();
694 	if(ok)
695 		Bprint(&bout, "%s OK %s messages erased\r\n", tg, cmd);
696 	else
697 		Bprint(&bout, "%s NO %s some messages not expunged\r\n", tg, cmd);
698 }
699 
700 static void
701 fetchCmd(char *tg, char *cmd)
702 {
703 	fetchUCmd(tg, cmd, 0);
704 }
705 
706 static void
707 fetchUCmd(char *tg, char *cmd, int uids)
708 {
709 	Fetch *f;
710 	MsgSet *ms;
711 	MbLock *ml;
712 	char *uid;
713 	ulong max;
714 	int ok;
715 
716 	mustBe(' ');
717 	ms = msgSet(uids);
718 	mustBe(' ');
719 	f = fetchWhat();
720 	crnl();
721 	uid = "";
722 	if(uids)
723 		uid = "uid ";
724 	max = selected->max;
725 	ml = checkBox(selected, 1);
726 	if(ml != nil)
727 		forMsgs(selected, ms, max, uids, fetchSeen, f);
728 	closeImp(selected, ml);
729 	ok = ml != nil && forMsgs(selected, ms, max, uids, fetchMsg, f);
730 	status(uids, uids);
731 	if(ok)
732 		Bprint(&bout, "%s OK %s%s completed\r\n", tg, uid, cmd);
733 	else
734 		Bprint(&bout, "%s NO %s%s failed\r\n", tg, uid, cmd);
735 }
736 
737 static void
738 idleCmd(char *tg, char *cmd)
739 {
740 	int c, pid;
741 
742 	crnl();
743 	Bprint(&bout, "+ idling, waiting for done\r\n");
744 	if(Bflush(&bout) < 0)
745 		writeErr();
746 
747 	if(idlepid < 0){
748 		pid = rfork(RFPROC|RFMEM|RFNOWAIT);
749 		if(pid == 0){
750 			for(;;){
751 				qlock(&imaplock);
752 				if(exiting)
753 					break;
754 
755 				/*
756 				 * parent may have changed curDir, but it doesn't change our .
757 				 */
758 				resetCurDir();
759 
760 				check();
761 				if(Bflush(&bout) < 0)
762 					writeErr();
763 				qunlock(&imaplock);
764 				sleep(15*1000);
765 				enableForwarding();
766 			}
767 _exits("rob3");
768 			_exits(0);
769 		}
770 		idlepid = pid;
771 	}
772 
773 	qunlock(&imaplock);
774 
775 	/*
776 	 * clear out the next line, which is supposed to contain (case-insensitive)
777 	 * done\n
778 	 * this is special code since it has to dance with the idle polling proc
779 	 * and handle exiting correctly.
780 	 */
781 	for(;;){
782 		c = getc();
783 		if(c < 0){
784 			qlock(&imaplock);
785 			if(!exiting)
786 				cleaner();
787 _exits("rob4");
788 			_exits(0);
789 		}
790 		if(c == '\n')
791 			break;
792 	}
793 
794 	qlock(&imaplock);
795 	if(exiting)
796 {_exits("rob5");
797 		_exits(0);
798 }
799 
800 	/*
801 	 * child may have changed curDir, but it doesn't change our .
802 	 */
803 	resetCurDir();
804 
805 	check();
806 	Bprint(&bout, "%s OK %s terminated\r\n", tg, cmd);
807 }
808 
809 static void
810 listCmd(char *tg, char *cmd)
811 {
812 	char *s, *t, *ss, *ref, *mbox;
813 	int n;
814 
815 	mustBe(' ');
816 	s = astring();
817 	mustBe(' ');
818 	t = listmbox();
819 	crnl();
820 	check();
821 	ref = mutf7str(s);
822 	mbox = mutf7str(t);
823 	if(ref == nil || mbox == nil){
824 		Bprint(&bout, "%s BAD %s mailbox name not in modified utf-7\r\n", tg, cmd);
825 		return;
826 	}
827 
828 	/*
829 	 * special request for hierarchy delimiter and root name
830 	 * root name appears to be name up to and including any delimiter,
831 	 * or the empty string, if there is no delimiter.
832 	 *
833 	 * this must change if the # namespace convention is supported.
834 	 */
835 	if(*mbox == '\0'){
836 		s = strchr(ref, '/');
837 		if(s == nil)
838 			ref = "";
839 		else
840 			s[1] = '\0';
841 		Bprint(&bout, "* %s (\\Noselect) \"/\" \"%s\"\r\n", cmd, ref);
842 		Bprint(&bout, "%s OK %s\r\n", tg, cmd);
843 		return;
844 	}
845 
846 
847 	/*
848 	 * massage the listing name:
849 	 * clean up the components individually,
850 	 * then rip off componenets from the ref to
851 	 * take care of leading ..'s in the mbox.
852 	 *
853 	 * the cleanup can wipe out * followed by a ..
854 	 * tough luck if such a stupid pattern is given.
855 	 */
856 	cleanname(mbox);
857 	if(strcmp(mbox, ".") == 0)
858 		*mbox = '\0';
859 	if(mbox[0] == '/')
860 		*ref = '\0';
861 	else if(*ref != '\0'){
862 		cleanname(ref);
863 		if(strcmp(ref, ".") == 0)
864 			*ref = '\0';
865 	}else
866 		*ref = '\0';
867 	while(*ref && isdotdot(mbox)){
868 		s = strrchr(ref, '/');
869 		if(s == nil)
870 			s = ref;
871 		if(isdotdot(s))
872 			break;
873 		*s = '\0';
874 		mbox += 2;
875 		if(*mbox == '/')
876 			mbox++;
877 	}
878 	if(*ref == '\0'){
879 		s = mbox;
880 		ss = s;
881 	}else{
882 		n = strlen(ref) + strlen(mbox) + 2;
883 		t = binalloc(&parseBin, n, 0);
884 		if(t == nil)
885 			parseErr("out of memory");
886 		snprint(t, n, "%s/%s", ref, mbox);
887 		s = t;
888 		ss = s + strlen(ref);
889 	}
890 
891 	/*
892 	 * only allow activity in /mail/box
893 	 */
894 	if(s[0] == '/' || isdotdot(s)){
895 		Bprint(&bout, "%s NO illegal mailbox pattern\r\n", tg);
896 		return;
897 	}
898 
899 	if(cistrcmp(cmd, "lsub") == 0)
900 		lsubBoxes(cmd, s, ss);
901 	else
902 		listBoxes(cmd, s, ss);
903 	Bprint(&bout, "%s OK %s completed\r\n", tg, cmd);
904 }
905 
906 static char*
907 passCR(char*u, char*p)
908 {
909 	static char Ebadch[] = "can't get challenge";
910 	static char nchall[64];
911 	static char response[64];
912 	static Chalstate *ch = nil;
913 	AuthInfo *ai;
914 
915 again:
916 	if (ch == nil){
917 		if(!(ch = auth_challenge("proto=p9cr role=server user=%q", u)))
918 			return Ebadch;
919 		snprint(nchall, 64, " encrypt challenge: %s", ch->chal);
920 		return nchall;
921 	} else {
922 		strncpy(response, p, 64);
923 		ch->resp = response;
924 		ch->nresp = strlen(response);
925 		ai = auth_response(ch);
926 		auth_freechal(ch);
927 		ch = nil;
928 		if (ai == nil)
929 			goto again;
930 		setupuser(ai);
931 		return nil;
932 	}
933 
934 }
935 
936 static void
937 loginCmd(char *tg, char *cmd)
938 {
939 	char *s, *t;
940 	AuthInfo *ai;
941 	char*r;
942 	mustBe(' ');
943 	s = astring();	/* uid */
944 	mustBe(' ');
945 	t = astring();	/* password */
946 	crnl();
947 	if(allowCR){
948 		if ((r = passCR(s, t)) == nil){
949 			Bprint(&bout, "%s OK %s succeeded\r\n", tg, cmd);
950 			imapState = SAuthed;
951 		} else {
952 			Bprint(&bout, "* NO [ALERT] %s\r\n", r);
953 			Bprint(&bout, "%s NO %s succeeded\r\n", tg, cmd);
954 		}
955 		return;
956 	}
957 	else if(allowPass){
958 		if(ai = passLogin(s, t)){
959 			setupuser(ai);
960 			Bprint(&bout, "%s OK %s succeeded\r\n", tg, cmd);
961 			imapState = SAuthed;
962 		}else
963 			Bprint(&bout, "%s NO %s failed check\r\n", tg, cmd);
964 		return;
965 	}
966 	Bprint(&bout, "%s NO %s plaintext passwords disallowed\r\n", tg, cmd);
967 }
968 
969 /*
970  * logout or x-exit, which doesn't expunge the mailbox
971  */
972 static void
973 logoutCmd(char *tg, char *cmd)
974 {
975 	crnl();
976 
977 	if(cmd[0] != 'x' && selected){
978 		closeBox(selected, 1);
979 		selected = nil;
980 	}
981 	Bprint(&bout, "* bye\r\n");
982 	Bprint(&bout, "%s OK %s completed\r\n", tg, cmd);
983 exits("rob6");
984 	exits(0);
985 }
986 
987 static void
988 namespaceCmd(char *tg, char *cmd)
989 {
990 	crnl();
991 	check();
992 
993 	/*
994 	 * personal, other users, shared namespaces
995 	 * send back nil or descriptions of (prefix heirarchy-delim) for each case
996 	 */
997 	Bprint(&bout, "* NAMESPACE ((\"\" \"/\")) nil nil\r\n");
998 	Bprint(&bout, "%s OK %s completed\r\n", tg, cmd);
999 }
1000 
1001 static void
1002 noopCmd(char *tg, char *cmd)
1003 {
1004 	crnl();
1005 	check();
1006 	Bprint(&bout, "%s OK %s completed\r\n", tg, cmd);
1007 	enableForwarding();
1008 }
1009 
1010 /*
1011  * this is only a partial implementation
1012  * should copy files to other directories,
1013  * and copy & truncate inbox
1014  */
1015 static void
1016 renameCmd(char *tg, char *cmd)
1017 {
1018 	char *from, *to;
1019 	int ok;
1020 
1021 	mustBe(' ');
1022 	from = astring();
1023 	mustBe(' ');
1024 	to = astring();
1025 	crnl();
1026 	check();
1027 
1028 	to = mboxName(to);
1029 	if(to == nil || !okMbox(to) || cistrcmp(to, "inbox") == 0){
1030 		Bprint(&bout, "%s NO %s bad mailbox destination name\r\n", tg, cmd);
1031 		return;
1032 	}
1033 	if(access(to, AEXIST) >= 0){
1034 		Bprint(&bout, "%s NO %s mailbox already exists\r\n", tg, cmd);
1035 		return;
1036 	}
1037 	from = mboxName(from);
1038 	if(from == nil || !okMbox(from)){
1039 		Bprint(&bout, "%s NO %s bad mailbox destination name\r\n", tg, cmd);
1040 		return;
1041 	}
1042 	if(cistrcmp(from, "inbox") == 0)
1043 		ok = copyBox(from, to, 0);
1044 	else
1045 		ok = moveBox(from, to);
1046 
1047 	if(ok)
1048 		Bprint(&bout, "%s OK %s completed\r\n", tg, cmd);
1049 	else
1050 		Bprint(&bout, "%s NO %s failed\r\n", tg, cmd);
1051 }
1052 
1053 static void
1054 searchCmd(char *tg, char *cmd)
1055 {
1056 	searchUCmd(tg, cmd, 0);
1057 }
1058 
1059 static void
1060 searchUCmd(char *tg, char *cmd, int uids)
1061 {
1062 	Search rock;
1063 	Msg *m;
1064 	char *uid;
1065 	ulong id;
1066 
1067 	mustBe(' ');
1068 	rock.next = nil;
1069 	searchKeys(1, &rock);
1070 	crnl();
1071 	uid = "";
1072 	if(uids)
1073 		uid = "uid ";
1074 	if(rock.next != nil && rock.next->key == SKCharset){
1075 		if(cistrstr(rock.next->s, "utf-8") != 0
1076 		&& cistrcmp(rock.next->s, "us-ascii") != 0){
1077 			Bprint(&bout, "%s NO [BADCHARSET] (\"US-ASCII\" \"UTF-8\") %s%s failed\r\n", tg, uid, cmd);
1078 			checkBox(selected, 0);
1079 			status(uids, uids);
1080 			return;
1081 		}
1082 		rock.next = rock.next->next;
1083 	}
1084 	Bprint(&bout, "* search");
1085 	for(m = selected->msgs; m != nil; m = m->next)
1086 		m->matched = searchMsg(m, rock.next);
1087 	for(m = selected->msgs; m != nil; m = m->next){
1088 		if(m->matched){
1089 			if(uids)
1090 				id = m->uid;
1091 			else
1092 				id = m->seq;
1093 			Bprint(&bout, " %lud", id);
1094 		}
1095 	}
1096 	Bprint(&bout, "\r\n");
1097 	checkBox(selected, 0);
1098 	status(uids, uids);
1099 	Bprint(&bout, "%s OK %s%s completed\r\n", tg, uid, cmd);
1100 }
1101 
1102 static void
1103 selectCmd(char *tg, char *cmd)
1104 {
1105 	Msg *m;
1106 	char *s, *mbox;
1107 
1108 	mustBe(' ');
1109 	mbox = astring();
1110 	crnl();
1111 
1112 	if(selected){
1113 		imapState = SAuthed;
1114 		closeBox(selected, 1);
1115 		selected = nil;
1116 	}
1117 
1118 	mbox = mboxName(mbox);
1119 	if(mbox == nil || !okMbox(mbox)){
1120 		Bprint(&bout, "%s NO %s bad mailbox\r\n", tg, cmd);
1121 		return;
1122 	}
1123 
1124 	selected = openBox(mbox, "imap", cistrcmp(cmd, "select") == 0);
1125 	if(selected == nil){
1126 		Bprint(&bout, "%s NO %s can't open mailbox %s: %r\r\n", tg, cmd, mbox);
1127 		return;
1128 	}
1129 
1130 	imapState = SSelected;
1131 
1132 	Bprint(&bout, "* FLAGS (\\Seen \\Answered \\Flagged \\Deleted \\Draft)\r\n");
1133 	Bprint(&bout, "* %lud EXISTS\r\n", selected->max);
1134 	selected->toldMax = selected->max;
1135 	Bprint(&bout, "* %lud RECENT\r\n", selected->recent);
1136 	selected->toldRecent = selected->recent;
1137 	for(m = selected->msgs; m != nil; m = m->next){
1138 		if(!m->expunged && (m->flags & MSeen) != MSeen){
1139 			Bprint(&bout, "* OK [UNSEEN %ld]\r\n", m->seq);
1140 			break;
1141 		}
1142 	}
1143 	Bprint(&bout, "* OK [PERMANENTFLAGS (\\Seen \\Answered \\Flagged \\Draft \\Deleted)]\r\n");
1144 	Bprint(&bout, "* OK [UIDNEXT %ld]\r\n", selected->uidnext);
1145 	Bprint(&bout, "* OK [UIDVALIDITY %ld]\r\n", selected->uidvalidity);
1146 	s = "READ-ONLY";
1147 	if(selected->writable)
1148 		s = "READ-WRITE";
1149 	Bprint(&bout, "%s OK [%s] %s %s completed\r\n", tg, s, cmd, mbox);
1150 }
1151 
1152 static NamedInt	statusItems[] =
1153 {
1154 	{"MESSAGES",	SMessages},
1155 	{"RECENT",	SRecent},
1156 	{"UIDNEXT",	SUidNext},
1157 	{"UIDVALIDITY",	SUidValidity},
1158 	{"UNSEEN",	SUnseen},
1159 	{nil,		0}
1160 };
1161 
1162 static void
1163 statusCmd(char *tg, char *cmd)
1164 {
1165 	Box *box;
1166 	Msg *m;
1167 	char *s, *mbox;
1168 	ulong v;
1169 	int si, i;
1170 
1171 	mustBe(' ');
1172 	mbox = astring();
1173 	mustBe(' ');
1174 	mustBe('(');
1175 	si = 0;
1176 	for(;;){
1177 		s = atom();
1178 		i = mapInt(statusItems, s);
1179 		if(i == 0)
1180 			parseErr("illegal status item");
1181 		si |= i;
1182 		if(peekc() == ')')
1183 			break;
1184 		mustBe(' ');
1185 	}
1186 	mustBe(')');
1187 	crnl();
1188 
1189 	mbox = mboxName(mbox);
1190 	if(mbox == nil || !okMbox(mbox)){
1191 		check();
1192 		Bprint(&bout, "%s NO %s bad mailbox\r\n", tg, cmd);
1193 		return;
1194 	}
1195 
1196 	box = openBox(mbox, "status", 1);
1197 	if(box == nil){
1198 		check();
1199 		Bprint(&bout, "%s NO [TRYCREATE] %s can't open mailbox %s: %r\r\n", tg, cmd, mbox);
1200 		return;
1201 	}
1202 
1203 	Bprint(&bout, "* STATUS (");
1204 	s = "";
1205 	for(i = 0; statusItems[i].name != nil; i++){
1206 		if(si & statusItems[i].v){
1207 			v = 0;
1208 			switch(statusItems[i].v){
1209 			case SMessages:
1210 				v = box->max;
1211 				break;
1212 			case SRecent:
1213 				v = box->recent;
1214 				break;
1215 			case SUidNext:
1216 				v = box->uidnext;
1217 				break;
1218 			case SUidValidity:
1219 				v = box->uidvalidity;
1220 				break;
1221 			case SUnseen:
1222 				v = 0;
1223 				for(m = box->msgs; m != nil; m = m->next)
1224 					if((m->flags & MSeen) != MSeen)
1225 						v++;
1226 				break;
1227 			default:
1228 				Bprint(&bout, ")");
1229 				bye("internal error: status item not implemented");
1230 				break;
1231 			}
1232 			Bprint(&bout, "%s%s %lud", s, statusItems[i].name, v);
1233 			s = " ";
1234 		}
1235 	}
1236 	Bprint(&bout, ")\r\n");
1237 	closeBox(box, 1);
1238 
1239 	check();
1240 	Bprint(&bout, "%s OK %s completed\r\n", tg, cmd);
1241 }
1242 
1243 static void
1244 storeCmd(char *tg, char *cmd)
1245 {
1246 	storeUCmd(tg, cmd, 0);
1247 }
1248 
1249 static void
1250 storeUCmd(char *tg, char *cmd, int uids)
1251 {
1252 	Store *st;
1253 	MsgSet *ms;
1254 	MbLock *ml;
1255 	char *uid;
1256 	ulong max;
1257 	int ok;
1258 
1259 	mustBe(' ');
1260 	ms = msgSet(uids);
1261 	mustBe(' ');
1262 	st = storeWhat();
1263 	crnl();
1264 	uid = "";
1265 	if(uids)
1266 		uid = "uid ";
1267 	max = selected->max;
1268 	ml = checkBox(selected, 1);
1269 	ok = ml != nil && forMsgs(selected, ms, max, uids, storeMsg, st);
1270 	closeImp(selected, ml);
1271 	status(uids, uids);
1272 	if(ok)
1273 		Bprint(&bout, "%s OK %s%s completed\r\n", tg, uid, cmd);
1274 	else
1275 		Bprint(&bout, "%s NO %s%s failed\r\n", tg, uid, cmd);
1276 }
1277 
1278 /*
1279  * minimal implementation of subscribe
1280  * all folders are automatically subscribed,
1281  * and can't be unsubscribed
1282  */
1283 static void
1284 subscribeCmd(char *tg, char *cmd)
1285 {
1286 	Box *box;
1287 	char *mbox;
1288 	int ok;
1289 
1290 	mustBe(' ');
1291 	mbox = astring();
1292 	crnl();
1293 	check();
1294 	mbox = mboxName(mbox);
1295 	ok = 0;
1296 	if(mbox != nil && okMbox(mbox)){
1297 		box = openBox(mbox, "subscribe", 0);
1298 		if(box != nil){
1299 			ok = subscribe(mbox, 's');
1300 			closeBox(box, 1);
1301 		}
1302 	}
1303 	if(!ok)
1304 		Bprint(&bout, "%s NO %s bad mailbox\r\n", tg, cmd);
1305 	else
1306 		Bprint(&bout, "%s OK %s completed\r\n", tg, cmd);
1307 }
1308 
1309 static void
1310 uidCmd(char *tg, char *cmd)
1311 {
1312 	char *sub;
1313 
1314 	mustBe(' ');
1315 	sub = atom();
1316 	if(cistrcmp(sub, "copy") == 0)
1317 		copyUCmd(tg, sub, 1);
1318 	else if(cistrcmp(sub, "fetch") == 0)
1319 		fetchUCmd(tg, sub, 1);
1320 	else if(cistrcmp(sub, "search") == 0)
1321 		searchUCmd(tg, sub, 1);
1322 	else if(cistrcmp(sub, "store") == 0)
1323 		storeUCmd(tg, sub, 1);
1324 	else{
1325 		clearcmd();
1326 		Bprint(&bout, "%s BAD %s illegal uid command %s\r\n", tg, cmd, sub);
1327 	}
1328 }
1329 
1330 static void
1331 unsubscribeCmd(char *tg, char *cmd)
1332 {
1333 	char *mbox;
1334 
1335 	mustBe(' ');
1336 	mbox = astring();
1337 	crnl();
1338 	check();
1339 	mbox = mboxName(mbox);
1340 	if(mbox == nil || !okMbox(mbox) || !subscribe(mbox, 'u'))
1341 		Bprint(&bout, "%s NO %s can't unsubscribe\r\n", tg, cmd);
1342 	else
1343 		Bprint(&bout, "%s OK %s completed\r\n", tg, cmd);
1344 }
1345 
1346 static void
1347 badsyn(void)
1348 {
1349 	parseErr("bad syntax");
1350 }
1351 
1352 static void
1353 clearcmd(void)
1354 {
1355 	int c;
1356 
1357 	for(;;){
1358 		c = getc();
1359 		if(c < 0)
1360 			bye("end of input");
1361 		if(c == '\n')
1362 			return;
1363 	}
1364 }
1365 
1366 static void
1367 crnl(void)
1368 {
1369 	int c;
1370 
1371 	c = getc();
1372 	if(c == '\n')
1373 		return;
1374 	if(c != '\r' || getc() != '\n')
1375 		badsyn();
1376 }
1377 
1378 static void
1379 mustBe(int c)
1380 {
1381 	if(getc() != c){
1382 		ungetc();
1383 		badsyn();
1384 	}
1385 }
1386 
1387 /*
1388  * flaglist	: '(' ')' | '(' flags ')'
1389  */
1390 static int
1391 flagList(void)
1392 {
1393 	int f;
1394 
1395 	mustBe('(');
1396 	f = 0;
1397 	if(peekc() != ')')
1398 		f = flags();
1399 
1400 	mustBe(')');
1401 	return f;
1402 }
1403 
1404 /*
1405  * flags	: flag | flags ' ' flag
1406  * flag		: '\' atom | atom
1407  */
1408 static int
1409 flags(void)
1410 {
1411 	int ff, flags;
1412 	char *s;
1413 	int c;
1414 
1415 	flags = 0;
1416 	for(;;){
1417 		c = peekc();
1418 		if(c == '\\'){
1419 			mustBe('\\');
1420 			s = atomString(atomStop, "\\");
1421 		}else if(strchr(atomStop, c) != nil)
1422 			s = atom();
1423 		else
1424 			break;
1425 		ff = mapFlag(s);
1426 		if(ff == 0)
1427 			parseErr("flag not supported");
1428 		flags |= ff;
1429 		if(peekc() != ' ')
1430 			break;
1431 		mustBe(' ');
1432 	}
1433 	if(flags == 0)
1434 		parseErr("no flags given");
1435 	return flags;
1436 }
1437 
1438 /*
1439  * storeWhat	: osign 'FLAGS' ' ' storeflags
1440  *		| osign 'FLAGS.SILENT' ' ' storeflags
1441  * osign	:
1442  *		| '+' | '-'
1443  * storeflags	: flagList | flags
1444  */
1445 static Store*
1446 storeWhat(void)
1447 {
1448 	int f;
1449 	char *s;
1450 	int c, w;
1451 
1452 	c = peekc();
1453 	if(c == '+' || c == '-')
1454 		mustBe(c);
1455 	else
1456 		c = 0;
1457 	s = atom();
1458 	w = 0;
1459 	if(cistrcmp(s, "flags") == 0)
1460 		w = STFlags;
1461 	else if(cistrcmp(s, "flags.silent") == 0)
1462 		w = STFlagsSilent;
1463 	else
1464 		parseErr("illegal store attribute");
1465 	mustBe(' ');
1466 	if(peekc() == '(')
1467 		f = flagList();
1468 	else
1469 		f = flags();
1470 	return mkStore(c, w, f);
1471 }
1472 
1473 /*
1474  * fetchWhat	: "ALL" | "FULL" | "FAST" | fetchAtt | '(' fetchAtts ')'
1475  * fetchAtts	: fetchAtt | fetchAtts ' ' fetchAtt
1476  */
1477 static char *fetchAtom	= "(){}%*\"\\[]";
1478 static Fetch*
1479 fetchWhat(void)
1480 {
1481 	Fetch *f;
1482 	char *s;
1483 
1484 	if(peekc() == '('){
1485 		getc();
1486 		f = nil;
1487 		for(;;){
1488 			s = atomString(fetchAtom, "");
1489 			f = fetchAtt(s, f);
1490 			if(peekc() == ')')
1491 				break;
1492 			mustBe(' ');
1493 		}
1494 		getc();
1495 		return revFetch(f);
1496 	}
1497 
1498 	s = atomString(fetchAtom, "");
1499 	if(cistrcmp(s, "all") == 0)
1500 		f = mkFetch(FFlags, mkFetch(FInternalDate, mkFetch(FRfc822Size, mkFetch(FEnvelope, nil))));
1501 	else if(cistrcmp(s, "fast") == 0)
1502 		f = mkFetch(FFlags, mkFetch(FInternalDate, mkFetch(FRfc822Size, nil)));
1503 	else if(cistrcmp(s, "full") == 0)
1504 		f = mkFetch(FFlags, mkFetch(FInternalDate, mkFetch(FRfc822Size, mkFetch(FEnvelope, mkFetch(FBody, nil)))));
1505 	else
1506 		f = fetchAtt(s, nil);
1507 	return f;
1508 }
1509 
1510 /*
1511  * fetchAtt	: "ENVELOPE" | "FLAGS" | "INTERNALDATE"
1512  *		| "RFC822" | "RFC822.HEADER" | "RFC822.SIZE" | "RFC822.TEXT"
1513  *		| "BODYSTRUCTURE"
1514  *		| "UID"
1515  *		| "BODY"
1516  *		| "BODY" bodysubs
1517  *		| "BODY.PEEK" bodysubs
1518  * bodysubs	: sect
1519  *		| sect '<' number '.' nz-number '>'
1520  * sect		: '[' sectSpec ']'
1521  * sectSpec	: sectMsgText
1522  *		| sectPart
1523  *		| sectPart '.' sectText
1524  * sectPart	: nz-number
1525  *		| sectPart '.' nz-number
1526  */
1527 static Fetch*
1528 fetchAtt(char *s, Fetch *f)
1529 {
1530 	NList *sect;
1531 	int c;
1532 
1533 	if(cistrcmp(s, "envelope") == 0)
1534 		return mkFetch(FEnvelope, f);
1535 	if(cistrcmp(s, "flags") == 0)
1536 		return mkFetch(FFlags, f);
1537 	if(cistrcmp(s, "internaldate") == 0)
1538 		return mkFetch(FInternalDate, f);
1539 	if(cistrcmp(s, "RFC822") == 0)
1540 		return mkFetch(FRfc822, f);
1541 	if(cistrcmp(s, "RFC822.header") == 0)
1542 		return mkFetch(FRfc822Head, f);
1543 	if(cistrcmp(s, "RFC822.size") == 0)
1544 		return mkFetch(FRfc822Size, f);
1545 	if(cistrcmp(s, "RFC822.text") == 0)
1546 		return mkFetch(FRfc822Text, f);
1547 	if(cistrcmp(s, "bodystructure") == 0)
1548 		return mkFetch(FBodyStruct, f);
1549 	if(cistrcmp(s, "uid") == 0)
1550 		return mkFetch(FUid, f);
1551 
1552 	if(cistrcmp(s, "body") == 0){
1553 		if(peekc() != '[')
1554 			return mkFetch(FBody, f);
1555 		f = mkFetch(FBodySect, f);
1556 	}else if(cistrcmp(s, "body.peek") == 0)
1557 		f = mkFetch(FBodyPeek, f);
1558 	else
1559 		parseErr("illegal fetch attribute");
1560 
1561 	mustBe('[');
1562 	c = peekc();
1563 	if(c >= '1' && c <= '9'){
1564 		sect = mkNList(number(1), nil);
1565 		while(peekc() == '.'){
1566 			getc();
1567 			c = peekc();
1568 			if(c >= '1' && c <= '9'){
1569 				sect = mkNList(number(1), sect);
1570 			}else{
1571 				break;
1572 			}
1573 		}
1574 		f->sect = revNList(sect);
1575 	}
1576 	if(peekc() != ']')
1577 		sectText(f, f->sect != nil);
1578 	mustBe(']');
1579 
1580 	if(peekc() != '<')
1581 		return f;
1582 
1583 	f->partial = 1;
1584 	mustBe('<');
1585 	f->start = number(0);
1586 	mustBe('.');
1587 	f->size = number(1);
1588 	mustBe('>');
1589 	return f;
1590 }
1591 
1592 /*
1593  * sectText	: sectMsgText | "MIME"
1594  * sectMsgText	: "HEADER"
1595  *		| "TEXT"
1596  *		| "HEADER.FIELDS" ' ' hdrList
1597  *		| "HEADER.FIELDS.NOT" ' ' hdrList
1598  * hdrList	: '(' hdrs ')'
1599  * hdrs:	: astring
1600  *		| hdrs ' ' astring
1601  */
1602 static void
1603 sectText(Fetch *f, int mimeOk)
1604 {
1605 	SList *h;
1606 	char *s;
1607 
1608 	s = atomString(fetchAtom, "");
1609 	if(cistrcmp(s, "header") == 0){
1610 		f->part = FPHead;
1611 		return;
1612 	}
1613 	if(cistrcmp(s, "text") == 0){
1614 		f->part = FPText;
1615 		return;
1616 	}
1617 	if(mimeOk && cistrcmp(s, "mime") == 0){
1618 		f->part = FPMime;
1619 		return;
1620 	}
1621 	if(cistrcmp(s, "header.fields") == 0)
1622 		f->part = FPHeadFields;
1623 	else if(cistrcmp(s, "header.fields.not") == 0)
1624 		f->part = FPHeadFieldsNot;
1625 	else
1626 		parseErr("illegal fetch section text");
1627 	mustBe(' ');
1628 	mustBe('(');
1629 	h = nil;
1630 	for(;;){
1631 		h = mkSList(astring(), h);
1632 		if(peekc() == ')')
1633 			break;
1634 		mustBe(' ');
1635 	}
1636 	mustBe(')');
1637 	f->hdrs = revSList(h);
1638 }
1639 
1640 /*
1641  * searchWhat	: "CHARSET" ' ' astring searchkeys | searchkeys
1642  * searchkeys	: searchkey | searchkeys ' ' searchkey
1643  * searchkey	: "ALL" | "ANSWERED" | "DELETED" | "FLAGGED" | "NEW" | "OLD" | "RECENT"
1644  *		| "SEEN" | "UNANSWERED" | "UNDELETED" | "UNFLAGGED" | "DRAFT" | "UNDRAFT"
1645  *		| astrkey ' ' astring
1646  *		| datekey ' ' date
1647  *		| "KEYWORD" ' ' flag | "UNKEYWORD" flag
1648  *		| "LARGER" ' ' number | "SMALLER" ' ' number
1649  * 		| "HEADER" astring ' ' astring
1650  *		| set | "UID" ' ' set
1651  *		| "NOT" ' ' searchkey
1652  *		| "OR" ' ' searchkey ' ' searchkey
1653  *		| '(' searchkeys ')'
1654  * astrkey	: "BCC" | "BODY" | "CC" | "FROM" | "SUBJECT" | "TEXT" | "TO"
1655  * datekey	: "BEFORE" | "ON" | "SINCE" | "SENTBEFORE" | "SENTON" | "SENTSINCE"
1656  */
1657 static NamedInt searchMap[] =
1658 {
1659 	{"ALL",		SKAll},
1660 	{"ANSWERED",	SKAnswered},
1661 	{"DELETED",	SKDeleted},
1662 	{"FLAGGED",	SKFlagged},
1663 	{"NEW",		SKNew},
1664 	{"OLD",		SKOld},
1665 	{"RECENT",	SKRecent},
1666 	{"SEEN",	SKSeen},
1667 	{"UNANSWERED",	SKUnanswered},
1668 	{"UNDELETED",	SKUndeleted},
1669 	{"UNFLAGGED",	SKUnflagged},
1670 	{"DRAFT",	SKDraft},
1671 	{"UNDRAFT",	SKUndraft},
1672 	{"UNSEEN",	SKUnseen},
1673 	{nil,		0}
1674 };
1675 
1676 static NamedInt searchMapStr[] =
1677 {
1678 	{"CHARSET",	SKCharset},
1679 	{"BCC",		SKBcc},
1680 	{"BODY",	SKBody},
1681 	{"CC",		SKCc},
1682 	{"FROM",	SKFrom},
1683 	{"SUBJECT",	SKSubject},
1684 	{"TEXT",	SKText},
1685 	{"TO",		SKTo},
1686 	{nil,		0}
1687 };
1688 
1689 static NamedInt searchMapDate[] =
1690 {
1691 	{"BEFORE",	SKBefore},
1692 	{"ON",		SKOn},
1693 	{"SINCE",	SKSince},
1694 	{"SENTBEFORE",	SKSentBefore},
1695 	{"SENTON",	SKSentOn},
1696 	{"SENTSINCE",	SKSentSince},
1697 	{nil,		0}
1698 };
1699 
1700 static NamedInt searchMapFlag[] =
1701 {
1702 	{"KEYWORD",	SKKeyword},
1703 	{"UNKEYWORD",	SKUnkeyword},
1704 	{nil,		0}
1705 };
1706 
1707 static NamedInt searchMapNum[] =
1708 {
1709 	{"SMALLER",	SKSmaller},
1710 	{"LARGER",	SKLarger},
1711 	{nil,		0}
1712 };
1713 
1714 static Search*
1715 searchKeys(int first, Search *tail)
1716 {
1717 	Search *s;
1718 
1719 	for(;;){
1720 		if(peekc() == '('){
1721 			getc();
1722 			tail = searchKeys(0, tail);
1723 			mustBe(')');
1724 		}else{
1725 			s = searchKey(first);
1726 			tail->next = s;
1727 			tail = s;
1728 		}
1729 		first = 0;
1730 		if(peekc() != ' ')
1731 			break;
1732 		getc();
1733 	}
1734 	return tail;
1735 }
1736 
1737 static Search*
1738 searchKey(int first)
1739 {
1740 	Search *sr, rock;
1741 	Tm tm;
1742 	char *a;
1743 	int i, c;
1744 
1745 	sr = binalloc(&parseBin, sizeof(Search), 1);
1746 	if(sr == nil)
1747 		parseErr("out of memory");
1748 
1749 	c = peekc();
1750 	if(c >= '0' && c <= '9'){
1751 		sr->key = SKSet;
1752 		sr->set = msgSet(0);
1753 		return sr;
1754 	}
1755 
1756 	a = atom();
1757 	if(i = mapInt(searchMap, a))
1758 		sr->key = i;
1759 	else if(i = mapInt(searchMapStr, a)){
1760 		if(!first && i == SKCharset)
1761 			parseErr("illegal search key");
1762 		sr->key = i;
1763 		mustBe(' ');
1764 		sr->s = astring();
1765 	}else if(i = mapInt(searchMapDate, a)){
1766 		sr->key = i;
1767 		mustBe(' ');
1768 		c = peekc();
1769 		if(c == '"')
1770 			getc();
1771 		a = atom();
1772 		if(!imap4Date(&tm, a))
1773 			parseErr("bad date format");
1774 		sr->year = tm.year;
1775 		sr->mon = tm.mon;
1776 		sr->mday = tm.mday;
1777 		if(c == '"')
1778 			mustBe('"');
1779 	}else if(i = mapInt(searchMapFlag, a)){
1780 		sr->key = i;
1781 		mustBe(' ');
1782 		c = peekc();
1783 		if(c == '\\'){
1784 			mustBe('\\');
1785 			a = atomString(atomStop, "\\");
1786 		}else
1787 			a = atom();
1788 		i = mapFlag(a);
1789 		if(i == 0)
1790 			parseErr("flag not supported");
1791 		sr->num = i;
1792 	}else if(i = mapInt(searchMapNum, a)){
1793 		sr->key = i;
1794 		mustBe(' ');
1795 		sr->num = number(0);
1796 	}else if(cistrcmp(a, "HEADER") == 0){
1797 		sr->key = SKHeader;
1798 		mustBe(' ');
1799 		sr->hdr = astring();
1800 		mustBe(' ');
1801 		sr->s = astring();
1802 	}else if(cistrcmp(a, "UID") == 0){
1803 		sr->key = SKUid;
1804 		mustBe(' ');
1805 		sr->set = msgSet(0);
1806 	}else if(cistrcmp(a, "NOT") == 0){
1807 		sr->key = SKNot;
1808 		mustBe(' ');
1809 		rock.next = nil;
1810 		searchKeys(0, &rock);
1811 		sr->left = rock.next;
1812 	}else if(cistrcmp(a, "OR") == 0){
1813 		sr->key = SKOr;
1814 		mustBe(' ');
1815 		rock.next = nil;
1816 		searchKeys(0, &rock);
1817 		sr->left = rock.next;
1818 		mustBe(' ');
1819 		rock.next = nil;
1820 		searchKeys(0, &rock);
1821 		sr->right = rock.next;
1822 	}else
1823 		parseErr("illegal search key");
1824 	return sr;
1825 }
1826 
1827 /*
1828  * set	: seqno
1829  *	| seqno ':' seqno
1830  *	| set ',' set
1831  * seqno: nz-number
1832  *	| '*'
1833  *
1834  */
1835 static MsgSet*
1836 msgSet(int uids)
1837 {
1838 	MsgSet head, *last, *ms;
1839 	ulong from, to;
1840 
1841 	last = &head;
1842 	head.next = nil;
1843 	for(;;){
1844 		from = uids ? uidNo() : seqNo();
1845 		to = from;
1846 		if(peekc() == ':'){
1847 			getc();
1848 			to = uids ? uidNo() : seqNo();
1849 		}
1850 		ms = binalloc(&parseBin, sizeof(MsgSet), 0);
1851 		if(ms == nil)
1852 			parseErr("out of memory");
1853 		ms->from = from;
1854 		ms->to = to;
1855 		ms->next = nil;
1856 		last->next = ms;
1857 		last = ms;
1858 		if(peekc() != ',')
1859 			break;
1860 		getc();
1861 	}
1862 	return head.next;
1863 }
1864 
1865 static ulong
1866 seqNo(void)
1867 {
1868 	if(peekc() == '*'){
1869 		getc();
1870 		return ~0UL;
1871 	}
1872 	return number(1);
1873 }
1874 
1875 static ulong
1876 uidNo(void)
1877 {
1878 	if(peekc() == '*'){
1879 		getc();
1880 		return ~0UL;
1881 	}
1882 	return number(0);
1883 }
1884 
1885 /*
1886  * 7 bit, non-ctl chars, no (){%*"\
1887  * NIL is special case for nstring or parenlist
1888  */
1889 static char *
1890 atom(void)
1891 {
1892 	return atomString(atomStop, "");
1893 }
1894 
1895 /*
1896  * like an atom, but no +
1897  */
1898 static char *
1899 tag(void)
1900 {
1901 	return atomString("+(){%*\"\\", "");
1902 }
1903 
1904 /*
1905  * string or atom allowing %*
1906  */
1907 static char *
1908 listmbox(void)
1909 {
1910 	int c;
1911 
1912 	c = peekc();
1913 	if(c == '{')
1914 		return literal();
1915 	if(c == '"')
1916 		return quoted();
1917 	return atomString("(){\"\\", "");
1918 }
1919 
1920 /*
1921  * string or atom
1922  */
1923 static char *
1924 astring(void)
1925 {
1926 	int c;
1927 
1928 	c = peekc();
1929 	if(c == '{')
1930 		return literal();
1931 	if(c == '"')
1932 		return quoted();
1933 	return atom();
1934 }
1935 
1936 /*
1937  * 7 bit, non-ctl chars, none from exception list
1938  */
1939 static char *
1940 atomString(char *disallowed, char *initial)
1941 {
1942 	char *s;
1943 	int c, ns, as;
1944 
1945 	ns = strlen(initial);
1946 	s = binalloc(&parseBin, ns + StrAlloc, 0);
1947 	if(s == nil)
1948 		parseErr("out of memory");
1949 	strcpy(s, initial);
1950 	as = ns + StrAlloc;
1951 	for(;;){
1952 		c = getc();
1953 		if(c <= ' ' || c >= 0x7f || strchr(disallowed, c) != nil){
1954 			ungetc();
1955 			break;
1956 		}
1957 		s[ns++] = c;
1958 		if(ns >= as){
1959 			s = bingrow(&parseBin, s, as, as + StrAlloc, 0);
1960 			if(s == nil)
1961 				parseErr("out of memory");
1962 			as += StrAlloc;
1963 		}
1964 	}
1965 	if(ns == 0)
1966 		badsyn();
1967 	s[ns] = '\0';
1968 	return s;
1969 }
1970 
1971 /*
1972  * quoted: '"' chars* '"'
1973  * chars:	1-128 except \r and \n
1974  */
1975 static char *
1976 quoted(void)
1977 {
1978 	char *s;
1979 	int c, ns, as;
1980 
1981 	mustBe('"');
1982 	s = binalloc(&parseBin, StrAlloc, 0);
1983 	if(s == nil)
1984 		parseErr("out of memory");
1985 	as = StrAlloc;
1986 	ns = 0;
1987 	for(;;){
1988 		c = getc();
1989 		if(c == '"')
1990 			break;
1991 		if(c < 1 || c > 0x7f || c == '\r' || c == '\n')
1992 			badsyn();
1993 		if(c == '\\'){
1994 			c = getc();
1995 			if(c != '\\' && c != '"')
1996 				badsyn();
1997 		}
1998 		s[ns++] = c;
1999 		if(ns >= as){
2000 			s = bingrow(&parseBin, s, as, as + StrAlloc, 0);
2001 			if(s == nil)
2002 				parseErr("out of memory");
2003 			as += StrAlloc;
2004 		}
2005 	}
2006 	s[ns] = '\0';
2007 	return s;
2008 }
2009 
2010 /*
2011  * litlen: {number}\r\n
2012  */
2013 static ulong
2014 litlen(void)
2015 {
2016 	ulong v;
2017 
2018 	mustBe('{');
2019 	v = number(0);
2020 	mustBe('}');
2021 	crnl();
2022 	return v;
2023 }
2024 
2025 /*
2026  * literal: litlen data<0:litlen>
2027  */
2028 static char *
2029 literal(void)
2030 {
2031 	char *s;
2032 	ulong v;
2033 
2034 	v = litlen();
2035 	s = binalloc(&parseBin, v+1, 0);
2036 	if(s == nil)
2037 		parseErr("out of memory");
2038 	Bprint(&bout, "+ Ready for literal data\r\n");
2039 	if(Bflush(&bout) < 0)
2040 		writeErr();
2041 	if(v != 0 && Bread(&bin, s, v) != v)
2042 		badsyn();
2043 	s[v] = '\0';
2044 	return s;
2045 }
2046 
2047 /*
2048  * digits; number is 32 bits
2049  */
2050 static ulong
2051 number(int nonzero)
2052 {
2053 	ulong v;
2054 	int c, first;
2055 
2056 	v = 0;
2057 	first = 1;
2058 	for(;;){
2059 		c = getc();
2060 		if(c < '0' || c > '9'){
2061 			ungetc();
2062 			if(first)
2063 				badsyn();
2064 			break;
2065 		}
2066 		if(nonzero && first && c == '0')
2067 			badsyn();
2068 		c -= '0';
2069 		first = 0;
2070 		if(v > UlongMax/10 || v == UlongMax/10 && c > UlongMax%10)
2071 			parseErr("number out of range\r\n");
2072 		v = v * 10 + c;
2073 	}
2074 	return v;
2075 }
2076 
2077 static int
2078 getc(void)
2079 {
2080 	return Bgetc(&bin);
2081 }
2082 
2083 static void
2084 ungetc(void)
2085 {
2086 	Bungetc(&bin);
2087 }
2088 
2089 static int
2090 peekc(void)
2091 {
2092 	int c;
2093 
2094 	c = Bgetc(&bin);
2095 	Bungetc(&bin);
2096 	return c;
2097 }
2098 
2099