/*
 * Copyright (c) 2003-2005 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: updrcpt.c,v 1.96 2006/12/11 01:22:10 ca Exp $")
#include "sm/error.h"
#include "sm/assert.h"
#include "sm/io.h"
#include "sm/rcb.h"
#include "sm/qmgr.h"
#include "sm/qmgr-int.h"
#include "qmgr.h"
#include "qm_throttle.h"
#include "sm/edb.h"
#include "sm/edbc.h"
#include "sm/aqrdq.h"
#include "log.h"

/*
**  QDA_UPD_DSN -- Remove failed recipients (from DEFEDB) by adding them to
**	the EDB request list after a delivery attempt for their bounce message
**	has been made.
**
**	Parameters:
**		qmgr_ctx -- QMGR context
**		aq_ta -- AQ transaction
**		aq_rcpt -- AQ bounce recipient
**		ss_ta_id -- SMTPS transaction id
**		edb_req_hd -- head of request list for (DEF)EDB
**
**	Returns:
**		usual sm_error code; ENOMEM,
**
**	Side Effects:
**		if ok: decrease aqt_rcpts_perm, aqt_rcpts_left
**		aqr_dsn_rcpts = 0, free aqr_dsns
**		on error: some entries may have been appended to edb_req_hd,
**			it's up to the caller to get rid of them
**			(maybe undo this locally when requested??)
**
**	Called by: q_upd_rcpt_ok(), q_upd_rcpt_fail()
**
**	Last code review:
**	Last code change:
*/

static sm_ret_T
qda_upd_dsn(qmgr_ctx_P qmgr_ctx, aq_ta_P aq_ta, aq_rcpt_P aq_rcpt, sessta_id_T ss_ta_id, edb_req_hd_P edb_req_hd)
{
	sm_ret_T ret;
	uint idx;
	rcpt_idx_T rcpt_idx_dsn;
	rcpt_id_T rcpt_id_dsn;

	QM_LEV_DPRINTFC(QDC_UPDRCPT, 4, (QM_DEBFP, "sev=DBG, func=qda_upd_dsn, aq_ta=%p, aq_rcpt=%p, idx=%u, n=%d\n", aq_ta, aq_rcpt, aq_rcpt->aqr_idx, aq_rcpt->aqr_dsn_rcpts));
	ret = SM_SUCCESS;
	SM_ASSERT(aq_rcpt->aqr_dsn_rcpts_max >= aq_rcpt->aqr_dsn_rcpts);
	for (idx = 0; idx < aq_rcpt->aqr_dsn_rcpts; idx++)
	{
		/* remove rcpt from persistent DB */
		rcpt_idx_dsn = aq_rcpt->aqr_dsns[idx];

		/*
		**  Is the recipient in DEFEDB?
		**  q_upd_rcpt_fail() seems to guarantee this.
		**  Note: this is "hard" to check: it would require a DEFEDB
		**  access which is just not worth it.
		*/

		sm_snprintf(rcpt_id_dsn, sizeof(rcpt_id_dsn),
			SMTP_RCPTID_FORMAT, ss_ta_id, rcpt_idx_dsn);
#if QMGR_TEST
		/* trigger an error if requested (add some condition??) */
		if (SM_IS_FLAG(qmgr_ctx->qmgr_cnf.q_cnf_tests, QMGR_TEST_UPD_DSN))
			ret = sm_error_temp(SM_EM_EDB, ENOMEM);
		else /* WARNING: be careful about changing the next statement */
#endif /* QMGR_TEST */
		ret = edb_rcpt_rm_req(qmgr_ctx->qmgr_edb, rcpt_id_dsn, edb_req_hd);
		if (sm_is_err(ret))
		{
			/*
			**  XXX How to handle this error?
			**  Set a QMGR status flag?
			**  Set a flag in the recipient that should have been
			**  removed? The rcpt is (most likely) not in AQ
			**  but only in DEFEDB.
			**
			**  Bail out for now. Caller must handle the rest,
			**  currently: stop (due to resource problem).
			**  Note: this isn't really fatal, a better solution
			**  can be implemented later on.
			*/

			QM_LEV_DPRINTFC(QDC_UPDRCPT, 0, (QM_DEBFP, "sev=ERROR, func=qda_upd_dsn, ss_ta=%s, rcpt_idx_dsn=%u, edb_rcpt_rm_req=%#x\n", ss_ta_id, rcpt_idx_dsn, ret));
			sm_log_write(qmgr_ctx->qmgr_lctx,
				QM_LCAT_DASTAT, QM_LMOD_DASTAT,
				SM_LOG_ERR, 1,
				"sev=ERROR, func=qda_upd_dsn, ss_ta=%s, rcpt_idx_dsn=%d, edb_rcpt_rm_req=%#x",
				ss_ta_id, rcpt_idx_dsn, ret);
			goto error;
		}
		else
		{
			sm_ret_T res;

			AQ_TA_SET_FLAG(aq_ta, AQ_TA_FL_EDB_UPD_R);
			res = edbc_rmentry(qmgr_ctx->qmgr_edbc, rcpt_id_dsn);
			QM_LEV_DPRINTFC(QDC_UPDRCPT, 3, (QM_DEBFP, "sev=DBG, func=qda_upd_dsn, ss_ta=%s, idx=%u, status=remove_bounce, edb_rcpt_rm_req=%#x, edbc_rmentry=%r\n", ss_ta_id, rcpt_idx_dsn, ret, res));
		}
	}

	/*
	**  Decrement number of recipients that permanently failed.
	**  This is ok even if it's "just" a timeout because
	**  that caused an increment of aqt_rcpts_perm too
	**  (see q_upd_rcpt_fail()).
	*/

	idx = aq_rcpt->aqr_dsn_rcpts;
	if (aq_ta->aqt_rcpts_perm >= idx)
	{
		aq_ta->aqt_rcpts_perm -= idx;
		QM_LEV_DPRINTFC(QDC_UPDRCPT, 4, (QM_DEBFP, "sev=DBG, func=qda_upd_dsn, aq_ta=%p, aqt_rcpts_perm=%d\n", aq_ta, aq_ta->aqt_rcpts_perm));
	}
	else
	{
		sm_log_write(qmgr_ctx->qmgr_lctx,
			QM_LCAT_DASTAT, QM_LMOD_DASTAT,
			SM_LOG_INCONS, 2,
			"sev=FATAL, func=qda_upd_dsn, status=counter_error, ta=%s, aqt_rcpts_perm=%u, aqr_dsn_rcpts=%u",
			aq_ta->aqt_ss_ta_id, aq_ta->aqt_rcpts_perm, idx);
	}

	/*
	**  If multiple recipients have been delivered in a single DSN,
	**  then we need to decrease the number of recipients left accordingly.
	**  If the bounce was successful delivered, then
	**  aqt_rcpts_left has been decreased by 1 already.
	**  If the bounce was not successful delivered, then it will
	**  be added to defedb, hence there will be one more rcpt.
	**  Therefore we have to decrease aqt_rcpts_left by the number of
	**  additional recipients (aqr_dsn_rcpts - 1).
	*/

	/* SM_ASSERT(idx == aq_rcpt->aqr_dsn_rcpts); */
	if (idx > 1)
	{
		--idx;
		if (aq_ta->aqt_rcpts_left >= idx)
		{
			QM_LEV_DPRINTFC(QDC_UPDRCPT, 4, (QM_DEBFP, "sev=DBG, func=qda_upd_dsn, ta=%s, idx=%u, aqr_flags=%#x, dsn_rcpts=%u, aqt_rcpts_left=%u, decrease=%u\n",
				aq_ta->aqt_ss_ta_id, aq_rcpt->aqr_idx,
				aq_rcpt->aqr_flags, aq_rcpt->aqr_dsn_rcpts,
				aq_ta->aqt_rcpts_left, idx));
			aq_ta->aqt_rcpts_left -= idx;
			AQ_TA_SET_FLAG(aq_ta, AQ_TA_FL_EDB_UPD_C);
		}
		else
		{
			sm_log_write(qmgr_ctx->qmgr_lctx,
				QM_LCAT_DASTAT, QM_LMOD_DASTAT,
				SM_LOG_INCONS, 2,
				"sev=FATAL, func=qda_upd_dsn, ta=%s, idx=%u, aqr_flags=%#x, dsn_rcpts=%u, aqt_rcpts_left=%u, decrease=%u, when=before_decreasing",
				aq_ta->aqt_ss_ta_id, aq_rcpt->aqr_idx,
				aq_rcpt->aqr_flags, aq_rcpt->aqr_dsn_rcpts,
				aq_ta->aqt_rcpts_left, idx);
			/* SM_ASSERT(aq_ta->aqt_rcpts_left >= idx); ? */
		}
	}

	aq_rcpt->aqr_dsn_rcpts = 0;
	if (aq_rcpt->aqr_dsns != NULL)
	{
		size_t size_dsns;

		SM_ASSERT(aq_rcpt->aqr_dsn_rcpts_max > 0);
		size_dsns = sizeof(*(aq_rcpt->aqr_dsns)) *
				aq_rcpt->aqr_dsn_rcpts_max;
		SM_ASSERT(aq_rcpt->aqr_dsn_rcpts_max <= size_dsns);
		SM_FREE_SIZE(aq_rcpt->aqr_dsns, size_dsns);
		aq_rcpt->aqr_dsn_rcpts_max = 0;
	}
  error:
	return ret;
}

