summaryrefslogtreecommitdiffstats
path: root/src/local/alias.c
diff options
context:
space:
mode:
Diffstat (limited to 'src/local/alias.c')
-rw-r--r--src/local/alias.c386
1 files changed, 386 insertions, 0 deletions
diff --git a/src/local/alias.c b/src/local/alias.c
new file mode 100644
index 0000000..99e3dd6
--- /dev/null
+++ b/src/local/alias.c
@@ -0,0 +1,386 @@
+/*++
+/* NAME
+/* alias 3
+/* SUMMARY
+/* alias data base lookups
+/* SYNOPSIS
+/* #include "local.h"
+/*
+/* int deliver_alias(state, usr_attr, name, statusp)
+/* LOCAL_STATE state;
+/* USER_ATTR usr_attr;
+/* char *name;
+/* int *statusp;
+/* DESCRIPTION
+/* deliver_alias() looks up the expansion of the recipient in
+/* the global alias database and delivers the message to the
+/* listed destinations. The result is zero when no alias was found
+/* or when the message should be delivered to the user instead.
+/*
+/* deliver_alias() has wired-in knowledge about a few reserved
+/* recipient names.
+/* .IP \(bu
+/* When no alias is found for the local \fIpostmaster\fR or
+/* \fImailer-daemon\fR a warning is issued and the message
+/* is discarded.
+/* .IP \(bu
+/* When an alias exists for recipient \fIname\fR, and an alias
+/* exists for \fIowner-name\fR, the sender address is changed
+/* to \fIowner-name\fR, and the owner delivery attribute is
+/* set accordingly. This feature is disabled with
+/* "owner_request_special = no".
+/* .PP
+/* Arguments:
+/* .IP state
+/* Attributes that specify the message, recipient and more.
+/* Expansion type (alias, include, .forward).
+/* A table with the results from expanding aliases or lists.
+/* A table with delivered-to: addresses taken from the message.
+/* .IP usr_attr
+/* User attributes (rights, environment).
+/* .IP name
+/* The alias to be looked up.
+/* .IP statusp
+/* Delivery status. See below.
+/* DIAGNOSTICS
+/* Fatal errors: out of memory. The delivery status is non-zero
+/* when delivery should be tried again.
+/* 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 <fcntl.h>
+#include <errno.h>
+
+/* Utility library. */
+
+#include <msg.h>
+#include <htable.h>
+#include <dict.h>
+#include <argv.h>
+#include <stringops.h>
+#include <mymalloc.h>
+#include <vstring.h>
+#include <vstream.h>
+
+/* Global library. */
+
+#include <mail_params.h>
+#include <defer.h>
+#include <maps.h>
+#include <bounce.h>
+#include <mypwd.h>
+#include <canon_addr.h>
+#include <sent.h>
+#include <trace.h>
+#include <dsn_mask.h>
+
+/* Application-specific. */
+
+#include "local.h"
+
+/* Application-specific. */
+
+#define NO 0
+#define YES 1
+
+/* deliver_alias - expand alias file entry */
+
+int deliver_alias(LOCAL_STATE state, USER_ATTR usr_attr,
+ char *name, int *statusp)
+{
+ const char *myname = "deliver_alias";
+ const char *alias_result;
+ char *saved_alias_result;
+ char *owner;
+ char **cpp;
+ struct mypasswd *alias_pwd;
+ VSTRING *canon_owner;
+ DICT *dict;
+ const char *owner_rhs; /* owner alias, RHS */
+ int alias_count;
+ int dsn_notify;
+ char *dsn_envid;
+ int dsn_ret;
+ const char *dsn_orcpt;
+
+ /*
+ * Make verbose logging easier to understand.
+ */
+ state.level++;
+ if (msg_verbose)
+ MSG_LOG_STATE(myname, state);
+
+ /*
+ * DUPLICATE/LOOP ELIMINATION
+ *
+ * We cannot do duplicate elimination here. Sendmail compatibility requires
+ * that we allow multiple deliveries to the same alias, even recursively!
+ * For example, we must deliver to mailbox any messages that are addressed
+ * to the alias of a user that lists that same alias in her own .forward
+ * file. Yuck! This is just an example of some really perverse semantics
+ * that people will expect Postfix to implement just like sendmail.
+ *
+ * We can recognize one special case: when an alias includes its own name,
+ * deliver to the user instead, just like sendmail. Otherwise, we just
+ * bail out when nesting reaches some unreasonable depth, and blame it on
+ * a possible alias loop.
+ */
+ if (state.msg_attr.exp_from != 0
+ && strcasecmp_utf8(state.msg_attr.exp_from, name) == 0)
+ return (NO);
+ if (state.level > 100) {
+ msg_warn("alias database loop for %s", name);
+ dsb_simple(state.msg_attr.why, "5.4.6",
+ "alias database loop for %s", name);
+ *statusp = bounce_append(BOUNCE_FLAGS(state.request),
+ BOUNCE_ATTR(state.msg_attr));
+ return (YES);
+ }
+ state.msg_attr.exp_from = name;
+
+ /*
+ * There are a bunch of roles that we're trying to keep track of.
+ *
+ * First, there's the issue of whose rights should be used when delivering
+ * to "|command" or to /file/name. With alias databases, the rights are
+ * those of who owns the alias, i.e. the database owner. With aliases
+ * owned by root, a default user is used instead. When an alias with
+ * default rights references an include file owned by an ordinary user,
+ * we must use the rights of the include file owner, otherwise the
+ * include file owner could take control of the default account.
+ *
+ * Secondly, there's the question of who to notify of delivery problems.
+ * With aliases that have an owner- alias, the latter is used to set the
+ * sender and owner attributes. Otherwise, the owner attribute is reset
+ * (the alias is globally visible and could be sent to by anyone).
+ */
+ for (cpp = alias_maps->argv->argv; *cpp; cpp++) {
+ if ((dict = dict_handle(*cpp)) == 0)
+ msg_panic("%s: dictionary not found: %s", myname, *cpp);
+ if ((alias_result = dict_get(dict, name)) != 0) {
+ if (msg_verbose)
+ msg_info("%s: %s: %s = %s", myname, *cpp, name, alias_result);
+
+ /*
+ * 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",
+ "aliased to %s", alias_result);
+ *statusp = sent(BOUNCE_FLAGS(state.request),
+ SENT_ATTR(state.msg_attr));
+ return (YES);
+ }
+
+ /*
+ * DELIVERY POLICY
+ *
+ * Update the expansion type attribute, so we can decide if
+ * deliveries to |command and /file/name are allowed at all.
+ */
+ state.msg_attr.exp_type = EXPAND_TYPE_ALIAS;
+
+ /*
+ * DELIVERY RIGHTS
+ *
+ * What rights to use for |command and /file/name deliveries? The
+ * command and file code will use default rights when the alias
+ * database is owned by root, otherwise it will use the rights of
+ * the alias database owner.
+ */
+ if (dict->owner.status == DICT_OWNER_TRUSTED) {
+ alias_pwd = 0;
+ RESET_USER_ATTR(usr_attr, state.level);
+ } else {
+ if (dict->owner.status == DICT_OWNER_UNKNOWN) {
+ msg_warn("%s: no owner UID for alias database %s",
+ myname, *cpp);
+ dsb_simple(state.msg_attr.why, "4.3.0",
+ "mail system configuration error");
+ *statusp = defer_append(BOUNCE_FLAGS(state.request),
+ BOUNCE_ATTR(state.msg_attr));
+ return (YES);
+ }
+ if ((errno = mypwuid_err(dict->owner.uid, &alias_pwd)) != 0
+ || alias_pwd == 0) {
+ msg_warn(errno ?
+ "cannot find alias database owner for %s: %m" :
+ "cannot find alias database owner for %s", *cpp);
+ dsb_simple(state.msg_attr.why, "4.3.0",
+ "cannot find alias database owner");
+ *statusp = defer_append(BOUNCE_FLAGS(state.request),
+ BOUNCE_ATTR(state.msg_attr));
+ return (YES);
+ }
+ SET_USER_ATTR(usr_attr, alias_pwd, state.level);
+ }
+
+ /*
+ * WHERE TO REPORT DELIVERY PROBLEMS.
+ *
+ * Use the owner- alias if one is specified, otherwise reset the
+ * owner attribute and use the include file ownership if we can.
+ * Save the dict_lookup() result before something clobbers it.
+ *
+ * Don't match aliases that are based on regexps.
+ */
+#define OWNER_ASSIGN(own) \
+ (own = (var_ownreq_special == 0 ? 0 : \
+ concatenate("owner-", name, (char *) 0)))
+
+ saved_alias_result = mystrdup(alias_result);
+ if (OWNER_ASSIGN(owner) != 0
+ && (owner_rhs = maps_find(alias_maps, owner, DICT_FLAG_NONE)) != 0) {
+ canon_owner = canon_addr_internal(vstring_alloc(10),
+ var_exp_own_alias ? owner_rhs : owner);
+ /* Set envelope sender and owner attribute. */
+ SET_OWNER_ATTR(state.msg_attr, STR(canon_owner), state.level);
+ } else {
+ canon_owner = 0;
+ /* Note: this does not reset the envelope sender. */
+ if (var_reset_owner_attr)
+ RESET_OWNER_ATTR(state.msg_attr, state.level);
+ }
+
+ /*
+ * 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;
+
+ /*
+ * Deliver.
+ */
+ alias_count = 0;
+ if (owner != 0 && alias_maps->error != 0) {
+ dsb_simple(state.msg_attr.why, "4.3.0",
+ "alias database unavailable");
+ *statusp = defer_append(BOUNCE_FLAGS(state.request),
+ BOUNCE_ATTR(state.msg_attr));
+ } else {
+
+ /*
+ * XXX DSN
+ *
+ * When delivering to a mailing list (i.e. the envelope sender
+ * is replaced) the ENVID, NOTIFY, RET, and ORCPT parameters
+ * which accompany the redistributed message MUST NOT be
+ * derived from those of the original message.
+ *
+ * When delivering to an alias (i.e. the envelope sender 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.
+ */
+#define DSN_SAVE_UPDATE(saved, old, new) do { \
+ saved = old; \
+ old = new; \
+ } while (0)
+
+ DSN_SAVE_UPDATE(dsn_notify, state.msg_attr.rcpt.dsn_notify,
+ dsn_notify == DSN_NOTIFY_SUCCESS ?
+ DSN_NOTIFY_NEVER :
+ dsn_notify & ~DSN_NOTIFY_SUCCESS);
+ if (canon_owner != 0) {
+ DSN_SAVE_UPDATE(dsn_envid, state.msg_attr.dsn_envid, "");
+ DSN_SAVE_UPDATE(dsn_ret, state.msg_attr.dsn_ret, 0);
+ DSN_SAVE_UPDATE(dsn_orcpt, state.msg_attr.rcpt.dsn_orcpt, "");
+ state.msg_attr.rcpt.orig_addr = "";
+ }
+ *statusp =
+ deliver_token_string(state, usr_attr, saved_alias_result,
+ &alias_count);
+#if 0
+ if (var_ownreq_special
+ && strncmp("owner-", state.msg_attr.sender, 6) != 0
+ && alias_count > 10)
+ msg_warn("mailing list \"%s\" needs an \"owner-%s\" alias",
+ name, name);
+#endif
+ if (alias_count < 1) {
+ msg_warn("no recipient in alias lookup result for %s", name);
+ dsb_simple(state.msg_attr.why, "4.3.0",
+ "alias database unavailable");
+ *statusp = defer_append(BOUNCE_FLAGS(state.request),
+ BOUNCE_ATTR(state.msg_attr));
+ } else {
+
+ /*
+ * XXX DSN
+ *
+ * When delivering to a mailing list (i.e. the envelope
+ * sender address is replaced) and NOTIFY=SUCCESS was
+ * specified, report a DSN of "delivered".
+ *
+ * When delivering to an alias (i.e. the envelope sender
+ * address is not replaced) and NOTIFY=SUCCESS was
+ * specified, report a DSN of "expanded".
+ */
+ if (dsn_notify & DSN_NOTIFY_SUCCESS) {
+ state.msg_attr.rcpt.dsn_notify = dsn_notify;
+ if (canon_owner != 0) {
+ state.msg_attr.dsn_envid = dsn_envid;
+ state.msg_attr.dsn_ret = dsn_ret;
+ state.msg_attr.rcpt.dsn_orcpt = dsn_orcpt;
+ }
+ dsb_update(state.msg_attr.why, "2.0.0", canon_owner ?
+ "delivered" : "expanded",
+ DSB_SKIP_RMTA, DSB_SKIP_REPLY,
+ "alias expanded");
+ (void) trace_append(BOUNCE_FLAG_NONE,
+ SENT_ATTR(state.msg_attr));
+ }
+ }
+ }
+ myfree(saved_alias_result);
+ if (owner)
+ myfree(owner);
+ if (canon_owner)
+ vstring_free(canon_owner);
+ if (alias_pwd)
+ mypwfree(alias_pwd);
+ return (YES);
+ }
+
+ /*
+ * If the alias database was inaccessible for some reason, defer
+ * further delivery for the current top-level recipient.
+ */
+ if (alias_result == 0 && dict->error != 0) {
+ msg_warn("%s:%s: lookup of '%s' failed",
+ dict->type, dict->name, name);
+ dsb_simple(state.msg_attr.why, "4.3.0",
+ "alias database unavailable");
+ *statusp = defer_append(BOUNCE_FLAGS(state.request),
+ BOUNCE_ATTR(state.msg_attr));
+ return (YES);
+ } else {
+ if (msg_verbose)
+ msg_info("%s: %s: %s not found", myname, *cpp, name);
+ }
+ }
+
+ /*
+ * Try delivery to a local user instead.
+ */
+ return (NO);
+}