/*
* Copyright (c) 2006-2010 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
#include "mailprocd.h"
#include "pathnames.h"
#define ENVELOPE_MAX (164+(ADDRESS_MAX*3))
static char myname[MAXHOSTNAMELEN];
static char envel[ENVELOPE_MAX]; /* Envelope prepend buffer */
TBL *Tmailbox, *Tuid, *Tgid, *Trules, *Tlists; /* Tables */
static volatile int sig_pipe = 0;
static char *pathMailbox, *pathUID, *pathGID, *pathRules, *pathLists;
static char *suspShell, *sendmailPath;
static char *ruleHeader, *loopHeader;
static int emulQmail, detectSigPipe, suspBounce;
int
LOCAL_Init(CFG_File *cf)
{
FILE *f;
char *ln;
size_t len;
char *key, *val, *c;
int using_flock = 0;
if (gethostname(myname, sizeof(myname)) == -1)
Strlcpy(myname, "unknown", sizeof(myname));
CFG_GetInt(cf, "local.emul-qmail", &emulQmail, 1);
CFG_GetInt(cf, "local.detect-sigpipe", &detectSigPipe, 1);
CFG_GetStr(cf, "local.susp-shell", &suspShell, _PATH_SUSPENDED_SH);
CFG_GetInt(cf, "local.susp-bounce", &suspBounce, 1);
CFG_GetStr(cf, "local.sendmail-path", &sendmailPath, _PATH_SENDMAIL);
CFG_GetStr(cf, "local.rule-header", &ruleHeader, "X-Csoft-Rule: ");
CFG_GetStr(cf, "local.loop-header", &loopHeader, "X-Csoft-Pipe: ");
CFG_GetStr(cf, "mailbox-db", &pathMailbox, _PATH_DB_MAILBOX);
CFG_GetStr(cf, "uid-db", &pathUID, _PATH_DB_UID);
CFG_GetStr(cf, "gid-db", &pathGID, _PATH_DB_GID);
CFG_GetStr(cf, "rules-db", &pathRules, _PATH_DB_RULES);
CFG_GetStr(cf, "lists-db", &pathLists, _PATH_DB_LISTS);
/*
* Make damn sure that Postfix is using flock() locking for
* delivering to mailboxes.
*/
if ((f = fopen(_PATH_POSTFIX_MAIN_CF, "r")) != NULL) {
while ((ln = Fgetln(f, &len)) != NULL) {
if (ln[0] == '#' || ln[0] == '\n' ||
ln[len-1] != '\n') {
continue;
}
ln[len-1] = '\0';
if ((key = strsep(&ln, "=")) == NULL ||
(val = strsep(&ln, "=")) == NULL) {
continue;
}
for (; isspace(*key); key++) ;;
for (; isspace(*val); val++) ;;
for (c = &key[0]; *c != '\0' && !isspace(*c); c++) ;;
*c = '\0';
for (c = &val[0]; *c != '\0' && !isspace(*c); c++) ;;
*c = '\0';
if (strcmp(key, "virtual_mailbox_lock") == 0) {
if (strcmp(val, "flock") == 0)
using_flock = 1;
}
}
if (!using_flock) {
SF_SetError("Please set $virtual_mailbox_lock = "
"flock in %s", _PATH_POSTFIX_MAIN_CF);
return (-1);
}
fclose(f);
}
return LOCAL_OpenDatabases();
}
void
LOCAL_Destroy(void)
{
LOCAL_CloseDatabases();
}
int
LOCAL_OpenDatabases(void)
{
if ((Tmailbox = TBL_Load(pathMailbox)) == NULL||
(Tuid = TBL_Load(pathUID)) == NULL ||
(Tgid = TBL_Load(pathGID)) == NULL ||
(Trules = TBL_Load(pathRules)) == NULL ||
(Tlists = TBL_Load(pathLists)) == NULL) {
return (-1);
}
return (0);
}
void
LOCAL_CloseDatabases(void)
{
TBL_Destroy(Tmailbox);
TBL_Destroy(Tuid);
TBL_Destroy(Tgid);
TBL_Destroy(Trules);
TBL_Destroy(Tlists);
}
static int
LOCAL_DatabaseUpdate(TBL **pTbl, const char *path)
{
TBL *nTbl;
#if 0
struct stat sb;
if (fstat((*pTbl)->fd, &sb) == -1) {
syslog(LOG_ERR, "fstat(%s): %s", path, strerror(errno));
return (-1);
}
#endif
fprintf(sfLog, "Reloading database: %s\n", path);
if ((nTbl = TBL_Load(path)) == NULL) {
fprintf(sfLog, "%s: Reload failed: %s\n", path, SF_GetError());
syslog(LOG_ERR, "%s: Reload failed: %s", path, SF_GetError());
return (-1);
}
TBL_Destroy(*pTbl);
*pTbl = nTbl;
return (0);
}
void
LOCAL_ReloadDatabases(void)
{
LOCAL_DatabaseUpdate(&Tmailbox, pathMailbox);
LOCAL_DatabaseUpdate(&Tuid, pathUID);
LOCAL_DatabaseUpdate(&Tgid, pathGID);
LOCAL_DatabaseUpdate(&Trules, pathRules);
LOCAL_DatabaseUpdate(&Tlists, pathLists);
}
static __inline__ int
LOCAL_DropEffectivePrivs(uid_t uid, gid_t gid)
{
if (setegid(gid) < 0) {
SF_SetError("setegid(%d): %s", (int)gid, strerror(errno));
return (-1);
}
if (seteuid(uid) < 0) {
SF_SetError("seteuid(%d): %s", (int)uid, strerror(errno));
setegid(getgid());
return (-1);
}
return (0);
}
static __inline__ void
LOCAL_RegainEffectivePrivs(void)
{
if (seteuid(0) < 0) {
syslog(LOG_CRIT, "regain euid: %s; aborting!", strerror(errno));
exit(1);
}
if (setegid(0) < 0) {
syslog(LOG_CRIT, "regain egid: %s; aborting!", strerror(errno));
exit(1);
}
}
/* Check if a given user is suspended. */
int
LOCAL_SuspendedUser(struct passwd *pwd)
{
if (strcmp(pwd->pw_shell, suspShell) == 0) {
SF_SetError("Account is temporarily suspended (%lu)",
(unsigned long)pwd->pw_uid);
return (1);
}
return (0);
}
/* Look up the named symbolic ruleset. */
SF_Ruleset *
LOCAL_GetRulesetByName(char *name)
{
char *data;
if ((data = TBL_Lookup(Trules, name)) == NULL) {
SF_SetError("Reference to unexisting ruleset `%s'", name);
return (NULL);
}
return (SF_RulesetParse(name, data));
}
/*
* Return the ruleset matching the given recipient address (or -1).
* Search Order: , .
*/
SF_Ruleset *
LOCAL_GetRulesetByRcpt(char *rcpt)
{
char *data, *dom;
if ((data = TBL_Lookup(Trules, rcpt)) == NULL) {
for (dom = &rcpt[0];
*dom != '@' && *dom != '\0';
dom++)
;;
if (dom[0] == '@' && dom[1] != '\0') {
if ((data = TBL_Lookup(Trules, &dom[0])) == NULL)
return (NULL);
} else {
return (NULL);
}
}
return (SF_RulesetParse(rcpt, data));
}
/*
* Lookup the default UID and GID of the given recipient.
* Search Order: , .
*/
int
LOCAL_GetDefaultRecipientUID(char *rcpt, uid_t *uid, gid_t *gid)
{
char *key, *uid_data, *gid_data;
char *dom, *ep;
key = rcpt;
if ((uid_data = TBL_Lookup(Tuid, key)) == NULL ||
(gid_data = TBL_Lookup(Tgid, key)) == NULL) {
for (dom = &rcpt[0];
*dom != '@' && *dom != '\0';
dom++)
;;
if (dom[0] == '@' && dom[1] != '\0') {
key = dom;
if ((uid_data = TBL_Lookup(Tuid, key)) == NULL ||
(gid_data = TBL_Lookup(Tgid, key)) == NULL)
return (-1);
} else {
return (-1);
}
}
*uid = (uid_t)strtoul(uid_data, &ep, 10);
if (*uid == 0 || *ep != '\0') {
Debug("Malformed UID entry: %s", uid_data);
syslog(LOG_ERR, "<%s>: Bad UID entry: `%s'", key, uid_data);
return (-1);
}
*gid = (gid_t)strtoul(gid_data, &ep, 10);
if (*gid == 0 || *ep != '\0') {
Debug("Malformed GID entry: %s", gid_data);
syslog(LOG_ERR, "<%s>: Bad GID entry: `%s'", key, gid_data);
return (-1);
}
return (0);
}
#define ENVELOPE_FROM 0x01
#define ENVELOPE_RULE 0x02
#define ENVELOPE_PIPED 0x04
static size_t
GenEnvelope(char *buf, size_t len, SF_Message *msg, SF_Recipient *rcpt,
const char *rule, int flags)
{
if (flags & ENVELOPE_FROM) {
time_t now;
time(&now);
Strlcpy(buf, "From ", len);
Strlcat(buf, msg->mail_from, len);
Strlcat(buf, " ", len);
Strlcat(buf, asctime(localtime(&now)), len);
} else {
buf[0] = '\0';
}
Strlcat(buf, "Return-Path: <", len);
Strlcat(buf, msg->mail_from, len);
Strlcat(buf, ">\nX-Original-To: ", len);
Strlcat(buf, rcpt->addr, len);
if (flags & ENVELOPE_RULE) {
Strlcat(buf, "\n", len);
Strlcat(buf, ruleHeader, len);
Strlcat(buf, rule, len);
}
if (flags & ENVELOPE_PIPED) {
Strlcat(buf, "\n", len);
Strlcat(buf, loopHeader, len);
Strlcat(buf, rcpt->addr, len);
}
return (Strlcat(buf, "\n", len));
}
/* Deliver a message to a local mailbox file. */
int
LOCAL_DeliverToMailbox(SF_Message *msg, SF_Recipient *rcpt, const char *path,
const char *filter, uid_t uid, gid_t gid)
{
size_t envel_len;
off_t orig_len;
int fd;
Debug("Delivering to mailbox %s (as %d:%d)", path,
(int)uid, (int)gid);
if (LOCAL_DropEffectivePrivs(uid, gid) == -1)
return (-1);
tryopen:
#ifdef HAVE_OPEN_EXLOCK
if ((fd = open(path, O_WRONLY|O_APPEND|O_CREAT|O_EXLOCK, 0600)) == -1) {
if (errno == EINTR) { goto tryopen; }
SF_SetError("%s: %s", path, strerror(errno));
goto fail_privs;
}
#else
if ((fd = open(path, O_WRONLY|O_APPEND|O_CREAT, 0600)) == -1) {
if (errno == EINTR) { goto tryopen; }
SF_SetError("%s: %s", path, strerror(errno));
goto fail_privs;
}
LockFile(fd);
#endif /* HAVE_OPEN_EXLOCK */
if ((orig_len = lseek(fd, 0, SEEK_END)) == -1) {
SF_SetError("lseek: %s", path, strerror(errno));
goto fail_close;
}
envel_len = GenEnvelope(envel, sizeof(envel), msg, rcpt, filter,
ENVELOPE_FROM|ENVELOPE_RULE);
if (Write(fd, envel, envel_len) == -1 ||
Write(fd, msg->text, msg->text_len) == -1 ||
Write(fd, "\n\n", 2) == -1) {
goto fail_trunc;
}
(void)fsync(fd);
close(fd);
#ifndef HAVE_OPEN_EXLOCK
UnlockFile(fd);
#endif
LOCAL_RegainEffectivePrivs();
return (0);
fail_trunc:
if (ftruncate(fd, orig_len) == -1) {
syslog(LOG_CRIT, "%s: Write error (and cannot truncate "
"to %lu: %s)", path, (unsigned long)orig_len,
strerror(errno));
} else {
syslog(LOG_ERR, "%s: Write error (truncated to %lu)",
path, (unsigned long)orig_len);
}
fail_close:
#ifndef HAVE_OPEN_EXLOCK
UnlockFile(fd);
#endif
close(fd);
fail_privs:
LOCAL_RegainEffectivePrivs();
return (1);
}
static int
BuildMaildir(const char *maildir)
{
char dir[MAILBOX_PATH_MAX];
const char *subdirs[] = { "/cur", "/new", "/tmp" };
int i;
if (mkdir(maildir, 0700) == -1) {
SF_SetError("mk %s: %s", maildir, strerror(errno));
return (-1);
}
for (i = 0; i < 3; i++) {
Strlcpy(dir, maildir, sizeof(dir));
Strlcat(dir, subdirs[i], sizeof(dir));
if (mkdir(dir, 0700) == -1) {
SF_SetError("mk %s: %s", dir, strerror(errno));
return (-1);
}
}
return (0);
}
/* Deliver a message to a local maildir. */
int
LOCAL_DeliverToMaildir(SF_Message *msg, SF_Recipient *rcpt, const char *maildir,
const char *filter, uid_t uid, gid_t gid)
{
char dir[MAILBOX_PATH_MAX];
char tmp_file[MAILBOX_PATH_MAX];
char new_file[MAILBOX_PATH_MAX];
struct timeval start_time;
struct stat sb;
size_t envel_len;
int fd;
Debug("Delivering to maildir %s (as %d:%d)", maildir,
(int)uid, (int)gid);
if (LOCAL_DropEffectivePrivs(uid, gid) == -1)
return (-1);
if (stat(maildir, &sb) == -1 &&
BuildMaildir(maildir) == -1)
goto fail_perm;
/* Make sure ./cur exists. */
Strlcpy(dir, maildir, sizeof(dir));
Strlcat(dir, "/cur", sizeof(dir));
if (stat(dir, &sb) == -1 &&
mkdir(dir, 0700) == -1) {
SF_SetError("mk %s: %s", dir, strerror(errno));
goto fail_perm;
}
/* Write the message to a temporary file in ./tmp. */
gettimeofday(&start_time, (struct timezone *)0);
Strlcpy(dir, maildir, sizeof(dir));
Strlcat(dir, "/tmp", sizeof(dir));
snprintf(tmp_file, sizeof(tmp_file), "%s/%lu.P%u.%s", dir,
(unsigned long)start_time.tv_sec, (int)getpid(), myname);
tryopen:
if ((fd = open(tmp_file, O_WRONLY|O_CREAT|O_EXCL, 0600)) == -1) {
if (errno == ENOENT) {
if (mkdir(dir, 0700) == -1) {
SF_SetError("mk %s: %s", dir, strerror(errno));
goto fail_perm;
}
if ((fd = open(tmp_file, O_WRONLY|O_CREAT|O_EXCL, 0600))
== -1) {
SF_SetError("%s: %s", tmp_file,
strerror(errno));
goto fail_perm;
}
} else if (errno == EINTR) {
goto tryopen;
} else {
SF_SetError("%s: %s", tmp_file, strerror(errno));
goto fail_perm;
}
}
/* Write the envelope and message contents to the file. */
envel_len = GenEnvelope(envel, sizeof(envel), msg, rcpt, filter,
ENVELOPE_RULE);
if (Write(fd, envel, envel_len) == -1 ||
Write(fd, msg->text, msg->text_len) == -1 ||
Write(fd, "\n\n", 2) == -1)
goto fail_write;
/* Link the destination file in ./new. */
if (fstat(fd, &sb) == -1) {
SF_SetError("fstat: %s", strerror(errno));
goto fail_write;
}
Strlcpy(dir, maildir, sizeof(dir));
Strlcat(dir, "/new", sizeof(dir));
snprintf(new_file, sizeof(new_file), "%s/%lu.V%lxI%lxM%lu.%s", dir,
(unsigned long)start_time.tv_sec,
(unsigned long)sb.st_dev,
(unsigned long)sb.st_ino,
(unsigned long)start_time.tv_usec,
myname);
if (link(tmp_file, new_file) == -1) {
if (errno == ENOENT) {
if (mkdir(dir, 0700) == -1) {
SF_SetError("mk %s: %s", dir, strerror(errno));
goto fail_write;
}
if (link(tmp_file, new_file) == -1) {
SF_SetError("link %s: %s", new_file,
strerror(errno));
goto fail_write;
}
} else {
SF_SetError("link %s: %s", new_file, strerror(errno));
goto fail_write;
}
}
(void)fsync(fd);
close(fd);
Unlink(tmp_file);
LOCAL_RegainEffectivePrivs();
return (0);
fail_write:
close(fd);
LOCAL_RegainEffectivePrivs();
return (1);
fail_perm:
LOCAL_RegainEffectivePrivs();
return (-1);
}
/* Relay a message to a some e-mail address. */
int
LOCAL_DeliverToAddress(SF_Message *msg, SF_Recipient *rcpt, const char *dest,
uid_t uid, gid_t gid)
{
char *dom;
char *args[3];
int pp[2];
pid_t pid;
int status;
if (!ValidEmailAddress(dest) ||
(dom = strchr(dest, '@')) == NULL ||
*dom == '\0') {
SF_SetError("Invalid address: %s", dest);
return (-1);
}
if (SF_DomainForcedFiltering(&dom[1]) &&
msg->status.score >= forceFilterThreshold) {
Debug("Dropping spam to <%s> (%s rule)", dest, &dom[1]);
return (0);
}
Debug("Forwarding to <%s> (as %d:%d)", dest, uid, gid);
if (pipe(pp) == -1) {
SF_SetError("pipe: %s", strerror(errno));
return (1);
}
if ((pid = fork()) == -1) {
SF_SetError("fork: %s", strerror(errno));
return (1);
}
if (pid == 0) { /* Child */
sigset_t allsigs;
Setproctitle("forward");
close(pp[1]);
if (dup2(pp[0], 0) == -1) {
SF_SetError("dup2: %s", strerror(errno));
goto chld_fail;
}
close(1);
/* Drop privileges irrevocably. */
sigfillset(&allsigs);
sigprocmask(SIG_BLOCK, &allsigs, NULL);
if (setegid(gid) < 0 || setgid(gid) < 0 ||
seteuid(uid) < 0 || setuid(uid) < 0) {
sigprocmask(SIG_UNBLOCK, &allsigs, NULL);
SF_SetError("set*id(%d,%d -> %d,%d): %s",
(int)geteuid(), (int)getegid(),
(int)uid, (int)gid, strerror(errno));
goto chld_fail;
}
sigprocmask(SIG_UNBLOCK, &allsigs, NULL);
args[0] = sendmailPath;
args[1] = (char *)dest;
args[2] = NULL;
if (execv(args[0], args) == -1) {
exit(1);
} else {
exit(0);
}
SF_SetError("execv: %s", strerror(errno));
chld_fail:
syslog(LOG_ERR, "sendmail process: %s", SF_GetError());
exit(1);
}
close(pp[0]);
if (Write(pp[1], msg->text, msg->text_len) == -1 ||
Write(pp[1], "\n", 1) == -1) {
SF_SetError("Write error");
close(pp[1]);
goto fail_temp;
}
close(pp[1]);
wait:
if (waitpid(pid, &status, 0) == -1) {
if (errno == EINTR) {
goto wait;
}
SF_SetError("waitpid: %s", strerror(errno));
goto fail_temp;
}
if (WIFSIGNALED(status)) {
SF_SetError("sendmail: signal %d", WTERMSIG(status));
goto fail_temp;
} else if (WIFEXITED(status) && WEXITSTATUS(status)) {
SF_SetError("sendmail: error %d", WEXITSTATUS(status));
goto fail_temp;
}
return (0);
fail_temp:
syslog(LOG_ERR, "DeliverToAddress: %s (TEMP)", SF_GetError());
return (1);
}
#ifdef COMPAT_QMAIL
static void
EmulateQmailEnv(struct passwd *pw, SF_Message *msg, SF_Recipient *mr)
{
char rcpt[ADDRESS_MAX+16];
char line[ADDRESS_MAX+16];
time_t now;
setenv("HOST", mr->domain_part, 1);
setenv("SENDER", msg->mail_from, 1);
setenv("NEWSENDER", msg->mail_from, 1);
Strlcpy(rcpt, pw->pw_name, sizeof(rcpt));
Strlcat(rcpt, "-", sizeof(rcpt));
Strlcat(rcpt, mr->domain_part, sizeof(rcpt));
Strlcat(rcpt, "-", sizeof(rcpt));
Strlcat(rcpt, mr->user_part, sizeof(rcpt));
setenv("LOCAL", rcpt, 1);
Strlcat(rcpt, "@", sizeof(rcpt));
Strlcat(rcpt, mr->domain_part, sizeof(rcpt));
setenv("RECIPIENT", rcpt, 1);
Strlcpy(line, mr->domain_part, sizeof(line));
Strlcat(line, "-", sizeof(line));
Strlcat(line, mr->user_part, sizeof(line));
setenv("EXT", line, 1);
time(&now);
Strlcpy(line, "From ", sizeof(line));
Strlcat(line, msg->mail_from, sizeof(line));
Strlcat(line, " ", sizeof(line));
Strlcat(line, asctime(localtime(&now)), sizeof(line));
setenv("UFLINE", line, 1);
Strlcpy(line, "Delivered-To: ", sizeof(line));
Strlcat(line, rcpt, sizeof(line));
Strlcat(line, "\n", sizeof(line));
setenv("DTLINE", line, 1);
Strlcpy(line, "Return-Path: ", sizeof(line));
Strlcat(line, msg->mail_from, sizeof(line));
Strlcat(line, "\n", sizeof(line));
setenv("RPLINE", line, 1);
}
#endif /* COMPAT_QMAIL */
static void
handle_sig_pipe(int sigraised)
{
sig_pipe = 1;
}
/* Feed the message to a pipe. */
int
LOCAL_FeedToPipe(SF_Message *msg, SF_Recipient *rcpt, const char *cmd,
const char *filter, uid_t uid, gid_t gid, int dropPrivs)
{
struct passwd *pw;
int pp[2];
pid_t pid;
int status;
size_t envel_len, wrote;
ssize_t rv;
struct sigaction sa, saSave;
Debug("Pipe: %s (as %d:%d)", cmd, (int)uid, (int)gid);
if ((pw = getpwuid(uid)) == NULL) {
SF_SetError("No such UID: %d", (int)uid);
return (-1);
}
if (LOCAL_SuspendedUser(pw)) {
SF_SetError("Account suspended (mail-to-pipe not allowed)");
return suspBounce ? -1 : 1;
}
if (pipe(pp) == -1) {
SF_SetError("pipe: %s", strerror(errno));
return (1);
}
if ((pid = fork()) == -1) {
SF_SetError("fork: %s", strerror(errno));
return (1);
}
if (pid == 0) { /* Child */
char *args[4];
extern char **environ;
sigset_t allsigs;
sigfillset(&sa.sa_mask);
sa.sa_flags = SA_RESTART;
sa.sa_handler = SIG_DFL;
sigaction(SIGCHLD, &sa, NULL);
sigaction(SIGURG, &sa, NULL);
sigaction(SIGHUP, &sa, NULL);
sigaction(SIGPIPE, &sa, NULL);
sigaction(SIGINT, &sa, NULL);
sigaction(SIGQUIT, &sa, NULL);
sigaction(SIGTERM, &sa, NULL);
sigaction(SIGUSR1, &sa, NULL);
sigaction(SIGUSR2, &sa, NULL);
close(pp[1]);
if (dup2(pp[0], STDIN_FILENO) == -1) {
SF_SetError("dup2: %s", strerror(errno));
goto chld_fail;
}
close(STDOUT_FILENO);
/* Drop privileges irrevocably. */
if (dropPrivs) {
sigfillset(&allsigs);
sigprocmask(SIG_BLOCK, &allsigs, NULL);
if (gid != 0) {
if (setegid(gid) < 0 || setgid(gid) < 0) {
sigprocmask(SIG_UNBLOCK, &allsigs,NULL);
SF_SetError("setgid(%d): %s", (int)gid,
strerror(errno));
goto chld_fail;
}
}
if (uid != 0) {
if (seteuid(uid) < 0 || setuid(uid) < 0) {
sigprocmask(SIG_UNBLOCK, &allsigs,NULL);
SF_SetError("setuid(%d): %s", (int)uid,
strerror(errno));
goto chld_fail;
}
}
sigprocmask(SIG_UNBLOCK, &allsigs, NULL);
}
/* Move to the user's home and forge the environment vars. */
if (chdir(pw->pw_dir) == -1) {
SF_SetError("chdir %s: %s", pw->pw_dir,
strerror(errno));
goto chld_fail;
}
if ((environ = calloc(1, sizeof(char *))) == NULL) {
SF_SetError("Out of memory");
goto chld_fail;
}
setenv("USER", pw->pw_name, 1);
setenv("SHELL", mpdSafeShell, 1);
setenv("HOME", pw->pw_dir, 1);
setenv("PATH", mpdSafePath, 1);
#ifdef COMPAT_QMAIL
if (emulQmail)
EmulateQmailEnv(pw, msg, rcpt);
#endif
args[0] = mpdSafeShell;
args[1] = "-c";
args[2] = (char *)cmd; /* XXX */
args[3] = NULL;
execv(args[0], args);
SF_SetError("execv: %s", strerror(errno));
chld_fail:
syslog(LOG_ERR, "mail-to-command process: %s", SF_GetError());
exit(1);
}
close(pp[0]);
/* Set up a temporary SIGPIPE handler so we can detect bad filters. */
if (detectSigPipe) {
sigfillset(&sa.sa_mask);
sa.sa_flags = 0;
sa.sa_handler = handle_sig_pipe;
sigaction(SIGPIPE, &sa, &saSave);
sig_pipe = 0;
}
/* Write the message envelope and text. */
envel_len = GenEnvelope(envel, sizeof(envel), msg, rcpt, filter,
ENVELOPE_FROM|ENVELOPE_PIPED);
for (wrote = 0; wrote < envel_len;) {
rv = write(pp[1], &envel[wrote], (envel_len - wrote));
if (rv == -1) {
if (errno == EINTR) {
if (sig_pipe) {
syslog(LOG_ERR,
"SIGPIPE with \"%s\", notify %s!",
cmd, pw->pw_name);
goto fail_write;
}
continue;
} else {
goto fail_write;
}
} else if (rv == 0) {
goto fail_write;
}
wrote += rv;
}
for (wrote = 0; wrote < msg->text_len;) {
rv = write(pp[1], &msg->text[wrote], (msg->text_len - wrote));
if (rv == -1) {
if (errno == EINTR) {
if (sig_pipe) {
syslog(LOG_ERR,
"SIGPIPE with \"%s\", notify %s!",
cmd, pw->pw_name);
goto fail_write;
}
continue;
} else {
goto fail_write;
}
} else if (rv == 0) {
goto fail_write;
}
wrote += rv;
}
close(pp[1]);
wait:
if (waitpid(pid, &status, 0) == -1) {
if (errno == EINTR) {
goto wait;
}
SF_SetError("waitpid: %s", strerror(errno));
goto fail_perm;
}
if (WIFSIGNALED(status)) {
SF_SetError("signal %d", WTERMSIG(status));
goto fail_temp;
} else if (WIFEXITED(status) && WEXITSTATUS(status)) {
#ifdef COMPAT_QMAIL
if (emulQmail && WEXITSTATUS(status) == 99)
goto fail_99;
#endif
SF_SetError("error code %d", WEXITSTATUS(status));
goto fail_perm;
}
sigaction(SIGPIPE, &saSave, NULL);
return (0);
fail_temp:
syslog(LOG_ERR, "Mail-to-command error (TEMP): %s", SF_GetError());
sigaction(SIGPIPE, &saSave, NULL);
return (1);
fail_perm:
syslog(LOG_ERR, "Mail-to-command error (PERM): %s", SF_GetError());
sigaction(SIGPIPE, &saSave, NULL);
return (-1);
fail_write:
close(pp[1]);
syslog(LOG_ERR, "Mail-to-command write error (TEMP)");
sigaction(SIGPIPE, &saSave, NULL);
return (1);
#ifdef COMPAT_QMAIL
fail_99:
sigaction(SIGPIPE, &saSave, NULL);
return (99);
#endif
}