/*****************************************************************************/
/*
                                 Net.c

The networking essentials are based on DEC TCP/IP Services for OpenVMS (aka
UCX). The QIO interface was obviously chosen because of its asynchronous,
non-blocking capabilities. Although some functionalilty could have been
implemented using the BSD sockets abstraction and only the asynchronous I/O
done using the QIO I opted for working out the QIOs. It wasn't too difficult
and avoids the (slight) extra overhead of the sockets layer.

With resource wait explicitly enabled all sys$qio()s should wait until
resources become available. The only execption is ASTLM, a fundamental
requirement for this server. If SS$_EXQUOTA is returned from a sys$qio() (i.e.
without wait for completion, and therefore depending on AST delivery) then
EXIT THE SERVER with error status!

The 'ServiceStruct' structure has two groups of fields and performs two basic
roles. First, it provides a linked list which can be traversed to determine
from the destination IP address of a request which host was specified in a
(multi-homed) request. Second, for each unique port specified for the same
server the client components of the structure are used for the accept()ing of
requests.  Note that there may be more 'service' structures in the list than
actual sockets created (and client components used) because only one is
allocated against a given port even though the services may specify multiple
host names using that port.

The server identity is derived from the 'ServerHostName' (gethostname()) name
and the 'ServerPort' (which is the first port specified as a service, or the
port specified in the configuration or /PORT= qualifier).


WASD_NET_TEST_BREAK
-------------------
See logical name WASD_NET_TEST_BREAK and NetTestSupervisor() for assistance
with testing server behaviour under less-than ideal circumstances.


VERSION HISTORY
---------------
30-JAN-2020  MGD  NetIoWriteStatus() when request terminated by timeout
19-DEC-2019  MGD  bugfix; NetCreateService() only SesolaInitService() once
                  bugfix; use NetIoWriteStatus() to maintain NETIO ASTs
07-APR-2018  MGD  NetWriteBuffered() now writes before reset if necessary
                  NetWriteStrDsc() can now write synchronously
14-NOV-2017  MGD  NetResponseHeaderAst() fudge status as necessary
19-MAY-2016  MGD  bugfix; NetWrite() response header write error handling
11-AUG-2015  MGD  restructure of network I/O abstractions
09-JUN-2014  MGD  accounting for network blocks
07-JUN-2014  MGD  NetCreateService() check bind address string instead of
                    address to allow binding primary to 0.0.0.0 (INADDR_ANY)
15-OCT-2011  MGD  NetPurge() becomes NetControl() with expanded capability
12-FEB-2011  MGD  NetCloseSocket() using WatchFilterClientService() so
                    that final close can be noted if required
30-OCT-2010  MGD  NetCreateService() set primary service after address
23-JAN-2010  MGD  NetPeek() now requires buffer arguments
14-NOV-2009  MGD  NetPeek() non-consuming read
                  bugfix; NetHostNameLookup() IP address zeroed
13-OCT-2009  MGD  NetHostNameLookup() now 'resolves' IP address 'host names'
23-SEP-2009  MGD  bugfix; NetWriteStrDsc() flush all full descriptors
12-SEP-2009  MGD  bugfix; NetWriteGzip() ensure buffer size <= 65535
31-JUL-2009  MGD  NetCreateService() use primary if service IP addr reset
26-MAY-2009  MGD  NetHostNameLookup() retry attempts from zero to two
26-APR-2008  MGD  bugfix; NetRead() redact into DataPtr *not* into
                    rqNet.ReadBufferPtr (which works until subsequent read :-)
25-NOV-2007  MGD  NetRead() redact buffer processing
04-OCT-2007  MGD  call TcpIpSocketBufferSize()
10-JUN-2007  MGD  use STR_DSC
26-OCT-2006  MGD  bugfix; NetAcceptProcess() and NetDirectResponse()
                    should issue 503 for 'too busy', not 502
12-SEP-2006  MGD  bugfix; NetAccept(), NetAcceptAst(), NetAcceptProcess()
                    nasty problem where multihomed servers 'svptr' confusion
                    (due to the multihome pointer manipulation) could result
                    in an attempted re-queue of an accept on a service that
                    did not correspond to the original accept AST delivery
                    with the result that no accept ended up being queued
22-AUG-2006  MGD  maintenance; there seem to have been some changes in the
                    underlying TCP/IP Services handling of shared sockets
                  NetAcceptAst() if [DclGatewayBg] set socket share on client
                  NetClientSocketCcl() to control BG device carriage-control
                    (to parallel the APACHE$SET_CCL.EXE functionality)
08-AUG-2006  MGD  NetWrite() accomodate 304 (not modified)
15-JUL-2006  MGD  NetSuspend() and NetResume() to allow halt and resume
                    request processing
                  NetPassive() and NetActive() to allow non-supervisor
                     instances to be made quiescent 
                  NetPurge() to break idle (and in-progress) connections
01-APR-2006  MGD  no kidding; NetRead() cast AST routine and RequestGet
                  using (char*) to prevent %CC-E-NOEQUALITY compile-time
                  error under VAX VMS V7.3 and DECC V6.4
30-AUG-2005  JPP  bugfix; raw proxy tunnelling requires a contrived connect
                  request in NetRead() to initiate an AST to RequestGet()
10-JUN-2005  MGD  make EXQUOTA (particularly ASTLM) a little more obvious,
                  handle NetAccept() EXQUOTA via NetAcceptSupervisor()
26-MAY-2005  MGD  bugfix; multi-instance socket device name handling
19-APR-2005  MGD  revised multihoming so that the client specified IP address
                  of a accept()ed connection is used to identify the service
                  (this allows easier isolation of SSL certificates, etc.)
16-MAR-2005  JPP  allow client-side GZIPing of proxied responses
28-JAN-2005  MGD  bugfix; aarghh! NetWriteGzip()/NetWriteGzipAst()
20-JAN-2005  MGD  bugfix; NetWriteChunked() ensure an empty body is
                  terminated with a chunk of zero
13-JAN-2005  MGD  bugfix; NetWrite() distinguish between "empty" data and
                  end-of-stream (inducing occasional ZLIB buffer errors)
10-JAN-2005  MGD  NetWriteGzip() abandon using argument counts to determine
                  AST usage or direct call, use NetWriteGzipAst() instead
08-JAN-2005  MGD  minor mod to handle GZIP buffer flush
21-DEC-2004  MGD  bugfix; NetWriteGzip() AST no remaining data length
16-DEC-2004  MGD  NetWriteChunked() and NetWriteRawP5()
15-NOV-2004  MGD  bugfix; handling of GZIP encoding (with GZIP.C)
                  (thanks to jpp@esme.fr for isolating this during BETA)
16-OCT-2004  MGD  network write changes for GZIP encoding
10-APR-2004  MGD  significant modifications to support IPv6
18-FEB-2004  MGD  NetWriteBufferedSizeInit()
30-DEC-2003  MGD  NetTcpIpAgentInfo() mods for IA64
07-JUL-2003  MGD  support response header none/append mappings,
                  cache loading from network output
26-FEB-2003  MGD  disable 'NetMultiHome' (should not be required
                  for modern virtual service processing)
15-JUL-2002  MGD  all 'xray' functionality now performed in RequestScript()
02-JUN-2002  MGD  rework NetCreateService() to allow SS$_IVADDR (invalid
                  media address) service to be supported by using INADDR_ANY
25-APR-2002  MGD  NetAcceptSupervisor() and associated NOIOCHAN redress
31-MAR-2002  MGD  integrate client connection data into request structure,
                  make client host name lookup asynchronous
22-JAN-2002  MGD  bugfix; NetAcceptAst() deassign channel when connection
                  dropped during accept processing (jpp@esme.fr)
18-JAN-2002  MGD  allow ->BindIpAddressString to specify 0.0.0.0 (INADDR_ANY)
29-SEP-2001  MGD  service creation now supports multiple channels to the
                  one listening socket device for multiple per-node instances
04-AUG-2001  MGD  support module WATCHing,
                  CONNECT method connection cancellation is normal behaviour
04-JUL-2001  MGD  bugfix; (completed from 02-JUN, this time for SSL services),
                  also change behaviour, if a bind to INADDR_ANY fails attempt
                  to bind to the resolved host name address
02-JUN-2001  MGD  bugfix; port check when IP address explicitly supplied
18-APR-2001  MGD  rqNet.WriteErrorCount and rqNet.ReadErrorCount,
                  introduce NetTcpIpAgentInfo() (adapted from WATCH.C),
                  bugfix; NetThisVirtualService()
13-FEB-2001  MGD  ntohs() on client port
22-NOV-2000  MGD  rework service creation
17-OCT-2000  MGD  modify SSL initialization so that "sharing" conditions
                  (same port on same IP address) are more easily identified
08-AUG-2000  MGD  client sockets C_SHARE for direct script output to BG:
17-JUN-2000  MGD  modifications for SERVICE.C requirements
10-MAY-2000  MGD  per-service session tracking,
                  per-service listen queue backlog (for Compaq TCP/IP 5.0ff)
29-APR-2000  MGD  proxy authorization
10-NOV-1999  MGD  add IO$_NOWAIT to NetWriteDirect()
25-OCT-1999  MGD  remove NETLIB support
10-OCT-1999  MGD  allow virtual services more latitude,
                  check for request supervisor request timeout,
                  add FULL_DUPLEX_CLOSE,
                  workaround TCPWARE 5.3-3 behaviour (Laishev@SMTP.DeltaTel.RU)
18-AUG-1999  MGD  bugfix; parsing certificate/key from service
11-JUN-1999  MGD  bugfix; NetTooBusy() sys$fao() charset,
                  bugfix; NetDummyReadAst() UCX IOsb by reference
26-MAY-1999  MGD  bugfix; NetShutdownSocket() AST handling
04-APR-1999  MGD  provide HTTP/0.9 header absorbtion (finally!)
15-JAN-1999  MGD  changed AST delivery algorithm
07-NOV-1998  MGD  WATCH facility
24-OCT-1998  MGD  allow SSL certificate to be specified per-service
08-APR-1998  MGD  allow legitimate connects to be CANCELed in NetAcceptAst()
19-JAN-1998  MGD  redesigned NetWriteBuffered()
27-NOV-1997  MGD  hmmm, rationalized AST processing by making network
                  writes always deliver an AST (implicit or explicit)
                  (causing ACCVIOs for the last couple of versions)
25-OCT-1997  MGD  changes to MsgFor() function
06-SEP-1997  MGD  multi-homed hosts and multi-port services
30-AUG-1997  MGD  bugfix; get server host name before starting logging
                  (woops, problem introduced with NETLIB)
09-AUG-1997  MGD  message database
23-JUL-1997  MGD  HTTPD v4.3, MultiNet dropped, using the NETLIB Library
01-FEB-1997  MGD  HTTPd version 4
27-SEP-1996  MGD  add dummy read for early error reporting
12-APR-1996  MGD  observed Multinet disconnection/zero-byte behaviour
                  (request now aborts if Multinet returns zero bytes)
03-JAN-1996  MGD  support for both DEC TCP/IP Services and TGV MultiNet
01-DEC-1995  MGD  NetWriteBuffered() for improving network I/O
20-DEC-1994  MGD  multi-threaded version
20-JUN-1994  MGD  single-threaded version
*/
/*****************************************************************************/

#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 <stdarg.h>
#include <stdio.h>
#include <string.h>

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

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

#define WASD_MODULE "NET"

#define NET_TEST 0

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

BOOL  NetAcceptExQuota,
      NetAcceptFailure,
      NetAcceptNoIoChan,
      NetConnectSuspend,
      NetInstancePassive,
      NetMultiHome;

int  ConnectCountTotal,
     NetAcceptBytLmRequired,
     NetConcurrentMax,
     NetConcurrentProcessMax,
     NetCurrentHttp1Persistent,
     NetListenBytLmRequired,
     NetReadBufferSize,
     NetRequestMax,
     OutputBufferSize,
     OutputFileBufferSize,
     ServerHostNameLength;

ulong  NetCurrentConnected [HTTPN],
       NetCurrentProcessing [HTTPN];

char  ServerHostName [TCPIP_HOSTNAME_MAX+1],
      ServerHostPort [TCPIP_HOSTNAME_MAX+1+8];

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

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

extern BOOL  ControlExitRequested,
             HttpdServerStartup,
             InstanceNodeSupervisor,
             ProtocolHttpsAvailable,
             ProtocolHttpsConfigured,
             TcpIpv6Configured;

extern int  ConfigDnsLookupRetryCount,
            EfnWait,
            EfnNoWait,
            InstanceNodeConfig,
            InstanceNodeCurrent,
            InstanceNumber,
            InstanceStatusRequestCount,
            OpcomMessages,
            ServerPort,
            TcpIpSendBufferSize,
            TcpIpReceiveBufferSize,
            WebSockCurrent;

extern int  ToLowerCase[],
            ToUpperCase[];

extern char  CliLogFileName[],
             ControlBuffer[],
             ErrorSanityCheck[],
             HttpdName[],
             HttpdVersion[];

extern struct dsc$descriptor TcpIpDeviceDsc;

extern ACCOUNTING_STRUCT  *AccountingPtr;
extern CONFIG_STRUCT  Config;
extern HTTPD_GBLSEC  *HttpdGblSecPtr;
extern IPADDRESS  TcpIpEmptyAddress;
extern MSG_STRUCT  Msgs;
extern LIST_HEAD  RequestList,
                  ServiceList;
extern TCP_SOCKET_ITEM  TcpIpSocket4,
                        TcpIpSocket6;
extern VMS_ITEM_LIST2  TcpIpFullDuplexCloseOption;
extern VMS_ITEM_LIST2  TcpIpSocketCclOptionOn;
extern VMS_ITEM_LIST2  TcpIpSocketCclOptionOff;
extern VMS_ITEM_LIST2  TcpIpSocketReuseAddrOption;
extern VMS_ITEM_LIST2  TcpIpSocketShareOption;
extern WATCH_STRUCT  Watch;

/*****************************************************************************/
/*
Get local host (server) name.
*/ 

NetGetServerHostName ()

{
   int  status;
   unsigned short  Length;
   char  *cptr;

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

   if (WATCH_MODULE(WATCH_MOD_NET))
      WatchThis (WATCHALL, WATCH_MOD_NET, "NetGetServerHostName()");

#ifdef __x86_64
x86ASTs(FI_LI);
   if (gethostname (ServerHostName, sizeof(ServerHostName)) != 0)
      ErrorExitVmsStatus (vaxc$errno, "resolving server hostname", FI_LI);
x86ASTs(FI_LI);
   /* shouldn't need to re-enable ASTs - they haven't been disabled! */
   sys$setast (1);
#else
   if (gethostname (ServerHostName, sizeof(ServerHostName)) != 0)
      ErrorExitVmsStatus (vaxc$errno, "resolving server hostname", FI_LI);
#endif

   /* looks better if its all in lower case (host name is sometimes upper) */
   for (cptr = ServerHostName; *cptr; cptr++) *cptr = TOLO(*cptr);
   ServerHostNameLength = cptr - ServerHostName;

   if (!ServerHostNameLength)
      ErrorExitVmsStatus (SS$_BUGCHECK, "could not determine local host name",
                          FI_LI);
}

/*****************************************************************************/
/*
It is only necessary to create one socket for each port port on the same
server, even if that port is specified against a different (multi-home) host
name because the accept()ing socket can be interrogated to determine which
host had been specified.  Then in a infinite loop accept connections from
clients.

Binding services to sockets and allied topics ...

A service (host name and IP address) binds to INADDR_ANY and a port (say 80). 
This allows the socket to accept connections for any address supported by the
interface, and on that port.

Subsequent services (different host name but same IP address, an alias, CNAME
records?) using the same port does not (indeed could not) bind again (to
INADDR_ANY).  It just becomes part of the the existing bound socket's
environment inside the server.  Using the HTTP "Host:" field virtual services
are supported (once the HTTP request header is parsed).  Different ports are of
course bound to different sockets.

Some further subsequent service, this time with a different IP address (i.e.
implying there is a multi-homed system) wishes to establish a service on a
previously used port (say 80).  First it attempts a bind to INADDR_ANY, which
fails because the port is already bound to that (mind you it may not be, in
which case it won't fail).  So the server then retries the bind with it's IP
address, which we'll presume is OK.

Another service, with a third IP address tries to bind to INADDR_ANY on port
80, which fails, but it then successfully is bound using it's (so far) unique
address.

So far, no real site admin intervention.  It seems to simply and easily support
the potential requirements of SSL services, as well as multi-homed standard
services.  All "real" multi-homed services (with autonomous IP addresses) are
always bind against their unqiue address.  "Virtual" services using those
addresses only are ever bound once for each port, subsequent services being
software contrivances.  The [ServiceIpAddress] is still available to
"hard-wire" a service  name to a particular IP address, but the above "cascade"
of binds tends to mean it should be far less important.

Only when a service with an IP address that is not supported by the interface
is attempted to be bound does it fail (with an invalid media address IIRC).
*/ 

NetCreateService ()

