#include "common.h"
#include <thread.h>
#include <9pclient.h>
#include <ctype.h>

enum
{
	STACK = 32768
};

#define inline _inline

typedef struct Attach Attach;
typedef struct Alias Alias;
typedef struct Addr Addr;
typedef struct Ctype Ctype;

struct Attach {
	Attach	*next;
	char	*path;
	int	fd;
	char	*type;
	int	inline;
	Ctype	*ctype;
};

struct Alias
{
	Alias	*next;
	int	n;
	Addr	*addr;
};

struct Addr
{
	Addr	*next;
	char	*v;
};

enum {
	Hfrom,
	Hto,
	Hcc,
	Hbcc,
	Hsender,
	Hreplyto,
	Hinreplyto,
	Hdate,
	Hsubject,
	Hmime,
	Hpriority,
	Hmsgid,
	Hcontent,
	Hx,
	Hprecedence,
	Nhdr
};

enum {
	PGPsign = 1,
	PGPencrypt = 2
};

char *hdrs[Nhdr] = {
[Hfrom]		"from:",
[Hto]		"to:",
[Hcc]		"cc:",
[Hbcc]		"bcc:",
[Hreplyto]	"reply-to:",
[Hinreplyto]	"in-reply-to:",
[Hsender]	"sender:",
[Hdate]		"date:",
[Hsubject]	"subject:",
[Hpriority]	"priority:",
[Hmsgid]	"message-id:",
[Hmime]		"mime-",
[Hcontent]	"content-",
[Hx]		"x-",
[Hprecedence]	"precedence"
};

struct Ctype {
	char	*type;
	char 	*ext;
	int	display;
};

Ctype ctype[] = {
	{ "text/plain",			"txt",	1,	},
	{ "text/html",			"html",	1,	},
	{ "text/html",			"htm",	1,	},
	{ "text/tab-separated-values",	"tsv",	1,	},
	{ "text/richtext",		"rtx",	1,	},
	{ "message/rfc822",		"txt",	1,	},
	{ "", 				0,	0,	}
};

Ctype *mimetypes;

int pid = -1;
int pgppid = -1;

Attach*	mkattach(char*, char*, int);
int	readheaders(Biobuf*, int*, String**, Addr**, int);
void	body(Biobuf*, Biobuf*, int);
char*	mkboundary(void);
int	printdate(Biobuf*);
int	printfrom(Biobuf*);
int	printto(Biobuf*, Addr*);
int	printcc(Biobuf*, Addr*);
int	printsubject(Biobuf*, char*);
int	printinreplyto(Biobuf*, char*);
int	sendmail(Addr*, Addr*, int*, char*);
void	attachment(Attach*, Biobuf*);
int	cistrncmp(char*, char*, int);
int	cistrcmp(char*, char*);
char*	waitforsubprocs(void);
int	enc64(char*, int, uchar*, int);
Addr*	expand(int, char**);
Alias*	readaliases(void);
Addr*	expandline(String**, Addr*);
void	Bdrain(Biobuf*);
void	freeaddr(Addr *);
int	pgpopts(char*);
int	pgpfilter(int*, int, int);
void	readmimetypes(void);
char*	estrdup(char*);
void*	emalloc(int);
void*	erealloc(void*, int);
void	freeaddr(Addr*);
void	freeaddrs(Addr*);
void	freealias(Alias*);
void	freealiases(Alias*);
int	doublequote(Fmt*);
int	mountmail(void);
int	nprocexec;
int	rfc2047fmt(Fmt*);
char*	mksubject(char*);

int rflag, lbflag, xflag, holding, nflag, Fflag, eightflag, dflag;
int pgpflag = 0;
char *user;
char *login;
Alias *aliases;
int rfc822syntaxerror;
char lastchar;
char *replymsg;

CFsys *mailfs;

enum
{
	Ok = 0,
	Nomessage = 1,
	Nobody = 2,
	Error = -1
};

#pragma varargck	type	"Z"	char*

void
usage(void)
{
	fprint(2, "usage: %s [-Fr#xn] [-s subject] [-c ccrecipient] [-t type] [-aA attachment] [-p[es]] [-R replymsg] -8 | recipient-list\n",
		argv0);
	threadexitsall("usage");
}

void
fatal(char *fmt, ...)
{
	char buf[1024];
	va_list arg;

	if(pid >= 0)
		postnote(PNPROC, pid, "die");
	if(pgppid >= 0)
		postnote(PNPROC, pgppid, "die");

	va_start(arg, fmt);
	vseprint(buf, buf+sizeof(buf), fmt, arg);
	va_end(arg);
	fprint(2, "%s: %s\n", argv0, buf);
	holdoff(holding);
	threadexitsall(buf);
}

