/*
 * Program:	VMS mail routines
 *
 * Author:	Yehavi Bourvine, The Hebrew University of Jerusalem.
 *		Internet: Yehavi@VMS.huji.ac.il
 *
 * Modified:	Andy Harper, Kings College London
 *		Most of the original code has been rewritten but I've kept all
 *		acknowledgement notices anyway.
 *
 * Date:	2 August 1994
 * Last Edited: 7 October 1997
 *
 * Copyright 1993 by the University of Washington
 *
 *  Permission to use, copy, modify, and distribute this software and its
 * documentation for any purpose and without fee is hereby granted, provided
 * that the above copyright notice appears in all copies and that both the
 * above copyright notice and this permission notice appear in supporting
 * documentation, and that the name of the University of Washington not be
 * used in advertising or publicity pertaining to distribution of the software
 * without specific, written prior permission.  This software is made
 * available "as is", and
 * THE UNIVERSITY OF WASHINGTON DISCLAIMS ALL WARRANTIES, EXPRESS OR IMPLIED,
 * WITH REGARD TO THIS SOFTWARE, INCLUDING WITHOUT LIMITATION ALL IMPLIED
 * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE, AND IN
 * NO EVENT SHALL THE UNIVERSITY OF WASHINGTON BE LIABLE FOR ANY SPECIAL,
 * INDIRECT OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
 * LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, TORT
 * (INCLUDING NEGLIGENCE) OR STRICT LIABILITY, ARISING OUT OF OR IN CONNECTION
 * WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
 *
 */

#include <stdio.h>
#include <types.h>
#include <errno.h>
#include <signal.h>
#include "mail.h"
#include "osdep.h"
#include <sys/file.h>
#include <sys/stat.h>
#include "vms_mail.h"
#include "rfc822.h"
#include "misc.h"
#include "dummy.h"
#include <ctype.h>
#include <stdlib.h>
#include <string.h>
#include <descrip.h>
#include <starlet.h>
#include <rms.h>
#include <fdldef.h>

int fdl$create ();
int fdl$generate ();

/*
 - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
 - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
*/

/*
  VMS debugging code
*/
#ifdef LOGTRACE
# define DBGPRINT(level,fmt,a,b,c,d) \
  {char *dbg = getenv("CCLIENT_LOGLEVEL"); \
   if (dbg && (atoi(dbg) >= level)) \
     {char tmpmsg[256];sprintf(tmpmsg,fmt,a,b,c,d); mm_dlog(tmpmsg); \
     } \
  }
#else
# define DBGPRINT(level,fmt,a,b,c,d) {}
#endif



/* CCLIENT DEBUG LEVELS */

#define DBG$ENTRY	21
#define DBG$EXIT	21
#define DBG$SYSSTATUS	23
#define DBG$SHOWMORE	25

/*
 - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
   MAIL$ definitions which are not documented or in include files
 - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
*/

					/* Callable MAIL status codes */

#define MAIL$_NOTEXIST  0x007E80DA	/* MAIL - folder does not exist */
#define MAIL$_MSGTEXT   0x007EFD01	/* MAIL - text record returned */
#define MAIL$_NOMOREREC 0x007EDFA8	/* MAIL - No more records */


					/* Subscription database name */
#define MBXDATABASE ".MAILBOXLIST"

					/* Useful MACROS */
#define STRLIM(s) (s>65 ? 65 : s)
#define STREXT(s) (s>65 ? "..." : "")

					/* Structures */

struct MailBoxList			/* For 'created' mail boxes */
  {
    struct MailBoxList *next;
    char *mailfile;
    char *folder;
  };

struct FolderPattern			/* For folder scanning */
  {
    int count;				/* Incremented if name matches */
    char *mailfile;			/* mailfile being scanned */
    char *prefix;			/* Prefix for folder pattern */
    char *pattern;			/* Folder name pattern */
  };


					/* Abbreviation for item lists */
typedef struct ITEM_LIST item;

					/* Global variables */
static struct MailBoxList *MailBoxes = NULL;
static item   NullItemList[] = {{0, 0, NULL, NULL}};

static char alphanum[] =
              "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789";

/* Driver dispatch used by MAIL */

DRIVER vmsmaildriver = {
  "vms_mail",			/* driver name */
  (DRIVER *) NIL,		/* next driver */
  vms_mail_valid,		/* mailbox is valid for us */
  vms_mail_parameters,		/* manipulate parameters */
  vms_mail_find,		/* find mailboxes */
  vms_mail_find_bboards,	/* find bboards */
  vms_mail_find_all,		/* find all mailboxes */
  vms_mail_find_all_bboards,	/* find all bboards */
  vms_mail_subscribe,		/* subscribe to mailbox */
  vms_mail_unsubscribe,		/* unsubscribe from mailbox */
  vms_mail_subscribe_bboard,	/* subscribe to bboard */
  vms_mail_unsubscribe_bboard,	/* unsubscribe from bboard */
  vms_mail_create,		/* create mailbox */
  vms_mail_delete,		/* delete mailbox */
  vms_mail_rename,		/* rename mailbox */
  vms_mail_open,		/* open mailbox */
  vms_mail_close,		/* close mailbox */
  vms_mail_fetchfast,		/* fetch message "fast" attributes */
  vms_mail_fetchflags,		/* fetch message flags */
  vms_mail_fetchstructure,	/* fetch message envelopes */
  vms_mail_fetchheader,		/* fetch message header only */
  vms_mail_fetchtext,		/* fetch message body only */
  vms_mail_fetchbody,		/* fetch message body section */
  vms_mail_setflag,		/* set message flag */
  vms_mail_clearflag,		/* clear message flag */
  vms_mail_search,		/* search for message based on criteria */
  vms_mail_ping,		/* ping mailbox to see if still alive */
  vms_mail_check,		/* check for new messages */
  vms_mail_expunge,		/* expunge deleted messages */
  vms_mail_copy,		/* copy messages to another mailbox */
  vms_mail_move,		/* move messages to another mailbox */
  vms_mail_append,		/* append string message to mailbox */
  vms_mail_gc			/* garbage collect stream */
};

				/* prototype stream */
MAILSTREAM vmsmailproto = {&vmsmaildriver};
                                /* standard driver's prototype */
MAILSTREAM *mailstd_proto = &vmsmailproto;

/*
 - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
    Purpose:
	Simplify system status checking. Check the status message and print a
	message if it indicates an error. Exit with this status if the abort
        flag is set, otherwise return with LSB of the status.

    Input:
	message		message to display (often name of routine)
	abort		if T then abort, if NIL then just return
	status		status code to display

    Output:
	NONE

    Return Status:
	LSB of status code (1=ok, 0=error)

    Author:
 	Andy Harper, Kings College London, OCT 1996
 - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
*/
static int chkstat(char *message, int abort, int status)
  {
     DBGPRINT(DBG$SYSSTATUS,"%s: exit, status=0x%08x", message, status, 0, 0);
     if ((status & 0x1) == 0)
       {
          char errmess[256];
          sprintf(errmess, "%s: failed with status = 0x%08x", message, status);
          mm_dlog(errmess);

          if (abort)
            { fprintf(stderr,"%s\n", errmess); exit(status); }
       }

     return status & 0x1;
  }

/*
 - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
    Purpose:
	Get the default MAIL directory for the current user

    Input:
	NONE

    Output:
	Mail		Pointer to mail directory buffer pointer; if NULL
			nothing returned here.

    Return Value:
	Mail directory buffer pointer

    Comments:
	Remember what we found on the first successfull call and don't call
	the system routines again if called again.

    Author:
	Andy Harper, Kings College London, OCT 1996

 - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
*/

static char *__get_mail_directory(char **Mail)
{
  static char MailDirectory[MAILFILESIZE];
  static int PrevCall = 0;

  DBGPRINT(DBG$ENTRY,"__get_mail_directory: entered, user=[%s]",myusername(),0,0,0);

  if (!PrevCall)
    {
      int UserContext=0;
      int length=0;
      int status;

      item InItemList[] = {
	{ strlen(myusername()), MAIL$_USER_USERNAME, myusername(), NULL},
	{ 0, 0, NULL, NULL }};

      item OutItemList[] = {
	{ MAILFILESIZE-1, MAIL$_USER_FULL_DIRECTORY, MailDirectory, &length},
	{ 0, 0, NULL, NULL }};


					/* Quiz MAIL for the directory */
      status = mail$user_begin(&UserContext, NullItemList, NullItemList);
      if (!chkstat("__get_mail_directory: mail$user_begin", 0, status) ) return NULL;
      status = mail$user_get_info(&UserContext, InItemList, OutItemList);
      if (!chkstat("__get_mail_directory: mail$user_get_info", 0, status) ) return NULL;
      status = mail$user_end(&UserContext, NullItemList, NullItemList);
      if (!chkstat("__get_mail_directory: mail$user_end", 0, status) ) return NULL;

					/* Terminate string, store pointer */
      MailDirectory[length] = '\0';
      PrevCall = 1;
    }
  if (Mail) *Mail = MailDirectory;

  DBGPRINT(DBG$EXIT,"__get_mail_directory: exited, directory='%s'",MailDirectory,0,0,0);
  return MailDirectory;
}

/*
 - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

   Purpose:
	Get the login directory (SYS$LOGIN) for the current user

   Input:
	NONE

   Output:
	hp		Pointer to home directory buffer pointer; if NULL
			nothing is returned here.

   Return Value:
	home directory buffer pointer

   Comments:
	Remember what we found on the first call and don't call the system
	routines again if called again.

   Author:
	Andy Harper, Kings College London, 4-OCT-1996

 - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
*/
static char *__get_home_directory(char **hp)
  {
     static int PrevCall = 0;
     static char home[100];

     DBGPRINT(DBG$ENTRY,"__get_home_directory: entered, user=[%s]", myusername(),0,0,0);

     if (!PrevCall)
       {
         char directory[64];
         char device[32];
         int status;

         struct DESC usernameD =
            {strlen(myusername()), 0, myusername()};

         item UserInfo[] = {
	    {sizeof(device),       UAI$_DEFDEV,  device,     0},
	    {sizeof(directory),    UAI$_DEFDIR,  directory,  0},
            {0,                    0,            0,          0}};


 					/* Get directory from UAF */
         status = sys$getuai(0,0,&usernameD, UserInfo,0,0,0);
         if (!chkstat("__get_home_directory: sys$getuai", 0, status) ) return NULL;


					/* Convert to null terminated string */
         sprintf(home,"%.*s%.*s",device[0],device+1,directory[0],directory+1);
         PrevCall = 1;
       }
     if (hp) *hp = home;

     DBGPRINT(DBG$EXIT,"__get_home_directory: exit, directory=[%s]",home,0,0,0);
     return home;
}

/*
 - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
   Purpose:
	Set message flags in the elt and the cache

   Inputs:
	stream		The mail stream for the mailbox
	i		The message number
	elt		Pointer to the elt for the message
	f		New flags to set

   Output:
	NONE

   Return Value:
	NONE

   Author:
	Andy Harper, Kings College London, Jan 1997

 - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
*/
static void __setflag(MAILSTREAM *stream, long i, MESSAGECACHE *elt, short f)
  {
      FILECACHE *fc = &LOCAL->msgs[i-1];
      DBGPRINT(DBG$ENTRY,"__setflag: entered, msgno='%ld', set = '0x%08x'", i, f, 0, 0); 

      if (f&fDELETED)  {elt->deleted  = T; fc->flags |= fDELETED;}
      if (f&fSEEN)     {elt->seen     = T; fc->flags |= fSEEN;}
      if (f&fFLAGGED)   elt->flagged  = T;
      if (f&fANSWERED)  elt->answered = T;

      DBGPRINT(DBG$EXIT,"__setflag: exit", 0, 0, 0, 0);
  }

/*
 - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
   Purpose:
	Clear message flags in the elt and the cache

   Inputs:
	stream		The mail stream for the mailbox
	i		The message number
	elt		Pointer to the elt for the message
	f		New flags to clear

   Output:
	NONE

   Return Value:
	NONE

   Author:
	Andy Harper, Kings College London, Jan 1997

 - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
*/
static void __clearflag(MAILSTREAM *stream, long i, MESSAGECACHE *elt, short f)
  {
      FILECACHE *fc = &LOCAL->msgs[i-1];
      DBGPRINT(DBG$ENTRY,"__clearflag: entered, msgno='%ld', clear = '0x%08x'", i, f, 0, 0); 

      if (f&fDELETED)  {elt->deleted  = NIL; fc->flags &= ~fDELETED;}
      if (f&fSEEN)     {elt->seen     = NIL; fc->flags &= ~fSEEN;}
      if (f&fFLAGGED)   elt->flagged  = NIL;
      if (f&fANSWERED)  elt->answered = NIL;

      DBGPRINT(DBG$EXIT,"__clearflag: exit", 0, 0, 0, 0);
  }

/*
 - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
   Purpose:
	Update the new mail count by subtracting, from the current count, the
	number of messages seen

   Input:
	MessagesSeen		Number of messages seen

   Output:
	NONE

   Return Value:
	NONE

   Author:
	Andy Harper, Kings College London, Jan 1997

 - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
 */
static void __update_newmail_count(int MessagesSeen)
{
/*
  char username[256];
  char password[256];
  char host[256];
*/
  short Count;
  int status;
  int UserContext=0;

  item OldMessageUser[] = {
	{ 0,             MAIL$_USER_USERNAME,         NULL,      NULL},
	{ 0, 0, NULL, NULL }};

  item OldMessageCount[] = {
	{ sizeof(Count), MAIL$_USER_NEW_MESSAGES,     &Count,    NULL},
	{ 0, 0, NULL, NULL }};

  item NewMessageCount[] = {
	{ 0,             MAIL$_USER_USERNAME,         NULL,      NULL},
	{ sizeof(Count), MAIL$_USER_SET_NEW_MESSAGES, &Count,    NULL},
	{ 0, 0, NULL, NULL }};

  DBGPRINT(DBG$ENTRY, "__update_newmail_count: entered, user=[%s], Messages seen = [%d]", myusername(), MessagesSeen, 0, 0);

		/* Make sure we update the count for the current username */  
  OldMessageUser[0].buffer = NewMessageCount[0].buffer = myusername();
  OldMessageUser[0].length = NewMessageCount[0].length = strlen(myusername());

/*
  mm_login(host, username, password, -1L);
  OldMessageUser[0].length = strlen(username);
  NewMessageCount[0].length = strlen(username);
*/
		/* Get current count and update by specified amount */
  status = mail$user_begin(&UserContext, NullItemList, NullItemList);
  if (!chkstat("__update_newmail_count: mail$user_begin", 0, status) ) return;

  status = mail$user_get_info(&UserContext, OldMessageUser, OldMessageCount);
  if (!chkstat("__update_newmail_count: mail$user_get_info", 0, status) ) return;
  DBGPRINT(DBG$SHOWMORE,"__update_newmail_count: old count = '%d'", Count, 0, 0, 0); 
  if (MessagesSeen < Count)		/* Don't set a negative count */
    Count -= (short) MessagesSeen;
  else
    Count  = 0;
  DBGPRINT(DBG$SHOWMORE,"__update_newmail_count: new count = '%d'", Count, 0, 0, 0);
  status = mail$user_set_info(&UserContext, NewMessageCount, NullItemList);
  if (!chkstat("__update_newmail_count: mail$user_set_info", 0, status) ) return;

  status = mail$user_end(&UserContext, NullItemList, NullItemList);
  if (!chkstat("__update_newmail_count: mail$user_end", 0, status) ) return;

  DBGPRINT(DBG$EXIT, "__update_newmail_count: exit", 0, 0, 0, 0);
}

/*
 - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
   Purpose:
	Update the message status flags in the local database, and optionally
	store them in the mail file's message attributes

   Inputs:
	Stream		mail stream
	msgno		Message to update
	syncflag	T   = synchronize external folder
			NIL = just update local flags.

   Output:
	NONE

   Return Value:
	NONE

   Comments:
	It is necessary to remember the SEEN and DELETED flags locally because
	these are acted on at a later time. The FLAGGED and ANSWERED flags
	are merely informational in the mail file and are not acted upon later.

	Ensure that the seen/deleted flags are updated from the values in the
	elt. There are two special cases on which action is required:

	 * If the deleted flag is set, then we mark the message as seen so that
	   deleting an unread message also updates the newmail count.

	 * If the previous value of the local SEEN flag was FALSE and the
	   new one is TRUE, then the message has been read. Therefore we
	   can update the new mail count in the mail database

	If the syncflag is set, then the attribute values in the mail file
	are updated to be consistent with the values in the elt.  Other
	attribute flags in the mail file should not be touched so we AND/OR
	in the appropriate flag.

	Writing back the DELETED flag is not sensible, so we don't.

   Author:
	Andy Harper, Kings College London, Jan 1997

 - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
 */

static void __update_message_status (MAILSTREAM *stream, long msgno, long syncflag)
  {
     MESSAGECACHE *elt = mail_elt(stream,msgno);
     FILECACHE    *fc = &LOCAL->msgs[msgno-1];
     short sysflags = 0;
     int status;

     item MessageId[] = {
	{sizeof(msgno),    MAIL$_MESSAGE_ID,    &msgno,    NULL},
	{0,                0,                   NULL,      NULL}};

     item GetFlagsItem[] = {
	{sizeof(sysflags), MAIL$_MESSAGE_RETURN_FLAGS, &sysflags, NULL},
	{0,                0,                   NULL,      NULL}};

     item SetFlagsItem[] = {
	{sizeof(msgno),    MAIL$_MESSAGE_ID,    &msgno,    NULL},
	{sizeof(sysflags), MAIL$_MESSAGE_FLAGS, &sysflags, NULL},
	{0,                MAIL$_NOSIGNAL,      NULL,      NULL},
	{0,                0,                   NULL,      NULL}};

     DBGPRINT(DBG$ENTRY,"__update_message_status: entered, msgno=[%d], syncflag=[0x%08x]", msgno, syncflag, 0, 0);
     DBGPRINT(DBG$SHOWMORE,"__update_message_status: seen=[%d], deleted=[%d], answered=[%d],flagged=[%d]", elt->seen, elt->deleted, elt->answered, elt->flagged);

					/* Update cache flags from elt */
     DBGPRINT(DBG$SHOWMORE,"__update_message_status: old cache flags = [0x%08x]",fc->flags, 0, 0, 0);


     DBGPRINT(DBG$SHOWMORE,"__update_message_status: elt->deleted=[%d], fc->deleted=[%d]", elt->deleted, (fc->flags)&fDELETED, 0, 0);
     if (elt->deleted)
       { __setflag(stream, msgno, elt, fDELETED); elt->seen = T;}       
     else
       __clearflag(stream, msgno, elt, fDELETED);

     DBGPRINT(DBG$SHOWMORE,"__update_message_status: elt->seen=[%d], fc->seen=[%d]", elt->seen, (fc->flags)&fSEEN, 0, 0);
     if (elt->seen)
       {
         if ( syncflag && !((fc->flags)&fSEEN) )
          {
            DBGPRINT(DBG$SHOWMORE,"__update_message_status: updating new mail count",0,0,0,0);
            __update_newmail_count(1);
          }
         __setflag(stream, msgno, elt, fSEEN);
       }
     else
       __clearflag(stream, msgno, elt, fSEEN);

     DBGPRINT(DBG$SHOWMORE,"__update_message_status: new cache flags = [0x%08x]", fc->flags, 0, 0, 0);



					/* Update mailfile with message flags */
     if (syncflag)
       {
         DBGPRINT(DBG$SHOWMORE,"__update_message_status: syncing message [%d] flags with mailfile", msgno, 0, 0, 0);

					/* Get current status flags */
         status = mail$message_get(&LOCAL->msg_context, MessageId, GetFlagsItem);
         if (!chkstat("__update_message_status: mail$message_get", 0, status)) return;
         DBGPRINT(DBG$SHOWMORE,"__update_message_status: current sys flags = [0x%08x] (seen=[%d], flagged=[%d], answered=[%d])", sysflags, elt->seen, elt->flagged, elt->answered);

					/* Update ONLY specific flags ! */
         if (elt->seen)    sysflags &= ~MAIL$M_NEWMSG;   else sysflags |= MAIL$M_NEWMSG;
         DBGPRINT(DBG$SHOWMORE,"__update_message_status: seen=[%d], new sysflags=[0x%08x]", elt->seen, sysflags, 0, 0);
         if (elt->flagged) sysflags |= MAIL$M_MARKED;   else sysflags &= ~MAIL$M_MARKED;
         DBGPRINT(DBG$SHOWMORE,"__update_message_status: flag=[%d], new sysflags=[0x%08x]", elt->flagged, sysflags, 0, 0);
         if (elt->answered)sysflags |= MAIL$M_REPLIED;  else sysflags &= ~MAIL$M_REPLIED;
         DBGPRINT(DBG$SHOWMORE,"__update_message_status: answ=[%d], new sysflags=[0x%08x]", elt->answered, sysflags, 0, 0);

					/* Write back new ststus flags */
         DBGPRINT(DBG$SHOWMORE,"__update_message_status: new     sys flags = [0x%08x]", sysflags, 0, 0, 0);
         status = mail$message_modify(&LOCAL->msg_context, SetFlagsItem, NullItemList);
         if (!chkstat("__update_message_status: mail$message_modify", 0, status)) return;

         DBGPRINT(DBG$SHOWMORE,"__update_message_status: message/mailfile sync complete",0,0,0,0);
       }

    DBGPRINT(DBG$EXIT,"__update_message_status: exit, message [%d] status updated",msgno,0,0,0);
  }

/*
 - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
   #include "vms_callable_mail_jackets.c"

   This module is a set of jacket routines around the callable mail interface
   to simplify later access to the basic services.

   Routines are:

	__mailfile_open		Open a specified mailfile
	__mailfile_scan		Scan mailfile for folders
	__mailfile_close	Close a specified mailfile

        __message_open		Create a message context
	__message_close		Close current message context
	__message_folder	Open a specified folder within current context
	__message_copy		Copy message to another folder
	__message_delete	Delete a message

	__message_select	Open a specified folder within new context

 - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
*/

/*
 - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
   Purpose:
	Open a specific mail file and return the context value

   Input:
	mailfile		The name of the mailfile to open

   Output:
	NONE

   Return Value:
	The callable mail context; or zero if failed

   Author:
	Andy Harper, Kings College London, Jan 1997

 - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
 */

static int __mailfile_open(char *mailfile)
  {
    int status, context=0;

    item MailFileList[] = {	/* Set the name of the file */
	{ strlen(mailfile), MAIL$_MAILFILE_NAME, mailfile, NULL},
        { 0,                MAIL$_NOSIGNAL,      NULL,     NULL},
	{ 0, 0, NULL, NULL}};

    DBGPRINT(DBG$ENTRY,"__mailfile_open: Entered, mailfile=[%s]", mailfile,0,0,0);


					/* Set up mailfile context */
    status = mail$mailfile_begin(&context, NullItemList, NullItemList);
    if (!chkstat("__mailfile_open: mail$mailfile_begin", 0, status))
      {
        DBGPRINT(DBG$SHOWMORE,"__mailfile_open: can't init mailfile context",0,0,0,0);
        return 0;
      }

    status = mail$mailfile_open(&context, MailFileList, NullItemList);
    if (!chkstat("__mailfile_open: mail$mailfile_open", 0, status))
      {
        DBGPRINT(DBG$SHOWMORE,"__mailfile_open: can't open mailfile [%s]",mailfile,0,0,0);
        status = mail$mailfile_end(&context, NullItemList, NullItemList);
        chkstat("__mailfile_open: mail$mailfile_end", 0, status);
        return 0;
      }


    DBGPRINT(DBG$EXIT,"__mailfile_open: exiting, mail ctx=[0x%08x]", context, 0, 0, 0);
    return context;
  }

/*
 - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
   Purpose:
	Close the mailfile by ending the specified mail context

   Inputs:
	context		Pointer to current mail file context

   Outputs:
	context		As above; set to zero

   Return Value:
	NONE

   Author:
	Andy Harper, Kings College London, Jan 1997

 - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
 */
static void __mailfile_close(int *context)
  {
    DBGPRINT(DBG$ENTRY,"__mailfile_close: Entered, mail context=[0x%08x]",*context,0,0,0);
    if (*context != 0)
      {
        item CloseItem[] = {	/* Purge and reclaim during close */
		{ 0, MAIL$_MAILFILE_FULL_CLOSE, NULL, NULL},
		{ 0, MAIL$_NOSIGNAL,            NULL, NULL},
		{ 0, 0, NULL, NULL } };

        int purgereclaim = getenv("CCLIENT_MAILFILE_PURGE_RECLAIM");
	int status;

				/* Terminate the mail context */
        DBGPRINT(DBG$SHOWMORE,"__mailfile_close: Terminating mail context=[0x%08x]",*context,0,0,0);
        status = mail$mailfile_close(context, purgereclaim ? CloseItem : NullItemList, NullItemList);
        chkstat("__mailfile_close: mail$mailfile_close", 0, status);
        status = mail$mailfile_end(context,   purgereclaim ? CloseItem : NullItemList, NullItemList);
        chkstat("__mailfile_close: mail$mailfile_end", 0, status);
      }
    DBGPRINT(DBG$EXIT,"__mailfile_close: exiting, mail ctx=[0x%08x]", *context, 0, 0, 0);
  }

/*
 - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
   Purpose:
	Scan the folders in a mailfile and call an action routine to deal
	with each one.

   Inputs:
	context		Address of the mailfile context
	routine		Address of function to call
	userdata	Address of userdata to pass

   Outputs:
	NONE

   Return Value:
	NONE

   Author:
	Andy Harper, Kings College London, Jan 1997

 - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
*/
static void __mailfile_scan(int *context, int (*action)(), void *userdata)
  {
    int status;

    item ScanFolders[] = {
	{sizeof(action),   MAIL$_MAILFILE_FOLDER_ROUTINE, action,    NULL},
	{sizeof(userdata), MAIL$_MAILFILE_USER_DATA,      userdata, NULL},
        {0,                0,                             NULL,      NULL}};

    DBGPRINT(DBG$ENTRY, "__mailfile_scan: entered, action=[0x%08x], userdata=[0x%08x]",action, userdata, 0, 0);
    status = mail$mailfile_info_file(context, ScanFolders, NullItemList);
    chkstat("__mailfile_scan: mail$mailfile_info_file", 1, status);
    DBGPRINT(DBG$EXIT, "__mailfile_scan: exit",0,0,0,0);
  }

/*
 - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
   Purpose:
	Initialize the message context for an opened mail file

   Input:
	mailcontext		Pointer to the mail context for the open file

   Output:
	NONE

   Return Value:
	The message context; or zero if failure

   Author:
	Andy Harper, Kings College London, Jan 1997

 - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
*/
static int __message_open(int *context)
  {
    item MessageContext[] = {
	{sizeof(*context), MAIL$_MESSAGE_FILE_CTX, context, NULL},
	{0,                0,                      NULL,    NULL}};

    int msgcontext=0;
    int status;

    DBGPRINT(DBG$ENTRY,"__message_open: entered, context = [0x%08x]", *context, 0, 0, 0);
    status = mail$message_begin(&msgcontext, MessageContext, NullItemList);
    if (!chkstat("__message_open: mail$message_begin", 0, status))
      {
        DBGPRINT(DBG$SHOWMORE,"__message_open: can't init message context",0,0,0,0);
        return 0;
      }

    DBGPRINT(DBG$EXIT,"__message_open: exit, messagecontext=[0x%08x]",msgcontext,0,0,0);
    return msgcontext;
  }

/*
 - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
   Purpose:
	End the specified message context

   Inputs:
	context		Pointer to current message context

   Outputs:
	context		As above; set to zero

   Return Value:
	NONE

   Author:
	Andy Harper, Kings College London, Jan 1997

 - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
 */
static void __message_close(int *context)
  {
    int status;

    DBGPRINT(DBG$ENTRY,"__message_close: Entered, message context=[0x%08x]", *context,0,0,0);
    if (*context != 0)
      {
        DBGPRINT(DBG$SHOWMORE,"__message_close: Terminating message context (0x%08x)",*context,0,0,0);
        status = mail$message_end(context, NullItemList, NullItemList);
        chkstat("__message_close: mail$message_end", 0, status);
      }
    DBGPRINT(DBG$EXIT,"__message_close: exiting, mail ctx=[0x%08x]", *context, 0, 0, 0);
  }

/*
 - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
   Purpose:
	Select a folder using a given message context and return 
        the number of messages in the folder.

   Inputs:
	context		Pointer to message context
	folder		Name of folder to select

   Outputs:
	context		Pointer to mail context (updated)

   Function Return Value:
	Number of new messages in folder, 0 if no folder

   Comments:

   Revision History:
	1.0	Andy Harper	6-OCT-1997	Original Version

 - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
*/
static int __message_folder(int *context, char *folder)
  {
    int status;
    int nmsgs=0;

    item FolderItem[] = {			/* Folder's name */
	{ strlen(folder), MAIL$_MESSAGE_FOLDER,    folder, NULL },
	{ 0,              MAIL$_NOSIGNAL,          NULL, NULL},
	{ 0, 0, NULL, NULL }};

    item MessagesCountItem[] = {		/* No of messages */
	{ sizeof(nmsgs), MAIL$_MESSAGE_SELECTED,  &nmsgs, NULL },
	{ 0, 0, NULL, NULL }};


    DBGPRINT(DBG$ENTRY,"__message_folder: entered, context=[0x%08x], folder=[%s]",*context,folder,0,0);


				/* Now select a folder */
    status = mail$message_select(context, FolderItem, MessagesCountItem);
    if (!chkstat("__message_folder: mail$message_select", 0, status))
      {
        DBGPRINT(DBG$EXIT,"__message_folder: exit, can't select folder [%s]", folder,0,0,0);
        return 0;
      }

    DBGPRINT(DBG$EXIT,"__message_folder: exit, folder=[%s], nmsgs=[%d]", folder, nmsgs, 0, 0);
    return nmsgs;
  }

