tThanks to John Cummings. - plan9port - [fork] Plan 9 from user space
 (HTM) git clone git://src.adamsgaard.dk/plan9port
 (DIR) Log
 (DIR) Files
 (DIR) Refs
 (DIR) README
 (DIR) LICENSE
       ---
 (DIR) commit 5cdb17983ae6e6367ad7a940cb219eab247a9304
 (DIR) parent cd3745196389579fb78b9b01ef1daefb5a57aa71
 (HTM) Author: rsc <devnull@localhost>
       Date:   Sat, 29 Oct 2005 16:26:44 +0000
       
       Thanks to John Cummings.
       
       Diffstat:
         A src/cmd/upas/common/libsys.c        |    1001 +++++++++++++++++++++++++++++++
         A src/cmd/upas/common/mail.c          |      57 +++++++++++++++++++++++++++++++
         A src/cmd/upas/common/makefile        |      18 ++++++++++++++++++
         A src/cmd/upas/common/mkfile          |      20 ++++++++++++++++++++
         A src/cmd/upas/common/process.c       |     175 +++++++++++++++++++++++++++++++
         A src/cmd/upas/common/sys.h           |      85 +++++++++++++++++++++++++++++++
         A src/cmd/upas/filterkit/dat.h        |       8 ++++++++
         A src/cmd/upas/filterkit/deliver.c    |      60 +++++++++++++++++++++++++++++++
         A src/cmd/upas/filterkit/list.c       |     315 +++++++++++++++++++++++++++++++
         A src/cmd/upas/filterkit/mkfile       |      21 +++++++++++++++++++++
         A src/cmd/upas/filterkit/pipefrom.sa… |      24 ++++++++++++++++++++++++
         A src/cmd/upas/filterkit/pipeto.samp… |      73 +++++++++++++++++++++++++++++++
         A src/cmd/upas/filterkit/pipeto.samp… |      43 ++++++++++++++++++++++++++++++
         A src/cmd/upas/filterkit/readaddrs.c  |      98 +++++++++++++++++++++++++++++++
         A src/cmd/upas/filterkit/token.c      |      89 +++++++++++++++++++++++++++++++
         A src/cmd/upas/fs/dat.h               |     221 +++++++++++++++++++++++++++++++
         A src/cmd/upas/fs/fs.c                |    1704 +++++++++++++++++++++++++++++++
         A src/cmd/upas/fs/imap4.c             |     876 +++++++++++++++++++++++++++++++
         A src/cmd/upas/fs/mbox.c              |    1601 +++++++++++++++++++++++++++++++
         A src/cmd/upas/fs/mkfile              |      29 +++++++++++++++++++++++++++++
         A src/cmd/upas/fs/mkfile.9            |      27 +++++++++++++++++++++++++++
         A src/cmd/upas/fs/plan9.c             |     405 +++++++++++++++++++++++++++++++
         A src/cmd/upas/fs/pop3.c              |     700 +++++++++++++++++++++++++++++++
         A src/cmd/upas/fs/readdir.c           |      15 +++++++++++++++
         A src/cmd/upas/fs/strtotm.c           |     113 +++++++++++++++++++++++++++++++
         A src/cmd/upas/fs/tester.c            |      81 ++++++++++++++++++++++++++++++
         A src/cmd/upas/marshal/marshal.c      |    1857 ++++++++++++++++++++++++++++++
         A src/cmd/upas/marshal/mkfile         |      20 ++++++++++++++++++++
         A src/cmd/upas/misc/gone.fishing      |       9 +++++++++
         A src/cmd/upas/misc/gone.msg          |       4 ++++
         A src/cmd/upas/misc/mail.c            |      51 +++++++++++++++++++++++++++++++
         A src/cmd/upas/misc/mail.rc           |      12 ++++++++++++
         A src/cmd/upas/misc/mail.sh           |      12 ++++++++++++
         A src/cmd/upas/misc/makefile          |      44 +++++++++++++++++++++++++++++++
         A src/cmd/upas/misc/mkfile            |      39 +++++++++++++++++++++++++++++++
         A src/cmd/upas/misc/namefiles         |       2 ++
         A src/cmd/upas/misc/omail.rc          |      14 ++++++++++++++
         A src/cmd/upas/misc/qmail             |       6 ++++++
         A src/cmd/upas/misc/remotemail        |       7 +++++++
         A src/cmd/upas/misc/rewrite           |      20 ++++++++++++++++++++
         A src/cmd/upas/ml/common.c            |     197 +++++++++++++++++++++++++++++++
         A src/cmd/upas/ml/dat.h               |      25 +++++++++++++++++++++++++
         A src/cmd/upas/ml/mkfile              |      40 +++++++++++++++++++++++++++++++
         A src/cmd/upas/ml/ml.c                |     167 +++++++++++++++++++++++++++++++
         A src/cmd/upas/ml/mlmgr.c             |     110 +++++++++++++++++++++++++++++++
         A src/cmd/upas/ml/mlowner.c           |      64 +++++++++++++++++++++++++++++++
         A src/cmd/upas/ned/mkfile             |      20 ++++++++++++++++++++
         A src/cmd/upas/ned/nedmail.c          |    2586 +++++++++++++++++++++++++++++++
         A src/cmd/upas/pop3/mkfile            |      16 ++++++++++++++++
         A src/cmd/upas/pop3/pop3.c            |     804 ++++++++++++++++++++++++++++++
         A src/cmd/upas/q/mkfile               |      22 ++++++++++++++++++++++
         A src/cmd/upas/q/qer.c                |     193 +++++++++++++++++++++++++++++++
         A src/cmd/upas/q/runq.c               |     766 +++++++++++++++++++++++++++++++
         A src/cmd/upas/scanmail/common.c      |     667 +++++++++++++++++++++++++++++++
         A src/cmd/upas/scanmail/mkfile        |      24 ++++++++++++++++++++++++
         A src/cmd/upas/scanmail/scanmail.c    |     476 +++++++++++++++++++++++++++++++
         A src/cmd/upas/scanmail/spam.h        |      62 +++++++++++++++++++++++++++++++
         A src/cmd/upas/scanmail/testscan.c    |     212 ++++++++++++++++++++++++++++++
         A src/cmd/upas/send/authorize.c       |      29 +++++++++++++++++++++++++++++
         A src/cmd/upas/send/bind.c            |     133 +++++++++++++++++++++++++++++++
         A src/cmd/upas/send/cat_mail.c        |      60 +++++++++++++++++++++++++++++++
         A src/cmd/upas/send/dest.c            |     260 +++++++++++++++++++++++++++++++
         A src/cmd/upas/send/filter.c          |     128 +++++++++++++++++++++++++++++++
         A src/cmd/upas/send/gateway.c         |      24 ++++++++++++++++++++++++
         A src/cmd/upas/send/local.c           |     129 +++++++++++++++++++++++++++++++
         A src/cmd/upas/send/log.c             |      85 +++++++++++++++++++++++++++++++
         A src/cmd/upas/send/main.c            |     575 +++++++++++++++++++++++++++++++
         A src/cmd/upas/send/makefile          |      46 +++++++++++++++++++++++++++++++
         A src/cmd/upas/send/message.c         |     573 +++++++++++++++++++++++++++++++
         A src/cmd/upas/send/mkfile            |      52 +++++++++++++++++++++++++++++++
         A src/cmd/upas/send/regtest.c         |      36 +++++++++++++++++++++++++++++++
         A src/cmd/upas/send/rewrite.c         |     315 +++++++++++++++++++++++++++++++
         A src/cmd/upas/send/send.h            |     108 +++++++++++++++++++++++++++++++
         A src/cmd/upas/send/skipequiv.c       |      93 +++++++++++++++++++++++++++++++
         A src/cmd/upas/send/translate.c       |      43 ++++++++++++++++++++++++++++++
         A src/cmd/upas/send/tryit             |      29 +++++++++++++++++++++++++++++
         A src/cmd/upas/smtp/greylist.c        |     274 +++++++++++++++++++++++++++++++
         A src/cmd/upas/smtp/mkfile            |      54 +++++++++++++++++++++++++++++++
         A src/cmd/upas/smtp/mxdial.c          |     333 +++++++++++++++++++++++++++++++
         A src/cmd/upas/smtp/rfc822.tab.c      |    1260 +++++++++++++++++++++++++++++++
         A src/cmd/upas/smtp/rfc822.tab.h      |      98 +++++++++++++++++++++++++++++++
         A src/cmd/upas/smtp/rfc822.y          |     778 +++++++++++++++++++++++++++++++
         A src/cmd/upas/smtp/rmtdns.c          |      58 ++++++++++++++++++++++++++++++
         A src/cmd/upas/smtp/smtp.c            |    1122 +++++++++++++++++++++++++++++++
         A src/cmd/upas/smtp/smtp.h            |      61 +++++++++++++++++++++++++++++++
         A src/cmd/upas/smtp/smtpd.c           |    1494 ++++++++++++++++++++++++++++++
         A src/cmd/upas/smtp/smtpd.h           |      68 +++++++++++++++++++++++++++++++
         A src/cmd/upas/smtp/smtpd.y           |     317 +++++++++++++++++++++++++++++++
         A src/cmd/upas/smtp/spam.c            |     591 +++++++++++++++++++++++++++++++
         A src/cmd/upas/smtp/y.tab.h           |      25 +++++++++++++++++++++++++
         A src/cmd/upas/unesc/mkfile           |      17 +++++++++++++++++
         A src/cmd/upas/unesc/unesc.c          |      48 +++++++++++++++++++++++++++++++
         A src/cmd/upas/vf/mkfile              |      20 ++++++++++++++++++++
         A src/cmd/upas/vf/vf.c                |    1110 +++++++++++++++++++++++++++++++
       
       94 files changed, 26853 insertions(+), 0 deletions(-)
       ---
 (DIR) diff --git a/src/cmd/upas/common/libsys.c b/src/cmd/upas/common/libsys.c
       t@@ -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;
       +}
 (DIR) diff --git a/src/cmd/upas/common/mail.c b/src/cmd/upas/common/mail.c
       t@@ -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;
       +}
 (DIR) diff --git a/src/cmd/upas/common/makefile b/src/cmd/upas/common/makefile
       t@@ -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
       +
 (DIR) diff --git a/src/cmd/upas/common/mkfile b/src/cmd/upas/common/mkfile
       t@@ -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
 (DIR) diff --git a/src/cmd/upas/common/process.c b/src/cmd/upas/common/process.c
       t@@ -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);
       +}
 (DIR) diff --git a/src/cmd/upas/common/sys.h b/src/cmd/upas/common/sys.h
       t@@ -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
 (DIR) diff --git a/src/cmd/upas/filterkit/dat.h b/src/cmd/upas/filterkit/dat.h
       t@@ -0,0 +1,8 @@
       +typedef struct Addr Addr;
       +struct Addr
       +{
       +        Addr *next;
       +        char *val;
       +};
       +
       +extern Addr* readaddrs(char*, Addr*);
 (DIR) diff --git a/src/cmd/upas/filterkit/deliver.c b/src/cmd/upas/filterkit/deliver.c
       t@@ -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);
       +}
 (DIR) diff --git a/src/cmd/upas/filterkit/list.c b/src/cmd/upas/filterkit/list.c
       t@@ -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();
       +}
 (DIR) diff --git a/src/cmd/upas/filterkit/mkfile b/src/cmd/upas/filterkit/mkfile
       t@@ -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
       +
 (DIR) diff --git a/src/cmd/upas/filterkit/pipefrom.sample b/src/cmd/upas/filterkit/pipefrom.sample
       t@@ -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
 (DIR) diff --git a/src/cmd/upas/filterkit/pipeto.sample b/src/cmd/upas/filterkit/pipeto.sample
       t@@ -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
 (DIR) diff --git a/src/cmd/upas/filterkit/pipeto.sample-hold b/src/cmd/upas/filterkit/pipeto.sample-hold
       t@@ -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
 (DIR) diff --git a/src/cmd/upas/filterkit/readaddrs.c b/src/cmd/upas/filterkit/readaddrs.c
       t@@ -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;
       +}
 (DIR) diff --git a/src/cmd/upas/filterkit/token.c b/src/cmd/upas/filterkit/token.c
       t@@ -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);
       +}
 (DIR) diff --git a/src/cmd/upas/fs/dat.h b/src/cmd/upas/fs/dat.h
       t@@ -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;
       +
 (DIR) diff --git a/src/cmd/upas/fs/fs.c b/src/cmd/upas/fs/fs.c
       t@@ -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);
       +}
 (DIR) diff --git a/src/cmd/upas/fs/imap4.c b/src/cmd/upas/fs/imap4.c
       t@@ -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, '"');
       +}
 (DIR) diff --git a/src/cmd/upas/fs/mbox.c b/src/cmd/upas/fs/mbox.c
       t@@ -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);
       +}
       +
 (DIR) diff --git a/src/cmd/upas/fs/mkfile b/src/cmd/upas/fs/mkfile
       t@@ -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
 (DIR) diff --git a/src/cmd/upas/fs/mkfile.9 b/src/cmd/upas/fs/mkfile.9
       t@@ -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
 (DIR) diff --git a/src/cmd/upas/fs/plan9.c b/src/cmd/upas/fs/plan9.c
       t@@ -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;
       +}
 (DIR) diff --git a/src/cmd/upas/fs/pop3.c b/src/cmd/upas/fs/pop3.c
       t@@ -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;
       +}
       +
 (DIR) diff --git a/src/cmd/upas/fs/readdir.c b/src/cmd/upas/fs/readdir.c
       t@@ -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);
       +}
 (DIR) diff --git a/src/cmd/upas/fs/strtotm.c b/src/cmd/upas/fs/strtotm.c
       t@@ -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;
       +}
 (DIR) diff --git a/src/cmd/upas/fs/tester.c b/src/cmd/upas/fs/tester.c
       t@@ -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);
       +}
 (DIR) diff --git a/src/cmd/upas/marshal/marshal.c b/src/cmd/upas/marshal/marshal.c
       t@@ -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, '"');
       +}
 (DIR) diff --git a/src/cmd/upas/marshal/mkfile b/src/cmd/upas/marshal/mkfile
       t@@ -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
       +
 (DIR) diff --git a/src/cmd/upas/misc/gone.fishing b/src/cmd/upas/misc/gone.fishing
       t@@ -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
       +}
 (DIR) diff --git a/src/cmd/upas/misc/gone.msg b/src/cmd/upas/misc/gone.msg
       t@@ -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.
 (DIR) diff --git a/src/cmd/upas/misc/mail.c b/src/cmd/upas/misc/mail.c
       t@@ -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);
       +}
       +
 (DIR) diff --git a/src/cmd/upas/misc/mail.rc b/src/cmd/upas/misc/mail.rc
       t@@ -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 $*
       +}
 (DIR) diff --git a/src/cmd/upas/misc/mail.sh b/src/cmd/upas/misc/mail.sh
       t@@ -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
 (DIR) diff --git a/src/cmd/upas/misc/makefile b/src/cmd/upas/misc/makefile
       t@@ -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
       +
 (DIR) diff --git a/src/cmd/upas/misc/mkfile b/src/cmd/upas/misc/mkfile
       t@@ -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
 (DIR) diff --git a/src/cmd/upas/misc/namefiles b/src/cmd/upas/misc/namefiles
       t@@ -0,0 +1,2 @@
       +names.local
       +names.global
 (DIR) diff --git a/src/cmd/upas/misc/omail.rc b/src/cmd/upas/misc/omail.rc
       t@@ -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 $*
       +}
 (DIR) diff --git a/src/cmd/upas/misc/qmail b/src/cmd/upas/misc/qmail
       t@@ -0,0 +1,6 @@
       +#!/bin/rc
       +sender=$1
       +shift
       +addr=$1
       +shift
       +qer /mail/queue mail $sender $addr $* && runq /mail/queue /mail/lib/remotemail
 (DIR) diff --git a/src/cmd/upas/misc/remotemail b/src/cmd/upas/misc/remotemail
       t@@ -0,0 +1,7 @@
       +#!/bin/rc
       +shift
       +sender=$1
       +shift
       +addr=$1
       +shift
       +/bin/upas/smtp -g research.research.bell-labs.com $addr $sender $*
 (DIR) diff --git a/src/cmd/upas/misc/rewrite b/src/cmd/upas/misc/rewrite
       t@@ -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'"
 (DIR) diff --git a/src/cmd/upas/ml/common.c b/src/cmd/upas/ml/common.c
       t@@ -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;
       +        }
       +}
 (DIR) diff --git a/src/cmd/upas/ml/dat.h b/src/cmd/upas/ml/dat.h
       t@@ -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);
 (DIR) diff --git a/src/cmd/upas/ml/mkfile b/src/cmd/upas/ml/mkfile
       t@@ -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
       +        }
 (DIR) diff --git a/src/cmd/upas/ml/ml.c b/src/cmd/upas/ml/ml.c
       t@@ -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);
       +}
 (DIR) diff --git a/src/cmd/upas/ml/mlmgr.c b/src/cmd/upas/ml/mlmgr.c
       t@@ -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;
       +}
 (DIR) diff --git a/src/cmd/upas/ml/mlowner.c b/src/cmd/upas/ml/mlowner.c
       t@@ -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);
       +}
 (DIR) diff --git a/src/cmd/upas/ned/mkfile b/src/cmd/upas/ned/mkfile
       t@@ -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
       +
 (DIR) diff --git a/src/cmd/upas/ned/nedmail.c b/src/cmd/upas/ned/nedmail.c
       t@@ -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);
       +}
 (DIR) diff --git a/src/cmd/upas/pop3/mkfile b/src/cmd/upas/pop3/mkfile
       t@@ -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
 (DIR) diff --git a/src/cmd/upas/pop3/pop3.c b/src/cmd/upas/pop3/pop3.c
       t@@ -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);
       +}
       +
 (DIR) diff --git a/src/cmd/upas/q/mkfile b/src/cmd/upas/q/mkfile
       t@@ -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
 (DIR) diff --git a/src/cmd/upas/q/qer.c b/src/cmd/upas/q/qer.c
       t@@ -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;
       +}
 (DIR) diff --git a/src/cmd/upas/q/runq.c b/src/cmd/upas/q/runq.c
       t@@ -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);
       +}
 (DIR) diff --git a/src/cmd/upas/scanmail/common.c b/src/cmd/upas/scanmail/common.c
       t@@ -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,
       +};
 (DIR) diff --git a/src/cmd/upas/scanmail/mkfile b/src/cmd/upas/scanmail/mkfile
       t@@ -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
 (DIR) diff --git a/src/cmd/upas/scanmail/scanmail.c b/src/cmd/upas/scanmail/scanmail.c
       t@@ -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;
       +}
 (DIR) diff --git a/src/cmd/upas/scanmail/spam.h b/src/cmd/upas/scanmail/spam.h
       t@@ -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*);
 (DIR) diff --git a/src/cmd/upas/scanmail/testscan.c b/src/cmd/upas/scanmail/testscan.c
       t@@ -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;
       +}
 (DIR) diff --git a/src/cmd/upas/send/authorize.c b/src/cmd/upas/send/authorize.c
       t@@ -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);
       +}
 (DIR) diff --git a/src/cmd/upas/send/bind.c b/src/cmd/upas/send/bind.c
       t@@ -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;
       +}
       +
 (DIR) diff --git a/src/cmd/upas/send/cat_mail.c b/src/cmd/upas/send/cat_mail.c
       t@@ -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;
       +}
 (DIR) diff --git a/src/cmd/upas/send/dest.c b/src/cmd/upas/send/dest.c
       t@@ -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;
       +}
 (DIR) diff --git a/src/cmd/upas/send/filter.c b/src/cmd/upas/send/filter.c
       t@@ -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;
       +}
 (DIR) diff --git a/src/cmd/upas/send/gateway.c b/src/cmd/upas/send/gateway.c
       t@@ -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);
       +        }
       +}
 (DIR) diff --git a/src/cmd/upas/send/local.c b/src/cmd/upas/send/local.c
       t@@ -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;
       +}
 (DIR) diff --git a/src/cmd/upas/send/log.c b/src/cmd/upas/send/log.c
       t@@ -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);
       +}
 (DIR) diff --git a/src/cmd/upas/send/main.c b/src/cmd/upas/send/main.c
       t@@ -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;
       +}
 (DIR) diff --git a/src/cmd/upas/send/makefile b/src/cmd/upas/send/makefile
       t@@ -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
 (DIR) diff --git a/src/cmd/upas/send/message.c b/src/cmd/upas/send/message.c
       t@@ -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);
       +}
 (DIR) diff --git a/src/cmd/upas/send/mkfile b/src/cmd/upas/send/mkfile
       t@@ -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
       +#        }
 (DIR) diff --git a/src/cmd/upas/send/regtest.c b/src/cmd/upas/send/regtest.c
       t@@ -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> ");
       +        }
       +}
 (DIR) diff --git a/src/cmd/upas/send/rewrite.c b/src/cmd/upas/send/rewrite.c
       t@@ -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:"...");
       +        }
       +}
       +
 (DIR) diff --git a/src/cmd/upas/send/send.h b/src/cmd/upas/send/send.h
       t@@ -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);
 (DIR) diff --git a/src/cmd/upas/send/skipequiv.c b/src/cmd/upas/send/skipequiv.c
       t@@ -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;
       +}
 (DIR) diff --git a/src/cmd/upas/send/translate.c b/src/cmd/upas/send/translate.c
       t@@ -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;
       +}
 (DIR) diff --git a/src/cmd/upas/send/tryit b/src/cmd/upas/send/tryit
       t@@ -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
 (DIR) diff --git a/src/cmd/upas/smtp/greylist.c b/src/cmd/upas/smtp/greylist.c
       t@@ -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");
       +        }
       +}
 (DIR) diff --git a/src/cmd/upas/smtp/mkfile b/src/cmd/upas/smtp/mkfile
       t@@ -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
       +        }
 (DIR) diff --git a/src/cmd/upas/smtp/mxdial.c b/src/cmd/upas/smtp/mxdial.c
       t@@ -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);
       +
       +}
 (DIR) diff --git a/src/cmd/upas/smtp/rfc822.tab.c b/src/cmd/upas/smtp/rfc822.tab.c
       t@@ -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 */
       +}
 (DIR) diff --git a/src/cmd/upas/smtp/rfc822.tab.h b/src/cmd/upas/smtp/rfc822.tab.h
       t@@ -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;
       +
       +
       +
 (DIR) diff --git a/src/cmd/upas/smtp/rfc822.y b/src/cmd/upas/smtp/rfc822.y
       t@@ -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;
       +}
 (DIR) diff --git a/src/cmd/upas/smtp/rmtdns.c b/src/cmd/upas/smtp/rmtdns.c
       t@@ -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]));}
       +
       +*/
 (DIR) diff --git a/src/cmd/upas/smtp/smtp.c b/src/cmd/upas/smtp/smtp.c
       t@@ -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;
       +
       +}
 (DIR) diff --git a/src/cmd/upas/smtp/smtp.h b/src/cmd/upas/smtp/smtp.h
       t@@ -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*);
 (DIR) diff --git a/src/cmd/upas/smtp/smtpd.c b/src/cmd/upas/smtp/smtpd.c
       t@@ -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);
       +}
 (DIR) diff --git a/src/cmd/upas/smtp/smtpd.h b/src/cmd/upas/smtp/smtpd.h
       t@@ -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);
 (DIR) diff --git a/src/cmd/upas/smtp/smtpd.y b/src/cmd/upas/smtp/smtpd.y
       t@@ -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;
       +}
 (DIR) diff --git a/src/cmd/upas/smtp/spam.c b/src/cmd/upas/smtp/spam.c
       t@@ -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;
       +}
 (DIR) diff --git a/src/cmd/upas/smtp/y.tab.h b/src/cmd/upas/smtp/y.tab.h
       t@@ -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
 (DIR) diff --git a/src/cmd/upas/unesc/mkfile b/src/cmd/upas/unesc/mkfile
       t@@ -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
 (DIR) diff --git a/src/cmd/upas/unesc/unesc.c b/src/cmd/upas/unesc/unesc.c
       t@@ -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);
       +}
 (DIR) diff --git a/src/cmd/upas/vf/mkfile b/src/cmd/upas/vf/mkfile
       t@@ -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
 (DIR) diff --git a/src/cmd/upas/vf/vf.c b/src/cmd/upas/vf/vf.c
       t@@ -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;
       +}