/* * Copyright (c) 2006-2017 Hypertriton, 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. * * 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 AUTHOR OR 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 "mailprocd.h" #include "pathnames.h" #include "ports.h" #include #include #include #include #include #include int mpdSoftFail, mpdMaxWorkers, mpdCleanupInterval; char *mpdHome, *mpdSafePath, *mpdSafeShell, *mpdSocketDir, *mpdPidFile; char *saUserConfDir, *saUserPrefs; int saSocketBacklog, saMaxProcMsgs, saMaxIdle, saLearning, saMaxSize; int smtpEnable, smtpListenBacklog, smtpMaxClients; char *smtpHost, *smtpPort; #ifdef HAVE_LMTP int lmtpEnable, lmtpListenBacklog, lmtpMaxClients; char *lmtpSockPath; static int lmtpSock; static Uint lmtpClientCount = 0; #endif #ifdef HAVE_POLICY int polEnable, polListenBacklog; static int polSock; #endif #ifdef HAVE_CONTROL int ctlEnable, ctlListenBacklog; static int ctlSock; #endif int mbdReportEnable; char *mbdHost, *mbdPort, *mbdPass; char *forceFilterDomains; float forceFilterThreshold; #ifdef HAVE_PERL PerlInterpreter *my_perl; #endif FILE *mpdLog = NULL; char mpdErrorMessage[MPD_ERROR_MAX]; static int smtpSocks[MPD_SMTP_MAXSOCKETS]; static int smtpSockCount=0; static TAILQ_HEAD(,smtp_session) smtpSessions; static Uint smtpClientCount=0; static volatile int SigDIE = 0; static volatile int SigRELOAD = 0; static volatile int SigCHLD = 0; static fd_set servfds; void MPD_Fatal(const char *fmt, ...) { char err[128]; va_list ap; va_start(ap, fmt); vsnprintf(err, sizeof(err), fmt, ap); va_end(ap); fputs(err, stderr); fputc('\n', stderr); abort(); } void * MPD_Malloc(size_t size) { void *p; if ((p = malloc(size)) == NULL) { Fatal("Out of memory"); } return (p); } #ifdef DEBUG void MPD_Debug(const char *fmt, ...) { static char msg[160]; va_list ap; va_start(ap, fmt); vsnprintf(msg, sizeof(msg), fmt, ap); va_end(ap); fprintf(mpdLog, "[%d] %s\n", getpid(), msg); fflush(mpdLog); } #endif /* DEBUG */ #ifdef HAVE_PERL static void xs_init (pTHX); EXTERN_C void boot_DynaLoader(pTHX_ CV *cv); EXTERN_C void xs_init(pTHX) { char *file = __FILE__; dXSUB_SYS; newXS("DynaLoader::boot_DynaLoader", boot_DynaLoader, file); } #endif /* HAVE_PERL */ static void ReportExit(const char *what, pid_t rpid, int code) { if (WIFSIGNALED(code)) { syslog(LOG_ERR, "[%d] %s exited (signal %d)", (int)rpid, what, WTERMSIG(code)); } else if (WIFEXITED(code) && WEXITSTATUS(code) != 0) { syslog(LOG_ERR, "[%d] %s exited due to failure (%d)", (int)rpid, what, WEXITSTATUS(code)); } } #ifdef HAVE_CONTROL static void ReapCTLSession(CTL_Session *ctls, int exitCode) { Debug("ReapCTLSession: #%d (pid %d)", ctlClientCount-1, (int)ctls->pid); TAILQ_REMOVE(&ctlSessions, ctls, sessions); free(ctls); if (ctlClientCount == 0) { syslog(LOG_ERR, "Bogus control session count!"); } else { ctlClientCount--; } ReportExit("ctlsession", ctls->pid, exitCode); } #endif /* HAVE_CONTROL */ static void ReapSMTPSession(SMTP_Session *smtps, int exitCode) { TAILQ_REMOVE(&smtpSessions, smtps, sessions); FD_CLR(smtps->pipe, &servfds); close(smtps->pipe); #ifdef HAVE_LMTP if (smtps->prot == LMTP_PROTOCOL) { if (lmtpClientCount == 0) { syslog(LOG_ERR, "Bogus LMTP client count!"); } else { lmtpClientCount--; } ReportExit("lmtpd", smtps->pid, exitCode); } else #endif { if (smtpClientCount == 0) { syslog(LOG_ERR, "Bogus SMTP client count!"); } else { smtpClientCount--; } ReportExit("smtpd", smtps->pid, exitCode); } free(smtps); } static void ReapWorker(QMGR_Worker *worker, int exitCode) { #if 0 Debug("ReapWorker: #%d (pid %d)", qmgrWorkerCount-1, (int)worker->pid); #endif TAILQ_REMOVE(&qmgrWorkers, worker, workers); close(worker->pipe); free(worker); if (qmgrWorkerCount == 0) { syslog(LOG_ERR, "Bad qmgrWorkerCount"); } else { qmgrWorkerCount--; } ReportExit("worker", worker->pid, exitCode); } static void MAIN_CheckCHLD(void) { int save_errno = errno; int rpid, code; do { rpid = waitpid(-1, &code, WNOHANG); if (polProc != 0 && rpid == polProc) { ReportExit("policy", rpid, code); polProc = 0; } else if (rpid > 0) { SMTP_Session *sSMTP; QMGR_Worker *worker; #ifdef HAVE_CONTROL CTL_Session *sCtl; TAILQ_FOREACH(sCtl, &ctlSessions, sessions) { if (rpid != sCtl->pid) { continue; } ReapCTLSession(sCtl, code); break; } #endif TAILQ_FOREACH(sSMTP, &smtpSessions, sessions) { if (rpid == sSMTP->pid) { ReapSMTPSession(sSMTP, code); break; } } if (sSMTP == NULL) { TAILQ_FOREACH(worker, &qmgrWorkers, workers) { if (rpid == worker->pid) { ReapWorker(worker, code); break; } } if (worker == NULL) ReportExit("mailprocd", rpid, code); } } } while (rpid > 0 || (rpid == -1 && errno == EINTR)); errno = save_errno; } static void child_sig_die(int sigraised) { _exit(0); } static void MAIN_SigTERM(int sigraised) { SigDIE = 1; } static void MAIN_SigCHLD(int sigraised) { SigCHLD = 1; } static void MAIN_SigRELOAD(int sigraised) { SigRELOAD = 1; } static void CloseFiles(void) { SMTP_Session *smtps; int i; TAILQ_FOREACH(smtps, &smtpSessions, sessions) { close(smtps->pipe); } #ifdef HAVE_POLICY if (polEnable) close(polSock); #endif #ifdef HAVE_CONTROL if (ctlEnable) close(ctlSock); #endif if (smtpEnable) { for (i = 0; i < smtpSockCount; i++) close(smtpSocks[i]); } #ifdef HAVE_LMTP if (lmtpEnable) close(lmtpSock); #endif } void MPD_EnterServerProc(const char *name) { struct sigaction sa; CloseFiles(); sigemptyset(&sa.sa_mask); sa.sa_handler = SIG_DFL; sa.sa_flags = SA_RESTART; sigaction(SIGCHLD, &sa, NULL); sa.sa_handler = SIG_IGN; sigaction(SIGHUP, &sa, NULL); sigaction(SIGPIPE, &sa, NULL); sigfillset(&sa.sa_mask); sa.sa_flags = 0; sa.sa_handler = child_sig_die; sigaction(SIGTERM, &sa, NULL); sigaction(SIGUSR1, &sa, NULL); closelog(); openlog(name, LOG_PID|LOG_NDELAY, LOG_LOCAL0); Setproctitle("%s", name); } void MPD_ExitServerProc(int code) { closelog(); exit(code); } /* (Re)open the log file */ static void MPD_OpenLog(void) { int fd; if (mpdLog != NULL) { fclose(mpdLog); } if ((mpdLog = fopen(_PATH_LOG_FILE, "a")) == NULL) { syslog(LOG_ERR, "%s: %m", _PATH_LOG_FILE); fprintf(stderr, "%s: %s\n", _PATH_LOG_FILE, strerror(errno)); exit(1); } fd = fileno(mpdLog); fchmod(fd, 0640); fchown(fd, 0, 20); } static int MAIN_CheckSignals(void) { if (SigCHLD) { SigCHLD = 0; MAIN_CheckCHLD(); } if (SigDIE) { SigDIE = 0; if (kill(0, SIGTERM) == -1) { syslog(LOG_ERR, "SigDIE: %m"); } MPD_SetError("Master exiting (signal)"); return (1); } if (SigRELOAD) { SigRELOAD = 0; LOCAL_ReloadDatabases(); MPD_OpenLog(); } return (0); } #ifdef HAVE_PERL static void InitPerl(int argc, char **argv, char **env) { char initPath[FILENAME_MAX]; char *myArgv[2] = { "", initPath }; Strlcpy(initPath, SHAREDIR, sizeof(initPath)); Strlcat(initPath, "/init.pl", sizeof(initPath)); PERL_SYS_INIT3(&argc, &argv, &env); my_perl = perl_alloc(); perl_construct(my_perl); perl_parse(my_perl, xs_init, 2, myArgv, (char **)NULL); PL_exit_flags |= PERL_EXIT_DESTRUCT_END; perl_run(my_perl); } #endif /* HAVE_PERL */ /* * Cleanup routine. Wake workers for any users who happen to have a non-empty active queue. */ static int QMGR_Cleanup(void) { char pathBase[FILENAME_MAX], path[FILENAME_MAX]; struct dirent *dpTop, *dp; DIR *dirTop, *dirBase; /* Debug("Cleanup: %s", pathQueue); */ if ((dirTop = opendir(pathQueue)) == NULL) { MPD_SetError("%s: %s", pathQueue, strerror(errno)); return (-1); } while ((dpTop = readdir(dirTop)) != NULL) { if (!isdigit(dpTop->d_name[0])) { continue; } Strlcpy(pathBase, pathQueue, sizeof(pathBase)); Strlcat(pathBase, dpTop->d_name, sizeof(pathBase)); Strlcat(pathBase, "/", sizeof(pathBase)); if ((dirBase = opendir(pathBase)) == NULL) { syslog(LOG_WARNING, "%s: %s", pathBase, strerror(errno)); continue; } while ((dp = readdir(dirBase)) != NULL) { QMGR_MetaData meta; ssize_t rv, nRead; int fd; if (dp->d_name[0] == '.') { continue; } Strlcpy(path, pathBase, sizeof(path)); Strlcat(path, dp->d_name, sizeof(path)); try_open: if ((fd = open(path, O_RDONLY)) == -1) { if (errno == EINTR) { if (QMGR_CheckSignals()) { closedir(dirBase); goto out; } goto try_open; } else { syslog(LOG_ERR, "%s: %s", path, strerror(errno)); continue; } } for (nRead = 0; nRead < sizeof(meta); ) { rv = read(fd, ((void *)&meta)+nRead, sizeof(meta)-nRead); if (rv == -1) { if (errno == EINTR || errno == EAGAIN) { if (MAIN_CheckSignals()) { close(fd); goto out; } continue; } else { syslog(LOG_ERR, "Cleanup read: %s", strerror(errno)); close(fd); break; } } else if (rv == 0) { break; } nRead += rv; } close(fd); if (nRead < sizeof(meta)) continue; Debug("[CLEANUP] %u:%u Q=<%s> From=<%s> Rcpt=<%s> IP=<%s>", meta.uid, meta.gid, meta.qid, meta.from, meta.rcpt, meta.ip); if (QMGR_WakeWorker(&meta) == -1) { syslog(LOG_ERR, "Cleanup-Worker(%s): %s", meta.qid, MPD_GetError()); Debug("Cleanup-Worker(%s): %s", meta.qid, MPD_GetError()); } break; } closedir(dirBase); } out: closedir(dirTop); return (0); } int main(int argc, char **argv, char **env) { struct addrinfo hints, *res, *res0; const char *cause = NULL; int rv, i, maxfd = 0; struct sigaction sa; my_socklen_t socklen; struct stat sb; FILE *f; struct sockaddr_un unaddr, paddr; struct passwd *pwd; pid_t pid; int pp[2]; SMTP_Session *smtps; CFG_File *cf; if ((cf = CFG_OpenFile(SYSCONFDIR, _PATH_CONFIG_FILE)) == NULL) { fprintf(stderr, "Config file: %s\n", MPD_GetError()); exit(1); } #ifdef HAVE_PERL InitPerl(argc, argv, env); #endif MPD_OpenLog(); #ifdef HAVE_SA if (SA_GetVersion(saVersion, sizeof(saVersion)) == -1) { Fatal("Cannot get SA version"); } Debug("mailprocd %s (SpamAssassin %s)", VERSION, saVersion); syslog(LOG_INFO, "Server startup (%s with SpamAssassin %s)", VERSION, saVersion); SA_CompileNow(0, 0); #else Debug("mailprocd %s", VERSION); syslog(LOG_INFO, "Server startup (%s)", VERSION); #endif TAILQ_INIT(&smtpSessions); TAILQ_INIT(&qmgrWorkers); qmgrWorkerCount = 0; CFG_GetInt(cf, "soft-fail", &mpdSoftFail, 0); CFG_GetInt(cf, "max-workers", &mpdMaxWorkers, 50); CFG_GetInt(cf, "cleanup-interval", &mpdCleanupInterval, 30); CFG_GetStr(cf, "home", &mpdHome, _PATH_HOME); CFG_GetStr(cf, "socket-dir", &mpdSocketDir, _PATH_SOCKETDIR); CFG_GetStr(cf, "pid-file", &mpdPidFile, _PATH_PID); CFG_GetStr(cf, "safe-path", &mpdSafePath, _SAFE_PATH); CFG_GetStr(cf, "safe-shell", &mpdSafeShell, _SAFE_SHELL); CFG_GetStr(cf, "sa.user-conf-dir", &saUserConfDir, _PATH_SACONF); CFG_GetStr(cf, "sa.user-prefs", &saUserPrefs, _PATH_SAUSERPREFS); CFG_GetInt(cf, "sa.socket-backlog", &saSocketBacklog, 20); CFG_GetInt(cf, "sa.max-proc-msgs", &saMaxProcMsgs, 100); CFG_GetInt(cf, "sa.max-idle", &saMaxIdle, 60); CFG_GetInt(cf, "sa.learning", &saLearning, 1); CFG_GetInt(cf, "sa.max-size", &saMaxSize, 65536); CFG_GetInt(cf, "smtp.enable", &smtpEnable, 1); CFG_GetStr(cf, "smtp.host", &smtpHost, "localhost"); CFG_GetStr(cf, "smtp.port", &smtpPort, _PORT_MAILPROCD); CFG_GetInt(cf, "smtp.listen-backlog", &smtpListenBacklog, 10); CFG_GetInt(cf, "smtp.max-clients", &smtpMaxClients, 100); #ifdef HAVE_LMTP CFG_GetInt(cf, "lmtp.enable", &lmtpEnable, 1); CFG_GetInt(cf, "lmtp.listen-backlog", &lmtpListenBacklog, 10); CFG_GetStr(cf, "lmtp.socket-path", &lmtpSockPath, _PATH_LMTP_SOCKET); CFG_GetInt(cf, "lmtp.max-clients", &lmtpMaxClients, 100); #endif CFG_GetInt(cf, "pol.enable", &polEnable, 0); CFG_GetInt(cf, "pol.listen-backlog", &polListenBacklog, 10); CFG_GetInt(cf, "ctl.enable", &ctlEnable, 0); CFG_GetInt(cf, "ctl.listen-backlog", &ctlListenBacklog, 10); CFG_GetInt(cf, "sa.mbd-report", &mbdReportEnable, 0); CFG_GetStr(cf, "sa.mbd-host", &mbdHost, "localhost"); CFG_GetStr(cf, "sa.mbd-port", &mbdPort, "925"); CFG_GetStr(cf, "sa.mbd-pass", &mbdPass, "secret"); CFG_GetStr(cf, "sa.force-filter-domains", &forceFilterDomains, ""); CFG_GetFlt(cf, "sa.force-filter-threshold", &forceFilterThreshold, 6.0); /* * Initialization of the subsystems */ if (stat(mpdHome, &sb) != 0 && mkdir(mpdHome, 0755) == -1) { MPD_SetError("mkdir %s: %s", mpdHome, strerror(errno)); goto fatal; } if (stat(mpdSocketDir, &sb) != 0 && mkdir(mpdSocketDir, 0755) == -1) { MPD_SetError("mkdir %s: %s", mpdSocketDir, strerror(errno)); goto fatal; } if (LOCAL_Init(cf) == -1) { CFG_CloseFile(cf); goto fatal; } if (QMGR_Init(cf) == -1) { LOCAL_Destroy(); CFG_CloseFile(cf); goto fatal; } POL_Init(cf); #ifdef HAVE_CONTROL CTL_Init(cf); #endif ML_Init(cf); CFG_CloseFile(cf); FD_ZERO(&servfds); #ifdef HAVE_LMTP /* * Set up the LMTP listening socket. */ if (lmtpEnable) { if ((lmtpSock = socket(AF_UNIX, SOCK_STREAM, 0)) == -1) { syslog(LOG_ERR, "socket(AF_UNIX): %m"); goto out; } unaddr.sun_family = AF_UNIX; Strlcpy(unaddr.sun_path, lmtpSockPath, sizeof(unaddr.sun_path)); unlink(unaddr.sun_path); socklen = SUN_LEN(&unaddr); if (bind(lmtpSock, (struct sockaddr *)&unaddr, socklen) == -1 || listen(lmtpSock, lmtpListenBacklog) == -1) { syslog(LOG_ERR, "%s: %m", unaddr.sun_path); goto out; } chmod(unaddr.sun_path, 0777); FD_SET(lmtpSock, &servfds); if (lmtpSock > maxfd) { maxfd = lmtpSock; } } #endif /* HAVE_LMTP */ /* * Set up the SMTP server listening socket(s). */ if (smtpEnable) { memset(&hints, 0, sizeof(hints)); hints.ai_family = PF_UNSPEC; hints.ai_socktype = SOCK_STREAM; hints.ai_flags = AI_PASSIVE; if ((rv = getaddrinfo(smtpHost, smtpPort, &hints, &res0)) != 0) { syslog(LOG_ERR, "%s:%s: %s", smtpHost, smtpPort, gai_strerror(rv)); goto out; } for (smtpSockCount = 0, res = res0; res != NULL && smtpSockCount < MPD_SMTP_MAXSOCKETS; res = res->ai_next) { rv = socket(res->ai_family, res->ai_socktype, res->ai_protocol); if (rv == -1) { cause = "socket"; continue; } i = 1; setsockopt(rv, SOL_SOCKET, SO_REUSEADDR, &i, (my_socklen_t)sizeof(i)); if (bind(rv, res->ai_addr, res->ai_addrlen) == -1) { cause = "bind"; close(rv); continue; } if (listen(rv, smtpListenBacklog) == -1) { cause = "listen"; close(rv); continue; } smtpSocks[smtpSockCount++] = rv; FD_SET(rv, &servfds); if (rv > maxfd) { maxfd = rv; } } if (smtpSockCount == 0) { syslog(LOG_ERR, "%s: %m", cause); goto out; } freeaddrinfo(res0); } #ifdef HAVE_CONTROL if (ctlEnable) { if ((ctlSock = socket(AF_UNIX, SOCK_STREAM, 0)) == -1) { syslog(LOG_ERR, "socket(AF_UNIX): %m"); goto out; } unaddr.sun_family = AF_UNIX; Strlcpy(unaddr.sun_path, ctlSockPath, sizeof(unaddr.sun_path)); unlink(unaddr.sun_path); socklen = SUN_LEN(&unaddr); if (bind(ctlSock, (struct sockaddr *)&unaddr, socklen) == -1 || listen(ctlSock, ctlListenBacklog) == -1) { syslog(LOG_ERR, "%s: %m", unaddr.sun_path); goto out; } chmod(unaddr.sun_path, 0777); FD_SET(ctlSock, &servfds); if (ctlSock > maxfd) { maxfd = ctlSock; } } #endif /* HAVE_CONTOL */ #ifdef HAVE_POLICY if (polEnable) { if ((polSock = socket(AF_UNIX, SOCK_STREAM, 0)) == -1) { syslog(LOG_ERR, "socket(AF_UNIX): %m"); goto out; } unaddr.sun_family = AF_UNIX; Strlcpy(unaddr.sun_path, polSockPath, sizeof(unaddr.sun_path)); unlink(unaddr.sun_path); socklen = SUN_LEN(&unaddr); if (bind(polSock, (struct sockaddr *)&unaddr, socklen) == -1 || listen(polSock, polListenBacklog) == -1) { syslog(LOG_ERR, "%s: %m", unaddr.sun_path); goto out; } if ((pwd = getpwnam(polOwner)) != NULL) { chown(unaddr.sun_path, pwd->pw_uid, pwd->pw_gid); chmod(unaddr.sun_path, 0700); } else { syslog(LOG_ERR, "Invalid policy owner: %s", polOwner); } FD_SET(polSock, &servfds); if (polSock > maxfd) { maxfd = polSock; } } #endif /* HAVE_POLICY */ sigemptyset(&sa.sa_mask); sa.sa_flags = 0; sa.sa_handler = MAIN_SigCHLD; sigaction(SIGCHLD, &sa, NULL); sa.sa_handler = MAIN_SigRELOAD; sigaction(SIGHUP, &sa, NULL); sa.sa_handler = SIG_IGN; sigaction(SIGUSR1, &sa, NULL); sigaction(SIGUSR2, &sa, NULL); sigfillset(&sa.sa_mask); sa.sa_handler = MAIN_SigTERM; sigaction(SIGTERM, &sa, NULL); /* Write the PID file */ if (stat(mpdPidFile, &sb) == 0) { Debug("WARNING: Overwriting existing PID file!"); syslog(LOG_WARNING, "Overwriting %s!", mpdPidFile); } if ((f = fopen(mpdPidFile, "w")) == NULL) { Debug("%s: %s", mpdPidFile, strerror(errno)); syslog(LOG_ERR, "%s: %m", mpdPidFile); goto out_shutdown; } fprintf(f, "%lu\n", (Ulong)getpid()); fclose(f); /* * Immediately process any entry left in the active queue due * to a previous crash or restart. */ if (QMGR_Cleanup() == -1) syslog(LOG_WARNING, "Cleanup: %s", MPD_GetError()); /* * Main loop. */ for (;;) { fd_set rfds = servfds; struct timeval tv; int nEvents = 0; tv.tv_sec = mpdCleanupInterval; tv.tv_usec = 0; rv = select(maxfd+1, &rfds, NULL, NULL, &tv); if (rv == -1) { if (errno == EINTR) { if (MAIN_CheckSignals()) { break; } continue; } else { syslog(LOG_ERR, "select: %m"); exit(1); } } #ifdef HAVE_CONTROL if (ctlEnable && FD_ISSET(ctlSock, &rfds)) { socklen = sizeof(paddr); rv = accept(ctlSock, (struct sockaddr *)&paddr, &socklen); if (rv == -1) { if (errno == EINTR || errno == EAGAIN) { if (MAIN_CheckSignals()) { break; } continue; } else { syslog(LOG_ERR, "accept(CTL): %m"); exit(1); } } else { CTL_ForkInstance(rv); close(rv); } nEvents++; } #endif /* HAVE_CONTROL */ if (polEnable && FD_ISSET(polSock, &rfds)) { socklen = sizeof(paddr); rv = accept(polSock, (struct sockaddr *)&paddr, &socklen); if (rv == -1) { if (errno == EINTR || errno == EAGAIN) { if (MAIN_CheckSignals()) { break; } continue; } else { syslog(LOG_ERR, "accept(POL): %m"); exit(1); } } else { POL_ForkInstance(rv); close(rv); } nEvents++; } #ifdef HAVE_LMTP if (lmtpEnable && FD_ISSET(lmtpSock, &rfds)) { if (lmtpClientCount+1 > lmtpMaxClients) { syslog(LOG_ERR, "Too many LMTP clients!"); nEvents++; continue; } socklen = sizeof(paddr); rv = accept(lmtpSock, (struct sockaddr *)&paddr, &socklen); if (rv == -1) { if (errno == EINTR || errno == EAGAIN) { if (MAIN_CheckSignals()) { break; } continue; } else { syslog(LOG_ERR, "accept(LMTP): %m"); exit(1); } } if (pipe(pp) == -1) { syslog(LOG_ERR, "pipe(LMTP): %m"); close(rv); continue; } if ((pid = fork()) == -1) { syslog(LOG_ERR, "fork(LMTP): %m"); close(rv); close(pp[0]); close(pp[1]); continue; } else if (pid == 0) { /* Child */ dup2(rv, STDIN_FILENO); dup2(rv, STDOUT_FILENO); close(pp[0]); smtpMasterPipe = pp[1]; MPD_EnterServerProc("lmtp"); if (SMTP_Main(LMTP_PROTOCOL) == -1) { syslog(LOG_ERR, "LMTP session: %s", MPD_GetError()); } MPD_ExitServerProc(0); } else { smtps = Malloc(sizeof(SMTP_Session)); smtps->prot = LMTP_PROTOCOL; smtps->pid = pid; smtps->pipe = pp[0]; FD_SET(smtps->pipe, &servfds); if (smtps->pipe > maxfd) { maxfd = smtps->pipe; } TAILQ_INSERT_TAIL(&smtpSessions, smtps, sessions); lmtpClientCount++; close(pp[1]); close(rv); nEvents++; } } #endif /* HAVE_LMTP */ /* Process SMTP Connections */ for (i = 0; i < smtpSockCount; i++) { if (!FD_ISSET(smtpSocks[i], &rfds)) { continue; } if (smtpClientCount+1 > smtpMaxClients) { syslog(LOG_ERR, "Too many SMTP clients (%d+1 > %d)", smtpClientCount, smtpMaxClients); nEvents++; continue; } socklen = sizeof(paddr); rv = accept(smtpSocks[i], (struct sockaddr *)&paddr, &socklen); if (rv == -1) { if (errno == EINTR || errno == EAGAIN) { if (MAIN_CheckSignals()) { goto out; } continue; } else { syslog(LOG_ERR, "accept(SMTP): %m"); exit(1); } } if (pipe(pp) == -1) { syslog(LOG_ERR, "pipe(SMTP): %m"); close(rv); continue; } if ((pid = fork()) == -1) { syslog(LOG_ERR, "fork(SMTP): %m"); close(rv); close(pp[0]); close(pp[1]); continue; } else if (pid == 0) { /* Child */ dup2(rv, STDIN_FILENO); dup2(rv, STDOUT_FILENO); close(pp[0]); smtpMasterPipe = pp[1]; MPD_EnterServerProc("smtp"); if (SMTP_Main(ESMTP_PROTOCOL) == -1) { syslog(LOG_ERR, "SMTP session: %s", MPD_GetError()); } MPD_ExitServerProc(0); } else { smtps = Malloc(sizeof(SMTP_Session)); smtps->prot = ESMTP_PROTOCOL; smtps->pid = pid; smtps->pipe = pp[0]; FD_SET(smtps->pipe, &servfds); if (smtps->pipe > maxfd) { maxfd = smtps->pipe; } TAILQ_INSERT_TAIL(&smtpSessions, smtps, sessions); smtpClientCount++; close(pp[1]); close(rv); nEvents++; } } /* Process incoming data on listening SMTP sockets */ TAILQ_FOREACH(smtps, &smtpSessions, sessions) { QMGR_MetaData meta; ssize_t nRead, rvMeta; if (!FD_ISSET(smtps->pipe, &rfds)) { continue; } for (nRead = 0; nRead < sizeof(meta); ) { rvMeta = read(smtps->pipe, ((void *)&meta)+nRead, sizeof(meta)-nRead); if (rvMeta == -1) { if (errno == EINTR || errno == EAGAIN) { if (MAIN_CheckSignals()) { goto out_shutdown; } continue; } else { syslog(LOG_ERR, "%s; shutdown", MPD_GetError()); goto out_shutdown; } } else if (rvMeta == 0) { break; } nRead += rvMeta; } if (nRead < sizeof(QMGR_MetaData)) { /* EOF */ nEvents++; continue; } Debug("[SMTP] %d:%d Q=<%s> From=<%s> Rcpt=<%s> IP=<%s>", meta.uid, meta.gid, meta.qid, meta.from, meta.rcpt, meta.ip); if (QMGR_WakeWorker(&meta) == -1) { syslog(LOG_ERR, "Worker(%s): %s", meta.qid, MPD_GetError()); } nEvents++; } if (MAIN_CheckSignals()) break; if (nEvents == 0) { /* Timeout */ if (QMGR_Cleanup() != 0) syslog(LOG_ERR, "cleanup(0): %s", MPD_GetError()); } } out_shutdown: syslog(LOG_NOTICE, "Server shutdown (signal)"); CloseFiles(); ML_Destroy(); #ifdef HAVE_CONTROL CTL_Destroy(); #endif POL_Destroy(); LOCAL_Destroy(); #ifdef HAVE_SA SPAM_Destroy(); #endif unlink(mpdPidFile); out: #ifdef HAVE_PERL perl_destruct(my_perl); perl_free(my_perl); PERL_SYS_TERM(); #endif return (0); fatal: fprintf(stderr, "FATAL: %s\n", MPD_GetError()); syslog(LOG_CRIT, "FATAL: %s", MPD_GetError()); return (1); }