/*
 - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
   Purpose:
	Copy message from current context into specified mailfile/folder

   Inputs:
	context		Pointer to message context
	msgno		Number of message to copy
	mailfile	Pointer to name of destination mailfile
	folder		Pointer to name of destination folder in mailfile
	delete		Flag to delete current message once copied

   Outputs:
	context		Pointer to message context (updated)

   Function Return Value:
	1		Copy OK
	0		Copy failed

   Revision History:
	1.0	Andy Harper	6-OCT-1997	Original Version

 - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
*/
static int __message_copy(int *context, long int msgno, char *mailfile, char *folder, int delete)
  {
    item CopyFolderItem[] = {
	{strlen(folder),      MAIL$_MESSAGE_FOLDER,   folder,   NULL},	/* must be 1st,foldername */
	{strlen(mailfile),    MAIL$_MESSAGE_FILENAME, mailfile, NULL},	/* must be 2nd */
	{sizeof(msgno),       MAIL$_MESSAGE_ID,       &msgno,   NULL},	/* Message number to move */
	{0, 0, NULL, NULL } };
    
    item CopyAndDeleteFolderItem[] = {
	{strlen(folder),      MAIL$_MESSAGE_FOLDER,   folder,   NULL},	/* must be 1st,foldername */
	{strlen(mailfile),    MAIL$_MESSAGE_FILENAME, mailfile, NULL},	/* must be 2nd */
	{sizeof(msgno),       MAIL$_MESSAGE_ID,       &msgno,   NULL},	/* Message number to move */
        {0,                   MAIL$_MESSAGE_DELETE,   NULL,     NULL},
	{0, 0, NULL, NULL } };
    
    int status;

    DBGPRINT(DBG$ENTRY,"__message_copy: entered, msgno=[%ld], destination=[%s -- %s]", msgno, mailfile, folder, 0);
    status = mail$message_copy(context, delete ? CopyAndDeleteFolderItem : CopyFolderItem , NullItemList);
    if (!chkstat("__message_copy: mail$message_copy", 0, status))
      {
        DBGPRINT(DBG$EXIT,"__message_copy: exit, message [%d] failed", msgno,0, 0, 0);
        return 0;
      }
    DBGPRINT(DBG$EXIT,"__message_copy: exit, message [%d] copied", msgno,0, 0, 0);
    return 1;    
  }

/*
 - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
   Purpose:
	Delete message from current context

   Inputs:
	context		Pointer to message context
	msgno		Number of message to copy

   Outputs:
	context		Pointer to message context (updated)

   Function Return Value:
	1		Delete OK
	0		Delete failed

   Revision History:
	1.0	Andy Harper	6-OCT-1997	Original Version

 - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
*/
static int __message_delete(int *context, long int msgno)
  {
    item DeleteItemList[] =
      {{sizeof(msgno), MAIL$_MESSAGE_ID,  &msgno,  NULL},
       {0, 0, 0, 0}};

    int status;

    DBGPRINT(DBG$ENTRY,"__message_delete: entered, msgno=[%ld]", msgno, 0, 0, 0);
    status=mail$message_delete(context,DeleteItemList,NullItemList);
    if (!chkstat("__message__delete: mail$message_delete", 0, status))
      {
       DBGPRINT(DBG$EXIT,"__message_delete: exit, message [%d] failed",msgno,0,0,0);
       return 0;
      }
    DBGPRINT(DBG$EXIT,"__message_delete: exit, message [%d] deleted",msgno,0,0,0);
    return 1;
  }

/*
 - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
   Purpose:
	Select a folder from a given mail context, return a new message
        context and the number of messages in the folder.

   Inputs:
	context		Pointer to mail context
	folder		Name of folder to select

   Outputs:
	context		Pointer to mail context (updated)
	nmsgs		Pointer to message count

   Function Return Value:
	New message context; zero if failed

   Comments:

   Revision History:
	1.0	Andy Harper	6-OCT-1997	Original Version

 - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
*/
static int __message_select(int *context, char *folder, long int *nmsgs)
  {
    int status;
    int msgcontext;


    DBGPRINT(DBG$ENTRY,"__message_select: entered, context=[0x%08x], folder=[%s]",*context,folder,0,0);


				/* Try to get a new message context */
    msgcontext = __message_open(context);
    if (msgcontext == 0) return 0;

				/* Now select a folder */
    *nmsgs = __message_folder(&msgcontext, folder);
    if ( ! *nmsgs )
      {__message_close(&msgcontext); return 0;}


    return msgcontext;
  }

/*
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
   #include "vms_string.c"

   A set of functions for dealing with searching and comparing strings and
   characters.

	chrcasecmp		Case-insensitive character comparison

	__strcasecmp		Case-insensitive string comparison

	strcasecmpn		Case-insenstive string comparison, first n
				chars only

	matchwild		Simple wildcard match (*, ? and %)

	strscan			Find first character not in set

	strbreak		Find first character in set

	strfind			Find substring

	stritem			Find first character in set, outside quotes

- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
*/

/*
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
   Purpose:
      Case insensitive character match

   Inputs:
	c1		Character to check
	c2		Character to check

   Output:
	NONE

   Return Value:
	T		If a case-insensitive match
	NIL		If no match

   Author:
	Andy Harper, Kings College London, January 1997

- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
*/
static int chrcasecmp(char c1, char c2)
  {
     return (	(isupper(c1) ? tolower(c1) : c1) ==
		(isupper(c2) ? tolower(c2) : c2)  );
  }

/*
 - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
   Purpose:
	Case insensitive string equality test

   Inputs:
	s1		String to check
	s2		String to check

   Output:
	NONE

   Return Value:
	T		If case-insensitive match
	NIL		If no match

   Author:
	Andy Harper, Kings College London, JAN 1997

 - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
*/
static int __strcasecmp(char *s1, char *s2)
 {
    int n1 = strlen(s1);
    int n2 = strlen(s2);
    if (n1 != n2) return NIL;

    while (n1--)
     if ( !chrcasecmp(*s1++, *s2++) ) return NIL;

    return T;
 }

/*
 - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
   strcasecmpn

   Purpose:
	Case-independent comparison of two fixed length strings

   Inputs:
	s1		Pointer to first string
	s2		Pointer to second string
	m		Number of characters to compare

   Outputs:
	NONE

   Function Return Value:
	1		If a match
	0		If no match

   Comments:

   Revision History:
	1.0	Andy Harper	23-SEP-1997	Original Version

 - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
*/
static int strcasecmpn(char *s1, char *s2, int m)
  {
    while (m-- > 0)
      if ( ! chrcasecmp(*s1++,*s2++) ) return 0;
    return 1;
  }

/*
 - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

   Purpose:
	Match a case-insensitive wildcarded pattern

   Inputs:
	p		The pattern to match against
	s		The string to be matched

   Outputs:
	NONE

   Return Value:
	T		If a match
	NIL		If no match

   Comments:
	Special characters allowed in the pattern are:
		*	Match any string
		? or %	Match exactly one character

        Alphabetic characters are case-insensitive; other characters must match
	themselves exactly.

   Author:
	Andy Harper, Kings College London, Jan 1997

 - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
*/
static int matchwild(char *p, char *s)
  {
    int i;

    while (*p)
      switch (*p)
        {
         case '*' :				/* Wildcard match */
                    for (i=strlen(s); i>=0; i--)
                      if (matchwild(p+1,s+i)) return T;
                    return NIL;


         case '?':				/* Match any single char */
         case '%':
                    if (!*s++) return NIL; p++; break;


         default:				/* Case-Insensitive match */
                    if ( !chrcasecmp(*p++, *s++) ) return NIL;
        }

    return chrcasecmp(*p, *s);
  }

/*
 - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

 Purpose:
	Find the length of the next item made up exclusively from characters in
	a specified set

 Inputs:
	s	Pointer to string to search
	size	Length of string
	charset	Set of characters which next item must contain

 Outputs:
	NONE

 Function Return Value:
	Length of next item. If length is equal to original size then all
	characters in the original string match the set.

 Revision History:
	1.0	Andy Harper	August 1997	Original Version

 - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
*/
static int strscan(char *s, int size, char *charset)
  {
     char *p = s;

     DBGPRINT(DBG$ENTRY,"strscan: searching '%.*s%s' for char not in set '%s'", STRLIM(size), s, STREXT(size), charset);

     while (size--)
       if (strchr(charset, *p++) == NULL) return (p-s)-1;

     return p-s;
  }

/*
 - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
   Purpose:
	Find the first character in a string which matches any one of a set
	of characters (similar to the 'C' function strpbrk).

   Inputs:
	s		Pointer to the string to be searched
	size		length of string to be searched
	charset		Pointer NULL-terminated string of characters to find

   Output:
	NONE

   Return Value:
	Offset to the first matching character (starting at 1), or zero if
	not found

   Author:
	Andy Harper, Kings College London, March 1997

 - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
*/
static int strbreak(char *s, int size, char *charset)
  {
     char *p = s;

     DBGPRINT(DBG$ENTRY,"strbreak: searching '%.*s%s' for char in set '%s'", STRLIM(size), s, STREXT(size), charset);

     while (size--)
       if (strchr(charset, *p++) != NULL) return p-s;

     return 0;
  }

/*
 - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
   Purpose:
	Scan a string, defined by a start and a length, for a substring and
	return the starting point of the substring.

	Similar to 'strstr' but is case-insensitive.

   Inputs:
	s		pointer to string to be searched
	size		Size of string to be searched
	item		Pointer to null-terminated string to be found

   Output:
	NONE

   Return Value:
	Pointer to start of substring

   Author:
	Andy Harper, Kings College London, Feb 1997

 - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
*/
static char *strfind(char *s, int size, char *item)
  {
    int i, n = strlen(item);
    int match;

    DBGPRINT(DBG$ENTRY, "strfind: scanning '%.*s%s' for '%s'", STRLIM(size),s,STREXT(size), item); 
    while (size-- >= n)
      {
         match = 1;

         for (i=0; i<n; i++)
           if (!chrcasecmp(s[i], item[i]))
              {match=0; break;}

         if (match)
           return s;      

         s++;
      }

    return NULL;
  }

/*
 - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
   Purpose:
	Find the length of the next item assuming that the input is a list of
        items separated by one of a set of characters, properly skipping
        quoted strings and escaped single characters (" ... "  and \x).

   Inputs:
	address		Pointer to string containing zero or more items
	n		Length of string
	charset		null-terminated string of terminator characters

   Output:
	NONE

   Return Value:
	Length of the item

   Comments:
	The separators are normally specified as comma, space and tab

   Author:
	Andy Harper, Kings College London, Feb 1997

 - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
*/
static int stritem(char *address, int n, char *charset)
  {
    char *a = address;
    int quotenxt=0, inquotes=0;


    DBGPRINT(DBG$ENTRY,"stritem: entered, input = '%.*s%s'", STRLIM(n), address, STREXT(n), 0);
    while (n--)
     {
       if (quotenxt)			/* Need to skip next char */
         quotenxt=0;

       else if (*a == '\\')		/* Quoting flag ?? */
         quotenxt = ~quotenxt;

       else if (*a == '"')		/* Start/end of quoted string */
         inquotes = ~inquotes;

					/* Unquoted separators */
       else if (!inquotes && (strchr(charset, *a) != NULL))
         break; 

       a++;
      }


     DBGPRINT(DBG$EXIT, "stritem: exit, item='%.*s'", a-address, address, 0, 0);
     return a-address;
  }

/*
   ---------------------------------------------------------------------------
   #include "vms_pattern.c"

   A set of routines for handling pattern matching and replacement.

   Routines are:

	wildmatch		Does wildcard pattern matching on a string

	replace_pat		Substitute markers in string by replacement
				strings.

	match_replace		Match a wildcard pattern, pick out the strings
				that match the wildcards and insert them into
				a replacement string.

   ---------------------------------------------------------------------------
*/

/*
   ---------------------------------------------------------------------------
   wildmatch

   Purpose:
	Match a string against pattern and return the individual matched
        portions.

   Inputs:
	pat		The pattern to match against
	plen		The length of the pattern
	str		The string to match
	slen		The length of the string to match

   Outputs:
	mt		Pointer to array of matched strings, one per pattern
			element.
	mtlen		Pointer to lengths of matched strings, one per pattern
			element.

   Function Return Value
	1		If a successful match
	0		No match

   Comments:
	Pattern characters are:
		*	match arbitrary length string, including null
		?	Match any single character
		\	Treat next character as literal
		other	Match itself (but ignore case of letters)

   Revision History:
	1.0	Andy Harper	15-SEP-1997	Original Version
	1.1	Andy Harper	18-SEP-1997	Allow literal quoting via '\'

   ---------------------------------------------------------------------------
*/
static int wildmatch(char *pat, int plen, char *str, int slen, char **mt, int *mtlen)
  {
    int n;
    char c;

    while (plen-- > 0)
      switch(c = *pat++)
        {
	  case '\\':			/* Use next character 'as is' */
                    if (plen<=1) return 0;
                    c = *pat++; plen--; *mt++ = ""; *mtlen++ = 0;

          default:			/* Ordinary - match itself only */
                    if ((slen==0) || !chrcasecmp(c,*str)) return 0;
      
          case '?':			/* Wildcard - match exactly one */
                    if (slen==0) return 0;
                    *mt++ = str; *mtlen++ = 1; str++; slen--; break;

          case '*':			/* Wildcard - match any length */
                    for (n=slen; n>=0; n--)
                      if (wildmatch(pat, plen,str+n,slen-n, mt+1, mtlen+1))
                        {*mt++ = str; *mtlen++ = n; return 1;}
                    return 0;
          }

    return slen ? 0 : 1;
  }

/* 
   ---------------------------------------------------------------------------
   replace_pat

   Purpose:
	Copy a string, replace special markers with new strings.

   Inputs:
	newpat		The string to be scanned, containing markers to flag
			replacements
	nlen		Length of string
	repstr		Array of replacement string pointers
	replen		Array of replacement string lengths
	repmax		Number of replacement strings

   Outputs:
	output		Output buffer for rewritten string

   Function Return Value:
	Length of string written to output buffer

   Comments:
	The input string markers have the form  '?n'  where 'n' is a single
	decimal digit 0-9. This indicates the 'n'th replacement string is to
	be substituted. 'n' must be less than repmax or a null string will be
	substituted.

	If the character after '?' is not a digit, it is inserted literally,
	so '??' allows a single '?' to be substituted.

   Revision History:
	1.0	Andy Harper	15-SEP-1997	Original Version
	1.1	Andy Harper	16-SEP-1997	Allow '?' to be inserted.
	1.2	Andy Harper	19-SEP-1997	Use '?' to escape next char

   ---------------------------------------------------------------------------
*/
static int replace_pat(char *output, char *newpat, int nlen, char *repstr[], int replen[], int maxrep)
  {
     char *outptr = output;
     char c;
     int n;

     DBGPRINT(DBG$ENTRY, "replace_pat: new pattern=[%.*s], replace string count='%d'", nlen, newpat, maxrep, 0);
     while (nlen-- > 0)
       if ( ((c = *newpat++) == '?') && (nlen>0) && isdigit(*newpat) )
         {
	  if ((n = *newpat++ - '0') < maxrep)
            {DBGPRINT(DBG$SHOWMORE,"replace_pat: insert [%d]: [%.*s]", n, replen[n], repstr[n], 0);
             outptr += sprintf(outptr, "%.*s", replen[n],repstr[n]);}
          else
             DBGPRINT(DBG$SHOWMORE,"replace_pat: insert [%d]: out of range", n, 0, 0, 0);

          nlen--;
         }

       else if ((c=='?') && (nlen>0))
         {DBGPRINT(DBG$SHOWMORE,"replace_pat: insert char: [%c]", *newpat, 0, 0, 0);
          *outptr++ = *newpat++; nlen--;}

       else
         {DBGPRINT(DBG$SHOWMORE,"replace_pat: insert char: [%c]", c, 0, 0, 0);
          *outptr++ = c;}

     DBGPRINT(DBG$EXIT, "replace_pat: rewritten address = [%.*s]", outptr-output, output, 0, 0);
     return outptr-output;
  }

/*
   ---------------------------------------------------------------------------
   match_replace

   Purpose:
	Match a string against a pattern and rewrite the string according to a
	replacement specification.

   Inputs:
	a	Pointer to string to be matched
	n	Length of string to be matched
	pat	Pointer to pattern
	patlen	Length of pattern
	rep	Pointer to replacement specification
	replen	Length of replacement specification

   Outputs:
	output	Pointer to buffer for rewritten string

   Function Return Value:
	Length of rewritten string (0=no match, or null output string)

   Comments:
	The string is matched using the wildcarded pattern matching routine. In
	order to remember the matched strings, we allocate space for an array
	of string pointers, and corresponding lengths, in which the matched
	strings are held. These arrays can never be longer than the input
	pattern string.

	These matched strings are then squeezed to leave only the strings
	which matched a wildcarded specification (* or ?) in the original
	pattern. This should leave only those strings referred to by the ?n
	syntax in the replacement string.
	
	A null replacement string is indistinguishable from a non-matching
	string in this version of the code. The return values are the same.


   Revision History:
	1.0	Andy Harper	16-SEP-1997	Original Version

   ---------------------------------------------------------------------------
*/
static int match_replace(char *output, char *a, int n, char *pat, int patlen, char *rep, int replen)
  {
     char *outptr = output;

     char **matchstr = (char **) fs_get(sizeof(char *) * patlen + 1);
     int   *matchlen = (int *)   fs_get(sizeof(int)    * patlen + 1);

     DBGPRINT(DBG$ENTRY, "match_replace: input  =[%.*s], pattern=[%.*s]", n, a, patlen, pat);

				/* Does string match pattern ?? */
     if ( wildmatch(pat, patlen, a, n, matchstr, matchlen) )
       {
	 int n, m=0;
         int prefixnext=0;

				/* Remove non-wildcard match strings */
         for (n=0; n<patlen; n++)
           {
             DBGPRINT(DBG$SHOWMORE,"match_replace: pattern character=[%c], matches [%d]:[%.*s]", pat[n], matchlen[n], matchlen[n], matchstr[n]);
             if (prefixnext)		/* This character prefixed ? */
               {prefixnext=0;}

             else if (pat[n]=='\\')	/* Prefix next character ??  */
               {prefixnext=1;}

             else if ( strchr("*?", pat[n]) )
               {matchstr[m]=matchstr[n]; matchlen[m]=matchlen[n]; m++;}
           }

				/* Rewrite the string */
         outptr += replace_pat(outptr, rep, replen, matchstr, matchlen, m);
         DBGPRINT(DBG$EXIT, "match_replace: rewrite=[%.*s] --> [%.*s]", replen, rep, outptr-output, output);
       }

     fs_give((void **) &matchstr);
     fs_give((void **) &matchlen);

     return outptr-output;
  }

/*
   ---------------------------------------------------------------------------
   #include "vms_address_rewrite.c"

   A set of routines for handling the rewriting of addresses in various ways.

   Routines are:

	__parse_nodename	parse the leading namespace/node from an
				address

	__strnode		Parse multiple leading namespace/nodes

	__fmt_specials		Look for special chars and quote if found

	__fmt_internet_style	Generate an internet style address for supplied
				local part and various nodename defaults.

	__fmt_local_encoding	Encode arbitrary string using the RFC1405 local
				encoding mechanism

	__fmt_cbs_address	Re-encode a CBS format address to
				internet-style

	__fmt_x400_label	Map certain X.400 labels to standard form

	__fmt_x400_address	Format Message Router/X.400 address to
				internet-style.

	__fmt_unprefix		Remove backslash prefixing from quoted
				addresses

	__fmt_internet_address	Handle address already in internet format

	__fmt_quoted_string	Handle address that are quoted strings

	__fmt_extension		Handle addresses that use foreign mail
				interface format.

	__fmt_user_pattern	Translate address according to user-specified
				rewrite rules (patter-match and replace)

	__to_rfc822		Examine address and select type of processing
				needed

	rewrite_address		Handle list of addresses; reformat one at a
				time

   ---------------------------------------------------------------------------
*/

/*
 - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
   __parse_nodename

   Purpose:
	Given a VMS MAIL format address sting, parse the leading namespace and
	nodename

   Inputs:
	a		Pointer to VMS MAIL format address
	n		Length of VMS MAIL format address

   Outputs:
	ns		Pointer to namespace pointer
	nslen		Pointer to namespace length
	nd		Pointer to nodename pointer
	ndlen		Pointer to nodename length

   Function Return Value:
	Total length of leading namespace/nodename; 0 if none

   Comments:

   Revision History:
	1.0	Andy Harper	9-SEP-1997	Original Version

 - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
*/
static int __parse_nodename(char *a, int n, char **ns, int *nslen, char **nd, int *ndlen)
  {
    static char alphanumdot[] =
      "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789.";

    char *as=a;
    int m;

    *ns    = *nd    = a;
    *nslen = *ndlen = 0;

				/* Parse DECnet/OSI namespace */
    if ( ((m=strscan(a,n,alphanumdot)) < (n-1)) && (a[m]==':') && (a[m+1]=='.'))
       { *ns = a; *nslen = m; a+=m+2; n-=m+2;}

				/* Parse DECnet nodename */
    if ( ((m=strscan(a,n,alphanumdot)) < (n-1)) && (a[m]==':') && (a[m+1]==':'))
       { *nd = a; *ndlen = m; a+=m+2; n-=m+2;}

    return a-as;
  }

/*
 - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
   __strnode

   Purpose:
	Find length of leading namespace/nodenames. Multiple leading names
	are found.

	This is a simple jacket routine around parse_nodename

   Inputs:
	a	Address string
	n	Length of address string

   Outputs:
	NONE

   Function Return Value:
	Length of leading namespace/nodenames [ 0 if none ]

   Comments:

   Revision History:
	1.0	Andy Harper	9-SEP-1997	Original Version
	1.1	Andy Harper	16-SEP-1997	Find multiple nodes

 - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
*/
static int __strnode(char *a, int n)
  {
    char *b = a;
    int m;

    char *ns, *nd;
    int nslen, ndlen;

    while (m=__parse_nodename(a, n, &ns, &nslen, &nd, &ndlen))
      {a+=m; n-=m;}

    return a-b;
  }

/*
 - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
  __fmt_specials

  Purpose:
	If the address contains any special characters, they are backslash
	prefixed and the whole enclosed within " ... ".
	Otherwise, the string is unchanged.

  Inputs:
	a	Address string
	n	Length of address string
	quoted	Flag if input is a quoted string

  Outputs:
	output	Buffer for converted address

  Return Value:
	Length of converted address

  Comments:
	Because of the use of some specific special characters by VMS, the
	string is quoted if it contains any of the normal rfc822 specials
	BUT only a subset of the characters are actually prefixed.
	See 'rfc822_specials_standard' and 'rfc822_specials_prefix'.

  Revision History:
	1.0	Andy Harper	3-SEP-1997	Original Version
	1.1	Andy Harper	16-SEP-1997	Prefixing charset modified
	1.2	Andy Harper	17-SEP-1997	Don't prefix quoted strings
	1.3	Andy Harper	23-SEP-1997	Add flag for pre-quoted strings

 - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
*/
static int __fmt_specials(char *output, char *a, int n, int quoted)
  {
    static char rfc822_specials_standard[] = "()<>@,;:\\\"[]\t ";
    static char rfc822_specials_prefix[]= "()<>@,;\\\"[]\t";

    char *outptr = output;

    DBGPRINT(DBG$ENTRY,"__fmt_specials: string=[%.*s]", n, a, 0, 0);

    if (!quoted && strbreak(a, n, rfc822_specials_standard))
      {
        *outptr++ = '"';
        while (n-- > 0)
          {if (strchr(rfc822_specials_prefix,*a)) *outptr++ = '\\'; *outptr++ = *a++;}
        *outptr++ = '"';
      }

    else
      outptr += sprintf(output, "%.*s", n, a);

    DBGPRINT(DBG$EXIT,"__fmt_specials: quoted address=[%.*s]", outptr-output, output, 0, 0);
    return outptr-output;
  }

/*
 - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
  __fmt_internet_style

  Purpose:
	Quote the address string if needed, then append the specified
        nodename to form an internet style address.

  Inputs:
	a	Address string
	n	Length of address string
	nd	Nodename
	ndlen	Length of nodename
	quoted	Flag if address is already quoted

  Outputs:
	output	Buffer for converted address

  Return Value:
	Length of converted address

  Comments:

  Revision History:
	1.0	Andy Harper	3-SEP-1997	Original Version
	1.1	Andy Harper	23-SEP-1997	Flag pre-quoted address

 - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
*/
static int __fmt_internet_style(char *output, char *a, int n, char *nd, int ndlen, int quoted)
  {
    char *outptr = output;

    DBGPRINT(DBG$ENTRY,"__fmt_internet_style: string=[%.*s], node=[%.*s]", n, a, ndlen, nd);
    outptr += __fmt_specials(outptr, a, n, quoted);
    outptr += sprintf(outptr,"@%.*s", ndlen, nd);
    DBGPRINT(DBG$EXIT,"__fmt_internet_style: output=[%.*s]", outptr-output,output,0,0);
    return outptr-output;
  }

/*
 - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
   __fmt_local_encoding

   Purpose:   
	Encode an address string using the local encoding format

   Inputs:
	a	Pointer to address string
	n	Total size of address string
	ndlen	Length of leading nodename part
	quoted	Flag if address after nodename is already quoted

   Output:
	output	Pointer to output buffer for converted address

   Function Return Value:
	Length of converted address

   Comments:
	This routine encodes an address into a local format string and applies
	a number of translations to the namespace/nodename, using logical
        names.	

	* If a translation of the logical name 
          CCLIENT_NODE[_namespace][_nodename] exists, then it is used as the
          internet host name.

	* Otherwise, if the logical name CCLIENT_NORFC1405 exists (any value)
	  then the namespace is discarded and am internet host name chosen
	  from the following set (in order):
	    1. The original nodename
	    2. Translation of logical name CCLIENT_GW_MAIL11
	    3. Any local internet host name
            4. Translation of logical name SYS$NODE

	  If none of the above results in a usable host name, then just the
	  username portion is written.

	  This is provided for compatibility with earlier versions of encoding.

	* Otherwise, if the logical name CCLIENT_GW_MAIL11 exists, both
	  namespace and nodename are discarded and the translation of this
	  logical name used as the internet host name.

	* Otherwise, if a local internet system name exists, this is used as
          the internet host name.

	* Otherwise, if a system node name exists, this is used as the internet
          host name.

        * Otherwise, the username is treated as a local username and used on
          its own with no nodename.

	In all cases, the username part is examined for special characters;
	if any are found then they will be backslash prefixed and the whole
	string enclosed in double quotes.


   Revision History:
	1.0	Andy Harper	2-SEP-1997	Original Version
	1.1	Andy Harper	11-SEP-1997	Add logical names
	1.2	Andy Harper	16-SEP-1997	Fix up nodename handling
	1.3	Andy Harper	16-SEP-1997	Fix up non-RFC1405 encoding
	1.4	Andy Harper	23-SEP-1997	Flag pre-quoted address

 - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
*/
static int __fmt_local_encoding(char *output, char *a, int n, int ndlen, int quoted)
  {
    char lgname[512];
    char *lgptr = lgname;
    char *nsp, *ndn;
    int   nsl,  ndl;
    char *p;

    DBGPRINT(DBG$ENTRY, "__fmt_local_encoding: address=[%.*s], nodename part=[%.*s]", n, a, ndlen, a);


			/* Generate a possible logical name translation */
    lgptr += sprintf(lgptr, "CCLIENT_NODE");
    if (__parse_nodename(a, ndlen, &nsp, &nsl, &ndn, &ndl) > 0)
      {
        if (nsl>0)
          lgptr += sprintf(lgptr, "_%.*s", nsl, nsp);
        if (ndl>0)
          lgptr += sprintf(lgptr, "_%.*s", ndl, ndn);

        for (p=lgname; *p; p++)
          if (islower(*p))
            *p=toupper(*p);
      }

			/* Logical name translation ?? */
    DBGPRINT(DBG$SHOWMORE, "__fmt_local_encoding: logical name = [%s]", lgname, 0, 0, 0);
    if (p=getenv(lgname))
      return __fmt_internet_style(output, a+ndlen, n-ndlen, p, strlen(p),quoted);


    if (p=getenv("CCLIENT_NORFC1405"))
      {
        if (ndl>0)	/* Have a node name ?? */
          return __fmt_internet_style(output, a+ndlen, n-ndlen, ndn, ndl, quoted);

	else
	  {a+=ndlen; n-=ndlen;}
      }

			/* Locate a usable nodename, if possible  */
    if ( (p=getenv("CCLIENT_GW_MAIL11")) || (p=mylocalhost()) )
      return __fmt_internet_style(output, a, n, p, strlen(p), 0);


			/* Last resort for node, use DECnet node name */
    if ( (p=getenv("SYS$NODE")) )
      return __fmt_internet_style(output, a, n, p, strlen(p)-2, 0);


			/* No host name detectable, just encode string as is */
    return __fmt_specials(output, a, n, quoted);
  }