{
   int  cnt, status,
        BytLmAfter,
        BytLmBefore,
        IpPort;
   unsigned short  Length,
                   ServerChannel;
   char  *cptr,
         *ProtocolPtr,
         *SocketDevNamePtr;
   IPADDRESS  IpAddress,
              PrimaryIpAddress;
   SOCKADDRIN  *sin4ptr;
   SOCKADDRIN6  *sin6ptr;
   SERVICE_STRUCT  *svptr, *tsvptr;
   TCP_SOCKET_ITEM  *TcpSocketPtr;
   VMS_ITEM_LIST2  SocketNameItem;
   VMS_ITEM_LIST2  *il2ptr;
   $DESCRIPTOR (BgDevNameDsc, "");
   
   /*********/
   /* begin */
   /*********/

   if (WATCH_MODULE(WATCH_MOD_NET))
      WatchThis (WATCHALL, WATCH_MOD_NET,
                 "NetCreateService() !UL", ServiceList.EntryCount);

   InstanceMutexLock (INSTANCE_MUTEX_HTTPD);
   NetConnectSuspend = HttpdGblSecPtr->ConnectSuspend;
   NetInstancePassive = HttpdGblSecPtr->InstancePassive;
   InstanceMutexUnLock (INSTANCE_MUTEX_HTTPD);

   IPADDRESS_ZERO (&PrimaryIpAddress);

   /********************************/
   /* process the list of services */
   /********************************/

   /* block other instances from concurrently attempting to create services */
   if (VMSnok (status = InstanceLock (INSTANCE_NODE_SOCKET)))
      ErrorExitVmsStatus (status, "InstanceLock()", FI_LI);

   LIST_ITERATE (svptr, &ServiceList)
   {
      FaoToStdout ("%HTTPD-I-SERVICE, !AZ//!AZ\n",
                   svptr->RequestSchemeNamePtr, svptr->ServerHostPort);

      /* if the service has been given a a specific IP address to bind to */
      if (svptr->BindIpAddressString[0])
      {
         IPADDRESS_COPY (&IpAddress, &svptr->BindIpAddress)
         IPADDRESS_COPY (&svptr->MultiHomeIpAddress, &svptr->BindIpAddress)
      }
      else
      if (IPADDRESS_IS_SAME (&svptr->ServerIpAddress, &PrimaryIpAddress))
      {
         /* zeroing these is the equivalent of setting INADDR_ANY */
         if (IPADDRESS_IS_V4 (&PrimaryIpAddress))
            IPADDRESS_ZERO4 (&IpAddress)
         else
         if (IPADDRESS_IS_V6 (&PrimaryIpAddress))
            IPADDRESS_ZERO6 (&IpAddress)
         else
            ErrorExitVmsStatus (SS$_BUGCHECK, ErrorSanityCheck, FI_LI);
         IPADDRESS_COPY (&svptr->MultiHomeIpAddress, &PrimaryIpAddress)
      }
      else
      if (IPADDRESS_IS_RESET (&svptr->ServerIpAddress))
      {
         IPADDRESS_COPY (&IpAddress, &svptr->ServerIpAddress)
         if (IPADDRESS_IS_RESET (&PrimaryIpAddress))
         {
            IPADDRESS_COPY (&svptr->MultiHomeIpAddress, &IpAddress)
            strcpy (&svptr->ServerIpAddressString,
                    TcpIpAddressToString (&IpAddress, 0));
         }
         else
         {
            if (IPADDRESS_IS_V4 (&PrimaryIpAddress))
               IPADDRESS_ZERO4 (&IpAddress)
            else
            if (IPADDRESS_IS_V6 (&PrimaryIpAddress))
               IPADDRESS_ZERO6 (&IpAddress)
            else
               ErrorExitVmsStatus (SS$_BUGCHECK, ErrorSanityCheck, FI_LI);
            IPADDRESS_COPY (&svptr->MultiHomeIpAddress, &PrimaryIpAddress)
            strcpy (&svptr->ServerIpAddressString,
                    TcpIpAddressToString (&PrimaryIpAddress, 0));
         }
      }
      else
      {
         IPADDRESS_COPY (&IpAddress, &svptr->ServerIpAddress)
         IPADDRESS_COPY (&svptr->MultiHomeIpAddress, &svptr->ServerIpAddress)
      }
      IpPort = svptr->ServerPort;

      /* get the address of the primary (first) service */
      if (IPADDRESS_IS_RESET (&PrimaryIpAddress))
         IPADDRESS_COPY (&PrimaryIpAddress, &IpAddress);

      if (!IPADDRESS_IS_SAME (&svptr->MultiHomeIpAddress, &PrimaryIpAddress))
         NetMultiHome = true;

      /**********************************/
      /* we may need to try this twice */
      /**********************************/

      /*
         Once to try an bind to any non-primary or supplied IP address.
         If this fails then a second attempt against address INADDR_ANY.
      */

      svptr->ServerBindStatus = SS$_NORMAL;

      for (cnt = 0; cnt <= 1; cnt++)
      {
         /* status explicitly set to zero (is checked for at end of loop!) */
         ServerChannel = status = 0;

         /* check if this instance has a channel to the socket */
         SocketDevNamePtr = InstanceSocket (&IpAddress, IpPort, NULL);
         if (SocketDevNamePtr && SocketDevNamePtr[0] == '_')
         {
            /********************/
            /* existing channel */
            /********************/

            /* find it by device name */
            LIST_ITERATE (tsvptr, &ServiceList)
               if (strsame (SocketDevNamePtr+1, tsvptr->BgDevName, -1)) break;
            if (!tsvptr)
            {
               char  String [256];
               FaoToBuffer (String, sizeof(String), NULL, "!&I,!UL !AZ",
                            &IpAddress, IpPort, SocketDevNamePtr+1);
               ErrorExitVmsStatus (SS$_BUGCHECK, String, FI_LI);
            }

            if (svptr->RequestScheme != tsvptr->RequestScheme)
            {
               FaoToStdout (
"-SERVICE-W-INUSE, IP address and port already in use for \
!&?SSL\rHTTP\r service\n",
                  svptr->RequestScheme == SCHEME_HTTP);
               break;
            }

            /* reuse the existing socket's data */
            svptr->ServerBindStatus = 0;
            IPADDRESS_COPY (&svptr->ServerIpAddress, &tsvptr->ServerIpAddress);
            svptr->ServerChannel = tsvptr->ServerChannel;
            strcpy (svptr->BgDevName, tsvptr->BgDevName);
         }

         if (svptr->RequestScheme == SCHEME_HTTPS)
         {
            /* SSL service, initialize */
            if (!cnt)
            {
               /* but only the first time through */
               if (!SesolaInitService (svptr))
               {
                  FaoToStdout ("-SERVICE-W-SSL, service not configured\n");
                  break;
               }
            }
            LIST_ITERATE (tsvptr, &ServiceList)
            {
               if (tsvptr == svptr) continue;
               if (tsvptr->RequestScheme != SCHEME_HTTPS) continue;
               if (!IPADDRESS_IS_SAME (&tsvptr->MultiHomeIpAddress,
                                       &svptr->MultiHomeIpAddress)) continue;
               if (tsvptr->ServerPort != svptr->ServerPort) continue;
               FaoToStdout ("-SERVICE-W-SSL, shares address/port with !AZ\n",
                            tsvptr->ServerHostPort);
            }
         }

         if (svptr->SSLclientEnabled)
         {
            /* if HTTP->SSL capable proxy service, initialize */
            if (!SesolaInitClientService (svptr))
            {
               FaoToStdout ("-SERVICE-W-SSL, client not configured\n");
               break;
            }
         }

         /* if there is an existing channel to a socket */
         if (SocketDevNamePtr && SocketDevNamePtr[0] == '_')
         {
            svptr->SharedSocket = true;
            break;
         }

         /******************************************/
         /* create and/or assign channel to socket */
         /******************************************/

         if (!NetListenBytLmRequired) BytLmBefore = GetJpiBytLm ();

         if (!SocketDevNamePtr)
         {
            /*****************/
            /* create socket */
            /*****************/

            /* create it now then */
            status = sys$assign (&TcpIpDeviceDsc, &ServerChannel, 0, 0);
            if (VMSnok (status))
               ErrorExitVmsStatus (status, "sys$assign()", FI_LI);

            /* prepare to bind the server socket to the IP address and port */
            if (IPADDRESS_IS_V4 (&IpAddress))
            {
               SOCKADDRESS_ZERO4 (&svptr->ServerSocketName)
               sin4ptr = &svptr->ServerSocketName.sa.v4;
               sin4ptr->SIN$W_FAMILY = TCPIP$C_AF_INET;
               sin4ptr->SIN$W_PORT = htons(IpPort);
               IPADDRESS_SET4 (sin4ptr->SIN$L_ADDR, &IpAddress)

               il2ptr = &SocketNameItem;
               il2ptr->buf_len = sizeof(SOCKADDRIN);
               il2ptr->item = 0;
               il2ptr->buf_addr = sin4ptr;

               TcpSocketPtr = &TcpIpSocket4;
            }
            else
            if (IPADDRESS_IS_V6 (&IpAddress))
            {
               SOCKADDRESS_ZERO6 (&svptr->ServerSocketName)
               sin6ptr = &svptr->ServerSocketName.sa.v6;
               sin6ptr->SIN6$B_FAMILY = TCPIP$C_AF_INET6;
               sin6ptr->SIN6$W_PORT = htons(IpPort);
               IPADDRESS_SET6 (&sin6ptr->SIN6$R_ADDR_OVERLAY.SIN6$T_ADDR,
                               &IpAddress)

               il2ptr = &SocketNameItem;
               il2ptr->buf_len = sizeof(SOCKADDRIN6);
               il2ptr->item = 0;
               il2ptr->buf_addr = sin6ptr;

               TcpSocketPtr = &TcpIpSocket6;
            }
            else
               ErrorExitVmsStatus (SS$_BUGCHECK, ErrorSanityCheck, FI_LI);

            /* make the channel a TCP, connection-oriented socket */
            status = sys$qiow (EfnWait, ServerChannel, IO$_SETMODE,
                               &svptr->ServerIOsb, 0, 0, TcpSocketPtr, 0, 0, 0,
                               &TcpIpSocketReuseAddrOption, 0);
            if (VMSok (status)) status = svptr->ServerIOsb.Status;
            if (VMSnok (status))
               ErrorExitVmsStatus (status, NULL, FI_LI);

            if (VMSok (status) && svptr->AdminService)
            {
               /* admin service chooses the first available of a range */
               while (IpPort < 65535)
               {
                  status = sys$qiow (EfnWait, ServerChannel, IO$_SETMODE,
                                     &svptr->ServerIOsb, 0, 0,
                                     0, 0, &SocketNameItem,
                                     svptr->ListenBacklog, 0, 0);
                  if (VMSok (status)) status = svptr->ServerIOsb.Status;
                  if (status != SS$_DUPLNAM) break;
                  IpPort++;
                  if (IPADDRESS_IS_V4 (&IpAddress))
                     sin4ptr->SIN$W_PORT = htons(IpPort);
                  else
                     sin6ptr->SIN6$W_PORT = htons(IpPort);
               }
            }
            else
            if (VMSok (status))
            {
               /* no existing device bound to this address and port */
               status = sys$qiow (EfnWait, ServerChannel, IO$_SETMODE,
                                  &svptr->ServerIOsb, 0, 0,
                                  0, 0, &SocketNameItem,
                                  svptr->ListenBacklog, 0, 0);
               if (VMSok (status)) status = svptr->ServerIOsb.Status;
            }
            if (VMSok (status))
               SocketDevNamePtr = NetGetBgDevice(ServerChannel, NULL, 0);
         }
         else
         {
            /* socket already exists */
            status = SS$_NORMAL;
         }

         if (VMSok (status))
         {
            /*********************************************/
            /* assign channel to existing/created socket */
            /*********************************************/

            strcpy (svptr->BgDevName, SocketDevNamePtr);
            BgDevNameDsc.dsc$a_pointer = svptr->BgDevName;
            BgDevNameDsc.dsc$w_length = strlen(svptr->BgDevName);
            status = sys$assign (&BgDevNameDsc, &svptr->ServerChannel, 0, 0);
            if (VMSnok (status))
            {
               FaoToStdout (
"-SERVICE-W-ASSIGN, error assigning channel to !AZ \
(!&I!&?(INADDR_ANY)\r\r!UL)\n-!&M\n",
                  SocketDevNamePtr,
                  &IpAddress, IPADDRESS_IS_ANY(&IpAddress),
                  IpPort, status);
            }
         }
         else
         {
            FaoToStdout (
"-SERVICE-W-BIND, error binding to !&I!&?(INADDR_ANY)\r\r:!UL\n-\!&M\n",
               &IpAddress, IPADDRESS_IS_ANY(&IpAddress), IpPort, status);
         }

         if (VMSok(status) || IPADDRESS_IS_ANY(&IpAddress)) break;

         /* this time try to bind to 'any' address it can! */
         if (ServerChannel) sys$dassgn (ServerChannel);
         svptr->ServerBindStatus = status;
         IPADDRESS_SET_ANY (&IpAddress)
         FaoToStdout ("-SERVICE-W-RETRY, try again using INADDR_ANY\n");

         /*************/
         /* try again */
         /*************/
      }

      /* status explicitly set to zero, just continue with next service */
      if (!status) continue;

      IPADDRESS_COPY (&svptr->ServerIpAddress, &IpAddress)

      if (VMSok (status) && InstanceNodeConfig > 1)
      {
         /* make the socket shareable (seems to work only if done here) */
         status = sys$qiow (EfnWait, svptr->ServerChannel, IO$_SETMODE,
                            &svptr->ServerIOsb, 0, 0,
                            0, 0, 0, 0, &TcpIpSocketShareOption, 0);
         if (VMSok (status)) status = svptr->ServerIOsb.Status;
         if (VMSnok (status)) ErrorExitVmsStatus (status, NULL, FI_LI);
      }

      if (VMSnok (status))
      {
         /***********/
         /* problem */
         /***********/

         if (ServerChannel) sys$dassgn (ServerChannel);
         svptr->ServerBindStatus = status;
         svptr->ServerChannel = svptr->AdminPort = 0;
         svptr->BgDevName[0] = '\0';
         continue;
      }

      /***********/
      /* success */
      /***********/

      if (WATCH_MODULE(WATCH_MOD_NET))
         WatchThis (WATCHALL, WATCH_MOD_NET,
            "LISTEN !AZ !&I!&?(INADDR_ANY)\r\r,!UL (!UL)",
            svptr->BgDevName, &IpAddress, IPADDRESS_IS_ANY(&IpAddress),
            IpPort, svptr->ListenBacklog);

      if (svptr->AdminService)
      {
         /* set the IP address and port of this instance's admin service */
         InstanceSocketAdmin ((short)IpPort);
         svptr->AdminPort = IpPort;
      }
      else
      if (ServerChannel)
      {
         /* inform the instance socket lock and table of the new device name */
         InstanceSocket (NULL, 0, svptr->BgDevName);
      }
      if (ServerChannel) sys$dassgn (ServerChannel);

      if (!NetListenBytLmRequired)
      {
         BytLmAfter = GetJpiBytLm ();
         NetListenBytLmRequired = BytLmBefore - BytLmAfter;
      }

      if (IPADDRESS_IS_V6 (&IpAddress)) TcpIpv6Configured = true;
   }

   /* finished with service creation */
   if (VMSnok (status = InstanceUnLock (INSTANCE_NODE_SOCKET)))
      ErrorExitVmsStatus (status, "InstanceUnLock()", FI_LI);

   if (WATCH_MODULE(WATCH_MOD_NET))
      WatchThis (WATCHALL, WATCH_MOD_NET,
                 "NetMultiHome: !&B", NetMultiHome);

   return (SS$_NORMAL);
}

/*****************************************************************************/
/*
Queue the first accept for every service channel.
Call as an AST so that it's not interrupted during processing.
*/

void NetAcceptBegin ()

{
   SERVICE_STRUCT  *svptr;

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

   if (WATCH_MODULE(WATCH_MOD_NET))
      WatchThis (WATCHALL, WATCH_MOD_NET, "NetAcceptBegin()");

   LIST_ITERATE (svptr, &ServiceList) NetAccept (svptr);
}

/*****************************************************************************/
/*
Just get the "BGnnn:" device name associated with the channel, returning it in
the storage supplied.  If the storage pointer is NULL internal, static storage
is used ... good for one call per whatever.  If an error occurs the message
string is returned instead.
*/