void
threadmain(int argc, char **argv)
{
	Attach *first, **l, *a;
	char *subject, *type, *boundary;
	int flags, fd;
	Biobuf in, out, *b;
	Addr *to;
	Addr *cc;
	String *file, *hdrstring;
	int noinput, headersrv;
	int ccargc;
	char *ccargv[32];

	noinput = 0;
	subject = nil;
	first = nil;
	l = &first;
	type = nil;
	hdrstring = nil;
	ccargc = 0;

	quotefmtinstall();
	fmtinstall('Z', doublequote);
	fmtinstall('U', rfc2047fmt);
	threadwaitchan();

	ARGBEGIN{
	case 't':
		type = EARGF(usage());
		break;
	case 'a':
		flags = 0;
		goto aflag;
	case 'A':
		flags = 1;
	aflag:
		a = mkattach(EARGF(usage()), type, flags);
		if(a == nil)
			threadexitsall("bad args");
		type = nil;
		*l = a;
		l = &a->next;
		break;
	case 'C':
		if(ccargc >= nelem(ccargv)-1)
			sysfatal("too many cc's");
		ccargv[ccargc] = ARGF();
		if(ccargv[ccargc] == nil)
			usage();
		ccargc++;
		break;
	case 'R':
		replymsg = EARGF(usage());
		break;
	case 's':
		subject = EARGF(usage());
		break;
	case 'F':
		Fflag = 1;		/* file message */
		break;
	case 'r':
		rflag = 1;		/* for sendmail */
		break;
	case 'd':
		dflag = 1;		/* for sendmail */
		break;
	case '#':
		lbflag = 1;		/* for sendmail */
		break;
	case 'x':
		xflag = 1;		/* for sendmail */
		break;
	case 'n':			/* no standard input */
		nflag = 1;
		break;
	case '8':			/* read recipients from rfc822 header */
		eightflag = 1;
		break;
	case 'p':			/* pgp flag: encrypt, sign, or both */
		if(pgpopts(EARGF(usage())) < 0)
			sysfatal("bad pgp options");
		break;
	default:
		usage();
		break;
	}ARGEND;

	login = getlog();
	user = getenv("upasname");
	if(user == nil || *user == 0)
		user = login;
	if(user == nil || *user == 0)
		sysfatal("can't read user name");

	if(Binit(&in, 0, OREAD) < 0)
		sysfatal("can't Binit 0: %r");

	if(nflag && eightflag)
		sysfatal("can't use both -n and -8");
	if(eightflag && argc >= 1)
		usage();
	else if(!eightflag && argc < 1)
		usage();

	aliases = readaliases();
	if(!eightflag){
		to = expand(argc, argv);
		cc = expand(ccargc, ccargv);
	} else {
		to = nil;
		cc = nil;
	}

	flags = 0;
	headersrv = Nomessage;
	if(!nflag && !xflag && !lbflag &&!dflag) {
		/* pass through headers, keeping track of which we've seen, */
		/* perhaps building to list. */
		holding = holdon();
		headersrv = readheaders(&in, &flags, &hdrstring, eightflag ? &to : nil, 1);
		if(rfc822syntaxerror){
			Bdrain(&in);
			fatal("rfc822 syntax error, message not sent");
		}
		if(to == nil){
			Bdrain(&in);
			fatal("no addresses found, message not sent");
		}

		switch(headersrv){
		case Error:		/* error */
			fatal("reading");
			break;
		case Nomessage:		/* no message, just exit mimicking old behavior */
			noinput = 1;
			if(first == nil)
				threadexitsall(0);
			break;
		}
	}

	fd = sendmail(to, cc, &pid, Fflag ? argv[0] : nil);
	if(fd < 0)
		sysfatal("execing sendmail: %r\n:");
	if(xflag || lbflag || dflag){
		close(fd);
		threadexitsall(waitforsubprocs());
	}
	
	if(Binit(&out, fd, OWRITE) < 0)
		fatal("can't Binit 1: %r");

	if(!nflag){
		if(Bwrite(&out, s_to_c(hdrstring), s_len(hdrstring)) != s_len(hdrstring))
			fatal("write error");
		s_free(hdrstring);
		hdrstring = nil;

		/* read user's standard headers */
		file = s_new();
		mboxpath("headers", user, file, 0);
		b = Bopen(s_to_c(file), OREAD);
		if(b != nil){
			switch(readheaders(b, &flags, &hdrstring, nil, 0)){
			case Error:	/* error */
				fatal("reading");
			}
			Bterm(b);
			if(Bwrite(&out, s_to_c(hdrstring), s_len(hdrstring)) != s_len(hdrstring))
				fatal("write error");
			s_free(hdrstring);
			hdrstring = nil;
		}
	}

	/* add any headers we need */
	if((flags & (1<<Hdate)) == 0)
		if(printdate(&out) < 0)
			fatal("writing");
	if((flags & (1<<Hfrom)) == 0)
		if(printfrom(&out) < 0)
			fatal("writing");
	if((flags & (1<<Hto)) == 0)
		if(printto(&out, to) < 0)
			fatal("writing");
	if((flags & (1<<Hcc)) == 0)
		if(printcc(&out, cc) < 0)
			fatal("writing");
	if((flags & (1<<Hsubject)) == 0 && subject != nil)
		if(printsubject(&out, subject) < 0)
			fatal("writing");
	if(replymsg != nil)
		printinreplyto(&out, replymsg);	/* ignore errors */
	Bprint(&out, "MIME-Version: 1.0\n");

	if(pgpflag){	/* interpose pgp process between us and sendmail to handle body */
		Bflush(&out);
		Bterm(&out);
		fd = pgpfilter(&pgppid, fd, pgpflag);
		if(Binit(&out, fd, OWRITE) < 0)
			fatal("can't Binit 1: %r");
	}

	/* if attachments, stick in multipart headers */
	boundary = nil;
	if(first != nil){
		boundary = mkboundary();
		Bprint(&out, "Content-Type: multipart/mixed;\n");
		Bprint(&out, "\tboundary=\"%s\"\n\n", boundary);
		Bprint(&out, "This is a multi-part message in MIME format.\n");
		Bprint(&out, "--%s\n", boundary);
		Bprint(&out, "Content-Disposition: inline\n");
	}

	if(!nflag){
		if(!noinput && headersrv == Ok){
			body(&in, &out, 1);
		}
	} else
		Bprint(&out, "\n");
	holdoff(holding);

	Bflush(&out);
	for(a = first; a != nil; a = a->next){
		if(lastchar != '\n')
			Bprint(&out, "\n");
		Bprint(&out, "--%s\n", boundary);
		attachment(a, &out);
	}

	if(first != nil){
		if(lastchar != '\n')
			Bprint(&out, "\n");
		Bprint(&out, "--%s--\n", boundary);
	}

	Bterm(&out);
	close(fd);
	threadexitsall(waitforsubprocs());
}

/* evaluate pgp option string */
int
pgpopts(char *s)
{
	if(s == nil || s[0] == '\0')
		return -1;
	while(*s){
		switch(*s++){
		case 's':  case 'S':
			pgpflag |= PGPsign;
			break;
		case 'e': case 'E':
			pgpflag |= PGPencrypt;
			break;
		default:
			return -1;
		}
	}
	return 0;
}

