/*
 * Copyright (c) 2002-2006 Sendmail, Inc. and its suppliers.
 *	All rights reserved.
 *
 * By using this file, you agree to the terms and conditions set
 * forth in the LICENSE file which can be found at the top level of
 * the sendmail distribution.
 */

#include "sm/generic.h"
SM_RCSID("@(#)$Id: smtpc.c,v 1.209 2007/05/13 16:26:16 ca Exp $")

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

#if SM_HEAP_CHECK
extern SM_DEBUG_T SmHeapCheck;
# define HEAP_CHECK (SmHeapCheck > 0)
# define HEAP_REPORT do { if (HEAP_CHECK) sm_heap_report(smioerr, SmHeapCheck);} while (0)
#else /* SM_HEAP_CHECK */
# define HEAP_CHECK	false
# define HEAP_REPORT
#endif /* SM_HEAP_CHECK */

extern sm_stream_T SmStThrNetIO;

/*
**  Client configuration parameters
*/

/* Log files */
#define PID_FILE    "sc.pid"

/*
**  Global data
*/

static sc_ctx_T		 Sc_ctx;	/* SMTPC context */

/* some of the following data should be in sc_ctx */

uint			 conns;
uint			 busy = 0;
ulong			 total = 0;

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

/*
**  Configuration flags/parameters
*/

#if 0
static bool rpools = false;	/* use rpools... */
#endif

/*
**  Forward declarations.
**  Many functions here just exit on error, change them to return
**  an error code instead.
*/

static void	 sc_usage(const char *progname);
static void	 sc_parse_arguments(sc_ctx_P _sc_ctx, int argc, char *argv[]);
#if 0
static void	 sc_start_daemon(void);
#endif
static void	 sc_start_processes(sc_ctx_P _sc_ctx);
#if 0
static void	 wdog_sighandler(int signo);
#endif
static void	 sc_child_sighandler(int signo);
static void	 sc_install_sighandlers(void);
static void	 sc_process_signals(sc_ctx_P _sc_ctx);
static void	 sc_signal(int sig, void (*handler)(int));

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

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

	va_start(ap, fmt);
	if (Sc_ctx.scc_lctx == NULL)
	{
		sm_io_vfprintf(smioerr, fmt, ap);
		sm_io_putc(smioerr, '\n');
	}
	else
	{
		sm_log_vwrite(Sc_ctx.scc_lctx,
			SC_LCAT_INIT, SC_LMOD_CONFIG,
			SM_LOG_ERR, 0,
			fmt, ap);
	}
	va_end(ap);
	exit(ex_code);
}

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

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

	prg = argv[0];
	sm_memzero(&Sc_ctx, sizeof(Sc_ctx));
	if (getuid() == 0 || geteuid() == 0)
	{
		sc_err_quit(EX_USAGE, SM_DONTRUNASROOT, prg);
		exit(EX_USAGE);
	}
	Sc_ctx.sm_magic = SM_SC_CTX_MAGIC;
	Sc_ctx.scc_cnf.sm_magic = SM_SC_CNF_MAGIC;

	ret = sc_init0(&Sc_ctx);
	if (sm_is_err(ret))
	{
		sm_log_write(Sc_ctx.scc_lctx,
			SC_LCAT_INIT, SC_LMOD_CONFIG,
			SM_LOG_ERR, 0,
			"sev=ERROR, func=main, initialization=failed, init0=%m"
			, ret);
		exit(sm_is_perm_err(ret) ? EX_SOFTWARE : EX_OSERR);
	}

	/* Parse command-line options */
	sc_parse_arguments(&Sc_ctx, argc, argv);

	if (Sc_ctx.scc_cnf.sc_cnf_debug > 1)
	{
		sm_log_write(Sc_ctx.scc_lctx,
			SC_LCAT_INIT, SC_LMOD_CONFIG,
			SM_LOG_DEBUG, 10,
			"sev=DBG, func=main, uid=%ld, gid=%ld, euid=%ld, egid=%ld"
			, (long) getuid(), (long) getgid(), (long) geteuid()
			, (long) getegid());
	}

	ret = sc_init_chk(&Sc_ctx);
	if (sm_is_err(ret))
	{
		sm_log_write(Sc_ctx.scc_lctx,
			SC_LCAT_INIT, SC_LMOD_CONFIG,
			SM_LOG_ERR, 0,
			"sev=ERROR, func=main, initialization=failed, init_chk=%m"
			, ret);
		exit(sm_is_perm_err(ret) ? EX_SOFTWARE : EX_OSERR);
	}

