/*
 * mail [ person ] ... [ -g grpname ] ... [ alias ] ... mail [-lpqr] [ -s
 * subject] [ -f file ]
 * 
 * This is an enhanced version of the UNIX 5.0 mail command.
 */

#include   <stdio.h>
#include   <sys/types.h>
#include   <pwd.h>
#include   <grp.h>
#include   <utmp.h>
#include   <signal.h>
#include   <ctype.h>
#include   <time.h>
#include   <sys/stat.h>
#include   <setjmp.h>
#include   <sys/utsname.h>
#include   "mail.h"

void toc ();

struct passwd *getpwuid (), *getpwent (), *getpwnam ();
struct group *getgrnam ();
struct utsname utsn;

char *calloc (), *getdate (), *strtok (), *Mailpgm;
char tz[16], sendto[256], *mailfile, *thissys, curlock[50];
char line[LSIZE], *home, *my_name, name[40], *nameptr, *getlogin ();
char **alias (), *finddest (), *frflsh (), *strchr (), *strpbrk (), *strcpy ();
char *malloc (), lfil[256], *ctime (), *getenv (), *strrchr (), *mktemp ();
char *unalias (), cclist[LSIZE], resub[80], reto[80], subject[70];
char *lettmp = "/tmp/maXXXXXX";
char *edtmp = "/tmp/maedXXXXXX";
char deadansw[] = "dead.answer";
char from[] = "From ";
char TO[] = "To: ";
char PREPEND[] = "    ";
char maildir[128];		/* prepended with a '/' */
char ORG[128];
char maillock[] = ".lock";
char dead[] = "dead.letter";
char rmtbuf[] = "/tmp/marXXXXXX";
char *rmtmsg = " remote from %s\n";
char rmtfrom[] = " remote from ";
char *forwmsg = " forwarded by %s\n";
char fwrdmsg[] = " forwarded by ";
char frwrd[] = "Forward to ";
char NoLetter[] = "Not positioned on a letter\n";
char mbox[] = "/mbox";

FILE *tmpf, *malf, *logf, *readinput ();
FILE *answf;			/* answer file pointer into lettmp */

int error, nlet = 0, curlet, locked, changed, forward, delete (), flgf = 0;
int flgp = 0, flge = 0, flgl = 0, delflg = 1, savdead (), (*saveint) ();
int (*setsig ())(), 
umsave, mypclose ();
int ttyin;			/* input from a tty? */
int ttyout;			/* output to a tty? */
int rmail;			/* restricted mail; run by uucp */
int DestSub;			/* Any alias or group substitutions? */
int sflag = 0;			/* subject on cmd line? */

long ftell ();
long iop;

jmp_buf sjbuf;

main (argc, argv)
char **argv;
{
  register i;
  register char *p;
  char *Maillog;

  loadcfg (MAILCFG);
  Mailpgm = argv[0];
  if ((Maillog = getenv ("MAILLOG")) == NULL || *Maillog == '\0')
  {
    logf = NULL;
  }
  else
    logf = fopen (Maillog, "a");
  cclist[0] = NULL;
  resub[0] = NULL;
  reto[0] = NULL;
  if ((p = getenv ("TZ")) == NULL)
    tz[0] = 0;
  else
  {
    tz[0] = ' ';
    strncpy (&tz[1], p, 3);
  }
  home = getenv ("HOME");
  if ((home == NULL) || (strlen (home) == 0))
    home = ".";
  umsave = umask (MASK);
  /* setbuf(stdout, malloc(BUFSIZ)); */
  strcpy (lettmp, mktemp (lettmp));
  unlink (lettmp);
  uname (&utsn);
  thissys = utsn.nodename;
  /* thissys = SYSNAME; */

  /*
   * my_name = getenv("LOGNAME"); if ((my_name == NULL) || (strlen(my_name)
   * == 0)) my_name = getlogin(); if ((my_name == NULL) || (strlen(my_name)
   * == 0))
   */
  my_name = getpwuid ((realid = getuid ()))->pw_name;
  /* append the real user name */

  /*
   * strcpy(name, my_name); strcat(my_name, " ("); strcat(my_name,
   * unalias(name, 0)); strcat(my_name, ")  ");
   */
  if (setjmp (sjbuf))
    done ();

  /*
   * for (i=2; i<SIGCLD; i++) setsig(i, delete);
   */
  setsig (1, done);
  /* setsig (SIGSEGV, SIG_DFL); */
  tmpf = fopen (lettmp, "w");
  if (tmpf == NULL)
  {
    fprintf (stderr, "%s: cannot open %s for writing\n", Mailpgm, lettmp);
    error = 2;
    done ();
  }
  ttyin = isatty (0);
  ttyout = isatty (1);
  if (ttyout)
    flgl = 1;
  if ((p = strrchr (argv[0], '/')) == NULL)
    p = argv[0];
  else
    p++;
  if (*p == 'r')
    rmail++;
  if (!rmail &&			       /* no favors for rmail */
      (argc == 1 || argv[1][0] == '+' ||
       (argv[1][0] == '-' && argv[1][1] != 'g' && argv[1][1] != 's')))
    printmail (argc, argv);
  else
    sendmail (argc, argv);
  done ();
}

