/*****************************************************************************/
/*
                                 proxyMUNGE.c

Munges (rewrites) URIs/URLs to provide an effective reverse-proxy, including:

  o  response header "Location:" field
  o  response header "Set-Cookie:" field path and domain components
  o  HTML document
       *  "href=" tag attributes
       *  "src=" tag attributes
       *  "background=" tag attributes
       *  "action=" (form) tag attributes
       *  "code=" (applet, object) tag attributes
       *  "codebase=" (applet, object) tag attributes
       *  "archive=" (object) tag attributes
       *  "data=" (object) tag attributes
       *  "usemap=" (object) tag attributes
  o  HTML tag attributes must be in the form
       *  attrib="data"
       *  attrib='data'
       *  attrib=data
  o  HTML "style=" attribute
       *  as per CSS document
  o  CSS document
       *  url("..") and url(..) property  

Does not munge JavaScript.

Supports reverse-proxy to http:// and https:// (if built against SSL).

Operates as a CGIplus or standard CGI script.


CONFIGURATION
-------------
To avoid odd behaviours some care must be taken to include all the relevant
subtleties of this configuration (the '?', 'script=query-relaxed').

  # HTTPD$MAP
  [[the.reverse-proxy.service:port]]
  redirect /whatever/* /proxymunge/http://the.internal.host/*?
  set /proxymunge/http://the.internal.host/* script=params=\
  (REVERSE="/whatever",HOST="another.internal.host=/another",\
  DOMAIN="internal.host.name=external.host.name")
  script+ /proxymunge/* /cgi-bin/proxymunge/* map=once script=query=relaxed

  # HTTPD$AUTH
  [opaque]
  /proxymunge/* r+w

The REVERSE parameter in the configuration below is used to rewrite URLs/URIs
in HTML documents returned to the client so that they may be accessed via the
proxy script.  A single REVERSE parameter must be supplied with each activation.

The HOST parameter is used to replace the host[:port] component of a URL
with the specified string (might be another reverse-proxy path).  The HOST
parameter may contain multiple  'match=substitute' by comma-separation.

  script=params=HOST=("one=two","three=four")

The DOMAIN parameter is used to replace the domain component of a "Set-Cookie:"
response header field.  The DOMAIN parameter may contain multiple
'match=substitute' by comma-separation.

  script=params=DOMAIN=("one=two","three=four")

This second example just illustrates how to extend the basic configuration to
support reverse-proxies into multiple internal hosts.

  # HTTPD$MAP
  [[the.reverse-proxy.service:port]]
  redirect /one/* /proxymunge/http://one.internal.host/*?
  redirect /two/* /proxymunge/http://two.internal.host/*?
  #
  set /proxymunge/http://one.internal.host/* script=params=\
  (REVERSE="/one",HOST="two.internal.host=/two",\
  DOMAIN="one.host.name=external.host.name")
  #
  set /proxymunge/http://two.internal.host/* script=params=\
  (REVERSE="/two",HOST="one.internal.host=/one",\
  DOMAIN="two.host.name=external.host.name")
  #
  script+ /proxymunge/* /cgi-bin/proxymunge/* map=once script=query=relaxed


COMMAND-LINE PARAMETERS
-----------------------
/DBUG                    low level debug statements
/WATCH                   output WASD server WATCH-able statements

/SSLV23                  support SSL version 2 or 3 (default is 3 only)
                         (no longer supported with OpenSSL 1.0.n and later)

/TLS1                    support TLSv1 (deprecated with OpenSSL 1.1.n)
/TLS11                   support TLSv1.1 (OpenSSL 1.0.n and later)
/TLS12                   support TLSv1.2 (OpenSSL 1.0.n and later)
/TLS13                   support TLSv1.3 (OpenSSL 1.1.1 and later)
(if multiple /TLS qualifiers then lowest and highest become range)


LOGICAL NAMES
-------------
PROXYMUNGE$DBUG       same as /DBUG
PROXYMUNGE$WATCH      same as /WATCH
PROXYMUNGE$PARAM      same as CLI


BUILD DETAILS
-------------
Compile then link:
  $ @BUILD_PROXYMUNGE BUILD [<root directory of WASD OpenSSL package>]
To just link:
  $ @BUILD_PROXYMUNGE LINK [<root directory of WASD OpenSSL package>]


COPYRIGHT
---------
Copyright (C) 2007-2020 Mark G.Daniel
This program, comes with ABSOLUTELY NO WARRANTY.
This is free software, and you are welcome to redistribute it
under the conditions of the GNU GENERAL PUBLIC LICENSE, version 2.


VERSION HISTORY (update SOFTWAREVN as well)
---------------
25-FEB-2020  MGD  v2.1.0, add /TLS.. qualifiers
23-FEB-2020  MGD  v2.0.0, adapt to OpenSSL v1.1.n and later
22-SEP-2007  MGD  v1.0.0, initial development
*/

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

#define COPYRIGHT_DATE "2007-2020"
#define SOFTWAREVN "2.1.0"
#define SOFTWARENM "PROXYMUNGE"
#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
#define SOFTWARECR "Copyright (C) " COPYRIGHT_DATE " Mark G.Daniel"

#ifndef MUNGE_VIA_SSL
/* default is to build without SSL support */
#define MUNGE_VIA_SSL 0
#endif

#ifndef __VAX
#   pragma nomember_alignment
#endif

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

/* Internet-related header files */
#include <socket.h>
#include <in.h>
#include <netdb.h>
#include <inet.h>

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

/* OpenSSL header files */
#if MUNGE_VIA_SSL
#include <openssl/crypto.h>
#include <openssl/ssl.h>
#include <openssl/err.h>
#endif

/* application header files */
#include <cgilib.h>

#define VMSok(x) ((x) & STS$M_SUCCESS)
#define VMSnok(x) !(((x) & STS$M_SUCCESS))

#define BOOL int
#define TRUE 1
#define FALSE 0

#define FOUTS(from,to) { \
   if (to > from) { \
      char  ch; \
      ch = *(to); \
      *(to) = '\0'; \
      fputs (from,stdout); \
      *(to) = ch; \
   } \
}

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

char  CopyrightDate [] = COPYRIGHT_DATE,
      SoftwareCopy [] = SOFTWARECR,
      Utility [] = "PROXYMUNGE";

BOOL  CliSSLv23,
      CliTLSv1,
      CliTLSv11,
      CliTLSv12,
      CliTLSv13,
      Debug,
      HttpScheme,
      HttpsScheme,
      IsCgiPlus,
      ResponseContentOpaque,
      ResponseContentTextHtml,
      ResponseContentTextCss,
      WatchEnabled;

int  CgiGatewayMrs,
     CgiPlusUsageCount,
     CgiParamDomainLength,
     CgiParamInternalLength,
     CgiParamReverseLength,
     ReadBufferCount,
     ReadBufferSize,
     ResponseBodyCount,
     ResponseBodySize,
     ResponseContentLength,
     ResponseHeaderCount,
     ResponseHeaderSize,
     ResponseBodyLength,
     ServerSocket,
     ServerPortNumber;

struct sockaddr_in  ServerSockAddr;

char  *CgiContentLengthPtr,
      *CgiContentTypePtr,
      *CgiEnvironmentPtr,
      *CgiPathInfoPtr,
      *CgiQueryStringPtr,
      *CgiRequestMethodPtr,
      *CgiParamDomainPtr,
      *CgiParamHostPtr,
      *CgiParamReversePtr,
      *CgiGatewayMrsPtr,
      *MungeDomainPtr,
      *MungeHostPtr,
      *ReadBufferPtr,
      *RequestUriPtr,
      *ResponseHeaderPtr,
      *ResponseBodyPtr;

char  ServerHostName [128],
      ServerHostPort [128+16],
      ServerPort [16],
      SoftwareEnv [128];

#if MUNGE_VIA_SSL

