Logo Search packages:      
Sourcecode: mailavenger version File versions  Download package

local.c

/* $Id$ */

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

int garbage;

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)
    garbage = 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;
}

Generated by  Doxygen 1.6.0   Back to index