/*****************************************************************************/
/*
                                 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_URI          URI of original request
FORM_ERROR_VMS          VMS status value in decimal (if applicable)


VERSION HISTORY
---------------
22-SEP-2020  MGD  bugfix; ErrorReportFooter() use request heap for signature
20-JAN-2019  MGD  ErrorNoticed() extended to allow $FAO formatting
15-JUN-2019  MGD  ErrorNoticed() tweak NULL explanation from "" to "(none)"
30-JUN-2016  MGD  ErrorRedirectQueryString() ERROR_URI variable
29-APR-2015  MGD  bugfix; Error..() earlier and broader detection of WebDAV
10-MAY-2010  JPP  bugfix; do not use REDIRECT for WebDAV request error report
18-MAY-2008  MGD  bugfix; ErrorNoticed() use of 'rqptr' (from 16-NOV-2007)
16-NOV-2007  MGD  ErrorNoticed() add script/CGI data to OPCOM message
09-JUN-2007  MGD  use STR_DSC
17-SEP-2006  MGD  ErrorExitVmsStatus() add '-HTTPD-E-WHEN, <date/time>'
                  add '-HTTPD-E-STATUS, %X99999999' and restrict exit with
                  error status to the instance supervisor (as applicable) to
                  restrict image restart to the supervisor instance
16-JUL-2005  MGD  add selected request data to ErrorNoticed() report
14-JUL-2005  MGD  provide "Allow:" header with 405 (method not supported)
11-JUN-2005  MGD  allow report path to exclude using negative codes
12-AUG-2004  MGD  simple tunnel suitable messages
02-AUG-2004  MGD  error reports now have the response header generated
                  using the report buffer length, where possible, as a
                  content length (allowing connections to persist)
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 FaoToBuffer/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_V7
#undef _VMS__V6__SOURCE
#define _VMS__V6__SOURCE
#undef __VMS_VER
#define __VMS_VER 70000000
#undef __CRTL_VER
#define __CRTL_VER 70000000
#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;

int64  ErrorsNoticedTime64;

char  ErrorSanityCheck [] = "sanity check failure!",
      ErrorServerFao [] = "significant HTTPd sys$fao() failed.",
      ErrorSslFailure [] = "secure connection failure",
      ErrorXvalNotValid [] = "reverting to pre-V8.2 locking,";

char  *ErrorRecommendNotifyPtr;

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

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

extern BOOL  InstanceNodeSupervisor,
             LoggingEnabled;

extern int  InstanceNodeConfig,
            OpcomMessages;

extern int64  HttpdTime64;

extern char  HttpProtocol[],
             ResponseAllowResource[],
             ResponseAllowServer[],
             SoftwareID[];

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.
*/

