/*
 ===========================================================================
 =                                                                         =
 =  (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.C - Version 1.8                                                =
 =                                                                         =
 = Synopsis:                                                               =
 =   This file contains miscellaneous functions that support the           =
 =   IUPOP3 server for VMS.                                                =
 =                                                                         =
 = 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.           =
 =                                                                         =
 ===========================================================================
*/

/* ======================================================================== */
/* Includes */
/* ======================================================================== */

#if !defined(WINS) && !defined(MULTINET) && !defined(UCX)
    Don't know how to make IUPOP3 for your TCP/IP implementation!
#endif

#include <stdio.h>
#include <sys/types.h>
#include <sys/time.h>
#include <netinet/in.h>
#include <sys/socket.h>
#include <sys/netdb.h>
#include <ctype.h>
#include <errno.h>
#include <iodef.h>
#include <ssdef.h>
#include <descrip.h>
#include <syidef.h>
#include <climsgdef.h>
#ifndef UCX
#include <vms/inetiodef.h>
#endif

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

#include "iupop3_general.h"
#include "iupop3_vms.h"
#define Main
#include "iupop3_global.h"
#undef  Main
#include "version.h"

/* ======================================================================== */
/* Defines */
/* ======================================================================== */
#define MAX_THREADS 31  /* VMS callable mail limit */

#define ATTN_SLEEP      0
#define ATTN_CONNECT    1
#define ATTN_DISCONNECT 2
#define ATTN_TIMEOUT    3
#define ATTN_DATA       4
#define ATTN_UNBLOCK    5

/* ======================================================================== */
/* Global Variables */
/* ======================================================================== */
int initial_socket;

volatile POP pop[MAX_THREADS];

volatile int attn_state, attn_threadnum;

volatile struct IOSB connect_iosb;

/* ======================================================================== */
/* Prototypes */
/* ======================================================================== */
int  cancel_client_timer();
void client_read_ast();
void client_timeout_ast();
void client_unblock_ast();
int  close_pop_thread();
int  create_initial_socket();
int  init_pop_thread();
int  issue_client_read_qio();
int  issue_client_timeout_timer();
int  issue_client_unblock_timer();
int  issue_logfile_flush_timer();
int  issue_new_connect_qio();
void logfile_flush_ast();
void new_connect_ast();
int  open_log_file();
void pop_log();
int  process_new_connect();
void process_thread();
void system_log();

