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

This module provides the essential networking functionality for proxy
processing.  It also maintains the pool of persistent proxy->origin server
connections.


VERSION HISTORY
---------------
19-JAN-2020  MGD  more proxy persistent connection (per JPP)
11-AUG-2015  MGD  restructure of network I/O abstractions
09-APR-2011  MGD  ProxyNetLocalPort() for access logging purposes
08-NOV-2006  JPP  bugfix; ProxyNetConnectPersist() rejects all further
                    requests once ProxyConnectPersistMax has been hit
04-JUL-2006  MGD  use PercentOf() for more accurate percentages
21-APR-2006  JPP  client to origin server affinity
20-JUL-2005  JPP  proxy to origin server failover
                    (support multiple IP addresses per name in the host cache)
09-JUN-2005  MGD  bugfix; ProxyEnd(rqptr) should be ProxyEnd(tkptr) in
                    ProxyNetHostConnectAst()  (jpp@esme.fr)
20-SEP-2004  MGD  extracted functionality from PROXY.C
*/
/*****************************************************************************/

#ifdef WASD_VMS_V7
#undef _VMS__V6__SOURCE
#define _VMS__V6__SOURCE
#undef __VMS_VER
#define __VMS_VER 70000000
#undef __CRTL_VER
#define __CRTL_VER 70000000
#endif

/* standard C header files */
#include <ctype.h>
#include <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 "PROXYNET"

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

int  ProxyNetConnectCount,
     ProxyNetConnectCountMax,
     ProxyNetConnectFreeCount;

unsigned long  ProxyNetConnectTimeoutDelta [2];

LIST_HEAD  ProxyNetConnectList,
           ProxyNetConnectFreeList;

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

extern BOOL  LoggingProxyLocalPort;

extern int  EfnWait,
            EfnNoWait,
            HttpdTickSecond,
            OptionEnabled,
            ProxyConnectPersistMax,
            ProxyConnectPersistSeconds,
            ProxyConnectTimeoutSeconds;

extern int  ToLowerCase[],
            ToUpperCase[];

extern unsigned long  HttpdTime64[];

extern char  ErrorSanityCheck[],
             SoftwareID[];

extern struct dsc$descriptor TcpIpDeviceDsc;

extern ACCOUNTING_STRUCT  *AccountingPtr;
extern IPADDRESS  TcpIpEmptyAddress;
extern PROXY_ACCOUNTING_STRUCT  *ProxyAccountingPtr;
extern MSG_STRUCT  Msgs;
extern TCP_SOCKET_ITEM  TcpIpSocket4,
                        TcpIpSocket6;
extern VMS_ITEM_LIST2  ReuseAddress,
                       ReuseAddressSocketOption,
                       TcpIpFullDuplexCloseOption;
extern WATCH_STRUCT  Watch;

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

ProxyNetInit ()

{
   int  status;

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

   if (WATCH_MODULE(WATCH_MOD_PROXY))
      WatchThis (WATCHALL, WATCH_MOD_PROXY, "ProxyNetInit()");

   if (ProxyConnectPersistMax < 0 || ProxyConnectPersistMax > 1000)
      ProxyConnectPersistMax = 0;

   if (ProxyConnectPersistSeconds < 0 || ProxyConnectPersistSeconds > 3600)
      ProxyConnectPersistSeconds = 0;

   if (ProxyConnectTimeoutSeconds < 0 || ProxyConnectTimeoutSeconds > 60)
      ProxyConnectTimeoutSeconds = 0;
   else
   if (ProxyConnectTimeoutSeconds)
   {
      /* intialize to one second delta then multiply by number of seconds */
      ProxyNetConnectTimeoutDelta[0] = -10000000;
      ProxyNetConnectTimeoutDelta[1] = -1;
      status = lib$mult_delta_time (&ProxyConnectTimeoutSeconds,
                                    &ProxyNetConnectTimeoutDelta);
      if (VMSnok (status))
         ErrorExitVmsStatus (status, NULL, FI_LI);
   }

   ProxyNetConnectSupervisor (0);
}


/****************************************************************************/
/*
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 ProxyNetHostConnect() when ready.
*/

ProxyNetResolveHost (PROXY_TASK *tkptr)

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

   if (WATCHMOD (tkptr, WATCH_MOD_PROXY))
      WatchThis (WATCHITM(tkptr), WATCH_MOD_PROXY,
                 "ProxyNetResolveHost() !&Z", tkptr->RequestHostName);

   if (IPADDRESS_IS_RESET (&tkptr->ChainIpAddress))
   {
      tkptr->ConnectPort = tkptr->RequestPort;
      tkptr->ConnectHostPortPtr = tkptr->RequestHostPort;

      if ((tkptr->ServicePtr->ProxyAffinity ||
           tkptr->RequestPtr->rqPathSet.ProxyAffinity) &&
           tkptr->RequestPtr->rqHeader.CookiePtr)
         ProxyNetCheckAffinityCookie (tkptr);
      else
         TcpIpNameToAddress (&tkptr->HostLookup,
                             tkptr->RequestHostName,
                             tkptr->ProxyLookupRetryCount,
                             &ProxyNetHostConnect,
                             tkptr);
      return;
   }

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

   if (WATCHING (tkptr, WATCH_PROXY))
      WatchThis (WATCHITM(tkptr), WATCH_PROXY,
                 "HOST-PROXY-CHAIN !AZ", tkptr->ChainHostPortPtr);

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

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