/*
 - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
   __fmt_cbs_address

   Purpose:
	Reformat an address in the UK Coloured Book format into an
	internet-style format.

   Inputs:
	a	Address
	n	Length of address

   Outputs:
	output	Pointer to output buffer for converted address

   Function Return Value:
	length of converted address

   Comments:
	CBS format is:
	   [relay::]site::user

   Revision History:
	1.0	Andy Harper	2-SEP-1997	Original Version

 - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
*/
static int __fmt_cbs_address(char *output, char *a, int n)
  {
    static struct {char *pat; char *rep;} cbstab[] = {
	{"*::*::*::*::*::*::*::*::*::\"*\"", "\"?9\"%?8%?7%?6%?5%?4%?3%?2%?1@?0"},
	{"*::*::*::*::*::*::*::*::*::*",     "?9%?8%?7%?6%?5%?4%?3%?2%?1@?0"},
	{"*::*::*::*::*::*::*::*::\"*\"",    "\"?8\"%?7%?6%?5%?4%?3%?2%?1@?0"},
	{"*::*::*::*::*::*::*::*::*",        "?8%?7%?6%?5%?4%?3%?2%?1@?0"},
	{"*::*::*::*::*::*::*::\"*\"",       "\"?7\"%?6%?5%?4%?3%?2%?1@?0"},
	{"*::*::*::*::*::*::*::*",           "?7%?6%?5%?4%?3%?2%?1@?0"},
	{"*::*::*::*::*::*::\"*\"",          "\"?6\"%?5%?4%?3%?2%?1@?0"},
	{"*::*::*::*::*::*::*",              "?6%?5%?4%?3%?2%?1@?0"},
	{"*::*::*::*::*::\"*\"",             "\"?5\"%?4%?3%?2%?1@?0"},
	{"*::*::*::*::*::*",                 "?5%?4%?3%?2%?1@?0"},
	{"*::*::*::*::\"*\"",                "\"?4\"%?3%?2%?1@?0"},
	{"*::*::*::*::*",                    "?4%?3%?2%?1@?0"},
	{"*::*::*::\"*\"",                   "\"?3\"%?2%?1@?0"},
	{"*::*::*::*",                       "?3%?2%?1@?0"},
	{"*::*::\"*\"",                      "\"?2\"%?1@?0"},
	{"*::*::*",                          "?2%?1@?0"},
	{"*::\"*\"",                         "\"?1\"@?0"},
	{"*::*",                             "?1@?0"},
	{"*",                                "?0"}
      };

    static int size = sizeof(cbstab)/sizeof(cbstab[0]);
    int m,k;

    DBGPRINT(DBG$ENTRY,"__fmt_cbs_address: address='%.*s", n, a, 0, 0)

    for (m=0; m<size; m++)
      if ( k = match_replace(output, a, n, cbstab[m].pat, strlen(cbstab[m].pat), cbstab[m].rep, strlen(cbstab[m].rep)) )
         return k;

    return 0;
  }

/*
 - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
   __fmt_x400_label

   Purpose:
	Rewrite a single X400 element.

   Inputs:
	a	X400 label string ("key=value" pair)
	n	Length of address

   Output:
	output	Buffer for converted address

   Function Return Value:
	Length of converted string

   Comments:
	Valid elements are:

         * *key\value			--->	DD.key=value

	 * [NAME=]surname		--->	S=surname
	   [NAME=]given surname		--->	G=given/S=surname
	   [NAME=]given initials surname--->	G=given/I=initials/S=surname

	 * [PN=]surname			--->	S=surname
	   [PN=]given surname		--->	G=given/S=surname
	   [PN=]given initials surname	--->	G=given/I=initials/S=surname

	 * key=value			--->	key=value

	 * other			--->	other


	All fields are substituted using the pattern matching facilities.

   Revision History:
	1.0	Andy Harper	3-SEP-1997	Original Version
	1.1	Andy Harper	16-SEP-1997	Added 'NAME=' processing
						Added '*..\..' processing
						Added 'G=../I=../S=..' insertion
	1.2	Andy Harper	17-SEP-1997	Use patterns for most keys
	1.3	Andy Harper	17-SEP-1997	Added HP/SUN [PN=] keyword

 - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
*/
static int __fmt_x400_label(char *output, char *a, int n)
  {
		/* Table of key translations - ORDER IS VERY IMPORTANT!!! */
     static struct {char *pat; char *rep;} pattab[] = {
	{"\\**\\\\*",   "DD.?0=?1"},
	{"NAME=* * *", "G=?0/I=?1/S=?2"},	/* Forename, Initials, Surname */
	{"PN=* * *",   "G=?0/I=?1/S=?2"},	/* Forename, Initials, Surname */
	{"NAME=* *",   "G=?0/S=?1"},		/* Forename, Surname */
	{"PN=* *",     "G=?0/S=?1"},		/* Forename, Surname */
	{"NAME=*",     "S=?0"},			/* Surname */
	{"PN=*",       "S=?0"},			/* Surname */
        {"1=*",        "C=?0"},			/* Map numeric 1 key */
        {"2=*",        "A=?0"},			/* Map numeric 2 key */
        {"3=*",        "P=?0"},			/* Map numeric 3 key */
        {"4=*",        "OU=?0"},		/* Map numeric 4 key */
        {"5=*",        "O=?0"},			/* Map numeric 5 key */
        {"6=*",        "S=?0"},			/* Map numeric 6 key */
        {"7=*",        "G=?0"},			/* Map numeric 7 key */
        {"8=*",        "I=?0"},			/* Map numeric 8 key */
        {"9=*",        "GQ=?0"},		/* Map numeric 9 key */
        {"10=*",       "U=?0"},			/* Map numeric 10 key */
        {"11=*",       "X=?0"},			/* Map numeric 11 key */
        {"12=*",       "T=?0"},			/* Map numeric 12 key */
        {"*=*",        "?0=?1"},		/* All other key=value pairs */
	{"* * *",      "G=?0/I=?1/S=?2"},	/* Forename, Initials, Surname */
	{"* *",        "G=?0/S=?1"},		/* Forename, Surname */
	{"*",          "S=?0"}			/* Surname */
      };

     static int   num   = sizeof(pattab) / sizeof(pattab[0]);

     char *p;
     int   m,k;

     DBGPRINT(DBG$ENTRY, "__fmt_x400_label: X400 label = [%.*s]", n, a, 0, 0);
    

				/* Reformat according to patterns */
     for (m=0; m<num; m++)
       if ( k = match_replace(output, a, n, pattab[m].pat, strlen(pattab[m].pat), pattab[m].rep, strlen(pattab[m].rep)) )
          return k;

				/* Default, return it as is */
     return sprintf(output, "%.*s", n, a);
  }

/*
 - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
   __fmt_x400_address

   Purpose:
	Reformat X400 address

   Inputs:
	a	Initial VMS format X400 address string; leading nodename
		removed and surrounding quotes stripped
	n	Length of address string

   Output:
	output	Output buffer for converted string

   Function Return Value:
	Length of converted string

   Comments:
	A VMS format X400 string has this form:
	    key=value :: ... :: key=value

	And should be converted to:
	    "/key=value/ ... /key=value/"@hostname

	The local hostname is used unless an alternate one is specified by
	the logical name CCLIENT_GW_X400.


   Revision History:
	1.0	Andy Harper	3-SEP-1997	Original Version

 - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
*/
static int __fmt_x400_address(char *output, char *a, int n)
  {
     char *outptr = output;
     char *x400gw = getenv("CCLIENT_GW_X400");
     char *p;

     DBGPRINT(DBG$ENTRY,"__fmt_x400_address: address=[%.*s]", n, a, 0, 0);

     outptr += sprintf(output, "\"/");
     while ( p=strfind(a, n, "::") )
       {
         outptr += __fmt_x400_label(outptr,  a, p-a);
         outptr += sprintf(outptr, "/");
         n = n - (p-a) -2; 
         a = p+2;
       }

     outptr += __fmt_x400_label(outptr, a, n);
     outptr += sprintf(outptr, "/\"@%s", x400gw ? x400gw : mylocalhost() );

     DBGPRINT(DBG$EXIT, "__fmt_x400_address: exit, address=[%.*s]", outptr-output, output, 0, 0);
     return outptr-output;
  }

/*
 - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
   __fmt_unprefix

   Purpose:
	Remove the backslash prefixing characters from an internet-style
	address

   Inputs:
	a	Pointer Address
	n	Length of address
	m	Length of address prior to '@'

   Outputs:
	output	Buffer for converted string

   Function Return Value:
	Length of converted string written to buffer

   Comments:

   Revision History:
	1.0	Andy Harper	22-SEP-1997	Original Version

 - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
*/
static int __fmt_unprefix(char *output, char *a, int n, int m)
  {
    char *outptr = output;
    int   sn     = n-m;
    int   prefix = 0;

    DBGPRINT(DBG$ENTRY, "__fmt_unprefix: input=[%.*s], at=[%d]", n, a, m, 0);

    while (m-- > 0)
      {
        if (prefix)			/* This char preceded by '\' ?? */
          {*outptr++ = *a; prefix=0;}

        else if (*a == '\\')		/* Prefix next character */
          {prefix=1;}

      else				/* Ordinary character */
          {*outptr++ = *a;}

      a++;
      }

    DBGPRINT(DBG$SHOWMORE, "__fmt_unprefix: usernm=[%.*s]", outptr-output, output, 0, 0);
    outptr += sprintf(outptr, "%.*s", sn, a);

    DBGPRINT(DBG$EXIT, "__fmt_unprefix: output=[%.*s]", outptr-output, output, 0, 0);
    return outptr-output;
  }

/*
 - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
   __fmt_internet_address

   Purpose:
	Reformat the quotes used within an apparent internet-style address.

   Inputs:
	a	address
	n	Length of address 
	m	Length of username part (prior to '@')

   Outputs:
	output	Pointer to output buffer for converted address

   Function Return Value:
	Length of converted address

   Comments:
	This routine is called when an internet-style address is detected
	by the transport extension module. In such addresses, an additional
        set of quotes is placed around the address. Due to limitations within
	VMS MAIL parsing, the existing quotes must be changed to something else.

	For example:
	  *  Original:		"Joe Smith"@mysite.com
	  *  After encoding:	smtp%"'Joe Smith'@mysite.com"

	This routine attempts to undo this to restore the original address

	The replacement quote symbols can be:
	  *  ¢			Cent-sign (used with UCX, VMS 7.x)
	  *  '			single quote (used with MX and others)

	The use of ' can result in invalid addresses on delivery and we are
	UNABLE to detect this accurately. So no attempt is made to do so.

	With the original quoted string, backslash prefixing may be used to
	quote special characters (like " as part of the local-part). These are
	removed by this routine.

   Examples:
	John.Smith@kcl.ac.uk	-->	John.Smith@kcl.ac.uk
	'John.Smith'@kcl.ac.uk	-->	"John.Smith"@kcl.ac.uk
	'Claudio Luigi'.Allochio@infn.it
				 --->	"Claudio Luigi".Allochio@infn.it
   Revision History:
	1.0	Andy Harper	2-SEP-1997	Original Version
	1.1	Andy Harper	16-SEP-1997	Add \"..\" usage
	1.2	Andy Harper	17-SEP-1997	Add VMS 7 ¢..¢ (cent) quoting
	1.3	Andy Harper	18-SEP-1996	Allow embedded quotes
	1.4	Andy Harper	22-SEP-1997	Simplify to what's really needed

 - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
*/
static int __fmt_internet_address(char *output, char *a, int n, int m)
  {
    char *outptr = output;
    int   sitelen= n-m;
    int   prefix = 0;

    DBGPRINT(DBG$ENTRY, "__fmt_internet_address: input=[%.*s], @='%d'", n, a, m, 0);

    while (m-- > 0)
      {
         if (prefix)				/* This char follows prefix */
           {prefix = 0; *outptr++ = *a;}

         else if (*a == '\\')			/* prefix next char ?? */
           {prefix = 1;}

         else if ( (*a == '¢') || (*a == '\'') )/* Replacement quotes ?? */
           {*outptr++ = '"';}

         else					/* ordinary char */
           {*outptr++ = *a;}

         a++;
      }    


    DBGPRINT(DBG$SHOWMORE, "__fmt_internet_address: usernm=[%.*s]", outptr-output, output, 0, 0);
    outptr += sprintf(outptr, "%.*s", sitelen, a);

    DBGPRINT(DBG$EXIT, "__fmt_internet_address: output=[%.*s]", outptr-output, output, 0, 0);
    return outptr-output;
  }

/*
 - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
   __fmt_quoted_string

   Purpose:
	Reformat a quoted address into an internet-style format

   Inputs:
	a	Full original address
	n	Length of address
	ndlen	Length of leading namespace/nodename part

   Outputs:
	output	Pointer to output buffer for converted address

   Function Return Value:
	Length of converted address

   Comments:
        Format of input string is:
	  [[namespace:.]node::]" ... "

	Possible contents of the quoted string are:
	  [node::] key=value:: ... ::key=value		X.400
	  ... @ ...					Internet
          other						local format

   Revision History:
	1.0	Andy Harper	2-SEP-1997	Original Version

 - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
*/
static int __fmt_quoted_string(char *output, char *a, int n, int ndlen)
  {
    char *b = a+ndlen+1;	/* Skip node and leading quote */
    int   bn= n-ndlen-2;	/* Adjust length .. */
    int   no;
    int   m;

    DBGPRINT(DBG$ENTRY, "__fmt_quoted_string: node=[%.*s], address=[%.*s]", ndlen, a, bn, b);


			/* Looks like X400 with extra leading nodes ?? */
    if ( ((m=stritem(b, bn, "=")) < bn) && ((no=__strnode(b, m)) > 0) )
      return __fmt_x400_address(output, b+no, bn-no);


			/* Looks like X400 with no leading nodes ?? */
    if (  m < bn )
      return __fmt_x400_address(output, b, bn);


			/* Internet-style address inside the quotes  ?? */
    if (  (m=stritem(b, bn, "@")) < bn )
      return __fmt_unprefix(output, b, bn, m);


			/* Something unknown; assume local format  */
    return __fmt_local_encoding(output, a, n, ndlen, 1);
  }

/*
 - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
   __fmt_extension

   Purpose:
	Reformat a VMS address that specifies a specific transport extension

   Inputs:
	a	Address
	n	Length of address
	ndlen	Length of nodename
	extlen	Offset to end of transport extension name

   Outputs:
	output	Pointer to output buffer for converted address

   Function Return Value:
	length of converted address

   Comments:
        Input format is:
	   [[ns:.]nd::]tsp%address

	where:
	   tsp		is the transport name
	   address	is the transport specific address

	There are three main categories of transport predefined here:
	  *  Internet format      tsp%"...@..."
          *  UK coloured book     tsp%[relay::][relay::] ... [site::]user
          *  Other

	The list of known internet transports is taken from two different
	places:
	  * The system logical name MAIL$INTERNET_TRANSPORT
	  * The built in table of known internet transports.

	If the logical name CCLIENT_INTERNET_AUTO is defined (any value), then
	the transport name is assumed to be one that handles an internet style
	address if the address portion has the format "..@..".  If it does not
	have this form, or the logical name is not defined, then local format
	encoding is used.


   Revision History:
	1.0	Andy Harper	2-SEP-1997	Original Version
	1.1	Andy Harper	5-SEP-1997	Use table of known transports
	1.2	Andy Harper	17-SEP-1997	Added "IN" and "WINS" prefixes
	1.3	Andy Harper	18-SEP-1997	Added CCLIENT_INTERNET_AUTO

 - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
*/
static int __fmt_extension(char *output, char *a, int n, int ndlen, int extlen)
  {
    static char			
       *tspinet[] = {"MX", "SMTP", "INET", "IN", "WINS", "JANET"},/* Internet transports */
       *tspcbs[]  = {"CBS"};					/* UK CBS transports*/

    static int
       inetsize  = sizeof(tspinet) / sizeof(tspinet[0]),
       cbssize   = sizeof(tspcbs)  / sizeof(tspcbs[0] );

    char lgname[256];
    int  lgnum=0;

    char *inetdefault=getenv("MAIL$INTERNET_TRANSPORT");
    char *b = a+ndlen;		/* String, excluding nodename, 'tsp%...'*/
    int   bn= n-ndlen;		/* Length, excluding nodename		*/
    int   en= extlen-ndlen;	/* Length of extension name    'tsp'	*/
    char *c = b + en + 1;	/* String, excluding transport '...'	*/
    int   cn= bn- en - 1;	/* Length of address portion		*/

    char *p;
    int m;

    DBGPRINT(DBG$ENTRY, "__fmt_extension: transport=[%.*s], address=[%.*s]", en, b, cn, c);


			/* System's default internet transport */
   if (inetdefault)
      if ( (en==strlen(inetdefault)) && (strcasecmpn(inetdefault, b, en)) )
        return __fmt_internet_address(output, c+1, cn-2, stritem(c+1,cn-2,"@"));
         

			/* Other Known internet-style transport extensions */
    for (m=0; m<inetsize; m++)
      if ( (en==strlen(tspinet[m])) && (strcasecmpn(tspinet[m], b, en)) )
        return __fmt_internet_address(output, c+1, cn-2, stritem(c+1,cn-2,"@"));



			/* UK Coloured Book Software extension */
    for (m=0; m<cbssize; m++)
      if ( (en==strlen(tspcbs[m])) && (strcasecmpn(tspcbs[m], b, en)) )
        return __fmt_cbs_address(output, c, cn);



			/* See if we should assume internet format ?? */
    if (p=getenv("CCLIENT_INTERNET_AUTO"))
      if ( (c[0] == '"')  &&  (c[cn-1] == '"') && ((m=stritem(c+1,cn-2,"@")) <(cn-2)) )
        return __fmt_internet_address(output, c+1, cn-2, m);


			/* Unknown extension */
    return __fmt_local_encoding(output, a, n, ndlen, 0);
  }

/*
   ---------------------------------------------------------------------------
   __fmt_user_pattern

   Purpose:
	Use address rewriting rules taken from logical names specified by the
	user to implement customized translations.

   Inputs:
	a	the address to be rewritten
	n	length of the address
	logname	Logical name prefix

   Outputs:
	output	Output buffer for rewritten address

   Function Return Value:
	Length of the rewritten address. If 0 then either no matching rule
	could be found, or the result of the rewrite was the null string.

   Comments:
	Logical Names have this format:
		'logname'_n	(n=1 upwards)

	Logical names are located starting with n = 1 and finishing when a
	match is found, or when no logical name is found. Thus rewrite rules
        should have no gaps in the range of suffix values.

	Rewrite rules specifying more specific patterns should be specified
	before rules specifying less specific patterns, by assigning them to
	logical names with a lower value of 'n'. If not, patterns may be
        matched unexpectedly giving unexpected rewrites.

	Value of logical name must be a complete rewrite rule in this format:
		<..pattern..>=<..replacement..>

        If this format is not found, the logical name is ignored. No spaces are
	allowed within the '>=<' string, nor at the start/end of the string.
	Other spaces are used verbatim.

	Format of the 'pattern' is any wildcarded string made up from
	ordinary characters, ? or *. These have the usual meanings.

	Format of 'replacement' is any arbitrary string containing marker
	characters of the form '?n' where 'n' is a single digit 0-9. Each
	marker is replaced by the corresponding matching portion of the string
	taken from the original patterm. The leftmost wildcard portion that
	matches is numbered 0 and increases left to right. Only portions which
	match the ? and * patterns are counted.

	For example:
	   <*::*>=<?0@?1>

	applied to:
	   MYNODE::MYUSER

	?0 matches 'MYNODE'
	?1 matches 'MYUSER'
	?2-?9 are undefined and will be replaced by the null strings

	This would result in:
	   MYUSER@MYNODE

   Revision History:
	1.0	Andy Harper	15-SEP-1997	Original Version
	1.1	Andy Harper	22-SEP-1997	Make logical name a parameter	

   ---------------------------------------------------------------------------
*/
static int __fmt_user_pattern(char *output, char *a, int n, char *logname)
  {
     char *outptr = output;
     char lgname[256];
     char *p;
     int lgnum = 0;

     DBGPRINT(DBG$ENTRY,"__fmt_user_pattern: address=[%.*s]", n, a, 0, 0);
     while (sprintf(lgname, "%s_%d", logname, ++lgnum) && (p=getenv(lgname)) )
       {
         char *sp;
         int m;
         int pn = strlen(p);

         DBGPRINT(DBG$SHOWMORE,"__fmt_user_pattern: logical name:[%s], rewrite pattern=[%.*s]", lgname, pn, p, 0);

          			/* Format:  <..pattern..>=<..replacement..> */
         if ( (p[0] == '<')  && (p[pn-1] == '>') && (sp=strfind(p, pn, ">=<")) )
           if (m=match_replace(output, a, n, p+1, sp-p-1, sp+3, pn-(sp-p)-4))
             return m;
       }

     return 0;
  }

/*
 - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
   __to_rfc822

   Purpose:
	Examine the VMS MAIL style address and try to reformat into an internet
	style equivalent if possible.

   Inputs:
	a	Pointer to a VMS-style address string
	n	Length of the address string

   Output:
	output	Pointer to output buffer for converted string

   Function Return Value:
	Length of converted string

   Comments:
	In general, VMS MAIL headers can have several formats

	  * [[ns:.]nd::]		! Optional leading namespace/nodename

	then one of these:
	  * " ... "			! A quoted string
	  * tsp%...			! A mail transport extension
	  * ...@...			! An internet-style address
	  * ...				! Something else (local format)

	Within each category, the address can be further classified and
	formatted accordingly.

	Addresses are first pre-processed via a set of user specified rewrite
	rules. The resulting address is then processed internally to match one
	of the above formats. Logical names CCLIENT_ADDRESS_PREPROCESS_n are
	used to specify the preprocessing rules.

	Once pre-processing is complete, the address is formatted into an
	Internet-style address.  Addresses are first checked against a set of
	user-defined rewrite rules; any successful translation is used as the
	final result. Logical names CCLIENT_ADDRESS_REWRITE_n are used to
	specify the rewrite rules.

	If the rewrite rules fail to match, the preprocessed address is then
	reformatted according to the built-in rules.


   Revision History:
	1.0	Andy Harper	2-SEP-1997	Original Version
	1.1	Andy Harper	15-SEP-1997	Add user specified absolute
						address rewrite rules.
	1.2	Andy Harper	22-SEP-1997	Add user-specified address
						preprocessing via rewrite rules.
	1.3	Andy Harper	23-SEP-1997	Apply pre-processing rules
						before rewrite rules.

 - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
*/
static int __to_rfc822(char *output, char *a, int n)
  {
    char  postaddr[1024];	/* Space for pre-processed address */
    char *addr    = a;		/* Initial address */
    int   addrlen = n;		/* Initial length */

    char *b;
    int   bn;
    int   ndlen;
    int   m;

    DBGPRINT(DBG$ENTRY, "__to_rfc822: address=[%.*s]", n, a, 0, 0);


				/* Any pre-processing rewrites ?? */
    if ((m=__fmt_user_pattern(postaddr, a, n, "CCLIENT_ADDRESS_PREPROCESS")))
      { addr = postaddr; addrlen = m;}


				/* See if we match user specified rewrites */
    if ((m=__fmt_user_pattern(output,addr,addrlen,"CCLIENT_ADDRESS_REWRITE")))
      return m;

				/* Find any leading nodename, adjust ptrs */
    ndlen = __strnode(addr,addrlen);
    b = addr    + ndlen;
    bn= addrlen - ndlen;

				/* Quoted string */
    if ( (b[0] == '"') && (b[bn-1] == '"') )
      return __fmt_quoted_string(output, addr, addrlen, ndlen);


				/* Mail transport extension */
    if ( ((m=strscan(b,bn,alphanum)) < bn) && (b[m] == '%') )
      return __fmt_extension(output, addr, addrlen, ndlen, ndlen+m);


				/* Outside quotes, but Internet-style address */
    if ( (m=stritem(b, bn, "@")) < bn )
      return sprintf(output, "%.*s", bn, b);


				/* Outside quotes, not an internet address */
    return __fmt_local_encoding(output, addr, addrlen, ndlen, 0);
  }

/*
 - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
   __rewrite_address

   Purpose:
	Extract each comma-separated address and convert it to a more
	conventional RFC822 format. Spaces are allowed as an alternative
        separator. Optional spaces are allowed around a comma separator.
        Spaces or commas within quoted strings are not separators.

   Inputs:
	a		Pointer to the address string(s)
	n		Length of the address string

   Output:
	output		Pointer to output buffer

   Return Value:
	Length of new address field

   Comments:
	If at the start of an address, the line length exceeds 72 characters,
	then we insert a continuation line in accordance with the RFC
	specification.

   Author:
	Andy Harper, Kings College London, Jan 1997

 - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
*/

static int __rewrite_address(char *output, char *a, int n)
 {
   static int maxlinelength = 72;
   char *p       =  output;
   char *nl      =  output;
   char *sepstrng= "";			/* Initial separator string */
   char *pst;
   int   i;

					/* Loop through address list */
   while (n > 0)
    {
					/* Skip leading separators */
      while (n && ((*a == ',') || isspace(*a)))
        { a++; n--;}

					/* Find end of address string */
      if ((i=stritem(a,n,", \t")) > 0)
        {
					/* Add the current separator string */
          strcpy(p, sepstrng); p += strlen(sepstrng);
          sepstrng = ",  ";

          if ( (p-nl) > maxlinelength)	/* Break long line */
            { *p++ = '\n'; nl = p; *p++ = ' '; *p++ =' ';}

					/* Rewrite this address */
          DBGPRINT(DBG$SHOWMORE,"__rewrite_address: original ='%.*s'",i,a,0,0);
          p += __to_rfc822(pst=p, a, i);
          DBGPRINT(DBG$SHOWMORE,"__rewrite_address: rewritten='%.*s'",p-pst,pst,0,0);

          a += i;			/* Skip past old address */
          n -= i;
        }       
    }

   return p-output;			/* return new size */
 }

/*
 - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
  #include "vms_mail_interface.c"

  This is a suite of library routines which can be used to fetch status
  information, headers and the text of a message from a folder.

  The routines will use undocumented features of callable mail to access long
  records (>255) if the logical name CCLIENT_MESSAGE_USE_LONG_RECORDS is defined
  Otherwise, only standard documented calls will be used.

  Routine sstarting with __ are not intended to be called directly by external
  applications.

  Routines are:

    * Utilities

	__resize_buffer		Increase size of message buffer if need be.


    * Those which give access via the undocumented callable mail interface

	__parse_record		Extracts the data from the undocumented
				internal record format

        get_message_record	Given the current pointer to the undocumented
				in-memory data structure of the message text,
				this returns unpacks the next record into a
				buffer and advances the pointer. Can be used
				by an external application to read records
				one at a time.

	parse_message		Given the pointer to the undocumented in-memory
				data structure of the message text, unpack the
				records to a user supplied buffer, terminating
				each record by newline and returning the total
				size of the unpacked message in bytes.

	__get_message_ptr	Using callable mail, get the message into the
				undocumented message structure in memory and
				return the pointer to that structure


     * Those which give access via documented callable mail features only

	__get_message_text	Using callable mail, get the text of the
				message into a user supplied buffer.

	get_message_headers	Retrieve the attribute flags, record count and
				the VMS MAIL headers for a given message

	get_message_flags	Retrive the attribute flags and record count for
				a given message.


     * General Applications Interface:

	Readmessage		Retrieve the message body using either
				documented or undocumented features, depending
				on the presence of the logical name.

 - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
*/

/*
 - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

 In order to retrieve lines longer than 256 bytes from the callable mail
 subsystem, we use an undocumented item code (MAIL$_MESSAGE_BUFFER) that
 returns a pointer to the in-memory text. This text has a particular block
 structure and the following definitions simplify the handling of that
 structure.

 In essence:

  Type			OffSet from	Description
			Block Start
					+------------------------------+
  Longword pointer	0		+ Link to next block           +
					+------------------------------+
  Unsigned longword	4		+ Size of data in this block   +
					+------------------------------+
  Unsigned longword	8		+ Length of this block         +
					+------------------------------+
					+ Text records, in a format    +
  Text records,prefixed	12		: similar to variable length   :
  by unsigned word			+ files                        +
  containing length			+------------------------------+
       

  This information was supplied by Hunter Goatley.

 - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
*/


struct TEXTBLOCKHEADER			/* Structure of a message block */
  { struct TEXTBLOCKHEADER *next;
    unsigned int    size;
    unsigned int    length;
    char            first;
  };


struct RECORDHEADER			/* Structure of a message record */
  {
    unsigned short int length;
  };

