/*****************************************************************************
/*
                                 Support.c

Miscellaneous support functions for HTTPd.


VERSION HISTORY
---------------
09-APR-2003  MGD  allow ParseNetMask() to accept IP address without a mask
10-JAN-2003  MGD  ServerSignature() use 'HttpdName' and 'HttpdVersion'
13-OCT-2001  MGD  move string functions to STRNG.C module
04-AUG-2001  MGD  support module WATCHing
17-MAY-2001  MGD  move ...Fao() functions to FAO.C
30-APR-2001  MGD  use permanent global section for monitor data
05-APR-2001  MGD  bugfix; ParseNetMask() VLSM mask octet ordering
21-FEB-2001  MGD  modify ParseNetMask() to allow VLSM (variable-length subnet
                  masking, e.g. '131.185.250.0/24')
02-FEB-2001  MGD  add !%I to WriteFormatted()
17-JAN-2000  MGD  bugfix; HttpHeaderChallenge()
23-DEC-2000  MGD  GenerateUniqueId() substitute '@' for '_',
                  remove non-DECC strftime() kludge (VAXC no longer supported!)
01-OCT-2000  MGD  move privilege functions here
27-AUG-2000  MGD  add !SL to WriteFormatted()
09-AUG-2000  MGD  bugfix; ParseQueryField() string length check
11-JUN-2000  MGD  add ParseNetMask()
08-APR-2000  MGD  add GenerateUniqueId() based on Apache implementation
04-MAR-2000  MGD  add WriteFormatted() and it's siblings
                  WriteFao(), WriteFaol(), WriteFaoNet(),
                  remove CopyTohtml(), UrlEncodeString(),
                  improved SearchTextString()
01-JAN-2000  MGD  support ODS-2 and ODS-5,
                  add formatted OPCOM messages
15-OCT-1999  MGD  use common 'HttpdProcess.Pid'
28-AUG-1999  MGD  add strzcpy(), GetSysInfo.VersionInteger()
25-MAY-1999  MGD  reverse the order of the digest and basic challenges
                  (MS IE5 will not authenticate if digest comes first!!)
31-MAR-1999  MGD  bugfix; HttpHeaderChallenge() 'AuthRealmPtr' NULL check
18-JAN-1999  MGD  FaoPrint() and WriteFaoStdout() for sys$fao()
                  variable-argument string printing,
                  provide proxy accounting logical and zeroing
01-OCT-1998  MGD  HttpHeader() "Content-Type: text/...; charset=...",
                  support SET mapping rules for charset and content-type,
                  UrlEncodeString(),
                  bugfix; NameOfDirectoryFile()
10-AUG-1998  MGD  refined CopyToHtml() slightly,
                  bugfix; TimeSetTimezone() unsigned to signed longs
10-JUL-1998  MGD  a little Y2K insurance
21-JUN-1998  MGD  changed format of request logical
02-APR-1998  MGD  after email about non-conforming date formats back to RFC1123
12-MAR-1998  MGD  added FormatProtection()
08-JAN-1997  MGD  TimeSetGmt() now can use SYS$TIMEZONE_DIFFERENTIAL
05-OCT-1997  MGD  additional list processing functions
28-SEP-1997  MGD  accounting structure has grown beyond 255 bytes
09-AUG-1997  MGD  message database, added SearchTextString() and
                  NameOfDirectoryFile() functions
12-JUL-1997  MGD  EnableSysPrv(), DisableSysPrv()
07-JUL-1997  MGD  attempt to reduce dynamic memory fragmentation within C RTL
                  using HEAP_MIN_CHUNK_SIZE functionality,
                   prevent request logical redefinition if keep-alive timeout 
16-JUN-1997  MGD  generic linked list functions
12-MAR-1997  MGD  HTTP header generation
01-FEB-1997  MGD  HTTPd version 4
01-OCT-1996  MGD  report menu
18-JUN-1996  MGD  bugfix; HTTPD$GMT conversion to VMS delta time TimeSetGmt()
06-APR-1996  MGD  persistent connections ("keep-alive")
01-DEC-1995  MGD  HTTPd version 3.0
27-SEP-1995  MGD  provide GMT functions
20-DEC-1994  MGD  initial development
*/
/*****************************************************************************/

#ifdef WASD_VMS_V6
#undef _VMS_V6_SOURCE
#define _VMS_V6_SOURCE
#undef __VMS_VER
#define __VMS_VER 60000000
#undef __CRTL_VER
#define __CRTL_VER 60000000
#endif

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

/* VMS related header files */
#include <descrip.h>
#include <jpidef.h>
#include <libdef.h>
#include <libdtdef.h>
#include <lnmdef.h>
#include <opcdef.h>
#include <prvdef.h>
#include <psldef.h>
#include <rms.h>
#include <ssdef.h>
#include <stsdef.h>

/* application related header files */
#include "wasd.h"

#define WASD_MODULE "SUPPORT"

/******************/
/* global storage */
/******************/

#define RFC_1123_DATE yup

BOOL  TimeAheadOfGmt;

unsigned long  TimeGmtDeltaBinary [2];

char  TimeGmtString [48],
      TimeGmtVmsString [50];

char  *DayName [] =
   { "", "Monday", "Tuesday", "Wednesday",
     "Thursday", "Friday", "Saturday", "Sunday" };

char  *MonthName [] =
   { "", "January", "February", "March", "April", "May", "June",
         "July", "August", "September", "October", "November", "December" };

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

#ifdef DBUG
extern BOOL Debug;
#else
#define Debug 0 
#endif

extern BOOL  AccountingZeroOnStartup,
             MonitorEnabled;

extern int  CharsetCount,
            EfnWait,
            ServerPort;

extern unsigned long  SysPrvMask[];

extern char  ErrorSanityCheck[],
             HttpdName[],
             HttpdVersion[],
             ServerHostName[],
             SoftwareID[],
             Utility[];

extern ACCOUNTING_STRUCT  *AccountingPtr;
extern CONFIG_STRUCT  Config;
extern HTTPD_GBLSEC  *HttpdGblSecPtr;
extern HTTPD_PROCESS  HttpdProcess;
extern MSG_STRUCT  Msgs;
extern WATCH_STRUCT  Watch;

/*****************************************************************************/
/*
Declare an AST, exit if there is any problem ... must have our ASTs!
*/ 

void SysDclAst
(
void *Address,
unsigned long Parameter
)
{
   int  status;

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

   if (WATCH_MODULE(WATCH_MOD__OTHER))
      WatchThis (NULL, FI_LI, WATCH_MOD__OTHER,
                 "SysDclAst() !&A(!&X)", Address, Parameter);

   if (VMSok (status = sys$dclast (Address, Parameter, 0))) return;
   ErrorExitVmsStatus (status, "sys$dclast()", FI_LI);
}

/*****************************************************************************/
/*
Generic list handling function.  Add entry to head of list.
*/ 

LIST_ENTRY* ListAddHead
(
LIST_HEAD *lptr,
LIST_ENTRY *eptr
)
{
   /*********/
   /* begin */
   /*********/

   if (Debug)
   {
      fprintf (stdout, "ListAddHead() %d %d\n", lptr, eptr);
      ListDebug (lptr);
   }

   if (!lptr->HeadPtr)
   {
      /* empty list */
      lptr->HeadPtr = lptr->TailPtr = eptr;
      eptr->PrevPtr = eptr->NextPtr = NULL;
      lptr->EntryCount++;
      if (Debug) ListDebug (lptr);
      return (eptr);
   }
   else
   {
      /* non-empty list */
      eptr->PrevPtr = NULL;
      eptr->NextPtr = lptr->HeadPtr;
      eptr->NextPtr->PrevPtr = lptr->HeadPtr = eptr;
      lptr->EntryCount++;
      if (Debug) ListDebug (lptr);
      return (eptr);
   }
}