/****************************************************************************/
/*
Resolve host using TCP/IP lookup unless cookie indicates a preferred IP
address.  Remove the affinity cookie from the cookie field.  If that is the
only cookie data then remove the cookie field entirely from the request.
*/

ProxyNetCheckAffinityCookie (PROXY_TASK *tkptr)

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

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

   if (WATCH_MODULE(WATCH_MOD_PROXY))
      WatchThis (WATCHALL, WATCH_MOD_PROXY, "ProxyNetCheckAffinityCookie()");


   rqptr = tkptr->RequestPtr;
   RequestHostNameLength = strlen(tkptr->RequestHostName);

   cptr = rqptr->rqHeader.CookiePtr;
   while (*cptr)
   {
      while (*cptr && *cptr != PROXY_AFFINITY_COOKIE_PREFIX[0]) cptr++;
      if (!*cptr) break;
      if (!MATCH0 (cptr, PROXY_AFFINITY_COOKIE_PREFIX,
                        sizeof(PROXY_AFFINITY_COOKIE_PREFIX)-1))
      {
         cptr++;
         continue;
      }

      /* note start of affinity cookie */
      sptr = cptr;

      cptr += sizeof(PROXY_AFFINITY_COOKIE_PREFIX)-1;
      if (MATCH0 (cptr, tkptr->RequestHostName, RequestHostNameLength))
      {
         cptr += RequestHostNameLength;
         if (*cptr == '=')
         {
            for (zptr = ++cptr; *zptr && *zptr != ';'; zptr++);
            ch = *zptr;
            *zptr = '\0';
            
            if (WATCHING (tkptr, WATCH_PROXY))
               WatchThis (WATCHITM(tkptr), WATCH_PROXY,
                          "AFFINITY with !AZ", cptr);

            tkptr->HostLookup.LookupIOsb.Status = 
               TcpIpStringToAddress (cptr, &tkptr->HostLookup.IpAddress);

            *zptr = ch;
            if (*zptr) zptr++;
            while (*zptr && ISLWS(*zptr)) zptr++;
            
            /* remove the affinity cookie */
            while (*zptr) *sptr++ = *zptr++;
            *sptr = '\0';

            /* ensure it's not now an empty field */
            for (cptr = rqptr->rqHeader.CookiePtr;
                 *cptr && ISLWS(*cptr);
                 cptr++);
            if (!*cptr)
            {
               /* just an empty cookie jar! */
               rqptr->rqHeader.CookiePtr = NULL;
               DictRemove (rqptr->rqDictPtr, DICT_TYPE_REQUEST, "cookie", 6);
            }

            /* try to validate IP address from cache */
            /* (prevents forged cookies that would allow open proxy) */
            status = TcpIpCacheAddresstoName (&tkptr->HostLookup,
                                              &tkptr->HostLookup.IpAddress);
            if (VMSok(status))
            {
               if (!strcmp (tkptr->HostLookup.HostName,
                            tkptr->RequestHostName))
               {
                  /* affinity cookie match */
                  tkptr->ProxyAffinityInUse = true;
                  ProxyNetHostConnect(tkptr);
                  return;
               }
               else
               if (WATCHING (tkptr, WATCH_PROXY))
                  WatchThis (WATCHITM(tkptr), WATCH_PROXY,
                             "AFFINITY hint not correct");
            }
            else
            {
               /* not found in cache, try loading it only once */
               if (WATCHING (tkptr, WATCH_PROXY))
                  WatchThis (WATCHITM(tkptr), WATCH_PROXY,
                             "AFFINITY hint not in cache");

               if (!tkptr->ProxyAffinityInUse)
               {
                  tkptr->ProxyAffinityInUse = true;
                  TcpIpNameToAddress (&tkptr->HostLookup,
                                      tkptr->RequestHostName,
                                      tkptr->ProxyLookupRetryCount,
                                      &ProxyNetResolveHost,
                                      tkptr);
                  return;
               }
            }
         }
         if (*cptr) cptr++;
      }
   }

   /* no applicable affinity cookie present */
   TcpIpNameToAddress (&tkptr->HostLookup,
                       tkptr->RequestHostName,
                       tkptr->ProxyLookupRetryCount,
                       &ProxyNetHostConnect,
                       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 ProxyNetHostConnectAst().
*/

ProxyNetHostConnect (PROXY_TASK *tkptr)

{
   static BOOL  UseFullDuplexClose = true;

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

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

   if (WATCHMOD (tkptr, WATCH_MOD_PROXY))
      WatchThis (WATCHITM(tkptr), WATCH_MOD_PROXY,
                 "ProxyNetHostConnect() !&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_PROXY))
         WatchThis (WATCHITM(tkptr), WATCH_PROXY,
                    "HOST-LOOKUP !AZ !&S",
                    tkptr->RequestHostName,
                    tkptr->HostLookup.LookupIOsb.Status);

      /* dispose of the non-connected channel/socket used for the lookup */
      ProxyNetCloseSocket (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;
   }

   /*****************/
   /* host resolved */
   /*****************/

   IPADDRESS_COPY (&tkptr->RequestHostIpAddress, &tkptr->HostLookup.IpAddress)

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

   if (ProxyConnectPersistMax)
   {
      /* search for an existing connection */
      ProxyNetConnectSearch (tkptr);
      if (tkptr->NetIoPtr->Channel)
      {
         /***************************************/
         /* persistent proxy->origin connection */
         /***************************************/

         /* peek to ensure it's still connected and there's nothing buffered */
         static char  ThrowAwayBuffer [1];
         status = sys$qio (EfnNoWait, tkptr->NetIoPtr->Channel,
                           IO$_READVBLK, &tkptr->ProxyConnectIOsb,
                           &ProxyNetHostConnectAst, tkptr,
                           ThrowAwayBuffer, sizeof(ThrowAwayBuffer),
                           0, TCPIP$C_MSG_PEEK|TCPIP$C_MSG_NBIO, 0, 0);
         if (VMSnok (status))
         {
            /* leave it to the AST function to report! */
            tkptr->ProxyConnectIOsb.Status = status;
            SysDclAst (ProxyNetHostConnectAst, tkptr);
         }
         return;
      }
   }

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

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

   tkptr->ConnectionCount = 1;

   /* assign a channel to the internet template device */
   status = sys$assign (&TcpIpDeviceDsc, &tkptr->NetIoPtr->Channel, 0, 0);
   if (VMSnok (status))
   {
      /* leave it to the AST function to report! */
      tkptr->ProxyConnectIOsb.Status = status;
      SysDclAst (ProxyNetHostConnectAst, 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->NetIoPtr->Channel, IO$_SETMODE,
                         &tkptr->ProxyConnectIOsb, 0, 0,
                         TcpSocketPtr, 0, BindSocketNamePtr,
                         0, UseFullDuplexClose ?
                               &TcpIpFullDuplexCloseOption : 0, 0);
      if (WATCHMOD (tkptr, WATCH_MOD_PROXY))
         WatchThis (WATCHITM(tkptr), WATCH_MOD_PROXY, "sys$qiow() !&S !&S",
                       status, tkptr->ProxyConnectIOsb.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->NetIoPtr->Channel);
         status = sys$assign (&TcpIpDeviceDsc, &tkptr->NetIoPtr->Channel, 0, 0);
         if (VMSnok (status))
         {
            /* leave it to the AST function to report! */
            tkptr->ProxyConnectIOsb.Status = status;
            SysDclAst (ProxyNetHostConnectAst, 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 (ProxyNetHostConnectAst, tkptr);
      return;
   }

   if (IPADDRESS_IS_V4 (&tkptr->ConnectIpAddress))
   {
      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))
   {
      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);

   if (ProxyConnectTimeoutSeconds)
   {
      status = sys$setimr (0, &ProxyNetConnectTimeoutDelta,
                           &ProxyNetHostConnectTimeoutAst, tkptr, 0);
      if (VMSnok (status))
      {
         /* leave it to the AST function to report! */
         tkptr->ProxyConnectIOsb.Status = status;
         SysDclAst (ProxyNetHostConnectAst, tkptr);
         return;
      }
   }

   status = sys$qio (EfnNoWait, tkptr->NetIoPtr->Channel, IO$_ACCESS,
                     &tkptr->ProxyConnectIOsb, &ProxyNetHostConnectAst, tkptr,
                     0, 0, &tkptr->ProxySocketNameItem, 0, 0, 0);

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

/****************************************************************************/
/*
Called as an AST from ProxyNetHostConnect(). The remote host didn't respond.
*/

ProxyNetHostConnectTimeoutAst (PROXY_TASK *tkptr)

{
   sys$cancel (tkptr->NetIoPtr->Channel);

   tkptr->ProxyConnectIOsb.Status = SS$_TIMEOUT;
}

/****************************************************************************/
/*
Called as an AST from ProxyNetHostConnect().  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.
*/

ProxyNetHostConnectAst (PROXY_TASK *tkptr)

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

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

   if (WATCHMOD (tkptr, WATCH_MOD_PROXY))
      WatchThis (WATCHITM(tkptr), WATCH_MOD_PROXY,
                 "ProxyNetHostConnectAst() !&F !&Z !&S", ProxyNetHostConnectAst,
                 tkptr->ConnectHostPortPtr, tkptr->ProxyConnectIOsb.Status);

   /* First cancel the host connection timer */
   sys$cantim (tkptr,0);

   rqptr = tkptr->RequestPtr;

   if (tkptr->ConnectionCount > 1)
   {
      /***************************************/
      /* persistent proxy->origin connection */
      /***************************************/

      /* of course shouldn't be able to read anything at all at this stage!! */
      if (VMSnok (tkptr->ProxyConnectIOsb.Status))
      {
         if (tkptr->ProxyConnectIOsb.Status == SS$_SUSPENDED)
         {
            /* this means the non-blocking peek found nothing buffered */
            tkptr->ProxyConnectIOsb.Status = SS$_NORMAL;
         }
         else
         {
            /* assume just a remote disconnection, close and restart */
            ProxyNetCloseSocket (tkptr);
            ProxyNetHostConnect (tkptr);
            return;
         }
      }
      else
      {
         /* so now that we did consider it an error, close and restart */
         ProxyNetCloseSocket (tkptr);
         ProxyNetHostConnect (tkptr);
         return;
      }
   }

   /*************************/
   /* IP version accounting */
   /*************************/

   /* after any connection persistence has been taken into account */
   if (IPADDRESS_IS_V4 (&tkptr->ConnectIpAddress))
   {
      InstanceMutexLock (INSTANCE_MUTEX_HTTPD);
      ProxyAccountingPtr->ConnectIpv4Count++;
      /* [ipv4=0,ipv6=1][ipv4=0,ipv6=1] */
      if (IPADDRESS_IS_V4 (&tkptr->ServicePtr->ServerIpAddress))
         ProxyAccountingPtr->GatewayIpvCount[0][0]++;
      else
         ProxyAccountingPtr->GatewayIpvCount[1][0]++;
      InstanceMutexUnLock (INSTANCE_MUTEX_HTTPD);
   }
   else
   if (IPADDRESS_IS_V6 (&tkptr->ConnectIpAddress))
   {
      InstanceMutexLock (INSTANCE_MUTEX_HTTPD);
      ProxyAccountingPtr->ConnectIpv6Count++;
      /* [ipv4=0,ipv6=1][ipv4=0,ipv6=1] */
      if (IPADDRESS_IS_V4 (&tkptr->ServicePtr->ServerIpAddress))
         ProxyAccountingPtr->GatewayIpvCount[0][1]++;
      else
         ProxyAccountingPtr->GatewayIpvCount[1][1]++;
      InstanceMutexUnLock (INSTANCE_MUTEX_HTTPD);
   }
   else
      ErrorExitVmsStatus (SS$_BUGCHECK, ErrorSanityCheck, FI_LI);

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

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

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

      /* if we have some affinity hint, use it */
      if (tkptr->ProxyAffinityInUse)
      {
	  ProxyNetResolveHost (tkptr);
	  return;
      }

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

      /* if we have an other address cached for this host, retry */
      status = TcpIpCacheNameToAddress (&tkptr->HostLookup,
	                                tkptr->RequestHostName,
                                        strlen(tkptr->RequestHostName));
      if (VMSok(status))
      {
         ProxyNetHostConnect (tkptr);
         return;
      }

      tkptr->ResponseStatusCode = 502;

      if (rqptr->ServicePtr->ProxyTunnel == PROXY_TUNNEL_RAW ||
          rqptr->ServicePtr->ProxyTunnel == PROXY_TUNNEL_FIREWALL)
      {
         /* can end up here with chained CONNECT requests */
         ProxyEnd (tkptr);
         return;
      }

      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);

               tkptr->ResponseStatusCode = 504;
               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);
         }
      }

      ProxyEnd (tkptr);
      return;
   }

   if (LoggingProxyLocalPort)
      rqptr->ProxyLocalPort = ProxyNetLocalPort (tkptr);

   /* it's successful if the remote host accepts it */
   if (rqptr) rqptr->rqResponse.HttpStatus = 200;

   /* once we start reading into the buffer the request header is kaput */
   rqptr->rqHeader.RequestHeaderPtrInvalid = true;

   if (tkptr->ProxyTunnel)
   {
      /**************/
      /* tunnelling */
      /**************/

      if (IPADDRESS_IS_SET (&tkptr->ChainIpAddress))
         ProxyTunnelChainConnect (tkptr);
      else
      if (tkptr->ProxyTunnel == PROXY_TUNNEL_HTTPS)
         SesolaNetClientBegin (tkptr);
      else
         ProxyTunnelBegin (tkptr);

      return;
   }

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

   /* if affinity required and wasn't previouly set, do it now */
   if ((tkptr->ServicePtr->ProxyAffinity ||
        rqptr->rqPathSet.ProxyAffinity) &&
       !tkptr->ProxyAffinityInUse)
      ProxyNetSetAffinityCookie (tkptr);

   if (tkptr->ConnectPort == DEFAULT_HTTPS_PORT ||
       tkptr->RequestScheme == PROXY_SCHEME_HTTPSSL)
   {
      if (tkptr->ServicePtr->SSLclientEnabled)
      {
         if (tkptr->ConnectionCount > 1)
            ProxyWriteRequest (tkptr);
         else
            SesolaNetClientBegin (tkptr);
      }
      else
      {
         if (WATCHING (rqptr, WATCH_PROXY))
            WatchThis (WATCHITM(rqptr), WATCH_PROXY,
                       "CLIENT SSL not configured");
         rqptr->rqResponse.HttpStatus = 503;
         ErrorGeneral (rqptr, MsgFor(rqptr,MSG_PROXY_GATEWAY), FI_LI);
         ProxyEnd (tkptr);
         return;
      }
   }
   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);
}

