/*
 * 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: smtpch.c,v 1.23 2007/08/19 17:39:33 ca Exp $")

#include "sm/common.h"
#include "sm/assert.h"
#include "sm/error.h"
#include "sm/io.h"
#include "sm/sysexits.h"
#include "sm/str.h"
#include "sm/string.h"
#include "sm/limits.h"
#include "sm/net.h"
#include "statethreads/st.h"
#include "sm/ctype.h"
#include "sm/memops.h"
#include "sm/signal.h"
#include "sm/wait.h"
#include "sm/qmgrcomm.h"
#include "sm/hostname.h"
#include "sm/misc.h"
#include "smtpc.h"
#include "sm/da.h"
#include "sm/das.h"
#include "c2q.h"
#include "smtpch.h"
#include "sm/log.h"
#include "sm/version.h"
#include "log.h"
#include "sm/resource.h"
#include "sm/smtpdef.h"
#include "sm/sm-conf.h"
#include "sm/sm-conf-prt.h"
#include "sm/sccnfdef.h"
#include "sm/confsetpath.h"

static sm_file_T	*Sc_Errfp = smiolog;

/*
**  SC_INIT0 -- initialize SMTPC context
**	before options are read
**
**	Parameters:
**		sc_ctx -- SMTPC context
**
**	Returns:
**		usual sm_error code
*/

sm_ret_T
sc_init0(sc_ctx_P sc_ctx)
{
	sm_ret_T ret;

	SM_IS_SC_CTX(sc_ctx);
	ret = sm_log_create(NULL, &sc_ctx->scc_lctx, &sc_ctx->scc_lcfg);
	if (sm_is_err(ret))
		return ret;
	ret = sm_log_setfp_fd(sc_ctx->scc_lctx, Sc_Errfp, SMIOLOG_FILENO);
	if (sm_is_err(ret))
		return ret;

	sc_ctx->scc_cnf.sc_cnf_cdb_base = "";
	sc_ctx->scc_cnf.sc_cnf_smtpcsock = smsmtpcsock;
	sc_ctx->scc_cnf.sc_cnf_timeout = SC_IO_TIMEOUT;
	sc_ctx->scc_cnf.sc_cnf_qmgr_tmo = SC_RCB_SND_TMO;
	sc_ctx->scc_cnf.sc_cnf_lmtpsock = LMTPSOCK;
	sc_ctx->scc_cnf.sc_cnf_port = SMTPC_PORT;
	sc_ctx->scc_cnf.sc_cnf_loglevel = UINT_MAX;
	sc_ctx->scc_cnf.sc_cnf_wait4srv = 1;
	sc_ctx->scc_cnf.sc_cnf_id = SMTPC_ID;
	SC_MAX_WAIT_THREADS(sc_ctx) = 0;
	SC_MIN_WAIT_THREADS(sc_ctx) = 2;

#if MTA_USE_TLS
	ret = sm_tlsversionok();
	if (sm_is_err(ret))
		return ret;
#endif /* MTA_USE_TLS */

	return ret;
}

/*
**  SC_INIT_CHK -- check configuration etc (after options are read)
**	after options are read
**
**	Parameters:
**		sc_ctx -- SMTPC context
**
**	Returns:
**		usual sm_error code
*/

sm_ret_T
sc_init_chk(sc_ctx_P sc_ctx)
{
	sm_ret_T ret;

	SM_IS_SC_CTX(sc_ctx);
	ret = SM_SUCCESS;
#if MTA_USE_TLS
	ret = sm_tlsversionok();
	if (sm_is_err(ret))
		return ret;
#endif
	return ret;
}

/*
**  SC_INIT1 -- initialize SMTPC context
**	after options are read
**
**	Parameters:
**		sc_ctx -- SMTPC context
**
**	Returns:
**		usual sm_error code
*/

sm_ret_T
sc_init1(sc_ctx_P sc_ctx)
{
	sm_ret_T ret;
	size_t l;

	SM_IS_SC_CTX(sc_ctx);
	ret = sm_log_setdebuglevel(sc_ctx->scc_lctx, sc_ctx->scc_cnf.sc_cnf_loglevel);
	if (sm_is_err(ret))
		return ret;
	l = SC_MAX_THREADS(sc_ctx) * sizeof(*(sc_ctx->scc_scts));
	sc_ctx->scc_scts = sm_zalloc(l);
	if (NULL == sc_ctx->scc_scts)
		return sm_error_temp(SM_EM_SMTPC, ENOMEM);

	/* convert timeout from s to micro seconds for statethreads */
	sc_ctx->scc_qmgr_tmo = SEC2USEC(sc_ctx->scc_cnf.sc_cnf_qmgr_tmo);

	/* might have been set in the configuration file */
	if (NULL == sc_ctx->scc_hostname) {
		ret = sm_myhostname(&sc_ctx->scc_hostname);
		if (sm_is_err(ret)) {
			sm_log_write(sc_ctx->scc_lctx,
				SC_LCAT_INIT, SC_LMOD_CONFIG,
				SM_LOG_ERR, 1,
				"sev=ERROR, func=sc_init1, status=cannot_determine_my_hostname, ret=%m"
				, ret);
			return ret;
		}
	}

	ret = sm_gen_conf_path(sc_ctx->scc_cnf.sc_cnf_cdb_base,
		sc_ctx->scc_cnf.sc_cnf_smtpcsock,
		smsmtpcsock,
		&sc_ctx->scc_cnf.sc_cnf_smtpcsock_abs,
		&sc_ctx->scc_cnf.sc_cnf_smtpcsock_alloc);
	if (sm_is_err(ret))
		return ret;

	ret = sm_gen_conf_path(sc_ctx->scc_cnf.sc_cnf_cdb_base,
		sc_ctx->scc_cnf.sc_cnf_lmtpsock,
		LMTPSOCK,
		&sc_ctx->scc_cnf.sc_cnf_lmtpsock_abs,
		&sc_ctx->scc_cnf.sc_cnf_lmtpsock_alloc);
	if (sm_is_err(ret))
		return ret;

	ret = cdb_start(sc_ctx->scc_cnf.sc_cnf_cdb_base, &sc_ctx->scc_cdb_ctx);
	if (sm_is_err(ret)) {
		sm_log_write(sc_ctx->scc_lctx,
			SC_LCAT_INIT, SC_LMOD_CONFIG,
			SM_LOG_ERR, 1,
			"sev=ERROR, func=sc_init1, cdb_start=%m", ret);
		return ret;
	}

	ret = c2q_init(sc_ctx, &c2q_ctx, SC_MAX_THREADS(sc_ctx));
	if (sm_is_err(ret)) {
		sm_log_write(sc_ctx->scc_lctx,
			SC_LCAT_INIT, SC_LMOD_CONFIG,
			SM_LOG_ERR, 1,
			"sev=ERROR, func=sc_init1, c2q_init=%m", ret);
		return ret;
	}
#if MTA_USE_TLS
	ret = sm_tls_init_library(&sc_ctx->scc_tlsl_ctx);
	if (sm_is_success(ret)) {
		char confdir[PATH_MAX];

		ret = sm_dirname(sc_ctx->scc_cnf.sc_cnf_conffile, confdir, sizeof(confdir));
		if (sm_is_err(ret)) {
			sm_log_write(sc_ctx->scc_lctx,
				SC_LCAT_INIT, SC_LMOD_CONFIG,
				SM_LOG_ERR, 1,
				"sev=ERROR, func=sc_init1, sm_dirname=%m", ret);
			return ret;
		}

		TLS_GEN_PATHS(sc_ctx->scc_cnf.sc_cnf_tls, sc_ctx->scc_cnf, sc);
		ret = sm_tls_init(sc_ctx->scc_tlsl_ctx, &sc_ctx->scc_ssl_ctx,
			TLS_I_CLT, false, &sc_ctx->scc_cnf.sc_cnf_tls);
		if (sm_is_success(ret))
			SC_SET_FLAG(sc_ctx, SCC_FL_TLS_OK);
		else {
			sm_log_write(sc_ctx->scc_lctx,
				SC_LCAT_INIT, SC_LMOD_CONFIG,
				SM_LOG_INFO, 9,
				"sev=INFO, func=sc_init1, sm_tls_init=%m", ret);
		}
	}
	ret = SM_SUCCESS;	/* fake it */
#endif /* MTA_USE_TLS */

	SC_SET_FLAG(sc_ctx, SCC_FL_INIT);
	return ret;

#if MTA_USE_TLS
  enomem:
	return sm_error_temp(SM_EM_SMTPC, ENOMEM);
#endif
}

/*
**  SC_SET_THREAD_THROTTLING -- set appropriate values for number of threads
**
**	Parameters:
**		sc_ctx -- SMTPC context
**
**	Returns:
**		none.
*/

