/* 
 ===========================================================================
 =                                                                         =
 =  (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_VMS.C                                                          =
 =                                                                         =
 = Synopsis:                                                               =
 =   A collection of VMS system services and callable mail functions       =
 =   for 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 */
/* ======================================================================== */
#ifndef UCX
#include <sys/ioctl.h>
#endif /* UCX */
#include <stdio.h>
#include <descrip.h>
#include <ssdef.h>
#include <maildef.h>
#include <uaidef.h>
#include <jpidef.h>
#include <stat.h>
#include <errno.h>

#include <sys/types.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"
#include "mailmsgdef.h"

/* ======================================================================== */
/* Defines */
/* ======================================================================== */
#ifdef MULTINET
#define ioctl socket_ioctl
#endif

/* ======================================================================== */
/* Prototypes */
/* ======================================================================== */
char  *date_vms2unix();
float get_cpu();
int   mail_close_file_context();
int   mail_close_message_context();
int   mail_close_user_context();
int   mail_delete_message();
int   mail_folder_select();
int   mail_message_bytes();
int   mail_message_lines();
int   mail_message_new_count();
int   mail_message_move();
int   mail_open_file_context();
int   mail_open_message_context();
int   mail_open_user_context();
int   mail_purge_waste();
int   mail_retrieve_message();
int   mail_retrieve_message_headers();
int   mail_retrieve_message_text();
int   mail_user_info();
void  pop_log();
void  system_log();
int   valid_vms_user();
char  *vms_message();

#ifdef UCX
#include "ucx_ioctl.c"
#endif

/* ======================================================================== */
/* Date - VMS to Unix Format */
/* ======================================================================== */
char *date_vms2unix(char *vmsdate)
{
  int  year, month, day, saveyear, dayofweek;
  long julian, j1, j2, j3;
  char *daynames[] = { "Sun", "Mon", "Tues", "Wed", "Thu", "Fri", "Sat" };
  char time[9];
  char monthname[4];
  static char buffer[32];

  memcpy(buffer,vmsdate+7,4); buffer[4] = '\0';
  saveyear = atoi(buffer);
  year = saveyear;

  memcpy(monthname,vmsdate+3,3); monthname[3] = '\0';
  switch (monthname[0])
  {
    case 'J':
      if (monthname[1] == 'A')
        month = 1;
      else if (monthname[2] == 'N')
        month = 6;
      else
        month = 7;
      break;
    case 'F':
      month = 2;
      break;
    case 'M':
      if (monthname[2] == 'R') month = 3; else month = 5;
      break;
    case 'A':
      if (monthname[1] == 'P') month = 4; else month = 8;
      break;
    case 'S':
      month = 9;
      break;
    case 'O':
      month = 10;
      break;
    case 'N':
      month = 11;
      break;
    default:
      month = 12;
      break;
  }
  monthname[1] = tolower(monthname[1]);
  monthname[2] = tolower(monthname[2]);

  memcpy(buffer,vmsdate,2); buffer[2] = '\0';
  day = atoi(buffer);

  memcpy(time,vmsdate+12,8); time[8] = '\0';

  if (year < 1584)
    buffer[0] = '\0';
  else
  {
    if (month > 2)
      month -= 3;
    else
    {
      month += 9;
      year--;
    }
    j1 = 146097 * (year / 100) / 4;
    j2 = 1461L  * (year % 100) / 4;
    j3 = (153L  * month + 2) / 5 + day + 1721119;
    julian = j1 + j2 + j3;
    dayofweek = (int)(++julian % 7);
    sprintf(buffer,"%-3s, %02d %-3s %02d %-8s", daynames[dayofweek], day,
            monthname, (saveyear-1900), time);
  }
  return(buffer);
}

/* ======================================================================== */
/* Get CPU Utilization */
/* ======================================================================== */
float get_cpu()
{
  int status;
  int cpu;
  int length;
  static int pid = 0;

  itemopen(outlist_ptr,outlist);
  itemadd(outlist_ptr,sizeof(cpu),JPI$_CPUTIM,&cpu,&length);
  itemclose(outlist_ptr);
  status = sys$getjpiw(0,&pid,0,outlist,0,0,0);
  if (vms_error(status))
  {
    system_log(LOG_ERROR, "sys$getjpiw: %s", vms_message(status));
    cpu = 0;
  }
  return(((float)cpu) / (100.0));
}