/* read headers from stdin into a String, expanding local aliases, */
/* keep track of which headers are there, which addresses we have */
/* remove Bcc: line. */
int
readheaders(Biobuf *in, int *fp, String **sp, Addr **top, int strict)
{
	Addr *to;
	String *s, *sline;
	char *p;
	int i, seen, hdrtype;

	s = s_new();
	sline = nil;
	to = nil;
	hdrtype = -1;
	seen = 0;
	for(;;) {
		if((p = Brdline(in, '\n')) != nil) {
			seen = 1;
			p[Blinelen(in)-1] = 0;

			/* coalesce multiline headers */
			if((*p == ' ' || *p == '\t') && sline){
				s_append(sline, "\n");
				s_append(sline, p);
				p[Blinelen(in)-1] = '\n';
				continue;
			}
		}

		/* process the current header, it's all been read */
		if(sline) {
			assert(hdrtype != -1);
			if(top){
				switch(hdrtype){
				case Hto:
				case Hcc:
				case Hbcc:
					to = expandline(&sline, to);
					break;
				}
			}
			if(hdrtype == Hsubject){
				s_append(s, mksubject(s_to_c(sline)));
				s_append(s, "\n");
			}else if(top==nil || hdrtype!=Hbcc){
				s_append(s, s_to_c(sline));
				s_append(s, "\n");
			}
			s_free(sline);
			sline = nil;
		}

		if(p == nil)
			break;

		/* if no :, it's not a header, seek back and break */
		if(strchr(p, ':') == nil){
			p[Blinelen(in)-1] = '\n';
			Bseek(in, -Blinelen(in), 1);
			break;
		}

		sline = s_copy(p);

		/* classify the header.  If we don't recognize it, break.  This is */
		/* to take care of user's that start messages with lines that contain */
		/* ':'s but that aren't headers.  This is a bit hokey.  Since I decided */
		/* to let users type headers, I need some way to distinguish.  Therefore, */
		/* marshal tries to know all likely headers and will indeed screw up if */
		/* the user types an unlikely one. -- presotto */
		hdrtype = -1;
		for(i = 0; i < nelem(hdrs); i++){
			if(cistrncmp(hdrs[i], p, strlen(hdrs[i])) == 0){
				*fp |= 1<<i;
				hdrtype = i;
				break;
			}
		}
		if(strict){
			if(hdrtype == -1){
				p[Blinelen(in)-1] = '\n';
				Bseek(in, -Blinelen(in), 1);
				break;
			}
		} else
			hdrtype = 0;
		p[Blinelen(in)-1] = '\n';
	}

	*sp = s;
	if(top)
		*top = to;

	if(seen == 0){
		if(Blinelen(in) == 0)
			return Nomessage;
		else
			return Ok;
	}
	if(p == nil)
		return Nobody;
	return Ok;
}

/* pass the body to sendmail, make sure body starts and ends with a newline */
void
body(Biobuf *in, Biobuf *out, int docontenttype)
{
	char *buf, *p;
	int i, n, len;

	n = 0;
	len = 16*1024;
	buf = emalloc(len);

	/* first char must be newline */
	i = Bgetc(in);
	if(i > 0){
		if(i != '\n')
			buf[n++] = '\n';
		buf[n++] = i;
	} else {
		buf[n++] = '\n';
	}

	/* read into memory */
	if(docontenttype){
		while(docontenttype){
			if(n == len){
				len += len>>2;
				buf = realloc(buf, len);
				if(buf == nil)
					sysfatal("%r");
			}
			p = buf+n;
			i = Bread(in, p, len - n);
			if(i < 0)
				fatal("input error2");
			if(i == 0)
				break;
			n += i;
			for(; i > 0; i--)
				if((*p++ & 0x80) && docontenttype){
					Bprint(out, "Content-Type: text/plain; charset=\"UTF-8\"\n");
					Bprint(out, "Content-Transfer-Encoding: 8bit\n");
					docontenttype = 0;
					break;
				}
		}
		if(docontenttype){
			Bprint(out, "Content-Type: text/plain; charset=\"US-ASCII\"\n");
			Bprint(out, "Content-Transfer-Encoding: 7bit\n");
		}
	}

	/* write what we already read */
	if(Bwrite(out, buf, n) < 0)
		fatal("output error");
	if(n > 0)
		lastchar = buf[n-1];
	else
		lastchar = '\n';


	/* pass the rest */
	for(;;){
		n = Bread(in, buf, len);
		if(n < 0)
			fatal("input error2");
		if(n == 0)
			break;
		if(Bwrite(out, buf, n) < 0)
			fatal("output error");
		lastchar = buf[n-1];
	}
}

/* pass the body to sendmail encoding with base64 */
/* */
/*  the size of buf is very important to enc64.  Anything other than */
/*  a multiple of 3 will cause enc64 to output a termination sequence. */
/*  To ensure that a full buf corresponds to a multiple of complete lines, */
/*  we make buf a multiple of 3*18 since that's how many enc64 sticks on */
/*  a single line.  This avoids short lines in the output which is pleasing */
/*  but not necessary. */
/* */
void
body64(Biobuf *in, Biobuf *out)
{
	uchar buf[3*18*54];
	char obuf[3*18*54*2];
	int m, n;

	Bprint(out, "\n");
	for(;;){
		n = Bread(in, buf, sizeof(buf));
		if(n < 0)
			fatal("input error");
		if(n == 0)
			break;
		m = enc64(obuf, sizeof(obuf), buf, n);
		if((n=Bwrite(out, obuf, m)) < 0)
			fatal("output error");
	}
	lastchar = '\n';
}

/* pass message to sendmail, make sure body starts with a newline */
void
copy(Biobuf *in, Biobuf *out)
{
	char buf[4*1024];
	int n;

	for(;;){
		n = Bread(in, buf, sizeof(buf));
		if(n < 0)
			fatal("input error");
		if(n == 0)
			break;
		if(Bwrite(out, buf, n) < 0)
			fatal("output error");
	}
}

