/*****************************************************************************/
/*
                               ReportError.c

A CGI-compliant script.
The greater efficiencies mean it's much better used as a CGIplus script!

Provided to work in with the WASD HTTPd [ErrorReportScript] configuration
parameter, this script is designed to be both a basic work-horse for
implementing a local error reporting format as well as a template and/or basis
for a local reporting script.  Configuration directives (either/or):

   [ErrorReportScript] /cgi-bin/reporterror

   [ErrorReportScript] /cgiplus-bin/reporterror

The /DEFAULT qualifier generates an error report much like that supplied by
native WASD HTTPd error reporting.


LOCALLY IMPLEMENTED REPORTING CODE
----------------------------------

Using the /HEADER qualifier this program can be called from a DCL procedure to
supply an appropriate (and correct) HTTP response header, IMPORTANTLY providing
a correct challenge for 401 errors (authorization required).  The procedure
could then go on to provide the body of the error report.

The /LOCAL qualifier results in the LocalReport() function being called.  This
function stub is provided as a simple place-holder for a locally implemented
error report.  ANY LOCAL VERSION MUST BE CAREFUL TO SUPPLY CORRECT RESPONSE
HEADERS FOR 401 ERRORS (authorization required) ... using the HttpHeader()
function is strongly recommended.  The "WWW_FORM_ERROR..." CGI variables listed
below supply relevant information specially generated by the HTTPd for error
reporting.  When developing a local version be sure to move the source code out
of the WASD_ROOT:[SRC.MISC] so it's not lost in some future update!


CGI VARIABLES
-------------

WWW_FORM_ERROR_ABOUT        item message is about (if applicable)
WWW_FORM_ERROR_ABOUT2       additional information (if applicable)
WWW_FORM_ERROR_BASIC        basic authentication challenge
WWW_FORM_ERROR_DIGEST       digest authentication challenge
WWW_FORM_ERROR_LINE         name of source code line (e.g. "1732")
WWW_FORM_ERROR_MODULE       name of source code module (e.g. "REQUEST")
WWW_FORM_ERROR_STATUS       HTTP status code (e.g. "404")
WWW_FORM_ERROR_STATUS_TEXT  brief description of status code (e.g. "Not Found")
WWW_FORM_ERROR_STATUS_EXPLANATION  longer description of status code
WWW_FORM_ERROR_TEXT         server-generated error message
WWW_FORM_ERROR_TEXT2        server suggestion about what to do about it :^)
WWW_FORM_ERROR_TYPE         "basic" or "detailed"
WWW_FORM_ERROR_VMS          VMS status value, decimal (if applicable)

WWW_HTTP_PRAGMA             "no-cache" (optional)
WWW_HTTP_REFERER            "close" button becomes active
WWW_PATH_INFO               URL path to shelf
WWW_PATH_TRANSLATED         VMS file specification for shelf
WWW_REQUEST_METHOD          any method
WWW_REQUEST_CHARSET         request determined character set
WWW_SCRIPT_NAME             path to script
WWW_SERVER_CHARSET          server default character set
WWW_SERVER_NAME             host on which the script is executing
WWW_SERVER_PORT             port from which the script is executing
WWW_SERVER_SIGNATURE        server's "signature"
WWW_SERVER_SOFTWARE         HTTPd identifying string


QUALIFIERS
----------
/CHARSET=       "Content-Type: text/html; charset=...", empty suppress charset
/DBUG           turns on all "if (Debug)" statements (don't use with OSU)
/DEFAULT        output the default error report
/HEADER         output an appropriate HTTP response header only
/LOCAL          execute the error reporting function ProcessLocal()
/MORE=          string added to bottom of default report (can be any HTML)


LOGICAL NAMES
-------------
REPORTERROR$DBUG       turns on all "if (Debug)" statements
REPORTERROR$PARAM      equivalent to (overrides) the command line
                       parameters/qualifiers (define as a system-wide logical)


BUILD DETAILS
-------------
See BUILD_REPORTERROR.COM procedure.


COPYRIGHT
---------
Copyright (C) 1998-2022 Mark G.Daniel

Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at

    http://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.


VERSION HISTORY (update SoftwareID as well!)
---------------
23-DEC-2003  MGD  v1.3.1, minor conditional mods to support IA64
28-OCT-2000  MGD  v1.3.0, use CGILIB object module
12-APR-2000  MGD  v1.2.0, reflect changes in WASD v7.0 report format
24-APR-1999  MGD  v1.1.0, use CGILIB.C
18-OCT-1998  MGD  v1.0.0, initial (with HTTPd v5.3 [ErrorReportPath])
*/
/*****************************************************************************/

