/*
 * SMTP sink based on server.c
 *
 * $Id: smtps2.c,v 1.110 2007/08/23 23:25:44 ca Exp $
 */

/*
 * Copyright (C) 2000 Silicon Graphics, Inc.  All Rights Reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met: 
 *
 * 1. Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer. 
 * 2. Redistributions in binary form must reproduce the above copyright
 *    notice, this list of conditions and the following disclaimer in the
 *    documentation and/or other materials provided with the distribution.
 * 3. Neither the name of Silicon Graphics, Inc. nor the names of its
 *    contributors may be used to endorse or promote products derived from
 *    this software without specific prior written permission. 
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
 * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
 * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
 * HOLDERS AND CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED
 * TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
 * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
 * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
 * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
 * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <time.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <sys/socket.h>
#include <sys/wait.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <netdb.h>
#include <fcntl.h>
#include <signal.h>
#include <pwd.h>
#include "sm/sysexits.h"
#include "sm/io.h"
#define MTA_USE_STATETHREADS 1
#include "sm/cmsg.h"
#include "sm/stsock.h"
#include "sm/bitstring.h"
#include "sm/str.h"
#include "sm/queue.h"
#include "sm/net.h"
#include "st.h"
#include "data.h"
#include "sm/resource.h"
#include "sm/mta.h"
#include "sm/smreplycodes.h"
#if SMTPS2_CDB
#include "sm/cdb.h"
#endif

#include <ctype.h>

#if !HAVE_SNPRINTF
# define snprintf sm_snprintf
#endif

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


/******************************************************************
 * Server configuration parameters
 */

/* Log files */
#define PID_FILE    "pid"
#define ERRORS_FILE "errors"
#define ACCESS_FILE "access"

/* Default server port */
#define SERV_PORT_DEFAULT 8000

/* Socket listen queue size */
#define LISTENQ_SIZE_DEFAULT 256

/* Max number of listening sockets ("hardware virtual servers") */
#define MAX_BIND_ADDRS 16

/* 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 */
#define FD_PER_THREAD 1

/* Access log buffer flushing interval (in seconds) */
#define ACCLOG_FLUSH_INTERVAL 30

/* Request read timeout (in seconds) */
#define REQUEST_TIMEOUT 30

#define SM_DROP_IT	'D'

/*
 * multiline replies are generated if the first code is an
 * SMTP reply code + MULTILINE_SMTP_OFF
 */
#define MULTILINE_SMTP_OFF	4
#define IS_MULTILINE_SMTP_CODE(buf, off)	\
	(((buf[off] == '6') || (buf[off] == '8') || (buf[off] == '9')) \
	&& (buf[(off)+1] >= '0') && (buf[(off)+1] <= '9')	\
	&& (buf[(off)+2] >= '0') && (buf[(off)+2] <= '9'))

/******************************************************************
 * Global data
 */

struct socket_info
{
	st_netfd_t      nfd;		/* Listening socket */
	char           *addr;		/* Bind address */
	int             port;		/* Port */
	int             wait_threads;	/* # of threads waiting to accept  */
	int             busy_threads;	/* # of threads processing request */
	int             maxb_threads;	/* max # of threads */
	int             concurrent;
	unsigned int    rqst_count;	/* Total # of processed requests */
	unsigned int    mail_count;	/* Total # of processed transactions */
	unsigned int    rcpt_count;	/* Total # of recipients */
	unsigned int    rcpt_cnt_ok;	/* Total # of accepted recipients */
	unsigned int    ta_cnt_ok;	/* Total # of accepted transactions */
	unsigned int    ta_cnt;		/* Total # of transactions */
	unsigned int    ta_cnt_each;	/* for -e option */
	unsigned int    total_rate;
	int             lmtp;		/* use LMTP */
	sm_file_T      *da_fp;		/* fp for data */
} srv_socket[MAX_BIND_ADDRS];	/* Array of listening sockets           */

static int sk_count = 0;        /* Number of listening sockets          */

static int vp_count = 0;        /* Number of server processes (VPs)     */
static pid_t *vp_pids;          /* Array of VP pids                     */

static int my_index = -1;       /* Current process index */
static pid_t my_pid = -1;       /* Current process pid   */
static long nrejmsgs = 0;       /* Counter of messages to reject */
static int rejmsgs = 0;         /* whether to reject messages */
static long randrej = 0;        /* randomly reject messages */

static time_t oktime = 0;
static time_t firstmail = 0;
static time_t lastmail = 0;
static time_t firstNmail = 0;
static unsigned int eachN = 0;
static char *fullehlo = NULL;
static char greeting[] = "220";
static char ehloresp[] = "250";
static char starttls[] = "xxx";
static char rsetresp[] = "250";
#define FINALDOT "250 ok final dot"
static char finaldot[] = FINALDOT;
#define EXPLICIT_FINALDOT	(strcmp(finaldot, FINALDOT) != 0)
static char dataresponse[] = "354";
static char *Myname = "local.host";

static st_netfd_t sig_pipe[2];  /* Signal pipe           */

/*
 * Configuration flags/parameters
 */
static bool interactive_mode = true;
static bool serialize_accept = false;
static bool log_access = false;
static bool pipelining = false;
static bool offer_drr = false;
static bool use_drr = false;
static bool offer_rsad = false;
static bool use_rsad = false;
static bool dsn = false;
static bool onercpt = false; /* accept only one rcpt per TA */
static bool drop421 = true; /* close connection when returning 421 */
static bool Stoponerror = false;
static bool Hugegreeting = false;
static bool UseSMTP = false;
static bool idisplay = false;	/* interactive display */
static int tempok = 0; /* cause a temp fail first, then accept */
static int permok = 0; /* cause a permanent fail first, then accept */
static int tempperm = 0; /* cause a temp fail first, then a perm fail */
static bool wastemp = false;
static bool wasperm = false;
static char temp2perm[3] = "00"; /* cause a temp fail first, then a perm fail */
static bool was2temp = false;	/* but in two different stages */
static char temptemp[3] = "00"; /* causes two temp fails */
static bool wastt = false;
static char *logdir = ".";
static char *username = NULL;
static char *fd_socket = NULL;
static int listenq_size = LISTENQ_SIZE_DEFAULT;
static int errfd = STDERR_FILENO;
static int debug = 0;
static int sequence = -1;
static int seqleft = -1;
static unsigned int tasexpected	= 0;
static bitstr_t *gotit = NULL;
#if SMTPS2_CDB
static int Maxcdbs = 0;
static int Cdbfiles = 0;
static id_count_T Rm_id_count = 0;
static bool Usecdb = false;
static bool Unsafe = false;
static bool Flat = false;
static cdb_ctx_P Cdb_ctx = NULL;
static char *Cdb_base = NULL;
typedef char *ss_id_T;
#include "../../smtps/id.c"
#endif /* SMTPS2_CDB */
static bool writedata = false;
static size_t total_size = 0;
static unsigned long count = 0;
static int iotimeout = REQUEST_TIMEOUT;

typedef struct ss_rcpt_S	ss_rcpt_T, *ss_rcpt_P;
typedef struct ss_rcpts_S	ss_rcpts_T, *ss_rcpts_P;

struct ss_rcpt_S
{
	char						*ssr_reply;
	SIMPLEQ_ENTRY(ss_rcpt_S)	 ssr_link;
};
SIMPLEQ_HEAD(ss_rcpts_S, ss_rcpt_S);

/* operations on rcpt lists */
#define SS_RCPTS_INIT(ss_rcpts_hd)	SIMPLEQ_INIT(ss_rcpts_hd)
#define SS_RCPTS_EMPTY(ss_rcpts_hd)	SIMPLEQ_EMPTY(ss_rcpts_hd)
#define SS_RCPTS_FIRST(ss_rcpts_hd)	SIMPLEQ_FIRST(ss_rcpts_hd)
#define SS_RCPTS_END(ss_rcpts_hd)	SIMPLEQ_END(ss_rcpts_hd)
#define SS_RCPTS_NEXT(rcpt)		SIMPLEQ_NEXT(rcpt, ssr_link)
#define SS_RCPTS_INSERT_TAIL(ss_rcpts_hd, rcpt) SIMPLEQ_INSERT_TAIL(ss_rcpts_hd, rcpt, ssr_link)
#define SS_RCPTS_REMOVE_HEAD(ss_rcpts_hd)	SIMPLEQ_REMOVE_HEAD(ss_rcpts_hd, ssr_link)


/*
 * Thread throttling parameters (all numbers are per listening socket).
 * Zero values mean use default.
 */
static int max_threads = 0;       /* Max number of threads         */
static int max_wait_threads = 0;  /* Max number of "spare" threads */
static int min_wait_threads = 2;  /* Min number of "spare" threads */

static int datacomp = 0;       /* perform some computation */
static int datawait = 0;       /* sleep... */
static int delayreply = 0;     /* how long to delay replies */
static int msgwait = 0;        /* sleep... */
static unsigned int delaydotreply = 0;	/* how long to delay reply to final dot */
static unsigned int delaydotrand = 0;	/* random delay */
static st_utime_t delaymsg = 0;	/* how long to delay read in msg */
static unsigned long max_msg_size = 0;	/* SIZE */

/******************************************************************
 * Useful macros
 */

#ifndef INADDR_NONE
#define INADDR_NONE 0xffffffff
#endif

#define SEC2USEC(s) ((s)*1000000LL)

#define WAIT_THREADS(i)	(srv_socket[i].wait_threads)
#define BUSY_THREADS(i)	(srv_socket[i].busy_threads)
#define MAXB_THREADS(i)	(srv_socket[i].maxb_threads)
#define CONC_THREADS(i)	(srv_socket[i].concurrent)
#define TOTAL_THREADS(i) (WAIT_THREADS(i) + BUSY_THREADS(i))
#define RQST_COUNT(i)	(srv_socket[i].rqst_count)
#define MAIL_COUNT(i)	(srv_socket[i].mail_count)
#define RCPT_COUNT(i)	(srv_socket[i].rcpt_count)
#define TA_CNT_OK(i)	(srv_socket[i].ta_cnt_ok)
#define TA_CNT_EACH(i)	(srv_socket[i].ta_cnt_each)
#define TOTAL_RATE(i)	(srv_socket[i].total_rate)
#define RCPT_CNT_OK(i)	(srv_socket[i].rcpt_cnt_ok)
#define TA_CNT(i)	(srv_socket[i].ta_cnt)


/******************************************************************
 * Forward declarations
 */

static void usage(const char *progname);
static void parse_arguments(int argc, char *argv[]);
static void start_daemon(void);
static void set_thread_throttling(void);
static void create_listeners(void);
static void change_user(void);
static void open_log_files(void);
static void wrt_pid(void);
static void start_processes(void);
static void wdog_sighandler(int signo);
static void child_sighandler(int signo);
static void install_sighandlers(void);
static void start_threads(void);
static void *process_signals(void *arg);
static void *flush_acclog_buffer(void *arg);
static void *handle_connections(void *arg);
static void dump_server_info(void);
static void dump_stats(int fd, int myid, int all);
static void ss_shutdown(void);

static void Signal(int sig, void (*handler)(int));
static int cpu_count(void);