char* NetGetBgDevice
(
unsigned short Channel,
char *DevName,
int SizeOfDevName
)
{
   static char  StaticDevName [64];
   static unsigned short  Length;
   static VMS_ITEM_LIST3  DevNamItemList [] = 
   {
      { 0, DVI$_DEVNAM, 0, &Length },
      { 0, 0, 0, 0 }
   };

   int  status;
   IO_SB  IOsb;

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

   if (WATCH_MODULE(WATCH_MOD_NET))
      WatchThis (WATCHALL, WATCH_MOD_NET, "NetGetBgDevice()");

   if (!DevName)
   {
      DevName = StaticDevName;
      SizeOfDevName = sizeof(StaticDevName);
   }

   if (!Channel)
   {
      /* multiple successive nulls */
      *(ULONGPTR)DevName = 0;
      return (DevName);
   }

   DevNamItemList[0].buf_addr = DevName;
   DevNamItemList[0].buf_len = SizeOfDevName-1;

   status = sys$getdviw (EfnWait, Channel, 0, &DevNamItemList, &IOsb, 0, 0, 0);
   if (VMSok (status)) status = IOsb.Status;
   if (VMSok (status))
      DevName[Length] = '\0';
   else
      FaoToBuffer (DevName, SizeOfDevName, NULL, "%!&M", status);

   if (WATCH_MODULE(WATCH_MOD_NET))
      WatchThis (WATCHALL, WATCH_MOD_NET, "!AZ", DevName);

   if (DevName[0] == '_') return (DevName+1);
   return (DevName);
}

/*****************************************************************************/
/*
Get the device reference count of the supplied channel.
*/

int NetGetRefCnt (unsigned short Channel)

{
   static int  DviRefCnt;
   static VMS_ITEM_LIST3  DevNamItemList [] = 
   {
      { sizeof(DviRefCnt), DVI$_REFCNT, &DviRefCnt, 0 },
      { 0, 0, 0, 0 }
   };

   int  status;
   IO_SB  IOsb;

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

   if (WATCH_MODULE(WATCH_MOD_NET))
      WatchThis (WATCHALL, WATCH_MOD_NET, "NetGetRefCnt()");

   status = sys$getdviw (EfnWait, Channel, 0, &DevNamItemList, &IOsb, 0, 0, 0);
   if (VMSok (status)) status = IOsb.Status;
   if (VMSok (status)) return (DviRefCnt);
   return (0);
}

/*****************************************************************************/
/*
Zero the per-service accounting counters.
*/ 

NetServiceZeroAccounting ()

{
   SERVICE_STRUCT  *svptr;

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

   if (WATCH_MODULE(WATCH_MOD_NET))
      WatchThis (WATCHALL, WATCH_MOD_NET, "NetServiceZeroAccounting()");

   LIST_ITERATE (svptr, &ServiceList)
   {
      svptr->ConnectCount = svptr->ReadErrorCount = svptr->WriteErrorCount = 0;
      PUT_ZERO_QUAD (svptr->BytesRawRx);
      PUT_ZERO_QUAD (svptr->BytesRawTx);
   }
}

/*****************************************************************************/
/*
Output service statistics (called from AdminReportServerStats()).
*/ 

int NetServiceReportStats (REQUEST_STRUCT *rqptr)