/*
 - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
    Purpose:
	Ensure that buffer is at least the right size; increase it if not

    Inputs:
	buffer		Pointer to the buffer pointer
	cursize		Pointer to current size
	newsize		Desired size of buffer

   Outputs:
	buffer		Updated to point to newly resized buffer
	cursize		Updated with new size

   Function Return Value:
	Pointer to the allocated buffer;

   Comments:

   Revision History:
	1.0	Andy Harper	3-OCT-1997	Original Version

 - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
*/
static int __resize_buffer(char **buffer, int *cursize, int newsize)
  {
     DBGPRINT(DBG$ENTRY, "__resize_buffer: entered, size=[%d], new size=[%d]", *cursize, newsize, 0, 0);
     if (newsize > *cursize)
       { if (*buffer) fs_give(buffer); *buffer = fs_get(newsize+1); *cursize= newsize;}
     DBGPRINT(DBG$ENTRY, "__resize_buffer: exit,    size=[%d]", *cursize, 0, 0, 0);
     return *buffer;
  }


/*
 - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
   __PARSE_RECORD

   Purpose:
	Parse the undocumented callable mail memory structure of a message and
	extract the next data record; advance pointers to the next record

   Inputs:
	dataptr		Pointer to the pointer to the record
	ext		Flag if record comes from external file

   Outputs:
	dataptr		Pointer to the pointer to the next record
	buffer		Pointer to the pointer to the output buffer
			(or pointer to NULL if no output required)

   Function Return Value:
	Size of the record, including the end of line character

   Comments:
	This routine assumes the undocumented callable mail structure described
	by TEXTHEADER and RECORDHEADER.

	A newline is appended to each record extracted.

	The record format is slightly different between internal and external
	messages: In the external case, the records are padded to EVEN length;
        in the internal case, they are not padded. This requires some special
	adjustment to the record pointer to ensure we stay in sync.
	Thanks to Michael Hitch for this detail.

  Revision History:
	1.0	Andy Harper	August 1997	Original Version

 - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
*/

static int __parse_record (char **dataptr, char **buffer, int ext)
  {
    unsigned short int rlen = ((struct RECORDHEADER *)(*dataptr))->length;

    (*dataptr) += sizeof(struct RECORDHEADER);
    if (*buffer)
      {memcpy(*buffer, *dataptr, rlen); (*buffer) += rlen; *(*buffer)++ = '\n';}

    DBGPRINT(DBG$SHOWMORE, "__parse_record: [%d]: [%.*s%s]", rlen, STRLIM(rlen), *dataptr, STREXT(rlen));
    (*dataptr) += rlen;
    if (ext && (rlen &1)) (*dataptr)++;

    return rlen+1;
  }

/*
 - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
   GET_MESSAGE_RECORD

   Purpose:
	This provides an external interface to the undocumented in-memory
	structure, should an application wish to extract records one at a time
	into its own buffer.

	From the supplied context, get the next record from the message buffer
	and update the context.

   Inputs:
	msgptr		Address of current message block (as returned by the
			MAIL$_MEMORY_BUFFER callable mail item).
	data		Address of current record in block
	ext		0 if internal message, 1 if external

   Outputs:
        msgptr		Address of current message block
	buffer		Pointer to space for next record (or NULL)
	data		Address of next record block

   Function Return Value:
	Size of record, including end of line. 0 = end of message

   Comment:
	This routine parses the undocumented message structure used by callable
	mail to get the next message into memory. See above for details.

   Revision History:
	1.0	Andy Harper	August 1997	Original Version

 - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
*/

int get_message_record(char **msgptr, char *buffer, char **data, int ext)
  {
    struct TEXTBLOCKHEADER **tbh = ((struct TEXTBLOCKHEADER **) msgptr);
    int size = 0;

    DBGPRINT(DBG$ENTRY,"get_message_record: entered, msgptr = [0x%08x], data=[0x%08x]", *msgptr,*data,0,0);
    if (*tbh)
      {
			/* Initialize if first entry in block */
        if (!*data)
          *data = &(*tbh)->first;


			/* Parse out next record */
        size += __parse_record(data, &buffer, ext);


			/* If now at end of block, move to next one */
        if (*data >= (&(*tbh)->first+(*tbh)->size) )
          {(*tbh)=(*tbh)->next; *data = NULL;}
      }

    DBGPRINT(DBG$EXIT,"get_message_record: exit, record size = [%d]", size, 0, 0, 0);
    return size;
  }

/*
 - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
   __PARSE_MESSAGE

   Purpose:
	Given a pointer to the internal message structure, extract the text
	records to a buffer.

   Inputs:
	tbh		Pointer to the internal message structure
	ext		Flag if message came from external file

   Outputs:
	message		Pointer to buffer for message (or NULL)

   Return Value:
	Size of message, including newline character at end of each record

   Comment:
	This routine parses the undocumented message structure used by callable
	mail to get the next message into memory. See above for details.

    Revision History:
	1.0	Andy Harper	August 1997	Original Version

 - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
*/
int __parse_message(char *memptr, int ext, char *message)
  {
    struct TEXTBLOCKHEADER *tbh = (struct TEXTBLOCKHEADER *) memptr;
    int size = 0;

    DBGPRINT(DBG$ENTRY,"__parse_message: entered, Message pointer = [0x%08x], Buffer pointer = [0x%08x]", message, memptr, 0, 0);
    while (tbh)
      {
        char *dataptr  = &tbh->first;
        char *dataend  = dataptr + tbh->size;

    			/* Process current data block */
        while (dataptr < dataend)
          {
            size += __parse_record(&dataptr, &message, ext);
            DBGPRINT(DBG$SHOWMORE,"__parse_message: Remaining buffer size = [%d]", dataend-dataptr, 0, 0, 0);
          }
       
        DBGPRINT(DBG$SHOWMORE,"__parse_message: next block: [0x%08x]", tbh->next, 0, 0, 0);
        tbh = tbh->next;		/* Next data block */
      }

    DBGPRINT(DBG$EXIT, "__parse_message: exit, message size = [%d]", size, 0, 0, 0);
    return size;
  }

/*
 - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
   __GET_MESSAGE_PTR

   Purpose:
	Using undocumented calls to callable mail, get the current message into
	memory and return a pointer to the undocumented message structure.

   Input:
	context		Pointer to current message context

   Outputs:
	NONE

   Function Return Value:
	Pointer to the internal message structure

   Comments:
	The item code MAIL$_MEMORY_BUFFER is undocumented.  It is set only when
	the first text record of the message is required. Thus is is necessary
	to loop over the headers until we get that first text record (or end of
	message). Only then will the buffer be valid.
	Thanks to Michael Hitch for this detail.


   Revision History:
	1.0	Andy Harper	25-SEP-1997	Original Version

 - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
*/

static char *__get_message_ptr(int *context)
  {
    char      record[256];
    short int rtype;
    int       status;
    int       reclen = 0;
    char     *ptr = NULL;
    
    item MessageContList[] = {
	{ sizeof(int),        MAIL$_MESSAGE_CONTINUE,     NULL,      NULL},
	{ 0, 0, NULL, NULL}};

    item MessageBuffList[] = {
        { sizeof(record),     MAIL$_MESSAGE_RECORD,       record,    &reclen},
        { sizeof(rtype),      MAIL$_MESSAGE_RECORD_TYPE,  &rtype,    NULL},
        { sizeof(ptr),        MAIL$_MESSAGE_BUFFER,       &ptr,      NULL},
	{ 0, 0, NULL, NULL}};

    DBGPRINT(DBG$ENTRY,"__get_message_ptr: entered",0,0,0,0);
    do {
      status = mail$message_get(context, MessageContList, MessageBuffList);
      DBGPRINT(DBG$SHOWMORE,"__get_message_ptr: mail$message_get status = [0x%08x]", status, 0, 0, 0);
    } while ((status == MAIL$_MSGTEXT) && (rtype == MAIL$_MESSAGE_HEADER));

    DBGPRINT(DBG$EXIT,"__get_message_ptr: exit, pointer=[0x%08x]",ptr,0,0,0);
    return ptr;
  }

/*
 - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
   __GET_MESSAGE_TEXT

   Purpose:
	Copy the text of a message from the mailbox into a memory buffer,
	terminating each record by \n.

   Inputs:
	context		Pointer to current message context

   Outputs:
	msgptr		Pointer to buffer in which to store message

   Function Return Value:
	Size of message in bytes

   Comments:
	This routine is fully compatible with documented callable mail
	interfaces!

   Revision History:
	1.0	Andy Harper	25-SEP-1997	Original Version

 - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
*/
static int __get_message_text(int *context, char *msgptr)
  {
    short int rtype;
    int       status;
    int       reclen   = 0;
    char     *msgstart = msgptr;
    
    item MessageContList[] = {
	{ sizeof(int),        MAIL$_MESSAGE_CONTINUE,     NULL,      NULL},
	{ 0, 0, NULL, NULL}};

    item MessageBuffList[] = {
        { 256,                MAIL$_MESSAGE_RECORD,       msgptr,    &reclen},
        { sizeof(rtype),      MAIL$_MESSAGE_RECORD_TYPE,  &rtype,    NULL},
	{ 0, 0, NULL, NULL}};


    DBGPRINT(DBG$ENTRY,"__get_message_text: entered",0,0,0,0);
    do {
      status = mail$message_get(context, MessageContList, MessageBuffList);
      DBGPRINT(DBG$SHOWMORE,"__get_message_text: mail$message_get status = [0x%08x]", status, 0, 0, 0);
    } while ((status == MAIL$_MSGTEXT) && (rtype == MAIL$_MESSAGE_HEADER));


    while (status == MAIL$_MSGTEXT)
     {
      DBGPRINT(DBG$SHOWMORE,"__get_message_text: size=[%d], record=[%.*s]", reclen, reclen, msgptr, 0);
      msgptr += reclen;
      *msgptr++ = '\n';
      MessageBuffList[0].buffer = msgptr;
      status = mail$message_get(context, MessageContList, MessageBuffList);
      DBGPRINT(DBG$SHOWMORE,"__get_message: mail$message_get status = [0x%08x]", status, 0, 0, 0);
     }

    DBGPRINT(DBG$EXIT,"__get_message_text: exit, size=[%d]",msgptr-msgstart,0,0,0);
    return msgptr-msgstart;
  }

/*
 - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
   GET_MESSAGE_HEADERS

   Purpose:
        Return info about a mail message from context value and message
	number.

   Inputs:
	msgcontext	Pointer to space for message context value
	msgnum		Number of message

   Outputs:
	from		Pointer to FROM field
	to		Pointer to TO field
	cc		Pointer to CC field
	date		Pointer to DATE field
	subj		Pointer to SUBJ field
	MsgFlags	Message status flags (new, marked, replied, external 
			 etc.)
   Return Value:
	Number of records in the message

   Comments:
	This routine is fully compatible with documented callable mail
	interfaces.

	The meaning of the message flag bits are as follows (taken from the
	maildef.h include header):

	BIT	DECIMAL	SYMBOLIC NAME		MEANING
	  0	1	MAIL$M_NEWMSG		A NEW message
	  1	2	MAIL$M_REPLIED		Message has been REPLIED to
	  2	4	MAIL$M_DEL		Message has been deleted
	  3	8	MAIL$M_EXTMSG		Message text is in external file
	  4	16	 :
	  5	32	 :  Miscellaneous status, not useful to us
	  6	64	 :
	  7	128	MAIL$M_MARKED		Message has been marked

   Revision History:
	1.0	Andy Harper	25-SEP-1997	Original Version

 - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
*/
int get_message_headers(int *msgcontext, int msgnum, char *from, char *to, char *cc, char *date, char *subj, short int *MsgFlags)
  {
    int headersize = 255;

    int fromlen=0, tolen=0, cclen=0, datelen=0, subjlen=0;

    int rcount = 0;

    item MessageIdentList[] = {
        { sizeof(msgnum),     MAIL$_MESSAGE_ID,           &msgnum,   NULL},
	{ 0, 0, NULL, NULL}};

    item MessageFirstList[] = {
	{ headersize,        MAIL$_MESSAGE_FROM,         from,     &fromlen},
	{ headersize,        MAIL$_MESSAGE_TO,           to,       &tolen},
	{ headersize,        MAIL$_MESSAGE_CC,           cc,       &cclen},
	{ headersize,        MAIL$_MESSAGE_DATE,         date,     &datelen},
	{ headersize,        MAIL$_MESSAGE_SUBJECT,      subj,     &subjlen},
        { sizeof(*MsgFlags), MAIL$_MESSAGE_RETURN_FLAGS, MsgFlags, NULL},
	{ sizeof(rcount),    MAIL$_MESSAGE_SIZE,         &rcount,  NULL},
	{ 0, 0, NULL, NULL}};

    int status;


		/* Get basic info about message */
    status = mail$message_get(msgcontext, MessageIdentList, MessageFirstList);
    chkstat("get_message_headers: mail$message_get", 0, status);

		/* Terminate the strings */
    from[fromlen] = to[tolen] = cc[cclen] = date[datelen] = subj[subjlen]= '\0';


		/* Massage the date field into dd mmm yyyy hh:mm:ss */
    date[2] = date[6] = ' ';

    return rcount;
  }

/*
 - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
   GET_MESSAGE_FLAGS

   Purpose:
        Return message attribute flags of a mail message from context value
	and message number.

   Inputs:
	msgcontext	Pointer to space for message context value
	msgnum		Number of message

   Outputs:
	MsgFlags	Message status flags (new, marked, replied, external 
			 etc.)
   Return Value:
	Number of records in the message

   Comments:
	This routine is fully compatible with documented callable mail
	interfaces.

	The meaning of the message flag bits are as follows (taken from the
	maildef.h include header):

	BIT	DECIMAL	SYMBOLIC NAME		MEANING
	  0	1	MAIL$M_NEWMSG		A NEW message
	  1	2	MAIL$M_REPLIED		Message has been REPLIED to
	  2	4	MAIL$M_DEL		Message has been deleted
	  3	8	MAIL$M_EXTMSG		Message text is in external file
	  4	16	 :
	  5	32	 :  Miscellaneous status, not useful to us
	  6	64	 :
	  7	128	MAIL$M_MARKED		Message has been marked

   Revision History:
	1.0	Andy Harper	25-SEP-1997	Original Version

 - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
*/
int get_message_flags(int *msgcontext, int msgnum, short int *MsgFlags)
  {
    int rcount = 0;

    item MessageIdentList[] = {
        { sizeof(msgnum),     MAIL$_MESSAGE_ID,           &msgnum,   NULL},
	{ 0, 0, NULL, NULL}};

    item MessageFirstList[] = {
        { sizeof(*MsgFlags), MAIL$_MESSAGE_RETURN_FLAGS, MsgFlags, NULL},
	{ sizeof(rcount),    MAIL$_MESSAGE_SIZE,         &rcount,  NULL},
	{ 0, 0, NULL, NULL}};

    int status;

		/* Get message attributes */
    status = mail$message_get(msgcontext, MessageIdentList, MessageFirstList);
    chkstat("get_message_flags: mail$message_get", 0, status);

    return rcount;
  }

/*
 - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
   READMESSAGE

   Purpose:
	Get text of a message

   Inputs:
	context		Pointer to context of current folder
	msgno		Number of required message
	msgsize		Pointer to current size of allocated buffer
	msgptr		Pointer to currently allocated buffer pointer
	rcount		Record count of message
	flags		Status flags of message

   Outputs:
	msgsize		Pointer to new size of allocated buffer
	msgptr		Pointer to resized buffer pointer

   Function Return Value:
	Size of message in bytes

   Comments:
	The message is retrieved using standard calls to the callable mail
	software. This limits the size of an individual record in the message
	to 255 bytes.

	If the logical name CCLIENT_MESSAGE_USE_LONG_RECORDS is defined (any
	value), then the message is retrieved using undocumented callable mail
	features which increases the maximum record length to 65535 bytes. Use
	of this feature may render the software susceptable to future changes
	to callable mail!

   Revision History:
	1.0	Andy Harper	25-SEP-1997	Original Version

 - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
*/
int ReadMessage(int *context, int msgno, int *msgsize, char **msgptr, int rcount, short int flags)
  {
    int bytes;

			/* Use undocumented long records interface ?? */
    if (getenv("CCLIENT_MESSAGE_USE_LONG_RECORDS"))
      {
        char *memptr= __get_message_ptr(context);
        bytes = __parse_message(memptr, flags&MAIL$M_EXTMSG, NULL);
        __resize_buffer(msgptr, msgsize, bytes);
        bytes = __parse_message(memptr, flags&MAIL$M_EXTMSG, *msgptr);
      }


			/* Use callable mail compatible interface */
    else
      {
        bytes = (rcount+1) * MAXRECORDSIZE;
        __resize_buffer(msgptr, msgsize, bytes);
        bytes = __get_message_text(context, *msgptr);
      }

    return bytes;
  }

/*
 - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
  #include "vms_message_reformat.c"

  This is a suite of routines used to reformat the messages found in a mailbox
  into a standardised form.

  The standard form returned is:

        RFC822 headers
          includes trailing blank line
        Message body


  The input can be:
        *  Standard VMS MAIL messages with no extra headers in the body
        *  Internet messages with their headers in the body of the message
	*  Message Router style headers with their headers in the body of the
	   message

  Additional headers in the body can be at the top or the end (controlled by
  the logical name CCLIENT_HEADERS_ATEND).

  Routines are:

	__save_message		Store the formatted header and text parts into
				the local messagestream cache

	__is_mrtype		Examines the data field of the current header
				for a message router style header.

	__is_rfc822		Examines the current record to see if it is
				an rfc822-style message header. Additional info
				about the type of the header is also returned.

	__is_hdr_blcok		Examines the headers down to the the next blank
				record to see if the block of records looks
				like a set of RFC822-style headers.


	__format_VMS_headers	Generate a set of RFC822-style headers from
				the VMS MAIL headers

	__prefix_fields		Examine a header block and prefix all
				originator and recipient fields by 'X-' (used
				for Message Router only)

	__format_mr_headers	generate a set of RFC822-style headers from
				The original Message router headers, the VMS
				MAIL headers and any optionally set of headers
				prefixed by RFC-822-Headers:

	__format_message	Format the headers and message bassed on what
				kind of headers were detected.

 - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
*/

/*
 - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
   __save_message

   Purpose:
	Save the header and text strings in the LOCAL area.

   Inputs:
	stream		Pointer to the current mail stream
	header		Pointer to the current header text
	headersize	Size of the current header
	text		Pointer to the current text body
	textsize	Size of the current text body

   Outputs:
	stream		Pointer to current mail stream

   Function Return Value:
	Total Message SIze

   Comments:

   Revision History:
	1.0	Andy Harper	1-OCT-1997	Original Version

 - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
*/
static int __save_message(MAILSTREAM *stream, char *header, int headersize, char *text, int textsize)
  {
     DBGPRINT(DBG$SHOWMORE,"__save_message: Saving header, size=[%d]", headersize, 0, 0, 0);
     if (LOCAL->header)  fs_give((void **) &LOCAL->header);
     LOCAL->header = fs_get( (LOCAL->headersize=headersize)+1);
     memcpy(LOCAL->header,  header,  headersize);

     DBGPRINT(DBG$SHOWMORE,"__save_message: Saving text  , size=[%d]", textsize, 0, 0, 0);
     if (LOCAL->text)  fs_give((void **) &LOCAL->text);
     LOCAL->text = fs_get( (LOCAL->textsize=textsize)+1);
     memcpy(LOCAL->text,    text,    textsize);

     return headersize+textsize;
  }

/*
 - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
   __is_mrtype

   Purpose:
	Examine the data field of the current header for one of the special
	keywords or phrases that suggests a Message Router / X.400 header.

   Inputs:
	a		Pointer to the data portion of a header field
			(assumed to be an originator or sender field only)
	n		length of the data

   Outputs:
	NONE

   Function Return Value:
	1		If header contains the Message Router/X.400 phrases
	0		If it doesn't.

   Comments:
	I'm indebted to Claudio Allocchio for information about the Message
	Router/X.400 formats.

   Revision History:
	1.0	Andy Harper	2-OCT-1997	Original Version

 - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
*/
static int __is_mrtype(char *a, int n)
  {
    static char *mrkeywords[] = {"\tNAME:","\tFUNC:","\tINITLS:"};
    static char *mraddrkeys[] = {"@C=","@A=","@P=","DD.RFC-822"};
    static int   mrkeynum     = sizeof(mrkeywords)/sizeof(mrkeywords[0]);
    static int   mradrnum     = sizeof(mraddrkeys)/sizeof(mraddrkeys[0]);
    int m;

    DBGPRINT(DBG$ENTRY,"__is_mrtype: entered, field data=[%.*s]", n, a, 0, 0);


				/* Does this start with special keywords ?? */
    for (m=0; m<mrkeynum; m++)
      if (strfind(a, n, mrkeywords[m]) == a)
        return 1;

				/* Does this contain special address flags ?? */
    for (m=0; m<mradrnum; m++)
      if (strfind(a, n, mraddrkeys[m]) != NULL)
        return 1;

				/* Nothing detected, not an MRtype record */
    DBGPRINT(DBG$EXIT,"__is_mrtype: exit, nothing found",0,0,0,0);
    return 0;
  }

/*
 - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
   __is_rfc822

   Purpose:
	This routine examines the header record and returns T if it looks like
	an RFC822 header and NIL otherwise.

   Inputs:
	record		Header record buffer
	size		Size of the buffer

   Output:
	originate	Flagged if field is an originator
	recipient	Flagged if field is a recipient
	MRtype		Flagged if field has special Message Router format
	suspect		Flagged if field is not recognized

   Comments:
	Because of the volume of different RFC822 fields, and the widespread
	use of non-standard fields not beginning with 'X-', it is not possible
	to reliably determine whether a field is an acceptable header.

	This routine examines the format of the record to see if it looks like
	the general form of a header:
	   label : data

	where
	   label consists of alphanumeric characters, minus and hyphen only
           label is followed by a colon
	   There may be optional blanks between label and colon
           
	If it looks like it might be a header, further information about the
	field is returned:
	   originate	True if field is a standard source field
           recipient    True if field is a standard destination field
	   MRtype	True if field is in Message Router format
           suspect	True if field doesn't match the known list of field
			names and is not an X-... extension.


   Return Value:
	T		Record appears to be an RFC822 mail header
	NIL		Record is not a header

   Revision History:
	1.0	Andy Harper	30-SEP-1996	Original Version
	1.1	Andy Harper	26-SEP-1997	Ignore label values, syntax only

 - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
*/

static int __is_rfc822_header(char *record, int size, int *originate, int *recipient, int *MRtype, int *suspect)
  {
    static char labelchars[]=
       "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789-_";

    static char *labelprefix[] = {
	"x400-",
	"resent-",
	"original-",
	"originally-",
	"original_",
	"originally_",
	"apparent-",
	"apparently-",
	"apparent_",
	"apparently_"
      };

    static char *fieldlist[] = {
        "alternate-recipient",
        "approved",
	"autoforwarded",
	"autosubmitted",
	"bcc",
	"cc",
	"comment",
	"comments",
	"content-identifier",
	"content-language",
	"content-transfer-encoding",
	"content-type",
	"control",
	"conversion-with-loss",
	"conversion",
	"date",
	"delivery-date",
        "delivery-receipt-to",
	"discarded-ipms-extensions",
	"discarded-mts-extensions",
        "disposition-notification-to",
	"distribution",
	"dl-expansion-deferred-delivery",
        "dl-expansion-history",
	"encrypted",
        "errors-to",
	"expires",
	"followup-to",
	"from",
	"importance",
	"in-reply-to",
	"incomplete-latest-delivery-time",
	"keywords",
	"lines",
	"message-id",
	"message-original-encoded-information-types",
	"mime-version",
	"mts-identifier",
	"newsgroups",
	"organization",
	"originator-return-address",
	"originator",
	"path",
	"priority",
        "precedence",
	"read-receipt-to",
	"received",
	"recipients",
	"references",
	"relay-version",
	"reply-by",
	"reply-to",
	"return-path",
        "return-receipt-to",
	"sender",
	"sensitivity",
        "status",
	"subject",
	"supercedes",
	"supersedes",
	"to",
	"via",
        "warnings-to"
      };

    static char *originatefields[]= {
	"from",
        "reply-to",
        "reply_to",
        "return-path",
        "return_path",
	"sender"
       };

    static char *recipientfields[]= {
	"bcc",
	"cc",
	"to"
       };

    static int
	prefixsize=sizeof(labelprefix) / sizeof(labelprefix[0]),
	fieldsize= sizeof(fieldlist) / sizeof(fieldlist[0]),
	origsize = sizeof(originatefields) / sizeof(originatefields[0]),
        recpsize = sizeof(recipientfields) / sizeof(recipientfields[0]);


    int n,m;


		/* Initialize information fields */
    *originate = *recipient = *MRtype = *suspect = 0;


    		/* Scan for the format  "label : field" */
    if ( ((n=strscan(record, size, labelchars)) < size)   && 
         ((m=strscan(record+n, size-n, " \t"))  < size-n) &&
         ( record[n+m] == ':'))
	    {
	       int len = n+m+1;

			/* Look for an originator field */
	       for (m=0; m<origsize; m++)
                 if ( (n==strlen(originatefields[m])) && strcasecmpn(record, originatefields[m], n) )
	           {*originate = 1; *MRtype = __is_mrtype(record+len,size-len); return T;}


			/* Look for a recipient field */
	       for (m=0; m<recpsize; m++)
                 if ( (n==strlen(recipientfields[m])) && strcasecmpn(record, recipientfields[m], n) )
	           {*recipient = 1; *MRtype = __is_mrtype(record+len,size-len); return T;}


			/* If it's an extension field, it's not unknown */
	       if ( (n>2) && strcasecmpn(record, "X-", 2))
		 return T;

			/* Strip prefixes, then test for known label  */
	       for (m=0; m<prefixsize; m++)
                 if ( (n>=(len=strlen(labelprefix[m]))) && strcasecmpn(record, labelprefix[m], len) )
                   {record+=len; n-=len; break;}
	       for (m=0; m<fieldsize; m++)
                 if ( (n==strlen(fieldlist[m])) && strcasecmpn(record, fieldlist[m], n) )
                   return T;


			/* Field label not recognized, set suspect flag */
               *suspect = 1;
               return T;
	    }


		/* Does not have a labelled field format */
    return NIL;
  }

/*
 - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
   _is_hdr_block

   Purpose:
	Check the message, up to the next blank line, to see if the format
	looks like a set of RFC822 headers. 

   Inputs:
	message		Pointer to the text of the message
	size		length of the message

   Outputs:
	headertype	Flag for type of headers found (undefined if not a
			header block):
			  1 Message Router/X.400
			  0 standard RFC822

   Function Return Value:
	n		Length of header section (including trailing blank line)
	0		If not headers

   Comments:
	Scan is terminated by:
	  First non-header line
	  End of message (blank line)

	We adopt a heuristic method to see if the block consists of
	headers.  Because of the wide variation in conformance to RFC822
	standards on field names, we adopt a slightly permissive approach.

	First, if any line is not in the right format, then the block is not
	headers and we retire immediately.

	Second, if the line does not have a recognized label, we flag the
	header block as suspect and move on to check the remaining lines

	Third, if the line is a recognized originating field or recipient
	field, we flag that fact (separately for each type).

	Finally, at the end of the header block, we use this algorithm:

         * If we've had no headers, this is not a valid header block

	 * Otherwise, If no suspect fields were flagged this is a valid header
	   block.

	 * Otherwise, if both an originator and recipient field were found then
	   this is a valid header block.

	 * Otherwise it's not a valid header block.

	I'm indebted to Claudio Allocchio for the algorithm

   Revision History:
	1.0	Andy Harper	10-SEP-1997	Original Version
	1.1	Andy Harper	26-SEP-1997	Rewrite algorithm

 - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
*/
static int _is_hdr_block(char *message, int size, int *headertype)
  {
    char *p         = message;
    int   hadheader = 0;
    int   originate = 0;
    int   recipient = 0;
    int   suspect   = 0;
    int   MRtype    = 0;
    int   o;
    int   r;
    int   s;
    int   m;
    int   rlen;

    *headertype = 0;
    while ( size>0 )
      {
        DBGPRINT(DBG$SHOWMORE,"_is_hdr_block: originate=[%d], recipient=[%d], MRtype=[%d], suspect=[%d]", originate, recipient, MRtype, suspect);
        if ( (rlen=strbreak(p, size, "\n")) == 0)
          rlen = size;

			/* Blank line?? Terminates headers */
        if (*p == '\n')
          { p++; break;}


			/* Continuation record (not on first line)?? */
        else if (isspace(*p))
           {if (!hadheader) return 0;}


			/* If looks like a header, set flags and go check next */
        else if ( __is_rfc822_header(p, rlen-1, &o, &r, &m, &s) )
           {hadheader=1; if (o) originate=1; if (r) recipient=1; if (m) MRtype=1; if (s) suspect=1;}

			/* Something else ??? */
        else
          return 0;

			/* Next line */
        p    += rlen;
        size -= rlen;
      }

			/* Analyze what we found */
    if (MRtype) *headertype = 1;
    DBGPRINT(DBG$SHOWMORE,"_is_hdr_block: originate=[%d], recipient=[%d], MRtype=[%d], suspect=[%d]", originate, recipient, MRtype, suspect);
    return (hadheader && (!suspect || (originate && recipient))) ? p-message : 0;
  }

