/*--------------------------------------------------------------------*/
/*       m a i l . c                                                  */
/*                                                                    */
/*       Mailer User-Agent (UA)                                       */
/*--------------------------------------------------------------------*/

/*--------------------------------------------------------------------*/
/*       Changes Copyright (c) 1989-1993 by Kendra Electronic         */
/*       Wonderworks.                                                 */
/*                                                                    */
/*       All rights reserved except those explicitly granted by       */
/*       the UUPC/extended license agreement.                         */
/*--------------------------------------------------------------------*/

/*--------------------------------------------------------------------*/
/*                          RCS Information                           */
/*--------------------------------------------------------------------*/

/*
 *    $Id: mail.c 1.8 1993/09/23 03:26:51 ahd Exp $
 *
 *    Revision history:
 *    $Log: mail.c $
 * Revision 1.8  1993/09/23  03:26:51  ahd
 * Use current version variables for Visual C++ under Windows NT
 *
 * Revision 1.7  1993/09/20  04:39:51  ahd
 * OS/2 2.x support
 *
 * Revision 1.6  1993/07/31  16:26:01  ahd
 * Changes in support of Robert Denny's Windows support
 *
 * Revision 1.5  1993/07/24  03:40:55  ahd
 * Change description of "-" command, previous command.
 *
 * version  1.0   Stuart Lynne
 * version 1.5 Samuel Lam <skl@van-bc.UUCP>  August/87
 *
 * version 1.6 Drew Derbyshire   May/89
 *             Support for single user aliases, -u option for reading
 *             alternate mailboxes, parsing addresses via external routine,
 *             parsing Resent- fields, suppressing Received: fields,
 *             automatic positioning to next message.                   ahd
 * 23 Sep 89   Version 1.07a
 *             Support lists in aliases                                 ahd
 *
 * 29 Sep 89   Version 1.07b
 *             Add prompting for subject in outgoing mail.              ahd
 * 01 Oct 89   Add additional function prototypes to catch bad calls    ahd
 * 02 Oct 89   Alter large strings/structures to use malloc()/free()    ahd
 * 12 Oct 89   Version 1.07d
 *             Correct free() of line in Send_Mail
 * 12 Dec 89   Version 1.07g
 *             Various spelling corrections
 * 18 Mar 90   Version 1.07i
 *             Add ~user support for save/write command
 *             Add ignore list for user
 *             Shorten lines printed by aborting from a print command   ahd
 * 30 Apr  90  Add autoedit support for sending mail                    ahd
 *  2 May  90  Add support for options= flags                           ahd
 *  3 May  90  Split selected subroutines into maillib.c                ahd
 *  4 May  90  Add 'save' option.                                       ahd
 *  8 May  90  Add 'pager' option                                       ahd
 * 10 May  90  Add 'purge' option                                       ahd
 * 13 May  90  Alter logging so that no numbers are printed on console  ahd
 * Additions for unofficial version 1.07k, Philip David Meese June 1990
 * 16 June 90
 *            -added mail command: Copy current (without delete)        pdm
 *            -altered calls to Collect_Mail to support mail subcmds    pdm
 *            -added handling of '+' to indicate "relative to home
 *                directory" for BSD like mail users.                   pdm
 * 12 Feb 91 rewrite parser a for more BSD like syntax
*/

 static const char rcsid[] =
      "$Id: mail.c 1.8 1993/09/23 03:26:51 ahd Exp $";

/*--------------------------------------------------------------------*/
/*                        System include files                        */
/*--------------------------------------------------------------------*/

#include <ctype.h>
#include <stdio.h>
#include <io.h>
#include <stdlib.h>
#include <string.h>
#include <limits.h>
#include <time.h>
#include <dos.h>
#include <direct.h>

#ifdef _Windows
#include <windows.h>
#include <alloc.h>
#endif

/*--------------------------------------------------------------------*/
/*                    UUPC/extended include files                     */
/*--------------------------------------------------------------------*/

#include "lib.h"
#include "address.h"
#include "alias.h"                                            /* ahd */
#include "dater.h"
#include "expath.h"
#include "getopt.h"
#include "hlib.h"
#include "mail.h"
#include "mailblib.h"
#include "maillib.h"                                           /* ahd */
#include "mailsend.h"
#include "mlib.h"
#include "pushpop.h"
#include "stater.h"
#include "timestmp.h"

#if defined(_Windows)
#include "winutil.h"
#endif

/*--------------------------------------------------------------------*/
/*                          Global variables                          */
/*--------------------------------------------------------------------*/

#ifdef _Windows
unsigned _stklen = 10 * 1024;
unsigned _heaplen = 30 * 1024;
#endif

currentfile();

static char *tmailbox;
static char mfilename[FILENAME_MAX];
int letternum = 0;

static boolean useto = FALSE;

FILE *fmailbox;

#define MAXLETTERS   100

static int maxletters = MAXLETTERS;

struct  ldesc *letters;

/*--------------------------------------------------------------------*/
/*                       Local procedure names                        */
/*--------------------------------------------------------------------*/

static void    Cleanup(void);

static void Interactive_Mail( const boolean PrintOnly,
                              const boolean postoffice );

static void    IncludeNew( const char *target, const char *user);

static void    PrintSubject(int msgnum, int letternum);

static void    UpdateMailbox(int letternum, boolean postoffice);

static int     CreateBox(FILE *rmailbox,
                         FILE *fmailbox,
                         const char *tmailbox);

