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