/* ======================================================================== */
/* Main */
/* ======================================================================== */
main(int argc, char *argv[])
{
  state_table *s;
  char *message;
  int  status;
  int  count;
  int  pop_port   = RFC_PORT;
  int  serving    = TRUE;
  int  bad_option = FALSE;

  while ((argc > 1) && !bad_option)
  {
    argc--; argv++;

    if (strcmp(argv[0], "-port") == 0)
    {
      argc--; argv++;
      if (!argc)
        bad_option = TRUE;
      else
      {
        pop_port = atoi(argv[0]);
        if (pop_port <= 0) bad_option = TRUE;
      }
    }

    else if (strcmp(argv[0], "-loglevel") == 0)
    {
      argc--; argv++;
      if (!argc)
        bad_option = TRUE;
      else
      {
        if (strcmp(argv[0], "debug") == 0)
          master_log_level = LOG_DEBUG;
        else if (strcmp(argv[0], "thread") == 0)
          master_log_level = LOG_THREAD;
        else if (strcmp(argv[0], "info") == 0)
          master_log_level = LOG_INFO;
        else if (strcmp(argv[0], "error") == 0)
          master_log_level = LOG_ERROR;
        else
          bad_option = TRUE;
      }
    } 

    else if (strcmp(argv[0], "-logfile") == 0)
    {
      argc--; argv++;
      if (!argc)
        bad_option = TRUE;
      else
        log_filename = argv[0];
    }

    else if (strcmp(argv[0], "-maxmsg") == 0)
    {
      argc--; argv++;
      if (!argc)
        bad_option = TRUE;
      else
      {
        max_messages = atoi(argv[0]);
        if (max_messages <= 0) max_messages = MAX_MESSAGES;
        system_log(LOG_DEBUG, "max new messages per client = %d", max_messages);
      }
    }

    else if (strcmp(argv[0], "-purge_reclaim") == 0)
    {
      purge_reclaim = TRUE;
    }

    else if (strcmp(argv[0], "-xtnd") == 0)
    {
      argc--; argv++;
      if (!argc)
        bad_option = TRUE;
      else
        xtnd_filename = argv[0];
    }

    else
      bad_option = TRUE;
  }

  if (bad_option)
  {
    printf("Usage: iupop3 [-port n] [xtnd file] [-loglevel level]\n");
    printf("              [-logfile file] [-maxmsgs N] [-purge_reclaim]\n");
    exit(CLI$_INVQUAL);
  }
 
  open_log_file("w");

  get_date(start_time);
  get_time(start_time+11);
  start_time[10] = ' ';

  system_log(LOG_INFO, "starting IUPOP3 V%s server on port %d",
             VERSION, pop_port);

#ifdef KERBEROS
  if ((status = krb_get_lrealm(local_realm, 1)) != KSUCCESS)
  {
    system_log(LOG_ERROR, "realm lookup failed: %s", krb_err_txt[status]);
    exit(-1);
  }
  system_log(LOG_INFO, "local Kerberos realm is %s", local_realm);
#endif /* KERBEROS */

  gethostname(myhostname, MAXHOSTNAMELEN);

  if (!create_initial_socket(pop_port))
  {
    system_log(LOG_ERROR, "could not create or bind initial socket");
    exit(-1);
  }

  for (count=0; count<MAX_THREADS; count++)
     pop[count].in_use = FALSE;

  status = ASTOFF;
  listen(initial_socket, SOMAXCONN);
  issue_new_connect_qio();
  attn_state = ATTN_SLEEP;
  while (serving)
  {
    if ((current_threads == 0) && server_shutdown) break;
    status = ASTON;
    if (vms_error(status)) 
      system_log(LOG_ERROR, "setast: %s", vms_message(status));
    status = sys$hiber();
    switch (attn_state)
    {
      case ATTN_CONNECT:
        total_threads++;
        if (process_new_connect())
        {
#ifdef KERBEROS
          if (client_authenticated(&pop[attn_threadnum]))
          {
            issue_client_read_qio(attn_threadnum);
            issue_client_timeout_timer(attn_threadnum);
          }
          else
          {
            authentication_failures++;
            close_pop_thread(&pop[attn_threadnum]);
          }
#else /* KERBEROS */
          issue_client_read_qio(attn_threadnum);
          issue_client_timeout_timer(attn_threadnum);
#endif /* KERBEROS */
        }
        issue_new_connect_qio();
        break;

      case ATTN_DISCONNECT:
        abnormal_disconnects++;
        pop_log(LOG_ERROR, &pop[attn_threadnum], "abnormal disconnect");
        if ((pop[attn_threadnum].CurrentState != auth1) &&
            (pop[attn_threadnum].CurrentState != auth2))
          pop_updt(&pop[attn_threadnum]);
        cancel_client_timer(attn_threadnum);
        close_pop_thread(&pop[attn_threadnum]);
        break;

      case ATTN_TIMEOUT:
        timeouts++;
        pop_log(LOG_ERROR, &pop[attn_threadnum],"timed out");
        if ((pop[attn_threadnum].CurrentState != auth1) &&
            (pop[attn_threadnum].CurrentState != auth2))
          pop_updt(&pop[attn_threadnum]);
        close_pop_thread(&pop[attn_threadnum]);
        break;

      case ATTN_DATA:
        cancel_client_timer(attn_threadnum);
        process_thread(&pop[attn_threadnum]);
        if (pop[attn_threadnum].CurrentState == halt)
        {
          normal_disconnects++;
          pop_log(LOG_DEBUG, &pop[attn_threadnum], "normal disconnect");
          close_pop_thread(&pop[attn_threadnum]);
        }
        else if (pop[attn_threadnum].authentication_attempts >=
                 MAX_AUTHENTICATION_ATTEMPTS)
        {
          pop_log(LOG_ERROR, &pop[attn_threadnum], "auth attempts exceeded");
          close_pop_thread(&pop[attn_threadnum]);
        }
        else if (pop[attn_threadnum].retrieve.blocked)
        {
          issue_client_unblock_timer(attn_threadnum);
        }
        else 
        {
          issue_client_read_qio(attn_threadnum);
          issue_client_timeout_timer(attn_threadnum);
        }
        break;

      case ATTN_UNBLOCK:
        if (pop[attn_threadnum].retrieve.tries++ <= 10)
        {
          mail_retrieve_message_text(&pop[attn_threadnum]);
          if (pop[attn_threadnum].retrieve.blocked)
            issue_client_unblock_timer(attn_threadnum);
          else
          {
            issue_client_read_qio(attn_threadnum);
            issue_client_timeout_timer(attn_threadnum);
          }
        }
        else
        {
          pop_log(LOG_ERROR, &pop[attn_threadnum], 
                  "socket blocked for too long...disconnecting");
          pop_updt(&pop[attn_threadnum]);
          close_pop_thread(&pop[attn_threadnum]);
        }
        break;

      default:
        system_log(LOG_ERROR, "invalid attn_state: %d, attn_threadnum = %d",
                   attn_state, attn_threadnum);
        serving = FALSE;
        break;
    }
    attn_state = ATTN_SLEEP;
  }

  system_log(LOG_INFO, "server is shutting down");
  fclose(log_file);

#ifdef UCX
  status = sys$cancel((unsigned)vaxc$get_sdc(initial_socket));
#else
  status = sys$cancel(initial_socket);
#endif

  netclose(initial_socket);

  exit(1);
}

