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