/*
* Copyright (c) 2002-2006 Sendmail, Inc. and its suppliers.
* All rights reserved.
*
* By using this file, you agree to the terms and conditions set
* forth in the LICENSE file which can be found at the top level of
* the sendmail distribution.
*/
#define MTA_USE_STATETHREADS 1 /* -> Makefile? */
#include "sm/generic.h"
SM_RCSID("@(#)$Id: smtps.c,v 1.440 2007/10/17 02:54:26 ca Exp $")
#include "sm/common.h"
#include "sm/assert.h"
#include "sm/error.h"
#include "sm/ctype.h"
#include "sm/memops.h"
#include "sm/io.h"
#include "sm/str.h"
#include "sm/string.h"
#include "sm/limits.h"
#include "sm/types.h"
#include "sm/net.h"
#include "sm/wait.h"
#include "sm/fcntl.h"
#include "sm/signal.h"
#include "sm/pwd.h"
#include "sm/grp.h"
#include "sm/cdb.h"
#include "statethreads/st.h"
#include "sm/stsock.h"
#include "sm/cmsg.h"
#include "sm/hostname.h"
#include "sm/misc.h"
#include "sm/sysexits.h"
#include "sm/util.h"
#include "sm/units.h"
#include "sm/tls.h"
#include "sm/tlsbio.h"
#include "sm/sasl.h"
#include "s2q.h"
#include "smtps.h"
#define SMTPSRV_DEFINE 1
#include "smtpsrv.h"
#include "s2m.h"
#define SMTPS_LOG_DEFINES 1
#include "sm/smar.h"
#include "log.h"
#include "sm/resource.h"
#include "sm/version.h"
#include "sm/sm-conf.h"
#include "sm/sm-conf-prt.h"
#include "sm/smtpdef.h"
#include "sm/confsetpath.h"
#include "smtpsh.h"
#include "pmilter.h"
#if SMTPS_WITH_C
#include "smtpc/smtpc.h"
#include "sm/da.h"
#include "sm/das.h"
#include "smtpc/c2q.h"
#define SMTPC_DEFINE 1
#include "smtpc/smtpch.h"
#define SMTPC_LOG_DEFINES 1
#include "smtpc/log.h"
#include "sm/sccnfdef.h"
#include "sm/confsetpath.h"
#endif
#if SM_HEAP_CHECK
extern SM_DEBUG_T SmHeapCheck;
# define HEAP_CHECK (SmHeapCheck > 0)
#else
# define HEAP_CHECK 0
#endif
extern sm_stream_T SmStThrNetIO;
/*
** Some comments about things to do/change/...
The sequence:
ret = sm_s2q_*()
if (sm_is_err(ret)) goto error;
ret = sm_w4q2s_reply(ss_sess);
can be merged into a single call if we always wait directly
for a response from the QMGR after we sent some request.
However, note the comment about PIPELINING in the smX docs;
we may want to hide the latency of address (anti-spam) checks.
*/
/*
** Server configuration parameters
*/
/* Default server port */
#define SERV_PORT_DEFAULT 25
/* Max number of "spare" threads per process per socket */
#define MAX_WAIT_THREADS_DEFAULT 8
/*
** Number of file descriptors needed to handle one client session
** 1 client communication
** 1 data file
*/
#define SS_FD_PER_THREAD 2
/*
** Global data
** Put this into server context instead (-> smtps.h)?
*/
int Sk_count = 0; /* Number of listening sockets */
static st_netfd_t Ssfd = NULL; /* Socket for SMTPS (one session if set) */
static pid_t *Vp_pids; /* Array of VP pids */
pid_t My_pid = -1; /* Current process pid */
st_netfd_t Sig_pipe[2]; /* Signal pipe */
/*
** Configuration flags/parameters
*/
sm_cstr_P HostnameNone, HostnameNoMatch, HostnameTempPTR, HostnameTempA,
HostnameBogus;
/* see below: sm_log_setfp_fd(..., Errfp, SMIOOUT_FILENO); */
static sm_file_T *Errfp = smiolog;
#if SS_TEST
int Unsafe = 0;
static in_addr_T Test_client_ip;
#endif
/* should be in some "global" context */
ss_ctx_T Ss_ctx;
/*
** Thread throttling parameters (all numbers are per listening socket).
** Zero values mean use default.
*/
/* todo: put this into a SMTP context? */
uint Max_cur_threads = 0; /* current max number of threads */
bool Rpools = false; /* use rpools... */
/*
** Forward declarations
*/
static void ss_usage(const char *_progname);
static void ss_parse_arguments(ss_ctx_P _ss_ctx, int _argc, char *_argv[]);
#if 0
static void ss_start_daemon(void);
#endif
static void ss_set_thread_throttling(ss_ctx_P _ss_ctx);
static void ss_create_listeners(ss_ctx_P _ss_ctx);
#if 0
static void ss_open_log_files(void);
#endif
static void ss_start_processes(ss_ctx_P _ss_ctx);
static void ss_install_sighandlers(void);
void ss_sighdl_err(int _err, void *_ctx);
static void ss_start_threads(ss_ctx_P _ss_ctx);
static void ss_process_signals(ss_ctx_P _ss_ctx, bool _children);
static void *ss_handle_connections(void *arg);
static void ss_dump_info(ss_ctx_P _ss_ctx);
static int ss_one_session(st_netfd_t _ssfd, ss_ctx_P _ss_ctx);
static sm_ret_T ss_init0(ss_ctx_P _ss_ctx);
static sm_ret_T ss_init_chk(ss_ctx_P _ss_ctx);
static sm_ret_T ss_init1(ss_ctx_P _ss_ctx);
static sm_ret_T ss_initp(ss_ctx_P _ss_ctx);
#if 0
static void ss_load_configs(ss_ctx_P _ss_ctx);
#endif
/*
** SMTP server
**
** Note: the functionality described in this paragraph has been disabled.
** This server creates a constant number of processes ("virtual processors"
** or VPs) and replaces them when they die. Each virtual processor manages
** its own independent set of state threads (STs), the number of which varies
** with load against the server. Each state thread listens to exactly one
** listening socket. The initial process becomes the watchdog, waiting for
** children (VPs) to die or for a signal requesting termination or restart.
** Upon receiving a restart signal (SIGHUP), all VPs close and then reopen
** log files and reload configuration. All currently active connections remain
** active. It is assumed that new configuration affects only request
** processing and not the general server parameters such as number of VPs,
** thread limits, bind addresses, etc. Those are specified as command line
** arguments, so the server has to be stopped and then started again in order
** to change them.
**
** Each state thread loops processing connections from a single listening
** socket. Only one ST runs on a VP at a time, and VPs do not share memory,
** so no mutual exclusion locking is necessary on any data, and the entire
** server is free to use all the static variables and non-reentrant library
** functions it wants, greatly simplifying programming and debugging and
** increasing performance (for example, it is safe to ++ and -- all global
** counters or call inet_ntoa(3) without any mutexes). The current thread on
** each VP maintains equilibrium on that VP, starting a new thread or
** terminating itself if the number of spare threads exceeds the lower or
** upper limit.
**
** All I/O operations on sockets must use the State Thread library's I/O
** functions because only those functions prevent blocking of the entire VP
** process and perform state thread scheduling.
*/
/*
** Fatal error unrelated to a system call.
** Print a message and terminate.
*/
static void
ss_err_quit(int ex_code, const char *fmt,...)
{
va_list ap;
va_start(ap, fmt);
if (Ss_ctx.ssc_lctx == NULL) {
sm_io_vfprintf(smioerr, fmt, ap);
sm_io_putc(smioerr, '\n');
}
else {
sm_log_vwrite(Ss_ctx.ssc_lctx,
SS_LCAT_SERVER, SS_LMOD_SERVER,
SM_LOG_ERROR, 1,
fmt, ap);
}
va_end(ap);
exit(ex_code);
/* NOTREACHED */
}
/*
** MAIN -- SMTPS main
**
** Parameters:
** argc -- number of arguments
** argv -- vector of arguments
**
** Returns:
** exit code
*/
int
main(int argc, char *argv[])
{
sm_ret_T ret;
size_t size;
char *prg;
prg = argv[0];
sm_memzero(&Ss_ctx, sizeof(Ss_ctx));
if (getuid() == 0 || geteuid() == 0) {
ss_err_quit(EX_USAGE, SM_DONTRUNASROOT, prg);
/* NOTREACHED */
return EX_USAGE;
}
#if SS_CTX_CHECK
Ss_ctx.sm_magic = SM_SS_CTX_MAGIC;
#endif
Ss_ctx.ssc_cnf.sm_magic = SM_SS_CNF_MAGIC;
ret = ss_init0(&Ss_ctx);
if (sm_is_err(ret)) {
/* XXX How to properly map an error code to an exit code? */
ss_err_quit(sm_is_perm_err(ret) ? EX_SOFTWARE : EX_OSERR,
"sev=ERROR, func=main, initialization=failed, ss_init0=%m"
, ret);
}
/* Parse command-line options */
ss_parse_arguments(&Ss_ctx, argc, argv);
if (Ss_ctx.ssc_cnf.ss_cnf_debug > 4)
sm_log_write(Ss_ctx.ssc_lctx,
SS_LCAT_SERVER, SS_LMOD_SERVER,
SM_LOG_INFO, 10,
"sev=INFO, func=main, uid=%ld, gid=%ld, euid=%ld, egid=%ld\n"
, (long) getuid(), (long) getgid(), (long) geteuid()
, (long) getegid());
ret = ss_init_chk(&Ss_ctx);
if (sm_is_err(ret)) {
/* XXX How to properly map an error code to an exit code? */
ss_err_quit(sm_is_perm_err(ret) ? EX_SOFTWARE : EX_OSERR,
"sev=ERROR, func=main, initialization=failed, ss_init_chk=%m"
, ret);
}
/* Allocate array of server pids */
size = Ss_ctx.ssc_cnf.ss_cnf_vp_count * sizeof(*Vp_pids);
Vp_pids = sm_zalloc(size);
if (NULL == Vp_pids)
ss_err_quit(EX_TEMPFAIL,
"sev=ERROR, func=main, malloc=failed, size=%lu",
(ulong) size);
#if 0
/* Start the daemon */
if (SSC_IS_CFLAG(&Ss_ctx, SSC_CFL_BACKGROUND))
ss_start_daemon();
#endif /* 0 */
/* Initialize the ST library */
if (st_init() < 0)
ss_err_quit(sm_is_perm_err(ret) ? EX_SOFTWARE : EX_OSERR,
"sev=ERROR, func=main, initialization=failed, st_init=failed");
/* Set thread throttling parameters */
ss_set_thread_throttling(&Ss_ctx);
ret = ss_init1(&Ss_ctx);
if (sm_is_err(ret))
ss_err_quit(sm_is_perm_err(ret) ? EX_SOFTWARE : EX_OSERR,
"sev=ERROR, func=main, initialization=failed, init=%m"
, ret);
/* Create listening sockets */
if (NULL == Ssfd)
ss_create_listeners(&Ss_ctx);
#if 0
/* Open log files */
ss_open_log_files();
#endif /* 0 */
/* Start server processes (VPs) */
ss_start_processes(&Ss_ctx);
ret = ss_initp(&Ss_ctx);
if (sm_is_err(ret)) {
int rc;
if (SSC_IS_FLAG(&Ss_ctx, SSC_FL_RESTARTDEP))
rc = EX_RESTARTDEP;
else
rc = sm_is_perm_err(ret) ? EX_SOFTWARE : EX_OSERR;
ss_err_quit(rc,
"sev=ERROR, func=main, initialization=failed, initp=%M"
, ret);
}
/* Turn time caching on */
st_timecache_set(1);
/* Install signal handlers */
ss_install_sighandlers();
#if 0
/* Load configuration from config files */
ss_load_configs(&Ss_ctx);
#endif
if (Ssfd != NULL)
return ss_one_session(Ssfd, &Ss_ctx);
/* Start all threads */
ss_start_threads(&Ss_ctx);
/* Become a signal processing thread */
ss_process_signals(&Ss_ctx, false);
/* NOTREACHED */
return 1;
}
/*
** SS_USAGE -- print usage
**
** Parameters:
** progname -- name of program
**
** Returns:
** exit(EX_USAGE)
*/
static void
ss_usage(const char *progname)
{
sm_io_fprintf(smioerr, "Usage: %s [options]\n"
"%s SMTP server\n"
"Possible options:\n"
"-1 Serialize all accept() calls\n"
"-8 Offer 8BITMIME\n"
"-a Use access map (via SMAR)\n"
"-b host:port Bind to specified address; multiple addresses\n"
" are permitted\n"
"-C regex client IP addresses from which relaying is allowed\n"
" [%s]\n"
#if SS_TEST
"-c ipaddr set fixed client IP address (for testing!)\n"
#endif
"-D Run in background\n"
"-d n Set debug level\n"
"-F n Set configuration flags\n"
"-f name Name of configuration file\n"
"-g n Set group id (numeric) for CDB\n"
"-h Print this message\n"
#if SM_HEAP_CHECK
"-H n Set heap check level\n"
#endif
"-I n Set server id [0]\n"
"-i Run in interactive mode (default)\n"
"-L socket Socket over which to receive listen fd\n"
#if SS_LOGDIR
"-l socket Directory for logging\n"
#endif
"-N name Name of smtps section in configuration file to use\n"
"-O timeout I/O timeout [%d]\n"
"-p num_processes Create specified number of processes\n"
"-q backlog Set max length of pending connections queue\n"
"-s Perform one SMTP session over stdin/stdout\n"
"-S a=name Socket for communication with SMAR [%s]\n"
"-S C=name Directory for CDB [%s]\n"
"-S q=name Socket for communication with QMGR [%s]\n"
"-t min_thr:max_thr Specify thread limits per listening\n"
"-T regex Recipient addresses to which relaying is allowed\n"
" (localhost is replaced by hostname if possible)\n"
" [%s]\n"
"-v loglevel Set loglevel\n"
"-V Show version\n"
"-w timeout Time to wait for QMGR to be ready\n"
, progname
, MTA_VERSION_STR
, RELAY_CLT /* -C */
, SS_IO_TIMEOUT /* -O */
, Ss_ctx.ssc_cnf.ss_cnf_smarsock
, Ss_ctx.ssc_cnf.ss_cnf_cdb_base
, Ss_ctx.ssc_cnf.ss_cnf_smtpssock
, RELAY_RCPT /* -T */
);
exit(EX_USAGE);
/* NOTREACHED */
}
/*
** SS_SHOWVERSION -- show version information
**
** Parameters:
** ss_ctx -- SMTP Server context
** progname -- program name
** cnf -- configuration file name
** level -- how much detail?
**
** Returns:
** usual error code
*/
static sm_ret_T
ss_showversion(ss_ctx_P ss_ctx, const char *progname, const char *cnf, uint level)
{
char *prefix;
prefix = "";
if (level >= SM_SV_RD_CONF)
prefix = "# ";
sm_io_fprintf(smioout, "%s%s: %s\n", prefix, progname, MTA_VERSION_STR);
if (SM_SHOW_VERSION(level))
sm_io_fprintf(smioout, "%sversion=0x%08x\n",
prefix, MTA_VERSION);
#if MTA_USE_TLS
if (SM_SHOW_LIBS(level))
(void) sm_tlsversionprt(smioout);
#endif
#if MTA_USE_SASL
if (SM_SHOW_LIBS(level))
(void) sm_saslversionprt(smioout);
#endif
if (SM_SHOW_OPTIONS(level)) {
sm_io_fprintf(smioout, "compile time options:\n"
#if MTA_USE_TLS
"MTA_USE_TLS\n"
#endif
#if MTA_USE_SASL
"MTA_USE_SASL\n"
#endif
#if MTA_USE_PMILTER
"MTA_USE_PMILTER\n"
#endif
#if SS_CHECK_LINE_LEN
/* untested feature */
"SS_CHECK_LINE_LEN\n"
#endif
#if SS_EHLO_ACCESS_CHK
"SS_EHLO_ACCESS_CHK\n"
#endif
#if MTA_USE_RSAD
"MTA_USE_RSAD\n"
#endif
#if SS_TEST
"SS_TEST\n"
#endif
);
}
if (SM_SHOW_RD_CONF(level) && cnf != NULL && *cnf != '\0') {
sm_io_fprintf(smioout,
"# configuration-file=%s\n\n", cnf);
(void) sm_conf_prt_conf(ss_global_defs,
&ss_ctx->ssc_cnf, smioout);
}
else if (SM_SHOW_DFLT_CONF(level)) {
sm_io_fprintf(smioout, "# default configuration:\n\n");
(void) sm_conf_prt_dflt(ss_global_defs, 0, smioout);
}
else if (SM_SHOW_ALL_CONF(level)) {
sm_io_fprintf(smioout,
"# configuration including flags.\n"
"# note: this data cannot be used as configuration filen\n"
"# but it shows the available flags.\n"
);
(void) sm_conf_prt_dflt(ss_global_defs, SMC_FLD_FLAGS, smioout);
}
sm_io_flush(smioout);
return sm_error_perm(SM_EM_SMTPS, SM_E_DONTSTART);
}
/*
** SS_RELAY_TO -- parse relay-to regex
**
** Parameters:
** ss_ctx -- SMTP Server context
** str -- string representation of regex
**
** Returns:
** usual error code
*/
sm_ret_T
ss_relay_to(ss_ctx_P ss_ctx, const char *str)
{
int r;
if (SSC_IS_FLAG(ss_ctx, SSC_FL_RELAYTO))
regfree(&ss_ctx->ssc_relayto);
r = regcomp(&ss_ctx->ssc_relayto, str,
REG_EXTENDED|REG_ICASE|REG_NOSUB);
if (0 == r)
SSC_SET_FLAG(ss_ctx, SSC_FL_RELAYTO);
return r;
}
/*
** SS_RELAY_FROM -- parse relay-from regex
**
** Parameters:
** ss_ctx -- SMTP Server context
** str -- string representation of regex
**
** Returns:
** usual error code
*/
sm_ret_T
ss_relay_from(ss_ctx_P ss_ctx, const char *str)
{
int r;
if (SSC_IS_FLAG(ss_ctx, SSC_FL_RELAYFROM))
regfree(&ss_ctx->ssc_relayfrom);
r = regcomp(&ss_ctx->ssc_relayfrom, str,
REG_EXTENDED|REG_ICASE|REG_NOSUB);
if (0 == r)
SSC_SET_FLAG(ss_ctx, SSC_FL_RELAYFROM);
return r;
}
/*
** SS_PARSE_ARGUMENTS -- parse arguments
**
** Parameters:
** ss_ctx -- SMTP Server context
** argc -- number of arguments
** argv -- vector of arguments
**
** Returns:
** nothing.
**
** Side Effects: may invoke exit()
*/
#if SM_TEST_HDRMOD
sm_cstr_P Hdr_pre = NULL;
sm_cstr_P Hdr_app = NULL;
#endif /* SM_TEST_HDRMOD */
static void
ss_parse_arguments(ss_ctx_P ss_ctx, int argc, char *argv[])
{
extern char *optarg;
int opt, r;
uint u, showversion;
char *c;
#if SM_TEST_HDRMOD
char hdr[256];
#endif
showversion = 0;
while ((opt = getopt(argc, argv,
"18ab:C:c:Dd:F:f:g:H:hI:il:L:M:N:o:O:p:q:rsS:t:T:U:v:Vw:")) != -1)
{
switch (opt) {
case '1':
/*
** Serialization decision is tricky on some platforms.
** For example, Solaris 2.6 and above has kernel
** sockets implementation, so supposedly there is no
** need for serialization. The ST library may be
** compiled on one OS version, but used on another,
** so the need for serialization should be determined
** at run time by the application. Since it's just
** an example, the serialization decision is left up
** to user. Only on platforms where the serialization
** is never needed on any OS version
** st_netfd_serialize_accept() is a no-op.
*/
SSC_SET_CFLAG(ss_ctx, SSC_CFL_SERIAL_ACC);
break;
case '8':
SSC_SET_CFLAG(ss_ctx, SSC_CFL_8BITMIME);
break;
case 'a':
SSC_SET_CFLAG(ss_ctx, SSC_CFL_ACCESS_DB);
break;
case 'b':
if (Sk_count >= SS_MAX_BIND_ADDRS)
ss_err_quit(EX_USAGE,
"sev=ERROR, func=ss_parse_arguments, max_number_of_bind_addresses=%d, status=limit_exceeded"
, SS_MAX_BIND_ADDRS);
if ((c = strdup(optarg)) == NULL)
ss_err_quit(EX_TEMPFAIL,
"sev=ERROR, func=ss_parse_arguments, argument=\"%@s\", strdup=%m"
, optarg, sm_err_temp(errno));
ss_sck_ctx[Sk_count++].sssc_addr = c;
break;
case 'C': /* Client from which we allow relaying */
r = ss_relay_from(ss_ctx, optarg);
if (r != 0)
ss_err_quit(EX_OSERR,
"sev=ERROR, func=ss_parse_arguments, args=\"%s\", recomp=%d"
, optarg, r);
break;
#if SS_TEST
case 'c': /* client IP address (testing only!) */
(void) sm_inet_a2ipv4(optarg, NULL, &Test_client_ip.s_addr);
break;
#endif
case 'D':
SSC_SET_CFLAG(ss_ctx, SSC_CFL_BACKGROUND);
break;
case 'd':
ss_ctx->ssc_cnf.ss_cnf_debug = (uint) atoi(optarg);
break;
case 'F':
ss_ctx->ssc_cnf.ss_cnf_cflags = (uint32_t) strtoul(optarg, NULL, 0);
break;
case 'f':
c = strdup(optarg);
if (NULL == c) {
ss_err_quit(EX_OSERR,
"sev=ERROR, func=ss_parse_arguments, argument=\"%@s\", strdup=%m"
, optarg, sm_err_temp(errno));
}
ss_ctx->ssc_cnf.ss_cnf_conffile= c;
r = ss_read_cnf(ss_ctx, c, &ss_ctx->ssc_cnf.ss_cnf_smc);
if (r != 0)
ss_err_quit(EX_USAGE,
"sev=ERROR, func=ss_parse_arguments, status=invalid_configuration_file, error=%m"
, r);
break;
case 'g':
ss_ctx->ssc_cnf.ss_cnf_cdb_gid = (gid_t) atoi(optarg);
/* todo: more checks, e.g., gid in getgrouplist()? */
if (ss_ctx->ssc_cnf.ss_cnf_cdb_gid <= 0) {
ss_err_quit(EX_USAGE,
"sev=ERROR, func=ss_parse_arguments, gid=%d, status=must_be_greater_than_zero"
, ss_ctx->ssc_cnf.ss_cnf_cdb_gid);
}
break;
case 'H':
#if SM_HEAP_CHECK
SmHeapCheck = atoi(optarg);
if (SmHeapCheck < 0)
ss_err_quit(EX_USAGE,
"sev=ERROR, func=ss_parse_arguments, SmHeapCheck=\"%s\", status=invalid_value"
, optarg);
#endif /* SM_HEAP_CHECK */
break;
case 'I':
ss_ctx->ssc_cnf.ss_cnf_id_base = atoi(optarg);
break;
case 'i':
SSC_CLR_CFLAG(ss_ctx, SSC_CFL_BACKGROUND);
break;
case 'l':
#if SS_LOGDIR
c = strdup(optarg);
if (NULL == c)
ss_err_quit(EX_TEMPFAIL,
"sev=ERROR, func=ss_parse_arguments, argument=\"%@s\", strdup=%m"
, optarg, sm_err_temp(errno));
ss_ctx->ssc_cnf.ss_cnf_logdir = c;
#endif /* SS_LOGDIR */
break;
case 'L':
c = strdup(optarg);
if (NULL == c)
ss_err_quit(EX_TEMPFAIL,
"sev=ERROR, func=ss_parse_arguments, argument=\"%@s\", strdup=%m"
, optarg, sm_err_temp(errno));
ss_ctx->ssc_cnf.ss_cnf_fd_socket = c;
break;
case 'M':
#if SM_TEST_HDRMOD
if (NULL == optarg ||
(optarg[0] != 'p' && optarg[0] != 'a') || optarg[1] != '=')
{
ss_err_quit(EX_USAGE,
"sev=ERROR, func=ss_parse_arguments, -M wrong: %s"
, optarg);
break;
}
strlcpy(hdr, optarg + 2, sizeof(hdr));
strlcat(hdr, "\r\n", sizeof(hdr));
if (optarg[0] == 'p')
Hdr_pre = sm_cstr_scpyn((const uchar *)hdr,
strlen(hdr));
else
Hdr_app = sm_cstr_scpyn((const uchar *)hdr,
strlen(hdr));
#else /* SM_TEST_HDRMOD */
sm_io_fprintf(smioerr,
"status=option %c not available, "
"fix=compile with -DSM_TEST_HDRMOD\n"
, opt);
#endif /* SM_TEST_HDRMOD */
break;
case 'N':
if (ss_ctx->ssc_cnf.ss_cnf_conffile != NULL)
ss_err_quit(EX_USAGE,
"sev=ERROR, func=ss_parse_arguments, -N must be before -f");
c = strdup(optarg);
if (NULL == c) {
ss_err_quit(EX_OSERR,
"sev=ERROR, func=ss_parse_arguments, argument=\"%@s\", strdup=%m"
, optarg, sm_err_temp(errno));
}
ss_ctx->ssc_cnf.ss_cnf_section = c;
break;
case 'o':
if (optarg != NULL && ISALPHA(optarg[0]) && optarg[1] == '=')
r = 2;
else {
ss_err_quit(EX_USAGE,
"sev=ERROR, func=ss_parse_arguments, option=\"%@s\", status=illegal_syntax"
, optarg);
break;
}
u = (uint) atoi(optarg + r);
if (u > 1) {
switch (optarg[0]) {
case 'T':
ss_ctx->ssc_cnf.ss_cnf_t_tot_lim = u;
break;
case 'R':
ss_ctx->ssc_cnf.ss_cnf_r_tot_lim = u;
break;
case 'r':
ss_ctx->ssc_cnf.ss_cnf_r_ta_lim = u;
break;
#if 0
case 'o':
ss_ctx->ssc_r_ok_lim = u;
break;
#endif /* 0 */
default:
break;
}
}
else
ss_err_quit(EX_USAGE,
"sev=ERROR, func=ss_parse_arguments, option=\"%@s\", status=invalid_value"
, optarg);
break;
case 'O':
r = atoi(optarg);
if (r <= 0)
ss_err_quit(EX_USAGE,
"sev=ERROR, func=ss_parse_arguments, timeout=\"%@s\", status=invalid_value"
, optarg);
ss_ctx->ssc_cnf.ss_cnf_timeout = (sm_intvl_T) r;
break;
case 'p':
ss_ctx->ssc_cnf.ss_cnf_vp_count = atoi(optarg);
if (ss_ctx->ssc_cnf.ss_cnf_vp_count < 1)
ss_err_quit(EX_USAGE,
"sev=ERROR, func=ss_parse_arguments, processes=\"%@s\", status=invalid_number",
optarg);
break;
case 'r':
Rpools = true;
break;
case 's': {
int newfd;
/* use macro?? */
newfd = open("/dev/null", O_RDWR);
if (newfd < 0)
ss_err_quit(EX_OSERR,
"sev=ERROR, func=ss_parse_arguments, open(/dev/null)=failed, errno=%d"
, errno);
newfd = dup2(STDIN_FILENO, newfd);
if (newfd < 0)
ss_err_quit(EX_OSERR,
"sev=ERROR, func=ss_parse_arguments, dup2(STDIN)=failed, errno=%d"
, errno);
newfd = dup2(STDOUT_FILENO, newfd);
if (newfd < 0)
ss_err_quit(EX_OSERR,
"sev=ERROR, func=ss_parse_arguments, dup2(STDOUT)=failed, errno=%d"
, errno);
Ssfd = st_netfd_open(newfd);
if (NULL == Ssfd)
ss_err_quit(EX_OSERR,
"sev=ERROR, func=ss_parse_arguments, st_netfd_open()=failed, errno=%d"
, errno);
ss_ctx->ssc_cnf.ss_cnf_max_threads = 1;
SSC_CLR_CFLAG(ss_ctx, SSC_CFL_BACKGROUND);
}
break;
case 'S':
if (optarg != NULL && ISALPHA(optarg[0])
&& optarg[1] == '=' && optarg[2] != '\0')
r = 2;
else {
ss_usage(argv[0]);
/* NOTREACHED */
break; /* shut up stupid compiler */
}
c = NULL;
switch (optarg[0]) {
case 'a':
case 'C':
case 'q':
c = strdup(optarg + r);
if (NULL == c) {
ss_err_quit(EX_OSERR,
"sev=ERROR, func=ss_parse_arguments, argument=\"%@s\", strdup=failed, errno=%d"
, optarg + r, errno);
}
break;
default:
ss_usage(argv[0]);
break;
}
switch (optarg[0]) {
case 'a':
ss_ctx->ssc_cnf.ss_cnf_smarsock = c;
break;
case 'C':
ss_ctx->ssc_cnf.ss_cnf_cdb_base = c;
break;
case 'q':
ss_ctx->ssc_cnf.ss_cnf_smtpssock = c;
break;
}
c = NULL;
break;
case 't':
ss_ctx->ssc_cnf.ss_cnf_max_wait_threads =
(int) strtol(optarg, &c, 10);
if (*c++ == ':')
ss_ctx->ssc_cnf.ss_cnf_max_threads = atoi(c);
if (ss_ctx->ssc_cnf.ss_cnf_max_wait_threads <= 0
|| ss_ctx->ssc_cnf.ss_cnf_max_threads <= 0
|| ss_ctx->ssc_cnf.ss_cnf_max_wait_threads >
ss_ctx->ssc_cnf.ss_cnf_max_threads)
ss_err_quit(EX_USAGE,
"sev=ERROR, func=ss_parse_arguments, threads=\"%@s\", status=invalid_value"
, optarg);
break;
case 'U':
#if SS_TEST
Unsafe = atoi(optarg);
#endif
break;
case 'q':
r = atoi(optarg);
if (r < 1)
ss_err_quit(EX_USAGE,
"sev=ERROR, func=ss_parse_arguments, listen_queue_size=\"%@s\", status=invalid_value"
, optarg);
ss_ctx->ssc_cnf.ss_cnf_listenq_size = r;
break;
case 'T': /* Recipient to which we allow relaying */
r = ss_relay_to(ss_ctx, optarg);
if (r != 0)
ss_err_quit(EX_OSERR,
"sev=ERROR, func=ss_parse_arguments, recomp=%d", r);
break;
case 'v':
ss_ctx->ssc_cnf.ss_cnf_loglevel =
(uint) atoi(optarg);
break;
case 'V':
++showversion;
break;
case 'w':
ss_ctx->ssc_cnf.ss_cnf_wait4srv = (uint) atoi(optarg);
break;
case 'x':
#if SS_DEBUG
s = optarg;
rv = sm_set_dbgcats(&s, ss_debug, SM_ARRAY_SIZE(ss_debug));
if (sm_is_err(rv))
ret = rv;
#endif
case 'h':
case '?':
default:
ss_usage(argv[0]);
}
}
if (showversion > 0) {
(void) ss_showversion(ss_ctx, argv[0],
ss_ctx->ssc_cnf.ss_cnf_conffile, showversion);
exit(0);
/* NOTREACHED */
}
if (ss_ctx->ssc_cnf.ss_cnf_conffile != NULL &&
ss_ctx->ssc_cnf.ss_cnf_loglevel > 64)
{
ss_prt_cnf(&ss_ctx->ssc_cnf, smioerr, true);
}
#if SS_LOGDIR
if (NULL == ss_ctx->ssc_cnf.ss_cnf_logdir &&
SSC_IS_CFLAG(ss_ctx, SSC_CFL_BACKGROUND))
{
/* not really... */
sm_log_write(ss_ctx->ssc_lctx,
SS_LCAT_SERVER, SS_LMOD_SERVER,
SM_LOG_ERROR, 1,
"sev=ERROR, func=ss_parse_arguments, status=logging_directory_is_required");
ss_usage(argv[0]);
}
#endif /* SS_LOGDIR */
if (0 == ss_ctx->ssc_cnf.ss_cnf_vp_count)
ss_ctx->ssc_cnf.ss_cnf_vp_count = 1;
if (ss_ctx->ssc_cnf.ss_cnf_cdb_gid > 0 &&
getgrgid(ss_ctx->ssc_cnf.ss_cnf_cdb_gid) == NULL)
{
ss_err_quit(EX_USAGE,
"sev=ERROR, func=ss_parse_arguments, gid=%d, status=group_does_not_exist"
, ss_ctx->ssc_cnf.ss_cnf_cdb_gid);
}
if (Sk_count != 0 && ss_ctx->ssc_cnf.ss_cnf_fd_socket != NULL) {
ss_err_quit(EX_USAGE,
"sev=ERROR, func=ss_parse_arguments, status=cannot_use_-L_and_-b_together");
}
if (0 == Sk_count) {
Sk_count = 1;
ss_sck_ctx[0].sssc_addr = "0.0.0.0";
}
}
/*
** SS_REAP_CHILD -- A child process exited
**
** Parameters:
** ss_ctx -- SMTP Server context
**
** Returns:
** pid
*/
static int
ss_reap_child(ss_ctx_P ss_ctx)
{
int i, status, failtype;
pid_t pid;
sigset_t mask, omask;
for (;;) {
pid = wait3(&status, WNOHANG, (struct rusage *) 0);
if (pid <= 0)
return -1;
/* Find index of the exited child */
for (i = 0; i < ss_ctx->ssc_cnf.ss_cnf_vp_count; i++) {
if (Vp_pids[i] == pid)
break;
}
/* Block signals while printing and forking */
sigemptyset(&mask);
sigaddset(&mask, SIGTERM);
sigaddset(&mask, SIGHUP);
sigaddset(&mask, SIGUSR1);
sigaddset(&mask, SIGUSR2);
sigprocmask(SIG_BLOCK, &mask, &omask);
failtype = sm_child_status(status);
if (WIFEXITED(status)) {
sm_log_write(ss_ctx->ssc_lctx,
SS_LCAT_SERVER, SS_LMOD_SERVER,
SM_LOG_WARN, 4,
"sev=WARN, func=ss_reap_child, pid=%d, process=%d, child_pid=%d, status=exited, stat=%d"
, (int) My_pid, i, pid, WEXITSTATUS(status));
}
else if (WIFSIGNALED(status)) {
sm_log_write(ss_ctx->ssc_lctx,
SS_LCAT_SERVER, SS_LMOD_SERVER,
SM_LOG_WARN, 4,
"sev=WARN, func=ss_reap_child, pid=%d, process=%d, child_pid=%d, status=terminated, signal=%d"
, (int) My_pid, i, pid, WTERMSIG(status));
}
else if (WIFSTOPPED(status)) {
/* fixme: don't start a new process?? */
failtype = SM_FAILED_STOP;
sm_log_write(ss_ctx->ssc_lctx,
SS_LCAT_SERVER, SS_LMOD_SERVER,
SM_LOG_WARN, 4,
"sev=WARN, func=ss_reap_child, pid=%d, process=%d, child_pid=%d, status=stopped, signal=%d"
, (int) My_pid, i, pid, WSTOPSIG(status));
}
else {
sm_log_write(ss_ctx->ssc_lctx,
SS_LCAT_SERVER, SS_LMOD_SERVER,
SM_LOG_WARN, 4,
"sev=WARN, func=ss_reap_child, pid=%d, process=%d, child_pid=%d, status=terminated, why=unknown_termination_reason"
, (int) My_pid, i, pid);
}
if (!SSC_IS_FLAG(ss_ctx, SSC_FL_TERMINATING) &&
failtype != SM_FAILED_STOP)
{
/* Fork another VP */
if ((pid = fork()) < 0) {
sm_log_write(ss_ctx->ssc_lctx,
SS_LCAT_SERVER, SS_LMOD_SERVER,
SM_LOG_ERROR, 4,
"sev=ERROR, func=ss_reap_child, pid=%d, status=cannot_create_process, errno=%d"
, (int) My_pid, errno);
}
else if (0 == pid) {
ss_ctx->ssc_id = i + ss_ctx->ssc_cnf.ss_cnf_id_base;
My_pid = getpid();
/* Child returns to continue in main() */
return pid;
}
Vp_pids[i] = pid;
}
else {
Vp_pids[i] = 0;
}
/* Restore the signal mask */
sigprocmask(SIG_SETMASK, &omask, NULL);
}
/* NOTREACHED */
return -1;
}
/*
** SS_SIG_CHILDREN -- send a signal to all children
**
** Parameters:
** ss_ctx -- SMTP Server context
** signo -- signal number
**
** Returns:
** nothing.
*/
static void
ss_sig_children(ss_ctx_P ss_ctx, int signo)
{
int i;
for (i = 0; i < ss_ctx->ssc_cnf.ss_cnf_vp_count; i++) {
if (Vp_pids[i] > 0)
kill(Vp_pids[i], signo);
}
}
/*
** SS_DO_SHUTDOWN -- really perform shutdown?
**
** Parameters:
** ss_ctx -- SMTP Server context
**
** Returns:
** true iff shutdown is required
*/
static bool
ss_do_shutdown(ss_ctx_P ss_ctx)
{
if (SSC_IS_FLAG(ss_ctx, SSC_FL_SHTDWN_FRC))
return true;
if (0 == BUSY_THREADS(0))
return true;
if (ss_ctx->ssc_shutdown > 0 &&
ss_ctx->ssc_shutdown + 2 * ss_ctx->ssc_cnf.ss_cnf_timeout < st_time()) {
return true;
}
return false;
}
/*
** SS_START_PROCESSES -- start SMTP server processes if requested
**
** Parameters:
** ss_ctx -- SMTP Server context
**
** Returns:
** nothing.
**
** Side Effects:
** in background mode: invoke fork(); does not return.
** exit() on error
*/
static void
ss_start_processes(ss_ctx_P ss_ctx)
{
sm_ret_T ret;
int i;
pid_t pid;
My_pid = getpid();
if (!SSC_IS_CFLAG(ss_ctx, SSC_CFL_BACKGROUND) &&
ss_ctx->ssc_cnf.ss_cnf_vp_count <= 1)
{
ss_ctx->ssc_id = ss_ctx->ssc_cnf.ss_cnf_id_base;
return;
}
for (i = 0; i < ss_ctx->ssc_cnf.ss_cnf_vp_count; i++) {
if ((pid = fork()) < 0) {
sm_log_write(ss_ctx->ssc_lctx,
SS_LCAT_SERVER, SS_LMOD_SERVER,
SM_LOG_ERROR, 1,
"sev=ERROR, func=ss_start_processes, pid=%d, status=cannot_create_process, errno=%d"
, (int) My_pid, errno);
if (0 == i)
exit(EX_OSERR);
sm_log_write(ss_ctx->ssc_lctx,
SS_LCAT_SERVER, SS_LMOD_SERVER,
SM_LOG_WARN, 7,
"sev=WARN, func=ss_start_processes, pid=%d, processes_started=%d, processes_configured=%d"
, (int) My_pid, i
, ss_ctx->ssc_cnf.ss_cnf_vp_count);
ss_ctx->ssc_cnf.ss_cnf_vp_count = i;
break;
}
if (0 == pid) {
ss_ctx->ssc_id = i + ss_ctx->ssc_cnf.ss_cnf_id_base;
My_pid = getpid();
/* Child returns to continue in main() */
return;
}
Vp_pids[i] = pid;
}
/*
** Parent process becomes a "watchdog" and never returns to main().
*/
ret = st_install_sighandlers(
SM_HDL_SIG_CHLD|SM_HDL_SIG_TERM|SM_HDL_SIG_HUP|SM_HDL_SIG_USR1|SM_HDL_SIG_USR2,
ss_sighdl_err, NULL);
if (sm_is_err(ret)) {
ss_err_quit(EX_OSERR,
"sev=ERROR, func=ss_start_processes, pid=%d, st_install_sighandlers=%#x"
, (int) My_pid, ret);
}
ss_process_signals(ss_ctx, true);
}
/*
** SS_SET_THREAD_THROTTLING -- set min/max thread numbers
**
** Parameters:
** ss_ctx -- SMTP Server context
** (uses global variables)
**
** Returns:
** nothing
*/
static void
ss_set_thread_throttling(ss_ctx_P ss_ctx)
{
/*
** For simplicity, the minimal size of thread pool is considered
** as a maximum number of spare threads (ss_cnf_max_wait_threads) that
** will be created upon server startup. The pool size can grow up
** to the ss_cnf_max_threads value. Note that this is a per listening
** socket limit. It is also possible to limit the total number of
** threads for all sockets rather than impose a per socket limit.
*/
/*
** Calculate total values across all processes.
** All numbers are per listening socket.
*/
if (0 == ss_ctx->ssc_cnf.ss_cnf_max_wait_threads)
ss_ctx->ssc_cnf.ss_cnf_max_wait_threads =
MAX_WAIT_THREADS_DEFAULT
* ss_ctx->ssc_cnf.ss_cnf_vp_count;
/* Assuming that each client session needs SS_FD_PER_THREAD fds */
if (0 == ss_ctx->ssc_cnf.ss_cnf_max_threads)
ss_ctx->ssc_cnf.ss_cnf_max_threads =
(st_getfdlimit() * ss_ctx->ssc_cnf.ss_cnf_vp_count)
/ SS_FD_PER_THREAD / Sk_count;
if (ss_ctx->ssc_cnf.ss_cnf_max_wait_threads
> ss_ctx->ssc_cnf.ss_cnf_max_threads)
ss_ctx->ssc_cnf.ss_cnf_max_wait_threads =
ss_ctx->ssc_cnf.ss_cnf_max_threads;
/*
** Now calculate per-process values.
*/
if ((ss_ctx->ssc_cnf.ss_cnf_max_wait_threads
% ss_ctx->ssc_cnf.ss_cnf_vp_count) != 0)
ss_ctx->ssc_cnf.ss_cnf_max_wait_threads =
ss_ctx->ssc_cnf.ss_cnf_max_wait_threads
/ ss_ctx->ssc_cnf.ss_cnf_vp_count + 1;
else
ss_ctx->ssc_cnf.ss_cnf_max_wait_threads =
ss_ctx->ssc_cnf.ss_cnf_max_wait_threads
/ ss_ctx->ssc_cnf.ss_cnf_vp_count;
if ((ss_ctx->ssc_cnf.ss_cnf_max_threads
% ss_ctx->ssc_cnf.ss_cnf_vp_count) != 0)
ss_ctx->ssc_cnf.ss_cnf_max_threads =
ss_ctx->ssc_cnf.ss_cnf_max_threads
/ ss_ctx->ssc_cnf.ss_cnf_vp_count + 1;
else
ss_ctx->ssc_cnf.ss_cnf_max_threads =
ss_ctx->ssc_cnf.ss_cnf_max_threads
/ ss_ctx->ssc_cnf.ss_cnf_vp_count;
if (ss_ctx->ssc_cnf.ss_cnf_min_wait_threads
> ss_ctx->ssc_cnf.ss_cnf_max_wait_threads)
ss_ctx->ssc_cnf.ss_cnf_min_wait_threads =
ss_ctx->ssc_cnf.ss_cnf_max_wait_threads;
Max_cur_threads = ss_ctx->ssc_cnf.ss_cnf_max_threads;
}
/*
** SS_GET_FD -- get fd to listen on via ss_ctx->ssc_cnf.ss_cnf_fd_socket
**
** Parameters:
** ss_ctx -- SMTP Server context
**
** Returns:
** >=0: socket (fd)
** <0: usual error code
*/
static sm_ret_T
ss_get_fd(ss_ctx_P ss_ctx)
{
sm_ret_T ret;
int sock;
int addrlen;
ssize_t i;
st_netfd_t fd, lfd;
struct sockaddr addr;
char buf[2]; /* Should this be bigger to receive some data, */
/* e.g., IP address, port number for tracing etc? */
lfd = fd = NULL;
sock = INVALID_SOCKET;
ret = un_st_server_listen(ss_ctx->ssc_cnf.ss_cnf_fd_socket, 10, &lfd);
if (sm_is_err(ret)) {
ss_err_quit(sm_is_perm_err(ret) ? EX_SOFTWARE : EX_OSERR,
"sev=ERROR, func=ss_get_fd, socket=%s, listen=%m"
, ss_ctx->ssc_cnf.ss_cnf_fd_socket, ret);
}
addrlen = sizeof(addr);
fd = st_accept(lfd, &addr, &addrlen, (st_utime_t) -1);
if (NULL == fd) {
st_netfd_close(lfd);
ss_err_quit(sm_is_perm_err(ret) ? EX_SOFTWARE : EX_OSERR,
"sev=ERROR, func=ss_get_fd, socket=%s, accept=failed, error=%m"
, ss_ctx->ssc_cnf.ss_cnf_fd_socket, sm_err_temp(errno));
}
i = sm_read_fd(fd, buf, 1, &sock);
if (!is_valid_socket(sock))
ss_err_quit(sm_is_perm_err(ret) ? EX_SOFTWARE : EX_OSERR,
"sev=ERROR, func=ss_get_fd, socket=%s, sock_fd=%d, read_fd=%d"
, ss_ctx->ssc_cnf.ss_cnf_fd_socket, sock, i);
return sock;
}
/*
** SS_CREATE_LISTENERS -- create listeners based on ss_sck_ctx[] data
**
** Parameters:
** ss_ctx -- SMTP Server context
** (uses global variables Sk_count, ss_sck_ctx[])
**
** Returns:
** nothing
*/
static void
ss_create_listeners(ss_ctx_P ss_ctx)
{
int i, n, sock;
char *c;
struct sockaddr_in serv_addr;
struct hostent *hp;
short port;
for (i = 0; i < Sk_count && ss_sck_ctx[i].sssc_addr != NULL; i++) {
if (ss_ctx->ssc_cnf.ss_cnf_fd_socket != NULL) {
sock = ss_get_fd(ss_ctx);
if (!is_valid_socket(sock))
ss_err_quit(EX_OSERR,
"sev=ERROR, func=ss_create_listeners, socket=%s, ss_get_fd=failed"
, ss_ctx->ssc_cnf.ss_cnf_fd_socket);
i = 0;
/* todo: read port from configuration file? */
ss_sck_ctx[i].sssc_port = -1;
}
else {
port = 0;
if ((c = strchr(ss_sck_ctx[i].sssc_addr, ':')) != NULL) {
*c++ = '\0';
port = (short) atoi(c);
}
if (ss_sck_ctx[i].sssc_addr[0] == '\0')
ss_sck_ctx[i].sssc_addr = "0.0.0.0";
if (port <= 0)
port = SERV_PORT_DEFAULT;
/* Create server socket */
if ((sock = socket(PF_INET, SOCK_STREAM, 0)) < 0)
ss_err_quit(EX_OSERR,
"sev=ERROR, func=ss_create_listeners, socket()=failed, error=%m"
, sm_err_temp(errno));
n = 1;
if (setsockopt(sock, SOL_SOCKET, SO_REUSEADDR,
(char *)&n, sizeof(n)) < 0)
ss_err_quit(EX_OSERR,
"sev=ERROR, func=ss_create_listeners, setsockopt(SO_REUSEADDR)=failed, error=%m"
, sm_err_temp(errno));
sm_memzero(&serv_addr, sizeof(serv_addr));
serv_addr.sin_family = AF_INET;
serv_addr.sin_port = htons(port);
serv_addr.sin_addr.s_addr = inet_addr(ss_sck_ctx[i].sssc_addr);
if (INADDR_NONE == serv_addr.sin_addr.s_addr) {
/* not dotted-decimal */
hp = gethostbyname(ss_sck_ctx[i].sssc_addr);
if (NULL == hp)
ss_err_quit(EX_CONFIG,
"sev=ERROR, func=ss_create_listeners, hostname=%s, gethostbyname=failed, error=%m"
, ss_sck_ctx[i].sssc_addr
, sm_err_temp(errno));
sm_memcpy(&serv_addr.sin_addr, hp->h_addr,
hp->h_length);
}
ss_sck_ctx[i].sssc_port = port;
if (bind(sock, (struct sockaddr *)&serv_addr, sizeof(serv_addr))
< 0)
{
ss_err_quit(EX_OSERR,
"sev=ERROR, func=ss_create_listeners, bind()=failed, address=%s, port=%hd, error=%m"
, ss_sck_ctx[i].sssc_addr, port
, sm_err_temp(errno));
}
if (listen(sock, ss_ctx->ssc_cnf.ss_cnf_listenq_size)
< 0)
ss_err_quit(EX_OSERR,
"sev=ERROR, func=ss_create_listeners, listen()=failed, error=%m"
, sm_err_temp(errno));
}
/* Create file descriptor object from OS socket */
if ((ss_sck_ctx[i].sssc_lfd = st_netfd_open_socket(sock)) == NULL)
ss_err_quit(EX_OSERR,
"sev=ERROR, func=ss_create_listeners, st_netfd_open_socket()=failed, erron=%m"
, sm_err_temp(errno));
/*
** On some platforms (e.g. IRIX, Linux) accept() serialization
** is never needed for any OS version. In that case
** st_netfd_serialize_accept() is just a no-op.
** Also see the comment above.
*/
if (SSC_IS_CFLAG(ss_ctx, SSC_CFL_SERIAL_ACC) &&
st_netfd_serialize_accept(ss_sck_ctx[i].sssc_lfd) < 0)
ss_err_quit(EX_OSERR,
"sev=ERROR, func=ss_create_listeners, st_netfd_serialize_accept()=failed, error=%m"
, sm_err_temp(errno));
}
}
/*
** SS_SIGHDL_ERR -- Callback for signal handler error
**
** Parameters:
** err -- errno encountered
** ctx -- context (UNUSED)
**
** Returns:
** exits.
*/
void
ss_sighdl_err(int err, void *ctx /* UNUSED */)
{
(void)ctx; /* UNUSED */
ss_err_quit(EX_OSERR,
"sev=ERROR, func=ss_sighdl_err, pid=%d, err=%m"
, (int) My_pid, sm_err_temp(err));
}
/*
** SS_INSTALL_SIGHANDLERS -- install signal handlers
**
** Parameters:
** none
**
** Returns:
** nothing; exits on error
*/
static void
ss_install_sighandlers(void)
{
sm_ret_T ret;
ret = st_install_sighandlers(
SM_HDL_SIG_TERM|SM_HDL_SIG_HUP|SM_HDL_SIG_USR1|SM_HDL_SIG_USR2,
ss_sighdl_err, NULL);
if (sm_is_err(ret))
ss_err_quit(EX_OSERR,
"sev=ERROR, func=ss_install_sighandlers, pid=%d, st_install_sighandlers=%#x"
, (int) My_pid, ret);
}
/*
** SS_PROCESS_SIGNALS -- signal processing thread.
** Read signal number from pipe and acts accordingly
**
** Parameters:
** ss_ctx -- SMTP Server context
**
** Returns:
** does not return, but may exit.
*/
static void
ss_process_signals(ss_ctx_P ss_ctx, bool children)
{
char sig;
st_utime_t tmo;
tmo = (st_utime_t)-1;
for (;;) {
/* Read the next signal from the signal pipe */
if (st_read(Sig_pipe[SM_RD_SIG_PIPE], &sig, sizeof(char), tmo)
!= sizeof(char))
{
if (tmo != (st_utime_t)-1 && ETIME == errno) {
sm_log_write(ss_ctx->ssc_lctx,
SS_LCAT_SERVER, SS_LMOD_SERVER,
SM_LOG_INFO, 11,
"sev=INFO, func=ss_process_signals, pid=%d, st_read=timeout, flags=%#x"
, (int) My_pid, ss_ctx->ssc_flags);
if (SSC_IS_FLAG(ss_ctx, SSC_FL_SHTDWN_TS)) {
sig = SM_SIG_SHUT;
SSC_SET_FLAG(ss_ctx, SSC_FL_SHTDWN_FRC);
}
}
else
ss_err_quit(EX_OSERR,
"sev=ERROR, func=ss_process_signals, pid=%d, st_read=%m"
, (int) My_pid, sm_err_temp(errno));
}
if (children) {
int signo;
pid_t pid;
signo = 0;
switch (sig) {
case SM_SIG_HUP:
signo = SIGHUP;
break;
case SM_SIG_TERM:
signo = SIGTERM;
SSC_SET_FLAG(ss_ctx, SSC_FL_TERMINATING);
break;
case SM_SIG_USR1:
signo = SIGUSR1;
break;
case SM_SIG_USR2:
signo = SIGUSR2;
break;
case SM_SIG_CHLD:
pid = ss_reap_child(ss_ctx);
if (0 == pid)
return;
break;
default:
break;
}
if (signo != 0)
ss_sig_children(ss_ctx, signo);
}
switch (sig) {
case SM_SIG_HUP:
sm_log_write(ss_ctx->ssc_lctx,
SS_LCAT_SERVER, SS_LMOD_SERVER,
SM_LOG_INFO, 9,
"sev=INFO, func=ss_process_signals, pid=%d, signal=SIGHUP, action=none"
, (int) My_pid);
#if 0
ss_load_configs(ss_ctx);
#endif
break;
case SM_SIG_TERM:
/*
** Terminate ungracefully since it is generally not
** known how long it will take to gracefully complete
** all client sessions.
*/
sm_log_write(ss_ctx->ssc_lctx,
SS_LCAT_SERVER, SS_LMOD_SERVER,
SM_LOG_INFO, 9,
"sev=INFO, func=ss_process_signals, pid=%d, signal=SIGTERM, action=terminating"
, (int) My_pid);
(void) sm_log_destroy(Ss_ctx.ssc_lctx);
exit(0);
/* NOTREACHED */
case SM_SIG_USR1:
sm_log_write(ss_ctx->ssc_lctx,
SS_LCAT_SERVER, SS_LMOD_SERVER,
SM_LOG_INFO, 9,
"sev=INFO, func=ss_process_signals, pid=%d, signal=SIGUSR1, action=printinfo"
, (int) My_pid);
/* Print server info to stderr */
ss_dump_info(ss_ctx);
/* Print server info to stderr */
if (HEAP_CHECK)
sm_heap_report(smioerr, 3);
break;
case SM_SIG_USR2:
/* Reopen log files - needed for log rotation */
(void) sm_log_reopen(ss_ctx->ssc_lctx);
sm_log_write(ss_ctx->ssc_lctx,
SS_LCAT_SERVER, SS_LMOD_SERVER,
SM_LOG_INFO, 9,
"sev=INFO, func=ss_process_signals, pid=%d, signal=SIGUSR2, action=reopened_logfiles"
, (int) My_pid);
break;
case SM_SIG_SHUT:
/* graceful termination (if possible) */
if (!SSC_IS_FLAG(ss_ctx, SSC_FL_SHTDWN_TS)) {
tmo = SEC2USEC(2 * ss_ctx->ssc_cnf.ss_cnf_timeout);
SSC_SET_FLAG(ss_ctx, SSC_FL_SHTDWN_TS);
}
if (!ss_do_shutdown(ss_ctx))
break;
sm_log_write(ss_ctx->ssc_lctx,
SS_LCAT_SERVER, SS_LMOD_SERVER,
SM_LOG_INFO, 9,
"sev=INFO, func=ss_process_signals, pid=%d, signal=SHUT, action=terminating"
, (int) My_pid);
exit(0);
/* NOTREACHED */
default:
sm_log_write(ss_ctx->ssc_lctx,
SS_LCAT_SERVER, SS_LMOD_SERVER,
SM_LOG_INFO, 9,
"sev=INFO, func=ss_process_signals, pid=%d, signal=%c, action=ignored"
, (int) My_pid, sig);
}
}
return;
}
/*
** SS_START_THREADS -- start minimum number of connection handling threads
**
** Parameters:
** ss_ctx -- SMTP Server context
**
** Returns:
** nothing
**
** Side Effects:
** exit() on error
*/
static void
ss_start_threads(ss_ctx_P ss_ctx)
{
int i, threads;
uint u;
/* Create connections handling threads */
for (i = 0; i < Sk_count; i++) {
sm_log_write(ss_ctx->ssc_lctx,
SS_LCAT_SERVER, SS_LMOD_SERVER,
SM_LOG_INFO, 9,
"sev=INFO, func=ss_start_threads, version=%s, pid=%d, where=start, threads=%d, addr=%s, port=%d"
, MTA_VERSION_STR
, (int) My_pid, ss_ctx->ssc_cnf.ss_cnf_max_wait_threads
, ss_sck_ctx[i].sssc_addr, ss_sck_ctx[i].sssc_port);
WAIT_THREADS(i) = 0;
BUSY_THREADS(i) = 0;
MAXB_THREADS(i) = 0;
RQST_COUNT(i) = 0;
TA_COUNT(i) = 0;
threads = 0;
for (u = 0; u < ss_ctx->ssc_cnf.ss_cnf_max_wait_threads; u++) {
if (st_thread_create(ss_handle_connections, (void *) (long) i, 0, 0) != NULL)
threads++;
else
sm_log_write(ss_ctx->ssc_lctx,
SS_LCAT_SERVER, SS_LMOD_SERVER,
SM_LOG_ERROR, 8,
"sev=ERROR, func=ss_start_threads, pid=%d, st_thread_create()=failed, u=%u"
, (int) My_pid, u);
}
if (0 == threads) {
sm_s2q_stop(ss_ctx->ssc_s2q_ctx);
sm_s2q_stop(ss_ctx->ssc_s2a_ctx);
#if MTA_USE_PMILTER
sm_s2q_stop(ss_ctx->ssc_s2m_ctx);
#endif
exit(EX_OSERR);
/* NOTREACHED */
}
}
}
/*
** SS_INIT0 -- first part of initialization (before options are read)
**
** Parameters:
** ss_ctx -- SMTP Server context
**
** Returns:
** usual return code
*/
static sm_ret_T
ss_init0(ss_ctx_P ss_ctx)
{
sm_ret_T ret;
SM_IS_SS_CTX(ss_ctx);
ret = sm_log_create(NULL, &ss_ctx->ssc_lctx, &ss_ctx->ssc_lcfg);
if (sm_is_err(ret))
return ret;
ret = sm_log_setfp_fd(ss_ctx->ssc_lctx, Errfp, SMIOLOG_FILENO);
if (sm_is_err(ret))
return ret;
ss_ctx->ssc_cnf.ss_cnf_timeout = SS_IO_TIMEOUT;
ss_ctx->ssc_cnf.ss_cnf_wait4srv = 1;
ss_ctx->ssc_cnf.ss_cnf_listenq_size = LISTENQ_SIZE_DEFAULT;
ss_ctx->ssc_cnf.ss_cnf_loglevel = UINT_MAX;
ss_ctx->ssc_cnf.ss_cnf_w4a2s = TMO_W4A2S;
ss_ctx->ssc_cnf.ss_cnf_mod_tmo = SS_RCB_SND_TMO;
ss_ctx->ssc_cnf.ss_cnf_maxhops = MTA_MAXHOPS;
ss_ctx->ssc_cnf.ss_cnf_max_msg_sz_kb = SM_MAX_MSG_SZ_KB;
ss_ctx->ssc_cnf.ss_cnf_min_wait_threads = 2;
/* some limits, set very high so they don't interfere by default */
ss_ctx->ssc_cnf.ss_cnf_t_tot_lim = UINT_MAX / 2;
ss_ctx->ssc_cnf.ss_cnf_r_tot_lim = UINT_MAX / 2;
ss_ctx->ssc_cnf.ss_cnf_r_ta_lim = UINT_MAX / 4;
#if 0
ss_ctx->ssc_cnf.ss_cnf_r_fail_lim = UINT_MAX / 4;
ss_ctx->ssc_cnf.ss_cnf_r_ok_lim = UINT_MAX / 2;
#endif /* 0 */
ss_ctx->ssc_cnf.ss_cnf_sess_max_badcmds = SS_SESS_MAX_BADCMDS;
ss_ctx->ssc_cnf.ss_cnf_sess_max_nopcmds = SS_SESS_MAX_NOPCMDS;
ss_ctx->ssc_cnf.ss_cnf_sess_max_invldaddr = SS_SESS_MAX_INVLDADDR;
ss_ctx->ssc_cnf.ss_cnf_ta_max_badcmds = SS_TA_MAX_BADCMDS;
ss_ctx->ssc_cnf.ss_cnf_ta_max_nopcmds = SS_TA_MAX_NOPCMDS;
ss_ctx->ssc_cnf.ss_cnf_ta_max_invldaddr = SS_TA_MAX_INVLDADDR;
ss_ctx->ssc_cnf.ss_cnf_smarsock = smarsock;
ss_ctx->ssc_cnf.ss_cnf_cdb_base = "";
ss_ctx->ssc_cnf.ss_cnf_smtpssock = smsmtpssock;
#if MTA_USE_PMILTER
ss_ctx->ssc_cnf.ss_cnf_w4m2s = TMO_W4M2S;
#endif
ss_ctx->ssc_cnf.ss_cnf_cflags = SSC_CFL_STARTTLS|SSC_CFL_AUTH|SSC_CFL_RSAD;
#define SM_CRT_CSTR(cstr, val) \
do { \
cstr = sm_cstr_crt((uchar *) (val), strlen((val))); \
if (NULL == (cstr)) \
return sm_error_temp(SM_EM_SMTPS, ENOMEM); \
} while (0)
SM_CRT_CSTR(HostnameNone, "Hostname_Not_Determined");
SM_CRT_CSTR(HostnameNoMatch, "Hostname_No_Match");
SM_CRT_CSTR(HostnameTempPTR, "Hostname_Temp_PTR");
SM_CRT_CSTR(HostnameTempA, "Hostname_Temp_A");
SM_CRT_CSTR(HostnameBogus, "Hostname_Bogus");
#if SS_TEST
Test_client_ip.s_addr = INADDR_ANY;
#endif
return ret;
}
/*
** SS_INIT_CHK -- check configuration etc (after options are read)
**
** Parameters:
** ss_ctx -- SMTP Server context
**
** Returns:
** usual return code
*/
static sm_ret_T
ss_init_chk(ss_ctx_P ss_ctx)
{
sm_ret_T ret;
SM_IS_SS_CTX(ss_ctx);
ret = SM_SUCCESS;
#if MTA_USE_TLS
ret = sm_tlsversionok();
if (sm_is_err(ret))
return ret;
#endif
#if MTA_USE_SASL
ret = sm_saslversionok();
if (sm_is_err(ret))
return ret;
#endif
/* question: check map version?? */
return ret;
}
/*
** SS_INIT1 -- second part of initialization; after options are read,
** before individual processes are started, i.e., only initialize data
** here that is "global" (identical, can be shared) for every process.
**
** Parameters:
** ss_ctx -- SMTP Server context
**
** Returns:
** usual return code
*/
static sm_ret_T
ss_init1(ss_ctx_P ss_ctx)
{
sm_ret_T ret;
int r;
SM_IS_SS_CTX(ss_ctx);
/* convert timeout from s to micro seconds for statethreads */
ss_ctx->ssc_mod_tmo = SEC2USEC(ss_ctx->ssc_cnf.ss_cnf_mod_tmo);
ret = sm_log_setdebuglevel(ss_ctx->ssc_lctx,
ss_ctx->ssc_cnf.ss_cnf_loglevel);
if (sm_is_err(ret))
return ret;
if (NULL == ss_ctx->ssc_hostname) {
ret = sm_myhostname(&ss_ctx->ssc_hostname);
if (sm_is_err(ret)) {
sm_log_write(ss_ctx->ssc_lctx,
SS_LCAT_SERVER, SS_LMOD_SERVER,
SM_LOG_ERROR, 1,
"sev=ERROR, func=ss_init1, status=cannot_determine_my_hostname, ret=%m"
, ret);
return ret;
}
}
if (!SSC_IS_FLAG(ss_ctx, SSC_FL_RELAYFROM)) {
r = regcomp(&ss_ctx->ssc_relayfrom, RELAY_CLT, REG_ICASE|REG_NOSUB);
if (r != 0) {
sm_log_write(ss_ctx->ssc_lctx,
SS_LCAT_SERVER, SS_LMOD_SERVER,
SM_LOG_ERROR, 2,
"sev=ERROR, func=ss_init1, where=relay_from, pattern=%s, recomp=%d"
, RELAY_CLT, r);
return sm_error_perm(SM_EM_SMTPS, r);
}
SSC_SET_FLAG(ss_ctx, SSC_FL_RELAYFROM);
}
if (!SSC_IS_FLAG(ss_ctx, SSC_FL_RELAYTO)) {
char *pat;
char relto[MAXHOSTNAMELEN + 18];
if (sm_snprintf(relto, sizeof(relto),
"(^<postmaster|@%S)>$", ss_ctx->ssc_hostname) <
(int) sizeof(relto))
pat = relto;
else
pat = RELAY_RCPT;
r = regcomp(&ss_ctx->ssc_relayto, pat, REG_EXTENDED|REG_ICASE|REG_NOSUB);
if (r != 0) {
sm_log_write(ss_ctx->ssc_lctx,
SS_LCAT_SERVER, SS_LMOD_SERVER,
SM_LOG_ERROR, 2,
"sev=ERROR, func=ss_init1, where=relay_to, pattern=%s, recomp=%d"
, pat, r);
return sm_error_perm(SM_EM_SMTPS, r);
}
SSC_SET_FLAG(ss_ctx, SSC_FL_RELAYTO);
}
ret = sm_gen_conf_path(ss_ctx->ssc_cnf.ss_cnf_cdb_base,
ss_ctx->ssc_cnf.ss_cnf_smarsock,
smarsock,
&ss_ctx->ssc_cnf.ss_cnf_smarsock_abs,
&ss_ctx->ssc_cnf.ss_cnf_smarsock_alloc);
if (sm_is_err(ret))
return ret;
ret = sm_gen_conf_path(ss_ctx->ssc_cnf.ss_cnf_cdb_base,
ss_ctx->ssc_cnf.ss_cnf_smtpssock,
smsmtpssock,
&ss_ctx->ssc_cnf.ss_cnf_smtpssock_abs,
&ss_ctx->ssc_cnf.ss_cnf_smtpssock_alloc);
if (sm_is_err(ret))
return ret;
ret = cdb_start(ss_ctx->ssc_cnf.ss_cnf_cdb_base,
&ss_ctx->ssc_cdb_ctx);
if (sm_is_err(ret))
return ret;
return ret;
}
#if MTA_USE_PMILTER
/*
** SS_CONN2MILT -- connect to milter
**
** Parameters:
** ss_ctx -- SMTP Server context
**
** Returns:
** usual return code
*/
static sm_ret_T
ss_conn2milt(ss_ctx_P ss_ctx, ss_sess_P ss_sess)
{
sm_ret_T ret;
SM_IS_SS_CTX(ss_ctx);
ret = SM_SUCCESS;
/* SSC_FL_PM_TRYCONN should be checked by caller */
if (0 == ss_ctx->ssc_cnf.ss_cnf_miltsockspec.sckspc_type
|| !SSC_IS_CFLAG(ss_ctx, SSC_CFL_PMILTER)
|| SSC_IS_FLAG(ss_ctx, SSC_FL_PM_USE)
|| SSC_IS_FLAG(ss_ctx, SSC_FL_PM_TRYING)
#if 0
|| (ss_ctx->ssc_pm_lasttry > 0 &&
ss_ctx->ssc_pm_lasttry + 1 >= st_time()) /* CONF timeout */
#endif
/* exponential delay? */
)
{
sm_log_write(ss_ctx->ssc_lctx,
SS_LCAT_SERVER, SS_LMOD_SERVER,
SM_LOG_DEBUG, 14,
"sev=DBG, func=ss_conn2milt, ss_sess=%s, status=do_not_try_to_reconnect, flags=%#x, lasttry=%ld, now=%ld"
, ss_sess->ssse_id, ss_ctx->ssc_flags
, (long) ss_ctx->ssc_pm_lasttry, (long) st_time());
return ret;
}
/* SSC_FL_PM_TRYING acts as mutex, no race in statethreads */
SSC_SET_FLAG(ss_ctx, SSC_FL_PM_TRYING);
ret = sm_s2q_open(ss_ctx->ssc_s2m_ctx,
&ss_ctx->ssc_cnf.ss_cnf_miltsockspec,
ss_ctx->ssc_cnf.ss_cnf_wait4srv,
ss_ctx->ssc_cnf.ss_cnf_max_threads,
S2Q_T_PMILTER, 0);
ss_ctx->ssc_pm_lasttry = st_time();
/* do we care about errors? */
if (sm_is_err(ret)) {
sm_log_write(ss_ctx->ssc_lctx,
SS_LCAT_SERVER, SS_LMOD_SERVER,
SM_LOG_ERROR, 9,
"sev=ERROR, func=ss_conn2milt, ss_sess=%s, sm_s2q_open=%m"
, ss_sess->ssse_id, ret);
if (SSC_IS_MFLAG(ss_ctx, SSC_MFL_PM_421)) {
ret = SMTP_R_SSD;
SSC_SET_FLAG(ss_ctx, SSC_FL_PM_TRYCONN);
SSC_SET_STATE(ss_ctx, SSC_ST_SSD);
}
else if (SSC_IS_MFLAG(ss_ctx, SSC_MFL_PM_AGAIN))
SSC_SET_FLAG(ss_ctx, SSC_FL_PM_TRYCONN);
else
ret = SM_SUCCESS;
}
else {
SSC_SET_FLAG(ss_ctx, SSC_FL_PM_USE);
SSC_CLR_FLAG(ss_ctx, SSC_FL_PM_TRYCONN);
sm_log_write(ss_ctx->ssc_lctx,
SS_LCAT_SERVER, SS_LMOD_SERVER,
SM_LOG_DEBUG, 14,
"sev=DBG, func=ss_conn2milt, ss_sess=%s, status=reconnected"
, ss_sess->ssse_id);
}
SSC_CLR_FLAG(ss_ctx, SSC_FL_PM_TRYING);
return ret;
}
/*
** SS_INIT_MILT -- initialize pmilter connection
**
** Parameters:
** ss_ctx -- SMTP Server context
**
** Returns:
** usual return code
*/
static sm_ret_T
ss_init_milt(ss_ctx_P ss_ctx)
{
sm_ret_T ret;
char *which;
SM_IS_SS_CTX(ss_ctx);
ret = SM_SUCCESS;
if (0 == ss_ctx->ssc_cnf.ss_cnf_miltsockspec.sckspc_type
|| !SSC_IS_CFLAG(ss_ctx, SSC_CFL_PMILTER))
return ret;
/* initialize capabilities */
SSC_SET_PMCAP(ss_ctx, SM_SCAP_PM_ALL);
/* SSC_FL_PM_TRYING acts as mutex, no race in statethreads */
SSC_SET_FLAG(ss_ctx, SSC_FL_PM_TRYING);
ret = sm_s2q_create(&ss_ctx->ssc_s2m_ctx, ss_ctx,
ss_ctx->ssc_cnf.ss_cnf_max_threads);
if (sm_is_success(ret)) {
ret = sm_s2q_open(ss_ctx->ssc_s2m_ctx,
&ss_ctx->ssc_cnf.ss_cnf_miltsockspec,
ss_ctx->ssc_cnf.ss_cnf_wait4srv,
ss_ctx->ssc_cnf.ss_cnf_max_threads,
S2Q_T_PMILTER, 0);
which = "sm_s2q_open";
}
else
which = "sm_s2q_create";
ss_ctx->ssc_pm_lasttry = st_time();
/* do we care about errors? */
if (sm_is_err(ret)) {
sm_log_write(ss_ctx->ssc_lctx,
SS_LCAT_SERVER, SS_LMOD_SERVER,
SM_LOG_ERROR, 9,
"sev=ERROR, func=ss_init_milt, %s=%m"
, which, ret);
if (SSC_IS_MFLAG(ss_ctx, SSC_MFL_PM_421)) {
ret = SMTP_R_SSD;
SSC_SET_FLAG(ss_ctx, SSC_FL_PM_TRYCONN);
SSC_SET_STATE(ss_ctx, SSC_ST_SSD);
}
else if (SSC_IS_MFLAG(ss_ctx, SSC_MFL_PM_AGAIN))
SSC_SET_FLAG(ss_ctx, SSC_FL_PM_TRYCONN);
else
ret = SM_SUCCESS;
}
else
SSC_SET_FLAG(ss_ctx, SSC_FL_PM_USE);
if (sm_is_err(ret)) {
sm_log_write(ss_ctx->ssc_lctx,
SS_LCAT_SERVER, SS_LMOD_SERVER,
SM_LOG_ERROR, 2,
"sev=ERROR, func=ss_init_milt, %s=%m, where=pmilter"
, which, ret);
/* fall through to return error and release mutex */
}
SSC_CLR_FLAG(ss_ctx, SSC_FL_PM_TRYING);
return ret;
}
#endif /* MTA_USE_PMILTER */
#if MTA_USE_SASL
/*
** PROXY_POLICY -- define proxy policy for AUTH
**
** Parameters:
** conn -- unused.
** context -- unused.
** requested_user -- authorization identity.
** rlen -- authorization identity length.
** auth_identity -- authentication identity.
** alen -- authentication identity length.
** def_realm -- default user realm.
** urlen -- user realm length.
** propctx -- unused.
**
** Returns:
** ok?
*/
static int
sm_proxy_policy(sasl_conn_t *conn, void *context, const char *requested_user,
unsigned rlen, const char *auth_identity, unsigned alen,
const char *def_realm, unsigned urlen, struct propctx *propctx)
{
if (NULL == auth_identity)
return SASL_FAIL;
return SASL_OK;
}
static sasl_callback_t sm_sasl_srvcbs[] =
{
{ SASL_CB_PROXY_POLICY, &sm_proxy_policy, NULL },
{ SASL_CB_LIST_END, NULL, NULL }
};
#endif
/*
** SS_INITP -- third part of initialization
** Called after processes have been started, i.e., per-process
** initialization is performed here.
**
** Parameters:
** ss_ctx -- SMTP Server context
**
** Returns:
** usual return code
*/
static sm_ret_T
ss_initp(ss_ctx_P ss_ctx)
{
sm_ret_T ret;
SM_IS_SS_CTX(ss_ctx);
ret = sm_s2q_init_u(&ss_ctx->ssc_s2q_ctx, ss_ctx,
ss_ctx->ssc_cnf.ss_cnf_smtpssock_abs,
ss_ctx->ssc_cnf.ss_cnf_wait4srv,
ss_ctx->ssc_cnf.ss_cnf_max_threads,
ss_ctx->ssc_id,
ss_ctx->ssc_cnf.ss_cnf_max_threads,
S2Q_T_QMGR, 0);
if (sm_is_err(ret)) {
sm_log_write(ss_ctx->ssc_lctx,
SS_LCAT_SERVER, SS_LMOD_SERVER,
SM_LOG_ERROR, 2,
"sev=ERROR, func=ss_initp, id=%u, sm_s2q_init=%m, socket=%s"
, ss_ctx->ssc_id, ret, ss_ctx->ssc_cnf.ss_cnf_smtpssock_abs);
SSC_SET_FLAG(ss_ctx, SSC_FL_RESTARTDEP);
return ret;
}
ret = sm_s2q_init_u(&ss_ctx->ssc_s2a_ctx, ss_ctx,
ss_ctx->ssc_cnf.ss_cnf_smarsock_abs,
ss_ctx->ssc_cnf.ss_cnf_wait4srv,
ss_ctx->ssc_cnf.ss_cnf_max_threads,
ss_ctx->ssc_id,
ss_ctx->ssc_cnf.ss_cnf_max_threads,
S2Q_T_SMAR,
SSC_IS_CFLAG(ss_ctx, SSC_CFL_ACCESS_DB) ? SMARCL_FL_ACC : 0);
if (sm_is_err(ret)) {
sm_log_write(ss_ctx->ssc_lctx,
SS_LCAT_SERVER, SS_LMOD_SERVER,
SM_LOG_ERROR, 2,
"sev=ERROR, func=ss_initp, id=%u, sm_s2q_init=%m, socket=%s"
, ss_ctx->ssc_id, ret, ss_ctx->ssc_cnf.ss_cnf_smarsock);
SSC_SET_FLAG(ss_ctx, SSC_FL_RESTARTDEP);
return ret;
}
#if MTA_USE_PMILTER
ret = ss_init_milt(ss_ctx);
if (sm_is_err(ret)) {
sm_log_write(ss_ctx->ssc_lctx,
SS_LCAT_SERVER, SS_LMOD_SERVER,
SM_LOG_ERROR, 6,
"sev=ERROR, func=ss_initp, id=%u, ss_init_milt=%m, where=pmilter"
, ss_ctx->ssc_id, ret);
/* ignore error, system should try to reconnect later on */
ret = SM_SUCCESS;
}
#endif /* MTA_USE_PMILTER */
#if MTA_USE_TLS
/* XXX Is this per process or "global"? */
ret = sm_tls_init_library(&ss_ctx->ssc_tlsl_ctx);
if (sm_is_success(ret)) {
char confdir[PATH_MAX];
ret = sm_dirname(ss_ctx->ssc_cnf.ss_cnf_conffile, confdir,
sizeof(confdir));
if (sm_is_err(ret)) {
sm_log_write(ss_ctx->ssc_lctx,
SS_LCAT_INIT, SS_LMOD_CONFIG,
SM_LOG_ERR, 1,
"sev=ERROR, func=ss_initp, sm_dirname=%m", ret);
return ret;
}
TLS_GEN_PATHS(ss_ctx->ssc_cnf.ss_cnf_tls, ss_ctx->ssc_cnf, ss);
ret = sm_tls_init(ss_ctx->ssc_tlsl_ctx, &ss_ctx->ssc_ssl_ctx,
TLS_I_SRV, true, &ss_ctx->ssc_cnf.ss_cnf_tls);
if (sm_is_success(ret))
SSC_SET_FLAG(ss_ctx, SSC_FL_TLS_OK);
else {
sm_log_write(ss_ctx->ssc_lctx,
SS_LCAT_INIT, SS_LMOD_CONFIG,
SM_LOG_INFO, 9,
"sev=INFO, func=ss_initp, sm_tls_init=%m", ret);
}
/*
** HACK! it should check whether log is in config file
** however it's not clear how to do that...
** this only works because facility 0 is KERN.
*/
if (ss_ctx->ssc_cnf.ss_cnf_log.sm_logspc_facility != 0) {
(void) sm_set_tls_log(ss_ctx->ssc_tlsl_ctx, NULL,
INVALID_FD, ss_ctx->ssc_cnf.ss_cnf_loglevel);
}
}
ret = SM_SUCCESS; /* ignore errors for now; just disable TLS */
#endif /* MTA_USE_TLS */
#if MTA_USE_SASL
ret = sm_sasl_init(true, ss_ctx->ssc_cnf.ss_cnf_auth_flags,
sm_sasl_srvcbs, ss_ctx->ssc_lctx,
&ss_ctx->ssc_sasl_ctx);
if (sm_is_err(ret)) {
sm_log_write(ss_ctx->ssc_lctx,
SS_LCAT_SERVER, SS_LMOD_SERVER,
SM_LOG_ERROR, 2,
"sev=ERROR, func=ss_initp, sm_sasl_init=%m", ret);
}
else
SSC_SET_FLAG(ss_ctx, SSC_FL_SASL_OK);
#endif /* MTA_USE_SASL */
return ret;
#if MTA_USE_TLS
enomem:
return sm_error_temp(SM_EM_SMTPS, ENOMEM);
#endif
}
/*
** SS_ONE_SESSION -- perform one SMTP server session
**
** Parameters:
** ssfd -- file descriptor to use for I/O
** ss_ctx -- SMTP server context
**
** Returns:
** usual return code
*/
static int
ss_one_session(st_netfd_t ssfd, ss_ctx_P ss_ctx)
{
sm_file_T *fp;
struct sockaddr_in clt_addr;
long i;
int r;
sm_ret_T ret;
ss_sess_P ss_sess;
fp = NULL;
i = 0;
/* get a new session context */
ret = ss_sess_new(ss_ctx, &ss_sess);
if (sm_is_err(ret))
goto sess_error;
ret = sm_io_open(&SmStThrNetIO, (void *) &ssfd, SM_IO_RDWR,
&fp, SM_IO_WHAT_END);
if (ret != SM_SUCCESS) {
sm_log_write(ss_ctx->ssc_lctx,
SS_LCAT_SERVER, SS_LMOD_SERVER,
SM_LOG_ERR, 6,
"sev=ERROR, func=ss_one_session, pid=%d, sm_io_open()=%m"
, (int) My_pid, ret);
/* ??? */
st_netfd_close(ssfd);
return ret;
}
/* switch to non-blocking */
sm_io_clrblocking(fp);
r = ss_ctx->ssc_cnf.ss_cnf_timeout;
ret = sm_io_setinfo(fp, SM_IO_WHAT_TIMEOUT, &r);
if (ret != SM_SUCCESS) {
sm_log_write(ss_ctx->ssc_lctx,
SS_LCAT_SERVER, SS_LMOD_SERVER,
SM_LOG_ERROR, 5,
"sev=ERROR, func=ss_one_session, pid=%d, set_timeout()=%m"
, (int) My_pid, ret);
sm_io_close(fp, SM_IO_CF_NONE);
return ret;
}
ret = sm_io_setinfo(fp, SM_IO_DOUBLE, NULL);
if (ret != SM_SUCCESS) {
sm_log_write(ss_ctx->ssc_lctx,
SS_LCAT_SERVER, SS_LMOD_SERVER,
SM_LOG_ERROR, 5,
"sev=ERROR, func=ss_one_session, pid=%d, set_double=%m"
, (int) My_pid, ret);
sm_io_close(fp, SM_IO_CF_NONE);
return ret;
}
clt_addr.sin_family = AF_INET;
clt_addr.sin_port = htons(587);
clt_addr.sin_addr.s_addr = inet_addr("127.0.0.1");
ss_sess->ssse_fp = fp;
ss_sess->ssse_client = clt_addr.sin_addr;
ss_sess->ssse_state = SSSE_ST_CONNECTED;
ss_sess->ssse_idx = i;
ret = sm_s2q_nseid(ss_sess, ss_ctx->ssc_s2q_ctx, ss_sess->ssse_id);
if (sm_is_err(ret))
goto sess_error;
ret = sm_w4q2s_reply(ss_sess, TMO_W4Q2S, ss_ctx->ssc_s2q_ctx);
if (sm_is_err(ret)) {
sm_log_write(ss_ctx->ssc_lctx,
SS_LCAT_SERVER, SS_LMOD_SERVER,
SM_LOG_ERROR, 5,
"sev=ERROR, func=ss_one_session, ss_sess=%s, ss_one_session=after_sm_s2q_nseid, sm_w4q2s_reply=%m"
, ss_sess->ssse_id, ret);
goto sess_error;
}
/* ss_sess must be deallocated by ss_hdl_session() */
fp = NULL;
ret = ss_hdl_session(ss_sess, ret);
ss_sess = NULL;
sess_error:
if (fp != NULL)
sm_io_close(fp, SM_IO_CF_NONE);
ss_sess_free(ss_sess);
return ret;
}
/*
** SS_HANDLE_CONNECTIONS -- accept incoming connections, a thread handles it.
** This runs as a thread; it may create more threads or it may
** terminate itself.
**
** Parameters:
** arg -- index for ss_sck_ctx[] array
**
** Returns:
** NULL (should return an error code)
*/
static void *
ss_handle_connections(void *arg)
{
st_netfd_t srv_lfd, cli_fd;
sm_file_T *fp;
struct sockaddr_in clt_addr;
int save_errno, r;
long i;
sm_ret_T ret;
ss_sess_P ss_sess;
ss_ctx_P ss_ctx;
ss_sess = NULL;
i = (long) arg;
srv_lfd = ss_sck_ctx[i].sssc_lfd;
/* just for consistency... maybe later on this is passed in? */
ss_ctx = &Ss_ctx;
ret = ss_sess_new(ss_ctx, &ss_sess);
if (sm_is_err(ret))
return NULL; /* question: some error?? */
WAIT_THREADS(i)++;
while (WAIT_THREADS(i) <= ss_ctx->ssc_cnf.ss_cnf_max_wait_threads
&& !SSC_IS_FLAG(ss_ctx, SSC_FL_SHUTDOWN))
{
r = sizeof(clt_addr);
cli_fd = st_accept(srv_lfd, (struct sockaddr *)&clt_addr,
&r, (st_utime_t) -1);
if (NULL == cli_fd) {
int priority, level;
char *sev;
save_errno = errno;
if (EINTR == save_errno || ECONNABORTED == save_errno) {
level = 14;
priority = SM_LOG_INFO;
sev = "INFO";
}
else {
level = 2;
priority = SM_LOG_ERROR;
sev = "ERROR";
}
sm_log_write(ss_ctx->ssc_lctx,
SS_LCAT_SERVER, SS_LMOD_SERVER,
priority, level,
"sev=%s, func=ss_handle_connections, st_accept()=%m"
, sev, sm_err_temp(save_errno));
if (EINTR == save_errno || ECONNABORTED == save_errno)
continue;
else if (EMFILE == save_errno || ENFILE == save_errno ||
ENOMEM == save_errno)
{
sleep(1);
continue;
}
break;
}
ss_sess->ssse_connect = st_time();
#if 0
sm_log_write(ss_ctx->ssc_lctx,
SS_LCAT_SERVER, SS_LMOD_SERVER,
SM_LOG_DEBUG, 13,
"sev=DBG, func=ss_handle_connections, pid=%d, idx=%d, accept=%d"
, (int) My_pid, ss_ctx->ssc_id, cli_fd);
#endif /* 0 */
/* Save peer address, so we can retrieve it later */
st_netfd_setspecific(cli_fd, &clt_addr.sin_addr, NULL);
ret = sm_io_open(&SmStThrNetIO, (void *) &cli_fd, SM_IO_RDWR,
&fp, SM_IO_WHAT_END);
if (ret != SM_SUCCESS) {
sm_log_write(ss_ctx->ssc_lctx,
SS_LCAT_SERVER, SS_LMOD_SERVER,
SM_LOG_ERROR, 2,
"sev=ERROR, func=ss_handle_connections, pid=%d, sm_io_open()=%m"
, (int) My_pid, ret);
/* ??? */
st_netfd_close(cli_fd);
continue;
}
/* switch to non-blocking */
sm_io_clrblocking(fp);
r = ss_ctx->ssc_cnf.ss_cnf_timeout;
ret = sm_io_setinfo(fp, SM_IO_WHAT_TIMEOUT, &r);
if (ret != SM_SUCCESS) {
sm_log_write(ss_ctx->ssc_lctx,
SS_LCAT_SERVER, SS_LMOD_SERVER,
SM_LOG_ERROR, 5,
"sev=ERROR, func=ss_handle_connections, pid=%d, set_timeout()=%m"
, (int) My_pid, ret);
sm_io_close(fp, SM_IO_CF_NONE);
continue;
}
ret = sm_io_setinfo(fp, SM_IO_DOUBLE, NULL);
if (ret != SM_SUCCESS) {
sm_log_write(ss_ctx->ssc_lctx,
SS_LCAT_SERVER, SS_LMOD_SERVER,
SM_LOG_ERROR, 5,
"sev=ERROR, func=ss_handle_connections, pid=%d, set_double=%m"
, (int) My_pid, ret);
sm_io_close(fp, SM_IO_CF_NONE);
continue;
}
SM_ASSERT(WAIT_THREADS(i) > 0);
WAIT_THREADS(i)--;
BUSY_THREADS(i)++;
if (BUSY_THREADS(i) > MAXB_THREADS(i))
MAXB_THREADS(i) = BUSY_THREADS(i);
if (WAIT_THREADS(i) < ss_ctx->ssc_cnf.ss_cnf_min_wait_threads &&
TOTAL_THREADS(i) < Max_cur_threads)
{
/* Create another spare thread */
if (st_thread_create(ss_handle_connections, (void *)i, 0, 0) == NULL)
sm_log_write(ss_ctx->ssc_lctx,
SS_LCAT_SERVER, SS_LMOD_SERVER,
SM_LOG_ERROR, 2,
"sev=ERROR, func=ss_handle_connections, pid=%d, st_thread_create=%m"
, (int) My_pid, sm_err_temp(errno));
}
ss_sess->ssse_fp = fp;
ss_sess->ssse_client = clt_addr.sin_addr;
ss_sess->ssse_state = SSSE_ST_CONNECTED;
ss_sess->ssse_idx = i;
sm_str_clr(ss_sess->ssse_wr);
#if SS_TEST
if (Test_client_ip.s_addr != INADDR_ANY)
ss_sess->ssse_client = Test_client_ip;
#endif
/* SM_ASSERT(SM_SUCCESS == ret); */
if (0 == Max_cur_threads) {
if (!SSC_IS_FLAG(ss_ctx, SSC_FL_SHUTDOWN))
sm_log_write(ss_ctx->ssc_lctx,
SS_LCAT_SERVER, SS_LMOD_SERVER,
SM_LOG_WARN, 2,
"sev=WARN, func=ss_handle_connections, pid=%d, Max_cur_threads=0"
, (int) My_pid);
SSSE_SET_FLAG(ss_sess, SSSE_FL_LOCAL_SSD);
ret = SMTP_R_SSD;
}
#if MTA_USE_PMILTER
/* check system state */
if (SSC_ST_SSD == ss_ctx->ssc_state) {
/* similar for TEMP? */
SSSE_SET_FLAG(ss_sess, SSSE_FL_LOCAL_SSD);
ret = SMTP_R_SSD;
}
#endif /* MTA_USE_PMILTER */
/* SM_ASSERT(SM_SUCCESS == ret || SMTP_R_SSD == ret); */
/* contact access db first? */
if (SM_SUCCESS == ret && SSC_IS_CFLAG(ss_ctx, SSC_CFL_ACCESS_DB)) {
uint32_t ltype;
ltype = SMARA_LT_CLT_A_ACC
|SMARA_LT_RVRS_N_ACC /* always?? */
#if MTA_USE_TLS
|(SSC_IS_CFLAG(ss_ctx, SSC_CFL_SE_CONF)
? SMARA_LT_SS_SE_CONF : 0)
#endif
;
sm_str_clr(ss_sess->ssse_str);
sm_inet_inaddr2str(ss_sess->ssse_client, ss_sess->ssse_str);
ret = sm_s2a_clt(ss_sess, ss_ctx->ssc_s2a_ctx,
ss_sess->ssse_id, ss_sess->ssse_str,
RT_S2A_CLT_A, ltype,
SMARA_LFL_IPV4|SMARA_LFL_SUB
|SMARA_LFL_RVRS4|SMARA_LFL_RVACC|SMARA_LFL_DNSBL);
if (sm_is_err(ret)) {
sm_log_write(ss_ctx->ssc_lctx,
SS_LCAT_SERVER, SS_LMOD_SERVER,
SM_LOG_ERROR, 5,
"sev=ERROR, func=ss_handle_connections, ss_sess=%s, sm_s2a_clt=%m"
, ss_sess->ssse_id, ret);
goto sess_error;
}
if (!sm_is_err(ret)) {
ret = sm_w4q2s_reply(ss_sess, ss_ctx->ssc_cnf.ss_cnf_w4a2s,
ss_ctx->ssc_s2a_ctx);
if (sm_is_err(ret)) {
sm_log_write(ss_ctx->ssc_lctx,
SS_LCAT_SERVER, SS_LMOD_SERVER,
SM_LOG_ERROR, 5,
"sev=ERROR, func=ss_handle_connections, ss_sess=%s, client_ipv4=%A, sm_w4q2s_reply=%m"
, ss_sess->ssse_id, ss_sess->ssse_client.s_addr, ret);
}
}
sm_log_write(ss_ctx->ssc_lctx,
SS_LCAT_SERVER, SS_LMOD_SERVER,
SM_LOG_INFO, 13,
"sev=INFO, func=ss_handle_connections, ss_sess=%s, where=client_access, stat=%m"
, ss_sess->ssse_id, ret);
if (sm_is_err(ret)) {
SSSE_SET_FLAG(ss_sess, SSSE_FL_LOCAL_SSD);
ret = SMTP_R_SSD;
}
else if (SMAR_RISQUICK(ret)) {
SSSE_SET_FLAG(ss_sess, SSSE_FL_QUICK);
SMAR_RCLRQUICK(ss_sess->ssse_acc.ssa_reply_code);
SMAR_RCLRQUICK(ret);
}
if (SMTP_R_RELAY == ret) {
SSSE_SET_FLAG(ss_sess, SSSE_FL_CLIENT_RELAY);
ret = SM_SUCCESS; /* "normalize" */
}
else if (SMTP_R_DISCARD == ret) {
SSSE_SET_FLAG(ss_sess, SSSE_FL_DISCARD);
ret = SM_SUCCESS; /* "normalize" */
}
else if (SMTP_R_OK == ret) {
SSSE_SET_FLAG(ss_sess, SSSE_FL_CLIENT_OK);
ret = SM_SUCCESS; /* "normalize" */
}
else if (SMTP_R_CONT == ret)
ret = SM_SUCCESS; /* "normalize" */
#if MTA_USE_TLS
if (sm_is_success(ss_sess->ssse_maprescnf) &&
sm_str_getlen(ss_sess->ssse_str) > 0)
{
sm_ret_T r;
r = ss_sess_conf(ss_sess);
sm_log_write(ss_ctx->ssc_lctx,
SS_LCAT_SERVER, SS_LMOD_SERVER,
SM_LOG_DEBUG, 12,
"sev=DBG, func=ss_handle_connections, ss_sess=%s, session_conf=%#T, ss_sess_conf=%r"
, ss_sess->ssse_id, ss_sess->ssse_str, r);
}
else if (SM_IS_TEMP_ERR(ss_sess->ssse_maprescnf)) {
sm_log_write(ss_ctx->ssc_lctx,
SS_LCAT_SERVER, SS_LMOD_SERVER,
SM_LOG_INFO, 9,
"sev=INFO, func=ss_handle_connections, ss_sess=%s, ss_sess_conf_lookup=%r"
, ss_sess->ssse_id, ss_sess->ssse_maprescnf);
SSSE_SET_FLAG(ss_sess, SSSE_FL_LOCAL_SSD);
ret = SMTP_R_SSD;
}
#endif /* MTA_USE_TLS */
/* reject accept session/transaction? */
if (IS_SMTP_REPLY(ret) && SMTP_IS_REPLY_ERROR(ret)) {
sm_log_write(ss_ctx->ssc_lctx,
SS_LCAT_SERVER, SS_LMOD_SERVER,
SM_LOG_DEBUG, 12,
"sev=DBG, func=ss_handle_connections, ss_sess=%s, where=smar, sm_w4q2s_reply=%m, reply=%r, text=%#T"
, ss_sess->ssse_id, ret
, ss_sess->ssse_acc.ssa_reply_code
, ss_sess->ssse_wr);
/* copy error text (if valid) */
if (sm_str_getlen(ss_sess->ssse_wr) > 4 &&
sm_str_cpy(ss_sess->ssse_acc.ssa_reply_text,
ss_sess->ssse_wr) == SM_SUCCESS)
{
/* delay rejection? */
if (SSC_IS_CFLAG(ss_ctx, SSC_CFL_DELAY_CHKS) &&
!SSSE_IS_FLAG(ss_sess, SSSE_FL_QUICK))
{
sm_str_clr(ss_sess->ssse_wr);
ret = SMTP_R_OK;
}
}
else {
/*
** Can't accept session; complain??
** Decrease concurrency?
*/
ret = SMTP_R_SSD;
sm_str_clr(ss_sess->ssse_wr);
sm_str_clr(ss_sess->ssse_acc.ssa_reply_text);
}
}
}
#if MTA_USE_PMILTER
/*
** XXX COPY of smar/access check from above
** 1. should these two have the same functionality?
** 2. if so, put it into a function that can be called...
** 3. can milter override access checks? how do these two interact?
*/
/* contact milter next? */
if (SM_SUCCESS == ret
&& SSC_IS_CFLAG(ss_ctx, SSC_CFL_PMILTER)
&& SSC_IS_FLAG(ss_ctx, SSC_FL_PM_TRYCONN))
{
/* try to reconnect to milter */
ret = ss_conn2milt(ss_ctx, ss_sess);
}
/* should milter be used for this session? */
if (SM_SUCCESS == ret && SSC_IS_FLAG(ss_ctx, SSC_FL_PM_USE))
{
SSSE_SET_FLAG(ss_sess, SSSE_FL_PM_USE);
ss_sess->ssse_s2q_id[SS_COMM_PMILTER] =
ss_ctx->ssc_s2m_ctx->s2q_q_id;
}
/*
** Always call libpmilter for a new session even if
** SM_SCAP_PM_CNNCT is not set, otherwise libpmilter
** doesn't set up a its session context etc.
** This could be solved by making libpmilter more complex,
** i.e., on each call try to figure out whether the
** session context has been already established, but that's
** not worth the hassle right now.
**
** Move this into pmilter.c?
*/
if (SSSE_IS_FLAG(ss_sess, SSSE_FL_PM_USE)) {
ret = sm_s2m_clt(ss_sess,
ss_ctx->ssc_s2m_ctx,
ss_sess->ssse_id,
(uint32_t) ss_sess->ssse_client.s_addr,
(uint32_t) clt_addr.sin_port);
if (sm_is_err(ret)) {
sm_log_write(ss_ctx->ssc_lctx,
SS_LCAT_SERVER, SS_LMOD_SERVER,
SM_LOG_ERROR, 5,
"sev=ERROR, func=ss_handle_connections, ss_sess=%s, sm_s2m_clt=%m"
, ss_sess->ssse_id, ret);
/* error is handled below */
}
else {
ret = sm_w4q2s_reply(ss_sess, ss_ctx->ssc_cnf.ss_cnf_w4m2s,
ss_ctx->ssc_s2m_ctx);
SSSE_SET_FLAG(ss_sess, SSSE_FL_PM_CALLED);
}
sm_log_write(ss_ctx->ssc_lctx,
SS_LCAT_SERVER, SS_LMOD_SERVER,
SM_LOG_INFO, 13,
"sev=INFO, func=ss_handle_connections, ss_sess=%s, where=client_pmilter, stat=%m"
, ss_sess->ssse_id, ret);
if (sm_is_err(ret)) {
SSPM_TRY_AGAIN(ss_ctx, ss_sess);
/* don't use milter anymore in this session */
SSSE_CLR_FLAG(ss_sess, SSSE_FL_PM_USE);
if (SSC_IS_MFLAG(ss_ctx, SSC_MFL_PM_421)) {
SSSE_SET_FLAG(ss_sess, SSSE_FL_LOCAL_SSD);
ret = SMTP_R_SSD;
}
else /* ignore error */
ret = SM_SUCCESS;
}
else {
if (SMAR_RISQUICK(ret)) {
/*
** Don't use milter anymore in this
** session (no matter whether it was
** due to OK, REJECT, RELAY, etc)
** Note: QUICK:OK still need a relay
** check at RCPT!
*/
SSSE_CLR_FLAG(ss_sess, SSSE_FL_PM_USE);
SSSE_SET_FLAG(ss_sess, SSSE_FL_QUICK);
SMAR_RCLRQUICK(ret);
}
}
if (SMTP_R_RELAY == ret) {
SSSE_SET_FLAG(ss_sess, SSSE_FL_CLIENT_RELAY);
ret = SM_SUCCESS; /* "normalize" */
}
/* reject accept session/transaction? */
if (IS_SMTP_REPLY(ret) && SMTP_IS_REPLY_ERROR(ret)) {
int rc;
sm_log_write(ss_ctx->ssc_lctx,
SS_LCAT_SERVER, SS_LMOD_SERVER,
SM_LOG_DEBUG, 12,
"sev=DBG, func=ss_handle_connections, ss_sess=%s, where=pmilter, ret=%r, text=%@T"
, ss_sess->ssse_id, ret
, ss_sess->ssse_wr);
/* ignore bogus text */
if (!SMTP_REPLY_MATCHES_RCODE(ss_sess->ssse_wr, ret, 0, rc)) {
(void) ss_crt_reply(ss_sess->ssse_wr,
ret, SS_PHASE_OTHER, true);
}
/* copy error text (if valid) */
if (sm_str_getlen(ss_sess->ssse_wr) > 4 &&
sm_str_cpy(ss_sess->ssse_acc.ssa_reply_text,
ss_sess->ssse_wr) == SM_SUCCESS)
{
/* delay rejection? */
if (SSC_IS_CFLAG(ss_ctx, SSC_CFL_DELAY_CHKS)) {
sm_str_clr(ss_sess->ssse_wr);
ret = SMTP_R_OK;
}
}
else {
/*
** Can't accept session; complain?
** Decrease concurrency?
*/
ret = SMTP_R_SSD;
sm_str_clr(ss_sess->ssse_wr);
sm_str_clr(ss_sess->ssse_acc.ssa_reply_text);
}
}
}
#endif /* MTA_USE_PMILTER */
/* ret == {SM_SUCCESS,SMTP_R_SSD} || IS_SMTP_REPLY(ret) */
/* tell QMGR about it? necessary for further access control */
if (ret != SMTP_R_SSD) {
/* reject accept session/transaction? */
if (IS_SMTP_REPLY(ret) && SMTP_IS_REPLY_ERROR(ret))
SSSE_SET_FLAG(ss_sess, SSSE_FL_NULL);
ret = sm_s2q_nseid(ss_sess, ss_ctx->ssc_s2q_ctx, ss_sess->ssse_id);
if (sm_is_err(ret))
goto sess_error;
ret = sm_w4q2s_reply(ss_sess, TMO_W4Q2S, ss_ctx->ssc_s2q_ctx);
if (sm_is_err(ret)) {
sm_log_write(ss_ctx->ssc_lctx,
SS_LCAT_SERVER, SS_LMOD_SERVER,
SM_LOG_ERROR, 6,
"sev=ERROR, func=ss_handle_connections, ss_sess=%s, client_ipv4=%A, where=after_sm_s2q_nseid, sm_w4q2s_reply=%m"
, ss_sess->ssse_id, ss_sess->ssse_client.s_addr, ret);
/* inform the client */
ret = SMTP_R_SSD;
}
/*
** qmgr doesn't need to be informed if it rejected
** the connection. other errors?
*/
/* HACK: clear quick for now without saving it! */
SMAR_RCLRQUICK(ret);
if (ret != SMTP_R_SSD)
SSSE_SET_FLAG(ss_sess, SSSE_FL_CSEID);
#if 0
/*
** XXX Same as above?
** Where should logging happen? -> ss_hdl_session()
** Note: a rejection from QMGR should NOT be delayed
** because QMGR didn't accept the session. Hence this
** must be treated as "QUICK" or some flag must be
** set not to bother QMGR with further informations
** about this sessions.
** PS: can QMGR return anything else but SMTP_R_SSD?
*/
else if (IS_SMTP_REPLY(ret) && SMTP_IS_REPLY_ERROR(ret)) {
sm_log_write(ss_ctx->ssc_lctx,
SS_LCAT_SERVER, SS_LMOD_SERVER,
SM_LOG_DEBUG, 12,
"sev=DBG, func=ss_handle_connections, ss_sess=%s, qmgr_reply=%r, text=%#T"
, ss_sess->ssse_id, ret
, ss_sess->ssse_wr);
/* copy error text (if valid) */
if (sm_str_getlen(ss_sess->ssse_wr) > 4 &&
sm_str_cpy(ss_sess->ssse_acc.ssa_reply_text,
ss_sess->ssse_wr) == SM_SUCCESS)
{
/* delay rejection? */
if (SSC_IS_CFLAG(ss_ctx, SSC_CFL_DELAY_CHKS)) {
sm_str_clr(ss_sess->ssse_wr);
ret = SMTP_R_OK;
}
}
else {
/*
** Can't accept session: ENOMEM
** complain?
** Decrease concurrency?
*/
ret = SMTP_R_SSD;
sm_str_clr(ss_sess->ssse_wr);
sm_str_clr(ss_sess->ssse_acc.ssa_reply_text);
}
}
#endif /* 0 */
/* check again... could be set in the meantime */
if (0 == Max_cur_threads) {
if (!SSC_IS_FLAG(ss_ctx, SSC_FL_SHUTDOWN))
sm_log_write(ss_ctx->ssc_lctx,
SS_LCAT_SERVER, SS_LMOD_SERVER,
SM_LOG_ERROR, 2,
"sev=ERROR, func=ss_handle_connections, pid=%d, Max_cur_threads=0, ss_sess=%s"
, (int) My_pid, ss_sess->ssse_id);
ret = SMTP_R_SSD;
SSSE_SET_FLAG(ss_sess, SSSE_FL_CSEID);
}
}
/* ss_sess must be deallocated by ss_hdl_session() */
ret = ss_hdl_session(ss_sess, ret);
ss_sess = NULL;
#if 0
/*
** How much do we care? What kind of errors should
** stop the server?
*/
if (sm_is_err(ret))
goto sess_error;
#endif /* 0 */
if (SSC_IS_FLAG(ss_ctx, SSC_FL_SHUTDOWN))
goto sess_error;
/* get a new session context */
ret = ss_sess_new(&Ss_ctx, &ss_sess);
sess_error:
if (ss_sess != NULL && ss_sess->ssse_fp != NULL) {
sm_io_close(ss_sess->ssse_fp, SM_IO_CF_NONE);
ss_sess->ssse_fp = NULL;
}
if (sm_is_err(ret)) {
/*
** On what kind of errors do we want to terminate?
** Basically only on fatal errors. On temporary
** errors (including ENOMEM) at least one thread
** has to stay alive (for some time).
*/
if (SM_E_FULL == sm_error_value(ret)
|| SM_E_CONN_CLSD == sm_error_value(ret)
|| S2s_IS_IOERR(ss_ctx))
{
Max_cur_threads = 0; /* force shutdown */
SSC_SET_FLAG(ss_ctx, SSC_FL_TERMINATING|SSC_FL_SHUTDOWN);
if (0 == ss_ctx->ssc_shutdown)
ss_ctx->ssc_shutdown = st_time();
sm_log_write(ss_ctx->ssc_lctx,
SS_LCAT_SERVER, SS_LMOD_SERVER,
SM_LOG_ERROR, 1,
"sev=ERROR, func=ss_handle_connections, pid=%d, status=forcing_shutdown"
, (int) My_pid);
}
ss_sess_free(ss_sess);
return NULL;
}
WAIT_THREADS(i)++;
SM_ASSERT(BUSY_THREADS(i) > 0);
BUSY_THREADS(i)--;
if (WAIT_THREADS(i) > Max_cur_threads + 1)
break;
}
ss_sess_free(ss_sess);
SM_ASSERT(WAIT_THREADS(i) > 0);
WAIT_THREADS(i)--;
if (SSC_IS_FLAG(ss_ctx, SSC_FL_SHUTDOWN)) {
char sig;
sig = SM_SIG_SHUT;
(void) st_write(Sig_pipe[SM_WR_SIG_PIPE], &sig, sizeof(char),
(st_utime_t)10);
}
return NULL;
}
/*
** SS_DUMP_INFO -- print status information to smioerr
**
** Parameters:
** ss_ctx -- SMTP server context
**
** Returns:
** nothing
*/
static void
ss_dump_info(ss_ctx_P ss_ctx)
{
int i, len, s, l;
ssize_t b;
sm_str_P str;
struct rusage rusage;
s = (Sk_count * 512) + 1024;
str = sm_str_new(NULL, s, s + 1024);
if (NULL == str) {
sm_log_write(ss_ctx->ssc_lctx,
SS_LCAT_SERVER, SS_LMOD_SERVER,
SM_LOG_ERROR, 1,
"sev=ERROR, func=ss_dump_info, bytes=%d, malloc=failed, error=%m"
, s, sm_err_temp(errno));
return;
}
len = sm_strprintf(str, "Process pid=%d:\n", (int) My_pid);
for (i = 0; i < Sk_count; i++) {
l = sm_strprintf(str,
"Listening Socket #%d:\n"
"-------------------------\n"
"Address= %s:%d\n"
"Thread limits (min/cur/max)=%u/%u/%u\n"
"Waiting threads= %d\n"
"Busy threads= %d\n"
"Max busy threads= %d\n"
"Requests served= %lu\n"
"Transactions= %lu\n"
, i, ss_sck_ctx[i].sssc_addr, ss_sck_ctx[i].sssc_port
, Ss_ctx.ssc_cnf.ss_cnf_max_wait_threads
, Max_cur_threads
, Ss_ctx.ssc_cnf.ss_cnf_max_threads
, WAIT_THREADS(i), BUSY_THREADS(i), MAXB_THREADS(i)
, RQST_COUNT(i)
, TA_COUNT(i));
}
i = getrusage(RUSAGE_SELF, &rusage);
if (0 == i) {
l = sm_strprintf(str,
"ru_utime= %7ld.%07ld\n"
"ru_stime= %7ld.%07ld\n"
"ru_maxrss= %7ld\n"
"ru_ixrss= %7ld\n"
"ru_idrss= %7ld\n"
"ru_isrss= %7ld\n"
"ru_minflt= %7ld\n"
"ru_majflt= %7ld\n"
"ru_nswap= %7ld\n"
"ru_inblock= %7ld\n"
"ru_oublock= %7ld\n"
"ru_msgsnd= %7ld\n"
"ru_msgrcv= %7ld\n"
"ru_nsignals=%7ld\n"
"ru_nvcsw= %7ld\n"
"ru_nivcsw= %7ld\n"
, rusage.ru_utime.tv_sec
, rusage.ru_utime.tv_usec
, rusage.ru_stime.tv_sec
, rusage.ru_stime.tv_usec
, rusage.ru_maxrss
, rusage.ru_ixrss
, rusage.ru_idrss
, rusage.ru_isrss
, rusage.ru_minflt
, rusage.ru_majflt
, rusage.ru_nswap
, rusage.ru_inblock
, rusage.ru_oublock
, rusage.ru_msgsnd
, rusage.ru_msgrcv
, rusage.ru_nsignals
, rusage.ru_nvcsw
, rusage.ru_nivcsw
);
}
(void) sm_prtrlimits(str);
sm_io_write(smioerr, sm_str_getdata(str), sm_str_getlen(str), &b);
SM_STR_FREE(str);
}
#if SSQ_DEBUG
void
dump_thrd_info(void)
{
sm_io_fprintf(smioerr, "threads: wait=%d, busy=%d\n"
, WAIT_THREADS(0), BUSY_THREADS(0));
}
#endif /* SSQ_DEBUG */
#if 0
/*
** SS_LOAD_CONFIGS -- Configuration loading function stub. NOT IMPLEMENTED.
**
** Parameters:
** ss_ctx -- SMTP server context
**
** Returns:
** nothing.
*/
static void
ss_load_configs(ss_ctx_P ss_ctx)
{
sm_log_write(ss_ctx->ssc_lctx,
SS_LCAT_SERVER, SS_LMOD_SERVER,
SM_LOG_INFO, 12,
"sev=INFO, func=ss_load_configs, pid=%d, status=configuration_reload_not_yet_implemented"
, (int) My_pid);
}
#endif /* 0 */
syntax highlighted by Code2HTML, v. 0.9.1