/*****************************************************************************/
/*
Get the local port of the outgoing connection (only for access log purposes).
*/

int ProxyNetLocalPort (PROXY_TASK *tkptr)

{
   int  status,
        SocketNameLength;
   IO_SB  IOsb;
   REQUEST_STRUCT  *rqptr;
   SOCKADDRESS  SocketName;
   VMS_ITEM_LIST3  SocketNameItem;
   VMS_ITEM_LIST3  *il3ptr;

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

   rqptr = tkptr->RequestPtr;

   if (WATCHMOD (rqptr, WATCH_MOD_PROXY))
      WatchThis (WATCHITM(rqptr), WATCH_MOD_PROXY, "ProxyNetLocalPort()");

   il3ptr = &SocketNameItem;
   if (IPADDRESS_IS_V4 (&tkptr->ConnectIpAddress))
   {
      il3ptr->buf_len = sizeof(SOCKADDRIN);
      il3ptr->item = 0;
      il3ptr->buf_addr = &SocketName.sa.v4;
      il3ptr->ret_len = &SocketNameLength;
   }
   else
   if (IPADDRESS_IS_V6 (&tkptr->ConnectIpAddress))
   {
      il3ptr->buf_len = sizeof(SOCKADDRIN6);
      il3ptr->item = 0;
      il3ptr->buf_addr = &SocketName.sa.v6;
      il3ptr->ret_len = &SocketNameLength;
   }
   else
      ErrorExitVmsStatus (SS$_BUGCHECK, ErrorSanityCheck, FI_LI);

   status = sys$qiow (EfnWait, tkptr->NetIoPtr->Channel, IO$_SENSEMODE,
                      &IOsb, 0, 0, 0, 0, il3ptr, 0, 0, 0);
   if (VMSok (status)) status = IOsb.Status;
   if (VMSnok (status))
   {
      ErrorNoticed (rqptr, status, "ProxyNetLocalPort()", FI_LI);
      return (0);
   }

   if (IPADDRESS_IS_V4 (&tkptr->ConnectIpAddress))
      return (ntohs(SocketName.sa.v4.SIN$W_PORT));
   else
      return (ntohs(SocketName.sa.v6.SIN6$W_PORT));
}

