/*****************************************************************************/
/*
                                 CallMail.c

All of the functions interacting with the VMS callable Mail API.


PMDP IMAP
--------
Information supplied 03-JUL-2008 by Jeremy Begg on info-WASD mailing list:

From an email discussion on this topic back at the beginning of last year...

~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Mark> soyMAIL displays these PMDF IMAP subfolder mail files in the selectable
Mark> list along with other file and folders.  I have chosen to indicate
Mark> it's a PMDF IMAP folder using a double trailing slash.  User's need to
Mark> be aware of what they are dealing with because if they move messages
Mark> into or out of these subfolder areas, or create additional subfolders
Mark> in an exisiting area, PMDF IMAP will not necessarily reflect the
Mark> changes (presumably because it keeps it's own state in all it's
Mark> ancilliary files).

Jeremy> I don't think there's anything to worry about there.  PMDF IMAP uses a
Jeremy> few sources to find the folders assocated with a given VMS MAIL .MAI
Jeremy> ISAM file: the folder list as provided by Callable MAIL, the
Jeremy> MAIL.MAI-UIDDIR file, and its' own super-index file
Jeremy> (PMDF_IMAP.MAILBOX) in the user's primary mail directory.  If the user
Jeremy> creates a folder using some other means (e.g. VMS MAIL or soyMAIL) the
Jeremy> IMAP server will notice it.  (The user may have to re-subscribe their
Jeremy> IMAP client to find the new folder, depending on the IMAP client.)

This means that soyMAIL should look for PMDF_IMAP.MAILBOX in the user's
primary mail directory, this file will contain all the folders which the
IMAP server knows about.  The folder names will end in "!" or "=" but I'm
not sure of the significance of that character, suffice to say it's a flag
of some sort and not part of the folder's name.

Any folder name with a "/" in it indicates that the folder is a two-level
folder.  For example the folder "H3/LEVEL7" indicates there is a folder
called "H3" and a subfolder called "LEVEL7".  The messages for "H3/LEVEL7"
will be held in a subdirectory.  That subdirectory is defined by the file
PMDF_IMAP.MBXDIR, an RMS indexed file. The first 128 characters of each
record in PMDF_IMAP.MBXDIR are the "parent" folder name (e.g. "H3") and the
rest of the record identifies the directory. Both fields are NUL-filled to
pad them out to the required length.  Here's a sample:

$ dump/rec pmdf_imap.mbxdir

Dump of file FLASH$USER:[JEREMY.MAIL]PMDF_IMAP.MBXDIR;1 on  3-JUL-2008 12:58
File ID (30392,10,0)   End of file block 8 / Allocated 9

Record number 1 (00000001), 256 (0100) bytes, RFA(0003,0000,0001)

 00000000 00000000 00000000 00003348 H3.............. 000000
 00000000 00000000 00000000 00000000 ................ 000010
 00000000 00000000 00000000 00000000 ................ 000020
 00000000 00000000 00000000 00000000 ................ 000030
 00000000 00000000 00000000 00000000 ................ 000040
 00000000 00000000 00000000 00000000 ................ 000050
 00000000 00000000 00000000 00000000 ................ 000060
 00000000 00000000 00000000 00000000 ................ 000070
 4B31302E 4C49414D 2E594D45 52454A5B [JEREMY.MAIL.01K 000080
 5D5A4757 445A3845 36534F35 5A533946 F9SZ5OS6E8ZDWGZ] 000090
 00000000 00000000 00000000 00000000 ................ 0000A0
 00000000 00000000 00000000 00000000 ................ 0000B0
 00000000 00000000 00000000 00000000 ................ 0000C0
 00000000 00000000 00000000 00000000 ................ 0000D0
 00000000 00000000 00000000 00000000 ................ 0000E0
 00000000 00000000 00000000 00000000 ................ 0000F0

etc.

Hope this helps.
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

Notes on soyMAIL processing of PMDF IMAP:

1) The user option [PMDF-IMAP] flag is always checked and all of the following
PMDF IMAP-specific behaviours predicated on that.

2) When listing folders the VMS folders are first listed and then the
PMDF_IMAP.MAILBOX file read and the folders listed.  This consolidated list is
then sorted and duplicates eliminated.  When the [PMDF-IMAP] flag is set
soyMAIL will never attempt to list folders in other than the primary MAIL.MAI.

3) Before opening a mail file the name is checked.  If it is "MAIL" then the
usual soyMAIL callable Mail code paths followed.  If not "MAIL" then the
PMDF_IMAP.MBXDIR is read and the file name searched for to obtain the PMDF
IMAP-specific subdirectory specification to which MAIL.MAI is appended.  If the
search is unsuccessful an empty file specification is set (which will
ultimately result in callable mail reporting mail file not found).

4) When a subfolder is created by PMDF IMAP there is no MAIL.MAI file in
associated directory until a message is placed there.  soyMAIL needs to allow
for this situation.

5) PMDF IMAP does recreate the PMDF_IMAP.MAILBOX and PMDF_IMAP.MBXDIR if
necessary but in my observation only when messages are moved around.

6) An exclamation mark at the end of a PMDF_IMAP.MAILBOX folder name indicates
a trashed (but presumably not yet deleted) folder.  I'm guessing the equate
symbol is used as something different to that :-)  The trashed folder name
seems to be appended to the list and is changed to an exclamation mark when
finally deleted from the trash folder.


COPYRIGHT
---------
Copyright (C) 2005-2025 Mark G.Daniel
This program, comes with ABSOLUTELY NO WARRANTY.
This is free software, and you are welcome to redistribute it under the
conditions of the GNU GENERAL PUBLIC LICENSE, version 3, or any later version.


VERSION HISTORY
---------------
19-FEB-2021  MGD  bugfix; get it RIGHT! CallMailMessageContextEnd()
03-JAN-2021  MGD  bugfix; DEFAULT_ZONE memory leak from multiple missing
                     CallMailMessageContextEnd() after CallMailMessageContext()
                     (applicable to CGIplus usage only)
10-DEC-2013  MGD  bugfix; CallMailCopyFile() finalise with mail$mailfile_end()
15-NOV-2008  MGD  when VMS-newmsg set comply with VMS Mail handling
                    of the new mail count, do not reset new message flag
08-JUL-2008  MGD  major revision in conjunction with PMDF IMAP processing
06-JAN-2008  MGD  Happy Birthday Naomi!
                  bugfix; CallMailFileIn() do not prefix if name contains
                    a device or logical name (i.e. if ':' present)
                  bugfix; CallMailFileIn() if SYS$LOGIN: present in a file
                    name get the username home directory and use that!
                  bugfix; CallMailFileIn() do not munge if ODS-5 volume
30-NOV-2007  MGD  CallMailFileClose() accomodate bogus OPENIN return value
                    (see description of issue in function prologue)
                  CallMailFileClose() restore full close only if auto-purge
02-NOV-2007  MGD  CallMailDeleteIfEmpty() modularise empty mail file deletion
                  bugfix; delete empty mail files after msg move (and deletion)
12-AUG-2007  MGD  CallMailMessageGet() resets the NEWMSG flag as appropriate
                  CallMailMessageFlags() can be used to set flags
21-APR-2007  MGD  bugfix; CallMailFileList() set name length from PMDF folder
12-APR-2007  MGD  CallMailCopyFile() update newmail count if necessary
05-FEB-2007  MGD  CallMailCopyFile() allows 'sequential Mail files'
                    to be copied into mail file/folders
                  CallMailFileClose() explicitly handle MAIL$_OPENIN
                    return if mail file locked during close and reclaim
27-JAN-2007  MGD  specially process PMDF IMAP folder subdirectories
                    see CallMailPmdfImapFolder() for explanation
30-DEC-2006  MGD  CallMailFolderList() ensure an non-existant/empty mail
                    file produces an empty folder list
                  bugfix; CallMailMessageCopyMove() new message count update
28-DEC-2006  MGD  CallMailVolumeStruct() for volume structure identification
                  CallMailFileOpen() and CallMailMessageCopyMove() provide
                     rudimentary support for EFS (ODS-5) mail file names
21-OCT-2006  MGD  CallMailFileClose() enforce full-close purge of WASTEBASKET
                  CallMailMessageDelete() now checks non-MAIL.MAI mail files
                    and deletes them if empty (contain no folders)
04-JUL-2006  MGD  bugfix; CallMailMessageList() restore from temporary
                    'MessageContext' when searching
20-JUN-2006  MGD  CallMailUserDiskQuota() to obtain permanent and used
26-APR-2006  MGD  optimise CallMailMessageHeader() by completely replacing
                    mail$message_get() with mail$message_info() and adding
                    mail$message_get() to CallMailMessageGet() for non-external
                    mail messages only (suggested by Dave Jones after his
                    investigations demonstrated a reduction to 35% of original
                    search durations by eliminating an RMS file open implicit
                    in mail$message_get() with external message files).
20-APR-2006  MGD  bugfix; CallMailFileList() parsing of directory entry
                    name length and entry flags (per Dave Jones) and
                    use memmove() for shuffling inside the one buffer
25-FEB-2006  MGD  CallMailGoToNewMail() if the new message count differs
                    from the messages selected count then adjust it
                  bugfix; CallMailSetUserProfile() VMS option copy-send
                  bugfix; CallMailMessageDelete() close mail file to
                    empty WASTEBASKET before continuing request
                  bugfix; CallMailMessageCopyMove() adjust new message
                    count regardless of whether it's a copy or a move
01-FEB-2005  MGD  initial
*/

/*****************************************************************************/

#ifdef SOYMAIL_VMS_V7
#undef _VMS_V6_SOURCE
#define _VMS_V6_SOURCE
#undef __VMS_VER
#define __VMS_VER 70000000
#undef __CRTL_VER
#define __CRTL_VER 70000000
#endif

#pragma nomember_alignment

/* standard C header files */
#include <ctype.h>
#include <errno.h>
#include <file.h>
#include <stat.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unixio.h>

/* VMS related header files */
#include <devdef.h>
#include <dvidef.h>
#include <descrip.h>
#include <fibdef.h>
#include <iodef.h>
#include <maildef.h>
#include <mailmsgdef.h>
#include <rms.h>
#include <ssdef.h>
#include <starlet.h>
#include <stsdef.h>
#include <uaidef.h>

/* application header file */
#include "soymail.h"
#include "cgilib.h"
#include "callmail.h"
#include "other.h"
#include "request.h"

#define FI_LI "CALLMAIL", __LINE__

/* global storage */

char  CallMailNoSuchUser [] =
"An entry for this user could not be found in VMSMAIL_PROFILE (new account?)";

VMS_ITEM_LIST3  CallMailNullItem = {0,0,0,0};

VMS_ITEM_LIST3  CallMailNoSignalItem [] = {
   { 0, MAIL$_NOSIGNAL, 0, 0 },
   { 0,0,0,0 }
};

/* external storage */

extern BOOL  Debug;
extern int  VmsVersion;

/* required prototypes (some older VAXen have no MAIL$ROUTINES.H) */
int mail$message_begin (__unknown_params);
int mail$message_copy (__unknown_params);
int mail$message_delete (__unknown_params);
int mail$message_end (__unknown_params);
int mail$message_get (__unknown_params);
int mail$message_info (__unknown_params);
int mail$message_modify (__unknown_params);
int mail$message_select (__unknown_params);
int mail$mailfile_begin (__unknown_params);
int mail$mailfile_close (__unknown_params);
int mail$mailfile_end (__unknown_params);
int mail$mailfile_info_file (__unknown_params);
int mail$mailfile_open (__unknown_params);
int mail$mailfile_purge_waste (__unknown_params);
int mail$user_begin (__unknown_params);
int mail$user_get_info (__unknown_params);
int mail$user_set_info (__unknown_params);
int mail$user_end (__unknown_params);

int sys$setprv (__unknown_params);

/* some older VAXen do not have these either */
#ifndef MAIL$_USER_SIGFILE
#  define MAIL$_USER_SIGFILE 3141
#endif
#ifndef MAIL$_USER_SET_SIGFILE
#  define MAIL$_USER_SET_SIGFILE 3110
#endif

/*****************************************************************************/
/*
Mail status returns may be found in 'MailVmsStatus'.

A list of 'muptr->ListMessageIdStart' messages from 'muptr->ListMessageIdStart'
is placed into a linked list of VMS_MAIL_MSG structures pointed to by
'muptr->MessageList' beginning with the most recent (larger ID numbers). 

Automatically closes the message context on an error (including
no-more-messages) so an explicit close call is not required.  It could be
closed peremtorially by setting 'MailVmsStatus' to an error value can calling.
*/

int CallMailMessageList (VMS_MAIL_USER *muptr)

{
   int  hit, status;
   long  MessageIdVector;
   unsigned long  MessageCount,
                  MessageId;
   VMS_MAIL_MSG  *vmptr, *vmlptr,
                 *PrevMessagePtr;

   /*********/
   /* begin */
   /*********/

   if (Debug)
      fprintf (stdout, "CallMailMessageList() %d %d %d\n",
               muptr->ListMessageIdStart, muptr->ListMessageIdCount,
               muptr->ListMessageIdVector);

   vmptr = &muptr->VmsMailMsg;

   if (!muptr->VmsMailFullDirectoryLength) ErrorExit (SS$_BUGCHECK, FI_LI);

   muptr->MessageListCount = 0;

   if (!vmptr->MessageContext)
   {
      status = CallMailMessageContext (muptr, vmptr);
      if (VMSnok (status)) return (muptr->MailVmsStatus = status);
   }

   if (muptr->ListMessageIdStart == RANGE_ALL_MSGS)
   {
      /* absolutely all messages from last to first */
      MessageId = MessageCount = vmptr->MessageSelectedCount;
      MessageIdVector = -1;
   }
   else
   if (muptr->ListMessageIdStart == RANGE_MOST_RECENT ||
       muptr->ListMessageIdStart > vmptr->MessageSelectedCount)
   {
      /* from the highest numbered message down for the count */
      MessageId = vmptr->MessageSelectedCount;
      MessageCount = muptr->ListMessageIdCount;
      MessageIdVector = -1;
   }
   else
   if (muptr->ListMessageIdVector < 0)
   {
      /* stepping from higher to lower numbers (more to less recent) */
      MessageId = muptr->ListMessageIdStart;
      MessageCount = muptr->ListMessageIdCount;
      MessageIdVector = -1;
   }
   else
   {
      /* stepping from lower to higher numbers (less to more recent) */
      MessageId = muptr->ListMessageIdStart;
      MessageCount = muptr->ListMessageIdCount;
      MessageIdVector = +1;
   }
   /* when searching this puts a limit on the number of hits before results */
   if (muptr->ListMessageIdMax) MessageCount = muptr->ListMessageIdMax;

   /********************/
   /* process messages */
   /********************/

   vmlptr = NULL;
   while (VMSok (muptr->MailVmsStatus))
   {
      if (!MessageCount || !MessageId) break;

      if (!vmlptr) vmlptr =  CgiLibVeeMemCalloc (sizeof(VMS_MAIL_MSG));
      if (!vmlptr)
      {
         muptr->MailVmsStatus = vaxc$errno;
         break;
      }

      vmlptr->MessageId = MessageId;
      /* either up or down */
      MessageId += MessageIdVector;

      /* borrow the message context from the main structure */
      vmlptr->MessageContext = vmptr->MessageContext;

      CallMailMessageHeader (muptr, vmlptr);

      if (VMSnok (muptr->MailVmsStatus))
      {
         /* and return it! */
         vmptr->MessageContext = vmlptr->MessageContext;
         break;
      }

      if (muptr->SearchFunction)
      {
         CallMailMessageGet (muptr, vmlptr);

         /* and return it! */
         vmptr->MessageContext = vmlptr->MessageContext;

         if (VMSnok (muptr->MailVmsStatus))
         {
            if (muptr->MailVmsStatus == MAIL$_OPENIN)
            {
               /* special-case, propogate it up to where we can report it */
               vmptr->ExtIdLength = vmlptr->ExtIdLength;
               strcpy (vmptr->ExtId, vmlptr->ExtId);
            }
            break;
         }

         hit = muptr->SearchFunction (vmlptr, muptr->SearchDataPtr);
         CallMailMessageFree (vmlptr);
         if (Debug) fprintf (stdout, "HIT: %d\n", hit);

         if (!hit)
         {
            /* save potentially freeing and reallocating the memory */
            memset (vmlptr, 0, sizeof(VMS_MAIL_MSG));
            continue;
         }
      }

      /* ensure the most recent is always at the front of the list */
      if (!muptr->MessageList)
      {
         /* first entry in list */
         muptr->MessageList = vmlptr;
      }
      else
      if (MessageIdVector < 0)
      {
         /* message IDs will be decreasing, append this to the list */
         vmlptr->PrevPtr = PrevMessagePtr;
         PrevMessagePtr->NextPtr = vmlptr;
      }
      else
      {
         /* message IDs will be increasing, prepend this to the list */
         PrevMessagePtr->PrevPtr = vmlptr;
         vmlptr->NextPtr = PrevMessagePtr;
         muptr->MessageList = vmlptr;
      }
      PrevMessagePtr = vmlptr;

      /* make sure the context borrow is not propagated in any way */
      vmlptr->MessageContext = 0;

      /* this memory has been used, more will be allocated */
      vmlptr = NULL;

      /* and on to the next */
      muptr->MessageListCount++;
      MessageCount--;
   }

   /* just to be tidy */
   if (vmlptr) CgiLibVeeMemFree (vmlptr);

   /* if we run out before reaching the end-of-range */
   if (muptr->MailVmsStatus == MAIL$_NOMOREMSG)
      muptr->MailVmsStatus = SS$_NORMAL;

   if (vmptr->MessageContext &&
       VMSnok (muptr->MailVmsStatus))
   {
      /*************************/
      /* close message context */
      /*************************/

      status = mail$message_end (&vmptr->MessageContext,
                                 &CallMailNoSignalItem,
                                 &CallMailNullItem);
      if (Debug) fprintf (stdout, "mail$message_end() %%X%08.08X\n", status);
      vmptr->MessageContext = 0;
   }

   CallMailMessageContextEnd (muptr, vmptr);

   return (muptr->MailVmsStatus);
}