/* ======================================================================== */
/* Mail Close File Context */
/* ======================================================================== */
int mail_close_file_context(POP *p)
{
  int status = SS$_NORMAL;

  if (p->file_context)
  {
     status = mail$mailfile_close(&p->file_context,nullist,nullist);
     if (vms_error(status))
       pop_log(LOG_ERROR, p, "mail$mailfile_close: %s", vms_message(status));
     status = mail$mailfile_end(&p->file_context,nullist,nullist);
     if (vms_error(status))
       pop_log(LOG_ERROR, p, "mail$mailfile_end: %s", vms_message(status));
  }
  return(status);
}

/* ======================================================================== */
/* Mail Close Message Context */
/* ======================================================================== */
int mail_close_message_context(POP *p)
{
  int status = SS$_NORMAL;

  if (p->message_context)
  {
    status = mail$message_end(&p->message_context,nullist,nullist);
    if (vms_error(status))
      pop_log(LOG_ERROR, p, "mail$message_end: %s", vms_message(status));
  }
  return(status);
}

/* ======================================================================== */
/* Mail Close User Context */
/* ======================================================================== */
int mail_close_user_context(POP *p)
{
  int status = SS$_NORMAL;

  if (p->user_context)
  {
    status = mail$user_end(&p->user_context,nullist,nullist);
    if (vms_error(status))
      pop_log(LOG_ERROR, p, "mail$user_end: %s", vms_message(status));
  }
  return(status);
}

/* ======================================================================== */
/* Mail Delete Message */
/* ======================================================================== */
int mail_delete_message(POP *p,int *message_id)
{
  int status = SS$_NORMAL;

  if (!p->newmail_selected)
  {
    status = mail_folder_select(p,newmail_folder);
    if (vms_error(status))
      return(status);
  }

  itemopen(inlist_ptr,inlist);
  itemadd(inlist_ptr,sizeof(message_id),MAIL$_MESSAGE_ID,message_id,0);
  itemadd(inlist_ptr,0,MAIL$_NOSIGNAL,0,0);
  itemclose(inlist_ptr);
  status = mail$message_delete(&p->message_context,inlist,nullist);
  if (vms_error(status))
    pop_log(LOG_ERROR, p, "mail$message_delete: %s", vms_message(status));

  return(status);
}

/* ======================================================================== */
/* Mail Folder Select */
/* ======================================================================== */
int mail_folder_select(POP *p,char *folder_name)
{
  int length;
  int messages = 0;
  int status;

  p->msg_count = 0;

  itemopen(inlist_ptr,inlist);
  itemadd(inlist_ptr,strlen(folder_name),MAIL$_MESSAGE_FOLDER,folder_name,0);
  itemadd(inlist_ptr,0,MAIL$_NOSIGNAL,0,0);
  itemclose(inlist_ptr);

  itemopen(outlist_ptr,outlist);
  itemadd(outlist_ptr,sizeof(messages),MAIL$_MESSAGE_SELECTED,
          &messages,&length);
  itemadd(outlist_ptr,0,MAIL$_NOSIGNAL,0,0);
  itemclose(outlist_ptr);

  status = mail$message_select(&p->message_context,inlist,outlist);
  if (vms_error(status))
  {
    p->msg_count = 0;
    if (status == MAIL$_NOTEXIST)
      status = SS$_NORMAL;
    else
      pop_log(LOG_ERROR, p, "mail$message_select: %s", vms_message(status));
  }
  else
  {
    /* limit the number of newmail messages per connection */
    p->msg_count        = min(messages, max_messages);
    p->newmail_selected = TRUE;
    p->msgs_deleted     = 0;
  }
  return(status);
}