void
attachment(Attach *a, Biobuf *out)
{
	Biobuf *f;
	char *p;

	f = emalloc(sizeof *f);
	Binit(f, a->fd, OREAD);
	/* if it's already mime encoded, just copy */
	if(strcmp(a->type, "mime") == 0){
		copy(f, out);
		Bterm(f);
		free(f);
		return;
	}
	
	/* if it's not already mime encoded ... */
	if(strcmp(a->type, "text/plain") != 0)
		Bprint(out, "Content-Type: %s\n", a->type);

	if(a->inline){
		Bprint(out, "Content-Disposition: inline\n");
	} else {
		p = strrchr(a->path, '/');
		if(p == nil)
			p = a->path;
		else
			p++;
		Bprint(out, "Content-Disposition: attachment; filename=%Z\n", p);
	}

	/* dump our local 'From ' line when passing along mail messages */
	if(strcmp(a->type, "message/rfc822") == 0){
		p = Brdline(f, '\n');
		if(strncmp(p, "From ", 5) != 0)
			Bseek(f, 0, 0);
	}
	if(a->ctype->display){
		body(f, out, strcmp(a->type, "text/plain") == 0);
	} else {
		Bprint(out, "Content-Transfer-Encoding: base64\n");
		body64(f, out);
	}
	Bterm(f);
	free(f);
}

char *ascwday[] =
{
	"Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"
};

char *ascmon[] =
{
	"Jan", "Feb", "Mar", "Apr", "May", "Jun",
	"Jul", "Aug", "Sep", "Oct", "Nov", "Dec"
};

int
printdate(Biobuf *b)
{
	Tm *tm;
	int tz;

	tm = localtime(time(0));
	tz = (tm->tzoff/3600)*100 + ((tm->tzoff/60)%60);

	return Bprint(b, "Date: %s, %d %s %d %2.2d:%2.2d:%2.2d %s%.4d\n",
		ascwday[tm->wday], tm->mday, ascmon[tm->mon], 1900+tm->year,
		tm->hour, tm->min, tm->sec, tz>=0?"+":"", tz);
}

int
printfrom(Biobuf *b)
{
	return Bprint(b, "From: %s\n", user);
}

int
printto(Biobuf *b, Addr *a)
{
	int i;

	if(Bprint(b, "To: %s", a->v) < 0)
		return -1;
	i = 0;
	for(a = a->next; a != nil; a = a->next)
		if(Bprint(b, "%s%s", ((i++ & 7) == 7)?",\n\t":", ", a->v) < 0)
			return -1;
	if(Bprint(b, "\n") < 0)
		return -1;
	return 0;
}

int
printcc(Biobuf *b, Addr *a)
{
	int i;

	if(a == nil)
		return 0;
	if(Bprint(b, "CC: %s", a->v) < 0)
		return -1;
	i = 0;
	for(a = a->next; a != nil; a = a->next)
		if(Bprint(b, "%s%s", ((i++ & 7) == 7)?",\n\t":", ", a->v) < 0)
			return -1;
	if(Bprint(b, "\n") < 0)
		return -1;
	return 0;
}

int
printsubject(Biobuf *b, char *subject)
{
	return Bprint(b, "Subject: %s\n", subject);
}

int
printinreplyto(Biobuf *out, char *dir)
{
	String *s;
	char buf[256];
	int fd;
	int n;

	if(mountmail() < 0)
		return -1;
	if(strncmp(dir, "Mail/", 5) != 0)
		return -1;
	s = s_copy(dir+5);
	s_append(s, "/messageid");
	fd = fsopenfd(mailfs, s_to_c(s), OREAD);
	s_free(s);
	if(fd < 0)
		return -1;
	n = readn(fd, buf, sizeof(buf)-1);
	close(fd);
	if(n <= 0)
		return -1;
	buf[n] = 0;
	return Bprint(out, "In-Reply-To: %s\n", buf);
}

int
mopen(char *file, int mode)
{
	int fd;
	
	if((fd = open(file, mode)) >= 0)
		return fd;
	if(strncmp(file, "Mail/", 5) == 0 && mountmail() >= 0 && (fd = fsopenfd(mailfs, file+5, mode)) >= 0)
		return fd;
	return -1;
}

Attach*
mkattach(char *file, char *type, int inline)
{
	Ctype *c;
	Attach *a;
	char ftype[64];
	char *p;
	int fd, n, pfd[2], xfd[3];

	if(file == nil)
		return nil;
	if((fd = mopen(file, OREAD)) < 0)
		return nil;
	a = emalloc(sizeof(*a));
	a->fd = fd;
	a->path = file;
	a->next = nil;
	a->type = type;
	a->inline = inline;
	a->ctype = nil;
	if(type != nil){
		for(c = ctype; ; c++)
			if(strncmp(type, c->type, strlen(c->type)) == 0){
				a->ctype = c;
				break;
			}
		return a;
	}

	/* pick a type depending on extension */
	p = strchr(file, '.');
	if(p != nil)
		p++;

	/* check the builtin extensions */
	if(p != nil){
		for(c = ctype; c->ext != nil; c++)
			if(strcmp(p, c->ext) == 0){
				a->type = c->type;
				a->ctype = c;
				return a;
			}
	}

	/* try the mime types file */
	if(p != nil){
		if(mimetypes == nil)
			readmimetypes();
		for(c = mimetypes; c != nil && c->ext != nil; c++)
			if(strcmp(p, c->ext) == 0){
				a->type = c->type;
				a->ctype = c;
				return a;
			}
	}

	/* run file to figure out the type */
	a->type = "application/octet-stream";		/* safest default */
	if(pipe(pfd) < 0)
		return a;
	
	xfd[0] = mopen(file, OREAD);
	xfd[1] = pfd[0];
	xfd[2] = dup(2, -1);
	if((pid=threadspawnl(xfd, unsharp("#9/bin/file"), "file", "-m", nil)) < 0){
		close(xfd[0]);
		close(xfd[1]);
		close(xfd[2]);
		return a;
	}
	/* threadspawnl closed pfd[0] */

	n = readn(pfd[1], ftype, sizeof(ftype));
	if(n > 0){
		ftype[n-1] = 0;
		a->type = estrdup(ftype);
	}
	close(pfd[1]);
	procwait(pid);

	for(c = ctype; ; c++)
		if(strncmp(a->type, c->type, strlen(c->type)) == 0){
			a->ctype = c;
			break;
		}

	return a;
}