static void usage( void );

/*--------------------------------------------------------------------*/
/*                          Global variables                          */
/*--------------------------------------------------------------------*/

static char *replytolist[] = { "Resent-Reply-To:",
                        "Resent-From:",
                        "Reply-To:",
                        "From:",
                         NULL };

static char *fromlist[] =    { "Resent-From:",
                               "From:",
                               NULL};

static char *tolist[] =    {   "Resent-To:",
                               "To:",
                               NULL};

static char *subjectlist[] = { "Resent-Subject:",
                               "Subject:",
                                NULL };

static char *datelist[]  =   { "Resent-Date:",
                               "Date:" ,
                               NULL} ;


/*--------------------------------------------------------------------*/
/*                  Information on existing mailbox                   */
/*--------------------------------------------------------------------*/

static long    mboxsize = 0;
static time_t  mboxage  = 0;

/*--------------------------------------------------------------------*/
/*                       Command parsing table                        */
/*--------------------------------------------------------------------*/

#define NUMERIC_CMD "9999"
#define EMPTY_CMD   ""

static struct CommandTable {
   char *sym;
   ACTION verb;
   unsigned int bits;
   char *help;
} table[] = {
 { EMPTY_CMD,     M_EMPTY,    NODISPLAY | NO_OPERANDS | AUTOPRINT ,
         NULL},
 { "!",           M_SYSTEM,   STRING_OP,
         "Execute DOS command"},
 { "+",           M_DOWN,     KEWSHORT_OP | AUTOPRINT,
         "Alias for next"},
 { "-",           M_UP,       KEWSHORT_OP | AUTOPRINT,
         "Alias for previous"},
 { "?",           M_FASTHELP, NO_OPERANDS,
         "Print this help"},
 { "alias",       M_ALIAS,    TOKEN_OP,
         "Print user alias"},
 { "copy",        M_COPY,     LETTER_OP | FILE_OP ,
         "Copy item to file"},
 { "delete",      M_DELETE,   LETTER_OP | POSITION | AUTOPRINT ,
         "Delete mail item"},
 { "debug",       M_DEBUG,    KEWSHORT_OP,
         "Enable debug output"},
 { "dquit",       M_DELETEQ,  LETTER_OP ,
         "Delete then quit"},
 { "exit",        M_EXIT,     NO_OPERANDS,
         "Exit without updating mailbox"},
 { "forward",     M_FORWARD,  LETTER_OP | USER_OP,
         "Resend item to others"},
 { "go",          M_GOTO,     LETTER_OP | AUTOPRINT ,
         "Go to item"},
 { "Headers",     M_HEADERS,  LETTER_OP | POSITION ,
         "Print specified item summary"},
 { "headers",     M_HEADERS,  NO_OPERANDS,
         "Print all item summaries"},
 { "help",        M_HELP,     NO_OPERANDS,
         "Print long help text"},
 { "mail",        M_MAIL,     USER_OP,
         "Compose and send mail"},
 { "next",        M_DOWN,     KEWSHORT_OP | AUTOPRINT ,
         "Move to next item"},
  {"print",       M_EXTPRINT, LETTER_OP | POSITION ,
         "Print item (condensed)"},
  {"Print",       M_INTPRINT, LETTER_OP | POSITION ,
         "Print item (condensed)"},
  {"previous",    M_UP,       KEWSHORT_OP | AUTOPRINT ,
         "Move to previous item"},
  {"quit",        M_QUIT,     NO_OPERANDS,
         "Update mailbox, exit"},
  {"reply",       M_REPLY,    LETTER_OP | POSITION ,
         "Reply to sender of item"},
  {"save",        M_SAVE,     LETTER_OP | FILE_OP | POSITION | AUTOPRINT ,
         "Copy item, delete"},
  {"set",         M_SET,      STRING_OP,
         "Print/set boolean options"},
  {"status",     M_STATUS,  NO_OPERANDS,
         "Report version/status info"},
  {"type",        M_EXTTYPE,  LETTER_OP | POSITION,
         "Print item with all headers"},
  {"Type",        M_INTTYPE,  LETTER_OP | POSITION,
         "Print item with all headers"},
  {"undelete",    M_UNDELETE, LETTER_OP | POSITION | AUTOPRINT ,
         "Rescue item after save/delete"},
  {"write",       M_WRITE,    LETTER_OP | FILE_OP | POSITION | AUTOPRINT ,
         "Copy item w/o header, delete"},
  {"xit",         M_EXIT,     NO_OPERANDS,
         "alias for exit"},
  { NUMERIC_CMD,   M_GOTO,     NODISPLAY | KEWSHORT_OP | AUTOPRINT ,
         NULL} ,
  { NULL,          M_INVALID,  NODISPLAY | STRING_OP,
         NULL }
         } ;

/*--------------------------------------------------------------------*/
/*    m a i n                                                         */
/*                                                                    */
/*    Main program                                                    */
/*--------------------------------------------------------------------*/