{
   static char  ServicesFao [] =
"<p><table class=\"ctgry\">\n\
<tr><th class=\"ctttl\">!&?\rInstance \rServices</th></tr>\n\
<tr><td>\n\
<table class=\"rghtrght\">\n\
<tr><td></td><td></td>\
<th class=\"sbttl\">IP</th>\
<th class=\"sbttl\">HTTP</th>\
<th class=\"sbttl\">Count</th>\
<th class=\"sbttl\">bytes Rx</th>\
<th class=\"sbttl\">err</th>\
<th class=\"sbttl\">bytes Tx</th>\
<th class=\"sbttl\">err</th>\
<th class=\"sbttl\">Traffic</th></tr>\n";

   static char  OneServiceFao [] =
"<tr><th>!UL.</th>\
<td style=\"text-align:left;\">!AZ//!AZ</td>\
<td>!&?v4\rv6\r</td>\
<td>!&?2+1\r1\r</td>\
<td>!&L</td>\
<td>!&,@SQ</td>\
<td>!&L</td>\
<td>!&,@SQ</td>\
<td>!&L</td>\
<td>!UL%</td></tr>\n";

   static char  TotalFao [] =
"<tr><th colspan=\"4\">total:</th>\
<td>!&L</td>\
<td>!&,@SQ</td>\
<td>!&L</td>\
<td>!&,@SQ</td>\
<td>!&L</td>\
</tr>\n\
<tr><td></td><td style=\"font-size:90%;text-align:left;\">\
<sup>*</sup><i>counts are per-startup only</i></td></tr>\n\
</table>\n\
</td></tr>\n\
</table>\n";

   int  status,
        ServiceListCount,
        ServiceTotalCount;
   unsigned short  Length;
   unsigned long  *vecptr;
   unsigned long  NetReadErrorTotal,
                  NetWriteErrorTotal;
   unsigned long  FaoVector [32],
                  BytesRawRx [QUAD2],
                  BytesRawTx [QUAD2],
                  BytesRxTx [QUAD2],
                  BytesTotal [QUAD2];
   SERVICE_STRUCT  *svptr;

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

   if (WATCH_MODULE(WATCH_MOD_NET))
      WatchThis (WATCHALL, WATCH_MOD_NET, "NetServiceReportStats()");

   NetReadErrorTotal = NetWriteErrorTotal = ServiceTotalCount = 0;
   PUT_ZERO_QUAD (BytesRawRx);
   PUT_ZERO_QUAD (BytesRawTx);
   PUT_ZERO_QUAD (BytesTotal);

   /* accumulate the raw (network) bytes for the services */
   LIST_ITERATE (svptr, &ServiceList)
   {
      ServiceTotalCount += svptr->ConnectCount;
      ADD_QUAD_QUAD (svptr->BytesRawRx, BytesRawRx);
      ADD_QUAD_QUAD (svptr->BytesRawTx, BytesRawTx);
   }
   ADD_QUAD_QUAD (BytesRawRx, BytesTotal);
   ADD_QUAD_QUAD (BytesRawTx, BytesTotal);

   vecptr = FaoVector;
   *vecptr++ = (InstanceNodeConfig <= 1);
   status = FaolToNet (rqptr, ServicesFao, &FaoVector);
   if (VMSnok (status)) ErrorNoticed (rqptr, status, NULL, FI_LI);

   ServiceListCount = 1;
   LIST_ITERATE (svptr, &ServiceList)
   {
      NetReadErrorTotal += svptr->ReadErrorCount;
      NetWriteErrorTotal += svptr->WriteErrorCount;

      vecptr = FaoVector;
      *vecptr++ = ServiceListCount++;
      *vecptr++ = svptr->RequestSchemeNamePtr;
      *vecptr++ = svptr->ServerHostPort;
      *vecptr++ = IPADDRESS_IS_V4(&svptr->ServerIpAddress);
      *vecptr++ = svptr->Http2Enabled;
      *vecptr++ = svptr->ConnectCount;
      *vecptr++ = &svptr->BytesRawRx;
      *vecptr++ = svptr->ReadErrorCount;
      *vecptr++ = &svptr->BytesRawTx;
      *vecptr++ = svptr->WriteErrorCount;

      PLUS_QUAD_QUAD (svptr->BytesRawRx, svptr->BytesRawTx, BytesRxTx);
      *vecptr++ = QuadPercentOf (BytesRxTx, BytesTotal);

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

   vecptr = FaoVector;

   *vecptr++ = ServiceTotalCount;
   *vecptr++ = &BytesRawRx;
   *vecptr++ = NetReadErrorTotal;
   *vecptr++ = &BytesRawTx;
   *vecptr++ = NetWriteErrorTotal;

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

   return (SS$_NORMAL);
}

/*****************************************************************************/
/*
Disconnect network connections. If no criteria supplied then disconnects all
persistent connects leaving requests in-progress.  If ConnectNumber then
disconnect that number, or if -1 then all connections.  Otherwise, disconnect
matching requests in-progress.
*/ 

NetControl
(
int ConnectNumber,
char *RequestUri
)
{
   int  PurgeCount = 0;
   LIST_ENTRY  *leptr;
   REQUEST_STRUCT  *rqeptr;

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

   if (WATCH_MODULE(WATCH_MOD_NET))
      WatchThis (WATCHALL, WATCH_MOD_NET,
                 "NetControl() !SL !&Z", ConnectNumber, RequestUri);

   for (leptr = RequestList.HeadPtr; leptr; leptr = leptr->NextPtr)
   {
      rqeptr = (REQUEST_STRUCT*)leptr;

      /* do NOT disconnect any WATCHing client (might be me :-) */
      if (rqeptr == Watch.RequestPtr) continue;

      if (ConnectNumber == -1)
      {
         /* purge all */
         if (rqeptr->Http2Stream.Http2Ptr)
            Http2RequestResetStream (rqeptr);
         else
            NetIoCloseSocket (rqeptr->NetIoPtr);
         PurgeCount++;
      }
      else
      if (ConnectNumber && ConnectNumber == rqeptr->ConnectNumber)
      {
         /* purge matching */
         if (rqeptr->Http2Stream.Http2Ptr)
            Http2RequestResetStream (rqeptr);
         else
            NetIoCloseSocket (rqeptr->NetIoPtr);
         PurgeCount++;
      }
      else
      if (RequestUri && RequestUri[0] &&
          !StringMatch (NULL, rqeptr->rqHeader.RequestUriPtr, RequestUri))
      {
         /* purge all except */
         if (rqeptr->Http2Stream.Http2Ptr)
            Http2RequestResetStream (rqeptr);
         else
            NetIoCloseSocket (rqeptr->NetIoPtr);
         PurgeCount++;
      }
      else
      if (rqeptr->rqNet.PersistentCount &&
          QUAD_ZERO (rqeptr->BytesRx) &&
          QUAD_ZERO (rqeptr->BytesTx))
      {
         /* purge idle */
         if (rqeptr->Http2Stream.Http2Ptr)
            Http2RequestResetStream (rqeptr);
         else
            NetIoCloseSocket (rqeptr->NetIoPtr);
         PurgeCount++;
      }
   }

   /* and the equivalent for HTTP/2 connections */
   PurgeCount += Http2NetControl (ConnectNumber);

   FaoToStdout ("%HTTPD-I-NET, !UL connections purged\n", PurgeCount);
   if (OpcomMessages)
      FaoToOpcom ("%HTTPD-I-NET, !UL connections purged", PurgeCount);
}

/*****************************************************************************/
/*
Suspend request processing by cancelling all queued net-accept socket I/O.
The boolean just aborts any network I/O (request) in-progress.
*/ 

NetSuspend (BOOL RightNow)

{
   LIST_ENTRY  *leptr;
   REQUEST_STRUCT  *rqeptr;
   SERVICE_STRUCT  *svptr;

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

   if (WATCH_MODULE(WATCH_MOD_NET))
      WatchThis (WATCHALL, WATCH_MOD_NET, "NetSuspend()");

   InstanceMutexLock (INSTANCE_MUTEX_HTTPD);
   HttpdGblSecPtr->ConnectSuspend = true;
   InstanceMutexUnLock (INSTANCE_MUTEX_HTTPD);

   if (NetConnectSuspend) return;
   NetConnectSuspend = true;

   LIST_ITERATE (svptr, &ServiceList) sys$cancel (svptr->ServerChannel);

   /* process the request list looking for persistent connections to break */
   for (leptr = RequestList.HeadPtr; leptr; leptr = leptr->NextPtr)
   {
      rqeptr = (REQUEST_STRUCT*)leptr;
      if (RightNow)
         sys$cancel (rqeptr->NetIoPtr->Channel);
      else
      if (rqeptr->rqNet.PersistentCount && QUAD_ZERO (rqeptr->BytesRx))
         sys$cancel (rqeptr->NetIoPtr->Channel);
   }

   FaoToStdout ("%HTTPD-I-NET, request processing suspended\n");
   if (OpcomMessages)
      FaoToOpcom ("%HTTPD-I-NET, request processing suspended");
}

/*****************************************************************************/
/*
Resume request processing by requeueing net-accept I/O to all sockets.
*/ 

NetResume ()

{
   SERVICE_STRUCT  *svptr;

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

   if (WATCH_MODULE(WATCH_MOD_NET))
      WatchThis (WATCHALL, WATCH_MOD_NET, "NetResume()");

   InstanceMutexLock (INSTANCE_MUTEX_HTTPD);
   HttpdGblSecPtr->ConnectSuspend = false;
   InstanceMutexUnLock (INSTANCE_MUTEX_HTTPD);

   if (!NetConnectSuspend) return;
   NetConnectSuspend = false;

   LIST_ITERATE (svptr, &ServiceList) NetAccept (svptr);

   FaoToStdout ("%HTTPD-I-NET, request processing resumed\n");
   if (OpcomMessages)
      FaoToOpcom ("%HTTPD-I-NET, request processing resumed");
}

/*****************************************************************************/
/*
Cancel all the queued net-accept socket I/O.
Also see description on active/passive modes in INSTANCE.C module.
*/ 

NetPassive ()

{
   LIST_ENTRY  *leptr;
   REQUEST_STRUCT  *rqeptr;
   SERVICE_STRUCT  *svptr;

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

   if (WATCH_MODULE(WATCH_MOD_NET))
      WatchThis (WATCHALL, WATCH_MOD_NET, "NetPassive()");

   if (NetInstancePassive) return;

   InstanceMutexLock (INSTANCE_MUTEX_HTTPD);
   NetInstancePassive = HttpdGblSecPtr->InstancePassive = true;
   InstanceMutexUnLock (INSTANCE_MUTEX_HTTPD);

   if (InstanceNodeSupervisor)
   {
      /* adjust to provide greater capacity in this one instance */
      NetConcurrentMax *= 2;
      NetConcurrentProcessMax *= 2;

      /* of course the supervisor is the only one left processing requests! */
      return;
   }

   if (InstanceNodeCurrent == 1 || NetConnectSuspend) return;

   LIST_ITERATE (svptr, &ServiceList) sys$cancel (svptr->ServerChannel);

   /* process the request list looking for persistent connections to break */
   for (leptr = RequestList.HeadPtr; leptr; leptr = leptr->NextPtr)
   {
      rqeptr = (REQUEST_STRUCT*)leptr;
      if (rqeptr->rqNet.PersistentCount && QUAD_ZERO (rqeptr->BytesRx))
         sys$cancel (rqeptr->NetIoPtr->Channel);
   }

   FaoToStdout ("%HTTPD-I-NET, instance to passive mode\n");
   if (OpcomMessages)
      FaoToOpcom ("%HTTPD-I-NET, instance to passive mode");
}

/*****************************************************************************/
/*
Requeue the net-accept I/O to all sockets.
Also see description on active/passive modes in INSTANCE.C module.
*/ 

NetActive (BOOL NowSupervisor)

{
   BOOL  InstancePassive;
   SERVICE_STRUCT  *svptr;

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

   if (WATCH_MODULE(WATCH_MOD_NET))
      WatchThis (WATCHALL, WATCH_MOD_NET, "NetActive()");

   if (!NetInstancePassive) return;

   if (NowSupervisor)
   {
      /* adjust to provide greater capacity in this one instance */
      NetConcurrentMax *= 2;
      NetConcurrentProcessMax *= 2;

      if (NetConnectSuspend) return;

      LIST_ITERATE (svptr, &ServiceList) NetAccept (svptr);
   }
   else
   {
      InstanceMutexLock (INSTANCE_MUTEX_HTTPD);
      NetInstancePassive = HttpdGblSecPtr->InstancePassive = false;
      InstanceMutexUnLock (INSTANCE_MUTEX_HTTPD);

      if (InstanceNodeSupervisor)
      {
         /* restore the original values */
         NetConcurrentMax /= 2;
         NetConcurrentProcessMax /= 2;

         /* the supervisor is already processing requests! */
         return;
      }

      if (InstanceNodeCurrent == 1 || NetConnectSuspend) return;

      LIST_ITERATE (svptr, &ServiceList) NetAccept (svptr);

      FaoToStdout ("%HTTPD-I-NET, instance to active mode\n");
      if (OpcomMessages)
         FaoToOpcom ("%HTTPD-I-NET, instance to active mode");
   }
}

/*****************************************************************************/
/*
Called every second by HttpdTick().  If there has been a failure in channel
assignment then attempt to queue fresh NetAccept()s for each service.
*/ 

NetAcceptSupervisor ()

{
   static BOOL  PrevNetAcceptFailure;

   SERVICE_STRUCT  *svptr;

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

   if (WATCH_MODULE(WATCH_MOD_NET) && WATCH_MODULE(WATCH_MOD__DETAIL))
      WatchThis (NULL, FI_LI, WATCH_MOD_NET, "NetAcceptSupervisor() !&B !&B",
                 PrevNetAcceptFailure, NetAcceptFailure);

   if (PrevNetAcceptFailure && !NetAcceptFailure)
   LIST_ITERATE (svptr, &ServiceList) NetAccept (svptr);

   PrevNetAcceptFailure = NetAcceptFailure;

   return (PrevNetAcceptFailure);
}

/*****************************************************************************/
/*
Queue an accept() to the listening server socket.
*/ 

NetAccept (SERVICE_STRUCT *svptr)

{
   int  status,
        BytLmBefore;
   CLIENT_STRUCT  *clptr;
   NETIO_STRUCT  *ioptr;
   VMS_ITEM_LIST3  *il3ptr;

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

   if (WATCH_MODULE(WATCH_MOD_NET))
      WatchThis (WATCHALL, WATCH_MOD_NET,
                 "NetAccept() !UL", svptr->AcceptQueued);

   /* do the obvious */
   if (NetConnectSuspend) return;

   /* if instances are passive and not the instance supervisor */
   if (NetInstancePassive && !InstanceNodeSupervisor) return;

   /* server channel has been shutdown, most probably restart/exit underway */
   if (!svptr->ServerChannel) return;

   /* do not queue a second time on a shared socket */
   if (svptr->SharedSocket) return;

   /* only need the one queued at a time */
   if (svptr->AcceptQueued) return;

   if (!NetAcceptBytLmRequired) BytLmBefore = GetJpiBytLm ();

   if ((ioptr = NetIoBegin ()) == NULL)
   {
      NetAcceptFailure = true;
      NetAcceptBytLmRequired = BytLmBefore - GetJpiBytLm();
      return;
   }

   ioptr->ServicePtr = svptr;
   ioptr->ClientPtr = clptr = &ioptr->ClientIp;

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

   status = sys$qio (EfnNoWait, svptr->ServerChannel,
                     IO$_ACCESS | IO$M_ACCEPT,
                     &ioptr->AcceptIOsb, &NetAcceptAst, ioptr,
                     0, 0, &clptr->SocketNameItem,
                     &ioptr->Channel, &TcpIpFullDuplexCloseOption, 0);
   if (VMSnok (status)) ErrorExitVmsStatus (status, NULL, FI_LI);

   svptr->AcceptQueued++;

   if (WATCH_CATEGORY(WATCH_NETWORK) && Watch.Category != WATCH_ONE_SHOT_CAT)
      WatchThis (WATCHALL, WATCH_NETWORK,
                 "ACCEPT !AZ !&I!&?(INADDR_ANY)\r\r,!AZ",
                 NetGetBgDevice(svptr->ServerChannel, NULL, 0),
                 &svptr->ServerIpAddress,
                 IPADDRESS_IS_ANY(&svptr->ServerIpAddress),
                 svptr->ServerPortString);
}

/*****************************************************************************/
/*
A connection has been accept()ed on the specified server socket.
*/ 

NetAcceptAst (NETIO_STRUCT *ioptr)

{
   int  status;
   CLIENT_STRUCT  *clptr;
   SERVICE_STRUCT  *svptr;

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

   if (Watch.Category || Watch.Module)
      if (Watch.Category != WATCH_ONE_SHOT_CAT)
         if (!Watch.FilterSet)
            ioptr->WatchItem = WatchSetWatch (NULL, WATCH_NEW_ITEM);

   if (WATCH_MODULE(WATCH_MOD_NET))
      WatchThis (WATCHALL, WATCH_MOD_NET,
                 "NetAcceptAst() !&F !UL !&S !&X",
                 &NetAcceptAst, ioptr->ServicePtr->AcceptQueued,
                 ioptr->AcceptIOsb.Status, ioptr->AcceptIOsb.Unused);

   clptr = ioptr->ClientPtr;
   svptr = ioptr->ServicePtr;

   if (svptr->AcceptQueued) svptr->AcceptQueued--;

   if (VMSnok (status = ioptr->AcceptIOsb.Status)) 
   {
      if (ControlExitRequested)
      {
         /* server exiting or restarting, no new accepts, just return */
         return;
      }

      /* if connect dropped, forget it, ready for next connection */
      if (status == SS$_CANCEL ||
          status == SS$_ABORT ||
          status == SS$_CONNECFAIL ||
          status == SS$_LINKABORT ||
          status == SS$_REJECT ||
          status == SS$_TIMEOUT ||
          status == SS$_INSFMEM)
      {
         /* if server channel zero most probably restart/exit underway */
         if (!svptr->ServerChannel) return;

         if (status != SS$_CANCEL &&
             status != SS$_CONNECFAIL)
            ErrorNoticed (NULL, status, NULL, FI_LI);

         NetIoEnd (ioptr);

         /* queue up the next request acceptance */
         NetAccept (svptr);
         return;
      }

      /* most often network/system shutting down ... SS$_SHUT */
      ErrorExitVmsStatus (status, "accept()", FI_LI);
   }

#if NET_TEST

   NetTestRequest (ioptr->Channel);
   NetAccept (svptr);
   return;

#endif /* NET_TEST */

   if (Config.cfScript.GatewayBg)
   {
      if (svptr->RequestScheme == SCHEME_HTTP)
      {
         /* make the socket shareable */
         status = sys$qiow (EfnWait, ioptr->Channel, IO$_SETMODE,
                            &ioptr->AcceptIOsb, 0, 0,
                            0, 0, 0, 0, &TcpIpSocketShareOption, 0);
         if (VMSok (status)) status = ioptr->AcceptIOsb.Status;
         if (VMSnok (status)) ErrorExitVmsStatus (status, NULL, FI_LI);
      }
   }

   if (svptr->RequestScheme == SCHEME_HTTPS)
      InstanceGblSecIncrLong (&AccountingPtr->ConnectSSLCount);

   if (IPADDRESS_IS_V4 (&svptr->ServerIpAddress))
   {
      InstanceGblSecIncrLong (&AccountingPtr->ConnectIpv4Count);
      clptr->IpPort = ntohs(clptr->SocketName.sa.v4.SIN$W_PORT);
      IPADDRESS_GET4 (&clptr->IpAddress,
                      clptr->SocketName.sa.v4.SIN$L_ADDR);
      strcpy (clptr->IpAddressString,
              TcpIpAddressToString (&clptr->IpAddress, 0));
   }
   else
   if (IPADDRESS_IS_V6 (&svptr->ServerIpAddress))
   {
      InstanceGblSecIncrLong (&AccountingPtr->ConnectIpv6Count);
      clptr->IpPort = ntohs(clptr->SocketName.sa.v6.SIN6$W_PORT);
      IPADDRESS_GET6 (&clptr->IpAddress,
                      clptr->SocketName.sa.v6.SIN6$R_ADDR_OVERLAY.SIN6$T_ADDR)
      strcpy (clptr->IpAddressString,
              TcpIpAddressToString (&clptr->IpAddress, 0));
   }
   else
      ErrorExitVmsStatus (SS$_BUGCHECK, ErrorSanityCheck, FI_LI);

   /* meanwhile queue another accept */
   NetAccept (svptr);

   if (Config.cfMisc.DnsLookupClient)
   {
      if (WATCH_CATEGORY(WATCH_NETWORK))
         WatchThis (WATCHALL, WATCH_NETWORK,
            "RESOLVE !&I", &clptr->IpAddress);

      /* asynchronous DNS lookup */
      TcpIpAddressToName (&clptr->Lookup, &clptr->IpAddress,
                          ConfigDnsLookupRetryCount,
                          &NetAcceptProcess, ioptr);
   }
   else
   {
      /* not using client DNS lookup, carry on synchronously */
      NetAcceptProcess (ioptr);
   }
}

/*****************************************************************************/
/*
This function can be called either as an AST by TcpIpAdddressToName() if DNS
host name lookup is enabled, or directly from NetAccept() if it's disabled. 
Either way just continue to process the connection accept.
*/ 

NetAcceptProcess (NETIO_STRUCT *ioptr)

{
   int  idx, status,
        SocketNameLength,
        ServerSocketNameLength;
   char  ServerIpAddressString [32];
   CLIENT_STRUCT  *clptr;
   REQUEST_STRUCT  *rqptr;
   SERVICE_STRUCT  *asvptr,
                   *svptr,
                   *tsvptr;
   IO_SB  IOsb;
   SOCKADDRESS  SocketName;
   VMS_ITEM_LIST3  SocketNameItem;
   VMS_ITEM_LIST3  *il3ptr;

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

   if (WATCH_MODULE(WATCH_MOD_NET))
      WatchThis (WATCHALL, WATCH_MOD_NET,
                 "NetAcceptProcess() !&F !&S !UL",
                 &NetAcceptProcess, ioptr->AcceptIOsb.Status,
                 ioptr->ClientPtr->Lookup.HostNameLength);

   clptr = ioptr->ClientPtr;
   svptr = ioptr->ServicePtr;

   if (VMSnok (clptr->Lookup.LookupIOsb.Status))
   {
      /* lookup not done or failed, substitute the IP address for the name */
      strcpy (clptr->Lookup.HostName, clptr->IpAddressString);
      clptr->Lookup.HostNameLength = strlen(clptr->Lookup.HostName);
   }

   if (WATCH_MODULE(WATCH_MOD_NET))
      WatchDataFormatted ("!&Z !&Z\n", clptr->IpAddressString,
                                       clptr->Lookup.HostName);

   if (NetMultiHome)
   {
      /***********************/
      /* multihomed services */
      /***********************/

      /* get actual IP address and port specified by the client */
      il3ptr = &SocketNameItem;
      il3ptr->item = 0;
      if (IPADDRESS_IS_V4 (&svptr->ServerIpAddress))
      {
         il3ptr->buf_len = sizeof(SOCKADDRIN);
         il3ptr->buf_addr = &SocketName.sa.v4;
      }
      else
      if (IPADDRESS_IS_V6 (&svptr->ServerIpAddress))
      {
         il3ptr->buf_len = sizeof(SOCKADDRIN6);
         il3ptr->buf_addr = &SocketName.sa.v6;
      }
      else
         ErrorExitVmsStatus (SS$_BUGCHECK, ErrorSanityCheck, FI_LI);
      il3ptr->ret_len = &SocketNameLength;

      status = sys$qiow (EfnWait, ioptr->Channel, IO$_SENSEMODE,
                         &IOsb, 0, 0, 0, 0, &SocketNameItem, 0, 0, 0);

      if (WATCH_MODULE(WATCH_MOD_NET))
         WatchThis (WATCHALL, WATCH_MOD_NET,
                    "$QIOW() !&S !&S", status, IOsb.Status);

      if (VMSok (status)) status = IOsb.Status;
      if (VMSnok (status))
      {
         /* hmmm, forget it, ready for next connection */
         sys$dassgn (ioptr->Channel);
         VmFree (ioptr, FI_LI);
         return;
      }

      if (IPADDRESS_IS_V4 (&svptr->ServerIpAddress))
         IPADDRESS_GET4 (&clptr->MultiHomeIpAddress,
                         SocketName.sa.v4.SIN$L_ADDR)
      else
      if (IPADDRESS_IS_V6 (&svptr->ServerIpAddress))
         IPADDRESS_GET6 (&clptr->MultiHomeIpAddress,
                         &SocketName.sa.v6.SIN6$R_ADDR_OVERLAY.SIN6$T_ADDR)
      else
         ErrorExitVmsStatus (SS$_BUGCHECK, ErrorSanityCheck, FI_LI);

      if (WATCH_MODULE(WATCH_MOD_NET))
         WatchThis (WATCHALL, WATCH_MOD_NET, "!&I,!UL !&I,!UL",
                    &clptr->MultiHomeIpAddress, svptr->ServerPort,
                    &svptr->MultiHomeIpAddress, svptr->ServerPort);

      /* first check against the service it arrived at (port is implicit) */
      asvptr = svptr;
      if (IPADDRESS_IS_SAME(&clptr->MultiHomeIpAddress,
                            &asvptr->MultiHomeIpAddress))
         tsvptr = asvptr;
      else
      /* then if necessary scan through the list of services */
      LIST_ITERATE (tsvptr, &ServiceList)
      {
         if (WATCH_CATEGORY(WATCH_NETWORK))
            WatchThis (WATCHALL, WATCH_NETWORK,
                       "MULTIHOME !&I,!UL !&I,!UL",
                       &clptr->MultiHomeIpAddress, asvptr->ServerPort,
                       &tsvptr->MultiHomeIpAddress, tsvptr->ServerPort);

         if (IPADDRESS_IS_SAME(&clptr->MultiHomeIpAddress,
                               &tsvptr->MultiHomeIpAddress) &&
             (asvptr->ServerPort == tsvptr->ServerPort ||
              asvptr->ServerPort == tsvptr->AdminPort)) break;
      }
 
      if (tsvptr)
      {
         /* matched, change to the resolved (multihomed) service */
         ioptr->ServicePtr = svptr = tsvptr;
      }
      else
      {
         /* the presence of this value indicates a multi-home mismatch */
         strcpy (clptr->MultiHomeIpAddressString,
                 TcpIpAddressToString(&clptr->MultiHomeIpAddress,0));
      }
   }
   else
      tsvptr = NULL;

   if (WATCH_MODULE(WATCH_MOD_NET))
      WatchThis (WATCHALL, WATCH_MOD_NET, "!AZ !AZ:!UL",
                 svptr->ServerIpAddressString,
                 svptr->ServerHostName, svptr->ServerPort);

   if (NetCurrentConnected[HTTP12] > NetConcurrentMax)
   {
      /************/
      /* too busy */
      /************/

      if (WATCHING (ioptr, WATCH_CONNECT))
         WatchThis (WATCHALL, WATCH_CONNECT,
                    "ACCEPTED !UL too-busy !AZ,!UL on !AZ//!AZ,!AZ !AZ",
                    NetConcurrentMax,
                    clptr->Lookup.HostName, clptr->IpPort, 
                    svptr->RequestSchemeNamePtr,
                    svptr->ServerIpAddressString,
                    svptr->ServerPortString,
                    NetGetBgDevice(ioptr->Channel, NULL, 0));

      svptr->ConnectCount++;
      InstanceMutexLock (INSTANCE_MUTEX_HTTPD);
      AccountingPtr->ConnectTooBusyCount++;
      AccountingPtr->ResponseStatusCodeGroup[5]++;
      AccountingPtr->ResponseStatusCodeCount[RequestHttpStatusIndex(503)]++;
      InstanceMutexUnLock (INSTANCE_MUTEX_HTTPD);
      /* just drop (as yet unnegotiated) TLS/SSL connection if too busy */
      if (svptr->RequestScheme == SCHEME_HTTPS)
         NetIoEnd (ioptr);
      else
         NetDirectResponse (ioptr, MSG_GENERAL_TOO_BUSY);
      return;
   }

   if (!ConfigAcceptClientHostName (clptr->IpAddressString,
                                    clptr->Lookup.HostName))
   {
      /************/
      /* rejected */
      /************/

      if (WATCHING (ioptr, WATCH_CONNECT))
         WatchThis (WATCHALL, WATCH_CONNECT,
                    "ACCEPTED reject !AZ,!UL on !AZ//!AZ,!AZ !AZ",
                     clptr->Lookup.HostName, clptr->IpPort, 
                     svptr->RequestSchemeNamePtr,
                     svptr->ServerIpAddressString,
                     svptr->ServerPortString,
                     NetGetBgDevice(ioptr->Channel, NULL, 0));

      svptr->ConnectCount++;
      InstanceMutexLock (INSTANCE_MUTEX_HTTPD);
      AccountingPtr->ConnectRejectedCount++;
      AccountingPtr->ResponseStatusCodeGroup[4]++;
      AccountingPtr->ResponseStatusCodeCount[RequestHttpStatusIndex(403)]++;
      InstanceMutexUnLock (INSTANCE_MUTEX_HTTPD);
      /* just drop (as yet unnegotiated) TLS/SSL connection if rejected */
      if (svptr->RequestScheme == SCHEME_HTTPS)
         NetIoEnd (ioptr);
      else
         NetDirectResponse (ioptr, MSG_GENERAL_ACCESS_DENIED);
      return;
   }

   /************/
   /* accepted */
   /************/

   if (ioptr->WatchItem &&
       WATCH_CATEGORY(WATCH_CONNECT))
   {
      if (NetMultiHome)
         WatchThis (WATCHITM(ioptr), WATCH_CONNECT,
"MULTIHOME !&?match\rno match\r for !&I,!UL arrived at !&I,!UL",
                    tsvptr,
                    &clptr->MultiHomeIpAddress, svptr->ServerPort,
                    &asvptr->ServerIpAddress, asvptr->ServerPort);
      WatchThis (WATCHITM(ioptr), WATCH_CONNECT,
                 "ACCEPTED !AZ,!UL on !AZ//!AZ,!UL (!&I) !AZ",
                 clptr->Lookup.HostName, clptr->IpPort, 
                 svptr->RequestSchemeNamePtr,
                 svptr->ServerHostName,
                 svptr->ServerPort,
                 &svptr->ServerIpAddress,
                 NetGetBgDevice(ioptr->Channel, NULL, 0));
   }

   RequestAccept (ioptr);
}

/*****************************************************************************/
/*
Change the socket BG devices implied carriage-control bit; 0 forces the CCL bit
off, 1 forces it on(, and -1 one toggles it [perhaps one day]).
*/ 

int NetClientSocketCcl
(
NETIO_STRUCT *ioptr,
int CclOption
)
{
   int  status;
   IO_SB  IOsb;

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

   if (WATCHMOD (ioptr, WATCH_MOD_NET))
      WatchThis (WATCHITM(ioptr), WATCH_MOD_NET,
                 "NetClientSocketCcl() !SL !&A !UL",
                 CclOption, ioptr->Http2StreamPtr, ioptr->Channel);

   if (ioptr->Http2StreamPtr)
      status = SS$_BADPARAM;
   else
   if (ioptr->ServicePtr->RequestScheme == SCHEME_HTTP)
   {
      if (ioptr->Channel)
      {
         if (CclOption == 1)
            status = sys$qiow (EfnWait, ioptr->Channel, IO$_SETMODE,
                               &IOsb, 0, 0,
                               0, 0, 0, 0, &TcpIpSocketCclOptionOn, 0);
         else
         if (CclOption == 0)
            status = sys$qiow (EfnWait, ioptr->Channel, IO$_SETMODE,
                               &IOsb, 0, 0,
                               0, 0, 0, 0, &TcpIpSocketCclOptionOff, 0);
         else
            status = SS$_BADPARAM;
         if (VMSok (status)) status = IOsb.Status;

         if (WATCHING (ioptr, WATCH_NETWORK))
            WatchThis (WATCHITM(ioptr), WATCH_NETWORK,
                       "SOCKET CCL !SL !&S", CclOption, status);
      }
      else
         status = SS$_BADPARAM;
   }
   else
      status = SS$_BADPARAM;

   return (status);
}

/*****************************************************************************/
/*
'HostNamePort' can contain a "host.name:port" or just a "host.name" if the port
is supplied via 'PortNumber'.  Using the supplied or parsed host name get the
lookup host name into 'HostNamePtr', the decimal-dot-notation IP address string
into 'IpAddressStringPtr' and the IP address into 'IpAddressPtr', the IP port
number (particularly if parsed from 'HostNamePort' in to 'IpPortPtr'.  Any of
the '...Ptr' parameters can be NULL and won't have the respective information
returned.
*/ 

int NetHostNameLookup
(
char *HostNamePort,
int PortNumber,
char *HostNamePtr,
char *HostPortPtr,
char *IpAddressStringPtr,
IPADDRESS *IpAddressPtr,
int *IpPortPtr
)
{
   static $DESCRIPTOR (HostPortFaoDsc, "!AZ:!UL\0");
   static $DESCRIPTOR (LogNameDsc, "WASD_NET_LOOKUP_RETRY");
   static $DESCRIPTOR (LnmFileDevDsc, "LNM$FILE_DEV");
   static $DESCRIPTOR (StringDsc, "");

   static int  RetryAttempts = -1;
   static char  LogValue [32];
   static unsigned short  sLength;
   static VMS_ITEM_LIST3  LnmItems [] =
   {
      { sizeof(LogValue)-1, LNM$_STRING, LogValue, &sLength },
      { 0,0,0,0 }
   };

   BOOL  AddressOnly,
         FullyQualified;
   int  idx,
        status;
   char  *cptr, *sptr, *zptr;
   char  HostNameScratch [128];
   IPADDRESS  IpAddress;
   TCPIP_HOST_LOOKUP  HostLookup;

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

   if (WATCH_MODULE(WATCH_MOD_NET))
      WatchThis (WATCHALL, WATCH_MOD_NET,
                 "NetHostNameLookup() !AZ", HostNamePort);

   AddressOnly = true;
   FullyQualified = false;

   zptr = (sptr = HostNameScratch) + sizeof(HostNameScratch)-1;
   if (HostNamePort[0] == '[')
   {
      /* IPv6 address */
      for (cptr = HostNamePort;
           *cptr && *cptr != ']' && sptr < zptr;
           *sptr++ = *cptr++);
      if (*cptr == ']' && sptr < zptr) *sptr++ = *cptr++;
      *sptr = '\0';
   }
   else
   {
      for (cptr = HostNamePort;
           *cptr && *cptr != ':' && sptr < zptr;
           *sptr++ = TOLO(*cptr++))
         if (isalpha(*cptr)) AddressOnly = false;
      *sptr = '\0';
   }
   if (*cptr == ':' && !PortNumber) PortNumber = atoi(cptr+1);

   /*************************/
   /* UCX resolve host name */
   /*************************/

   IPADDRESS_ZERO (&IpAddress);

   if (AddressOnly)
      status = SS$_NORMAL;
   else
   {
      if (RetryAttempts < 0)
      {
         status = sys$trnlnm (0, &LnmFileDevDsc, &LogNameDsc, 0, &LnmItems);
         if (VMSok (status))
            RetryAttempts = atoi(LogValue);
         else
            RetryAttempts = NET_LOOKUP_RETRY;
      }

      memset (&HostLookup, 0, sizeof(HostLookup));
      status = TcpIpNameToAddress (&HostLookup, HostNameScratch, 
                                   RetryAttempts, NULL, 0);
      if (VMSok (status))
      {
         /* use the resolved name */
         zptr = (sptr = HostNameScratch) + 127;
         for (cptr = HostLookup.HostName;
              *cptr && sptr < zptr;
              *sptr++ = TOLO(*cptr++));
         *sptr = '\0';
         IPADDRESS_COPY (&IpAddress, &HostLookup.IpAddress)
      }
   }

   /*****************************/
   /* return values as required */
   /*****************************/

   if (HostNamePtr)
   {
      strcpy (HostNamePtr, HostNameScratch);
      if (WATCH_MODULE(WATCH_MOD_NET))
         WatchThis (WATCHALL, WATCH_MOD_NET, "!AZ", HostNamePtr);
   }

   if (HostPortPtr)
   {
      StringDsc.dsc$a_pointer = HostPortPtr;
      StringDsc.dsc$w_length = 128+16;
      sys$fao (&HostPortFaoDsc, 0, &StringDsc, HostNameScratch, PortNumber);
      if (WATCH_MODULE(WATCH_MOD_NET))
         WatchThis (WATCHALL, WATCH_MOD_NET, "!AZ", HostPortPtr);
   }

   if (IpAddressStringPtr)
   {
      /* convert the binary address into a string */
      strcpy (IpAddressStringPtr, TcpIpAddressToString (&IpAddress, 0));
      if (WATCH_MODULE(WATCH_MOD_NET))
         WatchThis (WATCHALL, WATCH_MOD_NET, "!AZ", IpAddressStringPtr);
   }

   if (IpAddressPtr) IPADDRESS_COPY (IpAddressPtr, &IpAddress)
   if (IpPortPtr) *IpPortPtr = PortNumber;

   return (status);
}

/****************************************************************************/
/*
Just close the socket, bang!
*/

void NetCloseSocket (REQUEST_STRUCT *rqptr)

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

   if (WATCHMOD (rqptr, WATCH_MOD_NET))
      WatchThis (WATCHITM(rqptr), WATCH_MOD_NET, "NetCloseSocket()");

   NetIoCloseSocket (rqptr->NetIoPtr);
}

