From mboxrd@z Thu Jan 1 00:00:00 1970 MIME-Version: 1.0 Content-Type: multipart/mixed; boundary="ssAFoO5g4d" Content-Transfer-Encoding: 7bit Message-ID: <15829.5531.317920.808359@nanonic.hilbert.space> From: paurea@gsyc.escet.urjc.es To: 9fans@cse.psu.edu Subject: [9fans] imap4d bug Date: Fri, 15 Nov 2002 16:41:15 +0100 Topicbox-Message-UUID: 21644ac0-eacb-11e9-9e20-41e7f4b1d025 --ssAFoO5g4d Content-Type: text/plain; charset=us-ascii Content-Description: message body text Content-Transfer-Encoding: 7bit Some of the imap commands where written downcase and because of that it didn't interact with fetchmail, emacs or Netscape 7 mail reader.(it did work with mozilla don't know why...). This are the archives which change: --ssAFoO5g4d Content-Type: text/plain Content-Disposition: inline; filename="imap4d.c" Content-Transfer-Encoding: 7bit #include #include #include #include #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(void); 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 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; void main(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 [-acp] [-d site] [-r remotehost] [-s servername]\n"); bye("usage"); break; }ARGEND 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 void imap4(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); } } void bye(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); } void parseErr(char *msg) { parseMsg = msg; longjmp(parseJmp, 1); } /* * an error occured while writing to the client */ void writeErr(void) { cleaner(); _exits("connection closed"); } static int catcher(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 void cleaner(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 void status(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 void check(void) { if(!selected) return; checkBox(selected, 0); status(1, 0); } static void appendCmd(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 void authenticateCmd(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 void capabilityCmd(char *tg, char *cmd) { crnl(); check(); Bprint(&bout, "* capability IMAP4rev1 idle namespace auth=cram-md5\r\n"); Bprint(&bout, "%s ok %s\r\n", tg, cmd); } static void closeCmd(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 void copyCmd(char *tg, char *cmd) { copyUCmd(tg, cmd, 0); } static void copyUCmd(char *tg, char *cmd, int uids) { MsgSet *ms; char *uid, *mbox; ulong max; int ok; mustBe(' '); ms = msgSet(); 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 void createCmd(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 void deleteCmd(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 void expungeCmd(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 fetchCmd(char *tg, char *cmd) { fetchUCmd(tg, cmd, 0); } static void fetchUCmd(char *tg, char *cmd, int uids) { Fetch *f; MsgSet *ms; MbLock *ml; char *uid; ulong max; int ok; mustBe(' '); ms = msgSet(); mustBe(' '); f = fetchWhat(); crnl(); uid = ""; if(uids) uid = "uid "; max = selected->max; ml = checkBox(selected, 1); if(ml != nil) forMsgs(selected, ms, max, uids, fetchSeen, f); closeImp(selected, ml); ok = ml != nil && forMsgs(selected, ms, max, uids, fetchMsg, f); status(uids, 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 void idleCmd(char *tg, char *cmd) { int c, pid; crnl(); Bprint(&bout, "+ idling, waiting for done\r\n"); if(Bflush(&bout) < 0) writeErr(); if(idlepid < 0){ pid = rfork(RFPROC|RFMEM|RFNOWAIT); if(pid == 0){ for(;;){ qlock(&imaplock); if(exiting) break; /* * parent may have changed curDir, but it doesn't change our . */ resetCurDir(); check(); if(Bflush(&bout) < 0) writeErr(); qunlock(&imaplock); sleep(15*1000); enableForwarding(); } _exits("rob3"); _exits(0); } idlepid = pid; } qunlock(&imaplock); /* * clear out the next line, which is supposed to contain (case-insensitive) * done\n * this is special code since it has to dance with the idle polling proc * and handle exiting correctly. */ for(;;){ c = getc(); if(c < 0){ qlock(&imaplock); if(!exiting) cleaner(); _exits("rob4"); _exits(0); } if(c == '\n') break; } qlock(&imaplock); if(exiting) {_exits("rob5"); _exits(0); } /* * child may have changed curDir, but it doesn't change our . */ resetCurDir(); check(); Bprint(&bout, "%s ok %s terminated\r\n", tg, cmd); } static void listCmd(char *tg, char *cmd) { char *s, *t, *ss, *ref, *mbox; int n; mustBe(' '); s = astring(); mustBe(' '); t = listmbox(); crnl(); check(); ref = mutf7str(s); mbox = mutf7str(t); if(ref == nil || mbox == nil){ Bprint(&bout, "%s bad %s mailbox name not in modified utf-7\r\n", tg, cmd); return; } /* * special request for hierarchy delimiter and root name * root name appears to be name up to and including any delimiter, * or the empty string, if there is no delimiter. * * this must change if the # namespace convention is supported. */ if(*mbox == '\0'){ s = strchr(ref, '/'); if(s == nil) ref = ""; else s[1] = '\0'; Bprint(&bout, "* %s (\\Noselect) \"/\" \"%s\"\r\n", cmd, ref); Bprint(&bout, "%s ok %s\r\n", tg, cmd); return; } /* * massage the listing name: * clean up the components individually, * then rip off componenets from the ref to * take care of leading ..'s in the mbox. * * the cleanup can wipe out * followed by a .. * tough luck if such a stupid pattern is given. */ cleanname(mbox); if(strcmp(mbox, ".") == 0) *mbox = '\0'; if(mbox[0] == '/') *ref = '\0'; else if(*ref != '\0'){ cleanname(ref); if(strcmp(ref, ".") == 0) *ref = '\0'; }else *ref = '\0'; while(*ref && isdotdot(mbox)){ s = strrchr(ref, '/'); if(s == nil) s = ref; if(isdotdot(s)) break; *s = '\0'; mbox += 2; if(*mbox == '/') mbox++; } if(*ref == '\0'){ s = mbox; ss = s; }else{ n = strlen(ref) + strlen(mbox) + 2; t = binalloc(&parseBin, n, 0); if(t == nil) parseErr("out of memory"); snprint(t, n, "%s/%s", ref, mbox); s = t; ss = s + strlen(ref); } /* * only allow activity in /mail/box */ if(s[0] == '/' || isdotdot(s)){ Bprint(&bout, "%s no illegal mailbox pattern\r\n", tg); return; } if(cistrcmp(cmd, "lsub") == 0) lsubBoxes(cmd, s, ss); else listBoxes(cmd, s, ss); Bprint(&bout, "%s OK %s completed\r\n", tg, cmd); } static char* passCR(char*u, char*p) { static char Ebadch[] = "can't get challenge"; static char nchall[64]; static char response[64]; static Chalstate *ch = nil; AuthInfo *ai; again: if (ch == nil){ if(!(ch = auth_challenge("proto=p9cr role=server user=%q", u))) return Ebadch; snprint(nchall, 64, " encrypt challenge: %s", ch->chal); return nchall; } else { strncpy(response, p, 64); ch->resp = response; ch->nresp = strlen(response); ai = auth_response(ch); auth_freechal(ch); ch = nil; if (ai == nil) goto again; setupuser(ai); return nil; } } static void loginCmd(char *tg, char *cmd) { char *s, *t; AuthInfo *ai; char*r; mustBe(' '); s = astring(); /* uid */ mustBe(' '); t = astring(); /* password */ crnl(); if(allowCR){ if ((r = passCR(s, t)) == nil){ Bprint(&bout, "%s ok %s succeeded\r\n", tg, cmd); imapState = SAuthed; } else { Bprint(&bout, "* no [ALERT] %s\r\n", r); Bprint(&bout, "%s no %s succeeded\r\n", tg, cmd); } return; } else if(allowPass){ if(ai = passLogin(s, t)){ setupuser(ai); Bprint(&bout, "%s ok %s succeeded\r\n", tg, cmd); imapState = SAuthed; }else Bprint(&bout, "%s no %s failed check\r\n", tg, cmd); return; } Bprint(&bout, "%s no %s plaintext passwords disallowed\r\n", tg, cmd); } /* * logout or x-exit, which doesn't expunge the mailbox */ static void logoutCmd(char *tg, char *cmd) { crnl(); if(cmd[0] != 'x' && selected){ closeBox(selected, 1); selected = nil; } Bprint(&bout, "* bye\r\n"); Bprint(&bout, "%s OK %s completed\r\n", tg, cmd); exits("rob6"); exits(0); } static void namespaceCmd(char *tg, char *cmd) { crnl(); check(); /* * personal, other users, shared namespaces * send back nil or descriptions of (prefix heirarchy-delim) for each case */ Bprint(&bout, "* namespace ((\"\" \"/\")) nil nil\r\n"); Bprint(&bout, "%s OK %s completed\r\n", tg, cmd); } static void noopCmd(char *tg, char *cmd) { crnl(); check(); Bprint(&bout, "%s OK %s completed\r\n", tg, cmd); enableForwarding(); } /* * this is only a partial implementation * should copy files to other directories, * and copy & truncate inbox */ static void renameCmd(char *tg, char *cmd) { char *from, *to; int ok; mustBe(' '); from = astring(); mustBe(' '); to = astring(); crnl(); check(); to = mboxName(to); if(to == nil || !okMbox(to) || cistrcmp(to, "inbox") == 0){ Bprint(&bout, "%s no %s bad mailbox destination name\r\n", tg, cmd); return; } if(access(to, AEXIST) >= 0){ Bprint(&bout, "%s no %s mailbox already exists\r\n", tg, cmd); return; } from = mboxName(from); if(from == nil || !okMbox(from)){ Bprint(&bout, "%s no %s bad mailbox destination name\r\n", tg, cmd); return; } if(cistrcmp(from, "inbox") == 0) ok = copyBox(from, to, 0); else ok = moveBox(from, to); if(ok) Bprint(&bout, "%s OK %s completed\r\n", tg, cmd); else Bprint(&bout, "%s no %s failed\r\n", tg, cmd); } static void searchCmd(char *tg, char *cmd) { searchUCmd(tg, cmd, 0); } static void searchUCmd(char *tg, char *cmd, int uids) { Search rock; Msg *m; char *uid; ulong id; mustBe(' '); rock.next = nil; searchKeys(1, &rock); crnl(); uid = ""; if(uids) uid = "uid "; if(rock.next != nil && rock.next->key == SKCharset){ if(cistrstr(rock.next->s, "utf-8") != 0 && cistrcmp(rock.next->s, "us-ascii") != 0){ Bprint(&bout, "%s no [BADCHARSET] (\"US-ASCII\" \"UTF-8\") %s%s failed\r\n", tg, uid, cmd); checkBox(selected, 0); status(uids, uids); return; } rock.next = rock.next->next; } Bprint(&bout, "* search"); for(m = selected->msgs; m != nil; m = m->next) m->matched = searchMsg(m, rock.next); for(m = selected->msgs; m != nil; m = m->next){ if(m->matched){ if(uids) id = m->uid; else id = m->seq; Bprint(&bout, " %lud", id); } } Bprint(&bout, "\r\n"); checkBox(selected, 0); status(uids, uids); Bprint(&bout, "%s OK %s%s completed\r\n", tg, uid, cmd); } static void selectCmd(char *tg, char *cmd) { Msg *m; char *s, *mbox; mustBe(' '); mbox = astring(); crnl(); if(selected){ imapState = SAuthed; closeBox(selected, 1); selected = nil; } mbox = mboxName(mbox); if(mbox == nil || !okMbox(mbox)){ Bprint(&bout, "%s no %s bad mailbox\r\n", tg, cmd); return; } selected = openBox(mbox, "imap", cistrcmp(cmd, "select") == 0); if(selected == nil){ Bprint(&bout, "%s no %s can't open mailbox %s: %r\r\n", tg, cmd, mbox); return; } imapState = SSelected; Bprint(&bout, "* flags (\\Seen \\Answered \\Flagged \\Deleted \\Draft)\r\n"); Bprint(&bout, "* %lud exists\r\n", selected->max); selected->toldMax = selected->max; Bprint(&bout, "* %lud recent\r\n", selected->recent); selected->toldRecent = selected->recent; for(m = selected->msgs; m != nil; m = m->next){ if(!m->expunged && (m->flags & MSeen) != MSeen){ Bprint(&bout, "* ok [UNSEEN %ld]\r\n", m->seq); break; } } Bprint(&bout, "* ok [PERMANENTFLAGS (\\Seen \\Answered \\Flagged \\Draft)]\r\n"); Bprint(&bout, "* ok [UIDNEXT %ld]\r\n", selected->uidnext); Bprint(&bout, "* ok [UIDVALIDITY %ld]\r\n", selected->uidvalidity); s = "READ-ONLY"; if(selected->writable) s = "READ-WRITE"; Bprint(&bout, "%s OK [%s] %s %s completed\r\n", tg, s, cmd, mbox); } static NamedInt statusItems[] = { {"MESSAGES", SMessages}, {"RECENT", SRecent}, {"UIDNEXT", SUidNext}, {"UIDVALIDITY", SUidValidity}, {"UNSEEN", SUnseen}, {nil, 0} }; static void statusCmd(char *tg, char *cmd) { Box *box; Msg *m; char *s, *mbox; ulong v; int si, i; mustBe(' '); mbox = astring(); mustBe(' '); mustBe('('); si = 0; for(;;){ s = atom(); i = mapInt(statusItems, s); if(i == 0) parseErr("illegal status item"); si |= i; if(peekc() == ')') break; mustBe(' '); } mustBe(')'); crnl(); mbox = mboxName(mbox); if(mbox == nil || !okMbox(mbox)){ check(); Bprint(&bout, "%s no %s bad mailbox\r\n", tg, cmd); return; } box = openBox(mbox, "status", 1); if(box == nil){ check(); Bprint(&bout, "%s no [TRYCREATE] %s can't open mailbox %s: %r\r\n", tg, cmd, mbox); return; } Bprint(&bout, "* STATUS ("); s = ""; for(i = 0; statusItems[i].name != nil; i++){ if(si & statusItems[i].v){ v = 0; switch(statusItems[i].v){ case SMessages: v = box->max; break; case SRecent: v = box->recent; break; case SUidNext: v = box->uidnext; break; case SUidValidity: v = box->uidvalidity; break; case SUnseen: v = 0; for(m = box->msgs; m != nil; m = m->next) if((m->flags & MSeen) != MSeen) v++; break; default: Bprint(&bout, ")"); bye("internal error: status item not implemented"); break; } Bprint(&bout, "%s%s %lud", s, statusItems[i].name, v); s = " "; } } Bprint(&bout, ")\r\n"); closeBox(box, 1); check(); Bprint(&bout, "%s OK %s completed\r\n", tg, cmd); } static void storeCmd(char *tg, char *cmd) { storeUCmd(tg, cmd, 0); } static void storeUCmd(char *tg, char *cmd, int uids) { Store *st; MsgSet *ms; MbLock *ml; char *uid; ulong max; int ok; mustBe(' '); ms = msgSet(); mustBe(' '); st = storeWhat(); crnl(); uid = ""; if(uids) uid = "uid "; max = selected->max; ml = checkBox(selected, 1); ok = ml != nil && forMsgs(selected, ms, max, uids, storeMsg, st); closeImp(selected, ml); status(uids, 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); } /* * minimal implementation of subscribe * all folders are automatically subscribed, * and can't be unsubscribed */ static void subscribeCmd(char *tg, char *cmd) { Box *box; char *mbox; int ok; mustBe(' '); mbox = astring(); crnl(); check(); mbox = mboxName(mbox); ok = 0; if(mbox != nil && okMbox(mbox)){ box = openBox(mbox, "subscribe", 0); if(box != nil){ ok = subscribe(mbox, 's'); closeBox(box, 1); } } if(!ok) Bprint(&bout, "%s no %s bad mailbox\r\n", tg, cmd); else Bprint(&bout, "%s OK %s completed\r\n", tg, cmd); } static void uidCmd(char *tg, char *cmd) { char *sub; mustBe(' '); sub = atom(); if(cistrcmp(sub, "copy") == 0) copyUCmd(tg, sub, 1); else if(cistrcmp(sub, "fetch") == 0) fetchUCmd(tg, sub, 1); else if(cistrcmp(sub, "search") == 0) searchUCmd(tg, sub, 1); else if(cistrcmp(sub, "store") == 0) storeUCmd(tg, sub, 1); else{ clearcmd(); Bprint(&bout, "%s bad %s illegal uid command %s\r\n", tg, cmd, sub); } } static void unsubscribeCmd(char *tg, char *cmd) { char *mbox; mustBe(' '); mbox = astring(); crnl(); check(); mbox = mboxName(mbox); if(mbox == nil || !okMbox(mbox) || !subscribe(mbox, 'u')) Bprint(&bout, "%s no %s can't unsubscribe\r\n", tg, cmd); else Bprint(&bout, "%s OK %s completed\r\n", tg, cmd); } static void badsyn(void) { parseErr("bad syntax"); } static void clearcmd(void) { int c; for(;;){ c = getc(); if(c < 0) bye("end of input"); if(c == '\n') return; } } static void crnl(void) { int c; c = getc(); if(c == '\n') return; if(c != '\r' || getc() != '\n') badsyn(); } static void mustBe(int c) { if(getc() != c){ ungetc(); badsyn(); } } /* * flaglist : '(' ')' | '(' flags ')' */ static int flagList(void) { int f; mustBe('('); f = 0; if(peekc() != ')') f = flags(); mustBe(')'); return f; } /* * flags : flag | flags ' ' flag * flag : '\' atom | atom */ static int flags(void) { int ff, flags; char *s; int c; flags = 0; for(;;){ c = peekc(); if(c == '\\'){ mustBe('\\'); s = atomString(atomStop, "\\"); }else if(strchr(atomStop, c) != nil) s = atom(); else break; ff = mapFlag(s); if(ff == 0) parseErr("flag not supported"); flags |= ff; if(peekc() != ' ') break; mustBe(' '); } if(flags == 0) parseErr("no flags given"); return flags; } /* * storeWhat : osign 'FLAGS' ' ' storeflags * | osign 'FLAGS.SILENT' ' ' storeflags * osign : * | '+' | '-' * storeflags : flagList | flags */ static Store* storeWhat(void) { int f; char *s; int c, w; c = peekc(); if(c == '+' || c == '-') mustBe(c); else c = 0; s = atom(); w = 0; if(cistrcmp(s, "flags") == 0) w = STFlags; else if(cistrcmp(s, "flags.silent") == 0) w = STFlagsSilent; else parseErr("illegal store attribute"); mustBe(' '); if(peekc() == '(') f = flagList(); else f = flags(); return mkStore(c, w, f); } /* * fetchWhat : "ALL" | "FULL" | "FAST" | fetchAtt | '(' fetchAtts ')' * fetchAtts : fetchAtt | fetchAtts ' ' fetchAtt */ static char *fetchAtom = "(){}%*\"\\[]"; static Fetch* fetchWhat(void) { Fetch *f; char *s; if(peekc() == '('){ getc(); f = nil; for(;;){ s = atomString(fetchAtom, ""); f = fetchAtt(s, f); if(peekc() == ')') break; mustBe(' '); } getc(); return revFetch(f); } s = atomString(fetchAtom, ""); if(cistrcmp(s, "all") == 0) f = mkFetch(FFlags, mkFetch(FInternalDate, mkFetch(FRfc822Size, mkFetch(FEnvelope, nil)))); else if(cistrcmp(s, "fast") == 0) f = mkFetch(FFlags, mkFetch(FInternalDate, mkFetch(FRfc822Size, nil))); else if(cistrcmp(s, "full") == 0) f = mkFetch(FFlags, mkFetch(FInternalDate, mkFetch(FRfc822Size, mkFetch(FEnvelope, mkFetch(FBody, nil))))); else f = fetchAtt(s, nil); return f; } /* * fetchAtt : "ENVELOPE" | "FLAGS" | "INTERNALDATE" * | "RFC822" | "RFC822.HEADER" | "RFC822.SIZE" | "RFC822.TEXT" * | "BODYSTRUCTURE" * | "UID" * | "BODY" * | "BODY" bodysubs * | "BODY.PEEK" bodysubs * bodysubs : sect * | sect '<' number '.' nz-number '>' * sect : '[' sectSpec ']' * sectSpec : sectMsgText * | sectPart * | sectPart '.' sectText * sectPart : nz-number * | sectPart '.' nz-number */ static Fetch* fetchAtt(char *s, Fetch *f) { NList *sect; int c; if(cistrcmp(s, "envelope") == 0) return mkFetch(FEnvelope, f); if(cistrcmp(s, "flags") == 0) return mkFetch(FFlags, f); if(cistrcmp(s, "internaldate") == 0) return mkFetch(FInternalDate, f); if(cistrcmp(s, "RFC822") == 0) return mkFetch(FRfc822, f); if(cistrcmp(s, "RFC822.header") == 0) return mkFetch(FRfc822Head, f); if(cistrcmp(s, "RFC822.size") == 0) return mkFetch(FRfc822Size, f); if(cistrcmp(s, "RFC822.text") == 0) return mkFetch(FRfc822Text, f); if(cistrcmp(s, "bodystructure") == 0) return mkFetch(FBodyStruct, f); if(cistrcmp(s, "uid") == 0) return mkFetch(FUid, f); if(cistrcmp(s, "body") == 0){ if(peekc() != '[') return mkFetch(FBody, f); f = mkFetch(FBodySect, f); }else if(cistrcmp(s, "body.peek") == 0) f = mkFetch(FBodyPeek, f); else parseErr("illegal fetch attribute"); mustBe('['); c = peekc(); if(c >= '1' && c <= '9'){ sect = mkNList(number(1), nil); while(peekc() == '.'){ getc(); c = peekc(); if(c >= '1' && c <= '9'){ sect = mkNList(number(1), sect); }else{ break; } } f->sect = revNList(sect); } if(peekc() != ']') sectText(f, f->sect != nil); mustBe(']'); if(peekc() != '<') return f; f->partial = 1; mustBe('<'); f->start = number(0); mustBe('.'); f->size = number(1); mustBe('>'); return f; } /* * sectText : sectMsgText | "MIME" * sectMsgText : "HEADER" * | "TEXT" * | "HEADER.FIELDS" ' ' hdrList * | "HEADER.FIELDS.NOT" ' ' hdrList * hdrList : '(' hdrs ')' * hdrs: : astring * | hdrs ' ' astring */ static void sectText(Fetch *f, int mimeOk) { SList *h; char *s; s = atomString(fetchAtom, ""); if(cistrcmp(s, "header") == 0){ f->part = FPHead; return; } if(cistrcmp(s, "text") == 0){ f->part = FPText; return; } if(mimeOk && cistrcmp(s, "mime") == 0){ f->part = FPMime; return; } if(cistrcmp(s, "header.fields") == 0) f->part = FPHeadFields; else if(cistrcmp(s, "header.fields.not") == 0) f->part = FPHeadFieldsNot; else parseErr("illegal fetch section text"); mustBe(' '); mustBe('('); h = nil; for(;;){ h = mkSList(astring(), h); if(peekc() == ')') break; mustBe(' '); } mustBe(')'); f->hdrs = revSList(h); } /* * searchWhat : "CHARSET" ' ' astring searchkeys | searchkeys * searchkeys : searchkey | searchkeys ' ' searchkey * searchkey : "ALL" | "ANSWERED" | "DELETED" | "FLAGGED" | "NEW" | "OLD" | "RECENT" * | "SEEN" | "UNANSWERED" | "UNDELETED" | "UNFLAGGED" | "DRAFT" | "UNDRAFT" * | astrkey ' ' astring * | datekey ' ' date * | "KEYWORD" ' ' flag | "UNKEYWORD" flag * | "LARGER" ' ' number | "SMALLER" ' ' number * | "HEADER" astring ' ' astring * | set | "UID" ' ' set * | "NOT" ' ' searchkey * | "OR" ' ' searchkey ' ' searchkey * | '(' searchkeys ')' * astrkey : "BCC" | "BODY" | "CC" | "FROM" | "SUBJECT" | "TEXT" | "TO" * datekey : "BEFORE" | "ON" | "SINCE" | "SENTBEFORE" | "SENTON" | "SENTSINCE" */ static NamedInt searchMap[] = { {"ALL", SKAll}, {"ANSWERED", SKAnswered}, {"DELETED", SKDeleted}, {"FLAGGED", SKFlagged}, {"NEW", SKNew}, {"OLD", SKOld}, {"RECENT", SKRecent}, {"SEEN", SKSeen}, {"UNANSWERED", SKUnanswered}, {"UNDELETED", SKUndeleted}, {"UNFLAGGED", SKUnflagged}, {"DRAFT", SKDraft}, {"UNDRAFT", SKUndraft}, {"UNSEEN", SKUnseen}, {nil, 0} }; static NamedInt searchMapStr[] = { {"CHARSET", SKCharset}, {"BCC", SKBcc}, {"BODY", SKBody}, {"CC", SKCc}, {"FROM", SKFrom}, {"SUBJECT", SKSubject}, {"TEXT", SKText}, {"TO", SKTo}, {nil, 0} }; static NamedInt searchMapDate[] = { {"BEFORE", SKBefore}, {"ON", SKOn}, {"SINCE", SKSince}, {"SENTBEFORE", SKSentBefore}, {"SENTON", SKSentOn}, {"SENTSINCE", SKSentSince}, {nil, 0} }; static NamedInt searchMapFlag[] = { {"KEYWORD", SKKeyword}, {"UNKEYWORD", SKUnkeyword}, {nil, 0} }; static NamedInt searchMapNum[] = { {"SMALLER", SKSmaller}, {"LARGER", SKLarger}, {nil, 0} }; static Search* searchKeys(int first, Search *tail) { Search *s; for(;;){ if(peekc() == '('){ getc(); tail = searchKeys(0, tail); mustBe(')'); }else{ s = searchKey(first); tail->next = s; tail = s; } first = 0; if(peekc() != ' ') break; getc(); } return tail; } static Search* searchKey(int first) { Search *sr, rock; Tm tm; char *a; int i, c; sr = binalloc(&parseBin, sizeof(Search), 1); if(sr == nil) parseErr("out of memory"); c = peekc(); if(c >= '0' && c <= '9'){ sr->key = SKSet; sr->set = msgSet(); return sr; } a = atom(); if(i = mapInt(searchMap, a)) sr->key = i; else if(i = mapInt(searchMapStr, a)){ if(!first && i == SKCharset) parseErr("illegal search key"); sr->key = i; mustBe(' '); sr->s = astring(); }else if(i = mapInt(searchMapDate, a)){ sr->key = i; mustBe(' '); c = peekc(); if(c == '"') getc(); a = atom(); if(!imap4Date(&tm, a)) parseErr("bad date format"); sr->year = tm.year; sr->mon = tm.mon; sr->mday = tm.mday; if(c == '"') mustBe('"'); }else if(i = mapInt(searchMapFlag, a)){ sr->key = i; mustBe(' '); c = peekc(); if(c == '\\'){ mustBe('\\'); a = atomString(atomStop, "\\"); }else a = atom(); i = mapFlag(a); if(i == 0) parseErr("flag not supported"); sr->num = i; }else if(i = mapInt(searchMapNum, a)){ sr->key = i; mustBe(' '); sr->num = number(0); }else if(cistrcmp(a, "HEADER") == 0){ sr->key = SKHeader; mustBe(' '); sr->hdr = astring(); mustBe(' '); sr->s = astring(); }else if(cistrcmp(a, "UID") == 0){ sr->key = SKUid; mustBe(' '); sr->set = msgSet(); }else if(cistrcmp(a, "NOT") == 0){ sr->key = SKNot; mustBe(' '); rock.next = nil; searchKeys(0, &rock); sr->left = rock.next; }else if(cistrcmp(a, "OR") == 0){ sr->key = SKOr; mustBe(' '); rock.next = nil; searchKeys(0, &rock); sr->left = rock.next; mustBe(' '); rock.next = nil; searchKeys(0, &rock); sr->right = rock.next; }else parseErr("illegal search key"); return sr; } /* * set : seqno * | seqno ':' seqno * | set ',' set * seqno: nz-number * | '*' * */ static MsgSet* msgSet(void) { MsgSet head, *last, *ms; ulong from, to; last = &head; head.next = nil; for(;;){ from = seqNo(); to = from; if(peekc() == ':'){ getc(); to = seqNo(); } ms = binalloc(&parseBin, sizeof(MsgSet), 0); if(ms == nil) parseErr("out of memory"); ms->from = from; ms->to = to; ms->next = nil; last->next = ms; last = ms; if(peekc() != ',') break; getc(); } return head.next; } static ulong seqNo(void) { if(peekc() == '*'){ getc(); return ~0UL; } return number(1); } /* * 7 bit, non-ctl chars, no (){%*"\ * NIL is special case for nstring or parenlist */ static char * atom(void) { return atomString(atomStop, ""); } /* * like an atom, but no + */ static char * tag(void) { return atomString("+(){%*\"\\", ""); } /* * string or atom allowing %* */ static char * listmbox(void) { int c; c = peekc(); if(c == '{') return literal(); if(c == '"') return quoted(); return atomString("(){\"\\", ""); } /* * string or atom */ static char * astring(void) { int c; c = peekc(); if(c == '{') return literal(); if(c == '"') return quoted(); return atom(); } /* * 7 bit, non-ctl chars, none from exception list */ static char * atomString(char *disallowed, char *initial) { char *s; int c, ns, as; ns = strlen(initial); s = binalloc(&parseBin, ns + StrAlloc, 0); if(s == nil) parseErr("out of memory"); strcpy(s, initial); as = ns + StrAlloc; for(;;){ c = getc(); if(c <= ' ' || c >= 0x7f || strchr(disallowed, c) != nil){ ungetc(); break; } s[ns++] = c; if(ns >= as){ s = bingrow(&parseBin, s, as, as + StrAlloc, 0); if(s == nil) parseErr("out of memory"); as += StrAlloc; } } if(ns == 0) badsyn(); s[ns] = '\0'; return s; } /* * quoted: '"' chars* '"' * chars: 1-128 except \r and \n */ static char * quoted(void) { char *s; int c, ns, as; mustBe('"'); s = binalloc(&parseBin, StrAlloc, 0); if(s == nil) parseErr("out of memory"); as = StrAlloc; ns = 0; for(;;){ c = getc(); if(c == '"') break; if(c < 1 || c > 0x7f || c == '\r' || c == '\n') badsyn(); if(c == '\\'){ c = getc(); if(c != '\\' && c != '"') badsyn(); } s[ns++] = c; if(ns >= as){ s = bingrow(&parseBin, s, as, as + StrAlloc, 0); if(s == nil) parseErr("out of memory"); as += StrAlloc; } } s[ns] = '\0'; return s; } /* * litlen: {number}\r\n */ static ulong litlen(void) { ulong v; mustBe('{'); v = number(0); mustBe('}'); crnl(); return v; } /* * literal: litlen data<0:litlen> */ static char * literal(void) { char *s; ulong v; v = litlen(); s = binalloc(&parseBin, v+1, 0); if(s == nil) parseErr("out of memory"); Bprint(&bout, "+ Ready for literal data\r\n"); if(Bflush(&bout) < 0) writeErr(); if(v != 0 && Bread(&bin, s, v) != v) badsyn(); s[v] = '\0'; return s; } /* * digits; number is 32 bits */ static ulong number(int nonzero) { ulong v; int c, first; v = 0; first = 1; for(;;){ c = getc(); if(c < '0' || c > '9'){ ungetc(); if(first) badsyn(); break; } if(nonzero && first && c == '0') badsyn(); c -= '0'; first = 0; if(v > UlongMax/10 || v == UlongMax/10 && c > UlongMax%10) parseErr("number out of range\r\n"); v = v * 10 + c; } return v; } static int getc(void) { return Bgetc(&bin); } static void ungetc(void) { Bungetc(&bin); } static int peekc(void) { int c; c = Bgetc(&bin); Bungetc(&bin); return c; } --ssAFoO5g4d Content-Type: text/plain Content-Disposition: inline; filename="fetch.c" Content-Transfer-Encoding: 7bit #include #include #include #include #include "imap4d.h" char *fetchPartNames[FPMax] = { "", "HEADER", "HEADER.FIELDS", "HEADER.FIELDS.NOT", "MIME", "TEXT", }; /* * implicitly set the \seen flag. done in a separate pass * so the .imp file doesn't need to be open while the * messages are sent to the client. */ int fetchSeen(Box *box, Msg *m, int uids, void *vf) { Fetch *f; USED(uids); if(m->expunged) return uids; for(f = vf; f != nil; f = f->next){ switch(f->op){ case FRfc822: case FRfc822Text: case FBodySect: msgSeen(box, m); goto breakout; } } breakout: return 1; } /* * fetch messages * * imap4 body[] requestes get translated to upas/fs files as follows * body[id.header] == id/rawheader file + extra \r\n * body[id.text] == id/rawbody * body[id.mime] == id/mimeheader + extra \r\n * body[id] === body[id.header] + body[id.text] */ int fetchMsg(Box *box, Msg *m, int uids, void *vf) { Tm tm; Fetch *f; char *sep; int todo; if(m->expunged) return uids; todo = 0; for(f = vf; f != nil; f = f->next){ switch(f->op){ case FFlags: /* * flags get sent with status update */ box->sendFlags = 1; m->sendFlags = 1; break; case FUid: todo = 1; break; case FInternalDate: case FEnvelope: case FRfc822: case FRfc822Head: case FRfc822Size: case FRfc822Text: case FBodySect: case FBodyPeek: case FBody: case FBodyStruct: todo = 1; if(!msgStruct(m, 1)){ msgDead(m); return uids; } break; default: bye("bad implementation of fetch"); return 0; } } if(m->expunged) return uids; if(!todo) return 1; /* * note: it is allowed to send back the responses one at a time * rather than all together. this is exploited to send flags elsewhere. */ Bprint(&bout, "* %lud FETCH (", m->seq); sep = ""; if(uids){ Bprint(&bout, "uid %lud", m->uid); sep = " "; } for(f = vf; f != nil; f = f->next){ switch(f->op){ default: bye("bad implementation of fetch"); break; case FFlags: continue; case FUid: if(uids) continue; Bprint(&bout, "%suid %lud", sep, m->uid); break; case FEnvelope: Bprint(&bout, "%senvelope ", sep); fetchEnvelope(m); break; case FInternalDate: Bprint(&bout, "%sinternaldate ", sep); Bimapdate(&bout, date2tm(&tm, m->unixDate)); break; case FBody: Bprint(&bout, "%sbody ", sep); fetchBodyStruct(m, &m->head, 0); break; case FBodyStruct: Bprint(&bout, "%sbodystructure ", sep); fetchBodyStruct(m, &m->head, 1); break; case FRfc822Size: Bprint(&bout, "%srfc822.size %lud", sep, msgSize(m)); break; case FRfc822: f->part = FPAll; Bprint(&bout, "%srfc822", sep); fetchBody(m, f); break; case FRfc822Head: f->part = FPHead; Bprint(&bout, "%srfc822.header", sep); fetchBody(m, f); break; case FRfc822Text: f->part = FPText; Bprint(&bout, "%srfc822.text", sep); fetchBody(m, f); break; case FBodySect: case FBodyPeek: Bprint(&bout, "%sbody", sep); fetchBody(fetchSect(m, f), f); break; } sep = " "; } Bprint(&bout, ")\r\n"); return 1; } /* * print out section, part, headers; * find and return message section */ Msg * fetchSect(Msg *m, Fetch *f) { Bputc(&bout, '['); BNList(&bout, f->sect, "."); if(f->part != FPAll){ if(f->sect != nil) Bputc(&bout, '.'); Bprint(&bout, "%s", fetchPartNames[f->part]); if(f->hdrs != nil){ Bprint(&bout, " ("); BSList(&bout, f->hdrs, " "); Bputc(&bout, ')'); } } Bprint(&bout, "]"); return findMsgSect(m, f->sect); } /* * actually return the body pieces */ void fetchBody(Msg *m, Fetch *f) { Pair p; char *s, *t, *e, buf[BufSize + 2]; ulong n, start, stop, pos; int fd, nn; if(m == nil){ fetchBodyStr(f, "", 0); return; } switch(f->part){ case FPHeadFields: case FPHeadFieldsNot: n = m->head.size + 3; s = emalloc(n); n = selectFields(s, n, m->head.buf, f->hdrs, f->part == FPHeadFields); fetchBodyStr(f, s, n); free(s); return; case FPHead: fetchBodyStr(f, m->head.buf, m->head.size); return; case FPMime: fetchBodyStr(f, m->mime.buf, m->mime.size); return; case FPAll: fd = msgFile(m, "rawbody"); if(fd < 0){ msgDead(m); fetchBodyStr(f, "", 0); return; } p = fetchBodyPart(f, msgSize(m)); start = p.start; if(start < m->head.size){ stop = p.stop; if(stop > m->head.size) stop = m->head.size; Bwrite(&bout, &m->head.buf[start], stop - start); start = 0; stop = p.stop; if(stop <= m->head.size){ close(fd); return; } }else start -= m->head.size; stop = p.stop - m->head.size; break; case FPText: fd = msgFile(m, "rawbody"); if(fd < 0){ msgDead(m); fetchBodyStr(f, "", 0); return; } p = fetchBodyPart(f, m->size); start = p.start; stop = p.stop; break; default: fetchBodyStr(f, "", 0); return; } /* * read in each block, convert \n without \r to \r\n. * this means partial fetch requires fetching everything * through stop, since we don't know how many \r's will be added */ buf[0] = ' '; for(pos = 0; pos < stop; ){ n = BufSize; if(n > stop - pos) n = stop - pos; n = read(fd, &buf[1], n); if(n <= 0){ fetchBodyFill(stop - pos); break; } e = &buf[n + 1]; *e = '\0'; for(s = &buf[1]; s < e && pos < stop; s = t + 1){ t = memchr(s, '\n', e - s); if(t == nil) t = e; n = t - s; if(pos < start){ if(pos + n <= start){ s = t; pos += n; }else{ s += start - pos; pos = start; } n = t - s; } nn = n; if(pos + nn > stop) nn = stop - pos; if(Bwrite(&bout, s, nn) != nn) writeErr(); pos += n; if(*t == '\n'){ if(t[-1] != '\r'){ if(pos >= start && pos < stop) Bputc(&bout, '\r'); pos++; } if(pos >= start && pos < stop) Bputc(&bout, '\n'); pos++; } } buf[0] = e[-1]; } close(fd); } /* * resolve the actual bounds of any partial fetch, * and print out the bounds & size of string returned */ Pair fetchBodyPart(Fetch *f, ulong size) { Pair p; ulong start, stop; start = 0; stop = size; if(f->partial){ start = f->start; if(start > size) start = size; stop = start + f->size; if(stop > size) stop = size; Bprint(&bout, "<%lud>", start); } Bprint(&bout, " {%lud}\r\n", stop - start); p.start = start; p.stop = stop; return p; } /* * something went wrong fetching data * produce fill bytes for what we've committed to produce */ void fetchBodyFill(ulong n) { while(n-- > 0) if(Bputc(&bout, ' ') < 0) writeErr(); } /* * return a simple string */ void fetchBodyStr(Fetch *f, char *buf, ulong size) { Pair p; p = fetchBodyPart(f, size); Bwrite(&bout, &buf[p.start], p.stop-p.start); } char* printnlist(NList *sect) { static char buf[100]; char *p; for(p= buf; sect; sect=sect->next){ p += sprint(p, "%ld", sect->n); if(sect->next) *p++ = '.'; } *p = '\0'; return buf; } /* * find the numbered sub-part of the message */ Msg* findMsgSect(Msg *m, NList *sect) { ulong id; for(; sect != nil; sect = sect->next){ id = sect->n; #ifdef HACK /* HACK to solve extra level of structure not visible from upas/fs */ if(m->kids == 0 && id == 1 && sect->next == nil){ if(m->mime.type->s && strcmp(m->mime.type->s, "message")==0) if(m->mime.type->t && strcmp(m->mime.type->t, "rfc822")==0) if(m->head.type->s && strcmp(m->head.type->s, "text")==0) if(m->head.type->t && strcmp(m->head.type->t, "plain")==0) break; } /* end of HACK */ #endif HACK for(m = m->kids; m != nil; m = m->next) if(m->id == id) break; if(m == nil) return nil; } return m; } void fetchEnvelope(Msg *m) { Tm tm; Bputc(&bout, '('); Brfc822date(&bout, date2tm(&tm, m->info[IDate])); Bputc(&bout, ' '); Bimapstr(&bout, m->info[ISubject]); Bputc(&bout, ' '); Bimapaddr(&bout, m->from); Bputc(&bout, ' '); Bimapaddr(&bout, m->sender); Bputc(&bout, ' '); Bimapaddr(&bout, m->replyTo); Bputc(&bout, ' '); Bimapaddr(&bout, m->to); Bputc(&bout, ' '); Bimapaddr(&bout, m->cc); Bputc(&bout, ' '); Bimapaddr(&bout, m->bcc); Bputc(&bout, ' '); Bimapstr(&bout, m->info[IInReplyTo]); Bputc(&bout, ' '); Bimapstr(&bout, m->info[IMessageId]); Bputc(&bout, ')'); } void fetchBodyStruct(Msg *m, Header *h, int extensions) { Msg *k; ulong len; if(msgIsMulti(h)){ Bputc(&bout, '('); for(k = m->kids; k != nil; k = k->next) fetchBodyStruct(k, &k->mime, extensions); Bputc(&bout, ' '); Bimapstr(&bout, h->type->t); if(extensions){ Bputc(&bout, ' '); BimapMimeParams(&bout, h->type->next); fetchStructExt(h); } Bputc(&bout, ')'); return; } Bputc(&bout, '('); if(h->type != nil){ Bimapstr(&bout, h->type->s); Bputc(&bout, ' '); Bimapstr(&bout, h->type->t); Bputc(&bout, ' '); BimapMimeParams(&bout, h->type->next); }else Bprint(&bout, "\"text\" \"plain\" NIL"); Bputc(&bout, ' '); if(h->id != nil) Bimapstr(&bout, h->id->s); else Bprint(&bout, "NIL"); Bputc(&bout, ' '); if(h->description != nil) Bimapstr(&bout, h->description->s); else Bprint(&bout, "NIL"); Bputc(&bout, ' '); if(h->encoding != nil) Bimapstr(&bout, h->encoding->s); else Bprint(&bout, "NIL"); /* * this is so strange: return lengths for a body[text] response, * except in the case of a multipart message, when return lengths for a body[] response */ len = m->size; if(h == &m->mime) len += m->head.size; Bprint(&bout, " %lud", len); len = m->lines; if(h == &m->mime) len += m->head.lines; if(h->type == nil || cistrcmp(h->type->s, "text") == 0){ Bprint(&bout, " %lud", len); }else if(msgIsRfc822(h)){ Bputc(&bout, ' '); k = m; if(h != &m->mime) k = m->kids; if(k == nil) Bprint(&bout, "(NIL NIL NIL NIL NIL NIL NIL NIL NIL NIL) (\"text\" \"plain\" NIL NIL NIL NIL 0 0) 0"); else{ fetchEnvelope(k); Bputc(&bout, ' '); fetchBodyStruct(k, &k->head, extensions); Bprint(&bout, " %lud", len); } } if(extensions){ Bputc(&bout, ' '); /* * don't have the md5 laying around, * since the header & body have added newlines. */ Bprint(&bout, "NIL"); fetchStructExt(h); } Bputc(&bout, ')'); } /* * common part of bodystructure extensions */ void fetchStructExt(Header *h) { Bputc(&bout, ' '); if(h->disposition != nil){ Bputc(&bout, '('); Bimapstr(&bout, h->disposition->s); Bputc(&bout, ' '); BimapMimeParams(&bout, h->disposition->next); Bputc(&bout, ')'); }else Bprint(&bout, "NIL"); Bputc(&bout, ' '); if(h->language != nil){ if(h->language->next != nil) BimapMimeParams(&bout, h->language->next); else Bimapstr(&bout, h->language->s); }else Bprint(&bout, "NIL"); } int BimapMimeParams(Biobuf *b, MimeHdr *mh) { char *sep; int n; if(mh == nil) return Bprint(b, "NIL"); n = Bputc(b, '('); sep = ""; for(; mh != nil; mh = mh->next){ n += Bprint(b, sep); n += Bimapstr(b, mh->s); n += Bputc(b, ' '); n += Bimapstr(b, mh->t); sep = " "; } n += Bputc(b, ')'); return n; } /* * print a list of addresses; * each address is printed as '(' personalName AtDomainList mboxName hostName ')' * the AtDomainList is always NIL */ int Bimapaddr(Biobuf *b, MAddr *a) { char *host, *sep; int n; if(a == nil) return Bprint(b, "NIL"); n = Bputc(b, '('); sep = ""; for(; a != nil; a = a->next){ n += Bprint(b, "%s(", sep); n += Bimapstr(b, a->personal); n += Bprint(b," NIL "); n += Bimapstr(b, a->box); n += Bputc(b, ' '); /* * can't send NIL as hostName, since that is code for a group */ host = a->host; if(host == nil) host = ""; n += Bimapstr(b, host); n += Bputc(b, ')'); sep = " "; } n += Bputc(b, ')'); return n; } --ssAFoO5g4d Content-Type: text/plain; charset=us-ascii Content-Description: message body and .signature Content-Transfer-Encoding: 7bit (sorry, I don't have diff access to the original version due to a net failure). -- Saludos, Gorka "Curiosity sKilled the cat" --ssAFoO5g4d--