/*
 * Copyright (c) 2002-2006 Sendmail, Inc. and its suppliers.
 *	All rights reserved.
 * Copyright (c) 2006 Claus Assmann
 *
 * 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: smclt.c,v 1.243 2007/09/01 15:44:26 ca Exp $")
#include "sm/assert.h"
#include "sm/error.h"
#include "sm/string.h"
#include "sm/str.h"
#include "sm/io.h"
#include "sm/ctype.h"
#include "sm/net.h"
#include "sm/cdb.h"
#include "sm/reccom.h"
#include "sm/da.h"
#include "sm/tls.h"
#include "sm/tlsbio.h"
#include "smtpc.h"
#include "log.h"

#include "statethreads/st.h"
#if SM_STATETHREADS_DEBUG
/* HACK for debugging, otherwise structs are hidden */
# include "statethreads/common.h"
#endif

#if SM_SMTPC_HDRMOD_TEST
extern sm_cstr_P Hdr_pre, Hdr_app;
#endif

static sm_ret_T	 sc_command(sc_t_ctx_P _sc_t_ctx, int _phase);

/*
**  macros for sc_rd_reply() phase
**  This is bit of a hack: it combines a state with a flag
*/

#define SC_PIPELINE_OK	0x0100		/* don't need to read reply now */
#define SC_PHASE_OTHER	(0|SC_PIPELINE_OK)	/* no special treatment */
#define SC_PHASE_INIT	1			/* initial 220 greeting */
#define SC_PHASE_EHLO	2			/* EHLO response */
#define SC_PHASE_TLS	3			/* STARTTLS response */
#define SC_PHASE_MAIL	(4|SC_PIPELINE_OK)	/* MAIL response */
#define SC_PHASE_RCPT	(5|SC_PIPELINE_OK)	/* RCPT response */
#define SC_PHASE_DATA	6			/* DATA response */
#define SC_PHASE_LDAT	7			/* LMTP DATA response */
#define SC_PHASE_DOT	8			/* response to final dot */
#define SC_PHASE_RSAD	9			/* RCPT status after final dot  */
#define SC_PHASE_QUIT	10			/* QUIT */
#define SC_PHASE_NOREPLY	11		/* no reply requested */

#define sc_pipeline_ok(phase)	(((phase) & SC_PIPELINE_OK) != 0)

/* 3 digits reply code plus ' ' or '-' */
#define SMTP_REPLY_OFFSET	4

/*
**  SC_EHLO_OPTIONS -- read server capabilities
**
**	Parameters:
**		sc_sess -- SMTPC session context
**
**	Returns:
**		usual sm_error code
**		(currently only SM_SUCCESS)
*/

static sm_ret_T
sc_ehlo_options(sc_sess_P sc_sess)
{
	char *opt;

	SM_IS_SC_SE(sc_sess);

	/* "250 " + something useful: at least 2 characters??? */
	if (sm_str_getlen(sc_sess->scse_rd) < 6)
		return SM_SUCCESS;
	if (sm_str_rd_elem(sc_sess->scse_rd, 0) != '2' ||
	    sm_str_rd_elem(sc_sess->scse_rd, 1) != '5' ||
	    sm_str_rd_elem(sc_sess->scse_rd, 2) != '0' ||
	    (sm_str_rd_elem(sc_sess->scse_rd, 3) != ' ' &&
	     sm_str_rd_elem(sc_sess->scse_rd, 3) != '-'))
		return SM_SUCCESS;	/* error?? */

#define SMTPC_IS_CAPN(str)	(sm_strcaseeqn(opt, (str), strlen(str)))
#define SMTPC_IS_CAP(str)	(sm_strcaseeq(opt, (str)))

	opt = (char *) sm_str_getdata(sc_sess->scse_rd);
	SM_REQUIRE(opt != NULL);
	opt += SMTP_REPLY_OFFSET;

	if (SMTPC_IS_CAP("pipelining"))
		SCSE_SET_CAP(sc_sess, SCSE_CAP_PIPELINING);
	else if (SMTPC_IS_CAP("8bitmime"))
		SCSE_SET_CAP(sc_sess, SCSE_CAP_8BITMIME);
	else if (SMTPC_IS_CAP("size"))
		SCSE_SET_CAP(sc_sess, SCSE_CAP_SIZE);
	else if (SMTPC_IS_CAPN("size ")) {
		ulonglong_T max_sz_b;	/* off_t? */
		char *endptr;

		errno = 0;

		/* 5 == strlen("size ") */
		max_sz_b = sm_strtoull(opt + 5, &endptr, 10);
		if (!((max_sz_b == ULLONG_MAX && errno == ERANGE) || endptr == opt + 5))
		{
			SCSE_SET_CAP(sc_sess, SCSE_CAP_SIZE);
			sc_sess->scse_max_sz_b = max_sz_b;
		}
		/* else: silently ignore ... */
	}
	else if (SMTPC_IS_CAP("enhancedstatuscodes"))
		SCSE_SET_CAP(sc_sess, SCSE_CAP_ENHSTAT);
	else if (SMTPC_IS_CAPN("auth "))
		SCSE_SET_CAP(sc_sess, SCSE_CAP_AUTH);
	else if (SMTPC_IS_CAP("starttls"))
		SCSE_SET_CAP(sc_sess, SCSE_CAP_STARTTLS);
#if MTA_USE_RSAD
	else if (SMTPC_IS_CAP("prdr"))
		SCSE_SET_CAP(sc_sess, SCSE_CAP_RSAD);
#endif
	return SM_SUCCESS;
}

/*
**  SC_TALKINGTOMYSELF -- does the server greet us with my own name?
**
**	Parameters:
**		sc_sess -- SMTPC session context
**
**	Returns:
**		usual sm_error code
*/

static sm_ret_T
sc_talkingtomyself(sc_sess_P sc_sess)
{
	sm_str_P hn;
	size_t l;

	SM_IS_SC_SE(sc_sess);
	if (SCSE_IS_FLAG(sc_sess, SCSE_FL_LMTP|SCSE_FL_NOTTM))
		return SM_SUCCESS;

	/* "250 " + something useful: at least 1 characters */
	if (sm_str_getlen(sc_sess->scse_rd) < 5)
		return SM_SUCCESS;
	if (sm_str_rd_elem(sc_sess->scse_rd, 0) != '2' ||
	    sm_str_rd_elem(sc_sess->scse_rd, 1) != '5' ||
	    sm_str_rd_elem(sc_sess->scse_rd, 2) != '0' ||
	    (sm_str_rd_elem(sc_sess->scse_rd, 3) != ' ' &&
	     sm_str_rd_elem(sc_sess->scse_rd, 3) != '-'))
		return SM_SUCCESS;	/* error?? */

	hn = sc_sess->scse_sct_ctx->sct_sc_ctx->scc_hostname;
	l = sm_str_getlen(hn) + SMTP_REPLY_OFFSET;
	if (sm_str_getlen(sc_sess->scse_rd) >= l
	    && (strncasecmp((char *) (sm_str_getdata(sc_sess->scse_rd) +
					SMTP_REPLY_OFFSET),
		(char *) sm_str_getdata(hn), l - SMTP_REPLY_OFFSET) == 0)
	    && (sm_str_getlen(sc_sess->scse_rd) == l ||
	        (sm_str_getlen(sc_sess->scse_rd) > l &&
	         sm_str_rd_elem(sc_sess->scse_rd, l) == ' '))
	    )
	{
		return sm_error_perm(SM_EM_SMTPC, SM_E_TTMYSELF);
	}
	return SM_SUCCESS;
}

/*
**  SC_RD_REPLY -- read reply/replies from SMTP server
**
**	Parameters:
**		sc_t_ctx -- SMTPC thread context
**		phase -- phase of SMTP dialogue
**
**	Returns:
**		SMTP reply code (2xy -> SMTP_OK) or error code
**
**	Side Effects:
**		If the server responded:
**		  sc_sess->scse_str contains entire response (provided it is
**		    big enough to hold all data);
**		  sc_sess->scse_rd contains last line of response
**		  (identical for single line responses).
*/

