Exemple #1
0
static int eval_command_status(int command_status, char *service,
			          DELIVER_REQUEST *request, PIPE_ATTR *attr,
			               DSN_BUF *why)
{
    RECIPIENT *rcpt;
    int     status;
    int     result = 0;
    int     n;

    /*
     * Depending on the result, bounce or defer the message, and mark the
     * recipient as done where appropriate.
     */
    switch (command_status) {
    case PIPE_STAT_OK:
	dsb_update(why, "2.0.0", (attr->flags & PIPE_OPT_FINAL_DELIVERY) ?
		   "delivered" : "relayed", DSB_SKIP_RMTA, DSB_SKIP_REPLY,
		   "delivered via %s service", service);
	(void) DSN_FROM_DSN_BUF(why);
	for (n = 0; n < request->rcpt_list.len; n++) {
	    rcpt = request->rcpt_list.info + n;
	    status = sent(DEL_REQ_TRACE_FLAGS(request->flags),
			  request->queue_id, &request->msg_stats, rcpt,
			  service, &why->dsn);
	    if (status == 0 && (request->flags & DEL_REQ_FLAG_SUCCESS))
		deliver_completed(request->fp, rcpt->offset);
	    result |= status;
	}
	break;
    case PIPE_STAT_BOUNCE:
    case PIPE_STAT_DEFER:
	(void) DSN_FROM_DSN_BUF(why);
	for (n = 0; n < request->rcpt_list.len; n++) {
	    rcpt = request->rcpt_list.info + n;
	    /* XXX Maybe encapsulate this with ndr_append(). */
	    status = (STR(why->status)[0] != '4' ?
		      bounce_append : defer_append)
		(DEL_REQ_TRACE_FLAGS(request->flags),
		 request->queue_id,
		 &request->msg_stats, rcpt,
		 service, &why->dsn);
	    if (status == 0)
		deliver_completed(request->fp, rcpt->offset);
	    result |= status;
	}
	break;
    case PIPE_STAT_CORRUPT:
	/* XXX DSN should we send something? */
	result |= DEL_STAT_DEFER;
	break;
    default:
	msg_panic("eval_command_status: bad status %d", command_status);
	/* NOTREACHED */
    }

    return (result);
}
Exemple #2
0
void    smtp_rcpt_done(SMTP_STATE *state, SMTP_RESP *resp, RECIPIENT *rcpt)
{
    DELIVER_REQUEST *request = state->request;
    SMTP_SESSION *session = state->session;
    SMTP_ITERATOR *iter = state->iterator;
    DSN_BUF *why = state->why;
    const char *dsn_action = "relayed";
    int     status;

    /*
     * Assume this was intermediate delivery when the server announced DSN
     * support, and don't send a DSN "SUCCESS" notification.
     */
    if (session->features & SMTP_FEATURE_DSN)
	rcpt->dsn_notify &= ~DSN_NOTIFY_SUCCESS;

    /*
     * Assume this was final delivery when the LMTP server announced no DSN
     * support. In backwards compatibility mode, send a "relayed" instead of
     * a "delivered" DSN "SUCCESS" notification. Do not attempt to "simplify"
     * the expression. The redundancy is for clarity. It is trivially
     * eliminated by the compiler. There is no need to sacrifice clarity for
     * the sake of "performance".
     */
    if ((session->features & SMTP_FEATURE_DSN) == 0
	&& !smtp_mode
	&& var_lmtp_assume_final != 0)
	dsn_action = "delivered";

    /*
     * Report success and delete the recipient from the delivery request.
     * Defer if the success can't be reported.
     * 
     * Note: the DSN action is ignored in case of address probes.
     */
    dsb_update(why, resp->dsn, dsn_action, DSB_MTYPE_DNS, STR(iter->host),
	       DSB_DTYPE_SMTP, resp->str, "%s", resp->str);

    status = sent(DEL_REQ_TRACE_FLAGS(request->flags),
		  request->queue_id, &request->msg_stats, rcpt,
		  session->namaddrport, DSN_FROM_DSN_BUF(why));
    if (status == 0)
	if (request->flags & DEL_REQ_FLAG_SUCCESS)
	    deliver_completed(state->src, rcpt->offset);
    SMTP_RCPT_DROP(state, rcpt);
    state->status |= status;
}
int     smtp_tls_policy_cache_query(DSN_BUF *why, SMTP_TLS_POLICY *tls,
				            SMTP_ITERATOR *iter)
{
    VSTRING *key;

    /*
     * Create an empty TLS Policy cache on the fly.
     */
    if (policy_cache == 0)
	policy_cache =
	    ctable_create(CACHE_SIZE, policy_create, policy_delete, (void *) 0);

    /*
     * Query the TLS Policy cache, with a search key that reflects our shared
     * values that also appear in other cache and table search keys.
     */
    key = vstring_alloc(100);
    smtp_key_prefix(key, ":", iter, SMTP_KEY_FLAG_NEXTHOP
		    | SMTP_KEY_FLAG_HOSTNAME
		    | SMTP_KEY_FLAG_PORT);
    ctable_newcontext(policy_cache, (void *) iter);
    *tls = *(SMTP_TLS_POLICY *) ctable_locate(policy_cache, STR(key));
    vstring_free(key);

    /*
     * Report errors. Both error and non-error results are cached. We must
     * therefore copy the cached DSN buffer content to the caller's buffer.
     */
    if (tls->level == TLS_LEV_INVALID) {
	/* XXX Simplify this by implementing a "copy" primitive. */
	dsb_update(why,
		   STR(tls->why->status), STR(tls->why->action),
		   STR(tls->why->mtype), STR(tls->why->mname),
		   STR(tls->why->dtype), STR(tls->why->dtext),
		   "%s", STR(tls->why->reason));
	return (0);
    } else {
	return (1);
    }
}
Exemple #4
0
int     smtp_sasl_authenticate(SMTP_SESSION *session, DSN_BUF *why)
{
    const char *myname = "smtp_sasl_authenticate";
    SMTP_RESP *resp;
    const char *mechanism;
    int     result;
    char   *line;
    int     steps = 0;

    /*
     * Sanity check.
     */
    if (session->sasl_mechanism_list == 0)
	msg_panic("%s: no mechanism list", myname);

    if (msg_verbose)
	msg_info("%s: %s: SASL mechanisms %s",
		 myname, session->namaddrport, session->sasl_mechanism_list);

    /*
     * Avoid repeated login failures after a recent 535 error.
     */
#ifdef HAVE_SASL_AUTH_CACHE
    if (smtp_sasl_auth_cache
	&& smtp_sasl_auth_cache_find(smtp_sasl_auth_cache, session)) {
	char   *resp_dsn = smtp_sasl_auth_cache_dsn(smtp_sasl_auth_cache);
	char   *resp_str = smtp_sasl_auth_cache_text(smtp_sasl_auth_cache);

	if (var_smtp_sasl_auth_soft_bounce && resp_dsn[0] == '5')
	    resp_dsn[0] = '4';
	dsb_update(why, resp_dsn, DSB_DEF_ACTION, DSB_MTYPE_DNS,
		   session->host, var_procname, resp_str,
		   "SASL [CACHED] authentication failed; server %s said: %s",
		   session->host, resp_str);
	return (0);
    }
#endif

    /*
     * Start the client side authentication protocol.
     */
    result = xsasl_client_first(session->sasl_client,
				session->sasl_mechanism_list,
				session->sasl_username,
				session->sasl_passwd,
				&mechanism, session->sasl_reply);
    if (result != XSASL_AUTH_OK) {
	dsb_update(why, "4.7.0", DSB_DEF_ACTION, DSB_SKIP_RMTA,
		   DSB_DTYPE_SASL, STR(session->sasl_reply),
		   "SASL authentication failed; "
		   "cannot authenticate to server %s: %s",
		   session->namaddr, STR(session->sasl_reply));
	return (-1);
    }

    /*
     * Send the AUTH command and the optional initial client response.
     * sasl_encode64() produces four bytes for each complete or incomplete
     * triple of input bytes. Allocate an extra byte for string termination.
     */
    if (LEN(session->sasl_reply) > 0) {
	smtp_chat_cmd(session, "AUTH %s %s", mechanism,
		      STR(session->sasl_reply));
    } else {
	smtp_chat_cmd(session, "AUTH %s", mechanism);
    }

    /*
     * Step through the authentication protocol until the server tells us
     * that we are done.
     */
    while ((resp = smtp_chat_resp(session))->code / 100 == 3) {

	/*
	 * Sanity check.
	 */
	if (++steps > 100) {
	    dsb_simple(why, "4.3.0", "SASL authentication failed; "
		       "authentication protocol loop with server %s",
		       session->namaddr);
	    return (-1);
	}

	/*
	 * Process a server challenge.
	 */
	line = resp->str;
	(void) mystrtok(&line, "- \t\n");	/* skip over result code */
	result = xsasl_client_next(session->sasl_client, line,
				   session->sasl_reply);
	if (result != XSASL_AUTH_OK) {
	    dsb_update(why, "4.7.0", DSB_DEF_ACTION,	/* Fix 200512 */
		    DSB_SKIP_RMTA, DSB_DTYPE_SASL, STR(session->sasl_reply),
		       "SASL authentication failed; "
		       "cannot authenticate to server %s: %s",
		       session->namaddr, STR(session->sasl_reply));
	    return (-1);			/* Fix 200512 */
	}

	/*
	 * Send a client response.
	 */
	smtp_chat_cmd(session, "%s", STR(session->sasl_reply));
    }

    /*
     * We completed the authentication protocol.
     */
    if (resp->code / 100 != 2) {
#ifdef HAVE_SASL_AUTH_CACHE
	/* Update the 535 authentication failure cache. */
	if (smtp_sasl_auth_cache && resp->code == 535)
	    smtp_sasl_auth_cache_store(smtp_sasl_auth_cache, session, resp);
#endif
	if (var_smtp_sasl_auth_soft_bounce && resp->code / 100 == 5)
	    STR(resp->dsn_buf)[0] = '4';
	dsb_update(why, resp->dsn, DSB_DEF_ACTION,
		   DSB_MTYPE_DNS, session->host,
		   var_procname, resp->str,
		   "SASL authentication failed; server %s said: %s",
		   session->namaddr, resp->str);
	return (0);
    }
    return (1);
}
Exemple #5
0
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;
    uid_t   alias_uid;
    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 messags 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(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 ((alias_uid = dict_owner(*cpp)) == 0) {
		alias_pwd = 0;
		RESET_USER_ATTR(usr_attr, state.level);
	    } else {
		if ((alias_pwd = mypwuid(alias_uid)) == 0) {
		    msg_warn("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 (dict_errno != 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 (dict_errno != 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));
	    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);
}
Exemple #6
0
static int eval_command_status(int command_status, char *service,
			          DELIVER_REQUEST *request, PIPE_ATTR *attr,
			               DSN_BUF *why)
{
    RECIPIENT *rcpt;
    int     status;
    int     result = 0;
    int     n;
    char   *saved_text;

    /*
     * Depending on the result, bounce or defer the message, and mark the
     * recipient as done where appropriate.
     */
    switch (command_status) {
    case PIPE_STAT_OK:
	/* Save the command output before dsb_update() clobbers it. */
	vstring_truncate(why->reason, trimblanks(STR(why->reason),
			      VSTRING_LEN(why->reason)) - STR(why->reason));
	if (VSTRING_LEN(why->reason) > 0) {
	    VSTRING_TERMINATE(why->reason);
	    saved_text =
		vstring_export(vstring_sprintf(
				    vstring_alloc(VSTRING_LEN(why->reason)),
					    " (%.100s)", STR(why->reason)));
	} else
	    saved_text = mystrdup("");		/* uses shared R/O storage */
	dsb_update(why, "2.0.0", (attr->flags & PIPE_OPT_FINAL_DELIVERY) ?
		   "delivered" : "relayed", DSB_SKIP_RMTA, DSB_SKIP_REPLY,
		   "delivered via %s service%s", service, saved_text);
	myfree(saved_text);
	(void) DSN_FROM_DSN_BUF(why);
	for (n = 0; n < request->rcpt_list.len; n++) {
	    rcpt = request->rcpt_list.info + n;
	    status = sent(DEL_REQ_TRACE_FLAGS(request->flags),
			  request->queue_id, &request->msg_stats, rcpt,
			  service, &why->dsn);
	    if (status == 0 && (request->flags & DEL_REQ_FLAG_SUCCESS))
		deliver_completed(request->fp, rcpt->offset);
	    result |= status;
	}
	break;
    case PIPE_STAT_BOUNCE:
    case PIPE_STAT_DEFER:
	(void) DSN_FROM_DSN_BUF(why);
	for (n = 0; n < request->rcpt_list.len; n++) {
	    rcpt = request->rcpt_list.info + n;
	    /* XXX Maybe encapsulate this with ndr_append(). */
	    status = (STR(why->status)[0] != '4' ?
		      bounce_append : defer_append)
		(DEL_REQ_TRACE_FLAGS(request->flags),
		 request->queue_id,
		 &request->msg_stats, rcpt,
		 service, &why->dsn);
	    if (status == 0)
		deliver_completed(request->fp, rcpt->offset);
	    result |= status;
	}
	break;
    case PIPE_STAT_CORRUPT:
	/* XXX DSN should we send something? */
	result |= DEL_STAT_DEFER;
	break;
    default:
	msg_panic("eval_command_status: bad status %d", command_status);
	/* NOTREACHED */
    }

    return (result);
}
Exemple #7
0
int     deliver_dotforward(LOCAL_STATE state, USER_ATTR usr_attr, int *statusp)
{
    const char *myname = "deliver_dotforward";
    struct stat st;
    VSTRING *path;
    struct mypasswd *mypwd;
    int     fd;
    VSTREAM *fp;
    int     status;
    int     forward_found = NO;
    int     lookup_status;
    int     addr_count;
    char   *saved_forward_path;
    char   *lhs;
    char   *next;
    int     expand_status;
    int     saved_notify;

    /*
     * Make verbose logging easier to understand.
     */
    state.level++;
    if (msg_verbose)
        MSG_LOG_STATE(myname, state);

    /*
     * Skip this module if per-user forwarding is disabled.
     */
    if (*var_forward_path == 0)
        return (NO);

    /*
     * Skip non-existing users. The mailbox delivery routine will catch the
     * error.
     */
    if ((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");
        *statusp = defer_append(BOUNCE_FLAGS(state.request),
                                BOUNCE_ATTR(state.msg_attr));
        return (YES);
    }
    if (mypwd == 0)
        return (NO);

    /*
     * From here on no early returns or we have a memory leak.
     */

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

    /*
     * DELIVERY RIGHTS
     *
     * Do not inherit rights from the .forward file owner. Instead, use the
     * recipient's rights, and insist that the .forward file is owned by the
     * recipient. This is a small but significant difference. Use the
     * recipient's rights for all /file and |command deliveries, and pass on
     * these rights to command/file destinations in included files. When
     * these are the rights of root, the /file and |command delivery routines
     * will use unprivileged default rights instead. Better safe than sorry.
     */
    SET_USER_ATTR(usr_attr, mypwd, state.level);

    /*
     * DELIVERY POLICY
     *
     * Update the expansion type attribute so that we can decide if deliveries
     * to |command and /file/name are allowed at all.
     */
    state.msg_attr.exp_type = EXPAND_TYPE_FWD;

    /*
     * WHERE TO REPORT DELIVERY PROBLEMS
     *
     * Set the owner attribute so that 1) include files won't set the sender to
     * be this user and 2) mail forwarded to other local users will be
     * resubmitted as a new queue file.
     */
    state.msg_attr.owner = state.msg_attr.user;

    /*
     * Search the forward_path for an existing forward file.
     *
     * If unmatched extensions should never be propagated, or if a forward file
     * name includes the address extension, don't propagate the extension to
     * the recipient addresses.
     */
    status = 0;
    path = vstring_alloc(100);
    saved_forward_path = mystrdup(var_forward_path);
    next = saved_forward_path;
    lookup_status = -1;

    while ((lhs = mystrtok(&next, ", \t\r\n")) != 0) {
        expand_status = local_expand(path, lhs, &state, &usr_attr,
                                     var_fwd_exp_filter);
        if ((expand_status & (MAC_PARSE_ERROR | MAC_PARSE_UNDEF)) == 0) {
            lookup_status =
                lstat_as(STR(path), &st, usr_attr.uid, usr_attr.gid);
            if (msg_verbose)
                msg_info("%s: path %s expand_status %d look_status %d", myname,
                         STR(path), expand_status, lookup_status);
            if (lookup_status >= 0) {
                if ((expand_status & LOCAL_EXP_EXTENSION_MATCHED) != 0
                        || (local_ext_prop_mask & EXT_PROP_FORWARD) == 0)
                    state.msg_attr.unmatched = 0;
                break;
            }
        }
    }

    /*
     * Process the forward file.
     *
     * Assume that usernames do not have file system meta characters. Open the
     * .forward file as the user. Ignore files that aren't regular files,
     * files that are owned by the wrong user, or files that have world write
     * permission enabled.
     *
     * DUPLICATE/LOOP ELIMINATION
     *
     * If this user includes (an alias of) herself in her own .forward file,
     * deliver to the user instead.
     */
    if (lookup_status >= 0) {

        /*
         * 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",
                       "forward via file: %s", STR(path));
            *statusp = sent(BOUNCE_FLAGS(state.request),
                            SENT_ATTR(state.msg_attr));
            forward_found = YES;
        } else if (been_here(state.dup_filter, "forward %s", STR(path)) == 0) {
            state.msg_attr.exp_from = state.msg_attr.local;
            if (S_ISREG(st.st_mode) == 0) {
                msg_warn("file %s is not a regular file", STR(path));
            } else if (st.st_uid != 0 && st.st_uid != usr_attr.uid) {
                msg_warn("file %s has bad owner uid %ld",
                         STR(path), (long) st.st_uid);
            } else if (st.st_mode & 002) {
                msg_warn("file %s is world writable", STR(path));
            } else if ((fd = open_as(STR(path), O_RDONLY, 0, usr_attr.uid, usr_attr.gid)) < 0) {
                msg_warn("cannot open file %s: %m", STR(path));
            } else {

                /*
                 * XXX DSN. When delivering to an alias (i.e. the envelope
                 * sender address 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.
                 */
                close_on_exec(fd, CLOSE_ON_EXEC);
                addr_count = 0;
                fp = vstream_fdopen(fd, O_RDONLY);
                saved_notify = state.msg_attr.rcpt.dsn_notify;
                state.msg_attr.rcpt.dsn_notify =
                    (saved_notify == DSN_NOTIFY_SUCCESS ?
                     DSN_NOTIFY_NEVER : saved_notify & ~DSN_NOTIFY_SUCCESS);
                status = deliver_token_stream(state, usr_attr, fp, &addr_count);
                if (vstream_fclose(fp))
                    msg_warn("close file %s: %m", STR(path));
                if (addr_count > 0) {
                    forward_found = YES;
                    been_here(state.dup_filter, "forward-done %s", STR(path));

                    /*
                     * XXX DSN. When delivering to an alias (i.e. the
                     * envelope sender address is not replaced) and the
                     * original NOTIFY parameter for the alias contained the
                     * SUCCESS keyword, an "expanded" DSN is issued for the
                     * alias.
                     */
                    if (status == 0 && (saved_notify & DSN_NOTIFY_SUCCESS)) {
                        state.msg_attr.rcpt.dsn_notify = saved_notify;
                        dsb_update(state.msg_attr.why, "2.0.0", "expanded",
                                   DSB_SKIP_RMTA, DSB_SKIP_REPLY,
                                   "alias expanded");
                        (void) trace_append(BOUNCE_FLAG_NONE,
                                            SENT_ATTR(state.msg_attr));
                    }
                }
            }
        } else if (been_here_check(state.dup_filter, "forward-done %s", STR(path)) != 0)
            forward_found = YES;		/* else we're recursive */
    }

    /*
     * Clean up.
     */
    vstring_free(path);
    myfree(saved_forward_path);
    mypwfree(mypwd);

    *statusp = status;
    return (forward_found);
}
Exemple #8
0
static int forward_send(FORWARD_INFO *info, DELIVER_REQUEST *request,
			        DELIVER_ATTR attr, char *delivered)
{
    const char *myname = "forward_send";
    VSTRING *buffer = vstring_alloc(100);
    VSTRING *folded;
    int     status;
    int     rec_type = 0;

    /*
     * Start the message content segment. Prepend our Delivered-To: header to
     * the message data. Stop at the first error. XXX Rely on the front-end
     * services to enforce record size limits.
     */
    rec_fputs(info->cleanup, REC_TYPE_MESG, "");
    vstring_strcpy(buffer, delivered);
    rec_fprintf(info->cleanup, REC_TYPE_NORM, "Received: by %s (%s)",
		var_myhostname, var_mail_name);
    rec_fprintf(info->cleanup, REC_TYPE_NORM, "\tid %s; %s",
		info->queue_id, mail_date(info->posting_time.tv_sec));
    if (local_deliver_hdr_mask & DELIVER_HDR_FWD) {
	folded = vstring_alloc(100);
	rec_fprintf(info->cleanup, REC_TYPE_NORM, "Delivered-To: %s",
		    casefold(folded, (STR(buffer))));
	vstring_free(folded);
    }
    if ((status = vstream_ferror(info->cleanup)) == 0)
	if (vstream_fseek(attr.fp, attr.offset, SEEK_SET) < 0)
	    msg_fatal("%s: seek queue file %s: %m:",
		      myname, VSTREAM_PATH(attr.fp));
    while (status == 0 && (rec_type = rec_get(attr.fp, buffer, 0)) > 0) {
	if (rec_type != REC_TYPE_CONT && rec_type != REC_TYPE_NORM)
	    break;
	status = (REC_PUT_BUF(info->cleanup, rec_type, buffer) != rec_type);
    }
    if (status == 0 && rec_type != REC_TYPE_XTRA) {
	msg_warn("%s: bad record type: %d in message content",
		 info->queue_id, rec_type);
	status |= mark_corrupt(attr.fp);
    }

    /*
     * Send the end-of-data marker only when there were no errors.
     */
    if (status == 0) {
	rec_fputs(info->cleanup, REC_TYPE_XTRA, "");
	rec_fputs(info->cleanup, REC_TYPE_END, "");
    }

    /*
     * Retrieve the cleanup service completion status only if there are no
     * problems.
     */
    if (status == 0)
	if (vstream_fflush(info->cleanup)
	    || attr_scan(info->cleanup, ATTR_FLAG_MISSING,
			 RECV_ATTR_INT(MAIL_ATTR_STATUS, &status),
			 ATTR_TYPE_END) != 1)
	    status = 1;

    /*
     * Log successful forwarding.
     * 
     * XXX DSN alias and .forward expansion already report SUCCESS, so don't do
     * it again here.
     */
    if (status == 0) {
	attr.rcpt.dsn_notify =
	    (attr.rcpt.dsn_notify == DSN_NOTIFY_SUCCESS ?
	     DSN_NOTIFY_NEVER : attr.rcpt.dsn_notify & ~DSN_NOTIFY_SUCCESS);
	dsb_update(attr.why, "2.0.0", "relayed", DSB_SKIP_RMTA, DSB_SKIP_REPLY,
		   "forwarded as %s", info->queue_id);
	status = sent(BOUNCE_FLAGS(request), SENT_ATTR(attr));
    }

    /*
     * Cleanup.
     */
    vstring_free(buffer);
    return (status);
}