shithub: fork

Download patch

ref: 937634c293c7101505746f537bd165b6a57bebdd
parent: 738be8c36f9f2765894a526f69ec043e483002b3
author: qwx <[email protected]>
date: Sat Dec 28 23:00:45 EST 2024

remove upas: revert nocert patch

--- a/sys/src/cmd/upas/fs/dat.h
+++ /dev/null
@@ -1,360 +1,0 @@
-#include <avl.h>
-
-enum {
-	/* cache states */
-	Cidx		= 1<<0,
-	Cidxstale	= 1<<1,
-	Cheader 	= 1<<2,
-	Cbody		= 1<<3,
-	Cnew		= 1<<4,
-	Cmod		= 1<<5,
-
-	/* encodings */
-	Enone=	0,
-	Ebase64,
-	Equoted,
-
-	/* dispositions */
-	Dnone=	0,
-	Dinline,
-	Dfile,
-	Dignore,
-
-	/* mb create flags */
-	DMcreate	=  0x02000000,
-
-	/* rm flags */
-	Rrecur		= 1<<0,
-	Rtrunc		= 1<<1,
-
-	/* m->deleted flags */
-	Deleted		= 1<<0,
-	Dup		= 1<<1,
-	Dead		= 1<<2,
-	Disappear	= 1<<3,
-	Dmark		= 1<<4,	/* temporary mark for idx scan */
-
-	Maxmsg		= 75*1024*1024,	/* maxmessage size; debugging */
-	Maxcache	= 512*1024,	/* target cache size; set low for debugging */
-	Nctab		= 15,		/* max # of cached messages >10 */
-	Nref		= 10,
-};
-
-typedef struct {
-	Avl;
-	uchar	*digest;
-} Mtree;
-
-typedef struct Idx Idx;
-struct Idx {
-	Mtree;
-
-	char	*str;			/* as read from idx file */
-	uchar	flags;
-	uvlong	fileid;
-	ulong	lines;
-	ulong	size;
-	ulong	rawbsize;			/* nasty imap4d */
-	ulong	ibadchars;
-
-	char	*ffrom;
-	char	*from;
-	char	*to;
-	char	*cc;
-	char	*bcc;
-	char	*replyto;
-	char	*messageid;
-	char	*subject;
-	char	*sender;
-	char	*inreplyto;
-	char	*date822;
-	char	*idxaux;		/* mailbox specific */
-
-	char	*type;			/* mime info */
-	char	*filename;
-	char	disposition;
-
-	int	nparts;
-};
-
-typedef struct Message Message;
-struct Message {
-	ulong	id;
-	int	refs;
-	int	subname;
-	char	name[12];
-
-	/* top-level indexed information */
-	Idx;
-
-	/* caching help */
-	uchar	cstate;
-	ulong	infolen;
-	ulong	csize;
-
-	/*
-	 * a plethoria of pointers into message
-	 * and some status.  not valid unless cached
-	 */
-	char	*start;		/* start of message */
-	char	*end;		/* end of message */
-	char	*header;		/* start of header */
-	char	*hend;		/* end of header */
-	int	hlen;		/* length of header minus ignored fields */
-	char	*mheader;	/* start of mime header */
-	char	*mhend;		/* end of mime header */
-	char	*body;		/* start of body */
-	char	*bend;		/* end of body */
-	char	*rbody;		/* raw (unprocessed) body */
-	char	*rbend;		/* end of raw (unprocessed) body */
-	char	mallocd;		/* message is malloc'd */
-	char	ballocd;		/* body is malloc'd */
-	char	hallocd;		/* header is malloc'd */
-	int	badchars;	/* running count of bad chars. */
-
-	char	deleted;
-	char	inmbox;
-
-	/* mail info */
-	char	*unixheader;
-	char	*unixfrom;
-	char	*unixdate;
-	char	*references[Nref]; /* nil terminated unless full */
-
-	/* mime info */
-	char	*charset;		
-	char	*boundary;
-	char	converted;
-	char	encoding;
-	char	decoded;
-
-	Message	*next;
-	Message	*part;
-	Message	*whole;
-	Message	*lru;		/* least recently use chain */
-
-	union{
-		char	*lim;	/* used by plan9; not compatable with cache */
-		vlong	imapuid;	/* used by imap4 */
-		void	*aux;
-	};
-};
-
-typedef struct Mcache Mcache;
-struct Mcache {
-	uvlong	cached;
-	int	nlru;
-	Message	*lru;
-};
-
-typedef struct Mailbox Mailbox;
-struct Mailbox {
-	int	refs;
-	Mailbox	*next;
-	ulong	id;
-	int	flags;
-	char	rmflags;
-	char	dolock;		/* lock when syncing? */
-	char	addfrom;
-	char	name[Elemlen];
-	char	path[Pathlen];
-	Dir	*d;
-	Message	*root;
-	Avltree	*mtree;
-	ulong	vers;		/* goes up each time mailbox is changed */
-
-	/* cache tracking */
-	Mcache;
-
-	/* index tracking */
-	Qid	qid;
-
-	ulong	waketime;
-	void	(*close)(Mailbox*);
-	void	(*decache)(Mailbox*, Message*);
-	char	*(*move)(Mailbox*, Message*, char*);
-	int	(*fetch)(Mailbox*, Message*, uvlong, ulong);
-	void	(*delete)(Mailbox*, Message*);
-	char	*(*ctl)(Mailbox*, int, char**);
-	char	*(*remove)(Mailbox *, int);
-	char	*(*rename)(Mailbox*, char*, int);
-	char	*(*sync)(Mailbox*);
-	void	(*modflags)(Mailbox*, Message*, int);
-	void	(*idxwrite)(Biobuf*, Mailbox*);
-	int	(*idxread)(char*, Mailbox*);
-	void	(*idxinvalid)(Mailbox*);
-	void	*aux;		/* private to Mailbox implementation */
-
-	int	syncing;	/* currently syncing? */
-};
-
-typedef char *Mailboxinit(Mailbox*, char*);
-
-Mailboxinit	plan9mbox;
-Mailboxinit	pop3mbox;
-Mailboxinit	imap4mbox;
-Mailboxinit	mdirmbox;
-
-void		genericidxwrite(Biobuf*, Mailbox*);
-int		genericidxread(char*, Mailbox*);
-void		genericidxinvalid(Mailbox*);
-
-void		cachehash(Mailbox*, Message*);
-int		cacheheaders(Mailbox*, Message*);		/* "getcache" */
-int		cachebody(Mailbox*, Message*);
-int		cacheidx(Mailbox*, Message*);
-int		ensurecache(Mailbox*, Message*);
-
-/**/
-void		putcache(Mailbox*, Message*);		/* asymmetricial */
-void		cachefree(Mailbox*, Message*);
-
-char*		syncmbox(Mailbox*, int);
-void*		emalloc(ulong);
-void*		erealloc(void*, ulong);
-Message*	newmessage(Message*);
-void		unnewmessage(Mailbox*, Message*, Message*);
-char*		delmessages(int, char**);
-char		*flagmessages(int, char**);
-char*		movemessages(int, char**);
-void		digestmessage(Mailbox*, Message*);
-
-int		wraptls(int, char*);
-
-void		eprint(char*, ...);
-void		iprint(char *, ...);
-char*		newmbox(char*, char*, int, Mailbox**);
-void		freembox(char*);
-char*		removembox(char*, int);
-void		syncallmboxes(void);
-void		logmsg(Message*, char*, ...);
-void		msgincref(Mailbox*, Message*);
-void		msgdecref(Mailbox*, Message*);
-void		mboxincref(Mailbox*);
-void		mboxdecref(Mailbox*);
-char		*mboxrename(char*, char*, int);
-void		convert(Message*);
-void		decode(Message*);
-int		decquoted(char*, char*, char*, int);
-int		xtoutf(char*, char**, char*, char*);
-ulong		countlines(Message*);
-void		parse(Mailbox*, Message*, int, int);
-void		parseheaders(Mailbox*, Message*, int, int);
-void		parsebody(Message*, Mailbox*);
-int		strtotm(char*, Tm*);
-char*		lowercase(char*);
-
-char*		sputc(char*, char*, int);
-char*		seappend(char*, char*, char*, int);
-
-int		hdrlen(char*, char*);
-char*		rfc2047(char*, char*, char*, int, int);
-
-char*		localremove(Mailbox*, int);
-char*		localrename(Mailbox*, char*, int);
-void		rmidx(char*, int);
-int		vremove(char*);
-int		rename(char *, char*, int);
-
-void		mtreeinit(Mailbox *);
-void		mtreefree(Mailbox *);
-Message*	mtreefind(Mailbox*, uchar*);
-Message*	mtreeadd(Mailbox*, Message*);
-void		mtreedelete(Mailbox*, Message*);
-
-enum {
-	/* mail sub-objects; must be sorted */
-	Qbcc,
-	Qbody,
-	Qcc,
-	Qdate,
-	Qdigest,
-	Qdisposition,
-	Qffrom,
-	Qfileid,
-	Qfilename,
-	Qflags,
-	Qfrom,
-	Qheader,
-	Qinfo,
-	Qinreplyto,
-	Qlines,
-	Qmessageid,
-	Qmimeheader,
-	Qraw,
-	Qrawbody,
-	Qrawheader,
-	Qrawunix,
-	Qreferences,
-	Qreplyto,
-	Qsender,
-	Qsize,
-	Qsubject,
-	Qto,
-	Qtype,
-	Qunixdate,
-	Qunixheader,
-	Qmax,
-
-	/* other files */
-	Qtop,
-	Qmbox,
-	Qdir,
-	Qctl,
-	Qmboxctl,
-};
-
-#define PATH(id, f)	(((uvlong)(id)<<10) | (f))
-#define FILE(p)		((int) (p) & 0x3ff)
-
-/* hash table to aid in name lookup, all files have an entry */
-typedef struct Hash Hash;
-struct Hash {
-	Hash	*next;
-	char	*name;
-	uvlong	ppath;
-	Qid	qid;
-	Mailbox	*mb;
-	Message	*m;
-};
-
-ulong	hash(char*);
-Hash	*hlook(uvlong, char*);
-void	henter(uvlong, char*, Qid, Message*, Mailbox*);
-void	hfree(uvlong, char*);
-
-char	*intern(char*);
-void	idxfree(Idx*);
-int	rdidxfile(Mailbox*);
-int	wridxfile(Mailbox*);
-
-char	*modflags(Mailbox*, Message*, char*);
-int	getmtokens(char *, char**, int, int);
-
-extern char	Enotme[];
-extern char	*mntpt;
-extern char	user[Elemlen];
-extern char 	*dirtab[];
-extern int	Sflag;
-extern int	iflag;
-extern int	biffing;
-extern ulong	cachetarg;
-extern int	debug;
-extern int	lflag;
-extern int	plumbing;
-extern ulong	msgallocd;
-extern ulong	msgfreed;
-extern int	nocertcheck;
-extern Mailbox	*mbl;
-extern Message	*root;
-extern char	*logf;
-
-#define	dprint(...)	if(debug) fprint(2, __VA_ARGS__); else {}
-#define	Topmsg(mb, m)	(m->whole == mb->root)
-#pragma	varargck	type	"A"	uchar*
-#pragma	varargck	type	"D"	uvlong
-#pragma	varargck	type	"Δ"	uvlong
-#pragma	varargck	argpos	eprint	1
-#pragma	varargck	argpos	iprint	1
-#pragma	varargck	argpos	logmsg	2
-
--- a/sys/src/cmd/upas/fs/fs.c
+++ /dev/null
@@ -1,1692 +1,0 @@
-#include "common.h"
-#include <fcall.h>
-#include <libsec.h>
-#include <pool.h>
-#include "dat.h"
-
-typedef struct Fid Fid;
-struct Fid
-{
-	Qid	qid;
-	short	busy;
-	short	open;
-	int	fid;
-	Fid	*next;
-	Mailbox	*mb;
-	Message	*m;
-
-	long	foff;		/* offset/DIRLEN of finger */
-	Message	*fptr;		/* pointer to message at off */
-	int	fvers;		/* mailbox version when finger was saved */
-};
-
-Fid		*newfid(int);
-void		error(char*);
-void		io(void);
-void		*erealloc(void*, ulong);
-void		*emalloc(ulong);
-void		usage(void);
-void		reader(void);
-int		readheader(Message*, char*, int, int);
-void		post(char*, char*, int);
-
-char	*rflush(Fid*), *rauth(Fid*),
-	*rattach(Fid*), *rwalk(Fid*),
-	*ropen(Fid*), *rcreate(Fid*),
-	*rread(Fid*), *rwrite(Fid*), *rclunk(Fid*),
-	*rremove(Fid*), *rstat(Fid*), *rwstat(Fid*),
-	*rversion(Fid*);
-
-char 	*(*fcalls[])(Fid*) = {
-	[Tflush]	rflush,
-	[Tversion]	rversion,
-	[Tauth]	rauth,
-	[Tattach]	rattach,
-	[Twalk]		rwalk,
-	[Topen]		ropen,
-	[Tcreate]	rcreate,
-	[Tread]		rread,
-	[Twrite]	rwrite,
-	[Tclunk]	rclunk,
-	[Tremove]	rremove,
-	[Tstat]		rstat,
-	[Twstat]	rwstat,
-};
-
-char	Eperm[] =	"permission denied";
-char	Enotdir[] =	"not a directory";
-char	Enoauth[] =	"upas/fs: authentication not required";
-char	Enotexist[] =	"file does not exist";
-char	Einuse[] =	"file in use";
-char	Eexist[] =	"file exists";
-char	Enotowner[] =	"not owner";
-char	Eisopen[] = 	"file already open for I/O";
-char	Excl[] = 	"exclusive use file already open";
-char	Ename[] = 	"illegal name";
-char	Ebadctl[] =	"unknown control message";
-char	Ebadargs[] =	"invalid arguments";
-char 	Enotme[] =	"path not served by this file server";
-char	Eio[] =		"I/O error";
-
-char *dirtab[] = {
-[Qdir]		".",
-[Qbcc]		"bcc",
-[Qbody]		"body",
-[Qcc]		"cc",
-[Qdate]		"date",
-[Qdigest]	"digest",
-[Qdisposition]	"disposition",
-[Qffrom]		"ffrom",
-[Qfileid]		"fileid",
-[Qfilename]	"filename",
-[Qflags]		"flags",
-[Qfrom]		"from",
-[Qheader]	"header",
-[Qinfo]		"info",
-[Qinreplyto]	"inreplyto",
-[Qlines]		"lines",
-[Qmessageid]	"messageid",
-[Qmimeheader]	"mimeheader",
-[Qraw]		"raw",
-[Qrawbody]	"rawbody",
-[Qrawheader]	"rawheader",
-[Qrawunix]	"rawunix",
-[Qreferences]	"references",
-[Qreplyto]	"replyto",
-[Qsender]	"sender",
-[Qsize]		"size",
-[Qsubject]	"subject",
-[Qto]		"to",
-[Qtype]		"type",
-[Qunixdate]	"unixdate",
-[Qunixheader]	"unixheader",
-[Qctl]		"ctl",
-[Qmboxctl]	"ctl",
-};
-
-char	*mntpt;
-char	user[Elemlen];
-int	Dflag;
-int	Sflag;
-int	iflag;
-int	lflag;
-int	biffing;
-int	debug;
-int	plumbing = 1;
-ulong	cachetarg = Maxcache;
-int	nocertcheck; /* ignore unrecognized certs. Still logged */
-Mailbox	*mbl;
-
-static	int	messagesize = IOUNIT + IOHDRSZ;
-static	int	mfd[2];
-static	char	hbuf[32*1024];
-static	uchar	mbuf[IOUNIT];
-static	uchar	mdata[IOUNIT + IOHDRSZ];
-static	ulong	path;		/* incremented for each new file */
-static	Hash	*htab[2053];
-static	Fcall	rhdr;
-static	Fcall	thdr;
-static	Fid	*fids;
-static QLock	synclock;
-
-void
-sanemsg(Message *m)
-{
-	if(m->end < m->start)
-		abort();
-	if(m->ballocd && (m->start <= m->body && m->end >= m->body))
-		abort();
-	if(m->end - m->start > Maxmsg)
-		abort();
-	if(m->size > Maxmsg)
-		abort();
-	if(m->fileid != 0 && m->fileid <= 1000000ull<<8)
-		abort();
-}
-
-void
-sanembmsg(Mailbox *mb, Message *m)
-{
-	sanemsg(m);
-	if(Topmsg(mb, m)){
-		if(m->start > end && m->size == 0)
-			abort();
-		if(m->fileid <= 1000000ull<<8)
-			abort();
-	}
-}
-
-static int
-Afmt(Fmt *f)
-{
-	char buf[SHA1dlen*2 + 1];
-	uchar *u, i;
-
-	u = va_arg(f->args, uchar*);
-	if(u == 0 && f->flags & FmtSharp)
-		return fmtstrcpy(f, "-");
-	if(u == 0)
-		return fmtstrcpy(f, "<nildigest>");
-	for(i = 0; i < SHA1dlen; i++)
-		sprint(buf + 2*i, "%2.2ux", u[i]);
-	return fmtstrcpy(f, buf);
-}
-
-static int
-Δfmt(Fmt *f)
-{
-	uvlong v;
-	Tm tm;
-
-	v = va_arg(f->args, uvlong);
-	if(f->flags & FmtSharp)
-		if((v>>8) == 0)
-			return fmtstrcpy(f, "");
-	tmtime(&tm, v>>8, tzload("local"));
-	return fmtprint(f, "%τ", tmfmt(&tm, "WW MMM _D hh:mm:ss Z YYYY"));
-}
-
-static int
-Dfmt(Fmt *f)
-{
-	char buf[32];
-	int seq;
-	uvlong v;
-
-	v = va_arg(f->args, uvlong);
-	seq = v & 0xff;
-	if(seq > 99)
-		seq = 99;
-	snprint(buf, sizeof buf, "%llud.%.2d", v>>8, seq);
-	return fmtstrcpy(f, buf);
-}
-
-void
-usage(void)
-{
-	fprint(2, "usage: upas/fs [-CDSbdlmnps] [-c cachetarg] [-f mboxfile] [-m mountpoint]\n");
-	exits("usage");
-}
-
-void
-notifyf(void *, char *s)
-{
-	if(strncmp(s, "interrupt", 9) == 0)
-		noted(NCONT);
-	if(strncmp(s, "die: yankee pig dog", 19) != 0)
-		/* don't want to call syslog from notify handler */
-		fprint(2, "upas/fs: user: %s; note: %s\n", getuser(), s);
-	noted(NDFLT);
-}
-
-void
-setname(char **v)
-{
-	char buf[128], *p, *e;
-	int fd, i;
-
-	snprint(buf, sizeof buf, "/proc/%d/args", getpid());
-	if((fd = open(buf, OWRITE)) < 0)
-		return;
-	e = buf + sizeof buf;
-	p = seprint(buf, e, "%s", v[0]);
-	for(i = 0; v[++i]; )
-		p = seprint(p, e, " %s", v[i]);
-	write(fd, buf, p - buf);
-	close(fd);
-}
-
-ulong
-ntoi(char *s)
-{
-	ulong n;
-
-	n = strtoul(s, &s, 0);
-	for(;;)
-	switch(*s++){
-	default:
-		usage();
-	case 'g':
-		n *= 1024;
-	case 'm':
-		n *= 1024;
-	case 'k':
-		n *= 1024;
-		break;
-	case 0:
-		return n;
-	}
-}
-
-void
-main(int argc, char *argv[])
-{
-	char maildir[Pathlen], mbox[Pathlen], srvfile[64], **v;
-	char *mboxfile, *err;
-	int p[2], nodflt, srvpost;
-
-	rfork(RFNOTEG);
-	mboxfile = nil;
-	nodflt = 0;
-	srvpost = 0;
-	v = argv;
-
-	ARGBEGIN{
-	case 'C':
-		nocertcheck = 1;
-		break;
-	case 'D':
-		Dflag = 1;
-		break;
-	case 'S':
-		Sflag = 1;
-		break;
-	case 'b':
-		biffing = 1;
-		break;
-	case 'c':
-		cachetarg = ntoi(EARGF(usage()));
-		break;
-	case 'd':
-		if(++debug > 1)
-			mainmem->flags |= POOL_PARANOIA;
-		break;
-	case 'f':
-		mboxfile = EARGF(usage());
-		break;
-	case 'i':
-		iflag++;
-		break;
-	case 'l':
-		lflag = 1;
-		break;
-	case 'm':
-		mntpt = EARGF(usage());
-		break;
-	case 'n':
-		nodflt = 1;
-		break;
-	case 'p':
-		plumbing = 0;
-		break;
-	case 's':
-		srvpost = 1;
-		break;
-	default:
-		usage();
-	}ARGEND
-
-	if(argc)
-		usage();
-	fmtinstall('A', Afmt);
-	fmtinstall('D', Dfmt);
-	fmtinstall(L'Δ', Δfmt);
-	fmtinstall('F', fcallfmt);
-	fmtinstall('H', encodefmt);		/* forces tls stuff */
-	tmfmtinstall();
-	quotefmtinstall();
-	if(pipe(p) < 0)
-		error("pipe failed");
-	mfd[0] = p[0];
-	mfd[1] = p[0];
-
-	notify(notifyf);
-	strcpy(user, getuser());
-	if(mntpt == nil){
-		snprint(maildir, sizeof(maildir), "/mail/fs");
-		mntpt = maildir;
-	}
-	if(mboxfile == nil && !nodflt){
-		snprint(mbox, sizeof mbox, "/mail/box/%s/mbox", user);
-		mboxfile = mbox;
-	}
-
-	if(mboxfile != nil)
-		if(err = newmbox(mboxfile, "mbox", 0, nil))
-			sysfatal("opening %s: %s", mboxfile, err);
-
-	switch(rfork(RFFDG|RFPROC|RFNAMEG|RFNOTEG|RFREND)){
-	case -1:
-		error("fork");
-	case 0:
-		henter(PATH(0, Qtop), dirtab[Qctl],
-			(Qid){PATH(0, Qctl), 0, QTFILE}, nil, nil);
-		close(p[1]);
-		setname(v);
-		io();
-		syncallmboxes();
-		syskillpg(getpid());
-		break;
-	default:
-		close(p[0]);	/* don't deadlock if child fails */
-		if(srvpost){
-			snprint(srvfile, sizeof srvfile, "/srv/upasfs.%s", user);
-			post(srvfile, "upasfs", p[1]);
-		}else
-			if(mount(p[1], -1, mntpt, MREPL, "") == -1)
-				error("mount failed");
-	}
-	exits("");
-}
-
-char*
-sputc(char *p, char *e, int c)
-{
-	if(p < e - 1)
-		*p++ = c;
-	return p;
-}
-
-char*
-seappend(char *s, char *e, char *a, int n)
-{
-	int l;
-
-	l = e - s - 1;
-	if(l < n)
-		n = l;
-	memcpy(s, a, n);
-	s += n;
-	*s = 0;
-	return s;
-}
-
-static int
-fileinfo(Mailbox *mb, Message *m, int t, char **pp)
-{
-	char *s, *e, *p;
-	int len, i;
-	static char buf[64 + 512];
-
-	if(cacheidx(mb, m) < 0)
-		return -1;
-	sanembmsg(mb, m);
-	p = nil;
-	len = -1;
-	switch(t){
-	case Qbody:
-		if(cachebody(mb, m) < 0)
-			return -1;
-		p = m->body;
-		len = m->bend - p;
-		break;
-	case Qbcc:
-		p = m->bcc;
-		break;
-	case Qcc:
-		p = m->cc;
-		break;
-	case Qdisposition:
-		switch(m->disposition){
-		case Dinline:
-			p = "inline";
-			break;
-		case Dfile:
-			p = "file";
-			break;
-		}
-		break;
-	case Qdate:
-		if((p = m->date822) != nil)
-			break;
-		/* wet floor */
-	case Qunixdate:
-		p = buf;
-		len = snprint(buf, sizeof buf, "%#Δ", m->fileid);
-		break;
-	case Qfilename:
-		p = m->filename;
-		break;
-	case Qflags:
-		p = flagbuf(buf, m->flags);
-		break;
-	case Qinreplyto:
-		p = m->inreplyto;
-		break;
-	case Qmessageid:
-		p = m->messageid;
-		break;
-	case Qfrom:
-		p = m->from;
-		break;
-	case Qffrom:
-		p = m->ffrom;
-		break;
-	case Qlines:
-		len = snprint(buf, sizeof buf, "%lud", m->lines);
-		p = buf;
-		break;
-	case Qraw:
-		if(cachebody(mb, m) < 0)
-			return -1;
-		p = m->start;
-		if(p != nil)
-		if(strncmp(p, "From ", 5) == 0)
-		if(e = strchr(p, '\n'))
-			p = e + 1;
-		len = m->rbend - p;
-		break;
-	case Qrawunix:
-		if(cachebody(mb, m) < 0)
-			return -1;
-		p = m->start;
-		len = m->end - p;
-		break;
-	case Qrawbody:
-		if(cachebody(mb, m) < 0)
-			return -1;
-		p = m->rbody;
-		len = m->rbend - p;
-		break;
-	case Qrawheader:
-		if(cacheheaders(mb, m) < 0)
-			return -1;
-		p = m->header;
-		len = m->hend - p;
-		break;
-	case Qmimeheader:
-		if(cacheheaders(mb, m) < 0)
-			return -1;
-		p = m->mheader;
-		len = m->mhend - p;
-		break;
-	case Qreferences:
-		if(cacheheaders(mb, m) < 0)
-			return -1;
-		e = buf + sizeof buf;
-		s = buf;
-		for(i = 0; i < nelem(m->references); i++){
-			if(m->references[i] == nil)
-				break;
-			s = seprint(s, e, "%s\n", m->references[i]);
-		}
-		*s = 0;
-		p = buf;
-		len = s - buf;
-		break;
-	case Qreplyto:
-		p = m->replyto;
-		break;
-	case Qsender:
-		p = m->sender;
-		break;
-	case Qsubject:
-		p = m->subject;
-		break;
-	case Qsize:
-		len = snprint(buf, sizeof buf, "%lud", m->size);
-		p = buf;
-		break;
-	case Qto:
-		p = m->to;
-		break;
-	case Qtype:
-		p = m->type;
-		break;
-	case Qfileid:
-		p = buf;
-		len = snprint(buf, sizeof buf, "%D", m->fileid);
-		break;
-	case Qunixheader:
-		if(cacheheaders(mb, m) < 0)
-			return -1;
-		p = m->unixheader;
-		break;
-	case Qdigest:
-		p = buf;
-		len = snprint(buf, sizeof buf, "%A", m->digest);
-		break;
-	}
-	if(p == nil)
-		p = "";
-	if(len == -1)
-		len = strlen(p);
-	*pp = p;
-	putcache(mb, m);
-	return len;
-}
-
-int infofields[] = {
-	Qfrom,
-	Qto,
-	Qcc,
-	Qreplyto,
-	Qunixdate,
-	Qsubject,
-	Qtype,
-	Qdisposition,
-	Qfilename,
-	Qdigest,
-	Qbcc,
-	Qinreplyto,
-	Qdate,
-	Qsender,
-	Qmessageid,
-	Qlines,
-	Qsize,
-	Qflags,
-	Qfileid,
-	Qffrom,
-};
-
-int
-readinfo(Mailbox *mb, Message *m, char *buf, long off, int count)
-{
-	char *s, *p, *e;
-	int i, n;
-	long off0;
-
-	if(m->infolen > 0 && off >= m->infolen)
-		return 0;
-	off0 = off;
-	s = buf;
-	e = s + count;
-	for(i = 0; s < e; i++){
-		if(i == nelem(infofields)){
-			m->infolen = s - buf + off0;
-			break;
-		}
-		if((n = fileinfo(mb, m, infofields[i], &p)) < 0)
-			return -1;
-		if(off > n){
-			off -= n + 1;
-			continue;
-		}
-		if(off){
-			n -= off;
-			p += off;
-			off = 0;
-		}
-		if(s + n > e)
-			n = e - s;
-		memcpy(s, p, n);
-		s += n;
-		if(s < e)
-			*s++ = '\n';
-	}
-	return s - buf;
-}
-
-static int
-mkstat(Dir *d, Mailbox *mb, Message *m, int t)
-{
-	char *p, *e;
-	int n;
-
-	d->uid = user;
-	d->gid = user;
-	d->muid = user;
-	d->mode = 0444;
-	d->qid.vers = 0;
-	d->qid.type = QTFILE;
-	d->type = 0;
-	d->dev = 0;
-	if(m && m->fileid > 1000000ull)
-		d->atime = m->fileid >> 8;
-	else if(mb && mb->d)
-		d->atime = mb->d->mtime;
-	else
-		d->atime = time(0);
-	d->mtime = d->atime;
-
-	switch(t){
-	case Qtop:
-		d->name = ".";
-		d->mode = DMDIR|0555;
-		d->atime = d->mtime = time(0);
-		d->length = 0;
-		d->qid.path = PATH(0, Qtop);
-		d->qid.type = QTDIR;
-		break;
-	case Qmbox:
-		d->name = mb->name;
-		d->mode = DMDIR|0555;
-		d->length = 0;
-		d->qid.path = PATH(mb->id, Qmbox);
-		d->qid.type = QTDIR;
-		d->qid.vers = mb->vers;
-		break;
-	case Qdir:
-		d->name = m->name;
-		d->mode = DMDIR|0555;
-		d->length = 0;
-		d->qid.path = PATH(m->id, Qdir);
-		d->qid.type = QTDIR;
-		break;
-	case Qctl:
-		d->name = dirtab[t];
-		d->mode = 0666;
-		d->atime = d->mtime = time(0);
-		d->length = 0;
-		d->qid.path = PATH(0, Qctl);
-		break;
-	case Qheader:
-		if(cacheheaders(mb, m) < 0)
-			return -1;
-		d->name = dirtab[t];
-		d->length = readheader(m, hbuf, 0, sizeof hbuf);
-		putcache(mb, m);
-		break;
-	case Qmboxctl:
-		d->name = dirtab[t];
-		d->mode = 0222;
-		d->atime = d->mtime = time(0);
-		d->length = 0;
-		d->qid.path = PATH(mb->id, Qmboxctl);
-		break;
-	case Qinfo:
-		if((n = readinfo(mb, m, hbuf, 0, sizeof hbuf)) < 0)
-			return -1;
-		d->name = dirtab[t];
-		d->length = n;
-		d->qid.path = PATH(m->id, t);
-		break;
-	case Qraw:
-		if(cacheheaders(mb, m) < 0)
-			return -1;
-		d->name = dirtab[t];
-		d->length = m->size;
-		p = m->start;
-		if(p != nil)
-		if(strncmp(p, "From ", 5) == 0)
-		if(e = strchr(p, '\n'))
-			d->length -= ++e - p;
-		putcache(mb, m);
-		break;
-	case Qrawbody:
-		d->name = dirtab[t];
-		d->length = m->rawbsize;
-		break;
-	case Qrawunix:
-		d->name = dirtab[t];
-		d->length = m->size;
-		if(mb->addfrom && Topmsg(mb, m)){
-			if(cacheheaders(mb, m) < 0)
-				return -1;
-			d->length += strlen(m->unixheader);
-			putcache(mb, m);
-		}
-		break;
-	case Qflags:
-		d->mode = 0666;
-	default:
-		if((n = fileinfo(mb, m, t, &p)) < 0)
-			return -1;
-		d->name = dirtab[t];
-		d->length = n;
-		d->qid.path = PATH(m->id, t);
-		break;
-	}
-	return 0;
-}
-
-char*
-rversion(Fid*)
-{
-	Fid *f;
-
-	if(thdr.msize < 256)
-		return "max messagesize too small";
-	if(thdr.msize < messagesize)
-		messagesize = thdr.msize;
-	rhdr.msize = messagesize;
-	rhdr.version = "9P2000";
-	if(strncmp(thdr.version, "9P", 2) != 0)
-		rhdr.version = "unknown";
-		
-	for(f = fids; f; f = f->next)
-		if(f->busy)
-			rclunk(f);
-	return nil;
-}
-
-char*
-rauth(Fid*)
-{
-	return Enoauth;
-}
-
-char*
-rflush(Fid*)
-{
-	return 0;
-}
-
-char*
-rattach(Fid *f)
-{
-	f->busy = 1;
-	f->m = nil;
-	f->mb = nil;
-	f->qid.path = PATH(0, Qtop);
-	f->qid.type = QTDIR;
-	f->qid.vers = 0;
-	rhdr.qid = f->qid;
-	if(strcmp(thdr.uname, user) != 0)
-		return Eperm;
-	return 0;
-}
-
-static Fid*
-doclone(Fid *f, int nfid)
-{
-	Fid *nf;
-
-	nf = newfid(nfid);
-	if(nf->busy)
-		return nil;
-	nf->busy = 1;
-	nf->open = 0;
-	if(nf->mb = f->mb)
-		mboxincref(nf->mb);
-	if(nf->m = f->m)
-		msgincref(nf->mb, nf->m);
-	nf->qid = f->qid;
-	return nf;
-}
-
-/* slow?  binary search? */
-static int
-dindex(char *name)
-{
-	int i;
-
-	for(i = 0; i < Qmax; i++)
-		if(dirtab[i] != nil)
-		if(strcmp(dirtab[i], name) == 0)
-			return i;
-	return -1;
-}
-
-char*
-dowalk(Fid *f, char *name)
-{
-	char *p;
-	Hash *h;
-	int t;
-
-	if(f->qid.type != QTDIR)
-		return Enotdir;
-	t = FILE(f->qid.path);
-	if(strcmp(name, ".") == 0)
-		return nil; 
-	if(strcmp(name, "..") == 0){
-		switch(t){
-		case Qtop:
-			f->qid.path = PATH(0, Qtop);
-			f->qid.type = QTDIR;
-			f->qid.vers = 0;
-			break;
-		case Qmbox:
-			f->qid.path = PATH(0, Qtop);
-			f->qid.type = QTDIR;
-			f->qid.vers = 0;
-			mboxdecref(f->mb);
-			f->mb = nil;
-			break;
-		case Qdir:
-			if(Topmsg(f->mb, f->m)){
-				f->qid.path = PATH(f->mb->id, Qmbox);
-				f->qid.type = QTDIR;
-				f->qid.vers = f->mb->vers;
-				msgdecref(f->mb, f->m);
-				f->m = nil;
-			} else {
-				msgincref(f->mb, f->m->whole);
-				msgdecref(f->mb, f->m);
-				f->m = f->m->whole;
-				f->qid.path = PATH(f->m->id, Qdir);
-				f->qid.type = QTDIR;
-			}
-			break;
-		}
-		return nil;
-	}
-
-	/* this must catch everything except . and .. */
-	if(t == Qdir && *name >= 'a' && *name <= 'z'){
-		for(;;){
-			t = dindex(name);
-			if(t == -1){
-				if((p = strchr(name, '.')) != nil && *name != '.'){
-					*p = 0;
-					continue;
-				}
-				return Enotexist;
-			}
-			break;
-		}
-		h = hlook(f->qid.path, "xxx");		/* sleezy speedup */
-	} else {
-		h = hlook(f->qid.path, name);
-	}
-
-	if(h == nil)
-		return Enotexist;
-
-	if(h->mb)
-		mboxincref(h->mb);
-	if(h->m)
-		msgincref(h->mb, h->m);
-	if(f->m)
-		msgdecref(f->mb, f->m);
-	if(f->mb)
-		mboxdecref(f->mb);
-	f->m = h->m;
-	f->mb = h->mb;
-	f->qid = h->qid;
-	if(t < Qmax)
-		f->qid.path = PATH(f->m->id, t);	/* sleezy speedup */
-	return nil;
-}
-
-char*
-rwalk(Fid *f)
-{
-	Fid *nf;
-	char *rv;
-	int i;
-
-	if(f->open)
-		return Eisopen;
-
-	rhdr.nwqid = 0;
-	nf = nil;
-
-	/* clone if requested */
-	if(thdr.newfid != thdr.fid){
-		nf = doclone(f, thdr.newfid);
-		if(nf == nil)
-			return "new fid in use";
-		f = nf;
-	}
-
-	/* if it's just a clone, return */
-	if(thdr.nwname == 0 && nf != nil)
-		return nil;
-
-	/* walk each element */
-	rv = nil;
-	for(i = 0; i < thdr.nwname; i++){
-		rv = dowalk(f, thdr.wname[i]);
-		if(rv != nil){
-			if(nf != nil)	
-				rclunk(nf);
-			break;
-		}
-		rhdr.wqid[i] = f->qid;
-	}
-	rhdr.nwqid = i;
-
-	/* we only error out if no walk */
-	if(i > 0)
-		rv = nil;
-	return rv;
-}
-
-char*
-ropen(Fid *f)
-{
-	int file;
-
-	if(f->open)
-		return Eisopen;
-	file = FILE(f->qid.path);
-	if(thdr.mode != OREAD)
-		if(file != Qctl && file != Qmboxctl && file != Qflags)
-			return Eperm;
-
-	/* make sure we've decoded */
-	if(file == Qbody){
-		if(cachebody(f->mb, f->m) < 0)
-			return Eio;
-		decode(f->m);
-		convert(f->m);
-		putcache(f->mb, f->m);
-	}
-
-	rhdr.iounit = 0;
-	rhdr.qid = f->qid;
-	f->open = 1;
-	return 0;
-}
-
-char*
-rcreate(Fid*)
-{
-	return Eperm;
-}
-
-int
-readtopdir(Fid*, uchar *buf, long off, int cnt, int blen)
-{
-	Dir d;
-	int m, n;
-	long pos;
-	Mailbox *mb;
-
-	n = 0;
-	pos = 0;
-	mkstat(&d, nil, nil, Qctl);
-	m = convD2M(&d, &buf[n], blen);
-	if(off <= pos){
-		if(m <= BIT16SZ || m > cnt)
-			return n;
-		n += m;
-		cnt -= m;
-	}
-	pos += m;
-		
-	for(mb = mbl; mb != nil; mb = mb->next){
-		assert(mb->refs > 0);
-
-		mkstat(&d, mb, nil, Qmbox);
-		m = convD2M(&d, &buf[n], blen - n);
-		if(off <= pos){
-			if(m <= BIT16SZ || m > cnt)
-				break;
-			n += m;
-			cnt -= m;
-		}
-		pos += m;
-	}
-	return n;
-}
-
-int
-readmboxdir(Fid *f, uchar *buf, long off, int cnt, int blen)
-{
-	Dir d;
-	int n, m;
-	long pos;
-	Message *msg;
-
-	assert(f->mb->refs > 0);
-
-	if(off == 0)
-		syncmbox(f->mb, 1);
-
-	n = 0;
-	if(f->mb->ctl){
-		mkstat(&d, f->mb, nil, Qmboxctl);
-		m = convD2M(&d, &buf[n], blen);
-		if(off == 0){
-			if(m <= BIT16SZ || m > cnt){
-				f->fptr = nil;
-				return n;
-			}
-			n += m;
-			cnt -= m;
-		} else
-			off -= m;
-	}
-
-	/* to avoid n**2 reads of the directory, use a saved finger pointer */
-	if(f->mb->vers == f->fvers && off >= f->foff && f->fptr != nil){
-		msg = f->fptr;
-		pos = f->foff;
-	} else {
-		msg = f->mb->root->part;
-		pos = 0;
-	}
-
-	for(; cnt > 0 && msg != nil; msg = msg->next){
-		/* act like deleted files aren't there */
-		if(msg->deleted)
-			continue;
-		if(mkstat(&d, f->mb, msg, Qdir) < 0)
-			continue;
-		m = convD2M(&d, &buf[n], blen - n);
-		if(off <= pos){
-			if(m <= BIT16SZ || m > cnt)
-				break;
-			n += m;
-			cnt -= m;
-		}
-		pos += m;
-	}
-
-	/* save a finger pointer for next read of the mbox directory */
-	f->foff = pos;
-	f->fptr = msg;
-	f->fvers = f->mb->vers;
-	return n;
-}
-
-int
-readmsgdir(Fid *f, uchar *buf, long off, int cnt, int blen)
-{
-	Dir d;
-	int i, n, m;
-	long pos;
-	Message *msg;
-
-	n = 0;
-	pos = 0;
-	for(i = 0; i < Qmax; i++){
-		if(mkstat(&d, f->mb, f->m, i) < 0)
-			continue;
-		m = convD2M(&d, &buf[n], blen - n);
-		if(off <= pos){
-			if(m <= BIT16SZ || m > cnt)
-				return n;
-			n += m;
-			cnt -= m;
-		}
-		pos += m;
-	}
-	for(msg = f->m->part; msg != nil; msg = msg->next){
-		if(mkstat(&d, f->mb, msg, Qdir) < 0)
-			continue;
-		m = convD2M(&d, &buf[n], blen - n);
-		if(off <= pos){
-			if(m <= BIT16SZ || m > cnt)
-				break;
-			n += m;
-			cnt -= m;
-		}
-		pos += m;
-	}
-	return n;
-}
-
-static int
-mboxctlread(Mailbox *mb, char **p)
-{
-	static char buf[128];
-
-	*p = buf;
-	return snprint(*p, sizeof buf, "%s\n%ld\n", mb->path, mb->vers);
-}
-
-char*
-rread(Fid *f)
-{
-	char *p;
-	int t, i, n, cnt;
-	long off;
-
-	rhdr.count = 0;
-	off = thdr.offset;
-	cnt = thdr.count;
-	if(cnt > messagesize - IOHDRSZ)
-		cnt = messagesize - IOHDRSZ;
-	rhdr.data = (char*)mbuf;
-
-	t = FILE(f->qid.path);
-	if(f->qid.type & QTDIR){
-		if(t == Qtop)
-			n = readtopdir(f, mbuf, off, cnt, messagesize - IOHDRSZ);
-		else if(t == Qmbox)
-			n = readmboxdir(f, mbuf, off, cnt, messagesize - IOHDRSZ);
-		else
-			n = readmsgdir(f, mbuf, off, cnt, messagesize - IOHDRSZ);
-		rhdr.count = n;
-		return nil;
-	}
-
-	switch(t){
-	case Qctl:
-		break;
-	case Qmboxctl:
-		i = mboxctlread(f->mb, &p);
-		goto output;
-	case Qheader:
-		if(cacheheaders(f->mb, f->m) < 0)
-			return Eio;
-		rhdr.count = readheader(f->m, (char*)mbuf, off, cnt);
-		putcache(f->mb, f->m);
-		break;
-	case Qinfo:
-		if(cnt > sizeof mbuf)
-			cnt = sizeof mbuf;
-		if((i = readinfo(f->mb, f->m, (char*)mbuf, off, cnt)) < 0)
-			return Eio;
-		rhdr.count = i;
-		break;
-	case Qrawunix:
-		if(f->mb->addfrom && Topmsg(f->mb, f->m)){
-			if(cacheheaders(f->mb, f->m) < 0)
-				return Eio;
-			p = f->m->unixheader;
-			if(off < strlen(p)){
-				rhdr.count = strlen(p + off);
-				memmove(mbuf, p + off, rhdr.count);
-				break;
-			}
-			off -= strlen(p);
-		}
-	default:
-		i = fileinfo(f->mb, f->m, t, &p);
-	output:
-		if(i < 0)
-			return Eio;
-		if(off < i){
-			if(off + cnt > i)
-				cnt = i - off;
-			if(cnt > sizeof mbuf)
-				cnt = sizeof mbuf;
-			memmove(mbuf, p + off, cnt);
-			rhdr.count = cnt;
-		}
-		break;
-	}
-	return nil;
-}
-
-char*
-modflags(Mailbox *mb, Message *m, char *p)
-{
-	char *err;
-	uchar f;
-
-	f = m->flags;
-	if(err = txflags(p, &f))
-		return err;
-	if(f != m->flags){
-		m->flags = f;
-		m->cstate |= Cidxstale;
-		m->cstate |= Cmod;
-		if(mb->modflags != nil)
-			mb->modflags(mb, m, f);
-	}
-	return nil;
-}
-
-char*
-rwrite(Fid *f)
-{
-	char *argvbuf[1024], **argv, file[Pathlen], *err, *v0;
-	int i, t, argc, flags;
-
-	t = FILE(f->qid.path);
-	rhdr.count = thdr.count;
-	if(thdr.count == 0)
-		return Ebadctl;
-	if(thdr.data[thdr.count - 1] == '\n')
-		thdr.data[thdr.count - 1] = 0;
-	else
-		thdr.data[thdr.count] = 0;
-	argv = argvbuf;
-	switch(t){
-	case Qctl:
-		memset(argvbuf, 0, sizeof argvbuf);
-		argc = tokenize(thdr.data, argv, nelem(argvbuf) - 1);
-		if(argc == 0)
-			return Ebadctl;
-		if(strcmp(argv[0], "open") == 0 || strcmp(argv[0], "create") == 0){
-			if(argc == 1 || argc > 3)
-				return Ebadargs;
-			mboxpathbuf(file, sizeof file, getlog(), argv[1]);
-			if(argc == 3){
-				if(strchr(argv[2], '/') != nil)
-					return "/ not allowed in mailbox name";
-			}else
-				argv[2] = nil;
-			flags = 0;
-			if(strcmp(argv[0], "create") == 0)
-				flags |= DMcreate;
-			return newmbox(file, argv[2], flags, nil);
-		}
-		if(strcmp(argv[0], "close") == 0){
-			if(argc < 2)
-				return nil;
-			for(i = 1; i < argc; i++)
-				freembox(argv[i]);
-			return nil;
-		}
-		if(strcmp(argv[0], "delete") == 0){
-			if(argc < 3)
-				return nil;
-			delmessages(argc - 1, argv + 1);
-			return nil;
-		}
-		if(strcmp(argv[0], "flag") == 0){
-			if(argc < 3)
-				return nil;
-			return flagmessages(argc - 1, argv + 1);
-		}
-		if(strcmp(argv[0], "move") == 0){
-			if(argc < 4)
-				return nil;
-			return movemessages(argc - 1, argv + 1);
-		}
-		if(strcmp(argv[0], "remove") == 0){
-			v0 = argv0;
-			flags = 0;
-			ARGBEGIN{
-			default:
-				argv0 = v0;
-				return Ebadargs;
-			case 'r':
-				flags |= Rrecur;
-				break;
-			case 't':
-				flags |= Rtrunc;
-				break;
-			}ARGEND
-			argv0 = v0;
-			if(argc == 0)
-				return Ebadargs;
-			for(; *argv; argv++){
-				mboxpathbuf(file, sizeof file, getlog(), *argv);
-				if(err = newmbox(file, nil, 0, nil))
-					return err;
-				if(err = removembox(file, flags))
-					return err;
-			}
-			return 0;
-		}
-		if(strcmp(argv[0], "rename") == 0){
-			v0 = argv0;
-			flags = 0;
-			ARGBEGIN{
-			case 't':
-				flags |= Rtrunc;
-				break;
-			}ARGEND
-			argv0 = v0;
-			if(argc != 2)
-				return Ebadargs;
-			return mboxrename(argv[0], argv[1], flags);
-		}
-		return Ebadctl;
-	case Qmboxctl:
-		if(f->mb->ctl == nil)
-			break;
-		argc = tokenize(thdr.data, argv, nelem(argvbuf));
-		if(argc == 0)
-			return Ebadctl;
-		return f->mb->ctl(f->mb, argc, argv);
-	case Qflags:
-		/*
-		 * modifying flags on subparts is a little strange.
-		 */
-		if(!Topmsg(f->mb, f->m))
-			break;
-		return modflags(f->mb, f->m, thdr.data);
-	}
-	return Eperm;
-}
-
-char*
-rclunk(Fid *f)
-{
-	f->busy = 1;
-	/* coherence(); */
-	f->fid = -1;
-	f->open = 0;
-	if(f->m != nil){
-		msgdecref(f->mb, f->m);
-		f->m = nil;
-	}
-	if(f->mb != nil){
-		mboxdecref(f->mb);
-		f->mb = nil;
-	}
-	f->busy = 0;
-	return 0;
-}
-
-char *
-rremove(Fid *f)
-{
-	if(f->mb != nil && f->m != nil && Topmsg(f->mb, f->m) && f->m->deleted == 0)
-		f->m->deleted = Deleted;
-	return rclunk(f);
-}
-
-char *
-rstat(Fid *f)
-{
-	Dir d;
-
-	if(FILE(f->qid.path) == Qmbox)
-		syncmbox(f->mb, 1);
-	if(mkstat(&d, f->mb, f->m, FILE(f->qid.path)) < 0)
-		return Eio;
-	rhdr.nstat = convD2M(&d, mbuf, messagesize - IOHDRSZ);
-	rhdr.stat = mbuf;
-	return 0;
-}
-
-char*
-rwstat(Fid*)
-{
-	return Eperm;
-}
-
-static Fid*
-checkfid(Fid *f)
-{
-	if(f->busy)
-	switch(FILE(f->qid.path)){
-	case Qtop:
-	case Qctl:
-		assert(f->mb == nil);
-		assert(f->m == nil);
-		break;
-	case Qmbox:
-	case Qmboxctl:
-		assert(f->mb != nil && f->mb->refs > 0);
-		assert(f->m == nil);
-		break;
-	default:
-		assert(f->mb != nil && f->mb->refs > 0);
-		assert(f->m != nil && f->m->refs > 0);
-		break;
-	}
-	return f;
-}
-
-Fid*
-newfid(int fid)
-{
-	Fid *f, *ff;
-
-	ff = 0;
-	for(f = fids; f; f = f->next)
-		if(f->fid == fid)
-			return checkfid(f);
-		else if(!ff && !f->busy)
-			ff = f;
-	if(ff){
-		ff->fid = fid;
-		ff->fptr = nil;
-		return ff;
-	}
-	f = emalloc(sizeof *f);
-	f->fid = fid;
-	f->fptr = nil;
-	f->next = fids;
-	fids = f;
-	return f;
-}
-
-void
-io(void)
-{
-	char *err;
-	int n;
-
-	/* start a process to watch the mailboxes*/
-	if(plumbing || biffing)
-		switch(rfork(RFPROC|RFMEM)){
-		case -1:
-			/* oh well */
-			break;
-		case 0:
-			reader();
-			exits("");
-		default:
-			break;
-		}
-
-	for(;;){
-		n = read9pmsg(mfd[0], mdata, messagesize);
-		if(n <= 0)
-			return;
-		if(convM2S(mdata, n, &thdr) == 0)
-			continue;
-
-		if(Dflag)
-			fprint(2, "%s:<-%F\n", argv0, &thdr);
-
-		qlock(&synclock);
-		rhdr.data = (char*)mdata + messagesize;
-		if(!fcalls[thdr.type])
-			err = "bad fcall type";
-		else
-			err = fcalls[thdr.type](newfid(thdr.fid));
-		if(err){
-			rhdr.type = Rerror;
-			rhdr.ename = err;
-		}else{
-			rhdr.type = thdr.type + 1;
-			rhdr.fid = thdr.fid;
-		}
-		rhdr.tag = thdr.tag;
-		qunlock(&synclock);
-
-		if(Dflag)
-			fprint(2, "%s:->%F\n", argv0, &rhdr);
-		n = convS2M(&rhdr, mdata, messagesize);
-		if(write(mfd[1], mdata, n) != n)
-			error("mount write");
-	}
-}
-
-static char *readerargv[] = {"upas/fs", "plumbing", 0};
-
-void
-reader(void)
-{
-	ulong t;
-	Dir *d;
-	Mailbox *mb;
-
-	setname(readerargv);
-	sleep(15*1000);
-	for(;;){
-		qlock(&synclock);
-		t = time(0);
-		for(mb = mbl; mb != nil; mb = mb->next){
-			if(mb->waketime != 0 && t >= mb->waketime){
-				mb->waketime = 0;
-				break;
-			}
-			if(mb->d != nil){
-				d = dirstat(mb->path);
-				if(d != nil){
-					if(d->qid.path != mb->d->qid.path
-					|| d->qid.vers != mb->d->qid.vers){
-						free(d);
-						break;
-					}
-					free(d);
-				}
-			}
-		}
-		if(mb != nil) {
-			syncmbox(mb, 1);
-			qunlock(&synclock);
-		} else {
-			qunlock(&synclock);
-			sleep(15*1000);
-		}
-	}
-}
-
-void
-error(char *s)
-{
-	syskillpg(getpid());
-	eprint("upas/fs: fatal error: %s: %r\n", s);
-	exits(s);
-}
-
-
-typedef struct Ignorance Ignorance;
-struct Ignorance
-{
-	Ignorance *next;
-	char	*str;
-	int	len;
-};
-static Ignorance *ignorance;
-
-/*
- *  read the file of headers to ignore
- */
-static void
-readignore(void)
-{
-	char *p;
-	Ignorance *i;
-	Biobuf *b;
-
-	if(ignorance != nil)
-		return;
-
-	b = Bopen("/mail/lib/ignore", OREAD);
-	if(b == 0)
-		return;
-	while(p = Brdline(b, '\n')){
-		p[Blinelen(b) - 1] = 0;
-		while(*p && (*p == ' ' || *p == '\t'))
-			p++;
-		if(*p == '#')
-			continue;
-		i = emalloc(sizeof *i);
-		i->len = strlen(p);
-		i->str = strdup(p);
-		if(i->str == 0){
-			free(i);
-			break;
-		}
-		i->next = ignorance;
-		ignorance = i;
-	}
-	Bterm(b);
-}
-
-static int
-ignore(char *p, int n)
-{
-	Ignorance *i;
-
-	readignore();
-	for(i = ignorance; i != nil; i = i->next)
-		if(i->len <= n && cistrncmp(i->str, p, i->len) == 0)
-			return 1;
-	return 0;
-}
-
-int
-readheader(Message *m, char *buf, int off, int cnt)
-{
-	char *s, *end, *se, *p, *e, *to;
-	int n, ns, salloc;
-
-	to = buf;
-	p = m->header;
-	e = m->hend;
-	s = emalloc(salloc = 2048);
-	end = s + salloc;
-
-	/* copy in good headers */
-	while(cnt > 0 && p < e){
-		if((n = hdrlen(p, e)) <= 0)
-			break;
-		if(ignore(p, n)){
-			p += n;
-			continue;
-		}
-		if(n + 1 > salloc){
-			s = erealloc(s, salloc = n + 1);
-			end = s + salloc;
-		}
-		se = rfc2047(s, end, p, n, 0);
-		ns = se - s;
-		if(off > 0){
-			if(ns <= off){
-				off -= ns;
-				p += n;
-				continue;
-			}
-			ns -= off;
-		}
-		if(ns > cnt)
-			ns = cnt;
-		memmove(to, s + off, ns);
-		to += ns;
-		p += n;
-		cnt -= ns;
-		off = 0;
-	}
-	free(s);
-	return to - buf;
-}
-
-ulong
-hash(char *s)
-{
-	ulong c, h;
-
-	h = 0;
-	while(c = *s++)
-		h = h*131 + c;
-
-	return h;
-}
-
-Hash*
-hlook(uvlong ppath, char *name)
-{
-	ulong h;
-	Hash *hp;
-
-	h = (hash(name)+ppath) % nelem(htab);
-	for(hp = htab[h]; hp != nil; hp = hp->next)
-		if(ppath == hp->ppath && strcmp(name, hp->name) == 0)
-			return hp;
-	return nil;
-}
-
-void
-henter(uvlong ppath, char *name, Qid qid, Message *m, Mailbox *mb)
-{
-	ulong h;
-	Hash *hp, **l;
-
-	h = (hash(name)+ppath) % nelem(htab);
-	for(l = &htab[h]; *l != nil; l = &(*l)->next){
-		hp = *l;
-		if(ppath == hp->ppath && strcmp(name, hp->name) == 0){
-			hp->m = m;
-			hp->mb = mb;
-			hp->qid = qid;
-			return;
-		}
-	}
-	*l = hp = emalloc(sizeof(*hp));
-	hp->m = m;
-	hp->mb = mb;
-	hp->qid = qid;
-	hp->name = name;
-	hp->ppath = ppath;
-}
-
-void
-hfree(uvlong ppath, char *name)
-{
-	ulong h;
-	Hash *hp, **l;
-
-	h = (hash(name)+ppath) % nelem(htab);
-	for(l = &htab[h]; *l != nil; l = &(*l)->next){
-		hp = *l;
-		if(ppath == hp->ppath && strcmp(name, hp->name) == 0){
-			hp->mb = nil;
-			*l = hp->next;
-			free(hp);
-			break;
-		}
-	}
-}
-
-void
-post(char *name, char *envname, int srvfd)
-{
-	char buf[32];
-	int fd;
-
-	fd = create(name, OWRITE, 0600);
-	if(fd < 0)
-		error("post failed");
-	snprint(buf, sizeof buf, "%d", srvfd);
-	if(write(fd, buf, strlen(buf)) != strlen(buf))
-		error("srv write");
-	close(fd);
-	putenv(envname, name);
-}
--- a/sys/src/cmd/upas/fs/mbox.c
+++ /dev/null
@@ -1,1725 +1,0 @@
-#include "common.h"
-#include <ctype.h>
-#include <plumb.h>
-#include <libsec.h>
-#include "dat.h"
-
-typedef struct Header Header;
-struct Header {
-	char	*type;
-	uintptr	offset;
-	char	*(*f)(Message*, Header*, char*, char*);
-	int	len;
-	int	str;
-};
-
-/* headers */
-static	char	*ctype(Message*, Header*, char*, char*);
-static	char	*cencoding(Message*, Header*, char*, char*);
-static	char	*cdisposition(Message*, Header*, char*, char*);
-static	char	*from822(Message*, Header*, char*, char*);
-static	char	*replace822(Message*, Header*, char*, char*);
-static	char	*concat822(Message*, Header*, char*, char*);
-static	char	*copy822(Message*, Header*, char*, char*);
-static	char	*ref822(Message*, Header*, char*, char*);
-
-enum
-{
-	Mhead	= 11,	/* offset of first mime header */
-};
-
-#define O(x)	offsetof(Message, x)
-static Header head[] =
-{
-	"date:",		O(date822),	copy822,		0,	0,
-	"from:", 		O(from),		from822,	0,	1,
-	"to:", 		O(to),		concat822,	0,	1,
-	"sender:",	O(sender),	replace822,	0,	1,
-	"reply-to:",	O(replyto),	replace822,	0,	1,
-	"subject:",	O(subject),	copy822,		0,	1,
-	"cc:", 		O(cc),		concat822,	0,	1,
-	"bcc:",		O(bcc),		concat822,	0,	1,
-	"in-reply-to:",	O(inreplyto),	replace822,	0,	1,
-	"message-id:",	O(messageid),	replace822,	0,	1,
-	"references:",	~0,		ref822,		0,	0,
-
-[Mhead]	"content-type:", 	~0,		ctype,		0, 	0,
-	"content-transfer-encoding:", ~0,	cencoding,	0,	0,
-	"content-disposition:", ~0,		cdisposition,	0,	0,
-};
-
-static Mailboxinit *boxinit[] = {
-	imap4mbox,
-	pop3mbox,
-	mdirmbox,
-	plan9mbox,
-};
-
-static void delmessage(Mailbox*, Message*);
-static void mailplumb(Mailbox*, Message*);
-
-char*
-syncmbox(Mailbox *mb, int doplumb)
-{
-	char *s;
-	int n, d, y, a;
-	Message *m, *next;
-
-	if(mb->syncing)
-		return nil;
-
-	mb->syncing = 1;
-
-	a = mb->root->subname;
-	if(rdidxfile(mb) == -2)
-		wridxfile(mb);
-	if(s = mb->sync(mb)){
-		mb->syncing = 0;
-		return s;
-	}
-	n = 0;
-	d = 0;
-	y = 0;
-	for(m = mb->root->part; m; m = next){
-		next = m->next;
-		if(m->deleted == 0){
-			if((m->cstate & Cidx) == 0){
-				cachehash(mb, m);
-				m->cstate |= Cnew;
-				n++;
-			}
-			if((doplumb && m->cstate & (Cnew|Cmod)) && ensurecache(mb, m) == 0){
-				mailplumb(mb, m);
-				msgdecref(mb, m);
-			}
-			m->cstate &= ~(Cnew|Cmod);
-		}
-		if(m->cstate & Cidxstale)
-			y++;
-		if(m->deleted == 0 || m->refs > 0)
-			continue;
-		if(mb->delete && m->inmbox && m->deleted & Deleted)
-			mb->delete(mb, m);
-		if(!m->inmbox){
-			if(doplumb)
-				mailplumb(mb, m);
-			delmessage(mb, m);
-			d++;
-		}
-	}
-	a = mb->root->subname - a;
-	assert(a >= 0);
-	if(n + d + y + a){
-		Hash *h;
-
-		iprint("deleted: %d; new %d; stale %d\n", d, n, y);
-		logmsg(nil, "deleted: %d; new %d; stale %d", d, n, y);
-		wridxfile(mb);
-
-		mb->vers++;
-		if(mb->refs > 0 && (h = hlook(PATH(0, Qtop), mb->name)) != nil && h->mb == mb)
-			h->qid.vers = mb->vers;
-	}
-
-	mb->syncing = 0;
-
-	return nil;
-}
-
-/*
- * not entirely clear where the locking should take place, if
- * it is required.
- */
-char*
-mboxrename(char *a, char *b, int flags)
-{
-	char f0[Pathlen + 4], f1[Pathlen + 4], *err, *p0, *p1;
-	Mailbox *mb;
-
-	snprint(f0, sizeof f0, "%s", a);
-	snprint(f1, sizeof f1, "%s", b);
-	err = newmbox(f0, nil, 0, &mb);
-	dprint("mboxrename %s %s -> %s\n", f0, f1, err);
-	if(err == nil && mb->rename == nil)
-		err = "rename not supported";
-	if(err)
-		goto done;
-	err = mb->rename(mb, f1, flags);
-	if(err)
-		goto done;
-	if(flags & Rtrunc)
-		/* we're comitted, so forget bailing */
-		err = newmbox(f0, nil, DMcreate, 0);
-	p0 = f0 + strlen(f0);
-	p1 = f1 + strlen(f1);
-
-	strcat(f0, ".idx");
-	strcat(f1, ".idx");
-	rename(f0, f1, 0);
-
-	*p0 = *p1 = 0;
-	strcat(f0, ".imp");
-	strcat(f1, ".imp");
-	rename(f0, f1, 0);
-
-	hfree(PATH(0, Qtop), mb->name);
-	snprint(mb->path, sizeof mb->path, "%s", b);
-	p0 = strrchr(mb->path, '/') + 1;
-	if(p0 == (char*)1)
-		p0 = mb->path;
-	snprint(mb->name, sizeof mb->name, "%s", p0);
-	henter(PATH(0, Qtop), mb->name,
-		(Qid){PATH(mb->id, Qmbox), mb->vers, QTDIR}, nil, mb);
-done:
-	return err;
-}
-
-static void
-initheaders(void)
-{
-	int i;
-	static int already;
-
-	if(already)
-		return;
-	already = 1;
-	for(i = 0; i < nelem(head); i++)
-		head[i].len = strlen(head[i].type);
-}
-
-static ulong
-newid(void)
-{
-	ulong rv;
-	static ulong id;
-	static Lock idlock;
-
-	lock(&idlock);
-	rv = ++id;
-	unlock(&idlock);
-
-	return rv;
-}
-
-char*
-newmbox(char *path, char *name, int flags, Mailbox **r)
-{
-	char *p, *rv;
-	int i;
-	Mailbox *mb, **l;
-
-	if(r)
-		*r = nil;
-	initheaders();
-	mb = emalloc(sizeof *mb);
-	mb->flags = flags;
-	strncpy(mb->path, path, sizeof mb->path - 1);
-	p = name;
-	if(p == nil){
-		p = strrchr(path, '/');
-		if(p == nil)
-			p = path;
-		else
-			p++;
-		if(*p == 0){
-			free(mb);
-			return "bad mbox name";
-		}
-	}
-	strncpy(mb->name, p, sizeof mb->name - 1);
-	mb->idxread = genericidxread;
-	mb->idxwrite = genericidxwrite;
-	mb->idxinvalid = genericidxinvalid;
-
-	/* check for a mailbox type */
-	rv = Enotme;	/* can't happen; shut compiler up */
-	for(i = 0; i < nelem(boxinit); i++)
-		if((rv = boxinit[i](mb, path)) != Enotme)
-			break;
-	if(rv){
-		free(mb);
-		return rv;
-	}
-
-	/* make sure name isn't taken */
-	for(l = &mbl; *l != nil; l = &(*l)->next)
-		if(strcmp((*l)->name, mb->name) == 0){
-			if(strcmp(path, (*l)->path) == 0)
-				rv = nil;
-			else
-				rv = "mbox name in use";
-			if(mb->close)
-				mb->close(mb);
-			free(mb);
-			return rv;
-		}
-
-	/* always try locking */
-	mb->dolock = 1;
-	mb->refs = 1;
-	mb->next = nil;
-	mb->id = newid();
-	mb->root = newmessage(nil);
-	mtreeinit(mb);
-
-	*l = mb;
-
-	henter(PATH(0, Qtop), mb->name,
-		(Qid){PATH(mb->id, Qmbox), mb->vers, QTDIR}, nil, mb);
-	if(mb->ctl)
-		henter(PATH(mb->id, Qmbox), "ctl",
-			(Qid){PATH(mb->id, Qmboxctl), 0, QTFILE}, nil, mb);
-	if(r)
-		*r = mb;
-
-	return syncmbox(mb, 0);
-}
-
-/* close the named mailbox */
-void
-freembox(char *name)
-{
-	Mailbox **l, *mb;
-
-	for(l = &mbl; (mb = *l) != nil; l = &mb->next)
-		if(strcmp(name, mb->name) == 0){
-			*l = mb->next;
-			mb->next = nil;
-			hfree(PATH(0, Qtop), mb->name);
-			if(mb->ctl)
-				hfree(PATH(mb->id, Qmbox), "ctl");
-			mboxdecref(mb);
-			break;
-		}
-}
-
-char*
-removembox(char *name, int flags)
-{
-	Mailbox *mb;
-
-	for(mb = mbl; mb != nil; mb = mb->next)
-		if(strcmp(name, mb->path) == 0){
-			mb->flags |= ORCLOSE;
-			mb->rmflags = flags;
-			freembox(mb->name);
-			return 0;
-		}
-	return "maibox not found";
-}
-
-void
-syncallmboxes(void)
-{
-	Mailbox *mb;
-	char *err;
-
-	for(mb = mbl; mb != nil; mb = mb->next)
-		if(err = syncmbox(mb, 0))
-			eprint("syncmbox: %s\n", err);
-}
-
-/*
- *  look for the date in the first Received: line.
- *  it's likely to be the right time zone (it's
- *  the local system) and in a convenient format.
- */
-static int
-rxtotm(Message *m, Tm *tm)
-{
-	char *p, *q;
-	int r;
-
-	if(cistrncmp(m->header, "received:", 9))
-		return -1;
-	q = strchr(m->header, ';');
-	if(!q)
-		return -1;
-	p = q;
-	while((p = strchr(p, '\n')) != nil){
-		if(p[1] != ' ' && p[1] != '\t' && p[1] != '\n')
-			break;
-		p++;
-	}
-	if(!p)
-		return -1;
-	*p = '\0';
-	r = strtotm(q + 1, tm);
-	*p = '\n';
-	return r;
-}
-
-static Message*
-gettopmsg(Mailbox *mb, Message *m)
-{
-	while(!Topmsg(mb, m))
-		m = m->whole;
-	return m;
-}
-
-static void
-datesec(Mailbox *mb, Message *m)
-{
-	vlong v;
-	Tm tm;
-
-	if(m->fileid > 1000000ull<<8)
-		return;
-	if(m->unixdate && strtotm(m->unixdate, &tm) >= 0)
-		v = tmnorm(&tm);
-	else if(m->date822 && strtotm(m->date822, &tm) >= 0)
-		v = tmnorm(&tm);
-	else if(rxtotm(m, &tm) >= 0)
-		v = tmnorm(&tm);
-	else{
-		logmsg(gettopmsg(mb, m), "%s:%s: datasec %s %s\n", mb->path,
-			m->whole? m->whole->name: "?",
-			m->name, m->type);
-		if(Topmsg(mb, m) || strcmp(m->type, "message/rfc822") == 0)
-			abort();
-		v = 0;
-	}
-	m->fileid = v<<8;
-}
-
-/*
- *  parse a message
- */
-extern void sanemsg(Message*);
-extern void sanembmsg(Mailbox*, Message*);
-
-static Message*
-haschild(Message *m, int i)
-{
-	for(m = m->part; m && i; i--)
-		m = m->next;
-	return m;
-}
-
-static void
-parseattachments(Message *m, Mailbox *mb)
-{
-	char *p, *x;
-	int i;
-	Message *nm, **l;
-
-	/* if there's a boundary, recurse... */
-	dprint("parseattachments %p %ld boundary %s\n", m->start, (ulong)(m->end - m->start), m->boundary);
-	if(m->boundary != nil){
-		p = m->body;
-		nm = nil;
-		l = &m->part;
-		for(i = 0;;){
-			x = strstr(p, m->boundary);
-			/* two sequential boundaries; ignore nil message */
-			if(nm && x == p){
-				p = strchr(x, '\n');
-				if(p == nil){
-					nm->rbend = nm->bend = nm->end = x;
-					sanemsg(nm);
-					break;
-				}
-				p = p + 1;
-				continue;
-			}
-			/* no boundary, we're done */
-			if(x == nil){
-				if(nm != nil)
-					nm->rbend = nm->bend = nm->end = m->bend;
-				break;
-			}
-			/* boundary must be at the start of a line */
-			if(x != m->body && x[-1] != '\n'){
-				p = x + 1;
-				continue;
-			}
-
-			if(nm != nil)
-				nm->rbend = nm->bend = nm->end = x;
-			x += strlen(m->boundary);
-
-			/* is this the last part? ignore anything after it */
-			if(strncmp(x, "--", 2) == 0)
-				break;
-			p = strchr(x, '\n');
-			if(p == nil)
-				break;
-			if((nm = haschild(m, i++)) == nil){
-				nm = newmessage(m);
-				*l = nm;
-				l = &nm->next;
-			}
-			nm->start = ++p;
-			assert(nm->ballocd == 0);
-			nm->mheader = nm->header = nm->body = nm->rbody = nm->start;
-		}
-		for(nm = m->part; nm != nil; nm = nm->next){
-			nm->size = nm->end - nm->start;
-			parse(mb, nm, 0, 1);
-			cachehash(mb, nm);		/* botchy place for this */
-		}
-		return;
-	}
-
-	/* if we've got an rfc822 message, recurse... */
-	if(strcmp(m->type, "message/rfc822") == 0){
-		if((nm = haschild(m, 0)) == nil){
-			nm = newmessage(m);
-			m->part = nm;
-		}
-		assert(nm->ballocd == 0);
-		nm->start = nm->header = nm->body = nm->rbody = m->body;
-		nm->end = nm->bend = nm->rbend = m->bend;
-		nm->size = nm->end - nm->start;
-		parse(mb, nm, 0, 0);
-		cachehash(mb, nm);			/* botchy place for this */
-	}
-}
-
-static void
-parseunix(Message *m)
-{
-	char *s, *p;
-
-	m->unixheader = smprint("%.*s", utfnlen(m->start, m->header - m->start), m->start);
-	s = m->unixheader + 5;
-	if((p = strchr(s, ' ')) == nil)
-		return;
-	*p = 0;
-	free(m->unixfrom);
-	m->unixfrom = strdup(s);
-	*p = ' ';
-	m->unixdate = ++p;
-}
-
-void
-parseheaders(Mailbox *mb, Message *m, int addfrom, int justmime)
-{
-	char *p, *e, *o, *t, *s;
-	int i, i0, n;
-	uintptr a;
-
-	if(m->header == nil)
-		m->header = m->start;
-
-	/* parse unix header */
-	if(!justmime && !addfrom && m->unixheader == nil){
-		if(strncmp(m->start, "From ", 5) == 0)
-		if((e = memchr(m->start, '\n', m->end - m->start)) != nil){
-			m->header = e + 1;
-			parseunix(m);
-		}
-	}
-
-	/* parse mime headers */
-	p = m->mheader = m->mhend = m->header;
-	i0 = 0;
-	if(justmime)
-		i0 = Mhead;
-	s = emalloc(2048);
-	e = s + 2048 - 1;
-	while((n = hdrlen(p, m->end)) > 0){
-		if(n > e - s){
-			s = erealloc(s, n);
-			e = s + n - 1;
-		}
-		rfc2047(s, e, p, n, 1);
-		p += n;
-
-		for(i = i0; i < nelem(head); i++)
-			if(!cistrncmp(s, head[i].type, head[i].len)){
-				a = head[i].offset;
-				if(a != ~0){
-					if(o = *(char**)((char*)m + a))
-						continue;
-					t = head[i].f(m, head + i, o, s);
-					*(char**)((char*)m + a) = t;
-				}else
-					head[i].f(m, head + i, 0, s);
-				break;
-			}
-	}
-	free(s);
-	/* the blank line isn't really part of the body or header */
-	if(justmime){
-		m->mhend = p;
-		m->hend = m->header;
-	} else{
-		m->hend = p;
-		m->mhend = m->header;
-	}
-	if(*p == '\n')
-		p++;
-	m->rbody = m->body = p;
-
-	if(!justmime)
-		datesec(mb, m);
-
-	/*
-	 *  only fake header for top-level messages for pop3 and imap4
-	 *  clients (those protocols don't include the unix header).
-	 *  adding the unix header all the time screws up mime-attached
-	 *  rfc822 messages.
-	 */
-	if(!addfrom && m->unixfrom == nil) {
-		free(m->unixheader);
-		m->unixheader = nil;
-	} else if(m->unixheader == nil){
-		if(m->unixfrom != nil && strcmp(m->unixfrom, "???") != 0)
-			p = m->unixfrom;
-		else if(m->from != nil)
-			p = m->from;
-		else
-			p = "???";
-		m->unixheader = smprint("From %s %Δ\n", p, m->fileid);
-	}
-	m->unixdate = nil;
-
-	m->cstate |= Cheader;
-sanembmsg(mb, m);
-}
-
-char*
-promote(char *s)
-{
-	return s? strdup(s): nil;
-}
-
-void
-parsebody(Message *m, Mailbox *mb)
-{
-	Message *nm;
-
-	/* recurse */
-	if(strncmp(m->type, "multipart/", 10) == 0)
-		parseattachments(m, mb);
-	else if(strcmp(m->type, "message/rfc822") == 0){
-		decode(m);
-		parseattachments(m, mb);
-		nm = m->part;
-
-		/* promote headers */
-		if(m->replyto == nil && m->from == nil && m->sender == nil){
-			m->from = promote(nm->from);
-			m->to = promote(nm->to);
-			m->date822 = promote(nm->date822);
-			m->sender = promote(nm->sender);
-			m->replyto = promote(nm->replyto);
-			m->subject = promote(nm->subject);
-		}
-	}else if(strncmp(m->type, "text/", 5) == 0)
-		sanemsg(m);
-
-	free(m->boundary);
-	m->boundary = nil;
-
-	if(m->replyto == nil){
-		if(m->from != nil)
-			m->replyto = strdup(m->from);
-		else if(m->sender != nil)
-			m->replyto = strdup(m->sender);
-		else if(m->unixfrom != nil)
-			m->replyto = strdup(m->unixfrom);
-	}
-	if(m->from == nil && m->unixfrom != nil)
-		m->from = strdup(m->unixfrom);
-
-	free(m->unixfrom);
-	m->unixfrom = nil;
-
-	m->rawbsize = m->rbend - m->rbody;
-	m->cstate |= Cbody;
-}
-
-void
-parse(Mailbox *mb, Message *m, int addfrom, int justmime)
-{
-	sanemsg(m);
-	if((m->cstate & Cheader) == 0)
-		parseheaders(mb, m, addfrom, justmime);
-	parsebody(m, mb);
-	sanemsg(m);
-}
-
-static char*
-skipwhite(char *p)
-{
-	while(isascii(*p) && isspace(*p))
-		p++;
-	return p;
-}
-
-static char*
-skiptosemi(char *p)
-{
-	while(*p && *p != ';')
-		p++;
-	while(*p == ';' || (isascii(*p) && isspace(*p)))
-		p++;
-	return p;
-}
-
-static char*
-getstring(char *p, char *s, char *e, int dolower)
-{
-	int c;
-
-	p = skipwhite(p);
-	if(*p == '"'){
-		for(p++; (c = *p) != '"'; p++){
-			if(c == '\\')
-				c = *++p;
-			/*
-			 * 821 says <x> after \ can be anything at all.
-			 * we just don't care.
-			 */
-			if(c == 0)
-				break;
-			if(c < ' ')
-				continue;
-			if(dolower && c >= 'A' && c <= 'Z')
-				c += 0x20;
-			s = sputc(s, e, c);
-		}
-		if(*p == '"')
-			p++;
-	}else{
-		for(; (c = *p) && !isspace(c) && c != ';'; p++){
-			if(c == '\\')
-				c = *++p;
-			/*
-			 * 821 says <x> after \ can be anything at all.
-			 * we just don't care.
-			 */
-			if(c == 0)
-				break;
-			if(c < ' ')
-				continue;
-			if(dolower && c >= 'A' && c <= 'Z')
-				c += 0x20;
-			s = sputc(s, e, c);
-		}
-	}
-	*s = 0;
-	return p;
-}
-
-static void
-setfilename(Message *m, char *p)
-{
-	char buf[Pathlen];
-
-	dprint("setfilename %p %s -> %s\n", m, m->filename, p);
-	if(m->filename != nil)
-		return;
-	getstring(p, buf, buf + sizeof buf - 1, 0);
-	m->filename = smprint("%s", buf);
-	for(p = m->filename; *p; p++)
-		if(*p == ' ' || *p == '\t' || *p == ';')
-			*p = '_';
-}
-
-static char*
-rtrim(char *p)
-{
-	char *e;
-
-	if(p == 0)
-		return p;
-	e = p + strlen(p) - 1;
-	while(e > p && isascii(*e) && isspace(*e))
-		*e-- = 0;
-	return p;
-}
-
-static char*
-unfold(char *s)
-{
-	char *p, *q;
-
-	q = s;
-	for(p = q; *p != '\0'; p++)
-		if(*p != '\r' && *p != '\n')
-			*q++ = *p;
-	*q = '\0';
-	return s;
-}
-
-static char*
-addr822(char *p, char **ac)
-{
-	int n, c, space, incomment, addrdone, inanticomment, quoted;
-	char s[128+1], *ps, *e, *x, *list;
-
-	list = 0;
-	s[0] = 0;
-	ps = s;
-	e = s + sizeof s;
-	space = quoted = incomment = addrdone = inanticomment = 0;
-	n = 0;
-	for(; c = *p; p++){
-		if(!inanticomment && !quoted && !space && ps != s && c == ' '){
-			ps = sputc(ps, e, c);
-			space = 1;
-			continue;
-		}
-		space = 0;
-		if(!quoted && isspace(c) || c == '\r')
-			continue;
-		/* strings are always treated as atoms */
-		if(!quoted && c == '"'){
-			if(!addrdone && !incomment && !ac)
-				ps = sputc(ps, e, c);
-			for(p++; c = *p; p++){
-				if(ac && c == '"')
-					break;
-				if(!addrdone && !incomment && c != '\r' && c != '\n')
-					ps = sputc(ps, e, c);
-				if(!quoted && *p == '"')
-					break;
-				if(*p == '\\')
-					quoted = 1;
-				else
-					quoted = 0;
-			}
-			if(c == 0)
-				break;
-			quoted = 0;
-			continue;
-		}
-
-		/* ignore everything in an expicit comment */
-		if(!quoted && c == '('){
-			incomment = 1;
-			continue;
-		}
-		if(incomment){
-			if(!quoted && c == ')')
-				incomment = 0;
-			quoted = 0;
-			continue;
-		}
-
-		/* anticomments makes everything outside of them comments */
-		if(!quoted && c == '<' && !inanticomment){
-			if(ac){
-				*ps-- = 0;
-				if(ps > s && *ps == ' ')
-					*ps = 0;
-				if(*ac){
-					*ac = smprint("%s, %s", x=*ac, s);
-					free(x);
-				}else
-					*ac = smprint("%s", s);
-			}
-
-			inanticomment = 1;
-			ps = s;
-			continue;
-		}
-		if(!quoted && c == '>' && inanticomment){
-			addrdone = 1;
-			inanticomment = 0;
-			continue;
-		}
-
-		/* commas separate addresses */
-		if(!quoted && c == ',' && !inanticomment){
-			*ps = 0;
-			addrdone = 0;
-			if(n++ != 0){
-				list = smprint("%s %s", x=list, s);
-				free(x);
-			}else
-				list = smprint("%s", s);
-			ps = s;
-			continue;
-		}
-
-		/* what's left is part of the address */
-		ps = sputc(ps, e, c);
-
-		/* quoted characters are recognized only as characters */
-		if(c == '\\')
-			quoted = 1;
-		else
-			quoted = 0;
-
-	}
-
-	if(ps > s){
-		*ps = 0;
-		if(n != 0){
-			list = smprint("%s %s", x=list, s);
-			free(x);
-		}else
-			list = smprint("%s", s);
-	}
-	return rtrim(list);
-}
-
-/*
- * per rfc2822 §4.5.3, permit multiple to, cc and bcc headers by
- * concatenating their values.
- */
-
-static char*
-concat822(Message*, Header *h, char *o, char *p)
-{
-	char *s, *n;
-
-	p += strlen(h->type);
-	s = addr822(p, 0);
-	if(o){
-		n = smprint("%s %s", o, s);
-		free(s);
-	}else
-		n = s;
-	return n;
-}
-
-static char*
-from822(Message *m, Header *h, char*, char *p)
-{
-	if(m->ffrom)
-		free(m->ffrom);
-	m->from = 0;
-	return addr822(p + h->len, &m->ffrom);
-}
-
-static char*
-replace822(Message *, Header *h, char*, char *p)
-{
-	return addr822(p + h->len, 0);
-}
-
-static char*
-copy822(Message*, Header *h, char*, char *p)
-{
-	return rtrim(unfold(strdup(skipwhite(p + h->len))));
-}
-
-static char*
-ref822(Message *m, Header *h, char*, char *p)
-{
-	char **a, *s, *f[Nref + 1];
-	int i, j, n;
-
-	s = strdup(skipwhite(p + h->len));
-	n = getfields(s, f, nelem(f), 1, "<> \n\t\r,");
-	if(n > Nref)
-		n = Nref;
-	/*
-	 * if there are too many references, drop from the beginning
-	 * of the list. If someone else has a duplicate, we keep the
-	 * old duplicate.
-	 */
-	a = m->references;
-	for(i = 0; i < n; i++){
-		for(j = 0; j < Nref; j++)
-			if(a[j] == nil || strcmp(a[j], f[i]) == 0)
-				break;
-		if(j == Nref){
-			free(a[0]);
-			memmove(&a[0], &a[1], (Nref - 1) * sizeof(a[0]));
-			j--;
-		} else if(a[j] != nil)
-			continue;
-		a[j] = strdup(f[i]);
-	}
-	free(s);
-	return (char*)~0;
-}
-
-static int
-isattribute(char **pp, char *attr)
-{
-	char *p;
-	int n;
-
-	n = strlen(attr);
-	p = *pp;
-	if(cistrncmp(p, attr, n) != 0)
-		return 0;
-	p += n;
-	while(*p == ' ')
-		p++;
-	if(*p++ != '=')
-		return 0;
-	while(*p == ' ')
-		p++;
-	*pp = p;
-	return 1;
-}
-
-static char*
-ctype(Message *m, Header *h, char*, char *p)
-{
-	char buf[128], *e;
-
-	e = buf + sizeof buf - 1;
-	p = getstring(skipwhite(p + h->len), buf, e, 1);
-	m->type = intern(buf);
-
-	for(; *p; p = skiptosemi(p))
-		if(isattribute(&p, "boundary")){
-			p = getstring(p, buf, e, 0);
-			free(m->boundary);
-			m->boundary = smprint("--%s", buf);
-		} else if(cistrncmp(p, "multipart", 9) == 0){
-			/*
-			 *  the first unbounded part of a multipart message,
-			 *  the preamble, is not displayed or saved
-			 */
-		} else if(isattribute(&p, "name")){
-			setfilename(m, p);
-		} else if(isattribute(&p, "charset")){
-			p = getstring(p, buf, e, 1);
-			m->charset = intern(buf);
-		}
-	return (char*)~0;
-}
-
-static char*
-cencoding(Message *m, Header *h, char*, char *p)
-{
-	p = skipwhite(p + h->len);
-	if(cistrncmp(p, "base64", 6) == 0)
-		m->encoding = Ebase64;
-	else if(cistrncmp(p, "quoted-printable", 16) == 0)
-		m->encoding = Equoted;
-	return (char*)~0;
-}
-
-static char*
-cdisposition(Message *m, Header *h, char*, char *p)
-{
-	for(p = skipwhite(p + h->len); *p; p = skiptosemi(p))
-		if(cistrncmp(p, "inline", 6) == 0)
-			m->disposition = Dinline;
-		else if(cistrncmp(p, "attachment", 10) == 0)
-			m->disposition = Dfile;
-		else if(cistrncmp(p, "filename=", 9) == 0){
-			p += 9;
-			setfilename(m, p);
-		}
-	return (char*)~0;
-}
-
-ulong	msgallocd;
-ulong	msgfreed;
-
-Message*
-newmessage(Message *parent)
-{
-	static int id;
-	Message *m;
-
-	msgallocd++;
-
-	m = emalloc(sizeof *m);
-	dprint("newmessage %ld	%p	%p\n", msgallocd, parent, m);
-	m->type = intern("text/plain");
-	m->charset = intern("iso-8859-1");
-	m->cstate = Cidxstale;
-	m->flags = Frecent;
-	m->id = newid();
-	if(parent)
-		snprint(m->name, sizeof m->name, "%d", ++(parent->subname));
-	if(parent == nil)
-		parent = m;
-	m->whole = parent;
-	m->hlen = -1;
-	return m;
-}
-
-/* delete a message from a mailbox */
-static void
-delmessage(Mailbox *mb, Message *m)
-{
-	Message **l;
-
-	assert(m->refs == 0);
-	while(m->part)
-		delmessage(mb, m->part);
-
-	mb->vers++;
-	msgfreed++;
-
-	if(m != m->whole){
-		/* unchain from parent */
-		for(l = &m->whole->part; *l && *l != m; l = &(*l)->next)
-			;
-		if(*l != nil)
-			*l = m->next;
-		m->next = nil;
-		/* clear out of name lookup hash table */
-		if(m->whole->whole == m->whole)
-			hfree(PATH(mb->id, Qmbox), m->name);
-		else
-			hfree(PATH(m->whole->id, Qdir), m->name);
-		hfree(PATH(m->id, Qdir), "xxx");		/* sleezy speedup */
-
-		if(Topmsg(mb, m))
-			mtreedelete(mb, m);
-		cachefree(mb, m);
-		idxfree(m);
-	}
-	free(m);
-}
-
-void
-unnewmessage(Mailbox *mb, Message *parent, Message *m)
-{
-	assert(parent->subname > 0);
-	m->deleted = Dup;
-	delmessage(mb, m);
-	parent->subname -= 1;
-}
-
-/* mark messages (identified by path) for deletion */
-char*
-delmessages(int ac, char **av)
-{
-	int i, needwrite;
-	Mailbox *mb;
-	Message *m;
-
-	for(mb = mbl; mb != nil; mb = mb->next)
-		if(strcmp(av[0], mb->name) == 0)
-			break;
-	if(mb == nil)
-		return "no such mailbox";
-
-	needwrite = 0;
-	for(i = 1; i < ac; i++)
-		for(m = mb->root->part; m != nil; m = m->next)
-			if(strcmp(m->name, av[i]) == 0){
-				if(!m->deleted){
-					needwrite = 1;
-					m->deleted = Deleted;
-					logmsg(m, "deleting");
-				}
-				break;
-			}
-	if(needwrite)
-		syncmbox(mb, 1);
-	return 0;
-}
-
-char*
-flagmessages(int argc, char **argv)
-{
-	char *err, *rerr;
-	int i, needwrite;
-	Mailbox *mb;
-	Message *m;
-
-	if(argc%2)
-		return "bad flags";
-	for(mb = mbl; mb != nil; mb = mb->next)
-		if(strcmp(*argv, mb->name) == 0)
-			break;
-	if(mb == nil)
-		return "no such mailbox";
-	needwrite = 0;
-	rerr = 0;
-	for(i = 1; i < argc; i += 2)
-		for(m = mb->root->part; m; m = m->next)
-			if(strcmp(m->name, argv[i]) == 0){
-				if(err = modflags(mb, m, argv[i + 1]))
-					rerr = err;
-				else
-					needwrite = 1;
-			}
-	if(needwrite)
-		syncmbox(mb, 1);
-	return rerr;
-}
-
-char*
-movemessages(int argc, char **argv)
-{
-	char *err, *dest, *rerr;
-	int i, needwrite;
-	Mailbox *mb;
-	Message *m;
-
-	rerr = 0;
-	for(mb = mbl; mb != nil; mb = mb->next)
-		if(strcmp(*argv, mb->name) == 0)
-			break;
-	if(mb == nil)
-		return "no such mailbox";
-	if(mb->move == nil)
-		return "move not supported";
-	dest = argv[argc - 1];
-	needwrite = 0;
-	for(i = 1; i < argc - 1; i++)
-		for(m = mb->root->part; m; m = m->next)
-			if(strcmp(m->name, argv[i]) == 0){
-				if(err = mb->move(mb, m, dest))
-					rerr = err;
-				else
-					needwrite = 1;
-			}
-	if(needwrite)
-		syncmbox(mb, 1);
-	return rerr;
-}
-
-void
-msgincref(Mailbox *mb, Message *m)
-{
-	assert(mb->refs >= 0);
-	for(;; m = m->whole){
-		assert(m->refs >= 0);
-		m->refs++;
-		if(Topmsg(mb, m))
-			break;
-	}
-}
-
-void
-msgdecref(Mailbox *mb, Message *m)
-{
-	assert(mb->refs >= 0);
-	for(;; m = m->whole){
-		assert(m->refs > 0);
-		m->refs--;
-		if(Topmsg(mb, m)){
-			if(m->refs == 0){
-				if(m->deleted)
-					syncmbox(mb, 1);
-				else
-					putcache(mb, m);
-			}
-			break;
-		}
-	}
-}
-
-void
-mboxincref(Mailbox *mb)
-{
-	assert(mb->refs > 0);
-	mb->refs++;
-}
-
-static void
-mbrmidx(char *path, int flags)
-{
-	char buf[Pathlen];
-
-	snprint(buf, sizeof buf, "%s.idx", path);
-	vremove(buf);
-	if((flags & Rtrunc) == 0){
-		snprint(buf, sizeof buf, "%s.imp", path);
-		vremove(buf);
-	}
-}
-
-void
-mboxdecref(Mailbox *mb)
-{
-	assert(mb->refs > 0);
-	if(--mb->refs)
-		return;
-	syncmbox(mb, 1);
-	delmessage(mb, mb->root);
-	if(mb->close)
-		mb->close(mb);
-	if(mb->flags & ORCLOSE && mb->remove)
-	if(mb->remove(mb, mb->rmflags))
-		rmidx(mb->path, mb->rmflags);
-	mtreefree(mb);
-	free(mb->d);
-	free(mb);
-}
-
-
-/* just space over \r.  sleezy but necessary for ms email. */
-int
-deccr(char *x, int len)
-{
-	char *e;
-
-	e = x + len;
-	for(;;){
-		x = memchr(x, '\r', e - x);
-		if(x == nil)
-			break;
-		*x = ' ';
-	}
-	return len;
-}
-
-/*
- *  undecode message body
- */
-void
-decode(Message *m)
-{
-	int i, len;
-	char *x;
-
-	if(m->decoded)
-		return;
-	dprint("decode %d %p\n", m->encoding, m);
-	switch(m->encoding){
-	case Ebase64:
-		len = m->bend - m->body;
-		i = (len*3)/4 + 1;	/* room for max chars + null */
-		x = emalloc(i);
-		len = dec64((uchar*)x, i, m->body, len);
-		if(len == -1){
-			free(x);
-			break;
-		}
-		if(strncmp(m->type, "text/", 5) == 0)
-			len = deccr(x, len);
-		if(m->ballocd)
-			free(m->body);
-		m->body = x;
-		m->bend = x + len;
-		m->ballocd = 1;
-		break;
-	case Equoted:
-		len = m->bend - m->body;
-		x = emalloc(len + 2);	/* room for null and possible extra nl */
-		len = decquoted(x, m->body, m->bend, 0);
-		if(m->ballocd)
-			free(m->body);
-		m->body = x;
-		m->bend = x + len;
-		m->ballocd = 1;
-		break;
-	default:
-		break;
-	}
-	m->decoded = 1;
-}
-
-/* convert x to utf8 */
-void
-convert(Message *m)
-{
-	int len;
-	char *x;
-
-	/* don't convert if we're not a leaf, not text, or already converted */
-	if(m->converted)
-		return;
-	dprint("convert type=%q charset=%q %p\n", m->type, m->charset, m);
-	m->converted = 1;
-	if(m->part != nil || strncmp(m->type, "text", 4) != 0 || *m->charset == 0)
-		return;
-	len = xtoutf(m->charset, &x, m->body, m->bend);
-	if(len > 0){
-		if(m->ballocd)
-			free(m->body);
-		m->body = x;
-		m->bend = x + len;
-		m->ballocd = 1;
-	}
-}
-
-static int
-hex2int(int x)
-{
-	if(x >= '0' && x <= '9')
-		return x - '0';
-	if(x >= 'A' && x <= 'F')
-		return x - 'A' + 10;
-	if(x >= 'a' && x <= 'f')
-		return x - 'a' + 10;
-	return -1;
-}
-
-/*
- *  underscores are translated in 2047 headers (uscores=1)
- *  but not in the body (uscores=0)
- */
-static char*
-decquotedline(char *out, char *in, char *e, int uscores)
-{
-	int c, soft;
-
-	/* dump trailing white space */
-	while(e >= in && (*e == ' ' || *e == '\t' || *e == '\r' || *e == '\n'))
-		e--;
-
-	/* trailing '=' means no newline */
-	if(*e == '='){
-		soft = 1;
-		e--;
-	} else
-		soft = 0;
-
-	while(in <= e){
-		c = (*in++) & 0xff;
-		switch(c){
-		case '_':
-			if(uscores){
-				*out++ = ' ';
-				break;
-			}
-		default:
-			*out++ = c;
-			break;
-		case '=':
-			c = hex2int(*in++)<<4;
-			c |= hex2int(*in++);
-			if(c != -1)
-				*out++ = c;
-			else{
-				*out++ = '=';
-				in -= 2;
-			}
-			break;
-		}
-	}
-	if(!soft)
-		*out++ = '\n';
-	*out = 0;
-
-	return out;
-}
-
-int
-decquoted(char *out, char *in, char *e, int uscores)
-{
-	char *p, *nl;
-
-	p = out;
-	while((nl = strchr(in, '\n')) != nil && nl < e){
-		p = decquotedline(p, in, nl, uscores);
-		in = nl + 1;
-	}
-	if(in < e)
-		p = decquotedline(p, in, e - 1, uscores);
-
-	/* make sure we end with a new line */
-	if(*(p - 1) != '\n'){
-		*p++ = '\n';
-		*p = 0;
-	}
-
-	return p - out;
-}
-
-char*
-lowercase(char *p)
-{
-	char *op;
-	int c;
-
-	for(op = p; c = *p; p++)
-		if(isupper(c))
-			*p = tolower(c);
-	return op;
-}
-
-/* translate latin1 directly since it fits neatly in utf */
-static int
-latin1toutf(char **out, char *in, char *e)
-{
-	int n;
-	char *p;
-	Rune r;
-
-	n = 0;
-	for(p = in; p < e; p++)
-		if(*p & 0x80)
-			n++;
-	if(n == 0)
-		return 0;
-
-	n += e - in;
-	*out = p = malloc(n + 1);
-	if(p == nil)
-		return 0;
-
-	for(; in < e; in++){
-		r = (uchar)*in;
-		p += runetochar(p, &r);
-	}
-	*p = 0;
-	return p - *out;
-}
-
-/* translate any thing using the tcs program */
-int
-xtoutf(char *charset, char **out, char *in, char *e)
-{
-	char *av[4], *p;
-	int totcs[2], fromtcs[2], n, len, sofar;
-
-	/* might not need to convert */
-	if(strcmp(charset, "us-ascii") == 0 || strcmp(charset, "utf-8") == 0)
-		return 0;
-	if(strcmp(charset, "iso-8859-1") == 0)
-		return latin1toutf(out, in, e);
-
-	len = e - in + 1;
-	sofar = 0;
-	*out = p = malloc(len + 1);
-	if(p == nil)
-		return 0;
-
-	av[0] = charset;
-	av[1] = "-f";
-	av[2] = charset;
-	av[3] = 0;
-	if(pipe(totcs) < 0)
-		goto error;
-	if(pipe(fromtcs) < 0){
-		close(totcs[0]); close(totcs[1]);
-		goto error;
-	}
-	switch(rfork(RFPROC|RFFDG|RFNOWAIT)){
-	case -1:
-		close(fromtcs[0]); close(fromtcs[1]);
-		close(totcs[0]); close(totcs[1]);
-		goto error;
-	case 0:
-		close(fromtcs[0]); close(totcs[1]);
-		dup(fromtcs[1], 1);
-		dup(totcs[0], 0);
-		close(fromtcs[1]); close(totcs[0]);
-		dup(open("/dev/null", OWRITE), 2);
-		exec("/bin/tcs", av);
-		_exits("");
-	default:
-		close(fromtcs[1]); close(totcs[0]);
-		switch(rfork(RFPROC|RFFDG|RFNOWAIT)){
-		case -1:
-			close(fromtcs[0]); close(totcs[1]);
-			goto error;
-		case 0:
-			close(fromtcs[0]);
-			while(in < e){
-				n = write(totcs[1], in, e - in);
-				if(n <= 0)
-					break;
-				in += n;
-			}
-			close(totcs[1]);
-			_exits("");
-		default:
-			close(totcs[1]);
-			for(;;){
-				n = read(fromtcs[0], &p[sofar], len - sofar);
-				if(n <= 0)
-					break;
-				sofar += n;
-				p[sofar] = 0;
-				if(sofar == len){
-					len += 1024;
-					p = realloc(p, len + 1);
-					if(p == nil)
-						goto error;
-					*out = p;
-				}
-			}
-			close(fromtcs[0]);
-			break;
-		}
-		break;
-	}
-	if(sofar == 0)
-		goto error;
-	return sofar;
-
-error:
-	free(*out);
-	*out = nil;
-	return 0;
-}
-
-void *
-emalloc(ulong n)
-{
-	void *p;
-
-	p = mallocz(n, 1);
-	if(!p)
-		sysfatal("malloc %lud: %r", n);
-	setmalloctag(p, getcallerpc(&n));
-	return p;
-}
-
-void *
-erealloc(void *p, ulong n)
-{
-	if(n == 0)
-		n = 1;
-	p = realloc(p, n);
-	if(!p)
-		sysfatal("realloc %lud: %r", n);
-	setrealloctag(p, getcallerpc(&p));
-	return p;
-}
-
-int
-myplumbsend(int fd, Plumbmsg *m)
-{
-	char *buf;
-	int n;
-
-	buf = plumbpack(m, &n);
-	if(buf == nil)
-		return -1;
-	n = write(fd, buf, n);
-	free(buf);
-	return n;
-}
-
-static void
-mailplumb(Mailbox *mb, Message *m)
-{
-	char buf[256], dbuf[SHA1dlen*2 + 1], len[10], date[32], *from, *subject;
-	int ai;
-	Plumbmsg p;
-	Plumbattr a[7];
-	static int fd = -1;
-
-	subject = m->subject;
-	if(subject == nil)
-		subject = "";
-
-	from = m->from;
-	if(from == nil)
-		from = "";
-
-	sprint(len, "%lud", m->size);
-	if(biffing && m->inmbox)
-		fprint(2, "[ %s / %s / %s ]\n", from, subject, len);
-	if(!plumbing)
-		return;
-
-	if(fd < 0)
-		fd = plumbopen("send", OWRITE);
-	if(fd < 0)
-		return;
-
-	p.src = "mailfs";
-	p.dst = "seemail";
-	p.wdir = "/mail/fs";
-	p.type = "text";
-
-	ai = 0;
-	a[ai].name = "filetype";
-	a[ai].value = "mail";
-
-	a[++ai].name = "sender";
-	a[ai].value = from;
-	a[ai-1].next = &a[ai];
-
-	a[++ai].name = "length";
-	a[ai].value = len;
-	a[ai-1].next = &a[ai];
-
-	a[++ai].name = "mailtype";
-	if(!m->inmbox)
-		a[ai].value = "delete";
-	else if(m->cstate & Cmod)
-		a[ai].value = "modify";
-	else
-		a[ai].value = "new";
-	a[ai-1].next = &a[ai];
-
-	snprint(date, sizeof date, "%Δ", m->fileid);
-	a[++ai].name = "date";
-	a[ai].value = date;
-	a[ai-1].next = &a[ai];
-
-	if(m->digest){
-		snprint(dbuf, sizeof dbuf, "%A", m->digest);
-		a[++ai].name = "digest";
-		a[ai].value = dbuf;
-		a[ai-1].next = &a[ai];
-	}
-	a[ai].next = nil;
-	p.attr = a;
-	snprint(buf, sizeof buf, "%s/%s/%s",
-		mntpt, mb->name, m->name);
-	p.ndata = strlen(buf);
-	p.data = buf;
-
-	myplumbsend(fd, &p);
-}
-
-/*
- *  count the number of lines in the body (for imap4)
- */
-ulong
-countlines(Message *m)
-{
-	char *p;
-	ulong i;
-
-	i = 0;
-	for(p = strchr(m->rbody, '\n'); p != nil && p < m->rbend; p = strchr(p + 1, '\n'))
-		i++;
-	return i;
-}
-
-char *logf = "fs";
-
-void
-logmsg(Message *m, char *fmt, ...)
-{
-	char buf[256], *p, *e;
-	va_list args;
-
-	if(!lflag)
-		return;
-	e = buf + sizeof buf;
-	p = seprint(buf, e, "%s.%d: ", user, getpid());
-	if(m)
-		p = seprint(p, e, "from %s digest %A ",
-			m->from, m->digest);
-	va_start(args, fmt);
-	vseprint(p, e, fmt, args);
-	va_end(args);
-
-	if(Sflag)
-		fprint(2, "%s\n", buf);
-	syslog(Sflag, logf, "%s", buf);
-}
-
-void
-iprint(char *fmt, ...)
-{
-	char buf[256], *p, *e;
-	va_list args;
-
-	if(!iflag)
-		return;
-	e = buf + sizeof buf;
-	p = seprint(buf, e, "%s.%d: ", user, getpid());
-	va_start(args, fmt);
-	vseprint(p, e, fmt, args);
-	vfprint(2, fmt, args);
-	va_end(args);
-	syslog(Sflag, logf, "%s", buf);
-}
-
-void
-eprint(char *fmt, ...)
-{
-	char buf[256], buf2[256], *p, *e;
-	va_list args;
-
-	e = buf + sizeof buf;
-	p = seprint(buf, e, "%s.%d: ", user, getpid());
-	va_start(args, fmt);
-	vseprint(p, e, fmt, args);
-	e = buf2 + sizeof buf2;
-	p = seprint(buf2, e, "upas/fs: ");
-	vseprint(p, e, fmt, args);
-	va_end(args);
-	syslog(Sflag, logf, "%s", buf);
-	fprint(2, "%s", buf2);
-}
--- a/sys/src/cmd/upas/fs/tls.c
+++ /dev/null
@@ -1,37 +1,0 @@
-#include "common.h"
-#include <libsec.h>
-#include <auth.h>
-#include "dat.h"
-
-int
-wraptls(int ofd, char *host)
-{
-	Thumbprint *thumb;
-	TLSconn conn;
-	int fd;
-
-	memset(&conn, 0, sizeof conn);
-	conn.serverName = host;
-	fd = tlsClient(ofd, &conn);
-	if(fd < 0){
-		close(ofd);
-		return -1;
-	}
-	if(nocertcheck){
-		syslog(Sflag, logf, "ignoring cert for %s", host);
-		goto skip;
-	}
-	thumb = initThumbprints("/sys/lib/tls/mail", "/sys/lib/tls/mail.exclude", "x509");
-	if(thumb != nil){
-		if(!okCertificate(conn.cert, conn.certlen, thumb)){
-			werrstr("cert for %s not recognized: %r", host);
-			close(fd);
-			fd = -1;
-		}
-		freeThumbprints(thumb);
-	}
-skip:
-	free(conn.cert);
-	free(conn.sessionID);
-	return fd;
-}
--- a/sys/src/cmd/upas/smtp/smtp.c
+++ /dev/null
@@ -1,1209 +1,0 @@
-#include "common.h"
-#include "smtp.h"
-#include <ctype.h>
-#include <mp.h>
-#include <libsec.h>
-#include <auth.h>
-
-static	char*	connect(char*, Mx*);
-static	char*	wraptls(void);
-static	char*	dotls(char*);
-static	char*	doauth(char*);
-
-void	addhostdom(String*, char*);
-String*	bangtoat(char*);
-String*	convertheader(String*);
-int	dBprint(char*, ...);
-#pragma varargck argpos dBprint 1
-int	dBputc(int);
-char*	data(String*, Biobuf*, Mx*);
-char*	domainify(char*, char*);
-String*	fixrouteaddr(String*, Node*, Node*);
-char*	getcrnl(String*);
-int	getreply(void);
-char*	hello(char*, int);
-char*	mailfrom(char*);
-int	printdate(Node*);
-int	printheader(void);
-void	putcrnl(char*, int);
-void	quit(char*);
-char*	rcptto(char*);
-char	*rewritezone(char *);
-
-char	Retry[] = "Retry, Temporary Failure";
-char	Giveup[] = "Permanent Failure";
-
-String	*reply;		/* last reply */
-String	*toline;
-
-int	alarmscale;
-int	autistic;
-int	debug;		/* true if we're debugging */
-int	filter;
-int	insecure;
-int	last = 'n';	/* last character sent by putcrnl() */
-int	ping;
-int	quitting;	/* when error occurs in quit */
-int	tryauth;	/* Try to authenticate, if supported */
-int	trysecure;	/* Try to use TLS if the other side supports it */
-int	nocertcheck; /* ignore unrecognized certs. Still logged */
-
-char	*quitrv;	/* deferred return value when in quit */
-char	ddomain[1024];	/* domain name of destination machine */
-char	*gdomain;	/* domain name of gateway */
-char	*uneaten;	/* first character after rfc822 headers */
-char	*farend;	/* system we are trying to send to */
-char	*user;		/* user we are authenticating as, if authenticating */
-char	hostdomain[256];
-Mx	*tmmx;		/* global for timeout */
-
-Biobuf	bin;
-Biobuf	bout;
-Biobuf	berr;
-Biobuf	bfile;
-
-int
-Dfmt(Fmt *fmt)
-{
-	Mx *mx;
-
-	mx = va_arg(fmt->args, Mx*);
-	if(mx == nil || mx->host[0] == 0)
-		return fmtstrcpy(fmt, "");
-	else
-		return fmtprint(fmt, "(%s:%s)", mx->host, mx->ip);
-}
-#pragma	varargck	type	"D"	Mx*
-
-char*
-deliverytype(void)
-{
-	if(ping)
-		return "ping";
-	return "delivery";
-}
-
-void
-usage(void)
-{
-	fprint(2, "usage: smtp [-aACdfipst] [-b busted-mx] [-g gw] [-h host] "
-		"[-u user] [.domain] net!host[!service] sender rcpt-list\n");
-	exits(Giveup);
-}
-
-int
-timeout(void *, char *msg)
-{
-	syslog(0, "smtp.fail", "%s interrupt: %s: %s %D", deliverytype(), farend,  msg, tmmx);
-	if(strstr(msg, "alarm")){
-		fprint(2, "smtp timeout: connection to %s timed out\n", farend);
-		if(quitting)
-			exits(quitrv);
-		exits(Retry);
-	}
-	if(strstr(msg, "closed pipe")){
-		fprint(2, "smtp timeout: connection closed to %s\n", farend);
-		if(quitting){
-			syslog(0, "smtp.fail", "%s closed pipe to %s %D", deliverytype(), farend, tmmx);
-			_exits(quitrv);
-		}
-		/* call _exits() to prevent Bio from trying to flush closed pipe */
-		_exits(Retry);
-	}
-	return 0;
-}
-
-void
-removenewline(char *p)
-{
-	int n = strlen(p) - 1;
-
-	if(n < 0)
-		return;
-	if(p[n] == '\n')
-		p[n] = 0;
-}
-
-void
-main(int argc, char **argv)
-{
-	char *phase, *addr, *rv, *trv, *host, *domain;
-	char **errs, *p, *e, hellodomain[256], allrx[512];
-	int i, ok, rcvrs, bustedmx;
-	String *from, *fromm, *sender;
-	Mx mx;
-	Tm tm;
-
-	alarmscale = 60*1000;	/* minutes */
-	tmfmtinstall();
-	quotefmtinstall();
-	mailfmtinstall();		/* 2047 encoding */
-	fmtinstall('D', Dfmt);
-	fmtinstall('[', encodefmt);
-	fmtinstall('H', encodefmt);
-	errs = malloc(argc*sizeof(char*));
-	reply = s_new();
-	host = 0;
-	bustedmx = 0;
-	ARGBEGIN{
-	case 'a':
-		tryauth = 1;
-		if(trysecure == 0)
-			trysecure = 1;
-		break;
-	case 'A':	/* autistic: won't talk to us until we talk (Verizon) */
-		autistic = 1;
-		break;
-	case 'b':
-		if(bustedmx >= Maxbustedmx)
-			sysfatal("more than %d busted mxs given", Maxbustedmx);
-		bustedmxs[bustedmx++] = EARGF(usage());
-		break;
-	case 'd':
-		debug = 1;
-		break;
-	case 'f':
-		filter = 1;
-		break;
-	case 'g':
-		gdomain = EARGF(usage());
-		break;
-	case 'h':
-		host = EARGF(usage());
-		break;
-	case 'i':
-		insecure = 1;
-		break;
-	case 'p':
-		alarmscale = 10*1000;	/* tens of seconds */
-		ping = 1;
-		break;
-	case 's':
-		if(trysecure == 0)
-			trysecure = 1;
-		break;
-	case 't':
-		trysecure = 2;
-		break;
-	case 'u':
-		user = EARGF(usage());
-		break;
-	case 'C':
-		nocertcheck = 1;
-		break;
-	default:
-		usage();
-		break;
-	}ARGEND;
-
-	Binit(&berr, 2, OWRITE);
-	Binit(&bfile, 0, OREAD);
-
-	/*
-	 *  get domain and add to host name
-	 */
-	if(*argv && **argv=='.'){
-		domain = *argv;
-		argv++; argc--;
-	} else
-		domain = domainname_read();
-	if(host == 0)
-		host = sysname_read();
-	if(user == nil)
-		user = getuser();
-	strcpy(hostdomain, domainify(host, domain));
-	strcpy(hellodomain, domainify(sysname_read(), domain));
-
-	/*
-	 *  get destination address
-	 */
-	if(*argv == 0)
-		usage();
-	addr = *argv++; argc--;
-	farend = addr;
-	if((rv = strrchr(addr, '!')) && rv[1] == '['){
-		syslog(0, "smtp.fail", "%s to %s failed: illegal address",
-			deliverytype(), addr);
-		exits(Giveup);
-	}
-
-	/*
-	 *  get sender's machine.
-	 *  get sender in internet style.  domainify if necessary.
-	 */
-	if(*argv == 0)
-		usage();
-	sender = unescapespecial(s_copy(*argv++));
-	argc--;
-	fromm = s_clone(sender);
-	rv = strrchr(s_to_c(fromm), '!');
-	if(rv)
-		*rv = 0;
-	else
-		*s_to_c(fromm) = 0;
-	from = bangtoat(s_to_c(sender));
-
-	/*
-	 *  send the mail
-	 */
-	rcvrs = 0;
-	phase = "";
-	USED(phase);			/* just in case */
-	if(filter){
-		Binit(&bout, 1, OWRITE);
-		rv = data(from, &bfile, nil);
-		if(rv != 0){
-			phase = "filter";
-			goto error;
-		}
-		exits(0);
-	}
-
-	/* mxdial uses its own timeout handler */
-	if((rv = connect(addr, &mx)) != 0)
-		exits(rv);
-
-	tmmx = &mx;
-	/* 10 minutes to get through the initial handshake */
-	atnotify(timeout, 1);
-	alarm(10*alarmscale);
-	if((rv = hello(hellodomain, 0)) != 0){
-		phase = "hello";
-		goto error;
-	}
-	alarm(10*alarmscale);
-	if((rv = mailfrom(s_to_c(from))) != 0){
-		phase = "mailfrom";
-		goto error;
-	}
-
-	ok = 0;
-	/* if any rcvrs are ok, we try to send the message */
-	phase = "rcptto";
-	for(i = 0; i < argc; i++){
-		if((trv = rcptto(argv[i])) != 0){
-			/* remember worst error */
-			if(rv != Giveup)
-				rv = trv;
-			errs[rcvrs] = strdup(s_to_c(reply));
-			removenewline(errs[rcvrs]);
-		} else {
-			ok++;
-			errs[rcvrs] = 0;
-		}
-		rcvrs++;
-	}
-
-	/* if no ok rcvrs or worst error is retry, give up */
-	if(ok == 0 && rcvrs == 0)
-		phase = "rcptto; no addresses";
-	if(ok == 0 || rv == Retry)
-		goto error;
-
-	if(ping){
-		quit(0);
-		exits(0);
-	}
-
-	rv = data(from, &bfile, &mx);
-	if(rv != 0)
-		goto error;
-	quit(0);
-	if(rcvrs == ok)
-		exits(0);
-
-	/*
-	 *  here when some but not all rcvrs failed
-	 */
-	fprint(2, "%τ connect to %s: %D %s:\n", thedate(&tm), addr, &mx, phase);
-	for(i = 0; i < rcvrs; i++){
-		if(errs[i]){
-			syslog(0, "smtp.fail", "delivery to %s at %s %D %s, failed: %s",
-				argv[i], addr, &mx, phase, errs[i]);
-			fprint(2, "  mail to %s failed: %s", argv[i], errs[i]);
-		}
-	}
-	exits(Giveup);
-
-	/*
-	 *  here when all rcvrs failed
-	 */
-error:
-	alarm(0);
-	removenewline(s_to_c(reply));
-	if(rcvrs > 0){
-		p = allrx;
-		e = allrx + sizeof allrx;
-		seprint(p, e, "to ");
-		for(i = 0; i < rcvrs - 1; i++)
-			p = seprint(p, e, "%s,", argv[i]);
-		seprint(p, e, "%s ", argv[i]);
-	}
-	syslog(0, "smtp.fail", "%s %s at %s %D %s failed: %s",
-		deliverytype(), allrx, addr, &mx, phase, s_to_c(reply));
-	fprint(2, "%τ connect to %s %D %s:\n%s\n", thedate(&tm), addr, &mx, phase, s_to_c(reply));
-	if(!filter)
-		quit(rv);
-	exits(rv);
-}
-
-/*
- *  connect to the remote host
- */
-static char *
-connect(char* net, Mx *mx)
-{
-	char buf[ERRMAX];
-	int fd;
-
-	fd = mxdial(net, ddomain, gdomain, mx);
-
-	if(fd < 0){
-		rerrstr(buf, sizeof buf);
-		Bprint(&berr, "smtp: %s (%s) %D\n", buf, net, mx);
-		syslog(0, "smtp.fail", "%s %s (%s) %D", deliverytype(), buf, net, mx);
-		if(strstr(buf, "illegal")
-		|| strstr(buf, "unknown")
-		|| strstr(buf, "can't translate"))
-			return Giveup;
-		else
-			return Retry;
-	}
-	Binit(&bin, fd, OREAD);
-	fd = dup(fd, -1);
-	Binit(&bout, fd, OWRITE);
-	return 0;
-}
-
-static char smtpthumbs[] =	"/sys/lib/tls/smtp";
-static char smtpexclthumbs[] =	"/sys/lib/tls/smtp.exclude";
-
-static int
-tracetls(char *fmt, ...)
-{
-	va_list ap;
-	
-	va_start(ap, fmt);
-	Bvprint(&berr, fmt, ap);
-	Bprint(&berr, "\n");
-	Bflush(&berr);
-	va_end(ap);
-	return 0;
-}
-
-static char*
-wraptls(void)
-{
-	TLSconn *c;
-	Thumbprint *goodcerts;
-	char *err;
-	int fd;
-
-	goodcerts = nil;
-	err = Giveup;
-	c = mallocz(sizeof(*c), 1);
-	if (c == nil)
-		return err;
-
-	if (debug)
-		c->trace = tracetls;
-
-	fd = tlsClient(Bfildes(&bout), c);
-	if (fd < 0) {
-		syslog(0, "smtp", "tlsClient to %q: %r", ddomain);
-		goto Out;
-	}
-	Bterm(&bout);
-	Binit(&bout, fd, OWRITE);
-	fd = dup(fd, Bfildes(&bin));
-	Bterm(&bin);
-	Binit(&bin, fd, OREAD);
-
-	if (nocertcheck) {
-		syslog(0, "smtp", "ignoring cert for %s", ddomain);
-		err = nil;
-		goto Out;
-	}
-	goodcerts = initThumbprints(smtpthumbs, smtpexclthumbs, "x509");
-	if (goodcerts == nil) {
-		syslog(0, "smtp", "bad thumbprints in %s", smtpthumbs);
-		goto Out;
-	}
-	if (!okCertificate(c->cert, c->certlen, goodcerts)) {
-		syslog(0, "smtp", "cert for %s not recognized: %r", ddomain);
-		goto Out;
-	}
-	syslog(0, "smtp", "started TLS to %q", ddomain);
-	err = nil;
-Out:
-	freeThumbprints(goodcerts);
-	free(c->cert);
-	free(c->sessionID);
-	free(c);
-	return err;
-}
-
-/*
- *  exchange names with remote host, attempt to
- *  enable encryption and optionally authenticate.
- *  not fatal if we can't.
- */
-static char *
-dotls(char *me)
-{
-	char *err;
-
-	dBprint("STARTTLS\r\n");
-	if (getreply() != 2)
-		return Giveup;
-
-	err = wraptls();
-	if (err != nil)
-		return err;
-
-	return(hello(me, 1));
-}
-
-static char*
-smtpcram(DS *ds)
-{
-	char *p, ch[128], usr[64], rbuf[128], ubuf[128], ebuf[192];
-	int i, n, l;
-
-	dBprint("AUTH CRAM-MD5\r\n");
-	if(getreply() != 3)
-		return Retry;
-	p = s_to_c(reply) + 4;
-	l = dec64((uchar*)ch, sizeof ch, p, strlen(p));
-	ch[l] = 0;
-	n = auth_respond(ch, l, usr, sizeof usr, rbuf, sizeof rbuf, auth_getkey,
-		"proto=cram role=client server=%q user=%q",
-		ds->host, user);
-	if(n == -1){
-		if(temperror())
-			return Retry;
-		syslog(0, "smtp.fail", "failed to get challenge response: %r");
-		return Giveup;
-	}
-	if(usr[0] == 0)
-		return "cannot find user name";
-	for(i = 0; i < n; i++)
-		rbuf[i] = tolower(rbuf[i]);
-	l = snprint(ubuf, sizeof ubuf, "%s %.*s", usr, utfnlen(rbuf, n), rbuf);
-	snprint(ebuf, sizeof ebuf, "%.*[", l, ubuf);
-
-	dBprint("%s\r\n", ebuf);
-	if(getreply() != 2)
-		return Retry;
-	return nil;
-}
-
-static char *
-doauth(char *methods)
-{
-	char buf[1024], *err;
-	UserPasswd *p;
-	DS ds;
-	int n;
-
-	dialstringparse(farend, &ds);
-	if(strstr(methods, "CRAM-MD5"))
-		return smtpcram(&ds);
-	p = auth_getuserpasswd(nil,
-		"proto=pass service=smtp server=%q user=%q",
-		ds.host, user);
-	if (p == nil) {
-		if(temperror())
-			return Retry;
-		syslog(0, "smtp.fail", "failed to get userpasswd: %r");
-		return Giveup;
-	}
-	err = Retry;
-	if (strstr(methods, "LOGIN")){
-		dBprint("AUTH LOGIN\r\n");
-		if (getreply() != 3)
-			goto out;
-
-		dBprint("%.*[\r\n", (int)strlen(p->user), p->user);
-		if (getreply() != 3)
-			goto out;
-
-		dBprint("%.*[\r\n", (int)strlen(p->passwd), p->passwd);
-		if (getreply() != 2)
-			goto out;
-
-		err = nil;
-	}
-	else if (strstr(methods, "PLAIN")){
-		n = snprint(buf, sizeof(buf), "%c%s%c%s", 0, p->user, 0, p->passwd);
-		dBprint("AUTH PLAIN %.*[\r\n", n, buf);
-		memset(buf, 0, sizeof(buf));
-		if (getreply() != 2)
-			goto out;
-		err = nil;
-	} else
-		err = "No supported AUTH method";
-out:
-	memset(p->user, 0, strlen(p->user));
-	memset(p->passwd, 0, strlen(p->passwd));
-	free(p);
-	return err;
-}
-
-char*
-hello(char *me, int encrypted)
-{
-	char *ret, *s, *t;
-	int ehlo;
-	String *r;
-
-	if(!encrypted){
-		if(trysecure > 1){
-			if((ret = wraptls()) != nil)
-				return ret;
-			encrypted = 1;
-		}
-
-		/*
-		 * Verizon fails to print the smtp greeting banner when it
-		 * answers a call.  Send a no-op in the hope of making it
-		 * talk.
-		 */
-		if(autistic){
-			dBprint("NOOP\r\n");
-			getreply();	/* consume the smtp greeting */
-			/* next reply will be response to noop */
-		}
-		switch(getreply()){
-		case 2:
-			break;
-		case 5:
-			return Giveup;
-		default:
-			return Retry;
-		}
-	}
-
-	ehlo = 1;
-  Again:
-	if(ehlo)
-		dBprint("EHLO %s\r\n", me);
-	else
-		dBprint("HELO %s\r\n", me);
-	switch(getreply()){
-	case 2:
-		break;
-	case 5:
-		if(ehlo){
-			ehlo = 0;
-			goto Again;
-		}
-		return Giveup;
-	default:
-		return Retry;
-	}
-	r = s_clone(reply);
-	if(r == nil)
-		return Retry;	/* Out of memory or couldn't get string */
-
-	/* Invariant: every line has a newline, a result of getcrlf() */
-	for(s = s_to_c(r); (t = strchr(s, '\n')) != nil; s = t + 1){
-		*t = '\0';
-		if(!encrypted && trysecure &&
-		    (cistrcmp(s, "250-STARTTLS") == 0 ||
-		     cistrcmp(s, "250 STARTTLS") == 0)){
-			s_free(r);
-			return dotls(me);
-		}
-		if(tryauth && (encrypted || insecure) &&
-		    (cistrncmp(s, "250 AUTH", strlen("250 AUTH")) == 0 ||
-		     cistrncmp(s, "250-AUTH", strlen("250 AUTH")) == 0)){
-			ret = doauth(s + strlen("250 AUTH "));
-			s_free(r);
-			return ret;
-		}
-	}
-	s_free(r);
-	return 0;
-}
-
-/*
- *  report sender to remote
- */
-char *
-mailfrom(char *from)
-{
-	if(!returnable(from))
-		dBprint("MAIL FROM:<>\r\n");
-	else if(strchr(from, '@'))
-		dBprint("MAIL FROM:<%s>\r\n", from);
-	else
-		dBprint("MAIL FROM:<%s@%s>\r\n", from, hostdomain);
-	switch(getreply()){
-	case 2:
-		return 0;
-	case 5:
-		return Giveup;
-	default:
-		return Retry;
-	}
-}
-
-/*
- *  report a recipient to remote
- */
-char *
-rcptto(char *to)
-{
-	String *s;
-
-	s = unescapespecial(bangtoat(to));
-	if(toline == 0)
-		toline = s_new();
-	else
-		s_append(toline, ", ");
-	s_append(toline, s_to_c(s));
-	if(strchr(s_to_c(s), '@'))
-		dBprint("RCPT TO:<%s>\r\n", s_to_c(s));
-	else {
-		s_append(toline, "@");
-		s_append(toline, ddomain);
-		dBprint("RCPT TO:<%s@%s>\r\n", s_to_c(s), ddomain);
-	}
-	alarm(10*alarmscale);
-	switch(getreply()){
-	case 2:
-		break;
-	case 5:
-		return Giveup;
-	default:
-		return Retry;
-	}
-	return 0;
-}
-
-/*
- *  send the damn thing
- */
-char *
-data(String *from, Biobuf *b, Mx *mx)
-{
-	char *buf, *cp, errmsg[ERRMAX];
-	int n, nbytes, bufsize, eof;
-	String *fromline;
-
-	/*
-	 *  input the header.
-	 */
-
-	buf = malloc(1);
-	if(buf == 0){
-		s_append(s_restart(reply), "out of memory");
-		return Retry;
-	}
-	n = 0;
-	eof = 0;
-	for(;;){
-		cp = Brdline(b, '\n');
-		if(cp == nil){
-			eof = 1;
-			break;
-		}
-		nbytes = Blinelen(b);
-		buf = realloc(buf, n + nbytes + 1);
-		if(buf == 0){
-			s_append(s_restart(reply), "out of memory");
-			return Retry;
-		}
-		strncpy(buf + n, cp, nbytes);
-		n += nbytes;
-		if(nbytes == 1)		/* end of header */
-			break;
-	}
-	buf[n] = 0;
-	bufsize = n;
-
-	/*
-	 *  parse the header, turn all addresses into @ format
-	 */
-	yyinit(buf, n);
-	yyparse();
-
-	/*
-	 *  print message observing '.' escapes and using \r\n for \n
-	 */
-	alarm(20*alarmscale);
-	if(!filter){
-		dBprint("DATA\r\n");
-		switch(getreply()){
-		case 3:
-			break;
-		case 5:
-			free(buf);
-			return Giveup;
-		default:
-			free(buf);
-			return Retry;
-		}
-	}
-	/*
-	 *  send header.  add a message-id, a sender, and a date if there
-	 *  isn't one
-	 */
-	nbytes = 0;
-	fromline = convertheader(from);
-	uneaten = buf;
-
-	if(messageid == 0){
-		uchar id[16];
-
-		genrandom(id, sizeof(id));
-		nbytes += dBprint("Message-ID: <%.*H@%s>\r\n",
-			sizeof(id), id, hostdomain);
-	}
-
-	if(originator == 0)
-		nbytes += dBprint("From: %s\r\n", s_to_c(fromline));
-	s_free(fromline);
-
-	if(destination == 0 && toline){
-		if(*s_to_c(toline) == '@')	/* route addr */
-			nbytes += dBprint("To: <%s>\r\n", s_to_c(toline));
-		else
-			nbytes += dBprint("To: %s\r\n", s_to_c(toline));
-	}
-
-	if(date == 0 && udate)
-		nbytes += printdate(udate);
-	if(usys)
-		uneaten = usys->end + 1;
-	nbytes += printheader();
-	if(*uneaten != '\n')
-		putcrnl("\n", 1);
-
-	/*
-	 *  send body
-	 */
-
-	putcrnl(uneaten, buf + n - uneaten);
-	nbytes += buf + n - uneaten;
-	if(eof == 0){
-		for(;;){
-			n = Bread(b, buf, bufsize);
-			if(n < 0){
-				rerrstr(errmsg, sizeof(errmsg));
-				s_append(s_restart(reply), errmsg);
-				free(buf);
-				return Retry;
-			}
-			if(n == 0)
-				break;
-			alarm(10*alarmscale);
-			putcrnl(buf, n);
-			nbytes += n;
-		}
-	}
-	free(buf);
-	if(!filter){
-		if(last != '\n')
-			dBprint("\r\n.\r\n");
-		else
-			dBprint(".\r\n");
-		alarm(10*alarmscale);
-		switch(getreply()){
-		case 2:
-			break;
-		case 5:
-			return Giveup;
-		default:
-			return Retry;
-		}
-		syslog(0, "smtp", "%s sent %d bytes to %s %D", s_to_c(from),
-				nbytes, s_to_c(toline), mx);
-	}
-	return 0;
-}
-
-/*
- *  we're leaving
- */
-void
-quit(char *rv)
-{
-		/* 60 minutes to quit */
-	quitting = 1;
-	quitrv = rv;
-	alarm(60*alarmscale);
-	dBprint("QUIT\r\n");
-	getreply();
-	Bterm(&bout);
-	Bterm(&bfile);
-}
-
-/*
- *  read a reply into a string, return the reply code
- */
-int
-getreply(void)
-{
-	char *line;
-	int rv;
-
-	reply = s_reset(reply);
-	for(;;){
-		line = getcrnl(reply);
-		if(debug)
-			Bflush(&berr);
-		if(line == 0)
-			return -1;
-		if(!isdigit(line[0]) || !isdigit(line[1]) || !isdigit(line[2]))
-			return -1;
-		if(line[3] != '-')
-			break;
-	}
-	if(debug)
-		Bflush(&berr);
-	rv = atoi(line)/100;
-	return rv;
-}
-void
-addhostdom(String *buf, char *host)
-{
-	s_append(buf, "@");
-	s_append(buf, host);
-}
-
-/*
- *	Convert from `bang' to `source routing' format.
- *
- *	   a.x.y!b.p.o!c!d ->	@a.x.y:[email protected]
- */
-String *
-bangtoat(char *addr)
-{
-	char *field[128];
-	int i, j, d;
-	String *buf;
-
-	/* parse the '!' format address */
-	buf = s_new();
-	for(i = 0; addr; i++){
-		field[i] = addr;
-		addr = strchr(addr, '!');
-		if(addr)
-			*addr++ = 0;
-	}
-	if(i == 1){
-		s_append(buf, field[0]);
-		return buf;
-	}
-
-	/*
-	 *  count leading domain fields (non-domains don't count)
-	 */
-	for(d = 0; d < i - 1; d++)
-		if(strchr(field[d], '.') == 0)
-			break;
-	/*
-	 *  if there are more than 1 leading domain elements,
-	 *  put them in as source routing
-	 */
-	if(d > 1){
-		addhostdom(buf, field[0]);
-		for(j = 1; j< d - 1; j++){
-			s_append(buf, ",");
-			s_append(buf, "@");
-			s_append(buf, field[j]);
-		}
-		s_append(buf, ":");
-	}
-
-	/*
-	 *  throw in the non-domain elements separated by '!'s
-	 */
-	s_append(buf, field[d]);
-	for(j = d + 1; j <= i - 1; j++){
-		s_append(buf, "!");
-		s_append(buf, field[j]);
-	}
-	if(d)
-		addhostdom(buf, field[d-1]);
-	return buf;
-}
-
-/*
- *  convert header addresses to @ format.
- *  if the address is a source address, and a domain is specified,
- *  make sure it falls in the domain.
- */
-String*
-convertheader(String *from)
-{
-	char *s, buf[64];
-	Field *f;
-	Node *p, *lastp;
-	String *a;
-
-	if(!returnable(s_to_c(from))){
-		from = s_new();
-		s_append(from, "Postmaster");
-		addhostdom(from, hostdomain);
-	} else
-	if(strchr(s_to_c(from), '@') == 0){
-		if(s = username(s_to_c(from))){
-			/* this has always been here, but username() was broken */
-			snprint(buf, sizeof buf, "%U", s);
-			s_append(a = s_new(), buf);
-			s_append(a, " <");
-			s_append(a, s_to_c(from));
-			addhostdom(a, hostdomain);
-			s_append(a, ">");
-			from = a;
-		} else {
-			from = s_copy(s_to_c(from));
-			addhostdom(from, hostdomain);
-		}
-	} else
-		from = s_copy(s_to_c(from));
-	for(f = firstfield; f; f = f->next){
-		lastp = 0;
-		for(p = f->node; p; lastp = p, p = p->next){
-			if(!p->addr)
-				continue;
-			a = bangtoat(s_to_c(p->s));
-			s_free(p->s);
-			if(strchr(s_to_c(a), '@') == 0)
-				addhostdom(a, hostdomain);
-			else if(*s_to_c(a) == '@')
-				a = fixrouteaddr(a, p->next, lastp);
-			p->s = a;
-		}
-	}
-	return from;
-}
-/*
- *	ensure route addr has brackets around it
- */
-String*
-fixrouteaddr(String *raddr, Node *next, Node *last)
-{
-	String *a;
-
-	if(last && last->c == '<' && next && next->c == '>')
-		return raddr;			/* properly formed already */
-
-	a = s_new();
-	s_append(a, "<");
-	s_append(a, s_to_c(raddr));
-	s_append(a, ">");
-	s_free(raddr);
-	return a;
-}
-
-/*
- *  print out the parsed header
- */
-int
-printheader(void)
-{
-	char *cp, c[1];
-	int n, len;
-	Field *f;
-	Node *p;
-
-	n = 0;
-	for(f = firstfield; f; f = f->next){
-		for(p = f->node; p; p = p->next){
-			if(p->s)
-				n += dBprint("%s", s_to_c(p->s));
-			else {
-				c[0] = p->c;
-				putcrnl(c, 1);
-				n++;
-			}
-			if(p->white){
-				cp = s_to_c(p->white);
-				len = strlen(cp);
-				putcrnl(cp, len);
-				n += len;
-			}
-			uneaten = p->end;
-		}
-		putcrnl("\n", 1);
-		n++;
-		uneaten++;		/* skip newline */
-	}
-	return n;
-}
-
-/*
- *  add a domain onto an name, return the new name
- */
-char *
-domainify(char *name, char *domain)
-{
-	char *p;
-	static String *s;
-
-	if(domain == 0 || strchr(name, '.') != 0)
-		return name;
-
-	s = s_reset(s);
-	s_append(s, name);
-	p = strchr(domain, '.');
-	if(p == 0){
-		s_append(s, ".");
-		p = domain;
-	}
-	s_append(s, p);
-	return s_to_c(s);
-}
-
-/*
- *  print message observing '.' escapes and using \r\n for \n
- */
-void
-putcrnl(char *cp, int n)
-{
-	int c;
-
-	for(; n; n--, cp++){
-		c = *cp;
-		if(c == '\n')
-			dBputc('\r');
-		else if(c == '.' && last=='\n')
-			dBputc('.');
-		dBputc(c);
-		last = c;
-	}
-}
-
-/*
- *  Get a line including a crnl into a string.  Convert crnl into nl.
- */
-char *
-getcrnl(String *s)
-{
-	int c, count;
-
-	count = 0;
-	for(;;){
-		c = Bgetc(&bin);
-		if(debug)
-			Bputc(&berr, c);
-		switch(c){
-		case -1:
-			s_append(s, "connection closed unexpectedly by remote system");
-			s_terminate(s);
-			return 0;
-		case '\r':
-			c = Bgetc(&bin);
-			if(c == '\n'){
-		case '\n':
-				s_putc(s, c);
-				if(debug)
-					Bputc(&berr, c);
-				count++;
-				s_terminate(s);
-				return s->ptr - count;
-			}
-			Bungetc(&bin);
-			s_putc(s, '\r');
-			if(debug)
-				Bputc(&berr, '\r');
-			count++;
-			break;
-		default:
-			s_putc(s, c);
-			count++;
-			break;
-		}
-	}
-}
-
-/*
- *  print out a parsed date
- */
-int
-printdate(Node *p)
-{
-	int n, sep;
-
-	n = dBprint("Date: %s,", s_to_c(p->s));
-	sep = 0;
-	for(p = p->next; p; p = p->next){
-		if(p->s){
-			if(sep == 0){
-				dBputc(' ');
-				n++;
-			}
-			if(p->next)
-				n += dBprint("%s", s_to_c(p->s));
-			else
-				n += dBprint("%s", rewritezone(s_to_c(p->s)));
-			sep = 0;
-		} else {
-			dBputc(p->c);
-			n++;
-			sep = 1;
-		}
-	}
-	n += dBprint("\r\n");
-	return n;
-}
-
-char *
-rewritezone(char *z)
-{
-	char s;
-	int mindiff;
-	Tm *tm;
-	static char x[7];
-
-	tm = localtime(time(0));
-	mindiff = tm->tzoff/60;
-
-	/* if not in my timezone, don't change anything */
-	if(strcmp(tm->zone, z) != 0)
-		return z;
-
-	if(mindiff < 0){
-		s = '-';
-		mindiff = -mindiff;
-	} else
-		s = '+';
-
-	sprint(x, "%c%.2d%.2d", s, mindiff/60, mindiff%60);
-	return x;
-}
-
-/*
- *  stolen from libc/port/print.c
- */
-
-int
-dBprint(char *fmt, ...)
-{
-	char buf[4096], *out;
-	int n;
-	va_list arg;
-
-	va_start(arg, fmt);
-	out = vseprint(buf, buf + sizeof buf, fmt, arg);
-	va_end(arg);
-	if(debug){
-		Bwrite(&berr, buf, out - buf);
-		Bflush(&berr);
-	}
-	n = Bwrite(&bout, buf,out - buf);
-	Bflush(&bout);
-	return n;
-}
-
-int
-dBputc(int x)
-{
-	if(debug)
-		Bputc(&berr, x);
-	return Bputc(&bout, x);
-}