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


syntax highlighted by Code2HTML, v. 0.9.1