/*
 * 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.
 */

#define MTA_USE_STATETHREADS 1	/* -> Makefile? */

#include "sm/generic.h"
SM_RCSID("@(#)$Id: smtps.c,v 1.440 2007/10/17 02:54:26 ca Exp $")

#include "sm/common.h"
#include "sm/assert.h"
#include "sm/error.h"
#include "sm/ctype.h"
#include "sm/memops.h"
#include "sm/io.h"
#include "sm/str.h"
#include "sm/string.h"
#include "sm/limits.h"
#include "sm/types.h"
#include "sm/net.h"
#include "sm/wait.h"
#include "sm/fcntl.h"
#include "sm/signal.h"
#include "sm/pwd.h"
#include "sm/grp.h"
#include "sm/cdb.h"
#include "statethreads/st.h"
#include "sm/stsock.h"
#include "sm/cmsg.h"
#include "sm/hostname.h"
#include "sm/misc.h"
#include "sm/sysexits.h"
#include "sm/util.h"
#include "sm/units.h"
#include "sm/tls.h"
#include "sm/tlsbio.h"
#include "sm/sasl.h"

#include "s2q.h"
#include "smtps.h"
#define SMTPSRV_DEFINE	1
#include "smtpsrv.h"
#include "s2m.h"
#define SMTPS_LOG_DEFINES	1
#include "sm/smar.h"
#include "log.h"
#include "sm/resource.h"
#include "sm/version.h"
#include "sm/sm-conf.h"
#include "sm/sm-conf-prt.h"
#include "sm/smtpdef.h"
#include "sm/confsetpath.h"
#include "smtpsh.h"
#include "pmilter.h"

#if SMTPS_WITH_C
#include "smtpc/smtpc.h"

#include "sm/da.h"
#include "sm/das.h"
#include "smtpc/c2q.h"
#define SMTPC_DEFINE 1
#include "smtpc/smtpch.h"
#define SMTPC_LOG_DEFINES 1
#include "smtpc/log.h"
#include "sm/sccnfdef.h"
#include "sm/confsetpath.h"

#endif

#if SM_HEAP_CHECK
extern SM_DEBUG_T SmHeapCheck;
# define HEAP_CHECK (SmHeapCheck > 0)
#else
# define HEAP_CHECK 0
#endif

extern sm_stream_T SmStThrNetIO;

/*
**  Some comments about things to do/change/...

The sequence:

ret = sm_s2q_*()
if (sm_is_err(ret)) goto error;
ret = sm_w4q2s_reply(ss_sess);

can be merged into a single call if we always wait directly
for a response from the QMGR after we sent some request.
However, note the comment about PIPELINING in the smX docs;
we may want to hide the latency of address (anti-spam) checks.

*/

/*
**  Server configuration parameters
*/

/* Default server port */
#define SERV_PORT_DEFAULT 25

/* Max number of "spare" threads per process per socket */
#define MAX_WAIT_THREADS_DEFAULT 8

/*
**  Number of file descriptors needed to handle one client session
**  1 client communication
**  1 data file
*/

#define SS_FD_PER_THREAD 2

/*
**  Global data
**	Put this into server context instead (-> smtps.h)?
*/

int Sk_count = 0;	/* Number of listening sockets */
static st_netfd_t Ssfd = NULL;  /* Socket for SMTPS (one session if set) */
static pid_t *Vp_pids;	/* Array of VP pids */
pid_t My_pid = -1;	/* Current process pid */

st_netfd_t Sig_pipe[2];  /* Signal pipe */

/*
**  Configuration flags/parameters
*/

sm_cstr_P HostnameNone, HostnameNoMatch, HostnameTempPTR, HostnameTempA,
	HostnameBogus;

/* see below: sm_log_setfp_fd(..., Errfp, SMIOOUT_FILENO); */
static sm_file_T	*Errfp = smiolog;
#if SS_TEST
int Unsafe = 0;
static in_addr_T Test_client_ip;
#endif

/* should be in some "global" context */
ss_ctx_T	Ss_ctx;

/*
**  Thread throttling parameters (all numbers are per listening socket).
**  Zero values mean use default.
*/

/* todo: put this into a SMTP context? */
uint Max_cur_threads = 0;   /* current max number of threads */

bool Rpools = false;       /* use rpools... */

/*
**  Forward declarations
*/

static void	 ss_usage(const char *_progname);
static void	 ss_parse_arguments(ss_ctx_P _ss_ctx, int _argc, char *_argv[]);
#if 0
static void	 ss_start_daemon(void);
#endif
static void	 ss_set_thread_throttling(ss_ctx_P _ss_ctx);
static void	 ss_create_listeners(ss_ctx_P _ss_ctx);
#if 0
static void	 ss_open_log_files(void);
#endif
static void	 ss_start_processes(ss_ctx_P _ss_ctx);
static void	 ss_install_sighandlers(void);
void		 ss_sighdl_err(int _err, void *_ctx);
static void	 ss_start_threads(ss_ctx_P _ss_ctx);
static void	 ss_process_signals(ss_ctx_P _ss_ctx, bool _children);
static void	*ss_handle_connections(void *arg);
static void	 ss_dump_info(ss_ctx_P _ss_ctx);

static int	 ss_one_session(st_netfd_t _ssfd, ss_ctx_P _ss_ctx);

static sm_ret_T	 ss_init0(ss_ctx_P _ss_ctx);
static sm_ret_T	 ss_init_chk(ss_ctx_P _ss_ctx);
static sm_ret_T	 ss_init1(ss_ctx_P _ss_ctx);
static sm_ret_T	 ss_initp(ss_ctx_P _ss_ctx);

#if 0
static void	 ss_load_configs(ss_ctx_P _ss_ctx);
#endif

/*
**  SMTP server
**
**  Note: the functionality described in this paragraph has been disabled.
**  This server creates a constant number of processes ("virtual processors"
**  or VPs) and replaces them when they die. Each virtual processor manages
**  its own independent set of state threads (STs), the number of which varies
**  with load against the server. Each state thread listens to exactly one
**  listening socket. The initial process becomes the watchdog, waiting for
**  children (VPs) to die or for a signal requesting termination or restart.
**  Upon receiving a restart signal (SIGHUP), all VPs close and then reopen
**  log files and reload configuration. All currently active connections remain
**  active. It is assumed that new configuration affects only request
**  processing and not the general server parameters such as number of VPs,
**  thread limits, bind addresses, etc. Those are specified as command line
**  arguments, so the server has to be stopped and then started again in order
**  to change them.
**
**  Each state thread loops processing connections from a single listening
**  socket. Only one ST runs on a VP at a time, and VPs do not share memory,
**  so no mutual exclusion locking is necessary on any data, and the entire
**  server is free to use all the static variables and non-reentrant library
**  functions it wants, greatly simplifying programming and debugging and
**  increasing performance (for example, it is safe to ++ and -- all global
**  counters or call inet_ntoa(3) without any mutexes). The current thread on
**  each VP maintains equilibrium on that VP, starting a new thread or
**  terminating itself if the number of spare threads exceeds the lower or
**  upper limit.
**
**  All I/O operations on sockets must use the State Thread library's I/O
**  functions because only those functions prevent blocking of the entire VP
**  process and perform state thread scheduling.
*/

/*
**  Fatal error unrelated to a system call.
**  Print a message and terminate.
*/

static void
ss_err_quit(int ex_code, const char *fmt,...)
{
	va_list ap;

	va_start(ap, fmt);
	if (Ss_ctx.ssc_lctx == NULL) {
		sm_io_vfprintf(smioerr, fmt, ap);
		sm_io_putc(smioerr, '\n');
	}
	else {
		sm_log_vwrite(Ss_ctx.ssc_lctx,
			SS_LCAT_SERVER, SS_LMOD_SERVER,
			SM_LOG_ERROR, 1,
			fmt, ap);
	}
	va_end(ap);
	exit(ex_code);
	/* NOTREACHED */
}

/*
**  MAIN -- SMTPS main
**
**	Parameters:
**		argc -- number of arguments
**		argv -- vector of arguments
**
**	Returns:
**		exit code
*/

int
main(int argc, char *argv[])
{
	sm_ret_T ret;
	size_t size;
	char *prg;

	prg = argv[0];
	sm_memzero(&Ss_ctx, sizeof(Ss_ctx));
	if (getuid() == 0 || geteuid() == 0) {
		ss_err_quit(EX_USAGE, SM_DONTRUNASROOT, prg);
		/* NOTREACHED */
		return EX_USAGE;
	}

#if SS_CTX_CHECK
	Ss_ctx.sm_magic = SM_SS_CTX_MAGIC;
#endif
	Ss_ctx.ssc_cnf.sm_magic = SM_SS_CNF_MAGIC;
	ret = ss_init0(&Ss_ctx);
	if (sm_is_err(ret)) {
		/* XXX How to properly map an error code to an exit code? */
		ss_err_quit(sm_is_perm_err(ret) ? EX_SOFTWARE : EX_OSERR,
			"sev=ERROR, func=main, initialization=failed, ss_init0=%m"
			, ret);
	}

	/* Parse command-line options */
	ss_parse_arguments(&Ss_ctx, argc, argv);

	if (Ss_ctx.ssc_cnf.ss_cnf_debug > 4)
		sm_log_write(Ss_ctx.ssc_lctx,
			SS_LCAT_SERVER, SS_LMOD_SERVER,
			SM_LOG_INFO, 10,
			"sev=INFO, func=main, uid=%ld, gid=%ld, euid=%ld, egid=%ld\n"
			, (long) getuid(), (long) getgid(), (long) geteuid()
			, (long) getegid());

	ret = ss_init_chk(&Ss_ctx);
	if (sm_is_err(ret)) {
		/* XXX How to properly map an error code to an exit code? */
		ss_err_quit(sm_is_perm_err(ret) ? EX_SOFTWARE : EX_OSERR,
			"sev=ERROR, func=main, initialization=failed, ss_init_chk=%m"
			, ret);
	}

	/* Allocate array of server pids */
	size = Ss_ctx.ssc_cnf.ss_cnf_vp_count * sizeof(*Vp_pids);
	Vp_pids = sm_zalloc(size);
	if (NULL == Vp_pids)
		ss_err_quit(EX_TEMPFAIL,
			"sev=ERROR, func=main, malloc=failed, size=%lu",
			(ulong) size);

#if 0
	/* Start the daemon */
	if (SSC_IS_CFLAG(&Ss_ctx, SSC_CFL_BACKGROUND))
		ss_start_daemon();
#endif /* 0 */

	/* Initialize the ST library */
	if (st_init() < 0)
		ss_err_quit(sm_is_perm_err(ret) ? EX_SOFTWARE : EX_OSERR,
			"sev=ERROR, func=main, initialization=failed, st_init=failed");

	/* Set thread throttling parameters */
	ss_set_thread_throttling(&Ss_ctx);

	ret = ss_init1(&Ss_ctx);
	if (sm_is_err(ret))
		ss_err_quit(sm_is_perm_err(ret) ? EX_SOFTWARE : EX_OSERR,
			"sev=ERROR, func=main, initialization=failed, init=%m"
			, ret);

	/* Create listening sockets */
	if (NULL == Ssfd)
		ss_create_listeners(&Ss_ctx);

#if 0
	/* Open log files */
	ss_open_log_files();
#endif /* 0 */

	/* Start server processes (VPs) */
	ss_start_processes(&Ss_ctx);

	ret = ss_initp(&Ss_ctx);
	if (sm_is_err(ret)) {
		int rc;

		if (SSC_IS_FLAG(&Ss_ctx, SSC_FL_RESTARTDEP))
			rc = EX_RESTARTDEP;
		else
			rc = sm_is_perm_err(ret) ? EX_SOFTWARE : EX_OSERR;
		ss_err_quit(rc,
			"sev=ERROR, func=main, initialization=failed, initp=%M"
			, ret);
	}

	/* Turn time caching on */
	st_timecache_set(1);

	/* Install signal handlers */
	ss_install_sighandlers();

#if 0
	/* Load configuration from config files */
	ss_load_configs(&Ss_ctx);
#endif

	if (Ssfd != NULL)
		return ss_one_session(Ssfd, &Ss_ctx);

	/* Start all threads */
	ss_start_threads(&Ss_ctx);

	/* Become a signal processing thread */
	ss_process_signals(&Ss_ctx, false);

	/* NOTREACHED */
	return 1;
}

/*
**  SS_USAGE  -- print usage
**
**	Parameters:
**		progname -- name of program
**
**	Returns:
**		exit(EX_USAGE)
*/

static void
ss_usage(const char *progname)
{
	sm_io_fprintf(smioerr, "Usage: %s [options]\n"
		"%s SMTP server\n"
		"Possible options:\n"
		"-1                  Serialize all accept() calls\n"
		"-8                  Offer 8BITMIME\n"
		"-a                  Use access map (via SMAR)\n"
		"-b host:port        Bind to specified address; multiple addresses\n"
		"                    are permitted\n"
		"-C regex            client IP addresses from which relaying is allowed\n"
		"                    [%s]\n"
#if SS_TEST
		"-c ipaddr           set fixed client IP address (for testing!)\n"
#endif
		"-D                  Run in background\n"
		"-d n                Set debug level\n"
		"-F n                Set configuration flags\n"
		"-f name             Name of configuration file\n"
		"-g n                Set group id (numeric) for CDB\n"
		"-h                  Print this message\n"
#if SM_HEAP_CHECK
		"-H n                Set heap check level\n"
#endif
		"-I n                Set server id [0]\n"
		"-i                  Run in interactive mode (default)\n"
		"-L socket           Socket over which to receive listen fd\n"
#if SS_LOGDIR
		"-l socket           Directory for logging\n"
#endif
		"-N name             Name of smtps section in configuration file to use\n"
		"-O timeout          I/O timeout [%d]\n"
		"-p num_processes    Create specified number of processes\n"
		"-q backlog          Set max length of pending connections queue\n"
		"-s                  Perform one SMTP session over stdin/stdout\n"
		"-S a=name           Socket for communication with SMAR [%s]\n"
		"-S C=name           Directory for CDB [%s]\n"
		"-S q=name           Socket for communication with QMGR [%s]\n"
		"-t min_thr:max_thr  Specify thread limits per listening\n"
		"-T regex            Recipient addresses to which relaying is allowed\n"
		"                    (localhost is replaced by hostname if possible)\n"
		"                    [%s]\n"
		"-v loglevel         Set loglevel\n"
		"-V                  Show version\n"
		"-w timeout          Time to wait for QMGR to be ready\n"
		, progname
		, MTA_VERSION_STR
		, RELAY_CLT	/* -C */
		, SS_IO_TIMEOUT	/* -O */
		, Ss_ctx.ssc_cnf.ss_cnf_smarsock
		, Ss_ctx.ssc_cnf.ss_cnf_cdb_base
		, Ss_ctx.ssc_cnf.ss_cnf_smtpssock
		, RELAY_RCPT	/* -T */
		);
	exit(EX_USAGE);
	/* NOTREACHED */
}

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