#define OPENSSL_SINCE_111 (OPENSSL_VERSION_NUMBER >= 0x10101000L)
#define OPENSSL_BEFORE_111 (OPENSSL_VERSION_NUMBER < 0x10101000L)
#define OPENSSL_SINCE_110 (OPENSSL_VERSION_NUMBER >= 0x10100000L)
#define OPENSSL_BEFORE_110 (OPENSSL_VERSION_NUMBER < 0x10100000L)

SSL_CTX  *SslContext;
SSL      *SslServer;
SSL_METHOD  *SslMethod;
BIO      *BioServer;

#endif

/***********************/
/* function prototypes */
/***********************/

char* GetCgiVar (char*);
char* GetCgiVarNull (char*);
void ErrorExit (int, int);
void GetParameters ();
void MungeCssText (char*, char*);
char* MungeDomain (char*);
char* MungeUrl (char*, char);
void ProcessRequest ();
void ReturnCssResponseBody ();
void ReturnHtmlResponseBody ();
void ReturnHttpResponseHeader ();
char* SpanQuoted (char*, char*);
char* SpanToEndTag (char*, char*, char*);
char* SpanToTagClose (char*, char*);
char* SysTrnLnm (char*, char*);
char* StatTimer (BOOL);
int WatchThis (int, char*, ...);
int WatchDump (char*, int);
BOOL strsame (char*, char*, int);

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

main ()

{
   int  minTLS, maxTLS;
   char  *cptr;
   unsigned char  RandBits[32];

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

   CgiEnvironmentPtr = CgiLibEnvironmentVersion();

   sprintf (SoftwareEnv, "%s, %s, %s",
            SOFTWAREID, CgiEnvironmentPtr, BUILD_DATETIME);

   Debug = (SysTrnLnm ("PROXYMUNGE$DBUG", NULL) != NULL);
   if (Debug)
   {
      fprintf (stdout,
"Content-Type: text/plain\nScript-Control: X-content-encoding-gzip=0\n\n");
      CgiLibEnvironmentSetDebug (1);
   }

   GetParameters ();

   CgiLibEnvironmentInit (0, NULL, FALSE);

   IsCgiPlus = CgiLibEnvironmentIsCgiPlus ();

#if MUNGE_VIA_SSL

   SSL_library_init ();
   OpenSSL_add_all_algorithms();
   SSL_load_error_strings ();

   /*
       http://www.openssl.org/support/faq.html
      'Why do I get a "PRNG not seeded" error message?'
   */
   sys$gettim (&RandBits);
   sys$numtim (&RandBits[8], &RandBits);
   memmove (&RandBits[sizeof(RandBits)/2], &RandBits[3], sizeof(RandBits)/2);

#if OPENSSL_SINCE_110 

/* 23-FEB-2020  MGD  linking against v1.1.1c (at least) required this */
#pragma names save
#pragma names as_is
   RAND_seed (&RandBits, sizeof(RandBits));
#pragma names restore

   ERR_clear_error();
   SslMethod = (SSL_METHOD*)TLS_client_method();
   SslContext = SSL_CTX_new (SslMethod);
   if (!SslContext)
   {
      fprintf (stdout, "Status: 500\n\n[line:%d] ", __LINE__);
      ERR_print_errors_fp (stdout);
      ErrorExit (SS$_BUGCHECK, __LINE__);
   }

   minTLS = TLS1_1_VERSION;
#if OPENSSL_SINCE_111 
   maxTLS = TLS1_3_VERSION;
#else
   maxTLS = TLS1_2_VERSION;
#endif

   if (CliTLSv1)
      minTLS = TLS1_VERSION;
   else
   if (CliTLSv11)
      minTLS = TLS1_1_VERSION;
   else
   if (CliTLSv12)
      minTLS = TLS1_2_VERSION;
#if OPENSSL_SINCE_111 
   else
   if (CliTLSv13)
      minTLS = TLS1_3_VERSION;
#endif

#if OPENSSL_SINCE_111 
   if (CliTLSv13)
      maxTLS = TLS1_3_VERSION;
   else
#endif
   if (CliTLSv12)
      maxTLS = TLS1_2_VERSION;
   else
   if (CliTLSv11)
      maxTLS = TLS1_1_VERSION;
   else
   if (CliTLSv1)
      maxTLS = TLS1_VERSION;

   ERR_clear_error();
   if (!SSL_CTX_set_min_proto_version (SslContext, minTLS))
   {
      fprintf (stdout, "Status: 500\n\n[line:%d] ", __LINE__);
      ERR_print_errors_fp (stdout);
      ErrorExit (SS$_BUGCHECK, __LINE__);
      return;
   }

   ERR_clear_error();
   if (!SSL_CTX_set_max_proto_version(SslContext, maxTLS))
   {
      fprintf (stdout, "Status: 500\n\n[line:%d] ", __LINE__);
      ERR_print_errors_fp (stdout);
      ErrorExit (SS$_BUGCHECK, __LINE__);
      return;
   }

   sprintf (SoftwareEnv, "%s, %s, %s, %s",
            SOFTWAREID, CgiEnvironmentPtr, OpenSSL_version (OPENSSL_VERSION),
            BUILD_DATETIME);

#else /* OPENSSL_SINCE_110 */

   RAND_seed (&RandBits, sizeof(RandBits));

   if (CliSSLv23)
      SslMethod = (SSL_METHOD*)SSLv23_client_method ();
   else
      SslMethod = (SSL_METHOD*)SSLv3_client_method ();

   SslContext = SSL_CTX_new (SslMethod);
   if (!SslContext) ErrorExit (SS$_BUGCHECK, __LINE__);

   sprintf (SoftwareEnv, "%s, %s, %s, %s",
            SOFTWAREID, CgiEnvironmentPtr, SSLeay_version(SSLEAY_VERSION),
            BUILD_DATETIME);

#endif /* OPENSSL_SINCE_110 */

#endif /* MUNGE_VIA_SSL */

   for (;;)
   {
      /* block waiting for the next request */
      CgiLibVar ("");

      if (WatchEnabled = (SysTrnLnm ("PROXYMUNGE$WATCH", NULL) != NULL))
         WatchThis (__LINE__, NULL);
         
      ProcessRequest ();

      if (WatchEnabled) WatchThis (__LINE__, NULL);

      if (!IsCgiPlus) break;

      CgiLibCgiPlusEOF ();
   }
}

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

void ProcessRequest ()
       
