8 * these should be in libraries
10 char *csquery(char *attr, char *val, char *rattr);
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
25 * outlook uses "Secure Password Authentication" aka ntlm authentication
27 * capabilities from nslocum
28 * CAPABILITY IMAP4 IMAP4REV1 NAMESPACE IDLE SCAN SORT MAILBOX-REFERRALS LOGIN-REFERRALS AUTH=LOGIN THREAD=ORDEREDSUBJECT
31 typedef struct ParseCmd ParseCmd;
35 UlongMax = 4294967295,
41 void (*f)(char *tg, char *cmd);
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);
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);
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*);
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);
108 static ParseCmd SNonAuthed[] =
110 {"capability", capabilityCmd},
111 {"logout", logoutCmd},
112 {"x-exit", logoutCmd},
116 {"authenticate", authenticateCmd},
121 static ParseCmd SAuthed[] =
123 {"capability", capabilityCmd},
124 {"logout", logoutCmd},
125 {"x-exit", logoutCmd},
128 {"append", appendCmd},
129 {"create", createCmd},
130 {"delete", deleteCmd},
131 {"examine", selectCmd},
132 {"select", selectCmd},
136 {"namespace", namespaceCmd},
137 {"rename", renameCmd},
138 {"status", statusCmd},
139 {"subscribe", subscribeCmd},
140 {"unsubscribe", unsubscribeCmd},
145 static ParseCmd SSelected[] =
147 {"capability", capabilityCmd},
148 {"logout", logoutCmd},
149 {"x-exit", logoutCmd},
152 {"append", appendCmd},
153 {"create", createCmd},
154 {"delete", deleteCmd},
155 {"examine", selectCmd},
156 {"select", selectCmd},
160 {"namespace", namespaceCmd},
161 {"rename", renameCmd},
162 {"status", statusCmd},
163 {"subscribe", subscribeCmd},
164 {"unsubscribe", unsubscribeCmd},
169 {"expunge", expungeCmd},
171 {"search", searchCmd},
178 static char *atomStop = "(){%*\"\\";
179 static Chalstate *chal;
181 static ParseCmd *imapState;
182 static jmp_buf parseJmp;
183 static char *parseMsg;
184 static int allowPass;
187 static QLock imaplock;
188 static int idlepid = -1;
192 char username[UserNameLen];
193 char mboxDir[MboxNameLen];
202 main(int argc, char *argv[])
207 Binit(&bin, 0, OREAD);
208 Binit(&bout, 1, OWRITE);
234 debuglog("imap4d debugging enabled\n");
237 fprint(2, "usage: ip/imap4d [-acpv] [-d site] [-r remotehost] [-s servername]\n");
242 if(allowPass && allowCR){
243 fprint(2, "%s: -c and -p are mutually exclusive\n", argv0);
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");
265 s = strchr(servername, '.');
272 snprint(site, n, "%s.%s", t, s);
276 rfork(RFNOTEG|RFREND);
278 atnotify(catcher, 1);
292 Bprint(&bout, "* preauth %s IMAP4rev1 server ready user %s authenticated\r\n", servername, username);
295 Bprint(&bout, "* OK %s IMAP4rev1 server ready\r\n", servername);
296 imapState = SNonAuthed;
298 if(Bflush(&bout) < 0)
305 if(setjmp(parseJmp)){
307 Bprint(&bout, "* bad empty command line: %s\r\n", parseMsg);
309 Bprint(&bout, "%s BAD no command: %s\r\n", tg, parseMsg);
311 Bprint(&bout, "%s BAD %s %s\r\n", tg, cmd, parseMsg);
313 if(Bflush(&bout) < 0)
319 bye("internal error: mailbox lock held");
327 * note: outlook express is broken: it requires echoing the
328 * command as part of matching response
330 for(st = imapState; st->name != nil; st++){
331 if(cistrcmp(cmd, st->name) == 0){
338 Bprint(&bout, "%s BAD %s illegal command\r\n", tg, cmd);
341 if(Bflush(&bout) < 0)
353 Bprint(&bout, "* bye ");
354 Bvprint(&bout, fmt, arg);
355 Bprint(&bout, "\r\n");
365 longjmp(parseJmp, 1);
369 * an error occured while writing to the client
375 _exits("connection closed");
379 catcher(void *v, char *msg)
382 if(strstr(msg, "closed pipe") != nil)
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.
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
410 for(i = 0; i < 4; i++)
411 postnote(PNGROUP, getpid(), "die");
415 * send any pending status updates to the client
416 * careful: shouldn't exit, because called by idle polling proc
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.
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.
433 * strategy for third party deletion of messages or of a mailbox
435 * deletion of a selected mailbox => act like all message are expunged
436 * not strictly allowed by rfc2180, but close to method 3.2.
438 * renaming same as deletion
441 * reject iff a deleted message is in the request
443 * search, store, fetch operations on expunged messages
444 * ignore the expunged messages
445 * return tagged no if referenced
448 status(int expungeable, int uids)
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;
463 if(tell || selected->toldRecent != selected->recent){
464 Bprint(&bout, "* %lud RECENT\r\n", selected->recent);
465 selected->toldRecent = selected->recent;
468 closeImp(selected, checkBox(selected, 1));
472 * careful: can't exit, because called by idle polling proc
479 checkBox(selected, 0);
484 appendCmd(char *tg, char *cmd)
486 char *mbox, head[128];
500 t = imap4DateTime(quoted());
502 parseErr("illegal date format");
510 mbox = mboxName(mbox);
511 if(mbox == nil || !okMbox(mbox)){
513 Bprint(&bout, "%s NO %s bad mailbox\r\n", tg, cmd);
516 if(!cdExists(mboxDir, mbox)){
518 Bprint(&bout, "%s NO [TRYCREATE] %s mailbox does not exist\r\n", tg, cmd);
522 snprint(head, sizeof(head), "From %s %s", username, ctime(t));
523 ok = appendSave(mbox, flags, head, &bin, n);
527 Bprint(&bout, "%s OK %s completed\r\n", tg, cmd);
529 Bprint(&bout, "%s NO %s message save failed\r\n", tg, cmd);
533 authenticateCmd(char *tg, char *cmd)
542 if(cistrcmp(s, "cram-md5") == 0){
545 Bprint(&bout, "%s OK %s\r\n", tg, cmd);
548 Bprint(&bout, "%s NO %s failed %s\r\n", tg, cmd, t);
550 Bprint(&bout, "%s NO %s unsupported authentication protocol\r\n", tg, cmd);
554 capabilityCmd(char *tg, char *cmd)
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);
565 closeCmd(char *tg, char *cmd)
569 closeBox(selected, 1);
571 Bprint(&bout, "%s OK %s mailbox closed, now in authenticated state\r\n", tg, cmd);
575 * note: message id's are before any pending expunges
578 copyCmd(char *tg, char *cmd)
580 copyUCmd(tg, cmd, 0);
584 copyUCmd(char *tg, char *cmd, int uids)
601 mbox = mboxName(mbox);
602 if(mbox == nil || !okMbox(mbox)){
604 Bprint(&bout, "%s NO %s%s bad mailbox\r\n", tg, uid, cmd);
607 if(!cdExists(mboxDir, mbox)){
609 Bprint(&bout, "%s NO [TRYCREATE] %s mailbox does not exist\r\n", tg, cmd);
614 checkBox(selected, 0);
615 ok = forMsgs(selected, ms, max, uids, copyCheck, nil);
617 ok = forMsgs(selected, ms, max, uids, copySave, mbox);
621 Bprint(&bout, "%s OK %s%s completed\r\n", tg, uid, cmd);
623 Bprint(&bout, "%s NO %s%s failed\r\n", tg, uid, cmd);
627 createCmd(char *tg, char *cmd)
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);
644 if(cistrcmp(mbox, "inbox") == 0){
645 Bprint(&bout, "%s NO %s cannot remotely create INBOX\r\n", tg, cmd);
648 if(access(mbox, AEXIST) >= 0){
649 Bprint(&bout, "%s NO %s mailbox already exists\r\n", tg, cmd);
653 fd = createBox(mbox, slash);
656 Bprint(&bout, "%s NO %s cannot create mailbox %s\r\n", tg, cmd, mbox);
658 Bprint(&bout, "%s OK %s %s completed\r\n", tg, mbox, cmd);
662 deleteCmd(char *tg, char *cmd)
671 mbox = mboxName(mbox);
672 if(mbox == nil || !okMbox(mbox)){
673 Bprint(&bout, "%s NO %s bad mailbox\r\n", tg, cmd);
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);
683 Bprint(&bout, "%s OK %s %s completed\r\n", tg, mbox, cmd);
687 expungeCmd(char *tg, char *cmd)
692 ok = deleteMsgs(selected);
695 Bprint(&bout, "%s OK %s messages erased\r\n", tg, cmd);
697 Bprint(&bout, "%s NO %s some messages not expunged\r\n", tg, cmd);
701 fetchCmd(char *tg, char *cmd)
703 fetchUCmd(tg, cmd, 0);
707 fetchUCmd(char *tg, char *cmd, int uids)
725 ml = checkBox(selected, 1);
727 forMsgs(selected, ms, max, uids, fetchSeen, f);
728 closeImp(selected, ml);
729 ok = ml != nil && forMsgs(selected, ms, max, uids, fetchMsg, f);
732 Bprint(&bout, "%s OK %s%s completed\r\n", tg, uid, cmd);
734 Bprint(&bout, "%s NO %s%s failed\r\n", tg, uid, cmd);
738 idleCmd(char *tg, char *cmd)
743 Bprint(&bout, "+ idling, waiting for done\r\n");
744 if(Bflush(&bout) < 0)
748 pid = rfork(RFPROC|RFMEM|RFNOWAIT);
756 * parent may have changed curDir, but it doesn't change our .
761 if(Bflush(&bout) < 0)
776 * clear out the next line, which is supposed to contain (case-insensitive)
778 * this is special code since it has to dance with the idle polling proc
779 * and handle exiting correctly.
801 * child may have changed curDir, but it doesn't change our .
806 Bprint(&bout, "%s OK %s terminated\r\n", tg, cmd);
810 listCmd(char *tg, char *cmd)
812 char *s, *t, *ss, *ref, *mbox;
823 if(ref == nil || mbox == nil){
824 Bprint(&bout, "%s BAD %s mailbox name not in modified utf-7\r\n", tg, cmd);
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.
833 * this must change if the # namespace convention is supported.
836 s = strchr(ref, '/');
841 Bprint(&bout, "* %s (\\Noselect) \"/\" \"%s\"\r\n", cmd, ref);
842 Bprint(&bout, "%s OK %s\r\n", tg, cmd);
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.
853 * the cleanup can wipe out * followed by a ..
854 * tough luck if such a stupid pattern is given.
857 if(strcmp(mbox, ".") == 0)
861 else if(*ref != '\0'){
863 if(strcmp(ref, ".") == 0)
867 while(*ref && isdotdot(mbox)){
868 s = strrchr(ref, '/');
882 n = strlen(ref) + strlen(mbox) + 2;
883 t = binalloc(&parseBin, n, 0);
885 parseErr("out of memory");
886 snprint(t, n, "%s/%s", ref, mbox);
888 ss = s + strlen(ref);
892 * only allow activity in /mail/box
894 if(s[0] == '/' || isdotdot(s)){
895 Bprint(&bout, "%s NO illegal mailbox pattern\r\n", tg);
899 if(cistrcmp(cmd, "lsub") == 0)
900 lsubBoxes(cmd, s, ss);
902 listBoxes(cmd, s, ss);
903 Bprint(&bout, "%s OK %s completed\r\n", tg, cmd);
907 passCR(char*u, char*p)
909 static char Ebadch[] = "can't get challenge";
910 static char nchall[64];
911 static char response[64];
912 static Chalstate *ch = nil;
917 if(!(ch = auth_challenge("proto=p9cr role=server user=%q", u)))
919 snprint(nchall, 64, " encrypt challenge: %s", ch->chal);
922 strncpy(response, p, 64);
924 ch->nresp = strlen(response);
925 ai = auth_response(ch);
937 loginCmd(char *tg, char *cmd)
943 s = astring(); /* uid */
945 t = astring(); /* password */
948 if ((r = passCR(s, t)) == nil){
949 Bprint(&bout, "%s OK %s succeeded\r\n", tg, cmd);
952 Bprint(&bout, "* NO [ALERT] %s\r\n", r);
953 Bprint(&bout, "%s NO %s succeeded\r\n", tg, cmd);
958 if(ai = passLogin(s, t)){
960 Bprint(&bout, "%s OK %s succeeded\r\n", tg, cmd);
963 Bprint(&bout, "%s NO %s failed check\r\n", tg, cmd);
966 Bprint(&bout, "%s NO %s plaintext passwords disallowed\r\n", tg, cmd);
970 * logout or x-exit, which doesn't expunge the mailbox
973 logoutCmd(char *tg, char *cmd)
977 if(cmd[0] != 'x' && selected){
978 closeBox(selected, 1);
981 Bprint(&bout, "* bye\r\n");
982 Bprint(&bout, "%s OK %s completed\r\n", tg, cmd);
988 namespaceCmd(char *tg, char *cmd)
994 * personal, other users, shared namespaces
995 * send back nil or descriptions of (prefix heirarchy-delim) for each case
997 Bprint(&bout, "* NAMESPACE ((\"\" \"/\")) nil nil\r\n");
998 Bprint(&bout, "%s OK %s completed\r\n", tg, cmd);
1002 noopCmd(char *tg, char *cmd)
1006 Bprint(&bout, "%s OK %s completed\r\n", tg, cmd);
1011 * this is only a partial implementation
1012 * should copy files to other directories,
1013 * and copy & truncate inbox
1016 renameCmd(char *tg, char *cmd)
1029 if(to == nil || !okMbox(to) || cistrcmp(to, "inbox") == 0){
1030 Bprint(&bout, "%s NO %s bad mailbox destination name\r\n", tg, cmd);
1033 if(access(to, AEXIST) >= 0){
1034 Bprint(&bout, "%s NO %s mailbox already exists\r\n", tg, cmd);
1037 from = mboxName(from);
1038 if(from == nil || !okMbox(from)){
1039 Bprint(&bout, "%s NO %s bad mailbox destination name\r\n", tg, cmd);
1042 if(cistrcmp(from, "inbox") == 0)
1043 ok = copyBox(from, to, 0);
1045 ok = moveBox(from, to);
1048 Bprint(&bout, "%s OK %s completed\r\n", tg, cmd);
1050 Bprint(&bout, "%s NO %s failed\r\n", tg, cmd);
1054 searchCmd(char *tg, char *cmd)
1056 searchUCmd(tg, cmd, 0);
1060 searchUCmd(char *tg, char *cmd, int uids)
1069 searchKeys(1, &rock);
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);
1082 rock.next = rock.next->next;
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){
1093 Bprint(&bout, " %lud", id);
1096 Bprint(&bout, "\r\n");
1097 checkBox(selected, 0);
1099 Bprint(&bout, "%s OK %s%s completed\r\n", tg, uid, cmd);
1103 selectCmd(char *tg, char *cmd)
1113 imapState = SAuthed;
1114 closeBox(selected, 1);
1118 mbox = mboxName(mbox);
1119 if(mbox == nil || !okMbox(mbox)){
1120 Bprint(&bout, "%s NO %s bad mailbox\r\n", tg, cmd);
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);
1130 imapState = SSelected;
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);
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);
1147 if(selected->writable)
1149 Bprint(&bout, "%s OK [%s] %s %s completed\r\n", tg, s, cmd, mbox);
1152 static NamedInt statusItems[] =
1154 {"MESSAGES", SMessages},
1155 {"RECENT", SRecent},
1156 {"UIDNEXT", SUidNext},
1157 {"UIDVALIDITY", SUidValidity},
1158 {"UNSEEN", SUnseen},
1163 statusCmd(char *tg, char *cmd)
1178 i = mapInt(statusItems, s);
1180 parseErr("illegal status item");
1189 mbox = mboxName(mbox);
1190 if(mbox == nil || !okMbox(mbox)){
1192 Bprint(&bout, "%s NO %s bad mailbox\r\n", tg, cmd);
1196 box = openBox(mbox, "status", 1);
1199 Bprint(&bout, "%s NO [TRYCREATE] %s can't open mailbox %s: %r\r\n", tg, cmd, mbox);
1203 Bprint(&bout, "* STATUS %s (", mbox);
1205 for(i = 0; statusItems[i].name != nil; i++){
1206 if(si & statusItems[i].v){
1208 switch(statusItems[i].v){
1219 v = box->uidvalidity;
1223 for(m = box->msgs; m != nil; m = m->next)
1224 if((m->flags & MSeen) != MSeen)
1229 bye("internal error: status item not implemented");
1232 Bprint(&bout, "%s%s %lud", s, statusItems[i].name, v);
1236 Bprint(&bout, ")\r\n");
1240 Bprint(&bout, "%s OK %s completed\r\n", tg, cmd);
1244 storeCmd(char *tg, char *cmd)
1246 storeUCmd(tg, cmd, 0);
1250 storeUCmd(char *tg, char *cmd, int uids)
1267 max = selected->max;
1268 ml = checkBox(selected, 1);
1269 ok = ml != nil && forMsgs(selected, ms, max, uids, storeMsg, st);
1270 closeImp(selected, ml);
1273 Bprint(&bout, "%s OK %s%s completed\r\n", tg, uid, cmd);
1275 Bprint(&bout, "%s NO %s%s failed\r\n", tg, uid, cmd);
1279 * minimal implementation of subscribe
1280 * all folders are automatically subscribed,
1281 * and can't be unsubscribed
1284 subscribeCmd(char *tg, char *cmd)
1294 mbox = mboxName(mbox);
1296 if(mbox != nil && okMbox(mbox)){
1297 box = openBox(mbox, "subscribe", 0);
1299 ok = subscribe(mbox, 's');
1304 Bprint(&bout, "%s NO %s bad mailbox\r\n", tg, cmd);
1306 Bprint(&bout, "%s OK %s completed\r\n", tg, cmd);
1310 uidCmd(char *tg, char *cmd)
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);
1326 Bprint(&bout, "%s BAD %s illegal uid command %s\r\n", tg, cmd, sub);
1331 unsubscribeCmd(char *tg, char *cmd)
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);
1343 Bprint(&bout, "%s OK %s completed\r\n", tg, cmd);
1349 parseErr("bad syntax");
1360 bye("end of input");
1374 if(c != '\r' || getc() != '\n')
1388 * flaglist : '(' ')' | '(' flags ')'
1405 * flags : flag | flags ' ' flag
1406 * flag : '\' atom | atom
1420 s = atomString(atomStop, "\\");
1421 }else if(strchr(atomStop, c) != nil)
1427 parseErr("flag not supported");
1434 parseErr("no flags given");
1439 * storeWhat : osign 'FLAGS' ' ' storeflags
1440 * | osign 'FLAGS.SILENT' ' ' storeflags
1443 * storeflags : flagList | flags
1453 if(c == '+' || c == '-')
1459 if(cistrcmp(s, "flags") == 0)
1461 else if(cistrcmp(s, "flags.silent") == 0)
1464 parseErr("illegal store attribute");
1470 return mkStore(c, w, f);
1474 * fetchWhat : "ALL" | "FULL" | "FAST" | fetchAtt | '(' fetchAtts ')'
1475 * fetchAtts : fetchAtt | fetchAtts ' ' fetchAtt
1477 static char *fetchAtom = "(){}%*\"\\[]";
1488 s = atomString(fetchAtom, "");
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)))));
1506 f = fetchAtt(s, nil);
1511 * fetchAtt : "ENVELOPE" | "FLAGS" | "INTERNALDATE"
1512 * | "RFC822" | "RFC822.HEADER" | "RFC822.SIZE" | "RFC822.TEXT"
1517 * | "BODY.PEEK" bodysubs
1519 * | sect '<' number '.' nz-number '>'
1520 * sect : '[' sectSpec ']'
1521 * sectSpec : sectMsgText
1523 * | sectPart '.' sectText
1524 * sectPart : nz-number
1525 * | sectPart '.' nz-number
1528 fetchAtt(char *s, Fetch *f)
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);
1552 if(cistrcmp(s, "body") == 0){
1554 return mkFetch(FBody, f);
1555 f = mkFetch(FBodySect, f);
1556 }else if(cistrcmp(s, "body.peek") == 0)
1557 f = mkFetch(FBodyPeek, f);
1559 parseErr("illegal fetch attribute");
1563 if(c >= '1' && c <= '9'){
1564 sect = mkNList(number(1), nil);
1565 while(peekc() == '.'){
1568 if(c >= '1' && c <= '9'){
1569 sect = mkNList(number(1), sect);
1574 f->sect = revNList(sect);
1577 sectText(f, f->sect != nil);
1585 f->start = number(0);
1587 f->size = number(1);
1593 * sectText : sectMsgText | "MIME"
1594 * sectMsgText : "HEADER"
1596 * | "HEADER.FIELDS" ' ' hdrList
1597 * | "HEADER.FIELDS.NOT" ' ' hdrList
1598 * hdrList : '(' hdrs ')'
1600 * | hdrs ' ' astring
1603 sectText(Fetch *f, int mimeOk)
1608 s = atomString(fetchAtom, "");
1609 if(cistrcmp(s, "header") == 0){
1613 if(cistrcmp(s, "text") == 0){
1617 if(mimeOk && cistrcmp(s, "mime") == 0){
1621 if(cistrcmp(s, "header.fields") == 0)
1622 f->part = FPHeadFields;
1623 else if(cistrcmp(s, "header.fields.not") == 0)
1624 f->part = FPHeadFieldsNot;
1626 parseErr("illegal fetch section text");
1631 h = mkSList(astring(), h);
1637 f->hdrs = revSList(h);
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"
1657 static NamedInt searchMap[] =
1660 {"ANSWERED", SKAnswered},
1661 {"DELETED", SKDeleted},
1662 {"FLAGGED", SKFlagged},
1665 {"RECENT", SKRecent},
1667 {"UNANSWERED", SKUnanswered},
1668 {"UNDELETED", SKUndeleted},
1669 {"UNFLAGGED", SKUnflagged},
1671 {"UNDRAFT", SKUndraft},
1672 {"UNSEEN", SKUnseen},
1676 static NamedInt searchMapStr[] =
1678 {"CHARSET", SKCharset},
1683 {"SUBJECT", SKSubject},
1689 static NamedInt searchMapDate[] =
1691 {"BEFORE", SKBefore},
1694 {"SENTBEFORE", SKSentBefore},
1695 {"SENTON", SKSentOn},
1696 {"SENTSINCE", SKSentSince},
1700 static NamedInt searchMapFlag[] =
1702 {"KEYWORD", SKKeyword},
1703 {"UNKEYWORD", SKUnkeyword},
1707 static NamedInt searchMapNum[] =
1709 {"SMALLER", SKSmaller},
1710 {"LARGER", SKLarger},
1715 searchKeys(int first, Search *tail)
1722 tail = searchKeys(0, tail);
1725 s = searchKey(first);
1738 searchKey(int first)
1745 sr = binalloc(&parseBin, sizeof(Search), 1);
1747 parseErr("out of memory");
1750 if(c >= '0' && c <= '9'){
1752 sr->set = msgSet(0);
1757 if(i = mapInt(searchMap, a))
1759 else if(i = mapInt(searchMapStr, a)){
1760 if(!first && i == SKCharset)
1761 parseErr("illegal search key");
1765 }else if(i = mapInt(searchMapDate, a)){
1772 if(!imap4Date(&tm, a))
1773 parseErr("bad date format");
1779 }else if(i = mapInt(searchMapFlag, a)){
1785 a = atomString(atomStop, "\\");
1790 parseErr("flag not supported");
1792 }else if(i = mapInt(searchMapNum, a)){
1795 sr->num = number(0);
1796 }else if(cistrcmp(a, "HEADER") == 0){
1799 sr->hdr = astring();
1802 }else if(cistrcmp(a, "UID") == 0){
1805 sr->set = msgSet(0);
1806 }else if(cistrcmp(a, "NOT") == 0){
1810 searchKeys(0, &rock);
1811 sr->left = rock.next;
1812 }else if(cistrcmp(a, "OR") == 0){
1816 searchKeys(0, &rock);
1817 sr->left = rock.next;
1820 searchKeys(0, &rock);
1821 sr->right = rock.next;
1823 parseErr("illegal search key");
1838 MsgSet head, *last, *ms;
1844 from = uids ? uidNo() : seqNo();
1848 to = uids ? uidNo() : seqNo();
1850 ms = binalloc(&parseBin, sizeof(MsgSet), 0);
1852 parseErr("out of memory");
1886 * 7 bit, non-ctl chars, no (){%*"\
1887 * NIL is special case for nstring or parenlist
1892 return atomString(atomStop, "");
1896 * like an atom, but no +
1901 return atomString("+(){%*\"\\", "");
1905 * string or atom allowing %*
1917 return atomString("(){\"\\", "");
1937 * 7 bit, non-ctl chars, none from exception list
1940 atomString(char *disallowed, char *initial)
1945 ns = strlen(initial);
1946 s = binalloc(&parseBin, ns + StrAlloc, 0);
1948 parseErr("out of memory");
1953 if(c <= ' ' || c >= 0x7f || strchr(disallowed, c) != nil){
1959 s = bingrow(&parseBin, s, as, as + StrAlloc, 0);
1961 parseErr("out of memory");
1972 * quoted: '"' chars* '"'
1973 * chars: 1-128 except \r and \n
1982 s = binalloc(&parseBin, StrAlloc, 0);
1984 parseErr("out of memory");
1991 if(c < 1 || c > 0x7f || c == '\r' || c == '\n')
1995 if(c != '\\' && c != '"')
2000 s = bingrow(&parseBin, s, as, as + StrAlloc, 0);
2002 parseErr("out of memory");
2011 * litlen: {number}\r\n
2026 * literal: litlen data<0:litlen>
2035 s = binalloc(&parseBin, v+1, 0);
2037 parseErr("out of memory");
2038 Bprint(&bout, "+ Ready for literal data\r\n");
2039 if(Bflush(&bout) < 0)
2041 if(v != 0 && Bread(&bin, s, v) != v)
2048 * digits; number is 32 bits
2060 if(c < '0' || c > '9'){
2066 if(nonzero && first && c == '0')
2070 if(v > UlongMax/10 || v == UlongMax/10 && c > UlongMax%10)
2071 parseErr("number out of range\r\n");