static sm_ret_T
ss_showversion(ss_ctx_P ss_ctx, const char *progname, const char *cnf, uint level)
{
	char *prefix;

	prefix = "";
	if (level >= SM_SV_RD_CONF)
		prefix = "# ";
	sm_io_fprintf(smioout, "%s%s: %s\n", prefix, progname, MTA_VERSION_STR);
	if (SM_SHOW_VERSION(level))
		sm_io_fprintf(smioout, "%sversion=0x%08x\n",
			prefix, MTA_VERSION);
#if MTA_USE_TLS
	if (SM_SHOW_LIBS(level))
		(void) sm_tlsversionprt(smioout);
#endif
#if MTA_USE_SASL
	if (SM_SHOW_LIBS(level))
		(void) sm_saslversionprt(smioout);
#endif
	if (SM_SHOW_OPTIONS(level)) {
		sm_io_fprintf(smioout, "compile time options:\n"
#if MTA_USE_TLS
			"MTA_USE_TLS\n"
#endif
#if MTA_USE_SASL
			"MTA_USE_SASL\n"
#endif
#if MTA_USE_PMILTER
			"MTA_USE_PMILTER\n"
#endif
#if SS_CHECK_LINE_LEN
			/* untested feature */
			"SS_CHECK_LINE_LEN\n"
#endif
#if SS_EHLO_ACCESS_CHK
			"SS_EHLO_ACCESS_CHK\n"
#endif
#if MTA_USE_RSAD
			"MTA_USE_RSAD\n"
#endif
#if SS_TEST
			"SS_TEST\n"
#endif
			);
	}
	if (SM_SHOW_RD_CONF(level) && cnf != NULL && *cnf != '\0') {
		sm_io_fprintf(smioout,
			"# configuration-file=%s\n\n", cnf);
		(void) sm_conf_prt_conf(ss_global_defs,
				&ss_ctx->ssc_cnf, smioout);
	}
	else if (SM_SHOW_DFLT_CONF(level)) {
		sm_io_fprintf(smioout, "# default configuration:\n\n");
		(void) sm_conf_prt_dflt(ss_global_defs, 0, smioout);
	}
	else if (SM_SHOW_ALL_CONF(level)) {
		sm_io_fprintf(smioout,
			"# configuration including flags.\n"
			"# note: this data cannot be used as configuration filen\n"
			"# but it shows the available flags.\n"
			);
		(void) sm_conf_prt_dflt(ss_global_defs, SMC_FLD_FLAGS, smioout);
	}
	sm_io_flush(smioout);
	return sm_error_perm(SM_EM_SMTPS, SM_E_DONTSTART);
}

/*
**  SS_RELAY_TO -- parse relay-to regex
**
**	Parameters:
**		ss_ctx -- SMTP Server context
**		str -- string representation of regex
**
**	Returns:
**		usual error code
*/

sm_ret_T
ss_relay_to(ss_ctx_P ss_ctx, const char *str)
{
	int r;

	if (SSC_IS_FLAG(ss_ctx, SSC_FL_RELAYTO))
		regfree(&ss_ctx->ssc_relayto);
	r = regcomp(&ss_ctx->ssc_relayto, str,
		REG_EXTENDED|REG_ICASE|REG_NOSUB);
	if (0 == r)
		SSC_SET_FLAG(ss_ctx, SSC_FL_RELAYTO);
	return r;
}


/*
**  SS_RELAY_FROM -- parse relay-from regex
**
**	Parameters:
**		ss_ctx -- SMTP Server context
**		str -- string representation of regex
**
**	Returns:
**		usual error code
*/

sm_ret_T
ss_relay_from(ss_ctx_P ss_ctx, const char *str)
{
	int r;

	if (SSC_IS_FLAG(ss_ctx, SSC_FL_RELAYFROM))
		regfree(&ss_ctx->ssc_relayfrom);
	r = regcomp(&ss_ctx->ssc_relayfrom, str,
		REG_EXTENDED|REG_ICASE|REG_NOSUB);
	if (0 == r)
		SSC_SET_FLAG(ss_ctx, SSC_FL_RELAYFROM);
	return r;
}

/*
**  SS_PARSE_ARGUMENTS -- parse arguments
**
**	Parameters:
**		ss_ctx -- SMTP Server context
**		argc -- number of arguments
**		argv -- vector of arguments
**
**	Returns:
**		nothing.
**
**	Side Effects: may invoke exit()
*/

#if SM_TEST_HDRMOD
sm_cstr_P Hdr_pre = NULL;
sm_cstr_P Hdr_app = NULL;
#endif /* SM_TEST_HDRMOD */

static void
ss_parse_arguments(ss_ctx_P ss_ctx, int argc, char *argv[])
{
	extern char *optarg;
	int opt, r;
	uint u, showversion;
	char *c;
#if SM_TEST_HDRMOD
	char hdr[256];
#endif

	showversion = 0;
	while ((opt = getopt(argc, argv,
		"18ab:C:c:Dd:F:f:g:H:hI:il:L:M:N:o:O:p:q:rsS:t:T:U:v:Vw:")) != -1)
	{
		switch (opt) {
		  case '1':
			/*
			**  Serialization decision is tricky on some platforms.
			**  For example, Solaris 2.6 and above has kernel
			**  sockets implementation, so supposedly there is no
			**  need for serialization. The ST library may be
			**  compiled on one OS version, but used on another,
			**  so the need for serialization should be determined
			**  at run time by the application. Since it's just
			**  an example, the serialization decision is left up
			**  to user. Only on platforms where the serialization
			**  is never needed on any OS version
			**  st_netfd_serialize_accept() is a no-op.
			*/

			SSC_SET_CFLAG(ss_ctx, SSC_CFL_SERIAL_ACC);
			break;
		  case '8':
			SSC_SET_CFLAG(ss_ctx, SSC_CFL_8BITMIME);
			break;
		  case 'a':
			SSC_SET_CFLAG(ss_ctx, SSC_CFL_ACCESS_DB);
			break;
		  case 'b':
			if (Sk_count >= SS_MAX_BIND_ADDRS)
				ss_err_quit(EX_USAGE,
					"sev=ERROR, func=ss_parse_arguments, max_number_of_bind_addresses=%d, status=limit_exceeded"
					, SS_MAX_BIND_ADDRS);
			if ((c = strdup(optarg)) == NULL)
				ss_err_quit(EX_TEMPFAIL,
					"sev=ERROR, func=ss_parse_arguments, argument=\"%@s\", strdup=%m"
					, optarg, sm_err_temp(errno));
			ss_sck_ctx[Sk_count++].sssc_addr = c;
			break;
		  case 'C':	/* Client from which we allow relaying */
			r = ss_relay_from(ss_ctx, optarg);
			if (r != 0)
				ss_err_quit(EX_OSERR,
					"sev=ERROR, func=ss_parse_arguments, args=\"%s\", recomp=%d"
					, optarg, r);
			break;
#if SS_TEST
		  case 'c':	/* client IP address (testing only!) */
			(void) sm_inet_a2ipv4(optarg, NULL, &Test_client_ip.s_addr);
			break;
#endif
		  case 'D':
			SSC_SET_CFLAG(ss_ctx, SSC_CFL_BACKGROUND);
			break;
		  case 'd':
			ss_ctx->ssc_cnf.ss_cnf_debug = (uint) atoi(optarg);
			break;
		  case 'F':
			ss_ctx->ssc_cnf.ss_cnf_cflags = (uint32_t) strtoul(optarg, NULL, 0);
			break;
		  case 'f':
			c = strdup(optarg);
			if (NULL == c) {
				ss_err_quit(EX_OSERR,
					"sev=ERROR, func=ss_parse_arguments, argument=\"%@s\", strdup=%m"
					, optarg, sm_err_temp(errno));
			}
			ss_ctx->ssc_cnf.ss_cnf_conffile= c;
			r = ss_read_cnf(ss_ctx, c, &ss_ctx->ssc_cnf.ss_cnf_smc);
			if (r != 0)
				ss_err_quit(EX_USAGE,
					"sev=ERROR, func=ss_parse_arguments, status=invalid_configuration_file, error=%m"
					, r);
			break;

		  case 'g':
			ss_ctx->ssc_cnf.ss_cnf_cdb_gid = (gid_t) atoi(optarg);

			/* todo: more checks, e.g., gid in getgrouplist()? */
			if (ss_ctx->ssc_cnf.ss_cnf_cdb_gid <= 0) {
				ss_err_quit(EX_USAGE,
					"sev=ERROR, func=ss_parse_arguments, gid=%d, status=must_be_greater_than_zero"
					, ss_ctx->ssc_cnf.ss_cnf_cdb_gid);
			}
			break;
		  case 'H':
#if SM_HEAP_CHECK
			SmHeapCheck = atoi(optarg);
			if (SmHeapCheck < 0)
				ss_err_quit(EX_USAGE,
					"sev=ERROR, func=ss_parse_arguments, SmHeapCheck=\"%s\", status=invalid_value"
					, optarg);
#endif /* SM_HEAP_CHECK */
			break;
		  case 'I':
			ss_ctx->ssc_cnf.ss_cnf_id_base = atoi(optarg);
			break;
		  case 'i':
			SSC_CLR_CFLAG(ss_ctx, SSC_CFL_BACKGROUND);
			break;
		  case 'l':
#if SS_LOGDIR
			c = strdup(optarg);
			if (NULL == c)
				ss_err_quit(EX_TEMPFAIL,
					"sev=ERROR, func=ss_parse_arguments, argument=\"%@s\", strdup=%m"
					, optarg, sm_err_temp(errno));
			ss_ctx->ssc_cnf.ss_cnf_logdir = c;
#endif /* SS_LOGDIR */
			break;
		  case 'L':
			c = strdup(optarg);
			if (NULL == c)
				ss_err_quit(EX_TEMPFAIL,
					"sev=ERROR, func=ss_parse_arguments, argument=\"%@s\", strdup=%m"
					, optarg, sm_err_temp(errno));
			ss_ctx->ssc_cnf.ss_cnf_fd_socket = c;
			break;

		  case 'M':
#if SM_TEST_HDRMOD
			if (NULL == optarg ||
			    (optarg[0] != 'p' && optarg[0] != 'a') || optarg[1] != '=')
			{
				ss_err_quit(EX_USAGE,
					"sev=ERROR, func=ss_parse_arguments, -M wrong: %s"
					, optarg);
				break;
			}

			strlcpy(hdr, optarg + 2, sizeof(hdr));
			strlcat(hdr, "\r\n", sizeof(hdr));
			if (optarg[0] == 'p')
				Hdr_pre = sm_cstr_scpyn((const uchar *)hdr,
							strlen(hdr));
			else
				Hdr_app = sm_cstr_scpyn((const uchar *)hdr,
							strlen(hdr));
#else /* SM_TEST_HDRMOD */
			sm_io_fprintf(smioerr,
				"status=option %c not available, "
				"fix=compile with -DSM_TEST_HDRMOD\n"
				, opt);
#endif /* SM_TEST_HDRMOD */
			break;

		  case 'N':
			if (ss_ctx->ssc_cnf.ss_cnf_conffile != NULL)
				ss_err_quit(EX_USAGE,
					"sev=ERROR, func=ss_parse_arguments, -N must be before -f");
			c = strdup(optarg);
			if (NULL == c) {
				ss_err_quit(EX_OSERR,
					"sev=ERROR, func=ss_parse_arguments, argument=\"%@s\", strdup=%m"
					, optarg, sm_err_temp(errno));
			}
			ss_ctx->ssc_cnf.ss_cnf_section = c;
			break;

		  case 'o':
			if (optarg != NULL && ISALPHA(optarg[0]) && optarg[1] == '=')
				r = 2;
			else {
				ss_err_quit(EX_USAGE,
					"sev=ERROR, func=ss_parse_arguments, option=\"%@s\", status=illegal_syntax"
					, optarg);
				break;
			}

			u = (uint) atoi(optarg + r);
			if (u > 1) {
				switch (optarg[0]) {
				  case 'T':
					ss_ctx->ssc_cnf.ss_cnf_t_tot_lim = u;
					break;
				  case 'R':
					ss_ctx->ssc_cnf.ss_cnf_r_tot_lim = u;
					break;
				  case 'r':
					ss_ctx->ssc_cnf.ss_cnf_r_ta_lim = u;
					break;
#if 0
				  case 'o':
					ss_ctx->ssc_r_ok_lim = u;
					break;
#endif /* 0 */
				  default:
					break;
				}
			}
			else
				ss_err_quit(EX_USAGE,
					"sev=ERROR, func=ss_parse_arguments, option=\"%@s\", status=invalid_value"
					, optarg);
			break;
		  case 'O':
			r = atoi(optarg);
			if (r <= 0)
				ss_err_quit(EX_USAGE,
					"sev=ERROR, func=ss_parse_arguments, timeout=\"%@s\", status=invalid_value"
					, optarg);
			ss_ctx->ssc_cnf.ss_cnf_timeout = (sm_intvl_T) r;
			break;
		  case 'p':
			ss_ctx->ssc_cnf.ss_cnf_vp_count = atoi(optarg);
			if (ss_ctx->ssc_cnf.ss_cnf_vp_count < 1)
				ss_err_quit(EX_USAGE,
					"sev=ERROR, func=ss_parse_arguments, processes=\"%@s\", status=invalid_number",
					optarg);
			break;

		  case 'r':
			Rpools = true;
			break;
		  case 's': {
			int newfd;

			/* use macro?? */
			newfd = open("/dev/null", O_RDWR);
			if (newfd < 0)
				ss_err_quit(EX_OSERR,
					"sev=ERROR, func=ss_parse_arguments, open(/dev/null)=failed, errno=%d"
					, errno);
			newfd = dup2(STDIN_FILENO, newfd);
			if (newfd < 0)
				ss_err_quit(EX_OSERR,
					"sev=ERROR, func=ss_parse_arguments, dup2(STDIN)=failed, errno=%d"
					, errno);
			newfd = dup2(STDOUT_FILENO, newfd);
			if (newfd < 0)
				ss_err_quit(EX_OSERR,
					"sev=ERROR, func=ss_parse_arguments, dup2(STDOUT)=failed, errno=%d"
					, errno);
			Ssfd = st_netfd_open(newfd);
			if (NULL == Ssfd)
				ss_err_quit(EX_OSERR,
					"sev=ERROR, func=ss_parse_arguments, st_netfd_open()=failed, errno=%d"
					, errno);
			ss_ctx->ssc_cnf.ss_cnf_max_threads = 1;
			SSC_CLR_CFLAG(ss_ctx, SSC_CFL_BACKGROUND);
			}
			break;

		  case 'S':
			if (optarg != NULL && ISALPHA(optarg[0])
			    && optarg[1] == '=' && optarg[2] != '\0')
				r = 2;
			else {
				ss_usage(argv[0]);
				/* NOTREACHED */
				break;	/* shut up stupid compiler */
			}
			c = NULL;
			switch (optarg[0]) {
			  case 'a':
			  case 'C':
			  case 'q':
				c = strdup(optarg + r);
				if (NULL == c) {
					ss_err_quit(EX_OSERR,
						"sev=ERROR, func=ss_parse_arguments, argument=\"%@s\", strdup=failed, errno=%d"
						, optarg + r, errno);
				}
				break;
			  default:
				ss_usage(argv[0]);
				break;
			}
			switch (optarg[0]) {
			  case 'a':
				ss_ctx->ssc_cnf.ss_cnf_smarsock = c;
				break;
			  case 'C':
				ss_ctx->ssc_cnf.ss_cnf_cdb_base = c;
				break;
			  case 'q':
				ss_ctx->ssc_cnf.ss_cnf_smtpssock = c;
				break;
			}
			c = NULL;
			break;

		  case 't':
			ss_ctx->ssc_cnf.ss_cnf_max_wait_threads =
				(int) strtol(optarg, &c, 10);
			if (*c++ == ':')
				ss_ctx->ssc_cnf.ss_cnf_max_threads = atoi(c);
			if (ss_ctx->ssc_cnf.ss_cnf_max_wait_threads <= 0
			    || ss_ctx->ssc_cnf.ss_cnf_max_threads <= 0
			    || ss_ctx->ssc_cnf.ss_cnf_max_wait_threads >
					ss_ctx->ssc_cnf.ss_cnf_max_threads)
				ss_err_quit(EX_USAGE,
					"sev=ERROR, func=ss_parse_arguments, threads=\"%@s\", status=invalid_value"
					, optarg);
			break;
		  case 'U':
#if SS_TEST
			Unsafe = atoi(optarg);
#endif
			break;
		  case 'q':
			r = atoi(optarg);
			if (r < 1)
				ss_err_quit(EX_USAGE,
					"sev=ERROR, func=ss_parse_arguments, listen_queue_size=\"%@s\", status=invalid_value"
					, optarg);
			ss_ctx->ssc_cnf.ss_cnf_listenq_size = r;
			break;
		  case 'T':	/* Recipient to which we allow relaying */
			r = ss_relay_to(ss_ctx, optarg);
			if (r != 0)
				ss_err_quit(EX_OSERR,
					"sev=ERROR, func=ss_parse_arguments, recomp=%d", r);
			break;
		  case 'v':
			ss_ctx->ssc_cnf.ss_cnf_loglevel =
				(uint) atoi(optarg);
			break;
		  case 'V':
			++showversion;
			break;
		  case 'w':
			ss_ctx->ssc_cnf.ss_cnf_wait4srv = (uint) atoi(optarg);
			break;

		  case 'x':
#if SS_DEBUG
			s = optarg;
			rv = sm_set_dbgcats(&s, ss_debug, SM_ARRAY_SIZE(ss_debug));
			if (sm_is_err(rv))
				ret = rv;
#endif

		  case 'h':
		  case '?':
		  default:
			ss_usage(argv[0]);
		}
	}

	if (showversion > 0) {
		(void) ss_showversion(ss_ctx, argv[0],
			ss_ctx->ssc_cnf.ss_cnf_conffile, showversion);
		exit(0);
		/* NOTREACHED */
	}

	if (ss_ctx->ssc_cnf.ss_cnf_conffile != NULL &&
	    ss_ctx->ssc_cnf.ss_cnf_loglevel > 64)
	{
		ss_prt_cnf(&ss_ctx->ssc_cnf, smioerr, true);
	}

#if SS_LOGDIR
	if (NULL == ss_ctx->ssc_cnf.ss_cnf_logdir &&
	    SSC_IS_CFLAG(ss_ctx, SSC_CFL_BACKGROUND))
	{
		/* not really... */
		sm_log_write(ss_ctx->ssc_lctx,
			SS_LCAT_SERVER, SS_LMOD_SERVER,
			SM_LOG_ERROR, 1,
			"sev=ERROR, func=ss_parse_arguments, status=logging_directory_is_required");
		ss_usage(argv[0]);
	}
#endif /* SS_LOGDIR */

	if (0 == ss_ctx->ssc_cnf.ss_cnf_vp_count)
		ss_ctx->ssc_cnf.ss_cnf_vp_count = 1;

	if (ss_ctx->ssc_cnf.ss_cnf_cdb_gid > 0 &&
	    getgrgid(ss_ctx->ssc_cnf.ss_cnf_cdb_gid) == NULL)
	{
		ss_err_quit(EX_USAGE,
			"sev=ERROR, func=ss_parse_arguments, gid=%d, status=group_does_not_exist"
			, ss_ctx->ssc_cnf.ss_cnf_cdb_gid);
	}

	if (Sk_count != 0 && ss_ctx->ssc_cnf.ss_cnf_fd_socket != NULL) {
		ss_err_quit(EX_USAGE,
			"sev=ERROR, func=ss_parse_arguments, status=cannot_use_-L_and_-b_together");
	}
	if (0 == Sk_count) {
		Sk_count = 1;
		ss_sck_ctx[0].sssc_addr = "0.0.0.0";
	}
}