void main(int argc, char **argv)
{

   boolean PrintOnly = FALSE;
   boolean postoffice = TRUE;
   boolean readmail   = FALSE;
   boolean sendmail   = FALSE;
   int option;
   char    *subject = NULL;

#if defined(__CORE__)
   copywrong = strdup(copyright);
   checkref(copywrong);
#endif

   banner( argv );

   if (!configure( B_MUA ))
      exit(1);    /* system configuration failed */

   if (!InitRouter())
      exit(1);    /* system configuration failed */

   tmailbox = mktempname(NULL, "TMP");
   PushDir(".");

/*--------------------------------------------------------------------*/
/*                       get mailbox file name                        */
/*--------------------------------------------------------------------*/

   strcpy( mfilename, E_mailbox );
   if ( strchr( mfilename ,'.' ) == NULL )
      mfilename[8] = '\0';       /* Prevent OS/2 filename length
                                    overrun                          */

   if ( bflag[ F_MULTITASK ] )
   {
      if (expand_path( mfilename, E_homedir, E_homedir, E_mailext ) == NULL )
         panic();
   }
   else
      mkmailbox(mfilename, E_mailbox );

/*--------------------------------------------------------------------*/
/*                          parse arguments                           */
/*--------------------------------------------------------------------*/

   while ((option = getopt(argc, argv, "f:ps:tu:x:")) != EOF)
   {
      char oname[FILENAME_MAX];

      switch (option)
      {
      case 'f':
         readmail = TRUE;
         strcpy( mfilename, optarg );
         if (expand_path( mfilename, NULL, E_homedir, E_mailext ) == NULL )
            usage();

/*--------------------------------------------------------------------*/
/*    This next one is a little tricky ...  If we log outgoing        */
/*    mail, we copy the name of the outgoing mail file into a         */
/*    temporary buffer, and expand the name of the file to include    */
/*    the path name.  If this name is the same as the current         */
/*    file, flip-flip the useto flag which says use the To:           */
/*    related fields when scanning headers, not the From:  related    */
/*    fields.                                                         */
/*--------------------------------------------------------------------*/

         if (( E_filesent != NULL ) &&
             (expand_path( strcpy( oname, E_filesent) ,
                          E_homedir, E_homedir , E_mailext ) != NULL ) &&
             equali( oname , mfilename ))
                           /* Our outgoing filename?              */
            useto = ! useto;  /* Yes --> Automatically switch     */
         postoffice = FALSE;
         break;

      case 'p':
         readmail = TRUE;
         PrintOnly = TRUE;
         break;

      case 'u':                  /* Read alternate mailbox?       */
         readmail = TRUE;
         mkmailbox(mfilename, optarg);
         postoffice = FALSE;
         break;

      case 'x':
         debuglevel = atoi(optarg);
         break;

      case 's':
         sendmail = TRUE;
         subject = optarg;
         break;

      case 't':
         readmail = TRUE;
         useto = ! useto;
         break;

      case '?':
         usage();

      } /* switch */

   } /* while */

/*--------------------------------------------------------------------*/
/*                        Check for conflicts                         */
/*--------------------------------------------------------------------*/

   sendmail |= (optind != argc);

   if ( sendmail && readmail )
   {
      puts("Conflicting options specified");
      usage();
   }

   if ((optind == argc) && sendmail)
   {
      puts("Missing addresses for sending mail");
      usage();
   }

/*--------------------------------------------------------------------*/
/*        We have the options, now decide how to process them         */
/*--------------------------------------------------------------------*/

   if (sendmail)
   {
      argc -= optind;

      if ( subject != NULL )
      {

         argv    = &argv[optind-2];
         argv[0] = "-s";
         argv[1] = subject;

         Collect_Mail(stdin, argc+2 , argv , -1, FALSE);
      } /* if ( subject != NULL ) */
      else {
         Collect_Mail(stdin, argc, &argv[optind], -1, FALSE);

#ifdef _Windows
         atexit ( CloseEasyWin );
#endif
      }

   } /* if (sendmail) */
   else  {
      if ( postoffice && bflag[ F_MULTITASK ] )
         IncludeNew( mfilename, E_mailbox);
      Interactive_Mail( PrintOnly , postoffice );
   }

   Cleanup();
   PopDir();
   exit(0);

} /*main*/

/*--------------------------------------------------------------------*/
/*    C l e a n u p                                                   */
/*                                                                    */
/*    Remove temporary files when exiting                             */
/*--------------------------------------------------------------------*/

void Cleanup()
{

   printmsg(2,"Deleting temporary mailbox %s", tmailbox);

   if ( fmailbox != NULL )
   {
      fclose(fmailbox);
      fmailbox = NULL;
   }

   unlink(tmailbox);

} /*Cleanup*/


/*--------------------------------------------------------------------*/
/*    I n t e r a c t i v e _ M a i l                                 */
/*                                                                    */
/*    main procedure for reading mail                                 */
/*--------------------------------------------------------------------*/