int (*setsig (i, f)) ()
int i;
int (*f) ();
{
  register int (*rc) ();

  if ((rc = signal (i, SIG_IGN)) != SIG_IGN)
    signal (i, f);
  return (rc);
}

openmail ()
{
  int aret, stret;
  struct stat stbuf;
  char frwrdbuf[256];

  stret = stat (mailfile, &stbuf);
  if ((aret = access (mailfile, 4)) == 0)
    malf = fopen (mailfile, "r");
  if (!stret && aret)
  {
    fprintf (stderr, "%s: permission denied!\n", Mailpgm);
    error = 2;
    return -1;
  }
  else if (flgf && (aret || (malf == NULL)))
  {
    fprintf (stderr, "%s: cannot open %s\n", Mailpgm, mailfile);
    error = 2;
    return -1;
  }
  else if (aret || (malf == NULL) || (stbuf.st_size == 0))
  {
    if (!flge)
      fprintf (stdout, "No mail.\n");
    error = 1;
    return -1;
  }
  mailock (mailfile);
  if (areforwarding (mailfile))
  {
    if (flge)
    {
      unmailock ();
      error = 1;
      return -1;
    }
    printf ("Your mail is being forwarded to ");
    fseek (malf, (long) (sizeof (frwrd) - 1), 0);
    fgets (frwrdbuf, sizeof (frwrdbuf), malf);
    printf ("%s", frwrdbuf);
    if (getc (malf) != EOF)
      printf ("and your mailbox contains extra stuff\n");
    unmailock ();
    return -1;
  }
  if (flge)
  {
    unmailock ();
    return -1;
  }
  copymt (malf, tmpf);
  fclose (malf);
  fclose (tmpf);
  unmailock ();
  tmpf = fopen (lettmp, "r");
  changed = 0;
  return 0;
}


help ()
{
  static char *mess[] =
  {
    "q\t\tquit",
    "x\t\texit without changing mail",
    "f\t\tsave changes and reopen mail file",
    "p\t\tprint letter",
    "p|cmd\t\tpipe to cmd",
    "nnn\t\tprint letter number nnn",
    "s [file]\tsave (default $HOME/mailbox/misc) (also deletes)",
    "w [file]\tsame without header (no delete)",
    "a\t\treply (answer) mail",
    "r\t\tsame as 'a'",
    "m users\t\tforward to users",
    "M users\t\tcomment and forward users",
    "-\t\tprint previous",
    "+\t\tnext",
    "<\t\tprint previous undeleted letter",
    ">\t\tprint next undeleted letter",
    "d\t\tdelete letter",
    "dq\t\tdelete letter and quit",
    "u\t\tundelete letter",
    "l\t\tlist table of contents",
    "i\t\tsame as 'l'",
    "!cmd\t\texecute cmd",
    "cd[!] [directory]\tchange directory",
    (char *) NULL
  };
  char **mp;

  for (mp = mess; *mp != NULL; mp++)
    fprintf (stderr, "%s\n", *mp);
}

