/* * 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 #include #include #include #include #include #include #include #include #include #include #include #include #include #include #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 #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 ... Your MX reply is \"501 5.1.7 Bad sender's mailbox address syntax.\"\r\n" "550 5.1.0 Sender validity for 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 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); } }