/* ======================================================================== */
/* Mail Message Bytes */
/* ======================================================================== */
int mail_message_bytes(POP *p,int *message_id,unsigned int *bytes)
{
  int  length = 0, size;
  int  lines  = 0;
  int  i, status;
  char external_id[256];
  char path[256];
  char buffer[256];
  stat_t statbuf;
  Message *mp;
  int   tmp, is_external=FALSE;
  char errmsg1[] = "An error has been detected in this VMS mail file !!!";
  char errmsg2[] =
           "It seems that the body of the message is missing on the VMS host";
  char errmsg3[] = "Use the return address to contact the original sender...";
  short msg_flags;

  *bytes = 0;
  mp = &p->mptr[*message_id-1];
  memset(external_id, '\0', sizeof(external_id));

  itemopen(inlist_ptr,inlist);
  itemadd(inlist_ptr,sizeof(message_id),MAIL$_MESSAGE_ID,message_id,0);
  itemadd(inlist_ptr,0,MAIL$_NOSIGNAL,0,0);
  itemclose(inlist_ptr);
  itemopen(outlist_ptr,outlist);
  itemadd(outlist_ptr, sizeof(msg_flags), MAIL$_MESSAGE_RETURN_FLAGS,
          &msg_flags, &length);
  itemadd(outlist_ptr,sizeof(external_id)-1,MAIL$_MESSAGE_EXTID,external_id,
          &length);
  itemadd(outlist_ptr,0,MAIL$_NOSIGNAL,0,0);
  itemclose(outlist_ptr);
  status = mail$message_info(&p->message_context,inlist,outlist);
  if (vms_error(status))
  {
    pop_log(LOG_ERROR, p, "mail$message_info: %s", vms_message(status));
    return(POP_FAILURE);
  }

  /* Check if message was sent with MAIL/FOREIGN */
  if (msg_flags & MAIL$M_EXTNSTD)
  {
    pop_log(LOG_INFO, p, "foreign message detected");
    mp->foreign_msg = TRUE;
  }

  is_external = *external_id;
  if (is_external)
  {                             /* Mail file is external to MAIL.MAI */
      sprintf(path,"%s%s",p->mail_directory,external_id);
      pop_log(LOG_DEBUG, p, "stat %s", path);
      statbuf.st_size = 0;
      if (stat(path,&statbuf) < 0)      /* problems with external file */
      {
        pop_log(LOG_ERROR, p, "stat() failed");
        if ((tmp = creat(path ,0,"rat=cr","rfm=var","shr=nil")) < 0)
          pop_log(LOG_ERROR, p, "creat failed");
        else
        {
            write(tmp, errmsg1, sizeof(errmsg1));
            write(tmp, errmsg2, sizeof(errmsg2));
            write(tmp, errmsg3, sizeof(errmsg3));
            close(tmp);
            mp->lines = 4;      /* adjust lines to report */
        }
      }
  }

  itemopen(inlist_ptr,inlist);
  itemadd(inlist_ptr,sizeof(message_id),MAIL$_MESSAGE_ID,message_id,0);
  itemadd(inlist_ptr,0,MAIL$_NOSIGNAL,0,0);
  itemclose(inlist_ptr);
  status = mail$message_get(&p->message_context,inlist,outlist);
  if (vms_error(status))
    pop_log(LOG_ERROR, p, "mail$message_get: %s", vms_message(status));
  else
  {
    /* get the size of all header lines */
    status = SS$_NORMAL;
    itemopen(inlist_ptr,inlist);
    itemadd(inlist_ptr,0,MAIL$_MESSAGE_CONTINUE,0,0);
    itemadd(inlist_ptr,0,MAIL$_NOSIGNAL,0,0);
    itemclose(inlist_ptr);
    itemopen(outlist_ptr,outlist);
    itemadd(outlist_ptr,sizeof(buffer)-1,MAIL$_MESSAGE_RECORD,buffer,&length);
    itemadd(outlist_ptr,0,MAIL$_NOSIGNAL,0,0);
    itemclose(outlist_ptr);
    status = SS$_NORMAL;
    for (i=1; i <=4; i++)
    {
        status = mail$message_get(&p->message_context,inlist,outlist);
        if (vms_error(status))
        {
            pop_log(LOG_ERROR, p, "mail$message_get in headers loop: %s",
                   vms_message(status));
            return(POP_FAILURE);
        }
        buffer[length] = '\0';
        *bytes += length;
    }
  }

  if (is_external)
  {
      sprintf(path,"%s%s",p->mail_directory,external_id);
      statbuf.st_size = 0;
      if (stat(path,&statbuf) < 0)   /* possible problems in external file */
        pop_log(LOG_ERROR, p, "stat failed");
      else
        *bytes = *bytes + statbuf.st_size;
  }

  /* Add some bytes for reformatted and new headers.    */
  /* No POP3 clients that we know of require this to be */
  /* exact, so we make it larger than it needs to be.   */
  if (*bytes > 0) *bytes += 150;

  return(status);
}

/* ======================================================================== */
/* Mail Message Lines */
/* ======================================================================== */
int mail_message_lines(POP *p,int *message_id)
{
  int  length = 0;
  int  status;
  int  size = 0;

  itemopen(inlist_ptr,inlist);
  itemadd(inlist_ptr,sizeof(message_id),MAIL$_MESSAGE_ID,message_id,0);
  itemadd(inlist_ptr,0,MAIL$_NOSIGNAL,0,0);
  itemclose(inlist_ptr);
  itemopen(outlist_ptr,outlist);
  itemadd(outlist_ptr,sizeof(size),MAIL$_MESSAGE_SIZE,&size,&length);
  itemadd(outlist_ptr,0,MAIL$_NOSIGNAL,0,0);
  itemclose(outlist_ptr);
  status = mail$message_info(&p->message_context,inlist,outlist);
  if (vms_error(status))
  {
    pop_log(LOG_ERROR, p, "mail$message_info: %s", vms_message(status));
    return(-1);
  }
  return(size);
}