/*
**  Q_RCPT_CHK_TMOUT -- Check whether recipient is too long in queue
**
**	Parameters:
**		qmgr_ctx -- QMGR context
**		ss_ta_id -- SMTPS transaction id
**		aq_ta -- AQ transaction
**		aq_rcpt -- AQ recipient
**		time_now -- current time
**
**	Returns:
**		SM_SUCCESS
**
**	Side Effects: may change status of recipient and ta counters
**		aqt_rcpts_temp, aqt_rcpts_perm
**
**	Called by: q_upd_rcpt_fail()
**
**	Last code review:
**	Last code change:
*/

static sm_ret_T
q_rcpt_chk_tmout(qmgr_ctx_P qmgr_ctx, sessta_id_T ss_ta_id, aq_ta_P aq_ta, aq_rcpt_P aq_rcpt, time_T time_now)
{
#if SM_DELAYED_DSN
	/* XXX defaults to on for now */
	if (!AQR_IS_DSNFL(aq_rcpt, AQR_DSNFL_D_NTG|AQR_DSNFL_D_HBG) &&
	    /* AQR_IS_DSNFL(aq_rcpt, AQR_DSNFL_D_REQ) && */
	    !AQR_IS_FLAG(aq_rcpt, AQR_FL_IS_DBNC) &&
	    aq_rcpt->aqr_st_time + qmgr_ctx->qmgr_cnf.q_cnf_tmo_delay
	    < time_now)
	{
		AQR_SET_DSNFL(aq_rcpt, AQR_DSNFL_D_NTG);
	}
#endif

	/*
	**  Too long in queue and can't be retried? Then count it
	**  as permanent failure (and mark it accordingly)
	*/

	if (aq_rcpt->aqr_st_time + qmgr_ctx->qmgr_cnf.q_cnf_tmo_return
	    < time_now &&
	    (!AQR_MORE_DESTS(aq_rcpt) || AQR_DEFER(aq_rcpt)))
	{
		/* SM_ASSERT(!AQR_IS_FLAG(aq_rcpt, AQR_FL_IS_DBNC)); */
		/* Should this really set AQR_FL_PERM?? */
		AQR_SET_FLAG(aq_rcpt, AQR_FL_PERM|AQR_FL_DSN_TMT);
		/* SM_ASSERT(aq_ta->aqt_rcpts_temp > 0); */
		if (aq_ta->aqt_rcpts_temp > 0)
			--aq_ta->aqt_rcpts_temp;
		else
		{
			sm_log_write(qmgr_ctx->qmgr_lctx,
				QM_LCAT_DASTAT, QM_LMOD_DASTAT,
				SM_LOG_INCONS, 2,
				"sev=FATAL, func=q_rcpt_chk_tmout, status=timeout, ss_ta=%s, idx=%u, aqt_rcpts_temp=0, when=before_decreasing"
				, ss_ta_id, aq_rcpt->aqr_idx);
			QM_LEV_DPRINTFC(QDC_UPDRCPT, 1, (QM_DEBFP, "sev=DBG, func=q_rcpt_chk_tmout, aq_ta=%p, perm=%u, left=%u\n", aq_ta, aq_ta->aqt_rcpts_perm , aq_ta->aqt_rcpts_left));

			/* abort */
			SM_ASSERT(aq_ta->aqt_rcpts_temp > 0);
		}
		SM_ASSERT(aq_ta->aqt_rcpts_perm < UINT_MAX);
		++aq_ta->aqt_rcpts_perm;
		QM_LEV_DPRINTFC(QDC_UPDRCPT, 1, (QM_DEBFP, "sev=DBG, func=q_rcpt_chk_tmout, status=TO, aq_rcpt=%p, rcpt=%@S, now-aqr_st_time=%ld, aqr_flags=%#x\n", aq_rcpt, aq_rcpt->aqr_pa, (long) (time_now - aq_rcpt->aqr_st_time), aq_rcpt->aqr_flags));
	}
	return SM_SUCCESS;
}