static void Interactive_Mail( const boolean PrintOnly,
                              const boolean postoffice )
{
   char resp[LSIZE];
   int current = 0;                                               /* ahd   */
   boolean done      = FALSE;                                     /* ahd   */
   boolean modified;
   FILE *rmailbox;

/*--------------------------------------------------------------------*/
/*               Open real and temporary mailbox files                */
/*--------------------------------------------------------------------*/

   if ((rmailbox = FOPEN(mfilename, "r",TEXT_MODE)) == nil(FILE)) {
      printf("No mail in %s\n", mfilename);
      return;
   }

   mboxage = stater( mfilename, &mboxsize );
                              /* Remember mailbox information        */

   if ((fmailbox = FOPEN(tmailbox, "w", BINARY_MODE)) == nil(FILE)) {
      printerr(tmailbox);
      return;
   }

   letters = calloc(maxletters,sizeof(letters[0]));
   checkref(letters);

/*--------------------------------------------------------------------*/
/*                 Copy real mailbox to temporary one                 */
/*--------------------------------------------------------------------*/

   setvbuf(rmailbox, NULL, _IOFBF, 8192);
   setvbuf(fmailbox, NULL, _IOFBF, 8192);

   letternum = CreateBox(rmailbox, fmailbox, tmailbox);

   fclose(rmailbox);
   fclose(fmailbox);

   rmailbox = fmailbox = NULL;

   if (letternum < 1)            /* Did we find any mail in the box? */
   {                             /* No --> Return to caller          */
      if (letternum == 0)
         printf("No mail in %s\n", mfilename);
      return;
   }

/*--------------------------------------------------------------------*/
/*        Shrink mailbox status array to what we actually need        */
/*--------------------------------------------------------------------*/

   letters = realloc( letters, (letternum + 1) *  sizeof(letters[0]));
   checkref(letters);

   fmailbox = FOPEN(tmailbox, "r", BINARY_MODE);

   if (fmailbox == NULL)
   {
      printerr(tmailbox);
      panic();
   } /* if */
   setvbuf(fmailbox, NULL, _IOFBF, 8192);

   modified = postoffice && (!PrintOnly);

   if (PrintOnly) {
      int j = 0;
      while (j < letternum)
      {
         Pager(j, TRUE, noreceived, !j );
         j++ ;
      }
      return;
   }

   PrintSubject(-1,letternum); /* print all subjects */

/*--------------------------------------------------------------------*/
/*               Determine first letter in to prompt at               */
/*--------------------------------------------------------------------*/

      if (letternum == 0)
         current = -1;
      else
         current = 0;

/*--------------------------------------------------------------------*/
/*            Begin main command loop for reading the mail            */
/*--------------------------------------------------------------------*/

   if (!bflag[F_EXPERT])
      printf("Enter \"?\" for short help or \"help\" for long help.\n");

#ifdef _Windows
   atexit ( CloseEasyWin );
#endif

   while( ! done )
   {
      char *command, *operand;
      int integer;
      boolean first_pass = TRUE;
      int previous = current;
      struct CommandTable *cmd_ptr = table;
      boolean success = TRUE;
      boolean crlf    = FALSE;      /* crlf after delete command?    */

      printf("%d%s",current + 1,
               (letters[current].status == M_DELETED) ? "*" : " ");
      if (!Console_fgets(resp, LSIZE, "? ")) /* End of file?         */
      {
         done = TRUE;
         continue;            /* Yes --> Exit loop                   */
      }
      PageReset();

/*--------------------------------------------------------------------*/
/*                     Locate command to execute                      */
/*--------------------------------------------------------------------*/

      integer = strlen( resp );
      if (integer && ( resp[ integer - 1 ] == '\n'))
         resp[ integer - 1 ] = '\0';   /* Trim newline, if any       */

      operand = command = strtok( resp, WHITESPACE );
      if ( command == NULL )
         command = EMPTY_CMD;
      else if (Numeric(command))
         command = NUMERIC_CMD;

      while( cmd_ptr->sym != NULL)
      {
         if (equaln(command, cmd_ptr->sym, strlen(command)))
            break;            /* Exit if we have a hit               */
         cmd_ptr++;           /* Examine next command                */
      } /* while */

/*--------------------------------------------------------------------*/
/*     Get rest of command line, and trim leading spaces from it      */
/*--------------------------------------------------------------------*/

      if (!equal(command, NUMERIC_CMD) && (operand != NULL))
      {
         operand = strtok( NULL , "");
                              /* Save rest of string for later       */
         if ( operand != NULL )
         {
            while( isspace( *operand ))
               operand++ ;

            if (*operand == '\0')
               operand = NULL ;
         } /* if */
      }

/*--------------------------------------------------------------------*/
/*        Parse items to be selected from mailbox for command         */
/*--------------------------------------------------------------------*/

      if (cmd_ptr->bits & (LETTER_OP) )
         success = SelectItems( &operand, current, cmd_ptr->bits);

/*--------------------------------------------------------------------*/
/*                  Process the operands in the list                  */
/*--------------------------------------------------------------------*/

      while( success &&
             Get_Operand( &integer, &operand, cmd_ptr->bits, first_pass) )
      {
         switch( cmd_ptr->verb )
         {
            case M_ALIAS:
               ShowAlias( operand );
               break;

            case M_COPY:
               success = SaveItem( integer,
                         FALSE,        /* Do not delete */
                         seperators,   /* Do save headers */
                         (operand == NULL) ? "PRN" : operand ,
                         cmd_ptr->verb );
               break;

            case M_DEBUG:
               debuglevel = integer;
               printmsg(0,"Debug set to %d",debuglevel);
               break;

            case M_DELETEQ:
               done = TRUE;
            case M_DELETE:
               if (letters[integer].status < M_DELETED)
               {
                  letters[integer].status = M_DELETED;
                  if ( ! crlf )
                     printf("Deleting item(s) %d",integer + 1 );
                  else
                     printf(" %d",integer + 1 );
                  crlf  = modified = TRUE;
               }
               break;

            case M_DOWN:
               current = Position( 0 , integer , current );
               break;

            case M_EMPTY:
               if ( bflag[F_DOSKEY] && !bflag[F_EXPERT] )
               {
                  printf("DOSKEY active, empty line ignored\n");
                  PrintSubject( current , letternum );
                  success = FALSE;
               }
               else if (letters[current].status == M_UNREAD)
                  success = Pager( current , TRUE, noreceived, first_pass);
               else
                  current = Position( 0 , 1 , current );
               break;

            case M_EXIT:
               modified = FALSE;
               done     = TRUE;
               break;

            case M_EXTPRINT:
               success = Pager( integer , TRUE, noreceived, first_pass);
               break;

            case M_EXTTYPE:
               success = Pager( integer , TRUE, noseperator, first_pass);
               break;

            case M_FASTHELP:
            {
               size_t subscript = 0;
#ifndef _Windows
               size_t column    = 0;
#endif
               fputs("Valid commands are:\n",stdout);
               while( table[subscript].sym != NULL)
               {
                  if ( !(table[subscript].bits & NODISPLAY ))
                  {
#ifdef _Windows
                     fputc( '\n' , stdout );
#else
                     fputc( ( column++ % 2 ) ? ' ' : '\n' , stdout );
#endif
                     printf("%-9s%-30s",table[subscript].sym,
                                       table[subscript].help );
                  } /* if */
                  subscript ++;
               } /* while */
               fputs("\n\nEnter \"help\" for additional information.\n",
                        stdout);
               break;
            } /* case */

            case M_FORWARD:
               success = ForwardItem( integer, operand);
               break;

            case M_GOTO:
               current = Position( integer, 0, current );
               break;

            case M_HEADERS:
               PrintSubject( (cmd_ptr->bits & NO_OPERANDS) ?
                                 -1 : integer, letternum );
               break;

            case M_HELP:
            {
               char filename[FILENAME_MAX];
               mkfilename(filename, E_confdir, "mail.hlp");
               Sub_Pager(filename, TRUE );
               break;
            }

            case M_INTPRINT:
               success = Pager( integer , FALSE, noreceived, first_pass);
               break;

            case M_INTTYPE:
               success = Pager( integer , FALSE, noseperator, first_pass);
               break;

            case M_INVALID:
               printf("Invalid command \"%s\".  Enter \"?\" for help.\n",
                        command);
               break;

            case M_MAIL:
               success = DeliverMail( operand , current);
               break;

            case M_NOOP:
               break;

            case M_REPLY:
               success = Reply( integer );
               break;

            case M_QUIT:
               done = TRUE;
               break;

            case M_SAVE:
               success = SaveItem( integer,
                         TRUE,         /* Do delete */
                         seperators,   /* Do save headers */
                         operand ,
                         cmd_ptr->verb );
               modified = TRUE;
               break;

            case M_SET:
               if (operand == NULL)
                  sayoptions( configFlags);
               else
                  options(operand, USER_CONFIG, configFlags, bflag);
               break;

            case M_SYSTEM:
               subshell( operand );
               break;

            case M_UNDELETE:
               letters[integer].status = M_UNREAD;
               break;

            case M_UP:
               current = Position( 0 , - integer , current );
               break;

            case M_STATUS:
               printf("%s:\t%s created %s %s running under %s %d.%02d\n",
                       compilep, compilev, compiled, compilet,

#ifdef WIN32
                     "Windows NT",
                     _winmajor,
                     _winminor);
#elif defined(__OS2__)
                    "OS/2(R)" ,
                    (int) _osmajor / 10,
                      _osminor);
#elif defined(__TURBOC__)
                    "DOS",
                    _osmajor,
                    _osminor);
#else
                    (_osmode == DOS_MODE) ? "DOS" : "OS/2(R)" ,
                    (_osmode == DOS_MODE) ? _osmajor : ((int) _osmajor / 10 ),
                     _osminor);
#endif
#ifdef _Windows
               printf("Windows version: %s\t", compilew );
#endif
               printf("Magic Word:\t%s\n","flarp");
               printf("Return address:\t\"%s\" <%s@%s>\n"
                      "Domain name:\t%s\tNodename:\t%s\n",
                        E_name, E_mailbox, E_fdomain, E_domain, E_nodename );
               printf("Current File:\t%s\tNumber of items: %d\n"
                      "File size:\t%ld bytes\tLast updated:\t%s",
                        mfilename, letternum + 1 , mboxsize ,
                        ctime( & mboxage ) );
               break;

            case M_WRITE:
               success = SaveItem( integer,
                         TRUE,      /* Do delete */
                         noheader,  /* Do not save headers */
                         operand,
                         cmd_ptr->verb );
               modified = TRUE;
         } /* switch */
         first_pass = FALSE;
      } /* while */

      success = ! first_pass; /* If first_pass not run, then
                                 Get_Operand failed                  */

      if ( crlf )
         putchar('\n');

      if ( success && !done )
      {
         if (cmd_ptr->bits & POSITION)
            current = Position( 0 , 0 , integer );

         if ( current != previous )
         {
            if ( (cmd_ptr->bits & AUTOPRINT ) &&
                  bflag[F_AUTOPRINT] &&
                  (letters[current].status != M_DELETED) )
               Pager( current , TRUE, noreceived, TRUE);
            else
               PrintSubject( current , letternum );
         } /* if */
      } /* if */
   } /* while */