{
   int  cnt, retval;
   char  *cptr, *sptr, *zptr;

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

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

   if (WatchEnabled) WatchThis (__LINE__, "!AZ", SoftwareEnv);

   if (IsCgiPlus)
   {
      CgiPlusUsageCount++;
      if (WatchEnabled) WatchThis (__LINE__, "CgiPlus: !UL", CgiPlusUsageCount);
   }

   if (!ReadBufferPtr)
   {
      CgiGatewayMrsPtr = GetCgiVar ("GATEWAY_MRS");
      CgiGatewayMrs = atoi(CgiGatewayMrsPtr);
      if (!CgiGatewayMrs) CgiGatewayMrs = 4096;

      ReadBufferSize = CgiGatewayMrs;
      ReadBufferPtr = calloc (1, ReadBufferSize);
      if (!ReadBufferPtr) ErrorExit (vaxc$errno, __LINE__);

      ResponseHeaderSize = CgiGatewayMrs;
      ResponseHeaderPtr = calloc (1, ResponseHeaderSize);
      if (!ResponseHeaderPtr) ErrorExit (vaxc$errno, __LINE__);
   }
   if (WatchEnabled) WatchThis (__LINE__, "GATEWAY_MRS !SL", CgiGatewayMrs);

   /********************/
   /* munge parameters */
   /********************/

   CgiParamReversePtr = GetCgiVar ("REVERSE");
   CgiParamReverseLength = strlen(CgiParamReversePtr);

   CgiParamHostPtr = GetCgiVar ("HOST");
   CgiParamInternalLength = strlen(CgiParamHostPtr);
   if (MungeHostPtr) free (MungeHostPtr);
   MungeHostPtr = NULL;
   if (CgiParamInternalLength)
   {
      MungeHostPtr = sptr = calloc (1, CgiParamInternalLength+4);
      if (!MungeHostPtr) ErrorExit (vaxc$errno, __LINE__);
      for (cptr = CgiParamHostPtr; *cptr; *sptr++ = *cptr++);
      *(unsigned long*)sptr = 0;
      for (sptr = MungeHostPtr; *sptr; sptr++)
         if (*sptr == '=' || *sptr == ',') *sptr = '\0';
      if (WatchEnabled)
      {
         for (sptr = MungeHostPtr; *sptr; sptr++)
         {
            for (cptr = sptr; *cptr; cptr++);
            cptr++;
            WatchThis (__LINE__, "URL host \'!AZ\' to \'!AZ\'", sptr, cptr);
            while (*cptr) cptr++;
            sptr = cptr;
         }
      }
   }

   CgiParamDomainPtr = GetCgiVar ("DOMAIN");
   CgiParamDomainLength = strlen(CgiParamDomainPtr);
   if (MungeDomainPtr) free (MungeDomainPtr);
   MungeDomainPtr = NULL;
   if (CgiParamDomainLength)
   {
      MungeDomainPtr = sptr = calloc (1, CgiParamDomainLength+4);
      if (!MungeDomainPtr) ErrorExit (vaxc$errno, __LINE__);
      for (cptr = CgiParamDomainPtr; *cptr; *sptr++ = *cptr++);
      *(unsigned long*)sptr = 0;
      for (sptr = MungeDomainPtr; *sptr; sptr++)
         if (*sptr == '=' || *sptr == ',') *sptr = '\0';
      if (WatchEnabled)
      {
         for (sptr = MungeDomainPtr; *sptr; sptr++)
         {
            for (cptr = sptr; *cptr; cptr++);
            cptr++;
            WatchThis (__LINE__, "COOKIE domain \'!AZ\' to \'!AZ\'",
                       sptr, cptr);
            while (*cptr) cptr++;
            sptr = cptr;
         }
      }
   }

   /**********************/
   /* parse host and URI */
   /**********************/

   CgiPathInfoPtr = GetCgiVar ("PATH_INFO");
   CgiQueryStringPtr = GetCgiVar ("QUERY_STRING");

   cptr = CgiPathInfoPtr;
   if (*cptr) cptr++;

   HttpScheme = HttpsScheme = FALSE;
   if (!strncmp (cptr, "http://", cnt = 7))
      HttpScheme = TRUE;
   else
   if (!strncmp (cptr, "https://", cnt = 8))
      HttpsScheme = TRUE;

#if !MUNGE_VIA_SSL

   if (HttpsScheme)
   {
      if (WatchEnabled) WatchThis (__LINE__, "SSL not supported");
      fprintf (stdout,
"Status: 500\n\
Script-Control: X-error-text=\'SSL not supported\'\n\n");
      return;
   }

#endif

   if (!(HttpScheme || HttpsScheme))
   {
      if (WatchEnabled) WatchThis (__LINE__, "CONFUSED mapping !AZ", cptr);
      fprintf (stdout,
"Status: 500\n\
Script-Control: X-error-text=\'CONFUSED mapping\'\n\n");
      return;
   }

   zptr = (sptr = ServerHostName) + sizeof(ServerHostName)-1;
   cptr += cnt;
   while (*cptr && *cptr != ':' && *cptr != '/' && sptr < zptr)
      *sptr++ = *cptr++;
   *sptr = '\0';
   while (*cptr && *cptr != ':' && *cptr != '/') cptr++;
   if (*cptr == ':')
   {
      cptr++;
      zptr = (sptr = ServerPort) + sizeof(ServerHostName)-1;
      while (*cptr && *cptr != '/' && sptr < zptr) *sptr++ = *cptr++;
      *sptr = '\0';
      ServerPortNumber = atoi(ServerPort);
      while (*cptr && *cptr != '/') cptr++;
   }
   else
   {
#if MUNGE_VIA_SSL
      if (HttpsScheme)
      {
         strcpy (ServerPort, "443");
         ServerPortNumber = 443;
      }
      else
#endif
      {
         strcpy (ServerPort, "80");
         ServerPortNumber = 80;
      }
   }
   sprintf (ServerHostPort, "%s:%s", ServerHostName, ServerPort);

   RequestUriPtr = cptr;

   /*****************/
   /* proxy request */
   /*****************/

   retval = ConnectToServer ();

#if MUNGE_VIA_SSL

   if (HttpsScheme && retval >= 0)
   {
      SslServer = SSL_new (SslContext);
      if (!SslServer)
      {
         if (WatchEnabled)
         {
            WatchThis (__LINE__, "SSL_new() failure");
            ERR_print_errors_fp (stdout);
         }
         fprintf (stdout,
"Status: 500\n\
Script-Control: X-error-text=\'SSL_new() failure\'\n\n");
         return;
      }

      retval = SSL_set_fd (SslServer, ServerSocket);
      if (retval <= 0)
      {
         if (WatchEnabled)
         {
            WatchThis (__LINE__, "SSL_set_fd() failure");
            ERR_print_errors_fp (stdout);
         }
         fprintf (stdout,
"Status: 500\n\
Script-Control: X-error-text=\'SSL_set_fd() failure\'\n\n");
         return;
      }

      ERR_clear_error();
      retval = SSL_connect (SslServer);
      if (retval <= 0)
      {
         if (WatchEnabled)
         {
            WatchThis (__LINE__, "SSL_connect() failure");
            ERR_print_errors_fp (stdout);
         }
         fprintf (stdout, "Status: 500\n\n[line:%d] ", __LINE__);
         ERR_print_errors_fp (stdout);
         return;
      }

      if (WatchEnabled)
         WatchThis (__LINE__, "SSL established using !AZ",
                    SSL_get_cipher (SslServer));
   }

#endif

   if (retval >= 0) retval = MakeHttpRequest ();

   if (retval >= 0) retval = GetHttpResponse ();

   if (retval >= 0)
   {
      if (ResponseContentTextHtml)
         ReturnHtmlResponseBody ();
      else
      if (ResponseContentTextCss)
         ReturnCssResponseBody ();
   }

#if MUNGE_VIA_SSL
   if (HttpsScheme) SSL_shutdown (SslServer);
#endif

   shutdown (ServerSocket, 2);
   close (ServerSocket);

#if MUNGE_VIA_SSL
   if (HttpsScheme) SSL_free (SslServer);
#endif
}

/*****************************************************************************/
/*
TCP/IP connect to the server specified by ServerHostName and ServerPortNumber.
*/

int ConnectToServer ()
       
