/*****************************************************************************/
/*
                                 Proxy.c

*************
** CAUTION **
*************

THIS MODULE IS TASK-ORIENTED, NOT REQUEST-ORIENTED.

That is, most of the functions take a pointer to proxy task rather than a
pointer to request as do other modules. The reason is simple. Many of these
functions are designed for use independently of a request.

Implements a basic HTTP proxy service in conjunction with ProxyCache.c module.
This module provides all the request parsing, networking, response parsing and
writing to the client.  The ProxyCache.c module provides the caching in files
of candidate requests.

All errors related to connecting to, requesting from, and processing the
response from the proxied server are reported as 502s (gateway failures).  Any
errors such as buffer overflows, etc., are reported as 500 (internal problems).


NON-PROXY REDIRECTION
---------------------
WASD provides for redirection from non-proxy to proxy request, allowing a type
of gatewaying between http: and https:, ftp: and ftp:, https: and http:, etc.
Warning: THIS REEKS OF BEING KLUDGY.  Although the redirection is implemented
in RequestRedirect() in REQUEST.C module RequestRedirect() function it is
described in greater detail here because it is wholly dependent on proxying
functionality.

Note that there are many combinations of mappings and services that could be
used to provide gatewaying between protocols.  What follows here are the
special case and some suggestions.  There are many other ways to organise these
capabilities.

Note that the proxy services must support the type of proxy request being
demanded of it (i.e. http:, https: and/or ftp:).


1. Simple in-line URL ("one-shot" proxy)
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
This is when a request to a proxy service contains, *not* a proxy format

  GET http://the.host.name:port/path HTTP/1.0

but a standard format request, with the URL containing another "full" URL,

  GET /http://the.host.name:port/path HTTP/1.0

because the 'user' entered

  http://the.proxy.service:8080/http://the.host.name:port/path

into the browser location field.  This known with WASD as a "one-shot" proxy
because the server will retrieve it but the anything other than a self-relative
link within any HTML document will not be correct.

The configuration requirements for this type of proxy are fairly simple.

  [[the.proxy.service:port]]
  pass /http://*  http://*
  pass /https://* https://*
  pass /ftp://*   ftp://*


2. Wildcard DNS (CNAME) record
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
This relies on being able to manipulate host records in the DNS or local name
resolution database.  If a "*.the.proxy.host" DNS (CNAME) record is resolved it
allows any host name ending in ".the.proxy.host" to resolve to the
corresponding IP address.  Similarly (at least the Compaq TCP/IP Services)
local host database allows an alias like "another.host.name.proxy.host.name"
for the proxy host name.  Both of these would allow a browser to access 
"another.host.name.proxy.host.name" with it resolved to the proxy service.  The
request "Host:" field would contain "another.host.name.proxy.host.name".

Using this approach a complete proxy may be implemented, where returned HTML
documents contain links that are always correct with reference to the host used
to request them.  For a wildcard DNS (CNAME) record the browser user may enter
any host name appended by the proxy service host name and port and have the
request proxied to the host name.  Entering the following URL into the browser
location field,

  http://the.host.name.the.proxy.service:8080/path

would result in a standard HTTP proxy request for "/path" being made to
"the.host.name:80".  The URL,

  https://the.host.name.the.proxy.service:8080/path

in an SSL proxy request.  Note that normally the well-known port would be used
to connect to (80 for http: and 443 for https:).  If the final,
period-separated component of the wildcard host name is all digits it is
interpreted as a specific port to connect to.  The example

  http://the.host.name.8001.the.proxy.service:8080/path

would connect to "the.host.name:8001", and

  https://the.host.name.8443.the.proxy.service:8080/path

to "the.host.name:8443".

A further special case may be developed where part of that host name may be
used to indicate the proxy gateway desired.

The REDIRECT mapping rule allows for the following configuration being
specially processed.  The following example shows a complex but powerful
arrangement where the host name used provides an indication of the type of
gatewaying required to be performed.

  [[the.proxy.service:port]]
  if (host:*.ftp.the.proxy.service:*)
     redirect * /ftp://ftp.the.proxy.service/*?
  elif (host:*.https.the.proxy.service:*)
     redirect * /https://https.the.proxy.service/*?
  elif (host:*.http.the.proxy.service:*)
     redirect * /http://http.the.proxy.service/*?
  elif (host:*.the.proxy.service:*)
     # fallback to HTTP if no other specified
     redirect * /http://the.proxy.service/*?
  else
     pass https://* https://*
     pass http://* http://*
     pass ftp://* ftp://*
  endif

In this case a user may specify to connect via HTTP but gateway to an
HTTP-over-SSL service using a request such as

  http://the.host.name.https.the.proxy.service:8080/path

where a proxy request will effectively be made to

  https://the.host.name:443/path

All digit components may still be used to indicate specific ports to be
connected to, as in this example

  http://the.host.name.7443.https.the.proxy.service:8080/path

where the proxy request would effectively be made to

  https://the.host.name:7443/path

Notice that the example configuration allows for gatewayed HTTP, HTTP-over-SSL
and FTP based on the original host name used for the request and the consequent
contents of the "Host:" request field (which is adjusted to reflect the changed
host contents during the gatewaying).  Note also that is a script author is
going to use the mechansim for 'protocol conversion' then the script needs to
provide an appropriate "Host:" request header field.


VERSION HISTORY
---------------
30-APR-2004  MGD  significant changes to eliminate RMS from cache file read
                  by using ACP/QIOs (saves a few cycles and a little latency,
                  continue to use the RMS abstraction for handling the greater
                  complexity of file creation and population).
10-APR-2004  MGD  significant modifications to support IPv6,
                  use generic name-to-address resolution function
20-NOV-2003  MGD  reverse proxy 302 "Location:" rewrite,
                  reverse proxy authorization verification
28-OCT-2003  MGD  bugfix; chained proxy CONNECT processing
                  bugfix; keep track of outstanding body reads
19-SEP-2003  MGD  bugfix; ProxyRebuildRequest() rebuild buffer space
21-AUG-2003  MGD  "Range:" header field
25-MAY-2003  MGD  improve efficiency of ProxyRebuildRequest(),
                  un-recognised/known request fields,
                  remove explicit setting 'rqBody.DataStatus = SS$_ENDOFFILE'
02-APR-2003  MGD  "X-Forwarded-For:" header field,
                  modify to "Forwarded: by" processing
15-MAY-2002  MGD  proxy gateway statistics
30-APR-2002  MGD  "Cache-Control:" field for Mozilla compatibility
17-MAR-2002  MGD  ParseRequest() allow for incomplete FTP URLs
02-FEB-2002  MGD  rework for request body processing changes
22-JAN-2002  MGD  ProxyHostConnectAst() invalidate host cache entry
                  if connection fails (jpp@esme.fr),
                  ProxyRequestParse() ignore leading '/' allowing "one-shot"
                  proxying using a standard request
04-JAN-2002  MGD  proxy HTTP-to-SSL gateway
14-OCT-2001  MGD  outgoing sockets may be bound to a specific IP address
04-AUG-2001  MGD  support module WATCHing
27-APR-2001  MGD  requirement for ProxyResolveHostCacheTimer() has been
                  eliminated with HttpTick() and 'HttpdTickSecond'
03-SEP-2000  MGD  bugfix; ProxyResolveHostLookup() can be called multiple
                  during host name resolution - only allocate channel once!!
                  bugfix; ProxyResolveHostLookup() hash collision list
04-JUL-2000  MGD  bugfix; ProxyEnd() for CONNECT (proxy SSL)
11-JUN-2000  MGD  refine numeric host detection,
                  ProxyRebuildRequest() now dynamically allocates a rebuild
                  buffer based on 'NetReadBufferSize'
01-JUN-2000  MGD  bugfix; use 'rqHeader.RequestUriPtr' as '->RequestUriPtr'
                  (non-URL-decoded path plus query string)
29-APR-2000  MGD  proxy authorization
04-MAR-2000  MGD  use NetWriteFaol(), et.al.
04-DEC-1999  MGD  default host name cache purge now 24 hours
11-NOV-1999  MGD  provide "ETag:" propagation
25-OCT-1999  MGD  remove NETLIB support
10-OCT-1999  MGD  just sys$dassn() the socket
26-JUN-1999  MGD  bugfix; ProxyEnd() call in ProxyReadResponseAst(),
                  bugfix; header pointer in ProxyHttp09ResponseHeader(),
                  always initialize cache parameters (even if not enabled),
                  revised request run down ProxyShutdownSocket()
19-AUG-1998  MGD  initial development (recommenced DEC 1998)
*/
/*****************************************************************************/

#ifdef WASD_VMS_V6
#undef _VMS_V6_SOURCE
#define _VMS_V6_SOURCE
#undef __VMS_VER
#define __VMS_VER 60000000
#undef __CRTL_VER
#define __CRTL_VER 60000000
#endif

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

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

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

#define WASD_MODULE "PROXY"

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

int ProxyReadBufferSize = 4096;

BOOL ProxyServingEnabled,
     ProxyUnknownRequestFields;

int  ProxyForwardedBy,
     ProxyHostLookupRetryCount,
     ProxyServiceCount,
     ProxyXForwardedFor;

PROXY_ACCOUNTING_STRUCT  *ProxyAccountingPtr;

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

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

extern int  EfnWait,
            EfnNoWait,
            HttpdTickSecond,
            NetReadBufferSize,
            OptionEnabled;

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

extern struct dsc$descriptor TcpIpDeviceDsc;

extern ACCOUNTING_STRUCT  *AccountingPtr;
extern CONFIG_STRUCT  Config;
extern IPADDRESS TcpIpEmptyAddress;
extern MSG_STRUCT  Msgs;
extern SERVICE_STRUCT  *ServiceListHead;
extern TCP_SOCKET_ITEM  TcpIpSocket4,
                        TcpIpSocket6;
extern VMS_ITEM_LIST2  ReuseAddress,
                       ReuseAddressSocketOption,
                       TcpIpFullDuplexCloseOption;
extern WATCH_STRUCT  Watch;

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

ProxyInit ()

{
   BOOL  CacheEnabled;
   char  *cptr;
   SERVICE_STRUCT  *svptr;

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

   if (WATCH_MODULE(WATCH_MOD_PROXY))
      WatchThis (NULL, FI_LI, WATCH_MOD_PROXY, "ProxyInit()");
 
   ProxyForwardedBy = ProxyServiceCount = ProxyXForwardedFor = 0;
   ProxyServingEnabled = CacheEnabled = false;

   /* copy the configuration parameters, ensuring they're reasonable */
   ProxyHostLookupRetryCount = Config.cfProxy.HostLookupRetryCount;
   if (!ProxyHostLookupRetryCount)
      ProxyHostLookupRetryCount = Config.cfMisc.DnsLookupRetryCount;
   if (!ProxyHostLookupRetryCount)
      ProxyHostLookupRetryCount = TCPIP_LOOKUP_RETRY_COUNT;
   if (ProxyHostLookupRetryCount < 0 || ProxyHostLookupRetryCount > 100)
      ProxyHostLookupRetryCount = 0;

   /* just some information about the proxy services */
   for (svptr = ServiceListHead; svptr; svptr = svptr->NextPtr)
   {
      if (!(svptr->ProxyService || svptr->ConnectService)) continue;

      ProxyServiceCount++;

      if (svptr->ProxyChainHostName[0])
         fprintf (stdout, "%%%s-I-PROXY, %s chains to %s\n",
                  Utility, svptr->ServerHostPort, svptr->ProxyChainHostPort);

      if (svptr->ProxyService)
      {
         fprintf (stdout, "%%%s-I-PROXY, HTTP service %s",
                  Utility, svptr->ServerHostPort);
         if (svptr->ProxyAuthRequired)
            fprintf (stdout, " (auth)");
         if (svptr->ProxyFileCacheEnabled)
         {
            CacheEnabled = true;
            fprintf (stdout, " (cached)");
         }
         if (svptr->ConnectService)
            fprintf (stdout, " (connect)");
         fputs ("\n", stdout);
      }
      else
      if (svptr->ConnectService)
      {
         fprintf (stdout, "%%%s-I-PROXY, connect service %s",
                  Utility, svptr->ServerHostPort);
         if (svptr->ProxyAuthRequired)
            fprintf (stdout, " (auth)");
         fputs ("\n", stdout);
      }
   }

   if (!Config.cfProxy.CacheEnabled) CacheEnabled = false;

   if (ProxyServiceCount)
   {
      /* initialize the proxy file cache (at least parameters) */
      ProxyCacheInit (CacheEnabled);
      if (Config.cfProxy.ServingEnabled)
      {
         ProxyServingEnabled = true;
         fprintf (stdout, "%%%s-I-PROXY, processing enabled\n", Utility);
      }
      else
         fprintf (stdout,
"%%%s-W-PROXY, disabled in configuration, services not enabled!\n",
                  Utility);
   }

   ProxyForwardedBy = Config.cfProxy.ForwardedBy;
   ProxyXForwardedFor = Config.cfProxy.XForwardedFor;
   ProxyUnknownRequestFields = Config.cfProxy.UnknownRequestFields;

   InstanceMutexLock (INSTANCE_MUTEX_HTTPD);
   ProxyAccountingPtr->ServingEnabled = ProxyServingEnabled;
   InstanceMutexUnLock (INSTANCE_MUTEX_HTTPD);

   ProxyVerifyInit ();
}

/****************************************************************************/
/*
************
*** NOTE ***  This function takes a pointer to a request!!!
************

*/

ProxyRequestBegin (REQUEST_STRUCT *rqptr)

