/*
 * Copyright (c) 2002-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: adb.c,v 1.111 2007/05/27 15:09:50 ca Exp $")
#include "sm/magic.h"
#include "sm/types.h"
#include "sm/assert.h"
#include "sm/memops.h"
#include "sm/str.h"
#include "sm/time.h"
#include "sm/mta.h"
#include "sm/rfc2821.h"
#include "sm/qmgr.h"
#include "sm/actdb-int.h"
#include "sm/qmgr-int.h"
#include "adb.h"

/*
**  Simple version of active envelope database
*/

/*
**  AQ_RCPT_ADD_QSR -- add new recipient (QMGR/SMTPS recipient)
**
**	Parameters:
**		aq_ctx -- AQ context
**		qss_rcpt -- QMGR/SMTPS recipient context
**		qmgr_ctx -- QMGR context
**		qss_ta -- QMGR/SMTPS transaction context
**		aq_ta -- aq_ta
**		aq_rcpt_prev -- previous aq_rcpt
**		paq_rcpt -- pointer to aq_rcpt (output)
**
**	Returns:
**		usual sm_error code; ENOMEM et.al.
**
**	Locking: aq_ctx must be locked
**
**	Last code review:
**	Last code change: 2006-06-11 04:05:04
*/

static sm_ret_T
aq_rcpt_add_qsr(aq_ctx_P aq_ctx, qss_rcpt_P qss_rcpt, qmgr_ctx_P qmgr_ctx, qss_ta_P qss_ta, time_T time_entered, aq_ta_P aq_ta, aq_rcpt_P aq_rcpt_prev, aq_rcpt_P *paq_rcpt)
{
	sm_ret_T ret;
	aq_rcpt_P aq_rcpt;

	SM_IS_AQ(aq_ctx);
	SM_REQUIRE(paq_rcpt != NULL);
	aq_rcpt = NULL;
	ret = aq_rcpt_add_new(aq_ctx, aq_ta, &aq_rcpt, AQR_FL_IQDB, THR_NO_LOCK);
	if (sm_is_err(ret))
		goto error;
	SM_REQUIRE(paq_rcpt != NULL);

	SESSTA_COPY(aq_rcpt->aqr_ss_ta_id, qss_ta->qssta_id);
	aq_rcpt->aqr_pa = sm_str_dup(NULL, qss_rcpt->qsr_pa);
	if (NULL == aq_rcpt->aqr_pa) {
		ret = sm_error_temp(SM_EM_AQ, ENOMEM);
		goto error;
	}

	/* get domain part */
	ret = aq_rcpt_set_domain(aq_rcpt, qmgr_ctx->qmgr_hostname);
	if (sm_is_err(ret))
		goto error;

	/*
	**  We can either now send here the data to the AR (which violates
	**  the layering principle: AQ shouldn't have to deal with
	**  that) or we do it in the QMGR when it walks through AQ.
	**  The latter is ugly since this here is the best place otherwise.
	**  We could treat it as some callback function...
	**  See also the design document: it could be done when SMTPS tells
	**  QMGR about the RCPT.
	*/

	aq_rcpt->aqr_da_idx = 0;
	aq_rcpt->aqr_st_time = qss_ta->qssta_st_time;
	aq_rcpt->aqr_entered = time_entered;

	aq_rcpt->aqr_idx = qss_rcpt->qsr_idx;
	aq_rcpt->aqr_status = AQR_ST_NEW;
	AQR_SET_FLAG(aq_rcpt, AQR_FL_IQDB);

	AQR_DA_INIT(aq_rcpt);
	ret = aq_rcpt_ss_insert(aq_rcpt_prev, aq_rcpt);
	if (sm_is_err(ret))
		goto error;

	/*
	**  What to do in case of an error??
	**  The recipient address won't be resolved...
	**  we can either remove it right now or implement some
	**  kind of timeout (the latter has been done: qmgr_cleanup()).
	**  Moreover, it might be better to move this call up to
	**  minimize the amount of work to "undo".
	*/

	ret = qmgr_rcpt2ar(qmgr_ctx, aq_rcpt, THR_NO_LOCK);
#if 0
	if (sm_is_err(ret) && ret != sm_error_temp(SM_EM_Q_Q2AR, SM_E_NO_AR))
		goto error;
#endif

	*paq_rcpt = aq_rcpt;
	return SM_SUCCESS;

  error:
	if (aq_rcpt != NULL)
		(void) aq_rcpt_rm(aq_ctx, aq_rcpt, 0);
	*paq_rcpt = NULL;
	return ret;
}

