aboutsummaryrefslogtreecommitdiff
path: root/src/cmd/upas
diff options
context:
space:
mode:
authorrsc <devnull@localhost>2005-10-29 16:26:44 +0000
committerrsc <devnull@localhost>2005-10-29 16:26:44 +0000
commit5cdb17983ae6e6367ad7a940cb219eab247a9304 (patch)
tree8ca1ef49af2a96e7daebe624d91fdf679814a057 /src/cmd/upas
parentcd3745196389579fb78b9b01ef1daefb5a57aa71 (diff)
downloadplan9port-5cdb17983ae6e6367ad7a940cb219eab247a9304.tar.gz
plan9port-5cdb17983ae6e6367ad7a940cb219eab247a9304.tar.bz2
plan9port-5cdb17983ae6e6367ad7a940cb219eab247a9304.zip
Thanks to John Cummings.
Diffstat (limited to 'src/cmd/upas')
-rw-r--r--src/cmd/upas/common/libsys.c1001
-rw-r--r--src/cmd/upas/common/mail.c57
-rw-r--r--src/cmd/upas/common/makefile18
-rw-r--r--src/cmd/upas/common/mkfile20
-rw-r--r--src/cmd/upas/common/process.c175
-rw-r--r--src/cmd/upas/common/sys.h85
-rw-r--r--src/cmd/upas/filterkit/dat.h8
-rw-r--r--src/cmd/upas/filterkit/deliver.c60
-rw-r--r--src/cmd/upas/filterkit/list.c315
-rw-r--r--src/cmd/upas/filterkit/mkfile21
-rwxr-xr-xsrc/cmd/upas/filterkit/pipefrom.sample24
-rw-r--r--src/cmd/upas/filterkit/pipeto.sample73
-rw-r--r--src/cmd/upas/filterkit/pipeto.sample-hold43
-rw-r--r--src/cmd/upas/filterkit/readaddrs.c98
-rw-r--r--src/cmd/upas/filterkit/token.c89
-rw-r--r--src/cmd/upas/fs/dat.h221
-rw-r--r--src/cmd/upas/fs/fs.c1704
-rw-r--r--src/cmd/upas/fs/imap4.c876
-rw-r--r--src/cmd/upas/fs/mbox.c1601
-rw-r--r--src/cmd/upas/fs/mkfile29
-rw-r--r--src/cmd/upas/fs/mkfile.927
-rw-r--r--src/cmd/upas/fs/plan9.c405
-rw-r--r--src/cmd/upas/fs/pop3.c700
-rw-r--r--src/cmd/upas/fs/readdir.c15
-rw-r--r--src/cmd/upas/fs/strtotm.c113
-rw-r--r--src/cmd/upas/fs/tester.c81
-rw-r--r--src/cmd/upas/marshal/marshal.c1857
-rw-r--r--src/cmd/upas/marshal/mkfile20
-rw-r--r--src/cmd/upas/misc/gone.fishing9
-rw-r--r--src/cmd/upas/misc/gone.msg4
-rw-r--r--src/cmd/upas/misc/mail.c51
-rwxr-xr-xsrc/cmd/upas/misc/mail.rc12
-rw-r--r--src/cmd/upas/misc/mail.sh12
-rw-r--r--src/cmd/upas/misc/makefile44
-rw-r--r--src/cmd/upas/misc/mkfile39
-rw-r--r--src/cmd/upas/misc/namefiles2
-rwxr-xr-xsrc/cmd/upas/misc/omail.rc14
-rwxr-xr-xsrc/cmd/upas/misc/qmail6
-rwxr-xr-xsrc/cmd/upas/misc/remotemail7
-rw-r--r--src/cmd/upas/misc/rewrite20
-rw-r--r--src/cmd/upas/ml/common.c197
-rw-r--r--src/cmd/upas/ml/dat.h25
-rw-r--r--src/cmd/upas/ml/mkfile40
-rw-r--r--src/cmd/upas/ml/ml.c167
-rw-r--r--src/cmd/upas/ml/mlmgr.c110
-rw-r--r--src/cmd/upas/ml/mlowner.c64
-rw-r--r--src/cmd/upas/ned/mkfile20
-rw-r--r--src/cmd/upas/ned/nedmail.c2586
-rw-r--r--src/cmd/upas/pop3/mkfile16
-rw-r--r--src/cmd/upas/pop3/pop3.c804
-rw-r--r--src/cmd/upas/q/mkfile22
-rw-r--r--src/cmd/upas/q/qer.c193
-rw-r--r--src/cmd/upas/q/runq.c766
-rw-r--r--src/cmd/upas/scanmail/common.c667
-rw-r--r--src/cmd/upas/scanmail/mkfile24
-rw-r--r--src/cmd/upas/scanmail/scanmail.c476
-rw-r--r--src/cmd/upas/scanmail/spam.h62
-rw-r--r--src/cmd/upas/scanmail/testscan.c212
-rw-r--r--src/cmd/upas/send/authorize.c29
-rw-r--r--src/cmd/upas/send/bind.c133
-rw-r--r--src/cmd/upas/send/cat_mail.c60
-rw-r--r--src/cmd/upas/send/dest.c260
-rw-r--r--src/cmd/upas/send/filter.c128
-rw-r--r--src/cmd/upas/send/gateway.c24
-rw-r--r--src/cmd/upas/send/local.c129
-rw-r--r--src/cmd/upas/send/log.c85
-rw-r--r--src/cmd/upas/send/main.c575
-rw-r--r--src/cmd/upas/send/makefile46
-rw-r--r--src/cmd/upas/send/message.c573
-rw-r--r--src/cmd/upas/send/mkfile52
-rw-r--r--src/cmd/upas/send/regtest.c36
-rw-r--r--src/cmd/upas/send/rewrite.c315
-rw-r--r--src/cmd/upas/send/send.h108
-rw-r--r--src/cmd/upas/send/skipequiv.c93
-rw-r--r--src/cmd/upas/send/translate.c43
-rw-r--r--src/cmd/upas/send/tryit29
-rw-r--r--src/cmd/upas/smtp/greylist.c274
-rw-r--r--src/cmd/upas/smtp/mkfile54
-rw-r--r--src/cmd/upas/smtp/mxdial.c333
-rw-r--r--src/cmd/upas/smtp/rfc822.tab.c1260
-rw-r--r--src/cmd/upas/smtp/rfc822.tab.h98
-rw-r--r--src/cmd/upas/smtp/rfc822.y778
-rw-r--r--src/cmd/upas/smtp/rmtdns.c58
-rw-r--r--src/cmd/upas/smtp/smtp.c1122
-rw-r--r--src/cmd/upas/smtp/smtp.h61
-rw-r--r--src/cmd/upas/smtp/smtpd.c1494
-rw-r--r--src/cmd/upas/smtp/smtpd.h68
-rw-r--r--src/cmd/upas/smtp/smtpd.y317
-rw-r--r--src/cmd/upas/smtp/spam.c591
-rw-r--r--src/cmd/upas/smtp/y.tab.h25
-rw-r--r--src/cmd/upas/unesc/mkfile17
-rw-r--r--src/cmd/upas/unesc/unesc.c48
-rw-r--r--src/cmd/upas/vf/mkfile20
-rw-r--r--src/cmd/upas/vf/vf.c1110
94 files changed, 26853 insertions, 0 deletions
diff --git a/src/cmd/upas/common/libsys.c b/src/cmd/upas/common/libsys.c
new file mode 100644
index 00000000..67f36798
--- /dev/null
+++ b/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;
+}
diff --git a/src/cmd/upas/common/mail.c b/src/cmd/upas/common/mail.c
new file mode 100644
index 00000000..5347c655
--- /dev/null
+++ b/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;
+}
diff --git a/src/cmd/upas/common/makefile b/src/cmd/upas/common/makefile
new file mode 100644
index 00000000..c7beae81
--- /dev/null
+++ b/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
+
diff --git a/src/cmd/upas/common/mkfile b/src/cmd/upas/common/mkfile
new file mode 100644
index 00000000..8cfaf267
--- /dev/null
+++ b/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
diff --git a/src/cmd/upas/common/process.c b/src/cmd/upas/common/process.c
new file mode 100644
index 00000000..16b21aef
--- /dev/null
+++ b/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);
+}
diff --git a/src/cmd/upas/common/sys.h b/src/cmd/upas/common/sys.h
new file mode 100644
index 00000000..960aebb3
--- /dev/null
+++ b/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
diff --git a/src/cmd/upas/filterkit/dat.h b/src/cmd/upas/filterkit/dat.h
new file mode 100644
index 00000000..dd4572db
--- /dev/null
+++ b/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*);
diff --git a/src/cmd/upas/filterkit/deliver.c b/src/cmd/upas/filterkit/deliver.c
new file mode 100644
index 00000000..33903708
--- /dev/null
+++ b/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);
+}
diff --git a/src/cmd/upas/filterkit/list.c b/src/cmd/upas/filterkit/list.c
new file mode 100644
index 00000000..6c23dcc1
--- /dev/null
+++ b/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();
+}
diff --git a/src/cmd/upas/filterkit/mkfile b/src/cmd/upas/filterkit/mkfile
new file mode 100644
index 00000000..c077d564
--- /dev/null
+++ b/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
+
diff --git a/src/cmd/upas/filterkit/pipefrom.sample b/src/cmd/upas/filterkit/pipefrom.sample
new file mode 100755
index 00000000..616bc683
--- /dev/null
+++ b/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
diff --git a/src/cmd/upas/filterkit/pipeto.sample b/src/cmd/upas/filterkit/pipeto.sample
new file mode 100644
index 00000000..82ef9d1f
--- /dev/null
+++ b/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
diff --git a/src/cmd/upas/filterkit/pipeto.sample-hold b/src/cmd/upas/filterkit/pipeto.sample-hold
new file mode 100644
index 00000000..5794f732
--- /dev/null
+++ b/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
diff --git a/src/cmd/upas/filterkit/readaddrs.c b/src/cmd/upas/filterkit/readaddrs.c
new file mode 100644
index 00000000..9aadc1ab
--- /dev/null
+++ b/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;
+}
diff --git a/src/cmd/upas/filterkit/token.c b/src/cmd/upas/filterkit/token.c
new file mode 100644
index 00000000..8fbcda66
--- /dev/null
+++ b/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);
+}
diff --git a/src/cmd/upas/fs/dat.h b/src/cmd/upas/fs/dat.h
new file mode 100644
index 00000000..ffcbf5b3
--- /dev/null
+++ b/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;
+
diff --git a/src/cmd/upas/fs/fs.c b/src/cmd/upas/fs/fs.c
new file mode 100644
index 00000000..b68b8d2e
--- /dev/null
+++ b/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);
+}
diff --git a/src/cmd/upas/fs/imap4.c b/src/cmd/upas/fs/imap4.c
new file mode 100644
index 00000000..152d15cf
--- /dev/null
+++ b/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, '"');
+}
diff --git a/src/cmd/upas/fs/mbox.c b/src/cmd/upas/fs/mbox.c
new file mode 100644
index 00000000..c539df8d
--- /dev/null
+++ b/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);
+}
+
diff --git a/src/cmd/upas/fs/mkfile b/src/cmd/upas/fs/mkfile
new file mode 100644
index 00000000..c596ddfc
--- /dev/null
+++ b/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
diff --git a/src/cmd/upas/fs/mkfile.9 b/src/cmd/upas/fs/mkfile.9
new file mode 100644
index 00000000..05239549
--- /dev/null
+++ b/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
diff --git a/src/cmd/upas/fs/plan9.c b/src/cmd/upas/fs/plan9.c
new file mode 100644
index 00000000..89ee2922
--- /dev/null
+++ b/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;
+}
diff --git a/src/cmd/upas/fs/pop3.c b/src/cmd/upas/fs/pop3.c
new file mode 100644
index 00000000..4ea9adb3
--- /dev/null
+++ b/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;
+}
+
diff --git a/src/cmd/upas/fs/readdir.c b/src/cmd/upas/fs/readdir.c
new file mode 100644
index 00000000..42028d4c
--- /dev/null
+++ b/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);
+}
diff --git a/src/cmd/upas/fs/strtotm.c b/src/cmd/upas/fs/strtotm.c
new file mode 100644
index 00000000..bcf0bcee
--- /dev/null
+++ b/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;
+}
diff --git a/src/cmd/upas/fs/tester.c b/src/cmd/upas/fs/tester.c
new file mode 100644
index 00000000..3d24012e
--- /dev/null
+++ b/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);
+}
diff --git a/src/cmd/upas/marshal/marshal.c b/src/cmd/upas/marshal/marshal.c
new file mode 100644
index 00000000..cfc186c0
--- /dev/null
+++ b/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, '"');
+}
diff --git a/src/cmd/upas/marshal/mkfile b/src/cmd/upas/marshal/mkfile
new file mode 100644
index 00000000..bf446476
--- /dev/null
+++ b/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
+
diff --git a/src/cmd/upas/misc/gone.fishing b/src/cmd/upas/misc/gone.fishing
new file mode 100644
index 00000000..a304271c
--- /dev/null
+++ b/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
+}
diff --git a/src/cmd/upas/misc/gone.msg b/src/cmd/upas/misc/gone.msg
new file mode 100644
index 00000000..9eb1e5a6
--- /dev/null
+++ b/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.
diff --git a/src/cmd/upas/misc/mail.c b/src/cmd/upas/misc/mail.c
new file mode 100644
index 00000000..20cf2397
--- /dev/null
+++ b/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);
+}
+
diff --git a/src/cmd/upas/misc/mail.rc b/src/cmd/upas/misc/mail.rc
new file mode 100755
index 00000000..6913d743
--- /dev/null
+++ b/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 $*
+}
diff --git a/src/cmd/upas/misc/mail.sh b/src/cmd/upas/misc/mail.sh
new file mode 100644
index 00000000..a41519ae
--- /dev/null
+++ b/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
diff --git a/src/cmd/upas/misc/makefile b/src/cmd/upas/misc/makefile
new file mode 100644
index 00000000..e00c4f7e
--- /dev/null
+++ b/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
+
diff --git a/src/cmd/upas/misc/mkfile b/src/cmd/upas/misc/mkfile
new file mode 100644
index 00000000..0bbd8a5a
--- /dev/null
+++ b/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
diff --git a/src/cmd/upas/misc/namefiles b/src/cmd/upas/misc/namefiles
new file mode 100644
index 00000000..ab3853b1
--- /dev/null
+++ b/src/cmd/upas/misc/namefiles
@@ -0,0 +1,2 @@
+names.local
+names.global
diff --git a/src/cmd/upas/misc/omail.rc b/src/cmd/upas/misc/omail.rc
new file mode 100755
index 00000000..ed64f38c
--- /dev/null
+++ b/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 $*
+}
diff --git a/src/cmd/upas/misc/qmail b/src/cmd/upas/misc/qmail
new file mode 100755
index 00000000..77a3d228
--- /dev/null
+++ b/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
diff --git a/src/cmd/upas/misc/remotemail b/src/cmd/upas/misc/remotemail
new file mode 100755
index 00000000..312ee587
--- /dev/null
+++ b/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 $*
diff --git a/src/cmd/upas/misc/rewrite b/src/cmd/upas/misc/rewrite
new file mode 100644
index 00000000..fd724eb1
--- /dev/null
+++ b/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'"
diff --git a/src/cmd/upas/ml/common.c b/src/cmd/upas/ml/common.c
new file mode 100644
index 00000000..307a4925
--- /dev/null
+++ b/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;
+ }
+}
diff --git a/src/cmd/upas/ml/dat.h b/src/cmd/upas/ml/dat.h
new file mode 100644
index 00000000..3527af50
--- /dev/null
+++ b/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);
diff --git a/src/cmd/upas/ml/mkfile b/src/cmd/upas/ml/mkfile
new file mode 100644
index 00000000..5142e56f
--- /dev/null
+++ b/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
+ }
diff --git a/src/cmd/upas/ml/ml.c b/src/cmd/upas/ml/ml.c
new file mode 100644
index 00000000..8372dc9d
--- /dev/null
+++ b/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);
+}
diff --git a/src/cmd/upas/ml/mlmgr.c b/src/cmd/upas/ml/mlmgr.c
new file mode 100644
index 00000000..a1d1b907
--- /dev/null
+++ b/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;
+}
diff --git a/src/cmd/upas/ml/mlowner.c b/src/cmd/upas/ml/mlowner.c
new file mode 100644
index 00000000..5bb98a29
--- /dev/null
+++ b/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);
+}
diff --git a/src/cmd/upas/ned/mkfile b/src/cmd/upas/ned/mkfile
new file mode 100644
index 00000000..82025558
--- /dev/null
+++ b/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
+
diff --git a/src/cmd/upas/ned/nedmail.c b/src/cmd/upas/ned/nedmail.c
new file mode 100644
index 00000000..4fa7a88f
--- /dev/null
+++ b/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);
+}
diff --git a/src/cmd/upas/pop3/mkfile b/src/cmd/upas/pop3/mkfile
new file mode 100644
index 00000000..3f3df413
--- /dev/null
+++ b/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
diff --git a/src/cmd/upas/pop3/pop3.c b/src/cmd/upas/pop3/pop3.c
new file mode 100644
index 00000000..84e92b18
--- /dev/null
+++ b/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);
+}
+
diff --git a/src/cmd/upas/q/mkfile b/src/cmd/upas/q/mkfile
new file mode 100644
index 00000000..0aa5e52c
--- /dev/null
+++ b/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
diff --git a/src/cmd/upas/q/qer.c b/src/cmd/upas/q/qer.c
new file mode 100644
index 00000000..10882896
--- /dev/null
+++ b/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;
+}
diff --git a/src/cmd/upas/q/runq.c b/src/cmd/upas/q/runq.c
new file mode 100644
index 00000000..7ab055ed
--- /dev/null
+++ b/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);
+}
diff --git a/src/cmd/upas/scanmail/common.c b/src/cmd/upas/scanmail/common.c
new file mode 100644
index 00000000..b6ea720d
--- /dev/null
+++ b/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,
+};
diff --git a/src/cmd/upas/scanmail/mkfile b/src/cmd/upas/scanmail/mkfile
new file mode 100644
index 00000000..5f0db855
--- /dev/null
+++ b/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
diff --git a/src/cmd/upas/scanmail/scanmail.c b/src/cmd/upas/scanmail/scanmail.c
new file mode 100644
index 00000000..444bbcdd
--- /dev/null
+++ b/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;
+}
diff --git a/src/cmd/upas/scanmail/spam.h b/src/cmd/upas/scanmail/spam.h
new file mode 100644
index 00000000..f1d24b2e
--- /dev/null
+++ b/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*);
diff --git a/src/cmd/upas/scanmail/testscan.c b/src/cmd/upas/scanmail/testscan.c
new file mode 100644
index 00000000..e5ea59ad
--- /dev/null
+++ b/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;
+}
diff --git a/src/cmd/upas/send/authorize.c b/src/cmd/upas/send/authorize.c
new file mode 100644
index 00000000..6fa12c57
--- /dev/null
+++ b/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);
+}
diff --git a/src/cmd/upas/send/bind.c b/src/cmd/upas/send/bind.c
new file mode 100644
index 00000000..8a8fc8ea
--- /dev/null
+++ b/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;
+}
+
diff --git a/src/cmd/upas/send/cat_mail.c b/src/cmd/upas/send/cat_mail.c
new file mode 100644
index 00000000..cdd16ece
--- /dev/null
+++ b/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;
+}
diff --git a/src/cmd/upas/send/dest.c b/src/cmd/upas/send/dest.c
new file mode 100644
index 00000000..fa538547
--- /dev/null
+++ b/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;
+}
diff --git a/src/cmd/upas/send/filter.c b/src/cmd/upas/send/filter.c
new file mode 100644
index 00000000..cfee5253
--- /dev/null
+++ b/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;
+}
diff --git a/src/cmd/upas/send/gateway.c b/src/cmd/upas/send/gateway.c
new file mode 100644
index 00000000..f3b2b36d
--- /dev/null
+++ b/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);
+ }
+}
diff --git a/src/cmd/upas/send/local.c b/src/cmd/upas/send/local.c
new file mode 100644
index 00000000..93136b7c
--- /dev/null
+++ b/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;
+}
diff --git a/src/cmd/upas/send/log.c b/src/cmd/upas/send/log.c
new file mode 100644
index 00000000..52df3800
--- /dev/null
+++ b/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);
+}
diff --git a/src/cmd/upas/send/main.c b/src/cmd/upas/send/main.c
new file mode 100644
index 00000000..6c455833
--- /dev/null
+++ b/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;
+}
diff --git a/src/cmd/upas/send/makefile b/src/cmd/upas/send/makefile
new file mode 100644
index 00000000..f0abcf0c
--- /dev/null
+++ b/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
diff --git a/src/cmd/upas/send/message.c b/src/cmd/upas/send/message.c
new file mode 100644
index 00000000..eab6160c
--- /dev/null
+++ b/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);
+}
diff --git a/src/cmd/upas/send/mkfile b/src/cmd/upas/send/mkfile
new file mode 100644
index 00000000..a49fde17
--- /dev/null
+++ b/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
+# }
diff --git a/src/cmd/upas/send/regtest.c b/src/cmd/upas/send/regtest.c
new file mode 100644
index 00000000..52e31258
--- /dev/null
+++ b/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> ");
+ }
+}
diff --git a/src/cmd/upas/send/rewrite.c b/src/cmd/upas/send/rewrite.c
new file mode 100644
index 00000000..4f40b293
--- /dev/null
+++ b/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:"...");
+ }
+}
+
diff --git a/src/cmd/upas/send/send.h b/src/cmd/upas/send/send.h
new file mode 100644
index 00000000..6caa37b2
--- /dev/null
+++ b/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);
diff --git a/src/cmd/upas/send/skipequiv.c b/src/cmd/upas/send/skipequiv.c
new file mode 100644
index 00000000..f40181ad
--- /dev/null
+++ b/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;
+}
diff --git a/src/cmd/upas/send/translate.c b/src/cmd/upas/send/translate.c
new file mode 100644
index 00000000..0332659c
--- /dev/null
+++ b/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;
+}
diff --git a/src/cmd/upas/send/tryit b/src/cmd/upas/send/tryit
new file mode 100644
index 00000000..fed3a2a6
--- /dev/null
+++ b/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
diff --git a/src/cmd/upas/smtp/greylist.c b/src/cmd/upas/smtp/greylist.c
new file mode 100644
index 00000000..915e688e
--- /dev/null
+++ b/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");
+ }
+}
diff --git a/src/cmd/upas/smtp/mkfile b/src/cmd/upas/smtp/mkfile
new file mode 100644
index 00000000..722f1357
--- /dev/null
+++ b/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
+ }
diff --git a/src/cmd/upas/smtp/mxdial.c b/src/cmd/upas/smtp/mxdial.c
new file mode 100644
index 00000000..aea5f256
--- /dev/null
+++ b/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);
+
+}
diff --git a/src/cmd/upas/smtp/rfc822.tab.c b/src/cmd/upas/smtp/rfc822.tab.c
new file mode 100644
index 00000000..1f12e48e
--- /dev/null
+++ b/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 */
+}
diff --git a/src/cmd/upas/smtp/rfc822.tab.h b/src/cmd/upas/smtp/rfc822.tab.h
new file mode 100644
index 00000000..cddbbc0f
--- /dev/null
+++ b/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;
+
+
+
diff --git a/src/cmd/upas/smtp/rfc822.y b/src/cmd/upas/smtp/rfc822.y
new file mode 100644
index 00000000..be77d2bd
--- /dev/null
+++ b/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;
+}
diff --git a/src/cmd/upas/smtp/rmtdns.c b/src/cmd/upas/smtp/rmtdns.c
new file mode 100644
index 00000000..54b679bb
--- /dev/null
+++ b/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]));}
+
+*/
diff --git a/src/cmd/upas/smtp/smtp.c b/src/cmd/upas/smtp/smtp.c
new file mode 100644
index 00000000..e88154f7
--- /dev/null
+++ b/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;
+
+}
diff --git a/src/cmd/upas/smtp/smtp.h b/src/cmd/upas/smtp/smtp.h
new file mode 100644
index 00000000..83619223
--- /dev/null
+++ b/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*);
diff --git a/src/cmd/upas/smtp/smtpd.c b/src/cmd/upas/smtp/smtpd.c
new file mode 100644
index 00000000..7042c37c
--- /dev/null
+++ b/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);
+}
diff --git a/src/cmd/upas/smtp/smtpd.h b/src/cmd/upas/smtp/smtpd.h
new file mode 100644
index 00000000..3a8e60e2
--- /dev/null
+++ b/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);
diff --git a/src/cmd/upas/smtp/smtpd.y b/src/cmd/upas/smtp/smtpd.y
new file mode 100644
index 00000000..57b89a02
--- /dev/null
+++ b/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;
+}
diff --git a/src/cmd/upas/smtp/spam.c b/src/cmd/upas/smtp/spam.c
new file mode 100644
index 00000000..84a8fccc
--- /dev/null
+++ b/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;
+}
diff --git a/src/cmd/upas/smtp/y.tab.h b/src/cmd/upas/smtp/y.tab.h
new file mode 100644
index 00000000..a31a0e67
--- /dev/null
+++ b/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
diff --git a/src/cmd/upas/unesc/mkfile b/src/cmd/upas/unesc/mkfile
new file mode 100644
index 00000000..612a5965
--- /dev/null
+++ b/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
diff --git a/src/cmd/upas/unesc/unesc.c b/src/cmd/upas/unesc/unesc.c
new file mode 100644
index 00000000..f3b4d652
--- /dev/null
+++ b/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);
+}
diff --git a/src/cmd/upas/vf/mkfile b/src/cmd/upas/vf/mkfile
new file mode 100644
index 00000000..b17e4578
--- /dev/null
+++ b/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
diff --git a/src/cmd/upas/vf/vf.c b/src/cmd/upas/vf/vf.c
new file mode 100644
index 00000000..db4f5ce8
--- /dev/null
+++ b/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;
+}