#if 0
	/* Start the daemon */
	if (SC_IS_CFLAG(&Sc_ctx, SCC_CFL_BACKGROUND))
		sc_start_daemon();
#endif /* 0 */

	/* Initialize the ST library */
	if (st_init() < 0)
		sc_err_quit(EX_OSERR,
			"sev=ERROR, func=main, initialization=failed, st_init=failed");

	/* Set thread throttling parameters */
	sc_set_thread_throttling(&Sc_ctx);

	ret = sc_init1(&Sc_ctx);
	if (sm_is_err(ret))
		sc_err_quit(sm_is_perm_err(ret) ? EX_SOFTWARE : EX_OSERR,
			"sev=ERROR, func=main, initialization=failed, sc_init1=%m"
			, ret);

	/* Start server processes (VPs) */
	sc_start_processes(&Sc_ctx);

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

	/* Install signal handlers */
	sc_install_sighandlers();

#if 0
	/* Load configuration from config files */
	sc_load_configs(&Sc_ctx);
#endif

	/* Start all threads */
	sc_start_threads(&Sc_ctx);

	/* Become a signal processing thread */
	sc_process_signals(&Sc_ctx);

	/* NOTREACHED */
	return 1;
}

/*
**  SC_USAGE -- sc_usage
**
**	Parameters:
**		progname -- program name
**
**	Returns:
**		none (doesn't return)
*/

static void
sc_usage(const char *progname)
{
	sm_io_fprintf(smioerr,
		"Usage: %s [options]\n"
		"%s SMTP/LMTP client\n"
		"Possible options:\n"
		"-D                    Run in background\n"
		"-d n                  Debug level\n"
		"-f name               Name of configuration file [none]\n"
		"-h                    Print this message\n"
#if SM_HEAP_CHECK
		"-H n                  Set heap check level\n"
#endif
		"-i id                 Set id\n"
		"-L socket             Socket location for LMTP [%s]\n"
		"-N name               Name of smtpc section in configuration file to use []\n"
		"-O timeout            I/O timeout [%d]\n"
		"-p num_processes      Create specified number of processes\n"
		"-P port               Connect to port [%d]\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\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
		, LMTPSOCK		/* -L */
		, SC_IO_TIMEOUT		/* -O */
		, SMTPC_PORT		/* -P */
		, Sc_ctx.scc_cnf.sc_cnf_cdb_base	/* -S C */
		, Sc_ctx.scc_cnf.sc_cnf_smtpcsock	/* -S q */
		);
	exit(EX_USAGE);
}

/*
**  SC_PARSE_ARGUMENTS -- parse arguments
**
**	Parameters:
**		sc_ctx -- SMTPC context
**		argc -- number of arguments
**		argv -- vector of arguments
**
**	Returns:
**		none.
*/

#if SM_SMTPC_HDRMOD_TEST
sm_cstr_P Hdr_pre = NULL;
sm_cstr_P Hdr_app = NULL;
#endif

