/* * Copyright (c) 2002-2006 Sendmail, Inc. and its suppliers. * All rights reserved. * * By using this file, you agree to the terms and conditions set * forth in the LICENSE file which can be found at the top level of * the sendmail distribution. */ #include "sm/generic.h" SM_RCSID("@(#)$Id: smtpc.c,v 1.209 2007/05/13 16:26:16 ca Exp $") #include "sm/common.h" #include "sm/assert.h" #include "sm/error.h" #include "sm/io.h" #include "sm/sysexits.h" #include "sm/str.h" #include "sm/string.h" #include "sm/limits.h" #include "sm/net.h" #include "statethreads/st.h" #include "sm/ctype.h" #include "sm/memops.h" #include "sm/signal.h" #include "sm/wait.h" #include "sm/qmgrcomm.h" #include "sm/hostname.h" #include "sm/misc.h" #include "smtpc.h" #include "sm/da.h" #include "sm/das.h" #include "c2q.h" #define SMTPC_DEFINE 1 #include "smtpch.h" #include "sm/log.h" #include "sm/version.h" #define SMTPC_LOG_DEFINES 1 #include "log.h" #include "sm/resource.h" #include "sm/smtpdef.h" #include "sm/sm-conf.h" #include "sm/sm-conf-prt.h" #include "sm/sccnfdef.h" #include "sm/confsetpath.h" #if SM_HEAP_CHECK extern SM_DEBUG_T SmHeapCheck; # define HEAP_CHECK (SmHeapCheck > 0) # define HEAP_REPORT do { if (HEAP_CHECK) sm_heap_report(smioerr, SmHeapCheck);} while (0) #else /* SM_HEAP_CHECK */ # define HEAP_CHECK false # define HEAP_REPORT #endif /* SM_HEAP_CHECK */ extern sm_stream_T SmStThrNetIO; /* ** Client configuration parameters */ /* Log files */ #define PID_FILE "sc.pid" /* ** Global data */ static sc_ctx_T Sc_ctx; /* SMTPC context */ /* some of the following data should be in sc_ctx */ uint conns; uint busy = 0; ulong total = 0; static st_netfd_t sig_pipe[2]; /* Signal pipe */ /* ** Configuration flags/parameters */ #if 0 static bool rpools = false; /* use rpools... */ #endif /* ** Forward declarations. ** Many functions here just exit on error, change them to return ** an error code instead. */ static void sc_usage(const char *progname); static void sc_parse_arguments(sc_ctx_P _sc_ctx, int argc, char *argv[]); #if 0 static void sc_start_daemon(void); #endif static void sc_start_processes(sc_ctx_P _sc_ctx); #if 0 static void wdog_sighandler(int signo); #endif static void sc_child_sighandler(int signo); static void sc_install_sighandlers(void); static void sc_process_signals(sc_ctx_P _sc_ctx); static void sc_signal(int sig, void (*handler)(int)); /* ** Fatal error unrelated to a system call. ** Print a message and terminate. */ static void sc_err_quit(int ex_code, const char *fmt,...) { va_list ap; va_start(ap, fmt); if (Sc_ctx.scc_lctx == NULL) { sm_io_vfprintf(smioerr, fmt, ap); sm_io_putc(smioerr, '\n'); } else { sm_log_vwrite(Sc_ctx.scc_lctx, SC_LCAT_INIT, SC_LMOD_CONFIG, SM_LOG_ERR, 0, fmt, ap); } va_end(ap); exit(ex_code); } /* ** MAIN -- SMTPC main ** ** Parameters: ** argc -- number of arguments ** argv -- vector of arguments ** ** Returns: ** exit code */ int main(int argc, char *argv[]) { sm_ret_T ret; char *prg; prg = argv[0]; sm_memzero(&Sc_ctx, sizeof(Sc_ctx)); if (getuid() == 0 || geteuid() == 0) { sc_err_quit(EX_USAGE, SM_DONTRUNASROOT, prg); exit(EX_USAGE); } Sc_ctx.sm_magic = SM_SC_CTX_MAGIC; Sc_ctx.scc_cnf.sm_magic = SM_SC_CNF_MAGIC; ret = sc_init0(&Sc_ctx); if (sm_is_err(ret)) { sm_log_write(Sc_ctx.scc_lctx, SC_LCAT_INIT, SC_LMOD_CONFIG, SM_LOG_ERR, 0, "sev=ERROR, func=main, initialization=failed, init0=%m" , ret); exit(sm_is_perm_err(ret) ? EX_SOFTWARE : EX_OSERR); } /* Parse command-line options */ sc_parse_arguments(&Sc_ctx, argc, argv); if (Sc_ctx.scc_cnf.sc_cnf_debug > 1) { sm_log_write(Sc_ctx.scc_lctx, SC_LCAT_INIT, SC_LMOD_CONFIG, SM_LOG_DEBUG, 10, "sev=DBG, func=main, uid=%ld, gid=%ld, euid=%ld, egid=%ld" , (long) getuid(), (long) getgid(), (long) geteuid() , (long) getegid()); } ret = sc_init_chk(&Sc_ctx); if (sm_is_err(ret)) { sm_log_write(Sc_ctx.scc_lctx, SC_LCAT_INIT, SC_LMOD_CONFIG, SM_LOG_ERR, 0, "sev=ERROR, func=main, initialization=failed, init_chk=%m" , ret); exit(sm_is_perm_err(ret) ? EX_SOFTWARE : EX_OSERR); } #if 0 /* Start the daemon */ if (SC_IS_CFLAG(&Sc_ctx, SCC_CFL_BACKGROUND)) sc_start_daemon(); #endif /* 0 */ /* Initialize the ST library */ if (st_init() < 0) sc_err_quit(EX_OSERR, "sev=ERROR, func=main, initialization=failed, st_init=failed"); /* Set thread throttling parameters */ sc_set_thread_throttling(&Sc_ctx); ret = sc_init1(&Sc_ctx); if (sm_is_err(ret)) sc_err_quit(sm_is_perm_err(ret) ? EX_SOFTWARE : EX_OSERR, "sev=ERROR, func=main, initialization=failed, sc_init1=%m" , ret); /* Start server processes (VPs) */ sc_start_processes(&Sc_ctx); /* Turn time caching on */ st_timecache_set(1); /* Install signal handlers */ sc_install_sighandlers(); #if 0 /* Load configuration from config files */ sc_load_configs(&Sc_ctx); #endif /* Start all threads */ sc_start_threads(&Sc_ctx); /* Become a signal processing thread */ sc_process_signals(&Sc_ctx); /* NOTREACHED */ return 1; } /* ** SC_USAGE -- sc_usage ** ** Parameters: ** progname -- program name ** ** Returns: ** none (doesn't return) */ static void sc_usage(const char *progname) { sm_io_fprintf(smioerr, "Usage: %s [options]\n" "%s SMTP/LMTP client\n" "Possible options:\n" "-D Run in background\n" "-d n Debug level\n" "-f name Name of configuration file [none]\n" "-h Print this message\n" #if SM_HEAP_CHECK "-H n Set heap check level\n" #endif "-i id Set id\n" "-L socket Socket location for LMTP [%s]\n" "-N name Name of smtpc section in configuration file to use []\n" "-O timeout I/O timeout [%d]\n" "-p num_processes Create specified number of processes\n" "-P port Connect to port [%d]\n" "-S C=name Directory for CDB [%s]\n" "-S q=name Socket for communication with QMGR [%s]\n" "-t min_thr:max_thr Specify thread limits\n" "-v loglevel Set loglevel\n" "-V Show version\n" "-w timeout Time to wait for QMGR to be ready\n" , progname , MTA_VERSION_STR , LMTPSOCK /* -L */ , SC_IO_TIMEOUT /* -O */ , SMTPC_PORT /* -P */ , Sc_ctx.scc_cnf.sc_cnf_cdb_base /* -S C */ , Sc_ctx.scc_cnf.sc_cnf_smtpcsock /* -S q */ ); exit(EX_USAGE); } /* ** SC_PARSE_ARGUMENTS -- parse arguments ** ** Parameters: ** sc_ctx -- SMTPC context ** argc -- number of arguments ** argv -- vector of arguments ** ** Returns: ** none. */ #if SM_SMTPC_HDRMOD_TEST sm_cstr_P Hdr_pre = NULL; sm_cstr_P Hdr_app = NULL; #endif static void sc_parse_arguments(sc_ctx_P sc_ctx, int argc, char *argv[]) { extern char *optarg; int opt, h; uint u, showversion; char *c; #if SM_SMTPC_HDRMOD_TEST char hdr[256]; #endif showversion = 0; while ((opt = getopt(argc, argv, "Dd:F:f:H:hi:l:L:M:N:O:P:rS:T:t:Vv:w:")) != -1) { switch (opt) { case 'D': SC_SET_CFLAG(sc_ctx, SCC_CFL_BACKGROUND); break; case 'd': sc_ctx->scc_cnf.sc_cnf_debug = atoi(optarg); break; case 'F': sc_ctx->scc_cnf.sc_cnf_confflags = atoi(optarg); break; case 'f': c = strdup(optarg); if (NULL == c) { sc_err_quit(EX_OSERR, "sev=ERROR, func=sc_parse_arguments, argument=\"%@s\", strdup=%m" , optarg, sm_err_temp(errno)); } sc_ctx->scc_cnf.sc_cnf_conffile = c; h = sc_read_cnf(sc_ctx, c, &sc_ctx->scc_cnf.sc_cnf_smc); if (h != 0) sc_err_quit(EX_USAGE, "sev=ERROR, func=sc_parse_arguments, status=invalid_configuration_file, error=%m" , h); break; case 'H': #if SM_HEAP_CHECK SmHeapCheck = atoi(optarg); if (SmHeapCheck < 0) sc_err_quit(EX_USAGE, "sev=ERROR, func=sc_parse_arguments, SmHeapCheck=%s, status=invalid_value" , optarg); #endif /* SM_HEAP_CHECK */ break; case 'i': sc_ctx->scc_cnf.sc_cnf_id = strtol(optarg, &c, 10); break; case 'l': #if 0 /* currently unused */ sc_ctx->scc_cnf.sc_cnf_logdir = optarg; #endif break; #if SM_SMTPC_HDRMOD_TEST case 'M': if (optarg == NULL || (optarg[0] != 'p' && optarg[0] != 'a') || optarg[1] != '=') { sc_usage(argv[0]); break; } strlcpy(hdr, optarg + 2, sizeof(hdr)); strlcat(hdr, "\r\n", sizeof(hdr)); if (optarg[0] == 'p') Hdr_pre = sm_cstr_scpyn((const uchar *)hdr, strlen(hdr)); else Hdr_app = sm_cstr_scpyn((const uchar *)hdr, strlen(hdr)); break; #endif /* SM_SMTPC_HDRMOD_TEST */ case 'L': c = strdup(optarg); if (NULL == c) sc_err_quit(EX_OSERR, "sev=ERROR, func=sc_parse_arguments, argument=\"%@s\", strdup=%m" , optarg, sm_err_temp(errno)); sc_ctx->scc_cnf.sc_cnf_lmtpsock = c; break; case 'N': if (sc_ctx->scc_cnf.sc_cnf_conffile != NULL) sc_err_quit(EX_USAGE, "sev=ERROR, func=sc_parse_arguments, -N must be before -f"); c = strdup(optarg); if (NULL == c) { sc_err_quit(EX_OSERR, "sev=ERROR, func=sc_parse_arguments, argument=\"%@s\", strdup=%m" , optarg, sm_err_temp(errno)); } sc_ctx->scc_cnf.sc_cnf_section = c; break; case 'O': h = atoi(optarg); if (h <= 0) sc_err_quit(EX_USAGE, "sev=ERROR, func=sc_parse_arguments, timeout=%@s, status=invalid_value" , optarg); sc_ctx->scc_cnf.sc_cnf_timeout = (sm_intvl_T) h; break; case 'P': sc_ctx->scc_cnf.sc_cnf_port = atoi(optarg); break; case 'r': #if 0 rpools = true; #endif break; case 'S': if (optarg != NULL && ISALPHA(optarg[0]) && optarg[1] == '=' && optarg[2] != '\0') h = 2; else { sc_usage(argv[0]); /* NOTREACHED */ break; /* shut up stupid compiler */ } c = NULL; switch (optarg[0]) { case 'C': case 'q': c = strdup(optarg + h); if (NULL == c) { sc_err_quit(EX_OSERR, "sev=ERROR, func=sc_parse_arguments, argument=\"%@s\", strdup=%m" , optarg + h , sm_err_temp(errno)); } break; default: sc_usage(argv[0]); break; } switch (optarg[0]) { case 'C': sc_ctx->scc_cnf.sc_cnf_cdb_base = c; break; case 'q': sc_ctx->scc_cnf.sc_cnf_smtpcsock = c; break; } c = NULL; break; case 'T': #if SC_TEST u = strtoul(optarg, &c, 0); sc_ctx->scc_cnf.sc_cnf_tests = u; #endif break; case 't': u = strtoul(optarg, &c, 10); if (*c++ == ':') SC_MAX_THREADS(sc_ctx) = strtoul(c, NULL, 10); if (u <= 0 || SC_MAX_THREADS(sc_ctx) <= 0) sc_err_quit(EX_USAGE, "sev=ERROR, func=sc_parse_arguments, threads=%s, status=invalid_value" , optarg); SC_MAX_WAIT_THREADS(sc_ctx) = u; break; case 'v': sc_ctx->scc_cnf.sc_cnf_loglevel = (uint) atoi(optarg); break; case 'V': ++showversion; break; case 'w': sc_ctx->scc_cnf.sc_cnf_wait4srv = (uint) atoi(optarg); break; case 'h': case '?': default: sc_usage(argv[0]); } } if (showversion > 0) { (void) sc_showversion(sc_ctx, argv[0], sc_ctx->scc_cnf.sc_cnf_conffile, showversion); exit(0); } #if 0 if (sc_ctx->scc_cnf.sc_cnf_logdir == NULL && SC_IS_CFLAG(sc_ctx, SCC_CFL_BACKGROUND)) { sm_log_write(sc_ctx->scc_lctx, SC_LCAT_INIT, SC_LMOD_CONFIG, SM_LOG_ERR, 0, "sev=ERROR, func=sc_parse_arguments, status=logging_directory_is_required"); sc_usage(argv[0]); } #endif /* 0 */ /* Already checked at startup */ if (getuid() == 0 || geteuid() == 0) sc_err_quit(EX_USAGE, "sev=ERROR, func=sc_parse_arguments, Sorry Dave, I can't do that."); } /* ** SC_START_PROCESSES -- start processes ** ** Parameters: ** sc_ctx -- SMTPC context ** ** Returns: ** none */ static void sc_start_processes(sc_ctx_P sc_ctx) { (void)sc_ctx; sc_index = 0; sc_pid = getpid(); return; } /* ** SC_INSTALL_SIGHANDLERS -- install sighandlers ** ** Parameters: ** none ** ** Returns: ** none */ static void sc_install_sighandlers(void) { sigset_t mask; int p[2]; /* Create signal pipe */ if (pipe(p) < 0) sc_err_quit(EX_OSERR, "sev=ERROR, func=sc_install_sighandlers, index=%d, pid=%d, pipe()=failed, error=%m" , sc_index, sc_pid, sm_err_temp(errno)); if ((sig_pipe[0] = st_netfd_open(p[0])) == NULL || (sig_pipe[1] = st_netfd_open(p[1])) == NULL) sc_err_quit(EX_OSERR, "sev=ERROR, func=sc_install_sighandlers, index=%d, pid=%d, st_netfd_open(pipe)=failed, error=%m" , sc_index, sc_pid, sm_err_temp(errno)); /* Install signal handlers */ sc_signal(SIGTERM, sc_child_sighandler); /* terminate */ sc_signal(SIGHUP, sc_child_sighandler); /* restart */ sc_signal(SIGUSR1, sc_child_sighandler); /* dump info */ sc_signal(SIGUSR2, sc_child_sighandler); /* dump info */ /* Unblock signals */ sigemptyset(&mask); sigaddset(&mask, SIGTERM); sigaddset(&mask, SIGHUP); sigaddset(&mask, SIGUSR1); sigaddset(&mask, SIGUSR2); sigprocmask(SIG_UNBLOCK, &mask, NULL); } /* ** SC_CHILD_SIGHANDLER -- signal handler for children ** ** Parameters: ** signo -- signal number ** ** Returns: ** none */ static void sc_child_sighandler(int signo) { int err, fd; err = errno; fd = st_netfd_fileno(sig_pipe[1]); /* write() is async-safe */ if (write(fd, &signo, sizeof(int)) != sizeof(int)) sc_err_quit(EX_OSERR, "sev=ERROR, func=sc_child_sighandler, index=%d, pid=%d, func=ss_child_sighandler, write=failed, error=%m" , sc_index, sc_pid, sm_err_temp(errno)); errno = err; } /* ** SC_PROCESS_SIGNALS -- signal processing thread. ** Note: this is not called by a signal handler, hence any function ** can be used here. ** ** Parameters: ** sc_ctx -- SMTPC context ** ** Returns: ** none */ static void sc_process_signals(sc_ctx_P sc_ctx) { int signo; sm_ret_T ret; for (;;) { /* Read the next signal from the signal pipe */ if (st_read(sig_pipe[0], &signo, sizeof(int), (st_utime_t)-1) != sizeof(int)) sc_err_quit(EX_OSERR, "sev=ERROR, func=sc_process_signals, index=%d, pid=%d, func=ss_process_signals, st_read()=failed, error=%m" , sc_index, sc_pid, sm_err_temp(errno)); switch (signo) { case SIGHUP: sm_log_write(sc_ctx->scc_lctx, SC_LCAT_INTERN, SC_LMOD_SIGNAL, SM_LOG_INFO, 9, "sev=INFO, func=sc_process_signals, index=%d, pid=%d, signal=SIGHUP, action=none" , sc_index, sc_pid); #if 0 sc_load_configs(&Sc_ctx); #endif break; case SIGTERM: /* "signal" shutdown to threads */ SC_SET_FLAG(&Sc_ctx, SCC_FL_SHUTDOWN); /* ** Terminate ungracefully since it is generally not ** known how long it will take to gracefully complete ** all client sessions. */ sm_log_write(sc_ctx->scc_lctx, SC_LCAT_INTERN, SC_LMOD_SIGNAL, SM_LOG_INFO, 9, "sev=INFO, func=sc_process_signals, index=%d, pid=%d, signal=SIGTERM, action=terminating" , sc_index, sc_pid); HEAP_REPORT; (void) sm_log_destroy(Sc_ctx.scc_lctx); exit(0); case SIGUSR1: sm_log_write(sc_ctx->scc_lctx, SC_LCAT_INTERN, SC_LMOD_SIGNAL, SM_LOG_INFO, 9, "sev=INFO, func=sc_process_signals, index=%d, pid=%d, signal=SIGUSR1, action=printinfo" , sc_index, sc_pid); sc_dump_info(&Sc_ctx); HEAP_REPORT; break; case SIGUSR2: /* Reopen log files - needed for log rotation */ ret = sm_log_reopen(Sc_ctx.scc_lctx); sm_log_write(sc_ctx->scc_lctx, SC_LCAT_INTERN, SC_LMOD_SIGNAL, SM_LOG_INFO, 9, "sev=INFO, func=sc_process_signals, index=%d, pid=%d, signal=SIGUSR2, action=reopened_logfiles" , sc_index, sc_pid); break; default: sm_log_write(sc_ctx->scc_lctx, SC_LCAT_INTERN, SC_LMOD_SIGNAL, SM_LOG_INFO, 8, "sev=INFO, func=sc_process_signals, index=%d, pid=%d, signal=%d, action=ignored" , sc_index, sc_pid, signo); } } /* NOTREACHED */ return; } /* ** SC_SIGNAL -- install signal handler ** ** Parameters: ** sig -- signal ** handler -- signal handler ** ** Returns: ** none */ static void sc_signal(int sig, void (*handler)(int)) { struct sigaction sa; sa.sa_handler = handler; sigemptyset(&sa.sa_mask); sa.sa_flags = 0; sigaction(sig, &sa, NULL); }