/*++ /* 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 #include #include #include #include /* Utility library. */ #include #include #include #include #include #include #include /* Global library. */ #include #include #include #include #include #include #include #include /* 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); }