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