{
#define SIZINADDR (sizeof(struct in_addr))

   int  retval;
   struct in_addr  InAddr;
   struct hostent  *HostEntryPtr;

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

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

   if (WatchEnabled) WatchThis (__LINE__, "CONNECT !AZ", ServerHostPort);

   memset (&ServerSockAddr, 0, sizeof(ServerSockAddr));

   ServerSockAddr.sin_family = AF_INET;
   ServerSockAddr.sin_port = htons (ServerPortNumber);

   InAddr.s_addr = inet_addr (ServerHostName);
   if (InAddr.s_addr == (unsigned int)INADDR_NONE)
   {
      HostEntryPtr = gethostbyname (ServerHostName);
      if (HostEntryPtr)
         memcpy (&ServerSockAddr.sin_addr, HostEntryPtr->h_addr, SIZINADDR);
      else
      {
         if (WatchEnabled) WatchThis (__LINE__, "host not resolved");
         fprintf (stdout,
"Status: 504\n\
Script-Control: X-error-text=\'host not resolved\'\n\n");
         return (-1);
      }
   }
   else
      memcpy (&ServerSockAddr.sin_addr, &InAddr.s_addr, SIZINADDR);

   ServerSocket = socket(AF_INET, SOCK_STREAM, 0);
   if (ServerSocket < 0)
   {
      if (WatchEnabled)
         WatchThis (__LINE__, "SOCKET failure %X!8XL", vaxc$errno);
      fprintf (stdout,
"Status: 500\n\
Script-Control: X-error-text=\'%%X%08.08XL %s\'\n\n",
               vaxc$errno, strerror(vaxc$errno));
      return (-1);
   }

   retval = connect (ServerSocket,
                     (const struct sockaddr*)&ServerSockAddr,
                     sizeof(ServerSockAddr));
   if (retval < 0)
   {
      if (WatchEnabled)
         WatchThis (__LINE__, "CONNECT failure %X!8XL", vaxc$errno);
      fprintf (stdout,
"Status: 500\n\
Script-Control: X-error-text=\'%%X%08.08XL %s\'\n\n",
               vaxc$errno, strerror(vaxc$errno));
      return (retval);
   }
}

/****************************************************************************/
/*
Rebuild an HTTP/1.0 request header representing the proxied request and write
it to the TCP/IP connected server.
*/ 

int MakeHttpRequest ()

{
   int  retval,
        ContentLength = 0,
        PostBufferCount = 0;
   char  *aptr, *cptr, *sptr, *zptr,
         *PostBufferPtr = NULL;
   char  RequestBuffer [2048];

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

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

   CgiRequestMethodPtr = GetCgiVar ("REQUEST_METHOD");

   zptr = (sptr = RequestBuffer) + sizeof(RequestBuffer)-1;
   for (cptr = CgiRequestMethodPtr; *cptr && sptr < zptr; *sptr++ = *cptr++);
   if (sptr < zptr) *sptr++ = ' ';
   for (cptr = RequestUriPtr; *cptr && sptr < zptr; *sptr++ = *cptr++);
   if (*CgiQueryStringPtr)
   {
      if (sptr < zptr) *sptr++ = '?';
      for (cptr = CgiQueryStringPtr; *cptr && sptr < zptr; *sptr++ = *cptr++);
   }
   for (cptr = " HTTP/1.0\r\n"; *cptr && sptr < zptr; *sptr++ = *cptr++);

   for (cptr = "Host: "; *cptr && sptr < zptr; *sptr++ = *cptr++);
   for (cptr = ServerHostName; *cptr && sptr < zptr; *sptr++ = *cptr++);
   if (ServerPortNumber != 80)
   {
      if (sptr < zptr) *sptr++ = ':';
      for (cptr = ServerPort; *cptr && sptr < zptr; *sptr++ = *cptr++);
   }
   for (cptr = "\r\n"; *cptr && sptr < zptr; *sptr++ = *cptr++);
 
   for (cptr = "X-Proxy-Agent: "; *cptr && sptr < zptr; *sptr++ = *cptr++);
   for (cptr = SoftwareEnv; *cptr && sptr < zptr; *sptr++ = *cptr++);
   for (cptr = "\r\n"; *cptr && sptr < zptr; *sptr++ = *cptr++);

   aptr = GetCgiVarNull ("HTTP_AUTHORIZATION");
   if (aptr)
   {
      for (cptr = "Authorization: "; *cptr && sptr < zptr; *sptr++ = *cptr++);
      for (cptr = aptr; *cptr && sptr < zptr; *sptr++ = *cptr++);
      for (cptr = "\r\n"; *cptr && sptr < zptr; *sptr++ = *cptr++);
   }

   aptr = GetCgiVarNull ("HTTP_USER_AGENT");
   if (aptr)
   {
      for (cptr = "User-Agent: "; *cptr && sptr < zptr; *sptr++ = *cptr++);
      for (cptr = aptr; *cptr && sptr < zptr; *sptr++ = *cptr++);
      for (cptr = "\r\n"; *cptr && sptr < zptr; *sptr++ = *cptr++);
   }

   aptr = GetCgiVarNull ("HTTP_ACCEPT");
   if (aptr)
   {
      for (cptr = "Accept: "; *cptr && sptr < zptr; *sptr++ = *cptr++);
      for (cptr = aptr; *cptr && sptr < zptr; *sptr++ = *cptr++);
      for (cptr = "\r\n"; *cptr && sptr < zptr; *sptr++ = *cptr++);
   }

   aptr = GetCgiVarNull ("HTTP_ACCEPT_CHARSET");
   if (aptr)
   {
      for (cptr = "Accept-Charset: "; *cptr && sptr < zptr; *sptr++ = *cptr++);
      for (cptr = aptr; *cptr && sptr < zptr; *sptr++ = *cptr++);
      for (cptr = "\r\n"; *cptr && sptr < zptr; *sptr++ = *cptr++);
   }

   aptr = GetCgiVarNull ("HTTP_LANGUAGE");
   if (aptr)
   {
      for (cptr = "Accept-Language: "; *cptr && sptr < zptr; *sptr++ = *cptr++);
      for (cptr = aptr; *cptr && sptr < zptr; *sptr++ = *cptr++);
      for (cptr = "\r\n"; *cptr && sptr < zptr; *sptr++ = *cptr++);
   }

   aptr = GetCgiVarNull ("HTTP_COOKIE");
   if (aptr)
   {
      for (cptr = "Cookie: "; *cptr && sptr < zptr; *sptr++ = *cptr++);
      for (cptr = aptr; *cptr && sptr < zptr; *sptr++ = *cptr++);
      for (cptr = "\r\n"; *cptr && sptr < zptr; *sptr++ = *cptr++);
   }

   aptr = GetCgiVarNull ("HTTP_IF_MODIFIED_SINCE");
   if (aptr)
   {
      for (cptr = "If-Modified-Since: "; *cptr && sptr < zptr; *sptr++ = *cptr++);
      for (cptr = aptr; *cptr && sptr < zptr; *sptr++ = *cptr++);
      for (cptr = "\r\n"; *cptr && sptr < zptr; *sptr++ = *cptr++);
   }

   if (!strcmp (CgiRequestMethodPtr, "GET"))
   {
      /* nothing additional required with this supported method */
   }
   else
   if (!strcmp (CgiRequestMethodPtr, "HEAD"))
   {
      /* nothing additional required with this supported method */
   }
   else
   if (!strcmp (CgiRequestMethodPtr, "POST"))
   {
      CgiContentTypePtr = GetCgiVar("CONTENT_TYPE");
      CgiContentLengthPtr = GetCgiVar("CONTENT_LENGTH");
      ContentLength = atoi(CgiContentLengthPtr);

      CgiLibReadRequestBody (&PostBufferPtr, &PostBufferCount);
      if (WatchEnabled) WatchThis (__LINE__, "POST !UL bytes", PostBufferCount);

      if (!PostBufferPtr || PostBufferCount != ContentLength)
      {
         if (PostBufferPtr) free (PostBufferPtr);
         if (WatchEnabled) WatchThis (__LINE__, "POST body read failure");
         fprintf (stdout,
"Status: 500\n\
Script-Control: X-error-text=\'POST body read failure\'\n\n");
         return (-1);
      }

      for (cptr = "Content-Type: "; *cptr && sptr < zptr; *sptr++ = *cptr++);
      for (cptr = CgiContentTypePtr; *cptr && sptr < zptr; *sptr++ = *cptr++);
      for (cptr = "\r\nContent-Length: "; *cptr && sptr < zptr; *sptr++ = *cptr++);
      for (cptr = CgiContentLengthPtr; *cptr && sptr < zptr; *sptr++ = *cptr++);
      for (cptr = "\r\n"; *cptr && sptr < zptr; *sptr++ = *cptr++);
   }
   else
   {
      if (WatchEnabled)
         WatchThis (__LINE__, "!AZ not supported", CgiRequestMethodPtr);
      fprintf (stdout,
"Status: 501\n\
Script-Control: X-error-text=\'%s not supported\'\n\n", CgiRequestMethodPtr);
      return (-1);
   }

   aptr = GetCgiVarNull ("HTTP_PRAGMA");
   if (aptr)
   {
      for (cptr = "Pragma: "; *cptr && sptr < zptr; *sptr++ = *cptr++);
      for (cptr = aptr; *cptr && sptr < zptr; *sptr++ = *cptr++);
      for (cptr = "\r\n"; *cptr && sptr < zptr; *sptr++ = *cptr++);
   }

   aptr = GetCgiVarNull ("HTTP_CACHE_CONTROL");
   if (aptr)
   {
      for (cptr = "Cache-Control: "; *cptr && sptr < zptr; *sptr++ = *cptr++);
      for (cptr = aptr; *cptr && sptr < zptr; *sptr++ = *cptr++);
      for (cptr = "\r\n"; *cptr && sptr < zptr; *sptr++ = *cptr++);
   }

   for (cptr = "Connection: close\r\n\r\n";
        *cptr && sptr < zptr;
        *sptr++ = *cptr++);
   *sptr = '\0';
   if (WatchEnabled) WatchThis (__LINE__, "!AZ", RequestBuffer);

   if (sptr >= zptr)
   {
      if (WatchEnabled) WatchThis (__LINE__, "Request buffer overflow");
      fprintf (stdout,
"Status: 500\n\
Script-Control: X-error-text=\'Request buffer overflow\'\n\n");
      return (-1);
   }

#if MUNGE_VIA_SSL
   if (HttpsScheme)
      retval = SSL_write (SslServer, RequestBuffer, sptr-RequestBuffer); 
   else
#endif
      retval = write (ServerSocket, RequestBuffer, sptr-RequestBuffer); 
   if (PostBufferPtr)
   {
      if (retval >= 0)
      {
#if MUNGE_VIA_SSL
         if (HttpsScheme)
            SSL_write (SslServer, PostBufferPtr, PostBufferCount); 
         else
#endif
            write (ServerSocket, PostBufferPtr, PostBufferCount); 
      }
      free (PostBufferPtr);
   }

   return (retval);
}