/*
 - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
    __format_VMS_headers

   Purpose:
	Fill a buffer with simulated RFC822 headers, generated from the
	original VMS mail headers.

   Inputs:
	stream		Pointer to mailstream

   Outputs:
	vmsheaders	Pointer to output buffer (already allocated and of the
			right size or larger)

   Function Return Value:
	Total size of the generated headers buffer

   Revision History:
	1.0	Andy Harper	August 1997	Original Version

 - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
*/
static int __format_VMS_headers(char *vmsheaders, MAILSTREAM *stream)
  {
     char from[256],  to[256], cc[256], date[256], subj[256];
     short int flags;
     char *vmsheadptr = vmsheaders;
     int vmsheadsize;

     get_message_headers(&LOCAL->msg_context, LOCAL->msgnum, from, to, cc,  date, subj, &flags);

     DBGPRINT(DBG$SHOWMORE, "__format_VMS_headers: reformating FROM: '%s'", from,0,0,0);
     vmsheadptr  += sprintf(vmsheadptr, "From: ");
     vmsheadptr  += __rewrite_address(vmsheadptr, from, stritem(from, strlen(from), ", \t"));
     vmsheadptr  += sprintf(vmsheadptr, "\n");

     DBGPRINT(DBG$SHOWMORE, "__format_VMS_headers: reformating TO:   '%s'", to,0,0,0);
     vmsheadptr  += sprintf(vmsheadptr, "To: ");
     vmsheadptr  += __rewrite_address(vmsheadptr, to, strlen(to));
     vmsheadptr  += sprintf(vmsheadptr, "\n");

     DBGPRINT(DBG$SHOWMORE, "__format_VMS_headers: reformating CC:   '%s'", cc,0,0,0);
     vmsheadptr  += sprintf(vmsheadptr, "Cc: ");
     vmsheadptr  += __rewrite_address(vmsheadptr, cc, strlen(cc));
     vmsheadptr  += sprintf(vmsheadptr, "\n");

     DBGPRINT(DBG$SHOWMORE, "__format_VMS_headers: processing  DATE: '%s'", date,0,0,0);
     vmsheadptr  += sprintf(vmsheadptr, "Date: %s\n",    date);

     DBGPRINT(DBG$SHOWMORE, "__format_VMS_headers: processing  SUBJ: '%s'", subj,0,0,0);
     vmsheadptr  += sprintf(vmsheadptr, "Subject: %s\n", subj);
     vmsheadptr  += sprintf(vmsheadptr, "\n");
     vmsheadsize = vmsheadptr - vmsheaders;

     DBGPRINT(DBG$SHOWMORE,"__format_VMS_headers: VMS headers:\n---\n%.*s---", vmsheadsize, vmsheaders,0,0);
     return vmsheadsize;
  }

/*
 - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
   __prefix_fields

   Purpose:
	Scan the block of header lines and prefix each originator and recipient
	field with 'X-' to turn it into an extension header.

   Inputs:
	a	Pointer to header block
	n	Length of header block

   Outputs:
	output	Pointer to output buffer

   Function Return Value:
	Length of output string

   Comments:

   Revision History:
	1.0	Andy Harper	3-OCT-1997	Original Version

 - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
*/
static int __prefix_fields(char *output, char *a, int n)
  {
    char *outptr = output;
    int   orig,recip,m,s;
    int   rlen;

    while (n > 0)
      {
			/* Find end of record */
        if ( (rlen = strbreak(a, n, "\n")) == 0)
           rlen = n;

			/* If originator or recipient field, prefix it */
        if ( __is_rfc822_header(a, rlen-1, &orig, &recip, &m, &s) && (orig || recip) )
          outptr += sprintf(outptr, "X-");
          
			/* Copy header line as is */
        outptr += sprintf(outptr, "%.*s", rlen, a);

			/* Move to next record */
        a += rlen;
        n -= rlen;
      }

    return outptr-output;
  }

/*
 - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
   __format_mr_headers

   Purpose:
	Reformat headers

   Inputs:
	stream		Pointer to current mail stream
	header		Pointer to headers
	headersize	Length of headers
	text		Pointer to text
	textsize	Length of text

   Outputs:
	NONE

   Function Return Value:
	Total message size

   Comments:
	Take the Message Router/X400 format headers and add the reformatted
	VMS MAIL headers; if the first line of the body is the special
	RFC-822-Headers: label, then append the following additional RFC
	headers to give a new set of headers.

   Revision History:
	1.0	Andy Harper	3-OCT-1997	Original Version

 - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
*/
static int __format_mr_headers(MAILSTREAM *stream, char *header, int headersize, char *text, int textsize)
  {
    static char x400rfc822header[] = "RFC-822-Headers:\n";
    static int  x400rfclen         = sizeof(x400rfc822header)-1;

    char  *t    = text;
    int    tsize= textsize;

    int    size = headersize + (MAXRECORDSIZE*5*2 + 1); 
    int    n    = 0;
    char  *sbuff;
    char  *buff;
    char  *p;

				/* Special RFC Headers in body ?? */
    if ( (textsize>=x400rfclen) && (strncmp(text, x400rfc822header, x400rfclen) == 0) )
      {
       DBGPRINT(DBG$SHOWMORE,"__format_mr_headers: Located additional RFC headers",0,0,0,0);
       t     += x400rfclen;	/* Skip the 'additional headers' header */
       tsize -= x400rfclen;
       if ((p=strfind(t, tsize, "\n\n")) != NULL)
          {n = (p-t)+2;} 
       else
          {n = tsize;}

       DBGPRINT(DBG$SHOWMORE,"__format_mr_headers: Size of additional RFC headers is [%d]",n,0,0,0);
       size  += n;		/* Increase size needed for headers buffer */
       tsize -= n;		/* Reduce size of text part */
       t     += n; 		/* Update text pointer to new start of text */
      }

				/* Allocate space for the new header block */
    DBGPRINT(DBG$SHOWMORE,"__format_mr_headers: Combined header block size=[%d]",size,0,0,0);
    buff = sbuff = fs_get( size );


				/* Append the three sets of headers */
    DBGPRINT(DBG$SHOWMORE,"__format_mr_headers: Adding reformatted X400 headers",0,0,0,0);
    buff += __prefix_fields (buff, header, headersize);

    if ( *(buff-1) == '\n' ) buff--;
    DBGPRINT(DBG$SHOWMORE,"__format_mr_headers: Adding reformatted VMS MAIL headers",0,0,0,0);
    buff += __format_VMS_headers (buff, stream);

    if (n > 0) 
      {
        if ( *(buff-1) == '\n' ) buff--;
        DBGPRINT(DBG$SHOWMORE,"__format_mr_headers: Adding additional RFC headers",0,0,0,0);
        buff +=   sprintf (buff, "%.*s", n, text+x400rfclen);

    				/* Strip final FF character */
        if ( (n>=3) && (*(buff-3) == '\f') )
          {*(buff-3) = '\n'; buff-=2;}
      }

				/* Save the info in the LOCAL cache */
    n=__save_message(stream, sbuff, buff-sbuff, t, tsize);


				/* Remove the temporary header buffer */
    fs_give((void **) &sbuff);
    return n;
  }

/*
 - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
   __format_message

   Purpose:
	Format the headers of the message (and text body if applicable) based
	on the type of the header (see 'headertype').

   Inputs:
	stream		Pointer to mail stream
	headertype	Type of header
	header		Pointer to header text
	headersize	Length of header text
	text		Pointer to text body
	textsize	Length of text body

   Outputs:
	None

   Function Return Value:
	Size of message

   Comments:
	Values of 'headertype' can be:
	  *	0	Standard RFC822 headers
	  *	1	Headers generated by Message Router/X.400

	If standard headers, formatting is already done, so just save the
	message as is.  IF Message Router/X.400, then post-process the headers
	before saving.

   Revision History:
	1.0	Andy Harper	1-OCT-1997	Original Version

 - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
*/
static int __format_message(MAILSTREAM *stream, int headertype, char *header, int headersize, char *text, int textsize)
  {
    int n;

    DBGPRINT(DBG$ENTRY, "__format_message: entered, headertype=[%d]", headertype, 0, 0, 0);
    switch (headertype)
      {
	default:		/* Anything not recognized */
	case 0:			/* Standard RFC822 headers */
          DBGPRINT(DBG$SHOWMORE, "__format_message: handling default/rfc822 headers, header type=[%d]", headertype, 0,0,0);
          n=__save_message(stream, header, headersize, text, textsize);
          break;

        case 1:			/* Message router X.400 headers - COMPLEX !! */
          DBGPRINT(DBG$SHOWMORE, "__format_message: handling X.400 headers, header type=[%d]", headertype, 0,0,0);
          n=__format_mr_headers(stream, header, headersize, text, textsize);
          break;
      }

   DBGPRINT(DBG$EXIT, "__format_message: exit, total size=[%d]", n,0,0,0);
   return n;
  }

/*
 - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
  General interface used by the VMS_MAIL driver to access a message and produce
  a suitably reformatted RFC822-style message

  Routine is:

	__read_message		Return the headers and text body of the
				nominated message, with all reformatting and
				address modifications as required.

 - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
*/

/*
 - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
   __read_message

   Purpose:
	Obtain the text of a message from the selected folder, return the
	header and body strings.

   Inputs:
	stream		The mailstream
	msgno		The message to read

   Outputs:
	NONE

   Return Value:
	NONE

   Comments:
	A message has the standard VMS headers however it was delivered. But
	the body can also contain additional headers if the message was
	delivered by an SMTP delivery agent or an X.400 delivery agent. These
	are detected and used as the headers if they exist, otherwise the
	standard VMS headers are reformatted into an RFC822 like syntax.

	These additional headers are assumed to be at the start of the message
	body by default:

	[
          RFC822 headers
	  blank line
	]
	[ message body ]


	However, if the CCLIENT_HEADERS_ATEND logical name is defined with a
	text value, then it is assumed that the headers could have been added
	at the end of the message, separated from the message body by a single
	line comprised of this text value (in FULL and in the right case):

	[ message body ]
	[ separator line
          RFC822 headers
	  blank line
	]
	
	This routine takes care to avoid the use of the C null string
	terminator where possible in order to allow null bytes to appear in a
	message (this may not be usual but it's a restriction that should not
	be imposed, so we don't).

	We use the LOCAL->buf structure as a temporary buffer in which to read
	the message. It is resized if needed.

   Author:
	2.0	Andy Harper	2-OCT-1997	Complete rewrite

 - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
*/

static void __read_message (MAILSTREAM *stream, long msgno)
  {
     char           from[256], to[256], cc[256], date[256], subj[256];
     short          flags;
     unsigned long  msgsize;
     char          *headbuff = NULL;
     int            hblen=0;
     char          *vmsheader;
     int            vmsheadersize;
     char          *mbptr;
     char          *p;
     int            n;
     int            s;
     int            headertype;
     int            rcount;

     DBGPRINT(DBG$ENTRY,"__read_message: entered, mailbox='%s', msgno='%ld'",stream->mailbox, msgno,0,0);

				/* If user asking for different message clear old cached one */
     if (msgno == LOCAL->msgnum)
       {
         DBGPRINT(DBG$EXIT,"__read_message: exit, message in cache",0,0,0,0);
         return;
       }

     DBGPRINT(DBG$EXIT,"__read_message: old msgno = [%ld], new msgno=[%ld]", LOCAL->msgnum, msgno, 0, 0);
     LOCAL->msgnum = msgno;


 				/* Read the message into the current buffer */
     rcount = get_message_headers(&LOCAL->msg_context, msgno, from, to, cc, date, subj, &flags);
     msgsize=ReadMessage(&LOCAL->msg_context, msgno, &LOCAL->buflen, &LOCAL->buf, rcount, flags);
     mail_parse_date(mail_elt(stream,msgno), date);
     mbptr = LOCAL->buf;


				/* Initialize headers at end test */
     if ( (p = getenv("CCLIENT_HEADERS_ATEND")) != NULL)
       {
         DBGPRINT(DBG$SHOWMORE, "__read_message: 'at end' separator defined as [%s]", p,0,0,0);
         headbuff = (char *) fs_get( strlen(p) + 2 );
         hblen=sprintf(headbuff, "%s\n", p);
       }
       

				/* Test for headers at end */
     if (headbuff && (p=strfind(mbptr,msgsize,headbuff)) && ( (p==mbptr) || (*(p-1) == '\n') ) && (n = _is_hdr_block(mbptr,msgsize,&headertype)))
       {
         DBGPRINT(DBG$SHOWMORE,"__read_message: Found trailing headers,length=[%d]", n, 0, 0, 0);
         s=__format_message(stream, headertype, p+hblen, msgsize-(p+hblen-mbptr), mbptr, p-mbptr);
       }

				/* Test for headers at start */
     else if (n = _is_hdr_block(mbptr, msgsize,&headertype))
       {
         DBGPRINT(DBG$SHOWMORE,"__read_message: Found leading headers, length = [%d]", n, 0, 0, 0);
         s=__format_message(stream, headertype, mbptr, n, mbptr+n, msgsize-n);
       }

				/* No headers, use VMS info */
     else
       {
         DBGPRINT(DBG$SHOWMORE,"__read_message: No headers - using reformatted VMS fields", 0, 0, 0, 0);
         vmsheader     = (char *) fs_get(MAXRECORDSIZE*2*5 + 1);
         vmsheadersize = __format_VMS_headers(vmsheader, stream);
         s=__save_message(stream, vmsheader, vmsheadersize, mbptr, msgsize);
         fs_give((void **)&vmsheader);
       }



				/* Get rid of 'at end' string buffer space */
     if (headbuff)
       fs_give((void **)&headbuff);

     DBGPRINT(DBG$EXIT,"__read_message, exit, message size=[%d]", s,0,0,0);
  }

/*
 - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
   Purpose:
	Determine the VMS MAIL transport prefix

   Input:
	NONE

   Output:
	NONE

   Return Value:
	Pointer to prefix name, or NULL

   Comments:
	The prefix is determined from two logical names:
	  *  PINE_MAIL_PROTOCOL
          *  MAIL$INTERNET_TRANSPORT

	The latter is usually defined for OpenVMS 6.2 and above

   Author:
	Andy Harper, Kings College London, Jan 1997

 - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
*/
static char *__transport_name(void)
  {
     char *p;

     if ((p=getenv("PINE_MAIL_PROTOCOL")))
       return p;

     else if ((p=getenv("MAIL$INTERNET_TRANSPORT")))
       return p;

     else
       return NULL;
  }

/*
 - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

   Purpose:
	Parse a mailbox specification and convert into a mailfile and folder
	specification. Defaults are added where needed.

   Input:
	name:		The mailbox name in standard format

   Outputs:
	mailfile	Pointer to VMS filename buffer, or NULL
	folder		Pointer to folder in mailfile buffer, or NULL
	inbox		Pointer to 'is an INBOX' flag

   Returns:
  	NIL mailbox parse error
	T   all ok

   Comments:
	The syntax of a mailbox specification is one of:
	  *	INBOX		The default folder where new mail is delivered
	  *     folder		A folder in the current user's default mailfile
	  *	~[user]/folder	A folder in [user]'s home directory
	  *	~[user]/dir/folder
				A folder in [user]'s mail file in "[.dir]"

	The general ~[user] format is currently not implemented due to security
	restrictions; if omitted, the current user is assumed.

        The filename part is always "MAIL.MAI" and this should not be
	specified in the pathname.

        A NULL folder name is an error, any other format is invalid.

	Except for INBOX, all folder names are case-sensitive.

	The INBOX can be mapped to any folder in the default mail file by
	defining the logical name CCLIENT_INBOXFOLDERNAME to the name of the
	desired folder; the default is "NEWMAIL".

   Author:
	2.0	Andy Harper	1-OCT-1997	Complete rewrite

 - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
*/

static int __parse_mailbox_name (char *name, char *mailfile, char *folder, int *inbox)
  {
     char temp[MAILFILESIZE];
     char *usermaildir = __get_mail_directory(NULL);
     char *mailfileptr = mailfile;
     char *useribox    = getenv("CCLIENT_INBOXFOLDERNAME");
     char *inboxname   = useribox ? useribox : "NEWMAIL";

     char *username;
     char *t=temp;
     char *p;

     DBGPRINT(DBG$ENTRY,"__parse_mailbox_name: entry, name=[%s]", name, 0,0,0);


				/* Convert special reserved name "INBOX" ??? */
     if ( __strcasecmp(name, "INBOX") )
      {
        DBGPRINT(DBG$SHOWMORE,"__parse_mailbox_name: INBOX: [%sMAIL.MAI, %s]",usermaildir,inboxname,0,0);
        name = inboxname;
      }


				/* Unqualified folder name ?? */
     if ( *name != '~' )
      {
        DBGPRINT(DBG$SHOWMORE,"__parse_mailbox_name: Folder [%sMAIL.MAI, %s]",usermaildir,name,0,0);
        if (mailfile) sprintf(mailfile, "%sMAIL.MAI", usermaildir);
        if (folder)   sprintf(folder,   "%s", name);
        *inbox = strcmp(name, inboxname) == 0 ? 1 : 0;
        return T;
      }

     

				/* Start of '~username...' format ?? */
     username = ++name;
     while (*name != '/')
       name++;

				/* Don't currently implement other users ! */
     if (username != name)
       {
         DBGPRINT(DBG$EXIT,"__parse_mailbox_name: [~user] format not available [%.*s]", name-username, username, 0, 0);
         return NIL;
       }


  				/* Append subdirectories to home directory */
     name++;
     t += sprintf(t, "%s", __get_home_directory(NULL)) - 1;
     while ( (p=strchr(name, '/')) != NULL)
       {
         if (p == name)
           {
             DBGPRINT(DBG$EXIT,"__parse_mailbox_name: null directory name",0,0,0,0);
             return NIL;
           }
         t += sprintf(t, ".%.*s", p-name, name);
         name = p+1;
       }
     t += sprintf(t, "]");

				/* Check folder name */
     if (!*name)
       {
         DBGPRINT(DBG$EXIT,"__parse_mailbox_name: null folder name",0,0,0,0);
         return NIL;
       }

						/* Copy to parameters */
        DBGPRINT(DBG$SHOWMORE,"__parse_mailbox_name: Folder [%sMAIL.MAI, %s]",temp,name,0,0);
     if (mailfile) sprintf(mailfile, "%sMAIL.MAI", temp);
     if (folder)   sprintf(folder, "%s", name);

						/* Are we the INBOX ? */
     *inbox = (strcmp (name, inboxname) == 0) && __strcasecmp(temp, usermaildir);

     return T;
  }

/*
 - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
   Purpose:
	Create an empty file from the info contained in the FDL string

   Inputs:
	mailfile		Name of mailfile to be created
	fdl			FDL string
	fdlsize			Size of the FDL string

   Outputs:
	NONE

   Return Value:
	T			If file created OK
	NIL			If file not created

   Comments:

   Author:
	Andy Harper, Kings College London, Jan 1997

 - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
*/
static int __fdl_create_file(char *mailfile, char *fdl, int fdlsize)
  {
     int status, sts, stv;
     int flags   = FDL$M_FDL_STRING;
     struct dsc$descriptor
	fdl_desc = {fdlsize,          DSC$K_DTYPE_T, DSC$K_CLASS_S, fdl},
	filename = {strlen(mailfile), DSC$K_DTYPE_T, DSC$K_CLASS_S, mailfile};

     DBGPRINT(DBG$ENTRY,"__fdl_create_file: entered, mailfile='%s'",mailfile,0,0,0);
     status = fdl$create(&fdl_desc, &filename, NULL, NULL, NULL, &flags, NULL, NULL, &sts, &stv);
     if (!chkstat("__fdl_create_file: fdl$create", 0, status))
       {
         chkstat("__fdl_create_file: fdl$create sts", 0, sts);
         chkstat("__fdl_create_file: fdl$create stv", 0, stv);
         return NIL;
       }

     DBGPRINT(DBG$EXIT,"__fdl_create_file: exit",0,0,0,0);
     return T;
  }

/*
 - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
   Purpose:
	Obtain an FDL description of a file into a string.

   Inputs:
	file			Name of file
        maxfdlsize		Size of output buffer

   Outputs:
	fdl			Returns the FDL string of the file
	fdlsize			Returns the string length

   Return Value:
	T			Obtained FDL ok
	NIL			FDL unobtainable

   Comments:
	In order to determine the key structure, we first get an XABSUM block
	which contains the number of keys and areas. We then use this to set up
	a dynamic XAB structure for this number of keys and areas.

	We use FDL$GENERATE to get details of the file. Note that the
	documentation on the output parameter is WRONG (as at time of writing)
	as it states that this must be a fixed-length string descriptor. It
	fact, it must be a dynamic string descriptor but with the buffer
	address pointer set to NULL. The buffer is allocated dynamically within
	FDL$GENERATE and must be copied and disposed of manually.

   Author:
	Andy Harper, Kings College London, Jan 1997

 - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
*/
static int __fdl_generate(char *file, char *fdl, int maxfdlsize, int *fdlsize)
  {
    struct FAB    fab             = cc$rms_fab;
    struct RAB    rab             = cc$rms_rab;
    struct XABSUM xabsum          = cc$rms_xabsum;	/* XAB Summary info */
    struct XAB    *xabptr;

    int noa, nok;
    char keyname[25][32];			/* Space for 25 key names !! */
    struct XABKEY **xabkey;
    struct XABALL **xaball;

    int ret=NIL;				/* Default return status */
    int status;					/* VMS system status returns */


    DBGPRINT(DBG$ENTRY,"__fdl_generate: entered, file='%s'",file,0,0,0);
						/* Set up filename */
    fab.fab$l_fna = file;
    fab.fab$b_fns = strlen(file);

						/* Open file, get basic info */
    fab.fab$l_xab = &xabsum;
    status = sys$open(&fab);
    if (!chkstat("__fdl_generate: sys$open", 0, status)) return NIL;

						/* Set up RAB */
    rab.rab$l_fab = &fab;
    status = sys$connect(&rab);
    if (!chkstat("__fdl_generate: sys$connect", 0, status))
      {status=sys$close(&fab); return NIL;}

						/* Set up the area XABs ... */
    xaball = (struct XABALL **) &fab.fab$l_xab;
    for (noa=0; noa<xabsum.xab$b_noa; noa++)
      {
         *xaball = malloc(sizeof(struct XABALL));
         **xaball= cc$rms_xaball;
         (*xaball)->xab$b_aid = noa;		/* Area number */
         xaball  = (struct XABALL **) &(*xaball)->xab$l_nxt;
      }

						/* Set up the key XABs ... */
    xabkey = (struct XABKEY **) xaball;
    for (nok=0; nok<xabsum.xab$b_nok; nok++)
      {
         *xabkey = malloc(sizeof(struct XABKEY));
         **xabkey= cc$rms_xabkey;
         (*xabkey)->xab$b_ref = nok;		/* Key number */
	 (*xabkey)->xab$l_knm = keyname[nok];	/* Key name */
         xabkey  = (struct XABKEY **) &(*xabkey)->xab$l_nxt;
      }

						/* Re-fill all XAB blocks */
    status = sys$display(&fab);
    if (chkstat("__fdl_generate: sys$display",0,status))
      {
        struct dsc$descriptor FDLdesc;
        struct FAB *fabptr   = &fab;
        struct RAB *rabptr   = &rab;
        int FDLflags         = FDL$M_FDL_STRING;
						/* Parse FAB/RAB to get FDL */
        FDLdesc.dsc$w_length = maxfdlsize;
        FDLdesc.dsc$b_dtype  = DSC$K_DTYPE_T;
        FDLdesc.dsc$b_class  = DSC$K_CLASS_D;
        FDLdesc.dsc$a_pointer= NULL;
        status = fdl$generate(&FDLflags, &fabptr, &rabptr, NULL, NULL, &FDLdesc, NULL, fdlsize);
        if (chkstat("__fdl_generate: fdl$generate", 0, status))
          {
						/* Copy to the receive buffer */
            memcpy(fdl, FDLdesc.dsc$a_pointer, *fdlsize);
            fdl[*fdlsize] = '\0';
						/* Dispose of dynamic buffer */
            status = lib$sfree1_dd(&FDLdesc);
            chkstat("__fdl_generate: lib$sfree1_dd", 0, status);
            ret=T;
          }
      }

						/* Close the file */
    status = sys$close(&fab);
    chkstat("__fdl_generate: sys$close", 0, status);

						/* Dispose of area/key XAB blocks */
    xabptr = fab.fab$l_xab;
    while (xabptr)
      {
         struct XAB *ptr = xabptr;
         xabptr = xabptr->xab$l_nxt;
         free(ptr);
      }
        
    DBGPRINT(DBG$EXIT,"__fdl_generate: exit, status='%d'",ret,0,0,0);
    return ret;
  }

/* VMS/MAIL mail lock file for parse/append permission
 * Accepts: file descriptor
 *	    lock file name buffer
 *	    type of locking operation (LOCK_SH or LOCK_EX)
 * Returns: file descriptor of lock or -1 if failure
 */

static int vms_mail_lock (FILE *fd, char *lock, int op)
{
 return 0;		/* no locking needed */
}


/* VMS/MAIL mail unlock file for parse/append permission
 * Accepts: file descriptor
 *	    lock file name from vms_mail_lock()
 */

static void vms_mail_unlock (FILE *fd, char *lock)
{
  return;
}

/*
 - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
   Purpose:
	Add a newly created mailfile/folder combination to the list of 'created'
	mailboxes.

   Inputs:
	mailfile		Pointer to name of mailfile
	folder			Pointer to name of folder in mailfile

   Outputs:
	NONE

   Return Value:
	NONE

   Author:
	Andy Harper, Kings College London, Jan 1997

 - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
*/

static void __Add_New_Mailbox(char *mailfile, char *folder)
  {
     struct MailBoxList *mb = fs_get( sizeof(struct MailBoxList) );

     DBGPRINT(DBG$ENTRY,"__Add_New_Mailbox, entered, mailfile='%s',folder='%s'",mailfile,folder,0,0);


					/* Link to head of chain */
     mb->next = MailBoxes;

					/* Copy items into structure */
     mb->mailfile = fs_get( strlen(mailfile) + 1);
     strcpy(mb->mailfile, mailfile);
     mb->folder   = fs_get( strlen(folder)   + 1);
     strcpy(mb->folder, folder);

					/* Link master pointer */
     MailBoxes = mb;

     DBGPRINT(DBG$EXIT,"__Add_New_Mailbox: exit",0,0,0,0);
  }

/*
 - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
   Purpose:
	Search the list of 'created' mailboxes

   Inputs:
	mailfile		Pointer to name of mail file
	folder			Pointer to name of folder

   Outputs:
	NONE

   Return Value:
	T			if found
	NIL			if not found

   Author:
	Andy Harper, Kings College London, Jan 1997

 - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
*/

static int __search_new_mailboxes(char *mailfile, char *folder)
  {
     struct MailBoxList *mb;

     DBGPRINT(DBG$ENTRY,"__search_new_mailboxes: entered, mailfile='%s', folder='%s'",mailfile,folder,0,0);
     for (mb=MailBoxes; mb; mb=mb->next)
       {
         DBGPRINT(DBG$SHOWMORE,"__search_new_mailboxes: checking mailfile='%s', folder='%s'",mb->mailfile,mb->folder,0,0);
         if ( (__strcasecmp(mb->mailfile, mailfile)   ) &&
              (strcmp(mb->folder,   folder)   == 0) )
           {
             DBGPRINT(DBG$EXIT,"__search_new_mailboxes: exit, found",0,0,0,0);
             return T;
           }
        }

     DBGPRINT(DBG$EXIT,"__search_new_mailboxes: exit, not found",0,0,0,0);
     return NIL;
  }

/*
 - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
   Purpose:
	Process 'created' folders in specified mailfile

   Inputs:
	action		Ptr to action function
	fp		Ptr to folder pattern structure

   Outputs:
	fp		Ptr to folder pattern structure

   Return Value:
	NONE

   Author:
	Andy Harper, Kings College London, May 1997

 - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
*/
static void __scan_new_mailboxes(int (*action)(), struct FolderPattern *fp)
  {
     struct MailBoxList *mb;
     struct DESC         FolderName;

     DBGPRINT(DBG$ENTRY,"__scan_new_mailboxes: entered, mailfile='%s'",fp->mailfile,0,0,0);
     for (mb=MailBoxes; mb; mb=mb->next)
       {
         DBGPRINT(DBG$SHOWMORE,"__scan_new_mailboxes: checking mailfile='%s', folder='%s'",mb->mailfile, mb->folder,0,0);
         if ( __strcasecmp(mb->mailfile, fp->mailfile) )
           {
             DBGPRINT(DBG$SHOWMORE,"__scan_new_mailboxes: processing folder '%s'", mb->mailfile, 0, 0, 0);
             FolderName.address=mb->folder;
             FolderName.length=strlen(mb->folder);
             (*action)(fp, &FolderName);
           }
        }
     DBGPRINT(DBG$EXIT,"__scan_new_mailboxes: exit",0,0,0,0);
  }