/*
**  SS_REAP_CHILD -- A child process exited
**
**	Parameters:
**		ss_ctx -- SMTP Server context
**
**	Returns:
**		pid
*/

static int
ss_reap_child(ss_ctx_P ss_ctx)
{
	int i, status, failtype;
	pid_t pid;
	sigset_t mask, omask;

	for (;;) {
		pid = wait3(&status, WNOHANG, (struct rusage *) 0);
		if (pid <= 0)
			return -1;

		/* Find index of the exited child */
		for (i = 0; i < ss_ctx->ssc_cnf.ss_cnf_vp_count; i++) {
			if (Vp_pids[i] == pid)
				break;
		}

		/* Block signals while printing and forking */
		sigemptyset(&mask);
		sigaddset(&mask, SIGTERM);
		sigaddset(&mask, SIGHUP);
		sigaddset(&mask, SIGUSR1);
		sigaddset(&mask, SIGUSR2);
		sigprocmask(SIG_BLOCK, &mask, &omask);

		failtype = sm_child_status(status);
		if (WIFEXITED(status)) {
			sm_log_write(ss_ctx->ssc_lctx,
				SS_LCAT_SERVER, SS_LMOD_SERVER,
				SM_LOG_WARN, 4,
				"sev=WARN, func=ss_reap_child, pid=%d, process=%d, child_pid=%d, status=exited, stat=%d"
				, (int) My_pid, i, pid, WEXITSTATUS(status));
		}
		else if (WIFSIGNALED(status)) {
			sm_log_write(ss_ctx->ssc_lctx,
				SS_LCAT_SERVER, SS_LMOD_SERVER,
				SM_LOG_WARN, 4,
				"sev=WARN, func=ss_reap_child, pid=%d, process=%d, child_pid=%d, status=terminated, signal=%d"
				, (int) My_pid, i, pid, WTERMSIG(status));
		}
		else if (WIFSTOPPED(status)) {
			/* fixme: don't start a new process?? */
			failtype = SM_FAILED_STOP;
			sm_log_write(ss_ctx->ssc_lctx,
				SS_LCAT_SERVER, SS_LMOD_SERVER,
				SM_LOG_WARN, 4,
				"sev=WARN, func=ss_reap_child, pid=%d, process=%d, child_pid=%d, status=stopped, signal=%d"
				, (int) My_pid, i, pid, WSTOPSIG(status));
		}
		else {
			sm_log_write(ss_ctx->ssc_lctx,
				SS_LCAT_SERVER, SS_LMOD_SERVER,
				SM_LOG_WARN, 4,
				"sev=WARN, func=ss_reap_child, pid=%d, process=%d, child_pid=%d, status=terminated, why=unknown_termination_reason"
				, (int) My_pid, i, pid);
		}

		if (!SSC_IS_FLAG(ss_ctx, SSC_FL_TERMINATING) &&
		    failtype != SM_FAILED_STOP)
		{
			/* Fork another VP */
			if ((pid = fork()) < 0) {
				sm_log_write(ss_ctx->ssc_lctx,
					SS_LCAT_SERVER, SS_LMOD_SERVER,
					SM_LOG_ERROR, 4,
					"sev=ERROR, func=ss_reap_child, pid=%d, status=cannot_create_process, errno=%d"
					, (int) My_pid, errno);
			}
			else if (0 == pid) {
				ss_ctx->ssc_id = i + ss_ctx->ssc_cnf.ss_cnf_id_base;
				My_pid = getpid();

				/* Child returns to continue in main() */
				return pid;
			}
			Vp_pids[i] = pid;
		}
		else {
			Vp_pids[i] = 0;
		}

		/* Restore the signal mask */
		sigprocmask(SIG_SETMASK, &omask, NULL);
	}

	/* NOTREACHED */
	return -1;
}

/*
**  SS_SIG_CHILDREN -- send a signal to all children
**
**	Parameters:
**		ss_ctx -- SMTP Server context
**		signo -- signal number
**
**	Returns:
**		nothing.
*/

static void
ss_sig_children(ss_ctx_P ss_ctx, int signo)
{
	int i;

	for (i = 0; i < ss_ctx->ssc_cnf.ss_cnf_vp_count; i++) {
		if (Vp_pids[i] > 0)
			kill(Vp_pids[i], signo);
	}
}

/*
**  SS_DO_SHUTDOWN -- really perform shutdown?
**
**	Parameters:
**		ss_ctx -- SMTP Server context
**
**	Returns:
**		true iff shutdown is required
*/

static bool
ss_do_shutdown(ss_ctx_P ss_ctx)
{
	if (SSC_IS_FLAG(ss_ctx, SSC_FL_SHTDWN_FRC))
		return true;
	if (0 == BUSY_THREADS(0))
		return true;
	if (ss_ctx->ssc_shutdown > 0 &&
		ss_ctx->ssc_shutdown + 2 * ss_ctx->ssc_cnf.ss_cnf_timeout < st_time()) {
		return true;
	}
	return false;
}

/*
**  SS_START_PROCESSES -- start SMTP server processes if requested
**
**	Parameters:
**		ss_ctx -- SMTP Server context
**
**	Returns:
**		nothing.
**
**	Side Effects:
**		in background mode: invoke fork(); does not return.
**		exit() on error
*/

static void
ss_start_processes(ss_ctx_P ss_ctx)
{
	sm_ret_T ret;
	int i;
	pid_t pid;

	My_pid = getpid();
	if (!SSC_IS_CFLAG(ss_ctx, SSC_CFL_BACKGROUND) &&
	    ss_ctx->ssc_cnf.ss_cnf_vp_count <= 1)
	{
		ss_ctx->ssc_id = ss_ctx->ssc_cnf.ss_cnf_id_base;
		return;
	}

	for (i = 0; i < ss_ctx->ssc_cnf.ss_cnf_vp_count; i++) {
		if ((pid = fork()) < 0) {
			sm_log_write(ss_ctx->ssc_lctx,
				SS_LCAT_SERVER, SS_LMOD_SERVER,
				SM_LOG_ERROR, 1,
				"sev=ERROR, func=ss_start_processes, pid=%d, status=cannot_create_process, errno=%d"
				, (int) My_pid, errno);
			if (0 == i)
				exit(EX_OSERR);
			sm_log_write(ss_ctx->ssc_lctx,
				SS_LCAT_SERVER, SS_LMOD_SERVER,
				SM_LOG_WARN, 7,
				"sev=WARN, func=ss_start_processes, pid=%d, processes_started=%d, processes_configured=%d"
				, (int) My_pid, i
				, ss_ctx->ssc_cnf.ss_cnf_vp_count);
			ss_ctx->ssc_cnf.ss_cnf_vp_count = i;
			break;
		}
		if (0 == pid) {
			ss_ctx->ssc_id = i + ss_ctx->ssc_cnf.ss_cnf_id_base;
			My_pid = getpid();

			/* Child returns to continue in main() */
			return;
		}
		Vp_pids[i] = pid;
	}

	/*
	**  Parent process becomes a "watchdog" and never returns to main().
	*/

	ret = st_install_sighandlers(
		SM_HDL_SIG_CHLD|SM_HDL_SIG_TERM|SM_HDL_SIG_HUP|SM_HDL_SIG_USR1|SM_HDL_SIG_USR2,
		ss_sighdl_err, NULL);
	if (sm_is_err(ret)) {
		ss_err_quit(EX_OSERR,
			"sev=ERROR, func=ss_start_processes, pid=%d, st_install_sighandlers=%#x"
			, (int) My_pid, ret);
	}

	ss_process_signals(ss_ctx, true);
}

