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