Commit Diff


commit - cd3745196389579fb78b9b01ef1daefb5a57aa71
commit + 5cdb17983ae6e6367ad7a940cb219eab247a9304
blob - /dev/null
blob + 67f367981d81015260bf8a7bce01b9ff594d41b8 (mode 644)
--- /dev/null
+++ src/cmd/upas/common/libsys.c
@@ -0,0 +1,1001 @@
+#include "common.h"
+#include <auth.h>
+#include <ndb.h>
+
+/*
+ *  number of predefined fd's
+ */
+int nsysfile=3;
+
+static char err[Errlen];
+
+/*
+ *  return the date
+ */
+extern char *
+thedate(void)
+{
+	static char now[64];
+	char *cp;
+
+	strcpy(now, ctime(time(0)));
+	cp = strchr(now, '\n');
+	if(cp)
+		*cp = 0;
+	return now;
+}
+
+/*
+ *  return the user id of the current user
+ */
+extern char *
+getlog(void)
+{
+	return getuser();
+}
+#if 0  /* jpc */
+extern char *
+getlog(void)
+{
+	static char user[64];
+	int fd;
+	int n;
+
+	fd = open("/dev/user", 0);
+	if(fd < 0)
+		return nil;
+	if((n=read(fd, user, sizeof(user)-1)) <= 0)
+		return nil;
+	close(fd);
+	user[n] = 0;
+	return user;
+}
+#endif /* jpc */
+/*
+ *  return the lock name (we use one lock per directory)
+ */
+static String *
+lockname(char *path)
+{
+	String *lp;
+	char *cp;
+
+	/*
+	 *  get the name of the lock file
+	 */
+	lp = s_new();
+	cp = strrchr(path, '/');
+	if(cp)
+		s_nappend(lp, path, cp - path + 1);
+	s_append(lp, "L.mbox");
+
+	return lp;
+}
+
+int
+syscreatelocked(char *path, int mode, int perm)
+{
+	return create(path, mode, DMEXCL|perm);
+}
+
+int
+sysopenlocked(char *path, int mode)
+{
+/*	return open(path, OEXCL|mode);/**/
+	return open(path, mode);		/* until system call is fixed */
+}
+
+int
+sysunlockfile(int fd)
+{
+	return close(fd);
+}
+
+/*
+ *  try opening a lock file.  If it doesn't exist try creating it.
+ */
+static int
+openlockfile(Mlock *l)
+{
+	int fd;
+	Dir *d;
+	Dir nd;
+	char *p;
+
+	fd = open(s_to_c(l->name), OREAD);
+	if(fd >= 0){
+		l->fd = fd;
+		return 0;
+	}
+
+	d = dirstat(s_to_c(l->name));
+	if(d == nil){
+		/* file doesn't exist */
+		/* try creating it */
+		fd = create(s_to_c(l->name), OREAD, DMEXCL|0666);
+		if(fd >= 0){
+			nulldir(&nd);
+			nd.mode = DMEXCL|0666;
+			if(dirfwstat(fd, &nd) < 0){
+				/* if we can't chmod, don't bother */
+				/* live without the lock but log it */
+				syslog(0, "mail", "lock error: %s: %r", s_to_c(l->name));
+				remove(s_to_c(l->name));
+			}
+			l->fd = fd;
+			return 0;
+		}
+
+		/* couldn't create */
+		/* do we have write access to the directory? */
+		p = strrchr(s_to_c(l->name), '/');
+		if(p != 0){
+			*p = 0;
+			fd = access(s_to_c(l->name), 2);
+			*p = '/';
+			if(fd < 0){
+				/* live without the lock but log it */
+				syslog(0, "mail", "lock error: %s: %r", s_to_c(l->name));
+				return 0;
+			}
+		} else {
+			fd = access(".", 2);
+			if(fd < 0){
+				/* live without the lock but log it */
+				syslog(0, "mail", "lock error: %s: %r", s_to_c(l->name));
+				return 0;
+			}
+		}
+	} else
+		free(d);
+
+	return 1; /* try again later */
+}
+
+#define LSECS 5*60
+
+/*
+ *  Set a lock for a particular file.  The lock is a file in the same directory
+ *  and has L. prepended to the name of the last element of the file name.
+ */
+extern Mlock *
+syslock(char *path)
+{
+	Mlock *l;
+	int tries;
+
+	l = mallocz(sizeof(Mlock), 1);
+	if(l == 0)
+		return nil;
+
+	l->name = lockname(path);
+
+	/*
+	 *  wait LSECS seconds for it to unlock
+	 */
+	for(tries = 0; tries < LSECS*2; tries++){
+		switch(openlockfile(l)){
+		case 0:
+			return l;
+		case 1:
+			sleep(500);
+			break;
+		default:
+			goto noway;
+		}
+	}
+
+noway:
+	s_free(l->name);
+	free(l);
+	return nil;
+}
+
+/*
+ *  like lock except don't wait
+ */
+extern Mlock *
+trylock(char *path)
+{
+	Mlock *l;
+	char buf[1];
+	int fd;
+
+	l = malloc(sizeof(Mlock));
+	if(l == 0)
+		return 0;
+
+	l->name = lockname(path);
+	if(openlockfile(l) != 0){
+		s_free(l->name);
+		free(l);
+		return 0;
+	}
+	
+	/* fork process to keep lock alive */
+	switch(l->pid = rfork(RFPROC)){
+	default:
+		break;
+	case 0:
+		fd = l->fd;
+		for(;;){
+			sleep(1000*60);
+			if(pread(fd, buf, 1, 0) < 0)
+				break;
+		}
+		_exits(0);
+	}
+	return l;
+}
+
+extern void
+syslockrefresh(Mlock *l)
+{
+	char buf[1];
+
+	pread(l->fd, buf, 1, 0);
+}
+
+extern void
+sysunlock(Mlock *l)
+{
+	if(l == 0)
+		return;
+	if(l->name){
+		s_free(l->name);
+	}
+	if(l->fd >= 0)
+		close(l->fd);
+	if(l->pid > 0)
+		postnote(PNPROC, l->pid, "time to die");
+	free(l);
+}
+
+/*
+ *  Open a file.  The modes are:
+ *
+ *	l	- locked
+ *	a	- set append permissions
+ *	r	- readable
+ *	w	- writable
+ *	A	- append only (doesn't exist in Bio)
+ */
+extern Biobuf *
+sysopen(char *path, char *mode, ulong perm)
+{
+	int sysperm;
+	int sysmode;
+	int fd;
+	int docreate;
+	int append;
+	int truncate;
+	Dir *d, nd;
+	Biobuf *bp;
+
+	/*
+	 *  decode the request
+	 */
+	sysperm = 0;
+	sysmode = -1;
+	docreate = 0;
+	append = 0;
+	truncate = 0;
+ 	for(; mode && *mode; mode++)
+		switch(*mode){
+		case 'A':
+			sysmode = OWRITE;
+			append = 1;
+			break;
+		case 'c':
+			docreate = 1;
+			break;
+		case 'l':
+			sysperm |= DMEXCL;
+			break;
+		case 'a':
+			sysperm |= DMAPPEND;
+			break;
+		case 'w':
+			if(sysmode == -1)
+				sysmode = OWRITE;
+			else
+				sysmode = ORDWR;
+			break;
+		case 'r':
+			if(sysmode == -1)
+				sysmode = OREAD;
+			else
+				sysmode = ORDWR;
+			break;
+		case 't':
+			truncate = 1;
+			break;
+		default:
+			break;
+		}
+	switch(sysmode){
+	case OREAD:
+	case OWRITE:
+	case ORDWR:
+		break;
+	default:
+		if(sysperm&DMAPPEND)
+			sysmode = OWRITE;
+		else
+			sysmode = OREAD;
+		break;
+	}
+
+	/*
+	 *  create file if we need to
+	 */
+	if(truncate)
+		sysmode |= OTRUNC;
+	fd = open(path, sysmode);
+	if(fd < 0){
+		d = dirstat(path);
+		if(d == nil){
+			if(docreate == 0)
+				return 0;
+
+			fd = create(path, sysmode, sysperm|perm);
+			if(fd < 0)
+				return 0;
+			nulldir(&nd);
+			nd.mode = sysperm|perm;
+			dirfwstat(fd, &nd);
+		} else {
+			free(d);
+			return 0;
+		}
+	}
+
+	bp = (Biobuf*)malloc(sizeof(Biobuf));
+	if(bp == 0){
+		close(fd);
+		return 0;
+	}
+	memset(bp, 0, sizeof(Biobuf));
+	Binit(bp, fd, sysmode&~OTRUNC);
+
+	if(append)
+		Bseek(bp, 0, 2);
+	return bp;
+}
+
+/*
+ *  close the file, etc.
+ */
+int
+sysclose(Biobuf *bp)
+{
+	int rv;
+
+	rv = Bterm(bp);
+	close(Bfildes(bp));
+	free(bp);
+	return rv;
+}
+
+/*
+ *  create a file
+ */
+int
+syscreate(char *file, int mode, ulong perm)
+{
+	return create(file, mode, perm);
+}
+
+/*
+ *  make a directory
+ */
+int
+sysmkdir(char *file, ulong perm)
+{
+	int fd;
+
+	if((fd = create(file, OREAD, DMDIR|perm)) < 0)
+		return -1;
+	close(fd);
+	return 0;
+}
+
+/*
+ *  change the group of a file
+ */
+int
+syschgrp(char *file, char *group)
+{
+	Dir nd;
+
+	if(group == 0)
+		return -1;
+	nulldir(&nd);
+	nd.gid = group;
+	return dirwstat(file, &nd);
+}
+
+extern int
+sysdirreadall(int fd, Dir **d)
+{
+	return dirreadall(fd, d);
+}
+
+/*
+ *  read in the system name
+ */
+extern char *
+sysname_read(void)
+{
+	static char name[128];
+	char *cp;
+
+	cp = getenv("site");
+	if(cp == 0 || *cp == 0)
+		cp = alt_sysname_read();
+	if(cp == 0 || *cp == 0)
+		cp = "kremvax";
+	strecpy(name, name+sizeof name, cp);
+	return name;
+}
+extern char *
+alt_sysname_read(void)
+{
+	static char name[128];
+	int n, fd;
+
+	fd = open("/dev/sysname", OREAD);
+	if(fd < 0)
+		return 0;
+	n = read(fd, name, sizeof(name)-1);
+	close(fd);
+	if(n <= 0)
+		return 0;
+	name[n] = 0;
+	return name;
+}
+
+/*
+ *  get all names
+ */
+extern char**
+sysnames_read(void)
+{
+	static char **namev;
+	Ndbtuple *t, *nt;
+	Ndb* db;
+	Ndbs s;
+	int n;
+	char *cp;
+
+	if(namev)
+		return namev;
+
+	/* free(csgetvalue(0, "sys", alt_sysname_read(), "dom", &t));  jpc */
+	db = ndbopen(unsharp("#9/ndb/local"));
+	free(ndbgetvalue(db, &s, "sys", sysname(),"dom", &t));
+	/* t = nil; /* jpc */
+	/* fprint(2,"csgetvalue called: fixme"); /* jpc */
+
+	n = 0;
+	for(nt = t; nt; nt = nt->entry)
+		if(strcmp(nt->attr, "dom") == 0)
+			n++;
+
+	namev = (char**)malloc(sizeof(char *)*(n+3));
+
+	if(namev){
+		n = 0;
+		namev[n++] = strdup(sysname_read());
+		cp = alt_sysname_read();
+		if(cp)
+			namev[n++] = strdup(cp);
+		for(nt = t; nt; nt = nt->entry)
+			if(strcmp(nt->attr, "dom") == 0)
+				namev[n++] = strdup(nt->val);
+		namev[n] = 0;
+	}
+	if(t)
+		ndbfree(t);
+
+	return namev;
+}
+
+/*
+ *  read in the domain name
+ */
+extern char *
+domainname_read(void)
+{
+	char **namev;
+
+	for(namev = sysnames_read(); *namev; namev++)
+		if(strchr(*namev, '.'))
+			return *namev;
+	return 0;
+}
+
+/*
+ *  return true if the last error message meant file
+ *  did not exist.
+ */
+extern int
+e_nonexistent(void)
+{
+	rerrstr(err, sizeof(err));
+	return strcmp(err, "file does not exist") == 0;
+}
+
+/*
+ *  return true if the last error message meant file
+ *  was locked.
+ */
+extern int
+e_locked(void)
+{
+	rerrstr(err, sizeof(err));
+	return strcmp(err, "open/create -- file is locked") == 0;
+}
+
+/*
+ *  return the length of a file
+ */
+extern long
+sysfilelen(Biobuf *fp)
+{
+	Dir *d;
+	long rv;
+
+	d = dirfstat(Bfildes(fp));
+	if(d == nil)
+		return -1;
+	rv = d->length;
+	free(d);
+	return rv;
+}
+
+/*
+ *  remove a file
+ */
+extern int
+sysremove(char *path)
+{
+	return remove(path);
+}
+
+/*
+ *  rename a file, fails unless both are in the same directory
+ */
+extern int
+sysrename(char *old, char *new)
+{
+	Dir d;
+	char *obase;
+	char *nbase;
+
+	obase = strrchr(old, '/');
+	nbase = strrchr(new, '/');
+	if(obase){
+		if(nbase == 0)
+			return -1;
+		if(strncmp(old, new, obase-old) != 0)
+			return -1;
+		nbase++;
+	} else {
+		if(nbase)
+			return -1;
+		nbase = new;
+	}
+	nulldir(&d);
+	d.name = nbase;
+	return dirwstat(old, &d);
+}
+
+/*
+ *  see if a file exists
+ */
+extern int
+sysexist(char *file)
+{
+	Dir	*d;
+
+	d = dirstat(file);
+	if(d == nil)
+		return 0;
+	free(d);
+	return 1;
+}
+
+/*
+ *  return nonzero if file is a directory
+ */
+extern int
+sysisdir(char *file)
+{
+	Dir	*d;
+	int	rv;
+
+	d = dirstat(file);
+	if(d == nil)
+		return 0;
+	rv = d->mode & DMDIR;
+	free(d);
+	return rv;
+}
+
+/*
+ * kill a process or process group
+ */
+
+static int
+stomp(int pid, char *file)
+{
+	char name[64];
+	int fd;
+
+	snprint(name, sizeof(name), "/proc/%d/%s", pid, file);
+	fd = open(name, 1);
+	if(fd < 0)
+		return -1;
+	if(write(fd, "die: yankee pig dog\n", sizeof("die: yankee pig dog\n") - 1) <= 0){
+		close(fd);
+		return -1;
+	}
+	close(fd);
+	return 0;
+	
+}
+
+/*
+ *  kill a process
+ */
+extern int
+syskill(int pid)
+{
+	return stomp(pid, "note");
+	
+}
+
+/*
+ *  kill a process group
+ */
+extern int
+syskillpg(int pid)
+{
+	return stomp(pid, "notepg");
+}
+
+extern int
+sysdetach(void)
+{
+	if(rfork(RFENVG|RFNAMEG|RFNOTEG) < 0) {
+		werrstr("rfork failed");
+		return -1;
+	}
+	return 0;
+}
+
+/*
+ *  catch a write on a closed pipe
+ */
+static int *closedflag;
+static int
+catchpipe(void *a, char *msg)
+{
+	static char *foo = "sys: write on closed pipe";
+
+	USED(a);
+	if(strncmp(msg, foo, strlen(foo)) == 0){
+		if(closedflag)
+			*closedflag = 1;
+		return 1;
+	}
+	return 0;
+}
+void
+pipesig(int *flagp)
+{
+	closedflag = flagp;
+	atnotify(catchpipe, 1);
+}
+void
+pipesigoff(void)
+{
+	atnotify(catchpipe, 0);
+}
+
+void
+exit9(int i)
+{
+	char buf[32];
+
+	if(i == 0)
+		exits(0);
+	snprint(buf, sizeof(buf), "%d", i);
+	exits(buf);
+}
+
+static int
+islikeatty(int fd)
+{
+	Dir *d;
+	int rv;
+
+	d = dirfstat(fd);
+	if(d == nil)
+		return 0;
+	rv = strcmp(d->name, "cons") == 0;
+	free(d);
+	return rv;
+}
+
+#if 0
+/* jpc */
+static int
+islikeatty(int fd)
+{
+	char buf[64];
+
+	if(fd2path(fd, buf, sizeof buf) != 0)
+		return 0;
+
+	/* might be /mnt/term/dev/cons */
+	return strlen(buf) >= 9 && strcmp(buf+strlen(buf)-9, "/dev/cons") == 0;
+}
+#endif
+
+extern int
+holdon(void)
+{
+	int fd;
+
+	if(!islikeatty(0))
+		return -1;
+
+	fd = open("/dev/consctl", OWRITE);
+	write(fd, "holdon", 6);
+
+	return fd;
+}
+
+extern int
+sysopentty(void)
+{
+	return open("/dev/cons", ORDWR);
+}
+
+extern void
+holdoff(int fd)
+{
+	write(fd, "holdoff", 7);
+	close(fd);
+}
+
+extern int
+sysfiles(void)
+{
+	return 128;
+}
+
+/*
+ *  expand a path relative to the user's mailbox directory
+ *
+ *  if the path starts with / or ./, don't change it
+ *
+ */
+extern String *
+mboxpath(char *path, char *user, String *to, int dot)
+{
+	if (dot || *path=='/' || strncmp(path, "./", 2) == 0
+			      || strncmp(path, "../", 3) == 0) {
+		to = s_append(to, path);
+	} else {
+		to = s_append(to, unsharp(MAILROOT));
+		to = s_append(to, "/box/");
+		to = s_append(to, user);
+		to = s_append(to, "/");
+		to = s_append(to, path);
+	}
+	return to;
+}
+
+extern String *
+mboxname(char *user, String *to)
+{
+	return mboxpath("mbox", user, to, 0);
+}
+
+extern String *
+deadletter(String *to)		/* pass in sender??? */
+{
+	char *cp;
+
+	cp = getlog();
+	if(cp == 0)
+		return 0;
+	return mboxpath("dead.letter", cp, to, 0);
+}
+
+char *
+homedir(char *user)
+{
+	USED(user);
+	return getenv("home");
+}
+
+String *
+readlock(String *file)
+{
+	char *cp;
+
+	cp = getlog();
+	if(cp == 0)
+		return 0;
+	return mboxpath("reading", cp, file, 0);
+}
+
+String *
+username(String *from)
+{
+	int n;
+	Biobuf *bp;
+	char *p, *q;
+	String *s;
+
+	bp = Bopen("/adm/keys.who", OREAD);
+	if(bp == 0)
+		bp = Bopen("/adm/netkeys.who", OREAD);
+	if(bp == 0)
+		return 0;
+
+	s = 0;
+	n = strlen(s_to_c(from));
+	for(;;) {
+		p = Brdline(bp, '\n');
+		if(p == 0)
+			break;
+		p[Blinelen(bp)-1] = 0;
+		if(strncmp(p, s_to_c(from), n))
+			continue;
+		p += n;
+		if(*p != ' ' && *p != '\t')	/* must be full match */
+			continue;
+		while(*p && (*p == ' ' || *p == '\t'))
+				p++;
+		if(*p == 0)
+			continue;
+		for(q = p; *q; q++)
+			if(('0' <= *q && *q <= '9') || *q == '<')
+				break;
+		while(q > p && q[-1] != ' ' && q[-1] != '\t')
+			q--;
+		while(q > p && (q[-1] == ' ' || q[-1] == '\t'))
+			q--;
+		*q = 0;
+		s = s_new();
+		s_append(s, "\"");
+		s_append(s, p);
+		s_append(s, "\"");
+		break;
+	}
+	Bterm(bp);
+	return s;
+}
+
+char *
+remoteaddr(int fd, char *dir)
+{
+	char buf[128], *p;
+	int n;
+
+	if(dir == 0){
+		fprint(2,"remoteaddr: called fd2path: fixme\n"); /* jpc
+		if(fd2path(fd, buf, sizeof(buf)) != 0)
+			return ""; */
+
+		/* parse something of the form /net/tcp/nnnn/data */
+		p = strrchr(buf, '/');
+		if(p == 0)
+			return "";
+		strncpy(p+1, "remote", sizeof(buf)-(p-buf)-2);
+	} else
+		snprint(buf, sizeof buf, "%s/remote", dir);
+	buf[sizeof(buf)-1] = 0;
+
+	fd = open(buf, OREAD);
+	if(fd < 0)
+		return "";
+	n = read(fd, buf, sizeof(buf)-1);
+	close(fd);
+	if(n > 0){
+		buf[n] = 0;
+		p = strchr(buf, '!');
+		if(p)
+			*p = 0;
+		return strdup(buf);
+	}
+	return "";
+}
+
+//  create a file and 
+//	1) ensure the modes we asked for
+//	2) make gid == uid
+static int
+docreate(char *file, int perm)
+{
+	int fd;
+	Dir ndir;
+	Dir *d;
+
+	//  create the mbox
+	fd = create(file, OREAD, perm);
+	if(fd < 0){
+		fprint(2, "couldn't create %s\n", file);
+		return -1;
+	}
+	d = dirfstat(fd);
+	if(d == nil){
+		fprint(2, "couldn't stat %s\n", file);
+		return -1;
+	}
+	nulldir(&ndir);
+	ndir.mode = perm;
+	ndir.gid = d->uid;
+	if(dirfwstat(fd, &ndir) < 0)
+		fprint(2, "couldn't chmod %s: %r\n", file);
+	close(fd);
+	return 0;
+}
+
+//  create a mailbox
+int
+creatembox(char *user, char *folder)
+{
+	char *p;
+	String *mailfile;
+	char buf[512];
+	Mlock *ml;
+
+	mailfile = s_new();
+	if(folder == 0)
+		mboxname(user, mailfile);
+	else {
+		snprint(buf, sizeof(buf), "%s/mbox", folder);
+		mboxpath(buf, user, mailfile, 0);
+	}
+
+	// don't destroy existing mailbox
+	if(access(s_to_c(mailfile), 0) == 0){
+		fprint(2, "mailbox already exists\n");
+		return -1;
+	}
+	fprint(2, "creating new mbox: %s\n", s_to_c(mailfile));
+
+	//  make sure preceding levels exist
+	for(p = s_to_c(mailfile); p; p++) {
+		if(*p == '/')	/* skip leading or consecutive slashes */
+			continue;
+		p = strchr(p, '/');
+		if(p == 0)
+			break;
+		*p = 0;
+		if(access(s_to_c(mailfile), 0) != 0){
+			if(docreate(s_to_c(mailfile), DMDIR|0711) < 0)
+				return -1;
+		}
+		*p = '/';
+	}
+
+	//  create the mbox
+	if(docreate(s_to_c(mailfile), 0622|DMAPPEND|DMEXCL) < 0)
+		return -1;
+
+	/*
+	 *  create the lock file if it doesn't exist
+	 */
+	ml = trylock(s_to_c(mailfile));
+	if(ml != nil)
+		sysunlock(ml);
+
+	return 0;
+}
blob - /dev/null
blob + 5347c6550c2b586e599962a0069ca3179f28e055 (mode 644)
--- /dev/null
+++ src/cmd/upas/common/mail.c
@@ -0,0 +1,57 @@
+#include "common.h"
+
+/* format of REMOTE FROM lines */
+char *REMFROMRE =
+	"^>?From[ \t]+((\".*\")?[^\" \t]+?(\".*\")?[^\" \t]+?)[ \t]+(.+)[ \t]+remote[ \t]+from[ \t]+(.*)\n$";
+int REMSENDERMATCH = 1;
+int REMDATEMATCH = 4;
+int REMSYSMATCH = 5;
+
+/* format of LOCAL FROM lines */
+char *FROMRE =
+	"^>?From[ \t]+((\".*\")?[^\" \t]+?(\".*\")?[^\" \t]+?)[ \t]+(.+)\n$";
+int SENDERMATCH = 1;
+int DATEMATCH = 4;
+
+/* output a unix style local header */
+int
+print_header(Biobuf *fp, char *sender, char *date)
+{
+	return Bprint(fp, "From %s %s\n", sender, date);
+}
+
+/* output a unix style remote header */
+int
+print_remote_header(Biobuf *fp, char *sender, char *date, char *system)
+{
+	return Bprint(fp, "From %s %s remote from %s\n", sender, date, system);
+}
+
+/* parse a mailbox style header */
+int
+parse_header(char *line, String *sender, String *date)
+{
+	if (!IS_HEADER(line))
+		return -1;
+	line += sizeof("From ") - 1;
+	s_restart(sender);
+	while(*line==' '||*line=='\t')
+		line++;
+	if(*line == '"'){
+		s_putc(sender, *line++);
+		while(*line && *line != '"')
+			s_putc(sender, *line++);
+		s_putc(sender, *line++);
+	} else {
+		while(*line && *line != ' ' && *line != '\t')
+			s_putc(sender, *line++);
+	}
+	s_terminate(sender);
+	s_restart(date);
+	while(*line==' '||*line=='\t')
+		line++;
+	while(*line)
+		s_putc(date, *line++);
+	s_terminate(date);
+	return 0;
+}
blob - /dev/null
blob + c7beae817b5468dc7ffd4a74f9f5073dd9fe9b9e (mode 644)
--- /dev/null
+++ src/cmd/upas/common/makefile
@@ -0,0 +1,18 @@
+CFLAGS=${UNIX} -g -I. -I../libc -I../common -I/usr/include ${SCFLAGS}
+OBJS=mail.o aux.o string.o ${SYSOBJ}
+AR=ar
+.c.o: ; ${CC} -c ${CFLAGS} $*.c
+
+common.a: ${OBJS}
+	${AR} cr common.a ${OBJS}
+	-ranlib common.a
+
+aux.o:		aux.h string.h mail.h
+string.o:	string.h mail.h
+mail.o:		mail.h
+syslog.o:	sys.h
+mail.h:		sys.h
+
+clean:
+	-rm -f *.[oO] core a.out *.a *.sL common.a
+
blob - /dev/null
blob + 8cfaf267e8c51405770c3eb38b1ff6acd6f2f856 (mode 644)
--- /dev/null
+++ src/cmd/upas/common/mkfile
@@ -0,0 +1,20 @@
+<$PLAN9/src/mkhdr
+
+LIB=libcommon.a
+
+OFILES=aux.$O\
+	become.$O\
+	mail.$O\
+	process.$O\
+	libsys.$O\
+	config.$O\
+	appendfiletombox.$O\
+
+HFILES=common.h\
+	sys.h\
+
+<$PLAN9/src/mklib
+
+nuke:V:
+	mk clean
+	rm -f libcommon.a
blob - /dev/null
blob + 16b21aefe4ad3db08a1f982ed6183e9cb2b64ed0 (mode 644)
--- /dev/null
+++ src/cmd/upas/common/process.c
@@ -0,0 +1,175 @@
+#include "common.h"
+
+/* make a stream to a child process */
+extern stream *
+instream(void)
+{
+	stream *rv;
+	int pfd[2];
+
+	if ((rv = (stream *)malloc(sizeof(stream))) == 0)
+		return 0;
+	memset(rv, 0, sizeof(stream));
+	if (pipe(pfd) < 0)
+		return 0;
+	if(Binit(&rv->bb, pfd[1], OWRITE) < 0){
+		close(pfd[0]);
+		close(pfd[1]);
+		return 0;
+	}
+	rv->fp = &rv->bb;
+	rv->fd = pfd[0];	
+	return rv;
+}
+
+/* make a stream from a child process */
+extern stream *
+outstream(void)
+{
+	stream *rv;
+	int pfd[2];
+
+	if ((rv = (stream *)malloc(sizeof(stream))) == 0)
+		return 0;
+	memset(rv, 0, sizeof(stream));
+	if (pipe(pfd) < 0)
+		return 0;
+	if (Binit(&rv->bb, pfd[0], OREAD) < 0){
+		close(pfd[0]);
+		close(pfd[1]);
+		return 0;
+	}
+	rv->fp = &rv->bb;
+	rv->fd = pfd[1];
+	return rv;
+}
+
+extern void
+stream_free(stream *sp)
+{
+	int fd;
+
+	close(sp->fd);
+	fd = Bfildes(sp->fp);
+	Bterm(sp->fp);
+	close(fd);
+	free((char *)sp);
+}
+
+/* start a new process */
+extern process *
+noshell_proc_start(char **av, stream *inp, stream *outp, stream *errp, int newpg, char *who)
+{
+	process *pp;
+	int i, n;
+
+	if ((pp = (process *)malloc(sizeof(process))) == 0) {
+		if (inp != 0)
+			stream_free(inp);
+		if (outp != 0)
+			stream_free(outp);
+		if (errp != 0)
+			stream_free(errp);
+		return 0;
+	}
+	pp->std[0] = inp;
+	pp->std[1] = outp;
+	pp->std[2] = errp;
+	switch (pp->pid = fork()) {
+	case -1:
+		proc_free(pp);
+		return 0;
+	case 0:
+		if(newpg)
+			sysdetach();
+		for (i=0; i<3; i++)
+			if (pp->std[i] != 0){
+				close(Bfildes(pp->std[i]->fp));
+				while(pp->std[i]->fd < 3)
+					pp->std[i]->fd = dup(pp->std[i]->fd, -1);
+			}
+		for (i=0; i<3; i++)
+			if (pp->std[i] != 0)
+				dup(pp->std[i]->fd, i);
+		for (n = sysfiles(); i < n; i++)
+			close(i);
+		if(who) {
+			fprint(2,"process.c: trying to become(%s,%s)\n",av,who);
+			// jpc become(av, who);
+		}
+		exec(av[0], av);
+		perror("proc_start");
+		exits("proc_start");
+	default:
+		for (i=0; i<3; i++)
+			if (pp->std[i] != 0) {
+				close(pp->std[i]->fd);
+				pp->std[i]->fd = -1;
+			}
+		return pp;
+	}
+}
+
+/* start a new process under a shell */
+extern process *
+proc_start(char *cmd, stream *inp, stream *outp, stream *errp, int newpg, char *who)
+{
+	char *av[4];
+
+	av[0] = unsharp(SHELL);
+	av[1] = "-c";
+	av[2] = cmd;
+	av[3] = 0;
+	return noshell_proc_start(av, inp, outp, errp, newpg, who);
+}
+
+/* wait for a process to stop */
+extern int
+proc_wait(process *pp)
+{
+	Waitmsg *status;
+	char err[Errlen];
+
+	for(;;){
+		status = wait();
+		if(status == nil){
+			errstr(err, sizeof(err));
+			if(strstr(err, "interrupt") == 0)
+				break;
+		}
+		if (status->pid==pp->pid)
+			break;
+	}
+	pp->pid = -1;
+	if(status == nil)
+		pp->status = -1;
+	else
+		pp->status = status->msg[0];
+	pp->waitmsg = status;
+	return pp->status;
+}
+
+/* free a process */
+extern int
+proc_free(process *pp)
+{
+	int i;
+
+	if(pp->std[1] == pp->std[2])
+		pp->std[2] = 0;		/* avoid freeing it twice */
+	for (i = 0; i < 3; i++)
+		if (pp->std[i])
+			stream_free(pp->std[i]);
+	if (pp->pid >= 0)
+		proc_wait(pp);
+	free(pp->waitmsg);
+	free((char *)pp);
+	return 0;
+}
+
+/* kill a process */
+extern int
+proc_kill(process *pp)
+{
+	return syskill(pp->pid);
+}
blob - /dev/null
blob + 960aebb37327c9dc5b510a4a6e78d021d32f01f4 (mode 644)
--- /dev/null
+++ src/cmd/upas/common/sys.h
@@ -0,0 +1,85 @@
+/*
+ * System dependent header files for research
+ */
+
+#include <u.h>
+#include <libc.h>
+#include <regexp.h>
+#include <bio.h>
+#include "libString.h"   /* jpc String.h -> libString.h */
+
+/*
+ *  for the lock routines in libsys.c
+ */
+typedef struct Mlock	Mlock;
+struct Mlock {
+	int fd;
+	int pid;
+	String *name;
+};
+
+/*
+ *  from config.c
+ */
+extern char *MAILROOT;	/* root of mail system */
+extern char *UPASLOG;	/* log directory */
+extern char *UPASLIB;	/* upas library directory */
+extern char *UPASBIN;	/* upas binary directory */
+extern char *UPASTMP;	/* temporary directory */
+extern char *SHELL;	/* path name of shell */
+extern char *POST;	/* path name of post server addresses */
+extern int MBOXMODE;	/* default mailbox protection mode */
+
+/*
+ *  files in libsys.c
+ */
+extern char	*sysname_read(void);
+extern char	*alt_sysname_read(void);
+extern char	*domainname_read(void);
+extern char	**sysnames_read(void);
+extern char	*getlog(void);
+extern char	*thedate(void);
+extern Biobuf	*sysopen(char*, char*, ulong);
+extern int	sysopentty(void);
+extern int	sysclose(Biobuf*);
+extern int	sysmkdir(char*, ulong);
+extern int	syschgrp(char*, char*);
+extern Mlock	*syslock(char *);
+extern void	sysunlock(Mlock *);
+extern void	syslockrefresh(Mlock *);
+extern int	e_nonexistent(void);
+extern int	e_locked(void);
+extern long	sysfilelen(Biobuf*);
+extern int	sysremove(char*);
+extern int	sysrename(char*, char*);
+extern int	sysexist(char*);
+extern int	sysisdir(char*);
+extern int	syskill(int);
+extern int	syskillpg(int);
+extern int	syscreate(char*, int, ulong);
+extern Mlock	*trylock(char *);
+extern void	exit9(int);
+extern void	pipesig(int*);
+extern void	pipesigoff(void);
+extern int	holdon(void);
+extern void	holdoff(int);
+extern int	syscreatelocked(char*, int, int);
+extern int	sysopenlocked(char*, int);
+extern int	sysunlockfile(int);
+extern int	sysfiles(void);
+extern int 	become(char**, char*);
+extern int	sysdetach(void);
+extern int	sysdirreadall(int, Dir**);
+extern String	*username(String*);
+extern char*	remoteaddr(int, char*);
+extern int	creatembox(char*, char*);
+
+extern String	*readlock(String*);
+extern char	*homedir(char*);
+extern String	*mboxname(char*, String*);
+extern String	*deadletter(String*);
+
+/*
+ *  maximum size for a file path
+ */
+#define MAXPATHLEN 128
blob - /dev/null
blob + dd4572db84ee193ac151c461338dad82c970fe2f (mode 644)
--- /dev/null
+++ src/cmd/upas/filterkit/dat.h
@@ -0,0 +1,8 @@
+typedef struct Addr Addr;
+struct Addr
+{
+	Addr *next;
+	char *val;
+};
+
+extern Addr* readaddrs(char*, Addr*);
blob - /dev/null
blob + 33903708a4f6d40e97464c6cce657ef590de8c46 (mode 644)
--- /dev/null
+++ src/cmd/upas/filterkit/deliver.c
@@ -0,0 +1,60 @@
+#include "dat.h"
+#include "common.h"
+
+void
+usage(void)
+{
+	fprint(2, "usage: %s recipient fromaddr-file mbox\n", argv0);
+	exits("usage");
+}
+
+void
+main(int argc, char **argv)
+{
+	int fd;
+	char now[30];
+	Addr *a;
+	char *deliveredto;
+	Mlock *l;
+	int bytes;
+
+	ARGBEGIN{
+	}ARGEND;
+
+	if(argc != 3)
+		usage();
+
+	deliveredto = strrchr(argv[0], '!');
+	if(deliveredto == nil)
+		deliveredto = argv[0];
+	else
+		deliveredto++;
+	a = readaddrs(argv[1], nil);
+	if(a == nil)
+		sysfatal("missing from address");
+
+	l = syslock(argv[2]);
+
+	/* append to mbox */
+	fd = open(argv[2], OWRITE);
+	if(fd < 0)
+		sysfatal("opening mailbox: %r");
+	seek(fd, 0, 2);
+	strncpy(now, ctime(time(0)), sizeof(now));
+	now[28] = 0;
+	if(fprint(fd, "From %s %s\n", a->val, now) < 0)
+		sysfatal("writing mailbox: %r");
+
+	/* copy message handles escapes and any needed new lines */
+	bytes = appendfiletombox(0, fd);
+	if(bytes < 0)
+		sysfatal("writing mailbox: %r");
+
+	close(fd);
+	sysunlock(l);
+
+	/* log it */
+	syslog(0, "mail", "delivered %s From %s %s (%s) %d", deliveredto,
+		a->val, now, argv[0], bytes);
+	exits(0);
+}
blob - /dev/null
blob + 6c23dcc19c19a6f1a427af2a9e345506e4f93c2d (mode 644)
--- /dev/null
+++ src/cmd/upas/filterkit/list.c
@@ -0,0 +1,315 @@
+#include <u.h>
+#include <libc.h>
+#include <regexp.h>
+#include <libsec.h>
+#include <String.h>
+#include <bio.h>
+#include "dat.h"
+
+int debug;
+
+enum
+{
+	Tregexp=	(1<<0),		/* ~ */
+	Texact=		(1<<1),		/* = */
+};
+
+typedef struct Pattern Pattern;
+struct Pattern
+{
+	Pattern	*next;
+	int	type;
+	char	*arg;
+	int	bang;
+};
+
+String	*patternpath;
+Pattern	*patterns;
+String	*mbox;
+
+static void
+usage(void)
+{
+	fprint(2, "usage: %s 'check|add' patternfile addr [addr*]\n", argv0);
+	exits("usage");
+}
+
+/*
+ *  convert string to lower case
+ */
+static void
+mklower(char *p)
+{
+	int c;
+
+	for(; *p; p++){
+		c = *p;
+		if(c <= 'Z' && c >= 'A')
+			*p = c - 'A' + 'a';
+	}
+}
+
+/*
+ *  simplify an address, reduce to a domain
+ */
+static String*
+simplify(char *addr)
+{
+	int dots;
+	char *p, *at;
+	String *s;
+
+	mklower(addr);
+	at = strchr(addr, '@');
+	if(at == nil){
+		/* local address, make it an exact match */
+		s = s_copy("=");
+		s_append(s, addr);
+		return s;
+	}
+
+	/* copy up to the '@' sign */
+	at++;
+	s = s_copy("~");
+	for(p = addr; p < at; p++){
+		if(strchr(".*+?(|)\\[]^$", *p))
+			s_putc(s, '\\');
+		s_putc(s, *p);
+	}
+
+	/* just any address matching the two most significant domain elements */
+	s_append(s, "(.*\\.)?");
+	p = addr+strlen(addr);
+	dots = 0;
+	for(; p > at; p--){
+		if(*p != '.')
+			continue;
+		if(dots++ > 0){
+			p++;
+			break;
+		}
+	}
+	for(; *p; p++){
+		if(strchr(".*+?(|)\\[]^$", *p) != 0)
+			s_putc(s, '\\');
+		s_putc(s, *p);
+	}
+	s_terminate(s);
+
+	return s;
+}
+
+/*
+ *  link patterns in order
+ */
+static int
+newpattern(int type, char *arg, int bang)
+{
+	Pattern *p;
+	static Pattern *last;
+
+	mklower(arg);
+
+	p = mallocz(sizeof *p, 1);
+	if(p == nil)
+		return -1;
+	if(type == Tregexp){
+		p->arg = malloc(strlen(arg)+3);
+		if(p->arg == nil){
+			free(p);
+			return -1;
+		}
+		p->arg[0] = 0;
+		strcat(p->arg, "^");
+		strcat(p->arg, arg);
+		strcat(p->arg, "$");
+	} else {
+		p->arg = strdup(arg);
+		if(p->arg == nil){
+			free(p);
+			return -1;
+		}
+	}
+	p->type = type;
+	p->bang = bang;
+	if(last == nil)
+		patterns = p;
+	else
+		last->next = p;
+	last = p;
+
+	return 0;
+}
+
+/*
+ *  patterns are either
+ *	~ regular expression
+ *	= exact match string
+ *
+ *  all comparisons are case insensitive
+ */
+static int
+readpatterns(char *path)
+{
+	Biobuf *b;
+	char *p;
+	char *token[2];
+	int n;
+	int bang;
+
+	b = Bopen(path, OREAD);
+	if(b == nil)
+		return -1;
+	while((p = Brdline(b, '\n')) != nil){
+		p[Blinelen(b)-1] = 0;
+		n = tokenize(p, token, 2);
+		if(n == 0)
+			continue;
+
+		mklower(token[0]);
+		p = token[0];
+		if(*p == '!'){
+			p++;
+			bang = 1;
+		} else
+			bang = 0;
+
+		if(*p == '='){
+			if(newpattern(Texact, p+1, bang) < 0)
+				return -1;
+		} else if(*p == '~'){
+			if(newpattern(Tregexp, p+1, bang) < 0)
+				return -1;
+		} else if(strcmp(token[0], "#include") == 0 && n == 2)
+			readpatterns(token[1]);
+	}
+	Bterm(b);
+	return 0;
+}
+
+/* fuck, shit, bugger, damn */
+void regerror(char*)
+{
+}
+
+/*
+ *  check lower case version of address agains patterns
+ */
+static Pattern*
+checkaddr(char *arg)
+{
+	Pattern *p;
+	Reprog *rp;
+	String *s;
+
+	s = s_copy(arg);
+	mklower(s_to_c(s));
+
+	for(p = patterns; p != nil; p = p->next)
+		switch(p->type){
+		case Texact:
+			if(strcmp(p->arg, s_to_c(s)) == 0){
+				free(s);
+				return p;
+			}
+			break;
+		case Tregexp:
+			rp = regcomp(p->arg);
+			if(rp == nil)
+				continue;
+			if(regexec(rp, s_to_c(s), nil, 0)){
+				free(rp);
+				free(s);
+				return p;
+			}
+			free(rp);
+			break;
+		}
+	s_free(s);
+	return 0;
+}
+static char*
+check(int argc, char **argv)
+{
+	int i;
+	Addr *a;
+	Pattern *p;
+	int matchedbang;
+
+	matchedbang = 0;
+	for(i = 0; i < argc; i++){
+		a = readaddrs(argv[i], nil);
+		for(; a != nil; a = a->next){
+			p = checkaddr(a->val);
+			if(p == nil)
+				continue;
+			if(p->bang)
+				matchedbang = 1;
+			else
+				return nil;
+		}
+	}
+	if(matchedbang)
+		return "!match";
+	else
+		return "no match";
+}
+
+/*
+ *  add anything that isn't already matched, all matches are lower case
+ */
+static char*
+add(char *pp, int argc, char **argv)
+{
+	int fd, i;
+	String *s;
+	char *cp;
+	Addr *a;
+
+	a = nil;
+	for(i = 0; i < argc; i++)
+		a = readaddrs(argv[i], a);
+
+	fd = open(pp, OWRITE);
+	seek(fd, 0, 2);
+	for(; a != nil; a = a->next){
+		if(checkaddr(a->val))
+			continue;
+		s = simplify(a->val);
+		cp = s_to_c(s);
+		fprint(fd, "%q\t%q\n", cp, a->val);
+		if(*cp == '=')
+			newpattern(Texact, cp+1, 0);
+		else if(*cp == '~')
+			newpattern(Tregexp, cp+1, 0);
+		s_free(s);
+	}
+	close(fd);
+	return nil;	
+}
+
+void
+main(int argc, char **argv)
+{
+	char *patternpath;
+
+	ARGBEGIN {
+	case 'd':
+		debug++;
+		break;
+	} ARGEND;
+
+	quotefmtinstall();
+
+	if(argc < 3)
+		usage();
+
+	patternpath = argv[1];
+	readpatterns(patternpath);
+	if(strcmp(argv[0], "add") == 0)
+		exits(add(patternpath, argc-2, argv+2));
+	else if(strcmp(argv[0], "check") == 0)
+		exits(check(argc-2, argv+2));
+	else
+		usage();
+}
blob - /dev/null
blob + c077d56411bd1392db7b4aee235e33aea2b9cd61 (mode 644)
--- /dev/null
+++ src/cmd/upas/filterkit/mkfile
@@ -0,0 +1,21 @@
+</$objtype/mkfile
+
+TARG=\
+	token\
+	list\
+	deliver\
+
+LIB=../common/libcommon.a$O\
+
+BIN=/$objtype/bin/upas
+OFILES=readaddrs.$O
+UPDATE=\
+	mkfile\
+	${TARG:%=%.c}\
+	pipeto.sample\
+	pipefrom.sample\
+	pipeto.sample-hold\
+
+</sys/src/cmd/mkmany
+CFLAGS=$CFLAGS -I../common
+
blob - /dev/null
blob + 616bc683e1dcdb91110a041382f0613db63484c3 (mode 755)
--- /dev/null
+++ src/cmd/upas/filterkit/pipefrom.sample
@@ -0,0 +1,24 @@
+#!/bin/rc
+
+rfork e
+TMP=/tmp/myupassend.$pid
+
+# collect upas/send options
+options=()
+while (! ~ $#* 0 && ~ $1 -*) {
+	options=($options $1);
+	shift
+}
+
+# collect addresses and add them to my patterns
+dests=()
+while (! ~ $#* 0) {
+	dests=($dests $1);
+	shift
+}
+echo $dests > $TMP
+upas/list add /mail/box/$user/_pattern $TMP >[2] /dev/null
+rm $TMP
+
+# send mail
+upas/send $options $dests
blob - /dev/null
blob + 82ef9d1fcd03a221728f6c6d88a842ad66080b8d (mode 644)
--- /dev/null
+++ src/cmd/upas/filterkit/pipeto.sample
@@ -0,0 +1,73 @@
+#!/bin/rc
+
+# create a /tmp for here documents
+rfork en
+bind -c /mail/tmp /tmp
+
+KEY=whocares
+USER=ken
+
+RECIP=$1
+MBOX=$2
+PF=/mail/box/$USER/_pattern
+TMP=/mail/tmp/mine.$pid
+BIN=/bin/upas
+D=/mail/fs/mbox/1
+
+# save and parse the mail file
+{sed '/^$/,$ s/^From / From /'; echo} > $TMP
+upas/fs -f $TMP
+
+# if we like the source
+# or if the subject contains a valid token
+# then deliver the mail and allow all the addresses
+if( $BIN/list check $PF $D/from $D/sender $D/replyto )
+{
+	$BIN/deliver $RECIP $D/from $MBOX < $D/raw
+	$BIN/list add $PF $D/from $D/to $D/cc $D/sender
+	rm $TMP
+	exit 0
+}
+switch($status){
+case *!match*
+	echo `{date} dropped $RECIP From `{cat $D/replyto} >> /mail/box/$USER/_bounced >[2] /dev/null
+	rm $TMP
+	exit 0
+}
+if ( $BIN/token $KEY $D/subject )
+{
+	$BIN/deliver $RECIP $D/from $MBOX < $D/raw
+	$BIN/list add $PF $D/from $D/to $D/cc $D/sender
+	rm $TMP
+	echo `{date} added $RECIP From `{cat $D/replyto} \
+		>> /mail/box/$USER/_bounced >[2] /dev/null
+	exit 0
+}
+
+# don't recognize the sender so
+# return the message with instructions
+TOKEN=`{upas/token $KEY}
+upasname=/dev/null
+{{cat; cat $D/raw} | upas/send `{cat $D/replyto}}<<EOF
+Subject: $USER's mail filter
+I've been getting so much junk mail that I'm resorting to
+a draconian mechanism to avoid the mail.  In order
+to make sure that there's a real person sending mail, I'm
+asking you to explicitly enable access.  To do that, send
+mail to $USER at this domain with the token:
+	$TOKEN
+in the subject of your mail message.  After that, you
+shouldn't get any bounces from me.  Sorry if this is
+an inconvenience.
+
+----------------
+Original message
+----------------
+EOF
+
+echo `{date} bounced $RECIP From `{cat $D/replyto} \
+	>> /mail/box/$USER/_bounced >[2] /dev/null
+
+rv=$status
+rm $TMP
+exit $status
blob - /dev/null
blob + 5794f7322a00bf717bae0794d0cec6420ccf39af (mode 644)
--- /dev/null
+++ src/cmd/upas/filterkit/pipeto.sample-hold
@@ -0,0 +1,43 @@
+#!/bin/rc
+
+# create a /tmp for here documents
+rfork en
+bind -c /mail/tmp /tmp
+
+KEY=whocares
+USER=ken
+
+RECIP=$1
+MBOX=$2
+PF=/mail/box/$USER/_pattern
+TMP=/mail/tmp/mine.$pid
+BIN=/bin/upas
+D=/mail/fs/mbox/1
+
+# save and parse the mail file
+{sed '/^$/,$ s/^From / From /'; echo} > $TMP
+upas/fs -f $TMP
+
+# if we like the source
+# or if the subject contains a valid token
+# then deliver the mail and allow all the addresses
+if( $BIN/list check $PF $D/from $D/sender $D/replyto )
+{
+	$BIN/deliver $RECIP $D/from $MBOX < $D/raw
+	$BIN/list add $PF $D/from $D/to $D/cc $D/sender
+	rm $TMP
+	exit 0
+}
+switch($status){
+case *!match*
+	echo `{date} dropped $RECIP From `{cat $D/replyto} >> /mail/box/$USER/_bounced >[2] /dev/null
+	rm $TMP
+	exit 0
+}
+
+# don't recognize the sender so hold the message
+$BIN/deliver $RECIP $D/from /mail/box/$USER/_held < $D/raw
+
+rv=$status
+rm $TMP
+exit $status
blob - /dev/null
blob + 9aadc1ab188ecd8a280acc8fbbc1a7f59e682bae (mode 644)
--- /dev/null
+++ src/cmd/upas/filterkit/readaddrs.c
@@ -0,0 +1,98 @@
+#include <u.h>
+#include <libc.h>
+#include "dat.h"
+
+void*
+emalloc(int size)
+{
+	void *a;
+
+	a = mallocz(size, 1);
+	if(a == nil)
+		sysfatal("%r");
+	return a;
+}
+
+char*
+estrdup(char *s)
+{
+	s = strdup(s);
+	if(s == nil)
+		sysfatal("%r");
+	return s;
+}
+
+/*
+ * like tokenize but obey "" quoting
+ */
+int
+tokenize822(char *str, char **args, int max)
+{
+	int na;
+	int intok = 0, inquote = 0;
+
+	if(max <= 0)
+		return 0;	
+	for(na=0; ;str++)
+		switch(*str) {
+		case ' ':
+		case '\t':
+			if(inquote)
+				goto Default;
+			/* fall through */
+		case '\n':
+			*str = 0;
+			if(!intok)
+				continue;
+			intok = 0;
+			if(na < max)
+				continue;
+			/* fall through */
+		case 0:
+			return na;
+		case '"':
+			inquote ^= 1;
+			/* fall through */
+		Default:
+		default:
+			if(intok)
+				continue;
+			args[na++] = str;
+			intok = 1;
+		}
+	return 0;	/* can't get here; silence compiler */
+}
+
+Addr*
+readaddrs(char *file, Addr *a)
+{
+	int fd;
+	int i, n;
+	char buf[8*1024];
+	char *f[128];
+	Addr **l;
+	Addr *first;
+
+	/* add to end */
+	first = a;
+	for(l = &first; *l != nil; l = &(*l)->next)
+		;
+
+	/* read in the addresses */
+	fd = open(file, OREAD);
+	if(fd < 0)
+		return first;
+	n = read(fd, buf, sizeof(buf)-1);
+	close(fd);
+	if(n <= 0)
+		return first;
+	buf[n] = 0;
+
+	n = tokenize822(buf, f, nelem(f));
+	for(i = 0; i < n; i++){
+		*l = a = emalloc(sizeof *a);
+		l = &a->next;
+		a->val = estrdup(f[i]);
+	}
+	return first;
+}
blob - /dev/null
blob + 8fbcda668d5b6a865c5b7d870b1a9d19e4cde22a (mode 644)
--- /dev/null
+++ src/cmd/upas/filterkit/token.c
@@ -0,0 +1,89 @@
+#include <u.h>
+#include <libc.h>
+#include <libsec.h>
+#include <String.h>
+#include "dat.h"
+
+void
+usage(void)
+{
+	fprint(2, "usage: %s key [token]\n", argv0);
+	exits("usage");
+}
+
+static String*
+mktoken(char *key, long thetime)
+{
+	char *now;
+	uchar digest[SHA1dlen];
+	char token[64];
+	String *s;
+	
+	now = ctime(thetime);
+	memset(now+11, ':', 8);
+	hmac_sha1((uchar*)now, strlen(now), (uchar*)key, strlen(key), digest, nil);
+	enc64(token, sizeof token, digest, sizeof digest);
+	s = s_new();
+	s_nappend(s, token, 5);
+	return s;
+}
+
+static char*
+check_token(char *key, char *file)
+{
+	String *s;
+	long now;
+	int i;
+	char buf[1024];
+	int fd;
+
+	fd = open(file, OREAD);
+	if(fd < 0)
+		return "no match";
+	i = read(fd, buf, sizeof(buf)-1);
+	close(fd);
+	if(i < 0)
+		return "no match";
+	buf[i] = 0;
+	
+	now = time(0);
+
+	for(i = 0; i < 14; i++){
+		s = mktoken(key, now-24*60*60*i);
+		if(strstr(buf, s_to_c(s)) != nil){
+			s_free(s);
+			return nil;
+		}
+		s_free(s);
+	}
+	return "no match";
+}
+
+static char*
+create_token(char *key)
+{
+	String *s;
+
+	s = mktoken(key, time(0));
+	print("%s", s_to_c(s));
+	return nil;
+}
+
+void
+main(int argc, char **argv)
+{
+	ARGBEGIN {
+	} ARGEND;
+
+	switch(argc){
+	case 2:
+		exits(check_token(argv[0], argv[1]));
+		break;
+	case 1:
+		exits(create_token(argv[0]));
+		break;
+	default:
+		usage();
+	}
+	exits(0);
+}
blob - /dev/null
blob + ffcbf5b3caabb66243e11f1749e4d965b98815a5 (mode 644)
--- /dev/null
+++ src/cmd/upas/fs/dat.h
@@ -0,0 +1,221 @@
+typedef struct Message Message;
+struct Message
+{
+	int	id;
+	int	refs;
+	int	subname;
+	char	name[Elemlen];
+
+	// pointers into message
+	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	*lim;
+	char	deleted;
+	char	inmbox;
+	char	mallocd;	// message is malloc'd
+	char	ballocd;	// body is malloc'd
+	char	hallocd;	// header is malloce'd
+
+	// mail info
+	String	*unixheader;
+	String	*unixfrom;
+	String	*unixdate;
+	String	*from822;
+	String	*sender822;
+	String	*to822;
+	String	*bcc822;
+	String	*cc822;
+	String	*replyto822;
+	String	*date822;
+	String	*inreplyto822;
+	String	*subject822;
+	String	*messageid822;
+	String	*addrs;
+	String	*mimeversion;
+	String	*sdigest;
+
+	// mime info
+	String	*boundary;
+	String	*type;
+	int	encoding;
+	int	disposition;
+	String	*charset;
+	String	*filename;
+	int	converted;
+	int	decoded;
+	char	lines[10];	// number of lines in rawbody
+
+	Message	*next;		// same level
+	Message	*part;		// down a level
+	Message	*whole;		// up a level
+
+	uchar	digest[SHA1dlen];
+
+	vlong	imapuid;	// used by imap4
+
+	char		uidl[80];	// used by pop3
+	int		mesgno;
+};
+
+enum
+{
+	// encodings
+	Enone=	0,
+	Ebase64,
+	Equoted,
+
+	// disposition possibilities
+	Dnone=	0,
+	Dinline,
+	Dfile,
+	Dignore,
+
+	PAD64=	'=',
+};
+
+typedef struct Mailbox Mailbox;
+struct Mailbox
+{
+	QLock ql;      /* jpc named Qlock */
+	int	refs;
+	Mailbox	*next;
+	int	id;
+	int	dolock;		// lock when syncing?
+	int	std;
+	char	name[Elemlen];
+	char	path[Pathlen];
+	Dir	*d;
+	Message	*root;
+	int	vers;		// goes up each time mailbox is read
+
+	ulong waketime;
+	char	*(*sync)(Mailbox*, int);
+	void	(*close)(Mailbox*);
+	char	*(*fetch)(Mailbox*, Message*);
+	char	*(*ctl)(Mailbox*, int, char**);
+	void	*aux;		// private to Mailbox implementation
+};
+
+typedef char *Mailboxinit(Mailbox*, char*);
+
+extern Message	*root;
+extern Mailboxinit	plan9mbox;
+extern Mailboxinit	pop3mbox;
+extern Mailboxinit	imap4mbox;
+
+char*		syncmbox(Mailbox*, int);
+char*		geterrstr(void);
+void*		emalloc(ulong);
+void*		erealloc(void*, ulong);
+Message*	newmessage(Message*);
+void		delmessage(Mailbox*, Message*);
+void		delmessages(int, char**);
+int		newid(void);
+void		mailplumb(Mailbox*, Message*, int);
+char*		newmbox(char*, char*, int);
+void		freembox(char*);
+void		logmsg(char*, Message*);
+void		msgincref(Message*);
+void		msgdecref(Mailbox*, Message*);
+void		mboxincref(Mailbox*);
+void		mboxdecref(Mailbox*);
+void		convert(Message*);
+void		decode(Message*);
+int		cistrncmp(char*, char*, int);
+int		cistrcmp(char*, char*);
+int		latin1toutf(char*, char*, char*);
+int		windows1257toutf(char*, char*, char*);
+int		decquoted(char*, char*, char*);
+int		xtoutf(char*, char**, char*, char*);
+void		countlines(Message*);
+int		headerlen(Message*);
+void		parse(Message*, int, Mailbox*, int);
+void		parseheaders(Message*, int, Mailbox*, int);
+void		parsebody(Message*, Mailbox*);
+void		parseunix(Message*);
+String*	date822tounix(char*);
+int		fidmboxrefs(Mailbox*);
+int		hashmboxrefs(Mailbox*);
+void		checkmboxrefs(void);
+
+extern int	debug;
+extern int	fflag;
+extern int	logging;
+extern char	user[Elemlen];
+extern char	stdmbox[Pathlen];
+extern QLock	mbllock;
+extern Mailbox	*mbl;
+extern char	*mntpt;
+extern int	biffing;
+extern int	plumbing;
+extern char*	Enotme;
+
+enum
+{
+	/* mail subobjects */
+	Qbody,
+	Qbcc,
+	Qcc,
+	Qdate,
+	Qdigest,
+	Qdisposition,
+	Qfilename,
+	Qfrom,
+	Qheader,
+	Qinreplyto,
+	Qlines,
+	Qmimeheader,
+	Qmessageid,
+	Qraw,
+	Qrawbody,
+	Qrawheader,
+	Qrawunix,
+	Qreplyto,
+	Qsender,
+	Qsubject,
+	Qto,
+	Qtype,
+	Qunixheader,
+	Qinfo,
+	Qunixdate,
+	Qmax,
+
+	/* other files */
+	Qtop,
+	Qmbox,
+	Qdir,
+	Qctl,
+	Qmboxctl,
+};
+
+#define PATH(id, f)	((((id)&0xfffff)<<10) | (f))
+#define FILE(p)		((p) & 0x3ff)
+
+/* char *dirtab[]; jpc */
+
+// hash table to aid in name lookup, all files have an entry
+typedef struct Hash Hash;
+struct Hash {
+	Hash	*next;
+	char	*name;
+	ulong	ppath;
+	Qid	qid;
+	Mailbox	*mb;
+	Message	*m;
+};
+
+Hash	*hlook(ulong, char*);
+void	henter(ulong, char*, Qid, Message*, Mailbox*);
+void	hfree(ulong, char*);
+
+ulong msgallocd, msgfreed;
+
blob - /dev/null
blob + b68b8d2e3ef111262257f9a41dc05bef16bb1f0c (mode 644)
--- /dev/null
+++ src/cmd/upas/fs/fs.c
@@ -0,0 +1,1704 @@
+#include "common.h"
+#include <auth.h>
+#include <fcall.h>
+#include <libsec.h>
+#include <9pclient.h> /* jpc */
+#include <thread.h> /* jpc */
+#include "dat.h"
+
+enum
+{
+	OPERM	= 0x3,		// mask of all permission types in open mode
+};
+
+typedef struct Fid Fid;
+
+struct Fid
+{
+	Qid	qid;
+	short	busy;
+	short	open;
+	int	fid;
+	Fid	*next;
+	Mailbox	*mb;
+	Message	*m;
+	Message *mtop;		// top level message
+
+	//finger pointers to speed up reads of large directories
+	long	foff;	// offset/DIRLEN of finger
+	Message	*fptr;	// pointer to message at off
+	int	fvers;	// mailbox version when finger was saved
+};
+
+ulong	path;		// incremented for each new file
+Fid	*fids;
+int	mfd[2];
+char	user[Elemlen];
+int	messagesize = 4*1024*IOHDRSZ;
+uchar	mdata[8*1024*IOHDRSZ];
+uchar	mbuf[8*1024*IOHDRSZ];
+Fcall	thdr;
+Fcall	rhdr;
+int	fflg;
+char	*mntpt;
+int	biffing;
+int	plumbing = 1;
+
+QLock	mbllock;
+Mailbox	*mbl;
+
+Fid		*newfid(int);
+void		error(char*);
+void		io(void);
+void		*erealloc(void*, ulong);
+void		*emalloc(ulong);
+void		usage(void);
+void		run_io(void*);
+void		reader(void*);
+int		readheader(Message*, char*, int, int);
+int		cistrncmp(char*, char*, int);
+int		tokenconvert(String*, char*, int);
+String*		stringconvert(String*, char*, 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 *dirtab[] =
+{
+[Qdir]		".",
+[Qbody]		"body",
+[Qbcc]		"bcc",
+[Qcc]		"cc",
+[Qdate]		"date",
+[Qdigest]	"digest",
+[Qdisposition]	"disposition",
+[Qfilename]	"filename",
+[Qfrom]		"from",
+[Qheader]	"header",
+[Qinfo]		"info",
+[Qinreplyto]	"inreplyto",
+[Qlines]	"lines",
+[Qmimeheader]	"mimeheader",
+[Qmessageid]	"messageid",
+[Qraw]		"raw",
+[Qrawunix]	"rawunix",
+[Qrawbody]	"rawbody",
+[Qrawheader]	"rawheader",
+[Qreplyto]	"replyto",
+[Qsender]	"sender",
+[Qsubject]	"subject",
+[Qto]		"to",
+[Qtype]		"type",
+[Qunixdate]	"unixdate",
+[Qunixheader]	"unixheader",
+[Qctl]		"ctl",
+[Qmboxctl]	"ctl",
+};
+
+enum
+{
+	Hsize=	1277,
+};
+
+Hash	*htab[Hsize];
+
+int	debug;
+int	fflag;
+int	logging;
+
+void
+usage(void)
+{
+	fprint(2, "usage: %s [-b -m mountpoint]\n", argv0);
+	threadexits("usage");
+}
+
+void
+notifyf(void *a, char *s)
+{
+	USED(a);
+	if(strncmp(s, "interrupt", 9) == 0)
+		noted(NCONT);
+	noted(NDFLT);
+}
+
+void
+threadmain(int argc, char *argv[])
+{
+	int p[2], std, nodflt;
+	char maildir[128];
+	char mbox[128];
+	char *mboxfile, *err;
+	char srvfile[64];
+	int srvpost;
+
+	rfork(RFNOTEG);
+	mntpt = nil;
+	fflag = 0;
+	mboxfile = nil;
+	std = 0;
+	nodflt = 0;
+	srvpost = 0;
+
+	ARGBEGIN{
+	case 'b':
+		biffing = 1;
+		break;
+	case 'f':
+		fflag = 1;
+		mboxfile = ARGF();
+		break;
+	case 'm':
+		mntpt = ARGF();
+		break;
+	case 'd':
+		debug = 1;
+		break;
+	case 'p':
+		plumbing = 0;
+		break;
+	case 's':
+		srvpost = 1;
+		break;
+	case 'l':
+		logging = 1;
+		break;
+	case 'n':
+		nodflt = 1;
+		break;
+	default:
+		usage();
+	}ARGEND
+
+	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;
+		std = 1;
+	}
+
+	if(debug)
+		fmtinstall('F', fcallfmt);
+
+	if(mboxfile != nil){
+		err = newmbox(mboxfile, "mbox", std);
+		if(err != nil)
+			sysfatal("opening mailbox: %s", err);
+	}
+
+	switch(rfork(RFFDG|RFPROC|RFNAMEG|RFNOTEG)){ /* jpc removed RFEND */
+	case -1:
+		error("fork");
+	case 0:
+		henter(PATH(0, Qtop), dirtab[Qctl],
+			(Qid){PATH(0, Qctl), 0, QTFILE}, nil, nil);
+		close(p[1]);
+		io();
+		postnote(PNGROUP, getpid(), "die yankee pig dog");
+		break;
+	default:
+		close(p[0]);	/* don't deadlock if child fails */
+		if(srvpost){
+			sprint(srvfile, "/srv/upasfs.%s", user);
+			/* post(srvfile, "upasfs", p[1]);  jpc */
+			post9pservice(p[1], "upasfs");   /* jpc */
+		} else {
+			error("tried to mount, fixme");     /* jpc */
+			/* if(mount(p[1], -1, mntpt, MREPL, "") < 0)
+				error("mount failed");   jpc */
+		}
+	}
+	threadexits(0);
+}
+
+void run_io(void *v) {
+	int *p;
+
+	p =  v;
+	henter(PATH(0, Qtop), dirtab[Qctl],
+		(Qid){PATH(0, Qctl), 0, QTFILE}, nil, nil);
+	close(p[1]);
+	io();
+	postnote(PNGROUP, getpid(), "die yankee pig dog");
+}
+
+static int
+fileinfo(Message *m, int t, char **pp)
+{
+	char *p;
+	int len;
+
+	p = "";
+	len = 0;
+	switch(t){
+	case Qbody:
+		p = m->body;
+		len = m->bend - m->body;
+		break;
+	case Qbcc:
+		if(m->bcc822){
+			p = s_to_c(m->bcc822);
+			len = strlen(p);
+		}
+		break;
+	case Qcc:
+		if(m->cc822){
+			p = s_to_c(m->cc822);
+			len = strlen(p);
+		}
+		break;
+	case Qdisposition:
+		switch(m->disposition){
+		case Dinline:
+			p = "inline";
+			break;
+		case Dfile:
+			p = "file";
+			break;
+		}
+		len = strlen(p);
+		break;
+	case Qdate:
+		if(m->date822){
+			p = s_to_c(m->date822);
+			len = strlen(p);
+		} else if(m->unixdate != nil){
+			p = s_to_c(m->unixdate);
+			len = strlen(p);
+		}
+		break;
+	case Qfilename:
+		if(m->filename){
+			p = s_to_c(m->filename);
+			len = strlen(p);
+		}
+		break;
+	case Qinreplyto:
+		if(m->inreplyto822){
+			p = s_to_c(m->inreplyto822);
+			len = strlen(p);
+		}
+		break;
+	case Qmessageid:
+		if(m->messageid822){
+			p = s_to_c(m->messageid822);
+			len = strlen(p);
+		}
+		break;
+	case Qfrom:
+		if(m->from822){
+			p = s_to_c(m->from822);
+			len = strlen(p);
+		} else if(m->unixfrom != nil){
+			p = s_to_c(m->unixfrom);
+			len = strlen(p);
+		}
+		break;
+	case Qheader:
+		p = m->header;
+		len = headerlen(m);
+		break;
+	case Qlines:
+		p = m->lines;
+		if(*p == 0)
+			countlines(m);
+		len = strlen(m->lines);
+		break;
+	case Qraw:
+		p = m->start;
+		if(strncmp(m->start, "From ", 5) == 0){
+			p = strchr(p, '\n');
+			if(p == nil)
+				p = m->start;
+			else
+				p++;
+		}
+		len = m->end - p;
+		break;
+	case Qrawunix:
+		p = m->start;
+		len = m->end - p;
+		break;
+	case Qrawbody:
+		p = m->rbody;
+		len = m->rbend - p;
+		break;
+	case Qrawheader:
+		p = m->header;
+		len = m->hend - p;
+		break;
+	case Qmimeheader:
+		p = m->mheader;
+		len = m->mhend - p;
+		break;
+	case Qreplyto:
+		p = nil;
+		if(m->replyto822 != nil){
+			p = s_to_c(m->replyto822);
+			len = strlen(p);
+		} else if(m->from822 != nil){
+			p = s_to_c(m->from822);
+			len = strlen(p);
+		} else if(m->sender822 != nil){
+			p = s_to_c(m->sender822);
+			len = strlen(p);
+		} else if(m->unixfrom != nil){
+			p = s_to_c(m->unixfrom);
+			len = strlen(p);
+		}
+		break;
+	case Qsender:
+		if(m->sender822){
+			p = s_to_c(m->sender822);
+			len = strlen(p);
+		}
+		break;
+	case Qsubject:
+		p = nil;
+		if(m->subject822){
+			p = s_to_c(m->subject822);
+			len = strlen(p);
+		}
+		break;
+	case Qto:
+		if(m->to822){
+			p = s_to_c(m->to822);
+			len = strlen(p);
+		}
+		break;
+	case Qtype:
+		if(m->type){
+			p = s_to_c(m->type);
+			len = strlen(p);
+		}
+		break;
+	case Qunixdate:
+		if(m->unixdate){
+			p = s_to_c(m->unixdate);
+			len = strlen(p);
+		}
+		break;
+	case Qunixheader:
+		if(m->unixheader){
+			p = s_to_c(m->unixheader);
+			len = s_len(m->unixheader);
+		}
+		break;
+	case Qdigest:
+		if(m->sdigest){
+			p = s_to_c(m->sdigest);
+			len = strlen(p);
+		}
+		break;
+	}
+	*pp = p;
+	return len;
+}
+
+int infofields[] = {
+	Qfrom,
+	Qto,
+	Qcc,
+	Qreplyto,
+	Qunixdate,
+	Qsubject,
+	Qtype,
+	Qdisposition,
+	Qfilename,
+	Qdigest,
+	Qbcc,
+	Qinreplyto,
+	Qdate,
+	Qsender,
+	Qmessageid,
+	Qlines,
+	-1,
+};
+
+static int
+readinfo(Message *m, char *buf, long off, int count)
+{
+	char *p;
+	int len, i, n;
+	String *s;
+
+	s = s_new();
+	len = 0;
+	for(i = 0; len < count && infofields[i] >= 0; i++){
+		n = fileinfo(m, infofields[i], &p);
+		s = stringconvert(s, p, n);
+		s_append(s, "\n");
+		p = s_to_c(s);
+		n = strlen(p);
+		if(off > 0){
+			if(off >= n){
+				off -= n;
+				continue;
+			}
+			p += off;
+			n -= off;
+			off = 0;
+		}
+		if(n > count - len)
+			n = count - len;
+		if(buf)
+			memmove(buf+len, p, n);
+		len += n;
+	}
+	s_free(s);
+	return len;
+}
+
+static void
+mkstat(Dir *d, Mailbox *mb, Message *m, int t)
+{
+	char *p;
+
+	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(mb != nil && mb->d != nil){
+		d->atime = mb->d->atime;
+		d->mtime = 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 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:
+		d->name = dirtab[t];
+		d->length = readinfo(m, nil, 0, 1<<30);
+		d->qid.path = PATH(m->id, t);
+		break;
+	default:
+		d->name = dirtab[t];
+		d->length = fileinfo(m, t, &p);
+		d->qid.path = PATH(m->id, t);
+		break;
+	}
+}
+
+char*
+rversion(Fid* dummy)
+{
+	Fid *f;
+
+	if(thdr.msize < 256)
+		return "max messagesize too small";
+	if(thdr.msize < messagesize)
+		messagesize = thdr.msize;
+	rhdr.msize = messagesize;
+	if(strncmp(thdr.version, "9P2000", 6) != 0)
+		return "unknown 9P version";
+	else
+		rhdr.version = "9P2000";
+	for(f = fids; f; f = f->next)
+		if(f->busy)
+			rclunk(f);
+	return nil;
+}
+
+char*
+rauth(Fid* dummy)
+{
+	return Enoauth;
+}
+
+char*
+rflush(Fid *f)
+{
+	USED(f);
+	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;
+	nf->m = f->m;
+	nf->mtop = f->mtop;
+	nf->mb = f->mb;
+	if(f->mb != nil)
+		mboxincref(f->mb);
+	if(f->mtop != nil){
+		qlock(&f->mb->ql);
+		msgincref(f->mtop);
+		qunlock(&f->mb->ql);
+	}
+	nf->qid = f->qid;
+	return nf;
+}
+
+char*
+dowalk(Fid *f, char *name)
+{
+	int t;
+	Mailbox *omb, *mb;
+	char *rv, *p;
+	Hash *h;
+
+	t = FILE(f->qid.path);
+
+	rv = Enotexist;
+
+	omb = f->mb;
+	if(omb)
+		qlock(&omb->ql);
+	else
+		qlock(&mbllock);
+
+	// this must catch everything except . and ..
+retry:
+	h = hlook(f->qid.path, name);
+	if(h != nil){
+		f->mb = h->mb;
+		f->m = h->m;
+		switch(t){
+		case Qtop:
+			if(f->mb != nil)
+				mboxincref(f->mb);
+			break;
+		case Qmbox:
+			if(f->m){
+				msgincref(f->m);
+				f->mtop = f->m;
+			}
+			break;
+		}
+		f->qid = h->qid;
+		rv = nil;
+	} else if((p = strchr(name, '.')) != nil && *name != '.'){
+		*p = 0;
+		goto retry;
+	}
+
+	if(omb)
+		qunlock(&omb->ql);
+	else
+		qunlock(&mbllock);
+	if(rv == nil)
+		return rv;
+
+	if(strcmp(name, ".") == 0)
+		return nil;
+
+	if(f->qid.type != QTDIR)
+		return Enotdir;
+
+	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;
+			qlock(&mbllock);
+			mb = f->mb;
+			f->mb = nil;
+			mboxdecref(mb);
+			qunlock(&mbllock);
+			break;
+		case Qdir:
+			qlock(&f->mb->ql);
+			if(f->m->whole == f->mb->root){
+				f->qid.path = PATH(f->mb->id, Qmbox);
+				f->qid.type = QTDIR;
+				f->qid.vers = f->mb->d->qid.vers;
+				msgdecref(f->mb, f->mtop);
+				f->m = f->mtop = nil;
+			} else {
+				f->m = f->m->whole;
+				f->qid.path = PATH(f->m->id, Qdir);
+				f->qid.type = QTDIR;
+			}
+			qunlock(&f->mb->ql);
+			break;
+		}
+		rv = nil;
+	}
+	return rv;
+}
+
+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)
+			return Eperm;
+
+	// make sure we've decoded
+	if(file == Qbody){
+		if(f->m->decoded == 0)
+			decode(f->m);
+		if(f->m->converted == 0)
+			convert(f->m);
+	}
+
+	rhdr.iounit = 0;
+	rhdr.qid = f->qid;
+	f->open = 1;
+	return 0;
+}
+
+char *
+rcreate(Fid* dummy)
+{
+	return Eperm;
+}
+
+int
+readtopdir(Fid* dummy, 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 0;
+		n += m;
+		cnt -= m;
+	}
+	pos += m;
+		
+	for(mb = mbl; mb != nil; mb = mb->next){
+		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;
+
+	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 0;
+			}
+			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;
+
+		mkstat(&d, f->mb, msg, Qdir);
+		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++){
+		mkstat(&d, f->mb, f->m, i);
+		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){
+		mkstat(&d, f->mb, msg, Qdir);
+		m = convD2M(&d, &buf[n], blen-n);
+		if(off <= pos){
+			if(m <= BIT16SZ || m > cnt)
+				break;
+			n += m;
+			cnt -= m;
+		}
+		pos += m;
+	}
+
+	return n;
+}
+
+char*
+rread(Fid *f)
+{
+	long off;
+	int t, i, n, cnt;
+	char *p;
+
+	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) {
+			qlock(&mbllock);
+			n = readtopdir(f, mbuf, off, cnt, messagesize - IOHDRSZ);
+			qunlock(&mbllock);
+		} else if(t == Qmbox) {
+			qlock(&f->mb->ql);
+			if(off == 0)
+				syncmbox(f->mb, 1);
+			n = readmboxdir(f, mbuf, off, cnt, messagesize - IOHDRSZ);
+			qunlock(&f->mb->ql);
+		} else if(t == Qmboxctl) {
+			n = 0;
+		} else {
+			n = readmsgdir(f, mbuf, off, cnt, messagesize - IOHDRSZ);
+		}
+
+		rhdr.count = n;
+		return nil;
+	}
+
+	if(FILE(f->qid.path) == Qheader){
+		rhdr.count = readheader(f->m, (char*)mbuf, off, cnt);
+		return nil;
+	}
+
+	if(FILE(f->qid.path) == Qinfo){
+		rhdr.count = readinfo(f->m, (char*)mbuf, off, cnt);
+		return nil;
+	}
+
+	i = fileinfo(f->m, FILE(f->qid.path), &p);
+	if(off < i){
+		if((off + cnt) > i)
+			cnt = i - off;
+		memmove(mbuf, p + off, cnt);
+		rhdr.count = cnt;
+	}
+	return nil;
+}
+
+char*
+rwrite(Fid *f)
+{
+	char *err;
+	char *token[1024];
+	int t, n;
+	String *file;
+
+	t = FILE(f->qid.path);
+	rhdr.count = thdr.count;
+	switch(t){
+	case Qctl:
+		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;
+		n = tokenize(thdr.data, token, nelem(token));
+		if(n == 0)
+			return Ebadctl;
+		if(strcmp(token[0], "open") == 0){
+			file = s_new();
+			switch(n){
+			case 1:
+				err = Ebadctl;
+				break;
+			case 2:
+				mboxpath(token[1], getlog(), file, 0);
+				err = newmbox(s_to_c(file), nil, 0);
+				break;
+			default:
+				mboxpath(token[1], getlog(), file, 0);
+				if(strchr(token[2], '/') != nil)
+					err = "/ not allowed in mailbox name";
+				else
+					err = newmbox(s_to_c(file), token[2], 0);
+				break;
+			}
+			s_free(file);
+			return err;
+		}
+		if(strcmp(token[0], "close") == 0){
+			if(n < 2)
+				return nil;
+			freembox(token[1]);
+			return nil;
+		}
+		if(strcmp(token[0], "delete") == 0){
+			if(n < 3)
+				return nil;
+			delmessages(n-1, &token[1]);
+			return nil;
+		}
+		return Ebadctl;
+	case Qmboxctl:
+		if(f->mb && f->mb->ctl){
+			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;
+			n = tokenize(thdr.data, token, nelem(token));
+			if(n == 0)
+				return Ebadctl;
+			return (*f->mb->ctl)(f->mb, n, token);
+		}
+	}
+	return Eperm;
+}
+
+char *
+rclunk(Fid *f)
+{
+	Mailbox *mb;
+
+	f->busy = 0;
+	f->open = 0;
+	if(f->mtop != nil){
+		qlock(&f->mb->ql);
+		msgdecref(f->mb, f->mtop);
+		qunlock(&f->mb->ql);
+	}
+	f->m = f->mtop = nil;
+	mb = f->mb;
+	if(mb != nil){
+		f->mb = nil;
+		assert(mb->refs > 0);
+		qlock(&mbllock);
+		mboxdecref(mb);
+		qunlock(&mbllock);
+	}
+	f->fid = -1;
+	return 0;
+}
+
+char *
+rremove(Fid *f)
+{
+	if(f->m != nil){
+		if(f->m->deleted == 0)
+			mailplumb(f->mb, f->m, 1);
+		f->m->deleted = 1;
+	}
+	return rclunk(f);
+}
+
+char *
+rstat(Fid *f)
+{
+	Dir d;
+
+	if(FILE(f->qid.path) == Qmbox){
+		qlock(&f->mb->ql);
+		syncmbox(f->mb, 1);
+		qunlock(&f->mb->ql);
+	}
+	mkstat(&d, f->mb, f->m, FILE(f->qid.path));
+	rhdr.nstat = convD2M(&d, mbuf, messagesize - IOHDRSZ);
+	rhdr.stat = mbuf;
+	return 0;
+}
+
+char *
+rwstat(Fid* dummy)
+{
+	return Eperm;
+}
+
+Fid *
+newfid(int fid)
+{
+	Fid *f, *ff;
+
+	ff = 0;
+	for(f = fids; f; f = f->next)
+		if(f->fid == fid)
+			return 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;
+}
+
+int
+fidmboxrefs(Mailbox *mb)
+{
+	Fid *f;
+	int refs = 0;
+
+	for(f = fids; f; f = f->next){
+		if(f->mb == mb)
+			refs++;
+	}
+	return refs;
+}
+
+void
+io(void)
+{
+	char *err;
+	int n, nw;
+
+	/* start a process to watch the mailboxes*/
+	if(plumbing){
+		proccreate(reader, nil, 16000);
+#if 0 /* jpc */
+		switch(rfork(RFPROC|RFMEM)){
+		case -1:
+			/* oh well */
+			break;
+		case 0:
+			reader();
+			threadexits(nil);
+		default:
+			break;
+		}
+#endif /* jpc */
+	}
+
+	for(;;){
+		/*
+		 * reading from a pipe or a network device
+		 * will give an error after a few eof reads
+		 * however, we cannot tell the difference
+		 * between a zero-length read and an interrupt
+		 * on the processes writing to us,
+		 * so we wait for the error
+		 */
+		checkmboxrefs();
+		n = read9pmsg(mfd[0], mdata, messagesize);
+		if(n == 0)
+			continue;
+		if(n < 0)
+			return;
+		if(convM2S(mdata, n, &thdr) == 0)
+			continue;
+
+		if(debug)
+			fprint(2, "%s:<-%F\n", argv0, &thdr);
+
+		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;
+		if(debug)
+			fprint(2, "%s:->%F\n", argv0, &rhdr);/**/
+		n = convS2M(&rhdr, mdata, messagesize);
+		if((nw = write(mfd[1], mdata, n)) != n) {
+			fprint(2,"wrote %d bytes\n",nw);
+			error("mount write");
+		}
+	}
+}
+
+void
+reader(void *dummy)
+{
+	ulong t;
+	Dir *d;
+	Mailbox *mb;
+
+	sleep(15*1000);
+	for(;;){
+		t = time(0);
+		qlock(&mbllock);
+		for(mb = mbl; mb != nil; mb = mb->next){
+			assert(mb->refs > 0);
+			if(mb->waketime != 0 && t > mb->waketime){
+				qlock(&mb->ql);
+				mb->waketime = 0;
+				break;
+			}
+
+			d = dirstat(mb->path);
+			if(d == nil)
+				continue;
+
+			qlock(&mb->ql);
+			if(mb->d)
+			if(d->qid.path != mb->d->qid.path
+			   || d->qid.vers != mb->d->qid.vers){
+				free(d);
+				break;
+			}
+			qunlock(&mb->ql);
+			free(d);
+		}
+		qunlock(&mbllock);
+		if(mb != nil){
+			syncmbox(mb, 1);
+			qunlock(&mb->ql);
+		} else
+			sleep(15*1000);
+	}
+}
+
+int
+newid(void)
+{
+	int rv;
+	static int id;
+	static Lock idlock;
+
+	lock(&idlock);
+	rv = ++id;
+	unlock(&idlock);
+
+	return rv;
+}
+
+void
+error(char *s)
+{
+	postnote(PNGROUP, getpid(), "die yankee pig dog");
+	fprint(2, "%s: %s: %r\n", argv0, s);
+	threadexits(s);
+}
+
+
+typedef struct Ignorance Ignorance;
+struct Ignorance
+{
+	Ignorance *next;
+	char	*str;		/* string */
+	int	partial;	/* true if not exact match */
+};
+Ignorance *ignorance;
+
+/*
+ *  read the file of headers to ignore 
+ */
+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 = malloc(sizeof(Ignorance));
+		if(i == 0)
+			break;
+		i->partial = strlen(p);
+		i->str = strdup(p);
+		if(i->str == 0){
+			free(i);
+			break;
+		}
+		i->next = ignorance;
+		ignorance = i;
+	}
+	Bterm(b);
+}
+
+int
+ignore(char *p)
+{
+	Ignorance *i;
+
+	readignore();
+	for(i = ignorance; i != nil; i = i->next)
+		if(cistrncmp(i->str, p, i->partial) == 0)
+			return 1;
+	return 0;
+}
+
+int
+hdrlen(char *p, char *e)
+{
+	char *ep;
+
+	ep = p;
+	do {
+		ep = strchr(ep, '\n');
+		if(ep == nil){
+			ep = e;
+			break;
+		}
+		ep++;
+		if(ep >= e){
+			ep = e;
+			break;
+		}
+	} while(*ep == ' ' || *ep == '\t');
+	return ep - p;
+}
+
+// rfc2047 non-ascii
+typedef struct Charset Charset;
+struct Charset {
+	char *name;
+	int len;
+	int convert;
+	char *tcsname;
+} charsets[] =
+{
+	{ "us-ascii",		8,	1, nil, },
+	{ "utf-8",		5,	0, nil, },
+	{ "iso-8859-1",		10,	1, nil, },
+	{ "iso-8859-2",		10,	2, "8859-2", },
+	{ "big5",		4,	2, "big5", },
+	{ "iso-2022-jp",	11, 2, "jis", },
+	{ "windows-1251",	12,	2, "cp1251"},
+	{ "koi8-r",		6,	2, "koi8"},
+};
+
+int
+rfc2047convert(String *s, char *token, int len)
+{
+	char decoded[1024];
+	char utfbuf[2*1024];
+	int i;
+	char *e, *x;
+
+	if(len == 0)
+		return -1;
+
+	e = token+len-2;
+	token += 2;
+
+	// bail if we don't understand the character set
+	for(i = 0; i < nelem(charsets); i++)
+		if(cistrncmp(charsets[i].name, token, charsets[i].len) == 0)
+		if(token[charsets[i].len] == '?'){
+			token += charsets[i].len + 1;
+			break;
+		}
+	if(i >= nelem(charsets))
+		return -1;
+
+	// bail if it doesn't fit 
+	if(e-token > sizeof(decoded)-1)
+		return -1;
+
+	// bail if we don't understand the encoding
+	if(cistrncmp(token, "b?", 2) == 0){
+		token += 2;
+		len = dec64((uchar*)decoded, sizeof(decoded), token, e-token);
+		decoded[len] = 0;
+	} else if(cistrncmp(token, "q?", 2) == 0){
+		token += 2;
+		len = decquoted(decoded, token, e);
+		if(len > 0 && decoded[len-1] == '\n')
+			len--;
+		decoded[len] = 0;
+	} else
+		return -1;
+
+	switch(charsets[i].convert){
+	case 0:
+		s_append(s, decoded);
+		break;
+	case 1:
+		latin1toutf(utfbuf, decoded, decoded+len);
+		s_append(s, utfbuf);
+		break;
+	case 2:
+		if(xtoutf(charsets[i].tcsname, &x, decoded, decoded+len) <= 0){
+			s_append(s, decoded);
+		} else {
+			s_append(s, x);
+			free(x);
+		}
+		break;
+	}
+
+	return 0;
+}
+
+char*
+rfc2047start(char *start, char *end)
+{
+	int quests;
+
+	if(*--end != '=')
+		return nil;
+	if(*--end != '?')
+		return nil;
+
+	quests = 0;
+	for(end--; end >= start; end--){
+		switch(*end){
+		case '=':
+			if(quests == 3 && *(end+1) == '?')
+				return end;
+			break;
+		case '?':
+			++quests;
+			break;
+		case ' ':
+		case '\t':
+		case '\n':
+		case '\r':
+			/* can't have white space in a token */
+			return nil;
+		}
+	}
+	return nil;
+}
+
+// convert a header line
+String*
+stringconvert(String *s, char *uneaten, int len)
+{
+	char *token;
+	char *p;
+	int i;
+
+	s = s_reset(s);
+	p = uneaten;
+	for(i = 0; i < len; i++){
+		if(*p++ == '='){
+			token = rfc2047start(uneaten, p);
+			if(token != nil){
+				s_nappend(s, uneaten, token-uneaten);
+				if(rfc2047convert(s, token, p - token) < 0)
+					s_nappend(s, token, p - token);
+				uneaten = p;
+			}
+		}
+	}
+	if(p > uneaten)
+		s_nappend(s, uneaten, p-uneaten);
+	return s;
+}
+
+int
+readheader(Message *m, char *buf, int off, int cnt)
+{
+	char *p, *e;
+	int n, ns;
+	char *to = buf;
+	String *s;
+
+	p = m->header;
+	e = m->hend;
+	s = nil;
+
+	// copy in good headers
+	while(cnt > 0 && p < e){
+		n = hdrlen(p, e);
+		if(ignore(p)){
+			p += n;
+			continue;
+		}
+
+		// rfc2047 processing
+		s = stringconvert(s, p, n);
+		ns = s_len(s);
+		if(off > 0){
+			if(ns <= off){
+				off -= ns;
+				p += n;
+				continue;
+			}
+			ns -= off;
+		}
+		if(ns > cnt)
+			ns = cnt;
+		memmove(to, s_to_c(s)+off, ns);
+		to += ns;
+		p += n;
+		cnt -= ns;
+		off = 0;
+	}
+
+	s_free(s);
+	return to - buf;
+}
+
+int
+headerlen(Message *m)
+{
+	char buf[1024];
+	int i, n;
+
+	if(m->hlen >= 0)
+		return m->hlen;
+	for(n = 0; ; n += i){
+		i = readheader(m, buf, n, sizeof(buf));
+		if(i <= 0)
+			break;
+	}
+	m->hlen = n;
+	return n;
+}
+
+QLock hashlock;
+
+uint
+hash(ulong ppath, char *name)
+{
+	uchar *p;
+	uint h;
+
+	h = 0;
+	for(p = (uchar*)name; *p; p++)
+		h = h*7 + *p;
+	h += ppath;
+
+	return h % Hsize;
+}
+
+Hash*
+hlook(ulong ppath, char *name)
+{
+	int h;
+	Hash *hp;
+
+	qlock(&hashlock);
+	h = hash(ppath, name);
+	for(hp = htab[h]; hp != nil; hp = hp->next)
+		if(ppath == hp->ppath && strcmp(name, hp->name) == 0){
+			qunlock(&hashlock);
+			return hp;
+		}
+	qunlock(&hashlock);
+	return nil;
+}
+
+void
+henter(ulong ppath, char *name, Qid qid, Message *m, Mailbox *mb)
+{
+	int h;
+	Hash *hp, **l;
+
+	qlock(&hashlock);
+	h = hash(ppath, name);
+	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;
+			qunlock(&hashlock);
+			return;
+		}
+	}
+
+	*l = hp = emalloc(sizeof(*hp));
+	hp->m = m;
+	hp->mb = mb;
+	hp->qid = qid;
+	hp->name = name;
+	hp->ppath = ppath;
+	qunlock(&hashlock);
+}
+
+void
+hfree(ulong ppath, char *name)
+{
+	int h;
+	Hash *hp, **l;
+
+	qlock(&hashlock);
+	h = hash(ppath, name);
+	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;
+		}
+	}
+	qunlock(&hashlock);
+}
+
+int
+hashmboxrefs(Mailbox *mb)
+{
+	int h;
+	Hash *hp;
+	int refs = 0;
+
+	qlock(&hashlock);
+	for(h = 0; h < Hsize; h++){
+		for(hp = htab[h]; hp != nil; hp = hp->next)
+			if(hp->mb == mb)
+				refs++;
+	}
+	qunlock(&hashlock);
+	return refs;
+}
+
+void
+checkmboxrefs(void)
+{
+	int f, refs;
+	Mailbox *mb;
+
+	qlock(&mbllock);
+	for(mb=mbl; mb; mb=mb->next){
+		qlock(&mb->ql);
+		refs = (f=fidmboxrefs(mb))+1;
+		if(refs != mb->refs){
+			fprint(2, "mbox %s %s ref mismatch actual %d (%d+1) expected %d\n", mb->name, mb->path, refs, f, mb->refs);
+			abort();
+		}
+		qunlock(&mb->ql);
+	}
+	qunlock(&mbllock);
+}
+
+void
+post(char *name, char *envname, int srvfd)
+{
+	int fd;
+	char buf[32];
+
+	fd = create(name, OWRITE, 0600);
+	if(fd < 0)
+		error("post failed");
+	sprint(buf, "%d",srvfd);
+	if(write(fd, buf, strlen(buf)) != strlen(buf))
+		error("srv write");
+	close(fd);
+	putenv(envname, name);
+}
blob - /dev/null
blob + 152d15cf427a104df203fe444718537fdd1b6f18 (mode 644)
--- /dev/null
+++ src/cmd/upas/fs/imap4.c
@@ -0,0 +1,876 @@
+#include "common.h"
+#include <ctype.h>
+#include <plumb.h>
+#include <libsec.h>
+#include <auth.h>
+#include "dat.h"
+
+#pragma varargck argpos imap4cmd 2
+#pragma varargck	type	"Z"	char*
+
+int	doublequote(Fmt*);
+int	pipeline = 1;
+
+/* static char Eio[] = "i/o error"; jpc */
+
+typedef struct Imap Imap;
+struct Imap {
+	char *freep;	// free this to free the strings below
+
+	char *host;
+	char *user;
+	char *mbox;
+
+	int mustssl;
+	int refreshtime;
+	int debug;
+
+	ulong tag;
+	ulong validity;
+	int nmsg;
+	int size;
+	char *base;
+	char *data;
+
+	vlong *uid;
+	int nuid;
+	int muid;
+
+	Thumbprint *thumb;
+
+	// open network connection
+	Biobuf bin;
+	Biobuf bout;
+	int fd;
+};
+
+static char*
+removecr(char *s)
+{
+	char *r, *w;
+
+	for(r=w=s; *r; r++)
+		if(*r != '\r')
+			*w++ = *r;
+	*w = '\0';
+	return s;
+}
+
+//
+// send imap4 command
+//
+static void
+imap4cmd(Imap *imap, char *fmt, ...)
+{
+	char buf[128], *p;
+	va_list va;
+
+	va_start(va, fmt);
+	p = buf+sprint(buf, "9X%lud ", imap->tag);
+	vseprint(p, buf+sizeof(buf), fmt, va);
+	va_end(va);
+
+	p = buf+strlen(buf);
+	if(p > (buf+sizeof(buf)-3))
+		sysfatal("imap4 command too long");
+
+	if(imap->debug)
+		fprint(2, "-> %s\n", buf);
+	strcpy(p, "\r\n");
+	Bwrite(&imap->bout, buf, strlen(buf));
+	Bflush(&imap->bout);
+}
+
+enum {
+	OK,
+	NO,
+	BAD,
+	BYE,
+	EXISTS,
+	STATUS,
+	FETCH,
+	UNKNOWN,
+};
+
+static char *verblist[] = {
+[OK]		"OK",
+[NO]		"NO",
+[BAD]	"BAD",
+[BYE]	"BYE",
+[EXISTS]	"EXISTS",
+[STATUS]	"STATUS",
+[FETCH]	"FETCH",
+};
+
+static int
+verbcode(char *verb)
+{
+	int i;
+	char *q;
+
+	if(q = strchr(verb, ' '))
+		*q = '\0';
+
+	for(i=0; i<nelem(verblist); i++)
+		if(verblist[i] && strcmp(verblist[i], verb)==0){
+			if(q)
+				*q = ' ';
+			return i;
+		}
+	if(q)
+		*q = ' ';
+	return UNKNOWN;
+}
+
+static void
+strupr(char *s)
+{
+	for(; *s; s++)
+		if('a' <= *s && *s <= 'z')
+			*s += 'A'-'a';
+}
+
+static void
+imapgrow(Imap *imap, int n)
+{
+	int i;
+
+	if(imap->data == nil){
+		imap->base = emalloc(n+1);	
+		imap->data = imap->base;
+		imap->size = n+1;
+	}
+	if(n >= imap->size){
+		// friggin microsoft - reallocate
+		i = imap->data - imap->base;
+		imap->base = erealloc(imap->base, i+n+1);
+		imap->data = imap->base + i;
+		imap->size = n+1;
+	}
+}
+
+
+//
+// get imap4 response line.  there might be various 
+// data or other informational lines mixed in.
+//
+static char*
+imap4resp(Imap *imap)
+{
+	char *line, *p, *ep, *op, *q, *r, *en, *verb;
+	int i, n;
+	static char error[256];
+
+	while(p = Brdline(&imap->bin, '\n')){
+		ep = p+Blinelen(&imap->bin);
+		while(ep > p && (ep[-1]=='\n' || ep[-1]=='\r'))
+			*--ep = '\0';
+		
+		if(imap->debug)
+			fprint(2, "<- %s\n", p);
+		strupr(p);
+
+		switch(p[0]){
+		case '+':
+			if(imap->tag == 0)
+				fprint(2, "unexpected: %s\n", p);
+			break;
+
+		// ``unsolicited'' information; everything happens here.
+		case '*':
+			if(p[1]!=' ')
+				continue;
+			p += 2;
+			line = p;
+			n = strtol(p, &p, 10);
+			if(*p==' ')
+				p++;
+			verb = p;
+			
+			if(p = strchr(verb, ' '))
+				p++;
+			else
+				p = verb+strlen(verb);
+
+			switch(verbcode(verb)){
+			case OK:
+			case NO:
+			case BAD:
+				// human readable text at p;
+				break;
+			case BYE:
+				// early disconnect
+				// human readable text at p;
+				break;
+
+			// * 32 EXISTS
+			case EXISTS:
+				imap->nmsg = n;
+				break;
+
+			// * STATUS Inbox (MESSAGES 2 UIDVALIDITY 960164964)
+			case STATUS:
+				if(q = strstr(p, "MESSAGES"))
+					imap->nmsg = atoi(q+8);
+				if(q = strstr(p, "UIDVALIDITY"))
+					imap->validity = strtoul(q+11, 0, 10);
+				break;
+
+			case FETCH:
+				// * 1 FETCH (uid 8889 RFC822.SIZE 3031 body[] {3031}
+				// <3031 bytes of data>
+ 				// )
+				if(strstr(p, "RFC822.SIZE") && strstr(p, "BODY[]")){
+					if((q = strchr(p, '{')) 
+					&& (n=strtol(q+1, &en, 0), *en=='}')){
+						if(imap->data == nil || n >= imap->size)
+							imapgrow(imap, n);
+						if((i = Bread(&imap->bin, imap->data, n)) != n){
+							snprint(error, sizeof error,
+								"short read %d != %d: %r\n",
+								i, n);
+							return error;
+						}
+						if(imap->debug)
+							fprint(2, "<- read %d bytes\n", n);
+						imap->data[n] = '\0';
+						if(imap->debug)
+							fprint(2, "<- %s\n", imap->data);
+						imap->data += n;
+						imap->size -= n;
+						p = Brdline(&imap->bin, '\n');
+						if(imap->debug)
+							fprint(2, "<- ignoring %.*s\n",
+								Blinelen(&imap->bin), p);
+					}else if((q = strchr(p, '"')) && (r = strchr(q+1, '"'))){
+						*r = '\0';
+						q++;
+						n = r-q;
+						if(imap->data == nil || n >= imap->size)
+							imapgrow(imap, n);
+						memmove(imap->data, q, n);
+						imap->data[n] = '\0';
+						imap->data += n;
+						imap->size -= n;
+					}else
+						return "confused about FETCH response";
+					break;
+				}
+
+				// * 1 FETCH (UID 1 RFC822.SIZE 511)
+				if(q=strstr(p, "RFC822.SIZE")){
+					imap->size = atoi(q+11);
+					break;
+				}
+
+				// * 1 FETCH (UID 1 RFC822.HEADER {496}
+				// <496 bytes of data>
+ 				// )
+				// * 1 FETCH (UID 1 RFC822.HEADER "data")
+				if(strstr(p, "RFC822.HEADER") || strstr(p, "RFC822.TEXT")){
+					if((q = strchr(p, '{')) 
+					&& (n=strtol(q+1, &en, 0), *en=='}')){
+						if(imap->data == nil || n >= imap->size)
+							imapgrow(imap, n);
+						if((i = Bread(&imap->bin, imap->data, n)) != n){
+							snprint(error, sizeof error,
+								"short read %d != %d: %r\n",
+								i, n);
+							return error;
+						}
+						if(imap->debug)
+							fprint(2, "<- read %d bytes\n", n);
+						imap->data[n] = '\0';
+						if(imap->debug)
+							fprint(2, "<- %s\n", imap->data);
+						imap->data += n;
+						imap->size -= n;
+						p = Brdline(&imap->bin, '\n');
+						if(imap->debug)
+							fprint(2, "<- ignoring %.*s\n",
+								Blinelen(&imap->bin), p);
+					}else if((q = strchr(p, '"')) && (r = strchr(q+1, '"'))){
+						*r = '\0';
+						q++;
+						n = r-q;
+						if(imap->data == nil || n >= imap->size)
+							imapgrow(imap, n);
+						memmove(imap->data, q, n);
+						imap->data[n] = '\0';
+						imap->data += n;
+						imap->size -= n;
+					}else
+						return "confused about FETCH response";
+					break;
+				}
+
+				// * 1 FETCH (UID 1)
+				// * 2 FETCH (UID 6)
+				if(q = strstr(p, "UID")){
+					if(imap->nuid < imap->muid)
+						imap->uid[imap->nuid++] = ((vlong)imap->validity<<32)|strtoul(q+3, nil, 10);
+					break;
+				}
+			}
+
+			if(imap->tag == 0)
+				return line;
+			break;
+
+		case '9':		// response to our message
+			op = p;
+			if(p[1]=='X' && strtoul(p+2, &p, 10)==imap->tag){
+				while(*p==' ')
+					p++;
+				imap->tag++;
+				return p;
+			}
+			fprint(2, "expected %lud; got %s\n", imap->tag, op);
+			break;
+
+		default:
+			if(imap->debug || *p)
+				fprint(2, "unexpected line: %s\n", p);
+		}
+	}
+	snprint(error, sizeof error, "i/o error: %r\n");
+	return error;
+}
+
+static int
+isokay(char *resp)
+{
+	return strncmp(resp, "OK", 2)==0;
+}
+
+//
+// log in to IMAP4 server, select mailbox, no SSL at the moment
+//
+static char*
+imap4login(Imap *imap)
+{
+	char *s;
+	UserPasswd *up;
+
+	imap->tag = 0;
+	s = imap4resp(imap);
+	if(!isokay(s))
+		return "error in initial IMAP handshake";
+
+	if(imap->user != nil)
+		up = auth_getuserpasswd(auth_getkey, "proto=pass service=imap server=%q user=%q", imap->host, imap->user);
+	else
+		up = auth_getuserpasswd(auth_getkey, "proto=pass service=imap server=%q", imap->host);
+	if(up == nil)
+		return "cannot find IMAP password";
+
+	imap->tag = 1;
+	imap4cmd(imap, "LOGIN %Z %Z", up->user, up->passwd);
+	free(up);
+	if(!isokay(s = imap4resp(imap)))
+		return s;
+
+	imap4cmd(imap, "SELECT %Z", imap->mbox);
+	if(!isokay(s = imap4resp(imap)))
+		return s;
+
+	return nil;
+}
+
+//
+// push tls onto a connection
+//
+int
+mypushtls(int fd)
+{
+	int p[2];
+	char buf[10];
+
+	if(pipe(p) < 0)
+		return -1;
+
+	switch(fork()){
+	case -1:
+		close(p[0]);
+		close(p[1]);
+		return -1;
+	case 0:
+		close(p[1]);
+		dup(p[0], 0);
+		dup(p[0], 1);
+		sprint(buf, "/fd/%d", fd);
+		execl("/bin/tlsrelay", "tlsrelay", "-f", buf, nil);
+		_exits(nil);
+	default:
+		break;
+	}
+	close(fd);
+	close(p[0]);
+	return p[1];
+}
+
+//
+// dial and handshake with the imap server
+//
+static char*
+imap4dial(Imap *imap)
+{
+	char *err, *port;
+	uchar digest[SHA1dlen];
+	int sfd;
+	TLSconn conn;
+
+	if(imap->fd >= 0){
+		imap4cmd(imap, "noop");
+		if(isokay(imap4resp(imap)))
+			return nil;
+		close(imap->fd);
+		imap->fd = -1;
+	}
+
+	if(imap->mustssl)
+		port = "imaps";
+	else
+		port = "imap4";
+
+	if((imap->fd = dial(netmkaddr(imap->host, "net", port), 0, 0, 0)) < 0)
+		return geterrstr();
+
+	if(imap->mustssl){
+		memset(&conn, 0, sizeof conn);
+		sfd = tlsClient(imap->fd, &conn);
+		if(sfd < 0)
+			sysfatal("tlsClient: %r");
+		if(conn.cert==nil || conn.certlen <= 0)
+			sysfatal("server did not provide TLS certificate");
+		sha1(conn.cert, conn.certlen, digest, nil);
+		if(!imap->thumb || !okThumbprint(digest, imap->thumb)){
+			fmtinstall('H', encodefmt);
+			sysfatal("server certificate %.*H not recognized", SHA1dlen, digest);
+		}
+		free(conn.cert);
+		close(imap->fd);
+		imap->fd = sfd;
+
+		if(imap->debug){
+			char fn[128];
+			int fd;
+
+			snprint(fn, sizeof fn, "%s/ctl", conn.dir);
+			fd = open(fn, ORDWR);
+			if(fd < 0)
+				fprint(2, "opening ctl: %r\n");
+			if(fprint(fd, "debug") < 0)
+				fprint(2, "writing ctl: %r\n");
+			close(fd);
+		}
+	}
+	Binit(&imap->bin, imap->fd, OREAD);
+	Binit(&imap->bout, imap->fd, OWRITE);
+
+	if(err = imap4login(imap)) {
+		close(imap->fd);
+		return err;
+	}
+
+	return nil;
+}
+
+//
+// close connection
+//
+#if 0  /* jpc */
+static void
+imap4hangup(Imap *imap)
+{
+	imap4cmd(imap, "LOGOUT");
+	imap4resp(imap);
+	close(imap->fd);
+}
+#endif
+
+//
+// download a single message
+//
+static char*
+imap4fetch(Mailbox *mb, Message *m)
+{
+	int i;
+	char *p, *s, sdigest[2*SHA1dlen+1];
+	Imap *imap;
+
+	imap = mb->aux;
+
+	imap->size = 0;
+
+	if(!isokay(s = imap4resp(imap)))
+		return s;
+
+	p = imap->base;
+	if(p == nil)
+		return "did not get message body";
+
+	removecr(p);
+	free(m->start);
+	m->start = p;
+	m->end = p+strlen(p);
+	m->bend = m->rbend = m->end;
+	m->header = m->start;
+
+	imap->base = nil;
+	imap->data = nil;
+
+	parse(m, 0, mb, 1);
+
+	// digest headers
+	sha1((uchar*)m->start, m->end - m->start, m->digest, nil);
+	for(i = 0; i < SHA1dlen; i++)
+		sprint(sdigest+2*i, "%2.2ux", m->digest[i]);
+	m->sdigest = s_copy(sdigest);
+
+	return nil;
+}
+
+//
+// check for new messages on imap4 server
+// download new messages, mark deleted messages
+//
+static char*
+imap4read(Imap *imap, Mailbox *mb, int doplumb)
+{
+	char *s;
+	int i, ignore, nnew, t;
+	Message *m, *next, **l;
+
+	imap4cmd(imap, "STATUS %Z (MESSAGES UIDVALIDITY)", imap->mbox);
+	if(!isokay(s = imap4resp(imap)))
+		return s;
+
+	imap->nuid = 0;
+	imap->uid = erealloc(imap->uid, imap->nmsg*sizeof(imap->uid[0]));
+	imap->muid = imap->nmsg;
+
+	if(imap->nmsg > 0){
+		imap4cmd(imap, "UID FETCH 1:* UID");
+		if(!isokay(s = imap4resp(imap)))
+			return s;
+	}
+
+	l = &mb->root->part;
+	for(i=0; i<imap->nuid; i++){
+		ignore = 0;
+		while(*l != nil){
+			if((*l)->imapuid == imap->uid[i]){
+				ignore = 1;
+				l = &(*l)->next;
+				break;
+			}else{
+				// old mail, we don't have it anymore
+				if(doplumb)
+					mailplumb(mb, *l, 1);
+				(*l)->inmbox = 0;
+				(*l)->deleted = 1;
+				l = &(*l)->next;
+			}
+		}
+		if(ignore)
+			continue;
+
+		// new message
+		m = newmessage(mb->root);
+		m->mallocd = 1;
+		m->inmbox = 1;
+		m->imapuid = imap->uid[i];
+
+		// add to chain, will download soon
+		*l = m;
+		l = &m->next;
+	}
+
+	// whatever is left at the end of the chain is gone
+	while(*l != nil){
+		if(doplumb)
+			mailplumb(mb, *l, 1);
+		(*l)->inmbox = 0;
+		(*l)->deleted = 1;
+		l = &(*l)->next;
+	}
+
+	// download new messages
+	t = imap->tag;
+	if(pipeline)
+	switch(rfork(RFPROC|RFMEM)){
+	case -1:
+		sysfatal("rfork: %r");
+	default:
+		break;
+	case 0:
+		for(m = mb->root->part; m != nil; m = m->next){
+			if(m->start != nil)
+				continue;
+			if(imap->debug)
+				fprint(2, "9X%d UID FETCH %lud (UID RFC822.SIZE BODY[])\r\n",
+					t, (ulong)m->imapuid);
+			Bprint(&imap->bout, "9X%d UID FETCH %lud (UID RFC822.SIZE BODY[])\r\n",
+				t++, (ulong)m->imapuid);
+		}
+		Bflush(&imap->bout);
+		_exits(nil);
+	}
+
+	nnew = 0;
+	for(m=mb->root->part; m!=nil; m=next){
+		next = m->next;
+		if(m->start != nil)
+			continue;
+
+		if(!pipeline){
+			Bprint(&imap->bout, "9X%lud UID FETCH %lud (UID RFC822.SIZE BODY[])\r\n",
+				(ulong)imap->tag, (ulong)m->imapuid);
+			Bflush(&imap->bout);
+		}
+
+		if(s = imap4fetch(mb, m)){
+			// message disappeared?  unchain
+			fprint(2, "download %lud: %s\n", (ulong)m->imapuid, s);
+			delmessage(mb, m);
+			mb->root->subname--;
+			continue;
+		}
+		nnew++;
+		if(doplumb)
+			mailplumb(mb, m, 0);
+	}
+	if(pipeline)
+		waitpid();
+
+	if(nnew || mb->vers == 0){
+		mb->vers++;
+		henter(PATH(0, Qtop), mb->name,
+			(Qid){PATH(mb->id, Qmbox), mb->vers, QTDIR}, nil, mb);
+	}
+	return nil;
+}
+
+//
+// sync mailbox
+//
+static void
+imap4purge(Imap *imap, Mailbox *mb)
+{
+	int ndel;
+	Message *m, *next;
+
+	ndel = 0;
+	for(m=mb->root->part; m!=nil; m=next){
+		next = m->next;
+		if(m->deleted && m->refs==0){
+			if(m->inmbox && (ulong)(m->imapuid>>32)==imap->validity){
+				imap4cmd(imap, "UID STORE %lud +FLAGS (\\Deleted)", (ulong)m->imapuid);
+				if(isokay(imap4resp(imap))){
+					ndel++;
+					delmessage(mb, m);
+				}
+			}else
+				delmessage(mb, m);
+		}
+	}
+
+	if(ndel){
+		imap4cmd(imap, "EXPUNGE");
+		imap4resp(imap);
+	}
+}
+
+//
+// connect to imap4 server, sync mailbox
+//
+static char*
+imap4sync(Mailbox *mb, int doplumb)
+{
+	char *err;
+	Imap *imap;
+
+	imap = mb->aux;
+
+	if(err = imap4dial(imap)){
+		mb->waketime = time(0) + imap->refreshtime;
+		return err;
+	}
+
+	if((err = imap4read(imap, mb, doplumb)) == nil){
+		imap4purge(imap, mb);
+		mb->d->atime = mb->d->mtime = time(0);
+	}
+	/*
+	 * don't hang up; leave connection open for next time.
+	 */
+	// imap4hangup(imap);
+	mb->waketime = time(0) + imap->refreshtime;
+	return err;
+}
+
+static char Eimap4ctl[] = "bad imap4 control message";
+
+static char*
+imap4ctl(Mailbox *mb, int argc, char **argv)
+{
+	int n;
+	Imap *imap;
+
+	imap = mb->aux;
+	if(argc < 1)
+		return Eimap4ctl;
+
+	if(argc==1 && strcmp(argv[0], "debug")==0){
+		imap->debug = 1;
+		return nil;
+	}
+
+	if(argc==1 && strcmp(argv[0], "nodebug")==0){
+		imap->debug = 0;
+		return nil;
+	}
+
+	if(argc==1 && strcmp(argv[0], "thumbprint")==0){
+		if(imap->thumb)
+			freeThumbprints(imap->thumb);
+		imap->thumb = initThumbprints("/sys/lib/tls/mail", "/sys/lib/tls/mail.exclude");
+	}
+	if(strcmp(argv[0], "refresh")==0){
+		if(argc==1){
+			imap->refreshtime = 60;
+			return nil;
+		}
+		if(argc==2){
+			n = atoi(argv[1]);
+			if(n < 15)
+				return Eimap4ctl;
+			imap->refreshtime = n;
+			return nil;
+		}
+	}
+
+	return Eimap4ctl;
+}
+
+//
+// free extra memory associated with mb
+//
+static void
+imap4close(Mailbox *mb)
+{
+	Imap *imap;
+
+	imap = mb->aux;
+	free(imap->freep);
+	free(imap->base);
+	free(imap->uid);
+	if(imap->fd >= 0)
+		close(imap->fd);
+	free(imap);
+}
+
+//
+// open mailboxes of the form /imap/host/user
+//
+char*
+imap4mbox(Mailbox *mb, char *path)
+{
+	char *f[10];
+	int mustssl, nf;
+	Imap *imap;
+
+	quotefmtinstall();
+	fmtinstall('Z', doublequote);
+	if(strncmp(path, "/imap/", 6) != 0 && strncmp(path, "/imaps/", 7) != 0)
+		return Enotme;
+	mustssl = (strncmp(path, "/imaps/", 7) == 0);
+
+	path = strdup(path);
+	if(path == nil)
+		return "out of memory";
+
+	nf = getfields(path, f, 5, 0, "/");
+	if(nf < 3){
+		free(path);
+		return "bad imap path syntax /imap[s]/system[/user[/mailbox]]";
+	}
+
+	imap = emalloc(sizeof(*imap));
+	imap->fd = -1;
+	imap->debug = debug;
+	imap->freep = path;
+	imap->mustssl = mustssl;
+	imap->host = f[2];
+	if(nf < 4)
+		imap->user = nil;
+	else
+		imap->user = f[3];
+	if(nf < 5)
+		imap->mbox = "Inbox";
+	else
+		imap->mbox = f[4];
+	imap->thumb = initThumbprints("/sys/lib/tls/mail", "/sys/lib/tls/mail.exclude");
+
+	mb->aux = imap;
+	mb->sync = imap4sync;
+	mb->close = imap4close;
+	mb->ctl = imap4ctl;
+	mb->d = emalloc(sizeof(*mb->d));
+	//mb->fetch = imap4fetch;
+
+	return nil;
+}
+
+//
+// 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, '"');
+}
blob - /dev/null
blob + c539df8de3ff86d967699ad7de54d5d56b427021 (mode 644)
--- /dev/null
+++ src/cmd/upas/fs/mbox.c
@@ -0,0 +1,1601 @@
+#include "common.h"
+#include <ctype.h>
+#include <plumb.h>
+#include <libsec.h>
+#include <thread.h>
+#include "dat.h"
+
+extern char* dirtab[]; /* jpc */
+
+typedef struct Header Header;
+
+struct Header {
+	char *type;
+	void (*f)(Message*, Header*, char*);
+	int len;
+};
+
+/* headers */
+static	void	ctype(Message*, Header*, char*);
+static	void	cencoding(Message*, Header*, char*);
+static	void	cdisposition(Message*, Header*, char*);
+static	void	date822(Message*, Header*, char*);
+static	void	from822(Message*, Header*, char*);
+static	void	to822(Message*, Header*, char*);
+static	void	sender822(Message*, Header*, char*);
+static	void	replyto822(Message*, Header*, char*);
+static	void	subject822(Message*, Header*, char*);
+static	void	inreplyto822(Message*, Header*, char*);
+static	void	cc822(Message*, Header*, char*);
+static	void	bcc822(Message*, Header*, char*);
+static	void	messageid822(Message*, Header*, char*);
+static	void	mimeversion(Message*, Header*, char*);
+static	void	nullsqueeze(Message*);
+enum
+{
+	Mhead=	11,	/* offset of first mime header */
+};
+
+Header head[] =
+{
+	{ "date:", date822, },
+	{ "from:", from822, },
+	{ "to:", to822, },
+	{ "sender:", sender822, },
+	{ "reply-to:", replyto822, },
+	{ "subject:", subject822, },
+	{ "cc:", cc822, },
+	{ "bcc:", bcc822, },
+	{ "in-reply-to:", inreplyto822, },
+	{ "mime-version:", mimeversion, },
+	{ "message-id:", messageid822, },
+
+[Mhead]	{ "content-type:", ctype, },
+	{ "content-transfer-encoding:", cencoding, },
+	{ "content-disposition:", cdisposition, },
+	{ 0, },
+};
+
+/* static	void	fatal(char *fmt, ...); jpc */
+static	void	initquoted(void);
+/* static	void	startheader(Message*);
+static	void	startbody(Message*); jpc */
+static	char*	skipwhite(char*);
+static	char*	skiptosemi(char*);
+static	char*	getstring(char*, String*, int);
+static	void	setfilename(Message*, char*);
+/* static	char*	lowercase(char*); jpc */
+static	int	is8bit(Message*);
+static	int	headerline(char**, String*);
+static	void	initheaders(void);
+static void	parseattachments(Message*, Mailbox*);
+
+int		debug;
+
+char *Enotme = "path not served by this file server";
+
+enum
+{
+	Chunksize = 1024,
+};
+
+Mailboxinit *boxinit[] = {
+	imap4mbox,
+	pop3mbox,
+	plan9mbox,
+};
+
+char*
+syncmbox(Mailbox *mb, int doplumb)
+{
+	return (*mb->sync)(mb, doplumb);
+}
+
+/* create a new mailbox */
+char*
+newmbox(char *path, char *name, int std)
+{
+	Mailbox *mb, **l;
+	char *p, *rv;
+	int i;
+
+	initheaders();
+
+	mb = emalloc(sizeof(*mb));
+	strncpy(mb->path, path, sizeof(mb->path)-1);
+	if(name == 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);
+	} else {
+		strncpy(mb->name, name, sizeof(mb->name)-1);
+	}
+
+	rv = nil;
+	// check for a mailbox type
+	for(i=0; i<nelem(boxinit); i++)
+		if((rv = (*boxinit[i])(mb, path)) != Enotme)
+			break;
+	if(i == nelem(boxinit)){
+		free(mb);
+		return "bad path";
+	}
+
+	// on error, give up
+	if(rv){
+		free(mb);
+		return rv;
+	}
+
+	// make sure name isn't taken
+	qlock(&mbllock);
+	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);
+			qunlock(&mbllock);
+			return rv;
+		}
+	}
+
+	// always try locking
+	mb->dolock = 1;
+
+	mb->refs = 1;
+	mb->next = nil;
+	mb->id = newid();
+	mb->root = newmessage(nil);
+	mb->std = std;
+	*l = mb;
+	qunlock(&mbllock);
+
+	qlock(&mb->ql);
+	if(mb->ctl){
+		henter(PATH(mb->id, Qmbox), "ctl",
+			(Qid){PATH(mb->id, Qmboxctl), 0, QTFILE}, nil, mb);
+	}
+	rv = syncmbox(mb, 0);
+	qunlock(&mb->ql);
+
+	return rv;
+}
+
+// close the named mailbox
+void
+freembox(char *name)
+{
+	Mailbox **l, *mb;
+
+	qlock(&mbllock);
+	for(l=&mbl; *l != nil; l=&(*l)->next){
+		if(strcmp(name, (*l)->name) == 0){
+			mb = *l;
+			*l = mb->next;
+			mboxdecref(mb);
+			break;
+		}
+	}
+	hfree(PATH(0, Qtop), name);
+	qunlock(&mbllock);
+}
+
+static void
+initheaders(void)
+{
+	Header *h;
+	static int already;
+
+	if(already)
+		return;
+	already = 1;
+
+	for(h = head; h->type != nil; h++)
+		h->len = strlen(h->type);
+}
+
+/*
+ *  parse a Unix style header
+ */
+void
+parseunix(Message *m)
+{
+	char *p;
+	String *h;
+
+	h = s_new();
+	for(p = m->start + 5; *p && *p != '\r' && *p != '\n'; p++)
+		s_putc(h, *p);
+	s_terminate(h);
+	s_restart(h);
+
+	m->unixfrom = s_parse(h, s_reset(m->unixfrom));
+	m->unixdate = s_append(s_reset(m->unixdate), h->ptr);
+
+	s_free(h);
+}
+
+/*
+ *  parse a message
+ */
+void
+parseheaders(Message *m, int justmime, Mailbox *mb, int addfrom)
+{
+	String *hl;
+	Header *h;
+	char *p, *q;
+	int i;
+
+	if(m->whole == m->whole->whole){
+		henter(PATH(mb->id, Qmbox), m->name,
+			(Qid){PATH(m->id, Qdir), 0, QTDIR}, m, mb);
+	} else {
+		henter(PATH(m->whole->id, Qdir), m->name,
+			(Qid){PATH(m->id, Qdir), 0, QTDIR}, m, mb);
+	}
+	for(i = 0; i < Qmax; i++)
+		henter(PATH(m->id, Qdir), dirtab[i],
+			(Qid){PATH(m->id, i), 0, QTFILE}, m, mb);
+
+	// parse mime headers
+	p = m->header;
+	hl = s_new();
+	while(headerline(&p, hl)){
+		if(justmime)
+			h = &head[Mhead];
+		else
+			h = head;
+		for(; h->type; h++){
+			if(cistrncmp(s_to_c(hl), h->type, h->len) == 0){
+				(*h->f)(m, h, s_to_c(hl));
+				break;
+			}
+		}
+		s_reset(hl);
+	}
+	s_free(hl);
+
+	// 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;
+	}
+	if(*p == '\n')
+		p++;
+	m->rbody = m->body = p;
+
+	// if type is text, get any nulls out of the body.  This is
+	// for the two seans and imap clients that get confused.
+	if(strncmp(s_to_c(m->type), "text/", 5) == 0)
+		nullsqueeze(m);
+
+	//
+	// cobble together Unix-style from line
+	// for local mailbox messages, we end up recreating the
+	// original header.
+	// for pop3 messages, the best we can do is 
+	// use the From: information and the RFC822 date.
+	//
+	if(m->unixdate == nil || strcmp(s_to_c(m->unixdate), "???") == 0
+	|| strcmp(s_to_c(m->unixdate), "Thu Jan 1 00:00:00 GMT 1970") == 0){
+		if(m->unixdate){
+			s_free(m->unixdate);
+			m->unixdate = nil;
+		}
+		// 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.
+		if(cistrncmp(m->header, "received:", 9)==0){
+			if((q = strchr(m->header, ';')) != nil){
+				p = q;
+				while((p = strchr(p, '\n')) != nil){
+					if(p[1] != ' ' && p[1] != '\t' && p[1] != '\n')
+						break;
+					p++;
+				}
+				if(p){
+					*p = '\0';
+					m->unixdate = date822tounix(q+1);
+					*p = '\n';
+				}
+			}
+		}
+
+		// fall back on the rfc822 date	
+		if(m->unixdate==nil && m->date822)
+			m->unixdate = date822tounix(s_to_c(m->date822));
+	}
+
+	if(m->unixheader != nil)
+		s_free(m->unixheader);
+
+	// 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){
+		m->unixheader = nil;
+		return;
+	}
+
+	m->unixheader = s_copy("From ");
+	if(m->unixfrom && strcmp(s_to_c(m->unixfrom), "???") != 0)
+		s_append(m->unixheader, s_to_c(m->unixfrom));
+	else if(m->from822)
+		s_append(m->unixheader, s_to_c(m->from822));
+	else
+		s_append(m->unixheader, "???");
+
+	s_append(m->unixheader, " ");
+	if(m->unixdate)
+		s_append(m->unixheader, s_to_c(m->unixdate));
+	else
+		s_append(m->unixheader, "Thu Jan  1 00:00:00 GMT 1970");
+
+	s_append(m->unixheader, "\n");
+}
+
+String*
+promote(String **sp)
+{
+	String *s;
+
+	if(*sp != nil)
+		s = s_clone(*sp);
+	else
+		s = nil;
+	return s;
+}
+
+void
+parsebody(Message *m, Mailbox *mb)
+{
+	Message *nm;
+
+	// recurse
+	if(strncmp(s_to_c(m->type), "multipart/", 10) == 0){
+		parseattachments(m, mb);
+	} else if(strcmp(s_to_c(m->type), "message/rfc822") == 0){
+		decode(m);
+		parseattachments(m, mb);
+		nm = m->part;
+
+		// promote headers
+		if(m->replyto822 == nil && m->from822 == nil && m->sender822 == nil){
+			m->from822 = promote(&nm->from822);
+			m->to822 = promote(&nm->to822);
+			m->date822 = promote(&nm->date822);
+			m->sender822 = promote(&nm->sender822);
+			m->replyto822 = promote(&nm->replyto822);
+			m->subject822 = promote(&nm->subject822);
+			m->unixdate = promote(&nm->unixdate);
+		}
+	}
+}
+
+void
+parse(Message *m, int justmime, Mailbox *mb, int addfrom)
+{
+	parseheaders(m, justmime, mb, addfrom);
+	parsebody(m, mb);
+}
+
+static void
+parseattachments(Message *m, Mailbox *mb)
+{
+	Message *nm, **l;
+	char *p, *x;
+
+	// if there's a boundary, recurse...
+	if(m->boundary != nil){
+		p = m->body;
+		nm = nil;
+		l = &m->part;
+		for(;;){
+			x = strstr(p, s_to_c(m->boundary));
+
+			/* 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(s_to_c(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;
+			nm = newmessage(m);
+			nm->start = nm->header = nm->body = nm->rbody = ++p;
+			nm->mheader = nm->header;
+			*l = nm;
+			l = &nm->next;
+		}
+		for(nm = m->part; nm != nil; nm = nm->next)
+			parse(nm, 1, mb, 0);
+		return;
+	}
+
+	// if we've got an rfc822 message, recurse...
+	if(strcmp(s_to_c(m->type), "message/rfc822") == 0){
+		nm = newmessage(m);
+		m->part = nm;
+		nm->start = nm->header = nm->body = nm->rbody = m->body;
+		nm->end = nm->bend = nm->rbend = m->bend;
+		parse(nm, 0, mb, 0);
+	}
+}
+
+/*
+ *  pick up a header line
+ */
+static int
+headerline(char **pp, String *hl)
+{
+	char *p, *x;
+
+	s_reset(hl);
+	p = *pp;
+	x = strpbrk(p, ":\n");
+	if(x == nil || *x == '\n')
+		return 0;
+	for(;;){
+		x = strchr(p, '\n');
+		if(x == nil)
+			x = p + strlen(p);
+		s_nappend(hl, p, x-p);
+		p = x;
+		if(*p != '\n' || *++p != ' ' && *p != '\t')
+			break;
+		while(*p == ' ' || *p == '\t')
+			p++;
+		s_putc(hl, ' ');
+	}
+	*pp = p;
+	return 1;
+}
+
+static String*
+addr822(char *p)
+{
+	String *s, *list;
+	int incomment, addrdone, inanticomment, quoted;
+	int n;
+	int c;
+
+	list = s_new();
+	s = s_new();
+	quoted = incomment = addrdone = inanticomment = 0;
+	n = 0;
+	for(; *p; p++){
+		c = *p;
+
+		// whitespace is ignored
+		if(!quoted && isspace(c) || c == '\r')
+			continue;
+
+		// strings are always treated as atoms
+		if(!quoted && c == '"'){
+			if(!addrdone && !incomment)
+				s_putc(s, c);
+			for(p++; *p; p++){
+				if(!addrdone && !incomment)
+					s_putc(s, *p);
+				if(!quoted && *p == '"')
+					break;
+				if(*p == '\\')
+					quoted = 1;
+				else
+					quoted = 0;
+			}
+			if(*p == 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){
+			inanticomment = 1;
+			s = s_reset(s);
+			continue;
+		}
+		if(!quoted && c == '>' && inanticomment){
+			addrdone = 1;
+			inanticomment = 0;
+			continue;
+		}
+
+		// commas separate addresses
+		if(!quoted && c == ',' && !inanticomment){
+			s_terminate(s);
+			addrdone = 0;
+			if(n++ != 0)
+				s_append(list, " ");
+			s_append(list, s_to_c(s));
+			s = s_reset(s);
+			continue;
+		}
+
+		// what's left is part of the address
+		s_putc(s, c);
+
+		// quoted characters are recognized only as characters
+		if(c == '\\')
+			quoted = 1;
+		else
+			quoted = 0;
+
+	}
+
+	if(*s_to_c(s) != 0){
+		s_terminate(s);
+		if(n++ != 0)
+			s_append(list, " ");
+		s_append(list, s_to_c(s));
+	}
+	s_free(s);
+
+	if(n == 0){
+		s_free(list);
+		return nil;
+	}
+	return list;
+}
+
+static void
+to822(Message *m, Header *h, char *p)
+{
+	p += strlen(h->type);
+	s_free(m->to822);
+	m->to822 = addr822(p);
+}
+
+static void
+cc822(Message *m, Header *h, char *p)
+{
+	p += strlen(h->type);
+	s_free(m->cc822);
+	m->cc822 = addr822(p);
+}
+
+static void
+bcc822(Message *m, Header *h, char *p)
+{
+	p += strlen(h->type);
+	s_free(m->bcc822);
+	m->bcc822 = addr822(p);
+}
+
+static void
+from822(Message *m, Header *h, char *p)
+{
+	p += strlen(h->type);
+	s_free(m->from822);
+	m->from822 = addr822(p);
+}
+
+static void
+sender822(Message *m, Header *h, char *p)
+{
+	p += strlen(h->type);
+	s_free(m->sender822);
+	m->sender822 = addr822(p);
+}
+
+static void
+replyto822(Message *m, Header *h, char *p)
+{
+	p += strlen(h->type);
+	s_free(m->replyto822);
+	m->replyto822 = addr822(p);
+}
+
+static void
+mimeversion(Message *m, Header *h, char *p)
+{
+	p += strlen(h->type);
+	s_free(m->mimeversion);
+	m->mimeversion = addr822(p);
+}
+
+static void
+killtrailingwhite(char *p)
+{
+	char *e;
+
+	e = p + strlen(p) - 1;
+	while(e > p && isspace(*e))
+		*e-- = 0;
+}
+
+static void
+date822(Message *m, Header *h, char *p)
+{
+	p += strlen(h->type);
+	p = skipwhite(p);
+	s_free(m->date822);
+	m->date822 = s_copy(p);
+	p = s_to_c(m->date822);
+	killtrailingwhite(p);
+}
+
+static void
+subject822(Message *m, Header *h, char *p)
+{
+	p += strlen(h->type);
+	p = skipwhite(p);
+	s_free(m->subject822);
+	m->subject822 = s_copy(p);
+	p = s_to_c(m->subject822);
+	killtrailingwhite(p);
+}
+
+static void
+inreplyto822(Message *m, Header *h, char *p)
+{
+	p += strlen(h->type);
+	p = skipwhite(p);
+	s_free(m->inreplyto822);
+	m->inreplyto822 = s_copy(p);
+	p = s_to_c(m->inreplyto822);
+	killtrailingwhite(p);
+}
+
+static void
+messageid822(Message *m, Header *h, char *p)
+{
+	p += strlen(h->type);
+	p = skipwhite(p);
+	s_free(m->messageid822);
+	m->messageid822 = s_copy(p);
+	p = s_to_c(m->messageid822);
+	killtrailingwhite(p);
+}
+
+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 void
+ctype(Message *m, Header *h, char *p)
+{
+	String *s;
+
+	p += h->len;
+	p = skipwhite(p);
+
+	p = getstring(p, m->type, 1);
+	
+	while(*p){
+		if(isattribute(&p, "boundary")){
+			s = s_new();
+			p = getstring(p, s, 0);
+			m->boundary = s_reset(m->boundary);
+			s_append(m->boundary, "--");
+			s_append(m->boundary, s_to_c(s));
+			s_free(s);
+		} 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")){
+			if(m->filename == nil)
+				setfilename(m, p);
+		} else if(isattribute(&p, "charset")){
+			p = getstring(p, s_reset(m->charset), 0);
+		}
+		
+		p = skiptosemi(p);
+	}
+}
+
+static void
+cencoding(Message *m, Header *h, char *p)
+{
+	p += h->len;
+	p = skipwhite(p);
+	if(cistrncmp(p, "base64", 6) == 0)
+		m->encoding = Ebase64;
+	else if(cistrncmp(p, "quoted-printable", 16) == 0)
+		m->encoding = Equoted;
+}
+
+static void
+cdisposition(Message *m, Header *h, char *p)
+{
+	p += h->len;
+	p = skipwhite(p);
+	while(*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);
+		}
+		p = skiptosemi(p);
+	}
+
+}
+
+ulong msgallocd, msgfreed;
+
+Message*
+newmessage(Message *parent)
+{
+	/* static int id; jpc */
+	Message *m;
+
+	msgallocd++;
+
+	m = emalloc(sizeof(*m));
+	memset(m, 0, sizeof(*m));
+	m->disposition = Dnone;
+	m->type = s_copy("text/plain");
+	m->charset = s_copy("iso-8859-1");
+	m->id = newid();
+	if(parent)
+		sprint(m->name, "%d", ++(parent->subname));
+	if(parent == nil)
+		parent = m;
+	m->whole = parent;
+	m->hlen = -1;
+	return m;
+}
+
+// delete a message from a mailbox
+void
+delmessage(Mailbox *mb, Message *m)
+{
+	Message **l;
+	int i;
+
+	mb->vers++;
+	msgfreed++;
+
+	if(m->whole != m){
+		// unchain from parent
+		for(l = &m->whole->part; *l && *l != m; l = &(*l)->next)
+			;
+		if(*l != nil)
+			*l = m->next;
+
+		// 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);
+		for(i = 0; i < Qmax; i++)
+			hfree(PATH(m->id, Qdir), dirtab[i]);
+	}
+
+	/* recurse through sub-parts */
+	while(m->part)
+		delmessage(mb, m->part);
+
+	/* free memory */
+	if(m->mallocd)
+		free(m->start);
+	if(m->hallocd)
+		free(m->header);
+	if(m->ballocd)
+		free(m->body);
+	s_free(m->unixfrom);
+	s_free(m->unixdate);
+	s_free(m->unixheader);
+	s_free(m->from822);
+	s_free(m->sender822);
+	s_free(m->to822);
+	s_free(m->bcc822);
+	s_free(m->cc822);
+	s_free(m->replyto822);
+	s_free(m->date822);
+	s_free(m->inreplyto822);
+	s_free(m->subject822);
+	s_free(m->messageid822);
+	s_free(m->addrs);
+	s_free(m->mimeversion);
+	s_free(m->sdigest);
+	s_free(m->boundary);
+	s_free(m->type);
+	s_free(m->charset);
+	s_free(m->filename);
+
+	free(m);
+}
+
+// mark messages (identified by path) for deletion
+void
+delmessages(int ac, char **av)
+{
+	Mailbox *mb;
+	Message *m;
+	int i, needwrite;
+
+	qlock(&mbllock);
+	for(mb = mbl; mb != nil; mb = mb->next)
+		if(strcmp(av[0], mb->name) == 0){
+			qlock(&mb->ql);
+			break;
+		}
+	qunlock(&mbllock);
+	if(mb == nil)
+		return;
+
+	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){
+					mailplumb(mb, m, 1);
+					needwrite = 1;
+					m->deleted = 1;
+					logmsg("deleting", m);
+				}
+				break;
+			}
+	}
+	if(needwrite)
+		syncmbox(mb, 1);
+	qunlock(&mb->ql);
+}
+
+/*
+ *  the following are called with the mailbox qlocked
+ */
+void
+msgincref(Message *m)
+{
+	m->refs++;
+}
+void
+msgdecref(Mailbox *mb, Message *m)
+{
+	m->refs--;
+	if(m->refs == 0 && m->deleted)
+		syncmbox(mb, 1);
+}
+
+/*
+ *  the following are called with mbllock'd
+ */
+void
+mboxincref(Mailbox *mb)
+{
+	assert(mb->refs > 0);
+	mb->refs++;
+}
+void
+mboxdecref(Mailbox *mb)
+{
+	assert(mb->refs > 0);
+	qlock(&mb->ql);
+	mb->refs--;
+	if(mb->refs == 0){
+		delmessage(mb, mb->root);
+		if(mb->ctl)
+			hfree(PATH(mb->id, Qmbox), "ctl");
+		if(mb->close)
+			(*mb->close)(mb);
+		free(mb);
+	} else
+		qunlock(&mb->ql);
+}
+
+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 char*
+skipwhite(char *p)
+{
+	while(isspace(*p))
+		p++;
+	return p;
+}
+
+static char*
+skiptosemi(char *p)
+{
+	while(*p && *p != ';')
+		p++;
+	while(*p == ';' || isspace(*p))
+		p++;
+	return p;
+}
+
+static char*
+getstring(char *p, String *s, int dolower)
+{
+	s = s_reset(s);
+	p = skipwhite(p);
+	if(*p == '"'){
+		p++;
+		for(;*p && *p != '"'; p++)
+			if(dolower)
+				s_putc(s, tolower(*p));
+			else
+				s_putc(s, *p);
+		if(*p == '"')
+			p++;
+		s_terminate(s);
+
+		return p;
+	}
+
+	for(; *p && !isspace(*p) && *p != ';'; p++)
+		if(dolower)
+			s_putc(s, tolower(*p));
+		else
+			s_putc(s, *p);
+	s_terminate(s);
+
+	return p;
+}
+
+static void
+setfilename(Message *m, char *p)
+{
+	m->filename = s_reset(m->filename);
+	getstring(p, m->filename, 0);
+	for(p = s_to_c(m->filename); *p; p++)
+		if(*p == ' ' || *p == '\t' || *p == ';')
+			*p = '_';
+}
+
+//
+// undecode message body
+//
+void
+decode(Message *m)
+{
+	int i, len;
+	char *x;
+
+	if(m->decoded)
+		return;
+	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(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);
+		if(m->ballocd)
+			free(m->body);
+		m->body = x;
+		m->bend = x + len;
+		m->ballocd = 1;
+		break;
+	default:
+		break;
+	}
+	m->decoded = 1;
+}
+
+// convert latin1 to utf
+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;
+	if(m->part != nil)
+		return;
+	if(cistrncmp(s_to_c(m->type), "text", 4) != 0)
+		return;
+
+	if(cistrcmp(s_to_c(m->charset), "us-ascii") == 0 ||
+	   cistrcmp(s_to_c(m->charset), "iso-8859-1") == 0){
+		len = is8bit(m);
+		if(len > 0){
+			len = 2*len + m->bend - m->body + 1;
+			x = emalloc(len);
+			len = latin1toutf(x, m->body, m->bend);
+			if(m->ballocd)
+				free(m->body);
+			m->body = x;
+			m->bend = x + len;
+			m->ballocd = 1;
+		}
+	} else if(cistrcmp(s_to_c(m->charset), "iso-8859-2") == 0){
+		len = xtoutf("8859-2", &x, m->body, m->bend);
+		if(len != 0){
+			if(m->ballocd)
+				free(m->body);
+			m->body = x;
+			m->bend = x + len;
+			m->ballocd = 1;
+		}
+	} else if(cistrcmp(s_to_c(m->charset), "iso-8859-15") == 0){
+		len = xtoutf("8859-15", &x, m->body, m->bend);
+		if(len != 0){
+			if(m->ballocd)
+				free(m->body);
+			m->body = x;
+			m->bend = x + len;
+			m->ballocd = 1;
+		}
+	} else if(cistrcmp(s_to_c(m->charset), "big5") == 0){
+		len = xtoutf("big5", &x, m->body, m->bend);
+		if(len != 0){
+			if(m->ballocd)
+				free(m->body);
+			m->body = x;
+			m->bend = x + len;
+			m->ballocd = 1;
+		}
+	} else if(cistrcmp(s_to_c(m->charset), "iso-2022-jp") == 0){
+		len = xtoutf("jis", &x, m->body, m->bend);
+		if(len != 0){
+			if(m->ballocd)
+				free(m->body);
+			m->body = x;
+			m->bend = x + len;
+			m->ballocd = 1;
+		}
+	} else if(cistrcmp(s_to_c(m->charset), "windows-1257") == 0
+			|| cistrcmp(s_to_c(m->charset), "windows-1252") == 0){
+		len = is8bit(m);
+		if(len > 0){
+			len = 2*len + m->bend - m->body + 1;
+			x = emalloc(len);
+			len = windows1257toutf(x, m->body, m->bend);
+			if(m->ballocd)
+				free(m->body);
+			m->body = x;
+			m->bend = x + len;
+			m->ballocd = 1;
+		}
+	} else if(cistrcmp(s_to_c(m->charset), "windows-1251") == 0){
+		len = xtoutf("cp1251", &x, m->body, m->bend);
+		if(len != 0){
+			if(m->ballocd)
+				free(m->body);
+			m->body = x;
+			m->bend = x + len;
+			m->ballocd = 1;
+		}
+	} else if(cistrcmp(s_to_c(m->charset), "koi8-r") == 0){
+		len = xtoutf("koi8", &x, m->body, m->bend);
+		if(len != 0){
+			if(m->ballocd)
+				free(m->body);
+			m->body = x;
+			m->bend = x + len;
+			m->ballocd = 1;
+		}
+	}
+
+	m->converted = 1;
+}
+
+enum
+{
+	Self=	1,
+	Hex=	2,
+};
+uchar	tableqp[256];
+
+static void
+initquoted(void)
+{
+	int c;
+
+	memset(tableqp, 0, 256);
+	for(c = ' '; c <= '<'; c++)
+		tableqp[c] = Self;
+	for(c = '>'; c <= '~'; c++)
+		tableqp[c] = Self;
+	tableqp['\t'] = Self;
+	tableqp['='] = Hex;
+}
+
+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 0;
+}
+
+static char*
+decquotedline(char *out, char *in, char *e)
+{
+	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(tableqp[c]){
+		case Self:
+			*out++ = c;
+			break;
+		case Hex:
+			c = hex2int(*in++)<<4;
+			c |= hex2int(*in++);
+			*out++ = c;
+			break;
+		}
+	}
+	if(!soft)
+		*out++ = '\n';
+	*out = 0;
+
+	return out;
+}
+
+int
+decquoted(char *out, char *in, char *e)
+{
+	char *p, *nl;
+
+	if(tableqp[' '] == 0)
+		initquoted();
+
+	p = out;
+	while((nl = strchr(in, '\n')) != nil && nl < e){
+		p = decquotedline(p, in, nl);
+		in = nl + 1;
+	}
+	if(in < e)
+		p = decquotedline(p, in, e-1);
+
+	// make sure we end with a new line
+	if(*(p-1) != '\n'){
+		*p++ = '\n';
+		*p = 0;
+	}
+
+	return p - out;
+}
+
+#if 0 /* jpc */
+static char*
+lowercase(char *p)
+{
+	char *op;
+	int c;
+
+	for(op = p; c = *p; p++)
+		if(isupper(c))
+			*p = tolower(c);
+	return op;
+}
+#endif
+
+/*
+ *  return number of 8 bit characters
+ */
+static int
+is8bit(Message *m)
+{
+	int count = 0;
+	char *p;
+
+	for(p = m->body; p < m->bend; p++)
+		if(*p & 0x80)
+			count++;
+	return count;
+}
+
+// translate latin1 directly since it fits neatly in utf
+int
+latin1toutf(char *out, char *in, char *e)
+{
+	Rune r;
+	char *p;
+
+	p = out;
+	for(; in < e; in++){
+		r = (*in) & 0xff;
+		p += runetochar(p, &r);
+	}
+	*p = 0;
+	return p - out;
+}
+
+// translate any thing else using the tcs program
+int
+xtoutf(char *charset, char **out, char *in, char *e)
+{
+	char *av[4];
+	int totcs[2];
+	int fromtcs[2];
+	int n, len, sofar;
+	char *p;
+
+	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)
+		return 0;
+	if(pipe(fromtcs) < 0){
+		close(totcs[0]); close(totcs[1]);
+		return 0;
+	}
+	switch(rfork(RFPROC|RFFDG|RFNOWAIT)){
+	case -1:
+		close(fromtcs[0]); close(fromtcs[1]);
+		close(totcs[0]); close(totcs[1]);
+		return 0;
+	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);
+		//jpc exec("/bin/tcs", av);
+		exec(unsharp("#9/bin/tcs"), av);
+		/* _exits(0); */
+		threadexits(nil);
+	default:
+		close(fromtcs[1]); close(totcs[0]);
+		switch(rfork(RFPROC|RFFDG|RFNOWAIT)){
+		case -1:
+			close(fromtcs[0]); close(totcs[1]);
+			return 0;
+		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(0); */
+			threadexits(nil);
+		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;
+					*out = p = realloc(p, len+1);
+					if(p == nil)
+						return 0;
+				}
+			}
+			close(fromtcs[0]);
+			break;
+		}
+		break;
+	}
+	return sofar;
+}
+
+enum {
+	Winstart= 0x7f,
+	Winend= 0x9f,
+};
+
+Rune winchars[] = {
+	L'•',
+	L'•', L'•', L'‚', L'ƒ', L'„', L'…', L'†', L'‡',
+	L'ˆ', L'‰', L'Š', L'‹', L'Œ', L'•', L'•', L'•',
+	L'•', L'‘', L'’', L'“', L'”', L'•', L'–', L'—',
+	L'˜', L'™', L'š', L'›', L'œ', L'•', L'•', L'Ÿ',
+};
+
+int
+windows1257toutf(char *out, char *in, char *e)
+{
+	Rune r;
+	char *p;
+
+	p = out;
+	for(; in < e; in++){
+		r = (*in) & 0xff;
+		if(r >= 0x7f && r <= 0x9f)
+			r = winchars[r-0x7f];
+		p += runetochar(p, &r);
+	}
+	*p = 0;
+	return p - out;
+}
+
+void *
+emalloc(ulong n)
+{
+	void *p;
+
+	p = mallocz(n, 1);
+	if(!p){
+		fprint(2, "%s: out of memory alloc %lud\n", argv0, n);
+		threadexits("out of memory");
+	}
+	setmalloctag(p, getcallerpc(&n));
+	return p;
+}
+
+void *
+erealloc(void *p, ulong n)
+{
+	if(n == 0)
+		n = 1;
+	p = realloc(p, n);
+	if(!p){
+		fprint(2, "%s: out of memory realloc %lud\n", argv0, n);
+		threadexits("out of memory");
+	}
+	setrealloctag(p, getcallerpc(&p));
+	return p;
+}
+
+void
+mailplumb(Mailbox *mb, Message *m, int delete)
+{
+	Plumbmsg p;
+	Plumbattr a[7];
+	char buf[256];
+	int ai;
+	char lenstr[10], *from, *subject, *date;
+	static int fd = -1;
+
+	if(m->subject822 == nil)
+		subject = "";
+	else
+		subject = s_to_c(m->subject822);
+
+	if(m->from822 != nil)
+		from = s_to_c(m->from822);
+	else if(m->unixfrom != nil)
+		from = s_to_c(m->unixfrom);
+	else
+		from = "";
+
+	if(m->unixdate != nil)
+		date = s_to_c(m->unixdate);
+	else
+		date = "";
+
+	sprint(lenstr, "%ld", m->end-m->start);
+
+	if(biffing && !delete)
+		print("[ %s / %s / %s ]\n", from, subject, lenstr);
+
+	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 = lenstr;
+	a[ai-1].next = &a[ai];
+
+	a[++ai].name = "mailtype";
+	a[ai].value = delete?"delete":"new";
+	a[ai-1].next = &a[ai];
+
+	a[++ai].name = "date";
+	a[ai].value = date;
+	a[ai-1].next = &a[ai];
+
+	if(m->sdigest){
+		a[++ai].name = "digest";
+		a[ai].value = s_to_c(m->sdigest);
+		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;
+
+	plumbsend(fd, &p);
+}
+
+//
+// count the number of lines in the body (for imap4)
+//
+void
+countlines(Message *m)
+{
+	int i;
+	char *p;
+
+	i = 0;
+	for(p = strchr(m->rbody, '\n'); p != nil && p < m->rbend; p = strchr(p+1, '\n'))
+		i++;
+	sprint(m->lines, "%d", i);
+}
+
+char *LOG = "fs";
+
+void
+logmsg(char *s, Message *m)
+{
+	int pid;
+
+	if(!logging)
+		return;
+	pid = getpid();
+	if(m == nil)
+		syslog(0, LOG, "%s.%d: %s", user, pid, s);
+	else
+		syslog(0, LOG, "%s.%d: %s msg from %s digest %s",
+			user, pid, s,
+			m->from822 ? s_to_c(m->from822) : "?",
+			s_to_c(m->sdigest));
+}
+
+/*
+ *  squeeze nulls out of the body
+ */
+static void
+nullsqueeze(Message *m)
+{
+	char *p, *q;
+
+	q = memchr(m->body, 0, m->end-m->body);
+	if(q == nil)
+		return;
+
+	for(p = m->body; q < m->end; q++){
+		if(*q == 0)
+			continue;
+		*p++ = *q;
+	}
+	m->bend = m->rbend = m->end = p;
+}
+
+
+//
+// convert an RFC822 date into a Unix style date
+// for when the Unix From line isn't there (e.g. POP3).
+// enough client programs depend on having a Unix date
+// that it's easiest to write this conversion code once, right here.
+//
+// people don't follow RFC822 particularly closely,
+// so we use strtotm, which is a bunch of heuristics.
+//
+
+extern int strtotm(char*, Tm*);
+String*
+date822tounix(char *s)
+{
+	char *p, *q;
+	Tm tm;
+
+	if(strtotm(s, &tm) < 0)
+		return nil;
+
+	p = asctime(&tm);
+	if(q = strchr(p, '\n'))
+		*q = '\0';
+	return s_copy(p);
+}
+
blob - /dev/null
blob + c596ddfc89d4511a05916944b39a54afc7d2f592 (mode 644)
--- /dev/null
+++ src/cmd/upas/fs/mkfile
@@ -0,0 +1,29 @@
+<$PLAN9/src/mkhdr
+
+TARG=	fs\
+
+OFILES=\
+	fs.$O\
+	imap4.$O\
+	mbox.$O\
+	plan9.$O\
+	pop3.$O\
+	strtotm.$O\
+
+LIB=../common/libcommon.a\
+#	/usr/local/plan9/lib/libthread.a
+
+HFILES= ../common/common.h\
+	dat.h
+
+BIN=$PLAN9/bin/upas
+
+UPDATE=\
+	mkfile\
+	$HFILES\
+	${TARG:%=%.c}\
+	${OFILES:%.$O=%.c}\
+
+<$PLAN9/src/mkone
+CFLAGS=$CFLAGS  -I../common
+# CFLAGS=$CFLAGS -I/sys/include -I../common
blob - /dev/null
blob + 05239549cae34c5edc033138d4d29994fdf5f24b (mode 644)
--- /dev/null
+++ src/cmd/upas/fs/mkfile.9
@@ -0,0 +1,27 @@
+</$objtype/mkfile
+
+TARG=	fs\
+
+OFILES=\
+	fs.$O\
+	imap4.$O\
+	mbox.$O\
+	plan9.$O\
+	pop3.$O\
+	strtotm.$O\
+
+LIB=../common/libcommon.a$O\
+
+HFILES= ../common/common.h\
+	dat.h
+
+BIN=/$objtype/bin/upas
+
+UPDATE=\
+	mkfile\
+	$HFILES\
+	${TARG:%=%.c}\
+	${OFILES:%.$O=%.c}\
+
+</sys/src/cmd/mkone
+CFLAGS=$CFLAGS -I/sys/include -I../common
blob - /dev/null
blob + 89ee2922c01ed0e39497fea2723babdf98f866ac (mode 644)
--- /dev/null
+++ src/cmd/upas/fs/plan9.c
@@ -0,0 +1,405 @@
+#include "common.h"
+#include <ctype.h>
+#include <plumb.h>
+#include <libsec.h>
+#include "dat.h"
+
+enum {
+	Buffersize = 64*1024,
+};
+
+typedef struct Inbuf Inbuf;
+struct Inbuf
+{
+	int	fd;
+	uchar	*lim;
+	uchar	*rptr;
+	uchar	*wptr;
+	uchar	data[Buffersize+7];
+};
+
+static void
+addtomessage(Message *m, uchar *p, int n, int done)
+{
+	int i, len;
+
+	// add to message (+ 1 in malloc is for a trailing null)
+	if(m->lim - m->end < n){
+		if(m->start != nil){
+			i = m->end-m->start;
+			if(done)
+				len = i + n;
+			else
+				len = (4*(i+n))/3;
+			m->start = erealloc(m->start, len + 1);
+			m->end = m->start + i;
+		} else {
+			if(done)
+				len = n;
+			else
+				len = 2*n;
+			m->start = emalloc(len + 1);
+			m->end = m->start;
+		}
+		m->lim = m->start + len;
+	}
+
+	memmove(m->end, p, n);
+	m->end += n;
+}
+
+//
+//  read in a single message
+//
+static int
+readmessage(Message *m, Inbuf *inb)
+{
+	int i, n, done;
+	uchar *p, *np;
+	char sdigest[SHA1dlen*2+1];
+	char tmp[64];
+
+	for(done = 0; !done;){
+		n = inb->wptr - inb->rptr;
+		if(n < 6){
+			if(n)
+				memmove(inb->data, inb->rptr, n);
+			inb->rptr = inb->data;
+			inb->wptr = inb->rptr + n;
+			i = read(inb->fd, inb->wptr, Buffersize);
+			if(i < 0){
+				/* if(fd2path(inb->fd, tmp, sizeof tmp) < 0)
+					strcpy(tmp, "unknown mailbox");  jpc */
+				fprint(2, "error reading '%s': %r\n", tmp);
+				return -1;
+			}
+			if(i == 0){
+				if(n != 0)
+					addtomessage(m, inb->rptr, n, 1);
+				if(m->end == m->start)
+					return -1;
+				break;
+			}
+			inb->wptr += i;
+		}
+
+		// look for end of message
+		for(p = inb->rptr; p < inb->wptr; p = np+1){
+			// first part of search for '\nFrom '
+			np = memchr(p, '\n', inb->wptr - p);
+			if(np == nil){
+				p = inb->wptr;
+				break;
+			}
+
+			/*
+			 *  if we've found a \n but there's
+			 *  not enough room for '\nFrom ', don't do
+			 *  the comparison till we've read in more.
+			 */
+			if(inb->wptr - np < 6){
+				p = np;
+				break;
+			}
+
+			if(strncmp((char*)np, "\nFrom ", 6) == 0){
+				done = 1;
+				p = np+1;
+				break;
+			}
+		}
+
+		// add to message (+ 1 in malloc is for a trailing null)
+		n = p - inb->rptr;
+		addtomessage(m, inb->rptr, n, done);
+		inb->rptr += n;
+	}
+
+	// if it doesn't start with a 'From ', this ain't a mailbox
+	if(strncmp(m->start, "From ", 5) != 0)
+		return -1;
+
+	// dump trailing newline, make sure there's a trailing null
+	// (helps in body searches)
+	if(*(m->end-1) == '\n')
+		m->end--;
+	*m->end = 0;
+	m->bend = m->rbend = m->end;
+
+	// digest message
+	sha1((uchar*)m->start, m->end - m->start, m->digest, nil);
+	for(i = 0; i < SHA1dlen; i++)
+		sprint(sdigest+2*i, "%2.2ux", m->digest[i]);
+	m->sdigest = s_copy(sdigest);
+
+	return 0;
+}
+
+
+// throw out deleted messages.  return number of freshly deleted messages
+int
+purgedeleted(Mailbox *mb)
+{
+	Message *m, *next;
+	int newdels;
+
+	// forget about what's no longer in the mailbox
+	newdels = 0;
+	for(m = mb->root->part; m != nil; m = next){
+		next = m->next;
+		if(m->deleted && m->refs == 0){
+			if(m->inmbox)
+				newdels++;
+			delmessage(mb, m);
+		}
+	}
+	return newdels;
+}
+
+//
+//  read in the mailbox and parse into messages.
+//
+static char*
+_readmbox(Mailbox *mb, int doplumb, Mlock *lk)
+{
+	int fd;
+	String *tmp;
+	Dir *d;
+	static char err[128];
+	Message *m, **l;
+	Inbuf *inb;
+	char *x;
+
+	l = &mb->root->part;
+
+	/*
+	 *  open the mailbox.  If it doesn't exist, try the temporary one.
+	 */
+retry:
+	fd = open(mb->path, OREAD);
+	if(fd < 0){
+		errstr(err, sizeof(err));
+		if(strstr(err, "exist") != 0){
+			tmp = s_copy(mb->path);
+			s_append(tmp, ".tmp");
+			if(sysrename(s_to_c(tmp), mb->path) == 0){
+				s_free(tmp);
+				goto retry;
+			}
+			s_free(tmp);
+		}
+		return err;
+	}
+
+	/*
+	 *  a new qid.path means reread the mailbox, while
+	 *  a new qid.vers means read any new messages
+	 */
+	d = dirfstat(fd);
+	if(d == nil){
+		close(fd);
+		errstr(err, sizeof(err));
+		return err;
+	}
+	if(mb->d != nil){
+		if(d->qid.path == mb->d->qid.path && d->qid.vers == mb->d->qid.vers){
+			close(fd);
+			free(d);
+			return nil;
+		}
+		if(d->qid.path == mb->d->qid.path){
+			while(*l != nil)
+				l = &(*l)->next;
+			seek(fd, mb->d->length, 0);
+		}
+		free(mb->d);
+	}
+	mb->d = d;
+	mb->vers++;
+	henter(PATH(0, Qtop), mb->name,
+		(Qid){PATH(mb->id, Qmbox), mb->vers, QTDIR}, nil, mb);
+
+	inb = emalloc(sizeof(Inbuf));
+	inb->rptr = inb->wptr = inb->data;
+	inb->fd = fd;
+
+	//  read new messages
+	snprint(err, sizeof err, "reading '%s'", mb->path);
+	logmsg(err, nil);
+	for(;;){
+		if(lk != nil)
+			syslockrefresh(lk);
+		m = newmessage(mb->root);
+		m->mallocd = 1;
+		m->inmbox = 1;
+		if(readmessage(m, inb) < 0){
+			delmessage(mb, m);
+			mb->root->subname--;
+			break;
+		}
+
+		// merge mailbox versions
+		while(*l != nil){
+			if(memcmp((*l)->digest, m->digest, SHA1dlen) == 0){
+				// matches mail we already read, discard
+				logmsg("duplicate", *l);
+				delmessage(mb, m);
+				mb->root->subname--;
+				m = nil;
+				l = &(*l)->next;
+				break;
+			} else {
+				// old mail no longer in box, mark deleted
+				logmsg("disappeared", *l);
+				if(doplumb)
+					mailplumb(mb, *l, 1);
+				(*l)->inmbox = 0;
+				(*l)->deleted = 1;
+				l = &(*l)->next;
+			}
+		}
+		if(m == nil)
+			continue;
+
+		x = strchr(m->start, '\n');
+		if(x == nil)
+			m->header = m->end;
+		else
+			m->header = x + 1;
+		m->mheader = m->mhend = m->header;
+		parseunix(m);
+		parse(m, 0, mb, 0);
+		logmsg("new", m);
+
+		/* chain in */
+		*l = m;
+		l = &m->next;
+		if(doplumb)
+			mailplumb(mb, m, 0);
+
+	}
+	logmsg("mbox read", nil);
+
+	// whatever is left has been removed from the mbox, mark deleted
+	while(*l != nil){
+		if(doplumb)
+			mailplumb(mb, *l, 1);
+		(*l)->inmbox = 0;
+		(*l)->deleted = 1;
+		l = &(*l)->next;
+	}
+
+	close(fd);
+	free(inb);
+	return nil;
+}
+
+static void
+_writembox(Mailbox *mb, Mlock *lk)
+{
+	Dir *d;
+	Message *m;
+	String *tmp;
+	int mode, errs;
+	Biobuf *b;
+
+	tmp = s_copy(mb->path);
+	s_append(tmp, ".tmp");
+
+	/*
+	 * preserve old files permissions, if possible
+	 */
+	d = dirstat(mb->path);
+	if(d != nil){
+		mode = d->mode&0777;
+		free(d);
+	} else
+		mode = MBOXMODE;
+
+	sysremove(s_to_c(tmp));
+	b = sysopen(s_to_c(tmp), "alc", mode);
+	if(b == 0){
+		fprint(2, "can't write temporary mailbox %s: %r\n", s_to_c(tmp));
+		return;
+	}
+
+	logmsg("writing new mbox", nil);
+	errs = 0;
+	for(m = mb->root->part; m != nil; m = m->next){
+		if(lk != nil)
+			syslockrefresh(lk);
+		if(m->deleted)
+			continue;
+		logmsg("writing", m);
+		if(Bwrite(b, m->start, m->end - m->start) < 0)
+			errs = 1;
+		if(Bwrite(b, "\n", 1) < 0)
+			errs = 1;
+	}
+	logmsg("wrote new mbox", nil);
+
+	if(sysclose(b) < 0)
+		errs = 1;
+
+	if(errs){
+		fprint(2, "error writing temporary mail file\n");
+		s_free(tmp);
+		return;
+	}
+
+	sysremove(mb->path);
+	if(sysrename(s_to_c(tmp), mb->path) < 0)
+		fprint(2, "%s: can't rename %s to %s: %r\n", argv0,
+			s_to_c(tmp), mb->path);
+	s_free(tmp);
+	if(mb->d != nil)
+		free(mb->d);
+	mb->d = dirstat(mb->path);
+}
+
+char*
+plan9syncmbox(Mailbox *mb, int doplumb)
+{
+	Mlock *lk;
+	char *rv;
+
+	lk = nil;
+	if(mb->dolock){
+		lk = syslock(mb->path);
+		if(lk == nil)
+			return "can't lock mailbox";
+	}
+
+	rv = _readmbox(mb, doplumb, lk);		/* interpolate */
+	if(purgedeleted(mb) > 0)
+		_writembox(mb, lk);
+
+	if(lk != nil)
+		sysunlock(lk);
+
+	return rv;
+}
+
+//
+//  look to see if we can open this mail box
+//
+char*
+plan9mbox(Mailbox *mb, char *path)
+{
+	static char err[64];
+	String *tmp;
+
+	if(access(path, AEXIST) < 0){
+		errstr(err, sizeof(err));
+		tmp = s_copy(path);
+		s_append(tmp, ".tmp");
+		if(access(s_to_c(tmp), AEXIST) < 0){
+			s_free(tmp);
+			return err;
+		}
+		s_free(tmp);
+	}
+
+	mb->sync = plan9syncmbox;
+	return nil;
+}
blob - /dev/null
blob + 4ea9adb32ebdda4a9e3c4abb9e0e082dec9b2bb7 (mode 644)
--- /dev/null
+++ src/cmd/upas/fs/pop3.c
@@ -0,0 +1,700 @@
+#include "common.h"
+#include <ctype.h>
+#include <plumb.h>
+#include <libsec.h>
+#include <auth.h>
+#include <thread.h>
+#include "dat.h"
+
+#pragma varargck type "M" uchar*
+#pragma varargck argpos pop3cmd 2
+
+typedef struct Pop Pop;
+struct Pop {
+	char *freep;	// free this to free the strings below
+
+	char *host;
+	char *user;
+	char *port;
+
+	int ppop;
+	int refreshtime;
+	int debug;
+	int pipeline;
+	int encrypted;
+	int needtls;
+	int notls;
+	int needssl;
+
+	// open network connection
+	Biobuf bin;
+	Biobuf bout;
+	int fd;
+	char *lastline;	// from Brdstr
+
+	Thumbprint *thumb;
+};
+
+char*
+geterrstr(void)
+{
+	static char err[64];
+
+	err[0] = '\0';
+	errstr(err, sizeof(err));
+	return err;
+}
+
+//
+// get pop3 response line , without worrying
+// about multiline responses; the clients
+// will deal with that.
+//
+static int
+isokay(char *s)
+{
+	return s!=nil && strncmp(s, "+OK", 3)==0;
+}
+
+static void
+pop3cmd(Pop *pop, char *fmt, ...)
+{
+	char buf[128], *p;
+	va_list va;
+
+	va_start(va, fmt);
+	vseprint(buf, buf+sizeof(buf), fmt, va);
+	va_end(va);
+
+	p = buf+strlen(buf);
+	if(p > (buf+sizeof(buf)-3))
+		sysfatal("pop3 command too long");
+
+	if(pop->debug)
+		fprint(2, "<- %s\n", buf);
+	strcpy(p, "\r\n");
+	Bwrite(&pop->bout, buf, strlen(buf));
+	Bflush(&pop->bout);
+}
+
+static char*
+pop3resp(Pop *pop)
+{
+	char *s;
+	char *p;
+
+	alarm(60*1000);
+	if((s = Brdstr(&pop->bin, '\n', 0)) == nil){
+		close(pop->fd);
+		pop->fd = -1;
+		alarm(0);
+		return "unexpected eof";
+	}
+	alarm(0);
+
+	p = s+strlen(s)-1;
+	while(p >= s && (*p == '\r' || *p == '\n'))
+		*p-- = '\0';
+
+	if(pop->debug)
+		fprint(2, "-> %s\n", s);
+	free(pop->lastline);
+	pop->lastline = s;
+	return s;
+}
+
+#if 0 /* jpc */
+static int
+pop3log(char *fmt, ...)
+{
+	va_list ap;
+
+	va_start(ap,fmt);
+	syslog(0, "/sys/log/pop3", fmt, ap);
+	va_end(ap);
+	return 0;
+}
+#endif
+
+static char*
+pop3pushtls(Pop *pop)
+{
+	int fd;
+	uchar digest[SHA1dlen];
+	TLSconn conn;
+
+	memset(&conn, 0, sizeof conn);
+	// conn.trace = pop3log;
+	fd = tlsClient(pop->fd, &conn);
+	if(fd < 0)
+		return "tls error";
+	if(conn.cert==nil || conn.certlen <= 0){
+		close(fd);
+		return "server did not provide TLS certificate";
+	}
+	sha1(conn.cert, conn.certlen, digest, nil);
+	if(!pop->thumb || !okThumbprint(digest, pop->thumb)){
+		fmtinstall('H', encodefmt);
+		close(fd);
+		free(conn.cert);
+		fprint(2, "upas/fs pop3: server certificate %.*H not recognized\n", SHA1dlen, digest);
+		return "bad server certificate";
+	}
+	free(conn.cert);
+	close(pop->fd);
+	pop->fd = fd;
+	pop->encrypted = 1;
+	Binit(&pop->bin, pop->fd, OREAD);
+	Binit(&pop->bout, pop->fd, OWRITE);
+	return nil;
+}
+
+//
+// get capability list, possibly start tls
+//
+static char*
+pop3capa(Pop *pop)
+{
+	char *s;
+	int hastls;
+
+	pop3cmd(pop, "CAPA");
+	if(!isokay(pop3resp(pop)))
+		return nil;
+
+	hastls = 0;
+	for(;;){
+		s = pop3resp(pop);
+		if(strcmp(s, ".") == 0 || strcmp(s, "unexpected eof") == 0)
+			break;
+		if(strcmp(s, "STLS") == 0)
+			hastls = 1;
+		if(strcmp(s, "PIPELINING") == 0)
+			pop->pipeline = 1;
+	}
+
+	if(hastls && !pop->notls){
+		pop3cmd(pop, "STLS");
+		if(!isokay(s = pop3resp(pop)))
+			return s;
+		if((s = pop3pushtls(pop)) != nil)
+			return s;
+	}
+	return nil;
+}
+
+//
+// log in using APOP if possible, password if allowed by user
+//
+static char*
+pop3login(Pop *pop)
+{
+	int n;
+	char *s, *p, *q;
+	char ubuf[128], user[128];
+	char buf[500];
+	UserPasswd *up;
+
+	s = pop3resp(pop);
+	if(!isokay(s))
+		return "error in initial handshake";
+
+	if(pop->user)
+		snprint(ubuf, sizeof ubuf, " user=%q", pop->user);
+	else
+		ubuf[0] = '\0';
+
+	// look for apop banner
+	if(pop->ppop==0 && (p = strchr(s, '<')) && (q = strchr(p+1, '>'))) {
+		*++q = '\0';
+		if((n=auth_respond(p, q-p, user, sizeof user, buf, sizeof buf, auth_getkey, "proto=apop role=client server=%q%s",
+			pop->host, ubuf)) < 0)
+			return "factotum failed";
+		if(user[0]=='\0')
+			return "factotum did not return a user name";
+
+		if(s = pop3capa(pop))
+			return s;
+
+		pop3cmd(pop, "APOP %s %.*s", user, n, buf);
+		if(!isokay(s = pop3resp(pop)))
+			return s;
+
+		return nil;
+	} else {
+		if(pop->ppop == 0)
+			return "no APOP hdr from server";
+
+		if(s = pop3capa(pop))
+			return s;
+
+		if(pop->needtls && !pop->encrypted)
+			return "could not negotiate TLS";
+
+		up = auth_getuserpasswd(auth_getkey, "role=client proto=pass service=pop dom=%q%s",
+			pop->host, ubuf);
+		/* up = auth_getuserpasswd(auth_getkey, "proto=pass service=pop dom=%q%s",
+			pop->host, ubuf); jpc */
+		if(up == nil)
+			return "no usable keys found";
+
+		pop3cmd(pop, "USER %s", up->user);
+		if(!isokay(s = pop3resp(pop))){
+			free(up);
+			return s;
+		}
+		pop3cmd(pop, "PASS %s", up->passwd);
+		free(up);
+		if(!isokay(s = pop3resp(pop)))
+			return s;
+
+		return nil;
+	}
+}
+
+//
+// dial and handshake with pop server
+//
+static char*
+pop3dial(Pop *pop)
+{
+	char *err;
+
+	if((pop->fd = dial(netmkaddr(pop->host, "net", pop->needssl ? "pop3s" : "pop3"), 0, 0, 0)) < 0)
+		return geterrstr();
+
+	if(pop->needssl){
+		if((err = pop3pushtls(pop)) != nil)
+			return err;
+	}else{
+		Binit(&pop->bin, pop->fd, OREAD);
+		Binit(&pop->bout, pop->fd, OWRITE);
+	}
+
+	if(err = pop3login(pop)) {
+		close(pop->fd);
+		return err;
+	}
+
+	return nil;
+}
+
+//
+// close connection
+//
+static void
+pop3hangup(Pop *pop)
+{
+	pop3cmd(pop, "QUIT");
+	pop3resp(pop);
+	close(pop->fd);
+}
+
+//
+// download a single message
+//
+static char*
+pop3download(Pop *pop, Message *m)
+{
+	char *s, *f[3], *wp, *ep;
+	char sdigest[SHA1dlen*2+1];
+	int i, l, sz;
+
+	if(!pop->pipeline)
+		pop3cmd(pop, "LIST %d", m->mesgno);
+	if(!isokay(s = pop3resp(pop)))
+		return s;
+
+	if(tokenize(s, f, 3) != 3)
+		return "syntax error in LIST response";
+
+	if(atoi(f[1]) != m->mesgno)
+		return "out of sync with pop3 server";
+
+	sz = atoi(f[2])+200;	/* 200 because the plan9 pop3 server lies */
+	if(sz == 0)
+		return "invalid size in LIST response";
+
+	m->start = wp = emalloc(sz+1);
+	ep = wp+sz;
+
+	if(!pop->pipeline)
+		pop3cmd(pop, "RETR %d", m->mesgno);
+	if(!isokay(s = pop3resp(pop))) {
+		m->start = nil;
+		free(wp);
+		return s;
+	}
+
+	s = nil;
+	while(wp <= ep) {
+		s = pop3resp(pop);
+		if(strcmp(s, "unexpected eof") == 0) {
+			free(m->start);
+			m->start = nil;
+			return "unexpected end of conversation";
+		}
+		if(strcmp(s, ".") == 0)
+			break;
+
+		l = strlen(s)+1;
+		if(s[0] == '.') {
+			s++;
+			l--;
+		}
+		/*
+		 * grow by 10%/200bytes - some servers
+		 *  lie about message sizes
+		 */
+		if(wp+l > ep) {
+			int pos = wp - m->start;
+			sz += ((sz / 10) < 200)? 200: sz/10;
+			m->start = erealloc(m->start, sz+1);
+			wp = m->start+pos;
+			ep = m->start+sz;
+		}
+		memmove(wp, s, l-1);
+		wp[l-1] = '\n';
+		wp += l;
+	}
+
+	if(s == nil || strcmp(s, ".") != 0)
+		return "out of sync with pop3 server";
+
+	m->end = wp;
+
+	// make sure there's a trailing null
+	// (helps in body searches)
+	*m->end = 0;
+	m->bend = m->rbend = m->end;
+	m->header = m->start;
+
+	// digest message
+	sha1((uchar*)m->start, m->end - m->start, m->digest, nil);
+	for(i = 0; i < SHA1dlen; i++)
+		sprint(sdigest+2*i, "%2.2ux", m->digest[i]);
+	m->sdigest = s_copy(sdigest);
+
+	return nil;
+}
+
+//
+// check for new messages on pop server
+// UIDL is not required by RFC 1939, but 
+// netscape requires it, so almost every server supports it.
+// we'll use it to make our lives easier.
+//
+static char*
+pop3read(Pop *pop, Mailbox *mb, int doplumb)
+{
+	char *s, *p, *uidl, *f[2];
+	int mesgno, ignore, nnew;
+	Message *m, *next, **l;
+
+	// Some POP servers disallow UIDL if the maildrop is empty.
+	pop3cmd(pop, "STAT");
+	if(!isokay(s = pop3resp(pop)))
+		return s;
+
+	// fetch message listing; note messages to grab
+	l = &mb->root->part;
+	if(strncmp(s, "+OK 0 ", 6) != 0) {
+		pop3cmd(pop, "UIDL");
+		if(!isokay(s = pop3resp(pop)))
+			return s;
+
+		for(;;){
+			p = pop3resp(pop);
+			if(strcmp(p, ".") == 0 || strcmp(p, "unexpected eof") == 0)
+				break;
+
+			if(tokenize(p, f, 2) != 2)
+				continue;
+
+			mesgno = atoi(f[0]);
+			uidl = f[1];
+			if(strlen(uidl) > 75)	// RFC 1939 says 70 characters max
+				continue;
+
+			ignore = 0;
+			while(*l != nil) {
+				if(strcmp((*l)->uidl, uidl) == 0) {
+					// matches mail we already have, note mesgno for deletion
+					(*l)->mesgno = mesgno;
+					ignore = 1;
+					l = &(*l)->next;
+					break;
+				} else {
+					// old mail no longer in box mark deleted
+					if(doplumb)
+						mailplumb(mb, *l, 1);
+					(*l)->inmbox = 0;
+					(*l)->deleted = 1;
+					l = &(*l)->next;
+				}
+			}
+			if(ignore)
+				continue;
+
+			m = newmessage(mb->root);
+			m->mallocd = 1;
+			m->inmbox = 1;
+			m->mesgno = mesgno;
+			strcpy(m->uidl, uidl);
+
+			// chain in; will fill in message later
+			*l = m;
+			l = &m->next;
+		}
+	}
+
+	// whatever is left has been removed from the mbox, mark as deleted
+	while(*l != nil) {
+		if(doplumb)
+			mailplumb(mb, *l, 1);
+		(*l)->inmbox = 0;
+		(*l)->deleted = 1;
+		l = &(*l)->next;
+	}
+
+	// download new messages
+	nnew = 0;
+	if(pop->pipeline){
+		switch(rfork(RFPROC|RFMEM)){
+		case -1:
+			fprint(2, "rfork: %r\n");
+			pop->pipeline = 0;
+
+		default:
+			break;
+
+		case 0:
+			for(m = mb->root->part; m != nil; m = m->next){
+				if(m->start != nil)
+					continue;
+				Bprint(&pop->bout, "LIST %d\r\nRETR %d\r\n", m->mesgno, m->mesgno);
+			}
+			Bflush(&pop->bout);
+			threadexits(nil);
+			/* _exits(nil); jpc */
+		}
+	}
+
+	for(m = mb->root->part; m != nil; m = next) {
+		next = m->next;
+
+		if(m->start != nil)
+			continue;
+
+		if(s = pop3download(pop, m)) {
+			// message disappeared? unchain
+			fprint(2, "download %d: %s\n", m->mesgno, s);
+			delmessage(mb, m);
+			mb->root->subname--;
+			continue;
+		}
+		nnew++;
+		parse(m, 0, mb, 1);
+
+		if(doplumb)
+			mailplumb(mb, m, 0);
+	}
+	if(pop->pipeline)
+		waitpid();
+
+	if(nnew || mb->vers == 0) {
+		mb->vers++;
+		henter(PATH(0, Qtop), mb->name,
+			(Qid){PATH(mb->id, Qmbox), mb->vers, QTDIR}, nil, mb);
+	}
+
+	return nil;	
+}
+
+//
+// delete marked messages
+//
+static void
+pop3purge(Pop *pop, Mailbox *mb)
+{
+	Message *m, *next;
+
+	if(pop->pipeline){
+		switch(rfork(RFPROC|RFMEM)){
+		case -1:
+			fprint(2, "rfork: %r\n");
+			pop->pipeline = 0;
+
+		default:
+			break;
+
+		case 0:
+			for(m = mb->root->part; m != nil; m = next){
+				next = m->next;
+				if(m->deleted && m->refs == 0){
+					if(m->inmbox)
+						Bprint(&pop->bout, "DELE %d\r\n", m->mesgno);
+				}
+			}
+			Bflush(&pop->bout);
+			/* _exits(nil); jpc */
+			threadexits(nil);
+		}
+	}
+	for(m = mb->root->part; m != nil; m = next) {
+		next = m->next;
+		if(m->deleted && m->refs == 0) {
+			if(m->inmbox) {
+				if(!pop->pipeline)
+					pop3cmd(pop, "DELE %d", m->mesgno);
+				if(isokay(pop3resp(pop)))
+					delmessage(mb, m);
+			} else
+				delmessage(mb, m);
+		}
+	}
+}
+
+
+// connect to pop3 server, sync mailbox
+static char*
+pop3sync(Mailbox *mb, int doplumb)
+{
+	char *err;
+	Pop *pop;
+
+	pop = mb->aux;
+
+	if(err = pop3dial(pop)) {
+		mb->waketime = time(0) + pop->refreshtime;
+		return err;
+	}
+
+	if((err = pop3read(pop, mb, doplumb)) == nil){
+		pop3purge(pop, mb);
+		mb->d->atime = mb->d->mtime = time(0);
+	}
+	pop3hangup(pop);
+	mb->waketime = time(0) + pop->refreshtime;
+	return err;
+}
+
+static char Epop3ctl[] = "bad pop3 control message";
+
+static char*
+pop3ctl(Mailbox *mb, int argc, char **argv)
+{
+	int n;
+	Pop *pop;
+	char *m, *me;
+
+	pop = mb->aux;
+	if(argc < 1)
+		return Epop3ctl;
+
+	if(argc==1 && strcmp(argv[0], "debug")==0){
+		pop->debug = 1;
+		return nil;
+	}
+
+	if(argc==1 && strcmp(argv[0], "nodebug")==0){
+		pop->debug = 0;
+		return nil;
+	}
+
+	if(argc==1 && strcmp(argv[0], "thumbprint")==0){
+		if(pop->thumb)
+			freeThumbprints(pop->thumb);
+		/* pop->thumb = initThumbprints("/sys/lib/tls/mail", "/sys/lib/tls/mail.exclude"); jpc */
+		m = unsharp("#9/sys/lib/tls/mail");
+		me = unsharp("#9/sys/lib/tls/mail.exclude");
+		pop->thumb = initThumbprints(m, me);
+	}
+	if(strcmp(argv[0], "refresh")==0){
+		if(argc==1){
+			pop->refreshtime = 60;
+			return nil;
+		}
+		if(argc==2){
+			n = atoi(argv[1]);
+			if(n < 15)
+				return Epop3ctl;
+			pop->refreshtime = n;
+			return nil;
+		}
+	}
+
+	return Epop3ctl;
+}
+
+// free extra memory associated with mb
+static void
+pop3close(Mailbox *mb)
+{
+	Pop *pop;
+
+	pop = mb->aux;
+	free(pop->freep);
+	free(pop);
+}
+
+//
+// open mailboxes of the form /pop/host/user or /apop/host/user
+//
+char*
+pop3mbox(Mailbox *mb, char *path)
+{
+	char *f[10];
+	int nf, apop, ppop, popssl, apopssl, apoptls, popnotls, apopnotls, poptls;
+	Pop *pop;
+	char *m, *me;
+
+	quotefmtinstall();
+	popssl = strncmp(path, "/pops/", 6) == 0;
+	apopssl = strncmp(path, "/apops/", 7) == 0;
+	poptls = strncmp(path, "/poptls/", 8) == 0;
+	popnotls = strncmp(path, "/popnotls/", 10) == 0;
+	ppop = popssl || poptls || popnotls || strncmp(path, "/pop/", 5) == 0;
+	apoptls = strncmp(path, "/apoptls/", 9) == 0;
+	apopnotls = strncmp(path, "/apopnotls/", 11) == 0;
+	apop = apopssl || apoptls || apopnotls || strncmp(path, "/apop/", 6) == 0;
+
+	if(!ppop && !apop)
+		return Enotme;
+
+	path = strdup(path);
+	if(path == nil)
+		return "out of memory";
+
+	nf = getfields(path, f, nelem(f), 0, "/");
+	if(nf != 3 && nf != 4) {
+		free(path);
+		return "bad pop3 path syntax /[a]pop[tls|ssl]/system[/user]";
+	}
+
+	pop = emalloc(sizeof(*pop));
+	pop->freep = path;
+	pop->host = f[2];
+	if(nf < 4)
+		pop->user = nil;
+	else
+		pop->user = f[3];
+	pop->ppop = ppop;
+	pop->needssl = popssl || apopssl;
+	pop->needtls = poptls || apoptls;
+	pop->refreshtime = 60;
+	pop->notls = popnotls || apopnotls;
+	/* pop->thumb = initThumbprints("/sys/lib/tls/mail", "/sys/lib/tls/mail.exclude"); jpc */
+		m = unsharp("#9/sys/lib/tls/mail");
+		me = unsharp("#9/sys/lib/tls/mail.exclude");
+		pop->thumb = initThumbprints(m, me);
+
+	mb->aux = pop;
+	mb->sync = pop3sync;
+	mb->close = pop3close;
+	mb->ctl = pop3ctl;
+	mb->d = emalloc(sizeof(*mb->d));
+
+	return nil;
+}
+
blob - /dev/null
blob + 42028d4c1565a454ab4355dfe98a2274b4e13e0f (mode 644)
--- /dev/null
+++ src/cmd/upas/fs/readdir.c
@@ -0,0 +1,15 @@
+#include <u.h>
+#include <libc.h>
+
+void
+main(void)
+{
+	Dir d;
+	int fd, n;
+
+	fd = open("/mail/fs", OREAD);
+	while((n = dirread(fd, &d, sizeof(d))) > 0){
+		print("%s\n", d.name);
+	}
+	print("n = %d\n", n);
+}
blob - /dev/null
blob + bcf0bcee0ca1a9c00015d7141432f0cdf50ecbdd (mode 644)
--- /dev/null
+++ src/cmd/upas/fs/strtotm.c
@@ -0,0 +1,113 @@
+#include <u.h>
+#include <libc.h>
+#include <ctype.h>
+
+static char*
+skiptext(char *q)
+{
+	while(*q!='\0' && *q!=' ' && *q!='\t' && *q!='\r' && *q!='\n')
+		q++;
+	return q;
+}
+
+static char*
+skipwhite(char *q)
+{
+	while(*q==' ' || *q=='\t' || *q=='\r' || *q=='\n')
+		q++;
+	return q;
+}
+
+static char* months[] = {
+	"jan", "feb", "mar", "apr",
+	"may", "jun", "jul", "aug", 
+	"sep", "oct", "nov", "dec"
+};
+
+static int
+strcmplwr(char *a, char *b, int n)
+{
+	char *eb;
+
+	eb = b+n;
+	while(*a && *b && b<eb){
+		if(tolower(*a) != tolower(*b))
+			return 1;
+		a++;
+		b++;
+	}
+	if(b==eb)
+		return 0;
+	return *a != *b;
+}
+
+int
+strtotm(char *p, Tm *tmp)
+{
+	char *q, *r;
+	int j;
+	Tm tm;
+	int delta;
+
+	delta = 0;
+	memset(&tm, 0, sizeof(tm));
+	tm.mon = -1;
+	tm.hour = -1;
+	tm.min = -1;
+	tm.year = -1;
+	tm.mday = -1;
+	for(p=skipwhite(p); *p; p=skipwhite(q)){
+		q = skiptext(p);
+
+		/* look for time in hh:mm[:ss] */
+		if(r = memchr(p, ':', q-p)){
+			tm.hour = strtol(p, 0, 10);
+			tm.min = strtol(r+1, 0, 10);
+			if(r = memchr(r+1, ':', q-(r+1)))
+				tm.sec = strtol(r+1, 0, 10);
+			else
+				tm.sec = 0;
+			continue;
+		}
+
+		/* look for month */
+		for(j=0; j<12; j++)
+			if(strcmplwr(p, months[j], 3)==0){
+				tm.mon = j;
+				break;
+			}
+
+		if(j!=12)
+			continue;
+
+		/* look for time zone [A-Z][A-Z]T */
+		if(q-p==3 && 'A' <= p[0] && p[0] <= 'Z' 
+		&& 'A' <= p[1] && p[1] <= 'Z' && p[2] == 'T'){
+			strecpy(tm.zone, tm.zone+4, p);
+			continue;
+		}
+
+		if(p[0]=='+'||p[0]=='-')
+		if(q-p==5 && strspn(p+1, "0123456789") == 4){
+			delta = (((p[1]-'0')*10+p[2]-'0')*60+(p[3]-'0')*10+p[4]-'0')*60;
+			if(p[0] == '-')
+				delta = -delta;
+			continue;
+		}
+		if(strspn(p, "0123456789") == q-p){
+			j = strtol(p, nil, 10);
+			if(1 <= j && j <= 31)
+				tm.mday = j;
+			if(j >= 1900)
+				tm.year = j-1900;
+		}
+	}
+
+	if(tm.mon<0 || tm.year<0
+	|| tm.hour<0 || tm.min<0
+	|| tm.mday<0)
+		return -1;
+
+	*tmp = *localtime(tm2sec(&tm)-delta);
+	return 0;
+}
blob - /dev/null
blob + 3d24012efa7eaa37b814786c7b6d213e2ad6838e (mode 644)
--- /dev/null
+++ src/cmd/upas/fs/tester.c
@@ -0,0 +1,81 @@
+#include <u.h>
+#include <libc.h>
+#include <ctype.h>
+#include <String.h>
+#include "message.h"
+
+Message *root;
+
+void
+prindent(int i)
+{
+	for(; i > 0; i--)
+		print(" ");
+}
+
+void
+prstring(int indent, char *tag, String *s)
+{
+	if(s == nil)
+		return;
+	prindent(indent+1);
+	print("%s %s\n", tag, s_to_c(s));
+}
+
+void
+info(int indent, int mno, Message *m)
+{
+	int i;
+	Message *nm;
+
+	prindent(indent);
+	print("%d%c %d ", mno, m->allocated?'*':' ', m->end - m->start);
+	if(m->unixfrom != nil)
+		print("uf %s ", s_to_c(m->unixfrom));
+	if(m->unixdate != nil)
+		print("ud %s ", s_to_c(m->unixdate));
+	print("\n");
+	prstring(indent, "from:", m->from822);
+	prstring(indent, "sender:", m->sender822);
+	prstring(indent, "to:", m->to822);
+	prstring(indent, "cc:", m->cc822);
+	prstring(indent, "reply-to:", m->replyto822);
+	prstring(indent, "subject:", m->subject822);
+	prstring(indent, "date:", m->date822);
+	prstring(indent, "filename:", m->filename);
+	prstring(indent, "type:", m->type);
+	prstring(indent, "charset:", m->charset);
+
+	i = 1;
+	for(nm = m->part; nm != nil; nm = nm->next){
+		info(indent+1, i++, nm);
+	}
+}
+	
+
+void
+main(int argc, char **argv)
+{
+	char *err;
+	char *mboxfile;
+
+	ARGBEGIN{
+	}ARGEND;
+
+	if(argc > 0)
+		mboxfile = argv[0];
+	else
+		mboxfile = "./mbox";
+
+	root = newmessage(nil);
+
+	err = readmbox(mboxfile, &root->part);
+	if(err != nil){
+		fprint(2, "boom: %s\n", err);
+		exits(0);
+	}
+
+	info(0, 1, root);
+
+	exits(0);
+}
blob - /dev/null
blob + cfc186c0a8dabd89c7a5a2f399fb4b470414d93b (mode 644)
--- /dev/null
+++ src/cmd/upas/marshal/marshal.c
@@ -0,0 +1,1857 @@
+#include "common.h"
+#include <ctype.h>
+
+typedef struct Attach Attach;
+typedef struct Alias Alias;
+typedef struct Addr Addr;
+typedef struct Ctype Ctype;
+
+struct Attach {
+	Attach	*next;
+	char	*path;
+	char	*type;
+	int	tinline;
+	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 rflag, lbflag, xflag, holding, nflag, Fflag, eightflag, dflag;
+int pgpflag = 0;
+char *user;
+char *login;
+Alias *aliases;
+int rfc822syntaxerror;
+char lastchar;
+char *replymsg;
+
+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);
+	exits("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);
+	exits(buf);
+}
+
+void
+main(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);
+
+	ARGBEGIN{
+	case 't':
+		type = ARGF();
+		if(type == nil)
+			usage();
+		break;
+	case 'a':
+		flags = 0;
+		goto aflag;
+	case 'A':
+		flags = 1;
+	aflag:
+		a = mkattach(ARGF(), type, flags);
+		if(a == nil)
+			exits("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 = ARGF();
+		break;
+	case 's':
+		subject = ARGF();
+		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(ARGF()) < 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)
+				exits(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);
+		exits(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)
+		if(printinreplyto(&out, replymsg) < 0)
+			fatal("writing");
+	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);
+	exits(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(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));
+		fprint(2,"read %d bytes\n",n);
+		if(n < 0)
+			fatal("input error");
+		if(n == 0)
+			break;
+		m = enc64(obuf, sizeof(obuf), buf, n);
+		fprint(2,"encoded %d bytes\n",m);
+		fprint(2,"writing to %x\n",out);
+		if((n=Bwrite(out, obuf, m)) < 0)
+			fatal("output error");
+		fprint(2,"wrote %d bytes\n",n);
+	}
+	lastchar = '\n';
+		fprint(2,"done with attachment\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;
+
+	// if it's already mime encoded, just copy
+	if(strcmp(a->type, "mime") == 0){
+		f = Bopen(a->path, OREAD);
+		if(f == nil){
+			/* hack: give marshal time to stdin, before we kill it (for dead.letter) */
+			sleep(500);
+			postnote(PNPROC, pid, "interrupt");
+			sysfatal("opening %s: %r", a->path);
+		}
+		copy(f, out);
+		Bterm(f);
+	}
+	
+	// if it's not already mime encoded ...
+	if(strcmp(a->type, "text/plain") != 0)
+		Bprint(out, "Content-Type: %s\n", a->type);
+
+	if(a->tinline){
+		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);
+	}
+
+	f = Bopen(a->path, OREAD);
+	if(f == nil){
+		/* hack: give marshal time to stdin, before we kill it (for dead.letter) */
+		sleep(500);
+		postnote(PNPROC, pid, "interrupt");
+		sysfatal("opening %s: %r", a->path);
+	}
+
+	/* 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);
+}
+
+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 = s_copy(dir);
+	char buf[256];
+	int fd;
+	int n;
+
+	s_append(s, "/messageid");
+	fd = open(s_to_c(s), OREAD);
+	s_free(s);
+	if(fd < 0)
+		return 0;
+	n = read(fd, buf, sizeof(buf)-1);
+	close(fd);
+	if(n <= 0)
+		return 0;
+	buf[n] = 0;
+	return Bprint(out, "In-Reply-To: %s\n", buf);
+}
+
+Attach*
+mkattach(char *file, char *type, int tinline)
+{
+	Ctype *c;
+	Attach *a;
+	char ftype[64];
+	char *p;
+	int n, pfd[2];
+
+	if(file == nil)
+		return nil;
+	if(access(file, 4) == -1){
+		fprint(2, "%s: %s can't read file\n", argv0, file);
+		return nil;
+	}
+	a = emalloc(sizeof(*a));
+	a->path = file;
+	a->next = nil;
+	a->type = type;
+	a->tinline = tinline;
+	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;
+	switch(fork()){
+	case -1:
+		break;
+	case 0:
+		close(pfd[1]);
+		close(0);
+		dup(pfd[0], 0);
+		close(1);
+		dup(pfd[0], 1);
+		execl(unsharp("#9/bin/file"), "file", "-m", file, nil);
+		exits(0);
+	default:
+		close(pfd[0]);
+		n = read(pfd[1], ftype, sizeof(ftype));
+		if(n > 0){
+			ftype[n-1] = 0;
+			a->type = estrdup(ftype);
+		}
+		close(pfd[1]);
+		waitpid();
+		break;
+	}
+
+	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;
+	}
+}
+
+// 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;
+	int pfd[2];
+	String *cmd;
+	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("%r");
+	switch(*pid = rfork(RFFDG|RFPROC)){   // jpc - removed |RFENVG|RFREND|
+	case -1:
+		fatal("%r");
+		break;
+	case 0:
+		if(holding)
+			close(holding);
+		close(pfd[1]);
+		dup(pfd[0], 0);
+		close(pfd[0]);
+
+		if(rcvr != nil){
+			if(pipe(pfd) < 0)
+				fatal("%r");
+			switch(fork()){
+			case -1:
+				fatal("%r");
+				break;
+			case 0:
+				close(pfd[0]);
+				seek(fd, 0, 2);
+				printunixfrom(fd);
+				tee(0, pfd[1], fd);
+				write(fd, "\n", 1);
+				exits(0);
+			default:
+				close(fd);
+				close(pfd[1]);
+				dup(pfd[0], 0);
+				break;
+			}
+		}
+
+		if(replymsg != nil)
+			putenv("replymsg", replymsg);
+
+		cmd = mboxpath("pipefrom", login, s_new(), 0);
+		exec(s_to_c(cmd), av);
+		exec(unsharp("#9/bin/myupassend"), av);
+		exec(unsharp("#9/bin/upas/send"), av);
+		fatal("execing: %r");
+		break;
+	default:
+		if(rcvr != nil)
+			close(fd);
+		close(pfd[0]);
+		break;
+	}
+	return pfd[1];
+}
+
+// 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";
+	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);
+
+		exec("/bin/upas/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;
+	while((w = wait()) != nil){
+		if(w->pid == pid || w->pid == pgppid){
+			if(w->msg[0] != 0)
+				err = estrdup(w->msg);
+		}
+		free(w);
+	}
+	if(err)
+		exits(err);
+	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;
+	// jpc - static int already;
+	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/sys/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, '"');
+}
blob - /dev/null
blob + bf446476d61ce46fe61dd8f60729d769beb96e2a (mode 644)
--- /dev/null
+++ src/cmd/upas/marshal/mkfile
@@ -0,0 +1,20 @@
+<$PLAN9/src/mkhdr
+
+TARG=marshal
+
+LIB=../common/libcommon.a\
+
+HFILES=	../common/common.h\
+
+OFILES= marshal.$O
+
+BIN=$PLAN9/bin/upas
+
+UPDATE=\
+	mkfile\
+	$HFILES\
+	${OFILES:%.$O=%.c}\
+	
+<$PLAN9/src/mkone
+CFLAGS=$CFLAGS -I../common
+
blob - /dev/null
blob + a304271c07ab0c34d298286429d2ea1b5c65f580 (mode 644)
--- /dev/null
+++ src/cmd/upas/misc/gone.fishing
@@ -0,0 +1,9 @@
+#!/bin/sh
+PATH=/bin:/usr/bin
+message=${1-/usr/lib/upas/gone.msg}
+return=`sed '2,$s/^From[ 	]/>&/'|tee -a $HOME/gone.mail|sed -n '1s/^From[ 	]\([^ 	]*\)[ 	].*$/\1/p'`
+echo '' >>$HOME/gone.mail
+grep "^$return" $HOME/gone.addrs >/dev/null 2>/dev/null || {
+	echo $return >>$HOME/gone.addrs
+	mail $return < $message
+}
blob - /dev/null
blob + 9eb1e5a6bba1b2c3bbdf8f066b50becce3d24c68 (mode 644)
--- /dev/null
+++ src/cmd/upas/misc/gone.msg
@@ -0,0 +1,4 @@
+This is a recorded message.  I am currently out of contact with my
+computer system.  Your message to me has been saved and will be
+read upon my return.  This is the last time you will receive this
+message during my absence.  Thank you.
blob - /dev/null
blob + 20cf239706faa1ceb05f4db5d9b1220c5412f6cc (mode 644)
--- /dev/null
+++ src/cmd/upas/misc/mail.c
@@ -0,0 +1,51 @@
+/*
+ * #!/bin/sh
+ * case $1 in
+ * -n)
+ * 	exit 0 ;;
+ * -m*|-f*|-r*|-p*|-e*|"")
+ * 	exec /usr/lib/upas/edmail $*
+ * 	exit $? ;;
+ * *)
+ * 	exec /usr/lib/upas/send $*
+ * 	exit $? ;;
+ * esac
+ */
+
+
+extern *UPASROOT;
+
+#define	EDMAIL	"edmail"
+#define	SEND	"send"
+
+main (argc, argv)
+	int argc;
+	char **argv;
+{
+	char *progname = SEND;
+	char realprog[500];
+
+	if (argc > 1) {
+		if (argv[1][0] == '-') {
+			switch (argv[1][1]) {
+			case 'n':
+				exit (0);
+
+			case 'm':
+			case 'f':
+			case 'r':
+			case 'p':
+			case 'e':
+			case '\0':
+				progname = EDMAIL;
+			}
+		}
+	} else
+		progname = EDMAIL;
+
+	sprint(realprog, "%s/%s", UPASROOT, progname);
+	execv (realprog, argv);
+	perror (realprog);
+	exit (1);
+}
+
blob - /dev/null
blob + 6913d743e1a9b52044800494e6d15e8d8518240f (mode 755)
--- /dev/null
+++ src/cmd/upas/misc/mail.rc
@@ -0,0 +1,12 @@
+#!/bin/rc
+switch($#*){
+case 0
+	exec upas/nedmail
+}
+
+switch($1){
+case -f* -r* -c* -m*
+	exec upas/nedmail $*
+case *
+	exec upas/marshal $*
+}
blob - /dev/null
blob + a41519aefa7e8826de8ee6f7b0a4f85c25a0915b (mode 644)
--- /dev/null
+++ src/cmd/upas/misc/mail.sh
@@ -0,0 +1,12 @@
+#!/bin/sh
+case $1 in
+-n)
+	exec LIBDIR/notify
+	exit $? ;;
+-m*|-f*|-r*|-p*|-e*|"")
+	exec LIBDIR/edmail $*
+	exit $? ;;
+*)
+	exec LIBDIR/send $*
+	exit $? ;;
+esac
blob - /dev/null
blob + e00c4f7e8f13e0b8f2d592ae134bc73e302fbb04 (mode 644)
--- /dev/null
+++ src/cmd/upas/misc/makefile
@@ -0,0 +1,44 @@
+LIB=/usr/lib/upas
+CFLAGS=${UNIX} -g -I. -I../libc -I../common -I/usr/include -I/usr/include/sys
+LFLAGS=-g
+HOSTNAME=cat /etc/whoami
+
+.c.o: ; $(CC) -c $(CFLAGS) $*.c
+all: mail
+
+sedfile:
+	echo 's+LIBDIR+$(LIB)+g' >sed.file
+	echo 's+HOSTNAME+$(HOSTNAME)+g' >>sed.file
+
+install: sedfile install.fish install.mail.sh
+
+install.fish:
+	cp gone.msg $(LIB)
+	sed -f sed.file gone.fishing >$(LIB)/gone.fishing
+	-chmod 775 $(LIB)/gone.fishing
+	-chown bin $(LIB)/gone.fishing $(LIB)/gone.msg
+
+install.mail.sh:
+	sed -f sed.file mail.sh >/bin/mail
+	-chown bin /bin/mail
+	-chmod 775 /bin/mail
+
+install.notify: notify
+	cp notify $(LIB)/notify
+	-chmod 775 $(LIB)/notify
+	-chown bin $(LIB)/notify
+
+install.mail: mail
+	cp mail /bin
+	strip /bin/mail
+
+notify: notify.o
+	cc $(LFLAGS) notify.o -o notify
+
+mail: mail.o ../config/config.o
+	cc $(LFLAGS) mail.o ../config/config.o -o mail
+
+clean:
+	-rm -f *.[oOa] core a.out *.sL notify
+	-rm -f sed.file mail
+
blob - /dev/null
blob + 0bbd8a5ab355647dab620b7060a3834d95b782b9 (mode 644)
--- /dev/null
+++ src/cmd/upas/misc/mkfile
@@ -0,0 +1,39 @@
+
+RCFILES=mail.rc\
+
+all:Q:
+	;
+
+installall:Q:	install
+	;
+
+install:V:
+	cp mail.rc /rc/bin/mail
+
+safeinstall:V:
+	cp mail.rc /rc/bin/mail
+
+safeinstallall:V:
+	cp mail.rc /rc/bin/mail
+
+clean:Q:
+	;
+nuke:V:
+	rm /rc/bin/mail
+
+UPDATE=\
+	gone.fishing\
+	gone.msg\
+	mail.c\
+	mail.rc\
+	mail.sh\
+	makefile\
+	mkfile\
+	namefiles\
+	omail.rc\
+	qmail\
+	remotemail\
+	rewrite\
+
+update:V:
+	update $UPDATEFLAGS $UPDATE
blob - /dev/null
blob + ab3853b1c2d15052b787b167d986d00e9fb75355 (mode 644)
--- /dev/null
+++ src/cmd/upas/misc/namefiles
@@ -0,0 +1,2 @@
+names.local
+names.global
blob - /dev/null
blob + ed64f38c439ca03cbf5773d05b430a46c3d288bf (mode 755)
--- /dev/null
+++ src/cmd/upas/misc/omail.rc
@@ -0,0 +1,14 @@
+#!/bin/rc
+switch($#*){
+case 0
+	exec upas/edmail -m
+}
+
+switch($1){
+case -F* -m* -f* -r* -p* -e* -c* -D*
+	exec upas/edmail -m $*
+case '-#'* -a*
+	exec upas/sendmail $*
+case *
+	exec upas/sendmail $*
+}
blob - /dev/null
blob + 77a3d228ad1413f28264fb31486354360ab7c1b1 (mode 755)
--- /dev/null
+++ src/cmd/upas/misc/qmail
@@ -0,0 +1,6 @@
+#!/bin/rc
+sender=$1
+shift
+addr=$1
+shift
+qer /mail/queue mail $sender $addr $* && runq /mail/queue /mail/lib/remotemail
blob - /dev/null
blob + 312ee58748d80bde6e815aab91935cdf4417ee03 (mode 755)
--- /dev/null
+++ src/cmd/upas/misc/remotemail
@@ -0,0 +1,7 @@
+#!/bin/rc
+shift
+sender=$1
+shift
+addr=$1
+shift
+/bin/upas/smtp -g research.research.bell-labs.com $addr $sender $*
blob - /dev/null
blob + fd724eb1e76497bba88f7f448d4c5b7da379e864 (mode 644)
--- /dev/null
+++ src/cmd/upas/misc/rewrite
@@ -0,0 +1,20 @@
+# case conversion for postmaster
+pOsTmAsTeR	alias		postmaster
+
+# local mail
+[^!@]+		translate	"/bin/upas/aliasmail '&'"
+local!(.*)	>>		/mail/box/\1/mbox
+\l!(.*)		alias		\1
+(helix|helix.bell-labs.com)!(.*)	alias		\2
+
+# we can be just as complicated as BSD sendmail...
+# convert source domain address to a chain a@b@c@d...
+@([^@!,]*):([^!@]*)@([^!]*)	alias	\2@\3@\1
+@([^@!]*),([^!@,]*):([^!@]*)@([^!]*)	alias	@\1:\3@\4@\2
+
+# convert a chain a@b@c@d... to ...d!c!b!a
+([^@]+)@([^@]+)@(.+)	alias	\2!\1@\3
+([^@]+)@([^@]+)		alias	\2!\1
+
+# /mail/lib/remotemail will take care of gating to systems we don't know
+([^!]*)!(.*) 		| 		"/mail/lib/qmail '\s' 'net!\1'" "'\2'"
blob - /dev/null
blob + 307a49255d9be40263571c98a790872bf2875e21 (mode 644)
--- /dev/null
+++ src/cmd/upas/ml/common.c
@@ -0,0 +1,197 @@
+#include "common.h"
+#include "dat.h"
+
+String*
+getaddr(Node *p)
+{
+	for(; p; p = p->next){
+		if(p->s && p->addr)
+			return p->s;
+	}
+	return nil;
+}
+
+/* send messae adding our own reply-to and precedence */
+void
+getaddrs(void)
+{
+	Field *f;
+
+	for(f = firstfield; f; f = f->next){
+		if(f->node->c == FROM && from == nil)
+			from = getaddr(f->node);
+		if(f->node->c == SENDER && sender == nil)
+			sender = getaddr(f->node);
+	}
+}
+
+/* write address file, should be append only */
+void
+writeaddr(char *file, char *addr, int rem, char *listname)
+{
+	int fd;
+	Dir nd;
+
+	fd = open(file, OWRITE);
+	if(fd < 0){
+		fd = create(file, OWRITE, DMAPPEND|0666);
+		if(fd < 0)
+			sysfatal("creating address list %s: %r", file);
+		nulldir(&nd);
+		nd.mode = DMAPPEND|0666;
+		dirwstat(file, &nd);
+	} else
+		seek(fd, 0, 2);
+	if(rem)
+		fprint(fd, "!%s\n", addr);
+	else
+		fprint(fd, "%s\n", addr);
+	close(fd);
+
+	if(*addr != '#')
+		sendnotification(addr, listname, rem);
+}
+
+void
+remaddr(char *addr)
+{
+	Addr **l;
+	Addr *a;
+
+	for(l = &al; *l; l = &(*l)->next){
+		a = *l;
+		if(strcmp(addr, a->addr) == 0){
+			(*l) = a->next;
+			free(a);
+			na--;
+			break;
+		}
+	}
+}
+
+int
+addaddr(char *addr)
+{
+	Addr **l;
+	Addr *a;
+
+	for(l = &al; *l; l = &(*l)->next){
+		if(strcmp(addr, (*l)->addr) == 0)
+			return 0;
+	}
+	na++;
+	*l = a = malloc(sizeof(*a)+strlen(addr)+1);
+	if(a == nil)
+		sysfatal("allocating: %r");
+	a->addr = (char*)&a[1];
+	strcpy(a->addr, addr);
+	a->next = nil;
+	*l = a;
+	return 1;
+}
+
+/* read address file */
+void
+readaddrs(char *file)
+{
+	Biobuf *b;
+	char *p;
+
+	b = Bopen(file, OREAD);
+	if(b == nil)
+		return;
+
+	while((p = Brdline(b, '\n')) != nil){
+		p[Blinelen(b)-1] = 0;
+		if(*p == '#')
+			continue;
+		if(*p == '!')
+			remaddr(p+1);
+		else
+			addaddr(p);
+	}
+	Bterm(b);
+}
+
+/* start a mailer sending to all the receivers */
+int
+startmailer(char *name)
+{
+	int pfd[2];
+	char **av;
+	int ac;
+	Addr *a;
+
+	putenv("upasname", "/dev/null");
+	if(pipe(pfd) < 0)
+		sysfatal("creating pipe: %r");
+	switch(fork()){
+	case -1:
+		sysfatal("starting mailer: %r");
+	case 0:
+		close(pfd[1]);
+		break;
+	default:
+		close(pfd[0]);
+		return pfd[1];
+	}
+
+	dup(pfd[0], 0);
+	close(pfd[0]);
+
+	av = malloc(sizeof(char*)*(na+2));
+	if(av == nil)
+		sysfatal("starting mailer: %r");
+	ac = 0;
+	av[ac++] = name;
+	for(a = al; a != nil; a = a->next)
+		av[ac++] = a->addr;
+	av[ac] = 0;
+	exec("/bin/upas/send", av);
+	sysfatal("execing mailer: %r");
+
+	/* not reached */
+	return -1;
+}
+
+void
+sendnotification(char *addr, char *listname, int rem)
+{
+	int pfd[2];
+	Waitmsg *w;
+
+	putenv("upasname", "/dev/null");
+	if(pipe(pfd) < 0)
+		sysfatal("creating pipe: %r");
+	switch(fork()){
+	case -1:
+		sysfatal("starting mailer: %r");
+	case 0:
+		close(pfd[1]);
+		dup(pfd[0], 0);
+		close(pfd[0]);
+		execl("/bin/upas/send", "mlnotify", addr, nil);
+		sysfatal("execing mailer: %r");
+		break;
+	default:
+		close(pfd[0]);
+		fprint(pfd[1], "From: %s-owner\n\n", listname);
+		if(rem)
+			fprint(pfd[1], "You have removed from the %s mailing list\n", listname);
+		else{
+			fprint(pfd[1], "You have been added to the %s mailing list\n", listname);
+			fprint(pfd[1], "To be removed, send an email to %s-owner containing\n",
+				listname);
+			fprint(pfd[1], "the word 'remove' in the subject or body.\n");
+		}
+		close(pfd[1]);
+	
+		/* wait for mailer to end */
+		while(w = wait()){
+			if(w->msg != nil && w->msg[0])
+				sysfatal("%s", w->msg);
+			free(w);
+		}
+		break;
+	}
+}
blob - /dev/null
blob + 3527af508d3c1d995cc8aa29fc8d4490d06f3b37 (mode 644)
--- /dev/null
+++ src/cmd/upas/ml/dat.h
@@ -0,0 +1,25 @@
+
+#include "../smtp/smtp.h"
+#include "../smtp/y.tab.h"
+
+typedef struct Addr Addr;
+struct Addr
+{
+	char *addr;
+	Addr *next;
+};
+
+String *from;
+String *sender;
+Field *firstfield;
+int na;
+Addr *al;
+
+extern String*	getaddr(Node *p);
+extern void	getaddrs(void);
+extern void	writeaddr(char *file, char *addr, int, char *);
+extern void	remaddr(char *addr);
+extern int	addaddr(char *addr);
+extern void	readaddrs(char *file);
+extern int	startmailer(char *name);
+extern void	sendnotification(char *addr, char *listname, int rem);
blob - /dev/null
blob + 5142e56f45d3dd97bee0646cc3992839f0596585 (mode 644)
--- /dev/null
+++ src/cmd/upas/ml/mkfile
@@ -0,0 +1,40 @@
+</$objtype/mkfile
+
+TARG=ml\
+	mlowner\
+	mlmgr\
+
+OFILES=\
+	common.$O\
+
+LIB=../common/libcommon.av\
+
+UHFILES= ../common/common.h\
+	../common/sys.h\
+	dat.h\
+
+HFILES=$UHFILES\
+	../smtp/y.tab.h\
+
+LIB=../common/libcommon.a$O\
+
+BIN=/$objtype/bin/upas
+
+UPDATE=\
+	mkfile\
+	$UHFILES\
+	${TARG:%=%.c}\
+	${OFILES:%.$O=%.c}\
+	../smtp/rfc822.y\
+
+</sys/src/cmd/mkmany
+CFLAGS=$CFLAGS -I../common
+
+$O.ml: ../smtp/rfc822.tab.$O
+$O.mlowner: ../smtp/rfc822.tab.$O
+
+../smtp/y.tab.h ../smtp/rfc822.tab.$O:
+	@{
+		cd ../smtp
+		mk rfc822.tab.$O
+	}
blob - /dev/null
blob + 8372dc9d26410f16eeeee619c7f8fc3096f4f873 (mode 644)
--- /dev/null
+++ src/cmd/upas/ml/ml.c
@@ -0,0 +1,167 @@
+#include "common.h"
+#include "dat.h"
+
+Biobuf in;
+
+Addr *al;
+int na;
+String *from;
+String *sender;
+
+void printmsg(int fd, String *msg, char *replyto, char *listname);
+void appendtoarchive(char* listname, String *firstline, String *msg);
+void printsubject(int fd, Field *f, char *listname);
+
+void
+usage(void)
+{
+	fprint(2, "usage: %s address-list-file listname\n", argv0);
+	exits("usage");
+}
+
+void
+main(int argc, char **argv)
+{
+	String *msg;
+	String *firstline;
+	char *listname, *alfile;
+	Waitmsg *w;
+	int fd;
+	char *replytoname = nil;
+
+	ARGBEGIN{
+	case 'r':
+		replytoname = ARGF();
+		break;
+	}ARGEND;
+
+	rfork(RFENVG|RFREND);
+
+	if(argc < 2)
+		usage();
+	alfile = argv[0];
+	listname = argv[1];
+	if(replytoname == nil)
+		replytoname = listname;
+
+	readaddrs(alfile);
+
+	if(Binit(&in, 0, OREAD) < 0)
+		sysfatal("opening input: %r");
+
+	msg = s_new();
+	firstline = s_new();
+
+	/* discard the 'From ' line */
+	if(s_read_line(&in, firstline) == nil)
+		sysfatal("reading input: %r");
+
+	/* read up to the first 128k of the message.  more is redculous. 
+	     Not if word documents are distributed.  Upped it to 2MB (pb) */
+	if(s_read(&in, msg, 2*1024*1024) <= 0)
+		sysfatal("reading input: %r");
+
+	/* parse the header */
+	yyinit(s_to_c(msg), s_len(msg));
+	yyparse();
+
+	/* get the sender */
+	getaddrs();
+	if(from == nil)
+		from = sender;
+	if(from == nil)
+		sysfatal("message must contain From: or Sender:");
+	if(strcmp(listname, s_to_c(from)) == 0)
+		sysfatal("can't remail messages from myself");
+	addaddr(s_to_c(from));
+
+	/* start the mailer up and return a pipe to it */
+	fd = startmailer(listname);
+
+	/* send message adding our own reply-to and precedence */
+	printmsg(fd, msg, replytoname, listname);
+	close(fd);
+
+	/* wait for mailer to end */
+	while(w = wait()){
+		if(w->msg != nil && w->msg[0])
+			sysfatal("%s", w->msg);
+		free(w);
+	}
+
+	/* if the mailbox exits, cat the mail to the end of it */
+	appendtoarchive(listname, firstline, msg);
+	exits(0);
+}
+
+/* send message filtering Reply-to out of messages */
+void
+printmsg(int fd, String *msg, char *replyto, char *listname)
+{
+	Field *f, *subject;
+	Node *p;
+	char *cp, *ocp;
+
+	subject = nil;
+	cp = s_to_c(msg);
+	for(f = firstfield; f; f = f->next){
+		ocp = cp;
+		for(p = f->node; p; p = p->next)
+			cp = p->end+1;
+		if(f->node->c == REPLY_TO)
+			continue;
+		if(f->node->c == PRECEDENCE)
+			continue;
+		if(f->node->c == SUBJECT){
+			subject = f;
+			continue;
+		}
+		write(fd, ocp, cp-ocp);
+	}
+	printsubject(fd, subject, listname);
+	fprint(fd, "Reply-To: %s\nPrecedence: bulk\n", replyto);
+	write(fd, cp, s_len(msg) - (cp - s_to_c(msg)));
+}
+
+/* if the mailbox exits, cat the mail to the end of it */
+void
+appendtoarchive(char* listname, String *firstline, String *msg)
+{
+	String *mbox;
+	int fd;
+
+	mbox = s_new();
+	mboxpath("mbox", listname, mbox, 0);
+	if(access(s_to_c(mbox), 0) < 0)
+		return;
+	fd = open(s_to_c(mbox), OWRITE);
+	if(fd < 0)
+		return;
+	s_append(msg, "\n");
+	write(fd, s_to_c(firstline), s_len(firstline));
+	write(fd, s_to_c(msg), s_len(msg));
+}
+
+/* add the listname to the subject */
+void
+printsubject(int fd, Field *f, char *listname)
+{
+	char *s, *e;
+	Node *p;
+	char *ln;
+
+	if(f == nil || f->node == nil){
+		fprint(fd, "Subject: [%s]\n", listname);
+		return;
+	}
+	s = e = f->node->end + 1;
+	for(p = f->node; p; p = p->next)
+		e = p->end;
+	*e = 0;
+	ln = smprint("[%s]", listname);
+	if(ln != nil && strstr(s, ln) == nil)
+		fprint(fd, "Subject: %s%s\n", ln, s);
+	else
+		fprint(fd, "Subject:%s\n", s);
+	free(ln);
+}
blob - /dev/null
blob + a1d1b907e9d4233fe36355327fa3fabfe16fa936 (mode 644)
--- /dev/null
+++ src/cmd/upas/ml/mlmgr.c
@@ -0,0 +1,110 @@
+#include "common.h"
+#include "dat.h"
+
+int cflag;
+int aflag;
+int rflag;
+
+int createpipeto(char *alfile, char *user, char *listname, int owner);
+
+void
+usage(void)
+{
+	fprint(2, "usage:\t%s -c listname\n", argv0);
+	fprint(2, "\t%s -[ar] listname addr\n", argv0);
+	exits("usage");
+}
+
+void
+main(int argc, char **argv)
+{
+	char *listname, *addr;
+	String *owner, *alfile;
+
+	rfork(RFENVG|RFREND);
+
+	ARGBEGIN{
+	case 'c':
+		cflag = 1;
+		break;
+	case 'r':
+		rflag = 1;
+		break;
+	case 'a':
+		aflag = 1;
+		break;
+	}ARGEND;
+
+	if(aflag + rflag + cflag > 1){
+		fprint(2, "%s: -a, -r, and -c are mutually exclusive\n", argv0);
+		exits("usage");
+	}
+
+	if(argc < 1)
+		usage();
+
+	listname = argv[0];
+	alfile = s_new();
+	mboxpath("address-list", listname, alfile, 0);
+
+	if(cflag){
+		owner = s_copy(listname);
+		s_append(owner, "-owner");
+		if(creatembox(listname, nil) < 0)
+			sysfatal("creating %s's mbox: %r", listname);
+		if(creatembox(s_to_c(owner), nil) < 0)
+			sysfatal("creating %s's mbox: %r", s_to_c(owner));
+		if(createpipeto(s_to_c(alfile), listname, listname, 0) < 0)
+			sysfatal("creating %s's pipeto: %r", s_to_c(owner));
+		if(createpipeto(s_to_c(alfile), s_to_c(owner), listname, 1) < 0)
+			sysfatal("creating %s's pipeto: %r", s_to_c(owner));
+		writeaddr(s_to_c(alfile), "# mlmgr c flag", 0, listname);
+	} else if(rflag){
+		if(argc != 2)
+			usage();
+		addr = argv[1];
+		writeaddr(s_to_c(alfile), "# mlmgr r flag", 0, listname);
+		writeaddr(s_to_c(alfile), addr, 1, listname);
+	} else if(aflag){
+		if(argc != 2)
+			usage();
+		addr = argv[1];
+		writeaddr(s_to_c(alfile), "# mlmgr a flag", 0, listname);
+		writeaddr(s_to_c(alfile), addr, 0, listname);
+	} else
+		usage();
+	exits(0);
+}
+
+int
+createpipeto(char *alfile, char *user, char *listname, int owner)
+{
+	String *f;
+	int fd;
+	Dir *d;
+
+	f = s_new();
+	mboxpath("pipeto", user, f, 0);
+	fprint(2, "creating new pipeto: %s\n", s_to_c(f));
+	fd = create(s_to_c(f), OWRITE, 0775);
+	if(fd < 0)
+		return -1;
+	d = dirfstat(fd);
+	if(d == nil){
+		fprint(fd, "Couldn't stat %s: %r\n", s_to_c(f));
+		return -1;
+	}
+	d->mode |= 0775;
+	if(dirfwstat(fd, d) < 0)
+		fprint(fd, "Couldn't wstat %s: %r\n", s_to_c(f));
+	free(d);
+
+	fprint(fd, "#!/bin/rc\n");
+	if(owner)
+		fprint(fd, "/bin/upas/mlowner %s %s\n", alfile, listname);
+	else
+		fprint(fd, "/bin/upas/ml %s %s\n", alfile, user);
+	close(fd);
+
+	return 0;
+}
blob - /dev/null
blob + 5bb98a298fdc03cc921558cace2fb1fd456f8b36 (mode 644)
--- /dev/null
+++ src/cmd/upas/ml/mlowner.c
@@ -0,0 +1,64 @@
+#include "common.h"
+#include "dat.h"
+
+Biobuf in;
+
+String *from;
+String *sender;
+
+
+void
+usage(void)
+{
+	fprint(2, "usage: %s address-list-file listname\n", argv0);
+	exits("usage");
+}
+
+void
+main(int argc, char **argv)
+{
+	String *msg;
+	char *alfile;
+	char *listname;
+
+	ARGBEGIN{
+	}ARGEND;
+
+	rfork(RFENVG|RFREND);
+
+	if(argc < 2)
+		usage();
+	alfile = argv[0];
+	listname = argv[1];
+
+	if(Binit(&in, 0, OREAD) < 0)
+		sysfatal("opening input: %r");
+
+	msg = s_new();
+
+	/* discard the 'From ' line */
+	if(s_read_line(&in, msg) == nil)
+		sysfatal("reading input: %r");
+
+	/* read up to the first 128k of the message.  more is redculous */
+	if(s_read(&in, s_restart(msg), 128*1024) <= 0)
+		sysfatal("reading input: %r");
+
+	/* parse the header */
+	yyinit(s_to_c(msg), s_len(msg));
+	yyparse();
+
+	/* get the sender */
+	getaddrs();
+	if(from == nil)
+		from = sender;
+	if(from == nil)
+		sysfatal("message must contain From: or Sender:");
+
+	if(strstr(s_to_c(msg), "remove")||strstr(s_to_c(msg), "unsubscribe"))
+		writeaddr(alfile, s_to_c(from), 1, listname);
+	else if(strstr(s_to_c(msg), "subscribe"))
+		writeaddr(alfile, s_to_c(from), 0, listname);
+
+	exits(0);
+}
blob - /dev/null
blob + 82025558dd41fff37b8ac46693e7e9b92f314b26 (mode 644)
--- /dev/null
+++ src/cmd/upas/ned/mkfile
@@ -0,0 +1,20 @@
+<$PLAN9/src/mkhdr
+
+TARG=nedmail
+
+LIB=../common/libcommon.a\
+
+HFILES=	../common/common.h\
+
+OFILES=nedmail.$O
+
+BIN=$PLAN9/bin/upas
+
+UPDATE=\
+	mkfile\
+	${OFILES:%.$O=%.c}\
+	$HFILES\
+
+<$PLAN9/src/mkone
+CFLAGS=$CFLAGS -I../common
+
blob - /dev/null
blob + 4fa7a88f8b29e568e0dd49fc8ee5458d7931745d (mode 644)
--- /dev/null
+++ src/cmd/upas/ned/nedmail.c
@@ -0,0 +1,2586 @@
+#include "common.h"
+#include <ctype.h>
+#include <plumb.h>
+#include <9pclient.h>
+#include <thread.h>
+
+typedef struct Message Message;
+typedef struct Ctype Ctype;
+typedef struct Cmd Cmd;
+
+char	root[Pathlen];
+char	mbname[Elemlen];
+int	rootlen;
+int	didopen;
+char	*user;
+char	wd[2048];
+String	*mbpath;
+int	natural;
+int	doflush;
+
+int interrupted;
+
+struct Message {
+	Message	*next;
+	Message	*prev;
+	Message	*cmd;
+	Message	*child;
+	Message	*parent;
+	String	*path;
+	int	id;
+	int	len;
+	int	fileno;	// number of directory
+	String	*info;
+	char	*from;
+	char	*to;
+	char	*cc;
+	char	*replyto;
+	char	*date;
+	char	*subject;
+	char	*type;
+	char	*disposition;
+	char	*filename;
+	char	deleted;
+	char	stored;
+};
+
+Message top;
+
+struct Ctype {
+	char	*type;
+	char 	*ext;
+	int	display;
+	char	*plumbdest;
+	Ctype	*next;
+};
+
+Ctype ctype[] = {
+	{ "text/plain",			"txt",	1,	0	},
+	{ "text/html",			"htm",	1,	0	},
+	{ "text/html",			"html",	1,	0	},
+	{ "text/tab-separated-values",	"tsv",	1,	0	},
+	{ "text/richtext",		"rtx",	1,	0	},
+	{ "text/rtf",			"rtf",	1,	0	},
+	{ "text",			"txt",	1,	0	},
+	{ "message/rfc822",		"msg",	0,	0	},
+	{ "image/bmp",			"bmp",	0,	"image"	},
+	{ "image/jpeg",			"jpg",	0,	"image"	},
+	{ "image/gif",			"gif",	0,	"image"	},
+	{ "application/pdf",		"pdf",	0,	"postscript"	},
+	{ "application/postscript",	"ps",	0,	"postscript"	},
+	{ "application/",		0,	0,	0	},
+	{ "image/",			0,	0,	0	},
+	{ "multipart/",			"mul",	0,	0	},
+
+};
+
+Message*	acmd(Cmd*, Message*);
+Message*	bcmd(Cmd*, Message*);
+Message*	dcmd(Cmd*, Message*);
+Message*	eqcmd(Cmd*, Message*);
+Message*	hcmd(Cmd*, Message*);
+Message*	Hcmd(Cmd*, Message*);
+Message*	helpcmd(Cmd*, Message*);
+Message*	icmd(Cmd*, Message*);
+Message*	pcmd(Cmd*, Message*);
+Message*	qcmd(Cmd*, Message*);
+Message*	rcmd(Cmd*, Message*);
+Message*	scmd(Cmd*, Message*);
+Message*	ucmd(Cmd*, Message*);
+Message*	wcmd(Cmd*, Message*);
+Message*	xcmd(Cmd*, Message*);
+Message*	ycmd(Cmd*, Message*);
+Message*	pipecmd(Cmd*, Message*);
+Message*	rpipecmd(Cmd*, Message*);
+Message*	bangcmd(Cmd*, Message*);
+Message*	Pcmd(Cmd*, Message*);
+Message*	mcmd(Cmd*, Message*);
+Message*	fcmd(Cmd*, Message*);
+Message*	quotecmd(Cmd*, Message*);
+
+struct {
+	char		*cmd;
+	int		args;
+	Message*	(*f)(Cmd*, Message*);
+	char		*help;
+} cmdtab[] = {
+	{ "a",	1,	acmd,	"a        reply to sender and recipients" },
+	{ "A",	1,	acmd,	"A        reply to sender and recipients with copy" },
+	{ "b",	0,	bcmd,	"b        print the next 10 headers" },
+	{ "d",	0,	dcmd,	"d        mark for deletion" },
+	{ "f",	0,	fcmd,	"f        file message by from address" },
+	{ "h",	0,	hcmd,	"h        print elided message summary (,h for all)" },
+	{ "help", 0,	helpcmd, "help     print this info" },
+	{ "H",	0,	Hcmd,	"H        print message's MIME structure " },
+	{ "i",	0,	icmd,	"i        incorporate new mail" },
+	{ "m",	1,	mcmd,	"m addr   forward mail" },
+	{ "M",	1,	mcmd,	"M addr   forward mail with message" },
+	{ "p",	0,	pcmd,	"p        print the processed message" },
+	{ "P",	0,	Pcmd,	"P        print the raw message" },
+	{ "\"",	0,	quotecmd, "\"        print a quoted version of msg" },
+	{ "q",	0,	qcmd,	"q        exit and remove all deleted mail" },
+	{ "r",	1,	rcmd,	"r [addr] reply to sender plus any addrs specified" },
+	{ "rf",	1,	rcmd,	"rf [addr]file message and reply" },
+	{ "R",	1,	rcmd,	"R [addr] reply including copy of message" },
+	{ "Rf",	1,	rcmd,	"Rf [addr]file message and reply with copy" },
+	{ "s",	1,	scmd,	"s file   append raw message to file" },
+	{ "u",	0,	ucmd,	"u        remove deletion mark" },
+	{ "w",	1,	wcmd,	"w file   store message contents as file" },
+	{ "x",	0,	xcmd,	"x        exit without flushing deleted messages" },
+	{ "y",	0,	ycmd,	"y        synchronize with mail box" },
+	{ "=",	1,	eqcmd,	"=        print current message number" },
+	{ "|",	1,	pipecmd, "|cmd     pipe message body to a command" },
+	{ "||",	1,	rpipecmd, "||cmd     pipe raw message to a command" },
+	{ "!",	1,	bangcmd, "!cmd     run a command" },
+	{ nil,	0,	nil, 	nil },
+};
+
+enum
+{
+	NARG=	32,
+};
+
+struct Cmd {
+	Message	*msgs;
+	Message	*(*f)(Cmd*, Message*);
+	int	an;
+	char	*av[NARG];
+	int	delete;
+};
+
+Biobuf out;
+int startedfs;
+int reverse;
+int longestfrom = 12;
+
+String*		file2string(String*, char*);
+int		dir2message(Message*, int);
+int		filelen(String*, char*);
+String*		extendpath(String*, char*);
+void		snprintheader(char*, int, Message*);
+void		cracktime(char*, char*, int);
+int		cistrncmp(char*, char*, int);
+int		cistrcmp(char*, char*);
+Reprog*		parsesearch(char**);
+char*		parseaddr(char**, Message*, Message*, Message*, Message**);
+char*		parsecmd(char*, Cmd*, Message*, Message*);
+char*		readline(char*, char*, int);
+void		messagecount(Message*);
+void		system9(char*, char**, int);
+void		mkid(String*, Message*);
+int		switchmb(char*, char*);
+void		closemb(void);
+int		lineize(char*, char**, int);
+int		rawsearch(Message*, Reprog*);
+Message*	dosingleton(Message*, char*);
+String*		rooted(String*);
+int		plumb(Message*, Ctype*);
+String*		addrecolon(char*);
+void		exitfs(char*);
+Message*	flushdeleted(Message*);
+
+CFsys *upasfs;
+
+void
+usage(void)
+{
+	fprint(2, "usage: %s [-nr] [-f mailfile] [-s mailfile]\n", argv0);
+	fprint(2, "       %s -c dir\n", argv0);
+	threadexits("usage");
+}
+
+void
+catchnote(void* dummy, char *note)
+{
+	if(strstr(note, "interrupt") != nil){
+		interrupted = 1;
+		noted(NCONT);
+	}
+	noted(NDFLT);
+}
+
+char *
+plural(int n)
+{
+	if (n == 1)
+		return "";
+
+	return "s";		
+}
+
+void
+threadmain(int argc, char **argv)
+{
+	Message *cur, *m, *x;
+	char cmdline[4*1024];
+	Cmd cmd;
+	Ctype *cp;
+	char *err;
+	int n, cflag;
+	char *av[4];
+	String *prompt;
+	char *file, *singleton;
+	char *fscmd;
+
+	Binit(&out, 1, OWRITE);
+
+	file = nil;
+	singleton = nil;
+	reverse = 1;
+	cflag = 0;
+	ARGBEGIN {
+	case 'c':
+		cflag = 1;
+		break;
+	case 'f':
+		file = EARGF(usage());
+		break;
+	case 's':
+		singleton = EARGF(usage());
+		break;
+	case 'r':
+		reverse = 0;
+		break;
+	case 'n':
+		natural = 1;
+		reverse = 0;
+		break;
+	default:
+		usage();
+		break;
+	} ARGEND;
+
+	user = getlog();
+	if(user == nil || *user == 0)
+		sysfatal("can't read user name");
+
+	if(cflag){
+		if(argc > 0)
+			creatembox(user, argv[0]);
+		else
+			creatembox(user, nil);
+		threadexits(0);
+	}
+
+	if(argc)
+		usage();
+
+#if 0 /* jpc */
+	if(access("/mail/fs/ctl", 0) < 0){
+		startedfs = 1;
+		av[0] = "fs";
+		av[1] = "-p";
+		av[2] = 0;
+		system9("/bin/upas/fs", av, -1);
+	}
+#endif
+	if( (upasfs = nsmount("upasfs", nil)) == nil ) {
+		startedfs = 1;
+		av[0] = "fs";
+		av[1] = "-p";
+		av[2] = 0;
+		fscmd = unsharp("#9/bin/upas/fs");
+		system9(fscmd, av, -1);
+	}
+
+fprint(2,"switchmb\n");
+	switchmb(file, singleton);
+fprint(2,"switchmb2\n");
+
+	top.path = s_copy(root);
+
+	for(cp = ctype; cp < ctype+nelem(ctype)-1; cp++)
+		cp->next = cp+1;
+
+	if(singleton != nil){
+		cur = dosingleton(&top, singleton);
+		if(cur == nil){
+			Bprint(&out, "no message\n");
+			exitfs(0);
+		}
+		pcmd(nil, cur);
+	} else {
+		cur = &top;
+		n = dir2message(&top, reverse);
+		if(n < 0)
+			sysfatal("can't read %s", s_to_c(top.path));
+		Bprint(&out, "%d message%s\n", n, plural(n));
+	}
+
+
+	notify(catchnote);
+	prompt = s_new();
+	for(;;){
+		s_reset(prompt);
+		if(cur == &top)
+			s_append(prompt, ": ");
+		else {
+			mkid(prompt, cur);
+			s_append(prompt, ": ");
+		}
+
+		// leave space at the end of cmd line in case parsecmd needs to
+		// add a space after a '|' or '!'
+		if(readline(s_to_c(prompt), cmdline, sizeof(cmdline)-1) == nil)
+			break;
+		err = parsecmd(cmdline, &cmd, top.child, cur);
+		if(err != nil){
+			Bprint(&out, "!%s\n", err);
+			continue;
+		}
+		if(singleton != nil && cmd.f == icmd){
+			Bprint(&out, "!illegal command\n");
+			continue;
+		}
+		interrupted = 0;
+		if(cmd.msgs == nil || cmd.msgs == &top){
+			x = (*cmd.f)(&cmd, &top);
+			if(x != nil)
+				cur = x;
+		} else for(m = cmd.msgs; m != nil; m = m->cmd){
+			x = m;
+			if(cmd.delete){
+				dcmd(&cmd, x);
+
+				// dp acts differently than all other commands
+				// since its an old lesk idiom that people love.
+				// it deletes the current message, moves the current
+				// pointer ahead one and prints.
+				if(cmd.f == pcmd){
+					if(x->next == nil){
+						Bprint(&out, "!address\n");
+						cur = x;
+						break;
+					} else
+						x = x->next;
+				}
+			}
+			x = (*cmd.f)(&cmd, x);
+			if(x != nil)
+				cur = x;
+			if(interrupted)
+				break;
+			if(singleton != nil && (cmd.delete || cmd.f == dcmd))
+				qcmd(nil, nil);
+		}
+		if(doflush)
+			cur = flushdeleted(cur);
+	}
+	qcmd(nil, nil);
+}
+
+//
+// read the message info
+//
+Message*
+file2message(Message *parent, char *name)
+{
+	Message *m;
+	String *path;
+	char *f[10];
+
+	m = mallocz(sizeof(Message), 1);
+	if(m == nil)
+		return nil;
+	m->path = path = extendpath(parent->path, name);
+	m->fileno = atoi(name);
+	m->info = file2string(path, "info");
+	lineize(s_to_c(m->info), f, nelem(f));
+	m->from = f[0];
+	m->to = f[1];
+	m->cc = f[2];
+	m->replyto = f[3];
+	m->date = f[4];
+	m->subject = f[5];
+	m->type = f[6];
+	m->disposition = f[7];
+	m->filename = f[8];
+	m->len = filelen(path, "raw");
+	if(strstr(m->type, "multipart") != nil || strcmp(m->type, "message/rfc822") == 0)
+		dir2message(m, 0);
+	m->parent = parent;
+
+	return m;
+}
+
+void
+freemessage(Message *m)
+{
+	Message *nm, *next;
+
+	for(nm = m->child; nm != nil; nm = next){
+		next = nm->next;
+		freemessage(nm);
+	}
+	s_free(m->path);
+	s_free(m->info);
+	free(m);
+}
+
+//
+//  read a directory into a list of messages
+//
+int
+dir2message(Message *parent, int reverse)
+{
+//jpc	int i, n, fd, highest, newmsgs;
+	int i, n, highest, newmsgs;
+	CFid *fid;
+	
+	Dir *d;
+	Message *first, *last, *m;
+
+/*	fd = open(s_to_c(parent->path), OREAD);
+	if(fd < 0)
+		return -1; jpc */
+	fid = fsopen(upasfs, s_to_c(parent->path), OREAD);
+	if(fid == nil)
+		return -1;
+
+	// count current entries
+	first = parent->child;
+	highest = newmsgs = 0;
+	for(last = parent->child; last != nil && last->next != nil; last = last->next)
+		if(last->fileno > highest)
+			highest = last->fileno;
+	if(last != nil)
+		if(last->fileno > highest)
+			highest = last->fileno;
+
+	n = fsdirreadall(fid, &d);
+	fprint(2,"read %d messages\n", n);
+	for(i = 0; i < n; i++){
+		if((d[i].qid.type & QTDIR) == 0)
+			continue;
+		if(atoi(d[i].name) <= highest)
+			continue;
+		fprint(2,"calling file2message %d\n", i);
+		m = file2message(parent, d[i].name);
+		// fprint(2,"returned from file2message\n");
+		if(m == nil)
+			break;
+		newmsgs++;
+		if(reverse){
+			m->next = first;
+			if(first != nil)
+				first->prev = m;
+			first = m;
+		} else {
+			if(first == nil)
+				first = m;
+			else
+				last->next = m;
+			m->prev = last;
+			last = m;
+		}
+	} 
+	fprint(2,"exiting loop\n");
+	free(d);
+	fprint(2,"close fid\n");
+	fsclose(fid);
+	fprint(2,"fid closed\n");
+	parent->child = first;
+
+	// renumber and file longest from
+	i = 1;
+	longestfrom = 12;
+	for(m = first; m != nil; m = m->next){
+		fprint(2,"m:%x from: %s\n", m, m->from);
+		m->id = natural ? m->fileno : i++;
+		n = strlen(m->from);
+		fprint(2,"in loop\n");
+		if(n > longestfrom)
+			longestfrom = n;
+	}
+	fprint(2,"exiting dir2message\n");
+
+	return newmsgs;
+}
+
+//
+//  point directly to a message
+//
+Message*
+dosingleton(Message *parent, char *path)
+{
+	char *p, *np;
+	Message *m;
+
+	// walk down to message and read it
+	if(strlen(path) < rootlen)
+		return nil;
+	if(path[rootlen] != '/')
+		return nil;
+	p = path+rootlen+1;
+	np = strchr(p, '/');
+	if(np != nil)
+		*np = 0;
+	m = file2message(parent, p);
+	if(m == nil)
+		return nil;
+	parent->child = m;
+	m->id = 1;
+
+	// walk down to requested component
+	while(np != nil){
+		*np = '/';
+		np = strchr(np+1, '/');
+		if(np != nil)
+			*np = 0;
+		for(m = m->child; m != nil; m = m->next)
+			if(strcmp(path, s_to_c(m->path)) == 0)
+				return m;
+		if(m == nil)
+			return nil;
+	}
+	return m;
+}
+
+//
+//  read a file into a string
+//
+String*
+file2string(String *dir, char *file)
+{
+	String *s;
+//jpc	int fd, n, m;
+	int n, m;
+	CFid *fid;
+
+	s = extendpath(dir, file);
+// jpc	fd = open(s_to_c(s), OREAD);
+	fid = fsopen(upasfs,s_to_c(s), OREAD);
+	s_grow(s, 512);			/* avoid multiple reads on info files */
+	s_reset(s);
+//jpc	if(fd < 0)
+	if(fid == nil)
+		return s;
+
+	for(;;){
+		n = s->end - s->ptr;
+		if(n == 0){
+			s_grow(s, 128);
+			continue;
+		}
+//jpc		m = read(fd, s->ptr, n);
+		m = fsread(fid, s->ptr, n);
+		if(m <= 0)
+			break;
+		s->ptr += m;
+		if(m < n)
+			break;
+	}
+	s_terminate(s);
+//jpc	close(fd);
+	fsclose(fid);
+
+	return s;
+}
+
+//
+//  get the length of a file
+//
+int
+filelen(String *dir, char *file)
+{
+	String *path;
+	Dir *d;
+	int rv;
+
+	path = extendpath(dir, file);
+//jpc	d = dirstat(s_to_c(path));
+	d = fsdirstat(upasfs,s_to_c(path));
+	if(d == nil){
+		s_free(path);
+		return -1;
+	}
+	s_free(path);
+	rv = d->length;
+	free(d);
+	return rv;
+}
+
+//
+//  walk the path name an element
+//
+String*
+extendpath(String *dir, char *name)
+{
+	String *path;
+
+	if(strcmp(s_to_c(dir), ".") == 0)
+		path = s_new();
+	else {
+		path = s_copy(s_to_c(dir));
+		s_append(path, "/");
+	}
+	s_append(path, name);
+	return path;
+}
+
+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;
+}
+
+char*
+nosecs(char *t)
+{
+	char *p;
+
+	p = strchr(t, ':');
+	if(p == nil)
+		return t;
+	p = strchr(p+1, ':');
+	if(p != nil)
+		*p = 0;
+	return t;
+}
+
+char *months[12] =
+{
+	"jan", "feb", "mar", "apr", "may", "jun",
+	"jul", "aug", "sep", "oct", "nov", "dec"
+};
+
+int
+month(char *m)
+{
+	int i;
+
+	for(i = 0; i < 12; i++)
+		if(cistrcmp(m, months[i]) == 0)
+			return i+1;
+	return 1;
+}
+
+enum
+{
+	Yearsecs= 365*24*60*60
+};
+
+void
+cracktime(char *d, char *out, int len)
+{
+	char in[64];
+	char *f[6];
+	int n;
+	Tm tm;
+	long now, then;
+	char *dtime;
+
+	*out = 0;
+	if(d == nil)
+		return;
+	strncpy(in, d, sizeof(in));
+	in[sizeof(in)-1] = 0;
+	n = getfields(in, f, 6, 1, " \t\r\n");
+	if(n != 6){
+		// unknown style
+		snprint(out, 16, "%10.10s", d);
+		return;
+	}
+	now = time(0);
+	memset(&tm, 0, sizeof tm);
+	if(strchr(f[0], ',') != nil && strchr(f[4], ':') != nil){
+		// 822 style
+		tm.year = atoi(f[3])-1900;
+		tm.mon = month(f[2]);
+		tm.mday = atoi(f[1]);
+		dtime = nosecs(f[4]);
+		then = tm2sec(&tm);
+	} else if(strchr(f[3], ':') != nil){
+		// unix style
+		tm.year = atoi(f[5])-1900;
+		tm.mon = month(f[1]);
+		tm.mday = atoi(f[2]);
+		dtime = nosecs(f[3]);
+		then = tm2sec(&tm);
+	} else {
+		then = now;
+		tm = *localtime(now);
+		dtime = "";
+	}
+
+	if(now - then < Yearsecs/2)
+		snprint(out, len, "%2d/%2.2d %s", tm.mon, tm.mday, dtime);
+	else
+		snprint(out, len, "%2d/%2.2d  %4d", tm.mon, tm.mday, tm.year+1900);
+}
+
+Ctype*
+findctype(Message *m)
+{
+	char *p;
+	char ftype[128];
+	int n, pfd[2];
+	Ctype *a, *cp;
+	/* static Ctype nulltype	= { "", 0, 0, 0 }; jpc */
+	static Ctype bintype 	= { "application/octet-stream", "bin", 0, 0 };
+
+	for(cp = ctype; cp; cp = cp->next)
+		if(strncmp(cp->type, m->type, strlen(cp->type)) == 0)
+			return cp;
+
+/*	use file(1) for any unknown mimetypes
+ *
+ *	if (strcmp(m->type, bintype.type) != 0)
+ *		return &nulltype;
+ */
+	if(pipe(pfd) < 0)
+		return &bintype;
+
+	*ftype = 0;
+	switch(fork()){
+	case -1:
+		break;
+	case 0:
+		close(pfd[1]);
+		close(0);
+		dup(pfd[0], 0);
+		close(1);
+		dup(pfd[0], 1);
+//jpc 		execl("/bin/file", "file", "-m", s_to_c(extendpath(m->path, "body")), nil);
+		execl(unsharp("#9/bin/file"), "file", "-m", s_to_c(extendpath(m->path, "body")), nil);
+		threadexits(0);
+	default:
+		close(pfd[0]);
+		n = read(pfd[1], ftype, sizeof(ftype));
+		if(n > 0)
+			ftype[n] = 0;
+		close(pfd[1]);
+		waitpid();
+		break;
+	}
+
+	if (*ftype=='\0' || (p = strchr(ftype, '/')) == nil)
+		return &bintype;
+	*p++ = 0;
+
+	a = mallocz(sizeof(Ctype), 1);
+	a->type = strdup(ftype);
+	a->ext = strdup(p);
+	a->display = 0;
+	a->plumbdest = strdup(ftype);
+	for(cp = ctype; cp->next; cp = cp->next)
+		continue;
+	cp->next = a;
+	a->next = nil;
+	return a;
+}
+
+void
+mkid(String *s, Message *m)
+{
+	char buf[32];
+
+	if(m->parent != &top){
+		mkid(s, m->parent);
+		s_append(s, ".");
+	}
+	sprint(buf, "%d", m->id);
+	s_append(s, buf);
+}
+
+void
+snprintheader(char *buf, int len, Message *m)
+{
+	char timebuf[32];
+	String *id;
+	char *p, *q;;
+
+	// create id
+	id = s_new();
+	mkid(id, m);
+
+	if(*m->from == 0){
+		// no from
+		snprint(buf, len, "%-3s    %s %6d  %s",
+			s_to_c(id),
+			m->type,
+			m->len,
+			m->filename);
+	} else if(*m->subject){
+		q = p = strdup(m->subject);
+		while(*p == ' ')
+			p++;
+		if(strlen(p) > 50)
+			p[50] = 0;
+		cracktime(m->date, timebuf, sizeof(timebuf));
+		snprint(buf, len, "%-3s %c%c%c %6d  %11.11s %-*.*s %s",
+			s_to_c(id),
+			m->child ? 'H' : ' ',
+			m->deleted ? 'd' : ' ',
+			m->stored ? 's' : ' ',
+			m->len,
+			timebuf,
+			longestfrom, longestfrom, m->from,
+			p);
+		free(q);
+	} else {
+		cracktime(m->date, timebuf, sizeof(timebuf));
+		snprint(buf, len, "%-3s %c%c%c %6d  %11.11s %s",
+			s_to_c(id),
+			m->child ? 'H' : ' ',
+			m->deleted ? 'd' : ' ',
+			m->stored ? 's' : ' ',
+			m->len,
+			timebuf,
+			m->from);
+	}
+	s_free(id);
+}
+
+char *spaces = "                                                                    ";
+
+void
+snprintHeader(char *buf, int len, int indent, Message *m)
+{
+	String *id;
+	char typeid[64];
+	char *p, *e;
+
+	// create id
+	id = s_new();
+	mkid(id, m);
+
+	e = buf + len;
+
+	snprint(typeid, sizeof typeid, "%s    %s", s_to_c(id), m->type);
+	if(indent < 6)
+		p = seprint(buf, e, "%-32s %-6d ", typeid, m->len);
+	else
+		p = seprint(buf, e, "%-64s %-6d ", typeid, m->len);
+	if(m->filename && *m->filename)
+		p = seprint(p, e, "(file,%s)", m->filename);
+	if(m->from && *m->from)
+		p = seprint(p, e, "(from,%s)", m->from);
+	if(m->subject && *m->subject)
+		seprint(p, e, "(subj,%s)", m->subject);
+
+	s_free(id);
+}
+
+char sstring[256];
+
+//	cmd := range cmd ' ' arg-list ; 
+//	range := address
+//		| address ',' address
+//		| 'g' search ;
+//	address := msgno
+//		| search ;
+//	msgno := number
+//		| number '/' msgno ;
+//	search := '/' string '/'
+//		| '%' string '%' ;
+//
+Reprog*
+parsesearch(char **pp)
+{
+	char *p, *np;
+	int c, n;
+
+	p = *pp;
+	c = *p++;
+	np = strchr(p, c);
+	if(np != nil){
+		*np++ = 0;
+		*pp = np;
+	} else {
+		n = strlen(p);
+		*pp = p + n;
+	}
+	if(*p == 0)
+		p = sstring;
+	else{
+		strncpy(sstring, p, sizeof(sstring));
+		sstring[sizeof(sstring)-1] = 0;
+	}
+	return regcomp(p);
+}
+
+char*
+parseaddr(char **pp, Message *first, Message *cur, Message *unspec, Message **mp)
+{
+	int n;
+	Message *m;
+	char *p;
+	Reprog *prog;
+	int c, sign;
+	char buf[256];
+
+	*mp = nil;
+	p = *pp;
+
+	if(*p == '+'){
+		sign = 1;
+		p++;
+		*pp = p;
+	} else if(*p == '-'){
+		sign = -1;
+		p++;
+		*pp = p;
+	} else
+		sign = 0;
+
+	switch(*p){
+	default:
+		if(sign){
+			n = 1;
+			goto number;
+		}
+		*mp = unspec;
+		break;	
+	case '0': case '1': case '2': case '3': case '4':
+	case '5': case '6': case '7': case '8': case '9':
+		n = strtoul(p, pp, 10);
+		if(n == 0){
+			if(sign)
+				*mp = cur;
+			else
+				*mp = &top;
+			break;
+		}
+	number:
+		m = nil;
+		switch(sign){
+		case 0:
+			for(m = first; m != nil; m = m->next)
+				if(m->id == n)
+					break;
+			break;
+		case -1:
+			if(cur != &top)
+				for(m = cur; m != nil && n > 0; n--)
+					m = m->prev;
+			break;
+		case 1:
+			if(cur == &top){
+				n--;
+				cur = first;
+			}
+			for(m = cur; m != nil && n > 0; n--)
+				m = m->next;
+			break;
+		}
+		if(m == nil)
+			return "address";
+		*mp = m;
+		break;
+	case '%':
+	case '/':
+	case '?':
+		c = *p;
+		prog = parsesearch(pp);
+		if(prog == nil)
+			return "badly formed regular expression";
+		m = nil;
+		switch(c){
+		case '%':
+			for(m = cur == &top ? first : cur->next; m != nil; m = m->next){
+				if(rawsearch(m, prog))
+					break;
+			}
+			break;
+		case '/':
+			for(m = cur == &top ? first : cur->next; m != nil; m = m->next){
+				snprintheader(buf, sizeof(buf), m);
+				if(regexec(prog, buf, nil, 0))
+					break;
+			}
+			break;
+		case '?':
+			for(m = cur == &top ? nil : cur->prev; m != nil; m = m->prev){
+				snprintheader(buf, sizeof(buf), m);
+				if(regexec(prog, buf, nil, 0))
+					break;
+			}
+			break;
+		}
+		if(m == nil)
+			return "search";
+		*mp = m;
+		free(prog);
+		break;
+	case '$':
+		for(m = first; m != nil && m->next != nil; m = m->next)
+			;
+		*mp = m;
+		*pp = p+1;
+		break;
+	case '.':
+		*mp = cur;
+		*pp = p+1;
+		break;
+	case ',':
+		*mp = first;
+		*pp = p;
+		break;
+	}
+
+	if(*mp != nil && **pp == '.'){
+		(*pp)++;
+		if((*mp)->child == nil)
+			return "no sub parts";
+		return parseaddr(pp, (*mp)->child, (*mp)->child, (*mp)->child, mp);
+	}
+	if(**pp == '+' || **pp == '-' || **pp == '/' || **pp == '%')
+		return parseaddr(pp, first, *mp, *mp, mp);
+
+	return nil;
+}
+
+//
+//  search a message for a regular expression match
+//
+int
+rawsearch(Message *m, Reprog *prog)
+{
+	char buf[4096+1];
+//jpc	int i, fd, rv;
+	int i, rv;
+	CFid *fid;
+	String *path;
+
+	path = extendpath(m->path, "raw");
+//jpc	fd = open(s_to_c(path), OREAD);
+//jpc	if(fd < 0)
+//jpc		return 0;
+	fid = fsopen(upasfs,s_to_c(path), OREAD);
+	if(fid == nil)
+		return 0;
+
+	// march through raw message 4096 bytes at a time
+	// with a 128 byte overlap to chain the re search.
+	rv = 0;
+	for(;;){
+//jpc		i = read(fd, buf, sizeof(buf)-1);
+		i = fsread(fid, buf, sizeof(buf)-1);
+		if(i <= 0)
+			break;
+		buf[i] = 0;
+		if(regexec(prog, buf, nil, 0)){
+			rv = 1;
+			break;
+		}
+		if(i < sizeof(buf)-1)
+			break;
+//jpc		if(seek(fd, -128LL, 1) < 0)
+		if(fsseek(fid, -128LL, 1) < 0)
+			break;
+	}
+
+	fsclose(fid);
+	s_free(path);
+	return rv;
+}
+
+
+char*
+parsecmd(char *p, Cmd *cmd, Message *first, Message *cur)
+{
+	Reprog *prog;
+	Message *m, *s, *e, **l, *last;
+	char buf[256];
+	char *err;
+	int i, c;
+	char *q;
+	static char errbuf[Errlen];
+
+	cmd->delete = 0;
+	l = &cmd->msgs;
+	*l = nil;
+
+	// eat white space
+	while(*p == ' ')
+		p++;
+
+	// null command is a special case (advance and print)
+	if(*p == 0){
+		if(cur == &top){
+			// special case
+			m = first;
+		} else {
+			// walk to the next message even if we have to go up
+			m = cur->next;
+			while(m == nil && cur->parent != nil){
+				cur = cur->parent;
+				m = cur->next;
+			}
+		}
+		if(m == nil)
+			return "address";
+		*l = m;
+		m->cmd = nil;
+		cmd->an = 0;
+		cmd->f = pcmd;
+		return nil;
+	}
+
+	// global search ?
+	if(*p == 'g'){
+		p++;
+
+		// no search string means all messages
+		if(*p != '/' && *p != '%'){
+			for(m = first; m != nil; m = m->next){
+				*l = m;
+				l = &m->cmd;
+				*l = nil;
+			}
+		} else {
+			// mark all messages matching this search string
+			c = *p;
+			prog = parsesearch(&p);
+			if(prog == nil)
+				return "badly formed regular expression";
+			if(c == '%'){
+				for(m = first; m != nil; m = m->next){
+					if(rawsearch(m, prog)){
+						*l = m;
+						l = &m->cmd;
+						*l = nil;
+					}
+				}
+			} else {
+				for(m = first; m != nil; m = m->next){
+					snprintheader(buf, sizeof(buf), m);
+					if(regexec(prog, buf, nil, 0)){
+						*l = m;
+						l = &m->cmd;
+						*l = nil;
+					}
+				}
+			}
+			free(prog);
+		}
+	} else {
+	
+		// parse an address
+		s = e = nil;
+		err = parseaddr(&p, first, cur, cur, &s);
+		if(err != nil)
+			return err;
+		if(*p == ','){
+			// this is an address range
+			if(s == &top)
+				s = first;
+			p++;
+			for(last = s; last != nil && last->next != nil; last = last->next)
+				;
+			err = parseaddr(&p, first, cur, last, &e);
+			if(err != nil)
+				return err;
+	
+			// select all messages in the range
+			for(; s != nil; s = s->next){
+				*l = s;
+				l = &s->cmd;
+				*l = nil;
+				if(s == e)
+					break;
+			}
+			if(s == nil)
+				return "null address range";
+		} else {
+			// single address
+			if(s != &top){
+				*l = s;
+				s->cmd = nil;
+			}
+		}
+	}
+
+	// insert a space after '!'s and '|'s
+	for(q = p; *q; q++)
+		if(*q != '!' && *q != '|')
+			break;
+	if(q != p && *q != ' '){
+		memmove(q+1, q, strlen(q)+1);
+		*q = ' ';
+	}
+
+	cmd->an = getfields(p, cmd->av, nelem(cmd->av) - 1, 1, " \t\r\n");
+	if(cmd->an == 0 || *cmd->av[0] == 0)
+		cmd->f = pcmd;
+	else {
+		// hack to allow all messages to start with 'd'
+		if(*(cmd->av[0]) == 'd' && *(cmd->av[0]+1) != 0){
+			cmd->delete = 1;
+			cmd->av[0]++;
+		}
+
+		// search command table
+		for(i = 0; cmdtab[i].cmd != nil; i++)
+			if(strcmp(cmd->av[0], cmdtab[i].cmd) == 0)
+				break;
+		if(cmdtab[i].cmd == nil)
+			return "illegal command";
+		if(cmdtab[i].args == 0 && cmd->an > 1){
+			snprint(errbuf, sizeof(errbuf), "%s doesn't take an argument", cmdtab[i].cmd);
+			return errbuf;
+		}
+		cmd->f = cmdtab[i].f;
+	}
+	return nil; 
+}
+
+// inefficient read from standard input
+char*
+readline(char *prompt, char *line, int len)
+{
+	char *p, *e;
+	int n;
+
+retry:
+	interrupted = 0;
+	Bprint(&out, "%s", prompt);
+	Bflush(&out);
+	e = line + len;
+	for(p = line; p < e; p++){
+		n = read(0, p, 1);
+		if(n < 0){
+			if(interrupted)
+				goto retry;
+			return nil;
+		}
+		if(n == 0)
+			return nil;
+		if(*p == '\n')
+			break;
+	}
+	*p = 0;
+	return line;
+}
+
+void
+messagecount(Message *m)
+{
+	int i;
+
+	i = 0;
+	for(; m != nil; m = m->next)
+		i++;
+	Bprint(&out, "%d message%s\n", i, plural(i));
+}
+
+Message*
+aichcmd(Message *m, int indent)
+{
+	char	hdr[256];
+
+	if(m == &top)
+		return nil;
+
+	snprintHeader(hdr, sizeof(hdr), indent, m);
+	Bprint(&out, "%s\n", hdr);
+	for(m = m->child; m != nil; m = m->next)
+		aichcmd(m, indent+1);
+	return nil;
+}
+
+Message*
+Hcmd(Cmd* dummy, Message *m)
+{
+	if(m == &top)
+		return nil;
+	aichcmd(m, 0);
+	return nil;
+}
+
+Message*
+hcmd(Cmd* dummy, Message *m)
+{
+	char	hdr[256];
+
+	if(m == &top)
+		return nil;
+
+	snprintheader(hdr, sizeof(hdr), m);
+	Bprint(&out, "%s\n", hdr);
+	return nil;
+}
+
+Message*
+bcmd(Cmd* dummy, Message *m)
+{
+	int i;
+	Message *om = m;
+
+	if(m == &top)
+		m = top.child;
+	for(i = 0; i < 10 && m != nil; i++){
+		hcmd(nil, m);
+		om = m;
+		m = m->next;
+	}
+
+	return om;
+}
+
+Message*
+ncmd(Cmd* dummy, Message *m)
+{
+	if(m == &top)
+		return m->child;
+	return m->next;
+}
+
+int
+printpart(String *s, char *part)
+{
+	char buf[4096];
+//jpc	int n, fd, tot;
+	int n, tot;
+	CFid *fid;
+	String *path;
+
+	path = extendpath(s, part);
+//jpc	fd = open(s_to_c(path), OREAD);
+	fid = fsopen(upasfs,s_to_c(path), OREAD);
+	s_free(path);
+//jpc	if(fd < 0){
+	if(fid ==  nil){
+		fprint(2, "!message dissappeared\n");
+		return 0;
+	}
+	tot = 0;
+//jpc 	while((n = read(fd, buf, sizeof(buf))) > 0){
+	while((n = fsread(fid, buf, sizeof(buf))) > 0){
+		if(interrupted)
+			break;
+		if(Bwrite(&out, buf, n) <= 0)
+			break;
+		tot += n;
+	}
+	fsclose(fid);
+	return tot;
+}
+
+int
+printhtml(Message *m)
+{
+	Cmd c;
+
+	c.an = 3;
+	c.av[1] = unsharp("#9/bin/htmlfmt");
+	c.av[2] = "-l 40 -cutf-8";
+	Bprint(&out, "!%s\n", c.av[1]);
+	Bflush(&out);
+	pipecmd(&c, m);
+	return 0;
+}
+
+Message*
+Pcmd(Cmd* dummy, Message *m)
+{
+	if(m == &top)
+		return &top;
+	if(m->parent == &top)
+		printpart(m->path, "unixheader");
+	printpart(m->path, "raw");
+	return m;
+}
+
+void
+compress(char *p)
+{
+	char *np;
+	int last;
+
+	last = ' ';
+	for(np = p; *p; p++){
+		if(*p != ' ' || last != ' '){
+			last = *p;
+			*np++ = last;
+		}
+	}
+	*np = 0;
+}
+
+Message*
+pcmd(Cmd* dummy, Message *m)
+{
+	Message *nm;
+	Ctype *cp;
+	String *s;
+	char buf[128];
+
+	if(m == &top)
+		return &top;
+	if(m->parent == &top)
+		printpart(m->path, "unixheader");
+	if(printpart(m->path, "header") > 0)
+		Bprint(&out, "\n");
+	cp = findctype(m);
+	if(cp->display){
+		if(strcmp(m->type, "text/html") == 0)
+			printhtml(m);
+		else
+			printpart(m->path, "body");
+	} else if(strcmp(m->type, "multipart/alternative") == 0){
+		for(nm = m->child; nm != nil; nm = nm->next){
+			cp = findctype(nm);
+			if(cp->ext != nil && strncmp(cp->ext, "txt", 3) == 0)
+				break;
+		}
+		if(nm == nil)
+			for(nm = m->child; nm != nil; nm = nm->next){
+				cp = findctype(nm);
+				if(cp->display)
+					break;
+			}
+		if(nm != nil)
+			pcmd(nil, nm);
+		else
+			hcmd(nil, m);
+	} else if(strncmp(m->type, "multipart/", 10) == 0){
+		nm = m->child;
+		if(nm != nil){
+			// always print first part
+			pcmd(nil, nm);
+
+			for(nm = nm->next; nm != nil; nm = nm->next){
+				s = rooted(s_clone(nm->path));
+				cp = findctype(nm);
+				snprintHeader(buf, sizeof buf, -1, nm);
+				compress(buf);
+				if(strcmp(nm->disposition, "inline") == 0){
+					if(cp->ext != nil)
+						Bprint(&out, "\n--- %s %s/body.%s\n\n",
+							buf, s_to_c(s), cp->ext);
+					else
+						Bprint(&out, "\n--- %s %s/body\n\n",
+							buf, s_to_c(s));
+					pcmd(nil, nm);
+				} else {
+					if(cp->ext != nil)
+						Bprint(&out, "\n!--- %s %s/body.%s\n",
+							buf, s_to_c(s), cp->ext);
+					else
+						Bprint(&out, "\n!--- %s %s/body\n",
+							buf, s_to_c(s));
+				}
+				s_free(s);
+			}
+		} else {
+			hcmd(nil, m);
+		}
+	} else if(strcmp(m->type, "message/rfc822") == 0){
+		pcmd(nil, m->child);
+	} else if(plumb(m, cp) >= 0)
+		Bprint(&out, "\n!--- using plumber to display message of type %s\n", m->type);
+	else
+		Bprint(&out, "\n!--- cannot display messages of type %s\n", m->type);
+		
+	return m;
+}
+
+void
+printpartindented(String *s, char *part, char *indent)
+{
+	char *p;
+	String *path;
+	Biobuf *b;
+
+	fprint(2,"printpartindented: fixme\n");
+	path = extendpath(s, part);
+	b = Bopen(s_to_c(path), OREAD);
+	s_free(path);
+	if(b == nil){
+		fprint(2, "!message dissappeared\n");
+		return;
+	}
+	while((p = Brdline(b, '\n')) != nil){
+		if(interrupted)
+			break;
+		p[Blinelen(b)-1] = 0;
+		if(Bprint(&out, "%s%s\n", indent, p) <= 0)
+			break;
+	}
+	Bprint(&out, "\n");
+	Bterm(b);
+}
+
+Message*
+quotecmd(Cmd* dummy, Message *m)
+{
+	Message *nm;
+	Ctype *cp;
+
+	if(m == &top)
+		return &top;
+	Bprint(&out, "\n");
+	if(m->from != nil && *m->from)
+		Bprint(&out, "On %s, %s wrote:\n", m->date, m->from);
+	cp = findctype(m);
+	if(cp->display){
+		printpartindented(m->path, "body", "> ");
+	} else if(strcmp(m->type, "multipart/alternative") == 0){
+		for(nm = m->child; nm != nil; nm = nm->next){
+			cp = findctype(nm);
+			if(cp->ext != nil && strncmp(cp->ext, "txt", 3) == 0)
+				break;
+		}
+		if(nm == nil)
+			for(nm = m->child; nm != nil; nm = nm->next){
+				cp = findctype(nm);
+				if(cp->display)
+					break;
+			}
+		if(nm != nil)
+			quotecmd(nil, nm);
+	} else if(strncmp(m->type, "multipart/", 10) == 0){
+		nm = m->child;
+		if(nm != nil){
+			cp = findctype(nm);
+			if(cp->display || strncmp(m->type, "multipart/", 10) == 0)
+				quotecmd(nil, nm);
+		}
+	}
+	return m;
+}
+
+// really delete messages
+Message*
+flushdeleted(Message *cur)
+{
+	Message *m, **l;
+	char buf[1024], *p, *e, *msg;
+//jpc	int deld, n, fd;
+	int deld, n;
+	CFid *fid;
+	int i;
+
+	doflush = 0;
+	deld = 0;
+
+//jpc	fd = open("/mail/fs/ctl", ORDWR);
+//jpc	if(fd < 0){
+//jpc		fprint(2, "!can't delete mail, opening /mail/fs/ctl: %r\n");
+//jpc		exitfs(0);
+//jpc	}
+	fid = fsopen(upasfs,"ctl", ORDWR);
+	if(fid == nil){
+		fprint(2, "!can't delete mail, opening /mail/fs/ctl: %r\n");
+		exitfs(0);
+	}
+	e = &buf[sizeof(buf)];
+	p = seprint(buf, e, "delete %s", mbname);
+	n = 0;
+	for(l = &top.child; *l != nil;){
+		m = *l;
+		if(!m->deleted){
+			l = &(*l)->next;
+			continue;
+		}
+
+		// don't return a pointer to a deleted message
+		if(m == cur)
+			cur = m->next;
+
+		deld++;
+		msg = strrchr(s_to_c(m->path), '/');
+		if(msg == nil)
+			msg = s_to_c(m->path);
+		else
+			msg++;
+		if(e-p < 10){
+//jpc			write(fd, buf, p-buf);
+			fswrite(fid, buf, p-buf);
+			n = 0;
+			p = seprint(buf, e, "delete %s", mbname);
+		}
+		p = seprint(p, e, " %s", msg);
+		n++;
+
+		// unchain and free
+		*l = m->next;
+		if(m->next)
+			m->next->prev = m->prev;
+		freemessage(m);
+	}
+	if(n)
+		fswrite(fid, buf, p-buf);
+//jpc		write(fd, buf, p-buf);
+
+//jpc	close(fd);
+	fsclose(fid);
+
+	if(deld)
+		Bprint(&out, "!%d message%s deleted\n", deld, plural(deld));
+
+	// renumber
+	i = 1;
+	for(m = top.child; m != nil; m = m->next)
+		m->id = natural ? m->fileno : i++;
+
+	// if we're out of messages, go back to first
+	// if no first, return the fake first
+	if(cur == nil){
+		if(top.child)
+			return top.child;
+		else
+			return &top;
+	}
+	return cur;
+}
+
+Message*
+qcmd(Cmd* dummy, Message* dummy2)
+{
+	flushdeleted(nil);
+
+	if(didopen)
+		closemb();
+	Bflush(&out);
+
+	exitfs(0);
+	return nil;	// not reached
+}
+
+Message*
+ycmd(Cmd* dummy, Message *m)
+{
+	doflush = 1;
+
+	return icmd(nil, m);
+}
+
+Message*
+xcmd(Cmd* dummy, Message* dummy2)
+{
+	exitfs(0);
+	return nil;	// not reached
+}
+
+Message*
+eqcmd(Cmd* dummy, Message *m)
+{
+	if(m == &top)
+		Bprint(&out, "0\n");
+	else
+		Bprint(&out, "%d\n", m->id);
+	return nil;
+}
+
+Message*
+dcmd(Cmd* dummy, Message *m)
+{
+	if(m == &top){
+		Bprint(&out, "!address\n");
+		return nil;
+	}
+	while(m->parent != &top)
+		m = m->parent;
+	m->deleted = 1;
+	return m;
+}
+
+Message*
+ucmd(Cmd* dummy, Message *m)
+{
+	if(m == &top)
+		return nil;
+	while(m->parent != &top)
+		m = m->parent;
+	if(m->deleted < 0)
+		Bprint(&out, "!can't undelete, already flushed\n");
+	m->deleted = 0;
+	return m;
+}
+
+
+Message*
+icmd(Cmd* dummy, Message *m)
+{
+	int n;
+
+	n = dir2message(&top, reverse);
+	if(n > 0)
+		Bprint(&out, "%d new message%s\n", n, plural(n));
+	return m;
+}
+
+Message*
+helpcmd(Cmd* dummy, Message *m)
+{
+	int i;
+
+	Bprint(&out, "Commands are of the form [<range>] <command> [args]\n");
+	Bprint(&out, "<range> := <addr> | <addr>','<addr>| 'g'<search>\n");
+	Bprint(&out, "<addr> := '.' | '$' | '^' | <number> | <search> | <addr>'+'<addr> | <addr>'-'<addr>\n");
+	Bprint(&out, "<search> := '/'<regexp>'/' | '?'<regexp>'?' | '%%'<regexp>'%%'\n");
+	Bprint(&out, "<command> :=\n");
+	for(i = 0; cmdtab[i].cmd != nil; i++)
+		Bprint(&out, "%s\n", cmdtab[i].help);
+	return m;
+}
+
+int
+tomailer(char **av)
+{
+	Waitmsg *w;
+	int pid, i;
+
+	// start the mailer and get out of the way
+	switch(pid = fork()){
+	case -1:
+		fprint(2, "can't fork: %r\n");
+		return -1;
+	case 0:
+//jpc		Bprint(&out, "!/bin/upas/marshal");
+		Bprint(&out, "!%s",unsharp("#9/bin/upas/marshal"));
+		for(i = 1; av[i]; i++){
+			if(strchr(av[i], ' ') != nil)
+				Bprint(&out, " '%s'", av[i]);
+			else
+				Bprint(&out, " %s", av[i]);
+		}
+		Bprint(&out, "\n");
+		Bflush(&out);
+		av[0] = "marshal";
+		chdir(wd);
+//jpc		exec("/bin/upas/marshal", av);
+//jpc		fprint(2, "couldn't exec /bin/upas/marshal\n");
+		exec(unsharp("#9/bin/upas/marshal"), av);
+		fprint(2, "couldn't exec %s\n",unsharp("#9/bin/upas/marshal"));
+		threadexits(0);
+	default:
+		w = wait();
+		if(w == nil){
+			if(interrupted)
+				postnote(PNPROC, pid, "die");
+			waitpid();
+			return -1;
+		}
+		if(w->msg[0]){
+			fprint(2, "mailer failed: %s\n", w->msg);
+			free(w);
+			return -1;
+		}
+		free(w);
+		Bprint(&out, "!\n");
+		break;
+	}
+	return 0;
+}
+
+//
+// like tokenize but obey "" quoting
+//
+int
+tokenize822(char *str, char **args, int max)
+{
+	int na;
+	int intok = 0, inquote = 0;
+
+	if(max <= 0)
+		return 0;	
+	for(na=0; ;str++)
+		switch(*str) {
+		case ' ':
+		case '\t':
+			if(inquote)
+				goto Default;
+			/* fall through */
+		case '\n':
+			*str = 0;
+			if(!intok)
+				continue;
+			intok = 0;
+			if(na < max)
+				continue;
+			/* fall through */
+		case 0:
+			return na;
+		case '"':
+			inquote ^= 1;
+			/* fall through */
+		Default:
+		default:
+			if(intok)
+				continue;
+			args[na++] = str;
+			intok = 1;
+		}
+	return 0;	/* can't get here; silence compiler */
+}
+
+Message*
+rcmd(Cmd *c, Message *m)
+{
+	char *av[128];
+	int i, ai = 1;
+	Message *nm;
+	char *addr;
+	String *path = nil;
+	String *rpath;
+	String *subject = nil;
+	String *from;
+
+	if(m == &top){
+		Bprint(&out, "!address\n");
+		return nil;
+	}
+
+	addr = nil;
+	for(nm = m; nm != &top; nm = nm->parent){
+ 		if(*nm->replyto != 0){
+			addr = nm->replyto;
+			break;
+		}
+	}
+	if(addr == nil){
+		Bprint(&out, "!no reply address\n");
+		return nil;
+	}
+
+	if(nm == &top){
+		print("!noone to reply to\n");
+		return nil;
+	}
+
+	for(nm = m; nm != &top; nm = nm->parent){
+		if(*nm->subject){
+			av[ai++] = "-s";
+			subject = addrecolon(nm->subject);
+			av[ai++] = s_to_c(subject);;
+			break;
+		}
+	}
+
+	av[ai++] = "-R";
+	rpath = rooted(s_clone(m->path));
+	av[ai++] = s_to_c(rpath);
+
+	if(strchr(c->av[0], 'f') != nil){
+		fcmd(c, m);
+		av[ai++] = "-F";
+	}
+
+	if(strchr(c->av[0], 'R') != nil){
+		av[ai++] = "-t";
+		av[ai++] = "message/rfc822";
+		av[ai++] = "-A";
+		path = rooted(extendpath(m->path, "raw"));
+		av[ai++] = s_to_c(path);
+	}
+
+	for(i = 1; i < c->an && ai < nelem(av)-1; i++)
+		av[ai++] = c->av[i];
+	from = s_copy(addr);
+	ai += tokenize822(s_to_c(from), &av[ai], nelem(av) - ai);
+	av[ai] = 0;
+	if(tomailer(av) < 0)
+		m = nil;
+	s_free(path);
+	s_free(rpath);
+	s_free(subject);
+	s_free(from);
+	return m;
+}
+
+Message*
+mcmd(Cmd *c, Message *m)
+{
+	char **av;
+	int i, ai;
+	String *path;
+
+	if(m == &top){
+		Bprint(&out, "!address\n");
+		return nil;
+	}
+
+	if(c->an < 2){
+		fprint(2, "!usage: M list-of addresses\n");
+		return nil;
+	}
+
+	ai = 1;
+	av = malloc(sizeof(char*)*(c->an + 8));
+
+	av[ai++] = "-t";
+	if(m->parent == &top)
+		av[ai++] = "message/rfc822";
+	else
+		av[ai++] = "mime";
+
+	av[ai++] = "-A";
+	path = rooted(extendpath(m->path, "raw"));
+	av[ai++] = s_to_c(path);
+
+	if(strchr(c->av[0], 'M') == nil)
+		av[ai++] = "-n";
+
+	for(i = 1; i < c->an; i++)
+		av[ai++] = c->av[i];
+	av[ai] = 0;
+
+	if(tomailer(av) < 0)
+		m = nil;
+	if(path != nil)
+		s_free(path);
+	free(av);
+	return m;
+}
+
+Message*
+acmd(Cmd *c, Message *m)
+{
+	char *av[128];
+	int i, ai;
+	String *from, *to, *cc, *path = nil, *subject = nil;
+
+	if(m == &top){
+		Bprint(&out, "!address\n");
+		return nil;
+	}
+
+	ai = 1;
+	if(*m->subject){
+		av[ai++] = "-s";
+		subject = addrecolon(m->subject);
+		av[ai++] = s_to_c(subject);
+	}
+
+	if(strchr(c->av[0], 'A') != nil){
+		av[ai++] = "-t";
+		av[ai++] = "message/rfc822";
+		av[ai++] = "-A";
+		path = rooted(extendpath(m->path, "raw"));
+		av[ai++] = s_to_c(path);
+	}
+
+	for(i = 1; i < c->an && ai < nelem(av)-1; i++)
+		av[ai++] = c->av[i];
+	from = s_copy(m->from);
+	ai += tokenize822(s_to_c(from), &av[ai], nelem(av) - ai);
+	to = s_copy(m->to);
+	ai += tokenize822(s_to_c(to), &av[ai], nelem(av) - ai);
+	cc = s_copy(m->cc);
+	ai += tokenize822(s_to_c(cc), &av[ai], nelem(av) - ai);
+	av[ai] = 0;
+	if(tomailer(av) < 0)
+		return nil;
+	s_free(from);
+	s_free(to);
+	s_free(cc);
+	s_free(subject);
+	s_free(path);
+	return m;
+}
+
+String *
+relpath(char *path, String *to)
+{
+	if (*path=='/' || strncmp(path, "./", 2) == 0
+			      || strncmp(path, "../", 3) == 0) {
+		to = s_append(to, path);
+	} else if(mbpath) {
+		to = s_append(to, s_to_c(mbpath));
+		to->ptr = strrchr(to->base, '/')+1;
+		s_append(to, path);
+	}
+	return to;
+}
+
+int
+appendtofile(Message *m, char *part, char *base, int mbox)
+{
+	String *file, *h;
+	int in, out, rv;
+
+	file = extendpath(m->path, part);
+	in = open(s_to_c(file), OREAD);
+	if(in < 0){
+		fprint(2, "!message disappeared\n");
+		return -1;
+	}
+
+	s_reset(file);
+
+	relpath(base, file);
+	if(sysisdir(s_to_c(file))){
+		s_append(file, "/");
+		if(m->filename && strchr(m->filename, '/') == nil)
+			s_append(file, m->filename);
+		else {
+			s_append(file, "att.XXXXXXXXXXX");
+			mktemp(s_to_c(file));
+		}
+	}
+	if(mbox)
+		out = open(s_to_c(file), OWRITE);
+	else
+		out = open(s_to_c(file), OWRITE|OTRUNC);
+	if(out < 0){
+		out = create(s_to_c(file), OWRITE, 0666);
+		if(out < 0){
+			fprint(2, "!can't open %s: %r\n", s_to_c(file));
+			close(in);
+			s_free(file);
+			return -1;
+		}
+	}
+	if(mbox)
+		seek(out, 0, 2);
+
+	// put on a 'From ' line
+	if(mbox){
+		while(m->parent != &top)
+			m = m->parent;
+		h = file2string(m->path, "unixheader");
+		fprint(out, "%s", s_to_c(h));
+		s_free(h);
+	}
+
+	// copy the message escaping what we have to ad adding newlines if we have to
+	if(mbox)
+		rv = appendfiletombox(in, out);
+	else
+		rv = appendfiletofile(in, out);
+
+	close(in);
+	close(out);
+
+	if(rv >= 0)
+		print("!saved in %s\n", s_to_c(file));
+	s_free(file);
+	return rv;
+}
+
+Message*
+scmd(Cmd *c, Message *m)
+{
+	char *file;
+
+	if(m == &top){
+		Bprint(&out, "!address\n");
+		return nil;
+	}
+
+	switch(c->an){
+	case 1:
+		file = "stored";
+		break;
+	case 2:
+		file = c->av[1];
+		break;
+	default:
+		fprint(2, "!usage: s filename\n");
+		return nil;
+	}
+
+	if(appendtofile(m, "raw", file, 1) < 0)
+		return nil;
+
+	m->stored = 1;
+	return m;
+}
+
+Message*
+wcmd(Cmd *c, Message *m)
+{
+	char *file;
+
+	if(m == &top){
+		Bprint(&out, "!address\n");
+		return nil;
+	}
+
+	switch(c->an){
+	case 2:
+		file = c->av[1];
+		break;
+	case 1:
+		if(*m->filename == 0){
+			fprint(2, "!usage: w filename\n");
+			return nil;
+		}
+		file = strrchr(m->filename, '/');
+		if(file != nil)
+			file++;
+		else
+			file = m->filename;
+		break;
+	default:
+		fprint(2, "!usage: w filename\n");
+		return nil;
+	}
+
+	if(appendtofile(m, "body", file, 0) < 0)
+		return nil;
+	m->stored = 1;
+	return m;
+}
+
+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 String*
+foldername(char *rcvr)
+{
+	char *p;
+	int c;
+	String *file;
+	Dir *d;
+	int scarey;
+
+	file = s_new();
+	mboxpath("f", user, file, 0);
+	d = dirstat(s_to_c(file));
+
+	// if $mail/f exists, store there, otherwise in $mail
+	s_restart(file);
+	if(d && d->qid.type == QTDIR){
+		scarey = 0;
+		s_append(file, "f/");
+	} else {
+		scarey = 1;
+	}
+	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, "!won't overwrite %s\n", s_to_c(file));
+		s_free(file);
+		return nil;
+	}
+
+	return file;
+}
+
+Message*
+fcmd(Cmd *c, Message *m)
+{
+	String *folder;
+
+	if(c->an > 1){
+		fprint(2, "!usage: f takes no arguments\n");
+		return nil;
+	}
+
+	if(m == &top){
+		Bprint(&out, "!address\n");
+		return nil;
+	}
+
+	folder = foldername(m->from);
+	if(folder == nil)
+		return nil;
+
+	if(appendtofile(m, "raw", s_to_c(folder), 1) < 0){
+		s_free(folder);
+		return nil;
+	}
+	s_free(folder);
+
+	m->stored = 1;
+	return m;
+}
+
+void
+system9(char *cmd, char **av, int in)
+{
+	int pid;
+
+	switch(pid=fork()){
+	case -1:
+		return;
+	case 0:
+		if(in >= 0){
+			close(0);
+			dup(in, 0);
+			close(in);
+		}
+		if(wd[0] != 0)
+			chdir(wd);
+		exec(cmd, av);
+		fprint(2, "!couldn't exec %s\n", cmd);
+		threadexits(0);
+	default:
+		if(in >= 0)
+			close(in);
+		while(waitpid() < 0){
+			if(!interrupted)
+				break;
+			postnote(PNPROC, pid, "die");
+			continue;
+		}
+		break;
+	}
+}
+
+Message*
+bangcmd(Cmd *c, Message *m)
+{
+	char cmd[4*1024];
+	char *p, *e;
+	char *av[4];
+	int i;
+
+	cmd[0] = 0;
+	p = cmd;
+	e = cmd+sizeof(cmd);
+	for(i = 1; i < c->an; i++)
+		p = seprint(p, e, "%s ", c->av[i]);
+	av[0] = "rc";
+	av[1] = "-c";
+	av[2] = cmd;
+	av[3] = 0;
+	system9(unsharp("#9/bin/rc"), av, -1);
+	Bprint(&out, "!\n");
+	return m;
+}
+
+Message*
+xpipecmd(Cmd *c, Message *m, char *part)
+{
+	char cmd[128];
+	char *p, *e;
+	char *av[4];
+	String *path;
+//jpc	int i, fd;
+	int i;
+	CFid *fid;
+
+	if(c->an < 2){
+		Bprint(&out, "!usage: | cmd\n");
+		return nil;
+	}
+
+	if(m == &top){
+		Bprint(&out, "!address\n");
+		return nil;
+	}
+
+	path = extendpath(m->path, part);
+//jpc	fd = open(s_to_c(path), OREAD);
+	fid = fsopen(upasfs,s_to_c(path), OREAD);
+	s_free(path);
+//jpc	if(fd < 0){	// compatibility with older upas/fs
+	if(fid == nil){	// compatibility with older upas/fs
+		path = extendpath(m->path, "raw");
+//jpc		fd = open(s_to_c(path), OREAD);
+		fid = fsopen(upasfs,s_to_c(path), OREAD);
+		s_free(path);
+	}
+	if(fid < 0){
+		fprint(2, "!message disappeared\n");
+		return nil;
+	}
+
+	p = cmd;
+	e = cmd+sizeof(cmd);
+	cmd[0] = 0;
+	for(i = 1; i < c->an; i++)
+		p = seprint(p, e, "%s ", c->av[i]);
+	av[0] = "rc";
+	av[1] = "-c";
+	av[2] = cmd;
+	av[3] = 0;
+//	system9("/bin/rc", av, fd);	/* system closes fd */
+	system9(unsharp("#9/bin/rc"), av, 0);
+	fsclose(fid);
+	Bprint(&out, "!\n");
+	return m;
+}
+
+Message*
+pipecmd(Cmd *c, Message *m)
+{
+	return xpipecmd(c, m, "body");
+}
+
+Message*
+rpipecmd(Cmd *c, Message *m)
+{
+	return xpipecmd(c, m, "rawunix");
+}
+
+#if 0 /* jpc */
+void
+closemb(void)
+{
+	int fd;
+
+	fd = open("/mail/fs/ctl", ORDWR);
+	if(fd < 0)
+		sysfatal("can't open /mail/fs/ctl: %r");
+
+	// close current mailbox
+	if(*mbname && strcmp(mbname, "mbox") != 0)
+		fprint(fd, "close %s", mbname);
+
+	close(fd);
+}
+#endif
+void
+closemb(void)
+{
+	CFid *fid;
+	char s[256];
+
+	fid = fsopen(upasfs,"ctl", ORDWR);
+	if(fid == nil)
+		sysfatal("can't open upasfs/ctl: %r");
+
+	// close current mailbox
+	if(*mbname && strcmp(mbname, "mbox") != 0) {
+		snprint(s, 256, "close %s", mbname);
+		fswrite(fid,s,strlen(s));
+	}
+
+//jpc	close(fd);
+	fsclose(fid);
+}
+
+int
+switchmb(char *file, char *singleton)
+{
+	char *p;
+	int n, fd;
+	String *path;
+	char buf[256];
+
+	// if the user didn't say anything and there
+	// is an mbox mounted already, use that one
+	// so that the upas/fs -fdefault default is honored.
+	if(file 
+	|| (singleton && access(singleton, 0)<0)
+/* 	|| (!singleton && access("/mail/fs/mbox", 0)<0)){ jpc */
+	|| (!singleton && fsdirstat(upasfs, "upasfs/mbox") )){
+		fprint(2,"can't access /mail/fs/mbox\n");
+		if(file == nil)
+			file = "mbox";
+
+		// close current mailbox
+		closemb();
+		didopen = 1;
+
+		fd = open("/mail/fs/ctl", ORDWR);
+		if(fd < 0)
+			sysfatal("can't open /mail/fs/ctl: %r");
+	
+		path = s_new();
+	
+		// get an absolute path to the mail box
+		if(strncmp(file, "./", 2) == 0){
+			// resolve path here since upas/fs doesn't know
+			// our working directory
+			if(getwd(buf, sizeof(buf)-strlen(file)) == nil){
+				fprint(2, "!can't get working directory: %s\n", buf);
+				return -1;
+			}
+			s_append(path, buf);
+			s_append(path, file+1);
+		} else {
+			mboxpath(file, user, path, 0);
+		}
+	
+		// make up a handle to use when talking to fs
+		p = strrchr(file, '/');
+		if(p == nil){
+			// if its in the mailbox directory, just use the name
+			strncpy(mbname, file, sizeof(mbname));
+			mbname[sizeof(mbname)-1] = 0;
+		} else {
+			// make up a mailbox name
+			p = strrchr(s_to_c(path), '/');
+			p++;
+			if(*p == 0){
+				fprint(2, "!bad mbox name");
+				return -1;
+			}
+			strncpy(mbname, p, sizeof(mbname));
+			mbname[sizeof(mbname)-1] = 0;
+			n = strlen(mbname);
+			if(n > Elemlen-12)
+				n = Elemlen-12;
+			sprint(mbname+n, "%ld", time(0));
+		}
+
+		if(fprint(fd, "open %s %s", s_to_c(path), mbname) < 0){
+			fprint(2, "!can't 'open %s %s': %r\n", file, mbname);
+			s_free(path);
+			return -1;
+		}
+		close(fd);
+	}else
+	if (singleton && access(singleton, 0)==0
+	    && strncmp(singleton, "/mail/fs/", 9) == 0){
+		if ((p = strchr(singleton +10, '/')) == nil){
+			fprint(2, "!bad mbox name");
+			return -1;
+		}
+		n = p-(singleton+9);
+		strncpy(mbname, singleton+9, n);
+		mbname[n+1] = 0;
+		path = s_reset(nil);
+		mboxpath(mbname, user, path, 0);
+	}else{
+		path = s_reset(nil);
+		mboxpath("mbox", user, path, 0);
+		strcpy(mbname, "mbox");
+	}
+
+	sprint(root, "%s", mbname);
+	if(getwd(wd, sizeof(wd)) == 0)
+		wd[0] = 0;
+	if(singleton == nil && chdir(root) >= 0)
+		strcpy(root, ".");
+	rootlen = strlen(root);
+
+	if(mbpath != nil)
+		s_free(mbpath);
+	mbpath = path;
+	return 0;
+}
+
+// like tokenize but for into lines
+int
+lineize(char *s, char **f, int n)
+{
+	int i;
+
+	for(i = 0; *s && i < n; i++){
+		f[i] = s;
+		s = strchr(s, '\n');
+		if(s == nil)
+			break;
+		*s++ = 0;
+	}
+	return i;
+}
+
+
+
+String*
+rooted(String *s)
+{
+	static char buf[256];
+
+	if(strcmp(root, ".") != 0)
+		return s;
+	snprint(buf, sizeof(buf), "/mail/fs/%s/%s", mbname, s_to_c(s));
+	s_free(s);
+	return s_copy(buf);
+}
+
+int
+plumb(Message *m, Ctype *cp)
+{
+	String *s;
+	Plumbmsg *pm;
+	static int fd = -2;
+
+	if(cp->plumbdest == nil)
+		return -1;
+
+	if(fd < -1)
+		fd = plumbopen("send", OWRITE);
+	if(fd < 0)
+		return -1;
+
+	pm = mallocz(sizeof(Plumbmsg), 1);
+	pm->src = strdup("mail");
+	if(*cp->plumbdest)
+		pm->dst = strdup(cp->plumbdest);
+	pm->wdir = nil;
+	pm->type = strdup("text");
+	pm->ndata = -1;
+	s = rooted(extendpath(m->path, "body"));
+	if(cp->ext != nil){
+		s_append(s, ".");
+		s_append(s, cp->ext);
+	}
+	pm->data = strdup(s_to_c(s));
+	s_free(s);
+	plumbsend(fd, pm);
+	plumbfree(pm);
+	return 0;
+}
+
+void
+regerror(char* dummy)
+{
+}
+
+String*
+addrecolon(char *s)
+{
+	String *str;
+
+	if(cistrncmp(s, "re:", 3) != 0){
+		str = s_copy("Re: ");
+		s_append(str, s);
+	} else
+		str = s_copy(s);
+	return str;
+}
+
+void
+exitfs(char *rv)
+{
+	if(startedfs) {
+		fsunmount(upasfs);
+		/* unmount(nil, "/mail/fs"); jpc */
+	}
+//jpc chdir("/sys/src/cmd/upas/ned");
+	threadexits(rv);
+}
blob - /dev/null
blob + 3f3df413487d6c5c93d1de0306a60c0224aeb0a2 (mode 644)
--- /dev/null
+++ src/cmd/upas/pop3/mkfile
@@ -0,0 +1,16 @@
+</$objtype/mkfile
+
+TARG=pop3
+
+OFILES=pop3.$O
+
+BIN=/$objtype/bin/upas
+LIB=../common/libcommon.a$O
+
+UPDATE=\
+	mkfile\
+	${OFILES:%.$O=%.c}\
+
+</sys/src/cmd/mkone
+
+CFLAGS=$CFLAGS -I../common
blob - /dev/null
blob + 84e92b1824beaac666a495077d421e1d34c8fd19 (mode 644)
--- /dev/null
+++ src/cmd/upas/pop3/pop3.c
@@ -0,0 +1,804 @@
+#include "common.h"
+#include <ctype.h>
+#include <auth.h>
+#include <libsec.h>
+
+typedef struct Cmd Cmd;
+struct Cmd
+{
+	char *name;
+	int needauth;
+	int (*f)(char*);
+};
+
+static void hello(void);
+static int apopcmd(char*);
+static int capacmd(char*);
+static int delecmd(char*);
+static int listcmd(char*);
+static int noopcmd(char*);
+static int passcmd(char*);
+static int quitcmd(char*);
+static int rsetcmd(char*);
+static int retrcmd(char*);
+static int statcmd(char*);
+static int stlscmd(char*);
+static int topcmd(char*);
+static int synccmd(char*);
+static int uidlcmd(char*);
+static int usercmd(char*);
+static char *nextarg(char*);
+static int getcrnl(char*, int);
+static int readmbox(char*);
+static void sendcrnl(char*, ...);
+static int senderr(char*, ...);
+static int sendok(char*, ...);
+#pragma varargck argpos sendcrnl 1
+#pragma varargck argpos senderr 1
+#pragma varargck argpos sendok 1
+
+Cmd cmdtab[] =
+{
+	"apop", 0, apopcmd,
+	"capa", 0, capacmd,
+	"dele", 1, delecmd,
+	"list", 1, listcmd,
+	"noop", 0, noopcmd,
+	"pass", 0, passcmd,
+	"quit", 0, quitcmd,
+	"rset", 0, rsetcmd,
+	"retr", 1, retrcmd,
+	"stat", 1, statcmd,
+	"stls", 0, stlscmd,
+	"sync", 1, synccmd,
+	"top", 1, topcmd,
+	"uidl", 1, uidlcmd,
+	"user", 0, usercmd,
+	0, 0, 0,
+};
+
+static Biobuf in;
+static Biobuf out;
+static int passwordinclear;
+static int didtls;
+
+typedef struct Msg Msg;
+struct Msg 
+{
+	int upasnum;
+	char digest[64];
+	int bytes;
+	int deleted;
+};
+
+static int totalbytes;
+static int totalmsgs;
+static Msg *msg;
+static int nmsg;
+static int loggedin;
+static int debug;
+static uchar *tlscert;
+static int ntlscert;
+static char *peeraddr;
+static char tmpaddr[64];
+
+void
+usage(void)
+{
+	fprint(2, "usage: upas/pop3 [-a authmboxfile] [-d debugfile] [-p]\n");
+	exits("usage");
+}
+
+void
+main(int argc, char **argv)
+{
+	int fd;
+	char *arg, cmdbuf[1024];
+	Cmd *c;
+
+	rfork(RFNAMEG);
+	Binit(&in, 0, OREAD);
+	Binit(&out, 1, OWRITE);
+
+	ARGBEGIN{
+	case 'a':
+		loggedin = 1;
+		if(readmbox(EARGF(usage())) < 0)
+			exits(nil);
+		break;
+	case 'd':
+		debug++;
+		if((fd = create(EARGF(usage()), OWRITE, 0666)) >= 0 && fd != 2){
+			dup(fd, 2);
+			close(fd);
+		}
+		break;
+	case 'r':
+		strecpy(tmpaddr, tmpaddr+sizeof tmpaddr, EARGF(usage()));
+		if(arg = strchr(tmpaddr, '!'))
+			*arg = '\0';
+		peeraddr = tmpaddr;
+		break;
+	case 't':
+		tlscert = readcert(EARGF(usage()), &ntlscert);
+		if(tlscert == nil){
+			senderr("cannot read TLS certificate: %r");
+			exits(nil);
+		}
+		break;
+	case 'p':
+		passwordinclear = 1;
+		break;
+	}ARGEND
+
+	/* do before TLS */
+	if(peeraddr == nil)
+		peeraddr = remoteaddr(0,0);
+
+	hello();
+
+	while(Bflush(&out), getcrnl(cmdbuf, sizeof cmdbuf) > 0){
+		arg = nextarg(cmdbuf);
+		for(c=cmdtab; c->name; c++)
+			if(cistrcmp(c->name, cmdbuf) == 0)
+				break;
+		if(c->name == 0){
+			senderr("unknown command %s", cmdbuf);
+			continue;
+		}
+		if(c->needauth && !loggedin){
+			senderr("%s requires authentication", cmdbuf);
+			continue;
+		}
+		(*c->f)(arg);
+	}
+	exits(nil);
+}
+
+/* sort directories in increasing message number order */
+static int
+dircmp(void *a, void *b)
+{
+	return atoi(((Dir*)a)->name) - atoi(((Dir*)b)->name);
+}
+
+static int
+readmbox(char *box)
+{
+	int fd, i, n, nd, lines, pid;
+	char buf[100], err[ERRMAX];
+	char *p;
+	Biobuf *b;
+	Dir *d, *draw;
+	Msg *m;
+	Waitmsg *w;
+
+	unmount(nil, "/mail/fs");
+	switch(pid = fork()){
+	case -1:
+		return senderr("can't fork to start upas/fs");
+
+	case 0:
+		close(0);
+		close(1);
+		open("/dev/null", OREAD);
+		open("/dev/null", OWRITE);
+		execl("/bin/upas/fs", "upas/fs", "-np", "-f", box, nil);
+		snprint(err, sizeof err, "upas/fs: %r");
+		_exits(err);
+		break;
+
+	default:
+		break;
+	}
+
+	if((w = wait()) == nil || w->pid != pid || w->msg[0] != '\0'){
+		if(w && w->pid==pid)
+			return senderr("%s", w->msg);
+		else
+			return senderr("can't initialize upas/fs");
+	}
+	free(w);
+
+	if(chdir("/mail/fs/mbox") < 0)
+		return senderr("can't initialize upas/fs: %r");
+
+	if((fd = open(".", OREAD)) < 0)
+		return senderr("cannot open /mail/fs/mbox: %r");
+	nd = dirreadall(fd, &d);
+	close(fd);
+	if(nd < 0)
+		return senderr("cannot read from /mail/fs/mbox: %r");
+
+	msg = mallocz(sizeof(Msg)*nd, 1);
+	if(msg == nil)
+		return senderr("out of memory");
+
+	if(nd == 0)
+		return 0;
+	qsort(d, nd, sizeof(d[0]), dircmp);
+
+	for(i=0; i<nd; i++){
+		m = &msg[nmsg];
+		m->upasnum = atoi(d[i].name);
+		sprint(buf, "%d/digest", m->upasnum);
+		if((fd = open(buf, OREAD)) < 0)
+			continue;
+		n = readn(fd, m->digest, sizeof m->digest - 1);
+		close(fd);
+		if(n < 0)
+			continue;
+		m->digest[n] = '\0';
+
+		/*
+		 * We need the number of message lines so that we
+		 * can adjust the byte count to include \r's.
+		 * Upas/fs gives us the number of lines in the raw body
+		 * in the lines file, but we have to count rawheader ourselves.
+		 * There is one blank line between raw header and raw body.
+		 */
+		sprint(buf, "%d/rawheader", m->upasnum);
+		if((b = Bopen(buf, OREAD)) == nil)
+			continue;
+		lines = 0;
+		for(;;){
+			p = Brdline(b, '\n');
+			if(p == nil){
+				if((n = Blinelen(b)) == 0)
+					break;
+				Bseek(b, n, 1);
+			}else
+				lines++;
+		}
+		Bterm(b);
+		lines++;
+		sprint(buf, "%d/lines", m->upasnum);
+		if((fd = open(buf, OREAD)) < 0)
+			continue;
+		n = readn(fd, buf, sizeof buf - 1);
+		close(fd);
+		if(n < 0)
+			continue;
+		buf[n] = '\0';
+		lines += atoi(buf);
+
+		sprint(buf, "%d/raw", m->upasnum);
+		if((draw = dirstat(buf)) == nil)
+			continue;
+		m->bytes = lines+draw->length;
+		free(draw);
+		nmsg++;
+		totalmsgs++;
+		totalbytes += m->bytes;
+	}
+	return 0;
+}
+
+/*
+ *  get a line that ends in crnl or cr, turn terminating crnl into a nl
+ *
+ *  return 0 on EOF
+ */
+static int
+getcrnl(char *buf, int n)
+{
+	int c;
+	char *ep;
+	char *bp;
+	Biobuf *fp = &in;
+
+	Bflush(&out);
+
+	bp = buf;
+	ep = bp + n - 1;
+	while(bp != ep){
+		c = Bgetc(fp);
+		if(debug) {
+			seek(2, 0, 2);
+			fprint(2, "%c", c);
+		}
+		switch(c){
+		case -1:
+			*bp = 0;
+			if(bp==buf)
+				return 0;
+			else
+				return bp-buf;
+		case '\r':
+			c = Bgetc(fp);
+			if(c == '\n'){
+				if(debug) {
+					seek(2, 0, 2);
+					fprint(2, "%c", c);
+				}
+				*bp = 0;
+				return bp-buf;
+			}
+			Bungetc(fp);
+			c = '\r';
+			break;
+		case '\n':
+			*bp = 0;
+			return bp-buf;
+		}
+		*bp++ = c;
+	}
+	*bp = 0;
+	return bp-buf;
+}
+
+static void
+sendcrnl(char *fmt, ...)
+{
+	char buf[1024];
+	va_list arg;
+
+	va_start(arg, fmt);
+	vseprint(buf, buf+sizeof(buf), fmt, arg);
+	va_end(arg);
+	if(debug)
+		fprint(2, "-> %s\n", buf);
+	Bprint(&out, "%s\r\n", buf);
+}
+
+static int
+senderr(char *fmt, ...)
+{
+	char buf[1024];
+	va_list arg;
+
+	va_start(arg, fmt);
+	vseprint(buf, buf+sizeof(buf), fmt, arg);
+	va_end(arg);
+	if(debug)
+		fprint(2, "-> -ERR %s\n", buf);
+	Bprint(&out, "-ERR %s\r\n", buf);
+	return -1;
+}
+
+static int
+sendok(char *fmt, ...)
+{
+	char buf[1024];
+	va_list arg;
+
+	va_start(arg, fmt);
+	vseprint(buf, buf+sizeof(buf), fmt, arg);
+	va_end(arg);
+	if(*buf){
+		if(debug)
+			fprint(2, "-> +OK %s\n", buf);
+		Bprint(&out, "+OK %s\r\n", buf);
+	} else {
+		if(debug)
+			fprint(2, "-> +OK\n");
+		Bprint(&out, "+OK\r\n");
+	}
+	return 0;
+}
+
+static int
+capacmd(char*)
+{
+	sendok("");
+	sendcrnl("TOP");
+	if(passwordinclear || didtls)
+		sendcrnl("USER");
+	sendcrnl("PIPELINING");
+	sendcrnl("UIDL");
+	sendcrnl("STLS");
+	sendcrnl(".");
+	return 0;
+}
+
+static int
+delecmd(char *arg)
+{
+	int n;
+
+	if(*arg==0)
+		return senderr("DELE requires a message number");
+
+	n = atoi(arg)-1;
+	if(n < 0 || n >= nmsg || msg[n].deleted)
+		return senderr("no such message");
+
+	msg[n].deleted = 1;
+	totalmsgs--;
+	totalbytes -= msg[n].bytes;
+	sendok("message %d deleted", n+1);
+	return 0;
+}
+
+static int
+listcmd(char *arg)
+{
+	int i, n;
+
+	if(*arg == 0){
+		sendok("+%d message%s (%d octets)", totalmsgs, totalmsgs==1 ? "":"s", totalbytes);
+		for(i=0; i<nmsg; i++){
+			if(msg[i].deleted)
+				continue;
+			sendcrnl("%d %d", i+1, msg[i].bytes);
+		}
+		sendcrnl(".");
+	}else{
+		n = atoi(arg)-1;
+		if(n < 0 || n >= nmsg || msg[n].deleted)
+			return senderr("no such message");
+		sendok("%d %d", n+1, msg[n].bytes);
+	}
+	return 0;
+}
+
+static int
+noopcmd(char *arg)
+{
+	USED(arg);
+	sendok("");
+	return 0;
+}
+
+static void
+_synccmd(char*)
+{
+	int i, fd;
+	char *s;
+	Fmt f;
+
+	if(!loggedin){
+		sendok("");
+		return;
+	}
+
+	fmtstrinit(&f);
+	fmtprint(&f, "delete mbox");
+	for(i=0; i<nmsg; i++)
+		if(msg[i].deleted)
+			fmtprint(&f, " %d", msg[i].upasnum);
+	s = fmtstrflush(&f);
+	if(strcmp(s, "delete mbox") != 0){	/* must have something to delete */
+		if((fd = open("../ctl", OWRITE)) < 0){
+			senderr("open ctl to delete messages: %r");
+			return;
+		}
+		if(write(fd, s, strlen(s)) < 0){
+			senderr("error deleting messages: %r");
+			return;
+		}
+	}
+	sendok("");
+}
+
+static int
+synccmd(char*)
+{
+	_synccmd(nil);
+	return 0;
+}
+
+static int
+quitcmd(char*)
+{
+	synccmd(nil);
+	exits(nil);
+	return 0;
+}
+
+static int
+retrcmd(char *arg)
+{
+	int n;
+	Biobuf *b;
+	char buf[40], *p;
+
+	if(*arg == 0)
+		return senderr("RETR requires a message number");
+	n = atoi(arg)-1;
+	if(n < 0 || n >= nmsg || msg[n].deleted)
+		return senderr("no such message");
+	snprint(buf, sizeof buf, "%d/raw", msg[n].upasnum);
+	if((b = Bopen(buf, OREAD)) == nil)
+		return senderr("message disappeared");
+	sendok("");
+	while((p = Brdstr(b, '\n', 1)) != nil){
+		if(p[0]=='.')
+			Bwrite(&out, ".", 1);
+		Bwrite(&out, p, strlen(p));
+		Bwrite(&out, "\r\n", 2);
+		free(p);
+	}
+	Bterm(b);
+	sendcrnl(".");
+	return 0;
+}
+
+static int
+rsetcmd(char*)
+{
+	int i;
+
+	for(i=0; i<nmsg; i++){
+		if(msg[i].deleted){
+			msg[i].deleted = 0;
+			totalmsgs++;
+			totalbytes += msg[i].bytes;
+		}
+	}
+	return sendok("");
+}
+
+static int
+statcmd(char*)
+{
+	return sendok("%d %d", totalmsgs, totalbytes);
+}
+
+static int
+trace(char *fmt, ...)
+{
+	va_list arg;
+	int n;
+
+	va_start(arg, fmt);
+	n = vfprint(2, fmt, arg);
+	va_end(arg);
+	return n;
+}
+
+static int
+stlscmd(char*)
+{
+	int fd;
+	TLSconn conn;
+
+	if(didtls)
+		return senderr("tls already started");
+	if(!tlscert)
+		return senderr("don't have any tls credentials");
+	sendok("");
+	Bflush(&out);
+
+	memset(&conn, 0, sizeof conn);
+	conn.cert = tlscert;
+	conn.certlen = ntlscert;
+	if(debug)
+		conn.trace = trace;
+	fd = tlsServer(0, &conn);
+	if(fd < 0)
+		sysfatal("tlsServer: %r");
+	dup(fd, 0);
+	dup(fd, 1);
+	close(fd);
+	Binit(&in, 0, OREAD);
+	Binit(&out, 1, OWRITE);
+	didtls = 1;
+	return 0;
+}		
+
+static int
+topcmd(char *arg)
+{
+	int done, i, lines, n;
+	char buf[40], *p;
+	Biobuf *b;
+
+	if(*arg == 0)
+		return senderr("TOP requires a message number");
+	n = atoi(arg)-1;
+	if(n < 0 || n >= nmsg || msg[n].deleted)
+		return senderr("no such message");
+	arg = nextarg(arg);
+	if(*arg == 0)
+		return senderr("TOP requires a line count");
+	lines = atoi(arg);
+	if(lines < 0)
+		return senderr("bad args to TOP");
+	snprint(buf, sizeof buf, "%d/raw", msg[n].upasnum);
+	if((b = Bopen(buf, OREAD)) == nil)
+		return senderr("message disappeared");
+	sendok("");
+	while(p = Brdstr(b, '\n', 1)){
+		if(p[0]=='.')
+			Bputc(&out, '.');
+		Bwrite(&out, p, strlen(p));
+		Bwrite(&out, "\r\n", 2);
+		done = p[0]=='\0';
+		free(p);
+		if(done)
+			break;
+	}
+	for(i=0; i<lines; i++){
+		p = Brdstr(b, '\n', 1);
+		if(p == nil)
+			break;
+		if(p[0]=='.')
+			Bwrite(&out, ".", 1);
+		Bwrite(&out, p, strlen(p));
+		Bwrite(&out, "\r\n", 2);
+		free(p);
+	}
+	sendcrnl(".");
+	Bterm(b);
+	return 0;
+}
+
+static int
+uidlcmd(char *arg)
+{
+	int n;
+
+	if(*arg==0){
+		sendok("");
+		for(n=0; n<nmsg; n++){
+			if(msg[n].deleted)
+				continue;
+			sendcrnl("%d %s", n+1, msg[n].digest);
+		}
+		sendcrnl(".");
+	}else{
+		n = atoi(arg)-1;
+		if(n < 0 || n >= nmsg || msg[n].deleted)
+			return senderr("no such message");
+		sendok("%d %s", n+1, msg[n].digest);
+	}
+	return 0;	
+}
+
+static char*
+nextarg(char *p)
+{
+	while(*p && *p != ' ' && *p != '\t')
+		p++;
+	while(*p == ' ' || *p == '\t')
+		*p++ = 0;
+	return p;
+}
+
+/*
+ * authentication
+ */
+Chalstate *chs;
+char user[256];
+char box[256];
+char cbox[256];
+
+static void
+hello(void)
+{
+	fmtinstall('H', encodefmt);
+	if((chs = auth_challenge("proto=apop role=server")) == nil){
+		senderr("auth server not responding, try later");
+		exits(nil);
+	}
+
+	sendok("POP3 server ready %s", chs->chal);
+}
+
+static int
+setuser(char *arg)
+{
+	char *p;
+
+	strcpy(box, "/mail/box/");
+	strecpy(box+strlen(box), box+sizeof box-7, arg);
+	strcpy(cbox, box);
+	cleanname(cbox);
+	if(strcmp(cbox, box) != 0)
+		return senderr("bad mailbox name");
+	strcat(box, "/mbox");
+
+	strecpy(user, user+sizeof user, arg);
+	if(p = strchr(user, '/'))
+		*p = '\0';
+	return 0;
+}
+
+static int
+usercmd(char *arg)
+{
+	if(loggedin)
+		return senderr("already authenticated");
+	if(*arg == 0)
+		return senderr("USER requires argument");
+	if(setuser(arg) < 0)
+		return -1;
+	return sendok("");
+}
+
+static void
+enableaddr(void)
+{
+	int fd;
+	char buf[64];
+
+	/* hide the peer IP address under a rock in the ratifier FS */
+	if(peeraddr == 0 || *peeraddr == 0)
+		return;
+
+	sprint(buf, "/mail/ratify/trusted/%s#32", peeraddr);
+
+	/*
+	 * if the address is already there and the user owns it,
+	 * remove it and recreate it to give him a new time quanta.
+	 */
+	if(access(buf, 0) >= 0  && remove(buf) < 0)
+		return;
+
+	fd = create(buf, OREAD, 0666);
+	if(fd >= 0){
+		close(fd);
+//		syslog(0, "pop3", "ratified %s", peeraddr);
+	}
+}
+
+static int
+dologin(char *response)
+{
+	AuthInfo *ai;
+	static int tries;
+
+	chs->user = user;
+	chs->resp = response;
+	chs->nresp = strlen(response);
+	if((ai = auth_response(chs)) == nil){
+		if(tries++ >= 5){
+			senderr("authentication failed: %r; server exiting");
+			exits(nil);
+		}	
+		return senderr("authentication failed");
+	}
+
+	if(auth_chuid(ai, nil) < 0){
+		senderr("chuid failed: %r; server exiting");
+		exits(nil);
+	}
+	auth_freeAI(ai);
+	auth_freechal(chs);
+	chs = nil;
+
+	loggedin = 1;
+	if(newns(user, 0) < 0){
+		senderr("newns failed: %r; server exiting");
+		exits(nil);
+	}
+
+	enableaddr();
+	if(readmbox(box) < 0)
+		exits(nil);
+	return sendok("mailbox is %s", box);
+}
+
+static int
+passcmd(char *arg)
+{
+	DigestState *s;
+	uchar digest[MD5dlen];
+	char response[2*MD5dlen+1];
+
+	if(passwordinclear==0 && didtls==0)
+		return senderr("password in the clear disallowed");
+
+	/* use password to encode challenge */
+	if((chs = auth_challenge("proto=apop role=server")) == nil)
+		return senderr("couldn't get apop challenge");
+
+	// hash challenge with secret and convert to ascii
+	s = md5((uchar*)chs->chal, chs->nchal, 0, 0);
+	md5((uchar*)arg, strlen(arg), digest, s);
+	snprint(response, sizeof response, "%.*H", MD5dlen, digest);
+	return dologin(response);
+}
+
+static int
+apopcmd(char *arg)
+{
+	char *resp;
+
+	resp = nextarg(arg);
+	if(setuser(arg) < 0)
+		return -1;
+	return dologin(resp);
+}
+
blob - /dev/null
blob + 0aa5e52c410b337aa69114c85bf7875b30ffe45c (mode 644)
--- /dev/null
+++ src/cmd/upas/q/mkfile
@@ -0,0 +1,22 @@
+<$PLAN9/src/mkhdr
+
+TARG = qer\
+	runq\
+
+OFILES=
+
+HFILES=../common/common.h\
+	../common/sys.h\
+
+LIB=../common/libcommon.a\
+
+BIN=$PLAN9/bin/upas
+
+UPDATE=\
+	mkfile\
+	$HFILES\
+	${OFILES:%.$O=%.c}\
+	${TARG:%=%.c}\
+
+<$PLAN9/src/mkmany
+CFLAGS=$CFLAGS -I../common
blob - /dev/null
blob + 108828960645e06a1ea11d6d259d1c6ab68c8dba (mode 644)
--- /dev/null
+++ src/cmd/upas/q/qer.c
@@ -0,0 +1,193 @@
+#include "common.h"
+
+typedef struct Qfile Qfile;
+struct Qfile
+{
+	Qfile	*next;
+	char	*name;
+	char	*tname;
+} *files;
+
+char *user;
+int isnone;
+
+int	copy(Qfile*);
+
+void
+usage(void)
+{
+	fprint(2, "usage: qer [-f file] [-q dir] q-root description reply-to arg-list\n");
+	exits("usage");
+}
+
+void
+error(char *f, char *a)
+{
+	char err[Errlen+1];
+	char buf[256];
+
+	rerrstr(err, sizeof(err));
+	snprint(buf, sizeof(buf),  f, a);
+	fprint(2, "qer: %s: %s\n", buf, err);
+	exits(buf);
+}
+
+void
+main(int argc, char**argv)
+{
+	Dir	*dir;
+	String	*f, *c;
+	int	fd;
+	char	file[1024];
+	char	buf[1024];
+	long	n;
+	char	*cp, *qdir;
+	int	i;
+	Qfile	*q, **l;
+
+	l = &files;
+	qdir = 0;
+
+	ARGBEGIN {
+	case 'f':
+		q = malloc(sizeof(Qfile));
+		q->name = ARGF();
+		q->next = *l;
+		*l = q;
+		break;
+	case 'q':
+		qdir = ARGF();
+		if(qdir == 0)
+			usage();
+		break;
+	default:
+		usage();
+	} ARGEND;
+
+	if(argc < 3)
+		usage();
+	user = getuser();
+	isnone = (qdir != 0) || (strcmp(user, "none") == 0);
+
+	if(qdir == 0) {
+		qdir = user;
+		if(qdir == 0)
+			error("unknown user", 0);
+	}
+	snprint(file, sizeof(file), "%s/%s", argv[0], qdir);
+
+	/*
+	 *  data file name
+	 */
+	f = s_copy(file);
+	s_append(f, "/D.XXXXXX");
+	mktemp(s_to_c(f));
+	cp = utfrrune(s_to_c(f), '/');
+	cp++;
+
+	/*
+	 *  create directory and data file.  once the data file
+	 *  exists, runq won't remove the directory
+	 */
+	fd = -1;
+	for(i = 0; i < 10; i++){
+		int perm;
+
+		dir = dirstat(file);
+		if(dir == nil){
+			perm = isnone?0777:0775;
+			if(sysmkdir(file, perm) < 0)
+				continue;
+		} else {
+			if((dir->qid.type&QTDIR)==0)
+				error("not a directory %s", file);
+		}
+		perm = isnone?0664:0660;
+		fd = create(s_to_c(f), OWRITE, perm);
+		if(fd >= 0)
+			break;
+		sleep(250);
+	}
+	if(fd < 0)
+		error("creating data file %s", s_to_c(f));
+
+	/*
+	 *  copy over associated files
+	 */
+	if(files){
+		*cp = 'F';
+		for(q = files; q; q = q->next){
+			q->tname = strdup(s_to_c(f));
+			if(copy(q) < 0)
+				error("copying %s to queue", q->name);
+			(*cp)++;
+		}
+	}
+
+	/*
+	 *  copy in the data file
+	 */
+	i = 0;
+	while((n = read(0, buf, sizeof(buf)-1)) > 0){
+		if(i++ == 0 && strncmp(buf, "From", 4) != 0){
+			buf[n] = 0;
+			syslog(0, "smtp", "qer usys data starts with %-40.40s\n", buf);
+		}
+		if(write(fd, buf, n) != n)
+			error("writing data file %s", s_to_c(f));
+	}
+/*	if(n < 0)
+		error("reading input"); */
+	close(fd);
+
+	/*
+	 *  create control file
+	 */
+	*cp = 'C';
+	fd = syscreatelocked(s_to_c(f), OWRITE, 0664);
+	if(fd < 0)
+		error("creating control file %s", s_to_c(f));
+	c = s_new();
+	for(i = 1; i < argc; i++){
+		s_append(c, argv[i]);
+		s_append(c, " ");
+	}
+	for(q = files; q; q = q->next){
+		s_append(c, q->tname);
+		s_append(c, " ");
+	}
+	s_append(c, "\n");
+	if(write(fd, s_to_c(c), strlen(s_to_c(c))) < 0) {
+		sysunlockfile(fd);
+		error("writing control file %s", s_to_c(f));
+	}
+	sysunlockfile(fd);
+	exits(0);
+}
+
+int
+copy(Qfile *q)
+{
+	int from, to, n;
+	char buf[4096];
+
+	from = open(q->name, OREAD);
+	if(from < 0)
+		return -1;
+	to = create(q->tname, OWRITE, 0660);
+	if(to < 0){
+		close(from);
+		return -1;
+	}
+	for(;;){
+		n = read(from, buf, sizeof(buf));
+		if(n <= 0)
+			break;
+		n = write(to, buf, n);
+		if(n < 0)
+			break;
+	}
+	close(to);
+	close(from);
+	return n;
+}
blob - /dev/null
blob + 7ab055ed84f83bb06937185fdf8419b817ffbbe1 (mode 644)
--- /dev/null
+++ src/cmd/upas/q/runq.c
@@ -0,0 +1,766 @@
+#include "common.h"
+#include <ctype.h>
+
+void	doalldirs(void);
+void	dodir(char*);
+void	dofile(Dir*);
+void	rundir(char*);
+char*	file(char*, char);
+void	warning(char*, void*);
+void	error(char*, void*);
+int	returnmail(char**, char*, char*);
+void	logit(char*, char*, char**);
+void	doload(int);
+
+#define HUNK 32
+char	*cmd;
+char	*root;
+int	debug;
+int	giveup = 2*24*60*60;
+int	load;
+int	limit;
+
+/* the current directory */
+Dir	*dirbuf;
+long	ndirbuf = 0;
+int	nfiles;
+char	*curdir;
+
+char *runqlog = "runq";
+
+int	*pidlist;
+char	**badsys;		/* array of recalcitrant systems */
+int	nbad;
+int	npid = 50;
+int	sflag;			/* single thread per directory */
+int	aflag;			/* all directories */
+int	Eflag;			/* ignore E.xxxxxx dates */
+int	Rflag;			/* no giving up, ever */
+
+void
+usage(void)
+{
+	fprint(2, "usage: runq [-adsE] [-q dir] [-l load] [-t time] [-r nfiles] [-n nprocs] q-root cmd\n");
+	exits("");
+}
+
+void
+main(int argc, char **argv)
+{
+	char *qdir, *x;
+
+	qdir = 0;
+
+	ARGBEGIN{
+	case 'l':
+		x = ARGF();
+		if(x == 0)
+			usage();
+		load = atoi(x);
+		if(load < 0)
+			load = 0;
+		break;
+	case 'E':
+		Eflag++;
+		break;
+	case 'R':	/* no giving up -- just leave stuff in the queue */
+		Rflag++;
+		break;
+	case 'a':
+		aflag++;
+		break;
+	case 'd':
+		debug++;
+		break;
+	case 'r':
+		limit = atoi(ARGF());
+		break;
+	case 's':
+		sflag++;
+		break;
+	case 't':
+		giveup = 60*60*atoi(ARGF());
+		break;
+	case 'q':
+		qdir = ARGF();
+		if(qdir == 0)
+			usage();
+		break;
+	case 'n':
+		npid = atoi(ARGF());
+		if(npid == 0)
+			usage();
+		break;
+	}ARGEND;
+
+	if(argc != 2)
+		usage();
+
+	pidlist = malloc(npid*sizeof(*pidlist));
+	if(pidlist == 0)
+		error("can't malloc", 0);
+
+	if(aflag == 0 && qdir == 0) {
+		qdir = getuser();
+		if(qdir == 0)
+			error("unknown user", 0);
+	}
+	root = argv[0];
+	cmd = argv[1];
+
+	if(chdir(root) < 0)
+		error("can't cd to %s", root);
+
+	doload(1);
+	if(aflag)
+		doalldirs();
+	else
+		dodir(qdir);
+	doload(0);
+	exits(0);
+}
+
+int
+emptydir(char *name)
+{
+	int fd;
+	long n;
+	char buf[2048];
+
+	fd = open(name, OREAD);
+	if(fd < 0)
+		return 1;
+	n = read(fd, buf, sizeof(buf));
+	close(fd);
+	if(n <= 0) {
+		if(debug)
+			fprint(2, "removing directory %s\n", name);
+		syslog(0, runqlog, "rmdir %s", name);
+		sysremove(name);
+		return 1;
+	}
+	return 0;
+}
+
+int
+forkltd(void)
+{
+	int i;
+	int pid;
+
+	for(i = 0; i < npid; i++){
+		if(pidlist[i] <= 0)
+			break;
+	}
+
+	while(i >= npid){
+		pid = waitpid();
+		if(pid < 0){
+			syslog(0, runqlog, "forkltd confused");
+			exits(0);
+		}
+
+		for(i = 0; i < npid; i++)
+			if(pidlist[i] == pid)
+				break;
+	}
+	pidlist[i] = fork();
+	return pidlist[i];
+}
+
+/*
+ *  run all user directories, must be bootes (or root on unix) to do this
+ */
+void
+doalldirs(void)
+{
+	Dir *db;
+	int fd;
+	long i, n;
+
+
+	fd = open(".", OREAD);
+	if(fd == -1){
+		warning("reading %s", root);
+		return;
+	}
+	n = sysdirreadall(fd, &db);
+	if(n > 0){
+		for(i=0; i<n; i++){
+			if(db[i].qid.type & QTDIR){
+				if(emptydir(db[i].name))
+					continue;
+				switch(forkltd()){
+				case -1:
+					syslog(0, runqlog, "out of procs");
+					doload(0);
+					exits(0);
+				case 0:
+					if(sysdetach() < 0)
+						error("%r", 0);
+					dodir(db[i].name);
+					exits(0);
+				default:
+					break;
+				}
+			}
+		}
+		free(db);
+	}
+	close(fd);
+}
+
+/*
+ *  cd to a user directory and run it
+ */
+void
+dodir(char *name)
+{
+	curdir = name;
+
+	if(chdir(name) < 0){
+		warning("cd to %s", name);
+		return;
+	}
+	if(debug)
+		fprint(2, "running %s\n", name);
+	rundir(name);
+	chdir("..");
+}
+
+/*
+ *  run the current directory
+ */
+void
+rundir(char *name)
+{
+	int fd;
+	long i;
+
+	if(aflag && sflag)
+		fd = sysopenlocked(".", OREAD);
+	else
+		fd = open(".", OREAD);
+	if(fd == -1){
+		warning("reading %s", name);
+		return;
+	}
+	nfiles = sysdirreadall(fd, &dirbuf);
+	if(nfiles > 0){
+		for(i=0; i<nfiles; i++){
+			if(dirbuf[i].name[0]!='C' || dirbuf[i].name[1]!='.')
+				continue;
+			dofile(&dirbuf[i]);
+		}
+		free(dirbuf);
+	}
+	if(aflag && sflag)
+		sysunlockfile(fd);
+	else
+		close(fd);
+}
+
+/*
+ *  free files matching name in the current directory
+ */
+void
+remmatch(char *name)
+{
+	long i;
+
+	syslog(0, runqlog, "removing %s/%s", curdir, name);
+
+	for(i=0; i<nfiles; i++){
+		if(strcmp(&dirbuf[i].name[1], &name[1]) == 0)
+			sysremove(dirbuf[i].name);
+	}
+
+	/* error file (may have) appeared after we read the directory */
+	/* stomp on data file in case of phase error */
+	sysremove(file(name, 'D'));
+	sysremove(file(name, 'E'));
+}
+
+/*
+ *  like trylock, but we've already got the lock on fd,
+ *  and don't want an L. lock file.
+ */
+static Mlock *
+keeplockalive(char *path, int fd)
+{
+	char buf[1];
+	Mlock *l;
+
+	l = malloc(sizeof(Mlock));
+	if(l == 0)
+		return 0;
+	l->fd = fd;
+	l->name = s_new();
+	s_append(l->name, path);
+
+	/* fork process to keep lock alive until sysunlock(l) */
+	switch(l->pid = rfork(RFPROC)){
+	default:
+		break;
+	case 0:
+		fd = l->fd;
+		for(;;){
+			sleep(1000*60);
+			if(pread(fd, buf, 1, 0) < 0)
+				break;
+		}
+		_exits(0);
+	}
+	return l;
+}
+
+/*
+ *  try a message
+ */
+void
+dofile(Dir *dp)
+{
+	Dir *d;
+	int dfd, ac, dtime, efd, pid, i, etime;
+	char *buf, *cp, **av;
+	Waitmsg *wm;
+	Biobuf *b;
+	Mlock *l = nil;
+
+	if(debug)
+		fprint(2, "dofile %s\n", dp->name);
+	/*
+	 *  if no data file or empty control or data file, just clean up
+	 *  the empty control file must be 15 minutes old, to minimize the
+	 *  chance of a race.
+	 */
+	d = dirstat(file(dp->name, 'D'));
+	if(d == nil){
+		syslog(0, runqlog, "no data file for %s", dp->name);
+		remmatch(dp->name);
+		return;
+	}
+	if(dp->length == 0){
+		if(time(0)-dp->mtime > 15*60){
+			syslog(0, runqlog, "empty ctl file for %s", dp->name);
+			remmatch(dp->name);
+		}
+		return;
+	}
+	dtime = d->mtime;
+	free(d);
+
+	/*
+	 *  retry times depend on the age of the errors file
+	 */
+	if(!Eflag && (d = dirstat(file(dp->name, 'E'))) != nil){
+		etime = d->mtime;
+		free(d);
+		if(etime - dtime < 60*60){
+			/* up to the first hour, try every 15 minutes */
+			if(time(0) - etime < 15*60)
+				return;
+		} else {
+			/* after the first hour, try once an hour */
+			if(time(0) - etime < 60*60)
+				return;
+		}
+
+	}
+
+	/*
+	 *  open control and data
+	 */
+	b = sysopen(file(dp->name, 'C'), "rl", 0660);
+	if(b == 0) {
+		if(debug)
+			fprint(2, "can't open %s: %r\n", file(dp->name, 'C'));
+		return;
+	}
+	dfd = open(file(dp->name, 'D'), OREAD);
+	if(dfd < 0){
+		if(debug)
+			fprint(2, "can't open %s: %r\n", file(dp->name, 'D'));
+		Bterm(b);
+		sysunlockfile(Bfildes(b));
+		return;
+	}
+
+	/*
+	 *  make arg list
+	 *	- read args into (malloc'd) buffer
+	 *	- malloc a vector and copy pointers to args into it
+	 */
+	buf = malloc(dp->length+1);
+	if(buf == 0){
+		warning("buffer allocation", 0);
+		Bterm(b);
+		sysunlockfile(Bfildes(b));
+		close(dfd);
+		return;
+	}
+	if(Bread(b, buf, dp->length) != dp->length){
+		warning("reading control file %s\n", dp->name);
+		Bterm(b);
+		sysunlockfile(Bfildes(b));
+		close(dfd);
+		free(buf);
+		return;
+	}
+	buf[dp->length] = 0;
+	av = malloc(2*sizeof(char*));
+	if(av == 0){
+		warning("argv allocation", 0);
+		close(dfd);
+		free(buf);
+		Bterm(b);
+		sysunlockfile(Bfildes(b));
+		return;
+	}
+	for(ac = 1, cp = buf; *cp; ac++){
+		while(isspace(*cp))
+			*cp++ = 0;
+		if(*cp == 0)
+			break;
+
+		av = realloc(av, (ac+2)*sizeof(char*));
+		if(av == 0){
+			warning("argv allocation", 0);
+			close(dfd);
+			free(buf);
+			Bterm(b);
+			sysunlockfile(Bfildes(b));
+			return;
+		}
+		av[ac] = cp;
+		while(*cp && !isspace(*cp)){
+			if(*cp++ == '"'){
+				while(*cp && *cp != '"')
+					cp++;
+				if(*cp)
+					cp++;
+			}
+		}
+	}
+	av[0] = cmd;
+	av[ac] = 0;
+
+	if(!Eflag &&time(0) - dtime > giveup){
+		if(returnmail(av, dp->name, "Giveup") != 0)
+			logit("returnmail failed", dp->name, av);
+		remmatch(dp->name);
+		goto done;
+	}
+
+	for(i = 0; i < nbad; i++){
+		if(strcmp(av[3], badsys[i]) == 0)
+			goto done;
+	}
+
+	/*
+	 * Ken's fs, for example, gives us 5 minutes of inactivity before
+	 * the lock goes stale, so we have to keep reading it.
+ 	 */
+	l = keeplockalive(file(dp->name, 'C'), Bfildes(b));
+
+	/*
+	 *  transfer
+	 */
+	pid = fork();
+	switch(pid){
+	case -1:
+		sysunlock(l);
+		sysunlockfile(Bfildes(b));
+		syslog(0, runqlog, "out of procs");
+		exits(0);
+	case 0:
+		if(debug) {
+			fprint(2, "Starting %s", cmd);
+			for(ac = 0; av[ac]; ac++)
+				fprint(2, " %s", av[ac]);
+			fprint(2, "\n");
+		}
+		logit("execing", dp->name, av);
+		close(0);
+		dup(dfd, 0);
+		close(dfd);
+		close(2);
+		efd = open(file(dp->name, 'E'), OWRITE);
+		if(efd < 0){
+			if(debug) syslog(0, "runq", "open %s as %s: %r", file(dp->name,'E'), getuser());
+			efd = create(file(dp->name, 'E'), OWRITE, 0666);
+			if(efd < 0){
+				if(debug) syslog(0, "runq", "create %s as %s: %r", file(dp->name, 'E'), getuser());
+				exits("could not open error file - Retry");
+			}
+		}
+		seek(efd, 0, 2);
+		exec(cmd, av);
+		error("can't exec %s", cmd);
+		break;
+	default:
+		for(;;){
+			wm = wait();
+			if(wm == nil)
+				error("wait failed: %r", "");
+			if(wm->pid == pid)
+				break;
+			free(wm);
+		}
+		if(debug)
+			fprint(2, "wm->pid %d wm->msg == %s\n", wm->pid, wm->msg);
+
+		if(wm->msg[0]){
+			if(debug)
+				fprint(2, "[%d] wm->msg == %s\n", getpid(), wm->msg);
+			if(!Rflag && strstr(wm->msg, "Retry")==0){
+				/* return the message and remove it */
+				if(returnmail(av, dp->name, wm->msg) != 0)
+					logit("returnmail failed", dp->name, av);
+				remmatch(dp->name);
+			} else {
+				/* add sys to bad list and try again later */
+				nbad++;
+				badsys = realloc(badsys, nbad*sizeof(char*));
+				badsys[nbad-1] = strdup(av[3]);
+			}
+		} else {
+			/* it worked remove the message */
+			remmatch(dp->name);
+		}
+		free(wm);
+
+	}
+done:
+	if (l)
+		sysunlock(l);
+	Bterm(b);
+	sysunlockfile(Bfildes(b));
+	free(buf);
+	free(av);
+	close(dfd);
+}
+
+
+/*
+ *  return a name starting with the given character
+ */
+char*
+file(char *name, char type)
+{
+	static char nname[Elemlen+1];
+
+	strncpy(nname, name, Elemlen);
+	nname[Elemlen] = 0;
+	nname[0] = type;
+	return nname;
+}
+
+/*
+ *  send back the mail with an error message
+ *
+ *  return 0 if successful
+ */
+int
+returnmail(char **av, char *name, char *msg)
+{
+	int pfd[2];
+	Waitmsg *wm;
+	int fd;
+	char buf[256];
+	char attachment[256];
+	int i;
+	long n;
+	String *s;
+	char *sender;
+
+	if(av[1] == 0 || av[2] == 0){
+		logit("runq - dumping bad file", name, av);
+		return 0;
+	}
+
+	s = unescapespecial(s_copy(av[2]));
+	sender = s_to_c(s);
+
+	if(!returnable(sender) || strcmp(sender, "postmaster") == 0) {
+		logit("runq - dumping p to p mail", name, av);
+		return 0;
+	}
+
+	if(pipe(pfd) < 0){
+		logit("runq - pipe failed", name, av);
+		return -1;
+	}
+
+	switch(rfork(RFFDG|RFPROC|RFENVG)){
+	case -1:
+		logit("runq - fork failed", name, av);
+		return -1;
+	case 0:
+		logit("returning", name, av);
+		close(pfd[1]);
+		close(0);
+		dup(pfd[0], 0);
+		close(pfd[0]);
+		putenv("upasname", "/dev/null");
+		snprint(buf, sizeof(buf), "%s/marshal", UPASBIN);
+		snprint(attachment, sizeof(attachment), "%s", file(name, 'D'));
+		execl(buf, "send", "-A", attachment, "-s", "permanent failure", sender, nil);
+		error("can't exec", 0);
+		break;
+	default:
+		break;
+	}
+
+	close(pfd[0]);
+	fprint(pfd[1], "\n");	/* get out of headers */
+	if(av[1]){
+		fprint(pfd[1], "Your request ``%.20s ", av[1]);
+		for(n = 3; av[n]; n++)
+			fprint(pfd[1], "%s ", av[n]);
+	}
+	fprint(pfd[1], "'' failed (code %s).\nThe symptom was:\n\n", msg);
+	fd = open(file(name, 'E'), OREAD);
+	if(fd >= 0){
+		for(;;){
+			n = read(fd, buf, sizeof(buf));
+			if(n <= 0)
+				break;
+			if(write(pfd[1], buf, n) != n){
+				close(fd);
+				goto out;
+			}
+		}
+		close(fd);
+	}
+	close(pfd[1]);
+out:
+	wm = wait();
+	if(wm == nil){
+		syslog(0, "runq", "wait: %r");
+		logit("wait failed", name, av);
+		return -1;
+	}
+	i = 0;
+	if(wm->msg[0]){
+		i = -1;
+		syslog(0, "runq", "returnmail child: %s", wm->msg);
+		logit("returnmail child failed", name, av);
+	}
+	free(wm);
+	return i;
+}
+
+/*
+ *  print a warning and continue
+ */
+void
+warning(char *f, void *a)
+{
+	char err[65];
+	char buf[256];
+
+	rerrstr(err, sizeof(err));
+	snprint(buf, sizeof(buf), f, a);
+	fprint(2, "runq: %s: %s\n", buf, err);
+}
+
+/*
+ *  print an error and die
+ */
+void
+error(char *f, void *a)
+{
+	char err[Errlen];
+	char buf[256];
+
+	rerrstr(err, sizeof(err));
+	snprint(buf, sizeof(buf), f, a);
+	fprint(2, "runq: %s: %s\n", buf, err);
+	exits(buf);
+}
+
+void
+logit(char *msg, char *file, char **av)
+{
+	int n, m;
+	char buf[256];
+
+	n = snprint(buf, sizeof(buf), "%s/%s: %s", curdir, file, msg);
+	for(; *av; av++){
+		m = strlen(*av);
+		if(n + m + 4 > sizeof(buf))
+			break;
+		sprint(buf + n, " '%s'", *av);
+		n += m + 3;
+	}
+	syslog(0, runqlog, "%s", buf);
+}
+
+char *loadfile = ".runqload";
+
+/*
+ *  load balancing
+ */
+void
+doload(int start)
+{
+	int fd;
+	char buf[32];
+	int i, n;
+	Mlock *l;
+	Dir *d;
+
+	if(load <= 0)
+		return;
+
+	if(chdir(root) < 0){
+		load = 0;
+		return;
+	}
+
+	l = syslock(loadfile);
+	fd = open(loadfile, ORDWR);
+	if(fd < 0){
+		fd = create(loadfile, 0666, ORDWR);
+		if(fd < 0){
+			load = 0;
+			sysunlock(l);
+			return;
+		}
+	}
+
+	/* get current load */
+	i = 0;
+	n = read(fd, buf, sizeof(buf)-1);
+	if(n >= 0){
+		buf[n] = 0;
+		i = atoi(buf);
+	}
+	if(i < 0)
+		i = 0;
+
+	/* ignore load if file hasn't been changed in 30 minutes */
+	d = dirfstat(fd);
+	if(d != nil){
+		if(d->mtime + 30*60 < time(0))
+			i = 0;
+		free(d);
+	}
+
+	/* if load already too high, give up */
+	if(start && i >= load){
+		sysunlock(l);
+		exits(0);
+	}
+
+	/* increment/decrement load */
+	if(start)
+		i++;
+	else
+		i--;
+	seek(fd, 0, 0);
+	fprint(fd, "%d\n", i);
+	sysunlock(l);
+	close(fd);
+}
blob - /dev/null
blob + b6ea720d1fccfce7245cf1d2560388a3c9d26d62 (mode 644)
--- /dev/null
+++ src/cmd/upas/scanmail/common.c
@@ -0,0 +1,667 @@
+#include <u.h>
+#include <libc.h>
+#include <bio.h>
+#include <regexp.h>
+#include "spam.h"
+
+enum {
+	Quanta	= 8192,
+	Minbody = 6000,
+	HdrMax	= 15,
+};
+
+typedef struct keyword Keyword;
+typedef struct word Word;
+
+struct word{
+	char	*string;
+	int	n;
+};
+
+struct	keyword{
+	char	*string;
+	int	value;
+};
+
+Word	htmlcmds[] =
+{
+	"html",		4,
+	"!doctype html", 13,
+	0,
+
+};
+
+Word	hrefs[] =
+{
+	"a href=",	7,
+	"a title=",	8,
+	"a target=",	9,
+	"base href=",	10,
+	"img src=",	8,
+	"img border=",	11,
+	"form action=", 12,
+	"!--",		3,
+	0,
+
+};
+
+/*
+ *	RFC822 header keywords to look for for fractured header.
+ *	all lengths must be less than HdrMax defined above.
+ */
+Word	hdrwords[] =
+{
+	"cc:",			3,
+	"bcc:", 		4,
+	"to:",			3,
+	0,			0,
+
+};
+
+Keyword	keywords[] =
+{
+	"header",	HoldHeader,
+	"line",		SaveLine,
+	"hold",		Hold,
+	"dump",		Dump,
+	"loff",		Lineoff,
+	0,		Nactions,
+};
+
+Patterns patterns[] = {
+[Dump]		{ "DUMP:", 0, 0 },
+[HoldHeader]	{ "HEADER:", 0, 0 },
+[Hold]		{ "HOLD:", 0, 0 },
+[SaveLine]	{ "LINE:", 0, 0 },
+[Lineoff]	{ "LINEOFF:", 0, 0 },
+[Nactions]	{ 0, 0, 0 },
+};
+
+static char*	endofhdr(char*, char*);
+static	int	escape(char**);
+static	int	extract(char*);
+static	int	findkey(char*);
+static	int	hash(int);
+static	int	isword(Word*, char*, int);
+static	void	parsealt(Biobuf*, char*, Spat**);
+
+/*
+ *	The canonicalizer: convert input to canonical representation
+ */
+char*
+readmsg(Biobuf *bp, int *hsize, int *bufsize)
+{
+	char *p, *buf;
+	int n, offset, eoh, bsize, delta;
+
+	buf = 0;
+	offset = 0;
+	if(bufsize)
+		*bufsize = 0;
+	if(hsize)
+		*hsize = 0;
+	for(;;) {
+		buf = Realloc(buf, offset+Quanta+1);
+		n = Bread(bp, buf+offset, Quanta);
+		if(n < 0){
+			free(buf);
+			return 0;
+		}
+		p = buf+offset;			/* start of this chunk */
+		offset += n;			/* end of this chunk */
+		buf[offset] = 0;
+		if(n == 0){
+			if(offset == 0)
+				return 0;
+			break;
+		}
+
+		if(hsize == 0)			/* don't process header */
+			break;
+		if(p != buf && p[-1] == '\n')	/* check for EOH across buffer split */
+			p--;
+		p = endofhdr(p, buf+offset);
+		if(p)
+			break;
+		if(offset >= Maxread)		/* gargantuan header - just punt*/
+		{
+			if(hsize)
+				*hsize = offset;
+			if(bufsize)
+				*bufsize = offset;
+			return buf;
+		}
+	}
+	eoh = p-buf;				/* End of header */
+	bsize = offset - eoh;			/* amount of body already read */
+
+		/* Read at least Minbody bytes of the body */
+	if (bsize < Minbody){
+		delta = Minbody-bsize;
+		buf = Realloc(buf, offset+delta+1);
+		n = Bread(bp, buf+offset, delta);
+		if(n > 0) {
+			offset += n;
+			buf[offset] = 0;
+		}
+	}
+	if(hsize)
+		*hsize = eoh;
+	if(bufsize)
+		*bufsize = offset;
+	return buf;
+}
+
+static	int
+isword(Word *wp, char *text, int len)
+{
+	for(;wp->string; wp++)
+		if(len >= wp->n && strncmp(text, wp->string, wp->n) == 0)
+			return 1;
+	return 0;
+}
+
+static char*
+endofhdr(char *raw, char *end)
+{
+	int i;
+	char *p, *q;
+	char buf[HdrMax];
+
+	/*
+ 	 * can't use strchr to search for newlines because
+	 * there may be embedded NULL's.
+	 */
+	for(p = raw; p < end; p++){
+		if(*p != '\n' || p[1] != '\n')
+			continue;
+		p++;
+		for(i = 0, q = p+1; i < sizeof(buf) && *q; q++){
+			buf[i++] = tolower(*q);
+			if(*q == ':' || *q == '\n')
+				break;
+		}
+		if(!isword(hdrwords, buf, i))
+			return p+1;
+	}
+	return 0;
+}
+
+static	int
+htmlmatch(Word *wp, char *text, char *end, int *n)
+{
+	char *cp;
+	int i, c, lastc;
+	char buf[MaxHtml];
+
+	/*
+	 * extract a string up to '>'
+	 */
+
+	i = lastc = 0;
+	cp = text;
+	while (cp < end && i < sizeof(buf)-1){
+		c = *cp++;
+		if(c == '=')
+			c = escape(&cp);
+		switch(c){
+		case 0:
+		case '\r':
+			continue;
+		case '>':
+			goto out;
+		case '\n':
+		case ' ':
+		case '\t':
+			if(lastc == ' ')
+				continue;
+			c = ' ';
+			break;
+		default:
+			c = tolower(c);
+			break;
+		}
+		buf[i++] = lastc = c;
+	}
+out:
+	buf[i] = 0;
+	if(n)
+		*n = cp-text;
+	return isword(wp, buf, i);
+}
+
+static int
+escape(char **msg)
+{
+	int c;
+	char *p;
+
+	p = *msg;
+	c = *p;
+	if(c == '\n'){
+		p++;
+		c = *p++;
+	} else
+	if(c == '2'){
+		c = tolower(p[1]);
+		if(c == 'e'){
+			p += 2;
+			c = '.';
+		}else
+		if(c == 'f'){
+			p += 2;
+			c = '/';
+		}else
+		if(c == '0'){
+			p += 2;
+			c = ' ';
+		}
+		else c = '=';
+	} else {
+		if(c == '3' && tolower(p[1]) == 'd')
+			p += 2;
+		c = '=';
+	}
+	*msg = p;
+	return c;
+}
+
+static int
+htmlchk(char **msg, char *end)
+{
+	int n;
+	char *p;
+
+	static int ishtml;
+
+	p = *msg;
+	if(ishtml == 0){
+		ishtml = htmlmatch(htmlcmds, p, end, &n);
+	
+		/* If not an HTML keyword, check if it's
+		 * an HTML comment (<!comment>).  if so,
+		 * skip over it; otherwise copy it in.
+		 */
+		if(ishtml == 0 && *p != '!')	/* not comment */
+			return '<';		/* copy it */
+
+	} else if(htmlmatch(hrefs, p, end, &n))	/* if special HTML string  */
+		return '<';			/* copy it */
+	
+	/*
+	 * this is an uninteresting HTML command; skip over it.
+	 */
+	p += n;
+	*msg = p+1;
+	return *p;
+}
+
+/*
+ * decode a base 64 encode body
+ */
+void
+conv64(char *msg, char *end, char *buf, int bufsize)
+{
+	int len, i;
+	char *cp;
+
+	len = end - msg;
+	i = (len*3)/4+1;	// room for max chars + null
+	cp = Malloc(i);
+	len = dec64((uchar*)cp, i, msg, len);
+	convert(cp, cp+len, buf, bufsize, 1);
+	free(cp);
+}
+
+int
+convert(char *msg, char *end, char *buf, int bufsize, int isbody)
+{
+
+	char *p;
+	int c, lastc, base64;
+
+	lastc = 0;
+	base64 = 0;
+	while(msg < end && bufsize > 0){
+		c = *msg++;
+
+		/*
+		 * In the body only, try to strip most HTML and
+		 * replace certain MIME escape sequences with the character
+		 */
+		if(isbody) {
+			do{
+				p = msg;
+				if(c == '<')
+					c = htmlchk(&msg, end);
+				if(c == '=')
+					c = escape(&msg);
+			} while(p != msg && p < end);
+		}
+		switch(c){
+		case 0:
+		case '\r':
+			continue;
+		case '\t':
+		case ' ':
+		case '\n':
+			if(lastc == ' ')
+				continue;
+			c = ' ';
+			break;
+		case 'C':	/* check for MIME base 64 encoding in header */
+		case 'c':
+			if(isbody == 0)
+			if(msg < end-32 && *msg == 'o' && msg[1] == 'n')
+			if(cistrncmp(msg+2, "tent-transfer-encoding: base64", 30) == 0)
+				base64 = 1;
+			c = 'c';
+			break;
+		default:
+			c = tolower(c);
+			break;
+		}
+		*buf++ = c;
+		lastc = c;
+		bufsize--;
+	}
+	*buf = 0;
+	return base64;
+}
+
+/*
+ *	The pattern parser: build data structures from the pattern file
+ */
+
+static int
+hash(int c)
+{
+	return c & 127;
+}
+
+static	int
+findkey(char *val)
+{
+	Keyword *kp;
+
+	for(kp = keywords; kp->string; kp++)
+		if(strcmp(val, kp->string) == 0)
+				break;
+	return kp->value;
+}
+
+#define	whitespace(c)	((c) == ' ' || (c) == '\t')
+
+void
+parsepats(Biobuf *bp)
+{
+	Pattern *p, *new;
+	char *cp, *qp;
+	int type, action, n, h;
+	Spat *spat;
+
+	for(;;){
+		cp = Brdline(bp, '\n');
+		if(cp == 0)
+			break;
+		cp[Blinelen(bp)-1] = 0;
+		while(*cp == ' ' || *cp == '\t')
+			cp++;
+		if(*cp == '#' || *cp == 0)
+			continue;
+		type = regexp;
+		if(*cp == '*'){
+			type = string;
+			cp++;
+		}
+		qp = strchr(cp, ':');
+		if(qp == 0)
+			continue;
+		*qp = 0;
+		if(debug)
+			fprint(2, "action = %s\n", cp);
+		action = findkey(cp);
+		if(action >= Nactions)
+			continue;
+		cp = qp+1;
+		n = extract(cp);
+		if(n <= 0 || *cp == 0)
+			continue;
+
+		qp = strstr(cp, "~~");
+		if(qp){
+			*qp = 0;
+			n = strlen(cp);
+		}
+		if(debug)
+			fprint(2, " Pattern: `%s'\n", cp);
+
+			/* Hook regexps into a chain */
+		if(type == regexp) {
+			new = Malloc(sizeof(Pattern));
+			new->action = action;
+			new->pat = regcomp(cp);
+			if(new->pat == 0){
+				free(new);
+				continue;
+			}
+			new->type = regexp;
+			new->alt = 0;
+			new->next = 0;
+
+			if(qp)
+				parsealt(bp, qp+2, &new->alt);
+
+			new->next = patterns[action].regexps;
+			patterns[action].regexps = new;
+			continue;
+
+		}
+			/* not a Regexp - hook strings into Pattern hash chain */
+		spat = Malloc(sizeof(*spat));
+		spat->next = 0;
+		spat->alt = 0;
+		spat->len = n;
+		spat->string = Malloc(n+1);
+		spat->c1 = cp[1];
+		strcpy(spat->string, cp);
+
+		if(qp)
+			parsealt(bp, qp+2, &spat->alt);
+
+		p = patterns[action].strings;
+		if(p == 0) {
+			p = Malloc(sizeof(Pattern));
+			memset(p, 0, sizeof(*p));
+			p->action = action;
+			p->type = string;
+			patterns[action].strings = p;
+		}
+		h = hash(*spat->string);
+		spat->next = p->spat[h];
+		p->spat[h] = spat;
+	}
+}
+
+static void
+parsealt(Biobuf *bp, char *cp, Spat** head)
+{
+	char *p;
+	Spat *alt;
+
+	while(cp){
+		if(*cp == 0){		/*escaped newline*/
+			do{
+				cp = Brdline(bp, '\n');
+				if(cp == 0)
+					return;
+				cp[Blinelen(bp)-1] = 0;
+			} while(extract(cp) <= 0 || *cp == 0);
+		}
+
+		p = cp;
+		cp = strstr(p, "~~");
+		if(cp){
+			*cp = 0;
+			cp += 2;
+		}
+		if(strlen(p)){
+			alt = Malloc(sizeof(*alt));
+			alt->string = strdup(p);
+			alt->next = *head;
+			*head = alt;
+		}
+	}
+}
+
+static int
+extract(char *cp)
+{
+	int c;
+	char *p, *q, *r;
+
+	p = q = r = cp;
+	while(whitespace(*p))
+		p++;
+	while(c = *p++){
+		if (c == '#')
+			break;
+		if(c == '"'){
+			while(*p && *p != '"'){
+				if(*p == '\\' && p[1] == '"')
+					p++;
+				if('A' <= *p && *p <= 'Z')
+					*q++ = *p++ + ('a'-'A');
+				else
+					*q++ = *p++;
+			}
+			if(*p)
+				p++;
+			r = q;		/* never back up over a quoted string */
+		} else {
+			if('A' <= c && c <= 'Z')
+				c += ('a'-'A');
+			*q++ = c;
+		}
+	}
+	while(q > r && whitespace(q[-1]))
+		q--;
+	*q = 0;
+	return q-cp;
+}
+
+/*
+ *	The matching engine: compare canonical input to pattern structures
+ */
+
+static Spat*
+isalt(char *message, Spat *alt)
+{
+	while(alt) {
+		if(*cmd)
+		if(message != cmd && strstr(cmd, alt->string))
+			break;
+		if(message != header+1 && strstr(header+1, alt->string))
+			break;
+		if(strstr(message, alt->string))
+			break;
+		alt = alt->next;
+	}
+	return alt;
+}
+
+int
+matchpat(Pattern *p, char *message, Resub *m)
+{
+	Spat *spat;
+	char *s;
+	int c, c1;
+
+	if(p->type == string){
+		c1 = *message;
+		for(s=message; c=c1; s++){
+			c1 = s[1];
+			for(spat=p->spat[hash(c)]; spat; spat=spat->next){
+				if(c1 == spat->c1)
+				if(memcmp(s, spat->string, spat->len) == 0)
+				if(!isalt(message, spat->alt)){
+					m->sp = s;
+					m->ep = s + spat->len;
+					return 1;
+				}
+			}
+		}
+		return 0;
+	}
+	m->sp = m->ep = 0;
+	if(regexec(p->pat, message, m, 1) == 0)
+		return 0;
+	if(isalt(message, p->alt))
+		return 0;
+	return 1;
+}
+
+
+void
+xprint(int fd, char *type, Resub *m)
+{
+	char *p, *q;
+	int i;
+
+	if(m->sp == 0 || m->ep == 0)
+		return;
+
+		/* back up approx 30 characters to whitespace */
+	for(p = m->sp, i = 0; *p && i < 30; i++, p--)
+			;
+	while(*p && *p != ' ')
+		p--;
+	p++;
+
+		/* grab about 30 more chars beyond the end of the match */
+	for(q = m->ep, i = 0; *q && i < 30; i++, q++)
+			;
+	while(*q && *q != ' ')
+		q++;
+
+	fprint(fd, "%s %.*s~%.*s~%.*s\n", type, (int)(m->sp-p), p, (int)(m->ep-m->sp), m->sp, (int)(q-m->ep), m->ep);
+}
+
+enum {
+	INVAL=	255
+};
+
+static uchar t64d[256] = {
+/*00 */	INVAL, INVAL, INVAL, INVAL, INVAL, INVAL, INVAL, INVAL,
+	INVAL, INVAL, INVAL, INVAL, INVAL, INVAL, INVAL, INVAL,
+/*10*/	INVAL, INVAL, INVAL, INVAL, INVAL, INVAL, INVAL, INVAL,
+	INVAL, INVAL, INVAL, INVAL, INVAL, INVAL, INVAL, INVAL,
+/*20*/	INVAL, INVAL, INVAL, INVAL, INVAL, INVAL, INVAL, INVAL,
+	INVAL, INVAL, INVAL,    62, INVAL, INVAL, INVAL,    63,
+/*30*/	   52,	  53,	 54,	55,    56,    57,    58,    59,
+	   60,	  61, INVAL, INVAL, INVAL, INVAL, INVAL, INVAL,
+/*40*/	INVAL,    0,      1,     2,     3,     4,     5,     6,
+	    7,    8,      9,    10,    11,    12,    13,    14,
+/*50*/	   15,   16,     17,    18,    19,    20,    21,    22,
+	   23,   24,     25, INVAL, INVAL, INVAL, INVAL, INVAL,
+/*60*/	INVAL,   26,     27,    28,    29,    30,    31,    32,
+	   33,   34,     35,    36,    37,    38,    39,    40,
+/*70*/	   41,   42,     43,    44,    45,    46,    47,    48,
+	   49,   50,     51, INVAL, INVAL, INVAL, INVAL, INVAL,
+/*80*/	INVAL, INVAL, INVAL, INVAL, INVAL, INVAL, INVAL, INVAL,
+	INVAL, INVAL, INVAL, INVAL, INVAL, INVAL, INVAL, INVAL,
+/*90*/	INVAL, INVAL, INVAL, INVAL, INVAL, INVAL, INVAL, INVAL,
+	INVAL, INVAL, INVAL, INVAL, INVAL, INVAL, INVAL, INVAL,
+/*A0*/	INVAL, INVAL, INVAL, INVAL, INVAL, INVAL, INVAL, INVAL,
+	INVAL, INVAL, INVAL, INVAL, INVAL, INVAL, INVAL, INVAL,
+/*B0*/	INVAL, INVAL, INVAL, INVAL, INVAL, INVAL, INVAL, INVAL,
+	INVAL, INVAL, INVAL, INVAL, INVAL, INVAL, INVAL, INVAL,
+/*C0*/	INVAL, INVAL, INVAL, INVAL, INVAL, INVAL, INVAL, INVAL,
+	INVAL, INVAL, INVAL, INVAL, INVAL, INVAL, INVAL, INVAL,
+/*D0*/	INVAL, INVAL, INVAL, INVAL, INVAL, INVAL, INVAL, INVAL,
+	INVAL, INVAL, INVAL, INVAL, INVAL, INVAL, INVAL, INVAL,
+/*E0*/	INVAL, INVAL, INVAL, INVAL, INVAL, INVAL, INVAL, INVAL,
+	INVAL, INVAL, INVAL, INVAL, INVAL, INVAL, INVAL, INVAL,
+/*F0*/	INVAL, INVAL, INVAL, INVAL, INVAL, INVAL, INVAL, INVAL,
+	INVAL, INVAL, INVAL, INVAL, INVAL, INVAL, INVAL, INVAL,
+};
blob - /dev/null
blob + 5f0db8556d50d27e5ac57aebab2a7dae82927b3a (mode 644)
--- /dev/null
+++ src/cmd/upas/scanmail/mkfile
@@ -0,0 +1,24 @@
+</$objtype/mkfile
+
+TARG=scanmail\
+	testscan
+
+OFILES=	common.$O
+
+HFILES=	spam.h\
+	../common/sys.h\
+
+LIB=	../common/libcommon.a$O\
+
+BIN=/$objtype/bin/upas
+UPDATE=\
+	mkfile\
+	$HFILES\
+	${OFILES:%.$O=%.c}\
+	${TARG:%=%.c}\
+
+</sys/src/cmd/mkmany
+CFLAGS=$CFLAGS -I../common
+
+scanmail.$O:	scanmail.c
+	$CC $CFLAGS -D'SPOOL="/mail"' scanmail.c
blob - /dev/null
blob + 444bbcdd20065d1b2eb19bd2b114b345b34f05a8 (mode 644)
--- /dev/null
+++ src/cmd/upas/scanmail/scanmail.c
@@ -0,0 +1,476 @@
+#include "common.h"
+#include "spam.h"
+
+int	cflag;
+int	debug;
+int	hflag;
+int	nflag;
+int	sflag;
+int	tflag;
+int	vflag;
+Biobuf	bin, bout, *cout;
+
+	/* file names */
+char	patfile[128];
+char	linefile[128];
+char	holdqueue[128];
+char	copydir[128];
+
+char	header[Hdrsize+2];
+char	cmd[1024];
+char	**qname;
+char	**qdir;
+char	*sender;
+String	*recips;
+
+char*	canon(Biobuf*, char*, char*, int*);
+int	matcher(char*, Pattern*, char*, Resub*);
+int	matchaction(int, char*, Resub*);
+Biobuf	*opencopy(char*);
+Biobuf	*opendump(char*);
+char	*qmail(char**, char*, int, Biobuf*);
+void	saveline(char*, char*, Resub*);
+int	optoutofspamfilter(char*);
+
+void
+usage(void)
+{
+	fprint(2, "missing or bad arguments to qer\n");
+	exits("usage");
+}
+
+void
+regerror(char *s)
+{
+	fprint(2, "scanmail: %s\n", s);
+}
+
+void *
+Malloc(long n)
+{
+	void *p;
+
+	p = malloc(n);
+	if(p == 0)
+		exits("malloc");
+	return p;
+}
+
+void*
+Realloc(void *p, ulong n)
+{
+	p = realloc(p, n);
+	if(p == 0)
+		exits("realloc");
+	return p;
+}
+
+void
+main(int argc, char *argv[])
+{
+	int i, n, nolines, optout;
+	char **args, **a, *cp, *buf;
+	char body[Bodysize+2];
+	Resub match[1];
+	Biobuf *bp;
+
+	optout = 1;
+	a = args = Malloc((argc+1)*sizeof(char*));
+	sprint(patfile, "%s/patterns", UPASLIB);
+	sprint(linefile, "%s/lines", UPASLOG);
+	sprint(holdqueue, "%s/queue.hold", SPOOL);
+	sprint(copydir, "%s/copy", SPOOL);
+
+	*a++ = argv[0];
+	for(argc--, argv++; argv[0] && argv[0][0] == '-'; argc--, argv++){
+		switch(argv[0][1]){
+		case 'c':			/* save copy of message */
+			cflag = 1;
+			break;
+		case 'd':			/* debug */
+			debug++;
+			*a++ = argv[0];
+			break;
+		case 'h':			/* queue held messages by sender domain */
+			hflag = 1;		/* -q flag must be set also */
+			break;
+		case 'n':			/* NOHOLD mode */
+			nflag = 1;
+			break;
+		case 'p':			/* pattern file */
+			if(argv[0][2] || argv[1] == 0)
+				usage();
+			argc--;
+			argv++;
+			strecpy(patfile, patfile+sizeof patfile, *argv);
+			break;
+		case 'q':			/* queue name */
+			if(argv[0][2] ||  argv[1] == 0)
+				usage();
+			*a++ = argv[0];
+			argc--;
+			argv++;
+			qname = a;
+			*a++ = argv[0];
+			break;
+		case 's':			/* save copy of dumped message */
+			sflag = 1;
+			break;
+		case 't':			/* test mode - don't log match
+						 * and write message to /dev/null
+						 */
+			tflag = 1;
+			break;
+		case 'v':			/* vebose - print matches */
+			vflag = 1;
+			break;
+		default:
+			*a++ = argv[0];
+			break;
+		}
+	}
+
+	if(argc < 3)
+		usage();
+
+	Binit(&bin, 0, OREAD);
+	bp = Bopen(patfile, OREAD);
+	if(bp){
+		parsepats(bp);
+		Bterm(bp);
+	}
+	qdir = a;
+	sender = argv[2];
+
+		/* copy the rest of argv, acummulating the recipients as we go */
+	for(i = 0; argv[i]; i++){
+		*a++ = argv[i];
+		if(i < 4)	/* skip queue, 'mail', sender, dest sys */
+			continue;
+			/* recipients and smtp flags - skip the latter*/
+		if(strcmp(argv[i], "-g") == 0){
+			*a++ = argv[++i];
+			continue;
+		}
+		if(recips)
+			s_append(recips, ", ");
+		else
+			recips = s_new();
+		s_append(recips, argv[i]);
+		if(optout && !optoutofspamfilter(argv[i]))
+			optout = 0;
+	}
+	*a = 0;
+		/* construct a command string for matching */
+	snprint(cmd, sizeof(cmd)-1, "%s %s", sender, s_to_c(recips));
+	cmd[sizeof(cmd)-1] = 0;
+	for(cp = cmd; *cp; cp++)
+		*cp = tolower(*cp);
+
+		/* canonicalize a copy of the header and body.
+		 * buf points to orginal message and n contains
+		 * number of bytes of original message read during
+		 * canonicalization.
+		 */
+	*body = 0;
+	*header = 0;
+	buf = canon(&bin, header+1, body+1, &n);
+	if (buf == 0)
+		exits("read");
+
+		/* if all users opt out, don't try matches */
+	if(optout){
+		if(cflag)
+			cout = opencopy(sender);
+		exits(qmail(args, buf, n, cout));
+	}
+
+		/* Turn off line logging, if command line matches */
+	nolines = matchaction(Lineoff, cmd, match);
+
+	for(i = 0; patterns[i].action; i++){
+			/* Lineoff patterns were already done above */
+		if(i == Lineoff)
+			continue;
+			/* don't apply "Line" patterns if excluded above */
+		if(nolines && i == SaveLine)
+			continue;
+			/* apply patterns to the sender/recips, header and body */
+		if(matchaction(i, cmd, match))
+			break;
+		if(matchaction(i, header+1, match))
+			break;
+		if(i == HoldHeader)
+			continue;
+		if(matchaction(i, body+1, match))
+			break;
+	}
+	if(cflag && patterns[i].action == 0)	/* no match found - save msg */
+		cout = opencopy(sender);
+
+	exits(qmail(args, buf, n, cout));
+}
+
+char*
+qmail(char **argv, char *buf, int n, Biobuf *cout)
+{
+	Waitmsg *status;
+	int i, pid, pipefd[2];
+	char path[512];
+	Biobuf *bp;
+
+	pid = 0;
+	if(tflag == 0){
+		if(pipe(pipefd) < 0)
+			exits("pipe");
+		pid = fork();
+		if(pid == 0){
+			dup(pipefd[0], 0);
+			for(i = sysfiles(); i >= 3; i--)
+				close(i);
+			snprint(path, sizeof(path), "%s/qer", UPASBIN);
+			*argv=path;
+			exec(path, argv);
+			exits("exec");
+		}
+		Binit(&bout, pipefd[1], OWRITE);
+		bp = &bout;
+	} else
+		bp = Bopen("/dev/null", OWRITE);
+
+	while(n > 0){
+		Bwrite(bp, buf, n);
+		if(cout)
+			Bwrite(cout, buf, n);
+		n = Bread(&bin, buf, sizeof(buf)-1);
+	}
+	Bterm(bp);
+	if(cout)
+		Bterm(cout);
+	if(tflag)
+		return 0;
+
+	close(pipefd[1]);
+	close(pipefd[0]);
+	for(;;){
+		status = wait();
+		if(status == nil || status->pid == pid)
+			break;
+		free(status);
+	}
+	if(status == nil)
+		strcpy(buf, "wait failed");
+	else{
+		strcpy(buf, status->msg);
+		free(status);
+	}
+	return buf;
+}
+
+char*
+canon(Biobuf *bp, char *header, char *body, int *n)
+{
+	int hsize;
+	char *raw;
+
+	hsize = 0;
+	*header = 0;
+	*body = 0;
+	raw = readmsg(bp, &hsize, n);
+	if(raw){
+		if(convert(raw, raw+hsize, header, Hdrsize, 0))
+			conv64(raw+hsize, raw+*n, body, Bodysize);	/* base64 */
+		else
+			convert(raw+hsize, raw+*n, body, Bodysize, 1);	/* text */
+	}
+	return raw;
+}
+
+int
+matchaction(int action, char *message, Resub *m)
+{
+	char *name;
+	Pattern *p;
+
+	if(message == 0 || *message == 0)
+		return 0;
+
+	name = patterns[action].action;
+	p = patterns[action].strings;
+	if(p)
+		if(matcher(name, p, message, m))
+			return 1;
+
+	for(p = patterns[action].regexps; p; p = p->next)
+		if(matcher(name, p, message, m))
+			return 1;
+	return 0;
+}
+
+int
+matcher(char *action, Pattern *p, char *message, Resub *m)
+{
+	char *cp;
+	String *s;
+
+	for(cp = message; matchpat(p, cp, m); cp = m->ep){
+		switch(p->action){
+		case SaveLine:
+			if(vflag)
+				xprint(2, action, m);
+			saveline(linefile, sender, m);
+			break;
+		case HoldHeader:
+		case Hold:
+			if(nflag)
+				continue;
+			if(vflag)
+				xprint(2, action, m);
+			*qdir = holdqueue;
+			if(hflag && qname){
+				cp = strchr(sender, '!');
+				if(cp){
+					*cp = 0;
+					*qname = strdup(sender);
+					*cp = '!';
+				} else
+					*qname = strdup(sender);
+			}
+			return 1;
+		case Dump:
+			if(vflag)
+				xprint(2, action, m);
+			*(m->ep) = 0;
+			if(!tflag){
+				s = s_new();
+				s_append(s, sender);
+				s = unescapespecial(s);
+				syslog(0, "smtpd", "Dumped %s [%s] to %s", s_to_c(s), m->sp,
+					s_to_c(s_restart(recips)));
+				s_free(s);
+			}
+			tflag = 1;
+			if(sflag)
+				cout = opendump(sender);
+			return 1;
+		default:
+			break;
+		}
+	}
+	return 0;
+}
+
+void
+saveline(char *file, char *sender, Resub *rp)
+{
+	char *p, *q;
+	int i, c;
+	Biobuf *bp;
+
+	if(rp->sp == 0 || rp->ep == 0)
+		return;
+		/* back up approx 20 characters to whitespace */
+	for(p = rp->sp, i = 0; *p && i < 20; i++, p--)
+			;
+	while(*p && *p != ' ')
+		p--;
+	p++;
+
+		/* grab about 20 more chars beyond the end of the match */
+	for(q = rp->ep, i = 0; *q && i < 20; i++, q++)
+			;
+	while(*q && *q != ' ')
+		q++;
+
+	c = *q;
+	*q = 0;
+	bp = sysopen(file, "al", 0644);
+	if(bp){
+		Bprint(bp, "%s-> %s\n", sender, p);
+		Bterm(bp);
+	}
+	else if(debug)
+		fprint(2, "can't save line: (%s) %s\n", sender, p);
+	*q = c;
+}
+
+Biobuf*
+opendump(char *sender)
+{
+	int i;
+	ulong h;
+	char buf[512];
+	Biobuf *b;
+	char *cp;
+
+	cp = ctime(time(0));
+	cp[7] = 0;
+	cp[10] = 0;
+	if(cp[8] == ' ')
+		sprint(buf, "%s/queue.dump/%s%c", SPOOL, cp+4, cp[9]);
+	else
+		sprint(buf, "%s/queue.dump/%s%c%c", SPOOL, cp+4, cp[8], cp[9]);
+	cp = buf+strlen(buf);
+	if(access(buf, 0) < 0 && sysmkdir(buf, 0777) < 0){
+		syslog(0, "smtpd", "couldn't dump mail from %s: %r", sender);
+		return 0;
+	}
+
+	h = 0;
+	while(*sender)
+		h = h*257 + *sender++;
+	for(i = 0; i < 50; i++){
+		h += lrand();
+		sprint(cp, "/%lud", h);
+		b = sysopen(buf, "wlc", 0644);
+		if(b){
+			if(vflag)
+				fprint(2, "saving in %s\n", buf);
+			return b;
+		}
+	}
+	return 0;
+}
+
+Biobuf*
+opencopy(char *sender)
+{
+	int i;
+	ulong h;
+	char buf[512];
+	Biobuf *b;
+
+	h = 0;
+	while(*sender)
+		h = h*257 + *sender++;
+	for(i = 0; i < 50; i++){
+		h += lrand();
+		sprint(buf, "%s/%lud", copydir, h);
+		b = sysopen(buf, "wlc", 0600);
+		if(b)
+			return b;
+	}
+	return 0;
+}
+
+int
+optoutofspamfilter(char *addr)
+{
+	char *p, *f;
+	int rv;
+
+	p = strchr(addr, '!');
+	if(p)
+		p++;
+	else
+		p = addr;
+
+	rv = 0;
+	f = smprint("/mail/box/%s/nospamfiltering", p);
+	if(f != nil){
+		rv = access(f, 0)==0;
+		free(f);
+	}
+
+	return rv;
+}
blob - /dev/null
blob + f1d24b2e0a6b13abe9565a47d94d30c712d76235 (mode 644)
--- /dev/null
+++ src/cmd/upas/scanmail/spam.h
@@ -0,0 +1,62 @@
+
+enum{
+	Dump		= 0,		/* Actions must be in order of descending importance */
+	HoldHeader,
+	Hold,
+	SaveLine,
+	Lineoff,			/* Lineoff must be the last action code */
+	Nactions,
+
+	Nhash		= 128,
+
+	regexp		= 1,		/* types: literal string or regular expression */
+	string		= 2,
+
+	MaxHtml		= 256,
+	Hdrsize		= 4096,
+	Bodysize	= 8192,
+	Maxread		= 64*1024,
+};
+
+typedef struct spat 	Spat;
+typedef struct pattern	Pattern;
+typedef	struct patterns	Patterns;
+struct	spat
+{
+	char*	string;
+	int	len;
+	int	c1;
+	Spat*	next;
+	Spat*	alt;
+};
+
+struct	pattern{
+	struct	pattern *next;
+	int	action;
+	int	type;
+	Spat*	alt;
+	union{
+		Reprog*	pat;
+		Spat*	spat[Nhash];
+	};
+};
+
+struct	patterns {
+	char	*action;
+	Pattern	*strings;
+	Pattern	*regexps;
+};
+
+extern	int	debug;
+extern	Patterns patterns[];
+extern	char	header[];
+extern	char	cmd[];
+
+extern	void	conv64(char*, char*, char*, int);
+extern	int	convert(char*, char*, char*, int, int);
+extern	void*	Malloc(long n);
+extern	int	matchpat(Pattern*, char*, Resub*);
+extern	char*	readmsg(Biobuf*, int*, int*);
+extern	void	parsepats(Biobuf*);
+extern	void*	Realloc(void*, ulong);
+extern	void	xprint(int, char*, Resub*);
blob - /dev/null
blob + e5ea59ad5788f5bdcf96c0d9d316f5b1d14abe38 (mode 644)
--- /dev/null
+++ src/cmd/upas/scanmail/testscan.c
@@ -0,0 +1,212 @@
+#include "sys.h"
+#include "spam.h"
+
+int 	debug;
+Biobuf	bin;
+char	patfile[128], header[Hdrsize+2];
+char	cmd[1024];
+
+char*	canon(Biobuf*, char*, char*, int*);
+int	matcher(char *, Pattern*, char*, Resub*);
+int	matchaction(Patterns*, char*);
+
+void
+usage(void)
+{
+	fprint(2, "missing or bad arguments to qer\n");
+	exits("usage");
+}
+
+void *
+Malloc(long n)
+{
+	void *p;
+
+	p = malloc(n);
+	if(p == 0){
+		fprint(2, "malloc error");
+		exits("malloc");
+	}
+	return p;
+}
+
+void*
+Realloc(void *p, ulong n)
+{
+	p = realloc(p, n);
+	if(p == 0){
+		fprint(2, "realloc error");
+		exits("realloc");
+	}
+	return p;
+}
+
+void
+dumppats(void)
+{
+	int i, j;
+	Pattern *p;
+	Spat *s, *q;
+
+	for(i = 0; patterns[i].action; i++){
+		for(p = patterns[i].regexps; p; p = p->next){
+			print("%s <REGEXP>\n", patterns[i].action);
+			if(p->alt)
+				print("Alt:");
+			for(s = p->alt; s; s = s->next)
+				print("\t%s\n", s->string);
+		}
+		p = patterns[i].strings;
+		if(p == 0)
+			continue;
+
+		for(j = 0; j < Nhash; j++){
+			for(s = p->spat[j]; s; s = s->next){
+				print("%s %s\n", patterns[i].action, s->string);
+				if(s->alt)
+					print("Alt:");
+				for(q = s->alt; q; q = q->next)
+					print("\t%s\n", q->string);
+			}
+		}
+	}
+}
+
+void
+main(int argc, char *argv[])
+{
+	int i, fd, n, aflag, vflag;
+	char body[Bodysize+2], *raw, *ret;
+	Biobuf *bp;
+
+	sprint(patfile, "%s/patterns", UPASLIB);
+	aflag = -1;
+	vflag = 0;
+	ARGBEGIN {
+	case 'a':
+		aflag = 1;
+		break;
+	case 'v':
+		vflag = 1;
+		break;
+	case 'd':
+		debug++;
+		break;
+	case 'p':
+		strcpy(patfile,ARGF());
+		break;
+	} ARGEND
+
+	bp = Bopen(patfile, OREAD);
+	if(bp){
+		parsepats(bp);
+		Bterm(bp);
+	}
+
+	if(argc >= 1){
+		fd = open(*argv, OREAD);
+		if(fd < 0){
+			fprint(2, "can't open %s\n", *argv);
+			exits("open");
+		}
+		Binit(&bin, fd, OREAD);
+	} else 
+		Binit(&bin, 0, OREAD);
+
+	*body = 0;
+	*header = 0;
+	ret = 0;
+	for(;;){
+		raw = canon(&bin, header+1, body+1, &n);
+		if(raw == 0)
+			break;
+		if(aflag == 0)
+			continue;
+		if(aflag < 0)
+			aflag = 0;
+		if(vflag){
+			if(header[1]) {
+				fprint(2, "\t**** Header ****\n\n");
+				write(2, header+1, strlen(header+1));
+				fprint(2, "\n");
+			}
+			fprint(2, "\t**** Body ****\n\n");
+			if(body[1])
+				write(2, body+1, strlen(body+1));
+			fprint(2, "\n");
+		}
+
+		for(i = 0; patterns[i].action; i++){
+			if(matchaction(&patterns[i], header+1))
+				ret = patterns[i].action;
+			if(i == HoldHeader)
+				continue;
+			if(matchaction(&patterns[i], body+1))
+				ret = patterns[i].action;
+		}
+	}
+	exits(ret);
+}
+
+char*
+canon(Biobuf *bp, char *header, char *body, int *n)
+{
+	int hsize, base64;
+
+	static char *raw;
+
+	hsize = 0;
+	base64 = 0;
+	*header = 0;
+	*body = 0;
+	if(raw == 0){
+		raw = readmsg(bp, &hsize, n);
+		if(raw)
+			base64 = convert(raw, raw+hsize, header, Hdrsize, 0);
+	} else {
+		free(raw);
+		raw = readmsg(bp, 0, n);
+	}
+	if(raw){
+		if(base64)
+			conv64(raw+hsize, raw+*n, body, Bodysize);
+		else
+			convert(raw+hsize, raw+*n, body, Bodysize, 1);
+	}
+	return raw;
+}
+
+int
+matchaction(Patterns *pp, char *message)
+{
+	char *name, *cp;
+	int ret;
+	Pattern *p;
+	Resub m[1];
+
+	if(message == 0 || *message == 0)
+		return 0;
+
+	name = pp->action;
+	p = pp->strings;
+	ret = 0;
+	if(p)
+		for(cp = message; matcher(name, p, cp, m); cp = m[0].ep)
+				ret++;
+
+	for(p = pp->regexps; p; p = p->next)
+		for(cp = message; matcher(name, p, cp, m); cp = m[0].ep)
+				ret++;
+	return ret;
+}
+
+int
+matcher(char *action, Pattern *p, char *message, Resub *m)
+{
+	if(matchpat(p, message, m)){
+		if(p->action != Lineoff)
+			xprint(1, action, m);
+		return 1;
+	}
+	return 0;
+}
blob - /dev/null
blob + 6fa12c57cc5aab62cb0deda75b0c51da495e3284 (mode 644)
--- /dev/null
+++ src/cmd/upas/send/authorize.c
@@ -0,0 +1,29 @@
+#include "common.h"
+#include "send.h"
+
+/*
+ *  Run a command to authorize or refuse entry.  Return status 0 means
+ *  authorize, -1 means refuse.
+ */
+void
+authorize(dest *dp)
+{
+	process *pp;
+	String *errstr;
+
+	dp->authorized = 1;
+	pp = proc_start(s_to_c(dp->repl1), (stream *)0, (stream *)0, outstream(), 1, 0);
+	if (pp == 0){
+		dp->status = d_noforward;
+		return;
+	}
+	errstr = s_new();
+	while(s_read_line(pp->std[2]->fp, errstr))
+		;
+	if ((dp->pstat = proc_wait(pp)) != 0) {
+		dp->repl2 = errstr;
+		dp->status = d_noforward;
+	} else
+		s_free(errstr);
+	proc_free(pp);
+}
blob - /dev/null
blob + 8a8fc8eacf1d390698cf7666c354847aec0ac7cd (mode 644)
--- /dev/null
+++ src/cmd/upas/send/bind.c
@@ -0,0 +1,133 @@
+#include "common.h"
+#include "send.h"
+
+static int forward_loop(char *, char *);
+
+/* bind the destinations to the commands to be executed */
+extern dest *
+up_bind(dest *destp, message *mp, int checkforward)
+{
+	dest *list[2];		/* lists of unbound destinations */
+	int li;			/* index into list[2] */
+	dest *bound=0;	/* bound destinations */
+	dest *dp;
+	int i;
+
+	list[0] = destp;
+	list[1] = 0;
+
+	/*
+	 *  loop once to check for:
+	 *	- forwarding rights
+	 *	- addressing loops
+	 *	- illegal characters
+	 *	- characters that need escaping
+	 */
+	for (dp = d_rm(&list[0]); dp != 0; dp = d_rm(&list[0])) {
+		if (!checkforward)
+			dp->authorized = 1;
+		dp->addr = escapespecial(dp->addr);
+		if (forward_loop(s_to_c(dp->addr), thissys)) {
+			dp->status = d_eloop;
+			d_same_insert(&bound, dp);
+		} else if(forward_loop(s_to_c(mp->sender), thissys)) {
+			dp->status = d_eloop;
+			d_same_insert(&bound, dp);
+		} else if(shellchars(s_to_c(dp->addr))) {
+			dp->status = d_syntax;
+			d_same_insert(&bound, dp);
+		} else
+			d_insert(&list[1], dp);
+	}
+	li = 1;
+
+	/* Loop until all addresses are bound or address loop detected */
+	for (i=0; list[li]!=0 && i<32; ++i, li ^= 1) {
+		/* Traverse the current list.  Bound items are put on the
+		 * `bound' list.  Unbound items are put on the next list to
+		 * traverse, `list[li^1]'.
+		 */
+		for (dp = d_rm(&list[li]); dp != 0; dp = d_rm(&list[li])){
+			dest *newlist;
+
+			rewrite(dp, mp);
+			if(debug)
+				fprint(2, "%s -> %s\n", s_to_c(dp->addr),
+					dp->repl1 ? s_to_c(dp->repl1):"");
+			switch (dp->status) {
+			case d_auth:
+				/* authorize address if not already authorized */
+				if(!dp->authorized){
+					authorize(dp);
+					if(dp->status==d_auth)
+						d_insert(&list[li^1], dp);
+					else
+						d_insert(&bound, dp);
+				}
+				break;
+			case d_cat:
+				/* address -> local */
+				newlist = expand_local(dp);
+				if (newlist == 0) {
+					/* append to mailbox (or error) */
+					d_same_insert(&bound, dp);
+				} else if (newlist->status == d_undefined) {
+					/* Forward to ... */
+					d_insert(&list[li^1], newlist);
+				} else {
+					/* Pipe to ... */
+					d_same_insert(&bound, newlist);
+				}
+				break;
+			case d_pipe:
+				/* address -> command */
+				d_same_insert(&bound, dp);
+				break;
+			case d_alias:
+				/* address -> rewritten address */
+				newlist = s_to_dest(dp->repl1, dp);
+				if(newlist != 0)
+					d_insert(&list[li^1], newlist);
+				else
+					d_same_insert(&bound, dp);
+				break;
+			case d_translate:
+				/* pipe to a translator */
+				newlist = translate(dp);
+				if (newlist != 0)
+					d_insert(&list[li^1], newlist);
+				else
+					d_same_insert(&bound, dp);
+				break;
+			default:
+				/* error */
+				d_same_insert(&bound, dp);
+				break;
+			}
+		}
+	}
+
+	/* mark remaining comands as "forwarding loops" */
+	for (dp = d_rm(&list[li]); dp != 0; dp = d_rm(&list[li])) {
+		dp->status = d_loop;
+		d_same_insert(&bound, dp);
+	}
+
+	return bound;
+}
+
+/* Return TRUE if a forwarding loop exists, i.e., the String `system'
+ * is found more than 4 times in the return address.
+ */
+static int
+forward_loop(char *addr, char *system)
+{
+	int len = strlen(system), found = 0;
+
+	while (addr = strchr(addr, '!'))
+		if (!strncmp(++addr, system, len)
+		 && addr[len] == '!' && ++found == 4)
+			return 1;
+	return 0;
+}
+
blob - /dev/null
blob + cdd16ecedfebfda86c0769b1614f2c3d1ef43389 (mode 644)
--- /dev/null
+++ src/cmd/upas/send/cat_mail.c
@@ -0,0 +1,60 @@
+#include "common.h"
+#include "send.h"
+
+
+/* dispose of local addresses */
+int
+cat_mail(dest *dp, message *mp)
+{
+	Biobuf *fp;
+	char *rcvr, *cp;
+	Mlock *l;
+	String *tmp, *s;
+	int i, n;
+
+	s = unescapespecial(s_clone(dp->repl1));
+	if (nflg) {
+		if(!xflg)
+			print("cat >> %s\n", s_to_c(s));
+		else
+			print("%s\n", s_to_c(dp->addr));
+		s_free(s);
+		return 0;
+	}
+	for(i = 0;; i++){
+		l = syslock(s_to_c(s));
+		if(l == 0)
+			return refuse(dp, mp, "can't lock mail file", 0, 0);
+
+		fp = sysopen(s_to_c(s), "al", MBOXMODE);
+		if(fp)
+			break;
+		tmp = s_append(0, s_to_c(s));
+		s_append(tmp, ".tmp");
+		fp = sysopen(s_to_c(tmp), "al", MBOXMODE);
+		if(fp){
+			syslog(0, "mail", "error: used %s", s_to_c(tmp));
+			s_free(tmp);
+			break;
+		}
+		s_free(tmp);
+		sysunlock(l);
+		if(i >= 5)
+			return refuse(dp, mp, "mail file cannot be opened", 0, 0);
+		sleep(1000);
+	}
+	s_free(s);
+	n = m_print(mp, fp, (char *)0, 1);
+	if (Bprint(fp, "\n") < 0 || Bflush(fp) < 0 || n < 0){
+		sysclose(fp);
+		sysunlock(l);
+		return refuse(dp, mp, "error writing mail file", 0, 0);
+	}
+	sysclose(fp);
+	sysunlock(l);
+	rcvr = s_to_c(dp->addr);
+	if(cp = strrchr(rcvr, '!'))
+		rcvr = cp+1;
+	logdelivery(dp, rcvr, mp);
+	return 0;
+}
blob - /dev/null
blob + fa538547a746ab8916bd903855cc53f67102ae39 (mode 644)
--- /dev/null
+++ src/cmd/upas/send/dest.c
@@ -0,0 +1,260 @@
+#include "common.h"
+#include "send.h"
+
+static String* s_parseq(String*, String*);
+
+/* exports */
+dest *dlist;
+
+extern dest*
+d_new(String *addr)
+{
+	dest *dp;
+
+	dp = (dest *)mallocz(sizeof(dest), 1);
+	if (dp == 0) {
+		perror("d_new");
+		exit(1);
+	}
+	dp->same = dp;
+	dp->nsame = 1;
+	dp->nchar = 0;
+	dp->next = dp;
+	dp->addr = escapespecial(addr);
+	dp->parent = 0;
+	dp->repl1 = dp->repl2 = 0;
+	dp->status = d_undefined;
+	return dp;
+}
+
+extern void
+d_free(dest *dp)
+{
+	if (dp != 0) {
+		s_free(dp->addr);
+		s_free(dp->repl1);
+		s_free(dp->repl2);
+		free((char *)dp);
+	}
+}
+
+/* The following routines manipulate an ordered list of items.  Insertions
+ * are always to the end of the list.  Deletions are from the beginning.
+ *
+ * The list are circular witht the `head' of the list being the last item
+ * added.
+ */
+
+/*  Get first element from a circular list linked via 'next'. */
+extern dest *
+d_rm(dest **listp)
+{
+	dest *dp;
+
+	if (*listp == 0)
+		return 0;
+	dp = (*listp)->next;
+	if (dp == *listp)
+		*listp = 0;
+	else
+		(*listp)->next = dp->next;
+	dp->next = dp;
+	return dp;
+}
+
+/*  Insert a new entry at the end of the list linked via 'next'. */
+extern void
+d_insert(dest **listp, dest *new)
+{
+	dest *head;
+
+	if (*listp == 0) {
+		*listp = new;
+		return;
+	}
+	if (new == 0)
+		return;
+	head = new->next;
+	new->next = (*listp)->next;
+	(*listp)->next = head;
+	*listp = new;
+	return;
+}
+
+/*  Get first element from a circular list linked via 'same'. */
+extern dest *
+d_rm_same(dest **listp)
+{
+	dest *dp;
+
+	if (*listp == 0)
+		return 0;
+	dp = (*listp)->same;
+	if (dp == *listp)
+		*listp = 0;
+	else
+		(*listp)->same = dp->same;
+	dp->same = dp;
+	return dp;
+}
+
+/* Look for a duplicate on the same list */
+int
+d_same_dup(dest *dp, dest *new)
+{
+	dest *first = dp;
+
+	if(new->repl2 == 0)
+		return 1;
+	do {
+		if(strcmp(s_to_c(dp->repl2), s_to_c(new->repl2))==0)
+			return 1;
+		dp = dp->same;
+	} while(dp != first);
+	return 0;
+}
+
+/* Insert an entry into the corresponding list linked by 'same'.  Note that
+ * the basic structure is a list of lists.
+ */
+extern void
+d_same_insert(dest **listp, dest *new)
+{
+	dest *dp;
+	int len;
+
+	if(new->status == d_pipe || new->status == d_cat) {
+		len = new->repl2 ? strlen(s_to_c(new->repl2)) : 0;
+		if(*listp != 0){
+			dp = (*listp)->next;
+			do {
+				if(dp->status == new->status
+				&& strcmp(s_to_c(dp->repl1), s_to_c(new->repl1))==0){
+					/* remove duplicates */
+					if(d_same_dup(dp, new))
+						return;
+					/* add to chain if chain small enough */
+					if(dp->nsame < MAXSAME
+					&& dp->nchar + len < MAXSAMECHAR){
+						new->same = dp->same;
+						dp->same = new;
+						dp->nchar += len + 1;
+						dp->nsame++;
+						return;
+					}
+				}
+				dp = dp->next;
+			} while (dp != (*listp)->next);
+		}
+		new->nchar = strlen(s_to_c(new->repl1)) + len + 1;
+	}
+	new->next = new;
+	d_insert(listp, new);
+}
+
+/*
+ *  Form a To: if multiple destinations.
+ *  The local! and !local! checks are artificial intelligence,
+ *  there should be a better way.
+ */
+extern String*
+d_to(dest *list)
+{
+	dest *np, *sp;
+	String *s;
+	int i, n;
+	char *cp;
+
+	s = s_new();
+	s_append(s, "To: ");
+	np = list;
+	i = n = 0;
+	do {
+		np = np->next;
+		sp = np;
+		do {
+			sp = sp->same;
+			cp = s_to_c(sp->addr);
+
+			/* hack to get local! out of the names */
+			if(strncmp(cp, "local!", 6) == 0)
+				cp += 6;
+
+			if(n > 20){	/* 20 to appease mailers complaining about long lines */
+				s_append(s, "\n\t");
+				n = 0;
+			}
+			if(i != 0){
+				s_append(s, ", ");
+				n += 2;
+			}
+			s_append(s, cp);
+			n += strlen(cp);
+			i++;
+		} while(sp != np);
+	} while(np != list);
+
+	return unescapespecial(s);
+}
+
+/* expand a String of destinations into a linked list of destiniations */
+extern dest *
+s_to_dest(String *sp, dest *parent)
+{
+	String *addr;
+	dest *list=0;
+	dest *new;
+
+	if (sp == 0)
+		return 0;
+	addr = s_new();
+	while (s_parseq(sp, addr)!=0) {
+		addr = escapespecial(addr);
+		if(shellchars(s_to_c(addr))){
+			while(new = d_rm(&list))
+				d_free(new);
+			break;
+		}
+		new = d_new(addr);
+		new->parent = parent;
+		new->authorized = parent->authorized;
+		d_insert(&list, new);
+		addr = s_new();
+	}
+	s_free(addr);
+	return list;
+}
+
+#undef isspace
+#define isspace(c) ((c)==' ' || (c)=='\t' || (c)=='\n')
+
+/*  Get the next field from a String.  The field is delimited by white space.
+ *  Anything delimited by double quotes is included in the string.
+ */
+static String*
+s_parseq(String *from, String *to)
+{
+	int c;
+
+	if (*from->ptr == '\0')
+		return 0;
+	if (to == 0)
+		to = s_new();
+	for (c = *from->ptr;!isspace(c) && c != 0; c = *(++from->ptr)){
+		s_putc(to, c);
+		if(c == '"'){
+			for (c = *(++from->ptr); c && c != '"'; c = *(++from->ptr))
+				s_putc(to, *from->ptr);
+			s_putc(to, '"');
+			if(c == 0)
+				break;
+		}
+	}
+	s_terminate(to);
+
+	/* crunch trailing white */
+	while(isspace(*from->ptr))
+		from->ptr++;
+
+	return to;
+}
blob - /dev/null
blob + cfee5253532ce34be0d38305c781bfde25583529 (mode 644)
--- /dev/null
+++ src/cmd/upas/send/filter.c
@@ -0,0 +1,128 @@
+#include "common.h"
+#include "send.h"
+
+Biobuf	bin;
+int rmail, tflg;
+char *subjectarg;
+
+char *findbody(char*);
+
+void
+main(int argc, char *argv[])
+{
+	message *mp;
+	dest *dp;
+	Reprog *p;
+	Resub match[10];
+	char file[MAXPATHLEN];
+	Biobuf *fp;
+	char *rcvr, *cp;
+	Mlock *l;
+	String *tmp;
+	int i;
+	int header, body;
+
+	header = body = 0;
+	ARGBEGIN {
+	case 'h':
+		header = 1;
+		break;
+	case 'b':
+		header = 1;
+		body = 1;
+		break;
+	} ARGEND
+
+	Binit(&bin, 0, OREAD);
+	if(argc < 2){
+		fprint(2, "usage: filter rcvr mailfile [regexp mailfile ...]\n");
+		exits("usage");
+	}
+	mp = m_read(&bin, 1, 0);
+
+	/* get rid of local system name */
+	cp = strchr(s_to_c(mp->sender), '!');
+	if(cp){
+		cp++;
+		mp->sender = s_copy(cp);
+	}
+
+	dp = d_new(s_copy(argv[0]));
+	strecpy(file, file+sizeof file, argv[1]);
+	cp = findbody(s_to_c(mp->body));
+	for(i = 2; i < argc; i += 2){
+		p = regcomp(argv[i]);
+		if(p == 0)
+			continue;
+		if(regexec(p, s_to_c(mp->sender), match, 10)){
+			regsub(argv[i+1], file, sizeof(file), match, 10);
+			break;
+		}
+		if(header == 0 && body == 0)
+			continue;
+		if(regexec(p, s_to_c(mp->body), match, 10)){
+			if(body == 0 && match[0].s.sp >= cp)
+				continue;
+			regsub(argv[i+1], file, sizeof(file), match, 10);
+			break;
+		}
+	}
+
+	/*
+	 *  always lock the normal mail file to avoid too many lock files
+	 *  lying about.  This isn't right but it's what the majority prefers.
+	 */
+	l = syslock(argv[1]);
+	if(l == 0){
+		fprint(2, "can't lock mail file %s\n", argv[1]);
+		exit(1);
+	}
+
+	/*
+	 *  open the destination mail file
+	 */
+	fp = sysopen(file, "ca", MBOXMODE);
+	if (fp == 0){
+		tmp = s_append(0, file);
+		s_append(tmp, ".tmp");
+		fp = sysopen(s_to_c(tmp), "cal", MBOXMODE);
+		if(fp == 0){
+			sysunlock(l);
+			fprint(2, "can't open mail file %s\n", file);
+			exit(1);
+		}
+		syslog(0, "mail", "error: used %s", s_to_c(tmp));
+		s_free(tmp);
+	}
+	Bseek(fp, 0, 2);
+	if(m_print(mp, fp, (char *)0, 1) < 0
+	|| Bprint(fp, "\n") < 0
+	|| Bflush(fp) < 0){
+		sysclose(fp);
+		sysunlock(l);
+		fprint(2, "can't write mail file %s\n", file);
+		exit(1);
+	}
+	sysclose(fp);
+
+	sysunlock(l);
+	rcvr = argv[0];
+	if(cp = strrchr(rcvr, '!'))
+		rcvr = cp+1;
+	logdelivery(dp, rcvr, mp);
+	exit(0);
+}
+
+char*
+findbody(char *p)
+{
+	if(*p == '\n')
+		return p;
+
+	while(*p){
+		if(*p == '\n' && *(p+1) == '\n')
+			return p+1;
+		p++;
+	}
+	return p;
+}
blob - /dev/null
blob + f3b2b36dc8e7cf7bab54d98a81619774519e0a51 (mode 644)
--- /dev/null
+++ src/cmd/upas/send/gateway.c
@@ -0,0 +1,24 @@
+#include "common.h"
+#include "send.h"
+
+#undef isspace
+#define isspace(c) ((c)==' ' || (c)=='\t' || (c)=='\n')
+
+/*
+ *  Translate the last component of the sender address.  If the translation
+ *  yields the same address, replace the sender with its last component.
+ */
+extern void
+gateway(message *mp)
+{
+	char *base;
+	String *s;
+
+	/* first remove all systems equivalent to us */
+	base = skipequiv(s_to_c(mp->sender));
+	if(base != s_to_c(mp->sender)){
+		s = mp->sender;
+		mp->sender = s_copy(base);
+		s_free(s);
+	}
+}
blob - /dev/null
blob + 93136b7c67c4383bdc2ac5b034c12ba386503e6a (mode 644)
--- /dev/null
+++ src/cmd/upas/send/local.c
@@ -0,0 +1,129 @@
+#include "common.h"
+#include "send.h"
+
+static void
+mboxfile(dest *dp, String *user, String *path, char *file)
+{
+	char *cp;
+
+	mboxpath(s_to_c(user), s_to_c(dp->addr), path, 0);
+	cp = strrchr(s_to_c(path), '/');
+	if(cp)
+		path->ptr = cp+1;
+	else
+		path->ptr = path->base;
+	s_append(path, file);
+}
+
+/*
+ *  Check forwarding requests
+ */
+extern dest*
+expand_local(dest *dp)
+{
+	Biobuf *fp;
+	String *file, *line, *s;
+	dest *rv;
+	int forwardok;
+	char *user;
+
+	/* short circuit obvious security problems */
+	if(strstr(s_to_c(dp->addr), "/../")){
+		dp->status = d_unknown;
+		return 0;
+	}
+
+	/* isolate user's name if part of a path */
+	user = strrchr(s_to_c(dp->addr), '!');
+	if(user)
+		user++;
+	else
+		user = s_to_c(dp->addr);
+
+	/* if no replacement string, plug in user's name */
+	if(dp->repl1 == 0){
+		dp->repl1 = s_new();
+		mboxname(user, dp->repl1);
+	}
+
+	s = unescapespecial(s_clone(dp->repl1));
+
+	/*
+	 *  if this is the descendant of a `forward' file, don't
+	 *  look for a forward.
+	 */
+	forwardok = 1;
+	for(rv = dp->parent; rv; rv = rv->parent)
+		if(rv->status == d_cat){
+			forwardok = 0;
+			break;
+		}
+	file = s_new();
+	if(forwardok){
+		/*
+		 *  look for `forward' file for forwarding address(es)
+		 */
+		mboxfile(dp, s, file, "forward");
+		fp = sysopen(s_to_c(file), "r", 0);
+		if (fp != 0) {
+			line = s_new();
+			for(;;){
+				if(s_read_line(fp, line) == nil)
+					break;
+				if(*(line->ptr - 1) != '\n')
+					break;
+				if(*(line->ptr - 2) == '\\')
+					*(line->ptr-2) = ' ';
+				*(line->ptr-1) = ' ';
+			}
+			sysclose(fp);
+			if(debug)
+				fprint(2, "forward = %s\n", s_to_c(line));
+			rv = s_to_dest(s_restart(line), dp);
+			s_free(line);
+			if(rv){
+				s_free(file);
+				s_free(s);
+				return rv;
+			}
+		}
+	}
+
+	/*
+	 *  look for a 'pipe' file.  This won't work if there are
+	 *  special characters in the account name since the file
+	 *  name passes through a shell.  tdb.
+	 */
+	mboxfile(dp, dp->repl1, s_reset(file), "pipeto");
+	if(sysexist(s_to_c(file))){
+		if(debug)
+			fprint(2, "found a pipeto file\n");
+		dp->status = d_pipeto;
+		line = s_new();
+		s_append(line, "upasname='");
+		s_append(line, user);
+		s_append(line, "' ");
+		s_append(line, s_to_c(file));
+		s_append(line, " ");
+		s_append(line, s_to_c(dp->addr));
+		s_append(line, " ");
+		s_append(line, s_to_c(dp->repl1));
+		s_free(dp->repl1);
+		dp->repl1 = line;
+		s_free(file);
+		s_free(s);
+		return dp;
+	}
+
+	/*
+	 *  see if the mailbox directory exists
+	 */
+	mboxfile(dp, s, s_reset(file), ".");
+	if(sysexist(s_to_c(file)))
+		dp->status = d_cat;
+	else
+		dp->status = d_unknown;
+	s_free(file);
+	s_free(s);
+	return 0;
+}
blob - /dev/null
blob + 52df3800105f2c132bc10990c9bfbde935b33ced (mode 644)
--- /dev/null
+++ src/cmd/upas/send/log.c
@@ -0,0 +1,85 @@
+#include "common.h"
+#include "send.h"
+
+/* configuration */
+#define LOGBiobuf "log/status"
+
+/* log mail delivery */
+extern void
+logdelivery(dest *list, char *rcvr, message *mp)
+{
+	dest *parent;
+	String *srcvr, *sender;
+
+	srcvr = unescapespecial(s_copy(rcvr));
+	sender = unescapespecial(s_clone(mp->sender));
+
+	for(parent=list; parent->parent!=0; parent=parent->parent)
+		;
+	if(parent!=list && strcmp(s_to_c(parent->addr), s_to_c(srcvr))!=0)
+		syslog(0, "mail", "delivered %s From %.256s %.256s (%.256s) %d",
+			rcvr,
+			s_to_c(sender), s_to_c(mp->date),
+			s_to_c(parent->addr), mp->size);
+	else
+		syslog(0, "mail", "delivered %s From %.256s %.256s %d", s_to_c(srcvr),
+			s_to_c(sender), s_to_c(mp->date), mp->size);
+	s_free(srcvr);
+	s_free(sender);
+}
+
+/* log mail forwarding */
+extern void
+loglist(dest *list, message *mp, char *tag)
+{
+	dest *next;
+	dest *parent;
+	String *srcvr, *sender;
+
+	sender = unescapespecial(s_clone(mp->sender));
+
+	for(next=d_rm(&list); next != 0; next = d_rm(&list)) {
+		for(parent=next; parent->parent!=0; parent=parent->parent)
+			;
+		srcvr = unescapespecial(s_clone(next->addr));
+		if(parent!=next)
+			syslog(0, "mail", "%s %.256s From %.256s %.256s (%.256s) %d",
+				tag,
+				s_to_c(srcvr), s_to_c(sender),
+				s_to_c(mp->date), s_to_c(parent->addr), mp->size);
+		else
+			syslog(0, "mail", "%s %.256s From %.256s %.256s %d", tag,
+				s_to_c(srcvr), s_to_c(sender),
+				s_to_c(mp->date), mp->size);
+		s_free(srcvr);
+	}
+	s_free(sender);
+}
+
+/* log a mail refusal */
+extern void
+logrefusal(dest *dp, message *mp, char *msg)
+{
+	char buf[2048];
+	char *cp, *ep;
+	String *sender, *srcvr;
+
+	srcvr = unescapespecial(s_clone(dp->addr));
+	sender = unescapespecial(s_clone(mp->sender));
+
+	sprint(buf, "error %.256s From %.256s %.256s\nerror+ ", s_to_c(srcvr),
+		s_to_c(sender), s_to_c(mp->date));
+	s_free(srcvr);
+	s_free(sender);
+	cp = buf + strlen(buf);
+	ep = buf + sizeof(buf) - sizeof("error + ");
+	while(*msg && cp<ep) {
+		*cp++ = *msg;
+		if (*msg++ == '\n') {
+			strcpy(cp, "error+ ");
+			cp += sizeof("error+ ") - 1;
+		}
+	}
+	*cp = 0;
+	syslog(0, "mail", "%s", buf);
+}
blob - /dev/null
blob + 6c455833a76b1779a8eb19e4e8353d9d6c00ef00 (mode 644)
--- /dev/null
+++ src/cmd/upas/send/main.c
@@ -0,0 +1,575 @@
+#include "common.h"
+#include "send.h"
+
+/* globals to all files */
+int rmail;
+char *thissys, *altthissys;
+int nflg;
+int xflg;
+int debug;
+int rflg;
+int iflg = 1;
+int nosummary;
+
+/* global to this file */
+static String *errstring;
+static message *mp;
+static int interrupt;
+static int savemail;
+static Biobuf in;
+static int forked;
+static int add822headers = 1;
+static String *arglist;
+
+/* predeclared */
+static int	send(dest *, message *, int);
+static void	lesstedious(void);
+static void	save_mail(message *);
+static int	complain_mail(dest *, message *);
+static int	pipe_mail(dest *, message *);
+static void	appaddr(String *, dest *);
+static void	mkerrstring(String *, message *, dest *, dest *, char *, int);
+static int	replymsg(String *, message *, dest *);
+static int	catchint(void*, char*);
+
+void
+usage(void)
+{
+	fprint(2, "usage: mail [-birtx] list-of-addresses\n");
+	exits("usage");
+}
+
+void
+main(int argc, char *argv[])
+{
+	dest *dp=0;
+	int checkforward;
+	char *base;
+	int rv;
+
+	/* process args */
+	ARGBEGIN{
+	case '#':
+		nflg = 1;
+		break;
+	case 'b':
+		add822headers = 0;
+		break;
+	case 'x':
+		nflg = 1;
+		xflg = 1;
+		break;
+	case 'd':
+		debug = 1;
+		break;
+	case 'i':
+		iflg = 0;
+		break;
+	case 'r':
+		rflg = 1;
+		break;
+	default:
+		usage();
+	}ARGEND
+
+	while(*argv){
+		if(shellchars(*argv)){
+			fprint(2, "illegal characters in destination\n");
+			exits("syntax");
+		}
+		d_insert(&dp, d_new(s_copy(*argv++)));
+	}
+
+	if (dp == 0)
+		usage();
+	arglist = d_to(dp);
+
+	/*
+	 * get context:
+	 *	- whether we're rmail or mail
+	 */
+	base = basename(argv0);
+	checkforward = rmail = (strcmp(base, "rmail")==0) | rflg;
+	thissys = sysname_read();
+	altthissys = alt_sysname_read();
+	if(rmail)
+		add822headers = 0;
+
+	/*
+	 *  read the mail.  If an interrupt occurs while reading, save in
+	 *  dead.letter
+	 */
+	if (!nflg) {
+		Binit(&in, 0, OREAD);
+		if(!rmail)
+			atnotify(catchint, 1);
+		mp = m_read(&in, rmail, !iflg);
+		if (mp == 0)
+			exit(0);
+		if (interrupt != 0) {
+			save_mail(mp);
+			exit(1);
+		}
+	} else {
+		mp = m_new();
+		if(default_from(mp) < 0){
+			fprint(2, "%s: can't determine login name\n", argv0);
+			exit(1);
+		}
+	}
+	errstring = s_new();
+	getrules();
+
+	/*
+	 *  If this is a gateway, translate the sender address into a local
+	 *  address.  This only happens if mail to the local address is 
+	 *  forwarded to the sender.
+	 */
+	gateway(mp);
+
+	/*
+	 *  Protect against shell characters in the sender name for
+	 *  security reasons.
+	 */
+	mp->sender = escapespecial(mp->sender);
+	if (shellchars(s_to_c(mp->sender)))
+		mp->replyaddr = s_copy("postmaster");
+	else
+		mp->replyaddr = s_clone(mp->sender);
+
+	/*
+	 *  reject messages that have been looping for too long
+	 */
+	if(mp->received > 32)
+		exit(refuse(dp, mp, "possible forward loop", 0, 0));
+
+	/*
+	 *  reject messages that are too long.  We don't do it earlier
+	 *  in m_read since we haven't set up enough things yet.
+	 */
+	if(mp->size < 0)
+		exit(refuse(dp, mp, "message too long", 0, 0));
+
+	rv = send(dp, mp, checkforward);
+	if(savemail)
+		save_mail(mp);
+	if(mp)
+		m_free(mp);
+	exit(rv);
+}
+
+/* send a message to a list of sites */
+static int
+send(dest *destp, message *mp, int checkforward)
+{
+	dest *dp;		/* destination being acted upon */
+	dest *bound;		/* bound destinations */
+	int errors=0;
+
+	/* bind the destinations to actions */
+	bound = up_bind(destp, mp, checkforward);
+	if(add822headers && mp->haveto == 0){
+		if(nosummary)
+			mp->to = d_to(bound);
+		else
+			mp->to = arglist;
+	}
+
+	/* loop through and execute commands */
+	for (dp = d_rm(&bound); dp != 0; dp = d_rm(&bound)) {
+		switch (dp->status) {
+		case d_cat:
+			errors += cat_mail(dp, mp);
+			break;
+		case d_pipeto:
+		case d_pipe:
+			if (!rmail && !nflg && !forked) {
+				forked = 1;
+				lesstedious();
+			}
+			errors += pipe_mail(dp, mp);
+			break;
+		default:
+			errors += complain_mail(dp, mp);
+			break;
+		}
+	}
+
+	return errors;
+}
+
+/* avoid user tedium (as Mike Lesk said in a previous version) */
+static void
+lesstedious(void)
+{
+	int i;
+
+	if(debug)
+		return;
+
+	switch(fork()){
+	case -1:
+		break;
+	case 0:
+		sysdetach();
+		for(i=0; i<3; i++)
+			close(i);
+		savemail = 0;
+		break;
+	default:
+		exit(0);
+	}
+}
+
+
+/* save the mail */
+static void
+save_mail(message *mp)
+{
+	Biobuf *fp;
+	String *file;
+
+	file = s_new();
+	deadletter(file);
+	fp = sysopen(s_to_c(file), "cAt", 0660);
+	if (fp == 0)
+		return;
+	m_bprint(mp, fp);
+	sysclose(fp);
+	fprint(2, "saved in %s\n", s_to_c(file));
+	s_free(file);
+}
+
+/* remember the interrupt happened */
+
+static int
+catchint(void *a, char *msg)
+{
+	USED(a);
+	if(strstr(msg, "interrupt") || strstr(msg, "hangup")) {
+		interrupt = 1;
+		return 1;
+	}
+	return 0;
+}
+
+/* dispose of incorrect addresses */
+static int
+complain_mail(dest *dp, message *mp)
+{
+	char *msg;
+
+	switch (dp->status) {
+	case d_undefined:
+		msg = "Invalid address"; /* a little different, for debugging */
+		break;
+	case d_syntax:
+		msg = "invalid address";
+		break;
+	case d_unknown:
+		msg = "unknown user";
+		break;
+	case d_eloop:
+	case d_loop:
+		msg = "forwarding loop";
+		break;
+	case d_noforward:
+		if(dp->pstat && *s_to_c(dp->repl2))
+			return refuse(dp, mp, s_to_c(dp->repl2), dp->pstat, 0);
+		else
+			msg = "destination unknown or forwarding disallowed";
+		break;
+	case d_pipe:
+		msg = "broken pipe";
+		break;
+	case d_cat:
+		msg = "broken cat";
+		break;
+	case d_translate:
+		if(dp->pstat && *s_to_c(dp->repl2))
+			return refuse(dp, mp, s_to_c(dp->repl2), dp->pstat, 0);
+		else
+			msg = "name translation failed";
+		break;
+	case d_alias:
+		msg = "broken alias";
+		break;
+	case d_badmbox:
+		msg = "corrupted mailbox";
+		break;
+	case d_resource:
+		return refuse(dp, mp, "out of some resource.  Try again later.", 0, 1);
+	default:
+		msg = "unknown d_";
+		break;
+	}
+	if (nflg) {
+		print("%s: %s\n", msg, s_to_c(dp->addr));
+		return 0;
+	}
+	return refuse(dp, mp, msg, 0, 0);
+}
+
+/* dispose of remote addresses */
+static int
+pipe_mail(dest *dp, message *mp)
+{
+	dest *next, *list=0;
+	String *cmd;
+	process *pp;
+	int status;
+	char *none;
+	String *errstring=s_new();
+
+	if (dp->status == d_pipeto)
+		none = "none";
+	else
+		none = 0;
+	/*
+	 *  collect the arguments
+	 */
+	next = d_rm_same(&dp);
+	if(xflg)
+		cmd = s_new();
+	else
+		cmd = s_clone(next->repl1);
+	for(; next != 0; next = d_rm_same(&dp)){
+		if(xflg){
+			s_append(cmd, s_to_c(next->addr));
+			s_append(cmd, "\n");
+		} else {
+			if (next->repl2 != 0) {
+				s_append(cmd, " ");
+				s_append(cmd, s_to_c(next->repl2));
+			}
+		}
+		d_insert(&list, next);
+	}
+
+	if (nflg) {
+		if(xflg)
+			print("%s", s_to_c(cmd));
+		else
+			print("%s\n", s_to_c(cmd));
+		s_free(cmd);
+		return 0;
+	}
+
+	/*
+	 *  run the process
+	 */
+	pp = proc_start(s_to_c(cmd), instream(), 0, outstream(), 1, none);
+	if(pp==0 || pp->std[0]==0 || pp->std[2]==0)
+		return refuse(list, mp, "out of processes, pipes, or memory", 0, 1);
+	pipesig(0);
+	m_print(mp, pp->std[0]->fp, thissys, 0);
+	pipesigoff();
+	stream_free(pp->std[0]);
+	pp->std[0] = 0;
+	while(s_read_line(pp->std[2]->fp, errstring))
+		;
+	status = proc_wait(pp);
+	proc_free(pp);
+	s_free(cmd);
+
+	/*
+	 *  return status
+	 */
+	if (status != 0)
+		return refuse(list, mp, s_to_c(errstring), status, 0);
+	loglist(list, mp, "remote");
+	return 0;
+}
+
+static void
+appaddr(String *sp, dest *dp)
+{
+	dest *parent;
+	String *s;
+
+	if (dp->parent != 0) {
+		for(parent=dp->parent; parent->parent!=0; parent=parent->parent)
+			;
+		s = unescapespecial(s_clone(parent->addr));
+		s_append(sp, s_to_c(s));
+		s_free(s);
+		s_append(sp, "' alias `");
+	}
+	s = unescapespecial(s_clone(dp->addr));
+	s_append(sp, s_to_c(s));
+	s_free(s);
+}
+
+/*
+ *  reject delivery
+ *
+ *  returns	0	- if mail has been disposed of
+ *		other	- if mail has not been disposed
+ */
+int
+refuse(dest *list, message *mp, char *cp, int status, int outofresources)
+{
+	String *errstring=s_new();
+	dest *dp;
+	int rv;
+
+	dp = d_rm(&list);
+	mkerrstring(errstring, mp, dp, list, cp, status);
+
+	/*
+	 *  log first in case we get into trouble
+	 */
+	logrefusal(dp, mp, s_to_c(errstring));
+
+	/*
+	 *  bulk mail is never replied to, if we're out of resources,
+	 *  let the sender try again
+	 */
+	if(rmail){
+		/* accept it or request a retry */
+		if(outofresources){
+			fprint(2, "Mail %s\n", s_to_c(errstring));
+			rv = 1;					/* try again later */
+		} else if(mp->bulk)
+			rv = 0;					/* silently discard bulk */
+		else
+			rv = replymsg(errstring, mp, dp);	/* try later if we can't reply */
+	} else {
+		/* aysnchronous delivery only happens if !rmail */
+		if(forked){
+			/*
+			 *  if spun off for asynchronous delivery, we own the mail now.
+			 *  return it or dump it on the floor.  rv really doesn't matter.
+			 */
+			rv = 0;
+			if(!outofresources && !mp->bulk)
+				replymsg(errstring, mp, dp);
+		} else {
+			fprint(2, "Mail %s\n", s_to_c(errstring));
+			savemail = 1;
+			rv = 1;
+		}
+	}
+
+	s_free(errstring);
+	return rv;
+}
+
+/* make the error message */
+static void
+mkerrstring(String *errstring, message *mp, dest *dp, dest *list, char *cp, int status)
+{
+	dest *next;
+	char smsg[64];
+	String *sender;
+
+	sender = unescapespecial(s_clone(mp->sender));
+
+	/* list all aliases */
+	s_append(errstring, " from '");
+	s_append(errstring, s_to_c(sender));
+	s_append(errstring, "'\nto '");
+	appaddr(errstring, dp);
+	for(next = d_rm(&list); next != 0; next = d_rm(&list)) {
+		s_append(errstring, "'\nand '");
+		appaddr(errstring, next);
+		d_insert(&dp, next);
+	}
+	s_append(errstring, "'\nfailed with error '");
+	s_append(errstring, cp);
+	s_append(errstring, "'.\n");
+
+	/* >> and | deserve different flavored messages */
+	switch(dp->status) {
+	case d_pipe:
+		s_append(errstring, "The mailer `");
+		s_append(errstring, s_to_c(dp->repl1));
+		sprint(smsg, "' returned error status %x.\n\n", status);
+		s_append(errstring, smsg);
+		break;
+	}
+
+	s_free(sender);
+}
+
+/*
+ *  create a new boundary
+ */
+static String*
+mkboundary(void)
+{
+	char buf[32];
+	int i;
+	static int already;
+
+	if(already == 0){
+		srand((time(0)<<16)|getpid());
+		already = 1;
+	}
+	strcpy(buf, "upas-");
+	for(i = 5; i < sizeof(buf)-1; i++)
+		buf[i] = 'a' + nrand(26);
+	buf[i] = 0;
+	return s_copy(buf);
+}
+
+/*
+ *  reply with up to 1024 characters of the
+ *  original message
+ */
+static int
+replymsg(String *errstring, message *mp, dest *dp)
+{
+	message *refp = m_new();
+	dest *ndp;
+	char *rcvr;
+	int rv;
+	String *boundary;
+
+	boundary = mkboundary();
+
+	refp->bulk = 1;
+	refp->rfc822headers = 1;
+	rcvr = dp->status==d_eloop ? "postmaster" : s_to_c(mp->replyaddr);
+	ndp = d_new(s_copy(rcvr));
+	s_append(refp->sender, "postmaster");
+	s_append(refp->replyaddr, "/dev/null");
+	s_append(refp->date, thedate());
+	refp->haveto = 1;
+	s_append(refp->body, "To: ");
+	s_append(refp->body, rcvr);
+	s_append(refp->body, "\n");
+	s_append(refp->body, "Subject: bounced mail\n");
+	s_append(refp->body, "MIME-Version: 1.0\n");
+	s_append(refp->body, "Content-Type: multipart/mixed;\n");
+	s_append(refp->body, "\tboundary=\"");
+	s_append(refp->body, s_to_c(boundary));
+	s_append(refp->body, "\"\n");
+	s_append(refp->body, "Content-Disposition: inline\n");
+	s_append(refp->body, "\n");
+	s_append(refp->body, "This is a multi-part message in MIME format.\n");
+	s_append(refp->body, "--");
+	s_append(refp->body, s_to_c(boundary));
+	s_append(refp->body, "\n");
+	s_append(refp->body, "Content-Disposition: inline\n");
+	s_append(refp->body, "Content-Type: text/plain; charset=\"US-ASCII\"\n");
+	s_append(refp->body, "Content-Transfer-Encoding: 7bit\n");
+	s_append(refp->body, "\n");
+	s_append(refp->body, "The attached mail");
+	s_append(refp->body, s_to_c(errstring));
+	s_append(refp->body, "--");
+	s_append(refp->body, s_to_c(boundary));
+	s_append(refp->body, "\n");
+	s_append(refp->body, "Content-Type: message/rfc822\n");
+	s_append(refp->body, "Content-Disposition: inline\n\n");
+	s_append(refp->body, s_to_c(mp->body));
+	s_append(refp->body, "--");
+	s_append(refp->body, s_to_c(boundary));
+	s_append(refp->body, "--\n");
+
+	refp->size = s_len(refp->body);
+	rv = send(ndp, refp, 0);
+	m_free(refp);
+	d_free(ndp);
+	return rv;
+}
blob - /dev/null
blob + f0abcf0c1bed4f992f5183d36457abe4f8c50f49 (mode 644)
--- /dev/null
+++ src/cmd/upas/send/makefile
@@ -0,0 +1,46 @@
+SSRC=	message.c main.c bind.c rewrite.c local.c dest.c process.c translate.c\
+	log.c chkfwd.c notify.c gateway.c authorize.o ../common/*.c
+SOBJ=	message.o main.o bind.o rewrite.o local.o dest.o process.o translate.o\
+	log.o chkfwd.o notify.o gateway.o authorize.o\
+	../config/config.o ../common/common.a ../libc/libc.a
+SINC=	../common/mail.h ../common/string.h ../common/aux.h
+CFLAGS=${UNIX} -g -I. -I../libc -I../common -I/usr/include ${SCFLAGS}
+LFLAGS=-g
+.c.o: ; $(CC) -c $(CFLAGS) $*.c
+LIB=/usr/lib/upas
+
+all: send
+
+send: $(SOBJ)
+	$(CC) $(SOBJ) $(LFLAGS) -o send
+
+chkfwd.o: $(SINC) message.h dest.h
+dest.o: $(SINC) dest.h
+local.o: $(SINC) dest.h process.h
+log.o: $(SINC) message.h
+main.o: $(SINC) message.h dest.h process.h
+bind.o: $(SINC) dest.h message.h
+process.o: $(SINC) process.h
+rewrite.o: $(SINC) dest.h
+translate.o: $(SINC) dest.h process.h
+message.o: $(SINC) message.h
+notify.o: $(SINC) message.h
+gateway.o: $(SINC) dest.h message.h
+
+prcan:
+	prcan $(SSRC)
+
+clean:
+	-rm -f send *.[oO] a.out core *.sL rmail
+
+cyntax:
+	cyntax $(CFLAGS) $(SSRC)
+
+install: send
+	rm -f $(LIB)/send /bin/rmail
+	cp send $(LIB)/send
+	cp send /bin/rmail
+	strip /bin/rmail
+	strip $(LIB)/send
+	chown root $(LIB)/send /bin/rmail
+	chmod 4755 $(LIB)/send /bin/rmail
blob - /dev/null
blob + eab6160ca3a00cd874065781e3492c0905860bac (mode 644)
--- /dev/null
+++ src/cmd/upas/send/message.c
@@ -0,0 +1,573 @@
+#include "common.h"
+#include "send.h"
+
+#include "../smtp/smtp.h"
+#include "../smtp/y.tab.h"
+
+/* global to this file */
+static Reprog *rfprog;
+static Reprog *fprog;
+
+#define VMLIMIT (64*1024)
+#define MSGLIMIT (128*1024*1024)
+
+int received;	/* from rfc822.y */
+
+static String*	getstring(Node *p);
+static String*	getaddr(Node *p);
+
+extern int
+default_from(message *mp)
+{
+	char *cp, *lp;
+
+	cp = getenv("upasname");
+	lp = getlog();
+	if(lp == nil)
+		return -1;
+
+	if(cp && *cp)
+		s_append(mp->sender, cp);
+	else
+		s_append(mp->sender, lp);
+	s_append(mp->date, thedate());
+	return 0;
+}
+
+extern message *
+m_new(void)
+{
+	message *mp;
+
+	mp = (message *)mallocz(sizeof(message), 1);
+	if (mp == 0) {
+		perror("message:");
+		exit(1);
+	}
+	mp->sender = s_new();
+	mp->replyaddr = s_new();
+	mp->date = s_new();
+	mp->body = s_new();
+	mp->size = 0;
+	mp->fd = -1;
+	return mp;
+}
+
+extern void
+m_free(message *mp)
+{
+	if(mp->fd >= 0){
+		close(mp->fd);
+		sysremove(s_to_c(mp->tmp));
+		s_free(mp->tmp);
+	}
+	s_free(mp->sender);
+	s_free(mp->date);
+	s_free(mp->body);
+	s_free(mp->havefrom);
+	s_free(mp->havesender);
+	s_free(mp->havereplyto);
+	s_free(mp->havesubject);
+	free((char *)mp);
+}
+
+/* read a message into a temp file , return an open fd to it */
+static int
+m_read_to_file(Biobuf *fp, message *mp)
+{
+	int fd;
+	int n;
+	String *file;
+	char buf[4*1024];
+
+	file = s_new();
+	/*
+	 *  create temp file to be remove on close
+	 */
+	abspath("mtXXXXXX", UPASTMP, file);
+	mktemp(s_to_c(file));
+	if((fd = syscreate(s_to_c(file), ORDWR|ORCLOSE, 0600))<0){
+		s_free(file);
+		return -1;
+	}
+	mp->tmp = file;
+
+	/*
+	 *  read the rest into the temp file
+	 */
+	while((n = Bread(fp, buf, sizeof(buf))) > 0){
+		if(write(fd, buf, n) != n){
+			close(fd);
+			return -1;
+		}
+		mp->size += n;
+		if(mp->size > MSGLIMIT){
+			mp->size = -1;
+			break;
+		}
+	}
+
+	mp->fd = fd;
+	return 0;
+}
+
+/* get the first address from a node */
+static String*
+getaddr(Node *p)
+{
+	for(; p; p = p->next)
+		if(p->s && p->addr)
+			return s_copy(s_to_c(p->s));
+}
+
+/* get the text of a header line minus the field name */
+static String*
+getstring(Node *p)
+{
+	String *s;
+
+	s = s_new();
+	if(p == nil)
+		return s;
+
+	for(p = p->next; p; p = p->next){
+		if(p->s){
+			s_append(s, s_to_c(p->s));
+		}else{
+			s_putc(s, p->c);
+			s_terminate(s);
+		}
+		if(p->white)
+			s_append(s, s_to_c(p->white));
+	}
+	return s;
+}
+
+#if 0 /* jpc */
+static char *fieldname[] =
+{
+[WORD-WORD]	"WORD",
+[DATE-WORD]	"DATE",
+[RESENT_DATE-WORD]	"RESENT_DATE",
+[RETURN_PATH-WORD]	"RETURN_PATH",
+[FROM-WORD]	"FROM",
+[SENDER-WORD]	"SENDER",
+[REPLY_TO-WORD]	"REPLY_TO",
+[RESENT_FROM-WORD]	"RESENT_FROM",
+[RESENT_SENDER-WORD]	"RESENT_SENDER",
+[RESENT_REPLY_TO-WORD]	"RESENT_REPLY_TO",
+[SUBJECT-WORD]	"SUBJECT",
+[TO-WORD]	"TO",
+[CC-WORD]	"CC",
+[BCC-WORD]	"BCC",
+[RESENT_TO-WORD]	"RESENT_TO",
+[RESENT_CC-WORD]	"RESENT_CC",
+[RESENT_BCC-WORD]	"RESENT_BCC",
+[REMOTE-WORD]	"REMOTE",
+[PRECEDENCE-WORD]	"PRECEDENCE",
+[MIMEVERSION-WORD]	"MIMEVERSION",
+[CONTENTTYPE-WORD]	"CONTENTTYPE",
+[MESSAGEID-WORD]	"MESSAGEID",
+[RECEIVED-WORD]	"RECEIVED",
+[MAILER-WORD]	"MAILER",
+[BADTOKEN-WORD]	"BADTOKEN",
+};
+#endif /* jpc */
+
+/* fix 822 addresses */
+static void
+rfc822cruft(message *mp)
+{
+	Field *f;
+	Node *p;
+	String *body, *s;
+	char *cp;
+
+	/*
+	 *  parse headers in in-core part
+	 */
+	yyinit(s_to_c(mp->body), s_len(mp->body));
+	mp->rfc822headers = 0;
+	yyparse();
+	mp->rfc822headers = 1;
+	mp->received = received;
+
+	/*
+	 *  remove equivalent systems in all addresses
+	 */
+	body = s_new();
+	cp = s_to_c(mp->body);
+	for(f = firstfield; f; f = f->next){
+		if(f->node->c == MIMEVERSION)
+			mp->havemime = 1;
+		if(f->node->c == FROM)
+			mp->havefrom = getaddr(f->node);
+		if(f->node->c == SENDER)
+			mp->havesender = getaddr(f->node);
+		if(f->node->c == REPLY_TO)
+			mp->havereplyto = getaddr(f->node);
+		if(f->node->c == TO)
+			mp->haveto = 1;
+		if(f->node->c == DATE)
+			mp->havedate = 1;
+		if(f->node->c == SUBJECT)
+			mp->havesubject = getstring(f->node);
+		if(f->node->c == PRECEDENCE && f->node->next && f->node->next->next){
+			s = f->node->next->next->s;
+			if(s && (strcmp(s_to_c(s), "bulk") == 0
+				|| strcmp(s_to_c(s), "Bulk") == 0))
+					mp->bulk = 1;
+		}
+		for(p = f->node; p; p = p->next){
+			if(p->s){
+				if(p->addr){
+					cp = skipequiv(s_to_c(p->s));
+					s_append(body, cp);
+				} else 
+					s_append(body, s_to_c(p->s));
+			}else{
+				s_putc(body, p->c);
+				s_terminate(body);
+			}
+			if(p->white)
+				s_append(body, s_to_c(p->white));
+			cp = p->end+1;
+		}
+		s_append(body, "\n");
+	}
+
+	if(*s_to_c(body) == 0){
+		s_free(body);
+		return;
+	}
+
+	if(*cp != '\n')
+		s_append(body, "\n");
+	s_memappend(body, cp, s_len(mp->body) - (cp - s_to_c(mp->body)));
+	s_terminate(body);
+
+	firstfield = 0;
+	mp->size += s_len(body) - s_len(mp->body);
+	s_free(mp->body);
+	mp->body = body;
+}
+
+/* read in a message, interpret the 'From' header */
+extern message *
+m_read(Biobuf *fp, int rmail, int interactive)
+{
+	message *mp;
+	Resub subexp[10];
+	char *line;
+	int first;
+	int n;
+
+	mp = m_new();
+
+	/* parse From lines if remote */
+	if (rmail) {
+		/* get remote address */
+		String *sender=s_new();
+
+		if (rfprog == 0)
+			rfprog = regcomp(REMFROMRE);
+		first = 1;
+		while(s_read_line(fp, s_restart(mp->body)) != 0) {
+			memset(subexp, 0, sizeof(subexp));
+			if (regexec(rfprog, s_to_c(mp->body), subexp, 10) == 0){
+				if(first == 0)
+					break;
+				if (fprog == 0)
+					fprog = regcomp(FROMRE);
+				memset(subexp, 0, sizeof(subexp));
+				if(regexec(fprog, s_to_c(mp->body), subexp,10) == 0)
+					break;
+				s_restart(mp->body);
+				append_match(subexp, s_restart(sender), SENDERMATCH);
+				append_match(subexp, s_restart(mp->date), DATEMATCH);
+				break;
+			}
+			append_match(subexp, s_restart(sender), REMSENDERMATCH);
+			append_match(subexp, s_restart(mp->date), REMDATEMATCH);
+			if(subexp[REMSYSMATCH].s.sp!=subexp[REMSYSMATCH].e.ep){
+				append_match(subexp, mp->sender, REMSYSMATCH);
+				s_append(mp->sender, "!");
+			}
+			first = 0;
+		}
+		s_append(mp->sender, s_to_c(sender));
+
+		s_free(sender);
+	}
+	if(*s_to_c(mp->sender)=='\0')
+		default_from(mp);
+
+	/* if sender address is unreturnable, treat message as bulk mail */
+	if(!returnable(s_to_c(mp->sender)))
+		mp->bulk = 1;
+
+	/* get body */
+	if(interactive && !rmail){
+		/* user typing on terminal: terminator == '.' or EOF */
+		for(;;) {
+			line = s_read_line(fp, mp->body);
+			if (line == 0)
+				break;
+			if (strcmp(".\n", line)==0) {
+				mp->body->ptr -= 2;
+				*mp->body->ptr = '\0';
+				break;
+			}
+		}
+		mp->size = mp->body->ptr - mp->body->base;
+	} else {
+		/*
+		 *  read up to VMLIMIT bytes (more or less) into main memory.
+		 *  if message is longer put the rest in a tmp file.
+		 */
+		mp->size = mp->body->ptr - mp->body->base;
+		n = s_read(fp, mp->body, VMLIMIT);
+		if(n < 0){
+			perror("m_read");
+			exit(1);
+		}
+		mp->size += n;
+		if(n == VMLIMIT){
+			if(m_read_to_file(fp, mp) < 0){
+				perror("m_read");
+				exit(1);
+			}
+		}
+
+	}
+
+	/*
+	 *  ignore 0 length messages from a terminal
+	 */
+	if (!rmail && mp->size == 0)
+		return 0;
+
+	rfc822cruft(mp);
+
+	return mp;
+}
+
+/* return a piece of message starting at `offset' */
+extern int
+m_get(message *mp, long offset, char **pp)
+{
+	static char buf[4*1024];
+
+	/*
+	 *  are we past eof?
+	 */
+	if(offset >= mp->size)
+		return 0;
+
+	/*
+	 *  are we in the virtual memory portion?
+	 */
+	if(offset < s_len(mp->body)){
+		*pp = mp->body->base + offset;
+		return mp->body->ptr - mp->body->base - offset;
+	}
+
+	/*
+	 *  read it from the temp file
+	 */
+	offset -= s_len(mp->body);
+	if(mp->fd < 0)
+		return -1;
+	if(seek(mp->fd, offset, 0)<0)
+		return -1;
+	*pp = buf;
+	return read(mp->fd, buf, sizeof buf);
+}
+
+/* output the message body without ^From escapes */
+static int
+m_noescape(message *mp, Biobuf *fp)
+{
+	long offset;
+	int n;
+	char *p;
+
+	for(offset = 0; offset < mp->size; offset += n){
+		n = m_get(mp, offset, &p);
+		if(n <= 0){
+			Bflush(fp);
+			return -1;
+		}
+		if(Bwrite(fp, p, n) < 0)
+			return -1;
+	}
+	return Bflush(fp);
+}
+
+/*
+ *  Output the message body with '^From ' escapes.
+ *  Ensures that any line starting with a 'From ' gets a ' ' stuck
+ *  in front of it.
+ */
+static int
+m_escape(message *mp, Biobuf *fp)
+{
+	char *p, *np;
+	char *end;
+	long offset;
+	int m, n;
+	char *start;
+
+	for(offset = 0; offset < mp->size; offset += n){
+		n = m_get(mp, offset, &start);
+		if(n < 0){
+			Bflush(fp);
+			return -1;
+		}
+
+		p = start;
+		for(end = p+n; p < end; p += m){
+			np = memchr(p, '\n', end-p);
+			if(np == 0){
+				Bwrite(fp, p, end-p);
+				break;
+			}
+			m = np - p + 1;
+			if(m > 5 && strncmp(p, "From ", 5) == 0)
+				Bputc(fp, ' ');
+			Bwrite(fp, p, m);
+		}
+	}
+	Bflush(fp);
+	return 0;
+}
+
+static int
+printfrom(message *mp, Biobuf *fp)
+{
+	String *s;
+	int rv;
+
+	if(!returnable(s_to_c(mp->sender)))
+		return Bprint(fp, "From: Postmaster\n");
+
+	s = username(mp->sender);
+	if(s) {
+		s_append(s, " <");
+		s_append(s, s_to_c(mp->sender));
+		s_append(s, ">");
+	} else {
+		s = s_copy(s_to_c(mp->sender));
+	}
+	s = unescapespecial(s);
+	rv = Bprint(fp, "From: %s\n", s_to_c(s));
+	s_free(s);
+	return rv;
+}
+
+static char *
+rewritezone(char *z)
+{
+	int mindiff;
+	char s;
+	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;
+}
+
+int
+isutf8(String *s)
+{
+	char *p;
+	
+	for(p = s_to_c(s);  *p; p++)
+		if(*p&0x80)
+			return 1;
+	return 0;
+}
+
+void
+printutf8mime(Biobuf *b)
+{
+	Bprint(b, "MIME-Version: 1.0\n");
+	Bprint(b, "Content-Type: text/plain; charset=\"UTF-8\"\n");
+	Bprint(b, "Content-Transfer-Encoding: 8bit\n");
+}
+
+/* output a message */
+extern int
+m_print(message *mp, Biobuf *fp, char *remote, int mbox)
+{
+	String *date, *sender;
+	char *f[6];
+	int n;
+
+	sender = unescapespecial(s_clone(mp->sender));
+
+	if (remote != 0){
+		if(print_remote_header(fp,s_to_c(sender),s_to_c(mp->date),remote) < 0){
+			s_free(sender);
+			return -1;
+		}
+	} else {
+		if(print_header(fp, s_to_c(sender), s_to_c(mp->date)) < 0){
+			s_free(sender);
+			return -1;
+		}
+	}
+	s_free(sender);
+	if(!rmail && !mp->havedate){
+		/* add a date: line Date: Sun, 19 Apr 1998 12:27:52 -0400 */
+		date = s_copy(s_to_c(mp->date));
+		n = getfields(s_to_c(date), f, 6, 1, " \t");
+		if(n == 6)
+			Bprint(fp, "Date: %s, %s %s %s %s %s\n", f[0], f[2], f[1],
+			 f[5], f[3], rewritezone(f[4]));
+	}
+	if(!rmail && !mp->havemime && isutf8(mp->body))
+		printutf8mime(fp);
+	if(mp->to){
+		/* add the to: line */
+		if (Bprint(fp, "%s\n", s_to_c(mp->to)) < 0)
+			return -1;
+		/* add the from: line */
+		if (!mp->havefrom && printfrom(mp, fp) < 0)
+			return -1;
+		if(!mp->rfc822headers && *s_to_c(mp->body) != '\n')
+			if (Bprint(fp, "\n") < 0)
+				return -1;
+	} else if(!rmail){
+		/* add the from: line */
+		if (!mp->havefrom && printfrom(mp, fp) < 0)
+			return -1;
+		if(!mp->rfc822headers && *s_to_c(mp->body) != '\n')
+			if (Bprint(fp, "\n") < 0)
+				return -1;
+	}
+
+	if (!mbox)
+		return m_noescape(mp, fp);
+	return m_escape(mp, fp);
+}
+
+/* print just the message body */
+extern int
+m_bprint(message *mp, Biobuf *fp)
+{
+	return m_noescape(mp, fp);
+}
blob - /dev/null
blob + a49fde1762c2aaf323fcda25732192a4f92e7247 (mode 644)
--- /dev/null
+++ src/cmd/upas/send/mkfile
@@ -0,0 +1,52 @@
+<$PLAN9/src/mkhdr
+
+TARG=send\
+	filter
+
+UOFILES=message.$O\
+	dest.$O\
+	log.$O\
+	skipequiv.$O\
+
+OFILES=\
+	$UOFILES\
+	../smtp/rfc822.tab.$O\
+
+SMOBJ=main.$O\
+	bind.$O\
+	rewrite.$O\
+	local.$O\
+	translate.$O\
+	authorize.$O\
+	gateway.$O\
+	cat_mail.$O\
+
+LIB=../common/libcommon.av\
+
+HFILES=send.h\
+	../common/common.h\
+	../common/sys.h\
+
+LIB=../common/libcommon.a\
+
+BIN=$PLAN9/bin/upas
+UPDATE=\
+	mkfile\
+	$HFILES\
+	${UOFILES:%.$O=%.c}\
+	${SMOBJ:%.$O=%.c}\
+	${TARG:%=%.c}\
+
+<$PLAN9/src/mkmany
+CFLAGS=$CFLAGS -I../common
+
+$O.send: $SMOBJ $OFILES
+	$LD $LDFLAGS -o $target $prereq $LIB
+
+message.$O:	../smtp/y.tab.h
+
+../smtp/y.tab.h ../smtp/rfc822.tab.$O: ../smtp/rfc822.y
+#	@{
+		cd ../smtp
+		mk rfc822.tab.$O
+#	}
blob - /dev/null
blob + 52e31258e3c3ae73b4481571e0683b45eb2ffd74 (mode 644)
--- /dev/null
+++ src/cmd/upas/send/regtest.c
@@ -0,0 +1,36 @@
+#include <u.h>
+#include <libc.h>
+#include <regexp.h>
+#include <bio.h>
+
+main(void)
+{
+	char *re;
+	char *line;
+	Reprog *prog;
+	char *cp;
+	Biobuf in;
+
+	Binit(&in, 0, OREAD);
+	print("re> ");
+	while(re = Brdline(&in, '\n')){
+		re[Blinelen(&in)-1] = 0;
+		if(*re == 0)
+			break;
+		prog = regcomp(re);
+		print("> ");
+		while(line = Brdline(&in, '\n')){
+			line[Blinelen(&in)-1] = 0;
+			if(cp = strchr(line, '\n'))
+				*cp = 0;
+			if(*line == 0)
+				break;
+			if(regexec(prog, line, 0))
+				print("yes\n");
+			else
+				print("no\n");
+			print("> ");
+		}
+		print("re> ");
+	}
+}
blob - /dev/null
blob + 4f40b29338b9003991ee364bc2704feff8d202da (mode 644)
--- /dev/null
+++ src/cmd/upas/send/rewrite.c
@@ -0,0 +1,315 @@
+#include "common.h"
+#include "send.h"
+
+extern int debug;
+
+/* 
+ *	Routines for dealing with the rewrite rules.
+ */
+
+/* globals */
+typedef struct rule rule;
+
+#define NSUBEXP 10
+struct rule {
+	String *matchre;	/* address match */
+	String *repl1;		/* first replacement String */
+	String *repl2;		/* second replacement String */
+	d_status type;		/* type of rule */
+	Reprog *program;
+	Resub subexp[NSUBEXP];
+	rule *next;
+};
+static rule *rulep;
+static rule *rlastp;
+
+/* predeclared */
+static String *substitute(String *, Resub *, message *);
+static rule *findrule(String *, int);
+
+
+/*
+ *  Get the next token from `line'.  The symbol `\l' is replaced by
+ *  the name of the local system.
+ */
+extern String *
+rule_parse(String *line, char *system, int *backl)
+{
+	String *token;
+	String *expanded;
+	char *cp;
+
+	token = s_parse(line, 0);
+	if(token == 0)
+		return(token);
+	if(strchr(s_to_c(token), '\\')==0)
+		return(token);
+	expanded = s_new();
+	for(cp = s_to_c(token); *cp; cp++) {
+		if(*cp == '\\') switch(*++cp) {
+		case 'l':
+			s_append(expanded, system);
+			*backl = 1;
+			break;
+		case '\\':
+			s_putc(expanded, '\\');
+			break;
+		default:
+			s_putc(expanded, '\\');
+			s_putc(expanded, *cp);
+			break;
+		} else
+			s_putc(expanded, *cp);
+	}
+	s_free(token);
+	s_terminate(expanded);
+	return(expanded);
+}
+
+static int
+getrule(String *line, String *type, char *system)
+{
+	rule	*rp;
+	String	*re;
+	int	backl;
+
+	backl = 0;
+
+	/* get a rule */
+	re = rule_parse(s_restart(line), system, &backl);
+	if(re == 0)
+		return 0;
+	rp = (rule *)malloc(sizeof(rule));
+	if(rp == 0) {
+		perror("getrules:");
+		exit(1);
+	}
+	rp->next = 0;
+	s_tolower(re);
+	rp->matchre = s_new();
+	s_append(rp->matchre, s_to_c(re));
+	s_restart(rp->matchre);
+	s_free(re);
+	s_parse(line, s_restart(type));
+	rp->repl1 = rule_parse(line, system, &backl);
+	rp->repl2 = rule_parse(line, system, &backl);
+	rp->program = 0;
+	if(strcmp(s_to_c(type), "|") == 0)
+		rp->type = d_pipe;
+	else if(strcmp(s_to_c(type), ">>") == 0)
+		rp->type = d_cat;
+	else if(strcmp(s_to_c(type), "alias") == 0)
+		rp->type = d_alias;
+	else if(strcmp(s_to_c(type), "translate") == 0)
+		rp->type = d_translate;
+	else if(strcmp(s_to_c(type), "auth") == 0)
+		rp->type = d_auth;
+	else {
+		s_free(rp->matchre);
+		s_free(rp->repl1);
+		s_free(rp->repl2);
+		free((char *)rp);
+		fprint(2,"illegal rewrite rule: %s\n", s_to_c(line));
+		return 0;
+	}
+	if(rulep == 0)
+		rulep = rlastp = rp;
+	else
+		rlastp = rlastp->next = rp;
+	return backl;
+}
+
+/*
+ *  rules are of the form:
+ *	<reg exp> <String> <repl exp> [<repl exp>]
+ */
+extern int
+getrules(void)
+{
+	Biobuf	*rfp;
+	String	*line;
+	String	*type;
+	String	*file;
+
+	file = abspath("rewrite", unsharp(UPASLIB), (String *)0);
+	rfp = sysopen(s_to_c(file), "r", 0);
+	if(rfp == 0) {
+		rulep = 0;
+		return -1;
+	}
+	rlastp = 0;
+	line = s_new();
+	type = s_new();
+	while(s_getline(rfp, s_restart(line)))
+		if(getrule(line, type, thissys) && altthissys)
+			getrule(s_restart(line), type, altthissys);
+	s_free(type);
+	s_free(line);
+	s_free(file);
+	sysclose(rfp);
+	return 0;
+}
+
+/* look up a matching rule */
+static rule *
+findrule(String *addrp, int authorized)
+{
+	rule *rp;
+	static rule defaultrule;
+
+	if(rulep == 0)
+		return &defaultrule;
+	for (rp = rulep; rp != 0; rp = rp->next) {
+		if(rp->type==d_auth && authorized)
+			continue;
+		if(rp->program == 0)
+			rp->program = regcomp(rp->matchre->base);
+		if(rp->program == 0)
+			continue;
+		memset(rp->subexp, 0, sizeof(rp->subexp));
+		if(debug)
+			print("matching %s aginst %s\n", s_to_c(addrp), rp->matchre->base);
+		if(regexec(rp->program, s_to_c(addrp), rp->subexp, NSUBEXP))
+		if(s_to_c(addrp) == rp->subexp[0].s.sp)
+		if((s_to_c(addrp) + strlen(s_to_c(addrp))) == rp->subexp[0].e.ep)
+			return rp;
+	}
+	return 0;
+}
+
+/*  Transforms the address into a command.
+ *  Returns:	-1 ifaddress not matched by reules
+ *		 0 ifaddress matched and ok to forward
+ *		 1 ifaddress matched and not ok to forward
+ */
+extern int
+rewrite(dest *dp, message *mp)
+{
+	rule *rp;		/* rewriting rule */
+	String *lower;		/* lower case version of destination */
+
+	/*
+	 *  Rewrite the address.  Matching is case insensitive.
+	 */
+	lower = s_clone(dp->addr);
+	s_tolower(s_restart(lower));
+	rp = findrule(lower, dp->authorized);
+	if(rp == 0){
+		s_free(lower);
+		return -1;
+	}
+	strcpy(s_to_c(lower), s_to_c(dp->addr));
+	dp->repl1 = substitute(rp->repl1, rp->subexp, mp);
+	dp->repl2 = substitute(rp->repl2, rp->subexp, mp);
+	dp->status = rp->type;
+	if(debug){
+		print("\t->");
+		if(dp->repl1)
+			print("%s", s_to_c(dp->repl1));
+		if(dp->repl2)
+			print("%s", s_to_c(dp->repl2));
+		print("\n");
+	}
+	s_free(lower);
+	return 0;
+}
+
+static String *
+substitute(String *source, Resub *subexp, message *mp)
+{
+	int i;
+	char *s;
+	char *sp;
+	String *stp;
+	
+	if(source == 0)
+		return 0;
+	sp = s_to_c(source);
+
+	/* someplace to put it */
+	stp = s_new();
+
+	/* do the substitution */
+	while (*sp != '\0') {
+		if(*sp == '\\') {
+			switch (*++sp) {
+			case '0': case '1': case '2': case '3': case '4':
+			case '5': case '6': case '7': case '8': case '9':
+				i = *sp-'0';
+				if(subexp[i].s.sp != 0)
+					for (s = subexp[i].s.sp;
+					     s < subexp[i].e.ep;
+					     s++)
+						s_putc(stp, *s);
+				break;
+			case '\\':
+				s_putc(stp, '\\');
+				break;
+			case '\0':
+				sp--;
+				break;
+			case 's':
+				for(s = s_to_c(mp->replyaddr); *s; s++)
+					s_putc(stp, *s);
+				break;
+			case 'p':
+				if(mp->bulk)
+					s = "bulk";
+				else
+					s = "normal";
+				for(;*s; s++)
+					s_putc(stp, *s);
+				break;
+			default:
+				s_putc(stp, *sp);
+				break;
+			}
+		} else if(*sp == '&') {				
+			if(subexp[0].s.sp != 0)
+				for (s = subexp[0].s.sp;
+				     s < subexp[0].e.ep; s++)
+					s_putc(stp, *s);
+		} else
+			s_putc(stp, *sp);
+		sp++;
+	}
+	s_terminate(stp);
+
+	return s_restart(stp);
+}
+
+extern void
+regerror(char* s)
+{
+	fprint(2, "rewrite: %s\n", s);
+}
+
+extern void
+dumprules(void)
+{
+	rule *rp;
+
+	for (rp = rulep; rp != 0; rp = rp->next) {
+		fprint(2, "'%s'", rp->matchre->base);
+		switch (rp->type) {
+		case d_pipe:
+			fprint(2, " |");
+			break;
+		case d_cat:
+			fprint(2, " >>");
+			break;
+		case d_alias:
+			fprint(2, " alias");
+			break;
+		case d_translate:
+			fprint(2, " translate");
+			break;
+		default:
+			fprint(2, " UNKNOWN");
+			break;
+		}
+		fprint(2, " '%s'", rp->repl1 ? rp->repl1->base:"...");
+		fprint(2, " '%s'\n", rp->repl2 ? rp->repl2->base:"...");
+	}
+}
+
blob - /dev/null
blob + 6caa37b25c0b646ac15c7453dac47f39c2b78513 (mode 644)
--- /dev/null
+++ src/cmd/upas/send/send.h
@@ -0,0 +1,108 @@
+#define MAXSAME 16
+#define MAXSAMECHAR 1024
+
+/* status of a destination*/
+typedef enum {
+	d_undefined,	/* address has not been matched*/
+	d_pipe,		/* repl1|repl2 == delivery command, rep*/
+	d_cat,		/* repl1 == mail file */
+	d_translate,	/* repl1 == translation command*/
+	d_alias,	/* repl1 == translation*/
+	d_auth,		/* repl1 == command to authorize*/
+	d_syntax,	/* addr contains illegal characters*/
+	d_unknown,	/* addr does not match a rewrite rule*/
+	d_loop,		/* addressing loop*/
+	d_eloop,	/* external addressing loop*/
+	d_noforward,	/* forwarding not allowed*/
+	d_badmbox,	/* mailbox badly formatted*/
+	d_resource,	/* ran out of something we needed*/
+	d_pipeto,	/* pipe to from a mailbox*/
+} d_status;
+
+/* a destination*/
+typedef struct dest dest;
+struct dest {
+	dest	*next;		/* for chaining*/
+	dest	*same;		/* dests with same cmd*/
+	dest	*parent;	/* destination we're a translation of*/
+	String	*addr;		/* destination address*/
+	String	*repl1;		/* substitution field 1*/
+	String	*repl2;		/* substitution field 2*/
+	int	pstat;		/* process status*/
+	d_status status;	/* delivery status*/
+	int	authorized;	/* non-zero if we have been authorized*/
+	int	nsame;		/* number of same dests chained to this entry*/
+	int	nchar;		/* number of characters in the command*/
+};
+
+typedef struct message message;
+struct message {
+	String	*sender;
+	String	*replyaddr;
+	String	*date;
+	String	*body;
+	String	*tmp;		/* name of temp file */
+	String	*to;
+	int	size;
+	int	fd;		/* if >= 0, the file the message is stored in*/
+	char	haveto;
+	String	*havefrom;
+	String	*havesender;
+	String	*havereplyto;
+	char	havedate;
+	char	havemime;
+	String	*havesubject;
+	char	bulk;		/* if Precedence: Bulk in header */
+	char	rfc822headers;
+	int	received;	/* number of received lines */
+	char	*boundary;	/* bondary marker for attachments */
+};
+
+/*
+ *  exported variables
+ */
+extern int rmail;
+extern int onatty;
+extern char *thissys, *altthissys;
+extern int xflg;
+extern int nflg;
+extern int tflg;
+extern int debug;
+extern int nosummary;
+
+/*
+ *  exported procedures
+ */
+extern void	authorize(dest*);
+extern int	cat_mail(dest*, message*);
+extern dest	*up_bind(dest*, message*, int);
+extern int	ok_to_forward(char*);
+extern int	lookup(char*, char*, Biobuf**, char*, Biobuf**);
+extern dest	*d_new(String*);
+extern void	d_free(dest*);
+extern dest	*d_rm(dest**);
+extern void	d_insert(dest**, dest*);
+extern dest	*d_rm_same(dest**);
+extern void	d_same_insert(dest**, dest*);
+extern String	*d_to(dest*);
+extern dest	*s_to_dest(String*, dest*);
+extern void	gateway(message*);
+extern dest	*expand_local(dest*);
+extern void	logdelivery(dest*, char*, message*);
+extern void	loglist(dest*, message*, char*);
+extern void	logrefusal(dest*, message*, char*);
+extern int	default_from(message*);
+extern message	*m_new(void);
+extern void	m_free(message*);
+extern message	*m_read(Biobuf*, int, int);
+extern int	m_get(message*, long, char**);
+extern int	m_print(message*, Biobuf*, char*, int);
+extern int	m_bprint(message*, Biobuf*);
+extern String	*rule_parse(String*, char*, int*);
+extern int	getrules(void);
+extern int	rewrite(dest*, message*);
+extern void	dumprules(void);
+extern void	regerror(char*);
+extern dest	*translate(dest*);
+extern char*	skipequiv(char*);
+extern int	refuse(dest*, message*, char*, int, int);
blob - /dev/null
blob + f40181ad58d56b659bcb3a19657c752d9cf711ac (mode 644)
--- /dev/null
+++ src/cmd/upas/send/skipequiv.c
@@ -0,0 +1,93 @@
+#include "common.h"
+#include "send.h"
+
+#undef isspace
+#define isspace(c) ((c)==' ' || (c)=='\t' || (c)=='\n')
+
+/*
+ *  skip past all systems in equivlist
+ */
+extern char*
+skipequiv(char *base)
+{
+	char *sp;
+	static Biobuf *fp;
+
+	while(*base){
+		sp = strchr(base, '!');
+		if(sp==0)
+			break;
+		*sp = '\0';
+		if(lookup(base, "equivlist", &fp, 0, 0)==1){
+			/* found or us, forget this system */
+			*sp='!';
+			base=sp+1;
+		} else {
+			/* no files or system is not found, and not us */
+			*sp='!';
+			break;
+		}
+	}
+	return base;
+}
+
+static int
+okfile(char *cp, Biobuf *fp)
+{
+	char *buf;
+	int len;
+	char *bp, *ep;
+	int c;
+
+	len = strlen(cp);
+	Bseek(fp, 0, 0);
+	
+	/* one iteration per system name in the file */
+	while(buf = Brdline(fp, '\n')) {
+		ep = &buf[Blinelen(fp)];
+		for(bp=buf; bp < ep;){
+			while(isspace(*bp) || *bp==',')
+				bp++;
+			if(strncmp(bp, cp, len) == 0) {
+				c = *(bp+len);
+				if(isspace(c) || c==',')
+					return 1;
+			}
+			while(bp < ep && (!isspace(*bp)) && *bp!=',')
+				bp++;
+		}
+	}
+
+	/* didn't find it, prohibit forwarding */
+	return 0;
+}
+
+/* return 1 if name found in one of the files
+ *	  0 if name not found in one of the files
+ *	  -1 if neither file exists
+ */
+extern int
+lookup(char *cp, char *local, Biobuf **lfpp, char *global, Biobuf **gfpp)
+{
+	static String *file = 0;
+
+	if (local) {
+		if (file == 0)
+			file = s_new();
+		abspath(local, UPASLIB, s_restart(file));
+		if (*lfpp != 0 || (*lfpp = sysopen(s_to_c(file), "r", 0)) != 0) {
+			if (okfile(cp, *lfpp))
+				return 1;
+		} else
+			local = 0;
+	}
+	if (global) {
+		abspath(global, UPASLIB, s_restart(file));
+		if (*gfpp != 0 || (*gfpp = sysopen(s_to_c(file), "r", 0)) != 0) {
+			if (okfile(cp, *gfpp))
+				return 1;
+		} else
+			global = 0;
+	}
+	return (local || global)? 0 : -1;
+}
blob - /dev/null
blob + 0332659c0af00c7444c87c53e6318b6492b0ba6d (mode 644)
--- /dev/null
+++ src/cmd/upas/send/translate.c
@@ -0,0 +1,43 @@
+#include "common.h"
+#include "send.h"
+
+/* pipe an address through a command to translate it */
+extern dest *
+translate(dest *dp)
+{
+	process *pp;
+	String *line;
+	dest *rv;
+	char *cp;
+	int n;
+
+	pp = proc_start(s_to_c(dp->repl1), (stream *)0, outstream(), outstream(), 1, 0);
+	if (pp == 0) {
+		dp->status = d_resource;
+		return 0;
+	}
+	line = s_new();
+	for(;;) {
+		cp = Brdline(pp->std[1]->fp, '\n');
+		if(cp == 0)
+			break;
+		if(strncmp(cp, "_nosummary_", 11) == 0){
+			nosummary = 1;
+			continue;
+		}
+		n = Blinelen(pp->std[1]->fp);
+		cp[n-1] = ' ';
+		s_nappend(line, cp, n);
+	}
+	rv = s_to_dest(s_restart(line), dp);
+	s_restart(line);
+	while(s_read_line(pp->std[2]->fp, line))
+		;
+	if ((dp->pstat = proc_wait(pp)) != 0) {
+		dp->repl2 = line;
+		rv = 0;
+	} else
+		s_free(line);
+	proc_free(pp);
+	return rv;
+}
blob - /dev/null
blob + fed3a2a60fe4b6d068773abe4366fb70371f6155 (mode 644)
--- /dev/null
+++ src/cmd/upas/send/tryit
@@ -0,0 +1,29 @@
+#!/bin/sh
+set -x
+
+> /usr/spool/mail/test.local
+echo "Forward to test.local" > /usr/spool/mail/test.forward
+echo "Pipe to cat > /tmp/test.mail" > /usr/spool/mail/test.pipe
+chmod 644 /usr/spool/mail/test.pipe
+
+mail test.local <<EOF
+mailed to test.local
+EOF
+mail test.forward <<EOF
+mailed to test.forward
+EOF
+mail test.pipe <<EOF
+mailed to test.pipe
+EOF
+mail dutoit!bowell!test.local <<EOF
+mailed to dutoit!bowell!test.local
+EOF
+
+sleep 60
+
+ls -l /usr/spool/mail/test.*
+ls -l /tmp/test.mail
+echo ">>>test.local<<<"
+cat /usr/spool/mail/test.local
+echo ">>>test.mail<<<"
+cat /tmp/test.mail
blob - /dev/null
blob + 915e688e3c5deb94b40dbc346901d743bee9cdb3 (mode 644)
--- /dev/null
+++ src/cmd/upas/smtp/greylist.c
@@ -0,0 +1,274 @@
+#include "common.h"
+#include "smtpd.h"
+#include "smtp.h"
+#include <ctype.h>
+#include <ip.h>
+#include <ndb.h>
+
+typedef struct {
+	int	existed;	/* these two are distinct to cope with errors */
+	int	created;
+	int	noperm;
+	long	mtime;		/* mod time, iff it already existed */
+} Greysts;
+
+/*
+ * There's a bit of a problem with yahoo; they apparently have a vast
+ * pool of machines that all run the same queue(s), so a 451 retry can
+ * come from a different IP address for many, many retries, and it can
+ * take ~5 hours for the same IP to call us back.  Various other goofballs,
+ * notably the IEEE, try to send mail just before 9 AM, then refuse to try
+ * again until after 5 PM.  Doh!
+ */
+enum {
+	Nonspammax = 14*60*60,  /* must call back within this time if real */
+};
+static char whitelist[] = "/mail/lib/whitelist";
+
+/*
+ * matches ip addresses or subnets in whitelist against nci->rsys.
+ * ignores comments and blank lines in /mail/lib/whitelist.
+ */
+static int
+onwhitelist(void)
+{
+	int lnlen;
+	char *line, *parse;
+	char input[128];
+	uchar ip[IPaddrlen], ipmasked[IPaddrlen];
+	uchar mask4[IPaddrlen], addr4[IPaddrlen];
+	uchar mask[IPaddrlen], addr[IPaddrlen], addrmasked[IPaddrlen];
+	Biobuf *wl;
+	static int beenhere;
+	static allzero[IPaddrlen];
+
+	if (!beenhere) {
+		beenhere = 1;
+		fmtinstall('I', eipfmt);
+	}
+
+	parseip(ip, nci->rsys);
+	wl = Bopen(whitelist, OREAD);
+	if (wl == nil)
+		return 1;
+	while ((line = Brdline(wl, '\n')) != nil) {
+		if (line[0] == '#' || line[0] == '\n')
+			continue;
+		lnlen = Blinelen(wl);
+		line[lnlen-1] = '\0';		/* clobber newline */
+
+		/* default mask is /32 (v4) or /128 (v6) for bare IP */
+		parse = line;
+		if (strchr(line, '/') == nil) {
+			strncpy(input, line, sizeof input - 5);
+			if (strchr(line, '.') != nil)
+				strcat(input, "/32");
+			else
+				strcat(input, "/128");
+			parse = input;
+		}
+		/* sorry, dave; where's parsecidr for v4 or v6? */
+		v4parsecidr(addr4, mask4, parse);
+		v4tov6(addr, addr4);
+		v4tov6(mask, mask4);
+
+		maskip(addr, mask, addrmasked);
+		maskip(ip, mask, ipmasked);
+		if (memcmp(ipmasked, addrmasked, IPaddrlen) == 0)
+			break;
+	}
+	Bterm(wl);
+	return line != nil;
+}
+
+static int mkdirs(char *);
+
+/*
+ * if any directories leading up to path don't exist, create them.
+ * modifies but restores path.
+ */
+static int
+mkpdirs(char *path)
+{
+	int rv = 0;
+	char *sl = strrchr(path, '/');
+
+	if (sl != nil) {
+		*sl = '\0';
+		rv = mkdirs(path);
+		*sl = '/';
+	}
+	return rv;
+}
+
+/*
+ * if path or any directories leading up to it don't exist, create them.
+ * modifies but restores path.
+ */
+static int
+mkdirs(char *path)
+{
+	int fd;
+
+	if (access(path, AEXIST) >= 0)
+		return 0;
+
+	/* make presumed-missing intermediate directories */
+	if (mkpdirs(path) < 0)
+		return -1;
+
+	/* make final directory */
+	fd = create(path, OREAD, 0777|DMDIR);
+	if (fd < 0)
+		/*
+		 * we may have lost a race; if the directory now exists,
+		 * it's okay.
+		 */
+		return access(path, AEXIST) < 0? -1: 0;
+	close(fd);
+	return 0;
+}
+
+static long
+getmtime(char *file)
+{
+	long mtime = -1;
+	Dir *ds = dirstat(file);
+
+	if (ds != nil) {
+		mtime = ds->mtime;
+		free(ds);
+	}
+	return mtime;
+}
+
+static void
+tryaddgrey(char *file, Greysts *gsp)
+{
+	int fd = create(file, OWRITE|OEXCL, 0444|DMEXCL);
+
+	gsp->created = (fd >= 0);
+	if (fd >= 0) {
+		close(fd);
+		gsp->existed = 0;  /* just created; couldn't have existed */
+	} else {
+		/*
+		 * why couldn't we create file? it must have existed
+		 * (or we were denied perm on parent dir.).
+		 * if it existed, fill in gsp->mtime; otherwise
+		 * make presumed-missing intermediate directories.
+		 */
+		gsp->existed = access(file, AEXIST) >= 0;
+		if (gsp->existed)
+			gsp->mtime = getmtime(file);
+		else if (mkpdirs(file) < 0)
+			gsp->noperm = 1;
+	}
+}
+
+static void
+addgreylist(char *file, Greysts *gsp)
+{
+	tryaddgrey(file, gsp);
+	if (!gsp->created && !gsp->existed && !gsp->noperm)
+		/* retry the greylist entry with parent dirs created */
+		tryaddgrey(file, gsp);
+}
+
+static int
+recentcall(Greysts *gsp)
+{
+	long delay = time(0) - gsp->mtime;
+
+	if (!gsp->existed)
+		return 0;
+	/* reject immediate call-back; spammers are doing that now */
+	return delay >= 30 && delay <= Nonspammax;
+}
+
+/*
+ * policy: if (caller-IP, my-IP, rcpt) is not on the greylist,
+ * reject this message as "451 temporary failure".  if the caller is real,
+ * he'll retry soon, otherwise he's a spammer.
+ * at the first rejection, create a greylist entry for (my-ip, caller-ip,
+ * rcpt, time), where time is the file's mtime.  if they call back and there's
+ * already a greylist entry, and it's within the allowed interval,
+ * add their IP to the append-only whitelist.
+ *
+ * greylist files can be removed at will; at worst they'll cause a few
+ * extra retries.
+ */
+
+static int
+isrcptrecent(char *rcpt)
+{
+	char *user;
+	char file[256];
+	Greysts gs;
+	Greysts *gsp = &gs;
+
+	if (rcpt[0] == '\0' || strchr(rcpt, '/') != nil ||
+	    strcmp(rcpt, ".") == 0 || strcmp(rcpt, "..") == 0)
+		return 0;
+
+	/* shorten names to fit pre-fossil or pre-9p2000 file servers */
+	user = strrchr(rcpt, '!');
+	if (user == nil)
+		user = rcpt;
+	else
+		user++;
+
+	/* check & try to update the grey list entry */
+	snprint(file, sizeof file, "/mail/grey/%s/%s/%s",
+		nci->lsys, nci->rsys, user);
+	memset(gsp, 0, sizeof *gsp);
+	addgreylist(file, gsp);
+
+	/* if on greylist already and prior call was recent, add to whitelist */
+	if (gsp->existed && recentcall(gsp)) {
+		syslog(0, "smtpd",
+			"%s/%s was grey; adding IP to white", nci->rsys, rcpt);
+		return 1;
+	} else if (gsp->existed)
+		syslog(0, "smtpd", "call for %s/%s was seconds ago or long ago",
+			nci->rsys, rcpt);
+	else
+		syslog(0, "smtpd", "no call registered for %s/%s; registering",
+			nci->rsys, rcpt);
+	return 0;
+}
+
+void
+vfysenderhostok(void)
+{
+	char *fqdn;
+	int recent = 0;
+	Link *l;
+
+	if (onwhitelist())
+		return;
+
+	for (l = rcvers.first; l; l = l->next)
+		if (isrcptrecent(s_to_c(l->p)))
+			recent = 1;
+
+	/* if on greylist already and prior call was recent, add to whitelist */
+	if (recent) {
+		int fd = create(whitelist, OWRITE, 0666|DMAPPEND);
+
+		if (fd >= 0) {
+			seek(fd, 0, 2);			/* paranoia */
+			if ((fqdn = csgetvalue(nil, "ip", nci->rsys, "dom", nil)) != nil)
+				fprint(fd, "# %s\n%s\n\n", fqdn, nci->rsys);
+			else
+				fprint(fd, "# unknown\n%s\n\n", nci->rsys);
+			close(fd);
+		}
+	} else {
+		syslog(0, "smtpd",
+	"no recent call from %s for a rcpt; rejecting with temporary failure",
+			nci->rsys);
+		reply("451 please try again soon from the same IP.\r\n");
+		exits("no recent call for a rcpt");
+	}
+}
blob - /dev/null
blob + 722f1357b364678cf8dd2920c1e2a1ec7f52a53b (mode 644)
--- /dev/null
+++ src/cmd/upas/smtp/mkfile
@@ -0,0 +1,54 @@
+<$PLAN9/src/mkhdr
+
+TARG = # smtpd\
+	smtp\
+
+OFILES=
+
+LIB=../common/libcommon.a\
+	$PLAN9/lib/libthread.a   # why do i have to explicitly put this?
+
+HFILES=../common/common.h\
+	../common/sys.h\
+	smtpd.h\
+	smtp.h\
+
+BIN=$PLAN9/bin/upas
+UPDATE=\
+	greylist.c\
+	mkfile\
+	mxdial.c\
+	rfc822.y\
+	rmtdns.c\
+	smtpd.y\
+	spam.c\
+	$HFILES\
+	${OFILES:%.$O=%.c}\
+	${TARG:%=%.c}\
+
+<$PLAN9/src/mkmany
+CFLAGS=$CFLAGS -I../common -D'SPOOL="/mail"'
+
+$O.smtpd:	smtpd.tab.$O rmtdns.$O spam.$O rfc822.tab.$O greylist.$O
+$O.smtp:	rfc822.tab.$O mxdial.$O
+
+smtpd.$O: 	smtpd.h
+
+smtp.$O to.$O: 	smtp.h
+
+smtpd.tab.c: smtpd.y smtpd.h
+	yacc -o xxx smtpd.y
+	sed 's/yy/zz/g' < xxx > $target
+	rm xxx
+
+rfc822.tab.c: rfc822.y smtp.h
+	9 yacc -d -o $target rfc822.y
+
+clean:V:
+	rm -f *.[$OS] [$OS].$TARG smtpd.tab.c rfc822.tab.c y.tab.? y.debug $TARG
+
+../common/libcommon.a$O:
+	@{ 	
+		cd ../common
+		mk
+	}
blob - /dev/null
blob + aea5f2566f6e6992bec36461dc60fe655cf7c7d9 (mode 644)
--- /dev/null
+++ src/cmd/upas/smtp/mxdial.c
@@ -0,0 +1,333 @@
+#include "common.h"
+#include <ndb.h>
+#include "smtp.h"	/* to publish dial_string_parse */
+
+enum
+{
+	Nmx=	16,
+	Maxstring=	256,
+};
+
+typedef struct Mx	Mx;
+struct Mx
+{
+	char host[256];
+	char ip[24];
+	int pref;
+};
+static Mx mx[Nmx];
+
+Ndb *db;
+extern int debug;
+
+static int	mxlookup(DS*, char*);
+static int	mxlookup1(DS*, char*);
+static int	compar(void*, void*);
+static int	callmx(DS*, char*, char*);
+static void expand_meta(DS *ds);
+extern int	cistrcmp(char*, char*);
+
+int
+mxdial(char *addr, char *ddomain, char *gdomain)
+{
+	int fd;
+	DS ds;
+	char err[Errlen];
+
+	addr = netmkaddr(addr, 0, "smtp");
+	dial_string_parse(addr, &ds);
+
+	/* try connecting to destination or any of it's mail routers */
+	fd = callmx(&ds, addr, ddomain);
+
+	/* try our mail gateway */
+	rerrstr(err, sizeof(err));
+	if(fd < 0 && gdomain && strstr(err, "can't translate") != 0) {
+		fprint(2,"dialing %s\n",gdomain);
+		fd = dial(netmkaddr(gdomain, 0, "smtp"), 0, 0, 0);
+	}
+
+	return fd;
+}
+
+/*
+ *  take an address and return all the mx entries for it,
+ *  most preferred first
+ */
+static int
+callmx(DS *ds, char *dest, char *domain)
+{
+	int fd, i, nmx;
+	char addr[Maxstring];
+
+	/* get a list of mx entries */
+	nmx = mxlookup(ds, domain);
+	if(nmx < 0){
+		/* dns isn't working, don't just dial */
+		return -1;
+	}
+	if(nmx == 0){
+		if(debug)
+			fprint(2, "mxlookup returns nothing\n");
+		return dial(dest, 0, 0, 0);
+	}
+
+	/* refuse to honor loopback addresses given by dns */
+	for(i = 0; i < nmx; i++){
+		if(strcmp(mx[i].ip, "127.0.0.1") == 0){
+			if(debug)
+				fprint(2, "mxlookup returns loopback\n");
+			werrstr("illegal: domain lists 127.0.0.1 as mail server");
+			return -1;
+		}
+	}
+
+	/* sort by preference */
+	if(nmx > 1)
+		qsort(mx, nmx, sizeof(Mx), compar);
+
+	/* dial each one in turn */
+	for(i = 0; i < nmx; i++){
+		snprint(addr, sizeof(addr), "%s/%s!%s!%s", ds->netdir, ds->proto,
+			mx[i].host, ds->service);
+		if(debug)
+			fprint(2, "mxdial trying %s\n", addr);
+		fd = dial(addr, 0, 0, 0);
+		if(fd >= 0)
+			return fd;
+	}
+	return -1;
+}
+
+/*
+ *  call the dns process and have it try to resolve the mx request
+ *
+ *  this routine knows about the firewall and tries inside and outside
+ *  dns's seperately.
+ */
+static int
+mxlookup(DS *ds, char *domain)
+{
+	int n;
+
+	/* just in case we find no domain name */
+	strcpy(domain, ds->host);
+
+	if(ds->netdir){
+		n = mxlookup1(ds, domain);
+	} else {
+		ds->netdir = "/net";
+		n = mxlookup1(ds, domain);
+		if(n == 0) {
+			ds->netdir = "/net.alt";
+			n = mxlookup1(ds, domain);
+		}
+	}
+
+	return n;
+}
+
+static int
+mxlookup1(DS *ds, char *domain)
+{
+	char buf[1024];
+	char dnsname[Maxstring];
+	char *fields[4];
+	int i, n, fd, nmx;
+
+	snprint(dnsname, sizeof dnsname, "%s/dns", ds->netdir);
+
+	fd = open(dnsname, ORDWR);
+	if(fd < 0)
+		return 0;
+
+	nmx = 0;
+	snprint(buf, sizeof(buf), "%s mx", ds->host);
+	if(debug)
+		fprint(2, "sending %s '%s'\n", dnsname, buf);
+	n = write(fd, buf, strlen(buf));
+	if(n < 0){
+		rerrstr(buf, sizeof buf);
+		if(debug)
+			fprint(2, "dns: %s\n", buf);
+		if(strstr(buf, "dns failure")){
+			/* if dns fails for the mx lookup, we have to stop */
+			close(fd);
+			return -1;
+		}
+	} else {
+		/*
+		 *  get any mx entries
+		 */
+		seek(fd, 0, 0);
+		while(nmx < Nmx && (n = read(fd, buf, sizeof(buf)-1)) > 0){
+			buf[n] = 0;
+			if(debug)
+				fprint(2, "dns mx: %s\n", buf);
+			n = getfields(buf, fields, 4, 1, " \t");
+			if(n < 4)
+				continue;
+
+			if(strchr(domain, '.') == 0)
+				strcpy(domain, fields[0]);
+
+			strncpy(mx[nmx].host, fields[3], sizeof(mx[n].host)-1);
+			mx[nmx].pref = atoi(fields[2]);
+			nmx++;
+		}
+		if(debug)
+			fprint(2, "dns mx; got %d entries\n", nmx);
+	}
+
+	/*
+	 * no mx record? try name itself.
+	 */
+	/*
+	 * BUG? If domain has no dots, then we used to look up ds->host
+	 * but return domain instead of ds->host in the list.  Now we return
+	 * ds->host.  What will this break?
+	 */
+	if(nmx == 0){
+		mx[0].pref = 1;
+		strncpy(mx[0].host, ds->host, sizeof(mx[0].host));
+		nmx++;
+	}
+
+	/*
+	 * look up all ip addresses
+	 */
+	for(i = 0; i < nmx; i++){
+		seek(fd, 0, 0);
+		snprint(buf, sizeof buf, "%s ip", mx[i].host);
+		mx[i].ip[0] = 0;
+		if(write(fd, buf, strlen(buf)) < 0)
+			goto no;
+		seek(fd, 0, 0);
+		if((n = read(fd, buf, sizeof buf-1)) < 0)
+			goto no;
+		buf[n] = 0;
+		if(getfields(buf, fields, 4, 1, " \t") < 3)
+			goto no;
+		strncpy(mx[i].ip, fields[2], sizeof(mx[i].ip)-1);
+		continue;
+
+	no:
+		/* remove mx[i] and go around again */
+		nmx--;
+		mx[i] = mx[nmx];
+		i--;
+	}
+	return nmx;		
+}
+
+static int
+compar(void *a, void *b)
+{
+	return ((Mx*)a)->pref - ((Mx*)b)->pref;
+}
+
+/* break up an address to its component parts */
+void
+dial_string_parse(char *str, DS *ds)
+{
+	char *p, *p2;
+
+	strncpy(ds->buf, str, sizeof(ds->buf));
+	ds->buf[sizeof(ds->buf)-1] = 0;
+
+	p = strchr(ds->buf, '!');
+	if(p == 0) {
+		ds->netdir = 0;
+		ds->proto = "net";
+		ds->host = ds->buf;
+	} else {
+		if(*ds->buf != '/'){
+			ds->netdir = 0;
+			ds->proto = ds->buf;
+		} else {
+			for(p2 = p; *p2 != '/'; p2--)
+				;
+			*p2++ = 0;
+			ds->netdir = ds->buf;
+			ds->proto = p2;
+		}
+		*p = 0;
+		ds->host = p + 1;
+	}
+	ds->service = strchr(ds->host, '!');
+	if(ds->service)
+		*ds->service++ = 0;
+	if(*ds->host == '$')
+		expand_meta(ds);
+}
+
+#if 0 /* jpc */
+static void
+expand_meta(DS *ds)
+{
+	char buf[128], cs[128], *net, *p;
+	int fd, n;
+
+	net = ds->netdir;
+	if(!net)
+		net = "/net";
+
+	if(debug)
+		fprint(2, "expanding %s!%s\n", net, ds->host);
+	snprint(cs, sizeof(cs), "%s/cs", net);
+	if((fd = open(cs, ORDWR)) == -1){
+		if(debug)
+			fprint(2, "open %s: %r\n", cs);
+		syslog(0, "smtp", "cannot open %s: %r", cs);
+		return;
+	}
+
+	snprint(buf, sizeof(buf), "!ipinfo %s", ds->host+1);	// +1 to skip $
+	if(write(fd, buf, strlen(buf)) <= 0){
+		if(debug)
+			fprint(2, "write %s: %r\n", cs);
+		syslog(0, "smtp", "%s to %s - write failed: %r", buf, cs);
+		close(fd);
+		return;
+	}
+
+	seek(fd, 0, 0);
+	if((n = read(fd, ds->expand, sizeof(ds->expand)-1)) < 0){
+		if(debug)
+			fprint(2, "read %s: %r\n", cs);
+		syslog(0, "smtp", "%s - read failed: %r", cs);
+		close(fd);
+		return;
+	}
+	close(fd);
+
+	ds->expand[n] = 0;
+	if((p = strchr(ds->expand, '=')) == nil){
+		if(debug)
+			fprint(2, "response %s: %s\n", cs, ds->expand);
+		syslog(0, "smtp", "%q from %s - bad response: %r", ds->expand, cs);
+		return;
+	}
+	ds->host = p+1;
+
+	/* take only first one returned (quasi-bug) */
+	if((p = strchr(ds->host, ' ')) != nil)
+		*p = 0;
+}
+#endif /* jpc */
+
+static void
+expand_meta(DS *ds)
+{
+	Ndb *db;
+	Ndbs s;
+	char *sys, *smtpserver;
+
+	sys = sysname();
+	db = ndbopen(unsharp("#9/ndb/local"));
+	fprint(2,"%s",ds->host);
+	smtpserver = ndbgetvalue(db, &s, "sys", sys, "smtp", nil);
+	snprint(ds->host,128,"%s",smtpserver);
+	fprint(2," exanded to %s\n",ds->host);
+
+}
blob - /dev/null
blob + 1f12e48e78c6f7058b874912ecfa6780e808290b (mode 644)
--- /dev/null
+++ src/cmd/upas/smtp/rfc822.tab.c
@@ -0,0 +1,1260 @@
+
+#line	2	"/usr/local/plan9/src/cmd/upas/smtp/rfc822.y"
+#include "common.h"
+#include "smtp.h"
+#include <ctype.h>
+
+char	*yylp;		/* next character to be lex'd */
+int	yydone;		/* tell yylex to give up */
+char	*yybuffer;	/* first parsed character */
+char	*yyend;		/* end of buffer to be parsed */
+Node	*root;
+Field	*firstfield;
+Field	*lastfield;
+Node	*usender;
+Node	*usys;
+Node	*udate;
+char	*startfield, *endfield;
+int	originator;
+int	destination;
+int	date;
+int	received;
+int	messageid;
+extern	int	yyerrflag;
+#ifndef	YYMAXDEPTH
+#define	YYMAXDEPTH	150
+#endif
+#ifndef	YYSTYPE
+#define	YYSTYPE	int
+#endif
+YYSTYPE	yylval;
+YYSTYPE	yyval;
+#define	WORD	57346
+#define	DATE	57347
+#define	RESENT_DATE	57348
+#define	RETURN_PATH	57349
+#define	FROM	57350
+#define	SENDER	57351
+#define	REPLY_TO	57352
+#define	RESENT_FROM	57353
+#define	RESENT_SENDER	57354
+#define	RESENT_REPLY_TO	57355
+#define	SUBJECT	57356
+#define	TO	57357
+#define	CC	57358
+#define	BCC	57359
+#define	RESENT_TO	57360
+#define	RESENT_CC	57361
+#define	RESENT_BCC	57362
+#define	REMOTE	57363
+#define	PRECEDENCE	57364
+#define	MIMEVERSION	57365
+#define	CONTENTTYPE	57366
+#define	MESSAGEID	57367
+#define	RECEIVED	57368
+#define	MAILER	57369
+#define	BADTOKEN	57370
+#define YYEOFCODE 1
+#define YYERRCODE 2
+
+#line	246	"/usr/local/plan9/src/cmd/upas/smtp/rfc822.y"
+
+
+/*
+ *  Initialize the parsing.  Done once for each header field.
+ */
+void
+yyinit(char *p, int len)
+{
+	yybuffer = p;
+	yylp = p;
+	yyend = p + len;
+	firstfield = lastfield = 0;
+	received = 0;
+}
+
+/*
+ *  keywords identifying header fields we care about
+ */
+typedef struct Keyword	Keyword;
+struct Keyword {
+	char	*rep;
+	int	val;
+};
+
+/* field names that we need to recognize */
+Keyword key[] = {
+	{ "date", DATE },
+	{ "resent-date", RESENT_DATE },
+	{ "return_path", RETURN_PATH },
+	{ "from", FROM },
+	{ "sender", SENDER },
+	{ "reply-to", REPLY_TO },
+	{ "resent-from", RESENT_FROM },
+	{ "resent-sender", RESENT_SENDER },
+	{ "resent-reply-to", RESENT_REPLY_TO },
+	{ "to", TO },
+	{ "cc", CC },
+	{ "bcc", BCC },
+	{ "resent-to", RESENT_TO },
+	{ "resent-cc", RESENT_CC },
+	{ "resent-bcc", RESENT_BCC },
+	{ "remote", REMOTE },
+	{ "subject", SUBJECT },
+	{ "precedence", PRECEDENCE },
+	{ "mime-version", MIMEVERSION },
+	{ "content-type", CONTENTTYPE },
+	{ "message-id", MESSAGEID },
+	{ "received", RECEIVED },
+	{ "mailer", MAILER },
+	{ "who-the-hell-cares", WORD }
+};
+
+/*
+ *  Lexical analysis for an rfc822 header field.  Continuation lines
+ *  are handled in yywhite() when skipping over white space.
+ *
+ */
+int
+yylex(void)
+{
+	String *t;
+	int quoting;
+	int escaping;
+	char *start;
+	Keyword *kp;
+	int c, d;
+
+/*	print("lexing\n"); /**/
+	if(yylp >= yyend)
+		return 0;
+	if(yydone)
+		return 0;
+
+	quoting = escaping = 0;
+	start = yylp;
+	yylval = malloc(sizeof(Node));
+	yylval->white = yylval->s = 0;
+	yylval->next = 0;
+	yylval->addr = 0;
+	yylval->start = yylp;
+	for(t = 0; yylp < yyend; yylp++){
+		c = *yylp & 0xff;
+
+		/* dump nulls, they can't be in header */
+		if(c == 0)
+			continue;
+
+		if(escaping) {
+			escaping = 0;
+		} else if(quoting) {
+			switch(c){
+			case '\\':
+				escaping = 1;
+				break;
+			case '\n':
+				d = (*(yylp+1))&0xff;
+				if(d != ' ' && d != '\t'){
+					quoting = 0;
+					yylp--;
+					continue;
+				}
+				break;
+			case '"':
+				quoting = 0;
+				break;
+			}
+		} else {
+			switch(c){
+			case '\\':
+				escaping = 1;
+				break;
+			case '(':
+			case ' ':
+			case '\t':
+			case '\r':
+				goto out;
+			case '\n':
+				if(yylp == start){
+					yylp++;
+/*					print("lex(c %c)\n", c); /**/
+					yylval->end = yylp;
+					return yylval->c = c;
+				}
+				goto out;
+			case '@':
+			case '>':
+			case '<':
+			case ':':
+			case ',':
+			case ';':
+				if(yylp == start){
+					yylp++;
+					yylval->white = yywhite();
+/*					print("lex(c %c)\n", c); /**/
+					yylval->end = yylp;
+					return yylval->c = c;
+				}
+				goto out;
+			case '"':
+				quoting = 1;
+				break;
+			default:
+				break;
+			}
+		}
+		if(t == 0)
+			t = s_new();
+		s_putc(t, c);
+	}
+out:
+	yylval->white = yywhite();
+	if(t) {
+		s_terminate(t);
+	} else				/* message begins with white-space! */
+		return yylval->c = '\n';
+	yylval->s = t;
+	for(kp = key; kp->val != WORD; kp++)
+		if(cistrcmp(s_to_c(t), kp->rep)==0)
+			break;
+/*	print("lex(%d) %s\n", kp->val-WORD, s_to_c(t)); /**/
+	yylval->end = yylp;
+	return yylval->c = kp->val;
+}
+
+void
+yyerror(char *x)
+{
+	USED(x);
+
+	/*fprint(2, "parse err: %s\n", x);/**/
+}
+
+/*
+ *  parse white space and comments
+ */
+String *
+yywhite(void)
+{
+	String *w;
+	int clevel;
+	int c;
+	int escaping;
+
+	escaping = clevel = 0;
+	for(w = 0; yylp < yyend; yylp++){
+		c = *yylp & 0xff;
+
+		/* dump nulls, they can't be in header */
+		if(c == 0)
+			continue;
+
+		if(escaping){
+			escaping = 0;
+		} else if(clevel) {
+			switch(c){
+			case '\n':
+				/*
+				 *  look for multiline fields
+				 */
+				if(*(yylp+1)==' ' || *(yylp+1)=='\t')
+					break;
+				else
+					goto out;
+			case '\\':
+				escaping = 1;
+				break;
+			case '(':
+				clevel++;
+				break;
+			case ')':
+				clevel--;
+				break;
+			}
+		} else {
+			switch(c){
+			case '\\':
+				escaping = 1;
+				break;
+			case '(':
+				clevel++;
+				break;
+			case ' ':
+			case '\t':
+			case '\r':
+				break;
+			case '\n':
+				/*
+				 *  look for multiline fields
+				 */
+				if(*(yylp+1)==' ' || *(yylp+1)=='\t')
+					break;
+				else
+					goto out;
+			default:
+				goto out;
+			}
+		}
+		if(w == 0)
+			w = s_new();
+		s_putc(w, c);
+	}
+out:
+	if(w)
+		s_terminate(w);
+	return w;
+}
+
+/*
+ *  link two parsed entries together
+ */
+Node*
+link2(Node *p1, Node *p2)
+{
+	Node *p;
+
+	for(p = p1; p->next; p = p->next)
+		;
+	p->next = p2;
+	return p1;
+}
+
+/*
+ *  link three parsed entries together
+ */
+Node*
+link3(Node *p1, Node *p2, Node *p3)
+{
+	Node *p;
+
+	for(p = p2; p->next; p = p->next)
+		;
+	p->next = p3;
+
+	for(p = p1; p->next; p = p->next)
+		;
+	p->next = p2;
+
+	return p1;
+}
+
+/*
+ *  make a:b, move all white space after both
+ */
+Node*
+colon(Node *p1, Node *p2)
+{
+	if(p1->white){
+		if(p2->white)
+			s_append(p1->white, s_to_c(p2->white));
+	} else {
+		p1->white = p2->white;
+		p2->white = 0;
+	}
+
+	s_append(p1->s, ":");
+	if(p2->s)
+		s_append(p1->s, s_to_c(p2->s));
+
+	if(p1->end < p2->end)
+		p1->end = p2->end;
+	freenode(p2);
+	return p1;
+}
+
+/*
+ *  concatenate two fields, move all white space after both
+ */
+Node*
+concat(Node *p1, Node *p2)
+{
+	char buf[2];
+
+	if(p1->white){
+		if(p2->white)
+			s_append(p1->white, s_to_c(p2->white));
+	} else {
+		p1->white = p2->white;
+		p2->white = 0;
+	}
+
+	if(p1->s == nil){
+		buf[0] = p1->c;
+		buf[1] = 0;
+		p1->s = s_new();
+		s_append(p1->s, buf);
+	}
+
+	if(p2->s)
+		s_append(p1->s, s_to_c(p2->s));
+	else {
+		buf[0] = p2->c;
+		buf[1] = 0;
+		s_append(p1->s, buf);
+	}
+
+	if(p1->end < p2->end)
+		p1->end = p2->end;
+	freenode(p2);
+	return p1;
+}
+
+/*
+ *  look for disallowed chars in the field name
+ */
+int
+badfieldname(Node *p)
+{
+	for(; p; p = p->next){
+		/* field name can't contain white space */
+		if(p->white && p->next)
+			return 1;
+	}
+	return 0;
+}
+
+/*
+ *  mark as an address
+ */
+Node *
+address(Node *p)
+{
+	p->addr = 1;
+	return p;
+}
+
+/*
+ *  case independent string compare
+ */
+int
+cistrcmp(char *s1, char *s2)
+{
+	int c1, c2;
+
+	for(; *s1; s1++, s2++){
+		c1 = isupper(*s1) ? tolower(*s1) : *s1;
+		c2 = isupper(*s2) ? tolower(*s2) : *s2;
+		if (c1 != c2)
+			return -1;
+	}
+	return *s2;
+}
+
+/*
+ *  free a node
+ */
+void
+freenode(Node *p)
+{
+	Node *tp;
+
+	while(p){
+		tp = p->next;
+		if(p->s)
+			s_free(p->s);
+		if(p->white)
+			s_free(p->white);
+		free(p);
+		p = tp;
+	}
+}
+
+
+/*
+ *  an anonymous user
+ */
+Node*
+nobody(Node *p)
+{
+	if(p->s)
+		s_free(p->s);
+	p->s = s_copy("pOsTmAsTeR");
+	p->addr = 1;
+	return p;
+}
+
+/*
+ *  add anything that was dropped because of a parse error
+ */
+void
+missing(Node *p)
+{
+	Node *np;
+	char *start, *end;
+	Field *f;
+	String *s;
+
+	start = yybuffer;
+	if(lastfield != nil){
+		for(np = lastfield->node; np; np = np->next)
+			start = np->end+1;
+	}
+
+	end = p->start-1;
+
+	if(end <= start)
+		return;
+
+	if(strncmp(start, "From ", 5) == 0)
+		return;
+
+	np = malloc(sizeof(Node));
+	np->start = start;
+	np->end = end;
+	np->white = nil;
+	s = s_copy("BadHeader: ");
+	np->s = s_nappend(s, start, end-start);
+	np->next = nil;
+
+	f = malloc(sizeof(Field));
+	f->next = 0;
+	f->node = np;
+	f->source = 0;
+	if(firstfield)
+		lastfield->next = f;
+	else
+		firstfield = f;
+	lastfield = f;
+}
+
+/*
+ *  create a new field
+ */
+void
+newfield(Node *p, int source)
+{
+	Field *f;
+
+	missing(p);
+
+	f = malloc(sizeof(Field));
+	f->next = 0;
+	f->node = p;
+	f->source = source;
+	if(firstfield)
+		lastfield->next = f;
+	else
+		firstfield = f;
+	lastfield = f;
+	endfield = startfield;
+	startfield = yylp;
+}
+
+/*
+ *  fee a list of fields
+ */
+void
+freefield(Field *f)
+{
+	Field *tf;
+
+	while(f){
+		tf = f->next;
+		freenode(f->node);
+		free(f);
+		f = tf;
+	}
+}
+
+/*
+ *  add some white space to a node
+ */
+Node*
+whiten(Node *p)
+{
+	Node *tp;
+
+	for(tp = p; tp->next; tp = tp->next)
+		;
+	if(tp->white == 0)
+		tp->white = s_copy(" ");
+	return p;
+}
+
+void
+yycleanup(void)
+{
+	Field *f, *fnext;
+	Node *np, *next;
+
+	for(f = firstfield; f; f = fnext){
+		for(np = f->node; np; np = next){
+			if(np->s)
+				s_free(np->s);
+			if(np->white)
+				s_free(np->white);
+			next = np->next;
+			free(np);
+		}
+		fnext = f->next;
+		free(f);
+	}
+	firstfield = lastfield = 0;
+}
+static	const	short	yyexca[] =
+{-1, 1,
+	1, -1,
+	-2, 0,
+-1, 47,
+	1, 4,
+	-2, 0,
+-1, 112,
+	29, 72,
+	31, 72,
+	32, 72,
+	35, 72,
+	-2, 74,
+};
+#define	YYNPROD	122
+#define	YYPRIVATE 57344
+#define	YYLAST	608
+static	const	short	yyact[] =
+{
+ 112, 133, 136,  53, 121, 111, 134,  55, 109, 118,
+ 119, 116, 162, 171,  35,  48, 166,  54,   5, 166,
+ 179, 114, 115, 155,  49, 101, 100,  99,  95,  94,
+  93,  92,  98,  91, 132,  90, 123,  89, 122,  88,
+  87,  86,  85,  84,  83,  82,  97,  81,  80, 106,
+  47,  46, 110, 117, 153, 168, 108,   2,  56,  57,
+  58,  59,  60,  61,  62,  63,  64,  65,  73,  66,
+  67,  68,  69,  70,  71,  72,  74,  75,  76,  77,
+  78,  79, 124, 124,  49,  55, 177, 131, 110,  52,
+ 110, 110, 138, 137, 140, 141, 124, 124,  51, 120,
+ 124, 124, 124,  50, 102, 104, 135, 154,  31,  32,
+ 107, 157, 105,  14,  55,  55, 156,  13, 161, 117,
+ 117, 139, 158, 124, 142, 143, 144, 145, 146, 147,
+ 163, 164, 160,  12, 148, 149,  11, 157, 150, 151,
+ 152,  10, 156,   9,   8,   7,   3,   1,   0, 124,
+ 124, 124, 124, 124,   0, 169,   0,   0, 110, 165,
+   0,   0, 170, 117,   0,   0,   0,   0, 173, 176,
+ 178,   0,   0,   0, 172,   0,   0,   0, 180,   0,
+   0, 182, 183,   0,   0, 165, 165, 165, 165, 165,
+   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,
+   0,   0, 174,  56,  57,  58,  59,  60,  61,  62,
+  63,  64,  65,  73,  66,  67,  68,  69,  70,  71,
+  72,  74,  75,  76,  77,  78,  79,   0,   0, 128,
+ 130, 129, 125, 126, 127,  15,   0,  36,  16,  17,
+  19, 103,  20,  18,  23,  22,  21,  30,  24,  26,
+  28,  25,  27,  29,   0,  34,  37,  38,  39,  33,
+  40,   0,   4,   0,  45,  44,  41,  42,  43,  56,
+  57,  58,  59,  60,  61,  62,  63,  64,  65,  73,
+  66,  67,  68,  69,  70,  71,  72,  74,  75,  76,
+  77,  78,  79,   0,   0,  96,  45,  44,  41,  42,
+  43,  15,   0,  36,  16,  17,  19,   6,  20,  18,
+  23,  22,  21,  30,  24,  26,  28,  25,  27,  29,
+   0,  34,  37,  38,  39,  33,  40,   0,   4,   0,
+  45,  44,  41,  42,  43,  15,   0,  36,  16,  17,
+  19, 103,  20,  18,  23,  22,  21,  30,  24,  26,
+  28,  25,  27,  29,   0,  34,  37,  38,  39,  33,
+  40,   0,   0,   0,  45,  44,  41,  42,  43,  56,
+  57,  58,  59,  60,  61,  62,  63,  64,  65,  73,
+  66,  67,  68,  69,  70,  71,  72,  74,  75,  76,
+  77,  78,  79,   0,   0,   0,   0, 175, 113,   0,
+  52,  56,  57,  58,  59,  60,  61,  62,  63,  64,
+  65,  73,  66,  67,  68,  69,  70,  71,  72,  74,
+  75,  76,  77,  78,  79,   0,   0,   0,   0,   0,
+ 113,   0,  52,  56,  57,  58,  59,  60,  61,  62,
+  63,  64,  65,  73,  66,  67,  68,  69,  70,  71,
+  72,  74,  75,  76,  77,  78,  79,   0,   0,   0,
+   0,   0,   0, 159,  52,  56,  57,  58,  59,  60,
+  61,  62,  63,  64,  65,  73,  66,  67,  68,  69,
+  70,  71,  72,  74,  75,  76,  77,  78,  79,   0,
+   0,   0,   0,   0,   0,   0,  52,  56,  57,  58,
+  59,  60,  61,  62,  63,  64,  65,  73,  66,  67,
+  68,  69,  70,  71,  72,  74,  75,  76,  77,  78,
+  79,   0,   0, 167,   0,   0, 113,  56,  57,  58,
+  59,  60,  61,  62,  63,  64,  65,  73,  66,  67,
+  68,  69,  70,  71,  72,  74,  75,  76,  77,  78,
+  79,   0,   0,   0,   0,   0, 113,  56,  57,  58,
+  59,  60,  61,  62,  63,  64,  65,  73,  66,  67,
+  68,  69,  70,  71,  72,  74,  75,  76,  77,  78,
+  79,   0,   0, 181,  56,  57,  58,  59,  60,  61,
+  62,  63,  64,  65,  73,  66,  67,  68,  69,  70,
+  71,  72,  74,  75,  76,  77,  78,  79
+};
+static	const	short	yypact[] =
+{
+ 299,-1000,-1000,  22,-1000,  21,  54,-1000,-1000,-1000,
+-1000,-1000,-1000,-1000,-1000,  19,  17,  15,  14,  13,
+  12,  11,  10,   9,   7,   5,   3,   1,   0,  -1,
+  -2, 265,  -3,  -4,  -5,-1000,-1000,-1000,-1000,-1000,
+-1000,-1000,-1000,-1000,-1000,-1000, 233, 233, 580, 397,
+  -9,-1000, 580, -26, -25,-1000,-1000,-1000,-1000,-1000,
+-1000,-1000,-1000,-1000,-1000,-1000,-1000,-1000,-1000,-1000,
+-1000,-1000,-1000,-1000,-1000,-1000,-1000,-1000,-1000,-1000,
+ 333, 199, 199, 397, 461, 397, 397, 397, 397, 397,
+ 397, 397, 397, 397, 397, 199, 199,-1000,-1000, 199,
+ 199, 199,-1000,  -6,-1000,  33, 580,  -8,-1000,-1000,
+ 523,-1000,-1000, 429, 580, -23,-1000,-1000, 580, 580,
+-1000,-1000, 199,-1000,-1000,-1000,-1000,-1000,-1000,-1000,
+-1000,-1000, -15,-1000,-1000,-1000, 493,-1000,-1000, -15,
+-1000,-1000, -15, -15, -15, -15, -15, -15, 199, 199,
+ 199, 199, 199,  47, 580, 397,-1000,-1000, -21,-1000,
+ -25, -26, 580,-1000,-1000,-1000, 397, 365, 580, 580,
+-1000,-1000,-1000,-1000, -12,-1000,-1000, 553,-1000,-1000,
+ 580, 580,-1000,-1000
+};
+static	const	short	yypgo[] =
+{
+   0, 147,  57, 146,  18, 145, 144, 143, 141, 136,
+ 133, 117, 113,   8, 112,   0,  34, 110,   6,   4,
+  38, 109, 108,   1, 106,   2,   5, 103,  17,  98,
+  11,   3,  36,  86,  14
+};
+static	const	short	yyr1[] =
+{
+   0,   1,   1,   2,   2,   2,   4,   4,   4,   4,
+   4,   4,   4,   4,   4,   3,   6,   6,   6,   6,
+   6,   6,   6,   5,   5,   7,   7,   7,   7,   7,
+   7,   7,   7,   7,   7,   7,   7,   8,   8,  11,
+  11,  12,  12,  10,  10,  21,  21,  21,  21,   9,
+   9,  16,  16,  23,  23,  24,  24,  17,  17,  18,
+  18,  18,  26,  26,  13,  13,  27,  27,  29,  29,
+  28,  28,  31,  30,  25,  25,  20,  20,  32,  32,
+  32,  32,  32,  32,  32,  19,  14,  33,  33,  15,
+  15,  15,  15,  15,  15,  15,  15,  15,  15,  15,
+  15,  15,  15,  15,  15,  15,  15,  15,  15,  15,
+  15,  15,  15,  22,  22,  22,  22,  34,  34,  34,
+  34,  34
+};
+static	const	short	yyr2[] =
+{
+   0,   1,   3,   1,   2,   3,   1,   1,   1,   1,
+   1,   1,   1,   1,   3,   6,   3,   3,   3,   3,
+   3,   3,   3,   3,   3,   2,   3,   2,   3,   2,
+   3,   2,   3,   2,   3,   2,   3,   3,   2,   3,
+   2,   3,   2,   3,   2,   1,   1,   1,   1,   3,
+   2,   1,   3,   1,   1,   4,   3,   1,   3,   1,
+   2,   1,   3,   2,   3,   1,   2,   4,   1,   1,
+   3,   3,   1,   1,   1,   2,   1,   2,   1,   1,
+   1,   1,   1,   1,   1,   1,   6,   1,   3,   1,
+   1,   1,   1,   1,   1,   1,   1,   1,   1,   1,
+   1,   1,   1,   1,   1,   1,   1,   1,   1,   1,
+   1,   1,   1,   1,   1,   2,   2,   1,   1,   1,
+   1,   1
+};
+static	const	short	yychk[] =
+{
+-1000,  -1,  -2,  -3,  29,  -4,   8,  -5,  -6,  -7,
+  -8,  -9, -10, -11, -12,   2,   5,   6,  10,   7,
+   9,  13,  12,  11,  15,  18,  16,  19,  17,  20,
+  14, -22, -21,  26,  22, -34,   4,  23,  24,  25,
+  27,  33,  34,  35,  32,  31,  29,  29, -13,  30,
+ -27, -29,  35, -31, -28, -15,   4,   5,   6,   7,
+   8,   9,  10,  11,  12,  13,  15,  16,  17,  18,
+  19,  20,  21,  14,  22,  23,  24,  25,  26,  27,
+  29,  30,  30,  30,  30,  30,  30,  30,  30,  30,
+  30,  30,  30,  30,  30,  30,  30, -34, -15,  30,
+  30,  30,  -2,   8,  -2, -14, -15, -17, -18, -13,
+ -25, -26, -15,  33,  30,  31, -30, -15,  35,  35,
+  -4, -19, -20, -32, -15,  33,  34,  35,  30,  32,
+  31, -19, -16, -23, -18, -24, -25, -13, -18, -16,
+ -18, -18, -16, -16, -16, -16, -16, -16, -20, -20,
+ -20, -20, -20,  21, -15,  31, -26, -15, -13,  34,
+ -28, -31,  35, -30, -30, -32,  31,  30,   8, -15,
+ -18,  34, -30, -23, -16,  32, -15, -33, -15,  32,
+ -15,  30, -15, -15
+};
+static	const	short	yydef[] =
+{
+   0,  -2,   1,   0,   3,   0,   0,   6,   7,   8,
+   9,  10,  11,  12,  13,   0,   0,   0,   0,   0,
+   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,
+   0,   0,   0,   0,   0, 113, 114,  45,  46,  47,
+  48, 117, 118, 119, 120, 121,   0,  -2,   0,   0,
+   0,  65,   0,  68,  69,  72,  89,  90,  91,  92,
+  93,  94,  95,  96,  97,  98,  99, 100, 101, 102,
+ 103, 104, 105, 106, 107, 108, 109, 110, 111, 112,
+   0,   0,   0,   0,   0,   0,   0,   0,   0,  25,
+  27,  29,  31,  33,  35,  38,  50, 115, 116,  44,
+  40,  42,   2,   0,   5,   0,   0,  18,  57,  59,
+   0,  61,  -2,   0,   0,   0,  66,  73,   0,   0,
+  14,  23,  85,  76,  78,  79,  80,  81,  82,  83,
+  84,  24,  16,  51,  53,  54,   0,  17,  19,  20,
+  21,  22,  26,  28,  30,  32,  34,  36,  37,  49,
+  43,  39,  41,   0,   0,   0,  60,  75,   0,  63,
+  64,   0,   0,  70,  71,  77,   0,   0,   0,   0,
+  58,  62,  67,  52,   0,  56,  15,   0,  87,  55,
+   0,   0,  86,  88
+};
+static	const	short	yytok1[] =
+{
+   1,   0,   0,   0,   0,   0,   0,   0,   0,   0,
+  29,   0,   0,   0,   0,   0,   0,   0,   0,   0,
+   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,
+   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,
+   0,   0,   0,   0,  31,   0,   0,   0,   0,   0,
+   0,   0,   0,   0,   0,   0,   0,   0,  30,  32,
+  33,   0,  34,   0,  35
+};
+static	const	short	yytok2[] =
+{
+   2,   3,   4,   5,   6,   7,   8,   9,  10,  11,
+  12,  13,  14,  15,  16,  17,  18,  19,  20,  21,
+  22,  23,  24,  25,  26,  27,  28
+};
+static	const	long	yytok3[] =
+{
+   0
+};
+#define YYFLAG 		-1000
+#define YYERROR		goto yyerrlab
+#define YYACCEPT	return(0)
+#define YYABORT		return(1)
+#define	yyclearin	yychar = -1
+#define	yyerrok		yyerrflag = 0
+
+#ifdef	yydebug
+#include	"y.debug"
+#else
+#define	yydebug		0
+static	const	char*	yytoknames[1];		/* for debugging */
+static	const	char*	yystates[1];		/* for debugging */
+#endif
+
+/*	parser for yacc output	*/
+#ifdef YYARG
+#define	yynerrs		yyarg->yynerrs
+#define	yyerrflag	yyarg->yyerrflag
+#define yyval		yyarg->yyval
+#define yylval		yyarg->yylval
+#else
+int	yynerrs = 0;		/* number of errors */
+int	yyerrflag = 0;		/* error recovery flag */
+#endif
+
+extern	int	fprint(int, char*, ...);
+extern	int	sprint(char*, char*, ...);
+
+static const char*
+yytokname(int yyc)
+{
+	static char x[10];
+
+	if(yyc > 0 && yyc <= sizeof(yytoknames)/sizeof(yytoknames[0]))
+	if(yytoknames[yyc-1])
+		return yytoknames[yyc-1];
+	sprint(x, "<%d>", yyc);
+	return x;
+}
+
+static const char*
+yystatname(int yys)
+{
+	static char x[10];
+
+	if(yys >= 0 && yys < sizeof(yystates)/sizeof(yystates[0]))
+	if(yystates[yys])
+		return yystates[yys];
+	sprint(x, "<%d>\n", yys);
+	return x;
+}
+
+static long
+#ifdef YYARG
+yylex1(struct Yyarg *yyarg)
+#else
+yylex1(void)
+#endif
+{
+	long yychar;
+	const long *t3p;
+	int c;
+
+#ifdef YYARG	
+	yychar = yylex(yyarg);
+#else
+	yychar = yylex();
+#endif
+	if(yychar <= 0) {
+		c = yytok1[0];
+		goto out;
+	}
+	if(yychar < sizeof(yytok1)/sizeof(yytok1[0])) {
+		c = yytok1[yychar];
+		goto out;
+	}
+	if(yychar >= YYPRIVATE)
+		if(yychar < YYPRIVATE+sizeof(yytok2)/sizeof(yytok2[0])) {
+			c = yytok2[yychar-YYPRIVATE];
+			goto out;
+		}
+	for(t3p=yytok3;; t3p+=2) {
+		c = t3p[0];
+		if(c == yychar) {
+			c = t3p[1];
+			goto out;
+		}
+		if(c == 0)
+			break;
+	}
+	c = 0;
+
+out:
+	if(c == 0)
+		c = yytok2[1];	/* unknown char */
+	if(yydebug >= 3)
+		fprint(2, "lex %.4lux %s\n", yychar, yytokname(c));
+	return c;
+}
+
+int
+#ifdef YYARG
+yyparse(struct Yyarg *yyarg)
+#else
+yyparse(void)
+#endif
+{
+	struct
+	{
+		YYSTYPE	yyv;
+		int	yys;
+	} yys[YYMAXDEPTH], *yyp, *yypt;
+	const short *yyxi;
+	int yyj, yym, yystate, yyn, yyg;
+	long yychar;
+#ifndef YYARG
+	YYSTYPE save1, save2;
+	int save3, save4;
+
+	save1 = yylval;
+	save2 = yyval;
+	save3 = yynerrs;
+	save4 = yyerrflag;
+#endif
+
+	yystate = 0;
+	yychar = -1;
+	yynerrs = 0;
+	yyerrflag = 0;
+	yyp = &yys[-1];
+	goto yystack;
+
+ret0:
+	yyn = 0;
+	goto ret;
+
+ret1:
+	yyn = 1;
+	goto ret;
+
+ret:
+#ifndef YYARG
+	yylval = save1;
+	yyval = save2;
+	yynerrs = save3;
+	yyerrflag = save4;
+#endif
+	return yyn;
+
+yystack:
+	/* put a state and value onto the stack */
+	if(yydebug >= 4)
+		fprint(2, "char %s in %s", yytokname(yychar), yystatname(yystate));
+
+	yyp++;
+	if(yyp >= &yys[YYMAXDEPTH]) {
+		yyerror("yacc stack overflow");
+		goto ret1;
+	}
+	yyp->yys = yystate;
+	yyp->yyv = yyval;
+
+yynewstate:
+	yyn = yypact[yystate];
+	if(yyn <= YYFLAG)
+		goto yydefault; /* simple state */
+	if(yychar < 0)
+#ifdef YYARG
+		yychar = yylex1(yyarg);
+#else
+		yychar = yylex1();
+#endif
+	yyn += yychar;
+	if(yyn < 0 || yyn >= YYLAST)
+		goto yydefault;
+	yyn = yyact[yyn];
+	if(yychk[yyn] == yychar) { /* valid shift */
+		yychar = -1;
+		yyval = yylval;
+		yystate = yyn;
+		if(yyerrflag > 0)
+			yyerrflag--;
+		goto yystack;
+	}
+
+yydefault:
+	/* default state action */
+	yyn = yydef[yystate];
+	if(yyn == -2) {
+		if(yychar < 0)
+#ifdef YYARG
+		yychar = yylex1(yyarg);
+#else
+		yychar = yylex1();
+#endif
+
+		/* look through exception table */
+		for(yyxi=yyexca;; yyxi+=2)
+			if(yyxi[0] == -1 && yyxi[1] == yystate)
+				break;
+		for(yyxi += 2;; yyxi += 2) {
+			yyn = yyxi[0];
+			if(yyn < 0 || yyn == yychar)
+				break;
+		}
+		yyn = yyxi[1];
+		if(yyn < 0)
+			goto ret0;
+	}
+	if(yyn == 0) {
+		/* error ... attempt to resume parsing */
+		switch(yyerrflag) {
+		case 0:   /* brand new error */
+			yyerror("syntax error");
+			if(yydebug >= 1) {
+				fprint(2, "%s", yystatname(yystate));
+				fprint(2, "saw %s\n", yytokname(yychar));
+			}
+			goto yyerrlab;
+		yyerrlab:
+			yynerrs++;
+
+		case 1:
+		case 2: /* incompletely recovered error ... try again */
+			yyerrflag = 3;
+
+			/* find a state where "error" is a legal shift action */
+			while(yyp >= yys) {
+				yyn = yypact[yyp->yys] + YYERRCODE;
+				if(yyn >= 0 && yyn < YYLAST) {
+					yystate = yyact[yyn];  /* simulate a shift of "error" */
+					if(yychk[yystate] == YYERRCODE)
+						goto yystack;
+				}
+
+				/* the current yyp has no shift onn "error", pop stack */
+				if(yydebug >= 2)
+					fprint(2, "error recovery pops state %d, uncovers %d\n",
+						yyp->yys, (yyp-1)->yys );
+				yyp--;
+			}
+			/* there is no state on the stack with an error shift ... abort */
+			goto ret1;
+
+		case 3:  /* no shift yet; clobber input char */
+			if(yydebug >= 2)
+				fprint(2, "error recovery discards %s\n", yytokname(yychar));
+			if(yychar == YYEOFCODE)
+				goto ret1;
+			yychar = -1;
+			goto yynewstate;   /* try again in the same state */
+		}
+	}
+
+	/* reduction by production yyn */
+	if(yydebug >= 2)
+		fprint(2, "reduce %d in:\n\t%s", yyn, yystatname(yystate));
+
+	yypt = yyp;
+	yyp -= yyr2[yyn];
+	yyval = (yyp+1)->yyv;
+	yym = yyn;
+
+	/* consult goto table to find next state */
+	yyn = yyr1[yyn];
+	yyg = yypgo[yyn];
+	yyj = yyg + yyp->yys + 1;
+
+	if(yyj >= YYLAST || yychk[yystate=yyact[yyj]] != -yyn)
+		yystate = yyact[yyg];
+	switch(yym) {
+		
+case 3:
+#line	56	"/usr/local/plan9/src/cmd/upas/smtp/rfc822.y"
+{ yydone = 1; } break;
+case 6:
+#line	61	"/usr/local/plan9/src/cmd/upas/smtp/rfc822.y"
+{ date = 1; } break;
+case 7:
+#line	63	"/usr/local/plan9/src/cmd/upas/smtp/rfc822.y"
+{ originator = 1; } break;
+case 8:
+#line	65	"/usr/local/plan9/src/cmd/upas/smtp/rfc822.y"
+{ destination = 1; } break;
+case 15:
+#line	74	"/usr/local/plan9/src/cmd/upas/smtp/rfc822.y"
+{ freenode(yypt[-5].yyv); freenode(yypt[-2].yyv); freenode(yypt[-1].yyv);
+			  usender = yypt[-4].yyv; udate = yypt[-3].yyv; usys = yypt[-0].yyv;
+			} break;
+case 16:
+#line	79	"/usr/local/plan9/src/cmd/upas/smtp/rfc822.y"
+{ newfield(link3(yypt[-2].yyv, yypt[-1].yyv, yypt[-0].yyv), 1); } break;
+case 17:
+#line	81	"/usr/local/plan9/src/cmd/upas/smtp/rfc822.y"
+{ newfield(link3(yypt[-2].yyv, yypt[-1].yyv, yypt[-0].yyv), 1); } break;
+case 18:
+#line	83	"/usr/local/plan9/src/cmd/upas/smtp/rfc822.y"
+{ newfield(link3(yypt[-2].yyv, yypt[-1].yyv, yypt[-0].yyv), 1); } break;
+case 19:
+#line	85	"/usr/local/plan9/src/cmd/upas/smtp/rfc822.y"
+{ newfield(link3(yypt[-2].yyv, yypt[-1].yyv, yypt[-0].yyv), 1); } break;
+case 20:
+#line	87	"/usr/local/plan9/src/cmd/upas/smtp/rfc822.y"
+{ newfield(link3(yypt[-2].yyv, yypt[-1].yyv, yypt[-0].yyv), 1); } break;
+case 21:
+#line	89	"/usr/local/plan9/src/cmd/upas/smtp/rfc822.y"
+{ newfield(link3(yypt[-2].yyv, yypt[-1].yyv, yypt[-0].yyv), 1); } break;
+case 22:
+#line	91	"/usr/local/plan9/src/cmd/upas/smtp/rfc822.y"
+{ newfield(link3(yypt[-2].yyv, yypt[-1].yyv, yypt[-0].yyv), 1); } break;
+case 23:
+#line	94	"/usr/local/plan9/src/cmd/upas/smtp/rfc822.y"
+{ newfield(link3(yypt[-2].yyv, yypt[-1].yyv, yypt[-0].yyv), 0); } break;
+case 24:
+#line	96	"/usr/local/plan9/src/cmd/upas/smtp/rfc822.y"
+{ newfield(link3(yypt[-2].yyv, yypt[-1].yyv, yypt[-0].yyv), 0); } break;
+case 25:
+#line	99	"/usr/local/plan9/src/cmd/upas/smtp/rfc822.y"
+{ newfield(link2(yypt[-1].yyv, yypt[-0].yyv), 0); } break;
+case 26:
+#line	101	"/usr/local/plan9/src/cmd/upas/smtp/rfc822.y"
+{ newfield(link3(yypt[-2].yyv, yypt[-1].yyv, yypt[-0].yyv), 0); } break;
+case 27:
+#line	103	"/usr/local/plan9/src/cmd/upas/smtp/rfc822.y"
+{ newfield(link2(yypt[-1].yyv, yypt[-0].yyv), 0); } break;
+case 28:
+#line	105	"/usr/local/plan9/src/cmd/upas/smtp/rfc822.y"
+{ newfield(link3(yypt[-2].yyv, yypt[-1].yyv, yypt[-0].yyv), 0); } break;
+case 29:
+#line	107	"/usr/local/plan9/src/cmd/upas/smtp/rfc822.y"
+{ newfield(link2(yypt[-1].yyv, yypt[-0].yyv), 0); } break;
+case 30:
+#line	109	"/usr/local/plan9/src/cmd/upas/smtp/rfc822.y"
+{ newfield(link3(yypt[-2].yyv, yypt[-1].yyv, yypt[-0].yyv), 0); } break;
+case 31:
+#line	111	"/usr/local/plan9/src/cmd/upas/smtp/rfc822.y"
+{ newfield(link2(yypt[-1].yyv, yypt[-0].yyv), 0); } break;
+case 32:
+#line	113	"/usr/local/plan9/src/cmd/upas/smtp/rfc822.y"
+{ newfield(link3(yypt[-2].yyv, yypt[-1].yyv, yypt[-0].yyv), 0); } break;
+case 33:
+#line	115	"/usr/local/plan9/src/cmd/upas/smtp/rfc822.y"
+{ newfield(link2(yypt[-1].yyv, yypt[-0].yyv), 0); } break;
+case 34:
+#line	117	"/usr/local/plan9/src/cmd/upas/smtp/rfc822.y"
+{ newfield(link3(yypt[-2].yyv, yypt[-1].yyv, yypt[-0].yyv), 0); } break;
+case 35:
+#line	119	"/usr/local/plan9/src/cmd/upas/smtp/rfc822.y"
+{ newfield(link2(yypt[-1].yyv, yypt[-0].yyv), 0); } break;
+case 36:
+#line	121	"/usr/local/plan9/src/cmd/upas/smtp/rfc822.y"
+{ newfield(link3(yypt[-2].yyv, yypt[-1].yyv, yypt[-0].yyv), 0); } break;
+case 37:
+#line	124	"/usr/local/plan9/src/cmd/upas/smtp/rfc822.y"
+{ newfield(link3(yypt[-2].yyv, yypt[-1].yyv, yypt[-0].yyv), 0); } break;
+case 38:
+#line	126	"/usr/local/plan9/src/cmd/upas/smtp/rfc822.y"
+{ newfield(link2(yypt[-1].yyv, yypt[-0].yyv), 0); } break;
+case 39:
+#line	129	"/usr/local/plan9/src/cmd/upas/smtp/rfc822.y"
+{ newfield(link3(yypt[-2].yyv, yypt[-1].yyv, yypt[-0].yyv), 0); received++; } break;
+case 40:
+#line	131	"/usr/local/plan9/src/cmd/upas/smtp/rfc822.y"
+{ newfield(link2(yypt[-1].yyv, yypt[-0].yyv), 0); received++; } break;
+case 41:
+#line	134	"/usr/local/plan9/src/cmd/upas/smtp/rfc822.y"
+{ newfield(link3(yypt[-2].yyv, yypt[-1].yyv, yypt[-0].yyv), 0); } break;
+case 42:
+#line	136	"/usr/local/plan9/src/cmd/upas/smtp/rfc822.y"
+{ newfield(link2(yypt[-1].yyv, yypt[-0].yyv), 0); } break;
+case 43:
+#line	139	"/usr/local/plan9/src/cmd/upas/smtp/rfc822.y"
+{ newfield(link3(yypt[-2].yyv, yypt[-1].yyv, yypt[-0].yyv), 0); } break;
+case 44:
+#line	141	"/usr/local/plan9/src/cmd/upas/smtp/rfc822.y"
+{ newfield(link2(yypt[-1].yyv, yypt[-0].yyv), 0); } break;
+case 47:
+#line	143	"/usr/local/plan9/src/cmd/upas/smtp/rfc822.y"
+{ messageid = 1; } break;
+case 49:
+#line	146	"/usr/local/plan9/src/cmd/upas/smtp/rfc822.y"
+{ /* hack to allow same lex for field names and the rest */
+			 if(badfieldname(yypt[-2].yyv)){
+				freenode(yypt[-2].yyv);
+				freenode(yypt[-1].yyv);
+				freenode(yypt[-0].yyv);
+				return 1;
+			 }
+			 newfield(link3(yypt[-2].yyv, yypt[-1].yyv, yypt[-0].yyv), 0);
+			} break;
+case 50:
+#line	156	"/usr/local/plan9/src/cmd/upas/smtp/rfc822.y"
+{ /* hack to allow same lex for field names and the rest */
+			 if(badfieldname(yypt[-1].yyv)){
+				freenode(yypt[-1].yyv);
+				freenode(yypt[-0].yyv);
+				return 1;
+			 }
+			 newfield(link2(yypt[-1].yyv, yypt[-0].yyv), 0);
+			} break;
+case 52:
+#line	167	"/usr/local/plan9/src/cmd/upas/smtp/rfc822.y"
+{ yyval = link3(yypt[-2].yyv, yypt[-1].yyv, yypt[-0].yyv); } break;
+case 55:
+#line	173	"/usr/local/plan9/src/cmd/upas/smtp/rfc822.y"
+{ yyval = link2(yypt[-3].yyv, link3(yypt[-2].yyv, yypt[-1].yyv, yypt[-0].yyv)); } break;
+case 56:
+#line	175	"/usr/local/plan9/src/cmd/upas/smtp/rfc822.y"
+{ yyval = link3(yypt[-2].yyv, yypt[-1].yyv, yypt[-0].yyv); } break;
+case 58:
+#line	179	"/usr/local/plan9/src/cmd/upas/smtp/rfc822.y"
+{ yyval = link3(yypt[-2].yyv, yypt[-1].yyv, yypt[-0].yyv); } break;
+case 60:
+#line	183	"/usr/local/plan9/src/cmd/upas/smtp/rfc822.y"
+{ yyval = link2(yypt[-1].yyv, yypt[-0].yyv); } break;
+case 62:
+#line	187	"/usr/local/plan9/src/cmd/upas/smtp/rfc822.y"
+{ yyval = link3(yypt[-2].yyv, yypt[-1].yyv, yypt[-0].yyv); } break;
+case 63:
+#line	189	"/usr/local/plan9/src/cmd/upas/smtp/rfc822.y"
+{ yyval = nobody(yypt[-0].yyv); freenode(yypt[-1].yyv); } break;
+case 64:
+#line	192	"/usr/local/plan9/src/cmd/upas/smtp/rfc822.y"
+{ yyval = address(concat(yypt[-2].yyv, concat(yypt[-1].yyv, yypt[-0].yyv))); } break;
+case 66:
+#line	196	"/usr/local/plan9/src/cmd/upas/smtp/rfc822.y"
+{ yyval = concat(yypt[-1].yyv, yypt[-0].yyv); } break;
+case 67:
+#line	198	"/usr/local/plan9/src/cmd/upas/smtp/rfc822.y"
+{ yyval = concat(yypt[-3].yyv, concat(yypt[-2].yyv, concat(yypt[-1].yyv, yypt[-0].yyv))); } break;
+case 68:
+#line	201	"/usr/local/plan9/src/cmd/upas/smtp/rfc822.y"
+{ yyval = address(yypt[-0].yyv); } break;
+case 70:
+#line	205	"/usr/local/plan9/src/cmd/upas/smtp/rfc822.y"
+{ yyval = address(concat(yypt[-2].yyv, concat(yypt[-1].yyv, yypt[-0].yyv)));} break;
+case 71:
+#line	207	"/usr/local/plan9/src/cmd/upas/smtp/rfc822.y"
+{ yyval = address(concat(yypt[-2].yyv, concat(yypt[-1].yyv, yypt[-0].yyv)));} break;
+case 75:
+#line	215	"/usr/local/plan9/src/cmd/upas/smtp/rfc822.y"
+{ yyval = link2(yypt[-1].yyv, yypt[-0].yyv); } break;
+case 77:
+#line	219	"/usr/local/plan9/src/cmd/upas/smtp/rfc822.y"
+{ yyval = link2(yypt[-1].yyv, yypt[-0].yyv); } break;
+case 86:
+#line	226	"/usr/local/plan9/src/cmd/upas/smtp/rfc822.y"
+{ yyval = link3(yypt[-5].yyv, yypt[-3].yyv, link3(yypt[-4].yyv, yypt[-0].yyv, link2(yypt[-2].yyv, yypt[-1].yyv))); } break;
+case 88:
+#line	230	"/usr/local/plan9/src/cmd/upas/smtp/rfc822.y"
+{ yyval = link3(yypt[-2].yyv, yypt[-1].yyv, yypt[-0].yyv); } break;
+case 115:
+#line	240	"/usr/local/plan9/src/cmd/upas/smtp/rfc822.y"
+{ yyval = link2(yypt[-1].yyv, yypt[-0].yyv); } break;
+case 116:
+#line	242	"/usr/local/plan9/src/cmd/upas/smtp/rfc822.y"
+{ yyval = link2(yypt[-1].yyv, yypt[-0].yyv); } break;
+	}
+	goto yystack;  /* stack new state and value */
+}
blob - /dev/null
blob + cddbbc0fae671d8a9ff7a56ae8d6267461491a8a (mode 644)
--- /dev/null
+++ src/cmd/upas/smtp/rfc822.tab.h
@@ -0,0 +1,98 @@
+/* A Bison parser, made by GNU Bison 2.0.  */
+
+/* Skeleton parser for Yacc-like parsing with Bison,
+   Copyright (C) 1984, 1989, 1990, 2000, 2001, 2002, 2003, 2004 Free Software Foundation, Inc.
+
+   This program is free software; you can redistribute it and/or modify
+   it under the terms of the GNU General Public License as published by
+   the Free Software Foundation; either version 2, or (at your option)
+   any later version.
+
+   This program is distributed in the hope that it will be useful,
+   but WITHOUT ANY WARRANTY; without even the implied warranty of
+   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+   GNU General Public License for more details.
+
+   You should have received a copy of the GNU General Public License
+   along with this program; if not, write to the Free Software
+   Foundation, Inc., 59 Temple Place - Suite 330,
+   Boston, MA 02111-1307, USA.  */
+
+/* As a special exception, when this file is copied by Bison into a
+   Bison output file, you may use that output file without restriction.
+   This special exception was added by the Free Software Foundation
+   in version 1.24 of Bison.  */
+
+/* Tokens.  */
+#ifndef YYTOKENTYPE
+# define YYTOKENTYPE
+   /* Put the tokens into the symbol table, so that GDB and other debuggers
+      know about them.  */
+   enum yytokentype {
+     WORD = 258,
+     DATE = 259,
+     RESENT_DATE = 260,
+     RETURN_PATH = 261,
+     FROM = 262,
+     SENDER = 263,
+     REPLY_TO = 264,
+     RESENT_FROM = 265,
+     RESENT_SENDER = 266,
+     RESENT_REPLY_TO = 267,
+     SUBJECT = 268,
+     TO = 269,
+     CC = 270,
+     BCC = 271,
+     RESENT_TO = 272,
+     RESENT_CC = 273,
+     RESENT_BCC = 274,
+     REMOTE = 275,
+     PRECEDENCE = 276,
+     MIMEVERSION = 277,
+     CONTENTTYPE = 278,
+     MESSAGEID = 279,
+     RECEIVED = 280,
+     MAILER = 281,
+     BADTOKEN = 282
+   };
+#endif
+#define WORD 258
+#define DATE 259
+#define RESENT_DATE 260
+#define RETURN_PATH 261
+#define FROM 262
+#define SENDER 263
+#define REPLY_TO 264
+#define RESENT_FROM 265
+#define RESENT_SENDER 266
+#define RESENT_REPLY_TO 267
+#define SUBJECT 268
+#define TO 269
+#define CC 270
+#define BCC 271
+#define RESENT_TO 272
+#define RESENT_CC 273
+#define RESENT_BCC 274
+#define REMOTE 275
+#define PRECEDENCE 276
+#define MIMEVERSION 277
+#define CONTENTTYPE 278
+#define MESSAGEID 279
+#define RECEIVED 280
+#define MAILER 281
+#define BADTOKEN 282
+
+
+
+
+#if ! defined (YYSTYPE) && ! defined (YYSTYPE_IS_DECLARED)
+typedef int YYSTYPE;
+# define yystype YYSTYPE /* obsolescent; will be withdrawn */
+# define YYSTYPE_IS_DECLARED 1
+# define YYSTYPE_IS_TRIVIAL 1
+#endif
+
+extern YYSTYPE yylval;
+
+
+
blob - /dev/null
blob + be77d2bd38efdd36a151fd44ba601ada57451cb2 (mode 644)
--- /dev/null
+++ src/cmd/upas/smtp/rfc822.y
@@ -0,0 +1,778 @@
+%{
+#include "common.h"
+#include "smtp.h"
+#include <ctype.h>
+
+char	*yylp;		/* next character to be lex'd */
+int	yydone;		/* tell yylex to give up */
+char	*yybuffer;	/* first parsed character */
+char	*yyend;		/* end of buffer to be parsed */
+Node	*root;
+Field	*firstfield;
+Field	*lastfield;
+Node	*usender;
+Node	*usys;
+Node	*udate;
+char	*startfield, *endfield;
+int	originator;
+int	destination;
+int	date;
+int	received;
+int	messageid;
+%}
+
+%term WORD
+%term DATE
+%term RESENT_DATE
+%term RETURN_PATH
+%term FROM
+%term SENDER
+%term REPLY_TO
+%term RESENT_FROM
+%term RESENT_SENDER
+%term RESENT_REPLY_TO
+%term SUBJECT
+%term TO
+%term CC
+%term BCC
+%term RESENT_TO
+%term RESENT_CC
+%term RESENT_BCC
+%term REMOTE
+%term PRECEDENCE
+%term MIMEVERSION
+%term CONTENTTYPE
+%term MESSAGEID
+%term RECEIVED
+%term MAILER
+%term BADTOKEN
+%start msg
+%%
+
+msg		: fields
+		| unixfrom '\n' fields
+		;
+fields		: '\n'
+			{ yydone = 1; }
+		| field '\n'
+		| field '\n' fields
+		;
+field		: dates
+			{ date = 1; }
+		| originator
+			{ originator = 1; }
+		| destination
+			{ destination = 1; }
+		| subject
+		| optional
+		| ignored
+		| received
+		| precedence
+		| error '\n' field
+		;
+unixfrom	: FROM route_addr unix_date_time REMOTE FROM word
+			{ freenode($1); freenode($4); freenode($5);
+			  usender = $2; udate = $3; usys = $6;
+			}
+		;
+originator	: REPLY_TO ':' address_list
+			{ newfield(link3($1, $2, $3), 1); }
+		| RETURN_PATH ':' route_addr
+			{ newfield(link3($1, $2, $3), 1); }
+		| FROM ':' mailbox_list
+			{ newfield(link3($1, $2, $3), 1); }
+		| SENDER ':' mailbox
+			{ newfield(link3($1, $2, $3), 1); }
+		| RESENT_REPLY_TO ':' address_list
+			{ newfield(link3($1, $2, $3), 1); }
+		| RESENT_SENDER ':' mailbox
+			{ newfield(link3($1, $2, $3), 1); }
+		| RESENT_FROM ':' mailbox
+			{ newfield(link3($1, $2, $3), 1); }
+		;
+dates 		: DATE ':' date_time
+			{ newfield(link3($1, $2, $3), 0); }
+		| RESENT_DATE ':' date_time
+			{ newfield(link3($1, $2, $3), 0); }
+		;
+destination	: TO ':'
+			{ newfield(link2($1, $2), 0); }
+		| TO ':' address_list
+			{ newfield(link3($1, $2, $3), 0); }
+		| RESENT_TO ':'
+			{ newfield(link2($1, $2), 0); }
+		| RESENT_TO ':' address_list
+			{ newfield(link3($1, $2, $3), 0); }
+		| CC ':'
+			{ newfield(link2($1, $2), 0); }
+		| CC ':' address_list
+			{ newfield(link3($1, $2, $3), 0); }
+		| RESENT_CC ':'
+			{ newfield(link2($1, $2), 0); }
+		| RESENT_CC ':' address_list
+			{ newfield(link3($1, $2, $3), 0); }
+		| BCC ':'
+			{ newfield(link2($1, $2), 0); }
+		| BCC ':' address_list
+			{ newfield(link3($1, $2, $3), 0); }
+		| RESENT_BCC ':' 
+			{ newfield(link2($1, $2), 0); }
+		| RESENT_BCC ':' address_list
+			{ newfield(link3($1, $2, $3), 0); }
+		;
+subject		: SUBJECT ':' things
+			{ newfield(link3($1, $2, $3), 0); }
+		| SUBJECT ':'
+			{ newfield(link2($1, $2), 0); }
+		;
+received	: RECEIVED ':' things
+			{ newfield(link3($1, $2, $3), 0); received++; }
+		| RECEIVED ':'
+			{ newfield(link2($1, $2), 0); received++; }
+		;
+precedence	: PRECEDENCE ':' things
+			{ newfield(link3($1, $2, $3), 0); }
+		| PRECEDENCE ':'
+			{ newfield(link2($1, $2), 0); }
+		;
+ignored		: ignoredhdr ':' things
+			{ newfield(link3($1, $2, $3), 0); }
+		| ignoredhdr ':'
+			{ newfield(link2($1, $2), 0); }
+		;
+ignoredhdr	: MIMEVERSION | CONTENTTYPE | MESSAGEID { messageid = 1; } | MAILER
+		;
+optional	: fieldwords ':' things
+			{ /* hack to allow same lex for field names and the rest */
+			 if(badfieldname($1)){
+				freenode($1);
+				freenode($2);
+				freenode($3);
+				return 1;
+			 }
+			 newfield(link3($1, $2, $3), 0);
+			}
+		| fieldwords ':'
+			{ /* hack to allow same lex for field names and the rest */
+			 if(badfieldname($1)){
+				freenode($1);
+				freenode($2);
+				return 1;
+			 }
+			 newfield(link2($1, $2), 0);
+			}
+		;
+address_list	: address
+		| address_list ',' address
+			{ $$ = link3($1, $2, $3); }
+		;
+address		: mailbox
+		| group
+		;
+group		: phrase ':' address_list ';'
+			{ $$ = link2($1, link3($2, $3, $4)); }
+		| phrase ':' ';'
+			{ $$ = link3($1, $2, $3); }
+		;
+mailbox_list	: mailbox
+		| mailbox_list ',' mailbox
+			{ $$ = link3($1, $2, $3); }
+		;
+mailbox		: route_addr
+		| phrase brak_addr
+			{ $$ = link2($1, $2); }
+		| brak_addr
+		;
+brak_addr	: '<' route_addr '>'
+			{ $$ = link3($1, $2, $3); }
+		| '<' '>'
+			{ $$ = nobody($2); freenode($1); }
+		;
+route_addr	: route ':' at_addr
+			{ $$ = address(concat($1, concat($2, $3))); }
+		| addr_spec
+		;
+route		: '@' domain
+			{ $$ = concat($1, $2); }
+		| route ',' '@' domain
+			{ $$ = concat($1, concat($2, concat($3, $4))); }
+		;
+addr_spec	: local_part
+			{ $$ = address($1); }
+		| at_addr
+		;
+at_addr		: local_part '@' domain
+			{ $$ = address(concat($1, concat($2, $3)));}
+		| at_addr '@' domain
+			{ $$ = address(concat($1, concat($2, $3)));}
+		;
+local_part	: word
+		;
+domain		: word
+		;
+phrase		: word
+		| phrase word
+			{ $$ = link2($1, $2); }
+		;
+things		: thing
+		| things thing
+			{ $$ = link2($1, $2); }
+		;
+thing		: word | '<' | '>' | '@' | ':' | ';' | ','
+		;
+date_time	: things
+		;
+unix_date_time	: word word word unix_time word word
+			{ $$ = link3($1, $3, link3($2, $6, link2($4, $5))); }
+		;
+unix_time	: word
+		| unix_time ':' word
+			{ $$ = link3($1, $2, $3); }
+		;
+word		: WORD | DATE | RESENT_DATE | RETURN_PATH | FROM | SENDER
+		| REPLY_TO | RESENT_FROM | RESENT_SENDER | RESENT_REPLY_TO
+		| TO | CC | BCC | RESENT_TO | RESENT_CC | RESENT_BCC | REMOTE | SUBJECT
+		| PRECEDENCE | MIMEVERSION | CONTENTTYPE | MESSAGEID | RECEIVED | MAILER
+		;
+fieldwords	: fieldword
+		| WORD
+		| fieldwords fieldword
+			{ $$ = link2($1, $2); }
+		| fieldwords word
+			{ $$ = link2($1, $2); }
+		;
+fieldword	: '<' | '>' | '@' | ';' | ','
+		;
+%%
+
+/*
+ *  Initialize the parsing.  Done once for each header field.
+ */
+void
+yyinit(char *p, int len)
+{
+	yybuffer = p;
+	yylp = p;
+	yyend = p + len;
+	firstfield = lastfield = 0;
+	received = 0;
+}
+
+/*
+ *  keywords identifying header fields we care about
+ */
+typedef struct Keyword	Keyword;
+struct Keyword {
+	char	*rep;
+	int	val;
+};
+
+/* field names that we need to recognize */
+Keyword key[] = {
+	{ "date", DATE },
+	{ "resent-date", RESENT_DATE },
+	{ "return_path", RETURN_PATH },
+	{ "from", FROM },
+	{ "sender", SENDER },
+	{ "reply-to", REPLY_TO },
+	{ "resent-from", RESENT_FROM },
+	{ "resent-sender", RESENT_SENDER },
+	{ "resent-reply-to", RESENT_REPLY_TO },
+	{ "to", TO },
+	{ "cc", CC },
+	{ "bcc", BCC },
+	{ "resent-to", RESENT_TO },
+	{ "resent-cc", RESENT_CC },
+	{ "resent-bcc", RESENT_BCC },
+	{ "remote", REMOTE },
+	{ "subject", SUBJECT },
+	{ "precedence", PRECEDENCE },
+	{ "mime-version", MIMEVERSION },
+	{ "content-type", CONTENTTYPE },
+	{ "message-id", MESSAGEID },
+	{ "received", RECEIVED },
+	{ "mailer", MAILER },
+	{ "who-the-hell-cares", WORD }
+};
+
+/*
+ *  Lexical analysis for an rfc822 header field.  Continuation lines
+ *  are handled in yywhite() when skipping over white space.
+ *
+ */
+int
+yylex(void)
+{
+	String *t;
+	int quoting;
+	int escaping;
+	char *start;
+	Keyword *kp;
+	int c, d;
+
+/*	print("lexing\n"); /**/
+	if(yylp >= yyend)
+		return 0;
+	if(yydone)
+		return 0;
+
+	quoting = escaping = 0;
+	start = yylp;
+	yylval = malloc(sizeof(Node));
+	yylval->white = yylval->s = 0;
+	yylval->next = 0;
+	yylval->addr = 0;
+	yylval->start = yylp;
+	for(t = 0; yylp < yyend; yylp++){
+		c = *yylp & 0xff;
+
+		/* dump nulls, they can't be in header */
+		if(c == 0)
+			continue;
+
+		if(escaping) {
+			escaping = 0;
+		} else if(quoting) {
+			switch(c){
+			case '\\':
+				escaping = 1;
+				break;
+			case '\n':
+				d = (*(yylp+1))&0xff;
+				if(d != ' ' && d != '\t'){
+					quoting = 0;
+					yylp--;
+					continue;
+				}
+				break;
+			case '"':
+				quoting = 0;
+				break;
+			}
+		} else {
+			switch(c){
+			case '\\':
+				escaping = 1;
+				break;
+			case '(':
+			case ' ':
+			case '\t':
+			case '\r':
+				goto out;
+			case '\n':
+				if(yylp == start){
+					yylp++;
+/*					print("lex(c %c)\n", c); /**/
+					yylval->end = yylp;
+					return yylval->c = c;
+				}
+				goto out;
+			case '@':
+			case '>':
+			case '<':
+			case ':':
+			case ',':
+			case ';':
+				if(yylp == start){
+					yylp++;
+					yylval->white = yywhite();
+/*					print("lex(c %c)\n", c); /**/
+					yylval->end = yylp;
+					return yylval->c = c;
+				}
+				goto out;
+			case '"':
+				quoting = 1;
+				break;
+			default:
+				break;
+			}
+		}
+		if(t == 0)
+			t = s_new();
+		s_putc(t, c);
+	}
+out:
+	yylval->white = yywhite();
+	if(t) {
+		s_terminate(t);
+	} else				/* message begins with white-space! */
+		return yylval->c = '\n';
+	yylval->s = t;
+	for(kp = key; kp->val != WORD; kp++)
+		if(cistrcmp(s_to_c(t), kp->rep)==0)
+			break;
+/*	print("lex(%d) %s\n", kp->val-WORD, s_to_c(t)); /**/
+	yylval->end = yylp;
+	return yylval->c = kp->val;
+}
+
+void
+yyerror(char *x)
+{
+	USED(x);
+
+	/*fprint(2, "parse err: %s\n", x);/**/
+}
+
+/*
+ *  parse white space and comments
+ */
+String *
+yywhite(void)
+{
+	String *w;
+	int clevel;
+	int c;
+	int escaping;
+
+	escaping = clevel = 0;
+	for(w = 0; yylp < yyend; yylp++){
+		c = *yylp & 0xff;
+
+		/* dump nulls, they can't be in header */
+		if(c == 0)
+			continue;
+
+		if(escaping){
+			escaping = 0;
+		} else if(clevel) {
+			switch(c){
+			case '\n':
+				/*
+				 *  look for multiline fields
+				 */
+				if(*(yylp+1)==' ' || *(yylp+1)=='\t')
+					break;
+				else
+					goto out;
+			case '\\':
+				escaping = 1;
+				break;
+			case '(':
+				clevel++;
+				break;
+			case ')':
+				clevel--;
+				break;
+			}
+		} else {
+			switch(c){
+			case '\\':
+				escaping = 1;
+				break;
+			case '(':
+				clevel++;
+				break;
+			case ' ':
+			case '\t':
+			case '\r':
+				break;
+			case '\n':
+				/*
+				 *  look for multiline fields
+				 */
+				if(*(yylp+1)==' ' || *(yylp+1)=='\t')
+					break;
+				else
+					goto out;
+			default:
+				goto out;
+			}
+		}
+		if(w == 0)
+			w = s_new();
+		s_putc(w, c);
+	}
+out:
+	if(w)
+		s_terminate(w);
+	return w;
+}
+
+/*
+ *  link two parsed entries together
+ */
+Node*
+link2(Node *p1, Node *p2)
+{
+	Node *p;
+
+	for(p = p1; p->next; p = p->next)
+		;
+	p->next = p2;
+	return p1;
+}
+
+/*
+ *  link three parsed entries together
+ */
+Node*
+link3(Node *p1, Node *p2, Node *p3)
+{
+	Node *p;
+
+	for(p = p2; p->next; p = p->next)
+		;
+	p->next = p3;
+
+	for(p = p1; p->next; p = p->next)
+		;
+	p->next = p2;
+
+	return p1;
+}
+
+/*
+ *  make a:b, move all white space after both
+ */
+Node*
+colon(Node *p1, Node *p2)
+{
+	if(p1->white){
+		if(p2->white)
+			s_append(p1->white, s_to_c(p2->white));
+	} else {
+		p1->white = p2->white;
+		p2->white = 0;
+	}
+
+	s_append(p1->s, ":");
+	if(p2->s)
+		s_append(p1->s, s_to_c(p2->s));
+
+	if(p1->end < p2->end)
+		p1->end = p2->end;
+	freenode(p2);
+	return p1;
+}
+
+/*
+ *  concatenate two fields, move all white space after both
+ */
+Node*
+concat(Node *p1, Node *p2)
+{
+	char buf[2];
+
+	if(p1->white){
+		if(p2->white)
+			s_append(p1->white, s_to_c(p2->white));
+	} else {
+		p1->white = p2->white;
+		p2->white = 0;
+	}
+
+	if(p1->s == nil){
+		buf[0] = p1->c;
+		buf[1] = 0;
+		p1->s = s_new();
+		s_append(p1->s, buf);
+	}
+
+	if(p2->s)
+		s_append(p1->s, s_to_c(p2->s));
+	else {
+		buf[0] = p2->c;
+		buf[1] = 0;
+		s_append(p1->s, buf);
+	}
+
+	if(p1->end < p2->end)
+		p1->end = p2->end;
+	freenode(p2);
+	return p1;
+}
+
+/*
+ *  look for disallowed chars in the field name
+ */
+int
+badfieldname(Node *p)
+{
+	for(; p; p = p->next){
+		/* field name can't contain white space */
+		if(p->white && p->next)
+			return 1;
+	}
+	return 0;
+}
+
+/*
+ *  mark as an address
+ */
+Node *
+address(Node *p)
+{
+	p->addr = 1;
+	return p;
+}
+
+/*
+ *  case independent string compare
+ */
+int
+cistrcmp(char *s1, char *s2)
+{
+	int c1, c2;
+
+	for(; *s1; s1++, s2++){
+		c1 = isupper(*s1) ? tolower(*s1) : *s1;
+		c2 = isupper(*s2) ? tolower(*s2) : *s2;
+		if (c1 != c2)
+			return -1;
+	}
+	return *s2;
+}
+
+/*
+ *  free a node
+ */
+void
+freenode(Node *p)
+{
+	Node *tp;
+
+	while(p){
+		tp = p->next;
+		if(p->s)
+			s_free(p->s);
+		if(p->white)
+			s_free(p->white);
+		free(p);
+		p = tp;
+	}
+}
+
+
+/*
+ *  an anonymous user
+ */
+Node*
+nobody(Node *p)
+{
+	if(p->s)
+		s_free(p->s);
+	p->s = s_copy("pOsTmAsTeR");
+	p->addr = 1;
+	return p;
+}
+
+/*
+ *  add anything that was dropped because of a parse error
+ */
+void
+missing(Node *p)
+{
+	Node *np;
+	char *start, *end;
+	Field *f;
+	String *s;
+
+	start = yybuffer;
+	if(lastfield != nil){
+		for(np = lastfield->node; np; np = np->next)
+			start = np->end+1;
+	}
+
+	end = p->start-1;
+
+	if(end <= start)
+		return;
+
+	if(strncmp(start, "From ", 5) == 0)
+		return;
+
+	np = malloc(sizeof(Node));
+	np->start = start;
+	np->end = end;
+	np->white = nil;
+	s = s_copy("BadHeader: ");
+	np->s = s_nappend(s, start, end-start);
+	np->next = nil;
+
+	f = malloc(sizeof(Field));
+	f->next = 0;
+	f->node = np;
+	f->source = 0;
+	if(firstfield)
+		lastfield->next = f;
+	else
+		firstfield = f;
+	lastfield = f;
+}
+
+/*
+ *  create a new field
+ */
+void
+newfield(Node *p, int source)
+{
+	Field *f;
+
+	missing(p);
+
+	f = malloc(sizeof(Field));
+	f->next = 0;
+	f->node = p;
+	f->source = source;
+	if(firstfield)
+		lastfield->next = f;
+	else
+		firstfield = f;
+	lastfield = f;
+	endfield = startfield;
+	startfield = yylp;
+}
+
+/*
+ *  fee a list of fields
+ */
+void
+freefield(Field *f)
+{
+	Field *tf;
+
+	while(f){
+		tf = f->next;
+		freenode(f->node);
+		free(f);
+		f = tf;
+	}
+}
+
+/*
+ *  add some white space to a node
+ */
+Node*
+whiten(Node *p)
+{
+	Node *tp;
+
+	for(tp = p; tp->next; tp = tp->next)
+		;
+	if(tp->white == 0)
+		tp->white = s_copy(" ");
+	return p;
+}
+
+void
+yycleanup(void)
+{
+	Field *f, *fnext;
+	Node *np, *next;
+
+	for(f = firstfield; f; f = fnext){
+		for(np = f->node; np; np = next){
+			if(np->s)
+				s_free(np->s);
+			if(np->white)
+				s_free(np->white);
+			next = np->next;
+			free(np);
+		}
+		fnext = f->next;
+		free(f);
+	}
+	firstfield = lastfield = 0;
+}
blob - /dev/null
blob + 54b679bb9249bdd7a34af2bafb2f53e6fd4441a3 (mode 644)
--- /dev/null
+++ src/cmd/upas/smtp/rmtdns.c
@@ -0,0 +1,58 @@
+#include	"common.h"
+#include	<ndb.h>
+
+int
+rmtdns(char *net, char *path)
+{
+
+	int fd, n, r;
+	char *domain, *cp, buf[1024];
+
+	if(net == 0 || path == 0)
+		return 0;
+
+	domain = strdup(path);
+	cp = strchr(domain, '!');
+	if(cp){
+		*cp = 0;
+		n = cp-domain;
+	} else
+		n = strlen(domain);
+
+	if(*domain == '[' && domain[n-1] == ']'){	/* accept [nnn.nnn.nnn.nnn] */
+		domain[n-1] = 0;
+		r = strcmp(ipattr(domain+1), "ip");
+		domain[n-1] = ']';
+	} else
+		r = strcmp(ipattr(domain), "ip");	/* accept nnn.nnn.nnn.nnn */
+
+	if(r == 0){
+		free(domain);
+		return 0;
+	}
+
+	snprint(buf, sizeof(buf), "%s/dns", net);
+
+	fd = open(buf, ORDWR);			/* look up all others */
+	if(fd < 0){				/* dns screw up - can't check */
+		free(domain);
+		return 0;
+	}
+
+	n = snprint(buf, sizeof(buf), "%s all", domain);
+	free(domain);
+	seek(fd, 0, 0);
+	n = write(fd, buf, n);
+	close(fd);
+	if(n < 0){
+		rerrstr(buf, sizeof(buf));
+		if (strcmp(buf, "dns: name does not exist") == 0)
+			return -1;
+	}
+	return 0;
+}
+
+/*
+void main(int, char *argv[]){ print("return = %d\n", rmtdns("/net.alt/tcp/109", argv[1]));}
+
+*/
blob - /dev/null
blob + e88154f78a2ae2ec215a72aeb575949cafb1f7da (mode 644)
--- /dev/null
+++ src/cmd/upas/smtp/smtp.c
@@ -0,0 +1,1122 @@
+#include "common.h"
+#include "smtp.h"
+#include <ctype.h>
+#include <mp.h>
+#include <libsec.h>
+#include <auth.h>
+#include <ndb.h>
+
+static	char*	connect(char*);
+static	char*	dotls(char*);
+static	char*	doauth(char*);
+char*	hello(char*, int);
+char*	mailfrom(char*);
+char*	rcptto(char*);
+char*	data(String*, Biobuf*);
+void	quit(char*);
+int	getreply(void);
+void	addhostdom(String*, char*);
+String*	bangtoat(char*);
+String*	convertheader(String*);
+int	printheader(void);
+char*	domainify(char*, char*);
+void	putcrnl(char*, int);
+char*	getcrnl(String*);
+int	printdate(Node*);
+char	*rewritezone(char *);
+int	dBprint(char*, ...);
+int	dBputc(int);
+String*	fixrouteaddr(String*, Node*, Node*);
+char* expand_addr(char* a);
+int	ping;
+int	insecure;
+
+#define Retry	"Retry, Temporary Failure"
+#define Giveup	"Permanent Failure"
+
+int	debug;		/* true if we're debugging */
+String	*reply;		/* last reply */
+String	*toline;
+int	alarmscale;
+int	last = 'n';	/* last character sent by putcrnl() */
+int	filter;
+int	trysecure;	/* Try to use TLS if the other side supports it */
+int	tryauth;	/* Try to authenticate, if supported */
+int	quitting;	/* when error occurs in quit */
+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];
+Biobuf	bin;
+Biobuf	bout;
+Biobuf	berr;
+Biobuf	bfile;
+
+void
+usage(void)
+{
+	fprint(2, "usage: smtp [-adips] [-uuser] [-hhost] [.domain] net!host[!service] sender rcpt-list\n");
+	exits(Giveup); 
+}
+
+int
+timeout(void *x, char *msg)
+{
+	USED(x);
+	syslog(0, "smtp.fail", "interrupt: %s: %s", farend,  msg);
+	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")){
+			/* call _exits() to prevent Bio from trying to flush closed pipe */
+		fprint(2, "smtp timeout: connection closed to %s\n", farend);
+		if(quitting){
+			syslog(0, "smtp.fail", "closed pipe to %s", farend);
+			_exits(quitrv);
+		}
+		_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
+threadmain(int argc, char **argv)
+{
+	char hellodomain[256];
+	char *host, *domain;
+	String *from;
+	String *fromm;
+	String *sender;
+	char *addr;
+	char *rv, *trv;
+	int i, ok, rcvrs;
+	char **errs;
+
+	alarmscale = 60*1000;	/* minutes */
+	quotefmtinstall();
+	errs = malloc(argc*sizeof(char*));
+	reply = s_new();
+	host = 0;
+	ARGBEGIN{
+	case 'a':
+		tryauth = 1;
+		trysecure = 1;
+		break;
+	case 'f':
+		filter = 1;
+		break;
+	case 'd':
+		debug = 1;
+		break;
+	case 'g':
+		gdomain = ARGF();
+		break;
+	case 'h':
+		host = ARGF();
+		break;
+	case 'i':
+		insecure = 1;
+		break;
+	case 'p':
+		alarmscale = 10*1000;	/* tens of seconds */
+		ping = 1;
+		break;
+	case 's':
+		trysecure = 1;
+		break;
+	case 'u':
+		user = ARGF();
+		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();
+	strcpy(hostdomain, domainify(host, domain));
+	strcpy(hellodomain, domainify(sysname_read(), domain));
+
+	/*
+	 *  get destination address
+	 */
+	if(*argv == 0)
+		usage();
+	addr = *argv++; argc--;
+	// expand $smtp if necessary
+	addr = expand_addr(addr);
+	farend = addr;
+
+	/*
+	 *  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
+	 */
+	if(filter){
+		Binit(&bout, 1, OWRITE);
+		rv = data(from, &bfile);
+		if(rv != 0)
+			goto error;
+		exits(0);
+	}
+
+	/* 10 minutes to get through the initial handshake */
+	atnotify(timeout, 1);
+
+	alarm(10*alarmscale);
+	if((rv = connect(addr)) != 0)
+		exits(rv);
+	alarm(10*alarmscale);
+	if((rv = hello(hellodomain, 0)) != 0)
+		goto error;
+	alarm(10*alarmscale);
+	if((rv = mailfrom(s_to_c(from))) != 0)
+		goto error;
+
+	ok = 0;
+	rcvrs = 0;
+	/* if any rcvrs are ok, we try to send the message */
+	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 || rv == Retry)
+		goto error;
+
+	if(ping){
+		quit(0);
+		exits(0);
+	}
+
+	rv = data(from, &bfile);
+	if(rv != 0)
+		goto error;
+	quit(0);
+	if(rcvrs == ok)
+		exits(0);
+
+	/*
+	 *  here when some but not all rcvrs failed
+	 */
+	fprint(2, "%s connect to %s:\n", thedate(), addr);
+	for(i = 0; i < rcvrs; i++){
+		if(errs[i]){
+			syslog(0, "smtp.fail", "delivery to %s at %s failed: %s", argv[i], addr, errs[i]);
+			fprint(2, "  mail to %s failed: %s", argv[i], errs[i]);
+		}
+	}
+	exits(Giveup);
+
+	/*
+	 *  here when all rcvrs failed
+	 */
+error:
+	removenewline(s_to_c(reply));
+	syslog(0, "smtp.fail", "%s to %s failed: %s",
+		ping ? "ping" : "delivery",
+		addr, s_to_c(reply));
+	fprint(2, "%s connect to %s:\n%s\n", thedate(), addr, s_to_c(reply));
+	if(!filter)
+		quit(rv);
+	exits(rv);
+}
+
+/*
+ *  connect to the remote host
+ */
+static char *
+connect(char* net)
+{
+	char buf[256];
+	int fd;
+
+	fd = mxdial(net, ddomain, gdomain);
+
+	if(fd < 0){
+		rerrstr(buf, sizeof(buf));
+		Bprint(&berr, "smtp: %s (%s)\n", buf, net);
+		syslog(0, "smtp.fail", "%s (%s)", buf, net);
+		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";
+
+/*
+ *  exchange names with remote host, attempt to
+ *  enable encryption and optionally authenticate.
+ *  not fatal if we can't.
+ */
+static char *
+dotls(char *me)
+{
+	TLSconn *c;
+	Thumbprint *goodcerts;
+	char *h;
+	int fd;
+	uchar hash[SHA1dlen];
+
+	c = mallocz(sizeof(*c), 1);	/* Note: not freed on success */
+	if (c == nil)
+		return Giveup;
+
+	dBprint("STARTTLS\r\n");
+	if (getreply() != 2)
+		return Giveup;
+
+	fd = tlsClient(Bfildes(&bout), c);
+	if (fd < 0) {
+		syslog(0, "smtp", "tlsClient to %q: %r", ddomain);
+		return Giveup;
+	}
+	goodcerts = initThumbprints(smtpthumbs, smtpexclthumbs);
+	if (goodcerts == nil) {
+		free(c);
+		close(fd);
+		syslog(0, "smtp", "bad thumbprints in %s", smtpthumbs);
+		return Giveup;		/* how to recover? TLS is started */
+	}
+
+	/* compute sha1 hash of remote's certificate, see if we know it */
+	sha1(c->cert, c->certlen, hash, nil);
+	if (!okThumbprint(hash, goodcerts)) {
+		/* TODO? if not excluded, add hash to thumb list */
+		free(c);
+		close(fd);
+		h = malloc(2*sizeof hash + 1);
+		if (h != nil) {
+			enc16(h, 2*sizeof hash + 1, hash, sizeof hash);
+			// print("x509 sha1=%s", h);
+			syslog(0, "smtp",
+		"remote cert. has bad thumbprint: x509 sha1=%s server=%q",
+				h, ddomain);
+			free(h);
+		}
+		return Giveup;		/* how to recover? TLS is started */
+	}
+	freeThumbprints(goodcerts);
+	Bterm(&bin);
+	Bterm(&bout);
+
+	/*
+	 * set up bin & bout to use the TLS fd, i/o upon which generates
+	 * i/o on the original, underlying fd.
+	 */
+	Binit(&bin, fd, OREAD);
+	fd = dup(fd, -1);
+	Binit(&bout, fd, OWRITE);
+
+	syslog(0, "smtp", "started TLS to %q", ddomain);
+	return(hello(me, 1));
+}
+
+static char *
+doauth(char *methods)
+{
+	char *buf, *base64;
+	int n;
+	DS ds;
+	UserPasswd *p;
+
+	dial_string_parse(ddomain, &ds);
+
+	if(user != nil)
+		p = auth_getuserpasswd(nil,
+	  	  "proto=pass service=smtp server=%q user=%q", ds.host, user);
+	else
+		p = auth_getuserpasswd(nil,
+	  	  "proto=pass service=smtp server=%q", ds.host);
+	if (p == nil)
+		return Giveup;
+
+	if (strstr(methods, "LOGIN")){
+		dBprint("AUTH LOGIN\r\n");
+		if (getreply() != 3)
+			return Retry;
+
+		n = strlen(p->user);
+		base64 = malloc(2*n);
+		if (base64 == nil)
+			return Retry;	/* Out of memory */
+		enc64(base64, 2*n, (uchar *)p->user, n);
+		dBprint("%s\r\n", base64);
+		if (getreply() != 3)
+			return Retry;
+
+		n = strlen(p->passwd);
+		base64 = malloc(2*n);
+		if (base64 == nil)
+			return Retry;	/* Out of memory */
+		enc64(base64, 2*n, (uchar *)p->passwd, n);
+		dBprint("%s\r\n", base64);
+		if (getreply() != 2)
+			return Retry;
+
+		free(base64);
+	}
+	else
+	if (strstr(methods, "PLAIN")){
+		n = strlen(p->user) + strlen(p->passwd) + 3;
+		buf = malloc(n);
+		base64 = malloc(2 * n);
+		if (buf == nil || base64 == nil) {
+			free(buf);
+			return Retry;	/* Out of memory */
+		}
+		snprint(buf, n, "%c%s%c%s", 0, p->user, 0, p->passwd);
+		enc64(base64, 2 * n, (uchar *)buf, n - 1);
+		free(buf);
+		dBprint("AUTH PLAIN %s\r\n", base64);
+		free(base64);
+		if (getreply() != 2)
+			return Retry;
+	}
+	else
+		return "No supported AUTH method";
+	return(0);
+}
+
+char *
+hello(char *me, int encrypted)
+{
+	int ehlo;
+	String *r;
+	char *ret, *s, *t;
+
+	if (!encrypted)
+		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';
+		for (t = s; *t != '\0'; t++)
+			*t = toupper(*t);
+		if(!encrypted && trysecure &&
+		    (strcmp(s, "250-STARTTLS") == 0 ||
+		     strcmp(s, "250 STARTTLS") == 0)){
+			s_free(r);
+			return(dotls(me));
+		}
+		if(tryauth && (encrypted || insecure) &&
+		    (strncmp(s, "250 AUTH", strlen("250 AUTH")) == 0 ||
+		     strncmp(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:
+		break;
+	case 5:
+		return Giveup;
+	default:
+		return Retry;
+	}
+	return 0;
+}
+
+/*
+ *  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;
+}
+
+static char hex[] = "0123456789abcdef";
+
+/*
+ *  send the damn thing
+ */
+char *
+data(String *from, Biobuf *b)
+{
+	char *buf, *cp;
+	int i, n, nbytes, bufsize, eof, r;
+	String *fromline;
+	char errmsg[Errlen];
+	char id[40];
+
+	/*
+	 *  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;
+
+	srand(truerand());
+	if(messageid == 0){
+		for(i=0; i<16; i++){
+			r = rand()&0xFF;
+			id[2*i] = hex[r&0xF];
+			id[2*i+1] = hex[(r>>4)&0xF];
+		}
+		id[2*i] = '\0';
+		nbytes += Bprint(&bout, "Message-ID: <%s@%s>\r\n", id, hostdomain);
+		if(debug)
+			Bprint(&berr, "Message-ID: <%s@%s>\r\n", id, hostdomain);
+	}	
+
+	if(originator==0){
+		nbytes += Bprint(&bout, "From: %s\r\n", s_to_c(fromline));
+		if(debug)
+			Bprint(&berr, "From: %s\r\n", s_to_c(fromline));
+	}
+	s_free(fromline);
+
+	if(destination == 0 && toline)
+		if(*s_to_c(toline) == '@'){	/* route addr */
+			nbytes += Bprint(&bout, "To: <%s>\r\n", s_to_c(toline));
+			if(debug)
+				Bprint(&berr, "To: <%s>\r\n", s_to_c(toline));
+		} else {
+			nbytes += Bprint(&bout, "To: %s\r\n", s_to_c(toline));
+			if(debug)
+				Bprint(&berr, "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", s_to_c(from),
+				nbytes, s_to_c(toline));/**/
+	}
+	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(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:c!d@b.p.o
+ */
+String *
+bangtoat(char *addr)
+{
+	String *buf;
+	register int i;
+	int j, d;
+	char *field[128];
+
+	/* 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)
+{
+	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){
+		a = username(from);
+		if(a) {
+			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)
+{
+	int n, len;
+	Field *f;
+	Node *p;
+	char *cp;
+	char c[1];
+
+	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)
+{
+	static String *s;
+	char *p;
+
+	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;
+	int 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'){
+				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;
+		}
+	}
+	return 0;
+}
+
+/*
+ *  print out a parsed date
+ */
+int
+printdate(Node *p)
+{
+	int n, sep = 0;
+
+	n = dBprint("Date: %s,", s_to_c(p->s));
+	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)
+{
+	int mindiff;
+	char s;
+	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
+ */
+#define	SIZE	4096
+int
+dBprint(char *fmt, ...)
+{
+	char buf[SIZE], *out;
+	va_list arg;
+	int n;
+
+	va_start(arg, fmt);
+	out = vseprint(buf, buf+SIZE, fmt, arg);
+	va_end(arg);
+	if(debug){
+		Bwrite(&berr, buf, (long)(out-buf));
+		Bflush(&berr);
+	}
+	n = Bwrite(&bout, buf, (long)(out-buf));
+	Bflush(&bout);
+	return n;
+}
+
+int
+dBputc(int x)
+{
+	if(debug)
+		Bputc(&berr, x);
+	return Bputc(&bout, x);
+}
+
+char* 
+expand_addr(char* a)
+{
+	Ndb *db;
+	Ndbs s;
+	char *sys, *ret, *proto, *host;
+
+	proto = strtok(a,"!");
+	if ( strcmp(proto,"net") != 0 ) {
+		fprint(2,"unknown proto %s\n",proto);
+	}
+	host = strtok(0,"!");
+	if ( strcmp(host,"$smtp") == 0 ) {
+		sys = sysname();
+		db = ndbopen(unsharp("#9/ndb/local"));
+		host = ndbgetvalue(db, &s, "sys", sys, "smtp", nil);
+	}
+	ret = malloc(strlen(proto)+strlen(host)+2);
+	sprint(ret,"%s!%s",proto,host);
+
+	return ret;
+
+}
blob - /dev/null
blob + 836192237d97db9a6e5935617c67cd18d117eb8c (mode 644)
--- /dev/null
+++ src/cmd/upas/smtp/smtp.h
@@ -0,0 +1,61 @@
+typedef struct Node Node;
+typedef struct Field Field;
+typedef Node *Nodeptr;
+#define YYSTYPE Nodeptr
+
+struct Node {
+	Node	*next;
+	int	c;	/* token type */
+	char	addr;	/* true if this is an address */
+	String	*s;	/* string representing token */
+	String	*white;	/* white space following token */
+	char	*start;	/* first byte for this token */
+	char	*end;	/* next byte in input */
+};
+
+struct Field {
+	Field	*next;
+	Node	*node;
+	int	source;
+};
+
+typedef struct DS	DS;
+struct DS {
+	/* dist string */
+	char	buf[128];
+	char	expand[128];
+	char	*netdir;
+	char	*proto;
+	char	*host;
+	char	*service;
+};
+
+extern Field	*firstfield;
+extern Field	*lastfield;
+extern Node	*usender;
+extern Node	*usys;
+extern Node	*udate;
+extern int	originator;
+extern int	destination;
+extern int	date;
+extern int	messageid;
+
+Node*	anonymous(Node*);
+Node*	address(Node*);
+int	badfieldname(Node*);
+Node*	bang(Node*, Node*);
+Node*	colon(Node*, Node*);
+int	cistrcmp(char*, char*);
+Node*	link2(Node*, Node*);
+Node*	link3(Node*, Node*, Node*);
+void	freenode(Node*);
+void	newfield(Node*, int);
+void	freefield(Field*);
+void	yyinit(char*, int);
+int	yyparse(void);
+int	yylex(void);
+String*	yywhite(void);
+Node*	whiten(Node*);
+void	yycleanup(void);
+int	mxdial(char*, char*, char*);
+void	dial_string_parse(char*, DS*);
blob - /dev/null
blob + 7042c37c3bf3b21105adf9408a1c05bb8b198b4c (mode 644)
--- /dev/null
+++ src/cmd/upas/smtp/smtpd.c
@@ -0,0 +1,1494 @@
+#include "common.h"
+#include "smtpd.h"
+#include "smtp.h"
+#include <ctype.h>
+#include <ip.h>
+#include <ndb.h>
+#include <mp.h>
+#include <libsec.h>
+#include <auth.h>
+#include "../smtp/y.tab.h"
+
+#define DBGMX 1
+
+char	*me;
+char	*him="";
+char	*dom;
+process	*pp;
+String	*mailer;
+NetConnInfo *nci;
+
+int	filterstate = ACCEPT;
+int	trusted;
+int	logged;
+int	rejectcount;
+int	hardreject;
+
+Biobuf	bin;
+
+int	debug;
+int	Dflag;
+int	fflag;
+int	gflag;
+int	rflag;
+int	sflag;
+int	authenticate;
+int	authenticated;
+int	passwordinclear;
+char	*tlscert;
+
+List	senders;
+List	rcvers;
+
+char pipbuf[ERRMAX];
+char	*piperror;
+int	pipemsg(int*);
+String*	startcmd(void);
+int	rejectcheck(void);
+String*	mailerpath(char*);
+
+static int
+catchalarm(void *a, char *msg)
+{
+	int rv = 1;
+
+	USED(a);
+
+	/* log alarms but continue */
+	if(strstr(msg, "alarm")){
+		if(senders.first && rcvers.first)
+			syslog(0, "smtpd", "note: %s->%s: %s", s_to_c(senders.first->p),
+				s_to_c(rcvers.first->p), msg);
+		else
+			syslog(0, "smtpd", "note: %s", msg);
+		rv = 0;
+	}
+
+	/* kill the children if there are any */
+	if(pp)
+		syskillpg(pp->pid);
+
+	return rv;
+}
+
+	/* override string error functions to do something reasonable */
+void
+s_error(char *f, char *status)
+{
+	char errbuf[Errlen];
+
+	errbuf[0] = 0;
+	rerrstr(errbuf, sizeof(errbuf));
+	if(f && *f)
+		reply("452 out of memory %s: %s\r\n", f, errbuf);
+	else
+		reply("452 out of memory %s\r\n", errbuf);
+	syslog(0, "smtpd", "++Malloc failure %s [%s]", him, nci->rsys);
+	exits(status);
+}
+
+void
+main(int argc, char **argv)
+{
+	char *p, buf[1024];
+	char *netdir;
+
+	netdir = nil;
+	quotefmtinstall();
+	ARGBEGIN{
+	case 'D':
+		Dflag++;
+		break;
+	case 'd':
+		debug++;
+		break;
+	case 'n':				/* log peer ip address */
+		netdir = ARGF();
+		break;
+	case 'f':				/* disallow relaying */
+		fflag = 1;
+		break;
+	case 'g':
+		gflag = 1;
+		break;
+	case 'h':				/* default domain name */
+		dom = ARGF();
+		break;
+	case 'k':				/* prohibited ip address */
+		p = ARGF();
+		if (p)
+			addbadguy(p);
+		break;
+	case 'm':				/* set mail command */
+		p = ARGF();
+		if(p)
+			mailer = mailerpath(p);
+		break;
+	case 'r':
+		rflag = 1;			/* verify sender's domain */
+		break;
+	case 's':				/* save blocked messages */
+		sflag = 1;
+		break;
+	case 'a':
+		authenticate = 1;
+		break;
+	case 'p':
+		passwordinclear = 1;
+		break;
+	case 'c':
+		tlscert = ARGF();
+		break;
+	case 't':
+		fprint(2, "%s: the -t option is no longer supported, see -c\n", argv0);
+		tlscert = "/sys/lib/ssl/smtpd-cert.pem";
+		break;
+	default:
+		fprint(2, "usage: smtpd [-dfhrs] [-n net] [-c cert]\n");
+		exits("usage");
+	}ARGEND;
+
+	nci = getnetconninfo(netdir, 0);
+	if(nci == nil)
+		sysfatal("can't get remote system's address");
+
+	if(mailer == nil)
+		mailer = mailerpath("send");
+
+	if(debug){
+		close(2);
+		snprint(buf, sizeof(buf), "%s/smtpd", UPASLOG);
+		if (open(buf, OWRITE) >= 0) {
+			seek(2, 0, 2);
+			fprint(2, "%d smtpd %s\n", getpid(), thedate());
+		} else
+			debug = 0;
+	}
+	getconf();
+	Binit(&bin, 0, OREAD);
+
+	chdir(UPASLOG);
+	me = sysname_read();
+	if(dom == 0 || dom[0] == 0)
+		dom = domainname_read();
+	if(dom == 0 || dom[0] == 0)
+		dom = me;
+	sayhi();
+	parseinit();
+		/* allow 45 minutes to parse the header */
+	atnotify(catchalarm, 1);
+	alarm(45*60*1000);
+	zzparse();
+	exits(0);
+}
+
+void
+listfree(List *l)
+{
+	Link *lp;
+	Link *next;
+
+	for(lp = l->first; lp; lp = next){
+		next = lp->next;
+		s_free(lp->p);
+		free(lp);
+	}
+	l->first = l->last = 0;
+}
+
+void
+listadd(List *l, String *path)
+{
+	Link *lp;
+
+	lp = (Link *)malloc(sizeof(Link));
+	lp->p = path;
+	lp->next = 0;
+
+	if(l->last)
+		l->last->next = lp;
+	else
+		l->first = lp;
+	l->last = lp;
+}
+
+#define	SIZE	4096
+int
+reply(char *fmt, ...)
+{
+	char buf[SIZE], *out;
+	va_list arg;
+	int n;
+
+	va_start(arg, fmt);
+	out = vseprint(buf, buf+SIZE, fmt, arg);
+	va_end(arg);
+	n = (long)(out-buf);
+	if(debug) {
+		seek(2, 0, 2);
+		write(2, buf, n);
+	}
+	write(1, buf, n);
+	return n;
+}
+
+void
+reset(void)
+{
+	if(rejectcheck())
+		return;
+	listfree(&rcvers);
+	listfree(&senders);
+	if(filterstate != DIALUP){
+		logged = 0;
+		filterstate = ACCEPT;
+	}
+	reply("250 ok\r\n");
+}
+
+void
+sayhi(void)
+{
+	reply("220 %s SMTP\r\n", dom);
+}
+
+void
+hello(String *himp, int extended)
+{
+	char **mynames;
+
+	him = s_to_c(himp);
+	syslog(0, "smtpd", "%s from %s as %s", extended ? "ehlo" : "helo", nci->rsys, him);
+	if(rejectcheck())
+		return;
+
+	if(strchr(him, '.') && nci && !trusted && fflag && strcmp(nci->rsys, nci->lsys) != 0){
+		/*
+		 * We don't care if he lies about who he is, but it is
+		 * not okay to pretend to be us.  Many viruses do this,
+		 * just parroting back what we say in the greeting.
+		 */
+		if(strcmp(him, dom) == 0)
+			goto Liarliar;
+		for(mynames=sysnames_read(); mynames && *mynames; mynames++){
+			if(cistrcmp(*mynames, him) == 0){
+			Liarliar:
+				syslog(0, "smtpd", "Hung up on %s; claimed to be %s",
+					nci->rsys, him);
+				reply("554 Liar!\r\n");
+				exits("client pretended to be us");
+				return;
+			}
+		}
+	}
+	/*
+	 * it is never acceptable to claim to be "localhost",
+	 * "localhost.localdomain" or "localhost.example.com"; only spammers
+	 * do this.  it should be unacceptable to claim any string that doesn't
+	 * look like a domain name (e.g., has at least one dot in it), but
+	 * Microsoft mail software gets this wrong.
+	 */
+	if (strcmp(him, "localhost") == 0 ||
+	    strcmp(him, "localhost.localdomain") == 0 ||
+	    strcmp(him, "localhost.example.com") == 0)
+		goto Liarliar;
+	if(strchr(him, '.') == 0 && nci != nil && strchr(nci->rsys, '.') != nil)
+		him = nci->rsys;
+
+	if(Dflag)
+		sleep(15*1000);
+	reply("250%c%s you are %s\r\n", extended ? '-' : ' ', dom, him);
+	if (extended) {
+		if(tlscert != nil)
+			reply("250-STARTTLS\r\n");
+		if (passwordinclear)		
+			reply("250 AUTH CRAM-MD5 PLAIN LOGIN\r\n");
+		else
+			reply("250 AUTH CRAM-MD5\r\n");
+	}
+}
+
+void
+sender(String *path)
+{
+	String *s;
+	static char *lastsender;
+
+	if(rejectcheck())
+		return;
+	if (authenticate && !authenticated) {
+		rejectcount++;
+		reply("530 Authentication required\r\n");
+		return;
+	}
+	if(him == 0 || *him == 0){
+		rejectcount++;
+		reply("503 Start by saying HELO, please.\r\n", s_to_c(path));
+		return;
+	}
+
+	/* don't add the domain onto black holes or we will loop */
+	if(strchr(s_to_c(path), '!') == 0 && strcmp(s_to_c(path), "/dev/null") != 0){
+		s = s_new();
+		s_append(s, him);
+		s_append(s, "!");
+		s_append(s, s_to_c(path));
+		s_terminate(s);
+		s_free(path);
+		path = s;
+	}
+	if(shellchars(s_to_c(path))){
+		rejectcount++;
+		reply("503 Bad character in sender address %s.\r\n", s_to_c(path));
+		return;
+	}
+
+	/*
+	 * if the last sender address resulted in a rejection because the sending
+	 * domain didn't exist and this sender has the same domain, reject immediately.
+	 */
+	if(lastsender){
+		if (strncmp(lastsender, s_to_c(path), strlen(lastsender)) == 0){
+			filterstate = REFUSED;
+			rejectcount++;
+			reply("554 Sender domain must exist: %s\r\n", s_to_c(path));
+			return;
+		}
+		free(lastsender);	/* different sender domain */
+		lastsender = 0;
+	}
+
+	/*
+	 * see if this ip address, domain name, user name or account is blocked
+	 */
+	filterstate = blocked(path);
+
+	logged = 0;
+	listadd(&senders, path);
+	reply("250 sender is %s\r\n", s_to_c(path));
+}
+
+enum { Rcpt, Domain, Ntoks };
+
+typedef struct Sender Sender;
+struct Sender {
+	Sender	*next;
+	char	*rcpt;
+	char	*domain;
+};
+static Sender *sendlist, *sendlast;
+static uchar rsysip[IPaddrlen];
+
+static int
+rdsenders(void)
+{
+	int lnlen, nf, ok = 1;
+	char *line, *senderfile;
+	char *toks[Ntoks];
+	Biobuf *sf;
+	Sender *snd;
+	static int beenhere = 0;
+
+	if (beenhere)
+		return 1;
+	beenhere = 1;
+
+	fmtinstall('I', eipfmt);
+	parseip(rsysip, nci->rsys);
+
+	/*
+	 * we're sticking with a system-wide sender list because
+	 * per-user lists would require fully resolving recipient
+	 * addresses to determine which users they correspond to
+	 * (barring syntactic conventions).
+	 */
+	senderfile = smprint("%s/senders", UPASLIB);
+	sf = Bopen(senderfile, OREAD);
+	free(senderfile);
+	if (sf == nil)
+		return 1;
+	while ((line = Brdline(sf, '\n')) != nil) {
+		if (line[0] == '#' || line[0] == '\n')
+			continue;
+		lnlen = Blinelen(sf);
+		line[lnlen-1] = '\0';		/* clobber newline */
+		nf = tokenize(line, toks, nelem(toks));
+		if (nf != nelem(toks))
+			continue;		/* malformed line */
+
+		snd = malloc(sizeof *snd);
+		if (snd == nil)
+			sysfatal("out of memory: %r");
+		memset(snd, 0, sizeof *snd);
+		snd->next = nil;
+
+		if (sendlast == nil)
+			sendlist = snd;
+		else
+			sendlast->next = snd;
+		sendlast = snd;
+		snd->rcpt = strdup(toks[Rcpt]);
+		snd->domain = strdup(toks[Domain]);
+	}
+	Bterm(sf);
+	return ok;
+}
+
+/*
+ * read (recipient, sender's DNS) pairs from /mail/lib/senders.
+ * Only allow mail to recipient from any of sender's IPs.
+ * A recipient not mentioned in the file is always permitted.
+ */
+static int
+senderok(char *rcpt)
+{
+	int mentioned = 0, matched = 0;
+	uchar dnsip[IPaddrlen];
+	Sender *snd;
+	Ndbtuple *nt, *next, *first;
+
+	rdsenders();
+	for (snd = sendlist; snd != nil; snd = snd->next) {
+		if (strcmp(rcpt, snd->rcpt) != 0)
+			continue;
+		/*
+		 * see if this domain's ips match nci->rsys.
+		 * if not, perhaps a later entry's domain will.
+		 */
+		mentioned = 1;
+		if (parseip(dnsip, snd->domain) != -1 &&
+		    memcmp(rsysip, dnsip, IPaddrlen) == 0)
+			return 1;
+		/*
+		 * NB: nt->line links form a circular list(!).
+		 * we need to make one complete pass over it to free it all.
+		 */
+		first = nt = dnsquery(nci->root, snd->domain, "ip");
+		if (first == nil)
+			continue;
+		do {
+			if (strcmp(nt->attr, "ip") == 0 &&
+			    parseip(dnsip, nt->val) != -1 &&
+			    memcmp(rsysip, dnsip, IPaddrlen) == 0)
+				matched = 1;
+			next = nt->line;
+			free(nt);
+			nt = next;
+		} while (nt != first);
+	}
+	if (matched)
+		return 1;
+	else
+		return !mentioned;
+}
+
+void
+receiver(String *path)
+{
+	char *sender, *rcpt;
+
+	if(rejectcheck())
+		return;
+	if(him == 0 || *him == 0){
+		rejectcount++;
+		reply("503 Start by saying HELO, please\r\n");
+		return;
+	}
+	if(senders.last)
+		sender = s_to_c(senders.last->p);
+	else
+		sender = "<unknown>";
+
+	if(!recipok(s_to_c(path))){
+		rejectcount++;
+		syslog(0, "smtpd", "Disallowed %s (%s/%s) to blocked name %s",
+				sender, him, nci->rsys, s_to_c(path));
+		reply("550 %s ... user unknown\r\n", s_to_c(path));
+		return;
+	}
+	rcpt = s_to_c(path);
+	if (!senderok(rcpt)) {
+		rejectcount++;
+		syslog(0, "smtpd", "Disallowed sending IP of %s (%s/%s) to %s",
+				sender, him, nci->rsys, rcpt);
+		reply("550 %s ... sending system not allowed\r\n", rcpt);
+		return;
+	}
+
+	logged = 0;
+		/* forwarding() can modify 'path' on loopback request */
+	if(filterstate == ACCEPT && (fflag && !authenticated) && forwarding(path)) {
+		syslog(0, "smtpd", "Bad Forward %s (%s/%s) (%s)",
+			s_to_c(senders.last->p), him, nci->rsys, s_to_c(path));
+		rejectcount++;
+		reply("550 we don't relay.  send to your-path@[] for loopback.\r\n");
+		return;
+	}
+	listadd(&rcvers, path);
+	reply("250 receiver is %s\r\n", s_to_c(path));
+}
+
+void
+quit(void)
+{
+	reply("221 Successful termination\r\n");
+	close(0);
+	exits(0);
+}
+
+void
+turn(void)
+{
+	if(rejectcheck())
+		return;
+	reply("502 TURN unimplemented\r\n");
+}
+
+void
+noop(void)
+{
+	if(rejectcheck())
+		return;
+	reply("250 Stop wasting my time!\r\n");
+}
+
+void
+help(String *cmd)
+{
+	if(rejectcheck())
+		return;
+	if(cmd)
+		s_free(cmd);
+	reply("250 Read rfc821 and stop wasting my time\r\n");
+}
+
+void
+verify(String *path)
+{
+	char *p, *q;
+	char *av[4];
+
+	if(rejectcheck())
+		return;
+	if(shellchars(s_to_c(path))){
+		reply("503 Bad character in address %s.\r\n", s_to_c(path));
+		return;
+	}
+	av[0] = s_to_c(mailer);
+	av[1] = "-x";
+	av[2] = s_to_c(path);
+	av[3] = 0;
+
+	pp = noshell_proc_start(av, (stream *)0, outstream(),  (stream *)0, 1, 0);
+	if (pp == 0) {
+		reply("450 We're busy right now, try later\r\n");
+		return;
+	}
+
+	p = Brdline(pp->std[1]->fp, '\n');
+	if(p == 0){
+		reply("550 String does not match anything.\r\n");
+	} else {
+		p[Blinelen(pp->std[1]->fp)-1] = 0;
+		if(strchr(p, ':'))
+			reply("550 String does not match anything.\r\n");
+		else{
+			q = strrchr(p, '!');
+			if(q)
+				p = q+1;
+			reply("250 %s <%s@%s>\r\n", s_to_c(path), p, dom);
+		}
+	}
+	proc_wait(pp);
+	proc_free(pp);
+	pp = 0;
+}
+
+/*
+ *  get a line that ends in crnl or cr, turn terminating crnl into a nl
+ *
+ *  return 0 on EOF
+ */
+static int
+getcrnl(String *s, Biobuf *fp)
+{
+	int c;
+
+	for(;;){
+		c = Bgetc(fp);
+		if(debug) {
+			seek(2, 0, 2);
+			fprint(2, "%c", c);
+		}
+		switch(c){
+		case -1:
+			goto out;
+		case '\r':
+			c = Bgetc(fp);
+			if(c == '\n'){
+				if(debug) {
+					seek(2, 0, 2);
+					fprint(2, "%c", c);
+				}
+				s_putc(s, '\n');
+				goto out;
+			}
+			Bungetc(fp);
+			s_putc(s, '\r');
+			break;
+		case '\n':
+			s_putc(s, c);
+			goto out;
+		default:
+			s_putc(s, c);
+			break;
+		}
+	}
+out:
+	s_terminate(s);
+	return s_len(s);
+}
+
+void
+logcall(int nbytes)
+{
+	Link *l;
+	String *to, *from;
+
+	to = s_new();
+	from = s_new();
+	for(l = senders.first; l; l = l->next){
+		if(l != senders.first)
+			s_append(from, ", ");
+		s_append(from, s_to_c(l->p));
+	}
+	for(l = rcvers.first; l; l = l->next){
+		if(l != rcvers.first)
+			s_append(to, ", ");
+		s_append(to, s_to_c(l->p));
+	}
+	syslog(0, "smtpd", "[%s/%s] %s sent %d bytes to %s", him, nci->rsys,
+		s_to_c(from), nbytes, s_to_c(to));
+	s_free(to);
+	s_free(from);
+}
+
+static void
+logmsg(char *action)
+{
+	Link *l;
+
+	if(logged)
+		return;
+
+	logged = 1;
+	for(l = rcvers.first; l; l = l->next)
+		syslog(0, "smtpd", "%s %s (%s/%s) (%s)", action,
+			s_to_c(senders.last->p), him, nci->rsys, s_to_c(l->p));
+}
+
+static int
+optoutall(int filterstate)
+{
+	Link *l;
+
+	switch(filterstate){
+	case ACCEPT:
+	case TRUSTED:
+		return filterstate;
+	}
+
+	for(l = rcvers.first; l; l = l->next)
+		if(!optoutofspamfilter(s_to_c(l->p)))
+			return filterstate;
+
+	return ACCEPT;
+}
+
+String*
+startcmd(void)
+{
+	int n;
+	Link *l;
+	char **av;
+	String *cmd;
+	char *filename;
+
+	/*
+	 *  ignore the filterstate if the all the receivers prefer it.
+	 */
+	filterstate = optoutall(filterstate);
+
+	switch (filterstate){
+	case BLOCKED:
+	case DELAY:
+		rejectcount++;
+		logmsg("Blocked");
+		filename = dumpfile(s_to_c(senders.last->p));
+		cmd = s_new();
+		s_append(cmd, "cat > ");
+		s_append(cmd, filename);
+		pp = proc_start(s_to_c(cmd), instream(), 0, outstream(), 0, 0);
+		break;
+	case DIALUP:
+		logmsg("Dialup");
+		rejectcount++;
+		reply("554 We don't accept mail from dial-up ports.\r\n");
+		/*
+		 * we could exit here, because we're never going to accept mail from this
+		 * ip address, but it's unclear that RFC821 allows that.  Instead we set
+		 * the hardreject flag and go stupid.
+		 */
+		hardreject = 1;
+		return 0;
+	case DENIED:
+		logmsg("Denied");
+		rejectcount++;
+		reply("554-We don't accept mail from %s.\r\n", s_to_c(senders.last->p));
+		reply("554 Contact postmaster@%s for more information.\r\n", dom);
+		return 0;
+	case REFUSED:
+		logmsg("Refused");
+		rejectcount++;
+		reply("554 Sender domain must exist: %s\r\n", s_to_c(senders.last->p));
+		return 0;
+	default:
+	case NONE:
+		logmsg("Confused");
+		rejectcount++;
+		reply("554-We have had an internal mailer error classifying your message.\r\n");
+		reply("554-Filterstate is %d\r\n", filterstate);
+		reply("554 Contact postmaster@%s for more information.\r\n", dom);
+		return 0;
+	case ACCEPT:
+	case TRUSTED:
+		/*
+		 * now that all other filters have been passed,
+		 * do grey-list processing.
+		 */
+		if(gflag)
+			vfysenderhostok();
+
+		/*
+		 *  set up mail command
+		 */
+		cmd = s_clone(mailer);
+		n = 3;
+		for(l = rcvers.first; l; l = l->next)
+			n++;
+		av = malloc(n*sizeof(char*));
+		if(av == nil){
+			reply("450 We're busy right now, try later\n");
+			s_free(cmd);
+			return 0;
+		}
+
+			n = 0;
+		av[n++] = s_to_c(cmd);
+		av[n++] = "-r";
+		for(l = rcvers.first; l; l = l->next)
+			av[n++] = s_to_c(l->p);
+		av[n] = 0;
+		/*
+		 *  start mail process
+		 */
+		pp = noshell_proc_start(av, instream(), outstream(), outstream(), 0, 0);
+		free(av);
+		break;
+	}
+	if(pp == 0) {
+		reply("450 We're busy right now, try later\n");
+		s_free(cmd);
+		return 0;
+	}
+	return cmd;
+}
+
+/*
+ *  print out a header line, expanding any domainless addresses into
+ *  address@him
+ */
+char*
+bprintnode(Biobuf *b, Node *p)
+{
+	if(p->s){
+		if(p->addr && strchr(s_to_c(p->s), '@') == nil){
+			if(Bprint(b, "%s@%s", s_to_c(p->s), him) < 0)
+				return nil;
+		} else {
+			if(Bwrite(b, s_to_c(p->s), s_len(p->s)) < 0)
+				return nil;
+		}
+	}else{
+		if(Bputc(b, p->c) < 0)
+			return nil;
+	}
+	if(p->white)
+		if(Bwrite(b, s_to_c(p->white), s_len(p->white)) < 0)
+			return nil;
+	return p->end+1;
+}
+
+static String*
+getaddr(Node *p)
+{
+	for(; p; p = p->next)
+		if(p->s && p->addr)
+			return p->s;
+	return nil;
+}
+
+/*
+ *  add waring headers of the form
+ *	X-warning: <reason>
+ *  for any headers that looked like they might be forged.
+ *
+ *  return byte count of new headers
+ */
+static int
+forgedheaderwarnings(void)
+{
+	int nbytes;
+	Field *f;
+
+	nbytes = 0;
+
+	/* warn about envelope sender */
+	if(strcmp(s_to_c(senders.last->p), "/dev/null") != 0 && masquerade(senders.last->p, nil))
+		nbytes += Bprint(pp->std[0]->fp, "X-warning: suspect envelope domain\n");
+
+	/*
+	 *  check Sender: field.  If it's OK, ignore the others because this is an
+	 *  exploded mailing list.
+	 */
+	for(f = firstfield; f; f = f->next){
+		if(f->node->c == SENDER){
+			if(masquerade(getaddr(f->node), him))
+				nbytes += Bprint(pp->std[0]->fp, "X-warning: suspect Sender: domain\n");
+			else
+				return nbytes;
+		}
+	}
+
+	/* check From: */
+	for(f = firstfield; f; f = f->next){
+		if(f->node->c == FROM && masquerade(getaddr(f->node), him))
+			nbytes += Bprint(pp->std[0]->fp, "X-warning: suspect From: domain\n");
+	}
+	return nbytes;
+}
+
+/*
+ *  pipe message to mailer with the following transformations:
+ *	- change \r\n into \n.
+ *	- add sender's domain to any addrs with no domain
+ *	- add a From: if none of From:, Sender:, or Replyto: exists
+ *	- add a Received: line
+ */
+int
+pipemsg(int *byteswritten)
+{
+	int status;
+	char *cp;
+	String *line;
+	String *hdr;
+	int n, nbytes;
+	int sawdot;
+	Field *f;
+	Node *p;
+	Link *l;
+
+	pipesig(&status);	/* set status to 1 on write to closed pipe */
+	sawdot = 0;
+	status = 0;
+
+	/*
+	 *  add a 'From ' line as envelope
+	 */
+	nbytes = 0;
+	nbytes += Bprint(pp->std[0]->fp, "From %s %s remote from \n",
+			s_to_c(senders.first->p), thedate());
+
+	/*
+	 *  add our own Received: stamp
+	 */
+	nbytes += Bprint(pp->std[0]->fp, "Received: from %s ", him);
+	if(nci->rsys)
+		nbytes += Bprint(pp->std[0]->fp, "([%s]) ", nci->rsys);
+	nbytes += Bprint(pp->std[0]->fp, "by %s; %s\n", me, thedate());
+
+	/*
+	 *  read first 16k obeying '.' escape.  we're assuming
+	 *  the header will all be there.
+	 */
+	line = s_new();
+	hdr = s_new();
+	while(sawdot == 0 && s_len(hdr) < 16*1024){
+		n = getcrnl(s_reset(line), &bin);
+
+		/* eof or error ends the message */
+		if(n <= 0)
+			break;
+
+		/* a line with only a '.' ends the message */
+		cp = s_to_c(line);
+		if(n == 2 && *cp == '.' && *(cp+1) == '\n'){
+			sawdot = 1;
+			break;
+		}
+
+		s_append(hdr, *cp == '.' ? cp+1 : cp);
+	}
+
+	/*
+ 	 *  parse header
+	 */
+	yyinit(s_to_c(hdr), s_len(hdr));
+	yyparse();
+
+	/*
+ 	 *  Look for masquerades.  Let Sender: trump From: to allow mailing list
+	 *  forwarded messages.
+	 */
+	if(fflag)
+		nbytes += forgedheaderwarnings();
+
+	/*
+	 *  add an orginator and/or destination if either is missing
+	 */
+	if(originator == 0){
+		if(senders.last == nil)
+			Bprint(pp->std[0]->fp, "From: /dev/null@%s\n", him);
+		else
+			Bprint(pp->std[0]->fp, "From: %s\n", s_to_c(senders.last->p));
+	}
+	if(destination == 0){
+		Bprint(pp->std[0]->fp, "To: ");
+		for(l = rcvers.first; l; l = l->next){
+			if(l != rcvers.first)
+				Bprint(pp->std[0]->fp, ", ");
+			Bprint(pp->std[0]->fp, "%s", s_to_c(l->p));
+		}
+		Bprint(pp->std[0]->fp, "\n");
+	}
+
+	/*
+	 *  add sender's domain to any domainless addresses
+	 *  (to avoid forging local addresses)
+	 */
+	cp = s_to_c(hdr);
+	for(f = firstfield; cp != nil && f; f = f->next){
+		for(p = f->node; cp != 0 && p; p = p->next)
+			cp = bprintnode(pp->std[0]->fp, p);
+		if(status == 0 && Bprint(pp->std[0]->fp, "\n") < 0){
+			piperror = "write error";
+			status = 1;
+		}
+	}
+	if(cp == nil){
+		piperror = "sender domain";
+		status = 1;
+	}
+
+	/* write anything we read following the header */
+	if(status == 0 && Bwrite(pp->std[0]->fp, cp, s_to_c(hdr) + s_len(hdr) - cp) < 0){
+		piperror = "write error 2";
+		status = 1;
+	}
+	s_free(hdr);
+
+	/*
+	 *  pass rest of message to mailer.  take care of '.'
+	 *  escapes.
+	 */
+	while(sawdot == 0){
+		n = getcrnl(s_reset(line), &bin);
+
+		/* eof or error ends the message */
+		if(n <= 0)
+			break;
+
+		/* a line with only a '.' ends the message */
+		cp = s_to_c(line);
+		if(n == 2 && *cp == '.' && *(cp+1) == '\n'){
+			sawdot = 1;
+			break;
+		}
+		nbytes += n;
+		if(status == 0 && Bwrite(pp->std[0]->fp, *cp == '.' ? cp+1 : cp, n) < 0){
+			piperror = "write error 3";
+			status = 1;
+		}
+	}
+	s_free(line);
+	if(sawdot == 0){
+		/* message did not terminate normally */
+		snprint(pipbuf, sizeof pipbuf, "network eof: %r");
+		piperror = pipbuf;
+		syskillpg(pp->pid);
+		status = 1;
+	}
+
+	if(status == 0 && Bflush(pp->std[0]->fp) < 0){
+		piperror = "write error 4";
+		status = 1;
+	}
+	stream_free(pp->std[0]);
+	pp->std[0] = 0;
+	*byteswritten = nbytes;
+	pipesigoff();
+	if(status && !piperror)
+		piperror = "write on closed pipe";
+	return status;
+}
+
+char*
+firstline(char *x)
+{
+	static char buf[128];
+	char *p;
+
+	strncpy(buf, x, sizeof(buf));
+	buf[sizeof(buf)-1] = 0;
+	p = strchr(buf, '\n');
+	if(p)
+		*p = 0;
+	return buf;
+}
+
+int
+sendermxcheck(void)
+{
+	char *cp, *senddom, *user;
+	char *who;
+	int pid;
+	Waitmsg *w;
+
+	who = s_to_c(senders.first->p);
+	if(strcmp(who, "/dev/null") == 0){
+		/* /dev/null can only send to one rcpt at a time */
+		if(rcvers.first != rcvers.last){
+			werrstr("rejected: /dev/null sending to multiple recipients");
+			return -1;
+		}
+		return 0;
+	}
+
+	if(access("/mail/lib/validatesender", AEXEC) < 0)
+		return 0;
+
+	senddom = strdup(who);
+	if((cp = strchr(senddom, '!')) == nil){
+		werrstr("rejected: domainless sender %s", who);
+		free(senddom);
+		return -1;
+	}
+	*cp++ = 0;
+	user = cp;
+
+	switch(pid = fork()){
+	case -1:
+		werrstr("deferred: fork: %r");
+		return -1;
+	case 0:
+		/*
+		 * Could add an option with the remote IP address
+		 * to allow validatesender to implement SPF eventually.
+		 */
+		execl("/mail/lib/validatesender", "validatesender", 
+			"-n", nci->root, senddom, user, nil);
+		_exits("exec validatesender: %r");
+	default:
+		break;
+	}
+
+	free(senddom);
+	w = wait();
+	if(w == nil){
+		werrstr("deferred: wait failed: %r");
+		return -1;
+	}
+	if(w->pid != pid){
+		werrstr("deferred: wait returned wrong pid %d != %d", w->pid, pid);
+		free(w);
+		return -1;
+	}
+	if(w->msg[0] == 0){
+		free(w);
+		return 0;
+	}
+	/*
+	 * skip over validatesender 143123132: prefix from rc.
+	 */
+	cp = strchr(w->msg, ':');
+	if(cp && *(cp+1) == ' ')
+		werrstr("%s", cp+2);
+	else
+		werrstr("%s", w->msg);
+	free(w);
+	return -1;
+}
+
+void
+data(void)
+{
+	String *cmd;
+	String *err;
+	int status, nbytes;
+	char *cp, *ep;
+	char errx[ERRMAX];
+	Link *l;
+
+	if(rejectcheck())
+		return;
+	if(senders.last == 0){
+		reply("503 Data without MAIL FROM:\r\n");
+		rejectcount++;
+		return;
+	}
+	if(rcvers.last == 0){
+		reply("503 Data without RCPT TO:\r\n");
+		rejectcount++;
+		return;
+	}
+	if(sendermxcheck()){
+		rerrstr(errx, sizeof errx);
+		if(strncmp(errx, "rejected:", 9) == 0)
+			reply("554 %s\r\n", errx);
+		else
+			reply("450 %s\r\n", errx);
+		for(l=rcvers.first; l; l=l->next)
+			syslog(0, "smtpd", "[%s/%s] %s -> %s sendercheck: %s",
+					him, nci->rsys, s_to_c(senders.first->p), 
+					s_to_c(l->p), errx);
+		rejectcount++;
+		return;
+	}
+
+	cmd = startcmd();
+	if(cmd == 0)
+		return;
+
+	reply("354 Input message; end with <CRLF>.<CRLF>\r\n");
+
+	/*
+	 *  allow 145 more minutes to move the data
+	 */
+	alarm(145*60*1000);
+
+	status = pipemsg(&nbytes);
+
+	/*
+	 *  read any error messages
+	 */
+	err = s_new();
+	while(s_read_line(pp->std[2]->fp, err))
+		;
+
+	alarm(0);
+	atnotify(catchalarm, 0);
+
+	status |= proc_wait(pp);
+	if(debug){
+		seek(2, 0, 2);
+		fprint(2, "%d status %ux\n", getpid(), status);
+		if(*s_to_c(err))
+			fprint(2, "%d error %s\n", getpid(), s_to_c(err));
+	}
+
+	/*
+	 *  if process terminated abnormally, send back error message
+	 */
+	if(status){
+		int code;
+
+		if(strstr(s_to_c(err), "mail refused")){
+			syslog(0, "smtpd", "++[%s/%s] %s %s refused: %s", him, nci->rsys,
+				s_to_c(senders.first->p), s_to_c(cmd), firstline(s_to_c(err)));
+			code = 554;
+		} else {
+			syslog(0, "smtpd", "++[%s/%s] %s %s %s%s%sreturned %#q %s", him, nci->rsys,
+				s_to_c(senders.first->p), s_to_c(cmd), 
+				piperror ? "error during pipemsg: " : "",
+				piperror ? piperror : "",
+				piperror ? "; " : "",
+				pp->waitmsg->msg, firstline(s_to_c(err)));
+			code = 450;
+		}
+		for(cp = s_to_c(err); ep = strchr(cp, '\n'); cp = ep){
+			*ep++ = 0;
+			reply("%d-%s\r\n", code, cp);
+		}
+		reply("%d mail process terminated abnormally\r\n", code);
+	} else {
+		if(filterstate == BLOCKED)
+			reply("554 we believe this is spam.  we don't accept it.\r\n");
+		else
+		if(filterstate == DELAY)
+			reply("554 There will be a delay in delivery of this message.\r\n");
+		else {
+			reply("250 sent\r\n");
+			logcall(nbytes);
+		}
+	}
+	proc_free(pp);
+	pp = 0;
+	s_free(cmd);
+	s_free(err);
+
+	listfree(&senders);
+	listfree(&rcvers);
+}
+
+/*
+ * when we have blocked a transaction based on IP address, there is nothing
+ * that the sender can do to convince us to take the message.  after the
+ * first rejection, some spammers continually RSET and give a new MAIL FROM:
+ * filling our logs with rejections.  rejectcheck() limits the retries and
+ * swiftly rejects all further commands after the first 500-series message
+ * is issued.
+ */
+int
+rejectcheck(void)
+{
+
+	if(rejectcount > MAXREJECTS){
+		syslog(0, "smtpd", "Rejected (%s/%s)", him, nci->rsys);
+		reply("554 too many errors.  transaction failed.\r\n");
+		exits("errcount");
+	}
+	if(hardreject){
+		rejectcount++;
+		reply("554 We don't accept mail from dial-up ports.\r\n");
+	}
+	return hardreject;
+}
+
+/*
+ *  create abs path of the mailer
+ */
+String*
+mailerpath(char *p)
+{
+	String *s;
+
+	if(p == nil)
+		return nil;
+	if(*p == '/')
+		return s_copy(p);
+	s = s_new();
+	s_append(s, UPASBIN);
+	s_append(s, "/");
+	s_append(s, p);
+	return s;
+}
+
+String *
+s_dec64(String *sin)
+{
+	String *sout;
+	int lin, lout;
+	lin = s_len(sin);
+
+	/*
+	 * if the string is coming from smtpd.y, it will have no nl.
+	 * if it is coming from getcrnl below, it will have an nl.
+	 */
+	if (*(s_to_c(sin)+lin-1) == '\n')
+		lin--;
+	sout = s_newalloc(lin+1);
+	lout = dec64((uchar *)s_to_c(sout), lin, s_to_c(sin), lin);
+	if (lout < 0) {
+		s_free(sout);
+		return nil;
+	}
+	sout->ptr = sout->base + lout;
+	s_terminate(sout);
+	return sout;
+}
+
+void
+starttls(void)
+{
+	uchar *cert;
+	int certlen, fd;
+	TLSconn *conn;
+
+	conn = mallocz(sizeof *conn, 1);
+	cert = readcert(tlscert, &certlen);
+	if (conn == nil || cert == nil) {
+		if (conn != nil)
+			free(conn);
+		reply("454 TLS not available\r\n");
+		return;
+	}
+	reply("220 Go ahead make my day\r\n");
+	conn->cert = cert;
+	conn->certlen = certlen;
+	fd = tlsServer(Bfildes(&bin), conn);
+	if (fd < 0) {
+		free(cert);
+		free(conn);
+		syslog(0, "smtpd", "TLS start-up failed with %s", him);
+
+		/* force the client to hang up */
+		close(Bfildes(&bin));		/* probably fd 0 */
+		close(1);
+		exits("tls failed");
+	}
+	Bterm(&bin);
+	Binit(&bin, fd, OREAD);
+	if (dup(fd, 1) < 0)
+		fprint(2, "dup of %d failed: %r\n", fd);
+	passwordinclear = 1;
+	syslog(0, "smtpd", "started TLS with %s", him);
+}
+
+void
+auth(String *mech, String *resp)
+{
+	Chalstate *chs = nil;
+	AuthInfo *ai = nil;
+	String *s_resp1_64 = nil;
+	String *s_resp2_64 = nil;
+	String *s_resp1 = nil;
+	String *s_resp2 = nil;
+	char *scratch = nil;
+	char *user, *pass;
+
+	if (rejectcheck())
+		goto bomb_out;
+
+ 	syslog(0, "smtpd", "auth(%s, %s) from %s", s_to_c(mech),
+		"(protected)", him);
+
+	if (authenticated) {
+	bad_sequence:
+		rejectcount++;
+		reply("503 Bad sequence of commands\r\n");
+		goto bomb_out;
+	}
+	if (cistrcmp(s_to_c(mech), "plain") == 0) {
+
+		if (!passwordinclear) {
+			rejectcount++;
+			reply("538 Encryption required for requested authentication mechanism\r\n");
+			goto bomb_out;
+		}
+		s_resp1_64 = resp;
+		if (s_resp1_64 == nil) {
+			reply("334 \r\n");
+			s_resp1_64 = s_new();
+			if (getcrnl(s_resp1_64, &bin) <= 0) {
+				goto bad_sequence;
+			}
+		}
+		s_resp1 = s_dec64(s_resp1_64);
+		if (s_resp1 == nil) {
+			rejectcount++;
+			reply("501 Cannot decode base64\r\n");
+			goto bomb_out;
+		}
+		memset(s_to_c(s_resp1_64), 'X', s_len(s_resp1_64));
+		user = (s_to_c(s_resp1) + strlen(s_to_c(s_resp1)) + 1);
+		pass = user + (strlen(user) + 1);
+		ai = auth_userpasswd(user, pass);
+		authenticated = ai != nil;
+		memset(pass, 'X', strlen(pass));
+		goto windup;
+	}
+	else if (cistrcmp(s_to_c(mech), "login") == 0) {
+
+		if (!passwordinclear) {
+			rejectcount++;
+			reply("538 Encryption required for requested authentication mechanism\r\n");
+			goto bomb_out;
+		}
+		if (resp == nil) {
+			reply("334 VXNlcm5hbWU6\r\n");
+			s_resp1_64 = s_new();
+			if (getcrnl(s_resp1_64, &bin) <= 0)
+				goto bad_sequence;
+		}
+		reply("334 UGFzc3dvcmQ6\r\n");
+		s_resp2_64 = s_new();
+		if (getcrnl(s_resp2_64, &bin) <= 0)
+			goto bad_sequence;
+		s_resp1 = s_dec64(s_resp1_64);
+		s_resp2 = s_dec64(s_resp2_64);
+		memset(s_to_c(s_resp2_64), 'X', s_len(s_resp2_64));
+		if (s_resp1 == nil || s_resp2 == nil) {
+			rejectcount++;
+			reply("501 Cannot decode base64\r\n");
+			goto bomb_out;
+		}
+		ai = auth_userpasswd(s_to_c(s_resp1), s_to_c(s_resp2));
+		authenticated = ai != nil;
+		memset(s_to_c(s_resp2), 'X', s_len(s_resp2));
+	windup:
+		if (authenticated)
+			reply("235 Authentication successful\r\n");
+		else {
+			rejectcount++;
+			reply("535 Authentication failed\r\n");
+		}
+		goto bomb_out;
+	}
+	else if (cistrcmp(s_to_c(mech), "cram-md5") == 0) {
+		char *resp;
+		int chal64n;
+		char *t;
+
+		chs = auth_challenge("proto=cram role=server");
+		if (chs == nil) {
+			rejectcount++;
+			reply("501 Couldn't get CRAM-MD5 challenge\r\n");
+			goto bomb_out;
+		}
+		scratch = malloc(chs->nchal * 2 + 1);
+		chal64n = enc64(scratch, chs->nchal * 2, (uchar *)chs->chal, chs->nchal);
+		scratch[chal64n] = 0;
+		reply("334 %s\r\n", scratch);
+		s_resp1_64 = s_new();
+		if (getcrnl(s_resp1_64, &bin) <= 0)
+			goto bad_sequence;
+		s_resp1 = s_dec64(s_resp1_64);
+		if (s_resp1 == nil) {
+			rejectcount++;
+			reply("501 Cannot decode base64\r\n");
+			goto bomb_out;
+		}
+		/* should be of form <user><space><response> */
+		resp = s_to_c(s_resp1);
+		t = strchr(resp, ' ');
+		if (t == nil) {
+			rejectcount++;
+			reply("501 Poorly formed CRAM-MD5 response\r\n");
+			goto bomb_out;
+		}
+		*t++ = 0;
+		chs->user = resp;
+		chs->resp = t;
+		chs->nresp = strlen(t);
+		ai = auth_response(chs);
+		authenticated = ai != nil;
+		goto windup;
+	}
+	rejectcount++;
+	reply("501 Unrecognised authentication type %s\r\n", s_to_c(mech));
+bomb_out:
+	if (ai)
+		auth_freeAI(ai);
+	if (chs)
+		auth_freechal(chs);
+	if (scratch)
+		free(scratch);
+	if (s_resp1)
+		s_free(s_resp1);
+	if (s_resp2)
+		s_free(s_resp2);
+	if (s_resp1_64)
+		s_free(s_resp1_64);
+	if (s_resp2_64)
+		s_free(s_resp2_64);
+}
blob - /dev/null
blob + 3a8e60e275b0da16761a8850620aad310863118d (mode 644)
--- /dev/null
+++ src/cmd/upas/smtp/smtpd.h
@@ -0,0 +1,68 @@
+enum {
+	ACCEPT = 0,
+	REFUSED,
+	DENIED,
+	DIALUP,
+	BLOCKED,
+	DELAY,
+	TRUSTED,
+	NONE,
+
+	MAXREJECTS = 100,
+};
+
+	
+typedef struct Link Link;
+typedef struct List List;
+
+struct Link {
+	Link *next;
+	String *p;
+};
+
+struct List {
+	Link *first;
+	Link *last;
+};
+
+extern	int	fflag;
+extern	int	rflag;
+extern	int	sflag;
+
+extern	int	debug;
+extern	NetConnInfo	*nci;
+extern	char	*dom;
+extern	char*	me;
+extern	int	trusted;
+extern	List	senders;
+extern	List	rcvers;
+
+void	addbadguy(char*);
+void	auth(String *, String *);
+int	blocked(String*);
+void	data(void);
+char*	dumpfile(char*);
+int	forwarding(String*);
+void	getconf(void);
+void	hello(String*, int extended);
+void	help(String *);
+int	isbadguy(void);
+void	listadd(List*, String*);
+void	listfree(List*);
+int	masquerade(String*, char*);
+void	noop(void);
+int	optoutofspamfilter(char*);
+void	quit(void);
+void	parseinit(void);
+void	receiver(String*);
+int	recipok(char*);
+int	reply(char*, ...);
+void	reset(void);
+int	rmtdns(char*, char*);
+void	sayhi(void);
+void	sender(String*);
+void	starttls(void);
+void	turn(void);
+void	verify(String*);
+void	vfysenderhostok(void);
+int	zzparse(void);
blob - /dev/null
blob + 57b89a02df7bcba2ee9e61245dcd4e51abd89db7 (mode 644)
--- /dev/null
+++ src/cmd/upas/smtp/smtpd.y
@@ -0,0 +1,317 @@
+%{
+#include "common.h"
+#include <ctype.h>
+#include "smtpd.h"
+
+#define YYSTYPE yystype
+typedef struct quux yystype;
+struct quux {
+	String	*s;
+	int	c;
+};
+Biobuf *yyfp;
+YYSTYPE *bang;
+extern Biobuf bin;
+extern int debug;
+
+YYSTYPE cat(YYSTYPE*, YYSTYPE*, YYSTYPE*, YYSTYPE*, YYSTYPE*, YYSTYPE*, YYSTYPE*);
+int yyparse(void);
+int yylex(void);
+YYSTYPE anonymous(void);
+%}
+
+%term SPACE
+%term CNTRL
+%term CRLF
+%start conversation
+%%
+
+conversation	: cmd
+		| conversation cmd
+		;
+cmd		: error
+		| 'h' 'e' 'l' 'o' spaces sdomain CRLF
+			{ hello($6.s, 0); }
+		| 'e' 'h' 'l' 'o' spaces sdomain CRLF
+			{ hello($6.s, 1); }
+		| 'm' 'a' 'i' 'l' spaces 'f' 'r' 'o' 'm' ':' spath CRLF
+			{ sender($11.s); }
+		| 'm' 'a' 'i' 'l' spaces 'f' 'r' 'o' 'm' ':' spath spaces 'a' 'u' 't' 'h' '=' sauth CRLF
+			{ sender($11.s); }
+		| 'r' 'c' 'p' 't' spaces 't' 'o' ':' spath CRLF
+			{ receiver($9.s); }
+		| 'd' 'a' 't' 'a' CRLF
+			{ data(); }
+		| 'r' 's' 'e' 't' CRLF
+			{ reset(); }
+		| 's' 'e' 'n' 'd' spaces 'f' 'r' 'o' 'm' ':' spath CRLF
+			{ sender($11.s); }
+		| 's' 'o' 'm' 'l' spaces 'f' 'r' 'o' 'm'  ':' spath CRLF
+			{ sender($11.s); }
+		| 's' 'a' 'm' 'l' spaces 'f' 'r' 'o' 'm' ':' spath CRLF
+			{ sender($11.s); }
+		| 'v' 'r' 'f' 'y' spaces string CRLF
+			{ verify($6.s); }
+		| 'e' 'x' 'p' 'n' spaces string CRLF
+			{ verify($6.s); }
+		| 'h' 'e' 'l' 'p' CRLF
+			{ help(0); }
+		| 'h' 'e' 'l' 'p' spaces string CRLF
+			{ help($6.s); }
+		| 'n' 'o' 'o' 'p' CRLF
+			{ noop(); }
+		| 'q' 'u' 'i' 't' CRLF
+			{ quit(); }
+		| 't' 'u' 'r' 'n' CRLF
+			{ turn(); }
+		| 's' 't' 'a' 'r' 't' 't' 'l' 's' CRLF
+			{ starttls(); }
+		| 'a' 'u' 't' 'h' spaces name spaces string CRLF
+			{ auth($6.s, $8.s); }
+		| 'a' 'u' 't' 'h' spaces name CRLF
+			{ auth($6.s, nil); }
+		| CRLF
+			{ reply("501 illegal command or bad syntax\r\n"); }
+		;
+path		: '<' '>'			={ $$ = anonymous(); }
+		| '<' mailbox '>'		={ $$ = $2; }
+		| '<' a_d_l ':' mailbox '>'	={ $$ = cat(&$2, bang, &$4, 0, 0 ,0, 0); }
+		;
+spath		: path			={ $$ = $1; }
+		| spaces path		={ $$ = $2; }
+		;
+auth		: path			={ $$ = $1; }
+		| mailbox		={ $$ = $1; }
+		;
+sauth		: auth			={ $$ = $1; }
+		| spaces auth		={ $$ = $2; }
+		;
+		;
+a_d_l		: at_domain		={ $$ = cat(&$1, 0, 0, 0, 0 ,0, 0); }
+		| at_domain ',' a_d_l	={ $$ = cat(&$1, bang, &$3, 0, 0, 0, 0); }
+		;
+at_domain	: '@' domain		={ $$ = cat(&$2, 0, 0, 0, 0 ,0, 0); }
+		;
+sdomain		: domain		={ $$ = $1; }
+		| domain spaces		={ $$ = $1; }
+		;
+domain		: element		={ $$ = cat(&$1, 0, 0, 0, 0 ,0, 0); }
+		| element '.'		={ $$ = cat(&$1, 0, 0, 0, 0 ,0, 0); }
+		| element '.' domain	={ $$ = cat(&$1, &$2, &$3, 0, 0 ,0, 0); }
+		;
+element		: name			={ $$ = cat(&$1, 0, 0, 0, 0 ,0, 0); }
+		| '#' number		={ $$ = cat(&$1, &$2, 0, 0, 0 ,0, 0); }
+		| '[' ']'		={ $$ = cat(&$1, &$2, 0, 0, 0 ,0, 0); }
+		| '[' dotnum ']'	={ $$ = cat(&$1, &$2, &$3, 0, 0 ,0, 0); }
+		;
+mailbox		: local_part		={ $$ = cat(&$1, 0, 0, 0, 0 ,0, 0); }
+		| local_part '@' domain	={ $$ = cat(&$3, bang, &$1, 0, 0 ,0, 0); }
+		;
+local_part	: dot_string		={ $$ = cat(&$1, 0, 0, 0, 0 ,0, 0); }
+		| quoted_string		={ $$ = cat(&$1, 0, 0, 0, 0 ,0, 0); }
+		;
+name		: let_dig			={ $$ = cat(&$1, 0, 0, 0, 0 ,0, 0); }
+		| let_dig ld_str		={ $$ = cat(&$1, &$2, 0, 0, 0 ,0, 0); }
+		| let_dig ldh_str ld_str	={ $$ = cat(&$1, &$2, &$3, 0, 0 ,0, 0); }
+		;
+ld_str		: let_dig
+		| let_dig ld_str		={ $$ = cat(&$1, &$2, 0, 0, 0 ,0, 0); }
+		;
+ldh_str		: hunder
+		| ld_str hunder		={ $$ = cat(&$1, &$2, 0, 0, 0 ,0, 0); }
+		| ldh_str ld_str hunder	={ $$ = cat(&$1, &$2, &$3, 0, 0 ,0, 0); }
+		;
+let_dig		: a
+		| d
+		;
+dot_string	: string			={ $$ = cat(&$1, 0, 0, 0, 0 ,0, 0); }
+		| string '.' dot_string		={ $$ = cat(&$1, &$2, &$3, 0, 0 ,0, 0); }
+		;
+
+string		: char	={ $$ = cat(&$1, 0, 0, 0, 0 ,0, 0); }
+		| string char	={ $$ = cat(&$1, &$2, 0, 0, 0 ,0, 0); }
+		;
+
+quoted_string	: '"' qtext '"'	={ $$ = cat(&$1, &$2, &$3, 0, 0 ,0, 0); }
+		;
+qtext		: '\\' x		={ $$ = cat(&$2, 0, 0, 0, 0 ,0, 0); }
+		| qtext '\\' x		={ $$ = cat(&$1, &$3, 0, 0, 0 ,0, 0); }
+		| q
+		| qtext q		={ $$ = cat(&$1, &$2, 0, 0, 0 ,0, 0); }
+		;
+char		: c
+		| '\\' x		={ $$ = $2; }
+		;
+dotnum		: snum '.' snum '.' snum '.' snum ={ $$ = cat(&$1, &$2, &$3, &$4, &$5, &$6, &$7); }
+		;
+number		: d		={ $$ = cat(&$1, 0, 0, 0, 0 ,0, 0); }
+		| number d	={ $$ = cat(&$1, &$2, 0, 0, 0 ,0, 0); }
+		;
+snum		: number		={ if(atoi(s_to_c($1.s)) > 255) print("bad snum\n"); } 
+		;
+spaces		: SPACE		={ $$ = $1; }
+		| SPACE	spaces	={ $$ = $1; }
+		;
+hunder		: '-' | '_'
+		;
+special1	: CNTRL
+		| '(' | ')' | ',' | '.'
+		| ':' | ';' | '<' | '>' | '@'
+		;
+special		: special1 | '\\' | '"'
+		;
+notspecial	: '!' | '#' | '$' | '%' | '&' | '\''
+		| '*' | '+' | '-' | '/'
+		| '=' | '?'
+		| '[' | ']' | '^' | '_' | '`' | '{' | '|' | '}' | '~'
+		;
+
+a		: 'a' | 'b' | 'c' | 'd' | 'e' | 'f' | 'g' | 'h' | 'i'
+		| 'j' | 'k' | 'l' | 'm' | 'n' | 'o' | 'p' | 'q' | 'r'
+		| 's' | 't' | 'u' | 'v' | 'w' | 'x' | 'y' | 'z'
+		;
+d		: '0' | '1' | '2' | '3' | '4' | '5' | '6' | '7' | '8' | '9'
+		;
+c		: a | d | notspecial
+		;		
+q		: a | d | special1 | notspecial | SPACE
+		;
+x		: a | d | special | notspecial | SPACE
+		;
+%%
+
+void
+parseinit(void)
+{
+	bang = (YYSTYPE*)malloc(sizeof(YYSTYPE));
+	bang->c = '!';
+	bang->s = 0;
+	yyfp = &bin;
+}
+
+yylex(void)
+{
+	int c;
+
+	for(;;){
+		c = Bgetc(yyfp);
+		if(c == -1)
+			return 0;
+		if(debug)
+			fprint(2, "%c", c);
+		yylval.c = c = c & 0x7F;
+		if(c == '\n'){
+			return CRLF;
+		}
+		if(c == '\r'){
+			c = Bgetc(yyfp);
+			if(c != '\n'){
+				Bungetc(yyfp);
+				c = '\r';
+			} else {
+				if(debug)
+					fprint(2, "%c", c);
+				return CRLF;
+			}
+		}
+		if(isalpha(c))
+			return tolower(c);
+		if(isspace(c))
+			return SPACE;
+		if(iscntrl(c))
+			return CNTRL;
+		return c;
+	}
+}
+
+YYSTYPE
+cat(YYSTYPE *y1, YYSTYPE *y2, YYSTYPE *y3, YYSTYPE *y4, YYSTYPE *y5, YYSTYPE *y6, YYSTYPE *y7)
+{
+	YYSTYPE rv;
+
+	if(y1->s)
+		rv.s = y1->s;
+	else {
+		rv.s = s_new();
+		s_putc(rv.s, y1->c);
+		s_terminate(rv.s);
+	}
+	if(y2){
+		if(y2->s){
+			s_append(rv.s, s_to_c(y2->s));
+			s_free(y2->s);
+		} else {
+			s_putc(rv.s, y2->c);
+			s_terminate(rv.s);
+		}
+	} else
+		return rv;
+	if(y3){
+		if(y3->s){
+			s_append(rv.s, s_to_c(y3->s));
+			s_free(y3->s);
+		} else {
+			s_putc(rv.s, y3->c);
+			s_terminate(rv.s);
+		}
+	} else
+		return rv;
+	if(y4){
+		if(y4->s){
+			s_append(rv.s, s_to_c(y4->s));
+			s_free(y4->s);
+		} else {
+			s_putc(rv.s, y4->c);
+			s_terminate(rv.s);
+		}
+	} else
+		return rv;
+	if(y5){
+		if(y5->s){
+			s_append(rv.s, s_to_c(y5->s));
+			s_free(y5->s);
+		} else {
+			s_putc(rv.s, y5->c);
+			s_terminate(rv.s);
+		}
+	} else
+		return rv;
+	if(y6){
+		if(y6->s){
+			s_append(rv.s, s_to_c(y6->s));
+			s_free(y6->s);
+		} else {
+			s_putc(rv.s, y6->c);
+			s_terminate(rv.s);
+		}
+	} else
+		return rv;
+	if(y7){
+		if(y7->s){
+			s_append(rv.s, s_to_c(y7->s));
+			s_free(y7->s);
+		} else {
+			s_putc(rv.s, y7->c);
+			s_terminate(rv.s);
+		}
+	} else
+		return rv;
+}
+
+void
+yyerror(char *x)
+{
+	USED(x);
+}
+
+/*
+ *  an anonymous user
+ */
+YYSTYPE
+anonymous(void)
+{
+	YYSTYPE rv;
+
+	rv.s = s_copy("/dev/null");
+	return rv;
+}
blob - /dev/null
blob + 84a8fccc32ac95965ed12ebba544556ca5097d96 (mode 644)
--- /dev/null
+++ src/cmd/upas/smtp/spam.c
@@ -0,0 +1,591 @@
+#include "common.h"
+#include "smtpd.h"
+#include <ip.h>
+
+enum {
+	NORELAY = 0,
+	DNSVERIFY,
+	SAVEBLOCK,
+	DOMNAME,
+	OURNETS,
+	OURDOMS,
+
+	IP = 0,
+	STRING,
+};
+
+
+typedef struct Keyword Keyword;
+
+struct Keyword {
+	char	*name;
+	int	code;
+};
+
+static Keyword options[] = {
+	"norelay",		NORELAY,
+	"verifysenderdom",	DNSVERIFY,
+	"saveblockedmsg",	SAVEBLOCK,
+	"defaultdomain",	DOMNAME,	
+	"ournets",		OURNETS,
+	"ourdomains",		OURDOMS,
+	0,			NONE,
+};
+
+static Keyword actions[] = {
+	"allow",		ACCEPT,
+	"block",		BLOCKED,
+	"deny",			DENIED,
+	"dial",			DIALUP,
+	"delay",		DELAY,
+	0,			NONE,
+};
+
+static	int	hisaction;
+static	List	ourdoms;
+static	List 	badguys;
+static	ulong	v4peerip;
+
+static	char*	getline(Biobuf*);
+static	int	cidrcheck(char*);
+
+static int
+findkey(char *val, Keyword *p)
+{
+
+	for(; p->name; p++)
+		if(strcmp(val, p->name) == 0)
+				break;
+	return p->code;
+}
+
+char*
+actstr(int a)
+{
+	char buf[32];
+	Keyword *p;
+
+	for(p=actions; p->name; p++)
+		if(p->code == a)
+			return p->name;
+	if(a==NONE)
+		return "none";
+	sprint(buf, "%d", a);
+	return buf;
+}
+
+int
+getaction(char *s, char *type)
+{
+	char buf[1024];
+	Keyword *k;
+
+	if(s == nil || *s == 0)
+		return ACCEPT;
+
+	for(k = actions; k->name != 0; k++){
+		snprint(buf, sizeof buf, "/mail/ratify/%s/%s/%s", k->name, type, s);
+		if(access(buf,0) >= 0)
+			return k->code;
+	}
+	return ACCEPT;
+}
+
+int
+istrusted(char *s)
+{
+	char buf[1024];
+
+	if(s == nil || *s == 0)
+		return 0;
+
+	snprint(buf, sizeof buf, "/mail/ratify/trusted/%s", s);
+	return access(buf,0) >= 0;
+}
+
+void
+getconf(void)
+{
+	Biobuf *bp;
+	char *cp, *p;
+	String *s;
+	char buf[512];
+	uchar addr[4];
+
+	v4parseip(addr, nci->rsys);
+	v4peerip = nhgetl(addr);
+
+	trusted = istrusted(nci->rsys);
+	hisaction = getaction(nci->rsys, "ip");
+	if(debug){
+		fprint(2, "istrusted(%s)=%d\n", nci->rsys, trusted);
+		fprint(2, "getaction(%s, ip)=%s\n", nci->rsys, actstr(hisaction));
+	}
+	snprint(buf, sizeof(buf), "%s/smtpd.conf", UPASLIB);
+	bp = sysopen(buf, "r", 0);
+	if(bp == 0)
+		return;
+
+	for(;;){
+		cp = getline(bp);
+		if(cp == 0)
+			break;
+		p = cp+strlen(cp)+1;
+		switch(findkey(cp, options)){
+		case NORELAY:
+			if(fflag == 0 && strcmp(p, "on") == 0)
+				fflag++;
+			break;
+		case DNSVERIFY:
+			if(rflag == 0 && strcmp(p, "on") == 0)
+				rflag++;
+			break;
+		case SAVEBLOCK:
+			if(sflag == 0 && strcmp(p, "on") == 0)
+				sflag++;
+			break;
+		case DOMNAME:
+			if(dom == 0)
+				dom = strdup(p);
+			break;
+		case OURNETS:
+			if (trusted == 0)
+				trusted = cidrcheck(p);
+			break;
+		case OURDOMS:
+			while(*p){
+				s = s_new();
+				s_append(s, p);
+				listadd(&ourdoms, s);
+				p += strlen(p)+1;
+			}
+			break;
+		default:
+			break;
+		}
+	}
+	sysclose(bp);
+}
+
+/*
+ *	match a user name.  the only meta-char is '*' which matches all
+ *	characters.  we only allow it as "*", which matches anything or
+ *	an * at the end of the name (e.g., "username*") which matches
+ *	trailing characters.
+ */
+static int
+usermatch(char *pathuser, char *specuser)
+{
+	int n;
+
+	n = strlen(specuser)-1;
+	if(specuser[n] == '*'){
+		if(n == 0)		/* match everything */
+			return 0;
+		return strncmp(pathuser, specuser, n);
+	}
+	return strcmp(pathuser, specuser);
+}
+
+static int
+dommatch(char *pathdom, char *specdom)
+{
+	int n;
+
+	if (*specdom == '*'){
+		if (specdom[1] == '.' && specdom[2]){
+			specdom += 2;
+			n = strlen(pathdom)-strlen(specdom);
+			if(n == 0 || (n > 0 && pathdom[n-1] == '.'))
+				return strcmp(pathdom+n, specdom);
+			return n;
+		}
+	}
+	return strcmp(pathdom, specdom);
+}
+
+/*
+ *  figure out action for this sender
+ */
+int
+blocked(String *path)
+{
+	String *lpath;
+	int action;
+
+	if(debug)
+		fprint(2, "blocked(%s)\n", s_to_c(path));
+
+	/* if the sender's IP address is blessed, ignore sender email address */
+	if(trusted){
+		if(debug)
+			fprint(2, "\ttrusted => trusted\n");
+		return TRUSTED;
+	}
+
+	/* if sender's IP address is blocked, ignore sender email address */
+	if(hisaction != ACCEPT){
+		if(debug)
+			fprint(2, "\thisaction=%s => %s\n", actstr(hisaction), actstr(hisaction));
+		return hisaction;
+	}
+
+	/* convert to lower case */
+	lpath = s_copy(s_to_c(path));
+	s_tolower(lpath);
+
+	/* classify */
+	action = getaction(s_to_c(lpath), "account");
+	if(debug)
+		fprint(2, "\tgetaction account %s => %s\n", s_to_c(lpath), actstr(action));
+	s_free(lpath);
+	return action;
+}
+
+/*
+ * get a canonicalized line: a string of null-terminated lower-case
+ * tokens with a two null bytes at the end.
+ */
+static char*
+getline(Biobuf *bp)
+{
+	char c, *cp, *p, *q;
+	int n;
+
+	static char *buf;
+	static int bufsize;
+
+	for(;;){
+		cp = Brdline(bp, '\n');
+		if(cp == 0)
+			return 0;
+		n = Blinelen(bp);
+		cp[n-1] = 0;
+		if(buf == 0 || bufsize < n+1){
+			bufsize += 512;
+			if(bufsize < n+1)
+				bufsize = n+1;
+			buf = realloc(buf, bufsize);
+			if(buf == 0)
+				break;
+		}
+		q = buf;
+		for (p = cp; *p; p++){
+			c = *p;
+			if(c == '\\' && p[1])	/* we don't allow \<newline> */
+				c = *++p;
+			else
+			if(c == '#')
+				break;
+			else
+			if(c == ' ' || c == '\t' || c == ',')
+				if(q == buf || q[-1] == 0)
+					continue;
+				else
+					c = 0;
+			*q++ = tolower(c);
+		}
+		if(q != buf){
+			if(q[-1])
+				*q++ = 0;
+			*q = 0;
+			break;
+		}
+	}
+	return buf;
+}
+
+static int
+isourdom(char *s)
+{
+	Link *l;
+
+	if(strchr(s, '.') == nil)
+		return 1;
+
+	for(l = ourdoms.first; l; l = l->next){
+		if(dommatch(s, s_to_c(l->p)) == 0)
+			return 1;
+	}
+	return 0;
+}
+
+int
+forwarding(String *path)
+{
+	char *cp, *s;
+	String *lpath;
+
+	if(debug)
+		fprint(2, "forwarding(%s)\n", s_to_c(path));
+
+	/* first check if they want loopback */
+	lpath = s_copy(s_to_c(s_restart(path)));
+	if(nci->rsys && *nci->rsys){
+		cp = s_to_c(lpath);
+		if(strncmp(cp, "[]!", 3) == 0){
+found:
+			s_append(path, "[");
+			s_append(path, nci->rsys);
+			s_append(path, "]!");
+			s_append(path, cp+3);
+			s_terminate(path);
+			s_free(lpath);
+			return 0;
+		}
+		cp = strchr(cp,'!');			/* skip our domain and check next */
+		if(cp++ && strncmp(cp, "[]!", 3) == 0)
+			goto found;
+	}
+
+	/* if mail is from a trusted IP addr, allow it to forward */
+	if(trusted) {
+		s_free(lpath);
+		return 0;
+	}
+
+	/* sender is untrusted; ensure receiver is in one of our domains */
+	for(cp = s_to_c(lpath); *cp; cp++)		/* convert receiver lc */
+		*cp = tolower(*cp);
+
+	for(s = s_to_c(lpath); cp = strchr(s, '!'); s = cp+1){
+		*cp = 0;
+		if(!isourdom(s)){
+			s_free(lpath);
+			return 1;
+		}
+	}
+	s_free(lpath);
+	return 0;
+}
+
+int
+masquerade(String *path, char *him)
+{
+	char *cp, *s;
+	String *lpath;
+	int rv = 0;
+
+	if(debug)
+		fprint(2, "masquerade(%s)\n", s_to_c(path));
+
+	if(trusted)
+		return 0;
+	if(path == nil)
+		return 0;
+
+	lpath = s_copy(s_to_c(path));
+
+	/* sender is untrusted; ensure receiver is in one of our domains */
+	for(cp = s_to_c(lpath); *cp; cp++)		/* convert receiver lc */
+		*cp = tolower(*cp);
+	s = s_to_c(lpath);
+
+	/* scan first element of ! or last element of @ paths */
+	if((cp = strchr(s, '!')) != nil){
+		*cp = 0;
+		if(isourdom(s))
+			rv = 1;
+	} else if((cp = strrchr(s, '@')) != nil){
+		if(isourdom(cp+1))
+			rv = 1;
+	} else {
+		if(isourdom(him))
+			rv = 1;
+	}
+
+	s_free(lpath);
+	return rv;
+}
+
+/* this is a v4 only check */
+static int
+cidrcheck(char *cp)
+{
+	char *p;
+	ulong a, m;
+	uchar addr[IPv4addrlen];
+	uchar mask[IPv4addrlen];
+
+	if(v4peerip == 0)
+		return 0;
+
+	/* parse a list of CIDR addresses comparing each to the peer IP addr */
+	while(cp && *cp){
+		v4parsecidr(addr, mask, cp);
+		a = nhgetl(addr);
+		m = nhgetl(mask);
+		/*
+		 * if a mask isn't specified, we build a minimal mask
+		 * instead of using the default mask for that net.  in this
+		 * case we never allow a class A mask (0xff000000).
+		 */
+		if(strchr(cp, '/') == 0){
+			m = 0xff000000;
+			p = cp;
+			for(p = strchr(p, '.'); p && p[1]; p = strchr(p+1, '.'))
+					m = (m>>8)|0xff000000;
+
+			/* force at least a class B */
+			m |= 0xffff0000;
+		}
+		if((v4peerip&m) == a)
+			return 1;
+		cp += strlen(cp)+1;
+	}		
+	return 0;
+}
+
+int
+isbadguy(void)
+{
+	Link *l;
+
+	/* check if this IP address is banned */
+	for(l = badguys.first; l; l = l->next)
+		if(cidrcheck(s_to_c(l->p)))
+			return 1;
+
+	return 0;
+}
+
+void
+addbadguy(char *p)
+{
+	listadd(&badguys, s_copy(p));
+};
+
+char*
+dumpfile(char *sender)
+{
+	int i, fd;
+	ulong h;
+	static char buf[512];
+	char *cp;
+
+	if (sflag == 1){
+		cp = ctime(time(0));
+		cp[7] = 0;
+		if(cp[8] == ' ')
+			sprint(buf, "%s/queue.dump/%s%c", SPOOL, cp+4, cp[9]);
+		else
+			sprint(buf, "%s/queue.dump/%s%c%c", SPOOL, cp+4, cp[8], cp[9]);
+		cp = buf+strlen(buf);
+		if(access(buf, 0) < 0 && sysmkdir(buf, 0777) < 0)
+			return "/dev/null";
+		h = 0;
+		while(*sender)
+			h = h*257 + *sender++;
+		for(i = 0; i < 50; i++){
+			h += lrand();
+			sprint(cp, "/%lud", h);
+			if(access(buf, 0) >= 0)
+				continue;
+			fd = syscreate(buf, ORDWR, 0666);
+			if(fd >= 0){
+				if(debug)
+					fprint(2, "saving in %s\n", buf);
+				close(fd);
+				return buf;
+			}
+		}
+	}
+	return "/dev/null";
+}
+
+char *validator = "/mail/lib/validateaddress";
+
+int
+recipok(char *user)
+{
+	char *cp, *p, c;
+	char buf[512];
+	int n;
+	Biobuf *bp;
+	int pid;
+	Waitmsg *w;
+
+	if(shellchars(user)){
+		syslog(0, "smtpd", "shellchars in user name");
+		return 0;
+	}
+
+	if(access(validator, AEXEC) == 0)
+	switch(pid = fork()) {
+	case -1:
+		break;
+	case 0:
+		execl(validator, "validateaddress", user, nil);
+		exits(0);
+	default:
+		while(w = wait()) {
+			if(w->pid != pid)
+				continue;
+			if(w->msg[0] != 0){
+				/*
+				syslog(0, "smtpd", "validateaddress %s: %s", user, w->msg);
+				*/
+				return 0;
+			}
+			break;
+		}
+	}
+
+	snprint(buf, sizeof(buf), "%s/names.blocked", UPASLIB);
+	bp = sysopen(buf, "r", 0);
+	if(bp == 0)
+		return 1;
+	for(;;){
+		cp = Brdline(bp, '\n');
+		if(cp == 0)
+			break;
+		n = Blinelen(bp);
+		cp[n-1] = 0;
+
+		while(*cp == ' ' || *cp == '\t')
+			cp++;
+		for(p = cp; c = *p; p++){
+			if(c == '#')
+				break;
+			if(c == ' ' || c == '\t')
+				break;
+		}
+		if(p > cp){
+			*p = 0;
+			if(cistrcmp(user, cp) == 0){
+				syslog(0, "smtpd", "names.blocked blocks %s", user);
+				Bterm(bp);
+				return 0;
+			}
+		}
+	}
+	Bterm(bp);
+	return 1;
+}
+
+/*
+ *  a user can opt out of spam filtering by creating
+ *  a file in his mail directory named 'nospamfiltering'.
+ */
+int
+optoutofspamfilter(char *addr)
+{
+	char *p, *f;
+	int rv;
+
+	p = strchr(addr, '!');
+	if(p)
+		p++;
+	else
+		p = addr;
+
+
+	rv = 0;
+	f = smprint("/mail/box/%s/nospamfiltering", p);
+	if(f != nil){
+		rv = access(f, 0)==0;
+		free(f);
+	}
+
+	return rv;
+}
blob - /dev/null
blob + a31a0e67b20868f78bf3afa07852d35e9a22ae77 (mode 644)
--- /dev/null
+++ src/cmd/upas/smtp/y.tab.h
@@ -0,0 +1,25 @@
+#define	WORD	57346
+#define	DATE	57347
+#define	RESENT_DATE	57348
+#define	RETURN_PATH	57349
+#define	FROM	57350
+#define	SENDER	57351
+#define	REPLY_TO	57352
+#define	RESENT_FROM	57353
+#define	RESENT_SENDER	57354
+#define	RESENT_REPLY_TO	57355
+#define	SUBJECT	57356
+#define	TO	57357
+#define	CC	57358
+#define	BCC	57359
+#define	RESENT_TO	57360
+#define	RESENT_CC	57361
+#define	RESENT_BCC	57362
+#define	REMOTE	57363
+#define	PRECEDENCE	57364
+#define	MIMEVERSION	57365
+#define	CONTENTTYPE	57366
+#define	MESSAGEID	57367
+#define	RECEIVED	57368
+#define	MAILER	57369
+#define	BADTOKEN	57370
blob - /dev/null
blob + 612a5965f05976d52a2c8ce6d2b634a8c1091d93 (mode 644)
--- /dev/null
+++ src/cmd/upas/unesc/mkfile
@@ -0,0 +1,17 @@
+</$objtype/mkfile
+
+TARG=unesc
+
+OFILES=unesc.$O\
+
+BIN=/$objtype/bin/upas
+
+CC=pcc -c
+CFLAGS=-B
+
+UPDATE=\
+	mkfile\
+	$HFILES\
+	${OFILES:%.$O=%.c}\
+
+</sys/src/cmd/mkone
blob - /dev/null
blob + f3b4d652d2ac3e6a28ce99d723090a337652423a (mode 644)
--- /dev/null
+++ src/cmd/upas/unesc/unesc.c
@@ -0,0 +1,48 @@
+/*
+ *	upas/unesc - interpret =?foo?bar?=char?= escapes
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+
+int
+hex(int c)
+{
+	if('0' <= c && c <= '9')
+		return c - '0';
+	if('A' <= c && c <= 'F')
+		return c - 'A' + 10;
+	if('a' <= c && c <= 'f')
+		return c - 'a' + 10;
+	return 0;
+}
+
+void
+main(int argc, char **argv)
+{
+	int c;
+
+	while((c=getchar()) != EOF){
+		if(c == '='){
+			if((c=getchar()) == '?'){
+				while((c=getchar()) != EOF && c != '?')
+					continue;
+				while((c=getchar()) != EOF && c != '?')
+					continue;
+				while((c=getchar()) != EOF && c != '?'){
+					if(c == '='){
+						c = hex(getchar()) << 4;
+						c |= hex(getchar());
+					}
+					putchar(c);
+				}
+				(void) getchar();	/* consume '=' */
+			}else{
+				putchar('=');
+				putchar(c);
+			}
+		}else
+			putchar(c);
+	}
+	exit(0);
+}
blob - /dev/null
blob + b17e4578835c211933263eb0740f5b7c05b586fb (mode 644)
--- /dev/null
+++ src/cmd/upas/vf/mkfile
@@ -0,0 +1,20 @@
+<$PLAN9/src/mkhdr
+
+TARG=vf
+
+OFILES=vf.$O\
+
+LIB=../common/libcommon.a\
+
+HFILES=../common/common.h\
+	 ../common/sys.h\
+
+
+BIN=$PLAN9/bin/upas
+UPDATE=\
+	mkfile\
+	$HFILES\
+	${OFILES:%.$O=%.c}\
+
+<$PLAN9/src/mkone
+CFLAGS=$CFLAGS -I../common
blob - /dev/null
blob + db4f5ce8b539eb157655c14b89d103084f16bc51 (mode 644)
--- /dev/null
+++ src/cmd/upas/vf/vf.c
@@ -0,0 +1,1110 @@
+/*
+ *  this is a filter that changes mime types and names of
+ *  suspect executable attachments.
+ */
+#include "common.h"
+#include <ctype.h>
+
+Biobuf in;
+Biobuf out;
+
+typedef struct Mtype Mtype;
+typedef struct Hdef Hdef;
+typedef struct Hline Hline;
+typedef struct Part Part;
+
+static int	badfile(char *name);
+static int	badtype(char *type);
+static void	ctype(Part*, Hdef*, char*);
+static void	cencoding(Part*, Hdef*, char*);
+static void	cdisposition(Part*, Hdef*, char*);
+static int	decquoted(char *out, char *in, char *e);
+static char*	getstring(char *p, String *s, int dolower);
+static void	init_hdefs(void);
+static int	isattribute(char **pp, char *attr);
+static int	latin1toutf(char *out, char *in, char *e);
+static String*	mkboundary(void);
+static Part*	part(Part *pp);
+static Part*	passbody(Part *p, int dobound);
+static void	passnotheader(void);
+static void	passunixheader(void);
+static Part*	problemchild(Part *p);
+static void	readheader(Part *p);
+static Hline*	readhl(void);
+static void	readmtypes(void);
+static int	save(Part *p, char *file);
+static void	setfilename(Part *p, char *name);
+static char*	skiptosemi(char *p);
+static char*	skipwhite(char *p);
+static String*	tokenconvert(String *t);
+static void	writeheader(Part *p, int);
+
+enum
+{
+	// encodings
+	Enone=	0,
+	Ebase64,
+	Equoted,
+
+	// disposition possibilities
+	Dnone=	0,
+	Dinline,
+	Dfile,
+	Dignore,
+
+	PAD64=	'=',
+};
+
+/*
+ *  a message part; either the whole message or a subpart
+ */
+struct Part
+{
+	Part	*pp;		/* parent part */
+	Hline	*hl;		/* linked list of header lines */
+	int	disposition;
+	int	encoding;
+	int	badfile;
+	int	badtype;
+	String	*boundary;	/* boundary for multiparts */
+	int	blen;
+	String	*charset;	/* character set */
+	String	*type;		/* content type */
+	String	*filename;	/* file name */
+	Biobuf	*tmpbuf;		/* diversion input buffer */
+};
+
+/*
+ *  a (multi)line header
+ */
+struct Hline
+{
+	Hline	*next;
+	String		*s;
+};
+
+/*
+ *  header definitions for parsing
+ */
+struct Hdef
+{
+	char *type;
+	void (*f)(Part*, Hdef*, char*);
+	int len;
+};
+
+Hdef hdefs[] =
+{
+	{ "content-type:", ctype, },
+	{ "content-transfer-encoding:", cencoding, },
+	{ "content-disposition:", cdisposition, },
+	{ 0, },
+};
+
+/*
+ *  acceptable content types and their extensions
+ */
+struct Mtype {
+	Mtype	*next;
+	char 	*ext;		/* extension */
+	char	*gtype;		/* generic content type */
+	char	*stype;		/* specific content type */
+	char	class;
+};
+Mtype *mtypes;
+
+int justreject;
+char *savefile;
+
+void
+main(int argc, char **argv)
+{
+	ARGBEGIN{
+	case 'r':
+		justreject = 1;
+		break;
+	case 's':
+		savefile = ARGF();
+		if(savefile == nil)
+			exits("usage");
+		break;
+	}ARGEND;
+
+	Binit(&in, 0, OREAD);
+	Binit(&out, 1, OWRITE);
+
+	init_hdefs();
+	readmtypes();
+
+	/* pass through our standard 'From ' line */
+	passunixheader();
+
+	/* parse with the top level part */
+	part(nil);
+
+	exits(0);
+}
+
+void
+refuse(void)
+{
+	postnote(PNGROUP, getpid(), "mail refused: we don't accept executable attachments");
+	exits("mail refused: we don't accept executable attachments");
+}
+
+
+/*
+ *  parse a part; returns the ancestor whose boundary terminated
+ *  this part or nil on EOF.
+ */
+static Part*
+part(Part *pp)
+{
+	Part *p, *np;
+
+	p = mallocz(sizeof *p, 1);
+	p->pp = pp;
+	readheader(p);
+
+	if(p->boundary != nil){
+		/* the format of a multipart part is always:
+		 *   header
+		 *   null or ignored body
+		 *   boundary
+		 *   header
+		 *   body
+		 *   boundary
+		 *   ...
+		 */
+		writeheader(p, 1);
+		np = passbody(p, 1);
+		if(np != p)
+			return np;
+		for(;;){
+			np = part(p);
+			if(np != p)
+				return np;
+		}
+	} else {
+		/* no boundary */
+		/* may still be multipart if this is a forwarded message */
+		if(p->type && cistrcmp(s_to_c(p->type), "message/rfc822") == 0){
+			/* the format of forwarded message is:
+			 *   header
+			 *   header
+			 *   body
+			 */
+			writeheader(p, 1);
+			passnotheader();
+			return part(p);
+		} else {
+			/* 
+			 * This is the meat.  This may be an executable.
+			 * if so, wrap it and change its type
+			 */
+			if(p->badtype || p->badfile){
+				if(p->badfile == 2){
+					if(savefile != nil)
+						save(p, savefile);
+					syslog(0, "vf", "vf rejected %s %s", p->type?s_to_c(p->type):"?",
+						p->filename?s_to_c(p->filename):"?");
+					fprint(2, "The mail contained an executable attachment.\n");
+					fprint(2, "We refuse all mail containing such.\n");
+					refuse();
+				}
+				np = problemchild(p);
+				if(np != p)
+					return np;
+				/* if problemchild returns p, it turns out p is okay: fall thru */
+			}
+			writeheader(p, 1);
+			return passbody(p, 1);
+		}
+	}
+}
+
+/*
+ *  read and parse a complete header
+ */
+static void
+readheader(Part *p)
+{
+	Hline *hl, **l;
+	Hdef *hd;
+
+	l = &p->hl;
+	for(;;){
+		hl = readhl();
+		if(hl == nil)
+			break;
+		*l = hl;
+		l = &hl->next;
+
+		for(hd = hdefs; hd->type != nil; hd++){
+			if(cistrncmp(s_to_c(hl->s), hd->type, hd->len) == 0){
+				(*hd->f)(p, hd, s_to_c(hl->s));
+				break;
+			}
+		}
+	}
+}
+
+/*
+ *  read a possibly multiline header line
+ */
+static Hline*
+readhl(void)
+{
+	Hline *hl;
+	String *s;
+	char *p;
+	int n;
+
+	p = Brdline(&in, '\n');
+	if(p == nil)
+		return nil;
+	n = Blinelen(&in);
+	if(memchr(p, ':', n) == nil){
+		Bseek(&in, -n, 1);
+		return nil;
+	}
+	s = s_nappend(s_new(), p, n);
+	for(;;){
+		p = Brdline(&in, '\n');
+		if(p == nil)
+			break;
+		n = Blinelen(&in);
+		if(*p != ' ' && *p != '\t'){
+			Bseek(&in, -n, 1);
+			break;
+		}
+		s = s_nappend(s, p, n);
+	}
+	hl = malloc(sizeof *hl);
+	hl->s = s;
+	hl->next = nil;
+	return hl;
+}
+
+/*
+ *  write out a complete header
+ */
+static void
+writeheader(Part *p, int xfree)
+{
+	Hline *hl, *next;
+
+	for(hl = p->hl; hl != nil; hl = next){
+		Bprint(&out, "%s", s_to_c(hl->s));
+		if(xfree)
+			s_free(hl->s);
+		next = hl->next;
+		if(xfree)
+			free(hl);
+	}
+	if(xfree)
+		p->hl = nil;
+}
+
+/*
+ *  pass a body through.  return if we hit one of our ancestors'
+ *  boundaries or EOF.  if we hit a boundary, return a pointer to
+ *  that ancestor.  if we hit EOF, return nil.
+ */
+static Part*
+passbody(Part *p, int dobound)
+{
+	Part *pp;
+	Biobuf *b;
+	char *cp;
+
+	for(;;){
+		if(p->tmpbuf){
+			b = p->tmpbuf;
+			cp = Brdline(b, '\n');
+			if(cp == nil){
+				Bterm(b);
+				p->tmpbuf = nil;
+				goto Stdin;
+			}
+		}else{
+		Stdin:
+			b = &in;
+			cp = Brdline(b, '\n');
+		}
+		if(cp == nil)
+			return nil;
+		for(pp = p; pp != nil; pp = pp->pp)
+			if(pp->boundary != nil
+			&& strncmp(cp, s_to_c(pp->boundary), pp->blen) == 0){
+				if(dobound)
+					Bwrite(&out, cp, Blinelen(b));
+				else
+					Bseek(b, -Blinelen(b), 1);
+				return pp;
+			}
+		Bwrite(&out, cp, Blinelen(b));
+	}
+	return nil;
+}
+
+/*
+ *  save the message somewhere
+ */
+static vlong bodyoff;	/* clumsy hack */
+static int
+save(Part *p, char *file)
+{
+	int fd;
+	char *cp;
+
+	Bterm(&out);
+	memset(&out, 0, sizeof(out));
+
+	fd = open(file, OWRITE);
+	if(fd < 0)
+		return -1;
+	seek(fd, 0, 2);
+	Binit(&out, fd, OWRITE);
+	cp = ctime(time(0));
+	cp[28] = 0;
+	Bprint(&out, "From virusfilter %s\n", cp);
+	writeheader(p, 0);
+	bodyoff = Boffset(&out);
+	passbody(p, 1);
+	Bprint(&out, "\n");
+	Bterm(&out);
+	close(fd);
+	
+	memset(&out, 0, sizeof out);
+	Binit(&out, 1, OWRITE);
+	return 0;
+}
+
+/*
+ * write to a file but save the fd for passbody.
+ */
+static char*
+savetmp(Part *p)
+{
+	char buf[40], *name;
+	int fd;
+	
+	strcpy(buf, "/tmp/vf.XXXXXXXXXXX");
+	name = mktemp(buf);
+	if((fd = create(name, OWRITE|OEXCL, 0666)) < 0){
+		fprint(2, "error creating temporary file: %r\n");
+		refuse();
+	}
+	close(fd);
+	if(save(p, name) < 0){
+		fprint(2, "error saving temporary file: %r\n");
+		refuse();
+	}
+	if(p->tmpbuf){
+		fprint(2, "error in savetmp: already have tmp file!\n");
+		refuse();
+	}
+	p->tmpbuf = Bopen(name, OREAD|ORCLOSE);
+	if(p->tmpbuf == nil){
+		fprint(2, "error reading tempoary file: %r\n");
+		refuse();
+	}
+	Bseek(p->tmpbuf, bodyoff, 0);
+	return strdup(name);
+}
+
+/*
+ * XXX save the decoded file, run 9 unzip -tf on it, and then
+ * look at the file list.
+ */
+static int
+runchecker(Part *p)
+{
+	int pid;
+	char *name;
+	Waitmsg *w;
+	
+	if(access("/mail/lib/validateattachment", AEXEC) < 0)
+		return 0;
+	
+	name = savetmp(p);
+	fprint(2, "run checker %s\n", name);
+	switch(pid = fork()){
+	case -1:
+		sysfatal("fork: %r");
+	case 0:
+		dup(2, 1);
+		execl("/mail/lib/validateattachment", "validateattachment", name, nil);
+		_exits("exec failed");
+	}
+
+	/*
+	 * Okay to return on error - will let mail through but wrapped.
+	 */
+	w = wait();
+	if(w == nil){
+		syslog(0, "mail", "vf wait failed: %r");
+		return 0;
+	}
+	if(w->pid != pid){
+		syslog(0, "mail", "vf wrong pid %d != %d", w->pid, pid);
+		return 0;
+	}
+	if(p->filename)
+		name = s_to_c(p->filename);
+	if(strstr(w->msg, "discard")){
+		syslog(0, "mail", "vf validateattachment rejected %s", name);
+		refuse();
+	}
+	if(strstr(w->msg, "accept")){
+		syslog(0, "mail", "vf validateattachment accepted %s", name);
+		return 1;
+	}
+	free(w);
+	return 0;
+}
+
+/*
+ *  emit a multipart Part that explains the problem
+ */
+static Part*
+problemchild(Part *p)
+{
+	Part *np;
+	Hline *hl;
+	String *boundary;
+	char *cp;
+
+	/*
+	 * We don't know whether the attachment is okay.
+	 * If there's an external checker, let it have a crack at it.
+	 */
+	if(runchecker(p) > 0)
+		return p;
+
+fprint(2, "x\n");
+	syslog(0, "mail", "vf wrapped %s %s", p->type?s_to_c(p->type):"?",
+		p->filename?s_to_c(p->filename):"?");
+fprint(2, "x\n");
+
+	boundary = mkboundary();
+fprint(2, "x\n");
+	/* print out non-mime headers */
+	for(hl = p->hl; hl != nil; hl = hl->next)
+		if(cistrncmp(s_to_c(hl->s), "content-", 8) != 0)
+			Bprint(&out, "%s", s_to_c(hl->s));
+
+fprint(2, "x\n");
+	/* add in our own multipart headers and message */
+	Bprint(&out, "Content-Type: multipart/mixed;\n");
+	Bprint(&out, "\tboundary=\"%s\"\n", s_to_c(boundary));
+	Bprint(&out, "Content-Disposition: inline\n");
+	Bprint(&out, "\n");
+	Bprint(&out, "This is a multi-part message in MIME format.\n");
+	Bprint(&out, "--%s\n", s_to_c(boundary));
+	Bprint(&out, "Content-Disposition: inline\n");
+	Bprint(&out, "Content-Type: text/plain; charset=\"US-ASCII\"\n");
+	Bprint(&out, "Content-Transfer-Encoding: 7bit\n");
+	Bprint(&out, "\n");
+	Bprint(&out, "from postmaster@%s:\n", sysname());
+	Bprint(&out, "The following attachment had content that we can't\n");
+	Bprint(&out, "prove to be harmless.  To avoid possible automatic\n");
+	Bprint(&out, "execution, we changed the content headers.\n");
+	Bprint(&out, "The original header was:\n\n");
+
+	/* print out original header lines */
+	for(hl = p->hl; hl != nil; hl = hl->next)
+		if(cistrncmp(s_to_c(hl->s), "content-", 8) == 0)
+			Bprint(&out, "\t%s", s_to_c(hl->s));
+	Bprint(&out, "--%s\n", s_to_c(boundary));
+
+	/* change file name */
+	if(p->filename)
+		s_append(p->filename, ".suspect");
+	else
+		p->filename = s_copy("file.suspect");
+
+	/* print out new header */
+	Bprint(&out, "Content-Type: application/octet-stream\n");
+	Bprint(&out, "Content-Disposition: attachment; filename=\"%s\"\n", s_to_c(p->filename));
+	switch(p->encoding){
+	case Enone:
+		break;
+	case Ebase64:
+		Bprint(&out, "Content-Transfer-Encoding: base64\n");
+		break;
+	case Equoted:
+		Bprint(&out, "Content-Transfer-Encoding: quoted-printable\n");
+		break;
+	}
+
+fprint(2, "z\n");
+	/* pass the body */
+	np = passbody(p, 0);
+
+fprint(2, "w\n");
+	/* add the new boundary and the original terminator */
+	Bprint(&out, "--%s--\n", s_to_c(boundary));
+	if(np && np->boundary){
+		cp = Brdline(&in, '\n');
+		Bwrite(&out, cp, Blinelen(&in));
+	}
+
+fprint(2, "a %p\n", np);
+	return np;
+}
+
+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;
+}
+
+/*
+ *  parse content type header 
+ */
+static void
+ctype(Part *p, Hdef *h, char *cp)
+{
+	String *s;
+
+	cp += h->len;
+	cp = skipwhite(cp);
+
+	p->type = s_new();
+	cp = getstring(cp, p->type, 1);
+	if(badtype(s_to_c(p->type)))
+		p->badtype = 1;
+	
+	while(*cp){
+		if(isattribute(&cp, "boundary")){
+			s = s_new();
+			cp = getstring(cp, s, 0);
+			p->boundary = s_reset(p->boundary);
+			s_append(p->boundary, "--");
+			s_append(p->boundary, s_to_c(s));
+			p->blen = s_len(p->boundary);
+			s_free(s);
+		} else if(cistrncmp(cp, "multipart", 9) == 0){
+			/*
+			 *  the first unbounded part of a multipart message,
+			 *  the preamble, is not displayed or saved
+			 */
+		} else if(isattribute(&cp, "name")){
+			setfilename(p, cp);
+		} else if(isattribute(&cp, "charset")){
+			if(p->charset == nil)
+				p->charset = s_new();
+			cp = getstring(cp, s_reset(p->charset), 0);
+		}
+		
+		cp = skiptosemi(cp);
+	}
+}
+
+/*
+ *  parse content encoding header 
+ */
+static void
+cencoding(Part *m, Hdef *h, char *p)
+{
+	p += h->len;
+	p = skipwhite(p);
+	if(cistrncmp(p, "base64", 6) == 0)
+		m->encoding = Ebase64;
+	else if(cistrncmp(p, "quoted-printable", 16) == 0)
+		m->encoding = Equoted;
+}
+
+/*
+ *  parse content disposition header 
+ */
+static void
+cdisposition(Part *p, Hdef *h, char *cp)
+{
+	cp += h->len;
+	cp = skipwhite(cp);
+	while(*cp){
+		if(cistrncmp(cp, "inline", 6) == 0){
+			p->disposition = Dinline;
+		} else if(cistrncmp(cp, "attachment", 10) == 0){
+			p->disposition = Dfile;
+		} else if(cistrncmp(cp, "filename=", 9) == 0){
+			cp += 9;
+			setfilename(p, cp);
+		}
+		cp = skiptosemi(cp);
+	}
+
+}
+
+static void
+setfilename(Part *p, char *name)
+{
+	if(p->filename == nil)
+		p->filename = s_new();
+	getstring(name, s_reset(p->filename), 0);
+	p->filename = tokenconvert(p->filename);
+	p->badfile = badfile(s_to_c(p->filename));
+}
+
+static char*
+skipwhite(char *p)
+{
+	while(isspace(*p))
+		p++;
+	return p;
+}
+
+static char*
+skiptosemi(char *p)
+{
+	while(*p && *p != ';')
+		p++;
+	while(*p == ';' || isspace(*p))
+		p++;
+	return p;
+}
+
+/*
+ *  parse a possibly "'d string from a header.  A
+ *  ';' terminates the string.
+ */
+static char*
+getstring(char *p, String *s, int dolower)
+{
+	s = s_reset(s);
+	p = skipwhite(p);
+	if(*p == '"'){
+		p++;
+		for(;*p && *p != '"'; p++)
+			if(dolower)
+				s_putc(s, tolower(*p));
+			else
+				s_putc(s, *p);
+		if(*p == '"')
+			p++;
+		s_terminate(s);
+
+		return p;
+	}
+
+	for(; *p && !isspace(*p) && *p != ';'; p++)
+		if(dolower)
+			s_putc(s, tolower(*p));
+		else
+			s_putc(s, *p);
+	s_terminate(s);
+
+	return p;
+}
+
+static void
+init_hdefs(void)
+{
+	Hdef *hd;
+	static int already;
+
+	if(already)
+		return;
+	already = 1;
+
+	for(hd = hdefs; hd->type != nil; hd++)
+		hd->len = strlen(hd->type);
+}
+
+/*
+ *  create a new boundary
+ */
+static String*
+mkboundary(void)
+{
+	char buf[32];
+	int i;
+	static int already;
+
+	if(already == 0){
+		srand((time(0)<<16)|getpid());
+		already = 1;
+	}
+	strcpy(buf, "upas-");
+	for(i = 5; i < sizeof(buf)-1; i++)
+		buf[i] = 'a' + nrand(26);
+	buf[i] = 0;
+	return s_copy(buf);
+}
+
+/*
+ *  skip blank lines till header
+ */
+static void
+passnotheader(void)
+{
+	char *cp;
+	int i, n;
+
+	while((cp = Brdline(&in, '\n')) != nil){
+		n = Blinelen(&in);
+		for(i = 0; i < n-1; i++)
+			if(cp[i] != ' ' && cp[i] != '\t' && cp[i] != '\r'){
+				Bseek(&in, -n, 1);
+				return;
+			}
+		Bwrite(&out, cp, n);
+	}
+}
+
+/*
+ *  pass unix header lines
+ */
+static void
+passunixheader(void)
+{
+	char *p;
+	int n;
+
+	while((p = Brdline(&in, '\n')) != nil){
+		n = Blinelen(&in);
+		if(strncmp(p, "From ", 5) != 0){
+			Bseek(&in, -n, 1);
+			break;
+		}
+		Bwrite(&out, p, n);
+	}
+}
+
+/*
+ *  Read mime types
+ */
+static void
+readmtypes(void)
+{
+	Biobuf *b;
+	char *p;
+	char *f[6];
+	Mtype *m;
+	Mtype **l;
+
+	b = Bopen(unsharp("#9/sys/lib/mimetype"), OREAD);
+	if(b == nil)
+		return;
+
+	l = &mtypes;
+	while((p = Brdline(b, '\n')) != nil){
+		if(*p == '#')
+			continue;
+		p[Blinelen(b)-1] = 0;
+		if(tokenize(p, f, nelem(f)) < 5)
+			continue;
+		m = mallocz(sizeof *m, 1);
+		if(m == nil)
+			goto err;
+		m->ext = strdup(f[0]);
+		if(m->ext == 0)
+			goto err;
+		m->gtype = strdup(f[1]);
+		if(m->gtype == 0)
+			goto err;
+		m->stype = strdup(f[2]);
+		if(m->stype == 0)
+			goto err;
+		m->class = *f[4];
+		*l = m;
+		l = &(m->next);
+	}
+	Bterm(b);
+	return;
+err:
+	if(m == nil)
+		return;
+	free(m->ext);
+	free(m->gtype);
+	free(m->stype);
+	free(m);
+	Bterm(b);
+}
+
+/*
+ *  if the class is 'm' or 'y', accept it
+ *  if the class is 'p' check a previous extension
+ *  otherwise, filename is bad
+ */
+static int
+badfile(char *name)
+{
+	char *p;
+	Mtype *m;
+	int rv;
+
+	p = strrchr(name, '.');
+	if(p == nil)
+		return 0;
+
+	for(m = mtypes; m != nil; m = m->next)
+		if(cistrcmp(p, m->ext) == 0){
+			switch(m->class){
+			case 'm':
+			case 'y':
+				return 0;
+			case 'p':
+				*p = 0;
+				rv = badfile(name);
+				*p = '.';
+				return rv;
+			case 'r':
+				return 2;
+			}
+		}
+	if(justreject)
+		return 0;
+	return 1;
+}
+
+/*
+ *  if the class is 'm' or 'y' or 'p', accept it
+ *  otherwise, filename is bad
+ */
+static int
+badtype(char *type)
+{
+	Mtype *m;
+	char *s, *fix;
+	int rv = 1;
+
+	if(justreject)
+		return 0;
+
+	fix = s = strchr(type, '/');
+	if(s != nil)
+		*s++ = 0;
+	else
+		s = "-";
+
+	for(m = mtypes; m != nil; m = m->next){
+		if(cistrcmp(type, m->gtype) != 0)
+			continue;
+		if(cistrcmp(s, m->stype) != 0)
+			continue;
+		switch(m->class){
+		case 'y':
+		case 'p':
+		case 'm':
+			rv = 0;
+			break;
+		}
+		break;
+	}
+
+	if(fix != nil)
+		*fix = '/';
+	return rv;
+}
+
+/* rfc2047 non-ascii */
+typedef struct Charset Charset;
+struct Charset {
+	char *name;
+	int len;
+	int convert;
+} charsets[] =
+{
+	{ "us-ascii",		8,	1, },
+	{ "utf-8",		5,	0, },
+	{ "iso-8859-1",		10,	1, },
+};
+
+/*
+ *  convert to UTF if need be
+ */
+static String*
+tokenconvert(String *t)
+{
+	String *s;
+	char decoded[1024];
+	char utfbuf[2*1024];
+	int i, len;
+	char *e;
+	char *token;
+
+	token = s_to_c(t);
+	len = s_len(t);
+
+	if(token[0] != '=' || token[1] != '?' ||
+	   token[len-2] != '?' || token[len-1] != '=')
+		goto err;
+	e = token+len-2;
+	token += 2;
+
+	// bail if we don't understand the character set
+	for(i = 0; i < nelem(charsets); i++)
+		if(cistrncmp(charsets[i].name, token, charsets[i].len) == 0)
+		if(token[charsets[i].len] == '?'){
+			token += charsets[i].len + 1;
+			break;
+		}
+	if(i >= nelem(charsets))
+		goto err;
+
+	// bail if it doesn't fit 
+	if(strlen(token) > sizeof(decoded)-1)
+		goto err;
+
+	// bail if we don't understand the encoding
+	if(cistrncmp(token, "b?", 2) == 0){
+		token += 2;
+		len = dec64((uchar*)decoded, sizeof(decoded), token, e-token);
+		decoded[len] = 0;
+	} else if(cistrncmp(token, "q?", 2) == 0){
+		token += 2;
+		len = decquoted(decoded, token, e);
+		if(len > 0 && decoded[len-1] == '\n')
+			len--;
+		decoded[len] = 0;
+	} else
+		goto err;
+
+	s = nil;
+	switch(charsets[i].convert){
+	case 0:
+		s = s_copy(decoded);
+		break;
+	case 1:
+		s = s_new();
+		latin1toutf(utfbuf, decoded, decoded+len);
+		s_append(s, utfbuf);
+		break;
+	}
+
+	return s;
+err:
+	return s_clone(t);
+}
+
+/*
+ *  decode quoted 
+ */
+enum
+{
+	Self=	1,
+	Hex=	2,
+};
+uchar	tableqp[256];
+
+static void
+initquoted(void)
+{
+	int c;
+
+	memset(tableqp, 0, 256);
+	for(c = ' '; c <= '<'; c++)
+		tableqp[c] = Self;
+	for(c = '>'; c <= '~'; c++)
+		tableqp[c] = Self;
+	tableqp['\t'] = Self;
+	tableqp['='] = Hex;
+}
+
+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 0;
+}
+
+static char*
+decquotedline(char *out, char *in, char *e)
+{
+	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(tableqp[c]){
+		case Self:
+			*out++ = c;
+			break;
+		case Hex:
+			c = hex2int(*in++)<<4;
+			c |= hex2int(*in++);
+			*out++ = c;
+			break;
+		}
+	}
+	if(!soft)
+		*out++ = '\n';
+	*out = 0;
+
+	return out;
+}
+
+static int
+decquoted(char *out, char *in, char *e)
+{
+	char *p, *nl;
+
+	if(tableqp[' '] == 0)
+		initquoted();
+
+	p = out;
+	while((nl = strchr(in, '\n')) != nil && nl < e){
+		p = decquotedline(p, in, nl);
+		in = nl + 1;
+	}
+	if(in < e)
+		p = decquotedline(p, in, e-1);
+
+	// make sure we end with a new line
+	if(*(p-1) != '\n'){
+		*p++ = '\n';
+		*p = 0;
+	}
+
+	return p - out;
+}
+
+/* translate latin1 directly since it fits neatly in utf */
+static int
+latin1toutf(char *out, char *in, char *e)
+{
+	Rune r;
+	char *p;
+
+	p = out;
+	for(; in < e; in++){
+		r = (*in) & 0xff;
+		p += runetochar(p, &r);
+	}
+	*p = 0;
+	return p - out;
+}