extern void handle_session(int srv_socket_index, st_netfd_t cli_nfd);
extern void load_configs(void);
extern void logbuf_open(void);
extern void logbuf_flush(void);
extern void logbuf_close(void);

/* Error reporting functions defined in the error.c file */
extern void err_sys_report(int fd, const char *fmt, ...);
extern void err_sys_quit(int fd, const char *fmt, ...);
extern void err_sys_dump(int fd, const char *fmt, ...);
extern void err_report(int fd, const char *fmt, ...);
extern void err_quit(int fd, const char *fmt, ...);

/*
 * General server example: accept a client connection and do something.
 * This program acts as an SMTP sink.
 *
 * 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.
 */

int 
main(int argc, char *argv[])
{
	if (geteuid() == 0)
		err_quit(errfd, "ERROR: do not run this as super-user!");

	/* Parse command-line options */
	parse_arguments(argc, argv);

	/* Allocate array of server pids */
	if ((vp_pids = calloc(vp_count, sizeof(pid_t))) == NULL)
		err_sys_quit(errfd, "ERROR: calloc failed");

	/* Start the daemon */
	if (!interactive_mode)
		start_daemon();

	/* Initialize the ST library */
	if (st_init() < 0)
		err_sys_quit(errfd, "ERROR: initialization failed: st_init");

#if SMTPS2_CDB
	ss_id_init(1);
	Rm_id_count = 0;
#endif

	/* Set thread throttling parameters */
	set_thread_throttling();

	/* don't disable this, some mcp test relies on it */
	wrt_pid();

	/* Create listening sockets */
	create_listeners();

	/* Change the user */
	if (username)
		change_user();

	/* Open log files */
	open_log_files();

	/* Start server processes (VPs) */
	start_processes();

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

	/* Install signal handlers */
	install_sighandlers();

	/* Load configuration from config files */
	load_configs();

	/* Start all threads */
	start_threads();

	/* Become a signal processing thread */
	process_signals(NULL);

	/* NOTREACHED */
	return 1;
}


/******************************************************************/

static void 
usage(const char *progname)
{
	fprintf(stderr, "Usage: %s [options]\n"
		"Possible options:\n"
		"-0                    Use SMTP.\n"
		"-1                    Accept only one recipient per transaction.\n"
		"-2 stage              First temporary then permanent error.\n"
		"                      Stage is one of\n"
		"                      M: Mail\n"
		"                      R: Recipient\n"
		"                      D: DATA\n"
		"                      d: final dot\n"
		"-3 stages             First temporary then permanent error;\n"
		"                      neeed to specify exactly two stages.\n"
		"-4                    Do not drop connection after returning 421\n"
		"-5 stages             Two (different) temporary errors for two stages.\n"
		"-6 stage              First temporary error then accept.\n"
		"-7 stage              First permanent error then accept.\n"
		"-8                    Offer Deferred RCPT Reply\n"
		"-9                    Offer RCPT status after dot.\n"
		"-a                    Enable access logging.\n"
		"-A SMTP               Use SMTP reply code for DATA response.\n"
		"-b host:port          Bind to specified address. Multiple"
		" addresses\n"
		"                      are permitted.\n"
		"-B n                  Wait n seconds before accepting message data.\n"
#if SMTPS2_CDB
		"-C basedir/           Use CDB (write mail to disk).\n"
#endif
		"-c n                  Show counter (print if counter %% n == 0)\n"
		"-d n                  Set debug level.\n"
		"-D n                  Perform data computation.\n"
		"-e n                  Print elapsed time after each n messages.\n"
		"-E SMTP               Use SMTP reply code for EHLO response.\n"
		"-F SMTP               Use SMTP reply code for final dot response.\n"
		"-G SMTP               Use SMTP reply code for greeting.\n"
		"                      If the greeting starts with '%c' then\n"
		"                      the connection is dropped without greeting.\n"
		"-g text               Use text for EHLO response (should be \"250-...\").\n"
		"-H                    Use a huge greeting.\n"
		"-h                    Print this message.\n"
		"-I SMTP               Use SMTP reply code for RSET response.\n"
		"-i                    Run in interactive mode [default].\n"
		"-J delay              Wait delay micro-seconds before reading another buffer of the message.\n"
		"-j                    Background mode (fork()).\n"
		"-k delay[:rand]       Wait delay seconds before replying to final dot.\n"
		"                      rand is an optional, maximum random time to sleep (additionally).\n"
		"-L host:port          Same as -b, but speak LMTP\n"
		"                      host:port can also be unix:/path/to/socket\n"
		"-l directory          Use directory for logfile [default: \".\"].\n"
#if SMTPS2_CDB
		"-M n                  Maximum number of CDB files.\n"
#endif
		"-N myname             Use myname as hostname in greeting.\n"
		"-n                    Offer DSN.\n"
		"-o n                  Accept all mail after n seconds.\n"
		"-O path               Open fd is passed via Unix socket path.\n"
		"-P                    Offer PIPELINING.\n"
		"-p num_processes      Create specified number of processes.\n"
		"-q backlog            Set max length of pending connections"
		" queue.\n"
		"-R n                  Randomly reject SMTP commands (temporarily).\n"
		"                      A command is rejected if random() < n.\n"
		"-r n                  Reject only first n SMTP commands.\n"
		"-s n                  Expect n messages with 1..n as body.\n"
		"-S                    Serialize all accept() calls.\n"
		"-t min_thr:max_thr    Specify thread limits per listening"
		" socket\n"
		"                      across all processes.\n"
		"-T n                  Expect n (accepted) messages.\n"
		"-u user               Change server's user id to specified"
		" value.\n"
#if SMTPS2_CDB
		"-U                    Don't commit CDB files (fsync()).\n"
#endif
		"-v                    In LMTP mode: delay error replies for RCPTs.\n"
		"-w n                  Wait n seconds before accepting DATA.\n"
		"-W n                  Wait n seconds before replying to any command.\n"
		"                      This also applies to the initial greeting.\n"
#if SMTPS2_CDB
		"-X                    Stop on some errors.\n"
#endif
		"-x SMTP               Offer STARTTLS but reply with specified error.\n"
#if !SMTPS2_CDB
		"-y                    Write incoming data into a single file.\n"
#endif
		"-Z n                  Offer SIZE n.\n"
		"-z n                  Set I/O timeout [%d].\n"
		"\nSignals:\n"
		"HUP                   Reload configuration, rotate logfile\n"
		"TERM                  Terminate\n"
		"USR1                  Show statististcs\n"
		"USR2                  Reset counter for option -e\n"
		, progname
		, SM_DROP_IT
		, iotimeout
		);
	exit(EX_USAGE);
}


/******************************************************************/

static void
parse_arguments(int argc, char *argv[])
{
	extern char    *optarg;
	int             opt;
	char           *c, *e;

	drop421 = true;
	while ((opt = getopt(argc, argv,
		"012:3:45:6:7:89A:aB:b:C:c:D:d:E:e:F:fG:g:HhI:iJ:jk:L:l:M:mN:nO:o:Pp:q:R:r:Ss:T:t:Uu:vW:w:Xx:yZ:z:"))
		!= -1)
	{
		switch (opt)
		{
		case '0':
			UseSMTP = true;
			break;
		case '1':
			onercpt = true;
			break;
		case '2':
			tempperm = *optarg;
			break;
		case '3':
			if (strlen(optarg) != 2)
				err_quit(errfd,
					"ERROR: need 2 stages for -3",
					optarg);
			temp2perm[0] = optarg[0];
			temp2perm[1] = optarg[1];
			break;
		case '4':
			drop421 = false;
			break;
		case '5':
			if (strlen(optarg) != 2)
				err_quit(errfd,
					"ERROR: need 2 stages for -3",
					optarg);
			temptemp[0] = optarg[0];
			temptemp[1] = optarg[1];
			break;
		case '6':
			tempok = *optarg;
			break;
		case '7':
			permok = *optarg;
			break;
		case '8':
			offer_drr = true;
			break;
		case '9':
			offer_rsad = true;
			break;
		case 'a':
			log_access = true;
			break;
		case 'A':
			if (IS_SMTP_CODE(optarg, 0))
				strlcpy(dataresponse, optarg,
					sizeof(dataresponse));
			break;
		case 'L':
			++srv_socket[sk_count].lmtp;
			/* FALLTHROUGH */
		case 'b':
			if (sk_count >= MAX_BIND_ADDRS)
				err_quit(errfd,
					"ERROR: max number of bind addresses (%d) exceeded",
					 MAX_BIND_ADDRS);
			if ((c = strdup(optarg)) == NULL)
				err_sys_quit(errfd, "ERROR: strdup");
			srv_socket[sk_count++].addr = c;
			break;
		case 'B':
			msgwait = strtoul(optarg, &e, 0);
			break;
#if SMTPS2_CDB
		case 'C':
			Cdb_base = strdup(optarg);
			if (NULL == Cdb_base)
				err_quit(errfd,
					"ERROR: strdup for CDB %s failed",
					optarg);
			Usecdb = true;
			break;
#endif /* SMTPS2_CDB */
		  case 'c':
			count = (unsigned long) strtoul(optarg, NULL, 0);
			break;
		case 'd':
			debug = atoi(optarg);
			break;
		case 'D':
			datacomp = atoi(optarg);
			break;
		case 'e':
			eachN = strtoul(optarg, NULL, 0);
			break;
		case 'E':
			strlcpy(ehloresp, optarg, sizeof(ehloresp));
			break;
#if SMTPS2_CDB
		case 'f':
			Flat = true;
			break;
#endif /* SMTPS2_CDB */
		case 'F':
			strlcpy(finaldot, optarg, sizeof(finaldot));
			break;
		case 'G':
			strlcpy(greeting, optarg, sizeof(greeting));
			break;
		case 'g':
			fullehlo = strdup(optarg);
			if (NULL == fullehlo)
				err_quit(errfd,
					"ERROR: strdup for fullehlo %s failed",
					optarg);
			break;
		case 'H':
			Hugegreeting = true;
			break;
		case 'I':
			if (IS_SMTP_CODE(optarg, 0))
				strlcpy(rsetresp, optarg, sizeof(rsetresp));
			break;
		case 'i':
			interactive_mode = true;
			break;
		case 'J':
			delaymsg = strtoul(optarg, &e, 0);
			break;
		case 'j':
			interactive_mode = false;
			break;
		case 'k':
			delaydotreply = strtoul(optarg, &e, 0);
			if (e != 0 && *e == ':')
			{
				++e;
				delaydotrand = strtoul(e, &c, 0);
			}
			break;
		case 'l':
			logdir = strdup(optarg);
			if (NULL == logdir)
				err_quit(errfd,
					"ERROR: strdup for logdir %s failed",
					optarg);
			break;
#if SMTPS2_CDB
		case 'M':
			Maxcdbs = atoi(optarg);
			break;
#endif
		  case 'm':
			idisplay = true;
			break;
		case 'N':
			Myname = strdup(optarg);
			if (NULL == Myname)
				err_quit(errfd,
					"ERROR: strdup for myname %s failed",
					optarg);
			break;
		case 'n':
			dsn = true;
			break;
		case 'o':
			oktime = atoi(optarg) + st_time();
			break;
		case 'O':
			fd_socket = strdup(optarg);
			if (NULL == fd_socket)
				err_quit(errfd,
					"ERROR: strdup for fd_socket %s failed",
					optarg);
			break;
		case 'p':
			vp_count = atoi(optarg);
			if (vp_count < 1)
				err_quit(errfd,
					"ERROR: invalid number of processes: %s",
					optarg);
			break;
		case 'P':
			pipelining = true;
			break;
		case 'q':
			listenq_size = atoi(optarg);
			if (listenq_size < 1)
				err_quit(errfd,
					"ERROR: invalid listen queue size: %s",
					optarg);
			break;
		case 'R':
			randrej = atol(optarg);
			break;
		case 'r':
			nrejmsgs = atol(optarg);
			rejmsgs = 1;
			break;
		case 's':
			sequence = (int) strtol(optarg, &c, 10);
			if (sequence < 1)
				err_quit(errfd,
					"ERROR: invalid number for sequence: %s",
					optarg);
			gotit = bit_alloc(sequence + 1);
			if (NULL == gotit)
				err_quit(errfd,
					"ERROR: can't allocate bit array of size %d",
					bitstr_size(sequence));
			bit_nclear(gotit, 0, sequence);
			bit_set(gotit, 0);
			seqleft = sequence;
			break;
		case 'S':
			/*
		         * 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.
		         */

			serialize_accept = true;
			break;
		case 't':
			max_wait_threads = (int) strtol(optarg, &c, 10);
			if (*c++ == ':')
				max_threads = atoi(c);
			if (max_wait_threads < 0 || max_threads < 0)
				err_quit(errfd, "ERROR: invalid number of threads: %s", optarg);
			break;
		case 'T':
			tasexpected = (unsigned int) strtol(optarg, &c, 10);
			if (tasexpected < 1)
				err_quit(errfd,
					"ERROR: invalid number for -T: %s",
					optarg);
			break;
		case 'u':
			username = optarg;
			break;
#if SMTPS2_CDB
		case 'U':
			Unsafe = true;
			break;
#endif /* SMTPS2_CDB */
		case 'v':
			++srv_socket[sk_count].lmtp;
			break;
		case 'w':
			datawait = atoi(optarg);
			break;
		case 'W':
			delayreply = atoi(optarg);
			break;
		case 'X':
			Stoponerror = true;
			break;
		case 'x':
			if (optarg[0] != '4' && optarg[0] != '5')
				err_quit(errfd,
					"ERROR: invalid reply code for -x: %s",
					optarg);
			strlcpy(starttls, optarg, sizeof(starttls));
			break;
		case 'y':
#if SMTPS2_CDB
			err_report(errfd, "ERROR: option 'y' not available");
#else
			writedata = true;
#endif
			break;
		case 'Z':
			max_msg_size = atol(optarg);
			break;
		case 'z':
			iotimeout = atoi(optarg);
			break;
		case 'h':
		case '?':
			usage(argv[0]);
		}
	}

	if (logdir == NULL && !interactive_mode)
	{
		err_report(errfd, "ERROR: logging directory is required");
		usage(argv[0]);
	}
	if (offer_rsad && offer_drr) {
		err_report(errfd, "ERROR: cannot offer DRR and RSAD together");
		usage(argv[0]);
	}

#if SMTPS2_CDB
	if (NULL == Cdb_base)
		Cdb_base = "./";
	opt = cdb_start(Cdb_base, &Cdb_ctx);
	if (sm_is_err(opt))
		err_quit(errfd, "ERROR: cdb_start=%#x", opt);
#endif /* SMTPS2_CDB */

	if (vp_count == 0 && (vp_count = cpu_count()) < 1)
		vp_count = 1;

	if (sk_count == 0)
	{
		sk_count = 1;
		srv_socket[0].addr = "0.0.0.0";
	}
}