{
   int  status;
   char  *cptr, *sptr, *zptr;
   PROXY_TASK  *tkptr;

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

   if (WATCHING(rqptr) && WATCH_MODULE(WATCH_MOD_PROXY))
      WatchThis (rqptr, FI_LI, WATCH_MOD_PROXY, "ProxyRequestBegin()");

   if (!ProxyServiceCount)
   {
      rqptr->rqResponse.HttpStatus = 403;
      ErrorGeneral (rqptr, MsgFor(rqptr,MSG_PROXY_NONE_CONFIGURED), FI_LI);
      RequestEnd (rqptr);
      return;
   }

   if (!ProxyServingEnabled)
   {
      rqptr->rqResponse.HttpStatus = 503;
      ErrorGeneral (rqptr, MsgFor(rqptr,MSG_PROXY_DISABLED), FI_LI);
      RequestEnd (rqptr);
      return;
   }

   if (rqptr->rqHeader.Method == HTTP_METHOD_CONNECT)
   {
      if (!rqptr->ServicePtr->ConnectService)
      {
         rqptr->rqResponse.HttpStatus = 403;
         ErrorGeneral (rqptr, MsgFor(rqptr,MSG_PROXY_NOT_CONNECT), FI_LI);
         RequestEnd (rqptr);
         return;
      }
   }
   else
   {
      if (!rqptr->ServicePtr->ProxyService)
      {
         rqptr->rqResponse.HttpStatus = 403;
         ErrorGeneral (rqptr, MsgFor(rqptr,MSG_PROXY_NOT_SERVICE), FI_LI);
         RequestEnd (rqptr);
         return;
      }
   }

   /* set up the task structure (only ever one per request!) */
   rqptr->ProxyTaskPtr = tkptr =
      (PROXY_TASK*)VmGetHeap (rqptr, sizeof(PROXY_TASK));

   /* point from the task structure to the request structure */
   tkptr->RequestPtr = rqptr;

   /* point to the service structure */
   tkptr->ServicePtr = rqptr->ServicePtr;

   /* if this request is being WATCHed, then this proxy task is too! */
   tkptr->WatchItem = rqptr->WatchItem;

   /* copy the request method number and HTTP version number */
   tkptr->HttpMethod = rqptr->rqHeader.Method;
   tkptr->RequestHttpVersion = rqptr->rqHeader.HttpVersion;

   /* if there is a specific proxy IP address to bind to */
   if (IPADDRESS_IS_SET (&rqptr->rqPathSet.ProxyBindIpAddress))
      IPADDRESS_COPY (&tkptr->BindIpAddress,
                      &rqptr->rqPathSet.ProxyBindIpAddress)

   if (IPADDRESS_IS_SET (&rqptr->rqPathSet.ProxyChainIpAddress))
   {
      /* a chain has been set by mapping rule, overrides any service */
      IPADDRESS_COPY (&tkptr->ChainIpAddress,
                      &rqptr->rqPathSet.ProxyChainIpAddress)
      tkptr->ChainIpPort = rqptr->rqPathSet.ProxyChainPort;
      tkptr->ChainHostPortPtr = tkptr->ChainHostPort;
      /* a local copy is needed because of the volatility of request heap */
      zptr = (sptr = tkptr->ChainHostPort) + sizeof(tkptr->ChainHostPort)-1;
      for (cptr = rqptr->rqPathSet.ProxyChainHostPortPtr;
           *cptr && sptr < zptr;
           *sptr++ = *cptr++);
      *sptr = '\0';
   }
   else
   if (IPADDRESS_IS_SET (&tkptr->ServicePtr->ProxyChainIpAddress))
   {
      /* chaining to the next proxy server in a cascade */
      IPADDRESS_COPY (&tkptr->ChainIpAddress,
                      &tkptr->ServicePtr->ProxyChainIpAddress)
      tkptr->ChainIpPort = tkptr->ServicePtr->ProxyChainPort;
      tkptr->ChainHostPortPtr = tkptr->ServicePtr->ProxyChainHostPort;
   }

   /* allocate a buffer used for creating the MD5 digest, network I/O, etc. */
   if (!tkptr->ResponseBufferPtr)
   {
      tkptr->ResponseBufferPtr = tkptr->ResponseBufferCurrentPtr =
         VmGetHeap (rqptr, ProxyReadBufferSize);
      tkptr->ResponseBufferSize = tkptr->ResponseBufferRemaining =
         ProxyReadBufferSize;
   }

   /* set the lookup retry count to default if necessary */
   if (!tkptr->ProxyLookupRetryCount)
      tkptr->ProxyLookupRetryCount = ProxyHostLookupRetryCount;

   /* a little accounting */
   switch (tkptr->HttpMethod)
   {
      case HTTP_METHOD_CONNECT :
         InstanceGblSecIncrLong (&ProxyAccountingPtr->MethodConnectCount);
         break;
      case HTTP_METHOD_DELETE :
         InstanceGblSecIncrLong (&ProxyAccountingPtr->MethodDeleteCount);
         break;
      case HTTP_METHOD_GET :
         InstanceGblSecIncrLong (&ProxyAccountingPtr->MethodGetCount);
         break;
      case HTTP_METHOD_HEAD :
         InstanceGblSecIncrLong (&ProxyAccountingPtr->MethodHeadCount);
         break;
      case HTTP_METHOD_POST :
         InstanceGblSecIncrLong (&ProxyAccountingPtr->MethodPostCount);
         break;
      case HTTP_METHOD_PUT :
         InstanceGblSecIncrLong (&ProxyAccountingPtr->MethodPutCount);
         break;
      default :
         ErrorExitVmsStatus (SS$_BUGCHECK, ErrorSanityCheck, FI_LI);
   }

   if (tkptr->HttpMethod == HTTP_METHOD_CONNECT)
   {
      /******************/
      /* CONNECT method */
      /******************/

      if (VMSnok (status = ProxyHttpConnectParse (rqptr)))
      {
         RequestEnd (rqptr);
         return;
      }

      ProxyResolveHost (tkptr);
      return;
   }

   /***************************/
   /* GET, POST, etc. methods */
   /***************************/

   /* get the method, host (and port), path and query string */
   if (VMSnok (status = ProxyRequestParse (rqptr)))
   {
      RequestEnd (rqptr);
      return;
   }

   if (WATCHING(tkptr) && WATCH_CATEGORY(WATCH_PROXY))
      WatchThis (rqptr, FI_LI, WATCH_PROXY,
                 "!AZ !AZ!AZ", tkptr->RequestHttpMethodNamePtr,
                 tkptr->RequestHostPort, tkptr->RequestUriPtr);

   if (tkptr->RequestScheme == PROXY_SCHEME_FTP)
   {
      /*************/
      /* proxy FTP */
      /*************/

      /* get (any requested) authentication before we do too much else */
      if (*(cptr = rqptr->rqHeader.QueryStringPtr))
      {
         if (ProxyFtpInQueryString (cptr, "login"))
         {
            if (rqptr->rqHeader.AuthorizationPtr)
            {
               /* our authorization source is "local" not proxy */
               rqptr->rqAuth.RequestAuthorizationPtr =
                  rqptr->rqHeader.AuthorizationPtr;
               BasicAuthorization (rqptr);
            }
            if (!rqptr->RemoteUser[0] ||
                !rqptr->RemoteUserPassword[0])
            {
               rqptr->rqResponse.HttpStatus = 401;
               rqptr->rqAuth.RealmDescrPtr =
                  MsgFor(rqptr,MSG_PROXY_FTP_SERVER);
               ErrorGeneral (rqptr,
                  MsgFor(rqptr,MSG_PROXY_FTP_USERNAME_PWD), FI_LI);
               RequestEnd (rqptr);
               return;
            }
         }
         if (ProxyFtpInQueryString (cptr, "upload"))
         {
            ProxyFtpStoreForm (rqptr);
            RequestEnd (rqptr);
            return;
         }
      }
   }
   else
   if (tkptr->RequestScheme == PROXY_SCHEME_HTTP)
   {
      /**************/
      /* proxy HTTP */
      /**************/

      /* if caching is enabled for this service then see if it can be used */
      if (rqptr->ServicePtr->ProxyFileCacheEnabled)
         if (VMSok (ProxyCacheReadBegin (tkptr)))
            return;
   }

   ProxyResolveHost (tkptr);
}

/*****************************************************************************/
/*
Only called when a cache check has failed to be able to supply the request.
Merely call ProxyResolveHost() to begin an attempt to supply the request from
the remote server.
*/

ProxyReadCacheFailed (PROXY_TASK *tkptr)

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

   if (WATCH_MOD && (!tkptr->RequestPtr || tkptr->RequestPtr->WatchItem) &&
       WATCH_MODULE(WATCH_MOD_PROXY))
      WatchThis (WATCHTK(tkptr), FI_LI, WATCH_MOD_PROXY,
                 "ProxyReadCacheFailed()");

   ProxyResolveHost (tkptr);
} 

/****************************************************************************/
/*
************
*** NOTE ***  This function takes a pointer to a request!!!
************

Parse the various components from the proxy request.
*/

ProxyRequestParse (REQUEST_STRUCT *rqptr)

{
   int  Length;
   char  *cptr, *sptr, *zptr;
   PROXY_TASK  *tkptr;

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

   if (WATCHING(rqptr) && WATCH_MODULE(WATCH_MOD_PROXY))
      WatchThis (rqptr, FI_LI, WATCH_MOD_PROXY,
                 "ProxyRequestParse() !&Z", rqptr->MappedPathPtr);

   /* get a local pointer to the proxy task structure */
   tkptr = rqptr->ProxyTaskPtr;

   cptr = rqptr->rqHeader.RequestUriPtr;
   zptr = (sptr = tkptr->RequestSchemeName) +
          sizeof(tkptr->RequestSchemeName)-1;
   /* this allows a proxy service to be used non-proxy (if mapped) */
   if (*cptr == '/') cptr++;
   while (*cptr && *cptr != ':' && sptr < zptr) *sptr++ = *cptr++;
   if (*cptr && sptr < zptr) *sptr++ = *cptr++;
   if (sptr >= zptr)
   {
      InstanceGblSecIncrLong (&AccountingPtr->RequestErrorCount);
      rqptr->rqResponse.HttpStatus = 502;
      ErrorGeneral (rqptr, MsgFor(rqptr,MSG_REQUEST_FORMAT), FI_LI);
      return (STS$K_ERROR);
   }
   *sptr = '\0';
   if (WATCHING(rqptr) && WATCH_MODULE(WATCH_MOD_PROXY))
      WatchThis (rqptr, FI_LI, WATCH_MOD_PROXY,
                 "!&Z", tkptr->RequestSchemeName);

   if (tkptr->HttpMethod == HTTP_METHOD_CONNECT)
      tkptr->RequestScheme = PROXY_SCHEME_CONNECT;
   else
   if (strsame (tkptr->RequestSchemeName, "http:", -1))
   {
      tkptr->RequestScheme = PROXY_SCHEME_HTTP;
      if (rqptr->ServicePtr->RequestScheme == SCHEME_HTTP)
         InstanceGblSecIncrLong (&ProxyAccountingPtr->GwayHttpHttpCount);
      else
         InstanceGblSecIncrLong (&ProxyAccountingPtr->GwayHttpsHttpCount);
   }
   else
   if (strsame (tkptr->RequestSchemeName, "ftp:", -1))
   {
      tkptr->RequestScheme = PROXY_SCHEME_FTP;
      if (rqptr->ServicePtr->RequestScheme == SCHEME_HTTP)
         InstanceGblSecIncrLong (&ProxyAccountingPtr->GwayHttpFtpCount);
      else
         InstanceGblSecIncrLong (&ProxyAccountingPtr->GwayHttpsFtpCount);
   }
   else
   if (strsame (tkptr->RequestSchemeName, "https:", -1) &&
       tkptr->ServicePtr->SSLclientEnabled)
   {
      /* proxy HTTP-to-SSL gateway "one-shot" request */
      tkptr->RequestScheme = PROXY_SCHEME_HTTPSSL;
      if (rqptr->ServicePtr->RequestScheme == SCHEME_HTTP)
         InstanceGblSecIncrLong (&ProxyAccountingPtr->GwayHttpHttpsCount);
      else
         InstanceGblSecIncrLong (&ProxyAccountingPtr->GwayHttpsHttpsCount);
   }
   else
      tkptr->RequestScheme = 0;

   if (!tkptr->RequestScheme)
   {
      rqptr->rqResponse.HttpStatus = 502;
      ErrorGeneral (rqptr, MsgFor(rqptr,MSG_PROXY_REQUEST_SCHEME), FI_LI);
      return (STS$K_ERROR);
   }

   if (*((unsigned short*)cptr) != '//')
   {
      InstanceGblSecIncrLong (&AccountingPtr->RequestErrorCount);
      rqptr->rqResponse.HttpStatus = 502;
      ErrorGeneral (rqptr, MsgFor(rqptr,MSG_REQUEST_FORMAT), FI_LI);
      return (STS$K_ERROR);
   }
   cptr += 2;

   /*******************************/
   /* check for username:password */
   /*******************************/

   for (sptr = cptr;
        *sptr && *sptr != '@' && *sptr != '/' && *sptr != '?';
        sptr++);
   if (*sptr == '@')
   {
      /* looks like we found one */
      zptr = (sptr = tkptr->UrlUserName) + sizeof(tkptr->UrlUserName)-1;
      while (*cptr && *cptr != ':' && *cptr != '@' && 
             *cptr != '/' && *cptr != '?' && sptr < zptr)
         *sptr++ = *cptr++;
      *sptr = '\0';
      if (sptr >= zptr) cptr = "";
      if (*cptr == ':')
      {
         cptr++;
         zptr = (sptr = tkptr->UrlPassword) + sizeof(tkptr->UrlPassword)-1;
         while (*cptr && *cptr != '@' && *cptr != '/' &&
                *cptr != '?' && sptr < zptr)
            *sptr++ = *cptr++;
         *sptr = '\0';
         if (sptr >= zptr) cptr = "";
      }
      if (*cptr != '@')
      {
         InstanceGblSecIncrLong (&AccountingPtr->RequestErrorCount);
         rqptr->rqResponse.HttpStatus = 502;
         ErrorGeneral (rqptr, MsgFor(rqptr,MSG_REQUEST_FORMAT), FI_LI);
         return (STS$K_ERROR);
      }
      cptr++;
   }

   /*************************/
   /* get server host name  */
   /*************************/

   zptr = (sptr = tkptr->RequestHostName) + sizeof(tkptr->RequestHostName)-1;
   while (*cptr && *cptr != ':' && *cptr != '/' && *cptr != '?' && sptr < zptr)
      *sptr++ = *cptr++;
   *sptr = '\0';
   if (sptr >= zptr) cptr = "";
   if (*cptr && *cptr != ':' && *cptr != '/' && *cptr != '?')
   {
      InstanceGblSecIncrLong (&AccountingPtr->RequestErrorCount);
      rqptr->rqResponse.HttpStatus = 502;
      ErrorGeneral (rqptr, MsgFor(rqptr,MSG_REQUEST_FORMAT), FI_LI);
      return (STS$K_ERROR);
   }
   if (WATCHING(rqptr) && WATCH_MODULE(WATCH_MOD_PROXY))
      WatchThis (rqptr, FI_LI, WATCH_MOD_PROXY,
                 "!&Z", tkptr->RequestHostName);

   /******************************/
   /* get (optional) server port */
   /******************************/

   if (*cptr == ':')
   {
      cptr++;
      if (isdigit(*cptr))
      {
         zptr = (sptr = tkptr->RequestPortString) +
                sizeof(tkptr->RequestPortString)-1;
         while (*cptr && isdigit(*cptr) && sptr < zptr) *sptr++ = *cptr++;
         if (sptr >= zptr)
         {
            InstanceGblSecIncrLong (&AccountingPtr->RequestErrorCount);
            rqptr->rqResponse.HttpStatus = 502;
            ErrorGeneral (rqptr, MsgFor(rqptr,MSG_REQUEST_FORMAT), FI_LI);
            return (STS$K_ERROR);
         }
         *sptr = '\0';
         tkptr->RequestPort = atol (tkptr->RequestPortString);
      }
   }

   if (!tkptr->RequestPort)
   {
      if (tkptr->RequestScheme == PROXY_SCHEME_HTTP)
      {
         tkptr->RequestPort = 80;
         *(unsigned long*)tkptr->RequestPortString = '80\0\0';
      }
      else
      if (tkptr->RequestScheme == PROXY_SCHEME_FTP)
      {
         tkptr->RequestPort = 21;
         *(unsigned long*)tkptr->RequestPortString = '21\0\0';
      }
      else
      if (tkptr->RequestScheme == PROXY_SCHEME_HTTPSSL)
      {
         tkptr->RequestPort = 443;
         *(unsigned long*)tkptr->RequestPortString = '443\0';
      }
   }
   if (WATCHING(rqptr) && WATCH_MODULE(WATCH_MOD_PROXY))
      WatchThis (rqptr, FI_LI, WATCH_MOD_PROXY,
                 "!UL !&Z", tkptr->RequestPort, tkptr->RequestPortString);

   /***************************************/
   /* get request URI and if query string */
   /***************************************/

   if (*cptr == '/')
      tkptr->RequestUriPtr = cptr;
   else
   if (tkptr->RequestScheme == PROXY_SCHEME_FTP)
   {
      /* no trailing slash after host[:port], redirect to add one */
      rqptr->rqResponse.LocationPtr = sptr =
         VmGetHeap (rqptr, rqptr->rqHeader.RequestUriLength+1);
      memcpy (rqptr->rqResponse.LocationPtr,
              rqptr->rqHeader.RequestUriPtr,
              rqptr->rqHeader.RequestUriLength);
      rqptr->rqResponse.LocationPtr[rqptr->rqHeader.RequestUriLength] = '/';
      return (STS$K_ERROR);
   }
   else
      tkptr->RequestUriPtr = "/";

   if (rqptr->rqHeader.QueryStringPtr[0])
   {
      while (*cptr && *cptr != '?') cptr++;
      tkptr->RequestUriQueryStringPtr = cptr;
   }
   else
      tkptr->RequestUriQueryStringPtr = "";

   /*******************************/
   /* point to any request cookie */
   /*******************************/

   tkptr->RequestHttpCookiePtr = rqptr->rqHeader.CookiePtr;

   /***********************/
   /* composite name:port */
   /***********************/

   /* store host name/port as FIRST ITEM in cache data block */
   zptr = (sptr = tkptr->RequestHostPort) + sizeof(tkptr->RequestHostPort);
   for (cptr = tkptr->RequestHostName;
        *cptr && sptr < zptr;
        *sptr++ = *cptr++);
   if (sptr < zptr) *sptr++ = ':';
   for (cptr = tkptr->RequestPortString;
        *cptr && sptr < zptr;
        *sptr++ = *cptr++);
   if (sptr >= zptr)
   {
      rqptr->rqResponse.HttpStatus = 500;
      ErrorGeneralOverflow (rqptr, FI_LI);
      return (STS$K_ERROR);
   }
   *sptr = '\0';
   tkptr->RequestHostPortLength = sptr - tkptr->RequestHostPort;
   if (WATCHING(rqptr) && WATCH_MODULE(WATCH_MOD_PROXY))
      WatchThis (rqptr, FI_LI, WATCH_MOD_PROXY,
                 "!&Z", tkptr->RequestHostPort);

   tkptr->RequestHttpMethod = rqptr->rqHeader.Method;
   tkptr->RequestHttpMethodNamePtr = rqptr->rqHeader.MethodName;
   tkptr->RequestPragmaNoCache = rqptr->PragmaNoCache;

   return (SS$_NORMAL);
} 