/*
**  Q_STORE_DDSN -- Store a delayed DSN recipient in DEFEDB etc
**
**	Parameters:
**		qmgr_ctx -- QMGR context
**		ss_ta_id -- SMTPS transaction id
**		aq_ta -- AQ transaction
**		aq_rcpt -- AQ recipient (which is delayed)
**		aq_rcpt_dsn -- AQ recipient which contains the DSN
**		edb_req_hd -- head of request list for (DEF)EDB
**		pdelay_next_try -- (pointer to) delay until next try (output)
**
**	Returns:
**		success: flags as shown in sm/qmgr-int.h
**			to activate scheduler or SMAR.
**			Alternatively flags might be an output parameter.
**		error: usual sm_error code
**
**	Side Effects:
**
**	Called by:
**
**	Locking: aq_ctx and edbc must be locked.
**
**	Last code review:
**	Last code change:
*/

static sm_ret_T
q_store_ddsn(qmgr_ctx_P qmgr_ctx, sessta_id_T ss_ta_id, aq_ta_P aq_ta, aq_rcpt_P aq_rcpt, aq_rcpt_P aq_rcpt_dsn, edb_req_hd_P edb_req_hd, time_T time_now, int *pdelay_next_try)
{
	sm_ret_T ret;
	int delay;

	/* treat this as temporary error */
	aq_rcpt_dsn->aqr_status = SMTP_TMO_DDSN;

	/* XXX "timeout" */
	delay = 2;
	aq_rcpt_dsn->aqr_next_try = time_now + delay;
	if (delay < *pdelay_next_try || *pdelay_next_try == 0)
		*pdelay_next_try = delay;
#if QMGR_TEST
	/* trigger an error if requested (add some condition??) */
	if (SM_IS_FLAG(qmgr_ctx->qmgr_cnf.q_cnf_tests, QMGR_TEST_DDSN))
	{
		/* only once */
		SM_CLR_FLAG(qmgr_ctx->qmgr_cnf.q_cnf_tests, QMGR_TEST_DDSN);
		ret = sm_error_temp(SM_EM_EDB, ENOMEM);
	}
	else /* WARNING: be careful about changing the next statement */
#endif /* QMGR_TEST */
	ret = edb_rcpt_app(qmgr_ctx->qmgr_edb, aq_rcpt_dsn, edb_req_hd, aq_rcpt_dsn->aqr_status);
QM_LEV_DPRINTFC(QDC_UPDRCPT, 3, (QM_DEBFP, "sev=DBG, func=q_store_ddsn, aq_rcpt_dsn=%p, ss_ta=%s, idx=%u, edb_rcpt_app=%r\n", aq_rcpt, ss_ta_id, aq_rcpt_dsn->aqr_idx, ret));
	if (sm_is_err(ret))
	{
		sm_log_write(qmgr_ctx->qmgr_lctx,
			QM_LCAT_DASTAT, QM_LMOD_DASTAT,
			SM_LOG_ERR, 1,
			"sev=ERROR, func=q_store_ddsn, aq_rcpt_dsn=%p, ss_ta=%s, edb_rcpt_app=%m",
			aq_rcpt_dsn, ss_ta_id, ret);

		/* let's hope it works the next time */
		AQR_CLR_DSNFL(aq_rcpt, AQR_DSNFL_D_HBG);
		SM_ASSERT(aq_ta->aqt_rcpts_left > 0);
		SM_ASSERT(aq_ta->aqt_rcpts_tot > 0);
		--aq_ta->aqt_rcpts_left;
		--aq_ta->aqt_rcpts_tot;
	}
	else
	{
		rcpt_id_T rcpt_dsn_id;

		++aq_ta->aqt_rcpts_temp;
		sm_snprintf(rcpt_dsn_id, sizeof(rcpt_dsn_id),
			SMTP_RCPTID_FORMAT, ss_ta_id, aq_rcpt_dsn->aqr_idx);
		AQ_TA_SET_FLAG(aq_ta, AQ_TA_FL_EDB_UPD_R|AQ_TA_FL_EDB_UPD_C);
		ret = edbc_add(qmgr_ctx->qmgr_edbc, rcpt_dsn_id, aq_rcpt_dsn->aqr_next_try, false);
		if (sm_is_err(ret))
		{
			QMGR_SET_SFLAG(qmgr_ctx, QMGR_SFL_EDBC);
			if (sm_error_value(ret) == ENOMEM)
				QMGR_SET_RFLAG(qmgr_ctx, QMGR_RFL_MEM);
			else if (ret == sm_error_temp(SM_EM_Q_EDBC, SM_E_FULL))
				QMGR_SET_RFLAG(qmgr_ctx, QMGR_RFL_EBDC);
			sm_log_write(qmgr_ctx->qmgr_lctx,
				QM_LCAT_DASTAT, QM_LMOD_DASTAT,
				SM_LOG_ERR, 3,
				"sev=ERROR, func=q_store_ddsn, rcpt_id=%s, next_try=%6ld, edbc_add=%m, entries=%u",
				rcpt_dsn_id, (long) aq_rcpt_dsn->aqr_next_try,
				ret, qmgr_ctx->qmgr_edbc->edbc_entries);
		}
	}

	ret = aq_rcpt_rm(qmgr_ctx->qmgr_aq, aq_rcpt_dsn, AQR_RM_I_RDQ|AQR_RM_I_WAITQ);
	if (sm_is_err(ret))
	{
		sm_log_write(qmgr_ctx->qmgr_lctx,
			QM_LCAT_DASTAT, QM_LMOD_DASTAT,
			SM_LOG_ERR, 1,
			"sev=ERROR, func=q_store_ddsn, aq_rcpt_dsn=%p, ss_ta=%s, aq_rcpt_rm=%m",
			aq_rcpt_dsn, ss_ta_id, ret);
	}
	return SM_SUCCESS;
}

/*
**  Q_UPD_RCPT_FAIL -- Update status for one failed recipient
**
**	This function may append entries to the update queues for
**	DEFEDB and IBDB which need to be taken care of by the caller.
**
**	Parameters:
**		qmgr_ctx -- QMGR context
**		ss_ta_id -- SMTPS transaction id
**		status -- status of (entire) transaction
**		aq_ta -- AQ transaction
**		aq_rcpt -- AQ recipient
**		edb_req_hd -- head of request list for (DEF)EDB
**		ibdb_req_hd -- head of request list for IBDB
**		pdelay_next_try -- (pointer to) delay until next try (output)
**		errmsg -- error message (might be NULL)
**			(must be "sanitized" by caller)
**
**	Returns:
**		success: flags as shown in sm/qmgr-int.h
**			to activate scheduler or SMAR.
**			Alternatively flags might be an output parameter.
**		error: usual sm_error code
**
**	Side Effects:
**		on error: might have changed edb request list (qda_upd_dsn),
**			ibdb request list.
**		may change aqt_rcpts_temp, aqt_rcpts_perm.
**		can change several aq_rcpt fields.
**
**	Called by: q_upd_rcpt_stat()
**
**	Locking: aq_ctx and edbc must be locked.
**
**	Last code review:
**	Last code change:
*/