/****************************************************************************/
/*
Deassign the request's underlying network connection regardless of whether it
is a directly connected socket or HTTP/2 connected socket.
*/

void NetAbortSocket (REQUEST_STRUCT *rqptr)

{
   int  status;

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

   if (WATCHMOD (rqptr, WATCH_MOD_NET))
      WatchThis (WATCHITM(rqptr), WATCH_MOD_NET, "NetAbortSocket()");

   if (rqptr->Http2Stream.Http2Ptr)
      NetIoCloseSocket (rqptr->Http2Stream.Http2Ptr->NetIoPtr);
   else
      NetIoCloseSocket (rqptr->NetIoPtr);
}

/****************************************************************************/
/*
Stop the server from receiving incoming requests.
*/

NetShutdownServerSocket ()

{
   SERVICE_STRUCT  *svptr;

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

   if (WATCH_MODULE(WATCH_MOD_NET))
      WatchThis (WATCHALL, WATCH_MOD_NET, "NetShutdownServerSocket()");

   LIST_ITERATE (svptr, &ServiceList)
   {
      sys$dassgn (svptr->ServerChannel);
      svptr->ServerChannel = 0;
   }
}

/*****************************************************************************/
/*
Called from NetWrite() is a response header needs to be sent before any data.
Response header has now been sent, send the data using the buffered
information about it.
*/ 

NetResponseHeaderAst (REQUEST_STRUCT *rqptr)

{
   int  status;

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

   if (WATCHMOD (rqptr, WATCH_MOD_NET))
      WatchThis (WATCHITM(rqptr), WATCH_MOD_NET,
                 "NetResponseHeaderAst() !&F !&A !&X !UL",
                 &NetResponseHeaderAst,
                 rqptr->rqResponse.HeaderAstFunction,
                 rqptr->rqResponse.HeaderDataPtr,
                 rqptr->rqResponse.HeaderDataLength);

   if (rqptr->rqResponse.HeaderDataPtr &&
       rqptr->rqResponse.HeaderDataLength)
      NetWrite (rqptr,
                rqptr->rqResponse.HeaderAstFunction,
                rqptr->rqResponse.HeaderDataPtr,
                rqptr->rqResponse.HeaderDataLength);
   else
      /* without a real network write just fudge the status */
      NetIoWriteStatus (rqptr->NetIoPtr, rqptr->rqResponse.HeaderAstFunction,
                        rqptr, SS$_NORMAL, 0);
}

/*****************************************************************************/
/*
Write 'DataLength' bytes located at 'DataPtr' to the client either using either
the "raw" network or via the Secure Sockets Layer.

If 'AstFunction' zero then use sys$qiow(), waiting for completion. If
an AST completion address is supplied then use sys$qio().  If empty data
buffer is supplied (zero length) then declare an AST to service any AST
routine supplied.  If none then just return.

For responses generated by the server the HTTP header is a separate structure
which can be sent separately. If the HTTP method is "HEAD" only allow bytes in
the header to be sent, absorb any other, explicitly calling the AST completion
routine as necessary (this, in particular, is for scripts that don't recognise
this method).

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 NetWrite
(
REQUEST_STRUCT *rqptr,
REQUEST_AST AstFunction,
char *DataPtr,
int DataLength
)
{
   int  cnt, length, nlcnt, status;
   BOOL	 WritingProxyResponseHeader;
   char  *cptr, *sptr;
   DICT_ENTRY_STRUCT  *denptr;
   NETIO_STRUCT  *ioptr;

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

   if (WATCHMOD (rqptr, WATCH_MOD_NET))
      WatchThis (WATCHITM(rqptr), WATCH_MOD_NET,
"NetWrite() ast:!&A data:!&X len:!UL state:!UL gen:!&B sent:!&B cnt:!UL",
                 AstFunction, DataPtr, DataLength,
                 rqptr->RequestState,
                 rqptr->rqResponse.HeaderGenerated,
                 rqptr->rqResponse.HeaderSent,
                 rqptr->rqResponse.HeaderNewlineCount);

   ioptr = rqptr->NetIoPtr;
   ioptr->WatchItem = rqptr->WatchItem;

   if (rqptr->RequestState >= REQUEST_STATE_ABORT)
   {
      NetIoWriteStatus (ioptr, AstFunction, rqptr, SS$_ABORT, 0);
      return (SS$_ABORT);
   }

   /* intermediate update to data transfer stats */
   PUT_QUAD_QUAD (ioptr->BlocksRawTx, rqptr->NetIoPtr->BlocksRawTx);
   PUT_QUAD_QUAD (ioptr->BytesRawTx, rqptr->NetIoPtr->BytesRawTx);

   status = SS$_NORMAL;

   /* initiate cache load if .. */
   if (rqptr->rqPathSet.CacheNet &&   /* the path indicates it */
       !rqptr->rqCache.LoadCheck &&   /* not been checked already */
       !rqptr->rqCache.EntryPtr)      /* output is not from the cache! */
   {
      /* request output to be cached */
      rqptr->rqCache.LoadFromNet =
         CacheLoadBegin (rqptr, rqptr->rqResponse.ContentLength,
                                rqptr->rqResponse.ContentTypePtr);
   } 

   if (rqptr->rqResponse.HeaderGenerated &&
       !rqptr->rqResponse.HeaderSent)
   {
      /*******************************************/
      /* nothing of response sent to client yet! */
      /*******************************************/

      rqptr->rqResponse.HeaderSent = true;

      if (rqptr->rqPathSet.ResponseHeaderNone &&
          rqptr->rqResponse.HttpStatus / 100 == 2)
      {
         /*********************/
         /* header suppressed */
         /*********************/

         if (WATCHING (rqptr, WATCH_RESPONSE_HEADER))
         {
            if (denptr = ResponseDictHeader (rqptr))
               length = DICT_GET_VALUE_LEN(denptr);
            else
               length = 0;
            WatchThis (WATCHITM(rqptr), WATCH_RESPONSE_HEADER,
                       "HEADER !UL bytes (NONE)", length);
         }
      }
      else
      if (rqptr->rqResponse.HttpVersion != HTTP_VERSION_0_9)
      {
         /************************/
         /* send response header */
         /************************/

         if (rqptr != Watch.RequestPtr &&
             WATCHPNT(rqptr) && WATCH_CATEGORY(WATCH_RESPONSE_HEADER))
         {
            WatchThis (WATCHITM(rqptr), WATCH_RESPONSE_HEADER, "DATA");
            DictWatchEntry (NULL);
            DictWatch (rqptr->rqDictPtr, DICT_TYPE_INTERNAL, "response_status");
            DictWatch (rqptr->rqDictPtr, DICT_TYPE_RESPONSE, "*");
         }

         if (AstFunction)
         {
            rqptr->rqResponse.HeaderAstFunction = AstFunction;
            rqptr->rqResponse.HeaderDataPtr = DataPtr;
            rqptr->rqResponse.HeaderDataLength = DataLength;
            AstFunction = &NetResponseHeaderAst;
         }

         /* only need header via the network if it's not already generated */
         if (rqptr->rqCache.LoadFromNet &&
             !rqptr->rqResponse.ContentTypePtr)
         {
            if (denptr = ResponseDictHeader (rqptr))
            {
               cptr = DICT_GET_VALUE(denptr);
               length = DICT_GET_VALUE_LEN(denptr);
               CacheLoadData (rqptr, cptr, length);
            }
         }
         else
            denptr = NULL;

         if (HTTP2_REQUEST(rqptr))
         {
            /* function will return SS$_ABORT if header not generated */
            if (VMSnok (status = Http2ResponseDictHeader (rqptr, AstFunction)))
               NetIoWriteStatus (ioptr, AstFunction, rqptr, status, 0);
         }
         else
         {
            if (denptr == NULL) denptr = ResponseDictHeader (rqptr);
            /* it is possible that a header has not been generated */
            if (denptr)
            {
               cptr = DICT_GET_VALUE(denptr);
               length = DICT_GET_VALUE_LEN(denptr);
               if (rqptr->rqPathSet.ResponseHeaderBegin &&
                   rqptr->rqResponse.HttpStatus / 100 == 2) length -= 2;

               if (rqptr != Watch.RequestPtr &&
                   WATCHPNT(rqptr) && WATCH_CATEGORY(WATCH_RESPONSE_HEADER))
               {
                  WatchThis (WATCHITM(rqptr), WATCH_RESPONSE_HEADER,
                             "HEADER !UL bytes", length);
                  WatchData (cptr, length);
               }

               status = NetIoWrite (ioptr, AstFunction, rqptr, cptr, length);
               if (VMSok (status)) ADD_LONG_QUAD (length, rqptr->BytesTx);
            }
            else
               NetIoWriteStatus (ioptr, AstFunction, rqptr,
                                 status = SS$_ABORT, 0);
         }

         if (AstFunction) return (status);
         /* a blocking write will continue on to write the data */
      }
   }

   if (DataLength)
   {
      /********/
      /* data */
      /********/

      ADD_LONG_QUAD (DataLength, rqptr->BytesTx)

      if ((rqptr->rqHeader.Method == HTTP_METHOD_HEAD ||
           rqptr->rqResponse.HttpStatus == 304) &&
          !rqptr->ProxyTaskPtr)
      {
         /*****************************/
         /* send only response header */
         /*****************************/

         if (!rqptr->rqResponse.HeaderSent &&
             rqptr->rqResponse.HeaderNewlineCount <= 1)
         {
            /**********************/
            /* header from script */
            /**********************/

            nlcnt = rqptr->rqResponse.HeaderNewlineCount;
            cptr = DataPtr;
            cnt = DataLength;
            while (cnt--)
            {
               if (*cptr == '\n' && ++nlcnt == 2)
               {
                  /* two successive end-of-lines, therefore end of header */
                  cptr++;
                  break;
               }
               else
               if (*cptr != '\r' && *cptr != '\n')
                  nlcnt = 0;
               cptr++;
            }

            if ((rqptr->rqResponse.HeaderNewlineCount = nlcnt) == 2)
            {
               /* finally found those two consecutive newlines! */
               rqptr->rqResponse.HeaderSent = true;
               if (DataLength = cptr - DataPtr)
               {
                  /* adjust data length to include only the HTTP header */
                  DataLength = cptr - DataPtr;
               }
               else
               {
                  /* no data in HTTP header left at all */
                  NetIoWriteStatus (ioptr, AstFunction, rqptr, SS$_NORMAL, 0);
                  return (SS$_NORMAL);
               }
            }
         }
         else
         {
            /* HTTP header has been completely sent, absorb anything else */
            NetIoWriteStatus (ioptr, AstFunction, rqptr, SS$_NORMAL, 0);
            return (SS$_NORMAL);
         }

         if (WATCHMOD (rqptr, WATCH_MOD_NET))
            WatchThis (WATCHITM(rqptr), WATCH_MOD_NET,
                       "HEAD !&X !UL", DataPtr, DataLength);
      }

      if (rqptr->rqResponse.HttpVersion == HTTP_VERSION_0_9 &&
          !rqptr->rqResponse.HeaderSent &&
          rqptr->rqResponse.HeaderNewlineCount <= 1 &&
          !rqptr->ProxyTaskPtr)
      {
         /*********************************/
         /* absorb any header from script */
         /*********************************/

         int  cnt, nlcnt;
         char  *cptr;

         nlcnt = rqptr->rqResponse.HeaderNewlineCount;
         cptr = DataPtr;
         cnt = DataLength;
         while (cnt--)
         {
            if (*cptr == '\n' && ++nlcnt == 2)
            {
               /* two successive end-of-lines, therefore end of header */
               cptr++;
               break;
            }
            else
            if (*cptr != '\r' && *cptr != '\n')
               nlcnt = 0;
            cptr++;
         }

         if ((rqptr->rqResponse.HeaderNewlineCount = nlcnt) == 2)
         {
            /* adjust data pointer and length to exclude header */
            rqptr->rqResponse.HeaderSent = true;
            DataLength = cptr - DataPtr;
            DataPtr = cptr;
         }
         else
         {
            /* no data in HTTP header left at all */
            NetIoWriteStatus (ioptr, AstFunction, rqptr, SS$_NORMAL, 0);
            return (SS$_NORMAL);
         }

         /* HTTP header has been completely absorbed, send everything else */
         if (WATCHMOD (rqptr, WATCH_MOD_NET))
            WatchThis (WATCHITM(rqptr), WATCH_MOD_NET,
                       "HTTP/0.9 !&X !UL", DataPtr, DataLength);
      }

      /*************/
      /* send data */
      /*************/

      if (WATCHING (rqptr, WATCH_RESPONSE_BODY))
      {
         if (rqptr->rqResponse.HeaderSent)
            WatchThis (WATCHITM(rqptr), WATCH_RESPONSE_BODY,
                       "BODY !UL bytes", DataLength);
         else
            WatchThis (WATCHITM(rqptr), WATCH_RESPONSE_BODY,
                       "STREAM !UL bytes", DataLength);
         WatchDataDump (DataPtr, DataLength);

#if WATCH_MOD
         if (rqptr->rqResponse.ContentTypePtr &&
             !strncmp (rqptr->rqResponse.ContentTypePtr, "text/", 5))
         {
            if (DataLength && DataPtr[DataLength-1] == '\n')
               WatchDataFormatted ("!#AZ", DataLength, DataPtr);
            else
               WatchDataFormatted ("!#AZ\n", DataLength, DataPtr);
         }
#endif /* WATCH_MOD */
      }

      if (rqptr->rqCache.LoadFromNet)
         CacheLoadData (rqptr, DataPtr, DataLength);

      if (rqptr->rqResponse.CharsetNcsCf)
      {
         status = ResponseCharsetConvert (rqptr, &DataPtr, &DataLength);
         if (VMSnok (status))
         {
            /* fudge the status */
            NetIoWriteStatus (ioptr, AstFunction, rqptr, status, 0);
            return (status);
         }
      }
   }
   else
   if (DataPtr)
   {
      /****************/
      /* "empty" data */
      /****************/

      /* without a real network write just fudge the status */
      NetIoWriteStatus (ioptr, AstFunction, rqptr, SS$_NORMAL, 0);
      return (SS$_NORMAL);
   }
   else
   {
      /*****************/
      /* end-of-stream */
      /*****************/

      if (rqptr->GzipCompress.DeflateStartStream &&
          !rqptr->GzipCompress.DeflateEndOfStream)
      {
         /* indicates to GZIP to flush then close the deflate stream */
         status = NetWriteGzip (rqptr, AstFunction, NULL, 0);
      }
      else
      if (rqptr->rqResponse.TransferEncodingChunked)
      {
         /* writes the end-of-content empty chunk */
         status = NetWriteChunked (rqptr, AstFunction, NULL, 0);
      }
      else
      {
         /* without a real network write just fudge the status */
         NetIoWriteStatus (ioptr, AstFunction, rqptr, status = SS$_NORMAL, 0);
      }
      return (status);
   }

   /******************/
   /* write the data */
   /******************/

   WritingProxyResponseHeader = rqptr->ProxyTaskPtr &&
                                !rqptr->ProxyTaskPtr->ResponseHeaderSent &&
                                !rqptr->rqResponse.HeaderSent;
   if (rqptr->rqResponse.ContentEncodeAsGzip && !WritingProxyResponseHeader)
      status = NetWriteGzip (rqptr, AstFunction, DataPtr, DataLength);
   else
   if (rqptr->rqResponse.TransferEncodingChunked && !WritingProxyResponseHeader)
      status = NetWriteChunked (rqptr, AstFunction, DataPtr, DataLength);
   else
   if (!DataLength)
      NetIoWriteStatus (ioptr, AstFunction, rqptr, status = SS$_NORMAL, 0);
   else
      status = NetIoWrite (ioptr, AstFunction, rqptr, DataPtr, DataLength);

   return (status);
}