/****************************************************************************/
/*
Read the response from the TCP/IP connected server.  Continue reading until the
HTTP response header is received.  Once that is available parse the contents
for information relevant to processing the body and send it to the client (via
the script stdout) fields munged as necessary.

If the response content-type is not HTML then it's opaque and continue to read,
sending each buffer back to the client, until the server disconnects.

If the response body is HTML then continue reading, buffering that body until
the server disconnects.
*/ 

int GetHttpResponse ()

{
   int  retval;
   char  *cptr, *sptr, *zptr;

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

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

   ResponseHeaderCount = 0;
   zptr = (sptr = ResponseHeaderPtr) + ResponseHeaderSize - 1;

   for (;;)
   {
#if MUNGE_VIA_SSL
      if (HttpsScheme)
         retval = SSL_read (SslServer, ReadBufferPtr, ReadBufferSize);
      else
#endif
         retval = read (ServerSocket, ReadBufferPtr, ReadBufferSize);
      if (WatchEnabled)
      {
         WatchThis (__LINE__, "READ !SL", retval);
         if (retval > 0) WatchDump (ReadBufferPtr, retval);
      }
      if (retval <= 0) break;
      ReadBufferCount = retval;

      if (!ResponseHeaderCount)
      {
         /*******************/
         /* response header */
         /*******************/

         cptr = ReadBufferPtr;
         while (retval-- && sptr < zptr)
         {
            *sptr++ = *cptr;
            if (*cptr++ == '\n')
            {
               if (sptr > ResponseHeaderPtr+2 &&
                   *(unsigned short*)(sptr-2) == '\n\n')
               {
                  *sptr = '\0';
                  ResponseHeaderCount = sptr - ResponseHeaderPtr;
                  break;
               }
               else
               if (sptr > ResponseHeaderPtr+4 &&
                   *(unsigned long*)(sptr-4) == '\r\n\r\n')
               {
                  *sptr = '\0';
                  ResponseHeaderCount = sptr - ResponseHeaderPtr;
                  break;
               }
            }
         }
         if (!ResponseHeaderCount) continue;

         ReturnHttpResponseHeader ();

         /*************/
         /* any body? */
         /*************/

         if (ResponseContentOpaque)
         {
            /* write anything remaining in the buffer following the header */
            if (retval > 0)
               if (ReadBufferCount - ResponseHeaderCount > 0)
                  retval = fwrite (ReadBufferPtr + ResponseHeaderCount,
                                   ReadBufferCount - ResponseHeaderCount,
                                   1, stdout);
         }
         else
         if (ReadBufferCount - ResponseHeaderCount)
         {
            /* response body remaining after parse of response header */
            if (ResponseContentLength)
               ResponseBodySize = ResponseContentLength;
            else
               ResponseBodySize = CgiGatewayMrs;
            ResponseBodyPtr = calloc (1, ResponseBodySize+16);
            if (!ResponseBodyPtr) ErrorExit (vaxc$errno, __LINE__);

            memcpy (ResponseBodyPtr,
                    ReadBufferPtr + ResponseHeaderCount,
                    ReadBufferCount - ResponseHeaderCount);
            ResponseBodyCount = ReadBufferCount - ResponseHeaderCount;
         }

         continue;
      }

      /*****************/
      /* response body */
      /*****************/

      if (ResponseContentOpaque)
      {
         /*************************/
         /* to client immediately */
         /*************************/

         fwrite (ReadBufferPtr, ReadBufferCount, 1, stdout);
      }
      else
      {
         /*******************/
         /* buffer the body */
         /*******************/

         if (!ResponseBodyPtr)
         {
            if (ResponseContentLength)
               ResponseBodySize = ResponseContentLength;
            else
               ResponseBodySize = CgiGatewayMrs;
            ResponseBodyPtr = calloc (1, ResponseBodySize+16);
            if (!ResponseBodyPtr) ErrorExit (vaxc$errno, __LINE__);
            ResponseBodyCount = 0;
         }

         if (ResponseBodyCount + ReadBufferCount > ResponseBodySize)
         {
            ResponseBodySize += CgiGatewayMrs;
            ResponseBodyPtr = realloc (ResponseBodyPtr, ResponseBodySize+16);
            if (!ResponseBodyPtr) ErrorExit (vaxc$errno, __LINE__);
         }

         memcpy (ResponseBodyPtr+ResponseBodyCount,
                 ReadBufferPtr,
                 ReadBufferCount);
         ResponseBodyCount += ReadBufferCount;

         if (WatchEnabled)
            WatchThis (__LINE__, "BODY !UL/!UL",
                       ResponseBodySize, ResponseBodyCount);
      }
   }

   return (retval);
}

/****************************************************************************/
/*
Return a munged (if required) HTTP response header to the client.
*/ 

void ReturnHttpResponseHeader ()