/* ======================================================================== */
/* Mail Open File Context */
/* ======================================================================== */
int mail_open_file_context(POP *p)
{
  int status;

  pop_log(LOG_DEBUG, p, "opening %s",p->mail_file);
  status = mail$mailfile_begin(&p->file_context,nullist,nullist);
  if (vms_error(status))
  {
    pop_log(LOG_ERROR, p, "mail$mailfile_begin: %s", vms_message(status));
    if (p->file_context)
    {
      int tempsts;

      tempsts = mail$mailfile_end(&p->file_context,nullist,nullist);
      if (vms_error(tempsts))
        pop_log(LOG_ERROR, p, "mail$mailfile_end: %s", vms_message(tempsts));
    }
  }
  else
  {
    itemopen(inlist_ptr,inlist);
    itemadd(inlist_ptr,strlen(p->mail_file),MAIL$_MAILFILE_NAME,
            &p->mail_file,0);
    itemadd(inlist_ptr,0,MAIL$_NOSIGNAL,0,0);
    itemclose(inlist_ptr);
    status = mail$mailfile_open(&p->file_context,inlist,nullist);
    if (vms_error(status))
    {
      int tempsts;

      if (status == MAIL$_OPENIN)
      {
        pop_log(LOG_DEBUG, p, "user's mail file does not exist");
        status = SS$_NORMAL;
        p->has_no_mail_file = TRUE;
      }
      else
        pop_log(LOG_ERROR, p, "mail$mailfile_open: %s", vms_message(status));
      tempsts = mail$mailfile_end(&p->file_context,nullist,nullist);
      if (vms_error(tempsts))
        pop_log(LOG_ERROR, p, "mail$mailfile_end: %s", vms_message(tempsts));
    }
  }

  return(status);
}

/* ======================================================================== */
/* Mail Open Message Context */
/* ======================================================================== */
int mail_open_message_context(POP *p)
{
  int status;

  itemopen(inlist_ptr,inlist);
  itemadd(inlist_ptr,sizeof(p->file_context),MAIL$_MESSAGE_FILE_CTX,
          &p->file_context,0);
  itemadd(inlist_ptr,0,MAIL$_NOSIGNAL,0,0);
  itemclose(inlist_ptr);
  status = mail$message_begin(&p->message_context,inlist,nullist);
  if (vms_error(status))
  {
    pop_log(LOG_ERROR, p, "mail$message_begin: %s", vms_message(status));
    if (p->message_context) mail_close_message_context(p);
  }
  return(status);
}

/* ======================================================================== */
/* Mail Open User Context */
/* ======================================================================== */
int mail_open_user_context(POP *p)
{
  int status;

  status = mail$user_begin(&p->user_context,nullist,nullist);
  if (vms_error(status))
  {
    pop_log(LOG_ERROR, p, "mail$user_begin: %s", vms_message(status));
    if (p->user_context) mail_close_user_context(p);
  }
  return(status);
}

/* ======================================================================== */
/*  Mail Retrieve Message */
/* ======================================================================== */
int mail_retrieve_message(POP *p)
{
  int status = SS$_NORMAL;
  int retval = POP_FAILURE;
#ifdef IGNORE_MAIL11_HEADERS
  int decnet = FALSE;
#endif IGNORE_MAIL11_HEADERS

  if (!p->newmail_selected)
    status = mail_folder_select(p,newmail_folder);

  if (vms_error(status))
    pop_log(LOG_ERROR, p, "retrieve_message context: %s", vms_message(status));
  else
  {
   if ((p->retrieve.message_id < 1) || (p->retrieve.message_id > p->msg_count))
     return(pop_msg(p, POP_FAILURE, "Message %d does not exist.", 
            p->retrieve.message_id));
   else
   {
#ifndef IGNORE_MAIL11_HEADERS
      if (mail_retrieve_message_headers(p))
#else
      if (mail_retrieve_message_headers(p, &decnet))
#endif IGNORE_MAIL11_HEADERS
      {
#ifndef IGNORE_MAIL11_HEADERS
        if (mail_retrieve_message_text(p))
#else
        if (mail_retrieve_message_text(p, decnet))
#endif IGNORE_MAIL11_HEADERS
          retval = POP_SUCCESS;
      }
    }
  }
  return(retval);
}