static sm_ret_T
q_upd_rcpt_fail(qmgr_ctx_P qmgr_ctx, sessta_id_T ss_ta_id,
	sm_ret_T rcpt_status, aq_ta_P aq_ta, aq_rcpt_P aq_rcpt,
	ibdb_rcpt_P ibdb_rcpt, rcpt_id_T rcpt_id,
	edb_req_hd_P edb_req_hd, ibdb_req_hd_P ibdb_req_hd, sm_str_P errmsg,
	int *pdelay_next_try)
{
	sm_ret_T ret, flags, rv;
	time_T time_now;

	SM_IS_QMGR_CTX(qmgr_ctx);
	SM_IS_AQ_TA(aq_ta);
	SM_IS_AQ_RCPT(aq_rcpt);
	SM_REQUIRE(pdelay_next_try != NULL);

	rv = ret = SM_SUCCESS;
	flags = 0;
	time_now = evthr_time(qmgr_ctx->qmgr_ev_ctx);

	/* SMAR failures will cause this to be NULL */
	if (aq_rcpt->aqr_addrs != NULL &&
	    aq_rcpt->aqr_addr_cur < aq_rcpt->aqr_addr_max)
	{
		QM_LEV_DPRINTFC(QDC_UPDRCPT, 6, (QM_DEBFP, "sev=DBG, func=q_upd_rcpt_fail, aq_rcpt=%p, addrs!=NULL, cur=%u, max=%u\n", aq_rcpt, aq_rcpt->aqr_addr_cur, aq_rcpt->aqr_addr_max));
		aq_rcpt->aqr_addr_fail = aq_rcpt->aqr_addrs[aq_rcpt->aqr_addr_cur].aqra_ipv4;
	}

	/* Unconditionally set status */
	aq_rcpt->aqr_status = rcpt_status;

	/* Do something more with this?? */
	switch (smtp_reply_type(rcpt_status))
	{
	  default:
		/* XXX What to do in this case?? abort? */
		sm_log_write(qmgr_ctx->qmgr_lctx,
			QM_LCAT_DASTAT, QM_LMOD_DASTAT,
			SM_LOG_INCONS, 2,
			"sev=FATAL, func=q_upd_rcpt_fail, ss_ta=%s, idx=%u, stat=%d, problem=status_is_unknown"
			, ss_ta_id, aq_rcpt->aqr_idx, rcpt_status);

		/* XXX set status to a temporary error for now; abort?? */
		aq_rcpt->aqr_status = rcpt_status = SMTPC_TEMP_ST;

		/* FALLTHROUGH */
	  case SMTP_RTYPE_TEMP:
		AQR_SET_FLAG(aq_rcpt, AQR_FL_TEMP);
		(void) q_rcpt_chk_tmout(qmgr_ctx, ss_ta_id, aq_ta, aq_rcpt, time_now);
		break;

	  case SMTP_RTYPE_PERM:
		/* Always set AQR_FL_DSN_PERM?? */
		AQR_SET_FLAG(aq_rcpt, AQR_FL_PERM|AQR_FL_DSN_PERM);
		break;
	}

	/* note: AQR_FL_DSN_TMT currently implies AQR_FL_PERM */
	if (!AQR_IS_FLAG(aq_rcpt, AQR_FL_IS_DBNC) &&
	    (AQR_IS_FLAG(aq_rcpt, AQR_FL_PERM|AQR_FL_DSN_TMT) ||
	     (AQR_IS_DSNFL(aq_rcpt, AQR_DSNFL_D_NTG) &&
	      !AQR_IS_DSNFL(aq_rcpt, AQR_DSNFL_D_HBG))))
	{
		bool isddsn;
		aq_rcpt_P aq_rcpt_dsn;

		isddsn = AQR_IS_DSNFL(aq_rcpt, AQR_DSNFL_D_NTG) &&
			 !AQR_IS_DSNFL(aq_rcpt, AQR_DSNFL_D_HBG);

		/* Need to generate DSN */
		ret = qm_bounce_add(qmgr_ctx, aq_ta, aq_rcpt, errmsg, &aq_rcpt_dsn);
		if (isddsn && sm_is_success(ret))
			(void) q_store_ddsn(qmgr_ctx, ss_ta_id, aq_ta, aq_rcpt, aq_rcpt_dsn, edb_req_hd, time_now, pdelay_next_try);
		if (sm_is_success(ret))
			flags |= ret;
		else if (sm_error_value(ret) == ENOMEM)
		{
			/* throttle servers; do this in caller?? fixme */
			(void) qm_control(qmgr_ctx, 1, 100, QMGR_RFL_MEM_I, THR_NO_LOCK);
		}
		/* note: error is handled below by putting rcpt into EDB */
	}

	/*
	**  This needs to be done only if there are no more chances
	**  to deliver the mail in this attempt (i.e., all destination hosts
	**  have been tried) or some other error occurred, e.g.,
	**  timeout in scheduler or failure in SMAR,
	**  or if a DSN has been generated.
	**  Note: the latter case is not really necessary but a result of
	**  the current bounce handling implementation, e.g.,
	**  qda_upd_dsn() requires that the recipients are in DEFEDB.
	**
	**  XXX Problem: a bounce isn't written to an external DB when it
	**  is generated, hence the data in DEFEDB is inconsistent at
	**  some point... does the recovery program deal with that?
	*/

	if (!AQR_MORE_DESTS(aq_rcpt) || AQR_DEFER(aq_rcpt) ||
	    (AQR_IS_FLAG(aq_rcpt, AQR_FL_PERM|AQR_FL_DSN_TMT) &&
	     !AQR_IS_FLAG(aq_rcpt, AQR_FL_IS_DBNC)))
	{
		aq_rcpt->aqr_tries++;

		/*
		**  Need to try this again for
		**  - temporary failure
		**  - permanent failure but no bounce (because generation
		**	of bounce failed, see above).
		*/

		if (smtp_is_reply_temp(aq_rcpt->aqr_status) ||
		    (smtp_is_reply_fail(aq_rcpt->aqr_status) &&
		     !aq_rcpt_has_bounce(aq_rcpt)))
		{
			uint d;

			d = qm_delay_next_try(qmgr_ctx, aq_rcpt);
			aq_rcpt->aqr_next_try = time_now + d;
			sm_log_write(qmgr_ctx->qmgr_lctx,
				QM_LCAT_DASTAT, QM_LMOD_DASTAT,
				SM_LOG_INFO, 14,
				"sev=INFO, func=q_upd_rcpt_fail, ss_ta=%s, idx=%u, qm_delay_next_try=%d, *pdelay_next_try=%d"
				, ss_ta_id, aq_rcpt->aqr_idx
				, d, *pdelay_next_try);
			if (d < *pdelay_next_try || *pdelay_next_try == 0)
				*pdelay_next_try = d;
		}

		ret = edb_rcpt_app(qmgr_ctx->qmgr_edb, aq_rcpt, edb_req_hd, rcpt_status);
		if (sm_is_err(ret))
		{
			/*
			**  fixme: How to handle this error?
			**  Set a QMGR status flag?
			**  Remember that the entry must not be removed
			**  from IBDB (if it was there) by setting a flag
			**  in aq_rcpt?
			**  For now: bail out and let caller deal with it.
			*/

			sm_log_write(qmgr_ctx->qmgr_lctx,
				QM_LCAT_DASTAT, QM_LMOD_DASTAT,
				SM_LOG_ERR, 1,
				"sev=ERROR, func=q_upd_rcpt_fail, aq_rcpt=%p, ss_ta=%s, idx=%u, edb_rcpt_app=%m",
				aq_rcpt, ss_ta_id, aq_rcpt->aqr_idx, ret);
			rv = ret;	/* XXX overwrite? */
			goto error;
		}
		else
		{
			QM_LEV_DPRINTFC(QDC_UPDRCPT, 3, (QM_DEBFP, "sev=DBG, func=q_upd_rcpt_fail, aq_rcpt=%p, ss_ta=%s, idx=%u, edb_rcpt_app=%r\n", aq_rcpt, ss_ta_id, aq_rcpt->aqr_idx, ret));
			AQ_TA_SET_FLAG(aq_ta, AQ_TA_FL_EDB_UPD_R);
		}

		if (sm_is_success(ret) &&
		    AQR_IS_FLAG(aq_rcpt, AQR_FL_IS_BNC|AQR_FL_IS_DBNC))
		{
			ret = qda_upd_dsn(qmgr_ctx, aq_ta, aq_rcpt, ss_ta_id, edb_req_hd);
			if (sm_is_err(ret))
			{
				rv = ret;
				goto error;
			}
		}

		/* Append to EDB cache if necessary. */
		if (!AQR_IS_FLAG(aq_rcpt, AQR_FL_PERM|AQR_FL_DSN_TMT))
		{
			ret = edbc_add(qmgr_ctx->qmgr_edbc, rcpt_id, aq_rcpt->aqr_next_try, false);
			if (sm_is_err(ret))
			{
				QMGR_SET_SFLAG(qmgr_ctx, QMGR_SFL_EDBC);
				if (sm_error_value(ret) == ENOMEM)
					QMGR_SET_RFLAG(qmgr_ctx, QMGR_RFL_MEM);
				else if (ret == sm_error_temp(SM_EM_Q_EDBC, SM_E_FULL))
					QMGR_SET_RFLAG(qmgr_ctx, QMGR_RFL_EBDC);
				sm_log_write(qmgr_ctx->qmgr_lctx,
					QM_LCAT_DASTAT, QM_LMOD_DASTAT,
					SM_LOG_ERR, 3,
					"sev=ERROR, func=q_upd_rcpt_fail, rcpt_id=%s, next_try=%6ld, edbc_add=%m, entries=%u",
					rcpt_id, (long)aq_rcpt->aqr_next_try,
					ret, qmgr_ctx->qmgr_edbc->edbc_entries);
			}
			else
				QM_LEV_DPRINTFC(QDC_UPDRCPT, 3, (QM_DEBFP, "sev=DBG, func=q_upd_rcpt_fail, rcpt_id=%s, next_try=%6ld, edbc_add=%r, entries=%u\n", rcpt_id, (long) aq_rcpt->aqr_next_try, ret, qmgr_ctx->qmgr_edbc->edbc_entries));
		}

		if (AQR_IS_FLAG(aq_rcpt, AQR_FL_IQDB))
		{
			ret = ibdb_rcpt_app(qmgr_ctx->qmgr_ibdb, ibdb_rcpt, ibdb_req_hd,
					smtp_is_reply_temp(aq_rcpt->aqr_status)
						? IBDB_RCPT_TEMP : IBDB_RCPT_PERM);
			if (sm_is_err(ret))
			{
				/*
				**  XXX How to handle this error?
				**  Set a QMGR status flag?
				**  The system is probably out of memory.
				**  Caller should deal with it?
				*/

				sm_log_write(qmgr_ctx->qmgr_lctx,
					QM_LCAT_DASTAT, QM_LMOD_DASTAT,
					SM_LOG_ERR, 1,
					"sev=ERROR, func=q_upd_rcpt_fail, ss_ta=%s, ibdb_rcpt_app=%m",
					ss_ta_id, ret);
				rv = ret;
				goto error;
			}
			else
				QM_LEV_DPRINTFC(QDC_UPDRCPT, 2, (QM_DEBFP, "sev=DBG, func=q_upd_rcpt_fail, ss_ta=%s, ibdb_rcpt_app=%r\n", ss_ta_id, ret));
		}
	}
#if 0
	else
	{
		/*
		**  Let's try again... changes done below
		**  otherwise the test whether a retry is
		**  possible fails (or at least must be changed).
		*/
	}
#endif /* 0 */
  error:
	return sm_is_err(rv) ? rv : flags;
}

