/*
* SMTP sink based on server.c
*
* $Id: smtps2.c,v 1.110 2007/08/23 23:25:44 ca Exp $
*/
/*
* Copyright (C) 2000 Silicon Graphics, Inc. All Rights Reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
*
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
* 3. Neither the name of Silicon Graphics, Inc. nor the names of its
* contributors may be used to endorse or promote products derived from
* this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
* ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
* HOLDERS AND CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED
* TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
* PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <time.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <sys/socket.h>
#include <sys/wait.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <netdb.h>
#include <fcntl.h>
#include <signal.h>
#include <pwd.h>
#include "sm/sysexits.h"
#include "sm/io.h"
#define MTA_USE_STATETHREADS 1
#include "sm/cmsg.h"
#include "sm/stsock.h"
#include "sm/bitstring.h"
#include "sm/str.h"
#include "sm/queue.h"
#include "sm/net.h"
#include "st.h"
#include "data.h"
#include "sm/resource.h"
#include "sm/mta.h"
#include "sm/smreplycodes.h"
#if SMTPS2_CDB
#include "sm/cdb.h"
#endif
#include <ctype.h>
#if !HAVE_SNPRINTF
# define snprintf sm_snprintf
#endif
#if SM_HEAP_CHECK
extern SM_DEBUG_T SmHeapCheck;
# define HEAP_CHECK (SmHeapCheck > 0)
#else /* SM_HEAP_CHECK */
# define HEAP_CHECK false
#endif /* SM_HEAP_CHECK */
/******************************************************************
* Server configuration parameters
*/
/* Log files */
#define PID_FILE "pid"
#define ERRORS_FILE "errors"
#define ACCESS_FILE "access"
/* Default server port */
#define SERV_PORT_DEFAULT 8000
/* Socket listen queue size */
#define LISTENQ_SIZE_DEFAULT 256
/* Max number of listening sockets ("hardware virtual servers") */
#define MAX_BIND_ADDRS 16
/* Max number of "spare" threads per process per socket */
#define MAX_WAIT_THREADS_DEFAULT 8
/* Number of file descriptors needed to handle one client session */
#define FD_PER_THREAD 1
/* Access log buffer flushing interval (in seconds) */
#define ACCLOG_FLUSH_INTERVAL 30
/* Request read timeout (in seconds) */
#define REQUEST_TIMEOUT 30
#define SM_DROP_IT 'D'
/*
* multiline replies are generated if the first code is an
* SMTP reply code + MULTILINE_SMTP_OFF
*/
#define MULTILINE_SMTP_OFF 4
#define IS_MULTILINE_SMTP_CODE(buf, off) \
(((buf[off] == '6') || (buf[off] == '8') || (buf[off] == '9')) \
&& (buf[(off)+1] >= '0') && (buf[(off)+1] <= '9') \
&& (buf[(off)+2] >= '0') && (buf[(off)+2] <= '9'))
/******************************************************************
* Global data
*/
struct socket_info
{
st_netfd_t nfd; /* Listening socket */
char *addr; /* Bind address */
int port; /* Port */
int wait_threads; /* # of threads waiting to accept */
int busy_threads; /* # of threads processing request */
int maxb_threads; /* max # of threads */
int concurrent;
unsigned int rqst_count; /* Total # of processed requests */
unsigned int mail_count; /* Total # of processed transactions */
unsigned int rcpt_count; /* Total # of recipients */
unsigned int rcpt_cnt_ok; /* Total # of accepted recipients */
unsigned int ta_cnt_ok; /* Total # of accepted transactions */
unsigned int ta_cnt; /* Total # of transactions */
unsigned int ta_cnt_each; /* for -e option */
unsigned int total_rate;
int lmtp; /* use LMTP */
sm_file_T *da_fp; /* fp for data */
} srv_socket[MAX_BIND_ADDRS]; /* Array of listening sockets */
static int sk_count = 0; /* Number of listening sockets */
static int vp_count = 0; /* Number of server processes (VPs) */
static pid_t *vp_pids; /* Array of VP pids */
static int my_index = -1; /* Current process index */
static pid_t my_pid = -1; /* Current process pid */
static long nrejmsgs = 0; /* Counter of messages to reject */
static int rejmsgs = 0; /* whether to reject messages */
static long randrej = 0; /* randomly reject messages */
static time_t oktime = 0;
static time_t firstmail = 0;
static time_t lastmail = 0;
static time_t firstNmail = 0;
static unsigned int eachN = 0;
static char *fullehlo = NULL;
static char greeting[] = "220";
static char ehloresp[] = "250";
static char starttls[] = "xxx";
static char rsetresp[] = "250";
#define FINALDOT "250 ok final dot"
static char finaldot[] = FINALDOT;
#define EXPLICIT_FINALDOT (strcmp(finaldot, FINALDOT) != 0)
static char dataresponse[] = "354";
static char *Myname = "local.host";
static st_netfd_t sig_pipe[2]; /* Signal pipe */
/*
* Configuration flags/parameters
*/
static bool interactive_mode = true;
static bool serialize_accept = false;
static bool log_access = false;
static bool pipelining = false;
static bool offer_drr = false;
static bool use_drr = false;
static bool offer_rsad = false;
static bool use_rsad = false;
static bool dsn = false;
static bool onercpt = false; /* accept only one rcpt per TA */
static bool drop421 = true; /* close connection when returning 421 */
static bool Stoponerror = false;
static bool Hugegreeting = false;
static bool UseSMTP = false;
static bool idisplay = false; /* interactive display */
static int tempok = 0; /* cause a temp fail first, then accept */
static int permok = 0; /* cause a permanent fail first, then accept */
static int tempperm = 0; /* cause a temp fail first, then a perm fail */
static bool wastemp = false;
static bool wasperm = false;
static char temp2perm[3] = "00"; /* cause a temp fail first, then a perm fail */
static bool was2temp = false; /* but in two different stages */
static char temptemp[3] = "00"; /* causes two temp fails */
static bool wastt = false;
static char *logdir = ".";
static char *username = NULL;
static char *fd_socket = NULL;
static int listenq_size = LISTENQ_SIZE_DEFAULT;
static int errfd = STDERR_FILENO;
static int debug = 0;
static int sequence = -1;
static int seqleft = -1;
static unsigned int tasexpected = 0;
static bitstr_t *gotit = NULL;
#if SMTPS2_CDB
static int Maxcdbs = 0;
static int Cdbfiles = 0;
static id_count_T Rm_id_count = 0;
static bool Usecdb = false;
static bool Unsafe = false;
static bool Flat = false;
static cdb_ctx_P Cdb_ctx = NULL;
static char *Cdb_base = NULL;
typedef char *ss_id_T;
#include "../../smtps/id.c"
#endif /* SMTPS2_CDB */
static bool writedata = false;
static size_t total_size = 0;
static unsigned long count = 0;
static int iotimeout = REQUEST_TIMEOUT;
typedef struct ss_rcpt_S ss_rcpt_T, *ss_rcpt_P;
typedef struct ss_rcpts_S ss_rcpts_T, *ss_rcpts_P;
struct ss_rcpt_S
{
char *ssr_reply;
SIMPLEQ_ENTRY(ss_rcpt_S) ssr_link;
};
SIMPLEQ_HEAD(ss_rcpts_S, ss_rcpt_S);
/* operations on rcpt lists */
#define SS_RCPTS_INIT(ss_rcpts_hd) SIMPLEQ_INIT(ss_rcpts_hd)
#define SS_RCPTS_EMPTY(ss_rcpts_hd) SIMPLEQ_EMPTY(ss_rcpts_hd)
#define SS_RCPTS_FIRST(ss_rcpts_hd) SIMPLEQ_FIRST(ss_rcpts_hd)
#define SS_RCPTS_END(ss_rcpts_hd) SIMPLEQ_END(ss_rcpts_hd)
#define SS_RCPTS_NEXT(rcpt) SIMPLEQ_NEXT(rcpt, ssr_link)
#define SS_RCPTS_INSERT_TAIL(ss_rcpts_hd, rcpt) SIMPLEQ_INSERT_TAIL(ss_rcpts_hd, rcpt, ssr_link)
#define SS_RCPTS_REMOVE_HEAD(ss_rcpts_hd) SIMPLEQ_REMOVE_HEAD(ss_rcpts_hd, ssr_link)
/*
* Thread throttling parameters (all numbers are per listening socket).
* Zero values mean use default.
*/
static int max_threads = 0; /* Max number of threads */
static int max_wait_threads = 0; /* Max number of "spare" threads */
static int min_wait_threads = 2; /* Min number of "spare" threads */
static int datacomp = 0; /* perform some computation */
static int datawait = 0; /* sleep... */
static int delayreply = 0; /* how long to delay replies */
static int msgwait = 0; /* sleep... */
static unsigned int delaydotreply = 0; /* how long to delay reply to final dot */
static unsigned int delaydotrand = 0; /* random delay */
static st_utime_t delaymsg = 0; /* how long to delay read in msg */
static unsigned long max_msg_size = 0; /* SIZE */
/******************************************************************
* Useful macros
*/
#ifndef INADDR_NONE
#define INADDR_NONE 0xffffffff
#endif
#define SEC2USEC(s) ((s)*1000000LL)
#define WAIT_THREADS(i) (srv_socket[i].wait_threads)
#define BUSY_THREADS(i) (srv_socket[i].busy_threads)
#define MAXB_THREADS(i) (srv_socket[i].maxb_threads)
#define CONC_THREADS(i) (srv_socket[i].concurrent)
#define TOTAL_THREADS(i) (WAIT_THREADS(i) + BUSY_THREADS(i))
#define RQST_COUNT(i) (srv_socket[i].rqst_count)
#define MAIL_COUNT(i) (srv_socket[i].mail_count)
#define RCPT_COUNT(i) (srv_socket[i].rcpt_count)
#define TA_CNT_OK(i) (srv_socket[i].ta_cnt_ok)
#define TA_CNT_EACH(i) (srv_socket[i].ta_cnt_each)
#define TOTAL_RATE(i) (srv_socket[i].total_rate)
#define RCPT_CNT_OK(i) (srv_socket[i].rcpt_cnt_ok)
#define TA_CNT(i) (srv_socket[i].ta_cnt)
/******************************************************************
* Forward declarations
*/
static void usage(const char *progname);
static void parse_arguments(int argc, char *argv[]);
static void start_daemon(void);
static void set_thread_throttling(void);
static void create_listeners(void);
static void change_user(void);
static void open_log_files(void);
static void wrt_pid(void);
static void start_processes(void);
static void wdog_sighandler(int signo);
static void child_sighandler(int signo);
static void install_sighandlers(void);
static void start_threads(void);
static void *process_signals(void *arg);
static void *flush_acclog_buffer(void *arg);
static void *handle_connections(void *arg);
static void dump_server_info(void);
static void dump_stats(int fd, int myid, int all);
static void ss_shutdown(void);
static void Signal(int sig, void (*handler)(int));
static int cpu_count(void);
extern void handle_session(int srv_socket_index, st_netfd_t cli_nfd);
extern void load_configs(void);
extern void logbuf_open(void);
extern void logbuf_flush(void);
extern void logbuf_close(void);
/* Error reporting functions defined in the error.c file */
extern void err_sys_report(int fd, const char *fmt, ...);
extern void err_sys_quit(int fd, const char *fmt, ...);
extern void err_sys_dump(int fd, const char *fmt, ...);
extern void err_report(int fd, const char *fmt, ...);
extern void err_quit(int fd, const char *fmt, ...);
/*
* General server example: accept a client connection and do something.
* This program acts as an SMTP sink.
*
* This server creates a constant number of processes ("virtual processors"
* or VPs) and replaces them when they die. Each virtual processor manages
* its own independent set of state threads (STs), the number of which varies
* with load against the server. Each state thread listens to exactly one
* listening socket. The initial process becomes the watchdog, waiting for
* children (VPs) to die or for a signal requesting termination or restart.
* Upon receiving a restart signal (SIGHUP), all VPs close and then reopen
* log files and reload configuration. All currently active connections remain
* active. It is assumed that new configuration affects only request
* processing and not the general server parameters such as number of VPs,
* thread limits, bind addresses, etc. Those are specified as command line
* arguments, so the server has to be stopped and then started again in order
* to change them.
*
* Each state thread loops processing connections from a single listening
* socket. Only one ST runs on a VP at a time, and VPs do not share memory,
* so no mutual exclusion locking is necessary on any data, and the entire
* server is free to use all the static variables and non-reentrant library
* functions it wants, greatly simplifying programming and debugging and
* increasing performance (for example, it is safe to ++ and -- all global
* counters or call inet_ntoa(3) without any mutexes). The current thread on
* each VP maintains equilibrium on that VP, starting a new thread or
* terminating itself if the number of spare threads exceeds the lower or
* upper limit.
*
* All I/O operations on sockets must use the State Thread library's I/O
* functions because only those functions prevent blocking of the entire VP
* process and perform state thread scheduling.
*/
int
main(int argc, char *argv[])
{
if (geteuid() == 0)
err_quit(errfd, "ERROR: do not run this as super-user!");
/* Parse command-line options */
parse_arguments(argc, argv);
/* Allocate array of server pids */
if ((vp_pids = calloc(vp_count, sizeof(pid_t))) == NULL)
err_sys_quit(errfd, "ERROR: calloc failed");
/* Start the daemon */
if (!interactive_mode)
start_daemon();
/* Initialize the ST library */
if (st_init() < 0)
err_sys_quit(errfd, "ERROR: initialization failed: st_init");
#if SMTPS2_CDB
ss_id_init(1);
Rm_id_count = 0;
#endif
/* Set thread throttling parameters */
set_thread_throttling();
/* don't disable this, some mcp test relies on it */
wrt_pid();
/* Create listening sockets */
create_listeners();
/* Change the user */
if (username)
change_user();
/* Open log files */
open_log_files();
/* Start server processes (VPs) */
start_processes();
/* Turn time caching on */
st_timecache_set(1);
/* Install signal handlers */
install_sighandlers();
/* Load configuration from config files */
load_configs();
/* Start all threads */
start_threads();
/* Become a signal processing thread */
process_signals(NULL);
/* NOTREACHED */
return 1;
}
/******************************************************************/
static void
usage(const char *progname)
{
fprintf(stderr, "Usage: %s [options]\n"
"Possible options:\n"
"-0 Use SMTP.\n"
"-1 Accept only one recipient per transaction.\n"
"-2 stage First temporary then permanent error.\n"
" Stage is one of\n"
" M: Mail\n"
" R: Recipient\n"
" D: DATA\n"
" d: final dot\n"
"-3 stages First temporary then permanent error;\n"
" neeed to specify exactly two stages.\n"
"-4 Do not drop connection after returning 421\n"
"-5 stages Two (different) temporary errors for two stages.\n"
"-6 stage First temporary error then accept.\n"
"-7 stage First permanent error then accept.\n"
"-8 Offer Deferred RCPT Reply\n"
"-9 Offer RCPT status after dot.\n"
"-a Enable access logging.\n"
"-A SMTP Use SMTP reply code for DATA response.\n"
"-b host:port Bind to specified address. Multiple"
" addresses\n"
" are permitted.\n"
"-B n Wait n seconds before accepting message data.\n"
#if SMTPS2_CDB
"-C basedir/ Use CDB (write mail to disk).\n"
#endif
"-c n Show counter (print if counter %% n == 0)\n"
"-d n Set debug level.\n"
"-D n Perform data computation.\n"
"-e n Print elapsed time after each n messages.\n"
"-E SMTP Use SMTP reply code for EHLO response.\n"
"-F SMTP Use SMTP reply code for final dot response.\n"
"-G SMTP Use SMTP reply code for greeting.\n"
" If the greeting starts with '%c' then\n"
" the connection is dropped without greeting.\n"
"-g text Use text for EHLO response (should be \"250-...\").\n"
"-H Use a huge greeting.\n"
"-h Print this message.\n"
"-I SMTP Use SMTP reply code for RSET response.\n"
"-i Run in interactive mode [default].\n"
"-J delay Wait delay micro-seconds before reading another buffer of the message.\n"
"-j Background mode (fork()).\n"
"-k delay[:rand] Wait delay seconds before replying to final dot.\n"
" rand is an optional, maximum random time to sleep (additionally).\n"
"-L host:port Same as -b, but speak LMTP\n"
" host:port can also be unix:/path/to/socket\n"
"-l directory Use directory for logfile [default: \".\"].\n"
#if SMTPS2_CDB
"-M n Maximum number of CDB files.\n"
#endif
"-N myname Use myname as hostname in greeting.\n"
"-n Offer DSN.\n"
"-o n Accept all mail after n seconds.\n"
"-O path Open fd is passed via Unix socket path.\n"
"-P Offer PIPELINING.\n"
"-p num_processes Create specified number of processes.\n"
"-q backlog Set max length of pending connections"
" queue.\n"
"-R n Randomly reject SMTP commands (temporarily).\n"
" A command is rejected if random() < n.\n"
"-r n Reject only first n SMTP commands.\n"
"-s n Expect n messages with 1..n as body.\n"
"-S Serialize all accept() calls.\n"
"-t min_thr:max_thr Specify thread limits per listening"
" socket\n"
" across all processes.\n"
"-T n Expect n (accepted) messages.\n"
"-u user Change server's user id to specified"
" value.\n"
#if SMTPS2_CDB
"-U Don't commit CDB files (fsync()).\n"
#endif
"-v In LMTP mode: delay error replies for RCPTs.\n"
"-w n Wait n seconds before accepting DATA.\n"
"-W n Wait n seconds before replying to any command.\n"
" This also applies to the initial greeting.\n"
#if SMTPS2_CDB
"-X Stop on some errors.\n"
#endif
"-x SMTP Offer STARTTLS but reply with specified error.\n"
#if !SMTPS2_CDB
"-y Write incoming data into a single file.\n"
#endif
"-Z n Offer SIZE n.\n"
"-z n Set I/O timeout [%d].\n"
"\nSignals:\n"
"HUP Reload configuration, rotate logfile\n"
"TERM Terminate\n"
"USR1 Show statististcs\n"
"USR2 Reset counter for option -e\n"
, progname
, SM_DROP_IT
, iotimeout
);
exit(EX_USAGE);
}
/******************************************************************/
static void
parse_arguments(int argc, char *argv[])
{
extern char *optarg;
int opt;
char *c, *e;
drop421 = true;
while ((opt = getopt(argc, argv,
"012:3:45:6:7:89A:aB:b:C:c:D:d:E:e:F:fG:g:HhI:iJ:jk:L:l:M:mN:nO:o:Pp:q:R:r:Ss:T:t:Uu:vW:w:Xx:yZ:z:"))
!= -1)
{
switch (opt)
{
case '0':
UseSMTP = true;
break;
case '1':
onercpt = true;
break;
case '2':
tempperm = *optarg;
break;
case '3':
if (strlen(optarg) != 2)
err_quit(errfd,
"ERROR: need 2 stages for -3",
optarg);
temp2perm[0] = optarg[0];
temp2perm[1] = optarg[1];
break;
case '4':
drop421 = false;
break;
case '5':
if (strlen(optarg) != 2)
err_quit(errfd,
"ERROR: need 2 stages for -3",
optarg);
temptemp[0] = optarg[0];
temptemp[1] = optarg[1];
break;
case '6':
tempok = *optarg;
break;
case '7':
permok = *optarg;
break;
case '8':
offer_drr = true;
break;
case '9':
offer_rsad = true;
break;
case 'a':
log_access = true;
break;
case 'A':
if (IS_SMTP_CODE(optarg, 0))
strlcpy(dataresponse, optarg,
sizeof(dataresponse));
break;
case 'L':
++srv_socket[sk_count].lmtp;
/* FALLTHROUGH */
case 'b':
if (sk_count >= MAX_BIND_ADDRS)
err_quit(errfd,
"ERROR: max number of bind addresses (%d) exceeded",
MAX_BIND_ADDRS);
if ((c = strdup(optarg)) == NULL)
err_sys_quit(errfd, "ERROR: strdup");
srv_socket[sk_count++].addr = c;
break;
case 'B':
msgwait = strtoul(optarg, &e, 0);
break;
#if SMTPS2_CDB
case 'C':
Cdb_base = strdup(optarg);
if (NULL == Cdb_base)
err_quit(errfd,
"ERROR: strdup for CDB %s failed",
optarg);
Usecdb = true;
break;
#endif /* SMTPS2_CDB */
case 'c':
count = (unsigned long) strtoul(optarg, NULL, 0);
break;
case 'd':
debug = atoi(optarg);
break;
case 'D':
datacomp = atoi(optarg);
break;
case 'e':
eachN = strtoul(optarg, NULL, 0);
break;
case 'E':
strlcpy(ehloresp, optarg, sizeof(ehloresp));
break;
#if SMTPS2_CDB
case 'f':
Flat = true;
break;
#endif /* SMTPS2_CDB */
case 'F':
strlcpy(finaldot, optarg, sizeof(finaldot));
break;
case 'G':
strlcpy(greeting, optarg, sizeof(greeting));
break;
case 'g':
fullehlo = strdup(optarg);
if (NULL == fullehlo)
err_quit(errfd,
"ERROR: strdup for fullehlo %s failed",
optarg);
break;
case 'H':
Hugegreeting = true;
break;
case 'I':
if (IS_SMTP_CODE(optarg, 0))
strlcpy(rsetresp, optarg, sizeof(rsetresp));
break;
case 'i':
interactive_mode = true;
break;
case 'J':
delaymsg = strtoul(optarg, &e, 0);
break;
case 'j':
interactive_mode = false;
break;
case 'k':
delaydotreply = strtoul(optarg, &e, 0);
if (e != 0 && *e == ':')
{
++e;
delaydotrand = strtoul(e, &c, 0);
}
break;
case 'l':
logdir = strdup(optarg);
if (NULL == logdir)
err_quit(errfd,
"ERROR: strdup for logdir %s failed",
optarg);
break;
#if SMTPS2_CDB
case 'M':
Maxcdbs = atoi(optarg);
break;
#endif
case 'm':
idisplay = true;
break;
case 'N':
Myname = strdup(optarg);
if (NULL == Myname)
err_quit(errfd,
"ERROR: strdup for myname %s failed",
optarg);
break;
case 'n':
dsn = true;
break;
case 'o':
oktime = atoi(optarg) + st_time();
break;
case 'O':
fd_socket = strdup(optarg);
if (NULL == fd_socket)
err_quit(errfd,
"ERROR: strdup for fd_socket %s failed",
optarg);
break;
case 'p':
vp_count = atoi(optarg);
if (vp_count < 1)
err_quit(errfd,
"ERROR: invalid number of processes: %s",
optarg);
break;
case 'P':
pipelining = true;
break;
case 'q':
listenq_size = atoi(optarg);
if (listenq_size < 1)
err_quit(errfd,
"ERROR: invalid listen queue size: %s",
optarg);
break;
case 'R':
randrej = atol(optarg);
break;
case 'r':
nrejmsgs = atol(optarg);
rejmsgs = 1;
break;
case 's':
sequence = (int) strtol(optarg, &c, 10);
if (sequence < 1)
err_quit(errfd,
"ERROR: invalid number for sequence: %s",
optarg);
gotit = bit_alloc(sequence + 1);
if (NULL == gotit)
err_quit(errfd,
"ERROR: can't allocate bit array of size %d",
bitstr_size(sequence));
bit_nclear(gotit, 0, sequence);
bit_set(gotit, 0);
seqleft = sequence;
break;
case 'S':
/*
* Serialization decision is tricky on some platforms.
* For example, Solaris 2.6 and above has kernel
* sockets implementation, so supposedly
* there is no need for serialization.
* The ST library may be compiled on one OS version,
* but used on another, so the need for serialization
* should be determined at run time by the application.
* Since it's just an example,
* the serialization decision is left up to user.
* Only on platforms where the serialization is
* never needed on any OS
* version st_netfd_serialize_accept() is a no-op.
*/
serialize_accept = true;
break;
case 't':
max_wait_threads = (int) strtol(optarg, &c, 10);
if (*c++ == ':')
max_threads = atoi(c);
if (max_wait_threads < 0 || max_threads < 0)
err_quit(errfd, "ERROR: invalid number of threads: %s", optarg);
break;
case 'T':
tasexpected = (unsigned int) strtol(optarg, &c, 10);
if (tasexpected < 1)
err_quit(errfd,
"ERROR: invalid number for -T: %s",
optarg);
break;
case 'u':
username = optarg;
break;
#if SMTPS2_CDB
case 'U':
Unsafe = true;
break;
#endif /* SMTPS2_CDB */
case 'v':
++srv_socket[sk_count].lmtp;
break;
case 'w':
datawait = atoi(optarg);
break;
case 'W':
delayreply = atoi(optarg);
break;
case 'X':
Stoponerror = true;
break;
case 'x':
if (optarg[0] != '4' && optarg[0] != '5')
err_quit(errfd,
"ERROR: invalid reply code for -x: %s",
optarg);
strlcpy(starttls, optarg, sizeof(starttls));
break;
case 'y':
#if SMTPS2_CDB
err_report(errfd, "ERROR: option 'y' not available");
#else
writedata = true;
#endif
break;
case 'Z':
max_msg_size = atol(optarg);
break;
case 'z':
iotimeout = atoi(optarg);
break;
case 'h':
case '?':
usage(argv[0]);
}
}
if (logdir == NULL && !interactive_mode)
{
err_report(errfd, "ERROR: logging directory is required");
usage(argv[0]);
}
if (offer_rsad && offer_drr) {
err_report(errfd, "ERROR: cannot offer DRR and RSAD together");
usage(argv[0]);
}
#if SMTPS2_CDB
if (NULL == Cdb_base)
Cdb_base = "./";
opt = cdb_start(Cdb_base, &Cdb_ctx);
if (sm_is_err(opt))
err_quit(errfd, "ERROR: cdb_start=%#x", opt);
#endif /* SMTPS2_CDB */
if (vp_count == 0 && (vp_count = cpu_count()) < 1)
vp_count = 1;
if (sk_count == 0)
{
sk_count = 1;
srv_socket[0].addr = "0.0.0.0";
}
}
/******************************************************************/
static void
start_daemon(void)
{
pid_t pid;
/* Start forking */
if ((pid = fork()) < 0)
err_sys_quit(errfd, "ERROR: fork");
if (pid > 0)
exit(0); /* parent */
/* First child process */
setsid(); /* become session leader */
if ((pid = fork()) < 0)
err_sys_quit(errfd, "ERROR: fork");
if (pid > 0) /* first child */
exit(0);
umask(022);
if (chdir(logdir) < 0)
err_sys_quit(errfd, "ERROR: can't change directory to %s: chdir", logdir);
}
/******************************************************************
* For simplicity, the minimal size of thread pool is considered
* as a maximum number of spare threads (max_wait_threads) that
* will be created upon server startup. The pool size can grow up
* to the max_threads value. Note that this is a per listening
* socket limit. It is also possible to limit the total number of
* threads for all sockets rather than impose a per socket limit.
*/
static void
set_thread_throttling(void)
{
/*
* Calculate total values across all processes.
* All numbers are per listening socket.
*/
if (max_wait_threads == 0)
max_wait_threads = MAX_WAIT_THREADS_DEFAULT * vp_count;
/*
* Assuming that each client session needs FD_PER_THREAD file
* descriptors
*/
if (max_threads == 0)
max_threads = (st_getfdlimit() * vp_count) / FD_PER_THREAD / sk_count;
if (max_wait_threads > max_threads)
max_wait_threads = max_threads;
/*
* Now calculate per-process values.
*/
if (max_wait_threads % vp_count)
max_wait_threads = max_wait_threads / vp_count + 1;
else
max_wait_threads = max_wait_threads / vp_count;
if (max_threads % vp_count)
max_threads = max_threads / vp_count + 1;
else
max_threads = max_threads / vp_count;
if (min_wait_threads > max_wait_threads)
min_wait_threads = max_wait_threads;
}
static int
ss2_get_fd(void)
{
sm_ret_T ret;
int sock;
int addrlen;
ssize_t i;
st_netfd_t fd, lfd;
struct sockaddr addr;
char buf[2]; /* Should this be bigger to receive some data, */
/* e.g., IP address, port number for tracing etc? */
lfd = fd = NULL;
sock = -1;
if (debug > 5)
sm_io_fprintf(smioerr, "before un_st_server_listen\n");
ret = un_st_server_listen(fd_socket, 10, &lfd);
if (sm_is_err(ret))
{
err_quit(errfd,
"sev=ERROR, socket=%s, listen=%#x"
, fd_socket, ret);
}
addrlen = sizeof(addr);
if (debug > 5)
sm_io_fprintf(smioerr, "before st_accept\n");
fd = st_accept(lfd, &addr, &addrlen, (st_utime_t) -1);
if (NULL == fd)
{
st_netfd_close(lfd);
err_sys_quit(errfd,
"sev=ERROR, socket=%s, accept=failed"
, fd_socket);
}
if (debug > 5)
sm_io_fprintf(smioerr, "before sm_read_fd\n");
i = sm_read_fd(fd, buf, 1, &sock);
if (sock < 0)
err_quit(errfd,
"sev=ERROR, socket=%s, sock=%d, read_fd=%d"
, fd_socket, sock, i);
if (debug > 5)
sm_io_fprintf(smioerr, "sm_read_fd=%d\n", sock);
/* pass only one fd over this socket */
(void) unlink(fd_socket);
return sock;
}
/******************************************************************/
static void
create_listeners(void)
{
int i, n, sock, addrlen, pass;
char *c;
short port;
struct hostent *hp;
sm_sockaddr_T serv_addr;
pass = (fd_socket != NULL && *fd_socket != '\0');
for (i = 0; i < sk_count; i++)
{
port = 0;
addrlen = 0;
if (pass)
{
sock = ss2_get_fd();
}
else if (strncmp(srv_socket[i].addr, "unix:", 5) == 0)
{
size_t len;
port = 1025;
c = srv_socket[i].addr + 5;
if (*c == '\0')
err_sys_quit(errfd, "ERROR: missing unix: pathname");
/* Create server socket */
if ((sock = socket(AF_UNIX, SOCK_STREAM, 0)) < 0)
err_sys_quit(errfd, "ERROR: can't create socket: socket");
n = 1;
if (setsockopt(sock, SOL_SOCKET, SO_REUSEADDR, (char *) &n, sizeof(n)) < 0)
err_sys_quit(errfd, "ERROR: can't set SO_REUSEADDR: setsockopt");
sm_memset(&serv_addr, '\0', sizeof(serv_addr));
addrlen = sizeof(serv_addr.sunix);
serv_addr.sa.sa_family = AF_UNIX;
len = strlen(c);
if (strlcpy(serv_addr.sunix.sun_path, c,
sizeof(serv_addr.sunix.sun_path)) >= sizeof(serv_addr.sunix.sun_path))
err_sys_quit(errfd, "ERROR: pathname too long");
#if HAVE_SOCK_UN_SUN_LEN
serv_addr.sunix.sun_len = len;
#endif
}
else
{
if ((c = strchr(srv_socket[i].addr, ':')) != NULL)
{
*c++ = '\0';
port = (short) atoi(c);
}
if (srv_socket[i].addr[0] == '\0')
srv_socket[i].addr = "0.0.0.0";
if (port == 0)
port = SERV_PORT_DEFAULT;
/* Create server socket */
if ((sock = socket(PF_INET, SOCK_STREAM, 0)) < 0)
err_sys_quit(errfd, "ERROR: can't create socket: socket");
n = 1;
if (setsockopt(sock, SOL_SOCKET, SO_REUSEADDR, (char *) &n, sizeof(n)) < 0)
err_sys_quit(errfd, "ERROR: can't set SO_REUSEADDR: setsockopt");
addrlen = sizeof(serv_addr.sin);
memset(&serv_addr, 0, sizeof(serv_addr));
serv_addr.sa.sa_family = AF_INET;
serv_addr.sin.sin_port = htons(port);
serv_addr.sin.sin_addr.s_addr = inet_addr(srv_socket[i].addr);
if (serv_addr.sin.sin_addr.s_addr == INADDR_NONE)
{
/* not dotted-decimal */
if ((hp = gethostbyname(srv_socket[i].addr)) == NULL)
err_quit(errfd, "ERROR: can't resolve address: %s",
srv_socket[i].addr);
memcpy(&serv_addr.sin.sin_addr, hp->h_addr, hp->h_length);
}
srv_socket[i].port = port;
}
if (!pass)
{
/* Do bind and listen */
if (bind(sock, (struct sockaddr *) & serv_addr,
addrlen) < 0)
{
err_sys_quit(errfd,
"ERROR: can't bind to address %s, port %d",
srv_socket[i].addr, port);
}
if (listen(sock, listenq_size) < 0)
err_sys_quit(errfd, "ERROR: listen");
}
/* Create file descriptor object from OS socket */
if ((srv_socket[i].nfd = st_netfd_open_socket(sock)) == NULL)
err_sys_quit(errfd, "ERROR: st_netfd_open_socket");
/*
* On some platforms (e.g. IRIX, Linux) accept()
* serialization is never needed for any OS version.
* In that case st_netfd_serialize_accept() is just a
* no-op. Also see the comment above.
*/
if (serialize_accept &&
st_netfd_serialize_accept(srv_socket[i].nfd) < 0)
err_sys_quit(errfd, "ERROR: st_netfd_serialize_accept");
srv_socket[i].da_fp = NULL;
if (writedata)
{
sm_ret_T ret;
char p[8];
snprintf(p, sizeof(p), "d%d", i);
ret = sm_io_open(SmStStdio, p, SM_IO_APPEND,
&srv_socket[i].da_fp, SM_IO_WHAT_END);
if (sm_is_err(ret))
err_sys_quit(errfd,
"ERROR: open(%s)=failed, err=%#X"
, p, ret);
}
}
}
/******************************************************************/
static void
change_user(void)
{
struct passwd *pw;
if ((pw = getpwnam(username)) == NULL)
err_quit(errfd, "ERROR: can't find user '%s': getpwnam failed", username);
if (setgid(pw->pw_gid) < 0)
err_sys_quit(errfd, "ERROR: can't change group id: setgid");
if (setuid(pw->pw_uid) < 0)
err_sys_quit(errfd, "ERROR: can't change user id: setuid");
err_report(errfd, "INFO: changed process user id to '%s'", username);
}
/******************************************************************/
static void
wrt_pid(void)
{
int fd;
char str[32];
/* Open and write pid to pid file */
if ((fd = open(PID_FILE, O_CREAT | O_WRONLY | O_TRUNC, 0644)) < 0)
err_sys_quit(errfd, "ERROR: can't open pid file: open");
snprintf(str, sizeof(str), "%d\n", (int) getpid());
if (write(fd, str, strlen(str)) != (ssize_t) strlen(str))
err_sys_quit(errfd, "ERROR: can't write to pid file: write");
close(fd);
}
static void
open_log_files(void)
{
int fd;
if (interactive_mode)
return;
/* Open access log */
if (log_access)
logbuf_open();
/* Open error log file */
if ((fd = open(ERRORS_FILE, O_CREAT | O_WRONLY | O_APPEND, 0644)) < 0)
err_sys_quit(errfd, "ERROR: can't open error log file: open");
errfd = fd;
err_report(errfd, "INFO: starting the server...");
}
/******************************************************************/
static void
start_processes(void)
{
int i, status, restart, children;
pid_t pid;
sigset_t mask, omask;
if (interactive_mode)
{
my_index = 0;
my_pid = getpid();
return;
}
children = vp_count;
for (i = 0; i < vp_count; i++)
{
if ((pid = fork()) < 0)
{
err_sys_report(errfd, "ERROR: can't create process: fork");
if (i == 0)
exit(1);
err_report(errfd, "WARN: started only %d processes out of %d", i,
vp_count);
children = vp_count = i;
break;
}
if (pid == 0)
{
my_index = i;
my_pid = getpid();
/* Child returns to continue in main() */
return;
}
vp_pids[i] = pid;
}
/*
* Parent process becomes a "watchdog" and never returns to main().
*/
/* Install signal handlers */
Signal(SIGTERM, wdog_sighandler); /* terminate */
Signal(SIGHUP, wdog_sighandler); /* restart */
Signal(SIGUSR1, wdog_sighandler); /* dump info */
Signal(SIGUSR2, wdog_sighandler); /* reset info */
/* Now go to sleep waiting for a child termination or a signal */
for (;;)
{
restart = 1;
if ((pid = wait(&status)) < 0)
{
if (EINTR == errno)
continue;
err_sys_quit(errfd, "ERROR: watchdog: wait");
}
/* Find index of the exited child */
for (i = 0; i < vp_count; i++)
{
if (vp_pids[i] == pid)
break;
}
/* Block signals while printing and forking */
sigemptyset(&mask);
sigaddset(&mask, SIGTERM);
sigaddset(&mask, SIGHUP);
sigaddset(&mask, SIGUSR1);
sigaddset(&mask, SIGUSR2);
sigprocmask(SIG_BLOCK, &mask, &omask);
if (WIFEXITED(status))
{
err_report(errfd,
"WARN: watchdog: process %d (pid %d) exited"
" with status %d", i, pid, WEXITSTATUS(status));
restart = WEXITSTATUS(status) != 0;
}
else if (WIFSIGNALED(status))
err_report(errfd, "WARN: watchdog: process %d (pid %d) terminated"
" by signal %d", i, pid, WTERMSIG(status));
else if (WIFSTOPPED(status))
err_report(errfd, "WARN: watchdog: process %d (pid %d) stopped"
" by signal %d", i, pid, WSTOPSIG(status));
else
err_report(errfd, "WARN: watchdog: process %d (pid %d) terminated:"
" unknown termination reason", i, pid);
if (restart)
{
/* Fork another VP */
if ((pid = fork()) < 0)
{
err_sys_report(errfd,
"ERROR: watchdog: can't create process: fork");
}
else if (pid == 0)
{
my_index = i;
my_pid = getpid();
/* Child returns to continue in main() */
return;
}
vp_pids[i] = pid;
}
else
{
vp_pids[i] = 0;
if (--children <= 0)
exit(0);
}
/* Restore the signal mask */
sigprocmask(SIG_SETMASK, &omask, NULL);
}
}
/******************************************************************/
static void
wdog_sighandler(int signo)
{
int i, err;
/* Save errno */
err = errno;
/* Forward the signal to all children */
for (i = 0; i < vp_count; i++)
{
if (vp_pids[i] > 0)
kill(vp_pids[i], signo);
}
/*
* It is safe to do pretty much everything here because process is
* sleeping in wait() which is async-safe.
*/
switch (signo)
{
case SIGHUP:
err_report(errfd, "INFO: watchdog: caught SIGHUP");
/* Reopen log files - needed for log rotation */
if (log_access)
{
logbuf_close();
logbuf_open();
}
close(errfd);
if ((errfd = open(ERRORS_FILE, O_CREAT | O_WRONLY | O_APPEND, 0644)) < 0)
err_sys_quit(STDERR_FILENO, "ERROR: watchdog: open");
break;
case SIGTERM:
/* Non-graceful termination */
if (gotit != NULL)
{
bit_ffc(gotit, sequence + 1, &i);
if (i != -1)
{
err_report(errfd,
"ERROR: missing at least %d", i);
}
}
err_report(errfd,
"INFO: watchdog: caught SIGTERM, terminating");
unlink(PID_FILE);
ss_shutdown();
exit(0);
case SIGUSR1:
err_report(errfd, "INFO: watchdog: caught SIGUSR1");
break;
case SIGUSR2:
break;
default:
err_report(errfd, "INFO: watchdog: caught signal %d", signo);
}
/* Restore errno */
errno = err;
}
/******************************************************************/
static void
install_sighandlers(void)
{
sigset_t mask;
int p[2];
/* Create signal pipe */
if (pipe(p) < 0)
err_sys_quit(errfd, "ERROR: process %d (pid %d): can't create"
" signal pipe: pipe", my_index, my_pid);
if ((sig_pipe[0] = st_netfd_open(p[0])) == NULL ||
(sig_pipe[1] = st_netfd_open(p[1])) == NULL)
err_sys_quit(errfd, "ERROR: process %d (pid %d): can't create"
" signal pipe: st_netfd_open", my_index, my_pid);
/* Install signal handlers */
Signal(SIGTERM, child_sighandler); /* terminate */
Signal(SIGHUP, child_sighandler); /* restart */
Signal(SIGUSR1, child_sighandler); /* dump info */
Signal(SIGUSR2, child_sighandler); /* reset info */
/* Unblock signals */
sigemptyset(&mask);
sigaddset(&mask, SIGTERM);
sigaddset(&mask, SIGHUP);
sigaddset(&mask, SIGUSR1);
sigaddset(&mask, SIGUSR2);
sigprocmask(SIG_UNBLOCK, &mask, NULL);
}
/******************************************************************/
static void
child_sighandler(int signo)
{
int err, fd;
err = errno;
fd = st_netfd_fileno(sig_pipe[1]);
/* write() is async-safe */
if (write(fd, &signo, sizeof(int)) != sizeof(int))
err_sys_quit(errfd, "ERROR: process %d (pid %d): child's signal"
" handler: write", my_index, my_pid);
errno = err;
}
/******************************************************************
* The "main" function of the signal processing thread.
*/
/* ARGSUSED */
static void *
process_signals(void *arg)
{
int signo, i;
(void) arg;
for (;;)
{
/* Read the next signal from the signal pipe */
if (st_read(sig_pipe[0], &signo, sizeof(int), -1) != sizeof(int))
err_sys_quit(errfd, "ERROR: process %d (pid %d): signal processor:"
" st_read", my_index, my_pid);
switch (signo)
{
case SIGHUP:
err_report(errfd, "INFO: process %d (pid %d): caught SIGHUP,"
" reloading configuration", my_index, my_pid);
if (interactive_mode)
{
load_configs();
break;
}
/* Reopen log files - needed for log rotation */
if (log_access)
{
logbuf_flush();
logbuf_close();
logbuf_open();
}
close(errfd);
if ((errfd = open(ERRORS_FILE, O_CREAT | O_WRONLY | O_APPEND, 0644)) < 0)
err_sys_quit(STDERR_FILENO, "ERROR: process %d (pid %d): signal"
" processor: open", my_index, my_pid);
/* Reload configuration */
load_configs();
break;
case SIGTERM:
/*
** Terminate ungracefully since it is generally not
** known how long it will take to gracefully complete
** all client sessions.
*/
if (gotit != NULL)
{
bit_ffc(gotit, sequence + 1, &i);
if (i != -1)
{
err_report(errfd,
"ERROR: missing at least %d", i);
}
}
err_report(errfd, "INFO: process %d (pid %d): caught SIGTERM,"
" terminating", my_index, my_pid);
if (idisplay) {
time_t lastNmail, elapsed;
lastNmail = st_time();
elapsed = lastNmail - firstNmail;
sm_io_fprintf(smioerr,
"\n%ld, %ld %ld, %ld"
, (long) firstNmail
, (long) lastNmail
, (long) elapsed
, (elapsed > 0) ? (long) (eachN / elapsed) : -1L
);
}
if (log_access)
logbuf_flush();
ss_shutdown();
exit(0);
case SIGUSR1:
err_report(errfd, "INFO: process %d (pid %d): caught SIGUSR1",
my_index, my_pid);
/* Print server info to stderr */
dump_server_info();
for (i = 0; i < sk_count; i++)
dump_stats(errfd, i, 0);
break;
case SIGUSR2:
TA_CNT_EACH(my_index) = 0;
break;
default:
err_report(errfd, "INFO: process %d (pid %d): caught signal %d",
my_index, my_pid, signo);
}
}
/* NOTREACHED */
return NULL;
}
/******************************************************************
* The "main" function of the access log flushing thread.
*/
/* ARGSUSED */
static void *
flush_acclog_buffer(void *arg)
{
(void) arg;
for (;;)
{
st_sleep(ACCLOG_FLUSH_INTERVAL);
logbuf_flush();
}
/* NOTREACHED */
return NULL;
}
/******************************************************************/
static void
start_threads(void)
{
long i, n;
/* Create access log flushing thread */
if (log_access &&
st_thread_create(flush_acclog_buffer, NULL, 0, 0) == NULL)
err_sys_quit(errfd, "ERROR: process %d (pid %d): can't create"
" log flushing thread", my_index, my_pid);
if (idisplay)
sm_io_fprintf(smioerr,
" msg time_i time_t msgs/s t-msgs/s busy conc\n");
/* Create connections handling threads */
for (i = 0; i < sk_count; i++)
{
err_report(errfd, "INFO: process %d (pid %d): starting %d"
" threads on %s:%d", my_index, my_pid,
max_wait_threads, srv_socket[i].addr,
srv_socket[i].port);
WAIT_THREADS(i) = 0;
BUSY_THREADS(i) = 0;
CONC_THREADS(i) = 0;
MAXB_THREADS(i) = 0;
RQST_COUNT(i) = 0;
MAIL_COUNT(i) = 0;
RCPT_COUNT(i) = 0;
TA_CNT_OK(i) = 0;
TA_CNT_EACH(i) = 0;
TOTAL_RATE(i) = 0;
TA_CNT(i) = 0;
RCPT_CNT_OK(i) = 0;
for (n = 0; n < max_wait_threads; n++)
{
if (st_thread_create(handle_connections, (void *) i,
0, 0) != NULL)
WAIT_THREADS(i)++;
else
err_sys_report(errfd,
"ERROR: process %d (pid %d): can't "
"create thread", my_index, my_pid);
}
if (WAIT_THREADS(i) == 0)
exit(1);
}
}
/******************************************************************/
static void *
handle_connections(void *arg)
{
st_netfd_t srv_nfd, cli_nfd;
st_utime_t tmo;
struct sockaddr_in from;
int fromlen, save_errno;
long i;
i = (long) arg;
srv_nfd = srv_socket[i].nfd;
fromlen = sizeof(from);
tmo = -1;
while (WAIT_THREADS(i) <= max_wait_threads)
{
cli_nfd = st_accept(srv_nfd, (struct sockaddr *) & from,
&fromlen, tmo);
if (NULL == cli_nfd)
{
save_errno = errno;
if (ETIME == save_errno)
goto done;
err_sys_report(errfd,
"ERROR: can't accept connection: st_accept");
if (save_errno == EBADF
|| save_errno == ENOTSOCK
|| save_errno == EINVAL)
break;
/* slow down... */
sleep(1);
/* break on certain errors? */
continue;
}
if (firstmail == 0)
firstmail = st_time();
/* Save peer address, so we can retrieve it later */
st_netfd_setspecific(cli_nfd, &from.sin_addr, NULL);
WAIT_THREADS(i)--;
BUSY_THREADS(i)++;
if (BUSY_THREADS(i) > MAXB_THREADS(i))
MAXB_THREADS(i) = BUSY_THREADS(i);
if (WAIT_THREADS(i) < min_wait_threads
&& TOTAL_THREADS(i) < max_threads
&& seqleft != 0)
{
/* Create another spare thread */
if (st_thread_create(handle_connections, (void *) i, 0,
0) != NULL)
WAIT_THREADS(i)++;
else
err_sys_report(errfd,
"ERROR: process %d (pid %d): can't"
" create thread", my_index, my_pid);
}
handle_session(i, cli_nfd);
st_netfd_close(cli_nfd);
WAIT_THREADS(i)++;
BUSY_THREADS(i)--;
done:
if (seqleft == 0 ||
(tasexpected > 0 && tasexpected <= TA_CNT_OK(i)))
{
if (lastmail == 0)
lastmail = st_time();
tmo = 3000;
/*
** give it a grace period as smtpc2 may send too
** many mails
*/
if (BUSY_THREADS(i) == 0 &&
(lastmail == 0 || lastmail + 2 < st_time()))
{
if (lastmail == 0)
lastmail = st_time();
err_report(errfd,
"start=%ld, end=%ld\n"
"elapsed %ld\n"
, (long) firstmail
, (long) lastmail
, (long) (lastmail - firstmail));
dump_stats(errfd, i, 1);
if (srv_socket[i].da_fp != NULL)
{
sm_io_close(srv_socket[i].da_fp,
SM_IO_CF_NONE);
srv_socket[i].da_fp = NULL;
}
exit(0);
}
}
}
WAIT_THREADS(i)--;
return NULL;
}
/******************************************************************/
static void
dump_stats(int fd, int myid, int all)
{
struct rusage rusage;
if (all)
{
err_report(fd,
"Thread limits (min/max) %d/%d\n"
"Waiting threads %d\n"
"Busy threads %d\n"
"Max busy threads %d\n"
"Requests served %d\n"
"Transactions %u\n"
"Recipients %u\n"
"Transactions accepted %u\n"
"Recipients accepted %u\n"
"Total size (data) %lu\n"
"Total rate %lu\n"
, max_wait_threads, max_threads
, WAIT_THREADS(myid), BUSY_THREADS(myid)
, MAXB_THREADS(myid)
, RQST_COUNT(myid)
, MAIL_COUNT(myid)
, RCPT_COUNT(myid)
, TA_CNT_OK(myid)
, RCPT_CNT_OK(myid)
, (unsigned long) total_size
, (unsigned long) TOTAL_RATE(myid)
);
}
if (getrusage(RUSAGE_SELF, &rusage) == 0)
{
err_report(fd,
"ru_utime= %7ld.%07ld\n"
"ru_stime= %7ld.%07ld\n"
"ru_maxrss= %7ld\n"
"ru_ixrss= %7ld\n"
"ru_idrss= %7ld\n"
"ru_isrss= %7ld\n"
"ru_minflt= %7ld\n"
"ru_majflt= %7ld\n"
"ru_nswap= %7ld\n"
"ru_inblock= %7ld\n"
"ru_oublock= %7ld\n"
"ru_msgsnd= %7ld\n"
"ru_msgrcv= %7ld\n"
"ru_nsignals=%7ld\n"
"ru_nvcsw= %7ld\n"
"ru_nivcsw= %7ld\n"
, rusage.ru_utime.tv_sec
, rusage.ru_utime.tv_usec
, rusage.ru_stime.tv_sec
, rusage.ru_stime.tv_usec
, rusage.ru_maxrss
, rusage.ru_ixrss
, rusage.ru_idrss
, rusage.ru_isrss
, rusage.ru_minflt
, rusage.ru_majflt
, rusage.ru_nswap
, rusage.ru_inblock
, rusage.ru_oublock
, rusage.ru_msgsnd
, rusage.ru_msgrcv
, rusage.ru_nsignals
, rusage.ru_nvcsw
, rusage.ru_nivcsw
);
}
}
static void
dump_server_info(void)
{
char *buf;
int i, len, s;
s = sk_count * 512;
if ((buf = malloc(s)) == NULL)
{
err_sys_report(errfd, "ERROR: malloc failed");
return;
}
len = snprintf(buf, s, "\n\nProcess #%d (pid %d):\n",
my_index, (int) my_pid);
for (i = 0; i < sk_count; i++)
{
len += snprintf(buf + len, s - len, "\nListening Socket #%d:\n"
"-------------------------\n"
"Address %s:%d\n"
"Thread limits (min/max) %d/%d\n"
"Waiting threads %d\n"
"Busy threads %d\n"
"Max busy threads %d\n"
"Requests served %d\n"
"Transactions %u\n"
"Recipients %u\n"
"Transactions accepted %u\n"
"Recipients accepted %u\n"
"Total rate %lu\n"
, i, srv_socket[i].addr, srv_socket[i].port
, max_wait_threads, max_threads
, WAIT_THREADS(i), BUSY_THREADS(i)
, MAXB_THREADS(i)
, RQST_COUNT(i)
, MAIL_COUNT(i)
, RCPT_COUNT(i)
, TA_CNT_OK(i)
, RCPT_CNT_OK(i)
, (unsigned long) TOTAL_RATE(i)
);
}
write(STDERR_FILENO, buf, len);
#if SM_HEAP_CHECK
SmHeapCheck = 1;
if (HEAP_CHECK)
sm_heap_report(smioerr, 3);
#endif /* SM_HEAP_CHECK */
free(buf);
}
/******************************************************************
* Stubs
*/
/*
** SMTPSREAD -- read an SMTP command
**
** Parameters:
** fp -- SMTP I/O file
** buf -- buffer in which the SMTP command should be stored
** len -- length of buffer
** rd -- str to use for reading
**
** Returns:
** length of str or -1 on error
*/
static int
smtpsread(sm_file_T *fp, char *buf, size_t len, sm_str_P rd)
{
ssize_t r;
sm_ret_T ret;
char *s;
sm_str_clr(rd);
/* don't use fgetline0() because this is also for DATA and needs \r\n */
ret = sm_fgetline(fp, rd);
if (debug > 4)
{
sm_io_fprintf(smioerr,
"rcvd [len=%d, res=%#x]: ",
sm_str_getlen(rd), ret);
sm_io_write(smioerr, sm_str_getdata(rd),
sm_str_getlen(rd), &r);
sm_io_flush(smioerr);
}
if (sm_is_error(ret))
{
sm_io_fprintf(smioerr, "read error ret=%#x\n", ret);
return -1;
}
s = (char *) sm_str_getdata(rd);
if (NULL == s)
return -1;
strlcpy(buf, s, len);
return sm_str_getlen(rd);
}
static sm_ret_T
smtpreply(char *buf, size_t len, sm_file_T *fp, bool flushit)
{
ssize_t r;
sm_ret_T ret;
if (debug > 4) {
sm_io_fprintf(smioerr, "send: ");
sm_io_write(smioerr, (const unsigned char *) buf, len, &r);
sm_io_flush(smioerr);
}
if (delayreply > 0)
st_sleep(delayreply);
ret = sm_io_write(fp, (const unsigned char *) buf, len, &r);
if (r != (ssize_t) len) {
sm_io_fprintf(smioerr,
"write error n=%d, r=%d, ret=%#x\n",
len, (int) r, ret);
return -1;
}
if (flushit) {
ret = sm_io_flush(fp);
if (sm_is_error(ret)) {
sm_io_fprintf(smioerr, "flush error=%#x\n", ret);
return -1;
}
}
return r;
}
/* transaction states, these must be sorted! */
#define SSTA_NONE 0x00 /* must be 0 */
#define SSTA_INIT 0x01 /* ta initialized */
#define SSTA_MAIL 0x02 /* MAIL received */
#define SSTA_RCPT 0x04 /* RCPT received */
#define SSTA_DATA 0x08 /* DATA received */
#define SSTA_DOT 0x10 /* Final dot received */
/* Note: this ONLY works in an if else "environment"! */
#define TEMPPERM(stage) \
else if (tempperm == (stage) && wastemp) \
strlcpy(resp, "551 perm after temp\r\n" , sizeof resp); \
else if (tempperm == (stage) && !wastemp) { \
wastemp = true; \
strlcpy(resp, "451 temp before perm\r\n" , sizeof resp); \
} \
else if (tempok == (stage) && !wastemp) { \
wastemp = true; \
strlcpy(resp, "451 temp before ok\r\n" , sizeof resp); \
} \
else if (permok == (stage) && !wasperm) { \
wasperm = true; \
strlcpy(resp, "551 perm before ok\r\n" , sizeof resp); \
} \
else if (temp2perm[1] == (stage) && was2temp) { \
strlcpy(resp, "551 perm after temp 2\r\n" , sizeof resp); \
was2temp = false; \
} \
else if (temp2perm[0] == (stage) && !was2temp) { \
was2temp = true; \
strlcpy(resp, "451 temp before perm 2\r\n" , sizeof resp); \
} \
else if (temptemp[1] == (stage) && wastt) { \
strlcpy(resp, "454 4.7.4 temp 4\r\n" , sizeof resp); \
} \
else if (temptemp[0] == (stage) && !wastt) { \
wastt = true; \
strlcpy(resp, "453 4.7.3 temp 3\r\n" , sizeof resp); \
}
extern sm_stream_T SmStThrNetIO;
#define SM_BUF_SZ (1024 * 4)
/*
** Maybe reject a command if
** oktime is not set or it hasn't expired yet.
*/
#define MAYBE_REJECT(cond) ((cond) && \
(((oktime == 0 || st_time() < oktime) && rejmsgs == 0) || \
(rejmsgs == 1 && nrejmsgs-- > 0)))
static void
gen_ehlo_resp(char *resp, size_t len)
{
if (MAYBE_REJECT(ehloresp[0] != '2'))
{
sm_snprintf(resp, len, "%s %s %sSMTP Hi\r\n", ehloresp,
UseSMTP ? "" : "S", Myname);
return;
}
if (fullehlo != NULL)
{
strlcpy(resp, fullehlo, len);
strlcat(resp, "\r\n", len);
}
else if (max_msg_size > 0)
{
sm_snprintf(resp, len,
"250-%s ESMTP Hi there\r\n250-SIZE %lu\r\n"
, Myname, max_msg_size);
}
else
sm_snprintf(resp, len, "250-%s ESMTP Hi there\r\n", Myname);
if (pipelining)
strlcat(resp, "250-PIPELINING\r\n", len);
if (offer_drr)
strlcat(resp, "250-DRR\r\n", len);
else if (offer_rsad)
strlcat(resp, "250-PRDR\r\n", len);
if (dsn)
strlcat(resp, "250-DSN\r\n", len);
if (starttls[0] != 'x')
strlcat(resp, "250-STARTTLS\r\n", len);
strlcat(resp, "250 HELP\r\n", len);
}
static int
max_rand(unsigned int maxval)
{
return random() % maxval;
}
static void
checkN(int srv_socket_index)
{
unsigned int ta_cnt_e;
if (eachN > 0 && (++TA_CNT_EACH(srv_socket_index) % eachN) == 0)
{
time_t lastNmail, elapsed, elapsed_t;
ta_cnt_e = TA_CNT_EACH(srv_socket_index);
lastNmail = st_time();
elapsed = lastNmail - firstNmail;
elapsed_t = lastNmail - firstmail;
if (idisplay) {
if (elapsed_t > 0)
TOTAL_RATE(srv_socket_index) = ta_cnt_e / elapsed_t;
sm_io_fprintf(smioerr,
"%6u %6ld %6ld %6ld %6ld %4d %4d\n"
, ta_cnt_e
, (long) elapsed
, (long) elapsed_t
, (elapsed > 0) ? (long) (eachN / elapsed) : -1L
, (elapsed_t > 0) ? (long) TOTAL_RATE(srv_socket_index) : -1L
, BUSY_THREADS(srv_socket_index)
, CONC_THREADS(srv_socket_index)
);
/* sm_io_flush(smioerr); */
}
else {
err_report(errfd,
"msg=%u, start=%ld, end=%ld"
", elapsed=%ld, msg/s=%ld"
, TA_CNT_EACH(srv_socket_index)
, (long) firstNmail
, (long) lastNmail
, (long) elapsed
, (elapsed > 0) ? (long) (eachN / elapsed) : -1L
);
}
firstNmail = 0;
}
}
static void
ss_rcpts_remove_head(ss_rcpts_P ss_rcpts_hd)
{
ss_rcpt_P ss_rcpt;
if (SS_RCPTS_EMPTY(ss_rcpts_hd))
return;
ss_rcpt = SS_RCPTS_FIRST(ss_rcpts_hd);
SS_RCPTS_REMOVE_HEAD(ss_rcpts_hd);
free(ss_rcpt->ssr_reply);
free(ss_rcpt);
}
static int
ss_rcpts_append(ss_rcpts_P ss_rcpts_hd, const char *reply)
{
ss_rcpt_P ss_rcpt;
ss_rcpt = (ss_rcpt_P)malloc(sizeof(*ss_rcpt));
if (NULL == ss_rcpt)
return ENOMEM;
ss_rcpt->ssr_reply = strdup(reply);
if (NULL == ss_rcpt->ssr_reply) {
free(ss_rcpt);
return ENOMEM;
}
SS_RCPTS_INSERT_TAIL(ss_rcpts_hd, ss_rcpt);
return 0;
}
/*
* Session handling function stub.
*/
void
handle_session(int srv_socket_index, st_netfd_t cli_nfd)
{
char resp[2048];
char buf[SM_BUF_SZ];
short indata = 0;
int r, n, val, gotval, state, ta_state, rcpts;
int min_rcpts_reply, common_rcpt_status;
struct in_addr *from = st_netfd_getspecific(cli_nfd);
sm_file_T *fp;
#if SMTPS2_CDB
sm_file_T *dfp;
sessta_id_T ta_id;
#endif /* SMTPS2_CDB */
ssize_t byteswritten;
sm_ret_T ret;
sm_str_P rd;
static char eot[] = "\r\n.\r\n";
char sbuf[64];
ss_rcpts_T ss_rcpts_hd;
#define CRS_INIT (-1)
#define CRS_NONE (-2)
#define TA_INIT do { \
while (!SS_RCPTS_EMPTY(&ss_rcpts_hd)) \
ss_rcpts_remove_head(&ss_rcpts_hd); \
SS_RCPTS_INIT(&ss_rcpts_hd); \
rcpts = 0; \
use_drr = false; \
use_rsad = false; \
common_rcpt_status = CRS_INIT; \
} while (0)
fp = NULL;
#if SMTPS2_CDB
dfp = NULL;
ta_id[0] = '\0';
#endif /* SMTPS2_CDB */
rcpts = 0;
SS_RCPTS_INIT(&ss_rcpts_hd);
TA_INIT;
rd = sm_str_new(NULL, SM_BUF_SZ, SM_BUF_SZ);
if (NULL == rd)
{
err_sys_report(errfd,
"ERROR: process %d (pid %d): sm_str_new=failed",
my_index, my_pid);
return;
}
ret = sm_io_open(&SmStThrNetIO, (void *) &cli_nfd, SM_IO_RDWR,
&fp, SM_IO_WHAT_END);
if (ret != SM_SUCCESS)
{
err_sys_report(errfd,
"ERROR: process %d (pid %d): sm_io_open()=%#x",
my_index, my_pid, ret);
goto done;
}
/* switch to non-blocking */
sm_io_clrblocking(fp);
r = iotimeout;
ret = sm_io_setinfo(fp, SM_IO_WHAT_TIMEOUT, &r);
if (ret != SM_SUCCESS)
{
err_sys_report(errfd,
"ERROR: process %d (pid %d): set timeout()=%#x",
my_index, my_pid, ret);
goto done;
}
ret = sm_io_setinfo(fp, SM_IO_DOUBLE, NULL);
if (ret != SM_SUCCESS)
{
err_sys_report(errfd,
"ERROR: process %d (pid %d): set double=%#x",
my_index, my_pid, ret);
goto done;
}
++CONC_THREADS(srv_socket_index);
ta_state = SSTA_NONE;
if (greeting[0] == SM_DROP_IT)
{
if (delayreply > 0)
st_sleep(delayreply);
goto done;
}
if (Hugegreeting)
{
strlcpy(resp, "220-", sizeof resp);
strlcat(resp, Myname, sizeof resp);
strlcat(resp, " ESMTP ST sink\r\n"
"220-\r\n"
"220-The use of this host for the sending of unsolicited commercial\r\n"
"220-email, spam, or junk mail is strictly prohibited. Unauthorized\r\n"
"220-use of this machine will be prosecuted whereever possible by law.\r\n"
"220-\r\n"
"220-Hosts connecting to this server may be subjected to port scans,\r\n"
"220-call-backs, SMTP proxy relay tests, and/or other forms of\r\n"
"220-validation by this machine or others within our network. Any\r\n"
"220-further SMTP commands, other than QUIT, will constitute an\r\n"
"220-acceptance of this policy.\r\n"
"220-\r\n"
"220-This machine may no longer accept connections from IP addresses\r\n"
"220-which have no reverse-DNS (PTR record) assignment.\r\n"
"220 \r\n",
sizeof resp);
}
else
{
strlcpy(resp, MAYBE_REJECT(greeting[0] != '2')
? greeting : "220", sizeof resp);
strlcat(resp, " ", sizeof resp);
strlcat(resp, Myname, sizeof resp);
if (UseSMTP)
strlcat(resp, " SMTP ST sink\r\n", sizeof resp);
else
strlcat(resp, " ESMTP ST sink\r\n", sizeof resp);
}
n = strlen(resp);
if (n != 0 && smtpreply(resp, n, fp, true) != n)
{
err_sys_report(errfd,
"WARN: can't write greeting to %s: smtpreply",
inet_ntoa(*from));
goto done;
}
if (strcmp(greeting, "421") == 0)
goto done;
state = 0;
ta_state = SSTA_INIT;
if (seqleft != -1)
val = -2;
else
val = -3;
gotval = -1;
while ((r = smtpsread(fp, buf, sizeof(buf), rd)) >= 0) {
if (r == 0) {
switch (errno) {
case 0:
goto done;
case EINTR:
continue;
case ETIMEDOUT:
case EAGAIN:
default:
err_sys_report(errfd,
"WARN: can't read from %s",
inet_ntoa(*from));
goto done;
}
}
/* debug */
if (debug > (indata ? 8 : 0))
write(STDERR_FILENO, buf, r);
resp[0] = '\0';
if (indata > 0) {
int i, c;
if (indata == 1) {
if (msgwait > 0)
st_sleep(msgwait);
indata = 2;
}
else if (indata == 2) {
if (delaymsg > 0)
st_usleep(delaymsg);
}
total_size += r;
i = 0;
if (seqleft == -1)
val = -3;
if (datacomp > 0)
(void) data_comp(buf, r, datacomp);
while ((c = buf[i++]) && i <= r) {
if (val != -3 && c == '\n')
val = -1;
else if (val == -1) {
if (isascii(c) && isdigit(c))
val = 0;
else
val = -2;
}
if (val >= 0) {
if (isascii(c) && isdigit(c))
val = (val * 10) + c - '0';
else if (c == '\r') {
if (val > sequence) {
err_report(errfd,
"WARN: got %d > %d"
, val, sequence);
}
else if (bit_test(gotit, val))
err_report(errfd,
"WARN: got %d already"
, val);
else
gotval = val;
val = -10;
}
else
val = -2;
}
if (c == eot[state]) {
if (++state >= (int) strlen(eot))
break;
}
else {
state = 0;
if (c == eot[state])
++state;
}
}
if (debug > 7 && r > 0) {
if (r >= (int) sizeof(buf) - 1)
r -= 2;
buf[r + 1] = '\0';
snprintf(sbuf, sizeof sbuf, "state: %d, c=%c, i=%d, buf='%s'\r\n",
state, isprint(c) ? c : '_', i, buf + i);
write(STDERR_FILENO, sbuf, strlen(sbuf));
}
#if SMTPS2_CDB
if (dfp != NULL) {
ret = cdb_write(Cdb_ctx, dfp, (const unsigned char *) buf, r,
&byteswritten);
if (sm_is_err(ret)) {
err_report(errfd, "ERROR: cdb_write=%#x" , ret);
}
else if (r != byteswritten) {
err_report(errfd,
"ERROR: cdb_write=%#x, r=%d, written=%d"
, ret, r, byteswritten);
}
}
#else
if (writedata && srv_socket[srv_socket_index].da_fp != NULL) {
ret = sm_io_write(srv_socket[srv_socket_index].da_fp,
(const uchar *) buf, r, &byteswritten);
if (sm_is_err(ret) || r != byteswritten) {
err_report(errfd, "ERROR: write=%#x, written=%d"
, ret, byteswritten);
}
}
#endif /* SMTPS2_CDB */
if (state >= (int) strlen(eot)) {
if (MAYBE_REJECT(finaldot[0] != '2')) {
sm_snprintf(resp, sizeof(resp),
"%s Hmm\r\n", finaldot);
#if SMTPS2_CDB
if (dfp != NULL)
{
sm_io_close(dfp, SM_IO_CF_RM);
dfp = NULL;
}
#endif /* SMTPS2_CDB */
}
else {
/* no reply to '.' for DRR! */
if (use_drr)
;
/* no reply to '.' in LMTP mode! */
else if (srv_socket[srv_socket_index].lmtp > 0) {
if (delaydotreply > 0)
st_sleep(delaydotreply);
if (delaydotrand > 0)
st_sleep(max_rand(delaydotrand));
if (srv_socket[srv_socket_index].lmtp > 1) {
ss_rcpt_P ss_rcpt;
ss_rcpt = SS_RCPTS_FIRST(&ss_rcpts_hd);
strlcpy(resp, ss_rcpt->ssr_reply, sizeof resp);
}
else
#if SMTPS2_CDB
if (Usecdb && ta_id[0] != '\0')
snprintf(resp, sizeof resp, "250 OKR %s\r\n", ta_id);
else
#endif /* SMTPS2_CDB */
strlcpy(resp, "250 OKR\r\n", sizeof resp);
}
TEMPPERM('d')
else if (randrej > 0 && random() < randrej)
strlcpy(resp, "451 Random\r\n", sizeof resp);
else {
if (delaydotreply > 0)
st_sleep(delaydotreply);
if (delaydotrand > 0)
st_sleep(max_rand(delaydotrand));
#if SMTPS2_CDB
if (Usecdb && ta_id[0] != '\0')
snprintf(resp, sizeof resp, "250 got %s\r\n", ta_id);
else
#endif /* SMTPS2_CDB */
if (use_rsad && !SS_RCPTS_EMPTY(&ss_rcpts_hd) &&
!EXPLICIT_FINALDOT)
{
ss_rcpt_P ss_rcpt;
/* optimization: check for common RCPT status */
for (ss_rcpt = SS_RCPTS_FIRST(&ss_rcpts_hd);
ss_rcpt != SS_RCPTS_END(&ss_rcpts_hd);
ss_rcpt = SS_RCPTS_NEXT(ss_rcpt))
{
n = atoi(ss_rcpt->ssr_reply);
if (CRS_INIT == common_rcpt_status)
common_rcpt_status = n;
else if (n != common_rcpt_status) {
common_rcpt_status = CRS_NONE;
break;
}
}
if (IS_SMTP_REPLY(common_rcpt_status))
sm_snprintf(resp, sizeof(resp), "%d first\r\n",
common_rcpt_status);
else
strlcpy(resp, "353 RCPT status follows\r\n",
sizeof resp);
}
else
strlcpy(resp, "250 got it\r\n", sizeof resp);
TA_CNT_OK(srv_socket_index)++;
if (count > 0 &&
(TA_CNT_OK(srv_socket_index) % count) == 0)
sm_io_fprintf(smioerr, "%u\r",
TA_CNT_OK(srv_socket_index));
checkN(srv_socket_index);
if (gotval >= 0) {
bit_set(gotit, gotval);
if (seqleft > 0)
seqleft--;
else
err_report(errfd, "WARN: left=%d", seqleft);
}
}
#if SMTPS2_CDB
if (!Unsafe && dfp != NULL) {
if (Flat) {
r = fsync(f_fd(*dfp));
if (r == -1)
ret = sm_error_perm(SM_EM_CDB, errno);
}
else
ret = cdb_commit(Cdb_ctx, dfp);
if (sm_is_err(ret)) {
err_report(errfd, "ERROR: cdb_commit=%#x", ret);
}
}
if (dfp != NULL) {
ret = cdb_close(Cdb_ctx, dfp, SM_IO_CF_NONE);
if (sm_is_err(ret)) {
err_report(errfd, "ERROR: cdb_close=%#x", ret);
}
dfp = NULL;
++Cdbfiles;
}
#endif /* SMTPS2_CDB */
}
n = strlen(resp);
if (smtpreply(resp, n, fp, true) != n) {
err_sys_report(errfd,
"WARN: can't write response to %s: st_write",
inet_ntoa(*from));
goto done;
}
if (debug > 7 && n != 0)
write(STDERR_FILENO, resp, n);
/*
* one reply has already been given for LMTP
* other LMTP replies are delayed if lmtp > 1
*/
if (srv_socket[srv_socket_index].lmtp > 1 && rcpts > 1) {
ss_rcpts_remove_head(&ss_rcpts_hd);
while (--rcpts > 0) {
char *reply;
ss_rcpt_P ss_rcpt;
ss_rcpt = SS_RCPTS_FIRST(&ss_rcpts_hd);
reply = ss_rcpt->ssr_reply;
n = strlen(reply);
if (smtpreply(reply, n, fp, true) != n) {
err_sys_report(errfd,
"WARN: can't write response to %s: st_write",
inet_ntoa(*from));
goto done;
}
if (debug > 7 && n != 0)
write(STDERR_FILENO, reply, n);
ss_rcpts_remove_head(&ss_rcpts_hd);
}
}
else if (srv_socket[srv_socket_index].lmtp && rcpts > 1) {
strlcpy(resp, "250 OKR\r\n", sizeof resp);
n = strlen(resp);
while (--rcpts > 0) {
if (smtpreply(resp, n, fp, true) != n) {
err_sys_report(errfd,
"WARN: can't write response to %s: st_write",
inet_ntoa(*from));
goto done;
}
if (debug > 7 && n != 0)
write(STDERR_FILENO, resp, n);
}
}
while (use_drr && !SS_RCPTS_EMPTY(&ss_rcpts_hd)) {
char *reply;
ss_rcpt_P ss_rcpt;
ss_rcpt = SS_RCPTS_FIRST(&ss_rcpts_hd);
reply = ss_rcpt->ssr_reply;
n = strlen(reply);
if (smtpreply(reply, n, fp, true) != n) {
err_sys_report(errfd,
"WARN: can't write response to %s: st_write",
inet_ntoa(*from));
goto done;
}
if (debug > 7 && n != 0)
write(STDERR_FILENO, reply, n);
ss_rcpts_remove_head(&ss_rcpts_hd);
}
min_rcpts_reply = 550;
while (use_rsad && !IS_SMTP_REPLY(common_rcpt_status)
&& !SS_RCPTS_EMPTY(&ss_rcpts_hd)
&& !EXPLICIT_FINALDOT)
{
char *reply;
ss_rcpt_P ss_rcpt;
ss_rcpt = SS_RCPTS_FIRST(&ss_rcpts_hd);
reply = ss_rcpt->ssr_reply;
n = atoi(reply);
if (n < min_rcpts_reply)
min_rcpts_reply = n;
n = strlen(reply);
if (smtpreply(reply, n, fp, true) != n) {
err_sys_report(errfd,
"WARN: can't write response to %s: st_write",
inet_ntoa(*from));
goto done;
}
if (debug > 7 && n != 0)
write(STDERR_FILENO, reply, n);
ss_rcpts_remove_head(&ss_rcpts_hd);
}
if (use_rsad && !IS_SMTP_REPLY(common_rcpt_status)
&& !EXPLICIT_FINALDOT)
{
sm_snprintf(resp, sizeof(resp), "%d final\r\n",
min_rcpts_reply);
n = strlen(resp);
if (smtpreply(resp, n, fp, true) != n) {
err_sys_report(errfd,
"WARN: can't write response to %s: st_write",
inet_ntoa(*from));
goto done;
}
}
indata = 0;
state = 0;
ta_state = SSTA_INIT;
continue;
}
}
if (indata == 0) {
if (strncasecmp(buf, "data", 4) == 0) {
if (datawait > 0)
st_sleep(datawait);
else if (datawait < 0) {
st_sleep(0 - datawait);
datawait = 0;
}
/*
err_report(errfd, "rejmsg=%d, nrejmsg=%ld, MAYBE=%d",
rejmsgs, nrejmsgs, MAYBE_REJECT(dataresponse[0] != '3'));
*/
if (ta_state < SSTA_RCPT)
strlcpy(resp, "503 Need RCPT\r\n", sizeof resp);
else if (MAYBE_REJECT(dataresponse[0] != '3'))
sm_snprintf(resp, sizeof(resp), "%s Hmm\r\n", dataresponse);
TEMPPERM('D')
else if (randrej > 0 && random() < randrej)
strlcpy(resp, "451 Random\r\n", sizeof resp);
else
{
ret = SM_SUCCESS;
#if SMTPS2_CDB
if (Usecdb)
{
sessta_id_T rm_id;
if (Maxcdbs > 0 &&
Cdbfiles > Maxcdbs)
{
sm_snprintf(rm_id, SMTP_STID_SIZE,
SMTPS_STID_FORMAT, Rm_id_count, my_index);
cdb_unlink(Cdb_ctx, rm_id);
--Cdbfiles;
++Rm_id_count;
}
ss_id_next(my_index, ta_id);
if (Flat)
ret = sm_io_open(SmStStdio, ta_id, SM_IO_WRONLY,
&dfp, SM_IO_WHAT_END);
else
ret = cdb_open(Cdb_ctx, ta_id, dfp, SM_IO_WREXCL,
/*size*/ 0, /*hints*/ 0);
if (sm_is_err(ret))
{
err_report(errfd,
"ERROR: process %d, cdb_open=%#x, ta_id=%s"
, my_index, ret
, ta_id);
strlcpy(resp, "452 Nope\r\n", sizeof resp);
if (Stoponerror)
exit(1);
}
}
#endif /* SMTPS2_CDB */
if (!sm_is_err(ret)) {
indata = 1;
ta_state = SSTA_DATA;
strlcpy(resp, "354 Go\r\n", sizeof resp);
}
}
}
else if (strncasecmp(buf, "quit", 4) == 0) {
strlcpy(resp, "221 Bye\r\n", sizeof resp);
}
else if (strncasecmp(buf, "starttls", 8) == 0) {
strlcpy(resp, starttls, sizeof resp);
strlcat(resp, " oops\r\n", sizeof resp);
}
else if (strncasecmp(buf, "helo", 4) == 0) {
TA_INIT;
sm_snprintf(resp, sizeof(resp), "250 %s Hi there\r\n", Myname);
}
else if (strncasecmp(buf, "ehlo", 4) == 0 && !UseSMTP) {
TA_INIT;
gen_ehlo_resp(resp, sizeof(resp));
}
else if (strncasecmp(buf, "lhlo", 4) == 0 &&
srv_socket[srv_socket_index].lmtp > 0)
{
TA_INIT;
if (MAYBE_REJECT(ehloresp[0] != '2'))
sm_snprintf(resp, sizeof(resp),
"%s ESMTP Hi\r\n", ehloresp);
else if (pipelining)
strlcpy(resp, "250-ESMTP Hi there\r\n250-PIPELINING\r\n250 HELP\r\n",
sizeof resp);
else
strlcpy(resp, "250-ESMTP Hi there\r\n250 HELP\r\n", sizeof resp);
}
else if (strncasecmp(buf, "mail from:<", 11) == 0) {
if (firstmail == 0)
firstmail = st_time();
if (eachN > 0 && firstNmail == 0)
firstNmail = st_time();
TA_CNT(srv_socket_index)++;
ta_state = SSTA_MAIL;
TA_INIT;
if (MAYBE_REJECT('s' == buf[11] && IS_SMTP_CODE(buf, 12))) {
sm_snprintf(resp, sizeof(resp),
"%c%c%c whatever\r\n",
buf[12], buf[13], buf[14]);
}
else if (MAYBE_REJECT(IS_SMTP_CODE(buf, 11))) {
sm_snprintf(resp, sizeof(resp),
"%c%c%c whatever\r\n",
buf[11], buf[12], buf[13]);
}
else if (MAYBE_REJECT('s' == buf[11] &&
IS_MULTILINE_SMTP_CODE(buf, 12))) {
sm_snprintf(resp, sizeof(resp),
#if 0
"550-5.1.0 <example-sendmail@support.example.org>... Your MX reply is \"501 5.1.7 Bad sender's mailbox address syntax.\"\r\n"
"550 5.1.0 Sender validity for <example-sendmail@support.example.org> not confirmed by MX \"host.example.org\"\r\n");
#else
"%c%c%c-%c.7.0 multi line first one\r\n"
"%c%c%c %c.7.0 multi line last one with some other text\r\n",
buf[12] - MULTILINE_SMTP_OFF, buf[13], buf[14],
buf[12] - MULTILINE_SMTP_OFF,
buf[12] - MULTILINE_SMTP_OFF, buf[13], buf[14],
buf[12] - MULTILINE_SMTP_OFF);
#endif
}
else if (MAYBE_REJECT(IS_MULTILINE_SMTP_CODE(buf, 11))) {
sm_snprintf(resp, sizeof(resp),
"%c%c%c-multi line first one\r\n"
"%c%c%c-multi line second\r\n"
"%c%c%c multi line last one with some other text\r\n",
buf[11] - MULTILINE_SMTP_OFF, buf[12], buf[13],
buf[11] - MULTILINE_SMTP_OFF, buf[12], buf[13]);
}
TEMPPERM('M')
else if (randrej > 0 && random() < randrej)
strlcpy(resp, "451 Random\r\n", sizeof resp);
else
strlcpy(resp, "250 Ok\r\n", sizeof resp);
MAIL_COUNT(srv_socket_index)++;
/* HACK... this doesn't really parse arguments properly! */
use_drr = (offer_drr && strstr(buf, "> DRR") != NULL);
/* HACK... this doesn't really parse arguments properly! */
use_rsad = (offer_rsad && strstr(buf, "> PRDR") != NULL);
/* Use some library function??? */
}
else if (strncasecmp(buf, "rcpt to:<", 9) == 0) {
bool drr;
bool rsad;
rsad = false;
drr = false;
if (use_drr &&
MAYBE_REJECT('d' == buf[9] && IS_SMTP_CODE(buf, 10))) {
sm_snprintf(resp, sizeof(resp),
"%c%c%c whatever\r\n",
buf[10], buf[11], buf[12]);
drr = true;
}
else if (use_rsad &&
MAYBE_REJECT('d' == buf[9] && IS_SMTP_CODE(buf, 10)))
{
sm_snprintf(resp, sizeof(resp), "%c%c%c whatever\r\n",
buf[10], buf[11], buf[12]);
rsad = true;
}
else if (MAYBE_REJECT('r' == buf[9] && IS_SMTP_CODE(buf, 10))) {
sm_snprintf(resp, sizeof(resp),
"%c%c%c whatever\r\n",
buf[10], buf[11], buf[12]);
}
else if (MAYBE_REJECT(IS_SMTP_CODE(buf, 9))) {
sm_snprintf(resp, sizeof(resp),
"%c%c%c whatever\r\n",
buf[9], buf[10], buf[11]);
}
else if (ta_state < SSTA_MAIL)
strlcpy(resp, "503 Need MAIL\r\n", sizeof resp);
TEMPPERM('R')
else if (randrej > 0 && random() < randrej)
strlcpy(resp, "451 Random\r\n", sizeof resp);
else if (onercpt && rcpts > 0)
strlcpy(resp, "451 one rcpt only\r\n", sizeof resp);
else {
if (use_drr) {
drr = true;
strlcpy(resp, "250 Ok drr\r\n", sizeof resp);
}
else if (use_rsad) {
rsad = true;
strlcpy(resp, "250 Ok rsad\r\n", sizeof resp);
}
else
strlcpy(resp, "250 Ok\r\n", sizeof resp);
ta_state = SSTA_RCPT;
++rcpts;
RCPT_CNT_OK(srv_socket_index)++;
}
if (drr || rsad) {
n = ss_rcpts_append(&ss_rcpts_hd, resp);
if (n != 0) {
strlcpy(resp, "451 enomem\r\n", sizeof resp);
}
else {
strlcpy(resp, "250 deferred\r\n", sizeof resp);
ta_state = SSTA_RCPT;
++rcpts;
RCPT_CNT_OK(srv_socket_index)++;
}
}
if (srv_socket[srv_socket_index].lmtp > 1) {
n = ss_rcpts_append(&ss_rcpts_hd, resp);
if (n != 0)
strlcpy(resp, "451 enomem\r\n", sizeof resp);
else {
if (resp[0] != '2') {
ta_state = SSTA_RCPT;
++rcpts;
RCPT_CNT_OK(srv_socket_index)++;
}
strlcpy(resp, "250 lmtp\r\n", sizeof resp);
}
}
RCPT_COUNT(srv_socket_index)++;
}
else if (strncasecmp(buf, "rset", 4) == 0) {
if (MAYBE_REJECT(rsetresp[0] != '2'))
sm_snprintf(resp, sizeof(resp),
"%s whatever\r\n",
rsetresp);
else
strlcpy(resp, "250 Ok\r\n", sizeof resp);
TA_INIT;
ta_state = SSTA_INIT;
}
else if (strncasecmp(buf, "help", 4) == 0) {
strlcpy(resp, "250 Sorry, I lied.\r\n", sizeof resp);
}
else if (resp[0] == '\0') {
strlcpy(resp, "550 What?\r\n", sizeof resp);
}
n = strlen(resp);
if (n != 0 && (ret = smtpreply(resp, n, fp, true)) != n) {
err_sys_report(errfd,
"WARN: can't write response to %s: st_write=%d"
, inet_ntoa(*from), ret);
/* ignore error for QUIT */
if (strncasecmp(buf, "quit", 4) != 0)
goto done;
}
if (debug > 7 && n != 0)
write(STDERR_FILENO, resp, n);
if (strncasecmp(buf, "quit", 4) == 0 ||
(drop421 && strncmp(resp, "421", 3) == 0))
{
RQST_COUNT(srv_socket_index)++;
goto done;
}
}
}
if (r < 0) {
err_sys_report(errfd,
"sev=WARN, client=%s, st_read=%d, ta_state=%d",
inet_ntoa(*from), r, ta_state);
}
done:
--CONC_THREADS(srv_socket_index);
if (fp != NULL) {
sm_io_close(fp, SM_IO_CF_NONE);
fp = NULL;
}
#if SMTPS2_CDB
if (dfp != NULL) {
sm_io_close(dfp, SM_IO_CF_RM);
dfp = NULL;
}
#endif /* SMTPS2_CDB */
SM_STR_FREE(rd);
return;
}
/*
* Configuration loading function stub.
*/
void
load_configs(void)
{
err_report(errfd, "INFO: process %d (pid %d): configuration loaded",
my_index, my_pid);
}
/*
* Buffered access logging methods.
* Note that stdio functions (fopen(3), fprintf(3), fflush(3), etc.) cannot
* be used if multiple VPs are created since these functions can flush buffer
* at any point and thus write only partial log record to disk.
* Also, it is completely safe for all threads of the same VP to write to
* the same log buffer without any mutex protection (one buffer per VP, of
* course).
*/
void
logbuf_open(void)
{
}
void
logbuf_flush(void)
{
}
void
logbuf_close(void)
{
}
/******************************************************************
* Small utility functions
*/
static void
Signal(int sig, void (*handler) (int))
{
struct sigaction sa;
sa.sa_handler = handler;
sigemptyset(&sa.sa_mask);
sa.sa_flags = 0;
sigaction(sig, &sa, NULL);
}
static int
cpu_count(void)
{
int n;
#if defined (_SC_NPROCESSORS_ONLN)
n = (int) sysconf(_SC_NPROCESSORS_ONLN);
#elif defined (_SC_NPROC_ONLN)
n = (int) sysconf(_SC_NPROC_ONLN);
#elif defined (HPUX)
#include <sys/mpctl.h>
n = mpctl(MPC_GETNUMSPUS, 0, 0);
#else
n = -1;
errno = ENOSYS;
#endif
return n;
}
/******************************************************************/
static void
ss_shutdown(void)
{
int i;
for (i = 0; i < sk_count; i++)
{
if (strncmp(srv_socket[i].addr, "unix:", 5) == 0)
(void) unlink(srv_socket[i].addr + 5);
}
}
syntax highlighted by Code2HTML, v. 0.9.1