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