#define SOFTWAREVN "1.3.1"
#define SOFTWARENM "REPORTERROR"
#ifdef __ALPHA
#  define SOFTWAREID SOFTWARENM " AXP-" SOFTWAREVN
#endif
#ifdef __ia64
#  define SOFTWAREID SOFTWARENM " IA64-" SOFTWAREVN
#endif
#ifdef __VAX
#  define SOFTWAREID SOFTWARENM " VAX-" SOFTWAREVN
#endif
#ifdef __x86_64
#  define SOFTWAREID SOFTWARENM " X86-" SOFTWAREVN
#endif

/* standard C header files */
#include <ctype.h>
#include <errno.h>
#include <lib$routines.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <time.h>
#include <unixio.h>

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

/* application header file */
#include "cgilib.h"

#define boolean int
#define true 1
#define false 0

#ifndef __VAX
#   pragma nomember_alignment
#endif

#define VMSok(x) ((x) & STS$M_SUCCESS)
#define VMSnok(x) (!((x) & STS$M_SUCCESS))
 
#define DEFAULT_CHARSET "ISO-8859-1"

#define HTML_BODY_TAG "<BODY>"

char  Utility [] = "REPORTERROR";

boolean  Debug,
         DoDefaultReport,
         DoLocalReport,
         HeaderOnly,
         HttpHasBeenOutput,
         IsCgiPlus,
         MethodHead,
         PragmaNoCache,
         ReportDetailed;

int  StatusClass,
     StatusCode;

char  *CgiEnvironmentPtr,
      *CgiFormErrorBasicPtr,
      *CgiFormErrorDigestPtr,
      *CgiFormErrorAboutPtr,
      *CgiFormErrorAbout2Ptr,
      *CgiFormErrorLinePtr,
      *CgiFormErrorModulePtr,
      *CgiFormErrorTextPtr,
      *CgiFormErrorText2Ptr,
      *CgiFormErrorStatusPtr,
      *CgiFormErrorStatusTextPtr,
      *CgiFormErrorStatusExplPtr,
      *CgiFormErrorTypePtr,
      *CgiFormErrorVmsPtr,
      *CgiHttpPragmaPtr,
      *CgiPathInfoPtr,
      *CgiPathTranslatedPtr,
      *CgiRequestMethodPtr,
      *CgiRequestTimeGmtPtr,
      *CgiScriptNamePtr,
      *CgiServerNamePtr,
      *CgiServerPortPtr,
      *CgiServerProtocolPtr,
      *CgiServerSignaturePtr,
      *CgiServerSoftwarePtr,
      *CgiTypePtr,
      *CharsetPtr,
      *CliCharsetPtr,
      *MoreInfoPtr = "",
      *PageFileNamePtr;

char  BasicChallenge [256],
      ContentTypeCharset [64],
      DigestChallenge [256],
      SoftwareID [48];

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

main ()

{
   char  *cptr;

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

   sprintf (SoftwareID, "%s (%s)", SOFTWAREID, CgiLibEnvironmentVersion());

   if (getenv ("REPORTERROR$DBUG") != NULL) Debug = true;
   CgiLibEnvironmentSetDebug (Debug);

   CgiLibEnvironmentInit (0, NULL, false);

   GetParameters ();

   CgiLibResponseSetSoftwareID (SoftwareID);
   CgiLibResponseSetErrorMessage ("Reported by reportERROR");

   IsCgiPlus = CgiLibEnvironmentIsCgiPlus ();

   /***********/
   /* process */
   /***********/

   if (IsCgiPlus)
   {
      for (;;)
      {
         /* block waiting for next request */
         CgiLibVar ("");
         ProcessRequest ();
         CgiLibCgiPlusEOF ();
      }
   }
   else
      ProcessRequest ();

   exit (SS$_NORMAL);
}