/*****************************************************************************/
/*
Create a proxy cookie to set client affinity to the origin server.
*/

ProxyNetSetAffinityCookie (PROXY_TASK *tkptr)

{
   int  idx;
   unsigned short  Length;
   char  *cptr;
   char  Buffer [256];
   REQUEST_STRUCT  *rqptr;

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

   rqptr = tkptr->RequestPtr;

   if (WATCHMOD (rqptr, WATCH_MOD_PROXY))
      WatchThis (WATCHITM(rqptr), WATCH_MOD_PROXY, "ProxyNetSetAffinityCookie()");

   /* set proxy preferred origin server cookie */
   FaoToBuffer (Buffer, sizeof(Buffer), &Length, "!AZ!AZ=!&I; path=/;",
                PROXY_AFFINITY_COOKIE_PREFIX, tkptr->RequestHostName, 
                &tkptr->ConnectIpAddress);

   cptr = VmGetHeap (rqptr, Length+1);
   for (idx = 0; idx < RESPONSE_COOKIE_MAX; idx++)
   {
      if (!rqptr->rqResponse.CookiePtr[idx])
      {
         memcpy (rqptr->rqResponse.CookiePtr[idx] = cptr, Buffer, Length+1);
         break;
      }
   }

   if (WATCHING (rqptr, WATCH_PROXY))
      WatchThis (WATCHITM(rqptr), WATCH_PROXY, "AFFINITY cookie set to !&I",
                 &tkptr->ConnectIpAddress);
}

