/*
* Copyright (c) 2006-2008 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 "mailprocd.h"
#include "pathnames.h"
#include "ports.h"
#include
#include
#include
int mpdSoftFail;
char *mpdHome, *mpdSafePath, *mpdSafeShell, *mpdSocketDir, *mpdPidFile;
char *saUserDir, *saUserConfDir, *saUserPrefs;
int saSocketBacklog, saMaxProcMsgs, saMaxIdle, saLearning, saMaxSize;
int smtpEnable, smtpListenBacklog, smtpMaxClients;
char *smtpHost, *smtpPort;
int lmtpEnable, lmtpListenBacklog, lmtpMaxClients;
char *lmtpSockPath;
int polEnable, polListenBacklog;
int ctlEnable, ctlListenBacklog;
int mbdReportEnable;
char *mbdHost, *mbdPort, *mbdPass;
char *forceFilterDomains;
float forceFilterThreshold;
FILE *sfLog = NULL;
#ifdef HAVE_PERL
static PerlInterpreter *sfPerl;
#endif
static char sfError[1024];
static int smtpSocks[4], lmtpSock, polSock, ctlSock;
static int smtpSockCount;
static TAILQ_HEAD(,smtp_session) smtpSessions;
static u_int smtpClientCount = 0, lmtpClientCount = 0;
static TAILQ_HEAD(,qmgr_worker) qmgrWorkers;
static u_int qmgrWorkersCount = 0;
static volatile int die_flag = 0;
static volatile int reload_flag = 0;
static volatile int chld_flag = 0;
static fd_set servfds;
void
SF_SetError(const char *fmt, ...)
{
char errBuf[1024];
va_list ap;
va_start(ap, fmt);
vsnprintf(errBuf, sizeof(errBuf), fmt, ap);
va_end(ap);
strlcpy(sfError, errBuf, sizeof(sfError));
}
char *
SF_GetError(void)
{
return (sfError);
}
void
SF_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);
exit(1);
}
void *
SF_Malloc(size_t size)
{
void *p;
if ((p = malloc(size)) == NULL) {
Fatal("Out of memory");
}
return (p);
}
#ifdef DEBUG
void
SF_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(sfLog, "[%d] %s\n", getpid(), msg);
fflush(sfLog);
}
#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));
}
}
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);
}
static void
ReapSMTPSession(SMTP_Session *smtps, int exitCode)
{
TAILQ_REMOVE(&smtpSessions, smtps, sessions);
FD_CLR(smtps->pipe, &servfds);
close(smtps->pipe);
free(smtps);
switch (smtps->prot) {
case ESMTP_PROTOCOL:
if (smtpClientCount == 0) {
syslog(LOG_ERR, "Bogus SMTP client count!");
} else {
smtpClientCount--;
}
ReportExit("smtpd", smtps->pid, exitCode);
break;
case LMTP_PROTOCOL:
if (lmtpClientCount == 0) {
syslog(LOG_ERR, "Bogus LMTP client count!");
} else {
lmtpClientCount--;
}
ReportExit("lmtpd", smtps->pid, exitCode);
break;
}
}
static void
ReapWorker(QMGR_Worker *worker, int exitCode)
{
#if 0
Debug("ReapWorker: #%d (pid %d)", qmgrWorkersCount-1,
(int)worker->pid);
#endif
TAILQ_REMOVE(&qmgrWorkers, worker, workers);
close(worker->pipe);
free(worker);
if (qmgrWorkersCount == 0) {
syslog(LOG_ERR, "Bogus SMTP session count!");
} else {
qmgrWorkersCount--;
}
ReportExit("worker", worker->pid, exitCode);
}
static void
Reap(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 (qmgrProc != 0 && rpid == qmgrProc) {
ReportExit("qmgr", rpid, code);
qmgrProc = 0;
} else if (rpid > 0) {
CTL_Session *sCtl;
SMTP_Session *sSMTP;
QMGR_Worker *worker;
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) {
TAILQ_FOREACH(sCtl, &ctlSessions,
sessions) {
if (rpid == sCtl->pid) {
ReapCTLSession(sCtl,
code);
break;
}
}
if (sCtl == 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 master_sig_die(int sigraised) { die_flag = 1; }
static void master_sig_reload(int sigraised) { reload_flag = 1; }
static void master_sig_chld(int sigraised) { chld_flag = 1; }
static void
CloseFiles(void)
{
SMTP_Session *smtps;
int i;
TAILQ_FOREACH(smtps, &smtpSessions, sessions) {
close(smtps->pipe);
}
if (polEnable) {
close(polSock);
}
if (ctlEnable) {
close(ctlSock);
}
if (smtpEnable) {
for (i = 0; i < smtpSockCount; i++)
close(smtpSocks[i]);
}
if (lmtpEnable) {
close(lmtpSock);
}
}
void
SF_EnterServerProc(const char *name)
{
struct sigaction sa;
CloseFiles();
sigemptyset(&sa.sa_mask);
sa.sa_flags = SA_RESTART;
sa.sa_handler = SIG_DFL;
sigaction(SIGCHLD, &sa, NULL);
sa.sa_handler = SIG_IGN;
sigaction(SIGURG, &sa, NULL);
sigaction(SIGHUP, &sa, NULL);
sigaction(SIGPIPE, &sa, NULL);
sigfillset(&sa.sa_mask);
sa.sa_flags = SA_RESTART;
sa.sa_handler = child_sig_die;
sigaction(SIGINT, &sa, NULL);
sigaction(SIGQUIT, &sa, NULL);
sigaction(SIGTERM, &sa, NULL);
sigaction(SIGUSR1, &sa, NULL);
closelog();
openlog(name, LOG_PID|LOG_NDELAY, LOG_LOCAL0);
Setproctitle("%s", name);
}
void
SF_ExitServerProc(int code)
{
closelog();
exit(code);
}
static void
SF_OpenLog(void)
{
int fd;
if (sfLog != NULL) {
fclose(sfLog);
}
if ((sfLog = 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(sfLog);
fchmod(fd, 0640);
fchown(fd, 0, 20);
}
static __inline__ int
ProcessSignals(void)
{
if (die_flag) {
die_flag = 0;
if (kill(0, SIGUSR1) == -1) {
syslog(LOG_ERR, "USR1: %m");
}
return (1);
}
if (chld_flag) {
chld_flag = 0;
Reap();
}
if (reload_flag) {
reload_flag = 0;
LOCAL_ReloadDatabases();
SF_OpenLog();
}
return (0);
}
/*
* Process a notification originating from a smtpd process. We simply
* forward the notification to the worker process based on the UID, and
* fork a new worker process as necessary.
*/
static int
ProcessQueueNotification(SMTP_Session *smtps, const QMGR_MetaData *meta)
{
QMGR_Worker *worker;
TAILQ_FOREACH(worker, &qmgrWorkers, workers) {
if (worker->uid == meta->uid &&
worker->gid == meta->gid)
break;
}
if (worker == NULL) {
int pp[2];
pid_t pid;
Debug("Creating new worker for %d:%d", meta->uid, meta->gid);
if (pipe(pp) == -1) {
syslog(LOG_ERR, "pipe(WORKER): %m");
return (-1);
}
if ((pid = fork()) == -1) {
syslog(LOG_ERR, "fork(WORKER): %m");
close(pp[0]);
close(pp[1]);
return (-1);
} else if (pid == 0) { /* Child */
dup2(pp[0], STDIN_FILENO);
close(pp[1]);
SF_EnterServerProc("mailprocd");
Setproctitle("-worker: %d", meta->uid);
/*
* NOTE: We could drop privileges here if we did not
* allow users to assign e-mail addresses with multiple
* recipients of possibly different UIDs.
*/
fcntl(STDIN_FILENO, F_SETOWN, getpid());
if (QMGR_WorkerMain(meta->uid) == -1) {
Debug("WORKER failed: %s", SF_GetError());
syslog(LOG_ERR, "WORKER: %s", SF_GetError());
}
SF_ExitServerProc(0);
} else {
worker = Malloc(sizeof(QMGR_Worker));
worker->pid = pid;
worker->uid = meta->uid;
worker->gid = meta->gid;
worker->pipe = pp[1];
close(pp[0]);
TAILQ_INSERT_TAIL(&qmgrWorkers, worker, workers);
qmgrWorkersCount++;
}
} else {
Debug("Found existing worker %d for %d:%d", worker->pid,
worker->uid, worker->gid);
if (worker->pid <= 1) {
syslog(LOG_ERR, "Bad worker pid!");
} else {
if (kill(worker->pid, SIGUSR2) == -1) {
syslog(LOG_ERR, "USR2->worker%d: %s",
(int)worker->pid,
strerror(errno));
}
}
}
return (0);
}
#ifdef HAVE_PERL
static void
InitPerl(int argc, char **argv, char **env)
{
char initPath[1024];
char *myArgv[2] = { "", initPath };
Strlcpy(initPath, SHAREDIR, sizeof(initPath));
Strlcat(initPath, "/init.pl", sizeof(initPath));
PERL_SYS_INIT3(&argc, &argv, &env);
sfPerl = perl_alloc();
perl_construct(sfPerl);
perl_parse(sfPerl, xs_init, 2, myArgv, (char **)NULL);
PL_exit_flags |= PERL_EXIT_DESTRUCT_END;
perl_run(sfPerl);
}
#endif /* HAVE_PERL */
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, "Load configuration file: %s\n", SF_GetError());
exit(1);
}
#ifdef HAVE_PERL
InitPerl(argc, argv, env);
#endif
SF_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);
smtpClientCount = 0;
lmtpClientCount = 0;
TAILQ_INIT(&qmgrWorkers);
qmgrWorkersCount = 0;
CFG_GetInt(cf, "soft-fail", &mpdSoftFail, 0);
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-dir", &saUserDir, _PATH_SAUSERDIR);
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);
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);
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) {
SF_SetError("mkdir %s: %s", mpdHome, strerror(errno));
goto fatal;
}
if (stat(mpdSocketDir, &sb) != 0 && mkdir(mpdSocketDir, 0755) == -1) {
SF_SetError("mkdir %s: %s", mpdSocketDir, strerror(errno));
goto fatal;
}
if (stat(saUserDir, &sb) != 0 && mkdir(saUserDir, 0755) == -1) {
SF_SetError("mkdir %s: %s", saUserDir, 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);
CTL_Init(cf);
ML_Init(cf);
CFG_CloseFile(cf);
FD_ZERO(&servfds);
/*
* 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; }
}
/*
* 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 < 4;
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);
}
/*
* Set up the Control Interface socket.
*/
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; }
}
/*
* Set up the Policy Server socket.
*/
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; }
}
sigemptyset(&sa.sa_mask);
sa.sa_handler = master_sig_chld;
sa.sa_flags = SA_RESTART;
sigaction(SIGCHLD, &sa, NULL);
sa.sa_handler = master_sig_reload;
sa.sa_flags = SA_RESTART;
sigaction(SIGHUP, &sa, NULL);
sa.sa_handler = SIG_IGN;
sigaction(SIGUSR1, &sa, NULL);
sigaction(SIGUSR2, &sa, NULL);
sa.sa_handler = master_sig_die;
sa.sa_flags = 0;
sigaction(SIGINT, &sa, NULL);
sigaction(SIGQUIT, &sa, NULL);
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_close;
}
fprintf(f, "%lu\n", (unsigned long)getpid());
fclose(f);
/*
* Immediately process any entry left in the active queue due
* to a previous crash or restart.
*/
if ((rv = QMGR_Todo()) > 0)
QMGR_Cleanup();
/*
* Main loop.
*/
for (;;) {
fd_set rfds = servfds;
rv = select(maxfd+1, &rfds, NULL, NULL, NULL);
if (rv == -1) {
if (errno == EINTR) {
if (ProcessSignals() == 1) {
goto out_close;
}
continue;
} else {
syslog(LOG_ERR, "select: %m; shutdown");
exit(1);
}
}
if (ctlEnable && FD_ISSET(ctlSock, &rfds)) {
socklen = sizeof(paddr);
rv = accept(ctlSock, (struct sockaddr *)&paddr,
&socklen);
if (rv != -1) {
CTL_ForkInstance(rv);
close(rv);
} else {
syslog(LOG_ERR, "accept(CTL): %m");
}
}
if (polEnable && FD_ISSET(polSock, &rfds)) {
socklen = sizeof(paddr);
rv = accept(polSock, (struct sockaddr *)&paddr,
&socklen);
if (rv != -1) {
POL_ForkInstance(rv);
close(rv);
} else {
syslog(LOG_ERR, "accept(POL): %m");
}
}
if (lmtpEnable && FD_ISSET(lmtpSock, &rfds)) {
if (lmtpClientCount+1 > lmtpMaxClients) {
syslog(LOG_ERR, "Too many LMTP clients!");
goto out_smtp;
}
socklen = sizeof(paddr);
rv = accept(lmtpSock, (struct sockaddr *)&paddr,
&socklen);
if (rv == -1) {
syslog(LOG_ERR, "accept(LMTP): %m");
goto out_lmtp;
}
if (pipe(pp) == -1) {
syslog(LOG_ERR, "pipe(LMTP): %m");
close(rv);
goto out_lmtp;
}
if ((pid = fork()) == -1) {
syslog(LOG_ERR, "fork(LMTP): %m");
close(rv);
close(pp[0]);
close(pp[1]);
goto out_lmtp;
} else if (pid == 0) { /* Child */
dup2(rv, STDIN_FILENO);
dup2(rv, STDOUT_FILENO);
close(pp[0]);
smtpMasterPipe = pp[1];
SF_EnterServerProc("mailprocd");
fcntl(fileno(stdin), F_SETOWN, getpid());
setvbuf(stdout, NULL, _IOFBF, 0);
if (SMTP_Main(LMTP_PROTOCOL) == -1) {
syslog(LOG_ERR, "LMTP session: %s",
SF_GetError());
}
SF_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);
}
}
out_lmtp:
for (i = 0; i < smtpSockCount; i++) {
if (!FD_ISSET(smtpSocks[i], &rfds)) {
continue;
}
if (smtpClientCount+1 > smtpMaxClients) {
syslog(LOG_ERR, "Too many SMTP clients!");
goto out_smtp;
}
socklen = sizeof(paddr);
rv = accept(smtpSocks[i], (struct sockaddr *)&paddr,
&socklen);
if (rv == -1) {
syslog(LOG_ERR, "accept(SMTP): %m");
goto out_smtp;
}
if (pipe(pp) == -1) {
syslog(LOG_ERR, "pipe(SMTP): %m");
close(rv);
goto out_smtp;
}
if ((pid = fork()) == -1) {
syslog(LOG_ERR, "fork(SMTP): %m");
close(rv);
close(pp[0]);
close(pp[1]);
goto out_lmtp;
} else if (pid == 0) { /* Child */
dup2(rv, STDIN_FILENO);
dup2(rv, STDOUT_FILENO);
close(pp[0]);
smtpMasterPipe = pp[1];
SF_EnterServerProc("mailprocd");
fcntl(fileno(stdin), F_SETOWN, getpid());
setvbuf(stdout, NULL, _IOFBF, 0);
if (SMTP_Main(ESMTP_PROTOCOL) == -1) {
syslog(LOG_ERR, "SMTP session: %s",
SF_GetError());
}
SF_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);
}
}
out_smtp:
TAILQ_FOREACH(smtps, &smtpSessions, sessions) {
QMGR_MetaData meta;
ssize_t nread;
if (!FD_ISSET(smtps->pipe, &rfds)) {
continue;
}
for (nread = 0; nread < sizeof(QMGR_MetaData);) {
rv = read(smtps->pipe, ((void *)&meta)+nread,
(sizeof(QMGR_MetaData) - nread));
if (rv == -1) {
if (errno == EINTR) {
continue;
} else {
syslog(LOG_ERR,
"Read error on smtps%d: %m",
smtps->pid);
break;
}
} else if (rv == 0) {
break;
}
nread += rv;
}
if (nread < sizeof(QMGR_MetaData)) {
continue;
}
Debug("[MASTER] uid=%d:%d, qid=%s, from=%s, rcpt=%s",
meta.uid, meta.gid, meta.qid, meta.from, meta.rcpt);
if (ProcessQueueNotification(smtps, &meta) == -1) {
syslog(LOG_ERR, "<%s>: FAIL: %s", meta.qid,
SF_GetError());
Debug("<%s>: FAIL: %s", meta.qid,
SF_GetError());
}
}
if (ProcessSignals() == 1)
break;
}
out_close:
syslog(LOG_NOTICE, "Server shutdown (signal)");
CloseFiles();
ML_Destroy();
CTL_Destroy();
POL_Destroy();
LOCAL_Destroy();
#ifdef HAVE_SA
SPAM_Destroy();
#endif
unlink(mpdPidFile);
out:
#ifdef HAVE_PERL
perl_destruct(sfPerl);
perl_free(sfPerl);
PERL_SYS_TERM();
#endif
return (0);
fatal:
fprintf(stderr, "FATAL: %s\n", SF_GetError());
syslog(LOG_CRIT, "FATAL: %s", SF_GetError());
return (1);
}