xref: /plan9/sys/src/cmd/ip/imap4d/imap4d.c (revision 58da3067adcdccaaa043d0bfde28ba83b7ced07d)
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
main(int argc,char * argv[])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
imap4(int preauth)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
bye(char * fmt,...)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
parseErr(char * msg)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
writeErr(void)372 writeErr(void)
373 {
374 	cleaner();
375 	_exits("connection closed");
376 }
377 
378 static int
catcher(void * v,char * msg)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
cleaner(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
status(int expungeable,int uids)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
check(void)475 check(void)
476 {
477 	if(!selected)
478 		return;
479 	checkBox(selected, 0);
480 	status(1, 0);
481 }
482 
483 static void
appendCmd(char * tg,char * cmd)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
authenticateCmd(char * tg,char * cmd)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
capabilityCmd(char * tg,char * cmd)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
closeCmd(char * tg,char * cmd)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
copyCmd(char * tg,char * cmd)578 copyCmd(char *tg, char *cmd)
579 {
580 	copyUCmd(tg, cmd, 0);
581 }
582 
583 static void
copyUCmd(char * tg,char * cmd,int uids)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
createCmd(char * tg,char * cmd)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
deleteCmd(char * tg,char * cmd)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
expungeCmd(char * tg,char * cmd)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
fetchCmd(char * tg,char * cmd)703 fetchCmd(char *tg, char *cmd)
704 {
705 	fetchUCmd(tg, cmd, 0);
706 }
707 
708 static void
fetchUCmd(char * tg,char * cmd,int uids)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
idleCmd(char * tg,char * cmd)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
listCmd(char * tg,char * cmd)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*
passCR(char * u,char * p)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
loginCmd(char * tg,char * cmd)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
logoutCmd(char * tg,char * cmd)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
namespaceCmd(char * tg,char * cmd)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
noopCmd(char * tg,char * cmd)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
renameCmd(char * tg,char * cmd)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
searchCmd(char * tg,char * cmd)1056 searchCmd(char *tg, char *cmd)
1057 {
1058 	searchUCmd(tg, cmd, 0);
1059 }
1060 
1061 static void
searchUCmd(char * tg,char * cmd,int uids)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(cistrcmp(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",
1080 				tg, uid, cmd);
1081 			checkBox(selected, 0);
1082 			status(uids, uids);
1083 			return;
1084 		}
1085 		rock.next = rock.next->next;
1086 	}
1087 	Bprint(&bout, "* search");
1088 	for(m = selected->msgs; m != nil; m = m->next)
1089 		m->matched = searchMsg(m, rock.next);
1090 	for(m = selected->msgs; m != nil; m = m->next){
1091 		if(m->matched){
1092 			if(uids)
1093 				id = m->uid;
1094 			else
1095 				id = m->seq;
1096 			Bprint(&bout, " %lud", id);
1097 		}
1098 	}
1099 	Bprint(&bout, "\r\n");
1100 	checkBox(selected, 0);
1101 	status(uids, uids);
1102 	Bprint(&bout, "%s OK %s%s completed\r\n", tg, uid, cmd);
1103 }
1104 
1105 static void
selectCmd(char * tg,char * cmd)1106 selectCmd(char *tg, char *cmd)
1107 {
1108 	Msg *m;
1109 	char *s, *mbox;
1110 
1111 	mustBe(' ');
1112 	mbox = astring();
1113 	crnl();
1114 
1115 	if(selected){
1116 		imapState = SAuthed;
1117 		closeBox(selected, 1);
1118 		selected = nil;
1119 	}
1120 
1121 	mbox = mboxName(mbox);
1122 	if(mbox == nil || !okMbox(mbox)){
1123 		Bprint(&bout, "%s NO %s bad mailbox\r\n", tg, cmd);
1124 		return;
1125 	}
1126 
1127 	selected = openBox(mbox, "imap", cistrcmp(cmd, "select") == 0);
1128 	if(selected == nil){
1129 		Bprint(&bout, "%s NO %s can't open mailbox %s: %r\r\n", tg, cmd, mbox);
1130 		return;
1131 	}
1132 
1133 	imapState = SSelected;
1134 
1135 	Bprint(&bout, "* FLAGS (\\Seen \\Answered \\Flagged \\Deleted \\Draft)\r\n");
1136 	Bprint(&bout, "* %lud EXISTS\r\n", selected->max);
1137 	selected->toldMax = selected->max;
1138 	Bprint(&bout, "* %lud RECENT\r\n", selected->recent);
1139 	selected->toldRecent = selected->recent;
1140 	for(m = selected->msgs; m != nil; m = m->next){
1141 		if(!m->expunged && (m->flags & MSeen) != MSeen){
1142 			Bprint(&bout, "* OK [UNSEEN %ld]\r\n", m->seq);
1143 			break;
1144 		}
1145 	}
1146 	Bprint(&bout, "* OK [PERMANENTFLAGS (\\Seen \\Answered \\Flagged \\Draft \\Deleted)]\r\n");
1147 	Bprint(&bout, "* OK [UIDNEXT %ld]\r\n", selected->uidnext);
1148 	Bprint(&bout, "* OK [UIDVALIDITY %ld]\r\n", selected->uidvalidity);
1149 	s = "READ-ONLY";
1150 	if(selected->writable)
1151 		s = "READ-WRITE";
1152 	Bprint(&bout, "%s OK [%s] %s %s completed\r\n", tg, s, cmd, mbox);
1153 }
1154 
1155 static NamedInt	statusItems[] =
1156 {
1157 	{"MESSAGES",	SMessages},
1158 	{"RECENT",	SRecent},
1159 	{"UIDNEXT",	SUidNext},
1160 	{"UIDVALIDITY",	SUidValidity},
1161 	{"UNSEEN",	SUnseen},
1162 	{nil,		0}
1163 };
1164 
1165 static void
statusCmd(char * tg,char * cmd)1166 statusCmd(char *tg, char *cmd)
1167 {
1168 	Box *box;
1169 	Msg *m;
1170 	char *s, *mbox;
1171 	ulong v;
1172 	int si, i;
1173 
1174 	mustBe(' ');
1175 	mbox = astring();
1176 	mustBe(' ');
1177 	mustBe('(');
1178 	si = 0;
1179 	for(;;){
1180 		s = atom();
1181 		i = mapInt(statusItems, s);
1182 		if(i == 0)
1183 			parseErr("illegal status item");
1184 		si |= i;
1185 		if(peekc() == ')')
1186 			break;
1187 		mustBe(' ');
1188 	}
1189 	mustBe(')');
1190 	crnl();
1191 
1192 	mbox = mboxName(mbox);
1193 	if(mbox == nil || !okMbox(mbox)){
1194 		check();
1195 		Bprint(&bout, "%s NO %s bad mailbox\r\n", tg, cmd);
1196 		return;
1197 	}
1198 
1199 	box = openBox(mbox, "status", 1);
1200 	if(box == nil){
1201 		check();
1202 		Bprint(&bout, "%s NO [TRYCREATE] %s can't open mailbox %s: %r\r\n", tg, cmd, mbox);
1203 		return;
1204 	}
1205 
1206 	Bprint(&bout, "* STATUS %s (", mbox);
1207 	s = "";
1208 	for(i = 0; statusItems[i].name != nil; i++){
1209 		if(si & statusItems[i].v){
1210 			v = 0;
1211 			switch(statusItems[i].v){
1212 			case SMessages:
1213 				v = box->max;
1214 				break;
1215 			case SRecent:
1216 				v = box->recent;
1217 				break;
1218 			case SUidNext:
1219 				v = box->uidnext;
1220 				break;
1221 			case SUidValidity:
1222 				v = box->uidvalidity;
1223 				break;
1224 			case SUnseen:
1225 				v = 0;
1226 				for(m = box->msgs; m != nil; m = m->next)
1227 					if((m->flags & MSeen) != MSeen)
1228 						v++;
1229 				break;
1230 			default:
1231 				Bprint(&bout, ")");
1232 				bye("internal error: status item not implemented");
1233 				break;
1234 			}
1235 			Bprint(&bout, "%s%s %lud", s, statusItems[i].name, v);
1236 			s = " ";
1237 		}
1238 	}
1239 	Bprint(&bout, ")\r\n");
1240 	closeBox(box, 1);
1241 
1242 	check();
1243 	Bprint(&bout, "%s OK %s completed\r\n", tg, cmd);
1244 }
1245 
1246 static void
storeCmd(char * tg,char * cmd)1247 storeCmd(char *tg, char *cmd)
1248 {
1249 	storeUCmd(tg, cmd, 0);
1250 }
1251 
1252 static void
storeUCmd(char * tg,char * cmd,int uids)1253 storeUCmd(char *tg, char *cmd, int uids)
1254 {
1255 	Store *st;
1256 	MsgSet *ms;
1257 	MbLock *ml;
1258 	char *uid;
1259 	ulong max;
1260 	int ok;
1261 
1262 	mustBe(' ');
1263 	ms = msgSet(uids);
1264 	mustBe(' ');
1265 	st = storeWhat();
1266 	crnl();
1267 	uid = "";
1268 	if(uids)
1269 		uid = "uid ";
1270 	max = selected->max;
1271 	ml = checkBox(selected, 1);
1272 	ok = ml != nil && forMsgs(selected, ms, max, uids, storeMsg, st);
1273 	closeImp(selected, ml);
1274 	status(uids, uids);
1275 	if(ok)
1276 		Bprint(&bout, "%s OK %s%s completed\r\n", tg, uid, cmd);
1277 	else
1278 		Bprint(&bout, "%s NO %s%s failed\r\n", tg, uid, cmd);
1279 }
1280 
1281 /*
1282  * minimal implementation of subscribe
1283  * all folders are automatically subscribed,
1284  * and can't be unsubscribed
1285  */
1286 static void
subscribeCmd(char * tg,char * cmd)1287 subscribeCmd(char *tg, char *cmd)
1288 {
1289 	Box *box;
1290 	char *mbox;
1291 	int ok;
1292 
1293 	mustBe(' ');
1294 	mbox = astring();
1295 	crnl();
1296 	check();
1297 	mbox = mboxName(mbox);
1298 	ok = 0;
1299 	if(mbox != nil && okMbox(mbox)){
1300 		box = openBox(mbox, "subscribe", 0);
1301 		if(box != nil){
1302 			ok = subscribe(mbox, 's');
1303 			closeBox(box, 1);
1304 		}
1305 	}
1306 	if(!ok)
1307 		Bprint(&bout, "%s NO %s bad mailbox\r\n", tg, cmd);
1308 	else
1309 		Bprint(&bout, "%s OK %s completed\r\n", tg, cmd);
1310 }
1311 
1312 static void
uidCmd(char * tg,char * cmd)1313 uidCmd(char *tg, char *cmd)
1314 {
1315 	char *sub;
1316 
1317 	mustBe(' ');
1318 	sub = atom();
1319 	if(cistrcmp(sub, "copy") == 0)
1320 		copyUCmd(tg, sub, 1);
1321 	else if(cistrcmp(sub, "fetch") == 0)
1322 		fetchUCmd(tg, sub, 1);
1323 	else if(cistrcmp(sub, "search") == 0)
1324 		searchUCmd(tg, sub, 1);
1325 	else if(cistrcmp(sub, "store") == 0)
1326 		storeUCmd(tg, sub, 1);
1327 	else{
1328 		clearcmd();
1329 		Bprint(&bout, "%s BAD %s illegal uid command %s\r\n", tg, cmd, sub);
1330 	}
1331 }
1332 
1333 static void
unsubscribeCmd(char * tg,char * cmd)1334 unsubscribeCmd(char *tg, char *cmd)
1335 {
1336 	char *mbox;
1337 
1338 	mustBe(' ');
1339 	mbox = astring();
1340 	crnl();
1341 	check();
1342 	mbox = mboxName(mbox);
1343 	if(mbox == nil || !okMbox(mbox) || !subscribe(mbox, 'u'))
1344 		Bprint(&bout, "%s NO %s can't unsubscribe\r\n", tg, cmd);
1345 	else
1346 		Bprint(&bout, "%s OK %s completed\r\n", tg, cmd);
1347 }
1348 
1349 static void
badsyn(void)1350 badsyn(void)
1351 {
1352 	parseErr("bad syntax");
1353 }
1354 
1355 static void
clearcmd(void)1356 clearcmd(void)
1357 {
1358 	int c;
1359 
1360 	for(;;){
1361 		c = getc();
1362 		if(c < 0)
1363 			bye("end of input");
1364 		if(c == '\n')
1365 			return;
1366 	}
1367 }
1368 
1369 static void
crnl(void)1370 crnl(void)
1371 {
1372 	int c;
1373 
1374 	c = getc();
1375 	if(c == '\n')
1376 		return;
1377 	if(c != '\r' || getc() != '\n')
1378 		badsyn();
1379 }
1380 
1381 static void
mustBe(int c)1382 mustBe(int c)
1383 {
1384 	if(getc() != c){
1385 		ungetc();
1386 		badsyn();
1387 	}
1388 }
1389 
1390 /*
1391  * flaglist	: '(' ')' | '(' flags ')'
1392  */
1393 static int
flagList(void)1394 flagList(void)
1395 {
1396 	int f;
1397 
1398 	mustBe('(');
1399 	f = 0;
1400 	if(peekc() != ')')
1401 		f = flags();
1402 
1403 	mustBe(')');
1404 	return f;
1405 }
1406 
1407 /*
1408  * flags	: flag | flags ' ' flag
1409  * flag		: '\' atom | atom
1410  */
1411 static int
flags(void)1412 flags(void)
1413 {
1414 	int ff, flags;
1415 	char *s;
1416 	int c;
1417 
1418 	flags = 0;
1419 	for(;;){
1420 		c = peekc();
1421 		if(c == '\\'){
1422 			mustBe('\\');
1423 			s = atomString(atomStop, "\\");
1424 		}else if(strchr(atomStop, c) != nil)
1425 			s = atom();
1426 		else
1427 			break;
1428 		ff = mapFlag(s);
1429 		if(ff == 0)
1430 			parseErr("flag not supported");
1431 		flags |= ff;
1432 		if(peekc() != ' ')
1433 			break;
1434 		mustBe(' ');
1435 	}
1436 	if(flags == 0)
1437 		parseErr("no flags given");
1438 	return flags;
1439 }
1440 
1441 /*
1442  * storeWhat	: osign 'FLAGS' ' ' storeflags
1443  *		| osign 'FLAGS.SILENT' ' ' storeflags
1444  * osign	:
1445  *		| '+' | '-'
1446  * storeflags	: flagList | flags
1447  */
1448 static Store*
storeWhat(void)1449 storeWhat(void)
1450 {
1451 	int f;
1452 	char *s;
1453 	int c, w;
1454 
1455 	c = peekc();
1456 	if(c == '+' || c == '-')
1457 		mustBe(c);
1458 	else
1459 		c = 0;
1460 	s = atom();
1461 	w = 0;
1462 	if(cistrcmp(s, "flags") == 0)
1463 		w = STFlags;
1464 	else if(cistrcmp(s, "flags.silent") == 0)
1465 		w = STFlagsSilent;
1466 	else
1467 		parseErr("illegal store attribute");
1468 	mustBe(' ');
1469 	if(peekc() == '(')
1470 		f = flagList();
1471 	else
1472 		f = flags();
1473 	return mkStore(c, w, f);
1474 }
1475 
1476 /*
1477  * fetchWhat	: "ALL" | "FULL" | "FAST" | fetchAtt | '(' fetchAtts ')'
1478  * fetchAtts	: fetchAtt | fetchAtts ' ' fetchAtt
1479  */
1480 static char *fetchAtom	= "(){}%*\"\\[]";
1481 static Fetch*
fetchWhat(void)1482 fetchWhat(void)
1483 {
1484 	Fetch *f;
1485 	char *s;
1486 
1487 	if(peekc() == '('){
1488 		getc();
1489 		f = nil;
1490 		for(;;){
1491 			s = atomString(fetchAtom, "");
1492 			f = fetchAtt(s, f);
1493 			if(peekc() == ')')
1494 				break;
1495 			mustBe(' ');
1496 		}
1497 		getc();
1498 		return revFetch(f);
1499 	}
1500 
1501 	s = atomString(fetchAtom, "");
1502 	if(cistrcmp(s, "all") == 0)
1503 		f = mkFetch(FFlags, mkFetch(FInternalDate, mkFetch(FRfc822Size, mkFetch(FEnvelope, nil))));
1504 	else if(cistrcmp(s, "fast") == 0)
1505 		f = mkFetch(FFlags, mkFetch(FInternalDate, mkFetch(FRfc822Size, nil)));
1506 	else if(cistrcmp(s, "full") == 0)
1507 		f = mkFetch(FFlags, mkFetch(FInternalDate, mkFetch(FRfc822Size, mkFetch(FEnvelope, mkFetch(FBody, nil)))));
1508 	else
1509 		f = fetchAtt(s, nil);
1510 	return f;
1511 }
1512 
1513 /*
1514  * fetchAtt	: "ENVELOPE" | "FLAGS" | "INTERNALDATE"
1515  *		| "RFC822" | "RFC822.HEADER" | "RFC822.SIZE" | "RFC822.TEXT"
1516  *		| "BODYSTRUCTURE"
1517  *		| "UID"
1518  *		| "BODY"
1519  *		| "BODY" bodysubs
1520  *		| "BODY.PEEK" bodysubs
1521  * bodysubs	: sect
1522  *		| sect '<' number '.' nz-number '>'
1523  * sect		: '[' sectSpec ']'
1524  * sectSpec	: sectMsgText
1525  *		| sectPart
1526  *		| sectPart '.' sectText
1527  * sectPart	: nz-number
1528  *		| sectPart '.' nz-number
1529  */
1530 static Fetch*
fetchAtt(char * s,Fetch * f)1531 fetchAtt(char *s, Fetch *f)
1532 {
1533 	NList *sect;
1534 	int c;
1535 
1536 	if(cistrcmp(s, "envelope") == 0)
1537 		return mkFetch(FEnvelope, f);
1538 	if(cistrcmp(s, "flags") == 0)
1539 		return mkFetch(FFlags, f);
1540 	if(cistrcmp(s, "internaldate") == 0)
1541 		return mkFetch(FInternalDate, f);
1542 	if(cistrcmp(s, "RFC822") == 0)
1543 		return mkFetch(FRfc822, f);
1544 	if(cistrcmp(s, "RFC822.header") == 0)
1545 		return mkFetch(FRfc822Head, f);
1546 	if(cistrcmp(s, "RFC822.size") == 0)
1547 		return mkFetch(FRfc822Size, f);
1548 	if(cistrcmp(s, "RFC822.text") == 0)
1549 		return mkFetch(FRfc822Text, f);
1550 	if(cistrcmp(s, "bodystructure") == 0)
1551 		return mkFetch(FBodyStruct, f);
1552 	if(cistrcmp(s, "uid") == 0)
1553 		return mkFetch(FUid, f);
1554 
1555 	if(cistrcmp(s, "body") == 0){
1556 		if(peekc() != '[')
1557 			return mkFetch(FBody, f);
1558 		f = mkFetch(FBodySect, f);
1559 	}else if(cistrcmp(s, "body.peek") == 0)
1560 		f = mkFetch(FBodyPeek, f);
1561 	else
1562 		parseErr("illegal fetch attribute");
1563 
1564 	mustBe('[');
1565 	c = peekc();
1566 	if(c >= '1' && c <= '9'){
1567 		sect = mkNList(number(1), nil);
1568 		while(peekc() == '.'){
1569 			getc();
1570 			c = peekc();
1571 			if(c >= '1' && c <= '9'){
1572 				sect = mkNList(number(1), sect);
1573 			}else{
1574 				break;
1575 			}
1576 		}
1577 		f->sect = revNList(sect);
1578 	}
1579 	if(peekc() != ']')
1580 		sectText(f, f->sect != nil);
1581 	mustBe(']');
1582 
1583 	if(peekc() != '<')
1584 		return f;
1585 
1586 	f->partial = 1;
1587 	mustBe('<');
1588 	f->start = number(0);
1589 	mustBe('.');
1590 	f->size = number(1);
1591 	mustBe('>');
1592 	return f;
1593 }
1594 
1595 /*
1596  * sectText	: sectMsgText | "MIME"
1597  * sectMsgText	: "HEADER"
1598  *		| "TEXT"
1599  *		| "HEADER.FIELDS" ' ' hdrList
1600  *		| "HEADER.FIELDS.NOT" ' ' hdrList
1601  * hdrList	: '(' hdrs ')'
1602  * hdrs:	: astring
1603  *		| hdrs ' ' astring
1604  */
1605 static void
sectText(Fetch * f,int mimeOk)1606 sectText(Fetch *f, int mimeOk)
1607 {
1608 	SList *h;
1609 	char *s;
1610 
1611 	s = atomString(fetchAtom, "");
1612 	if(cistrcmp(s, "header") == 0){
1613 		f->part = FPHead;
1614 		return;
1615 	}
1616 	if(cistrcmp(s, "text") == 0){
1617 		f->part = FPText;
1618 		return;
1619 	}
1620 	if(mimeOk && cistrcmp(s, "mime") == 0){
1621 		f->part = FPMime;
1622 		return;
1623 	}
1624 	if(cistrcmp(s, "header.fields") == 0)
1625 		f->part = FPHeadFields;
1626 	else if(cistrcmp(s, "header.fields.not") == 0)
1627 		f->part = FPHeadFieldsNot;
1628 	else
1629 		parseErr("illegal fetch section text");
1630 	mustBe(' ');
1631 	mustBe('(');
1632 	h = nil;
1633 	for(;;){
1634 		h = mkSList(astring(), h);
1635 		if(peekc() == ')')
1636 			break;
1637 		mustBe(' ');
1638 	}
1639 	mustBe(')');
1640 	f->hdrs = revSList(h);
1641 }
1642 
1643 /*
1644  * searchWhat	: "CHARSET" ' ' astring searchkeys | searchkeys
1645  * searchkeys	: searchkey | searchkeys ' ' searchkey
1646  * searchkey	: "ALL" | "ANSWERED" | "DELETED" | "FLAGGED" | "NEW" | "OLD" | "RECENT"
1647  *		| "SEEN" | "UNANSWERED" | "UNDELETED" | "UNFLAGGED" | "DRAFT" | "UNDRAFT"
1648  *		| astrkey ' ' astring
1649  *		| datekey ' ' date
1650  *		| "KEYWORD" ' ' flag | "UNKEYWORD" flag
1651  *		| "LARGER" ' ' number | "SMALLER" ' ' number
1652  * 		| "HEADER" astring ' ' astring
1653  *		| set | "UID" ' ' set
1654  *		| "NOT" ' ' searchkey
1655  *		| "OR" ' ' searchkey ' ' searchkey
1656  *		| '(' searchkeys ')'
1657  * astrkey	: "BCC" | "BODY" | "CC" | "FROM" | "SUBJECT" | "TEXT" | "TO"
1658  * datekey	: "BEFORE" | "ON" | "SINCE" | "SENTBEFORE" | "SENTON" | "SENTSINCE"
1659  */
1660 static NamedInt searchMap[] =
1661 {
1662 	{"ALL",		SKAll},
1663 	{"ANSWERED",	SKAnswered},
1664 	{"DELETED",	SKDeleted},
1665 	{"FLAGGED",	SKFlagged},
1666 	{"NEW",		SKNew},
1667 	{"OLD",		SKOld},
1668 	{"RECENT",	SKRecent},
1669 	{"SEEN",	SKSeen},
1670 	{"UNANSWERED",	SKUnanswered},
1671 	{"UNDELETED",	SKUndeleted},
1672 	{"UNFLAGGED",	SKUnflagged},
1673 	{"DRAFT",	SKDraft},
1674 	{"UNDRAFT",	SKUndraft},
1675 	{"UNSEEN",	SKUnseen},
1676 	{nil,		0}
1677 };
1678 
1679 static NamedInt searchMapStr[] =
1680 {
1681 	{"CHARSET",	SKCharset},
1682 	{"BCC",		SKBcc},
1683 	{"BODY",	SKBody},
1684 	{"CC",		SKCc},
1685 	{"FROM",	SKFrom},
1686 	{"SUBJECT",	SKSubject},
1687 	{"TEXT",	SKText},
1688 	{"TO",		SKTo},
1689 	{nil,		0}
1690 };
1691 
1692 static NamedInt searchMapDate[] =
1693 {
1694 	{"BEFORE",	SKBefore},
1695 	{"ON",		SKOn},
1696 	{"SINCE",	SKSince},
1697 	{"SENTBEFORE",	SKSentBefore},
1698 	{"SENTON",	SKSentOn},
1699 	{"SENTSINCE",	SKSentSince},
1700 	{nil,		0}
1701 };
1702 
1703 static NamedInt searchMapFlag[] =
1704 {
1705 	{"KEYWORD",	SKKeyword},
1706 	{"UNKEYWORD",	SKUnkeyword},
1707 	{nil,		0}
1708 };
1709 
1710 static NamedInt searchMapNum[] =
1711 {
1712 	{"SMALLER",	SKSmaller},
1713 	{"LARGER",	SKLarger},
1714 	{nil,		0}
1715 };
1716 
1717 static Search*
searchKeys(int first,Search * tail)1718 searchKeys(int first, Search *tail)
1719 {
1720 	Search *s;
1721 
1722 	for(;;){
1723 		if(peekc() == '('){
1724 			getc();
1725 			tail = searchKeys(0, tail);
1726 			mustBe(')');
1727 		}else{
1728 			s = searchKey(first);
1729 			tail->next = s;
1730 			tail = s;
1731 		}
1732 		first = 0;
1733 		if(peekc() != ' ')
1734 			break;
1735 		getc();
1736 	}
1737 	return tail;
1738 }
1739 
1740 static Search*
searchKey(int first)1741 searchKey(int first)
1742 {
1743 	Search *sr, rock;
1744 	Tm tm;
1745 	char *a;
1746 	int i, c;
1747 
1748 	sr = binalloc(&parseBin, sizeof(Search), 1);
1749 	if(sr == nil)
1750 		parseErr("out of memory");
1751 
1752 	c = peekc();
1753 	if(c >= '0' && c <= '9'){
1754 		sr->key = SKSet;
1755 		sr->set = msgSet(0);
1756 		return sr;
1757 	}
1758 
1759 	a = atom();
1760 	if(i = mapInt(searchMap, a))
1761 		sr->key = i;
1762 	else if(i = mapInt(searchMapStr, a)){
1763 		if(!first && i == SKCharset)
1764 			parseErr("illegal search key");
1765 		sr->key = i;
1766 		mustBe(' ');
1767 		sr->s = astring();
1768 	}else if(i = mapInt(searchMapDate, a)){
1769 		sr->key = i;
1770 		mustBe(' ');
1771 		c = peekc();
1772 		if(c == '"')
1773 			getc();
1774 		a = atom();
1775 		if(!imap4Date(&tm, a))
1776 			parseErr("bad date format");
1777 		sr->year = tm.year;
1778 		sr->mon = tm.mon;
1779 		sr->mday = tm.mday;
1780 		if(c == '"')
1781 			mustBe('"');
1782 	}else if(i = mapInt(searchMapFlag, a)){
1783 		sr->key = i;
1784 		mustBe(' ');
1785 		c = peekc();
1786 		if(c == '\\'){
1787 			mustBe('\\');
1788 			a = atomString(atomStop, "\\");
1789 		}else
1790 			a = atom();
1791 		i = mapFlag(a);
1792 		if(i == 0)
1793 			parseErr("flag not supported");
1794 		sr->num = i;
1795 	}else if(i = mapInt(searchMapNum, a)){
1796 		sr->key = i;
1797 		mustBe(' ');
1798 		sr->num = number(0);
1799 	}else if(cistrcmp(a, "HEADER") == 0){
1800 		sr->key = SKHeader;
1801 		mustBe(' ');
1802 		sr->hdr = astring();
1803 		mustBe(' ');
1804 		sr->s = astring();
1805 	}else if(cistrcmp(a, "UID") == 0){
1806 		sr->key = SKUid;
1807 		mustBe(' ');
1808 		sr->set = msgSet(0);
1809 	}else if(cistrcmp(a, "NOT") == 0){
1810 		sr->key = SKNot;
1811 		mustBe(' ');
1812 		rock.next = nil;
1813 		searchKeys(0, &rock);
1814 		sr->left = rock.next;
1815 	}else if(cistrcmp(a, "OR") == 0){
1816 		sr->key = SKOr;
1817 		mustBe(' ');
1818 		rock.next = nil;
1819 		searchKeys(0, &rock);
1820 		sr->left = rock.next;
1821 		mustBe(' ');
1822 		rock.next = nil;
1823 		searchKeys(0, &rock);
1824 		sr->right = rock.next;
1825 	}else
1826 		parseErr("illegal search key");
1827 	return sr;
1828 }
1829 
1830 /*
1831  * set	: seqno
1832  *	| seqno ':' seqno
1833  *	| set ',' set
1834  * seqno: nz-number
1835  *	| '*'
1836  *
1837  */
1838 static MsgSet*
msgSet(int uids)1839 msgSet(int uids)
1840 {
1841 	MsgSet head, *last, *ms;
1842 	ulong from, to;
1843 
1844 	last = &head;
1845 	head.next = nil;
1846 	for(;;){
1847 		from = uids ? uidNo() : seqNo();
1848 		to = from;
1849 		if(peekc() == ':'){
1850 			getc();
1851 			to = uids ? uidNo() : seqNo();
1852 		}
1853 		ms = binalloc(&parseBin, sizeof(MsgSet), 0);
1854 		if(ms == nil)
1855 			parseErr("out of memory");
1856 		ms->from = from;
1857 		ms->to = to;
1858 		ms->next = nil;
1859 		last->next = ms;
1860 		last = ms;
1861 		if(peekc() != ',')
1862 			break;
1863 		getc();
1864 	}
1865 	return head.next;
1866 }
1867 
1868 static ulong
seqNo(void)1869 seqNo(void)
1870 {
1871 	if(peekc() == '*'){
1872 		getc();
1873 		return ~0UL;
1874 	}
1875 	return number(1);
1876 }
1877 
1878 static ulong
uidNo(void)1879 uidNo(void)
1880 {
1881 	if(peekc() == '*'){
1882 		getc();
1883 		return ~0UL;
1884 	}
1885 	return number(0);
1886 }
1887 
1888 /*
1889  * 7 bit, non-ctl chars, no (){%*"\
1890  * NIL is special case for nstring or parenlist
1891  */
1892 static char *
atom(void)1893 atom(void)
1894 {
1895 	return atomString(atomStop, "");
1896 }
1897 
1898 /*
1899  * like an atom, but no +
1900  */
1901 static char *
tag(void)1902 tag(void)
1903 {
1904 	return atomString("+(){%*\"\\", "");
1905 }
1906 
1907 /*
1908  * string or atom allowing %*
1909  */
1910 static char *
listmbox(void)1911 listmbox(void)
1912 {
1913 	int c;
1914 
1915 	c = peekc();
1916 	if(c == '{')
1917 		return literal();
1918 	if(c == '"')
1919 		return quoted();
1920 	return atomString("(){\"\\", "");
1921 }
1922 
1923 /*
1924  * string or atom
1925  */
1926 static char *
astring(void)1927 astring(void)
1928 {
1929 	int c;
1930 
1931 	c = peekc();
1932 	if(c == '{')
1933 		return literal();
1934 	if(c == '"')
1935 		return quoted();
1936 	return atom();
1937 }
1938 
1939 /*
1940  * 7 bit, non-ctl chars, none from exception list
1941  */
1942 static char *
atomString(char * disallowed,char * initial)1943 atomString(char *disallowed, char *initial)
1944 {
1945 	char *s;
1946 	int c, ns, as;
1947 
1948 	ns = strlen(initial);
1949 	s = binalloc(&parseBin, ns + StrAlloc, 0);
1950 	if(s == nil)
1951 		parseErr("out of memory");
1952 	strcpy(s, initial);
1953 	as = ns + StrAlloc;
1954 	for(;;){
1955 		c = getc();
1956 		if(c <= ' ' || c >= 0x7f || strchr(disallowed, c) != nil){
1957 			ungetc();
1958 			break;
1959 		}
1960 		s[ns++] = c;
1961 		if(ns >= as){
1962 			s = bingrow(&parseBin, s, as, as + StrAlloc, 0);
1963 			if(s == nil)
1964 				parseErr("out of memory");
1965 			as += StrAlloc;
1966 		}
1967 	}
1968 	if(ns == 0)
1969 		badsyn();
1970 	s[ns] = '\0';
1971 	return s;
1972 }
1973 
1974 /*
1975  * quoted: '"' chars* '"'
1976  * chars:	1-128 except \r and \n
1977  */
1978 static char *
quoted(void)1979 quoted(void)
1980 {
1981 	char *s;
1982 	int c, ns, as;
1983 
1984 	mustBe('"');
1985 	s = binalloc(&parseBin, StrAlloc, 0);
1986 	if(s == nil)
1987 		parseErr("out of memory");
1988 	as = StrAlloc;
1989 	ns = 0;
1990 	for(;;){
1991 		c = getc();
1992 		if(c == '"')
1993 			break;
1994 		if(c < 1 || c > 0x7f || c == '\r' || c == '\n')
1995 			badsyn();
1996 		if(c == '\\'){
1997 			c = getc();
1998 			if(c != '\\' && c != '"')
1999 				badsyn();
2000 		}
2001 		s[ns++] = c;
2002 		if(ns >= as){
2003 			s = bingrow(&parseBin, s, as, as + StrAlloc, 0);
2004 			if(s == nil)
2005 				parseErr("out of memory");
2006 			as += StrAlloc;
2007 		}
2008 	}
2009 	s[ns] = '\0';
2010 	return s;
2011 }
2012 
2013 /*
2014  * litlen: {number}\r\n
2015  */
2016 static ulong
litlen(void)2017 litlen(void)
2018 {
2019 	ulong v;
2020 
2021 	mustBe('{');
2022 	v = number(0);
2023 	mustBe('}');
2024 	crnl();
2025 	return v;
2026 }
2027 
2028 /*
2029  * literal: litlen data<0:litlen>
2030  */
2031 static char *
literal(void)2032 literal(void)
2033 {
2034 	char *s;
2035 	ulong v;
2036 
2037 	v = litlen();
2038 	s = binalloc(&parseBin, v+1, 0);
2039 	if(s == nil)
2040 		parseErr("out of memory");
2041 	Bprint(&bout, "+ Ready for literal data\r\n");
2042 	if(Bflush(&bout) < 0)
2043 		writeErr();
2044 	if(v != 0 && Bread(&bin, s, v) != v)
2045 		badsyn();
2046 	s[v] = '\0';
2047 	return s;
2048 }
2049 
2050 /*
2051  * digits; number is 32 bits
2052  */
2053 static ulong
number(int nonzero)2054 number(int nonzero)
2055 {
2056 	ulong v;
2057 	int c, first;
2058 
2059 	v = 0;
2060 	first = 1;
2061 	for(;;){
2062 		c = getc();
2063 		if(c < '0' || c > '9'){
2064 			ungetc();
2065 			if(first)
2066 				badsyn();
2067 			break;
2068 		}
2069 		if(nonzero && first && c == '0')
2070 			badsyn();
2071 		c -= '0';
2072 		first = 0;
2073 		if(v > UlongMax/10 || v == UlongMax/10 && c > UlongMax%10)
2074 			parseErr("number out of range\r\n");
2075 		v = v * 10 + c;
2076 	}
2077 	return v;
2078 }
2079 
2080 static int
getc(void)2081 getc(void)
2082 {
2083 	return Bgetc(&bin);
2084 }
2085 
2086 static void
ungetc(void)2087 ungetc(void)
2088 {
2089 	Bungetc(&bin);
2090 }
2091 
2092 static int
peekc(void)2093 peekc(void)
2094 {
2095 	int c;
2096 
2097 	c = Bgetc(&bin);
2098 	Bungetc(&bin);
2099 	return c;
2100 }
2101 
2102