/* $Id: local.c,v 1.31 2005/12/07 06:45:43 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 #include #include "getopt_long.h" char opt_separator = '+'; const char *opt_touser; const char *opt_extra; const char *opt_smuser; #ifdef PATH_MAIL_LOCAL const char *opt_fallback = PATH_MAIL_LOCAL; #else /* !PATH_MAIL_LOCAL */ const char *opt_fallback; #endif /* !PATH_MAIL_LOCAL */ const char *opt_bouncefrom = ""; const char *opt_sendmail = "sendmail -oi -os -oee"; const char *opt_default = "./Mailbox"; int opt_qmailexit; char *progname; int truncfd = -1; off_t truncpos; char *deleteme; static char *tmp_path; static struct stat tmp_sb; static pid_t chldpid = -1; static void doexit (int rcode) __attribute__ ((noreturn)); static void cleanup (void) { struct stat sb; if (truncfd >= 0) ftruncate (truncfd, truncpos); if (tmp_path && !lstat (tmp_path, &sb) && tmp_sb.st_dev == sb.st_dev && tmp_sb.st_ino == sb.st_ino) unlink (tmp_path); tmp_path = NULL; if (deleteme) unlink (deleteme); if (chldpid > 0) if (killpg (chldpid, SIGTERM) < 0) kill (chldpid, SIGTERM); } static void catch (int sig) { doexit (EX_TEMPFAIL); } static void close_on_exec (int fd) { if (fcntl (fd, F_SETFD, 1) < 0) { perror ("F_SETFD"); doexit (EX_OSERR); } } void fallback (void) __attribute__ ((noreturn)); void fallback (void) { char *av[6]; if (!opt_fallback || !*opt_fallback) { fprintf (stderr, "%s: no fallback mail.local program specified\n", progname); doexit (EX_CONFIG); } av[0] = (char *) opt_fallback; if (opt_from) { av[1] = "-f"; av[2] = (char *) opt_from; av[3] = "-d"; av[4] = (char *) opt_touser; av[5] = NULL; } else { av[1] = "-d"; av[2] = (char *) opt_touser; av[3] = NULL; } execv (av[0], av); fprintf (stderr, "%s: cannot exec %s: %s\n", progname, opt_fallback, strerror (errno)); exit (EX_OSERR); } static void push_arg (char ***avp, int *acp, const char *arg) { *avp = xrealloc (*avp, ++*acp * sizeof (**avp)); if (!arg) (*avp)[*acp-1] = NULL; else { (*avp)[*acp-1] = xmalloc (strlen (arg) + 1); strcpy ((*avp)[*acp-1], arg); } } static void free_vec (char **av, int ac) { int i; for (i = 0; i < ac; i++) free (av[i]); free (av); } static int hard_error (int rcode) { switch (rcode) { case 64: case 65: case 67: case 70: case 76: case 77: case 78: case 100: case 112: return 1; default: return 0; } } static int soft_error (int rcode) { switch (rcode) { case 0: case 99: return 0; default: return !hard_error (rcode); } } static int exit_error (int rcode) { if (rcode == 99 && opt_qmailexit) return 99; if (rcode == 67 && opt_qmailexit) /* qmail does not treat as hard error */ return 100; if (hard_error (rcode)) return opt_qmailexit ? 100 : (rcode < 64 || rcode > 78 ? EX_SOFTWARE : rcode); if (soft_error (rcode)) return opt_qmailexit ? 111 : EX_TEMPFAIL; return 0; } static void doexit (int rcode) { exit (exit_error (rcode)); } int parent (int cfd, int mfd) { char **av = NULL; int ac = 0; FILE *client = fdopen (dup (cfd), "r"); struct lnbuf buf; char *smb, *sm; char *arg; int runsm = 0; int n, last_n = LNBUF_OK; int cs, ss; int frompos; if (!client) { perror ("fdopen"); return EX_OSERR; } bzero (&buf, sizeof (buf)); shutdown (cfd, SHUT_WR); smb = xmalloc (1 + strlen (opt_sendmail)); sm = strcpy (smb, opt_sendmail); while ((arg = strnnsep (&sm, " "))) push_arg (&av, &ac, arg); free (smb); if (!ac) { fprintf (stderr, "no/bad sendmail program specified\n"); free_vec (av, ac); return EX_USAGE; } push_arg (&av, &ac, "-f"); frompos = ac; if (!opt_from || !*opt_from || !strcmp (opt_from, "@") || !strcmp (opt_from, "MAILER-DAEMON")) push_arg (&av, &ac, opt_bouncefrom); else push_arg (&av, &ac, opt_from); push_arg (&av, &ac, "--"); while ((n = readln (&buf, client, 1080))) { switch (n) { case LNBUF_TOOBIG: if (last_n != LNBUF_TOOBIG) fprintf (stderr, "ignoring address that is too long\n"); break; case LNBUF_NOMEM: free_vec (av, ac); fprintf (stderr, "out of memory reading line from child\n"); return EX_OSERR; break; case LNBUF_IOERR: free_vec (av, ac); fprintf (stderr, "Error reading from child\n"); return EX_OSERR; break; case LNBUF_EOFNL: fprintf (stderr, "ignoring incomplete line\n"); break; case LNBUF_OK: if (last_n != LNBUF_TOOBIG) { if (*buf.buf == '&') { runsm = 1; buf.buf[buf.size - 1] = '\0'; push_arg (&av, &ac, buf.buf + 1); } else if (*buf.buf == '<') { free (av[frompos]); buf.buf[buf.size - 1] = '\0'; av[frompos] = strcpy (xmalloc (buf.size), buf.buf + 1); } else fprintf (stderr, "ignoring invalid line\n"); } break; } last_n = n; } cs = -1; if (waitpid (chldpid, &cs, 0) == -1) { chldpid = -1; perror ("waitpid"); free_vec (av, ac); return EX_OSERR; } chldpid = -1; if (!WIFEXITED (cs)) { free_vec (av, ac); return EX_TEMPFAIL; } cs = WEXITSTATUS (cs); if (runsm && !exit_error (cs)) { push_arg (&av, &ac, NULL); switch ((chldpid = fork ())) { case -1: perror ("fork"); return EX_OSERR; break; case 0: { struct passwd *pw = getpwnam (opt_smuser ? opt_smuser : opt_touser); int err = pw ? become_user (pw, 1, 0) : 0; #if 0 int i; fprintf (stderr, "running %s", av[0]); for (i = 1; av[i]; i++) fprintf (stderr, " %s", av[i]); fprintf (stderr, "\n"); #endif if (err) _exit (err); if (lseek (mfd, SEEK_SET, 0) == -1) { perror ("lseek"); _exit (EX_OSERR); } dup2 (mfd, 0); execvp (av[0], av); perror (av[0]); _exit (EX_CONFIG); break; } } if (waitpid (chldpid, &ss, 0) == -1) { chldpid = -1; perror ("waitpid"); return EX_OSERR; } chldpid = -1; ss = WIFEXITED (ss) ? WEXITSTATUS (ss) : EX_TEMPFAIL; if (soft_error (ss) && !soft_error (cs)) cs = ss; else if (hard_error (ss) && (cs == 0 || cs == 99)) cs = ss; } free_vec (av, ac); return cs; } void mysetenv (const char *var, const char *val, int len) { char *env = xmalloc (strlen (var) + 2 + (len >= 0 ? (u_int) len : strlen (val))); if (len < 0) sprintf (env, "%s=%s", var, val); else sprintf (env, "%s=%.*s", var, len, val); putenv (env); } static void initenv (void) { const char *from = opt_from; if (!from || !*from || !strcmp (from, "@") || !strcmp (from, "MAILER-DAEMON")) from = ""; mysetenv ("UFLINE", msg_ufline, -1); mysetenv ("SENDER", from, -1); if (msg_rpline) mysetenv ("RPLINE", msg_rpline, -1); if (opt_recip) { char *p; mysetenv ("RECIPIENT", opt_recip, -1); p = strrchr (opt_recip, '@'); if (p) { mysetenv ("LOCAL", opt_recip, p - opt_recip); mysetenv ("RECIPIENT_LOCAL", opt_recip, p - opt_recip); mysetenv ("HOST", p + 1, -1); mysetenv ("RECIPIENT_HOST", p + 1, -1); for (p = getenv ("RECIPIENT_LOCAL"); *p; p++) *p = tolower (*p); for (p = getenv ("RECIPIENT_HOST"); *p; p++) *p = tolower (*p); } } if (msg_dtline) mysetenv ("DTLINE", msg_dtline, -1); if (opt_touser) mysetenv ("USER", opt_touser, -1); if (opt_extra) { mysetenv ("EXT", opt_extra, -1); if (*opt_extra) { char extn[80]; const char *p = opt_extra; int i = 1; while ((p = strchr (p, opt_separator))) { snprintf (extn, sizeof (extn), "EXT%d", i++); mysetenv (extn, ++p, -1); } } } if (opt_separator) mysetenv ("SEPARATOR", &opt_separator, 1); if (opt_sendmail) mysetenv ("SENDMAIL", opt_sendmail, -1); mysetenv ("SENDFROM", *from ? from : opt_bouncefrom, -1); } void launch (void) { struct passwd *pw = validuser (opt_touser); int wfd, rfd; int fds[2]; pid_t pid; if (!pw) fallback (); initfile (&tmp_path, &wfd, &rfd, &tmp_sb); copymsg (wfd, stdin); initenv (); if (socketpair (AF_UNIX, SOCK_STREAM, 0, fds) < 0) { perror ("socketpair"); doexit (EX_OSERR); } pid = fork (); if (pid == -1) { perror ("fork"); doexit (EX_OSERR); } if (!pid) { tmp_path = NULL; pid = getpid (); close (wfd); close (fds[0]); close_on_exec (fds[1]); _exit (child (pw, fds[1], rfd)); } chldpid = pid; close (rfd); close_on_exec (fds[0]); close (fds[1]); doexit (parent (fds[0], wfd)); } static void usage (void) __attribute__ ((noreturn)); static void usage (void) { fprintf (stderr, "usage: %s [-f sender] [-D recip] [-a extra] [-d] user\n", progname); doexit (EX_USAGE); } int main (int argc, char **argv) { struct option o[] = { { "version", no_argument, NULL, 'v' }, { "smuser", required_argument, NULL, 'A' }, { "fallback", required_argument, NULL, 'M' }, { "sendmail", required_argument, NULL, 0x100|'P' }, { "separator", required_argument, NULL, 'S' }, { "tmpfile", required_argument, NULL, 'T' }, { "to", required_argument, NULL, 'D' }, { "qmailexit", no_argument, NULL, 'Q' }, { "fcntl", no_argument, NULL, 'P' }, { NULL, 0, NULL, 0 } }; int c; progname = strrchr (argv[0], '/'); if (progname) progname++; else progname = argv[0]; while ((c = getopt_long (argc, argv, "+BD:a:d:f:r:YtP", o, NULL)) != -1) switch (c) { case 'B': opt_bouncefrom = "@"; break; case 'D': opt_recip = optarg; break; case 'Q': opt_qmailexit = 1; break; case 'a': if (opt_extra) usage (); opt_extra = optarg; break; case 'd': if (opt_touser) usage (); opt_touser = optarg; break; case 'f': case 'r': if (opt_from) usage (); opt_from = optarg; break; case 'v': version (progname, 0); if (opt_fallback && *opt_fallback) fprintf (stderr, "fallback mailer is %s\n", opt_fallback); exit (0); break; case 'A': opt_smuser = optarg; break; case 'P': opt_fcntl = 1; break; case 0x100|'P': opt_sendmail = optarg; break; case 'S': if (strlen (optarg) != 1) usage (); opt_separator = *optarg; break; case 'T': if (optarg[0] != '/' || optarg[strlen (optarg) - 1] != 'X') usage (); opt_tmplate = optarg; break; case 'Y': case 't': break; case 'M': opt_fallback = optarg; break; default: usage (); break; } if (optind + 1 < argc) usage (); if (optind + 1 == argc) { if (opt_touser) usage (); opt_touser = argv[optind]; } if (!opt_touser) usage (); if (opt_separator && !opt_extra) { char *p = strchr (opt_touser, opt_separator); if (p && *p) { *p++ = '\0'; opt_extra = p; } } if (opt_touser[0] == '.' || strstr (opt_touser, "..") || (opt_extra && (strchr (opt_extra, '/') || strstr (opt_extra, "..")))) { fprintf (stderr, "%s: %s%.1s%s.. malformed local mailbox name\n", progname, opt_touser, opt_extra ? &opt_separator : "", opt_extra ? opt_extra : ""); doexit (EX_USAGE); } if (!opt_from) opt_from = getlogin (); atexit (cleanup); signal (SIGHUP, catch); signal (SIGINT, catch); signal (SIGTERM, catch); umask (077); launch (); return 0; }