/*
 - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
   Purpose:
	Remove a mailbox from the special list of 'created' mailboxes. This
	should be called when a mailbox is deleted.

   Inputs:
	mailfile	The name of the mailfile
	folder		The name of the folder

   Outputs:
	NONE

   Function Return Value:
	NONE

   Author:
	Andy Harper, Kings College London, May 1997

 - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
*/
static void __Delete_New_Mailbox(char *mailfile, char *folder)
  {
     struct MailBoxList **mbptr = &MailBoxes;

     DBGPRINT(DBG$ENTRY, "__Delete_New_Mailbox: mailfile=%s, folder=%s", mailfile, folder, 0, 0);     

				/* Scan the list */
     while (*mbptr)
       {
				/* Does this mailbox match ? */
         DBGPRINT(DBG$SHOWMORE,"__Delete_New_Mailbox: cur link: ptr=0x%08x, link=0x%08x, next=0x%08x", mbptr, *mbptr, (*mbptr)->next, 0);
         if ( (__strcasecmp((*mbptr)->mailfile, mailfile)   ) &&
              (strcmp((*mbptr)->folder,   folder)   == 0) )
            { 
				/* temp copy of CURRENT pointer */
              struct MailBoxList *mb = *mbptr;

				/* Relink next to previous, skip current */
              *mbptr = (*mbptr)->next;
              DBGPRINT(DBG$SHOWMORE,"__Delete_New_Mailbox: new link: ptr=0x%08x, link=0x%08x", mbptr, *mbptr, 0, 0);

				/* Free up space occupied by current */
              free (mb->mailfile);
              free (mb->folder  );
              free (mb);
	      return;
            }
         mbptr = &(*mbptr)->next;
       }
    DBGPRINT(DBG$EXIT, "__Delete_New_Mailbox: exit, no matching link",0,0,0,0);
  }

/*
 - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
   Purpose:
	Rename a mailbox in the 'new' list

   Inputs:
	oldmfile	Pointer to old mailfile name
	oldfolder	Pointer to old folder name
	newmfile	Pointer to new mailfile name
	newfolder	Pointer to new folder name

   Outputs:
	NONE

   Function Return Value:
	NONE

   Comments:

   Revision History:
	1.0	Andy Harper	12-NOV-1997	Original Version

 - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
*/
static void __Rename_New_Mailbox(char *oldmfile, char *oldfolder, char *newmfile, char *newfolder)
  {
     struct MailBoxList *mbptr = MailBoxes;

     DBGPRINT(DBG$ENTRY, "__Rename_New_Mailbox: Entered, mailfile [%s], folder [%s]", oldmfile, oldfolder, 0, 0);
     while (mbptr)
       {
         DBGPRINT(DBG$SHOWMORE, "__Rename_New_Mailbox: Checking mailfile [%s], folder [%s]", mbptr->mailfile, mbptr->folder, 0, 0);
         if ( (__strcasecmp(mbptr->mailfile, oldmfile)   ) &&
              (strcmp(mbptr->folder,   oldfolder)   == 0) )
            { 
              DBGPRINT(DBG$SHOWMORE, "__Rename_New_Mailbox: freeing old space",0,0,0,0);
              free(mbptr->mailfile);
              free(mbptr->folder);

              DBGPRINT(DBG$SHOWMORE, "__Rename_New_Mailbox: Replacing with mailfile [%s], folder [%s]", newmfile, newfolder, 0, 0);
              mbptr->mailfile = fs_get( strlen(newmfile) +1 );
              mbptr->folder   = fs_get( strlen(newfolder)+1 );
              strcpy(mbptr->mailfile, newmfile);
              strcpy(mbptr->folder,   newfolder);
              break;
            }
         mbptr = mbptr->next;
       }
    DBGPRINT(DBG$EXIT, "__Rename_New_Mailbox: exit",0,0,0,0);
  }

/*
 - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
   Purpose:
	Action routine to process each folder found by the FIND ALL command
	It simply notifies the application of the folder name, via the
	mm_mailbox callback.

   Inputs:
	fpi			Folder pattern information
	FolderName		Name of latest folder found in mailbox

   Outputs:
	fpi			As above; contains return status flag

   Return Value:
	Ignored

   Side Effects:
	Calls mm_mailbox with the foldername; the application may do something
	with this name.

   Comments:
	The fpi structure contains the pattern to be matched against and
	returns the count of matches found. Any folder which matches the
	pattern is notified to the application using the mm_mailbox callback.
	The matched name is prefixed by the string in the data field of the fpi
	structure

	We hide the real name of the INBOX folder.

   Author:
	Andy Harper, Kings College London, Jan 1997

 - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
*/
static int __match_folders(struct FolderPattern *fpi, struct DESC *FolderName)
  {
    char mailbox[MAILFILESIZE];
    int inbox;

    if ( (FolderName==NULL) || (FolderName->length == 0) )/* End of list */
      {
        DBGPRINT(DBG$EXIT,"__match_folders: end of folder list",0,0,0,0);
        return 0;
      }

					/* Get the file mailbox name */
    sprintf(mailbox, "%s%.*s", fpi->prefix, FolderName->length, FolderName->address);
    
					/* Register if it matches the pattern */
    if (matchwild(fpi->pattern, mailbox) == T)
      if (__parse_mailbox_name(mailbox, NULL, NULL, &inbox) && !inbox)
        { fpi->count++; mm_mailbox( mailbox ); }

    DBGPRINT(DBG$EXIT,"__match_folders: pattern='%s', count='%3d', folder='%.*s'", fpi->pattern, fpi->count, FolderName->length, FolderName->address);
    return 1;
}

/*
 - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
   Purpose:
	Action routine to locate a particular folder within a mail file.

   Inputs:
	fpi			Folder pattern information
	FolderName		Name of latest folder found in mailbox

   Outputs:
	fpi			As above; contains return status flag

   Return Value:
	Ignored

   Comments:
	The fpi structure contains the folder name required. If found, the
	count field is incremented.

   Author:
	Andy Harper, Kings College London, Jan 1997

 - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
*/
static int __find_folder(struct FolderPattern *fpi, struct DESC *nextfolder)
  {
				 	/* End of list */
    if( (nextfolder==NULL)  ||  (nextfolder->length == 0) )
      {
        DBGPRINT(DBG$EXIT,"__find_folder: end of folder list",0,0,0,0);
        return 0;
      }

					/* See if this matches what we want */
    if (nextfolder->length == strlen(fpi->pattern))
      if ( strncmp(nextfolder->address, fpi->pattern, nextfolder->length) == 0)
        fpi->count++;


    DBGPRINT(DBG$EXIT,"__find_folder: '%s': count '%3d', folder '%.*s'", fpi->pattern, fpi->count, nextfolder->length, nextfolder->address);
    return 1;
}

/*
 - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
   Purpose:
	Get the names of each folder in a mailfile, together with the
	dummy mailboxes in the 'created' list, and pass each to an
	action routine along with some userdata.

   Inputs:
	context		Address of the mailfile context
	routine		Address of function to call
	userdata	Address of userdata to pass

   Outputs:
	NONE

   Return Value:
	NONE

   Author:
	Andy Harper, Kings College London, Jan 1997

 - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
*/
static void __scan_folders(int *context, int (*action)(), void *userdata)
  {
    DBGPRINT(DBG$ENTRY, "__scan_folders: entered, action = [0x%08x], userdata=[0x%08x]",action, userdata, 0, 0);

    DBGPRINT(DBG$SHOWMORE,"__scan_folders: scanning 'created' list",0,0,0,0);
    __scan_new_mailboxes(action, userdata);

    DBGPRINT(DBG$SHOWMORE,"__scan_folders: scanning folders in mailfile",0,0,0,0);
    __mailfile_scan(context, action, userdata);

    DBGPRINT(DBG$EXIT, "__scan_folders: exit",0,0,0,0);
  }

/*
 - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
   Purpose:
	Copy a set of messages from the current stream to another mailbox

   Inputs:
	stream		Pointer to current mail stream
	mailbox		name of new mailbox
	delete		Flag if original message should be marked deleted after
			the copy:  1 = delete, 0 = keep.
   Outputs:
	NONE

   Function Return Value
	1		If copy succeeded
	0		If some messages not copied

   Comments:
	The sequence flag on each message is interrogated to see if it should
	be copied

   Revision History:
	2.0	Andy Harper	7-OCT-1997	Rewrite
   
 - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
 */
static long __copy_messages (MAILSTREAM *stream, char *mailbox, int delete)
  {
    char outmfile[MAILFILESIZE],outfolder[FOLDERSIZE];
    int inbox;
    long i;

    DBGPRINT(DBG$ENTRY,"__copy_messages: entered, stream='%s', destination='%s'", stream->mailbox, mailbox, 0, 0);

    if (__parse_mailbox_name(mailbox, outmfile, outfolder, &inbox) == NIL)
      {
        mm_log("invalid destination folder", ERROR);
        return NIL;
      }

    for (i=1; i <= stream->nmsgs; i++)
      if (mail_elt(stream,i)->sequence)
        {
         if (!__message_copy(&LOCAL->msg_context,i,outmfile,outfolder,delete))
           {mm_log("message copy failed", ERROR); return NIL;}
         if (delete)
           {mail_elt(stream,i)->deleted=T; __update_message_status(stream, i, NIL);}
        }

    DBGPRINT(DBG$EXIT,"__copy_messages: exit",0,0,0,0);
    return LONGT;
  }

/*
 - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
   Purpose:
	If this is the INBOX folder and the auto-file inbox flag is set, then
	move all SEEN messages from the INBOX to the specified mailbox.

   Input:
	stream		The mail stream to be checked

   Output:
	NONE

   Return Value:
	NONE

   Comments:
	This is normally called during a close operation and will normally have
	been preceded by an expunge operation, so some messages flagged as SEEN
	will already have been deleted!

	The LOCAL->inbox flag is used to check if this an inbox

	The logical name CCLIENT_AUTOFILEINBOX defines which mailbox the
	messages are to be moved into. If it is not defined, then INBOX
	messages are not moved (leave undefined for standard PINE/IMAPD
	compatibility, define to "MAIL" for VMS MAIL compatibility).

    Author:
	Andy Harper, Kings College London, Jan 1997

 - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
 */
static void __file_inbox_messages(MAILSTREAM *stream)
  {
    char *nf = getenv("CCLIENT_AUTOFILEINBOX");
    char mailfile[MAILFILESIZE], folder[FOLDERSIZE];
    char temp[256];
    long int i;
    int inbox;
    int f;
    long filed=0;

    DBGPRINT(DBG$ENTRY,"__file_inbox_messages: entered, mailbox='%s'", stream->mailbox, 0, 0, 0);
				
    if (!LOCAL->inbox)		/* Is this the INBOX ? */
      {
        DBGPRINT(DBG$EXIT,"__file_inbox_messages: exit, folder not INBOX", 0, 0, 0, 0);
        return;
      }


    if (!nf)			/* Is the autofile option set up? */
      {
        DBGPRINT(DBG$EXIT,"__file_inbox_messages: exit, auto-file logical not defined", 0, 0, 0, 0);
        return;
      }

				/* Validate mailbox specification */
    if ( __parse_mailbox_name(nf, mailfile, folder, &inbox) == NIL)
      {
        DBGPRINT(DBG$EXIT,"__file_inbox_messages: exit, invalid destination mailbox",0,0,0,0);
        return;
      }


				/* Copy messages SEEN but not DELETED */
    DBGPRINT(DBG$SHOWMORE,"__file_inbox_messages: filing seen messages to '%s'", nf, 0, 0, 0);
    for(i = 1; i <= stream->nmsgs; i++)
     {
       f = LOCAL->msgs[i-1].flags;
       if( (f & fSEEN)  &&  !(f & fDELETED) )
        {
					/* Copy and delete the message, */
          DBGPRINT(DBG$SHOWMORE,"__file_inbox_messages: filing message '%d'", i,0,0,0);
          if (!__message_copy(&LOCAL->msg_context, i, mailfile, folder,1))
            {mm_log("Auto-file of INBOX messages not completed",WARN); return;}
          filed++;
        }
     }


    if (filed)
      {sprintf(temp,"auto-filed %d INBOX messages to %s", filed, nf); mm_log(temp, NIL); }    

    DBGPRINT(DBG$EXIT,"__file_inbox_messages: exit",0,0,0,0);
  }

/*
 - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
   Purpose:
	Test whether a particular mailbox exists by opening the mailfile and
	scanning for the folder.

   Inputs:
	mailbox			The mailbox to check
	mailcontext		Pointer to existing mail context, or NULL
	close			Leave open, or close when done

   Output:
	mailfile		Pointer to parsed mailfile buffer; or NULL
	folder			Pointer to parsed folder buffer; or NULL
	inbox			Pointer to 'is the INBOX' flag
		
   Return Value:
	T			mailbox exists
	NIL			mailbox does not exist

   Author:
	Andy Harper, Kings College London, Jan 1997

 - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
*/

static int __folder_exists(char *mailbox, int *mailcontext, int close, char *mailfile, char *folder, int *inbox)
  {
    char tmpmailfile[MAILFILESIZE], tmpfolder[FOLDERSIZE];
    struct stat sbuf;
    struct FolderPattern fp = {0, tmpmailfile, NULL, tmpfolder};

    int context      = 0;		/* internal context if none supplied */
    int *ctx         = &context;	/* Context pointer */

    DBGPRINT(DBG$ENTRY,"__folder_exists: entered, mailbox='%s'",mailbox,0,0,0);

					/* get mailfile and foldername */
    if (!__parse_mailbox_name(mailbox, tmpmailfile, tmpfolder, inbox))
      {
        DBGPRINT(DBG$EXIT,"__folder_exists: exit, cannot parse",0,0,0,0);
        return NIL;
      }


    if (stat(tmpmailfile, &sbuf) == -1)
      {
        DBGPRINT(DBG$EXIT,"__folder_exists: exit, cannot stat mailfile",0,0,0,0);
        return NIL;
      }

					/* Copy names, if wanted */
    if (mailfile) strcpy(mailfile,tmpmailfile);
    if (folder)   strcpy(folder,tmpfolder);

					/* Use external context if given */
    if (mailcontext)
      ctx = mailcontext;

					/* If not open, open it */
    if (!*ctx)
      {
        DBGPRINT(DBG$SHOWMORE,"__folder_exists: opening '%s'", tmpmailfile,0,0,0);
        if ((*ctx = __mailfile_open(tmpmailfile)) == 0)
           return NIL;
      }

					/* Scan the folder list */
    DBGPRINT(DBG$SHOWMORE,"__folder_exists: scanning '%s' for folder '%s'",tmpmailfile,tmpfolder,0,0);
    __scan_folders(ctx, __find_folder, &fp);


					/* Close if internal or close requested */
    if (context || close)
      {
        DBGPRINT(DBG$SHOWMORE,"__folder_exists: closing mail file",0,0,0,0);
        __mailfile_close(ctx);
      }

					/* Return T if found, else NIL */
    DBGPRINT(DBG$EXIT,"__folder_exists: exit, count ='%d'",fp.count,0,0,0);
    return (fp.count>0) ? T : NIL;
  }

/* VMS/MAIL manipulate driver parameters
 * Accepts: function code
 *	    function-dependent value
 * Returns: function-dependent return value
 */

void *vms_mail_parameters (long function, void *value)
{
  return NULL;
}



/* VMS/MAIL mail validate mailbox
 * Accepts: mailbox name
 * Returns: our driver if name is valid, NIL otherwise
 */

DRIVER *vms_mail_valid (char *name)
{
  return vms_mail_isvalid(name) ? &vmsmaildriver : NIL;
}

/*
 - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
   Purpose:
	Validate the mailbox. Check that the syntax is right for this driver
	and that the mailbox exists.

   Input:
	name		Pointer to mailbox name

   Output:
	NONE

   Return Value:
	T if mailbox exists
	NIL if it doesn't or the format is invalid

  Author:
	Andy Harper, Kings College London, Jan 1997

 - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
 */
long vms_mail_isvalid (char *name)
{
  static char *invalchrs = "{}()[]^&";
  char *i;
  int inbox;

  DBGPRINT(DBG$ENTRY,"vms_mail_isvalid: entered, name='%s'", name,0,0,0);

						/* Invalid chars ?? */
  for (i=invalchrs; *i; i++)
    if (strchr(name,*i))
      {
        DBGPRINT(DBG$EXIT,"vms_mail_isvalid: mailbox name has invalid char '%c'",*i,0,0,0);
        return NIL;
      }

						/* Successful parse ?? */
  if (__folder_exists(name, NULL, 1, NULL, NULL, &inbox))
    {
      DBGPRINT(DBG$EXIT,"vms_mail_isvalid: exiting, mailbox name [%s] OK",name,0,0,0);
      return T;
    }
  else
    {
      DBGPRINT(DBG$EXIT,"vms_mail_isvalid: exiting, could not find mailbox [%s]",name,0,0,0);
      return NIL;
    }

}

/*
 - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
   Purpose:
	Display all subscribed mailboxes, from the subscription database, which
	match the supplied pattern.

   Inputs:
	stream		The mail stream (not used)
	pat		The pattern to match

   Outputs:
	NONE

   Return Value:
	NONE

   Comments:
	results are reported back using the mm_mailbox() callback.

	The list of subscribed mail folders is maintained in a database file
	in the user's home directory (see define of MBXDATABASE)

	If the INBOX exists, it is always returned

   Author:
	Andy Harper, Kings College London, Jan 1997

 - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
*/
void vms_mail_find (MAILSTREAM *stream, char *pat)
{
  char *home, subscriptionfile[MAILFILESIZE], record[MAXRECORDSIZE];
  FILE *fp;

					/* Always have an INBOX if it exists */
  if (matchwild(pat,"INBOX") && vms_mail_isvalid("INBOX"))
    mm_mailbox("INBOX");

					/* Find location of subscription database */
  if (__get_home_directory(&home) == NULL)
    {
      mm_log("cannot locate subscription database",ERROR);
      return;
    }

					/* Scan subscription database */
  strcpy(subscriptionfile,home);
  strcat(subscriptionfile,MBXDATABASE);
  if ((fp=fopen(subscriptionfile,"r")) != NULL)
    {
      while ( fgets(record, MAXRECORDSIZE, fp)  )
       {
        record[strlen(record)-1] = '\0';/* strip '\n' */
        if ( matchwild(pat, record) )
           mm_mailbox(record);
       }
      fclose(fp);
    }

  return;
}

/*
 - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
   Purpose:
	Find all the mailboxes that match the pattern. This actually returns
	all folders in the mail file(s) that match the pattern.

   Inputs:
	stream		The mail stream (not used)
	pat		The pattern to match

   Outputs:
	NONE

   Return Value:
	NONE

   Comments:
	Results are reported back via the mm_mailbox() callback.

	The nominated mail file is scanned and all matching folders are
	reported.

   Author:
	Andy Harper, Kings College London, Jan 1997

 - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
 */
void vms_mail_find_all (MAILSTREAM *stream, char *pat)
{
  char *p, prefix[MAILFILESIZE], mailfile[MAILFILESIZE];
  int context=0, inbox;
  struct FolderPattern fp = {0, mailfile, "", pat};

  DBGPRINT(DBG$ENTRY,"vms_mail_find_all: entered, pattern = '%s'", pat,0,0,0);


					/* Find possible mailfile name prefix */
   strcpy(prefix, pat);
   if ((p = strrchr(prefix,'/')) != NULL)
     {
       *(p+1) = '\0';
       DBGPRINT(DBG$SHOWMORE,"vms_mail_find_all: found mailbox prefix '%s'", prefix, 0, 0, 0);
       fp.prefix = prefix;
     }

					/* parse mailbox pattern */
  if (__parse_mailbox_name(pat, mailfile, NULL, &inbox) == NIL)
    {
       mm_log("Invalid folder pattern", ERROR);
       DBGPRINT(DBG$EXIT,"vms_mail_find_all: exit : invalid pattern",0,0,0,0);
       return;
    }

					/* Open the mail file */
  if ( (context=__mailfile_open(mailfile)) == 0 )
    return;

					/* Find matching folders */
  __scan_folders(&context, __match_folders, &fp);
  __mailfile_close(&context);

  DBGPRINT(DBG$EXIT,"vms_mail_find_all: exit",0,0,0,0);
}

/* VMS/MAIL mail find list of bboards
 * Accepts: mail stream
 *	    pattern to search
 */

void vms_mail_find_bboards (MAILSTREAM *stream, char *pat)
{
  return;		/* no bboards supported on VMS */
}



/* VMS/MAIL mail find list of all bboards
 * Accepts: mail stream
 *	    pattern to search
 */

void vms_mail_find_all_bboards (MAILSTREAM *stream,char *pat)
{
   return;		/* Again, not supported at this stage */
}

/*
 - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
   Purpose:
	Add a new mailbox to the list of subscribed folders in the subscription
	database.

   Inputs:
	stream		The mail stream (not used)
	mailbox		Name of mailbox to add

   Outputs:
	NONE

   Return Value:
	T		If subscription accepted
	NIL		If subscription rejected

   Comments:
	Since INBOX is automatically included on the search of the subscription
	database, it may not be added to the file.

   Author:
	Andy Harper, Kings College London, Jan 1997

 - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
 */

long vms_mail_subscribe (MAILSTREAM *stream, char *mailbox)
{
  char *home,subscriptionlist[MAILFILESIZE], record[MAXRECORDSIZE];
  FILE *fp;

					/* INBOX has special treatment */
  if (__strcasecmp(mailbox, "INBOX"))
    {
      mm_log("INBOX already implictly subscribed",ERROR);
      return NIL;
    }

					/* Get home directory */
  if (__get_home_directory(&home) == NULL)
    {
       mm_log("can't locate subscription database",ERROR);
       return NIL;
    }

					/* Construct subscription database name */
  strcpy(subscriptionlist, home);
  strcat(subscriptionlist,MBXDATABASE);


					/* Do we have a subscription database ?? */
  if ((fp=fopen(subscriptionlist,"r")) != NULL)
    {
					/* Is mailbox already subscribed? */
      while ( fgets(record, MAXRECORDSIZE, fp)  )
        {
          record[strlen(record)-1] = '\0';	/* remove '\n' */
          if ( strcmp(mailbox,record) )
            {
              fclose(fp);
              sprintf(record,"already subscribed to %s",mailbox);
              mm_log(record,ERROR);
              return NIL;
            }
        };
      fclose(fp);
    }


						/* Append new subscription */
   if ((fp=fopen(subscriptionlist,"a","rfm=var","rat=cr")) == NULL)
     {
       mm_log("unable to append to subscription database",ERROR);
       return NIL;
     }
   fputs(mailbox,fp); fputc('\n',fp);
   fclose(fp);
   return T;
}

/*
 - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
   Purpose:
	Delete a mailbox from the mailbox subscription database

   Input:
	stream		Mail stream (not used)
	mailbox		Mailbox to unsubscribe

   Output:
	NONE

   Return Value:
	T		Subscription successfully removed
	NIL		Subscription not removed

   Comments:
	The name is removed from the subscription file in the user's home
        directory.

   Author:
	Andy Harper, Kings College London, Jan 1997

 - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
 */

long vms_mail_unsubscribe (MAILSTREAM *stream, char *mailbox)
{
  char subscriptionfile[MAILFILESIZE], newsubscriptionfile[MAILFILESIZE], oldsubscriptionfile[MAILFILESIZE];
  char *home, record[MAXRECORDSIZE];
  FILE *fp,*newfp;
  int subscribed;

					/* INBOX not valid */
  if ( __strcasecmp("INBOX", mailbox) )
    {
      mm_log("cannot unsubscribe INBOX",ERROR);
      return NIL;
    }

					/* Get location of subscription database */
  if (__get_home_directory(&home) == NULL)
    {
      mm_log("cannot locate subscription database",ERROR);
      return NIL;
    }

					/* Construct subscription database names */
  strcpy(subscriptionfile, home);
  strcat(subscriptionfile, MBXDATABASE);
  
  strcpy(oldsubscriptionfile, subscriptionfile);
  strcat(oldsubscriptionfile, "-OLD");
  strcpy(newsubscriptionfile, subscriptionfile);
  strcat(newsubscriptionfile, "-NEW");


					/* Open existing subscriptions */
  if ((fp=fopen(subscriptionfile,"r")) == NULL)
    {
      mm_log("no subscription file", ERROR);
      return NIL;
    }


					/* Create new subscription database */
  if ((newfp=fopen(newsubscriptionfile,"w","rfm=var","rat=cr")) == NULL)
    {
      fclose(fp);
      mm_log("cannot create new subscription database",ERROR);
      return NIL;
    }
      
					/* Copy, excluding one to delete */
  subscribed = 0;
  while ( fgets(record, MAXRECORDSIZE, fp) )
    {
       record[strlen(record)-1] = '\0';

       if (strcmp(mailbox,record))
	  { subscribed++; }
       else
          { fputs(record,newfp); fputc('\n',newfp);}
    }
  fclose(fp);
  fclose(newfp);


					/* Did we find the mailbox ?? */
  if (!subscribed)
    {
      remove(newsubscriptionfile);
      mm_log("mailbox not subscribed",ERROR);
      return NIL;
    }


					/* Preserve old subscriptions */
  if (rename(subscriptionfile, oldsubscriptionfile) != 0)
    {
      remove(newsubscriptionfile);
      mm_log("cannot save old subscription database",ERROR);
      return NIL;
    }


					/* Rename new copy to master */
  if (rename(newsubscriptionfile, subscriptionfile) != 0)
    {
       remove(newsubscriptionfile);
       rename(oldsubscriptionfile,subscriptionfile);
       mm_log("cannot rename new subscription database",ERROR);
       return NIL;
    }

					/* Get rid of old copy */
  if (remove(oldsubscriptionfile) !=0 )
     mm_log("could not remove old subscription file", WARN);

  return T;
}

/* VMS/MAIL mail subscribe to bboard
 * Accepts: mail stream
 *	    bboard to add to subscription list
 * Returns: T on success, NIL on failure
 */

long vms_mail_subscribe_bboard (MAILSTREAM *stream, char *mailbox)
{
  mm_log("not implemented on VMS",ERROR);
  return NIL;			/* never valid for VMS/MAIL */
}


/* VMS/MAIL mail unsubscribe to bboard
 * Accepts: mail stream
 *	    bboard to delete from subscription list
 * Returns: T on success, NIL on failure
 */

long vms_mail_unsubscribe_bboard (MAILSTREAM *stream, char *mailbox)
{
  mm_log("not implemented on VMS",ERROR);
  return NIL;			/* never valid for VMS/MAIL */
}

/*
 - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
   Purpose:
	Create a new mailbox

   Inputs:
	stream		Name of mail stream (not used)
	mailbox		Name of mailbox to create

   Output:
	NONE

   Return Value:
	T		mailbox successfully created
	NIL		mailbox not created

   Comments:
	This routine creates an empty file based on the FDL specification of
	another file:
		Either: the user's default MAIL file
		Or:	The prototype specified by CCLIENT_MAILPROTYPE

	We cannot create a 'folder' in a VMS MAIL file so we only create the
	mail file itself. VMS MAIL does not allow empty folders to exist as
	they are created automatically when a message is copied into them.

	We need to handle the checking carefully. We must check that the named
	folder exists within the mail file.

   Author:
	Andy Harper, Kings College London, Jan 1997

 - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
 */

long vms_mail_create (MAILSTREAM *stream, char *mailbox)
{
   char mailfile[MAILFILESIZE], folder[FOLDERSIZE], fdlbuffer[4096];
   char tmp[256], maildefault[MAILFILESIZE];
   char *p;
   int fdlsize,i,inbox;
   struct stat sbuf;

   char *prototypes[] = {maildefault,"CCLIENT_MAILPROTOTYPE"};
   int maxproto       = sizeof(prototypes)/sizeof(prototypes[0]);
   int first          = 0;
   int context        = 0;
   int dummycontext   = 0;


   DBGPRINT(DBG$ENTRY,"vms_mail_create: entered, mailbox='%s'", mailbox,0,0,0);

   if (!__parse_mailbox_name("INBOX", maildefault, NULL, &inbox))
     {
       DBGPRINT(DBG$SHOWMORE,"vms_mail_create: can't parse INBOX",0,0,0,0);
       first++;	/* skip attempt to get prototype from INBOX */
     }
      
   DBGPRINT(DBG$SHOWMORE,"vms_mail_create: parsing mailbox",0,0,0,0)
   if (!__parse_mailbox_name(mailbox,mailfile,folder,&inbox))
     {
       mm_log("unparseable mailbox name",ERROR);
       return NIL;
     }

   DBGPRINT(DBG$SHOWMORE,"vms_mail_create: checking existence of mail file",0,0,0,0);
   if (stat(mailfile, &sbuf) == 0)
     {
       __Add_New_Mailbox(mailfile,folder);
       DBGPRINT(DBG$SHOWMORE,"vms_mail_create: mailfile exists, but no folder",0,0,0,0);
       return T;
     }
  
   
   DBGPRINT(DBG$SHOWMORE,"vms_mail_create: creating directory", 0, 0, 0, 0);
   if ((p=strchr(mailfile,']')) != NULL)
     {
       sprintf(tmp,"%.*s", p-mailfile+1, mailfile);
       DBGPRINT(DBG$SHOWMORE,"vms_mail_create: directory is '%s'", tmp,0,0,0);
       if (mkdir(tmp,0) != 0)
         {
           DBGPRINT(DBG$SHOWMORE,"vms_mail_create: directory create failed",0,0,0,0);
         }
     }

   DBGPRINT(DBG$SHOWMORE,"vms_mail_create: attempting create from prototype",0,0,0,0);
   for (i=first; i<maxproto; i++)
     {
       if (!__fdl_generate(prototypes[i], fdlbuffer, sizeof(fdlbuffer)-1, &fdlsize)) continue;
       if (!__fdl_create_file(mailfile, fdlbuffer, fdlsize)) continue;
       __Add_New_Mailbox(mailfile,folder);
       DBGPRINT(DBG$EXIT,"vms_mail_create: exit, created mailfile",0,0,0,0);
       return T;
     }

   sprintf(tmp,"could not create '%s'",mailbox);
   mm_log(tmp, ERROR);
   DBGPRINT(DBG$EXIT,"vms_mail_create: exit, create failed",0,0,0,0);
   return NIL;
}

