/*
 ===========================================================================
 =                                                                         =
 =  (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.C - Version 1.6a                                               =
 =                                                                         =
 = Synopsis:                                                               =
 =   This file contains miscellaneous functions that support the           =
 =   VMS IUPOP3 server.                                                    =
 =                                                                         =
 = 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>
#ifdef MULTINET
#include <types.h>
#include <time.h>
#else /* WINS || UCX */
#include <sys/types.h>
#include <sys/time.h>
#endif
#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>
#ifndef UCX
#include <vmsnet/inetiodef.h>
#endif
#include "iupop3_general.h"
#define Main
#include "iupop3_global.h"
#undef  Main
#include "iupop3_vms.h"
#include "version.h"

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

#define READ_BUFFERSIZE 256

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

#ifdef UCX
#define netclose(a) close(a)
#endif

#ifdef MULTINET
#define netclose(a) socket_close(a)
#endif

/* ======================================================================== */
/* Global Variables */
/* ======================================================================== */
int initial_socket;
int uerrno,verrno;

POP pop[MAX_THREADS];

volatile int attn_state, attn_threadnum;
volatile char read_buffer[MAX_THREADS][READ_BUFFERSIZE];

typedef unsigned short unsigned_word;

volatile struct IOSB
{
  unsigned_word status;
  unsigned_word terminator_offset;
  unsigned_word terminator;
  unsigned_word terminator_size;
} read_iosb[MAX_THREADS];
volatile struct IOSB connect_iosb;

/* ======================================================================== */
/* Prototypes */
/* ======================================================================== */
int  cancel_client_timeout_qio();
void client_read_ast();
void client_timeout_ast();
int  close_pop_thread();
int  create_initial_socket();
int  init_pop_thread();
int  issue_client_read_qio();
int  issue_client_timeout_qio();
int  issue_new_connect_qio();
void new_connect_ast();
void pop_log();
void process_new_connect();
void process_thread();
void system_log();

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

  switch (argc)
  {
    case 1:
      pop_port = RFC_PORT;
      break;

    case 2:
      if (is_numeric(argv[1]))
        pop_port = atoi(argv[1]);
      else
      {
        puts("iupop3: portnumber must be numeric");
        exit(-1);
      }
      break;

    default:
      puts("Usage: iupop3 [portnumber]\n");
      exit(-1);
  }

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

  system_log("starting pop3 server on port %d", pop_port,0);
  if (!create_initial_socket(pop_port))
  {
    system_log("could not create or bind initial socket",0);
    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) && shutdown) break;
    status = ASTON;
    if (vms_error(status)) system_log("setast: %s",vms_message(status),0);
    status = sys$hiber();
    switch (attn_state)
    {
      case ATTN_CONNECT:
        total_threads++;
        process_new_connect();
        issue_new_connect_qio();
        break;

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

      case ATTN_TIMEOUT:
        timeouts++;
        pop_log(&pop[attn_threadnum],"timed out",0);
        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_timeout_qio(attn_threadnum);
        process_thread(&pop[attn_threadnum],read_buffer[attn_threadnum]);
        if (pop[attn_threadnum].CurrentState == halt)
        {
          normal_disconnects++;
          pop_log(&pop[attn_threadnum],"normal disconnect",0);
          close_pop_thread(&pop[attn_threadnum]);
        }
        else
        {
          issue_client_read_qio(attn_threadnum);
          issue_client_timeout_qio(attn_threadnum);
        }
        break;

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

  system_log("server is shutting down",0);
#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 Timeout QIO */
/* ======================================================================== */
int cancel_client_timeout_qio(int threadnum)
{
  int status;

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

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

  if (p->in_use)
  {
    current_threads--;
    pop_log(p,"closing thread",0);
#ifdef UCX
    status = sys$cancel(p->channel);
#else
    status = sys$cancel((unsigned)p->sockfd);
#endif
    if (vms_error(status)) pop_log(p,"sys$cancel: %s",vms_message(status),0);
    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;
    free(p->client);
    free(p->ipaddr);
    if (p->mptr) free((char *)p->mptr);
    memset(p,0,sizeof(pop[0]));
    status = TRUE;
  }
  else
  {
    pop_log(p,"thread not in use!",0);
    status = FALSE;
  }
  return(status);
}

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

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

    if (vms_error(read_iosb[threadnum].status) ||
       (read_iosb[threadnum].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;
      read_buffer[threadnum][read_iosb[threadnum].terminator_offset-2] = '\0';
    }
  }
  attn_threadnum = threadnum;
  status = sys$wake(0,0);
  if (vms_error(status))
    pop_log(&pop[threadnum],"sys$wake: %s",vms_message(status),0);
}