/*--------------------------------------------------------------------*/
/*                       End main command loop                        */
/*--------------------------------------------------------------------*/

   if (modified)
      UpdateMailbox(letternum, postoffice);

   free(letters);

} /*Interactive_Mail*/

/*--------------------------------------------------------------------*/
/*    I n c l u d e N e w                                             */
/*                                                                    */
/*    Includes mail from the system box into the user's local         */
/*    mailbox                                                         */
/*--------------------------------------------------------------------*/

static void IncludeNew( const char *target, const char *user)
{
   long age, size;
   FILE *stream_in;
   FILE *stream_out;
   int  bytes;

   char sysbox[FILENAME_MAX];
   char buf[BUFSIZ];

   mkmailbox(sysbox, user);

/*--------------------------------------------------------------------*/
/*      Return semi-quietly if we can't open the system mailbox       */
/*--------------------------------------------------------------------*/

   stream_in   = FOPEN( sysbox, "r", BINARY_MODE);
   if ( stream_in == NULL )
   {
      if ( debuglevel > 1 )
         printerr( sysbox );
      return;
   }

/*--------------------------------------------------------------------*/
/*      Determine if we have new mail, returning quietly if not       */
/*--------------------------------------------------------------------*/

   age = stater( sysbox , &size );

   if ( age == -1)
      panic();

   printmsg( 1, "Including mail from %s through %s",
            sysbox,
            ctime(&age));

/*--------------------------------------------------------------------*/
/*                    Now open up the output file                     */
/*--------------------------------------------------------------------*/

   stream_out  = FOPEN( target, "a+", BINARY_MODE);

   if ( stream_out == NULL )
   {
      printerr( target );
      panic();
   }

/*--------------------------------------------------------------------*/
/*                       Loop to read the data                        */
/*--------------------------------------------------------------------*/

   while ((bytes = fread(buf,sizeof(char), sizeof buf, stream_in)) > 0)
   {
      if ((int) fwrite(buf, sizeof(char), bytes, stream_out) != bytes)
      {
         printmsg(0, "Error including new mail into %s", target );
         printerr( target );
         fclose( stream_in );
         fclose( stream_out );
         panic();
      }
   } /* while */

/*--------------------------------------------------------------------*/
/*                   Clean up and return to caller                    */
/*--------------------------------------------------------------------*/

   if ( ferror( stream_in ))
   {
      printerr( sysbox );
      panic();
   }

   fclose( stream_in  );
   fclose( stream_out );

   filebkup( sysbox );
   unlink(sysbox);

} /* IncludeNew */