{
   char  *aptr, *cptr, *sptr;

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

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

   ResponseContentOpaque = TRUE;
   ResponseContentTextCss = ResponseContentTextHtml = FALSE;

   /* first, are we dealing with HTML? */
   cptr = ResponseHeaderPtr;
   while (*cptr)
   {
      if (strsame (cptr, "Content-Type:", 13))
      {
         for (cptr += 13; *cptr && *cptr == ' '; cptr++);
         if (!strncmp (cptr, "text/html", 9))
         {
            ResponseContentTextHtml = TRUE;
            ResponseContentOpaque = FALSE;
         }
         else
         if (!strncmp (cptr, "text/css", 8))
         {
            ResponseContentTextCss = TRUE;
            ResponseContentOpaque = FALSE;
         }
         break;
      }
      while (*cptr && *cptr != '\n') cptr++;
      if (*cptr) cptr++;
   }

   cptr = sptr = ResponseHeaderPtr;
   while (*cptr)
   {
      if (strsame (cptr, "Content-Length:", 15))
      {
         if (ResponseContentOpaque)
         {
            /* opaque content, we'll just be spitting out a bago'bytes */
            for (cptr += 15; *cptr && *cptr == ' '; cptr++);
            if (isdigit (*cptr)) ResponseContentLength = atoi(cptr);
         }
         else
         {
            /* once munged this content-length most likely won't be accurate */
            FOUTS (sptr, cptr)
            for (cptr += 15; *cptr && *cptr != '\n'; cptr++);
            if (*cptr) cptr++;
            sptr = cptr;
         }
      } 
      else
      if (strsame (cptr, "Set-Cookie:", 11))
      {
         cptr += 11;
         while (*cptr && *cptr != '\n')
         {
            if (tolower(*cptr) == 'p' && strsame (cptr, "path=", 5))
            {
               cptr += 5;
               FOUTS (sptr, cptr)
               cptr = sptr = MungeUrl (cptr, 0);
            }
            else
            if (tolower(*cptr) == 'd' && strsame (cptr, "domain=", 7))
            {
               cptr += 7;
               FOUTS (sptr, cptr)
               cptr = sptr = MungeDomain (cptr);
            }
            else
               cptr++;
         }
      }
      else
      if (strsame (cptr, "Location:", 9))
      {
         cptr += 9;
         while (*cptr && *cptr == ' ') cptr++;
         FOUTS (sptr, cptr)
         cptr = sptr = MungeUrl (cptr, 0);
      }
      while (*cptr && *cptr != '\n') cptr++;
      if (*cptr) cptr++;
   }

   FOUTS (sptr, cptr)
}

/****************************************************************************/
/*
Process the HTML content of the response body, munging URLs contained
in ACTION=, BACKGROUND=, HREF= and SRC= as required.  Free the dynamically
allocated body buffer when complete.
*/ 

void ReturnHtmlResponseBody ()

{
   char  ch;
   char  *aptr, *cptr, *sptr, *zptr;

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

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

   if (!ResponseBodyPtr) return;

   zptr = (cptr = sptr = ResponseBodyPtr) + ResponseBodyCount;

   while (cptr < zptr)
   {
      if (*cptr != '<')
      {
         /* not inside a markup tag */
         cptr++;
         continue;
      }

      if (strsame (cptr, "<SCRIPT", 7))
      {
         /**************/
         /* SCRIPT TAG */
         /**************/

         cptr = SpanToTagClose (cptr+7, zptr);
         cptr = SpanToEndTag ("</SCRIPT>", cptr, zptr);
         continue;
      }

      if (strsame (cptr, "<STYLE", 6))
      {
         /*************/
         /* STYLE TAG */
         /*************/

         cptr = SpanToTagClose (cptr+6, zptr);
         FOUTS (sptr, (aptr = cptr))
         cptr = sptr = SpanToEndTag ("</STYLE>", cptr, zptr);
         if (cptr < zptr) MungeCssText (aptr, (sptr=(cptr-8)));
         continue;
      }

      /*************/
      /* OTHER TAG */
      /*************/

      cptr++;
      while (cptr < zptr)
      {
         /* if reached the end of a markup tag (or non-well-formed one) */
         if (*cptr == '>' || *cptr == '<') break;

         if (*cptr == '\"' || *cptr == '\'')
         {
            cptr = SpanQuoted (cptr, zptr);
            continue;
         }

         if (tolower(*cptr) == 's' && strsame (cptr, "style=", 6))
         {
            /*******************/
            /* STYLE ATTRIBUTE */
            /*******************/

            cptr += 6;
            if (*cptr == '\"' || *cptr == '\'')
            {
               ch = *cptr++;
               FOUTS (sptr, (aptr = cptr))
               while (cptr < zptr && *cptr != ch) cptr++;
               MungeCssText (aptr, (sptr=cptr));
               if (cptr < zptr) cptr++;
            }
            else
            {
               FOUTS (sptr, (aptr = cptr))
               while (cptr < zptr && !isspace(*cptr)) cptr++;
               MungeCssText (aptr, (sptr=cptr));
            }
            continue;
         }

         /*******************/
         /* OTHER ATTRIBUTE */
         /*******************/

         if (tolower(*cptr) == 'h' && strsame (cptr, "href=", 5))
            cptr += 5;
         else
         if (tolower(*cptr) == 's' && strsame (cptr, "src=", 4))
            cptr += 4;
         else
         if (tolower(*cptr) == 'b' && strsame (cptr, "background=", 11))
            cptr += 11;
         else
         if (tolower(*cptr) == 'a' && strsame (cptr, "action=", 7))
            cptr += 7;
         else
         if (tolower(*cptr) == 'c' && strsame (cptr, "code=", 5))
            cptr += 5;
         else
         if (tolower(*cptr) == 'c' && strsame (cptr, "codebase=", 9))
            cptr += 9;
         else
         if (tolower(*cptr) == 'd' && strsame (cptr, "data=", 5))
            cptr += 5;
         else
         if (tolower(*cptr) == 'u' && strsame (cptr, "usemap=", 7))
            cptr += 7;
         else
         if (tolower(*cptr) == 'a' && strsame (cptr, "archive=", 8))
            cptr += 8;
         else
         {
            cptr++;
            continue;
         }

         if (*cptr == '\"' || *cptr == '\'')
         {
            ch = *cptr++;
            FOUTS (sptr, cptr)
            cptr = sptr = MungeUrl (cptr, ch);
            while (cptr < zptr && *cptr != ch) cptr++;
            if (cptr < zptr) cptr++;
         }
         else
         {
            FOUTS (sptr, cptr)
            cptr = sptr = MungeUrl (cptr, 0);
         }
      }
   }

   FOUTS (sptr, cptr)

   free (ResponseBodyPtr);
   ResponseBodyPtr = NULL;
   ResponseBodySize = 0;
}

/****************************************************************************/
/*
Span all text between the quotes.
*/ 

char* SpanQuoted
(
char *StartPtr,
char *EndPtr
)
{
   char  ch;
   char  *cptr;

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

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

   cptr = StartPtr;
   ch = *cptr++;
   while (cptr < EndPtr && *cptr != ch) cptr++;
   if (cptr < EndPtr) cptr++;
   return (cptr);
}

/****************************************************************************/
/*
Span all text between to the end-of-tag '>'.
*/ 

char* SpanToTagClose
(
char *cptr,
char *zptr
)
{
   char  ch;

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

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

   while (cptr < zptr)
   {
      if (*cptr == '\"' || *cptr == '\'')
      {
         cptr = SpanQuoted (cptr, zptr);
         continue;
      }
      /* if end of tag */
      if (*cptr == '>') break;
      cptr++;
   }
   if (cptr < zptr) cptr++;
   return (cptr);
}

/****************************************************************************/
/*
Span all text between to the specific end tag (e.g. "</SCRIPT>").
*/ 

char* SpanToEndTag
(
char *EndTag,
char *cptr,
char *zptr
)
{
   int  len;
   char  ch;

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

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

   len = strlen (EndTag);
   while (cptr < zptr)
   {
      if (*cptr == '\"' || *cptr == '\'')
      {
         cptr = SpanQuoted (cptr, zptr);
         continue;
      }
      /* if end tag */
      if (*cptr == '<' && strsame (cptr, EndTag, len)) break;
      cptr++;
   }
   if (cptr+len < zptr) cptr += len;
   return (cptr);
}

/****************************************************************************/
/*
Munge a "text/css" response body.
*/ 

void ReturnCssResponseBody ()

{
   char  *cptr, *sptr, *zptr;

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

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

   if (!ResponseBodyPtr) return;

   MungeCssText (ResponseBodyPtr, ResponseBodyPtr+ResponseBodyCount);

   free (ResponseBodyPtr);
   ResponseBodyPtr = NULL;
   ResponseBodySize = 0;
}

