/*****************************************************************************/
/*
                                  request.c


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


VERSION HISTORY
---------------
17-JUL-2012  MGD  bugfix; RequestPrivate() reset state if username changes
27-MAR-2011  MGD  bugfix; RequestVmsMailContext() CGIplus new mail detection
12-DEC-2007  MGD  bugfix; RequestPrivate() and RequestParsePath()
                    allow for GET request URI without /~
30-SEP-2007  MGD  RequestPrivate() and RequestPublic() when
                    [user-options-override] then OptionsParse() it
                  modify path parsing to allow [private-request] flagged
                    requests to employ non-script access URLs
13-APR-2007  MGD  RequestAccessLog() provide combined access log
16-MAR-2007  MGD  RequestVmsMailContext() recallable
17-APR-2006  MGD  RequestLastLogin() optionally sets private access
                    SYSUAF last-login at first (initial GET) access 
15-APR-2006  MGD  configuration [private-request] overrides the default path
                    prefix /~ for private access and requires changes to the
                    way private access is detected and the form action path
12-APR-2006  MGD  bugfix; RequestPublic() parsing folder with GET
09-APR-2006  MGD  RequestPublic() support public wildcard folder
                    configuration with MAIL and NEWMAIL prohibition
26-MAR-2006  MGD  RequestPrivate() Purveyor (1.2 at least) requires a full
                    URL to redirect (during logout)
16-MAR-2006  MGD  RequestPrivate() revised logout support
11-MAR-2006  MGD  RequestPrivate() no longer include the username when
                    generating the form action URI unless POSTMASTER
                    facility in use (this reduces the leakage of potentially
                    sensitive information through the "Referer:.." header)
                  bugfix; RequestPrivate() do not upper-case username when
                    parsing path as it can interfere with VMS external auth
18-FEB-2006  MGD  RequestPrivate() refine [logout] mechanism to improve
                    overall behaviour and avoid hole if MSIE [cancel]s
16-FEB-2006  MGD  better handle MAIL$_NOSUCHUSER from CallMailGetUserProfile()
30-JAN-2006  MGD  bugfix; RequestParsePath() attachment names can contain '/'
01-FEB-2005  MGD  initial
*/
/*****************************************************************************/

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

#pragma nomember_alignment

/* standard C header files */
#include <ctype.h>
#include <errno.h>
#include <ints.h>
#include <stdarg.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <time.h>
#include <unistd.h>

/* VMS related header files */
#include <descrip.h>
#include <prvdef.h>
#include <rmsdef.h>
#include <ssdef.h>
#include <stsdef.h>
#include <uaidef.h>

/* application specific */
#include "soymail.h"
#include "attach.h"
#include "callmail.h"
#include "cereal.h"
#include "cgilib.h"
#include "compose.h"
#include "config.h"
#include "lang.h"
#include "login.h"
#include "mainmenu.h"
#include "message.h"
#include "mta.h"
#include "newmail.h"
#include "options.h"
#include "other.h"
#include "public.h"
#include "request.h"

#define FI_LI "REGEX", __LINE__

#define MAIL$_NOSUCHUSER 8290346

/* can't use a language-specific message here, language not loaded yet */
#define STATE_ERROR "Inconsistent state data (%s).  Restarting session."

/********************/
/* external storage */
/********************/

extern BOOL  Debug,
             WatchEnabled;

extern int  SoyMailVolumeStruct;

extern char  CallMailNoSuchUser[],
             DocType[],
             SoftwareVn[];

extern int64  CurrentTime64;
extern unsigned short  CurrentNumTime[];

extern CONFIG_DATA  SoyMailConfig;
extern VMS_MAIL_USER  VmsMailUser;

/* required prototypes */
int sys$setuai (__unknown_params);

/****************************************************************************/
/*
Process a private (authenticated) request.
*/ 

RequestPrivate (REQUEST_DATA *rdptr)