/*****************************************************************************/
/*
The response content is being "Content-Encoding: gzip"ed.  Due to the way the
ZLIB compressed multiple consecutive input buffers before providing multiple
consecutive output buffers this routine must be able to handle both independent
of any other processing in the response.  It may AST to NetWriteGzipAst()
multiple times when outputing compressed data.  It ASTs to 'AstFunction'
(usually for more raw data) when any output ceases.
*/ 

NetWriteGzip
(
REQUEST_STRUCT *rqptr,
REQUEST_AST AstFunction,
char *DataPtr,
int DataLength
)
{
   int  status,
        BufferSize,
        OutDataLength,
        SanityCount;
   char  *OutDataPtr;
   GZIP_COMPRESS  *gzptr;
   NETIO_STRUCT  *ioptr;

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

   if (WATCHMOD (rqptr, WATCH_MOD_NET))
      WatchThis (WATCHITM(rqptr), WATCH_MOD_NET,
                 "NetWriteGzip() !&X !UL !&A",
                 DataPtr, DataLength, AstFunction);

   gzptr = &rqptr->GzipCompress;

   ioptr = rqptr->NetIoPtr;
   ioptr->WatchItem = rqptr->WatchItem;

   /* intermediate update to data transfer stats */
   PUT_QUAD_QUAD (ioptr->BlocksRawTx, rqptr->NetIoPtr->BlocksRawTx);
   PUT_QUAD_QUAD (ioptr->BytesRawTx, rqptr->NetIoPtr->BytesRawTx);

   if (!gzptr->DeflateStartStream)
   {
      if (DataLength <= OutputBufferSize)
         BufferSize = OutputBufferSize;
      else
         BufferSize = DataLength + (DataLength / 100) + 32;
      BufferSize &= 0x0000ffff;
      GzipDeflateBegin (rqptr, gzptr, BufferSize);
   }

   /* blocking I/O may require multiple writes */
   SanityCount = 0;
   for (;;)
   {
      /* if we didn't have or somehow have lost the zlib stream */
      if (!gzptr->DeflateZstreamPtr) break;

      if (!GzipDeflate (rqptr, gzptr,
                        &DataPtr, &DataLength,
                        &OutDataPtr, &OutDataLength)) break;

      if (OutDataLength)
      {
         /* if non-blocking I/O then AST back here (eventually) for any more */
         if (AstFunction)
         {
            rqptr->rqNet.GzipDataPtr = DataPtr;
            rqptr->rqNet.GzipDataLength = DataLength;
            rqptr->rqNet.GzipAstFunction = AstFunction;
            AstFunction = NetWriteGzipAst;
         }

         if (rqptr->rqResponse.TransferEncodingChunked)
            status = NetWriteChunked (rqptr, AstFunction,
                                      OutDataPtr, OutDataLength);
         else
            status = NetIoWrite (ioptr, AstFunction, rqptr,
                                 OutDataPtr, OutDataLength);

         if (AstFunction) return (status);
      }
      else
      if (!DataLength)
      {
         /* nothing still to input, nothing to write, fudge it */
         NetIoWriteStatus (ioptr, AstFunction, rqptr, SS$_WASECC, 0);
         return (SS$_NORMAL);
      }
      if (SanityCount++ > 100)
      {
         /* don't quite trust my logic! */
         ErrorNoticed (rqptr, SS$_BUGCHECK, ErrorSanityCheck, FI_LI);
         break;
      }
   }

   /* only ever breaks from the loop on an error */
   NetIoWriteStatus (ioptr, AstFunction, rqptr, SS$_ABORT, 0);
   return (SS$_ABORT);
}

/*****************************************************************************/
/*
AST delivered by asynchronous I/O initiated by NetWriteGzip().  Checks the
status of the network write and if OK reassembles the parameters required when
calling NetWriteGzip().
*/ 

NetWriteGzipAst (REQUEST_STRUCT *rqptr)

{
   int  DataLength;
   char  *DataPtr;
   NETIO_STRUCT  *ioptr;
   REQUEST_AST  AstFunction;

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

   ioptr = rqptr->NetIoPtr;
   ioptr->WatchItem = rqptr->WatchItem;

   if (WATCHMOD (rqptr, WATCH_MOD_NET))
      WatchThis (WATCHITM(rqptr), WATCH_MOD_NET,
                 "NetWriteGzipAst() !&F !&S !&X !UL !&A", NetWriteGzipAst,
                 ioptr->WriteStatus,
                 rqptr->rqNet.GzipDataPtr,
                 rqptr->rqNet.GzipDataLength,
                 rqptr->rqNet.GzipAstFunction);

   DataPtr = rqptr->rqNet.GzipDataPtr;
   DataLength = rqptr->rqNet.GzipDataLength;
   AstFunction = rqptr->rqNet.GzipAstFunction;
   rqptr->rqNet.GzipDataPtr =
      rqptr->rqNet.GzipDataLength =
      rqptr->rqNet.GzipAstFunction = 0;

   if (VMSok (ioptr->WriteStatus))
      NetWriteGzip (rqptr, AstFunction, DataPtr, DataLength);
   else
      NetIoWriteStatus (ioptr, AstFunction, rqptr, ioptr->WriteStatus, 0);
}

/*****************************************************************************/
/*
The response is being "Transfer-Encoding: chunked".

This is a little more complicated than it might have been in order improve the
efficiency of non-encrypted (non-SSL) network I/O.

In the case of encrypted data a separate chunk buffer is allocated.  In this
buffer the chunk size string is placed, followed by a copy of the data, and
then the required trailing arriage control.  This involves the expensive
copying of often large quantities of data, the reason why for non-encrypted
data the buffer list is used, avoiding this processing overhead.
*/ 

NetWriteChunked
(
REQUEST_STRUCT *rqptr,
REQUEST_AST AstFunction,
char *DataPtr,
int DataLength
)
{
   static char  HexDigits [] = "0123456789ABCDEF";

   int  status,
        ChunkOverhead;
   char  *cptr, *sptr;
   NETIO_STRUCT  *ioptr;
   VMS_ITEM_LIST2  *p5ptr;

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

   if (WATCHMOD (rqptr, WATCH_MOD_NET))
      WatchThis (WATCHITM(rqptr), WATCH_MOD_NET, "NetWriteChunked()");

   ioptr = rqptr->NetIoPtr;
   ioptr->WatchItem = rqptr->WatchItem;

   /* intermediate update to data transfer stats */
   PUT_QUAD_QUAD (ioptr->BlocksRawTx, rqptr->NetIoPtr->BlocksRawTx);
   PUT_QUAD_QUAD (ioptr->BytesRawTx, rqptr->NetIoPtr->BytesRawTx);

   if (DataLength & 0xffff0000)
   {
      /* maximum of 65kB */
      ErrorNoticed (rqptr, SS$_BUGCHECK, ErrorSanityCheck, FI_LI);
      NetIoWriteStatus (ioptr, AstFunction, rqptr, SS$_BUGCHECK, 0);
      return (SS$_BUGCHECK);
   }

   if (WATCHING (rqptr, WATCH_NETWORK_OCTETS))
      WatchThis (WATCHITM(rqptr), WATCH_NETWORK,
                 "CHUNK !UL bytes", DataLength);

   /* if it's the zero-length chunk (end-of-stream) then it is no longer */
   if (!DataLength) rqptr->rqResponse.TransferEncodingChunked = false;

   if (rqptr->rqResponse.ChunkedBufferSize < DataLength+16)
   {
      /* extra 16 allows plenty for the chunk size and carriage control */
      if (DataLength < OutputBufferSize)
         rqptr->rqResponse.ChunkedBufferSize = OutputBufferSize+16;
      else
         rqptr->rqResponse.ChunkedBufferSize = DataLength+16;
      if (rqptr->rqResponse.ChunkedBufferPtr)
         VmFreeFromHeap (rqptr, rqptr->rqResponse.ChunkedBufferPtr, FI_LI);
      rqptr->rqResponse.ChunkedBufferPtr =
         VmGetHeap (rqptr, rqptr->rqResponse.ChunkedBufferSize);
   }

   cptr = sptr = rqptr->rqResponse.ChunkedBufferPtr;

   /* relatively cheap hex string generation */
   if (DataLength & 0x0000f000)
   {
      *cptr++ = HexDigits[(DataLength & 0x0000f000) >> 12];
      *cptr++ = HexDigits[(DataLength & 0x00000f00) >> 8];
      *cptr++ = HexDigits[(DataLength & 0x000000f0) >> 4];
      *cptr++ = HexDigits[DataLength & 0x0000000f];
   }
   else
   if (DataLength & 0x00000f00)
   {
      *cptr++ = HexDigits[(DataLength & 0x00000f00) >> 8];
      *cptr++ = HexDigits[(DataLength & 0x000000f0) >> 4];
      *cptr++ = HexDigits[DataLength & 0x0000000f];
   }
   else
   if (DataLength & 0x000000f0)
   {
      *cptr++ = HexDigits[(DataLength & 0x000000f0) >> 4];
      *cptr++ = HexDigits[DataLength & 0x0000000f];
   }
   else
      *cptr++ = HexDigits[DataLength & 0x0000000f];
   *cptr++ = '\r';
   *cptr++ = '\n';

   ChunkOverhead = cptr - sptr + 2;
   ADD_LONG_QUAD (ChunkOverhead, rqptr->BytesTx)

   if (DataLength)
   {
      memcpy (cptr, DataPtr, DataLength);
      cptr += DataLength;
   }
   *cptr++ = '\r';
   *cptr++ = '\n';

   DataPtr = rqptr->rqResponse.ChunkedBufferPtr;
   DataLength = cptr - rqptr->rqResponse.ChunkedBufferPtr;

   status = NetIoWrite (rqptr->NetIoPtr, AstFunction, rqptr,
                        DataPtr, DataLength);
   return (status);
}

/*****************************************************************************/
/*
Write data directly to the network.  No fancy stuff.
*/

int NetWriteRaw
(
REQUEST_STRUCT *rqptr,
REQUEST_AST AstFunction,
char *DataPtr,
int DataLength
)

{
   int  status;
   NETIO_STRUCT  *ioptr;

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

   if (WATCHMOD (rqptr, WATCH_MOD_NET))
      WatchThis (WATCHITM(rqptr), WATCH_MOD_NET,
                 "NetWriteRaw() !&A !&X !UL", AstFunction, DataPtr, DataLength);

   ioptr = rqptr->NetIoPtr;
   ioptr->WatchItem = rqptr->WatchItem;

   status = NetIoWrite (ioptr, AstFunction, rqptr, DataPtr, DataLength);

   return (status);
}

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

int NetRead
(
REQUEST_STRUCT *rqptr,
REQUEST_AST AstFunction,
char *DataPtr,
int DataSize
)
{
   int  status;
   NETIO_STRUCT  *ioptr;

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

   if (WATCHMOD (rqptr, WATCH_MOD_NET))
      WatchThis (WATCHITM(rqptr), WATCH_MOD_NET, "NetRead() !&A !&X !UL",
                 AstFunction, DataPtr, DataSize);

   ioptr = rqptr->NetIoPtr;
   ioptr->WatchItem = rqptr->WatchItem;

   if (rqptr->RequestState >= REQUEST_STATE_ABORT)
   {
      NetIoReadStatus (ioptr, AstFunction, rqptr, SS$_ABORT, 0);
      return (SS$_ABORT);
   }

   /* intermediate update to data transfer stats */
   PUT_QUAD_QUAD (ioptr->BlocksRawRx, rqptr->NetIoPtr->BlocksRawRx);
   PUT_QUAD_QUAD (ioptr->BytesRawRx, rqptr->NetIoPtr->BytesRawRx);

   if (rqptr->rqNet.RedactBufferPtr)
   {
      int  ReadCount = rqptr->rqNet.RedactBufferSize -
                       rqptr->rqNet.RedactBufferCount;
      char  *RedactBufferPtr = rqptr->rqNet.RedactBufferPtr +
                               rqptr->rqNet.RedactBufferCount;

      if (WATCHMOD (rqptr, WATCH_MOD_NET))
         WatchThis (WATCHITM(rqptr), WATCH_MOD_NET, "REDACT !UL-!UL=!UL",
                    rqptr->rqNet.RedactBufferSize,
                    rqptr->rqNet.RedactBufferCount,
                    ReadCount);

      if (ReadCount >= DataSize)
      {
         /* redact buffer (still) contains more than a network buffer */
         status = SS$_NORMAL;
         ReadCount = DataSize;
         memcpy (DataPtr, RedactBufferPtr, ReadCount);
         rqptr->rqNet.RedactBufferCount += ReadCount;
      }
      else
      if (ReadCount)
      {
         /* whatever is remaining */
         status = SS$_NORMAL;
         memcpy (DataPtr, RedactBufferPtr, ReadCount);
         rqptr->rqNet.RedactBufferCount += ReadCount;
      }
      else
      {
         /* redact buffer has been completely read */
         status = SS$_ENDOFFILE;
         ReadCount = 0;
      }

      NetIoReadStatus (ioptr, AstFunction, rqptr, status, ReadCount);
      return (status);
   }

   if (rqptr->ServicePtr->ProxyTunnel == PROXY_TUNNEL_RAW &&
       /* prevent %CC-E-NOEQUALITY under VAX VMS V7.3 and DECC V6.4 */
       (void*)AstFunction == (void*)RequestGet)
   {
      /*
         For raw proxy tunneling, we need to send a connect request to
         the origin server immediately since the client will wait until it
         receives the server welcome message. This should be done only once
         upon SSL handshake completion.
      */
      /* fudge these as if it came from the network */
      NetIoReadStatus (ioptr, RequestGet, rqptr, SS$_NORMAL, 0);
      return (SS$_NORMAL);
   }

   status = NetIoRead (ioptr, AstFunction, rqptr, DataPtr, DataSize);

   return (status);
}

