/* * 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. */ /* ** SMTPS - QMGR/SMAR communication module */ #include "sm/generic.h" SM_RCSID("@(#)$Id: s2q.c,v 1.171 2007/10/23 03:57:40 ca Exp $") #include "sm/assert.h" #include "sm/error.h" #include "sm/memops.h" #include "sm/reccom.h" #include "statethreads/st.h" #include "sm/rcbst.h" #include "sm/fcntl.h" #include "sm/unixsock.h" #include "sm/stsock.h" #include "sm/stthreads.h" #include "smtps-str.h" #include "s2q.h" #include "smtps.h" #if MTA_USE_PMILTER #include "sm/pmilter.h" #include "sm/pmfdef.h" #include "smtpsh.h" # if MTA_USE_RSAD # include "pmilter.h" # endif #endif #include "sm/hdrmod.h" #include "log.h" #include "sm/smar.h" /* just a debugging help: number of open requests */ /* however, this should be per context, not global (fixme) */ static uint Open_rqs = 0; #define S2Q_SOCKNAME \ (SOCK_TYPE_UNIX == sockspec->sckspc_type) \ ? sockspec->sock_unix.unixsckspc_path : "inet" /* ** SM_S2Q_ID_MATCH -- check whether the id of the communication context ** matches the id stored in the session context ** ** Parameters: ** ss_sess -- session context ** s2q_ctx -- S2Q context ** ** Returns: ** match? */ bool sm_s2q_id_match(ss_sess_P ss_sess, s2q_ctx_P s2q_ctx) { int idx; bool match; SM_IS_SS_SESS(ss_sess); SM_REQUIRE(s2q_ctx != NULL); if (S2Q_ID_NONE == s2q_ctx->s2q_q_id) return true; idx = s2q_ctx->s2q_srv_type; SM_ASSERT(idx >= 0 && idx < SS_MAX_COMM_SRVS); /* id not yet set? */ if (S2Q_ID_NONE == ss_sess->ssse_s2q_id[idx]) { ss_sess->ssse_s2q_id[idx] = s2q_ctx->s2q_q_id; return true; } match = (ss_sess->ssse_s2q_id[idx] == s2q_ctx->s2q_q_id); if (!match) { sm_log_write(s2q_ctx->s2q_ss_ctx->ssc_lctx, SS_LCAT_COMM, SS_LMOD_COMM, SM_LOG_WARN, 9, "sev=WARN, func=sm_s2q_id_match, ss_sess=%s, s2q_srv_type=%u, ssse_s2q_id=%u, status=mismatch" , ss_sess->ssse_id, s2q_ctx->s2q_q_id, ss_sess->ssse_s2q_id[idx]); } return match; } /* ** SS_ADD_RQ -- add a session to the list of outstanding requests ** ** Parameters: ** ss_sess -- session context ** tid -- transaction (or session) id ** s2q_ctx -- S2Q context ** pi -- (pointer to) index in s2q_ctx->s2q_sess (output) ** ** Returns: ** usual sm_error code */ sm_ret_T ss_add_rq(ss_sess_P ss_sess, sessta_id_P tid, s2q_ctx_P s2q_ctx, uint *pi) { uint i; SM_REQUIRE(s2q_ctx != NULL); SM_IS_SS_SESS(ss_sess); S2Q_CHK_CONN_R(ss_sess, s2q_ctx); for (i = 0; i < s2q_ctx->s2q_maxrcbs; i++) { if (s2q_ctx->s2q_sess[i] == NULL) { s2q_ctx->s2q_sess[i] = ss_sess; s2q_ctx->s2q_sids[i] = tid; ss_sess->ssse_s2q_idx = i; ++Open_rqs; break; } } if (i >= s2q_ctx->s2q_maxrcbs) { SSQ_DPRINTF((smioerr, "sev=ERROR, func=ss_add_rq, sess=%p, i=%d, open=%u\n", ss_sess, i, Open_rqs)); #if SSQ_DEBUG dump_thrd_info(); #endif /* SM_ASSERT(false); */ return sm_error_perm(SM_EM_SMTPS, SM_E_FULL); } if (pi != NULL) *pi = i; return SM_SUCCESS; } /* ** SS_SEND_RQ -- add a session to the list of outstanding requests ** (unless reply is false) and send RCB to server ** ** Parameters: ** ss_sess -- session context ** tid -- transaction (or session) id ** s2q_ctx -- S2Q context ** rcb -- RCB ** reply -- expect reply? ** ** Returns: ** usual sm_error code */ sm_ret_T ss_send_rq(ss_sess_P ss_sess, sessta_id_P tid, s2q_ctx_P s2q_ctx, sm_rcb_P rcb, bool reply) { sm_ret_T ret; uint i; int r; SM_REQUIRE(s2q_ctx != NULL); if (reply) { ret = ss_add_rq(ss_sess, tid, s2q_ctx, &i); if (sm_is_err(ret)) { SSQ_DPRINTF((smioerr, "sev=ERROR, func=ss_send_rq, sess=%p, i=%d, open=%u\n", ss_sess, i, Open_rqs)); #if SSQ_DEBUG dump_thrd_info(); #endif /* SM_ASSERT(false); */ return ret; } } else { i = s2q_ctx->s2q_maxrcbs; } ret = sm_rcb_open_snd(rcb); if (sm_is_err(ret)) { SSQ_DPRINTF((smioerr, "sev=ERROR, func=ss_send_rq, err1, sess=%p, i=%d, open=%u\n", ss_sess, i, Open_rqs)); goto err1; } r = st_mutex_lock(s2q_ctx->s2q_wr_mutex); if (r != 0) { SSQ_DPRINTF((smioerr, "sev=ERROR, func=ss_send_rq, st_mutex_lock=%d, errno=%d\n", r, errno)); goto err2; } ret = sm_rcb_snd(s2q_ctx->s2q_fd, rcb, s2q_ctx->s2q_ss_ctx->ssc_mod_tmo); r = st_mutex_unlock(s2q_ctx->s2q_wr_mutex); if (r != 0) { SSQ_DPRINTF((smioerr, "sev=ERROR, func=ss_send_rq, st_mutex_unlock=%d, errno=%d\n", r, errno)); } if (sm_is_err(ret)) { SSQ_DPRINTF((smioerr, "sev=ERROR, func=ss_send_rq, err2, sess=%p, i=%d, open=%u, ret=%m\n", ss_sess, i, Open_rqs, ret)); if (ret == sm_error_perm(SM_EM_RECCOM, EPIPE)) { S2Q_SET_IOERR(s2q_ctx); ret = sm_error_perm(SM_EM_SMTPS, EIO); } goto err2; } /* ** This call doesn't fail, but what should we do if it could?? ** The request has been sent... so: keep it or remove it? ** Let's keep it. */ ret = sm_rcb_close_snd(rcb); if (sm_is_err(ret)) goto error; return ret; err2: (void) sm_rcb_close_snd(rcb); err1: if (i < s2q_ctx->s2q_maxrcbs) { s2q_ctx->s2q_sess[i] = NULL; s2q_ctx->s2q_sids[i] = NULL; ss_sess->ssse_s2q_idx = SSSE_S2Q_IDX_NONE; SM_ASSERT(Open_rqs > 0); --Open_rqs; } error: return ret; } /* ** SS_FIND_SESS_RQ -- find session context based on session id ** ** Parameters: ** s2q_ctx -- S2Q context ** sid -- session id ** ** Returns: ** session context (NULL on failure) ** ** Side Effects: ** frees entry in array if found */ static ss_sess_P ss_find_sess_rq(s2q_ctx_P s2q_ctx, sessta_id_P sid) { uint i; ss_sess_P ss_sess; SM_REQUIRE(s2q_ctx != NULL); ss_sess = NULL; for (i = 0; i < s2q_ctx->s2q_maxrcbs; i++) { if (s2q_ctx->s2q_sess[i] != NULL && s2q_ctx->s2q_sids[i] != NULL && SESSTA_EQ(s2q_ctx->s2q_sids[i], sid)) { /* simple check whether session is ok */ ss_sess = s2q_ctx->s2q_sess[i]; if (ss_sess->ssse_rcb == NULL #if SS_SESS_CHECK || ss_sess->sm_magic != SM_SS_SESS_MAGIC #endif ) { SSQ_DPRINTF((smioerr, "sev=ERROR, func=ss_find_sess_rq, sess=%p, flags=%x\n", ss_sess, ss_sess->ssse_flags)); ss_sess = NULL; } else ss_sess->ssse_s2q_idx = SSSE_S2Q_IDX_NONE; s2q_ctx->s2q_sess[i] = NULL; s2q_ctx->s2q_sids[i] = NULL; SM_ASSERT(Open_rqs > 0); --Open_rqs; return ss_sess; } } SSQ_DPRINTF((smioerr, "sev=ERROR, func=ss_find_sess_rq, id=%s, i=%d, open=%u\n", sid, i, Open_rqs)); return NULL; } /* ** SS_CLR_SESS_RQ -- clear session request ** ** Parameters: ** s2q_ctx -- S2Q context ** ss_sess -- session context ** ** Returns: ** usual sm_error code ** ** Side Effects: ** frees entry in array if found */ sm_ret_T ss_clr_sess_rq(s2q_ctx_P s2q_ctx, ss_sess_P ss_sess) { sm_ret_T ret; int i; uint u; SM_REQUIRE(s2q_ctx != NULL); SM_IS_SS_SESS(ss_sess); i = ss_sess->ssse_s2q_idx; if (SSSE_S2Q_IDX_NONE == i) return SM_SUCCESS; if (i >= 0 && (u = (uint)i) < s2q_ctx->s2q_maxrcbs && s2q_ctx->s2q_sess[u] != NULL && s2q_ctx->s2q_sids[u] != NULL && SESSTA_EQ(s2q_ctx->s2q_sids[u], ss_sess->ssse_id)) { s2q_ctx->s2q_sess[u] = NULL; s2q_ctx->s2q_sids[u] = NULL; ss_sess->ssse_s2q_idx = SSSE_S2Q_IDX_NONE; SM_ASSERT(Open_rqs > 0); --Open_rqs; ret = SM_SUCCESS; } else ret = sm_error_perm(SM_EM_SMTPS, EINVAL); return ret; } /* ** SS_SET_CLTNAME -- set clientname based on ssse_cltrslv ** ** Parameters: ** ss_sess -- session context ** ** Returns: ** usual sm_error code ** ** Side Effects: ** sets ss_sess->ssse_cltname (unless an error occurs) */ static sm_ret_T ss_set_cltname(ss_sess_P ss_sess) { extern sm_cstr_P HostnameNoMatch, HostnameTempPTR, HostnameTempA, HostnameBogus; if (SM_RVRS_MATCH == ss_sess->ssse_cltrslv) return SM_SUCCESS; if (ss_sess->ssse_cltname != NULL) SM_CSTR_FREE(ss_sess->ssse_cltname); switch (ss_sess->ssse_cltrslv) { case SM_RVRS_MATCH: SM_ASSERT(ss_sess->ssse_cltrslv != SM_RVRS_MATCH); break; case SM_RVRS_NOMATCH: ss_sess->ssse_cltname = SM_CSTR_DUP(HostnameNoMatch); break; case SM_RVRS_TEMP_PTR: ss_sess->ssse_cltname = SM_CSTR_DUP(HostnameTempPTR); break; case SM_RVRS_TEMP_A: ss_sess->ssse_cltname = SM_CSTR_DUP(HostnameTempA); break; default: ss_sess->ssse_cltname = SM_CSTR_DUP(HostnameBogus); /* SM_ASSERT(false); ??? */ break; } return SM_SUCCESS; } /* ** SS_GET_CLTNAME -- get clientname from RCB (ss_sess->ssse_cltname) ** ** Parameters: ** ss_sess -- session context ** l -- length of client name to read ** ** Returns: ** usual sm_error code ** ** Side Effects: ** sets ss_sess->ssse_cltname (unless an error occurs) */ static sm_ret_T ss_get_cltname(ss_sess_P ss_sess, uint32_t l) { sm_ret_T ret; sm_cstr_P cltname; cltname = NULL; if (ss_sess->ssse_cltrslv != SM_RVRS_MATCH) { ret = sm_rcb_skip(ss_sess->ssse_rcb, l); return ret; } ret = sm_rcb_getncstr(ss_sess->ssse_rcb, &cltname, l); if (sm_is_err(ret)) return ret; SM_ASSERT(cltname != NULL); if (ss_sess->ssse_cltname != NULL) SM_CSTR_FREE(ss_sess->ssse_cltname); ss_sess->ssse_cltname = cltname; return SM_SUCCESS; } /* ** SM_W4Q2S_REPLY -- wait for reply from QMGR/SMAR/pmilter, decode it, ** and store the data in the session context. ** ** Parameters: ** ss_sess -- session context ** tmo -- timeout (seconds) ** s2q_ctx -- S2Q context ** ** Returns: ** <=0: usual sm_error code ** >0: (SMTP) reply code (including flags: SMAR_R_flag) ** ** Side Effects: ** may set ss_sess->ssse_wr (error text) */ sm_ret_T sm_w4q2s_reply(ss_sess_P ss_sess, uint tmo, s2q_ctx_P s2q_ctx) { int r; uint32_t v, l, rt; sm_ret_T ret, rv; ss_acc_P ss_acc; ss_ctx_P ss_ctx; SM_IS_SS_SESS(ss_sess); ss_acc = NULL; SSQ_DPRINTF((smioerr, "sev=DBG, func=sm_w4q2s_reply, sess=%p\n", ss_sess)); do { r = st_cond_timedwait(ss_sess->ssse_cond_rd, SEC2USEC(tmo)); SSQ_DPRINTF((smioerr, "sev=DBG, func=sm_w4q2s_reply, sess=%p, ret=%d, errno=%d\n", ss_sess, r, (0 == r) ? 0 : errno)); if (r != 0 && errno != EINTR) { S2Q_SET_IOERR(s2q_ctx); sm_rcb_close_n(ss_sess->ssse_rcb); return sm_error_perm(SM_EM_SMTPS, errno); } } while (r != 0); if (SSSE_S2Q_IDX_CLSD == ss_sess->ssse_s2q_idx) { ss_sess->ssse_s2q_idx = SSSE_S2Q_IDX_NONE; sm_rcb_close_n(ss_sess->ssse_rcb); return SM_IO_EOF; } ss_ctx = s2q_ctx->s2q_ss_ctx; /* rcb is now open for decoding... */ ret = sm_rcb_get3uint32(ss_sess->ssse_rcb, &l, &rt, &v); SSQ_DPRINTF((smioerr, "sev=DBG, func=sm_w4q2s_reply, where=first, l=%d, rt=%x, v=%d, ret=%m\n", l, rt, v, ret)); if (sm_is_err(ret) || l != 4) { /* complain? */ goto errdec; } /* ** Question: What should be returned to the caller?? ** The result of the map lookup (found/notfound/...) ** or the value (OK, REJECT)? ** How about the following: ** map lookup == found -> return RHS value ** otherwise return map lookup */ /* define protocol smar -> smtps! it's not clear how the values are supposed to be transferred, e.g., should RT_A2S_xyz_STM and RT_A2S_xyz_STR be separate? yes... protocol: RT_A2S_MAP_RES if v == SM_ACC_FOUND RT_A2S_xyz_ST then optional: RT_A2S_STATT: status text (only if MAP_FOUND) RT_A2S_RVRS_ST: status of reverse lookup RT_A2S_RVRS_NAME: client name change smar/access.c first! */ /* preserve result value */ rv = (sm_ret_T) v; if (rt == RT_A2S_MAP_RES && rv == SM_ACC_FOUND) { ret = sm_rcb_get3uint32(ss_sess->ssse_rcb, &l, &rt, &v); if (sm_is_err(ret) || l != 4 || (rt != RT_A2S_RCPT_ST && rt != RT_A2S_MAIL_ST && rt != RT_A2S_CLT_A_ST && rt != RT_A2S_CLT_N_ST #if MTA_USE_TLS && rt != RT_A2S_CERT_ST #endif #if SS_EHLO_ACCESS_CHK && rt != RT_A2S_EHLO_ST #endif )) { /* complain? */ goto errdec; } switch (rt) { case RT_A2S_CLT_A_ST: case RT_A2S_CLT_N_ST: ss_acc = &ss_sess->ssse_acc; break; #if SS_EHLO_ACCESS_CHK case RT_A2S_EHLO_ST: ss_acc = &ss_sess->ssse_ta->ssta_ehlo_acc; break; #endif case RT_A2S_MAIL_ST: ss_acc = &ss_sess->ssse_ta->ssta_mail_acc; break; case RT_A2S_RCPT_ST: ss_acc = &ss_sess->ssse_ta->ssta_rcpt_acc; break; } /* HACK: do not store "CONT" here */ if (ss_acc != NULL && v != SMTP_R_CONT) { ss_acc->ssa_map_result = rv; ss_acc->ssa_reply_code = v; } #if 0 SSQ_DPRINTF((smioerr, "sev=DBG, ss_sess=%s, ss_ta=%s, func=sm_w4q2s_reply, rt=%x, reply=%m\n" , ss_sess->ssse_id, ss_sess->ssse_ta->ssta_id, rt , v )); #endif /* 0 */ rv = v; /* return map lookup RHS to caller */ } else if (rt != RT_A2S_MAP_RES && rt != RT_Q2S_STAT && rt != RT_Q2S_STATV #if MTA_USE_PMILTER && rt != RT_M2S_RCODE #endif #if MTA_USE_TLS && rt != RT_A2S_CERT_ST #endif ) { /* complain? */ goto errdec; } else if (rt == RT_A2S_MAP_RES && rv == SM_ACC_NOTFOUND) { /* HACK: if no entry is found return "DUNNO" (CONT) */ rv = (sm_ret_T) SMTP_R_CONT; } else { rv = (sm_ret_T) v; if (rv < 0) goto errdec; /* Need to clear error text if there is a new error */ if (rv != SM_SUCCESS && rt == RT_Q2S_STAT) sm_str_clr(ss_sess->ssse_wr); } while (!SM_RCB_ISEOB(ss_sess->ssse_rcb)) { ret = sm_rcb_get2uint32(ss_sess->ssse_rcb, &l, &rt); switch (rt) { case RT_Q2S_STATT: case RT_A2S_STATT: case RT_M2S_STATT: sm_str_clr(ss_sess->ssse_wr); ret = sm_rcb_getstr(ss_sess->ssse_rcb, ss_sess->ssse_wr, l); if (sm_is_err(ret)) goto errdec; break; case RT_A2S_RVRS_ST: ret = sm_rcb_getuint32(ss_sess->ssse_rcb, &v); if (sm_is_err(ret)) goto errdec; ss_sess->ssse_cltrslv = v; ret = ss_set_cltname(ss_sess); break; case RT_A2S_RVRS_NAME: ret = ss_get_cltname(ss_sess, l); /* ignore error for now; client name won't be set */ break; #if MTA_USE_PMILTER case RT_M2S_MAIL_NEW: { ss_ta_P ss_ta; ss_ta = ss_sess->ssse_ta; if (l >= MAXADDRLEN) ret = sm_err_perm(SM_E_2BIG); else ret = sm_rcb_getnstr(ss_sess->ssse_rcb, &ss_ta->ssta_mail_new, l); if (sm_is_success(ret)) SSTA_SET_FLAG(ss_sess->ssse_ta, SSTA_FL_MAIL_MOD); } break; case RT_M2S_RCPT_ADD: case RT_M2S_RCPT_DEL: { rcpt_idx_T rcpt_idx; ushort type; rcpt_idx = 0; type = (RT_M2S_RCPT_ADD == rt) ? PM_RCPT_ADD : PM_RCPT_DEL; sm_str_clr(ss_sess->ssse_rd); ret = sm_rcb_getstr(ss_sess->ssse_rcb, ss_sess->ssse_rd, l); if (sm_is_success(ret) && RT_M2S_RCPT_DEL == rt) { ret = sm_rcb_get3uint32(ss_sess->ssse_rcb, &l, &rt, &v); if (sm_is_success(ret) && RT_M2S_RCPT_IDX == rt && 4 == l) rcpt_idx = v; /* else complain */ } if (sm_is_success(ret)) ret = ssr_rcpt_add_mod(ss_sess, type, rcpt_idx); if (sm_is_success(ret)) SSTA_SET_FLAG(ss_sess->ssse_ta, SSTA_FL_RCPT_MOD); } break; case RT_M2S_HM_T_P: { sm_hdrmod_P sm_hdrmod; ss_ta_P ss_ta; ss_ta = ss_sess->ssse_ta; sm_hdrmod = NULL; if (l != 8) goto errdec; ret = sm_rcb_get2uint32(ss_sess->ssse_rcb, &l, &v); if (sm_is_err(ret)) goto errdec; ret = sm_hdrmod_new(&ss_ta->ssta_hdrmodhd, true, &sm_hdrmod); if (sm_is_err(ret)) goto errdec; sm_hdrmod->sm_hm_type = l; sm_hdrmod->sm_hm_pos = v; if (SM_RCB_ISEOB(ss_sess->ssse_rcb)) break; ret = sm_rcb_peek2uint32(ss_sess->ssse_rcb, &l, &rt); if (sm_is_err(ret)) { sm_hdrmod_rm_last(&ss_ta->ssta_hdrmodhd); goto errdec; } if (rt != RT_M2S_HM_HDR) break; ret = sm_rcb_get2uint32(ss_sess->ssse_rcb, &l, &rt); if (sm_is_err(ret) || rt != RT_M2S_HM_HDR || l > SM_MAXHDRLEN) { sm_hdrmod_rm_last(&ss_ta->ssta_hdrmodhd); goto errdec; } ret = sm_rcb_getncstr(ss_sess->ssse_rcb, &sm_hdrmod->sm_hm_hdr, l); if (sm_is_err(ret)) { sm_hdrmod_rm_last(&ss_ta->ssta_hdrmodhd); goto errdec; } } break; case RT_M2S_MSG_PART: ret = ss_replacemsg(ss_sess, l); if (sm_is_err(ret)) goto errdec; break; # if MTA_USE_RSAD case RT_M2S_RCODES: { uint nreplies; size_t u; ss_ta_P ss_ta; ss_ta = ss_sess->ssse_ta; sspm_clrreplies(ss_ta); nreplies = l / 4; if (nreplies < 1) { ret = sm_err_perm(EINVAL); goto errdec; } ss_ta->ssta_nreplies = nreplies; u = nreplies * sizeof(*ss_ta->ssta_rcodes); if (u <= nreplies || u < sizeof(*ss_ta->ssta_rcodes)) { ret = sm_err_perm(SM_E_OVFLW_SC); goto errdec; } ss_ta->ssta_rcodes = (uint32_t *) sm_zalloc(u); if (NULL == ss_ta->ssta_rcodes) return sm_err_temp(ENOMEM); ret = sm_rcb_getauint32(ss_sess->ssse_rcb, nreplies, NULL, NULL, ss_ta->ssta_rcodes); } break; # endif /* MTA_USE_RSAD */ #endif /* MTA_USE_PMILTER */ case RT_A2S_MAP_RES_CNF: ret = sm_rcb_getuint32(ss_sess->ssse_rcb, &v); if (sm_is_err(ret)) goto errdec; ss_sess->ssse_maprescnf = v; break; case RT_A2S_RHS_CNF: sm_str_clr(ss_sess->ssse_str); ret = sm_rcb_getstr(ss_sess->ssse_rcb, ss_sess->ssse_str, l); if (sm_is_err(ret)) goto errdec; break; default: SSQ_DPRINTF((smioerr, "sev=DBG, func=sm_w4q2s_reply, record-type=unkown, l=%d, rt=%x, ret=%m\n", l, rt, ret)); goto errdec; } } ret = sm_rcb_close_dec(ss_sess->ssse_rcb); SSQ_DPRINTF((smioerr, "sev=DBG, func=sm_w4q2s_reply, where=return, l=%d, rt=%x, ret=%m, rv=%d\n", l, rt, ret, rv)); return rv; errdec: (void) sm_rcb_close_dec(ss_sess->ssse_rcb); SSQ_DPRINTF((smioerr, "sev=DBG, func=sm_w4q2s_reply, where=errdec, l=%d, rt=%x, ret=%m\n", l, rt, ret)); /* no error code? then some protocol error occurred... */ if (!sm_is_err(ret)) ret = SMTP_R_SSD; return ret; } #if MTA_USE_PMILTER /* ** SM_GET_MACLIST -- get list of macros from libpmilter ** ** Parameters: ** s2q -- s2q context ** rcb -- RCB ** ** Returns: ** usual sm_error code */ static sm_ret_T sm_get_maclist(s2q_ctx_P s2q_ctx, sm_rcb_P rcb) { uint32_t v, l, rt, macw; uint i; sm_ret_T ret; ss_ctx_P ss_ctx; if (SM_RCB_ISEOB(rcb)) return SM_SUCCESS; /* ** Protocol: ** (RT_M2S_MACW RT_M2S_MACW*)* */ ss_ctx = s2q_ctx->s2q_ss_ctx; ret = sm_rcb_get3uint32(rcb, &l, &rt, &v); if (sm_is_err(ret) || l != 4 || rt != RT_M2S_MACW) goto errdec; macw = v; while (!SM_RCB_ISEOB(rcb) && rt == RT_M2S_MACW) { if (macw >= PM_SMST_MAX) { sm_log_write(s2q_ctx->s2q_ss_ctx->ssc_lctx, SS_LCAT_COMM, SS_LMOD_COMM, SM_LOG_WARN, 6, "sev=WARN, func=sm_get_maclist, mac_stage=%u, max=%u, status=too_large" , macw, PM_SMST_MAX); break; } i = 0; while (!SM_RCB_ISEOB(rcb)) { ret = sm_rcb_get3uint32(rcb, &l, &rt, &v); if (sm_is_err(ret) || l != 4) goto errdec; if (rt != RT_M2S_MACM) { if (RT_M2S_MACW == rt) macw = v; break; } if (PM_MAX_MACROS == i) { /* log it once */ sm_log_write(s2q_ctx->s2q_ss_ctx->ssc_lctx, SS_LCAT_COMM, SS_LMOD_COMM, SM_LOG_WARN, 6, "sev=WARN, func=sm_get_maclist, mac_stage=%u, macros=%u, max=%u, status=too_many" , macw, i, PM_MAX_MACROS); } else if (i < PM_MAX_MACROS) ss_ctx->ssc_mac_names[macw][i] = v; ++i; } } return ret; errdec: if (!sm_is_err(ret)) ret = sm_error_perm(SM_EM_SMTPS, SM_E_PR_ERR); return ret; } #endif /* MTA_USE_PMILTER */ /* ** SM_FIRST_RCB_FROM_SRV -- receive first RCB from QMGR/SMAR ** fixme: this is in large parts the same as ss_rcb_from_srv(); ** maybe do some common code extraction? ** ** Parameters: ** s2q -- s2q context ** rcb -- RCB ** type -- type of reply ** ** Returns: ** usual sm_error code ** ** Side Effects: ** initialize SMTPS id counter */ static sm_ret_T sm_first_rcb_from_srv(s2q_ctx_P s2q_ctx, sm_rcb_P rcb, uint type) { uint32_t v, l, rt, tl; uint64_t ul; sm_ret_T ret; ret = sm_rcb_open_rcv(rcb); SSQ_DPRINTF((smioerr, "sev=DBG, func=sm_first_rcb_from_srv, s2q_ctx=%p, rcb_open_rcv=%r\n", s2q_ctx, ret)); if (sm_is_err(ret)) goto error; ret = sm_rcb_rcv(s2q_ctx->s2q_fd, rcb, 12, s2q_ctx->s2q_ss_ctx->ssc_mod_tmo); if (sm_is_err(ret)) { SSQ_DPRINTF((smioerr, "sev=DBG, func=sm_first_rcb_from_srv, s2q_ctx=%p, sm_rcb_rcv=%r\n", s2q_ctx, ret)); goto error; } ret = sm_rcb_close_rcv(rcb); if (sm_is_err(ret)) goto error; ret = sm_rcb_open_dec(rcb); if (sm_is_err(ret)) { /* COMPLAIN */ SSQ_DPRINTF((smioerr, "sev=ERROR, func=sm_first_rcb_from_srv, open_dec=%m\n", ret)); goto error; } /* total length of record */ ret = sm_rcb_getuint32(rcb, &tl); SSQ_DPRINTF((smioerr, "sev=DBG, func=sm_first_rcb_from_srv, s2q_ctx=%p, tl=%d, ret=%m\n", s2q_ctx, tl, ret)); if (sm_is_err(ret) || tl > QSS_RC_MAXSZ) goto errdec; /* protocol header: version */ ret = sm_rcb_get3uint32(rcb, &l, &rt, &v); SSQ_DPRINTF((smioerr, "sev=DBG, func=sm_first_rcb_from_srv, l=%d, rt=%x, prot=%d, ret=%m\n", l, rt, v, ret)); if (sm_is_err(ret) || l != 4 || rt != RT_PROT_VER || v != PROT_VER_RT) goto errdec; /* RT_Q2S_ID: int smtps-id */ ret = sm_rcb_get3uint32(rcb, &l, &rt, &v); SSQ_DPRINTF((smioerr, "sev=DBG, func=sm_first_rcb_from_srv, l=%d, rt=%x, smtps-id=%d, ret=%m\n", l, rt, v, ret)); if (sm_is_err(ret) || l != 4) goto errdec; if (RT_Q2S_NID == rt && s2q_ctx->s2q_ss_ctx->ssc_id < 0) s2q_ctx->s2q_ss_ctx->ssc_id = v; else if ((rt != RT_Q2S_ID && rt != RT_A2S_ID #if MTA_USE_PMILTER && rt != RT_M2S_ID #endif ) || v != s2q_ctx->s2q_ss_ctx->ssc_id) goto errdec; #if MTA_USE_PMILTER if (S2Q_T_PMILTER == type) { ret = sm_rcb_get3uint32(rcb, &l, &rt, &v); SSQ_DPRINTF((smioerr, "sev=DBG, func=sm_first_rcb_from_srv, l=%d, rt=%x, m2sprot=%x, ret=%m\n", l, rt, v, ret)); if (sm_is_err(ret) || l != 4 || rt != RT_M2S_PROT || v != SM_PMILTER_PROT) goto errdec; ret = sm_rcb_get3uint32(rcb, &l, &rt, &v); if (sm_is_err(ret) || l != 4 || rt != RT_M2S_CAP) goto errdec; if ((v & SM_SCAP_PM_ALL) != v) sm_log_write(s2q_ctx->s2q_ss_ctx->ssc_lctx, SS_LCAT_COMM, SS_LMOD_COMM, SM_LOG_WARN, 6, "sev=WARN, func=sm_first_rcb_from_srv, server_cap=%#x, libpmilter_cap=%#x" , SM_SCAP_PM_ALL, v); s2q_ctx->s2q_ss_ctx->ssc_pmcap = v & SM_SCAP_PM_ALL; ret = sm_rcb_get3uint32(rcb, &l, &rt, &v); if (sm_is_err(ret) || l != 4 || rt != RT_M2S_FCT) goto errdec; /* XXX use this value somehow .... */ ret = sm_rcb_get3uint32(rcb, &l, &rt, &v); if (sm_is_err(ret) || l != 4 || rt != RT_M2S_FEAT) goto errdec; /* XXX use this value somehow .... */ ret = sm_get_maclist(s2q_ctx, rcb); } else { #endif /* MTA_USE_PMILTER */ /* expect RT_Q2S_IIDC (64bit) */ ret = sm_rcb_get3uint64(rcb, &l, &rt, &ul); if (sm_is_err(ret)) { SSQ_DPRINTF((smioerr, "sev=DBG, func=sm_first_rcb_from_srv, l=%d, rt=%x, ret=%m\n", l, rt, ret)); goto errdec; } if (l == 8 && rt == RT_Q2S_IIDC) ret = ss_id_init(ul); else ret = sm_error_perm(SM_EM_SMTPS, SM_E_PR_ERR); #if MTA_USE_PMILTER } #endif /* fall through for closing rcb */ errdec: if (sm_is_err(ret)) (void) sm_rcb_close_dec(rcb); else ret = sm_rcb_close_dec(rcb); error: return ret; } /* ** S2Q_CLOSE -- close connection to server ** ** Parameters: ** s2q_ctx -- S2Q context ** ** Returns: ** usual sm_error code */ static sm_ret_T s2q_close(s2q_ctx_P s2q_ctx) { sm_ret_T ret; int r; uint u; ss_sess_P ss_sess; SM_REQUIRE(s2q_ctx != NULL); /* don't bother cleaning up if the process terminates */ if (SSC_IS_FLAG(s2q_ctx->s2q_ss_ctx, SSC_FL_TERMINATING)) return SM_SUCCESS; if (s2q_ctx->s2q_fd != INVALID_NETFD) { ret = un_st_socket_close(s2q_ctx->s2q_fd); if (sm_is_err(ret)) goto error; s2q_ctx->s2q_fd = INVALID_NETFD; } /* close all open connections. how to "notify" sessions of this??? */ for (u = 0; u < s2q_ctx->s2q_maxrcbs; u++) { if (s2q_ctx->s2q_sess[u] == NULL) continue; ss_sess = s2q_ctx->s2q_sess[u]; ss_sess->ssse_s2q_idx = SSSE_S2Q_IDX_CLSD; r = st_cond_signal(ss_sess->ssse_cond_rd); if (r != 0) { SSQ_DPRINTF((smioerr, "sev=ERROR, func=s2q_close, st_cond_signal=%d\n", r)); } /* sm_io_fprintf(smioerr, "sev=DBG, func=s2q_close, u=%u, r=%d\n", u, r); */ s2q_ctx->s2q_sess[u] = NULL; s2q_ctx->s2q_sids[u] = NULL; SM_ASSERT(Open_rqs > 0); --Open_rqs; } s2q_ctx->s2q_status = S2Q_ST_CLOSED; return SM_SUCCESS; error: return ret; } /* ** SM_RCB_FROM_SRV -- receive RCB from QMGR/SMAR/..., notify thread (session) ** This permanently runs as a thread. ** ** Parameters: ** arg -- s2q context ** ** Returns: ** NULL on termination */ static void * ss_rcb_from_srv(void *arg) { uint32_t v, l, rt, tl; int r; sm_ret_T ret; sm_rcb_P rcb, rcbt; s2q_ctx_P s2q_ctx; sessta_id_T ssse_id; ss_sess_P ss_sess; SM_REQUIRE(arg != NULL); s2q_ctx = (s2q_ctx_P) arg; rcb = sm_rcb_new(NULL, S2Q_RCB_SIZE, QSS_RC_MAXSZ); if (NULL == rcb) goto errnomem; /* check some "terminate now" variable? */ for (;;) { ret = sm_rcb_open_rcv(rcb); SSQ_DPRINTF((smioerr, "sev=DBG, func=ss_rcb_from_srv, s2q_ctx=%p sm_rcb_open_rcv=%m\n", s2q_ctx, ret)); if (sm_is_err(ret)) goto error; /* note: no timeout, this will just wait for an RCB */ ret = sm_rcb_rcv(s2q_ctx->s2q_fd, rcb, 12, (st_utime_t) -1); if (sm_is_err(ret)) { if (SM_IO_EOF == ret) { (void) s2q_close(s2q_ctx); goto error; } else if (!E_IS_TEMP(sm_error_value(ret))) { sm_log_write(s2q_ctx->s2q_ss_ctx->ssc_lctx, SS_LCAT_COMM, SS_LMOD_COMM, SM_LOG_ERR, 6, "sev=ERROR, func=ss_rcb_from_srv, sm_rcb_rcv=%m", ret); goto error; } ret = sm_rcb_close_rcv(rcb); if (sm_is_err(ret)) goto error; continue; } ret = sm_rcb_close_rcv(rcb); if (sm_is_err(ret)) goto error; /* ** And now? Check protocol version etc, extract session ID. ** We must have a common header which can be analyzed here ** at least such that we can find the session (transaction) ** ID (either we have to guarantee that transaction and ** session ID have the same format or at least the same ** comparison routine (streq) or we need to store both; ** the latter is ugly to say at least). ** ** Hence the format should be: ** RT_Q2S_ID: int smtps-id ** either RT_Q2S_SEID: str session-id ** or RT_Q2S_TAID: str transaction-id */ ret = sm_rcb_open_dec(rcb); if (sm_is_err(ret)) { /* COMPLAIN */ SSQ_DPRINTF((smioerr, "sev=ERROR, func=ss_rcb_from_srv, open_dec=%m\n", ret)); continue; } /* total length of record */ ret = sm_rcb_getuint32(rcb, &tl); SSQ_DPRINTF((smioerr, "sev=DBG, func=ss_rcb_from_srv, s2q_ctx=%p, tl=%d, ret=%m\n", s2q_ctx, tl, ret)); if (sm_is_err(ret) || tl > QSS_RC_MAXSZ) goto errdec; /* protocol header: version */ ret = sm_rcb_get3uint32(rcb, &l, &rt, &v); SSQ_DPRINTF((smioerr, "sev=DBG, func=ss_rcb_from_srv, l=%d, rt=%x, prot=%d, ret=%m\n", l, rt, v, ret)); if (sm_is_err(ret) || l != 4 || rt != RT_PROT_VER || v != PROT_VER_RT) goto errdec; /* RT_Q2S_ID: int smtps-id */ ret = sm_rcb_get3uint32(rcb, &l, &rt, &v); SSQ_DPRINTF((smioerr, "sev=DBG, func=ss_rcb_from_srv, l=%d, rt=%x, smtps-id=%d, ret=%m\n", l, rt, v, ret)); if (sm_is_err(ret) || l != 4 || (rt != RT_Q2S_ID && rt != RT_A2S_ID #if MTA_USE_PMILTER && rt != RT_M2S_ID #endif ) || v != s2q_ctx->s2q_ss_ctx->ssc_id) goto errdec; /* ** Here we can get generic status information, ** e.g., "slow down", "shut down", "return to normal". */ ret = sm_rcb_get2uint32(rcb, &l, &rt); if (sm_is_err(ret)) { SSQ_DPRINTF((smioerr, "sev=DBG, func=ss_rcb_from_srv, l=%d, rt=%x, ret=%m\n", l, rt, ret)); goto errdec; } if (l == 4 && rt == RT_Q2S_THRDS) { /* ... */ ret = sm_rcb_getuint32(rcb, &v); if (sm_is_err(ret)) goto errdec; SSQ_DPRINTF((smioerr, "sev=DBG, func=ss_rcb_from_srv, where=change Max_cur_threads, cur=%u, new=%u, max=%u\n", Max_cur_threads, v, s2q_ctx->s2q_ss_ctx->ssc_cnf.ss_cnf_max_threads)); if (v <= s2q_ctx->s2q_ss_ctx->ssc_cnf.ss_cnf_max_threads) Max_cur_threads = v; else Max_cur_threads = s2q_ctx->s2q_ss_ctx->ssc_cnf.ss_cnf_max_threads; ret = sm_rcb_close_dec(rcb); if (sm_is_err(ret)) { /* COMPLAIN */ } continue; } SSQ_DPRINTF((smioerr, "sev=DBG, func=ss_rcb_from_srv, where=sess-id, l=%d, rt=%x, ret=%m\n", l, rt, ret)); if (l != SMTP_STID_SIZE || (rt != RT_Q2S_SEID && rt != RT_Q2S_TAID && rt != RT_A2S_TAID #if MTA_USE_PMILTER && rt != RT_M2S_SEID /* XXX check protocol && rt != RT_M2S_TAID */ #endif /* MTA_USE_PMILTER */ )) goto errdec; /* session or transaction id */ ret = sm_rcb_getn(rcb, (uchar *) ssse_id, l); if (sm_is_err(ret)) goto errdec; ss_sess = ss_find_sess_rq(s2q_ctx, ssse_id); SSQ_DPRINTF((smioerr, "sev=DBG, func=ss_rcb_from_srv, find_sess=%p, id=%s\n", ss_sess, ssse_id)); if (NULL == ss_sess) { /* COMPLAIN */ SSQ_DPRINTF((smioerr, "sev=ERROR, func=ss_rcb_from_srv, ss_find_sess_rq=failed\n")); goto errdec; } /* ss_sess != NULL */ /* ** Just "exchange" our RCB and the one of the session ** Notice: this requires that there is only one outstanding ** request per session. Otherwise we may exchange the RCB ** while the session is using it (which can only happen if ** the RCB is used across a scheduling point). */ rcbt = ss_sess->ssse_rcb; ss_sess->ssse_rcb = rcb; rcb = rcbt; /* notify session */ r = st_cond_signal(ss_sess->ssse_cond_rd); if (r != 0) { /* COMPLAIN */ SSQ_DPRINTF((smioerr, "sev=ERROR, func=ss_rcb_from_srv, st_cond_signal=%d\n", r)); goto errdec; } /* allow for switch to another thread */ st_usleep(0); /* ** Do NOT close the RCB: we exchanged it and we should ** have now a "clean" (closed) one. */ continue; errdec: ret = sm_rcb_close_dec(rcb); if (sm_is_err(ret)) { /* COMPLAIN */ continue; } } errnomem: ret = sm_error_temp(SM_EM_SMTPS, ENOMEM); error: if (rcb != NULL) sm_rcb_free(rcb); return NULL; } /* ** S2Q_CONNECT -- connect to server ** ** Parameters: ** s2q_ctx -- S2Q context ** sockspec -- socket to use for connection ** wait4srv -- time to wait for server (s) ** max_threads -- maximum number of threads ** smtps_id -- id of SMTPS ** srv_type -- server type ** flags -- flags ** ** Returns: ** usual sm_error code */ static sm_ret_T s2q_connect(s2q_ctx_P s2q_ctx, sockspec_P sockspec, uint wait4srv, uint max_threads, int smtps_id, uint srv_type, uint32_t flags) { sm_ret_T ret; sm_rcb_P rcb; time_t time1; SM_REQUIRE(s2q_ctx != NULL); rcb = NULL; time1 = st_time(); for (;;) { ret = st_sock_connect(sockspec, SEC2USEC(wait4srv), &s2q_ctx->s2q_fd); if (sm_is_err(ret) && time1 + wait4srv > st_time()) st_sleep(1); else break; } if (sm_is_err(ret)) { SSQ_DPRINTF((smioerr, "sev=ERROR, func=s2q_connect, socket=%s, un_st_client_connect=%m\n", S2Q_SOCKNAME, ret)); goto error; } /* send out RCB? */ if (srv_type == S2Q_T_QMGR || srv_type == S2Q_T_SMAR #if MTA_USE_PMILTER || srv_type == S2Q_T_PMILTER #endif ) { rcb = sm_rcb_new(NULL, S2Q_RCB_SIZE, QSS_RC_MAXSZ); if (NULL == rcb) goto errnomem; if (S2Q_T_QMGR == srv_type) { ret = sm_rcb_putrec(rcb, RCB_PUTR_DFLT, 0, -1, SM_RCBV_INT, RT_PROT_VER, PROT_VER_RT, SM_RCBV_INT, (smtps_id < 0) ? RT_S2Q_QID : RT_S2Q_NID, smtps_id, SM_RCBV_INT, RT_S2Q_MAXTHRDS, max_threads, SM_RCBV_END); } else if (S2Q_T_SMAR == srv_type) { ret = sm_rcb_putrec(rcb, RCB_PUTR_DFLT, 0, -1, SM_RCBV_INT, RT_PROT_VER, PROT_VER_RT, SM_RCBV_INT, RT_S2A_NID, smtps_id, SM_RCBV_INT, RT_S2A_MAXTHRDS, max_threads, SM_RCBV_INT, RT_S2A_FLAGS, flags, SM_RCBV_END); } #if MTA_USE_PMILTER else if (S2Q_T_PMILTER == srv_type) { uint32_t u32; u32 = SM_SCAP_PM_ALL #if !MTA_USE_TLS & ~SM_SCAP_PM_STTLS #endif #if !MTA_USE_SASL & ~SM_SCAP_PM_AUTH #endif ; if (!SSC_IS_CFLAG(s2q_ctx->s2q_ss_ctx, SSC_CFL_RSAD)) u32 &= ~SM_SCAP_PM_RSAD; ret = sm_rcb_putrec(rcb, RCB_PUTR_DFLT, 0, -1, SM_RCBV_INT, RT_PROT_VER, PROT_VER_RT, SM_RCBV_INT, RT_S2M_NID, smtps_id, SM_RCBV_INT, RT_S2M_PROT, SM_PMILTER_PROT, SM_RCBV_INT, RT_S2M_MAXTHRDS, max_threads, SM_RCBV_INT, RT_S2M_CAP, u32, #if SM_SFCT_PM_ALL != 0 SM_RCBV_INT, RT_S2M_FCT, SM_SFCT_PM_ALL, #endif #if SM_SFEAT_PM_ALL != 0 SM_RCBV_INT, RT_S2M_FEAT, SM_SFEAT_PM_ALL, #endif #if SM_SMISC_PM_ALL != 0 SM_RCBV_INT, RT_S2M_MISC, SM_SMISC_PM_ALL, #endif SM_RCBV_END); } #endif /* MTA_USE_PMILTER */ if (sm_is_err(ret)) goto error; ret = ss_send_rq(NULL, NULL, s2q_ctx, rcb, false); if (sm_is_err(ret)) { SSQ_DPRINTF((smioerr, "sev=DBG, func=s2q_init, socket=%s, ss_send_rq=%m\n", S2Q_SOCKNAME, ret)); goto error; } if (srv_type == S2Q_T_QMGR #if MTA_USE_PMILTER || srv_type == S2Q_T_PMILTER #endif ) { ret = sm_first_rcb_from_srv(s2q_ctx, rcb, srv_type); if (sm_is_err(ret)) { SSQ_DPRINTF((smioerr, "sev=DBG, func=s2q_init, socket=%s, sm_first_rcb_from_srv=%m\n", S2Q_SOCKNAME, ret)); goto error; } } SM_RCB_FREE(rcb); } return SM_SUCCESS; errnomem: ret = sm_error_temp(SM_EM_SMTPS, ENOMEM); error: SM_RCB_FREE(rcb); return ret; } /* ** SM_S2Q_CREATE -- create S2Q context ** ** Parameters: ** ps2q_ctx -- (pointer to) S2Q context (output) ** ss_ctx -- SMTP Server context ** sockspec -- specification of socket to use for connection ** maxrcbs -- maximum number of outstanding requests (RCBs) ** ** Returns: ** usual sm_error code */ sm_ret_T sm_s2q_create(s2q_ctx_P *ps2q_ctx, ss_ctx_P ss_ctx, uint maxrcbs) { sm_ret_T ret; size_t n; s2q_ctx_P s2q_ctx; SM_REQUIRE(ps2q_ctx != NULL); s2q_ctx = (s2q_ctx_P) sm_zalloc(sizeof(*s2q_ctx)); if (NULL == s2q_ctx) goto errnomem; s2q_ctx->s2q_ss_ctx = ss_ctx; s2q_ctx->s2q_fd = INVALID_NETFD; s2q_ctx->s2q_wr_mutex = st_mutex_new(); if (NULL == s2q_ctx->s2q_wr_mutex) { ret = sm_error_temp(SM_EM_SMTPS, errno); goto error; } s2q_ctx->s2q_maxrcbs = maxrcbs; s2q_ctx->s2q_currcbs = 0; n = maxrcbs * sizeof(*s2q_ctx->s2q_sids); s2q_ctx->s2q_sids = (sessta_id_P *) sm_zalloc(n); if (NULL == s2q_ctx->s2q_sids) goto errnomem; n = maxrcbs * sizeof(*s2q_ctx->s2q_sess); s2q_ctx->s2q_sess = (ss_sess_P *) sm_zalloc(n); if (NULL == s2q_ctx->s2q_sess) goto errnomem; *ps2q_ctx = s2q_ctx; return SM_SUCCESS; errnomem: ret = sm_error_temp(SM_EM_SMTPS, ENOMEM); error: if (s2q_ctx != NULL) { SM_FREE(s2q_ctx->s2q_sids); SM_FREE(s2q_ctx->s2q_sess); if (s2q_ctx->s2q_wr_mutex != NULL) { st_mutex_destroy(s2q_ctx->s2q_wr_mutex); s2q_ctx->s2q_wr_mutex = NULL; } SM_FREE_SIZE(s2q_ctx, sizeof(*s2q_ctx)); } return ret; } /* ** SM_S2Q_OPEN -- open S2Q connection ** ** Parameters: ** s2q_ctx -- S2Q context ** sockspec -- specification of socket to use for connection ** wait4srv -- time to wait for server (s) ** max_threads -- maximum number of threads ** srv_type -- server type ** flags -- flags ** ** Returns: ** usual sm_error code */ sm_ret_T sm_s2q_open(s2q_ctx_P s2q_ctx, sockspec_P sockspec, uint wait4srv, uint max_threads, uint srv_type, uint32_t flags) { sm_ret_T ret; st_thread_t rdtsk; SM_REQUIRE(s2q_ctx != NULL); ret = s2q_connect(s2q_ctx, sockspec, wait4srv, max_threads, s2q_ctx->s2q_ss_ctx->ssc_id, srv_type, flags); if (sm_is_err(ret)) goto error; rdtsk = st_thread_create(ss_rcb_from_srv, (void *) s2q_ctx, 0, 0); if (NULL == rdtsk) { ret = sm_error_temp(SM_EM_SMTPS, errno); goto error; } if (!S2Q_IS_STATELESS(srv_type)) ++s2q_ctx->s2q_q_id; s2q_ctx->s2q_srv_type = srv_type; s2q_ctx->s2q_status = S2Q_ST_OK; SSQ_DPRINTF((smioerr, "sev=DBG, func=s2q_open, sockname=%s, ctx=%p\n", S2Q_SOCKNAME, s2q_ctx)); return SM_SUCCESS; error: return ret; } /* ** SM_S2Q_INIT -- initialize S2Q ** ** Parameters: ** ps2q_ctx -- (pointer to) S2Q context (output) ** ss_ctx -- SMTP Server context ** sockspec -- specification of socket to use for connection ** wait4srv -- time to wait for server (s) ** max_threads -- maximum number of threads ** smtps_id -- id of SMTPS ** maxrcbs -- maximum number of outstanding requests (RCBs) ** srv_type -- server type ** flags -- flags ** ** Returns: ** usual sm_error code */ sm_ret_T sm_s2q_init(s2q_ctx_P *ps2q_ctx, ss_ctx_P ss_ctx, sockspec_P sockspec, uint wait4srv, uint max_threads, int smtps_id, uint maxrcbs, uint srv_type, uint32_t flags) { sm_ret_T ret; st_thread_t rdtsk; size_t n; s2q_ctx_P s2q_ctx; SM_REQUIRE(ps2q_ctx != NULL); s2q_ctx = (s2q_ctx_P) sm_zalloc(sizeof(*s2q_ctx)); if (NULL == s2q_ctx) goto errnomem; s2q_ctx->s2q_ss_ctx = ss_ctx; s2q_ctx->s2q_fd = INVALID_NETFD; s2q_ctx->s2q_wr_mutex = st_mutex_new(); if (NULL == s2q_ctx->s2q_wr_mutex) { ret = sm_error_temp(SM_EM_SMTPS, errno); goto error; } s2q_ctx->s2q_maxrcbs = maxrcbs; s2q_ctx->s2q_currcbs = 0; n = maxrcbs * sizeof(*s2q_ctx->s2q_sids); s2q_ctx->s2q_sids = (sessta_id_P *) sm_zalloc(n); if (NULL == s2q_ctx->s2q_sids) goto errnomem; n = maxrcbs * sizeof(*s2q_ctx->s2q_sess); s2q_ctx->s2q_sess = (ss_sess_P *) sm_zalloc(n); if (NULL == s2q_ctx->s2q_sess) goto errnomem; ret = s2q_connect(s2q_ctx, sockspec, wait4srv, max_threads, smtps_id, srv_type, flags); if (sm_is_err(ret)) goto error; rdtsk = st_thread_create(ss_rcb_from_srv, (void *) s2q_ctx, 0, 0); if (NULL == rdtsk) { ret = sm_error_temp(SM_EM_SMTPS, errno); goto error; } s2q_ctx->s2q_q_id = S2Q_IS_STATELESS(srv_type) ? S2Q_ID_NONE : 1; s2q_ctx->s2q_srv_type = srv_type; s2q_ctx->s2q_status = S2Q_ST_OK; SSQ_DPRINTF((smioerr, "sev=DBG, func=s2q_init, sockname=%s, ctx=%p\n", S2Q_SOCKNAME, s2q_ctx)); *ps2q_ctx = s2q_ctx; return SM_SUCCESS; errnomem: ret = sm_error_temp(SM_EM_SMTPS, ENOMEM); error: if (s2q_ctx != NULL) { SM_FREE(s2q_ctx->s2q_sids); SM_FREE(s2q_ctx->s2q_sess); if (s2q_ctx->s2q_wr_mutex != NULL) { st_mutex_destroy(s2q_ctx->s2q_wr_mutex); s2q_ctx->s2q_wr_mutex = NULL; } SM_FREE(s2q_ctx); } return ret; } /* ** SM_S2Q_INIT_U -- initialize S2Q ** ** Parameters: ** ps2q_ctx -- (pointer to) S2Q context (output) ** ss_ctx -- SMTP Server context ** sockname -- name of socket to use for connection ** wait4srv -- time to wait for server (s) ** max_threads -- maximum number of threads ** smtps_id -- id of SMTPS ** maxrcbs -- maximum number of outstanding requests (RCBs) ** srv_type -- server type ** flags -- flags ** ** Returns: ** usual sm_error code */ sm_ret_T sm_s2q_init_u(s2q_ctx_P *ps2q_ctx, ss_ctx_P ss_ctx, const char *sockname, uint wait4srv, uint max_threads, int smtps_id, uint maxrcbs, uint srv_type, uint32_t flags) { sockspec_T sockspec; sockspec.sckspc_type = SOCK_TYPE_UNIX; sockspec.sock_unix.unixsckspc_path = (char *)sockname; return sm_s2q_init(ps2q_ctx, ss_ctx, &sockspec, wait4srv, max_threads, smtps_id, maxrcbs, srv_type, flags); } /* ** SM_S2Q_STOP -- stop S2Q ** ** Parameters: ** s2q_ctx -- S2Q context ** ** Returns: ** usual sm_error code */ sm_ret_T sm_s2q_stop(s2q_ctx_P s2q_ctx) { sm_ret_T ret; SM_REQUIRE(s2q_ctx != NULL); ret = s2q_close(s2q_ctx); if (s2q_ctx->s2q_wr_mutex != NULL) { st_mutex_destroy(s2q_ctx->s2q_wr_mutex); s2q_ctx->s2q_wr_mutex = NULL; } SM_FREE(s2q_ctx->s2q_sids); SM_FREE(s2q_ctx->s2q_sess); /* free s2q_ctx? only if allocated above! */ SM_FREE_SIZE(s2q_ctx, sizeof(*s2q_ctx)); return ret; } /* need functions that are the counterpart for: qmgr_smtps_open(IN smtps-info, OUT status): RT_S2Q_NID: int smtps-id XXX should this also transfer an initial status? qmgr_session_open(IN connection-info, IN session-id, OUT status): RT_S2Q_ID: int smtps-id RT_S2Q_NSEID: str session-id RT_S2Q_CLTIPV4/RT_S2Q_CLTIPV6: str client IP address qmgr_session_status(IN connection-info, IN session-id, IN session-status, OUT status): RT_S2Q_ID: int smtps-id RT_S2Q_SEID: str session-id XXX: ? new: (2007-03-11) qmgr_one_trans(IN trans, OUT status): RT_S2Q_ID: int smtps-id RT_S2Q_1TAID: str transaction-id RT_S2Q_MAIL: str mail from RT_S2Q_NRCPTS: int # of rcpts (not yet) RT_S2Q_RCPT_IDX: int rcpt idx RT_S2Q_RCPT: str rcpt to RT_S2Q_CDBID: str cdb-id see docs about this optional: (and possibly multiple times) RT_S2Q_RCPT_ADD/RT_S2Q_RCPT_DEL str rcpt_pa RT_S2Q_RCPT_IDX: int rcpt_idx, optional: (and possibly multiple times) RT_S2Q_HM_T_P: int2 sm_hdrmod->sm_hm_type, sm_hdrmod->sm_hm_pos, RT_S2Q_HM_HDR: cstr sm_hdrmod->sm_hm_hdr [optional] qmgr_session_close(IN session-id, OUT status): RT_S2Q_ID: int smtps-id RT_S2Q_CSEID: str session-id qmgr_smtps_close(IN smtps-info, OUT status): RT_S2Q_CLID: int smtps-id */ /* ** SM_S2Q_NSEID -- new session ** ** Parameters: ** ss_sess -- session context ** s2q_ctx -- S2Q context ** sid -- session id ** ** Returns: ** usual sm_error code */ sm_ret_T sm_s2q_nseid(ss_sess_P ss_sess, s2q_ctx_P s2q_ctx, sessta_id_P sid) { sm_ret_T ret; sm_rcb_P rcb; SM_REQUIRE(s2q_ctx != NULL); SM_IS_SS_SESS(ss_sess); SM_REQUIRE(S2Q_ID_NONE == ss_sess->ssse_s2q_id[SS_COMM_QMGR]); ss_sess->ssse_s2q_id[SS_COMM_QMGR] = s2q_ctx->s2q_q_id; rcb = ss_sess->ssse_rcb; ret = sm_rcb_putrec(rcb, RCB_PUTR_DFLT, 0, -1, SM_RCBV_INT, RT_PROT_VER, PROT_VER_RT, SM_RCBV_INT, RT_S2Q_ID, s2q_ctx->s2q_ss_ctx->ssc_id, SM_RCBV_BUF, RT_S2Q_NSEID, sid, SMTP_STID_SIZE, SM_RCBV_INT, RT_S2Q_CLTIPV4, ss_sess->ssse_client.s_addr, SM_RCBV_END); if (sm_is_err(ret)) goto error; ret = ss_send_rq(ss_sess, ss_sess->ssse_id, s2q_ctx, rcb, true); if (sm_is_err(ret)) goto error; return SM_SUCCESS; error: return ret; } /* ** SM_S2Q_CSEID -- close session ** ** Parameters: ** ss_sess -- session context ** s2q_ctx -- S2Q context ** sid -- session id ** ** Returns: ** usual sm_error code */ sm_ret_T sm_s2q_cseid(ss_sess_P ss_sess, s2q_ctx_P s2q_ctx, sessta_id_P sid) { sm_ret_T ret; sm_rcb_P rcb; SM_REQUIRE(s2q_ctx != NULL); SM_IS_SS_SESS(ss_sess); if (!SSSE_IS_FLAG(ss_sess, SSSE_FL_CSEID)) return SM_SUCCESS; SSSE_CLR_FLAG(ss_sess, SSSE_FL_CSEID); S2Q_CHK_CONN_R(ss_sess, s2q_ctx); rcb = ss_sess->ssse_rcb; ret = sm_rcb_putrec(rcb, RCB_PUTR_DFLT, 0, -1, SM_RCBV_INT, RT_PROT_VER, PROT_VER_RT, SM_RCBV_INT, RT_S2Q_ID, s2q_ctx->s2q_ss_ctx->ssc_id, SM_RCBV_BUF, RT_S2Q_CSEID, sid, SMTP_STID_SIZE, SM_RCBV_END); if (sm_is_err(ret)) goto error; ret = ss_send_rq(ss_sess, ss_sess->ssse_id, s2q_ctx, rcb, false); if (sm_is_err(ret)) goto error; return SM_SUCCESS; error: return ret; } /* ** SM_S2Q_HDRMODS -- send header modifications to QMGR and free the list ** ** Parameters: ** ss_sess -- session context ** s2q_ctx -- S2Q context ** rcb -- RCB to fill in ** ** Returns: ** usual sm_error code */ static sm_ret_T sm_s2q_hdrmods(ss_sess_P ss_sess, s2q_ctx_P s2q_ctx, sm_rcb_P rcb) { ss_ta_P ss_ta; ss_ta = ss_sess->ssse_ta; SM_IS_SS_TA(ss_ta); if (HDRMODL_EMPTY(ss_ta->ssta_hdrmodhd)) return SM_SUCCESS; sm_hdrmodl_wr(ss_ta->ssta_hdrmodhd, rcb, RT_S2Q_HM_T_P, RT_S2Q_HM_HDR); sm_hdrmodl_free(&ss_ta->ssta_hdrmodhd); return SM_SUCCESS; } /* ** SM_S2Q_1TAID -- one complete transaction ** ** Parameters: ** ss_sess -- session context ** s2q_ctx -- S2Q context ** ** Returns: ** usual sm_error code */ sm_ret_T sm_s2q_1taid(ss_sess_P ss_sess, s2q_ctx_P s2q_ctx) { sm_ret_T ret; uint ui; sm_rcb_P rcb; ss_ta_P ss_ta; ss_rcpts_P ss_rcpt_hd; ss_rcpt_P ss_rcpt; SM_REQUIRE(s2q_ctx != NULL); SM_IS_SS_SESS(ss_sess); S2Q_CHK_CONN_R(ss_sess, s2q_ctx); ss_ta = ss_sess->ssse_ta; SM_IS_SS_TA(ss_ta); rcb = ss_sess->ssse_rcb; ret = sm_rcb_putrec(rcb, RCB_PUTR_FIRST|RCB_PUTR_OPEN, 0, -1, SM_RCBV_INT, RT_PROT_VER, PROT_VER_RT, SM_RCBV_INT, RT_S2Q_ID, s2q_ctx->s2q_ss_ctx->ssc_id, SM_RCBV_BUF, RT_S2Q_1TAID, ss_ta->ssta_id, SMTP_STID_SIZE, SM_RCBV_STR, RT_S2Q_MAIL, #if MTA_USE_PMILTER SSTA_IS_FLAG(ss_ta, SSTA_FL_MAIL_MOD) ? ss_ta->ssta_mail_new : #endif ss_ta->ssta_mail->ssm_pa, SM_RCBV_INT, SSTA_IS_FLAG(ss_ta, SSTA_FL_XVERP) ? RT_S2Q_MAIL_FL : RT_NOSEND, SM_TA_FL_VERP, SM_RCBV_END); /* * TODO: What's the number of rcpts? ssta_rcpts_ok? */ ss_rcpt_hd = &ss_ta->ssta_rcpts; for (ss_rcpt = SS_RCPTS_FIRST(ss_rcpt_hd); ss_rcpt != SS_RCPTS_END(ss_rcpt_hd) && sm_is_success(ret); ss_rcpt = SS_RCPTS_NEXT(ss_rcpt), ui++) { #if MTA_USE_PMILTER && MTA_USE_RSAD /* send only accepted RCPTs */ if (ss_rcpt->ssr_rcode != SMTP_OK) continue; #endif sm_str_clr(ss_sess->ssse_wr); ret = t2821_str(&ss_rcpt->ssr_a2821, ss_sess->ssse_wr, 0); if (sm_is_err(ret)) { /* todo: log an error */ continue; } ret = sm_rcb_putv(rcb, 0, SM_RCBV_INT, RT_S2Q_RCPT_IDX, ss_rcpt->ssr_idx, SM_RCBV_STR, RT_S2Q_RCPT, ss_sess->ssse_wr, SM_RCBV_END); sm_str_clr(ss_sess->ssse_wr); } if (sm_is_err(ret)) goto error; #if MTA_USE_PMILTER if (ss_ta->ssta_cdb_id != NULL) { ret = sm_rcb_putv(rcb, 0, SM_RCBV_CSTR, RT_S2Q_CDBID, ss_ta->ssta_cdb_id, SM_RCBV_OFF, RT_S2Q_SIZE_B, ss_ta->ssta_msg_sz_b, SM_RCBV_END); } else #endif /* MTA_USE_PMILTER */ { ret = sm_rcb_putv(rcb, 0, SM_RCBV_BUF, RT_S2Q_CDBID, ss_ta->ssta_id, SMTP_STID_SIZE, SM_RCBV_OFF, RT_S2Q_SIZE_B, ss_ta->ssta_msg_sz_b, SM_RCBV_END); } if (sm_is_err(ret)) goto error; ret = sm_s2q_hdrmods(ss_sess, s2q_ctx, rcb); if (sm_is_err(ret)) goto error; ret = sm_rcb_close_enc(rcb); if (sm_is_err(ret)) goto error; ret = ss_send_rq(ss_sess, ss_ta->ssta_id, s2q_ctx, rcb, true); if (sm_is_err(ret)) goto error; return SM_SUCCESS; error: return ret; }