/*--------------------------------------------------------------------*/
/*    C r e a t e B o x                                               */
/*                                                                    */
/*    Creates the temporary mailbox and related tables                */
/*--------------------------------------------------------------------*/

int CreateBox(FILE *rmailbox, FILE *fmailbox , const char *tmailbox)
{

/*--------------------------------------------------------------------*/
/*          Copy real mailbox file to temporary mailbox file          */
/*--------------------------------------------------------------------*/

   int letternum = 0;
   boolean inheader = FALSE;
   long position;
   char line[LSIZE];
   char **list;
   size_t replyprior = 0;
   size_t dateprior = 0;
   size_t subjectprior = 0;
   size_t fromprior = 0;

   struct ldesc *letter = NULL;

   while ((fgets(line, LSIZE, rmailbox) != nil(char)) ){

      if (inheader)
      {
         if (*line == '\n')
            inheader = FALSE;
      }  /* inheader */
      else {               /* Determine if starting new message   */
         if (equal(line,MESSAGESEP) ||
            (bflag[F_FROMSEP] && equaln(line, "From ", 5)))
         {
             while (equal(line,MESSAGESEP))
             if (fgets(line, LSIZE, rmailbox) == NULL)
             {
               printerr(mfilename);
               panic();
             } /* if */

/*--------------------------------------------------------------------*/
/*               Make the mailbox bigger if we need to                */
/*--------------------------------------------------------------------*/

             position = ftell(fmailbox);
             if ( (letternum+1) == maxletters )
             {
               maxletters = max((int) ((maxletters * mboxsize) / position),
                                 (letternum * 11) / 10 );
               printmsg(2,"Reallocating mailbox array from %d to %d entries",
                     letternum+1, maxletters );
               letters = realloc( letters, maxletters *  sizeof(letters[0]));
               checkref( letters );
             }

/*--------------------------------------------------------------------*/
/*             Initialize this entry in th mailbox array              */
/*--------------------------------------------------------------------*/

             letter = &letters[letternum++];

             fromprior = subjectprior = replyprior = dateprior = INT_MAX;
             letter->from = letter->subject = letter->date =
                  letter->replyto = MISSING;
             letter->adr = position;
             letter->status = M_UNREAD;
             letter->lines = 0L;
             inheader = TRUE;
             printf("Reading message %d (%d%% done)\r",letternum,
                        (int) (position * 100 / mboxsize));
         }
         else
         {
            if(letter == NULL)   /* Did we find first letter?     */
            {                    /* No --> Abort with message     */
               fprintf(stderr,"%s  %s\n\a",
                  "This mailbox is not in UUPC/extended format!",
                  bflag[F_FROMSEP] ?
                  "Messages must be seperated by From lines!" :
                  "(Try \"options=fromsep\" in your configuration file)");
               panic();
            } /* if */

            letter->lines++;
         } /* else */
      } /* else */

      if (inheader)
      {
         size_t priority = 0;

/*--------------------------------------------------------------------*/
/*              Search for the best Date: related field               */
/*--------------------------------------------------------------------*/

         while ( (dateprior > priority) && (datelist[priority] != NULL ))
         {
            if (equalni(line, datelist[priority],
                             strlen(datelist[priority]) ) )
            {
               letter->date = ftell(fmailbox);
               dateprior = priority;
            }
            priority++;
         }

/*--------------------------------------------------------------------*/
/*             Search for the best Subject: related field             */
/*--------------------------------------------------------------------*/

         priority = 0;
         while ( (subjectprior > priority) &&
                 (subjectlist[priority] != NULL ))
         {
            if (equalni(line, subjectlist[priority],
                             strlen(subjectlist[priority]) ) )
            {
               letter->subject = ftell(fmailbox);
               subjectprior = priority;
            }
            priority++;
         }

/*--------------------------------------------------------------------*/
/*           Search for the best From: header related field           */
/*--------------------------------------------------------------------*/

         list = (useto) ? tolist : fromlist;
         priority = 0;
         while ( (fromprior > priority) && (list[priority] != NULL ))
         {
            if (equalni(line, list[priority],
                             strlen(list[priority]) ) )
            {
               letter->from = ftell(fmailbox);
               fromprior = priority;
            }
            priority++;
         } /* while */

/*--------------------------------------------------------------------*/
/*             Search for the best Reply-To related field             */
/*--------------------------------------------------------------------*/

         priority = 0;
         while ( (replyprior > priority) &&
                 (replytolist[priority] != NULL ))
         {
            if (equalni(line, replytolist[priority],
                             strlen(replytolist[priority]) ) )
            {
               letter->replyto = ftell(fmailbox);
               replyprior = priority;
            } /* if */
            priority++;
         }  /* while */
      } /* inheader */

      if (fputs(line, fmailbox) == EOF )
      {
         printerr(tmailbox);
         panic();
      } /* if */


   } /* while */

   letters[letternum].adr = ftell(fmailbox);
   letters[letternum].status = M_DELETED;


   fclose(rmailbox);
   fclose(fmailbox);

   return letternum;

} /* CreateBox */