/*****************************************************************************/
/*
Generic list handling function.  Add entry to tail of list.
*/ 

LIST_ENTRY* ListAddTail
(
LIST_HEAD *lptr,
LIST_ENTRY *eptr
)
{
   /*********/
   /* begin */
   /*********/

   if (Debug)
   {
      fprintf (stdout, "ListAddTail() %d %d\n", lptr, eptr);
      ListDebug (lptr);
   }

   if (!lptr->HeadPtr)
   {
      /* empty list */
      lptr->HeadPtr = lptr->TailPtr = eptr;
      eptr->PrevPtr = eptr->NextPtr = NULL;
      lptr->EntryCount++;
      if (Debug) ListDebug (lptr);
      return (eptr);
   }
   else
   {
      /* non-empty list */
      eptr->NextPtr = NULL;
      eptr->PrevPtr = lptr->TailPtr;
      eptr->PrevPtr->NextPtr = lptr->TailPtr = eptr;
      lptr->EntryCount++;
      if (Debug) ListDebug (lptr);
      return (eptr);
   }
}

/*****************************************************************************/
/*
Generic list handling function.  Add entry 2 "in front of" entry 1.
*/ 

LIST_ENTRY* ListAddBefore
(
LIST_HEAD *lptr,
LIST_ENTRY *e1ptr,
LIST_ENTRY *e2ptr
)
{
   /*********/
   /* begin */
   /*********/

   if (Debug)
   {
      fprintf (stdout, "ListAddBefore() %d %d %d\n", lptr, e1ptr, e2ptr);
      ListDebug (lptr);
   }

   if (lptr->HeadPtr == e1ptr)
   {
      /* at head of list */
      e1ptr->PrevPtr = e2ptr;
      e2ptr->PrevPtr = NULL;
      e2ptr->NextPtr = e1ptr;
      lptr->HeadPtr = e2ptr;
      return (e2ptr);
   }
   else
   {
      /* not at head of list */
      if (e1ptr->PrevPtr)
      {
         e2ptr->PrevPtr = e1ptr->PrevPtr;
         e2ptr->PrevPtr->NextPtr = e2ptr;
      }
      e1ptr->PrevPtr = e2ptr;
      e2ptr->NextPtr = e1ptr;
      return (e2ptr);
   }

   if (Debug) ListDebug (lptr);
}

/*****************************************************************************/
/*
Generic list handling function.  Remove entry from list.  Check first that
entry looks legitimate, return NULL if not!
*/ 

LIST_ENTRY* ListRemove
(
LIST_HEAD *lptr,
LIST_ENTRY *eptr
)
{
   /*********/
   /* begin */
   /*********/

   if (Debug)
   {
      fprintf (stdout, "ListRemove() %d %d\n", lptr, eptr);
      ListDebug (lptr);
   }

   if (!eptr->PrevPtr)
   {                                                                        
      /* at head of list */
      if (!eptr->NextPtr)
      {
         /* only entry in list */
         if (lptr->HeadPtr != eptr || lptr->TailPtr != eptr) return (NULL);
         lptr->HeadPtr = lptr->TailPtr = NULL;
         if (lptr->EntryCount) lptr->EntryCount--;
         if (Debug) ListDebug (lptr);
         return (eptr);
      }
      else
      {
         /* remove from head of list */
         if (lptr->HeadPtr != eptr) return (NULL);
         eptr->NextPtr->PrevPtr = NULL;
         lptr->HeadPtr = eptr->NextPtr;
         if (lptr->EntryCount) lptr->EntryCount--;
         if (Debug) ListDebug (lptr);
         return (eptr);
      }
   }
   else
   {
      /* not at head of list */
      if (!eptr->NextPtr)
      {
         /* at tail of list */
         if (lptr->TailPtr != eptr) return (NULL);
         eptr->PrevPtr->NextPtr = NULL;
         lptr->TailPtr = eptr->PrevPtr;
         if (lptr->EntryCount) lptr->EntryCount--;
         if (Debug) ListDebug (lptr);
         return (eptr);
      }
      else
      {
         /* somewhere in the middle! */
         if (eptr->PrevPtr->NextPtr != eptr ||
             eptr->NextPtr->PrevPtr != eptr) return (NULL);
         eptr->PrevPtr->NextPtr = eptr->NextPtr;
         eptr->NextPtr->PrevPtr = eptr->PrevPtr;
         if (lptr->EntryCount) lptr->EntryCount--;
         if (Debug) ListDebug (lptr);
         return (eptr);
      }
   }
}

/*****************************************************************************/
/*
Generic list handling function.  If not already there move it to the head.
*/ 

#ifdef __DECC
#pragma inline(ListMoveHead)
#endif

LIST_ENTRY* ListMoveHead
(
LIST_HEAD *lptr,
LIST_ENTRY *eptr
)
{
   /*********/
   /* begin */
   /*********/

   if (Debug)
      fprintf (stdout, "ListMoveHead() %d %d %d\n",
               lptr, lptr->HeadPtr, eptr);

   /* move entry to the head of the cache list */
   if (lptr->HeadPtr != eptr)
   {
      ListRemove (lptr, eptr);
      ListAddHead (lptr, eptr);
   }

   return (eptr);
}

/*****************************************************************************/
/*
Generic list handling function.  If not already there move it to the tail.
*/ 

#ifdef __DECC
#pragma inline(ListMoveTail)
#endif

LIST_ENTRY* ListMoveTail
(
LIST_HEAD *lptr,
LIST_ENTRY *eptr
)
{
   /*********/
   /* begin */
   /*********/

   if (Debug)
      fprintf (stdout, "ListMoveTail() %d %d %d\n",
               lptr, lptr->HeadPtr, eptr);

   /* move entry to the head of the cache list */
   if (lptr->TailPtr != eptr)
   {
      ListRemove (lptr, eptr);
      ListAddTail (lptr, eptr);
   }

   return (eptr);
}

/*****************************************************************************/
/*
Generic list handling function.  Check if a specific entry is in specific list.
*/ 

LIST_ENTRY* ListCheck
(
LIST_HEAD *lptr,
LIST_ENTRY *eptr
)
{
   LIST_ENTRY  *teptr;

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

   fprintf (stdout, "ListCheck() %d %d %d %d\n",
            lptr, lptr->HeadPtr, lptr->TailPtr, lptr->EntryCount);

   for (teptr = lptr->HeadPtr; teptr; teptr = teptr->NextPtr)
      if (teptr == eptr) return (eptr);
   return (NULL);
}

/*****************************************************************************/
/*
list the entries in the list.
*/ 

ListDebug (LIST_HEAD* lptr)

{
   LIST_ENTRY  *eptr;

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

   fprintf (stdout, "ListDebug() %d %d %d %d\n",
            lptr, lptr->HeadPtr, lptr->TailPtr, lptr->EntryCount);

   for (eptr = lptr->HeadPtr; eptr; eptr = eptr->NextPtr)
      fprintf (stdout, "%d <- %d -> %d\n", eptr->PrevPtr, eptr, eptr->NextPtr);
}

/*****************************************************************************/
/*
Generate the appropriate server signature returned the supplied buffer.
*/