/*****************************************************************************/
/*
Mail status returns may be found in 'MailVmsStatus'.

Read the VMS Mail header fields from the specified message ID.

Generate a 32 bit hash 'MessageHash' from selected fields of the header and if
'ConfirmHash' is non-zero compare the two, returning SS$_BADCHECKSUM if they
are different. 
*/

int CallMailMessageHeader
(
VMS_MAIL_USER *muptr,
VMS_MAIL_MSG *vmptr
)
{
   int  status;
   char  *cptr;
   VMS_ITEM_LIST3  *itptr;
   /* reminder: these require one extra element for the null item */
   VMS_ITEM_LIST3  MessageIdItem [3];
   VMS_ITEM_LIST3  MessageInfoItem [12];

   /*********/
   /* begin */
   /*********/

   if (Debug) fprintf (stdout, "CallMailMessageHeader()\n");

   if (!muptr->VmsMailFullDirectoryLength) ErrorExit (SS$_BUGCHECK, FI_LI);

   /* default to the current message */
   if (!vmptr) vmptr = &muptr->VmsMailMsg;

   itptr = &MessageIdItem[0];
   memset (itptr, 0, sizeof(MessageIdItem));

   itptr->buf_len = sizeof(vmptr->MessageId);
   itptr->item = MAIL$_MESSAGE_ID;
   itptr->buf_addr = &vmptr->MessageId;
   itptr++;

   itptr->item = MAIL$_NOSIGNAL;

   itptr = &MessageInfoItem[0];
   memset (itptr, 0, sizeof(MessageInfoItem));

   itptr->buf_len = sizeof(vmptr->CurrentId);
   itptr->item = MAIL$_MESSAGE_CURRENT_ID;
   itptr->buf_addr = (void*)&vmptr->CurrentId;
   itptr++;

   itptr->buf_len = sizeof(vmptr->ExtId)-1;
   itptr->item = MAIL$_MESSAGE_EXTID;
   itptr->buf_addr = vmptr->ExtId;
   itptr->ret_len = (void*)&vmptr->ExtIdLength;
   itptr++;

   itptr->buf_len = sizeof(vmptr->MessageFlags);
   itptr->item = MAIL$_MESSAGE_RETURN_FLAGS;
   itptr->buf_addr = (void*)&vmptr->MessageFlags;
   itptr++;

   itptr->buf_len = sizeof(vmptr->SizeInRecords);
   itptr->item = MAIL$_MESSAGE_SIZE;
   itptr->buf_addr = (void*)&vmptr->SizeInRecords;
   itptr++;

   itptr->buf_len = sizeof(vmptr->BinDate);
   itptr->item = MAIL$_MESSAGE_BINARY_DATE;
   itptr->buf_addr = &vmptr->BinDate;
   itptr++;

   itptr->buf_len = sizeof(vmptr->Date)-1;
   itptr->item = MAIL$_MESSAGE_DATE;
   itptr->buf_addr = vmptr->Date;
   itptr->ret_len = (void*)&vmptr->DateLength;
   itptr++;

   itptr->buf_len = sizeof(vmptr->From)-1;
   itptr->item = MAIL$_MESSAGE_FROM;
   itptr->buf_addr = vmptr->From;
   itptr->ret_len = (void*)&vmptr->FromLength;
   itptr++;

   itptr->buf_len = sizeof(vmptr->Subject)-1;
   itptr->item = MAIL$_MESSAGE_SUBJECT;
   itptr->buf_addr = vmptr->Subject;
   itptr->ret_len = (void*)&vmptr->SubjectLength;
   itptr++;

   itptr->buf_len = sizeof(vmptr->To)-1;
   itptr->item = MAIL$_MESSAGE_TO;
   itptr->buf_addr = vmptr->To;
   itptr->ret_len = (void*)&vmptr->ToLength;
   itptr++;

   itptr->buf_len = sizeof(vmptr->Cc)-1;
   itptr->item = MAIL$_MESSAGE_CC;
   itptr->buf_addr = vmptr->Cc;
   itptr->ret_len = (void*)&vmptr->CcLength;
   itptr++;

   itptr->item = MAIL$_NOSIGNAL;

   status = mail$message_info (&vmptr->MessageContext,
                               &MessageIdItem,
                               &MessageInfoItem);
   if (Debug) fprintf (stdout, "mail$message_info() %%X%08.08X\n", status);

   if (VMSok (status))
   {
      vmptr->Cc[vmptr->CcLength] = '\0';
      vmptr->Date[vmptr->DateLength] = '\0';

      /* trim trailing spaces (that appear with VMS addresses) */
      cptr = vmptr->From + vmptr->FromLength;
      *cptr = '\0';
      if (cptr > vmptr->From) cptr--; 
      while (cptr > vmptr->From && isspace(*cptr)) cptr--;
      if (!isspace(*cptr)) cptr++;
      vmptr->FromLength = cptr - vmptr->From;
      vmptr->From[vmptr->FromLength] = '\0';

      vmptr->Subject[vmptr->SubjectLength] = '\0';
      vmptr->To[vmptr->ToLength] = '\0';
      vmptr->ExtId[vmptr->ExtIdLength] = '\0';

      vmptr->MessageHash = CallMailHeaderHash (vmptr);

      if (Debug)
         fprintf (stdout,
"id:%d size:%d hash:%d/%d flags:0x%04.04X\n\
|%s|\n|%s|\n|%s|\n|%s|\n|%s|\n|%s|\n",
                  vmptr->CurrentId, vmptr->SizeInRecords,
                  vmptr->MessageHash, vmptr->ConfirmHash,
                  vmptr->MessageFlags,
                  vmptr->Date, vmptr->ExtId, vmptr->From, vmptr->Subject,
                  vmptr->To, vmptr->Cc);

      if (vmptr->ConfirmHash &&
          vmptr->ConfirmHash != vmptr->MessageHash)
         status = SS$_BADCHECKSUM;
   }

   return (muptr->MailVmsStatus = status);
}

/*****************************************************************************/
/*
Read the message body into a dynamically allocated buffer pointed at by
'vmptr->BodyPtr' and 'vmptr->BodyLength' in length.

Mail status returns may be found in 'MailVmsStatus'.

Automatically closes the message context on an error (including NOMOREREC) so
an explicit close call is not required.  It could be closed peremtorially by
setting 'MailVmsStatus' to an error value can calling.
*/

int CallMailMessageGet
(
VMS_MAIL_USER *muptr,
VMS_MAIL_MSG *vmptr
)
{
   /* at an unknown VMS version max record size went from 255 to 2048 */
   static unsigned long  MaxRecordSize = 2048;

   static VMS_ITEM_LIST3  MessageContinueItem [] = {
      { 0, MAIL$_MESSAGE_CONTINUE, 0, 0 },
      { 0, MAIL$_NOSIGNAL, 0, 0 },
      { 0,0,0,0 }
   };

   int  efd, retval, status,
        BytesAllocated;
   unsigned short  MessageFlags;
   unsigned long  rlen;
   char  *cptr, *rptr, *sptr, *zptr;
   char  ExtFileName [256];
   stat_t  StatBuffer;
   VMS_ITEM_LIST3  *itptr;
   /* reminder: these require one extra element for the null item */
   VMS_ITEM_LIST3  MessageIdItem [3];
   VMS_ITEM_LIST3  MessageModifyItem [4];
   VMS_ITEM_LIST3  MessageRecordItem [3];

   /*********/
   /* begin */
   /*********/

   if (Debug) fprintf (stdout, "CallMailMessageGet()\n");

   if (!muptr->VmsMailFullDirectoryLength) ErrorExit (SS$_BUGCHECK, FI_LI);

   /* default to the current message */
   if (!vmptr) vmptr = &muptr->VmsMailMsg;

   if (!vmptr->MessageContext)
   {
      /****************/
      /* open message */
      /****************/

      status = CallMailMessageContext (muptr, vmptr);
      if (VMSnok (status)) return (muptr->MailVmsStatus = status);

      CallMailMessageHeader (muptr, vmptr);

      /* if it wasn't the message we were seeking */
      if (muptr->MailVmsStatus == SS$_BADCHECKSUM)
      {
         CallMailMessageContextEnd (muptr, vmptr);
         return (muptr->MailVmsStatus);
      }
   }
   else
      muptr->MailVmsStatus = SS$_NORMAL;

   /*******************/
   /* process message */
   /*******************/

   if (VMSok (muptr->MailVmsStatus))
   {
      /********************/
      /* buffer record(s) */
      /********************/

      if (vmptr->ExtIdLength)
      {
         zptr = (sptr = ExtFileName) + sizeof(ExtFileName)-1;
         for (cptr = muptr->VmsMailFullDirectory;
              *cptr && sptr < zptr;
              *sptr++ = *cptr++);
         for (cptr = vmptr->ExtId; *cptr && sptr < zptr; *sptr++ = *cptr++);
         *sptr = '\0';
         if (Debug) fprintf (stdout, "stat() |%s|\n", ExtFileName);
         if (stat (ExtFileName, &StatBuffer) < 0)
         {
            status = vaxc$errno;
            if (Debug) fprintf (stdout, "stat() %%X%08.08X\n", status);
            if (status == RMS$_FNF)
               muptr->MailVmsStatus = MAIL$_OPENIN;
            else
               muptr->MailVmsStatus = status;
            BytesAllocated = 0;
         }
         else
            BytesAllocated = StatBuffer.st_size;
      }
      else
         BytesAllocated = CALLMAIL_MSG_SIZE;

      if (Debug) fprintf (stdout, "%d bytes\n", BytesAllocated);
      vmptr->BodyPtr = rptr =  CgiLibVeeMemCalloc (BytesAllocated+32);
      if (!vmptr->BodyPtr) muptr->MailVmsStatus = vaxc$errno;
      vmptr->BodyLength = 0;
   }

   if (VMSok (muptr->MailVmsStatus))
   {
      if (vmptr->ExtIdLength)
      {
         /**********************/
         /* external mail file */
         /**********************/

         /* avoid the overhead of mail$message_get */
         efd = open (ExtFileName, O_RDONLY, 0, "ctx=stm", "shr=get");
         if (!efd)
         {
            if (Debug) fprintf (stdout, "open() %%X%08.08X\n", vaxc$errno);
            if (vaxc$errno == RMS$_FNF)
               muptr->MailVmsStatus = MAIL$_OPENIN;
            else
               muptr->MailVmsStatus = vaxc$errno;
         }
         else
         {
            retval = read (efd, vmptr->BodyPtr, BytesAllocated);
            if (Debug) fprintf (stdout, "retval: %d\n", retval);
            if (retval == -1)
            {
               muptr->MailVmsStatus = vaxc$errno;
               if (Debug) fprintf (stdout, "read() %%X%08.08X\n", vaxc$errno);
               close (efd);
            }
            else
            if (vmptr->MessageFlags & MAIL$M_EXTNSTD)
            {
               /* must be a fixed 512 byte foreign message */
               close (efd);
               vmptr->BodyLength = retval;
            }
            else
            {
               /* just assume it's variable-length record mode */
               close (efd);
               zptr = (sptr = rptr = vmptr->BodyPtr) + BytesAllocated;
               for (;;)
               {
                  rlen = (unsigned int)*(USHORTPTR)rptr;
                  if (rlen == 0xffff) break;
                  rptr += sizeof(unsigned short);
                  /* as it's shuffling inside the one buffer use memove() */
                  memmove (sptr, rptr, rlen);
                  sptr += rlen;
                  *sptr++ = '\n';
                  rptr += rlen;
                  /* records are always on even byte (word) boundaries */
                  if ((int)rptr & 1) rptr++;
                  /* sanity check */
                  if (rptr >= zptr) break;
               }
               *sptr = '\0';
               vmptr->BodyLength = sptr - vmptr->BodyPtr;
            }
         }
      }
      else
      {
         /************************/
         /* use MAIL$MESSAGE_GET */
         /************************/

         itptr = &MessageIdItem[0];
         memset (itptr, 0, sizeof(MessageIdItem));

         itptr->buf_len = sizeof(vmptr->MessageId);
         itptr->item = MAIL$_MESSAGE_ID;
         itptr->buf_addr = &vmptr->MessageId;
         itptr++;

         itptr->item = MAIL$_NOSIGNAL;

         status = mail$message_get (&vmptr->MessageContext,
                                    &MessageIdItem,
                                    &CallMailNullItem);
         if (Debug) fprintf (stdout, "mail$message_get() %%X%08.08X\n", status);

         itptr = &MessageRecordItem[0];
         memset (itptr, 0, sizeof(MessageRecordItem));
         /* might be a longword specification but only a short is written! */
         rlen = 0;

         while (VMSok (muptr->MailVmsStatus))
         {
            itptr = &MessageRecordItem[0];

            if (BytesAllocated >= MaxRecordSize)
            itptr->buf_len = MaxRecordSize;
            else
               itptr->buf_len = BytesAllocated;
            itptr->item = MAIL$_MESSAGE_RECORD;
            itptr->buf_addr = rptr;
            itptr->ret_len = &rlen;
            itptr++;

            itptr->item = MAIL$_NOSIGNAL;

            status = mail$message_get (&vmptr->MessageContext,
                                       &MessageContinueItem,
                                       &MessageRecordItem);
            if (Debug)
               fprintf (stdout, "mail$message_get() %%X%08.08X %d/%d bytes\n",
                        status, rlen, MaxRecordSize);

            if (VMSok (status))
            {
               /* not interested in the VMS Mail header */
               if (vmptr->RecordCount++ < 5)
               {
                  rptr[0] = '\0';
                  continue;
               }
               rptr[rlen++] = '\n';
               rptr += rlen;
               rptr[0] = '\0';
               vmptr->BodyLength += rlen;
               BytesAllocated -= rlen;
               continue;
            }
            if (status == MAIL$_INVITMLEN && MaxRecordSize > 255)
            {
               /* drop back from 2048 to 255 and try again */
               MaxRecordSize = 255;
               continue;
            }
            muptr->MailVmsStatus = status;
            break;
         }
      }

      if (muptr->MailVmsStatus == MAIL$_NOMOREREC)
         muptr->MailVmsStatus = SS$_NORMAL;
   }

   if (Debug)
      fprintf (stdout, "%d |%s|\n", vmptr->BodyLength, vmptr->BodyPtr);

   if (vmptr->RecordCount >= 5)
      vmptr->RecordCount -= 5;
   else
      vmptr->RecordCount = 0;

   if (VMSok (muptr->MailVmsStatus) &&
       vmptr->MessageFlags & MAIL$M_NEWMSG &&
       vmptr->ResetNewMsgFlag &&
       !muptr->VmsNewMsg)
   {
      /**********************/
      /* not unread anymore */
      /**********************/

      /* leave the bit set in the in-memory flags (for later detection) */
      MessageFlags = vmptr->MessageFlags & ~MAIL$M_NEWMSG;

      itptr = &MessageModifyItem[0];
      memset (itptr, 0, sizeof(MessageModifyItem));

      itptr->buf_len = sizeof(vmptr->MessageId);
      itptr->item = MAIL$_MESSAGE_ID;
      itptr->buf_addr = &vmptr->MessageId;
      itptr++;

      itptr->buf_len = sizeof(MessageFlags);
      itptr->item = MAIL$_MESSAGE_FLAGS;
      itptr->buf_addr = &MessageFlags;
      itptr++;

      itptr->item = MAIL$_NOSIGNAL;

      status = mail$message_modify (&vmptr->MessageContext,
                                    &MessageModifyItem,
                                    &CallMailNullItem);
      if (Debug) fprintf (stdout, "mail$message_modify() %%X%08.08X\n", status);

      if (VMSok(status)) vmptr->MessageFlags &= ~MAIL$M_NEWMSG;
   }

   if (vmptr->MessageContext &&
       VMSnok (muptr->MailVmsStatus))
   {
      /*****************/
      /* close message */
      /*****************/

      status = mail$message_end (&vmptr->MessageContext,
                                 &CallMailNoSignalItem,
                                 &CallMailNullItem);
      if (Debug) fprintf (stdout, "mail$message_end() %%X%08.08X\n", status);

      CallMailMessageContextEnd (muptr, vmptr);
   }

   return (muptr->MailVmsStatus);
}

/*****************************************************************************/
/*
Modify the specified message flags to the bit-vector supplied.
To reset one or more bits complement the required pattern with the most
significant bit set.  To set one or more bits supply the bit pattern with the
most significant bit reset.
*/

