/*****************************************************************************/
/*
                                 Error.c

Error reporting functions.

All errors are reported using HTML formatting.  The assumptions being: 

  o  That most errors will occur before a document begins being transfered
     (e.g. file not found), and so its content type has not been set, and 
     the error can sent with a "text/html" type.

  o  HTML documents are probably more extensive than plain-text documents.
     Hence if an error occurs during transfer (e.g. record too big for user's
     buffer) it can be reported within the document with correct formatting 
     (document context notwithstanding).

  o  If an error occurs while transfering a plain-text document the HTML
     message is still readable, albeit a little cluttered.  Additional,
     obvious text is included to try and highlight the message.

  o  In non-"text/..." transfers the message text is not included anyway.

After a request thread is created and an error is generated the storage
'rqptr->rqResponse.ErrorReportPtr' becomes non-NULL.  This mechanism can be
used  to detect whether an error has occured.  Most common errors create an
error  message in heap memory and point at this using
'rqptr->rqResponse.ErrorReportPtr'.  This message can be output at some time
convenient to the task underway.   After an error message is sent to the client
the 'rqptr->rqResponse.ErrorReportPtr'  is returned to a NULL value to indicate
no outstanding error (but usually the  thread is disposed of after an error
occurs).  Notification of other, more  critical errors are sent directly to the
client at the time they occur (for  instance, failed heap memory allocation).

If ErrorGeneral() is called without first setting rqptr->rqResponse.HttpStatus
it will default to 500, or "internal server error".  Therefore string
overflows and the like can be called without setting this code.  Query string
errors should set to 403 or 404 indication request cannot be processed.

If ErrorVmsStatus() is called without first setting rqptr->rqResponse.HttpStatus
the function will attempt to determine the most appropriate HTTP status code
from the VMS status value.  It will then default to 500, or "internal server
error", indicating some unsual (e.g. non file system) condition.

The server configuration can supply additional information included whenever
an error report is generated.  This includes a URL for contacting the system
administrator (note: URL, not text, etc.) when an implementation,
configuration, process quota, etc., error is reported.   It also can provide
text to be included against any other error report.  This text can contain
HTML markup, carriage control, etc., and is designed for a brief message
containing a link to an explanation page.

Error reporting can be performed by local (server-internal) redirection.  This
provides the opportunity for greater local-site control over content and
format.  When an error is redirected essential error information is encoded
into a query-string formatted (URL-encoded) string.  The redirection merely
creates a new request with the redirection document (SSI or CGI script) as the
path and this information as the query string.  The reporting document
environment can then use this information via the following CGI variables:

FORM_ERROR_ABOUT        item message is about (if applicable)
FORM_ERROR_ABOUT2       additional information (if applicable)
FORM_ERROR_BASIC        basic authentication challenge
FORM_ERROR_DIGEST       digest authentication challenge
FORM_ERROR_LINE         name of source code line (e.g. "1732")
FORM_ERROR_MODULE       name of source code module (e.g. "REQUEST")
FORM_ERROR_REALM        authentication realm
FORM_ERROR_STATUS       HTTP status code (e.g. "404")
FORM_ERROR_STATUS_TEXT  text meaning of above status code (e.g. "Not Found")
FORM_ERROR_STATUS_EXPLANATION  more detailed explanation of above status code
FORM_ERROR_TEXT         server-generated error message
FORM_ERROR_TEXT2        server suggestion about what to do about it :^)
FORM_ERROR_TYPE        "basic" or "detailed"
FORM_ERROR_VMS          VMS status value in decimal (if applicable)


VERSION HISTORY
---------------
01-JUN-2004  MGD  detailed report explicitly set by mapping rule
13-JAN-2004  MGD  bugfix; ErrorGeneral() always get module name and number
26-AUG-2003  MGD  remove suppression of detail for success messages
10-JUN-2003  MGD  SS$_BAD.. moved from 500 to 403 response
01-MAR-2003  MGD  set html= header and footer
12-OCT-2002  MGD  tweak RMS error HTTP status slightly
16-SEP-2002  MGD  rqPathSet.Report4NNas
04-SEP-2002  MGD  add end-of-tag markup to the divider in ErrorSendToClient()
04-AUG-2001  MGD  support module WATCHing
16-FEB-2001  MGD  add service-based error report status code handling
17-JAN-2001  MGD  bugfix; "!&%AZ" formatting for basic/digest challenge
03-DEC-2000  MGD  bugfix; ErrorGeneral()
23-NOV-2000  MGD  bugfix; ErrorRedirectQueryString() 
08-APR-2000  MGD  rework error functions (including redirect)
29-MAR-2000  MGD  bugfix; ErrorExitVmsStatus() line number from !AZ to !UL
04-MAR-2000  MGD  use WriteFao/l(),
                  add ErrorNoticed(), ErrorRedirectQueryString()
04-DEC-1999  MGD  divider handling
02-JAN-1999  MGD  proxy service
18-OCT-1998  MGD  error report redirection
30-AUG-1997  MGD  401 without a realm is now converted into a 403
09-AUG-1997  MGD  message database, with considerable rewrite!
01-FEB-1997  MGD  HTTPd version 4
06-JUN-1996  MGD  added authentication failure
01-DEC-1995  MGD  HTTPd version 3
25-MAR-1995  MGD  minor functional and cosmetic changes
20-DEC-1994  MGD  multi-threaded daemon
*/
/*****************************************************************************/

#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 <string.h>

/* VMS related header files */
#include <descrip.h>
#include <iodef.h>
#include <ssdef.h>
#include <stsdef.h>

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

#define WASD_MODULE "ERROR"

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

$DESCRIPTOR (ErrorNumberFaoDsc, "!UL\0");
$DESCRIPTOR (ErrorVmsStatusFaoDsc, "!&S\0");

BOOL  ErrorStatusReportFormatAbi;

int  ErrorsNoticedCount;

char  ErrorSanityCheck [] = "sanity check failure!",
      ErrorServerFao [] = "significant HTTPd sys$fao() failed.";

char  *ErrorRecommendNotifyPtr;

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

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

extern BOOL  LoggingEnabled;

extern int  OpcomMessages;

extern char  HttpProtocol[],
             SoftwareID[],
             Utility[];

extern CONFIG_STRUCT  Config;
extern MSG_STRUCT  Msgs;
extern WATCH_STRUCT  Watch;

/*****************************************************************************/
/*
Check that the quasi-FAO directives expected in a possibly user-configured
report format are those that are expected, and no more/no less.  This obviously
provides some pre-runtime integrity confidence, reducing the chance of a server
ACCVIO or some such.
*/

ErrorCheckReportFormats ()