/*****************************************************************************/
/*
Wrapper for AST parameter |tkptr|.
*/

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

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

   if (WATCHMOD (tkptr, WATCH_MOD_PROXY))
      WatchThis (WATCHITM(tkptr), WATCH_MOD_PROXY,
                  "ProxyNetWrite() !&F !&A !&X !UL", &ProxyNetWrite,
                  AstFunction, DataPtr, DataLength);

   return (NetIoWrite (tkptr->NetIoPtr, AstFunction, tkptr,
           DataPtr, DataLength));
}

/*****************************************************************************/
/*
Wrapper for AST parameter |tkptr|.
*/ 

int ProxyNetRead
(
PROXY_TASK *tkptr,
PROXY_AST AstFunction,
char *DataPtr,
int DataSize
)
{
   /*********/
   /* begin */
   /*********/

   if (WATCHMOD (tkptr, WATCH_MOD_PROXY))
      WatchThis (WATCHITM(tkptr), WATCH_MOD_PROXY,
                  "ProxyNetRead() !&F !&A !&X !UL", &ProxyNetRead,
                  AstFunction, DataPtr, DataSize);

   return (NetIoRead (tkptr->NetIoPtr, AstFunction, tkptr, DataPtr, DataSize));
}

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

int ProxyNetCloseSocket (PROXY_TASK *tkptr)

