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