/* ======================================================================== */
/* Cancel Client Timer */
/* ======================================================================== */
int cancel_client_timer(int threadnum)
{
  int status;

  status = sys$cantim(threadnum+1,0);
  if (vms_error(status))
  {
    system_log(LOG_ERROR, "sys$cantim: %s", vms_message(status));
    return(FALSE);
  }
  else
    return(TRUE);
}

/* ======================================================================== */
/* Close POP Thread */
/* ======================================================================== */
int close_pop_thread(POP *p)
{
  int  status;

  if (p->in_use)
  {
    current_threads--;
    pop_log(LOG_THREAD, p, "closing thread");
    status = sys$cancel(p->channel);
    if (vms_error(status)) 
      pop_log(LOG_ERROR, p,"sys$cancel: %s", vms_message(status));
    netclose(p->sockfd);

    if ((int) p->message_context != NO_CONTEXT)
      mail_close_message_context(p);
    if ((int) p->file_context != NO_CONTEXT)
      mail_close_file_context(p);
    if ((int) p->user_context != NO_CONTEXT)
      mail_close_user_context(p);

    p->in_use = FALSE;
    if (p->mptr) free((char *)p->mptr);
    memset(p,0,sizeof(pop[0]));
    status = TRUE;
  }
  else
  {
    pop_log(LOG_ERROR, p, "thread not in use -- cannot close it!");
    status = FALSE;
  }
  return(status);
}

/* ======================================================================== */
/* Client Read AST */
/* ======================================================================== */
void client_read_ast(int threadnum)
{
  int status;

  status = ASTOFF;
  if (pop[threadnum].read_iosb.status == SS$_CANCEL)
    attn_state = ATTN_TIMEOUT;
  else
  {
    if (vms_error(pop[threadnum].read_iosb.status))
      pop_log(LOG_ERROR, &pop[threadnum],"read iosb: %s",
              vms_message(pop[threadnum].read_iosb.status));

    if (vms_error(pop[threadnum].read_iosb.status) ||
       (pop[threadnum].read_iosb.terminator_offset <= 0))
    {
      /* Be paranoid and flag it as disconnected right now */
      attn_state = ATTN_DISCONNECT;
      pop[threadnum].connected = FALSE;
    }
    else
      attn_state = ATTN_DATA;
  }
  attn_threadnum = threadnum;
  status = sys$wake(0,0);
  if (vms_error(status))
    pop_log(LOG_ERROR, &pop[threadnum],"sys$wake: %s", vms_message(status));
}

/* ======================================================================== */
/* Client Timeout AST */
/* ======================================================================== */
void client_timeout_ast(int threadnum)
{
  int status;

  /*
   * Just cancel, and let the read ast handle it.  Remember
   * that the threadnum parameter needs to be decremented
   * since it was incremented when calling sys$setimr.
   */
  status = ASTOFF;
  status = sys$cancel(pop[--threadnum].channel);
  status = ASTON;
}