{
   static struct {
      char  *ReportName;
      char  *CheckString;
      char  *ReportFormat;
   } FormatCheck [] =
   {
     { "Report Format", "!AZ!AZ!UL!AZ!**!AZ!UL!AZ!&@!&@!&@", NULL },
     { "Signature Format", "!AZ", NULL },
     { "Server Signature", "!AZ!AZ!AZ!UL", NULL },
     { NULL, NULL, NULL, }
   };

   BOOL  ErrorReported;
   int  idx;
   char  *cptr, *sptr;

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

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

   FormatCheck[0].ReportFormat = MsgFor(NULL,MSG_STATUS_REPORT_FORMAT);
   FormatCheck[1].ReportFormat = MsgFor(NULL,MSG_STATUS_REPORT_FORMAT_SIG);
   FormatCheck[2].ReportFormat = MsgFor(NULL,MSG_STATUS_SIGNATURE);
   ErrorReported = false;

   for (idx = 0; FormatCheck[idx].ReportName; idx++)
   {
      cptr = FormatCheck[idx].ReportFormat;
      sptr = FormatCheck[idx].CheckString;

      while (*cptr)
      {
         while (*cptr && *cptr != '!') cptr++;
         if (*(unsigned short*)cptr == '!!')
         {
            cptr += 2;
            continue;
         }
         if (*sptr != '!') break;
         if (*(unsigned short*)cptr == '!+')
         {
            sptr++;
            if (*(unsigned short*)sptr == 'AZ' ||
                *(unsigned short*)sptr == 'UL' ||
                *(unsigned short*)sptr == '&@')
            {
               cptr += 2;
               sptr += 2;
               continue;
            }
            break;
         }
         if (*cptr == '!' && *(unsigned short*)(cptr+1) == '%%' &&
             *sptr == '!' && *(unsigned short*)(sptr+1) == '&@')
         {
            /* '%%' as <-7.3 backward compatible with 7.3-> '&@' */
            cptr += 3;
            sptr += 3;
            continue;
         }
         if ((*cptr == '!' && *(unsigned short*)(cptr+1) == 'AZ' ||
              *cptr == '!' && *(unsigned short*)(cptr+1) == '%%' ||
              *cptr == '!' && *(unsigned short*)(cptr+1) == '&@') &&
             *sptr == '!' && *(unsigned short*)(sptr+1) == '**')
         {
            /* allow either "!AZ" or "!%%" in report format string */
            cptr += 3;
            sptr += 3;
            continue;
         }
         while (*sptr && *cptr && *sptr == *cptr)
         {
            cptr++;
            sptr++;
            if (*sptr == '!') break;
         }
         if (*sptr && *sptr != '!') break;
      }

      if (*cptr && *sptr)
         sptr = "problem detected";
      else
      if (*sptr)
         sptr = "too few directives";
      else
      if (*cptr)
         sptr = "too many directives";
      else
         sptr = "";
      if (*sptr)
      {
         WriteFaoStdout ("%!AZ-E-MSG, !AZ in \"!AZ\"\n \\!AZ\\\n",
                         Utility, sptr, FormatCheck[idx].ReportName,
                         *cptr ? cptr : "(end-of-string)");
         ErrorReported = true;
      }
   }

   if (ErrorReported)
   {
      if (OpcomMessages & OPCOM_HTTPD)
         WriteFaoOpcom ("%!AZ-E-MSG, problem(s) with reports format", Utility);
      exit (SS$_ABORT);
   }

   /* check whether the fifth directive is a !AZ (<-8.2) or !%%/!&@ (8.2->) */
   cptr = FormatCheck[0].ReportFormat;
   for (idx = 0; idx < 5; idx++)
   {
      while (*cptr && *cptr != '!') cptr++;
      if (*cptr == '!') cptr++;
   }
   if (*(unsigned short*)cptr == '%%' ||
       *(unsigned short*)cptr == '&@')
       ErrorStatusReportFormatAbi = true;
   if (WATCH_MODULE(WATCH_MOD__OTHER))
      WatchThis (NULL, FI_LI, WATCH_MOD__OTHER, "!&B !&Z",
                 ErrorStatusReportFormatAbi, cptr);
}

/*****************************************************************************/
/*
Exit the application after output of the module and line location reporting 
the error, a brief explanation, and then exiting generating a DCL-reported 
message.
*/

ErrorExitVmsStatus
(
int StatusValue,
char *Explanation,
char *SourceModuleName,
int SourceLineNumber
)
{
   /*********/
   /* begin */
   /*********/

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

   if (!Explanation) Explanation = "(null)";

   WriteFaoStdout (
"%!AZ-E-SOFTWAREID, !AZ\n\
-!AZ-E-WHERE, module:!AZ line:!UL\n\
-!AZ-E-WHAT, !AZ\n",
            Utility, SoftwareID,
            Utility, SourceModuleName, SourceLineNumber,
            Utility, Explanation);

   if (!StatusValue) StatusValue = SS$_BADPARAM | STS$M_INHIB_MSG;
   exit (StatusValue);
}

/*****************************************************************************/
/*
Report to the server process log, and if enabled to OPCOM, a non-fatal error
that has been noticed.
*/

ErrorNoticed
(
int  StatusValue,
char *Explanation,
char *SourceModuleName,
int SourceLineNumber
)
{
   int  status;

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

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

   ErrorsNoticedCount++;

   if (!Explanation) Explanation = "";

   if (StatusValue)
   {
      WriteFaoStdout (
"%!AZ-W-NOTICED, !20%D, !AZ:!UL, !AZ!AZ%X!XL\n-!-!&M\n",
         Utility, 0, SourceModuleName, SourceLineNumber,
         Explanation, Explanation[0] ? " " : "", StatusValue);

      WriteFaoOpcom (
"%!AZ-W-NOTICED, !AZ:!UL, !AZ!AZ%X!XL\r\n-!-!&M",
         Utility, SourceModuleName, SourceLineNumber,
         Explanation, Explanation[0] ? " " : "", StatusValue);

      if (WATCH_CAT && Watch.Category)
      {
         WatchThis (NULL, FI_LI, WATCH_NOTICED, "!AZ:!UL, !AZ!AZ%X!XL",
                    SourceModuleName, SourceLineNumber,
                    Explanation, Explanation[0] ? " " : "", StatusValue);
         WatchDataFormatted ("%!&M\n", StatusValue);
      }
   }
   else
   {
      WriteFaoStdout (
"%!AZ-W-NOTICED, !20%D, !AZ:!UL, !AZ\n",
         Utility, 0, SourceModuleName, SourceLineNumber, Explanation);

      WriteFaoOpcom (
"%!AZ-W-NOTICED, !AZ:!UL, !AZ",
         Utility, SourceModuleName, SourceLineNumber, Explanation);

      if (WATCH_CAT && Watch.Category)
         WatchThis (NULL, FI_LI, WATCH_NOTICED, "!AZ:!UL, !AZ",
                    SourceModuleName, SourceLineNumber, Explanation);
   }
}

/****************************************************************************/
/*
It is a fatal error to call this function without an error message.  If a
response header and output has already been generated insert a plain-text and
HTML obvious divider into the output (hopefully it's text ... it doesn't really
matter anyway, it's an error condition after all :^) and then send the actual
error message.  If no response has yet been made then either redirect error
processing appropriately (using the query-string format error information
generated by ErrorVmsStatus() and ErrorGeneral()), or generate an response
header then send the message.
*/

ErrorSendToClient (REQUEST_STRUCT *rqptr)