static void
sc_parse_arguments(sc_ctx_P sc_ctx, int argc, char *argv[])
{
	extern char *optarg;
	int opt, h;
	uint u, showversion;
	char *c;
#if SM_SMTPC_HDRMOD_TEST
	char hdr[256];
#endif

	showversion = 0;
	while ((opt = getopt(argc, argv,
			"Dd:F:f:H:hi:l:L:M:N:O:P:rS:T:t:Vv:w:")) != -1)
	{
		switch (opt)
		{
		  case 'D':
			SC_SET_CFLAG(sc_ctx, SCC_CFL_BACKGROUND);
			break;
		  case 'd':
			sc_ctx->scc_cnf.sc_cnf_debug = atoi(optarg);
			break;
		  case 'F':
			sc_ctx->scc_cnf.sc_cnf_confflags = atoi(optarg);
			break;
		  case 'f':
			c = strdup(optarg);
			if (NULL == c)
			{
				sc_err_quit(EX_OSERR,
					"sev=ERROR, func=sc_parse_arguments, argument=\"%@s\", strdup=%m"
					, optarg, sm_err_temp(errno));
			}
			sc_ctx->scc_cnf.sc_cnf_conffile = c;
			h = sc_read_cnf(sc_ctx, c, &sc_ctx->scc_cnf.sc_cnf_smc);
			if (h != 0)
				sc_err_quit(EX_USAGE,
					"sev=ERROR, func=sc_parse_arguments, status=invalid_configuration_file, error=%m"
					, h);
			break;

		  case 'H':
#if SM_HEAP_CHECK
			SmHeapCheck = atoi(optarg);
			if (SmHeapCheck < 0)
				sc_err_quit(EX_USAGE,
					"sev=ERROR, func=sc_parse_arguments, SmHeapCheck=%s, status=invalid_value"
					, optarg);
#endif /* SM_HEAP_CHECK */
			break;
		  case 'i':
			sc_ctx->scc_cnf.sc_cnf_id = strtol(optarg, &c, 10);
			break;
		  case 'l':
#if 0
			/* currently unused */
			sc_ctx->scc_cnf.sc_cnf_logdir = optarg;
#endif
			break;

#if SM_SMTPC_HDRMOD_TEST
		  case 'M':
			if (optarg == NULL ||
			    (optarg[0] != 'p' && optarg[0] != 'a')
			    || optarg[1] != '=')
			{
				sc_usage(argv[0]);
				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));
			break;
#endif /* SM_SMTPC_HDRMOD_TEST */

		  case 'L':
			c = strdup(optarg);
			if (NULL == c)
				sc_err_quit(EX_OSERR,
					"sev=ERROR, func=sc_parse_arguments, argument=\"%@s\", strdup=%m"
					, optarg, sm_err_temp(errno));
			sc_ctx->scc_cnf.sc_cnf_lmtpsock = c;
			break;
		  case 'N':
			if (sc_ctx->scc_cnf.sc_cnf_conffile != NULL)
				sc_err_quit(EX_USAGE,
					"sev=ERROR, func=sc_parse_arguments, -N must be before -f");
			c = strdup(optarg);
			if (NULL == c)
			{
				sc_err_quit(EX_OSERR,
					"sev=ERROR, func=sc_parse_arguments, argument=\"%@s\", strdup=%m"
					, optarg, sm_err_temp(errno));
			}
			sc_ctx->scc_cnf.sc_cnf_section = c;
			break;

		  case 'O':
			h = atoi(optarg);
			if (h <= 0)
				sc_err_quit(EX_USAGE,
					"sev=ERROR, func=sc_parse_arguments, timeout=%@s, status=invalid_value"
					, optarg);
			sc_ctx->scc_cnf.sc_cnf_timeout = (sm_intvl_T) h;
			break;
		  case 'P':
			sc_ctx->scc_cnf.sc_cnf_port = atoi(optarg);
			break;
		  case 'r':
#if 0
			rpools = true;
#endif
			break;

		  case 'S':
			if (optarg != NULL && ISALPHA(optarg[0])
			    && optarg[1] == '=' && optarg[2] != '\0')
				h = 2;
			else
			{
				sc_usage(argv[0]);
				/* NOTREACHED */
				break;	/* shut up stupid compiler */
			}
			c = NULL;
			switch (optarg[0])
			{
			  case 'C':
			  case 'q':
				c = strdup(optarg + h);
				if (NULL == c)
				{
					sc_err_quit(EX_OSERR,
						"sev=ERROR, func=sc_parse_arguments, argument=\"%@s\", strdup=%m"
						, optarg + h
						, sm_err_temp(errno));
				}
				break;
			  default:
				sc_usage(argv[0]);
				break;
			}
			switch (optarg[0])
			{
			  case 'C':
				sc_ctx->scc_cnf.sc_cnf_cdb_base = c;
				break;
			  case 'q':
				sc_ctx->scc_cnf.sc_cnf_smtpcsock = c;
				break;
			}
			c = NULL;
			break;

		  case 'T':
#if SC_TEST
			u = strtoul(optarg, &c, 0);
			sc_ctx->scc_cnf.sc_cnf_tests = u;
#endif
			break;

		  case 't':
			u = strtoul(optarg, &c, 10);
			if (*c++ == ':')
				SC_MAX_THREADS(sc_ctx) = strtoul(c, NULL, 10);
			if (u <= 0 || SC_MAX_THREADS(sc_ctx) <= 0)
				sc_err_quit(EX_USAGE,
					"sev=ERROR, func=sc_parse_arguments, threads=%s, status=invalid_value"
					, optarg);
			SC_MAX_WAIT_THREADS(sc_ctx) = u;
			break;
		  case 'v':
			sc_ctx->scc_cnf.sc_cnf_loglevel =
				(uint) atoi(optarg);
			break;
		  case 'V':
			++showversion;
			break;
		  case 'w':
			sc_ctx->scc_cnf.sc_cnf_wait4srv =
				(uint) atoi(optarg);
			break;
		  case 'h':
		  case '?':
		  default:
			sc_usage(argv[0]);
		}
	}

	if (showversion > 0)
	{
		(void) sc_showversion(sc_ctx, argv[0],
			sc_ctx->scc_cnf.sc_cnf_conffile, showversion);
		exit(0);
	}
