/*
* Copyright (c) 2003-2006 Sendmail, Inc. and its suppliers.
* All rights reserved.
*
* By using this file, you agree to the terms and conditions set
* forth in the LICENSE file which can be found at the top level of
* the sendmail distribution.
*/
#include "sm/generic.h"
SM_RCSID("@(#)$Id: bounce.c,v 1.108 2007/06/02 04:11:40 ca Exp $")
#include "sm/error.h"
#include "sm/assert.h"
#include "sm/memops.h"
#include "sm/qmgr.h"
#include "sm/qmgr-int.h"
#include "sm/da.h"
#include "qmgr.h"
#include "log.h"
/*
** QM_BOUNCE_NEW -- Create new "bounce" recipient
**
** Parameters:
** qmgr_ctx -- QMGR context
** aq_ta -- AQ transaction
** bounce_type -- type of "bounce" (see below)
** owner_idx -- reference to owner- address (0: none)
** paq_rcpt_bounce -- (pointer to) bounce recipient (output)
**
** Returns:
** usual sm_error code; ENOMEM, SM_E_FULL, et.al.
**
** Side Effects: none on error
**
** Locking: aq_ctx must be locked by caller.
**
** Operation:
** Create new aq_rcpt which will be used to send out a DSN.
** rcpt_idx will be aqt_nxt_idx (which will also be stored in
** aqt_dbl_bounce_idx/aqt_bounce_idx/aqt_delay_idx).
** aqr_dsns is an array of rcpt_idx's which will be sent with this DSN;
** this array is probably oversized.
**
** Invokes qmgr_rcpt2ar() unless it's a dDSN (should be a flag instead
** of local "knowledge" that a dDSN isn't scheduled directly).
**
** Last code review: 2005-03-29 00:52:28; see comments below
** Last code change: 2005-05-02 23:51:40
*/
#define SM_STR_BOUNCE_LEN 1024
#define SM_STR_DBL_BOUNCE_LEN (SM_STR_BOUNCE_LEN + 256)
/* bounce_type */
#define SM_DELAY 1
#define SM_BOUNCE 2
#define SM_DBL_BOUNCE 4
static sm_ret_T
qm_bounce_new(qmgr_ctx_P qmgr_ctx, aq_ta_P aq_ta, uint bounce_type, rcpt_idx_T owner_idx, aq_rcpt_P *paq_rcpt_bounce)
{
sm_ret_T ret;
uint flags;
rcpt_idx_T bounce_idx;
sm_str_P pa;
aq_ctx_P aq_ctx;
aq_rcpt_P aq_rcpt;
time_T time_now;
size_t size_dsns;
#define SM_BN_FL_IDX_INCR 0x0001 /* aq_ta->aqt_nxt_idx has been incr */
SM_IS_QMGR_CTX(qmgr_ctx);
SM_IS_AQ_TA(aq_ta);
SM_REQUIRE(aq_ta->aqt_nxt_idx > 0);
aq_ctx = qmgr_ctx->qmgr_aq;
flags = 0;
if (aq_ta->aqt_nxt_idx >= SMTP_RCPTIDX_MAX - 1)
{
/*
** Oops, we reached the limit. There's no way to recover from
** this except by generating a completely new transaction.
** This limit should never be reached...
*/
return sm_error_perm(SM_EM_Q_DSN, SM_E_FULL);
}
if (bounce_type == SM_DBL_BOUNCE)
bounce_idx = aq_ta->aqt_dbl_bounce_idx = aq_ta->aqt_nxt_idx++;
else if (bounce_type == SM_DELAY)
bounce_idx = aq_ta->aqt_delay_idx = aq_ta->aqt_nxt_idx++;
else if (bounce_type == SM_BOUNCE)
bounce_idx = aq_ta->aqt_bounce_idx = aq_ta->aqt_nxt_idx++;
else
{
SM_ASSERT((bounce_type == SM_DBL_BOUNCE) ||
(bounce_type == SM_DELAY) ||
(bounce_type == SM_BOUNCE));
return sm_error_perm(SM_EM_Q_DSN, SM_E_UNEXPECTED);
/* NOTREACHED */
}
SM_SET_FLAG(flags, SM_BN_FL_IDX_INCR);
#if QMGR_TEST
if (SM_IS_FLAG(qmgr_ctx->qmgr_cnf.q_cnf_tests, QMGR_TEST_BNC_RCPT_ADD))
ret = sm_error_temp(SM_EM_AQ, ENOMEM);
else /* WARNING: be careful about changing the next statement */
#endif
ret = aq_rcpt_add_new(aq_ctx, aq_ta, paq_rcpt_bounce, 0, THR_NO_LOCK);
if (sm_is_err(ret))
{
SM_ASSERT(aq_ta->aqt_nxt_idx > 0);
--aq_ta->aqt_nxt_idx;
SM_CLR_FLAG(flags, SM_BN_FL_IDX_INCR);
return ret;
}
/* Just to simplify code below */
aq_rcpt = *paq_rcpt_bounce;
/*
** Can we use aqt_rcpts_perm instead? Is that value correct when
** the function is invoked or is it still incremented while going
** through the recipient lists? Note: there might be also timeouts
** for temporary errors so it would be at least
** aqt_rcpts_perm + aqt_rcpts_temp.
**
** If it is a double bounce: only 1 entry? Not necessarily:
** there could be multiple bounces.
*/
aq_rcpt->aqr_dsn_rcpts_max =
(qmgr_ctx->qmgr_cnf.q_cnf_dsn_rcpts_max > 0 &&
qmgr_ctx->qmgr_cnf.q_cnf_dsn_rcpts_max < aq_ta->aqt_rcpts_tot)
? qmgr_ctx->qmgr_cnf.q_cnf_dsn_rcpts_max
: aq_ta->aqt_rcpts_tot;
size_dsns = sizeof(*(aq_rcpt->aqr_dsns)) * aq_rcpt->aqr_dsn_rcpts_max;
if (size_dsns < aq_rcpt->aqr_dsn_rcpts_max) /* overflow? */
{
/* this is way too big... */
aq_rcpt->aqr_dsn_rcpts_max = UINT_MAX /
sizeof(*(aq_rcpt->aqr_dsns));
aq_rcpt->aqr_dsn_rcpts_max /= 2;
size_dsns = sizeof(*(aq_rcpt->aqr_dsns)) *
aq_rcpt->aqr_dsn_rcpts_max;
SM_ASSERT(size_dsns >= aq_rcpt->aqr_dsn_rcpts_max);
}
#if QMGR_TEST
if (SM_IS_FLAG(qmgr_ctx->qmgr_cnf.q_cnf_tests, QMGR_TEST_ALLOC_DSNS))
aq_rcpt->aqr_dsns = NULL;
else /* WARNING: be careful about changing the next statement */
#endif
aq_rcpt->aqr_dsns = (rcpt_idx_T *) sm_malloc(size_dsns);
if (aq_rcpt->aqr_dsns == NULL)
{
aq_rcpt->aqr_dsn_rcpts_max = 0;
goto error;
}
SM_ASSERT(owner_idx <= aq_ta->aqt_owners_n);
/* Fill in data... (should there be a "double bounce address"??) */
pa = (bounce_type == SM_DBL_BOUNCE) ? qmgr_ctx->qmgr_pm_addr
: ((owner_idx > 0) ? aq_ta->aqt_owners_pa[owner_idx - 1]
: aq_ta->aqt_mail->aqm_pa);
aq_rcpt->aqr_pa = sm_str_dup(NULL, pa);
if (aq_rcpt->aqr_pa == NULL)
goto error;
ret = aq_rcpt_set_domain(aq_rcpt, qmgr_ctx->qmgr_hostname);
if (sm_is_err(ret))
goto error;
AQR_DA_INIT(aq_rcpt);
AQR_SS_INIT(aq_rcpt);
aq_rcpt->aqr_idx = bounce_idx;
aq_rcpt->aqr_owner_idx = owner_idx;
time_now = evthr_time(qmgr_ctx->qmgr_ev_ctx);
aq_rcpt->aqr_entered = time_now;
aq_rcpt->aqr_st_time = time_now;
/* Use aqr_dsn_msg to hold error message report */
#if SM_RCB_MAX_LEN <= 2048
ERROR _SM_RCB_MAX_LEN <= 2048
SM_ASSERT(SM_RCB_MAX_LEN > 2048);
#endif
aq_rcpt->aqr_dsn_msg = sm_str_new(NULL,
(bounce_type == SM_DBL_BOUNCE)
? SM_STR_DBL_BOUNCE_LEN
: SM_STR_BOUNCE_LEN,
SM_RCB_MAX_LEN - 1024);
if (aq_rcpt->aqr_dsn_msg == NULL)
goto error;
/*
** fixme: From which queue? It's newly created... What to do?
** Usually a rcpt address is either in IBDB or DEFEDB, however,
** this is "artificial". Should it be recreated each time or
** stored in DEFEDB? If the latter, when?
*/
if (bounce_type == SM_DBL_BOUNCE)
AQR_SET_FLAG(aq_rcpt, AQR_FL_IS_DBNC);
else if (bounce_type == SM_DELAY)
AQR_SET_FLAG(aq_rcpt, AQR_FL_IS_DLY);
else
AQR_SET_FLAG(aq_rcpt, AQR_FL_IS_BNC);
SESSTA_COPY(aq_rcpt->aqr_ss_ta_id, aq_ta->aqt_ss_ta_id);
aq_rcpt->aqr_status = AQR_ST_NEW;
/* Send to SMAR */
if (bounce_type != SM_DELAY)
{
ret = qmgr_rcpt2ar(qmgr_ctx, aq_rcpt, THR_NO_LOCK);
#if 0
/* ignore error, rely on cleanup */
if (sm_is_err(ret))
goto error;
#endif
}
++aq_ta->aqt_rcpts_tot;
++aq_ta->aqt_rcpts_inaq;
QM_LEV_DPRINTFC(QDC_BOUNCE, 6, (QM_DEBFP, "sev=DBG, func=qm_bounce_new, bounce_type=%u, aq_rcpt=%p, aqr_dsn_rcpts_max=%d, aqt_rcpts_tot=%u, aqt_rcpts_inaq=%u\n", bounce_type, aq_rcpt, aq_rcpt->aqr_dsn_rcpts_max, aq_ta->aqt_rcpts_tot, aq_ta->aqt_rcpts_inaq));
return SM_SUCCESS;
error:
/* More clean up?? */
if (SM_IS_FLAG(flags, SM_BN_FL_IDX_INCR))
{
SM_ASSERT(aq_ta->aqt_nxt_idx > 0);
--aq_ta->aqt_nxt_idx;
SM_CLR_FLAG(flags, SM_BN_FL_IDX_INCR);
}
(void) aq_rcpt_rm(aq_ctx, aq_rcpt, 0);
ret = sm_is_err(ret) ? ret : sm_error_temp(SM_EM_Q_DSN, ENOMEM);
QM_LEV_DPRINTFC(QDC_BOUNCE, 0, (QM_DEBFP, "sev=ERROR, qm_bounce_new=%r\n", ret));
return ret;
}
/*
** QM_DSN_WHERE -- Return "where" something went wrong
** (see also aq_rcpt_err_state())
**
** Parameters:
** aq_rcpt -- AQ recipient
** err_txt -- buffer to write error text (for "variable" text)
** err_len -- length of buffer to write error text
** perr_msg -- error text (output)
**
** Returns:
** SM_SUCCESS
**
** Last code review:
** Last code change:
*/
static sm_ret_T
qm_dsn_where(aq_rcpt_P aq_rcpt, char *err_txt, uint err_len, char **perr_msg)
{
char *err_msg;
SM_REQUIRE(aq_rcpt != NULL);
SM_REQUIRE(perr_msg != NULL);
err_msg = NULL;
switch (da_err_cmd(aq_rcpt->aqr_err_st))
{
case DA_TA_ERR_MAIL:
err_msg = "during MAIL\r\n";
break;
case DA_TA_ERR_RCPT:
err_msg = "during RCPT\r\n";
break;
case DA_TA_ERR_DATA:
err_msg = "during DATA\r\n";
break;
case DA_TA_ERR_CDB:
err_msg = "during opening of body\r\n";
break;
case DA_TA_ERR_BODY:
err_msg = "during transmission of body\r\n";
break;
case DA_TA_ERR_DOT:
err_msg = "during final dot\r\n";
break;
case DA_TA_ERR_L_RCPT:
err_msg = "during LMTP RCPT\r\n";
break;
case DA_TA_ERR_RSET:
err_msg = "during RSET\r\n";
break;
case DA_TA_ERR_SIZE:
err_msg = "during SIZE check\r\n";
break;
case DA_SE_ERR_OPEN:
err_msg = "during session open\r\n";
break;
case DA_SE_ERR_GRET:
err_msg = "during session greeting\r\n";
break;
case DA_SE_ERR_EHLO:
err_msg = "during EHLO\r\n";
break;
case DA_SE_ERR_HELO:
err_msg = "during HELO\r\n";
break;
case DA_SE_ERR_STLS:
err_msg = "during STARTTLS\r\n";
break;
case DA_SE_ERR_TTMYSLEF:
#if 0
err_msg = "server greeting contains my hostname\r\n";
#else /* 0 */
err_msg = "config error: mail loops back to me\r\n";
#endif /* 0 */
break;
case DA_AR_ERR:
switch (aq_rcpt->aqr_status)
{
case SMTP_AR_NOTF:
err_msg = "error from address resolver\r\n"
"record not found\r\n";
break;
case SMTP_AR_TEMP:
err_msg = "error from address resolver\r\n"
"temporary error\r\n";
break;
case SMTP_AR_PERM:
err_msg = "error from address resolver\r\n"
"permanent error\r\n";
break;
case SMTP_AR_TMO:
err_msg = "error from address resolver\r\n"
"timeout during query\r\n";
break;
case SMTP_AR_MXEMPTY:
err_msg = "error from address resolver\r\n"
"MX points back to me\r\n";
break;
case SMTP_AR_ALIAS:
err_msg = "error from address resolver\r\n"
"alias expansion failed\r\n";
break;
case SMTP_AR_AL_REC:
err_msg = "error from address resolver\r\n"
"alias nesting too deep\r\n";
break;
case SMTP_AR_LOOP:
err_msg = "error from address resolver\r\n"
"CNAME recursion\r\n";
break;
case SMTP_MAP_TEMP:
err_msg = "error from address resolver\r\n"
"temporary map lookup error\r\n";
break;
default:
sm_snprintf(err_txt, err_len,
IS_SMTP_REPLY(aq_rcpt->aqr_status)
? "%d from address resolver\r\n"
: "unknown error %d from address resolver\r\n"
, aq_rcpt->aqr_status);
err_msg = err_txt;
break;
}
break;
case DA_AQ_TMO_ERR:
err_msg = "item too long in active queue\r\n";
break;
case DA_DQ_TMO_ERR:
err_msg = "maximum time in queue exceeded\r\n";
break;
case DA_TERMINATED:
err_msg = "delivery agent module did not respond before timeout\r\n";
break;
}
*perr_msg = err_msg;
return SM_SUCCESS;
}
/*
** QM_DSN_COMPOSE -- Compose DSN (bounce) message
** (see also qm_dsn_log())
**
** Parameters:
** aq_rcpt -- AQ recipient
** aq_rcpt_bounce -- AQ recipient containing the bounce
** errmsg -- error message (might be NULL)
** (must be "sanitized" by caller)
**
** Returns:
** error: usual sm_error code; SM_E_UNEXPECTED,
**
** Side Effects:
** fills in aq_rcpt_bounce->aqr_dsn_msg;
** caller needs to clean this up in case of an error.
**
** Locking: aq_ctx must be locked by caller.
**
** Last code review: 2005-03-24 19:25:34
** Last code change: 2005-06-09 02:05:38
*/
static sm_ret_T
qm_dsn_compose(aq_rcpt_P aq_rcpt, aq_rcpt_P aq_rcpt_bounce, sm_str_P errmsg)
{
sm_ret_T ret;
char *err_msg, err_txt[80];
struct in_addr addr;
addr.s_addr = aq_rcpt->aqr_addr_fail;
/* Compose error message for bounce. */
if (sm_is_err(ret = sm_str_scat(aq_rcpt_bounce->aqr_dsn_msg,
"Recipient:\r\n"))
|| sm_is_err(ret = sm_str_cat(aq_rcpt_bounce->aqr_dsn_msg,
aq_rcpt->aqr_pa)))
goto error;
/* Alias? [information leak? don't show expanded alias??] */
if (aq_rcpt->aqr_orig_pa != NULL)
{
if (sm_is_err(ret = sm_str_scat(aq_rcpt_bounce->aqr_dsn_msg,
"\r\nExpanded from:\r\n"))
|| sm_is_err(ret = sm_str_cat(aq_rcpt_bounce->aqr_dsn_msg,
aq_rcpt->aqr_orig_pa)))
goto error;
}
if (aq_rcpt->aqr_addr_fail != 0 &&
(sm_is_err(ret = sm_str_scat(aq_rcpt_bounce->aqr_dsn_msg,
"\r\nRemote-MTA: "))
|| sm_is_err(ret = sm_inet_inaddr2str(addr,
aq_rcpt_bounce->aqr_dsn_msg))
|| sm_is_err(ret = sm_str_scat(aq_rcpt_bounce->aqr_dsn_msg,
"\r\n"))))
goto error;
if (errmsg != NULL && sm_str_getlen(errmsg) > 0)
{
if (sm_is_err(ret = sm_str_scat(aq_rcpt_bounce->aqr_dsn_msg,
"Reason:\r\n"))
|| sm_is_err(ret = sm_str_cat(aq_rcpt_bounce->aqr_dsn_msg,
errmsg))
|| sm_is_err(ret = sm_str_scat(aq_rcpt_bounce->aqr_dsn_msg,
"\r\n")))
goto error;
}
else if (aq_rcpt->aqr_msg != NULL &&
sm_str_getlen(aq_rcpt->aqr_msg) > 0)
{
if (sm_is_err(ret = sm_str_scat(aq_rcpt_bounce->aqr_dsn_msg,
"Reason:\r\n"))
|| sm_is_err(ret = sm_str_cat(aq_rcpt_bounce->aqr_dsn_msg,
aq_rcpt->aqr_msg))
|| sm_is_err(ret = sm_str_scat(aq_rcpt_bounce->aqr_dsn_msg,
"\r\n")))
goto error;
}
/* HACKs ahead (3): session open error status */
else if (aq_rcpt->aqr_status == SMTPC_SE_OP_TMO)
{
if (sm_is_err(ret = sm_str_scat(aq_rcpt_bounce->aqr_dsn_msg,
"timeout\r\n")))
goto error;
}
else if (aq_rcpt->aqr_status == SMTPC_SE_OP_REFUSED)
{
if (sm_is_err(ret = sm_str_scat(aq_rcpt_bounce->aqr_dsn_msg,
"connection refused\r\n")))
goto error;
}
else if (aq_rcpt->aqr_status == SMTPC_SE_OP_UNREACH)
{
if (sm_is_err(ret = sm_str_scat(aq_rcpt_bounce->aqr_dsn_msg,
"host/net unreachable\r\n")))
goto error;
}
else if (sm_is_err(ret = sm_str_scat(aq_rcpt_bounce->aqr_dsn_msg,
"\r\n")))
goto error;
err_msg = NULL;
(void) qm_dsn_where(aq_rcpt, err_txt, sizeof(err_txt), &err_msg);
if (err_msg != NULL &&
sm_is_err(ret = sm_str_scat(aq_rcpt_bounce->aqr_dsn_msg, err_msg)))
goto error;
/* print also error state itself? */
if (AQR_IS_FLAG(aq_rcpt, AQR_FL_DSN_TMT) &&
sm_is_err(ret = sm_str_scat(aq_rcpt_bounce->aqr_dsn_msg,
"maximum time in queue exceeded\r\n")))
goto error;
else if (sm_is_err(ret = sm_str_scat(aq_rcpt_bounce->aqr_dsn_msg,
"\r\n")))
goto error;
return SM_SUCCESS;
error:
return ret;
}
/*
** QM_DSN_LOG -- Log fact that DSN was created
** (see also qm_dsn_compose())
**
** Parameters:
** qmgr_ctx -- QMGR context
** aq_rcpt -- AQ recipient
** errmsg -- error message (might be NULL)
** (must be "sanitized" by caller)
**
** Returns:
** error: usual sm_error code
**
** Last code review:
** Last code change:
*/
static sm_ret_T
qm_dsn_log(qmgr_ctx_P qmgr_ctx, aq_rcpt_P aq_rcpt, sm_str_P errmsg)
{
sm_ret_T ret;
sm_str_P log;
struct in_addr addr;
ret = SM_SUCCESS;
if (!sm_log_wouldlog(qmgr_ctx->qmgr_lctx,
QM_LCAT_SCHED, QM_LMOD_BOUNCE, 9))
return ret;
/* use macros for size... */
log = sm_str_new(NULL, 256, 512);
if (NULL == log)
return sm_error_temp(SM_EM_Q_DSN, ENOMEM);
/* Compose log message for bounce. */
ret = sm_strprintf(log,
"ss_ta=%s, rcpt=%@S, rcpt_idx=%u"
, aq_rcpt->aqr_ss_ta_id, aq_rcpt->aqr_pa, aq_rcpt->aqr_idx);
addr.s_addr = aq_rcpt->aqr_addr_fail;
if (aq_rcpt->aqr_addr_fail != 0 &&
(sm_is_err(ret = sm_str_scat(log, ", remote-MTA="))
|| sm_is_err(ret = sm_inet_inaddr2str(addr, log))))
goto error;
if (errmsg != NULL && sm_str_getlen(errmsg) > 0)
{
if (sm_is_err(ret = sm_str_scat(log, ", reason="))
|| sm_is_err(ret = sm_str_cat(log, errmsg)))
goto error;
}
else if (aq_rcpt->aqr_msg != NULL &&
sm_str_getlen(aq_rcpt->aqr_msg) > 0)
{
if (sm_is_err(ret = sm_str_scat(log, ", reason="))
|| sm_is_err(ret = sm_str_cat(log, aq_rcpt->aqr_msg)))
goto error;
}
/* HACKs ahead (3): session open error status */
else if (aq_rcpt->aqr_status == SMTPC_SE_OP_TMO)
{
if (sm_is_err(ret = sm_str_scat(log, ", reason=timeout")))
goto error;
}
else if (aq_rcpt->aqr_status == SMTPC_SE_OP_REFUSED)
{
if (sm_is_err(ret = sm_str_scat(log,
", reason=connection refused")))
goto error;
}
else if (aq_rcpt->aqr_status == SMTPC_SE_OP_UNREACH)
{
if (sm_is_err(ret = sm_str_scat(log,
", reason=host/net unreachable")))
goto error;
}
if (sm_is_err(ret = sm_str_scat(log, ", when=")))
goto error;
(void) aq_rcpt_err_state(aq_rcpt, true, log);
if (AQR_IS_FLAG(aq_rcpt, AQR_FL_DSN_TMT) &&
sm_is_err(ret = sm_str_scat(log,
", status=maximum time in queue exceeded")))
goto error;
sm_log_write(qmgr_ctx->qmgr_lctx,
QM_LCAT_SCHED, QM_LMOD_BOUNCE,
SM_LOG_INFO, 9,
"sev=INFO, func=qm_dsn_log, %S", log);
SM_STR_FREE(log);
return SM_SUCCESS;
error:
SM_STR_FREE(log);
return ret;
}
/*
** QM_BOUNCE_ADD -- Add a new recipient to a DSN (might create new aq_rcpt)
**
** Parameters:
** qmgr_ctx -- QMGR context
** aq_ta -- AQ transaction
** aq_rcpt -- AQ recipient (which causes the DSN)
** errmsg -- error message (might be NULL)
** (must be "sanitized" by caller)
** paq_rcpt_dsn -- (pointer to) AQ DSN recipient (output)
**
** Returns:
** success: flags as shown in sm/qmgr-int.h
** to activate scheduler or SMAR.
** Note: caller must act on this!
** error: usual sm_error code; ENOMEM, SM_E_UNEXPECTED,
**
** Side Effects:
** creates new "bounce" rcpt if non exists or if the bounce
** message is too long for an existing rcpt.
**
** Called by: qm_get_edb_entries(), q_upd_rcpt_fail()
**
** Locking: aq_ctx must be locked by caller.
**
** Note: if creating a double bounce fails, should this just be
** logged (with all necessary information?) and then
** be ignored (by the caller?)?
**
** Last code review: 2005-03-24 22:06:32; see comments below
** Last code change: 2005-05-02 23:51:28
*/
sm_ret_T
qm_bounce_add(qmgr_ctx_P qmgr_ctx, aq_ta_P aq_ta, aq_rcpt_P aq_rcpt, sm_str_P errmsg, aq_rcpt_P *paq_rcpt_dsn)
{
sm_ret_T ret, rflags;
uint flags;
rcpt_idx_T bounce_idx;
sm_str_T dsn_msg_save;
aq_ctx_P aq_ctx;
aq_rcpt_P aq_rcpt_dsn;
/* dsn msg has been saved in dsn_msg_save */
#define SM_B_FL_SAVED_DSN_MSG 0x0001
/* a new bounce recipient has been created */
#define SM_B_FL_NEW_BOUNCE 0x0002
/* sm_str_scat() failed */
#define SM_B_FL_SCAT_FAIL 0x0004
/* been in error code section before */
#define SM_B_FL_ERROR 0x0008
/* try again */
#define SM_B_FL_AGAIN 0x0010
SM_IS_QMGR_CTX(qmgr_ctx);
SM_IS_AQ_TA(aq_ta);
aq_ctx = qmgr_ctx->qmgr_aq;
aq_rcpt_dsn = NULL;
sm_memzero(&dsn_msg_save, sizeof(dsn_msg_save));
rflags = 0;
flags = 0;
/* Is this a double bounce? Shouldn't happen ... */
if (AQR_IS_FLAG(aq_rcpt, AQR_FL_IS_DBNC))
{
sm_log_write(qmgr_ctx->qmgr_lctx,
QM_LCAT_SCHED, QM_LMOD_BOUNCE,
SM_LOG_INCONS, 2,
"sev=FATAL, func=qm_bounce_add, ss_ta=%s, rcpt_idx=%u, aqr_flags=%#x, status=unexpected_doublebounce",
aq_ta->aqt_ss_ta_id, aq_rcpt->aqr_idx,
aq_rcpt->aqr_flags);
return sm_error_perm(SM_EM_Q_DSN, SM_E_UNEXPECTED);
}
/* Check this earlier?? (explicitly check for "<>"?) w*/
if (!AQ_TA_IS_FLAG(aq_ta, AQ_TA_FL_EMPTYSENDER) &&
sm_str_getlen(aq_ta->aqt_mail->aqm_pa) == 2)
AQ_TA_SET_FLAG(aq_ta, AQ_TA_FL_EMPTYSENDER);
if (AQ_TA_IS_FLAG(aq_ta, AQ_TA_FL_EMPTYSENDER))
AQR_SET_FLAG(aq_rcpt, AQR_FL_IS_BNC);
/* Does a bounce recipient exist? */
bounce_idx = 0; /* == has no bounce */
if (AQR_IS_DSNFL(aq_rcpt, AQR_DSNFL_D_NTG) &&
!AQR_IS_DSNFL(aq_rcpt, AQR_DSNFL_D_HBG) &&
aq_ta_has_delay(aq_ta))
bounce_idx = aq_ta->aqt_delay_idx;
else if (AQR_IS_FLAG(aq_rcpt, AQR_FL_IS_BNC) &&
aq_ta_has_dbl_bounce(aq_ta))
bounce_idx = aq_ta->aqt_dbl_bounce_idx;
else if (!AQR_IS_FLAG(aq_rcpt, AQR_FL_IS_BNC) &&
aq_ta_has_bounce(aq_ta))
bounce_idx = aq_ta->aqt_bounce_idx;
QM_LEV_DPRINTFC(QDC_BOUNCE, 3, (QM_DEBFP, "sev=DBG, func=qm_bounce_add, aq_ta=%p, aq_rcpt=%p, aqr_ss_ta=%s, aqr_flags=%#x, has_bounce=%d, dbl_bounce_idx=%d, bounce_idx=%d, err_st=%#x\n", aq_ta, aq_rcpt, aq_rcpt->aqr_ss_ta_id, aq_rcpt->aqr_flags, aq_ta_has_bounce(aq_ta), aq_ta->aqt_dbl_bounce_idx, aq_ta->aqt_bounce_idx, aq_rcpt->aqr_err_st));
if (bounce_idx != 0)
{
/* let's find the bounce rcpt ... */
ret = aq_rcpt_find_ss(aq_ctx, aq_ta->aqt_ss_ta_id, bounce_idx,
THR_NO_LOCK, &aq_rcpt_dsn);
if (sm_is_err(ret))
{
/* Not fatal, will simply create a new bounce */
aq_rcpt_dsn = NULL;
QM_LEV_DPRINTFC(QDC_BOUNCE, 0, (QM_DEBFP, "sev=INFO, func=qm_bounce_add, aq_ta=%p, aq_rcpt=%p, ss_ta=%s, idx=%d, aq_rcpt_find_ss=%#x, flags=%#x\n", aq_ta, aq_rcpt, aq_ta->aqt_ss_ta_id, bounce_idx, ret, aq_rcpt->aqr_flags));
sm_log_write(qmgr_ctx->qmgr_lctx,
QM_LCAT_SMTPS, QM_LMOD_BOUNCE,
SM_LOG_INFO, 9,
"sev=INFO, func=qm_bounce_add, ss_ta=%s, rcpt_idx=%d, bounce_idx=%d, aq_rcpt_find_ss=%m, flags=%#x",
aq_ta->aqt_ss_ta_id, aq_rcpt->aqr_idx, bounce_idx, ret, aq_rcpt->aqr_flags);
}
else if (
/* Recipient is already scheduled */
AQR_IS_FLAG(aq_rcpt_dsn, AQR_FL_SCHED) ||
/* all slots exhausted */
(aq_rcpt_dsn->aqr_dsn_rcpts
>= aq_rcpt_dsn->aqr_dsn_rcpts_max) ||
/* different bounce address */
(aq_rcpt_has_owner(aq_rcpt) &&
aq_rcpt->aqr_owner_idx !=
aq_rcpt_dsn->aqr_owner_idx)
)
{
aq_rcpt_dsn = NULL;
}
}
sm_log_write(qmgr_ctx->qmgr_lctx,
QM_LCAT_SCHED, QM_LMOD_BOUNCE,
SM_LOG_DEBUG, 15,
"func=qm_bounce_add, ss_ta=%s, rcpt_idx=%d, aqr_flags=%#x, has_bounce=%d, bounce_idx=%d, rcpt_bounce=%s",
aq_ta->aqt_ss_ta_id, aq_rcpt->aqr_idx, aq_rcpt->aqr_flags,
aq_ta_has_bounce(aq_ta), bounce_idx,
aq_rcpt_dsn == NULL ? "new" : "old");
do
{
SM_CLR_FLAG(flags, SM_B_FL_AGAIN);
if (aq_rcpt_dsn == NULL)
{
/* Create new bounce recipient */
ret = qm_bounce_new(qmgr_ctx, aq_ta,
AQR_IS_FLAG(aq_rcpt, AQR_FL_IS_BNC)
? SM_DBL_BOUNCE
/* XXX NOT CORRECT! How to recognize where a DELAY DSN should be generated? */
/* the sequence of operations is important, i.e., when is which flag set? */
: (AQR_IS_DSNFL(aq_rcpt,
AQR_DSNFL_D_NTG)
&& !AQR_IS_DSNFL(aq_rcpt,
AQR_DSNFL_D_HBG))
? SM_DELAY : SM_BOUNCE,
aq_rcpt->aqr_owner_idx,
&aq_rcpt_dsn);
if (sm_is_err(ret))
{
/* caller has to deal with this */
QM_LEV_DPRINTFC(QDC_BOUNCE, 0, (QM_DEBFP, "sev=ERROR, func=qm_bounce_add, aq_ta=%p, aq_rcpt=%p, qm_bounce_new=%r\n", aq_ta, aq_rcpt, ret));
sm_log_write(qmgr_ctx->qmgr_lctx,
QM_LCAT_SCHED, QM_LMOD_BOUNCE,
SM_LOG_ERR, 4,
"sev=ERROR, func=qm_bounce_add, ss_ta=%s, rcpt_idx=%u, qm_bounce_new=%m",
aq_ta->aqt_ss_ta_id, aq_rcpt->aqr_idx,
ret);
goto error;
}
SM_SET_FLAG(flags, SM_B_FL_NEW_BOUNCE);
if (aq_rcpt->aqr_dsn_msg != NULL)
{
SM_STR_SAVE(*aq_rcpt_dsn->aqr_dsn_msg,
dsn_msg_save);
SM_SET_FLAG(flags, SM_B_FL_SAVED_DSN_MSG);
}
/* if double bounce: copy old bounce message */
if (AQR_IS_FLAG(aq_rcpt_dsn, AQR_FL_IS_DBNC) &&
aq_rcpt->aqr_dsn_msg != NULL &&
(sm_is_err(ret = sm_str_cat(
aq_rcpt_dsn->aqr_dsn_msg,
aq_rcpt->aqr_dsn_msg))
|| sm_is_err(ret = sm_str_scat(
aq_rcpt_dsn->aqr_dsn_msg,
"\r\n\r\nOriginal bounce message follows:\r\n"))))
{
/*
** Shouldn't happen as double bounce msg is
** larger than a "normal" bounce msg.
** What to do in this case??
*/
sm_log_write(qmgr_ctx->qmgr_lctx,
QM_LCAT_SCHED, QM_LMOD_BOUNCE,
SM_LOG_ERR, 3,
"sev=ERROR, func=qm_bounce_add, ss_ta=%s, rcpt_idx=%d, where=create_double_bounce_dsn_msg, stat=%m",
aq_ta->aqt_ss_ta_id, aq_rcpt->aqr_idx,
ret);
goto error;
}
rflags |= QDA_FL_ACT_SMAR;
#if QMGR_DEBUG
/* Only required for debug output below */
if (AQR_IS_FLAG(aq_rcpt, AQR_FL_IS_BNC))
bounce_idx = aq_ta->aqt_dbl_bounce_idx;
else
bounce_idx = aq_ta->aqt_bounce_idx;
#endif /* QMGR_DEBUG */
}
else
rflags |= QDA_FL_ACT_SCHED;
if (!SM_IS_FLAG(flags, SM_B_FL_SAVED_DSN_MSG))
{
SM_STR_SAVE(*aq_rcpt_dsn->aqr_dsn_msg, dsn_msg_save);
SM_SET_FLAG(flags, SM_B_FL_SAVED_DSN_MSG);
}
#if QMGR_TEST
/*
** Trigger an error if flag is set and this is not the
** first bounce.
*/
if (SM_IS_FLAG(qmgr_ctx->qmgr_cnf.q_cnf_tests,
QMGR_TEST_BNC_FAIL)
&& !SM_IS_FLAG(flags, SM_B_FL_NEW_BOUNCE))
ret = sm_error_perm(SM_EM_STR, SM_E_OVFLW_NS);
else /* WARNING: be careful about changing the next statement */
#endif /* QMGR_TEST */
ret = qm_dsn_compose(aq_rcpt, aq_rcpt_dsn, errmsg);
if (sm_is_err(ret))
{
/* other errors? */
if (ret == sm_error_perm(SM_EM_STR, SM_E_OVFLW_SC) ||
ret == sm_error_perm(SM_EM_STR, SM_E_OVFLW_NS))
SM_SET_FLAG(flags, SM_B_FL_SCAT_FAIL);
if (SM_IS_FLAG(flags, SM_B_FL_SAVED_DSN_MSG) &&
aq_rcpt_dsn != NULL &&
aq_rcpt_dsn->aqr_dsn_msg != NULL)
{
SM_STR_RESTORE(*aq_rcpt_dsn->aqr_dsn_msg,
dsn_msg_save);
SM_CLR_FLAG(flags, SM_B_FL_SAVED_DSN_MSG);
}
if (!SM_IS_FLAG(flags, SM_B_FL_NEW_BOUNCE) &&
!SM_IS_FLAG(flags, SM_B_FL_ERROR) &&
SM_IS_FLAG(flags, SM_B_FL_SCAT_FAIL))
{
aq_rcpt_dsn = NULL;
SM_SET_FLAG(flags, SM_B_FL_AGAIN);
SM_SET_FLAG(flags, SM_B_FL_ERROR);
}
else
goto error;
}
} while (SM_IS_FLAG(flags, SM_B_FL_AGAIN));
/*
** If a smaller array is initially allocated then we may have to
** reallocate a bigger one here. For now we just make sure it's
** large enough.
*/
SM_ASSERT(aq_rcpt_dsn->aqr_dsn_rcpts
< aq_rcpt_dsn->aqr_dsn_rcpts_max);
aq_rcpt_dsn->aqr_dsns[aq_rcpt_dsn->aqr_dsn_rcpts++] =
aq_rcpt->aqr_idx;
/* no error hereafter, otherwise it's necessary to undo this too */
QM_LEV_DPRINTFC(QDC_BOUNCE, 3, (QM_DEBFP, "sev=DBG, func=qm_bounce_add, aq_ta=%p, aq_rcpt=%p, aq_rcpt_dsn=%p, ss_ta=%s, da_ta=%s, bounce_idx=%d, status=done\n", aq_ta, aq_rcpt, aq_rcpt_dsn, aq_rcpt->aqr_ss_ta_id, aq_rcpt->aqr_da_ta_id, bounce_idx));
if (AQR_IS_FLAG(aq_rcpt, AQR_FL_DSN_TMT))
(void) qm_dsn_log(qmgr_ctx, aq_rcpt, errmsg);
sm_log_write(qmgr_ctx->qmgr_lctx,
QM_LCAT_SCHED, QM_LMOD_BOUNCE,
SM_LOG_INFO, 10,
"func=qm_bounce_add, ss_ta=%s, rcpt=%@S, rcpt_idx=%d, rcpt_bounce_idx=%d, da_ta=%s, bounce_idx=%d",
aq_ta->aqt_ss_ta_id, aq_rcpt->aqr_pa,
aq_rcpt->aqr_idx, aq_rcpt_dsn->aqr_idx,
aq_rcpt->aqr_da_ta_id, bounce_idx);
if (AQR_IS_DSNFL(aq_rcpt, AQR_DSNFL_D_NTG) &&
!AQR_IS_DSNFL(aq_rcpt, AQR_DSNFL_D_HBG))
{
AQR_SET_DSNFL(aq_rcpt, AQR_DSNFL_D_HBG);
aq_rcpt->aqr_dly_idx = aq_rcpt_dsn->aqr_idx;
/* there's now one more recipient to deliver; see TODO */
++aq_ta->aqt_rcpts_left;
}
else
{
aq_rcpt->aqr_dsn_idx = aq_rcpt_dsn->aqr_idx;
AQR_SET_DSNFL(aq_rcpt, AQR_DSNFL_F_HBG);
}
if (paq_rcpt_dsn != NULL)
*paq_rcpt_dsn = aq_rcpt_dsn;
return rflags;
error:
QM_LEV_DPRINTFC(QDC_BOUNCE, 0, (QM_DEBFP, "sev=WARN, func=qm_bounce_add, aq_ta=%p, aq_rcpt=%p, flags=%#x, ret=%r\n", aq_ta, aq_rcpt, flags, ret));
if (SM_IS_FLAG(flags, SM_B_FL_SAVED_DSN_MSG) &&
aq_rcpt_dsn != NULL && aq_rcpt_dsn->aqr_dsn_msg != NULL)
{
SM_STR_RESTORE(*aq_rcpt_dsn->aqr_dsn_msg, dsn_msg_save);
SM_CLR_FLAG(flags, SM_B_FL_SAVED_DSN_MSG);
}
QM_LEV_DPRINTFC(QDC_BOUNCE, 0, (QM_DEBFP, "sev=ERROR, func=qm_bounce_add, aq_ta=%p, aq_rcpt=%p, aq_rcpt_dsn=%p, has_bounce=%d, failed=%r\n", aq_ta, aq_rcpt, aq_rcpt_dsn, aq_ta_has_bounce(aq_ta), ret));
sm_log_write(qmgr_ctx->qmgr_lctx,
QM_LCAT_SCHED, QM_LMOD_BOUNCE,
SM_LOG_ERR, 5,
"sev=ERROR, func=qm_bounce_add, ss_ta=%s, rcpt_idx=%u, rcpt_bounce_idx=%u, has_bounce=%d, failed=%m",
aq_ta->aqt_ss_ta_id, aq_rcpt->aqr_idx,
aq_rcpt_dsn == NULL ? SMTP_RCPTIDX_MAX
: aq_rcpt_dsn->aqr_idx,
aq_ta_has_bounce(aq_ta), ret);
if (SM_IS_FLAG(flags, SM_B_FL_NEW_BOUNCE) && aq_rcpt_dsn != NULL)
{
aq_rcpt_rm(aq_ctx, aq_rcpt_dsn, 0);
SM_CLR_FLAG(flags, SM_B_FL_NEW_BOUNCE);
aq_rcpt_dsn = NULL;
}
/* XXX More cleanup? */
return ret;
}
syntax highlighted by Code2HTML, v. 0.9.1