int CallMailMessageFlags
(
VMS_MAIL_USER *muptr,
VMS_MAIL_MSG *vmptr,
unsigned short MessageFlags
)
{
   int  status;
   VMS_ITEM_LIST3  *itptr;
   /* reminder: these require one extra element for the null item */
   VMS_ITEM_LIST3  MessageModifyItem [4];

   /*********/
   /* begin */
   /*********/

   if (Debug)
      fprintf (stdout, "CallMailMessageFlags() %%x%04.04X\n", MessageFlags);

   if (!muptr->VmsMailFullDirectoryLength) ErrorExit (SS$_BUGCHECK, FI_LI);

   /* default to the current message */
   if (!vmptr) vmptr = &muptr->VmsMailMsg;

   if (!vmptr->MessageContext)
   {
      /****************/
      /* open message */
      /****************/

      status = CallMailMessageContext (muptr, vmptr);
      if (VMSnok (status)) return (muptr->MailVmsStatus = status);

      CallMailMessageHeader (muptr, vmptr);

      /* if it wasn't the message we were seeking */
      if (muptr->MailVmsStatus == SS$_BADCHECKSUM)
      {
         CallMailMessageContextEnd (muptr, vmptr);
         return (muptr->MailVmsStatus);
      }
   }
   else
      muptr->MailVmsStatus = SS$_NORMAL;

   /*************/
   /* set flags */
   /*************/

   if (MessageFlags & 0x8000)
      vmptr->MessageFlags &= MessageFlags;
   else
      vmptr->MessageFlags |= MessageFlags;

   itptr = &MessageModifyItem[0];
   memset (itptr, 0, sizeof(MessageModifyItem));

   itptr->buf_len = sizeof(vmptr->MessageId);
   itptr->item = MAIL$_MESSAGE_ID;
   itptr->buf_addr = &vmptr->MessageId;
   itptr++;

   itptr->buf_len = sizeof(vmptr->MessageFlags);
   itptr->item = MAIL$_MESSAGE_FLAGS;
   itptr->buf_addr = &vmptr->MessageFlags;
   itptr++;

   itptr->item = MAIL$_NOSIGNAL;

   status = mail$message_modify (&vmptr->MessageContext,
                                 &MessageModifyItem,
                                 &CallMailNullItem);
   if (Debug) fprintf (stdout, "mail$message_modify() %%X%08.08X\n", status);

   muptr->MailVmsStatus = status;

   if (VMSnok (muptr->MailVmsStatus))
   {
      /*****************/
      /* close message */
      /*****************/

      status = mail$message_end (&vmptr->MessageContext,
                                 &CallMailNoSignalItem,
                                 &CallMailNullItem);
      if (Debug) fprintf (stdout, "mail$message_end() %%X%08.08X\n", status);
      CallMailMessageContextEnd (muptr, vmptr);
   }

   return (muptr->MailVmsStatus);
}

/*****************************************************************************/
/*
Release memory allocated by CallMailMessageGet().
*/

void CallMailMessageFree (VMS_MAIL_MSG *vmptr)

{
   /*********/
   /* begin */
   /*********/

   if (Debug) fprintf (stdout, "CallMailMessageFree()\n");

   if (!vmptr) ErrorExit (SS$_BUGCHECK, FI_LI);
   CgiLibVeeMemFree (vmptr->BodyPtr);
   vmptr->BodyPtr = NULL;
   vmptr->BodyLength = 0;
}

/*****************************************************************************/
/*
Establish a message context.
Then select message(s) based on item number, subject contents, etc.
This context can then be used to list messages, read a message, etc.
*/

int CallMailMessageContext
(
VMS_MAIL_USER *muptr,
VMS_MAIL_MSG *vmptr
)
{
   int  status;
   VMS_ITEM_LIST3  *itptr;
   /* reminder: these require one extra element for the null item */
   VMS_ITEM_LIST3  MessageBeginItem [3];

   /*********/
   /* begin */
   /*********/

   if (Debug) fprintf (stdout, "CallMailMessageContext()\n");

   status = CallMailFileOpen (muptr, muptr->VmsMailFileName);
   if (VMSnok (status)) return (muptr->MailVmsStatus = status);

   itptr = &MessageBeginItem[0];
   memset (itptr, 0, sizeof(MessageBeginItem));

   itptr->buf_len = sizeof(muptr->VmsMailFileContext);
   itptr->item = MAIL$_MESSAGE_FILE_CTX;
   itptr->buf_addr = (void*)&muptr->VmsMailFileContext;
   itptr++;

   itptr->item = MAIL$_NOSIGNAL;

   status = mail$message_begin (&vmptr->MessageContext,
                                &MessageBeginItem,
                                &CallMailNullItem);
   if (Debug) fprintf (stdout, "mail$message_begin() %%X%08.08X\n", status);

   if (VMSok (status)) status = CallMailMessageSelect (muptr);

   return (muptr->MailVmsStatus = status);
}

/*****************************************************************************/
/*
Close a message context.
*/

int CallMailMessageContextEnd
(
VMS_MAIL_USER *muptr,
VMS_MAIL_MSG *vmptr
)
{
   int  status;

   /*********/
   /* begin */
   /*********/

   if (Debug) fprintf (stdout, "CallMailMessageContextEnd()\n");

   if (!vmptr->MessageContext) return (SS$_NORMAL);;

   status = mail$message_end (&vmptr->MessageContext,
                              &CallMailNoSignalItem,
                              &CallMailNullItem);
   if (Debug) fprintf (stdout, "mail$message_end() %%X%08.08X\n", status);
   vmptr->MessageContext = 0;

   return (muptr->MailVmsStatus = status);
}

/*****************************************************************************/
/*
Reset all of the input items that control message selection.
*/

void CallMailMessageSelectReset (VMS_MAIL_USER *muptr)

{
   VMS_MAIL_MSG  *vmptr;

   /*********/
   /* begin */
   /*********/

   if (Debug) fprintf (stdout, "CallMailMessageSelectReset()\n");

   vmptr = &muptr->VmsMailMsg;

   vmptr->CcLength = vmptr->FromLength = vmptr->SubjectLength =
      vmptr->ToLength = vmptr->TimeBeforeLength = vmptr->TimeSinceLength = 0;

   vmptr->Cc[0] = vmptr->From[0] = vmptr->Subject[0] = 
      vmptr->To[0] = vmptr->TimeBefore[0] = vmptr->TimeSince[0] = '\0';
}

/*****************************************************************************/
/*
Select message(s) based on item number, subject contents, etc.
*/

int CallMailMessageSelect (VMS_MAIL_USER *muptr)

{
   int  status;
   VMS_MAIL_MSG  *vmptr;
   VMS_ITEM_LIST3  *itptr;
   /* reminder: these require one extra element for the null item */
   VMS_ITEM_LIST3  MessageSelectItem [7];
   VMS_ITEM_LIST3  MessageSelectedItem [8];

   /*********/
   /* begin */
   /*********/

   if (Debug)
      fprintf (stdout, "CallMailMessageSelect() %d |%s|\n",
               muptr->VmsMailFolderNameLength, muptr->VmsMailFolderName);

   /* the current msg structure is being kept in the user data area */
   vmptr = &muptr->VmsMailMsg;

   if (!vmptr->MessageContext)
   {
      status = CallMailMessageContext (muptr, vmptr);
      if (VMSnok (status)) return (muptr->MailVmsStatus = status);
   }

   /**********/
   /* select */
   /**********/

   if (!vmptr->MessageContext) ErrorExit (SS$_BUGCHECK, FI_LI);

   itptr = &MessageSelectItem[0];
   memset (itptr, 0, sizeof(MessageSelectItem));

   itptr->buf_len = muptr->VmsMailFolderNameLength;
   itptr->item = MAIL$_MESSAGE_FOLDER;
   itptr->buf_addr = muptr->VmsMailFolderName;
   itptr++;

   if (muptr->SelectNotSearch)
   {
      if (vmptr->CcLength)
      {
         itptr->buf_len = vmptr->CcLength;
         itptr->item = MAIL$_MESSAGE_CC_SUBSTRING;
         itptr->buf_addr = vmptr->Cc;
         itptr++;
      }

      if (vmptr->FromLength)
      {
         itptr->buf_len = vmptr->FromLength;
         itptr->item = MAIL$_MESSAGE_FROM_SUBSTRING;
         itptr->buf_addr = vmptr->From;
         itptr++;
      }

      if (vmptr->SubjectLength)
      {
         itptr->buf_len = vmptr->SubjectLength;
         itptr->item = MAIL$_MESSAGE_SUBJ_SUBSTRING;
         itptr->buf_addr = vmptr->Subject;
         itptr++;
      }

      if (vmptr->ToLength)
      {
         itptr->buf_len = vmptr->ToLength;
         itptr->item = MAIL$_MESSAGE_TO_SUBSTRING;
         itptr->buf_addr = vmptr->To;
         itptr++;
      }

      if (vmptr->TimeBeforeLength)
      {
         itptr->buf_len = vmptr->TimeBeforeLength;
         itptr->item = MAIL$_MESSAGE_BEFORE;
         itptr->buf_addr = vmptr->TimeBefore;
         itptr++;
      }

      if (vmptr->TimeSinceLength)
      {
         itptr->buf_len = vmptr->TimeSinceLength;
         itptr->item = MAIL$_MESSAGE_SINCE;
         itptr->buf_addr = vmptr->TimeSince;
         itptr++;
      }

      if (vmptr->SelectMessageFlags)
      {
         itptr->buf_len = sizeof(vmptr->SelectMessageFlags);
         itptr->item = MAIL$_MESSAGE_FLAGS;
         itptr->buf_addr = &vmptr->SelectMessageFlags;
         itptr++;
      }
   }

   itptr->item = MAIL$_NOSIGNAL;

   itptr = &MessageSelectedItem[0];
   memset (itptr, 0, sizeof(MessageSelectedItem));

   itptr->buf_len = sizeof(vmptr->MessageSelectedCount);
   itptr->item = MAIL$_MESSAGE_SELECTED;
   itptr->buf_addr = &vmptr->MessageSelectedCount;
   itptr++;

   itptr->item = MAIL$_NOSIGNAL;

   status = mail$message_select (&vmptr->MessageContext,
                                 &MessageSelectItem,
                                 &MessageSelectedItem);
   if (Debug)
      fprintf (stdout, "mail$message_select() %%X%08.08X selected: %d\n",
               status, vmptr->MessageSelectedCount);

   return (muptr->MailVmsStatus = status);
}

/*****************************************************************************/
/*
Open the VMS Mail file specified by argument MailFileName.  If a mail file is
currently open then close it first. 
*/

int CallMailFileOpen
(
VMS_MAIL_USER *muptr,
char *MailFileName
)
{
   BOOL  PmdfImap;
   int  status,
        OpenFileNameLength;
   unsigned long  MailFileNameLength;
   char  *cptr, *sptr, *zptr;
   char  OpenMailFileName [256];
   VMS_ITEM_LIST3  *itptr;
   /* reminder: these require one extra element for the null item */
   VMS_ITEM_LIST3  MailFileItem [3];
   VMS_ITEM_LIST3  WasteBasketItem [3];

   /*********/
   /* begin */
   /*********/

   if (Debug) fprintf (stdout, "CallMailFileOpen() |%s|\n", MailFileName);

   if (!muptr->VmsMailFullDirectoryLength) ErrorExit (SS$_BUGCHECK, FI_LI);

   /* look for the indicative trailing slash */
   for (cptr = MailFileName; *cptr && *cptr != '/'; cptr++);
   if (*cptr)
      PmdfImap = TRUE;
   else
      PmdfImap = FALSE;

   if (PmdfImap)
      OpenFileNameLength =
         CallMailPmdfImapMbxDir (muptr, MailFileName,
                                 OpenMailFileName, sizeof(OpenMailFileName));
   else
      OpenFileNameLength =
         CallMailBuildMailFileName (muptr, MailFileName,
                                    OpenMailFileName, sizeof(OpenMailFileName));
   if (Debug)
      fprintf (stdout, "|%s|%s|\n",
               OpenMailFileName, muptr->OpenMailFileName);

   if (muptr->VmsMailFileContext)
   {
      if (OpenFileNameLength == muptr->OpenMailFileNameLength && 
          !strcmp (OpenMailFileName, muptr->OpenMailFileName))
         return (muptr->MailVmsStatus = SS$_NORMAL);
      CallMailFileClose (muptr);
   }

   zptr = (sptr = muptr->OpenMailFileName) + sizeof(muptr->OpenMailFileName)-1;
   for (cptr = OpenMailFileName; *cptr && sptr < zptr; *sptr++ = *cptr++);
   *sptr = '\0';
   muptr->OpenMailFileNameLength = sptr - muptr->OpenMailFileName;

   muptr->MailDotMai = FALSE;

   status = mail$mailfile_begin (&muptr->VmsMailFileContext,
                                 &CallMailNullItem, &CallMailNullItem);
   if (Debug) fprintf (stdout, "mail$mailfile_begin() %%X%08.08X\n", status);
   if (VMSnok (status)) return (muptr->MailVmsStatus = status);

   itptr = &MailFileItem[0];
   memset (itptr, 0, sizeof(MailFileItem));

   itptr->buf_len = muptr->OpenMailFileNameLength;
   itptr->item = MAIL$_MAILFILE_NAME;
   itptr->buf_addr = muptr->OpenMailFileName;
   itptr->ret_len = &MailFileNameLength;
   itptr++;

   itptr->item = MAIL$_NOSIGNAL;

   itptr = &WasteBasketItem[0];
   memset (itptr, 0, sizeof(WasteBasketItem));

   itptr->buf_len = sizeof(muptr->VmsMailWasteBasketName)-1;
   itptr->item = MAIL$_MAILFILE_WASTEBASKET;
   itptr->buf_addr = muptr->VmsMailWasteBasketName;
   itptr->ret_len = &muptr->VmsMailWasteBasketNameLength;

   itptr->item = MAIL$_NOSIGNAL;

   status = mail$mailfile_open (&muptr->VmsMailFileContext,
                                &MailFileItem, &WasteBasketItem);
   if (Debug) fprintf (stdout, "mail$mailfile_open() %%X%08.08X\n", status);
   if (VMSok (status))
   {
      /* set a boolean if it's the primary "MAIL.MAI" */
      for (cptr = muptr->OpenMailFileName + muptr->OpenMailFileNameLength;
           cptr > muptr->OpenMailFileName && *cptr != ']';
           cptr--);
      if (*cptr == ']') cptr++;
      if (!memcmp (cptr, "MAIL\0", 5) || !memcmp (cptr, "MAIL.", 5))
         muptr->MailDotMai = TRUE;

      muptr->VmsMailWasteBasketName[muptr->VmsMailWasteBasketNameLength] = '\0';
      if (Debug)
         fprintf (stdout, "muptr->VmsMailWasteBasketName |%s|\n",
                  muptr->VmsMailWasteBasketName);
   }
   else
   {
      /* couldn't open the mail file, end the context */
      mail$mailfile_end (&muptr->VmsMailFileContext,
                         &CallMailNullItem, &CallMailNullItem);
      if (Debug) fprintf (stdout, "mail$mailfile_end() %%X%08.08X\n", status);
      muptr->OpenMailFileNameLength = muptr->VmsMailFileContext = 0;
      muptr->OpenMailFileName[0] = '\0';

      /* non-existant PMDF IMAP MAIL.MAI is non-fatal, indicate using this */
      if (PmdfImap && status == MAIL$_OPENIN) status = SS$_FISH;
   }

   return (muptr->MailVmsStatus = status);
}

/*****************************************************************************/
/*
Close the currently open mail file.
If the request involved a message delete the also purge the WASTEBASKET.

BOGUS_OPENIN: mail$mailfile_close() (under at least V7.3 and V8.3) can report
%X007E109A (8261786) which

  KLAATU$ exit %X007E109A
  %MAIL-E-OPENIN, error opening !AS as input

but is not the value defined in the header file

  #define MAIL$_OPENIN 8290802

Obviously a disconnect between callable mail and the header (the value 8261786
does not appear to represent anything anywhere in SYS$STARLET_C.TLB)!  Sooo ...
define our own, bogus OPENIN status to correspond to this real-world return. 
*/ 

CallMailFileClose (VMS_MAIL_USER *muptr)

{
#define BOGUS_OPENIN 0x007E109A

   static unsigned long  TotalReclaim;

   static VMS_ITEM_LIST3  FullCloseItem [] = {
      { 0, MAIL$_MAILFILE_FULL_CLOSE, 0, 0 },
      { 0, MAIL$_NOSIGNAL, 0, 0 },
      { 0,0,0,0 }
   };

   static VMS_ITEM_LIST3  TotalReclaimItem [] = {
      { sizeof(TotalReclaim), MAIL$_MAILFILE_TOTAL_RECLAIM, &TotalReclaim, 0 },
      { 0, MAIL$_NOSIGNAL, 0, 0 },
      { 0,0,0,0 }
   };

   int  status;

   /*********/
   /* begin */
   /*********/

   if (Debug)
      fprintf (stdout, "CallMailFileClose() %d |%s|\n",
               muptr->VmsMailFileContext, muptr->VmsMailFileName);

   if (!muptr->VmsMailFileContext) return (SS$_NORMAL);

   muptr->TotalReclaimBytes = TotalReclaim = 0;

   if (muptr->VmsMailAutoPurge)
      status = mail$mailfile_close (&muptr->VmsMailFileContext,
                                    &FullCloseItem, &TotalReclaimItem);
   else
      status = mail$mailfile_close (&muptr->VmsMailFileContext,
                                    &CallMailNoSignalItem, &CallMailNullItem);

   if (Debug) fprintf (stdout, "mail$mailfile_close() %%X%08.08X\n", status);
   if (VMSnok (status))
   {
      /*
         Callable Mail reports this if the Mail file is open (presumably
         for read) by some other application (apparently PMDF IMAP is a
         common culprit) when the close attempts to reclaim delete space.
      */
      if (status != MAIL$_OPENIN &&
          status != BOGUS_OPENIN)
      {
         muptr->MailVmsStatus = status;
         return (muptr->MailVmsStatus = status);
      }
   }

   muptr->TotalReclaimBytes = TotalReclaim;

   status = mail$mailfile_end (&muptr->VmsMailFileContext,
                               &CallMailNoSignalItem, &CallMailNullItem);
   if (Debug) fprintf (stdout, "mail$mailfile_end() %%X%08.08X\n", status);

   muptr->OpenMailFileNameLength = muptr->VmsMailFileContext = 0;
   muptr->OpenMailFileName[0] = '\0';

   return (muptr->MailVmsStatus = status);
}