/*****************************************************************************/
/*
************
*** NOTE ***  This function takes a pointer to a request!!!
************

HTTP CONNECT method (allows SSL connections thorugh firewall system).  Parse
the host name and optional port from the request.
*/

ProxyHttpConnectParse (REQUEST_STRUCT *rqptr)

{
   PROXY_TASK  *tkptr;
   char  *cptr, *sptr, *zptr;

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

   if (WATCHING(rqptr) && WATCH_MODULE(WATCH_MOD_PROXY))
      WatchThis (rqptr, FI_LI, WATCH_MOD_PROXY,
                 "ProxyHttpConnectParse() !&Z", rqptr->MappedPathPtr);

   tkptr = rqptr->ProxyTaskPtr;

   cptr = rqptr->MappedPathPtr;

   /*************************/
   /* get server host name  */
   /*************************/

   zptr = (sptr = tkptr->RequestHostName) + sizeof(tkptr->RequestHostName)-1;
   while (*cptr && *cptr != ':' && *cptr != '/' && sptr < zptr)
      *sptr++ = *cptr++;

   if (sptr >= zptr || (*cptr && *cptr != ':'))
   {
      InstanceGblSecIncrLong (&AccountingPtr->RequestErrorCount);
      rqptr->rqResponse.HttpStatus = 502;
      ErrorGeneral (rqptr, MsgFor(rqptr,MSG_REQUEST_FORMAT), FI_LI);
      return (STS$K_ERROR);
   }
   *sptr = '\0';

   /******************************/
   /* get (optional) server port */
   /******************************/

   if (*cptr == ':')
   {
      cptr++;
      if (isdigit(*cptr))
      {
         zptr = (sptr = tkptr->RequestPortString) +
                sizeof(tkptr->RequestPortString)-1;
         while (*cptr && isdigit(*cptr) && sptr < zptr) *sptr++ = *cptr++;
         if (sptr >= zptr)
         {
            InstanceGblSecIncrLong (&AccountingPtr->RequestErrorCount);
            rqptr->rqResponse.HttpStatus = 502;
            ErrorGeneral (rqptr, MsgFor(rqptr,MSG_REQUEST_FORMAT), FI_LI);
            return (STS$K_ERROR);
         }
         *sptr = '\0';
         tkptr->RequestPort = atol (tkptr->RequestPortString);
      }
      else
      {
         tkptr->RequestPort = 443;
         memcpy (tkptr->RequestPortString, "443", 4);
      }
   }
   else
   {
      tkptr->RequestPort = 443;
      memcpy (tkptr->RequestPortString, "443", 4);
   }

   /****************************/
   /* build host name and port */
   /****************************/

   zptr = (sptr = tkptr->RequestHostPort) + sizeof(tkptr->RequestHostPort);
   for (cptr = tkptr->RequestHostName;
        *cptr && sptr < zptr;
        *sptr++ = *cptr++);
   if (sptr < zptr) *sptr++ = ':';
   for (cptr = tkptr->RequestPortString;
        *cptr && sptr < zptr;
        *sptr++ = *cptr++);
   if (sptr >= zptr)
   {
      rqptr->rqResponse.HttpStatus = 500;
      ErrorGeneralOverflow (rqptr, FI_LI);
      return (STS$K_ERROR);
   }
   *sptr = '\0';
   tkptr->RequestHostPortLength = sptr - tkptr->RequestHostPort;

   if (WATCHING(rqptr) && WATCH_MODULE(WATCH_MOD_PROXY))
      WatchDataFormatted ("!&Z !UL !&Z !&Z\n",
                          tkptr->RequestHostName,
                          tkptr->RequestPort, tkptr->RequestPortString,
                          tkptr->RequestHostPort);

   return (SS$_NORMAL);
} 

/*****************************************************************************/
/*
If a proxied request is being supplied from cache then end that (basically
close the associated cache file).  If supplied via a network transaction and
the socket was created then shut and close the socket.  If concurrently writing
a request body to the proxied server then just return after closing the socket. 
The body write will receive an error and call this function to close the
request down.  Finally deallocate proxy memory and if an associated request
structure end that request.
*/

ProxyEnd (PROXY_TASK *tkptr)

{
   static long  Addx2 = 2;

   int  status;
   unsigned long  QuadScratch [2];

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

   if (WATCHING(tkptr) && WATCH_MODULE(WATCH_MOD_PROXY))
      WatchThis (WATCHTK(tkptr), FI_LI, WATCH_MOD_PROXY, "ProxyEnd()");

   if (tkptr->ParseInUse ||
       tkptr->CacheFileQio.AcpChannel ||
       tkptr->CacheFileQio.QioChannel ||
       tkptr->LoadFab.fab$w_ifi)
   {
      /* finalize cache file processing */
      ProxyCacheEnd (tkptr);
      return;
   }

   /* if Secure Sockets Layer request */
   if (tkptr->SesolaPtr)
   {
      SesolaNetClientEnd (tkptr);
      return;
   }

   ProxyCloseSocket (tkptr);

   if (tkptr->ProxyReadRawAstFunction ||
       tkptr->ProxyWriteRawAstFunction ||
       tkptr->QueuedBodyRead)
   {
      /* any outstanding proxy-specific I/O then forget it */
      return;
   }

   if (tkptr->HttpMethod == HTTP_METHOD_CONNECT &&
       (tkptr->RequestPtr->rqNet.ReadRawAstFunction ||
        tkptr->RequestPtr->rqNet.WriteRawAstFunction))
   {
      /* any outstanding I/O with the client then forget it */
      NetCloseSocket (tkptr->RequestPtr);
      return;
   }

   if (tkptr->HttpMethod != HTTP_METHOD_CONNECT &&
       (tkptr->RequestPtr->rqNet.ReadRawAstFunction ||
        tkptr->RequestPtr->rqNet.WriteRawAstFunction))
   {
      /* any outstanding I/O with the client then wait 'til it concludes */
      return;
   }

   /*********************************/
   /* finally end the proxy request */
   /*********************************/

   if (tkptr->VerifyRecordPtr) ProxyVerifyRecordReset (tkptr);

   /* a little (double-precision) accounting */
   InstanceMutexLock (INSTANCE_MUTEX_HTTPD);
   if (tkptr->BytesRawTx)
   {
      QuadScratch[0] = tkptr->BytesRawTx;
      QuadScratch[1] = 0;
      lib$addx (&QuadScratch, &ProxyAccountingPtr->QuadBytesRawTx,
                &ProxyAccountingPtr->QuadBytesRawTx, &Addx2);
      if (tkptr->NotCacheable)
      {
         QuadScratch[0] = tkptr->BytesRawTx;
         QuadScratch[1] = 0;
         lib$addx (&QuadScratch, &ProxyAccountingPtr->QuadBytesNotCacheableTx,
                   &ProxyAccountingPtr->QuadBytesNotCacheableTx, &Addx2);
      }
   }
   if (tkptr->BytesRawRx)
   {
      QuadScratch[0] = tkptr->BytesRawRx;
      QuadScratch[1] = 0;
      lib$addx (&QuadScratch, &ProxyAccountingPtr->QuadBytesRawRx,
                &ProxyAccountingPtr->QuadBytesRawRx, &Addx2);
      if (tkptr->NotCacheable)
      {
         QuadScratch[0] = tkptr->BytesRawRx;
         QuadScratch[1] = 0;
         lib$addx (&QuadScratch, &ProxyAccountingPtr->QuadBytesNotCacheableRx,
                   &ProxyAccountingPtr->QuadBytesNotCacheableRx, &Addx2);
      }
   }
   if (tkptr->ProxyCacheReadBytes)
   {
      QuadScratch[0] = tkptr->ProxyCacheReadBytes;
      QuadScratch[1] = 0;
      lib$addx (&QuadScratch, &ProxyAccountingPtr->QuadBytesCacheTx,
                &ProxyAccountingPtr->QuadBytesCacheTx, &Addx2);
      if (tkptr->RequestPtr)
      {
         QuadScratch[0] = tkptr->RequestPtr->rqHeader.RequestHeaderLength;
         QuadScratch[1] = 0;
         lib$addx (&QuadScratch, &ProxyAccountingPtr->QuadBytesCacheRx,
                   &ProxyAccountingPtr->QuadBytesCacheRx, &Addx2);
      }
   }
   InstanceMutexUnLock (INSTANCE_MUTEX_HTTPD);

   if (!tkptr->RequestPtr)
   {
      /* no request heap, free autonomous task memory */
      if (tkptr->ResponseBufferPtr)
         VmFree (tkptr->ResponseBufferPtr, FI_LI);
   }
   else
   {
      /* request heap memory will be freed by request rundown */
      if (tkptr->HttpMethod == HTTP_METHOD_CONNECT)
      {
         if (WATCHING(tkptr) && WATCH_CATEGORY(WATCH_PROXY))
            WatchThis (WATCHTK(tkptr), FI_LI, WATCH_PROXY,
                       "HTTP CONNECT concluded");
      }

      /* indicate this no longer has an associated proxy task */
      tkptr->RequestPtr->ProxyTaskPtr = NULL;

      RequestEnd (tkptr->RequestPtr);
   }
} 

/****************************************************************************/
/*
No need to resolve if this proxy server chains to another.  Otherwise, allow
TcpIpNameToAddress() to asynchronously resolve the name or numeric address into
the IP address and then call ProxyHostConnect() when ready.
*/

ProxyResolveHost (PROXY_TASK *tkptr)

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

   if (WATCHING(tkptr) && WATCH_MODULE(WATCH_MOD_PROXY))
      WatchThis (WATCHTK(tkptr), FI_LI, WATCH_MOD_PROXY,
                 "ProxyResolveHost() !&Z", tkptr->RequestHostName);

   if (IPADDRESS_IS_RESET (&tkptr->ChainIpAddress))
   {
      /************************************/
      /* resolve host using TCP/IP lookup */
      /************************************/

      tkptr->ConnectPort = tkptr->RequestPort;
      tkptr->ConnectHostPortPtr = tkptr->RequestHostPort;

      TcpIpNameToAddress (&tkptr->HostLookup,
                          tkptr->RequestHostName,
                          tkptr->ProxyLookupRetryCount,
                          &ProxyHostConnect,
                          tkptr);
      return;
   }

   /*********************************************************/
   /* this proxy server requests from another proxy server! */
   /*********************************************************/

   if (WATCHING(tkptr) && WATCH_CATEGORY(WATCH_PROXY))
      WatchThis (WATCHTK(tkptr), FI_LI, WATCH_PROXY,
                 "HOST-PROXY-CHAIN !AZ", tkptr->ChainHostPortPtr);

   tkptr->ConnectPort = tkptr->ChainIpPort;
   tkptr->ConnectHostPortPtr = tkptr->ChainHostPortPtr;

   tkptr->HostLookup.LookupIOsb.Status = SS$_NORMAL;
   ProxyHostConnect (tkptr);
}


/****************************************************************************/
/*
Called as an AST by TcpIpNameToAddress().  Check that the host name has been
resolved.  If not report the error. Create a socket and attempt to connect to
the remote, proxied server host.  AST to ProxyHostConnectAst().
*/

ProxyHostConnect (PROXY_TASK *tkptr)

