/*
* Copyright (c) 2006 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 "mailprocd.h"
#include "pathnames.h"
pid_t qmgrProc = 0;
int qmgrMaxCleanupProcs;
int qmgrMaxIdle;
static volatile int scan_flag = 0;
static char *pathQueue;
int
QMGR_Init(CFG_File *cf)
{
struct stat sb;
CFG_GetStr(cf, "qmgr.path", &pathQueue, _PATH_QUEUE);
CFG_GetInt(cf, "qmgr.max-cleanup-procs", &qmgrMaxCleanupProcs, 40);
CFG_GetInt(cf, "qmgr.max-idle", &qmgrMaxIdle, 30);
if (stat(pathQueue, &sb) != 0 && mkdir(pathQueue, 0700) == -1) {
SF_SetError("mkdir %s: %s", pathQueue, strerror(errno));
return (-1);
}
return (0);
}
/* Return the number of items in the queue. */
int
QMGR_Todo(void)
{
DIR *dir;
int count = 0;
if ((dir = opendir(pathQueue)) == NULL) {
syslog(LOG_ERR, "opendir %s: %m", pathQueue);
return (0);
}
while (readdir(dir) != NULL) { count++; }
closedir(dir);
return (count>2 ? count-2 : 0);
}
/*
* Load a message from the queue. If the meta argument is NULL, metadata is
* recovered from file.
*/
SF_Message *
QMGR_LoadMessage(const char *path, const QMGR_MetaData *meta)
{
QMGR_MetaData rmeta;
char rcpt_to[ADDRESS_MAX];
SF_Message *msg;
SF_Recipient *rcpt;
off_t lrv;
int fd;
tryopen:
if ((fd = open(path, O_RDONLY)) == -1) {
if (errno == EINTR) { goto tryopen; }
SF_SetError("%s", strerror(errno));
return (NULL);
}
if ((msg = SF_MessageNew()) == NULL)
return (NULL);
if (meta != NULL) {
Strlcpy(msg->mail_from, meta->from, sizeof(msg->mail_from));
Strlcpy(rcpt_to, meta->rcpt, sizeof(rcpt_to));
} else {
if (Read(fd, &rmeta, sizeof(QMGR_MetaData)) == -1) {
SF_SetError("Reading Metadata: %s", SF_GetError());
goto fail;
}
Strlcpy(msg->mail_from, rmeta.from, sizeof(msg->mail_from));
Strlcpy(msg->ip, rmeta.ip, sizeof(msg->ip));
Strlcpy(rcpt_to, rmeta.rcpt, sizeof(rcpt_to));
}
if ((rcpt = SF_MessageAddRecipient(msg, rcpt_to)) == NULL ||
SF_ParseRecipientParts(rcpt) == -1)
goto fail;
/* Read the message body */
if ((lrv = lseek(fd, 0, SEEK_END)) == -1) {
SF_SetError("SEEK_END: %s", strerror(errno));
goto fail;
}
if (lseek(fd, sizeof(QMGR_MetaData), SEEK_SET) == -1) {
SF_SetError("SEEK_SET: %s", strerror(errno));
goto fail;
}
msg->text_len = (size_t)lrv - sizeof(QMGR_MetaData);
if ((msg->text = malloc(msg->text_len+1)) == NULL) {
syslog(LOG_ERR, "LoadMessage: Out of memory for %ldB message",
(unsigned long)msg->text_len);
SF_SetError("Out of memory for %ldB message",
(unsigned long)msg->text_len);
goto fail;
}
if (Read(fd, msg->text, msg->text_len) == -1) {
SF_SetError("Reading Contents: %s", SF_GetError());
goto fail;
}
msg->text[msg->text_len] = '\0';
close(fd);
return (msg);
fail:
SF_MessageFree(msg);
close(fd);
return (NULL);
}
/* Save a message onto the queue and notify the master process. */
int
QMGR_Queue(SF_Message *msg, SF_Recipient *rcpt, int masterPipe)
{
QMGR_MetaData meta;
char path[MAXPATHLEN], *qid;
size_t wrote;
ssize_t rv;
int fd;
memset(&meta, 0, sizeof(QMGR_MetaData));
if (LOCAL_GetDefaultRecipientUID(rcpt->addr, &meta.uid, &meta.gid)
== -1) {
SF_SetError("<%s>: Cannot find UID", rcpt->addr);
return (-1);
}
Strlcpy(path, pathQueue, sizeof(path));
Strlcat(path, "XXXXXXXXXX", sizeof(path));
if ((fd = mkstemp(path)) == -1) {
SF_SetError("mkstemp: %s", strerror(errno));
return (-1);
}
if ((qid = strrchr(path, '/')) == NULL || qid[1] == '\0') {
SF_SetError("mkstemp problem");
close(fd);
unlink(path);
return (-1);
}
if (fchown(fd, meta.uid, meta.gid) == -1) {
SF_SetError("%s: chown: %s", path, strerror(errno));
close(fd);
unlink(path);
return (-1);
}
qid++;
Strlcpy(meta.qid, qid, sizeof(meta.qid));
Strlcpy(meta.from, msg->mail_from, sizeof(meta.from));
Strlcpy(meta.rcpt, rcpt->addr, sizeof(meta.rcpt));
Strlcpy(meta.ip, msg->ip, sizeof(meta.ip));
Debug("Queued message to %s", path);
/*
* Save the metadata followed by the message body. The metadata is
* only actually used in the event of a crash or server restart.
*/
for (wrote = 0; wrote < sizeof(QMGR_MetaData);) {
rv = write(fd, ((void *)&meta)+wrote,
(sizeof(QMGR_MetaData) - wrote));
if (rv == -1) {
if (errno == EINTR) { continue; }
else { goto fail_write; }
} else if (rv == 0) {
goto fail_write;
}
wrote += rv;
}
for (wrote = 0; wrote < msg->text_len;) {
rv = write(fd, &msg->text[wrote], (msg->text_len - wrote));
if (rv == -1) {
if (errno == EINTR) { continue; }
else { goto fail_write; }
} else if (rv == 0) {
goto fail_write;
}
wrote += rv;
}
close(fd);
/* Notify the master process about the new message. */
for (wrote = 0; wrote < sizeof(QMGR_MetaData);) {
rv = write(masterPipe, ((void *)&meta)+wrote,
(sizeof(QMGR_MetaData) - wrote));
if (rv == -1) {
if (errno == EINTR) {
continue;
} else {
SF_SetError("<%s>: Error writing to master: %s",
qid, strerror(errno));
return (-1);
}
} else if (rv == 0) {
SF_SetError("<%s>: EOF writing to master", qid);
return (-1);
}
wrote += rv;
}
return (0);
fail_write:
SF_SetError("%s: Write error", path);
close(fd);
return (-1);
}
static void
ProcessQueuedMessage(const char *path, const QMGR_MetaData *meta)
{
SF_Message *msg;
SF_Recipient *rcpt;
int rv;
if ((msg = QMGR_LoadMessage(path, meta)) == NULL) {
syslog(LOG_ERR, "%s: LoadMessage: %s", path, SF_GetError());
return;
}
rcpt = TAILQ_FIRST(&msg->rcpts); /* Only 1 rcpt possible */
rcpt->ml = ML_GetMailListReq(rcpt);
if (rcpt->ml != NULL) {
rv = ML_MessageProcess(msg, rcpt);
Debug("[%d] From <%s> to list <%s>, %lu bytes",
rv, msg->mail_from, rcpt->ml->name,
(unsigned long)msg->text_len);
} else {
rv = SF_MessageProcess(msg, rcpt);
Debug("[%d] From <%s> to <%s>, %lu bytes",
rv, msg->mail_from, rcpt->addr,
(unsigned long)msg->text_len);
}
SF_MessageFree(msg);
if (rv == 0) {
Unlink(path);
} else if (rv == -1) {
syslog(LOG_ERR, "%s: PERM: %s", path, SF_GetError());
unlink(path);
} else if (rv == 1) {
syslog(LOG_ERR, "%s: SOFTFAIL: %s", path, SF_GetError());
}
if (getuid() != 0 || geteuid() != 0 ||
getgid() != 0 || getegid() != 0) {
syslog(LOG_ERR, "Erroneous drop in privileges! (%d:%d)",
(int)getuid(), (int)geteuid());
exit(1);
}
}
/*
* Cleanup routine. This is used to process any entry left in the active
* queue as a result of a crash or server restart.
*/
void
QMGR_Cleanup(void)
{
char path[MAXPATHLEN];
DIR *dir;
struct dirent *dp;
pid_t pid;
int status;
int nproc = 0;
if ((pid = fork()) == -1) {
syslog(LOG_ERR, "fork(CLEANUP): %m");
return;
} else if (pid > 0) {
wait(&status);
return;
}
SF_EnterServerProc("mailprocd");
Setproctitle("-cleanup");
if ((dir = opendir(pathQueue)) == NULL) {
syslog(LOG_ERR, "opendir %s: %m", pathQueue);
return;
}
while ((dp = readdir(dir)) != NULL) {
if (dp->d_name[0] == '.') {
continue;
}
Strlcpy(path, pathQueue, sizeof(path));
Strlcat(path, dp->d_name, sizeof(path));
Debug("QMGR_Cleanup: Processing %s", path);
if ((pid = fork()) == -1) {
syslog(LOG_ERR, "fork(CLEANUP): %m");
return;
} else if (pid > 0) {
if (nproc++ > qmgrMaxCleanupProcs) {
wait(&status);
nproc--;
}
} else if (pid == 0) {
SF_EnterServerProc("mailprocd");
Setproctitle("-cleanup: %s", dp->d_name);
ProcessQueuedMessage(path, NULL);
SF_ExitServerProc(0);
}
}
closedir(dir);
SF_ExitServerProc(0);
}
static int
ScanQueue(uid_t uid)
{
char path[MAXPATHLEN];
DIR *dir;
struct dirent *dp;
struct stat sb;
int nents = 0;
#if 0
pid_t pid;
int exitCode;
#endif
if ((dir = opendir(pathQueue)) == NULL) {
SF_SetError("%s: %s", pathQueue, strerror(errno));
return (-1);
}
while ((dp = readdir(dir)) != NULL) {
if (dp->d_name[0] == '.') {
continue;
}
Strlcpy(path, pathQueue, sizeof(path));
Strlcat(path, dp->d_name, sizeof(path));
if (stat(path, &sb) == -1 ||
sb.st_uid != uid) {
continue;
}
#if 0
if ((pid = fork()) == -1) {
SF_SetError("fork: %s", strerror(errno));
return (-1);
}
if (pid > 0) { /* Parent */
waitpid(pid, &exitCode, 0);
if (WIFSIGNALED(exitCode)) {
syslog(LOG_ERR, "cleanup: signal %d",
WTERMSIG(exitCode));
} else if (WIFEXITED(exitCode) &&
WEXITSTATUS(exitCode) != 0) {
syslog(LOG_ERR, "cleanup: failure (%d)",
WEXITSTATUS(exitCode));
}
nents++;
} else if (pid == 0) { /* Child */
SF_EnterServerProc("mailprocd");
Setproctitle("-cleanup: %s", dp->d_name);
#endif
ProcessQueuedMessage(path, NULL);
nents++;
#if 0
SF_ExitServerProc(0);
}
#endif
}
closedir(dir);
return (nents);
}
static void
handle_usr2(int sigraised)
{
scan_flag++;
}
/* Main worker process routine. */
int
QMGR_WorkerMain(uid_t uid)
{
struct passwd *pw;
int rv, totMsgs = 0;
struct timeval timeout;
struct sigaction sa;
sigfillset(&sa.sa_mask);
sa.sa_flags = SA_RESTART;
sa.sa_handler = handle_usr2;
sigaction(SIGUSR2, &sa, NULL);
if ((pw = getpwuid(uid)) == NULL) {
SF_SetError("Bad UID: %d", uid);
return (-1);
}
if ((rv = ScanQueue(uid)) == -1) {
SF_SetError("ScanQueue: %s", SF_GetError());
return (-1);
} else {
totMsgs += rv;
}
for (;;) {
Setproctitle("-worker: %s (%d msgs)", pw->pw_name, totMsgs);
timeout.tv_sec = qmgrMaxIdle;
timeout.tv_usec = 0;
if (select(0, NULL, NULL, NULL, &timeout) == -1) {
if (errno == EINTR) {
if (scan_flag) {
scan_flag = 0;
if ((rv = ScanQueue(uid)) == -1) {
SF_SetError("ScanQueue: %s",
SF_GetError());
return (-1);
} else {
totMsgs += rv;
}
} else {
continue;
}
} else {
SF_SetError("select: %s", strerror(errno));
return (-1);
}
} else {
if ((rv = ScanQueue(uid)) == -1) {
SF_SetError("ScanQueue: %s", SF_GetError());
return (-1);
} else if (rv == 0) {
break; /* Process expired */
} else {
totMsgs += rv;
}
}
/* Just in case we caught USR2 in the middle of processing. */
if (scan_flag) {
scan_flag = 0;
if ((rv = ScanQueue(uid)) == -1) {
SF_SetError("ScanQueue: %s", SF_GetError());
return (-1);
} else {
totMsgs += rv;
}
}
}
return (0);
}