{
   int  status;
   unsigned long  CurrentSeconds;
   char  *cptr, *sptr, *zptr;
   char  ch;
   char  PathUserName [64];
   USER_OPTIONS  *uoptr;
   REQUEST_DATA  *sdptr;
   VMS_MAIL_USER  *muptr;

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

   if (WatchEnabled) WatchThis (FI_LI, "RequestPrivate()");

   uoptr = &rdptr->UserOptions;
   muptr = &VmsMailUser;

   /* if not subject to authorization */
   if (!rdptr->RemoteUser[0]) ErrorExit (SS$_NOPRIV, FI_LI);

   /* map the authenticated username to establish the VMS username */
   if (!ConfigPrivateAccess (rdptr)) ErrorExit (SS$_NOPRIV, FI_LI);

   /* get any username from the request path */
   cptr = rdptr->CgiPathInfoPtr;
   if (WatchEnabled) WatchThis (FI_LI, "PATH_INFO: !AZ", cptr);

   if (*cptr == '/' && *(cptr+1) && *(cptr+1) != '/' && !isalnum(*(cptr+1)))
   {
      /* path-specified username (postmaster) must begin "/~" or equivalent */
      ch = *++cptr;
      while (*cptr && *cptr == ch) cptr++;
      zptr = (sptr = PathUserName) + sizeof(PathUserName)-1;
      while (*cptr && *cptr != '/' && sptr < zptr) *sptr++ = *cptr++;
      if (sptr >= zptr) ErrorExit (SS$_BUGCHECK, FI_LI);
      *sptr = '\0';
      if (WatchEnabled) WatchThis (FI_LI, "PathUserName: !AZ", PathUserName);

      /* if the path username is not the same as the mapped VMS username */
      if (PathUserName[0] && !strsame (PathUserName, rdptr->UserName, -1))
      {
         /* if was not mapped as a postmaster */
         if (!rdptr->PostMaster) ErrorExit (SS$_NOPRIV, FI_LI);
         /* replace with the path-specified username */
         zptr = (sptr = rdptr->UserName) + sizeof(rdptr->UserName)-1;
         for (cptr = PathUserName; *cptr && sptr < zptr; *sptr++ = *cptr++);
         if (sptr >= zptr) ErrorExit (SS$_BUGCHECK, FI_LI);
         *sptr = '\0';
         rdptr->UserNameLength = sptr - rdptr->UserName;
      }
      else
         rdptr->PostMaster = FALSE;
   }
   else
   if (SoyMailConfig.PrivateRequest ||
       SoyMailConfig.LoginSecret)
      rdptr->PostMaster = FALSE;
   else
      ErrorExit (SS$_BUGCHECK, FI_LI);

   RequestStateRestore (rdptr);
   sdptr = (REQUEST_DATA*)rdptr->StateDataPtr;

   if (sdptr->UserName[0] && !strsame (sdptr->UserName, rdptr->UserName, -1))
   {
      /* username has changed (login page) reset state data */
      memset (sdptr, 0, sizeof(REQUEST_DATA));
   }

   /* propagate */
   rdptr->PrevPageIdent = sdptr->ThisPageIdent;

   /* if the personal options were not included in the state data */
   if (sdptr->UserOptions.OptionsLoaded)
      memcpy (&rdptr->UserOptions,
              &sdptr->UserOptions,
              sizeof(rdptr->UserOptions));
   else
      OptionsLoadSoy (rdptr);

   /* and if required override some options */
   if (cptr = SoyMailConfig.UserOptionsOverride)
   {
      /* duplicate because OptionsParse() munges the string */
      sptr =  CgiLibVeeMemCalloc (strlen(cptr)+1);
      if (!sptr) ErrorExit (vaxc$errno, FI_LI);
      strcpy (sptr, cptr);
      OptionsParse (&rdptr->UserOptions, sptr);
      OptionsMunge (&rdptr->UserOptions);
   }

   /* an important flag to set in the mail user structure */
   if (uoptr->VmsNewMsg > 0)
      muptr->VmsNewMsg = TRUE;
   else
   if (uoptr->VmsNewMsg < 0)
      muptr->VmsNewMsg = FALSE;
   else
      muptr->VmsNewMsg = SoyMailConfig.VmsNewMsg;

   LangLoad (rdptr->UserOptions.Language);
   if (!uoptr->CharSet[0]) strcpy (uoptr->CharSet, LangFor("lang_charset"));

   /* these must be set after the language is loaded */
   if (!uoptr->FolderDraftItems[0])
      strzcpy (uoptr->FolderDraftItems, 
               LangFor("folder_draft_items"),
               sizeof(uoptr->FolderDraftItems));
   if (!uoptr->FolderWasteItems[0])
      strzcpy (uoptr->FolderWasteItems, 
               LangFor("folder_waste_items"),
               sizeof(uoptr->FolderWasteItems));

   /* propagate (any) previous search parameters from request to request */
   SearchPropagate (rdptr);

   /* check for the logout sentinal in the path */
   if (!strcmp (rdptr->CgiQueryStringPtr, SOY_LOGOUT_SENTINAL+1))
   {
      if (SoyMailConfig.LogoutDisabled)
         CgiLibResponseError (FI_LI, 0, LangFor("logout_disabled"));
      else
      {
         fprintf (stdout, "Status: 401 Authentication cancelled!\n");

         if (SoyMailConfig.LogoutRealm)
         {
            CGIVARNULL (cptr, "AUTH_TYPE");
            if (!cptr) cptr = "basic";
            fprintf (stdout, "WWW-Authenticate: %s realm=\"%s\"\n",
                     cptr, SoyMailConfig.LogoutRealm);
         }

         /* belt, braces and a piece of string */
         fprintf (stdout,
"Content-Type: text/html\n\
\n\
%s\n\
<html>\n\
<head>\n\
<meta http-equiv=\"refresh\" content=\"0;url=%s\">\n\
</head>\n\
<body onload=\"window.location.reload()\">\n\
<b>%s</b>\n\
</body>\n\
</html>\n",
                  DocType,
                  rdptr->CgiRequestUriPtr,
                  LangFor("logout_not"));
      }

      return;
   }

   RequestPrivateDefaults (rdptr);

   if (!strcmp (rdptr->CgiRequestMethodPtr, "GET"))
   {
      RequestParsePath (rdptr);

      if (rdptr->CgiQueryStringPtr[0])
      {
         if (isdigit(rdptr->CgiQueryStringPtr[0]))
         {
            NewMailXMLHttpRequest (rdptr);
            return; 
         }

         if (!strcmp (rdptr->CgiQueryStringPtr, "yougotmail.mp3"))
         {
            NewMailPersonalYouGotMail (rdptr);
            return; 
         }

         if (!strcmp (rdptr->CgiQueryStringPtr, "export.ldif"))
         {
            ContactsExport (rdptr);
            return; 
         }

         if (!strncmp (rdptr->CgiQueryStringPtr, "mailto:", 7))
         {
            ComposePageRequest (rdptr);
            return; 
         }

         if (!strncmp (rdptr->CgiQueryStringPtr, "view:", 5))
         {
            AttachView (rdptr, rdptr->CgiQueryStringPtr+5);
            return; 
         }

         if (!strncmp (rdptr->CgiQueryStringPtr, "preview", 7))
         {
            MessagePreView (rdptr);
            return; 
         }
      }

      if (rdptr->AttachmentNameLength)
      {
         /* get an attachment or other part */
         RequestVmsMailContext (rdptr);
         if (!MessagePart (rdptr))
            CgiLibResponseError (FI_LI, 0, LangFor("part_not_found"));
         return;
      }

      /* only performed for the initial GET */
      if (SoyMailConfig.UpdateLastLogin) RequestLastLogin (rdptr);
   }

   if (rdptr->PrevPageIdent == PAGE_HELP)
   {
      HelpPageRequest (rdptr);
      return;
   }

   RequestVmsMailContext (rdptr);

   if (!MainMenuRequest (rdptr))
   {
      /* not handled directly by the main menu */
      switch (rdptr->PrevPageIdent)
      {
         case PAGE_FOLDER :
            MessagePageFolderRequest (rdptr);
            break;
         case PAGE_MESSAGE :
            MessagePageMsgRequest (rdptr);
            break;
         case PAGE_SEARCH :
            SearchPageRequest (rdptr);
            break;
         case PAGE_OPTIONS :
            OptionsPageRequest (rdptr);
            break;
         case PAGE_CONTACTS :
            ContactsPageRequest (rdptr);
            break;
         case PAGE_COMPOSE :
            ComposePageRequest (rdptr);
            break;
         default :
            MessageFolderPage (rdptr);
      }
   }
}

/****************************************************************************/
/*
After preparing request data according to what button has been clicked (etc.)
this function is called to complete any request parameters that have not been
established by the calling code but are, or may be, necessary for appropriate
request processing.  Processing order is important as later parameters can
override earlier.
*/ 

BOOL RequestPrivateDefaults (REQUEST_DATA *rdptr)

