/*
 ===========================================================================
 =                                                                         =
 =  (C) Copyright 1991-1994 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>
#include <sys/types.h>
#include <ctype.h>

#include <netinet/in.h>

#ifdef KERBEROS
#include <krb.h>
#include <des.h>
#endif /* KERBEROS */

#include "iupop3_general.h"
#include "iupop3_vms.h"
#include "iupop3_global.h"
#include "version.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_client();
int         pop_xtnd_shutdown();
int         pop_xtnd_stats();
int         pop_xtnd_loglevel();

/****************************************************************************
**  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))
    {
      pop_log(LOG_ERROR, p, "Message %d does not exist.", msg_num);
      return(pop_msg(p,POP_FAILURE,"Message %d does not exist.",msg_num));
    }

    /*
    **  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)
    {
      pop_log(LOG_ERROR, p, "Message %d has already been deleted.", msg_num);
      return(pop_msg(p,POP_FAILURE,"Message %d has already been deleted.",
             msg_num));
    }

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

/*****************************************************************************
**  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},
  error,  "quit", 0,  0,  pop_quit,   {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));

          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));

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

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

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

/************************************************************************
**  Pop_list:   List the contents of the VMS NEWMAIL folder
*************************************************************************/
int pop_list(POP *p)
{
    Message         *mp;	    
    register int    i;
    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));

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

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

    }

    /*
    **  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)
            netprintf(p, "%u %u\r\n", mp->number, mp->length);
    }

    netprintf(p, "%s", ENDMULTLINTRANS);

    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(LOG_ERROR, p, "too many command arguments (%d)", i);
          pop_msg(p, POP_FAILURE,"too many command arguments (%d)", i);
          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)
{
#ifdef KERBEROS

  int status = 1;

#else /* KERBEROS */

  int  status;
  char password[33];
  char username[13];

  /* If username is invalid, pretend it's a bad password */
  if (!valid_vms_user(p))
  {
    authentication_failures++;
    p->authentication_attempts++;
    return(pop_msg(p,POP_FAILURE,
            "user account \"%s\" not accessible.", p->user));
  }

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

#endif /* KERBEROS */

  switch (status)
  {
    case 0:
      authentication_failures++;
      p->authentication_attempts++;
      pop_log(LOG_ERROR, p, "password supplied for \"%s\" is incorrect.", 
              p->user);
      return(pop_msg(p,POP_FAILURE,
            "user account \"%s\" not accessible.", p->user));
      break;

     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)));
       else
       {
         status = mail_user_info(p);
         if (vms_error(status))
           return(pop_msg(p,POP_FAILURE,"Getting mail user info: %s",
                          vms_message(status)));
         else
         {
           status = mail_open_file_context(p);
           if (p->has_no_mail_file)
           {
             pop_msg(p,POP_SUCCESS,"Username/password combination ok");
             return(POP_SUCCESS);
           }
           else if (vms_error(status))
             return(pop_msg(p,POP_FAILURE,"Opening mail file context: %s",
                            vms_message(status)));
           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)));
             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)));
               else
               {
                 pop_msg(p,POP_SUCCESS,"Username/password combination ok");
                 pop_build_info(p);
                 return(POP_SUCCESS);
               }
             }
           }
         }
       }
       break;

     default:
       pop_log(LOG_ERROR, p, "error determining password validity");
       return(pop_msg(p,POP_FAILURE, "Error determining password validity."));
       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,
             "IUPOP3 server at %s signing off.", myhostname));
    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));
}

/****************************************************************************
**  Pop_send:   Send the header and a 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;

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

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

    /*
    **  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? */
    {
      pop_log(LOG_ERROR, p, "Message %d has been deleted.", msg_num);
      return(pop_msg(p,POP_FAILURE, "Message %d has been deleted.",msg_num));
    }

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

    /*
    **  Retrieve all the lines for this message.  
    **  VMS MAIL failure logged in the log file
    */
    p->retrieve.completed     = FALSE;
    p->retrieve.blocked       = FALSE;
    p->retrieve.message_id    = msg_num;
    p->retrieve.message_lines = msg_lines;
    p->retrieve.lines_sent    = 0;
    p->retrieve.tries         = 0;
    strcpy(p->retrieve.command, p->pop_command);
    status = mail_retrieve_message(p);

    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 messages, bytes;

  messages = p->msg_count    - p->msgs_deleted;
  bytes    = p->newmail_size - p->bytes_deleted;

  pop_log(LOG_THREAD, p, "%u messages, %u bytes", messages, bytes);
  return(pop_msg(p,POP_SUCCESS, "%u %u", messages, bytes));
}

/**************************************************************************
**  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(LOG_DEBUG, p, "updating %s\'s mail file", p->user);

    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 && !mp->foreign_msg)
        {
            pop_log(LOG_DEBUG, p, "deleting message #%d", msg_num);
            status = mail_delete_message(p, &msg_num);
            p->msg_count--;
            status = mail_message_new_count(p);
            PURGE = TRUE;
        }

        else if (mp->retr_flag || mp->foreign_msg) 
        {
            if (mp->del_flag && mp->foreign_msg)
              pop_log(LOG_DEBUG, p, "skipping delete of foreign message");

            pop_log(LOG_DEBUG, p, "moving message #%d to MAIL folder",
                    msg_num);
            mail_message_move(p, msg_num);
            p->msg_count--;
            status = mail_message_new_count(p);
        }
    }   /* --for loop */

    if (PURGE && purge_reclaim)
        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)
{
#ifdef KERBEROS
  return(pop_msg(p,POP_SUCCESS,"Username already verified"));
#else /* KERBEROS */
  strncpy(p->user, p->pop_args[1], MAXUSERNAMELEN);
  lower(p->user);
  return(pop_msg(p,POP_SUCCESS,"Password required for \"%s\"",p->user));
#endif /* KERBEROS */
}

