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

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


^ permalink raw reply	[flat|nested] 6+ messages in thread
* Re: [9fans] imap4d bug
@ 2002-11-15 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
* [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

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 18:03 [9fans] imap4d bug presotto
2002-11-17 14:22 ` paurea
  -- strict thread matches above, loose matches on Subject: below --
2002-11-15 21:04 presotto
2002-11-15 15:41 paurea
2002-11-15 16:06 ` paurea
2002-11-15 16:33   ` paurea

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