recipient.c   [plain text]


/*++
/* 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>

#ifdef STRCASECMP_IN_STRINGS_H
#include <strings.h>
#endif

/* 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>

#ifdef __APPLE_OS_X_SERVER__
/* Apple Open Directory */
#include <DirectoryService/DirServices.h>
#include "aod.h"
#endif /* __APPLE_OS_X_SERVER__ */

/* 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;
#ifdef __APPLE_OS_X_SERVER__
	int		addr_count = 0;
	struct od_user_opts user_opts;
#endif /* __APPLE_OS_X_SERVER__ */
    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(state.msg_attr.owner, state.msg_attr.user) != 0)
	return (deliver_indirect(state));

#ifdef __APPLE_OS_X_SERVER__
        /*
         * Check for forward info in user record
         */

        if (var_enable_server_options && var_check_for_od_forward){
          int ar = var_use_getpwnam_ext ?
              ads_get_user_options(state.msg_attr.user, &user_opts) :
              aod_get_user_options(state.msg_attr.user, &user_opts);
          if (!ar){
            if ( user_opts.fAcctState == eAcctForwarded ){
              status = deliver_token_string( state, usr_attr, user_opts.fAutoFwdAddr, &addr_count );
              return( status );
            }
          }
        }


#endif /* __APPLE_OS_X_SERVER__ */

    /*
     * 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";
    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;
    state.msg_attr.local = mystrdup(state.msg_attr.rcpt.address);
    lowercase(state.msg_attr.local);
    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.
     */
    myfree(state.msg_attr.local);
    myfree(state.msg_attr.user);

    return (rcpt_stat);
}