{
   static BOOL  UseFullDuplexClose = true;

   int  status;
   void  *BindSocketNamePtr;
   IO_SB  IOsb;
   REQUEST_STRUCT  *rqptr;
   SOCKADDRIN  *sin4ptr;
   SOCKADDRIN6  *sin6ptr;
   TCP_SOCKET_ITEM  *TcpSocketPtr;
   VMS_ITEM_LIST2  *il2ptr;

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

   if (WATCHING(tkptr) && WATCH_MODULE(WATCH_MOD_PROXY))
      WatchThis (WATCHTK(tkptr), FI_LI, WATCH_MOD_PROXY,
                 "ProxyHostConnect() !&Z !&S",
                 tkptr->ConnectHostPortPtr,
                 tkptr->HostLookup.LookupIOsb.Status);

   rqptr = tkptr->RequestPtr;

   if (VMSnok (tkptr->HostLookup.LookupIOsb.Status))
   {
      /*********************************/
      /* host address resolution error */
      /*********************************/

      if (WATCHING(tkptr) && WATCH_CATEGORY(WATCH_PROXY))
         WatchThis (WATCHTK(tkptr), FI_LI, WATCH_PROXY,
                    "HOST-LOOKUP !AZ !&S !-%!&M",
                    tkptr->RequestHostName,
                    tkptr->HostLookup.LookupIOsb.Status);

      /* dispose of the non-connected channel/socket used for the lookup */
      ProxyCloseSocket (tkptr);

      tkptr->ResponseStatusCode = 502;
      if (rqptr)
      {
         /* request associated with task */
         rqptr->rqResponse.HttpStatus = 502;
         rqptr->rqResponse.ErrorTextPtr = tkptr->RequestHostPort;
         if (tkptr->HostLookup.LookupIOsb.Status == SS$_ENDOFFILE)
            ErrorGeneral (rqptr,  MsgFor(rqptr,MSG_PROXY_HOST_UNKNOWN), FI_LI);
         else
            ErrorVmsStatus (rqptr, tkptr->HostLookup.LookupIOsb.Status, FI_LI);
      }
      ProxyEnd (tkptr);
      return;
   }

   /*******************/
   /* connect to host */
   /*******************/

   if (IPADDRESS_IS_RESET (&tkptr->ChainIpAddress))
   {
      /* if not chaining get the resolved address */
      IPADDRESS_COPY (&tkptr->ConnectIpAddress,
                      &tkptr->HostLookup.IpAddress)
      IPADDRESS_COPY (&tkptr->RequestHostIpAddress,
                      &tkptr->HostLookup.IpAddress)
   }
   else
   {
      /* use the specified chain address */
      IPADDRESS_COPY (&tkptr->ConnectIpAddress, &tkptr->ChainIpAddress)
   }

   if (WATCHING(tkptr) && WATCH_CATEGORY(WATCH_PROXY))
      WatchThis (WATCHTK(tkptr), FI_LI, WATCH_PROXY,
                 "CONNECT !&I,!UL as !&I", &tkptr->ConnectIpAddress,
                 tkptr->ConnectPort, &tkptr->BindIpAddress);

   /* assign a channel to the internet template device */
   status = sys$assign (&TcpIpDeviceDsc, &tkptr->ProxyChannel, 0, 0);
   if (VMSnok (status))
   {
      /* leave it to the AST function to report! */
      tkptr->ProxyConnectIOsb.Status = status;
      SysDclAst (ProxyHostConnectAst, tkptr);
      return;
   }

   if (IPADDRESS_IS_SET (&tkptr->BindIpAddress))
   {
      /* bind the proxy socket to a specific IP address */
      if (IPADDRESS_IS_V4 (&tkptr->BindIpAddress))
      {
         SOCKADDRESS_ZERO4 (&tkptr->ProxyBindSocketName)
         sin4ptr = &tkptr->ProxyBindSocketName.sa.v4;
         sin4ptr->SIN$W_FAMILY = TCPIP$C_AF_INET;
         sin4ptr->SIN$W_PORT = 0;
         IPADDRESS_SET4 (sin4ptr->SIN$L_ADDR, &tkptr->BindIpAddress)

         il2ptr = &tkptr->ProxyBindSocketNameItem;
         il2ptr->buf_len = sizeof(SOCKADDRIN);
         il2ptr->item = 0;
         il2ptr->buf_addr = sin4ptr;
      }
      else
      if (IPADDRESS_IS_V6 (&tkptr->BindIpAddress))
      {
         SOCKADDRESS_ZERO6 (&tkptr->ProxyBindSocketName)
         sin6ptr = &tkptr->ProxyBindSocketName.sa.v6;
         sin6ptr->SIN6$B_FAMILY = TCPIP$C_AF_INET6;
         sin6ptr->SIN6$W_PORT = 0;
         IPADDRESS_SET6 (sin6ptr->SIN6$R_ADDR_OVERLAY.SIN6$T_ADDR,
                         &tkptr->BindIpAddress)

         il2ptr = &tkptr->ProxyBindSocketNameItem;
         il2ptr->buf_len = sizeof(SOCKADDRIN);
         il2ptr->item = 0;
         il2ptr->buf_addr = sin6ptr;
      }
      else
         ErrorExitVmsStatus (SS$_BUGCHECK, ErrorSanityCheck, FI_LI);

      BindSocketNamePtr = (void*)il2ptr;
   }
   else
      BindSocketNamePtr = 0;

   if (IPADDRESS_IS_V4 (&tkptr->ConnectIpAddress))
      TcpSocketPtr = &TcpIpSocket4;
   else
   if (IPADDRESS_IS_V6 (&tkptr->ConnectIpAddress))
      TcpSocketPtr = &TcpIpSocket6;
   else
      ErrorExitVmsStatus (SS$_BUGCHECK, ErrorSanityCheck, FI_LI);

   /* make the channel a TCP, connection-oriented socket */
   for (;;)
   { 
      status = sys$qiow (EfnWait, tkptr->ProxyChannel, IO$_SETMODE,
                         &tkptr->ProxyConnectIOsb, 0, 0,
                         TcpSocketPtr, 0, BindSocketNamePtr,
                         0, UseFullDuplexClose ? &TcpIpFullDuplexCloseOption : 0, 0);
      if (WATCHING(tkptr) && WATCH_MODULE(WATCH_MOD_PROXY))
         WatchThis (WATCHTK(tkptr), FI_LI, WATCH_MOD_PROXY,
                       "sys$qiow() !&S !&S",
                       status, tkptr->FtpDataConnectIOsb.Status);

      if (VMSok (status) && VMSok (tkptr->ProxyConnectIOsb.Status)) break;
      if (!UseFullDuplexClose) break;
      UseFullDuplexClose = false;

      /* Multinet 3.2 UCX driver barfs on FULL_DUPLEX_CLOSE, try without */
      if (VMSok (status) && VMSnok (tkptr->ProxyConnectIOsb.Status))
      {
         /* assign a new channel before retrying */
         sys$dassgn (tkptr->ProxyChannel);
         status = sys$assign (&TcpIpDeviceDsc, &tkptr->ProxyChannel, 0, 0);
         if (VMSnok (status))
         {
            /* leave it to the AST function to report! */
            tkptr->ProxyConnectIOsb.Status = status;
            SysDclAst (ProxyHostConnectAst, tkptr);
            return;
         }
      }
   }

   /* it's a $QIOW so the IO status block is valid */
   if (VMSok (status) && VMSnok (tkptr->ProxyConnectIOsb.Status))
      status = tkptr->ProxyConnectIOsb.Status;
   if (VMSnok (status))
   {
      /* leave it to the AST function to report! */
      tkptr->ProxyConnectIOsb.Status = status;
      SysDclAst (ProxyHostConnectAst, tkptr);
      return;
   }

   if (IPADDRESS_IS_V4 (&tkptr->ConnectIpAddress))
   {
      InstanceMutexLock (INSTANCE_MUTEX_HTTPD);
      ProxyAccountingPtr->ConnectIpv4Count++;
      if (IPADDRESS_IS_V4 (&tkptr->ServicePtr->ServerIpAddress))
         ProxyAccountingPtr->GwayIpv4Ipv4Count++;
      else
         ProxyAccountingPtr->GwayIpv6Ipv4Count++;
      InstanceMutexUnLock (INSTANCE_MUTEX_HTTPD);

      SOCKADDRESS_ZERO4 (&tkptr->ProxySocketName);
      sin4ptr = &tkptr->ProxySocketName.sa.v4;
      sin4ptr->SIN$W_FAMILY = TCPIP$C_AF_INET;
      sin4ptr->SIN$W_PORT = htons (tkptr->ConnectPort);
      IPADDRESS_SET4 (sin4ptr->SIN$L_ADDR, &tkptr->ConnectIpAddress)

      il2ptr = &tkptr->ProxySocketNameItem;
      il2ptr->buf_len = sizeof(SOCKADDRIN);
      il2ptr->item = TCPIP$C_SOCK_NAME;
      il2ptr->buf_addr = sin4ptr;
   }
   else
   if (IPADDRESS_IS_V6 (&tkptr->ConnectIpAddress))
   {
      InstanceMutexLock (INSTANCE_MUTEX_HTTPD);
      ProxyAccountingPtr->ConnectIpv6Count++;
      if (IPADDRESS_IS_V4 (&tkptr->ServicePtr->ServerIpAddress))
         ProxyAccountingPtr->GwayIpv4Ipv6Count++;
      else
         ProxyAccountingPtr->GwayIpv6Ipv6Count++;
      InstanceMutexUnLock (INSTANCE_MUTEX_HTTPD);

      SOCKADDRESS_ZERO6 (&tkptr->ProxySocketName);
      sin6ptr = &tkptr->ProxySocketName.sa.v6;
      sin6ptr->SIN6$B_FAMILY = TCPIP$C_AF_INET6;
      sin6ptr->SIN6$W_PORT = htons (tkptr->ConnectPort);
      IPADDRESS_SET6 (sin6ptr->SIN6$R_ADDR_OVERLAY.SIN6$T_ADDR,
                      &tkptr->ConnectIpAddress)

      il2ptr = &tkptr->ProxySocketNameItem;
      il2ptr->buf_len = sizeof(SOCKADDRIN6);
      il2ptr->item = TCPIP$C_SOCK_NAME;
      il2ptr->buf_addr = sin6ptr;
   }
   else
      ErrorExitVmsStatus (SS$_BUGCHECK, ErrorSanityCheck, FI_LI);

   status = sys$qio (EfnNoWait, tkptr->ProxyChannel, IO$_ACCESS,
                     &tkptr->ProxyConnectIOsb, &ProxyHostConnectAst, tkptr,
                     0, 0, &tkptr->ProxySocketNameItem, 0, 0, 0);

   if (WATCHING(rqptr) && WATCH_MODULE(WATCH_MOD_PROXY))
      WatchDataFormatted ("sys$qio() !&S !&S\n",
                          status, tkptr->ProxyConnectIOsb.Status);

   if (VMSnok (status))
   {
      /* leave it to the AST function to report! */
      tkptr->ProxyConnectIOsb.Status = status;
      SysDclAst (ProxyHostConnectAst, tkptr);
      return;
   }
}

/****************************************************************************/
/*
Called as an AST from ProxyHostConnect().  The remote server host connect
attempt has completed and either been successful or returned an error status. 
If an error then explain it and end proxy processing.  If successful then begin
processing the request scheme.
*/

ProxyHostConnectAst (PROXY_TASK *tkptr)

{
   int  status;
   char  *cptr;
   REQUEST_STRUCT  *rqptr;

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

   if (WATCHING(tkptr) && WATCH_MODULE(WATCH_MOD_PROXY))
      WatchThis (WATCHTK(tkptr), FI_LI, WATCH_MOD_PROXY,
                 "ProxyHostConnectAst() !&F !&Z !&S", ProxyHostConnectAst,
                 tkptr->ConnectHostPortPtr, tkptr->ProxyConnectIOsb.Status);

   rqptr = tkptr->RequestPtr;

   if (VMSnok (tkptr->ProxyConnectIOsb.Status))
   {
      /*****************/
      /* connect error */
      /*****************/

      if (WATCHING(tkptr) && WATCH_CATEGORY(WATCH_PROXY))
         WatchThis (rqptr, FI_LI, WATCH_PROXY,
                    "CONNECT !&S %!&M",
                    tkptr->ProxyConnectIOsb.Status,
                    tkptr->ProxyConnectIOsb.Status);

      /* dispose of the non-connected channel/socket used for the lookup */
      ProxyCloseSocket (tkptr);

      tkptr->ResponseStatusCode = 502;
      if (rqptr)
      {
         /* request associated with task */
         rqptr->rqResponse.HttpStatus = 502;

         switch (tkptr->ProxyConnectIOsb.Status)
         {
            case PROXY_ERROR_CONNECT_REFUSED :

               if (IPADDRESS_IS_SET (&tkptr->ChainIpAddress))
                  cptr = MsgFor(rqptr,MSG_PROXY_CHAIN_REFUSED);
               else
                  cptr = MsgFor(rqptr,MSG_PROXY_CONNECT_REFUSED);

               ErrorGeneral (rqptr, cptr, FI_LI);
               break;

            case PROXY_ERROR_HOST_UNREACHABLE :

               if (IPADDRESS_IS_SET (&tkptr->ChainIpAddress))
                  cptr = MsgFor(rqptr,MSG_PROXY_CHAIN_UNREACHABLE);
               else
                  cptr = MsgFor(rqptr,MSG_PROXY_HOST_UNREACHABLE);

               ErrorGeneral (rqptr, cptr, FI_LI);
               break;

            case PROXY_ERROR_HOST_TIMEOUT :

               if (IPADDRESS_IS_SET (&tkptr->ChainIpAddress))
                  cptr = MsgFor(rqptr,MSG_PROXY_CHAIN_UNREACHABLE);
               else
                  cptr = MsgFor(rqptr,MSG_PROXY_HOST_UNREACHABLE);

               ErrorGeneral (rqptr, cptr, FI_LI);
               break;

            default :

               if (IPADDRESS_IS_SET (&tkptr->ChainIpAddress))
               {
                  rqptr->rqResponse.ErrorTextPtr =
                     MsgFor(rqptr,MSG_PROXY_CHAIN_FAILURE);
                  rqptr->rqResponse.ErrorOtherTextPtr =
                     tkptr->ChainHostPortPtr;
               }
               else
                  rqptr->rqResponse.ErrorTextPtr = tkptr->RequestHostPort;
               ErrorVmsStatus (rqptr, tkptr->ProxyConnectIOsb.Status, FI_LI);
         }
      }

      /* invalidate any entry (still) in the host name/address cache */
      TcpIpCacheSetEntry (tkptr->RequestHostName,
                          strlen(tkptr->RequestHostName),
                          NULL);

      ProxyEnd (tkptr);
      return;
   }

   if (tkptr->HttpMethod == HTTP_METHOD_CONNECT)
   {
      /*************************/
      /* HTTP "CONNECT" method */
      /*************************/

      if (WATCHING(tkptr) && WATCH_CATEGORY(WATCH_PROXY))
         WatchThis (rqptr, FI_LI, WATCH_PROXY,
                    "HTTP CONNECT established");

      if (IPADDRESS_IS_SET (&tkptr->ChainIpAddress))
      {
         /* assume it's successful if the chain accepts it */
         if (rqptr) rqptr->rqResponse.HttpStatus = 200;

         ProxyWriteRaw (tkptr, &ProxyHttpConnectProxyWriteAst,
                        rqptr->rqNet.ReadBufferPtr,
                        rqptr->rqNet.ReadIOsb.Count);

         ProxyReadRaw (tkptr, &ProxyHttpConnectProxyReadAst,
                       tkptr->ResponseBufferPtr,
                       tkptr->ResponseBufferSize);
      }
      else
      {
         ResponseHeader (rqptr, 200, NULL, -1, NULL, NULL);
         rqptr->rqResponse.HeaderSent = true;

         NetWriteRaw (rqptr, &ProxyHttpConnectNetWriteAst,
                      rqptr->rqResponse.HeaderPtr,
                      rqptr->rqResponse.HeaderLength);

         NetReadRaw (rqptr, &ProxyHttpConnectNetReadAst,
                     rqptr->rqNet.ReadBufferPtr,
                     rqptr->rqNet.ReadBufferSize);
      }

      return;
   }

   /*************************************/ 
   /* HTTP "GET", "POST", etc., methods */
   /*************************************/ 

   if (tkptr->ServicePtr->SSLclientEnabled &&
       (tkptr->ConnectPort == DEFAULT_HTTPS_PORT ||
        tkptr->RequestScheme == PROXY_SCHEME_HTTPSSL))
      SesolaNetClientBegin (tkptr);
   else
   if (tkptr->RequestScheme == PROXY_SCHEME_HTTP)
      ProxyWriteRequest (tkptr);
   else
   if (tkptr->RequestScheme == PROXY_SCHEME_FTP)
      ProxyFtpLifeCycle (tkptr);
   else
      ErrorExitVmsStatus (SS$_BUGCHECK, ErrorSanityCheck, FI_LI);
}

/****************************************************************************/
/*
Called from ProxyHostConnectAst() or SesolaNetClientConnect() to rebuild and
then write the request to the proxied server.
*/

ProxyWriteRequest (PROXY_TASK *tkptr)

{
   int  status;

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

   if (WATCHING(tkptr) && WATCH_MODULE(WATCH_MOD_PROXY))
      WatchThis (WATCHTK(tkptr), FI_LI, WATCH_MOD_PROXY,
                 "ProxyWriteRequest()");

   /* rebuild, then write request header */
   ProxyRebuildRequest (tkptr);

   if (WATCHING(tkptr) && WATCH_CATEGORY(WATCH_PROXY_REQU_HDR))
   {
      WatchThis (WATCHTK(tkptr), FI_LI, WATCH_PROXY_REQU_HDR,
                 "REQUEST HEADER !UL bytes", tkptr->RebuiltRequestLength);
      WatchData (tkptr->RebuiltRequestPtr, tkptr->RebuiltRequestLength);
   }

   if (tkptr->SesolaPtr)
      SesolaNetWrite (tkptr->SesolaPtr, &ProxyWriteRequestAst,
                      tkptr->RebuiltRequestPtr,
                      tkptr->RebuiltRequestLength);
   else
      ProxyWriteRaw (tkptr, &ProxyWriteRequestAst,
                     tkptr->RebuiltRequestPtr,
                     tkptr->RebuiltRequestLength);
}

/****************************************************************************/
/*
AST completion of read from connection (CONNECT direct or chained proxy).
Check status.  If OK write it to the client connection.
*/

ProxyHttpConnectProxyReadAst (PROXY_TASK *tkptr)

{
   REQUEST_STRUCT *rqptr;

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

   if (WATCHING(tkptr) && WATCH_MODULE(WATCH_MOD_PROXY))
      WatchThis (WATCHTK(tkptr), FI_LI, WATCH_MOD_PROXY,
                 "ProxyHttpConnectProxyReadAst() !&F !&X !UL",
                 &ProxyHttpConnectProxyReadAst,
                 tkptr->ProxyReadIOsb.Status,
                 tkptr->ProxyReadIOsb.Count);

   rqptr = tkptr->RequestPtr;

   if (VMSnok (tkptr->ProxyReadIOsb.Status))
   {
      ProxyEnd (tkptr);
      return;
   }

   NetWriteRaw (tkptr->RequestPtr, &ProxyHttpConnectNetWriteAst,
                tkptr->ResponseBufferPtr,
                tkptr->ProxyReadIOsb.Count);
}

/****************************************************************************/
/*
************
*** NOTE ***  This function takes a pointer to a request!!!
************

A write to the client has completed.  Check status.  If OK read more from the
proxy connection (CONNECT direct or via a chained proxy).
*/

ProxyHttpConnectNetWriteAst (REQUEST_STRUCT *rqptr)

{
   PROXY_TASK  *tkptr;

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

   if (WATCHING(rqptr) && WATCH_MODULE(WATCH_MOD_PROXY))
      WatchThis (rqptr, FI_LI, WATCH_MOD_PROXY,
                 "ProxyHttpConnectNetWriteAst() !&F !&S !UL",
                 &ProxyHttpConnectNetWriteAst,
                 rqptr->rqNet.WriteIOsb.Status,
                 rqptr->rqNet.WriteIOsb.Count);

   tkptr = rqptr->ProxyTaskPtr; 

   if (VMSnok (rqptr->rqNet.WriteIOsb.Status))
   {
      ProxyEnd (tkptr);
      return;
   }

   ProxyReadRaw (tkptr, &ProxyHttpConnectProxyReadAst,
                 tkptr->ResponseBufferPtr, tkptr->ResponseBufferSize);
}