/*****************************************************************************/
/*
*/ 
 
ProcessRequest ()

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

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

   CgiEnvironmentPtr = CgiLibEnvironmentName ();

   HttpHasBeenOutput = false;

   CgiHttpPragmaPtr = CgiLibVar ("WWW_HTTP_PRAGMA");
   CgiPathInfoPtr = CgiLibVar ("WWW_PATH_INFO");
   CgiPathTranslatedPtr = CgiLibVar ("WWW_PATH_TRANSLATED");
   CgiRequestMethodPtr = CgiLibVar ("WWW_REQUEST_METHOD");
   CgiRequestTimeGmtPtr = CgiLibVar ("WWW_REQUEST_TIME_GMT");
   CgiScriptNamePtr = CgiLibVar ("WWW_SCRIPT_NAME");
   CgiServerNamePtr = CgiLibVar ("WWW_SERVER_NAME");
   CgiServerPortPtr = CgiLibVar ("WWW_SERVER_PORT");
   CgiServerSignaturePtr = CgiLibVar ("WWW_SERVER_SIGNATURE");
   CgiServerSoftwarePtr = CgiLibVar ("WWW_SERVER_SOFTWARE");
   CgiServerProtocolPtr = CgiLibVar ("WWW_SERVER_PROTOCOL");

   if ((CharsetPtr = CliCharsetPtr) == NULL)
   {
      CharsetPtr = CgiLibVar ("WWW_REQUEST_CHARSET");
      if (!CharsetPtr[0]) CharsetPtr = CgiLibVar ("WWW_SERVER_CHARSET");
      if (!CharsetPtr[0]) CharsetPtr = DEFAULT_CHARSET;
   }
   if (CharsetPtr[0])
      sprintf (ContentTypeCharset, "; charset=%s", CharsetPtr);
   else
      ContentTypeCharset[0] = '\0';

   if (!strcmp (CgiRequestMethodPtr, "HEAD"))
      MethodHead = true;
   else
      MethodHead = false;

   if (strsame (CgiHttpPragmaPtr, "no-cache", -1))
      PragmaNoCache = true;
   else
      PragmaNoCache = true;

   CgiFormErrorBasicPtr = CgiLibVar ("WWW_FORM_ERROR_BASIC");
   CgiFormErrorDigestPtr = CgiLibVar ("WWW_FORM_ERROR_DIGEST");
   CgiFormErrorAboutPtr = CgiLibVar ("WWW_FORM_ERROR_ABOUT");
   CgiFormErrorAbout2Ptr = CgiLibVar ("WWW_FORM_ERROR_ABOUT2");
   CgiFormErrorLinePtr = CgiLibVar ("WWW_FORM_ERROR_LINE");
   CgiFormErrorModulePtr = CgiLibVar ("WWW_FORM_ERROR_MODULE");
   CgiFormErrorStatusPtr = CgiLibVar ("WWW_FORM_ERROR_STATUS");
   CgiFormErrorStatusTextPtr = CgiLibVar ("WWW_FORM_ERROR_STATUS_TEXT");
   CgiFormErrorStatusExplPtr = CgiLibVar ("WWW_FORM_ERROR_STATUS_EXPLANATION");
   CgiFormErrorTextPtr = CgiLibVar ("WWW_FORM_ERROR_TEXT");
   CgiFormErrorText2Ptr = CgiLibVar ("WWW_FORM_ERROR_TEXT2");
   CgiFormErrorTypePtr = CgiLibVar ("WWW_FORM_ERROR_TYPE");
   CgiFormErrorVmsPtr = CgiLibVar ("WWW_FORM_ERROR_VMS");

   if (strsame (CgiFormErrorTypePtr, "detailed", -1))
      ReportDetailed = true;
   else
      ReportDetailed = false;

   StatusCode = atoi(CgiFormErrorStatusPtr);
   StatusClass = StatusCode / 100;

   /* if it's an authorization challenge */
   if (StatusCode == 401 ||
       StatusCode == 407)
   {
      if (CgiFormErrorBasicPtr[0])
         sprintf (BasicChallenge, "%s\r\n", CgiFormErrorBasicPtr);
      else
         BasicChallenge[0] = '\0';
      if (CgiFormErrorDigestPtr[0])
         sprintf (DigestChallenge, "%s\r\n", CgiFormErrorDigestPtr);
      else
         DigestChallenge[0] = '\0';
   }
   else
      BasicChallenge[0] = DigestChallenge[0] = '\0';

   if (HeaderOnly)
      HttpHeader ();
   else
   if (DoDefaultReport || strsame (CgiPathInfoPtr, "/default", -1))
      DefaultReport ();
   else
   if (DoLocalReport || strsame (CgiPathInfoPtr, "/local", -1))
      LocalReport ();
   else
      DefaultReport ();
}

