/* $Id: mailexec.c,v 1.8 2005/06/03 21:12:31 dm Exp $ */
/*
*
* Copyright (C) 2004 David Mazieres (dm@uun.org)
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License as
* published by the Free Software Foundation; either version 2, or (at
* your option) any later version.
*
* This program is distributed in the hope that it will be useful, but
* WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
* USA
*
*/
#include "local.h"
#include <ctype.h>
#include <dirent.h>
#include <signal.h>
#include <sys/wait.h>
#include "getopt_long.h"
#include "util/vector.h"
int opt_printfrom = 1;
int opt_printrp = 1;
int opt_newonly;
int opt_verbose;
char *progname;
int cwdfd;
int closeme = -1;
char *deleteme;
static void
cleanup (void)
{
if (deleteme) {
unlink (deleteme);
deleteme = NULL;
}
if (closeme >= 0) {
close (closeme);
closeme = -1;
}
}
int
spawnit (pid_t *pidp, char *const *av)
{
int fds[2];
pid_t pid;
int execok[2];
int err;
if (pidp)
*pidp = -1;
if (pipe (fds) < 0) {
fprintf (stderr, "%s: pipe: %s\n", progname, strerror (errno));
return -1;
}
if (pipe (execok) < 0) {
close (fds[0]);
close (fds[1]);
fprintf (stderr, "%s: pipe: %s\n", progname, strerror (errno));
return -1;
}
pid = fork ();
if (pid == -1) {
fprintf (stderr, "%s: fork: %s\n", progname, strerror (errno));
close (fds[0]);
close (fds[1]);
close (execok[0]);
close (execok[1]);
return -1;
}
if (!pid) {
close (fds[1]);
close (execok[0]);
fcntl (execok[1], F_SETFD, 1);
if (fds[0] != 0) {
if (dup2 (fds[0], 0) < 0) {
fprintf (stderr, "%s: dup2: %s\n", progname, strerror (errno));
_exit (1);
}
close (fds[0]);
}
fchdir (cwdfd);
execvp (av[0], av);
err = errno;
write (execok[1], &err, sizeof (err));
fprintf (stderr, "%s: %s: %s\n", progname, av[0], strerror (err));
_exit (1);
}
close (fds[0]);
close (execok[1]);
err = 0;
read (execok[0], &err, sizeof (err));
close (execok[0]);
if (err) {
close (fds[1]);
return -1;
}
if (pidp)
*pidp = pid;
return fds[1];
}
static int
getfrom (char **rp, char **mf, FILE *msg, char *line, time_t when)
{
fpos_t pos;
char buf[8192];
char datebuf[25];
char *date = NULL;
char *from = NULL;
char *ffrom;
int size, res;
if (fgetpos (msg, &pos))
return -1;
if (!line && !(line = fgets (buf, sizeof (buf), msg)))
return -1;
if (!strncmp (line, "From ", 5)) {
char *f1 = line + 5, *f2;
for (f2 = f1; *f2 && !isspace (*f2); f2++)
;
if (f2 > f1) {
*f2 = '\0';
from = xstrdup (f1);
while (*++f2 && isspace (*f2));
datebuf[24] = '\0';
date = strncpy (datebuf, f2, 24);
}
if (fgetpos (msg, &pos) || !(line = fgets (buf, sizeof (buf), msg))) {
free (from);
return -1;
}
}
if (strncasecmp (line, "Return-Path:", 12)) {
if (fsetpos (msg, &pos)) {
free (from);
return -1;
}
}
else if (!from) {
char *f1 = line + 12, *f2 = line + strlen (line);
while (isspace (*f1))
f1++;
if (*f1++ == '<') {
while (f2 > f1 && *--f2 != '>')
;
if (f2 > f1) {
*f2 = '\0';
from = strcpy (xmalloc (f2 - f1 + 1), f1);
}
}
}
if (!from)
from = xstrdup ("");
if (!date) {
if (!when)
time (&when);
date = ctime (&when);
}
if (*from)
ffrom = from;
else
ffrom = "MAILER-DAEMON";
res = strlen (date);
if (res > 0 && date[res - 1] == '\n')
date[res - 1] = '\0';
size = strlen (ffrom) + strlen (date) + 9;
*mf = xmalloc (size);
res = sprintf (*mf, "From %s %s\n", ffrom, date);
assert (size == res + 1);
size = sizeof ("Return-Path: <>\n") + strlen (from);
*rp = xmalloc (size);
res = sprintf (*rp, "Return-Path: <%s>\n", from);
assert (size == res + 1);
free (from);
return 0;
}
static int
cmp_maildir_files (const void *_a, const void *_b)
{
const char *a = *(char **) _a;
const char *b = *(char **) _b;
const char *aa, *bb;
u_long na, nb;
if (sscanf (a, "%*[^/]/%ld:", &na) != 1
|| sscanf (b, "%*[^/]/%ld:", &nb) != 1)
return strcmp (a, b);
if (na != nb)
return na < nb ? -1 : 1;
if ((aa = strchr (a, 'M')) && (bb = strchr (b, 'M'))
&& sscanf (aa + 1, "%ld", &na) && sscanf (bb + 1, "%ld", &nb)
&& na != nb)
return na < nb ? -1 : 1;
return strcmp (a, b);
}
void
do_maildir_file (const char *path, char *const *av)
{
struct stat sb;
u_long mtime = 0;
FILE *msg;
char *mf, *rp;
int pfd;
pid_t pid;
char buf[8192];
int n;
msg = fopen (path, "r");
if (!msg) {
fprintf (stderr, "%s: %s: %s\n", progname, path, strerror (errno));
return;
}
if (sscanf (path, "%*[^/]/%ld:", &mtime) != 1
&& !fstat (fileno (msg), &sb))
mtime = sb.st_mtime;
if (getfrom (&rp, &mf, msg, NULL, mtime) < 0) {
fprintf (stderr, "%s: %s: %s\n", progname, path, strerror (errno));
fclose (msg);
return;
}
pfd = spawnit (&pid, av);
if (pfd < 0) {
free (mf);
free (rp);
fclose (msg);
exit (1);
}
if (opt_printfrom)
write (pfd, mf, strlen (mf));
if (opt_printrp)
write (pfd, rp, strlen (rp));
while ((n = fread (buf, 1, sizeof (buf), msg)) > 0
&& write (pfd, buf, n) == n)
;
close (pfd);
if (waitpid (pid, &n, 0) < 0)
fprintf (stderr, "%s: waitpid: %s\n", progname, strerror (errno));
else if (n) {
if (WIFEXITED (n))
fprintf (stderr, "%s: %s: %s exited with status %d\n",
progname, path, av[0], WEXITSTATUS (n));
else if (WIFSIGNALED (n))
fprintf (stderr, "%s: %s: %s exited on signal %d\n",
progname, path, av[0], WTERMSIG (n));
else
fprintf (stderr, "%s: %s: %s abnormal exit status %d\n",
progname, path, av[0], n);
}
free (mf);
free (rp);
fclose (msg);
}
int
exec_maildir (const char *maildir, char *const *av)
{
static const char *subdirs[] = { "cur", "new", NULL };
const char **d;
VECTOR (char *) mv;
int i;
bzero (&mv, sizeof (mv));
if (chdir (maildir)) {
fprintf (stderr, "%s: %s: %s\n", progname, maildir, strerror (errno));
return 1;
}
for (d = subdirs + opt_newonly; *d; d++) {
struct dirent *dep;
DIR *dp = opendir (*d);
if (!dp) {
fprintf (stderr, "%s: %s/%s: %s\n",
progname, maildir, *d, strerror (errno));
continue;
}
while ((dep = readdir (dp)))
if (dep->d_name[0] != '.') {
char *s = xmalloc (strlen (dep->d_name) + strlen (*d) + 2);
sprintf (s, "%s/%s", *d, dep->d_name);
VECTOR_PUSH (&mv, s);
}
closedir (dp);
}
qsort (mv.v_vec, mv.v_size, sizeof (mv.v_vec[0]), cmp_maildir_files);
for (i = 0; i < mv.v_size; i++) {
if (opt_verbose) {
fprintf (stdout, "%s/%s:\n", maildir, mv.v_vec[i]);
fflush (stdout);
}
do_maildir_file (mv.v_vec[i], av);
free (mv.v_vec[i]);
}
VECTOR_CLEAR (&mv);
return 0;
}
int
exec_mbox (const char *mbox, char *const *av)
{
FILE *f = NULL, *o;
char *mf = NULL, *rp = NULL, *line = NULL;
struct lnbuf buf;
bzero (&buf, sizeof (buf));
dotlock (&closeme, &deleteme, mbox, NULL);
f = fopen (mbox, "r");
if (!f) {
fprintf (stderr, "%s: %s: %s\n", progname, mbox, strerror (errno));
cleanup ();
return 1;
}
while (!getfrom (&rp, &mf, f, line, 0)) {
int n;
int blank = 0;
pid_t pid;
int msgid = 0;
int pfd = spawnit (&pid, av);
if (pfd < 0) {
free (mf);
free (rp);
fclose (f);
cleanup ();
return 1;
}
o = fdopen (pfd, "w");
if (!o) {
fprintf (stderr, "%s: fdopen: %s\n", progname, strerror (errno));
free (mf);
free (rp);
fclose (f);
close (pfd);
cleanup ();
return 1;
}
if (opt_printfrom)
fprintf (o, "%s", mf);
if (opt_printrp)
fprintf (o, "%s", rp);
while ((n = readln (&buf, f, -1)) == LNBUF_OK) {
char *p;
if (!strncmp (buf.buf, "From ", 5) && blank) {
line = buf.buf;
break;
}
if (opt_verbose && !msgid && !strncasecmp (buf.buf, "Message-ID:", 11)) {
char *f1 = buf.buf + 11, *f2 = buf.buf + buf.size - 1;
msgid = 1;
while (*f1 && *f1 != '<')
f1++;
while (f2 > f1 && *f2 != '>')
f2 --;
if (*f1 == '<' && *f2 == '>') {
fprintf (stdout, "%.*s:\n", (int) (f2 - f1 + 1), f1);
fflush (stdout);
}
}
if (blank)
fprintf (o, "\n");
if (buf.buf[0] == '\n') {
blank = 1;
msgid = 1;
continue;
}
blank = 0;
for (p = buf.buf; *p == '>'; p++)
;
if (!strncmp (p, "From ", 5))
fprintf (o, "%s", buf.buf + 1);
else
fprintf (o, "%s", buf.buf);
}
if (n != LNBUF_OK) {
if (n == LNBUF_EOFNL)
fprintf (o, "%s\n", buf.buf);
line = NULL;
}
fclose (o);
if (waitpid (pid, &n, 0) < 0)
fprintf (stderr, "%s: waitpid: %s\n", progname, strerror (errno));
else if (n) {
if (WIFEXITED (n))
fprintf (stderr, "%s: %s: %s exited with status %d\n",
progname, mbox, av[0], WEXITSTATUS (n));
else if (WIFSIGNALED (n))
fprintf (stderr, "%s: %s: %s exited on signal %d\n",
progname, mbox, av[0], WTERMSIG (n));
else
fprintf (stderr, "%s: %s: %s abnormal exit status %d\n",
progname, mbox, av[0], n);
}
free (mf);
free (rp);
mf = rp = NULL;
}
free (mf);
free (rp);
fclose (f);
cleanup ();
return 0;
}
static void killed (int) __attribute__ ((noreturn));
void
killed (int sig)
{
cleanup ();
exit (1);
}
static void usage (void) __attribute__ ((noreturn));
static void
usage (void)
{
fprintf (stderr, "usage: %s [-nvFR] mailbox cmd [arg ...]\n", progname);
exit (1);
}
int
main (int argc, char **argv)
{
struct option o[] = {
{ "version", no_argument, NULL, 'V' },
{ NULL, 0, NULL, 0 }
};
int c;
struct stat sb;
VECTOR (char *) av;
struct sigaction sa;
progname = strrchr (argv[0], '/');
if (progname)
progname++;
else
progname = argv[0];
bzero (&sa, sizeof (sa));
sa.sa_handler = SIG_IGN;
sigaction (SIGPIPE, &sa, NULL);
sa.sa_handler = killed;
sigaction (SIGINT, &sa, NULL);
sigaction (SIGTERM, &sa, NULL);
while ((c = getopt_long (argc, argv, "+RFnv", o, NULL)) != -1)
switch (c) {
case 'n':
opt_newonly = 1;
break;
case 'v':
opt_verbose = 1;
break;
case 'R':
opt_printrp = 0;
break;
case 'F':
opt_printfrom = 0;
break;
case 'V':
version (progname, 1);
exit (0);
break;
default:
usage ();
break;
}
if (optind + 2 > argc)
usage ();
bzero (&av, sizeof (av));
for (c = optind + 1; c < argc; c++)
VECTOR_PUSH (&av, argv[c]);
VECTOR_PUSH (&av, NULL);
if ((cwdfd = open (".", O_RDONLY)) < 0) {
fprintf (stderr, "%s: \".\": %s\n", progname, strerror (errno));
exit (1);
}
if (stat (argv[optind], &sb) < 0) {
fprintf (stderr, "%s: %s: %s\n", progname, argv[optind], strerror (errno));
exit (1);
}
if (S_ISDIR (sb.st_mode))
exit (exec_maildir (argv[optind], av.v_vec));
else
exit (exec_mbox (argv[optind], av.v_vec));
}
syntax highlighted by Code2HTML, v. 0.9.1