void
sc_set_thread_throttling(sc_ctx_P sc_ctx)
{
	uint max_wait_threads;

	/*
	**  For simplicity, the minimal size of thread pool is considered
	**  as a maximum number of spare threads (max_wait_threads) that
	**  will be created upon server startup. The pool size can grow up
	**  to the max_threads value. Note that this is a per listening
	**  socket limit. It is also possible to limit the total number of
	**  threads for all sockets rather than impose a per socket limit.
	**
	**  Calculate total values across all processes.
	**  All numbers are per listening socket.
	*/

	/* just a shorter name; will be reassigned before return */
	max_wait_threads = SC_MAX_WAIT_THREADS(sc_ctx);
	if (0 == max_wait_threads)
		max_wait_threads = MAX_WAIT_THREADS_DEFAULT;

	/* Assuming that each client session needs SC_FD_PER_THREAD fds */
	if (SC_MAX_THREADS(sc_ctx) == 0)
		SC_MAX_THREADS(sc_ctx) = st_getfdlimit() / SC_FD_PER_THREAD;
	if (max_wait_threads > SC_MAX_THREADS(sc_ctx))
		max_wait_threads = SC_MAX_THREADS(sc_ctx);

	if (SC_MIN_WAIT_THREADS(sc_ctx) > max_wait_threads)
		SC_MIN_WAIT_THREADS(sc_ctx) = max_wait_threads;

	SC_MAX_WAIT_THREADS(sc_ctx) = max_wait_threads;
}

/*
**  SC_START_THREADS -- start threads
**
**	Parameters:
**		sc_ctx -- SMTPC context
**
**	Returns:
**		none; may exit if no threads have been started.
*/

void
sc_start_threads(sc_ctx_P sc_ctx)
{
	uint n, threads;

	/* Create connections handling threads */
	sm_log_write(sc_ctx->scc_lctx,
		SC_LCAT_INIT, SC_LMOD_INTERN,
		SM_LOG_INFO, 10,
		"sev=INFO, func=sc_start_threads, version=%s, index=%d, pid=%d, threads=%d"
		, MTA_VERSION_STR, sc_index, sc_pid
		, SC_MAX_WAIT_THREADS(sc_ctx));
	SC_WAIT_THREADS(sc_ctx) = 0;
	SC_IDLE_THREADS(sc_ctx) = 0;
	SC_CLOSING_THREADS(sc_ctx) = 0;
	SC_BUSY_THREADS(sc_ctx) = 0;
	SC_MAX_USED_THREADS(sc_ctx) = 0;
	SC_RQST_COUNT(sc_ctx) = 0;
#if SC_STATS
	SC_MAIL_COUNT(sc_ctx) = 0;
	SC_RCPT_COUNT(sc_ctx) = 0;
	SC_TA_CNT_OK(sc_ctx) = 0;
	SC_RCPT_CNT_OK(sc_ctx) = 0;
	SC_SE_REUSE(sc_ctx) = 0;
	SC_OPEN_SE(sc_ctx) = 0;
	SC_MAX_OPEN_SE(sc_ctx) = 0;
#endif /* SC_STATS */
	threads = 0;
	for (n = 0; n < SC_MAX_WAIT_THREADS(sc_ctx); n++) {
		if (st_thread_create(sc_hdl_requests, (void *) sc_ctx, 0, 0) == NULL) {
			sm_log_write(sc_ctx->scc_lctx,
				SC_LCAT_INIT, SC_LMOD_INTERN,
				SM_LOG_ERR, 2,
				"sev=ERROR, func=sc_start_threads, index=%d, pid=%d, wait=%u, idle=%u, busy=%u, st_thread_create()=failed"
				, sc_index, sc_pid
				, SC_WAIT_THREADS(sc_ctx)
				, SC_IDLE_THREADS(sc_ctx)
				, SC_BUSY_THREADS(sc_ctx)
				);
		}
		else {
			SC_WAIT_THREADS(sc_ctx)++;
			sm_log_write(sc_ctx->scc_lctx,
				SC_LCAT_INIT, SC_LMOD_INTERN,
				SM_LOG_INFO, 14,
				"sev=INFO, func=sc_start_threads, index=%d, pid=%d, wait=%u, idle=%u, busy=%u, sc_hdl_requests=created"
				, sc_index, sc_pid
				, SC_WAIT_THREADS(sc_ctx)
				, SC_IDLE_THREADS(sc_ctx)
				, SC_BUSY_THREADS(sc_ctx)
				);
			threads++;
		}
	}
	if (0 == threads) {
		c2q_stop(&c2q_ctx);
		sm_log_write(sc_ctx->scc_lctx,
			SC_LCAT_INIT, SC_LMOD_INTERN,
			SM_LOG_ERR, 0,
			"sev=ERROR, func=sc_start_threads, status=no_thread_started__terminating");
		exit(EX_OSERR);
	}
}

/*
**  GET_NTHR_ID -- get a free thread id
**
**	Parameters:
**		sc_ctx -- SMTPC context
**
**	Returns:
**		<SC_MAX_THREADS(sc_ctx): free thread id
**		>=SC_MAX_THREADS(sc_ctx): no unused thread available
**			Notice: maybe use >=0: ok, <0: error?
*/

static uint
get_nthr_id(sc_ctx_P sc_ctx)
{
	uint i;

	SM_IS_SC_CTX(sc_ctx);
	for (i = 0; i < SC_MAX_THREADS(sc_ctx); i++)
	{
		if (NULL == (sc_ctx->scc_scts)[i])
			break;
	}
	return i;
}

/*
**  SC_HDL_REQUESTS -- handle requests from QMGR (thread)
**
**	Parameters:
**		arg -- SMTPC context
**
**	Returns:
**		none (NULL)
*/

