/*
 * Copyright (c) 2002-2006 Sendmail, Inc. and its suppliers.
 *	All rights reserved.
 *
 * By using this file, you agree to the terms and conditions set
 * forth in the LICENSE file which can be found at the top level of
 * the sendmail distribution.
 */

#include "sm/generic.h"
SM_RCSID("@(#)$Id: qm_fr_ss.c,v 1.206 2007/08/18 16:53:00 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 "sm/ssocc.h"
#include "sm/reccom.h"
#include "qmgr.h"
#include "qm_throttle.h"
#include "log.h"

/*
**  QM_SS_COMP_CONTROL -- (un)throttle SMTPS based on a component as needed
**	(currently the only component checked is SMAR)
**
**	Parameters:
**		qss_ctx -- QMGR/SMTPS context
**		unthrottle -- unthrottle only
**
**	Returns:
**		usual sm_error code
**
**	Called by: qm_fr_ss_react()
**	Calls: qss_control()
**
**	Locking:
**		qss_ctx should be "locked", i.e., under control of the caller.
**		XXX qmgr_ctx should be locked here.
*/

static sm_ret_T
qm_ss_comp_control(qss_ctx_P qss_ctx, bool unthrottle)
{
	sm_ret_T ret;
	qmgr_ctx_P qmgr_ctx;

	SM_IS_QSS_CTX(qss_ctx);
	qmgr_ctx = qss_ctx->qss_qmgr_ctx;
	SM_IS_QMGR_CTX(qmgr_ctx);
	ret = SM_SUCCESS;

	/* check whether some component (SMAR) is MIA... */
	QM_LEV_DPRINTFC(QDC_S2Q, 3, (QM_DEBFP, "func=qm_ss_comp_control, sflags=%#x, usage[%d]=%d\n",
qmgr_ctx->qmgr_sflags, QMGR_RFL_AR_I, qmgr_ctx->qmgr_usage[QMGR_RFL_AR_I]));
	if (!unthrottle && QMGR_IS_SFLAG(qmgr_ctx, QMGR_SFL_AR) &&
	    qmgr_ctx->qmgr_usage[QMGR_RFL_AR_I] != QMGR_R_USE_FULL)
	{
		qmgr_ctx->qmgr_usage[QMGR_RFL_AR_I] = QMGR_R_USE_FULL;
		ret = qss_control(qss_ctx, QMGR_THROTTLE, QMGR_R_USE_FULL,
			QMGR_RFL_AR_I, THR_LOCK_UNLOCK);
		return ret;
	}

	/* check whether component (SMAR) is back */
	if ((!QMGR_IS_SFLAG(qmgr_ctx, QMGR_SFL_AR) ||
	     qmgr_ctx->qmgr_ar_tsk != NULL)
	    && qmgr_ctx->qmgr_usage[QMGR_RFL_AR_I] != QMGR_R_USE_NONE)
	{
		QMGR_CLR_SFLAG(qmgr_ctx, QMGR_SFL_AR);
		qmgr_ctx->qmgr_usage[QMGR_RFL_AR_I] = QMGR_R_USE_NONE;
		ret = qss_control(qss_ctx, QMGR_UN_THROTTLE, QMGR_R_USE_NONE,
			QMGR_RFL_AR_I, THR_LOCK_UNLOCK);
	}
	return ret;
}

/*
**  QM_SS_FIND -- Find SMTPS id
**
**	Parameters:
**		qmgr_ctx -- QMGR context
**		qss_ctx -- QMGR/SMTPS context
**
**	Returns:
**		>=0: index
**		<0: usual sm_error code
**
**	Last code review:
*/

static sm_ret_T
qm_ss_find(qmgr_ctx_P qmgr_ctx, qss_ctx_P qss_ctx)
{
	int i, r;
	uint8_t	j;
	sm_ret_T ret;
	qss_ctx_P qss2_ctx;

	r = pthread_mutex_lock(&qmgr_ctx->qmgr_mutex);
	SM_LOCK_OK(r);
	if (r != 0) {
		sm_log_write(qmgr_ctx->qmgr_lctx,
			QM_LCAT_SMTPS, QM_LMOD_FROM_SMTPS,
			SM_LOG_CRIT, 4,
			"sev=CRIT, func=qm_ss_find, lock=%d",
			r);
		return sm_error_temp(SM_EM_Q_SS2Q, r);
	}

	ret = sm_error_perm(SM_EM_Q_SS2Q, SM_E_NOTFOUND);

	/* search for ss ctx */
	for (j = 1, i = 0; i < QM_N_SS_GLI(qmgr_ctx); i++, j *= 2) {
		if ((qmgr_ctx->qmgr_ss_li.qm_gli_used & j) != 0) {
			qss2_ctx = qmgr_li_ss(qmgr_ctx, i);
			if (qss2_ctx != qss_ctx && qss2_ctx->qss_id == qss_ctx->qss_id) {
				ret = i;
				break;
			}
		}
	}
	r = pthread_mutex_unlock(&qmgr_ctx->qmgr_mutex);
	SM_ASSERT(0 == r);
	return ret;
}

/*
**  QM_SS_FINDFREEID -- Find free SMTPS id
**
**	Parameters:
**		qmgr_ctx -- QMGR context
**		qss_ctx -- QMGR/SMTPS context
**
**	Returns:
**		usual sm_error code
**
**	Last code review:
*/

static sm_ret_T
qm_ss_findfreeid(qmgr_ctx_P qmgr_ctx, qss_ctx_P qss_ctx)
{
	int i, r, smtps_id;
	uint8_t	j;
	sm_ret_T ret;
	qss_ctx_P qss2_ctx;

	r = pthread_mutex_lock(&qmgr_ctx->qmgr_mutex);
	SM_LOCK_OK(r);
	if (r != 0) {
		sm_log_write(qmgr_ctx->qmgr_lctx,
			QM_LCAT_SMTPS, QM_LMOD_FROM_SMTPS,
			SM_LOG_CRIT, 4,
			"sev=CRIT, func=qm_ss_findfreeid, lock=%d",
			r);
		return sm_error_temp(SM_EM_Q_SS2Q, r);
	}

	ret = SM_SUCCESS;
	smtps_id = 0;

	/*
	 * find a free id; current "hack": biggest id +1
	 * note: this needs to be done better because the id is only
	 * allowed to SMTPS_MAX_SMTPS_ID (255)
	 * if smtps always ask qmgr for an id, then we could simply use the
	 * index in this array (could we?)
	 * we could use a bitfield to indicate which ids are used.
	 */
	for (j = 1, i = 0; i < QM_N_SS_GLI(qmgr_ctx); i++, j *= 2) {
		if ((qmgr_ctx->qmgr_ss_li.qm_gli_used & j) != 0) {
			qss2_ctx = qmgr_li_ss(qmgr_ctx, i);
			if (qss2_ctx->qss_id >= smtps_id)
				smtps_id = qss2_ctx->qss_id + 1;
		}
	}
	r = pthread_mutex_unlock(&qmgr_ctx->qmgr_mutex);
	SM_ASSERT(0 == r);
	qss_ctx->qss_id = smtps_id;
	return ret;
}

/*
**  QM_RD_HDRMODS -- read header modification list
**
**	Parameters:
**		qmgr_ctx -- QMGR context
**		qss_ta -- QMGR/SMTPS transaction
**		rcb -- RCB from which to read data
**
**	Returns:
**		usual sm_error code
*/

static sm_ret_T
qm_rd_hdrmods(qmgr_ctx_P qmgr_ctx, qss_ta_P qss_ta, sm_rcb_P rcb)
{
	sm_hdrmod_P sm_hdrmod;
	uint32_t l, rt, n1, n2;
	sm_ret_T ret;

	ret = SM_SUCCESS;
	while (!SM_RCB_ISEOB(rcb)) {
		ret = sm_hdrmod_new(&qss_ta->qssta_hdrmodhd, true, &sm_hdrmod);
		if (sm_is_err(ret))
			return ret;
		ret = sm_rcb_get4uint32(rcb, &l, &rt, &n1, &n2);
		if (sm_is_err(ret) || rt != RT_S2Q_HM_T_P || l != 8) {
			return sm_is_err(ret) ? ret
				: sm_error_perm(SM_EM_Q_SS2Q, SM_E_PR_ERR);
		}
		sm_hdrmod->sm_hm_type = n1;
		sm_hdrmod->sm_hm_pos = n2;

		if (SM_RCB_ISEOB(rcb))
			break;
		ret = sm_rcb_peek2uint32(rcb, &l, &rt);
		if (sm_is_err(ret)) {
			sm_hdrmod_rm_last(&qss_ta->qssta_hdrmodhd);
			return ret;
		}
		if (RT_S2Q_HM_T_P == rt && 8 == l)
			continue;
		if (rt != RT_S2Q_HM_HDR)
			break;

		ret = sm_rcb_get2uint32(rcb, &l, &rt);
		if (sm_is_err(ret) || rt != RT_S2Q_HM_HDR || l > SM_MAXHDRLEN) {
			return sm_is_err(ret) ? ret
				: sm_error_perm(SM_EM_Q_SS2Q, SM_E_PR_ERR);
		}
		ret = sm_rcb_getncstr(rcb, &sm_hdrmod->sm_hm_hdr, l);
		if (sm_is_err(ret))
			return ret;
	}
	return ret;
}

/*
**  SM_FR_SS_REACT -- Decode data received from SMTPS
**
**	Parameters:
**		qss_ctx -- QMGR/SMTPS context
**		tsk -- evthr task
**
**	Returns:
**		usual sm_error code
**
**	Locking:
**		XXX who locks qss_ctx?
**
**	Called by: qm_fr_ss
*/

static sm_ret_T
qm_fr_ss_react(qss_ctx_P qss_ctx, sm_evthr_task_P tsk
#if QM_TIMING
	, uint32_t *pfrt
#endif
	)
{
	uint32_t v, l, rt, tl;
	off_t off;
	sm_ret_T ret, ret2, rv;
	qss_status_T oldstatus;
	bool inwaitq;
	int r;
	uint32_t fct_state;
	sm_rcb_P rcb;
	sm_rcbe_P rcbe;
	qmgr_ctx_P qmgr_ctx;

#define FST_QSS_TA_LOCKED	0x0001	/* qss_ta is locked */

	SM_IS_QSS_CTX(qss_ctx);
	ret = rv = SM_SUCCESS;
	inwaitq = false;
	fct_state = 0;
	rcbe = NULL;
#if QM_TIMING
	*pfrt = 0;
#endif

	/*
	**  Generic component control XXX Is it ok to do this here?
	**  Not really: this will at most throttle SMTPS and then
	**  this function will not be called again.
	**  There needs to be some other place to reactivate SMTPS!
	**  XXX who does it?
	*/

	ret = qm_ss_comp_control(qss_ctx, false);
	if (sm_is_err(ret))
		QM_LEV_DPRINTFC(QDC_S2Q, 0, (QM_DEBFP, "sev=ERROR, func=qm_fr_ss_react, qm_ss_comp_control=%r\n", ret));
	/* What to do on error? */

	/*
	**  XXX Todo: add error codes before each goto err*
	**	this is necessary to figure out what error happened
	**	(at least for debugging...)
	**	it is also necessary in some cases to send a reply
	**	back to SMTPS, e.g., if QMGR can't allocate memory
	**	then SMTPS should be told about it...
	**
	**  In general the QMGR doesn't reply to data it can't decode.
	**  We assume that the sender is fubared and a reply won't help.
	**  Let it figure out itself that something went wrong.
	**  However, we should log an error (logging is completely missing).
	*/

	/* Decode rcb */
	rcb = qss_ctx->qss_com.rcbcom_rdrcb;
	ret = sm_rcb_open_dec(rcb);
	if (sm_is_err(ret)) {
		rv = ret;
		goto error;
	}
	qmgr_ctx = qss_ctx->qss_qmgr_ctx;
	SM_IS_QMGR_CTX(qmgr_ctx);

	/* Total length of record */
	ret = sm_rcb_getuint32(rcb, &tl);
	if (sm_is_err(ret) || tl > QM_SS_MAX_REC_LEN || tl > sm_rcb_getlen(rcb)) {
		rv = sm_is_err(ret) ? ret : sm_error_perm(SM_EM_Q_SS2Q, SM_E_RCB2LONG);
		goto err2;
	}

	/* Decode data, act accordingly... */

	/* Protocol header: version */
	ret = sm_rcb_get3uint32(rcb, &l, &rt, &v);
	if (sm_is_err(ret)) {
		rv = ret;
		goto err2;
	}
	if (l != 4 || rt != RT_PROT_VER || v != PROT_VER_RT) {
		rv = sm_error_perm(SM_EM_Q_SS2Q, SM_E_PR_V_MISM);
		goto err2;
	}

/*
queuemanager.func.tex:

\subsubsection{QMGR - SMTPS API}
\label{func:QMGR:QMGRSMTPSAPI}

Should we change the records such that it simpler to distinguish
the various types?
On one side having a common "header" makes it simpler to avoid duplicate
code, however, having distinct headers allow for "early" selection
of the right case instead of having a deeply nested if else structure.
Moreover, if we "know" the data type, we don't need to do:
get2int(); check record type; getint() or getn().

[read]
qss_ctx_open(IN smtps-info, OUT status):
RT_S2Q_NID: int smtps-id						[ok]
XXX should this also transfer an initial status?

[partially implemented]
qmgr_session_open(IN connection-info, IN session-id, OUT status):
RT_S2Q_ID: int smtps-id							[ok]
RT_S2Q_NSEID: str session-id						[ok]
RT_S2Q_CLTIPV4/RT_S2Q_CLTIPV6: str client IP address			[-]

[not yet implemented]
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: AUTH, STARTTLS, others?

[read]
qmgr_1trans(IN session-id, IN sender-env-info, IN trans-id, ..., OUT status):
RT_S2Q_ID: int smtps-id
RT_S2Q_1TAID: str transaction id
RT_S2Q_MAIL: str mail from
RT_S2Q_MAIL_FL: int mail flags [optional]
RT_S2Q_RCPT_IDX: int rcpt idx (?)
RT_S2Q_RCPT: str rcpt to
the last two values can be repeated to transmit a list.
RT_S2Q_CDBID: str cdb-id

[read]
qmgr_session_close(IN session-id, OUT status):
RT_S2Q_ID: int smtps-id
RT_S2Q_CSEID: str session-id

[read]
qss_ctx_close(IN smtps-info, OUT status):
RT_S2Q_CID: int smtps-id

*/

	/* SMTPS (new/existing/close) ID */
	ret = sm_rcb_get3uint32(rcb, &l, &rt, &v);
	if (sm_is_err(ret) || l != 4) {
		rv = sm_is_err(ret) ? ret : sm_error_perm(SM_EM_Q_SS2Q, SM_E_PR_ERR);
		goto err2;
	}
	if (RT_S2Q_NID == rt || RT_S2Q_QID == rt) {
		bool query_id;

		/* New SMTPS */
		query_id = RT_S2Q_QID == rt;
		if (qss_ctx->qss_status != QSS_ST_NONE ||
		    qss_ctx->qss_id != QSS_ID_NONE)
		{
			/* XXX Internal error? Stop task? SMTPS id exists */
			sm_log_write(qss_ctx->qss_qmgr_ctx->qmgr_lctx,
				QM_LCAT_SMTPS, QM_LMOD_FROM_SMTPS,
				SM_LOG_ERROR, 4,
				"sev=ERROR, func=qm_fr_ss_react, rt=%#x, qss_status=%d, qss_id=%d, status=unexpected_state"
				, rt, qss_ctx->qss_status, qss_ctx->qss_id);

			rv = sm_error_perm(SM_EM_Q_SS2Q, SM_E_PR_ERR);
			/* XXX use different error! */
			goto err2;
		}
		if (RT_S2Q_QID == rt && (int) v < 0) {
			ret = qm_ss_findfreeid(qmgr_ctx, qss_ctx);
			if (sm_is_err(ret)) {
				sm_log_write(qss_ctx->qss_qmgr_ctx->qmgr_lctx,
					QM_LCAT_SMTPS, QM_LMOD_FROM_SMTPS,
					SM_LOG_ERROR, 4,
					"sev=ERROR, func=qm_fr_ss_react, rt=RT_S2Q_QID, qss_status=%d, qss_id=%d, qm_ss_findfreeid=%m"
					, qss_ctx->qss_status, qss_ctx->qss_id, ret);
				rv = sm_error_perm(SM_EM_Q_SS2Q, SM_E_OVFLW_SC);
				/* XXX use different error! */
				goto err2;
			}
QM_LEV_DPRINTFC(QDC_S2Q, 2, (QM_DEBFP, "sev=DBG, func=qm_fr_ss_react, id=%d, nid=%d\n", (int) v, qss_ctx->qss_id, ret));
		}
		else
			qss_ctx->qss_id = v;
		qss_ctx->qss_cur_session = 0;
#if QMGR_STATS
		qss_ctx->qss_max_session = 0;
#endif
		ret = qm_ss_find(qmgr_ctx, qss_ctx);
		if (ret >= 0) {
			sm_log_write(qss_ctx->qss_qmgr_ctx->qmgr_lctx,
				QM_LCAT_SMTPS, QM_LMOD_FROM_SMTPS,
				SM_LOG_ERROR, 4,
				"sev=ERROR, func=qm_fr_ss_react, rt=RT_S2Q_NID, qss_status=%d, qss_id=%d, qm_ss_find=%m"
				, qss_ctx->qss_status, qss_ctx->qss_id, ret);

			/* XXX Internal error? Stop task? SMTPS id exists */
			rv = sm_error_perm(SM_EM_Q_SS2Q, SM_E_PR_ERR);
			/* XXX use different error! */
			goto err2;
		}

		qss_ctx->qss_status = QSS_ST_START;
		if (SM_RCB_ISEOB(rcb))
			goto done;
		ret = sm_rcb_get3uint32(rcb, &l, &rt, &v);
		if (sm_is_err(ret) || l != 4 || rt != RT_S2Q_MAXTHRDS) {
			rv = sm_is_err(ret) ? ret
				: sm_error_perm(SM_EM_Q_SS2Q, SM_E_PR_ERR);
			goto err2;
		}
		qss_ctx->qss_max_thrs = v;
		qss_ctx->qss_max_cur_thrs = v;

		if (!SM_RCB_ISEOB(rcb)) {
			rv = sm_error_perm(SM_EM_Q_SS2Q, SM_E_PR_ERR);
			goto err2;
		}

		/*
		**  Send a reply to let client know that the connection
		**  has been accepted.
		*/

		ret = qm_rcbcom_prerep(qmgr_ctx, &qss_ctx->qss_com, tsk, &rcbe);
		if (sm_is_err(ret))
			goto err2;
		inwaitq = true;

		/* XXX get real initial id (from IBDB?) */
		ret = sm_rcb_putv(&rcbe->rcbe_rcb, RCB_PUTV_FIRST,
			SM_RCBV_INT, RT_PROT_VER, PROT_VER_RT,
			SM_RCBV_INT, query_id ? RT_Q2S_NID : RT_Q2S_ID, qss_ctx->qss_id,
			SM_RCBV_INT64, RT_Q2S_IIDC, qmgr_ctx->qmgr_idc + 1,
			SM_RCBV_END);
		if (sm_is_err(ret)) {
			QM_LEV_DPRINTFC(QDC_S2Q, 0, (QM_DEBFP, "sev=ERROR, func=qm_fr_ss_react, id=%d, nid=%d, sm_pcb_putv=%r\n", qss_ctx->qss_id, v, ret));
			goto err2;
		}

		ret = sm_rcbcom_endrep(&qss_ctx->qss_com, tsk, false, &rcbe);
		if (sm_is_err(ret)) {
			QM_LEV_DPRINTFC(QDC_S2Q, 0, (QM_DEBFP, "sev=ERROR, func=qm_fr_ss_react, id=%d, nid=%d, sm_rcbcom_endrep=%r\n", qss_ctx->qss_id, v, ret));
			goto err2;
		}

		goto done;
	}
	else if (RT_S2Q_ID == rt) {
		/* SMTPS id just for identification */
		if (qss_ctx->qss_status == QSS_ST_NONE || qss_ctx->qss_id != v) {
			/* XXX Internal error? Stop task? SMTPS id exists */
			rv = sm_error_perm(SM_EM_Q_SS2Q, SM_E_PR_ERR);
			/* XXX use different error! */
			goto err2;
		}
	}
	else if (RT_S2Q_CID == rt) {
		/* SMTPS shuts down */
		if (qss_ctx->qss_status == QSS_ST_NONE || qss_ctx->qss_id != v) {
			/* Internal error? Stop task? SMTPS id exists */
			rv = sm_error_perm(SM_EM_Q_SS2Q, SM_E_PR_ERR);
			/* use different error?? */
			goto err2;
		}
		/* check for EOB? not really: we shut down anyway */

		qss_ctx->qss_status = QSS_ST_SH_DOWN;
		(void) sm_rcb_close_dec(qss_ctx->qss_com.rcbcom_rdrcb);

		/*
		**  We assume that all no open sessions/transactions exist,
		**  i.e., SMTPS properly terminated them before sending this
		**  message.
		*/

		/* Terminate (delete) this task, qss_ctx is cleaned in caller */
		return EVTHR_DEL;
	}
	else
		goto err2;

	/* rt == RT_S2Q_ID is the only case in which we continue here */
	oldstatus = qss_ctx->qss_status;

	/* what's next? */
	ret = sm_rcb_get2uint32(rcb, &l, &rt);
	if (sm_is_err(ret)) {
		rv = ret;
		goto err2;
	}
#if QM_TIMING
	*pfrt = rt;
#endif

	if (RT_S2Q_STAT == rt) {
		if (l != 4) {
			rv = sm_error_perm(SM_EM_Q_SS2Q, SM_E_PR_ERR);
			goto err2;
		}

		/* status */
		ret = sm_rcb_getuint32(rcb, &v);
		if (sm_is_err(ret)) {
			rv = sm_error_perm(SM_EM_Q_SS2Q, SM_E_PR_ERR);
			goto err2;
		}
		qss_ctx->qss_status = v;
		QM_LEV_DPRINTFC(QDC_S2Q, 1, (QM_DEBFP, "func=qm_fr_ss_react, id=%d, stat=%d\n", qss_ctx->qss_id, qss_ctx->qss_status));

		if (QSS_ST_NONE == oldstatus) {
			if (!SM_RCB_ISEOB(rcb)) {
				rv = sm_error_perm(SM_EM_Q_SS2Q, SM_E_PR_ERR);
				goto err2;
			}
			goto done;
		}

		/* XXX what to do? just be happy? */
		goto done;
	}
	else if (RT_S2Q_NSEID == rt) {
		qss_sess_P qss_sess;

		qss_sess = NULL;
		if (l != SMTP_STID_SIZE) {
			rv = sm_error_perm(SM_EM_Q_SS2Q, SM_E_PR_ERR);
			goto err2;
		}

		/* allocate new session? rpool??? */
		ret = qss_sess_new(&qss_sess, NULL);
		if (sm_is_err(ret))
			goto err2;
		qss_sess->qsses_st_time = evthr_time(qmgr_ctx->qmgr_ev_ctx);

		/* session id */
		ret = sm_rcb_getn(rcb, (uchar *) qss_sess->qsses_id, l);
		if (sm_is_err(ret)) {
			rv = ret;
			goto errnseid;
		}
		ret = sm_getsmtpsid(qss_sess->qsses_id);
		if (ret != qss_ctx->qss_id) {
			/* SMTPS id doesn't match */
			rv = sm_error_perm(SM_EM_Q_SS2Q, SM_E_PR_ERR);
			/* use different error?? */
			goto errnseid;
		}
		QM_LEV_DPRINTFC(QDC_S2Q, 3, (QM_DEBFP, "func=qm_fr_ss_react, id=%d, nsess-id=%s\n", qss_ctx->qss_id, qss_sess->qsses_id));

		/* XXX client IP address; must match structure later on! */
		ret = sm_rcb_get3uint32(rcb, &l, &rt, &v);
		if (sm_is_err(ret) || l != 4 || rt != RT_S2Q_CLTIPV4) {
			rv = sm_is_err(ret) ? ret
				: sm_error_perm(SM_EM_Q_SS2Q, SM_E_PR_ERR);
			goto errnseid;
		}
		qss_sess->qsess_client.s_addr = v;

		QM_LEV_DPRINTFC(QDC_S2Q, 3, (QM_DEBFP, "func=qm_fr_ss_react, id=%d, nsess-id=%s, IP=%s\n", qss_ctx->qss_id, qss_sess->qsses_id, inet_ntoa(qss_sess->qsess_client)));

		ret = qm_rcbcom_prerep(qmgr_ctx, &qss_ctx->qss_com, tsk, &rcbe);
		if (sm_is_err(ret))
			goto errnseid;
		inwaitq = true;

		ret2 = qm_ss_nseid(qss_ctx, qss_sess);
		QM_LEV_DPRINTFC(QDC_S2Q, 3, (QM_DEBFP, "func=qm_fr_ss_react, id=%d, nsess-id=%s, qm_ss_nseid=%d\n", qss_ctx->qss_id, qss_sess->qsses_id, ret2));
		if (sm_is_err(ret2))
			goto errnseid;

		ret = qm_2ss_nseid(qss_ctx, rcbe, (uchar *) qss_sess->qsses_id,
				(int) ret2);
		if (sm_is_err(ret)) {
			QM_LEV_DPRINTFC(QDC_S2Q, 0, (QM_DEBFP, "sev=ERROR, func=qm_fr_ss_react, id=%d, nsess-id=%s, qm_2ss_nseid=%r\n", qss_ctx->qss_id, qss_sess->qsses_id, ret));
			goto errnseid2;
		}
		QM_LEV_DPRINTFC(QDC_S2Q, 7, (QM_DEBFP, "sev=DBG, func=qm_fr_ss_react, evthr_en_wr=%p\n", &qss_ctx->qss_com));
		ret = sm_rcbcom_endrep(&qss_ctx->qss_com, tsk, false, &rcbe);
		QM_LEV_DPRINTFC(QDC_S2Q, 6, (QM_DEBFP, "sev=DBG, func=qm_fr_ss_react, evthr_en_wr=%p, done=%r\n", &qss_ctx->qss_com, ret));
		if (sm_is_err(ret)) {
			QM_LEV_DPRINTFC(QDC_S2Q, 0, (QM_DEBFP, "sev=ERROR, func=qm_fr_ss_react, id=%d, nsess-id=%s, sm_rcbcom_endrep=%r\n", qss_ctx->qss_id, qss_sess->qsses_id, ret));
			goto errnseid2;
		}
		ret = qss_control(qss_ctx, QMGR_THROTTLE,
				iqdb_usage(qmgr_ctx->qmgr_iqdb),
				QMGR_RFL_IQD_I, THR_LOCK_UNLOCK);
		sm_log_write(qss_ctx->qss_qmgr_ctx->qmgr_lctx,
			QM_LCAT_SMTPS, QM_LMOD_FROM_SMTPS,
			SM_LOG_INFO, 14,
			"func=qm_fr_ss_react, id=%d, new_sess=%s, client_ipv4=%s, stat=%m",
			qss_ctx->qss_id, qss_sess->qsses_id,
			inet_ntoa(qss_sess->qsess_client), ret2);
		return QMGR_R_ASYNC;

  errnseid2:
		/* remove the session from the iqdb */
		/* XXX this needs to undo all operations from qm_ss_nseid() */
		(void) iqdb_session_rm(qmgr_ctx->qmgr_iqdb, qss_sess->qsses_id,
				SMTP_STID_SIZE, THR_LOCK_UNLOCK);
  errnseid:
		if (qss_sess != NULL) {
			(void) qss_sess_free(qss_sess);
			qss_sess = NULL;
		}
		sm_log_write(qss_ctx->qss_qmgr_ctx->qmgr_lctx,
			QM_LCAT_SMTPS, QM_LMOD_FROM_SMTPS,
			SM_LOG_ERR, 4,
			"sev=ERROR, func=qm_fr_ss_react, sess_new=%d", ret);
		goto err2;
	}
	else if (RT_S2Q_CSEID == rt) {
		sessta_id_T sessid;
		qss_sess_P qss_sess;
		ssocc_entry_P ssocc_entry;

		qss_sess = NULL;
		if (l != SMTP_STID_SIZE) {
			rv = sm_error_perm(SM_EM_Q_SS2Q, SM_E_PR_ERR);
			goto err2;
		}

		/* session id */
		ret = sm_rcb_getn(rcb, (uchar *) sessid, l);
		if (sm_is_err(ret)) {
			rv = ret;
			goto errcseid;
		}
		ret = sm_getsmtpsid(sessid);
		if (ret != qss_ctx->qss_id) {
			/* XXX SMTPS id doesn't match */
			rv = sm_error_perm(SM_EM_Q_SS2Q, SM_E_PR_ERR);
			/* XXX use different error! */
			goto errcseid;
		}

		qss_sess = (qss_sess_P) iqdb_lookup(qmgr_ctx->qmgr_iqdb, sessid,
					SMTP_STID_SIZE, THR_LOCK_UNLOCK);
		if (NULL == qss_sess) {
			ret = sm_error_perm(SM_EM_Q_SS2Q, SM_E_NOTFOUND);
			QM_LEV_DPRINTFC(QDC_S2Q, 0, (QM_DEBFP, "sev=ERROR, func=qm_fr_ss_react, id=%d, sess-id=%s, siqdb_lookup=not_found\n", qss_ctx->qss_id, sessid));
			goto errcseid;
		}
		QM_LEV_DPRINTFC(QDC_S2Q, 3, (QM_DEBFP, "sev=DBG, func=qm_fr_ss_react, id=%d, csess-id=%s\n", qss_ctx->qss_id, qss_sess->qsses_id));
		/* check for EOR? */

/* XXX this should be in some other function? (qm_ss_cseid()) */

/* XXX this should be in some other function? (ssocc_new_se()) */
		ssocc_entry = NULL;
		ret = ssocc_entry_find(qmgr_ctx->qmgr_ssocc_ctx,
			qss_sess->qsess_client.s_addr, &ssocc_entry, THR_LOCK_UNLERR);
		if (sm_is_err(ret)) {
			QM_LEV_DPRINTFC(QDC_S2Q, 4, (QM_DEBFP, "sev=DBG, func=qm_fr_ss_react, ss_sess=%s, ssocc_entry_find=failed, ip=%A, ret=%r\n", qss_sess->qsses_id, (ipv4_T) qss_sess->qsess_client.s_addr, ret));
		}
		else {
			SM_ASSERT(ssocc_entry->ssocce_open_se > 0);
			--ssocc_entry->ssocce_open_se;
			if (0 == ssocc_entry->ssocce_open_se) {
				ret = ssocc_entry_free(qmgr_ctx->qmgr_ssocc_ctx, ssocc_entry,
						THR_NO_LOCK);
			}
			r = pthread_mutex_unlock(&qmgr_ctx->qmgr_ssocc_ctx->ssocc_mutex);
			if (r != 0) {
				/* XXX ??? COMPLAIN */
				QM_LEV_DPRINTFC(QDC_S2Q, 0, (QM_DEBFP, "sev=ERROR, func=qm_fr_ss_react, unlock(ssocc_ctx)=%d\n", r));
				SM_ASSERT(0 == r);
			}
		}


		/*
		**  XXX just remove it? do anything else with it first?
		**  e.g., check for open transactions (and abort those)?
		*/

		ret = iqdb_session_rm(qmgr_ctx->qmgr_iqdb, sessid,
				SMTP_STID_SIZE, THR_LOCK_UNLOCK);
		if (sm_is_err(ret)) {
			QM_LEV_DPRINTFC(QDC_S2Q, 0, (QM_DEBFP, "sev=ERROR, func=qm_fr_ss_react, id=%d, csess-id=%s, rm=%r\n", qss_ctx->qss_id, qss_sess->qsses_id, ret));
			goto errcseid;
		}
		sm_log_write(qss_ctx->qss_qmgr_ctx->qmgr_lctx,
			QM_LCAT_SMTPS, QM_LMOD_FROM_SMTPS,
			SM_LOG_INFO, 14,
			"func=qm_fr_ss_react, id=%d, close_sess=%s",
			qss_ctx->qss_id, qss_sess->qsses_id);
		qss_sess_free(qss_sess);
		SM_ASSERT(qss_ctx->qss_cur_session > 0);
		--qss_ctx->qss_cur_session;
		ret = qm_control(qmgr_ctx, QMGR_UN_THROTTLE,
				iqdb_usage(qmgr_ctx->qmgr_iqdb),
				QMGR_RFL_IQD_I, THR_LOCK_UNLOCK);

		/* XXX no reply??? */
		goto done;

  errcseid:
		/* more cleanup? (qss_sess isn't allocated, don't free it!) */
		sm_log_write(qss_ctx->qss_qmgr_ctx->qmgr_lctx,
			QM_LCAT_SMTPS, QM_LMOD_FROM_SMTPS,
			SM_LOG_ERR, 4,
			"sev=ERROR, func=qm_fr_ss_react, sess_close=%m", ret);
		goto err2;
	}
	else if (RT_S2Q_1TAID == rt) {
		qss_ta_P qss_ta;

		if (l != SMTP_STID_SIZE) {
			rv = sm_error_perm(SM_EM_Q_SS2Q, SM_E_PR_ERR);
			goto err2;
		}

		/* XXX allocate new transaction? rpool? */
		ret = qss_ta_new(&qss_ta, qss_ctx, NULL);
		if (sm_is_err(ret))
			goto err2;
		qss_ta->qssta_st_time = evthr_time(qmgr_ctx->qmgr_ev_ctx);

		/* transaction id */
		ret = sm_rcb_getn(rcb, (uchar *) qss_ta->qssta_id, l);
		if (sm_is_err(ret)) {
			rv = ret;
			goto errntaid;
		}
		QM_LEV_DPRINTFC(QDC_S2Q, 3, (QM_DEBFP, "sev=DBG, func=qm_fr_ss_react, id=%d, 1ta-id=%s\n", qss_ctx->qss_id, qss_ta->qssta_id));
		ret = sm_getsmtpsid(qss_ta->qssta_id);
		if (ret != qss_ctx->qss_id) {
			/* XXX SMTPS id doesn't match */
			rv = sm_error_perm(SM_EM_Q_SS2Q, SM_E_PR_ERR);
			/* XXX use different error! */
			goto err2;
		}

		if (sm_idcmp(qss_ctx->qss_ta_id, qss_ta->qssta_id) < 0)
			SESSTA_COPY(qss_ctx->qss_ta_id, qss_ta->qssta_id);

		ret = sm_rcb_get2uint32(rcb, &l, &rt);
		if (sm_is_err(ret) || rt != RT_S2Q_MAIL || l >= tl) {
			rv = sm_is_err(ret) ? ret
				: sm_error_perm(SM_EM_Q_SS2Q, SM_E_PR_ERR);
			goto errntaid;
		}
		ret = qss_mail_new(qss_ta);
		if (sm_is_err(ret))
			goto errntaid;
		ret = sm_rcb_getnstr(rcb, &qss_ta->qssta_mail->qsm_pa, l);
		if (sm_is_err(ret))
			goto errntaid;
		QM_LEV_DPRINTFC(QDC_S2Q, 2, (QM_DEBFP, "sev=DBG, func=qm_fr_ss_react, id=%d, 1ta-id=%s, mail from=%S, l=%d\n", qss_ctx->qss_id, qss_ta->qssta_id, qss_ta->qssta_mail->qsm_pa, l));

		ret = sm_rcb_peek2uint32(rcb, &l, &rt);
		if (sm_is_success(ret) && 4 == l && RT_S2Q_MAIL_FL == rt) {
			ret = sm_rcb_get3uint32(rcb, &l, &rt, &v);
			if (sm_is_err(ret) || l != 4 || rt != RT_S2Q_MAIL_FL) {
				rv = sm_is_err(ret) ? ret
					: sm_error_perm(SM_EM_Q_SS2Q, SM_E_PR_ERR);
				goto errntaid;
			}

			/* "translate" flags */
			if (SM_IS_FLAG(v, SM_TA_FL_VERP))
				QSS_TA_SET_FLAG(qss_ta, QSS_TA_FL_VERP);
		}

		ret = sm_rcb_get2uint32(rcb, &l, &rt);
		while (RT_S2Q_RCPT_IDX == rt) {
			rcpt_idx_T rcpt_idx;
			qss_rcpt_P qss_rcpt;
			sm_str_P rcpt_pa;

			qss_rcpt = NULL;
			if (l != 4) {
				rv = sm_error_perm(SM_EM_Q_SS2Q, SM_E_PR_ERR);
				goto errntaid2;
			}
			ret = sm_rcb_getuint32(rcb, &v);
			if (sm_is_err(ret)) {
				rv = ret;
				goto errntaid2;
			}
			rcpt_idx = v;
			ret = sm_rcb_get2uint32(rcb, &l, &rt);
			if (sm_is_err(ret) || rt != RT_S2Q_RCPT || l > tl) {
				rv = sm_is_err(ret) ? ret
					: sm_error_perm(SM_EM_Q_SS2Q, SM_E_PR_ERR);
				goto errntaid2;
			}

			ret = sm_rcb_getnstr(rcb, &rcpt_pa, l);
			if (sm_is_err(ret))
				goto errntaid2;

			/* add recipient to list */
			ret = qss_rcpts_new(qss_ta, &rcpt_pa, rcpt_idx, &qss_rcpt);
			if (sm_is_err(ret))
				goto errntaid2;
			QSRCPT_SET_FLAG(qss_rcpt, QSRCPT_FL_TA);
			QM_LEV_DPRINTFC(QDC_S2Q, 2, (QM_DEBFP, "sev=DBG, func=qm_fr_ss_react, id=%d, qss_ta-id=%s, rcpt_idx=%d, rcptaddr=%S, l=%d, nrctps=%d\n",
qss_ctx->qss_id, qss_ta->qssta_id, rcpt_idx, qss_rcpt->qsr_pa, l, qss_ta->qssta_rcpts_tot));

			/* add recipient to iqdb, ibdb; ret2 used down below */
/* 2003-10-20 03:23:06 XXX ret2 used where? (except for logging) */
			/* XXX split here: this function will be async. */
			ret2 = qm_ss_rcptid(qss_ctx, qss_ta, tsk, qss_rcpt);
			qss_rcpt = NULL; /* qm_ss_rcptid took care of it */

			/* qss_rcpt will be in qss_ta only if accepted */
			if (sm_is_err(ret2)) {
				QM_LEV_DPRINTFC(QDC_S2Q, 0, (QM_DEBFP, "sev=ERROR, func=qm_fr_ss_react, qm_ss_rcptid=%r\n", ret2));
				goto errntaid2;
			}

			ret = sm_rcb_get2uint32(rcb, &l, &rt);
			continue;
		}

		if (rt != RT_S2Q_CDBID || l > tl) {
			QM_LEV_DPRINTFC(QDC_S2Q, 0, (QM_DEBFP, "sev=ERROR, func=qm_fr_ss_react, rt=%#x, l=%u, tl=%u\n", rt, l, tl));
			rv = sm_error_perm(SM_EM_Q_SS2Q, SM_E_PR_ERR);
			goto errntaid2;
		}

		ret = sm_rcb_getncstr(rcb, &qss_ta->qssta_cdb_id, l);
QM_LEV_DPRINTFC(QDC_S2Q, 4, (QM_DEBFP, "sev=DBG, func=qm_fr_ss_react, cdb_id=%C, ret=%r\n", qss_ta->qssta_cdb_id, ret));
		if (sm_is_err(ret)) {
			/*
			**  XXX Throttle SMTPS? Send error back saying that
			**  transaction can't be accepted because QMGR is
			**  out of memory?
			*/

			if (qss_ta != NULL) {
				/*
				**  XXX Remove from DBs?
				**  It's not in IBDB nor in AQ.
				**  Use qss_ta_abort instead?
				*/

				ret = qss_ta_free(qss_ta, false, QSS_TA_FREE_ALWAYS, 0);
				qss_ta = NULL;
			}
			goto errntaid2;
		}
		ret = sm_rcb_get3off_t(rcb, &l, &rt, &off);
		if (sm_is_err(ret) || rt != RT_S2Q_SIZE_B || l != SIZEOF_OFF_T) {
			rv = sm_is_err(ret) ? ret
				: sm_error_perm(SM_EM_Q_SS2Q, SM_E_PR_ERR);
			goto errntaid2;
		}
		qss_ta->qssta_msg_sz_b = off;
		QM_LEV_DPRINTFC(QDC_S2Q, 3, (QM_DEBFP, "sev=DBG, func=qm_fr_ss_react, id=%d, size=%lu\n", qss_ctx->qss_id, (ulong) off));

		ret = qm_rd_hdrmods(qmgr_ctx, qss_ta, rcb);
		if (sm_is_err(ret))
			goto errntaid2;

		ret = qm_rcbcom_prerep(qmgr_ctx, &qss_ctx->qss_com, tsk, &rcbe);
		if (sm_is_err(ret)) {
			QM_LEV_DPRINTFC(QDC_S2Q, 0, (QM_DEBFP, "sev=ERROR, func=qm_fr_ss_react, qss_ta=%p, qm_rcbcom_prerep=%r\n", qss_ta, ret));
			goto errntaid2;
		}
		inwaitq = true;

		r = pthread_mutex_lock(&qss_ta->qssta_mutex);
		SM_LOCK_OK(r);
		if (r != 0) {
			QM_LEV_DPRINTFC(QDC_S2Q, 0, (QM_DEBFP, "sev=ERROR, func=qm_fr_ss_react, qss_ta=%p, lock=%d\n", qss_ta, r));
			goto errntaid2;
		}
		SM_SET_FLAG(fct_state, FST_QSS_TA_LOCKED);

		/* all data read, store it internally, prepare reply */

		ret2 = qm_ss_1taid(qss_ctx, qss_ta);
		QM_LEV_DPRINTFC(QDC_S2Q, 1, (QM_DEBFP, "sev=DBG, func=qm_fr_ss_react, id=%d, 1ta-id=%s, qm_ss_1taid=%r\n", qss_ctx->qss_id, qss_ta->qssta_id, ret2));

		/* check for EOR? */
		if (sm_is_err(ret2))
			goto errntaid;

		ret2 = qm_ss_ctaid(qss_ctx, qss_ta);
		if (sm_is_err(ret2)) {
			QM_LEV_DPRINTFC(QDC_S2Q, 1, (QM_DEBFP, "sev=ERROR, func=qm_fr_ss_react, qm_ss_ctaid=%r, id=%d, cta-id=%s, cdb=%C\n", ret2, qss_ctx->qss_id, qss_ta->qssta_id, qss_ta->qssta_cdb_id));
			goto errcdb2;
		}

		/*
		**  If we get an return code other than SMTP_R_OK
		**  then we can immediately tell SMTPS about;
		**  we don't need to schedule this for a group commit.
		*/

		if (ret2 != SMTP_R_OK) {
			sm_log_write(qss_ctx->qss_qmgr_ctx->qmgr_lctx,
				QM_LCAT_SMTPS, QM_LMOD_FROM_SMTPS,
				SM_LOG_INFO, 8,
				"sev=INFO, func=qm_fr_ss_react, smtps_id=%d, ss_ta=%s, stat=%d",
				qss_ctx->qss_id, qss_ta->qssta_id, ret2);

			ret = qm_2ss_ctaid(qss_ctx, qss_ta, rcbe, ret2);
			if (sm_is_err(ret))
				goto errcdb2;
			ret = sm_rcbcom_endrep(&qss_ctx->qss_com, tsk, false, &rcbe);
			/* ret checked below */
			QM_LEV_DPRINTFC(QDC_S2Q, 6, (QM_DEBFP, "sev=DBG, func=qm_fr_ss_react, 3 evthr_en_wr=%p, done=%r\n", &qss_ctx->qss_com, ret));
		}
		else {
#if QMGR_STATS
			/* XXX not protected by mutex */
			qmgr_ctx->qmgr_rcpts_rcvd += qss_ta->qssta_rcpts_tot;
			++qmgr_ctx->qmgr_tas_rcvd;
#endif

			/* don't need rcbe here: a reply scheduled in qm_ss_schedctaid() */
			sm_rcbe_free(rcbe);
			rcbe = NULL;

			/* protected by fs_* mutex */
			ret = cdb_sz_add(qmgr_ctx->qmgr_cdb_fsctx, qss_ta->qssta_id,
				SM_B2KB(qss_ta->qssta_msg_sz_b), &qmgr_ctx->qmgr_cdb_kbfree);
			QM_LEV_DPRINTFC(QDC_S2Q, 4, (QM_DEBFP, "cdb_sz_add=%r, size=%lu, kbfree=%lu\n", ret, (ulong) qss_ta->qssta_msg_sz_b, qmgr_ctx->qmgr_cdb_kbfree));
			if (sm_is_err(ret)) {
				QM_LEV_DPRINTFC(QDC_S2Q, 1, (QM_DEBFP, "sev=ERROR, func=qm_fr_ss_react, cdb_sz_add=%r\n", ret));
			}
			else if (qmgr_ctx->qmgr_cdb_kbfree < qmgr_ctx->qmgr_cnf.q_cnf_ok_df)
			{
				ret = qss_control(qss_ctx, QMGR_THROTTLE,
					DISK_USAGE(qmgr_ctx->qmgr_cdb_kbfree, qmgr_ctx),
					QMGR_RFL_CDB_I, THR_LOCK_UNLOCK);
			}

			ret = qm_ss_schedctaid(qmgr_ctx, qss_ta);
			/* ret checked below */
		}
		if (sm_is_err(ret))
			goto errcdb2;
		SM_ASSERT(SM_IS_FLAG(fct_state, FST_QSS_TA_LOCKED));
		r = pthread_mutex_unlock(&qss_ta->qssta_mutex);
		if (r != 0) {
			QM_LEV_DPRINTFC(QDC_S2Q, 0, (QM_DEBFP, "sev=ERROR, func=qm_fr_ss_react, qss_ta=%p, unlock=%d\n", qss_ta, r));
			SM_ASSERT(0 == r);
		}
		SM_CLR_FLAG(fct_state, FST_QSS_TA_LOCKED);

		/*
		**  if qm_ss_ctaid failed then qss_ta must be removed
		*/

		if (ret2 != SMTP_R_OK && qss_ta != NULL) {
			ret = qss_ta_free(qss_ta, false, QSS_TA_FREE_ALWAYS, 0);
			if (sm_is_err(ret))
				QM_LEV_DPRINTFC(QDC_S2Q, 0, (QM_DEBFP, "sev=ERROR, func=qm_fr_ss_react, qss_ta_free=%r\n", ret));
			qss_ta = NULL;
		}

		ret = qss_control(qss_ctx, QMGR_THROTTLE,
				iqdb_usage(qmgr_ctx->qmgr_iqdb),
				QMGR_RFL_IQD_I, THR_LOCK_UNLOCK);

		return QMGR_R_ASYNC;

	/* check: proper cleanup functionality??? */
  errcdb2:
		SM_ASSERT(SM_IS_FLAG(fct_state, FST_QSS_TA_LOCKED));
		r = pthread_mutex_unlock(&qss_ta->qssta_mutex);
		if (r != 0) {
			QM_LEV_DPRINTFC(QDC_S2Q, 0, (QM_DEBFP, "sev=ERROR, func=qm_fr_ss_react, qss_ta=%p, unlock=%d\n", qss_ta, r));
			SM_ASSERT(0 == r);
		}
		SM_CLR_FLAG(fct_state, FST_QSS_TA_LOCKED);

		QM_LEV_DPRINTFC(QDC_S2Q, 0, (QM_DEBFP, "sev=ERROR, func=qm_fr_ss_react, id=%d, cta-id=%s, ret=%r\n", qss_ctx->qss_id, qss_ta->qssta_id, ret));
		sm_rcbe_free(rcbe);
		rcbe = NULL;
		if (qss_ta != NULL) {
			/*
			**  XXX Remove from iqdb? XXX Really? Always?
			**  Maybe use the flags to denote what should happen
			**  with qss_ta? (indicate the "progress", i.e., where
			**  is qss_ta stored and whether something bad happened
			**  such that it must be freed).
			*/

			(void) qss_ta_free(qss_ta, false, QSS_TA_FREE_ALWAYS, 0);
			qss_ta = NULL;
		}
		sm_log_write(qss_ctx->qss_qmgr_ctx->qmgr_lctx,
			QM_LCAT_SMTPS, QM_LMOD_FROM_SMTPS,
			SM_LOG_ERR, 4,
			"sev=ERROR, func=qm_fr_ss_react, ss_ta=%s, close_ss_ta=%m",
			qss_ta != NULL ? qss_ta->qssta_id : "unknown", ret);
		goto err2;

	/* check: proper cleanup functionality??? */
  errntaid2:
		if (qss_ta != NULL && QSS_TA_IS_FLAG(qss_ta, QSS_TA_FL_IQDB)) {
			/* remove the transaction from iqdb */
			(void) iqdb_trans_rm(qmgr_ctx->qmgr_iqdb, qss_ta->qssta_id,
					SMTP_STID_SIZE, THR_LOCK_UNLOCK);
			QSS_TA_SET_FLAG(qss_ta, QSS_TA_FL_IQDB_RM);
		}
  errntaid:
		if (SM_IS_FLAG(fct_state, FST_QSS_TA_LOCKED)) {
			r = pthread_mutex_unlock(&qss_ta->qssta_mutex);
			if (r != 0) {
				QM_LEV_DPRINTFC(QDC_S2Q, 0, (QM_DEBFP, "sev=ERROR, func=qm_fr_ss_react, qss_ta=%p, unlock=%d\n", qss_ta, r));
				SM_ASSERT(0 == r);
			}
			SM_CLR_FLAG(fct_state, FST_QSS_TA_LOCKED);
		}
		if (qss_ta != NULL) {
			/* qss_ta is not in any DB */
			if (!QSS_TA_OK_FREE(qss_ta->qssta_flags))
				QM_LEV_DPRINTFC(QDC_S2Q, 0, (QM_DEBFP, "sev=DBG, func=qm_fr_ss_react, ERROR=unexpected_state, flags=%#x, id=%d, qss_ta-id=%s\n", qss_ta->qssta_flags, qss_ctx->qss_id, qss_ta->qssta_id));

			(void) qss_ta_free(qss_ta, false, QSS_TA_FREE_ALWAYS, 0);
			qss_ta = NULL;
		}
		sm_log_write(qss_ctx->qss_qmgr_ctx->qmgr_lctx,
			QM_LCAT_SMTPS, QM_LMOD_FROM_SMTPS,
			SM_LOG_ERR, 4,
			"sev=ERROR, func=qm_fr_ss_react, 1ta_new=%r", ret);
		goto err2;
	}

#if 0
	/* other cases? */
	else if (XXX == rt)
RT_S2Q_CLTIP	/* client IP address */
RT_S2Q_CID	/* close SMTPS (id) */
RT_S2Q_CLTIPV4	/* client IPv4 address */
RT_S2Q_CLTIPV6	/* client IPv6 address */

#endif /* 0 */
	else
		goto err2;

  done:
	if (!inwaitq) {
		ret = sm_rcb_close_dec(qss_ctx->qss_com.rcbcom_rdrcb);
		(void) sm_rcb_open_rcv(qss_ctx->qss_com.rcbcom_rdrcb);
	}
	if (ret == SM_SUCCESS && inwaitq)
		return QMGR_R_ASYNC;
	return rv;

	/* preserve original error code! */
  err2:
	/* use rcb functions that don't do check the state */
	if (!inwaitq)
		(void) sm_rcb_close_decn(qss_ctx->qss_com.rcbcom_rdrcb);
  error:
	/* open rcb for receiving next record */
	if (!inwaitq)
		(void) sm_rcb_open_rcvn(qss_ctx->qss_com.rcbcom_rdrcb);

#if 0
  errret:
#endif
	QM_LEV_DPRINTFC(QDC_S2Q, 0, (QM_DEBFP, "sev=DBG, func=qm_fr_ss_react, id=%d, stat=%d, error out, rt=%#x, v=%d, ret=%r, rv=%r, inwaitq=%d\n",
qss_ctx->qss_id, qss_ctx->qss_status, rt, v, ret, rv, inwaitq));
	if (rcbe != NULL)
		sm_rcbe_free(rcbe);
	if (inwaitq) {
		if (sm_is_err(rv))	/* shouldn't happen! */
			QM_LEV_DPRINTFC(QDC_S2Q, 0, (QM_DEBFP, "sev=ERROR, func=qm_fr_ss_react, rv=%r, inwaitq=%d\n", rv, inwaitq));
		return QMGR_R_ASYNC;
	}
	return rv;
#undef FST_QSS_TA_LOCKED
}

/*
**  QM_FR_SS -- SMTPS to QMGR interface
**
**	Parameters:
**		tsk -- evthr task
**
**	Returns:
**		usual sm_error code
**
**	Called by: sm_qmgr_smtps() for read events
*/

sm_ret_T
qm_fr_ss(sm_evthr_task_P tsk)
{
	int fd, r;
	sm_ret_T ret;
	qmgr_ctx_P qmgr_ctx;
	qss_ctx_P qss_ctx;
#if QM_TIMING
	uint32_t frt;
#endif
	id_count_T idc;

	SM_IS_EVTHR_TSK(tsk);
	qss_ctx = (qss_ctx_P) tsk->evthr_t_actx;
	SM_IS_QSS_CTX(qss_ctx);
	qmgr_ctx = qss_ctx->qss_qmgr_ctx;
	SM_IS_QMGR_CTX(qmgr_ctx);

	fd = tsk->evthr_t_fd;	/* checked in caller */
	ret = sm_rcb_rcv(fd, qss_ctx->qss_com.rcbcom_rdrcb, QSS_RC_MINSZ);

	QM_LEV_DPRINTTC(QDC_S2Q, 4, (QM_DEBFP, "sev=DBG, func=qm_fr_ss, fd=%d, got ret=%r, buf=%d, len=%d\n", fd, ret,
qss_ctx->qss_com.rcbcom_rdrcb->sm_rcb_base[0], sm_rcb_getlen(qss_ctx->qss_com.rcbcom_rdrcb)), qmgr_ctx->qmgr_ev_ctx->evthr_c_time);
	if (ret > 0)
		return EVTHR_WAITQ;
	else if (0 == ret) {
		ret = sm_rcb_close_rcv(qss_ctx->qss_com.rcbcom_rdrcb);

		/* start appropriate function ... */
QM_HRBT_DPRINTF(QDC_S2Q_TM, 3, (QM_DEBFP, "func=qm_fr_ss, qm_fr_ss_react=before\n"));
		ret = qm_fr_ss_react(qss_ctx, tsk
#if QM_TIMING
				, &frt
#endif
				);
QM_HRBT_DPRINTF(QDC_S2Q_TM, 3, (QM_DEBFP, "func=qm_fr_ss, qm_fr_ss_react=after, rt=%x\n", frt));
		if (sm_is_err(ret))
			goto termit;	/* too harsh? */
		else if (QMGR_R_WAITQ == ret)
			return EVTHR_WAITQ;
		else if (QMGR_R_ASYNC == ret)
			return EVTHR_OK;
		else if (EVTHR_DEL == ret)
			goto termit;	/* terminate this client */
		else
			return ret;
	}
	else if (SM_IO_EOF == ret) {
		ret = sm_rcb_close_rcv(qss_ctx->qss_com.rcbcom_rdrcb);
  termit:
		QM_LEV_DPRINTTC(QDC_S2Q, 1, (QM_DEBFP, "sev=DBG, func=qm_fr_ss, task=%p, status=terminate, rcbcom_tsk=%p, ret=%r\n", tsk, qss_ctx->qss_com.rcbcom_tsk, ret), qmgr_ctx->qmgr_ev_ctx->evthr_c_time);
		close(fd);

		/*
		**  XXX don't do this if we returned the task already!
		**  we need to lock the task first and invalidate it
		**  then. How to do this properly?
		**  It seems we need another evthr function...
		*/

#if 0
		tsk->evthr_t_fd = INVALID_FD;	/* make it invalid */
#endif

		r = pthread_mutex_lock(&qmgr_ctx->qmgr_mutex);
		SM_LOCK_OK(r);
		if (r != 0) {
			sm_log_write(qmgr_ctx->qmgr_lctx,
				QM_LCAT_SMTPS, QM_LMOD_FROM_SMTPS,
				SM_LOG_CRIT, 4,
				"sev=CRIT, func=qm_fr_ss, lock=%d",
				r);
			goto error;
		}

		ret = sm_id2idc(qss_ctx->qss_ta_id, &idc);
		if (ret == SM_SUCCESS && qmgr_ctx->qmgr_idc < idc)
			qmgr_ctx->qmgr_idc = idc;

		/* Close all outstanding sessions and update internal data */
		(void) qss_ctx_close(qss_ctx);

		/* Don't crash while holding lock?? */
		SM_ASSERT(qmgr_ctx->qmgr_ss_li.qm_gli_nfd > 0);

		/* free sss ctx */
		qmgr_ctx->qmgr_ss_li.qm_gli_nfd--;
		qmgr_ctx->qmgr_ss_li.qm_gli_used &= ~(qss_ctx->qss_bit);
		if (qss_ctx->qss_com.rcbcom_rdrcb != NULL)
			(void) sm_rcb_close_decn(qss_ctx->qss_com.rcbcom_rdrcb);

		r = pthread_mutex_unlock(&qmgr_ctx->qmgr_mutex);
		SM_ASSERT(0 == r);
		return EVTHR_DEL;
	}
	else /* if (ret < 0) */ {
		QM_LEV_DPRINTFC(QDC_S2Q, 0, (QM_DEBFP, "sev=ERROR, func=qm_fr_ss, ret=%r, errno=%d\n", ret, errno));
	}
	QM_LEV_DPRINTFC(QDC_S2Q, 0, (QM_DEBFP, "sev=ERROR, func=qm_fr_ss, fd=%d\n", fd));

 error:
	return EVTHR_DEL;
}


syntax highlighted by Code2HTML, v. 0.9.1