/* ======================================================================== */
/* Mail Retrieve Message Headers */
/* ======================================================================== */
#ifndef IGNORE_MAIL11_HEADERS
int mail_retrieve_message_headers(POP *p)
#else
int mail_retrieve_message_headers(POP *p, int *decnet)
#endif IGNORE_MAIL11_HEADERS
{
  int  length;
  int  status;
  int  headers;
  short int record_type;
  char *pointer;
  char message_date[32], date[32];
  char buffer[256], from[256], xfrom[256], cc[256], to[256], subject[256];
  char date_str[11];
  char time_str[9];

  get_time(time_str);
  get_date(date_str);

  pop_log(LOG_DEBUG, p, "headers follow");

  /* Get message date */
  itemopen(inlist_ptr, inlist);
  itemadd(inlist_ptr, sizeof(p->retrieve.message_id), MAIL$_MESSAGE_ID,
          &p->retrieve.message_id, 0);
  itemadd(inlist_ptr, 0, MAIL$_NOSIGNAL, 0, 0);
  itemclose(inlist_ptr);
  itemopen(outlist_ptr, outlist);
  itemadd(outlist_ptr, sizeof(message_date), MAIL$_MESSAGE_DATE,
              message_date, &length);
  itemadd(outlist_ptr, 0, MAIL$_NOSIGNAL, 0, 0);
  itemclose(outlist_ptr);
  status = mail$message_get(&p->message_context,inlist,outlist);
  if (vms_error(status))
  {
    pop_log(LOG_ERROR, p, "mail$message_get: %s", vms_message(status));
    return(FALSE);
  }
  message_date[length] = '\0';
  strcpy(date, date_vms2unix(message_date));

  /* Prepare for remaining headers */
  itemopen(inlist_ptr,inlist);
  itemadd(inlist_ptr,0,MAIL$_MESSAGE_CONTINUE,0,0);
  itemadd(inlist_ptr,0,MAIL$_NOSIGNAL,0,0);
  itemclose(inlist_ptr);
  itemopen(outlist_ptr,outlist);
  itemadd(outlist_ptr,sizeof(buffer)-1,MAIL$_MESSAGE_RECORD,buffer,
          &length);
  itemadd(outlist_ptr,sizeof(record_type),MAIL$_MESSAGE_RECORD_TYPE,
          &record_type,0);
  itemadd(outlist_ptr,0,MAIL$_NOSIGNAL,0,0);
  itemclose(outlist_ptr);

  /* Process remaining headers */
  status = SS$_NORMAL;
  for (headers=1; headers<=MAXHEADERS && !vms_error(status); headers++)
  {
    memset(buffer, '\0', sizeof(buffer));
    status = mail$message_get(&p->message_context,inlist,outlist);
    if (vms_error(status))
    {
      pop_log(LOG_ERROR, p, "mail$message_get: %s", vms_message(status));
      return(FALSE);
    }
    else
    {
      while ((pointer = strchr(buffer,'\t')) != NULL) *pointer = ' ';
      switch (headers)
      {
        case FROM_LINE:
          strcpy(from, &buffer[6]);
          strcpy(xfrom, from);
#ifndef IGNORE_MAIL11_HEADERS
          patch_from_line(p, from);
#else
          patch_from_line(p, from, decnet);
#endif IGNORE_MAIL11_HEADERS
          break;

        case CC_LINE:
          strcpy(cc, &buffer[4]);
          lower(cc);
          break;

        case SUBJECT_LINE:
          strcpy(subject, &buffer[6]);
          break;

        case TO_LINE:
          strcpy(to, &buffer[4]);
          lower(to);
          break;
      }
    }
  }

  if (master_log_level >= LOG_DEBUG)
  {
    /* Print to log for debugging */
    fprintf(log_file, "   Date: %s\n", date);
    fprintf(log_file, "   From: %s\n", from);
    fprintf(log_file, "   Subject: %s\n", subject);
    fprintf(log_file, "   To: %s\n", to);
    fprintf(log_file, "   Cc: %s\n", cc);
    fprintf(log_file, "   X-VMS-From: %s\n", xfrom);
    fprintf(log_file, "   X-POP3-Server: %s IUPOP3 V%s\n", myhostname, VERSION);
    fprintf(log_file, "   X-POP3-ID: %s.%s.%d\n", 
            date_str, time_str, retrieved_messages);
  }
  else
  {
    pop_log(LOG_THREAD, p, "X-POP3-ID: %s.%s.%d", 
            date_str, time_str, retrieved_messages);
  }

#ifndef IGNORE_MAIL11_HEADERS
  /* Send to client */
  netprintf(p, "Date: %s\r\n", date);
  netprintf(p, "From: %s\r\n", from);
  netprintf(p, "Subject: %s\r\n", subject);
  netprintf(p, "To: %s\r\n", to);
  netprintf(p, "Cc: %s\r\n", cc);
  netprintf(p, "X-VMS-From: %s\r\n", xfrom);
  netprintf(p, "X-POP3-Server: %s IUPOP3 V%s\r\n", myhostname, VERSION);
  netprintf(p, "X-POP3-ID: %s.%s.%d\r\n", 
            date_str, time_str, retrieved_messages);
#else
  if (*decnet)
    {
      /* Send to client */
      netprintf(p, "Date: %s\r\n", date);
      netprintf(p, "From: %s\r\n", from);
      netprintf(p, "Subject: %s\r\n", subject);
      netprintf(p, "To: %s\r\n", to);
      netprintf(p, "Cc: %s\r\n", cc);
      netprintf(p, "X-VMS-From: %s\r\n", xfrom);
      netprintf(p, "X-POP3-Server: %s IUPOP3 V%s\r\n", myhostname, VERSION);
      netprintf(p, "X-POP3-ID: %s.%s.%d\r\n", 
                date_str, time_str, retrieved_messages);
    }
#endif IGNORE_MAIL11_HEADERS

  return(TRUE);
}