/*****************************************************************************/
/*
This function buffers output without actually sending it to the client until
ready, either when the first buffer fills or when all output is completely
buffered.  It will store any amount of output in a linked list of separately
allocated descriptors. This is useful when a function that must not be
interrupted can rapidly store all required output without being slowed by
actually transfering it on the network, then flush it all asynchronously (e.g.
the server administration reports, for instance CacheReport(), which are
blocking).

If the data pointer is not NULL and the data length is -1 then the data is
assumed to be a null-terminated string.

Providing an AST parameter and a data parameter implies that after the first
buffer fills (and usually overflows into a second) the current buffered output
should be written to the client.

Providing an AST parameter and setting the data parameter to NULL and
the data length to 0 indicates all the currently buffered contents should be
written to the client.

Providing an AST parameter and setting the data parameter to NULL and the data
length parameter to -1 indicates all the currently buffered contents should be
written to the client if more than buffer-full has been written, otherwise just
declare the AST. 

Providing all parameters as NULL and zero, (except 'rqptr') as appropriate,
results in the data buffer initialized (if it didn't exist) or reset (if it
did).
*/ 

void NetWriteBuffered
(
REQUEST_STRUCT *rqptr,
REQUEST_AST AstFunction,
char *DataPtr,
int DataLength
)
{
   int  status;
   char  *BufferPtr;
   STR_DSC  *sdptr;
   STR_DSC_AUTO (DataDsc);

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

   if (WATCHMOD (rqptr, WATCH_MOD_NET))
   {
      WatchThis (WATCHITM(rqptr), WATCH_MOD_NET,
                 "NetWriteBuffered() !UL !&A !&X !SL",
                 rqptr->RequestState, AstFunction, DataPtr, DataLength);
      WatchDataDump (DataPtr, DataLength);
   }

   if (rqptr->RequestState >= REQUEST_STATE_ABORT)
   {
      NetIoWriteStatus (rqptr->NetIoPtr, AstFunction, rqptr, SS$_ABORT, 0);
      return;
   }

   /* if all parameters empty */
   if (!AstFunction && !DataPtr && !DataLength)
   {
      /* reset the descriptor */
      if (STR_DSC_SANITY(&rqptr->NetWriteBufferDsc))
      {
         /* if there is data in the descriptor write it blocking */
         if (STR_DSC_LEN (&rqptr->NetWriteBufferDsc))
            NetWriteStrDsc (rqptr, NULL);
         StrDscNoContent (&rqptr->NetWriteBufferDsc);
      }
      else
         StrDscBegin (rqptr, &rqptr->NetWriteBufferDsc, OutputBufferSize);
      return;
   }

   if (DataPtr && DataLength)
   {
      /**********************/
      /* buffer this output */
      /**********************/

      if (!STR_DSC_SANITY(&rqptr->NetWriteBufferDsc))
         StrDscBegin (rqptr, &rqptr->NetWriteBufferDsc, OutputBufferSize);

      if (rqptr->NetWriteEscapeHtml)
      {
         if (DataLength == -1)
            StrDscBuildHtmlEscape (&rqptr->NetWriteBufferDsc, NULL, DataPtr);
         else
         {
            StrDscThis (NULL, &DataDsc, DataPtr, DataLength);
            StrDscBuildHtmlEscape (&rqptr->NetWriteBufferDsc, &DataDsc, NULL);
         }
      }
      else
      {
         if (DataLength == -1)
            StrDscBuild (&rqptr->NetWriteBufferDsc, NULL, DataPtr);
         else
         {
            StrDscThis (NULL, &DataDsc, DataPtr, DataLength);
            StrDscBuild (&rqptr->NetWriteBufferDsc, &DataDsc, NULL);
         }
      }
   }

   /* if an AST routine supplied then we can think about buffer flushing */
   if (!AstFunction) return;

   if (!DataPtr && DataLength != -1)
   {
      /*************************/
      /* flush is being forced */
      /*************************/

      NetWriteStrDsc (rqptr, AstFunction);
      return;
   }

   if (STR_DSC_LEN(&rqptr->NetWriteBufferDsc) >=
       STR_DSC_SIZE(&rqptr->NetWriteBufferDsc))
   {
      /************************/
      /* first buffer is full */
      /************************/

      rqptr->NetWriteFlushOnly = true;
      NetWriteStrDsc (rqptr, AstFunction);
      return;
   }

   /************************/
   /* just declare the AST */
   /************************/

   /* fudge this status for the AST routine check */
   NetIoWriteStatus (rqptr->NetIoPtr, AstFunction, rqptr, SS$_NORMAL, 0);
}

/*****************************************************************************/
/*
Synchronous (no AST function supplied) or asynchronous write of a WASD string
descriptor or linked list of string descriptors.  Where multiple writes are
required this function will be delivered to as an AST (and then the
'AstFunction' parameter is ignored).
*/ 

void NetWriteStrDsc
(
REQUEST_STRUCT *rqptr,
REQUEST_AST AstFunction
)
{
   BOOL  BlockingWrite;
   int  DataLength;
   char  *DataPtr,
         *EndPtr;
   NETIO_STRUCT  *ioptr;
   STR_DSC  *sdptr;

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

   if (WATCHMOD (rqptr, WATCH_MOD_NET))
      WatchThis (WATCHITM(rqptr), WATCH_MOD_NET,
                 "NetWriteStrDsc() !&F !&A", &NetWriteStrDsc,
                 rqptr->NetWriteAstFunction);
 
   ioptr = rqptr->NetIoPtr;
   ioptr->WatchItem = rqptr->WatchItem;

   /* loop for (possible) blocking write */
   BlockingWrite = false;
   for (;;)
   {
      if (BlockingWrite || rqptr->NetWriteAstFunction)
      {
         /* blocking write loop or AST delivery */
         if (VMSnok (ioptr->WriteStatus))
         {
            /* network write has failed, bail out now */
            NetIoWriteStatus (ioptr, rqptr->NetWriteAstFunction, rqptr,
                              ioptr->WriteStatus, 0);
            rqptr->NetWriteAstFunction = NULL;
            StrDscNoContent (&rqptr->NetWriteBufferDsc);
            return;
         }

         /* find the descriptor having content */
         STR_DSC_ITERATE (sdptr, &rqptr->NetWriteBufferDsc)
            if (STR_DSC_LEN(sdptr)) break;

         if (!sdptr) ErrorExitVmsStatus (SS$_BUGCHECK, ErrorSanityCheck, FI_LI);
      }
      else
      {
         /* initial call */
         if (!rqptr) ErrorExitVmsStatus (SS$_BUGCHECK, ErrorSanityCheck, FI_LI);
         BlockingWrite = !(rqptr->NetWriteAstFunction = AstFunction);
         rqptr->NetWriteBufferCount = 0;
         sdptr = &rqptr->NetWriteBufferDsc;
      }

      EndPtr = STR_DSC_PTR(sdptr) + STR_DSC_LEN(sdptr);
      DataPtr = STR_DSC_PTR(sdptr) + rqptr->NetWriteBufferCount;
      DataLength = EndPtr - DataPtr;
      rqptr->NetWriteBufferCount += DataLength;

      if (!BlockingWrite) AstFunction = &NetWriteStrDsc;

      if (rqptr->NetWriteBufferCount >= STR_DSC_LEN(sdptr))
      {
         /* this is the final write for this descriptor */
         rqptr->NetWriteBufferCount = STR_DSC_LEN(sdptr) = 0;

         if (rqptr->NetWriteFlushOnly)
         {
            /* output all (if multiple) leading full descriptors */
            if (!STR_DSC_NEXT(sdptr) ||
                STR_DSC_LEN(STR_DSC_NEXT(sdptr)) <
                STR_DSC_SIZE(STR_DSC_NEXT(sdptr)))
            {
               /* less than full (or none at all) */
               rqptr->NetWriteFlushOnly = false;
               if (STR_DSC_NEXT(sdptr))
               {
                  /* swap the now-emptied and partially filled storage */
                  StrDscStorageSwap (&rqptr->NetWriteBufferDsc,
                                     STR_DSC_NEXT(sdptr));
               }
               sdptr = NULL;
            }
         }
         else
         {
            /* find the next (if any) descriptor having content */
            for (sdptr = STR_DSC_NEXT(sdptr); sdptr; sdptr = STR_DSC_NEXT(sdptr))
               if (STR_DSC_LEN(sdptr)) break;
         }

         if (!sdptr)
         {
            /* final write entirely */
            AstFunction = rqptr->NetWriteAstFunction;
            rqptr->NetWriteAstFunction = NULL;
            BlockingWrite = false;
         }  
      }

      NetWrite (rqptr, AstFunction, DataPtr, DataLength);

      if (!BlockingWrite || AstFunction) return;
   }
}

/*****************************************************************************/
/*
This function MUST be called with the global section ALREADY locked.
Increment or decrement the counters of current network connections.  Account
for HTTP/2 and HTTP/1.n connections separately.  Global storage represents data
of the running image and process (i.e. this instance) and accounting data
the server overall (where multiple instances are deployed). 
*/ 

void NetUpdateConnected
(
void *DataPtr,
int MoreOrLess
)
{
   int  idx, tot1, tot2;
   ACCOUNTING_STRUCT  *accptr;
   HTTP2_STRUCT  *h2ptr = NULL;
   REQUEST_STRUCT  *rqptr = NULL;

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

   accptr = AccountingPtr;

   if (!DataPtr)
      h2ptr = rqptr = NULL;
   else
   if (((LIST_ENTRY*)DataPtr)->DataType == LIST_ENTRY_TYPE_HTTP2)
      h2ptr = (HTTP2_STRUCT*)DataPtr;
   else
   if (((LIST_ENTRY*)DataPtr)->DataType == LIST_ENTRY_TYPE_REQUEST)
      rqptr = (REQUEST_STRUCT*)DataPtr;
   /* else an accepted connection is not a "request" until RequestBegin() */

   if (WATCH_MODULE(WATCH_MOD_NET))
      WatchThis (WATCHALL, WATCH_MOD_NET,
                 "NetUpdateConnected() !SL !AZ !UL+!UL=!UL",
                 MoreOrLess, h2ptr ? "HTTP/2" : "HTTP/1.n",
                 NetCurrentConnected[HTTP1], NetCurrentConnected[HTTP2],
                 NetCurrentConnected[HTTP12]);

   if (MoreOrLess > 0)
   {
      /************/
      /* one more */
      /************/

      if (rqptr)
      {
         NetCurrentConnected[HTTP1]++;
         accptr->ConnectCount[HTTP1]++;
         accptr->CurrentInstanceConnected[HTTP1][InstanceNumber]++;
         /* total connections only increase with HTTP/1.n */
         accptr->ConnectCount[HTTP12]++;
      }
      else
      if (h2ptr)
      {
         NetCurrentConnected[HTTP2]++;
         accptr->ConnectCount[HTTP2]++;
         accptr->CurrentInstanceConnected[HTTP2][InstanceNumber]++;
      }
      else
         ErrorNoticed (NULL, SS$_BUGCHECK, NULL, FI_LI);
   }
   else
   if (MoreOrLess < 0)
   {
      /************/
      /* one less */
      /************/

      if (rqptr)
      {
         if (NetCurrentConnected[HTTP1]) NetCurrentConnected[HTTP1]--;
         if (accptr->CurrentInstanceConnected[HTTP1][InstanceNumber])
            accptr->CurrentInstanceConnected[HTTP1][InstanceNumber]--;
      }
      else
      if (h2ptr)
      {
         if (NetCurrentConnected[HTTP2]) NetCurrentConnected[HTTP2]--;
         if (accptr->CurrentInstanceConnected[HTTP2][InstanceNumber])
            accptr->CurrentInstanceConnected[HTTP2][InstanceNumber]--;
      }
      else
         ErrorNoticed (NULL, SS$_BUGCHECK, NULL, FI_LI);
   }
   /* else if zero then just drop through to re-accumulate accounting */

   /**************/
   /* accumulate */
   /**************/

   NetCurrentConnected[HTTP12] = NetCurrentConnected[HTTP1] +
                                 NetCurrentConnected[HTTP2];

   for (tot1 = tot2 = idx = 0; idx <= InstanceNodeCurrent; idx++)
   {
      tot1 += accptr->CurrentInstanceConnected[HTTP1][idx];
      tot2 += accptr->CurrentInstanceConnected[HTTP2][idx];
   }

   if (NetCurrentConnected[HTTP1] > accptr->ConnectPeak[HTTP1])
      accptr->ConnectPeak[HTTP1] = NetCurrentConnected[HTTP1];
   accptr->CurrentConnected[HTTP1] = tot1;
   if (tot1 > accptr->ConnectPeak[HTTP1]) accptr->ConnectPeak[HTTP1] = tot1;

   if (NetCurrentConnected[HTTP2] > accptr->ConnectPeak[HTTP2])
      accptr->ConnectPeak[HTTP2] = NetCurrentConnected[HTTP2];
   accptr->CurrentConnected[HTTP2] = tot2;
   if (tot2 > accptr->ConnectPeak[HTTP2]) accptr->ConnectPeak[HTTP2] = tot2;

   accptr->CurrentConnected[HTTP12] = tot1 + tot2;
   if (accptr->CurrentConnected[HTTP12] > accptr->ConnectPeak[HTTP12])
      accptr->ConnectPeak[HTTP12] = accptr->CurrentConnected[HTTP12];

   if (WATCH_MODULE(WATCH_MOD_NET))
      WatchThis (WATCHALL, WATCH_MOD_NET, "!AZ!AZ!UL+!UL=!UL !UL+!UL=!UL",
                 h2ptr ? "HTTP/2 " : "",
                 rqptr ? "HTTP/1.n " : "",
                 NetCurrentConnected[HTTP1], NetCurrentConnected[HTTP2],
                 NetCurrentConnected[HTTP12], tot1, tot2,
                 accptr->CurrentConnected[HTTP12]);
}

/*****************************************************************************/
/*
This function MUST be called with the global section ALREADY locked.
Increment or decrement the current count of persistent connections.
*/ 

void NetUpdatePersistent (int MoreOrLess)

{
   int  idx, tot1;
   ACCOUNTING_STRUCT  *accptr;

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

   if (WATCH_MODULE(WATCH_MOD_NET))
      WatchThis (WATCHALL, WATCH_MOD_NET, "NetUpdatePersistent() !SL !UL",
                 MoreOrLess, NetCurrentHttp1Persistent);

   accptr = AccountingPtr;

   if (MoreOrLess > 0)
   {
      NetCurrentHttp1Persistent++;
      accptr->CurrentPersistentHttp1[InstanceNumber]++;
   }
   else
   if (MoreOrLess < 0)
   {
      if (NetCurrentHttp1Persistent) NetCurrentHttp1Persistent--;
      if (accptr->CurrentPersistentHttp1[InstanceNumber])
         accptr->CurrentPersistentHttp1[InstanceNumber]--;
   }
   /* else if zero then just drop through to re-accumulate accounting */

   for (tot1 = idx = 0; idx <= InstanceNodeCurrent; idx++)
       tot1 += accptr->CurrentPersistentHttp1[idx];
   accptr->CurrentHttp1Persistent = tot1;
   if (accptr->CurrentHttp1Persistent > accptr->ConnectPeakPersistent)
      accptr->ConnectPeakPersistent = accptr->CurrentHttp1Persistent;

   if (WATCH_MODULE(WATCH_MOD_NET))
      WatchThis (WATCHALL, WATCH_MOD_NET, "!UL !UL",
                 NetCurrentHttp1Persistent, tot1);
}

