/*
 ===========================================================================
 =                                                                         =
 =  (C) Copyright 1991 The Trustees of Indiana University                  =
 =                                                                         =
 =  Permission to use, copy, modify, and distribute this program for       =
 =  non-commercial use and without fee is hereby granted, provided that    =
 =  this copyright and permission notice appear on all copies and          =
 =  supporting documentation, the name of Indiana University not be used   =
 =  in advertising or publicity pertaining to distribution of the program  =
 =  without specific prior permission, and notice be given in supporting   =
 =  documentation that copying and distribution is by permission of        =
 =  Indiana University.                                                    =
 =                                                                         =
 =  Indiana University makes no representations about the suitability of   =
 =  this software for any purpose. It is provided "as is" without express  =
 =  or implied warranty.                                                   =
 =                                                                         =
 ===========================================================================
 =                                                                         =
 = File:                                                                   =
 =   IUPOP3_COMMANDS.C                                                     =
 =                                                                         =
 = Synopsis:                                                               =
 =   This file contains functions that implement the pop3 commands.        =
 =                                                                         =
 = Authors:                                                                =
 =   Jacob Levanon & Larry Hughes                                          =
 =   Indiana University                                                    =
 =   University Computing Services, Network Applications                   =
 =                                                                         =
 = Credits:                                                                =
 =   This software is based on the Post Office Protocol version 3,         =
 =   as implemented by the University of California at Berkeley.           =
 =                                                                         =
 ===========================================================================
*/

/*************************************************************************
**  Include files
**************************************************************************/
#include <stdio.h>
#ifdef MULTINET
#include <types.h>
#else /* WINS || UCX */
#include <sys/types.h>
#endif
#include <ctype.h>
#include "iupop3_general.h"
#include "iupop3_global.h"
#include "iupop3_vms.h"

/*************************************************************************
**  Prototypes
/*************************************************************************
state_table *get_pop_command();
xtnd_table  *get_pop_subcommand();
int         pop_dele();
int         pop_last();
int         pop_list();
void        pop_log();
int         pop_parse();
int         pop_pass();
int         pop_quit();
int         pop_rset();
int         pop_send();
int         pop_stat();
int         pop_updt();
int         pop_user();
int         pop_xtnd();
int         pop_xtnd_shutdown();
int         pop_xtnd_stats();

/****************************************************************************
**  External Declarations
****************************************************************************/
extern int   PWDcheck();
extern void  make_argv();
extern float get_cpu();

/**************************************************************************
**  Pop_dele:   Delete a message from the VMS mail file
***************************************************************************/
int pop_dele(POP *p)
{
    Message     *mp;		
    int         msg_num;

    msg_num = atoi(p->pop_args[1]);

    /*
    **  Is message requested for deletetion out of range?
    */
    if ((msg_num < 1) || (msg_num > p->msg_count))
        return (pop_msg
            (p,POP_FAILURE,"Message %d does not exist.",msg_num, 0));

    /*
    **  Get a pointer to the message in the message list
    */
    mp = &(p->mptr[msg_num-1]);

    /*
    **  If the message already flagged for deletion, signal error
    */
    if (mp->del_flag)
        return (pop_msg
            (p,POP_FAILURE,"Message %d has already been deleted.",
             msg_num, 0));

    mp->del_flag = TRUE;        
    p->msgs_deleted++;
    p->bytes_deleted += mp->length;
    if (p->last_msg < msg_num) p->last_msg = msg_num;

    return (pop_msg
        (p,POP_SUCCESS,"Message %d has been deleted.",msg_num, 0));
}

/*****************************************************************************
**  Pop_get_command: Extract the command from an input line form a POP client
*****************************************************************************/

