9fans - fans of the OS Plan 9 from Bell Labs
 help / color / mirror / Atom feed
* [9fans] imap4d bug
@ 2002-11-15 15:41 paurea
  2002-11-15 16:06 ` paurea
  0 siblings, 1 reply; 6+ messages in thread
From: paurea @ 2002-11-15 15:41 UTC (permalink / raw)
  To: 9fans

[-- Attachment #1: message body text --]
[-- Type: text/plain, Size: 226 bytes --]

Some of the imap commands where written downcase and because of that it
didn't interact with fetchmail, emacs or Netscape 7 mail reader.(it did
work with mozilla don't know why...).

This are the archives which change:


[-- Attachment #2: imap4d.c --]
[-- Type: text/plain, Size: 42283 bytes --]

#include <u.h>
#include <libc.h>
#include <auth.h>
#include <bio.h>
#include "imap4d.h"

/*
 * these should be in libraries
 */
char	*csquery(char *attr, char *val, char *rattr);

/*
 * /lib/rfc/rfc2060 imap4rev1
 * /lib/rfc/rfc2683 is implementation advice
 * /lib/rfc/rfc2342 is namespace capability
 * /lib/rfc/rfc2222 is security protocols
 * /lib/rfc/rfc1731 is security protocols
 * /lib/rfc/rfc2221 is LOGIN-REFERRALS
 * /lib/rfc/rfc2193 is MAILBOX-REFERRALS
 * /lib/rfc/rfc2177 is IDLE capability
 * /lib/rfc/rfc2195 is CRAM-MD5 authentication
 * /lib/rfc/rfc2088 is LITERAL+ capability
 * /lib/rfc/rfc1760 is S/Key authentication
 *
 * outlook uses "Secure Password Authentication" aka ntlm authentication
 *
 * capabilities from nslocum
 * CAPABILITY IMAP4 IMAP4REV1 NAMESPACE IDLE SCAN SORT MAILBOX-REFERRALS LOGIN-REFERRALS AUTH=LOGIN THREAD=ORDEREDSUBJECT
 */

typedef struct	ParseCmd	ParseCmd;

enum
{
	UlongMax	= 4294967295,
};

struct ParseCmd
{
	char	*name;
	void	(*f)(char *tg, char *cmd);
};

static	void	appendCmd(char *tg, char *cmd);
static	void	authenticateCmd(char *tg, char *cmd);
static	void	capabilityCmd(char *tg, char *cmd);
static	void	closeCmd(char *tg, char *cmd);
static	void	copyCmd(char *tg, char *cmd);
static	void	createCmd(char *tg, char *cmd);
static	void	deleteCmd(char *tg, char *cmd);
static	void	expungeCmd(char *tg, char *cmd);
static	void	fetchCmd(char *tg, char *cmd);
static	void	idleCmd(char *tg, char *cmd);
static	void	listCmd(char *tg, char *cmd);
static	void	loginCmd(char *tg, char *cmd);
static	void	logoutCmd(char *tg, char *cmd);
static	void	namespaceCmd(char *tg, char *cmd);
static	void	noopCmd(char *tg, char *cmd);
static	void	renameCmd(char *tg, char *cmd);
static	void	searchCmd(char *tg, char *cmd);
static	void	selectCmd(char *tg, char *cmd);
static	void	statusCmd(char *tg, char *cmd);
static	void	storeCmd(char *tg, char *cmd);
static	void	subscribeCmd(char *tg, char *cmd);
static	void	uidCmd(char *tg, char *cmd);
static	void	unsubscribeCmd(char *tg, char *cmd);

static	void	copyUCmd(char *tg, char *cmd, int uids);
static	void	fetchUCmd(char *tg, char *cmd, int uids);
static	void	searchUCmd(char *tg, char *cmd, int uids);
static	void	storeUCmd(char *tg, char *cmd, int uids);

static	void	imap4(int);
static	void	status(int expungeable, int uids);
static	void	cleaner(void);
static	void	check(void);
static	int	catcher(void*, char*);

static	Search	*searchKey(int first);
static	Search	*searchKeys(int first, Search *tail);
static	char	*astring(void);
static	char	*atomString(char *disallowed, char *initial);
static	char	*atom(void);
static	void	badsyn(void);
static	void	clearcmd(void);
static	char	*command(void);
static	void	crnl(void);
static	Fetch	*fetchAtt(char *s, Fetch *f);
static	Fetch	*fetchWhat(void);
static	int	flagList(void);
static	int	flags(void);
static	int	getc(void);
static	char	*listmbox(void);
static	char	*literal(void);
static	ulong	litlen(void);
static	MsgSet	*msgSet(void);
static	void	mustBe(int c);
static	ulong	number(int nonzero);
static	int	peekc(void);
static	char	*quoted(void);
static	void	sectText(Fetch *f, int mimeOk);
static	ulong	seqNo(void);
static	Store	*storeWhat(void);
static	char	*tag(void);
static	void	ungetc(void);

static	ParseCmd	SNonAuthed[] =
{
	{"capability",		capabilityCmd},
	{"logout",		logoutCmd},
	{"x-exit",		logoutCmd},
	{"noop",		noopCmd},

	{"login",		loginCmd},
	{"authenticate",	authenticateCmd},

	nil
};

static	ParseCmd	SAuthed[] =
{
	{"capability",		capabilityCmd},
	{"logout",		logoutCmd},
	{"x-exit",		logoutCmd},
	{"noop",		noopCmd},

	{"append",		appendCmd},
	{"create",		createCmd},
	{"delete",		deleteCmd},
	{"examine",		selectCmd},
	{"select",		selectCmd},
	{"idle",		idleCmd},
	{"list",		listCmd},
	{"lsub",		listCmd},
	{"namespace",		namespaceCmd},
	{"rename",		renameCmd},
	{"status",		statusCmd},
	{"subscribe",		subscribeCmd},
	{"unsubscribe",		unsubscribeCmd},

	nil
};

static	ParseCmd	SSelected[] =
{
	{"capability",		capabilityCmd},
	{"logout",		logoutCmd},
	{"x-exit",		logoutCmd},
	{"noop",		noopCmd},

	{"append",		appendCmd},
	{"create",		createCmd},
	{"delete",		deleteCmd},
	{"examine",		selectCmd},
	{"select",		selectCmd},
	{"idle",		idleCmd},
	{"list",		listCmd},
	{"lsub",		listCmd},
	{"namespace",		namespaceCmd},
	{"rename",		renameCmd},
	{"status",		statusCmd},
	{"subscribe",		subscribeCmd},
	{"unsubscribe",		unsubscribeCmd},

	{"check",		noopCmd},
	{"close",		closeCmd},
	{"copy",		copyCmd},
	{"expunge",		expungeCmd},
	{"fetch",		fetchCmd},
	{"search",		searchCmd},
	{"store",		storeCmd},
	{"uid",			uidCmd},

	nil
};

static	char		*atomStop = "(){%*\"\\";
static	Chalstate	*chal;
static	int		chaled;
static	ParseCmd	*imapState;
static	jmp_buf		parseJmp;
static	char		*parseMsg;
static	int		allowPass;
static	int		allowCR;
static	int		exiting;
static	QLock		imaplock;
static	int		idlepid = -1;

Biobuf	bout;
Biobuf	bin;
char	username[UserNameLen];
char	mboxDir[MboxNameLen];
char	*servername;
char	*site;
char	*remote;
Box	*selected;
Bin	*parseBin;
int	debug;

void
main(int argc, char *argv[])
{
	char *s, *t;
	int preauth, n;

	Binit(&bin, 0, OREAD);
	Binit(&bout, 1, OWRITE);

	preauth = 0;
	allowPass = 0;
	allowCR = 0;
	ARGBEGIN{
	case 'a':
		preauth = 1;
		break;
	case 'd':
		site = ARGF();
		break;
	case 'c':
		allowCR = 1;
		break;
	case 'p':
		allowPass = 1;
		break;
	case 'r':
		remote = ARGF();
		break;
	case 's':
		servername = ARGF();
		break;
	case 'v':
		debug = 1;
		debuglog("imap4d debugging enabled\n");
		break;
	default:
		fprint(2, "usage: ip/imap4d [-acp] [-d site] [-r remotehost] [-s servername]\n");
		bye("usage");
		break;
	}ARGEND

	if(preauth)
		setupuser(nil);

	if(servername == nil){
		servername = csquery("sys", sysname(), "dom");
		if(servername == nil)
			servername = sysname();
		if(servername == nil){
			fprint(2, "ip/imap4d can't find server name: %r\n");
			bye("can't find system name");
		}
	}
	if(site == nil){
		t = getenv("site");
		if(t == nil)
			site = servername;
		else{
			n = strlen(t);
			s = strchr(servername, '.');
			if(s == nil)
				s = servername;
			else
				s++;
			n += strlen(s) + 2;
			site = emalloc(n);
			snprint(site, n, "%s.%s", t, s);
		}
	}

	rfork(RFNOTEG|RFREND);

	atnotify(catcher, 1);
	qlock(&imaplock);
	atexit(cleaner);
	imap4(preauth);
}

static void
imap4(int preauth)
{
	char *volatile tg;
	char *volatile cmd;
	ParseCmd *st;

	if(preauth){
		Bprint(&bout, "* preauth %s IMAP4rev1 server ready user %s authenticated\r\n", servername, username);
		imapState = SAuthed;
	}else{
		Bprint(&bout, "* ok %s IMAP4rev1 server ready\r\n", servername);
		imapState = SNonAuthed;
	}
	if(Bflush(&bout) < 0)
		writeErr();

	chaled = 0;

	tg = nil;
	cmd = nil;
	if(setjmp(parseJmp)){
		if(tg == nil)
			Bprint(&bout, "* bad empty command line: %s\r\n", parseMsg);
		else if(cmd == nil)
			Bprint(&bout, "%s bad no command: %s\r\n", tg, parseMsg);
		else
			Bprint(&bout, "%s bad %s %s\r\n", tg, cmd, parseMsg);
		clearcmd();
		if(Bflush(&bout) < 0)
			writeErr();
		binfree(&parseBin);
	}
	for(;;){
		if(mbLocked())
			bye("internal error: mailbox lock held");
		tg = nil;
		cmd = nil;
		tg = tag();
		mustBe(' ');
		cmd = atom();

		/*
		 * note: outlook express is broken: it requires echoing the
		 * command as part of matching response
		 */
		for(st = imapState; st->name != nil; st++){
			if(cistrcmp(cmd, st->name) == 0){
				(*st->f)(tg, cmd);
				break;
			}
		}
		if(st->name == nil){
			clearcmd();
			Bprint(&bout, "%s bad %s illegal command\r\n", tg, cmd);
		}

		if(Bflush(&bout) < 0)
			writeErr();
		binfree(&parseBin);
	}
}

void
bye(char *fmt, ...)
{
	va_list arg;

	va_start(arg, fmt);
	Bprint(&bout, "* bye ");
	Bvprint(&bout, fmt, arg);
	Bprint(&bout, "\r\n");
	Bflush(&bout);
exits("rob2");
	exits(0);
}

void
parseErr(char *msg)
{
	parseMsg = msg;
	longjmp(parseJmp, 1);
}

/*
 * an error occured while writing to the client
 */
void
writeErr(void)
{
	cleaner();
	_exits("connection closed");
}

static int
catcher(void *v, char *msg)
{
	USED(v);
	if(strstr(msg, "closed pipe") != nil)
		return 1;
	return 0;
}

/*
 * wipes out the idleCmd backgroung process if it is around.
 * this can only be called if the current proc has qlocked imaplock.
 * it must be the last piece of imap4d code executed.
 */
static void
cleaner(void)
{
	int i;

	if(idlepid < 0)
		return;
	exiting = 1;
	close(0);
	close(1);
	close(2);

	/*
	 * the other proc is either stuck in a read, a sleep,
	 * or is trying to lock imap4lock.
	 * get him out of it so he can exit cleanly
	 */
	qunlock(&imaplock);
	for(i = 0; i < 4; i++)
		postnote(PNGROUP, getpid(), "die");
}

/*
 * send any pending status updates to the client
 * careful: shouldn't exit, because called by idle polling proc
 *
 * can't always send pending info
 * in particular, can't send expunge info
 * in response to a fetch, store, or search command.
 *
 * rfc2060 5.2:	server must send mailbox size updates
 * rfc2060 5.2:	server may send flag updates
 * rfc2060 5.5:	servers prohibited from sending expunge while fetch, store, search in progress
 * rfc2060 7:	in selected state, server checks mailbox for new messages as part of every command
 * 		sends untagged EXISTS and RECENT respsonses reflecting new size of the mailbox
 * 		should also send appropriate untagged FETCH and EXPUNGE messages if another agent
 * 		changes the state of any message flags or expunges any messages
 * rfc2060 7.4.1	expunge server response must not be sent when no command is in progress,
 * 		nor while responding to a fetch, stort, or search command (uid versions are ok)
 * 		command only "in progress" after entirely parsed.
 *
 * strategy for third party deletion of messages or of a mailbox
 *
 * deletion of a selected mailbox => act like all message are expunged
 *	not strictly allowed by rfc2180, but close to method 3.2.
 *
 * renaming same as deletion
 *
 * copy
 *	reject iff a deleted message is in the request
 *
 * search, store, fetch operations on expunged messages
 *	ignore the expunged messages
 *	return tagged no if referenced
 */
static void
status(int expungeable, int uids)
{
	int tell;

	if(!selected)
		return;
	tell = 0;
	if(expungeable)
		tell = expungeMsgs(selected, 1);
	if(selected->sendFlags)
		sendFlags(selected, uids);
	if(tell || selected->toldMax != selected->max){
		Bprint(&bout, "* %lud exists\r\n", selected->max);
		selected->toldMax = selected->max;
	}
	if(tell || selected->toldRecent != selected->recent){
		Bprint(&bout, "* %lud recent\r\n", selected->recent);
		selected->toldRecent = selected->recent;
	}
	if(tell)
		closeImp(selected, checkBox(selected, 1));
}

/*
 * careful: can't exit, because called by idle polling proc
 */
static void
check(void)
{
	if(!selected)
		return;
	checkBox(selected, 0);
	status(1, 0);
}

static void
appendCmd(char *tg, char *cmd)
{
	char *mbox, head[128];
	ulong t, n, now;
	int flags, ok;

	mustBe(' ');
	mbox = astring();
	mustBe(' ');
	flags = 0;
	if(peekc() == '('){
		flags = flagList();
		mustBe(' ');
	}
	now = time(nil);
	if(peekc() == '"'){
		t = imap4DateTime(quoted());
		if(t == ~0)
			parseErr("illegal date format");
		mustBe(' ');
		if(t > now)
			t = now;
	}else
		t = now;
	n = litlen();

	mbox = mboxName(mbox);
	if(mbox == nil || !okMbox(mbox)){
		check();
		Bprint(&bout, "%s no %s bad mailbox\r\n", tg, cmd);
		return;
	}
	if(!cdExists(mboxDir, mbox)){
		check();
		Bprint(&bout, "%s no [TRYCREATE] %s mailbox does not exist\r\n", tg, cmd);
		return;
	}

	snprint(head, sizeof(head), "From %s %s", username, ctime(t));
	ok = appendSave(mbox, flags, head, &bin, n);
	crnl();
	check();
	if(ok)
		Bprint(&bout, "%s OK %s completed\r\n", tg, cmd);
	else
		Bprint(&bout, "%s no %s message save failed\r\n", tg, cmd);
}

static void
authenticateCmd(char *tg, char *cmd)
{
	char *s, *t;

	mustBe(' ');
	s = atom();
	crnl();
	auth_freechal(chal);
	chal = nil;
	if(cistrcmp(s, "cram-md5") == 0){
		t = cramauth();
		if(t == nil){
			Bprint(&bout, "%s ok %s\r\n", tg, cmd);
			imapState = SAuthed;
		}else
			Bprint(&bout, "%s no %s failed %s\r\n", tg, cmd, t);
	}else
		Bprint(&bout, "%s no %s unsupported authentication protocol\r\n", tg, cmd);
}

static void
capabilityCmd(char *tg, char *cmd)
{
	crnl();
	check();
	Bprint(&bout, "* capability IMAP4rev1 idle namespace auth=cram-md5\r\n");
	Bprint(&bout, "%s ok %s\r\n", tg, cmd);
}

static void
closeCmd(char *tg, char *cmd)
{
	crnl();
	imapState = SAuthed;
	closeBox(selected, 1);
	selected = nil;
	Bprint(&bout, "%s ok %s mailbox closed, now in authenticated state\r\n", tg, cmd);
}

/*
 * note: message id's are before any pending expunges
 */
static void
copyCmd(char *tg, char *cmd)
{
	copyUCmd(tg, cmd, 0);
}

static void
copyUCmd(char *tg, char *cmd, int uids)
{
	MsgSet *ms;
	char *uid, *mbox;
	ulong max;
	int ok;

	mustBe(' ');
	ms = msgSet();
	mustBe(' ');
	mbox = astring();
	crnl();

	uid = "";
	if(uids)
		uid = "uid ";

	mbox = mboxName(mbox);
	if(mbox == nil || !okMbox(mbox)){
		status(1, uids);
		Bprint(&bout, "%s no %s%s bad mailbox\r\n", tg, uid, cmd);
		return;
	}
	if(!cdExists(mboxDir, mbox)){
		check();
		Bprint(&bout, "%s no [TRYCREATE] %s mailbox does not exist\r\n", tg, cmd);
		return;
	}

	max = selected->max;
	checkBox(selected, 0);
	ok = forMsgs(selected, ms, max, uids, copyCheck, nil);
	if(ok)
		ok = forMsgs(selected, ms, max, uids, copySave, mbox);

	status(1, uids);
	if(ok)
		Bprint(&bout, "%s OK %s%s completed\r\n", tg, uid, cmd);
	else
		Bprint(&bout, "%s no %s%s failed\r\n", tg, uid, cmd);
}

static void
createCmd(char *tg, char *cmd)
{
	char *mbox, *m;
	int fd, slash;

	mustBe(' ');
	mbox = astring();
	crnl();
	check();

	m = strchr(mbox, '\0');
	slash = m != mbox && m[-1] == '/';
	mbox = mboxName(mbox);
	if(mbox == nil || !okMbox(mbox)){
		Bprint(&bout, "%s no %s bad mailbox\r\n", tg, cmd);
		return;
	}
	if(cistrcmp(mbox, "inbox") == 0){
		Bprint(&bout, "%s no %s cannot remotely create INBOX\r\n", tg, cmd);
		return;
	}
	if(access(mbox, AEXIST) >= 0){
		Bprint(&bout, "%s no %s mailbox already exists\r\n", tg, cmd);
		return;
	}

	fd = createBox(mbox, slash);
	close(fd);
	if(fd < 0)
		Bprint(&bout, "%s no %s cannot create mailbox %s\r\n", tg, cmd, mbox);
	else
		Bprint(&bout, "%s OK %s %s completed\r\n", tg, mbox, cmd);
}

static void
deleteCmd(char *tg, char *cmd)
{
	char *mbox, *imp;

	mustBe(' ');
	mbox = astring();
	crnl();
	check();

	mbox = mboxName(mbox);
	if(mbox == nil || !okMbox(mbox)){
		Bprint(&bout, "%s no %s bad mailbox\r\n", tg, cmd);
		return;
	}

	imp = impName(mbox);
	if(cistrcmp(mbox, "inbox") == 0
	|| imp != nil && cdRemove(mboxDir, imp) < 0 && cdExists(mboxDir, imp)
	|| cdRemove(mboxDir, mbox) < 0)
		Bprint(&bout, "%s no %s cannot delete mailbox %s\r\n", tg, cmd, mbox);
	else
		Bprint(&bout, "%s OK %s %s completed\r\n", tg, mbox, cmd);
}

static void
expungeCmd(char *tg, char *cmd)
{
	int ok;

	crnl();
	ok = deleteMsgs(selected);
	check();
	if(ok)
		Bprint(&bout, "%s ok %s messages erased\r\n", tg, cmd);
	else
		Bprint(&bout, "%s no %s some messages not expunged\r\n", tg, cmd);
}

static void
fetchCmd(char *tg, char *cmd)
{
	fetchUCmd(tg, cmd, 0);
}

static void
fetchUCmd(char *tg, char *cmd, int uids)
{
	Fetch *f;
	MsgSet *ms;
	MbLock *ml;
	char *uid;
	ulong max;
	int ok;

	mustBe(' ');
	ms = msgSet();
	mustBe(' ');
	f = fetchWhat();
	crnl();
	uid = "";
	if(uids)
		uid = "uid ";
	max = selected->max;
	ml = checkBox(selected, 1);
	if(ml != nil)
		forMsgs(selected, ms, max, uids, fetchSeen, f);
	closeImp(selected, ml);
	ok = ml != nil && forMsgs(selected, ms, max, uids, fetchMsg, f);
	status(uids, uids);
	if(ok)
		Bprint(&bout, "%s OK %s%s completed\r\n", tg, uid, cmd);
	else
		Bprint(&bout, "%s no %s%s failed\r\n", tg, uid, cmd);
}

static void
idleCmd(char *tg, char *cmd)
{
	int c, pid;

	crnl();
	Bprint(&bout, "+ idling, waiting for done\r\n");
	if(Bflush(&bout) < 0)
		writeErr();

	if(idlepid < 0){
		pid = rfork(RFPROC|RFMEM|RFNOWAIT);
		if(pid == 0){
			for(;;){
				qlock(&imaplock);
				if(exiting)
					break;

				/*
				 * parent may have changed curDir, but it doesn't change our .
				 */
				resetCurDir();

				check();
				if(Bflush(&bout) < 0)
					writeErr();
				qunlock(&imaplock);
				sleep(15*1000);
				enableForwarding();
			}
_exits("rob3");
			_exits(0);
		}
		idlepid = pid;
	}

	qunlock(&imaplock);

	/*
	 * clear out the next line, which is supposed to contain (case-insensitive)
	 * done\n
	 * this is special code since it has to dance with the idle polling proc
	 * and handle exiting correctly.
	 */
	for(;;){
		c = getc();
		if(c < 0){
			qlock(&imaplock);
			if(!exiting)
				cleaner();
_exits("rob4");
			_exits(0);
		}
		if(c == '\n')
			break;
	}

	qlock(&imaplock);
	if(exiting)
{_exits("rob5");
		_exits(0);
}

	/*
	 * child may have changed curDir, but it doesn't change our .
	 */
	resetCurDir();

	check();
	Bprint(&bout, "%s ok %s terminated\r\n", tg, cmd);
}

static void
listCmd(char *tg, char *cmd)
{
	char *s, *t, *ss, *ref, *mbox;
	int n;

	mustBe(' ');
	s = astring();
	mustBe(' ');
	t = listmbox();
	crnl();
	check();
	ref = mutf7str(s);
	mbox = mutf7str(t);
	if(ref == nil || mbox == nil){
		Bprint(&bout, "%s bad %s mailbox name not in modified utf-7\r\n", tg, cmd);
		return;
	}

	/*
	 * special request for hierarchy delimiter and root name
	 * root name appears to be name up to and including any delimiter,
	 * or the empty string, if there is no delimiter.
	 *
	 * this must change if the # namespace convention is supported.
	 */
	if(*mbox == '\0'){
		s = strchr(ref, '/');
		if(s == nil)
			ref = "";
		else
			s[1] = '\0';
		Bprint(&bout, "* %s (\\Noselect) \"/\" \"%s\"\r\n", cmd, ref);
		Bprint(&bout, "%s ok %s\r\n", tg, cmd);
		return;
	}


	/*
	 * massage the listing name:
	 * clean up the components individually,
	 * then rip off componenets from the ref to
	 * take care of leading ..'s in the mbox.
	 *
	 * the cleanup can wipe out * followed by a ..
	 * tough luck if such a stupid pattern is given.
	 */
	cleanname(mbox);
	if(strcmp(mbox, ".") == 0)
		*mbox = '\0';
	if(mbox[0] == '/')
		*ref = '\0';
	else if(*ref != '\0'){
		cleanname(ref);
		if(strcmp(ref, ".") == 0)
			*ref = '\0';
	}else
		*ref = '\0';
	while(*ref && isdotdot(mbox)){
		s = strrchr(ref, '/');
		if(s == nil)
			s = ref;
		if(isdotdot(s))
			break;
		*s = '\0';
		mbox += 2;
		if(*mbox == '/')
			mbox++;
	}
	if(*ref == '\0'){
		s = mbox;
		ss = s;
	}else{
		n = strlen(ref) + strlen(mbox) + 2;
		t = binalloc(&parseBin, n, 0);
		if(t == nil)
			parseErr("out of memory");
		snprint(t, n, "%s/%s", ref, mbox);
		s = t;
		ss = s + strlen(ref);
	}

	/*
	 * only allow activity in /mail/box
	 */
	if(s[0] == '/' || isdotdot(s)){
		Bprint(&bout, "%s no illegal mailbox pattern\r\n", tg);
		return;
	}

	if(cistrcmp(cmd, "lsub") == 0)
		lsubBoxes(cmd, s, ss);
	else
		listBoxes(cmd, s, ss);
	Bprint(&bout, "%s OK %s completed\r\n", tg, cmd);
}

static char*
passCR(char*u, char*p)
{
	static char Ebadch[] = "can't get challenge";
	static char nchall[64];
	static char response[64];
	static Chalstate *ch = nil;
	AuthInfo *ai;

again:
	if (ch == nil){
		if(!(ch = auth_challenge("proto=p9cr role=server user=%q", u)))
			return Ebadch;
		snprint(nchall, 64, " encrypt challenge: %s", ch->chal);
		return nchall;
	} else {
		strncpy(response, p, 64);
		ch->resp = response;
		ch->nresp = strlen(response);
		ai = auth_response(ch);
		auth_freechal(ch);
		ch = nil;
		if (ai == nil)
			goto again;
		setupuser(ai);
		return nil;
	}

}

static void
loginCmd(char *tg, char *cmd)
{
	char *s, *t;
	AuthInfo *ai;
	char*r;
	mustBe(' ');
	s = astring();	/* uid */
	mustBe(' ');
	t = astring();	/* password */
	crnl();
	if(allowCR){
		if ((r = passCR(s, t)) == nil){
			Bprint(&bout, "%s ok %s succeeded\r\n", tg, cmd);
			imapState = SAuthed;
		} else {
			Bprint(&bout, "* no [ALERT] %s\r\n", r);
			Bprint(&bout, "%s no %s succeeded\r\n", tg, cmd);
		}
		return;
	}
	else if(allowPass){
		if(ai = passLogin(s, t)){
			setupuser(ai);
			Bprint(&bout, "%s ok %s succeeded\r\n", tg, cmd);
			imapState = SAuthed;
		}else
			Bprint(&bout, "%s no %s failed check\r\n", tg, cmd);
		return;
	}
	Bprint(&bout, "%s no %s plaintext passwords disallowed\r\n", tg, cmd);
}

/*
 * logout or x-exit, which doesn't expunge the mailbox
 */
static void
logoutCmd(char *tg, char *cmd)
{
	crnl();

	if(cmd[0] != 'x' && selected){
		closeBox(selected, 1);
		selected = nil;
	}
	Bprint(&bout, "* bye\r\n");
	Bprint(&bout, "%s OK %s completed\r\n", tg, cmd);
exits("rob6");
	exits(0);
}

static void
namespaceCmd(char *tg, char *cmd)
{
	crnl();
	check();

	/*
	 * personal, other users, shared namespaces
	 * send back nil or descriptions of (prefix heirarchy-delim) for each case
	 */
	Bprint(&bout, "* namespace ((\"\" \"/\")) nil nil\r\n");
	Bprint(&bout, "%s OK %s completed\r\n", tg, cmd);
}

static void
noopCmd(char *tg, char *cmd)
{
	crnl();
	check();
	Bprint(&bout, "%s OK %s completed\r\n", tg, cmd);
	enableForwarding();
}

/*
 * this is only a partial implementation
 * should copy files to other directories,
 * and copy & truncate inbox
 */
static void
renameCmd(char *tg, char *cmd)
{
	char *from, *to;
	int ok;

	mustBe(' ');
	from = astring();
	mustBe(' ');
	to = astring();
	crnl();
	check();

	to = mboxName(to);
	if(to == nil || !okMbox(to) || cistrcmp(to, "inbox") == 0){
		Bprint(&bout, "%s no %s bad mailbox destination name\r\n", tg, cmd);
		return;
	}
	if(access(to, AEXIST) >= 0){
		Bprint(&bout, "%s no %s mailbox already exists\r\n", tg, cmd);
		return;
	}
	from = mboxName(from);
	if(from == nil || !okMbox(from)){
		Bprint(&bout, "%s no %s bad mailbox destination name\r\n", tg, cmd);
		return;
	}
	if(cistrcmp(from, "inbox") == 0)
		ok = copyBox(from, to, 0);
	else
		ok = moveBox(from, to);

	if(ok)
		Bprint(&bout, "%s OK %s completed\r\n", tg, cmd);
	else
		Bprint(&bout, "%s no %s failed\r\n", tg, cmd);
}

static void
searchCmd(char *tg, char *cmd)
{
	searchUCmd(tg, cmd, 0);
}

static void
searchUCmd(char *tg, char *cmd, int uids)
{
	Search rock;
	Msg *m;
	char *uid;
	ulong id;

	mustBe(' ');
	rock.next = nil;
	searchKeys(1, &rock);
	crnl();
	uid = "";
	if(uids)
		uid = "uid ";
	if(rock.next != nil && rock.next->key == SKCharset){
		if(cistrstr(rock.next->s, "utf-8") != 0
		&& cistrcmp(rock.next->s, "us-ascii") != 0){
			Bprint(&bout, "%s no [BADCHARSET] (\"US-ASCII\" \"UTF-8\") %s%s failed\r\n", tg, uid, cmd);
			checkBox(selected, 0);
			status(uids, uids);
			return;
		}
		rock.next = rock.next->next;
	}
	Bprint(&bout, "* search");
	for(m = selected->msgs; m != nil; m = m->next)
		m->matched = searchMsg(m, rock.next);
	for(m = selected->msgs; m != nil; m = m->next){
		if(m->matched){
			if(uids)
				id = m->uid;
			else
				id = m->seq;
			Bprint(&bout, " %lud", id);
		}
	}
	Bprint(&bout, "\r\n");
	checkBox(selected, 0);
	status(uids, uids);
	Bprint(&bout, "%s OK %s%s completed\r\n", tg, uid, cmd);
}

static void
selectCmd(char *tg, char *cmd)
{
	Msg *m;
	char *s, *mbox;

	mustBe(' ');
	mbox = astring();
	crnl();

	if(selected){
		imapState = SAuthed;
		closeBox(selected, 1);
		selected = nil;
	}

	mbox = mboxName(mbox);
	if(mbox == nil || !okMbox(mbox)){
		Bprint(&bout, "%s no %s bad mailbox\r\n", tg, cmd);
		return;
	}

	selected = openBox(mbox, "imap", cistrcmp(cmd, "select") == 0);
	if(selected == nil){
		Bprint(&bout, "%s no %s can't open mailbox %s: %r\r\n", tg, cmd, mbox);
		return;
	}

	imapState = SSelected;

	Bprint(&bout, "* flags (\\Seen \\Answered \\Flagged \\Deleted \\Draft)\r\n");
	Bprint(&bout, "* %lud exists\r\n", selected->max);
	selected->toldMax = selected->max;
	Bprint(&bout, "* %lud recent\r\n", selected->recent);
	selected->toldRecent = selected->recent;
	for(m = selected->msgs; m != nil; m = m->next){
		if(!m->expunged && (m->flags & MSeen) != MSeen){
			Bprint(&bout, "* ok [UNSEEN %ld]\r\n", m->seq);
			break;
		}
	}
	Bprint(&bout, "* ok [PERMANENTFLAGS (\\Seen \\Answered \\Flagged \\Draft)]\r\n");
	Bprint(&bout, "* ok [UIDNEXT %ld]\r\n", selected->uidnext);
	Bprint(&bout, "* ok [UIDVALIDITY %ld]\r\n", selected->uidvalidity);
	s = "READ-ONLY";
	if(selected->writable)
		s = "READ-WRITE";
	Bprint(&bout, "%s OK [%s] %s %s completed\r\n", tg, s, cmd, mbox);
}

static NamedInt	statusItems[] =
{
	{"MESSAGES",	SMessages},
	{"RECENT",	SRecent},
	{"UIDNEXT",	SUidNext},
	{"UIDVALIDITY",	SUidValidity},
	{"UNSEEN",	SUnseen},
	{nil,		0}
};

static void
statusCmd(char *tg, char *cmd)
{
	Box *box;
	Msg *m;
	char *s, *mbox;
	ulong v;
	int si, i;

	mustBe(' ');
	mbox = astring();
	mustBe(' ');
	mustBe('(');
	si = 0;
	for(;;){
		s = atom();
		i = mapInt(statusItems, s);
		if(i == 0)
			parseErr("illegal status item");
		si |= i;
		if(peekc() == ')')
			break;
		mustBe(' ');
	}
	mustBe(')');
	crnl();

	mbox = mboxName(mbox);
	if(mbox == nil || !okMbox(mbox)){
		check();
		Bprint(&bout, "%s no %s bad mailbox\r\n", tg, cmd);
		return;
	}

	box = openBox(mbox, "status", 1);
	if(box == nil){
		check();
		Bprint(&bout, "%s no [TRYCREATE] %s can't open mailbox %s: %r\r\n", tg, cmd, mbox);
		return;
	}

	Bprint(&bout, "* STATUS (");
	s = "";
	for(i = 0; statusItems[i].name != nil; i++){
		if(si & statusItems[i].v){
			v = 0;
			switch(statusItems[i].v){
			case SMessages:
				v = box->max;
				break;
			case SRecent:
				v = box->recent;
				break;
			case SUidNext:
				v = box->uidnext;
				break;
			case SUidValidity:
				v = box->uidvalidity;
				break;
			case SUnseen:
				v = 0;
				for(m = box->msgs; m != nil; m = m->next)
					if((m->flags & MSeen) != MSeen)
						v++;
				break;
			default:
				Bprint(&bout, ")");
				bye("internal error: status item not implemented");
				break;
			}
			Bprint(&bout, "%s%s %lud", s, statusItems[i].name, v);
			s = " ";
		}
	}
	Bprint(&bout, ")\r\n");
	closeBox(box, 1);

	check();
	Bprint(&bout, "%s OK %s completed\r\n", tg, cmd);
}

static void
storeCmd(char *tg, char *cmd)
{
	storeUCmd(tg, cmd, 0);
}

static void
storeUCmd(char *tg, char *cmd, int uids)
{
	Store *st;
	MsgSet *ms;
	MbLock *ml;
	char *uid;
	ulong max;
	int ok;

	mustBe(' ');
	ms = msgSet();
	mustBe(' ');
	st = storeWhat();
	crnl();
	uid = "";
	if(uids)
		uid = "uid ";
	max = selected->max;
	ml = checkBox(selected, 1);
	ok = ml != nil && forMsgs(selected, ms, max, uids, storeMsg, st);
	closeImp(selected, ml);
	status(uids, uids);
	if(ok)
		Bprint(&bout, "%s OK %s%s completed\r\n", tg, uid, cmd);
	else
		Bprint(&bout, "%s no %s%s failed\r\n", tg, uid, cmd);
}

/*
 * minimal implementation of subscribe
 * all folders are automatically subscribed,
 * and can't be unsubscribed
 */
static void
subscribeCmd(char *tg, char *cmd)
{
	Box *box;
	char *mbox;
	int ok;

	mustBe(' ');
	mbox = astring();
	crnl();
	check();
	mbox = mboxName(mbox);
	ok = 0;
	if(mbox != nil && okMbox(mbox)){
		box = openBox(mbox, "subscribe", 0);
		if(box != nil){
			ok = subscribe(mbox, 's');
			closeBox(box, 1);
		}
	}
	if(!ok)
		Bprint(&bout, "%s no %s bad mailbox\r\n", tg, cmd);
	else
		Bprint(&bout, "%s OK %s completed\r\n", tg, cmd);
}

static void
uidCmd(char *tg, char *cmd)
{
	char *sub;

	mustBe(' ');
	sub = atom();
	if(cistrcmp(sub, "copy") == 0)
		copyUCmd(tg, sub, 1);
	else if(cistrcmp(sub, "fetch") == 0)
		fetchUCmd(tg, sub, 1);
	else if(cistrcmp(sub, "search") == 0)
		searchUCmd(tg, sub, 1);
	else if(cistrcmp(sub, "store") == 0)
		storeUCmd(tg, sub, 1);
	else{
		clearcmd();
		Bprint(&bout, "%s bad %s illegal uid command %s\r\n", tg, cmd, sub);
	}
}

static void
unsubscribeCmd(char *tg, char *cmd)
{
	char *mbox;

	mustBe(' ');
	mbox = astring();
	crnl();
	check();
	mbox = mboxName(mbox);
	if(mbox == nil || !okMbox(mbox) || !subscribe(mbox, 'u'))
		Bprint(&bout, "%s no %s can't unsubscribe\r\n", tg, cmd);
	else
		Bprint(&bout, "%s OK %s completed\r\n", tg, cmd);
}

static void
badsyn(void)
{
	parseErr("bad syntax");
}

static void
clearcmd(void)
{
	int c;

	for(;;){
		c = getc();
		if(c < 0)
			bye("end of input");
		if(c == '\n')
			return;
	}
}

static void
crnl(void)
{
	int c;

	c = getc();
	if(c == '\n')
		return;
	if(c != '\r' || getc() != '\n')
		badsyn();
}

static void
mustBe(int c)
{
	if(getc() != c){
		ungetc();
		badsyn();
	}
}

/*
 * flaglist	: '(' ')' | '(' flags ')'
 */
static int
flagList(void)
{
	int f;

	mustBe('(');
	f = 0;
	if(peekc() != ')')
		f = flags();

	mustBe(')');
	return f;
}

/*
 * flags	: flag | flags ' ' flag
 * flag		: '\' atom | atom
 */
static int
flags(void)
{
	int ff, flags;
	char *s;
	int c;

	flags = 0;
	for(;;){
		c = peekc();
		if(c == '\\'){
			mustBe('\\');
			s = atomString(atomStop, "\\");
		}else if(strchr(atomStop, c) != nil)
			s = atom();
		else
			break;
		ff = mapFlag(s);
		if(ff == 0)
			parseErr("flag not supported");
		flags |= ff;
		if(peekc() != ' ')
			break;
		mustBe(' ');
	}
	if(flags == 0)
		parseErr("no flags given");
	return flags;
}

/*
 * storeWhat	: osign 'FLAGS' ' ' storeflags
 *		| osign 'FLAGS.SILENT' ' ' storeflags
 * osign	:
 *		| '+' | '-'
 * storeflags	: flagList | flags
 */
static Store*
storeWhat(void)
{
	int f;
	char *s;
	int c, w;

	c = peekc();
	if(c == '+' || c == '-')
		mustBe(c);
	else
		c = 0;
	s = atom();
	w = 0;
	if(cistrcmp(s, "flags") == 0)
		w = STFlags;
	else if(cistrcmp(s, "flags.silent") == 0)
		w = STFlagsSilent;
	else
		parseErr("illegal store attribute");
	mustBe(' ');
	if(peekc() == '(')
		f = flagList();
	else
		f = flags();
	return mkStore(c, w, f);
}

/*
 * fetchWhat	: "ALL" | "FULL" | "FAST" | fetchAtt | '(' fetchAtts ')'
 * fetchAtts	: fetchAtt | fetchAtts ' ' fetchAtt
 */
static char *fetchAtom	= "(){}%*\"\\[]";
static Fetch*
fetchWhat(void)
{
	Fetch *f;
	char *s;

	if(peekc() == '('){
		getc();
		f = nil;
		for(;;){
			s = atomString(fetchAtom, "");
			f = fetchAtt(s, f);
			if(peekc() == ')')
				break;
			mustBe(' ');
		}
		getc();
		return revFetch(f);
	}

	s = atomString(fetchAtom, "");
	if(cistrcmp(s, "all") == 0)
		f = mkFetch(FFlags, mkFetch(FInternalDate, mkFetch(FRfc822Size, mkFetch(FEnvelope, nil))));
	else if(cistrcmp(s, "fast") == 0)
		f = mkFetch(FFlags, mkFetch(FInternalDate, mkFetch(FRfc822Size, nil)));
	else if(cistrcmp(s, "full") == 0)
		f = mkFetch(FFlags, mkFetch(FInternalDate, mkFetch(FRfc822Size, mkFetch(FEnvelope, mkFetch(FBody, nil)))));
	else
		f = fetchAtt(s, nil);
	return f;
}

/*
 * fetchAtt	: "ENVELOPE" | "FLAGS" | "INTERNALDATE"
 *		| "RFC822" | "RFC822.HEADER" | "RFC822.SIZE" | "RFC822.TEXT"
 *		| "BODYSTRUCTURE"
 *		| "UID"
 *		| "BODY"
 *		| "BODY" bodysubs
 *		| "BODY.PEEK" bodysubs
 * bodysubs	: sect
 *		| sect '<' number '.' nz-number '>'
 * sect		: '[' sectSpec ']'
 * sectSpec	: sectMsgText
 *		| sectPart
 *		| sectPart '.' sectText
 * sectPart	: nz-number
 *		| sectPart '.' nz-number
 */
static Fetch*
fetchAtt(char *s, Fetch *f)
{
	NList *sect;
	int c;

	if(cistrcmp(s, "envelope") == 0)
		return mkFetch(FEnvelope, f);
	if(cistrcmp(s, "flags") == 0)
		return mkFetch(FFlags, f);
	if(cistrcmp(s, "internaldate") == 0)
		return mkFetch(FInternalDate, f);
	if(cistrcmp(s, "RFC822") == 0)
		return mkFetch(FRfc822, f);
	if(cistrcmp(s, "RFC822.header") == 0)
		return mkFetch(FRfc822Head, f);
	if(cistrcmp(s, "RFC822.size") == 0)
		return mkFetch(FRfc822Size, f);
	if(cistrcmp(s, "RFC822.text") == 0)
		return mkFetch(FRfc822Text, f);
	if(cistrcmp(s, "bodystructure") == 0)
		return mkFetch(FBodyStruct, f);
	if(cistrcmp(s, "uid") == 0)
		return mkFetch(FUid, f);

	if(cistrcmp(s, "body") == 0){
		if(peekc() != '[')
			return mkFetch(FBody, f);
		f = mkFetch(FBodySect, f);
	}else if(cistrcmp(s, "body.peek") == 0)
		f = mkFetch(FBodyPeek, f);
	else
		parseErr("illegal fetch attribute");

	mustBe('[');
	c = peekc();
	if(c >= '1' && c <= '9'){
		sect = mkNList(number(1), nil);
		while(peekc() == '.'){
			getc();
			c = peekc();
			if(c >= '1' && c <= '9'){
				sect = mkNList(number(1), sect);
			}else{
				break;
			}
		}
		f->sect = revNList(sect);
	}
	if(peekc() != ']')
		sectText(f, f->sect != nil);
	mustBe(']');

	if(peekc() != '<')
		return f;

	f->partial = 1;
	mustBe('<');
	f->start = number(0);
	mustBe('.');
	f->size = number(1);
	mustBe('>');
	return f;
}

/*
 * sectText	: sectMsgText | "MIME"
 * sectMsgText	: "HEADER"
 *		| "TEXT"
 *		| "HEADER.FIELDS" ' ' hdrList
 *		| "HEADER.FIELDS.NOT" ' ' hdrList
 * hdrList	: '(' hdrs ')'
 * hdrs:	: astring
 *		| hdrs ' ' astring
 */
static void
sectText(Fetch *f, int mimeOk)
{
	SList *h;
	char *s;

	s = atomString(fetchAtom, "");
	if(cistrcmp(s, "header") == 0){
		f->part = FPHead;
		return;
	}
	if(cistrcmp(s, "text") == 0){
		f->part = FPText;
		return;
	}
	if(mimeOk && cistrcmp(s, "mime") == 0){
		f->part = FPMime;
		return;
	}
	if(cistrcmp(s, "header.fields") == 0)
		f->part = FPHeadFields;
	else if(cistrcmp(s, "header.fields.not") == 0)
		f->part = FPHeadFieldsNot;
	else
		parseErr("illegal fetch section text");
	mustBe(' ');
	mustBe('(');
	h = nil;
	for(;;){
		h = mkSList(astring(), h);
		if(peekc() == ')')
			break;
		mustBe(' ');
	}
	mustBe(')');
	f->hdrs = revSList(h);
}

/*
 * searchWhat	: "CHARSET" ' ' astring searchkeys | searchkeys
 * searchkeys	: searchkey | searchkeys ' ' searchkey
 * searchkey	: "ALL" | "ANSWERED" | "DELETED" | "FLAGGED" | "NEW" | "OLD" | "RECENT"
 *		| "SEEN" | "UNANSWERED" | "UNDELETED" | "UNFLAGGED" | "DRAFT" | "UNDRAFT"
 *		| astrkey ' ' astring
 *		| datekey ' ' date
 *		| "KEYWORD" ' ' flag | "UNKEYWORD" flag
 *		| "LARGER" ' ' number | "SMALLER" ' ' number
 * 		| "HEADER" astring ' ' astring
 *		| set | "UID" ' ' set
 *		| "NOT" ' ' searchkey
 *		| "OR" ' ' searchkey ' ' searchkey
 *		| '(' searchkeys ')'
 * astrkey	: "BCC" | "BODY" | "CC" | "FROM" | "SUBJECT" | "TEXT" | "TO"
 * datekey	: "BEFORE" | "ON" | "SINCE" | "SENTBEFORE" | "SENTON" | "SENTSINCE"
 */
static NamedInt searchMap[] =
{
	{"ALL",		SKAll},
	{"ANSWERED",	SKAnswered},
	{"DELETED",	SKDeleted},
	{"FLAGGED",	SKFlagged},
	{"NEW",		SKNew},
	{"OLD",		SKOld},
	{"RECENT",	SKRecent},
	{"SEEN",	SKSeen},
	{"UNANSWERED",	SKUnanswered},
	{"UNDELETED",	SKUndeleted},
	{"UNFLAGGED",	SKUnflagged},
	{"DRAFT",	SKDraft},
	{"UNDRAFT",	SKUndraft},
	{"UNSEEN",	SKUnseen},
	{nil,		0}
};

static NamedInt searchMapStr[] =
{
	{"CHARSET",	SKCharset},
	{"BCC",		SKBcc},
	{"BODY",	SKBody},
	{"CC",		SKCc},
	{"FROM",	SKFrom},
	{"SUBJECT",	SKSubject},
	{"TEXT",	SKText},
	{"TO",		SKTo},
	{nil,		0}
};

static NamedInt searchMapDate[] =
{
	{"BEFORE",	SKBefore},
	{"ON",		SKOn},
	{"SINCE",	SKSince},
	{"SENTBEFORE",	SKSentBefore},
	{"SENTON",	SKSentOn},
	{"SENTSINCE",	SKSentSince},
	{nil,		0}
};

static NamedInt searchMapFlag[] =
{
	{"KEYWORD",	SKKeyword},
	{"UNKEYWORD",	SKUnkeyword},
	{nil,		0}
};

static NamedInt searchMapNum[] =
{
	{"SMALLER",	SKSmaller},
	{"LARGER",	SKLarger},
	{nil,		0}
};

static Search*
searchKeys(int first, Search *tail)
{
	Search *s;

	for(;;){
		if(peekc() == '('){
			getc();
			tail = searchKeys(0, tail);
			mustBe(')');
		}else{
			s = searchKey(first);
			tail->next = s;
			tail = s;
		}
		first = 0;
		if(peekc() != ' ')
			break;
		getc();
	}
	return tail;
}

static Search*
searchKey(int first)
{
	Search *sr, rock;
	Tm tm;
	char *a;
	int i, c;

	sr = binalloc(&parseBin, sizeof(Search), 1);
	if(sr == nil)
		parseErr("out of memory");

	c = peekc();
	if(c >= '0' && c <= '9'){
		sr->key = SKSet;
		sr->set = msgSet();
		return sr;
	}

	a = atom();
	if(i = mapInt(searchMap, a))
		sr->key = i;
	else if(i = mapInt(searchMapStr, a)){
		if(!first && i == SKCharset)
			parseErr("illegal search key");
		sr->key = i;
		mustBe(' ');
		sr->s = astring();
	}else if(i = mapInt(searchMapDate, a)){
		sr->key = i;
		mustBe(' ');
		c = peekc();
		if(c == '"')
			getc();
		a = atom();
		if(!imap4Date(&tm, a))
			parseErr("bad date format");
		sr->year = tm.year;
		sr->mon = tm.mon;
		sr->mday = tm.mday;
		if(c == '"')
			mustBe('"');
	}else if(i = mapInt(searchMapFlag, a)){
		sr->key = i;
		mustBe(' ');
		c = peekc();
		if(c == '\\'){
			mustBe('\\');
			a = atomString(atomStop, "\\");
		}else
			a = atom();
		i = mapFlag(a);
		if(i == 0)
			parseErr("flag not supported");
		sr->num = i;
	}else if(i = mapInt(searchMapNum, a)){
		sr->key = i;
		mustBe(' ');
		sr->num = number(0);
	}else if(cistrcmp(a, "HEADER") == 0){
		sr->key = SKHeader;
		mustBe(' ');
		sr->hdr = astring();
		mustBe(' ');
		sr->s = astring();
	}else if(cistrcmp(a, "UID") == 0){
		sr->key = SKUid;
		mustBe(' ');
		sr->set = msgSet();
	}else if(cistrcmp(a, "NOT") == 0){
		sr->key = SKNot;
		mustBe(' ');
		rock.next = nil;
		searchKeys(0, &rock);
		sr->left = rock.next;
	}else if(cistrcmp(a, "OR") == 0){
		sr->key = SKOr;
		mustBe(' ');
		rock.next = nil;
		searchKeys(0, &rock);
		sr->left = rock.next;
		mustBe(' ');
		rock.next = nil;
		searchKeys(0, &rock);
		sr->right = rock.next;
	}else
		parseErr("illegal search key");
	return sr;
}

/*
 * set	: seqno
 *	| seqno ':' seqno
 *	| set ',' set
 * seqno: nz-number
 *	| '*'
 *
 */
static MsgSet*
msgSet(void)
{
	MsgSet head, *last, *ms;
	ulong from, to;

	last = &head;
	head.next = nil;
	for(;;){
		from = seqNo();
		to = from;
		if(peekc() == ':'){
			getc();
			to = seqNo();
		}
		ms = binalloc(&parseBin, sizeof(MsgSet), 0);
		if(ms == nil)
			parseErr("out of memory");
		ms->from = from;
		ms->to = to;
		ms->next = nil;
		last->next = ms;
		last = ms;
		if(peekc() != ',')
			break;
		getc();
	}
	return head.next;
}

static ulong
seqNo(void)
{
	if(peekc() == '*'){
		getc();
		return ~0UL;
	}
	return number(1);
}

/*
 * 7 bit, non-ctl chars, no (){%*"\
 * NIL is special case for nstring or parenlist
 */
static char *
atom(void)
{
	return atomString(atomStop, "");
}

/*
 * like an atom, but no +
 */
static char *
tag(void)
{
	return atomString("+(){%*\"\\", "");
}

/*
 * string or atom allowing %*
 */
static char *
listmbox(void)
{
	int c;

	c = peekc();
	if(c == '{')
		return literal();
	if(c == '"')
		return quoted();
	return atomString("(){\"\\", "");
}

/*
 * string or atom
 */
static char *
astring(void)
{
	int c;

	c = peekc();
	if(c == '{')
		return literal();
	if(c == '"')
		return quoted();
	return atom();
}

/*
 * 7 bit, non-ctl chars, none from exception list
 */
static char *
atomString(char *disallowed, char *initial)
{
	char *s;
	int c, ns, as;

	ns = strlen(initial);
	s = binalloc(&parseBin, ns + StrAlloc, 0);
	if(s == nil)
		parseErr("out of memory");
	strcpy(s, initial);
	as = ns + StrAlloc;
	for(;;){
		c = getc();
		if(c <= ' ' || c >= 0x7f || strchr(disallowed, c) != nil){
			ungetc();
			break;
		}
		s[ns++] = c;
		if(ns >= as){
			s = bingrow(&parseBin, s, as, as + StrAlloc, 0);
			if(s == nil)
				parseErr("out of memory");
			as += StrAlloc;
		}
	}
	if(ns == 0)
		badsyn();
	s[ns] = '\0';
	return s;
}

/*
 * quoted: '"' chars* '"'
 * chars:	1-128 except \r and \n
 */
static char *
quoted(void)
{
	char *s;
	int c, ns, as;

	mustBe('"');
	s = binalloc(&parseBin, StrAlloc, 0);
	if(s == nil)
		parseErr("out of memory");
	as = StrAlloc;
	ns = 0;
	for(;;){
		c = getc();
		if(c == '"')
			break;
		if(c < 1 || c > 0x7f || c == '\r' || c == '\n')
			badsyn();
		if(c == '\\'){
			c = getc();
			if(c != '\\' && c != '"')
				badsyn();
		}
		s[ns++] = c;
		if(ns >= as){
			s = bingrow(&parseBin, s, as, as + StrAlloc, 0);
			if(s == nil)
				parseErr("out of memory");
			as += StrAlloc;
		}
	}
	s[ns] = '\0';
	return s;
}

/*
 * litlen: {number}\r\n
 */
static ulong
litlen(void)
{
	ulong v;

	mustBe('{');
	v = number(0);
	mustBe('}');
	crnl();
	return v;
}

/*
 * literal: litlen data<0:litlen>
 */
static char *
literal(void)
{
	char *s;
	ulong v;

	v = litlen();
	s = binalloc(&parseBin, v+1, 0);
	if(s == nil)
		parseErr("out of memory");
	Bprint(&bout, "+ Ready for literal data\r\n");
	if(Bflush(&bout) < 0)
		writeErr();
	if(v != 0 && Bread(&bin, s, v) != v)
		badsyn();
	s[v] = '\0';
	return s;
}

/*
 * digits; number is 32 bits
 */
static ulong
number(int nonzero)
{
	ulong v;
	int c, first;

	v = 0;
	first = 1;
	for(;;){
		c = getc();
		if(c < '0' || c > '9'){
			ungetc();
			if(first)
				badsyn();
			break;
		}
		if(nonzero && first && c == '0')
			badsyn();
		c -= '0';
		first = 0;
		if(v > UlongMax/10 || v == UlongMax/10 && c > UlongMax%10)
			parseErr("number out of range\r\n");
		v = v * 10 + c;
	}
	return v;
}

static int
getc(void)
{
	return Bgetc(&bin);
}

static void
ungetc(void)
{
	Bungetc(&bin);
}

static int
peekc(void)
{
	int c;

	c = Bgetc(&bin);
	Bungetc(&bin);
	return c;
}

[-- Attachment #3: fetch.c --]
[-- Type: text/plain, Size: 12391 bytes --]

#include <u.h>
#include <libc.h>
#include <bio.h>
#include <auth.h>
#include "imap4d.h"

char *fetchPartNames[FPMax] =
{
	"",
	"HEADER",
	"HEADER.FIELDS",
	"HEADER.FIELDS.NOT",
	"MIME",
	"TEXT",
};

/*
 * implicitly set the \seen flag.  done in a separate pass
 * so the .imp file doesn't need to be open while the
 * messages are sent to the client.
 */
int
fetchSeen(Box *box, Msg *m, int uids, void *vf)
{
	Fetch *f;

	USED(uids);

	if(m->expunged)
		return uids;
	for(f = vf; f != nil; f = f->next){
		switch(f->op){
		case FRfc822:
		case FRfc822Text:
		case FBodySect:
			msgSeen(box, m);
			goto breakout;
		}
	}
breakout:

	return 1;
}

/*
 * fetch messages
 *
 * imap4 body[] requestes get translated to upas/fs files as follows
 *	body[id.header] == id/rawheader file + extra \r\n
 *	body[id.text] == id/rawbody
 *	body[id.mime] == id/mimeheader + extra \r\n
 *	body[id] === body[id.header] + body[id.text]
*/
int
fetchMsg(Box *box, Msg *m, int uids, void *vf)
{
	Tm tm;
	Fetch *f;
	char *sep;
	int todo;

	if(m->expunged)
		return uids;

	todo = 0;
	for(f = vf; f != nil; f = f->next){
		switch(f->op){
		case FFlags:
			/*
			 * flags get sent with status update
			 */
			box->sendFlags = 1;
			m->sendFlags = 1;
			break;
		case FUid:
			todo = 1;
			break;
		case FInternalDate:
		case FEnvelope:
		case FRfc822:
		case FRfc822Head:
		case FRfc822Size:
		case FRfc822Text:
		case FBodySect:
		case FBodyPeek:
		case FBody:
		case FBodyStruct:
			todo = 1;
			if(!msgStruct(m, 1)){
				msgDead(m);
				return uids;
			}
			break;
		default:
			bye("bad implementation of fetch");
			return 0;
		}
	}

	if(m->expunged)
		return uids;
	if(!todo)
		return 1;

	/*
	 * note: it is allowed to send back the responses one at a time
	 * rather than all together.  this is exploited to send flags elsewhere.
	 */
	Bprint(&bout, "* %lud FETCH (", m->seq);
	sep = "";
	if(uids){
		Bprint(&bout, "uid %lud", m->uid);
		sep = " ";
	}
	for(f = vf; f != nil; f = f->next){
		switch(f->op){
		default:
			bye("bad implementation of fetch");
			break;
		case FFlags:
			continue;
		case FUid:
			if(uids)
				continue;
			Bprint(&bout, "%suid %lud", sep, m->uid);
			break;
		case FEnvelope:
			Bprint(&bout, "%senvelope ", sep);
			fetchEnvelope(m);
			break;
		case FInternalDate:
			Bprint(&bout, "%sinternaldate ", sep);
			Bimapdate(&bout, date2tm(&tm, m->unixDate));
			break;
		case FBody:
			Bprint(&bout, "%sbody ", sep);
			fetchBodyStruct(m, &m->head, 0);
			break;
		case FBodyStruct:
			Bprint(&bout, "%sbodystructure ", sep);
			fetchBodyStruct(m, &m->head, 1);
			break;
		case FRfc822Size:
			Bprint(&bout, "%srfc822.size %lud", sep, msgSize(m));
			break;
		case FRfc822:
			f->part = FPAll;
			Bprint(&bout, "%srfc822", sep);
			fetchBody(m, f);
			break;
		case FRfc822Head:
			f->part = FPHead;
			Bprint(&bout, "%srfc822.header", sep);
			fetchBody(m, f);
			break;
		case FRfc822Text:
			f->part = FPText;
			Bprint(&bout, "%srfc822.text", sep);
			fetchBody(m, f);
			break;
		case FBodySect:
		case FBodyPeek:
			Bprint(&bout, "%sbody", sep);
			fetchBody(fetchSect(m, f), f);
			break;
		}
		sep = " ";
	}
	Bprint(&bout, ")\r\n");

	return 1;
}

/*
 * print out section, part, headers;
 * find and return message section
 */
Msg *
fetchSect(Msg *m, Fetch *f)
{
	Bputc(&bout, '[');
	BNList(&bout, f->sect, ".");
	if(f->part != FPAll){
		if(f->sect != nil)
			Bputc(&bout, '.');
		Bprint(&bout, "%s", fetchPartNames[f->part]);
		if(f->hdrs != nil){
			Bprint(&bout, " (");
			BSList(&bout, f->hdrs, " ");
			Bputc(&bout, ')');
		}
	}
	Bprint(&bout, "]");
	return findMsgSect(m, f->sect);
}

/*
 * actually return the body pieces
 */
void
fetchBody(Msg *m, Fetch *f)
{
	Pair p;
	char *s, *t, *e, buf[BufSize + 2];
	ulong n, start, stop, pos;
	int fd, nn;

	if(m == nil){
		fetchBodyStr(f, "", 0);
		return;
	}
	switch(f->part){
	case FPHeadFields:
	case FPHeadFieldsNot:
		n = m->head.size + 3;
		s = emalloc(n);
		n = selectFields(s, n, m->head.buf, f->hdrs, f->part == FPHeadFields);
		fetchBodyStr(f, s, n);
		free(s);
		return;
	case FPHead:
		fetchBodyStr(f, m->head.buf, m->head.size);
		return;
	case FPMime:
		fetchBodyStr(f, m->mime.buf, m->mime.size);
		return;
	case FPAll:
		fd = msgFile(m, "rawbody");
		if(fd < 0){
			msgDead(m);
			fetchBodyStr(f, "", 0);
			return;
		}
		p = fetchBodyPart(f, msgSize(m));
		start = p.start;
		if(start < m->head.size){
			stop = p.stop;
			if(stop > m->head.size)
				stop = m->head.size;
			Bwrite(&bout, &m->head.buf[start], stop - start);
			start = 0;
			stop = p.stop;
			if(stop <= m->head.size){
				close(fd);
				return;
			}
		}else
			start -= m->head.size;
		stop = p.stop - m->head.size;
		break;
	case FPText:
		fd = msgFile(m, "rawbody");
		if(fd < 0){
			msgDead(m);
			fetchBodyStr(f, "", 0);
			return;
		}
		p = fetchBodyPart(f, m->size);
		start = p.start;
		stop = p.stop;
		break;
	default:
		fetchBodyStr(f, "", 0);
		return;
	}

	/*
	 * read in each block, convert \n without \r to \r\n.
	 * this means partial fetch requires fetching everything
	 * through stop, since we don't know how many \r's will be added
	 */
	buf[0] = ' ';
	for(pos = 0; pos < stop; ){
		n = BufSize;
		if(n > stop - pos)
			n = stop - pos;
		n = read(fd, &buf[1], n);
		if(n <= 0){
			fetchBodyFill(stop - pos);
			break;
		}
		e = &buf[n + 1];
		*e = '\0';
		for(s = &buf[1]; s < e && pos < stop; s = t + 1){
			t = memchr(s, '\n', e - s);
			if(t == nil)
				t = e;
			n = t - s;
			if(pos < start){
				if(pos + n <= start){
					s = t;
					pos += n;
				}else{
					s += start - pos;
					pos = start;
				}
				n = t - s;
			}
			nn = n;
			if(pos + nn > stop)
				nn = stop - pos;
			if(Bwrite(&bout, s, nn) != nn)
				writeErr();
			pos += n;
			if(*t == '\n'){
				if(t[-1] != '\r'){
					if(pos >= start && pos < stop)
						Bputc(&bout, '\r');
					pos++;
				}
				if(pos >= start && pos < stop)
					Bputc(&bout, '\n');
				pos++;
			}
		}
		buf[0] = e[-1];
	}
	close(fd);
}

/*
 * resolve the actual bounds of any partial fetch,
 * and print out the bounds & size of string returned
 */
Pair
fetchBodyPart(Fetch *f, ulong size)
{
	Pair p;
	ulong start, stop;

	start = 0;
	stop = size;
	if(f->partial){
		start = f->start;
		if(start > size)
			start = size;
		stop = start + f->size;
		if(stop > size)
			stop = size;
		Bprint(&bout, "<%lud>", start);
	}
	Bprint(&bout, " {%lud}\r\n", stop - start);
	p.start = start;
	p.stop = stop;
	return p;
}

/*
 * something went wrong fetching data
 * produce fill bytes for what we've committed to produce
 */
void
fetchBodyFill(ulong n)
{
	while(n-- > 0)
		if(Bputc(&bout, ' ') < 0)
			writeErr();
}

/*
 * return a simple string
 */
void
fetchBodyStr(Fetch *f, char *buf, ulong size)
{
	Pair p;

	p = fetchBodyPart(f, size);
	Bwrite(&bout, &buf[p.start], p.stop-p.start);
}

char*
printnlist(NList *sect)
{
	static char buf[100];
	char *p;

	for(p= buf; sect; sect=sect->next){
		p += sprint(p, "%ld", sect->n);
		if(sect->next)
			*p++ = '.';
	}
	*p = '\0';
	return buf;
}

/*
 * find the numbered sub-part of the message
 */
Msg*
findMsgSect(Msg *m, NList *sect)
{
	ulong id;

	for(; sect != nil; sect = sect->next){
		id = sect->n;
#ifdef HACK
		/* HACK to solve extra level of structure not visible from upas/fs  */
		if(m->kids == 0 && id == 1 && sect->next == nil){
			if(m->mime.type->s && strcmp(m->mime.type->s, "message")==0)
			if(m->mime.type->t && strcmp(m->mime.type->t, "rfc822")==0)
			if(m->head.type->s && strcmp(m->head.type->s, "text")==0)
			if(m->head.type->t && strcmp(m->head.type->t, "plain")==0)
				break;
		}
		/* end of HACK */
#endif HACK
		for(m = m->kids; m != nil; m = m->next)
			if(m->id == id)
				break;
		if(m == nil)
			return nil;
	}
	return m;
}

void
fetchEnvelope(Msg *m)
{
	Tm tm;

	Bputc(&bout, '(');
	Brfc822date(&bout, date2tm(&tm, m->info[IDate]));
	Bputc(&bout, ' ');
	Bimapstr(&bout, m->info[ISubject]);
	Bputc(&bout, ' ');
	Bimapaddr(&bout, m->from);
	Bputc(&bout, ' ');
	Bimapaddr(&bout, m->sender);
	Bputc(&bout, ' ');
	Bimapaddr(&bout, m->replyTo);
	Bputc(&bout, ' ');
	Bimapaddr(&bout, m->to);
	Bputc(&bout, ' ');
	Bimapaddr(&bout, m->cc);
	Bputc(&bout, ' ');
	Bimapaddr(&bout, m->bcc);
	Bputc(&bout, ' ');
	Bimapstr(&bout, m->info[IInReplyTo]);
	Bputc(&bout, ' ');
	Bimapstr(&bout, m->info[IMessageId]);
	Bputc(&bout, ')');
}

void
fetchBodyStruct(Msg *m, Header *h, int extensions)
{
	Msg *k;
	ulong len;

	if(msgIsMulti(h)){
		Bputc(&bout, '(');
		for(k = m->kids; k != nil; k = k->next)
			fetchBodyStruct(k, &k->mime, extensions);

		Bputc(&bout, ' ');
		Bimapstr(&bout, h->type->t);

		if(extensions){
			Bputc(&bout, ' ');
			BimapMimeParams(&bout, h->type->next);
			fetchStructExt(h);
		}

		Bputc(&bout, ')');
		return;
	}

	Bputc(&bout, '(');
	if(h->type != nil){
		Bimapstr(&bout, h->type->s);
		Bputc(&bout, ' ');
		Bimapstr(&bout, h->type->t);
		Bputc(&bout, ' ');
		BimapMimeParams(&bout, h->type->next);
	}else
		Bprint(&bout, "\"text\" \"plain\" NIL");

	Bputc(&bout, ' ');
	if(h->id != nil)
		Bimapstr(&bout, h->id->s);
	else
		Bprint(&bout, "NIL");

	Bputc(&bout, ' ');
	if(h->description != nil)
		Bimapstr(&bout, h->description->s);
	else
		Bprint(&bout, "NIL");

	Bputc(&bout, ' ');
	if(h->encoding != nil)
		Bimapstr(&bout, h->encoding->s);
	else
		Bprint(&bout, "NIL");

	/*
	 * this is so strange: return lengths for a body[text] response,
	 * except in the case of a multipart message, when return lengths for a body[] response
	 */
	len = m->size;
	if(h == &m->mime)
		len += m->head.size;
	Bprint(&bout, " %lud", len);

	len = m->lines;
	if(h == &m->mime)
		len += m->head.lines;

	if(h->type == nil || cistrcmp(h->type->s, "text") == 0){
		Bprint(&bout, " %lud", len);
	}else if(msgIsRfc822(h)){
		Bputc(&bout, ' ');
		k = m;
		if(h != &m->mime)
			k = m->kids;
		if(k == nil)
			Bprint(&bout, "(NIL NIL NIL NIL NIL NIL NIL NIL NIL NIL) (\"text\" \"plain\" NIL NIL NIL NIL 0 0) 0");
		else{
			fetchEnvelope(k);
			Bputc(&bout, ' ');
			fetchBodyStruct(k, &k->head, extensions);
			Bprint(&bout, " %lud", len);
		}
	}

	if(extensions){
		Bputc(&bout, ' ');

		/*
		 * don't have the md5 laying around,
		 * since the header & body have added newlines.
		 */
		Bprint(&bout, "NIL");

		fetchStructExt(h);
	}
	Bputc(&bout, ')');
}

/*
 * common part of bodystructure extensions
 */
void
fetchStructExt(Header *h)
{
	Bputc(&bout, ' ');
	if(h->disposition != nil){
		Bputc(&bout, '(');
		Bimapstr(&bout, h->disposition->s);
		Bputc(&bout, ' ');
		BimapMimeParams(&bout, h->disposition->next);
		Bputc(&bout, ')');
	}else
		Bprint(&bout, "NIL");
	Bputc(&bout, ' ');
	if(h->language != nil){
		if(h->language->next != nil)
			BimapMimeParams(&bout, h->language->next);
		else
			Bimapstr(&bout, h->language->s);
	}else
		Bprint(&bout, "NIL");
}

int
BimapMimeParams(Biobuf *b, MimeHdr *mh)
{
	char *sep;
	int n;

	if(mh == nil)
		return Bprint(b, "NIL");

	n = Bputc(b, '(');

	sep = "";
	for(; mh != nil; mh = mh->next){
		n += Bprint(b, sep);
		n += Bimapstr(b, mh->s);
		n += Bputc(b, ' ');
		n += Bimapstr(b, mh->t);
		sep = " ";
	}

	n += Bputc(b, ')');
	return n;
}

/*
 * print a list of addresses;
 * each address is printed as '(' personalName AtDomainList mboxName hostName ')'
 * the AtDomainList is always NIL
 */
int
Bimapaddr(Biobuf *b, MAddr *a)
{
	char *host, *sep;
	int n;

	if(a == nil)
		return Bprint(b, "NIL");

	n = Bputc(b, '(');
	sep = "";
	for(; a != nil; a = a->next){
		n += Bprint(b, "%s(", sep);
		n += Bimapstr(b, a->personal);
		n += Bprint(b," NIL ");
		n += Bimapstr(b, a->box);
		n += Bputc(b, ' ');

		/*
		 * can't send NIL as hostName, since that is code for a group
		 */
		host = a->host;
		if(host == nil)
			host = "";
		n += Bimapstr(b, host);

		n += Bputc(b, ')');
		sep = " ";
	}
	n += Bputc(b, ')');
	return n;
}

[-- Attachment #4: message body and .signature --]
[-- Type: text/plain, Size: 177 bytes --]


(sorry, I don't have diff access to the original version due to a net failure).
--
                 Saludos,
                         Gorka

"Curiosity sKilled the cat"

^ permalink raw reply	[flat|nested] 6+ messages in thread

* Re: [9fans] imap4d bug
  2002-11-15 15:41 [9fans] imap4d bug paurea
@ 2002-11-15 16:06 ` paurea
  2002-11-15 16:33   ` paurea
  0 siblings, 1 reply; 6+ messages in thread
From: paurea @ 2002-11-15 16:06 UTC (permalink / raw)
  To: 9fans

[-- Attachment #1: message body text --]
[-- Type: text/plain, Size: 410 bytes --]

paurea@gsyc.escet.urjc.es writes:
 > From: paurea@gsyc.escet.urjc.es
 > Subject: [9fans] imap4d bug
 > Date: Fri, 15 Nov 2002 16:41:15 +0100
 >
 > Some of the imap commands where written downcase and because of that it
 > didn't interact with fetchmail, emacs or Netscape 7 mail reader.(it did
 > work with mozilla don't know why...).
 >

Now it works with evolution too (more downcase commands).


[-- Attachment #2: imap4d.c --]
[-- Type: text/plain, Size: 42283 bytes --]

#include <u.h>
#include <libc.h>
#include <auth.h>
#include <bio.h>
#include "imap4d.h"

/*
 * these should be in libraries
 */
char	*csquery(char *attr, char *val, char *rattr);

/*
 * /lib/rfc/rfc2060 imap4rev1
 * /lib/rfc/rfc2683 is implementation advice
 * /lib/rfc/rfc2342 is namespace capability
 * /lib/rfc/rfc2222 is security protocols
 * /lib/rfc/rfc1731 is security protocols
 * /lib/rfc/rfc2221 is LOGIN-REFERRALS
 * /lib/rfc/rfc2193 is MAILBOX-REFERRALS
 * /lib/rfc/rfc2177 is IDLE capability
 * /lib/rfc/rfc2195 is CRAM-MD5 authentication
 * /lib/rfc/rfc2088 is LITERAL+ capability
 * /lib/rfc/rfc1760 is S/Key authentication
 *
 * outlook uses "Secure Password Authentication" aka ntlm authentication
 *
 * capabilities from nslocum
 * CAPABILITY IMAP4 IMAP4REV1 NAMESPACE IDLE SCAN SORT MAILBOX-REFERRALS LOGIN-REFERRALS AUTH=LOGIN THREAD=ORDEREDSUBJECT
 */

typedef struct	ParseCmd	ParseCmd;

enum
{
	UlongMax	= 4294967295,
};

struct ParseCmd
{
	char	*name;
	void	(*f)(char *tg, char *cmd);
};

static	void	appendCmd(char *tg, char *cmd);
static	void	authenticateCmd(char *tg, char *cmd);
static	void	capabilityCmd(char *tg, char *cmd);
static	void	closeCmd(char *tg, char *cmd);
static	void	copyCmd(char *tg, char *cmd);
static	void	createCmd(char *tg, char *cmd);
static	void	deleteCmd(char *tg, char *cmd);
static	void	expungeCmd(char *tg, char *cmd);
static	void	fetchCmd(char *tg, char *cmd);
static	void	idleCmd(char *tg, char *cmd);
static	void	listCmd(char *tg, char *cmd);
static	void	loginCmd(char *tg, char *cmd);
static	void	logoutCmd(char *tg, char *cmd);
static	void	namespaceCmd(char *tg, char *cmd);
static	void	noopCmd(char *tg, char *cmd);
static	void	renameCmd(char *tg, char *cmd);
static	void	searchCmd(char *tg, char *cmd);
static	void	selectCmd(char *tg, char *cmd);
static	void	statusCmd(char *tg, char *cmd);
static	void	storeCmd(char *tg, char *cmd);
static	void	subscribeCmd(char *tg, char *cmd);
static	void	uidCmd(char *tg, char *cmd);
static	void	unsubscribeCmd(char *tg, char *cmd);

static	void	copyUCmd(char *tg, char *cmd, int uids);
static	void	fetchUCmd(char *tg, char *cmd, int uids);
static	void	searchUCmd(char *tg, char *cmd, int uids);
static	void	storeUCmd(char *tg, char *cmd, int uids);

static	void	imap4(int);
static	void	status(int expungeable, int uids);
static	void	cleaner(void);
static	void	check(void);
static	int	catcher(void*, char*);

static	Search	*searchKey(int first);
static	Search	*searchKeys(int first, Search *tail);
static	char	*astring(void);
static	char	*atomString(char *disallowed, char *initial);
static	char	*atom(void);
static	void	badsyn(void);
static	void	clearcmd(void);
static	char	*command(void);
static	void	crnl(void);
static	Fetch	*fetchAtt(char *s, Fetch *f);
static	Fetch	*fetchWhat(void);
static	int	flagList(void);
static	int	flags(void);
static	int	getc(void);
static	char	*listmbox(void);
static	char	*literal(void);
static	ulong	litlen(void);
static	MsgSet	*msgSet(void);
static	void	mustBe(int c);
static	ulong	number(int nonzero);
static	int	peekc(void);
static	char	*quoted(void);
static	void	sectText(Fetch *f, int mimeOk);
static	ulong	seqNo(void);
static	Store	*storeWhat(void);
static	char	*tag(void);
static	void	ungetc(void);

static	ParseCmd	SNonAuthed[] =
{
	{"capability",		capabilityCmd},
	{"logout",		logoutCmd},
	{"x-exit",		logoutCmd},
	{"noop",		noopCmd},

	{"login",		loginCmd},
	{"authenticate",	authenticateCmd},

	nil
};

static	ParseCmd	SAuthed[] =
{
	{"capability",		capabilityCmd},
	{"logout",		logoutCmd},
	{"x-exit",		logoutCmd},
	{"noop",		noopCmd},

	{"append",		appendCmd},
	{"create",		createCmd},
	{"delete",		deleteCmd},
	{"examine",		selectCmd},
	{"select",		selectCmd},
	{"idle",		idleCmd},
	{"list",		listCmd},
	{"lsub",		listCmd},
	{"namespace",		namespaceCmd},
	{"rename",		renameCmd},
	{"status",		statusCmd},
	{"subscribe",		subscribeCmd},
	{"unsubscribe",		unsubscribeCmd},

	nil
};

static	ParseCmd	SSelected[] =
{
	{"capability",		capabilityCmd},
	{"logout",		logoutCmd},
	{"x-exit",		logoutCmd},
	{"noop",		noopCmd},

	{"append",		appendCmd},
	{"create",		createCmd},
	{"delete",		deleteCmd},
	{"examine",		selectCmd},
	{"select",		selectCmd},
	{"idle",		idleCmd},
	{"list",		listCmd},
	{"lsub",		listCmd},
	{"namespace",		namespaceCmd},
	{"rename",		renameCmd},
	{"status",		statusCmd},
	{"subscribe",		subscribeCmd},
	{"unsubscribe",		unsubscribeCmd},

	{"check",		noopCmd},
	{"close",		closeCmd},
	{"copy",		copyCmd},
	{"expunge",		expungeCmd},
	{"fetch",		fetchCmd},
	{"search",		searchCmd},
	{"store",		storeCmd},
	{"uid",			uidCmd},

	nil
};

static	char		*atomStop = "(){%*\"\\";
static	Chalstate	*chal;
static	int		chaled;
static	ParseCmd	*imapState;
static	jmp_buf		parseJmp;
static	char		*parseMsg;
static	int		allowPass;
static	int		allowCR;
static	int		exiting;
static	QLock		imaplock;
static	int		idlepid = -1;

Biobuf	bout;
Biobuf	bin;
char	username[UserNameLen];
char	mboxDir[MboxNameLen];
char	*servername;
char	*site;
char	*remote;
Box	*selected;
Bin	*parseBin;
int	debug;

void
main(int argc, char *argv[])
{
	char *s, *t;
	int preauth, n;

	Binit(&bin, 0, OREAD);
	Binit(&bout, 1, OWRITE);

	preauth = 0;
	allowPass = 0;
	allowCR = 0;
	ARGBEGIN{
	case 'a':
		preauth = 1;
		break;
	case 'd':
		site = ARGF();
		break;
	case 'c':
		allowCR = 1;
		break;
	case 'p':
		allowPass = 1;
		break;
	case 'r':
		remote = ARGF();
		break;
	case 's':
		servername = ARGF();
		break;
	case 'v':
		debug = 1;
		debuglog("imap4d debugging enabled\n");
		break;
	default:
		fprint(2, "usage: ip/imap4d [-acp] [-d site] [-r remotehost] [-s servername]\n");
		bye("usage");
		break;
	}ARGEND

	if(preauth)
		setupuser(nil);

	if(servername == nil){
		servername = csquery("sys", sysname(), "dom");
		if(servername == nil)
			servername = sysname();
		if(servername == nil){
			fprint(2, "ip/imap4d can't find server name: %r\n");
			bye("can't find system name");
		}
	}
	if(site == nil){
		t = getenv("site");
		if(t == nil)
			site = servername;
		else{
			n = strlen(t);
			s = strchr(servername, '.');
			if(s == nil)
				s = servername;
			else
				s++;
			n += strlen(s) + 2;
			site = emalloc(n);
			snprint(site, n, "%s.%s", t, s);
		}
	}

	rfork(RFNOTEG|RFREND);

	atnotify(catcher, 1);
	qlock(&imaplock);
	atexit(cleaner);
	imap4(preauth);
}

static void
imap4(int preauth)
{
	char *volatile tg;
	char *volatile cmd;
	ParseCmd *st;

	if(preauth){
		Bprint(&bout, "* preauth %s IMAP4rev1 server ready user %s authenticated\r\n", servername, username);
		imapState = SAuthed;
	}else{
		Bprint(&bout, "* ok %s IMAP4rev1 server ready\r\n", servername);
		imapState = SNonAuthed;
	}
	if(Bflush(&bout) < 0)
		writeErr();

	chaled = 0;

	tg = nil;
	cmd = nil;
	if(setjmp(parseJmp)){
		if(tg == nil)
			Bprint(&bout, "* bad empty command line: %s\r\n", parseMsg);
		else if(cmd == nil)
			Bprint(&bout, "%s bad no command: %s\r\n", tg, parseMsg);
		else
			Bprint(&bout, "%s bad %s %s\r\n", tg, cmd, parseMsg);
		clearcmd();
		if(Bflush(&bout) < 0)
			writeErr();
		binfree(&parseBin);
	}
	for(;;){
		if(mbLocked())
			bye("internal error: mailbox lock held");
		tg = nil;
		cmd = nil;
		tg = tag();
		mustBe(' ');
		cmd = atom();

		/*
		 * note: outlook express is broken: it requires echoing the
		 * command as part of matching response
		 */
		for(st = imapState; st->name != nil; st++){
			if(cistrcmp(cmd, st->name) == 0){
				(*st->f)(tg, cmd);
				break;
			}
		}
		if(st->name == nil){
			clearcmd();
			Bprint(&bout, "%s bad %s illegal command\r\n", tg, cmd);
		}

		if(Bflush(&bout) < 0)
			writeErr();
		binfree(&parseBin);
	}
}

void
bye(char *fmt, ...)
{
	va_list arg;

	va_start(arg, fmt);
	Bprint(&bout, "* bye ");
	Bvprint(&bout, fmt, arg);
	Bprint(&bout, "\r\n");
	Bflush(&bout);
exits("rob2");
	exits(0);
}

void
parseErr(char *msg)
{
	parseMsg = msg;
	longjmp(parseJmp, 1);
}

/*
 * an error occured while writing to the client
 */
void
writeErr(void)
{
	cleaner();
	_exits("connection closed");
}

static int
catcher(void *v, char *msg)
{
	USED(v);
	if(strstr(msg, "closed pipe") != nil)
		return 1;
	return 0;
}

/*
 * wipes out the idleCmd backgroung process if it is around.
 * this can only be called if the current proc has qlocked imaplock.
 * it must be the last piece of imap4d code executed.
 */
static void
cleaner(void)
{
	int i;

	if(idlepid < 0)
		return;
	exiting = 1;
	close(0);
	close(1);
	close(2);

	/*
	 * the other proc is either stuck in a read, a sleep,
	 * or is trying to lock imap4lock.
	 * get him out of it so he can exit cleanly
	 */
	qunlock(&imaplock);
	for(i = 0; i < 4; i++)
		postnote(PNGROUP, getpid(), "die");
}

/*
 * send any pending status updates to the client
 * careful: shouldn't exit, because called by idle polling proc
 *
 * can't always send pending info
 * in particular, can't send expunge info
 * in response to a fetch, store, or search command.
 *
 * rfc2060 5.2:	server must send mailbox size updates
 * rfc2060 5.2:	server may send flag updates
 * rfc2060 5.5:	servers prohibited from sending expunge while fetch, store, search in progress
 * rfc2060 7:	in selected state, server checks mailbox for new messages as part of every command
 * 		sends untagged EXISTS and RECENT respsonses reflecting new size of the mailbox
 * 		should also send appropriate untagged FETCH and EXPUNGE messages if another agent
 * 		changes the state of any message flags or expunges any messages
 * rfc2060 7.4.1	expunge server response must not be sent when no command is in progress,
 * 		nor while responding to a fetch, stort, or search command (uid versions are ok)
 * 		command only "in progress" after entirely parsed.
 *
 * strategy for third party deletion of messages or of a mailbox
 *
 * deletion of a selected mailbox => act like all message are expunged
 *	not strictly allowed by rfc2180, but close to method 3.2.
 *
 * renaming same as deletion
 *
 * copy
 *	reject iff a deleted message is in the request
 *
 * search, store, fetch operations on expunged messages
 *	ignore the expunged messages
 *	return tagged no if referenced
 */
static void
status(int expungeable, int uids)
{
	int tell;

	if(!selected)
		return;
	tell = 0;
	if(expungeable)
		tell = expungeMsgs(selected, 1);
	if(selected->sendFlags)
		sendFlags(selected, uids);
	if(tell || selected->toldMax != selected->max){
		Bprint(&bout, "* %lud exists\r\n", selected->max);
		selected->toldMax = selected->max;
	}
	if(tell || selected->toldRecent != selected->recent){
		Bprint(&bout, "* %lud recent\r\n", selected->recent);
		selected->toldRecent = selected->recent;
	}
	if(tell)
		closeImp(selected, checkBox(selected, 1));
}

/*
 * careful: can't exit, because called by idle polling proc
 */
static void
check(void)
{
	if(!selected)
		return;
	checkBox(selected, 0);
	status(1, 0);
}

static void
appendCmd(char *tg, char *cmd)
{
	char *mbox, head[128];
	ulong t, n, now;
	int flags, ok;

	mustBe(' ');
	mbox = astring();
	mustBe(' ');
	flags = 0;
	if(peekc() == '('){
		flags = flagList();
		mustBe(' ');
	}
	now = time(nil);
	if(peekc() == '"'){
		t = imap4DateTime(quoted());
		if(t == ~0)
			parseErr("illegal date format");
		mustBe(' ');
		if(t > now)
			t = now;
	}else
		t = now;
	n = litlen();

	mbox = mboxName(mbox);
	if(mbox == nil || !okMbox(mbox)){
		check();
		Bprint(&bout, "%s no %s bad mailbox\r\n", tg, cmd);
		return;
	}
	if(!cdExists(mboxDir, mbox)){
		check();
		Bprint(&bout, "%s no [TRYCREATE] %s mailbox does not exist\r\n", tg, cmd);
		return;
	}

	snprint(head, sizeof(head), "From %s %s", username, ctime(t));
	ok = appendSave(mbox, flags, head, &bin, n);
	crnl();
	check();
	if(ok)
		Bprint(&bout, "%s OK %s completed\r\n", tg, cmd);
	else
		Bprint(&bout, "%s no %s message save failed\r\n", tg, cmd);
}

static void
authenticateCmd(char *tg, char *cmd)
{
	char *s, *t;

	mustBe(' ');
	s = atom();
	crnl();
	auth_freechal(chal);
	chal = nil;
	if(cistrcmp(s, "cram-md5") == 0){
		t = cramauth();
		if(t == nil){
			Bprint(&bout, "%s OK %s\r\n", tg, cmd);
			imapState = SAuthed;
		}else
			Bprint(&bout, "%s no %s failed %s\r\n", tg, cmd, t);
	}else
		Bprint(&bout, "%s no %s unsupported authentication protocol\r\n", tg, cmd);
}

static void
capabilityCmd(char *tg, char *cmd)
{
	crnl();
	check();
	Bprint(&bout, "* CAPABILITY IMAP4REV1 IDLE NAMESPACE AUTH=CRAM-MD5\r\n");
	Bprint(&bout, "%s OK %s\r\n", tg, cmd);
}

static void
closeCmd(char *tg, char *cmd)
{
	crnl();
	imapState = SAuthed;
	closeBox(selected, 1);
	selected = nil;
	Bprint(&bout, "%s ok %s mailbox closed, now in authenticated state\r\n", tg, cmd);
}

/*
 * note: message id's are before any pending expunges
 */
static void
copyCmd(char *tg, char *cmd)
{
	copyUCmd(tg, cmd, 0);
}

static void
copyUCmd(char *tg, char *cmd, int uids)
{
	MsgSet *ms;
	char *uid, *mbox;
	ulong max;
	int ok;

	mustBe(' ');
	ms = msgSet();
	mustBe(' ');
	mbox = astring();
	crnl();

	uid = "";
	if(uids)
		uid = "uid ";

	mbox = mboxName(mbox);
	if(mbox == nil || !okMbox(mbox)){
		status(1, uids);
		Bprint(&bout, "%s no %s%s bad mailbox\r\n", tg, uid, cmd);
		return;
	}
	if(!cdExists(mboxDir, mbox)){
		check();
		Bprint(&bout, "%s no [TRYCREATE] %s mailbox does not exist\r\n", tg, cmd);
		return;
	}

	max = selected->max;
	checkBox(selected, 0);
	ok = forMsgs(selected, ms, max, uids, copyCheck, nil);
	if(ok)
		ok = forMsgs(selected, ms, max, uids, copySave, mbox);

	status(1, uids);
	if(ok)
		Bprint(&bout, "%s OK %s%s completed\r\n", tg, uid, cmd);
	else
		Bprint(&bout, "%s no %s%s failed\r\n", tg, uid, cmd);
}

static void
createCmd(char *tg, char *cmd)
{
	char *mbox, *m;
	int fd, slash;

	mustBe(' ');
	mbox = astring();
	crnl();
	check();

	m = strchr(mbox, '\0');
	slash = m != mbox && m[-1] == '/';
	mbox = mboxName(mbox);
	if(mbox == nil || !okMbox(mbox)){
		Bprint(&bout, "%s no %s bad mailbox\r\n", tg, cmd);
		return;
	}
	if(cistrcmp(mbox, "inbox") == 0){
		Bprint(&bout, "%s no %s cannot remotely create INBOX\r\n", tg, cmd);
		return;
	}
	if(access(mbox, AEXIST) >= 0){
		Bprint(&bout, "%s no %s mailbox already exists\r\n", tg, cmd);
		return;
	}

	fd = createBox(mbox, slash);
	close(fd);
	if(fd < 0)
		Bprint(&bout, "%s no %s cannot create mailbox %s\r\n", tg, cmd, mbox);
	else
		Bprint(&bout, "%s OK %s %s completed\r\n", tg, mbox, cmd);
}

static void
deleteCmd(char *tg, char *cmd)
{
	char *mbox, *imp;

	mustBe(' ');
	mbox = astring();
	crnl();
	check();

	mbox = mboxName(mbox);
	if(mbox == nil || !okMbox(mbox)){
		Bprint(&bout, "%s no %s bad mailbox\r\n", tg, cmd);
		return;
	}

	imp = impName(mbox);
	if(cistrcmp(mbox, "inbox") == 0
	|| imp != nil && cdRemove(mboxDir, imp) < 0 && cdExists(mboxDir, imp)
	|| cdRemove(mboxDir, mbox) < 0)
		Bprint(&bout, "%s no %s cannot delete mailbox %s\r\n", tg, cmd, mbox);
	else
		Bprint(&bout, "%s OK %s %s completed\r\n", tg, mbox, cmd);
}

static void
expungeCmd(char *tg, char *cmd)
{
	int ok;

	crnl();
	ok = deleteMsgs(selected);
	check();
	if(ok)
		Bprint(&bout, "%s ok %s messages erased\r\n", tg, cmd);
	else
		Bprint(&bout, "%s no %s some messages not expunged\r\n", tg, cmd);
}

static void
fetchCmd(char *tg, char *cmd)
{
	fetchUCmd(tg, cmd, 0);
}

static void
fetchUCmd(char *tg, char *cmd, int uids)
{
	Fetch *f;
	MsgSet *ms;
	MbLock *ml;
	char *uid;
	ulong max;
	int ok;

	mustBe(' ');
	ms = msgSet();
	mustBe(' ');
	f = fetchWhat();
	crnl();
	uid = "";
	if(uids)
		uid = "uid ";
	max = selected->max;
	ml = checkBox(selected, 1);
	if(ml != nil)
		forMsgs(selected, ms, max, uids, fetchSeen, f);
	closeImp(selected, ml);
	ok = ml != nil && forMsgs(selected, ms, max, uids, fetchMsg, f);
	status(uids, uids);
	if(ok)
		Bprint(&bout, "%s OK %s%s completed\r\n", tg, uid, cmd);
	else
		Bprint(&bout, "%s no %s%s failed\r\n", tg, uid, cmd);
}

static void
idleCmd(char *tg, char *cmd)
{
	int c, pid;

	crnl();
	Bprint(&bout, "+ idling, waiting for done\r\n");
	if(Bflush(&bout) < 0)
		writeErr();

	if(idlepid < 0){
		pid = rfork(RFPROC|RFMEM|RFNOWAIT);
		if(pid == 0){
			for(;;){
				qlock(&imaplock);
				if(exiting)
					break;

				/*
				 * parent may have changed curDir, but it doesn't change our .
				 */
				resetCurDir();

				check();
				if(Bflush(&bout) < 0)
					writeErr();
				qunlock(&imaplock);
				sleep(15*1000);
				enableForwarding();
			}
_exits("rob3");
			_exits(0);
		}
		idlepid = pid;
	}

	qunlock(&imaplock);

	/*
	 * clear out the next line, which is supposed to contain (case-insensitive)
	 * done\n
	 * this is special code since it has to dance with the idle polling proc
	 * and handle exiting correctly.
	 */
	for(;;){
		c = getc();
		if(c < 0){
			qlock(&imaplock);
			if(!exiting)
				cleaner();
_exits("rob4");
			_exits(0);
		}
		if(c == '\n')
			break;
	}

	qlock(&imaplock);
	if(exiting)
{_exits("rob5");
		_exits(0);
}

	/*
	 * child may have changed curDir, but it doesn't change our .
	 */
	resetCurDir();

	check();
	Bprint(&bout, "%s ok %s terminated\r\n", tg, cmd);
}

static void
listCmd(char *tg, char *cmd)
{
	char *s, *t, *ss, *ref, *mbox;
	int n;

	mustBe(' ');
	s = astring();
	mustBe(' ');
	t = listmbox();
	crnl();
	check();
	ref = mutf7str(s);
	mbox = mutf7str(t);
	if(ref == nil || mbox == nil){
		Bprint(&bout, "%s bad %s mailbox name not in modified utf-7\r\n", tg, cmd);
		return;
	}

	/*
	 * special request for hierarchy delimiter and root name
	 * root name appears to be name up to and including any delimiter,
	 * or the empty string, if there is no delimiter.
	 *
	 * this must change if the # namespace convention is supported.
	 */
	if(*mbox == '\0'){
		s = strchr(ref, '/');
		if(s == nil)
			ref = "";
		else
			s[1] = '\0';
		Bprint(&bout, "* %s (\\Noselect) \"/\" \"%s\"\r\n", cmd, ref);
		Bprint(&bout, "%s ok %s\r\n", tg, cmd);
		return;
	}


	/*
	 * massage the listing name:
	 * clean up the components individually,
	 * then rip off componenets from the ref to
	 * take care of leading ..'s in the mbox.
	 *
	 * the cleanup can wipe out * followed by a ..
	 * tough luck if such a stupid pattern is given.
	 */
	cleanname(mbox);
	if(strcmp(mbox, ".") == 0)
		*mbox = '\0';
	if(mbox[0] == '/')
		*ref = '\0';
	else if(*ref != '\0'){
		cleanname(ref);
		if(strcmp(ref, ".") == 0)
			*ref = '\0';
	}else
		*ref = '\0';
	while(*ref && isdotdot(mbox)){
		s = strrchr(ref, '/');
		if(s == nil)
			s = ref;
		if(isdotdot(s))
			break;
		*s = '\0';
		mbox += 2;
		if(*mbox == '/')
			mbox++;
	}
	if(*ref == '\0'){
		s = mbox;
		ss = s;
	}else{
		n = strlen(ref) + strlen(mbox) + 2;
		t = binalloc(&parseBin, n, 0);
		if(t == nil)
			parseErr("out of memory");
		snprint(t, n, "%s/%s", ref, mbox);
		s = t;
		ss = s + strlen(ref);
	}

	/*
	 * only allow activity in /mail/box
	 */
	if(s[0] == '/' || isdotdot(s)){
		Bprint(&bout, "%s no illegal mailbox pattern\r\n", tg);
		return;
	}

	if(cistrcmp(cmd, "lsub") == 0)
		lsubBoxes(cmd, s, ss);
	else
		listBoxes(cmd, s, ss);
	Bprint(&bout, "%s OK %s completed\r\n", tg, cmd);
}

static char*
passCR(char*u, char*p)
{
	static char Ebadch[] = "can't get challenge";
	static char nchall[64];
	static char response[64];
	static Chalstate *ch = nil;
	AuthInfo *ai;

again:
	if (ch == nil){
		if(!(ch = auth_challenge("proto=p9cr role=server user=%q", u)))
			return Ebadch;
		snprint(nchall, 64, " encrypt challenge: %s", ch->chal);
		return nchall;
	} else {
		strncpy(response, p, 64);
		ch->resp = response;
		ch->nresp = strlen(response);
		ai = auth_response(ch);
		auth_freechal(ch);
		ch = nil;
		if (ai == nil)
			goto again;
		setupuser(ai);
		return nil;
	}

}

static void
loginCmd(char *tg, char *cmd)
{
	char *s, *t;
	AuthInfo *ai;
	char*r;
	mustBe(' ');
	s = astring();	/* uid */
	mustBe(' ');
	t = astring();	/* password */
	crnl();
	if(allowCR){
		if ((r = passCR(s, t)) == nil){
			Bprint(&bout, "%s ok %s succeeded\r\n", tg, cmd);
			imapState = SAuthed;
		} else {
			Bprint(&bout, "* no [ALERT] %s\r\n", r);
			Bprint(&bout, "%s no %s succeeded\r\n", tg, cmd);
		}
		return;
	}
	else if(allowPass){
		if(ai = passLogin(s, t)){
			setupuser(ai);
			Bprint(&bout, "%s ok %s succeeded\r\n", tg, cmd);
			imapState = SAuthed;
		}else
			Bprint(&bout, "%s no %s failed check\r\n", tg, cmd);
		return;
	}
	Bprint(&bout, "%s no %s plaintext passwords disallowed\r\n", tg, cmd);
}

/*
 * logout or x-exit, which doesn't expunge the mailbox
 */
static void
logoutCmd(char *tg, char *cmd)
{
	crnl();

	if(cmd[0] != 'x' && selected){
		closeBox(selected, 1);
		selected = nil;
	}
	Bprint(&bout, "* bye\r\n");
	Bprint(&bout, "%s OK %s completed\r\n", tg, cmd);
exits("rob6");
	exits(0);
}

static void
namespaceCmd(char *tg, char *cmd)
{
	crnl();
	check();

	/*
	 * personal, other users, shared namespaces
	 * send back nil or descriptions of (prefix heirarchy-delim) for each case
	 */
	Bprint(&bout, "* namespace ((\"\" \"/\")) nil nil\r\n");
	Bprint(&bout, "%s OK %s completed\r\n", tg, cmd);
}

static void
noopCmd(char *tg, char *cmd)
{
	crnl();
	check();
	Bprint(&bout, "%s OK %s completed\r\n", tg, cmd);
	enableForwarding();
}

/*
 * this is only a partial implementation
 * should copy files to other directories,
 * and copy & truncate inbox
 */
static void
renameCmd(char *tg, char *cmd)
{
	char *from, *to;
	int ok;

	mustBe(' ');
	from = astring();
	mustBe(' ');
	to = astring();
	crnl();
	check();

	to = mboxName(to);
	if(to == nil || !okMbox(to) || cistrcmp(to, "inbox") == 0){
		Bprint(&bout, "%s no %s bad mailbox destination name\r\n", tg, cmd);
		return;
	}
	if(access(to, AEXIST) >= 0){
		Bprint(&bout, "%s no %s mailbox already exists\r\n", tg, cmd);
		return;
	}
	from = mboxName(from);
	if(from == nil || !okMbox(from)){
		Bprint(&bout, "%s no %s bad mailbox destination name\r\n", tg, cmd);
		return;
	}
	if(cistrcmp(from, "inbox") == 0)
		ok = copyBox(from, to, 0);
	else
		ok = moveBox(from, to);

	if(ok)
		Bprint(&bout, "%s OK %s completed\r\n", tg, cmd);
	else
		Bprint(&bout, "%s no %s failed\r\n", tg, cmd);
}

static void
searchCmd(char *tg, char *cmd)
{
	searchUCmd(tg, cmd, 0);
}

static void
searchUCmd(char *tg, char *cmd, int uids)
{
	Search rock;
	Msg *m;
	char *uid;
	ulong id;

	mustBe(' ');
	rock.next = nil;
	searchKeys(1, &rock);
	crnl();
	uid = "";
	if(uids)
		uid = "uid ";
	if(rock.next != nil && rock.next->key == SKCharset){
		if(cistrstr(rock.next->s, "utf-8") != 0
		&& cistrcmp(rock.next->s, "us-ascii") != 0){
			Bprint(&bout, "%s no [BADCHARSET] (\"US-ASCII\" \"UTF-8\") %s%s failed\r\n", tg, uid, cmd);
			checkBox(selected, 0);
			status(uids, uids);
			return;
		}
		rock.next = rock.next->next;
	}
	Bprint(&bout, "* search");
	for(m = selected->msgs; m != nil; m = m->next)
		m->matched = searchMsg(m, rock.next);
	for(m = selected->msgs; m != nil; m = m->next){
		if(m->matched){
			if(uids)
				id = m->uid;
			else
				id = m->seq;
			Bprint(&bout, " %lud", id);
		}
	}
	Bprint(&bout, "\r\n");
	checkBox(selected, 0);
	status(uids, uids);
	Bprint(&bout, "%s OK %s%s completed\r\n", tg, uid, cmd);
}

static void
selectCmd(char *tg, char *cmd)
{
	Msg *m;
	char *s, *mbox;

	mustBe(' ');
	mbox = astring();
	crnl();

	if(selected){
		imapState = SAuthed;
		closeBox(selected, 1);
		selected = nil;
	}

	mbox = mboxName(mbox);
	if(mbox == nil || !okMbox(mbox)){
		Bprint(&bout, "%s no %s bad mailbox\r\n", tg, cmd);
		return;
	}

	selected = openBox(mbox, "imap", cistrcmp(cmd, "select") == 0);
	if(selected == nil){
		Bprint(&bout, "%s no %s can't open mailbox %s: %r\r\n", tg, cmd, mbox);
		return;
	}

	imapState = SSelected;

	Bprint(&bout, "* FLAGS (\\Seen \\Answered \\Flagged \\Deleted \\Draft)\r\n");
	Bprint(&bout, "* %lud exists\r\n", selected->max);
	selected->toldMax = selected->max;
	Bprint(&bout, "* %lud recent\r\n", selected->recent);
	selected->toldRecent = selected->recent;
	for(m = selected->msgs; m != nil; m = m->next){
		if(!m->expunged && (m->flags & MSeen) != MSeen){
			Bprint(&bout, "* OK [UNSEEN %ld]\r\n", m->seq);
			break;
		}
	}
	Bprint(&bout, "* OK [PERMANENTFLAGS (\\Seen \\Answered \\Flagged \\Draft)]\r\n");
	Bprint(&bout, "* OK [UIDNEXT %ld]\r\n", selected->uidnext);
	Bprint(&bout, "* OK [UIDVALIDITY %ld]\r\n", selected->uidvalidity);
	s = "READ-ONLY";
	if(selected->writable)
		s = "READ-WRITE";
	Bprint(&bout, "%s OK [%s] %s %s completed\r\n", tg, s, cmd, mbox);
}

static NamedInt	statusItems[] =
{
	{"MESSAGES",	SMessages},
	{"RECENT",	SRecent},
	{"UIDNEXT",	SUidNext},
	{"UIDVALIDITY",	SUidValidity},
	{"UNSEEN",	SUnseen},
	{nil,		0}
};

static void
statusCmd(char *tg, char *cmd)
{
	Box *box;
	Msg *m;
	char *s, *mbox;
	ulong v;
	int si, i;

	mustBe(' ');
	mbox = astring();
	mustBe(' ');
	mustBe('(');
	si = 0;
	for(;;){
		s = atom();
		i = mapInt(statusItems, s);
		if(i == 0)
			parseErr("illegal status item");
		si |= i;
		if(peekc() == ')')
			break;
		mustBe(' ');
	}
	mustBe(')');
	crnl();

	mbox = mboxName(mbox);
	if(mbox == nil || !okMbox(mbox)){
		check();
		Bprint(&bout, "%s no %s bad mailbox\r\n", tg, cmd);
		return;
	}

	box = openBox(mbox, "status", 1);
	if(box == nil){
		check();
		Bprint(&bout, "%s no [TRYCREATE] %s can't open mailbox %s: %r\r\n", tg, cmd, mbox);
		return;
	}

	Bprint(&bout, "* STATUS (");
	s = "";
	for(i = 0; statusItems[i].name != nil; i++){
		if(si & statusItems[i].v){
			v = 0;
			switch(statusItems[i].v){
			case SMessages:
				v = box->max;
				break;
			case SRecent:
				v = box->recent;
				break;
			case SUidNext:
				v = box->uidnext;
				break;
			case SUidValidity:
				v = box->uidvalidity;
				break;
			case SUnseen:
				v = 0;
				for(m = box->msgs; m != nil; m = m->next)
					if((m->flags & MSeen) != MSeen)
						v++;
				break;
			default:
				Bprint(&bout, ")");
				bye("internal error: status item not implemented");
				break;
			}
			Bprint(&bout, "%s%s %lud", s, statusItems[i].name, v);
			s = " ";
		}
	}
	Bprint(&bout, ")\r\n");
	closeBox(box, 1);

	check();
	Bprint(&bout, "%s OK %s completed\r\n", tg, cmd);
}

static void
storeCmd(char *tg, char *cmd)
{
	storeUCmd(tg, cmd, 0);
}

static void
storeUCmd(char *tg, char *cmd, int uids)
{
	Store *st;
	MsgSet *ms;
	MbLock *ml;
	char *uid;
	ulong max;
	int ok;

	mustBe(' ');
	ms = msgSet();
	mustBe(' ');
	st = storeWhat();
	crnl();
	uid = "";
	if(uids)
		uid = "uid ";
	max = selected->max;
	ml = checkBox(selected, 1);
	ok = ml != nil && forMsgs(selected, ms, max, uids, storeMsg, st);
	closeImp(selected, ml);
	status(uids, uids);
	if(ok)
		Bprint(&bout, "%s OK %s%s completed\r\n", tg, uid, cmd);
	else
		Bprint(&bout, "%s no %s%s failed\r\n", tg, uid, cmd);
}

/*
 * minimal implementation of subscribe
 * all folders are automatically subscribed,
 * and can't be unsubscribed
 */
static void
subscribeCmd(char *tg, char *cmd)
{
	Box *box;
	char *mbox;
	int ok;

	mustBe(' ');
	mbox = astring();
	crnl();
	check();
	mbox = mboxName(mbox);
	ok = 0;
	if(mbox != nil && okMbox(mbox)){
		box = openBox(mbox, "subscribe", 0);
		if(box != nil){
			ok = subscribe(mbox, 's');
			closeBox(box, 1);
		}
	}
	if(!ok)
		Bprint(&bout, "%s no %s bad mailbox\r\n", tg, cmd);
	else
		Bprint(&bout, "%s OK %s completed\r\n", tg, cmd);
}

static void
uidCmd(char *tg, char *cmd)
{
	char *sub;

	mustBe(' ');
	sub = atom();
	if(cistrcmp(sub, "copy") == 0)
		copyUCmd(tg, sub, 1);
	else if(cistrcmp(sub, "fetch") == 0)
		fetchUCmd(tg, sub, 1);
	else if(cistrcmp(sub, "search") == 0)
		searchUCmd(tg, sub, 1);
	else if(cistrcmp(sub, "store") == 0)
		storeUCmd(tg, sub, 1);
	else{
		clearcmd();
		Bprint(&bout, "%s bad %s illegal uid command %s\r\n", tg, cmd, sub);
	}
}

static void
unsubscribeCmd(char *tg, char *cmd)
{
	char *mbox;

	mustBe(' ');
	mbox = astring();
	crnl();
	check();
	mbox = mboxName(mbox);
	if(mbox == nil || !okMbox(mbox) || !subscribe(mbox, 'u'))
		Bprint(&bout, "%s no %s can't unsubscribe\r\n", tg, cmd);
	else
		Bprint(&bout, "%s OK %s completed\r\n", tg, cmd);
}

static void
badsyn(void)
{
	parseErr("bad syntax");
}

static void
clearcmd(void)
{
	int c;

	for(;;){
		c = getc();
		if(c < 0)
			bye("end of input");
		if(c == '\n')
			return;
	}
}

static void
crnl(void)
{
	int c;

	c = getc();
	if(c == '\n')
		return;
	if(c != '\r' || getc() != '\n')
		badsyn();
}

static void
mustBe(int c)
{
	if(getc() != c){
		ungetc();
		badsyn();
	}
}

/*
 * flaglist	: '(' ')' | '(' flags ')'
 */
static int
flagList(void)
{
	int f;

	mustBe('(');
	f = 0;
	if(peekc() != ')')
		f = flags();

	mustBe(')');
	return f;
}

/*
 * flags	: flag | flags ' ' flag
 * flag		: '\' atom | atom
 */
static int
flags(void)
{
	int ff, flags;
	char *s;
	int c;

	flags = 0;
	for(;;){
		c = peekc();
		if(c == '\\'){
			mustBe('\\');
			s = atomString(atomStop, "\\");
		}else if(strchr(atomStop, c) != nil)
			s = atom();
		else
			break;
		ff = mapFlag(s);
		if(ff == 0)
			parseErr("flag not supported");
		flags |= ff;
		if(peekc() != ' ')
			break;
		mustBe(' ');
	}
	if(flags == 0)
		parseErr("no flags given");
	return flags;
}

/*
 * storeWhat	: osign 'FLAGS' ' ' storeflags
 *		| osign 'FLAGS.SILENT' ' ' storeflags
 * osign	:
 *		| '+' | '-'
 * storeflags	: flagList | flags
 */
static Store*
storeWhat(void)
{
	int f;
	char *s;
	int c, w;

	c = peekc();
	if(c == '+' || c == '-')
		mustBe(c);
	else
		c = 0;
	s = atom();
	w = 0;
	if(cistrcmp(s, "flags") == 0)
		w = STFlags;
	else if(cistrcmp(s, "flags.silent") == 0)
		w = STFlagsSilent;
	else
		parseErr("illegal store attribute");
	mustBe(' ');
	if(peekc() == '(')
		f = flagList();
	else
		f = flags();
	return mkStore(c, w, f);
}

/*
 * fetchWhat	: "ALL" | "FULL" | "FAST" | fetchAtt | '(' fetchAtts ')'
 * fetchAtts	: fetchAtt | fetchAtts ' ' fetchAtt
 */
static char *fetchAtom	= "(){}%*\"\\[]";
static Fetch*
fetchWhat(void)
{
	Fetch *f;
	char *s;

	if(peekc() == '('){
		getc();
		f = nil;
		for(;;){
			s = atomString(fetchAtom, "");
			f = fetchAtt(s, f);
			if(peekc() == ')')
				break;
			mustBe(' ');
		}
		getc();
		return revFetch(f);
	}

	s = atomString(fetchAtom, "");
	if(cistrcmp(s, "all") == 0)
		f = mkFetch(FFlags, mkFetch(FInternalDate, mkFetch(FRfc822Size, mkFetch(FEnvelope, nil))));
	else if(cistrcmp(s, "fast") == 0)
		f = mkFetch(FFlags, mkFetch(FInternalDate, mkFetch(FRfc822Size, nil)));
	else if(cistrcmp(s, "full") == 0)
		f = mkFetch(FFlags, mkFetch(FInternalDate, mkFetch(FRfc822Size, mkFetch(FEnvelope, mkFetch(FBody, nil)))));
	else
		f = fetchAtt(s, nil);
	return f;
}

/*
 * fetchAtt	: "ENVELOPE" | "FLAGS" | "INTERNALDATE"
 *		| "RFC822" | "RFC822.HEADER" | "RFC822.SIZE" | "RFC822.TEXT"
 *		| "BODYSTRUCTURE"
 *		| "UID"
 *		| "BODY"
 *		| "BODY" bodysubs
 *		| "BODY.PEEK" bodysubs
 * bodysubs	: sect
 *		| sect '<' number '.' nz-number '>'
 * sect		: '[' sectSpec ']'
 * sectSpec	: sectMsgText
 *		| sectPart
 *		| sectPart '.' sectText
 * sectPart	: nz-number
 *		| sectPart '.' nz-number
 */
static Fetch*
fetchAtt(char *s, Fetch *f)
{
	NList *sect;
	int c;

	if(cistrcmp(s, "envelope") == 0)
		return mkFetch(FEnvelope, f);
	if(cistrcmp(s, "flags") == 0)
		return mkFetch(FFlags, f);
	if(cistrcmp(s, "internaldate") == 0)
		return mkFetch(FInternalDate, f);
	if(cistrcmp(s, "RFC822") == 0)
		return mkFetch(FRfc822, f);
	if(cistrcmp(s, "RFC822.header") == 0)
		return mkFetch(FRfc822Head, f);
	if(cistrcmp(s, "RFC822.size") == 0)
		return mkFetch(FRfc822Size, f);
	if(cistrcmp(s, "RFC822.text") == 0)
		return mkFetch(FRfc822Text, f);
	if(cistrcmp(s, "bodystructure") == 0)
		return mkFetch(FBodyStruct, f);
	if(cistrcmp(s, "uid") == 0)
		return mkFetch(FUid, f);

	if(cistrcmp(s, "body") == 0){
		if(peekc() != '[')
			return mkFetch(FBody, f);
		f = mkFetch(FBodySect, f);
	}else if(cistrcmp(s, "body.peek") == 0)
		f = mkFetch(FBodyPeek, f);
	else
		parseErr("illegal fetch attribute");

	mustBe('[');
	c = peekc();
	if(c >= '1' && c <= '9'){
		sect = mkNList(number(1), nil);
		while(peekc() == '.'){
			getc();
			c = peekc();
			if(c >= '1' && c <= '9'){
				sect = mkNList(number(1), sect);
			}else{
				break;
			}
		}
		f->sect = revNList(sect);
	}
	if(peekc() != ']')
		sectText(f, f->sect != nil);
	mustBe(']');

	if(peekc() != '<')
		return f;

	f->partial = 1;
	mustBe('<');
	f->start = number(0);
	mustBe('.');
	f->size = number(1);
	mustBe('>');
	return f;
}

/*
 * sectText	: sectMsgText | "MIME"
 * sectMsgText	: "HEADER"
 *		| "TEXT"
 *		| "HEADER.FIELDS" ' ' hdrList
 *		| "HEADER.FIELDS.NOT" ' ' hdrList
 * hdrList	: '(' hdrs ')'
 * hdrs:	: astring
 *		| hdrs ' ' astring
 */
static void
sectText(Fetch *f, int mimeOk)
{
	SList *h;
	char *s;

	s = atomString(fetchAtom, "");
	if(cistrcmp(s, "header") == 0){
		f->part = FPHead;
		return;
	}
	if(cistrcmp(s, "text") == 0){
		f->part = FPText;
		return;
	}
	if(mimeOk && cistrcmp(s, "mime") == 0){
		f->part = FPMime;
		return;
	}
	if(cistrcmp(s, "header.fields") == 0)
		f->part = FPHeadFields;
	else if(cistrcmp(s, "header.fields.not") == 0)
		f->part = FPHeadFieldsNot;
	else
		parseErr("illegal fetch section text");
	mustBe(' ');
	mustBe('(');
	h = nil;
	for(;;){
		h = mkSList(astring(), h);
		if(peekc() == ')')
			break;
		mustBe(' ');
	}
	mustBe(')');
	f->hdrs = revSList(h);
}

/*
 * searchWhat	: "CHARSET" ' ' astring searchkeys | searchkeys
 * searchkeys	: searchkey | searchkeys ' ' searchkey
 * searchkey	: "ALL" | "ANSWERED" | "DELETED" | "FLAGGED" | "NEW" | "OLD" | "RECENT"
 *		| "SEEN" | "UNANSWERED" | "UNDELETED" | "UNFLAGGED" | "DRAFT" | "UNDRAFT"
 *		| astrkey ' ' astring
 *		| datekey ' ' date
 *		| "KEYWORD" ' ' flag | "UNKEYWORD" flag
 *		| "LARGER" ' ' number | "SMALLER" ' ' number
 * 		| "HEADER" astring ' ' astring
 *		| set | "UID" ' ' set
 *		| "NOT" ' ' searchkey
 *		| "OR" ' ' searchkey ' ' searchkey
 *		| '(' searchkeys ')'
 * astrkey	: "BCC" | "BODY" | "CC" | "FROM" | "SUBJECT" | "TEXT" | "TO"
 * datekey	: "BEFORE" | "ON" | "SINCE" | "SENTBEFORE" | "SENTON" | "SENTSINCE"
 */
static NamedInt searchMap[] =
{
	{"ALL",		SKAll},
	{"ANSWERED",	SKAnswered},
	{"DELETED",	SKDeleted},
	{"FLAGGED",	SKFlagged},
	{"NEW",		SKNew},
	{"OLD",		SKOld},
	{"RECENT",	SKRecent},
	{"SEEN",	SKSeen},
	{"UNANSWERED",	SKUnanswered},
	{"UNDELETED",	SKUndeleted},
	{"UNFLAGGED",	SKUnflagged},
	{"DRAFT",	SKDraft},
	{"UNDRAFT",	SKUndraft},
	{"UNSEEN",	SKUnseen},
	{nil,		0}
};

static NamedInt searchMapStr[] =
{
	{"CHARSET",	SKCharset},
	{"BCC",		SKBcc},
	{"BODY",	SKBody},
	{"CC",		SKCc},
	{"FROM",	SKFrom},
	{"SUBJECT",	SKSubject},
	{"TEXT",	SKText},
	{"TO",		SKTo},
	{nil,		0}
};

static NamedInt searchMapDate[] =
{
	{"BEFORE",	SKBefore},
	{"ON",		SKOn},
	{"SINCE",	SKSince},
	{"SENTBEFORE",	SKSentBefore},
	{"SENTON",	SKSentOn},
	{"SENTSINCE",	SKSentSince},
	{nil,		0}
};

static NamedInt searchMapFlag[] =
{
	{"KEYWORD",	SKKeyword},
	{"UNKEYWORD",	SKUnkeyword},
	{nil,		0}
};

static NamedInt searchMapNum[] =
{
	{"SMALLER",	SKSmaller},
	{"LARGER",	SKLarger},
	{nil,		0}
};

static Search*
searchKeys(int first, Search *tail)
{
	Search *s;

	for(;;){
		if(peekc() == '('){
			getc();
			tail = searchKeys(0, tail);
			mustBe(')');
		}else{
			s = searchKey(first);
			tail->next = s;
			tail = s;
		}
		first = 0;
		if(peekc() != ' ')
			break;
		getc();
	}
	return tail;
}

static Search*
searchKey(int first)
{
	Search *sr, rock;
	Tm tm;
	char *a;
	int i, c;

	sr = binalloc(&parseBin, sizeof(Search), 1);
	if(sr == nil)
		parseErr("out of memory");

	c = peekc();
	if(c >= '0' && c <= '9'){
		sr->key = SKSet;
		sr->set = msgSet();
		return sr;
	}

	a = atom();
	if(i = mapInt(searchMap, a))
		sr->key = i;
	else if(i = mapInt(searchMapStr, a)){
		if(!first && i == SKCharset)
			parseErr("illegal search key");
		sr->key = i;
		mustBe(' ');
		sr->s = astring();
	}else if(i = mapInt(searchMapDate, a)){
		sr->key = i;
		mustBe(' ');
		c = peekc();
		if(c == '"')
			getc();
		a = atom();
		if(!imap4Date(&tm, a))
			parseErr("bad date format");
		sr->year = tm.year;
		sr->mon = tm.mon;
		sr->mday = tm.mday;
		if(c == '"')
			mustBe('"');
	}else if(i = mapInt(searchMapFlag, a)){
		sr->key = i;
		mustBe(' ');
		c = peekc();
		if(c == '\\'){
			mustBe('\\');
			a = atomString(atomStop, "\\");
		}else
			a = atom();
		i = mapFlag(a);
		if(i == 0)
			parseErr("flag not supported");
		sr->num = i;
	}else if(i = mapInt(searchMapNum, a)){
		sr->key = i;
		mustBe(' ');
		sr->num = number(0);
	}else if(cistrcmp(a, "HEADER") == 0){
		sr->key = SKHeader;
		mustBe(' ');
		sr->hdr = astring();
		mustBe(' ');
		sr->s = astring();
	}else if(cistrcmp(a, "UID") == 0){
		sr->key = SKUid;
		mustBe(' ');
		sr->set = msgSet();
	}else if(cistrcmp(a, "NOT") == 0){
		sr->key = SKNot;
		mustBe(' ');
		rock.next = nil;
		searchKeys(0, &rock);
		sr->left = rock.next;
	}else if(cistrcmp(a, "OR") == 0){
		sr->key = SKOr;
		mustBe(' ');
		rock.next = nil;
		searchKeys(0, &rock);
		sr->left = rock.next;
		mustBe(' ');
		rock.next = nil;
		searchKeys(0, &rock);
		sr->right = rock.next;
	}else
		parseErr("illegal search key");
	return sr;
}

/*
 * set	: seqno
 *	| seqno ':' seqno
 *	| set ',' set
 * seqno: nz-number
 *	| '*'
 *
 */
static MsgSet*
msgSet(void)
{
	MsgSet head, *last, *ms;
	ulong from, to;

	last = &head;
	head.next = nil;
	for(;;){
		from = seqNo();
		to = from;
		if(peekc() == ':'){
			getc();
			to = seqNo();
		}
		ms = binalloc(&parseBin, sizeof(MsgSet), 0);
		if(ms == nil)
			parseErr("out of memory");
		ms->from = from;
		ms->to = to;
		ms->next = nil;
		last->next = ms;
		last = ms;
		if(peekc() != ',')
			break;
		getc();
	}
	return head.next;
}

static ulong
seqNo(void)
{
	if(peekc() == '*'){
		getc();
		return ~0UL;
	}
	return number(1);
}

/*
 * 7 bit, non-ctl chars, no (){%*"\
 * NIL is special case for nstring or parenlist
 */
static char *
atom(void)
{
	return atomString(atomStop, "");
}

/*
 * like an atom, but no +
 */
static char *
tag(void)
{
	return atomString("+(){%*\"\\", "");
}

/*
 * string or atom allowing %*
 */
static char *
listmbox(void)
{
	int c;

	c = peekc();
	if(c == '{')
		return literal();
	if(c == '"')
		return quoted();
	return atomString("(){\"\\", "");
}

/*
 * string or atom
 */
static char *
astring(void)
{
	int c;

	c = peekc();
	if(c == '{')
		return literal();
	if(c == '"')
		return quoted();
	return atom();
}

/*
 * 7 bit, non-ctl chars, none from exception list
 */
static char *
atomString(char *disallowed, char *initial)
{
	char *s;
	int c, ns, as;

	ns = strlen(initial);
	s = binalloc(&parseBin, ns + StrAlloc, 0);
	if(s == nil)
		parseErr("out of memory");
	strcpy(s, initial);
	as = ns + StrAlloc;
	for(;;){
		c = getc();
		if(c <= ' ' || c >= 0x7f || strchr(disallowed, c) != nil){
			ungetc();
			break;
		}
		s[ns++] = c;
		if(ns >= as){
			s = bingrow(&parseBin, s, as, as + StrAlloc, 0);
			if(s == nil)
				parseErr("out of memory");
			as += StrAlloc;
		}
	}
	if(ns == 0)
		badsyn();
	s[ns] = '\0';
	return s;
}

/*
 * quoted: '"' chars* '"'
 * chars:	1-128 except \r and \n
 */
static char *
quoted(void)
{
	char *s;
	int c, ns, as;

	mustBe('"');
	s = binalloc(&parseBin, StrAlloc, 0);
	if(s == nil)
		parseErr("out of memory");
	as = StrAlloc;
	ns = 0;
	for(;;){
		c = getc();
		if(c == '"')
			break;
		if(c < 1 || c > 0x7f || c == '\r' || c == '\n')
			badsyn();
		if(c == '\\'){
			c = getc();
			if(c != '\\' && c != '"')
				badsyn();
		}
		s[ns++] = c;
		if(ns >= as){
			s = bingrow(&parseBin, s, as, as + StrAlloc, 0);
			if(s == nil)
				parseErr("out of memory");
			as += StrAlloc;
		}
	}
	s[ns] = '\0';
	return s;
}

/*
 * litlen: {number}\r\n
 */
static ulong
litlen(void)
{
	ulong v;

	mustBe('{');
	v = number(0);
	mustBe('}');
	crnl();
	return v;
}

/*
 * literal: litlen data<0:litlen>
 */
static char *
literal(void)
{
	char *s;
	ulong v;

	v = litlen();
	s = binalloc(&parseBin, v+1, 0);
	if(s == nil)
		parseErr("out of memory");
	Bprint(&bout, "+ Ready for literal data\r\n");
	if(Bflush(&bout) < 0)
		writeErr();
	if(v != 0 && Bread(&bin, s, v) != v)
		badsyn();
	s[v] = '\0';
	return s;
}

/*
 * digits; number is 32 bits
 */
static ulong
number(int nonzero)
{
	ulong v;
	int c, first;

	v = 0;
	first = 1;
	for(;;){
		c = getc();
		if(c < '0' || c > '9'){
			ungetc();
			if(first)
				badsyn();
			break;
		}
		if(nonzero && first && c == '0')
			badsyn();
		c -= '0';
		first = 0;
		if(v > UlongMax/10 || v == UlongMax/10 && c > UlongMax%10)
			parseErr("number out of range\r\n");
		v = v * 10 + c;
	}
	return v;
}

static int
getc(void)
{
	return Bgetc(&bin);
}

static void
ungetc(void)
{
	Bungetc(&bin);
}

static int
peekc(void)
{
	int c;

	c = Bgetc(&bin);
	Bungetc(&bin);
	return c;
}

[-- Attachment #3: .signature --]
[-- Type: text/plain, Size: 96 bytes --]


--
                 Saludos,
                         Gorka

"Curiosity sKilled the cat"

^ permalink raw reply	[flat|nested] 6+ messages in thread

* Re: [9fans] imap4d bug
  2002-11-15 16:06 ` paurea