#if 0
	if (sc_ctx->scc_cnf.sc_cnf_logdir == NULL &&
	    SC_IS_CFLAG(sc_ctx, SCC_CFL_BACKGROUND))
	{
		sm_log_write(sc_ctx->scc_lctx,
			SC_LCAT_INIT, SC_LMOD_CONFIG,
			SM_LOG_ERR, 0,
			"sev=ERROR, func=sc_parse_arguments, status=logging_directory_is_required");
		sc_usage(argv[0]);
	}
#endif /* 0 */

	/* Already checked at startup */
	if (getuid() == 0 || geteuid() == 0)
		sc_err_quit(EX_USAGE,
			"sev=ERROR, func=sc_parse_arguments, Sorry Dave, I can't do that.");
}

/*
**  SC_START_PROCESSES -- start processes
**
**	Parameters:
**		sc_ctx -- SMTPC context
**
**	Returns:
**		none
*/

static void
sc_start_processes(sc_ctx_P sc_ctx)
{
	(void)sc_ctx;
	sc_index = 0;
	sc_pid = getpid();
	return;
}

/*
**  SC_INSTALL_SIGHANDLERS -- install sighandlers
**
**	Parameters:
**		none
**
**	Returns:
**		none
*/

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

	/* Create signal pipe */
	if (pipe(p) < 0)
		sc_err_quit(EX_OSERR,
			"sev=ERROR, func=sc_install_sighandlers, index=%d, pid=%d, pipe()=failed, error=%m"
			, sc_index, sc_pid, sm_err_temp(errno));
	if ((sig_pipe[0] = st_netfd_open(p[0])) == NULL ||
	    (sig_pipe[1] = st_netfd_open(p[1])) == NULL)
		sc_err_quit(EX_OSERR,
			"sev=ERROR, func=sc_install_sighandlers, index=%d, pid=%d, st_netfd_open(pipe)=failed, error=%m"
			 , sc_index, sc_pid, sm_err_temp(errno));

	/* Install signal handlers */
	sc_signal(SIGTERM, sc_child_sighandler);	/* terminate */
	sc_signal(SIGHUP,  sc_child_sighandler);	/* restart   */
	sc_signal(SIGUSR1, sc_child_sighandler);	/* dump info */
	sc_signal(SIGUSR2, sc_child_sighandler);	/* dump info */

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