void 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 (WATCHALL, 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 (SAME2(cptr,'!!'))
         {
            cptr += 2;
            continue;
         }
         if (*sptr != '!') break;
         if (SAME2(cptr,'!+'))
         {
            sptr++;
            if (SAME2(sptr,'AZ') ||
                SAME2(sptr,'UL') ||
                SAME2(sptr,'&@'))
            {
               cptr += 2;
               sptr += 2;
               continue;
            }
            break;
         }
         if (*cptr == '!' && SAME2(cptr+1,'%%') &&
             *sptr == '!' && SAME2(sptr+1,'&@'))
         {
            /* '%%' as <-7.3 backward compatible with 7.3-> '&@' */
            cptr += 3;
            sptr += 3;
            continue;
         }
         if ((*cptr == '!' && SAME2(cptr+1,'AZ') ||
              *cptr == '!' && SAME2(cptr+1,'%%') ||
              *cptr == '!' && SAME2(cptr+1,'&@')) &&
              *sptr == '!' && SAME2(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)
      {
         FaoToStdout ("%HTTPD-E-MSG, !AZ in \"!AZ\"\n \\!AZ\\\n",
                      sptr, FormatCheck[idx].ReportName,
                      *cptr ? cptr : "(end-of-string)");
         ErrorReported = true;
      }
   }

   if (ErrorReported)
   {
      if (OpcomMessages & OPCOM_HTTPD)
         FaoToOpcom ("%HTTPD-E-MSG, problem(s) with reports format");
      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 (SAME2(cptr,'%%') ||
       SAME2(cptr,'&@'))
       ErrorStatusReportFormatAbi = true;
   if (WATCH_MODULE(WATCH_MOD__OTHER))
      WatchThis (WATCHALL, 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.
*/

void ErrorExitVmsStatus
(
int StatusValue,
char *ExplanationPtr,
char *SourceModule,
int SourceLine
)
{
   /*********/
   /* begin */
   /*********/

   if (WATCH_MODULE(WATCH_MOD__OTHER))
      WatchThis (WATCHALL, WATCH_MOD__OTHER, "ErrorExitVmsStatus()");

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

   FaoToStdout (
"%HTTPD-F-SOFTWAREID, !AZ\n\
-HTTPD-F-WHEN, !%D\n\
-HTTPD-F-WHERE, module:!AZ line:!UL\n\
-HTTPD-F-WHAT, !AZ\n",
      SoftwareID, 0,
      SourceModule, SourceLine,
      ExplanationPtr);

   exit (StatusValue | STS$M_INHIB_MSG);
}

/*****************************************************************************/
/*
Report to the server process log, and if enabled to OPCOM, a non-fatal error
that has been noticed.  Add selected request data to the process log in an
effort to provide additional, pertinent information related to the report.
*/

void ErrorNoticed
(
REQUEST_STRUCT *rqptr,
int  StatusValue,
char *FaoPtr,
char *SourceModule,
int SourceLine,
...
)
{
   int  argcnt, hlen, llen, tlen, status;
   ushort  slen;
   ulong  *vecptr;
   ulong  FaoVector [32];
   char  *cptr, *httptr, *sptr;
   char  Buffer [4096];
   va_list  argptr;

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

   va_count (argcnt);

   if (WATCHMOD (rqptr, WATCH_MOD__OTHER))
      WatchThis (WATCHITM(rqptr), WATCH_MOD__OTHER,
                 "ErrorNoticed() !UL", argcnt);

   ErrorsNoticedCount++;
   ErrorsNoticedTime64 = HttpdTime64;

   if (!rqptr)
      httptr = "";
   else
   if (!rqptr->Http2Ptr)
      httptr = "HTTP/1, ";
   else
      httptr = "HTTP/2, ";

   if (argcnt > 5 + 32)
   {
      ErrorNoticed (rqptr, SS$_OVRMAXARG, "!AZ", FI_LI, FaoPtr);
      return;
   }

   if (!FaoPtr) FaoPtr = "(none)";

   if (argcnt > 5)
   {
      vecptr = FaoVector;
      va_start (argptr, SourceLine);
      for (argcnt -= 5; argcnt; argcnt--)
         *vecptr++ = va_arg (argptr, ulong);
      va_end (argptr);

      status = FaolToBuffer (Buffer, sizeof(Buffer), &slen, FaoPtr, &FaoVector);
      if (VMSnok (status) || status == SS$_BUFFEROVF)
      {
         ErrorNoticed (rqptr, status, "!AZ", FI_LI, FaoPtr);
         return;
      }

      FaoPtr = Buffer;
   }

   if (StatusValue)
   {
      if (WATCH_CAT && Watch.Category)
      {
         WatchThis (WATCHALL, WATCH_INTERNAL, "!AZ:!UL, !AZ!AZ%X!XL",
                    SourceModule, SourceLine,
                    FaoPtr, FaoPtr[0] ? " " : "", StatusValue);
         WatchDataFormatted ("%!&M\n", StatusValue);
      }

      FaoToStdout (
"%HTTPD-W-NOTICED, !20%D, !AZ!AZ:!UL, !AZ!AZ%X!XL\n-!-!&M\n",
         0, httptr, SourceModule, SourceLine,
         FaoPtr, FaoPtr[0] ? " " : "", StatusValue);

      FaoToOpcom (
"%HTTPD-W-NOTICED, !AZ!AZ:!UL, !AZ!AZ%X!XL\r\n-!-!&M",
         httptr, SourceModule, SourceLine,
         FaoPtr, FaoPtr[0] ? " " : "", StatusValue);
   }
   else
   {
      if (WATCH_CAT && Watch.Category)
         WatchThis (WATCHALL, WATCH_INTERNAL, "!AZ:!UL, !AZ",
                    SourceModule, SourceLine, FaoPtr);

      FaoToStdout ("%HTTPD-W-NOTICED, !20%D, !AZ!AZ:!UL, !AZ\n",
                   0, httptr, SourceModule, SourceLine, FaoPtr);

      if (rqptr && rqptr->ScriptName[0])
      {
         if (rqptr->rqCgi.HeaderPtr && rqptr->rqCgi.HeaderPtr[0])
         {
            for (sptr = cptr = rqptr->rqCgi.HeaderPtr; *sptr; sptr++);
            tlen = hlen = sptr - cptr;
            if (hlen > 24) hlen = 24;
            sptr = cptr;
            /* in case there is leading carriage-control */
            if (*sptr == '\r') sptr++;
            if (*sptr == '\n') sptr++;
            while (*sptr && *sptr != '\n') sptr++;
            llen = sptr - cptr;
            if (llen < hlen) llen = hlen;

            FaoToBuffer (Buffer, sizeof(Buffer), 0,
"!AZ\r\n\
-HTTPD-I-SCRIPT, !AZ (!AZ) !AZ\r\n\
-HTTPD-I-CGI, !#&H (!UL bytes) !#&.AZ",
                         FaoPtr,
                         rqptr->ScriptName,
                         rqptr->RequestMappedScript,
                         rqptr->RequestMappedRunTime,
                         hlen, cptr, tlen, llen, cptr);

            FaoPtr = Buffer;
         }
      }

      FaoToOpcom ("%HTTPD-W-NOTICED, !AZ!AZ:!UL, !AZ",
                  httptr, SourceModule, SourceLine, FaoPtr);
   }

   if (!rqptr) return;

   if (rqptr->RequestState >= REQUEST_STATE_SHUTDOWN) return;

   /****************/
   /* request data */
   /****************/

   if (rqptr->DclTaskPtr &&
       rqptr->DclTaskPtr->NextTaskFunction == &DclScriptProctorAst)
      FaoToStdout ("-NOTICED-I-PROCTOR, !UL+!UL !AZ!AZ!AZ!AZ !AZ!AZ!AZ\n",
                       rqptr->DclTaskPtr->ProctorPtr->NumberMin,
                       rqptr->DclTaskPtr->ProctorPtr->NumberIdle,
                       *rqptr->DclTaskPtr->ProctorPtr->RunTimePtr ? "(" : "",
                       rqptr->DclTaskPtr->ProctorPtr->RunTimePtr,
                       *rqptr->DclTaskPtr->ProctorPtr->RunTimePtr ? ")" : "",
                       rqptr->DclTaskPtr->ProctorPtr->ScriptPtr,
                       rqptr->DclTaskPtr->ProctorPtr->ActivatePtr,
                       *rqptr->DclTaskPtr->ProctorPtr->NotePadPtr ? " " : "",
                       rqptr->DclTaskPtr->ProctorPtr->NotePadPtr);

   if (rqptr->ServicePtr &&
       rqptr->ServicePtr->RequestSchemeNamePtr)
      FaoToStdout ("-NOTICED-I-SERVICE, !AZ//!AZ\n",
                   rqptr->ServicePtr->RequestSchemeNamePtr,
                   rqptr->ServicePtr->ServerHostPort);

   if (rqptr->ClientPtr &&
       &rqptr->ClientPtr->IpAddressString[0])
      FaoToStdout ("-NOTICED-I-CLIENT, !AZ\n", ClientHostString(rqptr));

    FaoToStdout ("-NOTICED-I-HTTP, !UL\n",
                 rqptr->Http2Ptr ? 2 : 1);

   if (rqptr->rqHeader.RequestUriPtr &&
       rqptr->rqHeader.RequestUriLength)
      FaoToStdout ("-NOTICED-I-URI, !AZ (!UL bytes) !AZ\n",
                   rqptr->rqHeader.MethodName,
                   rqptr->rqHeader.RequestUriLength,
                   rqptr->rqHeader.RequestUriPtr);

   if (rqptr->FileTaskPtr)
      if (rqptr->FileTaskPtr->FileNameLength)
         FaoToStdout ("-NOTICED-I-FILE, !#AZ\n",
                      rqptr->FileTaskPtr->FileNameLength,
                      rqptr->FileTaskPtr->FileName);

   if (rqptr->ScriptName[0])
   {
      FaoToStdout ("-NOTICED-I-SCRIPT, !AZ !AZ (!AZ) !AZ\n",
                   rqptr->ScriptName,
                   rqptr->RequestMappedScript,
                   rqptr->RequestMappedRunTime,
                   rqptr->rqCgi.ScriptFileNamePtr ?
                      rqptr->rqCgi.ScriptFileNamePtr : "");

      if (rqptr->rqCgi.HeaderPtr && rqptr->rqCgi.HeaderPtr[0])
      {
         for (sptr = cptr = rqptr->rqCgi.HeaderPtr; *sptr; sptr++);
         tlen = hlen = sptr - cptr;
         if (hlen > 24) hlen = 24;
         sptr = cptr;
         /* in case there is leading carriage-control */
         if (*sptr == '\r') sptr++;
         if (*sptr == '\n') sptr++;
         while (*sptr && *sptr != '\n') sptr++;
         llen = sptr - cptr;
         if (llen < hlen) llen = hlen;
         FaoToStdout ("-NOTICED-I-CGI, !#&H (!UL bytes) !#&.AZ\n",
                      hlen, cptr, tlen, llen, cptr);
      }
   }

   FaoToStdout ("-NOTICED-I-RXTX, err:!UL/!UL raw:!@UQ/!@UQ net:!@UQ/!@UQ\n",
                rqptr->rqNet.ReadErrorCount, rqptr->rqNet.WriteErrorCount,
                &rqptr->NetIoPtr->BytesRawRx64, &rqptr->NetIoPtr->BytesRawTx64,
                &rqptr->BytesRx64, &rqptr->BytesTx64);
}

/****************************************************************************/
/*
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.
*/

void 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,
        ContentLength;
   ushort  Length;
   ulong  *vecptr;
   ulong  FaoVector [32];
   char  *cptr, *sptr,
         *ResponseAllowPtr;
   char  Buffer [1024];

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

   if (WATCHMOD (rqptr, WATCH_MOD__OTHER))
      WatchThis (WATCHITM(rqptr), WATCH_MOD__OTHER, "ErrorSendToClient()");

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

   if (rqptr->rqPathSet.ReportType == ERROR_REPORT_TUNNEL ||
       rqptr->ServicePtr->ProxyTunnel == PROXY_TUNNEL_FIREWALL ||
       rqptr->ServicePtr->ProxyTunnel == PROXY_TUNNEL_RAW)
   {
      /******************/
      /* tunnel message */
      /******************/

      if (rqptr->rqPathSet.ReportType == ERROR_REPORT_TUNNEL)
         ResponseHeader (rqptr, rqptr->rqResponse.HttpStatus,
                         "text/plain", -1, NULL, NULL);

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

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

      return;
   }

   if (rqptr->rqResponse.HeaderGenerated &&
       STR_DSC_PTR(&rqptr->NetWriteBufferDsc))
   {
      /*******************/
      /* provide divider */
      /*******************/

      if (!DividerPtr)
      {
         status = FaoToBuffer (Buffer, sizeof(Buffer), &Length,
                               DividerFao, MsgFor(rqptr,MSG_STATUS_ERROR));
         if (VMSnok (status) || status == SS$_BUFFEROVF)
            ErrorNoticed (rqptr, status, NULL, 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 = FaoToBuffer (Buffer, sizeof(Buffer), &Length,
                            rqptr->ServicePtr->ErrorReportPath,
                            rqptr->rqResponse.HttpStatus);
      if (VMSnok (status) || status == SS$_BUFFEROVF)
         ErrorNoticed (rqptr, status, NULL, FI_LI);

      /* combine the redirection path and the error query string */
      Length += rqptr->rqResponse.ErrorReportLength;

      /* reserve space in the dictionary then build in situ */
      sptr = ResponseLocation (rqptr, NULL, Length);
      for (cptr = Buffer; *cptr; *sptr++ = *cptr++);
      for (cptr = rqptr->rqResponse.ErrorReportPtr; *cptr; *sptr++ = *cptr++);

      /* 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 */
   /***********************/

   if (!rqptr->rqResponse.HeaderGenerated &&
       !rqptr->rqResponse.HeaderSent)
      ContentLength = rqptr->rqResponse.ErrorReportLength;
   else
      ContentLength = -1;

   if (rqptr->rqResponse.HttpStatus == 405)
      ResponseAllowPtr = ResponseAllowResource;
   else
      ResponseAllowPtr = "";

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

   /* errors are always sent as the request is finalized */
   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.
*/

void ErrorMessageDividerAst (REQUEST_STRUCT *rqptr)

{
   int  status;

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

   if (WATCHMOD (rqptr, WATCH_MOD__OTHER))
      WatchThis (WATCHITM(rqptr), 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.
*/

void ErrorReportHeader
(
REQUEST_STRUCT *rqptr,
ulong  **vecptrptr
)
{
   ulong  *vecptr;

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

   if (WATCHMOD (rqptr, WATCH_MOD__OTHER))
      WatchThis (WATCHITM(rqptr), 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++ = REPORT_BODY_TAG;
      }

      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++ = REPORT_BODY_TAG;

   *vecptrptr = vecptr;
}

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

void ErrorReportEndHeader
(
REQUEST_STRUCT *rqptr,
ulong  **vecptrptr
)
{
   ulong  *vecptr;

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

   if (WATCHMOD (rqptr, WATCH_MOD__OTHER))
      WatchThis (WATCHITM(rqptr), WATCH_MOD__OTHER, "ErrorReportEndHeader()");

   vecptr = *vecptrptr;

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

   *vecptrptr = vecptr;
}

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

void ErrorReportFooter
(
REQUEST_STRUCT *rqptr,
ulong  **vecptrptr
)
{
#define SIG_SIZE 256

   ulong  *vecptr;
   char  *sptr;

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

   if (WATCHMOD (rqptr, WATCH_MOD__OTHER))
      WatchThis (WATCHITM(rqptr), WATCH_MOD__OTHER, "ErrorReportFooter()");

   if (Config.cfServer.Signature) sptr = VmGetHeap (rqptr, SIG_SIZE);

   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, sptr, SIG_SIZE);
      }
      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, sptr, SIG_SIZE);
   }
   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!
*/
 
void ErrorReportSuccess
(
REQUEST_STRUCT *rqptr,
char *ExplanationPtr,
...
)
{
   int  status,
        argcnt,
        StatusCode;
   ushort  Length;
   ulong  *vecptr;
   ulong  FaoVector [32];
   char  *cptr, *sptr, *zptr,
         *CategoryPtr,
         *ReportPtr;
   char  Buffer [4096];
   va_list  argptr;

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

   va_count (argcnt);

   if (WATCHMOD (rqptr, WATCH_MOD__OTHER))
      WatchThis (WATCHITM(rqptr), WATCH_MOD__OTHER,
                 "ErrorReportSuccess() !UL", argcnt);

   if (argcnt > 16)
   {
      ErrorNoticed (rqptr, SS$_OVRMAXARG, NULL, FI_LI);
      return;
   }

   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_RESPONSE))
   {
      cptr = "default";
      switch (rqptr->rqPathSet.ReportType)
      {
         case ERROR_REPORT_BASIC : cptr = "basic"; break;
         case ERROR_REPORT_DETAILED : cptr = "detailed"; break;
         case ERROR_REPORT_TUNNEL : cptr = "tunnel"; break;
         default: if (Config.cfReport.BasicOnly) cptr = "basic-only";
      }
      WatchThis (WATCHITM(rqptr), WATCH_RESPONSE,
                 "SUCCESS (!AZ) !3ZL \"!AZ\"",
                 cptr, StatusCode, ExplanationPtr);
   }
                
   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, ulong);
   va_end (argptr);
   *vecptr++ = MsgFor(rqptr,MSG_STATUS_INFO);

   ErrorReportFooter (rqptr, &vecptr);

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

   ResponseHeader (rqptr, 200, "text/html", Length, NULL, NULL);

   /* 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.
*/
 
void ErrorGeneral
(
REQUEST_STRUCT *rqptr,
char *ExplanationPtr,
...
)
{
   BOOL  StandardReport;
   int  idx, status,
        argcnt,
        OriginalStatusCode,
        SourceLine,
        StatusCode;
   ushort  Length;
   ulong  *vecptr;
   ulong  FaoVector [32];
   char  *cptr, *sptr, *zptr,
         *CategoryPtr,
         *ReportPtr,
         *SourceModule;
   char  Buffer [4096];
   va_list  argptr;

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

   va_count (argcnt);

   if (WATCHMOD (rqptr, WATCH_MOD__OTHER))
      WatchThis (WATCHITM(rqptr), WATCH_MOD__OTHER,
                 "ErrorGeneral() !UL", argcnt);

   if (argcnt > 16)
   {
      ErrorNoticed (rqptr, SS$_OVRMAXARG, NULL, FI_LI);
      return;
   }

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

   /* not if WebDAV is involved */
   if (rqptr->WebDavRequest || rqptr->WhiffOfWebDav || rqptr->WebDavTaskPtr)
      rqptr->rqResponse.ErrorReportByRedirect = false;

   /* 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.HeaderGenerated &&
       STR_DSC_PTR(&rqptr->NetWriteBufferDsc))
   {
      /* 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.ReportType = ERROR_REPORT_BASIC;
   }
   else
   if ((rqptr->rqResponse.HttpStatus == 403 ||
        rqptr->rqResponse.HttpStatus == 409) &&
       rqptr->rqPathSet.Report403as)
   {
      rqptr->rqResponse.HttpStatus = rqptr->rqPathSet.Report403as;
      rqptr->rqPathSet.ReportType = ERROR_REPORT_BASIC;
   }
   else
   if ((rqptr->rqResponse.HttpStatus == 404 ||
        rqptr->rqResponse.HttpStatus == 409) &&
       rqptr->rqPathSet.Report404as)
   {
      rqptr->rqResponse.HttpStatus = rqptr->rqPathSet.Report404as;
      rqptr->rqPathSet.ReportType = ERROR_REPORT_BASIC;
   }

   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)
         SourceModule = va_arg (argptr, ulong);
      else
      if (argcnt == 1)
         SourceLine = va_arg (argptr, ulong);
      else
         va_arg (argptr, ulong);
   }
   va_end (argptr);

   if (WATCHING (rqptr, WATCH_ERROR))
   {
      cptr = "default";
      switch (rqptr->rqPathSet.ReportType)
      {
         case ERROR_REPORT_BASIC : cptr = "basic"; break;
         case ERROR_REPORT_DETAILED : cptr = "detailed"; break;
         case ERROR_REPORT_TUNNEL : cptr = "tunnel"; break;
         default: if (Config.cfReport.BasicOnly) cptr = "basic-only";
      }
      WatchThis (WATCHITM(rqptr), WATCH_RESPONSE,
                 "!AZ:!UL (!AZ) !3ZL(!3ZL) \"!AZ\"",
                 SourceModule, SourceLine, cptr,
                 StatusCode, OriginalStatusCode, ExplanationPtr);
   }

   if (rqptr->rqPathSet.ReportType == ERROR_REPORT_TUNNEL ||
       rqptr->ServicePtr->ProxyTunnel == PROXY_TUNNEL_FIREWALL ||
       rqptr->ServicePtr->ProxyTunnel == PROXY_TUNNEL_RAW)
   {
      /******************/
      /* tunnel message */
      /******************/

      va_count (argcnt);
      if (argcnt <= 4)
         cptr = "!UL !AZ - !AZ\r\n";
      else
         cptr = "!UL !AZ - !&@\r\n";

      vecptr = FaoVector;

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

      status = FaolToBuffer (Buffer, sizeof(Buffer), &Length, cptr, &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+1);

      return;
   }

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

      if (rqptr->ServicePtr->ErrorReportPathCodesCount)
      {
         /* check for handled error code */
         StandardReport = true;
         for (idx = 0;
              idx < rqptr->ServicePtr->ErrorReportPathCodesCount;
              idx++)
         {
            if (rqptr->ServicePtr->ErrorReportPathCodes[idx] >= 0)
            {
               /* included HTTP status code */
               if (rqptr->ServicePtr->ErrorReportPathCodes[idx] == StatusCode)
               {
                  StandardReport = false;
                  break;
               }
            }
            else
            {
               /* excluded HTTP status code */
               StandardReport = false;
               if (rqptr->ServicePtr->ErrorReportPathCodes[idx] == -StatusCode)
               {
                  StandardReport = true;
                  break;
               }
            }
         }
         if (StandardReport)
         {
            /* 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, ulong);
         va_end (argptr);

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

         ErrorRedirectQueryString (rqptr, 0, Buffer, "",
                                   SourceModule, SourceLine);
         return;
      }
   }

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

   va_count (argcnt);

   vecptr = FaoVector;
   if (rqptr->rqPathSet.ReportType == ERROR_REPORT_DETAILED)
      *vecptr++ = ErrorSourceInfo (SourceModule, SourceLine);
   else
   if (rqptr->rqPathSet.ReportType == ERROR_REPORT_BASIC ||
       Config.cfReport.BasicOnly)
      *vecptr++ = "";
   else
      *vecptr++ = ErrorSourceInfo (SourceModule, SourceLine);
   *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.ReportType != ERROR_REPORT_DETAILED &&
       (rqptr->rqPathSet.ReportType == ERROR_REPORT_BASIC ||
        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, ulong);
      va_end (argptr);
   }
   *vecptr++ = MsgFor(rqptr,MSG_STATUS_INFO);

   ErrorReportFooter (rqptr, &vecptr);

   ReportPtr = MsgFor(rqptr,MSG_STATUS_REPORT_FORMAT);
   status = FaolToBuffer (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+1);
}

/*****************************************************************************/
/*
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.
*/

void ErrorVmsStatus
(
REQUEST_STRUCT *rqptr,
int StatusValue,
char *SourceModule,
int SourceLine
)
{
   BOOL  StandardReport;
   int  idx, status,
        OriginalStatusCode,
        StatusCode;
   ushort  Length;
   ushort  ShortLength;
   ulong  *vecptr;
   ulong  FaoVector [32];
   char  *cptr, *sptr, *zptr,
         *CategoryPtr,
         *ContentTypePtr,
         *ExplanationPtr,
         *RecommendPtr,
         *ReportPtr;
   char  Message [256],
         Buffer [4096];
   $DESCRIPTOR (MessageDsc, Message);

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

   if (WATCHMOD (rqptr, WATCH_MOD__OTHER))
      WatchThis (WATCHITM(rqptr), WATCH_MOD__OTHER, "ErrorVmsStatus()");
                                 
   /* don't overwrite any existing error message */
   if (ERROR_REPORTED (rqptr)) return;

   /* not if WebDAV is involved */
   if (rqptr->WebDavRequest || rqptr->WhiffOfWebDav || rqptr->WebDavTaskPtr)
      rqptr->rqResponse.ErrorReportByRedirect = false;

   /* 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.HeaderGenerated &&
       STR_DSC_PTR(&rqptr->NetWriteBufferDsc))
   {
      /* 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.ReportType = ERROR_REPORT_BASIC;
   }
   else
   if ((rqptr->rqResponse.HttpStatus == 403 ||
        rqptr->rqResponse.HttpStatus == 409) &&
       rqptr->rqPathSet.Report403as)
   {
      rqptr->rqResponse.HttpStatus = rqptr->rqPathSet.Report403as;
      rqptr->rqPathSet.ReportType = ERROR_REPORT_BASIC;
   }
   else
   if ((rqptr->rqResponse.HttpStatus == 404 ||
        rqptr->rqResponse.HttpStatus == 409) &&
       rqptr->rqPathSet.Report404as)
   {
      rqptr->rqResponse.HttpStatus = rqptr->rqPathSet.Report404as;
      rqptr->rqPathSet.ReportType = ERROR_REPORT_BASIC;
   }

   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 = to_upper(*cptr);

      /* don't allow access violation to be confused with forbidden access! */
      if (StatusValue == SS$_ACCVIO)
      {
         while (*cptr && *cptr != ',') cptr++;
         if (*cptr) strzcpy (cptr, " (segmentation fault)", 22);
         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
      strzcpy (Message, "*ERROR* sys$getmsg()", sizeof(Message));

   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_ERROR))
   {
      cptr = "default";
      switch (rqptr->rqPathSet.ReportType)
      {
         case ERROR_REPORT_BASIC : cptr = "basic"; break;
         case ERROR_REPORT_DETAILED : cptr = "detailed"; break;
         case ERROR_REPORT_TUNNEL : cptr = "tunnel"; break;
         default: if (Config.cfReport.BasicOnly) cptr = "basic-only";
      }
      WatchThis (WATCHITM(rqptr), WATCH_RESPONSE,
                 "!AZ:!UL (!AZ) !3ZL(!3ZL) !&S \"!AZ\" \"!AZ\" \"!AZ\"",
                 SourceModule, SourceLine, cptr,
                 StatusCode, OriginalStatusCode, StatusValue,
                 ExplanationPtr, rqptr->rqResponse.ErrorTextPtr,
                 rqptr->rqResponse.ErrorOtherTextPtr);
   }

   if (rqptr->rqPathSet.ReportType == ERROR_REPORT_TUNNEL ||
       rqptr->ServicePtr->ProxyTunnel == PROXY_TUNNEL_FIREWALL ||
       rqptr->ServicePtr->ProxyTunnel == PROXY_TUNNEL_RAW)
   {
      /******************/
      /* tunnel message */
      /******************/

      vecptr = FaoVector;
      *vecptr++ = StatusCode;
      *vecptr++ = HttpStatusCodeText(StatusCode);
      *vecptr++ = ExplanationPtr;
      *vecptr++ = rqptr->rqResponse.ErrorTextPtr;

      status = FaolToBuffer (Buffer, sizeof(Buffer), &Length,
                          "!UL !AZ - !AZ ... !AZ\r\n", &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+1);

      return;
   }

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

      if (rqptr->ServicePtr->ErrorReportPathCodesCount)
      {
         /* check for handled error code */
         StandardReport = true;
         for (idx = 0;
              idx < rqptr->ServicePtr->ErrorReportPathCodesCount;
              idx++)
         {
            if (rqptr->ServicePtr->ErrorReportPathCodes[idx] >= 0)
            {
               /* included HTTP status code */
               if (rqptr->ServicePtr->ErrorReportPathCodes[idx] == StatusCode)
               {
                  StandardReport = false;
                  break;
               }
            }
            else
            {
               /* excluded HTTP status code */
               StandardReport = false;
               if (rqptr->ServicePtr->ErrorReportPathCodes[idx] == -StatusCode)
               {
                  StandardReport = true;
                  break;
               }
            }
         }
         if (StandardReport)
         {
            /* 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,
                                   SourceModule, SourceLine);
         return;
      }
   }

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

   vecptr = FaoVector;
   if (rqptr->rqPathSet.ReportType == ERROR_REPORT_DETAILED)
      *vecptr++ = ErrorSourceInfo (SourceModule, SourceLine);
   else 
   if (rqptr->rqPathSet.ReportType == ERROR_REPORT_BASIC ||
       Config.cfReport.BasicOnly)
      *vecptr++ = "";
   else 
      *vecptr++ = ErrorSourceInfo (SourceModule, SourceLine);
   *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.ReportType != ERROR_REPORT_DETAILED &&
       (rqptr->rqPathSet.ReportType == ERROR_REPORT_BASIC ||
        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 = FaolToBuffer (Buffer, sizeof(Buffer), &Length, ReportPtr, &FaoVector);
   if (VMSnok (status) || status == SS$_BUFFEROVF)
      ErrorNoticed (rqptr, status, NULL, FI_LI);

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

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

void ErrorRedirectQueryString
(
REQUEST_STRUCT *rqptr,
int StatusValue,
char *ExplanationPtr,
char *RecommendPtr,
char *SourceModule,
int SourceLine
)
{
   int  status;
   ushort  Length;
   ulong  *vecptr;
   ulong  FaoVector [32];
   char  Buffer [16384];

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

   if (WATCHMOD (rqptr, WATCH_MOD__OTHER))
      WatchThis (WATCHITM(rqptr), WATCH_MOD__OTHER,
                 "ErrorRedirectQueryString()");

   vecptr = FaoVector;
   *vecptr++ = rqptr->rqPathSet.ReportType != ERROR_REPORT_DETAILED &&
               (rqptr->rqPathSet.ReportType == ERROR_REPORT_BASIC ||
                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++ = "";
   *vecptr++ = rqptr->rqHeader.RequestUriPtr;
   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++ = SourceModule;
   *vecptr++ = SourceLine;
   *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 = FaolToBuffer (Buffer, sizeof(Buffer), &Length,
"?httpd=error\
&error_type=!AZ\
&error_status=!UL\
&error_status_text=!&%AZ\
&error_status_explanation=!&%AZ\
&error_vms=!&@\
&error_uri=!&%AZ\
&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 (rqptr, status, NULL, FI_LI);

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

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

void ErrorGeneralOverflow
(
REQUEST_STRUCT *rqptr,
char *SourceModule,
int SourceLine
)
{
   int  status;

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

   if (WATCHMOD (rqptr, WATCH_MOD__OTHER))
      WatchThis (WATCHITM(rqptr), WATCH_MOD__OTHER, "ErrorGeneralOverflow()");

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

/*****************************************************************************/
/*
*/
 
void ErrorInternal
(
REQUEST_STRUCT *rqptr,
int StatusValue,
char *ExplanationPtr,
char *SourceModule,
int SourceLine
)
{
   /*********/
   /* begin */
   /*********/

   if (WATCHMOD (rqptr, WATCH_MOD__OTHER))
      WatchThis (WATCHITM(rqptr), WATCH_MOD__OTHER, "ErrorInternal()");

   rqptr->rqResponse.HttpStatus = 500;

   if (ExplanationPtr)
      ErrorGeneral (rqptr, ExplanationPtr, SourceModule, SourceLine);
   else
      ErrorVmsStatus (rqptr, StatusValue, SourceModule, SourceLine);
}

/*****************************************************************************/
/*
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 *SourceModule,
int SourceLine
) 
{
   static char  Buffer [256];

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

   if (WATCH_MODULE(WATCH_MOD__OTHER))
      WatchThis (WATCHALL, WATCH_MOD__OTHER, "ErrorSourceInfo()");

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

   status = FaoToBuffer (Buffer, sizeof(Buffer), NULL,
"<meta name=\"generator\" content=\"!AZ\">\n\
<meta name=\"module\" content=\"!AZ\">\n\
<meta name=\"line\" content=\"!UL\">\n",
      SoftwareID, SourceModule, SourceLine);

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

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