static sm_ret_T
sc_rd_reply(sc_t_ctx_P sc_t_ctx, int phase)
{
	sm_ret_T ret, ttmyself;
	uint thr_id;
	int rcode;
	bool firstline;
	bool used;	/* reply has been used for MAIL/RCPT? */
	sc_sess_P sc_sess;
#if SC_PIPELINING
	sc_ta_P sc_ta;
#endif
	sm_ret_T dot_ret;

	SM_IS_SC_T_CTX(sc_t_ctx);

	/* no need to wait for reply to QUIT */
	if (SC_PHASE_QUIT == phase &&
	    !SM_IS_FLAG(sc_t_ctx->sct_sc_ctx->scc_cnf.sc_cnf_confflags,
	                SCC_CFL_READ_QUIT))
		return SM_SUCCESS;

	sc_sess = sc_t_ctx->sct_sess;
	SM_IS_SC_SE(sc_sess);
	thr_id = sc_t_ctx->sct_thr_id;
	ttmyself = SM_SUCCESS;
#if SC_PIPELINING
	sc_ta = sc_sess->scse_ta;

  again:
#endif
	dot_ret = SMTP_NO_REPLY;
	firstline = true;
	used = false;
	rcode = -1;
	sm_str_clr(sc_sess->scse_str);
	do {
#if SC_PIPELINING
		/* also check how much data has been sent? */
		if (sc_pipeline_ok(phase)
		    && SCSE_IS_CAP(sc_sess, SCSE_CAP_PIPELINING)
		    && !sm_io_getinfo(sc_sess->scse_fp, SM_IO_IS_READABLE, NULL))
			return SMTP_NO_REPLY;
#endif /* SC_PIPELINING */

		sm_str_clr(sc_sess->scse_rd);
		if (sc_sess->scse_fp == NULL || SCSE_IS_FLAG(sc_sess, SCSE_FL_IO_ERR))
			ret = EOF;
		else
			ret = sm_fgetline0(sc_sess->scse_fp, sc_sess->scse_rd);
		/* timeout error: EOF */

#if SC_DEBUG
		if (sc_t_ctx->sct_sc_ctx->scc_cnf.sc_cnf_debug > 3) {
			ssize_t b;

			/*
			**  multiple statements to print one entry...
			**  non-trivial to convert to logging.
			*/

			sm_io_fprintf(smioerr,
				"thread=%u, da_sess=%s, read [len=%d, res=%r]: ",
				thr_id, sc_sess->scse_id, sm_str_getlen(sc_sess->scse_rd), ret);
#if SC_PIPELINING
			if (sc_ta != NULL)
				sm_io_fprintf(smioerr,
					"[flags=%x, rcpts_rcvd=%d, snt=%d]: ",
					sc_ta->scta_state,
					sc_ta->scta_rcpts_rcvd,
					sc_ta->scta_rcpts_snt);
#endif /* SC_PIPELINING */

			/*
			**  WARNING: data directly from server!
			**  May contain "bogus" characters.
			*/

			sm_io_write(smioerr, sm_str_data(sc_sess->scse_rd),
				sm_str_getlen(sc_sess->scse_rd), &b);
			sm_io_flush(smioerr);
		}
#endif /* SC_DEBUG */
		if (sm_is_err(ret)) {
			if (!SCSE_IS_FLAG(sc_sess, SCSE_FL_LOGGED) &&
			    !SCSE_IS_FLAG(sc_sess, SCSE_FL_IO_ERR))
			{
				sm_log_write(sc_t_ctx->sct_sc_ctx->scc_lctx,
					SC_LCAT_CLIENT, SC_LMOD_CLIENT,
					SM_LOG_INFO,
					SCSE_IS_FLAG(sc_sess, SCSE_FL_SSD)
						? 14 : 9,
					"sev=INFO, func=sc_rd_reply, thread=%u, da_sess=%s, phase=%#x, ret=%m",
					thr_id, sc_sess->scse_id, phase, ret);
			}

			/* I/O error (always??) */
			SCSE_SET_FLAG(sc_sess, SCSE_FL_IO_ERR|SCSE_FL_LOGGED);
			goto fail;
		}
		if (SC_PHASE_INIT == phase) {
			/* question: perform a "loops back to me" check?? */
		}
		else if (SC_PHASE_EHLO == phase) {
			if (firstline) {
				ttmyself = sc_talkingtomyself(sc_sess);
			}
			else {
				/* get server capabilities */
				ret = sc_ehlo_options(sc_sess);
			}
		}
		else if (sm_is_success(ret) && IS_SMTP_REPLY_STR(sc_sess->scse_rd, 0)) {
			int r;

			r = SMTP_REPLY_STR2VAL(sc_sess->scse_rd, 0);
			if (!firstline && rcode != r) {
				sm_log_write(sc_t_ctx->sct_sc_ctx->scc_lctx,
					SC_LCAT_CLIENT, SC_LMOD_CLIENT,
					SM_LOG_WARN, 9,
					"sev=WARN, func=sc_rd_reply, thread=%u, da_sess=%s, phase=%#x, status=inconsistent_multiline_reply_codes, previous=%d, current=%d"
					, thr_id, sc_sess->scse_id, phase
					, rcode, r);
			}
			rcode = r;
		}
		/* other cases? */

		/* bogus response? check every line, not just last */
		if (sm_is_success(ret) && !IS_SMTP_REPLY_STR(sc_sess->scse_rd, 0))
			ret = SMTPC_PROT;

		if (!firstline)
			(void) sm_str_put(sc_sess->scse_str, ' ');
		(void) sm_str_cat(sc_sess->scse_str, sc_sess->scse_rd);
		firstline = false;
	} while (sm_is_success(ret) && sm_str_getlen(sc_sess->scse_rd) > 3 &&
		 sm_str_rd_elem(sc_sess->scse_rd, 3) == '-');
	if (sm_is_err(ttmyself))
		ret = ttmyself;
	if (sm_is_err(ret) || sm_str_getlen(sc_sess->scse_rd) < 3)
		goto fail;

	/*
	**  Note: in case of a multi-line reply sc_sess->scse_rd contains
	**  now the last line... we may have to store all of them for
	**  better error handling, i.e., to return the complete reply
	**  to a user in case of a DSN or for logging.
	*/

#if SC_DEBUG
	if (sc_t_ctx->sct_sc_ctx->scc_cnf.sc_cnf_debug > 3
	    && phase == SC_PHASE_EHLO)
		sm_log_write(sc_t_ctx->sct_sc_ctx->scc_lctx,
			SC_LCAT_CLIENT, SC_LMOD_CLIENT,
			SM_LOG_INFO, 17,
			"sev=INFO, func=sc_rd_reply, thread=%u, da_sess=%s, capabilities=%x",
			thr_id, sc_sess->scse_id, sc_sess->scse_cap);
#endif /* SC_DEBUG */

	if (sm_str_rd_elem(sc_sess->scse_rd, 0) == '2')
		ret = SMTP_OK;
	else if (!IS_SMTP_REPLY_STR(sc_sess->scse_rd, 0))
		ret = SMTPC_PROT;
	else {
		ret = SMTP_REPLY_STR2VAL(sc_sess->scse_rd, 0);
		if (SMTP_R_SSD == ret)
			SCSE_SET_FLAG(sc_sess, SCSE_FL_SSD);
	}

#if SC_PIPELINING
	/*
	 * do we need to collect more responses (SC_PHASE_LDAT)
	 * or find the right command for a reponse?
	 */
	if (SCSE_IS_CAP(sc_sess, SCSE_CAP_PIPELINING) &&
	    (phase == SC_PHASE_RCPT
	     || phase == SC_PHASE_DATA
	     || phase == SC_PHASE_LDAT
#if MTA_USE_RSAD
	     || phase == SC_PHASE_RSAD
#endif
	    ))
	{
		SM_IS_SC_TA(sc_ta);
		if (!SCTA_IS_STATE(sc_ta, SCTA_MAIL_R)) {
			/* reply for MAIL */
			SCTA_SET_STATE(sc_ta, SCTA_MAIL_R);
			sc_ta->scta_mail->scm_st = ret;
			sm_log_write(sc_t_ctx->sct_sc_ctx->scc_lctx,
				SC_LCAT_CLIENT, SC_LMOD_CLIENT,
				SM_LOG_INFO, 8,
				"sev=INFO, func=sc_rd_reply, thread=%u, da_sess=%s, da_ta=%s, ss_ta=%s, mail=%@S, stat=%m, reply=%@S",
				thr_id, sc_sess->scse_id,
				sc_ta->scta_id, sc_ta->scta_ssta_id,
				sc_ta->scta_mail->scm_pa, ret,
				sc_sess->scse_rd);
			if (ret != SMTP_OK) {
				sc_ta->scta_mail->scm_reply = sm_str_dup(sc_ta->scta_rpool,
							sc_sess->scse_rd);
				sc_ta->scta_err_state = DA_TA_ERR_MAIL_S;
				sc_ta->scta_status = ret;
				SCTA_SET_STATE(sc_ta, smtp_is_reply_fail(ret)
					? SCTA_FAIL_PERM : SCTA_FAIL_TEMP);
			}
			ret = SMTP_NO_REPLY;
			used = true;
		}
		else if (sc_ta->scta_rcpts_rcvd < sc_ta->scta_rcpts_snt) {
			sc_rcpt_P sc_rcpt;

			/* only check rcpts that have been sent to the server */
			do {
				sc_rcpt = sc_ta->scta_rcpt_p;
				if (!SCR_IS_FLAG(sc_rcpt, SCR_FL_SENT))
					sc_ta->scta_rcpt_p = SC_RCPTS_NEXT(sc_rcpt);
			} while (!SCR_IS_FLAG(sc_rcpt, SCR_FL_SENT) &&
			         sc_rcpt != SC_RCPTS_END(&sc_ta->scta_rcpts));

			SM_ASSERT(sc_rcpt != SC_RCPTS_END(&sc_ta->scta_rcpts));
			if (SMTP_OK == ret) {
				sc_rcpt->scr_st = ret;
#if SC_STATS
				++SC_RCPT_CNT_OK(sc_t_ctx->sct_sc_ctx);
#endif
			}

			/*
			**  Set error state only if there wasn't a transaction
			**  error yet, i.e., if MAIL was successful, otherwise
			**  the RCPT commands are irrelevant anyway.
			*/

			else if (!SMTPC_R_IS_OK(ret) &&
			         !SCTA_IS_STATE(sc_ta, SCTA_FAIL_PERM|SCTA_FAIL_TEMP))
			{
				sc_rcpt->scr_st = ret;
				sc_rcpt->scr_reply = sm_str_dup(sc_ta->scta_rpool,
							sc_sess->scse_rd);
				if (DA_ERR_NONE == sc_ta->scta_err_state)
					sc_ta->scta_err_state = DA_TA_ERR_RCPT_S;
				SCTA_SET_STATE(sc_ta, smtp_is_reply_fail(ret)
					? SCTA_R_PERM : SCTA_R_TEMP);
				if (smtp_is_reply_temp(ret))
				{
					/*
					**  Save temporary failure state for
					**  later perusal if necessary
					*/

					sc_ta->scta_status = ret;
				}

			}
			if (ret != SMTP_NO_REPLY &&
			    !SCTA_IS_STATE(sc_ta, SCTA_FAIL_PERM|SCTA_FAIL_TEMP) &&
			    !SCR_IS_FLAG(sc_rcpt, SCR_FL_LOGGED))
			{
				sm_log_write(sc_t_ctx->sct_sc_ctx->scc_lctx,
					SC_LCAT_CLIENT, SC_LMOD_CLIENT,
					SM_LOG_INFO, 8,
					"sev=INFO, func=sc_rd_reply, thread=%u, da_sess=%s, da_ta=%s, ss_ta=%s, rcpt=%@S, stat=%m, reply=%@S",
					thr_id, sc_sess->scse_id,
					sc_ta->scta_id, sc_ta->scta_ssta_id,
					sc_rcpt->scr_pa, ret,
					sc_sess->scse_rd);
				SCR_SET_FLAG(sc_rcpt, SCR_FL_LOGGED);
			}

#if SC_DEBUG
			if (sc_t_ctx->sct_sc_ctx->scc_cnf.sc_cnf_debug > 3)
				sm_log_write(sc_t_ctx->sct_sc_ctx->scc_lctx,
					SC_LCAT_CLIENT, SC_LMOD_CLIENT,
					SM_LOG_INFO, 15,
					"sev=INFO, func=sc_rd_reply, thread=%u, da_sess=%s, da_ta=%s, ss_ta=%s, rcpt=%@S, idx=%d, stat=%m, scta_rcpts_rcvd=%d, scta_rcpts_snt=%d, reply=%p, err_st=%x, state=%x",
					thr_id, sc_sess->scse_id,
					sc_ta->scta_id, sc_ta->scta_ssta_id,
					sc_rcpt->scr_pa,
					sc_rcpt->scr_idx, ret,
					sc_ta->scta_rcpts_rcvd,
					sc_ta->scta_rcpts_snt,
					sc_rcpt->scr_reply,
					sc_ta->scta_err_state,
					sc_ta->scta_state);
#endif /* SC_DEBUG */

			if (SMTP_OK == ret)
				++sc_ta->scta_rcpts_ok;
			++sc_ta->scta_rcpts_rcvd;
			sc_ta->scta_rcpt_p = SC_RCPTS_NEXT(sc_rcpt);
			ret = SMTP_NO_REPLY;
			if (sc_ta->scta_rcpts_rcvd == sc_ta->scta_rcpts_snt)
				SCTA_SET_STATE(sc_ta, SCTA_RCPT_R);
			used = true;
		}

		/* need to collect all replies? */
		if (SC_PHASE_DATA == phase) {
			if (used)
				goto again;
			SCTA_SET_STATE(sc_ta, SCTA_DATA_R);
		}
	}
#endif /* SC_PIPELINING */

	if (SC_PHASE_LDAT == phase) {
		/*
		 * LMTP RCPT status after final dot:
		 * there is no reply for data, all replies are for
		 * RCPTs that have been "OK"ed before (250).
		 */
		sc_rcpt_P sc_rcpt;


		/* only check rcpts that have been sent to the server */
		do {
			sc_rcpt = sc_ta->scta_rcpt_p;
			if (!SCR_IS_FLAG(sc_rcpt, SCR_FL_SENT))
				sc_ta->scta_rcpt_p = SC_RCPTS_NEXT(sc_rcpt);
		} while (!SCR_IS_FLAG(sc_rcpt, SCR_FL_SENT) &&
		         sc_rcpt != SC_RCPTS_END(&sc_ta->scta_rcpts));
		SM_ASSERT(sc_rcpt != SC_RCPTS_END(&sc_ta->scta_rcpts));

		/* only collect replies for "OK" recipients */
		while (sc_rcpt != SC_RCPTS_END(&sc_ta->scta_rcpts)
		       && sc_rcpt->scr_st != SMTP_OK)
			sc_rcpt = SC_RCPTS_NEXT(sc_rcpt);

		if (sc_rcpt == SC_RCPTS_END(&sc_ta->scta_rcpts)) {
			/* went beyond the list... */
			sm_log_write(sc_t_ctx->sct_sc_ctx->scc_lctx,
				SC_LCAT_CLIENT, SC_LMOD_CLIENT,
				SM_LOG_ERR, 1,
				"sev=ERROR, func=sc_rd_reply, thread=%u, da_sess=%s, da_ta=%s, ss_ta=%s, where=LMTP, status=end_of_list",
				thr_id, sc_sess->scse_id,
				sc_ta->scta_id, sc_ta->scta_ssta_id);
			return ret;
		}

#if SC_DEBUG
		if (sc_t_ctx->sct_sc_ctx->scc_cnf.sc_cnf_debug > 3)
			sm_log_write(sc_t_ctx->sct_sc_ctx->scc_lctx,
				SC_LCAT_CLIENT, SC_LMOD_CLIENT,
				SM_LOG_DEBUG, 15,
				"sev=DBG, func=sc_rd_reply, thread=%u, da_sess=%s, da_ta=%s, ss_ta=%s, LMTP_rcpt=%S, idx=%d, stat=%m, rcpts_lmtp=%d",
				thr_id, sc_sess->scse_id,
				sc_ta->scta_id, sc_ta->scta_ssta_id,
				sc_rcpt->scr_pa,
				sc_rcpt->scr_idx, ret,
				sc_ta->scta_rcpts_dot);
#endif /* SC_DEBUG */

		sc_rcpt->scr_st = ret;
		if (ret != SMTP_OK) {
			sc_rcpt->scr_reply = sm_str_dup(sc_ta->scta_rpool,
					sc_sess->scse_rd);
			if (DA_ERR_NONE == sc_ta->scta_err_state)
				sc_ta->scta_err_state = DA_TA_ERR_RCPT_S;
			--sc_ta->scta_rcpts_ok;
		}

		/* Hack: use the "lowest" value for dot_ret */
		if (dot_ret > ret)
			dot_ret = ret;
		--sc_ta->scta_rcpts_dot;
		sc_ta->scta_rcpt_p = SC_RCPTS_NEXT(sc_rcpt);

		/* all responses collected? */
		if (0 == sc_ta->scta_rcpts_dot) {
			/*
			**  RFC2033 4.2: there is no separate response
			**  for DATA, but only responses for previously
			**  accepted recipients, hence we don't set used,
			**  but use dot_ret as return value.
			*/

			SCTA_SET_STATE(sc_ta, SCTA_L_RCPT_R);
			ret = dot_ret;
		}
		else {
			used = true;
			ret = SMTP_NO_REPLY;
		}
		if (used)
			goto again;
		SCTA_SET_STATE(sc_ta, SCTA_DOT_R);
	}
#if MTA_USE_RSAD
	/* RCPT status after final dot */
	if (SC_PHASE_RSAD == phase) {
		sc_rcpt_P sc_rcpt;

	/*
	 *  Maybe restructure this?
	 *  if (SC_PHASE_RSAD == phase && !SCTA_IS_STATE(sc_ta, SCTA_DOT_R))
	 *  	...
	 *  if (SC_PHASE_RSAD == phase && !SCTA_IS_STATE(sc_ta, SCTA_D_RCPT_R))
	 *  	...
	 */

		if (!SCTA_IS_STATE(sc_ta, SCTA_DOT_R)) {
			/*
			 * first reply: for end of mail data
			 *  353: expect individual RCPT replies
			 *  other values: overall status (and no RCPT replies!)
			 */
			SCTA_SET_STATE(sc_ta, SCTA_DOT_R);

			/* 3yz: individual RCPT replies follow */
			if (SMTP_RTYPE_CONT == smtp_reply_type(ret))
				goto again;

			/* else: do not try to get individual RCPT replies */
			SCTA_SET_STATE(sc_ta, SCTA_D_RCPT_R);

			/* this is also the final reply */
			SCTA_SET_STATE(sc_ta, SCTA_DOT_F_R);
		}

		/* get individual RCPT replies? */
		if (!SCTA_IS_STATE(sc_ta, SCTA_D_RCPT_R)) {

			/* only check rcpts that have been sent to the server */
			do {
				sc_rcpt = sc_ta->scta_rcpt_p;
				if (!SCR_IS_FLAG(sc_rcpt, SCR_FL_SENT))
					sc_ta->scta_rcpt_p = SC_RCPTS_NEXT(sc_rcpt);
			} while (!SCR_IS_FLAG(sc_rcpt, SCR_FL_SENT) &&
			         sc_rcpt != SC_RCPTS_END(&sc_ta->scta_rcpts));
			SM_ASSERT(sc_rcpt != SC_RCPTS_END(&sc_ta->scta_rcpts));

#define SMTPC_RCTP_IS_OK(ret) ((ret) == SMTP_OK || smtp_reply_type(ret) == SMTP_RTYPE_OK)

			/* only collect replies for "OK" recipients */
			while (sc_rcpt != SC_RCPTS_END(&sc_ta->scta_rcpts)
			       && !SMTPC_RCTP_IS_OK(sc_rcpt->scr_st))
				sc_rcpt = SC_RCPTS_NEXT(sc_rcpt);

			if (sc_rcpt == SC_RCPTS_END(&sc_ta->scta_rcpts)) {
				/* went beyond the list... */
				sm_log_write(sc_t_ctx->sct_sc_ctx->scc_lctx,
					SC_LCAT_CLIENT, SC_LMOD_CLIENT,
					SM_LOG_ERR, 1,
					"sev=ERROR, func=sc_rd_reply, thread=%u, da_sess=%s, da_ta=%s, ss_ta=%s, where=RSAD, status=end_of_list",
					thr_id, sc_sess->scse_id,
					sc_ta->scta_id, sc_ta->scta_ssta_id);
				return ret;
			}

#if SC_DEBUG || 1
			if (sc_t_ctx->sct_sc_ctx->scc_cnf.sc_cnf_debug > 3)
				sm_log_write(sc_t_ctx->sct_sc_ctx->scc_lctx,
					SC_LCAT_CLIENT, SC_LMOD_CLIENT,
					SM_LOG_DEBUG, 15,
					"sev=DBG, func=sc_rd_reply, thread=%u, da_sess=%s, da_ta=%s, ss_ta=%s, RSAD=%S, idx=%d, stat=%m, rcpts_dot=%d",
					thr_id, sc_sess->scse_id,
					sc_ta->scta_id, sc_ta->scta_ssta_id,
					sc_rcpt->scr_pa,
					sc_rcpt->scr_idx, ret,
					sc_ta->scta_rcpts_dot);
#endif /* SC_DEBUG */

			/* set reply for this RCPT */
			sc_rcpt->scr_st = ret;
			if (ret != SMTP_OK) {
				sc_rcpt->scr_reply = sm_str_dup(sc_ta->scta_rpool,
						sc_sess->scse_rd);
				if (DA_ERR_NONE == sc_ta->scta_err_state)
					sc_ta->scta_err_state = DA_TA_ERR_RCPT_S;
				if (SMTP_IS_REPLY_ERROR(ret))
					SCTA_SET_STATE(sc_ta, smtp_is_reply_fail(ret)
						? SCTA_R_PERM : SCTA_R_TEMP);
				--sc_ta->scta_rcpts_ok;
			}

			--sc_ta->scta_rcpts_dot;
			sc_ta->scta_rcpt_p = SC_RCPTS_NEXT(sc_rcpt);

			if (0 == sc_ta->scta_rcpts_dot)
				SCTA_SET_STATE(sc_ta, SCTA_D_RCPT_R);

			/* get another response */
			ret = SMTP_NO_REPLY;
			goto again;
		}

		/* last reply: overall status, ret will be returned to caller */
		SCTA_SET_STATE(sc_ta, SCTA_DOT_F_R);
 	}
#endif /* MTA_USE_RSAD */

#if SC_DEBUG
	if (sc_t_ctx->sct_sc_ctx->scc_cnf.sc_cnf_debug > 3)
		sm_log_write(sc_t_ctx->sct_sc_ctx->scc_lctx,
			SC_LCAT_CLIENT, SC_LMOD_CLIENT,
			SM_LOG_INFO, 15,
			"sev=INFO, func=sc_rd_reply, thread=%u, da_sess=%s, phase=%d, stat=%m",
			thr_id, sc_sess->scse_id, phase, ret);
#endif /* SC_DEBUG */
	return ret;

  fail:
	return ret;
}