void *
sc_hdl_requests(void *arg)
{
	uint thr_id;
	int r;
	sc_ctx_P sc_ctx;
	sc_t_ctx_P sc_t_ctx;
	sm_ret_T ret;
	sc_sess_P sess;

	sess = NULL;
	sc_ctx = (sc_ctx_P) arg;
	SM_IS_SC_CTX(sc_ctx);

	/* get a free thread id */
	thr_id = get_nthr_id(sc_ctx);
	if (thr_id >= SC_MAX_THREADS(sc_ctx)) {
		sm_log_write(sc_ctx->scc_lctx,
			SC_LCAT_CLIENT, SC_LMOD_CLIENT,
			SM_LOG_ERR, 1,
			"sev=ERROR, func=sc_hdl_requests, process=%d, pid=%d, thread=%u, max_threads=%u, status=cannot_find_thread_slot"
			, sc_index, sc_pid, thr_id, SC_MAX_THREADS(sc_ctx));
SM_ASSERT(0);
		SC_WAIT_THREADS(sc_ctx)--;
		return NULL;		/* question: some error?? */
	}

	/* allocate thread context */
	ret = sc_t_ctx_new(sc_ctx, &sc_t_ctx, thr_id);
	if (sm_is_err(ret)) {
		sm_log_write(sc_ctx->scc_lctx,
			SC_LCAT_CLIENT, SC_LMOD_CLIENT,
			SM_LOG_ERR, 1,
			"sev=ERROR, func=sc_hdl_requests, process=%d, pid=%d, sc_t_ctx_new=%m"
			, sc_index, sc_pid, ret);
		SC_WAIT_THREADS(sc_ctx)--;
		return NULL;		/* question: some error?? */
	}

	/* allocate session context */
	ret = sc_sess_new(&sess, sc_t_ctx);
	if (sm_is_err(ret)) {
		sm_log_write(sc_ctx->scc_lctx,
			SC_LCAT_CLIENT, SC_LMOD_CLIENT,
			SM_LOG_ERR, 1,
			"sev=ERROR, func=sc_hdl_requests, process=%d, pid=%d, sc_sess_new=%m"
			, sc_index, sc_pid, ret);
		(void) sc_t_ctx_free(sc_ctx, sc_t_ctx);
		SC_WAIT_THREADS(sc_ctx)--;
		return NULL;		/* question: some error?? */
	}

	sc_t_ctx->sct_status = SC_T_FREE;
	if (sc_ctx->scc_cnf.sc_cnf_debug > 3) {
		sm_log_write(sc_ctx->scc_lctx,
			SC_LCAT_CLIENT, SC_LMOD_CLIENT,
			SM_LOG_INFO, 14,
			"sev=INFO, func=sc_hdl_requests, thread=%u, status=free, sc_t_ctx->sct_sess=%p, waitthr=%u, busythr=%u"
			, thr_id, sc_t_ctx->sct_sess, SC_WAIT_THREADS(sc_ctx)
			, SC_BUSY_THREADS(sc_ctx));
	}

	/* SC_WAIT_THREADS(sc_ctx)++; */

	/* notify c2q thread that we are ready to go */
	if (SC_IS_FLAG(sc_ctx, SCC_FL_INIT) && !SC_IS_FLAG(sc_ctx, SCC_FL_COMMOK)) {
		SC_SET_FLAG(sc_ctx, SCC_FL_COMMOK);
		r = st_cond_signal(c2q_ctx.c2q_cond_rd);
		if (0 == r)
			SC_SET_FLAG(sc_ctx, SCC_FL_NOTIFIED);
		else
			sm_log_write(sc_ctx->scc_lctx,
				SC_LCAT_CLIENT, SC_LMOD_CLIENT,
				SM_LOG_ERR, 4,
				"sev=ERROR, func=sc_hdl_requests, thread=%u, st_cond_signal=%x"
				, thr_id, r);
	}

	while (SC_AVAIL_THREADS(sc_ctx) <= SC_MAX_WAIT_THREADS(sc_ctx)
	       || sc_t_ctx->sct_status != SC_T_FREE)
	{
		/* wait for a request from QMGR (indirectly) */
		r = st_cond_timedwait(sc_t_ctx->sct_cond_rd, SEC2USEC(REQUEST_TIMEOUT));

		if (SC_IS_FLAG(sc_ctx, SCC_FL_SHUTDOWN)) {
			/* shutdown session and terminate */
			ret = sc_shutdown_session(sc_t_ctx, false);
			SC_LEV_DPRINTF(sc_t_ctx->sct_sc_ctx, 1, (smioerr,
				"func=sc_hdl_requests, sc_t_ctx=%p, shutdown=%m\n"
				, sc_t_ctx, ret));
			break;
		}
		if (r == -1) {
			if (EINTR == errno)
				continue;
			sm_log_write(sc_ctx->scc_lctx,
				SC_LCAT_CLIENT, SC_LMOD_CLIENT,
				SM_LOG_INFO, SC_T_BUSY == sc_t_ctx->sct_status ? 9 : 19,
				"sev=INFO, func=sc_hdl_requests, thread=%u, status=timeout_waiting_for_QMGR, thread_stat=%d"
				, thr_id, sc_t_ctx->sct_status);

			/*
			**  "race condition": the wait could have timed out,
			**  but the thread just got work, i.e., it is marked
			**  as busy by sc_rcb_from_qmgr().
			**  this seems like a side effect from the statethreads
			**  implementation: the thread is not activated as
			**  long as another thread is running.
			*/

			if (sc_t_ctx->sct_status != SC_T_BUSY) {
				ret = sc_timeout_session(sc_t_ctx);
				continue;
			}
		}

		/* alread set in sc_rcb_from_qmgr() */
		/* sc_t_ctx->sct_status = SC_T_BUSY; */

		SC_WAIT_THREADS(sc_ctx)--;
		SC_BUSY_THREADS(sc_ctx)++;
		if (SC_MAX_USED_THREADS(sc_ctx) < SC_BUSY_THREADS(sc_ctx))
			SC_MAX_USED_THREADS(sc_ctx) = SC_BUSY_THREADS(sc_ctx);
		SM_ASSERT(SC_WAIT_THREADS(sc_ctx) >= SC_IDLE_THREADS(sc_ctx) +
						SC_CLOSING_THREADS(sc_ctx));
		if (SC_AVAIL_THREADS(sc_ctx) < SC_MIN_WAIT_THREADS(sc_ctx)
		    && SC_TOTAL_THREADS(sc_ctx) < SC_MAX_THREADS(sc_ctx)
		    && !SC_IS_FLAG(sc_ctx, SCC_FL_SHUTDOWN))
		{
			/* Create another spare thread */
			if (NULL == st_thread_create(sc_hdl_requests, (void *)sc_ctx, 0, 0))
			{
				sm_log_write(sc_ctx->scc_lctx,
					SC_LCAT_CLIENT, SC_LMOD_CLIENT,
					SM_LOG_ERR, 1,
					"sev=ERROR, func=sc_hdl_requests, process=%d, pid=%d, status=cannot_create_thread"
					, sc_index, sc_pid);
			}
			else {
				SC_WAIT_THREADS(sc_ctx)++;
				sm_log_write(sc_ctx->scc_lctx,
					SC_LCAT_INIT, SC_LMOD_INTERN,
					SM_LOG_INFO, 14,
					"sev=INFO, func=sc_hdl_requests, index=%d, pid=%d, wait=%u, idle=%u, busy=%u, sc_hdl_requests=created"
					, sc_index, sc_pid
					, SC_WAIT_THREADS(sc_ctx)
					, SC_IDLE_THREADS(sc_ctx)
					, SC_BUSY_THREADS(sc_ctx)
					);
			}
		}

		ret = sc_hdl_session(sc_t_ctx);
		/* any error will be handled below after some cleanup */

/*  sess_error: */
		SC_WAIT_THREADS(sc_ctx)++;
		SM_ASSERT(SC_BUSY_THREADS(sc_ctx) > 0);
		SC_BUSY_THREADS(sc_ctx)--;

		/* this thread is only free if there is no active session */
		if (NULL == sc_t_ctx->sct_sess || SCSE_ST_NONE == sc_t_ctx->sct_sess->scse_state)
			sc_t_ctx->sct_status = SC_T_FREE;
		else {
			SC_IDLE_THREADS(sc_ctx)++;
			sm_log_write(sc_ctx->scc_lctx,
				SC_LCAT_CLIENT, SC_LMOD_CLIENT,
				SM_LOG_INFO, 4,
				"sev=INFO, func=sc_hdl_requests, sess=%p, se_id=%s, status=idle, busy=%u, wait=%u, idle=%u"
				, sc_t_ctx->sct_sess
				, sc_t_ctx->sct_sess->scse_id
				, SC_BUSY_THREADS(sc_ctx), SC_WAIT_THREADS(sc_ctx)
				, SC_IDLE_THREADS(sc_ctx));
			sc_t_ctx->sct_status = SC_T_IDLE;
		}

		/* possible error from sc_hdl_session() */
		if (sm_is_err(ret)) {
			/* complain? or did sc_hdl_session() do that? */
			break;	/* question: some error?? */
		}
	}
	/* XXX preserve ret from possible error above? */

	ret = sc_sess_free(sc_t_ctx->sct_sess, sc_t_ctx);
	if (sm_is_err(ret))
		sm_log_write(sc_ctx->scc_lctx,
			SC_LCAT_CLIENT, SC_LMOD_CLIENT,
			SM_LOG_ERR, 4,
			"sev=ERROR, func=sc_hdl_requests, thread=%u, sc_sess_free=%m"
			, thr_id, ret);

	ret = sc_t_ctx_free(sc_ctx, sc_t_ctx);
	if (sm_is_err(ret))
		sm_log_write(sc_ctx->scc_lctx,
			SC_LCAT_CLIENT, SC_LMOD_CLIENT,
			SM_LOG_ERR, 4,
			"sev=ERROR, func=sc_hdl_requests, thread=%u, sc_t_ctx_free=%m"
			, thr_id, ret);

	if (sc_ctx->scc_cnf.sc_cnf_debug > 3)
		sm_log_write(sc_ctx->scc_lctx,
			SC_LCAT_CLIENT, SC_LMOD_CLIENT,
			SM_LOG_INFO, 18,
			"sev=INFO, func=sc_hdl_requests, thread=%u, status=terminates, ret=%m, busy=%u, wait=%u"
			, thr_id, ret, SC_BUSY_THREADS(sc_ctx)
			, SC_WAIT_THREADS(sc_ctx));
	SC_WAIT_THREADS(sc_ctx)--;
	if (0 == SC_WAIT_THREADS(sc_ctx) && 0 == SC_BUSY_THREADS(sc_ctx) &&
	    0 == SC_IDLE_THREADS(sc_ctx) && 0 == SC_CLOSING_THREADS(sc_ctx))
	{
		sm_log_write(sc_ctx->scc_lctx,
			SC_LCAT_CLIENT, SC_LMOD_CLIENT,
			SM_LOG_INFO, 4,
			"sev=INFO, func=sc_hdl_requests, status=terminating, busy=%u, wait=%u, idle=%u, closing=%u"
			, SC_BUSY_THREADS(sc_ctx), SC_WAIT_THREADS(sc_ctx)
			, SC_IDLE_THREADS(sc_ctx), SC_CLOSING_THREADS(sc_ctx));
		exit(0);
	}
	return NULL;
}

/*
**  SC_RD_HDRMODS -- read header modification list
**
**	Parameters:
**		sc_ta -- transaction
**		rcb -- RCB
**
**	Returns:
**		usual sm_error code
*/