/****************************************************************************/
/*
************
*** NOTE ***  This function takes a pointer to a request!!!
************

A read from the client has completed.  Check status.  If OK write it to the
connection (CONNECT direct or chained proxy).
*/

ProxyHttpConnectNetReadAst (REQUEST_STRUCT *rqptr)

{
   PROXY_TASK  *tkptr;

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

   if (WATCHING(rqptr) && WATCH_MODULE(WATCH_MOD_PROXY))
      WatchThis (rqptr, FI_LI, WATCH_MOD_PROXY,
                 "ProxyHttpConnectNetReadAst() !&F !&S !UL",
                 &ProxyHttpConnectNetReadAst,
                 rqptr->rqNet.ReadIOsb.Status,
                 rqptr->rqNet.ReadIOsb.Count);

   tkptr = rqptr->ProxyTaskPtr; 

   if (VMSnok (rqptr->rqNet.ReadIOsb.Status))
   {
      ProxyEnd (tkptr);
      return;
   }

   ProxyWriteRaw (tkptr, &ProxyHttpConnectProxyWriteAst,
                  rqptr->rqNet.ReadBufferPtr, rqptr->rqNet.ReadIOsb.Count);
}

/****************************************************************************/
/*
A write to the connection (CONNECT direct or chained proxy) has completed. 
Check status.  If OK read more from the client connection.
*/

ProxyHttpConnectProxyWriteAst (PROXY_TASK *tkptr)

{
   REQUEST_STRUCT *rqptr;

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

   if (WATCHING(tkptr) && WATCH_MODULE(WATCH_MOD_PROXY))
      WatchThis (WATCHTK(tkptr), FI_LI, WATCH_MOD_PROXY,
                 "ProxyHttpConnectProxyWriteAst() !&F !&S !UL\n",
                 &ProxyHttpConnectProxyWriteAst,
                 tkptr->ProxyWriteIOsb.Status,
                 tkptr->ProxyWriteIOsb.Count);

   rqptr = tkptr->RequestPtr;

   if (VMSnok (tkptr->ProxyWriteIOsb.Status))
   {
      ProxyEnd (tkptr);
      return;
   }

   NetReadRaw (rqptr, &ProxyHttpConnectNetReadAst,
               rqptr->rqNet.ReadBufferPtr, rqptr->rqNet.ReadBufferSize);
}

/****************************************************************************/
/*
Called as an AST from ProxyWriteRequest().  The write of the request header to
the remote server has completed. If the is a body to the request (i.e. a POST
or PUT method) then call the function to BEGIN CONCURRENTLY writing that.  If
no body check the status of the proxy write. If not successful then report the
error and end the proxy processing.  If OK then queue a read from the remote
server to begin to get the response.
*/

ProxyWriteRequestAst (PROXY_TASK *tkptr)

{
   int  DataLength;
   char  *cptr, *sptr, *zptr,
         *DataPtr;
   REQUEST_STRUCT *rqptr;

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

   if (WATCHING(tkptr) && WATCH_MODULE(WATCH_MOD_PROXY))
      WatchThis (WATCHTK(tkptr), FI_LI, WATCH_MOD_PROXY,
                 "ProxyWriteRequestAst() !&F !&S",
                 &ProxyWriteRequestAst, tkptr->ProxyWriteIOsb.Status);

   rqptr = tkptr->RequestPtr;

   if (VMSnok (tkptr->ProxyWriteIOsb.Status))
   {
      /***********************/
      /* write request error */
      /***********************/

      if (tkptr->ProxyChannel)
      {
         /* the socket has not been deliberately closed, so ... */
         tkptr->ResponseStatusCode = 502;
         if (rqptr)
         {
            /* a request associated with this proxy task */
            rqptr->rqResponse.HttpStatus = 502;
            rqptr->rqResponse.ErrorTextPtr = tkptr->RequestHostPort;
            ErrorVmsStatus (rqptr, tkptr->ProxyWriteIOsb.Status, FI_LI);
         }
      }

      /* toodleoo */
      ProxyEnd (tkptr);
      return;
   }

   /**********************************************************/
   /* concurrently send body as well as waiting for response */
   /**********************************************************/

   if (rqptr->rqHeader.ContentLength)
   {
      BodyReadBegin (rqptr, &ProxyWriteRequestBody, NULL);
      tkptr->QueuedBodyRead++;
   }

   /*************************************/
   /* begin (wait for) reading response */
   /*************************************/

   tkptr->ResponseHeaderPtr = tkptr->ResponseBufferCurrentPtr;
   tkptr->ResponseBodyLength =
      tkptr->ResponseHeaderLength =
      tkptr->ResponseConsecutiveNewLineCount = 0;

   /* responses can quite legitimately have a content-length of zero */
   tkptr->ResponseContentLength = -1;

   if (tkptr->SesolaPtr)
      SesolaNetRead (tkptr->SesolaPtr, &ProxyReadResponseAst,
                     tkptr->ResponseBufferCurrentPtr,
                     tkptr->ResponseBufferRemaining);
   else
      ProxyReadRaw (tkptr, &ProxyReadResponseAst,
                    tkptr->ResponseBufferCurrentPtr,
                    tkptr->ResponseBufferRemaining);
}

/*****************************************************************************/
/*
A network read of the request body has completed and BodyReadAst() has called
this function as an AST.  Write it to the remote server with a completion AST
to ProxyWriteRequestBodyAst().
*/ 

ProxyWriteRequestBody (REQUEST_STRUCT *rqptr)

{
   int  status;
   PROXY_TASK  *tkptr;

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

   tkptr = rqptr->ProxyTaskPtr;

   if (WATCHING(rqptr) && WATCH_MODULE(WATCH_MOD_PROXY))
      WatchThis (rqptr, FI_LI, WATCH_MOD_PROXY,
         "ProxyWriteRequestBody() !&F !&S !&X !UL",
         &ProxyWriteRequestBody,
         rqptr->rqBody.DataStatus, rqptr->rqBody.DataPtr,
         rqptr->rqBody.DataCount);

   if (tkptr->QueuedBodyRead) tkptr->QueuedBodyRead--;

   if (rqptr->rqBody.DataStatus == SS$_ENDOFFILE)
   {
      /****************************/
      /* request body is finished */
      /****************************/

      /* just finish up this concurrent activity */
      return;
   }

   if (VMSnok (rqptr->rqBody.DataStatus))
   {
      ProxyEnd (tkptr);
      return;
   }

   if (tkptr->SesolaPtr)
      SesolaNetWrite (tkptr->SesolaPtr, &ProxyWriteRequestBodyAst,
                      rqptr->rqBody.DataPtr, rqptr->rqBody.DataCount);
   else
      ProxyWriteRaw (tkptr, &ProxyWriteRequestBodyAst,
                     rqptr->rqBody.DataPtr, rqptr->rqBody.DataCount);
}

/*****************************************************************************/
/*
A queued write to the remote server has completed.
Get more of the request body.
*/ 

ProxyWriteRequestBodyAst (PROXY_TASK *tkptr)

{
   int  status;
   REQUEST_STRUCT  *rqptr;

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

   if (WATCHING(tkptr) && WATCH_MODULE(WATCH_MOD_PROXY))
      WatchThis (WATCHTK(tkptr), FI_LI, WATCH_MOD_PROXY,
                 "ProxyWriteRequestBodyAst() !&F !&S",
                 &ProxyWriteRequestBodyAst, tkptr->ProxyWriteIOsb.Status);

   rqptr = tkptr->RequestPtr;

   if (VMSnok (tkptr->ProxyWriteIOsb.Status))
   {
      /* write body error */
      if (tkptr->ProxyChannel)
      {
         /* only if the remote server has not responded do we report this */
         if (!tkptr->ResponseStatusCode)
         {
            /* the socket has not been deliberately closed */
            tkptr->ResponseStatusCode = 502;
            if (rqptr)
            {
               /* a request associated with this proxy task */
               rqptr->rqResponse.HttpStatus = 502;
               rqptr->rqResponse.ErrorTextPtr = tkptr->RequestHostPort;
               ErrorVmsStatus (rqptr, tkptr->ProxyWriteIOsb.Status, FI_LI);
            }
         }
      }

      /* finale */
      ProxyEnd (tkptr);
      return;
   }

   /* get more from the client */
   BodyRead (rqptr);
}

/****************************************************************************/
/*
Called as an AST when the read of a buffer from the proxied server completes.
It checks for, and analyzes, the response header.  When the response header
has been analyzed a decision is made as to whether the response is cachable.
If cacheable this function stops executing while the cache file is created.
When that is complete ProxyReadResponseCacheWrite() is called as an AST to
write the first buffer of data into the file.  If not cacheable then if
associated with a request the data is written to the client via the network.
*/

ProxyReadResponseAst (PROXY_TASK *tkptr)

{
   int  status;
   char  *cptr, *sptr, *zptr;
   REQUEST_STRUCT  *rqptr;

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

   if (WATCHING(tkptr) && WATCH_MODULE(WATCH_MOD_PROXY))
      WatchThis (WATCHTK(tkptr), FI_LI, WATCH_MOD_PROXY,
                 "ProxyReadResponseAst() !&F !&X !UL", &ProxyReadResponseAst,
                 tkptr->ProxyReadIOsb.Status, tkptr->ProxyReadIOsb.Count);

   rqptr = tkptr->RequestPtr;

   if (VMSnok (tkptr->ProxyReadIOsb.Status))
   {
      /**************/
      /* read error */
      /**************/

      if (tkptr->ProxyChannel)
      {
         /* the socket has not been deliberately shut down */

         if (tkptr->ResponseBytes)
         {
            /* we got some bytes back from the remote server */
            if (tkptr->ResponseHttpVersion != HTTP_VERSION_0_9 &&
                !tkptr->ResponseHeaderLength)
            {
               /* header was not completely received */
               tkptr->ResponseStatusCode = 502;
               if (rqptr)
               {
                  /* request associated with task */
                  rqptr->rqResponse.HttpStatus = 502;
                  ErrorGeneral (rqptr,
                     MsgFor(rqptr,MSG_PROXY_RESPONSE_HEADER), FI_LI);
               }
            }
         }
         else
         {
            /* absolutely no bytes at all from the remote server */
            tkptr->ResponseStatusCode = 502;
            if (rqptr)
            {
               /* request associated with task */
               rqptr->rqResponse.HttpStatus = 502;
               rqptr->rqResponse.ErrorTextPtr = tkptr->RequestHostPort;
               switch (tkptr->ProxyReadIOsb.Status)
               {
                  case PROXY_ERROR_HOST_DISCONNECTED :
                     /* disconnected by third party */
                     ErrorGeneral (rqptr,
                        MsgFor(rqptr,MSG_PROXY_HOST_DISCONNECTED), FI_LI);
                     break;
                  default :
                     ErrorVmsStatus (rqptr, tkptr->ProxyReadIOsb.Status, FI_LI);
               }
            }
         }
      }

      /* finis */
      ProxyEnd (tkptr);
      return;
   }

   tkptr->ResponseBytes += tkptr->ProxyReadIOsb.Count;

   if (tkptr->ResponseConsecutiveNewLineCount < 2)
   {                                                                        
      /***************************/
      /* examine response header */
      /***************************/

      tkptr->ResponseBufferCurrentPtr += tkptr->ProxyReadIOsb.Count;
      tkptr->ResponseBufferRemaining -= tkptr->ProxyReadIOsb.Count;

      status = ProxyFindResponseHeader (tkptr);

      if (VMSnok (status))
      {
         tkptr->ResponseStatusCode = 502;
         if (rqptr)
         {
            /* request associated with task */
            rqptr->rqResponse.HttpStatus = 502;
            ErrorGeneral (rqptr,
               MsgFor(rqptr,MSG_PROXY_RESPONSE_HEADER), FI_LI);
         }

         ProxyEnd (tkptr);
         return;
      }

      if (tkptr->ResponseConsecutiveNewLineCount < 2)
      {
         /*******************************************/
         /* entire header has not yet been received */
         /*******************************************/

         if (tkptr->SesolaPtr)
            SesolaNetRead (tkptr->SesolaPtr, &ProxyReadResponseAst,
                           tkptr->ResponseBufferCurrentPtr,
                           tkptr->ResponseBufferRemaining);
         else
            ProxyReadRaw (tkptr, &ProxyReadResponseAst,
                          tkptr->ResponseBufferCurrentPtr,
                          tkptr->ResponseBufferRemaining);
         return;
      }

      /****************************/
      /* header has been received */
      /****************************/

      if (tkptr->ResponseHttpVersion == HTTP_VERSION_0_9)
      {
         if (WATCHING(tkptr) && WATCH_CATEGORY(WATCH_PROXY_RESP_HDR))
         {
            WatchThis (rqptr, FI_LI, WATCH_PROXY_RESP_HDR,
                       "RESPONSE HTTP/0.9");
         }
      }
      else
      {
         if (WATCHING(tkptr) && WATCH_CATEGORY(WATCH_PROXY_RESP_HDR))
         {
            WatchThis (rqptr, FI_LI, WATCH_PROXY_RESP_HDR,
                       "RESPONSE HEADER !UL bytes",
                       tkptr->ResponseHeaderLength);
            WatchData (tkptr->ResponseHeaderPtr,
                       tkptr->ResponseHeaderLength);
         }
      }

      if ((tkptr->ResponseStatusCode == 301 ||
           tkptr->ResponseStatusCode == 302) &&
           rqptr->ProxyReverseLocationPtr)
      {
         /* this may adjust buffer pointers and lengths! */
         ProxyRebuildLocation (tkptr);
      }

      /* request associated with task */
      if (rqptr) rqptr->rqResponse.HttpStatus = tkptr->ResponseStatusCode;

      /* this portion to (any) cache file (includes cache file description) */
      tkptr->ResponseBufferCachePtr = tkptr->ResponseBufferPtr;
      tkptr->ResponseBufferCacheCount = tkptr->ResponseBufferCurrentPtr -
                                        tkptr->ResponseBufferPtr;

      /* this portion to (any) network client (excludes cache description) */
      tkptr->ResponseBufferNetPtr = tkptr->ResponseHeaderPtr;
      tkptr->ResponseBufferNetCount = tkptr->ResponseBufferCurrentPtr -
                                      tkptr->ResponseHeaderPtr;

      if (tkptr->ResponseBufferCurrentPtr >
          tkptr->ResponseHeaderPtr + tkptr->ResponseHeaderLength)
      {
         /******************************************************/
         /* (some of) the response body has also been received */
         /******************************************************/

         tkptr->ResponseBodyLength =
            tkptr->ResponseBufferCurrentPtr -
            (tkptr->ResponseHeaderPtr + tkptr->ResponseHeaderLength);

         if (WATCHING(tkptr) && WATCH_CATEGORY(WATCH_PROXY_RESP_BDY))
         {
            WatchThis (rqptr, FI_LI, WATCH_PROXY_RESP_BDY,
                       "RESPONSE BODY !UL bytes",
                       tkptr->ResponseBodyLength);
            WatchDataDump (tkptr->ResponseHeaderPtr +
                           tkptr->ResponseHeaderLength,
                           tkptr->ResponseBodyLength);
         }
      }

      if (tkptr->ServicePtr->ProxyFileCacheEnabled &&
          tkptr->ProxyCacheSuitable)
      {
         /***********************/
         /* begin proxy caching */
         /***********************/

         status = ProxyCacheLoadBegin (tkptr);

         /* error status means the file was not a candidate for being cached */
         if (VMSok (status)) return;
      }
   }
   else
   {
      /*******************************/
      /* just receiving the body now */
      /*******************************/

      tkptr->ResponseBodyLength += tkptr->ProxyReadIOsb.Count;

      /* both cache file and network contents are identical from here */
      tkptr->ResponseBufferNetPtr =
         tkptr->ResponseBufferCachePtr =
         tkptr->ResponseBufferPtr;
      tkptr->ResponseBufferNetCount =
         tkptr->ResponseBufferCacheCount =
         tkptr->ProxyReadIOsb.Count;

      if (WATCHING(tkptr) && WATCH_CATEGORY(WATCH_PROXY_RESP_BDY))
      {
         WatchThis (rqptr, FI_LI, WATCH_PROXY_RESP_BDY,
                    "RESPONSE BODY !UL bytes", tkptr->ProxyReadIOsb.Count);
         WatchDataDump (tkptr->ResponseBufferPtr,
                        tkptr->ProxyReadIOsb.Count);
      }
   }

   /*******************************************************/
   /* to cache file (then any client) or direct to client */
   /*******************************************************/

   if (tkptr->LoadFab.fab$w_ifi)
      ProxyCacheLoadWrite (tkptr);
   else
      ProxyResponseNetWrite (tkptr);
}