/*
**  SC_GEN_DSN_HDR -- generate bounce header
**	on successful return scse_str contains MIME delimiter for this TA.
**
**	Parameters:
**		sc_t_ctx -- SMTPC thread context
**
**	Returns:
**		usual sm_error code
**
**	Side Effects:
**		on success scse_wr contains a header for a bounce message
**		uses scse_str as temporary string;
**		on successful return scse_str contains the MIME delimiter!
*/

static sm_ret_T
sc_gen_dsn_hdr(sc_t_ctx_P sc_t_ctx)
{
	sm_ret_T ret;
	time_T now;
	sc_sess_P sc_sess;
	sc_ta_P sc_ta;
	sc_rcpt_P sc_rcpt;

	SM_IS_SC_T_CTX(sc_t_ctx);
	sc_sess = sc_t_ctx->sct_sess;
	SM_IS_SC_SE(sc_sess);
	sc_ta = sc_sess->scse_ta;
	SM_IS_SC_TA(sc_ta);

	if (!SCTA_IS_FLAG(sc_ta, SCTA_FL_DBOUNCE) &&
	    !SCTA_IS_FLAG(sc_ta, SCTA_FL_BOUNCE) &&
	    !SCTA_IS_FLAG(sc_ta, SCTA_FL_DELAY))
		return SM_SUCCESS;


	now = st_time();
	sm_str_clr(sc_sess->scse_str);
	ret = arpadate(&now, sc_sess->scse_str);
	sc_rcpt = SC_RCPTS_FIRST(&sc_ta->scta_rcpts);

	/* Send header for bounce */
	/* fixme: Put this in a library function */
	/* requires hostname (Hack!) */
	if (sm_is_err(ret = sm_str_scat(sc_sess->scse_wr,
				"From: Mailer-Daemon@")) ||
	    sm_is_err(ret = sm_str_cat(sc_sess->scse_wr,
				sc_t_ctx->sct_sc_ctx->scc_hostname)) ||
	    sm_is_err(ret = sm_str_scat(sc_sess->scse_wr,
				"\r\n"
				"Date: ")) ||
	    sm_is_err(ret = sm_str_cat(sc_sess->scse_wr,
				sc_sess->scse_str)) ||
	    sm_is_err(ret = sm_str_scat(sc_sess->scse_wr,
				"\r\nSubject: ")))
		goto error;

	if (sm_is_err(ret = sm_str_scat(sc_sess->scse_wr,
		SCTA_IS_FLAG(sc_ta, SCTA_FL_DELAY)
		? "Delayed mail\r\n"
		: SCTA_IS_FLAG(sc_ta, SCTA_FL_BOUNCE)
		? "Undeliverable mail\r\n"
		: "Double Bounce\r\n")))
			goto error;

	if (sm_is_err(ret = sm_str_scat(sc_sess->scse_wr, "To: ")) ||
	    sm_is_err(ret = sm_str_cat(sc_sess->scse_wr, sc_rcpt->scr_pa)) ||
	    sm_is_err(ret = sm_str_scat(sc_sess->scse_wr, "\r\n")))
		goto error;

	/* create MIME delimiter in scse_str */
	sm_str_clr(sc_sess->scse_str);
	if (sm_is_err(ret = sm_str_cat(sc_sess->scse_str,
				sc_t_ctx->sct_sc_ctx->scc_hostname)) ||
	    sm_is_err(ret = sm_str_put(sc_sess->scse_str, '/')) ||
	    sm_is_err(ret = sm_str_scat(sc_sess->scse_str, sc_ta->scta_id)))
		goto error;

	/* Add MIME header here... */
	if (SCTA_IS_FLAG(sc_ta, SCTA_FL_DSN_MIME) &&
	    (SCTA_IS_FLAG(sc_ta, SCTA_FL_HDR_ONLY) ||
	     !SCTA_IS_FLAG(sc_ta, SCTA_FL_NO_BODY)))
	{
		if (sm_is_err(ret = sm_str_scat(sc_sess->scse_wr,
				"MIME-Version: 1.0\r\n"
				"Content-Type: multipart/mixed; boundary=\""))
		   || sm_is_err(ret = sm_str_cat(sc_sess->scse_wr, sc_sess->scse_str))
		   || sm_is_err(ret = sm_str_scat(sc_sess->scse_wr, "\"\r\n\r\n"
				"This is a MIME-encapsulated message\r\n\r\n--"))
		   || sm_is_err(ret = sm_str_cat(sc_sess->scse_wr, sc_sess->scse_str))
		   || sm_is_err(ret = sm_str_scat(sc_sess->scse_wr, "\r\n")))
			goto error;
	}

	if (sm_is_err(ret = sm_str_scat(sc_sess->scse_wr,
		SCTA_IS_FLAG(sc_ta, SCTA_FL_DELAY)
		? "\r\n"
		  "A mail from you has not yet been successfully delivered.\r\n"
		  "It will be tried again, so you do not need to resend it!\r\n"
		  "See below for details.\r\n\r\n"
		: SCTA_IS_FLAG(sc_ta, SCTA_FL_BOUNCE)
		? "\r\n"
		  "A mail from you could not be delivered. "
		  "See below for details.\r\n\r\n"
		: "\r\nA bounce message could not be delivered.\r\n"
		  "See below for details.\r\n\r\n")))
	{
		goto error;
	}

	return SM_SUCCESS;

  error:
	sm_str_clr(sc_sess->scse_str);
	return ret;
}

/* ----- BEGIN COPY FROM smtpc/smclt.c ----- */

/*
**  SC_WRTBUF -- write buffer
**
**	Parameters:
**		sc_t_ctx -- SMTPC thread context
**		buf -- buffer to write
**		wrt -- number of bytes to write
**		cfp -- output file
**		ptotal_size -- (pointer to) total size (in/out)
**
**	Returns:
**		usual error code
*/

static sm_ret_T
sc_wrtbuf(sc_t_ctx_P sc_t_ctx, uchar *buf, size_t wrt, sm_file_T *cfp, off_t *ptotal_size)
{
	size_t offset;
	ssize_t byteswritten;
	sm_ret_T ret;
	off_t total_size;

	ret = SM_SUCCESS;
	if (wrt == 0)
		return ret;
	offset = 0;
	total_size = *ptotal_size;
	do {
		ret = sm_io_write(cfp, buf + offset, wrt, &byteswritten);
		if (sm_is_err(ret))
			return ret;
		if (byteswritten > 0) {
			total_size += byteswritten;
			offset += byteswritten;
		}

		/* paranoia... should this be just an if ()? */
		SM_ASSERT(wrt >= byteswritten);
		if (wrt > byteswritten)
			sm_log_write(sc_t_ctx->sct_sc_ctx->scc_lctx,
				SC_LCAT_CLIENT, SC_LMOD_CLIENT,
				SM_LOG_INFO, 8,
				"sev=INFO, func=sc_wrtbuf, wrt=%d, written=%d, ret=%m",
				wrt, byteswritten, ret);

		wrt -= byteswritten;
	} while (wrt > 0);
	*ptotal_size = total_size;
	return ret;
}

/*
**  SC_MSGDSN -- send msg (with modifications for DSN)
**
**	Parameters:
**		sc_t_ctx -- SMTPC thread context
**		ptotal_size -- (pointer to) total size (in/out)
**
**	Returns:
**		usual error code
*/

static sm_ret_T
sc_msgdsn(sc_t_ctx_P sc_t_ctx, off_t *ptotal_size)
{
	int c;
	size_t wrt, idx;
	sm_ret_T ret;
	uint eoh_state;
	off_t total_size, cdb_rd;
	sc_ta_P sc_ta;
	sc_sess_P sc_sess;
	sm_file_T *cfp;
	static SM_DECL_EOH;

	SM_IS_SC_T_CTX(sc_t_ctx);
	sc_sess = sc_t_ctx->sct_sess;
	SM_IS_SC_SE(sc_sess);
	sc_ta = sc_sess->scse_ta;
	SM_IS_SC_TA(sc_ta);
	cfp = sc_sess->scse_fp;
	eoh_state = 0;
	cdb_rd = 0;
	total_size = *ptotal_size;
	ret = SM_SUCCESS;

	do {
		/* get new buffer */
		c = sm_rget(sc_ta->scta_cdb_fp);
		if (SM_IO_EOF == c)
			break;

		/* +1 because we got the first char already */
		wrt = f_r(*(sc_ta->scta_cdb_fp)) + 1;
		cdb_rd += wrt;

		/* check whether eoh is in this block */
		if (SCTA_IS_FLAG(sc_ta, SCTA_FL_HDR_ONLY) && eoh_state < SM_EOH_LEN) {
			uchar *p;

			p = f_bfbase(*sc_ta->scta_cdb_fp);
			idx = 0;
			do {
				if (c == eoh[eoh_state])
					++eoh_state;
				else {
					eoh_state = 0;
					if (c == eoh[eoh_state])
						++eoh_state;
				}
				++idx;
				if (idx < wrt)
					c = p[idx];
			} while (eoh_state < SM_EOH_LEN && idx < wrt);

			/*
			**  Found end of header?  If yes: set the
			**  number of bytes to write; the buffer
			**  MUST end with \r\n such that the final
			**  dot is properly recognized (see below).
			*/

			if (eoh_state >= SM_EOH_LEN) {
				SCTA_SET_FLAG(sc_ta, SCTA_FL_CUT_HDR);
				wrt = idx;
			}
		}

		/*
		**  For a MIME DSN the last 3 bytes (.\r\n)
		**  of cdb must not be sent.
		*/

		if (SCTA_IS_FLAG(sc_ta, SCTA_FL_DSN_MIME) &&
		    !SCTA_IS_FLAG(sc_ta, SCTA_FL_CDB_CUT) &&
		    cdb_rd + 3 >= sc_ta->scta_msg_sz_b
		   )
		{
			size_t cutoff;

			SM_ASSERT(sc_ta->scta_msg_sz_b >= SM_EOT_LEN);
			SM_ASSERT(cdb_rd <= sc_ta->scta_msg_sz_b);
			cutoff = sc_ta->scta_msg_sz_b - cdb_rd + 3;
			if (wrt > cutoff)
				wrt -= cutoff;
			else
				wrt = 0;
			SCTA_SET_FLAG(sc_ta, SCTA_FL_CUT_DOT);
		}

		ret = sc_wrtbuf(sc_t_ctx,
			f_bfbase(*sc_ta->scta_cdb_fp), wrt, cfp, &total_size);
		if (sm_is_err(ret))
			goto error;

		if (SCTA_IS_FLAG(sc_ta, SCTA_FL_CUT_HDR) ||
		    SCTA_IS_FLAG(sc_ta, SCTA_FL_CUT_DOT))
		{
			/* the data above ended with \r\n (eoh) */
			if (SCTA_IS_FLAG(sc_ta, SCTA_FL_DSN_MIME)) {
				if (sm_is_err(ret =
					sm_str_scopy(sc_sess->scse_wr, "\r\n--"))
				   || sm_is_err(ret =
					sm_str_cat(sc_sess->scse_wr, sc_sess->scse_str))
				   || sm_is_err(ret =
					sm_str_scat(sc_sess->scse_wr, "--\r\n.\r\n")))
					goto error;
			}
			else
				ret = sm_str_scopy(sc_sess->scse_wr, ".\r\n");

			if (sm_is_err(ret))
				goto error;
			ret = sc_command(sc_t_ctx, SC_PHASE_NOREPLY);
			if (sm_is_err(ret))
				goto error;
			c = SM_IO_EOF;	/* force end of loop */
		}
	} while (c != SM_IO_EOF);
	*ptotal_size = total_size;
	return ret;

  error:
	*ptotal_size = total_size;
	return ret;
}

