Java程序辅导

C C++ Java Python Processing编程在线培训 程序编写 软件开发 视频讲解

客服在线QQ:2653320439 微信:ittutor Email:itutor@qq.com
wx: cjtutor
QQ: 2653320439
/*****************************************************************************/ /* 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). VERSION HISTORY --------------- 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, NetAcceptNoIoChan, NetConnectSuspend, NetInstancePassive, NetMultiHome; int ConnectCountTotal, NetAcceptBytLmRequired, NetAcceptExQuotaCount, NetAcceptNoIoChanCount, NetConcurrentMax, NetConcurrentProcessMax, NetCurrentConnected, NetCurrentProcessing, NetListenBytLmRequired, NetReadBufferSize, NetRequestMax, OutputBufferSize, OutputFileBufferSize, ServerHostNameLength; 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 ActivityCurrentConnected, ConfigDnsLookupRetryCount, EfnWait, EfnNoWait, InstanceNodeConfig, InstanceNodeCurrent, InstanceNumber, OpcomMessages, ServiceCount, ServerPort, TcpIpSendBufferSize, TcpIpReceiveBufferSize; 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; extern SERVICE_STRUCT *ServiceListHead, *ServiceListTail; 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 (NULL, FI_LI, 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 (NULL, FI_LI, WATCH_MOD_NET, "NetCreateService() !UL", ServiceCount); 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); for (svptr = ServiceListHead; svptr; svptr = svptr->NextPtr) { 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 (;;) { /* 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 */ for (tsvptr = ServiceListHead; tsvptr; tsvptr = tsvptr->NextPtr) 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 (!SesolaInitService (svptr)) { FaoToStdout ("-SERVICE-W-SSL, service not configured\n"); break; } for (tsvptr = ServiceListHead; tsvptr; tsvptr = tsvptr->NextPtr) { 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 (NULL, FI_LI, 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; /* queue the first accept */ NetAccept (svptr); } /* finished with service creation */ if (VMSnok (status = InstanceUnLock (INSTANCE_NODE_SOCKET))) ErrorExitVmsStatus (status, "InstanceUnLock()", FI_LI); if (WATCH_MODULE(WATCH_MOD_NET)) WatchThis (NULL, FI_LI, WATCH_MOD_NET, "NetMultiHome: !&B", NetMultiHome); return (SS$_NORMAL); } /*****************************************************************************/ /* 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 (NULL, FI_LI, WATCH_MOD_NET, "NetGetBgDevice()"); if (!DevName) { DevName = StaticDevName; SizeOfDevName = sizeof(StaticDevName); } 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 (NULL, FI_LI, 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 (NULL, FI_LI, 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 (NULL, FI_LI, WATCH_MOD_NET, "NetServiceZeroAccounting()"); for (svptr = ServiceListHead; svptr; svptr = svptr->NextPtr) { svptr->ConnectCount = svptr->ReadErrorCount = svptr->WriteErrorCount = 0; memset (&svptr->BytesRawRx, 0, 8); memset (&svptr->BytesRawTx, 0, 8); } } /*****************************************************************************/ /* Return a pointer to the name of the next service. Begin with 'ContextPtr' set to zero, then call successively until a NULL is returned. */ char* NetServiceNextHostPort (unsigned long *ContextPtr) { char *cptr; SERVICE_STRUCT *svptr; /*********/ /* begin */ /*********/ if (WATCH_MODULE(WATCH_MOD_NET)) WatchThis (NULL, FI_LI, WATCH_MOD_NET, "NetServiceNextHostPort()"); if (!*ContextPtr) *ContextPtr = (unsigned long)(svptr = ServiceListHead); else { svptr = (SERVICE_STRUCT*)*ContextPtr; *ContextPtr = (unsigned long)(svptr = svptr->NextPtr); } if ((cptr = (char*)svptr)) cptr = svptr->ServerHostPort; if (WATCH_MODULE(WATCH_MOD_NET)) WatchThis (NULL, FI_LI, WATCH_MOD_NET, "!AZ", cptr); return (cptr); } /*****************************************************************************/ /* Output service statistics (called from AdminReportServerStats()). */ int NetServiceReportStats (REQUEST_STRUCT *rqptr) { static char ServicesFao [] = " \n\ !&?\rInstance \rServices\n\ \n\ \n\ \   IP  \ Count\   bytes Rx\ err\   bytes Tx\ err\   Traffic\n"; static char OneServiceFao [] = " !UL. \ !AZ//!AZ\   !&?v4\rv6\r\ !&L\   !&,@SQ\  !&L\   !&,@SQ\  !&L\   !UL%\n"; static char TotalFao [] = " total:\ !&L\   !&,@SQ\  !&L\   !&,@SQ\  !&L\ \n\ \ *counts are per-startup only\n\ \n\ \n\ \n"; int status, ServiceListCount, ServiceTotalCount; unsigned short Length; unsigned long *vecptr; unsigned long NetReadErrorTotal, NetWriteErrorTotal; unsigned long FaoVector [32], BytesRawRx [2], BytesRawTx [2], BytesRxTx [2], BytesTotal [2]; SERVICE_STRUCT *svptr; /*********/ /* begin */ /*********/ if (WATCH_MODULE(WATCH_MOD_NET)) WatchThis (NULL, FI_LI, 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 */ for (svptr = ServiceListHead; svptr; svptr = svptr->NextPtr) { 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; for (svptr = ServiceListHead; svptr; svptr = svptr->NextPtr) { NetReadErrorTotal += svptr->ReadErrorCount; NetWriteErrorTotal += svptr->WriteErrorCount; vecptr = FaoVector; *vecptr++ = ServiceListCount++; *vecptr++ = svptr->RequestSchemeNamePtr; *vecptr++ = svptr->ServerHostPort; *vecptr++ = IPADDRESS_IS_V4(&svptr->ServerIpAddress); *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 (NULL, FI_LI, 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) { sys$cancel (rqeptr->rqClient.Channel); PurgeCount++; } else if (ConnectNumber && ConnectNumber == rqeptr->ConnectNumber) { sys$cancel (rqeptr->rqClient.Channel); PurgeCount++; } else if (RequestUri && RequestUri[0] && !StringMatch (NULL, rqeptr->rqHeader.RequestUriPtr, RequestUri)) { sys$cancel (rqeptr->rqClient.Channel); PurgeCount++; } else if (rqeptr->rqNet.ConnectionCount && QUAD_ZERO (rqeptr->BytesRx) && QUAD_ZERO (rqeptr->BytesTx)) { sys$cancel (rqeptr->rqClient.Channel); PurgeCount++; } } 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 (NULL, FI_LI, WATCH_MOD_NET, "NetSuspend()"); InstanceMutexLock (INSTANCE_MUTEX_HTTPD); HttpdGblSecPtr->ConnectSuspend = true; InstanceMutexUnLock (INSTANCE_MUTEX_HTTPD); if (NetConnectSuspend) return; NetConnectSuspend = true; for (svptr = ServiceListHead; svptr; svptr = svptr->NextPtr) 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->rqClient.Channel); else if (rqeptr->rqNet.ConnectionCount && QUAD_ZERO (rqeptr->BytesRx)) sys$cancel (rqeptr->rqClient.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 (NULL, FI_LI, WATCH_MOD_NET, "NetResume()"); InstanceMutexLock (INSTANCE_MUTEX_HTTPD); HttpdGblSecPtr->ConnectSuspend = false; InstanceMutexUnLock (INSTANCE_MUTEX_HTTPD); if (!NetConnectSuspend) return; NetConnectSuspend = false; for (svptr = ServiceListHead; svptr; svptr = svptr->NextPtr) 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 (NULL, FI_LI, 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; for (svptr = ServiceListHead; svptr; svptr = svptr->NextPtr) 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.ConnectionCount && QUAD_ZERO (rqeptr->BytesRx)) sys$cancel (rqeptr->rqClient.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 (NULL, FI_LI, 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; for (svptr = ServiceListHead; svptr; svptr = svptr->NextPtr) 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; for (svptr = ServiceListHead; svptr; svptr = svptr->NextPtr) NetAccept (svptr); FaoToStdout ("%HTTPD-I-NET, instance to active mode\n"); if (OpcomMessages) FaoToOpcom ("%HTTPD-I-NET, instance to active mode"); } } /*****************************************************************************/ /* When the server is active this function gets called once a second by HttpdTick(). If the sys$assign() in NetAccept() failed due to exhausted quota or IO channels exhaustion this function attempts to requeue that accept, until it succeeds or the server-wide attempt times-out. */ BOOL NetAcceptSupervisor () { int status; SERVICE_STRUCT *svptr; /*********/ /* begin */ /*********/ if (WATCH_MODULE(WATCH_MOD_NET) && WATCH_MODULE(WATCH_MOD__DETAIL)) WatchThis (NULL, FI_LI, WATCH_MOD_NET, "NetAcceptSupervisor()"); if (!NetAcceptExQuota && !NetAcceptNoIoChan) return (false); if (NetAcceptExQuota) if (!NetAcceptExQuotaCount++) ErrorNoticed (NULL, SS$_EXQUOTA, NULL, FI_LI); if (NetAcceptNoIoChan) if (!NetAcceptNoIoChanCount++) ErrorNoticed (NULL, SS$_NOIOCHAN, NULL, FI_LI); NetAcceptExQuota = NetAcceptNoIoChan = false; for (svptr = ServiceListHead; svptr; svptr = svptr->NextPtr) NetAccept (svptr); if (NetAcceptExQuota) { if (NetAcceptExQuotaCount > NET_ACCEPT_EXQUOTA_MAX) ErrorExitVmsStatus (SS$_BUGCHECK, "NetAcceptSupervisor() SS$_EXQUOTA", FI_LI); } else if (NetAcceptExQuotaCount) { NetAcceptExQuotaCount = 0; ErrorNoticed (NULL, SS$_NORMAL, "NetAcceptSupervisor() SS$_EXQUOTA", FI_LI); } if (NetAcceptNoIoChan) { if (NetAcceptNoIoChanCount > NET_ACCEPT_NOIOCHAN_MAX) ErrorExitVmsStatus (SS$_BUGCHECK, "NetAcceptSupervisor() SS$_NOIOCHAN", FI_LI); } else if (NetAcceptNoIoChanCount) { NetAcceptNoIoChanCount = 0; ErrorNoticed (NULL, SS$_NORMAL, "NetAcceptSupervisor() SS$_NOIOCHAN", FI_LI); } return (true); } /*****************************************************************************/ /* Queue an accept() to the listening server socket. */ NetAccept (SERVICE_STRUCT *svptr) { int status, BytLmAfter, BytLmBefore; unsigned short Channel; REQUEST_STRUCT *rqptr; VMS_ITEM_LIST3 *il3ptr; /*********/ /* begin */ /*********/ if (WATCH_MODULE(WATCH_MOD_NET)) WatchThis (NULL, FI_LI, 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 (); /* assign a channel to the internet template device */ status = sys$assign (&TcpIpDeviceDsc, &Channel, 0, 0); if (VMSnok (status)) { if (status == SS$_NOIOCHAN) { /* shouldn't have exhausted these, but seem to have, retry shortly */ NetAcceptNoIoChan = true; return; } if (status == SS$_EXQUOTA) { /* hmmm, probably BYTLM exhausted, retry shortly */ NetAcceptExQuota = true; return; } /* some other (serious) error */ ErrorExitVmsStatus (status, "sys$assign()", FI_LI); } /* allocate zeroed dynamic memory for the connection thread */ rqptr = VmGetRequest (); rqptr->ServicePtr = svptr; rqptr->rqClient.Channel = Channel; if (IPADDRESS_IS_V4 (&svptr->ServerIpAddress)) { SOCKADDRESS_ZERO4 (&rqptr->rqClient.SocketName) il3ptr = &rqptr->rqClient.SocketNameItem; il3ptr->buf_len = sizeof(SOCKADDRIN); il3ptr->item = 0; il3ptr->buf_addr = &rqptr->rqClient.SocketName.sa.v4; il3ptr->ret_len = &rqptr->rqClient.SocketNameLength; } else if (IPADDRESS_IS_V6 (&svptr->ServerIpAddress)) { SOCKADDRESS_ZERO6 (&rqptr->rqClient.SocketName) il3ptr = &rqptr->rqClient.SocketNameItem; il3ptr->buf_len = sizeof(SOCKADDRIN6); il3ptr->item = 0; il3ptr->buf_addr = &rqptr->rqClient.SocketName.sa.v6; il3ptr->ret_len = &rqptr->rqClient.SocketNameLength; } else ErrorExitVmsStatus (SS$_BUGCHECK, ErrorSanityCheck, FI_LI); status = sys$qio (EfnNoWait, svptr->ServerChannel, IO$_ACCESS | IO$M_ACCEPT, &rqptr->rqClient.IOsb, &NetAcceptAst, rqptr, 0, 0, &rqptr->rqClient.SocketNameItem, &rqptr->rqClient.Channel, &TcpIpFullDuplexCloseOption, 0); if (VMSnok (status)) ErrorExitVmsStatus (status, NULL, FI_LI); svptr->AcceptQueued++; if (!NetAcceptBytLmRequired) { BytLmAfter = GetJpiBytLm (); NetAcceptBytLmRequired = BytLmBefore - BytLmAfter; } if (WATCH_CATEGORY(WATCH_NETWORK)) WatchThis (NULL, FI_LI, 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 (REQUEST_STRUCT *rqptr) { int status; SERVICE_STRUCT *svptr; /*********/ /* begin */ /*********/ if (WATCH_MODULE(WATCH_MOD_NET)) WatchThis (NULL, FI_LI, WATCH_MOD_NET, "NetAcceptAst() !&F !UL !&S !&X", &NetAcceptAst, rqptr->ServicePtr->AcceptQueued, rqptr->rqClient.IOsb.Status, rqptr->rqClient.IOsb.Unused); svptr = rqptr->ServicePtr; if (svptr->AcceptQueued) svptr->AcceptQueued--; if (VMSnok (status = rqptr->rqClient.IOsb.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 (rqptr, status, NULL, FI_LI); /* deassign socket channel, deallocate request structure */ sys$dassgn (rqptr->rqClient.Channel); VmFreeRequest (rqptr, FI_LI); /* 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 (rqptr->rqClient.Channel); NetAccept (svptr); return; #endif /* NET_TEST */ if (Config.cfScript.GatewayBg) { if (rqptr->ServicePtr->RequestScheme == SCHEME_HTTP) { /* make the socket shareable */ status = sys$qiow (EfnWait, rqptr->rqClient.Channel, IO$_SETMODE, &rqptr->rqClient.IOsb, 0, 0, 0, 0, 0, 0, &TcpIpSocketShareOption, 0); if (VMSok (status)) status = rqptr->rqClient.IOsb.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); rqptr->rqClient.IpPort = ntohs(rqptr->rqClient.SocketName.sa.v4.SIN$W_PORT); IPADDRESS_GET4 (&rqptr->rqClient.IpAddress, rqptr->rqClient.SocketName.sa.v4.SIN$L_ADDR); strcpy (rqptr->rqClient.IpAddressString, TcpIpAddressToString (&rqptr->rqClient.IpAddress, 0)); } else if (IPADDRESS_IS_V6 (&svptr->ServerIpAddress)) { InstanceGblSecIncrLong (&AccountingPtr->ConnectIpv6Count); rqptr->rqClient.IpPort = ntohs(rqptr->rqClient.SocketName.sa.v6.SIN6$W_PORT); IPADDRESS_GET6 (&rqptr->rqClient.IpAddress, rqptr->rqClient.SocketName.sa.v6.SIN6$R_ADDR_OVERLAY.SIN6$T_ADDR) strcpy (rqptr->rqClient.IpAddressString, TcpIpAddressToString (&rqptr->rqClient.IpAddress, 0)); } else ErrorExitVmsStatus (SS$_BUGCHECK, ErrorSanityCheck, FI_LI); /* meanwhile queue another accept using a new request structure */ NetAccept (svptr); if (Config.cfMisc.DnsLookupClient) { if (WATCH_CATEGORY(WATCH_NETWORK)) WatchThis (NULL, FI_LI, WATCH_NETWORK, "RESOLVE !&I", &rqptr->rqClient.IpAddress); /* asynchronous DNS lookup */ TcpIpAddressToName (&rqptr->rqClient.Lookup, &rqptr->rqClient.IpAddress, ConfigDnsLookupRetryCount, &NetAcceptProcess, rqptr); } else { /* not using client DNS lookup, carry on synchronously */ NetAcceptProcess (rqptr); } } /*****************************************************************************/ /* 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 ( REQUEST_STRUCT *rqptr, int CclOption ) { int status; IO_SB IOsb; /*********/ /* begin */ /*********/ if (WATCH_MODULE(WATCH_MOD_NET)) WatchThis (rqptr, FI_LI, WATCH_MOD_NET, "NetClientSocketCcl() !SL", CclOption); if (rqptr->ServicePtr->RequestScheme == SCHEME_HTTP) { if (rqptr->rqClient.Channel) { if (CclOption == 1) status = sys$qiow (EfnWait, rqptr->rqClient.Channel, IO$_SETMODE, &IOsb, 0, 0, 0, 0, 0, 0, &TcpIpSocketCclOptionOn, 0); else if (CclOption == 0) status = sys$qiow (EfnWait, rqptr->rqClient.Channel, IO$_SETMODE, &IOsb, 0, 0, 0, 0, 0, 0, &TcpIpSocketCclOptionOff, 0); else status = SS$_BADPARAM; if (VMSok (status)) status = IOsb.Status; } else status = SS$_BADPARAM; } else status = SS$_BADPARAM; if (WATCH_CATEGORY(WATCH_NETWORK)) WatchThis (rqptr, FI_LI, WATCH_NETWORK, "SOCKET CCL !SL !&S", CclOption, status); return (status); } /*****************************************************************************/ /* 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 (REQUEST_STRUCT *rqptr) { int idx, status, SocketNameLength, ServerSocketNameLength; char ServerIpAddressString [32]; 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 (NULL, FI_LI, WATCH_MOD_NET, "NetAcceptProcess() !&F !&S !UL", &NetAcceptProcess, rqptr->rqClient.IOsb.Status, rqptr->rqClient.Lookup.HostNameLength); svptr = rqptr->ServicePtr; if (VMSnok (rqptr->rqClient.Lookup.LookupIOsb.Status)) { /* lookup not done or failed, substitute the IP address for the name */ strcpy (rqptr->rqClient.Lookup.HostName, rqptr->rqClient.IpAddressString); rqptr->rqClient.Lookup.HostNameLength = strlen(rqptr->rqClient.Lookup.HostName); } if (WATCH_MODULE(WATCH_MOD_NET)) WatchDataFormatted ("!&Z !&Z\n", rqptr->rqClient.IpAddressString, rqptr->rqClient.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, rqptr->rqClient.Channel, IO$_SENSEMODE, &IOsb, 0, 0, 0, 0, &SocketNameItem, 0, 0, 0); if (WATCH_MODULE(WATCH_MOD_NET)) WatchThis (NULL, FI_LI, 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 */ if (status != SS$_CONNECFAIL) ErrorNoticed (rqptr, status, NULL, FI_LI); /* deassign socket channel, deallocate request structure */ sys$dassgn (rqptr->rqClient.Channel); VmFreeRequest (rqptr, FI_LI); return; } if (IPADDRESS_IS_V4 (&svptr->ServerIpAddress)) IPADDRESS_GET4 (&rqptr->rqClient.MultiHomeIpAddress, SocketName.sa.v4.SIN$L_ADDR) else if (IPADDRESS_IS_V6 (&svptr->ServerIpAddress)) IPADDRESS_GET6 (&rqptr->rqClient.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 (NULL, FI_LI, WATCH_MOD_NET, "!&I,!UL !&I,!UL", &rqptr->rqClient.MultiHomeIpAddress, svptr->ServerPort, &svptr->MultiHomeIpAddress, svptr->ServerPort); /* first check against the service it arrived at (port is implicit) */ asvptr = svptr; if (IPADDRESS_IS_SAME(&rqptr->rqClient.MultiHomeIpAddress, &asvptr->MultiHomeIpAddress)) tsvptr = asvptr; else /* then if necessary scan through the list of services */ for (tsvptr = ServiceListHead; tsvptr; tsvptr = tsvptr->NextPtr) { if (WATCH_CATEGORY(WATCH_NETWORK)) WatchThis (NULL, FI_LI, WATCH_NETWORK, "MULTIHOME !&I,!UL !&I,!UL", &rqptr->rqClient.MultiHomeIpAddress, asvptr->ServerPort, &tsvptr->MultiHomeIpAddress, tsvptr->ServerPort); if (IPADDRESS_IS_SAME(&rqptr->rqClient.MultiHomeIpAddress, &tsvptr->MultiHomeIpAddress) && (asvptr->ServerPort == tsvptr->ServerPort || asvptr->ServerPort == tsvptr->AdminPort)) break; } if (tsvptr) { /* matched, change to the resolved (multihomed) service */ rqptr->ServicePtr = svptr = tsvptr; } else { /* the presence of this value indicates a multi-home mismatch */ strcpy (rqptr->rqClient.MultiHomeIpAddressString, TcpIpAddressToString(&rqptr->rqClient.MultiHomeIpAddress,0)); } } else tsvptr = NULL; if (WATCH_MODULE(WATCH_MOD_NET)) WatchThis (NULL, FI_LI, WATCH_MOD_NET, "!AZ !AZ:!UL", svptr->ServerIpAddressString, svptr->ServerHostName, svptr->ServerPort); if (Watch.Category && Watch.Category != WATCH_ONE_SHOT_CAT) WatchFilterClientService (rqptr); if (NetCurrentConnected > NetConcurrentMax) { /************/ /* too busy */ /************/ if (rqptr->WatchItem && WATCH_CATEGORY(WATCH_CONNECT)) WatchThis (NULL, FI_LI, WATCH_CONNECT, "ACCEPTED !UL too-busy !AZ,!UL on !AZ//!AZ,!AZ !AZ", NetConcurrentMax, rqptr->rqClient.Lookup.HostName, rqptr->rqClient.IpPort, svptr->RequestSchemeNamePtr, svptr->ServerIpAddressString, svptr->ServerPortString, NetGetBgDevice(rqptr->rqClient.Channel, NULL, 0)); svptr->ConnectCount++; InstanceMutexLock (INSTANCE_MUTEX_HTTPD); AccountingPtr->ConnectTooBusyCount++; AccountingPtr->ResponseStatusCodeGroup[5]++; AccountingPtr->ResponseStatusCodeCount[RequestHttpStatusIndex(503)]++; InstanceMutexUnLock (INSTANCE_MUTEX_HTTPD); NetDirectResponse (rqptr, MSG_GENERAL_TOO_BUSY); return; } if (!ConfigAcceptClientHostName (rqptr->rqClient.IpAddressString, rqptr->rqClient.Lookup.HostName)) { /************/ /* rejected */ /************/ if (rqptr->WatchItem && WATCH_CATEGORY(WATCH_CONNECT)) WatchThis (NULL, FI_LI, WATCH_CONNECT, "ACCEPTED reject !AZ,!UL on !AZ//!AZ,!AZ !AZ", rqptr->rqClient.Lookup.HostName, rqptr->rqClient.IpPort, svptr->RequestSchemeNamePtr, svptr->ServerIpAddressString, svptr->ServerPortString, NetGetBgDevice(rqptr->rqClient.Channel, NULL, 0)); svptr->ConnectCount++; InstanceMutexLock (INSTANCE_MUTEX_HTTPD); AccountingPtr->ConnectRejectedCount++; AccountingPtr->ResponseStatusCodeGroup[4]++; AccountingPtr->ResponseStatusCodeCount[RequestHttpStatusIndex(403)]++; InstanceMutexUnLock (INSTANCE_MUTEX_HTTPD); NetDirectResponse (rqptr, MSG_GENERAL_ACCESS_DENIED); return; } InstanceGblSecIncrLong (&AccountingPtr->ConnectAcceptedCount); /***************************/ /* process HTTP connection */ /***************************/ /* we have a client! */ NetCurrentConnected++; rqptr->ConnectNumber = ++ConnectCountTotal; InstanceMutexLock (INSTANCE_MUTEX_HTTPD); AccountingPtr->CurrentConnected[InstanceNumber]++; for (ActivityCurrentConnected = idx = 0; idx <= InstanceNodeCurrent; idx++) ActivityCurrentConnected += AccountingPtr->CurrentConnected[idx]; if (ActivityCurrentConnected > AccountingPtr->ConnectPeak) AccountingPtr->ConnectPeak = ActivityCurrentConnected; InstanceMutexUnLock (INSTANCE_MUTEX_HTTPD); if (rqptr->WatchItem) { if WATCH_CATEGORY(WATCH_CONNECT) { if (NetMultiHome) WatchThis (rqptr, FI_LI, WATCH_CONNECT, "MULTIHOME !&?match\rno match\r for !&I,!UL arrived at !&I,!UL", tsvptr, &rqptr->rqClient.MultiHomeIpAddress, svptr->ServerPort, &asvptr->ServerIpAddress, asvptr->ServerPort); WatchThis (rqptr, FI_LI, WATCH_CONNECT, "ACCEPTED !AZ,!UL on !AZ//!&I,!UL !AZ", rqptr->rqClient.Lookup.HostName, rqptr->rqClient.IpPort, svptr->RequestSchemeNamePtr, &svptr->ServerIpAddress, svptr->ServerPort, NetGetBgDevice(rqptr->rqClient.Channel, NULL, 0)); } } TcpIpSocketBufferSize (rqptr, rqptr->rqClient.Channel, -1, -1); /* begin processing this request */ RequestBegin (rqptr); } /*****************************************************************************/ /* '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 (NULL, FI_LI, 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 (NULL, FI_LI, 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 (NULL, FI_LI, 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 (NULL, FI_LI, WATCH_MOD_NET, "!AZ", IpAddressStringPtr); } if (IpAddressPtr) IPADDRESS_COPY (IpAddressPtr, &IpAddress) if (IpPortPtr) *IpPortPtr = PortNumber; return (status); } /****************************************************************************/ /* Just close the socket, bang! */ int NetCloseSocket (REQUEST_STRUCT *rqptr) { int status; /*********/ /* begin */ /*********/ if (WATCHING(rqptr) && WATCH_MODULE(WATCH_MOD_NET)) WatchThis (rqptr, FI_LI, WATCH_MOD_NET, "NetCloseSocket()"); if (!rqptr->rqClient.Channel) return (SS$_NORMAL); status = sys$dassgn (rqptr->rqClient.Channel); rqptr->rqClient.Channel = 0; if (rqptr->rqNet.SesolaPtr) SesolaNetSocketHasBeenClosed (rqptr->rqNet.SesolaPtr); /* if necessary reestablish WATCHing to note final close */ if (WATCH_CATEGORY(WATCH_CONNECT)) { WatchFilterClientService (rqptr); if (WATCHING(rqptr)) { if (VMSok(status)) WatchThis (rqptr, FI_LI, WATCH_CONNECT, "CLOSE !AZ,!UL", rqptr->rqClient.Lookup.HostName, rqptr->rqClient.IpPort); else WatchThis (rqptr, FI_LI, WATCH_CONNECT, "CLOSE !AZ,!UL !&S", rqptr->rqClient.Lookup.HostName, rqptr->rqClient.IpPort, status); } } return (status); } /****************************************************************************/ /* Stop the server from receiving incoming requests. */ NetShutdownServerSocket () { SERVICE_STRUCT *svptr; /*********/ /* begin */ /*********/ if (WATCH_MODULE(WATCH_MOD_NET)) WatchThis (NULL, FI_LI, WATCH_MOD_NET, "NetShutdownServerSocket()"); for (svptr = ServiceListHead; svptr; svptr = svptr->NextPtr) { 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 (WATCHING(rqptr) && WATCH_MODULE(WATCH_MOD_NET)) WatchThis (rqptr, FI_LI, WATCH_MOD_NET, "NetResponseHeaderAst() !&F !&X !&X !UL", &NetResponseHeaderAst, rqptr->rqResponse.HeaderAstFunction, rqptr->rqResponse.HeaderDataPtr, rqptr->rqResponse.HeaderDataLength); NetWrite (rqptr, rqptr->rqResponse.HeaderAstFunction, rqptr->rqResponse.HeaderDataPtr, rqptr->rqResponse.HeaderDataLength); } /*****************************************************************************/ /* 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, nlcnt, status, ResponseHeaderLength; BOOL WritingProxyResponseHeader; char *cptr, *sptr, *ResponseHeaderPtr; /*********/ /* begin */ /*********/ if (WATCHING(rqptr) && WATCH_MODULE(WATCH_MOD_NET)) WatchThis (rqptr, FI_LI, WATCH_MOD_NET, "NetWrite() !&A !&X !UL !&X !UL !&B !UL", AstFunction, DataPtr, DataLength, rqptr->rqResponse.HeaderPtr, rqptr->rqResponse.HeaderLength, rqptr->rqResponse.HeaderSent, rqptr->rqResponse.HeaderNewlineCount); 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, rqptr->rqResponse.HeaderPtr, rqptr->rqResponse.HeaderLength); } if (rqptr->rqResponse.HeaderPtr && !rqptr->rqResponse.HeaderSent) { /*******************************************/ /* nothing of response sent to client yet! */ /*******************************************/ rqptr->rqResponse.HeaderSent = true; if (rqptr->rqResponse.HeaderPtr && rqptr->rqPathSet.ResponseHeaderNone && rqptr->rqResponse.HttpStatus / 100 == 2) { /*********************/ /* header suppressed */ /*********************/ if (WATCHING(rqptr) && WATCH_CATEGORY(WATCH_RESPONSE_HEADER)) WatchThis (rqptr, FI_LI, WATCH_RESPONSE_HEADER, "HEADER !UL bytes (NONE)", rqptr->rqResponse.HeaderLength); } else if (rqptr->rqResponse.HeaderPtr && rqptr->rqResponse.HttpVersion != HTTP_VERSION_0_9) { /************************/ /* send response header */ /************************/ if (!rqptr->rqResponse.HeaderLength) rqptr->rqResponse.HeaderLength = strlen(rqptr->rqResponse.HeaderPtr); /* only need header via the network if it's not already generated */ if (rqptr->rqCache.LoadFromNet && !rqptr->rqResponse.ContentTypePtr) CacheLoadData (rqptr, rqptr->rqResponse.HeaderPtr, rqptr->rqResponse.HeaderLength); ResponseHeaderLength = rqptr->rqResponse.HeaderLength; if (rqptr->rqPathSet.ResponseHeaderBegin && rqptr->rqResponse.HttpStatus / 100 == 2) { cptr = (sptr = rqptr->rqResponse.HeaderPtr) + ResponseHeaderLength; if (cptr > sptr && cptr[-1] == '\n') { cptr--; if (cptr > sptr && cptr[-1] == '\r') cptr--; } ResponseHeaderLength = cptr - sptr; cptr = " (BEGIN)"; } else cptr = ""; if (WATCHING(rqptr) && WATCH_CATEGORY(WATCH_RESPONSE_HEADER) && rqptr != Watch.RequestPtr) { WatchThis (rqptr, FI_LI, WATCH_RESPONSE_HEADER, "HEADER !UL bytes!AZ", ResponseHeaderLength, cptr); WatchData (rqptr->rqResponse.HeaderPtr, ResponseHeaderLength); } ADD_LONG_QUAD (ResponseHeaderLength, rqptr->BytesTx); if (AstFunction) { /************************/ /* AST routine, no wait */ /************************/ if (DataLength) { rqptr->rqResponse.HeaderAstFunction = AstFunction; rqptr->rqResponse.HeaderDataPtr = DataPtr; rqptr->rqResponse.HeaderDataLength = DataLength; AstFunction = &NetResponseHeaderAst; } if (rqptr->rqNet.SesolaPtr) status = SesolaNetWrite (rqptr->rqNet.SesolaPtr, AstFunction, rqptr->rqResponse.HeaderPtr, ResponseHeaderLength); else status = NetWriteRaw (rqptr, AstFunction, rqptr->rqResponse.HeaderPtr, ResponseHeaderLength); /* return, the AST function will sys$qiow() the data */ return (status); } else { /************************/ /* no AST routine, wait */ /************************/ if (rqptr->rqNet.SesolaPtr) status = SesolaNetWrite (rqptr->rqNet.SesolaPtr, 0, rqptr->rqResponse.HeaderPtr, ResponseHeaderLength); else status = NetWriteRaw (rqptr, 0, rqptr->rqResponse.HeaderPtr, ResponseHeaderLength); /* continue on to sys$qiow() 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.HeaderPtr && 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 */ rqptr->rqNet.WriteIOsb.Status = STS$K_SUCCESS; rqptr->rqNet.WriteIOsb.Count = 0; if (AstFunction) SysDclAst (AstFunction, rqptr); return (SS$_NORMAL); } } } else { /* HTTP header has been completely sent, absorb anything else */ rqptr->rqNet.WriteIOsb.Status = STS$K_SUCCESS; rqptr->rqNet.WriteIOsb.Count = 0; if (AstFunction) SysDclAst (AstFunction, rqptr); return (SS$_NORMAL); } if (WATCHING(rqptr) && WATCH_MODULE(WATCH_MOD_NET)) WatchThis (rqptr, FI_LI, WATCH_MOD_NET, "HEAD !&X !UL", DataPtr, DataLength); } if (rqptr->rqResponse.HttpVersion == HTTP_VERSION_0_9 && !rqptr->rqResponse.HeaderPtr && 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 */ rqptr->rqNet.WriteIOsb.Status = STS$K_SUCCESS; rqptr->rqNet.WriteIOsb.Count = 0; if (AstFunction) SysDclAst (AstFunction, rqptr); return (SS$_NORMAL); } /* HTTP header has been completely absorbed, send everything else */ if (WATCHING(rqptr) && WATCH_MODULE(WATCH_MOD_NET)) WatchThis (rqptr, FI_LI, WATCH_MOD_NET, "HTTP/0.9 !&X !UL", DataPtr, DataLength); } /*************/ /* send data */ /*************/ if (WATCHING(rqptr) && WATCH_CATEGORY(WATCH_RESPONSE_BODY)) { if (rqptr->rqResponse.HeaderPtr) WatchThis (rqptr, FI_LI, WATCH_RESPONSE_BODY, "BODY !UL bytes", DataLength); else WatchThis (rqptr, FI_LI, 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", DataPtr); else WatchDataFormatted ("!AZ\n", 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 */ rqptr->rqNet.WriteIOsb.Status = status; rqptr->rqNet.WriteIOsb.Count = 0; if (!AstFunction) return (status); SysDclAst (AstFunction, rqptr); return (status); } } } else if (DataPtr) { /****************/ /* "empty" data */ /****************/ /* without a real network write just fudge the status */ rqptr->rqNet.WriteIOsb.Status = status = SS$_NORMAL; rqptr->rqNet.WriteIOsb.Count = 0; if (AstFunction) SysDclAst (AstFunction, rqptr); return (status); } 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, DataPtr, DataLength); } else if (rqptr->rqResponse.TransferEncodingChunked) { /* writes the end-of-content empty chunk */ status = NetWriteChunked (rqptr, AstFunction, DataPtr, DataLength); } else { /* without a real network write just fudge the status */ rqptr->rqNet.WriteIOsb.Status = status = SS$_NORMAL; rqptr->rqNet.WriteIOsb.Count = 0; if (AstFunction) SysDclAst (AstFunction, rqptr); } 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 (rqptr->rqNet.SesolaPtr) status = SesolaNetWrite (rqptr->rqNet.SesolaPtr, AstFunction, DataPtr, DataLength); else status = NetWriteRaw (rqptr, AstFunction, 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; /*********/ /* begin */ /*********/ if (WATCHING(rqptr) && WATCH_MODULE(WATCH_MOD_NET)) WatchThis (rqptr, FI_LI, WATCH_MOD_NET, "NetWriteGzip() !&X !UL !&A", DataPtr, DataLength, AstFunction); gzptr = &rqptr->GzipCompress; 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 if (rqptr->rqNet.SesolaPtr) status = SesolaNetWrite (rqptr->rqNet.SesolaPtr, AstFunction, OutDataPtr, OutDataLength); else status = NetWriteRaw (rqptr, AstFunction, OutDataPtr, OutDataLength); if (AstFunction) return (status); } else if (!DataLength) { /* nothing still to input, nothing to write, fudge it */ rqptr->rqNet.WriteIOsb.Status = SS$_NORMAL; rqptr->rqNet.WriteIOsb.Count = 0; if (AstFunction) SysDclAst (AstFunction, rqptr); 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 */ rqptr->rqNet.WriteIOsb.Status = SS$_ABORT; rqptr->rqNet.WriteIOsb.Count = 0; if (AstFunction) SysDclAst (AstFunction, rqptr); 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; REQUEST_AST AstFunction; /*********/ /* begin */ /*********/ if (WATCHING(rqptr) && WATCH_MODULE(WATCH_MOD_NET)) WatchThis (rqptr, FI_LI, WATCH_MOD_NET, "NetWriteGzipAst() !&F !&S !&X !UL !&A", NetWriteGzipAst, rqptr->rqNet.WriteIOsb.Status, 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 (VMSnok (rqptr->rqNet.WriteIOsb.Status)) { SysDclAst (AstFunction, rqptr); return (rqptr->rqNet.WriteIOsb.Status); } NetWriteGzip (rqptr, AstFunction, DataPtr, DataLength); } /*****************************************************************************/ /* 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 non-encrypted case NetWriteRawP5() is used to write (up to) three separate buffers of data. The first is the hex string providing the data length, then second is the data itself, and the third is the trailing carriage control required by the chunked format. In the case of the end-of-content empty chunk, only two buffers are written, the zero hex string and the trailing carriage control. 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; VMS_ITEM_LIST2 *p5ptr; /*********/ /* begin */ /*********/ if (WATCHING(rqptr) && WATCH_MODULE(WATCH_MOD_NET)) WatchThis (rqptr, FI_LI, WATCH_MOD_NET, "NetWriteChunked()"); if (DataLength & 0xffff0000) { /* maximum of 65kB */ ErrorNoticed (rqptr, SS$_BUGCHECK, ErrorSanityCheck, FI_LI); rqptr->rqNet.WriteIOsb.Status = SS$_BUGCHECK; rqptr->rqNet.WriteIOsb.Count = 0; if (AstFunction) SysDclAst (AstFunction, rqptr); return (SS$_BUGCHECK); } if (WATCHING(rqptr) && WATCH_CATEGORY(WATCH_NETWORK_OCTETS)) WatchThis (rqptr, FI_LI, 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->rqNet.SesolaPtr) { 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; } else cptr = sptr = rqptr->rqResponse.ChunkedSizeString; /* 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 (rqptr->rqNet.SesolaPtr) { if (DataLength) { memcpy (cptr, DataPtr, DataLength); cptr += DataLength; } *cptr++ = '\r'; *cptr++ = '\n'; DataPtr = rqptr->rqResponse.ChunkedBufferPtr; DataLength = cptr - rqptr->rqResponse.ChunkedBufferPtr; status = SesolaNetWrite (rqptr->rqNet.SesolaPtr, AstFunction, DataPtr, DataLength); } else { p5ptr = &rqptr->rqResponse.ChunkedP5[0]; if (DataLength) p5ptr->buf_len = sizeof(VMS_ITEM_LIST2) * 3; else p5ptr->buf_len = sizeof(VMS_ITEM_LIST2) * 2; p5ptr->buf_addr = &rqptr->rqResponse.ChunkedP5[1]; p5ptr++; p5ptr->buf_len = cptr - sptr; p5ptr->buf_addr = rqptr->rqResponse.ChunkedSizeString; if (DataLength) { p5ptr++; p5ptr->buf_len = DataLength; p5ptr->buf_addr = DataPtr; } p5ptr++; p5ptr->buf_len = 2; p5ptr->buf_addr = "\r\n"; status = NetWriteRawP5 (rqptr, AstFunction, &rqptr->rqResponse.ChunkedP5); } return (status); } /*****************************************************************************/ /* Write data to the network. Explicitly declares any AST routine if an error occurs. The calling function must not do any error recovery if an AST routine has been supplied but the associated AST routine must! If an AST was not supplied then the return status can be checked. AST to NetWriteRaw() which calls the supplied AST function. */ int NetWriteRaw ( REQUEST_STRUCT *rqptr, REQUEST_AST AstFunction, char *DataPtr, int DataLength ) { int status; /*********/ /* begin */ /*********/ if (WATCHING(rqptr) && WATCH_MODULE(WATCH_MOD_NET)) WatchThis (rqptr, FI_LI, WATCH_MOD_NET, "NetWriteRaw() !&A !&X !UL", AstFunction, DataPtr, DataLength); if (rqptr->rqNet.WriteRawAstFunction || !DataLength) { rqptr->rqNet.WriteIOsb.Status = SS$_BUGCHECK; rqptr->rqNet.WriteIOsb.Count = 0; if (AstFunction) SysDclAst (AstFunction, rqptr); return (rqptr->rqNet.WriteIOsb.Status); } rqptr->rqNet.WriteRawAstFunction = AstFunction; rqptr->rqNet.WriteRawDataPtr = DataPtr; rqptr->rqNet.WriteRawDataLength = DataLength; if (WATCHING(rqptr) && WATCH_CATEGORY(WATCH_NETWORK_OCTETS)) { WatchThis (rqptr, FI_LI, WATCH_NETWORK, "WRITE !UL bytes (!&?non-blocking\rblocking\r)", DataLength, AstFunction); WatchDataDump (DataPtr, DataLength); } if (AstFunction) { /*******************/ /* non-blocking IO */ /*******************/ status = sys$qio (EfnNoWait, rqptr->rqClient.Channel, IO$_WRITEVBLK, &rqptr->rqNet.WriteIOsb, &NetWriteRawAst, rqptr, DataPtr, DataLength, 0, 0, 0, 0); } else { /***************/ /* blocking IO */ /***************/ status = sys$qiow (EfnWait, rqptr->rqClient.Channel, IO$_WRITEVBLK, &rqptr->rqNet.WriteIOsb, 0, 0, DataPtr, DataLength, 0, 0, 0, 0); if (VMSok (status)) status = rqptr->rqNet.WriteIOsb.Status; } /****************/ /* check status */ /****************/ if (VMSok (status)) return (status); /* if resource wait enabled the only quota not waited for is ASTLM */ if (status == SS$_EXQUOTA) { /* no ASTs means not much of anything else can happen so just exit! */ sys$canexh(0); /* make the message a little more meaningful */ sys$exit (SS$_EXASTLM); } /* IVCHAN sometimes occurs if the socket has been closed (ignore) */ if (status != SS$_IVCHAN) ErrorNoticed (rqptr, status, "sys$qio", FI_LI); /* write failed, call AST explicitly, status in the IOsb */ rqptr->rqNet.WriteIOsb.Status = status; rqptr->rqNet.WriteIOsb.Count = 0; if (NetWriteRawAst) SysDclAst (NetWriteRawAst, rqptr); return (status); } /*****************************************************************************/ /* Same as NetWriteRaw() but uses a buffer list. Only used by NetWriteChunked(). */ int NetWriteRawP5 ( REQUEST_STRUCT *rqptr, REQUEST_AST AstFunction, VMS_ITEM_LIST2 *P5DscPtr ) { int status; /*********/ /* begin */ /*********/ if (WATCHING(rqptr) && WATCH_MODULE(WATCH_MOD_NET)) WatchThis (rqptr, FI_LI, WATCH_MOD_NET, "NetWriteRawP5() !&A", AstFunction); if (rqptr->rqNet.WriteRawAstFunction) { rqptr->rqNet.WriteIOsb.Status = SS$_BUGCHECK; rqptr->rqNet.WriteIOsb.Count = 0; if (AstFunction) SysDclAst (AstFunction, rqptr); return (rqptr->rqNet.WriteIOsb.Status); } rqptr->rqNet.WriteRawAstFunction = AstFunction; if (WATCHING(rqptr) && WATCH_CATEGORY(WATCH_NETWORK_OCTETS)) { int cnt, DataLength, BufferCount; VMS_ITEM_LIST2 *p5ptr; BufferCount = P5DscPtr->buf_len / sizeof(VMS_ITEM_LIST2); DataLength = 0; p5ptr = P5DscPtr; for (cnt = BufferCount; cnt; cnt--) { p5ptr++; DataLength += p5ptr->buf_len; } WatchThis (rqptr, FI_LI, WATCH_NETWORK, "WRITE (P5=!UL) !UL bytes (!&?non-blocking\rblocking\r)", BufferCount, DataLength, AstFunction); p5ptr = P5DscPtr; for (cnt = BufferCount; cnt; cnt--) { p5ptr++; if (p5ptr->buf_len) WatchDataDump (p5ptr->buf_addr, p5ptr->buf_len); } } if (AstFunction) { /*******************/ /* non-blocking IO */ /*******************/ status = sys$qio (EfnNoWait, rqptr->rqClient.Channel, IO$_WRITEVBLK, &rqptr->rqNet.WriteIOsb, &NetWriteRawAst, rqptr, 0, 0, 0, 0, P5DscPtr, 0); } else { /***************/ /* blocking IO */ /***************/ status = sys$qiow (EfnWait, rqptr->rqClient.Channel, IO$_WRITEVBLK, &rqptr->rqNet.WriteIOsb, 0, 0, 0, 0, 0, 0, P5DscPtr, 0); if (VMSok (status)) status = rqptr->rqNet.WriteIOsb.Status; } /****************/ /* check status */ /****************/ if (VMSok (status)) return (status); /* if resource wait enabled the only quota not waited for is ASTLM */ if (status == SS$_EXQUOTA) { /* no ASTs means not much of anything else can happen so just exit! */ sys$canexh(0); /* make the message a little more meaningful */ sys$exit (SS$_EXASTLM); } /* IVCHAN sometimes occurs if the socket has been closed (normal) */ if (status != SS$_IVCHAN) ErrorNoticed (rqptr, status, "sys$qio", FI_LI); /* write failed, call AST explicitly, status in the IOsb */ rqptr->rqNet.WriteIOsb.Status = status; rqptr->rqNet.WriteIOsb.Count = 0; if (NetWriteRawAst) SysDclAst (NetWriteRawAst, rqptr); return (status); } /*****************************************************************************/ /* AST from NetWriteRaw(). Call the AST function. */ NetWriteRawAst (REQUEST_STRUCT *rqptr) { int status; REQUEST_AST AstFunction; /*********/ /* begin */ /*********/ if (WATCHING(rqptr) && WATCH_MODULE(WATCH_MOD_NET)) WatchThis (rqptr, FI_LI, WATCH_MOD_NET, "NetWriteRawAst() !&F !&S !UL", &NetWriteRawAst, rqptr->rqNet.WriteIOsb.Status, rqptr->rqNet.WriteIOsb.Count); if (WATCHING(rqptr)) { if (WATCH_CATEGORY(WATCH_NETWORK)) WatchThis (rqptr, FI_LI, WATCH_NETWORK, "WRITE !&S !UL bytes (!&?non-blocking\rblocking\r)", rqptr->rqNet.WriteIOsb.Status, rqptr->rqNet.WriteIOsb.Count, rqptr->rqNet.WriteRawAstFunction); if WATCH_CATEGORY(WATCH_RESPONSE) if (VMSnok (rqptr->rqNet.WriteIOsb.Status)) WatchThis (rqptr, FI_LI, WATCH_RESPONSE, "NETWORK !&S (!&?non-blocking\rblocking\r)", rqptr->rqNet.WriteIOsb.Status, rqptr->rqNet.WriteRawAstFunction); } if (VMSok (rqptr->rqNet.WriteIOsb.Status)) { ADD_LONG_QUAD (1, rqptr->BlocksRawTx); ADD_LONG_QUAD (rqptr->rqNet.WriteIOsb.Count, rqptr->BytesRawTx) } else { rqptr->rqNet.WriteErrorCount++; /* just note the first error status */ if (!rqptr->rqNet.WriteErrorStatus) rqptr->rqNet.WriteErrorStatus = rqptr->rqNet.WriteIOsb.Status; if (rqptr->rqNet.WriteIOsb.Status != SS$_LINKDISCON && rqptr->rqNet.WriteIOsb.Status != SS$_CONNECFAIL && rqptr->rqNet.WriteIOsb.Status != SS$_ABORT && rqptr->rqNet.WriteIOsb.Status != SS$_CANCEL && rqptr->rqNet.WriteIOsb.Status != SS$_IVCHAN) ErrorNoticed (rqptr, rqptr->rqNet.WriteIOsb.Status, NULL, FI_LI); } rqptr->rqNet.WriteRawDataPtr = rqptr->rqNet.WriteRawDataLength = 0; if (!(AstFunction = rqptr->rqNet.WriteRawAstFunction)) return; rqptr->rqNet.WriteRawAstFunction = NULL; (*AstFunction)(rqptr); } /*****************************************************************************/ /* Wrapper for NetReadRaw(). */ int NetRead ( REQUEST_STRUCT *rqptr, REQUEST_AST AstFunction, char *DataPtr, int DataSize ) { int status; /*********/ /* begin */ /*********/ if (WATCHING(rqptr) && WATCH_MODULE(WATCH_MOD_NET)) WatchThis (rqptr, FI_LI, WATCH_MOD_NET, "NetRead() !&A !&X !UL", AstFunction, DataPtr, DataSize); if (rqptr->rqNet.RedactBufferPtr) { int ReadCount = rqptr->rqNet.RedactBufferSize - rqptr->rqNet.RedactBufferCount; char *RedactBufferPtr = rqptr->rqNet.RedactBufferPtr + rqptr->rqNet.RedactBufferCount; if (WATCHING(rqptr) && WATCH_MODULE(WATCH_MOD_NET)) WatchThis (rqptr, FI_LI, 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 */ rqptr->rqNet.ReadIOsb.Status = SS$_NORMAL; rqptr->rqNet.ReadIOsb.Count = ReadCount = DataSize; memcpy (DataPtr, RedactBufferPtr, ReadCount); rqptr->rqNet.RedactBufferCount += ReadCount; } else if (ReadCount) { /* whatever is remaining */ rqptr->rqNet.ReadIOsb.Status = SS$_NORMAL; rqptr->rqNet.ReadIOsb.Count = ReadCount; memcpy (DataPtr, RedactBufferPtr, ReadCount); rqptr->rqNet.RedactBufferCount += ReadCount; } else { /* redact buffer has been completely read */ rqptr->rqNet.ReadIOsb.Status = SS$_ENDOFFILE; rqptr->rqNet.ReadIOsb.Count = 0; } if (AstFunction) SysDclAst (AstFunction, rqptr); return (rqptr->rqNet.ReadIOsb.Status); } if (rqptr->rqNet.SesolaPtr) { /* 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 hadshake completion. */ if (rqptr->ServicePtr->ProxyTunnel == PROXY_TUNNEL_RAW && /* prevent %CC-E-NOEQUALITY under VAX VMS V7.3 and DECC V6.4 */ (char*)AstFunction == (char*)RequestGet) { /* fudge these as if it came from the network */ rqptr->rqNet.ReadIOsb.Status = SS$_NORMAL; rqptr->rqNet.ReadIOsb.Count = 0; SysDclAst (RequestGet, rqptr); return (SS$_NORMAL); } status = SesolaNetRead (rqptr->rqNet.SesolaPtr, AstFunction, DataPtr, DataSize); return (status); } status = NetReadRaw (rqptr, AstFunction, DataPtr, DataSize); return (status); } /*****************************************************************************/ /* Queue up a read from the client over the network. If 'AstFunction' is zero then no I/O completion AST routine is called. If it is non-zero then the function pointed to by the parameter is called when the network write completes. Explicitly declares any AST routine if an error occurs. The calling function must not do any error recovery if an AST routine has been supplied but the associated AST routine must! If an AST was not supplied then the return status can be checked. */ int NetReadRaw ( REQUEST_STRUCT *rqptr, REQUEST_AST AstFunction, char *DataPtr, int DataSize ) { int status; /*********/ /* begin */ /*********/ if (WATCHING(rqptr) && WATCH_MODULE(WATCH_MOD_NET)) WatchThis (rqptr, FI_LI, WATCH_MOD_NET, "NetReadRaw() !&A !&X !UL", AstFunction, DataPtr, DataSize); if (rqptr->rqNet.ReadRawAstFunction) { rqptr->rqNet.ReadIOsb.Status = SS$_BUGCHECK; rqptr->rqNet.ReadIOsb.Count = 0; if (AstFunction) SysDclAst (AstFunction, rqptr); return (rqptr->rqNet.ReadIOsb.Status); } rqptr->rqNet.ReadRawAstFunction = AstFunction; rqptr->rqNet.ReadRawDataPtr = DataPtr; rqptr->rqNet.ReadRawDataSize = DataSize; if (WATCHING(rqptr) && WATCH_CATEGORY(WATCH_NETWORK_OCTETS)) WatchThis (rqptr, FI_LI, WATCH_NETWORK, "READ !UL bytes max (!&?non-blocking\rblocking\r)", DataSize, AstFunction); if (AstFunction) { /*******************/ /* non-blocking IO */ /*******************/ status = sys$qio (EfnNoWait, rqptr->rqClient.Channel, IO$_READVBLK, &rqptr->rqNet.ReadIOsb, &NetReadRawAst, rqptr, DataPtr, DataSize, 0, 0, 0, 0); } else { /***************/ /* blocking IO */ /***************/ status = sys$qiow (EfnWait, rqptr->rqClient.Channel, IO$_READVBLK, &rqptr->rqNet.ReadIOsb, 0, 0, DataPtr, DataSize, 0, 0, 0, 0); if (VMSok (status)) status = rqptr->rqNet.ReadIOsb.Status; } /****************/ /* check status */ /****************/ /* if I/O successful */ if (VMSok (status)) return (status); /* with resource wait enabled the only quota not waited for is ASTLM */ if (status == SS$_EXQUOTA) { /* no ASTs means not much of anything else can happen so just exit! */ sys$canexh(0); /* make the message a little more meaningful */ sys$exit (SS$_EXASTLM); } /* IVCHAN sometimes occurs if the socket has been closed (ignore) */ if (status != SS$_IVCHAN) ErrorNoticed (rqptr, status, "sys$qio", FI_LI); /* queuing of read failed, call AST explicitly, status in the IOsb */ rqptr->rqNet.ReadIOsb.Status = status; rqptr->rqNet.ReadIOsb.Count = 0; if (NetReadRawAst) SysDclAst (NetReadRawAst, rqptr); return (status); } /*****************************************************************************/ /* AST from NetReadRaw(). Call the AST function. */ NetReadRawAst (REQUEST_STRUCT *rqptr) { int status; REQUEST_AST AstFunction; /*********/ /* begin */ /*********/ if (WATCHING(rqptr) && WATCH_MODULE(WATCH_MOD_NET)) WatchThis (rqptr, FI_LI, WATCH_MOD_NET, "NetReadRawAst() !&F !&S !UL", NetReadRawAst, rqptr->rqNet.ReadIOsb.Status, rqptr->rqNet.ReadIOsb.Count); if (WATCHING(rqptr)) { if (WATCH_CATEGORY(WATCH_NETWORK) || WATCH_CATEGORY(WATCH_NETWORK_OCTETS)) { WatchThis (rqptr, FI_LI, WATCH_NETWORK, "READ !&S !UL bytes (!&?non-blocking\rblocking\r)", rqptr->rqNet.ReadIOsb.Status, rqptr->rqNet.ReadIOsb.Count, rqptr->rqNet.ReadRawAstFunction); if WATCH_CATEGORY(WATCH_NETWORK_OCTETS) if (VMSok(rqptr->rqNet.ReadIOsb.Status)) WatchDataDump (rqptr->rqNet.ReadRawDataPtr, rqptr->rqNet.ReadIOsb.Count); } } if (VMSok (rqptr->rqNet.ReadIOsb.Status)) { ADD_LONG_QUAD (1, rqptr->BlocksRawRx); ADD_LONG_QUAD (rqptr->rqNet.ReadIOsb.Count, rqptr->BytesRawRx) /* zero bytes with a normal status (once seen with TGV-Multinet) */ if (!rqptr->rqNet.ReadIOsb.Count) rqptr->rqNet.ReadIOsb.Status = SS$_ABORT; } else /* CONNECT method connection cancellation is normal behaviour */ if (rqptr->rqHeader.Method != HTTP_METHOD_CONNECT || rqptr->rqNet.ReadIOsb.Status != SS$_CANCEL) { rqptr->rqNet.ReadErrorCount++; /* just note the first error status */ if (!rqptr->rqNet.ReadErrorStatus) rqptr->rqNet.ReadErrorStatus = rqptr->rqNet.ReadIOsb.Status; if (rqptr->rqNet.ReadIOsb.Status != SS$_LINKDISCON && rqptr->rqNet.ReadIOsb.Status != SS$_CONNECFAIL && rqptr->rqNet.ReadIOsb.Status != SS$_UNREACHABLE && rqptr->rqNet.ReadIOsb.Status != SS$_ABORT && rqptr->rqNet.ReadIOsb.Status != SS$_CANCEL && rqptr->rqNet.ReadIOsb.Status != SS$_TIMEOUT && rqptr->rqNet.ReadIOsb.Status != SS$_IVCHAN) ErrorNoticed (rqptr, rqptr->rqNet.ReadIOsb.Status, NULL, FI_LI); } rqptr->rqNet.ReadRawDataPtr = rqptr->rqNet.ReadRawDataSize = 0; if (!(AstFunction = rqptr->rqNet.ReadRawAstFunction)) return; rqptr->rqNet.ReadRawAstFunction = NULL; (*AstFunction)(rqptr); } /*****************************************************************************/ /* Read without consuming data from the socket. */ int NetPeek ( REQUEST_STRUCT *rqptr, REQUEST_AST AstFunction, char *DataPtr, int DataSize ) { int status; /*********/ /* begin */ /*********/ if (WATCHING(rqptr) && WATCH_MODULE(WATCH_MOD_NET)) WatchThis (rqptr, FI_LI, WATCH_MOD_NET, "NetPeek() !&A", AstFunction); if (WATCHING(rqptr) && WATCH_CATEGORY(WATCH_NETWORK_OCTETS)) WatchThis (rqptr, FI_LI, WATCH_NETWORK, "PEEK !UL bytes (!&?non-blocking\rblocking\r)", DataSize, AstFunction); if (AstFunction) { /*******************/ /* non-blocking IO */ /*******************/ status = sys$qio (EfnNoWait, rqptr->rqClient.Channel, IO$_READVBLK, &rqptr->rqNet.PeekIOsb, AstFunction, rqptr, DataPtr, DataSize, 0, TCPIP$C_MSG_PEEK, 0, 0); } else { /***************/ /* blocking IO */ /***************/ status = sys$qiow (EfnWait, rqptr->rqClient.Channel, IO$_READVBLK, &rqptr->rqNet.PeekIOsb, 0, 0, DataPtr, DataSize, 0, TCPIP$C_MSG_PEEK, 0, 0); if (VMSok (status)) status = rqptr->rqNet.ReadIOsb.Status; } /****************/ /* check status */ /****************/ /* if I/O successful */ if (VMSok (status)) return (status); /* with resource wait enabled the only quota not waited for is ASTLM */ if (status == SS$_EXQUOTA) { /* no ASTs means not much of anything else can happen so just exit! */ sys$canexh(0); /* make the message a little more meaningful */ sys$exit (SS$_EXASTLM); } /* IVCHAN sometimes occurs if the socket has been closed (ignore) */ if (status != SS$_IVCHAN) ErrorNoticed (rqptr, status, "sys$qio", FI_LI); /* queuing of read failed, call AST explicitly, status in the IOsb */ rqptr->rqNet.PeekIOsb.Status = status; rqptr->rqNet.PeekIOsb.Count = 0; if (AstFunction) SysDclAst (AstFunction, rqptr); return (status); } /*****************************************************************************/ /* Any outstanding network (raw) I/O? Return true if there is. */ BOOL NetInProgress (REQUEST_STRUCT *rqptr) { /*********/ /* begin */ /*********/ if (WATCHING(rqptr) && WATCH_MODULE(WATCH_MOD_NET)) WatchThis (rqptr, FI_LI, WATCH_MOD_NET, "NetInProgress() read:!&B write:!&B", rqptr->rqNet.ReadRawAstFunction, rqptr->rqNet.WriteRawAstFunction); if (rqptr->rqNet.ReadRawAstFunction) return (true); if (rqptr->rqNet.WriteRawAstFunction) return (true); if (rqptr->rqNet.SesolaPtr) if (SesolaNetInProgress (rqptr->rqNet.SesolaPtr)) return (true); return (false); } /*****************************************************************************/ /* 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). */ NetWriteBuffered ( REQUEST_STRUCT *rqptr, REQUEST_AST AstFunction, char *DataPtr, int DataLength ) { int status; char *BufferPtr; STR_DSC *sdptr; STR_DSC_AUTO (DataDsc); /*********/ /* begin */ /*********/ if (WATCHING(rqptr) && WATCH_MODULE(WATCH_MOD_NET)) { WatchThis (rqptr, FI_LI, WATCH_MOD_NET, "NetWriteBuffered() !&A !&X !SL", AstFunction, DataPtr, DataLength); WatchDataDump (DataPtr, DataLength); } /* if all parameters empty then reset the buffers */ if (!AstFunction && !DataPtr && !DataLength) { StrDscNoContent (&rqptr->NetWriteBufferDsc); 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 */ rqptr->rqNet.WriteIOsb.Status = SS$_NORMAL; rqptr->rqNet.WriteIOsb.Count = DataLength; SysDclAst (AstFunction, rqptr); } /*****************************************************************************/ /* 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). */ NetWriteStrDsc ( REQUEST_STRUCT *rqptr, REQUEST_AST AstFunction ) { int DataLength; char *DataPtr, *EndPtr; STR_DSC *sdptr; /*********/ /* begin */ /*********/ if (WATCHING(rqptr) && WATCH_MODULE(WATCH_MOD_NET)) WatchThis (rqptr, FI_LI, WATCH_MOD_NET, "NetWriteStrDsc() !&F !&A", &NetWriteStrDsc, rqptr->NetWriteAstFunction); if (rqptr->NetWriteAstFunction) { /* AST call */ if (VMSnok (rqptr->rqNet.WriteIOsb.Status)) { /* network write has failed (as AST), bail out now */ SysDclAst (rqptr->NetWriteAstFunction, rqptr); rqptr->NetWriteAstFunction = NULL; 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); rqptr->NetWriteAstFunction = AstFunction; rqptr->NetWriteBufferCount = 0; sdptr = &rqptr->NetWriteBufferDsc; } if (rqptr->NetWriteAstFunction) AstFunction = &NetWriteStrDsc; EndPtr = STR_DSC_PTR(sdptr) + STR_DSC_LEN(sdptr); DataPtr = STR_DSC_PTR(sdptr) + rqptr->NetWriteBufferCount; DataLength = EndPtr - DataPtr; if (DataLength > 65535) DataLength = 65535; rqptr->NetWriteBufferCount += DataLength; 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)); } /* final write entirely */ AstFunction = rqptr->NetWriteAstFunction; rqptr->NetWriteAstFunction = 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; } } } NetWrite (rqptr, AstFunction, DataPtr, DataLength); } /*****************************************************************************/ /* 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 ( REQUEST_STRUCT *rqptr, int Message ) { static unsigned long FiveSecondsDelta [2] = { -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 (NULL, FI_LI, WATCH_MOD_NET, "NetDirectResponse() !UL", Message); status = sys$setimr (0, &FiveSecondsDelta, &NetDirectResponseTimeoutAst, rqptr, 0); if (VMSnok (status)) { ErrorNoticed (rqptr, status, NULL, FI_LI); sys$dassgn (rqptr->rqClient.Channel); VmFreeRequest (rqptr, FI_LI); return; } rqptr->rqNet.ReadBufferPtr = VmGetHeap (rqptr, NetReadBufferSize); BufferDsc.dsc$a_pointer = rqptr->rqNet.ReadBufferPtr; 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 (rqptr, MSG_STATUS_ERROR), Config.cfServer.ReportBodyTag, MsgFor (rqptr, 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 (rqptr, MSG_STATUS_ERROR), Config.cfServer.ReportBodyTag, MsgFor (rqptr, MSG_GENERAL_TOO_BUSY)); else ErrorExitVmsStatus (SS$_BUGCHECK, ErrorSanityCheck, FI_LI); status = sys$qiow (EfnWait, rqptr->rqClient.Channel, IO$_WRITEVBLK | IO$M_NOWAIT, &IOsb, 0, 0, rqptr->rqNet.ReadBufferPtr, Length, 0, 0, 0, 0); if (VMSok (status)) status = IOsb.Status; if (VMSok (status)) { status = sys$qio (EfnNoWait, rqptr->rqClient.Channel, IO$_READVBLK, &rqptr->rqNet.ReadIOsb, &NetDirectResponseAst, rqptr, rqptr->rqNet.ReadBufferPtr, NetReadBufferSize, 0, 0, 0, 0); if (VMSnok (status)) ErrorNoticed (rqptr, status, NULL, FI_LI); } else ErrorNoticed (rqptr, status, NULL, FI_LI); if (VMSnok (status)) { sys$dassgn (rqptr->rqClient.Channel); VmFreeHeap (rqptr, FI_LI); VmFreeRequest (rqptr, FI_LI); } } /*****************************************************************************/ /* See commentary in NetDirectResponse(). */ NetDirectResponseAst (REQUEST_STRUCT *rqptr) { /*********/ /* begin */ /*********/ if (WATCH_MODULE(WATCH_MOD_NET)) WatchThis (NULL, FI_LI, WATCH_MOD_NET, "NetDirectResponseAst()"); sys$cantim (rqptr, 0); sys$dassgn (rqptr->rqClient.Channel); VmFreeHeap (rqptr, FI_LI); VmFreeRequest (rqptr, FI_LI); } /*****************************************************************************/ /* See commentary in NetDirectResponse(). */ NetDirectResponseTimeoutAst (REQUEST_STRUCT *rqptr) { /*********/ /* begin */ /*********/ if (WATCH_MODULE(WATCH_MOD_NET)) WatchThis (NULL, FI_LI, WATCH_MOD_NET, "NetDirectResponseTimeoutAst()"); sys$cancel (rqptr->rqClient.Channel); } /*****************************************************************************/ /* 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 (NULL, FI_LI, 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 (NULL, FI_LI, 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 (NULL, FI_LI, 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 */ /****************************************************************************/