/*****************************************************************************/
/*
Output a text/html HTTP response header complete with 401 basic and/or digest
authorization challenge if necessary.
*/

HttpHeader ()

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

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

   fprintf (stdout,
"%s %s %s\r\n\
Server: %s\r\n\
Date: %s\r\n\
%s\
%s\
Content-Type: text/html%s\r\n\
\r\n",
      CgiServerProtocolPtr, CgiFormErrorStatusPtr, CgiFormErrorStatusPtr,
      CgiServerSoftwarePtr, CgiRequestTimeGmtPtr,
      BasicChallenge, DigestChallenge,
      ContentTypeCharset);

   HttpHasBeenOutput = true;
}

/*****************************************************************************/
/*
This function will be called when the script is executed with /LOCAL qualifier.
Place any locally implemented error reporter here.  The DefaultReport() could
be used as a template.
*/

LocalReport ()

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

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

   /* of course remove this when a local reporter is implemented! */
   DefaultReport ();
}

/*****************************************************************************/
/*
Produce an error report that looks a lot like the WASD HTTPd native one!
(This code is based laregly on that in CGILIB.C CgiLibresponseError())
*/

DefaultReport ()

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

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

   HttpHeader ();

   if (MethodHead) return;

   fputs (
"<HTML>\n\
<HEAD>\n", stdout);

   if (ReportDetailed)
      fprintf (stdout,
"<META NAME=\"generator\" CONTENT=\"%s (%s)\">\n\
<META NAME=\"module\" CONTENT=\"%s\">\n\
<META NAME=\"line\" CONTENT=\"%s\">\n",
         CgiServerSoftwarePtr, SoftwareID,
         CgiFormErrorModulePtr,
         CgiFormErrorLinePtr);

   fprintf (stdout,
"<TITLE>%s %s %s</TITLE>\n\
</HEAD>\n\
%s\n\
<FONT SIZE=+1><B>%s %s</B> &nbsp;-&nbsp; %s</FONT>\n",
      (StatusCode >= 200 && StatusCode < 299) ? "SUCCESS" : "ERROR",
      CgiFormErrorStatusPtr,
      CgiFormErrorStatusTextPtr,
      HTML_BODY_TAG,
      (StatusCode >= 200 && StatusCode < 299) ? "SUCCESS" : "ERROR",
      CgiFormErrorStatusPtr,
      CgiFormErrorStatusExplPtr);

   if (ReportDetailed)
   {
      fprintf (stdout, "<P>%s", CgiFormErrorTextPtr);

      if (CgiFormErrorAboutPtr[0])
         fprintf (stdout, " ... %s\n", CgiFormErrorAboutPtr);
      else
         fputc ('\n', stdout);

      if (CgiFormErrorVmsPtr[0])
         fprintf (stdout, "<!-- sts: %s \"%s\" -->\n",
                  CgiFormErrorVmsPtr, CgiFormErrorAbout2Ptr);

      if (CgiFormErrorText2Ptr[0])
         fprintf (stdout, "%s\n", CgiFormErrorText2Ptr);
   }

   fprintf (stdout, "<P><HR WIDTH=85%% ALIGN=left SIZE=2 NOSHADE>\n");

   if (CgiServerSignaturePtr[0])
      fprintf (stdout, "%s\n", CgiServerSignaturePtr);

   fputs (
"</BODY>\n\
</HTML>\n",
      stdout);
}