/*
**  SC_HDRS_APP -- append headers
**
**	Parameters:
**		sc_t_ctx -- SMTPC thread context
**		sm_hdrmod -- current header to append
**		ptotal_size -- (pointer to) total size (in/out)
**
**	Returns:
**		usual error code
*/

static sm_ret_T
sc_hdrs_app(sc_t_ctx_P sc_t_ctx, sm_hdrmod_P sm_hdrmod, off_t *ptotal_size)
{
	sm_ret_T ret;
	sc_ta_P sc_ta;
	sc_sess_P sc_sess;
	sm_file_T *cfp;
	sm_cstr_P hdr;

	SM_IS_SC_T_CTX(sc_t_ctx);
	sc_sess = sc_t_ctx->sct_sess;
	SM_IS_SC_SE(sc_sess);
	sc_ta = sc_sess->scse_ta;
	SM_IS_SC_TA(sc_ta);
	cfp = sc_sess->scse_fp;
	ret = SM_SUCCESS;

	while (sm_hdrmod != NULL &&
		SM_HM_TYPE_APPEND == sm_hdrmod->sm_hm_type
		&& (hdr = sm_hdrmod->sm_hm_hdr) != NULL)
	{
		ret = sc_wrtbuf(sc_t_ctx, sm_cstr_data(hdr),
			sm_cstr_getlen(hdr),
			cfp, ptotal_size);
		sm_hdrmod_rm(&sc_ta->scta_hdrmodhd);
		sm_hdrmod = HDRMODL_FIRST(sc_ta->scta_hdrmodhd);
	}
	return ret;
}

/*
**  SC_HDRS_INS -- perform header insertions at a given position
**
**	Parameters:
**		sc_t_ctx -- SMTPC thread context
**		hdr_count -- current header number
**		writeit -- are we writing or skipping headers?
**		idx -- current (read) index in file buffer
**		poffset -- (ptr) current (write) offset in file buffer (in/out)
**		ptotal_size -- (pointer to) total size (in/out)
**
**	Returns:
**		>0: number of header insertions
**		<=0: usual error code
*/

static sm_ret_T
sc_hdrs_ins(sc_t_ctx_P sc_t_ctx, uint hdr_count, bool writeit, size_t idx, size_t *poffset, off_t *ptotal_size)
{
	sm_ret_T ret;
	uint mods;
	sm_hdrmod_P sm_hdrmod;
	sm_cstr_P hdr;
	sc_ta_P sc_ta;
	sc_sess_P sc_sess;
	sm_file_T *cfp;

	SM_IS_SC_T_CTX(sc_t_ctx);
	sc_sess = sc_t_ctx->sct_sess;
	SM_IS_SC_SE(sc_sess);
	sc_ta = sc_sess->scse_ta;
	SM_IS_SC_TA(sc_ta);
	mods = 0;

	cfp = sc_sess->scse_fp;

	if (writeit &&
	    (sm_hdrmod = HDRMODL_FIRST(sc_ta->scta_hdrmodhd)) != NULL &&
	    (SM_HM_TYPE_INSERT == sm_hdrmod->sm_hm_type ||
	     SM_HM_TYPE_REPLACE == sm_hdrmod->sm_hm_type) &&
	    hdr_count == sm_hdrmod->sm_hm_pos &&
	    (hdr = sm_hdrmod->sm_hm_hdr) != NULL)
	{
		size_t offset;

		SM_ASSERT(poffset != NULL);
		offset = *poffset;
		SM_ASSERT(idx >= offset);
		ret = sc_wrtbuf(sc_t_ctx,
			f_bfbase(*sc_ta->scta_cdb_fp) + offset,
			idx - offset, cfp, ptotal_size);
		if (sm_is_err(ret))
			return ret;
		*poffset = idx;
	}

	while ((sm_hdrmod = HDRMODL_FIRST(sc_ta->scta_hdrmodhd)) != NULL &&
	       (SM_HM_TYPE_INSERT == sm_hdrmod->sm_hm_type ||
		SM_HM_TYPE_REPLACE == sm_hdrmod->sm_hm_type) &&
	       hdr_count == sm_hdrmod->sm_hm_pos &&
	       (hdr = sm_hdrmod->sm_hm_hdr) != NULL)
	{
		++mods;
		ret = sc_wrtbuf(sc_t_ctx, sm_cstr_data(hdr),
			sm_cstr_getlen(hdr), cfp, ptotal_size);
		if (sm_is_err(ret))
			return ret;
		sm_hdrmod_rm(&sc_ta->scta_hdrmodhd);
		sm_hdrmod = HDRMODL_FIRST(sc_ta->scta_hdrmodhd);
	}
	return (mods > 0) ? mods : SM_SUCCESS;
}

/*
**  SC_MSGMOD -- send msg (possibly with modifications)
**
**	Parameters:
**		sc_t_ctx -- SMTPC thread context
**		ptotal_size -- (pointer to) total size (in/out)
**
**	Returns:
**		usual error code
*/

static sm_ret_T
sc_msgmod(sc_t_ctx_P sc_t_ctx, off_t *ptotal_size)
{
	int c;
	size_t wrt, idx;
	sm_ret_T ret;
	uint eoh_state;
	uint hdr_count, eol_state;
	bool writeit;
	off_t total_size, cdb_rd;
	sc_ta_P sc_ta;
	sc_sess_P sc_sess;
	sm_file_T *cfp;
	sm_hdrmod_P sm_hdrmod;
	sm_cstr_P hdr;
	static SM_DECL_EOH;
	static SM_DECL_EOL;

	SM_IS_SC_T_CTX(sc_t_ctx);
	sc_sess = sc_t_ctx->sct_sess;
	SM_IS_SC_SE(sc_sess);
	sc_ta = sc_sess->scse_ta;
	SM_IS_SC_TA(sc_ta);
	cfp = sc_sess->scse_fp;
	eoh_state = 0;
	cdb_rd = 0;
	total_size = *ptotal_size;
	ret = SM_SUCCESS;
	hdr_count = 0;
	eol_state = 0;
	writeit = true;

	sm_hdrmod = HDRMODL_FIRST(sc_ta->scta_hdrmodhd);
	while (sm_hdrmod != NULL &&
	       SM_HM_TYPE_PREPEND == sm_hdrmod->sm_hm_type &&
	       (hdr = sm_hdrmod->sm_hm_hdr) != NULL)
	{
		ret = sc_wrtbuf(sc_t_ctx, sm_cstr_data(hdr),
			sm_cstr_getlen(hdr), cfp, &total_size);
		if (sm_is_err(ret))
			goto error;
		sm_hdrmod_rm(&sc_ta->scta_hdrmodhd);
		sm_hdrmod = HDRMODL_FIRST(sc_ta->scta_hdrmodhd);
	}
	if (sm_hdrmod != NULL &&
	    (SM_HM_TYPE_REMOVE == sm_hdrmod->sm_hm_type ||
	     SM_HM_TYPE_REPLACE == sm_hdrmod->sm_hm_type) &&
	    hdr_count == sm_hdrmod->sm_hm_pos)
		writeit = false;

	ret = sc_hdrs_ins(sc_t_ctx, hdr_count, false, 0, NULL, &total_size);
	if (sm_is_err(ret))
		goto error;

	do {
		size_t offset;

		offset = 0;

		/* get new buffer */
		c = sm_rget(sc_ta->scta_cdb_fp);
		if (SM_IO_EOF == c)
			break;

		/* +1 because we got the first char already */
		wrt = f_r(*(sc_ta->scta_cdb_fp)) + 1;
		cdb_rd += wrt;

		/* analyse current buffer (if it contains headers) */
		if (SCTA_IS_FLAG(sc_ta, SCTA_FL_HDR_ONLY|SCTA_FL_HDR_SCAN)
		    && eoh_state < SM_EOH_LEN)
		{
			uchar *p;

			p = f_bfbase(*sc_ta->scta_cdb_fp);
			idx = 0;
			do {
				if (c == eoh[eoh_state])
					++eoh_state;
				else {
					eoh_state = 0;
					if (c == eoh[eoh_state])
						++eoh_state;
				}

				if (eol_state >= SM_EOL_LEN && c != ' ' && c != '\t') {
					bool hdr_rm;

					++hdr_count;
					hdr_rm = (sm_hdrmod != NULL &&
						  (SM_HM_TYPE_REMOVE == sm_hdrmod->sm_hm_type ||
						   SM_HM_TYPE_REPLACE == sm_hdrmod->sm_hm_type)
						  && hdr_count == sm_hdrmod->sm_hm_pos);
					if (hdr_rm &&
					    SM_HM_TYPE_REMOVE == sm_hdrmod->sm_hm_type)
					{
						sm_hdrmod_rm(&sc_ta->scta_hdrmodhd);
						sm_hdrmod = HDRMODL_FIRST(sc_ta->scta_hdrmodhd);
					}

					if (writeit && hdr_rm) {
						SM_ASSERT(idx >= offset);
						ret = sc_wrtbuf(sc_t_ctx,
							f_bfbase(*sc_ta->scta_cdb_fp) + offset,
							idx - offset,
							cfp,
							&total_size);
						if (sm_is_err(ret))
							goto error;
						offset = idx;
						if (c != '\r')
							writeit = false;
					}
					else if (!writeit && !hdr_rm) {
						writeit = true;
						offset = idx;
					}
					ret = sc_hdrs_ins(sc_t_ctx, hdr_count,
						writeit, idx, &offset, &total_size);
					if (ret > 0)
						sm_hdrmod = HDRMODL_FIRST(sc_ta->scta_hdrmodhd);
				}

				/*
				**  End of headers reached? This is not
				**  entirely correct as it just checks for CR
				**  not CR LF, but we have to "insert" these
				**  headers before the second CRLF
				*/

				if (eol_state >= SM_EOL_LEN && c == '\r' &&
				    sm_hdrmod != NULL &&
				    SM_HM_TYPE_APPEND == sm_hdrmod->sm_hm_type
				    && sm_hdrmod->sm_hm_hdr != NULL)
				{
					ret = sc_wrtbuf(sc_t_ctx,
						f_bfbase(*sc_ta->scta_cdb_fp) + offset,
						idx - offset, cfp,
						&total_size);
					if (sm_is_err(ret))
						goto error;
					ret = sc_hdrs_app(sc_t_ctx, sm_hdrmod, &total_size);
					offset = idx;
				}

				if (c == eol[eol_state])
					++eol_state;
				else {
					eol_state = 0;
					if (c == eol[eol_state])
						++eol_state;
				}

				++idx;
				if (idx < wrt)
					c = p[idx];
			} while (eoh_state < SM_EOH_LEN && idx < wrt);
		}

		if (writeit) {
			ret = sc_wrtbuf(sc_t_ctx,
				f_bfbase(*sc_ta->scta_cdb_fp) + offset,
				wrt - offset, cfp, &total_size);
			if (sm_is_err(ret))
				goto error;
		}
	} while (c != SM_IO_EOF);
	*ptotal_size = total_size;
	return ret;

  error:
	*ptotal_size = total_size;
	return ret;
}

/* ----- END COPY FROM smtpc/smclt.c ----- */

/*
**  SC_MSG -- send msg (without modifications)
**
**	Parameters:
**		sc_t_ctx -- SMTPC thread context
**		ptotal_size -- (pointer to) total size (in/out)
**
**	Returns:
**		usual error code
*/

static sm_ret_T
sc_msg(sc_t_ctx_P sc_t_ctx, off_t *ptotal_size)
{
	int c;
	size_t wrt;
	sm_ret_T ret;
	off_t total_size, cdb_rd;
	sc_ta_P sc_ta;
	sc_sess_P sc_sess;
	sm_file_T *cfp;

	SM_IS_SC_T_CTX(sc_t_ctx);
	sc_sess = sc_t_ctx->sct_sess;
	SM_IS_SC_SE(sc_sess);
	sc_ta = sc_sess->scse_ta;
	SM_IS_SC_TA(sc_ta);
	cfp = sc_sess->scse_fp;

	/* read data and send it out... */
	cdb_rd = 0;
	total_size = *ptotal_size;
	ret = SM_SUCCESS;
#if SC_TEST
	if (SM_IS_FLAG(sc_t_ctx->sct_sc_ctx->scc_cnf.sc_cnf_tests,
	               SCC_CNF_TEST_BODY_TMO))
	{
			st_sleep(SEC2USEC(10));
	}
#endif


	do {
		/* get new buffer */
		c = sm_rget(sc_ta->scta_cdb_fp);
		if (SM_IO_EOF == c)
			break;

		/* +1 because we got the first char already */
		wrt = f_r(*(sc_ta->scta_cdb_fp)) + 1;
		cdb_rd += wrt;
		ret = sc_wrtbuf(sc_t_ctx, f_bfbase(*sc_ta->scta_cdb_fp), wrt,
				cfp, &total_size);
		if (sm_is_err(ret))
			goto error;
	} while (c != SM_IO_EOF);
	*ptotal_size = total_size;
	return ret;

  error:
	*ptotal_size = total_size;
	return ret;
}

/*
**  SC_DATA -- send data
**
**	Parameters:
**		sc_t_ctx -- SMTPC thread context
**
**	Returns:
**		>=0: SMTP reply code
**		<0: error code
*/