/*
**  SS_SET_THREAD_THROTTLING -- set min/max thread numbers
**
**	Parameters:
**		ss_ctx -- SMTP Server context
**		(uses global variables)
**
**	Returns:
**		nothing
*/

static void
ss_set_thread_throttling(ss_ctx_P ss_ctx)
{

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

	/*
	**  Calculate total values across all processes.
	**  All numbers are per listening socket.
	*/

	if (0 == ss_ctx->ssc_cnf.ss_cnf_max_wait_threads)
		ss_ctx->ssc_cnf.ss_cnf_max_wait_threads =
			MAX_WAIT_THREADS_DEFAULT
			* ss_ctx->ssc_cnf.ss_cnf_vp_count;

	/* Assuming that each client session needs SS_FD_PER_THREAD fds */
	if (0 == ss_ctx->ssc_cnf.ss_cnf_max_threads)
		ss_ctx->ssc_cnf.ss_cnf_max_threads =
			(st_getfdlimit() * ss_ctx->ssc_cnf.ss_cnf_vp_count)
			/ SS_FD_PER_THREAD / Sk_count;
	if (ss_ctx->ssc_cnf.ss_cnf_max_wait_threads
	    > ss_ctx->ssc_cnf.ss_cnf_max_threads)
		ss_ctx->ssc_cnf.ss_cnf_max_wait_threads =
			ss_ctx->ssc_cnf.ss_cnf_max_threads;

	/*
	**  Now calculate per-process values.
	*/

	if ((ss_ctx->ssc_cnf.ss_cnf_max_wait_threads
	     % ss_ctx->ssc_cnf.ss_cnf_vp_count) != 0)
		ss_ctx->ssc_cnf.ss_cnf_max_wait_threads =
			ss_ctx->ssc_cnf.ss_cnf_max_wait_threads
			/ ss_ctx->ssc_cnf.ss_cnf_vp_count + 1;
	else
		ss_ctx->ssc_cnf.ss_cnf_max_wait_threads =
			ss_ctx->ssc_cnf.ss_cnf_max_wait_threads
			/ ss_ctx->ssc_cnf.ss_cnf_vp_count;
	if ((ss_ctx->ssc_cnf.ss_cnf_max_threads
	     % ss_ctx->ssc_cnf.ss_cnf_vp_count) != 0)
		ss_ctx->ssc_cnf.ss_cnf_max_threads =
			ss_ctx->ssc_cnf.ss_cnf_max_threads
			/ ss_ctx->ssc_cnf.ss_cnf_vp_count + 1;
	else
		ss_ctx->ssc_cnf.ss_cnf_max_threads =
			ss_ctx->ssc_cnf.ss_cnf_max_threads
			/ ss_ctx->ssc_cnf.ss_cnf_vp_count;
	if (ss_ctx->ssc_cnf.ss_cnf_min_wait_threads
	    > ss_ctx->ssc_cnf.ss_cnf_max_wait_threads)
		ss_ctx->ssc_cnf.ss_cnf_min_wait_threads =
			ss_ctx->ssc_cnf.ss_cnf_max_wait_threads;
	Max_cur_threads = ss_ctx->ssc_cnf.ss_cnf_max_threads;
}

/*
**  SS_GET_FD -- get fd to listen on via ss_ctx->ssc_cnf.ss_cnf_fd_socket
**
**	Parameters:
**		ss_ctx -- SMTP Server context
**
**	Returns:
**		>=0: socket (fd)
**		<0: usual error code
*/

static sm_ret_T
ss_get_fd(ss_ctx_P ss_ctx)
{
	sm_ret_T ret;
	int sock;
	int addrlen;
	ssize_t i;
	st_netfd_t fd, lfd;
	struct sockaddr addr;
	char buf[2];	/* Should this be bigger to receive some data, */
			/* e.g., IP address, port number for tracing etc? */

	lfd = fd = NULL;
	sock = INVALID_SOCKET;
	ret = un_st_server_listen(ss_ctx->ssc_cnf.ss_cnf_fd_socket, 10, &lfd);
	if (sm_is_err(ret)) {
		ss_err_quit(sm_is_perm_err(ret) ? EX_SOFTWARE : EX_OSERR,
			"sev=ERROR, func=ss_get_fd, socket=%s, listen=%m"
			, ss_ctx->ssc_cnf.ss_cnf_fd_socket, ret);
	}
	addrlen = sizeof(addr);
	fd = st_accept(lfd, &addr, &addrlen, (st_utime_t) -1);
	if (NULL == fd) {
		st_netfd_close(lfd);
		ss_err_quit(sm_is_perm_err(ret) ? EX_SOFTWARE : EX_OSERR,
			"sev=ERROR, func=ss_get_fd, socket=%s, accept=failed, error=%m"
			, ss_ctx->ssc_cnf.ss_cnf_fd_socket, sm_err_temp(errno));
	}
	i = sm_read_fd(fd, buf, 1, &sock);
	if (!is_valid_socket(sock))
		ss_err_quit(sm_is_perm_err(ret) ? EX_SOFTWARE : EX_OSERR,
			"sev=ERROR, func=ss_get_fd, socket=%s, sock_fd=%d, read_fd=%d"
			, ss_ctx->ssc_cnf.ss_cnf_fd_socket, sock, i);
	return sock;
}

/*
**  SS_CREATE_LISTENERS -- create listeners based on ss_sck_ctx[] data
**
**	Parameters:
**		ss_ctx -- SMTP Server context
**		(uses global variables Sk_count, ss_sck_ctx[])
**
**	Returns:
**		nothing
*/

static void
ss_create_listeners(ss_ctx_P ss_ctx)
{
	int i, n, sock;
	char *c;
	struct sockaddr_in serv_addr;
	struct hostent *hp;
	short port;

	for (i = 0; i < Sk_count && ss_sck_ctx[i].sssc_addr != NULL; i++) {
		if (ss_ctx->ssc_cnf.ss_cnf_fd_socket != NULL) {
			sock = ss_get_fd(ss_ctx);
			if (!is_valid_socket(sock))
				ss_err_quit(EX_OSERR,
					"sev=ERROR, func=ss_create_listeners, socket=%s, ss_get_fd=failed"
					, ss_ctx->ssc_cnf.ss_cnf_fd_socket);
			i = 0;

			/* todo: read port from configuration file? */
			ss_sck_ctx[i].sssc_port = -1;
		}
		else {
			port = 0;
			if ((c = strchr(ss_sck_ctx[i].sssc_addr, ':')) != NULL) {
				*c++ = '\0';
				port = (short) atoi(c);
			}
			if (ss_sck_ctx[i].sssc_addr[0] == '\0')
				ss_sck_ctx[i].sssc_addr = "0.0.0.0";
			if (port <= 0)
				port = SERV_PORT_DEFAULT;

			/* Create server socket */
			if ((sock = socket(PF_INET, SOCK_STREAM, 0)) < 0)
				ss_err_quit(EX_OSERR,
					"sev=ERROR, func=ss_create_listeners, socket()=failed, error=%m"
					, sm_err_temp(errno));
			n = 1;
			if (setsockopt(sock, SOL_SOCKET, SO_REUSEADDR,
					(char *)&n, sizeof(n)) < 0)
				ss_err_quit(EX_OSERR,
					"sev=ERROR, func=ss_create_listeners, setsockopt(SO_REUSEADDR)=failed, error=%m"
					, sm_err_temp(errno));
			sm_memzero(&serv_addr, sizeof(serv_addr));
			serv_addr.sin_family = AF_INET;
			serv_addr.sin_port = htons(port);
			serv_addr.sin_addr.s_addr = inet_addr(ss_sck_ctx[i].sssc_addr);
			if (INADDR_NONE == serv_addr.sin_addr.s_addr) {
				/* not dotted-decimal */
				hp = gethostbyname(ss_sck_ctx[i].sssc_addr);
				if (NULL == hp)
					ss_err_quit(EX_CONFIG,
						"sev=ERROR, func=ss_create_listeners, hostname=%s, gethostbyname=failed, error=%m"
						, ss_sck_ctx[i].sssc_addr
						, sm_err_temp(errno));
				sm_memcpy(&serv_addr.sin_addr, hp->h_addr,
					hp->h_length);
			}
			ss_sck_ctx[i].sssc_port = port;

			if (bind(sock, (struct sockaddr *)&serv_addr, sizeof(serv_addr))
			    < 0)
			{
				ss_err_quit(EX_OSERR,
					"sev=ERROR, func=ss_create_listeners, bind()=failed, address=%s, port=%hd, error=%m"
					, ss_sck_ctx[i].sssc_addr, port
					, sm_err_temp(errno));
			}
			if (listen(sock, ss_ctx->ssc_cnf.ss_cnf_listenq_size)
			    < 0)
				ss_err_quit(EX_OSERR,
					"sev=ERROR, func=ss_create_listeners, listen()=failed, error=%m"
					, sm_err_temp(errno));
		}

		/* Create file descriptor object from OS socket */
		if ((ss_sck_ctx[i].sssc_lfd = st_netfd_open_socket(sock)) == NULL)
			ss_err_quit(EX_OSERR,
				"sev=ERROR, func=ss_create_listeners, st_netfd_open_socket()=failed, erron=%m"
				, sm_err_temp(errno));

		/*
		**  On some platforms (e.g. IRIX, Linux) accept() serialization
		**  is never needed for any OS version.  In that case
		**  st_netfd_serialize_accept() is just a no-op.
		**  Also see the comment above.
		*/

		if (SSC_IS_CFLAG(ss_ctx, SSC_CFL_SERIAL_ACC) &&
		    st_netfd_serialize_accept(ss_sck_ctx[i].sssc_lfd) < 0)
			ss_err_quit(EX_OSERR,
				"sev=ERROR, func=ss_create_listeners, st_netfd_serialize_accept()=failed, error=%m"
				, sm_err_temp(errno));
	}
}

/*
**  SS_SIGHDL_ERR -- Callback for signal handler error
**
**	Parameters:
**		err -- errno encountered
**		ctx -- context (UNUSED)
**
**	Returns:
**		exits.
*/

void
ss_sighdl_err(int err, void *ctx /* UNUSED */)
{
	(void)ctx;	/* UNUSED */
	ss_err_quit(EX_OSERR,
		"sev=ERROR, func=ss_sighdl_err, pid=%d, err=%m"
		, (int) My_pid, sm_err_temp(err));
}

/*
**  SS_INSTALL_SIGHANDLERS -- install signal handlers
**
**	Parameters:
**		none
**
**	Returns:
**		nothing; exits on error
*/

static void
ss_install_sighandlers(void)
{
	sm_ret_T ret;

	ret = st_install_sighandlers(
		SM_HDL_SIG_TERM|SM_HDL_SIG_HUP|SM_HDL_SIG_USR1|SM_HDL_SIG_USR2,
		ss_sighdl_err, NULL);
	if (sm_is_err(ret))
		ss_err_quit(EX_OSERR,
			"sev=ERROR, func=ss_install_sighandlers, pid=%d, st_install_sighandlers=%#x"
			, (int) My_pid, ret);
}

/*
**  SS_PROCESS_SIGNALS -- signal processing thread.
**	Read signal number from pipe and acts accordingly
**
**	Parameters:
**		ss_ctx -- SMTP Server context
**
**	Returns:
**		does not return, but may exit.
*/