/*****************************************************************************/
/*
Get "command-line" parameters, whether from the command-line or from a
configuration symbol or logical containing the equivalent.
*/

GetParameters ()

{
   static char  CommandLine [256];
   static unsigned long  Flags = 0;

   int  status;
   unsigned short  Length;
   char  ch;
   char  *aptr, *cptr, *clptr, *sptr;
   $DESCRIPTOR (CommandLineDsc, CommandLine);

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

   if ((clptr = getenv ("REPORTERROR$PARAM")) == NULL)
   {
      /* get the entire command line following the verb */
      if (VMSnok (status =
          lib$get_foreign (&CommandLineDsc, 0, &Length, &Flags)))
         exit (status);
      (clptr = CommandLine)[Length] = '\0';
   }

   aptr = NULL;
   ch = *clptr;
   for (;;)
   {
      if (aptr != NULL) *aptr = '\0';
      if (!ch) break;

      *clptr = ch;
      if (Debug) fprintf (stdout, "clptr |%s|\n", clptr);
      while (*clptr && isspace(*clptr)) *clptr++ = '\0';
      aptr = clptr;
      if (*clptr == '/') clptr++;
      while (*clptr && !isspace (*clptr) && *clptr != '/')
      {
         if (*clptr != '\"')
         {
            clptr++;
            continue;
         }
         cptr = clptr;
         clptr++;
         while (*clptr)
         {
            if (*clptr == '\"')
               if (*(clptr+1) == '\"')
                  clptr++;
               else
                  break;
            *cptr++ = *clptr++;
         }
         *cptr = '\0';
         if (*clptr) clptr++;
      }
      ch = *clptr;
      if (*clptr) *clptr = '\0';
      if (Debug) fprintf (stdout, "aptr |%s|\n", aptr);
      if (!*aptr) continue;

      if (strsame (aptr, "/CHARSET=", 4))
      {
         for (cptr = aptr; *cptr && *cptr != '='; cptr++);
         if (*cptr) cptr++;
         CliCharsetPtr = cptr;
         continue;
      }
      if (strsame (aptr, "/DBUG", -1))
      {
         Debug = true;
         continue;
      }
      if (strsame (aptr, "/DEFAULT", 4))
      {
         DoDefaultReport = true;
         continue;
      }
      if (strsame (aptr, "/HEADER", 4))
      {
         HeaderOnly = true;
         continue;
      }
      if (strsame (aptr, "/LOCAL", 4))
      {
         DoLocalReport = true;
         continue;
      }
      if (strsame (aptr, "/MORE=", 4))
      {
         for (cptr = aptr; *cptr && *cptr != '='; cptr++);
         if (*cptr) cptr++;
         MoreInfoPtr = cptr;
         continue;
      }

      if (*aptr != '/')
      {
         fprintf (stdout, "%%%s-E-MAXPARM, too many parameters\n \\%s\\\n",
                  Utility, aptr);
         exit (STS$K_ERROR | STS$M_INHIB_MSG);
      }
      else
      {
         fprintf (stdout, "%%%s-E-IVQUAL, unrecognized qualifier\n \\%s\\\n",
                  Utility, aptr+1);
         exit (STS$K_ERROR | STS$M_INHIB_MSG);
      }
   }
}

/****************************************************************************/
/*
Does a case-insensitive, character-by-character string compare and returns
true if two strings are the same, or false if not.
*/

boolean strsame
(
char *sptr1,
char *sptr2,
int  count
)
{
   /*********/
   /* begin */
   /*********/

   if (count > 0)
      return (strncasecmp (sptr1, sptr2, count) == 0);
   else
      return (strcasecmp (sptr1, sptr2) == 0);
}

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