@ 2002-11-15 16:33   ` paurea
  0 siblings, 0 replies; 6+ messages in thread
From: paurea @ 2002-11-15 16:33 UTC (permalink / raw)
  To: 9fans

paurea@gsyc.escet.urjc.es writes:
 > Now it works with evolution too (more downcase commands).

Not true... It works just the authentication (before it didn't). It
works with emacs (vm) and mozilla and fetchmail, but not evolution.
Maybe more downcase things...
--
                 Saludos,
                         Gorka

"Curiosity sKilled the cat"


^ permalink raw reply	[flat|nested] 6+ messages in thread

* Re: [9fans] imap4d bug
  2002-11-15 18:03 presotto
@ 2002-11-17 14:22 ` paurea
  0 siblings, 0 replies; 6+ messages in thread
From: paurea @ 2002-11-17 14:22 UTC (permalink / raw)
  To: 9fans

presotto@plan9.bell-labs.com writes:
 > From: presotto@plan9.bell-labs.com
 > Subject: Re: [9fans] imap4d bug
 > Date: Fri, 15 Nov 2002 13:03:36 -0500
 >
 > Changing the case of the arguments to cistrcmp (the case insensitive
 > compare) is a noop.

It does seem to matter with fetchmail, vm (emacs reader) and evolution.
--
                 Saludos,
                         Gorka