/*****************************************************************************/
/*
This function MUST be called with the global section ALREADY locked.
Increment or decrement the counters of currently processing requests.  Account
for HTTP/2 and HTTP/1.n requests separately.  Global storage represents data of
the running image and process (i.e. this instance) and accounting data the
server overall (where multiple instances are deployed). 
*/ 

void NetUpdateProcessing
(
void *DataPtr,
int MoreOrLess
)
{
   int  idx, tot1, tot2;
   ACCOUNTING_STRUCT  *accptr;
   HTTP2_STRUCT  *h2ptr = NULL;
   REQUEST_STRUCT  *rqptr = NULL;

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

   accptr = AccountingPtr;

   if (!DataPtr)
      h2ptr = rqptr = NULL;
   else
   if (((LIST_ENTRY*)DataPtr)->DataType == LIST_ENTRY_TYPE_REQUEST)
   {
      rqptr = (REQUEST_STRUCT*)DataPtr;
      if (h2ptr = rqptr->Http2Stream.Http2Ptr) rqptr = NULL;
   }
   else
      ErrorExitVmsStatus (SS$_BUGCHECK, ErrorSanityCheck, FI_LI);

   if (WATCH_MODULE(WATCH_MOD_NET))
      WatchThis (WATCHALL, WATCH_MOD_NET,
                 "NetUpdateProcessing() !SL !AZ !UL+!UL=!UL",
                 MoreOrLess, h2ptr ? "HTTP/2" : "HTTP/1.n",
                 NetCurrentProcessing[HTTP1], NetCurrentProcessing[HTTP2],
                 NetCurrentProcessing[HTTP12]);

   if (MoreOrLess > 0)
   {
      /************/
      /* one more */
      /************/

      InstanceStatusRequestCount++;
      accptr->ProcessingTotalCount[HTTP12]++;

      if (rqptr)
      {
         NetCurrentProcessing[HTTP1]++;
         accptr->ProcessingTotalCount[HTTP1]++;
         accptr->CurrentInstanceProcessing[HTTP1][InstanceNumber]++;

         if (rqptr->WebSocketRequest)
         {
            WebSockCurrent++;
            accptr->CurrentWebSockets[InstanceNumber]++;
         }

         if (rqptr->ServicePtr->RequestScheme == SCHEME_HTTPS)
            AccountingPtr->RequestSSLCount++;
      }
      else
      if (h2ptr)
      {
         NetCurrentProcessing[HTTP2]++;
         accptr->ProcessingTotalCount[HTTP2]++;
         accptr->CurrentInstanceProcessing[HTTP2][InstanceNumber]++;

         if (h2ptr->ServicePtr->RequestScheme == SCHEME_HTTPS)
            AccountingPtr->RequestSSLCount++;
      }
      else
         ErrorNoticed (NULL, SS$_BUGCHECK, NULL, FI_LI);
   }
   else
   if (MoreOrLess < 0)
   {
      /************/
      /* one less */
      /************/

      if (rqptr)
      {
         if (NetCurrentProcessing[HTTP1]) NetCurrentProcessing[HTTP1]--;
         if (accptr->CurrentInstanceProcessing[HTTP1][InstanceNumber])
            accptr->CurrentInstanceProcessing[HTTP1][InstanceNumber]--;

         if (rqptr->WebSocketRequest)
         {
            if (WebSockCurrent) WebSockCurrent--;
            if (accptr->CurrentWebSockets[InstanceNumber])
               accptr->CurrentWebSockets[InstanceNumber]--;
         }
      }
      else
      if (h2ptr)
      {
         if (NetCurrentProcessing[HTTP2]) NetCurrentProcessing[HTTP2]--;
         if (accptr->CurrentInstanceProcessing[HTTP2][InstanceNumber])
            accptr->CurrentInstanceProcessing[HTTP2][InstanceNumber]--;
      }
      else
         ErrorNoticed (NULL, SS$_BUGCHECK, NULL, FI_LI);
   }
   /* else if zero then just drop through to re-accumulate accounting */

   /**************/
   /* accumulate */
   /**************/

   NetCurrentProcessing[HTTP12] = NetCurrentProcessing[HTTP1] +
                                  NetCurrentProcessing[HTTP2];

   for (tot1 = tot2 = idx = 0; idx <= InstanceNodeCurrent; idx++)
   {
      tot1 += accptr->CurrentInstanceProcessing[HTTP1][idx];
      tot2 += accptr->CurrentInstanceProcessing[HTTP2][idx];
   }

   accptr->CurrentProcessing[HTTP1] = tot1;
   if (tot1 > accptr->ProcessingPeak[HTTP1])
      accptr->ProcessingPeak[HTTP1] = tot1;
   accptr->CurrentProcessing[HTTP2] = tot2;
   if (tot2 > accptr->ProcessingPeak[HTTP2])
      accptr->ProcessingPeak[HTTP2] = tot2;
   accptr->CurrentProcessing[HTTP12] = tot1 + tot2;
   if (accptr->CurrentProcessing[HTTP12] > accptr->ProcessingPeak[HTTP12])
      accptr->ProcessingPeak[HTTP12] = accptr->CurrentProcessing[HTTP12];

   if (WATCH_MODULE(WATCH_MOD_NET))
      WatchThis (WATCHALL, WATCH_MOD_NET, "!UL+!UL=!UL !UL+!UL=!UL",
                 NetCurrentProcessing[HTTP1], NetCurrentProcessing[HTTP2],
                 NetCurrentProcessing[HTTP12], tot1, tot2,
                 accptr->CurrentProcessing[HTTP12]);
}

/*****************************************************************************/
/*
Respond direct to the client without beginning request processing.  Read
something from the client, they seem to enjoy (need) that!  These responses
are completely independent of the main request processing life-cycle.  These
functions should only be used from NetAcceptProcess()!!
*/
 
NetDirectResponse
(
NETIO_STRUCT *ioptr,
int Message
)
{
   static unsigned long  FiveSecondsDelta [QUAD2] = { -50000000, -1 };
   static $DESCRIPTOR (BufferDsc, "");

   static $DESCRIPTOR (TooBusyFaoDsc,
"HTTP/1.1 503 Too busy!!\r\n\
Content-Type: text/html!AZ!AZ\r\n\
Connection: close\r\n\
\r\n\
!AZ\
<html>\n\
<head>\n\
<title>!AZ 503</title>\n\
</head>\n\
!AZ\n\
!AZ\n\
</body>\n\
</html>\n");

   static $DESCRIPTOR (ForbiddenFaoDsc,
"HTTP/1.1 403 Forbidden\r\n\
Content-Type: text/html!AZ!AZ\r\n\
Connection: close\r\n\
\r\n\
!AZ\
<html>\n\
<head>\n\
<title>!AZ 403</title>\n\
</head>\n\
!AZ\n\
!AZ\n\
</body>\n\
</html>\n");

   int  status;
   short  Length;
   IO_SB  IOsb;

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

   if (WATCH_MODULE(WATCH_MOD_NET))
      WatchThis (WATCHALL, WATCH_MOD_NET,
                 "NetDirectResponse() !UL", Message);

   status = sys$setimr (0, &FiveSecondsDelta,
                        &NetDirectResponseTimeoutAst, ioptr, 0);
   if (VMSnok (status))
   {
      ErrorNoticed (NULL, status, NULL, FI_LI);
      sys$dassgn (ioptr->Channel);
      VmFree (ioptr, FI_LI);
      return;
   }

   ioptr->ReadPtr = VmGet (NetReadBufferSize);

   BufferDsc.dsc$a_pointer = ioptr->ReadPtr;
   BufferDsc.dsc$w_length = NetReadBufferSize;

   if (Message == MSG_GENERAL_ACCESS_DENIED)
      sys$fao (&ForbiddenFaoDsc, &Length, &BufferDsc,
               Config.cfContent.CharsetDefault[0] ? "; charset=" : "",
               Config.cfContent.CharsetDefault,
               WASD_DOCTYPE,
               MsgFor (NULL, MSG_STATUS_ERROR),
               REPORT_BODY_TAG,
               MsgFor (NULL, MSG_GENERAL_ACCESS_DENIED));
   else
   if (Message == MSG_GENERAL_TOO_BUSY)
      sys$fao (&TooBusyFaoDsc, &Length, &BufferDsc,
               Config.cfContent.CharsetDefault[0] ? "; charset=" : "",
               Config.cfContent.CharsetDefault,
               WASD_DOCTYPE,
               MsgFor (NULL, MSG_STATUS_ERROR),
               REPORT_BODY_TAG,
               MsgFor (NULL, MSG_GENERAL_TOO_BUSY));
   else
      ErrorExitVmsStatus (SS$_BUGCHECK, ErrorSanityCheck, FI_LI);

   status = sys$qiow (EfnWait, ioptr->Channel,
                      IO$_WRITEVBLK | IO$M_NOWAIT,
                      &IOsb, 0, 0, ioptr->ReadPtr, Length,
                      0, 0, 0, 0);
   if (VMSok (status)) status = IOsb.Status;
   if (VMSok (status))
   {
      status = sys$qio (EfnNoWait, ioptr->Channel,
                        IO$_READVBLK, &ioptr->WriteIOsb,
                        &NetDirectResponseAst, ioptr,
                        ioptr->ReadPtr, NetReadBufferSize,
                        0, 0, 0, 0);
      if (VMSnok (status)) ErrorNoticed (NULL, status, NULL, FI_LI);
   }
   else
      ErrorNoticed (NULL, status, NULL, FI_LI);

   if (VMSnok (status))
   {
      VmFree (ioptr->ReadPtr, FI_LI);
      NetIoEnd (ioptr);
   }
}

/*****************************************************************************/
/*
See commentary in NetDirectResponse().
*/ 

NetDirectResponseAst (NETIO_STRUCT *ioptr)

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

   if (WATCH_MODULE(WATCH_MOD_NET))
      WatchThis (WATCHALL, WATCH_MOD_NET, "NetDirectResponseAst()");

   sys$cantim (ioptr, 0);
   VmFree (ioptr->ReadPtr, FI_LI);
   NetIoEnd (ioptr);
}

/*****************************************************************************/
/*
See commentary in NetDirectResponse().
*/ 

NetDirectResponseTimeoutAst (NETIO_STRUCT *ioptr)

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

   if (WATCH_MODULE(WATCH_MOD_NET))
      WatchThis (WATCHALL, WATCH_MOD_NET, "NetDirectResponseTimeoutAst()");

   sys$cancel (ioptr->Channel);
}

/*****************************************************************************/
/*
Called by HttpdTick() every minute to continuously break network connections
using NetTestBreak().  The logical name WASD_NET_TEST_BREAK contains one or
more single-digit integers used to call NetTestBreak() every five seconds after
the minute.
*/ 

#if WATCH_MOD

void NetTestSupervisor ()

{
   static unsigned long  FiveSecondsDelta [QUAD2] = { -50000000, -1 };
   static char  *nptr = "";

   int  status;
   ulong number, seconds;
   char  *cptr;

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

   if (WATCH_MODULE(WATCH_MOD_NET))
      WatchThis (WATCHALL, WATCH_MOD_NET, "NetTestSupervisor()");

   if (!(cptr = SysTrnLnm ("WASD_NET_TEST_BREAK"))) return;
   if (!*nptr) nptr = cptr;
   while (*nptr && !isdigit(*nptr)) nptr++;
   number = atoi(nptr);
   while (isdigit(*nptr)) nptr++;

   status = sys$setimr (0, &FiveSecondsDelta, NetTestBreak, number, 0);
   if (VMSnok (status)) ErrorNoticed (NULL, status, NULL, FI_LI);
}

#endif /* WATCH_MOD */

/*****************************************************************************/
/*
This function is used to break every /nth/ network connection simply by
deassigning the network channel.

It is used on the development/test bench to see how the server behaves when
network connections are abruptly broken.   Any HTTP/2 request in progress has
the underlying HTTP/2 connection broken (and so may take-out multiple requests).
A selective sledgehammer!
*/ 

#if WATCH_MOD

#include "sesola.h"
void NetTestBreak (int every)

{
   int  count, total;
   HTTP2_STRUCT  *h2ptr;
   LIST_ENTRY  *leptr;
   NETIO_STRUCT  *ioptr;
   REQUEST_STRUCT  *rqeptr;

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

   if (WATCH_MODULE(WATCH_MOD_NET))
      WatchThis (WATCHALL, WATCH_MOD_NET, "NetTestBreak()");

   if (every <= 0) every = 1;
   count = total = 0;
   for (leptr = RequestList.HeadPtr; leptr; leptr = leptr->NextPtr)
   {
      if (++count % every) continue;
      rqeptr = (REQUEST_STRUCT*)leptr;
      /* do not kill WATCHing request */
      if (rqeptr == Watch.RequestPtr) continue;
      ioptr = rqeptr->NetIoPtr;
      if (ioptr->Http2StreamPtr) continue;
      if (rqeptr->rqHeader.RequestUriPtr)
         WatchThis (WATCHITM(rqeptr), WATCH_NETWORK, "BREAK1 !AZ !AZ !AZ",
                    rqeptr->ServicePtr->ServerHostPort,
                    rqeptr->rqHeader.MethodName,
                    rqeptr->rqHeader.RequestUriPtr);
      else
         WatchThis (WATCHITM(rqeptr), WATCH_NETWORK, "BREAK1 !AZ !UL",
                    rqeptr->ServicePtr->ServerHostPort,
                    rqeptr->rqNet.PersistentCount);
      sys$dassgn (ioptr->Channel);
      ioptr->Channel = 0;
      total++;
   }
   FaoToStdout ("%HTTPD-I-NET, !%T, !UL/!UL/!UL broken\n",
                0, every, total, count);
   Http2TestBreak (every);
}

#endif /* WATCH_MOD */

/*****************************************************************************/
/*
Needed a rig to test/experiment with multiple-instance network device (socket)
handling.
*/ 

#if NET_TEST

NetTestRequest (unsigned short Channel)

{
   int  status;
   struct NetTestStruct  *ntptr;

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

   if (WATCH_MODULE(WATCH_MOD_NET))
      WatchThis (WATCHALL, WATCH_MOD_NET, "NetTestRequest() !UL", Channel);

   ntptr = VmGet (sizeof(struct NetTestStruct));

   ntptr->Channel = Channel;

   status = sys$qio (EfnNoWait, ntptr->Channel, IO$_READVBLK, &ntptr->IOsb,
                     &NetTestReadAst, ntptr,
                     ntptr->Buffer, sizeof(ntptr->Buffer), 0, 0, 0, 0);
   if (VMSnok (status)) ErrorNoticed (rqptr, status, NULL, FI_LI);
}

/*****************************************************************************/
/*
See comments in NetTestRequest().
*/ 

NetTestReadAst (struct NetTestStruct *ntptr)

{
   static char  ResponseHeader [] =
"HTTP/1.0 200 OK\r\n\
Server: WASD/Test\r\n\
Content-Type: text/plain\r\n\
\r\n";
   static char  ResponseBody [] =
"abcdefghijklnmopqrstuvwxyzABCDEFGHIJKLNMOPQRSTUVWXYZ0123456789\n";

   int  status;
   char  *cptr, *sptr, *zptr;

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

   if (WATCH_MODULE(WATCH_MOD_NET))
      WatchThis (WATCHALL, WATCH_MOD_NET,
                 "NetTestReadAst() !UL !&S !UL",
                 ntptr->Channel, ntptr->IOsb.Status, ntptr->IOsb.Count);

   if (VMSnok (ntptr->IOsb.Status))
   {
      ErrorNoticed (rqptr, ntptr->IOsb.Status, NULL, FI_LI);
      status = sys$dassgn (ntptr->Channel);
      if (VMSnok (status)) ErrorNoticed (rqptr, status, NULL, FI_LI);
      VmFree (ntptr, FI_LI);
      return;
   }

   memcpy (ntptr->Buffer, ResponseHeader, sizeof(ResponseHeader));
   sptr = ntptr->Buffer + sizeof(ResponseHeader);
   zptr = ntptr->Buffer + sizeof(ntptr->Buffer);
   cptr = ResponseBody;
   while (sptr < zptr)
   {
      if (!*cptr) cptr = ResponseBody;
      *sptr++ = *cptr++;
   }

   status = sys$qio (EfnNoWait, ntptr->Channel, IO$_WRITEVBLK, &ntptr->IOsb,
                     &NetTestWriteAst, ntptr,
                     ntptr->Buffer, sizeof(ntptr->Buffer), 0, 0, 0, 0);
   if (VMSnok (status)) ErrorNoticed (rqptr, status, NULL, FI_LI);
}

/*****************************************************************************/
/*
See comments in NetTestRequest().
*/ 

NetTestWriteAst (struct NetTestStruct *ntptr)

{
   int  status;

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

   if (WATCH_MODULE(WATCH_MOD_NET))
      WatchThis (WATCHALL, WATCH_MOD_NET,
                 "NetTestWriteAst() !UL !&S",
                 ntptr->Channel, ntptr->IOsb.Status);

   status = sys$dassgn (ntptr->Channel);
   if (VMSnok (status)) ErrorNoticed (rqptr, status, NULL, FI_LI);
   VmFree (ntptr, FI_LI);
}

#endif /* NET_TEST */

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