static state_table states[] =
{
  auth1,  "user", 1,  1,  pop_user,   {auth1, auth2},
  auth2,  "pass", 1,  1,  pop_pass,   {auth1, trans},
  auth1,  "quit", 0,  0,  pop_quit,   {halt,  halt},
  auth2,  "quit", 0,  0,  pop_quit,   {halt,  halt},
  trans,  "stat", 0,  0,  pop_stat,   {trans, trans},
  trans,  "list", 0,  1,  pop_list,   {trans, trans},
  trans,  "retr", 1,  1,  pop_send,   {trans, trans},
  trans,  "dele", 1,  1,  pop_dele,   {trans, trans},
  trans,  "noop", 0,  0,  NULL,       {trans, trans},
  trans,  "rset", 0,  0,  pop_rset,   {trans, trans},
  trans,  "top",  2,  2,  pop_send,   {trans, trans},
  trans,  "last", 0,  0,  pop_last,   {trans, trans},
  trans,  "xtnd", 1,  99, pop_xtnd,   {trans, trans},
  trans,  "quit", 0,  0,  pop_updt,   {halt,  halt},
  (state)0, NULL, 0,  0,  NULL,       {halt,  halt},
};

state_table *pop_get_command(POP *p, register char *mp)
{
    state_table     *s;
    char            buf[MAXMSGLINELEN];

    if ((p->arg_count = pop_parse(p,mp)) < 0)
        return(NULL);

    /*
    **  Search for the POP command in the command/state table
    */

    for (s = states; s->command; s++)
    {
        /*
        **  current operating state?
        */
        if ((strcmp(s->command, p->pop_command) == 0)
             && (s->ValidCurrentState == p->CurrentState))
        {

          /*
          ** Make sure command line is syntacticly correct
          */
          if (p->arg_count < s->min_args)
                return((state_table *)pop_msg(p, POP_FAILURE,
                    "Too few arguments for the %s command.",p->pop_command,0));

          if (p->arg_count > s->max_args)
                return((state_table *)pop_msg(p, POP_FAILURE,
                    "Too many arguments for the %s command.",p->pop_command,0));

          /*
          ** Return a pointer to command in the command/state table
          */
          return (s);
        }
    }

    pop_log(p,"unknown command: \"%s\"",p->pop_command,0);
    return((state_table *)pop_msg(p, POP_FAILURE,
        "Unknown command: \"%s\".", p->pop_command, 0));
}

/************************************************************************
**  Pop_last:   Return the last message accessed
*************************************************************************/
int pop_last(POP *p)
{
  return(pop_msg
        (p, POP_SUCCESS, " %d was last message accessed", p->last_msg, 0));
}

/************************************************************************
**  Pop_list:   List the contents of the VMS NEWMAIL folder
*************************************************************************/
int pop_list(POP *p)
{
    Message         *mp;	    
    register int    status, len, i;
    register int    flag = 0;
    register int    msg_num;
    char            buffer[MAXLINELEN];

    if (p->arg_count > 0)              
    {
        msg_num = atoi(p->pop_args[1]);

        /*
        **  Is the requested message out of range?
        */
        if ((msg_num < 1) || (msg_num > p->msg_count))
            return (pop_msg (p,POP_FAILURE,
                "Message %d does not exist.",msg_num, 0));

        /*
        **  Get a pointer to the message in the message list
        */
        mp = &p->mptr[msg_num-1];

        /*
        **  Is the message already flagged for deletion?
        */
        if (mp->del_flag)
            return (pop_msg (p,POP_FAILURE,
                "Message %d has been deleted.",msg_num, 0));

        /*
        **  Display message information
        */
        return (pop_msg(p,POP_SUCCESS,"%u %u", msg_num, mp->length, 0));

    }

    /*
    **  Message number was not supplied - display the entire list of messages
    */
    pop_msg(p,POP_SUCCESS,
        "%u messages (%u octets)",
            p->msg_count - p->msgs_deleted,
            p->newmail_size - p->bytes_deleted);

    /*
    **  Walk through the message information list.
    **  Don't show messages marked as deleted.
    */
    for (i = p->msg_count, mp = p->mptr; i > 0; i--, mp++)
    {
        if (!mp->del_flag)
        {
            (void)sprintf(buffer, "%u %u\r\n\0", mp->number, mp->length);
            len = strlen(buffer);
            status = send(p->sockfd, buffer, len, flag);
        }
    }

    sprintf(buffer, "%s\0", ENDMULTLINTRANS);
    len = strlen(buffer);
    status = send(p->sockfd, buffer, len, flag);

    return(POP_SUCCESS);
}