{
   /* contains HTML, also enough to make it obvious in plain text */
   static char  DividerFao [] =
"</TT></B></U></I></STRIKE></SUB></SUP></PRE>\
</NOBR></OL></UL></DL></BLOCKQUOTE></TABLE>\n\
<P><FONT COLOR=\"#ff0000\"><B><HR WIDTH=100% SIZE=2 NOSHADE>\n\
\n\
<!!-- -- --   !AZ   -- -- -->\n\
\n";

   static int  DividerLength;
   static char  *DividerPtr;

   int  status;
   unsigned short  Length;
   unsigned long  *vecptr;
   unsigned long  FaoVector [32];
   char  *cptr, *sptr;
   char  Buffer [1024];

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

   if (WATCHING(rqptr) && WATCH_MODULE(WATCH_MOD__OTHER))
      WatchThis (rqptr, FI_LI, WATCH_MOD__OTHER, "ErrorSendToClient()");

   if (!rqptr->rqResponse.ErrorReportPtr)
      ErrorExitVmsStatus (SS$_BUGCHECK, ErrorSanityCheck, FI_LI);

   /* shouldn't happen ... but */
   if (!rqptr->rqResponse.HttpStatus) rqptr->rqResponse.HttpStatus = 500;

   if (rqptr->rqResponse.HeaderPtr &&
       rqptr->rqOutput.BufferPtr)
   {
      /*******************/
      /* provide divider */
      /*******************/

      if (!DividerPtr)
      {
         status = WriteFao (Buffer, sizeof(Buffer), &Length,
                            DividerFao, MsgFor(rqptr,MSG_STATUS_ERROR));
         if (VMSnok (status) || status == SS$_BUFFEROVF)
            ErrorNoticed (status, "WriteFao()", FI_LI);

         DividerPtr = VmGet (Length+1);
         memcpy (DividerPtr, Buffer, (DividerLength=Length)+1);
      }

      NetWrite (rqptr, &ErrorMessageDividerAst, DividerPtr, DividerLength);

      return;
   }

   if (rqptr->rqResponse.ErrorReportByRedirect)
   {
      /******************************/
      /* redirect to error reporter */
      /******************************/

      /* log the original request */
      if (LoggingEnabled) Logging (rqptr, LOGGING_ENTRY);

      status = WriteFao (Buffer, sizeof(Buffer), &Length,
                         rqptr->ServicePtr->ErrorReportPath,
                         rqptr->rqResponse.HttpStatus);
      if (VMSnok (status) || status == SS$_BUFFEROVF)
         ErrorNoticed (status, "WriteFao()", FI_LI);

      /* combine the redirection path and the error query string */
      Length += rqptr->rqResponse.ErrorReportLength + 1;
      rqptr->rqResponse.LocationPtr = sptr = VmGetHeap (rqptr, Length);
      for (cptr = Buffer; *cptr; *sptr++ = *cptr++);
      for (cptr = rqptr->rqResponse.ErrorReportPtr; *cptr; *sptr++ = *cptr++);
      *sptr = '\0';

      /* indicate the error message has been sent */
      rqptr->rqResponse.ErrorReportPtr = NULL;
      rqptr->rqResponse.ErrorReportLength = 0;

      /* indicate error redirection in this thread-permanent storage */
      rqptr->RedirectErrorStatusCode = rqptr->rqResponse.HttpStatus;
      rqptr->RedirectErrorAuthRealmDescrPtr = rqptr->rqAuth.RealmDescrPtr;

      /* errors are always sent as the request is finalized, continue that */
      RequestEnd (rqptr);

      return;
   }

   /***********************/
   /* provide HTTP header */
   /***********************/

   ResponseHeader (rqptr, rqptr->rqResponse.HttpStatus,
               "text/html", -1, NULL, NULL);

   /* errors are always sent as the request is finalized, continue that */
   NetWrite (rqptr, &RequestEnd,
             rqptr->rqResponse.ErrorReportPtr,
             rqptr->rqResponse.ErrorReportLength);

   /* indicate the error message has been sent */
   rqptr->rqResponse.ErrorReportPtr = NULL;
   rqptr->rqResponse.ErrorReportLength = 0;
}

/*****************************************************************************/
/*
Divider delivered, now output the error report.
*/

ErrorMessageDividerAst (REQUEST_STRUCT *rqptr)

{
   int  status;

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

   if (WATCHING(rqptr) && WATCH_MODULE(WATCH_MOD__OTHER))
      WatchThis (rqptr, FI_LI, WATCH_MOD__OTHER,
                 "ErrorMessageDividerAst() !&F", &ErrorMessageDividerAst);

   /* errors are always sent as the request is finalized, continue that */
   NetWrite (rqptr, &RequestEnd,
             rqptr->rqResponse.ErrorReportPtr,
             rqptr->rqResponse.ErrorReportLength);

   /* indicate the error message has been sent */
   rqptr->rqResponse.ErrorReportPtr = NULL;
   rqptr->rqResponse.ErrorReportLength = 0;
}

/*****************************************************************************/
/*
Set html= body and/or header.
*/

ErrorReportHeader
(
REQUEST_STRUCT *rqptr,
unsigned long  **vecptrptr
)
{
   unsigned long  *vecptr;

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

   if (WATCHING(rqptr) && WATCH_MODULE(WATCH_MOD__OTHER))
      WatchThis (rqptr, FI_LI, WATCH_MOD__OTHER, "ErrorReportHeader()");

   vecptr = *vecptrptr;

   if (ErrorStatusReportFormatAbi)
   {
      if (rqptr->rqPathSet.HtmlBodyTagPtr)
      {
         if (rqptr->rqPathSet.HtmlBodyTagPtr[0] == '<')
            *vecptr++ = "!AZ!&@";
         else
            *vecptr++ = "<BODY!&+AZ>\n!&@";
         *vecptr++ = rqptr->rqPathSet.HtmlBodyTagPtr;
      }
      else
      if (rqptr->ServicePtr->BodyTag[0])
      {
         *vecptr++ = "!AZ!&@";
         *vecptr++ = rqptr->ServicePtr->BodyTag;
      }
      else
      {
         *vecptr++ = "!AZ!&@";
         *vecptr++ = Config.cfServer.ReportBodyTag;
      }

      if (rqptr->rqPathSet.HtmlHeaderPtr ||
          rqptr->rqPathSet.HtmlHeaderTagPtr)
      {
         if (rqptr->rqPathSet.HtmlHeaderTagPtr &&
             rqptr->rqPathSet.HtmlHeaderTagPtr[0] == '<')
            *vecptr++ = "!AZ\n!&@";
         else
            *vecptr++ =
"<TABLE CELLPADDING=5 CELLSPACING=0 BORDER=0 WIDTH=100%><TR><TD!&+AZ>\n!&@";
         *vecptr++ = rqptr->rqPathSet.HtmlHeaderTagPtr;
         *vecptr++ = "!AZ<P>";
         *vecptr++ = rqptr->rqPathSet.HtmlHeaderPtr;
      }
      else
         *vecptr++ = "";
   }
   else
   if (rqptr->ServicePtr->BodyTag[0])
      *vecptr++ = rqptr->ServicePtr->BodyTag;
   else
      *vecptr++ = Config.cfServer.ReportBodyTag;

   *vecptrptr = vecptr;
}

/*****************************************************************************/
/*
Set html= body and/or header.
*/