/****************************************************************************/
/*
About all that is required for munging Cascading Style Sheets is to identify
the 'url' element and modify that appropriately.
*/ 

void MungeCssText
(
char *cptr,
char *zptr
)
{
   char  *sptr;

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

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

   sptr = cptr;
   while (cptr < zptr)
   {
      if (*cptr != '{')
      {
         /* not inside a property */
         if (*(unsigned short*)cptr == '/*')
         {
            /* ingore anything inside comment delimiters */
            cptr += 2;
            while (cptr < zptr && *(unsigned short*)cptr != '*/') cptr++;
            if (cptr < zptr) cptr += 2;
         }
         else
            cptr++;
         continue;
      }
      cptr++;

      while (cptr < zptr)
      {
         /* if reached the end of a property (or non-well-formed one) */
         if (*cptr == '}' || *cptr == '{') break;

         /* ingore anything inside a property between double quotes */
         if (*cptr == '\"')
         {
            cptr = SpanQuoted (cptr, zptr);
            continue;
         }

         /* inside a property */
         if (tolower(*cptr) == 'u' &&
             (!strncmp (cptr, "url(", 4) || !strncmp (cptr, "url ", 4)))
            cptr += 4;
         else
         {
            cptr++;
            continue;
         }

         while (*cptr && isspace(*cptr)) cptr++;
         if (*cptr == '\"')
         {
            cptr++;
            FOUTS (sptr, cptr)
            cptr = sptr = MungeUrl (cptr, '\"');
            while (cptr < zptr && *cptr != '\"') cptr++;
            if (cptr < zptr) cptr++;
         }
         else
         {
            FOUTS (sptr, cptr)
            cptr = sptr = MungeUrl (cptr, 0);
         }
      }
   }

   FOUTS (sptr, cptr)
}

/****************************************************************************/
/*
Modify a URL in a HREF=, SRC=, etc.  If the URL is not a relative reference
then check if it's an absolute URL.  If so check if the host component if the
same as the proxied-to server.  If it is then just substitute the reverse-proxy
path.  If a rooted URI then just prefix it with the reverse-proxy path.
*/ 

char* MungeUrl
(
char *UrlPtr,
char QuoteChar
)
{
   int  cnt, retval;
   char  *cptr, *sptr, *zptr;
   char  HostBuffer [256];

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

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

   if (!*CgiParamReversePtr) return (UrlPtr);

   cptr = UrlPtr;

   if (strsame (cptr, "//", cnt = 2) ||
       strsame (cptr, "http://", cnt = 7))
   {
      cptr += cnt;
      zptr = (sptr = HostBuffer) + sizeof(HostBuffer)-1;
      while (*cptr && *cptr != '/' && *cptr != QuoteChar &&
             !isspace(*cptr) && sptr < zptr)
         *sptr++ = *cptr++;
      *sptr = '\0';
      while (*cptr && *cptr != '/' && *cptr != QuoteChar && !isspace(*cptr))
         cptr++;

      if (sptr = MungeHostPtr)
      {
         while (*sptr)
         {
            if (strsame (HostBuffer, sptr, -1))
            {
               while (*sptr) sptr++;
               sptr++;
               fputs (sptr, stdout); 
               return (cptr);
            }
            while (*sptr) sptr++;
            sptr++;
            if (*sptr)
            {
               while (*sptr) sptr++;
               sptr++;
            }
         }
      }

      if (!strsame (HostBuffer, ServerHostName, -1) &&
          !strsame (HostBuffer, ServerHostPort, -1)) return (UrlPtr);

      fputs (CgiParamReversePtr, stdout); 
      return (cptr);
   }

   if (*cptr == '/')
   {
      /* rooted URI */
      fputs (CgiParamReversePtr, stdout); 
      return (cptr);
   }

   return (UrlPtr);
}

/****************************************************************************/
/*
Rewrites the 'domain=' component of a cookie.
*/ 

char* MungeDomain (char *DomainPtr)

{
   int  cnt, retval;
   char  *cptr, *sptr, *zptr;
   char  DomainBuffer [256];

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

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

   if (!MungeDomainPtr) return (DomainPtr);

   cptr = sptr = DomainPtr;

   zptr = (sptr = DomainBuffer) + sizeof(DomainBuffer)-1;
   while (*cptr && *cptr != ';' && !isspace(*cptr) && sptr < zptr)
      *sptr++ = *cptr++;
   *sptr = '\0';
   while (*cptr && *cptr != ';' && !isspace(*cptr)) cptr++;

   sptr = MungeDomainPtr;
   while (*sptr)
   {
      if (strsame (DomainBuffer, sptr, -1))
      {
         while (*sptr) sptr++;
         sptr++;
         fputs (sptr, stdout); 
         return (cptr);
      }
      while (*sptr) sptr++;
      sptr++;
      if (*sptr)
      {
         while (*sptr) sptr++;
         sptr++;
      }
   }

   return (DomainPtr);
}

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

char* GetCgiVar (char *VarName)

{
   char  *cptr;

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

   cptr = CgiLibVar (VarName);

   if (WatchEnabled) WatchThis (__LINE__, "!AZ=!AZ", VarName, cptr);

   return (cptr);
}

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

char* GetCgiVarNull (char *VarName)

{
   char  *cptr;

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

   cptr = CgiLibVarNull (VarName);

   if (WatchEnabled)
      WatchThis (__LINE__, "!AZ=!AZ", VarName, cptr ? cptr : "(null)");

   return (cptr);
}

/*****************************************************************************/
/*
Exit after reporting the source code module name and line number.
*/

void ErrorExit
(
int StatusValue,
int SourceLineNumber
)
{
   /*********/
   /* begin */
   /*********/

   if (WatchEnabled) WatchThis (__LINE__, "%X!8XL - fatal error");

   fprintf (stdout,
"Status: 500\n\
Script-Control: X-error-text=\'%X!8XL - fatal error\'\n\n", StatusValue);

   exit (StatusValue);
}

/*****************************************************************************/
/*
Hmmm, idea look familiar? :-)
*/

int WatchThis
(
int SourceCodeLine,
char *FaoString,
...
)
{
   static int  WatchCount;

   int  argcnt, status;
   char  *cptr, *sptr, *zptr;
   char  Buffer [16384],
         FaoBuffer [256];
   unsigned long  *vecptr;
   $DESCRIPTOR (BufferDsc, Buffer);
   $DESCRIPTOR (FaoBufferDsc, FaoBuffer);
   unsigned long  FaoVector [32];
   va_list  argptr;

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

   va_count (argcnt);

   if (Debug) fprintf (stdout, "WatchThis() %d |%s|\n", argcnt, FaoString);

   if (!FaoString)
   {
      if (WatchCount)
      {
         /* post-processing reset */
         WatchThis (__LINE__, "STATS !AZ", StatTimer(TRUE));
         WatchThis (__LINE__, "WATCH end");
         WatchEnabled = WatchCount = 0;
      }
      else
      {
         /* pre-processing initialize */
         WatchCount = 1;
         CgiLibResponseHeader (200, "text/plain",
                               "Script-Control: X-content-encoding-gzip=0\n");
         WatchThis (__LINE__, "WATCH begin");
         StatTimer(FALSE);
      }
      return (SS$_NORMAL);
   }

   vecptr = FaoVector;
   *vecptr++ = WatchCount++;
   *vecptr++ = 0;
   *vecptr++ = SourceCodeLine;
   va_start (argptr, FaoString);
   for (argcnt -= 2; argcnt; argcnt--)
      *vecptr++ = (unsigned long)va_arg (argptr, unsigned long);
   va_end (argptr);

   zptr = (sptr = FaoBuffer) + sizeof(FaoBuffer)-3;
   for (cptr = "|!4ZL|!%T|!4ZL|"; *cptr; *sptr++ = *cptr++);
   for (cptr = FaoString; *cptr && sptr < zptr; *sptr++ = *cptr++);
   *sptr++ = '|';
   *sptr++ = '\n';
   *sptr++ = '\0';
   FaoBufferDsc.dsc$a_pointer = FaoBuffer;
   FaoBufferDsc.dsc$w_length = sptr - FaoBuffer;
   status = sys$faol (&FaoBufferDsc, 0, &BufferDsc,
                      (unsigned long*)&FaoVector);
   if (Debug) fprintf (stdout, "sys$fao() %%X%08.08X\n", status);
   if (!(status & 1)) exit (status);
   fputs (Buffer, stdout);

   return (status);
}