/*****************************************************************************
**  Pop_parse:  Parse an input line from a POP client
**              into null-delimited tokens
**  Return:	the number of tokens extracted minus the command itself
*****************************************************************************/
int pop_parse(POP *p, char *buf)
{
    char            *mp;
    register int    i;

    /*
    **  Loop through the POP command array
    */
    for (mp = buf, i = 0; ; i++) 
    {
        while (isspace(*mp)) mp++;
        if (*mp == 0) break;    
        if (i >= MAXARGCOUNT) 
	{
            pop_log(p,"too many command arguments (%d)",i,0);
            return(-1);
        }
        p->pop_args[i] = mp;    
        while (!isspace(*mp) && *mp) mp++;
        if (*mp) *mp++ = 0;     
    }

    if (i == 0) return (NEGATIVE);    /*  No parameters were */
    lower(p->pop_command);
    return (i-1);

}

/***************************************************************************
** Pop_pass - Verify user's password in the VMS authorization file
** Return   - POP_SUCCESS or POP_FAILURE
***************************************************************************/
int pop_pass(POP *p)
{
  char password[33];
  char username[13];
  int  status;

  strcpy(password,p->pop_args[1]); upper(password);
  strcpy(username,p->user);        upper(username);
  status = PWDcheck(username,password);
  memset(password,0,sizeof(password));

  switch (status)
  {
    case 0:
      pop_log(p,"password supplied for \"%s\" is incorrect.", p->user, 0);
      return (pop_msg(p,POP_FAILURE,
              "Password supplied for \"%s\" is incorrect.", p->user, 0));

     case 1:
       status = mail_open_user_context(p);
       if (vms_error(status))
         return(pop_msg(p,POP_FAILURE,"Opening mail user context: %s",
                        vms_message(status),0));
       else
       {
         status = mail_user_info(p);
         if (vms_error(status))
           return(pop_msg(p,POP_FAILURE,"Getting mail user info: %s",
                          vms_message(status),0));
         else
         {
           status = mail_open_file_context(p);
           if (vms_error(status))
             return(pop_msg(p,POP_FAILURE,"Opening mail file context: %s",
                            vms_message(status),0));
           else
           {
             status = mail_open_message_context(p);
             if (vms_error(status))
               return(pop_msg(p,POP_FAILURE,
                              "Opening mail message context: %s",
                              vms_message(status),0));
             else
             {
               if (!p->newmail_selected)
                 status = mail_folder_select(p,newmail_folder);
               if (vms_error(status))
                 return(pop_msg(p,POP_FAILURE,
                                "Selecting mail folder: %s",
                                vms_message(status),0));
               else
               {
                 pop_msg(p,POP_SUCCESS,"Username/password combination ok",0);
                 pop_build_info(p);
                 return(POP_SUCCESS);
               }
             }
           }
         }
       }
       break;

     default:
       pop_log(p,"error determining password validity",0);
       return (pop_msg(p,POP_FAILURE,
               "Error determining password validity.",0));
       break;
  }
}

/***************************************************************************
**  Pop_quit:   Disconnect from server
**  Return:	Currently assumes POP_SUCCESS always...
****************************************************************************/
int pop_quit(POP *p)
{
    int status;

    status = mail_close_user_context(p);
    if (p->connected)
      return(pop_msg(p, POP_SUCCESS,
             "Pop server at %s signing off.",p->server, 0));
    else
      return(POP_SUCCESS);
}

/***************************************************************************
**  Pop_rset:   Undelete all messages flagged for deletion
**  Return:	Message count and Total nuber of bytes
****************************************************************************/
int pop_rset(POP *p)
{
    Message	    *mp;         
    register int    i;

    for (i = p->msg_count, mp = p->mptr; i > 0; i--, mp++)
    {
        mp->del_flag = FALSE;
        mp->retr_flag = FALSE;
    }

    p->msgs_deleted  = 0;
    p->bytes_deleted = 0;
    p->last_msg = 0;		    /*  Reset the last-message-access flag */

    return (pop_msg(p,POP_SUCCESS,"NEWMAIL Folder has %u messages (%u octets)",
        p->msg_count, p->newmail_size, 0));
}

