* [9fans] imap4d bug
@ 2002-11-15 15:41 paurea
2002-11-15 16:06 ` paurea
0 siblings, 1 reply; 6+ messages in thread
From: paurea @ 2002-11-15 15:41 UTC (permalink / raw)
To: 9fans
[-- Attachment #1: message body text --]
[-- Type: text/plain, Size: 226 bytes --]
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:
[-- Attachment #2: imap4d.c --]
[-- Type: text/plain, Size: 42283 bytes --]
#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(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;
}
[-- Attachment #3: fetch.c --]
[-- Type: text/plain, Size: 12391 bytes --]
#include <u.h>
#include <libc.h>
#include <bio.h>
#include <auth.h>
#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;
}
[-- Attachment #4: message body and .signature --]
[-- Type: text/plain, Size: 177 bytes --]
(sorry, I don't have diff access to the original version due to a net failure).
--
Saludos,
Gorka
"Curiosity sKilled the cat"
^ permalink raw reply [flat|nested] 6+ messages in thread
* Re: [9fans] imap4d bug
2002-11-15 15:41 [9fans] imap4d bug paurea
@ 2002-11-15 16:06 ` paurea
2002-11-15 16:33 ` paurea
0 siblings, 1 reply; 6+ messages in thread
From: paurea @ 2002-11-15 16:06 UTC (permalink / raw)
To: 9fans
[-- Attachment #1: message body text --]
[-- Type: text/plain, Size: 410 bytes --]
paurea@gsyc.escet.urjc.es writes:
> From: paurea@gsyc.escet.urjc.es
> Subject: [9fans] imap4d bug
> Date: Fri, 15 Nov 2002 16:41:15 +0100
>
> 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...).
>
Now it works with evolution too (more downcase commands).
[-- Attachment #2: imap4d.c --]
[-- Type: text/plain, Size: 42283 bytes --]
#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(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;
}
[-- Attachment #3: .signature --]
[-- Type: text/plain, Size: 96 bytes --]
--
Saludos,
Gorka
"Curiosity sKilled the cat"
^ permalink raw reply [flat|nested] 6+ messages in thread
* Re: [9fans] imap4d bug
2002-11-15 16:06 ` paurea
@ 2002-11-15 16:33 ` paurea
0 siblings, 0 replies; 6+ messages in thread
From: paurea @ 2002-11-15 16:33 UTC (permalink / raw)
To: 9fans
paurea@gsyc.escet.urjc.es writes:
> Now it works with evolution too (more downcase commands).
Not true... It works just the authentication (before it didn't). It
works with emacs (vm) and mozilla and fetchmail, but not evolution.
Maybe more downcase things...
--
Saludos,
Gorka
"Curiosity sKilled the cat"
^ permalink raw reply [flat|nested] 6+ messages in thread
* Re: [9fans] imap4d bug
2002-11-15 18:03 presotto
@ 2002-11-17 14:22 ` paurea
0 siblings, 0 replies; 6+ messages in thread
From: paurea @ 2002-11-17 14:22 UTC (permalink / raw)
To: 9fans
presotto@plan9.bell-labs.com writes:
> From: presotto@plan9.bell-labs.com
> Subject: Re: [9fans] imap4d bug
> Date: Fri, 15 Nov 2002 13:03:36 -0500
>
> Changing the case of the arguments to cistrcmp (the case insensitive
> compare) is a noop.
It does seem to matter with fetchmail, vm (emacs reader) and evolution.
--
Saludos,
Gorka
"Curiosity sKilled the cat"
^ permalink raw reply [flat|nested] 6+ messages in thread
* Re: [9fans] imap4d bug
@ 2002-11-15 21:04 presotto
0 siblings, 0 replies; 6+ messages in thread
From: presotto @ 2002-11-15 21:04 UTC (permalink / raw)
To: paurea, 9fans
[-- Attachment #1: Type: text/plain, Size: 579 bytes --]
I've got some questions about this bug fix. Not all responses are
converted from lower case to upper case. Was this intentional
or an oversight?
Also, a passCR()/allowCR seems to have crept in somewhere after sources.
I have no problem with it but in the code it is functionally
exclusive with passLogin()/allowPass. Should I exit with an error
if a user specifies both or did you intend something else?
Finally, as I said before, the cistrcmp's shouldn't care about
case. Did you find that they did, i.e., are you reporting a bug
or did you just not understand?
[-- Attachment #2: Type: message/rfc822, Size: 45149 bytes --]
[-- Attachment #2.1.1: message body text --]
[-- Type: text/plain, Size: 410 bytes --]
paurea@gsyc.escet.urjc.es writes:
> From: paurea@gsyc.escet.urjc.es
> Subject: [9fans] imap4d bug
> Date: Fri, 15 Nov 2002 16:41:15 +0100
>
> 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...).
>
Now it works with evolution too (more downcase commands).
[-- Attachment #2.1.2: imap4d.c --]
[-- Type: text/plain, Size: 42283 bytes --]
#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(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;
}
[-- Attachment #2.1.3: .signature --]
[-- Type: text/plain, Size: 96 bytes --]
--
Saludos,
Gorka
"Curiosity sKilled the cat"
^ permalink raw reply [flat|nested] 6+ messages in thread
* Re: [9fans] imap4d bug
@ 2002-11-15 18:03 presotto
2002-11-17 14:22 ` paurea
0 siblings, 1 reply; 6+ messages in thread
From: presotto @ 2002-11-15 18:03 UTC (permalink / raw)
To: 9fans
Changing the case of the arguments to cistrcmp (the case insensitive
compare) is a noop.
^ permalink raw reply [flat|nested] 6+ messages in thread
end of thread, other threads:[~2002-11-17 14:22 UTC | newest]
Thread overview: 6+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2002-11-15 15:41 [9fans] imap4d bug paurea
2002-11-15 16:06 ` paurea
2002-11-15 16:33 ` paurea
2002-11-15 18:03 presotto
2002-11-17 14:22 ` paurea
2002-11-15 21:04 presotto
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox;
as well as URLs for NNTP newsgroup(s).