/*****************************************************************************/
/*
This function retrieves the folder names in the current mail file context.
It places into the data area each null-terminated folder name, with the list
itself terminated by an empty name (i.e. "FOLDER1\0FOLDER2\0\0").
*/

int CallMailFolderList
(
VMS_MAIL_USER *muptr,
BOOL DestFolder,
BOOL PmdfImap
)
{
   int  status,
        ListCount,
        ListLength,
        ListSize;
   char  *cptr,
         *NamePtr;
   VMS_ITEM_LIST3  *itptr;
   /* reminder: these require one extra element for the null item */
   VMS_ITEM_LIST3  CallbackItem [4];

   /*********/
   /* begin */
   /*********/

   if (Debug) fprintf (stdout, "CallMailFolderList() %d\n", DestFolder);

   if (DestFolder)
   {
      /* destination mail file */
      if (muptr->DestFolderList.ListPtr)
      {
         CgiLibVeeMemFree (muptr->DestFolderList.ListPtr);
         muptr->DestFolderList.ListPtr = NULL;
         muptr->DestFolderList.ListCount = muptr->DestFolderList.ListLength =
            muptr->DestFolderList.ListSize = 0;
      }
      /* folders are always generated from "MAIL" with PMDF IMAP enabled */
      if (PmdfImap)
         NamePtr = "MAIL";
      else
         NamePtr = muptr->VmsMailDestFileName;
   }
   else
   {
      /* primary mail file */
      if (muptr->MailFolderList.ListPtr)
      {
         CgiLibVeeMemFree (muptr->MailFolderList.ListPtr);
         muptr->MailFolderList.ListPtr = NULL;
         muptr->MailFolderList.ListCount = muptr->MailFolderList.ListLength =
            muptr->MailFolderList.ListSize = 0;
      }
      /* folders are always generated from "MAIL" with PMDF IMAP enabled */
      if (PmdfImap)
         NamePtr = "MAIL";
      else
         NamePtr = muptr->VmsMailFileName;
   }

   status = CallMailFileOpen (muptr, NamePtr);

   muptr->TempFolderList.ListPtr = NULL;
   muptr->TempFolderList.ListCount = muptr->TempFolderList.ListLength =
      muptr->TempFolderList.ListSize = 0;

   if (VMSok (status))
   {
      muptr->DataBufferLength = 0;
      muptr->DataBufferPtr = muptr->DataBuffer;

      itptr = &CallbackItem[0];
      memset (itptr, 0, sizeof(CallbackItem));

      itptr->buf_len = sizeof(void*);
      itptr->item = MAIL$_MAILFILE_FOLDER_ROUTINE;
      itptr->buf_addr = (void*)CallMailFolderListCallback;
      itptr++;

      itptr->buf_len = sizeof(VMS_MAIL_USER*);
      itptr->item = MAIL$_MAILFILE_USER_DATA;
      itptr->buf_addr = muptr;
      itptr++;

      itptr->item = MAIL$_NOSIGNAL;

      status = mail$mailfile_info_file (&muptr->VmsMailFileContext,
                                        &CallbackItem,
                                        &CallMailNullItem);
      if (Debug)
         fprintf (stdout, "mail$mailfile_info_file() %%X%08.08X\n", status);

      if (!muptr->TempFolderList.ListCount)
      {
         /* ensure an non-existant/empty mail file results in an empty list */
         muptr->TempFolderList.ListPtr =  CgiLibVeeMemCalloc (muptr->TempFolderList.ListSize = 64);
         muptr->TempFolderList.ListLength = muptr->TempFolderList.ListCount = 0;
         if (!muptr->TempFolderList.ListPtr) ErrorExit (vaxc$errno, FI_LI);
      }
      else
      if (VMSnok (status))
      {
         /* ensure the list is empty in the case of an error */
         if (muptr->TempFolderList.ListPtr) muptr->TempFolderList.ListPtr[0] = '\0';
         muptr->TempFolderList.ListLength = muptr->TempFolderList.ListCount = 0;
      }
   }

   if (PmdfImap)
   {
      if (VMSok (status))
      {
         /* append the PMDF_IMAP.MAILBOX listed folders */
         status = CallMailPmdfImapMailBox (muptr);
         if (Debug) fprintf (stdout, "%%X%08.08X\n", status);

         /* sort eliminating duplicates in callable Mail and PMDF IMAP */
         if (VMSok (status)) CallMailPmdfImapSort (muptr);
      }
   }

   if (DestFolder)
   {
      muptr->DestFolderList.ListPtr = muptr->TempFolderList.ListPtr;
      muptr->DestFolderList.ListCount = muptr->TempFolderList.ListCount;
      muptr->DestFolderList.ListLength = muptr->TempFolderList.ListLength;
      muptr->DestFolderList.ListSize = muptr->TempFolderList.ListSize;
   }
   else
   {
      muptr->MailFolderList.ListPtr = muptr->TempFolderList.ListPtr;
      muptr->MailFolderList.ListCount = muptr->TempFolderList.ListCount;
      muptr->MailFolderList.ListLength = muptr->TempFolderList.ListLength;
      muptr->MailFolderList.ListSize = muptr->TempFolderList.ListSize;
   }

   muptr->TempFolderList.ListPtr = NULL;
   muptr->TempFolderList.ListCount = muptr->TempFolderList.ListLength =
      muptr->TempFolderList.ListSize = 0;

   return (muptr->MailVmsStatus = status);
}

/*****************************************************************************/
/*
Called once from CallMailFolderList() for each folder name in the current mail
file.
*/

int CallMailFolderListCallback
(
VMS_MAIL_USER *muptr,
struct dsc$descriptor *FolderDscPtr
)
{
   int  status,
        NewListSize;
   char  *cptr, *dzptr, *sptr;

   /*********/
   /* begin */
   /*********/

   if (Debug)
      fprintf (stdout, "CallMailFolderListCallback() %d %*.*s\n",
               FolderDscPtr->dsc$w_length, FolderDscPtr->dsc$w_length,
               FolderDscPtr->dsc$w_length, FolderDscPtr->dsc$a_pointer);

   if (!FolderDscPtr->dsc$w_length) return (SS$_NORMAL);

   NewListSize = muptr->TempFolderList.ListLength + FolderDscPtr->dsc$w_length + 1;
   while (NewListSize > muptr->TempFolderList.ListSize)
   {
      muptr->TempFolderList.ListSize = NewListSize + 256;
      muptr->TempFolderList.ListPtr = CgiLibVeeMemRealloc (muptr->TempFolderList.ListPtr,
                                          muptr->TempFolderList.ListSize);
      if (!muptr->TempFolderList.ListPtr) ErrorExit (vaxc$errno, FI_LI);
   }

   sptr = muptr->TempFolderList.ListPtr + muptr->TempFolderList.ListLength;
   dzptr = FolderDscPtr->dsc$a_pointer + FolderDscPtr->dsc$w_length;
   for (cptr = FolderDscPtr->dsc$a_pointer; cptr < dzptr; *sptr++ = *cptr++);
   *sptr = '\0';
   muptr->TempFolderList.ListLength += FolderDscPtr->dsc$w_length + 1;
   muptr->TempFolderList.ListCount++;

   return (SS$_NORMAL);
}

/*****************************************************************************/
/*
Copy or copy-then-delete (move) messages between folders.
*/

int CallMailMessageCopy (VMS_MAIL_USER *muptr) {
   return (CallMailMessageCopyMove (muptr, 0));
}

int CallMailMessageMove (VMS_MAIL_USER *muptr) {
   return (CallMailMessageCopyMove (muptr, 1));
}

int CallMailMessageCopyMove
(
VMS_MAIL_USER *muptr,
BOOL DeleteAfterCopy
)
{
   int  status,
        DestFileNameLength,
        VolumeStruct;
   unsigned long  MsgCnt;
   char  *cptr;
   char  DestFileName [256];
   VMS_ITEM_LIST3  *itptr;
   VMS_MAIL_MSG  *vmptr;
   VMS_MAIL_MSG  VmsMailMsg;
   /* reminder: these require one extra element for the null item */
   VMS_ITEM_LIST3  MessageIdItem [6];

   /*********/
   /* begin */
   /*********/

   if (Debug)
      fprintf (stdout, "CallMailMessageCopyMove() |%s|\n",
               muptr->MsgIdListPtr);

   muptr->MessageCopiedCount = muptr->NewMsgFlagCopiedCount = 0;

   vmptr = &muptr->VmsMailMsg;

   if (!vmptr->MessageContext)
   {
      /************************/
      /* open message context */
      /************************/

      status = CallMailMessageContext (muptr, vmptr);
      if (VMSnok (status)) return (muptr->MailVmsStatus = status);
   }

   if (Debug)
      fprintf (stdout, "|%s|%s|\n",
               muptr->VmsMailFileName, muptr->VmsMailDestFileName);

   if (!strcmp (muptr->VmsMailFileName, muptr->VmsMailDestFileName))
   {
      /******************/
      /* same mail file */
      /******************/

      DestFileName[DestFileNameLength = 0] = '\0';
   }
   else
   {
      /***********************/
      /* different mail file */
      /***********************/

      /* look for the indicative trailing slash */
      for (cptr = muptr->VmsMailDestFileName; *cptr && *cptr != '/'; cptr++);
      if (*cptr)
      {
         /* copying to a PMDF subfolder */
         DestFileNameLength = 
            CallMailPmdfImapMbxDir (muptr,
                                    muptr->VmsMailDestFileName,
                                    DestFileName, sizeof(DestFileName));
         if (!DestFileNameLength)
         {
            /* PMDF IMAP has not bee able to map the specification! */
            DestFileNameLength = 1;
            strcpy (DestFileName, "*");
         }
      }
      else
      {
         /* source and destination file names differ */
         char  *cptr, *sptr, *zptr;
         /* check if there are ODS-2 illegal characters present */
         for (cptr = muptr->VmsMailDestFileName; *cptr; cptr++)
            if (!(isalnum(*cptr) || *cptr == '$' ||
                  *cptr == '-' || *cptr == '_')) break;
         if (*cptr)
            VolumeStruct =
               CallMailVolumeStruct (muptr->VmsMailFullDirectory);

         /* build the destination mail file name */
         zptr = (sptr = DestFileName) + sizeof(DestFileName)-1;
         for (cptr = muptr->VmsMailFullDirectory;
              *cptr && sptr < zptr;
              *sptr++ = *cptr++);

         cptr = muptr->VmsMailDestFileName;
         while (*cptr && sptr < zptr)
         {
            if (isalnum(*cptr) || *cptr == '$' ||
                *cptr == '-' || *cptr == '_')
               *sptr++ = *cptr++;
            else
            if (*cptr == '^')
            {
               /* assume it's an escaped EFS-compliant character */
               *sptr++ = *cptr++;
               if (sptr < zptr) *sptr++ = *cptr++;
            }
            else
            if (VolumeStruct == 5)
            {
               /* assume EFS-compliant character requiring escaping */
               if (sptr < zptr) *sptr++ = '^';
               if (*cptr == ' ')
               {
                  if (sptr < zptr) *sptr++ = '_';
                  cptr++;
               }
               else
               if (sptr < zptr)
                  *sptr++ = *cptr++;
            }
            else
            {
               /* assume illegal character, substitute an underscore */
               *sptr++ = '_';
               cptr++;
            }
         }
         for (cptr = ".MAI;"; *cptr && sptr < zptr; *sptr++ = *cptr++);
         *sptr = '\0';
         DestFileNameLength = sptr - DestFileName;
      }
   }
   if (Debug) fprintf (stdout, "%d |%s|\n", DestFileNameLength, DestFileName);

   /**********************/
   /* process message(s) */
   /**********************/

   if (!(cptr = muptr->MsgIdListPtr)) ErrorExit (SS$_BUGCHECK, FI_LI);

   memset (&VmsMailMsg, 0, sizeof(VmsMailMsg));
   VmsMailMsg.MessageContext = vmptr->MessageContext;

   while (*cptr)
   {
      if (*cptr == '*')
         VmsMailMsg.MessageId++;
      else
      {
         VmsMailMsg.MessageId = atol(cptr);
         while (*cptr && *cptr != '.') cptr++;
         if (*cptr != '.') ErrorExit (SS$_BUGCHECK, FI_LI);
         cptr++;
         VmsMailMsg.ConfirmHash = atol(cptr);
         while (*cptr && *cptr != ',') cptr++;
         if (*cptr) cptr++;

         CallMailMessageHeader (muptr, &VmsMailMsg);

         /* if it wasn't the message we were seeking */
         if (muptr->MailVmsStatus == SS$_BADCHECKSUM) break;
      }

      itptr = &MessageIdItem[0];
      memset (itptr, 0, sizeof(MessageIdItem));

      itptr->buf_len = sizeof(VmsMailMsg.MessageId);
      itptr->item = MAIL$_MESSAGE_ID;
      itptr->buf_addr = &VmsMailMsg.MessageId;
      itptr++;

      if (DestFileNameLength)
      {
         itptr->buf_len = DestFileNameLength;
         itptr->item = MAIL$_MESSAGE_FILENAME;
         itptr->buf_addr = DestFileName;
         itptr++;
      }

      itptr->buf_len = muptr->VmsMailDestFolderNameLength;
      if (itptr->buf_len > 39) itptr->buf_len = 39;
      itptr->item = MAIL$_MESSAGE_FOLDER;
      itptr->buf_addr = &muptr->VmsMailDestFolderName;
      itptr++;

      if (DeleteAfterCopy)
      {
         itptr->item = MAIL$_MESSAGE_DELETE;
         itptr++;
      }

      itptr->item = MAIL$_NOSIGNAL;

      status = mail$message_copy (&VmsMailMsg.MessageContext,
                                  &MessageIdItem,
                                  &CallMailNullItem);
      if (Debug) fprintf (stdout, "mail$message_copy() %%X%08.08X\n", status);

      if (VMSnok (muptr->MailVmsStatus = status)) break;

      muptr->MessageCopiedCount++;
      if (VmsMailMsg.MessageFlags & MAIL$M_NEWMSG)
         muptr->NewMsgFlagCopiedCount++;
   }

   if (*cptr == '*' && (status == MAIL$_NOMSGS ||
                        status == MAIL$_NOMOREMSG))
      status = muptr->MailVmsStatus = SS$_NORMAL;

   /***************************************/
   /* update NEWMAIL count (if necessary) */
   /***************************************/

   MsgCnt = 0;

   /* if moved from MAIL.MAI's NEWMAIL folder */
   if (DeleteAfterCopy)
      if (muptr->MailDotMai && !strcmp (muptr->VmsMailFolderName, "NEWMAIL"))
         if (muptr->VmsNewMsg)
            MsgCnt -= muptr->NewMsgFlagCopiedCount;
         else
            MsgCnt -= muptr->MessageCopiedCount;

   /* if copied into MAIL.MAI's NEWMAIL folder */
   for (cptr = muptr->VmsMailDestFileName + muptr->VmsMailDestFileNameLength;
        cptr > muptr->VmsMailDestFileName && *cptr != ']';
        cptr--);
   if (*cptr == ']') cptr++;
   if (!memcmp (cptr, "MAIL\0", 5) || !memcmp (cptr, "MAIL.", 5))
      if (!strcmp (muptr->VmsMailDestFolderName, "NEWMAIL"))
         if (muptr->VmsNewMsg)
            MsgCnt += muptr->NewMsgFlagCopiedCount;
         else
            MsgCnt += muptr->MessageCopiedCount;

   if (MsgCnt)
   {
      status = CallMailSetUserNewMessages (muptr, MsgCnt);
      if (VMSok (muptr->MailVmsStatus))
         if (VMSnok (status))
            muptr->MailVmsStatus = status;
   }

   /*************************/
   /* close message context */
   /*************************/

   status = mail$message_end (&vmptr->MessageContext,
                              &CallMailNoSignalItem,
                              &CallMailNullItem);
   if (Debug) fprintf (stdout, "mail$message_end() %%X%08.08X\n", status);
   vmptr->MessageContext = 0;

   /* if a move then check for a resulting empty mail file */
   if (DeleteAfterCopy) CallMailDeleteIfEmpty (muptr);

   return (muptr->MailVmsStatus);
}

/*****************************************************************************/
/*
Copy the contents of a sequential message (mail) file into the destination
indexed Mail file and folder.  The sequential message file must have a VAR
record format with CR record attributes.

The format for the content of this file is:
           
  <form-feed>
  From:<tab>[<from-address>]<date/time>
  To:<tab>[<to-address>]
  CC:<tab>[<cc-address>]
  Subj:<tab>[<subject text>]
  <empty line>
  <body>

Note the leading <form-feed> record that is some sort of sentinal for each (if
multiple) message in the sequential file.

Many thanks to Andy Harper for originally asking the question and Hein van den
Heuvel for answering it way back in 1996, providing a significant insight into
this undocumented (or at least unobvious) facility.  (Search Google groups for
"harper van den vms mail sequential".)  Also Lance J. Wilkinson ("never
underestimate the bandwidth of a station wagon full of mag tapes") for asking
similar questions and elucidating the date requirement.

Although (oddly enough) not generated by a $MAIL/EXTRACT/MAIL the first line
("From:") requires a trailing date/time to correctly insert the email into the
folder.  Without it it appears to go into the folder but not be accessable at
all!  This is perhaps because Mail file keys on the quadword date/time as well
as the folder name and a zero date is not processed correctly or is a sentinal
of some sort.  This is intimated in a posting by Hunter Goatley where he quotes
the V5.0 VMS sources and it notes "Quadword: DATIM Date/time message received
(Date/times with high quadword 0 are reserved as info records)".

This requirement for such a date tacked onto the "From:" field seems a Mail
kludge and is bit disruptive to the header display, so I have decided to 'hide'
it by separating the address and date/time with a null character which tends to
conceal it from parsers such as soyMAIL (which scan as far as the null
'terminator') but still allows VMS (with it's 'records') to process it.
*/