/****************************************************************************/
#ifdef COMMENTS_WITH_COMMENTS
/*
The reverse-proxy response header "Location:" field needs to be rewritten.
The original mapping rule would have been something like

  redirect  /foo/*  /http://foo.bar/*  proxy=reverse=location=/foo/

Check the "Location:" field for the proxied-to host name and if so rewrite it
to use the proxy server and the path indicated.  If the location field does not
contain the proxied-to host name then assume it's not being redirected back to
the same service.

If the 'proxy=reverse=location=<string>' ends in an asterisk the entire 302
"Location:" URL is appended (rather than just the path) resulting in something
along the lines of

  Location: http://original.host/foo/http://foo.bar/example/

which once redirected by the client can be subsequently tested for and some
action made according to the content (just a bell or whistle ;^).

The original scheme, host and 'proxy=reverse=location=<string>' information is
conveyed across the reverse-proxy redirect using redirect-persistent storage.
*/ 
#endif /* COMMENTS_WITH_COMMENTS */

ProxyRebuildLocation (PROXY_TASK *tkptr)

{
   boolean  RebuildLocation,
            WildcardLocation;
   int  ExtraLength,
        LocationLength,
        RequiredLength;
   char  ch;
   char  *cptr, *lptr, *sptr, *zptr,
         *LocationUrlPtr;
   char  Scratch [1024];
   REQUEST_STRUCT  *rqptr;

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

   if (WATCHING(tkptr) && WATCH_MODULE(WATCH_MOD_PROXY))
      WatchThis (tkptr->RequestPtr, FI_LI, WATCH_MOD_PROXY,
                 "ProxyRebuildLocation()");

   rqptr = tkptr->RequestPtr;

   for (cptr = rqptr->ProxyReverseLocationPtr; *cptr; *cptr++);
   if (cptr > rqptr->ProxyReverseLocationPtr) cptr--;
   if (*cptr == '*')
   {
      WildcardLocation = true;
      *cptr = '\0';
   }
   else
      WildcardLocation = false;

   RebuildLocation = false;

   /* temporary null termination of the header */
   ch = tkptr->ResponseHeaderPtr[tkptr->ResponseHeaderLength];
   tkptr->ResponseHeaderPtr[tkptr->ResponseHeaderLength] = '\0';

   cptr = tkptr->ResponseHeaderPtr;
   while (*cptr)
   {
      while (EOL(*cptr)) cptr++;
      if (toupper(*cptr) != 'L' || !strsame (cptr, "Location:", 9))
      {
         while (!EOL(*cptr)) cptr++;
         continue;
      }

      /* found the location field */
      cptr += 9;
      while (ISLWS(*cptr)) cptr++;
      /* note the start of the location field */
      LocationUrlPtr = lptr = cptr;

      if (WildcardLocation)
      {
         /* we're going to use *whatever* is in the location field */
         RebuildLocation = true;
         break;
      }

      while (*lptr && *lptr != ':' && NOTEOL(*lptr)) lptr++;
      if (*lptr == ':') lptr++;
      if (*lptr == '/') lptr++;
      if (*lptr == '/') lptr++;
      /* this will contain the redirect-to/proxied-to host */
      sptr = rqptr->rqHeader.HostPtr;
      while (*sptr && *lptr && tolower(*sptr) == tolower(*lptr))
      {
         sptr++;
         lptr++;
      }
      if (!*sptr && (!*lptr || *lptr == '/')) RebuildLocation = true;
      break;
   }

   /* remove the temporary null termination */
   tkptr->ResponseHeaderPtr[tkptr->ResponseHeaderLength] = ch;

   if (WATCHING(tkptr) && WATCH_MODULE(WATCH_MOD_PROXY))
      WatchThis (tkptr->RequestPtr, FI_LI, WATCH_MOD_PROXY,
                 "!&?REBUILD\rNO REBUILD\r", RebuildLocation);

   if (!RebuildLocation) return;

   zptr = (sptr = Scratch) + sizeof(Scratch);
   for (cptr = rqptr->ProxyReverseLocationPtr;
        *cptr && sptr < zptr;
        *sptr++ = *cptr++);
   /* finished with this now */
   rqptr->ProxyReverseLocationPtr = NULL;
   /* prevent consecutive slashes */
   if (*(sptr-1) == '/' && *lptr == '/') lptr++;
   while (*lptr && !ISLWS(*lptr) && NOTEOL(*lptr) && sptr < zptr)
      *sptr++ = *lptr++;
   if (sptr >= zptr)
   {
      ErrorNoticed (SS$_RESULTOVF, "ProxyRebuildLocation()", FI_LI);
      return;
   }
   *sptr = '\0';

   LocationLength = lptr - LocationUrlPtr;
   RequiredLength = sptr - Scratch;
   /* no - not what the SPAMers are offering (*%!#*+! expletives deleted) */
   ExtraLength = RequiredLength - LocationLength;

   if (WATCHING(tkptr) && WATCH_MODULE(WATCH_MOD_PROXY))
      WatchThis (tkptr->RequestPtr, FI_LI, WATCH_MOD_PROXY,
                 "!UL !UL {!UL}!-!#AZ {!UL}!-!#AZ",
                 ExtraLength, tkptr->ResponseBufferRemaining,
                 LocationLength, LocationUrlPtr,
                 RequiredLength, Scratch);

   /* it will never be less space, only no extra or more */
   if (ExtraLength > 0)
   {
      if (ExtraLength > tkptr->ResponseBufferRemaining)
      {
         /* expand the current buffer space, adjusting required pointers */
         int  BufferUsed,
              HeaderOffset,
              LocationUrlOffset;

         BufferUsed = tkptr->ResponseBufferSize -
                      tkptr->ResponseBufferRemaining;
         HeaderOffset = tkptr->ResponseHeaderPtr - tkptr->ResponseBufferPtr;
         LocationUrlOffset = LocationUrlPtr - tkptr->ResponseHeaderPtr;

         tkptr->ResponseBufferSize += ProxyReadBufferSize;
         tkptr->ResponseBufferRemaining += ProxyReadBufferSize;
         tkptr->ResponseBufferPtr =
            VmReallocHeap (rqptr, tkptr->ResponseBufferPtr,
                           tkptr->ResponseBufferSize, FI_LI);
         tkptr->ResponseBufferCurrentPtr = tkptr->ResponseBufferPtr +
                                           BufferUsed;
         tkptr->ResponseHeaderPtr = tkptr->ResponseBufferPtr + HeaderOffset;
         LocationUrlPtr = tkptr->ResponseHeaderPtr + LocationUrlOffset;

         if (ExtraLength > tkptr->ResponseBufferRemaining)
         {
            ErrorNoticed (SS$_BUGCHECK, "ProxyRebuildLocation()", FI_LI);
            return;
         }
      }

      /* move the rest of the buffer contents down to make room */
      sptr = (cptr = tkptr->ResponseBufferCurrentPtr) + ExtraLength;
      while (cptr > LocationUrlPtr) *sptr-- = *cptr--;
      /* adjust pointers and lengths to suit */
      tkptr->ResponseBufferCurrentPtr += ExtraLength;
      tkptr->ResponseBufferRemaining -= ExtraLength;
      tkptr->ResponseHeaderLength += ExtraLength;
   }

   /* now copy the rebuilt location into the space just made */
   sptr = LocationUrlPtr;
   for (cptr = Scratch; *cptr; *sptr++ = *cptr++);

   if (WATCHING(tkptr) && WATCH_MODULE(WATCH_MOD_PROXY))
      WatchDataFormatted ("{!UL}!-!#AZ", tkptr->ResponseHeaderLength,
                                         tkptr->ResponseHeaderPtr);
}

/****************************************************************************/
/*
If a cache load is in progress this function is called when the file write is
complete. If not in progress then this is called directly from
ProxyReadResponseAst(). If a request is associated with the task the current
buffer is written out to the client via the network.  If no request then the
next buffer is read from the proxied server.
*/

ProxyResponseNetWrite (PROXY_TASK *tkptr)

{
   REQUEST_STRUCT  *rqptr;

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

   if (WATCHING(tkptr) && WATCH_MODULE(WATCH_MOD_PROXY))
      WatchThis (WATCHTK(tkptr), FI_LI, WATCH_MOD_PROXY,
                 "ProxyResponseNetWrite()");

   rqptr = tkptr->RequestPtr;

   if (rqptr)
   {
      /* request associated with task */
      NetWrite (tkptr->RequestPtr, &ProxyResponseNetWriteAst,
                tkptr->ResponseBufferNetPtr,
                tkptr->ResponseBufferNetCount);
   }
   else
   {
      /* no associated request, just read some more */
      if (tkptr->SesolaPtr)
         SesolaNetRead (tkptr->SesolaPtr, &ProxyReadResponseAst,
                        tkptr->ResponseBufferPtr,
                        tkptr->ResponseBufferSize);
      else
         ProxyReadRaw (tkptr, &ProxyReadResponseAst,
                       tkptr->ResponseBufferPtr,
                       tkptr->ResponseBufferSize);
   }
}

/****************************************************************************/
/*
************
*** NOTE ***  This function takes a pointer to a request!!!
************

Just a wrapper for when NetWrite() is used to AST queue a read of the next part
of the response. Check the network write status and if OK then queue another
read of data from the proxied server.
*/

ProxyResponseNetWriteAst (REQUEST_STRUCT *rqptr)

{
   PROXY_TASK  *tkptr;

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

   if (WATCHING(rqptr) && WATCH_MODULE(WATCH_MOD_PROXY))
      WatchThis (rqptr, FI_LI, WATCH_MOD_PROXY,
                 "ProxyResponseNetWriteAst() !&F !&X",
                 &ProxyResponseNetWriteAst, rqptr->rqNet.WriteIOsb.Status);

   tkptr = rqptr->ProxyTaskPtr; 

   if (VMSnok (rqptr->rqNet.WriteIOsb.Status))
   {
      /* write to client failed, just abandon the request */
      ProxyEnd (rqptr->ProxyTaskPtr);
      return;
   }

   if (tkptr->SesolaPtr)
      SesolaNetRead (tkptr->SesolaPtr, &ProxyReadResponseAst,
                     tkptr->ResponseBufferPtr,
                     tkptr->ResponseBufferSize);
   else
      ProxyReadRaw (tkptr, &ProxyReadResponseAst,
                    tkptr->ResponseBufferPtr,
                    tkptr->ResponseBufferSize);
}

/****************************************************************************/
/*
Just queue a read of the next buffer-full from the remote server.
*/

ProxyReadResponseNext (PROXY_TASK *tkptr)

{
   int  status;

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

   if (WATCHING(tkptr) && WATCH_MODULE(WATCH_MOD_PROXY))
      WatchThis (WATCHTK(tkptr), FI_LI, WATCH_MOD_PROXY,
                 "ProxyReadResponseNext()");

   if (tkptr->SesolaPtr)
      SesolaNetRead (tkptr->SesolaPtr, &ProxyReadResponseAst,
                     tkptr->ResponseBufferPtr,
                     tkptr->ResponseBufferSize);
   else
      ProxyReadRaw (tkptr, &ProxyReadResponseAst,
                    tkptr->ResponseBufferPtr,
                    tkptr->ResponseBufferSize);
}

/****************************************************************************/
/*
Check the HTTP version.  If it doesn't look like an HTTP/1.n then assume its an
HTTP/0.9 (stream of HTML) and don't look for header fields.  Look through the
data received from the remote server so far for two consecutive blank lines
(two newlines or two carriage-return/newline combinations).  This delimits the
response header.  When found parse the response header.
*/

int ProxyFindResponseHeader (PROXY_TASK *tkptr)

{
   int  status,
        cnlcnt;
   char  *cptr, *zptr;

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

   if (WATCHING(tkptr) && WATCH_MODULE(WATCH_MOD_PROXY))
      WatchThis (WATCHTK(tkptr), FI_LI, WATCH_MOD_PROXY,
                 "ProxyFindResponseHeader()");

   cnlcnt = tkptr->ResponseConsecutiveNewLineCount;
   cptr = tkptr->ResponseHeaderPtr;
   zptr = tkptr->ResponseBufferCurrentPtr;

   if (!tkptr->ResponseHttpVersion)
   {
      /***************************************/
      /* check it's not an HTTP/0.9 response */
      /***************************************/

      /* must look something like "HTTP/1.0 200" */
      if (memcmp (cptr, "HTTP/", 5) ||
          !isdigit(cptr[5]) ||
          cptr[6] != '.' ||
          !isdigit(cptr[7]))
      {
         ProxyHttp09ResponseHeader (tkptr);
         return (SS$_NORMAL);
      }
      for (cptr += 8; *cptr && ISLWS(*cptr) && cptr < zptr; cptr++);
      if (!isdigit(cptr[0]) ||
          !isdigit(cptr[1]) ||
          !isdigit(cptr[2]) ||
          (!ISLWS(cptr[3]) && NOTEOL(cptr[3])))
      {
         ProxyHttp09ResponseHeader (tkptr);
         return (SS$_NORMAL);
      }

      if (!memcmp (cptr, "HTTP/1.0", 8))
         tkptr->ResponseHttpVersion = HTTP_VERSION_1_0;
      else
      if (!memcmp (cptr, "HTTP/1.1", 8))
         tkptr->ResponseHttpVersion = HTTP_VERSION_1_1;
      else
         tkptr->ResponseHttpVersion = HTTP_VERSION_UNKNOWN;
   }

   /******************************/
   /* look for the end-of-header */
   /******************************/

   while (*cptr && cnlcnt < 2 && cptr < zptr)
   {
      if (*cptr == '\r') cptr++;
      if (!*cptr) break;
      if (*cptr != '\n')
      {
         cptr++;
         cnlcnt = 0;
         continue;
      }
      cptr++;
      cnlcnt++;
   }

   if ((tkptr->ResponseConsecutiveNewLineCount = cnlcnt) >= 2)
   {
      /* found the end of the response header */
      tkptr->ResponseHeaderLength = cptr - (char*)tkptr->ResponseHeaderPtr;
      return (ProxyParseResponseHeader (tkptr));
   }

   return (SS$_NORMAL);
}


/****************************************************************************/
/*
Fudge status response components for HTTP/0.9.  Leave the
'tkptr->ResponseHeaderPtr' pointing where-ever but set the
'tkptr->ResponseHeaderLength' to zero.
*/

ProxyHttp09ResponseHeader (PROXY_TASK *tkptr)

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

   if (WATCHING(tkptr) && WATCH_MODULE(WATCH_MOD_PROXY))
      WatchThis (WATCHTK(tkptr), FI_LI, WATCH_MOD_PROXY,
                 "ProxyHttp09ResponseHeader()");

   tkptr->ProxyCacheSuitable = false;
   tkptr->ResponseConsecutiveNewLineCount = 2;
   tkptr->ResponseHeaderLength = 0;
   /* do not touch 'tkptr->ResponseHeaderPtr' upon penalty of bug! */
   tkptr->ResponseHttpVersion = HTTP_VERSION_0_9;
   strcpy (tkptr->ResponseHttpProtocol, "HTTP/0.9");
   strcpy (tkptr->ResponseStatusCodeString, "200");
   tkptr->ResponseStatusCode = 200;
   strcpy (tkptr->ResponseStatusDescription, HttpStatusCodeText(200));

   if (WATCHING(tkptr) && WATCH_MODULE(WATCH_MOD_PROXY))
      WatchDataFormatted ("!&Z !&Z !UL !&Z\n",
         tkptr->ResponseHttpProtocol, tkptr->ResponseStatusCodeString,
         tkptr->ResponseStatusCode, tkptr->ResponseStatusDescription);
}


/****************************************************************************/
/*
Scan through the remote server response header.  Items of interest include the
three digit status code from the first line, any "Content-Length:", "Expires:"
and "Last-Modified:" header lines, as these are use in determining whether any
given response is suitable for file caching if enabled.
*/

