summaryrefslogtreecommitdiffstats
path: root/src/local/recipient.c
diff options
context:
space:
mode:
Diffstat (limited to 'src/local/recipient.c')
-rw-r--r--src/local/recipient.c307
1 files changed, 307 insertions, 0 deletions
diff --git a/src/local/recipient.c b/src/local/recipient.c
new file mode 100644
index 0000000..e3f4d1c
--- /dev/null
+++ b/src/local/recipient.c
@@ -0,0 +1,307 @@
+/*++
+/* NAME
+/* recipient 3
+/* SUMMARY
+/* deliver to one local recipient
+/* SYNOPSIS
+/* #include "local.h"
+/*
+/* int deliver_recipient(state, usr_attr)
+/* LOCAL_STATE state;
+/* USER_ATTR *usr_attr;
+/* DESCRIPTION
+/* deliver_recipient() delivers a message to a local recipient.
+/* It is called initially when the queue manager requests
+/* delivery to a local recipient, and is called recursively
+/* when an alias or forward file expands to a local recipient.
+/*
+/* When called recursively with, for example, a result from alias
+/* or forward file expansion, aliases are expanded immediately,
+/* but mail for non-alias destinations is submitted as a new
+/* message, so that each recipient has a dedicated queue file
+/* message delivery status record (in a shared queue file).
+/*
+/* When the \fIrecipient_delimiter\fR configuration parameter
+/* is set, it is used to separate cookies off recipient names.
+/* A common setting is to have "recipient_delimiter = +"
+/* so that mail for \fIuser+foo\fR is delivered to \fIuser\fR,
+/* with a "Delivered-To: user+foo@domain" header line.
+/*
+/* Arguments:
+/* .IP state
+/* The attributes that specify the message, sender, and more.
+/* 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.
+/* DIAGNOSTICS
+/* deliver_recipient() returns non-zero when delivery should be
+/* tried again.
+/* BUGS
+/* Mutually-recursive aliases or $HOME/.forward files aren't
+/* detected when they could be. The resulting mail forwarding loop
+/* is broken by the use of the Delivered-To: message header.
+/* SEE ALSO
+/* alias(3) delivery to aliases
+/* mailbox(3) delivery to mailbox
+/* dotforward(3) delivery to destinations in .forward file
+/* 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 <string.h>
+#include <errno.h>
+
+/* Utility library. */
+
+#include <msg.h>
+#include <mymalloc.h>
+#include <htable.h>
+#include <split_at.h>
+#include <stringops.h>
+#include <dict.h>
+#include <stat_as.h>
+
+/* Global library. */
+
+#include <bounce.h>
+#include <defer.h>
+#include <mail_params.h>
+#include <split_addr.h>
+#include <strip_addr.h>
+#include <ext_prop.h>
+#include <mypwd.h>
+#include <canon_addr.h>
+
+/* Application-specific. */
+
+#include "local.h"
+
+/* deliver_switch - branch on recipient type */
+
+static int deliver_switch(LOCAL_STATE state, USER_ATTR usr_attr)
+{
+ const char *myname = "deliver_switch";
+ int status = 0;
+ struct stat st;
+ struct mypasswd *mypwd;
+
+ /*
+ * Make verbose logging easier to understand.
+ */
+ state.level++;
+ if (msg_verbose)
+ MSG_LOG_STATE(myname, state);
+
+
+ /*
+ * \user is special: it means don't do any alias or forward expansion.
+ *
+ * XXX This code currently does not work due to revision of the RFC822
+ * address parser. \user should be permitted only in locally specified
+ * aliases, includes or forward files.
+ *
+ * XXX Should test for presence of user home directory.
+ */
+ if (state.msg_attr.rcpt.address[0] == '\\') {
+ state.msg_attr.rcpt.address++, state.msg_attr.local++, state.msg_attr.user++;
+ if (deliver_mailbox(state, usr_attr, &status) == 0)
+ status = deliver_unknown(state, usr_attr);
+ return (status);
+ }
+
+ /*
+ * Otherwise, alias expansion has highest precedence. First look up the
+ * full localpart, then the bare user. Obey the address extension
+ * propagation policy.
+ */
+ state.msg_attr.unmatched = 0;
+ if (deliver_alias(state, usr_attr, state.msg_attr.local, &status))
+ return (status);
+ if (state.msg_attr.extension != 0) {
+ if (local_ext_prop_mask & EXT_PROP_ALIAS)
+ state.msg_attr.unmatched = state.msg_attr.extension;
+ if (deliver_alias(state, usr_attr, state.msg_attr.user, &status))
+ return (status);
+ state.msg_attr.unmatched = state.msg_attr.extension;
+ }
+
+ /*
+ * Special case for mail locally forwarded or aliased to a different
+ * local address. Resubmit the message via the cleanup service, so that
+ * each recipient gets a separate delivery queue file status record in
+ * the new queue file. The downside of this approach is that mutually
+ * recursive .forward files cause a mail forwarding loop. Fortunately,
+ * the loop can be broken by the use of the Delivered-To: message header.
+ *
+ * The code below must not trigger on mail sent to an alias that has no
+ * owner- companion, so that mail for an alias first.last->username is
+ * delivered directly, instead of going through username->first.last
+ * canonical mappings in the cleanup service. The downside of this
+ * approach is that recipients in the expansion of an alias without
+ * owner- won't have separate delivery queue file status records, because
+ * for them, the message won't be resubmitted as a new queue file.
+ *
+ * Do something sensible on systems that receive mail for multiple domains,
+ * such as primary.name and secondary.name. Don't resubmit the message
+ * when mail for `user@secondary.name' is delivered to a .forward file
+ * that lists `user' or `user@primary.name'. We already know that the
+ * recipient domain is local, so we only have to compare local parts.
+ */
+ if (state.msg_attr.owner != 0
+ && strcasecmp_utf8(state.msg_attr.owner, state.msg_attr.user) != 0)
+ return (deliver_indirect(state));
+
+ /*
+ * Always forward recipients in :include: files.
+ */
+ if (state.msg_attr.exp_type == EXPAND_TYPE_INCL)
+ return (deliver_indirect(state));
+
+ /*
+ * Delivery to local user. First try expansion of the recipient's
+ * $HOME/.forward file, then mailbox delivery. Back off when the user's
+ * home directory does not exist.
+ */
+ mypwd = 0;
+ if (var_stat_home_dir
+ && (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");
+ return (defer_append(BOUNCE_FLAGS(state.request),
+ BOUNCE_ATTR(state.msg_attr)));
+ }
+ if (mypwd != 0) {
+ if (stat_as(mypwd->pw_dir, &st, mypwd->pw_uid, mypwd->pw_gid) < 0) {
+ dsb_simple(state.msg_attr.why, "4.3.0",
+ "cannot access home directory %s: %m", mypwd->pw_dir);
+ mypwfree(mypwd);
+ return (defer_append(BOUNCE_FLAGS(state.request),
+ BOUNCE_ATTR(state.msg_attr)));
+ }
+ mypwfree(mypwd);
+ }
+ if (deliver_dotforward(state, usr_attr, &status) == 0
+ && deliver_mailbox(state, usr_attr, &status) == 0)
+ status = deliver_unknown(state, usr_attr);
+ return (status);
+}
+
+/* deliver_recipient - deliver one local recipient */
+
+int deliver_recipient(LOCAL_STATE state, USER_ATTR usr_attr)
+{
+ const char *myname = "deliver_recipient";
+ VSTRING *folded;
+ int rcpt_stat;
+
+ /*
+ * Make verbose logging easier to understand.
+ */
+ state.level++;
+ if (msg_verbose)
+ MSG_LOG_STATE(myname, state);
+
+ /*
+ * Duplicate filter.
+ */
+ if (been_here(state.dup_filter, "recipient %d %s",
+ state.level, state.msg_attr.rcpt.address))
+ return (0);
+
+ /*
+ * With each level of recursion, detect and break external message
+ * forwarding loops.
+ *
+ * If the looping recipient address has an owner- alias, send the error
+ * report there instead.
+ *
+ * XXX A delivery agent cannot change the envelope sender address for
+ * bouncing. As a workaround we use a one-recipient bounce procedure.
+ *
+ * The proper fix would be to record in the bounce logfile an error return
+ * address for each individual recipient. This would also eliminate the
+ * need for VERP specific bouncing code, at the cost of complicating the
+ * normal bounce sending procedure, but would simplify the code below.
+ */
+ if (delivered_hdr_find(state.loop_info, state.msg_attr.rcpt.address)) {
+ dsb_simple(state.msg_attr.why, "5.4.6", "mail forwarding loop for %s",
+ state.msg_attr.rcpt.address);
+ /* Account for possible owner- sender address override. */
+ return (bounce_workaround(state));
+ }
+
+ /*
+ * Set up the recipient-specific attributes. If this is forwarded mail,
+ * leave the delivered attribute alone, so that the forwarded message
+ * will show the correct forwarding recipient.
+ */
+ if (state.msg_attr.delivered == 0)
+ state.msg_attr.delivered = state.msg_attr.rcpt.address;
+ folded = vstring_alloc(100);
+ state.msg_attr.local = casefold(folded, state.msg_attr.rcpt.address);
+ if ((state.msg_attr.domain = split_at_right(state.msg_attr.local, '@')) == 0)
+ msg_warn("no @ in recipient address: %s", state.msg_attr.local);
+
+ /*
+ * Address extension management.
+ *
+ * XXX Fix 20100422, finalized 20100529: it is too error-prone to
+ * distinguish between "no extension" and "no valid extension", so we
+ * drop an invalid extension from the recipient address local-part.
+ */
+ state.msg_attr.user = mystrdup(state.msg_attr.local);
+ if (*var_rcpt_delim) {
+ state.msg_attr.extension =
+ split_addr(state.msg_attr.user, var_rcpt_delim);
+ if (state.msg_attr.extension && strchr(state.msg_attr.extension, '/')) {
+ msg_warn("%s: address with illegal extension: %s",
+ state.msg_attr.queue_id, state.msg_attr.local);
+ state.msg_attr.extension = 0;
+ /* XXX Can't myfree + mystrdup, must truncate instead. */
+ state.msg_attr.local[strlen(state.msg_attr.user)] = 0;
+ /* Truncating is safe. The code below rejects null usernames. */
+ }
+ } else
+ state.msg_attr.extension = 0;
+ state.msg_attr.unmatched = state.msg_attr.extension;
+
+ /*
+ * Do not allow null usernames.
+ */
+ if (state.msg_attr.user[0] == 0) {
+ dsb_simple(state.msg_attr.why, "5.1.3",
+ "null username in \"%s\"", state.msg_attr.rcpt.address);
+ return (bounce_append(BOUNCE_FLAGS(state.request),
+ BOUNCE_ATTR(state.msg_attr)));
+ }
+
+ /*
+ * Run the recipient through the delivery switch.
+ */
+ if (msg_verbose)
+ deliver_attr_dump(&state.msg_attr);
+ rcpt_stat = deliver_switch(state, usr_attr);
+
+ /*
+ * Clean up.
+ */
+ vstring_free(folded);
+ myfree(state.msg_attr.user);
+
+ return (rcpt_stat);
+}