/******************************************************************/

static void 
start_daemon(void)
{
	pid_t           pid;

	/* Start forking */
	if ((pid = fork()) < 0)
		err_sys_quit(errfd, "ERROR: fork");
	if (pid > 0)
		exit(0);	/* parent */

	/* First child process */
	setsid();		/* become session leader */

	if ((pid = fork()) < 0)
		err_sys_quit(errfd, "ERROR: fork");
	if (pid > 0)		/* first child */
		exit(0);

	umask(022);

	if (chdir(logdir) < 0)
		err_sys_quit(errfd, "ERROR: can't change directory to %s: chdir", logdir);
}


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

static void 
set_thread_throttling(void)
{
	/*
         * Calculate total values across all processes.
         * All numbers are per listening socket.
         */
	if (max_wait_threads == 0)
		max_wait_threads = MAX_WAIT_THREADS_DEFAULT * vp_count;

	/*
	 * Assuming that each client session needs FD_PER_THREAD file
	 * descriptors
	 */
	if (max_threads == 0)
		max_threads = (st_getfdlimit() * vp_count) / FD_PER_THREAD / sk_count;
	if (max_wait_threads > max_threads)
		max_wait_threads = max_threads;

	/*
         * Now calculate per-process values.
         */
	if (max_wait_threads % vp_count)
		max_wait_threads = max_wait_threads / vp_count + 1;
	else
		max_wait_threads = max_wait_threads / vp_count;
	if (max_threads % vp_count)
		max_threads = max_threads / vp_count + 1;
	else
		max_threads = max_threads / vp_count;

	if (min_wait_threads > max_wait_threads)
		min_wait_threads = max_wait_threads;
}

static int
ss2_get_fd(void)
{
	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 = -1;
	if (debug > 5)
		sm_io_fprintf(smioerr, "before un_st_server_listen\n");
	ret = un_st_server_listen(fd_socket, 10, &lfd);
	if (sm_is_err(ret))
	{
		err_quit(errfd,
			"sev=ERROR, socket=%s, listen=%#x"
			, fd_socket, ret);
	}
	addrlen = sizeof(addr);
	if (debug > 5)
		sm_io_fprintf(smioerr, "before st_accept\n");
	fd = st_accept(lfd, &addr, &addrlen, (st_utime_t) -1);
	if (NULL == fd)
	{
		st_netfd_close(lfd);
		err_sys_quit(errfd,
			"sev=ERROR, socket=%s, accept=failed"
			, fd_socket);
	}
	if (debug > 5)
		sm_io_fprintf(smioerr, "before sm_read_fd\n");
	i = sm_read_fd(fd, buf, 1, &sock);
	if (sock < 0)
		err_quit(errfd,
			"sev=ERROR, socket=%s, sock=%d, read_fd=%d"
			, fd_socket, sock, i);
	if (debug > 5)
		sm_io_fprintf(smioerr, "sm_read_fd=%d\n", sock);

	/* pass only one fd over this socket */
	(void) unlink(fd_socket);
	return sock;
}


/******************************************************************/