/*
**  AQ_ENV_ADD_IQDB -- add new mail entry (transaction) from IQDB to AQ
**
**	Parameters:
**		aq_ctx -- AQ context
**		qss_ta -- QMGR/SMTPS transaction context
**		qmgr_ctx -- QMGR context
**
**	Returns:
**		>=0: AQ usage
**		<0: usual sm_error code; ENOMEM et.al.
**
**	Locking: locks entire aq_ctx during operation, returns unlocked
**
**	Note: this transfers the entire TA or nothing at all; i.e.,
**		there are no "partial" transfers (some recipients).
*/

sm_ret_T
aq_env_add_iqdb(aq_ctx_P aq_ctx, qss_ta_P qss_ta, qmgr_ctx_P qmgr_ctx)
{
	sm_ret_T ret;
	int r, aq_use;
	time_T time_entered;
	bool locked;
	aq_ta_P aq_ta;
	qss_rcpt_P qss_rcpt, qss_nxt_rcpt;
	aq_rcpt_P aq_rcpt, aq_nxt_rcpt;

	SM_IS_AQ(aq_ctx);
	locked = false;

	/* checked by qss_rcpts_new() */
	SM_REQUIRE(qss_ta->qssta_rcpts_tot < SMTP_RCPTIDX_MAX);
	ret = aq_ta_add_new(aq_ctx, &aq_ta, AQ_TA_FL_IQDB, qss_ta->qssta_rcpts_tot,
			THR_LOCK_IT|THR_UNL_IF_ERR);
	if (sm_is_err(ret))
		return ret;
	locked = true;

	qss_rcpt = NULL;
	aq_ta->aqt_st_time = qss_ta->qssta_st_time;
	aq_ta->aqt_rcpts_inaq = qss_ta->qssta_rcpts_tot;
	aq_ta->aqt_rcpts_tot = qss_ta->qssta_rcpts_tot;
	aq_ta->aqt_msg_sz_b = qss_ta->qssta_msg_sz_b;

	aq_ta->aqt_nxt_idx = qss_ta->qssta_rcpts_tot;
	aq_ta->aqt_rcpts_left = qss_ta->qssta_rcpts_tot;
	if (QSS_TA_IS_FLAG(qss_ta, QSS_TA_FL_VERP))
		AQ_TA_SET_FLAG(aq_ta, AQ_TA_FL_VERP);

	SESSTA_COPY(aq_ta->aqt_ss_ta_id, qss_ta->qssta_id);

	/*
	**  ToDo: It would be nice if we just "move" the data here
	**  instead of copying it around. the _free() functions
	**  would have to deal with NULL entries in that case,
	**  e.g.,
	**  aq_ta->aqt_cdb_id = qss_ta->qssta_cdb_id
	**  qss_ta->qssta_cdb_id = NULL
	**  Problems: rpool usage, access to components after they
	**  have been NULLed, errors that may happen later on
	**  may require to undo a "move".
	*/

	aq_ta->aqt_cdb_id = SM_CSTR_DUP(qss_ta->qssta_cdb_id);
#if 0
	/* SM_CSTR_DUP can't fail... */
	if (NULL == aq_ta->aqt_cdb_id)
		goto enomem;
#endif /* 0 */
	aq_ta->aqt_mail->aqm_pa = sm_str_dup(NULL, qss_ta->qssta_mail->qsm_pa);
	if (NULL == aq_ta->aqt_mail->aqm_pa)
		goto enomem;
	/*
	**  "transfer" header of list to aq_ta.
	**  The list is already stored in IBDB; otherwise we would need to
	**  make a copy or use some reference counting mechanism.
	*/

	aq_ta->aqt_hdrmodhd = qss_ta->qssta_hdrmodhd;
	qss_ta->qssta_hdrmodhd = NULL;

	aq_rcpt = NULL;
	time_entered = evthr_time(qmgr_ctx->qmgr_ev_ctx);
	for (qss_rcpt = QSRCPTS_FIRST(&qss_ta->qssta_rcpts);
	     qss_rcpt != QSRCPTS_END(&qss_ta->qssta_rcpts);
	     qss_rcpt = qss_nxt_rcpt)
	{
		qss_nxt_rcpt = QSRCPTS_NEXT(qss_rcpt);
		ret = aq_rcpt_add_qsr(aq_ctx, qss_rcpt, qmgr_ctx, qss_ta, time_entered,
				aq_ta, aq_rcpt,  &aq_nxt_rcpt);
		if (sm_is_err(ret))
			goto error;
		aq_rcpt =  aq_nxt_rcpt;
	}

	aq_use = aq_usage(aq_ctx, AQ_USAGE_ALL);
	r = pthread_mutex_unlock(&aq_ctx->aq_mutex);
	SM_ASSERT(0 == r);
	if (0 == r)
		locked = false;

	/* HACK notify qar task: done after unlocking AQ */
	{
		sm_evthr_task_P qar_tsk;
		qar_ctx_P qar_ctx;

		qar_ctx = qmgr_ctx->qmgr_ar_ctx;
		SM_IS_QAR_CTX(qar_ctx);
		qar_tsk = qmgr_ctx->qmgr_ar_tsk;

		/* There might not be a task... */
		if (qar_tsk != NULL) {
			ret = evthr_en_wr(qar_tsk);
			if (sm_is_err(ret))
				goto error;
		}
	}

	/* error from unlock? */
	if (r != 0)
		return sm_error_perm(SM_EM_AQ, r);
	return aq_use;

  enomem:
	ret = sm_error_temp(SM_EM_AQ, ENOMEM);
  error:
	/*
	**  This two stage "allocation" implementation is a bit ugly
	**  because it causes problems when something fails. It is not
	**  clear what parts have been done and needs to be undone since
	**  two separate functions are used. The "outer" function must
	**  "know" what has been done so far (which violates abstraction)
	**  or there must be some state which tells the _free() function
	**  what needs to be undone.
	*/

	/* XXX: just call aq_ta_rm(aq_ctx, aq_ta, false); ??? */

	/* free all recipients we added... this is ugly */
	for (aq_rcpt = AQR_FIRST(aq_ctx);
	     aq_rcpt != AQR_END(aq_ctx);
	     aq_rcpt = aq_nxt_rcpt)
	{
		aq_nxt_rcpt = AQR_NEXT(aq_rcpt);
		if (SESSTA_EQ(aq_ta->aqt_ss_ta_id, qss_ta->qssta_id))
			(void) aq_rcpt_rm(aq_ctx, aq_rcpt, AQR_RM_LOCK);
	}

	SM_CSTR_FREE(aq_ta->aqt_cdb_id);
	AQ_TAS_REMOVE(aq_ctx, aq_ta);
	sm_free_size(aq_ta, sizeof(*aq_ta));
	if (locked) {
		r = pthread_mutex_unlock(&aq_ctx->aq_mutex);
		SM_ASSERT(0 == r);
		if (r != 0 && sm_is_success(ret))
			ret = sm_error_perm(SM_EM_AQ, r);
	}
	return ret;
}