static sm_ret_T
sc_data(sc_t_ctx_P sc_t_ctx)
{
	ssize_t byteswritten;
	sm_ret_T ret;
	off_t total_size;
	sc_sess_P sc_sess;
	sc_ta_P sc_ta;
	sm_file_T *cfp;
	cdb_ctx_P cdb_ctx;
#if SM_SMTPC_HDRMOD_TEST
	sm_hdrmod_P sm_hdrmod;
#endif

	SM_IS_SC_T_CTX(sc_t_ctx);
	sc_sess = sc_t_ctx->sct_sess;
	SM_IS_SC_SE(sc_sess);
	sc_ta = sc_sess->scse_ta;
	SM_IS_SC_TA(sc_ta);
	cfp = sc_sess->scse_fp;
	total_size = 0;
	cdb_ctx = sc_t_ctx->sct_sc_ctx->scc_cdb_ctx;

#if SC_PIPELINING
	if (NO_RCPTS(sc_ta)) {
		const uchar eod[] = ".\r\n";

		/* just send a single dot, see RFC 2920, 3.1 */
		ret = sm_io_write(cfp, eod, 3, &byteswritten);
		if (sm_is_err(ret))
			goto error;
		if (byteswritten > 0)
			total_size += byteswritten;
	}
	else {
#endif /* SC_PIPELINING */

	/* Compose bounce message (header, text from QMGR) */
	if (SCTA_IS_FLAG(sc_ta, SCTA_FL_DELAY|SCTA_FL_BOUNCE|SCTA_FL_DBOUNCE)) {
		sm_str_P str_wr;

		sm_str_clr(sc_sess->scse_wr);
		if (SCSE_IS_FLAG(sc_sess, SCSE_FL_RETPATH)) {
			if (sm_is_err(ret = sm_str_scat(sc_sess->scse_wr,
						"Return-Path: ")) ||
			    sm_is_err(ret = sm_str_cat(sc_sess->scse_wr,
					sc_ta->scta_mail->scm_pa)) ||
			    sm_is_err(sm_str_scat(sc_sess->scse_wr, "\r\n")))
				goto error;
		}

		/* Create header for bounce */
		ret = sc_gen_dsn_hdr(sc_t_ctx);
		if (sm_is_err(ret))
			goto error;
		ret = sc_command(sc_t_ctx, SC_PHASE_NOREPLY);
		if (sm_is_err(ret))
			goto error;

		/* Hack: save wr string and replace it temporarily */
		str_wr = sc_sess->scse_wr;
		sc_sess->scse_wr = sc_ta->scta_b_msg;
		ret = sc_command(sc_t_ctx, SC_PHASE_NOREPLY);
		sc_sess->scse_wr = str_wr;
		if (sm_is_err(ret))
			goto error;
		total_size += sm_str_getlen(sc_ta->scta_b_msg);
		sm_str_clr(sc_sess->scse_wr);

		if (!SCTA_IS_FLAG(sc_ta, SCTA_FL_DSN_MIME) &&
		    SCTA_IS_FLAG(sc_ta, SCTA_FL_HDR_ONLY))
		{
			if (sm_is_err(ret = sm_str_scopy(sc_sess->scse_wr,
					"\r\nThe headers of your original mail follow:\r\n")))
				goto error;
		}
		else if (!SCTA_IS_FLAG(sc_ta, SCTA_FL_DSN_MIME) &&
			 !SCTA_IS_FLAG(sc_ta, SCTA_FL_NO_BODY))
		{
			if (sm_is_err(ret = sm_str_scopy(sc_sess->scse_wr,
					"\r\nYour original mail follows:\r\n")))
				goto error;
		}

		if (SCTA_IS_FLAG(sc_ta, SCTA_FL_DSN_MIME) &&
		    (SCTA_IS_FLAG(sc_ta, SCTA_FL_HDR_ONLY) ||
		     !SCTA_IS_FLAG(sc_ta, SCTA_FL_NO_BODY)))
		{
			if (sm_is_err(ret = sm_str_scopy(sc_sess->scse_wr,
					"\r\n--"))
			   || sm_is_err(ret = sm_str_cat(sc_sess->scse_wr,
					sc_sess->scse_str))
			   || sm_is_err(ret = sm_str_scat(sc_sess->scse_wr,
					"\r\n"
					"Content-Type: message/rfc822"
					"\r\n\r\n")))
				goto error;
		}

		if (sm_str_getlen(sc_sess->scse_wr)) {
			ret = sc_command(sc_t_ctx, SC_PHASE_NOREPLY);
			if (sm_is_err(ret))
				goto error;
			sm_str_clr(sc_sess->scse_wr);
		}
	}

	if (SCTA_IS_FLAG(sc_ta, SCTA_FL_NO_BODY)) {
		if (sm_is_err(ret = sm_str_scopy(sc_sess->scse_wr, "\r\n.\r\n")))
			goto error;
		ret = sc_command(sc_t_ctx, SC_PHASE_NOREPLY);
		if (sm_is_err(ret))
			goto error;
	}
	else {
		SM_ASSERT(sc_ta->scta_cdb_fp != NULL);

		if (SCTA_IS_FLAG(sc_ta, SCTA_FL_DSN_MIME)) {
			off_t cdb_sz_b;

			ret = sm_io_getinfo(sc_ta->scta_cdb_fp, SM_IO_WHAT_SIZE, &cdb_sz_b);
			if (sc_ta->scta_msg_sz_b == 0)
				sc_ta->scta_msg_sz_b = cdb_sz_b;
			else if (cdb_sz_b != sc_ta->scta_msg_sz_b) {
				sm_log_write(sc_t_ctx->sct_sc_ctx->scc_lctx,
					SC_LCAT_CLIENT, SC_LMOD_CLIENT,
					SM_LOG_ERROR, 8,
					"sev=ERROR, func=sc_data, status=size_mismatch, cdb_sz_b=%lu, msg_sz_b=%lu, ret=%d",
					(ulong) cdb_sz_b, (ulong) sc_ta->scta_msg_sz_b,
					ret);
			}
		}

		if (SCSE_IS_FLAG(sc_sess, SCSE_FL_RETPATH) &&
		    !SCTA_IS_FLAG(sc_ta, SCTA_FL_DELAY|SCTA_FL_BOUNCE|SCTA_FL_DBOUNCE))
		{
			if (sm_is_err(ret = sm_str_scopy(sc_sess->scse_wr,
						"Return-Path: ")) ||
			    sm_is_err(ret = sm_str_cat(sc_sess->scse_wr,
					sc_ta->scta_mail->scm_pa)) ||
			    sm_is_err(sm_str_scat(sc_sess->scse_wr, "\r\n")))
				goto error;
			ret = sc_command(sc_t_ctx, SC_PHASE_NOREPLY);
			if (sm_is_err(ret))
				goto error;
		}

#if SM_SMTPC_HDRMOD_TEST
		sm_hdrmod = NULL;
		if (Hdr_pre != NULL || Hdr_app != NULL)
			ret = sc_hdrmod_new(sc_ta, &sm_hdrmod);
		if (Hdr_pre != NULL && sm_hdrmod != NULL) {
			sm_hdrmod->sm_hm_hdr = Hdr_pre;
			sm_hdrmod->sm_hm_type = SM_HM_TYPE_PREPEND;
			Hdr_pre = NULL;
			if (Hdr_app != NULL)
				ret = sc_hdrmod_new(sc_ta, &sm_hdrmod);
		}
		if (Hdr_app != NULL && sm_hdrmod != NULL) {
			sm_hdrmod->sm_hm_hdr = Hdr_app;
			sm_hdrmod->sm_hm_type = SM_HM_TYPE_APPEND;
			Hdr_app = NULL;
		}
#endif /* SM_SMTPC_HDRMOD_TEST */

		if (SCTA_IS_FLAG(sc_ta, SCTA_FL_HDR_ONLY|SCTA_FL_DSN_MIME))
			ret = sc_msgdsn(sc_t_ctx, &total_size);
		else if (!HDRMODL_EMPTY(sc_ta->scta_hdrmodhd))
			ret = sc_msgmod(sc_t_ctx, &total_size);
		else
			ret = sc_msg(sc_t_ctx, &total_size);
		if (sm_is_err(ret))
			goto error;
		ret = cdb_close(cdb_ctx, sc_ta->scta_cdb_fp, SM_IO_CF_NONE);
		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, 8,
				"sev=ERROR, func=sc_data, cdb_close=%m"
				, ret);
		sc_ta->scta_cdb_fp = NULL;
	}

#if SC_PIPELINING
	}

	/*
	**  Send QUIT here if this is only one TA (and PIPELINING is active)
	**  to avoid one round trip.
	**  However, doing this makes error handling more complicated:
	**  why should we abort the transaction if there is an error on QUIT?
	**  The server could have replied properly to the final dot.
	*/

	if (SCSE_IS_CAP(sc_sess, SCSE_CAP_PIPELINING) &&
	    !SM_IS_FLAG(sc_t_ctx->sct_sc_ctx->scc_cnf.sc_cnf_confflags,
			SCC_CFL_SEP_DOT_QUIT) &&
	    SCSE_IS_FLAG(sc_sess, SCSE_FL_LAST_TA|SCSE_FL_CLOSE))
	{
		if (sm_is_err(ret = sm_str_scopy(sc_sess->scse_wr, "QUIT\r\n")))
			goto error;
		ret = sc_command(sc_t_ctx, SC_PHASE_NOREPLY);
		if (sm_is_err(ret))
			goto error;
		SCSE_SET_FLAG(sc_sess, SCSE_FL_QUIT_S);
	}
#endif

	SCTA_SET_STATE(sc_ta, SCTA_DOT_S);
	ret = sc_rd_reply(sc_t_ctx,
			SCSE_IS_FLAG(sc_sess, SCSE_FL_LMTP)
			? SC_PHASE_LDAT
			  : SCSE_IS_FLAG(sc_sess, SCSE_FL_RSAD)
			    ? SC_PHASE_RSAD : SC_PHASE_DOT);
	if (ret != SMTP_OK && sm_str_getlen(sc_sess->scse_str) > 0) {
		sc_ta->scta_reply = sm_str_dup(sc_ta->scta_rpool, sc_sess->scse_str);
	}
#if SC_STATS
	else
		++SC_TA_CNT_OK(sc_t_ctx->sct_sc_ctx);
#endif

	sm_log_write(sc_t_ctx->sct_sc_ctx->scc_lctx,
		SC_LCAT_CLIENT, SC_LMOD_CLIENT,
		SM_LOG_INFO, 8,
		"sev=INFO, func=sc_data, thread=%u, da_sess=%s, da_ta=%s, ss_ta=%s, where=final_dot, size=%lu, stat=%m, reply=%@S",
		sc_t_ctx->sct_thr_id, sc_sess->scse_id,
		sc_ta->scta_id, sc_ta->scta_ssta_id,
		(ulong) total_size, ret, sc_sess->scse_str);
	return ret;

  error:
	/* we have to abort the connection */
	if (sc_ta->scta_cdb_fp != NULL) {
		(void) cdb_close(cdb_ctx, sc_ta->scta_cdb_fp, SM_IO_CF_NONE);
		sc_ta->scta_cdb_fp = NULL;
	}
	if (cfp != NULL) {
		(void) sm_io_close(cfp, SM_IO_CF_NONE);
		sc_sess->scse_fp = NULL;
		sc_sess->scse_state = SCSE_ST_CLOSED;	/* error state? */
	}
	if (sc_ta->scta_err_state == DA_ERR_NONE ||
	    (da_ta_err_cmd(sc_ta->scta_err_state) == DA_TA_ERR_RCPT &&
	     sc_ta->scta_rcpts_ok > 0))
		sc_ta->scta_err_state = DA_TA_ERR_BODY_I;
	return ret;
}

/*
**  SC_COMMAND -- send one SMTP command, read reply (unless turned off)
**
**	Parameters:
**		sc_t_ctx -- SMTPC thread context
**		phase -- phase of SMTP dialogue
**
**	Returns:
**		SMTP reply code (2xy -> SMTP_OK) or error code
*/

static sm_ret_T
sc_command(sc_t_ctx_P sc_t_ctx, int phase)
{
	sm_ret_T ret;
	ssize_t b;
	size_t l;
	uint thr_id;
	sc_sess_P sc_sess;

	SM_IS_SC_T_CTX(sc_t_ctx);
	sc_sess = sc_t_ctx->sct_sess;
	SM_IS_SC_SE(sc_sess);
	thr_id = sc_t_ctx->sct_thr_id;

	l = sm_str_getlen(sc_sess->scse_wr);
#if SC_DEBUG
	if (sc_t_ctx->sct_sc_ctx->scc_cnf.sc_cnf_debug > 3) {
		/* multiple print statements... log? */
		sm_io_fprintf(smioerr, "thread=%u, da_sess=%s, send[%d]: ", thr_id, sc_sess->scse_id, l);
		sm_io_write(smioerr, sm_str_data(sc_sess->scse_wr), l, &b);
		sm_io_fprintf(smioerr, "\n");
		sm_io_flush(smioerr);
	}
#endif /* SC_DEBUG */

	do {
		ret = sm_io_write(sc_sess->scse_fp,
				sm_str_data(sc_sess->scse_wr), l, &b);
		if (sm_is_err(ret))
		{
			sm_log_write(sc_t_ctx->sct_sc_ctx->scc_lctx,
				SC_LCAT_CLIENT, SC_LMOD_CLIENT,
				SM_LOG_WARN, 11,
				"sev=WARN, func=sc_command, thread=%u, da_sess=%s, where=write, n=%d, r=%d, ret=%m",
				thr_id, sc_sess->scse_id, l, (int) b, ret);

			/* I/O error (always??) */
			SCSE_SET_FLAG(sc_sess, SCSE_FL_IO_ERR|SCSE_FL_LOGGED);
			return ret;
		}

		/* paranoia... should this be just an if (..)? */
		SM_ASSERT(l >= b);
		l -= b;
	} while (l > 0);

#if SC_PIPELINING
	if (!SCSE_IS_CAP(sc_sess, SCSE_CAP_PIPELINING) || !sc_pipeline_ok(phase)) {
#endif
		ret = sm_io_flush(sc_sess->scse_fp);
		if (sm_is_err(ret)) {
			sm_log_write(sc_t_ctx->sct_sc_ctx->scc_lctx,
				SC_LCAT_CLIENT, SC_LMOD_CLIENT,
				SM_LOG_WARN, 11,
				"sev=WARN, func=sc_command, thread=%u, da_sess=%s, where=flush, n=%d, ret=%m",
				thr_id, sc_sess->scse_id, (int) b, ret);
			return ret;
		}
#if SC_PIPELINING
	}
#endif

	if (SC_PHASE_NOREPLY == phase)
		return SM_SUCCESS;
	return sc_rd_reply(sc_t_ctx, phase);
}

#if MTA_USE_TLS
/*
**  SC_TLSREQ -- Check STARTTLS requirements
**
**	Parameters:
**		sc_t_ctx -- SMTPC thread context
**		rcode -- reply code to use in case of an error
**
**	Returns:
**		usual return code
*/

static sm_ret_T
sc_tlsreq(sc_t_ctx_P sc_t_ctx, tlsreq_cnf_P tlsreq_cnf, sm_ret_T rcode)
{
	sm_ret_T ret;
	uint u;
	const char *cstr;
	sm_str_P str;
	sc_sess_P sc_sess;

	SM_IS_SC_T_CTX(sc_t_ctx);
	sc_sess = sc_t_ctx->sct_sess;
	SM_IS_SC_SE(sc_sess);

	ret = SM_SUCCESS;
	if (SM_IS_FLAG(tlsreq_cnf->tlsreqcnf_flags, TLSREQ_FL_VRFD) &&
		TLS_VRFY_OK != sc_sess->scse_tlsi->tlsi_vrfy)
			return rcode;
	u = tlsreq_cnf->tlsreqcnf_min_cipher_bits;
	if (SM_IS_FLAG(tlsreq_cnf->tlsreqcnf_flags, TLSREQ_FL_ENCR) &&
		0 == u)
			return rcode;
	if (u > 0 && u > sc_sess->scse_tlsi->tlsi_cipher_bits)
			return rcode;

	if ((cstr = tlsreq_cnf->tlsreqcnf_cert_subject) != NULL &&
		((str = sc_sess->scse_tlsi->tlsi_cert_subject) == NULL ||
		strcasecmp(cstr, sm_str_getdata(str)) != 0))
	{
			sm_log_write(sc_t_ctx->sct_sc_ctx->scc_lctx,
				SC_LCAT_CLIENT, SC_LMOD_CLIENT, SM_LOG_INFO, 9,
				"sev=INFO, func=sc_tlsreq, cert_subject=%#S, required=%s"
				, str, cstr);
			return rcode;
	}
	if ((cstr = tlsreq_cnf->tlsreqcnf_cert_issuer) != NULL &&
		((str = sc_sess->scse_tlsi->tlsi_cert_issuer) == NULL ||
		strcasecmp(cstr, sm_str_getdata(str)) != 0))
	{
			sm_log_write(sc_t_ctx->sct_sc_ctx->scc_lctx,
				SC_LCAT_CLIENT, SC_LMOD_CLIENT, SM_LOG_INFO, 9,
				"sev=INFO, func=sc_tlsreq, cert_issuer=%#S, required=%s"
				, str, cstr);
			return rcode;
	}
	if ((cstr = tlsreq_cnf->tlsreqcnf_common_name) != NULL &&
		((str = sc_sess->scse_tlsi->tlsi_cn_subject) == NULL ||
		strcasecmp(cstr, sm_str_getdata(str)) != 0))
	{
			sm_log_write(sc_t_ctx->sct_sc_ctx->scc_lctx,
				SC_LCAT_CLIENT, SC_LMOD_CLIENT, SM_LOG_INFO, 9,
				"sev=INFO, func=sc_tlsreq, common_name=%#S, required=%s"
				, str, cstr);
			return rcode;
	}

	return ret;
}
#endif /* MTA_USE_TLS */

/*
**  SC_ONE_TA -- perform one SMTPC transaction
**
**	Parameters:
**		sc_t_ctx -- SMTPC thread context
**
**	Returns:
**		SMTP reply code or error code
*/

sm_ret_T
sc_one_ta(sc_t_ctx_P sc_t_ctx)
{
	uint thr_id;
	sm_ret_T ret;
	sc_sess_P sc_sess;
	sc_ta_P sc_ta;
	sc_rcpt_P sc_rcpt, sc_rcpt_nxt;
	sc_rcpts_P sc_rcpts;

	SM_IS_SC_T_CTX(sc_t_ctx);
	sc_sess = sc_t_ctx->sct_sess;
	SM_IS_SC_SE(sc_sess);
	sc_ta = sc_sess->scse_ta;
	SM_IS_SC_TA(sc_ta);
	ret = SM_SUCCESS;
	thr_id = sc_t_ctx->sct_thr_id;
	sm_log_write(sc_t_ctx->sct_sc_ctx->scc_lctx,
		SC_LCAT_CLIENT, SC_LMOD_CLIENT,
		SM_LOG_INFO, 14,
		"sev=INFO, func=sc_one_ta, thread=%u, da_sess=%s, da_ta=%s, ss_ta=%s, where=start_transaction, ta_state=%x",
		thr_id, sc_sess->scse_id, sc_ta->scta_id, sc_ta->scta_ssta_id,
		sc_ta->scta_state);
#if SC_TEST
	if (SM_IS_FLAG(sc_t_ctx->sct_sc_ctx->scc_cnf.sc_cnf_tests,
	               SCC_CNF_TEST_DUMMY))
	{
			SCTA_SET_STATE(sc_ta, SCTA_COMPLETE);
#if SC_STATS
			++SC_TOTAL(sc_t_ctx);
#endif
			return SM_SUCCESS;
	}
#endif

	/* already closed? check also state? */
	if (NULL == sc_sess->scse_fp)
		goto done;

#if MTA_USE_TLS
	ret = sc_tlsreq(sc_t_ctx, &sc_sess->scse_tlsreq_cnf, SMTP_R_SSD);
	if (sm_is_err(ret)) {
		ret = SMTP_R_SSD;
		goto done;
	}
	if (IS_SMTP_REPLY(ret) && SMTP_IS_REPLY_ERROR(ret)) {
		/* better/selectable error code? */
		if (SM_IS_FLAG(sc_sess->scse_tlsreq_cnf.tlsreqcnf_viol,
					TLSREQ_VIOL_PERM))
			sm_str_scopy(sc_sess->scse_wr,
				"503 5.7.0 security requirements not met.\r\n");
		else if (SM_IS_FLAG(sc_sess->scse_tlsreq_cnf.tlsreqcnf_viol,
					TLSREQ_VIOL_TEMP))
			sm_str_scopy(sc_sess->scse_wr,
				"403 4.7.0 security requirements not met.\r\n");
		else { /* default behavior */
			sm_str_scopy(sc_sess->scse_wr,
				"421 4.7.0 security requirements not met.\r\n");
			ret = SMTP_R_SSD;
		}
		goto done;
	}
#endif /* MTA_USE_TLS */

	if (!SCTA_IS_FLAG(sc_ta, SCTA_FL_NO_BODY)) {
		/* open cdb entry */
		ret = cdb_open_read(sc_t_ctx->sct_sc_ctx->scc_cdb_ctx,
				sm_str_getdata(sc_ta->scta_cdb_id),
				sc_ta->scta_cdb_fp);
		if (sm_is_err(ret)) {
			/*
			**  todo: send error code back to QMGR?? Don't retry
			**  if file does not exist? (some bozo may have
			**  removed it or there was a communication problem
			**  between qmgr and smtps)
			*/

			sm_log_write(sc_t_ctx->sct_sc_ctx->scc_lctx,
				SC_LCAT_CLIENT, SC_LMOD_CLIENT,
				SM_LOG_ERR, 4,
				"sev=ERROR, func=sc_one_ta, thread=%u, da_sess=%s, da_ta=%s, ss_ta=%s, cdb=%N, cdb_open=%m",
				sc_t_ctx->sct_thr_id, sc_sess->scse_id,
				sc_ta->scta_id, sc_ta->scta_ssta_id,
				sc_ta->scta_cdb_id, ret);
			sc_ta->scta_err_state = DA_TA_ERR_CDB_I;
			if (sm_error_value(ret) == ENOENT) {
				ret = SMTP_CDB_PERM;
				goto fail;
			}
			else {
				/*
				**  Treat every other error as temp??
				**  Note: this returns the cdb_open() error
				**  code directly to QMGR which might not
				**  understand it... fixme?
				*/

				sc_ta->scta_status = SMTP_CDB_TEMP;
				SCTA_SET_STATE(sc_ta, SCTA_FAIL_TEMP);
				goto done;
			}
		}
	}

	/*
	**  How can we simplify this? Create some constant str and just
	**  use sm_str_catv()?
	**
	**  What about PIPELINING?
	**  Decouple sending of commands and reading of replies.
	**
	**  How to treat errors in this code? That is, if some operation
	**  in smtpc fails, what should be returned to QMGR?
	**  Answer: an internal error, see sm/da.h
	*/

#if MTA_USE_RSAD
	if (SCSE_IS_CAP(sc_sess, SCSE_CAP_RSAD) && sc_ta->scta_rcpts_tot > 1)
		SCSE_SET_FLAG(sc_sess, SCSE_FL_RSAD);
	else
		SCSE_CLR_FLAG(sc_sess, SCSE_FL_RSAD);
#endif

#define MAIL_EXT1	(SCSE_IS_FLAG(sc_sess, SCSE_FL_RSAD) ? " PRDR" : "")

	if (SCSE_IS_CAP(sc_sess, SCSE_CAP_SIZE) && sc_ta->scta_msg_sz_b > 0) {
		sm_str_clr(sc_sess->scse_wr);
		if (sm_strprintf(sc_sess->scse_wr,
			"MAIL From:%S SIZE=%lu%s\r\n"
			, sc_ta->scta_mail->scm_pa, (unsigned long) sc_ta->scta_msg_sz_b
			, MAIL_EXT1) <= 18)
		{
			sc_ta->scta_err_state = DA_TA_ERR_MAIL_I;
			goto fail;
		}
	}
	else if (sm_is_err(ret = sm_str_scopy(sc_sess->scse_wr, "MAIL From:"))
		|| sm_is_err(ret = sm_str_cat(sc_sess->scse_wr,
					sc_ta->scta_mail->scm_pa))
#if MTA_USE_RSAD
		|| (SCSE_IS_FLAG(sc_sess, SCSE_FL_RSAD) &&
		    sm_is_err(ret = sm_str_scat(sc_sess->scse_wr, " PRDR")))
#endif
		|| sm_is_err(sm_str_scat(sc_sess->scse_wr, "\r\n")))
	{
		sc_ta->scta_err_state = DA_TA_ERR_MAIL_I;
		goto fail;
	}
	ret = sc_command(sc_t_ctx, SC_PHASE_MAIL);
#if SC_STATS
	++SC_MAIL_COUNT(sc_t_ctx->sct_sc_ctx);
#endif
	sc_ta->scta_mail->scm_st = ret;
	if (ret != SMTP_NO_REPLY) {
		sm_log_write(sc_t_ctx->sct_sc_ctx->scc_lctx,
			SC_LCAT_CLIENT, SC_LMOD_CLIENT,
			SM_LOG_INFO, 8,
			"sev=INFO, func=sc_one_ta, thread=%u, da_sess=%s, da_ta=%s, ss_ta=%s, mail=%@S, stat=%m, reply=%@S",
			thr_id, sc_sess->scse_id, sc_ta->scta_id,
			sc_ta->scta_ssta_id, sc_ta->scta_mail->scm_pa, ret,
			sc_sess->scse_str);
	}
	if (!SMTPC_OK_REPLY(ret)) {
		sc_ta->scta_mail->scm_reply = sm_str_dup(NULL, sc_sess->scse_rd);
		sc_ta->scta_err_state = DA_TA_ERR_MAIL_S;
		if (SMTP_R_SSD == ret)
			goto done;
		goto fail;
	}
#if SC_PIPELINING
	if (SMTP_NO_REPLY == ret)
		SCTA_SET_STATE(sc_ta, SCTA_MAIL_S);
	else
#endif
		SCTA_SET_STATE(sc_ta, SCTA_MAIL_S|SCTA_MAIL_R);

	sc_rcpts = &sc_ta->scta_rcpts;
#if SC_PIPELINING
	sc_ta->scta_rcpt_p = SC_RCPTS_FIRST(sc_rcpts);
#endif
	for (sc_rcpt = SC_RCPTS_FIRST(sc_rcpts);
	     sc_rcpt != SC_RCPTS_END(sc_rcpts);
	     sc_rcpt = sc_rcpt_nxt)
	{
		sc_rcpt_nxt = SC_RCPTS_NEXT(sc_rcpt);

#if MTA_USE_TLS
		/* default error code? */
		ret = sc_tlsreq(sc_t_ctx, &sc_rcpt->scr_tlsreq_cnf, 403);
		if (sm_is_err(ret) ||
		    (IS_SMTP_REPLY(ret) && SMTP_IS_REPLY_ERROR(ret)))
		{
			/* better/selectable error code? */
			if (!sm_is_err(ret) &&
				SM_IS_FLAG(sc_rcpt->scr_tlsreq_cnf.tlsreqcnf_viol,
						TLSREQ_VIOL_PERM))
				sm_str_scopy(sc_sess->scse_rd,
					"503 5.7.0 smtpc security requirements not met.\r\n");
			else if (!sm_is_err(ret) &&
					SM_IS_FLAG(sc_rcpt->scr_tlsreq_cnf.tlsreqcnf_viol,
						TLSREQ_VIOL_TEMP))
				sm_str_scopy(sc_sess->scse_rd,
					"403 4.7.0 smtpc security requirements not met.\r\n");
			else { /* default behavior */
				sm_str_scopy(sc_sess->scse_rd,
					"421 4.7.0 smtpc security requirements not met.\r\n");
				ret = SMTP_R_SSD;
			}
			sm_str_cpy(sc_sess->scse_str, sc_sess->scse_rd);
			if (DA_ERR_NONE == sc_ta->scta_err_state)
				sc_ta->scta_err_state = DA_TA_ERR_RCPT_I;

			/* fake reply */
			++sc_ta->scta_rcpts_rcvd;
		}
		else {
#endif /* MTA_USE_TLS */

		if (sm_is_err(ret = sm_str_scopy(sc_sess->scse_wr, "RCPT To:")) ||
		    sm_is_err(ret = sm_str_cat(sc_sess->scse_wr, sc_rcpt->scr_pa)) ||
		    sm_is_err(sm_str_scat(sc_sess->scse_wr, "\r\n")))
		{
			if (DA_ERR_NONE == sc_ta->scta_err_state)
				sc_ta->scta_err_state = DA_TA_ERR_RCPT_I;
			goto fail;
		}

		/*
		 *  note: this is a bit early but sc_command() doesn't have
		 *  a simple way to set this flag (a callback seems like overkill).
		 */
		SCR_SET_FLAG(sc_rcpt, SCR_FL_SENT);
		ret = sc_command(sc_t_ctx, SC_PHASE_RCPT);
#if SC_STATS
		++SC_RCPT_COUNT(sc_t_ctx->sct_sc_ctx);
#endif
#if MTA_USE_TLS
		}
#endif /* MTA_USE_TLS */

		/* this might not be valid (PIPELINING) */
		sc_rcpt->scr_st = ret;
		if (ret != SMTP_NO_REPLY) {
			SCR_SET_FLAG(sc_rcpt, SCR_FL_STAT);
			if (!SCR_IS_FLAG(sc_rcpt, SCR_FL_LOGGED)) {
				sm_log_write(sc_t_ctx->sct_sc_ctx->scc_lctx,
					SC_LCAT_CLIENT, SC_LMOD_CLIENT,
					SM_LOG_INFO, 8,
					"sev=INFO, func=sc_one_ta, thread=%u, da_sess=%s, da_ta=%s, ss_ta=%s, rcpt=%@S, stat=%m, reply=%@S",
					thr_id, sc_sess->scse_id, sc_ta->scta_id,
					sc_ta->scta_ssta_id, sc_rcpt->scr_pa, ret,
					sc_sess->scse_str);
				SCR_SET_FLAG(sc_rcpt, SCR_FL_LOGGED);
			}
		}
		if (!SMTPC_R_IS_OK(ret)) {
			sc_rcpt->scr_reply = sm_str_dup(sc_ta->scta_rpool, sc_sess->scse_rd);
			if (DA_ERR_NONE == sc_ta->scta_err_state)
				sc_ta->scta_err_state = DA_TA_ERR_RCPT_S;
			SCTA_SET_STATE(sc_ta, smtp_is_reply_fail(ret)
					? SCTA_R_PERM : SCTA_R_TEMP);
			if (SMTP_R_SSD == ret)
				goto done;
		}
#if SC_STATS
		else if (SMTP_OK == ret)
			++SC_RCPT_CNT_OK(sc_t_ctx->sct_sc_ctx);
#endif
		if (sm_is_err(ret))
			goto fail;
		sc_ta->scta_rcpts_snt++;

		if (SMTP_OK == ret) {
			sc_ta->scta_rcpts_ok++;
#if SC_PIPELINING
			if (sc_ta->scta_rcpts_rcvd == sc_ta->scta_rcpts_snt)
				SCTA_SET_STATE(sc_ta, SCTA_RCPT_S|SCTA_RCPT_R);
			else
				SCTA_SET_STATE(sc_ta, SCTA_RCPT_S);
#else
			SCTA_SET_STATE(sc_ta, SCTA_RCPT_S|SCTA_RCPT_R);
#endif
		}
#if SC_PIPELINING
		else
			SCTA_SET_STATE(sc_ta, SCTA_RCPT_S);
#endif
	}

	/* no valid recipients? */
	if (NO_RCPTS(sc_ta)
#if SC_PIPELINING
	    && !SCSE_IS_CAP(sc_sess, SCSE_CAP_PIPELINING)
#endif
	   )
		goto fail;

	ret = sm_str_scopy(sc_sess->scse_wr, "DATA\r\n");
	if (sm_is_err(ret)) {
		if (DA_ERR_NONE == sc_ta->scta_err_state)
			sc_ta->scta_err_state = DA_TA_ERR_DATA_I;
		goto fail;
	}

	/* this will collect all outstanding results if PIPELINING is active */
	ret = sc_command(sc_t_ctx, SC_PHASE_DATA);
	if (smtp_reply_type(ret) != SMTP_RTYPE_CONT) {
		/*
		**  where=data is confusing for PIPELINING...
		**  state gives an indication what's going, needs to be
		**  translated to text.
		*/

#if SC_PIPELINING
		if (NO_RCPTS(sc_ta))
			goto norcpts;
#endif
		sm_log_write(sc_t_ctx->sct_sc_ctx->scc_lctx,
			SC_LCAT_CLIENT, SC_LMOD_CLIENT,
			SM_LOG_INFO, 8,
			"sev=INFO, func=sc_one_ta, thread=%u, da_sess=%s, da_ta=%s, ss_ta=%s, where=data, ret=%m, rcpts_ok=%d, state=%r, reply=%@S"
			, thr_id, sc_sess->scse_id, sc_ta->scta_id
			, sc_ta->scta_ssta_id, ret
			, sc_ta->scta_rcpts_ok
			, sc_ta->scta_state
			, sc_sess->scse_str);
		if (sc_ta->scta_err_state == DA_ERR_NONE ||
		    (da_ta_err_cmd(sc_ta->scta_err_state) == DA_TA_ERR_RCPT &&
		     sc_ta->scta_rcpts_ok > 0))
			sc_ta->scta_err_state = DA_TA_ERR_DATA_S;
		if (SMTP_R_SSD == ret)
			goto done;
		goto fail;
	}
	SCTA_SET_STATE(sc_ta, SCTA_DATA_S|SCTA_DATA_R);
	if (SCSE_IS_FLAG(sc_sess, SCSE_FL_LMTP|SCSE_FL_RSAD)) {
#if SC_PIPELINING
		/* reset recipient list to collect results */
		sc_ta->scta_rcpt_p = SC_RCPTS_FIRST(sc_rcpts);
#endif
		sc_ta->scta_rcpts_dot = sc_ta->scta_rcpts_ok;
	}

	/* Send data */
	ret = sc_data(sc_t_ctx);
	if (ret != SMTP_OK) {
#if SC_PIPELINING
		if (0 == sc_ta->scta_rcpts_ok)
			goto norcpts;
#endif
		if (sc_ta->scta_err_state == DA_ERR_NONE ||
		    (da_ta_err_cmd(sc_ta->scta_err_state) == DA_TA_ERR_RCPT &&
		     sc_ta->scta_rcpts_ok > 0))
			sc_ta->scta_err_state = DA_TA_ERR_DOT_S; /* really?? */
		if (SMTP_R_SSD == ret)
			goto done;
		goto fail;
	}
	SCTA_SET_STATE(sc_ta, SCTA_DOT_R);
	SCTA_SET_STATE(sc_ta, SCTA_COMPLETE);

#if SC_STATS
	++SC_TOTAL(sc_t_ctx);
#endif
	goto done;

#if SC_PIPELINING
  norcpts:
	if (SCTA_IS_STATE(sc_ta, SCTA_FAIL_TEMP|SCTA_R_TEMP)) {
		ret = sc_ta->scta_status;
		goto done;
	}
#endif /* SC_PIPELINING */

  fail:
	/* clean up? */
	if (sm_is_err(ret)) {
		SCTA_SET_STATE(sc_ta, sm_is_temp_err(ret)
				? SCTA_FAIL_TEMP : SCTA_FAIL_PERM);
	}
	else if (IS_SMTP_REPLY(ret)) {
		SCTA_SET_STATE(sc_ta, smtp_is_reply_temp(ret)
				? SCTA_FAIL_TEMP : SCTA_FAIL_PERM);
	}
	else {
		sm_log_write(sc_t_ctx->sct_sc_ctx->scc_lctx,
			SC_LCAT_CLIENT, SC_LMOD_CLIENT,
			SM_LOG_ERR, 8,
			"sev=ERROR, func=sc_one_ta, thread=%u, da_sess=%s, da_ta=%s, ret=%d, status=unexpected_error_code"
			, thr_id, sc_sess->scse_id, sc_ta->scta_id
			, ret);
	}
  done:
	if (sc_ta->scta_cdb_fp != NULL) {
		(void) cdb_close(sc_t_ctx->sct_sc_ctx->scc_cdb_ctx,
				sc_ta->scta_cdb_fp, SM_IO_CF_NONE);
		sc_ta->scta_cdb_fp = NULL;
	}
	return ret;
}

/*
**  SC_SESS_OPEN -- open SMTPC session
**
**	Parameters:
**		sc_t_ctx -- SMTPC thread context
**
**	Returns:
**		SMTP reply code or error code
**
**	Side Effects:
**		on error sc_sess->scse_str should contain an error string
*/

sm_ret_T
sc_sess_open(sc_t_ctx_P sc_t_ctx)
{
	st_netfd_t rmt_nfd;
	int sock, r, addrlen;
	uint thr_id;
	sm_ret_T ret;
	sc_sess_P sc_sess;
#if MTA_USE_TLS
	sc_ctx_P sc_ctx;
#endif

	SM_IS_SC_T_CTX(sc_t_ctx);
	sc_sess = sc_t_ctx->sct_sess;
	SM_IS_SC_SE(sc_sess);
	thr_id = sc_t_ctx->sct_thr_id;
	ret = SM_SUCCESS;
	sc_sess->scse_fp = NULL;
	rmt_nfd = INVALID_NETFD;
	sm_str_clr(sc_sess->scse_str);
	sm_log_write(sc_t_ctx->sct_sc_ctx->scc_lctx,
		SC_LCAT_CLIENT, SC_LMOD_CLIENT,
		SM_LOG_INFO, 14,
		"sev=INFO, func=sc_sess_open, thread=%u, da_sess=%s, where=open",
		thr_id, sc_sess->scse_id);

#if SC_STATS
	++SC_BUSY(sc_t_ctx);
#endif
#if SC_TEST
	if (SM_IS_FLAG(sc_t_ctx->sct_sc_ctx->scc_cnf.sc_cnf_tests,
	               SCC_CNF_TEST_DUMMY))
	{
			sc_sess->scse_state = SCSE_ST_OPEN;
			return SM_SUCCESS;
	}
#endif

	/* Connect to remote host */
	sock = socket(sc_sess->scse_rmt_addr.sa.sa_family, SOCK_STREAM, 0);
	if (sock < 0) {
		sm_log_write(sc_t_ctx->sct_sc_ctx->scc_lctx,
			SC_LCAT_CLIENT, SC_LMOD_CLIENT,
			SM_LOG_ERR, 8,
			"sev=ERROR, func=sc_sess_open, thread=%u, da_sess=%s, socket()=failed, error=%s"
			, thr_id, sc_sess->scse_id, strerror(errno));
		sc_sess->scse_err_st = DA_SE_ERR_OPEN_I;
		goto done;
	}
	ret = sm_io_open(&SmStThrIO, (void *) &sock, SM_IO_RDWR,
			&sc_sess->scse_fp, NULL);
	if (ret != SM_SUCCESS) {
		sm_log_write(sc_t_ctx->sct_sc_ctx->scc_lctx,
			SC_LCAT_CLIENT, SC_LMOD_CLIENT,
			SM_LOG_ERR, 8,
			"sev=ERROR, func=sc_sess_open, thread=%u, da_sess=%s, sm_io_open()=%m, error=%s"
			, thr_id, sc_sess->scse_id, ret, strerror(errno));
		close(sock);
		sc_sess->scse_err_st = DA_SE_ERR_OPEN_I;
		goto done;
	}
	sm_io_clrblocking(sc_sess->scse_fp);
	rmt_nfd = (st_netfd_t) f_cookie(*(sc_sess->scse_fp));
	r = sc_t_ctx->sct_sc_ctx->scc_cnf.sc_cnf_timeout;
	ret = sm_io_setinfo(sc_sess->scse_fp, SM_IO_WHAT_TIMEOUT, &r);
	if (ret != SM_SUCCESS) {
		sm_log_write(sc_t_ctx->sct_sc_ctx->scc_lctx,
			SC_LCAT_CLIENT, SC_LMOD_CLIENT,
			SM_LOG_ERR, 8,
			"sev=ERROR, func=sc_sess_open, thread=%u, da_sess=%s, set_timeout()=%m, error=%s"
			, thr_id, sc_sess->scse_id, ret, strerror(errno));
		sc_sess->scse_err_st = DA_SE_ERR_OPEN_I;
		SCSE_SET_FLAG(sc_sess, SCSE_FL_IO_ERR);
		goto fail;
	}

	ret = sm_io_setinfo(sc_sess->scse_fp, SM_IO_DOUBLE, NULL);
	if (ret != SM_SUCCESS) {
		r = errno;
		sm_log_write(sc_t_ctx->sct_sc_ctx->scc_lctx,
			SC_LCAT_CLIENT, SC_LMOD_CLIENT,
			SM_LOG_ERR, 8,
			"sev=ERROR, func=sc_sess_open, thread=%u, da_sess=%s, set_double()=%m, error=%s"
			, thr_id, sc_sess->scse_id, ret, strerror(errno));
		sc_sess->scse_err_st = DA_SE_ERR_OPEN_I;
		SCSE_SET_FLAG(sc_sess, SCSE_FL_IO_ERR);
		goto fail;
	}

	/* fixme: put this into a library? */

	switch (sc_sess->scse_rmt_addr.sa.sa_family) {
	  case AF_INET:
		addrlen = sizeof(sc_sess->scse_rmt_addr.sin);
		break;
	  case AF_UNIX:
		addrlen = sizeof(sc_sess->scse_rmt_addr.sunix);
		break;
	  default:
		sm_log_write(sc_t_ctx->sct_sc_ctx->scc_lctx,
			SC_LCAT_CLIENT, SC_LMOD_CLIENT,
			SM_LOG_ERR, 8,
			"sev=ERROR, func=sc_sess_open, thread=%u, da_sess=%s, where=connect, unsupported_family=%d"
			, thr_id, sc_sess->scse_id
			, sc_sess->scse_rmt_addr.sa.sa_family);
		sc_sess->scse_err_st = DA_SE_ERR_OPEN_I;
		SCSE_SET_FLAG(sc_sess, SCSE_FL_IO_ERR);
		goto fail;
	}

	if (st_connect(rmt_nfd, (sockaddr_P) &sc_sess->scse_rmt_addr, addrlen,
		SEC2USEC(sc_t_ctx->sct_sc_ctx->scc_cnf.sc_cnf_timeout)) < 0)
	{
		r = errno;

		/* fixme: always temp?? */
		ret = sm_error_temp(SM_EM_SMTPC, r);
#if 0
		sc_sess->scse_ret = ret;
#endif
		sm_log_write(sc_t_ctx->sct_sc_ctx->scc_lctx,
			SC_LCAT_CLIENT, SC_LMOD_CLIENT,
			SM_LOG_INFO, 8,
			"sev=INFO, func=sc_sess_open, thread=%u, da_sess=%s, where=connect, port=%d, addr=%s, error=%s",
			thr_id, sc_sess->scse_id,
			(sc_sess->scse_rmt_addr.sa.sa_family == AF_INET) ?
				ntohs(sc_sess->scse_rmt_addr.sin.sin_port): -1,
			(sc_sess->scse_rmt_addr.sa.sa_family == AF_INET) ?
				inet_ntoa(sc_sess->scse_rmt_addr.sin.sin_addr) :
				sc_sess->scse_rmt_addr.sunix.sun_path,
			strerror(r));
		sm_str_scat(sc_sess->scse_str, strerror(r));
		sc_sess->scse_err_st = DA_SE_ERR_OPEN_I;
		SCSE_SET_FLAG(sc_sess, SCSE_FL_IO_ERR|SCSE_FL_LOGGED);
		goto fail;
	}
	sc_sess->scse_state = SCSE_ST_CONNECTED;
	sc_sess->scse_rmt_fd = rmt_nfd;

	sm_log_write(sc_t_ctx->sct_sc_ctx->scc_lctx,
		SC_LCAT_CLIENT, SC_LMOD_CLIENT,
		SM_LOG_INFO, 10,
		"sev=INFO, func=sc_sess_open, thread=%u, da_sess=%s, status=connected, port=%d, addr=%s",
		thr_id, sc_sess->scse_id,
		(sc_sess->scse_rmt_addr.sa.sa_family == AF_INET) ?
			ntohs(sc_sess->scse_rmt_addr.sin.sin_port): -1,
		(sc_sess->scse_rmt_addr.sa.sa_family == AF_INET) ?
			inet_ntoa(sc_sess->scse_rmt_addr.sin.sin_addr) :
			sc_sess->scse_rmt_addr.sunix.sun_path);

	ret = sc_rd_reply(sc_t_ctx, SC_PHASE_INIT);
	if (ret != SMTP_OK) {
		if (!SCSE_IS_FLAG(sc_sess, SCSE_FL_LOGGED)) {
			sm_log_write(sc_t_ctx->sct_sc_ctx->scc_lctx,
				SC_LCAT_CLIENT, SC_LMOD_CLIENT,
				SM_LOG_INFO, 9,
				"sev=INFO, func=sc_sess_open, thread=%u, da_sess=%s, where=connection_rd_reply, ret=%m"
				, thr_id, sc_sess->scse_id, ret);
			SCSE_SET_FLAG(sc_sess, SCSE_FL_LOGGED);
		}
		sc_sess->scse_err_st = DA_SE_ERR_GRET_R;
		if (sc_sess->scse_reply != NULL) {
			sm_str_clr(sc_sess->scse_reply);
			sm_str_cat(sc_sess->scse_reply, sc_sess->scse_str);
		}
		else
			sc_sess->scse_reply = sm_str_dup(NULL, sc_sess->scse_str);
		goto fail;
	}

#if 0
	/* HACK turn on LMTP */
	if (sc_t_ctx->sct_sc_ctx->scc_cnf.sc_cnf_debug == 9)
		SCSE_SET_FLAG(sc_sess, SCSE_FL_LMTP);
#endif

  ehlo:
	sc_sess->scse_cap = SCSE_CAP_NONE;
	if (sm_is_err(ret = sm_str_scopy(sc_sess->scse_wr,
				SCSE_IS_FLAG(sc_sess, SCSE_FL_LMTP)
				? "LHLO "
				: SCSE_IS_FLAG(sc_sess, SCSE_FL_EHLO)
				  ? "HELO " : "EHLO ")) ||
	    sm_is_err(ret = sm_str_cat(sc_sess->scse_wr,
				sc_t_ctx->sct_sc_ctx->scc_hostname)) ||
	    sm_is_err(ret = sm_str_scat(sc_sess->scse_wr, "\r\n")))
	{
		sc_sess->scse_err_st = SCSE_IS_FLAG(sc_sess, SCSE_FL_LMTP)
				? DA_SE_ERR_LHLO_I
				: SCSE_IS_FLAG(sc_sess, SCSE_FL_EHLO)
				  ? DA_SE_ERR_HELO_I : DA_SE_ERR_EHLO_I;
		goto fail;
	}
	ret = sc_command(sc_t_ctx, SC_PHASE_EHLO);
	if (ret == sm_error_perm(SM_EM_SMTPC, SM_E_TTMYSELF)) {
		sc_sess->scse_err_st = DA_SE_ERR_TTMYSLEF_S;
		goto fail;
	}
	else if (ret != SMTP_OK) {
		if (!SCSE_IS_FLAG(sc_sess, SCSE_FL_LMTP)
		    && !SCSE_IS_FLAG(sc_sess, SCSE_FL_EHLO)
		    && !SCSE_IS_FLAG(sc_sess, SCSE_FL_IO_ERR))
		{
			SCSE_SET_FLAG(sc_sess, SCSE_FL_EHLO);
			goto ehlo;
		}

		/* distinguish between SMTP reply code and sm/error code! */
		sc_sess->scse_err_st = DA_SET_ERR_RS(
				SCSE_IS_FLAG(sc_sess, SCSE_FL_LMTP)
				? DA_SE_ERR_LHLO
				: SCSE_IS_FLAG(sc_sess, SCSE_FL_EHLO)
				  ? DA_SE_ERR_HELO : DA_SE_ERR_EHLO, ret);
		goto fail;
	}
#if MTA_USE_TLS
	sc_ctx = sc_t_ctx->sct_sc_ctx;
	if (SC_IS_FLAG(sc_ctx, SCC_FL_TLS_OK) &&
	    SCSE_IS_CAP(sc_sess, SCSE_CAP_STARTTLS) &&
	    !SCSE_IS_FLAG(sc_sess, SCSE_FL_STARTTLS))
	{
		ssize_t written;

		sc_sess->scse_con = SSL_new(sc_ctx->scc_ssl_ctx);
		if (NULL == sc_sess->scse_con)
			goto notls;

		ret = sm_str_scopy(sc_sess->scse_wr, "STARTTLS\r\n");
		if (sm_is_err(ret)) {
			SSL_free(sc_sess->scse_con);
			sc_sess->scse_con = NULL;
			goto notls;
		}
		ret = sc_command(sc_t_ctx, SC_PHASE_TLS);
		if (ret != SMTP_OK) {
			SSL_free(sc_sess->scse_con);
			sc_sess->scse_con = NULL;
			goto notls;
		}

		SSL_set_connect_state(sc_sess->scse_con);
		ret = tls_open(sc_sess->scse_fp, sc_sess->scse_con,
				sc_ctx->scc_tlsl_ctx, &sc_sess->scse_fptls);
		if (sm_is_success(ret)) {
			/* HACK */
			sc_sess->scse_fp = sc_sess->scse_fptls;
			ret = do_tls_operation(sc_sess->scse_fp,
					SSL_connect, NULL, NULL, NULL, 0,
					&written);
			if (sm_is_err(ret)) {
				sm_log_write(sc_t_ctx->sct_sc_ctx->scc_lctx,
					SC_LCAT_CLIENT, SC_LMOD_CLIENT,
					SM_LOG_INFO, 8,
					"sev=INFO, func=sc_sess_open, thread=%u, da_sess=%s, where=connection, starttls=%m",
					thr_id, sc_sess->scse_id, ret);
				sc_sess->scse_err_st = DA_SE_ERR_STLS_S;

				/* get error message from OpenSSL? */
				sm_str_scat(sc_sess->scse_str,
					"TLS Handshake failed");

				/* avoid further I/O, TLS handshake failed */
				SCSE_SET_FLAG(sc_sess, SCSE_FL_IO_ERR);
				goto fail;
			}
			r = 1;
			ret = sm_io_setinfo(sc_sess->scse_fp, SM_IO_DOUBLE, &r);
			if (sm_is_err(ret))
				goto fail;
			(void) tls_get_info(sc_ctx->scc_tlsl_ctx,
				sc_sess->scse_con,
				TLS_F_SRV|TLS_F_CERT_REQ,
				(sc_sess->scse_rmt_addr.sa.sa_family == AF_INET)
				? inet_ntoa(sc_sess->scse_rmt_addr.sin.sin_addr)
				: sc_sess->scse_rmt_addr.sunix.sun_path,
				sc_sess->scse_tlsi);
			sm_log_write(sc_t_ctx->sct_sc_ctx->scc_lctx,
				SC_LCAT_CLIENT, SC_LMOD_CLIENT,
				SM_LOG_INFO, 8,
				"sev=INFO, func=sc_sess_open, thread=%u, da_sess=%s, where=connection, starttls=successful, cipher=%S, bits=%d/%d, verify=%s"
				, thr_id, sc_sess->scse_id
				, sc_sess->scse_tlsi->tlsi_cipher
				, sc_sess->scse_tlsi->tlsi_cipher_bits
				, sc_sess->scse_tlsi->tlsi_algs_bits
				, tls_vrfy2txt(sc_sess->scse_tlsi->tlsi_vrfy));

			SCSE_SET_FLAG(sc_sess, SCSE_FL_STARTTLS);
			goto ehlo;
		}
		else {
			/* more cleanup?? */
			SSL_free(sc_sess->scse_con);
			sc_sess->scse_con = NULL;
		}
	}
  notls:
#endif /* MTA_USE_TLS */
	sc_sess->scse_state = SCSE_ST_OPEN;
	return SM_SUCCESS;

  fail:
	sc_sess_close(sc_t_ctx);
	if (!SCSE_IS_FLAG(sc_sess, SCSE_FL_LOGGED)) {
		sm_log_write(sc_t_ctx->sct_sc_ctx->scc_lctx,
			SC_LCAT_CLIENT, SC_LMOD_CLIENT,
			SM_LOG_INFO, 8,
			"sev=INFO, func=sc_sess_open, thread=%u, da_sess=%s, where=connection, status=failed, state=0x%x, ret=%m",
			thr_id, sc_sess->scse_id, sc_sess->scse_err_st, ret);
		SCSE_SET_FLAG(sc_sess, SCSE_FL_LOGGED);
	}
	/* error state? */

  done:
	return ret;
}