static void 
create_listeners(void)
{
	int             i, n, sock, addrlen, pass;
	char           *c;
	short           port;
	struct hostent *hp;
	sm_sockaddr_T	serv_addr;

	pass  = (fd_socket != NULL && *fd_socket != '\0');
	for (i = 0; i < sk_count; i++)
	{
		port = 0;
		addrlen = 0;
		if (pass)
		{
			sock = ss2_get_fd();
		}
		else if (strncmp(srv_socket[i].addr, "unix:", 5) == 0)
		{
			size_t len;

			port = 1025;
			c = srv_socket[i].addr + 5;
			if (*c == '\0')
				err_sys_quit(errfd, "ERROR: missing unix: pathname");
			/* Create server socket */
			if ((sock = socket(AF_UNIX, SOCK_STREAM, 0)) < 0)
				err_sys_quit(errfd, "ERROR: can't create socket: socket");
			n = 1;
			if (setsockopt(sock, SOL_SOCKET, SO_REUSEADDR, (char *) &n, sizeof(n)) < 0)
				err_sys_quit(errfd, "ERROR: can't set SO_REUSEADDR: setsockopt");

			sm_memset(&serv_addr, '\0', sizeof(serv_addr));
			addrlen = sizeof(serv_addr.sunix);
			serv_addr.sa.sa_family = AF_UNIX;
			len = strlen(c);
			if (strlcpy(serv_addr.sunix.sun_path, c,
				sizeof(serv_addr.sunix.sun_path)) >= sizeof(serv_addr.sunix.sun_path)) 
				err_sys_quit(errfd, "ERROR: pathname too long");
#if HAVE_SOCK_UN_SUN_LEN
			serv_addr.sunix.sun_len = len;
#endif
		}
		else
		{
			if ((c = strchr(srv_socket[i].addr, ':')) != NULL)
			{
				*c++ = '\0';
				port = (short) atoi(c);
			}
			if (srv_socket[i].addr[0] == '\0')
				srv_socket[i].addr = "0.0.0.0";
			if (port == 0)
				port = SERV_PORT_DEFAULT;

			/* Create server socket */
			if ((sock = socket(PF_INET, SOCK_STREAM, 0)) < 0)
				err_sys_quit(errfd, "ERROR: can't create socket: socket");
			n = 1;
			if (setsockopt(sock, SOL_SOCKET, SO_REUSEADDR, (char *) &n, sizeof(n)) < 0)
				err_sys_quit(errfd, "ERROR: can't set SO_REUSEADDR: setsockopt");
			addrlen = sizeof(serv_addr.sin);
			memset(&serv_addr, 0, sizeof(serv_addr));
			serv_addr.sa.sa_family = AF_INET;
			serv_addr.sin.sin_port = htons(port);
			serv_addr.sin.sin_addr.s_addr = inet_addr(srv_socket[i].addr);
			if (serv_addr.sin.sin_addr.s_addr == INADDR_NONE)
			{
				/* not dotted-decimal */
				if ((hp = gethostbyname(srv_socket[i].addr)) == NULL)
					err_quit(errfd, "ERROR: can't resolve address: %s",
						 srv_socket[i].addr);
				memcpy(&serv_addr.sin.sin_addr, hp->h_addr, hp->h_length);
			}
			srv_socket[i].port = port;
		}

		if (!pass)
		{
			/* Do bind and listen */
			if (bind(sock, (struct sockaddr *) & serv_addr,
				addrlen) < 0)
			{
				err_sys_quit(errfd,
					"ERROR: can't bind to address %s, port %d",
					srv_socket[i].addr, port);
			}
			if (listen(sock, listenq_size) < 0)
				err_sys_quit(errfd, "ERROR: listen");
		}

		/* Create file descriptor object from OS socket */
		if ((srv_socket[i].nfd = st_netfd_open_socket(sock)) == NULL)
			err_sys_quit(errfd, "ERROR: st_netfd_open_socket");
		/*
                 * 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 (serialize_accept &&
		    st_netfd_serialize_accept(srv_socket[i].nfd) < 0)
			err_sys_quit(errfd, "ERROR: st_netfd_serialize_accept");
		srv_socket[i].da_fp = NULL;
		if (writedata)
		{
			sm_ret_T ret;
			char p[8];

			snprintf(p, sizeof(p), "d%d", i);
			ret = sm_io_open(SmStStdio, p, SM_IO_APPEND,
					&srv_socket[i].da_fp, SM_IO_WHAT_END);
			if (sm_is_err(ret))
				err_sys_quit(errfd,
					"ERROR: open(%s)=failed, err=%#X"
					, p, ret);
		}
	}
}


/******************************************************************/

static void 
change_user(void)
{
	struct passwd  *pw;

	if ((pw = getpwnam(username)) == NULL)
		err_quit(errfd, "ERROR: can't find user '%s': getpwnam failed", username);

	if (setgid(pw->pw_gid) < 0)
		err_sys_quit(errfd, "ERROR: can't change group id: setgid");
	if (setuid(pw->pw_uid) < 0)
		err_sys_quit(errfd, "ERROR: can't change user id: setuid");

	err_report(errfd, "INFO: changed process user id to '%s'", username);
}


/******************************************************************/

static void 
wrt_pid(void)
{
	int             fd;
	char            str[32];

	/* Open and write pid to pid file */
	if ((fd = open(PID_FILE, O_CREAT | O_WRONLY | O_TRUNC, 0644)) < 0)
		err_sys_quit(errfd, "ERROR: can't open pid file: open");
	snprintf(str, sizeof(str), "%d\n", (int) getpid());
	if (write(fd, str, strlen(str)) != (ssize_t) strlen(str))
		err_sys_quit(errfd, "ERROR: can't write to pid file: write");
	close(fd);
}

static void 
open_log_files(void)
{
	int             fd;

	if (interactive_mode)
		return;

	/* Open access log */
	if (log_access)
		logbuf_open();

	/* Open error log file */
	if ((fd = open(ERRORS_FILE, O_CREAT | O_WRONLY | O_APPEND, 0644)) < 0)
		err_sys_quit(errfd, "ERROR: can't open error log file: open");
	errfd = fd;

	err_report(errfd, "INFO: starting the server...");
}


/******************************************************************/

static void 
start_processes(void)
{
	int             i, status, restart, children;
	pid_t           pid;
	sigset_t        mask, omask;

	if (interactive_mode)
	{
		my_index = 0;
		my_pid = getpid();
		return;
	}

	children = vp_count;
	for (i = 0; i < vp_count; i++)
	{
		if ((pid = fork()) < 0)
		{
			err_sys_report(errfd, "ERROR: can't create process: fork");
			if (i == 0)
				exit(1);
			err_report(errfd, "WARN: started only %d processes out of %d", i,
				   vp_count);
			children = vp_count = i;
			break;
		}
		if (pid == 0)
		{
			my_index = i;
			my_pid = getpid();
			/* Child returns to continue in main() */
			return;
		}
		vp_pids[i] = pid;
	}

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

	/* Install signal handlers */
	Signal(SIGTERM, wdog_sighandler);	/* terminate */
	Signal(SIGHUP, wdog_sighandler);	/* restart   */
	Signal(SIGUSR1, wdog_sighandler);	/* dump info */
	Signal(SIGUSR2, wdog_sighandler);	/* reset info */

	/* Now go to sleep waiting for a child termination or a signal */
	for (;;)
	{
		restart = 1;
		if ((pid = wait(&status)) < 0)
		{
			if (EINTR == errno)
				continue;
			err_sys_quit(errfd, "ERROR: watchdog: wait");
		}
		/* Find index of the exited child */
		for (i = 0; i < 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);

		if (WIFEXITED(status))
		{
			err_report(errfd,
				"WARN: watchdog: process %d (pid %d) exited"
				" with status %d", i, pid, WEXITSTATUS(status));
			restart = WEXITSTATUS(status) != 0;
		}
		else if (WIFSIGNALED(status))
			err_report(errfd, "WARN: watchdog: process %d (pid %d) terminated"
				   " by signal %d", i, pid, WTERMSIG(status));
		else if (WIFSTOPPED(status))
			err_report(errfd, "WARN: watchdog: process %d (pid %d) stopped"
				   " by signal %d", i, pid, WSTOPSIG(status));
		else
			err_report(errfd, "WARN: watchdog: process %d (pid %d) terminated:"
				   " unknown termination reason", i, pid);

		if (restart)
		{
			/* Fork another VP */
			if ((pid = fork()) < 0)
			{
				err_sys_report(errfd,
					"ERROR: watchdog: can't create process: fork");
			}
			else if (pid == 0)
			{
				my_index = i;
				my_pid = getpid();
				/* Child returns to continue in main() */
				return;
			}
			vp_pids[i] = pid;
		}
		else
		{
			vp_pids[i] = 0;
			if (--children <= 0)
				exit(0);
		}

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


/******************************************************************/

static void 
wdog_sighandler(int signo)
{
	int             i, err;

	/* Save errno */
	err = errno;
	/* Forward the signal to all children */
	for (i = 0; i < vp_count; i++)
	{
		if (vp_pids[i] > 0)
			kill(vp_pids[i], signo);
	}
	/*
         * It is safe to do pretty much everything here because process is
         * sleeping in wait() which is async-safe.
         */
	switch (signo)
	{
	case SIGHUP:
		err_report(errfd, "INFO: watchdog: caught SIGHUP");
		/* Reopen log files - needed for log rotation */
		if (log_access)
		{
			logbuf_close();
			logbuf_open();
		}
		close(errfd);
		if ((errfd = open(ERRORS_FILE, O_CREAT | O_WRONLY | O_APPEND, 0644)) < 0)
			err_sys_quit(STDERR_FILENO, "ERROR: watchdog: open");
		break;
	case SIGTERM:
		/* Non-graceful termination */
		if (gotit != NULL)
		{
			bit_ffc(gotit, sequence + 1, &i);
			if (i != -1)
			{
				err_report(errfd,
					"ERROR: missing at least %d", i);
			}
		}
		err_report(errfd,
			"INFO: watchdog: caught SIGTERM, terminating");
		unlink(PID_FILE);
		ss_shutdown();
		exit(0);
	case SIGUSR1:
		err_report(errfd, "INFO: watchdog: caught SIGUSR1");
		break;
	case SIGUSR2:
		break;
	default:
		err_report(errfd, "INFO: watchdog: caught signal %d", signo);
	}
	/* Restore errno */
	errno = err;
}


/******************************************************************/

static void 
install_sighandlers(void)
{
	sigset_t        mask;
	int             p[2];

	/* Create signal pipe */
	if (pipe(p) < 0)
		err_sys_quit(errfd, "ERROR: process %d (pid %d): can't create"
			     " signal pipe: pipe", my_index, my_pid);
	if ((sig_pipe[0] = st_netfd_open(p[0])) == NULL ||
	    (sig_pipe[1] = st_netfd_open(p[1])) == NULL)
		err_sys_quit(errfd, "ERROR: process %d (pid %d): can't create"
			     " signal pipe: st_netfd_open", my_index, my_pid);

	/* Install signal handlers */
	Signal(SIGTERM, child_sighandler);	/* terminate */
	Signal(SIGHUP, child_sighandler);	/* restart   */
	Signal(SIGUSR1, child_sighandler);	/* dump info */
	Signal(SIGUSR2, child_sighandler);	/* reset info */

	/* Unblock signals */
	sigemptyset(&mask);
	sigaddset(&mask, SIGTERM);
	sigaddset(&mask, SIGHUP);
	sigaddset(&mask, SIGUSR1);
	sigaddset(&mask, SIGUSR2);
	sigprocmask(SIG_UNBLOCK, &mask, NULL);
}


/******************************************************************/

static void 
child_sighandler(int signo)
{
	int             err, fd;

	err = errno;
	fd = st_netfd_fileno(sig_pipe[1]);

	/* write() is async-safe */
	if (write(fd, &signo, sizeof(int)) != sizeof(int))
		err_sys_quit(errfd, "ERROR: process %d (pid %d): child's signal"
			     " handler: write", my_index, my_pid);
	errno = err;
}


/******************************************************************
 * The "main" function of the signal processing thread.
 */

/* ARGSUSED */
static void    *
process_signals(void *arg)
{
	int signo, i;

	(void) arg;
	for (;;)
	{
		/* Read the next signal from the signal pipe */
		if (st_read(sig_pipe[0], &signo, sizeof(int), -1) != sizeof(int))
			err_sys_quit(errfd, "ERROR: process %d (pid %d): signal processor:"
				     " st_read", my_index, my_pid);

		switch (signo)
		{
		case SIGHUP:
			err_report(errfd, "INFO: process %d (pid %d): caught SIGHUP,"
				" reloading configuration", my_index, my_pid);
			if (interactive_mode)
			{
				load_configs();
				break;
			}
			/* Reopen log files - needed for log rotation */
			if (log_access)
			{
				logbuf_flush();
				logbuf_close();
				logbuf_open();
			}
			close(errfd);
			if ((errfd = open(ERRORS_FILE, O_CREAT | O_WRONLY | O_APPEND, 0644)) < 0)
				err_sys_quit(STDERR_FILENO, "ERROR: process %d (pid %d): signal"
					" processor: open", my_index, my_pid);
			/* Reload configuration */
			load_configs();
			break;
		case SIGTERM:
			/*
		        **  Terminate ungracefully since it is generally not
			**  known how long it will take to gracefully complete
		        **  all client sessions.
		        */

			if (gotit != NULL)
			{
				bit_ffc(gotit, sequence + 1, &i);
				if (i != -1)
				{
					err_report(errfd,
						"ERROR: missing at least %d", i);
				}
			}
			err_report(errfd, "INFO: process %d (pid %d): caught SIGTERM,"
				   " terminating", my_index, my_pid);
			if (idisplay) {
				time_t lastNmail, elapsed;

				lastNmail = st_time();
				elapsed = lastNmail - firstNmail;
				sm_io_fprintf(smioerr,
					"\n%ld, %ld %ld, %ld"
					, (long) firstNmail
					, (long) lastNmail
					, (long) elapsed
					, (elapsed > 0) ? (long) (eachN / elapsed) : -1L
					);
			}
			if (log_access)
				logbuf_flush();
			ss_shutdown();
			exit(0);
		case SIGUSR1:
			err_report(errfd, "INFO: process %d (pid %d): caught SIGUSR1",
				   my_index, my_pid);
			/* Print server info to stderr */
			dump_server_info();
			for (i = 0; i < sk_count; i++)
				dump_stats(errfd, i, 0);
			break;
		case SIGUSR2:
			TA_CNT_EACH(my_index) = 0;
			break;
		default:
			err_report(errfd, "INFO: process %d (pid %d): caught signal %d",
				   my_index, my_pid, signo);
		}
	}

	/* NOTREACHED */
	return NULL;
}


/******************************************************************
 * The "main" function of the access log flushing thread.
 */

/* ARGSUSED */
static void    *
flush_acclog_buffer(void *arg)
{
	(void) arg;
	for (;;)
	{
		st_sleep(ACCLOG_FLUSH_INTERVAL);
		logbuf_flush();
	}

	/* NOTREACHED */
	return NULL;
}


/******************************************************************/

static void 
start_threads(void)
{
	long            i, n;

	/* Create access log flushing thread */
	if (log_access &&
	    st_thread_create(flush_acclog_buffer, NULL, 0, 0) == NULL)
		err_sys_quit(errfd, "ERROR: process %d (pid %d): can't create"
			     " log flushing thread", my_index, my_pid);

	if (idisplay)
		sm_io_fprintf(smioerr,
				"   msg    time_i   time_t  msgs/s t-msgs/s  busy  conc\n");

	/* Create connections handling threads */
	for (i = 0; i < sk_count; i++)
	{
		err_report(errfd, "INFO: process %d (pid %d): starting %d"
				" threads on %s:%d", my_index, my_pid,
				max_wait_threads, srv_socket[i].addr,
				srv_socket[i].port);
		WAIT_THREADS(i) = 0;
		BUSY_THREADS(i) = 0;
		CONC_THREADS(i) = 0;
		MAXB_THREADS(i) = 0;
		RQST_COUNT(i) = 0;
		MAIL_COUNT(i) = 0;
		RCPT_COUNT(i) = 0;
		TA_CNT_OK(i) = 0;
		TA_CNT_EACH(i) = 0;
		TOTAL_RATE(i) = 0;
		TA_CNT(i) = 0;
		RCPT_CNT_OK(i) = 0;
		for (n = 0; n < max_wait_threads; n++)
		{
			if (st_thread_create(handle_connections, (void *) i,
						0, 0) != NULL)
				WAIT_THREADS(i)++;
			else
				err_sys_report(errfd,
					"ERROR: process %d (pid %d): can't "
					"create thread", my_index, my_pid);
		}
		if (WAIT_THREADS(i) == 0)
			exit(1);
	}
}


/******************************************************************/

static void *
handle_connections(void *arg)
{
	st_netfd_t      srv_nfd, cli_nfd;
	st_utime_t	tmo;
	struct sockaddr_in from;
	int             fromlen, save_errno;
	long            i;

	i = (long) arg;
	srv_nfd = srv_socket[i].nfd;
	fromlen = sizeof(from);
	tmo = -1;

	while (WAIT_THREADS(i) <= max_wait_threads)
	{
		cli_nfd = st_accept(srv_nfd, (struct sockaddr *) & from,
				&fromlen, tmo);
		if (NULL == cli_nfd)
		{
			save_errno = errno;
			if (ETIME == save_errno)
				goto done;
			err_sys_report(errfd,
				"ERROR: can't accept connection: st_accept");

			if (save_errno == EBADF
			    || save_errno == ENOTSOCK
			    || save_errno == EINVAL)
				break;

			/* slow down... */
			sleep(1);

			/* break on certain errors? */
			continue;
		}
		if (firstmail == 0)
			firstmail = st_time();

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

		WAIT_THREADS(i)--;
		BUSY_THREADS(i)++;
		if (BUSY_THREADS(i) > MAXB_THREADS(i))
			MAXB_THREADS(i) = BUSY_THREADS(i);
		if (WAIT_THREADS(i) < min_wait_threads
		    && TOTAL_THREADS(i) < max_threads
		    && seqleft != 0)
		{
			/* Create another spare thread */
			if (st_thread_create(handle_connections, (void *) i, 0,
						0) != NULL)
				WAIT_THREADS(i)++;
			else
				err_sys_report(errfd,
					"ERROR: process %d (pid %d): can't"
					" create thread", my_index, my_pid);
		}

		handle_session(i, cli_nfd);
		st_netfd_close(cli_nfd);
		WAIT_THREADS(i)++;
		BUSY_THREADS(i)--;
  done:
		if (seqleft == 0 ||
		    (tasexpected > 0 && tasexpected <= TA_CNT_OK(i)))
		{
			if (lastmail == 0)
				lastmail = st_time();
			tmo = 3000;

			/*
			**  give it a grace period as smtpc2 may send too
			**  many mails
			*/

			if (BUSY_THREADS(i) == 0 &&
			    (lastmail == 0 || lastmail + 2 < st_time()))
			{
				if (lastmail == 0)
					lastmail = st_time();
				err_report(errfd,
					"start=%ld, end=%ld\n"
					"elapsed                    %ld\n"
					, (long) firstmail
					, (long) lastmail
					, (long) (lastmail - firstmail));
				dump_stats(errfd, i, 1);
				if (srv_socket[i].da_fp != NULL)
				{
					sm_io_close(srv_socket[i].da_fp,
						SM_IO_CF_NONE);
					srv_socket[i].da_fp = NULL;
				}
				exit(0);
			}
		}
	}

	WAIT_THREADS(i)--;
	return NULL;
}

/******************************************************************/

static void 
dump_stats(int fd, int myid, int all)
{
	struct rusage rusage;

	if (all)
	{
		err_report(fd,
			"Thread limits (min/max)    %d/%d\n"
			"Waiting threads            %d\n"
			"Busy threads               %d\n"
			"Max busy threads           %d\n"
			"Requests served            %d\n"
			"Transactions               %u\n"
			"Recipients                 %u\n"
			"Transactions accepted      %u\n"
			"Recipients accepted        %u\n"
			"Total size (data)          %lu\n"
			"Total rate                 %lu\n"
			, max_wait_threads, max_threads
			, WAIT_THREADS(myid), BUSY_THREADS(myid)
			, MAXB_THREADS(myid)
			, RQST_COUNT(myid)
			, MAIL_COUNT(myid)
			, RCPT_COUNT(myid)
			, TA_CNT_OK(myid)
			, RCPT_CNT_OK(myid)
			, (unsigned long) total_size
			, (unsigned long) TOTAL_RATE(myid)
			);
	}
	if (getrusage(RUSAGE_SELF, &rusage) == 0)
	{
		err_report(fd,
			"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
			);
	}
}

static void 
dump_server_info(void)
{
	char           *buf;
	int             i, len, s;

	s = sk_count * 512;
	if ((buf = malloc(s)) == NULL)
	{
		err_sys_report(errfd, "ERROR: malloc failed");
		return;
	}

	len = snprintf(buf, s, "\n\nProcess #%d (pid %d):\n",
			my_index, (int) my_pid);
	for (i = 0; i < sk_count; i++)
	{
		len += snprintf(buf + len, s - len, "\nListening Socket #%d:\n"
				"-------------------------\n"
				"Address                    %s:%d\n"
				"Thread limits (min/max)    %d/%d\n"
				"Waiting threads            %d\n"
				"Busy threads               %d\n"
				"Max busy threads           %d\n"
				"Requests served            %d\n"
				"Transactions               %u\n"
				"Recipients                 %u\n"
				"Transactions accepted      %u\n"
				"Recipients accepted        %u\n"
				"Total rate                 %lu\n"
				, i, srv_socket[i].addr, srv_socket[i].port
				, max_wait_threads, max_threads
				, WAIT_THREADS(i), BUSY_THREADS(i)
				, MAXB_THREADS(i)
				, RQST_COUNT(i)
				, MAIL_COUNT(i)
				, RCPT_COUNT(i)
				, TA_CNT_OK(i)
				, RCPT_CNT_OK(i)
				, (unsigned long) TOTAL_RATE(i)
				);
	}

	write(STDERR_FILENO, buf, len);
#if SM_HEAP_CHECK
	SmHeapCheck = 1;
	if (HEAP_CHECK)
		sm_heap_report(smioerr, 3);
#endif /* SM_HEAP_CHECK */
	free(buf);
}


/******************************************************************
 * Stubs
 */

/*
**  SMTPSREAD -- read an SMTP command
**
**	Parameters:
**		fp -- SMTP I/O file
**		buf -- buffer in which the SMTP command should be stored
**		len -- length of buffer
**		rd -- str to use for reading
**
**	Returns:
**		length of str or -1 on error
*/

static int
smtpsread(sm_file_T *fp, char *buf, size_t len, sm_str_P rd)
{
	ssize_t         r;
	sm_ret_T        ret;
	char		*s;

	sm_str_clr(rd);

	/* don't use fgetline0() because this is also for DATA and needs \r\n */
	ret = sm_fgetline(fp, rd);
	if (debug > 4)
	{
		sm_io_fprintf(smioerr,
			      "rcvd [len=%d, res=%#x]: ",
			      sm_str_getlen(rd), ret);
		sm_io_write(smioerr, sm_str_getdata(rd),
			    sm_str_getlen(rd), &r);
		sm_io_flush(smioerr);
	}
	if (sm_is_error(ret))
	{
		sm_io_fprintf(smioerr, "read error ret=%#x\n", ret);
		return -1;
	}
	s = (char *) sm_str_getdata(rd);
	if (NULL == s)
		return -1;
	strlcpy(buf, s, len);
	return sm_str_getlen(rd);
}

static sm_ret_T
smtpreply(char *buf, size_t len, sm_file_T *fp, bool flushit)
{
	ssize_t         r;
	sm_ret_T        ret;

	if (debug > 4) {
		sm_io_fprintf(smioerr, "send: ");
		sm_io_write(smioerr, (const unsigned char *) buf, len, &r);
		sm_io_flush(smioerr);
	}
	if (delayreply > 0)
		st_sleep(delayreply);

	ret = sm_io_write(fp, (const unsigned char *) buf, len, &r);
	if (r != (ssize_t) len) {
		sm_io_fprintf(smioerr,
			      "write error n=%d, r=%d, ret=%#x\n",
			      len, (int) r, ret);
		return -1;
	}
	if (flushit) {
		ret = sm_io_flush(fp);
		if (sm_is_error(ret)) {
			sm_io_fprintf(smioerr, "flush error=%#x\n", ret);
			return -1;
		}
	}
	return r;
}

/* transaction states, these must be sorted! */
#define SSTA_NONE	0x00	/* must be 0 */
#define SSTA_INIT	0x01	/* ta initialized */
#define SSTA_MAIL	0x02	/* MAIL received */
#define SSTA_RCPT	0x04	/* RCPT received */
#define SSTA_DATA	0x08	/* DATA received */
#define SSTA_DOT	0x10	/* Final dot received */

/* Note: this ONLY works in an if else "environment"! */
#define TEMPPERM(stage)	\
	else if (tempperm == (stage) && wastemp)			\
		strlcpy(resp, "551 perm after temp\r\n" , sizeof resp);	\
	else if (tempperm == (stage) && !wastemp) {			\
		wastemp = true;						\
		strlcpy(resp, "451 temp before perm\r\n" , sizeof resp); \
	}								\
	else if (tempok == (stage) && !wastemp)	{				\
		wastemp = true;						\
		strlcpy(resp, "451 temp before ok\r\n" , sizeof resp); \
	}								\
	else if (permok == (stage) && !wasperm)	{					\
		wasperm = true;						\
		strlcpy(resp, "551 perm before ok\r\n" , sizeof resp); \
	}								\
	else if (temp2perm[1] == (stage) && was2temp) {				\
		strlcpy(resp, "551 perm after temp 2\r\n" , sizeof resp); \
		was2temp = false;					\
	}								\
	else if (temp2perm[0] == (stage) && !was2temp) {		\
		was2temp = true;					\
		strlcpy(resp, "451 temp before perm 2\r\n" , sizeof resp); \
	}								\
	else if (temptemp[1] == (stage) && wastt) {					\
		strlcpy(resp, "454 4.7.4 temp 4\r\n" , sizeof resp);	\
	}								\
	else if (temptemp[0] == (stage) && !wastt) {				\
		wastt = true;						\
		strlcpy(resp, "453 4.7.3 temp 3\r\n" , sizeof resp);	\
	}

extern sm_stream_T SmStThrNetIO;

#define SM_BUF_SZ	(1024 * 4)

/*
**  Maybe reject a command if
**  oktime is not set or it hasn't expired yet.
*/

#define MAYBE_REJECT(cond) ((cond) && \
		(((oktime == 0 || st_time() < oktime) && rejmsgs == 0) || \
		 (rejmsgs == 1 && nrejmsgs-- > 0)))

static void
gen_ehlo_resp(char *resp, size_t len)
{
	if (MAYBE_REJECT(ehloresp[0] != '2'))
	{
		sm_snprintf(resp, len, "%s %s %sSMTP Hi\r\n", ehloresp,
			UseSMTP ? "" : "S", Myname);
		return;
	}
	if (fullehlo != NULL)
	{
		strlcpy(resp, fullehlo, len);
		strlcat(resp, "\r\n", len);
	}
	else if (max_msg_size > 0)
	{
		sm_snprintf(resp, len,
			"250-%s ESMTP Hi there\r\n250-SIZE %lu\r\n"
			, Myname, max_msg_size);
	}
	else
		sm_snprintf(resp, len, "250-%s ESMTP Hi there\r\n", Myname);
	if (pipelining)
		strlcat(resp, "250-PIPELINING\r\n", len);
	if (offer_drr)
		strlcat(resp, "250-DRR\r\n", len);
	else if (offer_rsad)
		strlcat(resp, "250-PRDR\r\n", len);
	if (dsn)
		strlcat(resp, "250-DSN\r\n", len);
	if (starttls[0] != 'x')
		strlcat(resp, "250-STARTTLS\r\n", len);
	strlcat(resp, "250 HELP\r\n", len);
}
static int
max_rand(unsigned int maxval)
{
	return random() % maxval;
}

static void
checkN(int srv_socket_index)
{
	unsigned int ta_cnt_e;

	if (eachN > 0 && (++TA_CNT_EACH(srv_socket_index) % eachN) == 0)
	{
		time_t lastNmail, elapsed, elapsed_t;

		ta_cnt_e = TA_CNT_EACH(srv_socket_index);
		lastNmail = st_time();
		elapsed = lastNmail - firstNmail;
		elapsed_t = lastNmail - firstmail;
		if (idisplay) {
			if (elapsed_t > 0)
				TOTAL_RATE(srv_socket_index) = ta_cnt_e / elapsed_t;
			sm_io_fprintf(smioerr,
				"%6u    %6ld   %6ld  %6ld  %6ld  %4d  %4d\n"
				, ta_cnt_e
				, (long) elapsed
				, (long) elapsed_t
				, (elapsed > 0) ? (long) (eachN / elapsed) : -1L
				, (elapsed_t > 0) ? (long) TOTAL_RATE(srv_socket_index) : -1L
				, BUSY_THREADS(srv_socket_index)
				, CONC_THREADS(srv_socket_index)
				);
			/* sm_io_flush(smioerr); */
		}
		else {
			err_report(errfd,
				"msg=%u, start=%ld, end=%ld"
				", elapsed=%ld, msg/s=%ld"
				, TA_CNT_EACH(srv_socket_index)
				, (long) firstNmail
				, (long) lastNmail
				, (long) elapsed
				, (elapsed > 0) ? (long) (eachN / elapsed) : -1L
				);
		}
		firstNmail = 0;
	}
}

static void
ss_rcpts_remove_head(ss_rcpts_P ss_rcpts_hd)
{
	ss_rcpt_P ss_rcpt;

	if (SS_RCPTS_EMPTY(ss_rcpts_hd))
		return;
	ss_rcpt = SS_RCPTS_FIRST(ss_rcpts_hd);
	SS_RCPTS_REMOVE_HEAD(ss_rcpts_hd);
	free(ss_rcpt->ssr_reply);
	free(ss_rcpt);
}

static int
ss_rcpts_append(ss_rcpts_P ss_rcpts_hd, const char *reply)
{
	ss_rcpt_P ss_rcpt;

	ss_rcpt = (ss_rcpt_P)malloc(sizeof(*ss_rcpt));
	if (NULL == ss_rcpt)
		return ENOMEM;
	ss_rcpt->ssr_reply = strdup(reply);
	if (NULL == ss_rcpt->ssr_reply) {
		free(ss_rcpt);
		return ENOMEM;
	}
	SS_RCPTS_INSERT_TAIL(ss_rcpts_hd, ss_rcpt);
	return 0;
}


/*
 * Session handling function stub.
 */
void
handle_session(int srv_socket_index, st_netfd_t cli_nfd)
{
	char resp[2048];
	char buf[SM_BUF_SZ];
	short indata = 0;
	int r, n, val, gotval, state, ta_state, rcpts;
	int min_rcpts_reply, common_rcpt_status;
	struct in_addr *from = st_netfd_getspecific(cli_nfd);
	sm_file_T      *fp;
#if SMTPS2_CDB
	sm_file_T      *dfp;
	sessta_id_T	ta_id;
#endif /* SMTPS2_CDB */
	ssize_t		byteswritten;
	sm_ret_T        ret;
	sm_str_P        rd;
	static char     eot[] = "\r\n.\r\n";
	char            sbuf[64];
	ss_rcpts_T		ss_rcpts_hd;

#define CRS_INIT	(-1)
#define CRS_NONE	(-2)

#define TA_INIT	do {	\
	while (!SS_RCPTS_EMPTY(&ss_rcpts_hd)) \
		ss_rcpts_remove_head(&ss_rcpts_hd); \
	SS_RCPTS_INIT(&ss_rcpts_hd);	\
	rcpts = 0;	\
	use_drr = false;	\
	use_rsad = false;	\
	common_rcpt_status = CRS_INIT; \
	} while (0)


	fp = NULL;
#if SMTPS2_CDB
	dfp = NULL;
	ta_id[0] = '\0';
#endif /* SMTPS2_CDB */
	rcpts = 0;
	SS_RCPTS_INIT(&ss_rcpts_hd);
	TA_INIT;
	rd = sm_str_new(NULL, SM_BUF_SZ, SM_BUF_SZ);
	if (NULL == rd)
	{
		err_sys_report(errfd,
			       "ERROR: process %d (pid %d): sm_str_new=failed",
			       my_index, my_pid);
		return;
	}
	ret = sm_io_open(&SmStThrNetIO, (void *) &cli_nfd, SM_IO_RDWR,
			 &fp, SM_IO_WHAT_END);
	if (ret != SM_SUCCESS)
	{
		err_sys_report(errfd,
			       "ERROR: process %d (pid %d): sm_io_open()=%#x",
			       my_index, my_pid, ret);
		goto done;
	}

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

	r = iotimeout;
	ret = sm_io_setinfo(fp, SM_IO_WHAT_TIMEOUT, &r);
	if (ret != SM_SUCCESS)
	{
		err_sys_report(errfd,
			       "ERROR: process %d (pid %d): set timeout()=%#x",
			       my_index, my_pid, ret);
		goto done;
	}

	ret = sm_io_setinfo(fp, SM_IO_DOUBLE, NULL);
	if (ret != SM_SUCCESS)
	{
		err_sys_report(errfd,
			       "ERROR: process %d (pid %d): set double=%#x",
			       my_index, my_pid, ret);
		goto done;
	}

	++CONC_THREADS(srv_socket_index);
	ta_state = SSTA_NONE;
	if (greeting[0] == SM_DROP_IT)
	{
		if (delayreply > 0)
			st_sleep(delayreply);
		goto done;
	}
	if (Hugegreeting)
	{
		strlcpy(resp, "220-", sizeof resp);
		strlcat(resp, Myname, sizeof resp);
		strlcat(resp, " ESMTP ST sink\r\n"
			"220-\r\n"
			"220-The use of this host for the sending of unsolicited commercial\r\n"
			"220-email, spam, or junk mail is strictly prohibited. Unauthorized\r\n"
			"220-use of this machine will be prosecuted whereever possible by law.\r\n"
			"220-\r\n"
			"220-Hosts connecting to this server may be subjected to port scans,\r\n"
			"220-call-backs, SMTP proxy relay tests, and/or other forms of\r\n"
			"220-validation by this machine or others within our network. Any\r\n"
			"220-further SMTP commands, other than QUIT, will constitute an\r\n"
			"220-acceptance of this policy.\r\n"
			"220-\r\n"
			"220-This machine may no longer accept connections from IP addresses\r\n"
			"220-which have no reverse-DNS (PTR record) assignment.\r\n"
			"220 \r\n",
			sizeof resp);
	}
	else
	{
		strlcpy(resp, MAYBE_REJECT(greeting[0] != '2')
			? greeting : "220", sizeof resp);
		strlcat(resp, " ", sizeof resp);
		strlcat(resp, Myname, sizeof resp);
		if (UseSMTP)
			strlcat(resp, " SMTP ST sink\r\n", sizeof resp);
		else
			strlcat(resp, " ESMTP ST sink\r\n", sizeof resp);
	}
	n = strlen(resp);
	if (n != 0 && smtpreply(resp, n, fp, true) != n)
	{
		err_sys_report(errfd,
			"WARN: can't write greeting to %s: smtpreply",
			inet_ntoa(*from));
		goto done;
	}
	if (strcmp(greeting, "421") == 0)
		goto done;
	state = 0;
	ta_state = SSTA_INIT;
	if (seqleft != -1)
		val = -2;
	else
		val = -3;
	gotval = -1;

	while ((r = smtpsread(fp, buf, sizeof(buf), rd)) >= 0) {
		if (r == 0) {
			switch (errno) {
			  case 0:
				goto done;
			  case EINTR:
				continue;
			  case ETIMEDOUT:
			  case EAGAIN:
			  default:
				err_sys_report(errfd,
					"WARN: can't read from %s",
					inet_ntoa(*from));
				goto done;
			}
		}

		/* debug */
		if (debug > (indata ? 8 : 0))
			write(STDERR_FILENO, buf, r);

		resp[0] = '\0';
		if (indata > 0) {
			int i, c;

			if (indata == 1) {
				if (msgwait > 0)
					st_sleep(msgwait);
				indata = 2;
			}
			else if (indata == 2) {
				if (delaymsg > 0)
					st_usleep(delaymsg);
			}
			total_size += r;
			i = 0;
			if (seqleft == -1)
				val = -3;
			if (datacomp > 0)
				(void) data_comp(buf, r, datacomp);
			while ((c = buf[i++]) && i <= r) {
				if (val != -3 && c == '\n')
					val = -1;
				else if (val == -1) {
					if (isascii(c) && isdigit(c))
						val = 0;
					else
						val = -2;
				}
				if (val >= 0) {
					if (isascii(c) && isdigit(c))
						val = (val * 10) + c - '0';
					else if (c == '\r') {
						if (val > sequence) {
							err_report(errfd,
								"WARN: got %d > %d"
								, val, sequence);
						}
						else if (bit_test(gotit, val))
							err_report(errfd,
								"WARN: got %d already"
								, val);
						else
							gotval = val;
						val = -10;
					}
					else
						val = -2;
				}

				if (c == eot[state]) {
					if (++state >= (int) strlen(eot))
						break;
				}
				else {
					state = 0;
					if (c == eot[state])
						++state;
				}
			}
			if (debug > 7 && r > 0) {
				if (r >= (int) sizeof(buf) - 1)
					r -= 2;
				buf[r + 1] = '\0';
				snprintf(sbuf, sizeof sbuf, "state: %d, c=%c, i=%d, buf='%s'\r\n",
				     state, isprint(c) ? c : '_', i, buf + i);
				write(STDERR_FILENO, sbuf, strlen(sbuf));
			}
#if SMTPS2_CDB
			if (dfp != NULL) {
				ret = cdb_write(Cdb_ctx, dfp, (const unsigned char *) buf, r,
					&byteswritten);
				if (sm_is_err(ret)) {
					err_report(errfd, "ERROR: cdb_write=%#x" , ret);
				}
				else if (r != byteswritten) {
					err_report(errfd,
						"ERROR: cdb_write=%#x, r=%d, written=%d"
						, ret, r, byteswritten);
				}
			}
#else
			if (writedata && srv_socket[srv_socket_index].da_fp != NULL) {
				ret = sm_io_write(srv_socket[srv_socket_index].da_fp,
					(const uchar *) buf, r, &byteswritten);
				if (sm_is_err(ret) || r != byteswritten) {
					err_report(errfd, "ERROR: write=%#x, written=%d"
						, ret, byteswritten);
				}
			}
#endif /* SMTPS2_CDB */
			if (state >= (int) strlen(eot)) {
				if (MAYBE_REJECT(finaldot[0] != '2')) {
					sm_snprintf(resp, sizeof(resp),
						"%s Hmm\r\n", finaldot);
#if SMTPS2_CDB
					if (dfp != NULL)
					{
						sm_io_close(dfp, SM_IO_CF_RM);
						dfp = NULL;
					}
#endif /* SMTPS2_CDB */
				}
				else {
					/* no reply to '.' for DRR! */
					if (use_drr)
						;
					/* no reply to '.' in LMTP mode! */
					else if (srv_socket[srv_socket_index].lmtp > 0) {
						if (delaydotreply > 0)
							st_sleep(delaydotreply);
						if (delaydotrand > 0)
							st_sleep(max_rand(delaydotrand));
						if (srv_socket[srv_socket_index].lmtp > 1) {
							ss_rcpt_P ss_rcpt;

							ss_rcpt = SS_RCPTS_FIRST(&ss_rcpts_hd);
							strlcpy(resp, ss_rcpt->ssr_reply, sizeof resp);
						}
						else
#if SMTPS2_CDB
						if (Usecdb && ta_id[0] != '\0')
							snprintf(resp, sizeof resp, "250 OKR %s\r\n", ta_id);
						else
#endif /* SMTPS2_CDB */
							strlcpy(resp, "250 OKR\r\n", sizeof resp);
					}
					TEMPPERM('d')
					else if (randrej > 0 && random() < randrej)
						strlcpy(resp, "451 Random\r\n", sizeof resp);
					else {
						if (delaydotreply > 0)
							st_sleep(delaydotreply);
						if (delaydotrand > 0)
							st_sleep(max_rand(delaydotrand));
#if SMTPS2_CDB
						if (Usecdb && ta_id[0] != '\0')
							snprintf(resp, sizeof resp, "250 got %s\r\n", ta_id);
						else
#endif /* SMTPS2_CDB */
						if (use_rsad && !SS_RCPTS_EMPTY(&ss_rcpts_hd) &&
						    !EXPLICIT_FINALDOT)
						{
							ss_rcpt_P ss_rcpt;

							/* 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);
						}
						else
							strlcpy(resp, "250 got it\r\n", sizeof resp);
						TA_CNT_OK(srv_socket_index)++;
						if (count > 0 &&
						    (TA_CNT_OK(srv_socket_index) % count) == 0)
							sm_io_fprintf(smioerr, "%u\r",
								TA_CNT_OK(srv_socket_index));

						checkN(srv_socket_index);

						if (gotval >= 0) {
							bit_set(gotit, gotval);
							if (seqleft > 0)
								seqleft--;
							else
								err_report(errfd, "WARN: left=%d", seqleft);
						}
					}
#if SMTPS2_CDB
					if (!Unsafe && dfp != NULL) {
						if (Flat) {
							r = fsync(f_fd(*dfp));
							if (r == -1)
								ret = sm_error_perm(SM_EM_CDB, errno);
						}
						else
							ret = cdb_commit(Cdb_ctx, dfp);
						if (sm_is_err(ret)) {
							err_report(errfd, "ERROR: cdb_commit=%#x", ret);
						}
					}
					if (dfp != NULL) {
						ret = cdb_close(Cdb_ctx, dfp, SM_IO_CF_NONE);
						if (sm_is_err(ret)) {
							err_report(errfd, "ERROR: cdb_close=%#x", ret);
						}
						dfp = NULL;
						++Cdbfiles;
					}
#endif /* SMTPS2_CDB */
				}

				n = strlen(resp);
				if (smtpreply(resp, n, fp, true) != n) {
					err_sys_report(errfd,
						"WARN: can't write response to %s: st_write",
						inet_ntoa(*from));
					goto done;
				}
				if (debug > 7 && n != 0)
					write(STDERR_FILENO, resp, n);

				/*
				 * one reply has already been given for LMTP
				 * other LMTP replies are delayed if lmtp > 1
				 */
				if (srv_socket[srv_socket_index].lmtp > 1 && rcpts > 1) {
					ss_rcpts_remove_head(&ss_rcpts_hd);
					while (--rcpts > 0) {
						char *reply;
						ss_rcpt_P ss_rcpt;

						ss_rcpt = SS_RCPTS_FIRST(&ss_rcpts_hd);
						reply = ss_rcpt->ssr_reply;
						n = strlen(reply);
						if (smtpreply(reply, n, fp, true) != n) {
							err_sys_report(errfd,
								"WARN: can't write response to %s: st_write",
								inet_ntoa(*from));
							goto done;
						}
						if (debug > 7 && n != 0)
							write(STDERR_FILENO, reply, n);
						ss_rcpts_remove_head(&ss_rcpts_hd);
					}
				}
				else if (srv_socket[srv_socket_index].lmtp && rcpts > 1) {
					strlcpy(resp, "250 OKR\r\n", sizeof resp);
					n = strlen(resp);
					while (--rcpts > 0) {
						if (smtpreply(resp, n, fp, true) != n) {
							err_sys_report(errfd,
								"WARN: can't write response to %s: st_write",
								inet_ntoa(*from));
							goto done;
						}
						if (debug > 7 && n != 0)
							write(STDERR_FILENO, resp, n);
					}
				}

				while (use_drr && !SS_RCPTS_EMPTY(&ss_rcpts_hd)) {
					char *reply;
					ss_rcpt_P ss_rcpt;

					ss_rcpt = SS_RCPTS_FIRST(&ss_rcpts_hd);
					reply = ss_rcpt->ssr_reply;
					n = strlen(reply);
					if (smtpreply(reply, n, fp, true) != n) {
						err_sys_report(errfd,
							"WARN: can't write response to %s: st_write",
							inet_ntoa(*from));
						goto done;
					}
					if (debug > 7 && n != 0)
						write(STDERR_FILENO, reply, n);
					ss_rcpts_remove_head(&ss_rcpts_hd);
				}

				min_rcpts_reply = 550;
				while (use_rsad && !IS_SMTP_REPLY(common_rcpt_status)
				       && !SS_RCPTS_EMPTY(&ss_rcpts_hd)
				       && !EXPLICIT_FINALDOT)
				{
					char *reply;
					ss_rcpt_P ss_rcpt;

					ss_rcpt = SS_RCPTS_FIRST(&ss_rcpts_hd);
					reply = ss_rcpt->ssr_reply;
					n = atoi(reply);
					if (n < min_rcpts_reply)
						min_rcpts_reply = n;
					n = strlen(reply);
					if (smtpreply(reply, n, fp, true) != n) {
						err_sys_report(errfd,
							"WARN: can't write response to %s: st_write",
							inet_ntoa(*from));
						goto done;
					}
					if (debug > 7 && n != 0)
						write(STDERR_FILENO, reply, n);
					ss_rcpts_remove_head(&ss_rcpts_hd);
				}
				if (use_rsad && !IS_SMTP_REPLY(common_rcpt_status)
				    && !EXPLICIT_FINALDOT)
				{
					sm_snprintf(resp, sizeof(resp), "%d final\r\n",
						min_rcpts_reply);
					n = strlen(resp);
					if (smtpreply(resp, n, fp, true) != n) {
						err_sys_report(errfd,
							"WARN: can't write response to %s: st_write",
							inet_ntoa(*from));
						goto done;
					}
				}

				indata = 0;
				state = 0;
				ta_state = SSTA_INIT;
				continue;
			}
		}

		if (indata == 0) {
			if (strncasecmp(buf, "data", 4) == 0) {
				if (datawait > 0)
					st_sleep(datawait);
				else if (datawait < 0) {
					st_sleep(0 - datawait);
					datawait = 0;
				}
/*
err_report(errfd, "rejmsg=%d, nrejmsg=%ld, MAYBE=%d",
rejmsgs, nrejmsgs, MAYBE_REJECT(dataresponse[0] != '3'));
*/

				if (ta_state < SSTA_RCPT)
					strlcpy(resp, "503 Need RCPT\r\n", sizeof resp);
				else if (MAYBE_REJECT(dataresponse[0] != '3'))
					sm_snprintf(resp, sizeof(resp), "%s Hmm\r\n", dataresponse);
				TEMPPERM('D')
				else if (randrej > 0 && random() < randrej)
					strlcpy(resp, "451 Random\r\n", sizeof resp);
				else
				{
					ret = SM_SUCCESS;
#if SMTPS2_CDB
					if (Usecdb)
					{
						sessta_id_T rm_id;

						if (Maxcdbs > 0 &&
						    Cdbfiles > Maxcdbs)
						{
							sm_snprintf(rm_id, SMTP_STID_SIZE,
								SMTPS_STID_FORMAT, Rm_id_count, my_index);
							cdb_unlink(Cdb_ctx, rm_id);
							--Cdbfiles;
							++Rm_id_count;
						}

						ss_id_next(my_index, ta_id);
						if (Flat)
							ret = sm_io_open(SmStStdio, ta_id, SM_IO_WRONLY,
								&dfp, SM_IO_WHAT_END);
						else
							ret = cdb_open(Cdb_ctx, ta_id, dfp, SM_IO_WREXCL,
								/*size*/ 0, /*hints*/ 0);
						if (sm_is_err(ret))
						{
							err_report(errfd,
							"ERROR: process %d, cdb_open=%#x, ta_id=%s"
								, my_index, ret
								, ta_id);
							strlcpy(resp, "452 Nope\r\n", sizeof resp);
							if (Stoponerror)
								exit(1);
						}
					}
#endif /* SMTPS2_CDB */
					if (!sm_is_err(ret)) {
						indata = 1;
						ta_state = SSTA_DATA;
						strlcpy(resp, "354 Go\r\n", sizeof resp);
					}
				}
			}
			else if (strncasecmp(buf, "quit", 4) == 0) {
				strlcpy(resp, "221 Bye\r\n", sizeof resp);
			}
			else if (strncasecmp(buf, "starttls", 8) == 0) {
				strlcpy(resp, starttls, sizeof resp);
				strlcat(resp, " oops\r\n", sizeof resp);
			}
			else if (strncasecmp(buf, "helo", 4) == 0) {
				TA_INIT;
				sm_snprintf(resp, sizeof(resp), "250 %s Hi there\r\n", Myname);
			}
			else if (strncasecmp(buf, "ehlo", 4) == 0 && !UseSMTP) {
				TA_INIT;
				gen_ehlo_resp(resp, sizeof(resp));
			}
			else if (strncasecmp(buf, "lhlo", 4) == 0 &&
				srv_socket[srv_socket_index].lmtp > 0)
			{
				TA_INIT;
				if (MAYBE_REJECT(ehloresp[0] != '2'))
					sm_snprintf(resp, sizeof(resp),
						"%s ESMTP Hi\r\n", ehloresp);
				else if (pipelining)
					strlcpy(resp, "250-ESMTP Hi there\r\n250-PIPELINING\r\n250 HELP\r\n",
						sizeof resp);
				else
					strlcpy(resp, "250-ESMTP Hi there\r\n250 HELP\r\n", sizeof resp);
			}
			else if (strncasecmp(buf, "mail from:<", 11) == 0) {
				if (firstmail == 0)
					firstmail = st_time();
				if (eachN > 0 && firstNmail == 0)
					firstNmail = st_time();
				TA_CNT(srv_socket_index)++;
				ta_state = SSTA_MAIL;
				TA_INIT;
				if (MAYBE_REJECT('s' == buf[11] && IS_SMTP_CODE(buf, 12))) {
					sm_snprintf(resp, sizeof(resp),
						"%c%c%c whatever\r\n",
						buf[12], buf[13], buf[14]);
				}
				else if (MAYBE_REJECT(IS_SMTP_CODE(buf, 11))) {
					sm_snprintf(resp, sizeof(resp),
						"%c%c%c whatever\r\n",
						buf[11], buf[12], buf[13]);
				}
				else if (MAYBE_REJECT('s' == buf[11] &&
				         IS_MULTILINE_SMTP_CODE(buf, 12))) {
					sm_snprintf(resp, sizeof(resp),
#if 0
						"550-5.1.0 <example-sendmail@support.example.org>... Your MX reply is \"501 5.1.7 Bad sender's mailbox address syntax.\"\r\n"
						"550 5.1.0 Sender validity for <example-sendmail@support.example.org> not confirmed by MX \"host.example.org\"\r\n");
#else
						"%c%c%c-%c.7.0 multi line first one\r\n"
						"%c%c%c %c.7.0 multi line last one with some other text\r\n",
						buf[12] - MULTILINE_SMTP_OFF, buf[13], buf[14],
						buf[12] - MULTILINE_SMTP_OFF,
						buf[12] - MULTILINE_SMTP_OFF, buf[13], buf[14],
						buf[12] - MULTILINE_SMTP_OFF);
#endif
				}
				else if (MAYBE_REJECT(IS_MULTILINE_SMTP_CODE(buf, 11))) {
					sm_snprintf(resp, sizeof(resp),
						"%c%c%c-multi line first one\r\n"
						"%c%c%c-multi line second\r\n"
						"%c%c%c multi line last one with some other text\r\n",
						buf[11] - MULTILINE_SMTP_OFF, buf[12], buf[13],
						buf[11] - MULTILINE_SMTP_OFF, buf[12], buf[13]);
				}
				TEMPPERM('M')
				else if (randrej > 0 && random() < randrej)
					strlcpy(resp, "451 Random\r\n", sizeof resp);
				else
					strlcpy(resp, "250 Ok\r\n", sizeof resp);
				MAIL_COUNT(srv_socket_index)++;

				/* HACK... this doesn't really parse arguments properly! */
				use_drr = (offer_drr && strstr(buf, "> DRR") != NULL);

				/* HACK... this doesn't really parse arguments properly! */
				use_rsad = (offer_rsad && strstr(buf, "> PRDR") != NULL);
				/* Use some library function??? */
			}
			else if (strncasecmp(buf, "rcpt to:<", 9) == 0) {
				bool drr;
				bool rsad;

				rsad = false;
				drr = false;
				if (use_drr &&
				    MAYBE_REJECT('d' == buf[9] && IS_SMTP_CODE(buf, 10))) {
					sm_snprintf(resp, sizeof(resp),
						"%c%c%c whatever\r\n",
						buf[10], buf[11], buf[12]);
					drr = true;
				}
				else if (use_rsad &&
				         MAYBE_REJECT('d' == buf[9] && IS_SMTP_CODE(buf, 10)))
				{
 					sm_snprintf(resp, sizeof(resp), "%c%c%c whatever\r\n",
						buf[10], buf[11], buf[12]);
					rsad = true;
 				}
				else if (MAYBE_REJECT('r' == buf[9] && IS_SMTP_CODE(buf, 10))) {
					sm_snprintf(resp, sizeof(resp),
						"%c%c%c whatever\r\n",
						buf[10], buf[11], buf[12]);
				}
				else if (MAYBE_REJECT(IS_SMTP_CODE(buf, 9))) {
					sm_snprintf(resp, sizeof(resp),
						"%c%c%c whatever\r\n",
						buf[9], buf[10], buf[11]);
				}
				else if (ta_state < SSTA_MAIL)
					strlcpy(resp, "503 Need MAIL\r\n", sizeof resp);
				TEMPPERM('R')
				else if (randrej > 0 && random() < randrej)
					strlcpy(resp, "451 Random\r\n", sizeof resp);
				else if (onercpt && rcpts > 0)
					strlcpy(resp, "451 one rcpt only\r\n", sizeof resp);
				else {
					if (use_drr) {
						drr = true;
						strlcpy(resp, "250 Ok drr\r\n", sizeof resp);
					}
					else if (use_rsad) {
						rsad = true;
						strlcpy(resp, "250 Ok rsad\r\n", sizeof resp);
					}
					else
						strlcpy(resp, "250 Ok\r\n", sizeof resp);
					ta_state = SSTA_RCPT;
					++rcpts;
					RCPT_CNT_OK(srv_socket_index)++;
				}
				if (drr || rsad) {
					n = ss_rcpts_append(&ss_rcpts_hd, resp);
					if (n != 0) {
						strlcpy(resp, "451 enomem\r\n", sizeof resp);
					}
					else {
						strlcpy(resp, "250 deferred\r\n", sizeof resp);
						ta_state = SSTA_RCPT;
						++rcpts;
						RCPT_CNT_OK(srv_socket_index)++;
					}
				}

				if (srv_socket[srv_socket_index].lmtp > 1) {
					n = ss_rcpts_append(&ss_rcpts_hd, resp);
					if (n != 0)
						strlcpy(resp, "451 enomem\r\n", sizeof resp);
					else {
						if (resp[0] != '2') {
							ta_state = SSTA_RCPT;
							++rcpts;
							RCPT_CNT_OK(srv_socket_index)++;
						}
						strlcpy(resp, "250 lmtp\r\n", sizeof resp);
					}
				}
				RCPT_COUNT(srv_socket_index)++;
			}
			else if (strncasecmp(buf, "rset", 4) == 0) {
				if (MAYBE_REJECT(rsetresp[0] != '2'))
					sm_snprintf(resp, sizeof(resp),
						"%s whatever\r\n",
						rsetresp);
				else
					strlcpy(resp, "250 Ok\r\n", sizeof resp);
				TA_INIT;
				ta_state = SSTA_INIT;
			}
			else if (strncasecmp(buf, "help", 4) == 0) {
				strlcpy(resp, "250 Sorry, I lied.\r\n", sizeof resp);
			}
			else if (resp[0] == '\0') {
				strlcpy(resp, "550 What?\r\n", sizeof resp);
			}
			n = strlen(resp);
			if (n != 0 && (ret = smtpreply(resp, n, fp, true)) != n) {
				err_sys_report(errfd,
					"WARN: can't write response to %s: st_write=%d"
					, inet_ntoa(*from), ret);

				/* ignore error for QUIT */
				if (strncasecmp(buf, "quit", 4) != 0)
					goto done;
			}
			if (debug > 7 && n != 0)
				write(STDERR_FILENO, resp, n);
			if (strncasecmp(buf, "quit", 4) == 0 ||
			    (drop421 && strncmp(resp, "421", 3) == 0))
			{
				RQST_COUNT(srv_socket_index)++;
				goto done;
			}
		}
	}
	if (r < 0) {
		err_sys_report(errfd,
			"sev=WARN, client=%s, st_read=%d, ta_state=%d",
			inet_ntoa(*from), r, ta_state);
	}

  done:
	--CONC_THREADS(srv_socket_index);
	if (fp != NULL) {
		sm_io_close(fp, SM_IO_CF_NONE);
		fp = NULL;
	}
#if SMTPS2_CDB
	if (dfp != NULL) {
		sm_io_close(dfp, SM_IO_CF_RM);
		dfp = NULL;
	}
#endif /* SMTPS2_CDB */
	SM_STR_FREE(rd);
	return;
}


/*
 * Configuration loading function stub.
 */
void
load_configs(void)
{
	err_report(errfd, "INFO: process %d (pid %d): configuration loaded",
		   my_index, my_pid);
}


/*
 * Buffered access logging methods.
 * Note that stdio functions (fopen(3), fprintf(3), fflush(3), etc.) cannot
 * be used if multiple VPs are created since these functions can flush buffer
 * at any point and thus write only partial log record to disk.
 * Also, it is completely safe for all threads of the same VP to write to
 * the same log buffer without any mutex protection (one buffer per VP, of
 * course).
 */
void
logbuf_open(void)
{

}


void
logbuf_flush(void)
{

}


void
logbuf_close(void)
{

}


/******************************************************************
 * Small utility functions
 */

static void
Signal(int sig, void (*handler) (int))
{
	struct sigaction sa;

	sa.sa_handler = handler;
	sigemptyset(&sa.sa_mask);
	sa.sa_flags = 0;
	sigaction(sig, &sa, NULL);
}

static int
cpu_count(void)
{
	int             n;

#if defined (_SC_NPROCESSORS_ONLN)
	n = (int) sysconf(_SC_NPROCESSORS_ONLN);
#elif defined (_SC_NPROC_ONLN)
	n = (int) sysconf(_SC_NPROC_ONLN);
#elif defined (HPUX)
#include <sys/mpctl.h>
	n = mpctl(MPC_GETNUMSPUS, 0, 0);
#else
	n = -1;
	errno = ENOSYS;
#endif

	return n;
}

/******************************************************************/

static void
ss_shutdown(void)
{
	int i;

	for (i = 0; i < sk_count; i++)
	{
		if (strncmp(srv_socket[i].addr, "unix:", 5) == 0)
			(void) unlink(srv_socket[i].addr + 5);
	}
}


syntax highlighted by Code2HTML, v. 0.9.1