char* ServerSignature
(
REQUEST_STRUCT *rqptr,
char *BufferPtr,
int BufferSize
)
{
   int  status,
        ServerPortNumber;
   char  *ServerHostNamePtr;
   char  EmailBuffer [256];

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

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

   if (!Config.cfServer.Signature)
   {
      BufferPtr[0] = '\0';
      return (BufferPtr);
   }

   if (rqptr)
   {
      ServerHostNamePtr = rqptr->ServicePtr->ServerHostName;
      ServerPortNumber = rqptr->ServicePtr->ServerPort;
   }
   else
   {
      ServerHostNamePtr = ServerHostName;
      ServerPortNumber = ServerPort;
   }

   if (Config.cfServer.Signature == CONFIG_SERVER_SIGNATURE_EMAIL &&
       Config.cfServer.AdminEmail[0])
   {
      status = WriteFao (EmailBuffer, sizeof(EmailBuffer), NULL,
                         "<A HREF=\"mailto:!AZ\">!AZ</A>",
                         Config.cfServer.AdminEmail, ServerHostNamePtr);
      if (VMSnok (status)) ErrorNoticed (status, "WriteFao()", FI_LI);
   }
   else
      EmailBuffer[0] = '\0';

   status = WriteFao (BufferPtr, BufferSize, NULL,
                      MsgFor(rqptr, MSG_STATUS_SIGNATURE),
                      HttpdName, HttpdVersion,
                      EmailBuffer[0] ? EmailBuffer : ServerHostNamePtr,
                      ServerPortNumber);
   if (VMSnok (status)) ErrorNoticed (status, "WriteFao()", FI_LI);

   if (Debug) fprintf (stdout, "BufferPtr |%s|\n", BufferPtr);
   return (BufferPtr);
}
   
/*****************************************************************************/
/*
Create an HTTP format local time string in the storage pointed at by
'TimeString', e.g. "Fri, 25 Aug 1995 17:32:40" (RFC1123). If 'BinTimePtr' is
null the time string represents the current time. If it points to a quadword,
VMS time value the string represents that time. 'TimeString' must point to
storage large enough for 31 characters.
*/

int HttpLocalTimeString
(
char *TimeString,
unsigned long *BinTimePtr
)
{
   /* alternate formats for time string */
#ifdef RFC_1123_DATE
   /* day, dd mmm yyyy hh:mm */
   static $DESCRIPTOR (HttpTimeFaoDsc, "!3AZ, !2ZW !3AZ !4ZW !2ZW:!2ZW:!2ZW");
#else
   /* weekday, dd-mmm-yyyy hh:mm */
   static $DESCRIPTOR (HttpTimeFaoDsc, "!3AZ, !2ZW-!3AZ-!4ZW !2ZW:!2ZW:!2ZW");
#endif

   static $DESCRIPTOR (TimeStringDsc, "");

   int  status;
   unsigned long  BinTime [2];
   unsigned short  Length;
   unsigned short  NumTime [7];
   unsigned long  DayOfWeek;

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

   if (WATCH_MODULE(WATCH_MOD__OTHER))
      WatchThis (NULL, FI_LI, WATCH_MOD__OTHER, "HttpLocalTimeString()");

   if (BinTimePtr)
   {
      BinTime[0] = BinTimePtr[0];
      BinTime[1] = BinTimePtr[1];
   }
   else
      sys$gettim (&BinTime);

   status = sys$numtim (&NumTime, &BinTime);
   if (Debug)
      fprintf (stdout, "sys$numtim() %%X%08.08X %d %d %d %d %d %d %d\n",
               status, NumTime[0], NumTime[1], NumTime[2],
               NumTime[3], NumTime[4], NumTime[5], NumTime[6]);

   if (VMSnok (status = lib$day_of_week (&BinTime, &DayOfWeek)))
      return (status);
   if (Debug)
      fprintf (stdout, "lib$day_of_week() %%X%08.08X is %d\n",
               status, DayOfWeek);

   /* set the descriptor address and size of the resultant time string */
   TimeStringDsc.dsc$w_length = 25;
   TimeStringDsc.dsc$a_pointer = TimeString;

   status = sys$fao (&HttpTimeFaoDsc, &Length, &TimeStringDsc,
                     DayName[DayOfWeek], NumTime[2], MonthName[NumTime[1]],
                     NumTime[0], NumTime[3], NumTime[4], NumTime[5]);
   if (VMSnok (status) || status == SS$_BUFFEROVF)
   {
      if (Debug) fprintf (stdout, "sys$fao() %%X%08.08X\n", status);
      TimeString[0] = '\0';
   }
   else
      TimeString[Length] = '\0';
   if (Debug) fprintf (stdout, "|%s|\n", TimeString);

   return (status);
}

/*****************************************************************************/
/*
Create an HTTP format Greenwich Mean Time (UTC) time string in the storage 
pointed at by 'TimeString', e.g. "Fri, 25 Aug 1995 17:32:40 GMT" (RFC1123). 
This must be at least 30 characters capacity.  If 'BinTimePtr' is null the 
time string represents the current time.  If it points to a quadword, VMS time 
value the string represents that time.  'TimeString' must point to storage 
large enough for 31 characters.
*/

int HttpGmTimeString
(
char *TimeString,
unsigned long *BinTimePtr
)
{
   /* alternate formats for time string */
#ifdef RFC_1123_DATE
   /* day, dd mmm yyyy hh:mm */
   static $DESCRIPTOR (HttpTimeFaoDsc,
          "!3AZ, !2ZW !3AZ !4ZW !2ZW:!2ZW:!2ZW GMT");
#else
   /* weekday, dd-mmm-yyyy hh:mm */
   static $DESCRIPTOR (HttpTimeFaoDsc,
          "!3AZ, !2ZW-!3AZ-!4ZW !2ZW:!2ZW:!2ZW GMT");
#endif

   static $DESCRIPTOR (TimeStringDsc, "");

   int  status;
   unsigned long  BinTime [2],
                  GmTime [2];
   unsigned short  Length;
   unsigned short  NumTime [7];
   unsigned long  DayOfWeek;

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

   if (WATCH_MODULE(WATCH_MOD__OTHER))
      WatchThis (NULL, FI_LI, WATCH_MOD__OTHER, "HttpGmTimeString()");

   if (BinTimePtr)
   {
      GmTime[0] = BinTimePtr[0];
      GmTime[1] = BinTimePtr[1];
   }
   else
      sys$gettim (&GmTime);

   if (VMSnok (status = TimeAdjustGMT (true, &GmTime)))
      return (status);

   status = sys$numtim (&NumTime, &GmTime);
   if (Debug)
      fprintf (stdout, "sys$numtim() %%X%08.08X %d %d %d %d %d %d %d\n",
               status, NumTime[0], NumTime[1], NumTime[2],
               NumTime[3], NumTime[4], NumTime[5], NumTime[6]);

   if (VMSnok (status = lib$day_of_week (&GmTime, &DayOfWeek)))
      return (status);
   if (Debug)
      fprintf (stdout, "lib$day_of_week() %%X%08.08X is %d\n",
               status, DayOfWeek);

   /* set the descriptor address and size of the resultant time string */
   TimeStringDsc.dsc$w_length = 29;
   TimeStringDsc.dsc$a_pointer = TimeString;

   status = sys$fao (&HttpTimeFaoDsc, &Length, &TimeStringDsc,
                     DayName[DayOfWeek], NumTime[2], MonthName[NumTime[1]],
                     NumTime[0], NumTime[3], NumTime[4], NumTime[5]);
   if (VMSnok (status) || status == SS$_BUFFEROVF)
   {
      if (Debug) fprintf (stdout, "sys$fao() %%X%08.08X\n", status);
      TimeString[0] = '\0';
   }
   else
      TimeString[Length] = '\0';
   if (Debug) fprintf (stdout, "|%s|\n", TimeString);

   return (status);
}

/*****************************************************************************/
/*
Given a string such as "Fri, 25 Aug 1995 17:32:40 GMT" (RFC1123), or "Friday,
25-Aug-1995 17:32:40 GMT" (RFC 1036), create an internal, local, binary time
with the current GMT offset.  See complementary function HttpGmTimeString().
*/ 

int HttpGmTime
(
char *TimeString,
unsigned long *BinTimePtr
)
{
   static char  *MonthName [] =
      { "", "Jan", "Feb", "Mar", "Apr", "May", "Jun",
            "Jul", "Aug", "Sep", "Oct", "Nov", "Dec" };

   int  status;
   unsigned short  Length;
   unsigned short  NumTime [7] = { 0,0,0,0,0,0,0 };
   unsigned long  DayOfWeek;
   char  *tptr;

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

   if (Debug) fprintf (stdout, "HttpGmTime() |%s|\n", TimeString);

   tptr = TimeString;
   /* hunt straight for the comma after the weekday name! */
   while (*tptr && *tptr != ',') tptr++;
   if (*tptr) tptr++;
   /* span white space between weekday name and date */
   while (*tptr && ISLWS(*tptr)) tptr++;
   /** if (Debug) fprintf (stdout, "tptr |%s|\n", tptr); **/
   if (!*tptr) return (STS$K_ERROR);

   /* get the date and then skip to month name */
   if (isdigit(*tptr)) NumTime[2] = atoi (tptr);
   while (*tptr && isdigit(*tptr)) tptr++;
   while (*tptr && (*tptr == '-' || ISLWS(*tptr))) tptr++;
   /** if (Debug) fprintf (stdout, "tptr |%s|\n", tptr); **/
   if (!*tptr) return (STS$K_ERROR);

   /* get the month number from the name and skip to the year */
   for (NumTime[1] = 1; NumTime[1] <= 12; NumTime[1]++)
      if (strsame (tptr, MonthName[NumTime[1]], 3)) break;
   if (NumTime[1] > 12) return (STS$K_ERROR);
   while (isalpha(*tptr)) tptr++;
   while (*tptr && (*tptr == '-' || ISLWS(*tptr))) tptr++;
   /** if (Debug) fprintf (stdout, "tptr |%s|\n", tptr); **/
   if (!*tptr) return (STS$K_ERROR);

   /* get the year and then skip to the hour */
   if (isdigit(*tptr))
   {
      NumTime[0] = atoi (tptr);
      if (NumTime[0] <= 99)
      {
         /* for older browsers supplying 2 digit years, some Y2K insurance! */
         if (NumTime[0] >= 70)
            NumTime[0] += 1900;
         else
            NumTime[0] += 2000;
      }
   }
   while (*tptr && isdigit(*tptr)) tptr++;
   while (*tptr && ISLWS(*tptr)) tptr++;
   /** if (Debug) fprintf (stdout, "tptr |%s|\n", tptr); **/
   if (!*tptr) return (STS$K_ERROR);

   /* get the hour, minute and second */
   if (isdigit(*tptr)) NumTime[3] = atoi (tptr);
   while (*tptr && isdigit(*tptr)) tptr++;
   if (*tptr == ':') tptr++;
   if (isdigit(*tptr)) NumTime[4] = atoi (tptr);
   while (*tptr && isdigit(*tptr)) tptr++;
   if (*tptr == ':') tptr++;
   if (isdigit(*tptr)) NumTime[5] = atoi (tptr);
   while (*tptr && isdigit(*tptr)) tptr++;
   if (*tptr == ':') tptr++;
   if (!*tptr) return (STS$K_ERROR);

   /* the only thing remaining should be the "GMT" */
   while (*tptr && ISLWS(*tptr)) tptr++;
   /** if (Debug) fprintf (stdout, "tptr |%s|\n", tptr); **/
   if (!strsame (tptr, "GMT", 3)) return (STS$K_ERROR);

   /*******************************************/
   /* convert what looks like legitimate GMT! */
   /*******************************************/

   if (Debug)
      fprintf (stdout, "NumTime[] %d %d %d %d %d %d %d\n",
               NumTime[0], NumTime[1], NumTime[2], NumTime[3], 
               NumTime[4], NumTime[5], NumTime[6]); 
   status = lib$cvt_vectim (&NumTime, BinTimePtr);
   if (VMSnok (status)) return (status);
   if (Debug) fprintf (stdout, "lib$cvt_vectim() %%X%08.08X\n", status);

   return (TimeAdjustGMT (false, BinTimePtr));
}

/*****************************************************************************/
/*
Determine the offset from GMT (UTC) using either the HTTPD$GMT or
SYS$TIMEZONE_DIFFERENTIAL logicals. If HTTPD$GMT is not defined time should be
set from the timezone differential logical. Function RequestBegin() calls
this every hour to recheck GMT offset and detect daylight saving or other
timezone changes.
*/

int TimeSetGMT ()

{
   static BOOL  UseTimezoneDifferential = false;

   int  status;

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

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

   if (!UseTimezoneDifferential)
   {
      status = TimeSetHttpdGmt();
      /* return if error and that error was not that the name did not exist */
      if (VMSok (status) || status != SS$_NOLOGNAM) return (status);
   }

   UseTimezoneDifferential = true;
   return (TimeSetTimezone());
}

/*****************************************************************************/
/*
The SYS$TIMEZONE_DIFFERENTIAL logical contains the number of seconds offset
from GMT (UTC) as a positive (ahead) or negative (behind) number.  Set the
'TimeGmtString' global storage to a "+hh:mm" or "-hh:mm" string equivalent, and
the 'TimeAheadOfGmt' global boolean and 'TimeGmtVmsString' delta-time global
string.
*/

int TimeSetTimezone ()

{
   static unsigned short  Length;
   static $DESCRIPTOR (TimezoneLogicalNameDsc, "SYS$TIMEZONE_DIFFERENTIAL");
   static $DESCRIPTOR (LnmSystemDsc, "LNM$SYSTEM");
   static $DESCRIPTOR (TimeGmtStringFaoDsc, "!AZ!2ZL:!2ZL");
   static $DESCRIPTOR (TimeGmtVmsStringFaoDsc, "0 !2ZL:!2ZL");
   static $DESCRIPTOR (TimeGmtStringDsc, TimeGmtString);
   static struct {
      short int  buf_len;
      short int  item;
      unsigned char   *buf_addr;
      unsigned short  *short_ret_len;
   } LnmItems [] =
   {
      { sizeof(TimeGmtString)-1, LNM$_STRING, TimeGmtString, &Length },
      { 0,0,0,0 }
   };

   int  status;
   long  Hours,
         Minutes,
         Seconds;
   char  *SignPtr;
   $DESCRIPTOR (TimeGmtVmsStringDsc, TimeGmtVmsString);

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

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

   status = sys$trnlnm (0, &LnmSystemDsc, &TimezoneLogicalNameDsc, 0, &LnmItems);
   if (Debug) fprintf (stdout, "sys$trnlnm() %%X%08.08X\n", status);
   if (VMSnok (status)) return (status);

   TimeGmtString[Length] = '\0';
   if (Debug) fprintf (stdout, "TimeGmtString |%s|\n", TimeGmtString);
   Seconds = atol(TimeGmtString);
   if (Seconds < 0)
   {
      TimeAheadOfGmt = false;
      Seconds = -Seconds;
      SignPtr = "-";
   }
   else
   {
      TimeAheadOfGmt = true;
      SignPtr = "+";
   }
   Hours = Seconds / 3600;
   Minutes = (Seconds - Hours * 3600) / 60;
   if (Debug)
      fprintf (stdout, "%d %s%d:%d\n", Seconds, SignPtr, Hours, Minutes);
   sys$fao (&TimeGmtStringFaoDsc, &Length, &TimeGmtStringDsc,
            SignPtr, Hours, Minutes);
   TimeGmtString[Length] = '\0';
   if (Debug) fprintf (stdout, "TimeGmtString |%s|\n", TimeGmtString);

   sys$fao (&TimeGmtVmsStringFaoDsc, &Length, &TimeGmtVmsStringDsc,
            Hours, Minutes);
   TimeGmtVmsString[Length] = '\0';
   if (Debug) fprintf (stdout, "TimeGmtVmsString |%s|\n", TimeGmtVmsString);

   TimeGmtVmsStringDsc.dsc$w_length = Length;

   if (VMSnok (status =
       sys$bintim (&TimeGmtVmsStringDsc, &TimeGmtDeltaBinary)))
      return (status);
   if (TimeGmtDeltaBinary[0] || TimeGmtDeltaBinary[1])
      return (status);
   /* time must have been zero, make it one, one-hundreth of a second */
   TimeGmtDeltaBinary[0] = -100000;
   TimeGmtDeltaBinary[1] = -1;
   return (SS$_NORMAL);
}

/*****************************************************************************/
/*
Translate the logical HTTPD$GMT (defined to be something like "+10:30" or "-
01:15") and convert it into a delta time structure and store in 
'TimeGmtDeltaBinary'.  Store whether it is in advance or behind GMT in boolean 
'TimeAheadOfGmt'.  Store the logical string in 'TimeGmtString'.
*/

int TimeSetHttpdGmt ()

{
   static unsigned short  Length;
   static $DESCRIPTOR (GmtLogicalNameDsc, "HTTPD$GMT");
   static $DESCRIPTOR (LnmFileDevDsc, "LNM$FILE_DEV");
   static struct {
      short int  buf_len;
      short int  item;
      unsigned char   *buf_addr;
      unsigned short  *short_ret_len;
   } LnmItems [] =
   {
      { sizeof(TimeGmtString)-1, LNM$_STRING, TimeGmtString, &Length },
      { 0,0,0,0 }
   };

   int  status;
   char  *cptr, *sptr;
   $DESCRIPTOR (TimeGmtVmsStringDsc, TimeGmtVmsString);

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

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

   status = sys$trnlnm (0, &LnmFileDevDsc, &GmtLogicalNameDsc, 0, &LnmItems);
   if (Debug) fprintf (stdout, "sys$trnlnm() %%X%08.08X\n", status);
   if (VMSnok (status)) return (status);

   TimeGmtString[Length] = '\0';
   if (Debug) fprintf (stdout, "TimeGmtString |%s|\n", TimeGmtString);

   if (TimeGmtString[0] == '$') return (SS$_NORMAL);

   if (*(cptr = TimeGmtString) == '-')
      TimeAheadOfGmt = false;
   else
      TimeAheadOfGmt = true;
   if (*cptr == '+' || *cptr == '-') cptr++;
   sptr = TimeGmtVmsString;
   *sptr++ = '0';
   *sptr++ = ' ';
   while (*cptr) *sptr++ = *cptr++;
   *sptr = '\0';
   if (Debug) fprintf (stdout, "TimeGmtVmsString |%s|\n", TimeGmtVmsString);

   TimeGmtVmsStringDsc.dsc$w_length = sptr - TimeGmtVmsString;

   if (VMSnok (status =
       sys$bintim (&TimeGmtVmsStringDsc, &TimeGmtDeltaBinary)))
      return (status);
   if (TimeGmtDeltaBinary[0] || TimeGmtDeltaBinary[1])
      return (status);
   /* time must have been zero, make it one, one-hundreth of a second */
   TimeGmtDeltaBinary[0] = -100000;
   TimeGmtDeltaBinary[1] = -1;
   return (SS$_NORMAL);
}

/*****************************************************************************/
/*
The GMT is generated by calculating using an offset using 
'TimeGmtDeltaOffset' and boolean 'TimeAheadOfGmt'.  Adjust either to or from 
GMT.
*/ 

int TimeAdjustGMT
(
BOOL ToGmTime,
unsigned long *BinTimePtr
)
{
   int  status;
   unsigned long  AdjustedTime [2];

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

   if (Debug) fprintf (stdout, "TimeAdjustGMT() ToGmTime: %d\n", ToGmTime);

   if ((ToGmTime && TimeAheadOfGmt) || (!ToGmTime && !TimeAheadOfGmt))
   {
      /* to GMT from local and ahead of GMT, or to local from GMT and behind */
      status = lib$sub_times (BinTimePtr, &TimeGmtDeltaBinary, &AdjustedTime);
      if (Debug) fprintf (stdout, "lib$sub_times() %%X%08.08X\n", status);
   }
   else
   {
      /* to GMT from local and behind GMT, or to local from GMT and ahead */
      status = lib$add_times (BinTimePtr, &TimeGmtDeltaBinary, &AdjustedTime);
      if (Debug) fprintf (stdout, "lib$add_times() %%X%08.08X\n", status);
   }

   if (Debug)
   {
      unsigned short  Length;
      char  String [64];
      $DESCRIPTOR (AdjustedTimeFaoDsc, "AdjustedTime: |!%D|\n");
      $DESCRIPTOR (StringDsc, String);

      sys$fao (&AdjustedTimeFaoDsc, &Length, &StringDsc, &AdjustedTime);
      String[Length] = '\0';
      fputs (String, stdout);
   }

   BinTimePtr[0] = AdjustedTime[0];
   BinTimePtr[1] = AdjustedTime[1];

   return (status);
}

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

char* DigitDayTime (unsigned long *BinTimePtr)

{
   static char  TimeString [16];
   static $DESCRIPTOR (TimeFaoDsc, "!2ZL !2ZL:!2ZL:!2ZL\0");
   static $DESCRIPTOR (TimeStringDsc, TimeString);

   int  status;
   unsigned long  BinTime[2];
   unsigned short  NumTime [7];

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

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

   if (!BinTimePtr) sys$gettim (BinTimePtr = &BinTime);
   if (VMSnok (status = sys$numtim (&NumTime, BinTimePtr)))
      return ("*ERROR*");
   if (VMSnok (sys$fao (&TimeFaoDsc, 0, &TimeStringDsc,
               NumTime[2], NumTime[3], NumTime[4], NumTime[5])))
      return ("*ERROR*");
   return (TimeString);
}

/*****************************************************************************/
/*
Return a pointer to the name of the day of the week for the supplied binary
time.  If a NULL pointer to the binary time then return current day of week.
*/

char* DayOfWeekName (unsigned long *BinTimePtr)

{
   int  status;
   unsigned long  DayOfWeek;
   unsigned long  BinTime[2];

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

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

   if (!BinTimePtr) sys$gettim (BinTimePtr = &BinTime);
   if (VMSnok (status = lib$day_of_week (BinTimePtr, &DayOfWeek)))
      DayOfWeek = 0;
   if (Debug)
      fprintf (stdout, "lib$day_of_week() %%X%08.08X is %d\n",
               status, DayOfWeek);
   return (DayName[DayOfWeek]);
}

/*****************************************************************************/
/*
If the content length has changed, or if it has been modified since the
specified date and time then return a normal status indicating that the object
has been modified.  If not modified then creat a 304 HTTP header and return a
LIB$_NEGTIM status to indicate the file should not be sent.  With VMS'
fractions of a second, add one second to the specified 'since' time to ensure a
reliable comparison.

Implements defacto HTTP/1.0 persistent connections.  Currently we only provide
"keep-alive" response if we're sending a file in binary mode and know its
precise length. An accurate "Content-Length:" field is vital for the client's
correct reception of the transmitted data.  The only other time a "keep-alive"
response can be provided is HERE, where a 304 header is returned (it has a
content length of precisely zero!)
*/ 
 
int HttpIfModifiedSince
(
REQUEST_STRUCT *rqptr,
unsigned long *RdtBinTimePtr,
int LastContentLength
)
{
   static unsigned long  OneSecondDelta [2] = { -10000000, -1 };

   int  status;
   unsigned long  AdjustedBinTime[2],
                  ScratchBinTime [2];
   char  *KeepAlivePtr;

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

   if (Debug)
      fprintf (stdout, "HttpIfModifiedSince() %d %d\n",
               LastContentLength, rqptr->rqHeader.IfModifiedSinceLength);

   if (rqptr->rqHeader.IfModifiedSinceLength >= 0 &&
       LastContentLength >= 0 &&
       rqptr->rqHeader.IfModifiedSinceLength != LastContentLength)
   {
      if (Debug) fprintf (stdout, "different lengths\n");
      return (SS$_NORMAL);
   }

   if (Debug) WriteFaoStdout ("RDT: !%D\n", RdtBinTimePtr);

   if (!rqptr->rqTime.IfModifiedSinceVMS64bit[0] &&
       !rqptr->rqTime.IfModifiedSinceVMS64bit[1])
   {
      if (Debug) fprintf (stdout, "no if-modified-since\n");
      return (SS$_NORMAL);
   }

   /* add one second to the modified time, ensures a negative time */
   if (VMSnok (status =
       lib$add_times (&rqptr->rqTime.IfModifiedSinceVMS64bit,
                      &OneSecondDelta, &AdjustedBinTime)))
   {
      rqptr->rqResponse.ErrorTextPtr = "sys$add_times()";
      ErrorVmsStatus (rqptr, status, FI_LI);
      return (status);
   }
   if (Debug) fprintf (stdout, "sys$add_times() %%X%08.08X\n", status);

   if (Debug) WriteFaoStdout ("adjusted: !%D\n", &AdjustedBinTime);

   /* if a positive time results the file has been modified */
   if (VMSok (status =
       lib$sub_times (RdtBinTimePtr, &AdjustedBinTime, &ScratchBinTime)))
      return (status);

   if (Debug) fprintf (stdout, "sys$sub_times() %%X%08.08X\n", status);
   if (status != LIB$_NEGTIM)
   {
      rqptr->rqResponse.ErrorTextPtr = "sys$sub_times()";
      ErrorVmsStatus (rqptr, status, FI_LI);
      return (status);
   }

   /*****************/
   /* not modified! */
   /*****************/

   rqptr->rqResponse.HttpStatus = 304;

   /* return a "keep-alive" response field if configured and was requested */
   if (Config.cfTimeout.KeepAlive && rqptr->KeepAliveRequest)
   {
      rqptr->KeepAliveResponse = true;
      rqptr->rqNet.KeepAliveCount++;
      KeepAlivePtr = DEFAULT_KEEPALIVE_HTTP_HEADER;
   }
   else
      KeepAlivePtr = "";

   /* note the zero content length! */
   ResponseHeader (rqptr, rqptr->rqResponse.HttpStatus,
                   NULL, 0, NULL, KeepAlivePtr);

   return (LIB$_NEGTIM);
}

/*****************************************************************************/
/*
Return the a pointer to abbreviated meaning of the supplied HTTP status code.
These are typical of those included on the response header status line.
*/

char *HttpStatusCodeText (int StatusCode)

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

   if (Debug) fprintf (stdout, "HttpStatusCodeText() %d\n", StatusCode);

   switch (StatusCode)
   {
      case 100 : return ("Continue"); /* (HTTP/1.1) */
      case 101 : return ("Switching Protocols"); /* (HTTP/1.1) */
      case 200 : return ("OK");
      case 201 : return ("Created");
      case 202 : return ("Accepted");
      case 203 : return ("No Content");
      case 204 : return ("Non-authoritative"); /* (HTTP/1.1) */
      case 205 : return ("Reset Content"); /* (HTTP/1.1) */
      case 206 : return ("Partial Content"); /* (HTTP/1.1) */
      case 300 : return ("Multiple Choices"); /* (HTTP/1.1) */
      case 301 : return ("Moved Permanently");
      case 302 : return ("Moved Temporarily");
      case 303 : return ("See Other"); /* (HTTP/1.1) */
      case 304 : return ("Not Modified");
      case 305 : return ("Use Proxy"); /* (HTTP/1.1) */
      case 400 : return ("Bad Request");
      case 401 : return ("Authorization Required");
      case 402 : return ("Payment Required"); /* (HTTP/1.1) */
      case 403 : return ("Forbidden");
      case 404 : return ("Not Found");
      case 405 : return ("Not Allowed"); /* (HTTP/1.1) */
      case 406 : return ("Not Acceptable"); /* (HTTP/1.1) */
      case 407 : return ("Proxy Authentication Required"); /* (HTTP/1.1) */
      case 408 : return ("Request Timeout"); /* (HTTP/1.1) */
      case 409 : return ("Conflict"); /* (HTTP/1.1) */
      case 410 : return ("Gone"); /* (HTTP/1.1) */
      case 411 : return ("Length Required"); /* (HTTP/1.1) */
      case 412 : return ("Precondition Failed"); /* (HTTP/1.1) */
      case 413 : return ("Request Entity Too Large"); /* (HTTP/1.1) */
      case 414 : return ("Request URI Too Long"); /* (HTTP/1.1) */
      case 415 : return ("Unsupported Media Type"); /* (HTTP/1.1) */
      case 500 : return ("Internal Error");
      case 501 : return ("Not Implemented");
      case 502 : return ("Bad Gateway");
      case 503 : return ("Service Unavailable");
      case 504 : return ("Gateway Timeout"); /* (HTTP/1.1) */
      case 505 : return ("HTTP Version Not Supported"); /* (HTTP/1.1) */
      default :  return ("Unknown Code!");
   }
}

/*****************************************************************************/
/*
Return the a pointer an explanation of the supplied HTTP status code.  These
are supplied from the message database can be are used in error messages, etc.
*/

char *HttpStatusCodeExplanation
(
REQUEST_STRUCT *rqptr,
int StatusCode
)
{
   /*********/
   /* begin */
   /*********/

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

   switch (StatusCode)
   {
      /* continue (HTTP/1.1) */
      case 100 : return (MsgFor(rqptr,MSG_HTTP_100));
      /* switching protocols (HTTP/1.1) */
      case 101 : return (MsgFor(rqptr,MSG_HTTP_101));
      /* success */
      case 200 : return (MsgFor(rqptr,MSG_HTTP_200));
      /* created */
      case 201 : return (MsgFor(rqptr,MSG_HTTP_201));
      /* accepted */
      case 202 : return (MsgFor(rqptr,MSG_HTTP_202));
      /* non-authoritative (HTTP/1.1) */
      case 203 : return (MsgFor(rqptr,MSG_HTTP_203));
      /* no content */
      case 204 : return (MsgFor(rqptr,MSG_HTTP_204));
      /* reset content (HTTP/1.1) */
      case 205 : return (MsgFor(rqptr,MSG_HTTP_205));
      /* partial content (HTTP/1.1) */
      case 206 : return (MsgFor(rqptr,MSG_HTTP_206));
      /* multiple choices (HTTP/1.1) */
      case 300 : return (MsgFor(rqptr,MSG_HTTP_300));
      /* moved permanently */
      case 301 : return (MsgFor(rqptr,MSG_HTTP_301));
      /* moved temporarily */
      case 302 : return (MsgFor(rqptr,MSG_HTTP_302));
      /* see other (HTTP/1.1)  */
      case 303 : return (MsgFor(rqptr,MSG_HTTP_303));
      /* not modified */
      case 304 : return (MsgFor(rqptr,MSG_HTTP_304));
      /* use proxy (HTTP/1.1)  */
      case 305 : return (MsgFor(rqptr,MSG_HTTP_305));
      /* bad request */
      case 400 : return (MsgFor(rqptr,MSG_HTTP_400));
      /* authorization required */
      case 401 : return (MsgFor(rqptr,MSG_HTTP_401));
      /* payment required */
      case 402 : return (MsgFor(rqptr,MSG_HTTP_402));
      /* forbidden */
      case 403 : return (MsgFor(rqptr,MSG_HTTP_403));
      /* not found */
      case 404 : return (MsgFor(rqptr,MSG_HTTP_404));
      /* method not allowed (HTTP/1.1) */
      case 405 : return (MsgFor(rqptr,MSG_HTTP_405));
      /* not acceptable (HTTP/1.1) */
      case 406 : return (MsgFor(rqptr,MSG_HTTP_406));
      /* proxy authentication required (HTTP/1.1) */
      case 407 : return (MsgFor(rqptr,MSG_HTTP_407));
      /* request timeout (HTTP/1.1) */
      case 408 : return (MsgFor(rqptr,MSG_HTTP_408));
      /* resource conflict (HTTP/1.1) */
      case 409 : return (MsgFor(rqptr,MSG_HTTP_409));
      /* gone (HTTP/1.1) */
      case 410 : return (MsgFor(rqptr,MSG_HTTP_410));
      /* length required (HTTP/1.1) */
      case 411 : return (MsgFor(rqptr,MSG_HTTP_411));
      /* precondition failed (HTTP/1.1) */
      case 412 : return (MsgFor(rqptr,MSG_HTTP_412));
      /* request entity too large (HTTP/1.1) */
      case 413 : return (MsgFor(rqptr,MSG_HTTP_413));
      /* request-URI too long (HTTP/1.1) */
      case 414 : return (MsgFor(rqptr,MSG_HTTP_414));
      /* unsupported media type (HTTP/1.1) */
      case 415 : return (MsgFor(rqptr,MSG_HTTP_415));
      /* internal error */
      case 500 : return (MsgFor(rqptr,MSG_HTTP_500));
      /* not implemented */
      case 501 : return (MsgFor(rqptr,MSG_HTTP_501));
      /* bad gateway */
      case 502 : return (MsgFor(rqptr,MSG_HTTP_502));
      /* service unavailable */
      case 503 : return (MsgFor(rqptr,MSG_HTTP_503));
      /* gateway timeout (HTTP/1.1) */
      case 504 : return (MsgFor(rqptr,MSG_HTTP_504));
      /* HTTP version not supported (HTTP/1.1) */
      case 505 : return (MsgFor(rqptr,MSG_HTTP_505));
      /* unknown code! */
      default :
         return ("The server is reporting an UNKNOWN status code!");
   }
}