{
   char  *cptr, *sptr, *zptr;
   char  FileFolderDest [256];
   VMS_MAIL_USER  *muptr;
   USER_OPTIONS  *uoptr;
   REQUEST_DATA  *sdptr;

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

   if (WatchEnabled) WatchThis (FI_LI, "RequestPrivateDefaults()");

   uoptr = &rdptr->UserOptions;
   sdptr = (REQUEST_DATA*)rdptr->StateDataPtr;
   muptr = &VmsMailUser;

   /****************/
   /* file/folder  */
   /****************/

   /* first check if a folder button has been selected */
   CGIVARNULL (cptr, "FORM_FOLDER_BTN");
   if (cptr)
   {
      /* although a little out-of-order look for message checkbox(es) */
      if (MessageIdList (rdptr))
      {
         /* yes there are, stay within the current folder */
         CGIVARNULL (cptr, "FORM_FOLDER_LIST_OPEN");
         if (cptr && cptr[0]) RequestParseFileFolder (rdptr, cptr);
      }
      else
      {
         /* parse the folder button */
         RequestParseFileFolder (rdptr, cptr);
         if (!rdptr->MailFileName[0])
         {
            /* folder (folder) buttons have no leading mail file */
            CGIVARNULL (cptr, "FORM_FOLDER_BTN_MAILFILE");
            if (cptr && cptr[0])
            {
               zptr = (sptr = rdptr->MailFileName) +
                      sizeof(rdptr->MailFileName)-1;
               while (*cptr && sptr < zptr) *sptr++ = *cptr++;
               *sptr = '\0';
               strcpy (rdptr->MailFileName, sdptr->MailFileName);
               rdptr->MailFileNameLength = sptr - rdptr->MailFileName;
            }
         }
      }
   }
   else
   {
      CGIVARNULL (cptr, "FORM_FOLDER_LIST_OPEN");
      if (cptr && cptr[0]) RequestParseFileFolder (rdptr, cptr);
   }

   if (uoptr->AllMailFiles)
   {
      zptr = (sptr = FileFolderDest) + sizeof(FileFolderDest)-1;
      CGIVAR (cptr, "FORM_FILE_TEXT_DEST");
      while (*cptr && isspace(*cptr)) cptr++;
      while (*cptr && sptr < zptr) *sptr++ = *cptr++;
      if (sptr > FileFolderDest && sptr < zptr) *sptr++ = '/';
      CGIVAR (cptr, "FORM_FOLDER_TEXT_DEST");
      while (*cptr && isspace(*cptr)) cptr++;
      while (*cptr && sptr < zptr) *sptr++ = *cptr++;
      if (sptr >= zptr) ErrorExit (SS$_BUGCHECK, FI_LI);
      *sptr = '\0';
      cptr = FileFolderDest;
   }
   else
   {
      CGIVAR (cptr, "FORM_FOLDER_TEXT_DEST");
      while (*cptr && isspace(*cptr)) cptr++;
   }

   /* when entering file/folder manually (creating?) refresh the lists */
   if (*cptr) FolderListReset (rdptr);

   if (!*cptr) CGIVAR (cptr, "FORM_FOLDER_LIST_DEST");
   if (*cptr) RequestParseDestFileFolder (rdptr, cptr);

   /****************************/
   /* basic request parameters */
   /****************************/

   if (!rdptr->MailFileName[0])
   {
      strcpy (rdptr->MailFileName, "MAIL");
      rdptr->MailFileNameLength = strlen(rdptr->MailFileName);
   }

   if (!rdptr->DestMailFileName[0])
   {
      strcpy (rdptr->DestMailFileName, rdptr->MailFileName);
      rdptr->DestMailFileNameLength = strlen(rdptr->DestMailFileName);
   }

   if (!rdptr->FolderName[0])
   {
      strcpy (rdptr->FolderName, "NEWMAIL");
      rdptr->FolderNameLength = strlen(rdptr->FolderName);
   }

   if (!rdptr->DestFolderName[0])
   {
      strcpy (rdptr->DestFolderName, rdptr->FolderName);
      rdptr->DestFolderNameLength = rdptr->FolderNameLength;
   }

   /*********************/
   /* refresh any lists */
   /*********************/

   if (WatchEnabled)
   {
      WatchThis (FI_LI, "MAIL file !AZ", rdptr->MailFileName);
      WatchThis (FI_LI, "MAIL folder !AZ", rdptr->FolderName);
      WatchThis (FI_LI, "DEST file !AZ", rdptr->DestMailFileName);
      WatchThis (FI_LI, "DEST folder !AZ", rdptr->DestFolderName);
   }

   /* sentinals and triggers to refresh the lists */
   if (strcmp (rdptr->MailFileName, sdptr->MailFileName))
      FolderListReset (rdptr);
   else
   if (strcmp (rdptr->DestMailFileName, sdptr->DestMailFileName))
      FolderListReset (rdptr);
   else
   if (LangSame ("(none)", rdptr->FolderName))
      FolderListReset (rdptr);
   else
   {
      unsigned long  TimeSeconds;
      time (&TimeSeconds);
      if (TimeSeconds - rdptr->FolderRefreshSeconds > FOLDER_REFRESH_SECONDS)
         FolderListReset (rdptr);
   }

   if (Debug)
      fprintf (stdout, "final |%s|%s|%s|%s|%s|%s|%d|\n",
               rdptr->UserName, rdptr->MailFileName, rdptr->DestMailFileName,
               rdptr->FolderName, rdptr->DestFolderName, rdptr->AttachmentName,
               rdptr->MessageId);

   /****************************/
   /* create the <form> action */
   /****************************/

   zptr = (sptr = rdptr->FormAction) + sizeof(rdptr->FormAction)-1;

   if (strsame (rdptr->CgiRequestUriPtr,
                rdptr->CgiScriptNamePtr,
                strlen(rdptr->CgiScriptNamePtr)))
   {
      cptr = rdptr->CgiScriptNamePtr;
      while (*cptr && sptr < zptr) *sptr++ = *cptr++;
      if (sptr < zptr) *sptr++ = '/';
      if (!SoyMailConfig.PrivateRequest && sptr < zptr) *sptr++ = '~';
      if (strcmp (rdptr->UserName, rdptr->RemoteUser))
         for (cptr = rdptr->UserName; *cptr && sptr < zptr; *sptr++ = *cptr++);
   }
   else
   {
      cptr = rdptr->CgiRequestUriPtr;
      if (*(USHORTPTR)cptr == '/\0') cptr = "";
      while (*cptr && sptr < zptr) *sptr++ = *cptr++;
   }
   if (sptr >= zptr) ErrorExit (SS$_BUGCHECK, FI_LI);
   *sptr = '\0';

   return (TRUE);
}

/****************************************************************************/
/*
Parse the mail file (optional) and folder names from a string in the format
'<file-name>/<folder-name>' or '<file-name>/' or '<folder-name>'
*/ 

void RequestParseFileFolder
(
REQUEST_DATA *rdptr,
char *FileFolderString
)
{
   int  SlashCount;
   char  *cptr, *sptr, *zptr;
   USER_OPTIONS  *uoptr;

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

   if (WatchEnabled)
      WatchThis (FI_LI, "RequestParseFileFolder() !AZ", FileFolderString);

   uoptr = &rdptr->UserOptions;

   cptr = FileFolderString;
   if (*cptr == '/') cptr++;
   SlashCount = 0;
   for (sptr = cptr; *sptr; sptr++) if (*sptr == '/') SlashCount++;
   if (SlashCount)
   {
      /* format is '<file-name>/<folder-name>' or '<filename>/' */
      zptr = (sptr = rdptr->MailFileName) + sizeof(rdptr->MailFileName)-1;
      while (*cptr && sptr < zptr)
      {
         if (*cptr == '/') SlashCount--;
         if (!SlashCount) break;
         *sptr++ = *cptr++;
      }
      *sptr = '\0';
      if (uoptr->PmdfImap)
      {
         /*
            With the PMDF IMAP user option enabled a mail file can only be
            "MAIL" (callable Mail) or something else (PMDF IMAP).  Append
            a trailing slash (the only character we can guarantee unused in
            a folder name) to indicate it's a PMDF IMAP file if not "MAIL".
         */
         if (strcmp (rdptr->MailFileName, "MAIL"))
            if (sptr < zptr)
            {
               *sptr++ = '/';
               *sptr = '\0';
            }
      }
      if (sptr >= zptr) ErrorExit (SS$_BUGCHECK, FI_LI);
      rdptr->MailFileNameLength = sptr - rdptr->MailFileName;

      /* if file name with trailing slash default to the "MAIL" folder */
      if (*cptr) cptr++;
      if (!*cptr) cptr = "MAIL";
      zptr = (sptr = rdptr->FolderName) + sizeof(rdptr->FolderName)-1;
      while (*cptr && sptr < zptr) *sptr++ = *cptr++;
      *sptr = '\0';
      if (sptr >= zptr) ErrorExit (SS$_BUGCHECK, FI_LI);
      rdptr->FolderNameLength = sptr - rdptr->FolderName;

      if (WatchEnabled)
         WatchThis (FI_LI, "FILE/FOLDER !AZ/!AZ",
                    rdptr->MailFileName, rdptr->FolderName);
   }
   else
   {
      /* just contains a folder name */
      strcpy (rdptr->MailFileName, "MAIL");
      rdptr->MailFileNameLength = 4;

      zptr = (sptr = rdptr->FolderName) + sizeof(rdptr->FolderName)-1;
      while (*cptr && sptr < zptr) *sptr++ = *cptr++;
      if (sptr >= zptr) ErrorExit (SS$_BUGCHECK, FI_LI);
      *sptr = '\0';
      rdptr->FolderNameLength = sptr - rdptr->FolderName;

      if (WatchEnabled)
         WatchThis (FI_LI, "(FILE/)FOLDER !AZ/!AZ",
                    rdptr->MailFileName, rdptr->FolderName);
   }
}

/****************************************************************************/
/*
Parse the destination mail file (optional) and folder names from a string in
the format '<file-name>/<folder-name>' or '<file-name>/' or '<folder-name>'
*/ 

void RequestParseDestFileFolder
(
REQUEST_DATA *rdptr,
char *FileFolderString
)
{
   int  SlashCount;
   char  *cptr, *sptr, *zptr;
   USER_OPTIONS  *uoptr;

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

   if (WatchEnabled)
      WatchThis (FI_LI, "RequestParseDestFileFolder() !AZ", FileFolderString);

   uoptr = &rdptr->UserOptions;

   cptr = FileFolderString;
   if (*cptr == '/') cptr++;
   SlashCount = 0;
   for (sptr = cptr; *sptr; sptr++) if (*sptr == '/') SlashCount++;
   if (SlashCount)
   {
      /* format is '<file-name>/<folder-name>' or '/<folder-name>' */
      zptr = (sptr = rdptr->DestMailFileName) +
             sizeof(rdptr->DestMailFileName)-1;
      while (*cptr && sptr < zptr)
      {
         if (*cptr == '/') SlashCount--;
         if (!SlashCount) break;
         *sptr++ = *cptr++;
      }
      *sptr = '\0';
      if (uoptr->PmdfImap)
      {
         /* seet comment in RequestParseFileFolder() above */
         if (strcmp (rdptr->DestMailFileName, "MAIL"))
            if (sptr < zptr)
            {
               *sptr++ = '/';
               *sptr = '\0';
            }
      }
      if (sptr >= zptr) ErrorExit (SS$_BUGCHECK, FI_LI);
      rdptr->DestMailFileNameLength = sptr - rdptr->DestMailFileName;

      /* if file name with trailing slash default to the MAIL folder */
      if (*cptr) cptr++;
      if (!*cptr) cptr = "MAIL";
      zptr = (sptr = rdptr->DestFolderName) +
             sizeof(rdptr->DestFolderName)-1;
      while (*cptr && sptr < zptr) *sptr++ = *cptr++;
      if (sptr >= zptr) ErrorExit (SS$_BUGCHECK, FI_LI);
      *sptr = '\0';
      rdptr->DestFolderNameLength = sptr - rdptr->DestFolderName;

      if (WatchEnabled)
         WatchThis (FI_LI, "DEST FILE/FOLDER !AZ/!AZ",
                    rdptr->DestMailFileName, rdptr->DestFolderName);
   }
   else
   {
      /* just contains a folder name */
      strcpy (rdptr->DestMailFileName, "MAIL");
      rdptr->DestMailFileNameLength = 4;

      zptr = (sptr = rdptr->DestFolderName) + sizeof(rdptr->DestFolderName)-1;
      while (*cptr && sptr < zptr) *sptr++ = *cptr++;
      if (sptr >= zptr) ErrorExit (SS$_BUGCHECK, FI_LI);
      *sptr = '\0';
      rdptr->DestFolderNameLength = sptr - rdptr->DestFolderName;

      /* now, is there a mail file being specified by the list? */
      CGIVARNULL (cptr, "FORM_FOLDER_LIST_DEST");
      if (cptr && cptr[0])
      {
         if (*cptr == '/') cptr++;
         for (sptr = cptr; *sptr && *sptr != '/' && *sptr != '<'; sptr++);
         if (*sptr && *sptr == '/')
         {
            zptr = (sptr = rdptr->DestMailFileName) +
                   sizeof(rdptr->DestMailFileName)-1;
            while (*cptr && *cptr != '/' && sptr < zptr) *sptr++ = *cptr++;
            if (sptr >= zptr) ErrorExit (SS$_BUGCHECK, FI_LI);
            *sptr = '\0';
            rdptr->DestMailFileNameLength = sptr - rdptr->DestMailFileName;
         }
      }

      if (WatchEnabled)
         WatchThis (FI_LI, "DEST (FILE/)FOLDER !AZ/!AZ",
                    rdptr->DestMailFileName, rdptr->DestFolderName);
   }
}

/****************************************************************************/
/*
Parse the request path for '/username/file/folder/message/attachment.name'.
This should be done last so that any path-supplied components (e.g. username)
override any form supplied components.
*/ 

BOOL RequestParsePath (REQUEST_DATA *rdptr)

{
   char  *cptr, *sptr, *zptr;
   char  ch;

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

   if (WatchEnabled) WatchThis (FI_LI, "RequestParsePath()");

   cptr = rdptr->CgiPathInfoPtr;

   if (*cptr == '/' && *(cptr+1) && *(cptr+1) != '/' && !isalnum(*(cptr+1)))
   {
      /* path-specified username (postmaster) must begin "/~" or equivalent */
      ch = *++cptr;
      while (*cptr && *cptr == ch) cptr++;
      if (isalnum(*cptr))
      {
         zptr = (sptr = rdptr->UserName) + sizeof(rdptr->UserName)-1;
         while (*cptr && *cptr != '/' && sptr < zptr) *sptr++ = *cptr++;
         if (sptr >= zptr) ErrorExit (SS$_BUGCHECK, FI_LI);
         *sptr = '\0';
         rdptr->UserNameLength = strlen(rdptr->UserName);
      }
   }
   else
   if (SoyMailConfig.PrivateRequest ||
       SoyMailConfig.LoginSecret)
   {
      /* just skip over any largely irrelevant username component */
      for (cptr++; *cptr && *cptr != '/'; *cptr++);
   }
   else
      ErrorExit (SS$_BUGCHECK, FI_LI);

   if (*cptr == '/')
   {
      cptr++;
      zptr = (sptr = rdptr->MailFileName) + sizeof(rdptr->MailFileName)-1;
      while (*cptr && *cptr != '/' && sptr < zptr) *sptr++ = *cptr++;
      if (sptr >= zptr) ErrorExit (SS$_BUGCHECK, FI_LI);
      *sptr = '\0';
      rdptr->MailFileNameLength = strlen(rdptr->MailFileName);
   }

   if (*cptr == '/')
   {
      cptr++;
      zptr = (sptr = rdptr->FolderName) + sizeof(rdptr->FolderName)-1;
      while (*cptr && *cptr != '/' && sptr < zptr) *sptr++ = *cptr++;
      if (sptr >= zptr) ErrorExit (SS$_BUGCHECK, FI_LI);
      *sptr = '\0';
      rdptr->FolderNameLength = strlen(rdptr->FolderName);
   }

   if (*cptr == '/')
   {
      cptr++;
      if (isdigit(*cptr))
      {
         rdptr->MessageId = atoi(cptr);
         while (*cptr && isdigit(*cptr)) cptr++;
         if (*cptr == '.')
         {
            cptr++;
            if (isdigit(*cptr))
            {
               rdptr->MessageHash = atoi(cptr);
               while (*cptr && isdigit(*cptr)) cptr++;
            }
         }
      }
   }

   if (*cptr == '/')
   {
      cptr++;
      zptr = (sptr = rdptr->AttachPartName) + sizeof(rdptr->AttachPartName)-1;
      while (*cptr && *cptr != '/' && sptr < zptr) *sptr++ = *cptr++;
      if (sptr >= zptr) ErrorExit (SS$_BUGCHECK, FI_LI);
      *sptr = '\0';
      rdptr->AttachPartNameLength = sptr - rdptr->AttachPartName;
   }

   if (*cptr == '/')
   {
      cptr++;
      zptr = (sptr = rdptr->AttachmentName) + sizeof(rdptr->AttachmentName)-1;
      /* right to the end of the path */
      while (*cptr && sptr < zptr) *sptr++ = *cptr++;
      if (sptr >= zptr) ErrorExit (SS$_BUGCHECK, FI_LI);
      *sptr = '\0';
      rdptr->AttachmentNameLength = sptr - rdptr->AttachmentName;
   }

   if (WatchEnabled)
      WatchThis (FI_LI, "PARSED /!AZ/!AZ/!AZ/!UL.!UL/!AZ/!AZ",
                 rdptr->UserName, rdptr->MailFileName, rdptr->FolderName,
                 rdptr->MessageId, rdptr->MessageHash, rdptr->AttachPartName,
                 rdptr->AttachmentName);

   return (TRUE);
}

/****************************************************************************/
/*
Process a public (non-authenticated) request.
*/ 

RequestPublic (REQUEST_DATA *rdptr)

{
   char  *cptr, *sptr, *zptr;
   USER_OPTIONS  *uoptr;
   REQUEST_DATA  *sdptr;
   VMS_MAIL_USER  *muptr;

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

   if (WatchEnabled) WatchThis (FI_LI, "RequestPublic()");

   /* if not subject to authorization - and should be! */
   if (!rdptr->RemoteUser[0] && rdptr->PublicAuthorized)
      ErrorExit (SS$_NOPRIV, FI_LI);

   muptr = &VmsMailUser;
   uoptr = &rdptr->UserOptions;

   RequestStateRestore (rdptr);
   sdptr = (REQUEST_DATA*)rdptr->StateDataPtr;

   /* propagate */
   rdptr->PrevPageIdent = sdptr->ThisPageIdent;

   /* if the 'personal' options were not included in the state data */
   if (sdptr->UserOptions.OptionsLoaded)
      memcpy (&rdptr->UserOptions,
              &sdptr->UserOptions,
              sizeof(rdptr->UserOptions));
   else
      OptionsPublicGet (rdptr);

   /* and if required override some options */
   if (cptr = SoyMailConfig.UserOptionsOverride)
   {
      /* duplicate because OptionsParse() munges the string */
      sptr =  CgiLibVeeMemCalloc (strlen(cptr)+1);
      if (!sptr) ErrorExit (vaxc$errno, FI_LI);
      strcpy (sptr, cptr);
      OptionsParse (&rdptr->UserOptions, sptr);
      OptionsMunge (&rdptr->UserOptions);
   }

   LangLoad (rdptr->UserOptions.Language);
   if (!uoptr->CharSet[0]) strcpy (uoptr->CharSet, LangFor("lang_charset"));

   /* propagate (any) previous search parameters from request to request */
   SearchPropagate (rdptr);

   RequestPublicDefaults (rdptr);

   if (rdptr->PublicAuthorized)
   {
      CGIVARNULL (cptr, "FORM_MAIN_BTN");
      if (cptr && LangSame ("logout", cptr))
      {
         /**********/
         /* logout */
         /**********/

         if (SoyMailConfig.LogoutDisabled)
         {
            StatusMessage (FI_LI, 1, LangFor("logout_disabled"));
            return (FALSE);
         }
         if (SoyMailConfig.LoginSecret)
         {
            /* using soyMAIL's autogenous authentication, force re-login */
            LoginPage (rdptr, "", NULL, 0);
            RequestAccessLog (NULL, "logout");
         }
         else
         {
            strcat (rdptr->FormAction, SOY_LOGOUT_SENTINAL);
            CgiLibResponseRedirect (rdptr->FormAction);
         }
         return (TRUE);
      }
   }

   if (!strcmp (rdptr->CgiRequestMethodPtr, "GET"))
   {
      /**************/
      /* GET method */
      /**************/

      /* check if there is attachment detail here */
      cptr = rdptr->CgiPathInfoPtr;
      if (*cptr == '/') cptr++;
      while (*cptr && *cptr != '/') cptr++;
      if (*cptr == '/') cptr++;
      if (*cptr && rdptr->PublicWildcard)
      {
         /* includes the folder name */
         zptr = (sptr = rdptr->FolderName) + sizeof(rdptr->FolderName)-1;
         while (*cptr && *cptr != '/' && sptr < zptr) *sptr++ = *cptr++;
         if (sptr >= zptr) ErrorExit (SS$_BUGCHECK, FI_LI);
         *sptr = '\0';
         rdptr->FolderNameLength = strlen(rdptr->FolderName);
         if (*cptr == '/') cptr++;
      }

      if (*cptr)
      {
         if (!isdigit(*cptr)) ErrorExit (SS$_BUGCHECK, FI_LI);
         rdptr->MessageId = atoi(cptr);
         while (isdigit(*cptr)) cptr++;
         if (*cptr == '.')
         {
            cptr++;
            if (isdigit(*cptr))
            {
               rdptr->MessageHash = atoi(cptr);
               while (*cptr && isdigit(*cptr)) cptr++;
            }
         }
      }

      if (*cptr == '/')
      {
         cptr++;
         zptr = (sptr = rdptr->AttachPartName) +
            sizeof(rdptr->AttachPartName)-1;
         while (*cptr && *cptr != '/' && sptr < zptr) *sptr++ = *cptr++;
         if (sptr >= zptr) ErrorExit (SS$_BUGCHECK, FI_LI);
         *sptr = '\0';
         rdptr->AttachPartNameLength = sptr - rdptr->AttachPartName;
      }

      if (*cptr == '/')
      {
         cptr++;
         zptr = (sptr = rdptr->AttachmentName) +
                sizeof(rdptr->AttachmentName)-1;
         while (*cptr && sptr < zptr) *sptr++ = *cptr++;
         if (sptr >= zptr) ErrorExit (SS$_BUGCHECK, FI_LI);
         *sptr = '\0';
         rdptr->AttachmentNameLength = sptr - rdptr->AttachmentName;
      }

      if (WatchEnabled)
         WatchThis (FI_LI, "PARSED /!AZ/!UL.!UL/!AZ/!AZ",
                    rdptr->FolderName, rdptr->MessageId, rdptr->MessageHash,
                    rdptr->AttachPartName, rdptr->AttachmentName);

      if (rdptr->AttachmentNameLength)
      {
         /***********************************/
         /* get an attachment or other part */
         /***********************************/

         if (rdptr->PublicWildcard &&
             rdptr->PublicNoNewMail &&
             (!strcmp (rdptr->FolderName, "MAIL") ||
              !strcmp (rdptr->FolderName, "NEWMAIL")))
            ErrorExit (SS$_NOPRIV, FI_LI);

         RequestVmsMailContext (rdptr);
         if (!MessagePart (rdptr))
            CgiLibResponseError (FI_LI, 0, LangFor("part_not_found"));
         return (TRUE);
      }
   }

   if (rdptr->PublicWildcard &&
       rdptr->PublicNoNewMail &&
       (!strcmp (rdptr->FolderName, "MAIL") ||
        !strcmp (rdptr->FolderName, "NEWMAIL")))
      ErrorExit (SS$_NOPRIV, FI_LI);

   RequestVmsMailContext (rdptr);

   if (rdptr->PrevPageIdent == PAGE_MESSAGE ||
       rdptr->MessageId)
      PublicMessageRequest (rdptr);
   else
      PublicFolderRequest (rdptr);
}

/****************************************************************************/
/*
Public defaults.  Processing order is important as later parameters can
override earlier. 
*/ 

BOOL RequestPublicDefaults (REQUEST_DATA *rdptr)

{
   char  *cptr, *sptr, *zptr;
   VMS_MAIL_USER  *muptr;

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

   if (WatchEnabled) WatchThis (FI_LI, "RequestPublicDefaults()");

   muptr = &VmsMailUser;

   if (rdptr->PublicWildcard)
   {
      /*******************/
      /* wildcard public */
      /*******************/

      /* folder selection (change) will propagate search, [open] doesn't */
      CGIVARNULL (cptr, "FORM_MAIN_BTN");
      if (cptr && *cptr) rdptr->SearchData.InProgress = FALSE;

      CGIVARNULL (cptr, "FORM_FOLDER_LIST_OPEN");
      if (cptr && cptr[0])
      {
         zptr = (sptr = rdptr->FolderName) + sizeof(rdptr->FolderName)-1;
         while (*cptr && sptr < zptr) *sptr++ = *cptr++;
         *sptr = '\0';
         rdptr->FolderNameLength = sptr - rdptr->FolderName;
      }
   }

   /****************************/
   /* basic request parameters */
   /****************************/

   if (!rdptr->MailFileName[0])
   {
      strcpy (rdptr->MailFileName, "MAIL");
      rdptr->MailFileNameLength = strlen(rdptr->MailFileName);
   }

   if (!rdptr->FolderName[0])
   {
      strcpy (rdptr->FolderName, "NEWMAIL");
      rdptr->FolderNameLength = strlen(rdptr->FolderName);
   }


   if (Debug)
      fprintf (stdout, "final |%s|%s|%s|%s|%d|\n",
               rdptr->UserName, rdptr->MailFileName,
               rdptr->FolderName,  rdptr->AttachmentName,
               rdptr->MessageId);

   /****************************/
   /* create the <form> action */
   /****************************/

   zptr = (sptr = rdptr->FormAction) + sizeof(rdptr->FormAction)-1;
   cptr = rdptr->CgiScriptNamePtr;
   while (*cptr && sptr < zptr) *sptr++ = *cptr++;
   cptr = rdptr->CgiPathInfoPtr;
   if (*cptr == '/' && sptr < zptr) *sptr++ = *cptr++;
   while (*cptr && *cptr != '/' && sptr < zptr) *sptr++ = *cptr++;
   if (*cptr == '/' && sptr < zptr) *sptr++ = *cptr++;
   if (sptr >= zptr) ErrorExit (SS$_BUGCHECK, FI_LI);
   *sptr = '\0';

   return (TRUE);
}

/****************************************************************************/
/*
Call VMS Mail to establish user context.
*/ 

void RequestVmsMailContext (REQUEST_DATA *rdptr)

{
   VMS_MAIL_USER  *muptr;

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

   if (WatchEnabled) WatchThis (FI_LI, "RequestVmsMailContext()");

   muptr = &VmsMailUser;

   if (!muptr->UserName[0])
   {
      strcpy (muptr->UserName, rdptr->UserName);
      muptr->UserNameLength = strlen(muptr->UserName);

      CallMailGetUserProfile (muptr);
      if (VMSnok (muptr->MailVmsStatus))
      {
         /* pretty fundamental error */
         if (WatchEnabled) WatchThis (FI_LI, "%X!8XL", muptr->MailVmsStatus);
         if (muptr->MailVmsStatus == MAIL$_NOSUCHUSER)
         {
            CgiLibResponseError (FI_LI, 0, CallMailNoSuchUser);
            exit (SS$_NORMAL);
         }
         else
            ErrorExit (muptr->MailVmsStatus, FI_LI);
      }

      if (WatchEnabled)
         WatchThis (FI_LI, "!AZ !AZ !AZ",
                    muptr->UserName, muptr->VmsMailFileName,
                    muptr->VmsMailFolderName);
   }

   rdptr->NewMessages = muptr->VmsMailNewMessages;

   strcpy (muptr->VmsMailFileName, rdptr->MailFileName);
   muptr->VmsMailFileNameLength = rdptr->MailFileNameLength;

   strcpy (muptr->VmsMailFolderName, rdptr->FolderName);
   muptr->VmsMailFolderNameLength = rdptr->FolderNameLength;

   if (rdptr->DestMailFileNameLength)
   {
      strcpy (muptr->VmsMailDestFileName, rdptr->DestMailFileName);
      muptr->VmsMailDestFileNameLength = rdptr->DestMailFileNameLength;
   }
   else
   {
      strcpy (muptr->VmsMailDestFileName, rdptr->MailFileName);
      muptr->VmsMailDestFileNameLength = rdptr->MailFileNameLength;
   }
   strcpy (muptr->VmsMailDestFolderName, rdptr->DestFolderName);
   muptr->VmsMailDestFolderNameLength = rdptr->DestFolderNameLength;

   SoyMailVolumeStruct = CallMailVolumeStruct (muptr->VmsMailFullDirectory);
}

/****************************************************************************/
/*
Set the SYSUAF last-login datum.
*/ 

void RequestLastLogin (REQUEST_DATA *rdptr)

{
   static struct ItemList3Struct
   {
      unsigned short  buf_len;
      unsigned short  item;
      void  *buf_addr;
      void  *ret_len;
   }
   LastLoginI_Item [] = 
   {
      { 8, UAI$_LASTLOGIN_I, &CurrentTime64, 0 },
      { 0, 0, 0, 0 } 
   },
   LastLoginN_Item [] = 
   {
      { 8, UAI$_LASTLOGIN_N, &CurrentTime64, 0 },
      { 0, 0, 0, 0 } 
   };

   int  status;
   $DESCRIPTOR (UserNameDsc, "");

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

   if (WatchEnabled)
      WatchThis (FI_LI, "UAI$_LASTLOGIN_!AZ !AZ !%D",
                 toupper(SoyMailConfig.UpdateLastLogin[0]) == 'I' ? "I" : "N",
                 rdptr->UserName, &CurrentTime64);

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

   if (toupper(SoyMailConfig.UpdateLastLogin[0]) == 'I')
      status = sys$setuai (0, 0, &UserNameDsc, &LastLoginI_Item, 0, 0, 0);
   else
      status = sys$setuai (0, 0, &UserNameDsc, &LastLoginN_Item, 0, 0, 0);

   if (VMSnok (status)) ErrorExit (status, FI_LI);
}

/*****************************************************************************/
/*
Serialize the private state data.
*/

void RequestPrivateStateSave (REQUEST_DATA *rdptr)

{
   char  *flptr, *dfptr, *rdfptr, *rflptr, *rsptr;
   VMS_MAIL_USER  *muptr;

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

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

   muptr = &VmsMailUser;

   rdptr->StateSaveCount++;
   memcpy (rdptr->StateVersion, SoftwareVn, sizeof(rdptr->StateVersion));

   rsptr = CerealDataOut (rdptr, sizeof(REQUEST_DATA));

   if (muptr->DestFolderList.ListLength)
      dfptr = CerealDataOut (muptr->DestFolderList.ListPtr,
                             muptr->DestFolderList.ListLength);
   else
      dfptr = "";

   if (muptr->RecentDestFolderList.ListLength)
      rdfptr = CerealDataOut (muptr->RecentDestFolderList.ListPtr,
                              muptr->RecentDestFolderList.ListLength);
   else
      rdfptr = "";

   if (muptr->MailFolderList.ListLength)
      flptr = CerealDataOut (muptr->MailFolderList.ListPtr,
                             muptr->MailFolderList.ListLength);
   else
      flptr = "";

   if (muptr->RecentMailFolderList.ListLength)
      rflptr = CerealDataOut (muptr->RecentMailFolderList.ListPtr,
                              muptr->RecentMailFolderList.ListLength);
   else
      rflptr = "";

   fprintf (stdout,
"<input type=\"hidden\" name=\"state_request\" value=\"%s\">\n\
<input type=\"hidden\" name=\"state_destin\" value=\"%s\">\n\
<input type=\"hidden\" name=\"state_folder\" value=\"%s\">\n\
<input type=\"hidden\" name=\"state_rdestin\" value=\"%s\">\n\
<input type=\"hidden\" name=\"state_rfolder\" value=\"%s\">\n",
            rsptr, dfptr, flptr, rdfptr, rflptr);

}

/*****************************************************************************/
/*
Serialize the public state data.
*/

void RequestPublicStateSave (REQUEST_DATA *rdptr)

{
   char  *rsptr;

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

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

   /* reset possibly sensitive information in the request structure */
   memset (rdptr->UserName, 0, sizeof(rdptr->UserName));
   if (!rdptr->PublicWildcard)
   {
      memset (rdptr->MailFileName, 0, sizeof(rdptr->MailFileName));
      memset (rdptr->FolderName, 0, sizeof(rdptr->FolderName));
   }

   rdptr->StateSaveCount++;
   memcpy (rdptr->StateVersion, SoftwareVn, sizeof(rdptr->StateVersion));

   rsptr = CerealDataOut (rdptr, sizeof(REQUEST_DATA));

   fprintf (stdout,
"<input type=\"hidden\" name=\"state_request\" value=\"%s\">\n",
            rsptr);

}

/*****************************************************************************/
/*
Allocate a dynamic state (request) structure.
Attempt to get data from the CGI request state variable.
*/

BOOL RequestStateRestore (REQUEST_DATA *rdptr)

{
   int  dlen, retval;
   char  *cptr, *zptr;
   REQUEST_DATA  *sdptr;
   VMS_MAIL_USER  *muptr;

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

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

   muptr = &VmsMailUser;

   /*****************/
   /* request state */
   /*****************/

   rdptr->StateDataPtr = sdptr =  CgiLibVeeMemCalloc (sizeof(REQUEST_DATA));

   CGIVARNULL (cptr, "FORM_STATE_REQUEST");
   if (!cptr) return (FALSE);

   retval = CerealDataIn (cptr, rdptr->StateDataPtr, sizeof(REQUEST_DATA));
   if (Debug) fprintf (stdout, "%d\n", retval);

   if (!retval)
   {
      StatusMessage (FI_LI, 1, STATE_ERROR, "corruption");
      return (FALSE);
   }
   if (memcmp (((REQUEST_DATA*)rdptr->StateDataPtr)->StateVersion,
               SoftwareVn,
               sizeof((REQUEST_DATA*)rdptr->StateDataPtr)->StateVersion))
   {
      StatusMessage (FI_LI, 1, STATE_ERROR, "version");
      memset (rdptr->StateDataPtr, 0, sizeof(REQUEST_DATA));
      return (FALSE);
   }

   if (WatchEnabled)
   {
      WatchThis (FI_LI, "STATE DATA UserName {!UL}!AZ",
                 strlen(sdptr->UserName), sdptr->UserName);
      WatchThis (FI_LI, "STATE DATA MailFileName {!UL}!AZ",
                 strlen(sdptr->MailFileName), sdptr->MailFileName);
      WatchThis (FI_LI, "STATE DATA DestMailFileName {!UL}!AZ",
                 strlen(sdptr->DestMailFileName), sdptr->DestMailFileName);
      WatchThis (FI_LI, "STATE DATA FolderName {!UL}!AZ",
                 strlen(sdptr->FolderName), sdptr->FolderName);
      WatchThis (FI_LI, "STATE DATA DestFolderName {!UL}!AZ",
                 strlen(sdptr->DestFolderName), sdptr->DestFolderName);
      WatchThis (FI_LI, "STATE DATA AttachmentName {!UL}!AZ",
                 strlen(sdptr->AttachmentName), sdptr->AttachmentName);
      WatchThis (FI_LI, "STATE DATA FolderMessageCount !UL",
                 sdptr->FolderMessageCount);
      WatchThis (FI_LI, "STATE DATA MessageId !UL", sdptr->MessageId);
      WatchThis (FI_LI, "STATE DATA RangeIdStart !UL", sdptr->RangeIdStart);
      WatchThis (FI_LI, "STATE DATA RangeIdCount !UL", sdptr->RangeIdCount);
      WatchThis (FI_LI, "STATE DATA \'search\' !UL !UL !UL !UL \
!AZ !AZ !AZ !AZ !AZ !AZ",
                 sdptr->SearchData.DateScope,
                 sdptr->SearchData.DateDay,
                 sdptr->SearchData.DateMonth,
                 sdptr->SearchData.DateYear,
                 sdptr->SearchData.From,
                 sdptr->SearchData.To,
                 sdptr->SearchData.Cc,
                 sdptr->SearchData.Subject,
                 sdptr->SearchData.Message,
                 sdptr->SearchData.Attachment);
   }

   RequestStateFolder ("FORM_STATE_DESTIN", &muptr->DestFolderList);

   RequestStateFolder ("FORM_STATE_RDESTIN", &muptr->RecentDestFolderList);

   RequestStateFolder ("FORM_STATE_FOLDER", &muptr->MailFolderList);

   RequestStateFolder ("FORM_STATE_RFOLDER", &muptr->RecentMailFolderList);

   return (TRUE);
}