/*
**  Q_UPD_RCPT_OK -- Update status for one delivered recipient
**	(or a double bounce)
**
**	Parameters:
**		qmgr_ctx -- QMGR context
**		ss_ta_id -- SMTPS transaction id
**		status -- status of (entire) transaction
**		aq_ta -- AQ transaction
**		aq_rcpt -- AQ recipient
**		ibdb_rcpt -- IBDB recipient
**		rcpt_id -- recipient id
**		edb_req_hd -- head of request list for (DEF)EDB
**		ibdb_req_hd -- head of request list for IBDB
**
**	Returns:
**		usual sm_error code; ENOMEM,
**		any error is "directly" returned to the caller, no cleanup
**		is performed.
**
**	Side Effects:
**		on error: might have changed edb request list (qda_upd_dsn)
**			may write to ibdb (directly, not via request)
**		may decrease aqt_rcpts_left.
**
**	Called by: q_upd_rcpt_stat()
**
**	Locking: aq_ctx and edbc must be locked.
**
**	Note: DSNs are not implemented! (in case of a SUCCESS DSN
**		we would need to store that information
**		somewhere, e.g., DEFEDB).
**
**	Last code review: 2005-03-01 23:58:05
**	Last code change: 2006-04-04 19:27:41
*/

static sm_ret_T
q_upd_rcpt_ok(qmgr_ctx_P qmgr_ctx, sessta_id_T ss_ta_id,
	sm_ret_T rcpt_status, aq_ta_P aq_ta,
	aq_rcpt_P aq_rcpt, ibdb_rcpt_P ibdb_rcpt, rcpt_id_T rcpt_id,
	edb_req_hd_P edb_req_hd, ibdb_req_hd_P ibdb_req_hd)
{
	sm_ret_T ret;

	SM_IS_QMGR_CTX(qmgr_ctx);
	SM_IS_AQ_TA(aq_ta);
	SM_IS_AQ_RCPT(aq_rcpt);
	ret = SM_SUCCESS;

	/* is this a double bounce that could not be delivered? */
	if (AQR_IS_FLAG(aq_rcpt, AQR_FL_IS_DBNC) && rcpt_status != SM_SUCCESS)
	{
		/* drop it on the floor... one less rcpt left */
		if (aq_ta->aqt_rcpts_left > 0)
			--aq_ta->aqt_rcpts_left;
		else
		{
			sm_log_write(qmgr_ctx->qmgr_lctx,
				QM_LCAT_DASTAT, QM_LMOD_DASTAT,
				SM_LOG_INCONS, 2,
				"sev=FATAL, func=q_upd_rcpt_ok, aq_ta=%p, aq_rcpt=%p, aqr_flags=%#x, bounce=doublebounce, aqt_rcpts_left=0, when=before_decreasing",
				aq_ta, aq_rcpt, aq_rcpt->aqr_flags);
			/* SM_ASSERT(aq_ta->aqt_rcpts_left > 0); ? */
		}

		/* can't do anything about this... just log it */
		sm_log_write(qmgr_ctx->qmgr_lctx,
			QM_LCAT_DASTAT, QM_LMOD_DASTAT,
			SM_LOG_DEBUG, 10,
			"sev=DBG, func=q_upd_rcpt_ok, aq_ta=%p, aq_rcpt=%p, aqr_flags=%#x, bounce=doublebounce, aqt_rcpts_left=%d, status=drop",
			aq_ta, aq_rcpt, aq_rcpt->aqr_flags,
			aq_ta->aqt_rcpts_left);

		sm_log_write(qmgr_ctx->qmgr_lctx,
			QM_LCAT_DASTAT, QM_LMOD_DASTAT,
			SM_LOG_WARN, 4,
			"sev=WARN, func=q_upd_rcpt_ok, ss_ta=%p, idx=%u, bounce=doublebounce, status=drop"
			, ss_ta_id, aq_rcpt->aqr_idx);
	}
	else if (SM_SUCCESS == rcpt_status)
	{
		time_T time_now;

		time_now = evthr_time(qmgr_ctx->qmgr_ev_ctx);
		sm_log_write(qmgr_ctx->qmgr_lctx,
			QM_LCAT_DASTAT, QM_LMOD_DASTAT,
			SM_LOG_INFO, 10,
			"sev=INFO, func=q_upd_rcpt_ok, rcpt_id=%s, rcpt=%@S, xdelay=%lu, delay=%lu"
			, rcpt_id, aq_rcpt->aqr_pa
			, time_now - aq_rcpt->aqr_last_try
			, time_now - aq_rcpt->aqr_st_time);
	}

	QM_LEV_DPRINTFC(QDC_UPDRCPT, 1, (QM_DEBFP, "sev=DBG, func=q_upd_rcpt_ok, found rcpt=%p, ss_ta=%s, stat=%d, flags=%#x\n", aq_rcpt, ss_ta_id, aq_rcpt->aqr_status, aq_rcpt->aqr_flags));

	if (AQR_IS_FLAG(aq_rcpt, AQR_FL_IQDB))
	{
		/* "Remove" rcpt from IBDB */
#if SM_IBDB_RCPT_REMOVE
		ret = ibdb_rcpt_status(qmgr_ctx->qmgr_ibdb, ibdb_rcpt,
			IBDB_RCPT_DONE, IBDB_FL_NOROLL, THR_LOCK_UNLOCK);
#else
		ret = ibdb_rcpt_app(qmgr_ctx->qmgr_ibdb, ibdb_rcpt,
				ibdb_req_hd, IBDB_RCPT_DONE);
#endif
		if (sm_is_err(ret))
		{
			sm_log_write(qmgr_ctx->qmgr_lctx,
				QM_LCAT_DASTAT, QM_LMOD_DASTAT,
				SM_LOG_ERR, 3,
				"sev=ERROR, func=q_upd_rcpt_ok, rcpt_id=%s, rcpt=%@S, ibdb_rcpt_status=%m",
				rcpt_id, aq_rcpt->aqr_pa, ret);
			goto error;
		}
	}
	else if (AQR_IS_FLAG(aq_rcpt, AQR_FL_DEFEDB))
	{
		ret = edb_rcpt_rm_req(qmgr_ctx->qmgr_edb, rcpt_id, edb_req_hd);
		if (sm_is_err(ret))
		{
			sm_log_write(qmgr_ctx->qmgr_lctx,
				QM_LCAT_DASTAT, QM_LMOD_DASTAT,
				SM_LOG_ERR, 1,
				"sev=ERROR, func=q_upd_rcpt_ok, rcpt_id=%s, rcpt=%@S, edb_rcpt_rm_req=%m",
				rcpt_id, aq_rcpt->aqr_pa, ret);
			goto error;
		}
		else
		{
			QM_LEV_DPRINTFC(QDC_UPDRCPT, 5, (QM_DEBFP, "sev=DBG, func=q_upd_rcpt_ok, edb_rcpt_rm_req(%S, %s)=%r\n", aq_rcpt->aqr_pa, rcpt_id, ret));
			AQ_TA_SET_FLAG(aq_ta, AQ_TA_FL_EDB_UPD_R);
		}
	}
#if QMGR_TEST
	else if (SM_IS_FLAG(qmgr_ctx->qmgr_cnf.q_cnf_tests, QMGR_TEST_INT_SRC))
		; /* internally generated transaction */
#endif
	else if (!AQR_IS_FLAG(aq_rcpt, AQR_FL_IS_DSN))
	{
		/* HACK ... special treatment for bounces */
		sm_log_write(qmgr_ctx->qmgr_lctx,
			QM_LCAT_DASTAT, QM_LMOD_DASTAT,
			SM_LOG_INCONS, 2,
			"sev=ERROR, func=q_upd_rcpt_ok, rcpt_id=%s, rcpt_flag=%#x, status=from_unknown_queue",
			rcpt_id, aq_rcpt->aqr_flags);
	}
	if (AQR_IS_FLAG(aq_rcpt, AQR_FL_IS_BNC|AQR_FL_IS_DBNC))
		ret = qda_upd_dsn(qmgr_ctx, aq_ta, aq_rcpt, ss_ta_id, edb_req_hd);
  error:
	/* currently no cleanup */
	return ret;
}

