📄 imap4d.c
字号:
#include <u.h>#include <libc.h>#include <auth.h>#include <bio.h>#include "imap4d.h"/* * these should be in libraries */char *csquery(char *attr, char *val, char *rattr);/* * /lib/rfc/rfc2060 imap4rev1 * /lib/rfc/rfc2683 is implementation advice * /lib/rfc/rfc2342 is namespace capability * /lib/rfc/rfc2222 is security protocols * /lib/rfc/rfc1731 is security protocols * /lib/rfc/rfc2221 is LOGIN-REFERRALS * /lib/rfc/rfc2193 is MAILBOX-REFERRALS * /lib/rfc/rfc2177 is IDLE capability * /lib/rfc/rfc2195 is CRAM-MD5 authentication * /lib/rfc/rfc2088 is LITERAL+ capability * /lib/rfc/rfc1760 is S/Key authentication * * outlook uses "Secure Password Authentication" aka ntlm authentication * * capabilities from nslocum * CAPABILITY IMAP4 IMAP4REV1 NAMESPACE IDLE SCAN SORT MAILBOX-REFERRALS LOGIN-REFERRALS AUTH=LOGIN THREAD=ORDEREDSUBJECT */typedef struct ParseCmd ParseCmd;enum{ UlongMax = 4294967295,};struct ParseCmd{ char *name; void (*f)(char *tg, char *cmd);};static void appendCmd(char *tg, char *cmd);static void authenticateCmd(char *tg, char *cmd);static void capabilityCmd(char *tg, char *cmd);static void closeCmd(char *tg, char *cmd);static void copyCmd(char *tg, char *cmd);static void createCmd(char *tg, char *cmd);static void deleteCmd(char *tg, char *cmd);static void expungeCmd(char *tg, char *cmd);static void fetchCmd(char *tg, char *cmd);static void idleCmd(char *tg, char *cmd);static void listCmd(char *tg, char *cmd);static void loginCmd(char *tg, char *cmd);static void logoutCmd(char *tg, char *cmd);static void namespaceCmd(char *tg, char *cmd);static void noopCmd(char *tg, char *cmd);static void renameCmd(char *tg, char *cmd);static void searchCmd(char *tg, char *cmd);static void selectCmd(char *tg, char *cmd);static void statusCmd(char *tg, char *cmd);static void storeCmd(char *tg, char *cmd);static void subscribeCmd(char *tg, char *cmd);static void uidCmd(char *tg, char *cmd);static void unsubscribeCmd(char *tg, char *cmd);static void copyUCmd(char *tg, char *cmd, int uids);static void fetchUCmd(char *tg, char *cmd, int uids);static void searchUCmd(char *tg, char *cmd, int uids);static void storeUCmd(char *tg, char *cmd, int uids);static void imap4(int);static void status(int expungeable, int uids);static void cleaner(void);static void check(void);static int catcher(void*, char*);static Search *searchKey(int first);static Search *searchKeys(int first, Search *tail);static char *astring(void);static char *atomString(char *disallowed, char *initial);static char *atom(void);static void badsyn(void);static void clearcmd(void);static char *command(void);static void crnl(void);static Fetch *fetchAtt(char *s, Fetch *f);static Fetch *fetchWhat(void);static int flagList(void);static int flags(void);static int getc(void);static char *listmbox(void);static char *literal(void);static ulong litlen(void);static MsgSet *msgSet(int);static void mustBe(int c);static ulong number(int nonzero);static int peekc(void);static char *quoted(void);static void sectText(Fetch *f, int mimeOk);static ulong seqNo(void);static Store *storeWhat(void);static char *tag(void);static ulong uidNo(void);static void ungetc(void);static ParseCmd SNonAuthed[] ={ {"capability", capabilityCmd}, {"logout", logoutCmd}, {"x-exit", logoutCmd}, {"noop", noopCmd}, {"login", loginCmd}, {"authenticate", authenticateCmd}, nil};static ParseCmd SAuthed[] ={ {"capability", capabilityCmd}, {"logout", logoutCmd}, {"x-exit", logoutCmd}, {"noop", noopCmd}, {"append", appendCmd}, {"create", createCmd}, {"delete", deleteCmd}, {"examine", selectCmd}, {"select", selectCmd}, {"idle", idleCmd}, {"list", listCmd}, {"lsub", listCmd}, {"namespace", namespaceCmd}, {"rename", renameCmd}, {"status", statusCmd}, {"subscribe", subscribeCmd}, {"unsubscribe", unsubscribeCmd}, nil};static ParseCmd SSelected[] ={ {"capability", capabilityCmd}, {"logout", logoutCmd}, {"x-exit", logoutCmd}, {"noop", noopCmd}, {"append", appendCmd}, {"create", createCmd}, {"delete", deleteCmd}, {"examine", selectCmd}, {"select", selectCmd}, {"idle", idleCmd}, {"list", listCmd}, {"lsub", listCmd}, {"namespace", namespaceCmd}, {"rename", renameCmd}, {"status", statusCmd}, {"subscribe", subscribeCmd}, {"unsubscribe", unsubscribeCmd}, {"check", noopCmd}, {"close", closeCmd}, {"copy", copyCmd}, {"expunge", expungeCmd}, {"fetch", fetchCmd}, {"search", searchCmd}, {"store", storeCmd}, {"uid", uidCmd}, nil};static char *atomStop = "(){%*\"\\";static Chalstate *chal;static int chaled;static ParseCmd *imapState;static jmp_buf parseJmp;static char *parseMsg;static int allowPass;static int allowCR;static int exiting;static QLock imaplock;static int idlepid = -1;Biobuf bout;Biobuf bin;char username[UserNameLen];char mboxDir[MboxNameLen];char *servername;char *site;char *remote;Box *selected;Bin *parseBin;int debug;voidmain(int argc, char *argv[]){ char *s, *t; int preauth, n; Binit(&bin, 0, OREAD); Binit(&bout, 1, OWRITE); preauth = 0; allowPass = 0; allowCR = 0; ARGBEGIN{ case 'a': preauth = 1; break; case 'd': site = ARGF(); break; case 'c': allowCR = 1; break; case 'p': allowPass = 1; break; case 'r': remote = ARGF(); break; case 's': servername = ARGF(); break; case 'v': debug = 1; debuglog("imap4d debugging enabled\n"); break; default: fprint(2, "usage: ip/imap4d [-acpv] [-d site] [-r remotehost] [-s servername]\n"); bye("usage"); break; }ARGEND if(allowPass && allowCR){ fprint(2, "%s: -c and -p are mutually exclusive\n", argv0); bye("usage"); } if(preauth) setupuser(nil); if(servername == nil){ servername = csquery("sys", sysname(), "dom"); if(servername == nil) servername = sysname(); if(servername == nil){ fprint(2, "ip/imap4d can't find server name: %r\n"); bye("can't find system name"); } } if(site == nil){ t = getenv("site"); if(t == nil) site = servername; else{ n = strlen(t); s = strchr(servername, '.'); if(s == nil) s = servername; else s++; n += strlen(s) + 2; site = emalloc(n); snprint(site, n, "%s.%s", t, s); } } rfork(RFNOTEG|RFREND); atnotify(catcher, 1); qlock(&imaplock); atexit(cleaner); imap4(preauth);}static voidimap4(int preauth){ char *volatile tg; char *volatile cmd; ParseCmd *st; if(preauth){ Bprint(&bout, "* preauth %s IMAP4rev1 server ready user %s authenticated\r\n", servername, username); imapState = SAuthed; }else{ Bprint(&bout, "* OK %s IMAP4rev1 server ready\r\n", servername); imapState = SNonAuthed; } if(Bflush(&bout) < 0) writeErr(); chaled = 0; tg = nil; cmd = nil; if(setjmp(parseJmp)){ if(tg == nil) Bprint(&bout, "* bad empty command line: %s\r\n", parseMsg); else if(cmd == nil) Bprint(&bout, "%s BAD no command: %s\r\n", tg, parseMsg); else Bprint(&bout, "%s BAD %s %s\r\n", tg, cmd, parseMsg); clearcmd(); if(Bflush(&bout) < 0) writeErr(); binfree(&parseBin); } for(;;){ if(mbLocked()) bye("internal error: mailbox lock held"); tg = nil; cmd = nil; tg = tag(); mustBe(' '); cmd = atom(); /* * note: outlook express is broken: it requires echoing the * command as part of matching response */ for(st = imapState; st->name != nil; st++){ if(cistrcmp(cmd, st->name) == 0){ (*st->f)(tg, cmd); break; } } if(st->name == nil){ clearcmd(); Bprint(&bout, "%s BAD %s illegal command\r\n", tg, cmd); } if(Bflush(&bout) < 0) writeErr(); binfree(&parseBin); }}voidbye(char *fmt, ...){ va_list arg; va_start(arg, fmt); Bprint(&bout, "* bye "); Bvprint(&bout, fmt, arg); Bprint(&bout, "\r\n"); Bflush(&bout);exits("rob2"); exits(0);}voidparseErr(char *msg){ parseMsg = msg; longjmp(parseJmp, 1);}/* * an error occured while writing to the client */voidwriteErr(void){ cleaner(); _exits("connection closed");}static intcatcher(void *v, char *msg){ USED(v); if(strstr(msg, "closed pipe") != nil) return 1; return 0;}/* * wipes out the idleCmd backgroung process if it is around. * this can only be called if the current proc has qlocked imaplock. * it must be the last piece of imap4d code executed. */static voidcleaner(void){ int i; if(idlepid < 0) return; exiting = 1; close(0); close(1); close(2); /* * the other proc is either stuck in a read, a sleep, * or is trying to lock imap4lock. * get him out of it so he can exit cleanly */ qunlock(&imaplock); for(i = 0; i < 4; i++) postnote(PNGROUP, getpid(), "die");}/* * send any pending status updates to the client * careful: shouldn't exit, because called by idle polling proc * * can't always send pending info * in particular, can't send expunge info * in response to a fetch, store, or search command. * * rfc2060 5.2: server must send mailbox size updates * rfc2060 5.2: server may send flag updates * rfc2060 5.5: servers prohibited from sending expunge while fetch, store, search in progress * rfc2060 7: in selected state, server checks mailbox for new messages as part of every command * sends untagged EXISTS and RECENT respsonses reflecting new size of the mailbox * should also send appropriate untagged FETCH and EXPUNGE messages if another agent * changes the state of any message flags or expunges any messages * rfc2060 7.4.1 expunge server response must not be sent when no command is in progress, * nor while responding to a fetch, stort, or search command (uid versions are ok) * command only "in progress" after entirely parsed. * * strategy for third party deletion of messages or of a mailbox * * deletion of a selected mailbox => act like all message are expunged * not strictly allowed by rfc2180, but close to method 3.2. * * renaming same as deletion * * copy * reject iff a deleted message is in the request * * search, store, fetch operations on expunged messages * ignore the expunged messages * return tagged no if referenced */static voidstatus(int expungeable, int uids){ int tell; if(!selected) return; tell = 0; if(expungeable) tell = expungeMsgs(selected, 1); if(selected->sendFlags) sendFlags(selected, uids); if(tell || selected->toldMax != selected->max){ Bprint(&bout, "* %lud EXISTS\r\n", selected->max); selected->toldMax = selected->max; } if(tell || selected->toldRecent != selected->recent){ Bprint(&bout, "* %lud RECENT\r\n", selected->recent); selected->toldRecent = selected->recent; } if(tell) closeImp(selected, checkBox(selected, 1));}/* * careful: can't exit, because called by idle polling proc */static voidcheck(void){ if(!selected) return; checkBox(selected, 0); status(1, 0);}static voidappendCmd(char *tg, char *cmd){ char *mbox, head[128]; ulong t, n, now; int flags, ok; mustBe(' '); mbox = astring(); mustBe(' '); flags = 0; if(peekc() == '('){ flags = flagList(); mustBe(' '); } now = time(nil); if(peekc() == '"'){ t = imap4DateTime(quoted()); if(t == ~0) parseErr("illegal date format"); mustBe(' '); if(t > now) t = now; }else t = now; n = litlen(); mbox = mboxName(mbox); if(mbox == nil || !okMbox(mbox)){ check(); Bprint(&bout, "%s NO %s bad mailbox\r\n", tg, cmd); return; } if(!cdExists(mboxDir, mbox)){ check(); Bprint(&bout, "%s NO [TRYCREATE] %s mailbox does not exist\r\n", tg, cmd); return; } snprint(head, sizeof(head), "From %s %s", username, ctime(t)); ok = appendSave(mbox, flags, head, &bin, n); crnl(); check(); if(ok) Bprint(&bout, "%s OK %s completed\r\n", tg, cmd); else Bprint(&bout, "%s NO %s message save failed\r\n", tg, cmd);}static voidauthenticateCmd(char *tg, char *cmd){ char *s, *t; mustBe(' '); s = atom(); crnl(); auth_freechal(chal); chal = nil; if(cistrcmp(s, "cram-md5") == 0){ t = cramauth(); if(t == nil){ Bprint(&bout, "%s OK %s\r\n", tg, cmd); imapState = SAuthed; }else Bprint(&bout, "%s NO %s failed %s\r\n", tg, cmd, t); }else Bprint(&bout, "%s NO %s unsupported authentication protocol\r\n", tg, cmd);}static voidcapabilityCmd(char *tg, char *cmd){ crnl(); check();// nslocum's capabilities// Bprint(&bout, "* CAPABILITY IMAP4 IMAP4REV1 NAMESPACE IDLE SCAN SORT MAILBOX-REFERRALS LOGIN-REFERRALS AUTH=LOGIN THREAD=ORDEREDSUBJECT\r\n"); Bprint(&bout, "* CAPABILITY IMAP4REV1 IDLE NAMESPACE AUTH=CRAM-MD5\r\n"); Bprint(&bout, "%s OK %s\r\n", tg, cmd);}static voidcloseCmd(char *tg, char *cmd){ crnl(); imapState = SAuthed; closeBox(selected, 1); selected = nil; Bprint(&bout, "%s OK %s mailbox closed, now in authenticated state\r\n", tg, cmd);}/* * note: message id's are before any pending expunges */static voidcopyCmd(char *tg, char *cmd){ copyUCmd(tg, cmd, 0);}static voidcopyUCmd(char *tg, char *cmd, int uids){ MsgSet *ms; char *uid, *mbox; ulong max; int ok; mustBe(' '); ms = msgSet(uids); mustBe(' '); mbox = astring(); crnl(); uid = ""; if(uids) uid = "uid "; mbox = mboxName(mbox); if(mbox == nil || !okMbox(mbox)){ status(1, uids); Bprint(&bout, "%s NO %s%s bad mailbox\r\n", tg, uid, cmd); return; } if(!cdExists(mboxDir, mbox)){ check(); Bprint(&bout, "%s NO [TRYCREATE] %s mailbox does not exist\r\n", tg, cmd); return; } max = selected->max; checkBox(selected, 0); ok = forMsgs(selected, ms, max, uids, copyCheck, nil); if(ok) ok = forMsgs(selected, ms, max, uids, copySave, mbox); status(1, uids); if(ok) Bprint(&bout, "%s OK %s%s completed\r\n", tg, uid, cmd); else Bprint(&bout, "%s NO %s%s failed\r\n", tg, uid, cmd);}static voidcreateCmd(char *tg, char *cmd){ char *mbox, *m; int fd, slash; mustBe(' '); mbox = astring(); crnl(); check(); m = strchr(mbox, '\0'); slash = m != mbox && m[-1] == '/'; mbox = mboxName(mbox); if(mbox == nil || !okMbox(mbox)){ Bprint(&bout, "%s NO %s bad mailbox\r\n", tg, cmd); return; } if(cistrcmp(mbox, "inbox") == 0){ Bprint(&bout, "%s NO %s cannot remotely create INBOX\r\n", tg, cmd); return; } if(access(mbox, AEXIST) >= 0){ Bprint(&bout, "%s NO %s mailbox already exists\r\n", tg, cmd); return; } fd = createBox(mbox, slash); close(fd); if(fd < 0) Bprint(&bout, "%s NO %s cannot create mailbox %s\r\n", tg, cmd, mbox); else Bprint(&bout, "%s OK %s %s completed\r\n", tg, mbox, cmd);}static voiddeleteCmd(char *tg, char *cmd){ char *mbox, *imp; mustBe(' '); mbox = astring(); crnl(); check(); mbox = mboxName(mbox); if(mbox == nil || !okMbox(mbox)){ Bprint(&bout, "%s NO %s bad mailbox\r\n", tg, cmd); return; } imp = impName(mbox); if(cistrcmp(mbox, "inbox") == 0 || imp != nil && cdRemove(mboxDir, imp) < 0 && cdExists(mboxDir, imp) || cdRemove(mboxDir, mbox) < 0) Bprint(&bout, "%s NO %s cannot delete mailbox %s\r\n", tg, cmd, mbox); else Bprint(&bout, "%s OK %s %s completed\r\n", tg, mbox, cmd);}static voidexpungeCmd(char *tg, char *cmd){ int ok; crnl(); ok = deleteMsgs(selected); check(); if(ok) Bprint(&bout, "%s OK %s messages erased\r\n", tg, cmd); else Bprint(&bout, "%s NO %s some messages not expunged\r\n", tg, cmd);}static void
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -