/*
 * 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: smtpsrv.c,v 1.201 2007/11/02 04:54:59 ca Exp $")

#include "sm/assert.h"
#include "sm/error.h"
#include "sm/ctype.h"
#include "sm/memops.h"
#include "sm/io.h"
#include "sm/ioctl.h"
#include "sm/filio.h"
#include "sm/str.h"
#include "sm/string.h"
#include "sm/time.h"
#include "sm/limits.h"
#include "sm/types.h"
#include "sm/net.h"
#include "sm/cdb.h"
#include "statethreads/st.h"
#define MTA_USE_STATETHREADS 1
#include "sm/stsock.h"
#include "sm/util.h"
#include "sm/tls.h"
#include "sm/tlsbio.h"
#include "sm/sasl.h"
#include "sm/rfc2821.h"
#include "sm/misc.h"

#include "s2q.h"
#include "smtps.h"
#include "s2m.h"
#include "smtpsrv.h"
#include "smtpsh.h"
#include "sm/smar.h"
#include "log.h"
#include "sm/resource.h"
#include "sm/version.h"

#if MTA_USE_PMILTER
# include "pmilter.h"
#endif

#define SMTPS_R_IS_OK(ret) ((ret) == SM_SUCCESS)
#define NO_RCPTS(ss_ta) (0 == (ss_ta)->ssta_rcpts_ok)

#define SS_DATA_GOT_IT(ss_sess, ss_ta)	do {					\
		sm_str_scopy(ss_sess->ssse_wr, "250 2.0.0 got it id=");	\
		sm_str_scat(ss_sess->ssse_wr, ss_ta->ssta_id);		\
		sm_str_scat(ss_sess->ssse_wr, "\r\n");			\
	} while (0)

#ifndef MTA_GREETING
# define MTA_GREETING MTA_VERSION_STR
#endif

#define SM_DONT_LOG	99	/* value beyond "reasonable" loglevel */

/*
**  SS_CONN_LOG -- log connection information
**
**	Parameters:
**		ss_sess -- SMTP server session context
**		level -- log level to use
**
**	Returns:
**		SM_SUCCESS
*/

#define SS_CONN_LOG(ss_sess, level, where)	\
	do {									\
		if (!SSSE_IS_FLAG(ss_sess, SSSE_FL_CONN_LOGGED))	\
			ss_conn_log(ss_sess, level, where);			\
	} while (0)

static sm_ret_T
ss_conn_log(ss_sess_P ss_sess, uint level, const char *where)
{
	ss_ctx_P ss_ctx;

	SM_IS_SS_SESS(ss_sess);
	if (SSSE_IS_FLAG(ss_sess, SSSE_FL_CONN_LOGGED))
		return SM_SUCCESS;
	ss_ctx = ss_sess->ssse_sctx;
	SM_IS_SS_CTX(ss_ctx);
#if SS_STATS
	sm_log_write(ss_ctx->ssc_lctx,
		SS_LCAT_SERVER, SS_LMOD_SERVER,
		SM_LOG_INFO, level,
		"sev=INFO, func=%s, ss_sess=%s, client_ipv4=%A, client_name=%C, connect=%ld"
		, where, ss_sess->ssse_id, ss_sess->ssse_client.s_addr
		, ss_sess->ssse_cltname
		, (long) ss_sess->ssse_connect);
#else /* SS_STATS */
	sm_log_write(ss_ctx->ssc_lctx,
		SS_LCAT_SERVER, SS_LMOD_SERVER,
		SM_LOG_INFO, level,
		"sev=INFO, func=%s, ss_sess=%s, client_ipv4=%A, client_name=%C"
		, where, ss_sess->ssse_id, ss_sess->ssse_client.s_addr
		, ss_sess->ssse_cltname);
#endif /* SS_STATS */
	if (sm_log_wouldlog(ss_ctx->ssc_lctx, SS_LCAT_SERVER, SS_LMOD_SERVER,
				level))
		SSSE_SET_FLAG(ss_sess, SSSE_FL_CONN_LOGGED);
	return SM_SUCCESS;
}

/*
**  macros for ss_crt_reply() phase
*/

#define SS_PHASE_OTHER	0	/* no special treatment */
#define SS_PHASE_INIT	1	/* initial 220 greeting */
#if SS_EHLO_ACCESS_CHK
#define SS_PHASE_EHLO	2	/* EHLO response */
#endif
#define SS_PHASE_TLS	3	/* STARTTLS response */
#define SS_PHASE_AUTH	4	/* AUTH response */
#define SS_PHASE_MAIL	5	/* MAIL response */
#define SS_PHASE_RCPT	6	/* RCPT response */
#define SS_PHASE_DATA	7	/* DATA response */
#define SS_PHASE_DOT	8	/* response to final dot */
#define SS_PHASE_QUIT	9	/* QUIT */
#define SS_PHASE_NOREPLY	10		/* no reply requested */


/*
**  SS_CRT_REPLY -- create SMTP reply text based on error code.
**  ToDo: add a "phase" parameter, e.g., RCPT, so REJECT can be changed to
**  550 5.1.1 User unknown
**
**	Parameters:
**		reply -- str for SMTP reply text (output)
**		ret -- error code to transform
**		phase -- SMTP phase
**		new -- clear reply before writing something into it?
**
**	Returns:
**		usual return code
**
**	Note:
**	If there's no matching error text for an SMTP reply code,
**	check the reply type and return a generic error text.
**	This overrides the reply code... hence every function that
**	generates an error should either use a defined value or its own
**	error text.
*/

sm_ret_T
ss_crt_reply(sm_str_P reply, sm_ret_T ret, int phase, bool new)
{
	SM_REQUIRE(reply != NULL);

	/* use existing string if it has an SMTP reply code */
	if (!new && sm_str_getlen(reply) > 3 && IS_SMTP_REPLY_STR(reply, 0))
		return SM_SUCCESS;
	sm_str_clr(reply);
	switch (ret) {
	  case SM_SUCCESS:
		switch (phase) {
		  case SS_PHASE_MAIL:
			return sm_str_scopy(reply, "250 2.1.0 Sender ok\r\n");
		  case SS_PHASE_RCPT:
			return sm_str_scopy(reply, "250 2.1.5 Recipient ok\r\n");
		  default:
			return sm_str_scopy(reply, SS_R_OK);
		}
#if 0
	  case SMTP_R_NULL_SERVER:
		if (ss_sess->ssse_acc.ssa_reply_text == NULL)
			return sm_str_scopy(ss_sess->ssse_wr, "550 5.5.0 No\r\n");
		else
			return sm_str_cpy(ss_sess->ssse_wr, ss_sess->ssse_acc.ssa_reply_text);
#endif /* 0 */
	  case SMTP_R_REJECT:
		switch (phase) {
#if SS_EHLO_ACCESS_CHK
		  case SS_PHASE_EHLO:
			return sm_str_scopy(reply, "550 5.7.1 Not now.\r\n");
#endif
		  case SS_PHASE_MAIL:
			return sm_str_scopy(reply, "550 5.1.8 Sender rejected.\r\n");
		  case SS_PHASE_RCPT:
			return sm_str_scopy(reply, "550 5.1.1 Recipient rejected.\r\n");
		  default:
			return sm_str_scopy(reply, SS_R_REJ);
		}
	  case SMTP_R_SYNTAX:
		switch (phase) {
		  case SS_PHASE_MAIL:
			return sm_str_scopy(reply, "550 5.1.7 Syntax error.\r\n");
		  case SS_PHASE_RCPT:
			return sm_str_scopy(reply, "550 5.1.3 Syntax error.\r\n");
		  default:
			return sm_str_scopy(reply, SS_R_SYN_PAR);
		}
	  case SMTP_R_TEMP:
		return sm_str_scopy(reply, SS_R_TMP);
	  case SMTP_R_SSD:
		return sm_str_scopy(reply, SS_R_SSD);
	}

	switch (smtp_reply_type(ret)) {
	  case SMTP_RTYPE_OK:
		return sm_str_scopy(reply, SS_R_OK);
	  case SMTP_RTYPE_CONT:
			return sm_str_scopy(reply, SS_R_CONT);
	  case SMTP_RTYPE_TEMP:
		return sm_str_scopy(reply, SS_R_TMP);
	  case SMTP_RTYPE_PERM:
		return sm_str_scopy(reply, SS_R_REJ);
	}
	return sm_str_scopy(reply, SS_R_OK);
}

/*
**  SS_REPLY  -- send an SMTP reply
**
**	Parameters:
**		ss_sess -- SMTPS session
**		str -- text to send
**		fp -- file to use
**		loglevel -- use for logging
**		flushit -- invoke flush()?
**
**	Returns:
**		usual return code
*/

/* read/write error */
#define SMTP_RD_ERR	sm_error_temp(SM_EM_SMTPS, SM_E_RD)
#define SMTP_WR_ERR	sm_error_temp(SM_EM_SMTPS, SM_E_WR)

static sm_ret_T
ss_reply(ss_sess_P ss_sess, sm_str_P str, sm_file_T *fp, int loglevel, bool flushit)
{
	ssize_t r;
	sm_ret_T ret;

	if (ss_sess->ssse_sctx->ssc_cnf.ss_cnf_debug > 3) {
		sm_io_fprintf(smioerr, "send: ");
		sm_io_write(smioerr, sm_str_getdata(str), sm_str_getlen(str), &r);
		sm_io_flush(smioerr);
	}

	/* softbounce? replace "5xy 5.a.b" -> "4xy 4.a.b" */
	if (SSC_IS_CFLAG(ss_sess->ssse_sctx, SSC_CFL_SOFTBOUNCE) &&
	    sm_str_getlen(str) > 3 &&
	    sm_str_rd_elem(str, 0) == '5')
	{
		sm_str_wr_elem(str, 0, (uchar) '4');
		if (sm_str_getlen(str) > 10 && sm_str_rd_elem(str, 4) == '5')
			sm_str_wr_elem(str, 4, (uchar) '4');
	}

	ret = sm_io_write(fp, sm_str_getdata(str), sm_str_getlen(str), &r);
	if (r != sm_str_getlen(str)) {
		sm_log_write(ss_sess->ssse_sctx->ssc_lctx,
			SS_LCAT_SERVER, SS_LMOD_SERVER,
			SM_LOG_WARN, (loglevel >= 0) ? loglevel : 5,
			"sev=WARN, func=ss_reply, ss_sess=%s, write=error, n=%d, r=%d, ret=%m"
			, ss_sess->ssse_id, sm_str_getlen(str), (int) r, ret);
		if (sm_is_err(ret))
			return ret;
		return SMTP_WR_ERR;
	}
	if (flushit) {
		ret = sm_io_flush(fp);
		if (sm_is_err(ret)) {
			sm_log_write(ss_sess->ssse_sctx->ssc_lctx,
				SS_LCAT_SERVER, SS_LMOD_SERVER,
				SM_LOG_WARN, (loglevel >= 0) ? loglevel : 5,
				"sev=WARN, func=ss_reply, ss_sess=%s, flush=%m"
				, ss_sess->ssse_id, ret);
			return ret;
		}
	}
	return SMTP_OK;
}

/*
**  SS_READ_CMD -- read an SMTP command (into ssse_rd, \r\n not in ssse_rd)
**
**	Parameters:
**		ss_sess -- SMTPS session
**
**	Returns:
**		usual return code
*/

static sm_ret_T
ss_read_cmd(ss_sess_P ss_sess)
{
	ssize_t r;
	sm_ret_T ret;

	sm_str_clr(ss_sess->ssse_rd);
#if 0
	/* Switch to read mode? Not really necessary */
	sm_iotord(ss_sess->ssse_fp);
#endif
	errno = 0;
	ret = sm_fgetline0(ss_sess->ssse_fp, ss_sess->ssse_rd);

	if (ss_sess->ssse_sctx->ssc_cnf.ss_cnf_debug > 3) {
		sm_io_fprintf(smioerr, "rcvd [len=%d, ret=%r]: "
			, sm_str_getlen(ss_sess->ssse_rd), ret);
		sm_io_write(smioerr, sm_str_getdata(ss_sess->ssse_rd),
			sm_str_getlen(ss_sess->ssse_rd), &r);
		sm_io_flush(smioerr);
	}
	if (SM_IO_EOF == ret)
		return ret;
	if (sm_is_err(ret)) {
		if (ss_sess->ssse_sctx->ssc_cnf.ss_cnf_debug > 1)
			sm_io_fprintf(smioerr,
				"sev=DEBUG, func=ss_read_cmd, read=error, ret=%r\n", ret);
		return ret;
	}
	return SMTP_OK;
}

/*
**  SS_NOPCMD -- Some "nop" cmd was entered: check whether counters are exceeded
**
**	Parameters:
**		ss_sess -- SMTPS session
**		checkonly -- do not increase counters, only check them
**		reply -- default reply
**
**	Returns:
**		usual return code
*/

static sm_ret_T
ss_nopcmd(ss_sess_P ss_sess, bool checkonly, const char *reply)
{
	ss_ta_P ss_ta;
	ss_ctx_P ss_ctx;
	bool toomany;

	SM_IS_SS_SESS(ss_sess);
	ss_ctx = ss_sess->ssse_sctx;
	SM_IS_SS_CTX(ss_ctx);
	ss_ta = ss_sess->ssse_ta;
	SM_IS_SS_TA(ss_ta);
	if (!checkonly)
		++ss_ta->ssta_nopcmds;
	toomany = ss_ta->ssta_nopcmds > ss_ctx->ssc_cnf.ss_cnf_ta_max_nopcmds;
	if (!toomany && !SSTA_IS_ACTIVE(ss_ta)) {
		if (!checkonly)
			++ss_sess->ssse_nopcmds;
		toomany = ss_sess->ssse_nopcmds > ss_ctx->ssc_cnf.ss_cnf_sess_max_nopcmds;
	}
	if (toomany) {
		sm_str_scopy(ss_sess->ssse_wr, "421 4.7.0 Too many useless commands.\r\n");
		ss_sess->ssse_state = SSSE_ST_QUIT;
		return sm_error_temp(SM_EM_SMTPS, EPERM);
	}
	sm_str_scopy(ss_sess->ssse_wr, reply);
	return SM_SUCCESS;
}

/*
**  SS_BADCMD -- Some "bad" cmd was entered: check whether counters are exceeded
**
**	Parameters:
**		ss_sess -- SMTPS session
**		checkonly -- do not increase counters, only check them
**		reply -- default reply
**
**	Returns:
**		usual return code
*/

static sm_ret_T
ss_badcmd(ss_sess_P ss_sess, bool checkonly, const char *reply)
{
	ss_ta_P ss_ta;
	ss_ctx_P ss_ctx;
	bool toomany;

	SM_IS_SS_SESS(ss_sess);
	ss_ctx = ss_sess->ssse_sctx;
	SM_IS_SS_CTX(ss_ctx);
	ss_ta = ss_sess->ssse_ta;
	SM_IS_SS_TA(ss_ta);
	if (SSTA_IS_ACTIVE(ss_ta)) {
		if (!checkonly)
			++ss_ta->ssta_badcmds;
		toomany = ss_ta->ssta_badcmds > ss_ctx->ssc_cnf.ss_cnf_ta_max_badcmds;
	}
	else {
		if (!checkonly)
			++ss_sess->ssse_badcmds;
		toomany = ss_sess->ssse_badcmds > ss_ctx->ssc_cnf.ss_cnf_sess_max_badcmds;
	}
	if (toomany) {
		sm_str_scopy(ss_sess->ssse_wr, "421 4.7.0 Too many bad commands.\r\n");
		ss_sess->ssse_state = SSSE_ST_QUIT;
		return sm_error_temp(SM_EM_SMTPS, EPERM);
	}
	if (reply != NULL)
		sm_str_scopy(ss_sess->ssse_wr, reply);
	return SM_SUCCESS;
}

/*
**  SS_INVALIDADDR -- invalid address: check whether counters are exceeded
**
**	Parameters:
**		ss_sess -- SMTPS session
**
**	Returns:
**		SUCCESS or SSD
*/

static sm_ret_T
ss_invalidaddr(ss_sess_P ss_sess)
{
	ss_ta_P ss_ta;
	ss_ctx_P ss_ctx;
	bool toomany;

	SM_IS_SS_SESS(ss_sess);
	ss_ctx = ss_sess->ssse_sctx;
	SM_IS_SS_CTX(ss_ctx);
	ss_ta = ss_sess->ssse_ta;
	SM_IS_SS_TA(ss_ta);
	toomany = false;
	if (SSTA_IS_ACTIVE(ss_ta)) {
		++ss_ta->ssta_invldaddr;
		toomany = ss_ta->ssta_invldaddr > ss_ctx->ssc_cnf.ss_cnf_ta_max_invldaddr;
	}
	++ss_sess->ssse_invldaddr;
	if (!toomany)
		toomany = ss_sess->ssse_invldaddr > ss_ctx->ssc_cnf.ss_cnf_sess_max_invldaddr;
	if (!toomany)
		return SM_SUCCESS;
	sm_str_scopy(ss_sess->ssse_wr, "421 4.7.0 Too many invalid addresses.\r\n");
	ss_sess->ssse_state = SSSE_ST_QUIT;
	return SMTP_R_SSD;
}

/*
**  SS_RSET -- RSET command
**
**	Parameters:
**		ss_sess -- SMTPS session
**
**	Returns:
**		usual return code
*/

static sm_ret_T
ss_rset(ss_sess_P ss_sess)
{
	sm_ret_T ret;
	ss_ta_P ss_ta;

	SM_IS_SS_SESS(ss_sess);
	ss_ta = ss_sess->ssse_ta;
	if (!SSTA_IS_ACTIVE(ss_ta)) {
		ret = ss_nopcmd(ss_sess, false, SS_R_OK);
		if (sm_is_err(ret))
			return ret;
	}
	ret = ss_ta_abort(ss_sess, ss_ta);
	ss_ta->ssta_state = SSTA_ST_NONE;
	if (sm_is_err(ret))
		return ret;	/* error message alread in ssse_wr */
	return sm_str_scopy(ss_sess->ssse_wr, SS_R_OK);
}

/*
**  SS_EHLO -- EHLO/HELO command
**
**	Parameters:
**		ss_sess -- SMTPS session
**		ehlo -- client sent ehlo?
**
**	Returns:
**		usual return code
*/

static sm_ret_T
ss_ehlo(ss_sess_P ss_sess, bool ehlo)
{
	size_t l, i;
	ss_ta_P ss_ta;
	sm_ret_T ret;
	ulong max_sz_b;
#if SS_EHLO_ACCESS_CHK
	ss_ctx_P ss_ctx;
#endif

	SM_IS_SS_SESS(ss_sess);
	ss_ta = ss_sess->ssse_ta;
	SM_IS_SS_TA(ss_ta);
	ret = ss_ta_abort(ss_sess, ss_ta);
	if (sm_is_err(ret))
		return ret;	/* error message already in ssse_wr */
#if SS_EHLO_ACCESS_CHK
	ss_ctx = ss_sess->ssse_sctx;
#endif

	if (!ehlo)
		SSSE_SET_FLAG(ss_sess, SSSE_FL_HELO);
	else
		SSSE_CLR_FLAG(ss_sess, SSSE_FL_HELO);

	/* need to copy HELO parameter here */
	sm_str_clr(ss_sess->ssse_helo);
	l = sm_str_getlen(ss_sess->ssse_rd);
	if (l < 5 || !ISSPACE(sm_str_rd_elem(ss_sess->ssse_rd, 4)))
		goto syntaxerror;
	i = 5;

	/* RFC 2821: only one space... */
	while (i < l && ISSPACE(sm_str_rd_elem(ss_sess->ssse_rd, i)))
		++i;
	if (i >= l)
		goto syntaxerror;

	/* RFC 2821: parameter must be Domain, see syntax in sm/rfc2821.h */
	while (i < l && sm_str_rd_elem(ss_sess->ssse_rd, i) != '\0' &&
	       !ISSPACE(sm_str_rd_elem(ss_sess->ssse_rd, i)) &&
	       ISPRINT(sm_str_rd_elem(ss_sess->ssse_rd, i)))
	{
		/* further syntax check?  valid char? */
		if (sm_is_err(sm_str_put(ss_sess->ssse_helo,
					sm_str_rd_elem(ss_sess->ssse_rd, i))))
			goto error;
		++i;
	}
	if (i > l || (i < l && sm_str_rd_elem(ss_sess->ssse_rd, i) != '\0'))
		goto syntaxerror;
	if (sm_is_err(sm_str_term(ss_sess->ssse_helo)))
		goto error;
	if (SSSE_IS_CFLAG(ss_sess, SSSE_CFL_EHLO_SYNTAX)) {
		if (!validdomain(ss_sess->ssse_helo, R2821_NO_TRLDOT)) {
			sm_log_write(ss_sess->ssse_sctx->ssc_lctx,
				SS_LCAT_SERVER, SS_LMOD_SERVER, SM_LOG_NOTICE, 10,
				"sev=NOTICE, func=ss_ehlo, ss_sess=%s, %s=%@S, stat=%d, status=Invalid_domain"
				, ss_sess->ssse_id, ehlo ? "ehlo" : "helo", ss_sess->ssse_helo, 501);
			goto syntaxerror;
		}
	}

	if (sm_io_getinfo(ss_sess->ssse_fp, SM_IO_IS_READABLE, NULL)) {
		sm_str_scopy(ss_sess->ssse_wr, "421 Illegal Pipelining detected\r\n");
		return sm_error_temp(SM_EM_SMTPS, SM_E_ILL_PIPE);
	}

#if SS_EHLO_ACCESS_CHK
	if (SSC_IS_CFLAG(ss_ctx, SSC_CFL_ACCESS_DB) &&
		SSSE_IS_CFLAG(ss_sess, SSSE_CFL_CHK_EHLO) &&
	    !SSSE_ARE_FLAGS(ss_sess, SSSE_FL_QUICK|SSSE_FL_CLIENT_RELAY) &&
	    !SSSE_ARE_FLAGS(ss_sess, SSSE_FL_QUICK|SSSE_FL_CLIENT_OK)
	   )
	{
		ret = sm_s2a_str(ss_sess, ss_ctx->ssc_s2a_ctx,
				ss_sess->ssse_id,
				ss_sess->ssse_helo, RT_S2A_EHLO, SMARA_LT_EHLO_ACC,
				SMARA_LFL_STR);
		if (sm_is_err(ret)) {
			sm_log_write(ss_ctx->ssc_lctx,
				SS_LCAT_SERVER, SS_LMOD_SERVER,
				SM_LOG_ERROR, 7,
				"sev=ERROR, func=ss_ehlo, ss_sess=%s, sm_s2a_str=%r"
				, ss_sess->ssse_id, ret);
			goto error;
		}
		else	/* not really required because of goto above */
			ret = sm_w4q2s_reply(ss_sess, ss_ctx->ssc_cnf.ss_cnf_w4a2s,
				ss_ctx->ssc_s2a_ctx);
		if (sm_is_err(ret)) {
			sm_log_write(ss_ctx->ssc_lctx,
				SS_LCAT_SERVER, SS_LMOD_SERVER,
				SM_LOG_ERROR, 6,
				"sev=ERROR, func=ss_ehlo, ss_sess=%s, func=ss_ehlo, where=smar_check, sm_w4q2s_reply=%r"
				, ss_sess->ssse_id, ret);
			goto error;
		}
		else if (SMAR_RISQUICK(ret)) {
			/* SSTA_SET_FLAG(ss_ta, SSTA_FL_EHLO_QCK); */
			SMAR_RCLRQUICK(ret);
		}
		else if (SSTA_IS_FLAG(ss_ta, SSTA_FL_DELAY_CHKS)
			 && IS_SMTP_REPLY(ret) && SMTP_IS_REPLY_ERROR(ret))
		{
			sm_str_P str;

			sm_log_write(ss_ctx->ssc_lctx,
				SS_LCAT_SERVER, SS_LMOD_SERVER,
				SM_LOG_DEBUG, 12,
				"sev=DEBUG, ss_sess=%s, func=ss_ehlo, where=smar_check, sm_w4q2s_reply=%r, reply=%r, text=%@T"
				, ss_sess->ssse_id, ret
				, ss_ta->ssta_ehlo_acc.ssa_reply_code
				, ss_sess->ssse_wr);

			/* delay rejection */
			str = sm_str_dup(NULL, ss_sess->ssse_wr);
			if (str != NULL) {
				ss_ta->ssta_ehlo_acc.ssa_reply_text = str;
				sm_str_clr(ss_sess->ssse_wr);
				ret = SMTP_R_OK;
			}
			else {
				/* can't accept transaction: ENOMEM */
				ret = SMTP_R_SSD;
				(void) ss_crt_reply(ss_sess->ssse_wr, ret, SS_PHASE_EHLO, true);
				sm_log_write(ss_sess->ssse_sctx->ssc_lctx,
					SS_LCAT_SERVER, SS_LMOD_SERVER,
					SM_LOG_ERROR, 4,
					"sev=ERROR, ss_sess=%s, ss_ta=%s, func=ss_ehlo, sm_str_dup=ENOMEM"
					, ss_sess->ssse_id, ss_ta->ssta_id);
			}
		}
		if (IS_SMTP_REPLY(ret) && SMTP_IS_REPLY_ERROR(ret)) {
			int rc;

			sm_log_write(ss_ctx->ssc_lctx,
				SS_LCAT_SERVER, SS_LMOD_SERVER,
				SM_LOG_INFO, 10,
				"sev=INFO, ss_sess=%s, ehlo=%@N, stat=%r, text=%@T"
				, ss_sess->ssse_id, ss_sess->ssse_helo, ret, ss_sess->ssse_wr);
			if (!SMTP_REPLY_MATCHES_RCODE(ss_sess->ssse_wr, ret, 0, rc))
				(void) ss_crt_reply(ss_sess->ssse_wr, ret, SS_PHASE_MAIL, true);
			return SM_SUCCESS;	/* to avoid abort in session loop */
		}
		else {
			sm_log_write(ss_ctx->ssc_lctx,
				SS_LCAT_SERVER, SS_LMOD_SERVER,
				SM_LOG_INFO, 13,
				"sev=INFO, ss_sess=%s, ss_ta=%s, func=ss_ehlo, where=smar_check, sm_w4q2s_reply=%r"
				, ss_sess->ssse_id
				, ss_ta->ssta_id, ret);
			if (SMTP_R_DISCARD == ret) {
				SSTA_SET_FLAG(ss_ta, SSTA_FL_DISCARD);
				ret = SM_SUCCESS;	/* "normalize" */
			}
		}
	}
#endif /* SS_EHLO_ACCESS_CHK */

	/* refuse hostname as EHLO parameter unless localhost */
	if (ss_sess->ssse_client.s_addr != ntohl(INADDR_LOOPBACK) &&
	    !SSSE_ARE_FLAGS(ss_sess, SSSE_FL_QUICK|SSSE_FL_CLIENT_RELAY) &&
	    !SSSE_ARE_FLAGS(ss_sess, SSSE_FL_QUICK|SSSE_FL_CLIENT_OK) &&
	    sm_str_getlen(ss_sess->ssse_helo) ==
	    sm_str_getlen(ss_sess->ssse_sctx->ssc_hostname) &&
		 strncasecmp((char *) sm_str_data(ss_sess->ssse_helo),
			(char *) sm_str_data(ss_sess->ssse_sctx->ssc_hostname),
			sm_str_getlen(ss_sess->ssse_helo)) == 0)
	{
		sm_log_write(ss_sess->ssse_sctx->ssc_lctx,
			SS_LCAT_SERVER, SS_LMOD_SERVER, SM_LOG_NOTICE, 9,
			"sev=NOTICE, func=ss_ehlo, ss_sess=%s, %s=%@S, stat=%d, status=Identity_Theft"
			, ss_sess->ssse_id, ehlo ? "ehlo" : "helo", ss_sess->ssse_helo, 550);
		sm_str_scopy(ss_sess->ssse_wr, "550 Identity Theft!\r\n");
		return SM_SUCCESS;	/* to avoid abort in session loop */
	}

#if MTA_USE_PMILTER
	ret = sspm_helo(ss_sess, ehlo);
	if (sm_is_err(ret))
		goto error;
	if (IS_SMTP_REPLY(ret) && SMTP_IS_REPLY_ERROR(ret))
		return ret;	/* goto some-error?? */
	/* currently no handling of delay_checks here! */
#endif

	if (SSSE_IS_FLAG(ss_sess, SSSE_FL_NULL) || !ehlo) {
		if (sm_is_err(sm_str_scopy(ss_sess->ssse_wr, "250 "))
		    || sm_is_err(sm_str_cat(ss_sess->ssse_wr, ss_sess->ssse_sctx->ssc_hostname))
		    || sm_is_err(sm_str_scat(ss_sess->ssse_wr, " Hi there\r\n")))
			goto error;
	}
	else {
		if (sm_is_err(sm_str_scopy(ss_sess->ssse_wr, "250-"))
		    || sm_is_err(sm_str_cat(ss_sess->ssse_wr, ss_sess->ssse_sctx->ssc_hostname))
		    || sm_is_err(sm_str_scat(ss_sess->ssse_wr, " ESMTP Hi there\r\n250-PIPELINING\r\n")))
			goto error;
		if (SSC_IS_FLAG(ss_sess->ssse_sctx, SSC_FL_TLS_OK) &&
		    SSSE_IS_CFLAG(ss_sess, SSSE_CFL_STARTTLS) &&
		    !SSSE_IS_FLAG(ss_sess, SSSE_FL_STARTTLS) &&
		    sm_is_err(sm_str_scat(ss_sess->ssse_wr, "250-STARTTLS\r\n")))
			goto error;
#if MTA_USE_SASL
		if (SSC_IS_FLAG(ss_sess->ssse_sctx, SSC_FL_SASL_OK) &&
		    SSSE_IS_CFLAG(ss_sess, SSSE_CFL_AUTH) &&
		    !SSSE_IS_FLAG(ss_sess, SSSE_FL_AUTH) &&
		    ss_sess->ssse_sasl_mech_list != NULL
		    && sm_is_err(sm_strprintf(ss_sess->ssse_wr, "250-AUTH %s\r\n", ss_sess->ssse_sasl_mech_list)))
			goto error;
#endif /* MTA_USE_SASL */
		max_sz_b = ss_sess->ssse_sctx->ssc_cnf.ss_cnf_max_msg_sz_kb * ONEKB;
		if (max_sz_b > 0 &&
		    sm_strprintf(ss_sess->ssse_wr, "250-SIZE %lu\r\n", max_sz_b) <= 8)
			goto error;
		if (SSC_IS_CFLAG(ss_sess->ssse_sctx, SSC_CFL_8BITMIME) &&
		    sm_is_err(sm_str_scat(ss_sess->ssse_wr, "250-8BITMIME\r\n")))
			goto error;
		if (sm_is_err(sm_str_scat(ss_sess->ssse_wr, "250-ENHANCEDSTATUSCODES\r\n")))
			goto error;
		if (SSC_IS_CFLAG(ss_sess->ssse_sctx, SSC_CFL_XVERP) &&
		    sm_is_err(sm_str_scat(ss_sess->ssse_wr, "250-XVERP\r\n")))
			goto error;
#if MTA_USE_PMILTER && MTA_USE_RSAD
		if (SSSE_IS_CFLAG(ss_sess, SSSE_CFL_RSAD) &&
		    sm_is_err(sm_str_scat(ss_sess->ssse_wr, "250-PRDR\r\n")))
			goto error;
#endif
		if (sm_is_err(sm_str_scat(ss_sess->ssse_wr, "250 HELP\r\n")))
			goto error;
	}
	ss_sess->ssse_state = ehlo ? SSSE_ST_EHLO : SSSE_ST_HELO;
	return SM_SUCCESS;

  error:
	sm_str_scopy(ss_sess->ssse_wr, SS_R_SSD);
	return sm_error_temp(SM_EM_SMTPS, ENOMEM);

  syntaxerror:
	ret = ss_badcmd(ss_sess, false, "501 Syntax error.\r\n");
	return ret;	/* to avoid abort in session loop */
}

/* check for null server and return proper error code */
/* ss_sess->ssse_acc.ssa_reply_text shouldn't be NULL */
#define SMTPS_NULL_SERVER	\
	do {			\
		if (SSSE_IS_FLAG(ss_sess, SSSE_FL_NULL))	\
			return (ss_sess->ssse_acc.ssa_reply_text == NULL || \
				sm_str_getlen(ss_sess->ssse_acc.ssa_reply_text)\
					<= 4)				\
				? sm_str_scopy(ss_sess->ssse_wr,	\
					"550 5.7.1 Access denied\r\n")	\
				: sm_str_cpy(ss_sess->ssse_wr,	\
					ss_sess->ssse_acc.ssa_reply_text); \
	} while (0)

/* hack: caller should send no reply; FIX THIS: return value semantics! */
#define SS_NO_REPLY	1

#if MTA_USE_TLS
/*
**  SS_TLSREQ -- Check STARTTLS requirements
**
**	Parameters:
**		ss_sess -- SMTPS session
**		rcode -- reply code to use in case of an error
**
**	Returns:
**		usual return code
*/

static sm_ret_T
ss_tlsreq(ss_sess_P ss_sess, sm_ret_T rcode)
{
	sm_ret_T ret;
	uint u;
	const char *cstr;
	sm_str_P str;

	SM_IS_SS_SESS(ss_sess);

	ret = SM_SUCCESS;
	if (SM_IS_FLAG(ss_sess->ssse_tlsreq_cnf.tlsreqcnf_flags, TLSREQ_FL_VRFD) &&
		TLS_VRFY_OK != ss_sess->ssse_tlsi->tlsi_vrfy)
			return rcode;
	u = ss_sess->ssse_tlsreq_cnf.tlsreqcnf_min_cipher_bits;
	if (SM_IS_FLAG(ss_sess->ssse_tlsreq_cnf.tlsreqcnf_flags, TLSREQ_FL_ENCR) &&
		0 == u)
			return rcode;
	if (u > 0 && u > ss_sess->ssse_tlsi->tlsi_cipher_bits)
			return rcode;

	if ((cstr = ss_sess->ssse_tlsreq_cnf.tlsreqcnf_cert_subject) != NULL &&
		((str = ss_sess->ssse_tlsi->tlsi_cert_subject) == NULL ||
		strcasecmp(cstr, sm_str_getdata(str)) != 0))
	{
			sm_log_write(ss_sess->ssse_sctx->ssc_lctx,
				SS_LCAT_SERVER, SS_LMOD_SERVER, SM_LOG_INFO, 9,
				"sev=INFO, func=ss_tlsreq, cert_subject=%#S, required=%s"
				, str, cstr);
			return rcode;
	}
	if ((cstr = ss_sess->ssse_tlsreq_cnf.tlsreqcnf_cert_issuer) != NULL &&
		((str = ss_sess->ssse_tlsi->tlsi_cert_issuer) == NULL ||
		strcasecmp(cstr, sm_str_getdata(str)) != 0))
	{
			sm_log_write(ss_sess->ssse_sctx->ssc_lctx,
				SS_LCAT_SERVER, SS_LMOD_SERVER, SM_LOG_INFO, 9,
				"sev=INFO, func=ss_tlsreq, cert_issuer=%#S, required=%s"
				, str, cstr);
			return rcode;
	}
	if ((cstr = ss_sess->ssse_tlsreq_cnf.tlsreqcnf_common_name) != NULL &&
		((str = ss_sess->ssse_tlsi->tlsi_cn_subject) == NULL ||
		strcasecmp(cstr, sm_str_getdata(str)) != 0))
	{
			sm_log_write(ss_sess->ssse_sctx->ssc_lctx,
				SS_LCAT_SERVER, SS_LMOD_SERVER, SM_LOG_INFO, 9,
				"sev=INFO, func=ss_tlsreq, common_name=%#S, required=%s"
				, str, cstr);
			return rcode;
	}

	return ret;
}

/*
**  SS_TLS -- STARTTLS command
**
**	Parameters:
**		ss_sess -- SMTPS session
**
**	Returns:
**		usual return code
*/

static sm_ret_T
ss_tls(ss_sess_P ss_sess)
{
	sm_ret_T ret;
	ssize_t written;
	int r;
	ss_ta_P ss_ta;
	ss_ctx_P ss_ctx;

	/* check handling of error cases; see comments below */
	SM_IS_SS_SESS(ss_sess);

	SMTPS_NULL_SERVER;
	ss_ta = ss_sess->ssse_ta;
	SM_IS_SS_TA(ss_ta);

	/* is this check ok? should we just reset the transaction? */
	if (ss_ta->ssta_state != SSTA_ST_NONE)
		return sm_str_scopy(ss_sess->ssse_wr, SS_R_NOTNOW);
	ss_ctx = ss_sess->ssse_sctx;
	if (!SSC_IS_FLAG(ss_ctx, SSC_FL_TLS_OK) ||
	    SSSE_IS_FLAG(ss_sess, SSSE_FL_STARTTLS) ||
	    !SSSE_IS_CFLAG(ss_sess, SSSE_CFL_STARTTLS))
		return sm_str_scopy(ss_sess->ssse_wr, SS_R_NOTNOW);

	/*
	**  Set verification globally (per SSL_CTX)
	**  Last parameter: request a client cert -- should be an option
	**  (globally, per client).
	*/

	SSSE_SET_FLAG(ss_sess, SSSE_FL_STARTTLS);
	ss_sess->ssse_con = SSL_new(ss_ctx->ssc_ssl_ctx);
	if (NULL == ss_sess->ssse_con)
		goto notls;
	tls_set_verify(ss_ctx->ssc_ssl_ctx, ss_sess->ssse_con, true);

	ret = sm_str_scopy(ss_sess->ssse_wr, "220 2.0.0 try it\r\n");
	if (sm_is_err(ret))
		goto fail;
	ret = ss_reply(ss_sess, ss_sess->ssse_wr, ss_sess->ssse_fp, -1, false);
	sm_str_clr(ss_sess->ssse_wr);
	if (ret != SMTP_OK) {
		SS_CONN_LOG(ss_sess, 2, "ss_tls");
		sm_log_write(ss_sess->ssse_sctx->ssc_lctx,
			SS_LCAT_SERVER, SS_LMOD_SERVER, SM_LOG_WARN, 2,
			"sev=WARN, func=ss_tls, ss_sess=%s, where=try_starttls, send=%m"
			, ss_sess->ssse_id, ret);
		goto fail;
	}

	/* maybe do this before replying in case this fails?? */
	ret = tls_open(ss_sess->ssse_fp, ss_sess->ssse_con,
		ss_sess->ssse_sctx->ssc_tlsl_ctx, &ss_sess->ssse_fptls);
	if (sm_is_err(ret)) {
		SS_CONN_LOG(ss_sess, 8, "ss_tls");
		sm_log_write(ss_sess->ssse_sctx->ssc_lctx,
			SS_LCAT_SERVER, SS_LMOD_SERVER,
			SM_LOG_WARN, 8,
			"sev=WARN, func=ss_tls, ss_sess=%s, where=connection, tls_open=%m"
			, ss_sess->ssse_id, ret);
		goto fail;
	}

	SSL_set_accept_state(ss_sess->ssse_con);
	ret = do_tls_operation(ss_sess->ssse_fptls, SSL_accept, NULL, NULL,
				NULL, 0, &written);
	if (sm_is_err(ret)) {
		SS_CONN_LOG(ss_sess, 8, "ss_tls");
		sm_log_write(ss_sess->ssse_sctx->ssc_lctx,
			SS_LCAT_SERVER, SS_LMOD_SERVER,
			SM_LOG_WARN, 8,
			"sev=WARN, func=ss_tls, ss_sess=%s, where=connection, starttls=%m"
			, ss_sess->ssse_id, ret);
		goto closetls;
	}
	r = 1;
	ret = sm_io_setinfo(ss_sess->ssse_fptls, SM_IO_DOUBLE, &r);
	if (sm_is_err(ret))
		goto closetls;

	(void) tls_get_info(ss_ctx->ssc_tlsl_ctx, ss_sess->ssse_con,
		TLS_F_SRV|TLS_F_CERT_REQ, inet_ntoa(ss_sess->ssse_client),
		ss_sess->ssse_tlsi);
	SS_CONN_LOG(ss_sess, 9, "ss_tls");
	sm_log_write(ss_sess->ssse_sctx->ssc_lctx,
		SS_LCAT_SERVER, SS_LMOD_SERVER,
		SM_LOG_INFO, 9,
		"sev=INFO, func=ss_tls, ss_sess=%s, where=connection, starttls=successful, cipher=%S, bits=%d/%d, verify=%s"
		, ss_sess->ssse_id
		, ss_sess->ssse_tlsi->tlsi_cipher
		, ss_sess->ssse_tlsi->tlsi_cipher_bits
		, ss_sess->ssse_tlsi->tlsi_algs_bits
		, tls_vrfy2txt(ss_sess->ssse_tlsi->tlsi_vrfy)
		);
	if (TLS_VRFY_OK == ss_sess->ssse_tlsi->tlsi_vrfy &&
	    SSC_IS_CFLAG(ss_ctx, SSC_CFL_TLS_REL_VRFY))
		SSSE_SET_FLAG(ss_sess, SSSE_FL_CLIENT_RELAY);
	else if (TLS_VRFY_OK == ss_sess->ssse_tlsi->tlsi_vrfy &&
	    SSC_IS_CFLAG(ss_ctx, SSC_CFL_ACCESS_DB) &&
	    SSC_IS_CFLAG(ss_ctx, SSC_CFL_TLS_REL_ACC))
	{
		/* check access map... */
		ret = sm_s2a_tls(ss_sess, ss_ctx->ssc_s2a_ctx);
		if (sm_is_err(ret)) {
			SS_CONN_LOG(ss_sess, 7, "ss_tls");
			sm_log_write(ss_ctx->ssc_lctx,
				SS_LCAT_SERVER, SS_LMOD_SERVER,
				SM_LOG_ERROR, 7,
				"sev=ERROR, func=ss_tls, ss_sess=%s, sm_s2a_tls=%m"
				, ss_sess->ssse_id, ret);
			goto closetls;
		}
		else {	/* not really required because of goto above */
			ret = sm_w4q2s_reply(ss_sess, ss_ctx->ssc_cnf.ss_cnf_w4a2s,
				ss_ctx->ssc_s2a_ctx);
		}
		if (sm_is_err(ret)) {
			SS_CONN_LOG(ss_sess, 6, "ss_tls");
			sm_log_write(ss_ctx->ssc_lctx,
				SS_LCAT_SERVER, SS_LMOD_SERVER,
				SM_LOG_ERROR, 6,
				"sev=ERROR, func=ss_tls, ss_sess=%s, sm_w4q2s_reply=%m"
				, ss_sess->ssse_id, ret);
			goto closetls;
		}
/* common code: see smtps.c */
		else if (SMAR_RISQUICK(ret)) {
			SSSE_SET_FLAG(ss_sess, SSSE_FL_QUICK);
			SMAR_RCLRQUICK(ret);
		}
		if (SMTP_R_RELAY == ret) {
			SSSE_SET_FLAG(ss_sess, SSSE_FL_CLIENT_RELAY);
			ret = SM_SUCCESS;	/* "normalize" */
		}
		else if (SMTP_R_DISCARD == ret) {
			SSSE_SET_FLAG(ss_sess, SSSE_FL_DISCARD);
			ret = SM_SUCCESS;	/* "normalize" */
		}
		else if (SMTP_R_OK == ret) {
			SSSE_SET_FLAG(ss_sess, SSSE_FL_CLIENT_OK);
			ret = SM_SUCCESS;	/* "normalize" */
		}
		else if (SMTP_R_CONT == ret) {
			ret = SM_SUCCESS;	/* "normalize" */
		}
/* end common code */

		/* reject/accept TLS??? there is no further reply after the handshake */
		if (IS_SMTP_REPLY(ret) && SMTP_IS_REPLY_ERROR(ret)) {
			SS_CONN_LOG(ss_sess, 12, "ss_tls");
			sm_log_write(ss_ctx->ssc_lctx,
				SS_LCAT_SERVER, SS_LMOD_SERVER,
				SM_LOG_DEBUG, 12,
				"sev=DBG, func=ss_tls, ss_sess=%s, sm_w4q2s_reply=%m, reply=%r, text=%@T"
				, ss_sess->ssse_id, ret
				, ss_sess->ssse_acc.ssa_reply_code
				, ss_sess->ssse_wr);

#if 0
			/* copy error text (if valid) */
			if (sm_str_getlen(ss_sess->ssse_wr) > 4 &&
			    sm_str_cpy(ss_sess->ssse_acc.ssa_reply_text,
					ss_sess->ssse_wr) == SM_SUCCESS)
			{
				/* delay rejection? */
				if (SSSE_IS_CFLAG(ss_sess, SSSE_CFL_DELAY_CHKS)) {
					sm_str_clr(ss_sess->ssse_wr);
					ret = SMTP_R_OK;
				}
			}
			else {
				/*
				**  Can't accept session; complain??
				**  Decrease concurrency?
				*/

				ret = SMTP_R_SSD;
				sm_str_clr(ss_sess->ssse_wr);
				sm_str_clr(ss_sess->ssse_acc.ssa_reply_text);
			}
#endif /* 0 */
		}
	}

	/* HACK */
	ss_sess->ssse_fp = ss_sess->ssse_fptls;
	SSSE_SET_FLAG(ss_sess, SSSE_FL_STARTTLS);
#if MTA_USE_SASL
	if (SSSE_IS_FLAG(ss_sess, SSSE_FL_SASL_OK)) {
		/* r = cipher_bits; see above! */
		ss_sess->ssse_sasl_ext_ssf = ss_sess->ssse_tlsi->tlsi_cipher_bits;
		ret = sasl_setprop(ss_sess->ssse_sasl_conn, SASL_SSF_EXTERNAL, &r);
		SS_CONN_LOG(ss_sess, 9, "ss_tls");
		sm_log_write(ss_sess->ssse_sctx->ssc_lctx,
			SS_LCAT_SERVER, SS_LMOD_SERVER,
			SM_LOG_INFO, 9,
			"sev=INFO, func=ss_tls, ss_sess=%s, set_ssf=%d"
			, ss_sess->ssse_id, ret);
		ss_ctx->ssc_sasl_ctx->sm_sasl_n_mechs = sm_saslmechs(
				ss_ctx->ssc_sasl_ctx, ss_sess->ssse_sasl_conn,
				&ss_sess->ssse_sasl_mech_list);
		if (ss_ctx->ssc_sasl_ctx->sm_sasl_n_mechs <= 0)
			SSSE_CLR_FLAG(ss_sess, SSSE_FL_SASL_OK);
	}
#endif /* MTA_USE_SASL */
	return SS_NO_REPLY;

  notls:
	ret = sm_str_scopy(ss_sess->ssse_wr, "454 4.3.3 Nope\r\n");
	return SM_SUCCESS;

  closetls:
	/* shut down */
	if (ss_sess->ssse_fptls != NULL) {
		sm_io_close(ss_sess->ssse_fptls, SM_IO_CF_NONE);
		ss_sess->ssse_fptls = NULL;
		ss_sess->ssse_fp = NULL;
	}
  fail:
	sm_str_clr(ss_sess->ssse_wr);
	return ret;
}
#endif /* MTA_USE_TLS */

#if MTA_USE_SASL

/*
**  RESET_SASLCONN -- reset SASL connection data
**
**	Parameters:
**		sasl_conn -- SASL connection context
**		hostname -- host name
**		various connection data
**
**	Returns:
**		SASL result
*/

static int
reset_saslconn(sasl_conn_t **sasl_conn, char *hostname, char *remoteip, char *localip, char *auth_id, sasl_ssf_t *ext_ssf)
{
	int ret;

	sasl_dispose(sasl_conn);
	ret = sasl_server_new("smtp", hostname, NULL, NULL, NULL, NULL, 0, sasl_conn);
	if (ret != SASL_OK)
		return ret;

# if NETINET || NETINET6
	if (remoteip != NULL) {
		ret = sasl_setprop(*sasl_conn, SASL_IPREMOTEPORT, remoteip);
		if (ret != SASL_OK)
			return ret;
	}
	if (localip != NULL) {
		ret = sasl_setprop(*sasl_conn, SASL_IPLOCALPORT, localip);
		if (ret != SASL_OK)
			return ret;
	}
# endif /* NETINET || NETINET6 */

	ret = sasl_setprop(*sasl_conn, SASL_SSF_EXTERNAL, ext_ssf);
	if (ret != SASL_OK)
		return ret;
	ret = sasl_setprop(*sasl_conn, SASL_AUTH_EXTERNAL, auth_id);
	if (ret != SASL_OK)
		return ret;

	return SASL_OK;
}

/*
**  SS_AUTH -- AUTH command
**
**	Parameters:
**		ss_sess -- SMTPS session
**
**	Returns:
**		usual return code
*/

#define RESET_SASLCONN	do {						\
	int r;								\
	ss_sess->ssse_sasl_state = SASL_NOT_AUTH;			\
	if ((r = reset_saslconn(&ss_sess->ssse_sasl_conn,		\
		(char *)sm_str_getdata(ss_ctx->ssc_hostname), NULL, NULL, NULL,	\
		(sasl_ssf_t *)&ss_sess->ssse_sasl_ext_ssf)) != SASL_OK)		\
	{								\
		SSSE_CLR_FLAG(ss_sess, SSSE_FL_SASL_OK);		\
		sm_log_write(ss_ctx->ssc_lctx,				\
			SS_LCAT_SERVER, SS_LMOD_SERVER,			\
			SM_LOG_WARN, 9,					\
			"sev=WARN, func=ss_auth, reset_saslconn=%d",	\
			r);						\
	}								\
	} while (0)

static sm_ret_T
ss_auth(ss_sess_P ss_sess)
{
	sm_ret_T ret, args;
	uint i, cltinlen, more;
	uint argv[SS_MAX_ARGS];
	uint challengelen, encodelen;
	ss_ta_P ss_ta;
	ss_ctx_P ss_ctx;
	char *cltin, *str;
	const char *challenge, *mech;

	SM_IS_SS_SESS(ss_sess);
	SMTPS_NULL_SERVER;
	ss_ta = ss_sess->ssse_ta;
	SM_IS_SS_TA(ss_ta);
	if (ss_ta->ssta_state != SSTA_ST_NONE)
		return sm_str_scopy(ss_sess->ssse_wr, SS_R_NOTNOW);
	ss_ctx = ss_sess->ssse_sctx;
	if (!SSC_IS_FLAG(ss_ctx, SSC_FL_SASL_OK) ||
	    !SSSE_IS_CFLAG(ss_sess, SSSE_CFL_AUTH) ||
	    SSSE_IS_FLAG(ss_sess, SSSE_FL_AUTH))
		return sm_str_scopy(ss_sess->ssse_wr, SS_R_NOTNOW);

	/* debugging .... remove/change later on */
	sm_log_write(ss_ctx->ssc_lctx,
		SS_LCAT_SERVER, SS_LMOD_SERVER,
		SM_LOG_INFO, 13,
		"auth=%S, tries=%u",
		ss_sess->ssse_rd, ss_sess->ssse_sasl_tries);

	/* hardcoded limit (CONF) */
	if (++ss_sess->ssse_sasl_tries > 3) {
		(void) ss_crt_reply(ss_sess->ssse_wr, SMTP_R_SSD, SS_PHASE_AUTH, true);
		return sm_error_temp(SM_EM_SMTPS, EPERM); /* error code?? */
	}

	/* 5: "auth " */
	args = sm_str2argv(ss_sess->ssse_rd, 5, sizeof(argv)/sizeof(argv[0]), argv);

	/* debugging .... remove/change later on */
	sm_log_write(ss_ctx->ssc_lctx,
		SS_LCAT_SERVER, SS_LMOD_SERVER,
		SM_LOG_INFO, 13,
		"auth=%S, ret=%#x",ss_sess->ssse_rd, args);

	if (sm_is_err(args) || args < 1)
		return sm_str_scopy(ss_sess->ssse_wr,
			"501 5.5.2 AUTH mechanism must be specified\r\n");

	more = 0;
	cltinlen = 0;
	cltin = NULL;
	ret = sm_str_cpy(ss_sess->ssse_str, ss_sess->ssse_rd);
	if (sm_is_err(ret))
		return sm_str_scopy(ss_sess->ssse_wr, SS_R_TMP);

	/*
	**  Note: mech only points to storage provided by ssse_str!
	**  That is, ssse_str must NOT be changed!
	*/

	mech = (const char *)sm_str_getdata(ss_sess->ssse_str) + argv[0];

	/*
	**  check whether mechanism is available;
	**  would sasl_server_start do that itself?
	*/

#if 0
	if (iteminlist(mech, ss_ctx->ssse_sasl_mech_list, " ") == NULL) {
		return sm_strprintf(ss_sess->ssse_wr,
			"501 5.5.2 AUTH mechanism %.32s not available\r\n",
			mech);
	}
#endif /* 0 */

	/*
	**  check whether argument is available
	**  start server (with/without argument)
	**  while (CONTINUE) {
	**    send response
	**    get more data
	**    feed it into sasl_server_step()
	**  }
	**  if (OK) done
	**  else fail
	*/

	if (args >= 2) {
		cltin = (char *)sm_str_getdata(ss_sess->ssse_str) + argv[1];
		i = strlen(cltin);

		/*
		**  RFC 2554
		**  4. The AUTH command [[...]]
		**  Unlike a zero-length client answer to a 334 reply, a zero-
		**  length initial response is sent as a single equals sign ("=").
		*/

		if ('=' == cltin[0] && '\0' == cltin[1]) {
			cltin = "";
			cltinlen = 0;
		}
		else {
			str = (char *)sm_str_data(ss_sess->ssse_wr);
			ret = sasl_decode64(cltin, i, str,
				sm_str_getsize(ss_sess->ssse_wr), &cltinlen);
			if (ret != SASL_OK) {
				return sm_strprintf(ss_sess->ssse_wr,
					"501 5.5.4 cannot decode '%s' %d\r\n",
					cltin, ret);
			}
			SM_STR_SETLEN(ss_sess->ssse_wr, cltinlen);
			cltin = (char *)sm_str_getdata(ss_sess->ssse_wr);
		}
	}
	else {
		cltin = NULL;
		cltinlen = 0;
	}

	/* see if that auth type exists; challenge is allocated by sasl */
	ret = sasl_server_start(ss_sess->ssse_sasl_conn, mech,
			cltin, cltinlen, &challenge, &challengelen);

	ss_sess->ssse_sasl_state = SASL_PROC_AUTH;
	while (SASL_CONTINUE == ret) {
		if (NULL == challenge) {
			/* ??? something seems to be wrong... */
			ret = SASL_FAIL;
			break;
		}

		/* encode data from challenge/challengelen into ssse_rd */
		sm_str_clr(ss_sess->ssse_rd);
		str = (char *)sm_str_data(ss_sess->ssse_rd);
		ret = sasl_encode64(challenge, challengelen,
			str, sm_str_getsize(ss_sess->ssse_rd),
			&encodelen);

		/*
		**  according to Cyrus SASL doc challenge is free()d by the
		**  library: it's stored in the context
		*/

		if (ret != SASL_OK) {
			sm_log_write(ss_ctx->ssc_lctx,
				SS_LCAT_SERVER, SS_LMOD_SERVER,
				SM_LOG_WARN, 5,
				"sev=WARN, func=ss_auth, ss_sess=%s, sasl_encode=%d"
				, ss_sess->ssse_id, ret);

			/* start over? */
			RESET_SASLCONN;
			return sm_str_scopy(ss_sess->ssse_wr,
				"454 4.5.4 Temporary authentication failure\r\n");
		}
		SM_STR_SETLEN(ss_sess->ssse_rd, encodelen);

		/* create a reply in ssse_wr */
		sm_str_clr(ss_sess->ssse_wr);
		sm_str_scat(ss_sess->ssse_wr, "334 ");
		sm_str_cat(ss_sess->ssse_wr, ss_sess->ssse_rd);
		sm_str_scat(ss_sess->ssse_wr, "\r\n");
		ret = ss_reply(ss_sess, ss_sess->ssse_wr, ss_sess->ssse_fp, -1, false);
		sm_str_clr(ss_sess->ssse_wr);
		if (ret != SMTP_OK) {
			sm_log_write(ss_ctx->ssc_lctx,
				SS_LCAT_SERVER, SS_LMOD_SERVER, SM_LOG_WARN, 2,
				"sev=WARN, func=ss_auth, ss_sess=%s, client_ipv4=%A, where=auth_continue, send=%m"
				, ss_sess->ssse_id
				, ss_sess->ssse_client.s_addr, ret);
			goto fail;
		}
		ret = ss_read_cmd(ss_sess);
		if (sm_is_err(ret)) {
			RESET_SASLCONN;
			return ss_crt_reply(ss_sess->ssse_wr, ret, SS_PHASE_AUTH, false);
			break;
		}

		/* remove "\r\n" */
		sm_str_rm_trail(ss_sess->ssse_rd, "\r\n");
		if (sm_str_getlen(ss_sess->ssse_rd) == 1 &&
		    sm_str_rd_elem(ss_sess->ssse_rd, 0) == '*')
		{
			/* rfc 2254 4. */
			RESET_SASLCONN;
			return sm_str_scopy(ss_sess->ssse_wr, "501 5.7.0 AUTH aborted\r\n");
			break;
		}

		/* decode data from _rd into _wr */
		str = (char *)sm_str_data(ss_sess->ssse_wr);
		ret = sasl_decode64((const char *)sm_str_getdata(ss_sess->ssse_rd),
				sm_str_getlen(ss_sess->ssse_rd), str,
				sm_str_getsize(ss_sess->ssse_wr), &cltinlen);
		if (ret != SASL_OK) {
			/* rfc 2254 4. */
			RESET_SASLCONN;
			return sm_str_scopy(ss_sess->ssse_wr,
				"501 5.5.4 cannot decode AUTH parameter\r\n");
			break;
		}
		SM_STR_SETLEN(ss_sess->ssse_wr, cltinlen);

		/*
		**  use decoded data in _wr for step,
		**  new data in challenge/challengelen
		*/

		str = (char *)sm_str_getdata(ss_sess->ssse_wr);
		ret = sasl_server_step(ss_sess->ssse_sasl_conn,
				str, sm_str_getlen(ss_sess->ssse_wr),
				&challenge, &challengelen);
	}

	if (SASL_OK == ret) {
		bool istrustedmech;

		SSSE_SET_FLAG(ss_sess, SSSE_FL_AUTH);
		istrustedmech = iteminlist(mech, ss_ctx->ssc_cnf.ss_cnf_trusted_mechs,
					" ") != NULL;
		if (istrustedmech)
			SSSE_SET_FLAG(ss_sess, SSSE_FL_CLIENT_RELAY);
		ss_sess->ssse_sasl_state = SASL_IS_AUTH;
		sm_log_write(ss_ctx->ssc_lctx,
			SS_LCAT_SERVER, SS_LMOD_SERVER,
			SM_LOG_INFO, 9,
			"sev=INFO, func=ss_auth, ss_sess=%s, auth=%s, trusted_mech=%s"
			, ss_sess->ssse_id, mech
			, istrustedmech ? "yes" : "no");
# if MTA_USE_PMILTER
		cltinlen = strlen(mech);
		ss_sess->ssse_sasl_mech = sm_str_scpy0(NULL, mech, cltinlen + 2);
/* need to set:
		ss_sess->ssse_sasl_authen
		ss_sess->ssse_sasl_author
*/
# endif /* MTA_USE_PMILTER */
		return sm_str_scopy(ss_sess->ssse_wr, "235 2.0.0 OK Authenticated\r\n");
	}
	else {
		sm_log_write(ss_ctx->ssc_lctx,
			SS_LCAT_SERVER, SS_LMOD_SERVER, SM_LOG_WARN, 2,
			"sev=WARN, func=ss_auth, ss_sess=%s, where=auth, ret=%d, errstring=%s, errdetail=%s"
			, ss_sess->ssse_id, ret
			, sasl_errstring(ret, NULL, NULL)
			, sasl_errdetail(ss_sess->ssse_sasl_conn));
		RESET_SASLCONN;
		return sm_str_scopy(ss_sess->ssse_wr,
			"535 5.7.0 authentication failed\r\n");
	}

	return ret;

  fail:
	sm_str_clr(ss_sess->ssse_wr);
	return ret;
}
#endif /* MTA_USE_SASL */

/*
**  SS_MAIL_ARGS -- Check arguments for MAIL command
**
**	Parameters:
**		ss_sess -- SMTPS session
**		offset -- offset in ssse_rd where arguments begin
**
**	Returns:
**		usual return code
**
**	Side Effects:
**		ss_sess->ssse_wr contains error text in case of an error
*/

static sm_ret_T
ss_mail_args(ss_sess_P ss_sess, size_t offset)
{
	int i, args;
	sm_ret_T ret;
	char *argname, *argvalue;
	uint argnames[SS_MAX_ARGS], argvalues[SS_MAX_ARGS];

	/* ssse_rd is '\0' terminated; make a copy as sm_str2args() "damages" it. */
	ret = sm_str_cpy(ss_sess->ssse_str, ss_sess->ssse_rd);
	if (sm_is_err(ret)) {
		sm_str_scopy(ss_sess->ssse_wr, SS_R_SSD);
		return ret;
	}
	args = sm_str2args(ss_sess->ssse_str, offset,
			sizeof(argnames)/sizeof(argnames[0]), argnames, argvalues);
	if (sm_is_err(args)) {
		sm_str_scopy(ss_sess->ssse_wr, SS_R_SYN_ARG);
		return sm_error_perm(SM_EM_SMTPS, EINVAL);
	}
	for (i = 0; i < args; i++) {
		argname = (char *) sm_str_getdata(ss_sess->ssse_str) + argnames[i];
		if (argvalues[i] > 0)
			argvalue = (char *)sm_str_getdata(ss_sess->ssse_str) + argvalues[i];
		else
			argvalue = NULL;

		if (sm_strcaseeq(argname, "body") &&
		    SSC_IS_CFLAG(ss_sess->ssse_sctx, SSC_CFL_8BITMIME))
		{
			if (!sm_strcaseeq(argvalue, "8bitmime")
			    && !sm_strcaseeq(argvalue, "7bit"))
			{
				sm_str_scopy(ss_sess->ssse_wr,
					"501 5.5.4 unknown body= value\r\n");
				return sm_error_perm(SM_EM_SMTPS, EINVAL);
			}
		}
		else if (sm_strcaseeq(argname, "size")) {
			ulong msg_size, max_msg_sz_kb;
			char *endptr;

			errno = 0;
			msg_size = strtoul(argvalue, &endptr, 10);
			if ((ULONG_MAX == msg_size && errno == ERANGE) ||
			    !ISDIGIT(argvalue[0]))
			{
				sm_str_scopy(ss_sess->ssse_wr,
					"501 5.5.4 invalid size= value\r\n");
				return sm_error_perm(SM_EM_SMTPS, EINVAL);
			}
			max_msg_sz_kb = ss_sess->ssse_sctx->ssc_cnf.ss_cnf_max_msg_sz_kb
					* ONEKB;
			if (max_msg_sz_kb > 0 && msg_size > max_msg_sz_kb) {
				sm_strprintf(ss_sess->ssse_wr,
					"552 5.3.4 Size %lu exceeds maximum value %lu\r\n"
					, msg_size, max_msg_sz_kb);
				return sm_error_perm(SM_EM_SMTPS, EINVAL);
			}
		}
		else if (sm_strcaseeq(argname, "xverp") &&
			 SSC_IS_CFLAG(ss_sess->ssse_sctx, SSC_CFL_XVERP))
		{
			SSTA_SET_FLAG(ss_sess->ssse_ta, SSTA_FL_XVERP);
			/* ignore argvalue for now */
		}
#if MTA_USE_PMILTER && MTA_USE_RSAD
		else if (sm_strcaseeq(argname, "prdr") &&
			 SSC_IS_CFLAG(ss_sess->ssse_sctx, SSC_CFL_RSAD) &&
		     NULL == argvalue)
		{
			SSTA_SET_FLAG(ss_sess->ssse_ta, SSTA_FL_RSAD);
		}
#endif
#if MTA_USE_SASL
		else if (sm_strcaseeq(argname, "auth")) {
			/* just accept it... log? */
			/* needed for pmilter macro? */
		}
#endif /* MTA_USE_SASL */
		else {
			sm_str_scopy(ss_sess->ssse_wr, "501 5.5.0 Unknown argument\r\n");
			return sm_error_perm(SM_EM_SMTPS, EINVAL);
		}
	}
	return SM_SUCCESS;
}

/*
**  SS_MAIL -- MAIL command
**
**	Parameters:
**		ss_sess -- SMTPS session
**
**	Returns:
**		usual return code
*/

static sm_ret_T
ss_mail(ss_sess_P ss_sess)
{
	sm_ret_T ret;
	uint flags, ltype, argoffset;
	int log;
	ss_ta_P ss_ta;
	ss_mail_P mail;
	ss_ctx_P ss_ctx;
#define MAILLEN	10	/* length of "MAIL FROM:" */

	SM_IS_SS_SESS(ss_sess);
	SMTPS_NULL_SERVER;
	ss_ta = ss_sess->ssse_ta;
	SM_IS_SS_TA(ss_ta);
	ss_ctx = ss_sess->ssse_sctx;
	mail = NULL;
	log = SM_DONT_LOG;
	argoffset = 0;
	sm_str_clr(ss_sess->ssse_wr);

	if (SSSE_IS_CFLAG(ss_sess, SSSE_CFL_EHLO_REQ) &&
	    !(SSSE_ST_EHLO == ss_sess->ssse_state ||
	      SSSE_ST_HELO == ss_sess->ssse_state))
	{
		log = 14;
		sm_str_scopy(ss_sess->ssse_wr, SS_R_EHLO_REQ);
		goto cleanup;
	}
	if (SSTA_ST_NONE == ss_ta->ssta_state) {
		ret = ss_ta_init(ss_sess, ss_ta);
		sm_log_write(ss_ctx->ssc_lctx,
			SS_LCAT_SERVER, SS_LMOD_SERVER,
			SM_LOG_INFO, 13,
			"sev=INFO, func=ss_mail, ss_sess=%s, ss_ta=%s, ss_ta_init=%m"
			, ss_sess->ssse_id, ss_ta->ssta_id, ret);
		if (sm_is_err(ret))
			goto ssd;
	}
	else {
		sm_log_write(ss_ctx->ssc_lctx,
			SS_LCAT_SERVER, SS_LMOD_SERVER,
			SM_LOG_DEBUG, 15,
			"sev=DBG, func=ss_mail, ss_sess=%s, tss_a=%s, status=ta_reuse"
			, ss_sess->ssse_id, ss_ta->ssta_id);
	}
	if (ss_ta->ssta_state != SSTA_ST_INIT) {
		sm_str_scopy(ss_sess->ssse_wr, SS_R_SEQ);
		goto cleanup;
	}
	if (strncasecmp((char *) sm_str_getdata(ss_sess->ssse_rd), "mail from:",
			MAILLEN) != 0)
	{
		sm_str_scopy(ss_sess->ssse_wr, SS_R_SYNE);
		goto cleanup;
	}

	if (ss_sess->ssse_ta_tot >= ss_ctx->ssc_cnf.ss_cnf_t_tot_lim) {
		log = 10;
		sm_str_scopy(ss_sess->ssse_wr, "452 4.3.0 Too many transactions.\r\n");
		goto cleanup;
	}
	++ss_sess->ssse_ta_tot;

#if MTA_USE_TLS
	if (ss_sess->ssse_cnf != NULL) {
		ret = ss_tlsreq(ss_sess, SMTP_R_SSD);
		if (sm_is_err(ret))
			goto ssd;
		if (IS_SMTP_REPLY(ret) && SMTP_IS_REPLY_ERROR(ret)) {
			log = 8;

			/* better/selectable error code? */
			if (SM_IS_FLAG(ss_sess->ssse_tlsreq_cnf.tlsreqcnf_viol,
						TLSREQ_VIOL_PERM))
				sm_str_scopy(ss_sess->ssse_wr,
					"503 5.7.0 security requirements not met.\r\n");
			else if (SM_IS_FLAG(ss_sess->ssse_tlsreq_cnf.tlsreqcnf_viol,
						TLSREQ_VIOL_TEMP))
				sm_str_scopy(ss_sess->ssse_wr,
					"403 4.7.0 security requirements not met.\r\n");
			else { /* default behavior */
				sm_str_scopy(ss_sess->ssse_wr,
					"421 4.7.0 security requirements not met.\r\n");
				goto ssd;
			}
			goto cleanup;
		}
	}
#endif

	ret = ssm_mail_new(ss_ta, true, &mail);
	if (sm_is_err(ret))
		goto ssd;

	ret = t2821_scan((sm_rdstr_P) ss_sess->ssse_rd, &mail->ssm_a2821, MAILLEN);
	if (sm_is_err(ret)) {
		log = 14;
		sm_str_scopy(ss_sess->ssse_wr, SS_R_SYN_S_A);
		goto cleanup;
	}
	if ((uint)ret < sm_str_getlen(ss_sess->ssse_rd) &&
	    !ISSPACE(sm_str_rd_elem(ss_sess->ssse_rd, ret)))
	{
		log = 18;
		sm_str_scopy(ss_sess->ssse_wr, SS_R_SYN_PAR);
		goto cleanup;
	}

	if (ret > MAILLEN && (uint)ret < sm_str_getlen(ss_sess->ssse_rd)) {
		argoffset = ret;
		ret = ss_mail_args(ss_sess, ret);
		if (sm_is_err(ret)) {
			log = 12;
			goto cleanup;
		}
	}

	flags = R2821_CORRECT|R2821_EMPTY;
	ret = t2821_parse(&mail->ssm_a2821, flags);
	if (sm_is_err(ret)) {
		log = 13;
		sm_str_scopy(ss_sess->ssse_wr, SS_R_SYN_S_A);
		goto cleanup;
	}

	sm_str_clr(ss_sess->ssse_str);
	ret = t2821_str(&mail->ssm_a2821, ss_sess->ssse_str, 0);
	if (sm_is_err(ret))
		goto ssd;
	sm_str_clr(ss_sess->ssse_wr);	/* clear reply string */

	if (sm_str_getlen(ss_sess->ssse_str) == 2
	    && sm_memeq(sm_str_data(ss_sess->ssse_str), "<>", 2))
		SSTA_SET_FLAG(ss_ta, SSTA_FL_DSN);
	sm_str_clr(mail->ssm_pa);
	ret = sm_str_cpy(mail->ssm_pa, ss_sess->ssse_str);
	if (sm_is_err(ret))
		goto ssd;

	/*
	**  Need to perform checks if
	**  sender is not "<>"
	**  and neither QUICK:RELAY nor QUICK:OK for session are set
	*/

	if (!SSTA_IS_FLAG(ss_ta, SSTA_FL_DSN) &&
	    !SSSE_ARE_FLAGS(ss_sess, SSSE_FL_QUICK|SSSE_FL_CLIENT_RELAY) &&
	    !SSSE_ARE_FLAGS(ss_sess, SSSE_FL_QUICK|SSSE_FL_CLIENT_OK)
	   )
	{
		ltype = SMARA_LT_MAIL_ROUTE|SMARA_LT_MAIL_LOCAL;
		flags = SMARA_LFL_2821;
		if (SSC_IS_CFLAG(ss_ctx, SSC_CFL_ACCESS_DB)) {
			ltype |= SMARA_LT_MAIL_ACC;
			flags |= SMARA_LFL_MXACC;
			if (SSC_IS_CFLAG(ss_ctx, SSC_CFL_ACCIMPLDET))
				flags |= SMARA_LFL_ACCIMPLDET;
		}

		ret = sm_s2a_addr(ss_sess, ss_ctx->ssc_s2a_ctx,
				ss_ta->ssta_id, ss_sess->ssse_str, RT_S2A_MAIL, ltype, flags);
		if (sm_is_err(ret)) {
			sm_log_write(ss_ctx->ssc_lctx,
				SS_LCAT_SERVER, SS_LMOD_SERVER,
				SM_LOG_ERROR, 7,
				"sev=ERROR, func=ss_mail, ss_sess=%s, ss_ta=%s, sm_s2a_addr=%m"
				, ss_sess->ssse_id, ss_ta->ssta_id, ret);
			goto ssd;
		}
		else	/* not really required because of goto above */
			ret = sm_w4q2s_reply(ss_sess, ss_ctx->ssc_cnf.ss_cnf_w4a2s,
				ss_ctx->ssc_s2a_ctx);
		if (SMAR_R_VAL(ret) == SMTP_R_CONT)
			ret = SMAR_R_FLAGS(ret)|SM_SUCCESS; /* "normalize" */
		if (sm_is_err(ret)) {
			sm_log_write(ss_ctx->ssc_lctx,
				SS_LCAT_SERVER, SS_LMOD_SERVER,
				SM_LOG_ERROR, 6,
				"sev=ERROR, func=ss_mail, ss_sess=%s, ss_ta=%s, where=smar_check, sm_w4q2s_reply=%m"
				, ss_sess->ssse_id, ss_ta->ssta_id, ret);
			goto ssd;
		}
		else if (SMAR_RISQUICK(ret)) {
			/* SSTA_SET_FLAG(ss_ta, SSTA_FL_MAIL_QCK); */
			SMAR_RCLRQUICK(ret);
		}
		else if (SSTA_IS_FLAG(ss_ta, SSTA_FL_DELAY_CHKS)
			 && IS_SMTP_REPLY(ret) && SMTP_IS_REPLY_ERROR(ret))
		{
			sm_str_P str;

			sm_log_write(ss_ctx->ssc_lctx,
				SS_LCAT_SERVER, SS_LMOD_SERVER,
				SM_LOG_DEBUG, 12,
				"sev=DBG, func=ss_mail, ss_sess=%s, ss_ta=%s, where=smar_check, sm_w4q2s_reply=%m, reply=%r, text=%@T"
				, ss_sess->ssse_id, ss_ta->ssta_id, ret
				, ss_ta->ssta_mail_acc.ssa_reply_code
				, ss_sess->ssse_wr);

			/* delay rejection */
			str = sm_str_dup(NULL, ss_sess->ssse_wr);
			if (str != NULL) {
				ss_ta->ssta_mail_acc.ssa_reply_text = str;
				sm_str_clr(ss_sess->ssse_wr);
				ret = SMTP_R_OK;
			}
			else {
				/* can't accept transaction: ENOMEM */
				ret = SMTP_R_SSD;
				(void) ss_crt_reply(ss_sess->ssse_wr, ret, SS_PHASE_MAIL, true);
				sm_log_write(ss_ctx->ssc_lctx,
					SS_LCAT_SERVER, SS_LMOD_SERVER,
					SM_LOG_ERROR, 4,
					"sev=ERROR, func=ss_mail, ss_sess=%s, ss_ta=%s, sm_str_dup=ENOMEM"
					, ss_sess->ssse_id, ss_ta->ssta_id);
			}
		}
		if (IS_SMTP_REPLY(ret) && SMTP_IS_REPLY_ERROR(ret)) {
			int rc;

			sm_log_write(ss_ctx->ssc_lctx,
				SS_LCAT_SERVER, SS_LMOD_SERVER,
				SM_LOG_INFO, 10,
				"sev=INFO, func=ss_mail, ss_sess=%s, ss_ta=%s, mail=%@N, stat=%d, text=%@T"
				, ss_sess->ssse_id, ss_ta->ssta_id
				, ss_sess->ssse_str
				, ret, ss_sess->ssse_wr);
			if (!SMTP_REPLY_MATCHES_RCODE(ss_sess->ssse_wr, ret, 0, rc))
				(void) ss_crt_reply(ss_sess->ssse_wr, ret, SS_PHASE_MAIL, true);
			goto error;
		}
		else {
			sm_log_write(ss_ctx->ssc_lctx,
				SS_LCAT_SERVER, SS_LMOD_SERVER,
				SM_LOG_INFO, 13,
				"sev=INFO, func=ss_mail, ss_sess=%s, ss_ta=%s,  where=smar_check, sm_w4q2s_reply=%m"
				, ss_sess->ssse_id
				, ss_ta->ssta_id, ret);
			if (SMTP_R_DISCARD == ret) {
				SSTA_SET_FLAG(ss_ta, SSTA_FL_DISCARD);
				ret = SM_SUCCESS;	/* "normalize" */
			}
		}
	}

#if MTA_USE_PMILTER
	ret = sspm_mail(ss_sess, ret, argoffset);
	if (sm_is_err(ret))
		goto error;
	if (IS_SMTP_REPLY(ret) && SMTP_IS_REPLY_ERROR(ret))
		goto error;
#endif

	ss_ta->ssta_state = SSTA_ST_MAIL;
	ss_ta->ssta_mail = mail;
	SS_CONN_LOG(ss_sess, 2, "ss_mail");
	sm_log_write(ss_ctx->ssc_lctx,
		SS_LCAT_SERVER, SS_LMOD_SERVER, SM_LOG_INFO, 2,
		"sev=INFO, ss_sess=%s, ss_ta=%s, mail=%@N, stat=%d"
		, ss_sess->ssse_id, ss_ta->ssta_id, ss_sess->ssse_str
		, ret | (SSTA_IS_FLAG(ss_ta, SSTA_FL_DISCARD) ? SMTP_R_DISCARD : 0));
	return ss_crt_reply(ss_sess->ssse_wr, ret, SS_PHASE_MAIL, false);

  cleanup:
	ret = SM_SUCCESS;
  ssd:	/* only called for sm_is_err(); hence fallthrough from cleanup is ok */
	if (sm_is_err(ret)) {
		sm_str_scopy(ss_sess->ssse_wr, SS_R_SSD);

		/* fail on which errors? */
		if (SM_E_FULL == sm_error_value(ret)
		    || SM_E_CONN_CLSD == sm_error_value(ret)
			|| S2s_IS_IOERR(ss_ctx))
		{
			Max_cur_threads = 0;	/* force shutdown */
			SSC_SET_FLAG(ss_ctx, SSC_FL_TERMINATING|SSC_FL_SHUTDOWN);
			if (0 == ss_ctx->ssc_shutdown)
				ss_ctx->ssc_shutdown = st_time();

#if 0
			/*
			 * ask for restart of server process?
			 * should not be really necessary as mcp should do that anyway
			 */
			if (SM_E_CONN_CLSD == sm_error_value(ret))
				SSC_SET_FLAG(ss_ctx, SSC_FL_RESTARTDEP);
#endif
			sm_log_write(ss_ctx->ssc_lctx,
				SS_LCAT_SERVER, SS_LMOD_SERVER,
				SM_LOG_ERROR, 1,
				"sev=ERROR, func=ss_mail, pid=%d, status=forcing_shutdown"
				, (int) My_pid);
		}
	}
  error:
	if (log < SM_DONT_LOG && sm_str_getlen(ss_sess->ssse_wr) > 0) {
		sm_log_write(ss_ctx->ssc_lctx,
			SS_LCAT_SERVER, SS_LMOD_SERVER,
			SM_LOG_INFO, log,
			"sev=INFO, func=ss_mail, ss_sess=%s, ss_ta=%s, ret=%m, status=%@T, input=%@T"
			, ss_sess->ssse_id, ss_ta->ssta_id, ret
			, ss_sess->ssse_wr, ss_sess->ssse_rd);
	}
	ssm_mail_free(ss_ta, mail);
	return ret;
}

/* macro for ss_rcpt */
#define SS_RELAYING_DENIED(rcpt_str) do {			\
		sm_log_write(ss_ctx->ssc_lctx,				\
			SS_LCAT_SERVER, SS_LMOD_SERVER,			\
			SM_LOG_NOTICE, 3,					\
			"sev=NOTICE, ss_sess=%s, ss_ta=%s, rcpt=%@N, stat=%d, status=%s Relaying denied"	\
			, ss_sess->ssse_id, ss_ta->ssta_id, rcpt_str	\
			, SSSE_IS_FLAG(ss_sess, SSSE_FL_CLT_RLY_TMP)	\
				? 450 : 550				\
			, SSSE_IS_FLAG(ss_sess, SSSE_FL_CLT_RLY_TMP)	\
				? "450 4.4.0" : "550 5.7.1");		\
		if (SSSE_IS_FLAG(ss_sess, SSSE_FL_CLT_RLY_TMP))		\
			sm_str_scopy(ss_sess->ssse_wr,			\
				"450 4.4.0 Relaying temporarily denied.\r\n"); \
		else							\
			sm_str_scopy(ss_sess->ssse_wr,			\
				"550 5.7.1 Relaying denied.\r\n");	\
	} while (0)

/*
**  SS_RCPT -- RCPT command
**
**	Parameters:
**		ss_sess -- SMTPS session
**
**	Returns:
**		usual return code
*/

static sm_ret_T
ss_rcpt(ss_sess_P ss_sess)
{
	uint flags, ltype;
	uint fct_flags;
	int log;
	sm_ret_T ret, res;
	ss_ta_P ss_ta;
	ss_rcpt_P ss_rcpt;
	ss_ctx_P ss_ctx;
#define RCPTLEN	8	/* length of "RCPT TO:" */

#define SSRF_FL_RELAY	0x0001	/* relaying allowed */
#define SSRF_FL_OK2CPM	0x0002	/* ok to call pmilter */
#define SSRF_FL_PMC	0x0004	/* pmilter has been called */
#define SSRF_FL_BAD	0x0008	/* treat as "bad command" */

	SM_IS_SS_SESS(ss_sess);
	ss_ta = ss_sess->ssse_ta;
	SM_IS_SS_TA(ss_ta);
	ss_rcpt = NULL;
	fct_flags = SSSE_IS_FLAG(ss_sess, SSSE_FL_CLIENT_RELAY) ?
			SSRF_FL_RELAY : 0;
	ss_ctx = ss_sess->ssse_sctx;
	ret = SM_SUCCESS;
	log = SM_DONT_LOG;

	sm_str_clr(ss_sess->ssse_wr);
	sm_str_clr(ss_sess->ssse_str);
	if (ss_ta->ssta_state != SSTA_ST_MAIL && ss_ta->ssta_state != SSTA_ST_RCPT)
	{
		log = 20;
		sm_str_scopy(ss_sess->ssse_wr, SS_R_SEQ);
		goto cleanup;
	}
	if (strncasecmp((char *) sm_str_getdata(ss_sess->ssse_rd), "rcpt to:",
			RCPTLEN) != 0)
	{
		log = 14;
		sm_str_scopy(ss_sess->ssse_wr, SS_R_SYNE);
		SM_SET_FLAG(fct_flags, SSRF_FL_BAD);
		goto cleanup;
	}

	if (ss_sess->ssse_rcpts_tot >= ss_ctx->ssc_cnf.ss_cnf_r_tot_lim
	    || ss_ta->ssta_rcpts_tot >= ss_ctx->ssc_cnf.ss_cnf_r_ta_lim
	    || ss_ta->ssta_rcpts_tot >= ss_ctx->ssc_cnf.ss_cnf_r_tot_lim
	   )
	{
		log = 14;
		sm_str_scopy(ss_sess->ssse_wr, "452 4.5.3 Too many recipients.\r\n");
		SM_SET_FLAG(fct_flags, SSRF_FL_BAD);
		goto cleanup;
	}
	if (ss_ta->ssta_rcpts_len >= QSS_RC_MAXSZ / 2) {
		log = 14;
		sm_str_scopy(ss_sess->ssse_wr, "452 4.5.3 Too many recipients.\r\n");
		SM_SET_FLAG(fct_flags, SSRF_FL_BAD);
		goto cleanup;
	}

	ret = ssr_rcpts_new(ss_ta, &ss_ta->ssta_rcpts, &ss_rcpt);
	if (sm_is_err(ret)) {
		log = 12;
		sm_str_scopy(ss_sess->ssse_wr, SS_R_SSD);
		goto error;
	}
	ss_sess->ssse_rcpts_tot++;

	/*
	**  HACK: convert "<postmaster>" to "<postmaster@HOST.NAME>"
	**  Notes:
	**  1. This checks the ENTIRE string! It will NOT work if arguments
	**  exist! (doesn't matter right now as there are none allowed).
	**  2. it unconditionally use ss_sess->ssse_sctx->ssc_hostname;
	**  maybe this should be configurable.
	**  3. Many other functions in smX "expect" an address of the
	**  form "<localpart@domain.part>", so this is the easiest way
	**  to satisfy that expectation.
	*/

	(void) sm_postmaster_add_domain(ss_sess->ssse_rd,
			ss_sess->ssse_sctx->ssc_hostname, RCPTLEN);

	ret = t2821_scan((sm_rdstr_P) ss_sess->ssse_rd, &ss_rcpt->ssr_a2821,
			RCPTLEN);
	if (sm_is_err(ret)) {
		log = 12;
		sm_str_scopy(ss_sess->ssse_wr, SS_R_SYN_R_A);
		SM_SET_FLAG(fct_flags, SSRF_FL_BAD);
		goto cleanup;
	}
	if ((uint)ret < sm_str_getlen(ss_sess->ssse_rd) &&
	    !ISSPACE(sm_str_rd_elem(ss_sess->ssse_rd, ret)))
	{
		log = 12;
		sm_str_scopy(ss_sess->ssse_wr, SS_R_SYNE);
		SM_SET_FLAG(fct_flags, SSRF_FL_BAD);
		goto cleanup;
	}

	/* check args: none for now, fixme: misleading error if no address */
	if ((uint)ret < sm_str_getlen(ss_sess->ssse_rd)) {
		log = 14;
		sm_str_scopy(ss_sess->ssse_wr, SS_R_SYNE);
			/* "555 5.5.5 Parameter unsupported.\r\n") */
		SM_SET_FLAG(fct_flags, SSRF_FL_BAD);
		goto cleanup;
	}

	/*
	 * keep track of total length of all rcpts to avoid overflowing RCB
	 * (entire string: it's too much but that doesn't matter much as we just
	 * try to establish some upper bound).
	 */
	ss_ta->ssta_rcpts_len += sm_str_getlen(ss_sess->ssse_rd);

	/* further checks should be somewhere else, configurable */
	ret = t2821_parse(&ss_rcpt->ssr_a2821, R2821_CORRECT);
	if (sm_is_err(ret)) {
		log = 12;
		sm_str_scopy(ss_sess->ssse_wr, SS_R_SYN_R_A);
		SM_SET_FLAG(fct_flags, SSRF_FL_BAD);
		goto cleanup;
	}
	SM_SET_FLAG(fct_flags, SSRF_FL_OK2CPM);

	/* Simple anti-relay test... */
	if (!SM_IS_FLAG(fct_flags, SSRF_FL_RELAY)) {
		int r;

		ret = t2821_str(&ss_rcpt->ssr_a2821, ss_sess->ssse_wr, 0);
		if (sm_is_err(ret)) {
			log = 14;
			sm_str_scopy(ss_sess->ssse_wr, SS_R_SYN_R_A);
			goto cleanup;
		}
		r = regexec(&ss_ctx->ssc_relayto,
			(const char *) sm_str_getdata(ss_sess->ssse_wr), 0, NULL, 0);
		if (0 == r)
			SM_SET_FLAG(fct_flags, SSRF_FL_RELAY);
		if (r != 0 && !SSC_IS_CFLAG(ss_ctx, SSC_CFL_ACCESS_DB)
		    && SSC_IS_CFLAG(ss_ctx, SSC_CFL_LMTPNOTRELAY))
		{
			SS_RELAYING_DENIED(ss_sess->ssse_wr);
			SM_SET_FLAG(fct_flags, SSRF_FL_BAD);
			goto cleanup;
		}
		sm_str_clr(ss_sess->ssse_wr);
	}

	/*
	**  Check whether RCPT is acceptable ... Where/Who should do it?
	**  For example: local domain: check user list.
	**  Should SMTPS do this or SMAR?
	**  There is a comment in qmgr (sm_q_rcptid()) to do it there;
	**  however, it might be simpler to call an "anti-spam" SMAR
	**  routine from here than going via QMGR.
	*/

	/* send the recipient information to SMAR then QMGR */
	sm_str_clr(ss_sess->ssse_str);
	ret = t2821_str(&ss_rcpt->ssr_a2821, ss_sess->ssse_str, 0);
	if (sm_is_err(ret)) {
		log = 12;
		sm_str_scopy(ss_sess->ssse_wr, SS_R_SSD);
		goto error;
	}

	ltype = SMARA_LT_RCPT_LOCAL;
	flags = SMARA_LFL_2821;
	if (!SSC_IS_CFLAG(ss_ctx, SSC_CFL_LMTPNOTRELAY))
		ltype |= SMARA_LT_RCPT_LCL_R;
	if (SSC_IS_CFLAG(ss_ctx, SSC_CFL_PROTBYMAIL|SSC_CFL_PROTBYCLTADDR))
		ltype |= SMARA_LT_RCPT_PROT;
	if (SSC_IS_CFLAG(ss_ctx, SSC_CFL_PROTMAP))
		flags |= SMARA_LFL_PROTMAP;
	if (SSC_IS_CFLAG(ss_ctx, SSC_CFL_PROTIMPLDET))
		flags |= SMARA_LFL_PROTIMPLDET;
	if (SSC_IS_CFLAG(ss_ctx, SSC_CFL_ACCIMPLDET))
		flags |= SMARA_LFL_ACCIMPLDET;

	/*
	**  Need to check access map if
	**  access feature is set
	**  but not QUICK:RELAY for session
	**  and not QUICK:OK for session unless !relay (i.e., it might be
	**  necessary to allow relaying)
	*/

	if (SSC_IS_CFLAG(ss_ctx, SSC_CFL_ACCESS_DB) &&
	    !(SSSE_ARE_FLAGS(ss_sess, SSSE_FL_QUICK|SSSE_FL_CLIENT_RELAY)) &&
	    !(SSSE_ARE_FLAGS(ss_sess, SSSE_FL_QUICK|SSSE_FL_CLIENT_OK)
		&& SM_IS_FLAG(fct_flags, SSRF_FL_RELAY))
	   )
	{
		ltype |= SMARA_LT_RCPT_ACC;
	}

	/*
	**  greylisting: if requested
	**  and not done before in this session
	**  and not client:ok
	**  and not client relaying allowed
	*/

	if (SSC_IS_CFLAG(ss_ctx, SSC_CFL_GREY) &&
	    !SSSE_IS_FLAG(ss_sess, SSSE_FL_GREYCHKD) &&
	    !SSSE_IS_FLAG(ss_sess, SSSE_FL_CLIENT_OK) &&
	    !SSSE_IS_FLAG(ss_sess, SSSE_FL_CLIENT_RELAY))
	{
		ltype |= SMARA_LT_GREY;
	}

	ret = sm_s2a_rcptid(ss_sess, ss_ctx->ssc_s2a_ctx,
		ss_ta->ssta_id, ss_rcpt->ssr_idx, ltype, flags, ss_sess->ssse_str);
	if (sm_is_err(ret)) {
		sm_log_write(ss_ctx->ssc_lctx,
			SS_LCAT_SERVER, SS_LMOD_SERVER,
			SM_LOG_ERROR, 6,
			"sev=ERROR, func=ss_rcpt, ss_sess=%s, ss_ta=%s, sm_s2a_rcptid=%m"
			, ss_sess->ssse_id, ss_ta->ssta_id, ret);
		goto error;
	}
	ret = sm_w4q2s_reply(ss_sess, ss_ctx->ssc_cnf.ss_cnf_w4a2s,
			ss_ctx->ssc_s2a_ctx);
	if (sm_is_err(ret)) {
		sm_log_write(ss_ctx->ssc_lctx,
			SS_LCAT_SERVER, SS_LMOD_SERVER,
			SM_LOG_ERROR, 6,
			"sev=ERROR, func=ss_rcpt, ss_sess=%s, ss_ta=%s, sm_w4q2s_reply=%m"
			, ss_sess->ssse_id, ss_ta->ssta_id, ret);
		goto error;
	}
	else if (SMAR_RISQUICK(ret)) {
		/* SSTA_SET_FLAG(ss_ta, SSTA_FL_RCPT_QCK); */
		SMAR_RCLRQUICK(ret);

		/* quick:discard -> discard entire transaction */
		if (SMTP_R_DISCARD == ret)
			SSTA_SET_FLAG(ss_ta, SSTA_FL_DISCARD);
	}
	if (SMAR_RISGREYCHKD(ret)) {
		SSSE_SET_FLAG(ss_sess, SSSE_FL_GREYCHKD);
		SMAR_RCLRGREYCHKD(ret);
	}
	if (SMAR_RISGREY(ret)) {
		SSSE_SET_FLAG(ss_sess, SSSE_FL_GREYLSTD);
		SMAR_RCLRGREY(ret);
		if (SSC_IS_CFLAG(ss_ctx, SSC_CFL_GREY_DATA))
		{
			ret = SMAR_R_FLAGS(ret)|SM_SUCCESS;
			sm_str_clr(ss_sess->ssse_wr);
		}
	}
	if (!SM_IS_FLAG(fct_flags, SSRF_FL_RELAY)
	    && SMAR_R_IS(ret, SMAR_R_LOC_RCPT)
	    && !SSC_IS_CFLAG(ss_ctx, SSC_CFL_LMTPNOTRELAY))
		SM_SET_FLAG(fct_flags, SSRF_FL_RELAY);

	SMAR_CLRFLAGS(ret);

	if (!SM_IS_FLAG(fct_flags, SSRF_FL_RELAY) && ret != SMTP_R_RELAY
	    && !SMTP_IS_REPLY_ERROR(ret))
	{
		SS_RELAYING_DENIED(ss_sess->ssse_str);
		SM_SET_FLAG(fct_flags, SSRF_FL_BAD);
		goto cleanup;
	}
	if (SMTP_R_RELAY == ret)
		ret = SM_SUCCESS;	/* "normalize" */
	else if (SMTP_R_DISCARD == ret)	/* check before relaying denied?? */
		goto discard;
	else if (SMTP_R_CONT == ret)
		ret = SM_SUCCESS;	/* "normalize" */

	/*
	**  Note: it might be useful to process several RCPT commands
	**	concurrently (if PIPELINING is active). This can "hide"
	**	latencies in the address processing, esp. DNS lookups.
	**  Question: how much would this complicate the code?
	**	Currently probably quite a lot, because the code has
	**	to check whether there is data from the client
	**	and whether there is a response from the QMGR.
	**
	**  Notes:
	**	If a pmilter should allow relaying, then this code needs
	**	to be moved up (before SS_RELAYING_DENIED()) or that
	**	information must be maintained somehow (e.g., check "relay"
	**	after pmilter call).
	**
	**	This is "flow through" code: ret may contain an error
	**	which must not be overridden (unless "increased").
	*/

#if MTA_USE_PMILTER
	res = sspm_rcpt(ss_sess, ss_rcpt, ret);
# if MTA_USE_RSAD
	ss_rcpt->ssr_rcode = res;
# endif
	SM_SET_FLAG(fct_flags, SSRF_FL_PMC);
	if (sm_is_err(res)) {
		/* check whether res is more "severe" than ret? */
		ret = res;
		goto error;
	}
	if (SMTP_R_DISCARD == res) {
		ret = res;
		goto discard;
	}

	/* HACK: relies on order of SMTP reply code: 5 more "severe" than 4 */
	if (smtp_reply_type(res) > smtp_reply_type(ret))
		ret = res;
#endif

/* XXX how does this interact with RSAD??? */
	/* everything is fine so far? check previous rejections */
	if (SMTPS_R_IS_OK(ret) && SSTA_IS_FLAG(ss_ta, SSTA_FL_DELAY_CHKS)) {
		ss_acc_P ss_acc;

		ss_acc = NULL;
		sm_log_write(ss_ctx->ssc_lctx,
			SS_LCAT_SERVER, SS_LMOD_SERVER,
			SM_LOG_DEBUG, 12,
			"sev=DBG, func=ss_rcpt, ss_sess=%s, ss_ta=%s, ta_result=%d, se_result=%d, map2_res=%d, reply2=%d"
			, ss_sess->ssse_id, ss_ta->ssta_id
			, ss_ta->ssta_mail_acc.ssa_map_result
			, ss_sess->ssse_acc.ssa_map_result
			, ss_ta->ssta_rcpt_acc2.ssa_map_result
			, ss_ta->ssta_rcpt_acc2.ssa_reply_code
			);

#if 0
		/* todo: should check for SpamFriend... */
		if (ss_ta->ssta_rcpt_acc2.ssa_map_result == SM_ACC_FOUND
		    && ss_ta->ssta_rcpt_acc2.ssa_reply_code == SMTP_R_OK)
		{
			ret = SM_SUCCESS;
		}
#else /* 0 */
		if (ss_ta->ssta_rcpt_acc.ssa_map_result == SM_ACC_FOUND
		    && SMAR_RISQUICK(ss_ta->ssta_rcpt_acc.ssa_reply_code)
		    && (SMAR_RWOFLAGS(ss_ta->ssta_rcpt_acc.ssa_reply_code)
				== SMTP_R_OK ||
			SMAR_RWOFLAGS(ss_ta->ssta_rcpt_acc.ssa_reply_code)
				== SMTP_R_RELAY)
		   )
		{
			ret = SM_SUCCESS;
		}
#endif /* 0 */
		else if (ss_ta->ssta_mail_acc.ssa_map_result == SM_ACC_FOUND
			 && IS_SMTP_REPLY(ss_ta->ssta_mail_acc.ssa_reply_code))
			ss_acc = &ss_ta->ssta_mail_acc;
		else if (ss_sess->ssse_acc.ssa_map_result == SM_ACC_FOUND
			 && IS_SMTP_REPLY(ss_sess->ssse_acc.ssa_reply_code))
			ss_acc = &ss_sess->ssse_acc;
		if (ss_acc != NULL) {
			ret = ss_acc->ssa_reply_code;
			if (ss_acc->ssa_reply_text != NULL)
				sm_str_cpy(ss_sess->ssse_wr, ss_acc->ssa_reply_text);
			else
				sm_str_clr(ss_sess->ssse_wr);
			sm_log_write(ss_ctx->ssc_lctx,
				SS_LCAT_SERVER, SS_LMOD_SERVER,
				SM_LOG_DEBUG, 12,
				"sev=DBG, func=ss_rcpt, ss_sess=%s, ss_ta=%s, reply=%m, text=%@T"
				, ss_sess->ssse_id, ss_ta->ssta_id
				, ret, ss_sess->ssse_wr);
		}
	}

	res = ss_crt_reply(ss_sess->ssse_wr, ret, SS_PHASE_RCPT, false);
	if (SMTPS_R_IS_OK(ret)) {
		sm_log_write(ss_ctx->ssc_lctx,
			SS_LCAT_SERVER, SS_LMOD_SERVER, SM_LOG_INFO, 2,
			"sev=INFO, func=ss_rcpt, ss_sess=%s, ss_ta=%s, rcpt=%@T, idx=%u, stat=%d"
			, ss_sess->ssse_id, ss_ta->ssta_id, ss_sess->ssse_str
			, ss_rcpt->ssr_idx, ret);
	}
	else {
		sm_log_write(ss_ctx->ssc_lctx,
			SS_LCAT_SERVER, SS_LMOD_SERVER, SM_LOG_INFO, 2,
			"sev=INFO, func=ss_rcpt, ss_sess=%s, ss_ta=%s, rcpt=%@T, idx=%u, stat=%d, status=%@T"
			, ss_sess->ssse_id, ss_ta->ssta_id, ss_sess->ssse_str
			, ss_rcpt->ssr_idx, ret, ss_sess->ssse_wr);
	}

	/* everything is fine? */
	if (SM_SUCCESS == ret) {
		ss_ta->ssta_state = SSTA_ST_RCPT;
		ss_ta->ssta_rcpts_ok++;
	}
	else {
		SS_RCPTS_REMOVE_FREE(ss_ta, &ss_ta->ssta_rcpts, ss_rcpt);
		if (IS_SMTP_REPLY(ret) && smtp_is_reply_fail(ret) &&
		    ss_invalidaddr(ss_sess) == SMTP_R_SSD)
			ret = SMTP_R_SSD;
	}
	return (SMTP_R_SSD == ret) ? ret : res;

  discard:
	ss_ta->ssta_state = SSTA_ST_RCPT;
	SSTA_SET_FLAG(ss_ta, SSTA_FL_DSCRD_RCPT);
	if (sm_str_getlen(ss_sess->ssse_wr) == 0)
		sm_str_scopy(ss_sess->ssse_wr, SS_R_OK);
	sm_log_write(ss_ctx->ssc_lctx,
		SS_LCAT_SERVER, SS_LMOD_SERVER, SM_LOG_INFO, 9,
		"sev=INFO, func=ss_rcpt, ss_sess=%s, ss_ta=%s, rcpt=%@T, stat=%d"
		, ss_sess->ssse_id, ss_ta->ssta_id, ss_sess->ssse_str
		, SMTP_R_DISCARD);
	log = SM_DONT_LOG;

  cleanup:

	/*
	**  Note: ssse_str might be empty if an error occurred before rcpt was
	**  parse properly.  Should we log the input instead?
	*/

	if (SM_SUCCESS == ret && IS_SMTP_REPLY_STR(ss_sess->ssse_wr, 0))
		ret = SMTP_REPLY_STR2VAL(ss_sess->ssse_wr, 0);
	if (log < SM_DONT_LOG && sm_str_getlen(ss_sess->ssse_wr) > 0) {
		sm_log_write(ss_ctx->ssc_lctx,
			SS_LCAT_SERVER, SS_LMOD_SERVER, SM_LOG_INFO, log,
			"sev=INFO, func=ss_rcpt, ss_sess=%s, ss_ta=%s, rcpt=%@T, error=%m, status=%@T"
			, ss_sess->ssse_id, ss_ta->ssta_id, ss_sess->ssse_str
			, ret, ss_sess->ssse_wr);
	}
#if MTA_USE_PMILTER
	if (!SM_IS_FLAG(fct_flags, SSRF_FL_PMC) &&
	    SM_IS_FLAG(fct_flags, SSRF_FL_OK2CPM))
	{
		res = sspm_rcpt(ss_sess, ss_rcpt, ret);
		SM_SET_FLAG(fct_flags, SSRF_FL_PMC);
	}
#endif

	ret = SM_SUCCESS;
  error:
	if (sm_str_getlen(ss_sess->ssse_wr) == 0)
		sm_str_scopy(ss_sess->ssse_wr, SS_R_SSD);
	if (ret != SM_SUCCESS && log < SM_DONT_LOG &&
	    sm_str_getlen(ss_sess->ssse_wr) > 0)
	{
		sm_log_write(ss_ctx->ssc_lctx,
			SS_LCAT_SERVER, SS_LMOD_SERVER, SM_LOG_INFO, log,
			"sev=INFO, func=ss_rcpt, ss_sess=%s, ss_ta=%s, rcpt=%@T, error=%m, status=%@T"
			, ss_sess->ssse_id, ss_ta->ssta_id, ss_sess->ssse_str
			, ret, ss_sess->ssse_wr);
	}

	/* remove rcpt from list again and free it */
	if (ss_rcpt != NULL)
		SS_RCPTS_REMOVE_FREE(ss_ta, &ss_ta->ssta_rcpts, ss_rcpt);

	if (SM_IS_FLAG(fct_flags, SSRF_FL_BAD) &&
	    IS_SMTP_REPLY(ret) && smtp_is_reply_fail(ret) &&
	    ss_invalidaddr(ss_sess) == SMTP_R_SSD)
		ret = SMTP_R_SSD;
	return ret;
}

/*
**  SS_DATA_EOB -- found EOB in data stream: write block to CDB etc
**
**	Parameters:
**		ss_sess -- SMTPS session
**		bufp -- pointer to (begin of) file buffer
**		pskip -- (pointer to) skip rest of input? (output)
**
**	Returns:
**		usual return code
*/

static sm_ret_T
ss_data_eob(ss_sess_P ss_sess, uchar *bufp, bool *pskip)
{
	sm_ret_T ret;
	size_t bytes2write, max_sz_kb;
	ssize_t byteswritten;
	ss_ta_P ss_ta;

	SM_IS_SS_SESS(ss_sess);
	ss_ta = ss_sess->ssse_ta;
	SM_IS_SS_TA(ss_ta);
	SM_REQUIRE(pskip != NULL);

	/* write the current buffer */
	bytes2write = f_p(*ss_sess->ssse_fp) - bufp;
	if (0 == bytes2write)
		return SM_SUCCESS;
	ret = cdb_write(cdb_ctx, ss_ta->ssta_dfp, bufp, bytes2write, &byteswritten);
	if (sm_is_err(ret)) {
		sm_log_write(ss_sess->ssse_sctx->ssc_lctx,
			SS_LCAT_SERVER, SS_LMOD_SERVER,
			SM_LOG_ERROR, 8,
			"sev=ERROR, func=ss_data_eob, ss_sess=%s, ss_ta=%s, write_cdb=%m"
			, ss_sess->ssse_id, ss_ta->ssta_id, ret);
		(void) ss_crt_reply(ss_sess->ssse_wr, SMTP_R_TEMP, SS_PHASE_DOT, true);
		*pskip = true;
		return ret;
	}
	ss_ta->ssta_msg_sz_b += byteswritten;
	max_sz_kb = ss_sess->ssse_sctx->ssc_cnf.ss_cnf_max_msg_sz_kb;
	if (max_sz_kb > 0 && max_sz_kb < ss_ta->ssta_msg_sz_b / 1024) {
		sm_log_write(ss_sess->ssse_sctx->ssc_lctx,
			SS_LCAT_SERVER, SS_LMOD_SERVER,
			SM_LOG_NOTICE, 8,
			"sev=NOTICE, func=ss_data_eob, ss_sess=%s, ss_ta=%s, max_message_size=%lu, status=exceeded"
			, ss_sess->ssse_id, ss_ta->ssta_id
			, (ulong) max_sz_kb);
		sm_str_scopy(ss_sess->ssse_wr,
			"552 5.3.4 Maximum message size exceeded.\r\n");
		*pskip = true;
	}
#if MTA_USE_PMILTER
	ret = sspm_msg(ss_sess, bufp, bytes2write, false, pskip);
#endif
	return ret;
}

#if MTA_USE_PMILTER && MTA_USE_RSAD
/*
**  SS_RSAD_SET_REPLIES -- Set RCPT replies after end of mail data
**
**	Parameters:
**		ss_ctx -- SMTPS context
**		ss_sess -- SMTPS session
**		ss_ta -- SMTP server transaction context
**
**	Returns:
**		usual return code
*/

static sm_ret_T
ss_rsad_set_replies(ss_ctx_P ss_ctx, ss_sess_P ss_sess, ss_ta_P ss_ta)
{
	sm_ret_T ret, rcode;
	uint u;
	ss_rcpt_P ss_rcpt;

	SM_IS_SS_SESS(ss_sess);
	SM_IS_SS_TA(ss_ta);
	if (ss_ta->ssta_nreplies != ss_ta->ssta_rcpts_ok_orig) {
		sm_log_write(ss_ctx->ssc_lctx,
			SS_LCAT_SERVER, SS_LMOD_SERVER,
			SM_LOG_NOTICE, 2,
			"sev=NOTICE, func=ss_rsad_set_replies, ss_sess=%s, ss_ta=%s, replies=%u, ok=%u, status=inconsistent"
			, ss_sess->ssse_id, ss_ta->ssta_id
			, ss_ta->ssta_nreplies, ss_ta->ssta_rcpts_ok_orig);
		return SMTP_R_SSD;
	}

#define SMTP_R_IS_OK(rcode)	(SMTP_OK == rcode || smtp_reply_type(rcode) == SMTP_RTYPE_OK)

	/* set replies */
	if (SS_RCPTS_EMPTY(&ss_ta->ssta_rcpts)) {
		sm_log_write(ss_ctx->ssc_lctx,
			SS_LCAT_SERVER, SS_LMOD_SERVER,
			SM_LOG_ERROR, 2,
			"sev=ERROR, func=ss_rsad_set_replies, ss_sess=%s, ss_ta=%s, replies=%u, ok=%u, status=no_rcpts"
			, ss_sess->ssse_id, ss_ta->ssta_id
			, ss_ta->ssta_nreplies, ss_ta->ssta_rcpts_ok);
		return SMTP_R_SSD;
	}
	ss_rcpt = SS_RCPTS_FIRST(&ss_ta->ssta_rcpts);
	rcode = ss_ta->ssta_rcodes[0];
	for (u = 0; u < ss_ta->ssta_nreplies; u++) {
		while (!SMTP_R_IS_OK(ss_rcpt->ssr_rcode)
		       && ss_rcpt->ssr_type != PM_RCPT_DEL)
		{
			ss_rcpt = SS_RCPTS_NEXT(ss_rcpt);
			if (ss_rcpt == SS_RCPTS_END(&ss_ta->ssta_rcpts)) {
				sm_log_write(ss_ctx->ssc_lctx,
					SS_LCAT_SERVER, SS_LMOD_SERVER,
					SM_LOG_WARN, 2,
					"sev=WARN, func=ss_rsad_set_replies, ss_sess=%s, ss_ta=%s, status=no_valid_rcpt_found"
					, ss_sess->ssse_id, ss_ta->ssta_id);
				return SMTP_R_SSD;
			}
		}

		ret = ss_ta->ssta_rcodes[u];
		if (ret < rcode)
			rcode = ret;
		if (!SMTP_R_IS_OK(ret)) {
			ss_rcpt->ssr_type = PM_RCPT_RSAD;
			ss_rcpt->ssr_rcode = ret;
			--ss_ta->ssta_rcpts_ok; /* ??? */
		}
		ss_rcpt = SS_RCPTS_NEXT(ss_rcpt);
	}

	/* "normalize" rcode */
	if (rcode != SMTP_OK && smtp_reply_type(rcode) == SMTP_RTYPE_OK)
		rcode = SMTP_OK;
	return rcode;
}

/*
**  SS_RSAD_SEND_REPLIES -- Send deferred RCPT replies after end of mail data
**
**	Parameters:
**		ss_ctx -- SMTPS context
**		ss_sess -- SMTPS session
**		ss_ta -- SMTP server transaction context
**
**	Returns:
**		usual return code
*/

static sm_ret_T
ss_rsad_send_replies(ss_ctx_P ss_ctx, ss_sess_P ss_sess, ss_ta_P ss_ta)
{
	sm_ret_T ret, rcode;
	uint ui;
	ss_rcpt_P ss_rcpt;

	SM_IS_SS_SESS(ss_sess);
	SM_IS_SS_TA(ss_ta);

	rcode = 599;

#if 0
	/* optimization: check for common RCPT status */
	for (ss_rcpt = SS_RCPTS_FIRST(&ss_rcpts_hd);
	     ss_rcpt != SS_RCPTS_END(&ss_rcpts_hd);
	     ss_rcpt = SS_RCPTS_NEXT(ss_rcpt))
	{
		n = atoi(ss_rcpt->ssr_reply);
		if (CRS_INIT == common_rcpt_status)
			common_rcpt_status = n;
		else if (n != common_rcpt_status) {
			common_rcpt_status = CRS_NONE;
			break;
		}
	}
	if (IS_SMTP_REPLY(common_rcpt_status))
		sm_snprintf(resp, sizeof(resp), "%d first\r\n", common_rcpt_status);
	else
		strlcpy(resp, "353 RCPT status follows\r\n", sizeof resp);
#endif

# if MTA_USE_RSAD == 2
	/* send "first reply" */
	sm_str_scopy(ss_sess->ssse_wr, "353 RCPT replies follow.\r\n");
	ret = ss_reply(ss_sess, ss_sess->ssse_wr, ss_sess->ssse_fp, -1, false);
	sm_str_clr(ss_sess->ssse_wr);
	if (ret != SMTP_OK) {
		sm_log_write(ss_ctx->ssc_lctx,
			SS_LCAT_SERVER, SS_LMOD_SERVER,
			SM_LOG_WARN, 2,
			"sev=WARN, func=ss_rsad_send_replies, ss_sess=%s, ss_ta=%s, status=write_error_first_reply, ret=%m"
			, ss_sess->ssse_id, ss_ta->ssta_id, ret);
		goto error;
	}
# endif /* MTA_USE_RSAD == 2 */

	/* send all replies back to client */
	for (ss_rcpt = SS_RCPTS_FIRST(&ss_ta->ssta_rcpts), ui = 0;
		 ss_rcpt != SS_RCPTS_END(&ss_ta->ssta_rcpts)
	     && ui < ss_ta->ssta_rcpts_ok_orig;
		 ss_rcpt = SS_RCPTS_NEXT(ss_rcpt), ui++)
	{
		if (PM_RCPT_ADD == ss_rcpt->ssr_type) {
			/* shouldn't happen, ssta_rcpts_ok_orig should have stopped loop! */
			break;
		}

		ret = ss_rcpt->ssr_rcode;
		if (ret < rcode)
			rcode = ret;

		(void) ss_crt_reply(ss_sess->ssse_wr, ret, SS_PHASE_RCPT, true);
		ret = ss_reply(ss_sess, ss_sess->ssse_wr, ss_sess->ssse_fp, -1, false);
		sm_str_clr(ss_sess->ssse_wr);
		if (ret != SMTP_OK) {
			sm_log_write(ss_ctx->ssc_lctx,
				SS_LCAT_SERVER, SS_LMOD_SERVER,
				SM_LOG_WARN, 2,
				"sev=WARN, func=ss_rsad_send_replies, ss_sess=%s, ss_ta=%s, idx=%u, status=write_error_rsad, ret=%m"
				, ss_sess->ssse_id, ss_ta->ssta_id, ss_rcpt->ssr_idx, ret);
			goto error;
		}
		else
			sm_log_write(ss_ctx->ssc_lctx,
				SS_LCAT_SERVER, SS_LMOD_SERVER,
				SM_LOG_INFO, 10,
				"sev=INFO, func=ss_rsad_send_replies, ss_sess=%s, ss_ta=%s, idx=%u, ret=%d"
				, ss_sess->ssse_id, ss_ta->ssta_id, ss_rcpt->ssr_idx, ss_rcpt->ssr_rcode);

#if 0
		if we do this, then the loop must be a bit more clever,
		i.e.,

		ss_rcpt_nxt = SS_RCPTS_NEXT(ss_rcpt);
		if (ss_rcpt->ssr_rcode != SMTP_OK)
			SS_RCPTS_REMOVE_FREE(ss_ta, &ss_ta->ssta_rcpts, ss_rcpt);

		and in the loop as "next" part:
		ss_rcpt = ss_rcpt_nxt;
#endif
	}

	/* "normalize" rcode */
	if (rcode != SMTP_OK && smtp_reply_type(rcode) == SMTP_RTYPE_OK)
		rcode = SMTP_OK;
	return rcode;

  error:
	return ret;
}
#endif /* MTA_USE_PMILTER && MTA_USE_RSAD */

/*
**  SS_DATA -- DATA command
**
**	Parameters:
**		ss_sess -- SMTPS session
**
**	Returns:
**		usual return code
**
**	Side Effects: writes cdb (unless DISCARD is set or errors occur)
**
**	Note: SMTP requires that the input is read completely before
**		returning a reply, hence error handling is done by
**		remembering the error and setting "skip" instead of
**		just jumping to the error handling label. The only
**		exception is a read error in which case the connection
**		is dropped.
*/

#ifndef SS_DATA_DEBUG
# define SS_DATA_DEBUG	0
#endif

/*
RFC 2822:
message-id      =       "Message-ID:" msg-id CRLF
in-reply-to     =       "In-Reply-To:" 1*msg-id CRLF
references      =       "References:" 1*msg-id CRLF
msg-id          =       [CFWS] "<" id-left "@" id-right ">" [CFWS]
id-left         =       dot-atom-text / no-fold-quote / obs-id-left
id-right        =       dot-atom-text / no-fold-literal / obs-id-right
no-fold-quote   =       DQUOTE *(qtext / quoted-pair) DQUOTE
no-fold-literal =       "[" *(dtext / quoted-pair) "]"

The state machine below does not implement this, it just skips leading
whitespace. This should be "good enough" because currently the message-id
is only used for logging.
*/

#define MSGID_ST_NONE	0
#define MSGID_ST_NEW	(-1)	/* found message-id: header */
#define MSGID_ST_FIRST	(-2)	/* found message-id: header, str allocated */
#define MSGID_ST_SKIP	(-3)	/* skipping over initial white space */
#define MSGID_ST_RDING	(-4)	/* reading message id */
#define MSGID_ST_GOT	(-5)	/* got message id */
#define MSGID_ST_NOMEM	(-9)	/* can't allocate memory for message id */

static sm_ret_T
ss_data(ss_sess_P ss_sess)
{
	int c;
	uint eot_state, eoh_state, rcvd_state, hops;
#if SS_CHECK_LINE_LEN
	uint eol_state, line_len;
#endif
	int msgid_state, res_msgid_state;
	size_t bufs, bytes2write, max_sz_kb;
	ssize_t byteswritten;
	bool skip;
#if SS_DATA_DEBUG
	char sbuf[512];
	ssize_t b;
#endif
	sm_ret_T ret, res;
	ss_ta_P ss_ta;
	uchar *bufp;
	time_t currt;
	cdb_ctx_P cdb_ctx;
	ss_ctx_P ss_ctx;
	static SM_DECL_EOT;
	static SM_DECL_EOH;
#if SS_CHECK_LINE_LEN
	static const char eol[] = "\r\n";
#define SM_EOL_LEN	(sizeof(eol) - 1)
#endif
	static const char rcvd[] = "\r\nreceived:";
	static const char msgid[] = "\r\nmessage-id:";
	static const char res_msgid[] = "\r\nresent-message-id:";
#if SS_TEST
	extern int Unsafe;
#endif

	SM_IS_SS_SESS(ss_sess);
	ss_ta = ss_sess->ssse_ta;
	SM_IS_SS_TA(ss_ta);
	ss_ctx = ss_sess->ssse_sctx;

	/* beware of pipelining and discarded recipients */
	if (ss_ta->ssta_state != SSTA_ST_RCPT ||
	    (NO_RCPTS(ss_ta) && !SSTA_IS_FLAG(ss_ta, SSTA_FL_DSCRD_RCPT)))
		return sm_str_scopy(ss_sess->ssse_wr, SS_R_SEQ);
	if (SSSE_IS_FLAG(ss_sess, SSSE_FL_GREYLSTD)) {
		(void) sm_str_scopy(ss_sess->ssse_wr, SS_R_GREY);
		ret = SMTP_R_SSD;
		sm_log_write(ss_ctx->ssc_lctx,
			SS_LCAT_SERVER, SS_LMOD_SERVER, SM_LOG_INFO, 9,
			"sev=INFO, func=ss_data, ss_sess=%s, ss_ta=%s, stat=%d, status=%@T"
			, ss_sess->ssse_id, ss_ta->ssta_id
			, ret, ss_sess->ssse_wr);
		return ret;
	}

	/* discard entire transaction if all recipients have been discarded */
	if (ss_ta->ssta_rcpts_ok == 0 &&
	    SSTA_IS_FLAG(ss_ta, SSTA_FL_DSCRD_RCPT))
		SSTA_SET_FLAG(ss_ta, SSTA_FL_DISCARD);
	skip = SSTA_IS_FLAG(ss_ta, SSTA_FL_DISCARD);
	ss_ta->ssta_msg_sz_b = 0;
	byteswritten = 0;
#if SS_CHECK_LINE_LEN
	eol_state = 0;
	line_len = 0;
	SSTA_CLR_FLAG(ss_ta, SSTA_FL_LL_EXC);
#endif
	max_sz_kb = ss_ctx->ssc_cnf.ss_cnf_max_msg_sz_kb;

	ss_ta->ssta_dfp = NULL;
	currt = st_time();
	cdb_ctx = ss_ctx->ssc_cdb_ctx;

	/* Check for "illegal" pipelining: DATA is synchronization point */
	if (sm_io_getinfo(ss_sess->ssse_fp, SM_IO_IS_READABLE, NULL)) {
		sm_str_scopy(ss_sess->ssse_wr,
			"421 4.5.0 Illegal Pipelining detected (DATA)\r\n");
		ret = sm_error_temp(SM_EM_SMTPS, SM_E_ILL_PIPE);
		goto error;
	}

#if MTA_USE_PMILTER
	ret = sspm_data(ss_sess, &skip);
	if (sm_is_err(ret))
		goto error;
	if (IS_SMTP_REPLY(ret) && SMTP_IS_REPLY_ERROR(ret))
		goto error;
#endif

	if (!skip) {
		/* open cdb entry */
		ret = cdb_open_write(cdb_ctx, ss_ta->ssta_id, ss_ta->ssta_dfp,
				SM_IO_WREXCL, 0, NULL);
		if (sm_is_err(ret)) {
			sm_log_write(ss_ctx->ssc_lctx,
				SS_LCAT_SERVER, SS_LMOD_SERVER,
				SM_LOG_ERROR, 8,
				"sev=ERROR, func=ss_data, ss_sess=%s, ss_ta=%s, cdb_open=%m"
				, ss_sess->ssse_id, ss_ta->ssta_id, ret);
			(void) ss_crt_reply(ss_sess->ssse_wr, SMTP_R_TEMP,
					SS_PHASE_DATA, true);
			goto error;
		}
		SSTA_SET_FLAG(ss_ta, SSTA_FL_CDB_EXISTS);

		/* set group id? It might be inherited from directory */
		if (ss_ctx->ssc_cnf.ss_cnf_cdb_gid > 0 &&
		    (c = fchown(sm_io_getinfo(ss_ta->ssta_dfp, SM_IO_WHAT_FD, NULL),
				(uid_t) -1, ss_ctx->ssc_cnf.ss_cnf_cdb_gid)) < 0)
		{
#if SS_DEBUG_CHOWN_CDB
			struct stat sb;
#endif

			ret = sm_error_temp(SM_EM_SMTPS, errno);
			sm_log_write(ss_ctx->ssc_lctx,
				SS_LCAT_SERVER, SS_LMOD_SERVER,
				SM_LOG_ERROR, 8,
				"sev=ERROR, func=ss_data, ss_sess=%s, ss_ta=%s, chgrp_cdb=%d"
				, ss_sess->ssse_id, ss_ta->ssta_id, errno);
#if SS_DEBUG_CHOWN_CDB
			c = fstat(f_fd(*ss_ta->ssta_dfp), &sb);
			if (0 == c)
				sm_log_write(ss_ctx->ssc_lctx,
					SS_LCAT_SERVER, SS_LMOD_SERVER,
					SM_LOG_WARN, 8,
					"sev=WARN, func=ss_data, ss_sess=%s, ss_ta=%s, uid=%d, gid=%d, cdb_gid=%d"
					, ss_sess->ssse_id, ss_ta->ssta_id
					, (int) sb.st_uid, (int) sb.st_gid
					, (int) ss_ctx->ssc_cnf.ss_cnf_cdb_gid);
			else
				sm_log_write(ss_ctx->ssc_lctx,
					SS_LCAT_SERVER, SS_LMOD_SERVER,
					SM_LOG_WARN, 8,
					"sev=WARN, func=ss_data, ss_sess=%s, ss_ta=%s, fstat=%d"
					, ss_sess->ssse_id, ss_ta->ssta_id, c);
#endif /* SS_DEBUG_CHOWN_CDB */

			(void) ss_crt_reply(ss_sess->ssse_wr, SMTP_R_TEMP,
					SS_PHASE_DATA, true);
			goto error;
		}
	}

	ss_ta->ssta_state = SSTA_ST_DATA;

	/* initial state: begin of line, hence \r\n are "skipped" */
	eot_state = rcvd_state = SM_GOT_CRLF;
	msgid_state = res_msgid_state = SM_GOT_CRLF;
	eoh_state = hops = 0;
	sm_str_scopy(ss_sess->ssse_wr, "354 Go\r\n");
	if ((ret = ss_reply(ss_sess, ss_sess->ssse_wr, ss_sess->ssse_fp, -1,
			true)) != SMTP_OK)
	{
		sm_log_write(ss_ctx->ssc_lctx,
			SS_LCAT_SERVER, SS_LMOD_SERVER,
			SM_LOG_WARN, 8,
			"sev=WARN, func=ss_data, ss_sess=%s, ss_ta=%s, write_354_response=%m"
			, ss_sess->ssse_id, ss_ta->ssta_id, ret);
		if (!sm_is_err(ret))
			ret = SMTP_WR_ERR;
		goto error;
	}

	/*
	**  Write Received: header; do NOT use %S for helo: it ends in '\0'.
	**  More data??
	*/

	sm_str_clr(ss_sess->ssse_str);
#if MTA_USE_TLS
	if (!skip && SSSE_IS_FLAG(ss_sess, SSSE_FL_STARTTLS)) {
		sm_str_scat(ss_sess->ssse_str, "(TLS");
		if (ss_sess->ssse_tlsi->tlsi_version != NULL) {
			sm_str_scat(ss_sess->ssse_str, "=");
			sm_str_cat(ss_sess->ssse_str, ss_sess->ssse_tlsi->tlsi_version);
		}
		if (ss_sess->ssse_tlsi->tlsi_cipher != NULL) {
			sm_str_scat(ss_sess->ssse_str, ", cipher=");
			sm_str_cat(ss_sess->ssse_str, ss_sess->ssse_tlsi->tlsi_cipher);
		}
		if (ss_sess->ssse_tlsi->tlsi_cipher_bits != 0) {
			sm_str_scat(ss_sess->ssse_str, ", bits=");
			sm_strprintf(ss_sess->ssse_str, "%d",
				ss_sess->ssse_tlsi->tlsi_cipher_bits);
		}
		sm_str_scat(ss_sess->ssse_str, ", verify=");
		sm_str_scat(ss_sess->ssse_str,
			tls_vrfy2txt(ss_sess->ssse_tlsi->tlsi_vrfy));
		if (sm_str_getlen(ss_sess->ssse_str) > 40)
			sm_str_scat(ss_sess->ssse_str, ")\r\n\t");
		else
			sm_str_scat(ss_sess->ssse_str, ") ");
	}
#endif
	sm_str_clr(ss_sess->ssse_wr);
	if (!skip) {
		sm_strprintf(ss_sess->ssse_wr,
			"Received: from %.128N (%.128C [%A])\r\n\tby %S (%s) with %sSMTP%s%s\r\n\t%Sid %s; "
			, ss_sess->ssse_helo
			, ss_sess->ssse_cltname
			, ss_sess->ssse_client.s_addr
			, ss_ctx->ssc_hostname
			, MTA_VERSION_STR
			, SSSE_IS_FLAG(ss_sess, SSSE_FL_HELO) ? "" : "E"
			, SSSE_IS_FLAG(ss_sess, SSSE_FL_STARTTLS) ? "S" : ""
			, SSSE_IS_FLAG(ss_sess, SSSE_FL_AUTH) ? "A" : ""
			, ss_sess->ssse_str
			, ss_ta->ssta_id
			);
		ret = arpadate(&currt, ss_sess->ssse_wr);
		ret = sm_str_scat(ss_sess->ssse_wr, "\r\n");

		ret = cdb_write(cdb_ctx, ss_ta->ssta_dfp,
			sm_str_data(ss_sess->ssse_wr),
			sm_str_getlen(ss_sess->ssse_wr), &byteswritten);
		if (sm_is_err(ret)) {
			sm_log_write(ss_ctx->ssc_lctx,
				SS_LCAT_SERVER, SS_LMOD_SERVER,
				SM_LOG_ERROR, 8,
				"sev=ERROR, func=ss_data, ss_sess=%s, ss_ta=%s, write_cdb=%m"
				, ss_sess->ssse_id, ss_ta->ssta_id, ret);
			(void) ss_crt_reply(ss_sess->ssse_wr, SMTP_R_TEMP,
					SS_PHASE_DOT, true);
			skip = true;
		}
		else {
#if MTA_USE_PMILTER
			ret = sspm_msg(ss_sess, sm_str_data(ss_sess->ssse_wr),
				sm_str_getlen(ss_sess->ssse_wr), true, &skip);
#endif
			sm_str_clr(ss_sess->ssse_wr);
		}
		ss_ta->ssta_msg_sz_b += byteswritten;
	}

	/*
	**  Notes: need to check whether the first line contains
	**	a header: if it doesn't: print a blank line.
	**  Also need to check the number of Received: headers,
	**	this means we need to recognize header/body boundary
	**	and Received: headers. This is done below in a simple way.
	*/

	/*
	**  See the smX docs: libraries.func.tex Required I/O Functionality
	*/

	bufs = 0;

	/* Switch to read mode before accessing the buffer */
	sm_iotord(ss_sess->ssse_fp);
	bufp = f_p(*ss_sess->ssse_fp);
	for (;;) {
		c = sm_getb(ss_sess->ssse_fp);
		do {
			if (SM_IO_EOF == c) {
				sm_log_write(ss_ctx->ssc_lctx,
					SS_LCAT_SERVER, SS_LMOD_SERVER,
					SM_LOG_WARN, 8,
					"sev=WARN, func=ss_data, ss_sess=%s, ss_ta=%s, read_data=EOF, bufs=%lu"
					, ss_sess->ssse_id, ss_ta->ssta_id
					, (ulong) bufs);
				ret = sm_error_perm(SM_EM_SMTPS, SM_E_EOF);
				goto error;
			}
			if (c != SM_IO_EOB) {
				SM_ASSERT(c >= 0);
				break;
			}
			if (!skip) {
				(void) ss_data_eob(ss_sess, bufp, &skip);
				/* ignore return code, skip is important */
			}

			/* get new buffer */
			c = sm_rget(ss_sess->ssse_fp);
			if (sm_is_err(c)) {
				sm_log_write(ss_ctx->ssc_lctx,
					SS_LCAT_SERVER, SS_LMOD_SERVER,
					SM_LOG_WARN, 8,
					"sev=WARN, func=ss_data, ss_sess=%s, ss_ta=%s, read_error=%m, bufs=%lu"
					, ss_sess->ssse_id, ss_ta->ssta_id, c
					, (ulong) bufs);
				ret = c; /* SMTP_RD_ERR; */
				goto error;
			}
			++bufs;
			bufp = f_p(*ss_sess->ssse_fp) - 1;
			if (bufp < f_bfbase(*ss_sess->ssse_fp))
				bufp = f_bfbase(*ss_sess->ssse_fp);
#if SS_DATA_DEBUG
			if (ss_ctx->ssc_cnf.ss_cnf_debug > 1) {
				int e = errno;

				sm_snprintf(sbuf, sizeof(sbuf),
					"data: read  buf=%d, c=%3d, f_r=%x, f_p=%lx, bufp=%lx, *bufp=%d, f_bfbase=%lx, f_bfsize=%x, e=%d\r\n"
					, bufs, c, f_r(*ss_sess->ssse_fp), (long) f_p(*ss_sess->ssse_fp), (long) bufp, (int) *bufp, (long) f_bfbase(*ss_sess->ssse_fp), f_bfsize(*ss_sess->ssse_fp), e);
				sm_io_write(smioerr, (uchar *)sbuf, strlen(sbuf), &b);
				sm_io_flush(smioerr);
				errno = e;
			}
#endif /* SS_DATA_DEBUG */
		} while (c < 0);	/* HACK!! */

#if SS_CHECK_LINE_LEN
		if (ss_sess->ssse_max_line_len > 0 && !skip) {
			if (++line_len > ss_sess->ssse_max_line_len) {
				skip = true;
				SSTA_SET_FLAG(ss_ta, SSTA_FL_LL_EXC);
			}
			if (c == eol[eol_state]) {
				if (++eol_state >= SM_EOL_LEN) {
					line_len = 0;
					eol_state = 0;
				}
			}
			else {
				eol_state = 0;
				if (c == eol[eol_state])
					++eol_state;
			}
		}
#endif /* SS_CHECK_LINE_LEN */

		if (eoh_state < SM_EOH_LEN) {
			if (c == eoh[eoh_state])
				++eoh_state;
			else {
				eoh_state = 0;
				if (c == eoh[eoh_state])
					++eoh_state;
			}
			if (TOLOWER(c) == rcvd[rcvd_state]) {
				if (++rcvd_state >= strlen(rcvd)) {
					if (++hops > ss_ctx->ssc_cnf.ss_cnf_maxhops)
						skip = true;
					rcvd_state = 0;
#if SS_DATA_DEBUG
					if (ss_ctx->ssc_cnf.ss_cnf_debug > 2) {
						sm_snprintf(sbuf, sizeof(sbuf), "data: hops=%d\r\n"
							, hops);
						sm_io_write(smioerr, (uchar *)sbuf, strlen(sbuf), &b);
						sm_io_flush(smioerr);
					}
#endif /* SS_DATA_DEBUG */
				}
			}
			else {
				rcvd_state = 0;
				if (TOLOWER(c) == rcvd[rcvd_state])
					++rcvd_state;
			}

			/* put this into some function so it can be reused?? */
			if (NULL == ss_ta->ssta_msgid && msgid_state >= 0) {
				if (TOLOWER(c) == msgid[msgid_state]) {
					if (++msgid_state >= (int)strlen(msgid))
						msgid_state = MSGID_ST_NEW;
				}
				else {
					msgid_state = 0;
					if (TOLOWER(c) == msgid[msgid_state])
						++msgid_state;
				}
			}

			if (res_msgid_state >= 0) {
				if (TOLOWER(c) == res_msgid[res_msgid_state]) {
					if (++res_msgid_state >=
					    (int)strlen(res_msgid))
						msgid_state = MSGID_ST_NEW;
				}
				else {
					res_msgid_state = 0;
					if (TOLOWER(c) ==
					    res_msgid[res_msgid_state])
						++res_msgid_state;
				}
			}

			if (MSGID_ST_NEW == msgid_state) {
				if (NULL == ss_ta->ssta_msgid)
					ss_ta->ssta_msgid = sm_str_new(NULL, SMTPMSGIDSIZE,
							SMTPMAXSIZE);
				else
					sm_str_clr(ss_ta->ssta_msgid);
				if (ss_ta->ssta_msgid != NULL)
					msgid_state = MSGID_ST_FIRST;
				else
					msgid_state = MSGID_ST_NOMEM;
			}

			if (MSGID_ST_FIRST == msgid_state)
				msgid_state = MSGID_ST_SKIP;
			else if (MSGID_ST_SKIP == msgid_state && ISSPACE(c))
				;
			else if (MSGID_ST_SKIP == msgid_state && ISPRINT(c))
				msgid_state = MSGID_ST_RDING;
			if (MSGID_ST_RDING == msgid_state) {
				if (ISSPACE(c)) {
					msgid_state = MSGID_ST_GOT;
					sm_str_term(ss_ta->ssta_msgid);
				}
				else if (ISPRINT(c))
					SM_STR_PUT(ss_ta->ssta_msgid, c);
			}
		}
		if (c == eot[eot_state]) {
			if (++eot_state >= SM_EOT_LEN)
				break;
		}
		else {
			eot_state = 0;
			if (c == eot[eot_state])
				++eot_state;
		}

#if 0
		if (ss_ctx->ssc_cnf.ss_cnf_debug > 1 && r > 0) {
			sm_snprintf(sbuf, sizeof(sbuf),
				"eot_state: %d, c=%c\n"
				, eot_state, isprint(c) ? c : '_');
			sm_io_write(smioerr, (uchar *)sbuf, strlen(sbuf), &b);
		}
#endif /* 0 */

	}
	SM_ASSERT(eot_state >= SM_EOT_LEN);	/* really?? */

	/* should we complain if skip is set?? */
	if (hops > ss_ctx->ssc_cnf.ss_cnf_maxhops) {
		sm_log_write(ss_ctx->ssc_lctx,
			SS_LCAT_SERVER, SS_LMOD_SERVER,
			SM_LOG_WARN, 8,
			"sev=WARN, func=ss_data, ss_sess=%s, ss_ta=%s, too_many_hops=%u, max=%d"
			, ss_sess->ssse_id, ss_ta->ssta_id
			, hops
			, ss_ctx->ssc_cnf.ss_cnf_maxhops);
		sm_str_scopy(ss_sess->ssse_wr, "554 5.4.6 Too many hops\r\n");
		ret = 554;
		goto error;
	}

#if SS_CHECK_LINE_LEN
	/* should we complain if skip is set?? */
	if (SSTA_IS_FLAG(ss_ta, SSTA_FL_LL_EXC)) {
		sm_log_write(ss_ctx->ssc_lctx,
			SS_LCAT_SERVER, SS_LMOD_SERVER,
			SM_LOG_WARN, 8,
			"sev=WARN, func=ss_data, ss_sess=%s, ss_ta=%s, line_length_exceeded=%u, max=%u"
			, ss_sess->ssse_id, ss_ta->ssta_id
			, line_len, ss_sess->ssse_max_line_len);
		sm_str_scopy(ss_sess->ssse_wr,
			"554 5.5.0 Some line in mail too long\r\n");
		ret = 554;
		goto error;
	}
#endif /* SS_CHECK_LINE_LEN */

	if (skip) {
		/* pmilter will not be contacted! */
		if (!SSTA_IS_FLAG(ss_ta, SSTA_FL_DISCARD))
			ret = 552;
		goto error;
	}

	/* write the current buffer */
	bytes2write = f_p(*ss_sess->ssse_fp) - bufp;
	ret = cdb_write(cdb_ctx, ss_ta->ssta_dfp, bufp, bytes2write, &byteswritten);
	if (sm_is_err(ret)) {
		sm_log_write(ss_ctx->ssc_lctx,
			SS_LCAT_SERVER, SS_LMOD_SERVER,
			SM_LOG_ERROR, 8,
			"sev=ERROR, func=ss_data, ss_sess=%s, ss_ta=%s, write_cdb=%m"
			, ss_sess->ssse_id, ss_ta->ssta_id, ret);
		(void) ss_crt_reply(ss_sess->ssse_wr, SMTP_R_TEMP,
				SS_PHASE_DOT, true);
		goto error;
	}

	/* "else" not needed because of "goto error" above */
	ss_ta->ssta_msg_sz_b += byteswritten;
	if (max_sz_kb > 0 && max_sz_kb < ss_ta->ssta_msg_sz_b / 1024) {
		sm_log_write(ss_ctx->ssc_lctx,
			SS_LCAT_SERVER, SS_LMOD_SERVER,
			SM_LOG_WARN, 8,
			"sev=WARN, func=ss_data, ss_sess=%s, ss_ta=%s, max_message_size=%lu, status=exceeded"
			, ss_sess->ssse_id, ss_ta->ssta_id
			, (ulong) max_sz_kb);
		sm_str_scopy(ss_sess->ssse_wr,
			"552 5.3.4 Maximum message size exceeded.\r\n");
		ret = 552;
		skip = true;
		goto error;
	}

#if MTA_USE_PMILTER
	ss_ta->ssta_rcpts_ok_orig = ss_ta->ssta_rcpts_ok;
	ss_ta->ssta_rcpts_tot_orig = ss_ta->ssta_rcpts_tot;
	ss_ta->ssta_hops = hops;
	ret = sspm_eob(ss_sess, bufp, bytes2write, &skip);
	if (sm_is_err(ret))
		goto error;
	if (IS_SMTP_REPLY(ret) && SMTP_IS_REPLY_ERROR(ret))
		goto error;

# if MTA_USE_RSAD
	if (ret != SMTP_R_D_EOM)
		SSTA_CLR_FLAG(ss_sess->ssse_ta, SSTA_FL_RSAD);
# endif

	if (SSC_IS_PMCAP(ss_ctx, SM_SCAP_PM_MSG_RC) &&
	    ss_ta->ssta_msg_acc.ssa_map_result == SM_ACC_FOUND &&
	    IS_SMTP_REPLY(ss_ta->ssta_msg_acc.ssa_reply_code) &&
	    SMTP_IS_REPLY_ERROR(ss_ta->ssta_msg_acc.ssa_reply_code))
	{
		if (ss_ta->ssta_msg_acc.ssa_reply_text != NULL) {
			sm_str_clr(ss_sess->ssse_wr);
			sm_str_cat(ss_sess->ssse_wr, ss_ta->ssta_msg_acc.ssa_reply_text);
		}
		ret = ss_ta->ssta_msg_acc.ssa_reply_code;
		goto error;
	}
	if (skip) {
		if (!SSTA_IS_FLAG(ss_ta, SSTA_FL_DISCARD))
			ret = 550;
		goto error;
	}
# if SM_TEST_HDRMOD
	{
		sm_hdrmod_P sm_hdrmod;
		extern sm_cstr_P Hdr_pre, Hdr_app;

		/* HACK FOR TESTING */
		sm_hdrmod = NULL;
		if (Hdr_pre != NULL || Hdr_app != NULL)
			ret = sm_hdrmod_new(&ss_ta->ssta_hdrmodhd, true, &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 = sm_hdrmod_new(&ss_ta->ssta_hdrmodhd, true, &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;
		}
		/* END HACK FOR TESTING */
	}
# endif /* SM_TEST_HDRMOD */

#endif /* MTA_USE_PMILTER */

	/* data MUST now be safely stored.... */
	if (ss_ta->ssta_dfp != NULL) {
		int flags;

		flags = SM_IO_CF_SYNC;
#if SS_TEST
		if (Unsafe > 0)
			flags = SM_IO_CF_NONE;
#endif
#if SS_TEST
		if (Unsafe <= 1) {
#endif
		ret = cdb_close(cdb_ctx, ss_ta->ssta_dfp, flags);
		if (sm_is_err(ret)) {
			sm_log_write(ss_ctx->ssc_lctx,
				SS_LCAT_SERVER, SS_LMOD_SERVER,
				SM_LOG_ERROR, 8,
				"sev=ERROR, func=ss_data, ss_sess=%s, ss_ta=%s, close_cdb=%m"
				, ss_sess->ssse_id, ss_ta->ssta_id, ret);
			(void) ss_crt_reply(ss_sess->ssse_wr, SMTP_R_TEMP,
					SS_PHASE_DOT, true);
			goto error;
		}
		ss_ta->ssta_dfp = NULL;
#if SS_TEST
		}
#endif
	}

#if MTA_USE_PMILTER && MTA_USE_RSAD
	if (SSTA_IS_FLAG(ss_sess->ssse_ta, SSTA_FL_RSAD)) {
		ret = ss_rsad_set_replies(ss_ctx, ss_sess, ss_ta);
		if (SMTP_R_SSD == ret)
			goto errorreply;

		/* overall status: error -> don't accept */
		if (ret != SMTP_OK) {
			(void) ss_rsad_send_replies(ss_ctx, ss_sess, ss_ta);
			goto errorreply;
		}
	}
#endif /* MTA_USE_PMILTER && MTA_USE_RSAD  */

	ret = sm_s2q_1taid(ss_sess, ss_ctx->ssc_s2q_ctx);
	if (sm_is_err(ret)) {
		/* change error */
		sm_log_write(ss_ctx->ssc_lctx,
			SS_LCAT_SERVER, SS_LMOD_SERVER,
			SM_LOG_ERROR, 8,
			"sev=ERROR, func=ss_data, ss_sess=%s, ss_ta=%s, sm_s2q_1taid=%m"
			, ss_sess->ssse_id, ss_ta->ssta_id, ret);
		ret = SMTP_R_SSD;
		(void) ss_crt_reply(ss_sess->ssse_wr, ret, SS_PHASE_DOT, true);
		goto error;
	}
	ret = sm_w4q2s_reply(ss_sess, TMO_W4Q2S, ss_ctx->ssc_s2q_ctx);
	if (sm_is_err(ret)) {
		/* change error */
		sm_log_write(ss_ctx->ssc_lctx,
			SS_LCAT_SERVER, SS_LMOD_SERVER,
			SM_LOG_ERROR, 8,
			"sev=ERROR, func=ss_data, ss_sess=%s, ss_ta=%s, where=ss_data, sm_w4q2s_reply=%m"
			, ss_sess->ssse_id, ss_ta->ssta_id, ret);
		ret = SMTP_R_SSD;
		(void) ss_crt_reply(ss_sess->ssse_wr, ret, SS_PHASE_DOT, true);
		goto error;
	}

#if MTA_USE_PMILTER && MTA_USE_RSAD
	if (SSTA_IS_FLAG(ss_sess->ssse_ta, SSTA_FL_RSAD))
		ret = ss_rsad_send_replies(ss_ctx, ss_sess, ss_ta);
#endif

	if (SM_SUCCESS == ret) {
		SS_DATA_GOT_IT(ss_sess, ss_ta);
	}
	else {
		(void) ss_crt_reply(ss_sess->ssse_wr, ret, SS_PHASE_DOT, false);
		goto error;
	}

	ss_ta->ssta_state = SSTA_ST_DOT;	/* NONE ? */
	sm_log_write(ss_ctx->ssc_lctx,
		SS_LCAT_SERVER, SS_LMOD_SERVER, SM_LOG_INFO, 3,
		"sev=INFO, ss_sess=%s, ss_ta=%s, msgid=%#N, size=%lu, stat=0"
		, ss_sess->ssse_id, ss_ta->ssta_id
		, ss_ta->ssta_msgid, (ulong) ss_ta->ssta_msg_sz_b);
	ret = SMTP_OK;

	/* clear transaction data */
	ss_ta_clr(ss_ta);
	ss_sess->ssse_nopcmds = 0;
	return ret;

  errorreply:
	(void) ss_crt_reply(ss_sess->ssse_wr, ret, SS_PHASE_DOT, true);
  error:
	/* Remove message */
	if (ss_ta->ssta_dfp != NULL) {
		res = cdb_abort(cdb_ctx, ss_ta->ssta_dfp);
		ss_ta->ssta_dfp = NULL;
		SSTA_CLR_FLAG(ss_ta, SSTA_FL_CDB_EXISTS);
	}
	else if (SSTA_IS_FLAG(ss_ta, SSTA_FL_CDB_EXISTS))
		res = cdb_unlink(cdb_ctx, ss_ta->ssta_id);
	else
		res = SM_SUCCESS;
	if (sm_is_err(res))
		sm_log_write(ss_ctx->ssc_lctx,
			SS_LCAT_SERVER, SS_LMOD_SERVER, SM_LOG_ERR, 4,
			"sev=ERROR, func=ss_data, ss_sess=%s, ss_ta=%s, cdb_%s=%m"
			, ss_sess->ssse_id, ss_ta->ssta_id
			, SSTA_IS_FLAG(ss_ta, SSTA_FL_CDB_EXISTS)
				? "unlink" : "abort"
			, res);

	if (SM_SUCCESS == ret) {
		/* pretend we got it: this works only for DISCARD !*/
		SM_ASSERT(SSTA_IS_FLAG(ss_ta, SSTA_FL_DISCARD));
		SS_DATA_GOT_IT(ss_sess, ss_ta);
	}
	(void) ss_ta_abort(ss_sess, ss_ta);

	/* returning an error will cause a session abort! */
	return ret;
}

/*
**  SS_TA_ZERO -- initialize transaction context (first time)
**	ss_ta is NOT allocated, it is a local variable in ss_hdl_session()
**
**	Parameters:
**		ss_ta -- SMTP server transaction context
**
**	Returns:
**		SM_SUCCESS
*/

static sm_ret_T
ss_ta_zero(ss_ta_P ss_ta)
{
	sm_memzero(ss_ta, sizeof(*ss_ta));
	return SM_SUCCESS;
}

/*
**  SS_HDL_SESSION -- Session handling function stub.
**
**	Parameters:
**		ss_sess -- SMTP server session context
**			must be freed after use.
**		sessok -- is it ok to accept the session?
**
**	Returns:
**		usual return code
*/

sm_ret_T
ss_hdl_session(ss_sess_P ss_sess, sm_ret_T sessok)
{
	int r;
	sm_ret_T ret;
	ssize_t b;
	bool dontlog;
	char *buf;
	ss_ta_T ss_ta;	/* should be malloc()ated? */
	ss_ctx_P ss_ctx;

	SM_IS_SS_SESS(ss_sess);
	dontlog = false;
	ss_ctx = ss_sess->ssse_sctx;
	SM_IS_SS_CTX(ss_ctx);
	ss_ta_zero(&ss_ta);

	/* don't accept at all? */
	if (SMTP_R_SSD == sessok) {
		(void) ss_crt_reply(ss_sess->ssse_wr, sessok, SS_PHASE_INIT, false);
		(void) ss_reply(ss_sess, ss_sess->ssse_wr, ss_sess->ssse_fp, -1, true);
		sm_log_write(ss_ctx->ssc_lctx,
			SS_LCAT_SERVER, SS_LMOD_SERVER,
			SM_LOG_INFO, 11,
			"sev=INFO, func=ss_hdl_session, ss_sess=%s, client_ipv4=%A, client_name=%C, stat=%d, text=%@T"
			, ss_sess->ssse_id, ss_sess->ssse_client.s_addr
			, ss_sess->ssse_cltname
			, sessok, ss_sess->ssse_wr);
		if (sm_log_wouldlog(ss_ctx->ssc_lctx, SS_LCAT_SERVER, SS_LMOD_SERVER, 11))
			SSSE_SET_FLAG(ss_sess, SSSE_FL_CONN_LOGGED);
		goto err_cseid;
	}

	sm_log_write(ss_ctx->ssc_lctx,
		SS_LCAT_SERVER, SS_LMOD_SERVER,
		SM_LOG_DEBUG, 13,
		"sev=DBG, func=ss_hdl_session, pid=%d, idx=%d, ss_sess=%s, hdl_session=%d"
		, (int) My_pid, ss_ctx->ssc_id, ss_sess->ssse_id, sessok);

	ret = ss_ta_clr(&ss_ta);
	if (sm_is_err(ret)) {
		sm_log_write(ss_ctx->ssc_lctx,
			SS_LCAT_SERVER, SS_LMOD_SERVER,
			SM_LOG_ERROR, 2,
			"sev=ERROR, func=ss_hdl_session, ss_sess=%s, ss_ta_clr=%m"
			, ss_sess->ssse_id, ret);
		(void) ss_crt_reply(ss_sess->ssse_wr, SMTP_R_SSD, SS_PHASE_INIT, false);
		(void) ss_reply(ss_sess, ss_sess->ssse_wr, ss_sess->ssse_fp, -1, true);
		goto err_no_ta;
	}
	ss_sess->ssse_state = SSSE_ST_CONNECTED;
	ss_sess->ssse_ta = &ss_ta;

	/* Check for "illegal" pipelining. NOT for smtp over SSL! */
	if (sm_io_getinfo(ss_sess->ssse_fp, SM_IO_IS_READABLE, NULL)) {
		bool iseof;

		/*
		**  RFC 2821 says only "SHOULD", not must wait for greeting.
		**  Hence this must be optional... (override per IP address?)
		*/

		iseof = false;
#ifdef FIONREAD
		/* alternative: try to read one byte, session terminates */
		r = 1;
		(void) ioctl(f_fd(*(ss_sess->ssse_fp)), FIONREAD, &r);
		iseof = (0 == r);
#endif
		sm_log_write(ss_ctx->ssc_lctx,
			SS_LCAT_SERVER, SS_LMOD_SERVER,
			SM_LOG_WARN, 3,
			"sev=WARN, func=ss_hdl_session, ss_sess=%s, client_ipv4=%A, status=%s_before_greeting"
			, ss_sess->ssse_id, ss_sess->ssse_client.s_addr
			, iseof ? "EOF" : "data");
		if (sm_log_wouldlog(ss_ctx->ssc_lctx, SS_LCAT_SERVER, SS_LMOD_SERVER, 3))
			SSSE_SET_FLAG(ss_sess, SSSE_FL_CONN_LOGGED);
		ret = sm_error_temp(SM_EM_SMTPS, SM_E_ILL_PIPE);
		dontlog = true;
		sm_str_clr(ss_sess->ssse_wr);
		if (!iseof && !SSSE_IS_CFLAG(ss_sess, SSSE_CFL_DATA_BEFORE_GREET))
		{
			sm_str_scopy(ss_sess->ssse_wr,
				"421 client SHOULD wait for greeting\r\n");
			goto errreply;
		}
		goto error;
	}

	/* create greeting */
	sm_str_scopy(ss_sess->ssse_wr, "220 ");
	sm_str_cat(ss_sess->ssse_wr, ss_ctx->ssc_hostname);
	sm_str_scat(ss_sess->ssse_wr, " ESMTP " MTA_GREETING "\r\n");
	ret = ss_reply(ss_sess, ss_sess->ssse_wr, ss_sess->ssse_fp, -1, true);
	if (ret != SMTP_OK) {
		sm_log_write(ss_ctx->ssc_lctx,
			SS_LCAT_SERVER, SS_LMOD_SERVER,
			SM_LOG_WARN, 9,
			"sev=WARN, func=ss_hdl_session, ss_sess=%s, client_ipv4=%A, write_greeting=%m"
			, ss_sess->ssse_id, ss_sess->ssse_client.s_addr, ret);
		if (sm_log_wouldlog(ss_ctx->ssc_lctx, SS_LCAT_SERVER,
					SS_LMOD_SERVER, 9))
			SSSE_SET_FLAG(ss_sess, SSSE_FL_CONN_LOGGED);
		goto error;
	}
	ss_sess->ssse_state = SSSE_ST_GREETED;

	if (!SSSE_IS_FLAG(ss_sess, SSSE_FL_CLIENT_RELAY)) {
		r = regexec(&ss_ctx->ssc_relayfrom, inet_ntoa(ss_sess->ssse_client),
			(size_t) 0, (regmatch_t *) NULL, 0);
		if (0 == r)
			SSSE_SET_FLAG(ss_sess, SSSE_FL_CLIENT_RELAY);
	}

	(void) ss_conn_log(ss_sess, 9, "ss_hdl_session");

	/*
	**  Note: the ss_*() commands must return SM_SUCCESS almost always;
	**  a (fatal) error must only be returned if the session should
	**  be terminated. Maybe the functions can return a warning.
	*/

	while ((ret = ss_read_cmd(ss_sess)) == SMTP_OK) {
		/*
		**  RFC 2821: 4.1.1 Command Semantics and Syntax
		**  SMTP commands are character strings terminated by
		**  <CRLF>.  The commands themselves are alphabetic characters
		**  terminated by <SP> if parameters follow and <CRLF>
		**  otherwise. (In the interest of improved interoperability,
		**  SMTP receivers are encouraged to tolerate trailing white
		**  space before the terminating <CRLF>.)
		*/

		r = sm_str_getlen(ss_sess->ssse_rd);
		if (0 == r) {
			if (sm_eof(ss_sess->ssse_fp)) {
				/* can't happen?? see ss_read_cmd() */
				sm_log_write(ss_ctx->ssc_lctx,
					SS_LCAT_SERVER, SS_LMOD_SERVER,
					SM_LOG_WARN, 6,
					"sev=WARN, func=ss_hdl_session, ss_sess=%s, client_ipv4=%A, status=EOF_without_QUIT"
					, ss_sess->ssse_id, ss_sess->ssse_client.s_addr);
				goto error;
			}
			switch (errno) {
			  case 0:
				break;
			  case EINTR:
				continue;
			  case ETIMEDOUT:
			  case EAGAIN:
			  default:
				sm_log_write(ss_ctx->ssc_lctx,
					SS_LCAT_SERVER, SS_LMOD_SERVER,
					SM_LOG_WARN, 2,
					"sev=WARN, func=ss_hdl_session, ss_sess=%s, client_ipv4=%A, status=read_error, error=%m"
					, ss_sess->ssse_id
					, ss_sess->ssse_client.s_addr
					, sm_err_temp(errno));
				if (sm_log_wouldlog(ss_ctx->ssc_lctx,
						SS_LCAT_SERVER, SS_LMOD_SERVER, 3))
					SSSE_SET_FLAG(ss_sess, SSSE_FL_CONN_LOGGED);
				goto error;
			}
		}
		(void) sm_str_rm_trail_sp(ss_sess->ssse_rd);
		buf = (char *)sm_str_getdata(ss_sess->ssse_rd);
		if (NULL == buf) {
			(void) ss_crt_reply(ss_sess->ssse_wr, SMTP_R_SSD,
					SS_PHASE_OTHER, false);
			(void) ss_reply(ss_sess, ss_sess->ssse_wr,
					ss_sess->ssse_fp, -1, true);
			goto errreply;
		}
		if (ss_ctx->ssc_cnf.ss_cnf_debug > 5)
			sm_io_write(smioerr, (uchar *)buf, r, &b);

		/*
		**  Try to find command and invoke corresponding function.
		**  Expected return codes:
		**  sm_is_err(): return an error to client and abort!
		**  SS_NO_REPLY: don't send a reply (only some commands)
		**  default: send reply stored in ss_sess->ssse_wr
		*/

		if (strcasecmp(buf, "quit") == 0) {
			sm_str_scopy(ss_sess->ssse_wr, "221 2.0.0 Bye\r\n");
			ss_sess->ssse_state = SSSE_ST_QUIT;
			(void) ss_ta_abort(ss_sess, &ss_ta);
		}
		else if (strncasecmp(buf, "helo", 4) == 0) {
			ret = ss_ehlo(ss_sess, false);
			if (sm_is_err(ret))
				goto errreply;
		}
		else if (strncasecmp(buf, "ehlo", 4) == 0) {
			ret = ss_ehlo(ss_sess, true);
			if (sm_is_err(ret))
				goto errreply;
		}
#if MTA_USE_TLS
		else if (strncasecmp(buf, "starttls", 8) == 0) {
			ret = ss_tls(ss_sess);
			if (sm_is_err(ret))
				goto errreply;
			if (SS_NO_REPLY == ret)
				continue;
		}
#endif /* MTA_USE_TLS */
#if MTA_USE_SASL
		else if (strncasecmp(buf, "auth ", 5) == 0) {
			ret = ss_auth(ss_sess);
			SS_CONN_LOG(ss_sess, 15, "ss_hdl_session");
			sm_log_write(ss_ctx->ssc_lctx,
				SS_LCAT_SERVER, SS_LMOD_SERVER,
				SM_LOG_INFO, 15,
				"sev=INFO, func=ss_hdl_session, ss_sess=%s, auth=%#x"
				, ss_sess->ssse_id, ret);
			if (sm_is_err(ret))
				goto errreply;
			if (SS_NO_REPLY == ret)
				continue;
		}
#endif /* MTA_USE_SASL */
		else if (strncasecmp(buf, "mail", 4) == 0) {
			ret = ss_mail(ss_sess);
			if (sm_is_err(ret))
				goto errreply;
		}
		else if (strncasecmp(buf, "rcpt", 4) == 0) {
			ret = ss_rcpt(ss_sess);
			if (sm_is_err(ret))
				goto errreply;
		}
		else if (strcasecmp(buf, "data") == 0) {
			ret = ss_data(ss_sess);
			if (sm_is_err(ret))
				goto errreply;
			TA_COUNT(ss_sess->ssse_idx)++;
		}
		else if (strcasecmp(buf, "rset") == 0) {
			ret = ss_rset(ss_sess);
			if (sm_is_err(ret))
				goto errreply;
		}
		else if (strncasecmp(buf, "expn", 4) == 0 ||
			 strncasecmp(buf, "vrfy", 4) == 0)
		{
			ret = ss_nopcmd(ss_sess, false, "502 5.7.0 Nope\r\n");
			if (sm_is_err(ret))
				goto errreply;
		}
		else if (strncasecmp(buf, "noop", 4) == 0) {
			ret = ss_nopcmd(ss_sess, false, SS_R_OK);
			if (sm_is_err(ret))
				goto errreply;
		}
		else if (strcasecmp(buf, "help") == 0) {
			sm_str_scopy(ss_sess->ssse_wr,
				"250 2.0.0 http://www.ietf.org/rfc/rfc2821.txt\r\n");
		}
		else if (strncasecmp(buf, "connect", 7) == 0 ||
			 strncasecmp(buf, "post", 4) == 0 ||
			 strncasecmp(buf, "user", 4) == 0 ||
			 strncasecmp(buf, "get", 3) == 0)
		{
			/* todo: more proxy commands? */
			sm_str_scopy(ss_sess->ssse_wr, "421 4.7.0 No HTTP\r\n");
			ss_sess->ssse_state = SSSE_ST_QUIT;
		}
		else {
			/* increment error counter */
			ret = ss_badcmd(ss_sess, false, "500 5.5.1 What?\r\n");
			if (sm_is_err(ret))
				goto errreply;

			/* maybe sleep... */
			st_sleep(1);
		}

#if 0
sm_log_write(ss_ctx->ssc_lctx, SS_LCAT_SERVER, SS_LMOD_SERVER, SM_LOG_INFO, 2,
"sev=INFO, func=ss_hdl_session, ss_sess=%s, reply=%S", ss_sess->ssse_id, ss_sess->ssse_wr);
#endif
		if (SMTP_R_SSD == ret)
			ss_sess->ssse_state = SSSE_ST_QUIT;

		if (ret != SMTP_NO_REPLY)
			ret = ss_reply(ss_sess, ss_sess->ssse_wr, ss_sess->ssse_fp, -1, false);
		else
			ret = SMTP_OK;
		sm_str_clr(ss_sess->ssse_wr);
		if (ret != SMTP_OK) {
			sm_log_write(ss_ctx->ssc_lctx,
				SS_LCAT_SERVER, SS_LMOD_SERVER,
				SM_LOG_WARN, 2,
				"sev=WARN, func=ss_hdl_session, ss_sess=%s, client_ipv4=%A, status=write_error, ret=%m"
				, ss_sess->ssse_id
				, ss_sess->ssse_client.s_addr, ret);
			if (sm_log_wouldlog(ss_ctx->ssc_lctx, SS_LCAT_SERVER,
					SS_LMOD_SERVER, 2))
				SSSE_SET_FLAG(ss_sess, SSSE_FL_CONN_LOGGED);
			goto error;
		}

		if (SSSE_ST_QUIT == ss_sess->ssse_state) {
			/* close transaction and session */
			RQST_COUNT(ss_sess->ssse_idx)++;
			goto done;
		}
	}
	if (SM_IO_EOF == ret) {
		sm_log_write(ss_ctx->ssc_lctx,
			SS_LCAT_SERVER, SS_LMOD_SERVER, SM_LOG_INFO, 11,
			"sev=INFO, func=ss_hdl_session, ss_sess=%s, client_ipv4=%A, status=EOF_without_QUIT, se_state=0x%x, ta_state=0x%x"
			, ss_sess->ssse_id, ss_sess->ssse_client.s_addr
			, ss_sess->ssse_state, ss_ta.ssta_state);
		sm_str_clr(ss_sess->ssse_wr);
		goto error;
	}
	if (sm_is_err(ret)) {
		sm_log_write(ss_ctx->ssc_lctx,
			SS_LCAT_SERVER, SS_LMOD_SERVER, SM_LOG_WARN, 2,
			"sev=WARN, func=ss_hdl_session, ss_sess=%s, client_ipv4=%A, status=read_error, ret=%m"
			, ss_sess->ssse_id, ss_sess->ssse_client.s_addr, ret);
		sm_str_clr(ss_sess->ssse_wr);
		goto error;
	}

  done:	/* same cleanup for now as in error */

  errreply:
	if (sm_str_getlen(ss_sess->ssse_wr) > 0)
		(void) ss_reply(ss_sess, ss_sess->ssse_wr, ss_sess->ssse_fp,
				dontlog ? 14 : -1, true);
  error:
	if (sm_is_err(ret) && (r = sm_str_getlen(ss_sess->ssse_wr)) > 0
	    && !dontlog)
	{
		sm_log_write(ss_ctx->ssc_lctx,
			SS_LCAT_SERVER, SS_LMOD_SERVER, SM_LOG_WARN, 8,
			"sev=WARN, func=ss_hdl_session, ss_sess=%s, client_ipv4=%A, status=%@T, ret=%m"
			, ss_sess->ssse_id, ss_sess->ssse_client.s_addr
			, ss_sess->ssse_wr, ret);
	}
	(void) ss_ta_abort(ss_sess, &ss_ta);
  err_no_ta:
	(void) sm_rcb_close_decn(ss_sess->ssse_rcb);
  err_cseid:
	ret = sm_s2q_cseid(ss_sess, ss_ctx->ssc_s2q_ctx, ss_sess->ssse_id);
	if (sm_is_err(ret)) {
		sm_log_write(ss_ctx->ssc_lctx,
			SS_LCAT_SERVER, SS_LMOD_SERVER, SM_LOG_WARN, 2,
			"sev=WARN, func=ss_hdl_session, ss_sess=%s, client_ipv4=%A, sm_s2q_cseid=%m"
			, ss_sess->ssse_id, ss_sess->ssse_client.s_addr, ret);
	}

#if MTA_USE_PMILTER
	if (SSSE_IS_FLAG(ss_sess, SSSE_FL_PM_CALLED)) {
		ret = sm_s2m_cseid(ss_sess, ss_ctx->ssc_s2m_ctx, ss_sess->ssse_id);
		if (sm_is_err(ret)) {
			sm_log_write(ss_ctx->ssc_lctx,
				SS_LCAT_SERVER, SS_LMOD_SERVER,
				SM_LOG_WARN, 5,
				"sev=WARN, func=ss_hdl_session, ss_sess=%s, client_ipv4=%A, sm_s2m_cseid=%m"
				, ss_sess->ssse_id, ss_sess->ssse_client.s_addr, ret);
		}
	}
#endif /* MTA_USE_PMILTER */

	sm_log_write(ss_ctx->ssc_lctx,
		SS_LCAT_SERVER, SS_LMOD_SERVER,
		SM_LOG_DEBUG, 13,
		"sev=DBG, func=ss_hdl_session, ss_sess=%s, status=closing"
		, ss_sess->ssse_id);
	ss_sess_free(ss_sess);

	/* always success... */
	return SM_SUCCESS;
}


syntax highlighted by Code2HTML, v. 0.9.1