/*--------------------------------------------------------------------*/
/*    P r i n t S u j e c t                                           */
/*                                                                    */
/*    Print the subject line of one or all messages in the mailbox    */
/*--------------------------------------------------------------------*/

void PrintSubject(int msgnum,int letternum)
{
   struct ldesc *ld;
   char from[LSIZE];
   char subject[LSIZE];
   char date[LSIZE];
   char line[LSIZE];

   int k, mink, maxk;

   if (msgnum == -1)
   {                                         /* print all of them? */
      sprintf(line," %d messages in file %s.\n",letternum,mfilename);
      PageLine(line);
      mink = 0;
      maxk = letternum - 1;
   } else
      mink = maxk = msgnum;

   for (k = mink ; k <= maxk ; k++) {

      ld = &letters[k];
      if ((ld->status == M_DELETED) && (msgnum == -1))
         continue;

      ReturnAddress(from,ld);       /* Get return address for letter */

      /* Date: Wed May 13 23:59:53 1987 */
      *date = '\0';  /* default date to null */
      if (RetrieveLine(ld->date, date, LSIZE)) {
         sscanf(date, "%*s %*s %s %s", line, subject);
         sprintf(date, "%s %s", line, subject);
      }

      strcpy(subject, "--- no subject ---");
      if (RetrieveLine(ld->subject, line, LSIZE)) {
         register char  *sp;
         sp = line;
         while (!isspace(*sp))
            sp++;
         while (isspace(*sp))
            sp++;
         strcpy(subject, sp);
      }

      /* make sure the fields aren't too long */

      from[25] = '\0';
      date[6] = '\0';
      subject[30] = '\0';

      sprintf(line, "%3d%c %6s  %-25s  %-30s  (%5ld)\n", k + 1,
         ((ld->status == M_DELETED) ? '*' : ' '),
            date, from, subject, ld->lines);

      if (PageLine(line))
         break;

   }

} /*PrintSubject*/


/*--------------------------------------------------------------------*/
/*    U p d a t e  M a i l b o x                                      */
/*                                                                    */
/*    Update the permanent mailbox for the user                       */
/*--------------------------------------------------------------------*/