char*
mkboundary(void)
{
	char buf[32];
	int i;

	srand((time(0)<<16)|getpid());
	strcpy(buf, "upas-");
	for(i = 5; i < sizeof(buf)-1; i++)
		buf[i] = 'a' + nrand(26);
	buf[i] = 0;
	return estrdup(buf);
}

/* copy types to two fd's */
static void
tee(int in, int out1, int out2)
{
	char buf[8*1024];
	int n;

	for(;;){
		n = read(in, buf, sizeof(buf));
		if(n <= 0)
			break;
		if(write(out1, buf, n) < 0)
			break;
		if(write(out2, buf, n) < 0)
			break;
	}
}

static void
teeproc(void *v)
{
	int *a;
	
	a = v;
	tee(a[0], a[1], a[2]);
	write(a[2], "\n", 1);
}

/* print the unix from line */
int
printunixfrom(int fd)
{
	Tm *tm;
	int tz;

	tm = localtime(time(0));
	tz = (tm->tzoff/3600)*100 + ((tm->tzoff/60)%60);

	return fprint(fd, "From %s %s %s %d %2.2d:%2.2d:%2.2d %s%.4d %d\n",
		user,
		ascwday[tm->wday], ascmon[tm->mon], tm->mday,
		tm->hour, tm->min, tm->sec, tz>=0?"+":"", tz, 1900+tm->year);
}

char *specialfile[] =
{
	"pipeto",
	"pipefrom",
	"L.mbox",
	"forward",
	"names"
};

/* return 1 if this is a special file */
static int
special(String *s)
{
	char *p;
	int i;

	p = strrchr(s_to_c(s), '/');
	if(p == nil)
		p = s_to_c(s);
	else
		p++;
	for(i = 0; i < nelem(specialfile); i++)
		if(strcmp(p, specialfile[i]) == 0)
			return 1;
	return 0;
}

/* open the folder using the recipients account name */
static int
openfolder(char *rcvr)
{
	char *p;
	int c;
	String *file;
	Dir *d;
	int fd;
	int scarey;

	file = s_new();
	mboxpath("f", user, file, 0);

	/* if $mail/f exists, store there, otherwise in $mail */
	d = dirstat(s_to_c(file));
	if(d == nil || d->qid.type != QTDIR){
		scarey = 1;
		file->ptr -= 1;
	} else {
		s_putc(file, '/');
		scarey = 0;
	}
	free(d);

	p = strrchr(rcvr, '!');
	if(p != nil)
		rcvr = p+1;

	while(*rcvr && *rcvr != '@'){
		c = *rcvr++;
		if(c == '/')
			c = '_';
		s_putc(file, c);
	}
	s_terminate(file);

	if(scarey && special(file)){
		fprint(2, "%s: won't overwrite %s\n", argv0, s_to_c(file));
		s_free(file);
		return -1;
	}

	fd = open(s_to_c(file), OWRITE);
	if(fd < 0)
		fd = create(s_to_c(file), OWRITE, 0660);

	s_free(file);
	return fd;
}

/* start up sendmail and return an fd to talk to it with */
int
sendmail(Addr *to, Addr *cc, int *pid, char *rcvr)
{
	char **av, **v;
	int ac, fd, *targ;
	int pfd[2], sfd, xfd[3];
	String *cmd;
	char *x;
	Addr *a;

	fd = -1;
	if(rcvr != nil)
		fd = openfolder(rcvr);

	ac = 0;
	for(a = to; a != nil; a = a->next)
		ac++;
	for(a = cc; a != nil; a = a->next)
		ac++;
	v = av = emalloc(sizeof(char*)*(ac+20));
	ac = 0;
	v[ac++] = "sendmail";
	if(xflag)
		v[ac++] = "-x";
	if(rflag)
		v[ac++] = "-r";
	if(lbflag)
		v[ac++] = "-#";
	if(dflag)
		v[ac++] = "-d";
	for(a = to; a != nil; a = a->next)
		v[ac++] = a->v;
	for(a = cc; a != nil; a = a->next)
		v[ac++] = a->v;
	v[ac] = 0;

	if(pipe(pfd) < 0)
		fatal("pipe: %r");
	
	xfd[0] = pfd[0];
	xfd[1] = dup(1, -1);
	xfd[2] = dup(2, -1);

	if(replymsg != nil)
		putenv("replymsg", replymsg);
	cmd = mboxpath("pipefrom", login, s_new(), 0);

	if((*pid = threadspawn(xfd, x=s_to_c(cmd), av)) < 0
	&& (*pid = threadspawn(xfd, x="myupassend", av)) < 0
	&& (*pid = threadspawn(xfd, x=unsharp("#9/bin/upas/send"), av)) < 0)
		fatal("exec: %r");
	/* threadspawn closed pfd[0] (== xfd[0]) */
	sfd = pfd[1];

	if(rcvr != nil){
		if(pipe(pfd) < 0)
			fatal("pipe: %r");
		seek(fd, 0, 2);
		printunixfrom(fd);
		targ = emalloc(3*sizeof targ[0]);
		targ[0] = sfd;
		targ[1] = pfd[0];
		targ[2] = fd;
		proccreate(teeproc, targ, STACK);
		sfd = pfd[1];
	}
	
	return sfd;
}