int CallMailCopyFile
(
VMS_MAIL_USER *muptr,
char *DestFileName,
char *DestFolderName,
char *FileName
)
{
   int  status,
        VmsMailStatus;
   unsigned long  MailFileContext,
                  MailFileIndexed,
                  MailFileNameLength,
                  MessageContext,
                  MessageId,
                  MessageSelected;
   char  *cptr, *sptr;
   char  MailFileName [256];
   VMS_ITEM_LIST3  *itptr;
   /* reminder: these require one extra element for the null item */
   VMS_ITEM_LIST3  MailFileInItem [3];
   VMS_ITEM_LIST3  MailFileOutItem [3];
   VMS_ITEM_LIST3  MessageBeginInItem [3];
   VMS_ITEM_LIST3  MessageBeginOutItem [3];
   VMS_ITEM_LIST3  MessageIdItem [5];

   /*********/
   /* begin */
   /*********/

   if (Debug)
      fprintf (stdout, "CallMailCopyFile() |%s|%s|%s|\n",
               DestFileName, DestFolderName, FileName);

   if (!muptr->VmsMailFullDirectoryLength) ErrorExit (SS$_BUGCHECK, FI_LI);
   if (!muptr->VmsMailFileNameLength) ErrorExit (SS$_BUGCHECK, FI_LI);

   MailFileNameLength =
      CallMailBuildMailFileName (muptr, DestFileName,
                                 MailFileName, sizeof(MailFileName));

   /*********************/
   /* mail file context */
   /*********************/

   MailFileContext = 0;
   status = mail$mailfile_begin (&MailFileContext,
                                 &CallMailNullItem, &CallMailNullItem);
   if (Debug) fprintf (stdout, "mail$mailfile_begin() %%X%08.08X\n", status);
   VmsMailStatus = status;

   if (VMSok (VmsMailStatus))
   {
      itptr = &MailFileInItem[0];
      memset (itptr, 0, sizeof(MailFileInItem));

      itptr->buf_len = strlen(FileName);
      itptr->item = MAIL$_MAILFILE_NAME;
      itptr->buf_addr = FileName;
      itptr++;

      itptr->item = MAIL$_NOSIGNAL;

      itptr = &MailFileOutItem[0];
      memset (itptr, 0, sizeof(MailFileOutItem));

      itptr->buf_len = sizeof(MailFileIndexed);
      itptr->item = MAIL$_MAILFILE_INDEXED;
      itptr->buf_addr = &MailFileIndexed;
      itptr++;

      itptr->item = MAIL$_NOSIGNAL;

      status = mail$mailfile_open (&MailFileContext,
                                   &MailFileInItem, &MailFileOutItem);
      if (Debug)
         fprintf (stdout, "mail$mailfile_open() %%X%08.08X INDEXED:%d\n",
                  status, MailFileIndexed);
      if (VMSnok (status)) VmsMailStatus = status;
   }

   /*******************/
   /* message context */
   /*******************/

   if (VMSok (VmsMailStatus))
   {
      itptr = &MessageBeginInItem[0];
      memset (itptr, 0, sizeof(MessageBeginInItem));

      itptr->buf_len = sizeof(MailFileContext);
      itptr->item = MAIL$_MESSAGE_FILE_CTX;
      itptr->buf_addr = (void*)&MailFileContext;
      itptr++;

      itptr->item = MAIL$_NOSIGNAL;

      itptr = &MessageBeginOutItem[0];
      memset (itptr, 0, sizeof(MessageBeginOutItem));

      itptr->buf_len = sizeof(MessageSelected);
      itptr->item = MAIL$_MESSAGE_SELECTED;
      itptr->buf_addr = (void*)&MessageSelected;
      itptr++;

      itptr->item = MAIL$_NOSIGNAL;

      MessageContext = 0;
      status = mail$message_begin (&MessageContext,
                                   &MessageBeginInItem, &MessageBeginOutItem);
      if (Debug)
         fprintf (stdout, "mail$message_begin() %%X%08.08X selected:%d\n",
                  status, MessageSelected);
      if (VMSnok (status)) VmsMailStatus = status;
   }

   /****************/
   /* copy message */
   /****************/

   if (VMSok (VmsMailStatus))
   {
      MessageId = 1;

      itptr = &MessageIdItem[0];
      memset (itptr, 0, sizeof(MessageIdItem));

      itptr->buf_len = sizeof(MessageId);
      itptr->item = MAIL$_MESSAGE_ID;
      itptr->buf_addr = (void*)&MessageId;
      itptr++;

      itptr->buf_len = MailFileNameLength;
      itptr->item = MAIL$_MESSAGE_FILENAME;
      itptr->buf_addr = MailFileName;
      itptr++;

      itptr->buf_len = strlen(DestFolderName);
      itptr->item = MAIL$_MESSAGE_FOLDER;
      itptr->buf_addr = DestFolderName;
      itptr++;

      itptr->item = MAIL$_NOSIGNAL;

      status = mail$message_copy (&MessageContext,
                                  &MessageIdItem,
                                  &CallMailNullItem);
      if (Debug) fprintf (stdout, "mail$message_copy() %%X%08.08X\n", status);
      /* NOMOREMSG when there has been a problem with the message format */
      if (status == MAIL$_NOMOREMSG) status = SS$_FORMAT;
      if (VMSnok (status)) VmsMailStatus = status;
   }

   /***************************************/
   /* update NEWMAIL count (if necessary) */
   /***************************************/

   if (VMSok (VmsMailStatus) &&
       muptr->MailDotMai &&
       !strcmp (muptr->VmsMailFolderName, "NEWMAIL") &&
       !muptr->VmsNewMsg)
   {
      /* NEWMAIL folder in primary MAIL.MAI file */
      status = CallMailSetUserNewMessages (muptr, 1);
      if (Debug)
         fprintf (stdout, "CallMailSetUserNewMessages() %%X%08.08X\n", status);
      if (VMSok (muptr->MailVmsStatus))
         if (VMSnok (status))
            muptr->MailVmsStatus = status;
   }

   /******************/
   /* close contexts */
   /******************/

   status = mail$message_end (&MessageContext,
                              &CallMailNoSignalItem,
                              &CallMailNullItem);
   if (Debug) fprintf (stdout, "mail$message_end() %%X%08.08X\n", status);
   if (VMSnok (status)) VmsMailStatus = status;

   status = mail$mailfile_close (&MailFileContext,
                                 &CallMailNoSignalItem, &CallMailNoSignalItem);
   if (Debug) fprintf (stdout, "mail$mailfile_close() %%X%08.08X\n", status);
   if (VMSnok (status)) VmsMailStatus = status;

   status = mail$mailfile_end (&MailFileContext,
                               &CallMailNoSignalItem, &CallMailNullItem);
   if (Debug) fprintf (stdout, "mail$mailfile_end() %%X%08.08X\n", status);

   return (VmsMailStatus);
}

/*****************************************************************************/
/*
Delete the specified message(s).
*/

int CallMailMessageDelete (VMS_MAIL_USER *muptr)

{
   int  status;
   char  *cptr;
   VMS_ITEM_LIST3  *itptr;
   VMS_MAIL_MSG  *vmptr;
   VMS_MAIL_MSG  VmsMailMsg;
   /* reminder: these require one extra element for the null item */
   VMS_ITEM_LIST3  MessageIdItem [3];

   /*********/
   /* begin */
   /*********/

   if (Debug)
      fprintf (stdout, "CallMailMessageDelete() |%s|\n",
               muptr->MsgIdListPtr);

   muptr->MessageDeletedCount = muptr->NewMsgFlagDeletedCount = 0;

   vmptr = &muptr->VmsMailMsg;

   if (!vmptr->MessageContext)
   {
      /************************/
      /* open message context */
      /************************/

      status = CallMailMessageContext (muptr, vmptr);
      if (VMSnok (status)) return (muptr->MailVmsStatus = status);
   }

   if (!(cptr = muptr->MsgIdListPtr)) ErrorExit (SS$_BUGCHECK, FI_LI);

   if (*cptr == '*' &&
       muptr->MailDotMai &&
       !strcmp (muptr->VmsMailFolderName, "WASTEBASKET"))
   {
      /*********************/
      /* purge wastebasket */
      /*********************/

      itptr = &MessageIdItem[0];
      memset (itptr, 0, sizeof(MessageIdItem));

      itptr->item = MAIL$_NOSIGNAL;

      status = mail$mailfile_purge_waste (&muptr->VmsMailFileContext,
                                          &MessageIdItem,
                                          &CallMailNullItem);
      if (Debug)
         fprintf (stdout, "mail$mailfile_purge_waste() %%X%08.08X\n", status);

      if (VMSok (status)) muptr->MessageDeletedCount = 999999;
      muptr->MailVmsStatus = status;
      return (muptr->MailVmsStatus);
   }

   /**********************/
   /* process message(s) */
   /**********************/

   memset (&VmsMailMsg, 0, sizeof(VmsMailMsg));
   VmsMailMsg.MessageContext = vmptr->MessageContext;

   while (*cptr)
   {
      if (*cptr == '*')
         VmsMailMsg.MessageId++;
      else
      {
         VmsMailMsg.MessageId = atol(cptr);
         while (*cptr && *cptr != '.') cptr++;
         if (*cptr != '.') ErrorExit (SS$_BUGCHECK, FI_LI);
         cptr++;
         VmsMailMsg.ConfirmHash = atol(cptr);
         while (*cptr && *cptr != ',') cptr++;
         if (*cptr) cptr++;

         CallMailMessageHeader (muptr, &VmsMailMsg);

         /* if it wasn't the message we were seeking */
         if (muptr->MailVmsStatus == SS$_BADCHECKSUM) break;
      }

      itptr = &MessageIdItem[0];
      memset (itptr, 0, sizeof(MessageIdItem));

      itptr->buf_len = sizeof(VmsMailMsg.MessageId);
      itptr->item = MAIL$_MESSAGE_ID;
      itptr->buf_addr = &VmsMailMsg.MessageId;
      itptr++;

      itptr->item = MAIL$_NOSIGNAL;

      status = mail$message_delete (&VmsMailMsg.MessageContext,
                                    &MessageIdItem,
                                    &CallMailNullItem);
      if (Debug) fprintf (stdout, "mail$message_delete() %%X%08.08X\n", status);

      muptr->MailVmsStatus = status;
      if (VMSnok (status)) break;

      muptr->MessageDeletedCount++;
      if (VmsMailMsg.MessageFlags & MAIL$M_NEWMSG)
         muptr->NewMsgFlagDeletedCount++;
   }

   if (*cptr == '*' && (status == MAIL$_NOMSGS ||
                        status == MAIL$_NOMOREMSG))
      muptr->MailVmsStatus = status = SS$_NORMAL;

   /***************************************/
   /* update NEWMAIL count (if necessary) */
   /***************************************/

   if (muptr->MailDotMai && !strcmp (muptr->VmsMailFolderName, "NEWMAIL"))
   {
      /* NEWMAIL folder in primary MAIL.MAI file */
      if (muptr->VmsNewMsg)
         status = CallMailSetUserNewMessages (muptr,
                                              -muptr->NewMsgFlagDeletedCount);
      else
         status = CallMailSetUserNewMessages (muptr,
                                              -muptr->MessageDeletedCount);
      if (Debug)
         fprintf (stdout, "CallMailSetUserNewMessages() %%X%08.08X\n", status);
      if (VMSok (muptr->MailVmsStatus))
         if (VMSnok (status))
            muptr->MailVmsStatus = status;
   }

   /*************************/
   /* close message context */
   /*************************/

   status = mail$message_end (&vmptr->MessageContext,
                              &CallMailNoSignalItem,
                              &CallMailNullItem);
   if (Debug) fprintf (stdout, "mail$message_end() %%X%08.08X\n", status);
   vmptr->MessageContext = 0;

   /* prevent the message complaining you can't delete from the WASTEBASKET */
   if (status == MAIL$_DELWASTE) muptr->MailVmsStatus = SS$_NORMAL;
   /* 'cause you can! */

   CallMailFileClose (muptr);

   CallMailDeleteIfEmpty (muptr);

   return (muptr->MailVmsStatus);
}

/*****************************************************************************/
/*
If empty, non-primary Mail file then delete it.
*/

int CallMailDeleteIfEmpty (VMS_MAIL_USER *muptr)

{
   int  retval, status;
   char  *cptr;

   /*********/
   /* begin */
   /*********/

   if (Debug) fprintf (stdout, "CallMailDeleteIfEmpty()\n");

   /* if primary mail file (MAIL.MAI) quit here */
   if (muptr->MailDotMai) return (SS$_NORMAL);

   /* look for the indicative slash (do not delete PMDF IMAP MAIL.MAIs) */
   for (cptr = muptr->OpenMailFileName; *cptr && *cptr != '/'; cptr++);
   if (*cptr) return (SS$_NORMAL);

   status = CallMailFolderList (muptr, FALSE, FALSE);
   if (VMSnok (status)) return (muptr->MailVmsStatus = status);
   if (Debug) fprintf (stdout, "%d\n", muptr->MailFolderList.ListCount);

   CallMailFileClose (muptr);

   /* if one or more folders left in the non-primary mail file return now */
   if (muptr->MailFolderList.ListCount) return (muptr->MailVmsStatus);

   /* ensure system:rwed,owner:rwed */
   retval = chmod (muptr->VmsMailFileName, 0x180);
   if (retval == -1) return (muptr->MailVmsStatus = vaxc$errno);

   for (muptr->MailFileDeletedCount = 0;
        !remove (muptr->VmsMailFileName);
        muptr->MailFileDeletedCount++);
   if (!muptr->MailFileDeletedCount)
      return (muptr->MailVmsStatus = vaxc$errno);

   return (SS$_NORMAL);
}

/*****************************************************************************/
/*
Move the message context to standard VMS Mail "NEWMAIL" folder.
*/

int CallMailGoToNewMail (VMS_MAIL_USER *muptr)

{
   int  cnt, status;
   VMS_ITEM_LIST3  *itptr;
   VMS_MAIL_MSG  *vmptr;

   /*********/
   /* begin */
   /*********/

   if (Debug) fprintf (stdout, "CallMailGoToNewMail()\n");

   vmptr = &muptr->VmsMailMsg;

   /* close any existing message context */
   if (vmptr->MessageContext)
   {
      status = mail$message_end (&vmptr->MessageContext,
                                 &CallMailNoSignalItem,
                                 &CallMailNullItem);
      if (Debug) fprintf (stdout, "mail$message_end() %%X%08.08X\n", status);
      vmptr->MessageContext = 0;
   }

   /* open message context */
   strcpy (muptr->VmsMailFileName, "MAIL");
   muptr->VmsMailFileNameLength = strlen(muptr->VmsMailFileName);

   strcpy (muptr->VmsMailFolderName, "NEWMAIL");
   muptr->VmsMailFolderNameLength = strlen(muptr->VmsMailFolderName);

   status = CallMailMessageContext (muptr, vmptr);
   if (VMSnok (status))
   {
      if (status == MAIL$_NOTEXIST)
      {
         /* NEWMAIL folder does not exist */
         muptr->MailVmsStatus = SS$_NORMAL;
         if (muptr->VmsMailNewMessages)
         {
            /* why then does it have a new message count? reset to zero! */
            CallMailSetUserNewMessages (muptr, -999999);
         }
      }
      return (muptr->MailVmsStatus);
   }
   else
   {
      /* if the counts don't match then adjust by difference */
      cnt = muptr->VmsMailNewMessages - vmptr->MessageSelectedCount;
      /* if VMS new msg option enabled then only if count is larger */
      if (muptr->VmsNewMsg && cnt < 0) cnt = 0;
      if (cnt) CallMailSetUserNewMessages (muptr, -cnt);
   }

   return (muptr->MailVmsStatus);
}

/*****************************************************************************/
/*
This function retrieves user profile information.
*/

int CallMailGetUserProfile (VMS_MAIL_USER *muptr)