ErrorReportEndHeader
(
REQUEST_STRUCT *rqptr,
unsigned long  **vecptrptr
)
{
   unsigned long  *vecptr;

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

   if (WATCHING(rqptr) && WATCH_MODULE(WATCH_MOD__OTHER))
      WatchThis (rqptr, FI_LI, WATCH_MOD__OTHER, "ErrorReportEndHeader()");

   vecptr = *vecptrptr;

   if (ErrorStatusReportFormatAbi &&
       (rqptr->rqPathSet.HtmlHeaderPtr ||
        rqptr->rqPathSet.HtmlHeaderTagPtr))
      *vecptr++ = "</TD></TR></TABLE>\n!&@";

   *vecptrptr = vecptr;
}

/*****************************************************************************/
/*
Set html= footer.
*/

ErrorReportFooter
(
REQUEST_STRUCT *rqptr,
unsigned long  **vecptrptr
)
{
   unsigned long  *vecptr;
   char  Signature [256];

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

   if (WATCHING(rqptr) && WATCH_MODULE(WATCH_MOD__OTHER))
      WatchThis (rqptr, FI_LI, WATCH_MOD__OTHER, "ErrorReportFooter()");

   vecptr = *vecptrptr;

   if (ErrorStatusReportFormatAbi &&
       (rqptr->rqPathSet.HtmlFooterPtr ||
        rqptr->rqPathSet.HtmlFooterTagPtr))
   {
      if (rqptr->rqPathSet.HtmlFooterTagPtr &&
          rqptr->rqPathSet.HtmlFooterTagPtr[0] == '<')
         *vecptr++ = "\n<P>!AZ\n!&@";
      else
         *vecptr++ =
"\n<P><TABLE CELLPADDING=5 CELLSPACING=0 BORDER=0 WIDTH=100%><TR><TD!&+AZ>\n!&@";
      *vecptr++ = rqptr->rqPathSet.HtmlFooterTagPtr;
      if (Config.cfServer.Signature)
      {
         *vecptr++ = "!AZ\n<P>!&@";
         *vecptr++ = ServerSignature (rqptr, Signature, sizeof(Signature));
      }
      else
         *vecptr++ = "!&@";
       *vecptr++ = "!AZ\n</TD></TR></TABLE>";
       *vecptr++ = rqptr->rqPathSet.HtmlFooterPtr;
   }
   else
   if (Config.cfServer.Signature)
   {
      *vecptr++ = "\n!&@";
      *vecptr++ = MsgFor(rqptr,MSG_STATUS_REPORT_FORMAT_SIG);
      *vecptr++ = ServerSignature (rqptr, Signature, sizeof(Signature));
   }
   else
      *vecptr++ = "";

   *vecptrptr = vecptr;
}

/*****************************************************************************/
/*
Yes!  Report success from the error module.  Most consistant place to put it. 
Perhaps the ERROR module should be renamed to REPORT!
*/
 
ErrorReportSuccess
(
REQUEST_STRUCT *rqptr,
char *ExplanationPtr,
...
)
{
   int  status,
        argcnt,
        StatusCode;
   unsigned short  Length;
   unsigned long  *vecptr;
   unsigned long  FaoVector [32];
   char  *cptr, *sptr, *zptr,
         *CategoryPtr,
         *ReportPtr;
   char  Buffer [4096];
   va_list  argptr;

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

   va_count (argcnt);

   if (WATCHING(rqptr) && WATCH_MODULE(WATCH_MOD__OTHER))
      WatchThis (rqptr, FI_LI, WATCH_MOD__OTHER,
                 "ErrorReportSuccess() !UL", argcnt);

   if (argcnt > 16) return (SS$_OVRMAXARG);

   if (!rqptr->rqResponse.HttpStatus) rqptr->rqResponse.HttpStatus = 200;
   StatusCode = rqptr->rqResponse.HttpStatus;
   /*
      HTTP status 201 inhibits the response informational message
      (in at least Netscape Navigator 3.0), so let's revert to 200!
   */
   if (rqptr->rqResponse.HttpStatus == 201) rqptr->rqResponse.HttpStatus = 200;

   if (WATCHING(rqptr) && WATCH_CATEGORY(WATCH_RESPONSE))
      WatchThis (rqptr, FI_LI, WATCH_RESPONSE,
                 "!AZ!3ZL \"!AZ\"",
                 !rqptr->rqPathSet.ReportDetailed &&
                 (rqptr->rqPathSet.ReportBasic ||
                  Config.cfReport.BasicOnly) ? "(basic) " : "(detailed)",
                 StatusCode, ExplanationPtr);
                   
   RESPONSE_HEADER_200_HTML (rqptr);

   vecptr = FaoVector;

   *vecptr++ = "";
   *vecptr++ = CategoryPtr = MsgFor(rqptr,MSG_STATUS_SUCCESS);
   *vecptr++ = StatusCode;
   *vecptr++ = HttpStatusCodeText(StatusCode);

   ErrorReportHeader (rqptr, &vecptr);

   *vecptr++ = CategoryPtr;
   *vecptr++ = StatusCode;
   *vecptr++ = HttpStatusCodeExplanation (rqptr,StatusCode);

   ErrorReportEndHeader (rqptr, &vecptr);

   *vecptr++ = "<P>!&@";
   /* a leading dollar indicates a server administration report */
   if (ExplanationPtr[0] == '$')
      *vecptr++ = ExplanationPtr+1;
   else
      *vecptr++ = ExplanationPtr;
   /* now add the variable length arguments to the vector */
   va_start (argptr, ExplanationPtr);
   for (argcnt -= 2; argcnt; argcnt--)
      *vecptr++ = va_arg (argptr, unsigned long);
   va_end (argptr);
   *vecptr++ = MsgFor(rqptr,MSG_STATUS_INFO);

   ErrorReportFooter (rqptr, &vecptr);

   ReportPtr = MsgFor(rqptr,MSG_STATUS_REPORT_FORMAT);
   status = WriteFaol (Buffer, sizeof(Buffer), &Length, ReportPtr, &FaoVector);
   if (VMSnok (status) || status == SS$_BUFFEROVF)
      ErrorExitVmsStatus (status, ErrorServerFao, FI_LI);

   /* dollar indicates a server administration report, write immediately! */
   if (ExplanationPtr[0] == '$')
      NetWrite (rqptr, NULL, Buffer, Length);
   else
      NetWriteBuffered (rqptr, NULL, Buffer, Length);
}

/*****************************************************************************/
/*
Generate an error message about a general (non-VMS status) problem for
subsequent reporting to the client.  If error redirection applies to the
request then generate the error information in a form suitable for provision as
a request query-string.
*/
 