/* start up pgp process and return an fd to talk to it with. */
/* its standard output will be the original fd, which goes to sendmail. */
int
pgpfilter(int *pid, int fd, int pgpflag)
{
	char **av, **v;
	int ac;
	int pfd[2];

	v = av = emalloc(sizeof(char*)*8);
	ac = 0;
	v[ac++] = "pgp";
	v[ac++] = "-fat";		/* operate as a filter, generate text */
	if(pgpflag & PGPsign)
		v[ac++] = "-s";
	if(pgpflag & PGPencrypt)
		v[ac++] = "-e";
	v[ac] = 0;

	if(pipe(pfd) < 0)
		fatal("%r");
	switch(*pid = fork()){
	case -1:
		fatal("%r");
		break;
	case 0:
		close(pfd[1]);
		dup(pfd[0], 0);
		close(pfd[0]);
		dup(fd, 1);
		close(fd);
		/* add newline to avoid confusing pgp output with 822 headers */
		write(1, "\n", 1);

		exec("pgp", av);
		fatal("execing: %r");
		break;
	default:
		close(pfd[0]);
		break;
	}
	close(fd);
	return pfd[1];
}

/* wait for sendmail and pgp to exit; exit here if either failed */
char*
waitforsubprocs(void)
{
	Waitmsg *w;
	char *err;

	err = nil;
	if(pgppid >= 0 && (w=procwait(pgppid)) && w->msg[0])
		err = w->msg;
	if(pid >= 0 && (w=procwait(pid)) && w->msg[0])
		err = w->msg;
	return nil;
}

int
cistrncmp(char *a, char *b, int n)
{
	while(n-- > 0){
		if(tolower(*a++) != tolower(*b++))
			return -1;
	}
	return 0;
}

int
cistrcmp(char *a, char *b)
{
	for(;;){
		if(tolower(*a) != tolower(*b++))
			return -1;
		if(*a++ == 0)
			break;
	}
	return 0;
}

static uchar t64d[256];
static char t64e[64];

static void
init64(void)
{
	int c, i;

	memset(t64d, 255, 256);
	memset(t64e, '=', 64);
	i = 0;
	for(c = 'A'; c <= 'Z'; c++){
		t64e[i] = c;
		t64d[c] = i++;
	}
	for(c = 'a'; c <= 'z'; c++){
		t64e[i] = c;
		t64d[c] = i++;
	}
	for(c = '0'; c <= '9'; c++){
		t64e[i] = c;
		t64d[c] = i++;
	}
	t64e[i] = '+';
	t64d['+'] = i++;
	t64e[i] = '/';
	t64d['/'] = i;
}

int
enc64(char *out, int lim, uchar *in, int n)
{
	int i;
	ulong b24;
	char *start = out;
	char *e = out + lim;

	if(t64e[0] == 0)
		init64();
	for(i = 0; i < n/3; i++){
		b24 = (*in++)<<16;
		b24 |= (*in++)<<8;
		b24 |= *in++;
		if(out + 5 >= e)
			goto exhausted;
		*out++ = t64e[(b24>>18)];
		*out++ = t64e[(b24>>12)&0x3f];
		*out++ = t64e[(b24>>6)&0x3f];
		*out++ = t64e[(b24)&0x3f];
		if((i%18) == 17)
			*out++ = '\n';
	}

	switch(n%3){
	case 2:
		b24 = (*in++)<<16;
		b24 |= (*in)<<8;
		if(out + 4 >= e)
			goto exhausted;
		*out++ = t64e[(b24>>18)];
		*out++ = t64e[(b24>>12)&0x3f];
		*out++ = t64e[(b24>>6)&0x3f];
		break;
	case 1:
		b24 = (*in)<<16;
		if(out + 4 >= e)
			goto exhausted;
		*out++ = t64e[(b24>>18)];
		*out++ = t64e[(b24>>12)&0x3f];
		*out++ = '=';
		break;
	case 0:
		if((i%18) != 0)
			*out++ = '\n';
		*out = 0;
		return out - start;
	}
exhausted:
	*out++ = '=';
	*out++ = '\n';
	*out = 0;
	return out - start;
}

void
freealias(Alias *a)
{
	freeaddrs(a->addr);
	free(a);
}

void
freealiases(Alias *a)
{
	Alias *next;

	while(a != nil){
		next = a->next;
		freealias(a);
		a = next;
	}
}

/* */
/*  read alias file */
/* */
Alias*
readaliases(void)
{
	Alias *a, **l, *first;
	Addr *addr, **al;
	String *file, *line, *token;
	Sinstack *sp;

	first = nil;
	file = s_new();
	line = s_new();
	token = s_new();

	/* open and get length */
	mboxpath("names", login, file, 0);
	sp = s_allocinstack(s_to_c(file));
	if(sp == nil)
		goto out;

	l = &first;

	/* read a line at a time. */
	while(s_rdinstack(sp, s_restart(line))!=nil) {
		s_restart(line);
		a = emalloc(sizeof(Alias));
		al = &a->addr;
		for(;;){
			if(s_parse(line, s_restart(token))==0)
				break;
			addr = emalloc(sizeof(Addr));
			addr->v = strdup(s_to_c(token));
			addr->next = 0;
			*al = addr;
			al = &addr->next;
		} 
		if(a->addr == nil || a->addr->next == nil){
			freealias(a);
			continue;
		}
		a->next = nil;
		*l = a;
		l = &a->next;
	}
	s_freeinstack(sp);

out:
	s_free(file);
	s_free(line);
	s_free(token);
	return first;
}

Addr*
newaddr(char *name)
{
	Addr *a;

	a = emalloc(sizeof(*a));
	a->next = nil;
	a->v = estrdup(name);
	if(a->v == nil)
		sysfatal("%r");
	return a;
}

