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