/*****************************************************************************/
/*
                                 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
---------------
27-APR-2021  MGD  BSD 4.4 sockaddr.. IO$M_EXTEND to $QIO
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 PercentOf32() 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;

int64  ProxyNetConnectTimeoutDelta;

LIST_HEAD  ProxyNetConnectList,
           ProxyNetConnectFreeList;

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

extern BOOL  LoggingProxyLocalPort;

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

extern char  ErrorSanityCheck[],
             SoftwareID[];

extern struct dsc$descriptor TcpIpDeviceDsc;

extern ACCOUNTING_STRUCT  *AccountingPtr;
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 (ProxyConnectTimeoutSeconds < 0 || ProxyConnectTimeoutSeconds > 60)
      ProxyConnectTimeoutSeconds = 0;
   else
   if (ProxyConnectTimeoutSeconds)
      ProxyNetConnectTimeoutDelta = DELTA64_ONE_SEC *
                                    ProxyConnectTimeoutSeconds;
}


/****************************************************************************/
/*
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  qiofun, 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)

   /*******************/
   /* 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$B_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;
      qiofun = IO$_SETMODE;
   }
   else
   if (IPADDRESS_IS_V6 (&tkptr->ConnectIpAddress))
   {
      TcpSocketPtr = &TcpIpSocket6;
      qiofun = IO$_SETMODE | IO$M_EXTEND;
   }
   else
      ErrorExitVmsStatus (SS$_BUGCHECK, ErrorSanityCheck, FI_LI);

   /* make the channel a TCP, connection-oriented socket */
   for (;;)
   { 
      status = sys$qiow (EfnWait, tkptr->NetIoPtr->Channel,
                         qiofun, &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$B_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;

      qiofun = IO$_ACCESS;
   }
   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;

      qiofun = IO$_ACCESS | IO$M_EXTEND;
   }
   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, qiofun,
                     &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 */
      IPADDRESS_SET_UNUSABLE (&tkptr->HostLookup.IpAddress);
      TcpIpCacheSetEntry (&tkptr->HostLookup);

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

               if (rqptr->rqHeader.UpgradeSocks5Ptr)
                  ProxySocks5Reply (rqptr, SOCKS5_REPLY_REFUSED);
               else
                  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);

               if (rqptr->rqHeader.UpgradeSocks5Ptr)
                  ProxySocks5Reply (rqptr, SOCKS5_REPLY_HOSTREACH);
               else
                  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);

               if (rqptr->rqHeader.UpgradeSocks5Ptr)
                  ProxySocks5Reply (rqptr, SOCKS5_REPLY_HOSTREACH);
               else
                  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;

               if (rqptr->rqHeader.UpgradeSocks5Ptr)
                  ProxySocks5Reply (rqptr, SOCKS5_REPLY_FAILURE);
               else
                  ErrorVmsStatus (rqptr, tkptr->ProxyConnectIOsb.Status, FI_LI);
         }
      }

      ProxyEnd (tkptr);
      return;
   }

   if (WATCHING (tkptr, WATCH_PROXY))
      WatchThis (WATCHITM(tkptr), WATCH_PROXY, "CONNECTED !&I,!UL as !&I !AZ",
                 &tkptr->ConnectIpAddress, tkptr->ConnectPort,
                 &tkptr->BindIpAddress,
                 NetGetBgDevice(tkptr->NetIoPtr->Channel, NULL, 0));

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

   TcpIpSocketMaxQio (tkptr->NetIoPtr);

   /* 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
         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  qiofun, 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;

      qiofun = IO$_SENSEMODE;
   }
   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;

      qiofun = IO$_SENSEMODE | IO$M_EXTEND;
   }
   else
      ErrorExitVmsStatus (SS$_BUGCHECK, ErrorSanityCheck, FI_LI);

   status = sys$qiow (EfnWait, tkptr->NetIoPtr->Channel,
                      qiofun, &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);
}

/****************************************************************************/
/*
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++;

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

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