/* */
/*  expand personal aliases since the names are meaningless in */
/*  other contexts */
/* */
Addr*
_expand(Addr *old, int *changedp)
{
	Alias *al;
	Addr *first, *next, **l, *a;

	*changedp = 0;
	first = nil;
	l = &first;
	for(;old != nil; old = next){
		next = old->next;
		for(al = aliases; al != nil; al = al->next){
			if(strcmp(al->addr->v, old->v) == 0){
				for(a = al->addr->next; a != nil; a = a->next){
					*l = newaddr(a->v);
					if(*l == nil)
						sysfatal("%r");
					l = &(*l)->next;
					*changedp = 1;
				}
				break;
			}
		}
		if(al != nil){
			freeaddr(old);
			continue;
		}
		*l = old;
		old->next = nil;
		l = &(*l)->next;
	}
	return first;
}

Addr*
rexpand(Addr *old)
{
	int i, changed;

	changed = 0;
	for(i=0; i<32; i++){
		old = _expand(old, &changed);
		if(changed == 0)
			break;
	}
	return old;
}

Addr*
unique(Addr *first)
{
	Addr *a, **l, *x;

	for(a = first; a != nil; a = a->next){
		for(l = &a->next; *l != nil;){
			if(strcmp(a->v, (*l)->v) == 0){
				x = *l;
				*l = x->next;
				freeaddr(x);
			} else
				l = &(*l)->next;
		}
	}
	return first;
}

Addr*
expand(int ac, char **av)
{
	Addr *first, **l;
	int i;

	first = nil;

	/* make a list of the starting addresses */
	l = &first;
	for(i = 0; i < ac; i++){
		*l = newaddr(av[i]);
		if(*l == nil)
			sysfatal("%r");
		l = &(*l)->next;
	}

	/* recurse till we don't change any more */
	return unique(rexpand(first));
}

Addr*
concataddr(Addr *a, Addr *b)
{
	Addr *oa;

	if(a == nil)
		return b;

	oa = a;
	for(; a->next; a=a->next)
		;
	a->next = b;
	return oa;
}

void
freeaddr(Addr *ap)
{
	free(ap->v);
	free(ap);
}

void
freeaddrs(Addr *ap)
{
	Addr *next;

	for(; ap; ap=next) {
		next = ap->next;
		freeaddr(ap);
	}
}

String*
s_copyn(char *s, int n)
{
	return s_nappend(s_reset(nil), s, n);
}

/* fetch the next token from an RFC822 address string */
/* we assume the header is RFC822-conformant in that */
/* we recognize escaping anywhere even though it is only */
/* supposed to be in quoted-strings, domain-literals, and comments. */
/* */
/* i'd use yylex or yyparse here, but we need to preserve  */
/* things like comments, which i think it tosses away. */
/* */
/* we're not strictly RFC822 compliant.  we misparse such nonsense as */
/* */
/*	To: gre @ (Grace) plan9 . (Emlin) bell-labs.com */
/* */
/* make sure there's no whitespace in your addresses and  */
/* you'll be fine. */
/* */
enum {
	Twhite,
	Tcomment,
	Twords,
	Tcomma,
	Tleftangle,
	Trightangle,
	Terror,
	Tend
};
/*char *ty82[] = {"white", "comment", "words", "comma", "<", ">", "err", "end"}; */
#define ISWHITE(p) ((p)==' ' || (p)=='\t' || (p)=='\n' || (p)=='\r')
int
get822token(String **tok, char *p, char **pp)
{
	char *op;
	int type;
	int quoting;

	op = p;
	switch(*p){
	case '\0':
		*tok = nil;
		*pp = nil;
		return Tend;

	case ' ':	/* get whitespace */
	case '\t':
	case '\n':
	case '\r':
		type = Twhite;
		while(ISWHITE(*p))
			p++;
		break;

	case '(':	/* get comment */
		type = Tcomment;
		for(p++; *p && *p != ')'; p++)
			if(*p == '\\') {
				if(*(p+1) == '\0') {
					*tok = nil;
					return Terror;
				}
				p++;
			}

		if(*p != ')') {
			*tok = nil;
			return Terror;
		}
		p++;
		break;
	case ',':
		type = Tcomma;
		p++;
		break;
	case '<':
		type = Tleftangle;
		p++;
		break;
	case '>':
		type = Trightangle;
		p++;
		break;
	default:	/* bunch of letters, perhaps quoted strings tossed in */
		type = Twords;
		quoting = 0;
		for(; *p && (quoting || (!ISWHITE(*p) && *p != '>' && *p != '<' && *p != ',')); p++) {
			if(*p == '"') 
				quoting = !quoting;
			if(*p == '\\') {
				if(*(p+1) == '\0') {
					*tok = nil;
					return Terror;
				}
				p++;
			}
		}
		break;
	}

	if(pp)
		*pp = p;
	*tok = s_copyn(op, p-op);
	return type;
}	