/*
**  Q_UPD_RCPT_STAT -- Update status for one recipient
**
**	This function may append entries to the update queues for
**	DEFEDB and IBDB which need to be taken care of by the caller.
**
**	Parameters:
**		qmgr_ctx -- QMGR context
**		ss_ta_id -- SMTPS transaction id
**		ta_status -- status of (entire) transaction
**		err_st -- state which cause error
**		aq_ta -- AQ transaction
**		aq_rcpt -- AQ recipient
**		edb_req_hd -- head of request list for (DEF)EDB
**		ibdb_req_hd -- head of request list for IBDB
**		errmsg -- error message (might be NULL)
**			(must be "sanitized" by caller)
**		piqdb_rcpts_done -- (pointer to) # of IQDB rcpts done (in/out)
**		pdelay_next_try -- (pointer to) delay until next try (output)
**
**	Returns:
**		success: flags as shown in sm/qmgr-int.h
**			to activate scheduler or SMAR.
**			Alternatively this might be an output parameter.
**		error: usual sm_error code
**
**	Side Effects:
**		on error: may modify aq_ta counters (aq_upd_ta_rcpt_cnts)
**		and increase iqdb_rcpts_done,
**		plus side effects of q_upd_rcpt_ok(), q_upd_rcpt_fail(),
**		i.e., may write to ibdb, change ibdb/edb request lists.
**
**	Called by: qda_upd_ta_rcpt_stat()
**
**	Locking: aq_ctx and edbc must be locked.
**
**	Last code review:
**	Last code change:
*/