static void
ss_process_signals(ss_ctx_P ss_ctx, bool children)
{
	char sig;
	st_utime_t tmo;

	tmo = (st_utime_t)-1;
	for (;;) {
		/* Read the next signal from the signal pipe */
		if (st_read(Sig_pipe[SM_RD_SIG_PIPE], &sig, sizeof(char), tmo)
		    != sizeof(char))
		{
			if (tmo != (st_utime_t)-1 && ETIME == errno) {
				sm_log_write(ss_ctx->ssc_lctx,
					SS_LCAT_SERVER, SS_LMOD_SERVER,
					SM_LOG_INFO, 11,
					"sev=INFO, func=ss_process_signals, pid=%d, st_read=timeout, flags=%#x"
					, (int) My_pid, ss_ctx->ssc_flags);
				if (SSC_IS_FLAG(ss_ctx, SSC_FL_SHTDWN_TS)) {
					sig = SM_SIG_SHUT;
					SSC_SET_FLAG(ss_ctx, SSC_FL_SHTDWN_FRC);
				}
			}
			else
				ss_err_quit(EX_OSERR,
					"sev=ERROR, func=ss_process_signals, pid=%d, st_read=%m"
					, (int) My_pid, sm_err_temp(errno));
		}

		if (children) {
			int signo;
			pid_t pid;

			signo = 0;
			switch (sig) {
			  case SM_SIG_HUP:
				signo = SIGHUP;
				break;
			  case SM_SIG_TERM:
				signo = SIGTERM;
				SSC_SET_FLAG(ss_ctx, SSC_FL_TERMINATING);
				break;
			  case SM_SIG_USR1:
				signo = SIGUSR1;
				break;
			  case SM_SIG_USR2:
				signo = SIGUSR2;
				break;
			  case SM_SIG_CHLD:
				pid = ss_reap_child(ss_ctx);
				if (0 == pid)
					return;
				break;
			  default:
				break;
			}
			if (signo != 0)
				ss_sig_children(ss_ctx, signo);
		}

		switch (sig) {
		  case SM_SIG_HUP:
			sm_log_write(ss_ctx->ssc_lctx,
				SS_LCAT_SERVER, SS_LMOD_SERVER,
				SM_LOG_INFO, 9,
				"sev=INFO, func=ss_process_signals, pid=%d, signal=SIGHUP, action=none"
				, (int) My_pid);

#if 0
			ss_load_configs(ss_ctx);
#endif
			break;
		  case SM_SIG_TERM:
			/*
			**  Terminate ungracefully since it is generally not
			**  known how long it will take to gracefully complete
			**  all client sessions.
			*/

			sm_log_write(ss_ctx->ssc_lctx,
				SS_LCAT_SERVER, SS_LMOD_SERVER,
				SM_LOG_INFO, 9,
				"sev=INFO, func=ss_process_signals, pid=%d, signal=SIGTERM, action=terminating"
				, (int) My_pid);
			(void) sm_log_destroy(Ss_ctx.ssc_lctx);
			exit(0);
			/* NOTREACHED */
		  case SM_SIG_USR1:
			sm_log_write(ss_ctx->ssc_lctx,
				SS_LCAT_SERVER, SS_LMOD_SERVER,
				SM_LOG_INFO, 9,
				"sev=INFO, func=ss_process_signals, pid=%d, signal=SIGUSR1, action=printinfo"
				, (int) My_pid);

			/* Print server info to stderr */
			ss_dump_info(ss_ctx);

			/* Print server info to stderr */
			if (HEAP_CHECK)
				sm_heap_report(smioerr, 3);
			break;
		  case SM_SIG_USR2:
			/* Reopen log files - needed for log rotation */
			(void) sm_log_reopen(ss_ctx->ssc_lctx);
			sm_log_write(ss_ctx->ssc_lctx,
				SS_LCAT_SERVER, SS_LMOD_SERVER,
				SM_LOG_INFO, 9,
				"sev=INFO, func=ss_process_signals, pid=%d, signal=SIGUSR2, action=reopened_logfiles"
				, (int) My_pid);
			break;
		  case SM_SIG_SHUT:
			/* graceful termination (if possible) */
			if (!SSC_IS_FLAG(ss_ctx, SSC_FL_SHTDWN_TS)) {
				tmo = SEC2USEC(2 * ss_ctx->ssc_cnf.ss_cnf_timeout);
				SSC_SET_FLAG(ss_ctx, SSC_FL_SHTDWN_TS);
			}
		    if (!ss_do_shutdown(ss_ctx))
				break;
			sm_log_write(ss_ctx->ssc_lctx,
				SS_LCAT_SERVER, SS_LMOD_SERVER,
				SM_LOG_INFO, 9,
				"sev=INFO, func=ss_process_signals, pid=%d, signal=SHUT, action=terminating"
				, (int) My_pid);
			exit(0);
			/* NOTREACHED */
		  default:
			sm_log_write(ss_ctx->ssc_lctx,
				SS_LCAT_SERVER, SS_LMOD_SERVER,
				SM_LOG_INFO, 9,
				"sev=INFO, func=ss_process_signals, pid=%d, signal=%c, action=ignored"
				, (int) My_pid, sig);
		}
	}

	return;
}

/*
**  SS_START_THREADS -- start minimum number of connection handling threads
**
**	Parameters:
**		ss_ctx -- SMTP Server context
**
**	Returns:
**		nothing
**
**	Side Effects:
**		exit() on error
*/

static void
ss_start_threads(ss_ctx_P ss_ctx)
{
	int i, threads;
	uint u;

	/* Create connections handling threads */
	for (i = 0; i < Sk_count; i++) {
		sm_log_write(ss_ctx->ssc_lctx,
			SS_LCAT_SERVER, SS_LMOD_SERVER,
			SM_LOG_INFO, 9,
			"sev=INFO, func=ss_start_threads, version=%s, pid=%d, where=start, threads=%d, addr=%s, port=%d"
			, MTA_VERSION_STR
			, (int) My_pid, ss_ctx->ssc_cnf.ss_cnf_max_wait_threads
			, ss_sck_ctx[i].sssc_addr, ss_sck_ctx[i].sssc_port);
		WAIT_THREADS(i) = 0;
		BUSY_THREADS(i) = 0;
		MAXB_THREADS(i) = 0;
		RQST_COUNT(i) = 0;
		TA_COUNT(i) = 0;
		threads = 0;
		for (u = 0; u < ss_ctx->ssc_cnf.ss_cnf_max_wait_threads; u++) {
			if (st_thread_create(ss_handle_connections, (void *) (long) i, 0, 0) != NULL)
				threads++;
			else
				sm_log_write(ss_ctx->ssc_lctx,
					SS_LCAT_SERVER, SS_LMOD_SERVER,
					SM_LOG_ERROR, 8,
					"sev=ERROR, func=ss_start_threads, pid=%d, st_thread_create()=failed, u=%u"
					, (int) My_pid, u);
		}
		if (0 == threads) {
			sm_s2q_stop(ss_ctx->ssc_s2q_ctx);
			sm_s2q_stop(ss_ctx->ssc_s2a_ctx);
#if MTA_USE_PMILTER
			sm_s2q_stop(ss_ctx->ssc_s2m_ctx);
#endif
			exit(EX_OSERR);
			/* NOTREACHED */
		}
	}
}

/*
**  SS_INIT0 -- first part of initialization (before options are read)
**
**	Parameters:
**		ss_ctx -- SMTP Server context
**
**	Returns:
**		usual return code
*/

static sm_ret_T
ss_init0(ss_ctx_P ss_ctx)
{
	sm_ret_T ret;

	SM_IS_SS_CTX(ss_ctx);
	ret = sm_log_create(NULL, &ss_ctx->ssc_lctx, &ss_ctx->ssc_lcfg);
	if (sm_is_err(ret))
		return ret;

	ret = sm_log_setfp_fd(ss_ctx->ssc_lctx, Errfp, SMIOLOG_FILENO);
	if (sm_is_err(ret))
		return ret;
	ss_ctx->ssc_cnf.ss_cnf_timeout = SS_IO_TIMEOUT;
	ss_ctx->ssc_cnf.ss_cnf_wait4srv = 1;
	ss_ctx->ssc_cnf.ss_cnf_listenq_size = LISTENQ_SIZE_DEFAULT;
	ss_ctx->ssc_cnf.ss_cnf_loglevel = UINT_MAX;
	ss_ctx->ssc_cnf.ss_cnf_w4a2s = TMO_W4A2S;
	ss_ctx->ssc_cnf.ss_cnf_mod_tmo = SS_RCB_SND_TMO;
	ss_ctx->ssc_cnf.ss_cnf_maxhops = MTA_MAXHOPS;
	ss_ctx->ssc_cnf.ss_cnf_max_msg_sz_kb = SM_MAX_MSG_SZ_KB;
	ss_ctx->ssc_cnf.ss_cnf_min_wait_threads = 2;

	/* some limits, set very high so they don't interfere by default */
	ss_ctx->ssc_cnf.ss_cnf_t_tot_lim = UINT_MAX / 2;
	ss_ctx->ssc_cnf.ss_cnf_r_tot_lim = UINT_MAX / 2;
	ss_ctx->ssc_cnf.ss_cnf_r_ta_lim = UINT_MAX / 4;
#if 0
	ss_ctx->ssc_cnf.ss_cnf_r_fail_lim = UINT_MAX / 4;
	ss_ctx->ssc_cnf.ss_cnf_r_ok_lim = UINT_MAX / 2;
#endif /* 0 */
	ss_ctx->ssc_cnf.ss_cnf_sess_max_badcmds = SS_SESS_MAX_BADCMDS;
	ss_ctx->ssc_cnf.ss_cnf_sess_max_nopcmds = SS_SESS_MAX_NOPCMDS;
	ss_ctx->ssc_cnf.ss_cnf_sess_max_invldaddr = SS_SESS_MAX_INVLDADDR;
	ss_ctx->ssc_cnf.ss_cnf_ta_max_badcmds = SS_TA_MAX_BADCMDS;
	ss_ctx->ssc_cnf.ss_cnf_ta_max_nopcmds = SS_TA_MAX_NOPCMDS;
	ss_ctx->ssc_cnf.ss_cnf_ta_max_invldaddr = SS_TA_MAX_INVLDADDR;

	ss_ctx->ssc_cnf.ss_cnf_smarsock = smarsock;
	ss_ctx->ssc_cnf.ss_cnf_cdb_base = "";
	ss_ctx->ssc_cnf.ss_cnf_smtpssock = smsmtpssock;
#if MTA_USE_PMILTER
	ss_ctx->ssc_cnf.ss_cnf_w4m2s = TMO_W4M2S;
#endif
	ss_ctx->ssc_cnf.ss_cnf_cflags = SSC_CFL_STARTTLS|SSC_CFL_AUTH|SSC_CFL_RSAD;

#define SM_CRT_CSTR(cstr, val)						\
	do {								\
		cstr = sm_cstr_crt((uchar *) (val), strlen((val)));	\
		if (NULL == (cstr))					\
			return sm_error_temp(SM_EM_SMTPS, ENOMEM);	\
	} while (0)
	SM_CRT_CSTR(HostnameNone, "Hostname_Not_Determined");
	SM_CRT_CSTR(HostnameNoMatch, "Hostname_No_Match");
	SM_CRT_CSTR(HostnameTempPTR, "Hostname_Temp_PTR");
	SM_CRT_CSTR(HostnameTempA, "Hostname_Temp_A");
	SM_CRT_CSTR(HostnameBogus, "Hostname_Bogus");
#if SS_TEST
	Test_client_ip.s_addr = INADDR_ANY;
#endif
	return ret;
}

/*
**  SS_INIT_CHK -- check configuration etc (after options are read)
**
**	Parameters:
**		ss_ctx -- SMTP Server context
**
**	Returns:
**		usual return code
*/

static sm_ret_T
ss_init_chk(ss_ctx_P ss_ctx)
{
	sm_ret_T ret;

	SM_IS_SS_CTX(ss_ctx);
	ret = SM_SUCCESS;
#if MTA_USE_TLS
	ret = sm_tlsversionok();
	if (sm_is_err(ret))
		return ret;
#endif
#if MTA_USE_SASL
	ret = sm_saslversionok();
	if (sm_is_err(ret))
		return ret;
#endif
	/* question: check map version?? */

	return ret;
}

/*
**  SS_INIT1 -- second part of initialization; after options are read,
**	before individual processes are started, i.e., only initialize data
**	here that is "global" (identical, can be shared) for every process.
**
**	Parameters:
**		ss_ctx -- SMTP Server context
**
**	Returns:
**		usual return code
*/

static sm_ret_T
ss_init1(ss_ctx_P ss_ctx)
{
	sm_ret_T ret;
	int r;

	SM_IS_SS_CTX(ss_ctx);

	/* convert timeout from s to micro seconds for statethreads */
	ss_ctx->ssc_mod_tmo = SEC2USEC(ss_ctx->ssc_cnf.ss_cnf_mod_tmo);
	ret = sm_log_setdebuglevel(ss_ctx->ssc_lctx,
				ss_ctx->ssc_cnf.ss_cnf_loglevel);
	if (sm_is_err(ret))
		return ret;
	if (NULL == ss_ctx->ssc_hostname) {
		ret = sm_myhostname(&ss_ctx->ssc_hostname);
		if (sm_is_err(ret)) {
			sm_log_write(ss_ctx->ssc_lctx,
				SS_LCAT_SERVER, SS_LMOD_SERVER,
				SM_LOG_ERROR, 1,
				"sev=ERROR, func=ss_init1, status=cannot_determine_my_hostname, ret=%m"
				, ret);
			return ret;
		}
	}

	if (!SSC_IS_FLAG(ss_ctx, SSC_FL_RELAYFROM)) {
		r = regcomp(&ss_ctx->ssc_relayfrom, RELAY_CLT, REG_ICASE|REG_NOSUB);
		if (r != 0) {
			sm_log_write(ss_ctx->ssc_lctx,
				SS_LCAT_SERVER, SS_LMOD_SERVER,
				SM_LOG_ERROR, 2,
				"sev=ERROR, func=ss_init1, where=relay_from, pattern=%s, recomp=%d"
				, RELAY_CLT, r);
			return sm_error_perm(SM_EM_SMTPS, r);
		}
		SSC_SET_FLAG(ss_ctx, SSC_FL_RELAYFROM);
	}
	if (!SSC_IS_FLAG(ss_ctx, SSC_FL_RELAYTO)) {
		char *pat;
		char relto[MAXHOSTNAMELEN + 18];

		if (sm_snprintf(relto, sizeof(relto),
			"(^<postmaster|@%S)>$", ss_ctx->ssc_hostname) <
		    (int) sizeof(relto))
			pat = relto;
		else
			pat = RELAY_RCPT;
		r = regcomp(&ss_ctx->ssc_relayto, pat, REG_EXTENDED|REG_ICASE|REG_NOSUB);
		if (r != 0) {
			sm_log_write(ss_ctx->ssc_lctx,
				SS_LCAT_SERVER, SS_LMOD_SERVER,
				SM_LOG_ERROR, 2,
				"sev=ERROR, func=ss_init1, where=relay_to, pattern=%s, recomp=%d"
				, pat, r);
			return sm_error_perm(SM_EM_SMTPS, r);
		}
		SSC_SET_FLAG(ss_ctx, SSC_FL_RELAYTO);
	}

	ret = sm_gen_conf_path(ss_ctx->ssc_cnf.ss_cnf_cdb_base,
		ss_ctx->ssc_cnf.ss_cnf_smarsock,
		smarsock,
		&ss_ctx->ssc_cnf.ss_cnf_smarsock_abs,
		&ss_ctx->ssc_cnf.ss_cnf_smarsock_alloc);
	if (sm_is_err(ret))
		return ret;

	ret = sm_gen_conf_path(ss_ctx->ssc_cnf.ss_cnf_cdb_base,
		ss_ctx->ssc_cnf.ss_cnf_smtpssock,
		smsmtpssock,
		&ss_ctx->ssc_cnf.ss_cnf_smtpssock_abs,
		&ss_ctx->ssc_cnf.ss_cnf_smtpssock_alloc);
	if (sm_is_err(ret))
		return ret;

	ret = cdb_start(ss_ctx->ssc_cnf.ss_cnf_cdb_base,
			&ss_ctx->ssc_cdb_ctx);
	if (sm_is_err(ret))
		return ret;

	return ret;
}