reenter (s, n)
char *s;
int n;
{
  fprintf (stdout, "Please reenter file name\n    or !command\n    or RETURN to cancel: ");
  fflush (stdout);
  if (fgets (s, n, stdin) == NULL)
    s[0] = '\0';
  else
    s[strlen (s) - 1] = '\0';	       /* kill newline */
}


/* Any new mail arrive in the meantime? */
newmail ()
{
  struct stat stbuf;

  stat (mailfile, &stbuf);
  return (stbuf.st_size != let[nlet].adr && !flgf);
}

copyback ()			/* copy temp or whatever back to /usr/mail */
{
  register i, n, c;
  int new = 0, aret;
  struct stat stbuf;

  signal (SIGINT, SIG_IGN);
  signal (SIGHUP, SIG_IGN);
  signal (SIGQUIT, SIG_IGN);
  mailock (mailfile);
  stat (mailfile, &stbuf);
  if (stbuf.st_size != let[nlet].adr)
  {
    /* new mail has arrived */
    malf = fopen (mailfile, "r");
    if (malf == NULL)
    {
      fprintf (stdout, "%s: can't re-read %s\n", Mailpgm, mailfile);
      error = 2;
      done ();
    }
    fseek (malf, let[nlet].adr, 0);
    fclose (tmpf);
    tmpf = fopen (lettmp, "r+");
    fseek (tmpf, let[nlet].adr, 0);
    n = 0;
    while ((c = fgetc (malf)) != EOF)
    {
      fputc (c, tmpf);
      if (c == '\n')
	n++;
      if (!isascii (c))
	fprintf (stderr, "%s: Nonascii character (0%o) \
encountered during rewrite of %s\n", Mailpgm,
		 mailfile);
    }
    fclose (malf);
    fclose (tmpf);
    tmpf = fopen (lettmp, "r");
    let[nlet].llns = n;
    let[++nlet].adr = stbuf.st_size;
    new = 1;
  }
  if ((aret = access (mailfile, 2)) == 0)
    malf = fopen (mailfile, "w");
  if ((malf == NULL) || (aret))
  {
    fprintf (stderr, "%s: can't rewrite %s\n", Mailpgm, mailfile);
    error = 2;
    done ();
  }
  n = 0;
  for (i = 0; i < nlet; i++)
  {
    if (let[i].change == 'd' || let[i].change == 's')
      continue;
    copylet (i, malf, ORDINARY, "");
    n++;
  }
  fclose (malf);

  /*
   * The standard /bin/mail command will delete the mail file if it becomes
   * empty and it has default permissions. If it has nondefault permissions,
   * it is not deleted so that the permissions are preserved. Should we
   * delete empty mailbox with default permissions?
   */

  /*
   * if ((n == 0) && ((stbuf.st_mode & 0777) == MFMODE)) if (unlink(mailfile)
   * < 0) perror("mailfile");
   */
  if (new && !flgf)
    fprintf (stdout, "new mail arrived\n");
  unmailock ();
}

allblank (s)
register char *s;
{
  while (*s)
  {
    if (!isspace (*s))
      return 0;
    ++s;
  }
  return 1;
}

char *
topic (lp)
char *lp;
{
  static char *keyword[] =
  {
    "SUBJECT:",
    "Subject:",
    "RE:",
    "Re:",
    "re:",
    NULL
  };
  register char *sp, **kp;
  int kn;

  /* skip over leading white space */
  while (*lp && isspace (*lp))
    ++lp;

  /* see if remainder of line begins with a subject keyword */
  sp = NULL;
  for (kp = keyword; *kp; ++kp)
  {
    if (strncmp (lp, *kp, (kn = strlen (*kp))) == 0)
    {
      sp = lp + kn;
      break;
    }
  }

  /* return pointer to part of line after keyword or NULL */
  return sp;
}