/* ======================================================================== */
/* Mail Retrieve Message Text */
/* ======================================================================== */
#ifndef IGNORE_MAIL11_HEADERS
int mail_retrieve_message_text(POP *p)
#else
int mail_retrieve_message_text(POP *p, int decnet)
#endif IGNORE_MAIL11_HEADERS
{
  /*
   * Problem:
   *   It is dangerous to let the network write() block us since we
   *   have ASTs disabled.  It is also dangerous to perform a 
   *   non-blocking $QIO for large messages, as the socket buffers
   *   will overflow causing garbled messages.
   *
   * Solution:
   *   Set the socket for non-blocking I/O for the duration of
   *   this function only.  If the sockets buffers are full,
   *   the network write() will fail with errno==EWOULDBLOCK.
   *   The 'retrieved' structure in the POP structure maintains
   *   the context, so when we return to this function after a
   *   timer AST expires, we can pick up where we left off.
   * 
   */
  static int zero = 0;
  static int one  = 1;
  int  bytes;
  int  status;
  int  length;
  int  save_errno;
  int  retval  = FALSE;
  int  sending = TRUE;
#ifdef IGNORE_MAIL11_HEADERS
  int  first = TRUE;
#endif IGNORE_MAIL11_HEADERS
#ifdef MULTINET
  extern int socket_errno;
#endif MULTINET
  Message *mp;

  /* If message is /FOREIGN, send an informative message instead */
  mp = &p->mptr[p->retrieve.message_id - 1];
  if (mp->foreign_msg)
  {
    pop_log(LOG_INFO, p, "cannot transfer foreign message");
    netprintf(p, "\r\nNote from IUPOP3 server:\r\n");
    netprintf(p, "You have received a foreign (binary) mail message from the sender shown \r\n");
    netprintf(p, "on the 'From:' line above.  That message is now in your VMS MAIL folder.\r\n\r\n");
    netprintf(p, "To access it, log in to your VMS host and use the VMS MAIL 'EXTRACT'\r\n");
    netprintf(p, "command.  If you have any questions, please contact your VMS system\r\n");
    netprintf(p, "administrator or support person.\r\n%s", ENDMULTLINTRANS);
    p->retrieve.completed = TRUE;
    p->retrieve.blocked   = FALSE;
    return(TRUE);
  }

  itemopen(inlist_ptr, inlist);
  itemadd(inlist_ptr, 0, MAIL$_MESSAGE_CONTINUE, 0, 0);
  itemadd(inlist_ptr, 0, MAIL$_NOSIGNAL, 0, 0);
  itemclose(inlist_ptr);

  itemopen(outlist_ptr, outlist);
  itemadd(outlist_ptr, sizeof(p->retrieve.buffer)-2, MAIL$_MESSAGE_RECORD,
          p->retrieve.buffer, &length);
  itemadd(outlist_ptr, 0, MAIL$_NOSIGNAL, 0, 0);
  itemclose(outlist_ptr);

  /* Set the socket for non-blocked I/O */
  ioctl(p->sockfd, FIONBIO, (long *)&one);

  while ((p->retrieve.lines_sent < p->retrieve.message_lines) && sending)
  {
    if (p->retrieve.blocked)
    {
      pop_log(LOG_DEBUG, p, "continuing");
      p->retrieve.blocked = FALSE;  /* hopefully! */
    }
    else
    {
      p->retrieve.tries = 0;
      status = mail$message_get(&p->message_context, inlist, outlist);
      if (vms_error(status))
      {
        pop_log(LOG_ERROR, p, "mail$message_get: %s", vms_message(status));
        sending = FALSE;
        break;
      }

#ifdef IGNORE_MAIL11_HEADERS
      if (!decnet) /* message received was not via DECNET */
        if (first)
          { /* skip first line of message (should be blank) */
            first = FALSE;
            p->retrieve.lines_sent++; /* make sure line is counted as sent */
            continue;
          } /* if */

#endif IGNORE_MAIL11_HEADERS

      p->retrieve.buffer[length++] = '\r';
      p->retrieve.buffer[length++] = '\n';
      p->retrieve.buffer[length]   = '\0';
      p->retrieve.bufsize = length;

      /* "Byte stuff" if necessary */
      if (p->retrieve.buffer[0] == POP_TERMINATE)
         bcopy(p->retrieve.buffer, p->retrieve.buffer+1, ++p->retrieve.bufsize);
    }

    /* Perform the non-blocked network write */
    bytes = netwrite(p->sockfd, p->retrieve.buffer, p->retrieve.bufsize);
#ifdef MULTINET
    save_errno = socket_errno;
#else
    save_errno = errno;
#endif

    if (bytes == p->retrieve.bufsize)
      p->retrieve.lines_sent++;
    else
    {
      if (bytes > 0)
      {
         /* Some bytes were written, so slide the buffer for next time */
         p->retrieve.bufsize -= bytes;
         bcopy(p->retrieve.buffer+bytes, p->retrieve.buffer, 
               p->retrieve.bufsize+1);
         save_errno = EWOULDBLOCK;
      }
      sending = FALSE;
      switch (save_errno)
      {
        case EWOULDBLOCK:
          pop_log(LOG_DEBUG, p, "blocked");
          blocked_count++;
          p->retrieve.blocked = TRUE;
          retval = TRUE;
          break;

        case EPIPE:
          pop_log(LOG_ERROR, p, "network write error: broken pipe");
          p->connected = FALSE;
          break;

        default:
          pop_log(LOG_ERROR, p, "network write error: errno = %d", save_errno);
          break;
      }
    }
  }

  if (p->retrieve.lines_sent == p->retrieve.message_lines)
  {
    netprintf(p, "\r\n%s", ENDMULTLINTRANS);
    p->retrieve.completed = TRUE;
    p->retrieve.blocked   = FALSE;
    if(strcmp(p->retrieve.command, "retr") == 0)
    { 
      int size;
      retrieved_messages++;
      size = p->mptr[p->retrieve.message_id-1].length;
      retrieved_octets += size;
      if (size > maximum_octets) maximum_octets = size;
      if (size < minimum_octets) minimum_octets = size;
    }
  }

  /* Reset the socket for blocked I/O */
  ioctl(p->sockfd, FIONBIO, (long *)&zero);

  return(retval);
}


