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