ErrorGeneral
(
REQUEST_STRUCT *rqptr,
char *ExplanationPtr,
...
)
{
   int  idx, status,
        argcnt,
        OriginalStatusCode,
        SourceLineNumber,
        StatusCode;
   unsigned short  Length;
   unsigned long  *vecptr;
   unsigned long  FaoVector [32];
   char  *cptr, *sptr, *zptr,
         *CategoryPtr,
         *ReportPtr,
         *SourceModuleName;
   char  Buffer [4096];
   va_list  argptr;

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

   va_count (argcnt);

   if (WATCHING(rqptr) && WATCH_MODULE(WATCH_MOD__OTHER))
      WatchThis (rqptr, FI_LI, WATCH_MOD__OTHER,
                 "ErrorGeneral() !UL", argcnt);

   if (argcnt > 16) return (SS$_OVRMAXARG);

   /* don't overwrite any existing error message */
   if (ERROR_REPORTED (rqptr)) return;

   /* if this request is processing an error report redirect */
   if (rqptr->rqResponse.ErrorReportByRedirect &&
       rqptr->RedirectErrorStatusCode)
   {
      /* problem processing the error report, no infinite loops here please! */
      rqptr->rqResponse.ErrorReportByRedirect = false;
      rqptr->RedirectErrorStatusCode = 0;
      rqptr->RedirectErrorAuthRealmDescrPtr = NULL;
   }

   if (rqptr->rqResponse.HeaderPtr &&
       rqptr->rqOutput.BufferPtr)
   {
      /* some output has already occured, internal error reporting */
      rqptr->rqResponse.ErrorReportByRedirect = false;
   }

   OriginalStatusCode = rqptr->rqResponse.HttpStatus;

   if (!rqptr->rqResponse.HttpStatus) rqptr->rqResponse.HttpStatus = 500;

   /* when mapping error status by implication its a basic report! */
   if ((rqptr->rqResponse.HttpStatus == 400 ||
        rqptr->rqResponse.HttpStatus == 409) &&
       rqptr->rqPathSet.Report400as)
   {
      rqptr->rqResponse.HttpStatus = rqptr->rqPathSet.Report400as;
      rqptr->rqPathSet.ReportBasic = true;
      rqptr->rqPathSet.ReportDetailed = false;
   }
   else
   if ((rqptr->rqResponse.HttpStatus == 403 ||
        rqptr->rqResponse.HttpStatus == 409) &&
       rqptr->rqPathSet.Report403as)
   {
      rqptr->rqResponse.HttpStatus = rqptr->rqPathSet.Report403as;
      rqptr->rqPathSet.ReportBasic = true;
      rqptr->rqPathSet.ReportDetailed = false;
   }
   else
   if ((rqptr->rqResponse.HttpStatus == 404 ||
        rqptr->rqResponse.HttpStatus == 409) &&
       rqptr->rqPathSet.Report404as)
   {
      rqptr->rqResponse.HttpStatus = rqptr->rqPathSet.Report404as;
      rqptr->rqPathSet.ReportBasic = true;
      rqptr->rqPathSet.ReportDetailed = false;
   }

   StatusCode = rqptr->rqResponse.HttpStatus;

   if (StatusCode == 401 || StatusCode == 407) ResponseHeaderChallenge (rqptr);

   /* get the reporting module name and line number */
   va_start (argptr, ExplanationPtr);
   for (argcnt -= 2; argcnt; argcnt--)
   {
      /* the last two arguments must be the macro "FI_LI" */
      if (argcnt == 2)
         SourceModuleName = va_arg (argptr, unsigned long);
      else
      if (argcnt == 1)
         SourceLineNumber = va_arg (argptr, unsigned long);
      else
         va_arg (argptr, unsigned long);
   }
   va_end (argptr);

   if (WATCHING(rqptr) && WATCH_CATEGORY(WATCH_ERROR))
      WatchThis (rqptr, FI_LI, WATCH_ERROR,
                 "!AZ:!UL!AZ !3ZL(!3ZL) \"!AZ\"",
                 SourceModuleName, SourceLineNumber,
                 !rqptr->rqPathSet.ReportDetailed &&
                 (rqptr->rqPathSet.ReportBasic ||
                  Config.cfReport.BasicOnly) ? " (basic)" : "(detailed)",
                 StatusCode, OriginalStatusCode, ExplanationPtr);

   if (rqptr->rqResponse.ErrorReportByRedirect)
   {
      /*************************/
      /* by-redirect available */
      /*************************/

      if (rqptr->ServicePtr->ErrorReportPathCodesCount)
      {
         /* check for handled error code */
         for (idx = 0;
              idx < rqptr->ServicePtr->ErrorReportPathCodesCount;
              idx++)
         {
            if (rqptr->ServicePtr->ErrorReportPathCodes[idx] == StatusCode)
               break;
         }
         if (idx >= rqptr->ServicePtr->ErrorReportPathCodesCount)
         {
            /* code not found, use standard error reporting */
            rqptr->rqResponse.ErrorReportByRedirect = false;
            rqptr->RedirectErrorStatusCode = 0;
            rqptr->RedirectErrorAuthRealmDescrPtr = NULL;
         }
      }

      if (rqptr->rqResponse.ErrorReportByRedirect)
      {
         /******************************************/
         /* redirect report, generate query string */
         /******************************************/

         va_count (argcnt);
         if (argcnt <= 4)
            cptr = "<P>!AZ";
         else
            cptr = "<P>!&@";

         vecptr = FaoVector;
         *vecptr++ = ExplanationPtr;
         /* add any variable length arguments to the vector */
         va_start (argptr, ExplanationPtr);
         for (argcnt -= 4; argcnt; argcnt--)
            *vecptr++ = va_arg (argptr, unsigned long);
         va_end (argptr);

         status = WriteFaol (Buffer, sizeof(Buffer), &Length, cptr, &FaoVector);
         if (VMSnok (status) || status == SS$_BUFFEROVF)
            ErrorExitVmsStatus (status, ErrorServerFao, FI_LI);

         ErrorRedirectQueryString (rqptr, 0, Buffer, "",
                                   SourceModuleName, SourceLineNumber);
         return;
      }
   }

   /*******************/
   /* generate report */
   /*******************/

   va_count (argcnt);

   vecptr = FaoVector;
   if (rqptr->rqPathSet.ReportDetailed)
      *vecptr++ = ErrorSourceInfo (SourceModuleName, SourceLineNumber);
   else
   if (rqptr->rqPathSet.ReportBasic || Config.cfReport.BasicOnly)
      *vecptr++ = "";
   else
      *vecptr++ = ErrorSourceInfo (SourceModuleName, SourceLineNumber);
   *vecptr++ = CategoryPtr = MsgFor(rqptr,MSG_STATUS_ERROR);
   *vecptr++ = StatusCode;
   *vecptr++ = HttpStatusCodeText(StatusCode);

   ErrorReportHeader (rqptr, &vecptr);

   *vecptr++ = CategoryPtr;
   *vecptr++ = StatusCode;
   *vecptr++ = HttpStatusCodeExplanation (rqptr,StatusCode);

   ErrorReportEndHeader (rqptr, &vecptr);

   if (!rqptr->rqPathSet.ReportDetailed &&
       (rqptr->rqPathSet.ReportBasic ||
        Config.cfReport.BasicOnly))
      *vecptr++ = "";
   else 
   if (argcnt <= 4)
   {
      *vecptr++ = "<P>!AZ";
      *vecptr++ = ExplanationPtr;
   }
   else 
   {
      *vecptr++ = "<P>!&@";
      *vecptr++ = ExplanationPtr;
      /* add any variable length arguments to the vector */
      va_start (argptr, ExplanationPtr);
      for (argcnt -= 4; argcnt; argcnt--)
         *vecptr++ = va_arg (argptr, unsigned long);
      va_end (argptr);
   }
   *vecptr++ = MsgFor(rqptr,MSG_STATUS_INFO);

   ErrorReportFooter (rqptr, &vecptr);

   ReportPtr = MsgFor(rqptr,MSG_STATUS_REPORT_FORMAT);
   status = WriteFaol (Buffer, sizeof(Buffer), &Length, ReportPtr, &FaoVector);
   if (VMSnok (status) || status == SS$_BUFFEROVF)
      ErrorExitVmsStatus (status, ErrorServerFao, FI_LI);

   Buffer[rqptr->rqResponse.ErrorReportLength = Length] = '\0';
   rqptr->rqResponse.ErrorReportPtr = VmGetHeap (rqptr, Length+1);
   memcpy (rqptr->rqResponse.ErrorReportPtr, Buffer, Length);
   rqptr->rqResponse.ErrorReportPtr[
      rqptr->rqResponse.ErrorReportLength = Length] = '\0';
}

