diff options
Diffstat (limited to 'src/local/alias.c')
-rw-r--r-- | src/local/alias.c | 386 |
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); +} |