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