/*
**  AQ_RCPT_STATUS -- update recipient status
**
**	Parameters:
**		aq_ctx -- AQ context
**		da_ta_id -- DA transaction id
**		rcpt_idx -- recipient index
**		rcpt_status -- new recipient status
**		err_st -- state which cause error
**		errmsg -- error message ("taken over", i.e., caller must not
**			access it afterwards)
**
**	Returns:
**		usual sm_error code: sm_error_perm(SM_EM_AQ, SM_E_NOTFOUND)
**			or maybe mutex error.
**
**	Locking: locks entire aq_ctx during operation, returns unlocked
**
**	Called by: qm_fr_sc_rcpts()
*/

sm_ret_T
aq_rcpt_status(aq_ctx_P aq_ctx, sessta_id_T da_ta_id, rcpt_idx_T rcpt_idx, smtp_status_T rcpt_status, uint err_st, sm_str_P errmsg)
{
	int r;
	sm_ret_T ret;
	aq_rcpt_P aq_rcpt;
	aq_ta_P aq_ta;

	SM_IS_AQ(aq_ctx);
	r = pthread_mutex_lock(&aq_ctx->aq_mutex);
	SM_LOCK_OK(r);
	if (r != 0) {
		SM_STR_FREE(errmsg);
		return sm_error_perm(SM_EM_AQ, r);
	}
	ret = aq_rcpt_find_da(aq_ctx, da_ta_id, rcpt_idx, THR_NO_LOCK, &aq_rcpt);
	if (sm_is_err(ret)) {
		/* can happen if rcpt was too long in AQ */
		/* COMPLAIN? */
		goto error;
	}
	aq_ta = aq_rcpt->aqr_ss_ta;
	SM_IS_AQ_TA(aq_ta);
	aq_rcpt->aqr_status_new = rcpt_status;
	SM_STR_FREE(aq_rcpt->aqr_msg);
	aq_rcpt->aqr_msg = errmsg;
	errmsg = NULL;
	aq_rcpt->aqr_err_st = err_st;
	AQR_SET_FLAG(aq_rcpt, AQR_FL_STAT_NEW|AQR_FL_ERRST_UPD);
	/* fall through for unlocking */

  error:
	r = pthread_mutex_unlock(&aq_ctx->aq_mutex);
	SM_ASSERT(0 == r);
	if (r != 0 && sm_is_success(ret))
		ret = sm_error_perm(SM_EM_AQ, r);
	SM_STR_FREE(errmsg);
	return ret;
}


syntax highlighted by Code2HTML, v. 0.9.1