* [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 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
* 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
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).