/*****************************************************************************/
/*
Retrive this list's state from the CGI variable.
*/

int RequestStateFolder
(
char *StateName,
VMS_MAIL_LIST *mlptr
)
{
   int  dlen;
   char  *cptr, *czptr;

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

   CGIVARNULL (cptr, StateName);
   if (!cptr) return (0);

   if (!(dlen = CerealDataIn (cptr, NULL, 0))) return (0);

   if (Debug) fprintf (stdout, "rfolder:%d\n", dlen);
   mlptr->ListPtr =  CgiLibVeeMemCalloc (dlen+8);
   mlptr->ListSize = mlptr->ListLength = dlen;
   CerealDataIn (cptr, mlptr->ListPtr, dlen);

   /* calculate the number of null-terminated items in the list */
   czptr = (cptr = mlptr->ListPtr) + mlptr->ListLength;
   mlptr->ListCount = 0;
   while (cptr < czptr) if (!*cptr++) mlptr->ListCount++;

   if (WatchEnabled) WatchThis (FI_LI, "!AZ !UL", StateName, mlptr->ListCount);
}

/*****************************************************************************/
/*
This function appends access log records to the file specified by [access-log]
configuration directive.  The log is designed supplement (not supplant) the
server access log with authenticated username data for soyMAIL autogenous
authentication (data that is no longer available in the server access log
because it is no longer performing the authentication).  It is in 'combined'
(NCSA) format so as to make it amenable to processing with a log analysis tool
if desired.  Datum for request 'response bytes' is always zero.

The 'Codicil' parameter provides additional information about the nature of the
request, in particular login/log-fail/logout data.  It can be identified by a
delimitting exclamation mark and then text following the requet URI.  This
helps supplement the HTTP status which will never indicate 401 for a failed
login attempt (it not being HTTP authentication and all).

The log file name can be fully specified by [access-log] or optionally can
include up to three C-RTL conversion sequences to introduce components of the
date into the log file name.  For example; HT_LOGS:SOYMAIL_%04d.LOG introduces
the year into the name, HT_LOGS:SOYMAIL_%04d%02d.LOG the year and month, and
HT_LOGS:SOYMAIL_%04d%02d%02d.LOG the year, month and day.  This allows access
log size to be managed more effectively for busy sites, and/or log files to be
periodically rotated.  (It's a bit rude and crude but there we go.)

The function is designed to be called twice.  At the start of request
processing to ensure that the log file is accessable (indicated by 'rdptr'
being NULL).  If not then the request is not processed.  Then at the conclusion
of the request where the actual access log entry is written.  The log file is
not kept open between these two calls so as to minimise the period the file is
actually open and therefore locked, which on a busy site could be indefinitely. 
There is an effective trade-off against the extra file-system activity.

Combined format:
'client rfc1413 auth-user date/time request status bytes referer user-agent'
*/

int RequestAccessLog
(
REQUEST_DATA *rdptr,
char *CodicilPtr
)
{
   static char  *MonName[] = { "", "Jan", "Feb", "Mar", "Apr", "May", "Jun",
                                   "Jul", "Aug", "Sep", "Oct", "Nov", "Dec" };
   static char  CodicilBuffer [64],
                LogFileName [256];

   int  cnt, status,
        HttpStatus;
   char  *cptr,
         *HttpRefererPtr,
         *HttpUserAgentPtr,
         *RemoteHostPtr,
         *ServerProtocolPtr,
         *UserNamePtr;
   FILE  *alptr;

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

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

   if (!SoyMailConfig.AccessLogFile) return (SS$_NORMAL);

   if (CodicilPtr)
   {
      strcpy (CodicilBuffer, "!");
      strcat (CodicilBuffer, CodicilPtr);
      if (!rdptr) return (SS$_NORMAL);
   }

   if (!LogFileName[0])
   {
      /* rudimentary sanity check - up to three "%d"s */
      cnt = 0;
      for (cptr = SoyMailConfig.AccessLogFile; *cptr; cptr++)
      {
         if (*cptr == '%')
         {
            cptr++;
            while (isdigit(*cptr)) cptr++;
            if (*cptr != 'd') break;
            if (++cnt > 3) break;
         }
      }
      if (*cptr) return (RMS$_SYN);

      if (cnt)
         sprintf (LogFileName, SoyMailConfig.AccessLogFile,
                  CurrentNumTime[0], CurrentNumTime[1],
                  CurrentNumTime[2]); 
      else
         strcpy (LogFileName, SoyMailConfig.AccessLogFile);
   }

   /* if it's locked for any reason then retry */
   for (cnt = 0; cnt < SOYMAIL_ACCESS_LOG_ATTEMPTS; cnt++)
   {
      /* record mode is required to support concurrent writers */
      alptr = fopen (LogFileName, "a", "ctx=rec", "shr=put");
      if (alptr) break;
      status = vaxc$errno;
      if (status != RMS$_FLK) break;
      sleep (1);
   }

   /* if failed to open the log file */
   if (!alptr) return (status);

   if (!rdptr)
   {
      /* just testing that the log file is accessable */
      fclose (alptr);
      return (SS$_NORMAL);
   }

   CGIVARNULL (RemoteHostPtr, "REMOTE_HOST");
   if (!RemoteHostPtr) CGIVARNULL (RemoteHostPtr, "REMOTE_ADDR");
   if (!RemoteHostPtr) RemoteHostPtr = "?";

   if (rdptr->RemoteUser[0])
      /* authenticated username */
      UserNamePtr = rdptr->RemoteUser;
   else
      /* attempt to obtain any supplied login page username form field */
      CGIVARNULL (UserNamePtr, "FORM_LOGIN_USERNAME");
   if (!UserNamePtr || !*UserNamePtr) UserNamePtr = "-";

   CGIVARNULL (ServerProtocolPtr, "SERVER_PROTOCOL");
   if (!ServerProtocolPtr) ServerProtocolPtr = "?";

   HttpStatus = CgiLibResponseGetStatus ();

   CGIVARNULL (HttpRefererPtr, "HTTP_REFERER");
   /* referer legitimately can be absent */
   if (!HttpRefererPtr) HttpRefererPtr = "";

   CGIVARNULL (HttpUserAgentPtr, "HTTP_USER_AGENT");
   /* user agent legitimately can be absent */
   if (!HttpUserAgentPtr) HttpUserAgentPtr = "";

   fprintf (alptr,
"%s - %s [%02d/%s/%4d:%02d:%02d:%02d %s] \
\"%s %s%s %s\" %03d 0 \"%s\" \"%s\"\n",
            RemoteHostPtr, UserNamePtr,
            CurrentNumTime[2], MonName[CurrentNumTime[1]],
            CurrentNumTime[0], CurrentNumTime[3], CurrentNumTime[4],
            CurrentNumTime[5], MtaTimezone(NULL),
            rdptr->CgiRequestMethodPtr, rdptr->CgiRequestUriPtr,
            CodicilBuffer, ServerProtocolPtr, HttpStatus,
            HttpRefererPtr, HttpUserAgentPtr);

   fclose (alptr);

   return (SS$_NORMAL);
}

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

