/* * 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: dadb.c,v 1.78 2006/11/22 17:01:27 ca Exp $") #include "sm/types.h" #include "sm/assert.h" #include "sm/magic.h" #include "sm/str.h" #include "sm/mta.h" #include "sm/memops.h" #include "sm/qmgr.h" #include "sm/qmgr-int.h" #include "sm/dadb.h" #include "dadb.h" #include "occ.h" #include "sm/io.h" #define LOCK_DPRINTF(x) /* ** Simple version of DA status database ** ** dadb_open: We use an array since we know the maximum number of ** "threads" in the DA. We use an index to access the DA data in that array. ** We use that index when exchanging data with the DA: it is encoded ** in the id; see include/sm/mta.h ** see also queuemanager.func.tex: func:QMGR:DAStatusCache */ /* ** DADB_SESS_OPEN -- open a session in DADB ** ** Parameters: ** qsc_ctx -- QMGR SMTPC context ** dadb_ctx -- DADB context ** ss_ta_id -- transaction id from SMTPS ** srv_ipv4 -- IPv4 address of server (HACK) ** aq_rcpt -- one recipient for a transaction (can be NULL) ** pdadb_entry -- pointer to DA DB entry (output) ** ** Returns: ** usual sm_error code ** ** Side Effects: none on error (except if unlock fails) but see below! ** ** Locking: locks entire dadb_ctx during operation, returns unlocked ** ** Last code review: 2005-03-13 02:59:57; see below (clean up) ** Last code change: */ sm_ret_T dadb_sess_open(qsc_ctx_P qsc_ctx, dadb_ctx_P dadb_ctx, sessta_id_P ss_ta_id, ipv4_T srv_ipv4, aq_rcpt_P aq_rcpt, dadb_entry_P *pdadb_entry) { #undef SMFCT #define SMFCT "dadb_sess_open" sm_ret_T ret; uint idx, st_flags; int r; dadb_entry_P dadb_entry; occ_entry_P occ_entry; time_T time_now; #define SM_D_FL_DADB_LOCKED 0x0001 #define SM_D_FL_DADBE_LOCKED 0x0002 SM_IS_DADB(dadb_ctx); SM_REQUIRE(pdadb_entry != NULL); st_flags = 0; dadb_entry = NULL; occ_entry = NULL; ret = occ_sess_open(qsc_ctx->qsc_qmgr_ctx, qsc_ctx->qsc_qmgr_ctx->qmgr_occ_ctx, srv_ipv4, &occ_entry, THR_LOCK_UNLOCK); if (sm_is_err(ret)) goto error; time_now = evthr_time(qsc_ctx->qsc_qmgr_ctx->qmgr_ev_ctx); r = pthread_mutex_lock(&dadb_ctx->dadb_mutex); SM_LOCK_OK(r); if (r != 0) return sm_error_perm(SM_EM_DA, r); SM_SET_FLAG(st_flags, SM_D_FL_DADB_LOCKED); if (!DADB_IS_OK(dadb_ctx)) { ret = sm_error_perm(SM_EM_DA, SM_E_UNAVAIL); goto error; } /* search for a free DA DB entry */ ret = dadb_entry_get(dadb_ctx, &dadb_entry, &idx, THR_NO_LOCK); DADB_DPRINTF((smioerr, "sev=INFO, func=dadb_sess_open, dadb_find_free=%d, idx=%d\n", ret, idx)); if (sm_is_err(ret)) /* (ret != SM_SUCCESS), see dadb_entry_get() */ goto error; SM_SET_FLAG(st_flags, SM_D_FL_DADBE_LOCKED); dadb_entry->dadbe_lastuse = time_now; dadb_entry->dadbe_srv_ipv4 = srv_ipv4; /* relies on dadb_ctx->dadb_mutex! */ ret = qsc_id_next(qsc_ctx, qsc_ctx->qsc_id, idx, dadb_entry->dadbe_da_se_id); if (sm_is_err(ret)) goto error; ret = qsc_id_next(qsc_ctx, qsc_ctx->qsc_id, idx, dadb_entry->dadbe_da_ta_id); if (sm_is_err(ret)) goto error; #if 0 // if (new) { // ret = bht2_add(&bht2e); // DADB_DPRINTF((smioerr, "func=dadb_sess_open, bht2_add=%x\n", ret)); // if (sm_is_err(ret)) // goto error; // } #endif /* 0 */ SESSTA_COPY(dadb_entry->dadbe_ss_ta_id, ss_ta_id); DADBE_SET_SE_OPEN(dadb_entry); dadb_entry->dadbe_rcpt = aq_rcpt; #if DADBE_MUTEX r = pthread_mutex_unlock(&dadb_entry->dadbe_mutex); SM_ASSERT(0 == r); if (r != 0) ret = sm_error_perm(SM_EM_DA, r); else SM_CLR_FLAG(st_flags, SM_D_FL_DADBE_LOCKED); #endif r = pthread_mutex_unlock(&dadb_ctx->dadb_mutex); SM_ASSERT(0 == r); if (r != 0) ret = sm_error_perm(SM_EM_DA, r); else SM_CLR_FLAG(st_flags, SM_D_FL_DADB_LOCKED); *pdadb_entry = dadb_entry; return ret; error: /* clean up?? */ if (occ_entry != NULL) { /* ** fixme: this should be a function in occ.c ** This is broken for !DA_OCC_RSC because the entry isn't ** free()d; moreover, it's not clear whether the entry ** must be free()d because that information is not returned ** by occ_sess_open(). Could that be a flag in occ_entry? ** Set it when the entry is allocated, clear it when ** it is reused? ** Last, but not least: access to occ_entry isn't locked here. */ if (occ_entry->occe_open_se > 0) --occ_entry->occe_open_se; if (occ_entry->occe_open_ta > 0) --occ_entry->occe_open_ta; } if (dadb_entry != NULL) { /* mark entry as free */ DADBE_CLR_FLAG(dadb_entry, DADBE_FL_BUSY|DADBE_FL_IDLE); dadb_entry->dadbe_da_se_id[0] = '\0'; dadb_entry->dadbe_da_ta_id[0] = '\0'; SM_ASSERT(dadb_ctx->dadb_entries_cur > 0); --dadb_ctx->dadb_entries_cur; } *pdadb_entry = NULL; #if DADBE_MUTEX if (SM_IS_FLAG(st_flags, SM_D_FL_DADBE_LOCKED)) { r = pthread_mutex_unlock(&dadb_entry->dadbe_mutex); SM_ASSERT(0 == r); if (r != 0 && sm_is_success(ret)) ret = sm_error_perm(SM_EM_DA, r); } #endif if (SM_IS_FLAG(st_flags, SM_D_FL_DADB_LOCKED)) { r = pthread_mutex_unlock(&dadb_ctx->dadb_mutex); SM_ASSERT(0 == r); if (r != 0 && sm_is_success(ret)) ret = sm_error_perm(SM_EM_DA, r); } return ret; } /* ** DADB_SESS_REUSE -- reuse a session in DADB ** ** Parameters: ** qsc_ctx -- QMGR SMTPC context ** dadb_ctx -- DADB context ** ss_ta_id -- transaction id from SMTPS ** dadb_entry -- DA DB entry to reuse ** occ_entry -- OCC entry ** aq_rcpt -- one recipient for a transaction (can be NULL) ** ** Returns: ** usual sm_error code; SM_E_UNEXPECTED ** ** Side Effects: none on error (except if unlock fails) ** ** Locking: locks entire dadb_ctx during operation, returns unlocked ** occ_entry MUST be locked by caller (use a parameter instead?) ** ** Last code review: 2005-03-16 05:23:53 ** Last code change: */ sm_ret_T dadb_sess_reuse(qsc_ctx_P qsc_ctx, dadb_ctx_P dadb_ctx, sessta_id_P ss_ta_id, dadb_entry_P dadb_entry, occ_entry_P occ_entry, aq_rcpt_P aq_rcpt) { #undef SMFCT #define SMFCT "dadb_sess_reuse" sm_ret_T ret; uint idx; int r; time_T time_now; SM_IS_DADB(dadb_ctx); time_now = evthr_time(qsc_ctx->qsc_qmgr_ctx->qmgr_ev_ctx); r = pthread_mutex_lock(&dadb_ctx->dadb_mutex); SM_LOCK_OK(r); if (r != 0) return sm_error_perm(SM_EM_DA, r); if (!DADB_IS_OK(dadb_ctx)) { ret = sm_error_perm(SM_EM_DA, SM_E_UNAVAIL); goto error; } r = SMTPC_GETTHRIDX(dadb_entry->dadbe_da_ta_id, idx); if (r != 1 || idx >= dadb_ctx->dadb_entries_max) { /* COMPLAIN */ ret = sm_error_perm(SM_EM_DA, SM_E_UNEXPECTED); goto error; } if (dadb_entry->dadbe_lastuse > 0 && dadb_entry->dadbe_lastuse < time_now && time_now - dadb_entry->dadbe_lastuse > SM_SESSION_TMO) { ret = sm_error_perm(SM_EM_DA, SM_E_EXPIRED); goto error; } ret = qsc_id_next(qsc_ctx, qsc_ctx->qsc_id, idx, dadb_entry->dadbe_da_ta_id); if (sm_is_err(ret)) goto error; SESSTA_COPY(dadb_entry->dadbe_ss_ta_id, ss_ta_id); DADBE_SET_SE_OPEN(dadb_entry); dadb_entry->dadbe_lastuse = time_now; dadb_entry->dadbe_rcpt = aq_rcpt; /* doesn't fail */ (void) occ_sess_reuse(qsc_ctx->qsc_qmgr_ctx->qmgr_occ_ctx, occ_entry, time_now, THR_NO_LOCK); r = pthread_mutex_unlock(&dadb_ctx->dadb_mutex); SM_ASSERT(0 == r); if (r != 0 && sm_is_success(ret)) ret = sm_error_perm(SM_EM_DA, r); return SM_SUCCESS; error: r = pthread_mutex_unlock(&dadb_ctx->dadb_mutex); SM_ASSERT(0 == r); if (r != 0 && sm_is_success(ret)) ret = sm_error_perm(SM_EM_DA, r); return ret; } /* ** DADB_SESS_CLOSE_ENTRY -- close a session in DADB ** ** Parameters: ** qmgr_ctx -- QMGR context (only qmgr_conf is needed) ** dadb_ctx -- DADB context ** dadb_entry -- DA DB entry (session) to close ** ok -- session was successful? ** pflags -- flags of session (output, may be NULL) ** locktype -- kind of locking ** ** Returns: ** usual sm_error code; SM_E_NOTFOUND, (un)lock errors ** ** Side Effects: ** reset ids in dadb_entry (even on error) ** ** Locking: locks dadb_ctx if requested ** ** Last code review: 2005-03-17 00:22:16 ** Last code change: */ sm_ret_T dadb_sess_close_entry(qmgr_ctx_P qmgr_ctx, dadb_ctx_P dadb_ctx, dadb_entry_P dadb_entry, bool ok, uint32_t *pflags, thr_lock_T locktype) { #undef SMFCT #define SMFCT "dadb_sess_close_entry" int r; sm_ret_T ret; SM_IS_DADB(dadb_ctx); SM_IS_DADBE(dadb_entry); ret = occ_sess_close_entry(qmgr_ctx->qmgr_occ_ctx, dadb_entry->dadbe_srv_ipv4, ok, evthr_time(qmgr_ctx->qmgr_ev_ctx), pflags, locktype); if (thr_lock_it(locktype)) { r = pthread_mutex_lock(&dadb_ctx->dadb_mutex); SM_LOCK_OK(r); if (r != 0) return sm_error_perm(SM_EM_DA, r); } if (DADB_IS_OK(dadb_ctx)) { /* paranoia... */ dadb_entry->dadbe_da_se_id[0] = '\0'; dadb_entry->dadbe_da_ta_id[0] = '\0'; dadb_entry->dadbe_rcpt = NULL; if (DADBE_IS_FLAG(dadb_entry, DADBE_FL_IDLE)) { SM_ASSERT(dadb_ctx->dadb_entries_idle > 0); --dadb_ctx->dadb_entries_idle; } DADBE_CLR_FLAG(dadb_entry, DADBE_FL_BUSY|DADBE_FL_IDLE); SM_ASSERT(dadb_ctx->dadb_entries_cur > 0); if (pflags != NULL && dadb_ctx->dadb_entries_cur == dadb_ctx->dadb_entries_lim) *pflags |= DADBE_FL_AVAIL; --dadb_ctx->dadb_entries_cur; } if ((!sm_is_err(ret) && thr_unl_no_err(locktype)) || (sm_is_err(ret) && thr_unl_if_err(locktype))) { r = pthread_mutex_unlock(&dadb_ctx->dadb_mutex); SM_ASSERT(0 == r); if (r != 0 && sm_is_success(ret)) ret = sm_error_perm(SM_EM_DA, r); } return ret; } /* ** DADB_TA_CLOSE_ENTRY -- close a transaction in DADB ** ** Parameters: ** qmgr_ctx -- QMGR context ** dadb_ctx -- DADB context ** dadb_entry -- DA DB entry to close ** pflags -- flags of session (output, may be NULL) ** locktype -- kind of locking ** ** Returns: ** usual sm_error code; SM_E_NOTFOUND, (un)lock errors ** ** Side Effects: ** change flags in dadb_entry (even on error) ** ** Locking: locks dadb_ctx if requested ** ** Last code review: 2005-03-17 00:22:40 ** Last code change: */ sm_ret_T dadb_ta_close_entry(qmgr_ctx_P qmgr_ctx, dadb_ctx_P dadb_ctx, dadb_entry_P dadb_entry, uint32_t *pflags, thr_lock_T locktype) { #undef SMFCT #define SMFCT "dadb_ta_close_entry" int r; sm_ret_T ret; SM_IS_DADB(dadb_ctx); SM_IS_DADBE(dadb_entry); ret = occ_ta_close_entry(qmgr_ctx->qmgr_occ_ctx, dadb_entry->dadbe_srv_ipv4, evthr_time(qmgr_ctx->qmgr_ev_ctx), pflags, locktype); if (thr_lock_it(locktype)) { r = pthread_mutex_lock(&dadb_ctx->dadb_mutex); SM_LOCK_OK(r); if (r != 0) return sm_error_perm(SM_EM_DA, r); } if (DADB_IS_OK(dadb_ctx)) { dadb_entry->dadbe_rcpt = NULL; DADBE_SET_CONN(dadb_entry); ++dadb_ctx->dadb_entries_idle; } if ((!sm_is_err(ret) && thr_unl_no_err(locktype)) || (sm_is_err(ret) && thr_unl_if_err(locktype))) { r = pthread_mutex_unlock(&dadb_ctx->dadb_mutex); SM_ASSERT(0 == r); if (r != 0 && sm_is_success(ret)) ret = sm_error_perm(SM_EM_DA, r); } return ret; } /* ** DADB_TA_FIND -- find a transaction in DADB ** ** Parameters: ** dadb_ctx -- DADB context ** da_ta_id -- DA transaction id ** pdadb_entry -- pointer to DA DB entry (output) ** ** Returns: ** usual sm_error code; SM_E_UNEXPECTED ** ** Side Effects: none on error (except if unlock fails) ** ** Locking: locks entire dadb_ctx during operation, returns unlocked ** ** Last code review: 2005-03-17 00:22:54 ** Last code change: */ sm_ret_T dadb_ta_find(dadb_ctx_P dadb_ctx, sessta_id_P da_ta_id, dadb_entry_P *pdadb_entry) { #undef SMFCT #define SMFCT "dadb_ta_find" uint i; sm_ret_T ret; int r; dadb_entry_P dadb_entry; SM_IS_DADB(dadb_ctx); SM_REQUIRE(pdadb_entry != NULL); SM_REQUIRE(da_ta_id != NULL); /* do we really need to lock this?? */ r = pthread_mutex_lock(&dadb_ctx->dadb_mutex); SM_LOCK_OK(r); if (r != 0) return sm_error_perm(SM_EM_DA, r); if (!DADB_IS_OK(dadb_ctx)) { ret = sm_error_perm(SM_EM_DA, SM_E_UNAVAIL); goto error; } ret = SM_SUCCESS; r = SMTPC_GETTHRIDX(da_ta_id, i); if (r != 1 || i >= dadb_ctx->dadb_entries_max) { /* COMPLAIN */ ret = sm_error_perm(SM_EM_DA, SM_E_UNEXPECTED); goto error; } dadb_entry = (dadb_ctx->dadb_entries)[i]; /* paranoia */ if (NULL == dadb_entry || !SESSTA_EQ(dadb_entry->dadbe_da_ta_id, da_ta_id)) { /* COMPLAIN */ DADB_DPRINTF((smioerr, "sev=ERROR, func=dadb_ta_find, i=%u, found:da_ta=%s, wanted:da_ta=%s\n", i, NULL == dadb_entry ? "NULL" : dadb_entry->dadbe_da_ta_id, da_ta_id)); ret = sm_error_perm(SM_EM_DA, SM_E_UNEXPECTED); goto error; } SM_IS_DADBE(dadb_entry); r = pthread_mutex_unlock(&dadb_ctx->dadb_mutex); SM_ASSERT(0 == r); if (r != 0 && sm_is_success(ret)) ret = sm_error_perm(SM_EM_DA, r); *pdadb_entry = dadb_entry; return ret; error: r = pthread_mutex_unlock(&dadb_ctx->dadb_mutex); SM_ASSERT(0 == r); if (r != 0 && sm_is_success(ret)) ret = sm_error_perm(SM_EM_DA, r); *pdadb_entry = NULL; return ret; } /* ** DADB_SE_FIND -- find a session in DADB ** ** Parameters: ** dadb_ctx -- DADB context ** da_se_id -- DA session id ** pdadb_entry -- pointer to DA DB entry (output) ** ** Returns: ** usual sm_error code ** ** Side Effects: none on error (except if unlock fails) ** ** Locking: locks entire dadb_ctx during operation, returns unlocked ** ** Last code review: 2005-03-14 17:15:12 ** Last code change: */ sm_ret_T dadb_se_find(dadb_ctx_P dadb_ctx, sessta_id_P da_se_id, dadb_entry_P *pdadb_entry) { #undef SMFCT #define SMFCT "dadb_se_find" uint i; sm_ret_T ret; int r; dadb_entry_P dadb_entry; SM_IS_DADB(dadb_ctx); SM_REQUIRE(pdadb_entry != NULL); /* do we really need to lock this?? */ r = pthread_mutex_lock(&dadb_ctx->dadb_mutex); SM_LOCK_OK(r); if (r != 0) return sm_error_perm(SM_EM_DA, r); ret = SM_SUCCESS; r = SMTPC_GETTHRIDX(da_se_id, i); if (r != 1 || i >= dadb_ctx->dadb_entries_max) { /* COMPLAIN */ ret = sm_error_perm(SM_EM_DA, SM_E_UNEXPECTED); goto errunl; } dadb_entry = (dadb_ctx->dadb_entries)[i]; /* paranoia */ if (NULL == dadb_entry || !SESSTA_EQ(dadb_entry->dadbe_da_se_id, da_se_id)) { /* COMPLAIN */ DADB_DPRINTF((smioerr, "sev=ERROR, func=dadb_se_find, i=%u, found da_se_id=%s, wanted da_se_id=%s\n", i, NULL == dadb_entry ? "NULL" : dadb_entry->dadbe_da_se_id, da_se_id)); ret = sm_error_perm(SM_EM_DA, SM_E_UNEXPECTED); goto errunl; } SM_IS_DADBE(dadb_entry); r = pthread_mutex_unlock(&dadb_ctx->dadb_mutex); SM_ASSERT(0 == r); if (r != 0 && sm_is_success(ret)) ret = sm_error_perm(SM_EM_DA, r); *pdadb_entry = dadb_entry; return ret; errunl: r = pthread_mutex_unlock(&dadb_ctx->dadb_mutex); SM_ASSERT(0 == r); if (r != 0 && sm_is_success(ret)) ret = sm_error_perm(SM_EM_DA, r); *pdadb_entry = NULL; return ret; } /* ** DADB_DESTROY -- free (destroy) a DADB ** ** Parameters: ** dadb_ctx -- DADB context ** ** Returns: ** SM_SUCCESS ** ** Locking: none, destroys DADB (and hence lock) ** ** Last code review: 2005-03-14 17:23:05 ** Last code change: 2005-03-14 17:22:53 */ sm_ret_T dadb_destroy(dadb_ctx_P dadb_ctx) { #undef SMFCT #define SMFCT "dadb_destroy" if (NULL == dadb_ctx) return SM_SUCCESS; SM_IS_DADB(dadb_ctx); dadb_ctx->dadb_state = DADB_ST_STOP; if (dadb_ctx->dadb_entries != NULL) { uint i; dadb_entry_P dadb_entry; for (i = 0; i < dadb_ctx->dadb_entries_max; i++) { dadb_entry = (dadb_ctx->dadb_entries)[i]; if (dadb_entry != NULL) dadb_entry_free(dadb_entry); } FREE_DADB_ENTRIES(dadb_ctx); } (void) pthread_mutex_destroy(&dadb_ctx->dadb_mutex); #if DADB_CHECK dadb_ctx->sm_magic = SM_MAGIC_NULL; #endif sm_free_size(dadb_ctx, sizeof(*dadb_ctx)); return SM_SUCCESS; } /* ** DADB_NEW -- create a new DADB ** ** Parameters: ** pdadb_ctx -- pointer to DADB context (output) ** max_elems -- maximum number of elements in DADB ** ** Returns: ** usual sm_error code; ENOMEM, mutex_init ** ** Side Effects: none on error ** ** Last code review: 2005-03-17 00:24:36 ** Last code change: 2005-03-14 17:29:05 */ sm_ret_T dadb_new(dadb_ctx_P *pdadb_ctx, uint max_elems) { #undef SMFCT #define SMFCT "dadb_new" int r; size_t size; sm_ret_T ret; dadb_ctx_P dadb_ctx; SM_REQUIRE(pdadb_ctx != NULL); SM_REQUIRE(max_elems > 0); size = 0; dadb_ctx = (dadb_ctx_P) sm_zalloc(sizeof(*dadb_ctx)); if (NULL == dadb_ctx) return sm_error_temp(SM_EM_DA, ENOMEM); size = max_elems * sizeof(*dadb_ctx->dadb_entries); SM_ASSERT(size > max_elems && size > sizeof(*dadb_ctx->dadb_entries)); dadb_ctx->dadb_entries = (dadb_entry_P *) sm_zalloc(size); if (NULL == dadb_ctx->dadb_entries) { ret = sm_error_temp(SM_EM_DA, ENOMEM); goto error; } r = pthread_mutex_init(&dadb_ctx->dadb_mutex, SM_PTHREAD_MUTEXATTR); if (r != 0) { ret = sm_error_perm(SM_EM_DA, r); goto error; } dadb_ctx->dadb_entries_max = max_elems; dadb_ctx->dadb_entries_lim = max_elems; dadb_ctx->dadb_state = DADB_ST_INIT; #if DADB_CHECK dadb_ctx->sm_magic = SM_DADB_MAGIC; #endif *pdadb_ctx = dadb_ctx; return SM_SUCCESS; error: SM_ASSERT(dadb_ctx != NULL); /* just paranoia */ if (dadb_ctx->dadb_entries != NULL) { /* ** NOTE: this cleanup code depends on the sequence above! ** If that is changed, this must be changed too. */ (void) pthread_mutex_destroy(&dadb_ctx->dadb_mutex); SM_ASSERT(size > 0); sm_free_size(dadb_ctx->dadb_entries, size); } #if DADB_CHECK dadb_ctx->sm_magic = SM_MAGIC_NULL; #endif sm_free_size(dadb_ctx, sizeof(*dadb_ctx)); *pdadb_ctx = NULL; return ret; }