aboutsummaryrefslogtreecommitdiff
path: root/src/cmd/upas/smtp
diff options
context:
space:
mode:
Diffstat (limited to 'src/cmd/upas/smtp')
-rw-r--r--src/cmd/upas/smtp/greylist.c274
-rw-r--r--src/cmd/upas/smtp/mkfile54
-rw-r--r--src/cmd/upas/smtp/mxdial.c333
-rw-r--r--src/cmd/upas/smtp/rfc822.tab.c1260
-rw-r--r--src/cmd/upas/smtp/rfc822.tab.h98
-rw-r--r--src/cmd/upas/smtp/rfc822.y778
-rw-r--r--src/cmd/upas/smtp/rmtdns.c58
-rw-r--r--src/cmd/upas/smtp/smtp.c1122
-rw-r--r--src/cmd/upas/smtp/smtp.h61
-rw-r--r--src/cmd/upas/smtp/smtpd.c1494
-rw-r--r--src/cmd/upas/smtp/smtpd.h68
-rw-r--r--src/cmd/upas/smtp/smtpd.y317
-rw-r--r--src/cmd/upas/smtp/spam.c591
-rw-r--r--src/cmd/upas/smtp/y.tab.h25
14 files changed, 6533 insertions, 0 deletions
diff --git a/src/cmd/upas/smtp/greylist.c b/src/cmd/upas/smtp/greylist.c
new file mode 100644
index 00000000..915e688e
--- /dev/null
+++ b/src/cmd/upas/smtp/greylist.c
@@ -0,0 +1,274 @@
+#include "common.h"
+#include "smtpd.h"
+#include "smtp.h"
+#include <ctype.h>
+#include <ip.h>
+#include <ndb.h>
+
+typedef struct {
+ int existed; /* these two are distinct to cope with errors */
+ int created;
+ int noperm;
+ long mtime; /* mod time, iff it already existed */
+} Greysts;
+
+/*
+ * There's a bit of a problem with yahoo; they apparently have a vast
+ * pool of machines that all run the same queue(s), so a 451 retry can
+ * come from a different IP address for many, many retries, and it can
+ * take ~5 hours for the same IP to call us back. Various other goofballs,
+ * notably the IEEE, try to send mail just before 9 AM, then refuse to try
+ * again until after 5 PM. Doh!
+ */
+enum {
+ Nonspammax = 14*60*60, /* must call back within this time if real */
+};
+static char whitelist[] = "/mail/lib/whitelist";
+
+/*
+ * matches ip addresses or subnets in whitelist against nci->rsys.
+ * ignores comments and blank lines in /mail/lib/whitelist.
+ */
+static int
+onwhitelist(void)
+{
+ int lnlen;
+ char *line, *parse;
+ char input[128];
+ uchar ip[IPaddrlen], ipmasked[IPaddrlen];
+ uchar mask4[IPaddrlen], addr4[IPaddrlen];
+ uchar mask[IPaddrlen], addr[IPaddrlen], addrmasked[IPaddrlen];
+ Biobuf *wl;
+ static int beenhere;
+ static allzero[IPaddrlen];
+
+ if (!beenhere) {
+ beenhere = 1;
+ fmtinstall('I', eipfmt);
+ }
+
+ parseip(ip, nci->rsys);
+ wl = Bopen(whitelist, OREAD);
+ if (wl == nil)
+ return 1;
+ while ((line = Brdline(wl, '\n')) != nil) {
+ if (line[0] == '#' || line[0] == '\n')
+ continue;
+ lnlen = Blinelen(wl);
+ line[lnlen-1] = '\0'; /* clobber newline */
+
+ /* default mask is /32 (v4) or /128 (v6) for bare IP */
+ parse = line;
+ if (strchr(line, '/') == nil) {
+ strncpy(input, line, sizeof input - 5);
+ if (strchr(line, '.') != nil)
+ strcat(input, "/32");
+ else
+ strcat(input, "/128");
+ parse = input;
+ }
+ /* sorry, dave; where's parsecidr for v4 or v6? */
+ v4parsecidr(addr4, mask4, parse);
+ v4tov6(addr, addr4);
+ v4tov6(mask, mask4);
+
+ maskip(addr, mask, addrmasked);
+ maskip(ip, mask, ipmasked);
+ if (memcmp(ipmasked, addrmasked, IPaddrlen) == 0)
+ break;
+ }
+ Bterm(wl);
+ return line != nil;
+}
+
+static int mkdirs(char *);
+
+/*
+ * if any directories leading up to path don't exist, create them.
+ * modifies but restores path.
+ */
+static int
+mkpdirs(char *path)
+{
+ int rv = 0;
+ char *sl = strrchr(path, '/');
+
+ if (sl != nil) {
+ *sl = '\0';
+ rv = mkdirs(path);
+ *sl = '/';
+ }
+ return rv;
+}
+
+/*
+ * if path or any directories leading up to it don't exist, create them.
+ * modifies but restores path.
+ */
+static int
+mkdirs(char *path)
+{
+ int fd;
+
+ if (access(path, AEXIST) >= 0)
+ return 0;
+
+ /* make presumed-missing intermediate directories */
+ if (mkpdirs(path) < 0)
+ return -1;
+
+ /* make final directory */
+ fd = create(path, OREAD, 0777|DMDIR);
+ if (fd < 0)
+ /*
+ * we may have lost a race; if the directory now exists,
+ * it's okay.
+ */
+ return access(path, AEXIST) < 0? -1: 0;
+ close(fd);
+ return 0;
+}
+
+static long
+getmtime(char *file)
+{
+ long mtime = -1;
+ Dir *ds = dirstat(file);
+
+ if (ds != nil) {
+ mtime = ds->mtime;
+ free(ds);
+ }
+ return mtime;
+}
+
+static void
+tryaddgrey(char *file, Greysts *gsp)
+{
+ int fd = create(file, OWRITE|OEXCL, 0444|DMEXCL);
+
+ gsp->created = (fd >= 0);
+ if (fd >= 0) {
+ close(fd);
+ gsp->existed = 0; /* just created; couldn't have existed */
+ } else {
+ /*
+ * why couldn't we create file? it must have existed
+ * (or we were denied perm on parent dir.).
+ * if it existed, fill in gsp->mtime; otherwise
+ * make presumed-missing intermediate directories.
+ */
+ gsp->existed = access(file, AEXIST) >= 0;
+ if (gsp->existed)
+ gsp->mtime = getmtime(file);
+ else if (mkpdirs(file) < 0)
+ gsp->noperm = 1;
+ }
+}
+
+static void
+addgreylist(char *file, Greysts *gsp)
+{
+ tryaddgrey(file, gsp);
+ if (!gsp->created && !gsp->existed && !gsp->noperm)
+ /* retry the greylist entry with parent dirs created */
+ tryaddgrey(file, gsp);
+}
+
+static int
+recentcall(Greysts *gsp)
+{
+ long delay = time(0) - gsp->mtime;
+
+ if (!gsp->existed)
+ return 0;
+ /* reject immediate call-back; spammers are doing that now */
+ return delay >= 30 && delay <= Nonspammax;
+}
+
+/*
+ * policy: if (caller-IP, my-IP, rcpt) is not on the greylist,
+ * reject this message as "451 temporary failure". if the caller is real,
+ * he'll retry soon, otherwise he's a spammer.
+ * at the first rejection, create a greylist entry for (my-ip, caller-ip,
+ * rcpt, time), where time is the file's mtime. if they call back and there's
+ * already a greylist entry, and it's within the allowed interval,
+ * add their IP to the append-only whitelist.
+ *
+ * greylist files can be removed at will; at worst they'll cause a few
+ * extra retries.
+ */
+
+static int
+isrcptrecent(char *rcpt)
+{
+ char *user;
+ char file[256];
+ Greysts gs;
+ Greysts *gsp = &gs;
+
+ if (rcpt[0] == '\0' || strchr(rcpt, '/') != nil ||
+ strcmp(rcpt, ".") == 0 || strcmp(rcpt, "..") == 0)
+ return 0;
+
+ /* shorten names to fit pre-fossil or pre-9p2000 file servers */
+ user = strrchr(rcpt, '!');
+ if (user == nil)
+ user = rcpt;
+ else
+ user++;
+
+ /* check & try to update the grey list entry */
+ snprint(file, sizeof file, "/mail/grey/%s/%s/%s",
+ nci->lsys, nci->rsys, user);
+ memset(gsp, 0, sizeof *gsp);
+ addgreylist(file, gsp);
+
+ /* if on greylist already and prior call was recent, add to whitelist */
+ if (gsp->existed && recentcall(gsp)) {
+ syslog(0, "smtpd",
+ "%s/%s was grey; adding IP to white", nci->rsys, rcpt);
+ return 1;
+ } else if (gsp->existed)
+ syslog(0, "smtpd", "call for %s/%s was seconds ago or long ago",
+ nci->rsys, rcpt);
+ else
+ syslog(0, "smtpd", "no call registered for %s/%s; registering",
+ nci->rsys, rcpt);
+ return 0;
+}
+
+void
+vfysenderhostok(void)
+{
+ char *fqdn;
+ int recent = 0;
+ Link *l;
+
+ if (onwhitelist())
+ return;
+
+ for (l = rcvers.first; l; l = l->next)
+ if (isrcptrecent(s_to_c(l->p)))
+ recent = 1;
+
+ /* if on greylist already and prior call was recent, add to whitelist */
+ if (recent) {
+ int fd = create(whitelist, OWRITE, 0666|DMAPPEND);
+
+ if (fd >= 0) {
+ seek(fd, 0, 2); /* paranoia */
+ if ((fqdn = csgetvalue(nil, "ip", nci->rsys, "dom", nil)) != nil)
+ fprint(fd, "# %s\n%s\n\n", fqdn, nci->rsys);
+ else
+ fprint(fd, "# unknown\n%s\n\n", nci->rsys);
+ close(fd);
+ }
+ } else {
+ syslog(0, "smtpd",
+ "no recent call from %s for a rcpt; rejecting with temporary failure",
+ nci->rsys);
+ reply("451 please try again soon from the same IP.\r\n");
+ exits("no recent call for a rcpt");
+ }
+}
diff --git a/src/cmd/upas/smtp/mkfile b/src/cmd/upas/smtp/mkfile
new file mode 100644
index 00000000..722f1357
--- /dev/null
+++ b/src/cmd/upas/smtp/mkfile
@@ -0,0 +1,54 @@
+<$PLAN9/src/mkhdr
+
+TARG = # smtpd\
+ smtp\
+
+OFILES=
+
+LIB=../common/libcommon.a\
+ $PLAN9/lib/libthread.a # why do i have to explicitly put this?
+
+HFILES=../common/common.h\
+ ../common/sys.h\
+ smtpd.h\
+ smtp.h\
+
+BIN=$PLAN9/bin/upas
+UPDATE=\
+ greylist.c\
+ mkfile\
+ mxdial.c\
+ rfc822.y\
+ rmtdns.c\
+ smtpd.y\
+ spam.c\
+ $HFILES\
+ ${OFILES:%.$O=%.c}\
+ ${TARG:%=%.c}\
+
+<$PLAN9/src/mkmany
+CFLAGS=$CFLAGS -I../common -D'SPOOL="/mail"'
+
+$O.smtpd: smtpd.tab.$O rmtdns.$O spam.$O rfc822.tab.$O greylist.$O
+$O.smtp: rfc822.tab.$O mxdial.$O
+
+smtpd.$O: smtpd.h
+
+smtp.$O to.$O: smtp.h
+
+smtpd.tab.c: smtpd.y smtpd.h
+ yacc -o xxx smtpd.y
+ sed 's/yy/zz/g' < xxx > $target
+ rm xxx
+
+rfc822.tab.c: rfc822.y smtp.h
+ 9 yacc -d -o $target rfc822.y
+
+clean:V:
+ rm -f *.[$OS] [$OS].$TARG smtpd.tab.c rfc822.tab.c y.tab.? y.debug $TARG
+
+../common/libcommon.a$O:
+ @{
+ cd ../common
+ mk
+ }
diff --git a/src/cmd/upas/smtp/mxdial.c b/src/cmd/upas/smtp/mxdial.c
new file mode 100644
index 00000000..aea5f256
--- /dev/null
+++ b/src/cmd/upas/smtp/mxdial.c
@@ -0,0 +1,333 @@
+#include "common.h"
+#include <ndb.h>
+#include "smtp.h" /* to publish dial_string_parse */
+
+enum
+{
+ Nmx= 16,
+ Maxstring= 256,
+};
+
+typedef struct Mx Mx;
+struct Mx
+{
+ char host[256];
+ char ip[24];
+ int pref;
+};
+static Mx mx[Nmx];
+
+Ndb *db;
+extern int debug;
+
+static int mxlookup(DS*, char*);
+static int mxlookup1(DS*, char*);
+static int compar(void*, void*);
+static int callmx(DS*, char*, char*);
+static void expand_meta(DS *ds);
+extern int cistrcmp(char*, char*);
+
+int
+mxdial(char *addr, char *ddomain, char *gdomain)
+{
+ int fd;
+ DS ds;
+ char err[Errlen];
+
+ addr = netmkaddr(addr, 0, "smtp");
+ dial_string_parse(addr, &ds);
+
+ /* try connecting to destination or any of it's mail routers */
+ fd = callmx(&ds, addr, ddomain);
+
+ /* try our mail gateway */
+ rerrstr(err, sizeof(err));
+ if(fd < 0 && gdomain && strstr(err, "can't translate") != 0) {
+ fprint(2,"dialing %s\n",gdomain);
+ fd = dial(netmkaddr(gdomain, 0, "smtp"), 0, 0, 0);
+ }
+
+ return fd;
+}
+
+/*
+ * take an address and return all the mx entries for it,
+ * most preferred first
+ */
+static int
+callmx(DS *ds, char *dest, char *domain)
+{
+ int fd, i, nmx;
+ char addr[Maxstring];
+
+ /* get a list of mx entries */
+ nmx = mxlookup(ds, domain);
+ if(nmx < 0){
+ /* dns isn't working, don't just dial */
+ return -1;
+ }
+ if(nmx == 0){
+ if(debug)
+ fprint(2, "mxlookup returns nothing\n");
+ return dial(dest, 0, 0, 0);
+ }
+
+ /* refuse to honor loopback addresses given by dns */
+ for(i = 0; i < nmx; i++){
+ if(strcmp(mx[i].ip, "127.0.0.1") == 0){
+ if(debug)
+ fprint(2, "mxlookup returns loopback\n");
+ werrstr("illegal: domain lists 127.0.0.1 as mail server");
+ return -1;
+ }
+ }
+
+ /* sort by preference */
+ if(nmx > 1)
+ qsort(mx, nmx, sizeof(Mx), compar);
+
+ /* dial each one in turn */
+ for(i = 0; i < nmx; i++){
+ snprint(addr, sizeof(addr), "%s/%s!%s!%s", ds->netdir, ds->proto,
+ mx[i].host, ds->service);
+ if(debug)
+ fprint(2, "mxdial trying %s\n", addr);
+ fd = dial(addr, 0, 0, 0);
+ if(fd >= 0)
+ return fd;
+ }
+ return -1;
+}
+
+/*
+ * call the dns process and have it try to resolve the mx request
+ *
+ * this routine knows about the firewall and tries inside and outside
+ * dns's seperately.
+ */
+static int
+mxlookup(DS *ds, char *domain)
+{
+ int n;
+
+ /* just in case we find no domain name */
+ strcpy(domain, ds->host);
+
+ if(ds->netdir){
+ n = mxlookup1(ds, domain);
+ } else {
+ ds->netdir = "/net";
+ n = mxlookup1(ds, domain);
+ if(n == 0) {
+ ds->netdir = "/net.alt";
+ n = mxlookup1(ds, domain);
+ }
+ }
+
+ return n;
+}
+
+static int
+mxlookup1(DS *ds, char *domain)
+{
+ char buf[1024];
+ char dnsname[Maxstring];
+ char *fields[4];
+ int i, n, fd, nmx;
+
+ snprint(dnsname, sizeof dnsname, "%s/dns", ds->netdir);
+
+ fd = open(dnsname, ORDWR);
+ if(fd < 0)
+ return 0;
+
+ nmx = 0;
+ snprint(buf, sizeof(buf), "%s mx", ds->host);
+ if(debug)
+ fprint(2, "sending %s '%s'\n", dnsname, buf);
+ n = write(fd, buf, strlen(buf));
+ if(n < 0){
+ rerrstr(buf, sizeof buf);
+ if(debug)
+ fprint(2, "dns: %s\n", buf);
+ if(strstr(buf, "dns failure")){
+ /* if dns fails for the mx lookup, we have to stop */
+ close(fd);
+ return -1;
+ }
+ } else {
+ /*
+ * get any mx entries
+ */
+ seek(fd, 0, 0);
+ while(nmx < Nmx && (n = read(fd, buf, sizeof(buf)-1)) > 0){
+ buf[n] = 0;
+ if(debug)
+ fprint(2, "dns mx: %s\n", buf);
+ n = getfields(buf, fields, 4, 1, " \t");
+ if(n < 4)
+ continue;
+
+ if(strchr(domain, '.') == 0)
+ strcpy(domain, fields[0]);
+
+ strncpy(mx[nmx].host, fields[3], sizeof(mx[n].host)-1);
+ mx[nmx].pref = atoi(fields[2]);
+ nmx++;
+ }
+ if(debug)
+ fprint(2, "dns mx; got %d entries\n", nmx);
+ }
+
+ /*
+ * no mx record? try name itself.
+ */
+ /*
+ * BUG? If domain has no dots, then we used to look up ds->host
+ * but return domain instead of ds->host in the list. Now we return
+ * ds->host. What will this break?
+ */
+ if(nmx == 0){
+ mx[0].pref = 1;
+ strncpy(mx[0].host, ds->host, sizeof(mx[0].host));
+ nmx++;
+ }
+
+ /*
+ * look up all ip addresses
+ */
+ for(i = 0; i < nmx; i++){
+ seek(fd, 0, 0);
+ snprint(buf, sizeof buf, "%s ip", mx[i].host);
+ mx[i].ip[0] = 0;
+ if(write(fd, buf, strlen(buf)) < 0)
+ goto no;
+ seek(fd, 0, 0);
+ if((n = read(fd, buf, sizeof buf-1)) < 0)
+ goto no;
+ buf[n] = 0;
+ if(getfields(buf, fields, 4, 1, " \t") < 3)
+ goto no;
+ strncpy(mx[i].ip, fields[2], sizeof(mx[i].ip)-1);
+ continue;
+
+ no:
+ /* remove mx[i] and go around again */
+ nmx--;
+ mx[i] = mx[nmx];
+ i--;
+ }
+ return nmx;
+}
+
+static int
+compar(void *a, void *b)
+{
+ return ((Mx*)a)->pref - ((Mx*)b)->pref;
+}
+
+/* break up an address to its component parts */
+void
+dial_string_parse(char *str, DS *ds)
+{
+ char *p, *p2;
+
+ strncpy(ds->buf, str, sizeof(ds->buf));
+ ds->buf[sizeof(ds->buf)-1] = 0;
+
+ p = strchr(ds->buf, '!');
+ if(p == 0) {
+ ds->netdir = 0;
+ ds->proto = "net";
+ ds->host = ds->buf;
+ } else {
+ if(*ds->buf != '/'){
+ ds->netdir = 0;
+ ds->proto = ds->buf;
+ } else {
+ for(p2 = p; *p2 != '/'; p2--)
+ ;
+ *p2++ = 0;
+ ds->netdir = ds->buf;
+ ds->proto = p2;
+ }
+ *p = 0;
+ ds->host = p + 1;
+ }
+ ds->service = strchr(ds->host, '!');
+ if(ds->service)
+ *ds->service++ = 0;
+ if(*ds->host == '$')
+ expand_meta(ds);
+}
+
+#if 0 /* jpc */
+static void
+expand_meta(DS *ds)
+{
+ char buf[128], cs[128], *net, *p;
+ int fd, n;
+
+ net = ds->netdir;
+ if(!net)
+ net = "/net";
+
+ if(debug)
+ fprint(2, "expanding %s!%s\n", net, ds->host);
+ snprint(cs, sizeof(cs), "%s/cs", net);
+ if((fd = open(cs, ORDWR)) == -1){
+ if(debug)
+ fprint(2, "open %s: %r\n", cs);
+ syslog(0, "smtp", "cannot open %s: %r", cs);
+ return;
+ }
+
+ snprint(buf, sizeof(buf), "!ipinfo %s", ds->host+1); // +1 to skip $
+ if(write(fd, buf, strlen(buf)) <= 0){
+ if(debug)
+ fprint(2, "write %s: %r\n", cs);
+ syslog(0, "smtp", "%s to %s - write failed: %r", buf, cs);
+ close(fd);
+ return;
+ }
+
+ seek(fd, 0, 0);
+ if((n = read(fd, ds->expand, sizeof(ds->expand)-1)) < 0){
+ if(debug)
+ fprint(2, "read %s: %r\n", cs);
+ syslog(0, "smtp", "%s - read failed: %r", cs);
+ close(fd);
+ return;
+ }
+ close(fd);
+
+ ds->expand[n] = 0;
+ if((p = strchr(ds->expand, '=')) == nil){
+ if(debug)
+ fprint(2, "response %s: %s\n", cs, ds->expand);
+ syslog(0, "smtp", "%q from %s - bad response: %r", ds->expand, cs);
+ return;
+ }
+ ds->host = p+1;
+
+ /* take only first one returned (quasi-bug) */
+ if((p = strchr(ds->host, ' ')) != nil)
+ *p = 0;
+}
+#endif /* jpc */
+
+static void
+expand_meta(DS *ds)
+{
+ Ndb *db;
+ Ndbs s;
+ char *sys, *smtpserver;
+
+ sys = sysname();
+ db = ndbopen(unsharp("#9/ndb/local"));
+ fprint(2,"%s",ds->host);
+ smtpserver = ndbgetvalue(db, &s, "sys", sys, "smtp", nil);
+ snprint(ds->host,128,"%s",smtpserver);
+ fprint(2," exanded to %s\n",ds->host);
+
+}
diff --git a/src/cmd/upas/smtp/rfc822.tab.c b/src/cmd/upas/smtp/rfc822.tab.c
new file mode 100644
index 00000000..1f12e48e
--- /dev/null
+++ b/src/cmd/upas/smtp/rfc822.tab.c
@@ -0,0 +1,1260 @@
+
+#line 2 "/usr/local/plan9/src/cmd/upas/smtp/rfc822.y"
+#include "common.h"
+#include "smtp.h"
+#include <ctype.h>
+
+char *yylp; /* next character to be lex'd */
+int yydone; /* tell yylex to give up */
+char *yybuffer; /* first parsed character */
+char *yyend; /* end of buffer to be parsed */
+Node *root;
+Field *firstfield;
+Field *lastfield;
+Node *usender;
+Node *usys;
+Node *udate;
+char *startfield, *endfield;
+int originator;
+int destination;
+int date;
+int received;
+int messageid;
+extern int yyerrflag;
+#ifndef YYMAXDEPTH
+#define YYMAXDEPTH 150
+#endif
+#ifndef YYSTYPE
+#define YYSTYPE int
+#endif
+YYSTYPE yylval;
+YYSTYPE yyval;
+#define WORD 57346
+#define DATE 57347
+#define RESENT_DATE 57348
+#define RETURN_PATH 57349
+#define FROM 57350
+#define SENDER 57351
+#define REPLY_TO 57352
+#define RESENT_FROM 57353
+#define RESENT_SENDER 57354
+#define RESENT_REPLY_TO 57355
+#define SUBJECT 57356
+#define TO 57357
+#define CC 57358
+#define BCC 57359
+#define RESENT_TO 57360
+#define RESENT_CC 57361
+#define RESENT_BCC 57362
+#define REMOTE 57363
+#define PRECEDENCE 57364
+#define MIMEVERSION 57365
+#define CONTENTTYPE 57366
+#define MESSAGEID 57367
+#define RECEIVED 57368
+#define MAILER 57369
+#define BADTOKEN 57370
+#define YYEOFCODE 1
+#define YYERRCODE 2
+
+#line 246 "/usr/local/plan9/src/cmd/upas/smtp/rfc822.y"
+
+
+/*
+ * Initialize the parsing. Done once for each header field.
+ */
+void
+yyinit(char *p, int len)
+{
+ yybuffer = p;
+ yylp = p;
+ yyend = p + len;
+ firstfield = lastfield = 0;
+ received = 0;
+}
+
+/*
+ * keywords identifying header fields we care about
+ */
+typedef struct Keyword Keyword;
+struct Keyword {
+ char *rep;
+ int val;
+};
+
+/* field names that we need to recognize */
+Keyword key[] = {
+ { "date", DATE },
+ { "resent-date", RESENT_DATE },
+ { "return_path", RETURN_PATH },
+ { "from", FROM },
+ { "sender", SENDER },
+ { "reply-to", REPLY_TO },
+ { "resent-from", RESENT_FROM },
+ { "resent-sender", RESENT_SENDER },
+ { "resent-reply-to", RESENT_REPLY_TO },
+ { "to", TO },
+ { "cc", CC },
+ { "bcc", BCC },
+ { "resent-to", RESENT_TO },
+ { "resent-cc", RESENT_CC },
+ { "resent-bcc", RESENT_BCC },
+ { "remote", REMOTE },
+ { "subject", SUBJECT },
+ { "precedence", PRECEDENCE },
+ { "mime-version", MIMEVERSION },
+ { "content-type", CONTENTTYPE },
+ { "message-id", MESSAGEID },
+ { "received", RECEIVED },
+ { "mailer", MAILER },
+ { "who-the-hell-cares", WORD }
+};
+
+/*
+ * Lexical analysis for an rfc822 header field. Continuation lines
+ * are handled in yywhite() when skipping over white space.
+ *
+ */
+int
+yylex(void)
+{
+ String *t;
+ int quoting;
+ int escaping;
+ char *start;
+ Keyword *kp;
+ int c, d;
+
+/* print("lexing\n"); /**/
+ if(yylp >= yyend)
+ return 0;
+ if(yydone)
+ return 0;
+
+ quoting = escaping = 0;
+ start = yylp;
+ yylval = malloc(sizeof(Node));
+ yylval->white = yylval->s = 0;
+ yylval->next = 0;
+ yylval->addr = 0;
+ yylval->start = yylp;
+ for(t = 0; yylp < yyend; yylp++){
+ c = *yylp & 0xff;
+
+ /* dump nulls, they can't be in header */
+ if(c == 0)
+ continue;
+
+ if(escaping) {
+ escaping = 0;
+ } else if(quoting) {
+ switch(c){
+ case '\\':
+ escaping = 1;
+ break;
+ case '\n':
+ d = (*(yylp+1))&0xff;
+ if(d != ' ' && d != '\t'){
+ quoting = 0;
+ yylp--;
+ continue;
+ }
+ break;
+ case '"':
+ quoting = 0;
+ break;
+ }
+ } else {
+ switch(c){
+ case '\\':
+ escaping = 1;
+ break;
+ case '(':
+ case ' ':
+ case '\t':
+ case '\r':
+ goto out;
+ case '\n':
+ if(yylp == start){
+ yylp++;
+/* print("lex(c %c)\n", c); /**/
+ yylval->end = yylp;
+ return yylval->c = c;
+ }
+ goto out;
+ case '@':
+ case '>':
+ case '<':
+ case ':':
+ case ',':
+ case ';':
+ if(yylp == start){
+ yylp++;
+ yylval->white = yywhite();
+/* print("lex(c %c)\n", c); /**/
+ yylval->end = yylp;
+ return yylval->c = c;
+ }
+ goto out;
+ case '"':
+ quoting = 1;
+ break;
+ default:
+ break;
+ }
+ }
+ if(t == 0)
+ t = s_new();
+ s_putc(t, c);
+ }
+out:
+ yylval->white = yywhite();
+ if(t) {
+ s_terminate(t);
+ } else /* message begins with white-space! */
+ return yylval->c = '\n';
+ yylval->s = t;
+ for(kp = key; kp->val != WORD; kp++)
+ if(cistrcmp(s_to_c(t), kp->rep)==0)
+ break;
+/* print("lex(%d) %s\n", kp->val-WORD, s_to_c(t)); /**/
+ yylval->end = yylp;
+ return yylval->c = kp->val;
+}
+
+void
+yyerror(char *x)
+{
+ USED(x);
+
+ /*fprint(2, "parse err: %s\n", x);/**/
+}
+
+/*
+ * parse white space and comments
+ */
+String *
+yywhite(void)
+{
+ String *w;
+ int clevel;
+ int c;
+ int escaping;
+
+ escaping = clevel = 0;
+ for(w = 0; yylp < yyend; yylp++){
+ c = *yylp & 0xff;
+
+ /* dump nulls, they can't be in header */
+ if(c == 0)
+ continue;
+
+ if(escaping){
+ escaping = 0;
+ } else if(clevel) {
+ switch(c){
+ case '\n':
+ /*
+ * look for multiline fields
+ */
+ if(*(yylp+1)==' ' || *(yylp+1)=='\t')
+ break;
+ else
+ goto out;
+ case '\\':
+ escaping = 1;
+ break;
+ case '(':
+ clevel++;
+ break;
+ case ')':
+ clevel--;
+ break;
+ }
+ } else {
+ switch(c){
+ case '\\':
+ escaping = 1;
+ break;
+ case '(':
+ clevel++;
+ break;
+ case ' ':
+ case '\t':
+ case '\r':
+ break;
+ case '\n':
+ /*
+ * look for multiline fields
+ */
+ if(*(yylp+1)==' ' || *(yylp+1)=='\t')
+ break;
+ else
+ goto out;
+ default:
+ goto out;
+ }
+ }
+ if(w == 0)
+ w = s_new();
+ s_putc(w, c);
+ }
+out:
+ if(w)
+ s_terminate(w);
+ return w;
+}
+
+/*
+ * link two parsed entries together
+ */
+Node*
+link2(Node *p1, Node *p2)
+{
+ Node *p;
+
+ for(p = p1; p->next; p = p->next)
+ ;
+ p->next = p2;
+ return p1;
+}
+
+/*
+ * link three parsed entries together
+ */
+Node*
+link3(Node *p1, Node *p2, Node *p3)
+{
+ Node *p;
+
+ for(p = p2; p->next; p = p->next)
+ ;
+ p->next = p3;
+
+ for(p = p1; p->next; p = p->next)
+ ;
+ p->next = p2;
+
+ return p1;
+}
+
+/*
+ * make a:b, move all white space after both
+ */
+Node*
+colon(Node *p1, Node *p2)
+{
+ if(p1->white){
+ if(p2->white)
+ s_append(p1->white, s_to_c(p2->white));
+ } else {
+ p1->white = p2->white;
+ p2->white = 0;
+ }
+
+ s_append(p1->s, ":");
+ if(p2->s)
+ s_append(p1->s, s_to_c(p2->s));
+
+ if(p1->end < p2->end)
+ p1->end = p2->end;
+ freenode(p2);
+ return p1;
+}
+
+/*
+ * concatenate two fields, move all white space after both
+ */
+Node*
+concat(Node *p1, Node *p2)
+{
+ char buf[2];
+
+ if(p1->white){
+ if(p2->white)
+ s_append(p1->white, s_to_c(p2->white));
+ } else {
+ p1->white = p2->white;
+ p2->white = 0;
+ }
+
+ if(p1->s == nil){
+ buf[0] = p1->c;
+ buf[1] = 0;
+ p1->s = s_new();
+ s_append(p1->s, buf);
+ }
+
+ if(p2->s)
+ s_append(p1->s, s_to_c(p2->s));
+ else {
+ buf[0] = p2->c;
+ buf[1] = 0;
+ s_append(p1->s, buf);
+ }
+
+ if(p1->end < p2->end)
+ p1->end = p2->end;
+ freenode(p2);
+ return p1;
+}
+
+/*
+ * look for disallowed chars in the field name
+ */
+int
+badfieldname(Node *p)
+{
+ for(; p; p = p->next){
+ /* field name can't contain white space */
+ if(p->white && p->next)
+ return 1;
+ }
+ return 0;
+}
+
+/*
+ * mark as an address
+ */
+Node *
+address(Node *p)
+{
+ p->addr = 1;
+ return p;
+}
+
+/*
+ * case independent string compare
+ */
+int
+cistrcmp(char *s1, char *s2)
+{
+ int c1, c2;
+
+ for(; *s1; s1++, s2++){
+ c1 = isupper(*s1) ? tolower(*s1) : *s1;
+ c2 = isupper(*s2) ? tolower(*s2) : *s2;
+ if (c1 != c2)
+ return -1;
+ }
+ return *s2;
+}
+
+/*
+ * free a node
+ */
+void
+freenode(Node *p)
+{
+ Node *tp;
+
+ while(p){
+ tp = p->next;
+ if(p->s)
+ s_free(p->s);
+ if(p->white)
+ s_free(p->white);
+ free(p);
+ p = tp;
+ }
+}
+
+
+/*
+ * an anonymous user
+ */
+Node*
+nobody(Node *p)
+{
+ if(p->s)
+ s_free(p->s);
+ p->s = s_copy("pOsTmAsTeR");
+ p->addr = 1;
+ return p;
+}
+
+/*
+ * add anything that was dropped because of a parse error
+ */
+void
+missing(Node *p)
+{
+ Node *np;
+ char *start, *end;
+ Field *f;
+ String *s;
+
+ start = yybuffer;
+ if(lastfield != nil){
+ for(np = lastfield->node; np; np = np->next)
+ start = np->end+1;
+ }
+
+ end = p->start-1;
+
+ if(end <= start)
+ return;
+
+ if(strncmp(start, "From ", 5) == 0)
+ return;
+
+ np = malloc(sizeof(Node));
+ np->start = start;
+ np->end = end;
+ np->white = nil;
+ s = s_copy("BadHeader: ");
+ np->s = s_nappend(s, start, end-start);
+ np->next = nil;
+
+ f = malloc(sizeof(Field));
+ f->next = 0;
+ f->node = np;
+ f->source = 0;
+ if(firstfield)
+ lastfield->next = f;
+ else
+ firstfield = f;
+ lastfield = f;
+}
+
+/*
+ * create a new field
+ */
+void
+newfield(Node *p, int source)
+{
+ Field *f;
+
+ missing(p);
+
+ f = malloc(sizeof(Field));
+ f->next = 0;
+ f->node = p;
+ f->source = source;
+ if(firstfield)
+ lastfield->next = f;
+ else
+ firstfield = f;
+ lastfield = f;
+ endfield = startfield;
+ startfield = yylp;
+}
+
+/*
+ * fee a list of fields
+ */
+void
+freefield(Field *f)
+{
+ Field *tf;
+
+ while(f){
+ tf = f->next;
+ freenode(f->node);
+ free(f);
+ f = tf;
+ }
+}
+
+/*
+ * add some white space to a node
+ */
+Node*
+whiten(Node *p)
+{
+ Node *tp;
+
+ for(tp = p; tp->next; tp = tp->next)
+ ;
+ if(tp->white == 0)
+ tp->white = s_copy(" ");
+ return p;
+}
+
+void
+yycleanup(void)
+{
+ Field *f, *fnext;
+ Node *np, *next;
+
+ for(f = firstfield; f; f = fnext){
+ for(np = f->node; np; np = next){
+ if(np->s)
+ s_free(np->s);
+ if(np->white)
+ s_free(np->white);
+ next = np->next;
+ free(np);
+ }
+ fnext = f->next;
+ free(f);
+ }
+ firstfield = lastfield = 0;
+}
+static const short yyexca[] =
+{-1, 1,
+ 1, -1,
+ -2, 0,
+-1, 47,
+ 1, 4,
+ -2, 0,
+-1, 112,
+ 29, 72,
+ 31, 72,
+ 32, 72,
+ 35, 72,
+ -2, 74,
+};
+#define YYNPROD 122
+#define YYPRIVATE 57344
+#define YYLAST 608
+static const short yyact[] =
+{
+ 112, 133, 136, 53, 121, 111, 134, 55, 109, 118,
+ 119, 116, 162, 171, 35, 48, 166, 54, 5, 166,
+ 179, 114, 115, 155, 49, 101, 100, 99, 95, 94,
+ 93, 92, 98, 91, 132, 90, 123, 89, 122, 88,
+ 87, 86, 85, 84, 83, 82, 97, 81, 80, 106,
+ 47, 46, 110, 117, 153, 168, 108, 2, 56, 57,
+ 58, 59, 60, 61, 62, 63, 64, 65, 73, 66,
+ 67, 68, 69, 70, 71, 72, 74, 75, 76, 77,
+ 78, 79, 124, 124, 49, 55, 177, 131, 110, 52,
+ 110, 110, 138, 137, 140, 141, 124, 124, 51, 120,
+ 124, 124, 124, 50, 102, 104, 135, 154, 31, 32,
+ 107, 157, 105, 14, 55, 55, 156, 13, 161, 117,
+ 117, 139, 158, 124, 142, 143, 144, 145, 146, 147,
+ 163, 164, 160, 12, 148, 149, 11, 157, 150, 151,
+ 152, 10, 156, 9, 8, 7, 3, 1, 0, 124,
+ 124, 124, 124, 124, 0, 169, 0, 0, 110, 165,
+ 0, 0, 170, 117, 0, 0, 0, 0, 173, 176,
+ 178, 0, 0, 0, 172, 0, 0, 0, 180, 0,
+ 0, 182, 183, 0, 0, 165, 165, 165, 165, 165,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 174, 56, 57, 58, 59, 60, 61, 62,
+ 63, 64, 65, 73, 66, 67, 68, 69, 70, 71,
+ 72, 74, 75, 76, 77, 78, 79, 0, 0, 128,
+ 130, 129, 125, 126, 127, 15, 0, 36, 16, 17,
+ 19, 103, 20, 18, 23, 22, 21, 30, 24, 26,
+ 28, 25, 27, 29, 0, 34, 37, 38, 39, 33,
+ 40, 0, 4, 0, 45, 44, 41, 42, 43, 56,
+ 57, 58, 59, 60, 61, 62, 63, 64, 65, 73,
+ 66, 67, 68, 69, 70, 71, 72, 74, 75, 76,
+ 77, 78, 79, 0, 0, 96, 45, 44, 41, 42,
+ 43, 15, 0, 36, 16, 17, 19, 6, 20, 18,
+ 23, 22, 21, 30, 24, 26, 28, 25, 27, 29,
+ 0, 34, 37, 38, 39, 33, 40, 0, 4, 0,
+ 45, 44, 41, 42, 43, 15, 0, 36, 16, 17,
+ 19, 103, 20, 18, 23, 22, 21, 30, 24, 26,
+ 28, 25, 27, 29, 0, 34, 37, 38, 39, 33,
+ 40, 0, 0, 0, 45, 44, 41, 42, 43, 56,
+ 57, 58, 59, 60, 61, 62, 63, 64, 65, 73,
+ 66, 67, 68, 69, 70, 71, 72, 74, 75, 76,
+ 77, 78, 79, 0, 0, 0, 0, 175, 113, 0,
+ 52, 56, 57, 58, 59, 60, 61, 62, 63, 64,
+ 65, 73, 66, 67, 68, 69, 70, 71, 72, 74,
+ 75, 76, 77, 78, 79, 0, 0, 0, 0, 0,
+ 113, 0, 52, 56, 57, 58, 59, 60, 61, 62,
+ 63, 64, 65, 73, 66, 67, 68, 69, 70, 71,
+ 72, 74, 75, 76, 77, 78, 79, 0, 0, 0,
+ 0, 0, 0, 159, 52, 56, 57, 58, 59, 60,
+ 61, 62, 63, 64, 65, 73, 66, 67, 68, 69,
+ 70, 71, 72, 74, 75, 76, 77, 78, 79, 0,
+ 0, 0, 0, 0, 0, 0, 52, 56, 57, 58,
+ 59, 60, 61, 62, 63, 64, 65, 73, 66, 67,
+ 68, 69, 70, 71, 72, 74, 75, 76, 77, 78,
+ 79, 0, 0, 167, 0, 0, 113, 56, 57, 58,
+ 59, 60, 61, 62, 63, 64, 65, 73, 66, 67,
+ 68, 69, 70, 71, 72, 74, 75, 76, 77, 78,
+ 79, 0, 0, 0, 0, 0, 113, 56, 57, 58,
+ 59, 60, 61, 62, 63, 64, 65, 73, 66, 67,
+ 68, 69, 70, 71, 72, 74, 75, 76, 77, 78,
+ 79, 0, 0, 181, 56, 57, 58, 59, 60, 61,
+ 62, 63, 64, 65, 73, 66, 67, 68, 69, 70,
+ 71, 72, 74, 75, 76, 77, 78, 79
+};
+static const short yypact[] =
+{
+ 299,-1000,-1000, 22,-1000, 21, 54,-1000,-1000,-1000,
+-1000,-1000,-1000,-1000,-1000, 19, 17, 15, 14, 13,
+ 12, 11, 10, 9, 7, 5, 3, 1, 0, -1,
+ -2, 265, -3, -4, -5,-1000,-1000,-1000,-1000,-1000,
+-1000,-1000,-1000,-1000,-1000,-1000, 233, 233, 580, 397,
+ -9,-1000, 580, -26, -25,-1000,-1000,-1000,-1000,-1000,
+-1000,-1000,-1000,-1000,-1000,-1000,-1000,-1000,-1000,-1000,
+-1000,-1000,-1000,-1000,-1000,-1000,-1000,-1000,-1000,-1000,
+ 333, 199, 199, 397, 461, 397, 397, 397, 397, 397,
+ 397, 397, 397, 397, 397, 199, 199,-1000,-1000, 199,
+ 199, 199,-1000, -6,-1000, 33, 580, -8,-1000,-1000,
+ 523,-1000,-1000, 429, 580, -23,-1000,-1000, 580, 580,
+-1000,-1000, 199,-1000,-1000,-1000,-1000,-1000,-1000,-1000,
+-1000,-1000, -15,-1000,-1000,-1000, 493,-1000,-1000, -15,
+-1000,-1000, -15, -15, -15, -15, -15, -15, 199, 199,
+ 199, 199, 199, 47, 580, 397,-1000,-1000, -21,-1000,
+ -25, -26, 580,-1000,-1000,-1000, 397, 365, 580, 580,
+-1000,-1000,-1000,-1000, -12,-1000,-1000, 553,-1000,-1000,
+ 580, 580,-1000,-1000
+};
+static const short yypgo[] =
+{
+ 0, 147, 57, 146, 18, 145, 144, 143, 141, 136,
+ 133, 117, 113, 8, 112, 0, 34, 110, 6, 4,
+ 38, 109, 108, 1, 106, 2, 5, 103, 17, 98,
+ 11, 3, 36, 86, 14
+};
+static const short yyr1[] =
+{
+ 0, 1, 1, 2, 2, 2, 4, 4, 4, 4,
+ 4, 4, 4, 4, 4, 3, 6, 6, 6, 6,
+ 6, 6, 6, 5, 5, 7, 7, 7, 7, 7,
+ 7, 7, 7, 7, 7, 7, 7, 8, 8, 11,
+ 11, 12, 12, 10, 10, 21, 21, 21, 21, 9,
+ 9, 16, 16, 23, 23, 24, 24, 17, 17, 18,
+ 18, 18, 26, 26, 13, 13, 27, 27, 29, 29,
+ 28, 28, 31, 30, 25, 25, 20, 20, 32, 32,
+ 32, 32, 32, 32, 32, 19, 14, 33, 33, 15,
+ 15, 15, 15, 15, 15, 15, 15, 15, 15, 15,
+ 15, 15, 15, 15, 15, 15, 15, 15, 15, 15,
+ 15, 15, 15, 22, 22, 22, 22, 34, 34, 34,
+ 34, 34
+};
+static const short yyr2[] =
+{
+ 0, 1, 3, 1, 2, 3, 1, 1, 1, 1,
+ 1, 1, 1, 1, 3, 6, 3, 3, 3, 3,
+ 3, 3, 3, 3, 3, 2, 3, 2, 3, 2,
+ 3, 2, 3, 2, 3, 2, 3, 3, 2, 3,
+ 2, 3, 2, 3, 2, 1, 1, 1, 1, 3,
+ 2, 1, 3, 1, 1, 4, 3, 1, 3, 1,
+ 2, 1, 3, 2, 3, 1, 2, 4, 1, 1,
+ 3, 3, 1, 1, 1, 2, 1, 2, 1, 1,
+ 1, 1, 1, 1, 1, 1, 6, 1, 3, 1,
+ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
+ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
+ 1, 1, 1, 1, 1, 2, 2, 1, 1, 1,
+ 1, 1
+};
+static const short yychk[] =
+{
+-1000, -1, -2, -3, 29, -4, 8, -5, -6, -7,
+ -8, -9, -10, -11, -12, 2, 5, 6, 10, 7,
+ 9, 13, 12, 11, 15, 18, 16, 19, 17, 20,
+ 14, -22, -21, 26, 22, -34, 4, 23, 24, 25,
+ 27, 33, 34, 35, 32, 31, 29, 29, -13, 30,
+ -27, -29, 35, -31, -28, -15, 4, 5, 6, 7,
+ 8, 9, 10, 11, 12, 13, 15, 16, 17, 18,
+ 19, 20, 21, 14, 22, 23, 24, 25, 26, 27,
+ 29, 30, 30, 30, 30, 30, 30, 30, 30, 30,
+ 30, 30, 30, 30, 30, 30, 30, -34, -15, 30,
+ 30, 30, -2, 8, -2, -14, -15, -17, -18, -13,
+ -25, -26, -15, 33, 30, 31, -30, -15, 35, 35,
+ -4, -19, -20, -32, -15, 33, 34, 35, 30, 32,
+ 31, -19, -16, -23, -18, -24, -25, -13, -18, -16,
+ -18, -18, -16, -16, -16, -16, -16, -16, -20, -20,
+ -20, -20, -20, 21, -15, 31, -26, -15, -13, 34,
+ -28, -31, 35, -30, -30, -32, 31, 30, 8, -15,
+ -18, 34, -30, -23, -16, 32, -15, -33, -15, 32,
+ -15, 30, -15, -15
+};
+static const short yydef[] =
+{
+ 0, -2, 1, 0, 3, 0, 0, 6, 7, 8,
+ 9, 10, 11, 12, 13, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 113, 114, 45, 46, 47,
+ 48, 117, 118, 119, 120, 121, 0, -2, 0, 0,
+ 0, 65, 0, 68, 69, 72, 89, 90, 91, 92,
+ 93, 94, 95, 96, 97, 98, 99, 100, 101, 102,
+ 103, 104, 105, 106, 107, 108, 109, 110, 111, 112,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 25,
+ 27, 29, 31, 33, 35, 38, 50, 115, 116, 44,
+ 40, 42, 2, 0, 5, 0, 0, 18, 57, 59,
+ 0, 61, -2, 0, 0, 0, 66, 73, 0, 0,
+ 14, 23, 85, 76, 78, 79, 80, 81, 82, 83,
+ 84, 24, 16, 51, 53, 54, 0, 17, 19, 20,
+ 21, 22, 26, 28, 30, 32, 34, 36, 37, 49,
+ 43, 39, 41, 0, 0, 0, 60, 75, 0, 63,
+ 64, 0, 0, 70, 71, 77, 0, 0, 0, 0,
+ 58, 62, 67, 52, 0, 56, 15, 0, 87, 55,
+ 0, 0, 86, 88
+};
+static const short yytok1[] =
+{
+ 1, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 29, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 31, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 30, 32,
+ 33, 0, 34, 0, 35
+};
+static const short yytok2[] =
+{
+ 2, 3, 4, 5, 6, 7, 8, 9, 10, 11,
+ 12, 13, 14, 15, 16, 17, 18, 19, 20, 21,
+ 22, 23, 24, 25, 26, 27, 28
+};
+static const long yytok3[] =
+{
+ 0
+};
+#define YYFLAG -1000
+#define YYERROR goto yyerrlab
+#define YYACCEPT return(0)
+#define YYABORT return(1)
+#define yyclearin yychar = -1
+#define yyerrok yyerrflag = 0
+
+#ifdef yydebug
+#include "y.debug"
+#else
+#define yydebug 0
+static const char* yytoknames[1]; /* for debugging */
+static const char* yystates[1]; /* for debugging */
+#endif
+
+/* parser for yacc output */
+#ifdef YYARG
+#define yynerrs yyarg->yynerrs
+#define yyerrflag yyarg->yyerrflag
+#define yyval yyarg->yyval
+#define yylval yyarg->yylval
+#else
+int yynerrs = 0; /* number of errors */
+int yyerrflag = 0; /* error recovery flag */
+#endif
+
+extern int fprint(int, char*, ...);
+extern int sprint(char*, char*, ...);
+
+static const char*
+yytokname(int yyc)
+{
+ static char x[10];
+
+ if(yyc > 0 && yyc <= sizeof(yytoknames)/sizeof(yytoknames[0]))
+ if(yytoknames[yyc-1])
+ return yytoknames[yyc-1];
+ sprint(x, "<%d>", yyc);
+ return x;
+}
+
+static const char*
+yystatname(int yys)
+{
+ static char x[10];
+
+ if(yys >= 0 && yys < sizeof(yystates)/sizeof(yystates[0]))
+ if(yystates[yys])
+ return yystates[yys];
+ sprint(x, "<%d>\n", yys);
+ return x;
+}
+
+static long
+#ifdef YYARG
+yylex1(struct Yyarg *yyarg)
+#else
+yylex1(void)
+#endif
+{
+ long yychar;
+ const long *t3p;
+ int c;
+
+#ifdef YYARG
+ yychar = yylex(yyarg);
+#else
+ yychar = yylex();
+#endif
+ if(yychar <= 0) {
+ c = yytok1[0];
+ goto out;
+ }
+ if(yychar < sizeof(yytok1)/sizeof(yytok1[0])) {
+ c = yytok1[yychar];
+ goto out;
+ }
+ if(yychar >= YYPRIVATE)
+ if(yychar < YYPRIVATE+sizeof(yytok2)/sizeof(yytok2[0])) {
+ c = yytok2[yychar-YYPRIVATE];
+ goto out;
+ }
+ for(t3p=yytok3;; t3p+=2) {
+ c = t3p[0];
+ if(c == yychar) {
+ c = t3p[1];
+ goto out;
+ }
+ if(c == 0)
+ break;
+ }
+ c = 0;
+
+out:
+ if(c == 0)
+ c = yytok2[1]; /* unknown char */
+ if(yydebug >= 3)
+ fprint(2, "lex %.4lux %s\n", yychar, yytokname(c));
+ return c;
+}
+
+int
+#ifdef YYARG
+yyparse(struct Yyarg *yyarg)
+#else
+yyparse(void)
+#endif
+{
+ struct
+ {
+ YYSTYPE yyv;
+ int yys;
+ } yys[YYMAXDEPTH], *yyp, *yypt;
+ const short *yyxi;
+ int yyj, yym, yystate, yyn, yyg;
+ long yychar;
+#ifndef YYARG
+ YYSTYPE save1, save2;
+ int save3, save4;
+
+ save1 = yylval;
+ save2 = yyval;
+ save3 = yynerrs;
+ save4 = yyerrflag;
+#endif
+
+ yystate = 0;
+ yychar = -1;
+ yynerrs = 0;
+ yyerrflag = 0;
+ yyp = &yys[-1];
+ goto yystack;
+
+ret0:
+ yyn = 0;
+ goto ret;
+
+ret1:
+ yyn = 1;
+ goto ret;
+
+ret:
+#ifndef YYARG
+ yylval = save1;
+ yyval = save2;
+ yynerrs = save3;
+ yyerrflag = save4;
+#endif
+ return yyn;
+
+yystack:
+ /* put a state and value onto the stack */
+ if(yydebug >= 4)
+ fprint(2, "char %s in %s", yytokname(yychar), yystatname(yystate));
+
+ yyp++;
+ if(yyp >= &yys[YYMAXDEPTH]) {
+ yyerror("yacc stack overflow");
+ goto ret1;
+ }
+ yyp->yys = yystate;
+ yyp->yyv = yyval;
+
+yynewstate:
+ yyn = yypact[yystate];
+ if(yyn <= YYFLAG)
+ goto yydefault; /* simple state */
+ if(yychar < 0)
+#ifdef YYARG
+ yychar = yylex1(yyarg);
+#else
+ yychar = yylex1();
+#endif
+ yyn += yychar;
+ if(yyn < 0 || yyn >= YYLAST)
+ goto yydefault;
+ yyn = yyact[yyn];
+ if(yychk[yyn] == yychar) { /* valid shift */
+ yychar = -1;
+ yyval = yylval;
+ yystate = yyn;
+ if(yyerrflag > 0)
+ yyerrflag--;
+ goto yystack;
+ }
+
+yydefault:
+ /* default state action */
+ yyn = yydef[yystate];
+ if(yyn == -2) {
+ if(yychar < 0)
+#ifdef YYARG
+ yychar = yylex1(yyarg);
+#else
+ yychar = yylex1();
+#endif
+
+ /* look through exception table */
+ for(yyxi=yyexca;; yyxi+=2)
+ if(yyxi[0] == -1 && yyxi[1] == yystate)
+ break;
+ for(yyxi += 2;; yyxi += 2) {
+ yyn = yyxi[0];
+ if(yyn < 0 || yyn == yychar)
+ break;
+ }
+ yyn = yyxi[1];
+ if(yyn < 0)
+ goto ret0;
+ }
+ if(yyn == 0) {
+ /* error ... attempt to resume parsing */
+ switch(yyerrflag) {
+ case 0: /* brand new error */
+ yyerror("syntax error");
+ if(yydebug >= 1) {
+ fprint(2, "%s", yystatname(yystate));
+ fprint(2, "saw %s\n", yytokname(yychar));
+ }
+ goto yyerrlab;
+ yyerrlab:
+ yynerrs++;
+
+ case 1:
+ case 2: /* incompletely recovered error ... try again */
+ yyerrflag = 3;
+
+ /* find a state where "error" is a legal shift action */
+ while(yyp >= yys) {
+ yyn = yypact[yyp->yys] + YYERRCODE;
+ if(yyn >= 0 && yyn < YYLAST) {
+ yystate = yyact[yyn]; /* simulate a shift of "error" */
+ if(yychk[yystate] == YYERRCODE)
+ goto yystack;
+ }
+
+ /* the current yyp has no shift onn "error", pop stack */
+ if(yydebug >= 2)
+ fprint(2, "error recovery pops state %d, uncovers %d\n",
+ yyp->yys, (yyp-1)->yys );
+ yyp--;
+ }
+ /* there is no state on the stack with an error shift ... abort */
+ goto ret1;
+
+ case 3: /* no shift yet; clobber input char */
+ if(yydebug >= 2)
+ fprint(2, "error recovery discards %s\n", yytokname(yychar));
+ if(yychar == YYEOFCODE)
+ goto ret1;
+ yychar = -1;
+ goto yynewstate; /* try again in the same state */
+ }
+ }
+
+ /* reduction by production yyn */
+ if(yydebug >= 2)
+ fprint(2, "reduce %d in:\n\t%s", yyn, yystatname(yystate));
+
+ yypt = yyp;
+ yyp -= yyr2[yyn];
+ yyval = (yyp+1)->yyv;
+ yym = yyn;
+
+ /* consult goto table to find next state */
+ yyn = yyr1[yyn];
+ yyg = yypgo[yyn];
+ yyj = yyg + yyp->yys + 1;
+
+ if(yyj >= YYLAST || yychk[yystate=yyact[yyj]] != -yyn)
+ yystate = yyact[yyg];
+ switch(yym) {
+
+case 3:
+#line 56 "/usr/local/plan9/src/cmd/upas/smtp/rfc822.y"
+{ yydone = 1; } break;
+case 6:
+#line 61 "/usr/local/plan9/src/cmd/upas/smtp/rfc822.y"
+{ date = 1; } break;
+case 7:
+#line 63 "/usr/local/plan9/src/cmd/upas/smtp/rfc822.y"
+{ originator = 1; } break;
+case 8:
+#line 65 "/usr/local/plan9/src/cmd/upas/smtp/rfc822.y"
+{ destination = 1; } break;
+case 15:
+#line 74 "/usr/local/plan9/src/cmd/upas/smtp/rfc822.y"
+{ freenode(yypt[-5].yyv); freenode(yypt[-2].yyv); freenode(yypt[-1].yyv);
+ usender = yypt[-4].yyv; udate = yypt[-3].yyv; usys = yypt[-0].yyv;
+ } break;
+case 16:
+#line 79 "/usr/local/plan9/src/cmd/upas/smtp/rfc822.y"
+{ newfield(link3(yypt[-2].yyv, yypt[-1].yyv, yypt[-0].yyv), 1); } break;
+case 17:
+#line 81 "/usr/local/plan9/src/cmd/upas/smtp/rfc822.y"
+{ newfield(link3(yypt[-2].yyv, yypt[-1].yyv, yypt[-0].yyv), 1); } break;
+case 18:
+#line 83 "/usr/local/plan9/src/cmd/upas/smtp/rfc822.y"
+{ newfield(link3(yypt[-2].yyv, yypt[-1].yyv, yypt[-0].yyv), 1); } break;
+case 19:
+#line 85 "/usr/local/plan9/src/cmd/upas/smtp/rfc822.y"
+{ newfield(link3(yypt[-2].yyv, yypt[-1].yyv, yypt[-0].yyv), 1); } break;
+case 20:
+#line 87 "/usr/local/plan9/src/cmd/upas/smtp/rfc822.y"
+{ newfield(link3(yypt[-2].yyv, yypt[-1].yyv, yypt[-0].yyv), 1); } break;
+case 21:
+#line 89 "/usr/local/plan9/src/cmd/upas/smtp/rfc822.y"
+{ newfield(link3(yypt[-2].yyv, yypt[-1].yyv, yypt[-0].yyv), 1); } break;
+case 22:
+#line 91 "/usr/local/plan9/src/cmd/upas/smtp/rfc822.y"
+{ newfield(link3(yypt[-2].yyv, yypt[-1].yyv, yypt[-0].yyv), 1); } break;
+case 23:
+#line 94 "/usr/local/plan9/src/cmd/upas/smtp/rfc822.y"
+{ newfield(link3(yypt[-2].yyv, yypt[-1].yyv, yypt[-0].yyv), 0); } break;
+case 24:
+#line 96 "/usr/local/plan9/src/cmd/upas/smtp/rfc822.y"
+{ newfield(link3(yypt[-2].yyv, yypt[-1].yyv, yypt[-0].yyv), 0); } break;
+case 25:
+#line 99 "/usr/local/plan9/src/cmd/upas/smtp/rfc822.y"
+{ newfield(link2(yypt[-1].yyv, yypt[-0].yyv), 0); } break;
+case 26:
+#line 101 "/usr/local/plan9/src/cmd/upas/smtp/rfc822.y"
+{ newfield(link3(yypt[-2].yyv, yypt[-1].yyv, yypt[-0].yyv), 0); } break;
+case 27:
+#line 103 "/usr/local/plan9/src/cmd/upas/smtp/rfc822.y"
+{ newfield(link2(yypt[-1].yyv, yypt[-0].yyv), 0); } break;
+case 28:
+#line 105 "/usr/local/plan9/src/cmd/upas/smtp/rfc822.y"
+{ newfield(link3(yypt[-2].yyv, yypt[-1].yyv, yypt[-0].yyv), 0); } break;
+case 29:
+#line 107 "/usr/local/plan9/src/cmd/upas/smtp/rfc822.y"
+{ newfield(link2(yypt[-1].yyv, yypt[-0].yyv), 0); } break;
+case 30:
+#line 109 "/usr/local/plan9/src/cmd/upas/smtp/rfc822.y"
+{ newfield(link3(yypt[-2].yyv, yypt[-1].yyv, yypt[-0].yyv), 0); } break;
+case 31:
+#line 111 "/usr/local/plan9/src/cmd/upas/smtp/rfc822.y"
+{ newfield(link2(yypt[-1].yyv, yypt[-0].yyv), 0); } break;
+case 32:
+#line 113 "/usr/local/plan9/src/cmd/upas/smtp/rfc822.y"
+{ newfield(link3(yypt[-2].yyv, yypt[-1].yyv, yypt[-0].yyv), 0); } break;
+case 33:
+#line 115 "/usr/local/plan9/src/cmd/upas/smtp/rfc822.y"
+{ newfield(link2(yypt[-1].yyv, yypt[-0].yyv), 0); } break;
+case 34:
+#line 117 "/usr/local/plan9/src/cmd/upas/smtp/rfc822.y"
+{ newfield(link3(yypt[-2].yyv, yypt[-1].yyv, yypt[-0].yyv), 0); } break;
+case 35:
+#line 119 "/usr/local/plan9/src/cmd/upas/smtp/rfc822.y"
+{ newfield(link2(yypt[-1].yyv, yypt[-0].yyv), 0); } break;
+case 36:
+#line 121 "/usr/local/plan9/src/cmd/upas/smtp/rfc822.y"
+{ newfield(link3(yypt[-2].yyv, yypt[-1].yyv, yypt[-0].yyv), 0); } break;
+case 37:
+#line 124 "/usr/local/plan9/src/cmd/upas/smtp/rfc822.y"
+{ newfield(link3(yypt[-2].yyv, yypt[-1].yyv, yypt[-0].yyv), 0); } break;
+case 38:
+#line 126 "/usr/local/plan9/src/cmd/upas/smtp/rfc822.y"
+{ newfield(link2(yypt[-1].yyv, yypt[-0].yyv), 0); } break;
+case 39:
+#line 129 "/usr/local/plan9/src/cmd/upas/smtp/rfc822.y"
+{ newfield(link3(yypt[-2].yyv, yypt[-1].yyv, yypt[-0].yyv), 0); received++; } break;
+case 40:
+#line 131 "/usr/local/plan9/src/cmd/upas/smtp/rfc822.y"
+{ newfield(link2(yypt[-1].yyv, yypt[-0].yyv), 0); received++; } break;
+case 41:
+#line 134 "/usr/local/plan9/src/cmd/upas/smtp/rfc822.y"
+{ newfield(link3(yypt[-2].yyv, yypt[-1].yyv, yypt[-0].yyv), 0); } break;
+case 42:
+#line 136 "/usr/local/plan9/src/cmd/upas/smtp/rfc822.y"
+{ newfield(link2(yypt[-1].yyv, yypt[-0].yyv), 0); } break;
+case 43:
+#line 139 "/usr/local/plan9/src/cmd/upas/smtp/rfc822.y"
+{ newfield(link3(yypt[-2].yyv, yypt[-1].yyv, yypt[-0].yyv), 0); } break;
+case 44:
+#line 141 "/usr/local/plan9/src/cmd/upas/smtp/rfc822.y"
+{ newfield(link2(yypt[-1].yyv, yypt[-0].yyv), 0); } break;
+case 47:
+#line 143 "/usr/local/plan9/src/cmd/upas/smtp/rfc822.y"
+{ messageid = 1; } break;
+case 49:
+#line 146 "/usr/local/plan9/src/cmd/upas/smtp/rfc822.y"
+{ /* hack to allow same lex for field names and the rest */
+ if(badfieldname(yypt[-2].yyv)){
+ freenode(yypt[-2].yyv);
+ freenode(yypt[-1].yyv);
+ freenode(yypt[-0].yyv);
+ return 1;
+ }
+ newfield(link3(yypt[-2].yyv, yypt[-1].yyv, yypt[-0].yyv), 0);
+ } break;
+case 50:
+#line 156 "/usr/local/plan9/src/cmd/upas/smtp/rfc822.y"
+{ /* hack to allow same lex for field names and the rest */
+ if(badfieldname(yypt[-1].yyv)){
+ freenode(yypt[-1].yyv);
+ freenode(yypt[-0].yyv);
+ return 1;
+ }
+ newfield(link2(yypt[-1].yyv, yypt[-0].yyv), 0);
+ } break;
+case 52:
+#line 167 "/usr/local/plan9/src/cmd/upas/smtp/rfc822.y"
+{ yyval = link3(yypt[-2].yyv, yypt[-1].yyv, yypt[-0].yyv); } break;
+case 55:
+#line 173 "/usr/local/plan9/src/cmd/upas/smtp/rfc822.y"
+{ yyval = link2(yypt[-3].yyv, link3(yypt[-2].yyv, yypt[-1].yyv, yypt[-0].yyv)); } break;
+case 56:
+#line 175 "/usr/local/plan9/src/cmd/upas/smtp/rfc822.y"
+{ yyval = link3(yypt[-2].yyv, yypt[-1].yyv, yypt[-0].yyv); } break;
+case 58:
+#line 179 "/usr/local/plan9/src/cmd/upas/smtp/rfc822.y"
+{ yyval = link3(yypt[-2].yyv, yypt[-1].yyv, yypt[-0].yyv); } break;
+case 60:
+#line 183 "/usr/local/plan9/src/cmd/upas/smtp/rfc822.y"
+{ yyval = link2(yypt[-1].yyv, yypt[-0].yyv); } break;
+case 62:
+#line 187 "/usr/local/plan9/src/cmd/upas/smtp/rfc822.y"
+{ yyval = link3(yypt[-2].yyv, yypt[-1].yyv, yypt[-0].yyv); } break;
+case 63:
+#line 189 "/usr/local/plan9/src/cmd/upas/smtp/rfc822.y"
+{ yyval = nobody(yypt[-0].yyv); freenode(yypt[-1].yyv); } break;
+case 64:
+#line 192 "/usr/local/plan9/src/cmd/upas/smtp/rfc822.y"
+{ yyval = address(concat(yypt[-2].yyv, concat(yypt[-1].yyv, yypt[-0].yyv))); } break;
+case 66:
+#line 196 "/usr/local/plan9/src/cmd/upas/smtp/rfc822.y"
+{ yyval = concat(yypt[-1].yyv, yypt[-0].yyv); } break;
+case 67:
+#line 198 "/usr/local/plan9/src/cmd/upas/smtp/rfc822.y"
+{ yyval = concat(yypt[-3].yyv, concat(yypt[-2].yyv, concat(yypt[-1].yyv, yypt[-0].yyv))); } break;
+case 68:
+#line 201 "/usr/local/plan9/src/cmd/upas/smtp/rfc822.y"
+{ yyval = address(yypt[-0].yyv); } break;
+case 70:
+#line 205 "/usr/local/plan9/src/cmd/upas/smtp/rfc822.y"
+{ yyval = address(concat(yypt[-2].yyv, concat(yypt[-1].yyv, yypt[-0].yyv)));} break;
+case 71:
+#line 207 "/usr/local/plan9/src/cmd/upas/smtp/rfc822.y"
+{ yyval = address(concat(yypt[-2].yyv, concat(yypt[-1].yyv, yypt[-0].yyv)));} break;
+case 75:
+#line 215 "/usr/local/plan9/src/cmd/upas/smtp/rfc822.y"
+{ yyval = link2(yypt[-1].yyv, yypt[-0].yyv); } break;
+case 77:
+#line 219 "/usr/local/plan9/src/cmd/upas/smtp/rfc822.y"
+{ yyval = link2(yypt[-1].yyv, yypt[-0].yyv); } break;
+case 86:
+#line 226 "/usr/local/plan9/src/cmd/upas/smtp/rfc822.y"
+{ yyval = link3(yypt[-5].yyv, yypt[-3].yyv, link3(yypt[-4].yyv, yypt[-0].yyv, link2(yypt[-2].yyv, yypt[-1].yyv))); } break;
+case 88:
+#line 230 "/usr/local/plan9/src/cmd/upas/smtp/rfc822.y"
+{ yyval = link3(yypt[-2].yyv, yypt[-1].yyv, yypt[-0].yyv); } break;
+case 115:
+#line 240 "/usr/local/plan9/src/cmd/upas/smtp/rfc822.y"
+{ yyval = link2(yypt[-1].yyv, yypt[-0].yyv); } break;
+case 116:
+#line 242 "/usr/local/plan9/src/cmd/upas/smtp/rfc822.y"
+{ yyval = link2(yypt[-1].yyv, yypt[-0].yyv); } break;
+ }
+ goto yystack; /* stack new state and value */
+}
diff --git a/src/cmd/upas/smtp/rfc822.tab.h b/src/cmd/upas/smtp/rfc822.tab.h
new file mode 100644
index 00000000..cddbbc0f
--- /dev/null
+++ b/src/cmd/upas/smtp/rfc822.tab.h
@@ -0,0 +1,98 @@
+/* A Bison parser, made by GNU Bison 2.0. */
+
+/* Skeleton parser for Yacc-like parsing with Bison,
+ Copyright (C) 1984, 1989, 1990, 2000, 2001, 2002, 2003, 2004 Free Software Foundation, Inc.
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 2, or (at your option)
+ any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program; if not, write to the Free Software
+ Foundation, Inc., 59 Temple Place - Suite 330,
+ Boston, MA 02111-1307, USA. */
+
+/* As a special exception, when this file is copied by Bison into a
+ Bison output file, you may use that output file without restriction.
+ This special exception was added by the Free Software Foundation
+ in version 1.24 of Bison. */
+
+/* Tokens. */
+#ifndef YYTOKENTYPE
+# define YYTOKENTYPE
+ /* Put the tokens into the symbol table, so that GDB and other debuggers
+ know about them. */
+ enum yytokentype {
+ WORD = 258,
+ DATE = 259,
+ RESENT_DATE = 260,
+ RETURN_PATH = 261,
+ FROM = 262,
+ SENDER = 263,
+ REPLY_TO = 264,
+ RESENT_FROM = 265,
+ RESENT_SENDER = 266,
+ RESENT_REPLY_TO = 267,
+ SUBJECT = 268,
+ TO = 269,
+ CC = 270,
+ BCC = 271,
+ RESENT_TO = 272,
+ RESENT_CC = 273,
+ RESENT_BCC = 274,
+ REMOTE = 275,
+ PRECEDENCE = 276,
+ MIMEVERSION = 277,
+ CONTENTTYPE = 278,
+ MESSAGEID = 279,
+ RECEIVED = 280,
+ MAILER = 281,
+ BADTOKEN = 282
+ };
+#endif
+#define WORD 258
+#define DATE 259
+#define RESENT_DATE 260
+#define RETURN_PATH 261
+#define FROM 262
+#define SENDER 263
+#define REPLY_TO 264
+#define RESENT_FROM 265
+#define RESENT_SENDER 266
+#define RESENT_REPLY_TO 267
+#define SUBJECT 268
+#define TO 269
+#define CC 270
+#define BCC 271
+#define RESENT_TO 272
+#define RESENT_CC 273
+#define RESENT_BCC 274
+#define REMOTE 275
+#define PRECEDENCE 276
+#define MIMEVERSION 277
+#define CONTENTTYPE 278
+#define MESSAGEID 279
+#define RECEIVED 280
+#define MAILER 281
+#define BADTOKEN 282
+
+
+
+
+#if ! defined (YYSTYPE) && ! defined (YYSTYPE_IS_DECLARED)
+typedef int YYSTYPE;
+# define yystype YYSTYPE /* obsolescent; will be withdrawn */
+# define YYSTYPE_IS_DECLARED 1
+# define YYSTYPE_IS_TRIVIAL 1
+#endif
+
+extern YYSTYPE yylval;
+
+
+
diff --git a/src/cmd/upas/smtp/rfc822.y b/src/cmd/upas/smtp/rfc822.y
new file mode 100644
index 00000000..be77d2bd
--- /dev/null
+++ b/src/cmd/upas/smtp/rfc822.y
@@ -0,0 +1,778 @@
+%{
+#include "common.h"
+#include "smtp.h"
+#include <ctype.h>
+
+char *yylp; /* next character to be lex'd */
+int yydone; /* tell yylex to give up */
+char *yybuffer; /* first parsed character */
+char *yyend; /* end of buffer to be parsed */
+Node *root;
+Field *firstfield;
+Field *lastfield;
+Node *usender;
+Node *usys;
+Node *udate;
+char *startfield, *endfield;
+int originator;
+int destination;
+int date;
+int received;
+int messageid;
+%}
+
+%term WORD
+%term DATE
+%term RESENT_DATE
+%term RETURN_PATH
+%term FROM
+%term SENDER
+%term REPLY_TO
+%term RESENT_FROM
+%term RESENT_SENDER
+%term RESENT_REPLY_TO
+%term SUBJECT
+%term TO
+%term CC
+%term BCC
+%term RESENT_TO
+%term RESENT_CC
+%term RESENT_BCC
+%term REMOTE
+%term PRECEDENCE
+%term MIMEVERSION
+%term CONTENTTYPE
+%term MESSAGEID
+%term RECEIVED
+%term MAILER
+%term BADTOKEN
+%start msg
+%%
+
+msg : fields
+ | unixfrom '\n' fields
+ ;
+fields : '\n'
+ { yydone = 1; }
+ | field '\n'
+ | field '\n' fields
+ ;
+field : dates
+ { date = 1; }
+ | originator
+ { originator = 1; }
+ | destination
+ { destination = 1; }
+ | subject
+ | optional
+ | ignored
+ | received
+ | precedence
+ | error '\n' field
+ ;
+unixfrom : FROM route_addr unix_date_time REMOTE FROM word
+ { freenode($1); freenode($4); freenode($5);
+ usender = $2; udate = $3; usys = $6;
+ }
+ ;
+originator : REPLY_TO ':' address_list
+ { newfield(link3($1, $2, $3), 1); }
+ | RETURN_PATH ':' route_addr
+ { newfield(link3($1, $2, $3), 1); }
+ | FROM ':' mailbox_list
+ { newfield(link3($1, $2, $3), 1); }
+ | SENDER ':' mailbox
+ { newfield(link3($1, $2, $3), 1); }
+ | RESENT_REPLY_TO ':' address_list
+ { newfield(link3($1, $2, $3), 1); }
+ | RESENT_SENDER ':' mailbox
+ { newfield(link3($1, $2, $3), 1); }
+ | RESENT_FROM ':' mailbox
+ { newfield(link3($1, $2, $3), 1); }
+ ;
+dates : DATE ':' date_time
+ { newfield(link3($1, $2, $3), 0); }
+ | RESENT_DATE ':' date_time
+ { newfield(link3($1, $2, $3), 0); }
+ ;
+destination : TO ':'
+ { newfield(link2($1, $2), 0); }
+ | TO ':' address_list
+ { newfield(link3($1, $2, $3), 0); }
+ | RESENT_TO ':'
+ { newfield(link2($1, $2), 0); }
+ | RESENT_TO ':' address_list
+ { newfield(link3($1, $2, $3), 0); }
+ | CC ':'
+ { newfield(link2($1, $2), 0); }
+ | CC ':' address_list
+ { newfield(link3($1, $2, $3), 0); }
+ | RESENT_CC ':'
+ { newfield(link2($1, $2), 0); }
+ | RESENT_CC ':' address_list
+ { newfield(link3($1, $2, $3), 0); }
+ | BCC ':'
+ { newfield(link2($1, $2), 0); }
+ | BCC ':' address_list
+ { newfield(link3($1, $2, $3), 0); }
+ | RESENT_BCC ':'
+ { newfield(link2($1, $2), 0); }
+ | RESENT_BCC ':' address_list
+ { newfield(link3($1, $2, $3), 0); }
+ ;
+subject : SUBJECT ':' things
+ { newfield(link3($1, $2, $3), 0); }
+ | SUBJECT ':'
+ { newfield(link2($1, $2), 0); }
+ ;
+received : RECEIVED ':' things
+ { newfield(link3($1, $2, $3), 0); received++; }
+ | RECEIVED ':'
+ { newfield(link2($1, $2), 0); received++; }
+ ;
+precedence : PRECEDENCE ':' things
+ { newfield(link3($1, $2, $3), 0); }
+ | PRECEDENCE ':'
+ { newfield(link2($1, $2), 0); }
+ ;
+ignored : ignoredhdr ':' things
+ { newfield(link3($1, $2, $3), 0); }
+ | ignoredhdr ':'
+ { newfield(link2($1, $2), 0); }
+ ;
+ignoredhdr : MIMEVERSION | CONTENTTYPE | MESSAGEID { messageid = 1; } | MAILER
+ ;
+optional : fieldwords ':' things
+ { /* hack to allow same lex for field names and the rest */
+ if(badfieldname($1)){
+ freenode($1);
+ freenode($2);
+ freenode($3);
+ return 1;
+ }
+ newfield(link3($1, $2, $3), 0);
+ }
+ | fieldwords ':'
+ { /* hack to allow same lex for field names and the rest */
+ if(badfieldname($1)){
+ freenode($1);
+ freenode($2);
+ return 1;
+ }
+ newfield(link2($1, $2), 0);
+ }
+ ;
+address_list : address
+ | address_list ',' address
+ { $$ = link3($1, $2, $3); }
+ ;
+address : mailbox
+ | group
+ ;
+group : phrase ':' address_list ';'
+ { $$ = link2($1, link3($2, $3, $4)); }
+ | phrase ':' ';'
+ { $$ = link3($1, $2, $3); }
+ ;
+mailbox_list : mailbox
+ | mailbox_list ',' mailbox
+ { $$ = link3($1, $2, $3); }
+ ;
+mailbox : route_addr
+ | phrase brak_addr
+ { $$ = link2($1, $2); }
+ | brak_addr
+ ;
+brak_addr : '<' route_addr '>'
+ { $$ = link3($1, $2, $3); }
+ | '<' '>'
+ { $$ = nobody($2); freenode($1); }
+ ;
+route_addr : route ':' at_addr
+ { $$ = address(concat($1, concat($2, $3))); }
+ | addr_spec
+ ;
+route : '@' domain
+ { $$ = concat($1, $2); }
+ | route ',' '@' domain
+ { $$ = concat($1, concat($2, concat($3, $4))); }
+ ;
+addr_spec : local_part
+ { $$ = address($1); }
+ | at_addr
+ ;
+at_addr : local_part '@' domain
+ { $$ = address(concat($1, concat($2, $3)));}
+ | at_addr '@' domain
+ { $$ = address(concat($1, concat($2, $3)));}
+ ;
+local_part : word
+ ;
+domain : word
+ ;
+phrase : word
+ | phrase word
+ { $$ = link2($1, $2); }
+ ;
+things : thing
+ | things thing
+ { $$ = link2($1, $2); }
+ ;
+thing : word | '<' | '>' | '@' | ':' | ';' | ','
+ ;
+date_time : things
+ ;
+unix_date_time : word word word unix_time word word
+ { $$ = link3($1, $3, link3($2, $6, link2($4, $5))); }
+ ;
+unix_time : word
+ | unix_time ':' word
+ { $$ = link3($1, $2, $3); }
+ ;
+word : WORD | DATE | RESENT_DATE | RETURN_PATH | FROM | SENDER
+ | REPLY_TO | RESENT_FROM | RESENT_SENDER | RESENT_REPLY_TO
+ | TO | CC | BCC | RESENT_TO | RESENT_CC | RESENT_BCC | REMOTE | SUBJECT
+ | PRECEDENCE | MIMEVERSION | CONTENTTYPE | MESSAGEID | RECEIVED | MAILER
+ ;
+fieldwords : fieldword
+ | WORD
+ | fieldwords fieldword
+ { $$ = link2($1, $2); }
+ | fieldwords word
+ { $$ = link2($1, $2); }
+ ;
+fieldword : '<' | '>' | '@' | ';' | ','
+ ;
+%%
+
+/*
+ * Initialize the parsing. Done once for each header field.
+ */
+void
+yyinit(char *p, int len)
+{
+ yybuffer = p;
+ yylp = p;
+ yyend = p + len;
+ firstfield = lastfield = 0;
+ received = 0;
+}
+
+/*
+ * keywords identifying header fields we care about
+ */
+typedef struct Keyword Keyword;
+struct Keyword {
+ char *rep;
+ int val;
+};
+
+/* field names that we need to recognize */
+Keyword key[] = {
+ { "date", DATE },
+ { "resent-date", RESENT_DATE },
+ { "return_path", RETURN_PATH },
+ { "from", FROM },
+ { "sender", SENDER },
+ { "reply-to", REPLY_TO },
+ { "resent-from", RESENT_FROM },
+ { "resent-sender", RESENT_SENDER },
+ { "resent-reply-to", RESENT_REPLY_TO },
+ { "to", TO },
+ { "cc", CC },
+ { "bcc", BCC },
+ { "resent-to", RESENT_TO },
+ { "resent-cc", RESENT_CC },
+ { "resent-bcc", RESENT_BCC },
+ { "remote", REMOTE },
+ { "subject", SUBJECT },
+ { "precedence", PRECEDENCE },
+ { "mime-version", MIMEVERSION },
+ { "content-type", CONTENTTYPE },
+ { "message-id", MESSAGEID },
+ { "received", RECEIVED },
+ { "mailer", MAILER },
+ { "who-the-hell-cares", WORD }
+};
+
+/*
+ * Lexical analysis for an rfc822 header field. Continuation lines
+ * are handled in yywhite() when skipping over white space.
+ *
+ */
+int
+yylex(void)
+{
+ String *t;
+ int quoting;
+ int escaping;
+ char *start;
+ Keyword *kp;
+ int c, d;
+
+/* print("lexing\n"); /**/
+ if(yylp >= yyend)
+ return 0;
+ if(yydone)
+ return 0;
+
+ quoting = escaping = 0;
+ start = yylp;
+ yylval = malloc(sizeof(Node));
+ yylval->white = yylval->s = 0;
+ yylval->next = 0;
+ yylval->addr = 0;
+ yylval->start = yylp;
+ for(t = 0; yylp < yyend; yylp++){
+ c = *yylp & 0xff;
+
+ /* dump nulls, they can't be in header */
+ if(c == 0)
+ continue;
+
+ if(escaping) {
+ escaping = 0;
+ } else if(quoting) {
+ switch(c){
+ case '\\':
+ escaping = 1;
+ break;
+ case '\n':
+ d = (*(yylp+1))&0xff;
+ if(d != ' ' && d != '\t'){
+ quoting = 0;
+ yylp--;
+ continue;
+ }
+ break;
+ case '"':
+ quoting = 0;
+ break;
+ }
+ } else {
+ switch(c){
+ case '\\':
+ escaping = 1;
+ break;
+ case '(':
+ case ' ':
+ case '\t':
+ case '\r':
+ goto out;
+ case '\n':
+ if(yylp == start){
+ yylp++;
+/* print("lex(c %c)\n", c); /**/
+ yylval->end = yylp;
+ return yylval->c = c;
+ }
+ goto out;
+ case '@':
+ case '>':
+ case '<':
+ case ':':
+ case ',':
+ case ';':
+ if(yylp == start){
+ yylp++;
+ yylval->white = yywhite();
+/* print("lex(c %c)\n", c); /**/
+ yylval->end = yylp;
+ return yylval->c = c;
+ }
+ goto out;
+ case '"':
+ quoting = 1;
+ break;
+ default:
+ break;
+ }
+ }
+ if(t == 0)
+ t = s_new();
+ s_putc(t, c);
+ }
+out:
+ yylval->white = yywhite();
+ if(t) {
+ s_terminate(t);
+ } else /* message begins with white-space! */
+ return yylval->c = '\n';
+ yylval->s = t;
+ for(kp = key; kp->val != WORD; kp++)
+ if(cistrcmp(s_to_c(t), kp->rep)==0)
+ break;
+/* print("lex(%d) %s\n", kp->val-WORD, s_to_c(t)); /**/
+ yylval->end = yylp;
+ return yylval->c = kp->val;
+}
+
+void
+yyerror(char *x)
+{
+ USED(x);
+
+ /*fprint(2, "parse err: %s\n", x);/**/
+}
+
+/*
+ * parse white space and comments
+ */
+String *
+yywhite(void)
+{
+ String *w;
+ int clevel;
+ int c;
+ int escaping;
+
+ escaping = clevel = 0;
+ for(w = 0; yylp < yyend; yylp++){
+ c = *yylp & 0xff;
+
+ /* dump nulls, they can't be in header */
+ if(c == 0)
+ continue;
+
+ if(escaping){
+ escaping = 0;
+ } else if(clevel) {
+ switch(c){
+ case '\n':
+ /*
+ * look for multiline fields
+ */
+ if(*(yylp+1)==' ' || *(yylp+1)=='\t')
+ break;
+ else
+ goto out;
+ case '\\':
+ escaping = 1;
+ break;
+ case '(':
+ clevel++;
+ break;
+ case ')':
+ clevel--;
+ break;
+ }
+ } else {
+ switch(c){
+ case '\\':
+ escaping = 1;
+ break;
+ case '(':
+ clevel++;
+ break;
+ case ' ':
+ case '\t':
+ case '\r':
+ break;
+ case '\n':
+ /*
+ * look for multiline fields
+ */
+ if(*(yylp+1)==' ' || *(yylp+1)=='\t')
+ break;
+ else
+ goto out;
+ default:
+ goto out;
+ }
+ }
+ if(w == 0)
+ w = s_new();
+ s_putc(w, c);
+ }
+out:
+ if(w)
+ s_terminate(w);
+ return w;
+}
+
+/*
+ * link two parsed entries together
+ */
+Node*
+link2(Node *p1, Node *p2)
+{
+ Node *p;
+
+ for(p = p1; p->next; p = p->next)
+ ;
+ p->next = p2;
+ return p1;
+}
+
+/*
+ * link three parsed entries together
+ */
+Node*
+link3(Node *p1, Node *p2, Node *p3)
+{
+ Node *p;
+
+ for(p = p2; p->next; p = p->next)
+ ;
+ p->next = p3;
+
+ for(p = p1; p->next; p = p->next)
+ ;
+ p->next = p2;
+
+ return p1;
+}
+
+/*
+ * make a:b, move all white space after both
+ */
+Node*
+colon(Node *p1, Node *p2)
+{
+ if(p1->white){
+ if(p2->white)
+ s_append(p1->white, s_to_c(p2->white));
+ } else {
+ p1->white = p2->white;
+ p2->white = 0;
+ }
+
+ s_append(p1->s, ":");
+ if(p2->s)
+ s_append(p1->s, s_to_c(p2->s));
+
+ if(p1->end < p2->end)
+ p1->end = p2->end;
+ freenode(p2);
+ return p1;
+}
+
+/*
+ * concatenate two fields, move all white space after both
+ */
+Node*
+concat(Node *p1, Node *p2)
+{
+ char buf[2];
+
+ if(p1->white){
+ if(p2->white)
+ s_append(p1->white, s_to_c(p2->white));
+ } else {
+ p1->white = p2->white;
+ p2->white = 0;
+ }
+
+ if(p1->s == nil){
+ buf[0] = p1->c;
+ buf[1] = 0;
+ p1->s = s_new();
+ s_append(p1->s, buf);
+ }
+
+ if(p2->s)
+ s_append(p1->s, s_to_c(p2->s));
+ else {
+ buf[0] = p2->c;
+ buf[1] = 0;
+ s_append(p1->s, buf);
+ }
+
+ if(p1->end < p2->end)
+ p1->end = p2->end;
+ freenode(p2);
+ return p1;
+}
+
+/*
+ * look for disallowed chars in the field name
+ */
+int
+badfieldname(Node *p)
+{
+ for(; p; p = p->next){
+ /* field name can't contain white space */
+ if(p->white && p->next)
+ return 1;
+ }
+ return 0;
+}
+
+/*
+ * mark as an address
+ */
+Node *
+address(Node *p)
+{
+ p->addr = 1;
+ return p;
+}
+
+/*
+ * case independent string compare
+ */
+int
+cistrcmp(char *s1, char *s2)
+{
+ int c1, c2;
+
+ for(; *s1; s1++, s2++){
+ c1 = isupper(*s1) ? tolower(*s1) : *s1;
+ c2 = isupper(*s2) ? tolower(*s2) : *s2;
+ if (c1 != c2)
+ return -1;
+ }
+ return *s2;
+}
+
+/*
+ * free a node
+ */
+void
+freenode(Node *p)
+{
+ Node *tp;
+
+ while(p){
+ tp = p->next;
+ if(p->s)
+ s_free(p->s);
+ if(p->white)
+ s_free(p->white);
+ free(p);
+ p = tp;
+ }
+}
+
+
+/*
+ * an anonymous user
+ */
+Node*
+nobody(Node *p)
+{
+ if(p->s)
+ s_free(p->s);
+ p->s = s_copy("pOsTmAsTeR");
+ p->addr = 1;
+ return p;
+}
+
+/*
+ * add anything that was dropped because of a parse error
+ */
+void
+missing(Node *p)
+{
+ Node *np;
+ char *start, *end;
+ Field *f;
+ String *s;
+
+ start = yybuffer;
+ if(lastfield != nil){
+ for(np = lastfield->node; np; np = np->next)
+ start = np->end+1;
+ }
+
+ end = p->start-1;
+
+ if(end <= start)
+ return;
+
+ if(strncmp(start, "From ", 5) == 0)
+ return;
+
+ np = malloc(sizeof(Node));
+ np->start = start;
+ np->end = end;
+ np->white = nil;
+ s = s_copy("BadHeader: ");
+ np->s = s_nappend(s, start, end-start);
+ np->next = nil;
+
+ f = malloc(sizeof(Field));
+ f->next = 0;
+ f->node = np;
+ f->source = 0;
+ if(firstfield)
+ lastfield->next = f;
+ else
+ firstfield = f;
+ lastfield = f;
+}
+
+/*
+ * create a new field
+ */
+void
+newfield(Node *p, int source)
+{
+ Field *f;
+
+ missing(p);
+
+ f = malloc(sizeof(Field));
+ f->next = 0;
+ f->node = p;
+ f->source = source;
+ if(firstfield)
+ lastfield->next = f;
+ else
+ firstfield = f;
+ lastfield = f;
+ endfield = startfield;
+ startfield = yylp;
+}
+
+/*
+ * fee a list of fields
+ */
+void
+freefield(Field *f)
+{
+ Field *tf;
+
+ while(f){
+ tf = f->next;
+ freenode(f->node);
+ free(f);
+ f = tf;
+ }
+}
+
+/*
+ * add some white space to a node
+ */
+Node*
+whiten(Node *p)
+{
+ Node *tp;
+
+ for(tp = p; tp->next; tp = tp->next)
+ ;
+ if(tp->white == 0)
+ tp->white = s_copy(" ");
+ return p;
+}
+
+void
+yycleanup(void)
+{
+ Field *f, *fnext;
+ Node *np, *next;
+
+ for(f = firstfield; f; f = fnext){
+ for(np = f->node; np; np = next){
+ if(np->s)
+ s_free(np->s);
+ if(np->white)
+ s_free(np->white);
+ next = np->next;
+ free(np);
+ }
+ fnext = f->next;
+ free(f);
+ }
+ firstfield = lastfield = 0;
+}
diff --git a/src/cmd/upas/smtp/rmtdns.c b/src/cmd/upas/smtp/rmtdns.c
new file mode 100644
index 00000000..54b679bb
--- /dev/null
+++ b/src/cmd/upas/smtp/rmtdns.c
@@ -0,0 +1,58 @@
+#include "common.h"
+#include <ndb.h>
+
+int
+rmtdns(char *net, char *path)
+{
+
+ int fd, n, r;
+ char *domain, *cp, buf[1024];
+
+ if(net == 0 || path == 0)
+ return 0;
+
+ domain = strdup(path);
+ cp = strchr(domain, '!');
+ if(cp){
+ *cp = 0;
+ n = cp-domain;
+ } else
+ n = strlen(domain);
+
+ if(*domain == '[' && domain[n-1] == ']'){ /* accept [nnn.nnn.nnn.nnn] */
+ domain[n-1] = 0;
+ r = strcmp(ipattr(domain+1), "ip");
+ domain[n-1] = ']';
+ } else
+ r = strcmp(ipattr(domain), "ip"); /* accept nnn.nnn.nnn.nnn */
+
+ if(r == 0){
+ free(domain);
+ return 0;
+ }
+
+ snprint(buf, sizeof(buf), "%s/dns", net);
+
+ fd = open(buf, ORDWR); /* look up all others */
+ if(fd < 0){ /* dns screw up - can't check */
+ free(domain);
+ return 0;
+ }
+
+ n = snprint(buf, sizeof(buf), "%s all", domain);
+ free(domain);
+ seek(fd, 0, 0);
+ n = write(fd, buf, n);
+ close(fd);
+ if(n < 0){
+ rerrstr(buf, sizeof(buf));
+ if (strcmp(buf, "dns: name does not exist") == 0)
+ return -1;
+ }
+ return 0;
+}
+
+/*
+void main(int, char *argv[]){ print("return = %d\n", rmtdns("/net.alt/tcp/109", argv[1]));}
+
+*/
diff --git a/src/cmd/upas/smtp/smtp.c b/src/cmd/upas/smtp/smtp.c
new file mode 100644
index 00000000..e88154f7
--- /dev/null
+++ b/src/cmd/upas/smtp/smtp.c
@@ -0,0 +1,1122 @@
+#include "common.h"
+#include "smtp.h"
+#include <ctype.h>
+#include <mp.h>
+#include <libsec.h>
+#include <auth.h>
+#include <ndb.h>
+
+static char* connect(char*);
+static char* dotls(char*);
+static char* doauth(char*);
+char* hello(char*, int);
+char* mailfrom(char*);
+char* rcptto(char*);
+char* data(String*, Biobuf*);
+void quit(char*);
+int getreply(void);
+void addhostdom(String*, char*);
+String* bangtoat(char*);
+String* convertheader(String*);
+int printheader(void);
+char* domainify(char*, char*);
+void putcrnl(char*, int);
+char* getcrnl(String*);
+int printdate(Node*);
+char *rewritezone(char *);
+int dBprint(char*, ...);
+int dBputc(int);
+String* fixrouteaddr(String*, Node*, Node*);
+char* expand_addr(char* a);
+int ping;
+int insecure;
+
+#define Retry "Retry, Temporary Failure"
+#define Giveup "Permanent Failure"
+
+int debug; /* true if we're debugging */
+String *reply; /* last reply */
+String *toline;
+int alarmscale;
+int last = 'n'; /* last character sent by putcrnl() */
+int filter;
+int trysecure; /* Try to use TLS if the other side supports it */
+int tryauth; /* Try to authenticate, if supported */
+int quitting; /* when error occurs in quit */
+char *quitrv; /* deferred return value when in quit */
+char ddomain[1024]; /* domain name of destination machine */
+char *gdomain; /* domain name of gateway */
+char *uneaten; /* first character after rfc822 headers */
+char *farend; /* system we are trying to send to */
+char *user; /* user we are authenticating as, if authenticating */
+char hostdomain[256];
+Biobuf bin;
+Biobuf bout;
+Biobuf berr;
+Biobuf bfile;
+
+void
+usage(void)
+{
+ fprint(2, "usage: smtp [-adips] [-uuser] [-hhost] [.domain] net!host[!service] sender rcpt-list\n");
+ exits(Giveup);
+}
+
+int
+timeout(void *x, char *msg)
+{
+ USED(x);
+ syslog(0, "smtp.fail", "interrupt: %s: %s", farend, msg);
+ if(strstr(msg, "alarm")){
+ fprint(2, "smtp timeout: connection to %s timed out\n", farend);
+ if(quitting)
+ exits(quitrv);
+ exits(Retry);
+ }
+ if(strstr(msg, "closed pipe")){
+ /* call _exits() to prevent Bio from trying to flush closed pipe */
+ fprint(2, "smtp timeout: connection closed to %s\n", farend);
+ if(quitting){
+ syslog(0, "smtp.fail", "closed pipe to %s", farend);
+ _exits(quitrv);
+ }
+ _exits(Retry);
+ }
+ return 0;
+}
+
+void
+removenewline(char *p)
+{
+ int n = strlen(p)-1;
+
+ if(n < 0)
+ return;
+ if(p[n] == '\n')
+ p[n] = 0;
+}
+
+void
+threadmain(int argc, char **argv)
+{
+ char hellodomain[256];
+ char *host, *domain;
+ String *from;
+ String *fromm;
+ String *sender;
+ char *addr;
+ char *rv, *trv;
+ int i, ok, rcvrs;
+ char **errs;
+
+ alarmscale = 60*1000; /* minutes */
+ quotefmtinstall();
+ errs = malloc(argc*sizeof(char*));
+ reply = s_new();
+ host = 0;
+ ARGBEGIN{
+ case 'a':
+ tryauth = 1;
+ trysecure = 1;
+ break;
+ case 'f':
+ filter = 1;
+ break;
+ case 'd':
+ debug = 1;
+ break;
+ case 'g':
+ gdomain = ARGF();
+ break;
+ case 'h':
+ host = ARGF();
+ break;
+ case 'i':
+ insecure = 1;
+ break;
+ case 'p':
+ alarmscale = 10*1000; /* tens of seconds */
+ ping = 1;
+ break;
+ case 's':
+ trysecure = 1;
+ break;
+ case 'u':
+ user = ARGF();
+ break;
+ default:
+ usage();
+ break;
+ }ARGEND;
+
+ Binit(&berr, 2, OWRITE);
+ Binit(&bfile, 0, OREAD);
+
+ /*
+ * get domain and add to host name
+ */
+ if(*argv && **argv=='.') {
+ domain = *argv;
+ argv++; argc--;
+ } else
+ domain = domainname_read();
+ if(host == 0)
+ host = sysname_read();
+ strcpy(hostdomain, domainify(host, domain));
+ strcpy(hellodomain, domainify(sysname_read(), domain));
+
+ /*
+ * get destination address
+ */
+ if(*argv == 0)
+ usage();
+ addr = *argv++; argc--;
+ // expand $smtp if necessary
+ addr = expand_addr(addr);
+ farend = addr;
+
+ /*
+ * get sender's machine.
+ * get sender in internet style. domainify if necessary.
+ */
+ if(*argv == 0)
+ usage();
+ sender = unescapespecial(s_copy(*argv++));
+ argc--;
+ fromm = s_clone(sender);
+ rv = strrchr(s_to_c(fromm), '!');
+ if(rv)
+ *rv = 0;
+ else
+ *s_to_c(fromm) = 0;
+ from = bangtoat(s_to_c(sender));
+
+ /*
+ * send the mail
+ */
+ if(filter){
+ Binit(&bout, 1, OWRITE);
+ rv = data(from, &bfile);
+ if(rv != 0)
+ goto error;
+ exits(0);
+ }
+
+ /* 10 minutes to get through the initial handshake */
+ atnotify(timeout, 1);
+
+ alarm(10*alarmscale);
+ if((rv = connect(addr)) != 0)
+ exits(rv);
+ alarm(10*alarmscale);
+ if((rv = hello(hellodomain, 0)) != 0)
+ goto error;
+ alarm(10*alarmscale);
+ if((rv = mailfrom(s_to_c(from))) != 0)
+ goto error;
+
+ ok = 0;
+ rcvrs = 0;
+ /* if any rcvrs are ok, we try to send the message */
+ for(i = 0; i < argc; i++){
+ if((trv = rcptto(argv[i])) != 0){
+ /* remember worst error */
+ if(rv != Giveup)
+ rv = trv;
+ errs[rcvrs] = strdup(s_to_c(reply));
+ removenewline(errs[rcvrs]);
+ } else {
+ ok++;
+ errs[rcvrs] = 0;
+ }
+ rcvrs++;
+ }
+
+ /* if no ok rcvrs or worst error is retry, give up */
+ if(ok == 0 || rv == Retry)
+ goto error;
+
+ if(ping){
+ quit(0);
+ exits(0);
+ }
+
+ rv = data(from, &bfile);
+ if(rv != 0)
+ goto error;
+ quit(0);
+ if(rcvrs == ok)
+ exits(0);
+
+ /*
+ * here when some but not all rcvrs failed
+ */
+ fprint(2, "%s connect to %s:\n", thedate(), addr);
+ for(i = 0; i < rcvrs; i++){
+ if(errs[i]){
+ syslog(0, "smtp.fail", "delivery to %s at %s failed: %s", argv[i], addr, errs[i]);
+ fprint(2, " mail to %s failed: %s", argv[i], errs[i]);
+ }
+ }
+ exits(Giveup);
+
+ /*
+ * here when all rcvrs failed
+ */
+error:
+ removenewline(s_to_c(reply));
+ syslog(0, "smtp.fail", "%s to %s failed: %s",
+ ping ? "ping" : "delivery",
+ addr, s_to_c(reply));
+ fprint(2, "%s connect to %s:\n%s\n", thedate(), addr, s_to_c(reply));
+ if(!filter)
+ quit(rv);
+ exits(rv);
+}
+
+/*
+ * connect to the remote host
+ */
+static char *
+connect(char* net)
+{
+ char buf[256];
+ int fd;
+
+ fd = mxdial(net, ddomain, gdomain);
+
+ if(fd < 0){
+ rerrstr(buf, sizeof(buf));
+ Bprint(&berr, "smtp: %s (%s)\n", buf, net);
+ syslog(0, "smtp.fail", "%s (%s)", buf, net);
+ if(strstr(buf, "illegal")
+ || strstr(buf, "unknown")
+ || strstr(buf, "can't translate"))
+ return Giveup;
+ else
+ return Retry;
+ }
+ Binit(&bin, fd, OREAD);
+ fd = dup(fd, -1);
+ Binit(&bout, fd, OWRITE);
+ return 0;
+}
+
+static char smtpthumbs[] = "/sys/lib/tls/smtp";
+static char smtpexclthumbs[] = "/sys/lib/tls/smtp.exclude";
+
+/*
+ * exchange names with remote host, attempt to
+ * enable encryption and optionally authenticate.
+ * not fatal if we can't.
+ */
+static char *
+dotls(char *me)
+{
+ TLSconn *c;
+ Thumbprint *goodcerts;
+ char *h;
+ int fd;
+ uchar hash[SHA1dlen];
+
+ c = mallocz(sizeof(*c), 1); /* Note: not freed on success */
+ if (c == nil)
+ return Giveup;
+
+ dBprint("STARTTLS\r\n");
+ if (getreply() != 2)
+ return Giveup;
+
+ fd = tlsClient(Bfildes(&bout), c);
+ if (fd < 0) {
+ syslog(0, "smtp", "tlsClient to %q: %r", ddomain);
+ return Giveup;
+ }
+ goodcerts = initThumbprints(smtpthumbs, smtpexclthumbs);
+ if (goodcerts == nil) {
+ free(c);
+ close(fd);
+ syslog(0, "smtp", "bad thumbprints in %s", smtpthumbs);
+ return Giveup; /* how to recover? TLS is started */
+ }
+
+ /* compute sha1 hash of remote's certificate, see if we know it */
+ sha1(c->cert, c->certlen, hash, nil);
+ if (!okThumbprint(hash, goodcerts)) {
+ /* TODO? if not excluded, add hash to thumb list */
+ free(c);
+ close(fd);
+ h = malloc(2*sizeof hash + 1);
+ if (h != nil) {
+ enc16(h, 2*sizeof hash + 1, hash, sizeof hash);
+ // print("x509 sha1=%s", h);
+ syslog(0, "smtp",
+ "remote cert. has bad thumbprint: x509 sha1=%s server=%q",
+ h, ddomain);
+ free(h);
+ }
+ return Giveup; /* how to recover? TLS is started */
+ }
+ freeThumbprints(goodcerts);
+ Bterm(&bin);
+ Bterm(&bout);
+
+ /*
+ * set up bin & bout to use the TLS fd, i/o upon which generates
+ * i/o on the original, underlying fd.
+ */
+ Binit(&bin, fd, OREAD);
+ fd = dup(fd, -1);
+ Binit(&bout, fd, OWRITE);
+
+ syslog(0, "smtp", "started TLS to %q", ddomain);
+ return(hello(me, 1));
+}
+
+static char *
+doauth(char *methods)
+{
+ char *buf, *base64;
+ int n;
+ DS ds;
+ UserPasswd *p;
+
+ dial_string_parse(ddomain, &ds);
+
+ if(user != nil)
+ p = auth_getuserpasswd(nil,
+ "proto=pass service=smtp server=%q user=%q", ds.host, user);
+ else
+ p = auth_getuserpasswd(nil,
+ "proto=pass service=smtp server=%q", ds.host);
+ if (p == nil)
+ return Giveup;
+
+ if (strstr(methods, "LOGIN")){
+ dBprint("AUTH LOGIN\r\n");
+ if (getreply() != 3)
+ return Retry;
+
+ n = strlen(p->user);
+ base64 = malloc(2*n);
+ if (base64 == nil)
+ return Retry; /* Out of memory */
+ enc64(base64, 2*n, (uchar *)p->user, n);
+ dBprint("%s\r\n", base64);
+ if (getreply() != 3)
+ return Retry;
+
+ n = strlen(p->passwd);
+ base64 = malloc(2*n);
+ if (base64 == nil)
+ return Retry; /* Out of memory */
+ enc64(base64, 2*n, (uchar *)p->passwd, n);
+ dBprint("%s\r\n", base64);
+ if (getreply() != 2)
+ return Retry;
+
+ free(base64);
+ }
+ else
+ if (strstr(methods, "PLAIN")){
+ n = strlen(p->user) + strlen(p->passwd) + 3;
+ buf = malloc(n);
+ base64 = malloc(2 * n);
+ if (buf == nil || base64 == nil) {
+ free(buf);
+ return Retry; /* Out of memory */
+ }
+ snprint(buf, n, "%c%s%c%s", 0, p->user, 0, p->passwd);
+ enc64(base64, 2 * n, (uchar *)buf, n - 1);
+ free(buf);
+ dBprint("AUTH PLAIN %s\r\n", base64);
+ free(base64);
+ if (getreply() != 2)
+ return Retry;
+ }
+ else
+ return "No supported AUTH method";
+ return(0);
+}
+
+char *
+hello(char *me, int encrypted)
+{
+ int ehlo;
+ String *r;
+ char *ret, *s, *t;
+
+ if (!encrypted)
+ switch(getreply()){
+ case 2:
+ break;
+ case 5:
+ return Giveup;
+ default:
+ return Retry;
+ }
+
+ ehlo = 1;
+ Again:
+ if(ehlo)
+ dBprint("EHLO %s\r\n", me);
+ else
+ dBprint("HELO %s\r\n", me);
+ switch (getreply()) {
+ case 2:
+ break;
+ case 5:
+ if(ehlo){
+ ehlo = 0;
+ goto Again;
+ }
+ return Giveup;
+ default:
+ return Retry;
+ }
+ r = s_clone(reply);
+ if(r == nil)
+ return Retry; /* Out of memory or couldn't get string */
+
+ /* Invariant: every line has a newline, a result of getcrlf() */
+ for(s = s_to_c(r); (t = strchr(s, '\n')) != nil; s = t + 1){
+ *t = '\0';
+ for (t = s; *t != '\0'; t++)
+ *t = toupper(*t);
+ if(!encrypted && trysecure &&
+ (strcmp(s, "250-STARTTLS") == 0 ||
+ strcmp(s, "250 STARTTLS") == 0)){
+ s_free(r);
+ return(dotls(me));
+ }
+ if(tryauth && (encrypted || insecure) &&
+ (strncmp(s, "250 AUTH", strlen("250 AUTH")) == 0 ||
+ strncmp(s, "250-AUTH", strlen("250 AUTH")) == 0)){
+ ret = doauth(s + strlen("250 AUTH "));
+ s_free(r);
+ return ret;
+ }
+ }
+ s_free(r);
+ return 0;
+}
+
+/*
+ * report sender to remote
+ */
+char *
+mailfrom(char *from)
+{
+ if(!returnable(from))
+ dBprint("MAIL FROM:<>\r\n");
+ else
+ if(strchr(from, '@'))
+ dBprint("MAIL FROM:<%s>\r\n", from);
+ else
+ dBprint("MAIL FROM:<%s@%s>\r\n", from, hostdomain);
+ switch(getreply()){
+ case 2:
+ break;
+ case 5:
+ return Giveup;
+ default:
+ return Retry;
+ }
+ return 0;
+}
+
+/*
+ * report a recipient to remote
+ */
+char *
+rcptto(char *to)
+{
+ String *s;
+
+ s = unescapespecial(bangtoat(to));
+ if(toline == 0)
+ toline = s_new();
+ else
+ s_append(toline, ", ");
+ s_append(toline, s_to_c(s));
+ if(strchr(s_to_c(s), '@'))
+ dBprint("RCPT TO:<%s>\r\n", s_to_c(s));
+ else {
+ s_append(toline, "@");
+ s_append(toline, ddomain);
+ dBprint("RCPT TO:<%s@%s>\r\n", s_to_c(s), ddomain);
+ }
+ alarm(10*alarmscale);
+ switch(getreply()){
+ case 2:
+ break;
+ case 5:
+ return Giveup;
+ default:
+ return Retry;
+ }
+ return 0;
+}
+
+static char hex[] = "0123456789abcdef";
+
+/*
+ * send the damn thing
+ */
+char *
+data(String *from, Biobuf *b)
+{
+ char *buf, *cp;
+ int i, n, nbytes, bufsize, eof, r;
+ String *fromline;
+ char errmsg[Errlen];
+ char id[40];
+
+ /*
+ * input the header.
+ */
+
+ buf = malloc(1);
+ if(buf == 0){
+ s_append(s_restart(reply), "out of memory");
+ return Retry;
+ }
+ n = 0;
+ eof = 0;
+ for(;;){
+ cp = Brdline(b, '\n');
+ if(cp == nil){
+ eof = 1;
+ break;
+ }
+ nbytes = Blinelen(b);
+ buf = realloc(buf, n+nbytes+1);
+ if(buf == 0){
+ s_append(s_restart(reply), "out of memory");
+ return Retry;
+ }
+ strncpy(buf+n, cp, nbytes);
+ n += nbytes;
+ if(nbytes == 1) /* end of header */
+ break;
+ }
+ buf[n] = 0;
+ bufsize = n;
+
+ /*
+ * parse the header, turn all addresses into @ format
+ */
+ yyinit(buf, n);
+ yyparse();
+
+ /*
+ * print message observing '.' escapes and using \r\n for \n
+ */
+ alarm(20*alarmscale);
+ if(!filter){
+ dBprint("DATA\r\n");
+ switch(getreply()){
+ case 3:
+ break;
+ case 5:
+ free(buf);
+ return Giveup;
+ default:
+ free(buf);
+ return Retry;
+ }
+ }
+ /*
+ * send header. add a message-id, a sender, and a date if there
+ * isn't one
+ */
+ nbytes = 0;
+ fromline = convertheader(from);
+ uneaten = buf;
+
+ srand(truerand());
+ if(messageid == 0){
+ for(i=0; i<16; i++){
+ r = rand()&0xFF;
+ id[2*i] = hex[r&0xF];
+ id[2*i+1] = hex[(r>>4)&0xF];
+ }
+ id[2*i] = '\0';
+ nbytes += Bprint(&bout, "Message-ID: <%s@%s>\r\n", id, hostdomain);
+ if(debug)
+ Bprint(&berr, "Message-ID: <%s@%s>\r\n", id, hostdomain);
+ }
+
+ if(originator==0){
+ nbytes += Bprint(&bout, "From: %s\r\n", s_to_c(fromline));
+ if(debug)
+ Bprint(&berr, "From: %s\r\n", s_to_c(fromline));
+ }
+ s_free(fromline);
+
+ if(destination == 0 && toline)
+ if(*s_to_c(toline) == '@'){ /* route addr */
+ nbytes += Bprint(&bout, "To: <%s>\r\n", s_to_c(toline));
+ if(debug)
+ Bprint(&berr, "To: <%s>\r\n", s_to_c(toline));
+ } else {
+ nbytes += Bprint(&bout, "To: %s\r\n", s_to_c(toline));
+ if(debug)
+ Bprint(&berr, "To: %s\r\n", s_to_c(toline));
+ }
+
+ if(date==0 && udate)
+ nbytes += printdate(udate);
+ if (usys)
+ uneaten = usys->end + 1;
+ nbytes += printheader();
+ if (*uneaten != '\n')
+ putcrnl("\n", 1);
+
+ /*
+ * send body
+ */
+
+ putcrnl(uneaten, buf+n - uneaten);
+ nbytes += buf+n - uneaten;
+ if(eof == 0){
+ for(;;){
+ n = Bread(b, buf, bufsize);
+ if(n < 0){
+ rerrstr(errmsg, sizeof(errmsg));
+ s_append(s_restart(reply), errmsg);
+ free(buf);
+ return Retry;
+ }
+ if(n == 0)
+ break;
+ alarm(10*alarmscale);
+ putcrnl(buf, n);
+ nbytes += n;
+ }
+ }
+ free(buf);
+ if(!filter){
+ if(last != '\n')
+ dBprint("\r\n.\r\n");
+ else
+ dBprint(".\r\n");
+ alarm(10*alarmscale);
+ switch(getreply()){
+ case 2:
+ break;
+ case 5:
+ return Giveup;
+ default:
+ return Retry;
+ }
+ syslog(0, "smtp", "%s sent %d bytes to %s", s_to_c(from),
+ nbytes, s_to_c(toline));/**/
+ }
+ return 0;
+}
+
+/*
+ * we're leaving
+ */
+void
+quit(char *rv)
+{
+ /* 60 minutes to quit */
+ quitting = 1;
+ quitrv = rv;
+ alarm(60*alarmscale);
+ dBprint("QUIT\r\n");
+ getreply();
+ Bterm(&bout);
+ Bterm(&bfile);
+}
+
+/*
+ * read a reply into a string, return the reply code
+ */
+int
+getreply(void)
+{
+ char *line;
+ int rv;
+
+ reply = s_reset(reply);
+ for(;;){
+ line = getcrnl(reply);
+ if(line == 0)
+ return -1;
+ if(!isdigit(line[0]) || !isdigit(line[1]) || !isdigit(line[2]))
+ return -1;
+ if(line[3] != '-')
+ break;
+ }
+ if(debug)
+ Bflush(&berr);
+ rv = atoi(line)/100;
+ return rv;
+}
+void
+addhostdom(String *buf, char *host)
+{
+ s_append(buf, "@");
+ s_append(buf, host);
+}
+
+/*
+ * Convert from `bang' to `source routing' format.
+ *
+ * a.x.y!b.p.o!c!d -> @a.x.y:c!d@b.p.o
+ */
+String *
+bangtoat(char *addr)
+{
+ String *buf;
+ register int i;
+ int j, d;
+ char *field[128];
+
+ /* parse the '!' format address */
+ buf = s_new();
+ for(i = 0; addr; i++){
+ field[i] = addr;
+ addr = strchr(addr, '!');
+ if(addr)
+ *addr++ = 0;
+ }
+ if (i==1) {
+ s_append(buf, field[0]);
+ return buf;
+ }
+
+ /*
+ * count leading domain fields (non-domains don't count)
+ */
+ for(d = 0; d<i-1; d++)
+ if(strchr(field[d], '.')==0)
+ break;
+ /*
+ * if there are more than 1 leading domain elements,
+ * put them in as source routing
+ */
+ if(d > 1){
+ addhostdom(buf, field[0]);
+ for(j=1; j<d-1; j++){
+ s_append(buf, ",");
+ s_append(buf, "@");
+ s_append(buf, field[j]);
+ }
+ s_append(buf, ":");
+ }
+
+ /*
+ * throw in the non-domain elements separated by '!'s
+ */
+ s_append(buf, field[d]);
+ for(j=d+1; j<=i-1; j++) {
+ s_append(buf, "!");
+ s_append(buf, field[j]);
+ }
+ if(d)
+ addhostdom(buf, field[d-1]);
+ return buf;
+}
+
+/*
+ * convert header addresses to @ format.
+ * if the address is a source address, and a domain is specified,
+ * make sure it falls in the domain.
+ */
+String*
+convertheader(String *from)
+{
+ Field *f;
+ Node *p, *lastp;
+ String *a;
+
+ if(!returnable(s_to_c(from))){
+ from = s_new();
+ s_append(from, "Postmaster");
+ addhostdom(from, hostdomain);
+ } else
+ if(strchr(s_to_c(from), '@') == 0){
+ a = username(from);
+ if(a) {
+ s_append(a, " <");
+ s_append(a, s_to_c(from));
+ addhostdom(a, hostdomain);
+ s_append(a, ">");
+ from = a;
+ } else {
+ from = s_copy(s_to_c(from));
+ addhostdom(from, hostdomain);
+ }
+ } else
+ from = s_copy(s_to_c(from));
+ for(f = firstfield; f; f = f->next){
+ lastp = 0;
+ for(p = f->node; p; lastp = p, p = p->next){
+ if(!p->addr)
+ continue;
+ a = bangtoat(s_to_c(p->s));
+ s_free(p->s);
+ if(strchr(s_to_c(a), '@') == 0)
+ addhostdom(a, hostdomain);
+ else if(*s_to_c(a) == '@')
+ a = fixrouteaddr(a, p->next, lastp);
+ p->s = a;
+ }
+ }
+ return from;
+}
+/*
+ * ensure route addr has brackets around it
+ */
+String*
+fixrouteaddr(String *raddr, Node *next, Node *last)
+{
+ String *a;
+
+ if(last && last->c == '<' && next && next->c == '>')
+ return raddr; /* properly formed already */
+
+ a = s_new();
+ s_append(a, "<");
+ s_append(a, s_to_c(raddr));
+ s_append(a, ">");
+ s_free(raddr);
+ return a;
+}
+
+/*
+ * print out the parsed header
+ */
+int
+printheader(void)
+{
+ int n, len;
+ Field *f;
+ Node *p;
+ char *cp;
+ char c[1];
+
+ n = 0;
+ for(f = firstfield; f; f = f->next){
+ for(p = f->node; p; p = p->next){
+ if(p->s)
+ n += dBprint("%s", s_to_c(p->s));
+ else {
+ c[0] = p->c;
+ putcrnl(c, 1);
+ n++;
+ }
+ if(p->white){
+ cp = s_to_c(p->white);
+ len = strlen(cp);
+ putcrnl(cp, len);
+ n += len;
+ }
+ uneaten = p->end;
+ }
+ putcrnl("\n", 1);
+ n++;
+ uneaten++; /* skip newline */
+ }
+ return n;
+}
+
+/*
+ * add a domain onto an name, return the new name
+ */
+char *
+domainify(char *name, char *domain)
+{
+ static String *s;
+ char *p;
+
+ if(domain==0 || strchr(name, '.')!=0)
+ return name;
+
+ s = s_reset(s);
+ s_append(s, name);
+ p = strchr(domain, '.');
+ if(p == 0){
+ s_append(s, ".");
+ p = domain;
+ }
+ s_append(s, p);
+ return s_to_c(s);
+}
+
+/*
+ * print message observing '.' escapes and using \r\n for \n
+ */
+void
+putcrnl(char *cp, int n)
+{
+ int c;
+
+ for(; n; n--, cp++){
+ c = *cp;
+ if(c == '\n')
+ dBputc('\r');
+ else if(c == '.' && last=='\n')
+ dBputc('.');
+ dBputc(c);
+ last = c;
+ }
+}
+
+/*
+ * Get a line including a crnl into a string. Convert crnl into nl.
+ */
+char *
+getcrnl(String *s)
+{
+ int c;
+ int count;
+
+ count = 0;
+ for(;;){
+ c = Bgetc(&bin);
+ if(debug)
+ Bputc(&berr, c);
+ switch(c){
+ case -1:
+ s_append(s, "connection closed unexpectedly by remote system");
+ s_terminate(s);
+ return 0;
+ case '\r':
+ c = Bgetc(&bin);
+ if(c == '\n'){
+ s_putc(s, c);
+ if(debug)
+ Bputc(&berr, c);
+ count++;
+ s_terminate(s);
+ return s->ptr - count;
+ }
+ Bungetc(&bin);
+ s_putc(s, '\r');
+ if(debug)
+ Bputc(&berr, '\r');
+ count++;
+ break;
+ default:
+ s_putc(s, c);
+ count++;
+ break;
+ }
+ }
+ return 0;
+}
+
+/*
+ * print out a parsed date
+ */
+int
+printdate(Node *p)
+{
+ int n, sep = 0;
+
+ n = dBprint("Date: %s,", s_to_c(p->s));
+ for(p = p->next; p; p = p->next){
+ if(p->s){
+ if(sep == 0) {
+ dBputc(' ');
+ n++;
+ }
+ if (p->next)
+ n += dBprint("%s", s_to_c(p->s));
+ else
+ n += dBprint("%s", rewritezone(s_to_c(p->s)));
+ sep = 0;
+ } else {
+ dBputc(p->c);
+ n++;
+ sep = 1;
+ }
+ }
+ n += dBprint("\r\n");
+ return n;
+}
+
+char *
+rewritezone(char *z)
+{
+ int mindiff;
+ char s;
+ Tm *tm;
+ static char x[7];
+
+ tm = localtime(time(0));
+ mindiff = tm->tzoff/60;
+
+ /* if not in my timezone, don't change anything */
+ if(strcmp(tm->zone, z) != 0)
+ return z;
+
+ if(mindiff < 0){
+ s = '-';
+ mindiff = -mindiff;
+ } else
+ s = '+';
+
+ sprint(x, "%c%.2d%.2d", s, mindiff/60, mindiff%60);
+ return x;
+}
+
+/*
+ * stolen from libc/port/print.c
+ */
+#define SIZE 4096
+int
+dBprint(char *fmt, ...)
+{
+ char buf[SIZE], *out;
+ va_list arg;
+ int n;
+
+ va_start(arg, fmt);
+ out = vseprint(buf, buf+SIZE, fmt, arg);
+ va_end(arg);
+ if(debug){
+ Bwrite(&berr, buf, (long)(out-buf));
+ Bflush(&berr);
+ }
+ n = Bwrite(&bout, buf, (long)(out-buf));
+ Bflush(&bout);
+ return n;
+}
+
+int
+dBputc(int x)
+{
+ if(debug)
+ Bputc(&berr, x);
+ return Bputc(&bout, x);
+}
+
+char*
+expand_addr(char* a)
+{
+ Ndb *db;
+ Ndbs s;
+ char *sys, *ret, *proto, *host;
+
+ proto = strtok(a,"!");
+ if ( strcmp(proto,"net") != 0 ) {
+ fprint(2,"unknown proto %s\n",proto);
+ }
+ host = strtok(0,"!");
+ if ( strcmp(host,"$smtp") == 0 ) {
+ sys = sysname();
+ db = ndbopen(unsharp("#9/ndb/local"));
+ host = ndbgetvalue(db, &s, "sys", sys, "smtp", nil);
+ }
+ ret = malloc(strlen(proto)+strlen(host)+2);
+ sprint(ret,"%s!%s",proto,host);
+
+ return ret;
+
+}
diff --git a/src/cmd/upas/smtp/smtp.h b/src/cmd/upas/smtp/smtp.h
new file mode 100644
index 00000000..83619223
--- /dev/null
+++ b/src/cmd/upas/smtp/smtp.h
@@ -0,0 +1,61 @@
+typedef struct Node Node;
+typedef struct Field Field;
+typedef Node *Nodeptr;
+#define YYSTYPE Nodeptr
+
+struct Node {
+ Node *next;
+ int c; /* token type */
+ char addr; /* true if this is an address */
+ String *s; /* string representing token */
+ String *white; /* white space following token */
+ char *start; /* first byte for this token */
+ char *end; /* next byte in input */
+};
+
+struct Field {
+ Field *next;
+ Node *node;
+ int source;
+};
+
+typedef struct DS DS;
+struct DS {
+ /* dist string */
+ char buf[128];
+ char expand[128];
+ char *netdir;
+ char *proto;
+ char *host;
+ char *service;
+};
+
+extern Field *firstfield;
+extern Field *lastfield;
+extern Node *usender;
+extern Node *usys;
+extern Node *udate;
+extern int originator;
+extern int destination;
+extern int date;
+extern int messageid;
+
+Node* anonymous(Node*);
+Node* address(Node*);
+int badfieldname(Node*);
+Node* bang(Node*, Node*);
+Node* colon(Node*, Node*);
+int cistrcmp(char*, char*);
+Node* link2(Node*, Node*);
+Node* link3(Node*, Node*, Node*);
+void freenode(Node*);
+void newfield(Node*, int);
+void freefield(Field*);
+void yyinit(char*, int);
+int yyparse(void);
+int yylex(void);
+String* yywhite(void);
+Node* whiten(Node*);
+void yycleanup(void);
+int mxdial(char*, char*, char*);
+void dial_string_parse(char*, DS*);
diff --git a/src/cmd/upas/smtp/smtpd.c b/src/cmd/upas/smtp/smtpd.c
new file mode 100644
index 00000000..7042c37c
--- /dev/null
+++ b/src/cmd/upas/smtp/smtpd.c
@@ -0,0 +1,1494 @@
+#include "common.h"
+#include "smtpd.h"
+#include "smtp.h"
+#include <ctype.h>
+#include <ip.h>
+#include <ndb.h>
+#include <mp.h>
+#include <libsec.h>
+#include <auth.h>
+#include "../smtp/y.tab.h"
+
+#define DBGMX 1
+
+char *me;
+char *him="";
+char *dom;
+process *pp;
+String *mailer;
+NetConnInfo *nci;
+
+int filterstate = ACCEPT;
+int trusted;
+int logged;
+int rejectcount;
+int hardreject;
+
+Biobuf bin;
+
+int debug;
+int Dflag;
+int fflag;
+int gflag;
+int rflag;
+int sflag;
+int authenticate;
+int authenticated;
+int passwordinclear;
+char *tlscert;
+
+List senders;
+List rcvers;
+
+char pipbuf[ERRMAX];
+char *piperror;
+int pipemsg(int*);
+String* startcmd(void);
+int rejectcheck(void);
+String* mailerpath(char*);
+
+static int
+catchalarm(void *a, char *msg)
+{
+ int rv = 1;
+
+ USED(a);
+
+ /* log alarms but continue */
+ if(strstr(msg, "alarm")){
+ if(senders.first && rcvers.first)
+ syslog(0, "smtpd", "note: %s->%s: %s", s_to_c(senders.first->p),
+ s_to_c(rcvers.first->p), msg);
+ else
+ syslog(0, "smtpd", "note: %s", msg);
+ rv = 0;
+ }
+
+ /* kill the children if there are any */
+ if(pp)
+ syskillpg(pp->pid);
+
+ return rv;
+}
+
+ /* override string error functions to do something reasonable */
+void
+s_error(char *f, char *status)
+{
+ char errbuf[Errlen];
+
+ errbuf[0] = 0;
+ rerrstr(errbuf, sizeof(errbuf));
+ if(f && *f)
+ reply("452 out of memory %s: %s\r\n", f, errbuf);
+ else
+ reply("452 out of memory %s\r\n", errbuf);
+ syslog(0, "smtpd", "++Malloc failure %s [%s]", him, nci->rsys);
+ exits(status);
+}
+
+void
+main(int argc, char **argv)
+{
+ char *p, buf[1024];
+ char *netdir;
+
+ netdir = nil;
+ quotefmtinstall();
+ ARGBEGIN{
+ case 'D':
+ Dflag++;
+ break;
+ case 'd':
+ debug++;
+ break;
+ case 'n': /* log peer ip address */
+ netdir = ARGF();
+ break;
+ case 'f': /* disallow relaying */
+ fflag = 1;
+ break;
+ case 'g':
+ gflag = 1;
+ break;
+ case 'h': /* default domain name */
+ dom = ARGF();
+ break;
+ case 'k': /* prohibited ip address */
+ p = ARGF();
+ if (p)
+ addbadguy(p);
+ break;
+ case 'm': /* set mail command */
+ p = ARGF();
+ if(p)
+ mailer = mailerpath(p);
+ break;
+ case 'r':
+ rflag = 1; /* verify sender's domain */
+ break;
+ case 's': /* save blocked messages */
+ sflag = 1;
+ break;
+ case 'a':
+ authenticate = 1;
+ break;
+ case 'p':
+ passwordinclear = 1;
+ break;
+ case 'c':
+ tlscert = ARGF();
+ break;
+ case 't':
+ fprint(2, "%s: the -t option is no longer supported, see -c\n", argv0);
+ tlscert = "/sys/lib/ssl/smtpd-cert.pem";
+ break;
+ default:
+ fprint(2, "usage: smtpd [-dfhrs] [-n net] [-c cert]\n");
+ exits("usage");
+ }ARGEND;
+
+ nci = getnetconninfo(netdir, 0);
+ if(nci == nil)
+ sysfatal("can't get remote system's address");
+
+ if(mailer == nil)
+ mailer = mailerpath("send");
+
+ if(debug){
+ close(2);
+ snprint(buf, sizeof(buf), "%s/smtpd", UPASLOG);
+ if (open(buf, OWRITE) >= 0) {
+ seek(2, 0, 2);
+ fprint(2, "%d smtpd %s\n", getpid(), thedate());
+ } else
+ debug = 0;
+ }
+ getconf();
+ Binit(&bin, 0, OREAD);
+
+ chdir(UPASLOG);
+ me = sysname_read();
+ if(dom == 0 || dom[0] == 0)
+ dom = domainname_read();
+ if(dom == 0 || dom[0] == 0)
+ dom = me;
+ sayhi();
+ parseinit();
+ /* allow 45 minutes to parse the header */
+ atnotify(catchalarm, 1);
+ alarm(45*60*1000);
+ zzparse();
+ exits(0);
+}
+
+void
+listfree(List *l)
+{
+ Link *lp;
+ Link *next;
+
+ for(lp = l->first; lp; lp = next){
+ next = lp->next;
+ s_free(lp->p);
+ free(lp);
+ }
+ l->first = l->last = 0;
+}
+
+void
+listadd(List *l, String *path)
+{
+ Link *lp;
+
+ lp = (Link *)malloc(sizeof(Link));
+ lp->p = path;
+ lp->next = 0;
+
+ if(l->last)
+ l->last->next = lp;
+ else
+ l->first = lp;
+ l->last = lp;
+}
+
+#define SIZE 4096
+int
+reply(char *fmt, ...)
+{
+ char buf[SIZE], *out;
+ va_list arg;
+ int n;
+
+ va_start(arg, fmt);
+ out = vseprint(buf, buf+SIZE, fmt, arg);
+ va_end(arg);
+ n = (long)(out-buf);
+ if(debug) {
+ seek(2, 0, 2);
+ write(2, buf, n);
+ }
+ write(1, buf, n);
+ return n;
+}
+
+void
+reset(void)
+{
+ if(rejectcheck())
+ return;
+ listfree(&rcvers);
+ listfree(&senders);
+ if(filterstate != DIALUP){
+ logged = 0;
+ filterstate = ACCEPT;
+ }
+ reply("250 ok\r\n");
+}
+
+void
+sayhi(void)
+{
+ reply("220 %s SMTP\r\n", dom);
+}
+
+void
+hello(String *himp, int extended)
+{
+ char **mynames;
+
+ him = s_to_c(himp);
+ syslog(0, "smtpd", "%s from %s as %s", extended ? "ehlo" : "helo", nci->rsys, him);
+ if(rejectcheck())
+ return;
+
+ if(strchr(him, '.') && nci && !trusted && fflag && strcmp(nci->rsys, nci->lsys) != 0){
+ /*
+ * We don't care if he lies about who he is, but it is
+ * not okay to pretend to be us. Many viruses do this,
+ * just parroting back what we say in the greeting.
+ */
+ if(strcmp(him, dom) == 0)
+ goto Liarliar;
+ for(mynames=sysnames_read(); mynames && *mynames; mynames++){
+ if(cistrcmp(*mynames, him) == 0){
+ Liarliar:
+ syslog(0, "smtpd", "Hung up on %s; claimed to be %s",
+ nci->rsys, him);
+ reply("554 Liar!\r\n");
+ exits("client pretended to be us");
+ return;
+ }
+ }
+ }
+ /*
+ * it is never acceptable to claim to be "localhost",
+ * "localhost.localdomain" or "localhost.example.com"; only spammers
+ * do this. it should be unacceptable to claim any string that doesn't
+ * look like a domain name (e.g., has at least one dot in it), but
+ * Microsoft mail software gets this wrong.
+ */
+ if (strcmp(him, "localhost") == 0 ||
+ strcmp(him, "localhost.localdomain") == 0 ||
+ strcmp(him, "localhost.example.com") == 0)
+ goto Liarliar;
+ if(strchr(him, '.') == 0 && nci != nil && strchr(nci->rsys, '.') != nil)
+ him = nci->rsys;
+
+ if(Dflag)
+ sleep(15*1000);
+ reply("250%c%s you are %s\r\n", extended ? '-' : ' ', dom, him);
+ if (extended) {
+ if(tlscert != nil)
+ reply("250-STARTTLS\r\n");
+ if (passwordinclear)
+ reply("250 AUTH CRAM-MD5 PLAIN LOGIN\r\n");
+ else
+ reply("250 AUTH CRAM-MD5\r\n");
+ }
+}
+
+void
+sender(String *path)
+{
+ String *s;
+ static char *lastsender;
+
+ if(rejectcheck())
+ return;
+ if (authenticate && !authenticated) {
+ rejectcount++;
+ reply("530 Authentication required\r\n");
+ return;
+ }
+ if(him == 0 || *him == 0){
+ rejectcount++;
+ reply("503 Start by saying HELO, please.\r\n", s_to_c(path));
+ return;
+ }
+
+ /* don't add the domain onto black holes or we will loop */
+ if(strchr(s_to_c(path), '!') == 0 && strcmp(s_to_c(path), "/dev/null") != 0){
+ s = s_new();
+ s_append(s, him);
+ s_append(s, "!");
+ s_append(s, s_to_c(path));
+ s_terminate(s);
+ s_free(path);
+ path = s;
+ }
+ if(shellchars(s_to_c(path))){
+ rejectcount++;
+ reply("503 Bad character in sender address %s.\r\n", s_to_c(path));
+ return;
+ }
+
+ /*
+ * if the last sender address resulted in a rejection because the sending
+ * domain didn't exist and this sender has the same domain, reject immediately.
+ */
+ if(lastsender){
+ if (strncmp(lastsender, s_to_c(path), strlen(lastsender)) == 0){
+ filterstate = REFUSED;
+ rejectcount++;
+ reply("554 Sender domain must exist: %s\r\n", s_to_c(path));
+ return;
+ }
+ free(lastsender); /* different sender domain */
+ lastsender = 0;
+ }
+
+ /*
+ * see if this ip address, domain name, user name or account is blocked
+ */
+ filterstate = blocked(path);
+
+ logged = 0;
+ listadd(&senders, path);
+ reply("250 sender is %s\r\n", s_to_c(path));
+}
+
+enum { Rcpt, Domain, Ntoks };
+
+typedef struct Sender Sender;
+struct Sender {
+ Sender *next;
+ char *rcpt;
+ char *domain;
+};
+static Sender *sendlist, *sendlast;
+static uchar rsysip[IPaddrlen];
+
+static int
+rdsenders(void)
+{
+ int lnlen, nf, ok = 1;
+ char *line, *senderfile;
+ char *toks[Ntoks];
+ Biobuf *sf;
+ Sender *snd;
+ static int beenhere = 0;
+
+ if (beenhere)
+ return 1;
+ beenhere = 1;
+
+ fmtinstall('I', eipfmt);
+ parseip(rsysip, nci->rsys);
+
+ /*
+ * we're sticking with a system-wide sender list because
+ * per-user lists would require fully resolving recipient
+ * addresses to determine which users they correspond to
+ * (barring syntactic conventions).
+ */
+ senderfile = smprint("%s/senders", UPASLIB);
+ sf = Bopen(senderfile, OREAD);
+ free(senderfile);
+ if (sf == nil)
+ return 1;
+ while ((line = Brdline(sf, '\n')) != nil) {
+ if (line[0] == '#' || line[0] == '\n')
+ continue;
+ lnlen = Blinelen(sf);
+ line[lnlen-1] = '\0'; /* clobber newline */
+ nf = tokenize(line, toks, nelem(toks));
+ if (nf != nelem(toks))
+ continue; /* malformed line */
+
+ snd = malloc(sizeof *snd);
+ if (snd == nil)
+ sysfatal("out of memory: %r");
+ memset(snd, 0, sizeof *snd);
+ snd->next = nil;
+
+ if (sendlast == nil)
+ sendlist = snd;
+ else
+ sendlast->next = snd;
+ sendlast = snd;
+ snd->rcpt = strdup(toks[Rcpt]);
+ snd->domain = strdup(toks[Domain]);
+ }
+ Bterm(sf);
+ return ok;
+}
+
+/*
+ * read (recipient, sender's DNS) pairs from /mail/lib/senders.
+ * Only allow mail to recipient from any of sender's IPs.
+ * A recipient not mentioned in the file is always permitted.
+ */
+static int
+senderok(char *rcpt)
+{
+ int mentioned = 0, matched = 0;
+ uchar dnsip[IPaddrlen];
+ Sender *snd;
+ Ndbtuple *nt, *next, *first;
+
+ rdsenders();
+ for (snd = sendlist; snd != nil; snd = snd->next) {
+ if (strcmp(rcpt, snd->rcpt) != 0)
+ continue;
+ /*
+ * see if this domain's ips match nci->rsys.
+ * if not, perhaps a later entry's domain will.
+ */
+ mentioned = 1;
+ if (parseip(dnsip, snd->domain) != -1 &&
+ memcmp(rsysip, dnsip, IPaddrlen) == 0)
+ return 1;
+ /*
+ * NB: nt->line links form a circular list(!).
+ * we need to make one complete pass over it to free it all.
+ */
+ first = nt = dnsquery(nci->root, snd->domain, "ip");
+ if (first == nil)
+ continue;
+ do {
+ if (strcmp(nt->attr, "ip") == 0 &&
+ parseip(dnsip, nt->val) != -1 &&
+ memcmp(rsysip, dnsip, IPaddrlen) == 0)
+ matched = 1;
+ next = nt->line;
+ free(nt);
+ nt = next;
+ } while (nt != first);
+ }
+ if (matched)
+ return 1;
+ else
+ return !mentioned;
+}
+
+void
+receiver(String *path)
+{
+ char *sender, *rcpt;
+
+ if(rejectcheck())
+ return;
+ if(him == 0 || *him == 0){
+ rejectcount++;
+ reply("503 Start by saying HELO, please\r\n");
+ return;
+ }
+ if(senders.last)
+ sender = s_to_c(senders.last->p);
+ else
+ sender = "<unknown>";
+
+ if(!recipok(s_to_c(path))){
+ rejectcount++;
+ syslog(0, "smtpd", "Disallowed %s (%s/%s) to blocked name %s",
+ sender, him, nci->rsys, s_to_c(path));
+ reply("550 %s ... user unknown\r\n", s_to_c(path));
+ return;
+ }
+ rcpt = s_to_c(path);
+ if (!senderok(rcpt)) {
+ rejectcount++;
+ syslog(0, "smtpd", "Disallowed sending IP of %s (%s/%s) to %s",
+ sender, him, nci->rsys, rcpt);
+ reply("550 %s ... sending system not allowed\r\n", rcpt);
+ return;
+ }
+
+ logged = 0;
+ /* forwarding() can modify 'path' on loopback request */
+ if(filterstate == ACCEPT && (fflag && !authenticated) && forwarding(path)) {
+ syslog(0, "smtpd", "Bad Forward %s (%s/%s) (%s)",
+ s_to_c(senders.last->p), him, nci->rsys, s_to_c(path));
+ rejectcount++;
+ reply("550 we don't relay. send to your-path@[] for loopback.\r\n");
+ return;
+ }
+ listadd(&rcvers, path);
+ reply("250 receiver is %s\r\n", s_to_c(path));
+}
+
+void
+quit(void)
+{
+ reply("221 Successful termination\r\n");
+ close(0);
+ exits(0);
+}
+
+void
+turn(void)
+{
+ if(rejectcheck())
+ return;
+ reply("502 TURN unimplemented\r\n");
+}
+
+void
+noop(void)
+{
+ if(rejectcheck())
+ return;
+ reply("250 Stop wasting my time!\r\n");
+}
+
+void
+help(String *cmd)
+{
+ if(rejectcheck())
+ return;
+ if(cmd)
+ s_free(cmd);
+ reply("250 Read rfc821 and stop wasting my time\r\n");
+}
+
+void
+verify(String *path)
+{
+ char *p, *q;
+ char *av[4];
+
+ if(rejectcheck())
+ return;
+ if(shellchars(s_to_c(path))){
+ reply("503 Bad character in address %s.\r\n", s_to_c(path));
+ return;
+ }
+ av[0] = s_to_c(mailer);
+ av[1] = "-x";
+ av[2] = s_to_c(path);
+ av[3] = 0;
+
+ pp = noshell_proc_start(av, (stream *)0, outstream(), (stream *)0, 1, 0);
+ if (pp == 0) {
+ reply("450 We're busy right now, try later\r\n");
+ return;
+ }
+
+ p = Brdline(pp->std[1]->fp, '\n');
+ if(p == 0){
+ reply("550 String does not match anything.\r\n");
+ } else {
+ p[Blinelen(pp->std[1]->fp)-1] = 0;
+ if(strchr(p, ':'))
+ reply("550 String does not match anything.\r\n");
+ else{
+ q = strrchr(p, '!');
+ if(q)
+ p = q+1;
+ reply("250 %s <%s@%s>\r\n", s_to_c(path), p, dom);
+ }
+ }
+ proc_wait(pp);
+ proc_free(pp);
+ pp = 0;
+}
+
+/*
+ * get a line that ends in crnl or cr, turn terminating crnl into a nl
+ *
+ * return 0 on EOF
+ */
+static int
+getcrnl(String *s, Biobuf *fp)
+{
+ int c;
+
+ for(;;){
+ c = Bgetc(fp);
+ if(debug) {
+ seek(2, 0, 2);
+ fprint(2, "%c", c);
+ }
+ switch(c){
+ case -1:
+ goto out;
+ case '\r':
+ c = Bgetc(fp);
+ if(c == '\n'){
+ if(debug) {
+ seek(2, 0, 2);
+ fprint(2, "%c", c);
+ }
+ s_putc(s, '\n');
+ goto out;
+ }
+ Bungetc(fp);
+ s_putc(s, '\r');
+ break;
+ case '\n':
+ s_putc(s, c);
+ goto out;
+ default:
+ s_putc(s, c);
+ break;
+ }
+ }
+out:
+ s_terminate(s);
+ return s_len(s);
+}
+
+void
+logcall(int nbytes)
+{
+ Link *l;
+ String *to, *from;
+
+ to = s_new();
+ from = s_new();
+ for(l = senders.first; l; l = l->next){
+ if(l != senders.first)
+ s_append(from, ", ");
+ s_append(from, s_to_c(l->p));
+ }
+ for(l = rcvers.first; l; l = l->next){
+ if(l != rcvers.first)
+ s_append(to, ", ");
+ s_append(to, s_to_c(l->p));
+ }
+ syslog(0, "smtpd", "[%s/%s] %s sent %d bytes to %s", him, nci->rsys,
+ s_to_c(from), nbytes, s_to_c(to));
+ s_free(to);
+ s_free(from);
+}
+
+static void
+logmsg(char *action)
+{
+ Link *l;
+
+ if(logged)
+ return;
+
+ logged = 1;
+ for(l = rcvers.first; l; l = l->next)
+ syslog(0, "smtpd", "%s %s (%s/%s) (%s)", action,
+ s_to_c(senders.last->p), him, nci->rsys, s_to_c(l->p));
+}
+
+static int
+optoutall(int filterstate)
+{
+ Link *l;
+
+ switch(filterstate){
+ case ACCEPT:
+ case TRUSTED:
+ return filterstate;
+ }
+
+ for(l = rcvers.first; l; l = l->next)
+ if(!optoutofspamfilter(s_to_c(l->p)))
+ return filterstate;
+
+ return ACCEPT;
+}
+
+String*
+startcmd(void)
+{
+ int n;
+ Link *l;
+ char **av;
+ String *cmd;
+ char *filename;
+
+ /*
+ * ignore the filterstate if the all the receivers prefer it.
+ */
+ filterstate = optoutall(filterstate);
+
+ switch (filterstate){
+ case BLOCKED:
+ case DELAY:
+ rejectcount++;
+ logmsg("Blocked");
+ filename = dumpfile(s_to_c(senders.last->p));
+ cmd = s_new();
+ s_append(cmd, "cat > ");
+ s_append(cmd, filename);
+ pp = proc_start(s_to_c(cmd), instream(), 0, outstream(), 0, 0);
+ break;
+ case DIALUP:
+ logmsg("Dialup");
+ rejectcount++;
+ reply("554 We don't accept mail from dial-up ports.\r\n");
+ /*
+ * we could exit here, because we're never going to accept mail from this
+ * ip address, but it's unclear that RFC821 allows that. Instead we set
+ * the hardreject flag and go stupid.
+ */
+ hardreject = 1;
+ return 0;
+ case DENIED:
+ logmsg("Denied");
+ rejectcount++;
+ reply("554-We don't accept mail from %s.\r\n", s_to_c(senders.last->p));
+ reply("554 Contact postmaster@%s for more information.\r\n", dom);
+ return 0;
+ case REFUSED:
+ logmsg("Refused");
+ rejectcount++;
+ reply("554 Sender domain must exist: %s\r\n", s_to_c(senders.last->p));
+ return 0;
+ default:
+ case NONE:
+ logmsg("Confused");
+ rejectcount++;
+ reply("554-We have had an internal mailer error classifying your message.\r\n");
+ reply("554-Filterstate is %d\r\n", filterstate);
+ reply("554 Contact postmaster@%s for more information.\r\n", dom);
+ return 0;
+ case ACCEPT:
+ case TRUSTED:
+ /*
+ * now that all other filters have been passed,
+ * do grey-list processing.
+ */
+ if(gflag)
+ vfysenderhostok();
+
+ /*
+ * set up mail command
+ */
+ cmd = s_clone(mailer);
+ n = 3;
+ for(l = rcvers.first; l; l = l->next)
+ n++;
+ av = malloc(n*sizeof(char*));
+ if(av == nil){
+ reply("450 We're busy right now, try later\n");
+ s_free(cmd);
+ return 0;
+ }
+
+ n = 0;
+ av[n++] = s_to_c(cmd);
+ av[n++] = "-r";
+ for(l = rcvers.first; l; l = l->next)
+ av[n++] = s_to_c(l->p);
+ av[n] = 0;
+ /*
+ * start mail process
+ */
+ pp = noshell_proc_start(av, instream(), outstream(), outstream(), 0, 0);
+ free(av);
+ break;
+ }
+ if(pp == 0) {
+ reply("450 We're busy right now, try later\n");
+ s_free(cmd);
+ return 0;
+ }
+ return cmd;
+}
+
+/*
+ * print out a header line, expanding any domainless addresses into
+ * address@him
+ */
+char*
+bprintnode(Biobuf *b, Node *p)
+{
+ if(p->s){
+ if(p->addr && strchr(s_to_c(p->s), '@') == nil){
+ if(Bprint(b, "%s@%s", s_to_c(p->s), him) < 0)
+ return nil;
+ } else {
+ if(Bwrite(b, s_to_c(p->s), s_len(p->s)) < 0)
+ return nil;
+ }
+ }else{
+ if(Bputc(b, p->c) < 0)
+ return nil;
+ }
+ if(p->white)
+ if(Bwrite(b, s_to_c(p->white), s_len(p->white)) < 0)
+ return nil;
+ return p->end+1;
+}
+
+static String*
+getaddr(Node *p)
+{
+ for(; p; p = p->next)
+ if(p->s && p->addr)
+ return p->s;
+ return nil;
+}
+
+/*
+ * add waring headers of the form
+ * X-warning: <reason>
+ * for any headers that looked like they might be forged.
+ *
+ * return byte count of new headers
+ */
+static int
+forgedheaderwarnings(void)
+{
+ int nbytes;
+ Field *f;
+
+ nbytes = 0;
+
+ /* warn about envelope sender */
+ if(strcmp(s_to_c(senders.last->p), "/dev/null") != 0 && masquerade(senders.last->p, nil))
+ nbytes += Bprint(pp->std[0]->fp, "X-warning: suspect envelope domain\n");
+
+ /*
+ * check Sender: field. If it's OK, ignore the others because this is an
+ * exploded mailing list.
+ */
+ for(f = firstfield; f; f = f->next){
+ if(f->node->c == SENDER){
+ if(masquerade(getaddr(f->node), him))
+ nbytes += Bprint(pp->std[0]->fp, "X-warning: suspect Sender: domain\n");
+ else
+ return nbytes;
+ }
+ }
+
+ /* check From: */
+ for(f = firstfield; f; f = f->next){
+ if(f->node->c == FROM && masquerade(getaddr(f->node), him))
+ nbytes += Bprint(pp->std[0]->fp, "X-warning: suspect From: domain\n");
+ }
+ return nbytes;
+}
+
+/*
+ * pipe message to mailer with the following transformations:
+ * - change \r\n into \n.
+ * - add sender's domain to any addrs with no domain
+ * - add a From: if none of From:, Sender:, or Replyto: exists
+ * - add a Received: line
+ */
+int
+pipemsg(int *byteswritten)
+{
+ int status;
+ char *cp;
+ String *line;
+ String *hdr;
+ int n, nbytes;
+ int sawdot;
+ Field *f;
+ Node *p;
+ Link *l;
+
+ pipesig(&status); /* set status to 1 on write to closed pipe */
+ sawdot = 0;
+ status = 0;
+
+ /*
+ * add a 'From ' line as envelope
+ */
+ nbytes = 0;
+ nbytes += Bprint(pp->std[0]->fp, "From %s %s remote from \n",
+ s_to_c(senders.first->p), thedate());
+
+ /*
+ * add our own Received: stamp
+ */
+ nbytes += Bprint(pp->std[0]->fp, "Received: from %s ", him);
+ if(nci->rsys)
+ nbytes += Bprint(pp->std[0]->fp, "([%s]) ", nci->rsys);
+ nbytes += Bprint(pp->std[0]->fp, "by %s; %s\n", me, thedate());
+
+ /*
+ * read first 16k obeying '.' escape. we're assuming
+ * the header will all be there.
+ */
+ line = s_new();
+ hdr = s_new();
+ while(sawdot == 0 && s_len(hdr) < 16*1024){
+ n = getcrnl(s_reset(line), &bin);
+
+ /* eof or error ends the message */
+ if(n <= 0)
+ break;
+
+ /* a line with only a '.' ends the message */
+ cp = s_to_c(line);
+ if(n == 2 && *cp == '.' && *(cp+1) == '\n'){
+ sawdot = 1;
+ break;
+ }
+
+ s_append(hdr, *cp == '.' ? cp+1 : cp);
+ }
+
+ /*
+ * parse header
+ */
+ yyinit(s_to_c(hdr), s_len(hdr));
+ yyparse();
+
+ /*
+ * Look for masquerades. Let Sender: trump From: to allow mailing list
+ * forwarded messages.
+ */
+ if(fflag)
+ nbytes += forgedheaderwarnings();
+
+ /*
+ * add an orginator and/or destination if either is missing
+ */
+ if(originator == 0){
+ if(senders.last == nil)
+ Bprint(pp->std[0]->fp, "From: /dev/null@%s\n", him);
+ else
+ Bprint(pp->std[0]->fp, "From: %s\n", s_to_c(senders.last->p));
+ }
+ if(destination == 0){
+ Bprint(pp->std[0]->fp, "To: ");
+ for(l = rcvers.first; l; l = l->next){
+ if(l != rcvers.first)
+ Bprint(pp->std[0]->fp, ", ");
+ Bprint(pp->std[0]->fp, "%s", s_to_c(l->p));
+ }
+ Bprint(pp->std[0]->fp, "\n");
+ }
+
+ /*
+ * add sender's domain to any domainless addresses
+ * (to avoid forging local addresses)
+ */
+ cp = s_to_c(hdr);
+ for(f = firstfield; cp != nil && f; f = f->next){
+ for(p = f->node; cp != 0 && p; p = p->next)
+ cp = bprintnode(pp->std[0]->fp, p);
+ if(status == 0 && Bprint(pp->std[0]->fp, "\n") < 0){
+ piperror = "write error";
+ status = 1;
+ }
+ }
+ if(cp == nil){
+ piperror = "sender domain";
+ status = 1;
+ }
+
+ /* write anything we read following the header */
+ if(status == 0 && Bwrite(pp->std[0]->fp, cp, s_to_c(hdr) + s_len(hdr) - cp) < 0){
+ piperror = "write error 2";
+ status = 1;
+ }
+ s_free(hdr);
+
+ /*
+ * pass rest of message to mailer. take care of '.'
+ * escapes.
+ */
+ while(sawdot == 0){
+ n = getcrnl(s_reset(line), &bin);
+
+ /* eof or error ends the message */
+ if(n <= 0)
+ break;
+
+ /* a line with only a '.' ends the message */
+ cp = s_to_c(line);
+ if(n == 2 && *cp == '.' && *(cp+1) == '\n'){
+ sawdot = 1;
+ break;
+ }
+ nbytes += n;
+ if(status == 0 && Bwrite(pp->std[0]->fp, *cp == '.' ? cp+1 : cp, n) < 0){
+ piperror = "write error 3";
+ status = 1;
+ }
+ }
+ s_free(line);
+ if(sawdot == 0){
+ /* message did not terminate normally */
+ snprint(pipbuf, sizeof pipbuf, "network eof: %r");
+ piperror = pipbuf;
+ syskillpg(pp->pid);
+ status = 1;
+ }
+
+ if(status == 0 && Bflush(pp->std[0]->fp) < 0){
+ piperror = "write error 4";
+ status = 1;
+ }
+ stream_free(pp->std[0]);
+ pp->std[0] = 0;
+ *byteswritten = nbytes;
+ pipesigoff();
+ if(status && !piperror)
+ piperror = "write on closed pipe";
+ return status;
+}
+
+char*
+firstline(char *x)
+{
+ static char buf[128];
+ char *p;
+
+ strncpy(buf, x, sizeof(buf));
+ buf[sizeof(buf)-1] = 0;
+ p = strchr(buf, '\n');
+ if(p)
+ *p = 0;
+ return buf;
+}
+
+int
+sendermxcheck(void)
+{
+ char *cp, *senddom, *user;
+ char *who;
+ int pid;
+ Waitmsg *w;
+
+ who = s_to_c(senders.first->p);
+ if(strcmp(who, "/dev/null") == 0){
+ /* /dev/null can only send to one rcpt at a time */
+ if(rcvers.first != rcvers.last){
+ werrstr("rejected: /dev/null sending to multiple recipients");
+ return -1;
+ }
+ return 0;
+ }
+
+ if(access("/mail/lib/validatesender", AEXEC) < 0)
+ return 0;
+
+ senddom = strdup(who);
+ if((cp = strchr(senddom, '!')) == nil){
+ werrstr("rejected: domainless sender %s", who);
+ free(senddom);
+ return -1;
+ }
+ *cp++ = 0;
+ user = cp;
+
+ switch(pid = fork()){
+ case -1:
+ werrstr("deferred: fork: %r");
+ return -1;
+ case 0:
+ /*
+ * Could add an option with the remote IP address
+ * to allow validatesender to implement SPF eventually.
+ */
+ execl("/mail/lib/validatesender", "validatesender",
+ "-n", nci->root, senddom, user, nil);
+ _exits("exec validatesender: %r");
+ default:
+ break;
+ }
+
+ free(senddom);
+ w = wait();
+ if(w == nil){
+ werrstr("deferred: wait failed: %r");
+ return -1;
+ }
+ if(w->pid != pid){
+ werrstr("deferred: wait returned wrong pid %d != %d", w->pid, pid);
+ free(w);
+ return -1;
+ }
+ if(w->msg[0] == 0){
+ free(w);
+ return 0;
+ }
+ /*
+ * skip over validatesender 143123132: prefix from rc.
+ */
+ cp = strchr(w->msg, ':');
+ if(cp && *(cp+1) == ' ')
+ werrstr("%s", cp+2);
+ else
+ werrstr("%s", w->msg);
+ free(w);
+ return -1;
+}
+
+void
+data(void)
+{
+ String *cmd;
+ String *err;
+ int status, nbytes;
+ char *cp, *ep;
+ char errx[ERRMAX];
+ Link *l;
+
+ if(rejectcheck())
+ return;
+ if(senders.last == 0){
+ reply("503 Data without MAIL FROM:\r\n");
+ rejectcount++;
+ return;
+ }
+ if(rcvers.last == 0){
+ reply("503 Data without RCPT TO:\r\n");
+ rejectcount++;
+ return;
+ }
+ if(sendermxcheck()){
+ rerrstr(errx, sizeof errx);
+ if(strncmp(errx, "rejected:", 9) == 0)
+ reply("554 %s\r\n", errx);
+ else
+ reply("450 %s\r\n", errx);
+ for(l=rcvers.first; l; l=l->next)
+ syslog(0, "smtpd", "[%s/%s] %s -> %s sendercheck: %s",
+ him, nci->rsys, s_to_c(senders.first->p),
+ s_to_c(l->p), errx);
+ rejectcount++;
+ return;
+ }
+
+ cmd = startcmd();
+ if(cmd == 0)
+ return;
+
+ reply("354 Input message; end with <CRLF>.<CRLF>\r\n");
+
+ /*
+ * allow 145 more minutes to move the data
+ */
+ alarm(145*60*1000);
+
+ status = pipemsg(&nbytes);
+
+ /*
+ * read any error messages
+ */
+ err = s_new();
+ while(s_read_line(pp->std[2]->fp, err))
+ ;
+
+ alarm(0);
+ atnotify(catchalarm, 0);
+
+ status |= proc_wait(pp);
+ if(debug){
+ seek(2, 0, 2);
+ fprint(2, "%d status %ux\n", getpid(), status);
+ if(*s_to_c(err))
+ fprint(2, "%d error %s\n", getpid(), s_to_c(err));
+ }
+
+ /*
+ * if process terminated abnormally, send back error message
+ */
+ if(status){
+ int code;
+
+ if(strstr(s_to_c(err), "mail refused")){
+ syslog(0, "smtpd", "++[%s/%s] %s %s refused: %s", him, nci->rsys,
+ s_to_c(senders.first->p), s_to_c(cmd), firstline(s_to_c(err)));
+ code = 554;
+ } else {
+ syslog(0, "smtpd", "++[%s/%s] %s %s %s%s%sreturned %#q %s", him, nci->rsys,
+ s_to_c(senders.first->p), s_to_c(cmd),
+ piperror ? "error during pipemsg: " : "",
+ piperror ? piperror : "",
+ piperror ? "; " : "",
+ pp->waitmsg->msg, firstline(s_to_c(err)));
+ code = 450;
+ }
+ for(cp = s_to_c(err); ep = strchr(cp, '\n'); cp = ep){
+ *ep++ = 0;
+ reply("%d-%s\r\n", code, cp);
+ }
+ reply("%d mail process terminated abnormally\r\n", code);
+ } else {
+ if(filterstate == BLOCKED)
+ reply("554 we believe this is spam. we don't accept it.\r\n");
+ else
+ if(filterstate == DELAY)
+ reply("554 There will be a delay in delivery of this message.\r\n");
+ else {
+ reply("250 sent\r\n");
+ logcall(nbytes);
+ }
+ }
+ proc_free(pp);
+ pp = 0;
+ s_free(cmd);
+ s_free(err);
+
+ listfree(&senders);
+ listfree(&rcvers);
+}
+
+/*
+ * when we have blocked a transaction based on IP address, there is nothing
+ * that the sender can do to convince us to take the message. after the
+ * first rejection, some spammers continually RSET and give a new MAIL FROM:
+ * filling our logs with rejections. rejectcheck() limits the retries and
+ * swiftly rejects all further commands after the first 500-series message
+ * is issued.
+ */
+int
+rejectcheck(void)
+{
+
+ if(rejectcount > MAXREJECTS){
+ syslog(0, "smtpd", "Rejected (%s/%s)", him, nci->rsys);
+ reply("554 too many errors. transaction failed.\r\n");
+ exits("errcount");
+ }
+ if(hardreject){
+ rejectcount++;
+ reply("554 We don't accept mail from dial-up ports.\r\n");
+ }
+ return hardreject;
+}
+
+/*
+ * create abs path of the mailer
+ */
+String*
+mailerpath(char *p)
+{
+ String *s;
+
+ if(p == nil)
+ return nil;
+ if(*p == '/')
+ return s_copy(p);
+ s = s_new();
+ s_append(s, UPASBIN);
+ s_append(s, "/");
+ s_append(s, p);
+ return s;
+}
+
+String *
+s_dec64(String *sin)
+{
+ String *sout;
+ int lin, lout;
+ lin = s_len(sin);
+
+ /*
+ * if the string is coming from smtpd.y, it will have no nl.
+ * if it is coming from getcrnl below, it will have an nl.
+ */
+ if (*(s_to_c(sin)+lin-1) == '\n')
+ lin--;
+ sout = s_newalloc(lin+1);
+ lout = dec64((uchar *)s_to_c(sout), lin, s_to_c(sin), lin);
+ if (lout < 0) {
+ s_free(sout);
+ return nil;
+ }
+ sout->ptr = sout->base + lout;
+ s_terminate(sout);
+ return sout;
+}
+
+void
+starttls(void)
+{
+ uchar *cert;
+ int certlen, fd;
+ TLSconn *conn;
+
+ conn = mallocz(sizeof *conn, 1);
+ cert = readcert(tlscert, &certlen);
+ if (conn == nil || cert == nil) {
+ if (conn != nil)
+ free(conn);
+ reply("454 TLS not available\r\n");
+ return;
+ }
+ reply("220 Go ahead make my day\r\n");
+ conn->cert = cert;
+ conn->certlen = certlen;
+ fd = tlsServer(Bfildes(&bin), conn);
+ if (fd < 0) {
+ free(cert);
+ free(conn);
+ syslog(0, "smtpd", "TLS start-up failed with %s", him);
+
+ /* force the client to hang up */
+ close(Bfildes(&bin)); /* probably fd 0 */
+ close(1);
+ exits("tls failed");
+ }
+ Bterm(&bin);
+ Binit(&bin, fd, OREAD);
+ if (dup(fd, 1) < 0)
+ fprint(2, "dup of %d failed: %r\n", fd);
+ passwordinclear = 1;
+ syslog(0, "smtpd", "started TLS with %s", him);
+}
+
+void
+auth(String *mech, String *resp)
+{
+ Chalstate *chs = nil;
+ AuthInfo *ai = nil;
+ String *s_resp1_64 = nil;
+ String *s_resp2_64 = nil;
+ String *s_resp1 = nil;
+ String *s_resp2 = nil;
+ char *scratch = nil;
+ char *user, *pass;
+
+ if (rejectcheck())
+ goto bomb_out;
+
+ syslog(0, "smtpd", "auth(%s, %s) from %s", s_to_c(mech),
+ "(protected)", him);
+
+ if (authenticated) {
+ bad_sequence:
+ rejectcount++;
+ reply("503 Bad sequence of commands\r\n");
+ goto bomb_out;
+ }
+ if (cistrcmp(s_to_c(mech), "plain") == 0) {
+
+ if (!passwordinclear) {
+ rejectcount++;
+ reply("538 Encryption required for requested authentication mechanism\r\n");
+ goto bomb_out;
+ }
+ s_resp1_64 = resp;
+ if (s_resp1_64 == nil) {
+ reply("334 \r\n");
+ s_resp1_64 = s_new();
+ if (getcrnl(s_resp1_64, &bin) <= 0) {
+ goto bad_sequence;
+ }
+ }
+ s_resp1 = s_dec64(s_resp1_64);
+ if (s_resp1 == nil) {
+ rejectcount++;
+ reply("501 Cannot decode base64\r\n");
+ goto bomb_out;
+ }
+ memset(s_to_c(s_resp1_64), 'X', s_len(s_resp1_64));
+ user = (s_to_c(s_resp1) + strlen(s_to_c(s_resp1)) + 1);
+ pass = user + (strlen(user) + 1);
+ ai = auth_userpasswd(user, pass);
+ authenticated = ai != nil;
+ memset(pass, 'X', strlen(pass));
+ goto windup;
+ }
+ else if (cistrcmp(s_to_c(mech), "login") == 0) {
+
+ if (!passwordinclear) {
+ rejectcount++;
+ reply("538 Encryption required for requested authentication mechanism\r\n");
+ goto bomb_out;
+ }
+ if (resp == nil) {
+ reply("334 VXNlcm5hbWU6\r\n");
+ s_resp1_64 = s_new();
+ if (getcrnl(s_resp1_64, &bin) <= 0)
+ goto bad_sequence;
+ }
+ reply("334 UGFzc3dvcmQ6\r\n");
+ s_resp2_64 = s_new();
+ if (getcrnl(s_resp2_64, &bin) <= 0)
+ goto bad_sequence;
+ s_resp1 = s_dec64(s_resp1_64);
+ s_resp2 = s_dec64(s_resp2_64);
+ memset(s_to_c(s_resp2_64), 'X', s_len(s_resp2_64));
+ if (s_resp1 == nil || s_resp2 == nil) {
+ rejectcount++;
+ reply("501 Cannot decode base64\r\n");
+ goto bomb_out;
+ }
+ ai = auth_userpasswd(s_to_c(s_resp1), s_to_c(s_resp2));
+ authenticated = ai != nil;
+ memset(s_to_c(s_resp2), 'X', s_len(s_resp2));
+ windup:
+ if (authenticated)
+ reply("235 Authentication successful\r\n");
+ else {
+ rejectcount++;
+ reply("535 Authentication failed\r\n");
+ }
+ goto bomb_out;
+ }
+ else if (cistrcmp(s_to_c(mech), "cram-md5") == 0) {
+ char *resp;
+ int chal64n;
+ char *t;
+
+ chs = auth_challenge("proto=cram role=server");
+ if (chs == nil) {
+ rejectcount++;
+ reply("501 Couldn't get CRAM-MD5 challenge\r\n");
+ goto bomb_out;
+ }
+ scratch = malloc(chs->nchal * 2 + 1);
+ chal64n = enc64(scratch, chs->nchal * 2, (uchar *)chs->chal, chs->nchal);
+ scratch[chal64n] = 0;
+ reply("334 %s\r\n", scratch);
+ s_resp1_64 = s_new();
+ if (getcrnl(s_resp1_64, &bin) <= 0)
+ goto bad_sequence;
+ s_resp1 = s_dec64(s_resp1_64);
+ if (s_resp1 == nil) {
+ rejectcount++;
+ reply("501 Cannot decode base64\r\n");
+ goto bomb_out;
+ }
+ /* should be of form <user><space><response> */
+ resp = s_to_c(s_resp1);
+ t = strchr(resp, ' ');
+ if (t == nil) {
+ rejectcount++;
+ reply("501 Poorly formed CRAM-MD5 response\r\n");
+ goto bomb_out;
+ }
+ *t++ = 0;
+ chs->user = resp;
+ chs->resp = t;
+ chs->nresp = strlen(t);
+ ai = auth_response(chs);
+ authenticated = ai != nil;
+ goto windup;
+ }
+ rejectcount++;
+ reply("501 Unrecognised authentication type %s\r\n", s_to_c(mech));
+bomb_out:
+ if (ai)
+ auth_freeAI(ai);
+ if (chs)
+ auth_freechal(chs);
+ if (scratch)
+ free(scratch);
+ if (s_resp1)
+ s_free(s_resp1);
+ if (s_resp2)
+ s_free(s_resp2);
+ if (s_resp1_64)
+ s_free(s_resp1_64);
+ if (s_resp2_64)
+ s_free(s_resp2_64);
+}
diff --git a/src/cmd/upas/smtp/smtpd.h b/src/cmd/upas/smtp/smtpd.h
new file mode 100644
index 00000000..3a8e60e2
--- /dev/null
+++ b/src/cmd/upas/smtp/smtpd.h
@@ -0,0 +1,68 @@
+enum {
+ ACCEPT = 0,
+ REFUSED,
+ DENIED,
+ DIALUP,
+ BLOCKED,
+ DELAY,
+ TRUSTED,
+ NONE,
+
+ MAXREJECTS = 100,
+};
+
+
+typedef struct Link Link;
+typedef struct List List;
+
+struct Link {
+ Link *next;
+ String *p;
+};
+
+struct List {
+ Link *first;
+ Link *last;
+};
+
+extern int fflag;
+extern int rflag;
+extern int sflag;
+
+extern int debug;
+extern NetConnInfo *nci;
+extern char *dom;
+extern char* me;
+extern int trusted;
+extern List senders;
+extern List rcvers;
+
+void addbadguy(char*);
+void auth(String *, String *);
+int blocked(String*);
+void data(void);
+char* dumpfile(char*);
+int forwarding(String*);
+void getconf(void);
+void hello(String*, int extended);
+void help(String *);
+int isbadguy(void);
+void listadd(List*, String*);
+void listfree(List*);
+int masquerade(String*, char*);
+void noop(void);
+int optoutofspamfilter(char*);
+void quit(void);
+void parseinit(void);
+void receiver(String*);
+int recipok(char*);
+int reply(char*, ...);
+void reset(void);
+int rmtdns(char*, char*);
+void sayhi(void);
+void sender(String*);
+void starttls(void);
+void turn(void);
+void verify(String*);
+void vfysenderhostok(void);
+int zzparse(void);
diff --git a/src/cmd/upas/smtp/smtpd.y b/src/cmd/upas/smtp/smtpd.y
new file mode 100644
index 00000000..57b89a02
--- /dev/null
+++ b/src/cmd/upas/smtp/smtpd.y
@@ -0,0 +1,317 @@
+%{
+#include "common.h"
+#include <ctype.h>
+#include "smtpd.h"
+
+#define YYSTYPE yystype
+typedef struct quux yystype;
+struct quux {
+ String *s;
+ int c;
+};
+Biobuf *yyfp;
+YYSTYPE *bang;
+extern Biobuf bin;
+extern int debug;
+
+YYSTYPE cat(YYSTYPE*, YYSTYPE*, YYSTYPE*, YYSTYPE*, YYSTYPE*, YYSTYPE*, YYSTYPE*);
+int yyparse(void);
+int yylex(void);
+YYSTYPE anonymous(void);
+%}
+
+%term SPACE
+%term CNTRL
+%term CRLF
+%start conversation
+%%
+
+conversation : cmd
+ | conversation cmd
+ ;
+cmd : error
+ | 'h' 'e' 'l' 'o' spaces sdomain CRLF
+ { hello($6.s, 0); }
+ | 'e' 'h' 'l' 'o' spaces sdomain CRLF
+ { hello($6.s, 1); }
+ | 'm' 'a' 'i' 'l' spaces 'f' 'r' 'o' 'm' ':' spath CRLF
+ { sender($11.s); }
+ | 'm' 'a' 'i' 'l' spaces 'f' 'r' 'o' 'm' ':' spath spaces 'a' 'u' 't' 'h' '=' sauth CRLF
+ { sender($11.s); }
+ | 'r' 'c' 'p' 't' spaces 't' 'o' ':' spath CRLF
+ { receiver($9.s); }
+ | 'd' 'a' 't' 'a' CRLF
+ { data(); }
+ | 'r' 's' 'e' 't' CRLF
+ { reset(); }
+ | 's' 'e' 'n' 'd' spaces 'f' 'r' 'o' 'm' ':' spath CRLF
+ { sender($11.s); }
+ | 's' 'o' 'm' 'l' spaces 'f' 'r' 'o' 'm' ':' spath CRLF
+ { sender($11.s); }
+ | 's' 'a' 'm' 'l' spaces 'f' 'r' 'o' 'm' ':' spath CRLF
+ { sender($11.s); }
+ | 'v' 'r' 'f' 'y' spaces string CRLF
+ { verify($6.s); }
+ | 'e' 'x' 'p' 'n' spaces string CRLF
+ { verify($6.s); }
+ | 'h' 'e' 'l' 'p' CRLF
+ { help(0); }
+ | 'h' 'e' 'l' 'p' spaces string CRLF
+ { help($6.s); }
+ | 'n' 'o' 'o' 'p' CRLF
+ { noop(); }
+ | 'q' 'u' 'i' 't' CRLF
+ { quit(); }
+ | 't' 'u' 'r' 'n' CRLF
+ { turn(); }
+ | 's' 't' 'a' 'r' 't' 't' 'l' 's' CRLF
+ { starttls(); }
+ | 'a' 'u' 't' 'h' spaces name spaces string CRLF
+ { auth($6.s, $8.s); }
+ | 'a' 'u' 't' 'h' spaces name CRLF
+ { auth($6.s, nil); }
+ | CRLF
+ { reply("501 illegal command or bad syntax\r\n"); }
+ ;
+path : '<' '>' ={ $$ = anonymous(); }
+ | '<' mailbox '>' ={ $$ = $2; }
+ | '<' a_d_l ':' mailbox '>' ={ $$ = cat(&$2, bang, &$4, 0, 0 ,0, 0); }
+ ;
+spath : path ={ $$ = $1; }
+ | spaces path ={ $$ = $2; }
+ ;
+auth : path ={ $$ = $1; }
+ | mailbox ={ $$ = $1; }
+ ;
+sauth : auth ={ $$ = $1; }
+ | spaces auth ={ $$ = $2; }
+ ;
+ ;
+a_d_l : at_domain ={ $$ = cat(&$1, 0, 0, 0, 0 ,0, 0); }
+ | at_domain ',' a_d_l ={ $$ = cat(&$1, bang, &$3, 0, 0, 0, 0); }
+ ;
+at_domain : '@' domain ={ $$ = cat(&$2, 0, 0, 0, 0 ,0, 0); }
+ ;
+sdomain : domain ={ $$ = $1; }
+ | domain spaces ={ $$ = $1; }
+ ;
+domain : element ={ $$ = cat(&$1, 0, 0, 0, 0 ,0, 0); }
+ | element '.' ={ $$ = cat(&$1, 0, 0, 0, 0 ,0, 0); }
+ | element '.' domain ={ $$ = cat(&$1, &$2, &$3, 0, 0 ,0, 0); }
+ ;
+element : name ={ $$ = cat(&$1, 0, 0, 0, 0 ,0, 0); }
+ | '#' number ={ $$ = cat(&$1, &$2, 0, 0, 0 ,0, 0); }
+ | '[' ']' ={ $$ = cat(&$1, &$2, 0, 0, 0 ,0, 0); }
+ | '[' dotnum ']' ={ $$ = cat(&$1, &$2, &$3, 0, 0 ,0, 0); }
+ ;
+mailbox : local_part ={ $$ = cat(&$1, 0, 0, 0, 0 ,0, 0); }
+ | local_part '@' domain ={ $$ = cat(&$3, bang, &$1, 0, 0 ,0, 0); }
+ ;
+local_part : dot_string ={ $$ = cat(&$1, 0, 0, 0, 0 ,0, 0); }
+ | quoted_string ={ $$ = cat(&$1, 0, 0, 0, 0 ,0, 0); }
+ ;
+name : let_dig ={ $$ = cat(&$1, 0, 0, 0, 0 ,0, 0); }
+ | let_dig ld_str ={ $$ = cat(&$1, &$2, 0, 0, 0 ,0, 0); }
+ | let_dig ldh_str ld_str ={ $$ = cat(&$1, &$2, &$3, 0, 0 ,0, 0); }
+ ;
+ld_str : let_dig
+ | let_dig ld_str ={ $$ = cat(&$1, &$2, 0, 0, 0 ,0, 0); }
+ ;
+ldh_str : hunder
+ | ld_str hunder ={ $$ = cat(&$1, &$2, 0, 0, 0 ,0, 0); }
+ | ldh_str ld_str hunder ={ $$ = cat(&$1, &$2, &$3, 0, 0 ,0, 0); }
+ ;
+let_dig : a
+ | d
+ ;
+dot_string : string ={ $$ = cat(&$1, 0, 0, 0, 0 ,0, 0); }
+ | string '.' dot_string ={ $$ = cat(&$1, &$2, &$3, 0, 0 ,0, 0); }
+ ;
+
+string : char ={ $$ = cat(&$1, 0, 0, 0, 0 ,0, 0); }
+ | string char ={ $$ = cat(&$1, &$2, 0, 0, 0 ,0, 0); }
+ ;
+
+quoted_string : '"' qtext '"' ={ $$ = cat(&$1, &$2, &$3, 0, 0 ,0, 0); }
+ ;
+qtext : '\\' x ={ $$ = cat(&$2, 0, 0, 0, 0 ,0, 0); }
+ | qtext '\\' x ={ $$ = cat(&$1, &$3, 0, 0, 0 ,0, 0); }
+ | q
+ | qtext q ={ $$ = cat(&$1, &$2, 0, 0, 0 ,0, 0); }
+ ;
+char : c
+ | '\\' x ={ $$ = $2; }
+ ;
+dotnum : snum '.' snum '.' snum '.' snum ={ $$ = cat(&$1, &$2, &$3, &$4, &$5, &$6, &$7); }
+ ;
+number : d ={ $$ = cat(&$1, 0, 0, 0, 0 ,0, 0); }
+ | number d ={ $$ = cat(&$1, &$2, 0, 0, 0 ,0, 0); }
+ ;
+snum : number ={ if(atoi(s_to_c($1.s)) > 255) print("bad snum\n"); }
+ ;
+spaces : SPACE ={ $$ = $1; }
+ | SPACE spaces ={ $$ = $1; }
+ ;
+hunder : '-' | '_'
+ ;
+special1 : CNTRL
+ | '(' | ')' | ',' | '.'
+ | ':' | ';' | '<' | '>' | '@'
+ ;
+special : special1 | '\\' | '"'
+ ;
+notspecial : '!' | '#' | '$' | '%' | '&' | '\''
+ | '*' | '+' | '-' | '/'
+ | '=' | '?'
+ | '[' | ']' | '^' | '_' | '`' | '{' | '|' | '}' | '~'
+ ;
+
+a : 'a' | 'b' | 'c' | 'd' | 'e' | 'f' | 'g' | 'h' | 'i'
+ | 'j' | 'k' | 'l' | 'm' | 'n' | 'o' | 'p' | 'q' | 'r'
+ | 's' | 't' | 'u' | 'v' | 'w' | 'x' | 'y' | 'z'
+ ;
+d : '0' | '1' | '2' | '3' | '4' | '5' | '6' | '7' | '8' | '9'
+ ;
+c : a | d | notspecial
+ ;
+q : a | d | special1 | notspecial | SPACE
+ ;
+x : a | d | special | notspecial | SPACE
+ ;
+%%
+
+void
+parseinit(void)
+{
+ bang = (YYSTYPE*)malloc(sizeof(YYSTYPE));
+ bang->c = '!';
+ bang->s = 0;
+ yyfp = &bin;
+}
+
+yylex(void)
+{
+ int c;
+
+ for(;;){
+ c = Bgetc(yyfp);
+ if(c == -1)
+ return 0;
+ if(debug)
+ fprint(2, "%c", c);
+ yylval.c = c = c & 0x7F;
+ if(c == '\n'){
+ return CRLF;
+ }
+ if(c == '\r'){
+ c = Bgetc(yyfp);
+ if(c != '\n'){
+ Bungetc(yyfp);
+ c = '\r';
+ } else {
+ if(debug)
+ fprint(2, "%c", c);
+ return CRLF;
+ }
+ }
+ if(isalpha(c))
+ return tolower(c);
+ if(isspace(c))
+ return SPACE;
+ if(iscntrl(c))
+ return CNTRL;
+ return c;
+ }
+}
+
+YYSTYPE
+cat(YYSTYPE *y1, YYSTYPE *y2, YYSTYPE *y3, YYSTYPE *y4, YYSTYPE *y5, YYSTYPE *y6, YYSTYPE *y7)
+{
+ YYSTYPE rv;
+
+ if(y1->s)
+ rv.s = y1->s;
+ else {
+ rv.s = s_new();
+ s_putc(rv.s, y1->c);
+ s_terminate(rv.s);
+ }
+ if(y2){
+ if(y2->s){
+ s_append(rv.s, s_to_c(y2->s));
+ s_free(y2->s);
+ } else {
+ s_putc(rv.s, y2->c);
+ s_terminate(rv.s);
+ }
+ } else
+ return rv;
+ if(y3){
+ if(y3->s){
+ s_append(rv.s, s_to_c(y3->s));
+ s_free(y3->s);
+ } else {
+ s_putc(rv.s, y3->c);
+ s_terminate(rv.s);
+ }
+ } else
+ return rv;
+ if(y4){
+ if(y4->s){
+ s_append(rv.s, s_to_c(y4->s));
+ s_free(y4->s);
+ } else {
+ s_putc(rv.s, y4->c);
+ s_terminate(rv.s);
+ }
+ } else
+ return rv;
+ if(y5){
+ if(y5->s){
+ s_append(rv.s, s_to_c(y5->s));
+ s_free(y5->s);
+ } else {
+ s_putc(rv.s, y5->c);
+ s_terminate(rv.s);
+ }
+ } else
+ return rv;
+ if(y6){
+ if(y6->s){
+ s_append(rv.s, s_to_c(y6->s));
+ s_free(y6->s);
+ } else {
+ s_putc(rv.s, y6->c);
+ s_terminate(rv.s);
+ }
+ } else
+ return rv;
+ if(y7){
+ if(y7->s){
+ s_append(rv.s, s_to_c(y7->s));
+ s_free(y7->s);
+ } else {
+ s_putc(rv.s, y7->c);
+ s_terminate(rv.s);
+ }
+ } else
+ return rv;
+}
+
+void
+yyerror(char *x)
+{
+ USED(x);
+}
+
+/*
+ * an anonymous user
+ */
+YYSTYPE
+anonymous(void)
+{
+ YYSTYPE rv;
+
+ rv.s = s_copy("/dev/null");
+ return rv;
+}
diff --git a/src/cmd/upas/smtp/spam.c b/src/cmd/upas/smtp/spam.c
new file mode 100644
index 00000000..84a8fccc
--- /dev/null
+++ b/src/cmd/upas/smtp/spam.c
@@ -0,0 +1,591 @@
+#include "common.h"
+#include "smtpd.h"
+#include <ip.h>
+
+enum {
+ NORELAY = 0,
+ DNSVERIFY,
+ SAVEBLOCK,
+ DOMNAME,
+ OURNETS,
+ OURDOMS,
+
+ IP = 0,
+ STRING,
+};
+
+
+typedef struct Keyword Keyword;
+
+struct Keyword {
+ char *name;
+ int code;
+};
+
+static Keyword options[] = {
+ "norelay", NORELAY,
+ "verifysenderdom", DNSVERIFY,
+ "saveblockedmsg", SAVEBLOCK,
+ "defaultdomain", DOMNAME,
+ "ournets", OURNETS,
+ "ourdomains", OURDOMS,
+ 0, NONE,
+};
+
+static Keyword actions[] = {
+ "allow", ACCEPT,
+ "block", BLOCKED,
+ "deny", DENIED,
+ "dial", DIALUP,
+ "delay", DELAY,
+ 0, NONE,
+};
+
+static int hisaction;
+static List ourdoms;
+static List badguys;
+static ulong v4peerip;
+
+static char* getline(Biobuf*);
+static int cidrcheck(char*);
+
+static int
+findkey(char *val, Keyword *p)
+{
+
+ for(; p->name; p++)
+ if(strcmp(val, p->name) == 0)
+ break;
+ return p->code;
+}
+
+char*
+actstr(int a)
+{
+ char buf[32];
+ Keyword *p;
+
+ for(p=actions; p->name; p++)
+ if(p->code == a)
+ return p->name;
+ if(a==NONE)
+ return "none";
+ sprint(buf, "%d", a);
+ return buf;
+}
+
+int
+getaction(char *s, char *type)
+{
+ char buf[1024];
+ Keyword *k;
+
+ if(s == nil || *s == 0)
+ return ACCEPT;
+
+ for(k = actions; k->name != 0; k++){
+ snprint(buf, sizeof buf, "/mail/ratify/%s/%s/%s", k->name, type, s);
+ if(access(buf,0) >= 0)
+ return k->code;
+ }
+ return ACCEPT;
+}
+
+int
+istrusted(char *s)
+{
+ char buf[1024];
+
+ if(s == nil || *s == 0)
+ return 0;
+
+ snprint(buf, sizeof buf, "/mail/ratify/trusted/%s", s);
+ return access(buf,0) >= 0;
+}
+
+void
+getconf(void)
+{
+ Biobuf *bp;
+ char *cp, *p;
+ String *s;
+ char buf[512];
+ uchar addr[4];
+
+ v4parseip(addr, nci->rsys);
+ v4peerip = nhgetl(addr);
+
+ trusted = istrusted(nci->rsys);
+ hisaction = getaction(nci->rsys, "ip");
+ if(debug){
+ fprint(2, "istrusted(%s)=%d\n", nci->rsys, trusted);
+ fprint(2, "getaction(%s, ip)=%s\n", nci->rsys, actstr(hisaction));
+ }
+ snprint(buf, sizeof(buf), "%s/smtpd.conf", UPASLIB);
+ bp = sysopen(buf, "r", 0);
+ if(bp == 0)
+ return;
+
+ for(;;){
+ cp = getline(bp);
+ if(cp == 0)
+ break;
+ p = cp+strlen(cp)+1;
+ switch(findkey(cp, options)){
+ case NORELAY:
+ if(fflag == 0 && strcmp(p, "on") == 0)
+ fflag++;
+ break;
+ case DNSVERIFY:
+ if(rflag == 0 && strcmp(p, "on") == 0)
+ rflag++;
+ break;
+ case SAVEBLOCK:
+ if(sflag == 0 && strcmp(p, "on") == 0)
+ sflag++;
+ break;
+ case DOMNAME:
+ if(dom == 0)
+ dom = strdup(p);
+ break;
+ case OURNETS:
+ if (trusted == 0)
+ trusted = cidrcheck(p);
+ break;
+ case OURDOMS:
+ while(*p){
+ s = s_new();
+ s_append(s, p);
+ listadd(&ourdoms, s);
+ p += strlen(p)+1;
+ }
+ break;
+ default:
+ break;
+ }
+ }
+ sysclose(bp);
+}
+
+/*
+ * match a user name. the only meta-char is '*' which matches all
+ * characters. we only allow it as "*", which matches anything or
+ * an * at the end of the name (e.g., "username*") which matches
+ * trailing characters.
+ */
+static int
+usermatch(char *pathuser, char *specuser)
+{
+ int n;
+
+ n = strlen(specuser)-1;
+ if(specuser[n] == '*'){
+ if(n == 0) /* match everything */
+ return 0;
+ return strncmp(pathuser, specuser, n);
+ }
+ return strcmp(pathuser, specuser);
+}
+
+static int
+dommatch(char *pathdom, char *specdom)
+{
+ int n;
+
+ if (*specdom == '*'){
+ if (specdom[1] == '.' && specdom[2]){
+ specdom += 2;
+ n = strlen(pathdom)-strlen(specdom);
+ if(n == 0 || (n > 0 && pathdom[n-1] == '.'))
+ return strcmp(pathdom+n, specdom);
+ return n;
+ }
+ }
+ return strcmp(pathdom, specdom);
+}
+
+/*
+ * figure out action for this sender
+ */
+int
+blocked(String *path)
+{
+ String *lpath;
+ int action;
+
+ if(debug)
+ fprint(2, "blocked(%s)\n", s_to_c(path));
+
+ /* if the sender's IP address is blessed, ignore sender email address */
+ if(trusted){
+ if(debug)
+ fprint(2, "\ttrusted => trusted\n");
+ return TRUSTED;
+ }
+
+ /* if sender's IP address is blocked, ignore sender email address */
+ if(hisaction != ACCEPT){
+ if(debug)
+ fprint(2, "\thisaction=%s => %s\n", actstr(hisaction), actstr(hisaction));
+ return hisaction;
+ }
+
+ /* convert to lower case */
+ lpath = s_copy(s_to_c(path));
+ s_tolower(lpath);
+
+ /* classify */
+ action = getaction(s_to_c(lpath), "account");
+ if(debug)
+ fprint(2, "\tgetaction account %s => %s\n", s_to_c(lpath), actstr(action));
+ s_free(lpath);
+ return action;
+}
+
+/*
+ * get a canonicalized line: a string of null-terminated lower-case
+ * tokens with a two null bytes at the end.
+ */
+static char*
+getline(Biobuf *bp)
+{
+ char c, *cp, *p, *q;
+ int n;
+
+ static char *buf;
+ static int bufsize;
+
+ for(;;){
+ cp = Brdline(bp, '\n');
+ if(cp == 0)
+ return 0;
+ n = Blinelen(bp);
+ cp[n-1] = 0;
+ if(buf == 0 || bufsize < n+1){
+ bufsize += 512;
+ if(bufsize < n+1)
+ bufsize = n+1;
+ buf = realloc(buf, bufsize);
+ if(buf == 0)
+ break;
+ }
+ q = buf;
+ for (p = cp; *p; p++){
+ c = *p;
+ if(c == '\\' && p[1]) /* we don't allow \<newline> */
+ c = *++p;
+ else
+ if(c == '#')
+ break;
+ else
+ if(c == ' ' || c == '\t' || c == ',')
+ if(q == buf || q[-1] == 0)
+ continue;
+ else
+ c = 0;
+ *q++ = tolower(c);
+ }
+ if(q != buf){
+ if(q[-1])
+ *q++ = 0;
+ *q = 0;
+ break;
+ }
+ }
+ return buf;
+}
+
+static int
+isourdom(char *s)
+{
+ Link *l;
+
+ if(strchr(s, '.') == nil)
+ return 1;
+
+ for(l = ourdoms.first; l; l = l->next){
+ if(dommatch(s, s_to_c(l->p)) == 0)
+ return 1;
+ }
+ return 0;
+}
+
+int
+forwarding(String *path)
+{
+ char *cp, *s;
+ String *lpath;
+
+ if(debug)
+ fprint(2, "forwarding(%s)\n", s_to_c(path));
+
+ /* first check if they want loopback */
+ lpath = s_copy(s_to_c(s_restart(path)));
+ if(nci->rsys && *nci->rsys){
+ cp = s_to_c(lpath);
+ if(strncmp(cp, "[]!", 3) == 0){
+found:
+ s_append(path, "[");
+ s_append(path, nci->rsys);
+ s_append(path, "]!");
+ s_append(path, cp+3);
+ s_terminate(path);
+ s_free(lpath);
+ return 0;
+ }
+ cp = strchr(cp,'!'); /* skip our domain and check next */
+ if(cp++ && strncmp(cp, "[]!", 3) == 0)
+ goto found;
+ }
+
+ /* if mail is from a trusted IP addr, allow it to forward */
+ if(trusted) {
+ s_free(lpath);
+ return 0;
+ }
+
+ /* sender is untrusted; ensure receiver is in one of our domains */
+ for(cp = s_to_c(lpath); *cp; cp++) /* convert receiver lc */
+ *cp = tolower(*cp);
+
+ for(s = s_to_c(lpath); cp = strchr(s, '!'); s = cp+1){
+ *cp = 0;
+ if(!isourdom(s)){
+ s_free(lpath);
+ return 1;
+ }
+ }
+ s_free(lpath);
+ return 0;
+}
+
+int
+masquerade(String *path, char *him)
+{
+ char *cp, *s;
+ String *lpath;
+ int rv = 0;
+
+ if(debug)
+ fprint(2, "masquerade(%s)\n", s_to_c(path));
+
+ if(trusted)
+ return 0;
+ if(path == nil)
+ return 0;
+
+ lpath = s_copy(s_to_c(path));
+
+ /* sender is untrusted; ensure receiver is in one of our domains */
+ for(cp = s_to_c(lpath); *cp; cp++) /* convert receiver lc */
+ *cp = tolower(*cp);
+ s = s_to_c(lpath);
+
+ /* scan first element of ! or last element of @ paths */
+ if((cp = strchr(s, '!')) != nil){
+ *cp = 0;
+ if(isourdom(s))
+ rv = 1;
+ } else if((cp = strrchr(s, '@')) != nil){
+ if(isourdom(cp+1))
+ rv = 1;
+ } else {
+ if(isourdom(him))
+ rv = 1;
+ }
+
+ s_free(lpath);
+ return rv;
+}
+
+/* this is a v4 only check */
+static int
+cidrcheck(char *cp)
+{
+ char *p;
+ ulong a, m;
+ uchar addr[IPv4addrlen];
+ uchar mask[IPv4addrlen];
+
+ if(v4peerip == 0)
+ return 0;
+
+ /* parse a list of CIDR addresses comparing each to the peer IP addr */
+ while(cp && *cp){
+ v4parsecidr(addr, mask, cp);
+ a = nhgetl(addr);
+ m = nhgetl(mask);
+ /*
+ * if a mask isn't specified, we build a minimal mask
+ * instead of using the default mask for that net. in this
+ * case we never allow a class A mask (0xff000000).
+ */
+ if(strchr(cp, '/') == 0){
+ m = 0xff000000;
+ p = cp;
+ for(p = strchr(p, '.'); p && p[1]; p = strchr(p+1, '.'))
+ m = (m>>8)|0xff000000;
+
+ /* force at least a class B */
+ m |= 0xffff0000;
+ }
+ if((v4peerip&m) == a)
+ return 1;
+ cp += strlen(cp)+1;
+ }
+ return 0;
+}
+
+int
+isbadguy(void)
+{
+ Link *l;
+
+ /* check if this IP address is banned */
+ for(l = badguys.first; l; l = l->next)
+ if(cidrcheck(s_to_c(l->p)))
+ return 1;
+
+ return 0;
+}
+
+void
+addbadguy(char *p)
+{
+ listadd(&badguys, s_copy(p));
+};
+
+char*
+dumpfile(char *sender)
+{
+ int i, fd;
+ ulong h;
+ static char buf[512];
+ char *cp;
+
+ if (sflag == 1){
+ cp = ctime(time(0));
+ cp[7] = 0;
+ if(cp[8] == ' ')
+ sprint(buf, "%s/queue.dump/%s%c", SPOOL, cp+4, cp[9]);
+ else
+ sprint(buf, "%s/queue.dump/%s%c%c", SPOOL, cp+4, cp[8], cp[9]);
+ cp = buf+strlen(buf);
+ if(access(buf, 0) < 0 && sysmkdir(buf, 0777) < 0)
+ return "/dev/null";
+ h = 0;
+ while(*sender)
+ h = h*257 + *sender++;
+ for(i = 0; i < 50; i++){
+ h += lrand();
+ sprint(cp, "/%lud", h);
+ if(access(buf, 0) >= 0)
+ continue;
+ fd = syscreate(buf, ORDWR, 0666);
+ if(fd >= 0){
+ if(debug)
+ fprint(2, "saving in %s\n", buf);
+ close(fd);
+ return buf;
+ }
+ }
+ }
+ return "/dev/null";
+}
+
+char *validator = "/mail/lib/validateaddress";
+
+int
+recipok(char *user)
+{
+ char *cp, *p, c;
+ char buf[512];
+ int n;
+ Biobuf *bp;
+ int pid;
+ Waitmsg *w;
+
+ if(shellchars(user)){
+ syslog(0, "smtpd", "shellchars in user name");
+ return 0;
+ }
+
+ if(access(validator, AEXEC) == 0)
+ switch(pid = fork()) {
+ case -1:
+ break;
+ case 0:
+ execl(validator, "validateaddress", user, nil);
+ exits(0);
+ default:
+ while(w = wait()) {
+ if(w->pid != pid)
+ continue;
+ if(w->msg[0] != 0){
+ /*
+ syslog(0, "smtpd", "validateaddress %s: %s", user, w->msg);
+ */
+ return 0;
+ }
+ break;
+ }
+ }
+
+ snprint(buf, sizeof(buf), "%s/names.blocked", UPASLIB);
+ bp = sysopen(buf, "r", 0);
+ if(bp == 0)
+ return 1;
+ for(;;){
+ cp = Brdline(bp, '\n');
+ if(cp == 0)
+ break;
+ n = Blinelen(bp);
+ cp[n-1] = 0;
+
+ while(*cp == ' ' || *cp == '\t')
+ cp++;
+ for(p = cp; c = *p; p++){
+ if(c == '#')
+ break;
+ if(c == ' ' || c == '\t')
+ break;
+ }
+ if(p > cp){
+ *p = 0;
+ if(cistrcmp(user, cp) == 0){
+ syslog(0, "smtpd", "names.blocked blocks %s", user);
+ Bterm(bp);
+ return 0;
+ }
+ }
+ }
+ Bterm(bp);
+ return 1;
+}
+
+/*
+ * a user can opt out of spam filtering by creating
+ * a file in his mail directory named 'nospamfiltering'.
+ */
+int
+optoutofspamfilter(char *addr)
+{
+ char *p, *f;
+ int rv;
+
+ p = strchr(addr, '!');
+ if(p)
+ p++;
+ else
+ p = addr;
+
+
+ rv = 0;
+ f = smprint("/mail/box/%s/nospamfiltering", p);
+ if(f != nil){
+ rv = access(f, 0)==0;
+ free(f);
+ }
+
+ return rv;
+}
diff --git a/src/cmd/upas/smtp/y.tab.h b/src/cmd/upas/smtp/y.tab.h
new file mode 100644
index 00000000..a31a0e67
--- /dev/null
+++ b/src/cmd/upas/smtp/y.tab.h
@@ -0,0 +1,25 @@
+#define WORD 57346
+#define DATE 57347
+#define RESENT_DATE 57348
+#define RETURN_PATH 57349
+#define FROM 57350
+#define SENDER 57351
+#define REPLY_TO 57352
+#define RESENT_FROM 57353
+#define RESENT_SENDER 57354
+#define RESENT_REPLY_TO 57355
+#define SUBJECT 57356
+#define TO 57357
+#define CC 57358
+#define BCC 57359
+#define RESENT_TO 57360
+#define RESENT_CC 57361
+#define RESENT_BCC 57362
+#define REMOTE 57363
+#define PRECEDENCE 57364
+#define MIMEVERSION 57365
+#define CONTENTTYPE 57366
+#define MESSAGEID 57367
+#define RECEIVED 57368
+#define MAILER 57369
+#define BADTOKEN 57370