/*
 - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
   Purpose:
	Delete a mailbox

   Inputs:
	stream		pointer to mail stream (not used)
	mailbox		Pointer to name of mailbox to delete

   Outputs:
	NONE

   Function Return Value:
	T		If deletion successful
	NIL		If not

   Comments:
	Deleting a mailbox simply requires removing all the messages in that
	mailbox.

   Revision History:
	2.0	Andy Harper	7-OCT-1997	Complete rewrite

 - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
*/
long vms_mail_delete (MAILSTREAM *stream, char *mailbox)
  {
   char mailfile[MAILFILESIZE], folder[FOLDERSIZE];
   int context;
   int msgctx;
   int msgcnt;
   long int n;
   long int ret = T;

   DBGPRINT(DBG$ENTRY,"vms_mail_delete: entered, mailbox=[%s]", mailbox,0,0,0)

   __parse_mailbox_name(mailbox, mailfile, folder, &n);
   DBGPRINT(DBG$SHOWMORE, "vms_mail_delete: mail=[%s], folder=[%s]", mailfile, folder, 0, 0);


					/* Open the folder */
   context = __mailfile_open(mailfile);
   if (!context) return NIL;
   msgctx  = __message_select(&context, folder, &msgcnt);
   if (!msgctx)  {__mailfile_close(&context); __Delete_New_Mailbox(mailfile, folder);return T;}


					/* Delete all the messages */
   for (n=1; n<=msgcnt; n++)
     if (! __message_delete(&msgctx, n) )
       { char tmp[256];
         sprintf(tmp,"Can't delete msg %d from %s", n, mailbox, 0, 0);
         mm_log(tmp,ERROR);
         ret=NIL;
       }

					/* Close the folder */
   __message_close(&msgctx);
   __mailfile_close(&context);
   __Delete_New_Mailbox(mailfile,folder);


   DBGPRINT(DBG$EXIT,"vms_mail_delete: exit, status=[%d]",ret,0,0,0);
   return ret;
  }

/*
 - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
   Purpose:
	Rename a mailbox

   Inputs:
	stream		mail stream to use if needed (it's not)
	old		old mailbox name
	new		new mailbox name

   Output:
	NONE

   Return Value:
	T		if rename ok
	NIL		if rename failed

   Comments:
	Because a mailbox is really a folder inside a file, we can't just
	rename it like a file. We move all messages to the new folder instead.

   Author:
	Andy Harper, Kings College London, May 1997

 - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
*/

long vms_mail_rename (MAILSTREAM *stream, char *old, char *new)
{
   char mailfile[MAILFILESIZE], folder[FOLDERSIZE];
   char dstmailfile[MAILFILESIZE], dstfolder[FOLDERSIZE];
   int context;
   int msgctx;
   int msgcnt;
   long int n;
   long int ret = T;

   DBGPRINT(DBG$ENTRY,"vms_mail_rename: entered, old=[%s], new=[%s]", old,new,0,0)

   __parse_mailbox_name(old, mailfile, folder, &n);
   DBGPRINT(DBG$SHOWMORE, "vms_mail_rename: mailfile=[%s], folder=[%s]",mailfile, folder, 0, 0);

   __parse_mailbox_name(new, dstmailfile, dstfolder, &n);
   DBGPRINT(DBG$SHOWMORE, "vms_mail_rename: dstmail=[%s], dstfolder=[%s]",dstmailfile, dstfolder, 0, 0);


					/* Open the source folder */
   context = __mailfile_open(mailfile);
   if (!context) return NIL;
   msgctx  = __message_select(&context, folder, &msgcnt);
   if (!msgctx)  {__mailfile_close(&context); __Rename_New_Mailbox(mailfile, folder,dstmailfile,dstfolder);return T;}


					/* Move all messages to new mailbox */
   for (n=1; n<=msgcnt; n++)
     if (! __message_copy(&msgctx, n, dstmailfile, dstfolder, 1))
       { char tmp[256];
         sprintf(tmp,"Can't rename msg %d", n, 0, 0, 0);
         mm_log(tmp,ERROR);
         ret=NIL;
       }

					/* Close the folder */
   __message_close(&msgctx);
   __mailfile_close(&context);
   __Rename_New_Mailbox(mailfile,folder,dstmailfile,dstfolder);

   DBGPRINT(DBG$EXIT,"vms_mail_rename: exit, status=[%d]",ret,0,0,0);
   return ret;
}

/* VMS/MAIL mail open - open a "mailbox" which is a folder in VMS/MAIL.
 * Accepts: stream to open
 * Returns: stream on success, NIL on failure
 */

MAILSTREAM *vms_mail_open (MAILSTREAM *stream)
  {
    short int flags;
    long int  i;
    int       inbox;
    int       status;
    int       newmail = 0;
					/* return prototype for OP_PROTOTYPE call */
    if (!stream) return &vmsmailproto;


    DBGPRINT(DBG$ENTRY,"vms_mail_open: Entered, mailbox='%s'",stream->mailbox,0,0,0);

					/* close old file if stream being recycled */
    if (LOCAL) {
      vms_mail_close (stream);		/* dump and save the changes */
      mail_free_cache (stream);		/* clean up cache */
    }


					/* Allocate local data area */
    stream->local = fs_get (sizeof (VMSMAILLOCAL));


					/* Parse the mailbox name */
    __parse_mailbox_name(stream->mailbox, LOCAL->mailfile, LOCAL->folder, &inbox);
    LOCAL->inbox = inbox;

					/* Create context values */
    if ( !(LOCAL->mail_context = __mailfile_open(LOCAL->mailfile)) || !(LOCAL->msg_context = __message_open(&LOCAL->mail_context)))
     {
      __mailfile_close(&LOCAL->mail_context);
      fs_give((void **) &stream->local);
      mm_log("cannot open mailfile",ERROR);
      return NIL;
     }

    stream->dtb = &vmsmaildriver;	/* reattach this driver */


					/* force readonly if bboard */
    if (*stream->mailbox == '*') stream->rdonly = T;
    stream->sequence++;			/* bump sequence number */


    LOCAL->pinged = 0;			/* Initial PING to set message count */
    stream->nmsgs = stream->recent = 0;
    if (vms_mail_ping (stream) && !stream->nmsgs)
      mm_log ("Mailbox is empty",(long) NIL);


    mail_exists(stream,stream->nmsgs);	/* Notify application of msg count */



    LOCAL->buflen = INITTEXTSIZE;	/* buffer space (initial size) */
    LOCAL->buf = (char *) fs_get (INITTEXTSIZE + 1);


    LOCAL->msgnum = 0;			/* Initialize the current message cache*/
    LOCAL->header = NULL;
    LOCAL->headersize = 0;
    LOCAL->text = NULL;
    LOCAL->textsize = 0;


    LOCAL->msgs = NULL;			/* Allocate & Initialize flags table */
    if (stream->nmsgs > 0)
       LOCAL->msgs = fs_get(stream->nmsgs * sizeof(FILECACHE));


    for (i = 1; i <= stream->nmsgs; i++)/* Instantiate elts, set flags */
      {
       MESSAGECACHE *elt = mail_elt(stream,i);
       get_message_flags(&LOCAL->msg_context, i, &flags);

					/* Fill in elt */
       elt->seen     = ((flags & MAIL$M_NEWMSG)  ? NIL : T  );
       elt->answered = ((flags & MAIL$M_REPLIED) ? T   : NIL);
       elt->flagged  = ((flags & MAIL$M_MARKED)  ? T   : NIL);
       elt->deleted  = NIL;
       elt->valid    = T;
					/* Fill in local flags */
       LOCAL->msgs[i-1].flags = elt->seen ? fSEEN : 0;
       LOCAL->msgs[i-1].mapnum = i;
       DBGPRINT(DBG$SHOWMORE,"vms_mail_open: msgno=[%d], seen flags: flags&MAIL$M_NEWMSG=[%d], flags&fSEEN=[%d], elt->seen=[%d]",i, flags&MAIL$M_NEWMSG, LOCAL->msgs[i-1].flags&fSEEN, elt->seen);
      }

    DBGPRINT(DBG$EXIT,"vms_mail_open: exiting",0,0,0,0);
    return stream;			/* return stream to caller */
  }

/* VMS/MAIL mail close
 * Accepts: MAIL stream
 */

void vms_mail_close (MAILSTREAM *stream)
  {
   DBGPRINT(DBG$ENTRY,"vms_mail_close: entered, mailbox='%s'", stream->mailbox,0,0,0);


   if (stream)					/* If stream open, close it */
     {
      DBGPRINT(DBG$SHOWMORE,"vms_mail_close: closing stream",0,0,0,0);
      if (LOCAL)
        {
          __file_inbox_messages(stream);	/* auto-file read msg*/


          __message_close(&LOCAL->msg_context);	/* Close mailbox */
          __mailfile_close(&LOCAL->mail_context);


						/* Free buffers & tables */
          if (LOCAL->text   != NULL) fs_give((void **) &LOCAL->text);
          if (LOCAL->header != NULL) fs_give((void **) &LOCAL->header);
          if (LOCAL->msgs   != NULL) fs_give((void **) &LOCAL->msgs);
          if (LOCAL->buf    != NULL) fs_give((void **) &LOCAL->buf);

						/* Return local data area */
          fs_give ((void **) &stream->local);
        }
      stream->dtb = NIL;			/* log out the DTB */
     }

   DBGPRINT(DBG$EXIT,"vms_mail_close: exiting",0,0,0,0);
  }

/* VMS/MAIL mail fetch fast information
 * Accepts: MAIL stream
 *	    sequence
 */

void vms_mail_fetchfast (MAILSTREAM *stream, char *sequence)
{
  long i;

  DBGPRINT(DBG$ENTRY,"vms_mail_fetchfast: mailbox='%s', sequence='%s'", stream->mailbox, sequence, 0, 0);

				/* scan the sequence */
  if (mail_sequence (stream,sequence))
    for (i = 1; i <= stream->nmsgs; i++)
      if (mail_elt (stream,i)->sequence)
         vms_mail_fetchstructure(stream,i,NIL);

  DBGPRINT(DBG$EXIT,"vms_mail_fetchfast: exit",0,0,0,0);
  return;
}


/* VMS/MAIL mail fetch flags
 * Accepts: MAIL stream
 *	    sequence
 */

void vms_mail_fetchflags (MAILSTREAM *stream, char *sequence)
{
  long i;

  DBGPRINT(DBG$ENTRY,"vms_mail_fetchflags: mailbox='%s', sequence='%s'", stream->mailbox, sequence, 0, 0);

/* 			--- Not needed, already in elt and local flags
  if (mail_sequence (stream,sequence))
    for (i = 1; i <= stream->nmsgs; i++)
      if (mail_elt (stream,i)->sequence) {
        short int flags;
	MESSAGECACHE *elt = mail_elt (stream,i);
        get_message_flags(&LOCAL->msg_context, i, &flags);
        elt->seen     = ((LOCAL->msgs[i-1].flags & fSEEN)    ? T : NIL);
        elt->answered = ((flags & MAIL$M_REPLIED)            ? T : NIL);
        elt->flagged  = ((flags & MAIL$M_MARKED)             ? T : NIL);
        elt->deleted  = ((LOCAL->msgs[i-1].flags & fDELETED) ? T : NIL);
     }

*/
  DBGPRINT(DBG$EXIT,"vms_mail_fetchflags: exit",0,0,0,0);
}

/* VMS/MAIL mail fetch structure
 * Accepts: MAIL stream
 *	    message # to fetch
 *	    pointer to return body
 * Returns: envelope of this message, body returned in body value
 *
 * Fetches the "fast" information as well
 */
ENVELOPE *vms_mail_fetchstructure (MAILSTREAM *stream, long msgno, BODY **body)
{
  LONGCACHE *lelt;
  ENVELOPE **env = NULL;
  BODY **b = NULL;
  STRING bs;

  DBGPRINT(DBG$ENTRY,"vms_mail_fetchstructure: mailbox='%s', msgno='%d'", stream->mailbox, msgno, 0, 0);
  if (stream->scache) {		/* short cache */
    DBGPRINT(DBG$SHOWMORE,"vms_mail_fetchstructure: in short cache: msgno=[%d], prev msgno=[%d]",msgno,stream->msgno,0,0);
    if (msgno != stream->msgno){/* flush old poop if a different message */
      
      mail_free_envelope (&stream->env);
      mail_free_body (&stream->body);
    }
    stream->msgno = msgno;
    env = &stream->env;		/* get pointers to envelope and body */
    b = &stream->body;
  }
  else {			/* long cache */
    DBGPRINT(DBG$SHOWMORE,"vms_mail_fetchstructure: in long cache: msgno=[%d], prev msgno=[%d]",msgno,stream->msgno,0,0);
    lelt = mail_lelt (stream,msgno);
    env = &lelt->env;		/* get pointers to envelope and body */
    b = &lelt->body;
  }

  DBGPRINT(DBG$SHOWMORE, "vms_mail_fetchstructure: body=[0x%08x], *b=[0x%08x], *env=[0x%08x]", body, *b, *env, 0);
  if ((body && !*b) || !*env) {	/* have the poop we need? */
    mail_free_envelope (env);	/* flush old envelope and body */
    mail_free_body (b);

				/* Fetch message headers and body */
    __read_message(stream,msgno);

    INIT (&bs,mail_string,(void *) LOCAL->text,LOCAL->textsize);
				/* parse envelope and body */
    rfc822_parse_msg (env,body ? b : NIL,LOCAL->header,LOCAL->headersize,&bs,mylocalhost(),LOCAL->buf);
    (lelt->elt).rfc822_size = LOCAL->headersize + LOCAL->textsize;       
  }

  DBGPRINT(DBG$EXIT,"vms_mail_fetchstructure: exit",0,0,0,0);
  if (body) *body = *b;		/* return the body */
  return *env;			/* return the envelope */
}

/* VMS/MAIL mail fetch message header
 * Accepts: MAIL stream
 *	    message # to fetch
 * Returns: message header in RFC822 format
 */

char *vms_mail_fetchheader (MAILSTREAM *stream,long msgno)
{
  DBGPRINT(DBG$ENTRY,"vms_mail_fetchheader: mailbox='%s', msgno='%d'", stream->mailbox, msgno, 0, 0);

  __read_message(stream,msgno);
  strcrlfcpy (&LOCAL->buf,&LOCAL->buflen,LOCAL->header,LOCAL->headersize);

  DBGPRINT(DBG$EXIT,"vms_mail_fetchheader: exit",0,0,0,0);
  return LOCAL->buf;
}




/* VMS/MAIL mail fetch message text (only)
	body only;
 * Accepts: MAIL stream
 *	    message # to fetch
 * Returns: message text in RFC822 format
 */

char *vms_mail_fetchtext (MAILSTREAM *stream, long msgno)
{
  MESSAGECACHE *elt = mail_elt(stream,msgno);

  DBGPRINT(DBG$ENTRY,"vms_mail_fetchtext: mailbox='%s', msgno='%d'", stream->mailbox, msgno, 0, 0);
				/* get to text position */
  __read_message(stream,msgno);
				/* copy the string */
  strcrlfcpy (&LOCAL->buf,&LOCAL->buflen,LOCAL->text,LOCAL->textsize);

				/* recalculate status */
  if (!elt->seen)
    { elt->seen = T; __update_message_status (stream,msgno,T); }

  DBGPRINT(DBG$EXIT,"vms_mail_fetchtext: exit",0,0,0,0);
  return LOCAL->buf;
}

/* VMS/MAIL fetch message body as a structure
 * Accepts: Mail stream
 *	    message # to fetch
 *	    section specifier
 *	    pointer to length
 * Returns: pointer to section of message body
 */
char *vms_mail_fetchbody (MAILSTREAM *stream, long m, char *s, unsigned long *len)
{
  BODY *b;
  PART *pt;
  char *t;
  ENVELOPE *env;
  unsigned long i;
  unsigned long offset = 0;
  MESSAGECACHE *elt = mail_elt (stream,m);

  DBGPRINT(DBG$ENTRY,"vms_mail_fetchbody: mailbox='%s', msgno='%d', section='%s'", stream->mailbox, m, s, 0);
				/* make sure have a body */
  env = vms_mail_fetchstructure (stream,m,&b);
  if (!(env && b && s && *s &&
	((i = strtol (s,&s,10)) > 0))) return NIL;
  do {				/* until find desired body part */
				/* multipart content? */
    if (b->type == TYPEMULTIPART) {
      pt = b->contents.part;	/* yes, find desired part */
      while (--i && (pt = pt->next));
      if (!pt) return NIL;	/* bad specifier */
				/* note new body, check valid nesting */
      if (((b = &pt->body)->type == TYPEMULTIPART) && !*s) return NIL;
      offset = pt->offset;	/* get new offset */
    }
    else if (i != 1) return NIL;/* otherwise must be section 1 */
				/* need to go down further? */
    if (i = *s) switch (b->type) {
    case TYPEMESSAGE:		/* embedded message, calculate new base */
      offset = b->contents.msg.offset;
      b = b->contents.msg.body;	/* get its body, drop into multipart case */
    case TYPEMULTIPART:		/* multipart, get next section */
      if ((*s++ == '.') && (i = strtol (s,&s,10)) > 0) break;
    default:			/* bogus subpart specification */
      return NIL;
    }
  } while (i);
				/* lose if body bogus */
  if ((!b) || b->type == TYPEMULTIPART) return NIL;

  if (!elt->seen)
    { elt->seen=T;  __update_message_status(stream,m,T); }

				/* move to that place in the data */
  __read_message(stream,m);
  t = LOCAL->text + offset;

  rfc822_contents(&LOCAL->buf,&LOCAL->buflen,len,t,b->size.ibytes,b->encoding);
  DBGPRINT(DBG$EXIT,"vms_mail_fetchbody: exit",0,0,0,0);
  return LOCAL->buf;
}

/* Parse flag list
 * Accepts: MAIL stream
 *	    flag list as a character string
 * Returns: flag command list
 */


short vms_mail_getflags (MAILSTREAM *stream, char *flag)
{
  char tmpbuf[256];
  char *t;
  short f = 0;
  short i,j;
  if (flag && *flag) {		/* no-op if no flag string */
				/* check if a list and make sure valid */
    if ((i = (*flag == '(')) ^ (flag[strlen (flag)-1] == ')')) {
      mm_log ("Bad flag list",ERROR);
      return NIL;
    }
				/* copy the flag string w/o list construct */
    strncpy (tmpbuf,flag+i,(j = strlen (flag) - (2*i)));
    tmpbuf[j] = '\0';
    t = ucase (tmpbuf);		/* uppercase only from now on */

    while (*t) {		/* parse the flags */
      if (*t == '\\') {		/* system flag? */
	switch (*++t) {		/* dispatch based on first character */
	case 'S':		/* possible \Seen flag */
          if (strncmp(t+1, "EEN", 3) == 0) i = fSEEN;
	  t += 4;		/* skip past flag name */
	  break;
	case 'D':		/* possible \Deleted flag */
          if (strncmp(t+1, "ELETED", 6) == 0) i = fDELETED;
	  t += 7;		/* skip past flag name */
	  break;
	case 'F':		/* possible \Flagged flag */
          if (strncmp(t+1, "LAGGED", 6) == 0) i = fFLAGGED;
	  t += 7;		/* skip past flag name */
	  break;
	case 'A':		/* possible \Answered flag */
          if (strncmp(t+1,"NSWERED", 7) == 0) i = fANSWERED;
	  t += 8;		/* skip past flag name */
	  break;
	default:		/* unknown */
	  i = 0;
	  break;
	}
				/* add flag to flags list */
	if (i && ((*t == '\0') || (*t++ == ' '))) f |= i;
	else {			/* bitch about bogus flag */
	  mm_log ("Unknown system flag",ERROR);
	  return NIL;
	}
      }
      else {			/* no user flags yet */
	mm_log ("Unknown flag",ERROR);
	return NIL;
      }
    }
  }
  return f;
}

/* VMS/MAIL mail set flag
 * Accepts: MAIL stream
 *	    sequence
 *	    flag(s)
 */

void vms_mail_setflag (MAILSTREAM *stream, char *sequence, char *flag)
{
  int	i;
  MESSAGECACHE *elt;

  short f = vms_mail_getflags (stream,flag);	/* Parse the list */
  if (!f) return;		/* no-op if no flags to modify */

				/* get sequence and loop on it */
  if (mail_sequence (stream,sequence)) for (i = 1; i <= stream->nmsgs; i++)
    if ((elt = mail_elt (stream,i))->sequence)
      { __setflag(stream,i,elt,f); __update_message_status(stream,i,T); }
}




/* VMS/MAIL mail clear flag
 * Accepts: MAIL stream
 *	    sequence
 *	    flag(s)
 */

void vms_mail_clearflag (MAILSTREAM *stream, char *sequence, char *flag)
{
  int	i;
  MESSAGECACHE *elt;

  short f = vms_mail_getflags (stream,flag);	/* Parse the list */
  if (!f) return;		/* no-op if no flags to modify */
				/* get sequence and loop on it */
  if (mail_sequence (stream,sequence)) for (i = 1; i <= stream->nmsgs; i++)
    if ((elt = mail_elt (stream,i))->sequence)
      { __clearflag(stream,i, elt, f); __update_message_status(stream,i,T); }
}

/* VMS/MAIL mail search for messages
 * Accepts: MAIL stream
 *	    search criteria
 */

void vms_mail_search (MAILSTREAM *stream, char *criteria)
{
  long i,n;
  char *d;
  search_t f;

				/* initially all searched */
  for (i = 1; i <= stream->nmsgs; ++i) mail_elt (stream,i)->searched = T;
				/* get first criterion */
  if (criteria && (criteria = strtok (criteria," "))) {
				/* for each criterion */
    for (; criteria; (criteria = strtok (NIL," "))) {
      f = NIL; d = NIL; n = 0;	/* init then scan the criterion */
      switch (*ucase (criteria)) {
      case 'A':			/* possible ALL, ANSWERED */
	if (!strcmp (criteria+1,"LL")) f = vms_mail_search_all;
	else if (!strcmp (criteria+1,"NSWERED")) f = vms_mail_search_answered;
	break;
      case 'B':			/* possible BCC, BEFORE, BODY */
	if (!strcmp (criteria+1,"CC"))
	  f = vms_mail_search_string (vms_mail_search_bcc,&d,&n);
	else if (!strcmp (criteria+1,"EFORE"))
	  f = vms_mail_search_date (vms_mail_search_before,&n);
	else if (!strcmp (criteria+1,"ODY"))
	  f = vms_mail_search_string (vms_mail_search_body,&d,&n);
	break;
      case 'C':			/* possible CC */
	if (!strcmp (criteria+1,"C"))
	  f = vms_mail_search_string (vms_mail_search_cc,&d,&n);
	break;
      case 'D':			/* possible DELETED */
	if (!strcmp (criteria+1,"ELETED")) f = vms_mail_search_deleted;
	break;
      case 'F':			/* possible FLAGGED, FROM */
	if (!strcmp (criteria+1,"LAGGED")) f = vms_mail_search_flagged;
	else if (!strcmp (criteria+1,"ROM"))
	  f = vms_mail_search_string (vms_mail_search_from,&d,&n);
	break;
      case 'K':			/* possible KEYWORD */
	if (!strcmp (criteria+1,"EYWORD"))
	  f = vms_mail_search_flag (vms_mail_search_keyword,&n,stream);
	break;
      case 'N':			/* possible NEW */
	if (!strcmp (criteria+1,"EW")) f = vms_mail_search_new;
	break;
      case 'O':			/* possible OLD, ON */
	if (!strcmp (criteria+1,"LD")) f = vms_mail_search_old;
	else if (!strcmp (criteria+1,"N"))
	  f = vms_mail_search_date (vms_mail_search_on,&n);
	break;
      case 'R':			/* possible RECENT */
	if (!strcmp (criteria+1,"ECENT")) f = vms_mail_search_recent;
	break;
      case 'S':			/* possible SEEN, SINCE, SUBJECT */
	if (!strcmp (criteria+1,"EEN")) f = vms_mail_search_seen;
	else if (!strcmp (criteria+1,"INCE"))
	  f = vms_mail_search_date (vms_mail_search_since,&n);
	else if (!strcmp (criteria+1,"UBJECT"))
	  f = vms_mail_search_string (vms_mail_search_subject,&d,&n);
	break;
      case 'T':			/* possible TEXT, TO */
	if (!strcmp (criteria+1,"EXT"))
	  f = vms_mail_search_string (vms_mail_search_text,&d,&n);
	else if (!strcmp (criteria+1,"O"))
	  f = vms_mail_search_string (vms_mail_search_to,&d,&n);
	break;
      case 'U':			/* possible UN* */
	if (criteria[1] == 'N') {
	  if (!strcmp (criteria+2,"ANSWERED")) f = vms_mail_search_unanswered;
	  else if (!strcmp (criteria+2,"DELETED")) f = vms_mail_search_undeleted;
	  else if (!strcmp (criteria+2,"FLAGGED")) f = vms_mail_search_unflagged;
	  else if (!strcmp (criteria+2,"KEYWORD"))
	    f = vms_mail_search_flag (vms_mail_search_unkeyword,&n,stream);
	  else if (!strcmp (criteria+2,"SEEN")) f = vms_mail_search_unseen;
	}
	break;
      default:			/* we will barf below */
	break;
      }
      if (!f) {			/* if can't determine any criteria */
	sprintf (LOCAL->buf,"Unknown search criterion: %.80s",criteria);
	mm_log (LOCAL->buf,ERROR);
	return;
      }
				/* run the search criterion */
      for (i = 1; i <= stream->nmsgs; ++i)
	if (mail_elt (stream,i)->searched && !(*f) (stream,i,d,n))
	  mail_elt (stream,i)->searched = NIL;
    }
				/* report search results to main program */
    for (i = 1; i <= stream->nmsgs; ++i)
      if (mail_elt (stream,i)->searched) mail_searched (stream,i);
  }
}

/* VMS/MAIL mail ping mailbox - find how many messages are in this folder.
 * Do it only when opening the folder.
 *
 * Accepts: MAIL stream
 * Returns: T if stream still alive, NIL if not
 */
long vms_mail_ping (MAILSTREAM *stream)
{
  DBGPRINT(DBG$ENTRY,"vms_mail_ping: entered, mailbox=[%s]",stream->mailbox,0,0,0);

  if (!LOCAL->pinged) 		/* Not checked yet ? */
    {
      LOCAL->pinged = 1;
      stream->nmsgs  = __message_folder(&LOCAL->msg_context, LOCAL->folder);
				
      if (!stream->nmsgs)	/* Check list of 'created' folders if not found */
        {
          DBGPRINT(DBG$EXIT,"vms_mail_ping: exit, messages=[%d]", stream->nmsgs,0,0,0);
          return __search_new_mailboxes(LOCAL->mailfile, LOCAL->folder);
        }
    }

  DBGPRINT(DBG$EXIT,"vms_mail_ping: exit, messages=[%d]", stream->nmsgs,0,0,0);
  return T;		/* local stream is always alive */
}

/*
 - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
   Purpose:
	Checkpoint the message flags back to the mailbox

   Inputs:
	stream		Pointer to the message stream

   Outputs:
	NONE

   Function Return Value:
	NONE

   Comments:

   Revision History:
	1.0	Andy Harper	7-OCT-1997	Original Version

 - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
*/

void vms_mail_check (MAILSTREAM *stream)
{
  long int i;

  for (i=1; i<=stream->nmsgs; i++)
    __update_message_status(stream, i, T);

  mm_log ("Check completed",(long) NIL);
}

/*
 - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
   vms_mail_expunge

   Purpose:
	Actually remove all the messages in the mailbox flagged as DELETED,
        then re-adjust the message pointers to remove them from the list.

   Inputs:
	stream		Pointer to affected mail stream

   Outputs:
	NONE

   Function Return Value:
	NONE

   Comments:
	Changing the message numbers in the current mailbox can only be done
	by re-selecting the folder.  This MAY result in new messages appearing
	anywhere in the folder if they have been added to it by another
	process. Thus we cannot relate the new numbers to the old ones except
	by closing and re-opening the folder as this would invalidate existing
	elts.

	Consequently, expunge should only be used during or just prior to
	folder close otherwise deleted messages will continue to be displayed.

	Expunge may be called twice for the same stream. Once just prior
	to calling the close operation and again, if an INBOX, as part of the
	close operation. The latter is used to delete messages that have been
	auto-filed to another folder.
  
	To avoid problems in deleting messages twice, we use a separate flag
	AS_DELETED to indicate if we deleted the message the first time
	around. That way, we know not to delete it again.

   Revision History:
	2.0	Andy Harper	7-OCT-1997	Rewrite

- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
*/