sm_ret_T
q_upd_rcpt_stat(qmgr_ctx_P qmgr_ctx, sessta_id_T ss_ta_id, sm_ret_T ta_status,
	uint err_st, aq_ta_P aq_ta, aq_rcpt_P aq_rcpt,
	edb_req_hd_P edb_req_hd, ibdb_req_hd_P ibdb_req_hd, sm_str_P errmsg,
	uint *piqdb_rcpts_done, int *pdelay_next_try)
{
	sm_ret_T ret, rcpt_status, flags, rv;
	aq_ctx_P aq_ctx;
	ibdb_rcpt_T ibdb_rcpt;
	rcpt_id_T rcpt_id;

	SM_REQUIRE(piqdb_rcpts_done != NULL);
	SM_IS_QMGR_CTX(qmgr_ctx);
	SM_IS_AQ_TA(aq_ta);
	SM_IS_AQ_RCPT(aq_rcpt);
	aq_ctx = qmgr_ctx->qmgr_aq;
	SM_IS_AQ(aq_ctx);

	QM_LEV_DPRINTFC(QDC_UPDRCPT, 1, (QM_DEBFP, "sev=DBG, func=q_upd_rcpt_stat, stat=%d, err_st=%r, aqt_rcpts_inaq=%u, aqr_flags=%#x\n", ta_status, err_st, aq_ta->aqt_rcpts_inaq, aq_rcpt->aqr_flags));
	sm_log_write(qmgr_ctx->qmgr_lctx,
		QM_LCAT_DA, QM_LMOD_DASTAT,
		SM_LOG_DEBUG, 14,
		"sev=DBG, func=q_upd_rcpt_stat, ss_ta=%s, rcpt=%@S, idx=%u, stat=%d, err_state=%#x",
		aq_rcpt->aqr_ss_ta_id, aq_rcpt->aqr_pa,
		aq_rcpt->aqr_idx, ta_status, err_st);

	rv = ret = SM_SUCCESS;
	flags = 0;

	QM_LEV_DPRINTFC(QDC_UPDRCPT, 5, (QM_DEBFP, "sev=DBG, func=q_upd_rcpt_stat, rcpt_da_id=%s, idx=%u, aq_rcpt=%p\n", aq_rcpt->aqr_da_ta_id, aq_rcpt->aqr_idx, aq_rcpt));

	/*
	**  If there is a new recipient status use that,
	**  otherwise the transaction status.
	*/

	if (AQR_IS_FLAG(aq_rcpt, AQR_FL_STAT_NEW))
	{
		rcpt_status = aq_rcpt->aqr_status_new;
		sm_log_write(qmgr_ctx->qmgr_lctx,
			QM_LCAT_DASTAT, QM_LMOD_DASTAT,
			SM_LOG_INFO, 8,
			"sev=DBG, func=q_upd_rcpt_stat, rcpt=%@S, idx=%u, stat=%d",
			aq_rcpt->aqr_pa, aq_rcpt->aqr_idx, rcpt_status);
	}
	else
		rcpt_status = ta_status;

	/* Set error state if necessary */
	if (!AQR_IS_FLAG(aq_rcpt, AQR_FL_ERRST_UPD))
		aq_rcpt->aqr_err_st = err_st;

	if (aq_ctx->aq_t_da > 0)
		--aq_ctx->aq_t_da;
	else
	{
		sm_log_write(qmgr_ctx->qmgr_lctx,
			QM_LCAT_DASTAT, QM_LMOD_DASTAT,
			SM_LOG_INCONS, 2,
			"sev=FATAL, func=q_upd_rcpt_stat, ss_ta=%s, idx=%s, aq_t_da=0, when=before_decreasing",
			ss_ta_id, aq_rcpt->aqr_idx);
	}

	/* Compose rcpt_id for later use */
	sm_snprintf(rcpt_id, sizeof(rcpt_id), SMTP_RCPTID_FORMAT,
			ss_ta_id, aq_rcpt->aqr_idx);
	if (AQR_IS_FLAG(aq_rcpt, AQR_FL_IQDB))
	{
		/* Store data in ibdb_rcpt for update */
		ibdb_rcpt.ibr_ta_id = ss_ta_id;
		ibdb_rcpt.ibr_pa = aq_rcpt->aqr_pa;
		ibdb_rcpt.ibr_idx = aq_rcpt->aqr_idx;

		/*
		**  XXX Remove recipient from IQDB in all cases???
		**  Note: the recipient is safely in IBDB, so we can
		**  do this now (instead of after transferring the
		**  recipient to other queues if required).
		**  Are there any cases in which we would like to have
		**  fast access to the recipient despite the fact
		**  that we already tried to deliver it?
		**
		**  XXX Why don't we remove the recipient from IQDB
		**  as soon as it is in AQ? (2003-02-07)
		**  Where is IQDB related data used after it has been
		**  copied into AQ? If it is used, can we refer to
		**  the copy in AQ instead?
		**
		**  XXX Should we do this only after an "entire"
		**  delivery attempt has been made, i.e., if there
		**  there are still more destinations to try then
		**  don't remove the entry now?
		**
		**  Remove it from iqdb in all cases no matter which
		**  result has been returned?
		**  It must be removed at least if
		**  - it has been successfully delivered
		**  - delivery failed permanently (-> trigger DSN)
		**  If delivery failed temporarily we could keep it in
		**  AQ depending on the scheduling strategy...
		**  Depending on the DSNs requested we may have to
		**  keep the transaction around. Hence we need different
		**  counters (do we?) or at least some flag that shows this.
		*/

		/*
		**  Other cases? For example: permanent failure?
		*/

#define AQR_NO_RETRIES(aq_rcpt, rcpt_status)			\
	(SMTP_DONE(rcpt_status) ||				\
	 smtp_reply_type(rcpt_status) == SMTP_RTYPE_PERM ||	\
	 !AQR_MORE_DESTS(aq_rcpt) || AQR_DEFER(aq_rcpt))

		if (AQR_NO_RETRIES(aq_rcpt, rcpt_status))
		{
			ret = iqdb_rcpt_rm(qmgr_ctx->qmgr_iqdb, rcpt_id,
					SMTP_RCPTID_SIZE, THR_LOCK_UNLOCK);
			if (sm_is_err(ret))
			{
				sm_log_write(qmgr_ctx->qmgr_lctx,
					QM_LCAT_DASTAT, QM_LMOD_DASTAT,
					SM_LOG_ERR,
					(sm_error_value(ret) == SM_E_NOTFOUND) ? 8 : 1,
					"sev=ERROR, func=q_upd_rcpt_stat, rcpt_id=%s, iqdb_rcpt_rm=%m",
					rcpt_id, ret);
				if (sm_error_value(ret) != SM_E_NOTFOUND)
				{
					rv = ret;
					goto error;
				}
			}
			else
			{
				/* One rcpt successfully removed from IQDB */
				SM_ASSERT(*piqdb_rcpts_done < UINT_MAX);
				++(*piqdb_rcpts_done);
			}
		}
	}

	/* Update counters in aq_ta */
#if QMGR_STATS
	if (SMTP_OK == rcpt_status)
		++qmgr_ctx->qmgr_rcpts_sent;
#endif
	QM_LEV_DPRINTFC(QDC_UPDRCPT, 3, (QM_DEBFP, "sev=DBG, func=q_upd_rcpt_stat, flags=%#x, old=%d, new=%d\n", aq_rcpt->aqr_flags, aq_rcpt->aqr_status, rcpt_status));
	ret = aq_upd_ta_rcpt_cnts(aq_ta, aq_rcpt->aqr_status, rcpt_status,
				qmgr_ctx->qmgr_lctx);
	SM_ASSERT(SM_SUCCESS == ret);	/* OK, see aq_upd_ta_rcpt_cnts() */

	/*
	**  Check whether recipient has been delivered ("taken care of"/"done")
	**  or is a double bounce that will be dropped on the floor, i.e.,
	**  delivery failed permanently or the item is too long in the queue.
	*/

	if (SMTP_DONE(rcpt_status) ||
	    (AQR_IS_FLAG(aq_rcpt, AQR_FL_IS_DBNC) &&
	     (smtp_reply_type(rcpt_status) == SMTP_RTYPE_PERM ||
	      aq_rcpt->aqr_st_time + qmgr_ctx->qmgr_cnf.q_cnf_tmo_return
	      < evthr_time(qmgr_ctx->qmgr_ev_ctx))))
	{
		ret = q_upd_rcpt_ok(qmgr_ctx, ss_ta_id, rcpt_status, aq_ta,
				aq_rcpt, &ibdb_rcpt, rcpt_id, edb_req_hd, ibdb_req_hd);
		if (sm_is_err(ret))
		{
			rv = ret;
			goto error;
		}
	}
	else
	{
		ret = q_upd_rcpt_fail(qmgr_ctx, ss_ta_id, rcpt_status,
				aq_ta, aq_rcpt, &ibdb_rcpt, rcpt_id,
				edb_req_hd, ibdb_req_hd, errmsg, pdelay_next_try);
		if (!sm_is_err(ret))
			flags |= ret;
		else
		{
			rv = ret;
			goto error;
		}
	}

	/*
	**  Do this only if really necessary:
	**  1. rcpt has been successfully delivered
	**  2. rcpt failed and no more chances for delivery now (see above).
	*/

	QM_LEV_DPRINTFC(QDC_UPDRCPT, 1, (QM_DEBFP, "sev=DBG, func=q_upd_rcpt_stat, aq_rcpt=%p, ss_ta=%s, idx=%u, cur=%d/max=%d, no_retries=%d\n", aq_rcpt, ss_ta_id, aq_rcpt->aqr_idx, aq_rcpt->aqr_addr_cur, aq_rcpt->aqr_addr_max, AQR_NO_RETRIES(aq_rcpt, rcpt_status)));
	if (AQR_NO_RETRIES(aq_rcpt, rcpt_status))
	{
		/* Remove recipient from AQ */

#if QMGR_DEBUG > 1
		QM_LEV_DPRINTFC(QDC_UPDRCPT, 3, (QM_DEBFP, "sev=DBG, func=q_upd_rcpt_stat, remove aq_rcpt=%p\n", aq_rcpt));
aq_rcpt_print(aq_rcpt);
#endif

		ret = aq_rcpt_rm(aq_ctx, aq_rcpt, AQR_RM_N_RDQ);
		if (sm_is_err(ret))
		{
			/* what to do now? */
			QM_LEV_DPRINTFC(QDC_UPDRCPT, 0, (QM_DEBFP, "sev=ERROR, func=q_upd_rcpt_stat, aq_rcpt_rm=%r\n", ret));
		}
		else
		{
			if (aq_ta->aqt_rcpts_inaq > 0)
				--aq_ta->aqt_rcpts_inaq;
			else
			{
				sm_log_write(qmgr_ctx->qmgr_lctx,
					QM_LCAT_DASTAT, QM_LMOD_DASTAT,
					SM_LOG_INCONS, 2,
					"sev=FATAL, func=q_upd_rcpt_stat, aq_ta=%p, ac_rcpt=%p, aqt_rcpts_inaq=%d, when=before_decreasing",
					aq_ta, aq_rcpt, aq_ta->aqt_rcpts_inaq);
			}
		}
	}
	else
	{
		ret = aq_waitq_rm(aq_ctx, aq_rcpt, AQWQ_ANY, false);
		if (sm_is_err(ret))
			QM_LEV_DPRINTFC(QDC_UPDRCPT, 0, (QM_DEBFP, "sev=ERROR, func=q_upd_rcpt_stat, aq_waitq_rm=%r\n", ret));
		/* ignore any error; see fct about possible problems */

		/* Let's try the next address... */
		aq_rcpt->aqr_addr_cur++;

		ret = aq_rdq_add(aq_ctx, aq_rcpt, NULL, THR_NO_LOCK);
		if (sm_is_err(ret))
		{
			rv = ret;
			sm_log_write(qmgr_ctx->qmgr_lctx,
				QM_LCAT_DASTAT, QM_LMOD_DASTAT,
				SM_LOG_ERR, 4,
				"sev=ERROR, func=q_upd_rcpt_stat, ac_rcpt=%p, aq_rdq_add=%m",
				aq_rcpt, ret);
			goto error;
		}

		/* Reset some flags; XXX more? */
		AQR_CLR_FLAG(aq_rcpt, AQR_FL_SCHED|AQR_FL_WAIT4UPD|AQR_FL_STAT_NEW|AQR_FL_ERRST_UPD);
		flags |= QDA_FL_ACT_SCHED;

		/* remove entry from delivery list */
		AQR_DA_DELENTRY(aq_rcpt);
	}
  error:
	return sm_is_err(rv) ? rv : flags;
}


syntax highlighted by Code2HTML, v. 0.9.1