#if MTA_USE_PMILTER
/*
**  SS_CONN2MILT -- connect to milter
**
**	Parameters:
**		ss_ctx -- SMTP Server context
**
**	Returns:
**		usual return code
*/

static sm_ret_T
ss_conn2milt(ss_ctx_P ss_ctx, ss_sess_P ss_sess)
{
	sm_ret_T ret;

	SM_IS_SS_CTX(ss_ctx);
	ret = SM_SUCCESS;

	/* SSC_FL_PM_TRYCONN should be checked by caller */
	if (0 == ss_ctx->ssc_cnf.ss_cnf_miltsockspec.sckspc_type
	    || !SSC_IS_CFLAG(ss_ctx, SSC_CFL_PMILTER)
	    || SSC_IS_FLAG(ss_ctx, SSC_FL_PM_USE)
	    || SSC_IS_FLAG(ss_ctx, SSC_FL_PM_TRYING)
#if 0
	    || (ss_ctx->ssc_pm_lasttry > 0 &&
		ss_ctx->ssc_pm_lasttry + 1 >= st_time()) /* CONF timeout */
#endif
						/* exponential delay? */
	   )
	{
		sm_log_write(ss_ctx->ssc_lctx,
			SS_LCAT_SERVER, SS_LMOD_SERVER,
			SM_LOG_DEBUG, 14,
			"sev=DBG, func=ss_conn2milt, ss_sess=%s, status=do_not_try_to_reconnect, flags=%#x, lasttry=%ld, now=%ld"
			, ss_sess->ssse_id, ss_ctx->ssc_flags
			, (long) ss_ctx->ssc_pm_lasttry, (long) st_time());
		return ret;
	}

	/* SSC_FL_PM_TRYING acts as mutex, no race in statethreads */
	SSC_SET_FLAG(ss_ctx, SSC_FL_PM_TRYING);
	ret = sm_s2q_open(ss_ctx->ssc_s2m_ctx,
		&ss_ctx->ssc_cnf.ss_cnf_miltsockspec,
		ss_ctx->ssc_cnf.ss_cnf_wait4srv,
		ss_ctx->ssc_cnf.ss_cnf_max_threads,
		S2Q_T_PMILTER, 0);

	ss_ctx->ssc_pm_lasttry = st_time();

	/* do we care about errors? */
	if (sm_is_err(ret)) {
		sm_log_write(ss_ctx->ssc_lctx,
			SS_LCAT_SERVER, SS_LMOD_SERVER,
			SM_LOG_ERROR, 9,
			"sev=ERROR, func=ss_conn2milt, ss_sess=%s, sm_s2q_open=%m"
			, ss_sess->ssse_id, ret);

		if (SSC_IS_MFLAG(ss_ctx, SSC_MFL_PM_421)) {
			ret = SMTP_R_SSD;
			SSC_SET_FLAG(ss_ctx, SSC_FL_PM_TRYCONN);
			SSC_SET_STATE(ss_ctx, SSC_ST_SSD);
		}
		else if (SSC_IS_MFLAG(ss_ctx, SSC_MFL_PM_AGAIN))
			SSC_SET_FLAG(ss_ctx, SSC_FL_PM_TRYCONN);
		else
			ret = SM_SUCCESS;
	}
	else {
		SSC_SET_FLAG(ss_ctx, SSC_FL_PM_USE);
		SSC_CLR_FLAG(ss_ctx, SSC_FL_PM_TRYCONN);
		sm_log_write(ss_ctx->ssc_lctx,
			SS_LCAT_SERVER, SS_LMOD_SERVER,
			SM_LOG_DEBUG, 14,
			"sev=DBG, func=ss_conn2milt, ss_sess=%s, status=reconnected"
			, ss_sess->ssse_id);
	}
	SSC_CLR_FLAG(ss_ctx, SSC_FL_PM_TRYING);
	return ret;
}

/*
**  SS_INIT_MILT -- initialize pmilter connection
**
**	Parameters:
**		ss_ctx -- SMTP Server context
**
**	Returns:
**		usual return code
*/

static sm_ret_T
ss_init_milt(ss_ctx_P ss_ctx)
{
	sm_ret_T ret;
	char *which;

	SM_IS_SS_CTX(ss_ctx);
	ret = SM_SUCCESS;

	if (0 == ss_ctx->ssc_cnf.ss_cnf_miltsockspec.sckspc_type
	    || !SSC_IS_CFLAG(ss_ctx, SSC_CFL_PMILTER))
		return ret;

	/* initialize capabilities */
	SSC_SET_PMCAP(ss_ctx, SM_SCAP_PM_ALL);

	/* SSC_FL_PM_TRYING acts as mutex, no race in statethreads */
	SSC_SET_FLAG(ss_ctx, SSC_FL_PM_TRYING);
	ret = sm_s2q_create(&ss_ctx->ssc_s2m_ctx, ss_ctx,
		ss_ctx->ssc_cnf.ss_cnf_max_threads);

	if (sm_is_success(ret)) {
		ret = sm_s2q_open(ss_ctx->ssc_s2m_ctx,
			&ss_ctx->ssc_cnf.ss_cnf_miltsockspec,
			ss_ctx->ssc_cnf.ss_cnf_wait4srv,
			ss_ctx->ssc_cnf.ss_cnf_max_threads,
			S2Q_T_PMILTER, 0);
		which = "sm_s2q_open";
	}
	else
		which = "sm_s2q_create";

	ss_ctx->ssc_pm_lasttry = st_time();

	/* do we care about errors? */
	if (sm_is_err(ret)) {
		sm_log_write(ss_ctx->ssc_lctx,
			SS_LCAT_SERVER, SS_LMOD_SERVER,
			SM_LOG_ERROR, 9,
			"sev=ERROR, func=ss_init_milt, %s=%m"
			, which, ret);

		if (SSC_IS_MFLAG(ss_ctx, SSC_MFL_PM_421)) {
			ret = SMTP_R_SSD;
			SSC_SET_FLAG(ss_ctx, SSC_FL_PM_TRYCONN);
			SSC_SET_STATE(ss_ctx, SSC_ST_SSD);
		}
		else if (SSC_IS_MFLAG(ss_ctx, SSC_MFL_PM_AGAIN))
			SSC_SET_FLAG(ss_ctx, SSC_FL_PM_TRYCONN);
		else
			ret = SM_SUCCESS;
	}
	else
		SSC_SET_FLAG(ss_ctx, SSC_FL_PM_USE);
	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_init_milt, %s=%m, where=pmilter"
			, which, ret);
		/* fall through to return error and release mutex */
	}
	SSC_CLR_FLAG(ss_ctx, SSC_FL_PM_TRYING);
	return ret;
}
#endif /* MTA_USE_PMILTER */

#if MTA_USE_SASL

/*
**  PROXY_POLICY -- define proxy policy for AUTH
**
**	Parameters:
**		conn -- unused.
**		context -- unused.
**		requested_user -- authorization identity.
**		rlen -- authorization identity length.
**		auth_identity -- authentication identity.
**		alen -- authentication identity length.
**		def_realm -- default user realm.
**		urlen -- user realm length.
**		propctx -- unused.
**
**	Returns:
**		ok?
*/

static int
sm_proxy_policy(sasl_conn_t *conn, void *context, const char *requested_user,
		unsigned rlen, const char *auth_identity, unsigned alen,
		const char *def_realm, unsigned urlen, struct propctx *propctx)
{
	if (NULL == auth_identity)
		return SASL_FAIL;
	return SASL_OK;
}

static sasl_callback_t sm_sasl_srvcbs[] =
{
	{	SASL_CB_PROXY_POLICY,	&sm_proxy_policy,	NULL	},
	{	SASL_CB_LIST_END,	NULL,		NULL	}
};
#endif

/*
**  SS_INITP -- third part of initialization
**	Called after processes have been started, i.e., per-process
**	initialization is performed here.
**
**	Parameters:
**		ss_ctx -- SMTP Server context
**
**	Returns:
**		usual return code
*/

static sm_ret_T
ss_initp(ss_ctx_P ss_ctx)
{
	sm_ret_T ret;

	SM_IS_SS_CTX(ss_ctx);

	ret = sm_s2q_init_u(&ss_ctx->ssc_s2q_ctx, ss_ctx,
			ss_ctx->ssc_cnf.ss_cnf_smtpssock_abs,
			ss_ctx->ssc_cnf.ss_cnf_wait4srv,
			ss_ctx->ssc_cnf.ss_cnf_max_threads,
			ss_ctx->ssc_id,
			ss_ctx->ssc_cnf.ss_cnf_max_threads,
			S2Q_T_QMGR, 0);
	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_initp, id=%u, sm_s2q_init=%m, socket=%s"
			, ss_ctx->ssc_id, ret, ss_ctx->ssc_cnf.ss_cnf_smtpssock_abs);
		SSC_SET_FLAG(ss_ctx, SSC_FL_RESTARTDEP);
		return ret;
	}
	ret = sm_s2q_init_u(&ss_ctx->ssc_s2a_ctx, ss_ctx,
			ss_ctx->ssc_cnf.ss_cnf_smarsock_abs,
			ss_ctx->ssc_cnf.ss_cnf_wait4srv,
			ss_ctx->ssc_cnf.ss_cnf_max_threads,
			ss_ctx->ssc_id,
			ss_ctx->ssc_cnf.ss_cnf_max_threads,
			S2Q_T_SMAR,
			SSC_IS_CFLAG(ss_ctx, SSC_CFL_ACCESS_DB) ? SMARCL_FL_ACC : 0);
	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_initp, id=%u, sm_s2q_init=%m, socket=%s"
			, ss_ctx->ssc_id, ret, ss_ctx->ssc_cnf.ss_cnf_smarsock);
		SSC_SET_FLAG(ss_ctx, SSC_FL_RESTARTDEP);
		return ret;
	}

#if MTA_USE_PMILTER
	ret = ss_init_milt(ss_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_initp, id=%u, ss_init_milt=%m, where=pmilter"
			, ss_ctx->ssc_id, ret);

		/* ignore error, system should try to reconnect later on */
		ret = SM_SUCCESS;
	}
#endif /* MTA_USE_PMILTER */

#if MTA_USE_TLS
	/* XXX Is this per process or "global"? */
	ret = sm_tls_init_library(&ss_ctx->ssc_tlsl_ctx);
	if (sm_is_success(ret)) {
		char confdir[PATH_MAX];

		ret = sm_dirname(ss_ctx->ssc_cnf.ss_cnf_conffile, confdir,
			sizeof(confdir));
		if (sm_is_err(ret)) {
			sm_log_write(ss_ctx->ssc_lctx,
				SS_LCAT_INIT, SS_LMOD_CONFIG,
				SM_LOG_ERR, 1,
				"sev=ERROR, func=ss_initp, sm_dirname=%m", ret);
			return ret;
		}

		TLS_GEN_PATHS(ss_ctx->ssc_cnf.ss_cnf_tls, ss_ctx->ssc_cnf, ss);
		ret = sm_tls_init(ss_ctx->ssc_tlsl_ctx, &ss_ctx->ssc_ssl_ctx,
			TLS_I_SRV, true, &ss_ctx->ssc_cnf.ss_cnf_tls);
		if (sm_is_success(ret))
			SSC_SET_FLAG(ss_ctx, SSC_FL_TLS_OK);
		else {
			sm_log_write(ss_ctx->ssc_lctx,
				SS_LCAT_INIT, SS_LMOD_CONFIG,
				SM_LOG_INFO, 9,
				"sev=INFO, func=ss_initp, sm_tls_init=%m", ret);
		}

		/*
		**  HACK! it should check whether log is in config file
		**  however it's not clear how to do that...
		**  this only works because facility 0 is KERN.
		*/

		if (ss_ctx->ssc_cnf.ss_cnf_log.sm_logspc_facility != 0) {
			(void) sm_set_tls_log(ss_ctx->ssc_tlsl_ctx, NULL,
				INVALID_FD, ss_ctx->ssc_cnf.ss_cnf_loglevel);
		}

	}
	ret = SM_SUCCESS; /* ignore errors for now; just disable TLS */
#endif /* MTA_USE_TLS */
#if MTA_USE_SASL
	ret = sm_sasl_init(true, ss_ctx->ssc_cnf.ss_cnf_auth_flags,
			sm_sasl_srvcbs, ss_ctx->ssc_lctx,
			&ss_ctx->ssc_sasl_ctx);
	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_initp, sm_sasl_init=%m", ret);
	}
	else
		SSC_SET_FLAG(ss_ctx, SSC_FL_SASL_OK);
#endif /* MTA_USE_SASL */

	return ret;

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

/*
**  SS_ONE_SESSION -- perform one SMTP server session
**
**	Parameters:
**		ssfd -- file descriptor to use for I/O
**		ss_ctx -- SMTP server context
**
**	Returns:
**		usual return code
*/