/* ======================================================================== */
/* Mail User Info */
/* ======================================================================== */
int mail_user_info(POP *p)
{
  int  status;
  int  length = 0;
  char scratch[256];

  itemopen(inlist_ptr,inlist);
  itemadd(inlist_ptr,strlen(p->user),MAIL$_USER_USERNAME,
          &p->user,0);
  itemadd(inlist_ptr,0,MAIL$_NOSIGNAL,0,0);
  itemclose(inlist_ptr);
  itemopen(outlist_ptr,outlist);
  itemadd(outlist_ptr,sizeof(scratch)-1,MAIL$_USER_FULL_DIRECTORY,scratch,
          &length);
  itemadd(outlist_ptr,0,MAIL$_NOSIGNAL,0,0);
  itemclose(outlist_ptr);
  status = mail$user_get_info(&p->user_context,inlist,outlist);
  scratch[length] = '\0';
  if (vms_error(status))
    pop_log(LOG_ERROR, p, "mail$user_get_info: %s", vms_message(status));
  else
  {
    strcpy(p->mail_directory,scratch);
    strcpy(p->mail_file,scratch);
    strcat(p->mail_file,"MAIL.MAI");
  }
  return(status);
}

/* ========================================================================= */
/* Valid VMS User */
/* ========================================================================= */
int valid_vms_user(POP *p)
{
  int  status;
  int  retval = FALSE;
  struct flags uai_flags;
  int  length;
  char upcase_username[32];
  $DESCRIPTOR(username_desc, p->user);

  strcpy(upcase_username, p->user);
  upper(upcase_username);
  username_desc.dsc$w_length  = strlen(upcase_username);
  itemopen(outlist_ptr, outlist);
  itemadd(outlist_ptr, sizeof(uai_flags), UAI$_FLAGS, &uai_flags, &length);
  itemclose(outlist_ptr);

  status = sys$getuai(0, 0, &username_desc, outlist, 0, 0, 0);
  if (vms_error(status))
    pop_log(LOG_ERROR, p, "sys$getuai: %s", vms_message(status));
  else
  {
    /* Accept only if DISUSER, DISMAIL, PWD_EXPIRED, PWD2_EXPIRED are off */
    if (!(uai_flags.uai$v_disacnt || uai_flags.uai$v_nomail ||
          uai_flags.uai$v_pwd_expired || uai_flags.uai$v_pwd2_expired))
      retval = TRUE;
    else
      pop_log(LOG_ERROR, p, 
        "account \"%s\"is administratively disabled", p->user);
  }
  return(retval);
}

