/* $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 <ctype.h>
#include <pwd.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <sys/socket.h>
#include <signal.h>
#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;
}
syntax highlighted by Code2HTML, v. 0.9.1