{
   int  status;
   unsigned long  UserNameLength;
   VMS_ITEM_LIST3  *itptr;
   /* reminder: these require one extra element for the null item */
   VMS_ITEM_LIST3  ProfileItem [14];
   VMS_ITEM_LIST3  SigFileItem [3];
   VMS_ITEM_LIST3  UserNameItem [3];

   /*********/
   /* begin */
   /*********/

   if (Debug)
      fprintf (stdout, "VmsMailGetUserProfile() |%s|\n", muptr->UserName);

   muptr->DiskQuotaPerm = muptr->DiskQuotaUsed = (unsigned long)-1;

   itptr = &UserNameItem[0];
   memset (itptr, 0, sizeof(UserNameItem));

   itptr->buf_len = muptr->UserNameLength;
   itptr->item = MAIL$_USER_USERNAME;
   itptr->buf_addr = muptr->UserName;
   itptr->ret_len = &UserNameLength;
   itptr++;

   itptr->item = MAIL$_NOSIGNAL;

   if (!muptr->VmsMailUserContext)
   {
      status = mail$user_begin (&muptr->VmsMailUserContext,
                                &CallMailNoSignalItem, &CallMailNullItem);
      if (Debug) fprintf (stdout, "mail$user_begin() %%X%08.08X\n", status);
      if (VMSnok (status)) return (muptr->MailVmsStatus = status);
   }

   itptr = &ProfileItem[0];
   memset (itptr, 0, sizeof(ProfileItem));

   itptr->buf_len = sizeof(muptr->VmsMailNewMessages);
   itptr->item = MAIL$_USER_NEW_MESSAGES;
   itptr->buf_addr = &muptr->VmsMailNewMessages;
   itptr++;

   itptr->buf_len = sizeof(muptr->VmsMailAutoPurge);
   itptr->item = MAIL$_USER_AUTO_PURGE;
   itptr->buf_addr = &muptr->VmsMailAutoPurge;
   itptr++;

   itptr->buf_len = sizeof(muptr->VmsMailCcPrompt);
   itptr->item = MAIL$_USER_CC_PROMPT;
   itptr->buf_addr = &muptr->VmsMailCcPrompt;
   itptr++;

   itptr->buf_len = sizeof(muptr->VmsMailCopyForward);
   itptr->item = MAIL$_USER_COPY_FORWARD ;
   itptr->buf_addr = &muptr->VmsMailCopyForward;
   itptr++;

   itptr->buf_len = sizeof(muptr->VmsMailCopyReply);
   itptr->item = MAIL$_USER_COPY_REPLY;
   itptr->buf_addr = &muptr->VmsMailCopyReply;
   itptr++;

   itptr->buf_len = sizeof(muptr->VmsMailCopySend);
   itptr->item = MAIL$_USER_COPY_SEND;
   itptr->buf_addr = &muptr->VmsMailCopySend;
   itptr++;

   itptr->buf_len = sizeof(muptr->VmsMailForm)-1;
   itptr->item = MAIL$_USER_FORM;
   itptr->buf_addr = muptr->VmsMailForm;
   itptr->ret_len = &muptr->VmsMailFormLength;
   itptr++;

   itptr->buf_len = sizeof(muptr->VmsMailEditor)-1;
   itptr->item = MAIL$_USER_EDITOR;
   itptr->buf_addr = muptr->VmsMailEditor;
   itptr->ret_len = &muptr->VmsMailEditorLength;
   itptr++;

   itptr->buf_len = sizeof(muptr->VmsMailForwarding)-1;
   itptr->item = MAIL$_USER_FORWARDING;
   itptr->buf_addr = muptr->VmsMailForwarding;
   itptr->ret_len = &muptr->VmsMailForwardingLength;
   itptr++;

   itptr->buf_len = sizeof(muptr->VmsMailFullDirectory)-1;
   itptr->item = MAIL$_USER_FULL_DIRECTORY;
   itptr->buf_addr = muptr->VmsMailFullDirectory;
   itptr->ret_len = &muptr->VmsMailFullDirectoryLength;
   itptr++;

   itptr->buf_len = sizeof(muptr->VmsMailPersonalName)-1;
   itptr->item = MAIL$_USER_PERSONAL_NAME;
   itptr->buf_addr = muptr->VmsMailPersonalName;
   itptr->ret_len = &muptr->VmsMailPersonalNameLength;
   itptr++;

   itptr->buf_len = sizeof(muptr->VmsMailQueue)-1;
   itptr->item = MAIL$_USER_QUEUE;
   itptr->buf_addr = muptr->VmsMailQueue;
   itptr->ret_len = &muptr->VmsMailQueueLength;
   itptr++;

   itptr->item = MAIL$_NOSIGNAL;

   status = mail$user_get_info (&muptr->VmsMailUserContext,
                                &UserNameItem,
                                &ProfileItem);
   if (Debug) fprintf (stdout, "mail$user_get_info() %%X%08.08X\n", status);

   if (VMSok (status))
   {
      muptr->VmsMailEditor[muptr->VmsMailEditorLength] = '\0';
      muptr->VmsMailForwarding[muptr->VmsMailForwardingLength] = '\0';
      muptr->VmsMailForm[muptr->VmsMailFormLength] = '\0';
      muptr->VmsMailFullDirectory[muptr->VmsMailFullDirectoryLength] = '\0';
      muptr->VmsMailPersonalName[muptr->VmsMailPersonalNameLength] = '\0';
      muptr->VmsMailQueue[muptr->VmsMailQueueLength] = '\0';
      muptr->VmsMailAutoPurge = muptr->VmsMailAutoPurge & 1;
      muptr->VmsMailCcPrompt = muptr->VmsMailCcPrompt & 1;
      muptr->VmsMailCopyForward = muptr->VmsMailCopyForward & 1;
      muptr->VmsMailCopyReply = muptr->VmsMailCopyReply & 1;
      muptr->VmsMailCopySend = muptr->VmsMailCopySend & 1;
      if (Debug)
         fprintf (stdout,
"%d %d %d %d %d %d\n|%s|\n|%s|\n|%s|\n|%s|\n|%s|\n|%s|\n",
                 muptr->VmsMailNewMessages,
                 muptr->VmsMailAutoPurge,
                 muptr->VmsMailCcPrompt,
                 muptr->VmsMailCopyForward,
                 muptr->VmsMailCopyReply, 
                 muptr->VmsMailCopySend, 
                 muptr->VmsMailEditor,
                 muptr->VmsMailForm,
                 muptr->VmsMailForwarding,
                 muptr->VmsMailFullDirectory,
                 muptr->VmsMailPersonalName,
                 muptr->VmsMailQueue);
   }

   muptr->VmsMailSigFile[0] = '\0';
   muptr->VmsMailSigFileLength = 0;

   if (VmsVersion >= 700 && VMSok (status))
   {
      itptr = &SigFileItem[0];
      memset (itptr, 0, sizeof(SigFileItem));

      itptr->buf_len = sizeof(muptr->VmsMailSigFile)-1;
      itptr->item = MAIL$_USER_SIGFILE;
      itptr->buf_addr = muptr->VmsMailSigFile;
      itptr->ret_len = &muptr->VmsMailSigFileLength;
      itptr++;

      itptr->item = MAIL$_NOSIGNAL;

      status = mail$user_get_info (&muptr->VmsMailUserContext,
                                   &UserNameItem,
                                   &SigFileItem);
      if (Debug) fprintf (stdout, "mail$user_get_info() %%X%08.08X\n", status);
      if (VMSok (status))
      {
         muptr->VmsMailSigFile[muptr->VmsMailSigFileLength] = '\0';
         if (Debug) fprintf (stdout, "|%s|\n", muptr->VmsMailSigFile);
      }
      status = SS$_NORMAL;
   }
   else
   {
      /* just substitute a fallback signature file name */
      strcpy (muptr->VmsMailSigFile, SOY_SIGFILE);
      muptr->VmsMailSigFileLength = strlen(muptr->VmsMailSigFile);
   }

   if (VMSnok (status)) CallMailUserClose (muptr);

   return (muptr->MailVmsStatus = status);
}

/*****************************************************************************/
/*
This function sets user profile information.
It's expects all relevant fields to have been populated.
*/

int CallMailSetUserProfile (VMS_MAIL_USER *muptr)

{
   int  status;
   unsigned long  UserNameLength;
   VMS_ITEM_LIST3  *itptr;
   /* reminder: these require one extra element for the null item */
   VMS_ITEM_LIST3 ProfileItem [13];
   VMS_ITEM_LIST3 SigFileItem [4];

   /*********/
   /* begin */
   /*********/

   if (Debug) fprintf (stdout, "VmsMailSetUserProfile()\n");

   if (!muptr->VmsMailFullDirectoryLength) ErrorExit (SS$_BUGCHECK, FI_LI);

   if (!muptr->VmsMailUserContext)
   {
      status = mail$user_begin (&muptr->VmsMailUserContext,
                                &CallMailNullItem, &CallMailNullItem);
      if (Debug) fprintf (stdout, "mail$user_begin() %%X%08.08X\n", status);
      if (VMSnok (status)) return (muptr->MailVmsStatus = status);
   }

   itptr = &ProfileItem[0];
   memset (itptr, 0, sizeof(ProfileItem));

   itptr->buf_len = muptr->UserNameLength;
   itptr->item = MAIL$_USER_USERNAME;
   itptr->buf_addr = muptr->UserName;
   itptr->ret_len = &UserNameLength;
   itptr++;

   if (muptr->VmsMailAutoPurge)
      itptr->item = MAIL$_USER_SET_AUTO_PURGE;
   else
      itptr->item = MAIL$_USER_SET_NO_AUTO_PURGE;
   itptr++;

   if (muptr->VmsMailCcPrompt)
      itptr->item = MAIL$_USER_SET_CC_PROMPT;
   else
      itptr->item = MAIL$_USER_SET_NO_CC_PROMPT;
   itptr++;

   if (muptr->VmsMailCopyForward)
      itptr->item = MAIL$_USER_SET_COPY_FORWARD ;
   else
      itptr->item = MAIL$_USER_SET_NO_COPY_FORWARD ;
   itptr++;

   if (muptr->VmsMailCopyReply)
      itptr->item = MAIL$_USER_SET_COPY_REPLY;
   else
      itptr->item = MAIL$_USER_SET_NO_COPY_REPLY;
   itptr++;

   if (muptr->VmsMailCopySend)
      itptr->item = MAIL$_USER_SET_COPY_SEND;
   else
      itptr->item = MAIL$_USER_SET_NO_COPY_SEND;
   itptr++;

   if (muptr->VmsMailFormLength)
   {
      itptr->buf_len = muptr->VmsMailFormLength;
      itptr->item = MAIL$_USER_SET_FORM;
      itptr->buf_addr = muptr->VmsMailForm;
   }
   else
      itptr->item = MAIL$_USER_SET_NO_FORM;
   itptr++;

   if (muptr->VmsMailEditorLength)
   {
      itptr->buf_len = muptr->VmsMailEditorLength;
      itptr->item = MAIL$_USER_SET_EDITOR;
      itptr->buf_addr = muptr->VmsMailEditor;
   }
   else
      itptr->item = MAIL$_USER_SET_NO_EDITOR;
   itptr++;

   if (muptr->VmsMailForwardingLength)
   {
      itptr->buf_len = muptr->VmsMailForwardingLength;
      itptr->item = MAIL$_USER_SET_FORWARDING;
      itptr->buf_addr = muptr->VmsMailForwarding;
   }
   else
      itptr->item = MAIL$_USER_SET_NO_FORWARDING;
   itptr++;

   if (muptr->VmsMailPersonalNameLength)
   {
      itptr->buf_len = muptr->VmsMailPersonalNameLength;
      itptr->item = MAIL$_USER_SET_PERSONAL_NAME;
      itptr->buf_addr = muptr->VmsMailPersonalName;
   }
   else
      itptr->item = MAIL$_USER_SET_NO_PERSONAL_NAME;
   itptr++;

   if (muptr->VmsMailQueueLength)
   {
      itptr->buf_len = muptr->VmsMailQueueLength;
      itptr->item = MAIL$_USER_SET_QUEUE;
      itptr->buf_addr = muptr->VmsMailQueue;
   }
   else
      itptr->item = MAIL$_USER_SET_NO_QUEUE;
   itptr++;

   itptr->item = MAIL$_NOSIGNAL;

   status = mail$user_set_info (&muptr->VmsMailUserContext,
                                &ProfileItem,
                                &CallMailNullItem);
   if (Debug) fprintf (stdout, "mail$user_set_info() %%X%08.08X\n", status);

   if (VmsVersion >= 700 && VMSok (status))
   {
      itptr = &SigFileItem[0];
      memset (itptr, 0, sizeof(SigFileItem));

      itptr->buf_len = muptr->UserNameLength;
      itptr->item = MAIL$_USER_USERNAME;
      itptr->buf_addr = muptr->UserName;
      itptr->ret_len = &UserNameLength;
      itptr++;

      itptr->buf_len = muptr->VmsMailSigFileLength;
      itptr->item = MAIL$_USER_SET_SIGFILE;
      itptr->buf_addr = muptr->VmsMailSigFile;
      itptr++;

      itptr->item = MAIL$_NOSIGNAL;

      status = mail$user_set_info (&muptr->VmsMailUserContext,
                                   &SigFileItem,
                                   &CallMailNullItem);
      if (Debug) fprintf (stdout, "mail$user_set_info() %%X%08.08X\n", status);
   }

   if (VMSnok (status)) CallMailUserClose (muptr);

   return (muptr->MailVmsStatus = status);
}

/*****************************************************************************/
/*
If messages are moved to or from, or deleted from the NEWMAIL folder update the
count of new messages.
*/

CallMailSetUserNewMessages
(
VMS_MAIL_USER *muptr,
int DeltaValue
)
{
   static short  NewMessages;

   static VMS_ITEM_LIST3  UserGetItem [] = {
      { sizeof(NewMessages), MAIL$_USER_NEW_MESSAGES, &NewMessages, 0 },
      { 0, MAIL$_NOSIGNAL, 0, 0 },
      { 0,0,0,0 }
   };

   static VMS_ITEM_LIST3  UserSetItem [] = {
      { sizeof(NewMessages), MAIL$_USER_SET_NEW_MESSAGES, &NewMessages, 0 },
      { 0, MAIL$_USER_USERNAME, 0, 0 },
      { 0, MAIL$_NOSIGNAL, 0, 0 },
      { 0,0,0,0 }
   };

   static VMS_ITEM_LIST3  UserNameItem [] = {
      { 0, MAIL$_USER_USERNAME, 0, 0 },
      { 0, MAIL$_NOSIGNAL, 0, 0 },
      { 0,0,0,0 }
   };

   int  status;

   /*********/
   /* begin */
   /*********/

   if (Debug) fprintf (stdout, "CallMailSetUserNewMessages()\n");

   if (!muptr->VmsMailUserContext)
   {
      status = mail$user_begin (&muptr->VmsMailUserContext,
                                &CallMailNoSignalItem, &CallMailNullItem);
      if (Debug) fprintf (stdout, "mail$user_begin() %%X%08.08X\n", status);
      if (VMSnok (status)) return (muptr->MailVmsStatus = status);
   }

   UserNameItem[0].buf_len = muptr->UserNameLength;
   UserNameItem[0].buf_addr = muptr->UserName;

   status = mail$user_get_info (&muptr->VmsMailUserContext,
                                &UserNameItem,
                                &UserGetItem);
   if (Debug) fprintf (stdout, "mail$user_get_info() %%X%08.08X\n", status);

   if (VMSok (status))
   {
      if (Debug)
         fprintf (stdout, "NewMessages: %d (%d) %d\n",
                  NewMessages, DeltaValue, NewMessages+DeltaValue);
      NewMessages += DeltaValue;
      if (NewMessages < 0) NewMessages = 0;

      UserSetItem[1].buf_addr = muptr->UserName;
      UserSetItem[1].buf_len = muptr->UserNameLength;

      status = mail$user_set_info (&muptr->VmsMailUserContext,
                                   &UserSetItem,
                                   &CallMailNullItem);
      if (Debug) fprintf (stdout, "mail$user_set_info() %%X%08.08X\n", status);

      if (VMSok (status)) muptr->VmsMailNewMessages = NewMessages;
   }

   if (VMSnok (status)) CallMailUserClose (muptr);

   return (muptr->MailVmsStatus = status);
}

/*****************************************************************************/
/*
Close the user context.
*/

CallMailUserClose (VMS_MAIL_USER *muptr)

{
   /*********/
   /* begin */
   /*********/

   if (Debug) fprintf (stdout, "CallMailUserClose()\n");

   if (!muptr->VmsMailUserContext) return (SS$_NORMAL);

   mail$user_end (&muptr->VmsMailUserContext,
                  &CallMailNoSignalItem, &CallMailNullItem);
   muptr->VmsMailUserContext = 0;
}

/*****************************************************************************/
/*
Using the device from the full directory specification of the VMS mail profile
and the $GETUI UIC queue an ACP $QIo to obtain the permananet and used disk
quotas.  Minus one on the permanent quota datum indicates quotas were disabled
or that an error status was returned.
*/

CallMailUserDiskQuota (VMS_MAIL_USER *muptr)

