/*****************************************************************************/ /* 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 #include #include #include #include /* VMS related header files */ #include #include #include #include #include #include #include /* 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()"); if (gethostname (ServerHostName, sizeof(ServerHostName)) != 0) ErrorExitVmsStatus (vaxc$errno, "resolving server hostname", FI_LI); /* 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_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 [] = "

\n\ \n\ \n\
!&?\rInstance \rServices
\n\ \n\ \ \ \ \ \ \ \ \ \n"; static char OneServiceFao [] = "\ \ \ \ \ \ \ \ \ \n"; static char TotalFao [] = "\ \ \ \ \ \ \n\ \n\
IPHTTPCountbytes Rxerrbytes TxerrTraffic
!UL.!AZ//!AZ!&?v4\rv6\r!&?2+1\r1\r!&L!&,@SQ!&L!&,@SQ!&L!UL%
total:!&L!&,@SQ!&L!&,@SQ!&L
\ *counts are per-startup only
\n\
\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\ \n\ \n\ !AZ 503\n\ \n\ !AZ\n\ !AZ\n\ \n\ \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\ \n\ \n\ !AZ 403\n\ \n\ !AZ\n\ !AZ\n\ \n\ \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 */ /****************************************************************************/