{
   int  status;

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

   if (WATCHMOD (tkptr, WATCH_MOD_PROXY))
      WatchThis (WATCHITM(tkptr), WATCH_MOD_PROXY,
                 "ProxyNetCloseSocket() !&F", &ProxyNetCloseSocket);

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

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

   status = sys$dassgn (tkptr->NetIoPtr->Channel);

   tkptr->NetIoPtr->Channel = 0;

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

   return (status);
}

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

BOOL ProxyNetInProgress (PROXY_TASK *tkptr)

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

   if (WATCHMOD (tkptr, WATCH_MOD_PROXY))
      WatchThis (WATCHITM(tkptr), WATCH_MOD_PROXY,
                 "ProxyNetInProgress() inprog:!&B body:!&B",
                 NETIO_IN_PROGRESS (tkptr->NetIoPtr), tkptr->QueuedBodyRead);

   if (NETIO_IN_PROGRESS (tkptr->NetIoPtr)) return (true);
   if (tkptr->QueuedBodyRead) return (true);
   return (false);
}

/****************************************************************************/
/*
Create a persistent connection structure (using process memory) and add it to
the list.  Copy the channel number from the proxy task socket into the
structure (zeroing the task channel), along with the IP address and port of the
connection.
*/

ProxyNetConnectPersist (PROXY_TASK *tkptr)

{
   int  status;
   PROXY_CONNECT  *pcptr;

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

   if (WATCHMOD (tkptr, WATCH_MOD_PROXY))
      WatchThis (WATCHITM(tkptr), WATCH_MOD_PROXY,
                 "ProxyNetConnectPersist() !&I,!UL !UL",
                 &tkptr->ConnectIpAddress, tkptr->ConnectPort,
                 tkptr->ConnectionCount);

   if (ProxyNetConnectFreeCount &&
       ProxyNetConnectCount >= ProxyConnectPersistMax)
   {
      InstanceGblSecIncrLong (&ProxyAccountingPtr->ConnectPersistFull);
      return;
   }

   if (pcptr = ProxyNetConnectFreeList.HeadPtr)
   {
      ListRemove (&ProxyNetConnectFreeList, pcptr);
      memset (pcptr, 0, sizeof(PROXY_CONNECT));
      if (ProxyNetConnectFreeCount) ProxyNetConnectFreeCount--;
   }
   else
      pcptr = (PROXY_CONNECT*)VmGet(sizeof(PROXY_CONNECT));

   /* move the network I/O structure from the task */
   pcptr->NetIoPtr = tkptr->NetIoPtr;
   tkptr->NetIoPtr = NULL;

   pcptr->SSLclientEnabled = tkptr->ServicePtr->SSLclientEnabled;

   pcptr->EntryTickSecond = HttpdTickSecond;
   IPADDRESS_COPY (&pcptr->ConnectIpAddress, &tkptr->ConnectIpAddress);
   pcptr->ConnectPort = tkptr->ConnectPort;
   pcptr->ConnectionCount = tkptr->ConnectionCount;

   ListAddTail (&ProxyNetConnectList, pcptr, LIST_ENTRY_TYPE_PROXY);

   ProxyNetConnectCount++;
   InstanceMutexLock (INSTANCE_MUTEX_HTTPD);
   if (ProxyNetConnectCount > ProxyAccountingPtr->ConnectPersistPeak)
      ProxyAccountingPtr->ConnectPersistPeak = ProxyNetConnectCount;
   InstanceMutexUnLock (INSTANCE_MUTEX_HTTPD);
}

/****************************************************************************/
/*
Search through any list of persistent connections comparing the requests IP
address and port to that stored in each list entry.  If one matched copy the
channel and persistence count to the proxy task and remove the entry from the
list.  Returns with the task connection socket channel non-zero if it found a
match or still as zero if not.
*/

ProxyNetConnectSearch (PROXY_TASK *tkptr)