{
   static unsigned long  UaiContext = -1;
   static unsigned long  UaiUic;

   static VMS_ITEM_LIST3  UaiItems [] = 
   {
      { sizeof(UaiUic), UAI$_UIC, &UaiUic, 0 },
      { 0,0,0,0 }
   };

   int  status;
   unsigned short  DeviceChannel,
                   OutLength;
   char  *cptr;
   $DESCRIPTOR (DeviceDsc, "");
   $DESCRIPTOR (UserNameDsc, "");
   struct fibdef  DeviceFib; 
   static VMS_ITEM_LIST2  DeviceFibDsc,
                          InQuotaDsc,
                          OutQuotaDsc;

   struct {
      unsigned long flags;
      unsigned long uic;
      unsigned long used;
      unsigned long perm;
      unsigned long over;
      unsigned long unused[3];
   } InQuota, OutQuota;

   struct {
      unsigned short  iosb$w_status;
      unsigned short  iosb$w_bcnt;
      unsigned long  iosb$l_reserved;
   } IOsb;

   /*********/
   /* begin */
   /*********/

   if (Debug) fprintf (stdout, "CallMailUserDiskQuota()\n");

   muptr->DiskQuotaPerm = muptr->DiskQuotaUsed = (unsigned long)-1;

   UserNameDsc.dsc$a_pointer = muptr->UserName;
   UserNameDsc.dsc$w_length = muptr->UserNameLength;

   status = sys$getuai (0, &UaiContext, &UserNameDsc, &UaiItems, 0, 0, 0);
   if (Debug) fprintf (stdout, "sys$getuai() %%X%08.08X\n", status);
   if (VMSnok (status)) return (status);

   DeviceDsc.dsc$a_pointer = cptr = muptr->VmsMailFullDirectory;
   while (*cptr && *cptr != ':') cptr++;
   if (*cptr == ':') cptr++;
   DeviceDsc.dsc$w_length = cptr - DeviceDsc.dsc$a_pointer;
   if (Debug)
      fprintf (stdout, "|%*.*s|\n",
               DeviceDsc.dsc$w_length, DeviceDsc.dsc$w_length,
               DeviceDsc.dsc$a_pointer);

   status = sys$assign (&DeviceDsc, &DeviceChannel, 0, 0);
   if (Debug) fprintf (stdout, "sys$assign() %%X%08.08X\n", status);
   if (VMSnok (status)) return (status);

   memset (&DeviceFib, 0, sizeof(DeviceFib));
   DeviceFib.fib$w_exctl = FIB$C_EXA_QUOTA;
   DeviceFibDsc.buf_len = sizeof(DeviceFib);
   DeviceFibDsc.buf_addr = &DeviceFib;

   memset (&InQuota, 0, sizeof(InQuota));
   InQuotaDsc.buf_len = sizeof(InQuota);
   InQuotaDsc.buf_addr = &InQuota;
   InQuota.uic = UaiUic;

   memset (&OutQuota, 0, sizeof(OutQuota));
   OutQuotaDsc.buf_len = sizeof(OutQuota);
   OutQuotaDsc.buf_addr = &OutQuota;

   status = sys$qiow (0, DeviceChannel, IO$_ACPCONTROL, &IOsb, 0, 0,
                      &DeviceFibDsc, &InQuotaDsc, &OutLength, &OutQuotaDsc,
                      0, 0);
   if (Debug)
      fprintf (stdout, "sys$qiow() %%X%08.08X %%X%08.08X\n",
               status, IOsb.iosb$w_status);
   if (VMSok (status)) status = IOsb.iosb$w_status;

   sys$dassgn (DeviceChannel);

   if (status == SS$_NORMAL)
   {
      muptr->DiskQuotaPerm = OutQuota.perm;
      muptr->DiskQuotaUsed = OutQuota.used;
   }
   if (Debug)
      fprintf (stdout, "%d/%d\n",
               muptr->DiskQuotaPerm, muptr->DiskQuotaUsed);

   if (status == SS$_QFNOTACT) status = SS$_NORMAL;
   return (status);
}

/*****************************************************************************/
/*
The 'FileName' can be a fully-specified mail file name (e.g.
DEVICE:[DIRECTORY]MAIL.MAI) or a file name only (e.g. MAIL).  Build a
fully-specified mail file name from it in the supplied storage.  Any non-ODS-2
compliant characters are caret-escaped assuming it's an ODS-5 specification.
*/

int CallMailBuildMailFileName
(
VMS_MAIL_USER *muptr,
char *FileName,
char *MailFileName,
int MailFileNameSize
)
{
   char  *cptr, *sptr, *zptr;
   char  FileNameBuffer [256];

   /*********/
   /* begin */
   /*********/

   if (Debug) fprintf (stdout, "CallMailBuildMailFileName() |%s|\n", FileName);

   if (FileName == MailFileName)
   {
      /* it's destination is the same as the source storage */
      zptr = (sptr = FileNameBuffer) + sizeof(FileNameBuffer)-1;
      for (cptr = FileName; *cptr && sptr < zptr; *sptr++ = *cptr++);
      if (sptr >= zptr) ErrorExit (SS$_BUGCHECK, FI_LI);
      *sptr = '\0';
      FileName = FileNameBuffer;
   }

   zptr = (sptr = MailFileName) + MailFileNameSize - 1;
   for (cptr = FileName; *cptr && *cptr != ':'; cptr++);
   if (!*cptr)
   {
      /* build a file specification from a name-only */
      for (cptr = muptr->VmsMailFullDirectory;
           *cptr && sptr < zptr;
           *sptr++ = *cptr++);

      cptr = FileName;
      while (*cptr && sptr < zptr)
      {
         if (isalnum(*cptr) || *cptr == '$' || *cptr == '-' || *cptr == '_')
            *sptr++ = *cptr++;
         else
         {
            /* assume it's a EFS-compliant character requiring escaping */
            if (sptr < zptr) *sptr++ = '^';
            if (*cptr == ' ')
            {
               if (sptr < zptr) *sptr++ = '_';
               cptr++;
            }
            else
            if (sptr < zptr)
               *sptr++ = *cptr++;
         }
      }
   }
   else
   {
      /* fully specified file name */
      for (cptr = FileName; *cptr && sptr < zptr; *sptr++ = *cptr++);
   }
   *sptr = '\0';
   for (cptr = sptr;
        cptr > MailFileName && *cptr != ']' && *cptr != '.' &&
           *(USHORTPTR)(cptr-1) != '^.';
        cptr--);
   if (*cptr != '.')
   {
      /* add the default file type */
      for (cptr = ".MAI;"; *cptr && sptr < zptr; *sptr++ = *cptr++);
      *sptr = '\0';
   }
   if (sptr >= zptr) ErrorExit (SS$_BUGCHECK, FI_LI);
   if (Debug) fprintf (stdout, "%d |%s|\n", sptr-MailFileName, MailFileName);
   /* return it's length */
   return (sptr - MailFileName);
}

/****************************************************************************/
/*
This function retrieves the the mail file names in the user's Mail directory.
It places into the data area each null-terminated file name (i.e.
"FILE1\0FILE22\0"), and a count in 'MailFileListCount'.

CAUTION: RMS processing of these *.MAI to get the few mail files (as contrasted
to message files) can be VERY EXPENSIVE in disk activity and can introduce
noticable latency into folder listings.  This is why it has been done at this
low-level.  Can temporarily chew up a bit of memory.
*/ 

CallMailFileList (VMS_MAIL_USER *muptr)

{
   int  bytes, dfd, retval, rlen, status,
        EntryFlags,
        FileCount,
        NameLength,
        NewListSize;
   char  *bptr, *cptr, *dzptr, *rptr, *sptr, *zptr,
         *BufferPtr,
         *NamePtr;
   char  DirFileName [256+128];
   stat_t  StatBuffer;

   /*********/
   /* begin */
   /*********/

   if (Debug) fprintf (stdout, "CallMailFileList()\n");

   if (!muptr->VmsMailFullDirectoryLength) ErrorExit (SS$_BUGCHECK, FI_LI);

   /* a clumsy way of getting to the mail directory file via itself */
   zptr = (sptr = DirFileName) + sizeof(DirFileName)-1;
   for (cptr = muptr->VmsMailFullDirectory;
        *cptr && sptr < zptr-128;
        *sptr++ = *cptr++);
   *sptr = '\0';
   while (sptr > muptr->VmsMailFullDirectory && *sptr != ']') sptr--;
   if (*sptr != ']') ErrorExit (SS$_BUGCHECK, FI_LI);
   cptr = sptr;
   while (cptr > muptr->VmsMailFullDirectory && *cptr != '[')
   {
      if (*cptr == '.' && *(cptr-1) != '^') break;
      cptr--;
   }
   if (*cptr != '[' && *cptr != '.') ErrorExit (SS$_BUGCHECK, FI_LI);
   cptr++;
   *sptr++ = '.';
   *sptr++ = '-';
   *sptr++ = ']';
   while (*cptr != '.' && sptr < zptr)
   {
      if (*cptr == '^') cptr++;
      *sptr++ = *cptr++;
   }
   for (cptr = ".DIR"; *cptr && sptr < zptr; *sptr++ = *cptr++);
   if (sptr > zptr) ErrorExit (SS$_BUGCHECK, FI_LI);
   *sptr = '\0';
   if (Debug) fprintf (stdout, "DirMailFile |%s|\n", DirFileName);

   if (muptr->MailFileList.ListPtr)
   {
      CgiLibVeeMemFree (muptr->MailFileList.ListPtr);
      muptr->MailFileList.ListPtr = NULL;
      muptr->MailFileList.ListCount = muptr->MailFileList.ListLength =
         muptr->MailFileList.ListSize = 0;
   }

   if (stat (DirFileName, &StatBuffer) < 0)
   {
      status = vaxc$errno;
      if (Debug) fprintf (stdout, "stat() %%X%08.08X\n", status);
      return (status);
   }
   bytes = StatBuffer.st_size;
   if (Debug) fprintf (stdout, "%d/%d bytes/blocks\n", bytes, bytes/512);

   dfd = open (DirFileName, O_RDONLY, 0, "ctx=stm", "shr=get");
   if (!dfd)
   {
      status = vaxc$errno;
      if (Debug) fprintf (stdout, "open() %%X%08.08X\n", status);
      return (status);
   }

   BufferPtr =  CgiLibVeeMemCalloc (bytes);
   if (!BufferPtr)
   {
      status = vaxc$errno;
      if (Debug) fprintf (stdout, "calloc() %%X%08.08X\n", status);
      close (dfd);
      return (status);
   }

   retval = read (dfd, BufferPtr, bytes);
   if (Debug) fprintf (stdout, "retval: %d\n", retval);
   if (retval == -1)
   {
      status = vaxc$errno;
      if (Debug) fprintf (stdout, "read() %%X%08.08X\n", status);
      close (dfd);
      return (status);
   }
   close (dfd);

   rlen = 0;
   rptr = bptr = BufferPtr;

   for (;;)
   {
      rptr += rlen;
      /* records are always on even byte (word) boundaries */
      if ((int)rptr & 1) rptr++;
      for (;;)
      {
         /* directory files have no-span record attributes */
         rlen = (unsigned int)*(USHORTPTR)rptr;
         if (rlen != 0xffff) break;
         rptr = (bptr += 512);
         if (bptr >= BufferPtr + bytes) break;
      }
      if (bptr >= BufferPtr + bytes) break;
      rptr += sizeof(unsigned short);

      EntryFlags = *(unsigned char*)(rptr+2);
      if (EntryFlags & 0x07) continue;  /* not an fid-type entry */
      if ((EntryFlags & 0x38) > 0x08) continue;  /* not using ASCII chars */
      if (EntryFlags & 0x80) continue;  /* duplicate of previous entry */

      NameLength = *(unsigned char*)(rptr+3);
      NamePtr = rptr + 4;
      if (Debug)
         fprintf (stdout, "%d %02d %02d |%*.*s|\n",
                  (bptr-BufferPtr)/512+1, rlen,
                  NameLength, NameLength, NameLength, NamePtr);

      /* not much point proceding with this one */
      if (NameLength <= 4) continue;

      /* if it doesn't have a .MAI file type */
      cptr = NamePtr + NameLength - 4;
      if (*(ULONGPTR)cptr != '.MAI') continue;

      /* if it's an externally stored mail message */
      if (NameLength == 21+4 && !strncmp (NamePtr, "MAIL$", 5)) continue;

      /* terminate on the period (assigned above) */
      *cptr = '\0';

      NewListSize = muptr->MailFileList.ListLength + NameLength + 1;
      while (NewListSize > muptr->MailFileList.ListSize)
      {
         muptr->MailFileList.ListSize = NewListSize + 256;
         muptr->MailFileList.ListPtr =
            CgiLibVeeMemRealloc (muptr->MailFileList.ListPtr,
                                 muptr->MailFileList.ListSize);
         if (!muptr->MailFileList.ListPtr) ErrorExit (vaxc$errno, FI_LI);
      }

      sptr = muptr->MailFileList.ListPtr + muptr->MailFileList.ListLength;
      zptr = muptr->MailFileList.ListPtr + muptr->MailFileList.ListSize;
      for (cptr = NamePtr; *cptr && sptr < zptr; *sptr++ = *cptr++);
      if (sptr < zptr) *sptr++ = '\0';
      if (sptr >= zptr) ErrorExit (SS$_BUGCHECK, FI_LI);
      muptr->MailFileList.ListCount++;
      muptr->MailFileList.ListLength = sptr - muptr->MailFileList.ListPtr;
   }

   CgiLibVeeMemFree (BufferPtr);

   return (SS$_NORMAL);
}

/*****************************************************************************/
/*
This function retrieves the the mail file names in the user's Mail directory.
It places into the data area each null-terminated file name (i.e.
"FILE1\0FILE22\0"), and a count in 'MailFileListCount'.

CAUTION: Listing *.MAI to get the few mail files (as contrasted to message
files) can be VERY EXPENSIVE in disk activity and can introduce noticable
latency into folder listings.  This is why it should only be done on demand.
*/

#ifdef FAR_TOO_EXPENSIVE  /* see my implementation immediately above */

CallMailFileList (VMS_MAIL_USER *muptr)

{
   int  status,
        FileCount,
        NewListSize;
   char  *cptr, *dzptr, *sptr, *zptr;
   char  Directory [255+1],
         ExpFileName [255+1],
         ResFileName [255+1];
   struct FAB  SearchFab;
   struct NAM  SearchNam;

   /*********/
   /* begin */
   /*********/

   if (Debug) fprintf (stdout, "CallMailFileList()\n");

   if (!muptr->VmsMailFullDirectoryLength) ErrorExit (SS$_BUGCHECK, FI_LI);

   if (muptr->MailFileList.ListPtr)
   {
      CgiLibVeeMemFree (muptr->MailFileList.ListPtr);
      muptr->MailFileList.ListPtr = NULL;
      muptr->MailFileList.ListCount = muptr->MailFileList.ListLength =
         muptr->MailFileList.ListSize = 0;
   }

   SearchFab = cc$rms_fab;
   SearchFab.fab$l_dna = muptr->VmsMailFullDirectory;
   SearchFab.fab$b_dns = muptr->VmsMailFullDirectoryLength;
   SearchFab.fab$l_fna = "*.MAI;";
   SearchFab.fab$b_fns = 6;
   SearchFab.fab$l_nam = &SearchNam;

   SearchNam = cc$rms_nam;
   SearchNam.nam$l_esa = ExpFileName;
   SearchNam.nam$b_ess = sizeof(ExpFileName)-1;
   SearchNam.nam$l_rsa = ResFileName;
   SearchNam.nam$b_rss = sizeof(ResFileName)-1;

   if (VMSnok (status = sys$parse (&SearchFab, 0, 0)))
      return (status);
   SearchNam.nam$l_ver[SearchNam.nam$b_ver] = '\0';
   if (Debug) fprintf (stdout, "|%s|\n", ExpFileName);

   FileCount = 0;
   while (VMSok (status = sys$search (&SearchFab, 0, 0)))
   {
      SearchNam.nam$l_ver[SearchNam.nam$b_ver] = '\0';
      if (Debug) fprintf (stdout, "%d |%s|\n", FileCount, ResFileName);

      /* if it's an externally stored mail message */
      if (SearchNam.nam$b_name == 21 &&
          !strncmp (SearchNam.nam$l_name, "MAIL$", 5)) continue;

      SearchNam.nam$l_name[SearchNam.nam$b_name] = '\0';
      if (Debug) fprintf (stdout, "|%s|\n", SearchNam.nam$l_name);

      NewListSize = muptr->MailFileListLength + strlen(SearchNam.nam$l_name)+1;
      while (NewListSize > muptr->MailFileListSize)
      {
         muptr->MailFileList.ListSize += 1024;
         muptr->MailFileList.ListPtr =
            CgiLibVeeMemRealloc (muptr->MailFileList.ListPtr,
                                 muptr->MailFileList.ListSize);
         if (!muptr->MailFileList.ListPtr) ErrorExit (vaxc$errno, FI_LI);
      }

      sptr = muptr->MailFileList.ListPtr + muptr->MailFileList.ListLength;
      zptr = muptr->MailFileList.ListPtr + muptr->MailFileList.ListSize;
      for (cptr = SearchNam.nam$l_name;
           *cptr && sptr < zptr;
           *sptr++ = *cptr++);
      if (sptr < zptr) *sptr++ = '\0';
      if (sptr >= zptr) ErrorExit (SS$_BUGCHECK, FI_LI);
      muptr->MailFileList.ListCount++;
      muptr->MailFileList.ListLength = sptr - muptr->MailFileList.ListPtr;

      SearchNam.nam$l_name[SearchNam.nam$b_name] = '.';
   }
   if (status == RMS$_FNF || status == RMS$_NMF) status = SS$_NORMAL;
   if (VMSnok (status))
   {
      /* ensure the list is empty in the case of an error */
      if (muptr->MailFileList.ListPtr) muptr->MailFileList.ListPtr[0] = '\0';
      muptr->MailFileList.ListLength = muptr->MailFileList.ListCount = 0;
   }
   return (status);
}

#endif  /* FAR_TOO_EXPENSIVE */

/*****************************************************************************/
/*
Supplied with a file name construct the full specification based on the user's
Mail directory and return a pointer to that.  This function massages the
file name to be ODS-2 compliant.
*/