static int
ss_one_session(st_netfd_t ssfd, ss_ctx_P ss_ctx)
{
	sm_file_T *fp;
	struct sockaddr_in clt_addr;
	long i;
	int r;
	sm_ret_T ret;
	ss_sess_P ss_sess;

	fp = NULL;
	i = 0;

	/* get a new session context */
	ret = ss_sess_new(ss_ctx, &ss_sess);
	if (sm_is_err(ret))
		goto sess_error;

	ret = sm_io_open(&SmStThrNetIO, (void *) &ssfd, SM_IO_RDWR,
			&fp, SM_IO_WHAT_END);
	if (ret != SM_SUCCESS) {
		sm_log_write(ss_ctx->ssc_lctx,
			SS_LCAT_SERVER, SS_LMOD_SERVER,
			SM_LOG_ERR, 6,
			"sev=ERROR, func=ss_one_session, pid=%d, sm_io_open()=%m"
			, (int) My_pid, ret);

		/* ??? */
		st_netfd_close(ssfd);
		return ret;
	}

	/* switch to non-blocking */
	sm_io_clrblocking(fp);

	r = ss_ctx->ssc_cnf.ss_cnf_timeout;
	ret = sm_io_setinfo(fp, SM_IO_WHAT_TIMEOUT, &r);
	if (ret != SM_SUCCESS) {
		sm_log_write(ss_ctx->ssc_lctx,
			SS_LCAT_SERVER, SS_LMOD_SERVER,
			SM_LOG_ERROR, 5,
			"sev=ERROR, func=ss_one_session, pid=%d, set_timeout()=%m"
			, (int) My_pid, ret);
		sm_io_close(fp, SM_IO_CF_NONE);
		return ret;
	}

	ret = sm_io_setinfo(fp, SM_IO_DOUBLE, NULL);
	if (ret != SM_SUCCESS) {
		sm_log_write(ss_ctx->ssc_lctx,
			SS_LCAT_SERVER, SS_LMOD_SERVER,
			SM_LOG_ERROR, 5,
			"sev=ERROR, func=ss_one_session, pid=%d, set_double=%m"
			, (int) My_pid, ret);
		sm_io_close(fp, SM_IO_CF_NONE);
		return ret;
	}

	clt_addr.sin_family = AF_INET;
	clt_addr.sin_port = htons(587);
	clt_addr.sin_addr.s_addr = inet_addr("127.0.0.1");
	ss_sess->ssse_fp = fp;
	ss_sess->ssse_client = clt_addr.sin_addr;
	ss_sess->ssse_state = SSSE_ST_CONNECTED;
	ss_sess->ssse_idx = i;

	ret = sm_s2q_nseid(ss_sess, ss_ctx->ssc_s2q_ctx, ss_sess->ssse_id);
	if (sm_is_err(ret))
		goto sess_error;
	ret = sm_w4q2s_reply(ss_sess, TMO_W4Q2S, ss_ctx->ssc_s2q_ctx);
	if (sm_is_err(ret)) {
		sm_log_write(ss_ctx->ssc_lctx,
			SS_LCAT_SERVER, SS_LMOD_SERVER,
			SM_LOG_ERROR, 5,
			"sev=ERROR, func=ss_one_session, ss_sess=%s, ss_one_session=after_sm_s2q_nseid, sm_w4q2s_reply=%m"
			, ss_sess->ssse_id, ret);
		goto sess_error;
	}

	/* ss_sess must be deallocated by ss_hdl_session() */
	fp = NULL;
	ret = ss_hdl_session(ss_sess, ret);
	ss_sess = NULL;

  sess_error:
	if (fp != NULL)
		sm_io_close(fp, SM_IO_CF_NONE);
	ss_sess_free(ss_sess);
	return ret;
}

/*
**  SS_HANDLE_CONNECTIONS  -- accept incoming connections, a thread handles it.
**	This runs as a thread; it may create more threads or it may
**	terminate itself.
**
**	Parameters:
**		arg -- index for ss_sck_ctx[] array
**
**	Returns:
**		NULL (should return an error code)
*/

