diff options
Diffstat (limited to '')
-rw-r--r-- | src/local/dotforward.c | 304 |
1 files changed, 304 insertions, 0 deletions
diff --git a/src/local/dotforward.c b/src/local/dotforward.c new file mode 100644 index 0000000..3ce2cfc --- /dev/null +++ b/src/local/dotforward.c @@ -0,0 +1,304 @@ +/*++ +/* NAME +/* dotforward 3 +/* SUMMARY +/* $HOME/.forward file expansion +/* SYNOPSIS +/* #include "local.h" +/* +/* int deliver_dotforward(state, usr_attr, statusp) +/* LOCAL_STATE state; +/* USER_ATTR usr_attr; +/* int *statusp; +/* DESCRIPTION +/* deliver_dotforward() delivers a message to the destinations +/* listed in a recipient's .forward file(s) as specified through +/* the forward_path configuration parameter. The result is +/* zero when no acceptable .forward file was found, or when +/* a recipient is listed in her own .forward file. Expansions +/* are scrutinized with the forward_expansion_filter parameter. +/* +/* Arguments: +/* .IP state +/* Message delivery attributes (sender, recipient etc.). +/* Attributes describing alias, include or forward expansion. +/* A table with the results from expanding aliases or lists. +/* A table with delivered-to: addresses taken from the message. +/* .IP usr_attr +/* Attributes describing user rights and environment. +/* .IP statusp +/* Message delivery status. See below. +/* DIAGNOSTICS +/* Fatal errors: out of memory. Warnings: bad $HOME/.forward +/* file type, permissions or ownership. The message delivery +/* status is non-zero when delivery should be tried again. +/* SEE ALSO +/* include(3) include file processor. +/* LICENSE +/* .ad +/* .fi +/* The Secure Mailer license must be distributed with this software. +/* AUTHOR(S) +/* Wietse Venema +/* IBM T.J. Watson Research +/* P.O. Box 704 +/* Yorktown Heights, NY 10598, USA +/*--*/ + +/* System library. */ + +#include <sys_defs.h> +#include <sys/stat.h> +#include <unistd.h> +#include <errno.h> +#include <fcntl.h> +#ifdef USE_PATHS_H +#include <paths.h> +#endif +#include <string.h> + +/* Utility library. */ + +#include <msg.h> +#include <vstring.h> +#include <vstream.h> +#include <htable.h> +#include <open_as.h> +#include <lstat_as.h> +#include <iostuff.h> +#include <stringops.h> +#include <mymalloc.h> +#include <mac_expand.h> + +/* Global library. */ + +#include <mypwd.h> +#include <bounce.h> +#include <defer.h> +#include <been_here.h> +#include <mail_params.h> +#include <mail_conf.h> +#include <ext_prop.h> +#include <sent.h> +#include <dsn_mask.h> +#include <trace.h> + +/* Application-specific. */ + +#include "local.h" + +#define NO 0 +#define YES 1 + +/* deliver_dotforward - expand contents of .forward file */ + +int deliver_dotforward(LOCAL_STATE state, USER_ATTR usr_attr, int *statusp) +{ + const char *myname = "deliver_dotforward"; + struct stat st; + VSTRING *path; + struct mypasswd *mypwd; + int fd; + VSTREAM *fp; + int status; + int forward_found = NO; + int lookup_status; + int addr_count; + char *saved_forward_path; + char *lhs; + char *next; + int expand_status; + int saved_notify; + + /* + * Make verbose logging easier to understand. + */ + state.level++; + if (msg_verbose) + MSG_LOG_STATE(myname, state); + + /* + * Skip this module if per-user forwarding is disabled. + */ + if (*var_forward_path == 0) + return (NO); + + /* + * Skip non-existing users. The mailbox delivery routine will catch the + * error. + */ + if ((errno = mypwnam_err(state.msg_attr.user, &mypwd)) != 0) { + msg_warn("error looking up passwd info for %s: %m", + state.msg_attr.user); + dsb_simple(state.msg_attr.why, "4.0.0", "user lookup error"); + *statusp = defer_append(BOUNCE_FLAGS(state.request), + BOUNCE_ATTR(state.msg_attr)); + return (YES); + } + if (mypwd == 0) + return (NO); + + /* + * From here on no early returns or we have a memory leak. + */ + + /* + * EXTERNAL LOOP CONTROL + * + * Set the delivered message attribute to the recipient, so that this + * message will list the correct forwarding address. + */ + if (var_frozen_delivered == 0) + state.msg_attr.delivered = state.msg_attr.rcpt.address; + + /* + * DELIVERY RIGHTS + * + * Do not inherit rights from the .forward file owner. Instead, use the + * recipient's rights, and insist that the .forward file is owned by the + * recipient. This is a small but significant difference. Use the + * recipient's rights for all /file and |command deliveries, and pass on + * these rights to command/file destinations in included files. When + * these are the rights of root, the /file and |command delivery routines + * will use unprivileged default rights instead. Better safe than sorry. + */ + SET_USER_ATTR(usr_attr, mypwd, state.level); + + /* + * DELIVERY POLICY + * + * Update the expansion type attribute so that we can decide if deliveries + * to |command and /file/name are allowed at all. + */ + state.msg_attr.exp_type = EXPAND_TYPE_FWD; + + /* + * WHERE TO REPORT DELIVERY PROBLEMS + * + * Set the owner attribute so that 1) include files won't set the sender to + * be this user and 2) mail forwarded to other local users will be + * resubmitted as a new queue file. + */ + state.msg_attr.owner = state.msg_attr.user; + + /* + * Search the forward_path for an existing forward file. + * + * If unmatched extensions should never be propagated, or if a forward file + * name includes the address extension, don't propagate the extension to + * the recipient addresses. + */ + status = 0; + path = vstring_alloc(100); + saved_forward_path = mystrdup(var_forward_path); + next = saved_forward_path; + lookup_status = -1; + + while ((lhs = mystrtok(&next, CHARS_COMMA_SP)) != 0) { + expand_status = local_expand(path, lhs, &state, &usr_attr, + var_fwd_exp_filter); + if ((expand_status & (MAC_PARSE_ERROR | MAC_PARSE_UNDEF)) == 0) { + lookup_status = + lstat_as(STR(path), &st, usr_attr.uid, usr_attr.gid); + if (msg_verbose) + msg_info("%s: path %s expand_status %d look_status %d", myname, + STR(path), expand_status, lookup_status); + if (lookup_status >= 0) { + if ((expand_status & LOCAL_EXP_EXTENSION_MATCHED) != 0 + || (local_ext_prop_mask & EXT_PROP_FORWARD) == 0) + state.msg_attr.unmatched = 0; + break; + } + } + } + + /* + * Process the forward file. + * + * Assume that usernames do not have file system meta characters. Open the + * .forward file as the user. Ignore files that aren't regular files, + * files that are owned by the wrong user, or files that have world write + * permission enabled. + * + * DUPLICATE/LOOP ELIMINATION + * + * If this user includes (an alias of) herself in her own .forward file, + * deliver to the user instead. + */ + if (lookup_status >= 0) { + + /* + * Don't expand a verify-only request. + */ + if (state.request->flags & DEL_REQ_FLAG_MTA_VRFY) { + dsb_simple(state.msg_attr.why, "2.0.0", + "forward via file: %s", STR(path)); + *statusp = sent(BOUNCE_FLAGS(state.request), + SENT_ATTR(state.msg_attr)); + forward_found = YES; + } else if (been_here(state.dup_filter, "forward %s", STR(path)) == 0) { + state.msg_attr.exp_from = state.msg_attr.local; + if (S_ISREG(st.st_mode) == 0) { + msg_warn("file %s is not a regular file", STR(path)); + } else if (st.st_uid != 0 && st.st_uid != usr_attr.uid) { + msg_warn("file %s has bad owner uid %ld", + STR(path), (long) st.st_uid); + } else if (st.st_mode & 002) { + msg_warn("file %s is world writable", STR(path)); + } else if ((fd = open_as(STR(path), O_RDONLY, 0, usr_attr.uid, usr_attr.gid)) < 0) { + msg_warn("cannot open file %s: %m", STR(path)); + } else { + + /* + * XXX DSN. When delivering to an alias (i.e. the envelope + * sender address is not replaced) any ENVID, RET, or ORCPT + * parameters are propagated to all forwarding addresses + * associated with that alias. The NOTIFY parameter is + * propagated to the forwarding addresses, except that any + * SUCCESS keyword is removed. + */ + close_on_exec(fd, CLOSE_ON_EXEC); + addr_count = 0; + fp = vstream_fdopen(fd, O_RDONLY); + saved_notify = state.msg_attr.rcpt.dsn_notify; + state.msg_attr.rcpt.dsn_notify = + (saved_notify == DSN_NOTIFY_SUCCESS ? + DSN_NOTIFY_NEVER : saved_notify & ~DSN_NOTIFY_SUCCESS); + status = deliver_token_stream(state, usr_attr, fp, &addr_count); + if (vstream_fclose(fp)) + msg_warn("close file %s: %m", STR(path)); + if (addr_count > 0) { + forward_found = YES; + been_here(state.dup_filter, "forward-done %s", STR(path)); + + /* + * XXX DSN. When delivering to an alias (i.e. the + * envelope sender address is not replaced) and the + * original NOTIFY parameter for the alias contained the + * SUCCESS keyword, an "expanded" DSN is issued for the + * alias. + */ + if (status == 0 && (saved_notify & DSN_NOTIFY_SUCCESS)) { + state.msg_attr.rcpt.dsn_notify = saved_notify; + dsb_update(state.msg_attr.why, "2.0.0", "expanded", + DSB_SKIP_RMTA, DSB_SKIP_REPLY, + "alias expanded"); + (void) trace_append(BOUNCE_FLAG_NONE, + SENT_ATTR(state.msg_attr)); + } + } + } + } else if (been_here_check(state.dup_filter, "forward-done %s", STR(path)) != 0) + forward_found = YES; /* else we're recursive */ + } + + /* + * Clean up. + */ + vstring_free(path); + myfree(saved_forward_path); + mypwfree(mypwd); + + *statusp = status; + return (forward_found); +} |