/*****************************************************************************/
/*
A heap string is one located in HTTPd heap allocated memory.  Append the
supplied, null-terminated string to that pointed to by the heap string pointer. 
If this is NULL then allocate new memory, if not reallocate sufficient memory
to append the new string.  Return a pointer the string.
*/

char* HeapStringAppend
(
REQUEST_STRUCT *rqptr,
char *HeapStringPtr,
char *StringPtr,
int StringLength
)
{
   int  HeapStringLength;

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

   if (Debug) fprintf (stdout, "HeapStringAppend()\n|%s|\n", HeapStringPtr);

   if (StringLength <= 0) StringLength = strlen(StringPtr);

   if (!HeapStringPtr)
   {
      HeapStringPtr = VmGetHeap (rqptr, StringLength+1);
      memcpy (HeapStringPtr, StringPtr, StringLength+1);
      if (Debug) fprintf (stdout, "|%s|\n", HeapStringPtr);
      return (HeapStringPtr);
   }
   else
   {
      HeapStringLength = strlen(HeapStringPtr);

      HeapStringPtr = VmReallocHeap (rqptr, HeapStringPtr,
                                     HeapStringLength+StringLength+1, FI_LI);
      /* copy just past the original end-of-string (incl. terminating null) */
      memcpy (HeapStringPtr+HeapStringLength, StringPtr, StringLength+1);
      if (Debug) fprintf (stdout, "|%s|\n", HeapStringPtr);
      return (HeapStringPtr);
   }
}

/*****************************************************************************/
/*
A heap string is one located in HTTPd heap allocated memory.  Prepend the
supplied, null-terminated string to that pointed to by the heap string pointer. 
If this is NULL then allocate new memory, if not reallocate sufficient memory
to append the new string.  Return a pointer the string.
*/

char* HeapStringPrepend
(
REQUEST_STRUCT *rqptr,
char *HeapStringPtr,
char *StringPtr,
int StringLength
)
{
   int  HeapStringLength;

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

   if (Debug) fprintf (stdout, "HeapStringPrepend()\n|%s|\n", HeapStringPtr);

   if (StringLength <= 0) StringLength = strlen(StringPtr);

   if (!HeapStringPtr)
   {
      HeapStringPtr = VmGetHeap (rqptr, StringLength+1);
      memcpy (HeapStringPtr, StringPtr, StringLength+1);
      if (Debug) fprintf (stdout, "|%s|\n", HeapStringPtr);
      return (HeapStringPtr);
   }
   else
   {
      HeapStringLength = strlen(HeapStringPtr);
      HeapStringPtr = VmReallocHeap (rqptr, HeapStringPtr,
                                     HeapStringLength+StringLength+1, FI_LI);
      /* shift the existing string along in memory creating a gap */
      memcpy (HeapStringPtr+HeapStringLength,
              HeapStringPtr,
              HeapStringLength+1);  /* includes terminating null */
      /* copy just past the original end-of-string */
      memcpy (HeapStringPtr, StringPtr, StringLength);
      if (Debug) fprintf (stdout, "|%s|\n", HeapStringPtr);
      return (HeapStringPtr);
   }
}

/*****************************************************************************/
/*
Returns 1 if time 1 is later than time 2, 0 if the same, and -1 if time 1 is
earlier than time 2.
*/

int CompareVmsBinTimes
(
unsigned long *BinTime1Ptr,
unsigned long *BinTime2Ptr
)
{
   int  status;
   unsigned long  BinTime [2];

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

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

   status = lib$sub_times (BinTime1Ptr, BinTime2Ptr, BinTime);
   if (Debug) fprintf (stdout, "lib$sub_times() %%X%08.08X\n", status);
   if (status == LIB$_NEGTIM)
   {
      if (Debug) fprintf (stdout, "1 < 2\n");
      return (-1);
   }
   else
   if (VMSok (status))
   {
      if (BinTime1Ptr[0] == BinTime2Ptr[0] && BinTime1Ptr[1] == BinTime2Ptr[1])
      {
         if (Debug) fprintf (stdout, "1 == 2\n");
         return (0);
      }
      else
      {
         if (Debug) fprintf (stdout, "1 > 2\n");
         return (1); 
      }
   }
   else
      ErrorExitVmsStatus (status, "lib$sub_times()", FI_LI);
}

/*****************************************************************************/
/*
Returns a pointer to a VmGetHeap()ed string of <META ...> tags for an
internally generated HTML document.
*/

char* HtmlMetaInfo
(
REQUEST_STRUCT *rqptr,
char *VmsInfoPtr
)
{
   int  status;
   unsigned long  FaoVector [16];
   unsigned long  *vecptr;
   unsigned short  Length;
   char  *MetaPtr;
   char  Buffer [2048];

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

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

   vecptr = &FaoVector;
   *vecptr++ = SoftwareID;
   *vecptr++ = rqptr->rqTime.GmDateTime;

   if (rqptr->RemoteUser[0])
   {
      *vecptr++ = "<META NAME=\"author\" CONTENT=\"!&;AZ.\'!&;AZ\'@!AZ\">\n";
      *vecptr++ = rqptr->RemoteUser;
      *vecptr++ = rqptr->rqAuth.RealmDescrPtr;
      *vecptr++ = rqptr->ServicePtr->ServerHostPort;
   }
   else
   {
      *vecptr++ = "<META NAME=\"host\" CONTENT=\"!AZ\">\n";
      *vecptr++ = rqptr->ServicePtr->ServerHostPort;
   }

   if (VmsInfoPtr && VmsInfoPtr[0])
   {
      if (Config.cfReport.MetaInfoEnabled)
      {
         *vecptr++ = "<META NAME=\"VMS\" CONTENT=\"!&;AZ\">\n!&@";
         *vecptr++ = VmsInfoPtr;
         if (rqptr->PathOds)
         {
            *vecptr++ = "<META NAME=\"ODS\" CONTENT=\"!AZ\">\n";
            switch (rqptr->PathOds)
            {
               case MAPURL_PATH_ODS_2 : *vecptr++ = "2"; break;
               case MAPURL_PATH_ODS_5 : *vecptr++ = "5"; break;
               case MAPURL_PATH_ODS_ADS : *vecptr++ = "ADS"; break;
               case MAPURL_PATH_ODS_PWK : *vecptr++ = "PWK"; break;
               case MAPURL_PATH_ODS_SMB : *vecptr++ = "SMB"; break;
               case MAPURL_PATH_ODS_SRI : *vecptr++ = "SRI"; break;
               default : *vecptr++ = "?";
            }
         }
         else
            *vecptr++ = "";
      }
      else
         *vecptr++ = "";
   }
   else
      *vecptr++ = "";

   status = WriteFaol (Buffer, sizeof(Buffer), &Length,
"<META NAME=\"generator\" CONTENT=\"!AZ\">\n\
<META NAME=\"date\" CONTENT=\"!AZ\">\n\
!&@\
!&@",
      &FaoVector);

   if (VMSnok (status) || status == SS$_BUFFEROVF)
   {
      ErrorNoticed (status, "WriteFaol()", FI_LI);
      return ("<!-- META overflow ERROR -->\n");
   }

   MetaPtr = VmGetHeap (rqptr, Length+1);
   memcpy (MetaPtr, Buffer, Length+1);

   return (MetaPtr);
}

