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


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

/* standard C header files */
#include <ctype.h>
#include <errno.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.  If a maximum number of 
characters are specified only those will be compared, if the entire strings 
should be compared then specify the number of characters as 0.
*/ 

boolean strsame
(
char *sptr1,
char *sptr2,
int  count
)
{
   while (*sptr1 && *sptr2)
   {
      if (toupper (*sptr1++) != toupper (*sptr2++)) return (false);
      if (count)
         if (!--count) return (true);
   }
   if (*sptr1 || *sptr2)
      return (false);
   else
      return (true);
}

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

