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