/* ======================================================================== */
/* Client Unblock AST */
/* ======================================================================== */
void client_unblock_ast(int threadnum)
{
  int status;

  /*
   * Remember that the threadnum parameter needs to be decremented
   * since it was incremented when calling sys$setimr.
   */
  status = ASTOFF;
  attn_state = ATTN_UNBLOCK;
  attn_threadnum = --threadnum;
  status = sys$wake(0,0);
}

/* ======================================================================== */
/* Create Initial Socket */
/* ======================================================================== */
int create_initial_socket(int pop_port)
{
  int return_value = FALSE;
  int status;
  struct sockaddr_in server;
  static int one = 1;

  initial_socket = socket(AF_INET,SOCK_STREAM,0);
  if (initial_socket < 0)
    system_log(LOG_ERROR, "error creating initial socket");
  else
  {
    if (setsockopt(initial_socket, SOL_SOCKET, SO_KEEPALIVE, 
                        (char *)&one, sizeof(one)) == -1)
      system_log(LOG_ERROR, "error setting SO_KEEPALIVE");

    if (setsockopt(initial_socket, SOL_SOCKET, SO_REUSEADDR,
                        (char *)&one, sizeof(one)) == -1)
      system_log(LOG_ERROR, "error setting SO_REUSEADDR");

    server.sin_family      = AF_INET;
    server.sin_addr.s_addr = INADDR_ANY;
    server.sin_port        = htons(pop_port);
    status = bind(initial_socket, &server, sizeof(server));
    if (status != 0)
      system_log(LOG_ERROR, "error binding initial socket");
    else
      return_value = TRUE;
  }
  return(return_value);
}

/* ====================================================================== */
/* Init POP Thread */
/* ====================================================================== */
int init_pop_thread(int threadnum, POP *p, int sockfd)
{
    int status, len;
    char *pointer;
    struct hostent *ch;

    system_log(LOG_THREAD, "thread %d: initializing thread", threadnum);

    /* Initialize this POP structure -- all fields below are vital! */
    memset(p, 0, sizeof(pop[0]));
    p->sockfd            = sockfd;
#ifdef UCX
    p->channel		 = vaxc$get_sdc(p->sockfd);
#else
    p->channel		 = p->sockfd;
#endif
    p->threadnum         = threadnum;
    p->bytes_deleted     = 0;
    p->msg_count         = 0;
    p->newmail_size      = 0;
    p->msgs_deleted      = 0;
    p->newmail_selected  = FALSE;
    p->user_context      = NO_CONTEXT;
    p->file_context      = NO_CONTEXT;
    p->message_context   = NO_CONTEXT;
    p->CurrentState      = auth1;
    p->connected         = TRUE;

    memset(p->read_buffer, '\0', READ_BUFFERSIZE);
    memset(p->command_buffer, '\0', READ_BUFFERSIZE);
    p->command_pointer = p->command_buffer;

    /*
    **  Get the address and socket of the client to whom we're speaking
    */
    len = sizeof(struct sockaddr_in);
    if ((status = getpeername(sockfd, (struct sockaddr *)&p->sin, &len)) < 0)
    {
      pop_log(LOG_ERROR, p, "getpeername failed: error %d", status);
      return(FALSE);
    }
    pointer = inet_ntoa(p->sin.sin_addr);
    strcpy(p->ipaddr, pointer);
    p->client = p->ipaddr;
    p->ipport = ntohs(p->sin.sin_port);
    system_log(LOG_THREAD, "thread %d: client address is %s,%d", p->threadnum,
               p->client, p->ipport);

    /*
    **  Everything succeeded, so mark this thread as "in use"
    */
    p->in_use = TRUE;
    return(TRUE);
}