/*****************************************************************************/
/*
Generate an error message about a VMS status problem for subsequent reporting
to the client.  Generally most errors are reported as documents in their own
right, making the full HTML document desirable.  If reported part-way through
some action the redundant tags should be ignored anyway.  If error redirection
applies to the request then generate the error information in a form suitable
for provision as a request query-string.
*/

ErrorVmsStatus
(
REQUEST_STRUCT *rqptr,
int StatusValue,
char *SourceModuleName,
int SourceLineNumber
)
{
   int  idx, status,
        OriginalStatusCode,
        StatusCode;
   unsigned short  Length;
   unsigned short  ShortLength;
   unsigned long  *vecptr;
   unsigned long  FaoVector [32];
   char  *cptr, *sptr, *zptr,
         *CategoryPtr,
         *ContentTypePtr,
         *ExplanationPtr,
         *RecommendPtr,
         *ReportPtr;
   char  Message [256],
         Buffer [4096];
   $DESCRIPTOR (MessageDsc, Message);

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

   if (WATCHING(rqptr) && WATCH_MODULE(WATCH_MOD__OTHER))
      WatchThis (rqptr, FI_LI, WATCH_MOD__OTHER, "ErrorVmsStatus()");
                                 
   /* don't overwrite any existing error message */
   if (ERROR_REPORTED (rqptr)) return;

   /* if this request is processing an error report redirect */
   if (rqptr->rqResponse.ErrorReportByRedirect &&
       rqptr->RedirectErrorStatusCode)
   {
      /* problem processing the error report, no infinite loops here please! */
      rqptr->rqResponse.ErrorReportByRedirect = false;
      rqptr->RedirectErrorStatusCode = 0;
      rqptr->RedirectErrorAuthRealmDescrPtr = NULL;
   }

   if (rqptr->rqResponse.HeaderPtr &&
       rqptr->rqOutput.BufferPtr)
   {
      /* some output has already occured, internal error reporting */
      rqptr->rqResponse.ErrorReportByRedirect = false;
   }

   OriginalStatusCode = rqptr->rqResponse.HttpStatus;

   /* if this error occured after header generation turn it into an error! */
   if (rqptr->rqResponse.HttpStatus == 200) rqptr->rqResponse.HttpStatus = 0;
   if (!rqptr->rqResponse.HttpStatus)
   {
      if (StatusValue == RMS$_FNF ||
          StatusValue == RMS$_DNF ||
          StatusValue == RMS$_DEV ||
          StatusValue == SS$_NOSUCHDEV ||
          StatusValue == SS$_NOSUCHFILE)
          rqptr->rqResponse.HttpStatus = 404;
      else
      if (StatusValue == RMS$_SYN ||
          StatusValue == RMS$_FNM ||
          StatusValue == RMS$_TYP ||
          StatusValue == RMS$_VER ||
          StatusValue == RMS$_DIR ||
          StatusValue == SS$_BADFILENAME ||
          StatusValue == SS$_BADFILEVER ||
          StatusValue == SS$_BADIRECTORY)
         rqptr->rqResponse.HttpStatus = 403;
      else
      if (StatusValue == RMS$_FLK ||
          StatusValue == SS$_DIRNOTEMPTY ||
          StatusValue == SS$_EXDISKQUOTA)
         rqptr->rqResponse.HttpStatus = 409;
      else
      if (StatusValue == RMS$_PRV ||
          StatusValue == SS$_NOPRIV)
         rqptr->rqResponse.HttpStatus = 403;
      else
         rqptr->rqResponse.HttpStatus = 500;
   }

   /* when mapping error status by implication its a basic report! */
   if ((rqptr->rqResponse.HttpStatus == 400 ||
        rqptr->rqResponse.HttpStatus == 409) &&
       rqptr->rqPathSet.Report400as)
   {
      rqptr->rqResponse.HttpStatus = rqptr->rqPathSet.Report400as;
      rqptr->rqPathSet.ReportBasic = true;
      rqptr->rqPathSet.ReportDetailed = false;
   }
   else
   if ((rqptr->rqResponse.HttpStatus == 403 ||
        rqptr->rqResponse.HttpStatus == 409) &&
       rqptr->rqPathSet.Report403as)
   {
      rqptr->rqResponse.HttpStatus = rqptr->rqPathSet.Report403as;
      rqptr->rqPathSet.ReportBasic = true;
      rqptr->rqPathSet.ReportDetailed = false;
   }
   else
   if ((rqptr->rqResponse.HttpStatus == 404 ||
        rqptr->rqResponse.HttpStatus == 409) &&
       rqptr->rqPathSet.Report404as)
   {
      rqptr->rqResponse.HttpStatus = rqptr->rqPathSet.Report404as;
      rqptr->rqPathSet.ReportBasic = true;
      rqptr->rqPathSet.ReportDetailed = false;
   }

   StatusCode = rqptr->rqResponse.HttpStatus;

   if (StatusCode == 401 || StatusCode == 407) ResponseHeaderChallenge (rqptr);

   if (rqptr->rqContentInfo.ContentTypePtr)
      ContentTypePtr = rqptr->rqContentInfo.ContentTypePtr;
   else
      ContentTypePtr = "";

   if (StatusValue == RMS$_FNF)
   {
      if (ConfigSameContentType (ContentTypePtr, "text/", 5))
         ExplanationPtr = MsgFor(rqptr,MSG_STATUS_DOC_NOT_FOUND);
      else
         ExplanationPtr = MsgFor(rqptr,MSG_STATUS_FILE_NOT_FOUND);
   }
   else
   if (StatusValue == RMS$_PRV ||
       StatusValue == SS$_NOPRIV)
   {
      if (ConfigSameContentType (ContentTypePtr, "text/", 5))
         ExplanationPtr = MsgFor(rqptr,MSG_STATUS_DOC_PROTECTION);
      else
         ExplanationPtr = MsgFor(rqptr,MSG_STATUS_FILE_PROTECTION);
   }
   else
   if (VMSok (status =
       sys$getmsg (StatusValue, &ShortLength, &MessageDsc, 1, 0)))
   {
      Message[ShortLength] = '\0';
      cptr = sptr = ExplanationPtr = Message;
      *cptr = toupper(*cptr);

      /* don't allow access violation to be confused with forbidden access! */
      if (StatusValue == SS$_ACCVIO)
      {
         while (*cptr && *cptr != ',') cptr++;
         if (*cptr) strcpy (cptr, " (segmentation fault)");
         cptr = sptr;
      }

      /* improve the look by removing any embedded sys$fao() formatting */
      while (*cptr)
      {
         if (*cptr == '!')
         {
            cptr++;
            *sptr++ = '?';
            /* step over any field width digits */
            while (isdigit(*cptr)) cptr++;
            /* usually two formatting characters */
            if (isalpha(*cptr)) cptr++;
            if (isalpha(*cptr)) cptr++;
         }
         else
            *sptr++ = *cptr++;
      }
      *sptr = '\0';
   }
   else
      strcpy (Message, "*ERROR* sys$getmsg()");

   if (!rqptr->rqResponse.ErrorTextPtr)
   {
      if (rqptr->rqHeader.PathInfoPtr)
         rqptr->rqResponse.ErrorTextPtr = rqptr->rqHeader.PathInfoPtr;
      else
         rqptr->rqResponse.ErrorTextPtr =
            MsgFor(rqptr, MSG_STATUS_NO_INFORMATION);
   }

   if (!rqptr->rqResponse.ErrorOtherTextPtr ||
       !Config.cfReport.MetaInfoEnabled)
      rqptr->rqResponse.ErrorOtherTextPtr =
         MsgFor(rqptr,MSG_STATUS_NO_INFORMATION);

   RecommendPtr = NULL;
   if (!Config.cfReport.ErrorRecommend ||
       StatusCode == 401 ||
       StatusCode == 407)
      RecommendPtr = "";
   else
   if (StatusValue == RMS$_FNF ||
       StatusValue == RMS$_DNF ||
       StatusValue == SS$_NOSUCHFILE)
      RecommendPtr = MsgFor(rqptr,MSG_STATUS_ADVISE_NOSUCHFILE);
   else
   if (StatusValue == RMS$_PRV)
      RecommendPtr = MsgFor(rqptr,MSG_STATUS_ADVISE_PRV);
   else
   if (StatusValue == SS$_NOPRIV)
      RecommendPtr = MsgFor(rqptr,MSG_STATUS_ADVISE_NOPRIV);
   else
   if (StatusValue == RMS$_SYN ||
       StatusValue == RMS$_DEV ||
       StatusValue == RMS$_DIR ||
       StatusValue == RMS$_FNM ||
       StatusValue == RMS$_TYP)
      RecommendPtr = MsgFor(rqptr,MSG_STATUS_ADVISE_SYNTAX);
   else
   if (StatusValue == RMS$_FLK)
      RecommendPtr = MsgFor(rqptr,MSG_STATUS_ADVISE_FLK);
   else
   if (StatusValue == SS$_DIRNOTEMPTY ||
       StatusValue == SS$_EXDISKQUOTA)
      RecommendPtr = MsgFor(rqptr,MSG_STATUS_ADVISE_CORRECT);

   if (!RecommendPtr) RecommendPtr = "";

   if (WATCHING(rqptr) && WATCH_CATEGORY(WATCH_ERROR))
      WatchThis (rqptr, FI_LI, WATCH_ERROR,
                 "!AZ:!UL!AZ !3ZL(!3ZL) !&S \"!AZ\" \"!AZ\" \"!AZ\"",
                 SourceModuleName, SourceLineNumber,
                 !rqptr->rqPathSet.ReportDetailed &&
                 (rqptr->rqPathSet.ReportBasic ||
                  Config.cfReport.BasicOnly) ? " (basic)" : "(detailed)",
                 StatusCode, OriginalStatusCode, StatusValue,
                 ExplanationPtr, rqptr->rqResponse.ErrorTextPtr,
                 rqptr->rqResponse.ErrorOtherTextPtr);

   if (rqptr->rqResponse.ErrorReportByRedirect)
   {
      /*************************/
      /* by-redirect available */
      /*************************/

      if (rqptr->ServicePtr->ErrorReportPathCodesCount)
      {
         /* check for handled error code */
         for (idx = 0;
              idx < rqptr->ServicePtr->ErrorReportPathCodesCount;
              idx++)
         {
            if (rqptr->ServicePtr->ErrorReportPathCodes[idx] == StatusCode)
               break;
         }
         if (idx >= rqptr->ServicePtr->ErrorReportPathCodesCount)
         {
            /* code not found, use standard error reporting */
            rqptr->rqResponse.ErrorReportByRedirect = false;
            rqptr->RedirectErrorStatusCode = 0;
            rqptr->RedirectErrorAuthRealmDescrPtr = NULL;
         }
      }

      if (rqptr->rqResponse.ErrorReportByRedirect)
      {
         /******************************************/
         /* redirect report, generate query string */
         /******************************************/

         ErrorRedirectQueryString (rqptr, StatusValue,
                                   ExplanationPtr, RecommendPtr,
                                   SourceModuleName, SourceLineNumber);
         return;
      }
   }

   /*******************/
   /* generate report */
   /*******************/

   vecptr = FaoVector;
   if (rqptr->rqPathSet.ReportDetailed)
      *vecptr++ = ErrorSourceInfo (SourceModuleName, SourceLineNumber);
   else 
   if (rqptr->rqPathSet.ReportBasic || Config.cfReport.BasicOnly)
      *vecptr++ = "";
   else 
      *vecptr++ = ErrorSourceInfo (SourceModuleName, SourceLineNumber);
   *vecptr++ = CategoryPtr = MsgFor(rqptr,MSG_STATUS_ERROR);
   *vecptr++ = StatusCode;
   *vecptr++ = HttpStatusCodeText(StatusCode);

   ErrorReportHeader (rqptr, &vecptr);

   *vecptr++ = CategoryPtr;
   *vecptr++ = StatusCode;
   *vecptr++ = HttpStatusCodeExplanation (rqptr,StatusCode);

   ErrorReportEndHeader (rqptr, &vecptr);

   if (!rqptr->rqPathSet.ReportDetailed &&
       (rqptr->rqPathSet.ReportBasic ||
        Config.cfReport.BasicOnly))
      *vecptr++ = "";
   else
   {
      *vecptr++ =
"<P>!AZ &nbsp;...&nbsp; !&@\n\
<!!-- sts: %X!XL \"!&;AZ\" -->\n\
!AZ";
      *vecptr++ = ExplanationPtr;
      /* if it looks like it starts with a path forward-slash */
      if (rqptr->rqResponse.ErrorTextPtr[0] == '/')
         *vecptr++ = "!&;&_AZ";
      else
         *vecptr++ = "!AZ";
      *vecptr++ = rqptr->rqResponse.ErrorTextPtr;
      *vecptr++ = StatusValue;
      *vecptr++ = rqptr->rqResponse.ErrorOtherTextPtr;
      *vecptr++ = RecommendPtr;
   }
   *vecptr++ = MsgFor(rqptr,MSG_STATUS_INFO);

   ErrorReportFooter (rqptr, &vecptr);

   ReportPtr = MsgFor(rqptr,MSG_STATUS_REPORT_FORMAT);
   status = WriteFaol (Buffer, sizeof(Buffer), &Length, ReportPtr, &FaoVector);
   if (VMSnok (status) || status == SS$_BUFFEROVF)
      ErrorNoticed (status, "WriteFao()", FI_LI);

   rqptr->rqResponse.ErrorReportPtr = VmGetHeap (rqptr, Length+1);
   memcpy (rqptr->rqResponse.ErrorReportPtr, Buffer, Length);
   rqptr->rqResponse.ErrorReportPtr[
      rqptr->rqResponse.ErrorReportLength = Length] = '\0';
}