void
toc ()
{
  extern int bfrlns;
  extern int lpp;

  int i, j, pgflg = 0, more;
  char tocline[LSIZE], name[70], *fromtail ();

  /* Assume direct access to mail (-l flag) */
  flgl = 1;

  if ((nlet + 1) >= 18 && ttyout)
  {
    pgflg = 1;
    bfrlns = ((lpp - 22) < 0) ? 0 : (lpp - 22);
    pgflsh (0);			       /* scratch anything in buffer */
  }

  if (nlet == 1)
    sprintf (tocline, "You have %d message...\n\n", nlet);
  else

    /*
     * sprintf(tocline, "You have %d messages in %s\n\n", nlet, mailfile);
     */
    sprintf (tocline, "You have %d messages...\n\n", nlet);
  pgflg ? pgputs (tocline) : fputs (tocline, stdout);

  sprintf (tocline, "%3s  %-3.3s  %-12.12s  %-20.20s %s\n",
	   " NO", "LNS", "RECEIVED", "FROM", "SUBJECT");
  pgflg ? pgputs (tocline) : fputs (tocline, stdout);

  for (i = 0, more = 1; i < nlet && more == 1; ++i)
  {
    j = forward ? i : (nlet - i - 1);
    M_change = let[j].change ? let[j].change : ' ';
    M_llns = let[j].llns;
    M_postmark = let[j].postmark ? let[j].postmark : "";
    strcpy (name, unalias (let[j].sender, 0));
    if (strcmp (name, let[j].sender) == NULL)
      strcpy (name, fromtail (let[j].sender, 20));
    sprintf (tocline, "%3d%c %3d  %-12.12s  %-20.20s %-.40s\n",
	     SHOW (i),
	     M_change, M_llns, M_postmark,
    /* unalias(fromtail(let[j].sender, 20)), */
	     name,
	     let[j].subject ? let[j].subject : "");

    if (pgflg)
    {
      more = pgputs (tocline);
    }
    else
      fputs (tocline, stdout);
  }

  if (pgflg)
  {
    pgputs ("\n");
    pgflsh (1);
  }
  else
    fputs ("\n", stdout);
}

char *
getdate (gdline)
char *gdline;
{
  static char *month[] =
  {
    "Jan", "Feb", "Mar", "Apr", "May", "Jun",
    "Jul", "Aug", "Sep", "Oct", "Nov", "Dec",
    (char *) NULL
  };
  char **mp, *cp, *tp;
  int i, n;

  /* Find the month in the "From" line */
  for (mp = month; *mp != NULL; mp++)
  {
    if ((i = sindex (gdline, *mp)) >= 0)
    {
      /* Scan past spaces after month */
      cp = &gdline[i + 3];
      while (*cp && isspace (*cp))
	++cp;

      /* Scan past digits in day */
      while (*cp && !isspace (*cp))
	++cp;

      /* Include time if it's there */
      if ((tp = strchr (cp, ':')) != NULL
	  && isdigit (tp[-2])
	  && isdigit (tp[-1])
	  && isdigit (tp[1])
	  && isdigit (tp[2]))
	cp = &tp[3];

      /* Save postmark (e.g., "Aug  2") */
      n = cp - &gdline[i];
      if ((cp = calloc (n + 1, 1)) != NULL)
      {
	strncpy (cp, &gdline[i], n);
	cp[n] = '\0';
      }
      break;
    }
  }
  return (*mp == NULL || cp == NULL) ? NULL : cp;
}


char *
fromtail (s, n)
char *s;
int n;
{
  static char sbuf[32];
  char *r, *or;

  if (strlen (s) <= n)
    return s;

  for (or = s; (strlen (or) + 1) > n && (r = strchr (or, '!')); or = r + 1)
    ;

  if (or == s)
    return s;

  strcpy (sbuf, "..!");		       /* assumes uname() is at least 1 char */
  strcat (sbuf, or);
  return sbuf;
}

areforwarding (s)
char *s;
{
  FILE *fd;
  char fbuf[256], *p;
  int c;

  fd = fopen (s, "r");
  if (fd == NULL)
    return (0);
  fread (fbuf, sizeof (frwrd) - 1, 1, fd);
  if (strncmp (fbuf, frwrd, sizeof (frwrd) - 1) == 0)
  {
    for (p = sendto; (c = getc (fd)) != EOF && c != '\n';)
      if (c != ' ')
	*p++ = c;
    *p = 0;
    fclose (fd);
    return (1);
  }
  fclose (fd);
  return (0);
}

