diff options
Diffstat (limited to 'src/cmd/upas/send')
-rw-r--r-- | src/cmd/upas/send/authorize.c | 29 | ||||
-rw-r--r-- | src/cmd/upas/send/bind.c | 133 | ||||
-rw-r--r-- | src/cmd/upas/send/cat_mail.c | 60 | ||||
-rw-r--r-- | src/cmd/upas/send/dest.c | 260 | ||||
-rw-r--r-- | src/cmd/upas/send/filter.c | 128 | ||||
-rw-r--r-- | src/cmd/upas/send/gateway.c | 24 | ||||
-rw-r--r-- | src/cmd/upas/send/local.c | 129 | ||||
-rw-r--r-- | src/cmd/upas/send/log.c | 85 | ||||
-rw-r--r-- | src/cmd/upas/send/main.c | 575 | ||||
-rw-r--r-- | src/cmd/upas/send/makefile | 46 | ||||
-rw-r--r-- | src/cmd/upas/send/message.c | 573 | ||||
-rw-r--r-- | src/cmd/upas/send/mkfile | 52 | ||||
-rw-r--r-- | src/cmd/upas/send/regtest.c | 36 | ||||
-rw-r--r-- | src/cmd/upas/send/rewrite.c | 315 | ||||
-rw-r--r-- | src/cmd/upas/send/send.h | 108 | ||||
-rw-r--r-- | src/cmd/upas/send/skipequiv.c | 93 | ||||
-rw-r--r-- | src/cmd/upas/send/translate.c | 43 | ||||
-rw-r--r-- | src/cmd/upas/send/tryit | 29 |
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 |