static sm_ret_T
sc_rd_hdrmods(sc_ta_P sc_ta, sm_rcb_P rcb)
{
	sm_hdrmod_P sm_hdrmod;
	uint32_t l, rt, n1, n2;
	sm_ret_T ret;

	while (!SM_RCB_ISEOB(rcb)) {
		ret = sm_rcb_peek2uint32(rcb, &l, &rt);
		if (sm_is_err(ret))
			return ret;
		if (rt != RT_Q2C_HM_T_P || l != 8)
			return SM_SUCCESS;
		ret = sm_hdrmod_new(&sc_ta->scta_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_Q2C_HM_T_P || l != 8) {
			return sm_is_err(ret) ? ret
				: sm_error_perm(SM_EM_SMTPC, SM_E_PR_ERR);
		}
		sm_hdrmod->sm_hm_type = n1;
		if (SM_HM_TYPE_APPEND == n1 ||
		    SM_HM_TYPE_INSERT == n1 ||
		    SM_HM_TYPE_REMOVE == n1 ||
		    SM_HM_TYPE_REPLACE == n1)
			SCTA_SET_FLAG(sc_ta, SCTA_FL_HDR_SCAN);
		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(&sc_ta->scta_hdrmodhd);
			return ret;
		}
		if (RT_Q2C_HM_T_P == rt && 8 == l)
			continue;
		if (rt != RT_Q2C_HM_HDR)
			break;

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

/*
**  SC_HANDLE_SESSION -- handle SMTPC session/transaction
**
**	Parameters:
**		sc_t_ctx -- SMTPC thread context
**
**	Returns:
**		usual sm_error code
*/

sm_ret_T
sc_hdl_session(sc_t_ctx_P sc_t_ctx)
{
	uint32_t v, l, rt;
	sm_ret_T ret, res;
	bool gotport;
	off_t off;
	sc_sess_P sc_sess;
	sc_ta_P sc_ta;
	sm_str_P rcpt_pa;
	rcpt_idx_T rcpt_idx;
	sc_rcpt_P rcpt;
	sc_ctx_P sc_ctx;

	SM_IS_SC_T_CTX(sc_t_ctx);
	sc_sess = sc_t_ctx->sct_sess;
	SM_IS_SC_SE(sc_sess);
	sc_ctx = sc_t_ctx->sct_sc_ctx;
	SM_IS_SC_CTX(sc_ctx);

	SC_LEV_DPRINTF(sc_ctx, 3, (smioerr, "func=sc_hdl_session, se_id=%s, state=%x\n", sc_sess->scse_id, sc_sess->scse_state));
	ret = SM_SUCCESS;
	gotport = false;
	SCSE_CLR_FLAG(sc_sess, SCSE_FL_SND_ST);
	SCSE_SET_FLAG(sc_sess, SCSE_FL_SE_CLS2QMGR);

	/* always get server IP address */
	/* fixme: server IP address; must match structure later on! */
	ret = sm_rcb_get3uint32(sc_t_ctx->sct_rcb, &l, &rt, &v);
	if (sm_is_err(ret) || l != 4)
		goto error;
	if (RT_Q2C_SRVPORT == rt) {
		sc_sess->scse_rmt_addr.sin.sin_port = htons((short) v);
		gotport = true;
		ret = sm_rcb_get3uint32(sc_t_ctx->sct_rcb, &l, &rt, &v);
	}
	if (RT_Q2C_DA_IDX == rt) {
		sc_sess->scse_da_idx = v;
		ret = sm_rcb_get3uint32(sc_t_ctx->sct_rcb, &l, &rt, &v);
	}

	SC_LEV_DPRINTF(sc_ctx, 5, (smioerr, "func=sc_hdl_session, l=%d, rt=%x, v=%x, ret=%m\n", l, rt, v, ret));
	if (sm_is_err(ret) || l != 4 || (rt != RT_Q2C_SRVIPV4 && rt != RT_Q2C_LMTP))
		goto error;

	/*
	**  NOTE: v is used below, do NOT transfer data after RT_Q2C_SRVIPV4
	**  that is read before this section!
	*/

	if (SCSE_ST_NEW == sc_sess->scse_state) {
		/* get connection data */

		/*
		**  HACK ahead: address LMTP_IPV4_ADDR is "special",
		**  RT_Q2C_LMTP is not yet used!
		**  da_idx can be used to select the "protocol".
		*/

		if (RT_Q2C_LMTP == rt || v == LMTP_IPV4_ADDR
		    || DA_IDX_LMTP_UNIX == sc_sess->scse_da_idx)
		{
			sc_sess->scse_rmt_addr.sunix.sun_family = AF_UNIX;
			if (strlcpy(sc_sess->scse_rmt_addr.sunix.sun_path,
				sc_ctx->scc_cnf.sc_cnf_lmtpsock_abs,
				sizeof(sc_sess->scse_rmt_addr.sunix.sun_path))
			    >= sizeof(sc_sess->scse_rmt_addr.sunix.sun_path))
				goto error;
#if HAVE_SOCK_UN_SUN_LEN
			sc_sess->scse_rmt_addr.sunix.sun_len =
				strlen(sc_ctx->scc_cnf.sc_cnf_lmtpsock_abs);
#endif
			/* the following hacks should depend on da_idx! */

			/* HACK turn on LMTP */
			SCSE_SET_FLAG(sc_sess, SCSE_FL_LMTP);
			SCSE_SET_FLAG(sc_sess, SCSE_FL_LWR);

			/* HACK Add Return-Path: */
			SCSE_SET_FLAG(sc_sess, SCSE_FL_RETPATH);
		}
		else if (sc_ctx->scc_cnf.sc_cnf_sink != 0) {
			sc_sess->scse_rmt_addr.sin.sin_addr.s_addr = sc_ctx->scc_cnf.sc_cnf_sink;
			sc_sess->scse_rmt_addr.sin.sin_family = AF_INET;
#if 0
			/* should smar port override connect_to default port? */
			if (!gotport)
#endif
				sc_sess->scse_rmt_addr.sin.sin_port =
					htons(sc_ctx->scc_cnf.sc_cnf_port);
		}
		else {
			sc_sess->scse_rmt_addr.sin.sin_addr.s_addr = v;
			sc_sess->scse_rmt_addr.sin.sin_family = AF_INET;
			if (!gotport)
				sc_sess->scse_rmt_addr.sin.sin_port =
					htons(sc_ctx->scc_cnf.sc_cnf_port);
		}

		/* use LMTP? */
		if (DA_IDX_LMTP_UNIX == sc_sess->scse_da_idx ||
		    DA_IDX_LMTP_INET == sc_sess->scse_da_idx)
			SCSE_SET_FLAG(sc_sess, SCSE_FL_LMTP);

		/* port? more? */

		ret = sc_sess_open(sc_t_ctx);
		if (ret != SM_SUCCESS) {
			if (!SCSE_IS_FLAG(sc_sess, SCSE_FL_LOGGED)) {
				sm_log_write(sc_ctx->scc_lctx,
					SC_LCAT_CLIENT, SC_LMOD_CLIENT,
					SM_LOG_WARN, 8,
					"sev=WARN, func=sc_hdl_session, se_id=%s, state=%d, flags=%x, sc_sess_open=%m"
					, sc_sess->scse_id, sc_sess->scse_state
					, sc_sess->scse_flags, ret);
				SCSE_SET_FLAG(sc_sess, SCSE_FL_LOGGED);
			}

			/* send reply codes back, HACK */
			ret = sc_c2q(sc_t_ctx, RT_C2Q_SESTAT, ret, &c2q_ctx);
			SCSE_CLR_FLAG(sc_sess, SCSE_FL_SE_CLS2QMGR);

			/* ret is used as return value for this function */
			goto error;
		}
#if SC_STATS
		++SC_OPEN_SE(sc_ctx);
		if (SC_MAX_OPEN_SE(sc_ctx) < SC_OPEN_SE(sc_ctx))
			SC_MAX_OPEN_SE(sc_ctx) = SC_OPEN_SE(sc_ctx);
#endif /* SC_STATS */
	}
#if 0
	else {
		/* compare  v with sc_sess->scse_rmt_addr.sin.sin_addr.s_addr */
	}
#endif /* 0 */

	SC_LEV_DPRINTF(sc_ctx, 3, (smioerr, "func=sc_hdl_session, after open: state=%d, flags=%x, ret=%m\n", sc_sess->scse_state, sc_sess->scse_flags, ret));
	if (sc_sess->scse_state != SCSE_ST_OPEN) {
		/* session isn't open for some reason: close it and stop */
		goto closesess;
	}

	if (SCSE_IS_FLAG(sc_sess, SCSE_FL_CLOSE))
		goto closesess;

	/* read transaction data */

	sc_ta = sc_sess->scse_ta;
	SC_LEV_DPRINTF(sc_ctx, 3, (smioerr, "func=sc_hdl_session, se_id=%s, ta=%p\n", sc_sess->scse_id, sc_ta));

	if (NULL == sc_ta)
		ret = sc_ta_new(&sc_ta, sc_sess);
	else
		ret = sc_ta_clr(sc_ta);
	if (sm_is_err(ret))
		goto closesess;
	SCTA_SET_STATE(sc_ta, SCTA_INIT);

	/* get transaction data */
	ret = sm_rcb_get2uint32(sc_t_ctx->sct_rcb, &l, &rt);
	SC_LEV_DPRINTF(sc_ctx, 4, (smioerr, "func=sc_hdl_session, ta: l=%d, rt=%x,  ret=%m\n", l, rt, ret));
	if (sm_is_err(ret) || l != SMTP_STID_SIZE ||
	    (rt != RT_Q2C_NTAID && rt != RT_Q2C_NTAIDB && rt != RT_Q2C_NTAIDDB
	     && rt != RT_Q2C_NTAIDD))
		goto abortsess;
	if (RT_Q2C_NTAIDD == rt)
		SCTA_SET_FLAG(sc_ta, SCTA_FL_DELAY);
	else if (RT_Q2C_NTAIDB == rt)
		SCTA_SET_FLAG(sc_ta, SCTA_FL_BOUNCE);
	else if (RT_Q2C_NTAIDDB == rt)
		SCTA_SET_FLAG(sc_ta, SCTA_FL_DBOUNCE);

	ret = sm_rcb_getn(sc_t_ctx->sct_rcb, (uchar *) sc_ta->scta_id, SMTP_STID_SIZE);
	if (sm_is_err(ret))
		goto abortsess;
#if SC_TIMING
	SC_HRBT_DPRINTF((smioerr, "receive da_ta_id=%s\n", sc_ta->scta_id));
#endif

	ret = sm_rcb_get2uint32(sc_t_ctx->sct_rcb, &l, &rt);
	if (sm_is_err(ret) || l != SMTP_STID_SIZE || rt != RT_Q2C_SSTAID)
		goto abortsess;
	ret = sm_rcb_getn(sc_t_ctx->sct_rcb, (uchar *) sc_ta->scta_ssta_id, SMTP_STID_SIZE);
	if (sm_is_err(ret))
		goto abortsess;

	ret = sm_rcb_get3off_t(sc_t_ctx->sct_rcb, &l, &rt, &off);
	SC_LEV_DPRINTF(sc_ctx, 4, (smioerr, "func=sc_hdl_session, where=msg_size, l=%d, rt=%x, off=%lu, ret=%m\n", l, rt, (ulong) off, ret));
	if (sm_is_err(ret) || l != SIZEOF_OFF_T || rt != RT_Q2C_SIZE_B)
		goto abortsess;
	sc_ta->scta_msg_sz_b = off;

	ret = sm_rcb_get2uint32(sc_t_ctx->sct_rcb, &l, &rt);
	SC_LEV_DPRINTF(sc_ctx, 4, (smioerr, "func=sc_hdl_session, where=mail, l=%d, rt=%x,  ret=%m\n", l, rt, ret));
	if (sm_is_err(ret) || rt != RT_Q2C_MAIL)
		goto abortsess;
	ret = sc_mail_new(sc_ta);
	if (sm_is_err(ret))
		goto abortsess;
	ret = sm_rcb_getnstr(sc_t_ctx->sct_rcb, &sc_ta->scta_mail->scm_pa, l);
	if (sm_is_err(ret))
		goto abortsess;

	ret = sm_rcb_get2uint32(sc_t_ctx->sct_rcb, &l, &rt);
	SC_LEV_DPRINTF(sc_ctx, 4, (smioerr, "func=sc_hdl_session, where=cdb, l=%d, rt=%x,  ret=%m\n", l, rt, ret));
	if (sm_is_err(ret) ||
	    ((rt != RT_Q2C_CDBID) && (rt != RT_Q2C_CDBID_NO)
	     && (rt != RT_Q2C_CDBID_HDR))
	   )
		goto abortsess;
	ret = sm_rcb_getn0str(sc_t_ctx->sct_rcb, &sc_ta->scta_cdb_id, l);
	if (sm_is_err(ret)) {
SC_LEV_DPRINTF(sc_ctx, 4, (smioerr, "sev=ERROR, func=sc_hdl_session, where=cdb_id, l=%d, rt=%x, ret=%m\n", l, rt, ret));
		goto abortsess;
	}
	if (RT_Q2C_CDBID_NO == rt)
		SCTA_SET_FLAG(sc_ta, SCTA_FL_NO_BODY);
	else if (RT_Q2C_CDBID_HDR == rt)
		SCTA_SET_FLAG(sc_ta, SCTA_FL_HDR_ONLY);

	ret = sm_rcb_get3uint32(sc_t_ctx->sct_rcb, &l, &rt, &v);
	if (sm_is_err(ret) || l != 4 || rt != RT_Q2C_TA_FLAGS)
		goto abortsess;
	sc_ta->scta_flags |= v;
SC_LEV_DPRINTF(sc_ctx, 1, (smioerr, "sev=INFO, func=sc_hdl_session, flags=%#x\n", v));

/* should be changed later when there is more conf data */
#if MTA_USE_TLS
		ret = sm_rcb_peek2uint32(sc_t_ctx->sct_rcb, &l, &rt);
		if (sm_is_err(ret)) {
			sm_log_write(sc_ctx->scc_lctx,
				SC_LCAT_CLIENT, SC_LMOD_CLIENT,
				SM_LOG_ERR, 12,
				"sev=ERROR, func=sc_hdl_requests, sc_sess=%s, ta_peek=%r"
				, sc_sess->scse_id, ret);
			goto abortsess;
		}
		if (RT_Q2C_MAP_RES_CNF_SRV == rt) {
			ret = sm_rcb_get3uint32(sc_t_ctx->sct_rcb, &l, &rt, &v);
			if (sm_is_err(ret)) goto abortsess;
			/* sc_sess->scse_maprescnf = v; */

			ret = sm_rcb_get2uint32(sc_t_ctx->sct_rcb, &l, &rt);
			if (RT_Q2C_RHS_CNF_SRV != rt) goto abortsess;
			sm_str_clr(sc_sess->scse_str);
			ret = sm_rcb_getstr(sc_t_ctx->sct_rcb, sc_sess->scse_str, l);
			if (sm_is_err(ret)) goto abortsess;
			if (SM_SUCCESS == v && sm_str_getlen(sc_sess->scse_str) > 0) {
				sm_ret_T r;

				r = sc_extra_conf(sc_ctx, sc_sess->scse_str,
						&sc_sess->scse_tlsreq_cnf, &sc_sess->scse_cnf);
				sm_log_write(sc_ctx->scc_lctx,
					SC_LCAT_CLIENT, SC_LMOD_CLIENT,
					SM_LOG_DEBUG, 12,
					"sev=DBG, func=sc_hdl_requests, sc_sess=%s, scse_conf=%#T, ss_sess_conf=%r"
					, sc_sess->scse_id, sc_sess->scse_str, r);
			}
		}
#endif /* MTA_USE_TLS */

	if (!SM_RCB_ISEOB(sc_t_ctx->sct_rcb) &&
	    !sm_is_err(ret = sm_rcb_peek2uint32(sc_t_ctx->sct_rcb, &l, &rt)) &&
	    RT_Q2C_HM_T_P == rt && 8 == l)
	{
		ret = sc_rd_hdrmods(sc_ta, sc_t_ctx->sct_rcb);
		if (sm_is_err(ret))
			goto abortsess;
	}

	do {
		ret = sm_rcb_get3uint32(sc_t_ctx->sct_rcb, &l, &rt, &v);
		SC_LEV_DPRINTF(sc_ctx, 4, (smioerr, "func=sc_hdl_session, where=rcptidx, l=%d, rt=%x, v=%d, ret=%m\n", l, rt, v, ret));
		if (sm_is_err(ret) || l != 4 || rt != RT_Q2C_RCPT_IDX)
			goto abortsess;
		rcpt_idx = v;

		ret = sm_rcb_get2uint32(sc_t_ctx->sct_rcb, &l, &rt);
		SC_LEV_DPRINTF(sc_ctx, 4, (smioerr, "func=sc_hdl_session, where=rcpt, l=%d, rt=%x,  ret=%m\n", l, rt, ret));
		if (sm_is_err(ret) || rt != RT_Q2C_RCPT)
			goto abortsess;

		ret = sm_rcb_getnstr(sc_t_ctx->sct_rcb, &rcpt_pa, l);
		if (sm_is_err(ret))
			goto abortsess;
		if (SCSE_IS_FLAG(sc_sess, SCSE_FL_LWR))
			sm_str2lower(rcpt_pa);

		/* add recipient to list */
		ret = sc_rcpts_new(sc_ta, rcpt_pa, rcpt_idx, &rcpt);
		if (sm_is_err(ret))
			goto abortsess;

/* should be changed later when there is more conf data */
#if MTA_USE_TLS
		if (SM_RCB_ISEOB(sc_t_ctx->sct_rcb))
			break;
		ret = sm_rcb_peek2uint32(sc_t_ctx->sct_rcb, &l, &rt);
		if (sm_is_err(ret)) {
			sm_log_write(sc_ctx->scc_lctx,
				SC_LCAT_CLIENT, SC_LMOD_CLIENT,
				SM_LOG_ERR, 12,
				"sev=ERROR, func=sc_hdl_requests, sc_sess=%s, rcpt_peek=%r"
				, sc_sess->scse_id, ret);
			goto abortsess;
		}
		if (RT_Q2C_MAP_RES_CNF_RCPT == rt) {
			ret = sm_rcb_get3uint32(sc_t_ctx->sct_rcb, &l, &rt, &v);
			if (sm_is_err(ret)) goto abortsess;
			/* sc_sess->scse_maprescnf = v; */

			ret = sm_rcb_get2uint32(sc_t_ctx->sct_rcb, &l, &rt);
			if (RT_Q2C_RHS_CNF_RCPT != rt) goto abortsess;
			sm_str_clr(sc_sess->scse_str);
			ret = sm_rcb_getstr(sc_t_ctx->sct_rcb, sc_sess->scse_str, l);
			if (sm_is_err(ret)) goto abortsess;
			if (SM_SUCCESS == v && sm_str_getlen(sc_sess->scse_str) > 0) {
				sm_ret_T r;

				r = sc_extra_conf(sc_ctx, sc_sess->scse_str,
						&rcpt->scr_tlsreq_cnf, &rcpt->scr_cnf);
				sm_log_write(sc_ctx->scc_lctx,
					SC_LCAT_CLIENT, SC_LMOD_CLIENT,
					SM_LOG_DEBUG, 12,
					"sev=DBG, func=sc_hdl_requests, sc_sess=%s, rcpt_conf=%#T, ss_sess_conf=%r"
					, sc_sess->scse_id, sc_sess->scse_str, r);
			}
		}
#endif /* MTA_USE_TLS */

		SC_LEV_DPRINTF(sc_ctx, 4, (smioerr,
			"func=sc_hdl_sess, ta-id=%s, rcptaddr=%N, l=%d, nrctps=%d\n",
			(char *) (sc_ta->scta_id), rcpt_pa, l,
			sc_ta->scta_rcpts_tot));

	/* Only one recipient allowed for bounces! */
	} while (!SM_RCB_ISEOB(sc_t_ctx->sct_rcb)
		 && !SCTA_IS_FLAG(sc_ta, SCTA_FL_DSN));

	if (SCTA_IS_FLAG(sc_ta, SCTA_FL_DSN)) {
		ret = sm_rcb_get2uint32(sc_t_ctx->sct_rcb, &l, &rt);
		if (sm_is_err(ret) || rt != RT_Q2C_B_MSG)
			goto abortsess;

		ret = sm_rcb_getnstr(sc_t_ctx->sct_rcb, &sc_ta->scta_b_msg, l);
		if (sm_is_err(ret))
			goto abortsess;
	}
	else if (SCTA_IS_FLAG(sc_ta, SCTA_FL_NO_BODY|SCTA_FL_HDR_ONLY)) {
		/*
		**  It's not a bounce but no body to send?
		**  Treat this as inconsistent for now.
		**  Later on this might become a "probe", but that should
		**  use an explicit flag so DATA isn't even tried.
		*/

		ret = sm_error_perm(SM_EM_SMTPC, SM_E_UNEXPECTED);
		sm_log_write(sc_ctx->scc_lctx,
			SC_LCAT_CLIENT, SC_LMOD_CLIENT,
			SM_LOG_ERR, 5,
			"sev=ERROR, func=sc_hdl_requests, se_id=%s, da_ta=%s, status=no_bounce_and_no_body"
			, sc_sess->scse_id, sc_ta->scta_id);
		goto abortsess;
	}

	/* fake message size if it's a bounce without body */
	if (SCTA_IS_FLAG(sc_ta, SCTA_FL_DSN) &&
	    SCTA_IS_FLAG(sc_ta, SCTA_FL_NO_BODY|SCTA_FL_HDR_ONLY))
	{
		sc_ta->scta_msg_sz_b = 0;
	}

	if (SCSE_IS_CAP(sc_sess, SCSE_CAP_SIZE) && sc_sess->scse_max_sz_b > 0
	    && sc_ta->scta_msg_sz_b > 0
	    && sc_ta->scta_msg_sz_b > sc_sess->scse_max_sz_b)
	{
		/* message too big */
		ret = SMTP_R_PERM;
		sc_ta->scta_err_state = DA_TA_ERR_SIZE_I;

		sm_log_write(sc_ctx->scc_lctx,
			SC_LCAT_CLIENT, SC_LMOD_CLIENT,
			SM_LOG_INFO, 9,
			"sev=INFO, func=sc_hdl_requests, se_id=%s, da_ta=%s, msg_size=%ld, accepted_size=%ld, status=message_size_bigger_than_accepted_by_server"
			, sc_sess->scse_id, sc_ta->scta_id
			, sc_ta->scta_msg_sz_b, sc_sess->scse_max_sz_b);
	}
	else {
		/* repeat transactions */
		ret = sc_one_ta(sc_t_ctx);
	}

	/* need to deal with 421 */
	if (SMTP_R_SSD == ret || SCSE_IS_FLAG(sc_sess, SCSE_FL_LAST_TA))
		SCSE_SET_FLAG(sc_sess, SCSE_FL_CLOSE);

	ret = sc_c2q(sc_t_ctx, RT_C2Q_TASTAT, ret, &c2q_ctx);
	if (!sm_is_err(ret))
		SCSE_SET_FLAG(sc_sess, SCSE_FL_SND_ST);
	else
		sm_log_write(sc_ctx->scc_lctx,
			SC_LCAT_CLIENT, SC_LMOD_CLIENT,
			SM_LOG_ERR, 5,
			"sev=ERROR, func=sc_hdl_session, se_id=%s, da_ta=%s, ss_ta=%s, sc_c2q=%m"
			, sc_sess->scse_id, sc_ta->scta_id, sc_ta->scta_ssta_id
			, ret);

	if (SCSE_IS_FLAG(sc_sess, SCSE_FL_CLOSE)) {
  closesess:
		{
			bool sendstatus;
#if SC_TIMING
			sessta_id_T da_ta_id;

			if (sc_sess->scse_ta != NULL)
				SESSTA_COPY(da_ta_id, sc_sess->scse_ta->scta_id);
			else
				da_ta_id[0] = '\0';
#endif

			/* free transaction */
			sc_ta_free(sc_t_ctx->sct_sess->scse_ta);
			sc_ta = sc_t_ctx->sct_sess->scse_ta = NULL;

			/* close session */
			sendstatus = SCSE_IS_FLAG(sc_sess, SCSE_FL_SND_ST);
			SCSE_CLR_FLAG(sc_sess, SCSE_FL_SE_CLS2QMGR);
			ret = sc_sess_close(sc_t_ctx);
			sc_sess_clr(sc_sess);
			if (sendstatus) {
#if SC_TIMING
				SC_HRBT_DPRINTF((smioerr, "send da_ta_id=%s\n", da_ta_id));
#endif
				res = sc_rcb_send(sc_t_ctx->sct_rcb, &c2q_ctx);
				if (sm_is_err(res)) {
					sm_log_write(sc_ctx->scc_lctx,
						SC_LCAT_CLIENT, SC_LMOD_CLIENT,
						SM_LOG_ERR, 6,
						"sev=ERROR, func=sc_hdl_session, thread=%u, sc_rcb_send=%m"
						, sc_t_ctx->sct_thr_id, res);
					goto error;
				}
			}
			if (sm_is_err(ret))
				goto error2;
			/* sc_t_ctx->sct_status = SC_T_FREE; */
		}
	}
	else if (SCSE_IS_FLAG(sc_sess, SCSE_FL_SND_ST)) {
		res = sc_rcb_send(sc_t_ctx->sct_rcb, &c2q_ctx);
		if (sm_is_err(res)) {
			sm_log_write(sc_ctx->scc_lctx,
				SC_LCAT_CLIENT, SC_LMOD_CLIENT,
				SM_LOG_ERR, 6,
				"sev=ERROR, func=sc_hdl_session, sc_rcb_send=%m"
				, res);
			goto error;
		}
		SCSE_CLR_FLAG(sc_sess, SCSE_FL_SND_ST);
	}

	/* XXX send appropriate status code to QMGR! */

	(void) sm_rcb_close_decn(sc_t_ctx->sct_rcb);
	return ret;

  abortsess:
	sm_log_write(sc_ctx->scc_lctx,
		SC_LCAT_CLIENT, SC_LMOD_CLIENT,
		SM_LOG_ERR, 9,
		"sev=ERROR, func=sc_hdl_session, status=abort, l=%d, rt=%x, sc_ta=%p, ret=%m"
		, l, rt, sc_ta, ret);
	sc_ta_free(sc_ta);
	sc_ta = sc_t_ctx->sct_sess->scse_ta = NULL;
	(void) sc_sess_close(sc_t_ctx);
	sc_t_ctx->sct_status = SC_T_FREE;

  error:
	/* XXX send appropriate status code to QMGR! */
	sc_sess_clr(sc_sess);
	sc_t_ctx_clr(sc_t_ctx);
  error2:
	return ret;
}

/*
**  SC_TIMEOUT_SESSION -- timeout occurred while waiting for QMGR: close session
**
**	Parameters:
**		sc_t_ctx -- SMTPC thread context
**
**	Returns:
**		usual sm_error code
*/

sm_ret_T
sc_timeout_session(sc_t_ctx_P sc_t_ctx)
{
	sm_ret_T ret;
	sc_sess_P sc_sess;
	sc_ctx_P sc_ctx;

	SM_IS_SC_T_CTX(sc_t_ctx);
	sc_sess = sc_t_ctx->sct_sess;
	sc_ctx = sc_t_ctx->sct_sc_ctx;

	if (sc_sess != NULL && sc_sess->scse_state != SCSE_ST_NONE) {
		/*
		**  add a "last used" timestamp?? close session only if a
		**  "session timeout" value is exceeded?
		*/

		if (sc_ctx->scc_cnf.sc_cnf_debug > 3) {
			sm_log_write(sc_ctx->scc_lctx,
				SC_LCAT_CLIENT, SC_LMOD_CLIENT,
				SM_LOG_INFO, 17,
				"sev=INFO, func=sc_timeout_session, sess=%p, se_id=%s"
				, sc_sess, sc_sess->scse_id);
		}

		/* SESSION REUSE DEBUG */
		sm_log_write(sc_ctx->scc_lctx,
			SC_LCAT_CLIENT, SC_LMOD_CLIENT,
			SM_LOG_INFO, 9,
			"sev=INFO, func=sc_timeout_session, sess=%p, se_id=%s, cls2qmgr=%d"
			, sc_sess, sc_sess->scse_id
			, SCSE_IS_FLAG(sc_sess, SCSE_FL_SE_CLS2QMGR));

		SM_REQUIRE(sc_t_ctx->sct_status != SC_T_BUSY);
		SM_REQUIRE(SC_T_IDLE == sc_t_ctx->sct_status); /* ??? */
		SM_ASSERT(SC_IDLE_THREADS(sc_ctx) > 0);
		SC_IDLE_THREADS(sc_ctx)--;
		sm_log_write(sc_ctx->scc_lctx,
			SC_LCAT_CLIENT, SC_LMOD_CLIENT,
			SM_LOG_INFO, 9,
			"sev=INFO, func=sc_timeout_session, se_id=%s, status=idle, busy=%u, wait=%u, idle=%u"
			, sc_sess->scse_id
			, SC_BUSY_THREADS(sc_ctx), SC_WAIT_THREADS(sc_ctx)
			, SC_IDLE_THREADS(sc_ctx));
		sc_t_ctx->sct_status = SC_T_CLOSING;
		SC_CLOSING_THREADS(sc_ctx)++;

		/* close SMTP session */
		ret = sc_sess_close(sc_t_ctx);

		sm_log_write(sc_ctx->scc_lctx,
			SC_LCAT_CLIENT, SC_LMOD_CLIENT,
			SM_LOG_INFO, 11,
			"sev=INFO, func=sc_timeout_session, se_id=%s, where=after_sc_sess_close(), status=%d, busy=%u, wait=%u, idle=%u"
			, sc_sess->scse_id, sc_t_ctx->sct_status
			, SC_BUSY_THREADS(sc_ctx), SC_WAIT_THREADS(sc_ctx)
			, SC_IDLE_THREADS(sc_ctx));

		/* tell qmgr about status change? */
		if (SCSE_IS_FLAG(sc_sess, SCSE_FL_SE_CLS2QMGR))
		{
			sc_sess->scse_err_st = 0;
			(void) sc_c2q(sc_t_ctx, RT_C2Q_SECLSD,
					0 /* SESS_TIMEOUT? */, &c2q_ctx);
			SCSE_CLR_FLAG(sc_sess, SCSE_FL_SE_CLS2QMGR);

		sm_log_write(sc_ctx->scc_lctx,
			SC_LCAT_CLIENT, SC_LMOD_CLIENT,
			SM_LOG_INFO, 11,
			"sev=INFO, func=sc_timeout_session, se_id=%s, where=after_sc_c2q(), status=%d, busy=%u, wait=%u, idle=%u"
			, sc_sess->scse_id, sc_t_ctx->sct_status
			, SC_BUSY_THREADS(sc_ctx), SC_WAIT_THREADS(sc_ctx)
			, SC_IDLE_THREADS(sc_ctx));
		}

		SM_ASSERT(SC_CLOSING_THREADS(sc_ctx) > 0);
		SC_CLOSING_THREADS(sc_ctx)--;

		(void) sc_sess_clr(sc_sess);
		if (sm_is_err(ret)) goto error;
	}
	else {
		if (SC_T_IDLE == sc_t_ctx->sct_status) {
			SM_ASSERT(SC_IDLE_THREADS(sc_ctx) > 0);
			SC_IDLE_THREADS(sc_ctx)--;
			sm_log_write(sc_ctx->scc_lctx,
				SC_LCAT_CLIENT, SC_LMOD_CLIENT,
				SM_LOG_INFO, 9,
				"sev=INFO, func=sc_timeout_session, sess=%p, se_id=%s, status=idle, busy=%u, wait=%u, idle=%u"
				, sc_sess
				, sc_sess != NULL ? sc_sess->scse_id : "-"
				, SC_BUSY_THREADS(sc_ctx), SC_WAIT_THREADS(sc_ctx)
				, SC_IDLE_THREADS(sc_ctx));
		}
		else if (SC_T_BUSY == sc_t_ctx->sct_status) {
			SC_WAIT_THREADS(sc_ctx)++;
			SM_ASSERT(SC_BUSY_THREADS(sc_ctx) > 0);
			SC_BUSY_THREADS(sc_ctx)--;
			sm_log_write(sc_ctx->scc_lctx,
				SC_LCAT_CLIENT, SC_LMOD_CLIENT,
				SM_LOG_WARN, 9,
				"sev=WARN, func=sc_timeout_session, sess=%p, se_id=%s, waitthr=%u, busythr=%u, total=%u"
				, sc_sess
				, sc_sess != NULL ? sc_sess->scse_id : "-"
				, SC_WAIT_THREADS(sc_ctx)
				, SC_BUSY_THREADS(sc_ctx)
				, SC_TOTAL_THREADS(sc_ctx));
		}
	}

	sc_t_ctx_clr(sc_t_ctx);
	if (SC_T_REUSE == sc_t_ctx->sct_status)
		(void) st_cond_signal(c2q_ctx.c2q_cond_rd);
	else
		sc_t_ctx->sct_status = SC_T_FREE;
	return SM_SUCCESS;

  error:
	sm_log_write(sc_ctx->scc_lctx,
		SC_LCAT_CLIENT, SC_LMOD_CLIENT,
		SM_LOG_ERR, 8,
		"sev=ERROR, func=sc_timeout_session, ret=%m", ret);
	sc_t_ctx_clr(sc_t_ctx);
	if (SC_T_REUSE == sc_t_ctx->sct_status)
		(void) st_cond_signal(c2q_ctx.c2q_cond_rd);
	else
		sc_t_ctx->sct_status = SC_T_FREE;
	return ret;
}

/*
**  SC_SHUTDOWN_SESSION -- shutdown session because the system is shutting down
**
**	Parameters:
**		sc_t_ctx -- SMTPC thread context
**		fast -- shutdown fast
**
**	Returns:
**		usual sm_error code
*/

sm_ret_T
sc_shutdown_session(sc_t_ctx_P sc_t_ctx, bool fast)
{
	sm_ret_T ret;
	sc_sess_P sc_sess;

	SM_IS_SC_T_CTX(sc_t_ctx);
	sc_sess = sc_t_ctx->sct_sess;
	ret = SM_SUCCESS;
	if (!fast && sc_sess != NULL && sc_sess->scse_state != SCSE_ST_NONE) {
		/* close session */
		ret = sc_sess_close(sc_t_ctx);
		if (sm_is_err(ret)) {
			sm_log_write(sc_t_ctx->sct_sc_ctx->scc_lctx,
				SC_LCAT_CLIENT, SC_LMOD_CLIENT,
				SM_LOG_ERR, 7,
				"sev=ERROR, func=sc_shutdown_session, sc_sess_close=%x"
				, ret);
		}
	}
	sc_sess_clr(sc_sess);
	return ret;
}

#if 0
/*
**  SC_LOAD_CONFIGS -- load configs; fixme: this does nothing right now.
**
**	Parameters:
**		sc_ctx -- SMTPC context
**
**	Returns:
**		none
*/

void
sc_load_configs(sc_ctx_P sc_ctx)
{
	sm_log_write(sc_ctx->scc_lctx,
		SC_LCAT_INIT, SC_LMOD_CONFIG,
		SM_LOG_INFO, 12,
		"sev=INFO, func=sc_load_configs, index=%d, pid=%d, status=configuration_reload_not_yet_implemented"
		, sc_index, sc_pid);
}
#endif /* 0 */

/*
**  SC_DUMP_INFO -- print some status information
**
**	Parameters:
**		sc_ctx -- SMTPC context
**
**	Returns:
**		none
*/

void
sc_dump_info(sc_ctx_P sc_ctx)
{
	char *buf;
	int len, s, l;
	ssize_t b;
	struct rusage rusage;

	s = 512;
	if ((buf = sm_malloc(s)) == NULL) {
		sm_log_write(sc_ctx->scc_lctx,
			SC_LCAT_INIT, SC_LMOD_CONFIG, SM_LOG_ERR, 1,
			"sev=ERROR, func=sc_dump_info, bytes=%d, malloc=failed", s);
		return;
	}

	len = sm_snprintf(buf, s, "\n\nProcess #%d (pid %d):\n", sc_index,
			(int)sc_pid);
	l = sm_snprintf(buf + len, s - len,
		"Max threads         %u\n"
		"Min waiting threads %u\n"
		"Max waiting threads %u\n"
		"Wait threads        %u\n"
		"Idle threads        %u\n"
		"Busy threads        %u\n"
		"Max used threads    %u\n"
		"Requests            %u\n"
#if SC_STATS
		"Transactions        %u\n"
		"Recipients          %u\n"
		"Transactions ok     %u\n"
		"Recipients ok       %u\n"
		"Sessions reused     %u\n"
		"Max open sessions   %u\n"
#endif /* SC_STATS */

		, SC_MAX_THREADS(sc_ctx)
		, SC_MIN_WAIT_THREADS(sc_ctx)
		, SC_MAX_WAIT_THREADS(sc_ctx)
		, SC_WAIT_THREADS(sc_ctx)
		, SC_IDLE_THREADS(sc_ctx)
		, SC_BUSY_THREADS(sc_ctx)
		, SC_MAX_USED_THREADS(sc_ctx)
		, SC_RQST_COUNT(sc_ctx)
#if SC_STATS
		, SC_MAIL_COUNT(sc_ctx)
		, SC_RCPT_COUNT(sc_ctx)
		, SC_TA_CNT_OK(sc_ctx)
		, SC_RCPT_CNT_OK(sc_ctx)
		, SC_SE_REUSE(sc_ctx)
		, SC_MAX_OPEN_SE(sc_ctx)
#endif /* SC_STATS */
		);
	if (l < s - len)
		len += l;

	l = getrusage(RUSAGE_SELF, &rusage);
	if (l == 0 && len < s) {
		l = sm_snprintf(buf + len, s - len,
			"ru_utime=   %7ld.%07ld\n"
			"ru_stime=   %7ld.%07ld\n"
			"ru_maxrss=  %7ld\n"
			"ru_ixrss=   %7ld\n"
			"ru_idrss=   %7ld\n"
			"ru_isrss=   %7ld\n"
			"ru_minflt=  %7ld\n"
			"ru_majflt=  %7ld\n"
			"ru_nswap=   %7ld\n"
			"ru_inblock= %7ld\n"
			"ru_oublock= %7ld\n"
			"ru_msgsnd=  %7ld\n"
			"ru_msgrcv=  %7ld\n"
			"ru_nsignals=%7ld\n"
			"ru_nvcsw=   %7ld\n"
			"ru_nivcsw=  %7ld\n"
			, rusage.ru_utime.tv_sec
			, rusage.ru_utime.tv_usec
			, rusage.ru_stime.tv_sec
			, rusage.ru_stime.tv_usec
			, rusage.ru_maxrss
			, rusage.ru_ixrss
			, rusage.ru_idrss
			, rusage.ru_isrss
			, rusage.ru_minflt
			, rusage.ru_majflt
			, rusage.ru_nswap
			, rusage.ru_inblock
			, rusage.ru_oublock
			, rusage.ru_msgsnd
			, rusage.ru_msgrcv
			, rusage.ru_nsignals
			, rusage.ru_nvcsw
			, rusage.ru_nivcsw
			);
		if (l < s - len)
			len += l;
	}
	sm_io_write(smioerr, (uchar *) buf, len, &b);
	sm_free_size(buf, s);
}

/*
**  SC_SHOWVERSION -- show version information
**
**	Parameters:
**		sc_ctx -- SMTP Client context
**		progname -- program name
**		cnf -- configuration file name
**		level -- how much detail?
**
**	Returns:
**		usual error code
*/

sm_ret_T
sc_showversion(sc_ctx_P sc_ctx, const char *progname, const char *cnf, uint level)
{
	char *prefix;

	prefix = "";
	if (level >= SM_SV_RD_CONF)
		prefix = "# ";
	sm_io_fprintf(smioout, "%s%s: %s\n", prefix, progname, MTA_VERSION_STR);
	if (SM_SHOW_VERSION(level))
		sm_io_fprintf(smioout, "%sversion=0x%08x\n", prefix, MTA_VERSION);
#if MTA_USE_TLS
	if (SM_SHOW_LIBS(level))
		(void) sm_tlsversionprt(smioout);
#endif
	if (SM_SHOW_OPTIONS(level)) {
		sm_io_fprintf(smioout, "compile time options:\n"
#if MTA_USE_TLS
			"MTA_USE_TLS\n"
#endif
#if SC_DEBUG
			"SC_DEBUG\n"
#endif
#if MTA_USE_RSAD
			"MTA_USE_RSAD\n"
#endif
#if SC_TEST
			"SC_TEST\n"
#endif
			);
	}
	if (SM_SHOW_RD_CONF(level) && cnf != NULL && *cnf != '\0') {
		sm_io_fprintf(smioout, "# configuration-file=%s\n\n", cnf);
		(void) sm_conf_prt_conf(sc_global_defs, &sc_ctx->scc_cnf, smioout);
	}
	else if (SM_SHOW_DFLT_CONF(level)) {
		sm_io_fprintf(smioout, "# default configuration:\n\n");
		(void) sm_conf_prt_dflt(sc_global_defs, 0, smioout);
	}
	else if (SM_SHOW_ALL_CONF(level)) {
		sm_io_fprintf(smioout,
			"# configuration including flags.\n"
			"# note: this data cannot be used as configuration file\n"
			"# but it shows the available flags.\n"
			);
		(void) sm_conf_prt_dflt(sc_global_defs, SMC_FLD_FLAGS, smioout);
	}
	sm_io_flush(smioout);
	return sm_error_perm(SM_EM_SMTPC, SM_E_DONTSTART);
}

/* should be changed later when there is more conf data */
#if MTA_USE_TLS
/*
**  SC_EXTRA_CONF -- set "extra" configuration
**
**	Parameters:
**		sc_ctx -- SMTPC context
**		conf_str -- configuration data
**
**	Returns:
**		usual return code
*/

static sm_conf_definition_T
scse_tlsreq_defs[] = {
TLSREQ_DEFS(tlsreq_cnf_T, tlsreqcnf),

/* Sentinel */
{ SM_CONF_DEF_MAGIC, NULL, 0, 0, 0, NULL, 0, NULL, NULL, NULL, NULL SM_LC_NO_ISSET SM_LC_SET_MAGIC(0)}
};

static sm_conf_definition_T
scse_mapcnf_defs[] = {
{ SM_CONF_DEF_MAGIC, "tls_requirements", sm_conf_type_section,
	0, sizeof(tlsreq_cnf_T), NULL,
	SM_CONF_FLAG_KEEP_DEFAULT,
	scse_tlsreq_defs,
	NULL, NULL, NULL
	SM_LC_NO_ISSET	SM_LC_SET_MAGIC(0) },

/* Sentinel */
{ SM_CONF_DEF_MAGIC, NULL, 0, 0, 0, NULL, 0, NULL, NULL, NULL, NULL SM_LC_NO_ISSET SM_LC_SET_MAGIC(0)}
};


sm_ret_T
sc_extra_conf(sc_ctx_P sc_ctx, sm_str_P conf_str, tlsreq_cnf_P tlsreq_cnf, sm_conf_T **pcnf)
{
	int err;
	sm_conf_T *cnf;
#define SC_EXTRA_CONF "sc_extra_conf"

	SM_REQUIRE(conf_str != NULL);
	SM_REQUIRE(pcnf != NULL);
	SM_REQUIRE(tlsreq_cnf != NULL);
	sm_memzero(tlsreq_cnf, sizeof(*tlsreq_cnf));
	cnf = sm_conf_new(SC_EXTRA_CONF);
	if (NULL == cnf) {
		err = errno;
		sm_log_write(sc_ctx->scc_lctx,
			SC_LCAT_INIT, SC_LMOD_CONFIG,
			SM_LOG_ERR, 8,
			"sev=ERROR, func=sc_extra_conf, sm_conf_new=NULL, errno=%d\n",
			err);
		return sm_error_temp(SM_EM_SMTPC, ENOMEM);
	}
	err = sm_conf_read_data(cnf, (char *)sm_str_getdata(conf_str),
				sm_str_getlen(conf_str), true);
	if (err != 0) {
		sm_prt_conferr(SC_EXTRA_CONF, cnf, err, smioerr);
		sm_log_write(sc_ctx->scc_lctx,
			SC_LCAT_INIT, SC_LMOD_CONFIG,
			SM_LOG_ERR, 8,
			"sev=ERROR, func=sc_extra_conf, sm_conf_read_data=%m\n",
			err);
		sm_conf_destroy(cnf);
		cnf = NULL;
		return err;
	}

	err = sm_conf_scan(cnf, scse_mapcnf_defs, 0, tlsreq_cnf);
	if (err != 0) {
		sm_log_write(sc_ctx->scc_lctx,
			SC_LCAT_INIT, SC_LMOD_CONFIG,
			SM_LOG_ERR, 8,
			"sev=ERROR, func=sc_extra_conf, sm_conf_scan=%m\n",
			err);
	}
	return err;
}
#endif /* MTA_USE_TLS */


syntax highlighted by Code2HTML, v. 0.9.1