delete (i)
{
  setsig (i, delete);
  if (i > 3)
  {
    fprintf (stderr, "%s: WARNING: error signal %d\n", Mailpgm, i);
    if (i == 11)
    {
      puts ("SIGSEGV - aborted!");
      exit (2);
    }
    if (fork () == 0)
    {
      setsig (i, delete);
      return;
    }
  }
  else
    fprintf (stderr, "\n");
  if (delflg)
    longjmp (sjbuf, 1);
  done ();
}

done ()
{
  unmailock ();
  unlink (lettmp);
  unlink (rmtbuf);
  unlink (edtmp);
  exit (error);
}

mailock (file)
char *file;
{
  int f, i;

  if (locked)
    return;
  cat (curlock, file, maillock);
  for (i = 0; i < 10; i++)
  {
    f = creat (curlock, 0);
    if (f >= 0)
    {
      close (f);
      locked = 1;
      return;
    }
    sleep (2);
  }
  fprintf (stderr, "%s: %s not creatable after %d tries\n", Mailpgm, curlock, i);
  error = 2;
  done ();
}

unmailock ()
{
  if (locked)
    unlink (curlock);
  locked = 0;
}

cat (to, from1, from2)
register char *to, *from1, *from2;
{
  register int i, j;

  j = 0;
  for (i = 0; from1[i]; i++)
    to[j++] = from1[i];
  for (i = 0; from2[i]; i++)
    to[j++] = from2[i];
  to[j] = 0;
}

char *
getarg (s, p)			/* copy p... into s, update p */
register char *s, *p;
{
  while (*p == ' ' || *p == '\t')
    p++;
  if (*p == '\n' || *p == '\0')
    return (NULL);
  while (*p != ' ' && *p != '\t' && *p != '\n' && *p != '\0')
    *s++ = *p++;
  *s = '\0';
  return (p);
}

legal (file)
char *file;
{
  char *sp, dfile[100];
  char *expand ();

  if (expand (file, EX_UNIQ) == NULL)  /* expand shell variables */
    return (0);
  if (!access (file, 0))	       /* if file exists */
    if (!access (file, 2))	       /* and can write it */
      return (1);		       /* return 1 */
    else
      return (0);		       /* else can't write it */
  else
  {
    if ((sp = strrchr (file, '/')) == NULL)
      cat (dfile, ".", "");
    else
    {
      strncpy (dfile, file, sp - file);
      dfile[sp - file] = '\0';
    }
    if (access (dfile, 2))
      return (0);
    return (2);
  }
}

/*
 * Determine if line is genuine article. Should be of 2 forms:
 * From.*[0-9][0-9]:[0-9][0-9].* or >From.*[0-9][0-9]:[0-9][0-9].*
 * 
 * The [0-9] stuff is the date representation by ctime. Some systems use only
 * the 09:43 part of the time (i.e. no seconds representation).
 */
Isfrom (iline)
register char *iline;
{
  register char *p;

  /* if(*iline == '>') fradd(iline+1); */
  if (equaln (iline, from, 5))
  {
    if ((p = strchr (iline, ':')) != NULL)
    {
    colontest:
      if (isdigit (p[-2]) &&
	  isdigit (p[-1]) &&
	  isdigit (p[1]) &&
	  isdigit (p[2]))
	return (1);
    }

    /*
     * If more colons, try again.
     */
    if (p = strchr (++p, ':'))
      goto colontest;
  }
  return (0);
}

char *frlist[50];
int frcnt;

fradd (buf)
register char *buf;
{
  frlist[frcnt] = calloc (strlen (buf) + 2, 1);
  strcpy (frlist[frcnt], buf);
  frcnt++;
}