/*****************************************************************************/
/*
Ditto? :-)
*/

int WatchDump
(
char *DataPtr,
int DataLength
)
{
   int  cnt, len;
   char  *cptr, *lptr, *sptr;
   char  WatchBuffer [256];

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

   if (Debug) fprintf (stdout, "WatchDump() %d\n", DataLength);

   cptr = DataPtr;
   len = DataLength;
   while (len)
   {
      sptr = WatchBuffer;
      lptr = cptr;
      cnt = 0;
      while (cnt < 32)
      {
         if (cnt < len)
            sptr += sprintf (sptr, "%02.02X", (unsigned char)*cptr++);
         else
         {
            *sptr++ = ' ';
            *sptr++ = ' ';
         }
         if (!(++cnt % 4)) *sptr++ = ' ';
      }
      *sptr++ = ' ';
      cptr = lptr;
      cnt = 0;
      while (cnt < 32 && cnt < len)
      {
         if (isprint(*cptr))
            *sptr++ = *cptr;
         else
            *sptr++ = '.';
         cptr++;
         cnt++;
      }
      if (len > 32) len -= 32; else len = 0;
      *sptr++ = '\n';
      *sptr = '\0';
      fputs (WatchBuffer, stdout);
   }

   return (1);
}

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

char* StatTimer (BOOL ShowStat)

{
   static $DESCRIPTOR (FaoDsc,
"REAL:!%T CPU:!UL.!2ZL DIO:!UL BIO:!UL FAULTS:!UL\0");

   static char  StatString [96];
   static $DESCRIPTOR (StatStringDsc, StatString);

   static unsigned long  LibStatTimerReal = 1,
                         LibStatTimerCpu = 2,
                         LibStatTimerBio = 3,
                         LibStatTimerDio = 4,
                         LibStatTimerFaults = 5;

   int  status;
   unsigned long  CpuBinTime,
                  CountBio,
                  CountDio,
                  CountFaults;
   unsigned long  RealBinTime [2];

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

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

   if (!ShowStat)
   {
      lib$init_timer (0);
      return (NULL);
   }

   /* post-processing reset */
   lib$stat_timer (&LibStatTimerReal, &RealBinTime, 0);
   lib$stat_timer (&LibStatTimerCpu, &CpuBinTime, 0);
   lib$stat_timer (&LibStatTimerBio, &CountBio, 0);
   lib$stat_timer (&LibStatTimerDio, &CountDio, 0);
   lib$stat_timer (&LibStatTimerFaults, &CountFaults, 0);
   sys$fao (&FaoDsc, 0, &StatStringDsc,
            &RealBinTime, CpuBinTime/100, CpuBinTime%100,
            CountDio, CountBio, CountFaults);

   return (StatString);
}

/*****************************************************************************/
/*
Translate a logical name using LNM$FILE_DEV. Return a pointer to the value
string, or NULL if the name does not exist.  If 'LogValue' is supplied the
logical name is translated into that (assumed to be large enough), otherwise
it's translated into an internal static buffer.
*/

char* SysTrnLnm
(
char *LogName,
char *LogValue
)
{
   static unsigned short  ShortLength;
   static char  StaticLogValue [256];
   static $DESCRIPTOR (LogNameDsc, "");
   static $DESCRIPTOR (LnmFileDevDsc, "LNM$FILE_DEV");
   static struct {
      short int  buf_len;
      short int  item;
      void  *buf_addr;
      unsigned short  *ret_len;
   } LnmItems [] =
   {
      { 255, LNM$_STRING, 0, &ShortLength },
      { 0,0,0,0 }
   };

   int  status;
   char  *cptr;

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

   if (Debug) fprintf (stdout, "SysTrnLnm() |%s|\n", LogName);

   LogNameDsc.dsc$a_pointer = LogName;
   LogNameDsc.dsc$w_length = strlen(LogName);
   if (LogValue)
      cptr = LnmItems[0].buf_addr = LogValue;
   else
      cptr = LnmItems[0].buf_addr = StaticLogValue;

   status = sys$trnlnm (0, &LnmFileDevDsc, &LogNameDsc, 0, &LnmItems);
   if (Debug) fprintf (stdout, "sys$trnlnm() %%X%08.08X\n", status);
   if (!(status & 1))
   {
      if (Debug) fprintf (stdout, "|(null)|\n");
      return (NULL);
   }

   cptr[ShortLength] = '\0';
   if (Debug) fprintf (stdout, "|%s|\n", cptr);
   return (cptr);
}

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

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

   while (*sptr1 && *sptr2)
   {
      if (toupper (*sptr1++) != toupper (*sptr2++)) return (FALSE);
      if (count)
         if (!--count) return (TRUE);
   }
   if (*sptr1 || *sptr2)
      return (FALSE);
   else
      return (TRUE);
}

/*****************************************************************************/
/*
Get "command-line" parameters, whether from the command-line or from a
configuration symbol or logical containing the equivalent.  OSU scripts have
the 'method', 'url' and 'protocol' supplied as P1, P2, P3 (these being detected
and used by CGILIB), and are of no interest to this function.
*/

void GetParameters ()

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

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

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

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

   if ((clptr = getenv ("PROXYMUNGE$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';
   }

   /* if OSU environment then skip P1, P2, P3 */
   if (getenv ("WWWEXEC_RUNDOWN_STRING") != NULL)
      SkipParameters = 3;
   else
      SkipParameters = 0;

   aptr = NULL;
   ch = *clptr;
   for (;;)
   {
      if (aptr != NULL && *aptr == '/') *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 (SkipParameters)
      {
         SkipParameters--;
         continue;
      }

      if (strsame (aptr, "/DBUG", -1))
      {
         Debug = TRUE;
         continue;
      }
      if (strsame (aptr, "/SOFTWAREID", 4) ||
          strsame (aptr, "/VERSION", 4))
      {
         fprintf (stdout, "%%%s-I-SOFTWAREID, %s\n%s\n",
                  Utility, SoftwareEnv, SoftwareCopy);
         exit (SS$_NORMAL);
      }

#if OPENSSL_SINCE_110
      if (strsame (aptr, "/TLS1", -1))
      {
         CliTLSv1 = TRUE;
         continue;
      }
      if (strsame (aptr, "/TLS11", -1))
      {
         CliTLSv11 = TRUE;
         continue;
      }
      if (strsame (aptr, "/TLS12", -1))
      {
         CliTLSv12 = TRUE;
         continue;
      }
#if OPENSSL_SINCE_111 
      if (strsame (aptr, "/TLS13", -1))
      {
         CliTLSv13 = TRUE;
         continue;
      }
#endif
#endif

#if OPENSSL_BEFORE_110
      if (strsame (aptr, "/SSLV23", -1))
      {
         CliSSLv23 = TRUE;
         continue;
      }
#endif

      if (strsame (aptr, "/WATCH", 4))
      {
         WatchEnabled = TRUE;
         continue;
      }

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

      fprintf (stdout, "%%%s-E-MAXPARM, too many parameters\n \\%s\\\n",
               Utility, aptr);
      exit (STS$K_ERROR | STS$M_INHIB_MSG);
   }
}

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