/*****************************************************************************/
/*
Convert a VMS quadword, binary time to a Unix-style time component structure.
*/

BOOL TimeVmsToUnix
(
unsigned long *BinTimePtr,
struct tm *TmPtr
)
{
   static unsigned long  LibDayOfWeek = LIB$K_DAY_OF_WEEK;
   static unsigned long  LibDayOfYear = LIB$K_DAY_OF_YEAR;

   int  status;
   unsigned long  DayOfWeek,
                  DayOfYear;
   unsigned short  NumTime [7];
   unsigned long  BinTime [2];

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

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

   if (!BinTimePtr) sys$gettim (BinTimePtr = &BinTime);

   if (VMSnok (status = sys$numtim (&NumTime, BinTimePtr)))
      return (status);

   lib$cvt_from_internal_time (&LibDayOfWeek, &DayOfWeek, BinTimePtr);
   lib$cvt_from_internal_time (&LibDayOfYear, &DayOfYear, BinTimePtr);

   TmPtr->tm_sec = NumTime[5];
   TmPtr->tm_min = NumTime[4];
   TmPtr->tm_hour = NumTime[3];
   TmPtr->tm_mday = NumTime[2];
   TmPtr->tm_mon = NumTime[1] - 1;
   TmPtr->tm_year = NumTime[0] - 1900;
   TmPtr->tm_wday = DayOfWeek;
   TmPtr->tm_yday = DayOfYear;
   TmPtr->tm_isdst = 0;

   if (Debug) fprintf (stdout, "%s", asctime(TmPtr));

   return (SS$_NORMAL);
}

/****************************************************************************/
/*
generate a standard VMS protection string from the mask. 'String' must be at
least 28 bytes (better use 32 :^)  Returns the length of the string.
*/ 
 
int FormatProtection
(
unsigned short pmask,
char *String
)
{
   char  *sptr;

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

   if (Debug)
      fprintf (stdout, "FormatProtection() %%X%04.04X\n", pmask);

   sptr = String;

   if (!(pmask & 0x0001)) *sptr++ = 'R';
   if (!(pmask & 0x0002)) *sptr++ = 'W';
   if (!(pmask & 0x0004)) *sptr++ = 'E';
   if (!(pmask & 0x0008)) *sptr++ = 'D';
   *sptr++ = ',';
   if (!(pmask & 0x0010)) *sptr++ = 'R';
   if (!(pmask & 0x0020)) *sptr++ = 'W';
   if (!(pmask & 0x0040)) *sptr++ = 'E';
   if (!(pmask & 0x0080)) *sptr++ = 'D';
   *sptr++ = ',';
   if (!(pmask & 0x0100)) *sptr++ = 'R';
   if (!(pmask & 0x0200)) *sptr++ = 'W';
   if (!(pmask & 0x0400)) *sptr++ = 'E';
   if (!(pmask & 0x0800)) *sptr++ = 'D';
   *sptr++ = ',';
   if (!(pmask & 0x1000)) *sptr++ = 'R';
   if (!(pmask & 0x2000)) *sptr++ = 'W';
   if (!(pmask & 0x4000)) *sptr++ = 'E';
   if (!(pmask & 0x8000)) *sptr++ = 'D';

   *sptr = '\0';
   if (Debug) fprintf (stdout, "|%s|\n", String);
   return (sptr - String);
}
 
/*****************************************************************************/
/*
Just return the process' current BYTLM quota.
*/ 

GetJpiBytLm ()

{
   static unsigned long  Pid = 0;

   static unsigned long  JpiBytLm;
   static struct
   {
      unsigned short  buf_len;
      unsigned short  item;
      unsigned char   *buf_addr;
      unsigned short  *short_ret_len;
   }
      JpiItems [] =
   {
      { sizeof(JpiBytLm), JPI$_BYTLM, &JpiBytLm, 0 },
      {0,0,0,0}
   };

   int  status;
   IO_SB  IOsb;

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

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

   status = sys$getjpiw (EfnWait, &Pid, 0, &JpiItems, &IOsb, 0, 0);
   if (VMSok (status)) status = IOsb.Status;
   if (VMSok (status)) return (JpiBytLm);
   ErrorExitVmsStatus (status, "sys$getjpiw()", FI_LI);
}

/*****************************************************************************/
/*
Generate a unique request identifier.  Based on the description and code of the
Apache Group's "mod_unique" module.  One change has been made to allow the ID
to be used as part of VMS file names, the substitution of '@' for '_'.
*/

char* GenerateUniqueId (REQUEST_STRUCT *rqptr)

{
   static unsigned char  ValueTable [] =
      "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789_-";

   static int  UniqueIdCount = 0;
   static char  UniqueIdString [UNIQUE_ID_SIZE+8];

   int  idx;
   unsigned long  TimeStamp;
   char *sptr; 
   unsigned char  *cptr; 

#ifndef __VAX
#   pragma member_alignment __save
#   pragma nomember_alignment
#endif

   struct {
      unsigned int  Stamp;
      unsigned int  IpAddr;
      unsigned int  Pid;
      unsigned short  Count;
      unsigned short  Zeroes;
   } UniqueId;

#ifndef __VAX
#   pragma member_alignment __restore
#endif

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

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

   TimeStamp = decc$fix_time (&rqptr->rqTime.Vms64bit);

   UniqueId.Stamp = htonl(TimeStamp);
   UniqueId.IpAddr = htonl(rqptr->ServicePtr->ServerIpAddress);
   UniqueId.Pid = htonl(HttpdProcess.Pid);
   UniqueId.Count = htons(UniqueIdCount);
   /* this is just padding for the encoding */
   UniqueId.Zeroes = 0;
   UniqueIdCount++;

   sptr = UniqueIdString;
   cptr = (unsigned char*)&UniqueId;
   for (idx = 0; idx < sizeof(UniqueId)-sizeof(short); idx += 3)
   {
      *sptr++ = ValueTable[cptr[idx]>>2];
      *sptr++ = ValueTable[((cptr[idx] & 0x03) << 4) |
                           ((cptr[idx+1] & 0xf0) >> 4)];
      *sptr++ = ValueTable[((cptr[idx+1] & 0x0f) << 2) |
                           ((cptr[idx+2] & 0xc0) >> 6)];
      *sptr++ = ValueTable[cptr[idx+2] & 0x3f];
   }
   UniqueIdString[UNIQUE_ID_SIZE] = '\0';
   if (Debug) fprintf (stdout, "UniqueIdString |%s|\n", UniqueIdString);

   return (UniqueIdString);
}

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