/****************************************************************************
**  Pop_send:   Send the header and a possibly specified number of lines
**              from a mail message to a POP client.
**  Return:	POP_SUCCESS or POP_FAILURE
****************************************************************************/
int pop_send(POP *p)
{
    Message	    *mp;
    register int    msg_num=0;
    register int    msg_lines=0;
    char            buffer[MAXMSGLINELEN];
    int             status, len, flag;

    msg_num = atoi(p->pop_args[1]);

    /*
    **  Is requested message out of range?
    */
    if ((msg_num < 1) || (msg_num > p->msg_count))
        return (pop_msg(p,POP_FAILURE,"Message %d does not exist.",msg_num, 0));

    /*
    **  Get a pointer to the message in the message list
    */
    mp = &p->mptr[msg_num-1];

    if (mp->del_flag)           /*  Is the message flagged for deletion? */
        return (pop_msg(p,POP_FAILURE,
            "Message %d has been deleted.",msg_num, 0));

    /*
    **  If this is a TOP command, get the number of lines to send
    */
    if (strcmp(p->pop_command,"top") == 0)
    {
        msg_lines = atoi(p->pop_args[2]);
        if (msg_lines > mp->lines)      /* Sanity check */
            msg_lines = mp->lines;
    }
    else				/* Assume RETR (retrieve) was issued */
    {
        msg_lines = mp->lines;
    }

    pop_msg(p,POP_SUCCESS,"%u octets",mp->length, 0);

    /*
    **  Retrieve all the lines for this message.  
    **  VMS MAIL failure logged in the log file
    */
    status = mail_retrieve_messages(p, msg_num, msg_lines);

    /*
    ** Mark the message as retrieved only if still connected,
    ** and if the command was "retr".
    */
    if ((strcmp(p->pop_command,"retr") == 0) && p->connected)
      mp->retr_flag = TRUE;

    sprintf(buffer, "%s\0", ENDMULTLINTRANS);
    len = strlen(buffer);
    status = send(p->sockfd, buffer, len, flag);

    return(POP_SUCCESS);
}

/***************************************************************************
**  Pop_stat:   Summary of NEW (unread) mail in the NEWMAIL folder
**  Return:	Message count and Total number of bytes for NEWMAIL folder.
****************************************************************************/
int pop_stat(POP *p)
{
  int  status;
  int  message_id;
  unsigned int bytes          = 0;
  unsigned int messages_bytes = 0;


    for (message_id=1; message_id<=p->msg_count; message_id++,bytes=0)
    {
      status = mail_message_bytes(p,&message_id,&bytes);
      messages_bytes += bytes;
    }

    p->newmail_size = messages_bytes;

    return (pop_msg (p,POP_SUCCESS, "%u %u",
            p->msg_count - p->msgs_deleted,
            p->newmail_size -  p->bytes_deleted, 0));
}

/**************************************************************************
**  Pop_updt:   Update user's mail file - delete (with purging) messages
**		marked for deletion and move retrieved messages to the
**		MAIL folder.
**  Return:	status of pop_quit call...
***************************************************************************/
int pop_updt(POP *p)
{
    char        buffer[BUFSIZ];         /*  Read buffer */
    Message     *mp; 
    int         msg_num;                /*  Current message counter */
    int         msg_cnt, status, PURGE=0;

    pop_log(p,"updating %s\'s VMS Mail file",p->user,0);

    msg_cnt = p->msg_count;
    for (msg_num = 1; msg_num <= msg_cnt; ++msg_num)
    {
        /*
        **  Get a pointer to the message information list
        */
        mp = &p->mptr[msg_num-1];

        if (mp->del_flag)           
        {
            status = mail_delete_message(p, &msg_num);
            p->msg_count--;
            status = mail_message_new_count(p);
            PURGE = TRUE;
        }

        else if (mp->retr_flag)     
        {
            pop_log(p,"pop_updt: moving message #%d to MAIL folder",msg_num,0);
            mail_message_move(p, msg_num);
            p->msg_count--;
            status = mail_message_new_count(p);
        }
    }   /* --for loop */

    if (PURGE)
        mail_purge_waste(p);    /* delete wastebasket folder & compress */
    return(pop_quit(p));
}