/*
**  SC_SESS_CLOSE -- close SMTPC session
**
**	Parameters:
**		sc_t_ctx -- SMTPC thread context
**
**	Returns:
**		SMTP reply code or error code
*/

sm_ret_T
sc_sess_close(sc_t_ctx_P sc_t_ctx)
{
	uint thr_id;
	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;
	SM_IS_SC_SE(sc_sess);
	thr_id = sc_t_ctx->sct_thr_id;
	sc_ctx = sc_t_ctx->sct_sc_ctx;
	SM_IS_SC_CTX(sc_ctx);
	sm_log_write(sc_ctx->scc_lctx,
		SC_LCAT_CLIENT, SC_LMOD_CLIENT,
		SM_LOG_INFO, 14,
		"sev=INFO, func=sc_sess_close, thread=%u, da_sess=%s, where=close, fp=%p",
		thr_id, sc_sess->scse_id, sc_sess->scse_fp);

	SC_RQST_COUNT(sc_ctx)++;
#if SC_STATS
	if (SC_OPEN_SE(sc_ctx) > 0)
		--SC_OPEN_SE(sc_ctx);
#endif
#if SC_TEST
	if (SM_IS_FLAG(sc_t_ctx->sct_sc_ctx->scc_cnf.sc_cnf_tests,
	               SCC_CNF_TEST_DUMMY))
			goto done;
#endif


	/*
	**  Already closed? Check also status?
	**  Don't send QUIT if we had an I/O error in the session...
	**  What about 421?
	*/

	if (sc_sess->scse_fp != NULL &&
	    !SCSE_IS_FLAG(sc_sess, SCSE_FL_IO_ERR) &&
	    sc_sess->scse_state >= SCSE_ST_CONNECTED &&
	    sc_sess->scse_state < SCSE_ST_CLOSED &&
	    sm_is_success(ret = sm_str_scopy(sc_sess->scse_wr, "QUIT\r\n")))
	{
		/* is it really necessary to wait for a reply? */
		if (SCSE_IS_FLAG(sc_sess, SCSE_FL_QUIT_S))
			ret = sc_rd_reply(sc_t_ctx, SC_PHASE_QUIT);
		else
			ret = sc_command(sc_t_ctx, SC_PHASE_QUIT);
		SCSE_SET_FLAG(sc_sess, SCSE_FL_QUIT_R);
		if (ret != SMTP_OK) {
			sm_log_write(sc_t_ctx->sct_sc_ctx->scc_lctx,
				SC_LCAT_CLIENT, SC_LMOD_CLIENT,
				SM_LOG_INFO, 14,
				"sev=INFO, func=sc_sess_close, thread=%u, da_sess=%s, quit=%m",
				thr_id, sc_sess->scse_id, ret);
		}
	}

	if (sc_sess->scse_fp != NULL) {
		sm_io_close(sc_sess->scse_fp, SM_IO_CF_NONE);
		sc_sess->scse_fp = NULL;
	}

#if 0
//	if (SCSE_IS_FLAG(sc_sess, SCSE_FL_TELL_QMGR)) {
//		sc_sess->scse_err_st = 0;
//		ret = sc_c2q(sc_t_ctx, RT_C2Q_SECLSD, 0 /* SESS_TIMEOUT? */, &c2q_ctx);
//		SCSE_CLR_FLAG(sc_sess, SCSE_FL_TELL_QMGR);
//	}
#endif /* 0 */

#if SC_TEST
  done:
#endif
	sc_sess->scse_state = SCSE_ST_CLOSED;
#if SC_STATS
	--SC_BUSY(sc_t_ctx);
#endif
	return SM_SUCCESS;
}


syntax highlighted by Code2HTML, v. 0.9.1