char *
frflsh (flg)
{
  register int i;
  register char *p;
  register char *buf;
  register int cnt = 0;
  register int j;
  int fwrdflg = 0;
  char bigbuf[TOSIZE];
  char *parts[50];
  static char address[TOSIZE];

  if (frcnt == 0)
    return ("");

  /*
   * PARSE THIS MESS
   */

  buf = bigbuf;
  for (i = 0; i < frcnt; i++)
  {
    if ((j = sindex (frlist[i], rmtfrom)) >= 0)
    {
      p = &frlist[i][j + sizeof (rmtfrom) - 1];

      /*
       * Copy machine name into bigbuf
       */
      parts[cnt++] = buf;
      while (!isspace (*p))
	*buf++ = *p++;
      *buf++ = 0;
    }
    else if ((j = sindex (frlist[i], fwrdmsg)) >= 0)
    {
      p = &frlist[i][j + sizeof (fwrdmsg) - 1];
      parts[cnt++] = buf;
      while (!isspace (*p))
	*buf++ = *p++;
      *buf++ = 0;
      if (isdaemon (parts[cnt - 1]))
      {
	buf = parts[--cnt];
      }
      else
      {
	fwrdflg = 1;
	break;
      }
    }
  }

  /*
   * Parse out the name. Names which have "uucp" in them are not real. Hence,
   * go back to previous frlist line. If a "forwarded by" msg has been seen,
   * then the name was already got.
   */
  if (fwrdflg == 0)
    for (i = frcnt - 1; i >= 0; i--)
    {
      p = frlist[i];
      if (*p == '>')
	p++;
      p += 5;			       /* Skip "From " */
      parts[cnt++] = buf;
      while (*p && *p != ' ' && *p != '\t' && *p != '\n')
	*buf++ = *p++;
      *buf++ = 0;
      if (sindex (parts[cnt - 1], "uucp") == -1)
	break;
      cnt--;
      buf = parts[cnt];
    }
  for (i = 0; i < frcnt; i++)
    free (frlist[i]);
  buf = address;
  for (i = 0; i < cnt; i++)
  {
    for (p = parts[i]; *p; p++)
      *buf++ = *p;
    *buf++ = '!';
  }
  *--buf = 0;
  if (flg)
    fprintf (stderr, "%s\n", address);
  frcnt = 0;
  return (address);
}

storedead (letno, file)
register int letno;
register char *file;
{
  FILE *dmalf;
  char dfile[256];
  register char *p;

  strcpy (dfile, file);
  if ((dmalf = fopen (dfile, "w")) == NULL)
  {
    if (!(p = strrchr (file, '/')))
    {
      cat (dfile, home, "/");
      strcat (dfile, file);
    }
    else
      cat (dfile, home, p);
    if ((dmalf = fopen (dfile, "w")) == NULL)
      return (1);
  }
  if ((dmalf = fopen (dfile, "w")) == NULL)
    return (1);
  copylet (letno, dmalf, DEAD, "");
  fclose (dmalf);
  chmod (file, 0600);
  fprintf (stderr, "%s: text saved in %s\n", Mailpgm, dfile);
  return (0);
}

/*
 * Execute the given command (com) with the indicated letter (lett) passed as
 * standard input to the command.
 */
pipelet (lett, com)
int lett;
char *com;
{
  static char lastcom[128];
  FILE *mypopen (), *pp;
  char *cp;

  /* zap newline in command line */
  for (cp = com; *cp; ++cp)
  {
    if (*cp == '\n')
    {
      *cp = '\0';
      break;
    }
  }
  if (com[0] == '|' && com[1] == '\0')
  {
    cp = lastcom;
    printf ("%s\n", cp);
    fflush (stdout);
  }
  else
  {
    strcpy (lastcom, com);
    cp = com;
  }
  umask (0);
  if ((pp = mypopen (cp, "w")) != NULL)
  {
    copylet (lett, pp, ORDINARY, "");
    mypclose (pp);
  }
  else
    fprintf (stderr, "Cannot fork\n");
  umask (MASK);
}

loadcfg (f)
char *f;
{
  FILE *cf;

  if ((cf = fopen (f, "r")) == (FILE *) NULL)
  {
    strcpy (maildir, MAILDIR);
    return;
  }
  fgets (maildir, 120, cf);
  maildir[strlen (maildir) - 1] = NULL;
  fgets (ORG, 120, cf);
  ORG[strlen (ORG) - 1] = NULL;
  return;
}
