aboutsummaryrefslogtreecommitdiff
path: root/src/cmd/upas/send
diff options
context:
space:
mode:
Diffstat (limited to 'src/cmd/upas/send')
-rw-r--r--src/cmd/upas/send/authorize.c29
-rw-r--r--src/cmd/upas/send/bind.c133
-rw-r--r--src/cmd/upas/send/cat_mail.c60
-rw-r--r--src/cmd/upas/send/dest.c260
-rw-r--r--src/cmd/upas/send/filter.c128
-rw-r--r--src/cmd/upas/send/gateway.c24
-rw-r--r--src/cmd/upas/send/local.c129
-rw-r--r--src/cmd/upas/send/log.c85
-rw-r--r--src/cmd/upas/send/main.c575
-rw-r--r--src/cmd/upas/send/makefile46
-rw-r--r--src/cmd/upas/send/message.c573
-rw-r--r--src/cmd/upas/send/mkfile52
-rw-r--r--src/cmd/upas/send/regtest.c36
-rw-r--r--src/cmd/upas/send/rewrite.c315
-rw-r--r--src/cmd/upas/send/send.h108
-rw-r--r--src/cmd/upas/send/skipequiv.c93
-rw-r--r--src/cmd/upas/send/translate.c43
-rw-r--r--src/cmd/upas/send/tryit29
18 files changed, 2718 insertions, 0 deletions
diff --git a/src/cmd/upas/send/authorize.c b/src/cmd/upas/send/authorize.c
new file mode 100644
index 00000000..6fa12c57
--- /dev/null
+++ b/src/cmd/upas/send/authorize.c
@@ -0,0 +1,29 @@
+#include "common.h"
+#include "send.h"
+
+/*
+ * Run a command to authorize or refuse entry. Return status 0 means
+ * authorize, -1 means refuse.
+ */
+void
+authorize(dest *dp)
+{
+ process *pp;
+ String *errstr;
+
+ dp->authorized = 1;
+ pp = proc_start(s_to_c(dp->repl1), (stream *)0, (stream *)0, outstream(), 1, 0);
+ if (pp == 0){
+ dp->status = d_noforward;
+ return;
+ }
+ errstr = s_new();
+ while(s_read_line(pp->std[2]->fp, errstr))
+ ;
+ if ((dp->pstat = proc_wait(pp)) != 0) {
+ dp->repl2 = errstr;
+ dp->status = d_noforward;
+ } else
+ s_free(errstr);
+ proc_free(pp);
+}
diff --git a/src/cmd/upas/send/bind.c b/src/cmd/upas/send/bind.c
new file mode 100644
index 00000000..8a8fc8ea
--- /dev/null
+++ b/src/cmd/upas/send/bind.c
@@ -0,0 +1,133 @@
+#include "common.h"
+#include "send.h"
+
+static int forward_loop(char *, char *);
+
+/* bind the destinations to the commands to be executed */
+extern dest *
+up_bind(dest *destp, message *mp, int checkforward)
+{
+ dest *list[2]; /* lists of unbound destinations */
+ int li; /* index into list[2] */
+ dest *bound=0; /* bound destinations */
+ dest *dp;
+ int i;
+
+ list[0] = destp;
+ list[1] = 0;
+
+ /*
+ * loop once to check for:
+ * - forwarding rights
+ * - addressing loops
+ * - illegal characters
+ * - characters that need escaping
+ */
+ for (dp = d_rm(&list[0]); dp != 0; dp = d_rm(&list[0])) {
+ if (!checkforward)
+ dp->authorized = 1;
+ dp->addr = escapespecial(dp->addr);
+ if (forward_loop(s_to_c(dp->addr), thissys)) {
+ dp->status = d_eloop;
+ d_same_insert(&bound, dp);
+ } else if(forward_loop(s_to_c(mp->sender), thissys)) {
+ dp->status = d_eloop;
+ d_same_insert(&bound, dp);
+ } else if(shellchars(s_to_c(dp->addr))) {
+ dp->status = d_syntax;
+ d_same_insert(&bound, dp);
+ } else
+ d_insert(&list[1], dp);
+ }
+ li = 1;
+
+ /* Loop until all addresses are bound or address loop detected */
+ for (i=0; list[li]!=0 && i<32; ++i, li ^= 1) {
+ /* Traverse the current list. Bound items are put on the
+ * `bound' list. Unbound items are put on the next list to
+ * traverse, `list[li^1]'.
+ */
+ for (dp = d_rm(&list[li]); dp != 0; dp = d_rm(&list[li])){
+ dest *newlist;
+
+ rewrite(dp, mp);
+ if(debug)
+ fprint(2, "%s -> %s\n", s_to_c(dp->addr),
+ dp->repl1 ? s_to_c(dp->repl1):"");
+ switch (dp->status) {
+ case d_auth:
+ /* authorize address if not already authorized */
+ if(!dp->authorized){
+ authorize(dp);
+ if(dp->status==d_auth)
+ d_insert(&list[li^1], dp);
+ else
+ d_insert(&bound, dp);
+ }
+ break;
+ case d_cat:
+ /* address -> local */
+ newlist = expand_local(dp);
+ if (newlist == 0) {
+ /* append to mailbox (or error) */
+ d_same_insert(&bound, dp);
+ } else if (newlist->status == d_undefined) {
+ /* Forward to ... */
+ d_insert(&list[li^1], newlist);
+ } else {
+ /* Pipe to ... */
+ d_same_insert(&bound, newlist);
+ }
+ break;
+ case d_pipe:
+ /* address -> command */
+ d_same_insert(&bound, dp);
+ break;
+ case d_alias:
+ /* address -> rewritten address */
+ newlist = s_to_dest(dp->repl1, dp);
+ if(newlist != 0)
+ d_insert(&list[li^1], newlist);
+ else
+ d_same_insert(&bound, dp);
+ break;
+ case d_translate:
+ /* pipe to a translator */
+ newlist = translate(dp);
+ if (newlist != 0)
+ d_insert(&list[li^1], newlist);
+ else
+ d_same_insert(&bound, dp);
+ break;
+ default:
+ /* error */
+ d_same_insert(&bound, dp);
+ break;
+ }
+ }
+ }
+
+ /* mark remaining comands as "forwarding loops" */
+ for (dp = d_rm(&list[li]); dp != 0; dp = d_rm(&list[li])) {
+ dp->status = d_loop;
+ d_same_insert(&bound, dp);
+ }
+
+ return bound;
+}
+
+/* Return TRUE if a forwarding loop exists, i.e., the String `system'
+ * is found more than 4 times in the return address.
+ */
+static int
+forward_loop(char *addr, char *system)
+{
+ int len = strlen(system), found = 0;
+
+ while (addr = strchr(addr, '!'))
+ if (!strncmp(++addr, system, len)
+ && addr[len] == '!' && ++found == 4)
+ return 1;
+ return 0;
+}
+
diff --git a/src/cmd/upas/send/cat_mail.c b/src/cmd/upas/send/cat_mail.c
new file mode 100644
index 00000000..cdd16ece
--- /dev/null
+++ b/src/cmd/upas/send/cat_mail.c
@@ -0,0 +1,60 @@
+#include "common.h"
+#include "send.h"
+
+
+/* dispose of local addresses */
+int
+cat_mail(dest *dp, message *mp)
+{
+ Biobuf *fp;
+ char *rcvr, *cp;
+ Mlock *l;
+ String *tmp, *s;
+ int i, n;
+
+ s = unescapespecial(s_clone(dp->repl1));
+ if (nflg) {
+ if(!xflg)
+ print("cat >> %s\n", s_to_c(s));
+ else
+ print("%s\n", s_to_c(dp->addr));
+ s_free(s);
+ return 0;
+ }
+ for(i = 0;; i++){
+ l = syslock(s_to_c(s));
+ if(l == 0)
+ return refuse(dp, mp, "can't lock mail file", 0, 0);
+
+ fp = sysopen(s_to_c(s), "al", MBOXMODE);
+ if(fp)
+ break;
+ tmp = s_append(0, s_to_c(s));
+ s_append(tmp, ".tmp");
+ fp = sysopen(s_to_c(tmp), "al", MBOXMODE);
+ if(fp){
+ syslog(0, "mail", "error: used %s", s_to_c(tmp));
+ s_free(tmp);
+ break;
+ }
+ s_free(tmp);
+ sysunlock(l);
+ if(i >= 5)
+ return refuse(dp, mp, "mail file cannot be opened", 0, 0);
+ sleep(1000);
+ }
+ s_free(s);
+ n = m_print(mp, fp, (char *)0, 1);
+ if (Bprint(fp, "\n") < 0 || Bflush(fp) < 0 || n < 0){
+ sysclose(fp);
+ sysunlock(l);
+ return refuse(dp, mp, "error writing mail file", 0, 0);
+ }
+ sysclose(fp);
+ sysunlock(l);
+ rcvr = s_to_c(dp->addr);
+ if(cp = strrchr(rcvr, '!'))
+ rcvr = cp+1;
+ logdelivery(dp, rcvr, mp);
+ return 0;
+}
diff --git a/src/cmd/upas/send/dest.c b/src/cmd/upas/send/dest.c
new file mode 100644
index 00000000..fa538547
--- /dev/null
+++ b/src/cmd/upas/send/dest.c
@@ -0,0 +1,260 @@
+#include "common.h"
+#include "send.h"
+
+static String* s_parseq(String*, String*);
+
+/* exports */
+dest *dlist;
+
+extern dest*
+d_new(String *addr)
+{
+ dest *dp;
+
+ dp = (dest *)mallocz(sizeof(dest), 1);
+ if (dp == 0) {
+ perror("d_new");
+ exit(1);
+ }
+ dp->same = dp;
+ dp->nsame = 1;
+ dp->nchar = 0;
+ dp->next = dp;
+ dp->addr = escapespecial(addr);
+ dp->parent = 0;
+ dp->repl1 = dp->repl2 = 0;
+ dp->status = d_undefined;
+ return dp;
+}
+
+extern void
+d_free(dest *dp)
+{
+ if (dp != 0) {
+ s_free(dp->addr);
+ s_free(dp->repl1);
+ s_free(dp->repl2);
+ free((char *)dp);
+ }
+}
+
+/* The following routines manipulate an ordered list of items. Insertions
+ * are always to the end of the list. Deletions are from the beginning.
+ *
+ * The list are circular witht the `head' of the list being the last item
+ * added.
+ */
+
+/* Get first element from a circular list linked via 'next'. */
+extern dest *
+d_rm(dest **listp)
+{
+ dest *dp;
+
+ if (*listp == 0)
+ return 0;
+ dp = (*listp)->next;
+ if (dp == *listp)
+ *listp = 0;
+ else
+ (*listp)->next = dp->next;
+ dp->next = dp;
+ return dp;
+}
+
+/* Insert a new entry at the end of the list linked via 'next'. */
+extern void
+d_insert(dest **listp, dest *new)
+{
+ dest *head;
+
+ if (*listp == 0) {
+ *listp = new;
+ return;
+ }
+ if (new == 0)
+ return;
+ head = new->next;
+ new->next = (*listp)->next;
+ (*listp)->next = head;
+ *listp = new;
+ return;
+}
+
+/* Get first element from a circular list linked via 'same'. */
+extern dest *
+d_rm_same(dest **listp)
+{
+ dest *dp;
+
+ if (*listp == 0)
+ return 0;
+ dp = (*listp)->same;
+ if (dp == *listp)
+ *listp = 0;
+ else
+ (*listp)->same = dp->same;
+ dp->same = dp;
+ return dp;
+}
+
+/* Look for a duplicate on the same list */
+int
+d_same_dup(dest *dp, dest *new)
+{
+ dest *first = dp;
+
+ if(new->repl2 == 0)
+ return 1;
+ do {
+ if(strcmp(s_to_c(dp->repl2), s_to_c(new->repl2))==0)
+ return 1;
+ dp = dp->same;
+ } while(dp != first);
+ return 0;
+}
+
+/* Insert an entry into the corresponding list linked by 'same'. Note that
+ * the basic structure is a list of lists.
+ */
+extern void
+d_same_insert(dest **listp, dest *new)
+{
+ dest *dp;
+ int len;
+
+ if(new->status == d_pipe || new->status == d_cat) {
+ len = new->repl2 ? strlen(s_to_c(new->repl2)) : 0;
+ if(*listp != 0){
+ dp = (*listp)->next;
+ do {
+ if(dp->status == new->status
+ && strcmp(s_to_c(dp->repl1), s_to_c(new->repl1))==0){
+ /* remove duplicates */
+ if(d_same_dup(dp, new))
+ return;
+ /* add to chain if chain small enough */
+ if(dp->nsame < MAXSAME
+ && dp->nchar + len < MAXSAMECHAR){
+ new->same = dp->same;
+ dp->same = new;
+ dp->nchar += len + 1;
+ dp->nsame++;
+ return;
+ }
+ }
+ dp = dp->next;
+ } while (dp != (*listp)->next);
+ }
+ new->nchar = strlen(s_to_c(new->repl1)) + len + 1;
+ }
+ new->next = new;
+ d_insert(listp, new);
+}
+
+/*
+ * Form a To: if multiple destinations.
+ * The local! and !local! checks are artificial intelligence,
+ * there should be a better way.
+ */
+extern String*
+d_to(dest *list)
+{
+ dest *np, *sp;
+ String *s;
+ int i, n;
+ char *cp;
+
+ s = s_new();
+ s_append(s, "To: ");
+ np = list;
+ i = n = 0;
+ do {
+ np = np->next;
+ sp = np;
+ do {
+ sp = sp->same;
+ cp = s_to_c(sp->addr);
+
+ /* hack to get local! out of the names */
+ if(strncmp(cp, "local!", 6) == 0)
+ cp += 6;
+
+ if(n > 20){ /* 20 to appease mailers complaining about long lines */
+ s_append(s, "\n\t");
+ n = 0;
+ }
+ if(i != 0){
+ s_append(s, ", ");
+ n += 2;
+ }
+ s_append(s, cp);
+ n += strlen(cp);
+ i++;
+ } while(sp != np);
+ } while(np != list);
+
+ return unescapespecial(s);
+}
+
+/* expand a String of destinations into a linked list of destiniations */
+extern dest *
+s_to_dest(String *sp, dest *parent)
+{
+ String *addr;
+ dest *list=0;
+ dest *new;
+
+ if (sp == 0)
+ return 0;
+ addr = s_new();
+ while (s_parseq(sp, addr)!=0) {
+ addr = escapespecial(addr);
+ if(shellchars(s_to_c(addr))){
+ while(new = d_rm(&list))
+ d_free(new);
+ break;
+ }
+ new = d_new(addr);
+ new->parent = parent;
+ new->authorized = parent->authorized;
+ d_insert(&list, new);
+ addr = s_new();
+ }
+ s_free(addr);
+ return list;
+}
+
+#undef isspace
+#define isspace(c) ((c)==' ' || (c)=='\t' || (c)=='\n')
+
+/* Get the next field from a String. The field is delimited by white space.
+ * Anything delimited by double quotes is included in the string.
+ */
+static String*
+s_parseq(String *from, String *to)
+{
+ int c;
+
+ if (*from->ptr == '\0')
+ return 0;
+ if (to == 0)
+ to = s_new();
+ for (c = *from->ptr;!isspace(c) && c != 0; c = *(++from->ptr)){
+ s_putc(to, c);
+ if(c == '"'){
+ for (c = *(++from->ptr); c && c != '"'; c = *(++from->ptr))
+ s_putc(to, *from->ptr);
+ s_putc(to, '"');
+ if(c == 0)
+ break;
+ }
+ }
+ s_terminate(to);
+
+ /* crunch trailing white */
+ while(isspace(*from->ptr))
+ from->ptr++;
+
+ return to;
+}
diff --git a/src/cmd/upas/send/filter.c b/src/cmd/upas/send/filter.c
new file mode 100644
index 00000000..cfee5253
--- /dev/null
+++ b/src/cmd/upas/send/filter.c
@@ -0,0 +1,128 @@
+#include "common.h"
+#include "send.h"
+
+Biobuf bin;
+int rmail, tflg;
+char *subjectarg;
+
+char *findbody(char*);
+
+void
+main(int argc, char *argv[])
+{
+ message *mp;
+ dest *dp;
+ Reprog *p;
+ Resub match[10];
+ char file[MAXPATHLEN];
+ Biobuf *fp;
+ char *rcvr, *cp;
+ Mlock *l;
+ String *tmp;
+ int i;
+ int header, body;
+
+ header = body = 0;
+ ARGBEGIN {
+ case 'h':
+ header = 1;
+ break;
+ case 'b':
+ header = 1;
+ body = 1;
+ break;
+ } ARGEND
+
+ Binit(&bin, 0, OREAD);
+ if(argc < 2){
+ fprint(2, "usage: filter rcvr mailfile [regexp mailfile ...]\n");
+ exits("usage");
+ }
+ mp = m_read(&bin, 1, 0);
+
+ /* get rid of local system name */
+ cp = strchr(s_to_c(mp->sender), '!');
+ if(cp){
+ cp++;
+ mp->sender = s_copy(cp);
+ }
+
+ dp = d_new(s_copy(argv[0]));
+ strecpy(file, file+sizeof file, argv[1]);
+ cp = findbody(s_to_c(mp->body));
+ for(i = 2; i < argc; i += 2){
+ p = regcomp(argv[i]);
+ if(p == 0)
+ continue;
+ if(regexec(p, s_to_c(mp->sender), match, 10)){
+ regsub(argv[i+1], file, sizeof(file), match, 10);
+ break;
+ }
+ if(header == 0 && body == 0)
+ continue;
+ if(regexec(p, s_to_c(mp->body), match, 10)){
+ if(body == 0 && match[0].s.sp >= cp)
+ continue;
+ regsub(argv[i+1], file, sizeof(file), match, 10);
+ break;
+ }
+ }
+
+ /*
+ * always lock the normal mail file to avoid too many lock files
+ * lying about. This isn't right but it's what the majority prefers.
+ */
+ l = syslock(argv[1]);
+ if(l == 0){
+ fprint(2, "can't lock mail file %s\n", argv[1]);
+ exit(1);
+ }
+
+ /*
+ * open the destination mail file
+ */
+ fp = sysopen(file, "ca", MBOXMODE);
+ if (fp == 0){
+ tmp = s_append(0, file);
+ s_append(tmp, ".tmp");
+ fp = sysopen(s_to_c(tmp), "cal", MBOXMODE);
+ if(fp == 0){
+ sysunlock(l);
+ fprint(2, "can't open mail file %s\n", file);
+ exit(1);
+ }
+ syslog(0, "mail", "error: used %s", s_to_c(tmp));
+ s_free(tmp);
+ }
+ Bseek(fp, 0, 2);
+ if(m_print(mp, fp, (char *)0, 1) < 0
+ || Bprint(fp, "\n") < 0
+ || Bflush(fp) < 0){
+ sysclose(fp);
+ sysunlock(l);
+ fprint(2, "can't write mail file %s\n", file);
+ exit(1);
+ }
+ sysclose(fp);
+
+ sysunlock(l);
+ rcvr = argv[0];
+ if(cp = strrchr(rcvr, '!'))
+ rcvr = cp+1;
+ logdelivery(dp, rcvr, mp);
+ exit(0);
+}
+
+char*
+findbody(char *p)
+{
+ if(*p == '\n')
+ return p;
+
+ while(*p){
+ if(*p == '\n' && *(p+1) == '\n')
+ return p+1;
+ p++;
+ }
+ return p;
+}
diff --git a/src/cmd/upas/send/gateway.c b/src/cmd/upas/send/gateway.c
new file mode 100644
index 00000000..f3b2b36d
--- /dev/null
+++ b/src/cmd/upas/send/gateway.c
@@ -0,0 +1,24 @@
+#include "common.h"
+#include "send.h"
+
+#undef isspace
+#define isspace(c) ((c)==' ' || (c)=='\t' || (c)=='\n')
+
+/*
+ * Translate the last component of the sender address. If the translation
+ * yields the same address, replace the sender with its last component.
+ */
+extern void
+gateway(message *mp)
+{
+ char *base;
+ String *s;
+
+ /* first remove all systems equivalent to us */
+ base = skipequiv(s_to_c(mp->sender));
+ if(base != s_to_c(mp->sender)){
+ s = mp->sender;
+ mp->sender = s_copy(base);
+ s_free(s);
+ }
+}
diff --git a/src/cmd/upas/send/local.c b/src/cmd/upas/send/local.c
new file mode 100644
index 00000000..93136b7c
--- /dev/null
+++ b/src/cmd/upas/send/local.c
@@ -0,0 +1,129 @@
+#include "common.h"
+#include "send.h"
+
+static void
+mboxfile(dest *dp, String *user, String *path, char *file)
+{
+ char *cp;
+
+ mboxpath(s_to_c(user), s_to_c(dp->addr), path, 0);
+ cp = strrchr(s_to_c(path), '/');
+ if(cp)
+ path->ptr = cp+1;
+ else
+ path->ptr = path->base;
+ s_append(path, file);
+}
+
+/*
+ * Check forwarding requests
+ */
+extern dest*
+expand_local(dest *dp)
+{
+ Biobuf *fp;
+ String *file, *line, *s;
+ dest *rv;
+ int forwardok;
+ char *user;
+
+ /* short circuit obvious security problems */
+ if(strstr(s_to_c(dp->addr), "/../")){
+ dp->status = d_unknown;
+ return 0;
+ }
+
+ /* isolate user's name if part of a path */
+ user = strrchr(s_to_c(dp->addr), '!');
+ if(user)
+ user++;
+ else
+ user = s_to_c(dp->addr);
+
+ /* if no replacement string, plug in user's name */
+ if(dp->repl1 == 0){
+ dp->repl1 = s_new();
+ mboxname(user, dp->repl1);
+ }
+
+ s = unescapespecial(s_clone(dp->repl1));
+
+ /*
+ * if this is the descendant of a `forward' file, don't
+ * look for a forward.
+ */
+ forwardok = 1;
+ for(rv = dp->parent; rv; rv = rv->parent)
+ if(rv->status == d_cat){
+ forwardok = 0;
+ break;
+ }
+ file = s_new();
+ if(forwardok){
+ /*
+ * look for `forward' file for forwarding address(es)
+ */
+ mboxfile(dp, s, file, "forward");
+ fp = sysopen(s_to_c(file), "r", 0);
+ if (fp != 0) {
+ line = s_new();
+ for(;;){
+ if(s_read_line(fp, line) == nil)
+ break;
+ if(*(line->ptr - 1) != '\n')
+ break;
+ if(*(line->ptr - 2) == '\\')
+ *(line->ptr-2) = ' ';
+ *(line->ptr-1) = ' ';
+ }
+ sysclose(fp);
+ if(debug)
+ fprint(2, "forward = %s\n", s_to_c(line));
+ rv = s_to_dest(s_restart(line), dp);
+ s_free(line);
+ if(rv){
+ s_free(file);
+ s_free(s);
+ return rv;
+ }
+ }
+ }
+
+ /*
+ * look for a 'pipe' file. This won't work if there are
+ * special characters in the account name since the file
+ * name passes through a shell. tdb.
+ */
+ mboxfile(dp, dp->repl1, s_reset(file), "pipeto");
+ if(sysexist(s_to_c(file))){
+ if(debug)
+ fprint(2, "found a pipeto file\n");
+ dp->status = d_pipeto;
+ line = s_new();
+ s_append(line, "upasname='");
+ s_append(line, user);
+ s_append(line, "' ");
+ s_append(line, s_to_c(file));
+ s_append(line, " ");
+ s_append(line, s_to_c(dp->addr));
+ s_append(line, " ");
+ s_append(line, s_to_c(dp->repl1));
+ s_free(dp->repl1);
+ dp->repl1 = line;
+ s_free(file);
+ s_free(s);
+ return dp;
+ }
+
+ /*
+ * see if the mailbox directory exists
+ */
+ mboxfile(dp, s, s_reset(file), ".");
+ if(sysexist(s_to_c(file)))
+ dp->status = d_cat;
+ else
+ dp->status = d_unknown;
+ s_free(file);
+ s_free(s);
+ return 0;
+}
diff --git a/src/cmd/upas/send/log.c b/src/cmd/upas/send/log.c
new file mode 100644
index 00000000..52df3800
--- /dev/null
+++ b/src/cmd/upas/send/log.c
@@ -0,0 +1,85 @@
+#include "common.h"
+#include "send.h"
+
+/* configuration */
+#define LOGBiobuf "log/status"
+
+/* log mail delivery */
+extern void
+logdelivery(dest *list, char *rcvr, message *mp)
+{
+ dest *parent;
+ String *srcvr, *sender;
+
+ srcvr = unescapespecial(s_copy(rcvr));
+ sender = unescapespecial(s_clone(mp->sender));
+
+ for(parent=list; parent->parent!=0; parent=parent->parent)
+ ;
+ if(parent!=list && strcmp(s_to_c(parent->addr), s_to_c(srcvr))!=0)
+ syslog(0, "mail", "delivered %s From %.256s %.256s (%.256s) %d",
+ rcvr,
+ s_to_c(sender), s_to_c(mp->date),
+ s_to_c(parent->addr), mp->size);
+ else
+ syslog(0, "mail", "delivered %s From %.256s %.256s %d", s_to_c(srcvr),
+ s_to_c(sender), s_to_c(mp->date), mp->size);
+ s_free(srcvr);
+ s_free(sender);
+}
+
+/* log mail forwarding */
+extern void
+loglist(dest *list, message *mp, char *tag)
+{
+ dest *next;
+ dest *parent;
+ String *srcvr, *sender;
+
+ sender = unescapespecial(s_clone(mp->sender));
+
+ for(next=d_rm(&list); next != 0; next = d_rm(&list)) {
+ for(parent=next; parent->parent!=0; parent=parent->parent)
+ ;
+ srcvr = unescapespecial(s_clone(next->addr));
+ if(parent!=next)
+ syslog(0, "mail", "%s %.256s From %.256s %.256s (%.256s) %d",
+ tag,
+ s_to_c(srcvr), s_to_c(sender),
+ s_to_c(mp->date), s_to_c(parent->addr), mp->size);
+ else
+ syslog(0, "mail", "%s %.256s From %.256s %.256s %d", tag,
+ s_to_c(srcvr), s_to_c(sender),
+ s_to_c(mp->date), mp->size);
+ s_free(srcvr);
+ }
+ s_free(sender);
+}
+
+/* log a mail refusal */
+extern void
+logrefusal(dest *dp, message *mp, char *msg)
+{
+ char buf[2048];
+ char *cp, *ep;
+ String *sender, *srcvr;
+
+ srcvr = unescapespecial(s_clone(dp->addr));
+ sender = unescapespecial(s_clone(mp->sender));
+
+ sprint(buf, "error %.256s From %.256s %.256s\nerror+ ", s_to_c(srcvr),
+ s_to_c(sender), s_to_c(mp->date));
+ s_free(srcvr);
+ s_free(sender);
+ cp = buf + strlen(buf);
+ ep = buf + sizeof(buf) - sizeof("error + ");
+ while(*msg && cp<ep) {
+ *cp++ = *msg;
+ if (*msg++ == '\n') {
+ strcpy(cp, "error+ ");
+ cp += sizeof("error+ ") - 1;
+ }
+ }
+ *cp = 0;
+ syslog(0, "mail", "%s", buf);
+}
diff --git a/src/cmd/upas/send/main.c b/src/cmd/upas/send/main.c
new file mode 100644
index 00000000..6c455833
--- /dev/null
+++ b/src/cmd/upas/send/main.c
@@ -0,0 +1,575 @@
+#include "common.h"
+#include "send.h"
+
+/* globals to all files */
+int rmail;
+char *thissys, *altthissys;
+int nflg;
+int xflg;
+int debug;
+int rflg;
+int iflg = 1;
+int nosummary;
+
+/* global to this file */
+static String *errstring;
+static message *mp;
+static int interrupt;
+static int savemail;
+static Biobuf in;
+static int forked;
+static int add822headers = 1;
+static String *arglist;
+
+/* predeclared */
+static int send(dest *, message *, int);
+static void lesstedious(void);
+static void save_mail(message *);
+static int complain_mail(dest *, message *);
+static int pipe_mail(dest *, message *);
+static void appaddr(String *, dest *);
+static void mkerrstring(String *, message *, dest *, dest *, char *, int);
+static int replymsg(String *, message *, dest *);
+static int catchint(void*, char*);
+
+void
+usage(void)
+{
+ fprint(2, "usage: mail [-birtx] list-of-addresses\n");
+ exits("usage");
+}
+
+void
+main(int argc, char *argv[])
+{
+ dest *dp=0;
+ int checkforward;
+ char *base;
+ int rv;
+
+ /* process args */
+ ARGBEGIN{
+ case '#':
+ nflg = 1;
+ break;
+ case 'b':
+ add822headers = 0;
+ break;
+ case 'x':
+ nflg = 1;
+ xflg = 1;
+ break;
+ case 'd':
+ debug = 1;
+ break;
+ case 'i':
+ iflg = 0;
+ break;
+ case 'r':
+ rflg = 1;
+ break;
+ default:
+ usage();
+ }ARGEND
+
+ while(*argv){
+ if(shellchars(*argv)){
+ fprint(2, "illegal characters in destination\n");
+ exits("syntax");
+ }
+ d_insert(&dp, d_new(s_copy(*argv++)));
+ }
+
+ if (dp == 0)
+ usage();
+ arglist = d_to(dp);
+
+ /*
+ * get context:
+ * - whether we're rmail or mail
+ */
+ base = basename(argv0);
+ checkforward = rmail = (strcmp(base, "rmail")==0) | rflg;
+ thissys = sysname_read();
+ altthissys = alt_sysname_read();
+ if(rmail)
+ add822headers = 0;
+
+ /*
+ * read the mail. If an interrupt occurs while reading, save in
+ * dead.letter
+ */
+ if (!nflg) {
+ Binit(&in, 0, OREAD);
+ if(!rmail)
+ atnotify(catchint, 1);
+ mp = m_read(&in, rmail, !iflg);
+ if (mp == 0)
+ exit(0);
+ if (interrupt != 0) {
+ save_mail(mp);
+ exit(1);
+ }
+ } else {
+ mp = m_new();
+ if(default_from(mp) < 0){
+ fprint(2, "%s: can't determine login name\n", argv0);
+ exit(1);
+ }
+ }
+ errstring = s_new();
+ getrules();
+
+ /*
+ * If this is a gateway, translate the sender address into a local
+ * address. This only happens if mail to the local address is
+ * forwarded to the sender.
+ */
+ gateway(mp);
+
+ /*
+ * Protect against shell characters in the sender name for
+ * security reasons.
+ */
+ mp->sender = escapespecial(mp->sender);
+ if (shellchars(s_to_c(mp->sender)))
+ mp->replyaddr = s_copy("postmaster");
+ else
+ mp->replyaddr = s_clone(mp->sender);
+
+ /*
+ * reject messages that have been looping for too long
+ */
+ if(mp->received > 32)
+ exit(refuse(dp, mp, "possible forward loop", 0, 0));
+
+ /*
+ * reject messages that are too long. We don't do it earlier
+ * in m_read since we haven't set up enough things yet.
+ */
+ if(mp->size < 0)
+ exit(refuse(dp, mp, "message too long", 0, 0));
+
+ rv = send(dp, mp, checkforward);
+ if(savemail)
+ save_mail(mp);
+ if(mp)
+ m_free(mp);
+ exit(rv);
+}
+
+/* send a message to a list of sites */
+static int
+send(dest *destp, message *mp, int checkforward)
+{
+ dest *dp; /* destination being acted upon */
+ dest *bound; /* bound destinations */
+ int errors=0;
+
+ /* bind the destinations to actions */
+ bound = up_bind(destp, mp, checkforward);
+ if(add822headers && mp->haveto == 0){
+ if(nosummary)
+ mp->to = d_to(bound);
+ else
+ mp->to = arglist;
+ }
+
+ /* loop through and execute commands */
+ for (dp = d_rm(&bound); dp != 0; dp = d_rm(&bound)) {
+ switch (dp->status) {
+ case d_cat:
+ errors += cat_mail(dp, mp);
+ break;
+ case d_pipeto:
+ case d_pipe:
+ if (!rmail && !nflg && !forked) {
+ forked = 1;
+ lesstedious();
+ }
+ errors += pipe_mail(dp, mp);
+ break;
+ default:
+ errors += complain_mail(dp, mp);
+ break;
+ }
+ }
+
+ return errors;
+}
+
+/* avoid user tedium (as Mike Lesk said in a previous version) */
+static void
+lesstedious(void)
+{
+ int i;
+
+ if(debug)
+ return;
+
+ switch(fork()){
+ case -1:
+ break;
+ case 0:
+ sysdetach();
+ for(i=0; i<3; i++)
+ close(i);
+ savemail = 0;
+ break;
+ default:
+ exit(0);
+ }
+}
+
+
+/* save the mail */
+static void
+save_mail(message *mp)
+{
+ Biobuf *fp;
+ String *file;
+
+ file = s_new();
+ deadletter(file);
+ fp = sysopen(s_to_c(file), "cAt", 0660);
+ if (fp == 0)
+ return;
+ m_bprint(mp, fp);
+ sysclose(fp);
+ fprint(2, "saved in %s\n", s_to_c(file));
+ s_free(file);
+}
+
+/* remember the interrupt happened */
+
+static int
+catchint(void *a, char *msg)
+{
+ USED(a);
+ if(strstr(msg, "interrupt") || strstr(msg, "hangup")) {
+ interrupt = 1;
+ return 1;
+ }
+ return 0;
+}
+
+/* dispose of incorrect addresses */
+static int
+complain_mail(dest *dp, message *mp)
+{
+ char *msg;
+
+ switch (dp->status) {
+ case d_undefined:
+ msg = "Invalid address"; /* a little different, for debugging */
+ break;
+ case d_syntax:
+ msg = "invalid address";
+ break;
+ case d_unknown:
+ msg = "unknown user";
+ break;
+ case d_eloop:
+ case d_loop:
+ msg = "forwarding loop";
+ break;
+ case d_noforward:
+ if(dp->pstat && *s_to_c(dp->repl2))
+ return refuse(dp, mp, s_to_c(dp->repl2), dp->pstat, 0);
+ else
+ msg = "destination unknown or forwarding disallowed";
+ break;
+ case d_pipe:
+ msg = "broken pipe";
+ break;
+ case d_cat:
+ msg = "broken cat";
+ break;
+ case d_translate:
+ if(dp->pstat && *s_to_c(dp->repl2))
+ return refuse(dp, mp, s_to_c(dp->repl2), dp->pstat, 0);
+ else
+ msg = "name translation failed";
+ break;
+ case d_alias:
+ msg = "broken alias";
+ break;
+ case d_badmbox:
+ msg = "corrupted mailbox";
+ break;
+ case d_resource:
+ return refuse(dp, mp, "out of some resource. Try again later.", 0, 1);
+ default:
+ msg = "unknown d_";
+ break;
+ }
+ if (nflg) {
+ print("%s: %s\n", msg, s_to_c(dp->addr));
+ return 0;
+ }
+ return refuse(dp, mp, msg, 0, 0);
+}
+
+/* dispose of remote addresses */
+static int
+pipe_mail(dest *dp, message *mp)
+{
+ dest *next, *list=0;
+ String *cmd;
+ process *pp;
+ int status;
+ char *none;
+ String *errstring=s_new();
+
+ if (dp->status == d_pipeto)
+ none = "none";
+ else
+ none = 0;
+ /*
+ * collect the arguments
+ */
+ next = d_rm_same(&dp);
+ if(xflg)
+ cmd = s_new();
+ else
+ cmd = s_clone(next->repl1);
+ for(; next != 0; next = d_rm_same(&dp)){
+ if(xflg){
+ s_append(cmd, s_to_c(next->addr));
+ s_append(cmd, "\n");
+ } else {
+ if (next->repl2 != 0) {
+ s_append(cmd, " ");
+ s_append(cmd, s_to_c(next->repl2));
+ }
+ }
+ d_insert(&list, next);
+ }
+
+ if (nflg) {
+ if(xflg)
+ print("%s", s_to_c(cmd));
+ else
+ print("%s\n", s_to_c(cmd));
+ s_free(cmd);
+ return 0;
+ }
+
+ /*
+ * run the process
+ */
+ pp = proc_start(s_to_c(cmd), instream(), 0, outstream(), 1, none);
+ if(pp==0 || pp->std[0]==0 || pp->std[2]==0)
+ return refuse(list, mp, "out of processes, pipes, or memory", 0, 1);
+ pipesig(0);
+ m_print(mp, pp->std[0]->fp, thissys, 0);
+ pipesigoff();
+ stream_free(pp->std[0]);
+ pp->std[0] = 0;
+ while(s_read_line(pp->std[2]->fp, errstring))
+ ;
+ status = proc_wait(pp);
+ proc_free(pp);
+ s_free(cmd);
+
+ /*
+ * return status
+ */
+ if (status != 0)
+ return refuse(list, mp, s_to_c(errstring), status, 0);
+ loglist(list, mp, "remote");
+ return 0;
+}
+
+static void
+appaddr(String *sp, dest *dp)
+{
+ dest *parent;
+ String *s;
+
+ if (dp->parent != 0) {
+ for(parent=dp->parent; parent->parent!=0; parent=parent->parent)
+ ;
+ s = unescapespecial(s_clone(parent->addr));
+ s_append(sp, s_to_c(s));
+ s_free(s);
+ s_append(sp, "' alias `");
+ }
+ s = unescapespecial(s_clone(dp->addr));
+ s_append(sp, s_to_c(s));
+ s_free(s);
+}
+
+/*
+ * reject delivery
+ *
+ * returns 0 - if mail has been disposed of
+ * other - if mail has not been disposed
+ */
+int
+refuse(dest *list, message *mp, char *cp, int status, int outofresources)
+{
+ String *errstring=s_new();
+ dest *dp;
+ int rv;
+
+ dp = d_rm(&list);
+ mkerrstring(errstring, mp, dp, list, cp, status);
+
+ /*
+ * log first in case we get into trouble
+ */
+ logrefusal(dp, mp, s_to_c(errstring));
+
+ /*
+ * bulk mail is never replied to, if we're out of resources,
+ * let the sender try again
+ */
+ if(rmail){
+ /* accept it or request a retry */
+ if(outofresources){
+ fprint(2, "Mail %s\n", s_to_c(errstring));
+ rv = 1; /* try again later */
+ } else if(mp->bulk)
+ rv = 0; /* silently discard bulk */
+ else
+ rv = replymsg(errstring, mp, dp); /* try later if we can't reply */
+ } else {
+ /* aysnchronous delivery only happens if !rmail */
+ if(forked){
+ /*
+ * if spun off for asynchronous delivery, we own the mail now.
+ * return it or dump it on the floor. rv really doesn't matter.
+ */
+ rv = 0;
+ if(!outofresources && !mp->bulk)
+ replymsg(errstring, mp, dp);
+ } else {
+ fprint(2, "Mail %s\n", s_to_c(errstring));
+ savemail = 1;
+ rv = 1;
+ }
+ }
+
+ s_free(errstring);
+ return rv;
+}
+
+/* make the error message */
+static void
+mkerrstring(String *errstring, message *mp, dest *dp, dest *list, char *cp, int status)
+{
+ dest *next;
+ char smsg[64];
+ String *sender;
+
+ sender = unescapespecial(s_clone(mp->sender));
+
+ /* list all aliases */
+ s_append(errstring, " from '");
+ s_append(errstring, s_to_c(sender));
+ s_append(errstring, "'\nto '");
+ appaddr(errstring, dp);
+ for(next = d_rm(&list); next != 0; next = d_rm(&list)) {
+ s_append(errstring, "'\nand '");
+ appaddr(errstring, next);
+ d_insert(&dp, next);
+ }
+ s_append(errstring, "'\nfailed with error '");
+ s_append(errstring, cp);
+ s_append(errstring, "'.\n");
+
+ /* >> and | deserve different flavored messages */
+ switch(dp->status) {
+ case d_pipe:
+ s_append(errstring, "The mailer `");
+ s_append(errstring, s_to_c(dp->repl1));
+ sprint(smsg, "' returned error status %x.\n\n", status);
+ s_append(errstring, smsg);
+ break;
+ }
+
+ s_free(sender);
+}
+
+/*
+ * create a new boundary
+ */
+static String*
+mkboundary(void)
+{
+ char buf[32];
+ int i;
+ static int already;
+
+ if(already == 0){
+ srand((time(0)<<16)|getpid());
+ already = 1;
+ }
+ strcpy(buf, "upas-");
+ for(i = 5; i < sizeof(buf)-1; i++)
+ buf[i] = 'a' + nrand(26);
+ buf[i] = 0;
+ return s_copy(buf);
+}
+
+/*
+ * reply with up to 1024 characters of the
+ * original message
+ */
+static int
+replymsg(String *errstring, message *mp, dest *dp)
+{
+ message *refp = m_new();
+ dest *ndp;
+ char *rcvr;
+ int rv;
+ String *boundary;
+
+ boundary = mkboundary();
+
+ refp->bulk = 1;
+ refp->rfc822headers = 1;
+ rcvr = dp->status==d_eloop ? "postmaster" : s_to_c(mp->replyaddr);
+ ndp = d_new(s_copy(rcvr));
+ s_append(refp->sender, "postmaster");
+ s_append(refp->replyaddr, "/dev/null");
+ s_append(refp->date, thedate());
+ refp->haveto = 1;
+ s_append(refp->body, "To: ");
+ s_append(refp->body, rcvr);
+ s_append(refp->body, "\n");
+ s_append(refp->body, "Subject: bounced mail\n");
+ s_append(refp->body, "MIME-Version: 1.0\n");
+ s_append(refp->body, "Content-Type: multipart/mixed;\n");
+ s_append(refp->body, "\tboundary=\"");
+ s_append(refp->body, s_to_c(boundary));
+ s_append(refp->body, "\"\n");
+ s_append(refp->body, "Content-Disposition: inline\n");
+ s_append(refp->body, "\n");
+ s_append(refp->body, "This is a multi-part message in MIME format.\n");
+ s_append(refp->body, "--");
+ s_append(refp->body, s_to_c(boundary));
+ s_append(refp->body, "\n");
+ s_append(refp->body, "Content-Disposition: inline\n");
+ s_append(refp->body, "Content-Type: text/plain; charset=\"US-ASCII\"\n");
+ s_append(refp->body, "Content-Transfer-Encoding: 7bit\n");
+ s_append(refp->body, "\n");
+ s_append(refp->body, "The attached mail");
+ s_append(refp->body, s_to_c(errstring));
+ s_append(refp->body, "--");
+ s_append(refp->body, s_to_c(boundary));
+ s_append(refp->body, "\n");
+ s_append(refp->body, "Content-Type: message/rfc822\n");
+ s_append(refp->body, "Content-Disposition: inline\n\n");
+ s_append(refp->body, s_to_c(mp->body));
+ s_append(refp->body, "--");
+ s_append(refp->body, s_to_c(boundary));
+ s_append(refp->body, "--\n");
+
+ refp->size = s_len(refp->body);
+ rv = send(ndp, refp, 0);
+ m_free(refp);
+ d_free(ndp);
+ return rv;
+}
diff --git a/src/cmd/upas/send/makefile b/src/cmd/upas/send/makefile
new file mode 100644
index 00000000..f0abcf0c
--- /dev/null
+++ b/src/cmd/upas/send/makefile
@@ -0,0 +1,46 @@
+SSRC= message.c main.c bind.c rewrite.c local.c dest.c process.c translate.c\
+ log.c chkfwd.c notify.c gateway.c authorize.o ../common/*.c
+SOBJ= message.o main.o bind.o rewrite.o local.o dest.o process.o translate.o\
+ log.o chkfwd.o notify.o gateway.o authorize.o\
+ ../config/config.o ../common/common.a ../libc/libc.a
+SINC= ../common/mail.h ../common/string.h ../common/aux.h
+CFLAGS=${UNIX} -g -I. -I../libc -I../common -I/usr/include ${SCFLAGS}
+LFLAGS=-g
+.c.o: ; $(CC) -c $(CFLAGS) $*.c
+LIB=/usr/lib/upas
+
+all: send
+
+send: $(SOBJ)
+ $(CC) $(SOBJ) $(LFLAGS) -o send
+
+chkfwd.o: $(SINC) message.h dest.h
+dest.o: $(SINC) dest.h
+local.o: $(SINC) dest.h process.h
+log.o: $(SINC) message.h
+main.o: $(SINC) message.h dest.h process.h
+bind.o: $(SINC) dest.h message.h
+process.o: $(SINC) process.h
+rewrite.o: $(SINC) dest.h
+translate.o: $(SINC) dest.h process.h
+message.o: $(SINC) message.h
+notify.o: $(SINC) message.h
+gateway.o: $(SINC) dest.h message.h
+
+prcan:
+ prcan $(SSRC)
+
+clean:
+ -rm -f send *.[oO] a.out core *.sL rmail
+
+cyntax:
+ cyntax $(CFLAGS) $(SSRC)
+
+install: send
+ rm -f $(LIB)/send /bin/rmail
+ cp send $(LIB)/send
+ cp send /bin/rmail
+ strip /bin/rmail
+ strip $(LIB)/send
+ chown root $(LIB)/send /bin/rmail
+ chmod 4755 $(LIB)/send /bin/rmail
diff --git a/src/cmd/upas/send/message.c b/src/cmd/upas/send/message.c
new file mode 100644
index 00000000..eab6160c
--- /dev/null
+++ b/src/cmd/upas/send/message.c
@@ -0,0 +1,573 @@
+#include "common.h"
+#include "send.h"
+
+#include "../smtp/smtp.h"
+#include "../smtp/y.tab.h"
+
+/* global to this file */
+static Reprog *rfprog;
+static Reprog *fprog;
+
+#define VMLIMIT (64*1024)
+#define MSGLIMIT (128*1024*1024)
+
+int received; /* from rfc822.y */
+
+static String* getstring(Node *p);
+static String* getaddr(Node *p);
+
+extern int
+default_from(message *mp)
+{
+ char *cp, *lp;
+
+ cp = getenv("upasname");
+ lp = getlog();
+ if(lp == nil)
+ return -1;
+
+ if(cp && *cp)
+ s_append(mp->sender, cp);
+ else
+ s_append(mp->sender, lp);
+ s_append(mp->date, thedate());
+ return 0;
+}
+
+extern message *
+m_new(void)
+{
+ message *mp;
+
+ mp = (message *)mallocz(sizeof(message), 1);
+ if (mp == 0) {
+ perror("message:");
+ exit(1);
+ }
+ mp->sender = s_new();
+ mp->replyaddr = s_new();
+ mp->date = s_new();
+ mp->body = s_new();
+ mp->size = 0;
+ mp->fd = -1;
+ return mp;
+}
+
+extern void
+m_free(message *mp)
+{
+ if(mp->fd >= 0){
+ close(mp->fd);
+ sysremove(s_to_c(mp->tmp));
+ s_free(mp->tmp);
+ }
+ s_free(mp->sender);
+ s_free(mp->date);
+ s_free(mp->body);
+ s_free(mp->havefrom);
+ s_free(mp->havesender);
+ s_free(mp->havereplyto);
+ s_free(mp->havesubject);
+ free((char *)mp);
+}
+
+/* read a message into a temp file , return an open fd to it */
+static int
+m_read_to_file(Biobuf *fp, message *mp)
+{
+ int fd;
+ int n;
+ String *file;
+ char buf[4*1024];
+
+ file = s_new();
+ /*
+ * create temp file to be remove on close
+ */
+ abspath("mtXXXXXX", UPASTMP, file);
+ mktemp(s_to_c(file));
+ if((fd = syscreate(s_to_c(file), ORDWR|ORCLOSE, 0600))<0){
+ s_free(file);
+ return -1;
+ }
+ mp->tmp = file;
+
+ /*
+ * read the rest into the temp file
+ */
+ while((n = Bread(fp, buf, sizeof(buf))) > 0){
+ if(write(fd, buf, n) != n){
+ close(fd);
+ return -1;
+ }
+ mp->size += n;
+ if(mp->size > MSGLIMIT){
+ mp->size = -1;
+ break;
+ }
+ }
+
+ mp->fd = fd;
+ return 0;
+}
+
+/* get the first address from a node */
+static String*
+getaddr(Node *p)
+{
+ for(; p; p = p->next)
+ if(p->s && p->addr)
+ return s_copy(s_to_c(p->s));
+}
+
+/* get the text of a header line minus the field name */
+static String*
+getstring(Node *p)
+{
+ String *s;
+
+ s = s_new();
+ if(p == nil)
+ return s;
+
+ for(p = p->next; p; p = p->next){
+ if(p->s){
+ s_append(s, s_to_c(p->s));
+ }else{
+ s_putc(s, p->c);
+ s_terminate(s);
+ }
+ if(p->white)
+ s_append(s, s_to_c(p->white));
+ }
+ return s;
+}
+
+#if 0 /* jpc */
+static char *fieldname[] =
+{
+[WORD-WORD] "WORD",
+[DATE-WORD] "DATE",
+[RESENT_DATE-WORD] "RESENT_DATE",
+[RETURN_PATH-WORD] "RETURN_PATH",
+[FROM-WORD] "FROM",
+[SENDER-WORD] "SENDER",
+[REPLY_TO-WORD] "REPLY_TO",
+[RESENT_FROM-WORD] "RESENT_FROM",
+[RESENT_SENDER-WORD] "RESENT_SENDER",
+[RESENT_REPLY_TO-WORD] "RESENT_REPLY_TO",
+[SUBJECT-WORD] "SUBJECT",
+[TO-WORD] "TO",
+[CC-WORD] "CC",
+[BCC-WORD] "BCC",
+[RESENT_TO-WORD] "RESENT_TO",
+[RESENT_CC-WORD] "RESENT_CC",
+[RESENT_BCC-WORD] "RESENT_BCC",
+[REMOTE-WORD] "REMOTE",
+[PRECEDENCE-WORD] "PRECEDENCE",
+[MIMEVERSION-WORD] "MIMEVERSION",
+[CONTENTTYPE-WORD] "CONTENTTYPE",
+[MESSAGEID-WORD] "MESSAGEID",
+[RECEIVED-WORD] "RECEIVED",
+[MAILER-WORD] "MAILER",
+[BADTOKEN-WORD] "BADTOKEN",
+};
+#endif /* jpc */
+
+/* fix 822 addresses */
+static void
+rfc822cruft(message *mp)
+{
+ Field *f;
+ Node *p;
+ String *body, *s;
+ char *cp;
+
+ /*
+ * parse headers in in-core part
+ */
+ yyinit(s_to_c(mp->body), s_len(mp->body));
+ mp->rfc822headers = 0;
+ yyparse();
+ mp->rfc822headers = 1;
+ mp->received = received;
+
+ /*
+ * remove equivalent systems in all addresses
+ */
+ body = s_new();
+ cp = s_to_c(mp->body);
+ for(f = firstfield; f; f = f->next){
+ if(f->node->c == MIMEVERSION)
+ mp->havemime = 1;
+ if(f->node->c == FROM)
+ mp->havefrom = getaddr(f->node);
+ if(f->node->c == SENDER)
+ mp->havesender = getaddr(f->node);
+ if(f->node->c == REPLY_TO)
+ mp->havereplyto = getaddr(f->node);
+ if(f->node->c == TO)
+ mp->haveto = 1;
+ if(f->node->c == DATE)
+ mp->havedate = 1;
+ if(f->node->c == SUBJECT)
+ mp->havesubject = getstring(f->node);
+ if(f->node->c == PRECEDENCE && f->node->next && f->node->next->next){
+ s = f->node->next->next->s;
+ if(s && (strcmp(s_to_c(s), "bulk") == 0
+ || strcmp(s_to_c(s), "Bulk") == 0))
+ mp->bulk = 1;
+ }
+ for(p = f->node; p; p = p->next){
+ if(p->s){
+ if(p->addr){
+ cp = skipequiv(s_to_c(p->s));
+ s_append(body, cp);
+ } else
+ s_append(body, s_to_c(p->s));
+ }else{
+ s_putc(body, p->c);
+ s_terminate(body);
+ }
+ if(p->white)
+ s_append(body, s_to_c(p->white));
+ cp = p->end+1;
+ }
+ s_append(body, "\n");
+ }
+
+ if(*s_to_c(body) == 0){
+ s_free(body);
+ return;
+ }
+
+ if(*cp != '\n')
+ s_append(body, "\n");
+ s_memappend(body, cp, s_len(mp->body) - (cp - s_to_c(mp->body)));
+ s_terminate(body);
+
+ firstfield = 0;
+ mp->size += s_len(body) - s_len(mp->body);
+ s_free(mp->body);
+ mp->body = body;
+}
+
+/* read in a message, interpret the 'From' header */
+extern message *
+m_read(Biobuf *fp, int rmail, int interactive)
+{
+ message *mp;
+ Resub subexp[10];
+ char *line;
+ int first;
+ int n;
+
+ mp = m_new();
+
+ /* parse From lines if remote */
+ if (rmail) {
+ /* get remote address */
+ String *sender=s_new();
+
+ if (rfprog == 0)
+ rfprog = regcomp(REMFROMRE);
+ first = 1;
+ while(s_read_line(fp, s_restart(mp->body)) != 0) {
+ memset(subexp, 0, sizeof(subexp));
+ if (regexec(rfprog, s_to_c(mp->body), subexp, 10) == 0){
+ if(first == 0)
+ break;
+ if (fprog == 0)
+ fprog = regcomp(FROMRE);
+ memset(subexp, 0, sizeof(subexp));
+ if(regexec(fprog, s_to_c(mp->body), subexp,10) == 0)
+ break;
+ s_restart(mp->body);
+ append_match(subexp, s_restart(sender), SENDERMATCH);
+ append_match(subexp, s_restart(mp->date), DATEMATCH);
+ break;
+ }
+ append_match(subexp, s_restart(sender), REMSENDERMATCH);
+ append_match(subexp, s_restart(mp->date), REMDATEMATCH);
+ if(subexp[REMSYSMATCH].s.sp!=subexp[REMSYSMATCH].e.ep){
+ append_match(subexp, mp->sender, REMSYSMATCH);
+ s_append(mp->sender, "!");
+ }
+ first = 0;
+ }
+ s_append(mp->sender, s_to_c(sender));
+
+ s_free(sender);
+ }
+ if(*s_to_c(mp->sender)=='\0')
+ default_from(mp);
+
+ /* if sender address is unreturnable, treat message as bulk mail */
+ if(!returnable(s_to_c(mp->sender)))
+ mp->bulk = 1;
+
+ /* get body */
+ if(interactive && !rmail){
+ /* user typing on terminal: terminator == '.' or EOF */
+ for(;;) {
+ line = s_read_line(fp, mp->body);
+ if (line == 0)
+ break;
+ if (strcmp(".\n", line)==0) {
+ mp->body->ptr -= 2;
+ *mp->body->ptr = '\0';
+ break;
+ }
+ }
+ mp->size = mp->body->ptr - mp->body->base;
+ } else {
+ /*
+ * read up to VMLIMIT bytes (more or less) into main memory.
+ * if message is longer put the rest in a tmp file.
+ */
+ mp->size = mp->body->ptr - mp->body->base;
+ n = s_read(fp, mp->body, VMLIMIT);
+ if(n < 0){
+ perror("m_read");
+ exit(1);
+ }
+ mp->size += n;
+ if(n == VMLIMIT){
+ if(m_read_to_file(fp, mp) < 0){
+ perror("m_read");
+ exit(1);
+ }
+ }
+
+ }
+
+ /*
+ * ignore 0 length messages from a terminal
+ */
+ if (!rmail && mp->size == 0)
+ return 0;
+
+ rfc822cruft(mp);
+
+ return mp;
+}
+
+/* return a piece of message starting at `offset' */
+extern int
+m_get(message *mp, long offset, char **pp)
+{
+ static char buf[4*1024];
+
+ /*
+ * are we past eof?
+ */
+ if(offset >= mp->size)
+ return 0;
+
+ /*
+ * are we in the virtual memory portion?
+ */
+ if(offset < s_len(mp->body)){
+ *pp = mp->body->base + offset;
+ return mp->body->ptr - mp->body->base - offset;
+ }
+
+ /*
+ * read it from the temp file
+ */
+ offset -= s_len(mp->body);
+ if(mp->fd < 0)
+ return -1;
+ if(seek(mp->fd, offset, 0)<0)
+ return -1;
+ *pp = buf;
+ return read(mp->fd, buf, sizeof buf);
+}
+
+/* output the message body without ^From escapes */
+static int
+m_noescape(message *mp, Biobuf *fp)
+{
+ long offset;
+ int n;
+ char *p;
+
+ for(offset = 0; offset < mp->size; offset += n){
+ n = m_get(mp, offset, &p);
+ if(n <= 0){
+ Bflush(fp);
+ return -1;
+ }
+ if(Bwrite(fp, p, n) < 0)
+ return -1;
+ }
+ return Bflush(fp);
+}
+
+/*
+ * Output the message body with '^From ' escapes.
+ * Ensures that any line starting with a 'From ' gets a ' ' stuck
+ * in front of it.
+ */
+static int
+m_escape(message *mp, Biobuf *fp)
+{
+ char *p, *np;
+ char *end;
+ long offset;
+ int m, n;
+ char *start;
+
+ for(offset = 0; offset < mp->size; offset += n){
+ n = m_get(mp, offset, &start);
+ if(n < 0){
+ Bflush(fp);
+ return -1;
+ }
+
+ p = start;
+ for(end = p+n; p < end; p += m){
+ np = memchr(p, '\n', end-p);
+ if(np == 0){
+ Bwrite(fp, p, end-p);
+ break;
+ }
+ m = np - p + 1;
+ if(m > 5 && strncmp(p, "From ", 5) == 0)
+ Bputc(fp, ' ');
+ Bwrite(fp, p, m);
+ }
+ }
+ Bflush(fp);
+ return 0;
+}
+
+static int
+printfrom(message *mp, Biobuf *fp)
+{
+ String *s;
+ int rv;
+
+ if(!returnable(s_to_c(mp->sender)))
+ return Bprint(fp, "From: Postmaster\n");
+
+ s = username(mp->sender);
+ if(s) {
+ s_append(s, " <");
+ s_append(s, s_to_c(mp->sender));
+ s_append(s, ">");
+ } else {
+ s = s_copy(s_to_c(mp->sender));
+ }
+ s = unescapespecial(s);
+ rv = Bprint(fp, "From: %s\n", s_to_c(s));
+ s_free(s);
+ return rv;
+}
+
+static char *
+rewritezone(char *z)
+{
+ int mindiff;
+ char s;
+ Tm *tm;
+ static char x[7];
+
+ tm = localtime(time(0));
+ mindiff = tm->tzoff/60;
+
+ /* if not in my timezone, don't change anything */
+ if(strcmp(tm->zone, z) != 0)
+ return z;
+
+ if(mindiff < 0){
+ s = '-';
+ mindiff = -mindiff;
+ } else
+ s = '+';
+
+ sprint(x, "%c%.2d%.2d", s, mindiff/60, mindiff%60);
+ return x;
+}
+
+int
+isutf8(String *s)
+{
+ char *p;
+
+ for(p = s_to_c(s); *p; p++)
+ if(*p&0x80)
+ return 1;
+ return 0;
+}
+
+void
+printutf8mime(Biobuf *b)
+{
+ Bprint(b, "MIME-Version: 1.0\n");
+ Bprint(b, "Content-Type: text/plain; charset=\"UTF-8\"\n");
+ Bprint(b, "Content-Transfer-Encoding: 8bit\n");
+}
+
+/* output a message */
+extern int
+m_print(message *mp, Biobuf *fp, char *remote, int mbox)
+{
+ String *date, *sender;
+ char *f[6];
+ int n;
+
+ sender = unescapespecial(s_clone(mp->sender));
+
+ if (remote != 0){
+ if(print_remote_header(fp,s_to_c(sender),s_to_c(mp->date),remote) < 0){
+ s_free(sender);
+ return -1;
+ }
+ } else {
+ if(print_header(fp, s_to_c(sender), s_to_c(mp->date)) < 0){
+ s_free(sender);
+ return -1;
+ }
+ }
+ s_free(sender);
+ if(!rmail && !mp->havedate){
+ /* add a date: line Date: Sun, 19 Apr 1998 12:27:52 -0400 */
+ date = s_copy(s_to_c(mp->date));
+ n = getfields(s_to_c(date), f, 6, 1, " \t");
+ if(n == 6)
+ Bprint(fp, "Date: %s, %s %s %s %s %s\n", f[0], f[2], f[1],
+ f[5], f[3], rewritezone(f[4]));
+ }
+ if(!rmail && !mp->havemime && isutf8(mp->body))
+ printutf8mime(fp);
+ if(mp->to){
+ /* add the to: line */
+ if (Bprint(fp, "%s\n", s_to_c(mp->to)) < 0)
+ return -1;
+ /* add the from: line */
+ if (!mp->havefrom && printfrom(mp, fp) < 0)
+ return -1;
+ if(!mp->rfc822headers && *s_to_c(mp->body) != '\n')
+ if (Bprint(fp, "\n") < 0)
+ return -1;
+ } else if(!rmail){
+ /* add the from: line */
+ if (!mp->havefrom && printfrom(mp, fp) < 0)
+ return -1;
+ if(!mp->rfc822headers && *s_to_c(mp->body) != '\n')
+ if (Bprint(fp, "\n") < 0)
+ return -1;
+ }
+
+ if (!mbox)
+ return m_noescape(mp, fp);
+ return m_escape(mp, fp);
+}
+
+/* print just the message body */
+extern int
+m_bprint(message *mp, Biobuf *fp)
+{
+ return m_noescape(mp, fp);
+}
diff --git a/src/cmd/upas/send/mkfile b/src/cmd/upas/send/mkfile
new file mode 100644
index 00000000..a49fde17
--- /dev/null
+++ b/src/cmd/upas/send/mkfile
@@ -0,0 +1,52 @@
+<$PLAN9/src/mkhdr
+
+TARG=send\
+ filter
+
+UOFILES=message.$O\
+ dest.$O\
+ log.$O\
+ skipequiv.$O\
+
+OFILES=\
+ $UOFILES\
+ ../smtp/rfc822.tab.$O\
+
+SMOBJ=main.$O\
+ bind.$O\
+ rewrite.$O\
+ local.$O\
+ translate.$O\
+ authorize.$O\
+ gateway.$O\
+ cat_mail.$O\
+
+LIB=../common/libcommon.av\
+
+HFILES=send.h\
+ ../common/common.h\
+ ../common/sys.h\
+
+LIB=../common/libcommon.a\
+
+BIN=$PLAN9/bin/upas
+UPDATE=\
+ mkfile\
+ $HFILES\
+ ${UOFILES:%.$O=%.c}\
+ ${SMOBJ:%.$O=%.c}\
+ ${TARG:%=%.c}\
+
+<$PLAN9/src/mkmany
+CFLAGS=$CFLAGS -I../common
+
+$O.send: $SMOBJ $OFILES
+ $LD $LDFLAGS -o $target $prereq $LIB
+
+message.$O: ../smtp/y.tab.h
+
+../smtp/y.tab.h ../smtp/rfc822.tab.$O: ../smtp/rfc822.y
+# @{
+ cd ../smtp
+ mk rfc822.tab.$O
+# }
diff --git a/src/cmd/upas/send/regtest.c b/src/cmd/upas/send/regtest.c
new file mode 100644
index 00000000..52e31258
--- /dev/null
+++ b/src/cmd/upas/send/regtest.c
@@ -0,0 +1,36 @@
+#include <u.h>
+#include <libc.h>
+#include <regexp.h>
+#include <bio.h>
+
+main(void)
+{
+ char *re;
+ char *line;
+ Reprog *prog;
+ char *cp;
+ Biobuf in;
+
+ Binit(&in, 0, OREAD);
+ print("re> ");
+ while(re = Brdline(&in, '\n')){
+ re[Blinelen(&in)-1] = 0;
+ if(*re == 0)
+ break;
+ prog = regcomp(re);
+ print("> ");
+ while(line = Brdline(&in, '\n')){
+ line[Blinelen(&in)-1] = 0;
+ if(cp = strchr(line, '\n'))
+ *cp = 0;
+ if(*line == 0)
+ break;
+ if(regexec(prog, line, 0))
+ print("yes\n");
+ else
+ print("no\n");
+ print("> ");
+ }
+ print("re> ");
+ }
+}
diff --git a/src/cmd/upas/send/rewrite.c b/src/cmd/upas/send/rewrite.c
new file mode 100644
index 00000000..4f40b293
--- /dev/null
+++ b/src/cmd/upas/send/rewrite.c
@@ -0,0 +1,315 @@
+#include "common.h"
+#include "send.h"
+
+extern int debug;
+
+/*
+ * Routines for dealing with the rewrite rules.
+ */
+
+/* globals */
+typedef struct rule rule;
+
+#define NSUBEXP 10
+struct rule {
+ String *matchre; /* address match */
+ String *repl1; /* first replacement String */
+ String *repl2; /* second replacement String */
+ d_status type; /* type of rule */
+ Reprog *program;
+ Resub subexp[NSUBEXP];
+ rule *next;
+};
+static rule *rulep;
+static rule *rlastp;
+
+/* predeclared */
+static String *substitute(String *, Resub *, message *);
+static rule *findrule(String *, int);
+
+
+/*
+ * Get the next token from `line'. The symbol `\l' is replaced by
+ * the name of the local system.
+ */
+extern String *
+rule_parse(String *line, char *system, int *backl)
+{
+ String *token;
+ String *expanded;
+ char *cp;
+
+ token = s_parse(line, 0);
+ if(token == 0)
+ return(token);
+ if(strchr(s_to_c(token), '\\')==0)
+ return(token);
+ expanded = s_new();
+ for(cp = s_to_c(token); *cp; cp++) {
+ if(*cp == '\\') switch(*++cp) {
+ case 'l':
+ s_append(expanded, system);
+ *backl = 1;
+ break;
+ case '\\':
+ s_putc(expanded, '\\');
+ break;
+ default:
+ s_putc(expanded, '\\');
+ s_putc(expanded, *cp);
+ break;
+ } else
+ s_putc(expanded, *cp);
+ }
+ s_free(token);
+ s_terminate(expanded);
+ return(expanded);
+}
+
+static int
+getrule(String *line, String *type, char *system)
+{
+ rule *rp;
+ String *re;
+ int backl;
+
+ backl = 0;
+
+ /* get a rule */
+ re = rule_parse(s_restart(line), system, &backl);
+ if(re == 0)
+ return 0;
+ rp = (rule *)malloc(sizeof(rule));
+ if(rp == 0) {
+ perror("getrules:");
+ exit(1);
+ }
+ rp->next = 0;
+ s_tolower(re);
+ rp->matchre = s_new();
+ s_append(rp->matchre, s_to_c(re));
+ s_restart(rp->matchre);
+ s_free(re);
+ s_parse(line, s_restart(type));
+ rp->repl1 = rule_parse(line, system, &backl);
+ rp->repl2 = rule_parse(line, system, &backl);
+ rp->program = 0;
+ if(strcmp(s_to_c(type), "|") == 0)
+ rp->type = d_pipe;
+ else if(strcmp(s_to_c(type), ">>") == 0)
+ rp->type = d_cat;
+ else if(strcmp(s_to_c(type), "alias") == 0)
+ rp->type = d_alias;
+ else if(strcmp(s_to_c(type), "translate") == 0)
+ rp->type = d_translate;
+ else if(strcmp(s_to_c(type), "auth") == 0)
+ rp->type = d_auth;
+ else {
+ s_free(rp->matchre);
+ s_free(rp->repl1);
+ s_free(rp->repl2);
+ free((char *)rp);
+ fprint(2,"illegal rewrite rule: %s\n", s_to_c(line));
+ return 0;
+ }
+ if(rulep == 0)
+ rulep = rlastp = rp;
+ else
+ rlastp = rlastp->next = rp;
+ return backl;
+}
+
+/*
+ * rules are of the form:
+ * <reg exp> <String> <repl exp> [<repl exp>]
+ */
+extern int
+getrules(void)
+{
+ Biobuf *rfp;
+ String *line;
+ String *type;
+ String *file;
+
+ file = abspath("rewrite", unsharp(UPASLIB), (String *)0);
+ rfp = sysopen(s_to_c(file), "r", 0);
+ if(rfp == 0) {
+ rulep = 0;
+ return -1;
+ }
+ rlastp = 0;
+ line = s_new();
+ type = s_new();
+ while(s_getline(rfp, s_restart(line)))
+ if(getrule(line, type, thissys) && altthissys)
+ getrule(s_restart(line), type, altthissys);
+ s_free(type);
+ s_free(line);
+ s_free(file);
+ sysclose(rfp);
+ return 0;
+}
+
+/* look up a matching rule */
+static rule *
+findrule(String *addrp, int authorized)
+{
+ rule *rp;
+ static rule defaultrule;
+
+ if(rulep == 0)
+ return &defaultrule;
+ for (rp = rulep; rp != 0; rp = rp->next) {
+ if(rp->type==d_auth && authorized)
+ continue;
+ if(rp->program == 0)
+ rp->program = regcomp(rp->matchre->base);
+ if(rp->program == 0)
+ continue;
+ memset(rp->subexp, 0, sizeof(rp->subexp));
+ if(debug)
+ print("matching %s aginst %s\n", s_to_c(addrp), rp->matchre->base);
+ if(regexec(rp->program, s_to_c(addrp), rp->subexp, NSUBEXP))
+ if(s_to_c(addrp) == rp->subexp[0].s.sp)
+ if((s_to_c(addrp) + strlen(s_to_c(addrp))) == rp->subexp[0].e.ep)
+ return rp;
+ }
+ return 0;
+}
+
+/* Transforms the address into a command.
+ * Returns: -1 ifaddress not matched by reules
+ * 0 ifaddress matched and ok to forward
+ * 1 ifaddress matched and not ok to forward
+ */
+extern int
+rewrite(dest *dp, message *mp)
+{
+ rule *rp; /* rewriting rule */
+ String *lower; /* lower case version of destination */
+
+ /*
+ * Rewrite the address. Matching is case insensitive.
+ */
+ lower = s_clone(dp->addr);
+ s_tolower(s_restart(lower));
+ rp = findrule(lower, dp->authorized);
+ if(rp == 0){
+ s_free(lower);
+ return -1;
+ }
+ strcpy(s_to_c(lower), s_to_c(dp->addr));
+ dp->repl1 = substitute(rp->repl1, rp->subexp, mp);
+ dp->repl2 = substitute(rp->repl2, rp->subexp, mp);
+ dp->status = rp->type;
+ if(debug){
+ print("\t->");
+ if(dp->repl1)
+ print("%s", s_to_c(dp->repl1));
+ if(dp->repl2)
+ print("%s", s_to_c(dp->repl2));
+ print("\n");
+ }
+ s_free(lower);
+ return 0;
+}
+
+static String *
+substitute(String *source, Resub *subexp, message *mp)
+{
+ int i;
+ char *s;
+ char *sp;
+ String *stp;
+
+ if(source == 0)
+ return 0;
+ sp = s_to_c(source);
+
+ /* someplace to put it */
+ stp = s_new();
+
+ /* do the substitution */
+ while (*sp != '\0') {
+ if(*sp == '\\') {
+ switch (*++sp) {
+ case '0': case '1': case '2': case '3': case '4':
+ case '5': case '6': case '7': case '8': case '9':
+ i = *sp-'0';
+ if(subexp[i].s.sp != 0)
+ for (s = subexp[i].s.sp;
+ s < subexp[i].e.ep;
+ s++)
+ s_putc(stp, *s);
+ break;
+ case '\\':
+ s_putc(stp, '\\');
+ break;
+ case '\0':
+ sp--;
+ break;
+ case 's':
+ for(s = s_to_c(mp->replyaddr); *s; s++)
+ s_putc(stp, *s);
+ break;
+ case 'p':
+ if(mp->bulk)
+ s = "bulk";
+ else
+ s = "normal";
+ for(;*s; s++)
+ s_putc(stp, *s);
+ break;
+ default:
+ s_putc(stp, *sp);
+ break;
+ }
+ } else if(*sp == '&') {
+ if(subexp[0].s.sp != 0)
+ for (s = subexp[0].s.sp;
+ s < subexp[0].e.ep; s++)
+ s_putc(stp, *s);
+ } else
+ s_putc(stp, *sp);
+ sp++;
+ }
+ s_terminate(stp);
+
+ return s_restart(stp);
+}
+
+extern void
+regerror(char* s)
+{
+ fprint(2, "rewrite: %s\n", s);
+}
+
+extern void
+dumprules(void)
+{
+ rule *rp;
+
+ for (rp = rulep; rp != 0; rp = rp->next) {
+ fprint(2, "'%s'", rp->matchre->base);
+ switch (rp->type) {
+ case d_pipe:
+ fprint(2, " |");
+ break;
+ case d_cat:
+ fprint(2, " >>");
+ break;
+ case d_alias:
+ fprint(2, " alias");
+ break;
+ case d_translate:
+ fprint(2, " translate");
+ break;
+ default:
+ fprint(2, " UNKNOWN");
+ break;
+ }
+ fprint(2, " '%s'", rp->repl1 ? rp->repl1->base:"...");
+ fprint(2, " '%s'\n", rp->repl2 ? rp->repl2->base:"...");
+ }
+}
+
diff --git a/src/cmd/upas/send/send.h b/src/cmd/upas/send/send.h
new file mode 100644
index 00000000..6caa37b2
--- /dev/null
+++ b/src/cmd/upas/send/send.h
@@ -0,0 +1,108 @@
+#define MAXSAME 16
+#define MAXSAMECHAR 1024
+
+/* status of a destination*/
+typedef enum {
+ d_undefined, /* address has not been matched*/
+ d_pipe, /* repl1|repl2 == delivery command, rep*/
+ d_cat, /* repl1 == mail file */
+ d_translate, /* repl1 == translation command*/
+ d_alias, /* repl1 == translation*/
+ d_auth, /* repl1 == command to authorize*/
+ d_syntax, /* addr contains illegal characters*/
+ d_unknown, /* addr does not match a rewrite rule*/
+ d_loop, /* addressing loop*/
+ d_eloop, /* external addressing loop*/
+ d_noforward, /* forwarding not allowed*/
+ d_badmbox, /* mailbox badly formatted*/
+ d_resource, /* ran out of something we needed*/
+ d_pipeto, /* pipe to from a mailbox*/
+} d_status;
+
+/* a destination*/
+typedef struct dest dest;
+struct dest {
+ dest *next; /* for chaining*/
+ dest *same; /* dests with same cmd*/
+ dest *parent; /* destination we're a translation of*/
+ String *addr; /* destination address*/
+ String *repl1; /* substitution field 1*/
+ String *repl2; /* substitution field 2*/
+ int pstat; /* process status*/
+ d_status status; /* delivery status*/
+ int authorized; /* non-zero if we have been authorized*/
+ int nsame; /* number of same dests chained to this entry*/
+ int nchar; /* number of characters in the command*/
+};
+
+typedef struct message message;
+struct message {
+ String *sender;
+ String *replyaddr;
+ String *date;
+ String *body;
+ String *tmp; /* name of temp file */
+ String *to;
+ int size;
+ int fd; /* if >= 0, the file the message is stored in*/
+ char haveto;
+ String *havefrom;
+ String *havesender;
+ String *havereplyto;
+ char havedate;
+ char havemime;
+ String *havesubject;
+ char bulk; /* if Precedence: Bulk in header */
+ char rfc822headers;
+ int received; /* number of received lines */
+ char *boundary; /* bondary marker for attachments */
+};
+
+/*
+ * exported variables
+ */
+extern int rmail;
+extern int onatty;
+extern char *thissys, *altthissys;
+extern int xflg;
+extern int nflg;
+extern int tflg;
+extern int debug;
+extern int nosummary;
+
+/*
+ * exported procedures
+ */
+extern void authorize(dest*);
+extern int cat_mail(dest*, message*);
+extern dest *up_bind(dest*, message*, int);
+extern int ok_to_forward(char*);
+extern int lookup(char*, char*, Biobuf**, char*, Biobuf**);
+extern dest *d_new(String*);
+extern void d_free(dest*);
+extern dest *d_rm(dest**);
+extern void d_insert(dest**, dest*);
+extern dest *d_rm_same(dest**);
+extern void d_same_insert(dest**, dest*);
+extern String *d_to(dest*);
+extern dest *s_to_dest(String*, dest*);
+extern void gateway(message*);
+extern dest *expand_local(dest*);
+extern void logdelivery(dest*, char*, message*);
+extern void loglist(dest*, message*, char*);
+extern void logrefusal(dest*, message*, char*);
+extern int default_from(message*);
+extern message *m_new(void);
+extern void m_free(message*);
+extern message *m_read(Biobuf*, int, int);
+extern int m_get(message*, long, char**);
+extern int m_print(message*, Biobuf*, char*, int);
+extern int m_bprint(message*, Biobuf*);
+extern String *rule_parse(String*, char*, int*);
+extern int getrules(void);
+extern int rewrite(dest*, message*);
+extern void dumprules(void);
+extern void regerror(char*);
+extern dest *translate(dest*);
+extern char* skipequiv(char*);
+extern int refuse(dest*, message*, char*, int, int);
diff --git a/src/cmd/upas/send/skipequiv.c b/src/cmd/upas/send/skipequiv.c
new file mode 100644
index 00000000..f40181ad
--- /dev/null
+++ b/src/cmd/upas/send/skipequiv.c
@@ -0,0 +1,93 @@
+#include "common.h"
+#include "send.h"
+
+#undef isspace
+#define isspace(c) ((c)==' ' || (c)=='\t' || (c)=='\n')
+
+/*
+ * skip past all systems in equivlist
+ */
+extern char*
+skipequiv(char *base)
+{
+ char *sp;
+ static Biobuf *fp;
+
+ while(*base){
+ sp = strchr(base, '!');
+ if(sp==0)
+ break;
+ *sp = '\0';
+ if(lookup(base, "equivlist", &fp, 0, 0)==1){
+ /* found or us, forget this system */
+ *sp='!';
+ base=sp+1;
+ } else {
+ /* no files or system is not found, and not us */
+ *sp='!';
+ break;
+ }
+ }
+ return base;
+}
+
+static int
+okfile(char *cp, Biobuf *fp)
+{
+ char *buf;
+ int len;
+ char *bp, *ep;
+ int c;
+
+ len = strlen(cp);
+ Bseek(fp, 0, 0);
+
+ /* one iteration per system name in the file */
+ while(buf = Brdline(fp, '\n')) {
+ ep = &buf[Blinelen(fp)];
+ for(bp=buf; bp < ep;){
+ while(isspace(*bp) || *bp==',')
+ bp++;
+ if(strncmp(bp, cp, len) == 0) {
+ c = *(bp+len);
+ if(isspace(c) || c==',')
+ return 1;
+ }
+ while(bp < ep && (!isspace(*bp)) && *bp!=',')
+ bp++;
+ }
+ }
+
+ /* didn't find it, prohibit forwarding */
+ return 0;
+}
+
+/* return 1 if name found in one of the files
+ * 0 if name not found in one of the files
+ * -1 if neither file exists
+ */
+extern int
+lookup(char *cp, char *local, Biobuf **lfpp, char *global, Biobuf **gfpp)
+{
+ static String *file = 0;
+
+ if (local) {
+ if (file == 0)
+ file = s_new();
+ abspath(local, UPASLIB, s_restart(file));
+ if (*lfpp != 0 || (*lfpp = sysopen(s_to_c(file), "r", 0)) != 0) {
+ if (okfile(cp, *lfpp))
+ return 1;
+ } else
+ local = 0;
+ }
+ if (global) {
+ abspath(global, UPASLIB, s_restart(file));
+ if (*gfpp != 0 || (*gfpp = sysopen(s_to_c(file), "r", 0)) != 0) {
+ if (okfile(cp, *gfpp))
+ return 1;
+ } else
+ global = 0;
+ }
+ return (local || global)? 0 : -1;
+}
diff --git a/src/cmd/upas/send/translate.c b/src/cmd/upas/send/translate.c
new file mode 100644
index 00000000..0332659c
--- /dev/null
+++ b/src/cmd/upas/send/translate.c
@@ -0,0 +1,43 @@
+#include "common.h"
+#include "send.h"
+
+/* pipe an address through a command to translate it */
+extern dest *
+translate(dest *dp)
+{
+ process *pp;
+ String *line;
+ dest *rv;
+ char *cp;
+ int n;
+
+ pp = proc_start(s_to_c(dp->repl1), (stream *)0, outstream(), outstream(), 1, 0);
+ if (pp == 0) {
+ dp->status = d_resource;
+ return 0;
+ }
+ line = s_new();
+ for(;;) {
+ cp = Brdline(pp->std[1]->fp, '\n');
+ if(cp == 0)
+ break;
+ if(strncmp(cp, "_nosummary_", 11) == 0){
+ nosummary = 1;
+ continue;
+ }
+ n = Blinelen(pp->std[1]->fp);
+ cp[n-1] = ' ';
+ s_nappend(line, cp, n);
+ }
+ rv = s_to_dest(s_restart(line), dp);
+ s_restart(line);
+ while(s_read_line(pp->std[2]->fp, line))
+ ;
+ if ((dp->pstat = proc_wait(pp)) != 0) {
+ dp->repl2 = line;
+ rv = 0;
+ } else
+ s_free(line);
+ proc_free(pp);
+ return rv;
+}
diff --git a/src/cmd/upas/send/tryit b/src/cmd/upas/send/tryit
new file mode 100644
index 00000000..fed3a2a6
--- /dev/null
+++ b/src/cmd/upas/send/tryit
@@ -0,0 +1,29 @@
+#!/bin/sh
+set -x
+
+> /usr/spool/mail/test.local
+echo "Forward to test.local" > /usr/spool/mail/test.forward
+echo "Pipe to cat > /tmp/test.mail" > /usr/spool/mail/test.pipe
+chmod 644 /usr/spool/mail/test.pipe
+
+mail test.local <<EOF
+mailed to test.local
+EOF
+mail test.forward <<EOF
+mailed to test.forward
+EOF
+mail test.pipe <<EOF
+mailed to test.pipe
+EOF
+mail dutoit!bowell!test.local <<EOF
+mailed to dutoit!bowell!test.local
+EOF
+
+sleep 60
+
+ls -l /usr/spool/mail/test.*
+ls -l /tmp/test.mail
+echo ">>>test.local<<<"
+cat /usr/spool/mail/test.local
+echo ">>>test.mail<<<"
+cat /tmp/test.mail