/* deliver.c
   Copyright (C) 1995-1996 Eberhard Mattes

This file is part of GNU Emacs.

GNU Emacs 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.

GNU Emacs 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 GNU Emacs; see the file COPYING.  If not, write to
the Free Software Foundation, Inc., 59 Temple Place - Suite 330,
Boston, MA 02111-1307, USA.  */

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <io.h>
#include <fcntl.h>
#include <time.h>
#include <errno.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <share.h>
#include "mailutil.h"


static char *dir;
static int max_attempts = 16;


static int copy_message (FILE *f, const char *fname)
{
  char *p;
  int len, in_header, rc;
  time_t now;

  in_header = 1;
  while ((rc = read_line (stdin, RL_CTRLZ_EOF, &p)) == EX_OK)
    {
      if (*p == 0x1a)
        break;
      len = strlen (p);
      if (len == 0)
        in_header = 0;
      if (strncmp (p, "From ", 5) == 0)
        {
          if (!in_header)
            putc ('>', f);
          else if (p[len-1] == ' ' && p[len-2] == ' ')
            {
              /* Work around a bug in IBM's sendmail: Sometimes,
                 sendmail does not replace $d with the current date.
                 Rather, it just drops $d and the line will end with
                 the two spaces preceding $d in the `Dl' line of
                 sendmail.cf). */

              time (&now);
              if (fprintf (f, "%s%s", p, ctime (&now)) == EOF)
                break;
              p = NULL;         /* Don't write the line again */
            }
        }
      if (p != NULL)
        if (fwrite (p, len, 1, f) != 1 || putc ('\n', f) == EOF)
          break;
    }
  if (rc != EX_OK && rc != EX_EOF)
    {
      perror ("read_line()");
      return (rc);
    }
  if (putc ('\n', f) == EOF)
    {
      perror (fname);
      return (EX_IOERR);
    }
  return (EX_OK);
}


static void deliver_lamailer (const char *user)
{
  time_t t;
  char fname[256];
  FILE *idx, *out;
  int i, rc, fd;

  /* Open the index file. */

  sprintf (fname, "%s%s", dir, "inbox.ndx");
  idx = NULL;
  for (i = 0; i < max_attempts; ++i)
    {
      idx = _fsopen (fname, "a", SH_DENYWR);
      if (idx != NULL)
        break;
      if (errno != EACCES)
        {
          perror (fname);
          exit (EX_CANTCREAT);
        }
      sleep (1);
    }
  if (idx == NULL)
    exit (EX_TEMPFAIL);

  /* Create a unique filename. */

  time (&t);
  fd = -1;
  for (i = 0; i < 1000; ++i)
    {
      sprintf (fname, "%s%.8lx.%03d", dir, (unsigned long)t, i);
      fd = sopen (fname, O_WRONLY | O_CREAT | O_EXCL, SH_DENYRW,
                  S_IREAD | S_IWRITE);
      if (fd != -1)
        break;
      if (errno != EEXIST)
        {
          perror (fname);
          exit (EX_CANTCREAT);
        }
    }
  if (fd == -1)
    exit (EX_CANTCREAT);

  out = fdopen (fd, "w");
  if (out == NULL)
    {
      perror ("fdopen()");
      exit (EX_OSERR);
    }
  rc = copy_message (out, fname);
  fclose (out);
  if (rc != EX_OK)
    {
      remove (fname);
      fclose (idx);
      exit (rc);
    }

  fprintf (idx, "         %-8.8s %-8.8s %.8lx %03d              "
           "%-8.8s %-5.5s            %c   "
           "%s\1%s\1%s\1%s\n",
           "user", "host", (unsigned long)t, i,
           "94/11/11", "11:11", ' ',
           "fullname", user, "", "user@host");
  fclose (idx);
}


static void deliver_mbox (const char *user)
{
  char fname[256];
  char line[256];
  int rc, i;
  long old_length;
  FILE *out, *usr;
  char *p;

  if ((p = getenv ("DELIVER.USR")) != NULL)
    strcpy (fname, p);
  else if ((p = getenv ("ETC")) != NULL)
    sprintf (fname, "%s/deliver.usr", p);
  else
    strcpy (fname, "/etc/deliver.usr");
  usr = fopen (fname, "r");
  if (usr == NULL)
    {
      perror (fname);
      exit (EX_OSERR);
    }
  rc = EX_NOUSER;
  while (fgets (line, sizeof (line), usr) != NULL)
    {
      p = strchr (line, '\n');
      if (p != NULL) *p = 0;
      if (line[0] != '#' && (stricmp (line, user) == 0
                             || strcmp (line, "*") == 0))
        {
          rc = 0;
          break;
        }
    }
  fclose (usr);
  if (rc != 0)
    {
      fprintf (stderr, "User \"%s\" unknown\n", user);
      exit (rc);
    }

  sprintf (fname, "%s%s", dir, user);
  out = NULL;
  for (i = 0; i < max_attempts; ++i)
    {
      out = _fsopen (fname, "a+", SH_DENYRW);
      if (out != NULL)
        break;
      if (errno != EACCES)
        {
          perror (fname);
          exit (EX_CANTCREAT);
        }
      sleep (1);
    }
  if (out == NULL)
    exit (EX_TEMPFAIL);

  old_length = filelength (fileno (out));
  if (old_length == -1)
    {
      perror (fname);
      exit (EX_CANTCREAT);
    }
  rc = copy_message (out, fname);
  fclose (out);
  if (rc != EX_OK)
    {
      ftruncate (fileno (out), old_length);
      exit (rc);
    }
}


int main (int argc, char *argv[])
{
  int i, len, mode;

  i = 1; mode = 0; dir = "";

  /* IBM's sendmail seems to set the argv vector incorrectly: if only
     one argument ($u) is used, garbage is passed in argv[1].  If two
     or more arguments are used, all arguments are passed; however,
     the first is passed in argv[1] instead of argv[0].  Here, we skip
     the first word if it is "deliver". */

  if (i < argc && strcmp (argv[i], "deliver") == 0)
    ++i;
  while (i < argc)
    if (strcmp (argv[i], "-L") == 0)
      mode = 'L', ++i;
    else if (strcmp (argv[i], "-M") == 0)
      mode = 'M', ++i;
    else if (strncmp (argv[i], "-D", 2) == 0)
      dir = argv[i] + 2, ++i;
    else if (strncmp (argv[i], "-R", 2) == 0)
      max_attempts = atoi (argv[i] + 2) + 1, ++i;
    else
      break;

  if (argc - i != 1)
    return (EX_USAGE);

  len = strlen (dir);
  if (len > 0 && strchr ("/\\:", dir[len-1]) == NULL)
    {
      char *tmp = malloc (len + 2);
      if (tmp == NULL)
        {
          fprintf (stderr, "Out of memory\n");
          exit (EX_OSERR);
        }
      strcpy (tmp, dir);
      strcat (tmp, "/");
      dir = tmp;
    }

  switch (mode)
    {
    case 'L':
      deliver_lamailer (argv[i]);
      break;
    case 'M':
      deliver_mbox (argv[i]);
      break;
    }
  return (EX_OK);
}