/***************************************************************************
**  Pop_xtnd_client - acknowledge client type and version
****************************************************************************/
int pop_xtnd_client(POP *p)
{
  pop_msg(p, POP_SUCCESS, "thanks");
  return(POP_SUCCESS);
}

/***************************************************************************
**  Pop_xtnd_shutdown - prepare to shutdown server
****************************************************************************/
int pop_xtnd_shutdown(POP *p)
{
  pop_log(LOG_INFO, p, "shutdown requested");
  pop_msg(p, POP_SUCCESS, "will shutdown when no threads are connected");
  server_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;
  extern POP pop[];

  pop_msg(p, POP_SUCCESS, "Statistics follow");
  get_time(time_str);
  get_date(date_str);

  netprintf(p, "  Version Number       : %s\r\n", VERSION);
  switch (master_log_level)
  {
    case LOG_ERROR:
      netprintf(p, "  Logging Level        : ERROR\r\n");
      break;
    case LOG_INFO:
      netprintf(p, "  Logging Level        : INFO\r\n");
      break;
    case LOG_THREAD:
      netprintf(p, "  Logging Level        : THREAD\r\n");
      break;
    case LOG_DEBUG:
      netprintf(p, "  Logging Level        : DEBUG\r\n");
      break;
  }
  netprintf(p, "  Current Time         : %s %s\r\n", date_str, time_str);
  netprintf(p, "  Start Time           : %s\r\n", start_time);
  cpu = get_cpu();
  netprintf(p, "  CPU Seconds          : %-8.2f (%d mins, %d secs)\r\n",
            cpu, (int)(cpu/60), (int)((int)cpu%60));
  netprintf(p, "  Current Threads      : %d\r\n", current_threads);
  netprintf(p, "  Total Threads        : %d\r\n", total_threads);
  netprintf(p, "  Max Threads          : %d\r\n", maximum_threads);
  netprintf(p, "  Too Many Threads     : %d\r\n", too_many_threads);
  netprintf(p, "  Normal Disconnects   : %d\r\n", normal_disconnects);
  netprintf(p, "  Abnormal Disconnects : %d\r\n", abnormal_disconnects);
  netprintf(p, "  Client Timeouts      : %d\r\n", timeouts);
  netprintf(p, "  Blocked Socket Count : %d\r\n", blocked_count);
  netprintf(p, "  Retrieved Messages   : %d\r\n", retrieved_messages);
  netprintf(p, "  Retrieved Octets     : %d\r\n", retrieved_octets);
  netprintf(p, "  Average Octets       : %d\r\n", 
     (retrieved_messages <= 0) ? 0 : (retrieved_octets/retrieved_messages));
  netprintf(p, "  Minimum Octets       : %d\r\n", minimum_octets);
  netprintf(p, "  Maximum Octets       : %d\r\n", maximum_octets);
  netprintf(p, "  Auth Failures        : %d\r\n", authentication_failures);

  netprintf(p, "  Current Users        :\r\n");
  for (count=0; count<current_threads; count++)
    netprintf(p, "    %2d. %s\r\n", count, pop[count].user);

  netprintf(p, "%s", ENDMULTLINTRANS);
  return(POP_SUCCESS);
}

/***************************************************************************
**  pop_xtnd_loglevel - modify server logging level
****************************************************************************/
int pop_xtnd_loglevel(POP *p)
{
  int  retval = POP_SUCCESS;
  char *level;

  level = p->pop_args[2];
  lower(level);

  if (strcmp(level, "info") == 0)
    master_log_level = LOG_INFO;
  else if (strcmp(level, "error") == 0)
    master_log_level = LOG_ERROR;
  else if (strcmp(level, "thread") == 0)
    master_log_level = LOG_THREAD;
  else if (strcmp(level, "debug") == 0)
    master_log_level = LOG_DEBUG;
  else
    retval = POP_FAILURE;

  if (retval == POP_SUCCESS)
  {
    pop_log(LOG_INFO, p, "logging level changed to %s", level);
    pop_msg(p, POP_SUCCESS, "logging level changed to %s", level);
  }
  else
  {
    pop_log(LOG_INFO, p, "invalid logging level %s", level);
    pop_msg(p, POP_FAILURE, "invalid logging level %s", level);
  }

  return(retval);
}

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

  if (xtnd_filename == (char *)0) return(FALSE);

  if ((valid_file = fopen(xtnd_filename ,"r")) != NULL)
  {
    while (!feof(valid_file) && !validated)
    {
      if (fgets(buffer,sizeof(buffer)-1,valid_file) == NULL)
        break;
      else
      {
        length = strlen(buffer) - 1;
        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;
              }
            }
          }
        }
      }
    }
    fclose(valid_file);
  }

  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  },
    { "client",   1, MAXARGCOUNT, pop_xtnd_client,   FALSE },
    { "loglevel", 1, MAXARGCOUNT, pop_xtnd_loglevel, 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));

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

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

      /*  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(LOG_ERROR, p, "unknown sub-command:\"%s %s\".",
          p->pop_command, p->pop_subcommand);
  return((xtnd_table *)pop_msg(p,POP_FAILURE,
         "Unknown sub-command: \"%s %s\".",p->pop_command,p->pop_subcommand));
}

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

