/*
* 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);
}