char* CallMailFileIn
(
VMS_MAIL_USER *muptr,
char *NamePrefix,
char *FileName
)
{
   /* some elbow room so we can do things like 'strcat(ptr,";-3")' */
   static unsigned long  UaiContext = -1;
   static char  FullFileName [256+16],
                UaiDefDev [1+31+1],
                UaiDefDir [1+63+1];
   static VMS_ITEM_LIST3  UaiItems [] = 
   {
      { sizeof(UaiDefDev)-1, UAI$_DEFDEV, &UaiDefDev, 0 },
      { sizeof(UaiDefDir)-1, UAI$_DEFDIR, &UaiDefDir, 0 },
      { 0,0,0,0 }
   };


   int  status,
        VolumeStruct;
   char  *cptr, *pptr, *sptr, *zptr;
   $DESCRIPTOR (UserNameDsc, "");

   /*********/
   /* begin */
   /*********/

   if (Debug) fprintf (stdout, "CallMailFileIn() |%s|\n", FileName);

   zptr = (sptr = FullFileName) + 255;
   *sptr = '\0';

   if (!strncmp (FileName, "SYS$LOGIN:", 10) ||
       !strncmp (FileName, "sys$login:", 10))
   {
      /* use SYSUAF-defined home directory */
      FileName += 10;

      UserNameDsc.dsc$a_pointer = muptr->UserName;
      UserNameDsc.dsc$w_length = muptr->UserNameLength;

      status = sys$getuai (0, &UaiContext, &UserNameDsc, &UaiItems, 0, 0, 0);
      if (Debug) fprintf (stdout, "sys$getuai() %%X%08.08X\n", status);
      if (VMSnok (status)) ErrorExit (status, FI_LI);

      UaiDefDev[UaiDefDev[0]+1] = '\0';
      UaiDefDir[UaiDefDir[0]+1] = '\0';

      for (cptr = UaiDefDev+1; *cptr && sptr < zptr; *sptr++ = *cptr++);
      for (cptr = UaiDefDir+1; *cptr && sptr < zptr; *sptr++ = *cptr++);
      *sptr = '\0';

      VolumeStruct = CallMailVolumeStruct (FullFileName);
   }
   else
   if (!strchr (FileName, ':'))
   {
      /* does not contain device/logical delimitting colon */
      for (cptr = muptr->VmsMailFullDirectory;
           *cptr && sptr < zptr;
           *sptr++ = *cptr++);
      *sptr = '\0';
      VolumeStruct = CallMailVolumeStruct (FullFileName);
   }
   else
      VolumeStruct = CallMailVolumeStruct (FileName);

   if (NamePrefix)
      for (cptr = NamePrefix; *cptr && sptr < zptr; *sptr++ = *cptr++);

   if (FileName)
   {
      pptr = NULL;
      for (cptr = FileName; *cptr && sptr < zptr; cptr++)
      {
         if (*cptr == '.') pptr = sptr;
         if (isspace(*cptr))
            *sptr++ = '_';
         else
         if (VolumeStruct == 5)
            *sptr++ = *cptr;
         else
         if (isalnum(*cptr))
            *sptr++ = *cptr;
         else
            *sptr++ = '_';
      }
      if (VolumeStruct != 5 && pptr) *pptr = '.';
   }

   if (sptr >= zptr) ErrorExit (SS$_BUGCHECK, FI_LI);
   *sptr = '\0';
   if (Debug) fprintf (stdout, "FullFileName |%s|\n", FullFileName);
   return (FullFileName);
}

/*****************************************************************************/
/*
Generates and returns a 32 bit hash of the major components of the VMS mail
message.  This is intended to help ensure that the message specified by
the 'MessageId' number is indeed the message intended.
*/

unsigned int CallMailHeaderHash (VMS_MAIL_MSG *vmptr)

{
   unsigned int  hash32;

   /*********/
   /* begin */
   /*********/

   CallMailElfHash (NULL, 0);  /* initialize */
   CallMailElfHash (vmptr->ExtId, vmptr->ExtIdLength);
   CallMailElfHash (vmptr->Date, vmptr->DateLength);
   CallMailElfHash (vmptr->From, vmptr->FromLength);
   CallMailElfHash (vmptr->To, vmptr->ToLength);
   hash32 = CallMailElfHash (vmptr->Subject, vmptr->SubjectLength);

   return (hash32);
}

/****************************************************************************/
/*
Hashing function that allows the hash to be developed over multiple calls. It
must be initialized before each hash generation with a CallMailElfHash(NULL,0).
Based on code by Arash Partow. 
*/ 

unsigned int CallMailElfHash
(
char *str,
int len
)
{
   static unsigned int  hash;

   unsigned int  x, i;

   /*********/
   /* begin */
   /*********/

   /* initialize */
   if (!str) return (hash = 0);

   for (i = 0; i < len; str++, i++)
   {
      hash = (hash << 4) + (*str);
      if ((x = hash & 0xF0000000L) != 0)
      {
         hash ^= (x >> 24);
         hash &= ~x;
      }
   }

   return (hash & 0x7FFFFFFF);
}

/****************************************************************************/
/*
Get the volume's underlying file system (ODS-2 or ODS-5), by sys$getdvi() the
ACPTYPE for the device.  Return 5 if ODS-5 (EFS), 2 if ODS-2, zero on error or
anything else.
*/ 

/* if pre-7.2 Alpha then define this */
#ifndef DVI$C_ACP_F11V5
#define DVI$C_ACP_F11V5 11
#endif

int CallMailVolumeStruct (char *DeviceName)

{
   static int  DevOds;
   static unsigned long  DevAcpType,
                         DevChar;
   static $DESCRIPTOR (DevNameDsc, "");
   static VMS_ITEM_LIST3  AcpTypeItemList [] = 
   {
      { sizeof(DevAcpType), DVI$_ACPTYPE, &DevAcpType, 0 },
      { sizeof(DevChar), DVI$_DEVCHAR, &DevChar, 0 },
      { 0, 0, 0, 0 }
   };

   int  status;
   char  *cptr;
   struct {
      unsigned short  Status;
      unsigned short  Count;
      unsigned long  Unused;
   } IOsb;

   /*********/
   /* begin */
   /*********/

   if (Debug) fprintf (stdout, "CallMailVolumeStruct()\n");

   for (cptr = DeviceName; *cptr && *cptr != ':'; cptr++);
   /* if we can't find a device in it */
   if (!*cptr) return (0);

   DevNameDsc.dsc$a_pointer = DeviceName;
   DevNameDsc.dsc$w_length = cptr - DeviceName;

   DevChar = DevAcpType = 0;

   status = sys$getdviw (0, 0, &DevNameDsc, &AcpTypeItemList,
                         &IOsb, 0, 0, 0);
   if (Debug)
      fprintf (stdout, "sys$getdviw() |%*s| %%X%08.08X %%X%08.08X %d\n",
               DevNameDsc.dsc$w_length, DevNameDsc.dsc$a_pointer,
               status, IOsb.Status, DevAcpType);

   if (VMSok (status)) status = IOsb.Status;
   if (VMSnok (status)) return (0); 
   /* if volume not mounted */
   if (!(DevChar & DEV$M_MNT)) return (0);
   if (DevAcpType == DVI$C_ACP_F11V5) return (5);
   if (DevAcpType == DVI$C_ACP_F11V2) return (2);
   return (0);
}

/*****************************************************************************/
/*
This function retrieves the folder names from the PMDF IMAP mailbox.  This is a
stream-LF file with newline separated folder names.  See description in module
prologue.  It places into the data area each null-terminated folder name, with
the list itself terminated by an empty name (i.e. "FOLDER1\0FOLDER2\0\0").
Return status values with the MSB set (inhib msg) to indicate to error
reporting it's from the IMAP routine.
*/

int CallMailPmdfImapMailBox (VMS_MAIL_USER *muptr)

{
   int  bytes, retval, status,
        FolderNameLength,
        ImapTextSize,
        NewListSize;
   char  *cptr, *itptr, *sptr, *zptr,
         *ImapTextPtr,
         *FolderNamePtr;
   char  FileName [256];
   FILE  *fp;
   stat_t  StatBuffer;

   /*********/
   /* begin */
   /*********/

   if (Debug) fprintf (stdout, "CallMailPmdfImapMailBox()\n");

   zptr = (sptr = FileName) + sizeof(FileName)-1;
   for (cptr = muptr->VmsMailFullDirectory;
        *cptr && sptr < zptr;
        *sptr++ = *cptr++);
   for (cptr = "PMDF_IMAP.MAILBOX"; *cptr && sptr < zptr; *sptr++ = *cptr++);
   if (sptr > zptr) ErrorExit (SS$_BUGCHECK, FI_LI);
   *sptr = '\0';
   if (Debug) fprintf (stdout, "|%s|\n", FileName);

   if (stat (FileName, &StatBuffer) < 0)
   {
      status = vaxc$errno;
      if (Debug) fprintf (stdout, "stat() %%X%08.08X\n", status);
      return (status | 0x80000000);
   }
   bytes = StatBuffer.st_size;
   if (Debug) fprintf (stdout, "%d/%d bytes/blocks\n", bytes, bytes/512);

   ImapTextSize = bytes;
   ImapTextPtr =  CgiLibVeeMemCalloc (ImapTextSize+128);
   if (!ImapTextPtr) return (vaxc$errno | 0x80000000);

   fp = fopen (FileName, "r", "ctx=bin", "shr=get");
   status = vaxc$errno;
   if (Debug) fprintf (stdout, "fopen() %%X%08.08X\n", status);
   if (!fp) return (vaxc$errno | 0x80000000);
   retval = fread (ImapTextPtr, 1, ImapTextSize, fp);
   status = vaxc$errno;
   if (Debug) fprintf (stdout, "fread() %d %%X%08.08X\n", retval, status);
   if (Debug) fprintf (stdout, "|%s|\n", ImapTextPtr);
   fclose (fp);
   if (!retval) return (status | 0x80000000);

   itptr = ImapTextPtr;
   while (*itptr)
   {
      FolderNamePtr = cptr = itptr;
      while (*cptr &&
             *(USHORTPTR)cptr != '= ' &&
             *(USHORTPTR)cptr != '! ') cptr++;
      if (!*cptr) break;
      if (*(USHORTPTR)cptr == '! ')
      {
         /* trashed but not deleted folder */
         while (*cptr && *cptr != '\n') cptr++;
         if (*cptr) cptr++;
         itptr = cptr;
         continue;
      }
      if (!(FolderNameLength = cptr - FolderNamePtr)) break;
      *cptr++ = '\0';
      if (Debug) fprintf (stdout, "|%s|\n", FolderNamePtr);
      while (*cptr && *cptr != '\n') cptr++;
      if (*cptr) cptr++;
      itptr = cptr;

      NewListSize = muptr->TempFolderList.ListLength + FolderNameLength;
      while (NewListSize > muptr->TempFolderList.ListSize)
      {
         muptr->TempFolderList.ListSize = NewListSize + 256;
         muptr->TempFolderList.ListPtr = CgiLibVeeMemRealloc (muptr->TempFolderList.ListPtr,
                                             muptr->TempFolderList.ListSize);
         if (!muptr->TempFolderList.ListPtr) ErrorExit (vaxc$errno, FI_LI);
      }

      sptr = muptr->TempFolderList.ListPtr + muptr->TempFolderList.ListLength;
      for (cptr = FolderNamePtr; *cptr; *sptr++ = *cptr++);
      *sptr = '\0';
      muptr->TempFolderList.ListLength += FolderNameLength + 1;
      muptr->TempFolderList.ListCount++;
   }

   return (SS$_NORMAL);
}

/*****************************************************************************/
/*
Generate a full VMS Mail file specification (source or destination) by using
the PMDF_IMAP.MBXDIR indexed file to lookup the directory associated with the
PMDF subfolder.  See description in module prologue.
*/

int CallMailPmdfImapMbxDir
(
VMS_MAIL_USER *muptr,
char *FileName,
char *MailFileName,
int MailFileNameSize
)
{
   int  retval, status;
   char  *cptr, *czptr, *sptr, *zptr;

   char  ImapName [256],
         MbxDirFileName [256],
         RecordBuffer [256];
   FILE  *fp;

   /*********/
   /* begin */
   /*********/

   if (Debug) fprintf (stdout, "CallMailPmdfImapMbxDir() |%s|\n", FileName);

   /* buffer to remove the indicative trailing PMDF IMAP trailing slash */
   zptr = (sptr = ImapName) + sizeof(ImapName)-1;
   for (cptr = FileName; *cptr && sptr < zptr; *sptr++ = *cptr++);
   if (sptr > FileName && *(sptr-1) == '/') sptr--;
   *sptr = '\0';

   zptr = (sptr = MbxDirFileName) + sizeof(MbxDirFileName)-1;
   for (cptr = muptr->VmsMailFullDirectory;
        *cptr && sptr < zptr;
        *sptr++ = *cptr++);
   for (cptr = "PMDF_IMAP.MBXDIR"; *cptr && sptr < zptr; *sptr++ = *cptr++);
   if (sptr > zptr) ErrorExit (SS$_BUGCHECK, FI_LI);
   *sptr = '\0';
   if (Debug) fprintf (stdout, "|%s|\n", MbxDirFileName);

   fp = fopen (MbxDirFileName, "r", "ctx=rec", "shr=get");
   status = vaxc$errno;
   if (Debug) fprintf (stdout, "fopen() %%X%08.08X\n", status);
   if (!fp)
   {
      MailFileName[0] = '\0';
      return (0);
   }

   for (;;)
   {
      retval = fread (RecordBuffer, sizeof(RecordBuffer), 1, fp);
      if (Debug)
         fprintf (stdout, "fread() %d %%X%08.08X\n", retval, vaxc$errno);
      if (retval != 1) break;
      if (Debug) WatchDump (RecordBuffer, sizeof(RecordBuffer));
      if (!strcmp (RecordBuffer, ImapName)) break;
   }

   fclose (fp);

   if (retval == 1)
   {
      /* hit! */
      zptr = (sptr = MailFileName) + MailFileNameSize-1;
      for (cptr = muptr->VmsMailFullDirectory;
           *cptr && *cptr != ':' && sptr < zptr;
           *sptr++ = *cptr++);
      if (sptr < zptr) *sptr++ = ':';
      for (cptr = RecordBuffer+128; *cptr && sptr < zptr; *sptr++ = *cptr++);
      for (cptr = "MAIL.MAI"; *cptr && sptr < zptr; *sptr++ = *cptr++);
      if (sptr > zptr) ErrorExit (SS$_BUGCHECK, FI_LI);
      *sptr = '\0';
      if (Debug) fprintf (stdout, "|%s|\n", MailFileName);
      return (sptr - MailFileName);
   }

   MailFileName[0] = '\0';
   return (0);
}

/*****************************************************************************/
/*
Given the folder list (i.e. "FOLDER1\0FOLDER2\0\0") sort this into descending
order and eliminate duplicate entries.  Normally a folder list generated by
callable Mail does not require this additional step but when a PMDF IMAP list
is used in addition to callable Mail sorting and reduction is required.
*/

int CallMailPmdfImapSort (VMS_MAIL_USER *muptr)

{
   int  idx1, idx2,
        ListCount;
   char  *cptr, *sptr, *zptr,
         *ListPtr;
   char  **SortArray;

   /*********/
   /* begin */
   /*********/

   if (Debug) fprintf (stdout, "CallMailPmdfImapSort()\n");

   /* buffer this, to be disposed of shortly */
   ListPtr = muptr->TempFolderList.ListPtr;

   /* generate an array of pointers to the folder names */
   *(&SortArray) = CgiLibVeeMemCalloc (muptr->TempFolderList.ListCount * sizeof(char*));
   if (!*(&SortArray)) ErrorExit (vaxc$errno, FI_LI);
   cptr = ListPtr;
   for (idx1 = 0; idx1 < muptr->TempFolderList.ListCount; idx1++)
   {
      SortArray[idx1] = cptr;
      while (*cptr) cptr++;
      cptr++;
   }

   /* sort the pointers (based on the strings they point to) */
   qsort (*(&SortArray), muptr->TempFolderList.ListCount,
          sizeof(char*), &CallMailPmdfImapSortCmp);

   /* allocate space for the sorted strings */
   muptr->TempFolderList.ListPtr =  CgiLibVeeMemCalloc (muptr->TempFolderList.ListSize);
   if (!muptr->TempFolderList.ListPtr) return (vaxc$errno);

   /* copy the strings to new storage using the sorted pointers */
   zptr = (sptr = muptr->TempFolderList.ListPtr) + muptr->TempFolderList.ListLength+1;
   idx1 = ListCount = 0;
   while (idx1 < muptr->TempFolderList.ListCount)
   {
      for (cptr = SortArray[idx1]; *cptr && sptr < zptr; *sptr++ = *cptr++);
      *sptr++ = '\0';
      ListCount++;
      /* eliminate duplicates by comparing ahead */
      for (idx2 = idx1 + 1; idx2 < muptr->TempFolderList.ListCount; idx2++)
         if (strcmp (SortArray[idx1], SortArray[idx2])) break;
      idx1 = idx2;
   }
   *sptr = '\0';
   if (sptr >= zptr) ErrorExit (SS$_BUGCHECK, FI_LI);

   muptr->TempFolderList.ListLength = sptr - muptr->TempFolderList.ListPtr;
   muptr->TempFolderList.ListCount = ListCount;

   CgiLibVeeMemFree (ListPtr);
   CgiLibVeeMemFree (*(&SortArray));

   return (SS$_NORMAL);
}

/*****************************************************************************/
/*
Yes, it's a bit messy!  The arguments are pointers to (pointers to char).
*/

int CallMailPmdfImapSortCmp
(
const void *ptr1,
const void *ptr2
)
{
   char  *str1, *str2;

   /*********/
   /* begin */
   /*********/

   str1 = *(char**)ptr1;
   str2 = *(char**)ptr2;

/**
   if (Debug)
      fprintf (stdout, "CallMailPmdfImapSortCmp() |%s|%s|\n", str1, str2);
**/

   /* case-insensitive compare */
   while (*str1 && *str2 && tolower(*str1) == tolower(*str2))
   {
      str1++;
      str2++;
   }
   return (tolower(*str1) - tolower(*str2));
}

/*****************************************************************************/