/*
**  SC_CHILD_SIGHANDLER -- signal handler for children
**
**	Parameters:
**		signo -- signal number
**
**	Returns:
**		none
*/

static void
sc_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))
		sc_err_quit(EX_OSERR,
			"sev=ERROR, func=sc_child_sighandler, index=%d, pid=%d, func=ss_child_sighandler, write=failed, error=%m"
			, sc_index, sc_pid, sm_err_temp(errno));
	errno = err;
}

/*
**  SC_PROCESS_SIGNALS -- signal processing thread.
**	Note: this is not called by a signal handler, hence any function
**		can be used here.
**
**	Parameters:
**		sc_ctx -- SMTPC context
**
**	Returns:
**		none
*/

static void
sc_process_signals(sc_ctx_P sc_ctx)
{
	int signo;
	sm_ret_T ret;

	for (;;)
	{
		/* Read the next signal from the signal pipe */
		if (st_read(sig_pipe[0], &signo, sizeof(int), (st_utime_t)-1)
		    != sizeof(int))
			sc_err_quit(EX_OSERR,
				"sev=ERROR, func=sc_process_signals, index=%d, pid=%d, func=ss_process_signals, st_read()=failed, error=%m"
				, sc_index, sc_pid, sm_err_temp(errno));

		switch (signo)
		{
		  case SIGHUP:
			sm_log_write(sc_ctx->scc_lctx,
				SC_LCAT_INTERN, SC_LMOD_SIGNAL,
				SM_LOG_INFO, 9,
				"sev=INFO, func=sc_process_signals, index=%d, pid=%d, signal=SIGHUP, action=none"
				, sc_index, sc_pid);

#if 0
			sc_load_configs(&Sc_ctx);
#endif
			break;
		  case SIGTERM:
			/* "signal" shutdown to threads */
			SC_SET_FLAG(&Sc_ctx, SCC_FL_SHUTDOWN);

			/*
			**  Terminate ungracefully since it is generally not
			**  known how long it will take to gracefully complete
			**  all client sessions.
			*/

			sm_log_write(sc_ctx->scc_lctx,
				SC_LCAT_INTERN, SC_LMOD_SIGNAL,
				SM_LOG_INFO, 9,
				"sev=INFO, func=sc_process_signals, index=%d, pid=%d, signal=SIGTERM, action=terminating"
				, sc_index, sc_pid);
			HEAP_REPORT;
			(void) sm_log_destroy(Sc_ctx.scc_lctx);
			exit(0);
		  case SIGUSR1:
			sm_log_write(sc_ctx->scc_lctx,
				SC_LCAT_INTERN, SC_LMOD_SIGNAL,
				SM_LOG_INFO, 9,
				"sev=INFO, func=sc_process_signals, index=%d, pid=%d, signal=SIGUSR1, action=printinfo"
				, sc_index, sc_pid);

			sc_dump_info(&Sc_ctx);
			HEAP_REPORT;
			break;
		  case SIGUSR2:
			/* Reopen log files - needed for log rotation */
			ret = sm_log_reopen(Sc_ctx.scc_lctx);
			sm_log_write(sc_ctx->scc_lctx,
				SC_LCAT_INTERN, SC_LMOD_SIGNAL,
				SM_LOG_INFO, 9,
				"sev=INFO, func=sc_process_signals, index=%d, pid=%d, signal=SIGUSR2, action=reopened_logfiles"
				, sc_index, sc_pid);

			break;
		  default:
			sm_log_write(sc_ctx->scc_lctx,
				SC_LCAT_INTERN, SC_LMOD_SIGNAL,
				SM_LOG_INFO, 8,
				"sev=INFO, func=sc_process_signals, index=%d, pid=%d, signal=%d, action=ignored"
				, sc_index, sc_pid, signo);
		}
	}

	/* NOTREACHED */
	return;
}

/*
**  SC_SIGNAL -- install signal handler
**
**	Parameters:
**		sig -- signal
**		handler -- signal handler
**
**	Returns:
**		none
*/

static void
sc_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);
}


syntax highlighted by Code2HTML, v. 0.9.1