/* expand local aliases in an RFC822 mail line */
/* add list of expanded addresses to to. */
Addr*
expandline(String **s, Addr *to)
{
	Addr *na, *nto, *ap;
	char *p;
	int tok, inangle, hadangle, nword;
	String *os, *ns, *stok, *lastword, *sinceword;

	os = s_copy(s_to_c(*s));
	p = strchr(s_to_c(*s), ':');
	assert(p != nil);
	p++;

	ns = s_copyn(s_to_c(*s), p-s_to_c(*s));
	stok = nil;
	nto = nil;
	/* */
	/* the only valid mailbox namings are word */
	/* and word* < addr > */
	/* without comments this would be simple. */
	/* we keep the following: */
	/*	lastword - current guess at the address */
	/*	sinceword - whitespace and comment seen since lastword */
	/* */
	lastword = s_new();
	sinceword = s_new();
	inangle = 0;
	nword = 0;
	hadangle = 0;
	for(;;) {
		stok = nil;
		switch(tok = get822token(&stok, p, &p)){
		default:
			abort();
		case Tcomma:
		case Tend:
			if(inangle)
				goto Error;
			if(nword != 1)
				goto Error;
			na = rexpand(newaddr(s_to_c(lastword)));
			s_append(ns, na->v);
			s_append(ns, s_to_c(sinceword));
			for(ap=na->next; ap; ap=ap->next) {
				s_append(ns, ", ");
				s_append(ns, ap->v);
			}
			nto = concataddr(na, nto);
			if(tok == Tcomma){
				s_append(ns, ",");
				s_free(stok);
			}
			if(tok == Tend)
				goto Break2;
			inangle = 0;
			nword = 0;
			hadangle = 0;
			s_reset(sinceword);
			s_reset(lastword);
			break;
		case Twhite:
		case Tcomment:
			s_append(sinceword, s_to_c(stok));
			s_free(stok);
			break;
		case Trightangle:
			if(!inangle)
				goto Error;
			inangle = 0;
			hadangle = 1;
			s_append(sinceword, s_to_c(stok));
			s_free(stok);
			break;
		case Twords:
		case Tleftangle:
			if(hadangle)
				goto Error;
			if(tok != Tleftangle && inangle && s_len(lastword))
				goto Error;
			if(tok == Tleftangle) {
				inangle = 1;
				nword = 1;
			}
			s_append(ns, s_to_c(lastword));
			s_append(ns, s_to_c(sinceword));
			s_reset(sinceword);
			if(tok == Tleftangle) {
				s_append(ns, "<");
				s_reset(lastword);
			} else {
				s_free(lastword);
				lastword = stok;
			}
			if(!inangle)
				nword++;
			break;
		case Terror:	/* give up, use old string, addrs */
		Error:
			ns = os;
			os = nil;
			freeaddrs(nto);
			nto = nil;
			werrstr("rfc822 syntax error");
			rfc822syntaxerror = 1;
			goto Break2;			
		}
	}
Break2:
	s_free(*s);
	s_free(os);
	*s = ns;
	nto = concataddr(nto, to);
	return nto;
}

void
Bdrain(Biobuf *b)
{
	char buf[8192];

	while(Bread(b, buf, sizeof buf) > 0)
		;
}

void
readmimetypes(void)
{
	Biobuf *b;
	char *p;
	char *f[6];
	char type[256];
	static int alloced, inuse;

	if(mimetypes == 0){
		alloced = 256;
		mimetypes = emalloc(alloced*sizeof(Ctype));
		mimetypes[0].ext = "";
	}

	b = Bopen(unsharp("#9/lib/mimetype"), OREAD);
	if(b == nil)
		return;
	for(;;){
		p = Brdline(b, '\n');
		if(p == nil)
			break;
		p[Blinelen(b)-1] = 0;
		if(tokenize(p, f, 6) < 4)
			continue;
		if(strcmp(f[0], "-") == 0 || strcmp(f[1], "-") == 0 || strcmp(f[2], "-") == 0)
			continue;
		if(inuse + 1 >= alloced){
			alloced += 256;
			mimetypes = erealloc(mimetypes, alloced*sizeof(Ctype));
		}
		snprint(type, sizeof(type), "%s/%s", f[1], f[2]);
		mimetypes[inuse].type = estrdup(type);
		mimetypes[inuse].ext = estrdup(f[0]+1);
		mimetypes[inuse].display = !strcmp(type, "text/plain");
		inuse++;

		/* always make sure there's a terminator */
		mimetypes[inuse].ext = 0;
	}
	Bterm(b);
}

char*
estrdup(char *x)
{
	x = strdup(x);
	if(x == nil)
		fatal("memory");
	return x;
}

void*
emalloc(int n)
{
	void *x;

	x = malloc(n);
	if(x == nil)
		fatal("%r");
	return x;
}

void*
erealloc(void *x, int n)
{
	x = realloc(x, n);
	if(x == nil)
		fatal("%r");
	return x;
}

/* */
/* Formatter for %" */
/* Use double quotes to protect white space, frogs, \ and " */
/* */
enum
{
	Qok = 0,
	Qquote,
	Qbackslash
};

static int
needtoquote(Rune r)
{
	if(r >= Runeself)
		return Qquote;
	if(r <= ' ')
		return Qquote;
	if(r=='\\' || r=='"')
		return Qbackslash;
	return Qok;
}

int
doublequote(Fmt *f)
{
	char *s, *t;
	int w, quotes;
	Rune r;

	s = va_arg(f->args, char*);
	if(s == nil || *s == '\0')
		return fmtstrcpy(f, "\"\"");

	quotes = 0;
	for(t=s; *t; t+=w){
		w = chartorune(&r, t);
		quotes |= needtoquote(r);
	}
	if(quotes == 0)
		return fmtstrcpy(f, s);

	fmtrune(f, '"');
	for(t=s; *t; t+=w){
		w = chartorune(&r, t);
		if(needtoquote(r) == Qbackslash)
			fmtrune(f, '\\');
		fmtrune(f, r);
	}
	return fmtrune(f, '"');
}

int
mountmail(void)
{
	if(mailfs != nil)
		return 0;
	if((mailfs = nsmount("mail", nil)) == nil)
		return -1;
	return 0;
}

int
rfc2047fmt(Fmt *fmt)
{
	char *s, *p;

	s = va_arg(fmt->args, char*);
	if(s == nil)
		return fmtstrcpy(fmt, "");
	for(p=s; *p; p++)
		if((uchar)*p >= 0x80)
			goto hard;
	return fmtstrcpy(fmt, s);

hard:
	fmtprint(fmt, "=?utf-8?q?");
	for(p=s; *p; p++){
		if(*p == ' ')
			fmtrune(fmt, '_');
		else if(*p == '_' || *p == '\t' || *p == '=' || *p == '?' || (uchar)*p >= 0x80)
			fmtprint(fmt, "=%.2uX", (uchar)*p);
		else
			fmtrune(fmt, (uchar)*p);
	}			
	fmtprint(fmt, "?=");
	return 0;
}

char*
mksubject(char *line)
{
	char *p, *q;
	static char buf[1024];

	p = strchr(line, ':')+1;
	while(*p == ' ')
		p++;
	for(q=p; *q; q++)
		if((uchar)*q >= 0x80)
			goto hard;
	return line;

hard:
	snprint(buf, sizeof buf, "Subject: %U", p);
	return buf;
}