void vms_mail_expunge (MAILSTREAM *stream)
  {
   long i, f;
   long MessagesDeleted = 0;		/* How many messages were deleted */

  
   DBGPRINT(DBG$ENTRY,"vms_mail_expunge: entered, mailbox=[%s]", stream->mailbox,0,0,0);
   for (i = 1; i <= stream->nmsgs; ++i)
     {
      f = LOCAL->msgs[i-1].flags;
      if( (f & fDELETED)  &&  !(f & fWAS_DELETED) )
        if ( __message_delete(&LOCAL->msg_context, i) )
          {
	   LOCAL->msgs[i - 1].flags |= fWAS_DELETED;
           MessagesDeleted++;	/* Count ALL deleted messages */
          }
     }

   DBGPRINT(DBG$EXIT,"vms_mail_expunge: exit, messages expunged =[%d]", MessagesDeleted,0,0,0);
  }

/* VMS/MAIL mail copy message(s)
 * Accepts: MAIL stream
 *	    sequence
 *	    destination mailbox
 * Returns: T if success, NIL if failed
 */

long vms_mail_copy (MAILSTREAM *stream, char *sequence, char *mailbox)
  {
				/* copy the messages */
   DBGPRINT(DBG$ENTRY,"vms_mail_copy: entered, sequence=[%s], mailbox=[%s]", sequence, mailbox, 0, 0);
   return mail_sequence (stream,sequence) ?
       __copy_messages (stream,mailbox,0) :
       NIL;
  }


/* VMS/MAIL mail move message(s)
 * Accepts: MAIL stream
 *	    sequence
 *	    destination mailbox
 * Returns: T if success, NIL if failed
 */

long vms_mail_move (MAILSTREAM *stream, char *sequence, char *mailbox)
  {
				/* copy and delete the messages */
   DBGPRINT(DBG$ENTRY,"vms_mail_move: entered, sequence=[%s], mailbox=[%s]", sequence, mailbox, 0, 0);
   return (mail_sequence (stream,sequence)) ?
         __copy_messages (stream,mailbox,1) :
         NIL;
  }

/*
 - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
   Purpose:
	Copy message(s) from the named sequential mail file into another
	mailfile/folder

    Inputs:
	seqfile		Name of a sequential mail file containing source
	msg_count	Number of messages to copy
	mailfile	Name of destination indexed mail file
	folder		Name of destination mail folder

    Outputs:
	NONE

    Return Status:
	T		Copied OK
	NIL		Some problem during copy

    Author:
	Thanks to Steve Pulley of Digital for basic idea and code, November 1996
	Modified by Andy Harper, Kings College London, November 1996.

 - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
*/

static int __copy_sequential_mail_messages(char *seqfile, long int msg_count, char *mailfile, char *folder)
  {
    int file_ctx   =0;
    int msg_context=0;
    int ret        =T;
    long int id;

    DBGPRINT(DBG$ENTRY,"__copy_sequential_mail_messages: entered, seq file=[%s], msg_count=[%ld], mailfile=[%s], folder=[%s]", seqfile, msg_count, mailfile, folder);

    if ( (file_ctx=__mailfile_open(seqfile)) && (msg_context=__message_open(&file_ctx)) )
      for (id = 1; id <= msg_count; id++)
        if ( ! __message_copy(&msg_context, id, mailfile, folder,0) )
          ret=NIL;

    __message_close(&msg_context);
    __mailfile_close(&file_ctx);

    DBGPRINT(DBG$EXIT,"__copy_sequential_mail_messages: exit, status=[%s]",ret?"T":"NIL",0,0,0);
    return ret;
  }

/*
 - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
   Purpose:
	Append a message stored in a STRING structure to a nominated mailbox

   Inputs:
	stream		A mail stream
	mailbox		The mailbox to which is to be appended
	flags		Pointer to list of flags to be set (can be NULL!)
	date		Pointer to date of message         (can be NULL!)
	message		The STRING structure holding the message

   Outputs:
	NONE

   Return Value:
	T		If message appended successfully
	NIL		If not

   Comments:
	'append' in VMS MAIL terms actually means copying the message into
	a particular folder. Its position will be based on the date.

	Appending a message to an arbitrary folder in VMS MAIL is non-trivial.
	Essentially, what we do is to write a temporary file in the form of a
	VMS sequential mail file, then use the mail copy function to get it
	into the required folder.

	The format of a sequential mail file has to be fairly precise but is
        not obviously documented. I've worked it out by trial and error to be
	this (though of course it may change in the future!):

	    From:<tab>[<from-address><spaces>[<personal-name><spaces>]]<date/time>
	    To:<tab>[<to-address>]
	    CC:<tab>[<cc-address>]
	    Subj:<tab>[<subject text>]
	    <blank line>
	    <text body>

	<..> items are data to be filled in
	[..] items are optional

	The data in the string structure is simply the text as received
	(normally the concatenation of the RFC822 format headers and the text
	body) so the headers must be parsed to extract the above information.

        The message text may contain '\r' characters. They are stripped out
	otherwise they confuse mail's header parsing. The '\n' is left alone
	though.

   Limitations:
	Currently, I can see no way to action the flags setting as, once the
        message has been copied into the mail folder, you have no means to
        locate it in order to use the message_modify function. Nor can you
	supply the flags setting at the time of the copy. This may be a 
	permanent restriction.

   Author:
	Andy Harper, Kings College London, January 1997
 - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
 */
long vms_mail_append (MAILSTREAM *stream, char *mailbox, char *flags, char *date, STRING *message)
  {
    static char *months[] = {
	"JAN","FEB","MAR","APR","MAY","JUN","JUL","AUG","SEP","OCT","NOV","DEC"
	};
    static char *filepat[] = {
	"SYS$SCRATCH:SEQMAIL%08x.TMP",
	"SYS$LOGIN:SEQMAIL%08x.TMP",
	"SYS$DISK:[]SEQMAIL%08x.TMP"
			  };
    static int        maxfiles     = sizeof(filepat) / sizeof(filepat[0]);
    long int          size         = SIZE(message);

    FILE              *fp;		/* for sequential mail file */
    ENVELOPE          *env;
    ADDRESS           *ap;
    MESSAGECACHE      elt;
    char              filename[MAILFILESIZE],mailfile[MAILFILESIZE],folder[FOLDERSIZE];
    char              address[256],datebuffer[256];
    char              c,*hdr,*ptr,*line,*tmp;
    int               hdrsize,inbox,i;
    short             f;

    DBGPRINT(DBG$ENTRY,"vms_mail_append: entered, mailbox='%s', flags='%s', size='%ld', date='%s'", mailbox ? mailbox : "(null)", flags ? flags : "(null)", size, date ? date : "(null)");

					/* If no date, use today's date */
    if (!date)
      { rfc822_date(datebuffer); date = datebuffer; }

					/* Get fields of date */
    DBGPRINT(DBG$ENTRY,"vms_mail_append: date='%s'", date, 0, 0, 0);
    if (!mail_parse_date(&elt,date))
     {
       DBGPRINT(DBG$SHOWMORE,"vms_mail_append: date parse of '%s' failed",date,0,0,0);
       mm_log("unparseable date field",ERROR);
       return NIL;
     }
					/* Create VMS format date */
    sprintf(datebuffer,"%d-%s-%04d %2d:%02d:%02d",
      elt.day,months[elt.month-1],elt.year+BASEYEAR,elt.hours,elt.minutes,elt.seconds);


    DBGPRINT(DBG$SHOWMORE,"vms_mail_append: VMS format date ='%s'", datebuffer, 0, 0, 0);

					/* Get the flags -- (NOT IMPLEMENTED!) */
/*    f = vms_mail_getflags(stream,flags); */


					/* parse mailbox name */
    if (!__folder_exists(mailbox,NULL,1,mailfile,folder,&inbox))
      {
        char buf[256];
        sprintf(buf,"no such mailbox '%s'",mailbox);
        mm_log(buf,ERROR);
        return NIL;
      }

					/* Init to scan message */
    line=ptr=hdr = (char *) fs_get(size+1);


					/* Accumulate header until blank line */
    while (size--)
      if ((c=SNX(message)) != '\r')	/* Ignore \r characters */
        if ((*ptr++ = c) == '\n')
          {
            DBGPRINT(DBG$SHOWMORE,"vms_mail_append: line '%.*s'", ptr-line, line, 0, 0);
            if (ptr-line <= 1) break;	/* If blank line, stop here */
            line=ptr;			/* .. else note start of new line */
           }

					/* Parse the RFC822 headers */
    DBGPRINT(DBG$SHOWMORE,"vms_mail_append: parsing headers", 0, 0, 0, 0);
    hdrsize = ptr-hdr;
    tmp = (char *) fs_get(hdrsize + 1);
    rfc822_parse_msg(&env, NULL, hdr, hdrsize, NULL, mylocalhost(), tmp);
    fs_give((void **) &tmp);

					/* Try to open a scratch file */
    DBGPRINT(DBG$SHOWMORE,"vms_mail_append: opening a scratch file (%d attempts)", maxfiles, 0, 0, 0);
    for (i=0; i<maxfiles; i++)
      {
        sprintf(filename, filepat[i], getpid());
        DBGPRINT(DBG$SHOWMORE,"vms_mail_append: trying '%s'", filename, 0, 0, 0);
        if ((fp=fopen(filename, "w", "rfm=var","rat=cr")) != NULL) break;
        DBGPRINT(DBG$SHOWMORE,"vms_mail_append: failed to open '%s'", filename, 0, 0, 0);
      }

    if (fp == NULL)
      { DBGPRINT(DBG$SHOWMORE,"vms_mail_append: exit, status=0 : cant write seq mail file",0,0,0,0);
        mm_log("cannot write sequential mail file",ERROR);
        fs_give((void **)&hdr);
        return NIL;
      }

					/* Determine transport prefix */
     DBGPRINT(DBG$SHOWMORE,"vms_mail_append: Looking for transport prefix",0,0,0,0);
     if ((tmp=__transport_name()) != NULL)
       {
         sprintf(address,"%s%%%%\"%%s@%%s\"%%s",tmp);
         DBGPRINT(DBG$SHOWMORE,"vms_mail_append: prefix='%s', address format='%s'",tmp,address,0,0);
       }
     else
       {
         strcpy(address,"%s@%s%s");
         DBGPRINT(DBG$SHOWMORE,"vms_mail_append: no prefix, address format '%s'", address,0,0,0);
       }

					/* Write the VMS headers */
     DBGPRINT(DBG$SHOWMORE,"vms_mail_append: writing VMS headers to %s",filename,0,0,0);
     fputc('\f',fp);
     fputc('\n',fp);

     DBGPRINT(DBG$SHOWMORE,"vms_mail_append: processing from: header",0,0,0,0);
     fprintf(fp,"From:\t");
     for (ap=env->from; ap ; ap=ap->next)
      { DBGPRINT(DBG$SHOWMORE,"vms_mail_append: FROM: <%s@%s>", ap->mailbox,ap->host,0,0);
        fprintf(fp, address, ap->mailbox, ap->host, ap->next ?  "," : "");}
     fprintf(fp," %s\n", datebuffer);

     DBGPRINT(DBG$SHOWMORE,"vms_mail_append: processing to: header",0,0,0,0);
     fprintf(fp,"To:\t");
     for (ap=env->to; ap ; ap=ap->next)
      { DBGPRINT(DBG$SHOWMORE,"vms_mail_append: TO:   <%s@%s>", ap->mailbox,ap->host,0,0);
        fprintf(fp, address, ap->mailbox, ap->host, ap->next ?  "," : "");}
     fputc('\n',fp);

     DBGPRINT(DBG$SHOWMORE,"vms_mail_append: processing cc: header",0,0,0,0);
     fprintf(fp,"CC:\t");
     for (ap=env->cc; ap ; ap=ap->next)
      { DBGPRINT(DBG$SHOWMORE,"vms_mail_append: CC:   <%s@%s>", ap->mailbox,ap->host,0,0);
        fprintf(fp, address, ap->mailbox, ap->host, ap->next ?  "," : "");}
     fputc('\n',fp);

     DBGPRINT(DBG$SHOWMORE,"vms_mail_append: processing subj: header",0,0,0,0);
     fprintf(fp,"Subj:\t%s\n", env->subject ? env->subject : "");
     fputc('\n',fp);

					/* Free up the envelope */
     DBGPRINT(DBG$SHOWMORE,"vms_mail_append: free envelope structure",0,0,0,0);
     mail_free_envelope(&env);

					/* Write the RFC822 headers */
     DBGPRINT(DBG$SHOWMORE,"vms_mail_append: writing RFC822 headers (size='%d')\n%.*s",hdrsize,hdrsize,hdr,0);
     for (ptr=hdr; hdrsize-- > 0; ptr++)
       fputc(*ptr,fp);
     fs_give((void **)&hdr);

					/* Copy and write the body */
     DBGPRINT(DBG$SHOWMORE,"vms_mail_append: writing message text (size=%d)",size,0,0,0);
     while (size-- > 0)
       {c=SNX(message); if (c != '\r') fputc(c, fp);}


     DBGPRINT(DBG$SHOWMORE,"vms_mail_append: closing sequential mail file",0,0,0,0);
     fclose(fp);

					/* Add message to folder */
     DBGPRINT(DBG$SHOWMORE,"vms_mail_append: copying message into '%s'",mailbox,0,0,0);
     if (!__copy_sequential_mail_messages(filename, 1, mailfile, folder))
       { DBGPRINT(DBG$SHOWMORE,"vms_mail_append: append to folder failed",0,0,0,0);
         mm_log("append to folder failed",ERROR);
         remove(filename);
         return NIL;
       }

     DBGPRINT(DBG$SHOWMORE,"vms_mail_append: deleting temporary file '%s'",filename,0,0,0);
     remove(filename);
     __Delete_New_Mailbox(mailfile,folder);

					/* Return appropriate status */
    DBGPRINT(DBG$EXIT,"vms_mail_append: exit,status=1",0,0,0,0);
    return T;
}

/* VMS/MAIL garbage collect stream
 * Accepts: Mail stream
 *	    garbage collection flags
 */

void vms_mail_gc (MAILSTREAM *stream, long gcflags)
{
  /* nothing here for now */
}

/* Search support routines
 * Accepts: MAIL stream
 *	    message number
 *	    pointer to additional data
 * Returns: T if search matches, else NIL
 */


char vms_mail_search_all (MAILSTREAM *stream, long msgno, char *d, long n)
{
  return T;			/* ALL always succeeds */
}


char vms_mail_search_answered (MAILSTREAM *stream, long msgno, char *d, long n)
{
  return mail_elt (stream,msgno)->answered ? T : NIL;
}


char vms_mail_search_deleted (MAILSTREAM *stream, long msgno, char *d, long n)
{
  return mail_elt (stream,msgno)->deleted ? T : NIL;
}


char vms_mail_search_flagged (MAILSTREAM *stream, long msgno, char *d, long n)
{
  return mail_elt (stream,msgno)->flagged ? T : NIL;
}


char vms_mail_search_keyword (MAILSTREAM *stream, long msgno, char *d, long n)
{
  return mail_elt (stream,msgno)->user_flags & n ? T : NIL;
}


char vms_mail_search_new (MAILSTREAM *stream, long msgno, char *d, long n)
{
  MESSAGECACHE *elt = mail_elt (stream,msgno);
  return (elt->recent && !elt->seen) ? T : NIL;
}



char vms_mail_search_old (MAILSTREAM *stream, long msgno, char *d, long n)
{
  return mail_elt (stream,msgno)->recent ? NIL : T;
}


char vms_mail_search_recent (MAILSTREAM *stream, long msgno, char *d, long n)
{
  return mail_elt (stream,msgno)->recent ? T : NIL;
}


char vms_mail_search_seen (MAILSTREAM *stream, long msgno, char *d, long n)
{
  return mail_elt (stream,msgno)->seen ? T : NIL;
}


char vms_mail_search_unanswered (MAILSTREAM *stream, long msgno, char *d, long n)
{
  return mail_elt (stream,msgno)->answered ? NIL : T;
}


char vms_mail_search_undeleted (MAILSTREAM *stream, long msgno, char *d, long n)
{
  return mail_elt (stream,msgno)->deleted ? NIL : T;
}


char vms_mail_search_unflagged (MAILSTREAM *stream, long msgno, char *d, long n)
{
  return mail_elt (stream,msgno)->flagged ? NIL : T;
}


char vms_mail_search_unkeyword (MAILSTREAM *stream, long msgno, char *d, long n)
{
  return mail_elt (stream,msgno)->user_flags & n ? NIL : T;
}


char vms_mail_search_unseen (MAILSTREAM *stream, long msgno, char *d, long n)
{
  return mail_elt (stream,msgno)->seen ? NIL : T;
}



char vms_mail_search_before (MAILSTREAM *stream, long msgno, char *d, long n)
{
  MESSAGECACHE *elt = mail_elt (stream,msgno);
  return (char) ((long)((elt->year << 9) + (elt->month << 5) + elt->day) < n);
}


char vms_mail_search_on (MAILSTREAM *stream, long msgno, char *d, long n)
{
  MESSAGECACHE *elt = mail_elt (stream,msgno);
  return (char) (((elt->year << 9) + (elt->month << 5) + elt->day) == n);
}


char vms_mail_search_since (MAILSTREAM *stream, long msgno, char *d, long n)
{
				/* everybody interprets "since" as .GE. */
  MESSAGECACHE *elt = mail_elt (stream,msgno);
  return (char) ((long)((elt->year << 9) + (elt->month << 5) + elt->day) >= n);
}

char vms_mail_search_body (MAILSTREAM *stream, long msgno, char *d, long n)
{
  char ret;
  char *s = vms_mail_fetchtext (stream,msgno);	/* get the text in LOCAL->buf */

  ret = search (s,strlen(s),d,n);/* do the search */
  return ret;			/* return search value */
}

char vms_mail_search_subject (MAILSTREAM *stream, long msgno, char *d, long n)
{
  char *s = vms_mail_fetchstructure (stream,msgno,NIL)->subject;
  return s ? search (s,(long) strlen (s),d,n) : NIL;
}


char vms_mail_search_text (MAILSTREAM *stream, long msgno, char *d, long n)
{
  char ret;
  char *s = vms_mail_fetchheader (stream,msgno);	/* Get the header in LOCAL->buf */

  ret = search (s,strlen(s),d,n);  /* First, search at the header */
  if(!ret)	/* Not found at header, search at the body */
	  ret = vms_mail_search_body (stream,msgno,d,n);
  return ret;			/* return search value */
}



char vms_mail_search_bcc (MAILSTREAM *stream, long msgno, char *d, long n)
{
  ADDRESS *a = vms_mail_fetchstructure (stream,msgno,NIL)->bcc;
  LOCAL->buf[0] = '\0';		/* initially empty string */
				/* get text for address */
  rfc822_write_address (LOCAL->buf,a);
  return search (LOCAL->buf,(long) strlen (LOCAL->buf),d,n);
}


char vms_mail_search_cc (MAILSTREAM *stream, long msgno, char *d, long n)
{
  ADDRESS *a = vms_mail_fetchstructure (stream,msgno,NIL)->cc;
  LOCAL->buf[0] = '\0';		/* initially empty string */
				/* get text for address */
  rfc822_write_address (LOCAL->buf,a);
  return search (LOCAL->buf,(long) strlen (LOCAL->buf),d,n);
}


char vms_mail_search_from (MAILSTREAM *stream, long msgno, char *d, long n)
{
  ADDRESS *a = vms_mail_fetchstructure (stream,msgno,NIL)->from;
  LOCAL->buf[0] = '\0';		/* initially empty string */
				/* get text for address */
  rfc822_write_address (LOCAL->buf,a);
  return search (LOCAL->buf,(long) strlen (LOCAL->buf),d,n);
}


char vms_mail_search_to (MAILSTREAM *stream, long msgno, char *d, long n)
{
  ADDRESS *a = vms_mail_fetchstructure (stream,msgno,NIL)->to;
  LOCAL->buf[0] = '\0';		/* initially empty string */
				/* get text for address */
  rfc822_write_address (LOCAL->buf,a);
  return search (LOCAL->buf,(long) strlen (LOCAL->buf),d,n);
}

/* Search parsers */


/* Parse a date
 * Accepts: function to return
 *	    pointer to date integer to return
 * Returns: function to return
 */

search_t vms_mail_search_date (search_t f, long *n)
{
  long i;
  char *s;
  MESSAGECACHE elt;
				/* parse the date and return fn if OK */
  return (vms_mail_search_string (f,&s,&i) && mail_parse_date (&elt,s) &&
	  (*n = (elt.year << 9) + (elt.month << 5) + elt.day)) ? f : NIL;
}



/* Parse a flag
 * Accepts: function to return
 *	    pointer to keyword integer to return
 *	    MAIL stream
 * Returns: function to return
 */

search_t vms_mail_search_flag (search_t f, long *n,MAILSTREAM *stream)
{
  short i;
  char *s,*t;
  if (t = strtok (NIL," ")) {	/* get a keyword */
    ucase (t);			/* get uppercase form of flag */
    for (i = 0; i < NUSERFLAGS && (s = stream->user_flags[i]); ++i)
      if (!strcmp (t,ucase (strcpy (LOCAL->buf,s))) && (*n = 1 << i)) return f;
  }
  return NIL;			/* couldn't find keyword */
}

/* Parse a string
 * Accepts: function to return
 *	    pointer to string to return
 *	    pointer to string length to return
 * Returns: function to return
 */


search_t vms_mail_search_string (search_t f,char **d, long *n)
{
  char *end = " ";
  char *c = strtok (NIL,"");	/* remainder of criteria */
  if (!c) return NIL;		/* missing argument */
  switch (*c) {			/* see what the argument is */
  case '{':			/* literal string */
    *n = strtol (c+1,d,10);	/* get its length */
    if ((*(*d)++ == '}') && (*(*d)++ == '\015') && (*(*d)++ == '\012') &&
	(!(*(c = *d + *n)) || (*c == ' '))) {
      char e = *--c;
      *c = DELIM;		/* make sure not a space */
      strtok (c," ");		/* reset the strtok mechanism */
      *c = e;			/* put character back */
      break;
    }
  case '\0':			/* catch bogons */
  case ' ':
    return NIL;
  case '"':			/* quoted string */
    if (strchr (c+1,'"')) end = "\"";
    else return NIL;
  default:			/* atomic string */
    if (*d = strtok (c,end)) *n = strlen (*d);
    else return NIL;
    break;
  }
  return f;
}

static unsigned int SendContext = 0;
static int MessageSentCount;		/* Set by the error/success routine */


/*
 - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
     Purpose:
	Check error status, cancel the current send and return pointer to
	suitable error message.

     Author:
	Andy Harper, Kings College London, Oct 1996
 - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
*/
static char *chksndstat(char *message, int status)
  {
     static char errmess[MAXRECORDSIZE];

     DBGPRINT(DBG$SYSSTATUS,"%s: exit, status=0x%08x", message, status, 0, 0);

     if ((status & 0x1) == 0)
       {
         sprintf(errmess,"%s: failed with status 0x%08x", message, status);
         if (SendContext != 0) mail$send_end(&SendContext, NullItemList, NullItemList);
         return errmess;
       }

     return NULL;
  }




/*
 | this routine is called only if the message was sent ok. We count the number
 | of messages sent ok.
 */
static int success_send_result(item *recipient, int *sigarray, int userdata)
  {
    MessageSentCount++;	/* OK */
  }

/*
 - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
 | Get the message's text filename, the addresses and send it using the
 | selected mail protocol.
 | Returns NULL in case of success, or a pointer to the error message.
 - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
 */
char *vms_mail_send(char *filename, ADDRESS *from, ADDRESS *to, ADDRESS *cc, char *subject)
  {
    static char line[MAXRECORDSIZE];
    static short int typesu = MAIL$_TO, typecc = MAIL$_CC;
    ADDRESS *ap;
    FILE *ifd;
    int status, AddressesCount;
    char *err,*p, *MailProtocolUsed;

    item ToItem[] = {
	{ 0,              MAIL$_SEND_USERNAME,      line,               NULL},
	{ sizeof(typesu), MAIL$_SEND_USERNAME_TYPE, &typesu,            NULL},
	{ 0, 0, NULL, NULL }};

    item CcItem[] = {
	{ 0,              MAIL$_SEND_USERNAME,      line,                NULL},
	{ sizeof(typecc), MAIL$_SEND_USERNAME_TYPE, &typecc,             NULL},
	{ 0, 0, NULL, NULL }};

    item FromItem[] = {
	{ 0,              MAIL$_SEND_FROM_LINE,     line,                NULL},
	{ 0, 0, NULL, NULL }};

    item SubjectItem[] = {
	{ 0,              MAIL$_SEND_SUBJECT,       line,                NULL},
	{ 0, 0, NULL, NULL }};

    item RecordItem[] = {
	{ 0,              MAIL$_SEND_RECORD,        line,                NULL},
	{ 0, 0, NULL, NULL }};

    item FileItem[] = {
	{ 0,              MAIL$_SEND_FILENAME,      line,                NULL},
	{ 0, 0, NULL, NULL }};

    item SendItem[] = {
	{ sizeof (int *), MAIL$_SEND_SUCCESS_ENTRY, success_send_result, NULL},
	{ 0,              MAIL$_NOSIGNAL,           NULL,                NULL},
	{ 0, 0, NULL, NULL }};

/* Get the foreign protocol to use */
    DBGPRINT(DBG$ENTRY,"vms_mail_send: entered",0,0,0,0);
    if((MailProtocolUsed = __transport_name()) == NULL)
      return "No foreign mail transport protocol defined";

/* Create mail context */
    SendContext = 0;
    status = mail$send_begin(&SendContext, NullItemList, NullItemList);
    if ( (err = chksndstat("mail$send_begin", status)) != NULL) return err;

/* Create the sender's address */
/* Use the system default FROM: address (to avoid privileges)
    FromItem[0].length = sprintf(line, "%s <%s@%s>",
	(from->personal ? from->personal : ""), from->mailbox, from->host);
    status = mail$send_add_attribute(&SendContext, FromItem, NullItemList);
    if ( (err = chksndstat("mail$send_add_attribute", status)) != NULL) return err;
*/

/* Now the subject */
    if(subject) {
	SubjectItem[0].length = sprintf(line, "%s", subject);
	status = mail$send_add_attribute(&SendContext, SubjectItem, NullItemList);
       	if ( (err = chksndstat("mail$send_add_attribute", status)) != NULL) return err;
    }

/* now add the TO addresses */
    AddressesCount = 0;
    ap = to;
    while(ap != NULL) {
	ToItem[0].length = sprintf(line, "%s%%\"%s@%s\"",
		MailProtocolUsed, ap->mailbox, ap->host);
	status = mail$send_add_address(&SendContext, ToItem, NullItemList);
       	if ( (err = chksndstat("mail$send_add_address", status)) != NULL) return err;
	ap = ap->next;
	AddressesCount++;
    }

/* Same for CC */
    ap = cc;
    while(ap != NULL) {
	CcItem[0].length = sprintf(line, "%s%%\"%s@%s\"",
		MailProtocolUsed, ap->mailbox, ap->host);
	status = mail$send_add_address(&SendContext, CcItem, NullItemList);
       	if ( (err = chksndstat("mail$send_add_address", status)) != NULL) return err;
	ap = ap->next;
	AddressesCount++;
    }

/* Open the message's file */
    if((ifd = fopen(filename, "r")) == NULL) {
	mail$send_end(&SendContext, NullItemList, NullItemList);
	sprintf(line, "Can't open '%s'", filename);
	return line;
    }

/* Now copy it. VMS/MAIL can accept up to MAXRECORDSIZE characters long lines */

/* --- Method 1: Add each record, one at a time
    while(fgets(line, (sizeof(line) < MAXRECORDSIZE ? sizeof(line) - 1 : MAXRECORDSIZE), ifd) != NULL) {
	char *p = strchr(line, '\n');
	if(p) *p = '\0';
	RecordItem[0].length = strlen(line);
	status = mail$send_add_bodypart(&SendContext, RecordItem, NullItemList);
       	if ( (err = chksndstat("mail$send_add_bodypart", status)) != NULL) return err;
    }
*/
    fclose(ifd);

/* --- Method 2: Just add the filename */
    FileItem[0].length = sprintf(line, "%s", filename);
    status = mail$send_add_bodypart(&SendContext, FileItem, NullItemList);
    if ( (err = chksndstat("mail$send_add_bodypart", status)) != NULL) return err;



/* OK, done - actually send the message. This routine will call via AST a routine
   that will count how many were sent ok.
*/
    MessageSentCount = 0;	/* None sent yet */
    status = mail$send_message(&SendContext, SendItem, NullItemList);
    if ( (err = chksndstat("mail$send_message", status)) != NULL) return err;

    status = mail$send_end(&SendContext, NullItemList, NullItemList);
    if ( (err = chksndstat("mail$send_end", status)) != NULL) return err;

    if(MessageSentCount < AddressesCount)
      {
        switch (MessageSentCount)
          {
            case 0: sprintf(line,"error - no messages sent"); break;
            case 1: sprintf(line,"error - 1 message sent, %d failed",AddressesCount-1); break;
            default:sprintf(line,"error - %d messages sent, %d failed",MessageSentCount, AddressesCount-MessageSentCount); break;
          }
        return line;
      }
    DBGPRINT(DBG$EXIT,"vms_mail_send: exit OK",0,0,0,0);
    return NULL;	/* Success */
}