/*************************************************************************
** Pop_user:	verify existance of user in the authorization file
** Return:	POP_SUCCESS or POP_FAILURE
*************************************************************************/
int pop_user(POP *p)
{
  strcpy(p->user, p->pop_args[1]);
  if (user_exists(p->user))
    return(pop_msg(p,POP_SUCCESS,"Password required for \"%s\"",p->user, 0));
  else
  {
    pop_log(p,"username \"%s\" is invalid", p->user, 0);
    return(pop_msg(p,POP_FAILURE,"Username \"%s\" is invalid", p->user, 0));
  }
}

/***************************************************************************
**  Pop_xtnd_stats - show pop server statistics
****************************************************************************/
int pop_xtnd_shutdown(POP *p)
{
  pop_log(p, "shutdown requested", 0);
  pop_msg(p, POP_SUCCESS, "will shutdown when no threads are connected", 0);
  shutdown = TRUE;
  return(POP_SUCCESS);
}

/***************************************************************************
**  Pop_xtnd_stats - show pop server statistics
****************************************************************************/
int pop_xtnd_stats(POP *p)
{
  char  buffer[128];
  char  date_str[11];
  char  time_str[9];
  float cpu;
  int   count;
  int   status;
  extern POP pop[];

  pop_msg(p,POP_SUCCESS,"Statistics follow", 0);

  get_time(time_str);
  get_date(date_str);
  sprintf(buffer, "  Current Time         : %s %s\n", date_str, time_str);
  status = send(p->sockfd, buffer, strlen(buffer), 0);

  sprintf(buffer, "  Start Time           : %s\n", start_time);
  status = send(p->sockfd, buffer, strlen(buffer), 0);

  cpu = get_cpu();
  sprintf(buffer, "  CPU Seconds          : %-8.2f (%d mins, %d secs)\n",
         cpu, (int)(cpu/60), (int)((int)cpu%60));
  status = send(p->sockfd, buffer, strlen(buffer), 0);

  sprintf(buffer, "  Current Threads      : %d\n", current_threads);
  status = send(p->sockfd, buffer, strlen(buffer), 0);

  sprintf(buffer, "  Total Threads        : %d\n", total_threads);
  status = send(p->sockfd, buffer, strlen(buffer), 0);

  sprintf(buffer, "  Max Threads          : %d\n", maximum_threads);
  status = send(p->sockfd, buffer, strlen(buffer), 0);

  sprintf(buffer, "  Too Many Threads     : %d\n", too_many_threads);
  status = send(p->sockfd, buffer, strlen(buffer), 0);

  sprintf(buffer, "  Retrieved Messages   : %d\n", retrieved_messages);
  status = send(p->sockfd, buffer, strlen(buffer), 0);

  sprintf(buffer, "  'From' Parse Errors  : %d\n", from_parse_errors);
  status = send(p->sockfd, buffer, strlen(buffer), 0);

  sprintf(buffer, "  Normal Disconnects   : %d\n", normal_disconnects);
  status = send(p->sockfd, buffer, strlen(buffer), 0);

  sprintf(buffer, "  Abnormal Disconnects : %d\n", abnormal_disconnects);
  status = send(p->sockfd, buffer, strlen(buffer), 0);

  sprintf(buffer, "  Client Timeouts      : %d\n", timeouts);
  status = send(p->sockfd, buffer, strlen(buffer), 0);

  sprintf(buffer, "  Current Users        :\n");
  status = send(p->sockfd, buffer, strlen(buffer), 0);
  for (count=0; count<current_threads; count++)
  {
    sprintf(buffer, "    %2d. %s\n", count, pop[count].user);
    status = send(p->sockfd, buffer, strlen(buffer), 0);
  }

  sprintf(buffer, "%s", ENDMULTLINTRANS);
  status = send(p->sockfd, buffer, strlen(buffer), 0);

  return(POP_SUCCESS);
}