{
   int  status;
   LIST_ENTRY  *leptr;
   PROXY_CONNECT  *pcptr;

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

   if (WATCHMOD (tkptr, WATCH_MOD_PROXY))
      WatchThis (WATCHITM(tkptr), WATCH_MOD_PROXY,
                 "ProxyNetConnectSearch() !&I,!UL !UL",
                 &tkptr->ConnectIpAddress, tkptr->ConnectPort,
                 ProxyNetConnectCount);

   /* important these be zeroed to indicate search failure */
   tkptr->NetIoPtr->Channel = tkptr->ConnectionCount = 0;

   if (!ProxyNetConnectCount) return;

   for (leptr = ProxyNetConnectList.HeadPtr; leptr; leptr = leptr->NextPtr)
   {
      pcptr = (PROXY_CONNECT*)leptr;

      if (WATCHMOD (tkptr, WATCH_MOD_PROXY))
         WatchThis (WATCHITM(tkptr), WATCH_MOD_PROXY, "!&I,!UL !UL !UL !&B !&B",
                    &pcptr->ConnectIpAddress, pcptr->ConnectPort,
                    pcptr->ConnectionCount,
                    pcptr->NetIoPtr->Channel,
                    pcptr->SSLclientEnabled,
                    tkptr->ServicePtr->SSLclientEnabled);

      if (!IPADDRESS_IS_SAME (&pcptr->ConnectIpAddress,
                              &tkptr->ConnectIpAddress)) continue;
      if (pcptr->ConnectPort != tkptr->ConnectPort) continue;
      if (pcptr->SSLclientEnabled !=
          tkptr->ServicePtr->SSLclientEnabled) continue;

      /*******/
      /* hit */
      /*******/

      /* move the network I/O structure to the task */
      tkptr->NetIoPtr = pcptr->NetIoPtr;
      pcptr->NetIoPtr;
      if (tkptr->NetIoPtr->SesolaPtr)
         SesolaNetSetProxyTask (tkptr);

      tkptr->ConnectionCount = pcptr->ConnectionCount + 1;

      ListRemove (&ProxyNetConnectList, pcptr);
      if (ProxyNetConnectCount) ProxyNetConnectCount--;
      ListAddTail (&ProxyNetConnectFreeList, pcptr, LIST_ENTRY_TYPE_PROXY);
      ProxyNetConnectFreeCount++;

      InstanceMutexLock (INSTANCE_MUTEX_HTTPD);
      ProxyAccountingPtr->ConnectPersistCount++;
      if (tkptr->ConnectionCount > ProxyAccountingPtr->ConnectPersistMax)
         ProxyAccountingPtr->ConnectPersistMax = tkptr->ConnectionCount;
      InstanceMutexUnLock (INSTANCE_MUTEX_HTTPD);

      if (WATCHING (tkptr, WATCH_PROXY))
         WatchThis (WATCHITM(tkptr), WATCH_PROXY,
                    "PERSISTENT !UL !AZ,!UL",
                    tkptr->ConnectionCount,
                    tkptr->RequestHostName, tkptr->RequestPort); 
      return;
   }

   if (WATCHMOD (tkptr, WATCH_MOD_PROXY))
      WatchThis (WATCHITM(tkptr), WATCH_MOD_PROXY, "NOT FOUND");
}

/****************************************************************************/
/*
Called by HttpdTick() once a second.  Scan the list of persistent proxy->origin
connections looking for those that have timed-out.  For each timeout just close
the socket by deassigning the channel and remove and free the entry from the
list.  If 'TimeoutSeconds' is negative use the configuration parameter.  If
zero then timeout all of the connections (purge the list).  If a positive
integer use this as the timeout period.
*/

int ProxyNetConnectSupervisor (int TimeoutPeriod)

{
   int  status,
        TimeoutSeconds;
   LIST_ENTRY  *leptr;
   PROXY_CONNECT  *pcptr;

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

/**
   if (WATCH_MODULE(WATCH_MOD_PROXY))
      WatchThis (WATCHALL, WATCH_MOD_PROXY,
                 "ProxyNetConnectSupervisor() !SL !UL !UL !UL",
                 TimeoutPeriod, ProxyConnectPersistSeconds,
                 HttpdTickSecond, ProxyNetConnectCount);
**/

   if (!ProxyNetConnectCount) return (false);

   if (!TimeoutPeriod)
      TimeoutSeconds = 0;
   else
   if (TimeoutPeriod < 0)
      TimeoutSeconds = ProxyConnectPersistSeconds;
   else
      TimeoutSeconds = TimeoutPeriod;

   for (leptr = ProxyNetConnectList.HeadPtr; leptr; leptr = leptr->NextPtr)
   {
      pcptr = (PROXY_CONNECT*)leptr;

      if (WATCH_MODULE(WATCH_MOD_PROXY))
         WatchThis (WATCHALL, WATCH_MOD_PROXY,
                    "!&I,!UL !UL+!UL(!&B) !UL !UL",
                    &pcptr->ConnectIpAddress, pcptr->ConnectPort,
                    pcptr->EntryTickSecond, TimeoutSeconds,
                    pcptr->EntryTickSecond + TimeoutSeconds <= HttpdTickSecond,
                    pcptr->ConnectionCount,
                    pcptr->NetIoPtr->Channel);

      if (pcptr->EntryTickSecond + TimeoutSeconds < HttpdTickSecond)
      {
         if (pcptr->NetIoPtr)
         {
            NetIoEnd (pcptr->NetIoPtr);
            pcptr->NetIoPtr = NULL;
         }
         ListRemove (&ProxyNetConnectList, pcptr);
         if (ProxyNetConnectCount) ProxyNetConnectCount--;
         ListAddTail (&ProxyNetConnectFreeList, pcptr, LIST_ENTRY_TYPE_PROXY);
         ProxyNetConnectFreeCount++;
      }
   }

   if (ProxyNetConnectCount) return (true);

   return (false);
}

/*****************************************************************************/
/*
Return a report on proxy->origin server persistent connections.
*/ 

ProxyNetPersistentReport (REQUEST_STRUCT *rqptr)