/*****************************************************************************/
/*
Internal redirect error report, generate a query string containing all the
information required to generate an error report.
*/

ErrorRedirectQueryString
(
REQUEST_STRUCT *rqptr,
int StatusValue,
char *ExplanationPtr,
char *RecommendPtr,
char *SourceModuleName,
int SourceLineNumber
)
{
   int  status;
   unsigned short  Length;
   unsigned long  *vecptr;
   unsigned long  FaoVector [32];
   char  Buffer [4096];

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

   if (WATCHING(rqptr) && WATCH_MODULE(WATCH_MOD__OTHER))
      WatchThis (rqptr, FI_LI, WATCH_MOD__OTHER,
                 "ErrorRedirectQueryString()");

   vecptr = FaoVector;
   *vecptr++ = !rqptr->rqPathSet.ReportDetailed &&
               (rqptr->rqPathSet.ReportBasic ||
                Config.cfReport.BasicOnly) ? "basic" : "detailed",
   *vecptr++ = rqptr->rqResponse.HttpStatus;
   *vecptr++ = HttpStatusCodeText(rqptr->rqResponse.HttpStatus);
   *vecptr++ = HttpStatusCodeExplanation(rqptr,rqptr->rqResponse.HttpStatus);
   /* '_vms' is empty to indicate no VMS status code */
   if (StatusValue)
   {
      *vecptr++ = "!UL";
      *vecptr++ = StatusValue;
   }
   else
      *vecptr++ = "";
   if (rqptr->rqResponse.ErrorTextPtr)
      *vecptr++ = rqptr->rqResponse.ErrorTextPtr;
   else
      *vecptr++ = "";
   if (rqptr->rqResponse.ErrorOtherTextPtr)
      *vecptr++ = rqptr->rqResponse.ErrorOtherTextPtr;
   else
      *vecptr++ = "";
   *vecptr++ = ExplanationPtr;
   *vecptr++ = RecommendPtr;
   *vecptr++ = SourceModuleName;
   *vecptr++ = SourceLineNumber;
   *vecptr++ = rqptr->ServicePtr->ServerHostPort;

   if (rqptr->rqResponse.HttpStatus == 401 ||
       rqptr->rqResponse.HttpStatus == 407)
   {
      *vecptr++ = "&error_realm=!AZ!&@!&@";
      *vecptr++ = rqptr->rqAuth.RealmPtr;
      if (rqptr->rqAuth.BasicChallengePtr &&
          rqptr->rqAuth.BasicChallengePtr[0])
      {
         *vecptr++ = "&error_basic=!&%AZ";
         *vecptr++ = rqptr->rqAuth.BasicChallengePtr;
      }
      else
         *vecptr++ = "";
      if (rqptr->rqAuth.DigestChallengePtr &&
          rqptr->rqAuth.DigestChallengePtr[0])
      {
         *vecptr++ = "&error_digest=!&%AZ";
         *vecptr++ = rqptr->rqAuth.DigestChallengePtr;
      }
      else
         *vecptr++ = "";
   }
   else
      *vecptr++ = "";

   status = WriteFaol (Buffer, sizeof(Buffer), &Length,
"?httpd=error\
&error_type=!AZ\
&error_status=!UL\
&error_status_text=!&%AZ\
&error_status_explanation=!&%AZ\
&error_vms=!&@\
&error_about=!&%AZ\
&error_about2=!&%AZ\
&error_text=!&%AZ\
&error_text2=!&%AZ\
&error_module=!&%AZ\
&error_line=!UL\
&error_proxy=!&%AZ\
!&@",
      &FaoVector);

   if (VMSnok (status) || status == SS$_BUFFEROVF)
      ErrorNoticed (status, "WriteFao()", FI_LI);

   rqptr->rqResponse.ErrorReportLength = Length;
   rqptr->rqResponse.ErrorReportPtr = VmGetHeap (rqptr, Length+1);
   memcpy (rqptr->rqResponse.ErrorReportPtr, Buffer, Length+1);
}