int ProxyParseResponseHeader (PROXY_TASK *tkptr)

{
   int  status;
   char  *cptr, *czptr, *hzptr, *sptr, *zptr;

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

   if (WATCHING(tkptr) && WATCH_MODULE(WATCH_MOD_PROXY))
      WatchThis (WATCHTK(tkptr), FI_LI, WATCH_MOD_PROXY,
                 "ProxyParseResponseHeader()");

   /* initialize content length to indicate it was not set */
   tkptr->ResponseContentLength = -1;

   cptr = tkptr->ResponseHeaderPtr;
   czptr = cptr + tkptr->ResponseHeaderLength;

   /************************/
   /* response status line */
   /************************/

   zptr = (sptr = tkptr->ResponseHttpProtocol) +
          sizeof(tkptr->ResponseHttpProtocol);
   while (cptr < czptr && !ISLWS(*cptr) && NOTEOL(*cptr) && sptr < zptr)
      *sptr++ = *cptr++;
   if (sptr >= zptr) sptr = tkptr->ResponseHttpProtocol;
   *sptr = '\0';

   while (cptr < czptr && ISLWS(*cptr) && NOTEOL(*cptr)) cptr++;

   zptr = (sptr = tkptr->ResponseStatusCodeString) +
          sizeof(tkptr->ResponseStatusCodeString);
   while (cptr < czptr && !ISLWS(*cptr) && NOTEOL(*cptr) && sptr < zptr)
      *sptr++ = *cptr++;
   if (sptr >= zptr) sptr = tkptr->ResponseStatusCodeString;
   *sptr = '\0';
   tkptr->ResponseStatusCode = atoi(tkptr->ResponseStatusCodeString);

   while (cptr < czptr && ISLWS(*cptr) && NOTEOL(*cptr)) cptr++;

   zptr = (sptr = tkptr->ResponseStatusDescription) +
          sizeof(tkptr->ResponseStatusDescription);
   while (cptr < czptr && NOTEOL(*cptr) && sptr < zptr)
      *sptr++ = *cptr++;
   if (sptr >= zptr) sptr--;
   *sptr = '\0';

   if (WATCHING(tkptr) && WATCH_MODULE(WATCH_MOD_PROXY))
      WatchDataFormatted ("!&Z !&Z !UL !&Z\n",
         tkptr->ResponseHttpProtocol, tkptr->ResponseStatusCodeString,
         tkptr->ResponseStatusCode, tkptr->ResponseStatusDescription);

   /* status description string can, after all, be empty! */
   if (!tkptr->ResponseHttpProtocol[0] ||
       !tkptr->ResponseStatusCodeString[0] ||
       !tkptr->ResponseStatusCode)
      return (STS$K_ERROR);

   /*********************************/
   /* rest of relevant header lines */
   /*********************************/

   while (cptr < czptr && NOTEOL(*cptr)) cptr++;
   while (cptr < czptr && EOL(*cptr)) cptr++;

   while (cptr < czptr)
   {
      /* scan down to the carriage-control of the field line */
      hzptr = cptr;      
      while (hzptr < czptr && NOTEOL(*hzptr)) hzptr++;

      if (WATCHING(tkptr) && WATCH_MODULE(WATCH_MOD_PROXY))
         WatchDataFormatted ("{!UL}!-!#AZ\n", hzptr-cptr, cptr);

      if (toupper(*cptr) == 'C')
      {
         if (strsame (cptr, "Content-Length:", 15))
         {
            cptr += 15;
            while (cptr < hzptr && ISLWS(*cptr)) cptr++;
            tkptr->ResponseContentLength = atoi(cptr);
            if (WATCHING(tkptr) && WATCH_MODULE(WATCH_MOD_PROXY))
               WatchDataFormatted ("!UL\n", tkptr->ResponseContentLength);
         }
         else
         if (strsame (cptr, "Content-Range:", 14))
         {
            cptr += 14;
            while (cptr < hzptr && ISLWS(*cptr)) cptr++;
            tkptr->ResponseContentRange = true;
            if (WATCHING(tkptr) && WATCH_MODULE(WATCH_MOD_PROXY))
               WatchDataFormatted ("!&B\n", tkptr->ResponseContentRange);
         }
         else
         if (strsame (cptr, "Content-Type:", 12))
         {
            cptr += 12;
            while (cptr < hzptr && ISLWS(*cptr)) cptr++;
            if (*cptr == 'm' && strsame (cptr, "multipart/", 10))
               tkptr->ResponseContentTypeMultipart = true;
            if (WATCHING(tkptr) && WATCH_MODULE(WATCH_MOD_PROXY))
               WatchDataFormatted ("!&B\n",
                  tkptr->ResponseContentTypeMultipart);
         }
         else
         if (strsame (cptr, "Cache-Control:", 14))
         {
            cptr += 14;
            while (cptr < hzptr && ISLWS(*cptr)) cptr++;
            while (cptr < hzptr)
            {
               if (strsame (cptr, "no-cache", 8) ||
                   strsame (cptr, "no-store", 8) ||
                   strsame (cptr, "max-age=0", 9))
               {
                  tkptr->ResponsePragmaNoCache = true;
                  break;
               }
               while (cptr < hzptr && !ISLWS(*cptr) && *cptr != ',') cptr++;
               while (cptr < hzptr && (ISLWS(*cptr) || *cptr == ',')) cptr++;
            }
            if (WATCHING(tkptr) && WATCH_MODULE(WATCH_MOD_PROXY))
               WatchDataFormatted ("!&B\n", tkptr->ResponsePragmaNoCache);
         }
      }
      else
      if (toupper(*cptr) == 'E' &&
          strsame (cptr, "Expires:", 8))
      {
         cptr += 8;
         while (cptr < hzptr && ISLWS(*cptr)) cptr++;
         zptr = (sptr = tkptr->ResponseExpires) +
            sizeof(tkptr->ResponseExpires);
         while (cptr < hzptr && sptr < zptr) *sptr++ = *cptr++;
         if (sptr >= zptr) sptr = tkptr->ResponseExpires;
         *sptr = '\0';
         status = HttpGmTime (tkptr->ResponseExpires,
                              &tkptr->ResponseExpiresBinaryTime);
         if (VMSnok (status))
         {
            tkptr->ResponseExpiresBinaryTime[0] =
               tkptr->ResponseExpiresBinaryTime[1] = 0;
            tkptr->ResponseExpires[0] = '\0';
         }
         if (WATCHING(tkptr) && WATCH_MODULE(WATCH_MOD_PROXY))
            WatchDataFormatted ("!&Z !&S !%D\n",
               tkptr->ResponseExpires, status,
               &tkptr->ResponseExpiresBinaryTime);
      }
      if (toupper(*cptr) == 'L' &&
          strsame (cptr, "Last-Modified:", 14))
      {
         cptr += 14;
         while (cptr < hzptr && ISLWS(*cptr)) cptr++;
         zptr = (sptr = tkptr->ResponseLastModified) +
            sizeof(tkptr->ResponseLastModified);
         while (cptr < hzptr && sptr < zptr) *sptr++ = *cptr++;
         if (sptr >= zptr) sptr = tkptr->ResponseLastModified;
         *sptr = '\0';
         status = HttpGmTime (tkptr->ResponseLastModified,
                              &tkptr->ResponseLastModifiedBinaryTime);
         if (VMSnok (status))
         {
            tkptr->ResponseLastModifiedBinaryTime[0] =
               tkptr->ResponseLastModifiedBinaryTime[1] = 0;
            tkptr->ResponseLastModified[0] = '\0';
         }
         if (WATCHING(tkptr) && WATCH_MODULE(WATCH_MOD_PROXY))
            WatchDataFormatted ("!&Z !&S !%D\n",
               tkptr->ResponseLastModified, status,
               &tkptr->ResponseLastModifiedBinaryTime);
      }
      else
      if (toupper(*cptr) == 'P' &&
          strsame (cptr, "Pragma:", 7))
      {
         cptr += 7;
         while (cptr < hzptr && ISLWS(*cptr)) cptr++;
         if (strsame (cptr, "no-cache", 8)) tkptr->ResponsePragmaNoCache = true;
         if (WATCHING(tkptr) && WATCH_MODULE(WATCH_MOD_PROXY))
            WatchDataFormatted ("!&B\n", tkptr->ResponsePragmaNoCache);
      }
      else
      if (toupper(*cptr) == 'S' &&
          strsame (cptr, "Set-Cookie:", 11))
      {
         /* just note that the response contains a cookie */
         tkptr->ResponseSetCookie = true;
         if (WATCHING(tkptr) && WATCH_MODULE(WATCH_MOD_PROXY))
            WatchDataFormatted ("!&B\n", tkptr->ResponseSetCookie);
      }

      /* scan over the carriage-control of the field line */
      while (hzptr < czptr && EOL(*hzptr)) hzptr++;
      cptr = hzptr;
   }

   return (SS$_NORMAL);
}

/****************************************************************************/
/*
Using the request header fields rebuild a request header suitable for use by
the proxy server.
*/ 

int ProxyRebuildRequest (PROXY_TASK *tkptr)

{
#define STRCAT(string) \
   for (cptr = string; *cptr && sptr < zptr; *sptr++ = *cptr++);
#define CHRCAT(character) { \
   if (sptr < zptr) *sptr++ = character; \
}

   static char  Number [16];
   static $DESCRIPTOR (NumberDsc, Number);
   static $DESCRIPTOR (NumberFaoDsc, "!UL\0");

   BOOL  ForwardedBy,
         XForwardedFor;
   int  idx;
   char  *cptr, *sptr, *zptr;
   REQUEST_STRUCT  *rqptr;

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

   if (WATCHING(tkptr) && WATCH_MODULE(WATCH_MOD_PROXY))
      WatchThis (tkptr->RequestPtr, FI_LI, WATCH_MOD_PROXY,
                 "ProxyRebuildRequest()");

   rqptr = tkptr->RequestPtr;

   tkptr->RebuiltRequestPtr = sptr =
      VmGetHeap (rqptr, rqptr->rqHeader.RequestHeaderLength + 512);
   zptr = sptr + rqptr->rqHeader.RequestHeaderLength + 512;

   STRCAT (rqptr->rqHeader.MethodName)
   CHRCAT (' ')
   if (IPADDRESS_IS_SET (&tkptr->ChainIpAddress))
   {
      STRCAT ("http://")
      STRCAT (tkptr->RequestHostPort)
   }
   STRCAT (tkptr->RequestUriPtr)

   if (tkptr->RequestHttpVersion == HTTP_VERSION_0_9)
   {
      /*******************/
      /* HTTP/0.9 header */
      /*******************/

      /* just end the line (and header) without an HTTP protocol version */
      STRCAT ("\r\n")
   }
   else
   {
      /*******************/
      /* HTTP/1.n header */
      /*******************/

      CHRCAT (' ')
      STRCAT (HttpProtocol)

      if (rqptr->rqHeader.AcceptPtr)
      {
         STRCAT ("\r\nAccept: ")
         STRCAT (rqptr->rqHeader.AcceptPtr)
      }
      if (rqptr->rqHeader.AcceptCharsetPtr)
      {
         STRCAT ("\r\nAccept-Charset: ")
         STRCAT (rqptr->rqHeader.AcceptCharsetPtr)
      }
      if (rqptr->rqHeader.AcceptEncodingPtr)
      {
         STRCAT ("\r\nAccept-Encoding: ")
         STRCAT (rqptr->rqHeader.AcceptEncodingPtr)
      }
      if (rqptr->rqHeader.AcceptLangPtr)
      {
         STRCAT ("\r\nAccept-Language: ")
         STRCAT (rqptr->rqHeader.AcceptLangPtr)
      }
      if (rqptr->rqHeader.UserAgentPtr)
      {
         STRCAT ("\r\nUser-Agent: ")
         STRCAT (rqptr->rqHeader.UserAgentPtr)
      }
      if (rqptr->rqHeader.RangePtr)
      {
         STRCAT ("\r\nRange: ")
         STRCAT (rqptr->rqHeader.RangePtr)
      }
      if (rqptr->rqHeader.RefererPtr)
      {
         STRCAT ("\r\nReferer: ")
         STRCAT (rqptr->rqHeader.RefererPtr)
      }
      if (rqptr->rqHeader.HostPtr)
      {
         STRCAT ("\r\nHost: ")
         STRCAT (rqptr->rqHeader.HostPtr)
      }
      /* only included if not used to authorize this proxy access */
      if (rqptr->rqHeader.ProxyAuthorizationPtr &&
          !rqptr->ServicePtr->ProxyAuthRequired)
      {
         STRCAT ("\r\nProxy-Authorization: ")
         STRCAT (rqptr->rqHeader.ProxyAuthorizationPtr)
      }
      if (rqptr->rqHeader.AuthorizationPtr)
      {
         if (rqptr->rqPathSet.ProxyReverseVerify)
         {
            ProxyVerifyRecordSet (tkptr);
            if (tkptr->VerifyRecordPtr)
            {
               STRCAT ("\r\nAuthorization: Basic ")
               STRCAT (tkptr->VerifyRecordPtr->AuthorizationString)
            }
            else
            {
               /* busy here indicates increase [ProxyVerifyRecordMax] */
               rqptr->rqResponse.HttpStatus = 503;
               ErrorGeneral (rqptr, MsgFor(rqptr,MSG_GENERAL_TOO_BUSY), FI_LI);
               return (STS$K_ERROR);
            }
         }
         else
         {
            STRCAT ("\r\nAuthorization: ")
            STRCAT (rqptr->rqHeader.AuthorizationPtr)
         }
      }
      if (rqptr->rqHeader.IfModifiedSincePtr)
      {
         STRCAT ("\r\nIf-Modified-Since: ")
         STRCAT (rqptr->rqHeader.IfModifiedSincePtr)
      }
      if (rqptr->rqHeader.CacheControlPtr)
      {
         STRCAT ("\r\nCache-Control: ")
         STRCAT (rqptr->rqHeader.CacheControlPtr)
      }
      if (rqptr->rqHeader.PragmaPtr)
      {
         STRCAT ("\r\nPragma: ")
         STRCAT (rqptr->rqHeader.PragmaPtr)
      }
      else
      if (rqptr->PragmaNoCache)
      {
         /* perhaps from a request "Cache-Control:" directive */
         STRCAT ("\r\nPragma: no-cache")
      }

#ifdef HTTP_PROXY_CONNECTION
      if (rqptr->rqHeader.ProxyConnectionPtr)
      {
         STRCAT ("\r\nProxy-Connection: ")
         STRCAT (rqptr->rqHeader.ProxyConnectionPtr)
      }
#endif /* HTTP_PROXY_CONNECTION */

      if (rqptr->rqHeader.CookiePtr)
      {
         STRCAT ("\r\nCookie: ")
         STRCAT (rqptr->rqHeader.CookiePtr)
      }
      if (rqptr->rqHeader.ContentTypePtr)
      {
         STRCAT ("\r\nContent-Type: ")
         STRCAT (rqptr->rqHeader.ContentTypePtr)
      }
      if (rqptr->rqHeader.ContentLength)
      {
         sys$fao (&NumberFaoDsc, 0, &NumberDsc, rqptr->rqHeader.ContentLength);
         STRCAT ("\r\nContent-Length: ")
         STRCAT (Number)
      }
      if (rqptr->rqHeader.ETagPtr)
      {
         STRCAT ("\r\nETag: ")
         STRCAT (rqptr->rqHeader.ETagPtr)
      }
      if (rqptr->rqHeader.KeepAlivePtr)
      {
         STRCAT ("\r\nKeep-Alive: ")
         STRCAT (rqptr->rqHeader.KeepAlivePtr)
      }

#ifdef HTTP_PROXY_CONNECTION
      if (rqptr->KeepAliveRequest)
         STRCAT ("\r\nConnection: Keep-Alive")
#else /* HTTP_PROXY_CONNECTION */
      STRCAT ("\r\nConnection: close")
#endif /* HTTP_PROXY_CONNECTION */

      ForwardedBy = ProxyForwardedBy;
      if (rqptr->rqPathSet.ProxyForwardedBy)
      {
         if (rqptr->rqPathSet.ProxyForwardedBy == PROXY_FORWARDED_NONE)
            ForwardedBy = PROXY_FORWARDED_DISABLED;
         else
            ForwardedBy = rqptr->rqPathSet.ProxyForwardedBy;
      }
      if (ForwardedBy)
      {
         STRCAT ("\r\nForwarded: ")
         if (ForwardedBy == PROXY_FORWARDED_BY ||
             ForwardedBy == PROXY_FORWARDED_FOR ||
             ForwardedBy == PROXY_FORWARDED_ADDRESS)
         {
            STRCAT ("by http://")
            if (rqptr->ServicePtr->ServerPort == 80)
               STRCAT (rqptr->ServicePtr->ServerHostName)
            else
               STRCAT (rqptr->ServicePtr->ServerHostPort)
            STRCAT (" (")
            STRCAT (SoftwareID)
            CHRCAT (')')
         }
         if (ForwardedBy == PROXY_FORWARDED_FOR)
         {
            STRCAT (" for ")
            STRCAT (rqptr->rqClient.Lookup.HostName)
         }
         else
         if (ForwardedBy == PROXY_FORWARDED_ADDRESS)
         {
            STRCAT (" for ")
            STRCAT (rqptr->rqClient.IpAddressString)
         }
         if (rqptr->rqHeader.ForwardedPtr)
         {
            if (ForwardedBy == PROXY_FORWARDED_BY ||
                ForwardedBy == PROXY_FORWARDED_FOR ||
                ForwardedBy == PROXY_FORWARDED_ADDRESS)
               STRCAT (", ")
            STRCAT (rqptr->rqHeader.ForwardedPtr)
         }
      }

      XForwardedFor = ProxyXForwardedFor;
      if (rqptr->rqPathSet.ProxyXForwardedFor)
      {
         if (rqptr->rqPathSet.ProxyXForwardedFor == PROXY_XFORWARDEDFOR_NONE)
            XForwardedFor = PROXY_XFORWARDEDFOR_DISABLED;
         else
            XForwardedFor = rqptr->rqPathSet.ProxyXForwardedFor;
      }
      if (XForwardedFor)
      {
         STRCAT ("\r\nX-Forwarded-For: ")
         if (rqptr->rqHeader.XForwardedForPtr)
         {
            STRCAT (rqptr->rqHeader.XForwardedForPtr)
            STRCAT (", ")
         }
         if (XForwardedFor == PROXY_XFORWARDEDFOR_ENABLED)
            STRCAT (rqptr->rqClient.Lookup.HostName)
         else
         if (XForwardedFor == PROXY_XFORWARDEDFOR_ADDRESS)
            STRCAT (rqptr->rqClient.IpAddressString)
         else
            STRCAT ("unknown")
      }

      if (ProxyUnknownRequestFields ||
          rqptr->rqPathSet.ProxyUnknownRequestFields)
      {
         /* unrecognised request fields */
         for (idx = 0; idx < rqptr->rqHeader.UnknownFieldsCount; idx++)
         {
            STRCAT ("\r\n")
            STRCAT (rqptr->rqHeader.UnknownFieldsPtr[idx])
         }
      }

      /* terminate last line, end-of-header empty line */
      STRCAT ("\r\n\r\n")

      /**************************/
      /* end of HTTP/1.n header */
      /**************************/
   }

   if (sptr >= zptr)
   {
      rqptr->rqResponse.HttpStatus = 500;
      ErrorGeneralOverflow (rqptr, FI_LI);
      return (STS$K_ERROR);
   }
   *sptr = '\0';
   tkptr->RebuiltRequestLength = sptr - tkptr->RebuiltRequestPtr;

   if (WATCHING(tkptr) && WATCH_MODULE(WATCH_MOD_PROXY))
      WatchDataFormatted ("!&Z\n", tkptr->RebuiltRequestPtr);

   return (SS$_NORMAL);
}