#ifdef KERBEROS
/* ======================================================================== */
/* Client Authenticated */
/* ======================================================================== */
int client_authenticated(POP *p)
{
  int retval = FALSE;
  int status;

  /* Receive the client's Kerberos ticket */
  strcpy(p->instance, "*");
  status = krb_recvauth(0L, p->sockfd, &p->ticket, "pop",
                        p->instance, &p->sin,
                        (struct sockaddr_in *) NULL, &p->kdata, "",
                        p->schedule, p->version);
  if (status != KSUCCESS)
  {
    pop_log(LOG_ERROR, p, "ticket receive error: %s", krb_err_txt[status]);
    pop_msg(p, POP_FAILURE, "ticket receive error: %s", krb_err_txt[status]);
  }
  else
  {
    /* Partial success -- ticket at least is OK */
    strcpy(p->user, p->kdata.pname);
    pop_log(LOG_THREAD, p, "ticket: %s.%s@%s (%s)", p->kdata.pname,
               p->kdata.pinst, p->kdata.prealm,
               inet_ntoa(p->sin.sin_addr));

    /* Is the user in OUR Kerberos realm? */
    if (strcmp(p->kdata.prealm, local_realm) != 0)
    {
      pop_log(LOG_ERROR, p, "(%s.%s@%s) realm not accepted", p->kdata.pname,
                 p->kdata.pinst, p->kdata.prealm);
      pop_msg(p, POP_FAILURE, "(%s.%s@%s) realm not accepted", p->kdata.pname,
                 p->kdata.pinst, p->kdata.prealm);
    }
    else
    {
      /* Usernames should have a null instance! */
      if (strcmp(p->kdata.pinst, ""))
      {
        pop_log(LOG_ERROR, p, "(%s.%s@%s) instance not accepted",
                   p->kdata.pname, p->kdata.pinst,
                   p->kdata.prealm);
        pop_msg(p, POP_FAILURE, "(%s.%s@%s) instance not accepted",
                   p->kdata.pname, p->kdata.pinst,
                   p->kdata.prealm);
      }
      else
      {
        pop_log(LOG_DEBUG, p, "Kerberos authenticated - client is '%s'", 
                p->user);
        if (valid_vms_user(p))
        {
          pop_log(LOG_DEBUG, p, "vms authenticated");
          pop_msg(p, POP_SUCCESS, "IUPOP3 server V%s at %s, up since %s",
                  VERSION, myhostname, start_time);
          retval = TRUE;
        }
        else
          pop_msg(p, POP_FAILURE, "user account \"%s\" not available", p->user);
      }
    }
  }
  return(retval);
}
#endif /* KERBEROS */

/* ======================================================================== */
/* Issue Client Read QIO */
/* ======================================================================== */
int issue_client_read_qio(int threadnum)
{
  int status;

  memset(pop[threadnum].read_buffer, '\0', READ_BUFFERSIZE);
  status = sys$qio(0, pop[threadnum].channel, IO$_READVBLK,
                   &pop[threadnum].read_iosb, client_read_ast, threadnum, 
                   pop[threadnum].read_buffer, READ_BUFFERSIZE, 0, 0, 0, 0);
  if (vms_error(status))
    pop_log(LOG_ERROR, &pop[threadnum], "client read sys$qio: %s", 
            vms_message(status));
  return(status);
}

/* ======================================================================== */
/* Issue Client Timeout Timer */
/* ======================================================================== */
int issue_client_timeout_timer(int threadnum)
{
  int status;
  int value = FALSE;
  int interval[2];
  $DESCRIPTOR(time_desc,"0 00:02:00.00");

  status = sys$bintim(&time_desc,interval);
  if (vms_error(status))
    pop_log(LOG_ERROR, &pop[threadnum], "sys$bintim: %s", vms_message(status));
  else
  {
    /* Use "threadnum+1" since sys$cantim uses 0 to cancel all requests */
    status = sys$setimr(0,interval,client_timeout_ast,threadnum+1,0);
    if (vms_error(status))
      pop_log(LOG_ERROR, &pop[threadnum], "sys$setimr: %s", vms_message(status));
    else
      value = TRUE;
  }
  return(value);
}

/* ======================================================================== */
/* Issue Client Unblock Timer */
/* ======================================================================== */
int issue_client_unblock_timer(int threadnum)
{
  int status;
  int value = FALSE;
  int interval[2];
  $DESCRIPTOR(time_desc,"0 00:00:02.00");

  status = sys$bintim(&time_desc,interval);
  if (vms_error(status))
    pop_log(LOG_ERROR, &pop[threadnum], "sys$bintim: %s", vms_message(status));
  else
  {
    /* Use "threadnum+1" since sys$cantim uses 0 to cancel all requests */
    status = sys$setimr(0,interval,client_unblock_ast,threadnum+1,0);
    if (vms_error(status))
      pop_log(LOG_ERROR, &pop[threadnum], "sys$setimr: %s", vms_message(status));
    else
      value = TRUE;
  }
  return(value);
}