/* ======================================================================== */
/* 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].sockfd);
  status = ASTON;
}

/* ======================================================================== */
/* 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("error creating initial socket",0);
  else
  {
    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("error binding initial socket",0);
    else
    {
      setsockopt(initial_socket,SOL_SOCKET,SO_KEEPALIVE,
                 (char *)&one,sizeof(one));
      return_value = TRUE;
    }
  }
  return(return_value);
}

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

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

    /* Initialize this POP structure -- all fields below are vital! */
    memset(p, 0, sizeof(pop[0]));
    p->sockfd            = sockfd;
    p->channel		 = vaxc$get_sdc(p->sockfd);
    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;

    (void) gethostname(p->server, MAXHOSTNAMELEN);

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

    /*
    **  Get the canonical name of the client
    */
    p->client = p->ipaddr;
/*****  Will hang with AST's disabled...
    ch = gethostbyaddr(&sin.sin_addr,sizeof(struct in_addr),sin.sin_family);
    if (ch == NULL)
    {
      pop_log(p,"gethostbyaddr failed",0);
      p->client = p->ipaddr;
    }
    else
    {
      p->client = malloc(sizeof(struct hostent));
      memcpy(p->client,ch->h_name,sizeof(struct hostent));
    }
*****/

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

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

#ifdef UCX
  status = sys$qio(0, pop[threadnum].channel, IO$_READVBLK,
                   &read_iosb[threadnum], client_read_ast, threadnum, 
                   read_buffer[threadnum], READ_BUFFERSIZE, 0, 0, 0, 0);
#else
  status = sys$qio(0, (unsigned)pop[threadnum].sockfd, IO$_READVBLK,
                   &read_iosb[threadnum], &client_read_ast, threadnum,
                   read_buffer[threadnum], READ_BUFFERSIZE, 0, 0, 0, 0);
#endif
  if (vms_error(status))
    pop_log(&pop[threadnum],"client read sys$qio: %s",vms_message(status),0);
  return(status);
}

/* ======================================================================== */
/* Issue Client Timeout QIO */
/* ======================================================================== */
int issue_client_timeout_qio(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(&pop[threadnum],"sys$bintim: %s",vms_message(status),0);
  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(&pop[threadnum],"sys$setimr: %s",vms_message(status),0);
    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, 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("new connect sys$qio: %s",vms_message(status),0);
    return(FALSE);
  }
}

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

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

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

  length = sizeof(addr);
  new_socket = accept(initial_socket,&addr,&length);
  if (new_socket < 0)
    system_log("accept failed",0);
  else
  {
    setsockopt(new_socket,SOL_SOCKET,SO_KEEPALIVE,
               (char *)&one,sizeof(one));
    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("new connection accepted: assigned thread %d",threadnum,0);
      if (init_pop_thread(threadnum,p,new_socket))
      {
        system_log("thread %d: client is %s",threadnum,p->client,0);
        pop_msg(p, POP_SUCCESS,
              "IU POP3 server V%s at %s, up since %s",
              VERSION, p->server, start_time, 0);
        issue_client_read_qio(threadnum);
        issue_client_timeout_qio(threadnum);
      }
      else
      {
        system_log("thread %d could not be initialized; closing",threadnum,0);
        netclose(new_socket);
      }
    }
    else
    {
      too_many_threads++;
      system_log("too many threads!",0);
      netclose(new_socket);
    }
  }
}

/* ======================================================================== */
/* Process Thread */
/* ======================================================================== */
void process_thread(p,message)
POP  *p;
char *message;
{
  int status;
  state_table *s;
  char original[512];

  strcpy(original,message);
  if ((s = pop_get_command(p, message)) == NULL) return;

  if (strcmp(s->command,"pass") == 0)
    pop_log(p,"will execute \"pass\"",0);
  else
    pop_log(p,"will execute \"%s\"",original,0);

  if (s->function)
  {
    p->CurrentState = s->PostState[(*s->function)(p)];
  }
  else
  {
    p->CurrentState = s->PostState[0];
    pop_msg(p, POP_SUCCESS, NULL);
  }
}