/*****************************************************************************/
/*
Write data to the proxied server. Explicitly declares any AST routine if an
error occurs. The calling function must not do any error recovery if an AST
routine has been supplied but the associated AST routine must! If an AST was
not supplied then the return status can be checked.  AST calls
ProxyWriteRawAST() which will then call the supplied AST function.
*/

int ProxyWriteRaw
(
PROXY_TASK *tkptr,
PROXY_AST AstFunction,
char *DataPtr,
int DataLength
)

{
   int  status;
   REQUEST_STRUCT *rqptr;

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

   if (WATCHING(tkptr) && WATCH_MODULE(WATCH_MOD_PROXY))
      WatchThis (WATCHTK(tkptr), FI_LI, WATCH_MOD_PROXY,
                 "ProxyWriteRaw() !&F !&A !&X !UL",
                 &ProxyWriteRaw, AstFunction, DataPtr, DataLength);

   rqptr = tkptr->RequestPtr;

   if (tkptr->ProxyWriteRawAstFunction ||
       !DataLength)
   {
      tkptr->ProxyWriteIOsb.Status = SS$_BUGCHECK;
      tkptr->ProxyWriteIOsb.Count = 0;
      if (AstFunction) SysDclAst (AstFunction, tkptr);
      return (tkptr->ProxyWriteIOsb.Status);
   }
   tkptr->ProxyWriteRawAstFunction = AstFunction;
   tkptr->ProxyWriteRawDataPtr = DataPtr;
   tkptr->ProxyWriteRawDataCount = DataLength;

   if (WATCHING(rqptr) && WATCH_CATEGORY(WATCH_NETWORK_OCTETS))
   {
      WatchThis (rqptr, FI_LI, WATCH_NETWORK,
                 "WRITE !UL bytes", DataLength);
      WatchDataDump (DataPtr, DataLength);
   }

   if (!AstFunction)
   {
      /***************/
      /* blocking IO */
      /***************/

      status = sys$qiow (EfnWait, tkptr->ProxyChannel,
                         IO$_WRITEVBLK, &tkptr->ProxyWriteIOsb, 0, 0,
                         DataPtr, DataLength, 0, 0, 0, 0);

      if (WATCHING(tkptr) && WATCH_MODULE(WATCH_MOD_PROXY))
         WatchDataFormatted ("sys$qiow() !&S !&S !UL\n",
            status, tkptr->ProxyWriteIOsb.Status, tkptr->ProxyWriteIOsb.Count);
   }
   else
   {
      /*******************/
      /* non-blocking IO */
      /*******************/

      status = sys$qio (EfnNoWait, tkptr->ProxyChannel,
                        IO$_WRITEVBLK, &tkptr->ProxyWriteIOsb,
                        &ProxyWriteRawAst, tkptr,
                        DataPtr, DataLength, 0, 0, 0, 0);

      if (WATCHING(tkptr) && WATCH_MODULE(WATCH_MOD_PROXY))
         WatchDataFormatted ("sys$qio() !&S\n", status);
   }

   /****************/
   /* check status */
   /****************/

   if (VMSok (status)) return (status);

   /* if resource wait enabled the only quota not waited for is ASTLM */
   if (status == SS$_EXQUOTA)
      ErrorExitVmsStatus (status, "sys$qio()", FI_LI);

   /* write failed, call AST explicitly, status in the IOsb */
   tkptr->ProxyWriteIOsb.Status = status;
   tkptr->ProxyWriteIOsb.Count = 0;
   SysDclAst (ProxyWriteRawAst, tkptr);
   return (status);
}

/*****************************************************************************/
/*
AST from ProxyWriteRaw().  Call the AST function.
*/ 

ProxyWriteRawAst (PROXY_TASK *tkptr)

{
   int  status;
   PROXY_AST  AstFunction;
   REQUEST_STRUCT *rqptr;

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

   if (WATCHING(tkptr) && WATCH_MODULE(WATCH_MOD_PROXY))
      WatchThis (WATCHTK(tkptr), FI_LI, WATCH_MOD_PROXY,
                 "ProxyWriteRawAst() !&F !&S !UL", &ProxyWriteRawAst,
                 tkptr->ProxyWriteIOsb.Status, tkptr->ProxyWriteIOsb.Count);

   rqptr = tkptr->RequestPtr;

   if (WATCHING(tkptr) && WATCH_CATEGORY(WATCH_NETWORK))
   {
      if (VMSnok(tkptr->ProxyWriteIOsb.Status))
         WatchThis (WATCHTK(tkptr), FI_LI, WATCH_NETWORK,
                    "WRITE !&S %!&M",
                    tkptr->ProxyWriteIOsb.Status,
                    tkptr->ProxyWriteIOsb.Status);
      else
         WatchThis (WATCHTK(tkptr), FI_LI, WATCH_NETWORK,
                    "WRITE !&S !UL bytes",
                    tkptr->ProxyWriteIOsb.Status,
                    tkptr->ProxyWriteIOsb.Count);
   }

   if (VMSok (tkptr->ProxyWriteIOsb.Status))
      tkptr->BytesRawTx += tkptr->ProxyWriteIOsb.Count;

   tkptr->ProxyWriteRawDataPtr = tkptr->ProxyWriteRawDataCount = 0;
   if (!tkptr->ProxyWriteRawAstFunction) return;
   AstFunction = tkptr->ProxyWriteRawAstFunction;
   tkptr->ProxyWriteRawAstFunction = NULL;
   (*AstFunction)(tkptr);
}

/*****************************************************************************/
/*
Queue up a read from the proxied server over the network. If 'AstFunction' 
is zero then no I/O completion AST routine is called.  If it is non-zero then 
the function pointed to by the parameter is called when the network write 
completes.

Explicitly declares any AST routine if an error occurs. The calling function
must not do any error recovery if an AST routine has been supplied but the
associated AST routine must!  If an AST was not supplied then the return
status can be checked.
*/ 

int ProxyReadRaw
(
PROXY_TASK *tkptr,
PROXY_AST AstFunction,
char *DataPtr,
int DataSize
)
{
   int  status;
   REQUEST_STRUCT *rqptr;

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

   if (WATCHING(tkptr) && WATCH_MODULE(WATCH_MOD_PROXY))
      WatchThis (WATCHTK(tkptr), FI_LI, WATCH_MOD_PROXY,
                 "ProxyReadRaw() !&F !&A !&X !UL",
                 &ProxyReadRaw, AstFunction, DataPtr, DataSize);

   rqptr = tkptr->RequestPtr;

   if (tkptr->ProxyReadRawAstFunction)
   {
      tkptr->ProxyReadIOsb.Status = SS$_BUGCHECK;
      tkptr->ProxyReadIOsb.Count = 0;
      if (AstFunction) SysDclAst (AstFunction, tkptr);
      return (tkptr->ProxyReadIOsb.Status);
   }
   tkptr->ProxyReadRawAstFunction = AstFunction;
   tkptr->ProxyReadRawDataPtr = DataPtr;
   tkptr->ProxyReadRawDataSize = DataSize;

   if (WATCHING(rqptr) && WATCH_CATEGORY(WATCH_NETWORK_OCTETS))
      WatchThis (rqptr, FI_LI, WATCH_NETWORK,
                 "READ !UL bytes max", DataSize);

   if (!AstFunction)
   {
      /***************/
      /* blocking IO */
      /***************/

      status = sys$qiow (EfnWait, tkptr->ProxyChannel,
                         IO$_READVBLK, &tkptr->ProxyReadIOsb, 0, 0,
                         DataPtr, DataSize, 0, 0, 0, 0);

      if (WATCHING(tkptr) && WATCH_MODULE(WATCH_MOD_PROXY))
         WatchDataFormatted ("sys$qiow() !&S !&S !UL\n",
            status, tkptr->ProxyReadIOsb.Status, tkptr->ProxyReadIOsb.Count);
   }
   else
   {
      /*******************/
      /* non-blocking IO */
      /*******************/

      status = sys$qio (EfnNoWait, tkptr->ProxyChannel,
                        IO$_READVBLK, &tkptr->ProxyReadIOsb,
                        &ProxyReadRawAst, tkptr,
                        DataPtr, DataSize, 0, 0, 0, 0);

      if (WATCHING(tkptr) && WATCH_MODULE(WATCH_MOD_PROXY))
         WatchDataFormatted ("sys$qio() !&S\n", status);
   }

   /****************/
   /* check status */
   /****************/

   if (VMSok (status)) return (status);

   /* with resource wait enabled the only quota not waited for is ASTLM */
   if (status == SS$_EXQUOTA)
      ErrorExitVmsStatus (status, "sys$qio()", FI_LI);

   /* queuing of read failed, call AST explicitly, status in the IOsb */
   tkptr->ProxyReadIOsb.Status = status;
   tkptr->ProxyReadIOsb.Count = 0;
   SysDclAst (ProxyReadRawAst, tkptr);
   return (status);
}

/*****************************************************************************/
/*
AST from ProxyReadRaw().  Call the AST function.
*/ 

ProxyReadRawAst (PROXY_TASK *tkptr)

{
   int  status;
   PROXY_AST  AstFunction;
   REQUEST_STRUCT *rqptr;

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

   if (WATCHING(tkptr) && WATCH_MODULE(WATCH_MOD_PROXY))
      WatchThis (WATCHTK(tkptr), FI_LI, WATCH_MOD_PROXY,
                 "ProxyReadRawAst() !&F !&S !UL", &ProxyReadRawAst,
                 tkptr->ProxyReadIOsb.Status, tkptr->ProxyReadIOsb.Count);

   rqptr = tkptr->RequestPtr;

   if (WATCHING(tkptr))
   {
      if (WATCH_CATEGORY(WATCH_NETWORK) ||
          WATCH_CATEGORY(WATCH_NETWORK_OCTETS))
      {
         if (VMSok(tkptr->ProxyReadIOsb.Status))
         {
            WatchThis (WATCHTK(tkptr), FI_LI, WATCH_NETWORK,
                       "READ !&S !UL bytes",
                       tkptr->ProxyReadIOsb.Status,
                       tkptr->ProxyReadIOsb.Count);

            if WATCH_CATEGORY(WATCH_NETWORK_OCTETS)
               WatchDataDump (tkptr->ProxyReadRawDataPtr,
                              tkptr->ProxyReadIOsb.Count);
         }
         else
            WatchThis (WATCHTK(tkptr), FI_LI, WATCH_NETWORK,
                       "READ !&S %!&M",
                       tkptr->ProxyReadIOsb.Status,
                       tkptr->ProxyReadIOsb.Status);
      }
   }

   if (VMSok (tkptr->ProxyReadIOsb.Status))
   {
      tkptr->BytesRawRx += tkptr->ProxyReadIOsb.Count;
      /* zero bytes with a normal status is a definite no-no (TGV-Multinet) */
      if (!tkptr->ProxyReadIOsb.Count) tkptr->ProxyReadIOsb.Status = SS$_ABORT;
   }

   tkptr->ProxyReadRawDataPtr = tkptr->ProxyReadRawDataSize = 0;
   if (!tkptr->ProxyReadRawAstFunction) return;
   AstFunction = tkptr->ProxyReadRawAstFunction;
   tkptr->ProxyReadRawAstFunction = NULL;
   (*AstFunction)(tkptr);
}

/****************************************************************************/
/*
Just shut the socket down, bang!
*/

int ProxyCloseSocket (PROXY_TASK *tkptr)

{
   int  status;

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

   if (WATCHING(tkptr) && WATCH_MODULE(WATCH_MOD_PROXY))
      WatchThis (WATCHTK(tkptr), FI_LI, WATCH_MOD_PROXY,
                 "ProxyCloseSocket() !&F", &ProxyCloseSocket);

   if (!tkptr->ProxyChannel) return (SS$_NORMAL);

   status = sys$dassgn (tkptr->ProxyChannel);
   tkptr->ProxyChannel = 0;
   if (tkptr->SesolaPtr) SesolaNetSocketHasBeenClosed (tkptr->SesolaPtr);

   if (WATCHING(tkptr) && WATCH_CATEGORY(WATCH_PROXY))
   {
      if (VMSok(status))
         WatchThis (WATCHTK(tkptr), FI_LI, WATCH_PROXY,
                    "CLOSE !AZ,!UL",
                    tkptr->RequestHostName, tkptr->RequestPort); 
      else
         WatchThis (WATCHTK(tkptr), FI_LI, WATCH_PROXY,
                    "CLOSE !AZ,!UL !&S %!&M",
                    tkptr->RequestHostName, tkptr->RequestPort,
                    status, status);
   }

   return (status);
}

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