/* ========================================================================== */
/* VMS Message String */
/* ========================================================================== */
char *vms_message(unsigned int code)
{
  static   char  message[256];
  unsigned short length = 0;
  struct   dsc$descriptor message_dx;

  memset(message,0,sizeof(message));
  message_dx.dsc$w_length  = sizeof(message);
  message_dx.dsc$a_pointer = &message;
  sys$getmsg(code,&length,&message_dx,0xf,0);
  message[length] = '\0';
  return(&message);
}

/* ======================================================================== */
/* Mail Message Move */
/* ======================================================================== */
int mail_message_move(POP *p,int message_id)
{
  int status = SS$_NORMAL;
  char mail_folder[] = "MAIL";

  if (!p->newmail_selected)
    status = mail_folder_select(p, newmail_folder);
  if (vms_error(status))
    return(POP_FAILURE);
  else
  {
    itemopen(inlist_ptr,inlist);
    itemadd(inlist_ptr,sizeof(message_id),MAIL$_MESSAGE_ID,&message_id,0);
    itemadd(inlist_ptr,strlen(p->mail_file),MAIL$_MESSAGE_DEFAULT_NAME,
            &p->mail_file,0);
    itemadd(inlist_ptr,strlen(mail_folder),MAIL$_MESSAGE_FOLDER,mail_folder,0);
    itemadd(inlist_ptr,0,MAIL$_MESSAGE_DELETE,0,0);
    itemadd(inlist_ptr,0,MAIL$_NOSIGNAL,0,0);
    itemclose(inlist_ptr);
    status = mail$message_copy(&p->message_context,inlist,nullist);
    if (vms_error(status))
    {
      pop_log(LOG_ERROR, p, "mail$message_copy: %s", vms_message(status));
      return(POP_FAILURE);
    }
  }
  return(POP_SUCCESS);
}

/* ======================================================================== */
/* Mail Mark Deleted */
/* ======================================================================== */
int mail_message_new_count(POP *p)
{
    int status = SS$_NORMAL;

    itemopen(inlist_ptr,inlist);
    itemadd(inlist_ptr,strlen(p->user),MAIL$_USER_USERNAME, &p->user,0);
    itemadd(inlist_ptr, sizeof(p->msg_count),MAIL$_USER_SET_NEW_MESSAGES,
        &p->msg_count,0);
    itemadd(inlist_ptr,0,MAIL$_NOSIGNAL,0,0);
    itemclose(inlist_ptr);
    status = mail$user_set_info(&p->user_context,inlist,nullist);
    if (vms_error(status))
      pop_log(LOG_ERROR, p, "mail$user_set_info: %s", vms_message(status));
    return(status);
}

/* ======================================================================== */
/* MailFile Purge Waste */
/* ======================================================================== */
int mail_purge_waste(POP *p)
{
    int status = SS$_NORMAL;

    pop_log(LOG_DEBUG, p, "performing PURGE/RECLAIM on mailbox");
    itemopen(inlist_ptr,inlist);
    itemadd(inlist_ptr,0,MAIL$_MAILFILE_RECLAIM,0,0);
    itemadd(inlist_ptr,0,MAIL$_NOSIGNAL,0,0);
    itemclose(inlist_ptr);
    status = mail$mailfile_purge_waste(&p->file_context,inlist,nullist);
    if (vms_error(status))
    {
      pop_log(LOG_ERROR, p, "mail$mailfile_purge_waste: %s",
              vms_message(status));
      return(POP_FAILURE);
    }
    return(POP_SUCCESS);
}