/*****************************************************************************/
/*
Report general string overflow.
*/

ErrorGeneralOverflow
(
REQUEST_STRUCT *rqptr,
char *SourceModuleName,
int SourceLineNumber
)
{
   int  status;

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

   if (WATCHING(rqptr) && WATCH_MODULE(WATCH_MOD__OTHER))
      WatchThis (rqptr, FI_LI, WATCH_MOD__OTHER, "ErrorGeneralOverflow()");

   if (!rqptr->rqResponse.HttpStatus) rqptr->rqResponse.HttpStatus = 500;
   ErrorGeneral (rqptr, MsgFor(rqptr,MSG_GENERAL_OVERFLOW),
                 SourceModuleName, SourceLineNumber);
}

/*****************************************************************************/
/*
*/
 
ErrorInternal
(
REQUEST_STRUCT *rqptr,
int StatusValue,
char *Explanation,
char *SourceModuleName,
int SourceLineNumber
)
{
   /*********/
   /* begin */
   /*********/

   if (WATCHING(rqptr) && WATCH_MODULE(WATCH_MOD__OTHER))
      WatchThis (rqptr, FI_LI, WATCH_MOD__OTHER, "ErrorInternal()");

   rqptr->rqResponse.HttpStatus = 500;

   if (Explanation)
      ErrorGeneral (rqptr, Explanation, SourceModuleName, SourceLineNumber);
   else
      ErrorVmsStatus (rqptr, StatusValue, SourceModuleName, SourceLineNumber);
}

/*****************************************************************************/
/*
If the server is configured for it return a pointer to some META information
containing softwareID, source code module and line in which the error occured,
otherwise pointer to an empty string.  This function is not used reentrantly
and so provided the contents of the static buffer are used before it is
recalled it will continue to work. The source file format provided by the
"__FILE__" macro will be "device:[directory]name.type;ver".  Reduce that to
"name".  The "__LINE__" macro just provides an integer.
*/
 
char* ErrorSourceInfo
(
char *SourceModuleName,
int SourceLineNumber
) 
{
   static char  Buffer [256];

   int  status;
 
   /*********/
   /* begin */
   /*********/

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

   if (!Config.cfReport.MetaInfoEnabled) return ("");

   status = WriteFao (Buffer, sizeof(Buffer), NULL,
"<META NAME=\"generator\" CONTENT=\"!AZ\">\n\
<META NAME=\"module\" CONTENT=\"!AZ\">\n\
<META NAME=\"line\" CONTENT=\"!UL\">\n",
      SoftwareID, SourceModuleName, SourceLineNumber);

   if (VMSnok (status) || status == SS$_BUFFEROVF)
      ErrorExitVmsStatus (status, ErrorServerFao, FI_LI);

   return (Buffer);
}
 
/****************************************************************************/