/* ======================================================================== */
/* Issue New Connect QIO */
/* ======================================================================== */
int issue_new_connect_qio()
{
  int status;

#ifdef UCX
  status = sys$qio(0, (unsigned)vaxc$get_sdc(initial_socket), 
                  IO$_SETMODE | IO$M_READATTN, &connect_iosb, 0, 0,
		   &new_connect_ast, 0, 0, 0, 0, 0);
#else
  status = sys$qio(0, (u_long)initial_socket, IO$_ACCEPT_WAIT, 
                   &connect_iosb, &new_connect_ast, 0, 0, 0, 0, 0, 0, 0);
#endif
  if (!vms_error(status))
    return(TRUE);
  else
  {
    system_log(LOG_ERROR, "new connect sys$qio: %s", vms_message(status));
    return(FALSE);
  }
}

/* ======================================================================== */
/* New Connect AST */
/* ======================================================================== */
void new_connect_ast()
{
  int status;

  status = ASTOFF;
  if (vms_error(connect_iosb.status))
    system_log(LOG_ERROR, "connect iosb: %s",vms_message(connect_iosb.status));
  attn_state = ATTN_CONNECT;
  status = sys$wake(0,0);
  if (vms_error(status))
    system_log(LOG_ERROR, "new connect ast sys$wake: %s",vms_message(status));
}

/* ======================================================================== */
/* Open Log File */
/* ======================================================================== */
int open_log_file(char *how)
{
  if (log_filename == (char *)0)
  { 
    log_file = stdout;
    return(FALSE);
  }

  log_file = fopen(log_filename, how, "shr=get");
  if (log_file == (FILE *)0)
  {
    log_file = stdout;
    system_log(LOG_ERROR, "log file could not be opened: error %d", errno);
    return(FALSE);
  }
  else
  {
    issue_logfile_flush_timer();
    return(TRUE);
  }
}

/* ======================================================================== */
/* Issue Logfile Flush Timer */
/* ======================================================================== */
int issue_logfile_flush_timer()
{
  int status;
  int value = FALSE;
  int interval[2];
  $DESCRIPTOR(time_desc, "0 00:03:00.00");

  status = sys$bintim(&time_desc, interval);
  if (vms_error(status))
    system_log(LOG_ERROR, "sys$bintim: %s", vms_message(status));
  else
  {
    status = sys$setimr(0, interval, logfile_flush_ast, 0, 0);
    if (vms_error(status))
      system_log(LOG_ERROR, "sys$setimr: %s", vms_message(status));
    else
      value = TRUE;
  }
  return(value);
}

/* ======================================================================== */
/* Logfile Flush AST */
/* ======================================================================== */
void logfile_flush_ast()
{
  static char old_time[3];
  static char new_time[9] = "00";

  strcpy(old_time, new_time);
  get_time(new_time);
  new_time[2] = '\0';

  if ((strcmp(old_time, "23") == 0) && (strcmp(new_time, "00") == 0))
  {
    system_log(LOG_INFO, "closing log file");
    fclose(log_file);
    open_log_file("w");
    system_log(LOG_INFO, "new log file, up since %s", start_time);
  }
  else
  {
    fclose(log_file);
    open_log_file("a+");
  }

}