"Curiosity sKilled the cat"


^ permalink raw reply	[flat|nested] 6+ messages in thread

* Re: [9fans] imap4d bug
@ 2002-11-15 21:04 presotto
  0 siblings, 0 replies; 6+ messages in thread
From: presotto @ 2002-11-15 21:04 UTC (permalink / raw)
  To: paurea, 9fans

[-- Attachment #1: Type: text/plain, Size: 579 bytes --]

I've got some questions about this bug fix.  Not all responses are
converted from lower case to upper case.  Was this intentional
or an oversight?

Also, a passCR()/allowCR seems to have crept in somewhere after sources.
I have no problem with it but in the code it is functionally
exclusive with passLogin()/allowPass.  Should I exit with an error
if a user specifies both or did you intend something else?

Finally, as I said before, the cistrcmp's shouldn't care about
case.  Did you find that they did, i.e., are you reporting a bug
or did you just not understand?

[-- Attachment #2: Type: message/rfc822, Size: 45149 bytes --]

[-- Attachment #2.1.1: message body text --]
[-- Type: text/plain, Size: 410 bytes --]

paurea@gsyc.escet.urjc.es writes:
 > From: paurea@gsyc.escet.urjc.es
 > Subject: [9fans] imap4d bug
 > Date: Fri, 15 Nov 2002 16:41:15 +0100
 >
 > Some of the imap commands where written downcase and because of that it
 > didn't interact with fetchmail, emacs or Netscape 7 mail reader.(it did
 > work with mozilla don't know why...).
 >

Now it works with evolution too (more downcase commands).


[-- Attachment #2.1.2: imap4d.c --]
[-- Type: text/plain, Size: 42283 bytes --]

#include <u.h>
#include <libc.h>
#include <auth.h>
#include <bio.h>
#include "imap4d.h"

/*
 * these should be in libraries
 */
char	*csquery(char *attr, char *val, char *rattr);

/*
 * /lib/rfc/rfc2060 imap4rev1
 * /lib/rfc/rfc2683 is implementation advice
 * /lib/rfc/rfc2342 is namespace capability
 * /lib/rfc/rfc2222 is security protocols
 * /lib/rfc/rfc1731 is security protocols
 * /lib/rfc/rfc2221 is LOGIN-REFERRALS
 * /lib/rfc/rfc2193 is MAILBOX-REFERRALS
 * /lib/rfc/rfc2177 is IDLE capability
 * /lib/rfc/rfc2195 is CRAM-MD5 authentication
 * /lib/rfc/rfc2088 is LITERAL+ capability
 * /lib/rfc/rfc1760 is S/Key authentication
 *
 * outlook uses "Secure Password Authentication" aka ntlm authentication
 *
 * capabilities from nslocum
 * CAPABILITY IMAP4 IMAP4REV1 NAMESPACE IDLE SCAN SORT MAILBOX-REFERRALS LOGIN-REFERRALS AUTH=LOGIN THREAD=ORDEREDSUBJECT
 */

typedef struct	ParseCmd	ParseCmd;

enum
{
	UlongMax	= 4294967295,
};

struct ParseCmd
{
	char	*name;
	void	(*f)(char *tg, char *cmd);
};

static	void	appendCmd(char *tg, char *cmd);
static	void	authenticateCmd(char *tg, char *cmd);
static	void	capabilityCmd(char *tg, char *cmd);
static	void	closeCmd(char *tg, char *cmd);
static	void	copyCmd(char *tg, char *cmd);
static	void	createCmd(char *tg, char *cmd);
static	void	deleteCmd(char *tg, char *cmd);
static	void	expungeCmd(char *tg, char *cmd);
static	void	fetchCmd(char *tg, char *cmd);
static	void	idleCmd(char *tg, char *cmd);
static	void	listCmd(char *tg, char *cmd);
static	void	loginCmd(char *tg, char *cmd);
static	void	logoutCmd(char *tg, char *cmd);
static	void	namespaceCmd(char *tg, char *cmd);
static	void	noopCmd(char *tg, char *cmd);
static	void	renameCmd(char *tg, char *cmd);
static	void	searchCmd(char *tg, char *cmd);
static	void	selectCmd(char *tg, char *cmd);
static	void	statusCmd(char *tg, char *cmd);
static	void	storeCmd(char *tg, char *cmd);
static	void	subscribeCmd(char *tg, char *cmd);
static	void	uidCmd(char *tg, char *cmd);
static	void	unsubscribeCmd(char *tg, char *cmd);

static	void	copyUCmd(char *tg, char *cmd, int uids);
static	void	fetchUCmd(char *tg, char *cmd, int uids);
static	void	searchUCmd(char *tg, char *cmd, int uids);
static	void	storeUCmd(char *tg, char *cmd, int uids);

static	void	imap4(int);
static	void	status(int expungeable, int uids);
static	void	cleaner(void);
static	void	check(void);
static	int	catcher(void*, char*);

static	Search	*searchKey(int first);
static	Search	*searchKeys(int first, Search *tail);
static	char	*astring(void);
static	char	*atomString(char *disallowed, char *initial);
static	char	*atom(void);
static	void	badsyn(void);
static	void	clearcmd(void);
static	char	*command(void);
static	void	crnl(void);
static	Fetch	*fetchAtt(char *s, Fetch *f);
static	Fetch	*fetchWhat(void);
static	int	flagList(void);
static	int	flags(void);
static	int	getc(void);
static	char	*listmbox(void);
static	char	*literal(void);
static	ulong	litlen(void);
static	MsgSet	*msgSet(void);
static	void	mustBe(int c);
static	ulong	number(int nonzero);
static	int	peekc(void);
static	char	*quoted(void);
static	void	sectText(Fetch *f, int mimeOk);
static	ulong	seqNo(void);
static	Store	*storeWhat(void);
static	char	*tag(void);
static	void	ungetc(void);

static	ParseCmd	SNonAuthed[] =
{
	{"capability",		capabilityCmd},
	{"logout",		logoutCmd},
	{"x-exit",		logoutCmd},
	{"noop",		noopCmd},

	{"login",		loginCmd},
	{"authenticate",	authenticateCmd},

	nil
};

static	ParseCmd	SAuthed[] =
{
	{"capability",		capabilityCmd},
	{"logout",		logoutCmd},
	{"x-exit",		logoutCmd},
	{"noop",		noopCmd},

	{"append",		appendCmd},
	{"create",		createCmd},
	{"delete",		deleteCmd},
	{"examine",		selectCmd},
	{"select",		selectCmd},
	{"idle",		idleCmd},
	{"list",		listCmd},
	{"lsub",		listCmd},
	{"namespace",		namespaceCmd},
	{"rename",		renameCmd},
	{"status",		statusCmd},
	{"subscribe",		subscribeCmd},
	{"unsubscribe",		unsubscribeCmd},

	nil
};

static	ParseCmd	SSelected[] =
{
	{"capability",		capabilityCmd},
	{"logout",		logoutCmd},
	{"x-exit",		logoutCmd},
	{"noop",		noopCmd},

	{"append",		appendCmd},
	{"create",		createCmd},
	{"delete",		deleteCmd},
	{"examine",		selectCmd},
	{"select",		selectCmd},
	{"idle",		idleCmd},
	{"list",		listCmd},
	{"lsub",		listCmd},
	{"namespace",		namespaceCmd},
	{"rename",		renameCmd},
	{"status",		statusCmd},
	{"subscribe",		subscribeCmd},
	{"unsubscribe",		unsubscribeCmd},

	{"check",		noopCmd},
	{"close",		closeCmd},
	{"copy",		copyCmd},
	{"expunge",		expungeCmd},
	{"fetch",		fetchCmd},
	{"search",		searchCmd},
	{"store",		storeCmd},
	{"uid",			uidCmd},

	nil
};

static	char		*atomStop = "(){%*\"\\";
static	Chalstate	*chal;
static	int		chaled;
static	ParseCmd	*imapState;
static	jmp_buf		parseJmp;
static	char		*parseMsg;
static	int		allowPass;
static	int		allowCR;
static	int		exiting;
static	QLock		imaplock;
static	int		idlepid = -1;

Biobuf	bout;
Biobuf	bin;
char	username[UserNameLen];
char	mboxDir[MboxNameLen];
char	*servername;
char	*site;
char	*remote;
Box	*selected;
Bin	*parseBin;
int	debug;

void
main(int argc, char *argv[])
{
	char *s, *t;
	int preauth, n;

	Binit(&bin, 0, OREAD);
	Binit(&bout, 1, OWRITE);

	preauth = 0;
	allowPass = 0;
	allowCR = 0;
	ARGBEGIN{
	case 'a':
		preauth = 1;
		break;
	case 'd':
		site = ARGF();
		break;
	case 'c':
		allowCR = 1;
		break;
	case 'p':
		allowPass = 1;
		break;
	case 'r':
		remote = ARGF();
		break;
	case 's':
		servername = ARGF();
		break;
	case 'v':
		debug = 1;
		debuglog("imap4d debugging enabled\n");
		break;
	default:
		fprint(2, "usage: ip/imap4d [-acp] [-d site] [-r remotehost] [-s servername]\n");
		bye("usage");
		break;
	}ARGEND

	if(preauth)
		setupuser(nil);

	if(servername == nil){
		servername = csquery("sys", sysname(), "dom");
		if(servername == nil)
			servername = sysname();
		if(servername == nil){
			fprint(2, "ip/imap4d can't find server name: %r\n");
			bye("can't find system name");
		}
	}
	if(site == nil){
		t = getenv("site");
		if(t == nil)
			site = servername;
		else{
			n = strlen(t);
			s = strchr(servername, '.');
			if(s == nil)
				s = servername;
			else
				s++;
			n += strlen(s) + 2;
			site = emalloc(n);
			snprint(site, n, "%s.%s", t, s);
		}
	}

	rfork(RFNOTEG|RFREND);

	atnotify(catcher, 1);
	qlock(&imaplock);
	atexit(cleaner);
	imap4(preauth);
}

static void
imap4(int preauth)
{
	char *volatile tg;
	char *volatile cmd;
	ParseCmd *st;

	if(preauth){
		Bprint(&bout, "* preauth %s IMAP4rev1 server ready user %s authenticated\r\n", servername, username);
		imapState = SAuthed;
	}else{
		Bprint(&bout, "* ok %s IMAP4rev1 server ready\r\n", servername);
		imapState = SNonAuthed;
	}
	if(Bflush(&bout) < 0)
		writeErr();

	chaled = 0;

	tg = nil;
	cmd = nil;
	if(setjmp(parseJmp)){
		if(tg == nil)
			Bprint(&bout, "* bad empty command line: %s\r\n", parseMsg);
		else if(cmd == nil)
			Bprint(&bout, "%s bad no command: %s\r\n", tg, parseMsg);
		else
			Bprint(&bout, "%s bad %s %s\r\n", tg, cmd, parseMsg);
		clearcmd();
		if(Bflush(&bout) < 0)
			writeErr();
		binfree(&parseBin);
	}
	for(;;){
		if(mbLocked())
			bye("internal error: mailbox lock held");
		tg = nil;
		cmd = nil;
		tg = tag();
		mustBe(' ');
		cmd = atom();

		/*
		 * note: outlook express is broken: it requires echoing the
		 * command as part of matching response
		 */
		for(st = imapState; st->name != nil; st++){
			if(cistrcmp(cmd, st->name) == 0){
				(*st->f)(tg, cmd);
				break;
			}
		}
		if(st->name == nil){
			clearcmd();
			Bprint(&bout, "%s bad %s illegal command\r\n", tg, cmd);
		}

		if(Bflush(&bout) < 0)
			writeErr();
		binfree(&parseBin);
	}
}

void
bye(char *fmt, ...)
{
	va_list arg;

	va_start(arg, fmt);
	Bprint(&bout, "* bye ");
	Bvprint(&bout, fmt, arg);
	Bprint(&bout, "\r\n");
	Bflush(&bout);
exits("rob2");
	exits(0);
}

void
parseErr(char *msg)
{
	parseMsg = msg;
	longjmp(parseJmp, 1);
}

/*
 * an error occured while writing to the client
 */
void
writeErr(void)
{
	cleaner();
	_exits("connection closed");
}

static int
catcher(void *v, char *msg)
{
	USED(v);
	if(strstr(msg, "closed pipe") != nil)
		return 1;
	return 0;
}

/*
 * wipes out the idleCmd backgroung process if it is around.
 * this can only be called if the current proc has qlocked imaplock.
 * it must be the last piece of imap4d code executed.
 */
static void
cleaner(void)
{
	int i;

	if(idlepid < 0)
		return;
	exiting = 1;
	close(0);
	close(1);
	close(2);

	/*
	 * the other proc is either stuck in a read, a sleep,
	 * or is trying to lock imap4lock.
	 * get him out of it so he can exit cleanly
	 */
	qunlock(&imaplock);
	for(i = 0; i < 4; i++)
		postnote(PNGROUP, getpid(), "die");
}

/*
 * send any pending status updates to the client
 * careful: shouldn't exit, because called by idle polling proc
 *
 * can't always send pending info
 * in particular, can't send expunge info
 * in response to a fetch, store, or search command.
 *
 * rfc2060 5.2:	server must send mailbox size updates
 * rfc2060 5.2:	server may send flag updates
 * rfc2060 5.5:	servers prohibited from sending expunge while fetch, store, search in progress
 * rfc2060 7:	in selected state, server checks mailbox for new messages as part of every command
 * 		sends untagged EXISTS and RECENT respsonses reflecting new size of the mailbox
 * 		should also send appropriate untagged FETCH and EXPUNGE messages if another agent
 * 		changes the state of any message flags or expunges any messages
 * rfc2060 7.4.1	expunge server response must not be sent when no command is in progress,
 * 		nor while responding to a fetch, stort, or search command (uid versions are ok)
 * 		command only "in progress" after entirely parsed.
 *
 * strategy for third party deletion of messages or of a mailbox
 *
 * deletion of a selected mailbox => act like all message are expunged
 *	not strictly allowed by rfc2180, but close to method 3.2.
 *
 * renaming same as deletion
 *
 * copy
 *	reject iff a deleted message is in the request
 *
 * search, store, fetch operations on expunged messages
 *	ignore the expunged messages
 *	return tagged no if referenced
 */
static void
status(int expungeable, int uids)
{
	int tell;

	if(!selected)
		return;
	tell = 0;
	if(expungeable)
		tell = expungeMsgs(selected, 1);
	if(selected->sendFlags)
		sendFlags(selected, uids);
	if(tell || selected->toldMax != selected->max){
		Bprint(&bout, "* %lud exists\r\n", selected->max);
		selected->toldMax = selected->max;
	}
	if(tell || selected->toldRecent != selected->recent){
		Bprint(&bout, "* %lud recent\r\n", selected->recent);
		selected->toldRecent = selected->recent;
	}
	if(tell)
		closeImp(selected, checkBox(selected, 1));
}

/*
 * careful: can't exit, because called by idle polling proc
 */
static void
check(void)
{
	if(!selected)
		return;
	checkBox(selected, 0);
	status(1, 0);
}

static void
appendCmd(char *tg, char *cmd)
{
	char *mbox, head[128];
	ulong t, n, now;
	int flags, ok;

	mustBe(' ');
	mbox = astring();
	mustBe(' ');
	flags = 0;
	if(peekc() == '('){
		flags = flagList();
		mustBe(' ');
	}
	now = time(nil);
	if(peekc() == '"'){
		t = imap4DateTime(quoted());
		if(t == ~0)
			parseErr("illegal date format");
		mustBe(' ');
		if(t > now)
			t = now;
	}else
		t = now;
	n = litlen();

	mbox = mboxName(mbox);
	if(mbox == nil || !okMbox(mbox)){
		check();
		Bprint(&bout, "%s no %s bad mailbox\r\n", tg, cmd);
		return;
	}
	if(!cdExists(mboxDir, mbox)){
		check();
		Bprint(&bout, "%s no [TRYCREATE] %s mailbox does not exist\r\n", tg, cmd);
		return;
	}

	snprint(head, sizeof(head), "From %s %s", username, ctime(t));
	ok = appendSave(mbox, flags, head, &bin, n);
	crnl();
	check();
	if(ok)
		Bprint(&bout, "%s OK %s completed\r\n", tg, cmd);
	else
		Bprint(&bout, "%s no %s message save failed\r\n", tg, cmd);
}

static void
authenticateCmd(char *tg, char *cmd)
{
	char *s, *t;

	mustBe(' ');
	s = atom();
	crnl();
	auth_freechal(chal);
	chal = nil;
	if(cistrcmp(s, "cram-md5") == 0){
		t = cramauth();
		if(t == nil){
			Bprint(&bout, "%s OK %s\r\n", tg, cmd);
			imapState = SAuthed;
		}else
			Bprint(&bout, "%s no %s failed %s\r\n", tg, cmd, t);
	}else
		Bprint(&bout, "%s no %s unsupported authentication protocol\r\n", tg, cmd);
}

static void
capabilityCmd(char *tg, char *cmd)
{
	crnl();
	check();
	Bprint(&bout, "* CAPABILITY IMAP4REV1 IDLE NAMESPACE AUTH=CRAM-MD5\r\n");
	Bprint(&bout, "%s OK %s\r\n", tg, cmd);
}

static void
closeCmd(char *tg, char *cmd)
{
	crnl();
	imapState = SAuthed;
	closeBox(selected, 1);
	selected = nil;
	Bprint(&bout, "%s ok %s mailbox closed, now in authenticated state\r\n", tg, cmd);
}

/*
 * note: message id's are before any pending expunges
 */
static void
copyCmd(char *tg, char *cmd)
{
	copyUCmd(tg, cmd, 0);
}

static void
copyUCmd(char *tg, char *cmd, int uids)
{
	MsgSet *ms;
	char *uid, *mbox;
	ulong max;
	int ok;

	mustBe(' ');
	ms = msgSet();
	mustBe(' ');
	mbox = astring();
	crnl();

	uid = "";
	if(uids)
		uid = "uid ";

	mbox = mboxName(mbox);
	if(mbox == nil || !okMbox(mbox)){
		status(1, uids);
		Bprint(&bout, "%s no %s%s bad mailbox\r\n", tg, uid, cmd);
		return;
	}
	if(!cdExists(mboxDir, mbox)){
		check();
		Bprint(&bout, "%s no [TRYCREATE] %s mailbox does not exist\r\n", tg, cmd);
		return;
	}

	max = selected->max;
	checkBox(selected, 0);
	ok = forMsgs(selected, ms, max, uids, copyCheck, nil);
	if(ok)
		ok = forMsgs(selected, ms, max, uids, copySave, mbox);

	status(1, uids);
	if(ok)
		Bprint(&bout, "%s OK %s%s completed\r\n", tg, uid, cmd);
	else
		Bprint(&bout, "%s no %s%s failed\r\n", tg, uid, cmd);
}

static void
createCmd(char *tg, char *cmd)
{
	char *mbox, *m;
	int fd, slash;

	mustBe(' ');
	mbox = astring();
	crnl();
	check();

	m = strchr(mbox, '\0');
	slash = m != mbox && m[-1] == '/';
	mbox = mboxName(mbox);
	if(mbox == nil || !okMbox(mbox)){
		Bprint(&bout, "%s no %s bad mailbox\r\n", tg, cmd);
		return;
	}
	if(cistrcmp(mbox, "inbox") == 0){
		Bprint(&bout, "%s no %s cannot remotely create INBOX\r\n", tg, cmd);
		return;
	}
	if(access(mbox, AEXIST) >= 0){
		Bprint(&bout, "%s no %s mailbox already exists\r\n", tg, cmd);
		return;
	}

	fd = createBox(mbox, slash);
	close(fd);
	if(fd < 0)
		Bprint(&bout, "%s no %s cannot create mailbox %s\r\n", tg, cmd, mbox);
	else
		Bprint(&bout, "%s OK %s %s completed\r\n", tg, mbox, cmd);
}

static void
deleteCmd(char *tg, char *cmd)
{
	char *mbox, *imp;

	mustBe(' ');
	mbox = astring();
	crnl();
	check();

	mbox = mboxName(mbox);
	if(mbox == nil || !okMbox(mbox)){
		Bprint(&bout, "%s no %s bad mailbox\r\n", tg, cmd);
		return;
	}

	imp = impName(mbox);
	if(cistrcmp(mbox, "inbox") == 0
	|| imp != nil && cdRemove(mboxDir, imp) < 0 && cdExists(mboxDir, imp)
	|| cdRemove(mboxDir, mbox) < 0)
		Bprint(&bout, "%s no %s cannot delete mailbox %s\r\n", tg, cmd, mbox);
	else
		Bprint(&bout, "%s OK %s %s completed\r\n", tg, mbox, cmd);
}

static void
expungeCmd(char *tg, char *cmd)
{
	int ok;

	crnl();
	ok = deleteMsgs(selected);
	check();
	if(ok)
		Bprint(&bout, "%s ok %s messages erased\r\n", tg, cmd);
	else
		Bprint(&bout, "%s no %s some messages not expunged\r\n", tg, cmd);
}

static void
fetchCmd(char *tg, char *cmd)
{
	fetchUCmd(tg, cmd, 0);
}

static void
fetchUCmd(char *tg, char *cmd, int uids)
{
	Fetch *f;
	MsgSet *ms;
	MbLock *ml;
	char *uid;
	ulong max;
	int ok;

	mustBe(' ');
	ms = msgSet();
	mustBe(' ');
	f = fetchWhat();
	crnl();
	uid = "";
	if(uids)
		uid = "uid ";
	max = selected->max;
	ml = checkBox(selected, 1);
	if(ml != nil)
		forMsgs(selected, ms, max, uids, fetchSeen, f);
	closeImp(selected, ml);
	ok = ml != nil && forMsgs(selected, ms, max, uids, fetchMsg, f);
	status(uids, uids);
	if(ok)
		Bprint(&bout, "%s OK %s%s completed\r\n", tg, uid, cmd);
	else
		Bprint(&bout, "%s no %s%s failed\r\n", tg, uid, cmd);
}

static void
idleCmd(char *tg, char *cmd)
{
	int c, pid;

	crnl();
	Bprint(&bout, "+ idling, waiting for done\r\n");
	if(Bflush(&bout) < 0)
		writeErr();

	if(idlepid < 0){
		pid = rfork(RFPROC|RFMEM|RFNOWAIT);
		if(pid == 0){
			for(;;){
				qlock(&imaplock);
				if(exiting)
					break;

				/*
				 * parent may have changed curDir, but it doesn't change our .
				 */
				resetCurDir();

				check();
				if(Bflush(&bout) < 0)
					writeErr();
				qunlock(&imaplock);
				sleep(15*1000);
				enableForwarding();
			}
_exits("rob3");
			_exits(0);
		}
		idlepid = pid;
	}

	qunlock(&imaplock);

	/*
	 * clear out the next line, which is supposed to contain (case-insensitive)
	 * done\n
	 * this is special code since it has to dance with the idle polling proc
	 * and handle exiting correctly.
	 */
	for(;;){
		c = getc();
		if(c < 0){
			qlock(&imaplock);
			if(!exiting)
				cleaner();
_exits("rob4");
			_exits(0);
		}
		if(c == '\n')
			break;
	}

	qlock(&imaplock);
	if(exiting)
{_exits("rob5");
		_exits(0);
}

	/*
	 * child may have changed curDir, but it doesn't change our .
	 */
	resetCurDir();

	check();
	Bprint(&bout, "%s ok %s terminated\r\n", tg, cmd);
}

static void
listCmd(char *tg, char *cmd)
{
	char *s, *t, *ss, *ref, *mbox;
	int n;

	mustBe(' ');
	s = astring();
	mustBe(' ');
	t = listmbox();
	crnl();
	check();
	ref = mutf7str(s);
	mbox = mutf7str(t);
	if(ref == nil || mbox == nil){
		Bprint(&bout, "%s bad %s mailbox name not in modified utf-7\r\n", tg, cmd);
		return;
	}

	/*
	 * special request for hierarchy delimiter and root name
	 * root name appears to be name up to and including any delimiter,
	 * or the empty string, if there is no delimiter.
	 *
	 * this must change if the # namespace convention is supported.
	 */
	if(*mbox == '\0'){
		s = strchr(ref, '/');
		if(s == nil)
			ref = "";
		else
			s[1] = '\0';
		Bprint(&bout, "* %s (\\Noselect) \"/\" \"%s\"\r\n", cmd, ref);
		Bprint(&bout, "%s ok %s\r\n", tg, cmd);
		return;
	}


	/*
	 * massage the listing name:
	 * clean up the components individually,
	 * then rip off componenets from the ref to
	 * take care of leading ..'s in the mbox.
	 *
	 * the cleanup can wipe out * followed by a ..
	 * tough luck if such a stupid pattern is given.
	 */
	cleanname(mbox);
	if(strcmp(mbox, ".") == 0)
		*mbox = '\0';
	if(mbox[0] == '/')
		*ref = '\0';
	else if(*ref != '\0'){
		cleanname(ref);
		if(strcmp(ref, ".") == 0)
			*ref = '\0';
	}else
		*ref = '\0';
	while(*ref && isdotdot(mbox)){
		s = strrchr(ref, '/');
		if(s == nil)
			s = ref;
		if(isdotdot(s))
			break;
		*s = '\0';
		mbox += 2;
		if(*mbox == '/')
			mbox++;
	}
	if(*ref == '\0'){
		s = mbox;
		ss = s;
	}else{
		n = strlen(ref) + strlen(mbox) + 2;
		t = binalloc(&parseBin, n, 0);
		if(t == nil)
			parseErr("out of memory");
		snprint(t, n, "%s/%s", ref, mbox);
		s = t;
		ss = s + strlen(ref);
	}

	/*
	 * only allow activity in /mail/box
	 */
	if(s[0] == '/' || isdotdot(s)){
		Bprint(&bout, "%s no illegal mailbox pattern\r\n", tg);
		return;
	}

	if(cistrcmp(cmd, "lsub") == 0)
		lsubBoxes(cmd, s, ss);
	else
		listBoxes(cmd, s, ss);
	Bprint(&bout, "%s OK %s completed\r\n", tg, cmd);
}

static char*
passCR(char*u, char*p)
{
	static char Ebadch[] = "can't get challenge";
	static char nchall[64];
	static char response[64];
	static Chalstate *ch = nil;
	AuthInfo *ai;

again:
	if (ch == nil){
		if(!(ch = auth_challenge("proto=p9cr role=server user=%q", u)))
			return Ebadch;
		snprint(nchall, 64, " encrypt challenge: %s", ch->chal);
		return nchall;
	} else {
		strncpy(response, p, 64);
		ch->resp = response;
		ch->nresp = strlen(response);
		ai = auth_response(ch);
		auth_freechal(ch);
		ch = nil;
		if (ai == nil)
			goto again;
		setupuser(ai);
		return nil;
	}

}

static void
loginCmd(char *tg, char *cmd)
{
	char *s, *t;
	AuthInfo *ai;
	char*r;
	mustBe(' ');
	s = astring();	/* uid */
	mustBe(' ');
	t = astring();	/* password */
	crnl();
	if(allowCR){
		if ((r = passCR(s, t)) == nil){
			Bprint(&bout, "%s ok %s succeeded\r\n", tg, cmd);
			imapState = SAuthed;
		} else {
			Bprint(&bout, "* no [ALERT] %s\r\n", r);
			Bprint(&bout, "%s no %s succeeded\r\n", tg, cmd);
		}
		return;
	}
	else if(allowPass){
		if(ai = passLogin(s, t)){
			setupuser(ai);
			Bprint(&bout, "%s ok %s succeeded\r\n", tg, cmd);
			imapState = SAuthed;
		}else
			Bprint(&bout, "%s no %s failed check\r\n", tg, cmd);
		return;
	}
	Bprint(&bout, "%s no %s plaintext passwords disallowed\r\n", tg, cmd);
}

/*
 * logout or x-exit, which doesn't expunge the mailbox
 */
static void
logoutCmd(char *tg, char *cmd)
{
	crnl();

	if(cmd[0] != 'x' && selected){
		closeBox(selected, 1);
		selected = nil;
	}
	Bprint(&bout, "* bye\r\n");
	Bprint(&bout, "%s OK %s completed\r\n", tg, cmd);
exits("rob6");
	exits(0);
}

static void
namespaceCmd(char *tg, char *cmd)
{
	crnl();
	check();

	/*
	 * personal, other users, shared namespaces
	 * send back nil or descriptions of (prefix heirarchy-delim) for each case
	 */
	Bprint(&bout, "* namespace ((\"\" \"/\")) nil nil\r\n");
	Bprint(&bout, "%s OK %s completed\r\n", tg, cmd);
}

static void
noopCmd(char *tg, char *cmd)
{
	crnl();
	check();
	Bprint(&bout, "%s OK %s completed\r\n", tg, cmd);
	enableForwarding();
}

/*
 * this is only a partial implementation
 * should copy files to other directories,
 * and copy & truncate inbox
 */
static void
renameCmd(char *tg, char *cmd)
{
	char *from, *to;
	int ok;

	mustBe(' ');
	from = astring();
	mustBe(' ');
	to = astring();
	crnl();
	check();

	to = mboxName(to);
	if(to == nil || !okMbox(to) || cistrcmp(to, "inbox") == 0){
		Bprint(&bout, "%s no %s bad mailbox destination name\r\n", tg, cmd);
		return;
	}
	if(access(to, AEXIST) >= 0){
		Bprint(&bout, "%s no %s mailbox already exists\r\n", tg, cmd);
		return;
	}
	from = mboxName(from);
	if(from == nil || !okMbox(from)){
		Bprint(&bout, "%s no %s bad mailbox destination name\r\n", tg, cmd);
		return;
	}
	if(cistrcmp(from, "inbox") == 0)
		ok = copyBox(from, to, 0);
	else
		ok = moveBox(from, to);

	if(ok)
		Bprint(&bout, "%s OK %s completed\r\n", tg, cmd);
	else
		Bprint(&bout, "%s no %s failed\r\n", tg, cmd);
}

static void
searchCmd(char *tg, char *cmd)
{
	searchUCmd(tg, cmd, 0);
}

static void
searchUCmd(char *tg, char *cmd, int uids)
{
	Search rock;
	Msg *m;
	char *uid;
	ulong id;

	mustBe(' ');
	rock.next = nil;
	searchKeys(1, &rock);
	crnl();
	uid = "";
	if(uids)
		uid = "uid ";
	if(rock.next != nil && rock.next->key == SKCharset){
		if(cistrstr(rock.next->s, "utf-8") != 0
		&& cistrcmp(rock.next->s, "us-ascii") != 0){
			Bprint(&bout, "%s no [BADCHARSET] (\"US-ASCII\" \"UTF-8\") %s%s failed\r\n", tg, uid, cmd);
			checkBox(selected, 0);
			status(uids, uids);
			return;
		}
		rock.next = rock.next->next;
	}
	Bprint(&bout, "* search");
	for(m = selected->msgs; m != nil; m = m->next)
		m->matched = searchMsg(m, rock.next);
	for(m = selected->msgs; m != nil; m = m->next){
		if(m->matched){
			if(uids)
				id = m->uid;
			else
				id = m->seq;
			Bprint(&bout, " %lud", id);
		}
	}
	Bprint(&bout, "\r\n");
	checkBox(selected, 0);
	status(uids, uids);
	Bprint(&bout, "%s OK %s%s completed\r\n", tg, uid, cmd);
}

static void
selectCmd(char *tg, char *cmd)
{
	Msg *m;
	char *s, *mbox;

	mustBe(' ');
	mbox = astring();
	crnl();

	if(selected){
		imapState = SAuthed;
		closeBox(selected, 1);
		selected = nil;
	}

	mbox = mboxName(mbox);
	if(mbox == nil || !okMbox(mbox)){
		Bprint(&bout, "%s no %s bad mailbox\r\n", tg, cmd);
		return;
	}

	selected = openBox(mbox, "imap", cistrcmp(cmd, "select") == 0);
	if(selected == nil){
		Bprint(&bout, "%s no %s can't open mailbox %s: %r\r\n", tg, cmd, mbox);
		return;
	}

	imapState = SSelected;

	Bprint(&bout, "* FLAGS (\\Seen \\Answered \\Flagged \\Deleted \\Draft)\r\n");
	Bprint(&bout, "* %lud exists\r\n", selected->max);
	selected->toldMax = selected->max;
	Bprint(&bout, "* %lud recent\r\n", selected->recent);
	selected->toldRecent = selected->recent;
	for(m = selected->msgs; m != nil; m = m->next){
		if(!m->expunged && (m->flags & MSeen) != MSeen){
			Bprint(&bout, "* OK [UNSEEN %ld]\r\n", m->seq);
			break;
		}
	}
	Bprint(&bout, "* OK [PERMANENTFLAGS (\\Seen \\Answered \\Flagged \\Draft)]\r\n");
	Bprint(&bout, "* OK [UIDNEXT %ld]\r\n", selected->uidnext);
	Bprint(&bout, "* OK [UIDVALIDITY %ld]\r\n", selected->uidvalidity);
	s = "READ-ONLY";
	if(selected->writable)
		s = "READ-WRITE";
	Bprint(&bout, "%s OK [%s] %s %s completed\r\n", tg, s, cmd, mbox);
}

static NamedInt	statusItems[] =
{
	{"MESSAGES",	SMessages},
	{"RECENT",	SRecent},
	{"UIDNEXT",	SUidNext},
	{"UIDVALIDITY",	SUidValidity},
	{"UNSEEN",	SUnseen},
	{nil,		0}
};

static void
statusCmd(char *tg, char *cmd)
{
	Box *box;
	Msg *m;
	char *s, *mbox;
	ulong v;
	int si, i;

	mustBe(' ');
	mbox = astring();
	mustBe(' ');
	mustBe('(');
	si = 0;
	for(;;){
		s = atom();
		i = mapInt(statusItems, s);
		if(i == 0)
			parseErr("illegal status item");
		si |= i;
		if(peekc() == ')')
			break;
		mustBe(' ');
	}
	mustBe(')');
	crnl();

	mbox = mboxName(mbox);
	if(mbox == nil || !okMbox(mbox)){
		check();
		Bprint(&bout, "%s no %s bad mailbox\r\n", tg, cmd);
		return;
	}

	box = openBox(mbox, "status", 1);
	if(box == nil){
		check();
		Bprint(&bout, "%s no [TRYCREATE] %s can't open mailbox %s: %r\r\n", tg, cmd, mbox);
		return;
	}

	Bprint(&bout, "* STATUS (");
	s = "";
	for(i = 0; statusItems[i].name != nil; i++){
		if(si & statusItems[i].v){
			v = 0;
			switch(statusItems[i].v){
			case SMessages:
				v = box->max;
				break;
			case SRecent:
				v = box->recent;
				break;
			case SUidNext:
				v = box->uidnext;
				break;
			case SUidValidity:
				v = box->uidvalidity;
				break;
			case SUnseen:
				v = 0;
				for(m = box->msgs; m != nil; m = m->next)
					if((m->flags & MSeen) != MSeen)
						v++;
				break;
			default:
				Bprint(&bout, ")");
				bye("internal error: status item not implemented");
				break;
			}
			Bprint(&bout, "%s%s %lud", s, statusItems[i].name, v);
			s = " ";
		}
	}
	Bprint(&bout, ")\r\n");
	closeBox(box, 1);

	check();
	Bprint(&bout, "%s OK %s completed\r\n", tg, cmd);
}

static void
storeCmd(char *tg, char *cmd)
{
	storeUCmd(tg, cmd, 0);
}

static void
storeUCmd(char *tg, char *cmd, int uids)
{
	Store *st;
	MsgSet *ms;
	MbLock *ml;
	char *uid;
	ulong max;
	int ok;

	mustBe(' ');
	ms = msgSet();
	mustBe(' ');
	st = storeWhat();
	crnl();
	uid = "";
	if(uids)
		uid = "uid ";
	max = selected->max;
	ml = checkBox(selected, 1);
	ok = ml != nil && forMsgs(selected, ms, max, uids, storeMsg, st);
	closeImp(selected, ml);
	status(uids, uids);
	if(ok)
		Bprint(&bout, "%s OK %s%s completed\r\n", tg, uid, cmd);
	else
		Bprint(&bout, "%s no %s%s failed\r\n", tg, uid, cmd);
}

/*
 * minimal implementation of subscribe
 * all folders are automatically subscribed,
 * and can't be unsubscribed
 */
static void
subscribeCmd(char *tg, char *cmd)
{
	Box *box;
	char *mbox;
	int ok;

	mustBe(' ');
	mbox = astring();
	crnl();
	check();
	mbox = mboxName(mbox);
	ok = 0;
	if(mbox != nil && okMbox(mbox)){
		box = openBox(mbox, "subscribe", 0);
		if(box != nil){
			ok = subscribe(mbox, 's');
			closeBox(box, 1);
		}
	}
	if(!ok)
		Bprint(&bout, "%s no %s bad mailbox\r\n", tg, cmd);
	else
		Bprint(&bout, "%s OK %s completed\r\n", tg, cmd);
}

static void
uidCmd(char *tg, char *cmd)
{
	char *sub;

	mustBe(' ');
	sub = atom();
	if(cistrcmp(sub, "copy") == 0)
		copyUCmd(tg, sub, 1);
	else if(cistrcmp(sub, "fetch") == 0)
		fetchUCmd(tg, sub, 1);
	else if(cistrcmp(sub, "search") == 0)
		searchUCmd(tg, sub, 1);
	else if(cistrcmp(sub, "store") == 0)
		storeUCmd(tg, sub, 1);
	else{
		clearcmd();
		Bprint(&bout, "%s bad %s illegal uid command %s\r\n", tg, cmd, sub);
	}
}

static void
unsubscribeCmd(char *tg, char *cmd)
{
	char *mbox;

	mustBe(' ');
	mbox = astring();
	crnl();
	check();
	mbox = mboxName(mbox);
	if(mbox == nil || !okMbox(mbox) || !subscribe(mbox, 'u'))
		Bprint(&bout, "%s no %s can't unsubscribe\r\n", tg, cmd);
	else
		Bprint(&bout, "%s OK %s completed\r\n", tg, cmd);
}

static void
badsyn(void)
{
	parseErr("bad syntax");
}

static void
clearcmd(void)
{
	int c;

	for(;;){
		c = getc();
		if(c < 0)
			bye("end of input");
		if(c == '\n')
			return;
	}
}

static void
crnl(void)
{
	int c;

	c = getc();
	if(c == '\n')
		return;
	if(c != '\r' || getc() != '\n')
		badsyn();
}

static void
mustBe(int c)
{
	if(getc() != c){
		ungetc();
		badsyn();
	}
}

/*
 * flaglist	: '(' ')' | '(' flags ')'
 */
static int
flagList(void)
{
	int f;

	mustBe('(');
	f = 0;
	if(peekc() != ')')
		f = flags();

	mustBe(')');
	return f;
}

/*
 * flags	: flag | flags ' ' flag
 * flag		: '\' atom | atom
 */
static int
flags(void)
{
	int ff, flags;
	char *s;
	int c;

	flags = 0;
	for(;;){
		c = peekc();
		if(c == '\\'){
			mustBe('\\');
			s = atomString(atomStop, "\\");
		}else if(strchr(atomStop, c) != nil)
			s = atom();
		else
			break;
		ff = mapFlag(s);
		if(ff == 0)
			parseErr("flag not supported");
		flags |= ff;
		if(peekc() != ' ')
			break;
		mustBe(' ');
	}
	if(flags == 0)
		parseErr("no flags given");
	return flags;
}

/*
 * storeWhat	: osign 'FLAGS' ' ' storeflags
 *		| osign 'FLAGS.SILENT' ' ' storeflags
 * osign	:
 *		| '+' | '-'
 * storeflags	: flagList | flags
 */
static Store*
storeWhat(void)
{
	int f;
	char *s;
	int c, w;

	c = peekc();
	if(c == '+' || c == '-')
		mustBe(c);
	else
		c = 0;
	s = atom();
	w = 0;
	if(cistrcmp(s, "flags") == 0)
		w = STFlags;
	else if(cistrcmp(s, "flags.silent") == 0)
		w = STFlagsSilent;
	else
		parseErr("illegal store attribute");
	mustBe(' ');
	if(peekc() == '(')
		f = flagList();
	else
		f = flags();
	return mkStore(c, w, f);
}

/*
 * fetchWhat	: "ALL" | "FULL" | "FAST" | fetchAtt | '(' fetchAtts ')'
 * fetchAtts	: fetchAtt | fetchAtts ' ' fetchAtt
 */
static char *fetchAtom	= "(){}%*\"\\[]";
static Fetch*
fetchWhat(void)
{
	Fetch *f;
	char *s;

	if(peekc() == '('){
		getc();
		f = nil;
		for(;;){
			s = atomString(fetchAtom, "");
			f = fetchAtt(s, f);
			if(peekc() == ')')
				break;
			mustBe(' ');
		}
		getc();
		return revFetch(f);
	}

	s = atomString(fetchAtom, "");
	if(cistrcmp(s, "all") == 0)
		f = mkFetch(FFlags, mkFetch(FInternalDate, mkFetch(FRfc822Size, mkFetch(FEnvelope, nil))));
	else if(cistrcmp(s, "fast") == 0)
		f = mkFetch(FFlags, mkFetch(FInternalDate, mkFetch(FRfc822Size, nil)));
	else if(cistrcmp(s, "full") == 0)
		f = mkFetch(FFlags, mkFetch(FInternalDate, mkFetch(FRfc822Size, mkFetch(FEnvelope, mkFetch(FBody, nil)))));
	else
		f = fetchAtt(s, nil);
	return f;
}

/*
 * fetchAtt	: "ENVELOPE" | "FLAGS" | "INTERNALDATE"
 *		| "RFC822" | "RFC822.HEADER" | "RFC822.SIZE" | "RFC822.TEXT"
 *		| "BODYSTRUCTURE"
 *		| "UID"
 *		| "BODY"
 *		| "BODY" bodysubs
 *		| "BODY.PEEK" bodysubs
 * bodysubs	: sect
 *		| sect '<' number '.' nz-number '>'
 * sect		: '[' sectSpec ']'
 * sectSpec	: sectMsgText
 *		| sectPart
 *		| sectPart '.' sectText
 * sectPart	: nz-number
 *		| sectPart '.' nz-number
 */
static Fetch*
fetchAtt(char *s, Fetch *f)
{
	NList *sect;
	int c;

	if(cistrcmp(s, "envelope") == 0)
		return mkFetch(FEnvelope, f);
	if(cistrcmp(s, "flags") == 0)
		return mkFetch(FFlags, f);
	if(cistrcmp(s, "internaldate") == 0)
		return mkFetch(FInternalDate, f);
	if(cistrcmp(s, "RFC822") == 0)
		return mkFetch(FRfc822, f);
	if(cistrcmp(s, "RFC822.header") == 0)
		return mkFetch(FRfc822Head, f);
	if(cistrcmp(s, "RFC822.size") == 0)
		return mkFetch(FRfc822Size, f);
	if(cistrcmp(s, "RFC822.text") == 0)
		return mkFetch(FRfc822Text, f);
	if(cistrcmp(s, "bodystructure") == 0)
		return mkFetch(FBodyStruct, f);
	if(cistrcmp(s, "uid") == 0)
		return mkFetch(FUid, f);

	if(cistrcmp(s, "body") == 0){
		if(peekc() != '[')
			return mkFetch(FBody, f);
		f = mkFetch(FBodySect, f);
	}else if(cistrcmp(s, "body.peek") == 0)
		f = mkFetch(FBodyPeek, f);
	else
		parseErr("illegal fetch attribute");

	mustBe('[');
	c = peekc();
	if(c >= '1' && c <= '9'){
		sect = mkNList(number(1), nil);
		while(peekc() == '.'){
			getc();
			c = peekc();
			if(c >= '1' && c <= '9'){
				sect = mkNList(number(1), sect);
			}else{
				break;
			}
		}
		f->sect = revNList(sect);
	}
	if(peekc() != ']')
		sectText(f, f->sect != nil);
	mustBe(']');

	if(peekc() != '<')
		return f;

	f->partial = 1;
	mustBe('<');
	f->start = number(0);
	mustBe('.');
	f->size = number(1);
	mustBe('>');
	return f;
}

/*
 * sectText	: sectMsgText | "MIME"
 * sectMsgText	: "HEADER"
 *		| "TEXT"
 *		| "HEADER.FIELDS" ' ' hdrList
 *		| "HEADER.FIELDS.NOT" ' ' hdrList
 * hdrList	: '(' hdrs ')'
 * hdrs:	: astring
 *		| hdrs ' ' astring
 */
static void
sectText(Fetch *f, int mimeOk)
{
	SList *h;
	char *s;

	s = atomString(fetchAtom, "");
	if(cistrcmp(s, "header") == 0){
		f->part = FPHead;
		return;
	}
	if(cistrcmp(s, "text") == 0){
		f->part = FPText;
		return;
	}
	if(mimeOk && cistrcmp(s, "mime") == 0){
		f->part = FPMime;
		return;
	}
	if(cistrcmp(s, "header.fields") == 0)
		f->part = FPHeadFields;
	else if(cistrcmp(s, "header.fields.not") == 0)
		f->part = FPHeadFieldsNot;
	else
		parseErr("illegal fetch section text");
	mustBe(' ');
	mustBe('(');
	h = nil;
	for(;;){
		h = mkSList(astring(), h);
		if(peekc() == ')')
			break;
		mustBe(' ');
	}
	mustBe(')');
	f->hdrs = revSList(h);
}

/*
 * searchWhat	: "CHARSET" ' ' astring searchkeys | searchkeys
 * searchkeys	: searchkey | searchkeys ' ' searchkey
 * searchkey	: "ALL" | "ANSWERED" | "DELETED" | "FLAGGED" | "NEW" | "OLD" | "RECENT"
 *		| "SEEN" | "UNANSWERED" | "UNDELETED" | "UNFLAGGED" | "DRAFT" | "UNDRAFT"
 *		| astrkey ' ' astring
 *		| datekey ' ' date
 *		| "KEYWORD" ' ' flag | "UNKEYWORD" flag
 *		| "LARGER" ' ' number | "SMALLER" ' ' number
 * 		| "HEADER" astring ' ' astring
 *		| set | "UID" ' ' set
 *		| "NOT" ' ' searchkey
 *		| "OR" ' ' searchkey ' ' searchkey
 *		| '(' searchkeys ')'
 * astrkey	: "BCC" | "BODY" | "CC" | "FROM" | "SUBJECT" | "TEXT" | "TO"
 * datekey	: "BEFORE" | "ON" | "SINCE" | "SENTBEFORE" | "SENTON" | "SENTSINCE"
 */
static NamedInt searchMap[] =
{
	{"ALL",		SKAll},
	{"ANSWERED",	SKAnswered},
	{"DELETED",	SKDeleted},
	{"FLAGGED",	SKFlagged},
	{"NEW",		SKNew},
	{"OLD",		SKOld},
	{"RECENT",	SKRecent},
	{"SEEN",	SKSeen},
	{"UNANSWERED",	SKUnanswered},
	{"UNDELETED",	SKUndeleted},
	{"UNFLAGGED",	SKUnflagged},
	{"DRAFT",	SKDraft},
	{"UNDRAFT",	SKUndraft},
	{"UNSEEN",	SKUnseen},
	{nil,		0}
};

static NamedInt searchMapStr[] =
{
	{"CHARSET",	SKCharset},
	{"BCC",		SKBcc},
	{"BODY",	SKBody},
	{"CC",		SKCc},
	{"FROM",	SKFrom},
	{"SUBJECT",	SKSubject},
	{"TEXT",	SKText},
	{"TO",		SKTo},
	{nil,		0}
};

static NamedInt searchMapDate[] =
{
	{"BEFORE",	SKBefore},
	{"ON",		SKOn},
	{"SINCE",	SKSince},
	{"SENTBEFORE",	SKSentBefore},
	{"SENTON",	SKSentOn},
	{"SENTSINCE",	SKSentSince},
	{nil,		0}
};

static NamedInt searchMapFlag[] =
{
	{"KEYWORD",	SKKeyword},
	{"UNKEYWORD",	SKUnkeyword},
	{nil,		0}
};

static NamedInt searchMapNum[] =
{
	{"SMALLER",	SKSmaller},
	{"LARGER",	SKLarger},
	{nil,		0}
};

static Search*
searchKeys(int first, Search *tail)
{
	Search *s;

	for(;;){
		if(peekc() == '('){
			getc();
			tail = searchKeys(0, tail);
			mustBe(')');
		}else{
			s = searchKey(first);
			tail->next = s;
			tail = s;
		}
		first = 0;
		if(peekc() != ' ')
			break;
		getc();
	}
	return tail;
}

static Search*
searchKey(int first)
{
	Search *sr, rock;
	Tm tm;
	char *a;
	int i, c;

	sr = binalloc(&parseBin, sizeof(Search), 1);
	if(sr == nil)
		parseErr("out of memory");

	c = peekc();
	if(c >= '0' && c <= '9'){
		sr->key = SKSet;
		sr->set = msgSet();
		return sr;
	}

	a = atom();
	if(i = mapInt(searchMap, a))
		sr->key = i;
	else if(i = mapInt(searchMapStr, a)){
		if(!first && i == SKCharset)
			parseErr("illegal search key");
		sr->key = i;
		mustBe(' ');
		sr->s = astring();
	}else if(i = mapInt(searchMapDate, a)){
		sr->key = i;
		mustBe(' ');
		c = peekc();
		if(c == '"')
			getc();
		a = atom();
		if(!imap4Date(&tm, a))
			parseErr("bad date format");
		sr->year = tm.year;
		sr->mon = tm.mon;
		sr->mday = tm.mday;
		if(c == '"')
			mustBe('"');
	}else if(i = mapInt(searchMapFlag, a)){
		sr->key = i;
		mustBe(' ');
		c = peekc();
		if(c == '\\'){
			mustBe('\\');
			a = atomString(atomStop, "\\");
		}else
			a = atom();
		i = mapFlag(a);
		if(i == 0)
			parseErr("flag not supported");
		sr->num = i;
	}else if(i = mapInt(searchMapNum, a)){
		sr->key = i;
		mustBe(' ');
		sr->num = number(0);
	}else if(cistrcmp(a, "HEADER") == 0){
		sr->key = SKHeader;
		mustBe(' ');
		sr->hdr = astring();
		mustBe(' ');
		sr->s = astring();
	}else if(cistrcmp(a, "UID") == 0){
		sr->key = SKUid;
		mustBe(' ');
		sr->set = msgSet();
	}else if(cistrcmp(a, "NOT") == 0){
		sr->key = SKNot;
		mustBe(' ');
		rock.next = nil;
		searchKeys(0, &rock);
		sr->left = rock.next;
	}else if(cistrcmp(a, "OR") == 0){
		sr->key = SKOr;
		mustBe(' ');
		rock.next = nil;
		searchKeys(0, &rock);
		sr->left = rock.next;
		mustBe(' ');
		rock.next = nil;
		searchKeys(0, &rock);
		sr->right = rock.next;
	}else
		parseErr("illegal search key");
	return sr;
}

/*
 * set	: seqno
 *	| seqno ':' seqno
 *	| set ',' set
 * seqno: nz-number
 *	| '*'
 *
 */
static MsgSet*
msgSet(void)
{
	MsgSet head, *last, *ms;
	ulong from, to;

	last = &head;
	head.next = nil;
	for(;;){
		from = seqNo();
		to = from;
		if(peekc() == ':'){
			getc();
			to = seqNo();
		}
		ms = binalloc(&parseBin, sizeof(MsgSet), 0);
		if(ms == nil)
			parseErr("out of memory");
		ms->from = from;
		ms->to = to;
		ms->next = nil;
		last->next = ms;
		last = ms;
		if(peekc() != ',')
			break;
		getc();
	}
	return head.next;
}

static ulong
seqNo(void)
{
	if(peekc() == '*'){
		getc();
		return ~0UL;
	}
	return number(1);
}

/*
 * 7 bit, non-ctl chars, no (){%*"\
 * NIL is special case for nstring or parenlist
 */
static char *
atom(void)
{
	return atomString(atomStop, "");
}

/*
 * like an atom, but no +
 */
static char *
tag(void)
{
	return atomString("+(){%*\"\\", "");
}

/*
 * string or atom allowing %*
 */
static char *
listmbox(void)
{
	int c;

	c = peekc();
	if(c == '{')
		return literal();
	if(c == '"')
		return quoted();
	return atomString("(){\"\\", "");
}

/*
 * string or atom
 */
static char *
astring(void)
{
	int c;

	c = peekc();
	if(c == '{')
		return literal();
	if(c == '"')
		return quoted();
	return atom();
}

/*
 * 7 bit, non-ctl chars, none from exception list
 */
static char *
atomString(char *disallowed, char *initial)
{
	char *s;
	int c, ns, as;

	ns = strlen(initial);
	s = binalloc(&parseBin, ns + StrAlloc, 0);
	if(s == nil)
		parseErr("out of memory");
	strcpy(s, initial);
	as = ns + StrAlloc;
	for(;;){
		c = getc();
		if(c <= ' ' || c >= 0x7f || strchr(disallowed, c) != nil){
			ungetc();
			break;
		}
		s[ns++] = c;
		if(ns >= as){
			s = bingrow(&parseBin, s, as, as + StrAlloc, 0);
			if(s == nil)
				parseErr("out of memory");
			as += StrAlloc;
		}
	}
	if(ns == 0)
		badsyn();
	s[ns] = '\0';
	return s;
}

/*
 * quoted: '"' chars* '"'
 * chars:	1-128 except \r and \n
 */
static char *
quoted(void)
{
	char *s;
	int c, ns, as;

	mustBe('"');
	s = binalloc(&parseBin, StrAlloc, 0);
	if(s == nil)
		parseErr("out of memory");
	as = StrAlloc;
	ns = 0;
	for(;;){
		c = getc();
		if(c == '"')
			break;
		if(c < 1 || c > 0x7f || c == '\r' || c == '\n')
			badsyn();
		if(c == '\\'){
			c = getc();
			if(c != '\\' && c != '"')
				badsyn();
		}
		s[ns++] = c;
		if(ns >= as){
			s = bingrow(&parseBin, s, as, as + StrAlloc, 0);
			if(s == nil)
				parseErr("out of memory");
			as += StrAlloc;
		}
	}
	s[ns] = '\0';
	return s;
}

/*
 * litlen: {number}\r\n
 */
static ulong
litlen(void)
{
	ulong v;

	mustBe('{');
	v = number(0);
	mustBe('}');
	crnl();
	return v;
}

/*
 * literal: litlen data<0:litlen>
 */
static char *
literal(void)
{
	char *s;
	ulong v;

	v = litlen();
	s = binalloc(&parseBin, v+1, 0);
	if(s == nil)
		parseErr("out of memory");
	Bprint(&bout, "+ Ready for literal data\r\n");
	if(Bflush(&bout) < 0)
		writeErr();
	if(v != 0 && Bread(&bin, s, v) != v)
		badsyn();
	s[v] = '\0';
	return s;
}

/*
 * digits; number is 32 bits
 */
static ulong
number(int nonzero)
{
	ulong v;
	int c, first;

	v = 0;
	first = 1;
	for(;;){
		c = getc();
		if(c < '0' || c > '9'){
			ungetc();
			if(first)
				badsyn();
			break;
		}
		if(nonzero && first && c == '0')
			badsyn();
		c -= '0';
		first = 0;
		if(v > UlongMax/10 || v == UlongMax/10 && c > UlongMax%10)
			parseErr("number out of range\r\n");
		v = v * 10 + c;
	}
	return v;
}

static int
getc(void)
{
	return Bgetc(&bin);
}

static void
ungetc(void)
{
	Bungetc(&bin);
}

static int
peekc(void)
{
	int c;

	c = Bgetc(&bin);
	Bungetc(&bin);
	return c;
}

[-- Attachment #2.1.3: .signature --]
[-- Type: text/plain, Size: 96 bytes --]


--
                 Saludos,
                         Gorka

"Curiosity sKilled the cat"

^ permalink raw reply	[flat|nested] 6+ messages in thread

* Re: [9fans] imap4d bug
@ 2002-11-15 18:03 presotto
  2002-11-17 14:22 ` paurea
  0 siblings, 1 reply; 6+ messages in thread
From: presotto @ 2002-11-15 18:03 UTC (permalink / raw)
  To: 9fans

Changing the case of the arguments to cistrcmp (the case insensitive
compare) is a noop.


^ permalink raw reply	[flat|nested] 6+ messages in thread

end of thread, other threads:[~2002-11-17 14:22 UTC | newest]

Thread overview: 6+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2002-11-15 15:41 [9fans] imap4d bug paurea
2002-11-15 16:06 ` paurea
2002-11-15 16:33   ` paurea
2002-11-15 18:03 presotto
2002-11-17 14:22 ` paurea
2002-11-15 21:04 presotto

This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox;
as well as URLs for NNTP newsgroup(s).