{
   static char  BeginPageFao [] =
"<p><table class=\"ctgry\">\n\
<tr><td>\n\
<table class=\"rghtlft\">\n\
<tr><th>Persist:</th><td colspan=\"2\">!AZ</td></tr>\n\
<tr><th>Limit:</th><td>!UL</td></tr>\n\
<tr><th>Current:</th>\
<td>!UL</td><td>(!UL)</td></tr>\n\
<tr><th>Full:</th><td>!UL</td></tr>\n\
<tr><th>Peak:</th><td>!UL</td></tr>\n\
<tr><th>Max:</th><td>!UL</td></tr>\n\
<tr><th>Count:</th>\
<td>!UL</td><td>(!UL%)</td></tr>\n\
</table>\n\
</td></tr>\n\
</table>\n\
\
<p><table class=\"rghtrght\">\n\
<tr>\
<th></th>\
<th class=\"sbttl\">Address</th>\
<th class=\"sbttl\">Port</th>\
<th class=\"sbttl\">Count</th>\
<th class=\"sbttl\">Persist</th>\
</tr>\n";

   static char  ItemFao [] =
"<tr class=\"hlght\"><th>!3ZL</th>\
<td class=\"talft\">!&I</td>\
<td>!UL</td>\
<td>!UL</td>\
<td>!AZ</td>\
<tr>\n";

   static char  ItemOddFao [] =
"<tr><th>!3ZL</th>\
<td>!&I&nbsp;&nbsp;</td>\
<td>!UL&nbsp;&nbsp;</td>\
<td>!UL&nbsp;&nbsp;</td>\
<td>!AZ</td>\
<tr>\n";

   static char EmptyListFao [] =
"<tr class=\"hlght\"><th>000</th>\
<td class=\"talft\" colspan=\"4\"><i>empty</i></td><tr>\n";

   static char  EndPageFao [] =
"</table>\n\
!AZ\
</div>\n\
</body>\n\
</html>\n";

   int  status,
        CountItem;
   unsigned short  Length;
   unsigned long  FaoVector [32];
   unsigned long  *vecptr;
   LIST_ENTRY  *leptr;
   PROXY_CONNECT  *pcptr;

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

   if (WATCHMOD (rqptr, WATCH_MOD_PROXY))
      WatchThis (WATCHITM(rqptr), WATCH_MOD_PROXY,
                 "ProxyNetPersistentReport()");

   AdminPageTitle (rqptr, "Proxy Connection Report");

   InstanceMutexLock (INSTANCE_MUTEX_HTTPD);

   vecptr = FaoVector;

   *vecptr++ = MetaConShowSeconds (rqptr, ProxyConnectPersistSeconds);
   *vecptr++ = ProxyConnectPersistMax;
   *vecptr++ = ProxyNetConnectCount;
   *vecptr++ = ProxyNetConnectFreeCount;
   *vecptr++ = ProxyAccountingPtr->ConnectPersistFull;
   *vecptr++ = ProxyAccountingPtr->ConnectPersistPeak;
   *vecptr++ = ProxyAccountingPtr->ConnectPersistMax;
   *vecptr++ = ProxyAccountingPtr->ConnectPersistCount;
   *vecptr++ = PercentOf (ProxyAccountingPtr->ConnectPersistCount,
                          ProxyAccountingPtr->ConnectIpv4Count +
                          ProxyAccountingPtr->ConnectIpv6Count);

   InstanceMutexUnLock (INSTANCE_MUTEX_HTTPD);

   status = FaolToNet (rqptr, BeginPageFao, &FaoVector);
   if (VMSnok (status)) ErrorNoticed (rqptr, status, NULL, FI_LI);

   CountItem = 0;
   for (leptr = ProxyNetConnectList.HeadPtr; leptr; leptr = leptr->NextPtr)
   {
      pcptr = (PROXY_CONNECT*)leptr;

      vecptr = FaoVector;

      *vecptr++ = ++CountItem;
      *vecptr++ = &pcptr->ConnectIpAddress;
      *vecptr++ = pcptr->ConnectPort;
      *vecptr++ = pcptr->ConnectionCount;
      *vecptr++ = MetaConShowSeconds (rqptr,
                     HttpdTickSecond - pcptr->EntryTickSecond);

      if (CountItem & 1)
         status = FaolToNet (rqptr, ItemOddFao, &FaoVector);
      else
         status = FaolToNet (rqptr, ItemFao, &FaoVector);
      if (VMSnok (status)) ErrorNoticed (rqptr, status, NULL, FI_LI);
   }
   if (!ProxyNetConnectList.HeadPtr)
   {
      status = FaolToNet (rqptr, EmptyListFao, NULL);
      if (VMSnok (status)) ErrorNoticed (rqptr, status, NULL, FI_LI);
   }

   status = FaoToNet (rqptr, EndPageFao, AdminRefresh());
   if (VMSnok (status)) ErrorNoticed (rqptr, status, NULL, FI_LI);

   rqptr->rqResponse.PreExpired = PRE_EXPIRE_ADMIN;
   ResponseHeader200 (rqptr, "text/html", &rqptr->NetWriteBufferDsc);

   AdminEnd (rqptr);
}

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