/* ======================================================================== */
/* Process New Connect */
/* ======================================================================== */
int process_new_connect()
{
  int  retval = FALSE;
  int  ok = FALSE;
  int  threadnum;
  int  status;
  int  new_socket;
  int  length;
  POP  *p;
  struct sockaddr addr;
  static int one = 1;
  static int sockbuf = SOCK_BUFSIZE;

  length = sizeof(addr);
  new_socket = accept(initial_socket,&addr,&length);
  if (new_socket < 0)
    system_log(LOG_ERROR, "accept failed");
  else
  {
    if (setsockopt(new_socket, SOL_SOCKET, SO_KEEPALIVE,
               (char *)&one, sizeof(one)) == -1)
      system_log(LOG_ERROR, "error setting SO_KEEPALIVE");

    if (setsockopt(new_socket, SOL_SOCKET, SO_SNDBUF,
               (char *)&sockbuf, sizeof(sockbuf)) == -1)
      system_log(LOG_ERROR, "error setting SO_SNDBUF");

    p = pop;
    for (threadnum=0; threadnum<MAX_THREADS; threadnum++,p++)
    {
      if (!p->in_use)
      {
        ok = TRUE;
        break;
      }
    }
    if (ok)
    {
      current_threads++;
      if (current_threads > maximum_threads) maximum_threads = current_threads;
      system_log(LOG_DEBUG, "new connection accepted: assigned thread %d",
                 threadnum);
      if (init_pop_thread(threadnum,p,new_socket))
      {
#ifndef KERBEROS
        pop_msg(p, POP_SUCCESS, "IUPOP3 server V%s at %s, up since %s",
                VERSION, myhostname, start_time);
#endif /* !KERBEROS */
        retval = TRUE;
        attn_threadnum = threadnum; /* assign for later qio issue */
      }
      else
      {
        system_log(LOG_ERROR, "thread %d could not be initialized; closing",
                   threadnum);
        netclose(new_socket);
      }
    }
    else
    {
      char message[80];
      sprintf(message, "%s too many threads!\r\n", POP_ERR);
#ifdef UCX
      sys$qio(0, (unsigned)vaxc$get_sdc(new_socket), IO$_WRITEVBLK,
                   0, 0, 0, message, strlen(message), 0, 0, 0, 0);
#else
      sys$qio(0, (unsigned)new_socket, IO$_WRITEVBLK,
                   0, 0, 0, message, strlen(message), 0, 0, 0, 0);
#endif
      too_many_threads++;
      system_log(LOG_ERROR, "error: too many threads!");
      netclose(new_socket);
    }
  }
  return(retval);
}

/* ======================================================================== */
/* Process Thread */
/* ======================================================================== */
void process_thread(POP *p)
{
  int status, size, index;
  state_table *s;
  char *pointer;

  /* Check to see if single character == backspace or delete */
  size = p->read_iosb.terminator_offset;
  if ((size == 1) && 
      ((*p->read_buffer == 8) || (*p->read_buffer == 127)))
  {
    if (p->command_pointer > p->command_buffer)
      *(--p->command_pointer) = '\0';
    return;
  }

  /* Move data from read_buffer to latest position in command_buffer */
  memcpy(p->command_pointer, p->read_buffer, size);
  p->command_pointer += size;

  while (TRUE)
  {
    /* Check for the CR/LF terminator; if it exists, continue. */
    if ((index = str_index(p->command_buffer, "\r\n")) == -1)
      break;
    {
      /* Remove the terminator, and advance the pointer. */
      pointer = p->command_buffer + index;
      *pointer = '\0';
      pointer+=2;

      if (strlen(p->command_buffer) > 0)
      {
        pop_log(LOG_THREAD, p, "rx: %s", 
          (strncasecmp(p->command_buffer, "pass ", 5) ? 
          p->command_buffer : "pass"));

        /*
         * If the previous command was "retr", it is finally safe to
         * mark the message as successfully retrieved.
         */
        if (p->previous_command)
        {
          if ((strcmp(p->previous_command, "retr") == 0) && 
              p->retrieve.completed)
          {
            Message *mp;
            if (p->retrieve.message_id > p->last_msg)
              p->last_msg = p->retrieve.message_id;
            mp = &(p->mptr[p->last_msg-1]);
            pop_log(LOG_DEBUG, p, "marking message #%d as retrieved", mp->number);
            mp->retr_flag = TRUE;
          } 
        } 

        /* Process the command. */
        if ((s = pop_get_command(p, p->command_buffer)) != NULL)
        {
          if (s->function)
            p->CurrentState = s->PostState[(*s->function)(p)];
          else
          {
            p->CurrentState = s->PostState[0];
            pop_msg(p, POP_SUCCESS, NULL);
          }
        }

        /* Retain previous command */
        if (strlen(p->pop_args[0]) < sizeof(p->previous_command))
          strcpy(p->previous_command, p->pop_args[0]);
        else
        {
          strncpy(p->previous_command, p->pop_args[0],
            sizeof(p->previous_command)-1);
          p->previous_command[sizeof(p->previous_command)-1] = '\0';
        }
      }

      /* Slide the buffer back to the beginning. */
      size = READ_BUFFERSIZE - (pointer - p->command_buffer);
      bcopy(pointer, p->command_buffer, size);
      p->command_pointer -= pointer - p->command_buffer;
      memset(p->command_buffer+size, '\0', READ_BUFFERSIZE-size);
    }
  }
}