static void *
ss_handle_connections(void *arg)
{
	st_netfd_t srv_lfd, cli_fd;
	sm_file_T *fp;
	struct sockaddr_in clt_addr;
	int save_errno, r;
	long i;
	sm_ret_T ret;
	ss_sess_P ss_sess;
	ss_ctx_P ss_ctx;

	ss_sess = NULL;
	i = (long) arg;
	srv_lfd = ss_sck_ctx[i].sssc_lfd;

	/* just for consistency... maybe later on this is passed in? */
	ss_ctx = &Ss_ctx;
	ret = ss_sess_new(ss_ctx, &ss_sess);
	if (sm_is_err(ret))
		return NULL;		/* question: some error?? */

	WAIT_THREADS(i)++;
	while (WAIT_THREADS(i) <= ss_ctx->ssc_cnf.ss_cnf_max_wait_threads
	       && !SSC_IS_FLAG(ss_ctx, SSC_FL_SHUTDOWN))
	{
		r = sizeof(clt_addr);
		cli_fd = st_accept(srv_lfd, (struct sockaddr *)&clt_addr,
				&r, (st_utime_t) -1);
		if (NULL == cli_fd) {
			int priority, level;
			char *sev;

			save_errno = errno;
			if (EINTR == save_errno || ECONNABORTED == save_errno) {
				level = 14;
				priority = SM_LOG_INFO;
				sev = "INFO";
			}
			else {
				level = 2;
				priority = SM_LOG_ERROR;
				sev = "ERROR";
			}
			sm_log_write(ss_ctx->ssc_lctx,
				SS_LCAT_SERVER, SS_LMOD_SERVER,
				priority, level,
				"sev=%s, func=ss_handle_connections, st_accept()=%m"
				, sev, sm_err_temp(save_errno));
			if (EINTR == save_errno || ECONNABORTED == save_errno)
				continue;
			else if (EMFILE == save_errno || ENFILE == save_errno ||
				 ENOMEM == save_errno)
			{
				sleep(1);
				continue;
			}
			break;
		}
		ss_sess->ssse_connect = st_time();

#if 0
		sm_log_write(ss_ctx->ssc_lctx,
			SS_LCAT_SERVER, SS_LMOD_SERVER,
			SM_LOG_DEBUG, 13,
			"sev=DBG, func=ss_handle_connections, pid=%d, idx=%d, accept=%d"
			, (int) My_pid, ss_ctx->ssc_id, cli_fd);
#endif /* 0 */

		/* Save peer address, so we can retrieve it later */
		st_netfd_setspecific(cli_fd, &clt_addr.sin_addr, NULL);

		ret = sm_io_open(&SmStThrNetIO, (void *) &cli_fd, SM_IO_RDWR,
				&fp, SM_IO_WHAT_END);
		if (ret != SM_SUCCESS) {
			sm_log_write(ss_ctx->ssc_lctx,
				SS_LCAT_SERVER, SS_LMOD_SERVER,
				SM_LOG_ERROR, 2,
				"sev=ERROR, func=ss_handle_connections, pid=%d, sm_io_open()=%m"
				, (int) My_pid, ret);

			/* ??? */
			st_netfd_close(cli_fd);
			continue;
		}

		/* switch to non-blocking */
		sm_io_clrblocking(fp);

		r = ss_ctx->ssc_cnf.ss_cnf_timeout;
		ret = sm_io_setinfo(fp, SM_IO_WHAT_TIMEOUT, &r);
		if (ret != SM_SUCCESS) {
			sm_log_write(ss_ctx->ssc_lctx,
				SS_LCAT_SERVER, SS_LMOD_SERVER,
				SM_LOG_ERROR, 5,
				"sev=ERROR, func=ss_handle_connections, pid=%d, set_timeout()=%m"
				, (int) My_pid, ret);
			sm_io_close(fp, SM_IO_CF_NONE);
			continue;
		}

		ret = sm_io_setinfo(fp, SM_IO_DOUBLE, NULL);
		if (ret != SM_SUCCESS) {
			sm_log_write(ss_ctx->ssc_lctx,
				SS_LCAT_SERVER, SS_LMOD_SERVER,
				SM_LOG_ERROR, 5,
				"sev=ERROR, func=ss_handle_connections, pid=%d, set_double=%m"
				, (int) My_pid, ret);
			sm_io_close(fp, SM_IO_CF_NONE);
			continue;
		}

		SM_ASSERT(WAIT_THREADS(i) > 0);
		WAIT_THREADS(i)--;
		BUSY_THREADS(i)++;
		if (BUSY_THREADS(i) > MAXB_THREADS(i))
			MAXB_THREADS(i) = BUSY_THREADS(i);
		if (WAIT_THREADS(i) < ss_ctx->ssc_cnf.ss_cnf_min_wait_threads &&
		    TOTAL_THREADS(i) < Max_cur_threads)
		{
			/* Create another spare thread */
			if (st_thread_create(ss_handle_connections, (void *)i, 0, 0) == NULL)
				sm_log_write(ss_ctx->ssc_lctx,
					SS_LCAT_SERVER, SS_LMOD_SERVER,
					SM_LOG_ERROR, 2,
					"sev=ERROR, func=ss_handle_connections, pid=%d, st_thread_create=%m"
					, (int) My_pid, sm_err_temp(errno));
		}

		ss_sess->ssse_fp = fp;
		ss_sess->ssse_client = clt_addr.sin_addr;
		ss_sess->ssse_state = SSSE_ST_CONNECTED;
		ss_sess->ssse_idx = i;
		sm_str_clr(ss_sess->ssse_wr);
#if SS_TEST
		if (Test_client_ip.s_addr != INADDR_ANY)
			ss_sess->ssse_client = Test_client_ip;
#endif

		/* SM_ASSERT(SM_SUCCESS == ret); */
		if (0 == Max_cur_threads) {
			if (!SSC_IS_FLAG(ss_ctx, SSC_FL_SHUTDOWN))
				sm_log_write(ss_ctx->ssc_lctx,
					SS_LCAT_SERVER, SS_LMOD_SERVER,
					SM_LOG_WARN, 2,
					"sev=WARN, func=ss_handle_connections, pid=%d, Max_cur_threads=0"
					, (int) My_pid);
			SSSE_SET_FLAG(ss_sess, SSSE_FL_LOCAL_SSD);
			ret = SMTP_R_SSD;
		}
#if MTA_USE_PMILTER
		/* check system state */
		if (SSC_ST_SSD == ss_ctx->ssc_state) {
			/* similar for TEMP? */
			SSSE_SET_FLAG(ss_sess, SSSE_FL_LOCAL_SSD);
			ret = SMTP_R_SSD;
		}
#endif /* MTA_USE_PMILTER */

		/* SM_ASSERT(SM_SUCCESS == ret || SMTP_R_SSD == ret); */

		/* contact access db first? */
		if (SM_SUCCESS == ret && SSC_IS_CFLAG(ss_ctx, SSC_CFL_ACCESS_DB)) {
			uint32_t ltype;

			ltype = SMARA_LT_CLT_A_ACC
				|SMARA_LT_RVRS_N_ACC /* always?? */
#if MTA_USE_TLS
				|(SSC_IS_CFLAG(ss_ctx, SSC_CFL_SE_CONF)
					? SMARA_LT_SS_SE_CONF : 0)
#endif
				;

			sm_str_clr(ss_sess->ssse_str);
			sm_inet_inaddr2str(ss_sess->ssse_client, ss_sess->ssse_str);
			ret = sm_s2a_clt(ss_sess, ss_ctx->ssc_s2a_ctx,
					ss_sess->ssse_id, ss_sess->ssse_str,
					RT_S2A_CLT_A, ltype,
					SMARA_LFL_IPV4|SMARA_LFL_SUB
					|SMARA_LFL_RVRS4|SMARA_LFL_RVACC|SMARA_LFL_DNSBL);
			if (sm_is_err(ret)) {
				sm_log_write(ss_ctx->ssc_lctx,
					SS_LCAT_SERVER, SS_LMOD_SERVER,
					SM_LOG_ERROR, 5,
					"sev=ERROR, func=ss_handle_connections, ss_sess=%s, sm_s2a_clt=%m"
					, ss_sess->ssse_id, ret);
				goto sess_error;
			}
			if (!sm_is_err(ret)) {
				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, 5,
						"sev=ERROR, func=ss_handle_connections, ss_sess=%s, client_ipv4=%A, sm_w4q2s_reply=%m"
						, ss_sess->ssse_id, ss_sess->ssse_client.s_addr, ret);
				}
			}
			sm_log_write(ss_ctx->ssc_lctx,
				SS_LCAT_SERVER, SS_LMOD_SERVER,
				SM_LOG_INFO, 13,
				"sev=INFO, func=ss_handle_connections, ss_sess=%s, where=client_access, stat=%m"
				, ss_sess->ssse_id, ret);
			if (sm_is_err(ret)) {
				SSSE_SET_FLAG(ss_sess, SSSE_FL_LOCAL_SSD);
				ret = SMTP_R_SSD;
			}
			else if (SMAR_RISQUICK(ret)) {
				SSSE_SET_FLAG(ss_sess, SSSE_FL_QUICK);
				SMAR_RCLRQUICK(ss_sess->ssse_acc.ssa_reply_code);
				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" */

#if MTA_USE_TLS
			if (sm_is_success(ss_sess->ssse_maprescnf) &&
			    sm_str_getlen(ss_sess->ssse_str) > 0)
			{
				sm_ret_T r;

				r = ss_sess_conf(ss_sess);
				sm_log_write(ss_ctx->ssc_lctx,
					SS_LCAT_SERVER, SS_LMOD_SERVER,
					SM_LOG_DEBUG, 12,
					"sev=DBG, func=ss_handle_connections, ss_sess=%s, session_conf=%#T, ss_sess_conf=%r"
					, ss_sess->ssse_id, ss_sess->ssse_str, r);
			}
			else if (SM_IS_TEMP_ERR(ss_sess->ssse_maprescnf)) {
				sm_log_write(ss_ctx->ssc_lctx,
					SS_LCAT_SERVER, SS_LMOD_SERVER,
					SM_LOG_INFO, 9,
					"sev=INFO, func=ss_handle_connections, ss_sess=%s, ss_sess_conf_lookup=%r"
					, ss_sess->ssse_id, ss_sess->ssse_maprescnf);
				SSSE_SET_FLAG(ss_sess, SSSE_FL_LOCAL_SSD);
				ret = SMTP_R_SSD;
			}
#endif /* MTA_USE_TLS */


			/* reject accept session/transaction? */
			if (IS_SMTP_REPLY(ret) && SMTP_IS_REPLY_ERROR(ret)) {
				sm_log_write(ss_ctx->ssc_lctx,
					SS_LCAT_SERVER, SS_LMOD_SERVER,
					SM_LOG_DEBUG, 12,
					"sev=DBG, func=ss_handle_connections, ss_sess=%s, where=smar, sm_w4q2s_reply=%m, reply=%r, text=%#T"
					, ss_sess->ssse_id, ret
					, ss_sess->ssse_acc.ssa_reply_code
					, ss_sess->ssse_wr);

				/* 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 (SSC_IS_CFLAG(ss_ctx, SSC_CFL_DELAY_CHKS) &&
					    !SSSE_IS_FLAG(ss_sess, SSSE_FL_QUICK))
					{
						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);
				}
			}
		}

#if MTA_USE_PMILTER
/*
**  XXX COPY of smar/access check from above
**  1. should these two have the same functionality?
**  2. if so, put it into a function that can be called...
**  3. can milter override access checks? how do these two interact?
*/
		/* contact milter next? */
		if (SM_SUCCESS == ret
		    && SSC_IS_CFLAG(ss_ctx, SSC_CFL_PMILTER)
		    && SSC_IS_FLAG(ss_ctx, SSC_FL_PM_TRYCONN))
		{
			/* try to reconnect to milter */
			ret = ss_conn2milt(ss_ctx, ss_sess);
		}

		/* should milter be used for this session? */
		if (SM_SUCCESS == ret && SSC_IS_FLAG(ss_ctx, SSC_FL_PM_USE))
		{
			SSSE_SET_FLAG(ss_sess, SSSE_FL_PM_USE);
			ss_sess->ssse_s2q_id[SS_COMM_PMILTER] =
						ss_ctx->ssc_s2m_ctx->s2q_q_id;
		}

		/*
		**  Always call libpmilter for a new session even if
		**  SM_SCAP_PM_CNNCT is not set, otherwise libpmilter
		**  doesn't set up a its session context etc.
		**  This could be solved by making libpmilter more complex,
		**  i.e., on each call try to figure out whether the
		**  session context has been already established, but that's
		**  not worth the hassle right now.
		**
		**  Move this into pmilter.c?
		*/

		if (SSSE_IS_FLAG(ss_sess, SSSE_FL_PM_USE)) {
			ret = sm_s2m_clt(ss_sess,
					ss_ctx->ssc_s2m_ctx,
					ss_sess->ssse_id,
					(uint32_t) ss_sess->ssse_client.s_addr,
					(uint32_t) clt_addr.sin_port);
			if (sm_is_err(ret)) {
				sm_log_write(ss_ctx->ssc_lctx,
					SS_LCAT_SERVER, SS_LMOD_SERVER,
					SM_LOG_ERROR, 5,
					"sev=ERROR, func=ss_handle_connections, ss_sess=%s, sm_s2m_clt=%m"
					, ss_sess->ssse_id, ret);
				/* error is handled below */
			}
			else {
				ret = sm_w4q2s_reply(ss_sess, ss_ctx->ssc_cnf.ss_cnf_w4m2s,
					ss_ctx->ssc_s2m_ctx);
				SSSE_SET_FLAG(ss_sess, SSSE_FL_PM_CALLED);
			}
			sm_log_write(ss_ctx->ssc_lctx,
				SS_LCAT_SERVER, SS_LMOD_SERVER,
				SM_LOG_INFO, 13,
				"sev=INFO, func=ss_handle_connections, ss_sess=%s, where=client_pmilter, stat=%m"
				, ss_sess->ssse_id, ret);
			if (sm_is_err(ret)) {
				SSPM_TRY_AGAIN(ss_ctx, ss_sess);

				/* don't use milter anymore in this session */
				SSSE_CLR_FLAG(ss_sess, SSSE_FL_PM_USE);
				if (SSC_IS_MFLAG(ss_ctx, SSC_MFL_PM_421)) {
					SSSE_SET_FLAG(ss_sess, SSSE_FL_LOCAL_SSD);
					ret = SMTP_R_SSD;
				}
				else	/* ignore error */
					ret = SM_SUCCESS;
			}
			else {
				if (SMAR_RISQUICK(ret)) {
					/*
					**  Don't use milter anymore in this
					**  session (no matter whether it was
					**  due to OK, REJECT, RELAY, etc)
					**  Note: QUICK:OK still need a relay
					**  check at RCPT!
					*/

					SSSE_CLR_FLAG(ss_sess, SSSE_FL_PM_USE);
					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" */
			}

			/* reject accept session/transaction? */
			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_DEBUG, 12,
					"sev=DBG, func=ss_handle_connections, ss_sess=%s, where=pmilter, ret=%r, text=%@T"
					, ss_sess->ssse_id, ret
					, ss_sess->ssse_wr);

				/* ignore bogus text */
				if (!SMTP_REPLY_MATCHES_RCODE(ss_sess->ssse_wr, ret, 0, rc)) {
					(void) ss_crt_reply(ss_sess->ssse_wr,
						ret, SS_PHASE_OTHER, true);
				}

				/* 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 (SSC_IS_CFLAG(ss_ctx, SSC_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 /* MTA_USE_PMILTER */

		/* ret == {SM_SUCCESS,SMTP_R_SSD} || IS_SMTP_REPLY(ret) */

		/* tell QMGR about it? necessary for further access control */
		if (ret != SMTP_R_SSD) {
			/* reject accept session/transaction? */
			if (IS_SMTP_REPLY(ret) && SMTP_IS_REPLY_ERROR(ret))
				SSSE_SET_FLAG(ss_sess, SSSE_FL_NULL);
			ret = sm_s2q_nseid(ss_sess, ss_ctx->ssc_s2q_ctx, ss_sess->ssse_id);
			if (sm_is_err(ret))
				goto sess_error;
			ret = sm_w4q2s_reply(ss_sess, TMO_W4Q2S, ss_ctx->ssc_s2q_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_handle_connections, ss_sess=%s, client_ipv4=%A, where=after_sm_s2q_nseid, sm_w4q2s_reply=%m"
					, ss_sess->ssse_id, ss_sess->ssse_client.s_addr, ret);

				/* inform the client */
				ret = SMTP_R_SSD;
			}

			/*
			**  qmgr doesn't need to be informed if it rejected
			**  the connection. other errors?
			*/

			/* HACK: clear quick for now without saving it! */
			SMAR_RCLRQUICK(ret);
			if (ret != SMTP_R_SSD)
				SSSE_SET_FLAG(ss_sess, SSSE_FL_CSEID);

#if 0
			/*
			**  XXX Same as above?
			**  Where should logging happen? -> ss_hdl_session()
			**  Note: a rejection from QMGR should NOT be delayed
			**  because QMGR didn't accept the session. Hence this
			**  must be treated as "QUICK" or some flag must be
			**  set not to bother QMGR with further informations
			**  about this sessions.
			**  PS: can QMGR return anything else but SMTP_R_SSD?
			*/

			else if (IS_SMTP_REPLY(ret) && SMTP_IS_REPLY_ERROR(ret)) {
				sm_log_write(ss_ctx->ssc_lctx,
					SS_LCAT_SERVER, SS_LMOD_SERVER,
					SM_LOG_DEBUG, 12,
					"sev=DBG, func=ss_handle_connections, ss_sess=%s, qmgr_reply=%r, text=%#T"
					, ss_sess->ssse_id, ret
					, ss_sess->ssse_wr);

				/* 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 (SSC_IS_CFLAG(ss_ctx, SSC_CFL_DELAY_CHKS)) {
						sm_str_clr(ss_sess->ssse_wr);
						ret = SMTP_R_OK;
					}
				}
				else {
					/*
					**  Can't accept session: ENOMEM
					**  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 */

			/* check again... could be set in the meantime */
			if (0 == Max_cur_threads) {
				if (!SSC_IS_FLAG(ss_ctx, SSC_FL_SHUTDOWN))
					sm_log_write(ss_ctx->ssc_lctx,
						SS_LCAT_SERVER, SS_LMOD_SERVER,
						SM_LOG_ERROR, 2,
						"sev=ERROR, func=ss_handle_connections, pid=%d, Max_cur_threads=0, ss_sess=%s"
						, (int) My_pid, ss_sess->ssse_id);
				ret = SMTP_R_SSD;
				SSSE_SET_FLAG(ss_sess, SSSE_FL_CSEID);
			}
		}

		/* ss_sess must be deallocated by ss_hdl_session() */
		ret = ss_hdl_session(ss_sess, ret);
		ss_sess = NULL;
#if 0
		/*
		**  How much do we care? What kind of errors should
		**   stop the server?
		*/

		if (sm_is_err(ret))
			goto sess_error;
#endif /* 0 */

		if (SSC_IS_FLAG(ss_ctx, SSC_FL_SHUTDOWN))
			goto sess_error;

		/* get a new session context */
		ret = ss_sess_new(&Ss_ctx, &ss_sess);

  sess_error:
		if (ss_sess != NULL && ss_sess->ssse_fp != NULL) {
			sm_io_close(ss_sess->ssse_fp, SM_IO_CF_NONE);
			ss_sess->ssse_fp = NULL;
		}
		if (sm_is_err(ret)) {
			/*
			**  On what kind of errors do we want to terminate?
			**  Basically only on fatal errors.  On temporary
			**  errors (including ENOMEM) at least one thread
			**  has to stay alive (for some time).
			*/

			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();
				sm_log_write(ss_ctx->ssc_lctx,
					SS_LCAT_SERVER, SS_LMOD_SERVER,
					SM_LOG_ERROR, 1,
					"sev=ERROR, func=ss_handle_connections, pid=%d, status=forcing_shutdown"
					, (int) My_pid);
			}
			ss_sess_free(ss_sess);
			return NULL;
		}
		WAIT_THREADS(i)++;
		SM_ASSERT(BUSY_THREADS(i) > 0);
		BUSY_THREADS(i)--;
		if (WAIT_THREADS(i) > Max_cur_threads + 1)
			break;
	}
	ss_sess_free(ss_sess);

	SM_ASSERT(WAIT_THREADS(i) > 0);
	WAIT_THREADS(i)--;
	if (SSC_IS_FLAG(ss_ctx, SSC_FL_SHUTDOWN)) {
		char sig;

		sig = SM_SIG_SHUT;
		(void) st_write(Sig_pipe[SM_WR_SIG_PIPE], &sig, sizeof(char),
				(st_utime_t)10);
	}
	return NULL;
}

/*
**  SS_DUMP_INFO  -- print status information to smioerr
**
**	Parameters:
**		ss_ctx -- SMTP server context
**
**	Returns:
**		nothing
*/

static void
ss_dump_info(ss_ctx_P ss_ctx)
{
	int i, len, s, l;
	ssize_t b;
	sm_str_P str;
	struct rusage rusage;

	s = (Sk_count * 512) + 1024;
	str = sm_str_new(NULL, s, s + 1024);
	if (NULL == str) {
		sm_log_write(ss_ctx->ssc_lctx,
			SS_LCAT_SERVER, SS_LMOD_SERVER,
			SM_LOG_ERROR, 1,
			"sev=ERROR, func=ss_dump_info, bytes=%d, malloc=failed, error=%m"
			, s, sm_err_temp(errno));
		return;
	}

	len = sm_strprintf(str, "Process pid=%d:\n", (int) My_pid);
	for (i = 0; i < Sk_count; i++) {
		l = sm_strprintf(str,
			"Listening Socket #%d:\n"
			"-------------------------\n"
			"Address=                    %s:%d\n"
			"Thread limits (min/cur/max)=%u/%u/%u\n"
			"Waiting threads=            %d\n"
			"Busy threads=               %d\n"
			"Max busy threads=           %d\n"
			"Requests served=            %lu\n"
			"Transactions=               %lu\n"
			, i, ss_sck_ctx[i].sssc_addr, ss_sck_ctx[i].sssc_port
			, Ss_ctx.ssc_cnf.ss_cnf_max_wait_threads
			, Max_cur_threads
			, Ss_ctx.ssc_cnf.ss_cnf_max_threads
			, WAIT_THREADS(i), BUSY_THREADS(i), MAXB_THREADS(i)
			, RQST_COUNT(i)
			, TA_COUNT(i));
	}

	i = getrusage(RUSAGE_SELF, &rusage);
	if (0 == i) {
		l = sm_strprintf(str,
			"ru_utime=   %7ld.%07ld\n"
			"ru_stime=   %7ld.%07ld\n"
			"ru_maxrss=  %7ld\n"
			"ru_ixrss=   %7ld\n"
			"ru_idrss=   %7ld\n"
			"ru_isrss=   %7ld\n"
			"ru_minflt=  %7ld\n"
			"ru_majflt=  %7ld\n"
			"ru_nswap=   %7ld\n"
			"ru_inblock= %7ld\n"
			"ru_oublock= %7ld\n"
			"ru_msgsnd=  %7ld\n"
			"ru_msgrcv=  %7ld\n"
			"ru_nsignals=%7ld\n"
			"ru_nvcsw=   %7ld\n"
			"ru_nivcsw=  %7ld\n"
			, rusage.ru_utime.tv_sec
			, rusage.ru_utime.tv_usec
			, rusage.ru_stime.tv_sec
			, rusage.ru_stime.tv_usec
			, rusage.ru_maxrss
			, rusage.ru_ixrss
			, rusage.ru_idrss
			, rusage.ru_isrss
			, rusage.ru_minflt
			, rusage.ru_majflt
			, rusage.ru_nswap
			, rusage.ru_inblock
			, rusage.ru_oublock
			, rusage.ru_msgsnd
			, rusage.ru_msgrcv
			, rusage.ru_nsignals
			, rusage.ru_nvcsw
			, rusage.ru_nivcsw
			);
	}

	(void) sm_prtrlimits(str);

	sm_io_write(smioerr, sm_str_getdata(str), sm_str_getlen(str), &b);
	SM_STR_FREE(str);
}

#if SSQ_DEBUG
void
dump_thrd_info(void)
{
	sm_io_fprintf(smioerr, "threads: wait=%d, busy=%d\n"
			, WAIT_THREADS(0), BUSY_THREADS(0));
}
#endif /* SSQ_DEBUG */

#if 0
/*
**  SS_LOAD_CONFIGS  -- Configuration loading function stub. NOT IMPLEMENTED.
**
**	Parameters:
**		ss_ctx -- SMTP server context
**
**	Returns:
**		nothing.
*/

static void
ss_load_configs(ss_ctx_P ss_ctx)
{
	sm_log_write(ss_ctx->ssc_lctx,
		SS_LCAT_SERVER, SS_LMOD_SERVER,
		SM_LOG_INFO, 12,
		"sev=INFO, func=ss_load_configs, pid=%d, status=configuration_reload_not_yet_implemented"
		, (int) My_pid);
}
#endif /* 0 */


syntax highlighted by Code2HTML, v. 0.9.1