void UpdateMailbox(int letternum, boolean postoffice)
{
   int current;
   boolean changed = FALSE;
   boolean problem = FALSE;
   FILE *fmailbag;
   FILE *mbox = NULL;
   char *mboxname = NULL;
   long newsize;
   time_t newage;
   size_t msave = 0;
   size_t psave = 0;

/*--------------------------------------------------------------------*/
/*    Auto save into user's home directory mailbox if we were         */
/*    reading the system mailbox and the user specified the           */
/*    'save' option.                                                  */
/*--------------------------------------------------------------------*/

   postoffice = postoffice && bflag[F_SAVE];

/*--------------------------------------------------------------------*/
/*     Determine if anything was actually changed in the mailbox      */
/*--------------------------------------------------------------------*/

   for (current = 0;
        (current < letternum) && (! changed);
        current++)
   {
      if (letters[current].status == M_DELETED)
         changed = TRUE;

      if (postoffice && (letters[current].status != M_UNREAD))
         changed = TRUE;
   }

   if (!changed)
      return;

/*--------------------------------------------------------------------*/
/*    Determine if the mailbox has changed since we built our         */
/*    temporary file                                                  */
/*--------------------------------------------------------------------*/

   newage = stater( mfilename , &newsize );

   if ( mboxsize != newsize )
   {
      printf("%s size has changed from %ld to %ld bytes\n",
            mfilename, mboxsize, newsize );
      problem = TRUE;
   }

   if ( mboxage != newage )
   {
      char mboxbuf[DATEBUF];
      char newbuf[DATEBUF];
      printf("%s date stamp has changed from %s to %s\n",
            mfilename, dater(mboxage, mboxbuf), dater(newage, newbuf) );
      problem = TRUE;
   }

   while ( problem )
   {
      int c;

      printf("WARNING! File %s has changed, data may be lost if updated!\n",
            mfilename);
      fputs("Update anyway? ",stdout);

      c     = Get_One();

      switch (tolower( c ))
      {
         case 'y':
            puts("Yes");
            problem = FALSE;
            break;

         case 'n':
            printf("No\nUpdate aborted, %s left unchanged.\n",
                     mfilename);
            return;

         default:
            printf("%c - Invalid Response\n",c);
            break;
      } /* switch */
   } /* while ( problem ) */

/*--------------------------------------------------------------------*/
/*                Allocate auto save related variables                */
/*--------------------------------------------------------------------*/

   if (postoffice)
   {
      mboxname = malloc(FILENAME_MAX);
      checkref(mboxname);
      strcpy( mboxname, "mbox" );
      expand_path( mboxname, E_homedir, E_homedir, E_mailext );
   } /* if (postoffice) */

/*--------------------------------------------------------------------*/
/*                   Create a backup file if needed                   */
/*--------------------------------------------------------------------*/

   if ( bflag[F_BACKUP] )
      filebkup( mfilename );

/*--------------------------------------------------------------------*/
/*                    Begin re-writing the mailbox                    */
/*--------------------------------------------------------------------*/

   if ((fmailbag = FOPEN(mfilename, "w",TEXT_MODE)) == nil(FILE))
   {
      printf("UpdateMailbox: can't rewrite %s.\n", mfilename);
      Cleanup();
   } /* if */

   setvbuf(fmailbag, NULL, _IOFBF, 8192);

/*--------------------------------------------------------------------*/
/*    We got the files open, now actually loop through copying        */
/*    data from our temporary mailbox back into the permenent one,    */
/*    or in the user's mbox if he read the message and the post       */
/*    office is open.                                                 */
/*--------------------------------------------------------------------*/

   printf("Cleaning up ", current+ 1);

   for (current = 0; current < letternum;  current++)
   {
      if (letters[current].status == M_DELETED)
      {
         /* No operation */
         fputc('.', stdout);
      }
      else if (postoffice && (letters[current].status != M_UNREAD))
      {

         if ( mbox == NULL )  /* Mailbox already open?               */
         {                    /* No --> Do so now                    */
            mbox = FOPEN(mboxname, "a",TEXT_MODE);
            if (mbox == NULL) /* Open fail?                          */
            {                  /* Yes --> Disable postoffice autosave*/
               printf("\nUpdateMailbox: can't append to %s.\n", mboxname);
               postoffice = FALSE;
               current--;     /* Process this entry again            */
            } /* if */
            else
               setvbuf(mbox, NULL, _IOFBF, 8192);
         } /* if ( mbox == NULL ) */

         if ( mbox != NULL )
         {
            fputc('+', stdout);
            CopyMsg(current, mbox, seperators, FALSE);
            msave++;
         } /* mbox */
      }
      else {
         fputc('*', stdout);
         CopyMsg(current, fmailbag, seperators, FALSE);
         psave ++;
      } /* else */

   } /* for */
   fputs(" done!\n", stdout);

/*--------------------------------------------------------------------*/
/*    Close the post office.  We close the 'mbox' in the user's       */
/*    home directory, report if any data was saved in it, and         */
/*    then free the storage associated with the postoffice processing.*/
/*--------------------------------------------------------------------*/

   if ( postoffice )
   {

      if (msave > 0)          /* Did we write data into mailbox?  */
      {                       /* Yes --> Report it                */
         fclose(mbox);
         printf("%d letter%s saved in %s%s",
               msave,
               (msave > 1) ? "s" : "" ,
               mboxname,
               (psave > 0) ? ", " : ".\n");
      }

      free(mboxname);
   } /* if (postoffice) */

/*--------------------------------------------------------------------*/
/*    Now, clean up after the input mailbox.  We close it, and        */
/*    report when anything was saved into it.  If nothing was         */
/*    saved, we delete the file if the 'purge' option is active.      */
/*--------------------------------------------------------------------*/

   fclose(fmailbag);

   if (psave > 0)
      printf("%d letter%s held in %s.\n",
            psave ,
            (psave > 1) ? "s" : "" , mfilename);
   else if (bflag[F_PURGE] )
   {
      remove(mfilename);
      printf("Empty mail box %s has been deleted.\n", mfilename);
   }

} /* UpdateMailbox */

/*--------------------------------------------------------------------*/
/*    U s a g e                                                       */
/*                                                                    */
/*    Report command line syntax                                      */
/*--------------------------------------------------------------------*/

static void usage( void )
{
   puts("\nUsage:\tmail [-s subject] recipient ... "
         "[-c recipient ...] [-b receipient ...]\n"
         "\tmail [-f mailbox] [-u user] [-t] [-p] [-x debug]");
   exit(1);
}
