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