/***************************************************************************
**  pop_xtnd_validate - validate user for accessing server's special functions
****************************************************************************/
int pop_xtnd_validate(char *user, char *command)
{
#define VALID_FILE "POP_XTND_LIST"
  FILE *valid_file;
  int  validated = FALSE;
  char buffer[256];
  char *tokens[100];
  int  count;
  int  length;
  int  number_tokens;

  if ((valid_file = fopen(VALID_FILE,"r")) != NULL)
  {
    while (!feof(valid_file) && !validated)
    {
      if (fgets(buffer,sizeof(buffer)-1,valid_file) == NULL)
        break;
      else
      {
        length = strlen(buffer);
        if (buffer[length-1] == '\n')
        {
          length--;
          buffer[length] = '\0';
        }
        if (length > 0)
        {
          lower(buffer);
          make_argv(buffer,&number_tokens,tokens," ,\t");
          if (strcmp(tokens[0],command) == 0)
          {
            for (count=1; count<number_tokens; count++)
            {
              if (strcmp(tokens[count],user) == 0)
              {
                validated = TRUE;
                break;
              }
            }
          }
        }
      }
    }
  }

  return(validated);
}

/***************************************************************************
**  get_pop_subcommand - extract XTND subcommand from client input line
****************************************************************************/
xtnd_table *get_pop_subcommand(POP *p)
{
  static xtnd_table subcommands[] =
  {
    { "stats",    0, 0, pop_xtnd_stats,    TRUE  },
    { "shutdown", 0, 0, pop_xtnd_shutdown, TRUE  },
    { NULL,       0, 0, 0,                 FALSE }
  };
  xtnd_table *s;

  /*  Search for the POP command in the command/state table */
  for (s = subcommands; s->subcommand; s++)
  {
    if (strcmp(s->subcommand,p->pop_subcommand) == 0)
    {
      /*  Were too few parameters passed to the subcommand? */
      if ((p->arg_count-1) < s->min_args)
        return((xtnd_table *)pop_msg(p,POP_FAILURE,
               "Too few arguments for the %s %s command.",
               p->pop_command,p->pop_subcommand,0));

      /*  Were too many parameters passed to the subcommand? */
      if ((p->arg_count-1) > s->max_args)
        return((xtnd_table *)pop_msg(p,POP_FAILURE,
               "Too many arguments for the %s %s command.",
               p->pop_command,p->pop_subcommand,0));

     /* Validate the user if necessary */
     if (s->validate)
     {
       if (!pop_xtnd_validate(p->user,s->subcommand))
          return((xtnd_table *)pop_msg(p,POP_FAILURE,
                 "Not validated for the %s %s command.",
                 p->pop_command,p->pop_subcommand,0));
     }

      /*  Return a pointer to entry for subcommand in the XTND table */
      return(s);
    }
  }

  /*  The client subcommand was not located in the XTND command table */
  pop_log(p,"unknown command:\"%s %s\".",p->pop_command,p->pop_subcommand,0);
  return((xtnd_table *)pop_msg(p,POP_FAILURE,
         "Unknown command: \"%s %s\".",p->pop_command,p->pop_subcommand,0));
}

/***************************************************************************
**  Pop_xtnd
****************************************************************************/
int pop_xtnd(POP *p)
{
  xtnd_table  *x;

  /*  Convert the XTND subcommand to lower case */
  lower(p->pop_subcommand);

  /*  Search for the subcommand in the XTND command table */
  if ((x = get_pop_subcommand(p)) == NULL) return(POP_FAILURE);

  /*  Call the function associated with this subcommand */
  if (x->function) return((*x->function)(p));

  /*  Otherwise assume NOOP */
  return (pop_msg(p,POP_SUCCESS,NULL,0));
}

