/*****************************************************************************/ /* Request.c Get, process and execute the HTTP request from the client. Some server directives are detected in this module. Server directives take the form of a special path or query string and are CASE SENSISTIVE. ?http=... any query string beginning "httpd=" will be ignored by the default query script and the HTTPd will pass it to the requested functionality /httpd/-/admin/ generates a server administration menu /httpd/-/admin/graphic/... generates graphics /httpd/-/admin/report/... generates various reports /httpd/-/admin/revise/... allows form-based configuration /httpd/-/admin/control/... allows server to be stopped, restarted, etc. /httpd/-/change/... change user authentication /httpd/-/verify/... reverse proxy authorization verification /echo/ echoes complete request (header and any body) /tree/ directory tree /upd/ activates the update module /where/ reports mapped and parsed path /Xray/ returns an "Xray" of the request (i.e. header and body of response as a plain-text document) Error counting within this module attempts to track the reason that any connection accepted fails before being passsed to another module for processing (i.e. request format error, path forbidden by rule). AUTHORIZATION MAY BE PERFORMED TWICE! ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ The first, to authorize the full path provided with the request (after it has been mapped!) The second, if the request path contained a script any path specification following the script component is also checked. In this way access to controlled information is not inadvertantly allowed because the script itself is not controlled. In general, script specifications should not be part of an authorized path if that script is used to access other paths (i.e. does not return data generated by itself), apply authorization to data paths. VERSION HISTORY --------------- 09-AUG-2024 MGD bugfix; RequestDiscardBody() regression 08-SEP-2023 MGD if (NetRejectStatis400) NetRejectSetStatus(); 09-APR-2022 MGD RequestReport() request & connection + history 18-MAR-2022 MGD RequestDiscardBody() use ->rqBody.ContentCount64 13-MAR-2021 MGD RequestGet() recognise SOCKS proxy 12-DEC-2020 MGD RequestEnd() _PROBE() to determine rqptr accessibility this kludge attempts to workaround HTTP/2+POST+DCL(PHP) occasionally throwing an errant AST out of the rats-nest 22-NOV-2020 MGD content length now 64 bit 26-SEP-2020 MGD RequestParseExecute() tighten ->WhiffOfWebDav 07-AUG-2020 MGD bugfix; RequestEnd() redirection 04-AUG-2020 MGD bugfix; RequestExecutePostAuth1() INTERNAL_PASSWORD_CHANGE should call HTAdminBegin() not AdminBegin() 01-AUG-2020 MGD bugfix; RequestParseExecute() ensure PUT and DELETE have WebDAV header field(s) before considering WebDAV 15-JUL-2020 MGD bugfix; RequestPersistentConnection() pipelined request 15-MAR-2020 MGD bugfix; RequestProcessFields() DictLookup (.."accept"..) 22-DEC-2019 MGD RequestGet() and RequestFields() refine header parsing bugfix; deliver via NetIoReadStatus() 18-NOV-2019 MGD bugfix; RequestShareBegin() if (!MATCH6 (cptr, "raw://")) 11-OCT-2019 MGD RequestAbort() accomodates HttpdSupervisor() refinement RequestShareBegin() reimplement eliminating peek approach 18-JAN-2018 MGD status code 418 (teapot) forces connection drop 04-JAN-2019 MGD bugfix; AuthCompleted() and AuthNotComplete() 02-SEP-2018 MGD RequestGet() PROXY_TUNNEL_RAW add "X-Forwarded-For:" header to allow ProxyTunnelNetReadAst() to provide original client host for the proxied-to server 27-JUL-2018 MGD bugfix; RequestEnd2() call SesolaClientCertEnd() 11-JUN-2018 MGD RequestControlRundown() implement /DO=REQUEST=RUNDOWN=.. 18-MAY-2018 MGD RequestAccept() and RequestBegin() ensure data available before calling SesolaNetBegin() on TLS/SSL service 23-APR-2018 MGD bugfix; RequestRundown() allow for cache activity 18-APR-2018 MGD RequestBegin() exit after consecutive SesolaNetBegin() fails 12-JAN-2018 MGD bugfix; RequestGet() read fail call RequestEnd4() 07-DEC-2017 MGD RequestExecuteMappingError() allow for 2nn 28-NOV-2017 MGD WebDAV "authorisation" allowed to be EXTERNAL or OPAQUE 19-NOV-2017 MGD RequestRundown() outstanding task sanity checks 26-MAY-2017 MGD RequestReport() when zero disable throttle button 21-MAR-2017 MGD bugfix; use rqHeader.RequestBody.. for body with header 31-OCT-2016 MGD bugfix; RequestExecutePostCache() keyword redirection count 14-JUL-2016 MGD bugfix; RequestProcessFields() if-range: regression 29-MAY-2016 MGD bugfix; RequestEnd2() decrement processing rx or (SSH) method 22-MAY-2016 MGD bugfix; RequestEnd2() read status OK -or- ENDOFFILE 09-MAY-2016 MGD RequestGblSecUpdate() method and URI only printable chars 11-FEB-2016 MGD add limit to consecutive failures on persistent connection remove limit to consecutive requests on persistent connection 28-DEC-2015 MGD RequestRedirect() moved to REDIRECT.C RedirectRequest() 05-AUG-2015 MGD HTTP/2 support remove flag of Netscape Navigator Gold 3.03 :-} 14-JUL-2015 MGD logical name WASD_REDIRECT_WILDCARD must be defined to enable "DNS wildcard" proxy redirection bugfix; rework pipelined request handling 29-APR-2015 MGD add WATCH "!42*x" to beginning and ending of requests 24-APR-2015 MGD RequestRedirect() always use dynamic buffers 29-JAN-2015 MGD RequestExececute() move ServiceChange() after mapping 21-JAN-2015 MGD bugfix; RequestEnd2() use ZERO_DELTA_TIME macro 07-NOV-2014 MGD bugfix; RequestEnd2() multi-instance ->DoNoModuleCount++ 29-JAN-2014 MGD bugfix; RequestRedirect() allocate using (possibly expanded) header length (not fixed) when allocating POST buffer 29-DEC-2013 MGD ResponseCorsProcess() for Cross-Origin Resource Sharing 07-JUL-2013 MGD remove reset of persistent flag from OPTIONS and DELETE 25-JUL-2012 MGD DNT: request header (do not track) 21-APR-2012 MGD bugfix; RequestBegin() remove RequestEnd() following failed SesolaNetBegin() resulted in redundant request rundown 06-FEB-2012 MGD RequestGet() no longer report 408 for unused connections RequestEnd2() likewise ignore unused connections (Chrome) 14-NOV-2011 MGD bugfix; RequestEnd2() '->WebSocketCount' already locked 17-SEP-2011 MGD bugfix; RequestRedirect() only concat '&' if including query 06-FEB-2011 MGD "Vary:" request header field 30-NOV-2010 MGD exclude WebSocket and proxy tunnel requests from overall min/max/ave duration statistics 04-SEP-2010 MGD RequestLineParse() allow for proxied http://[ipv6%eth0]/ 28-AUG-2010 MGD bugfix; regression at 10.0.1 with proxy authorization 19-JUN-2010 MGD bugfix; RequestGet() MAX_REQUEST_HEADER (per JPP) 29-MAY-2010 MGD RequestExecutePostCache() UTF-8 decode WebDAV objects 11-MAY-2010 JPP RequestRedirect() add support for WebDAV "Destination:" field 20-JAN-2010 MGD WebSockets support, including "Connection: upgrade", "Origin:", "WebSocket-Protocol:" 11-JAN-2010 MGD RequestRedirect() add return length (overflow) check 02-JAN-2010 MGD make proxy requests subject to throttle (per JPP) 14-NOV-2009 MGD [ServiceShareSSH] and RequestSshBegin() allows a Web service to "share" with and proxy to SSH 26-MAY-2009 MGD RequestRedirect() allow a redirect to include its own query string and then concatenate any request query with '&'.. 15-FEB-2008 MGD RequestReport() per-current, per-connection, per-throttle and per-history 05-JAN-2008 MGD RequestGblSecUpdate() include remote user and realm in request monitor data 10-DEC-2007 MGD RequestExecutePostCache() check again for RequestHomePage() before final RequestFile() 25-NOV-2007 MGD RequestRedirect() include response cookie(s) RequestRedact() and RequestEnd() redact buffer processing 06-JUN-2007 MGD RequestHttpStatusIndex() 29-MAY-2007 JPP bugfix; RequestGet() buggy browser kludge 02-MAY-2006 JPP RequestGet() now handles extraneous which buggy browsers can incorrectly insert after the body of a valid request. (See RFC 2616 section 4.1) 19-APR-2007 MGD RequestExecutePostAuth1() kludge to allow 'implied' scripts 19-SEP-2006 MGD RequestHttpStatusCode() provides more fine-grained HTTP response status code accounting (mainly for WOTSUP) 15-JUL-2006 MGD bugfix; RequestGet() timestamp the event immediately 26-JAN-2006 MGD RequestRedirect() "//:port/path" (i.e. begins with "//:") allows a redirect to a different port on the same host 26-DEC-2005 MGD bugfix; RequestFields() allow for header lines with no white-space between field name and value (jpp@esme.fr) 11-JUN-2005 MGD bugfix; "?httpd=content" should include SSI files 22-FEB-2005 MGD bugfix; keyword search exclusion on configured file type 21-JAN-2005 MGD disable SSL persistent connections for VMS Navigator Gold 05-JAN-2005 MGD RequestParseHeader() remove explicit disable of POST & PUT connection persistence 16-DEC-2004 MGD handle chunked transfer-encoding during request rundown 16-OCT-2004 MGD request rundown changes for GZIP encoding, configurable service unavailable 503 when log write fails 06-OCT-2004 MGD allow throttling of mapping/mapped error messages 12-AUG-2004 MGD bugfix; HttpTimerSet() after mapping in case of SET timeout 20-JUL-2004 MGD HTTP/1.1 compliance, "ETag:", "Expect:", "If-Match:", "If-None-Match:", "Max-Forwards:", "Transfer-Encoding:", "Trailer:" headers, persistent connection detection and processing refine, pipelined request processing, refine redirection request header rebuild 27-JAN-2004 MGD add connect processing and keep-alive accounting items 15-JAN-2004 MGD bugfix; RequestExecute() error by redirect 12-JAN-2004 MGD RequestExecute() resolve virtual service if local path 10-JAN-2004 MGD 'delete-on-close' file specification extended 16-DEC-2003 MGD mapping now URL-encodes a redirect wildcard path portions 18-NOV-2003 MGD reverse proxy 302 "Location:" rewrite persistent storage 07-OCT-2003 MGD bugfix; "internal" script detection 15-SEP-2003 MGD bugfix; keyword search exclude file type bugfix; keepalive notepad needs to be explicitly NULLed 21-AUG-2003 MGD "Range:" header field 03-AUG-2003 MGD RequestDump() 09-JUL-2003 MGD revise request and history report format 31-MAY-2003 MGD RequestHomePage() check [Welcome] against [DclScriptRunTime] for welcome/home pages that are provided by scripting 10-MAY-2003 MGD revise request header field processing and storage, improve efficiency of RequestRedirect() 02-APR-2003 MGD allow for "X-Forwarded-For:" request header field 26-MAR-2003 MGD minor changes to alert processing RequestRedirect() append remaining CGI response header 24-MAR-2003 MGD bugfix; RequestDiscardBody() reset of body processing ASTs 07-FEB-2003 MGD no default search script path setting 17-JAN-2003 MGD implement path setting 'script=path=find' 12-OCT-2002 MGD check for device and directory (minimum) before parse 15-AUG-2002 MGD rework (yet again) path alert for more flexibility, bugfix; 'Xray' broken in v8, repaired and reworked 08-AUG-2002 MGD RequestRedirect() should URL-encode local redirection 03-JUL-2002 MGD add ResponseHiss() 30-JUN-2002 MGD adjust RequestBodyDiscard() for already-started read 22-MAY-2002 MGD refine scheme detection in RequestRedirect() 06-JUN-2002 MGD RequestDiscardBody() for (at least) Netscape 3/4 15-MAY-2002 MGD RequestRedirect() allow for wildcard DNS "proxy", "Cache-Control:" field for Mozilla compatibility 31-MAR-2002 MGD mask potential passwords in request URIs, keep-alive decision logic to RequestFields() 02-FEB-2002 MGD rework echo due to request body processing changes 14-OCT-2001 MGD add an explicit test for, and message regarding, DECnet use in mapped file names, reporting it as unsupported 04-AUG-2001 MGD modifications in line with changes in the handling of file and cache (now MD5 hash based) processing, support module WATCHing 11-JUL-2001 MGD allow '?' on the end of a REDIRECT mapping template to propagate the original request's query string 28-JUN-2001 MGD extend local redirection syntax to reinstate "reverse proxy" (e.g. "/http://the.host.name/path") 10-MAY-2001 MGD calls to throttle module 27-FEB-2001 MGD script path parse content-type check 08-JAN-2001 MGD bugfix; RequestDiscardAst() 30-DEC-2000 MGD rework for FILE.C getting file contents in-memory 01-OCT-2000 MGD authorize either request *or* mapped path (script path authorization is/has always been on mapped) 26-AUG-2000 MGD WATCH processing, peek, or peek+processing 08-AUG-2000 MGD bugfix; include Accept-Encoding when redirecting, bugfix; (sort-of) ensure redirected BytesRawRx carried over 24-JUN-2000 MGD persistent run-time environments, bugfix; HEAD requests specifying content-length bugfix; increase size of buffer in RequestRedirect() 07-MAY-2000 MGD session track 04-MAR-2000 MGD use FaolToNet(), et.al., add "http:///" and "https:///" to redirection syntax 08-FEB-2000 MGD search script exclude specified file types 27-DEC-1999 MGD support ODS-2 and ODS-5 using ODS module 11-NOV-1999 MGD allow for "ETag:" (only for proxy propagation) 20-OCT-1999 MGD redirect now substitutes the scheme and "Host:" or server host:port into a mapping rule like "REDIRECT ///some/path" or just the scheme into "REDIRECT //host.domain/path/" 28-AUG-1999 MGD accomodation for asynchronous authorization 30-JUL-1999 MGD bugfix; HttpdExit() requires a parameter! 12-JUN-1999 MGD looks like a proxy request? send it to ProxyRequestBegin() 04-APR-1999 MGD provide HTTP/0.9 functionality (finally!) 10-JAN-1999 MGD proxy serving, history report format refined, added service information to history report 07-NOV-1998 MGD WATCH facility 18-OCT-1998 MGD error report redirection 19-SEP-1998 MGD improve granularity of cache search, RequestFileNoType() now redirects for directories, automatic scripting suppressed with HTTPd query string 12-JUL-1998 MGD bugfix; RequestEnd() no status returned from RequestBegin()! 14-MAY-1998 MGD ?httpd=... generalized from index processing to all requests 02-APR-1998 MGD no longer log internal redirects 28-MAR-1998 MGD declare an AST for local redirection parsing (in case a CGIplus script redirected and is exiting!) 28-JAN-1998 MGD moved more appropriate functions from HTTPd.C into here, header parsing now allows for hiatus in end-header blank line, allow for directories specified as "/dir1/dir2" 07-JAN-1998 MGD provide URL-encoded decode on path 05-OCT-1997 MGD file cache, added "Accept-Charset:", "Forwarded:" and "Host:" 18-SEP-1997 MGD HTTP status code mapping 09-AUG-1997 MGD message database 27-JUL-1997 MGD modified "Accept:" header lines processing 08-JUN-1997 MGD added "Pragma:" header field detection 27-MAR-1997 MGD added "temporary" file detection (for UPD/PUT preview) 01-FEB-1997 MGD HTTPd version 4 01-OCT-1996 MGD added more reports 25-JUL-1996 MGD use optional "length=" within "If-Modified-Since:" header 12-APR-1996 MGD RMS parse structures moved to thread data; persistent connections ("keep-alive"); changed internal directive from query string to path; observed Multinet disconnection/zero-byte behaviour (request now aborts if Multinet returns zero bytes) 01-DEC-1995 MGD HTTPd version 3 27-SEP-1995 MGD extensive rework of some functions; added 'Referer:', 'User-Agent:', 'If-Modified-Since:' 01-APR-1995 MGD initial development for addition to multi-threaded daemon */ /*****************************************************************************/ #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 /* VMS related header files */ #include #include #include #include #include #include /* application header files */ #include "wasd.h" #define WASD_MODULE "REQUEST" /******************/ /* global storage */ /******************/ /* used by VM.c */ const int RequestStructSize = sizeof(REQUEST_STRUCT); LIST_HEAD RequestList; LIST_HEAD RequestHistoryList; int RequestHistoryCount, RequestHistoryMax; /********************/ /* external storage */ /********************/ #ifdef DBUG extern BOOL Debug; #else #define Debug 0 #endif extern BOOL CacheEnabled, ControlExitRequested, ControlRestartRequested, Http2Enabled, HttpdTicking, LoggingEnabled, LoggingFileError, MonitorEnabled, MapUrlExtensionMethod, NetConnectSuspend, OdsExtended, WebDavEnabled, WebDavLockingEnabled; extern int ActivityTotalMinutes, ConfigNoticeInvalid, ConnectCountTotal, DclSysOutputSize, ErrorsNoticedCount, ExitStatus, Http2ClientPrefaceLength, HttpdGblSecPages, InstanceNodeConfig, InstanceNodeCurrent, InstanceNumber, NetConcurrentProcessMax, NetCurrentProcessing, NetReadBufferSize, NetRejectStatus400, NetRejectStatusCode, OpcomMessages, ServiceTunnelSshCount, SsiSizeMax, WebSockCurrent; extern const int64 Delta001uSec; extern int64 ErrorsNoticedTime64; extern unsigned long InstanceMutexCount[], InstanceMutexWaitCount[]; extern char ConfigContentTypeSsi[], ConfigContentTypeUrl[], ErrorSslFailure[], ErrorSanityCheck[], Http2ClientPreface[], NetRejectStatusString[], ServerHostPort[], SoftwareID[]; #define acptr AccountingPtr extern ACCOUNTING_STRUCT *AccountingPtr; extern CONFIG_STRUCT Config; extern HTTPD_GBLSEC *HttpdGblSecPtr; extern HTTPD_PROCESS HttpdProcess; extern LIST_HEAD Http2List; extern MSG_STRUCT Msgs; extern MAPPING_META *MappingMetaPtr; extern WATCH_STRUCT Watch; /*****************************************************************************/ /* The network connection has been established. Make a request around that connection and begin processing. */ void RequestAccept (NETIO_STRUCT *ioptr) { REQUEST_STRUCT *rqptr; SERVICE_STRUCT *svptr; /*********/ /* begin */ /*********/ if (WATCHMOD (ioptr, WATCH_MOD_REQUEST)) WatchThis (WATCHITM(ioptr), WATCH_MOD_REQUEST, "RequestAccept()"); rqptr = VmGetRequest (++ConnectCountTotal); /* add entry to the top of the request list */ ListAddHead (&RequestList, rqptr, LIST_ENTRY_TYPE_REQUEST); rqptr->NetIoPtr = ioptr; ioptr->RequestPtr = rqptr; rqptr->ClientPtr = ioptr->ClientPtr; rqptr->ServicePtr = ioptr->ServicePtr; rqptr->WatchItem = ioptr->WatchItem; rqptr->rqDictPtr = DictCreate (rqptr, -1); InstanceMutexLock (INSTANCE_MUTEX_HTTPD); NetUpdateConnected (rqptr, +1); InstanceMutexUnLock (INSTANCE_MUTEX_HTTPD); /* timestamp the transaction */ sys$gettim (&rqptr->rqTime.BeginTime64); sys$numtim (&rqptr->rqTime.BeginTime7, &rqptr->rqTime.BeginTime64); HttpGmTimeString (rqptr->rqTime.GmDateTime, &rqptr->rqTime.BeginTime64); /* if it's not already running kick-off the HTTPd ticker */ if (!HttpdTicking) HttpdTick (0); if (WATCH_NOT_ONE_SHOT(Watch.Category)) WatchFilterClientService (rqptr); HttpdTimerSet (rqptr, TIMER_INPUT, rqptr->NetIoPtr->ServicePtr->ShareSSH ? rqptr->NetIoPtr->ServicePtr->ShareSSH : 0); RequestBegin (rqptr); } /*****************************************************************************/ /* This function is called from NetAccept() where a new HTTP/1.n connection has been established and memory has just been allocated for this connection's thread structure. This function also endeavours to allow "sharing" of a service port for HTTP requests and other clients such as SSH. The objective is to allow tunneling of SSH via proxy server CONNECT which is usually confined to port 433 (tunneling of SSH, or any other protocol, on a standalone port is already possible using a RAW tunnel). Now port 443 is invariably configured to talk SSL and must talk raw octets if it's to be used as an opaque tunnel. The approach taken is to peek at the incoming TCP byte stream and see if it's TLS/SSL. If not the socket (request) is associated with a proxy tunneling service (to get the raw octets), the essential request data fudged, and proxy tunneling initiated. However, and of course, some clients do not initiate their exchange until after the server has. This is a Catch-22 of sorts. So what WASD does is after input timeout (the client waiting) it sets up the tunnel anyway and begins the proxy. The proxied server should then initiate the protocol and the client respond. The directive [ServiceShareSSH] non-zero both enables this facility for a service and sets the input timeout period (which perhaps should be shorter than the default 30 seconds because such clients will wait that long for any SSH server response). If it's a slow SSL connection (or other) the client setup will just fail with the server disconnecting when the protocol exchange makes no sense. A workable accomodation it would seem. */ void RequestBegin (REQUEST_STRUCT *rqptr) { int status, size; char *cptr, *sptr; SERVICE_STRUCT *svptr; /*********/ /* begin */ /*********/ if (WATCHMOD (rqptr, WATCH_MOD_REQUEST)) WatchThis (WATCHITM(rqptr), WATCH_MOD_REQUEST, "RequestBegin()"); rqptr->RequestState = REQUEST_STATE_CONNECTED; rqptr->NetworkHttp = 1; if (rqptr->NetIoPtr->SesolaPtr == NULL && rqptr->ServicePtr->RequestScheme == SCHEME_HTTPS) { /* Transport Layer Security (TLS/SSL) ("https://") transaction */ SesolaNetBegin (rqptr); return; } TcpIpSocketMaxQio (rqptr->NetIoPtr); rqptr->rqNet.ReadBufferSize = NetReadBufferSize; rqptr->rqNet.ReadBufferPtr = VmGetHeap (rqptr, NetReadBufferSize); OdsStructInit (&rqptr->ParseOds, false); /* if available then set any initial report to be via the script */ if (rqptr->ServicePtr->ErrorReportPath[0]) rqptr->rqResponse.ErrorReportByRedirect = true; if (rqptr->ServicePtr->ProxyTunnel == PROXY_TUNNEL_FIREWALL || rqptr->ServicePtr->ProxyTunnel == PROXY_TUNNEL_RAW) { /* fudge these as if it came from the network */ if (rqptr->ServicePtr->ProxyTunnel == PROXY_TUNNEL_FIREWALL) { cptr = MsgFor(rqptr,MSG_PROXY_FIREWALL_PROMPT); if (cptr[0]) { sptr = VmGetHeap (rqptr, size = strlen(cptr)+3); SET2(sptr,'\r\n'); strzcpy (sptr+2, cptr, size); NetWrite (rqptr, &RequestGet, sptr, strlen(sptr)); } else /* without a real network read just fudge the status */ NetIoReadStatus (rqptr->NetIoPtr, RequestGet, rqptr, SS$_NORMAL, 0); } else /* without a real network read just fudge the status */ NetIoReadStatus (rqptr->NetIoPtr, RequestGet, rqptr, SS$_NORMAL, 0); } else if (rqptr->ServicePtr->ConnectService) { /* request-on-connects do not wait for client data */ rqptr->ConnectRequest = true; /* without a real network read just fudge the status */ NetIoReadStatus (rqptr->NetIoPtr, RequestGet, rqptr, SS$_NORMAL, 0); } else if (rqptr->ServicePtr->RawSocket) { /* raw [web]sockets do not (necessarily) wait for client data */ rqptr->RawSocketRequest = true; /* without a real network read just fudge the status */ NetIoReadStatus (rqptr->NetIoPtr, RequestGet, rqptr, SS$_NORMAL, 0); } else NetRead (rqptr, &RequestGet, rqptr->rqNet.ReadBufferPtr, rqptr->rqNet.ReadBufferSize); } /*****************************************************************************/ /* When shared SSH the underlying protocol is opaque to WASD and so much of it needs to be fudged and the usual request processing pathway avoided. */ void RequestShareBegin (REQUEST_STRUCT *rqptr) { int idx, status; char *cptr, *sptr, *zptr; SERVICE_STRUCT *svptr; /*********/ /* begin */ /*********/ if (WATCHMOD (rqptr, WATCH_MOD_REQUEST)) WatchThis (WATCHITM(rqptr), WATCH_MOD_REQUEST, "RequestShareBegin()"); if (!rqptr->rqNet.ReadBufferPtr) { /* via TLS/SSL not RequestGet() */ rqptr->rqNet.ReadBufferSize = NetReadBufferSize; rqptr->rqNet.ReadBufferPtr = VmGetHeap (rqptr, NetReadBufferSize); } if (rqptr->NetIoPtr->SSHversionDigit) { /* TLS/SSL service so get the rest of the SSH version exchange */ if (!SAME4 (rqptr->rqNet.ReadBufferPtr, 'SSH-')) { strzcpy (rqptr->rqNet.ReadBufferPtr, "SSH-", 5); rqptr->rqNet.ReadBufferPtr[4] = rqptr->NetIoPtr->SSHversionDigit; NetRead (rqptr, &RequestShareBegin, rqptr->rqNet.ReadBufferPtr + 5, rqptr->rqNet.ReadBufferSize - 5); return; } rqptr->NetIoPtr->ReadCount += 5; rqptr->NetIoPtr->SSHversionDigit = 0; } if (rqptr->RequestState == REQUEST_STATE_ABORT) { strzcpy (rqptr->rqNet.ReadBufferPtr, "SSH-(timeout)", 14); rqptr->NetIoPtr->ReadCount = 13; } rqptr->RequestState = REQUEST_STATE_PROCESSING; rqptr->rqHeader.Method = HTTP_METHOD_SHARE_SSH; strzcpy (rqptr->rqHeader.MethodName, "SSH", 4); rqptr->rqHeader.HttpVersion = HTTP_VERSION_0_9; InstanceMutexLock (INSTANCE_MUTEX_HTTPD); NetUpdateProcessing (rqptr, +1); InstanceMutexUnLock (INSTANCE_MUTEX_HTTPD); if (WATCH_NOT_ONE_SHOT(Watch.Category)) WatchFilterHttpProtocol (rqptr); /* proxy tunneling uses the mapped path for it's host info */ cptr = MapUrl_Map (rqptr->rqNet.ReadBufferPtr, 0, NULL, 0, NULL, 0, NULL, 0, NULL, 0, 0, rqptr, &rqptr->rqPathSet); /* buffer the mapped path (cptr+1 allows error and non-error mappings) */ rqptr->MappedPathLength = strlen(cptr+1)+1; rqptr->MappedPathPtr = VmGetHeap (rqptr, rqptr->MappedPathLength+1); memcpy (rqptr->MappedPathPtr, cptr, rqptr->MappedPathLength); /* if mapping to status */ if (!cptr[0] && cptr[1]) { RequestMappedToStatus (rqptr); return; } if (!MATCH6 (cptr, "raw://")) { /* if its not raw then it's not on! */ rqptr->rqResponse.HttpStatus = 500; RequestEnd (rqptr); return; } if (rqptr->rqPathSet.ChangeServicePtr) { /* change service */ if (!ServiceChange (rqptr, rqptr->rqPathSet.ChangeServicePtr)) { RequestEnd (rqptr); return; } } if (WATCHING (rqptr, WATCH_REQUEST)) if (!rqptr->rqHeader.WatchNewRequest) { rqptr->rqHeader.WatchNewRequest = true; WatchDataFormatted ("|!#*+\n", 38 + Watch.ItemWidth); } if (WATCHING (rqptr, WATCH_REQUEST)) WatchThis (WATCHITM(rqptr), WATCH_REQUEST, "SSH !AZ", *cptr ? cptr : cptr+1); rqptr->rqHeader.RequestUriPtr = rqptr->MappedPathPtr; rqptr->rqHeader.PathInfoPtr = rqptr->MappedPathPtr; rqptr->rqHeader.QueryStringPtr = ""; InstanceGblSecIncrLong (&AccountingPtr->MethodSshCount); HttpdTimerSet (rqptr, TIMER_OUTPUT, 0); ProxyRequestBegin (rqptr); } /****************************************************************************/ /* Any request I/O currently outstanding? Outstanding direct network is indicated by non-NULL AST functions. Outstanding TLS (SSL) network can also have connection state I/O being underway so that need to be checked as well. */ BOOL RequestIoInProgress (REQUEST_STRUCT *rqptr) { /*********/ /* begin */ /*********/ if (WATCHMOD (rqptr, WATCH_MOD_REQUEST)) WatchThis (WATCHITM(rqptr), WATCH_MOD_REQUEST, "RequestIoInProgress() !&B !&B", rqptr->NetIoPtr && rqptr->NetIoPtr->WriteAstFunction, rqptr->NetIoPtr && rqptr->NetIoPtr->ReadAstFunction); if (rqptr->NetIoPtr) { if (rqptr->NetIoPtr->WriteAstFunction) return (true); if (rqptr->NetIoPtr->ReadAstFunction) return (true); if (rqptr->NetIoPtr->SesolaPtr) if (SesolaNetIoInProgress (rqptr->NetIoPtr)) return (true); } return (false); } /****************************************************************************/ /* Cancel any outstanding request I/O. */ void RequestIoCancel (REQUEST_STRUCT *rqptr) { /*********/ /* begin */ /*********/ if (WATCHMOD (rqptr, WATCH_MOD_REQUEST)) WatchThis (WATCHITM(rqptr), WATCH_MOD_REQUEST, "RequestIoCancel()"); if (rqptr->NetIoPtr) { if (rqptr->NetIoPtr->Stream2Ptr) Http2NetIoCancel (rqptr->NetIoPtr); else if (rqptr->NetIoPtr->SesolaPtr) SesolaNetIoCancel (rqptr->NetIoPtr); else NetIoCancel (rqptr->NetIoPtr); } } /*****************************************************************************/ /* Initiate request rundown for one or more requests. */ int RequestControlRunDown (int ConnectNumber) { int count; LIST_ENTRY *leptr; REQUEST_STRUCT *rqeptr; /*********/ /* begin */ /*********/ if (WATCH_MODULE(WATCH_MOD_REQUEST)) WatchThis (WATCHALL, WATCH_MOD_REQUEST, "RequestControlRunDown() !SL", ConnectNumber); if (!ConnectNumber) return (0); count = 0; for (leptr = RequestList.HeadPtr; leptr; leptr = leptr->NextPtr) { rqeptr = (REQUEST_STRUCT*)leptr; if (ConnectNumber < 0 || rqeptr->ConnectNumber == ConnectNumber) { HttpdTimerSet (rqeptr, TIMER_TERMINATE, 0); count++; } } return (count); } /*****************************************************************************/ /* Move this request's state to ABORT. A request may only be aborted the once. */ void RequestAbort (REQUEST_STRUCT *rqptr) { int state; /*********/ /* begin */ /*********/ if (WATCHMOD (rqptr, WATCH_MOD_REQUEST)) WatchThis (WATCHITM(rqptr), WATCH_MOD_REQUEST, "RequestAbort() !UL", rqptr->RequestState); if (HTTP2_REQUEST(rqptr)) { Http2RequestAbort (rqptr); return; } if ((state = rqptr->RequestState) >= REQUEST_STATE_ABORT) return; rqptr->RequestState = REQUEST_STATE_ABORT; if (rqptr->NetIoPtr->ServicePtr->ShareSSH) if (!rqptr->rqNet.PersistentCount) if (!rqptr->BytesRx64) { sys$cancel (rqptr->NetIoPtr->Channel); return; } if (rqptr->ProxyTaskPtr) NetCloseSocket (rqptr); if (rqptr->WebSocketRequest) NetCloseSocket (rqptr); if (state == REQUEST_STATE_PERSIST) NetIoCancel (rqptr->NetIoPtr); } /*****************************************************************************/ /* Check for various tasks and/or associated structures and call their finalise function to progressively shut the request processing down in an orderly fashion. Return true if the run down is complete or false if in-progress. */ BOOL RequestRunDown (REQUEST_STRUCT *rqptr) { /*********/ /* begin */ /*********/ if (WATCHMOD (rqptr, WATCH_MOD_REQUEST)) WatchThis (WATCHITM(rqptr), WATCH_MOD_REQUEST, "RequestRunDown() !AZ", RequestState(rqptr->RequestState)); if (!rqptr->rqHeader.Method) return (true); if (rqptr->RequestState < REQUEST_STATE_ENDING) return (false); if (rqptr->rqAuth.AstFunction) return (false); if (rqptr->AdminTaskPtr) return (false); if (rqptr->DclTaskPtr) { DclTaskRunDown (rqptr->DclTaskPtr); return (false); } if (rqptr->DECnetTaskPtr) if (!DECnetEnd (rqptr)) return (false); if (rqptr->DirTaskPtr) return (false); if (rqptr->FileTaskPtr) return (false); if (rqptr->rqCache.EntryPtr) return (false); if (rqptr->HissTaskPtr) return (false); if (rqptr->HTAdminTaskPtr) return (false); if (rqptr->ProxyTaskPtr) { ProxyEnd (rqptr->ProxyTaskPtr); return (false); } if (rqptr->PutTaskPtr) return (false); if (rqptr->SsiTaskPtr) return (false); if (rqptr->StreamTaskPtr) return (false); if (rqptr->TraceTaskPtr) return (false); if (rqptr->UpdTaskPtr) return (false); if (rqptr->WebDavTaskPtr) { if (rqptr->rqHeader.Method == HTTP_METHOD_GET || rqptr->rqHeader.Method == HTTP_METHOD_HEAD) rqptr->WebDavTaskPtr = NULL; else return (false); } if (rqptr->WebSocketRequest) if (!WebSockEnd (rqptr)) return (false); return (true); } /*****************************************************************************/ /* This function MAY be AST-delivery executed ONE OR MORE TIMES, FROM ITSELF, before a request and/or connection can be finally disposed of. Basically, all ASTs hard-wired or declared *MUST* deliver back to here! */ void RequestEnd (REQUEST_STRUCT *rqptr) { int status; FILE_CONTENT *fcptr; /*********/ /* begin */ /*********/ if (WATCHMOD (rqptr, WATCH_MOD_REQUEST)) WatchThis (WATCHITM(rqptr), WATCH_MOD_REQUEST, "RequestEnd() !&F !AZ", &RequestEnd, RequestState(rqptr->RequestState)); if (!(_PROBER (0, sizeof(REQUEST_STRUCT), rqptr))) { /* 12-DEC-2020 MGD kludge */ ErrorNoticed (rqptr, SS$_BUGCHECK, "_PROBE(!8XL)", FI_LI, rqptr); return; } if (rqptr->RequestState >= REQUEST_STATE_SHUTDOWN) { ErrorNoticed (rqptr, SS$_BUGCHECK, "HTTP/!AZ !AZ//!AZ !AZ !AZ", FI_LI, rqptr->NetworkHttp == 2 ? "2" : "1.n", rqptr->ServicePtr->RequestSchemeNamePtr, rqptr->ServicePtr->ServerHostPort, (rqptr->rqHeader.RequestUriPtr && rqptr->rqHeader.RequestUriPtr[0]) ? rqptr->rqHeader.RequestUriPtr : "(none)", HttpdTimeoutType(rqptr->rqTmr.TimeoutType)); return; } /****************/ /* post-process */ /****************/ if (rqptr->rqHeader.UpgradeSocks5Ptr) if (rqptr->rqAuth.FinalStatus) if (VMSnok (rqptr->rqAuth.FinalStatus)) { /* can only be failed proxy authentication */ ProxySocks5ReplyFail (rqptr); RequestEnd2 (rqptr); return; } /* if WebDAV request not being processed by WebDAV-specific code */ if (rqptr->WebDavTaskPtr) if (rqptr->WebDavTaskPtr->NotWebDavFun) rqptr->WebDavTaskPtr = NULL; if (Watch.RequestPtr == rqptr) WatchEnd (); if (rqptr->RequestState <= REQUEST_STATE_PROCESSING) { /* redacted requests require post proceessing */ if (rqptr->rqResponse.RedactBufferPtr) if (!RequestRedactEnd (rqptr)) return; if (fcptr = rqptr->FileContentPtr) { /* content and handler */ if (fcptr->ContentLength > fcptr->ContentSizeMax) { if (WATCHPNT(rqptr) && (WATCH_CATEGORY(WATCH_CGI) || WATCH_CATEGORY(WATCH_REQUEST))) WatchThis (WATCHITM(rqptr), WATCH_CATEGORY(WATCH_CGI) ? WATCH_CGI : WATCH_REQUEST, "CONTENT !UL exceeded !UL bytes max", fcptr->ContentLength, fcptr->ContentSizeMax); ErrorVmsStatus (rqptr, SS$_BUFFEROVF, FI_LI); rqptr->FileContentPtr = NULL; } else { /* next task gets control once file has been content-handled */ rqptr->FileContentPtr->NextTaskFunction = &RequestEnd; /* file contents loaded, now process using the specified handler */ SysDclAst (rqptr->FileContentPtr->ContentHandlerFunction, rqptr); rqptr->FileContentPtr->ContentHandlerFunction = NULL; return; } } } if (rqptr->rqResponse.LocationPtr) { /***************/ /* redirection */ /***************/ status = RedirectRequest (rqptr); /* SS$_NONLOCAL is returned when non-local redirect :^) */ if (status == SS$_NONLOCAL) return; /* SS$_NORMAL is returned when local redirect */ if (status == SS$_NORMAL) { if (RequestLineParse (rqptr)) { if (RequestProcessFields (rqptr)) { RequestParseExecute (rqptr); return; } } } } /***********/ /* rundown */ /***********/ if (rqptr->RequestState < REQUEST_STATE_ABORT) rqptr->RequestState = REQUEST_STATE_ENDING; if (!RequestRunDown (rqptr)) return; /**********/ /* ending */ /**********/ /* free any X509 client certificate */ SesolaClientCertEnd (rqptr); if (!rqptr->rqHeader.Method) { if (HTTP2_REQUEST(rqptr)) Http2RequestEnd5 (rqptr); else RequestEnd4 (rqptr); return; } /* can still be in a throttle queue even without a client connection */ if (rqptr->rqPathSet.ThrottleSet) ThrottleEnd (rqptr); OdsParseRelease (&rqptr->ParseOds); /* not interested in finalising network if terminated */ if (rqptr->RequestState >= REQUEST_STATE_ABORT) { if (HTTP2_REQUEST(rqptr)) RequestEnd2 (rqptr); else if (rqptr->rqHeader.ContentLength64 && !rqptr->rqBody.ContentCount64) RequestDiscardBody (rqptr); else RequestEnd2 (rqptr); return; } /* flush anything currently remaining in the request output buffer */ if (STR_DSC_LEN(&rqptr->NetWriteBufferDsc)) { NetWriteFullFlush (rqptr, &RequestEnd); return; } /* finalize output compression (before error reporting in plain text!) */ if (rqptr->GzipCompress.DeflateStartStream && !rqptr->GzipCompress.DeflateEndOfStream) { /* flush the GZIP compressed stream (ZLIB) buffers */ NetWrite (rqptr, &RequestEnd, NULL, 0); return; } /* finalize output transfer encoding */ if (rqptr->rqResponse.TransferEncodingChunked) { /* provide the empty chunk */ NetWrite (rqptr, &RequestEnd, NULL, 0); return; } /* if an error has been reported send this to the client */ if (ERROR_REPORTED (rqptr) && !rqptr->RawSocketRequest) { ErrorSendToClient (rqptr); return; } /* if the response header has not yet been sent then do it */ if (rqptr->rqResponse.HeaderGenerated && !rqptr->rqResponse.HeaderSent) { NetWrite (rqptr, &RequestEnd, NULL, 0); return; } if (HTTP2_REQUEST(rqptr)) RequestEnd2 (rqptr); else if (rqptr->rqHeader.ContentLength64 && !rqptr->rqBody.ContentCount64) RequestDiscardBody (rqptr); else RequestEnd2 (rqptr); } /*****************************************************************************/ /* Request rundown has reached a point where final accounting and other post-request processing can be performed. */ void RequestEnd2 (REQUEST_STRUCT *rqptr) { int idx, mful, status; unsigned long Remainder; int64 Time64; char *cptr; NETIO_STRUCT *ioptr; SERVICE_STRUCT *svptr; /*********/ /* begin */ /*********/ if (WATCHMOD (rqptr, WATCH_MOD_REQUEST)) WatchThis (WATCHITM(rqptr), WATCH_MOD_REQUEST, "RequestEnd2() !&F", &RequestEnd2); if (HTTP2_REQUEST (rqptr)) if (!rqptr->Stream2Ptr->RequestEnd) return (Http2RequestEnd2 (rqptr)); /* if request started but didn't get very far */ if (!rqptr->rqHeader.HttpVersion) rqptr->rqResponse.HttpStatus = 408; /* if keeping activity statistics (before counters are decremented) */ if (ActivityTotalMinutes) { InstanceMutexLock (INSTANCE_MUTEX_ACTIVITY); GraphActivityUpdate (rqptr, true); InstanceMutexUnLock (INSTANCE_MUTEX_ACTIVITY); } #if WATCH_MOD if (WATCHPNT(rqptr) && (Watch.Category & WATCH_ONE_SHOT_FLAG) && Watch.RequestPtr) WatchPeek (Watch.RequestPtr, rqptr); #endif /* WATCH_MOD */ /* cache load from network access point */ if (rqptr->rqCache.LoadFromNet) { rqptr->rqCache.LoadStatus = SS$_ENDOFFILE; CacheLoadEnd (rqptr); } else /* cache load has been unsuccessful somewhere, tidy-up */ if (rqptr->rqCache.Loading) { rqptr->rqCache.LoadStatus = SS$_ABORT; CacheLoadEnd (rqptr); } /* Mask possible password component of a request URI before logging, etc. (e.g. turn "ftp://username:password@the.host.name/" into "ftp://username:********@the.host.name/"). This permanently changes the supplied URI (not a problem at this point). Note the '&P' directive in FAO.C that performs the same function. */ if (cptr = rqptr->rqHeader.RequestUriPtr) { while (*cptr) { if (*cptr++ != ':') continue; if (*cptr++ != '/') continue; if (*cptr++ != '/') continue; break; } if (*cptr) { while (*cptr && *cptr != '/' && *cptr != '@') cptr++; if (*cptr == '@') { while (cptr > rqptr->rqHeader.RequestUriPtr && *cptr != ':') cptr--; if (*cptr == ':') { cptr++; while (*cptr && *cptr != '@') *cptr++ = '*'; } } } } if (WATCH_NOT_ONE_SHOT(Watch.Category)) WatchFilterHttpStatus (rqptr); ioptr = rqptr->NetIoPtr; /*************************/ /* locked global section */ /*************************/ InstanceMutexLock (INSTANCE_MUTEX_HTTPD); if (rqptr->rqHeader.Method) NetUpdateProcessing (rqptr, -1); #if WATCH_MOD if (rqptr->rqHeader.PathInfoPtr && MATCH16 (rqptr->rqHeader.PathInfoPtr, ADMIN_REPORT) && (strsame (rqptr->rqHeader.PathInfoPtr, ADMIN_REPORT_HTTP, -1) || strsame (rqptr->rqHeader.PathInfoPtr, ADMIN_REPORT_HPACK, -1))) { /* for development purposes do not include these in the stats */ rqptr->BytesRx64 = 0; } #endif if (rqptr->BytesRx64) { /* some bytes received so assume a real real request */ sys$gettim (&Time64); rqptr->rqResponse.Duration64 = rqptr->rqTime.BeginTime64 - Time64; svptr = rqptr->ServicePtr; svptr->ConnectCount++; /* if a module was never engaged (e.g. mapping only) */ if (!rqptr->AccountingDone++) acptr->DoNoModuleCount++; if (HTTP2_REQUEST(rqptr)) acptr->RequestHttp2Count++; else if (rqptr->rqResponse.HttpVersion == HTTP_VERSION_1_1) acptr->RequestHttp11Count++; else if (rqptr->rqResponse.HttpVersion == HTTP_VERSION_1_0) acptr->RequestHttp10Count++; else if (rqptr->rqResponse.HttpVersion == HTTP_VERSION_0_9) acptr->RequestHttp09Count++; if (rqptr->WebSocketRequest) { acptr->WebSocketCount++; if (rqptr->RawSocketRequest) acptr->WebSocketRawCount++; } else if (!rqptr->ProxyRequest && !rqptr->ProxyTunnelRequest) { /* first update combined HTTP/1+HTTP/2, then HTTP/1 or HTTP/2 */ for (idx = HTTP12;;) { acptr->ResponseDurationCount[idx]++; acptr->ResponseDuration64[idx] += rqptr->rqResponse.Duration64; /* do not update using durations less than 1 uS */ if (rqptr->rqResponse.Duration64 > Delta001uSec) { if (!acptr->ResponseDurationMin64[idx] || acptr->ResponseDurationMin64[idx] < rqptr->rqResponse.Duration64) acptr->ResponseDurationMin64[idx] = rqptr->rqResponse.Duration64; } if (!acptr->ResponseDurationMax64[idx] || rqptr->rqResponse.Duration64 < acptr->ResponseDurationMax64[idx]) acptr->ResponseDurationMax64[idx] = rqptr->rqResponse.Duration64; if (idx != HTTP12) break; if (HTTP2_REQUEST(rqptr)) idx = HTTP2; else idx = HTTP1; } } /* update any remainders in the running tally accumulators */ acptr->BytesRawRx64[HTTP12] += ioptr->BytesTallyRx64; acptr->BytesRawTx64[HTTP12] += ioptr->BytesTallyTx64; acptr->BlocksRawTx64[HTTP12] += ioptr->BlocksTallyTx64; acptr->BlocksRawRx64[HTTP12] += ioptr->BlocksTallyRx64; /* update the service accumulators */ svptr->BytesRawRx64 += ioptr->BytesRawRx64; svptr->BytesRawTx64 += ioptr->BytesRawTx64; /* update the accounting accumulators */ acptr->BytesRawRx64[HTTP12] += ioptr->BytesRawRx64; acptr->BlocksRawRx64[HTTP12] += ioptr->BlocksRawRx64; acptr->BytesRawTx64[HTTP12] += ioptr->BytesRawTx64; acptr->BlocksRawTx64[HTTP12] += ioptr->BlocksRawTx64; if (rqptr->WebDavRequest || rqptr->WhiffOfWebDav) { acptr->WebDavBytesRawTx64[HTTP12] += ioptr->BytesRawTx64; acptr->WebDavBytesRawRx64[HTTP12] += ioptr->BytesRawRx64; } if (rqptr->rqNet.PipelineBufferPtr) { rqptr->rqNet.PipelineRequestCount++; acptr->PipelineRequestCount++; if (rqptr->rqNet.PipelineRequestCount > acptr->PipelineRequestMax) acptr->PipelineRequestMax = rqptr->rqNet.PipelineRequestCount; } else rqptr->rqNet.PipelineRequestCount = 0; svptr->ReadErrorCount += rqptr->rqNet.ReadErrorCount; svptr->WriteErrorCount += rqptr->rqNet.WriteErrorCount; acptr->NetReadErrorCount += rqptr->rqNet.ReadErrorCount; acptr->NetWriteErrorCount += rqptr->rqNet.WriteErrorCount; rqptr->BytesPerSecond = BytesPerSecond (&ioptr->BytesRawRx64, &ioptr->BytesRawTx64, &rqptr->rqResponse.Duration64); /* only for "meaningful" responses */ mful = rqptr->BytesPerSecond > 0; mful = mful && !rqptr->InternalRequest; mful = mful && !rqptr->ProxyRequest; mful = mful && !rqptr->ProxyTunnelRequest; mful = mful && !rqptr->WebSocketRequest; mful = mful && (rqptr->rqResponse.HttpStatus / 100 == 2); mful = mful && (rqptr->rqHeader.Method != HTTP_METHOD_OPTIONS); mful = mful && (rqptr->rqHeader.Method != HTTP_METHOD_TRACE); if (HTTP2_REQUEST(rqptr)) mful = mful && !rqptr->Http2Ptr->FlowStallCount && !rqptr->Stream2Ptr->FlowStallCount; if (mful) { /***********************************/ /* only for "meaningful" responses */ /***********************************/ /* first update combined HTTP/1+HTTP/2, then HTTP/1 or HTTP/2 */ for (idx = HTTP12;;) { acptr->BytesPerSecondRawRx64[idx] += ioptr->BytesRawRx64; acptr->BytesPerSecondRawTotal64[idx] += ioptr->BytesRawRx64; acptr->BytesPerSecondRawTx64[idx] += ioptr->BytesRawTx64; acptr->BytesPerSecondRawTotal64[idx] += ioptr->BytesRawTx64; if (rqptr->BytesPerSecond > acptr->BytesPerSecondMax[idx]) { acptr->BytesPerSecondMax[idx] = rqptr->BytesPerSecond; acptr->BytesPerSecondMaxDuration64[idx] = rqptr->rqResponse.Duration64; acptr->BytesPerSecondMaxBytes64[idx] = ioptr->BytesRawRx64 + ioptr->BytesRawTx64; } if (!acptr->BytesPerSecondMin[idx] || rqptr->BytesPerSecond < acptr->BytesPerSecondMin[idx]) { acptr->BytesPerSecondMin[idx] = rqptr->BytesPerSecond; acptr->BytesPerSecondMinDuration64[idx] = rqptr->rqResponse.Duration64, acptr->BytesPerSecondMinBytes64[idx] += ioptr->BytesRawRx64 + ioptr->BytesRawTx64; } acptr->BytesPerSecondAve[idx] = BytesPerSecond (&acptr->BytesPerSecondRawRx64[idx], &acptr->BytesPerSecondRawTx64[idx], &acptr->ResponseDuration64[idx]); if (idx != HTTP12) break; if (HTTP2_REQUEST(rqptr)) idx = HTTP2; else idx = HTTP1; } } /* update the accumulators related to the HTTP status code */ RequestHttpStatusCode (rqptr); /* update the global section used by HTTPDMON utility */ if (MonitorEnabled) RequestGblSecUpdate (rqptr); } if (InstanceNodeConfig > 1) { /* update the global mutex accounting (with whatever up to this point) */ for (idx = 1; idx <= INSTANCE_MUTEX_COUNT; idx++) { if (InstanceMutexCount[idx]) { HttpdGblSecPtr->MutexCount[idx] += InstanceMutexCount[idx]; InstanceMutexCount[idx] = 0; } if (InstanceMutexWaitCount[idx]) { HttpdGblSecPtr->MutexWaitCount[idx] += InstanceMutexWaitCount[idx]; InstanceMutexWaitCount[idx] = 0; } } } if (ErrorsNoticedCount) { /* update the global accounting here to avoid deadlock issues */ acptr->ErrorsNoticedCount += ErrorsNoticedCount; if (ErrorsNoticedTime64 > acptr->ErrorsNoticedTime64) acptr->ErrorsNoticedTime64 = ErrorsNoticedTime64; ErrorsNoticedTime64 = 0; ErrorsNoticedCount = 0; } InstanceMutexUnLock (INSTANCE_MUTEX_HTTPD); /***************************/ /* unlocked global section */ /***************************/ if (rqptr->BytesRx64) { /* this was a real request, do some finalizing */ if (LoggingEnabled && rqptr->NetIoPtr->BytesRawRx64 && (!rqptr->RedirectErrorStatusCode || (rqptr->RedirectErrorStatusCode && !rqptr->rqResponse.ErrorReportByRedirect))) Logging (rqptr, LOGGING_ENTRY); /* if a request history is being kept then provide the entry */ if (RequestHistoryMax) RequestHistory (rqptr); /* if it's not already been reported */ if (rqptr->rqPathSet.Alert && !(rqptr->rqPathSet.Alert & MAPURL_PATH_ALERT_DONE)) RequestAlert (rqptr); if (WATCHING (rqptr, WATCH_REQUEST)) { if (rqptr->rqNet.ReadErrorCount) WatchThis (WATCHITM(rqptr), WATCH_REQUEST, "RX errors !UL !&S", rqptr->rqNet.ReadErrorCount, rqptr->rqNet.ReadErrorStatus); if (rqptr->rqNet.WriteErrorCount) WatchThis (WATCHITM(rqptr), WATCH_REQUEST, "TX errors !UL !&S", rqptr->rqNet.WriteErrorCount, rqptr->rqNet.WriteErrorStatus); WatchThis (WATCHITM(rqptr), WATCH_REQUEST, "STATUS !3ZL (!AZ) rx:!@UQ tx:!@UQ bytes !AZ !&L B/s!AZ", rqptr->rqResponse.HttpStatus, HttpStatusCodeText(rqptr->rqResponse.HttpStatus), &ioptr->BytesRawRx64, &ioptr->BytesRawTx64, DurationString (rqptr, &rqptr->rqResponse.Duration64), rqptr->BytesPerSecond, HttpdTimeoutType(rqptr->rqTmr.TimeoutType)); if (WATCHING (rqptr, WATCH_INTERNAL)) DictWatch (rqptr->rqDictPtr, NULL, "*"); WatchDataFormatted ("|!#*-\n", 38 + Watch.ItemWidth); } } else if (rqptr->rqNet.PersistentCount) { /* persistent connection timeout */ if (rqptr->ServicePtr->LogTimeoutEvents) { /* timestamp the event for logging purposes */ sys$gettim (&rqptr->rqTime.BeginTime64); sys$numtim (&rqptr->rqTime.BeginTime7, &rqptr->rqTime.BeginTime64); Logging (rqptr, LOGGING_ENTRY); } } /* if client data was set for transparent proxy/balancer/accelerator */ if (rqptr->ClientPtr->SetClientAddress) MapUrl_ResetClientAddress (rqptr); /* reset the running tally accumulators */ ioptr->BytesTallyRx64 = ioptr->BytesTallyTx64 = ioptr->BlocksTallyRx64 = ioptr->BlocksTallyTx64 = 0; /* reset these accumulators in case of persistent connection */ ioptr->BytesRawRx64 = ioptr->BytesRawTx64 = ioptr->BlocksRawRx64 = ioptr->BlocksRawTx64 = 0; /* reset any TLS/SSL data */ if (ioptr->SesolaPtr) SesolaNetIoReset (ioptr); /* HTTP/2 requests are not concerned with connection persistence */ if (HTTP2_REQUEST(rqptr)) Http2RequestEnd5 (rqptr); else if (rqptr->RequestState >= REQUEST_STATE_ABORT) RequestEnd4 (rqptr); else if (VMSok (ioptr->WriteStatus) && (VMSok (ioptr->ReadStatus) || ioptr->ReadStatus == SS$_ENDOFFILE)) RequestEnd3 (rqptr); else RequestEnd4 (rqptr); } /*****************************************************************************/ /* For a persistent connection (request/response) this function calls the approriate setup OR disposes of any SSL connection, then closes the network socket and disposes of the per-request memory. HTTP/1.n only! */ void RequestEnd3 (REQUEST_STRUCT *rqptr) { #ifdef WATCH_MOD static BOOL LimitChecked; static int PersistentLimit; char *cptr; #endif /*********/ /* begin */ /*********/ if (WATCHMOD (rqptr, WATCH_MOD_REQUEST)) WatchThis (WATCHITM(rqptr), WATCH_MOD_REQUEST, "RequestEnd3() !&F !&B !&B", &RequestEnd3, rqptr->PersistentRequest, rqptr->PersistentResponse); #ifdef WATCH_MOD /* development purposes only */ if (!LimitChecked) { LimitChecked = true; if (cptr = SysTrnLnm (WASD_PERSISTENT_LIMIT)) PersistentLimit = atoi(cptr); else PersistentLimit = PERSISTENT_LIMIT; } #endif if (rqptr->rqResponse.RedactBufferPtr && rqptr->rqResponse.RedactBufferCount) { /* a redacted request is implicitly persistent */ rqptr->PersistentRequest = rqptr->PersistentResponse = true; } if (rqptr->PersistentRequest) { /* if disabled, server control requested, or reached the limit */ if (!Config.cfTimeout.Persistent) rqptr->PersistentRequest = false; else if (rqptr->rqNet.ConsecutiveFailureCount >= PERSISTENT_FAILURE_MAX) { rqptr->PersistentRequest = false; if (WATCHING (rqptr, WATCH_CONNECT)) WatchThis (WATCHITM(rqptr), WATCH_CONNECT, "PERSISTENT consecutive failures exceed !UL !AZ,!UL", PERSISTENT_FAILURE_MAX, rqptr->ClientPtr->Lookup.HostName, rqptr->ClientPtr->IpPort); } else #ifdef WATCH_MOD if (rqptr->rqNet.PersistentCount >= PersistentLimit) rqptr->PersistentRequest = false; #else if (rqptr->rqNet.PersistentCount >= PERSISTENT_LIMIT) rqptr->PersistentRequest = false; #endif else if (ControlExitRequested || ControlRestartRequested) rqptr->PersistentRequest = false; else if (VMSnok (rqptr->NetIoPtr->ReadStatus) && rqptr->NetIoPtr->ReadStatus != SS$_ENDOFFILE) rqptr->PersistentRequest = false; else if (VMSnok (rqptr->NetIoPtr->WriteStatus)) rqptr->PersistentRequest = false; } if (!NetConnectSuspend && rqptr->PersistentRequest && rqptr->PersistentResponse && rqptr->rqResponse.HttpStatus != 418 && rqptr->rqResponse.HttpStatus != NetRejectStatusCode && rqptr->NetIoPtr->Channel) { /*************************/ /* persistent connection */ /*************************/ rqptr->RequestState = REQUEST_STATE_PERSIST; RequestPersistentConnection (rqptr); return; } RequestEnd4 (rqptr); } /*****************************************************************************/ /* Close the connection. */ void RequestEnd4 (REQUEST_STRUCT *rqptr) { /*********/ /* begin */ /*********/ if (WATCHMOD (rqptr, WATCH_MOD_REQUEST)) WatchThis (WATCHITM(rqptr), WATCH_MOD_REQUEST, "RequestEnd4()"); rqptr->RequestState = REQUEST_STATE_SHUTDOWN; NetIoEnd (rqptr->NetIoPtr); InstanceMutexLock (INSTANCE_MUTEX_HTTPD); NetUpdateConnected (rqptr, -1); InstanceMutexUnLock (INSTANCE_MUTEX_HTTPD); RequestEnd5 (rqptr); } /*****************************************************************************/ /* Dispose of the request structures (finally!) */ void RequestEnd5 (REQUEST_STRUCT *rqptr) { BOOL OneShot; /*********/ /* begin */ /*********/ if (WATCHMOD (rqptr, WATCH_MOD_REQUEST)) WatchThis (WATCHITM(rqptr), WATCH_MOD_REQUEST, "RequestEnd5() !&F", &RequestEnd5); rqptr->RequestState = REQUEST_STATE_NOMORE; HttpdSupervisorList (rqptr, -1); /* free any allocated pipeline buffer */ if (rqptr->rqNet.PipelineBufferPtr) VmFree (rqptr->rqNet.PipelineBufferPtr, FI_LI); /* free the per-request memory heap */ VmFreeHeap (rqptr, FI_LI); /* remove from the request list */ ListRemove (&RequestList, rqptr); /* if this request was being (one-shot) WATCHed */ OneShot = rqptr->WatchItem & WATCH_ITEM_ONE_SHOT_FLAG; /* resets any associated structures (e.g. HTTP/2) */ if (rqptr->WatchItem) WatchSetWatch (rqptr, 0); /* free memory allocated for the connection structure */ VmFreeRequest (rqptr, FI_LI); if (OneShot) WatchEnd (); /* don't bother checking for exit or restart if connections still exist */ if (!NetCurrentProcessing) { /* if the control module has requested server exit or restart */ if (ControlExitRequested) { fprintf (stdout, "%%HTTPD-I-CONTROL, delayed server exit\n"); ExitStatus = SS$_NORMAL; HttpdExit (&ExitStatus); /* cancel any startup messages provided for the monitor */ HttpdGblSecPtr->StatusMessage[0] = '\0'; /* record server event */ GraphActivityEvent (ACTIVITY_DELPRC); sys$delprc (0, 0); } /* check the server's doing the right thing */ HttpdCheckPriv (FI_LI); } } /*****************************************************************************/ /* Assumes the connection is capable of being persistent. It checks if a potential pipelined request has been (previously) detected. If it has it notes the fact and buffers the pipelined network data. It then reset the request data structure ready for a receiving the nertwork data of a new request. If pipelined data was buffered this is restored to the network buffer (as if 'fresh' from the network connection) otherwise a buffer is read from the network. In either case this is passed to RequestGet(). */ void RequestPersistentConnection (REQUEST_STRUCT *rqptr) { BOOL RedactRequest, WatchThisOne; int PathSetTimeoutPersistent, RedactBufferCount, RedactBufferSize; char *RedactBufferPtr; /*********/ /* begin */ /*********/ if (WATCHMOD (rqptr, WATCH_MOD_REQUEST)) WatchThis (WATCHITM(rqptr), WATCH_MOD_REQUEST, "RequestPersistentConnection() !&F", &RequestPersistentConnection); rqptr->rqNet.PersistentCount++; PathSetTimeoutPersistent = rqptr->rqPathSet.TimeoutPersistent; RedactRequest = false; if (rqptr->rqResponse.RedactBufferPtr && rqptr->rqResponse.RedactBufferCount) { /********************/ /* redacted request */ /********************/ RedactRequest = true; InstanceGblSecIncrLong (&acptr->RedactRequestCount); /* this is NOT heap memory and so will persist across requests */ RedactBufferPtr = rqptr->rqResponse.RedactBufferPtr; RedactBufferSize = rqptr->rqResponse.RedactBufferSize; RedactBufferCount = rqptr->rqResponse.RedactBufferCount; if (WATCHPNT(rqptr) && (WATCH_CATEGORY(WATCH_RESPONSE) || WATCH_CATEGORY(WATCH_RESPONSE_HEADER))) { char *cptr, *zptr; zptr = (cptr = RedactBufferPtr) + RedactBufferCount; while (cptr < zptr && !(*cptr == '\r' && SAME4(cptr,'\r\n\r\n')) && !(*cptr == '\n' && SAME2(cptr,'\n\n'))) cptr++; if (cptr < zptr && *cptr == '\r') cptr += 4; else if (cptr < zptr && *cptr == '\n') cptr += 2; WatchThis (WATCHITM(rqptr), WATCH_RESPONSE, "REDACT !UL bytes (!UL header, !UL body)", RedactBufferCount, cptr-RedactBufferPtr, RedactBufferCount-(cptr-RedactBufferPtr)); if (WATCH_CATEGORY(WATCH_RESPONSE_HEADER)) WatchDataDump (RedactBufferPtr, cptr-RedactBufferPtr); } } else if (rqptr->rqNet.PipelineBufferPtr && rqptr->rqNet.PipelineBufferCount) { /*********************/ /* pipelined request */ /*********************/ if (WATCHMOD (rqptr, WATCH_MOD_REQUEST)) WatchDataFormatted ("PIPELINE {!UL}!-!#AZ\n", rqptr->rqNet.PipelineBufferCount, rqptr->rqNet.PipelineBufferPtr); if (WATCHING (rqptr, WATCH_CONNECT)) WatchThis (WATCHITM(rqptr), WATCH_CONNECT, "PIPELINE !UL !AZ,!UL", rqptr->rqNet.PipelineRequestCount+1, rqptr->ClientPtr->Lookup.HostName, rqptr->ClientPtr->IpPort); } else { /**********************/ /* persistent request */ /**********************/ if (rqptr->rqNet.PipelineBufferPtr) { /* no need for this any longer */ VmFree (rqptr->rqNet.PipelineBufferPtr, FI_LI); rqptr->rqNet.PipelineBufferPtr = NULL; rqptr->rqNet.PipelineBufferSize = 0; } if (WATCHING (rqptr, WATCH_CONNECT)) WatchThis (WATCHITM(rqptr), WATCH_CONNECT, "PERSISTENT !UL with !AZ,!UL", rqptr->rqNet.PersistentCount, rqptr->ClientPtr->Lookup.HostName, rqptr->ClientPtr->IpPort); } /****************/ /* prepare next */ /****************/ /* These exist in the permanent part of the request structure but the memory is from the request heap. They persist across redirects but not persistent connections. No need to explicitly destroy the dictionary. It will go with the freed heap. */ rqptr->NotePadDictEntry = NULL; rqptr->NotePadPtr = rqptr->ProxyReverseLocationPtr = NULL; rqptr->RedactCount = rqptr->RedirectCount = rqptr->RedirectedXray = 0; /* free the per-request memory heap */ VmFreeHeap (rqptr, FI_LI); /* zero the portion of the request structure that is not persistent */ memset ((char*)&rqptr->ZeroedBegin, 0, (char*)&rqptr->ZeroedEnd - (char*)&rqptr->ZeroedBegin); /* initialise a new dictionary */ rqptr->rqDictPtr = DictCreate (rqptr, -1); /* Timestamp the persistent connection period for RequestReport(). Transaction will again be time-stamped if/when request received! */ sys$gettim (&rqptr->rqTime.BeginTime64); if (rqptr->ServicePtr->ErrorReportPath[0]) { rqptr->rqResponse.ErrorReportByRedirect = true; /* reset thread-permanent storage */ rqptr->RedirectErrorStatusCode = 0; rqptr->RedirectErrorAuthRealmDescrPtr = NULL; } /* initialize the timer for persistent connection */ HttpdTimerSet (rqptr, TIMER_PERSISTENT, PathSetTimeoutPersistent); InstanceMutexLock (INSTANCE_MUTEX_HTTPD); NetUpdatePersistent (+1); InstanceMutexUnLock (INSTANCE_MUTEX_HTTPD); if (rqptr->rqNet.PipelineBufferPtr && rqptr->rqNet.PipelineBufferCount) { /*********************/ /* pipelined request */ /*********************/ rqptr->rqNet.ReadBufferSize = rqptr->rqNet.PipelineBufferSize; rqptr->rqNet.ReadBufferPtr = VmGetHeap (rqptr, rqptr->rqNet.ReadBufferSize); /* copy previous trailing data into the read buffer */ memcpy (rqptr->rqNet.ReadBufferPtr, rqptr->rqNet.PipelineBufferPtr, rqptr->rqNet.PipelineBufferCount); /* without a real network read just fudge the status */ NetIoReadStatus (rqptr->NetIoPtr, RequestGet, rqptr, SS$_NORMAL, rqptr->rqNet.PipelineBufferCount); /* emptied the buffer (may fill again with further trailing octets) */ rqptr->rqNet.PipelineBufferCount = 0; return; } rqptr->rqNet.ReadBufferSize = NetReadBufferSize; rqptr->rqNet.ReadBufferPtr = VmGetHeap (rqptr, NetReadBufferSize); if (RedactRequest) { /********************/ /* redacted request */ /********************/ rqptr->rqNet.RedactBufferPtr = RedactBufferPtr; /* size here becomes the actual count of data in the buffer */ rqptr->rqNet.RedactBufferSize = RedactBufferCount; /* count tracks the quantity of data read from the buffer */ rqptr->rqNet.RedactBufferCount = 0; } NetRead (rqptr, &RequestGet, rqptr->rqNet.ReadBufferPtr, rqptr->rqNet.ReadBufferSize); } /*****************************************************************************/ /* Increment the approriate HTTP status code accumulator. Assumes that the appropriate global section is already locked. The accumulator for status codes is a 'somewhat sparse' array where each status code group, 100.., 200.., etc., is given 30 integers for codes 00..29 (heaps for most groups) resulting in 102 being index 32, 410 being index 130, 501 being index 151, etc. Convenient without being too profligate with memory. */ RequestHttpStatusCode (REQUEST_STRUCT *rqptr) { int idx, StatusCode, StatusGroup, StatusMod; /*********/ /* begin */ /*********/ if (WATCHMOD (rqptr, WATCH_MOD_REQUEST)) WatchThis (WATCHITM(rqptr), WATCH_MOD_REQUEST, "RequestHttpStatusCode() !UL", rqptr->rqResponse.HttpStatus); #if WATCH_MOD /* small sanity check when module WATCHing is compiled-in */ if (sizeof(acptr->ResponseStatusCodeCount) != sizeof(unsigned long) * (6*30)) ErrorExitVmsStatus (SS$_BUGCHECK, ErrorSanityCheck, FI_LI); #endif /* WATCH_MOD */ StatusGroup = (StatusCode = rqptr->rqResponse.HttpStatus) / 100; if (StatusGroup < 0 || StatusGroup > 5) StatusGroup = 0; acptr->ResponseStatusCodeGroup[StatusGroup]++; if (StatusGroup == 1 || StatusGroup == 2 || StatusGroup == 3) rqptr->rqNet.ConsecutiveFailureCount = 0; else rqptr->rqNet.ConsecutiveFailureCount++; if (StatusCode) { if ((StatusCode >= 100 && StatusCode <= 102) || (StatusCode >= 200 && StatusCode <= 207) || (StatusCode >= 300 && StatusCode <= 307) || (StatusCode >= 400 && StatusCode <= 418) || (StatusCode >= 421 && StatusCode <= 424) || (StatusCode >= 500 && StatusCode <= 505) || (StatusCode == 507)) { /* constrain to n00..n29 */ StatusMod = StatusCode % (StatusGroup * 10); /* an index from 0..179 */ idx = (StatusGroup * 30) + StatusMod; } else { /* HTTP status code set to something we don't know about */ idx = 0; } acptr->ResponseStatusCodeCount[idx]++; if (StatusCode == 403) acptr->RequestForbiddenCount++; } } /*****************************************************************************/ /* Return an integer that can be used as an index into the HTTP status code accumulator array. See explanation in RequestHttpStatusCode() above. */ int RequestHttpStatusIndex (int StatusCode) { int StatusGroup, StatusMod; /*********/ /* begin */ /*********/ StatusGroup = StatusCode / 100; if (StatusGroup < 0 || StatusGroup > 5) StatusGroup = 0; /* constrain to 0..599 */ if (StatusCode < 100 || StatusCode > 599) StatusCode = 0; /* constrain to n00..n29 */ if (!StatusCode || (StatusMod = (StatusCode % (StatusGroup * 10))) > 29) StatusCode = StatusMod = 0; /* generate an index from 0..179 */ return ((StatusGroup * 30) + StatusMod); } /*****************************************************************************/ /* Provide a path alert notification. */ void RequestAlert (REQUEST_STRUCT *rqptr) { int status, AlertItem, AlertValue; char Buffer [1024]; unsigned long *vecptr; unsigned long FaoVector [16]; /*********/ /* begin */ /*********/ if (WATCHMOD (rqptr, WATCH_MOD_REQUEST)) WatchThis (WATCHITM(rqptr), WATCH_MOD_REQUEST, "RequestAlert() 0x!2XL", rqptr->rqPathSet.Alert); AlertValue = rqptr->rqPathSet.Alert; /* set this bit to indicated the alert has been signalled */ rqptr->rqPathSet.Alert |= MAPURL_PATH_ALERT_DONE; if (AlertValue >= 100 && AlertValue <= 599) { /* alert on a specific HTTP status code */ AlertItem = AlertValue % 100; if (AlertItem == 99) { /* comparing to a category (e.g. 4nn, 5nn) but not that category */ if (AlertValue / 100 != rqptr->rqResponse.HttpStatus / 100) return; } else { /* comparing to an individual status value */ if (rqptr->rqResponse.HttpStatus != AlertValue) return; } /* continue on to report the alert */ } InstanceGblSecIncrLong (&acptr->PathAlertCount); vecptr = &FaoVector; *vecptr++ = UserAtClient(rqptr); *vecptr++ = rqptr->rqHeader.MethodName; *vecptr++ = rqptr->rqHeader.RequestUriPtr; if (rqptr->rqResponse.HttpStatus) { *vecptr++ = " !UL"; *vecptr++ = rqptr->rqResponse.HttpStatus; } else *vecptr++ = ""; FaoCheck (sizeof(FaoVector), &FaoVector, vecptr, FI_LI); status = FaolToBuffer (Buffer, sizeof(Buffer), NULL, "!&@ !AZ !AZ!&@", &FaoVector); if (VMSnok (status)) ErrorNoticed (rqptr, status, NULL, FI_LI); FaoToStdout ("%HTTPD-W-ALERT, !20%D, !AZ\n", 0, Buffer); if (Config.cfOpcom.Messages & OPCOM_ADMIN) FaoToOpcom ("%HTTPD-W-ALERT, !AZ", Buffer); if (WATCHING (rqptr, WATCH_REQUEST)) WatchThis (WATCHITM(rqptr), WATCH_REQUEST, "ALERT !AZ", Buffer); } /*****************************************************************************/ /* Some browsers seem unhappy if a request is aborted too quickly (and it's socket closed) during the POST or PUT of a request body (reports connection errors). We're here because all the body was not read during request processing (probably because an error is to be reported). Read some more of the body (just dropping it into the bit-bucket). Then after sufficient has been read (or end-of-content) continue to dispose of the request. */ void RequestDiscardBody (REQUEST_STRUCT *rqptr) { /*********/ /* begin */ /*********/ if (WATCHMOD (rqptr, WATCH_MOD_REQUEST)) WatchThis (WATCHITM(rqptr), WATCH_MOD_REQUEST, "RequestDiscardBody() !&F !UL !&S", &RequestDiscardBody, rqptr->rqBody.DiscardReadCount, rqptr->rqBody.DataStatus); if (WATCHMOD (rqptr, WATCH_MOD_REQUEST)) WatchThis (WATCHITM(rqptr), WATCH_MOD_REQUEST, "RequestDiscardBody() !&F !UL !&S", &RequestDiscardBody, rqptr->rqBody.DiscardReadCount, rqptr->rqBody.DataStatus); if (rqptr->rqBody.AstFunction != RequestDiscardBody) { /* ensure we just read raw and throw-away, no fancy processing thanks */ rqptr->rqBody.ProcessFunction = NULL; rqptr->rqBody.UnEncodeStream = false; if (rqptr->rqBody.AstFunction == NULL) { BodyReadBegin (rqptr, RequestDiscardBody, NULL); return; } rqptr->rqBody.AstFunction = RequestDiscardBody; } if (VMSnok(rqptr->rqBody.DataStatus)) { /* error or enough read (ensure it looks like it anyway) */ rqptr->rqBody.DataStatus = SS$_ENDOFFILE; RequestEnd2 (rqptr); return; } BodyRead (rqptr); } /*****************************************************************************/ /* Process (authorization or standard) script 'redact' callout. */ void RequestRedact ( REQUEST_STRUCT *rqptr, char *OutputPtr, int OutputCount, BOOL ProvideResponse ) { static char RspBadParam [] = "400 Bad parameter", RspSuccess [] = "200 Success", RspTooLarge [] = "413 Entity too large"; int MaxKbytes; char *cptr; /*********/ /* begin */ /*********/ if (WATCHMOD (rqptr, WATCH_MOD_REQUEST)) WatchThis (WATCHITM(rqptr), WATCH_MOD_REQUEST, "RequestRedact() !UL", OutputCount); if (!(MaxKbytes = rqptr->rqPathSet.PutMaxKbytes)) MaxKbytes = Config.cfMisc.PutMaxKbytes; if (strsame (OutputPtr, "REDACT:", 7)) { /***********/ /* REDACT: */ /***********/ /* restart a request from a rebuilt header (and body) */ OutputPtr += 7; OutputCount -= 7; if (WATCHMOD (rqptr, WATCH_MOD_REQUEST)) WatchThis (WATCHITM(rqptr), WATCH_MOD_REQUEST, "!UL+!UL=!UL !UL 0x!XL", rqptr->rqResponse.RedactBufferCount, OutputCount, rqptr->rqResponse.RedactBufferCount+OutputCount, rqptr->rqResponse.RedactBufferSize, rqptr->rqResponse.RedactBufferPtr); if (rqptr->rqResponse.RedactBufferCount + OutputCount > rqptr->rqResponse.RedactBufferSize) { /* expand the buffer size in chunks the size of a DCL I/O */ rqptr->rqResponse.RedactBufferSize += DclSysOutputSize; if ((rqptr->rqResponse.RedactBufferSize >> 10) > MaxKbytes) { /* limit total to the maximum request body size anyway */ if (ProvideResponse) DclCalloutQio (rqptr, RspTooLarge, sizeof(RspTooLarge)-1); return; } if (rqptr->rqResponse.RedactBufferPtr) { /* note, this is NOT heap memory */ rqptr->rqResponse.RedactBufferPtr = VmRealloc (rqptr->rqResponse.RedactBufferPtr, rqptr->rqResponse.RedactBufferSize, FI_LI); } else { /* note, this is NOT heap memory */ rqptr->rqResponse.RedactBufferPtr = VmGet (rqptr->rqResponse.RedactBufferSize); rqptr->RedactCount++; } } if (OutputCount) { memcpy (rqptr->rqResponse.RedactBufferPtr + rqptr->rqResponse.RedactBufferCount, OutputPtr, OutputCount); rqptr->rqResponse.RedactBufferCount += OutputCount; } if (ProvideResponse) DclCalloutQio (rqptr, RspSuccess, sizeof(RspSuccess)-1); return; } if (strsame (OutputPtr, "REDACT-SIZE:", 12)) { /****************/ /* REDACT-SIZE: */ /****************/ for (cptr = OutputPtr+12; *cptr && isspace(*cptr); cptr++); if (isdigit(*cptr) && !rqptr->rqResponse.RedactBufferSize) { rqptr->rqResponse.RedactBufferSize = atoi(cptr); if (*cptr == '0' && !rqptr->rqResponse.RedactBufferSize) { /* a redact size of zero resets any in-progress redact */ if (rqptr->rqResponse.RedactBufferPtr) { VmFreeFromHeap (rqptr, rqptr->rqResponse.RedactBufferPtr, FI_LI); rqptr->rqResponse.RedactBufferPtr = NULL; rqptr->rqResponse.RedactBufferCount = 0; if (rqptr->RedactCount) rqptr->RedactCount--; } if (ProvideResponse) DclCalloutQio (rqptr, RspSuccess, sizeof(RspSuccess)-1); return; } else if (rqptr->rqResponse.RedactBufferPtr) { if (ProvideResponse) DclCalloutQio (rqptr, RspBadParam, sizeof(RspBadParam)-1); return; } else if ((rqptr->rqResponse.RedactBufferSize >> 10) <= MaxKbytes) { /* limit total to the maximum request body size anyway */ rqptr->rqResponse.RedactBufferSize = ((rqptr->rqResponse.RedactBufferSize / 512) + 1) * 512; rqptr->rqResponse.RedactBufferPtr = VmGet (rqptr->rqResponse.RedactBufferSize); rqptr->RedactCount++; if (ProvideResponse) DclCalloutQio (rqptr, RspSuccess, sizeof(RspSuccess)-1); return; } if (ProvideResponse) DclCalloutQio (rqptr, RspTooLarge, sizeof(RspTooLarge)-1); return; } if (ProvideResponse) DclCalloutQio (rqptr, RspBadParam, sizeof(RspBadParam)-1); return; } ErrorNoticed (rqptr, SS$_BUGCHECK, NULL, FI_LI); } /*****************************************************************************/ /* Perform the redacted request and return false (do not run down the rquest) or run down the redacted resources and return true (continue running down the request). */ BOOL RequestRedactEnd (REQUEST_STRUCT *rqptr) { /*********/ /* begin */ /*********/ if (WATCHMOD (rqptr, WATCH_MOD_REQUEST)) WatchThis (WATCHITM(rqptr), WATCH_MOD_REQUEST, "RequestRedactEnd()"); if (rqptr->rqResponse.HeaderSent) { if (WATCHING (rqptr, WATCH_RESPONSE)) WatchThis (WATCHITM(rqptr), WATCH_RESPONSE, "REDACT ignored, HTTP response already sent!!"); } else if (!rqptr->rqResponse.RedactBufferCount) { rqptr->rqResponse.HttpStatus = 500; ErrorGeneral (rqptr, MsgFor(rqptr,MSG_REQUEST_REDIRECTION), FI_LI); } else if (rqptr->RedactCount >= REQUEST_REDACT_MAX) { rqptr->rqResponse.HttpStatus = 500; ErrorGeneral (rqptr, MsgFor(rqptr,MSG_REQUEST_REDIRECTION), FI_LI); } else { /* perform the redacted request */ RequestPersistentConnection (rqptr); return (false); } /* explicitly free this non-heap memory */ VmFree (rqptr->rqResponse.RedactBufferPtr, FI_LI); rqptr->rqResponse.RedactBufferPtr = NULL; rqptr->rqResponse.RedactBufferSize = 0; rqptr->rqResponse.RedactBufferCount = 0; return (true); } /*****************************************************************************/ /* Process first HTTP/1.n packet read from the client. In most cases this will contain the complete HTTP header and it can be processed without building up a buffered header. If it does not contain the full header allocate heap memory to contain the current packet and queue subsequent read(s), expanding the request header heap memory, until all is available (or it becomes completely rediculous!). */ void RequestGet (REQUEST_STRUCT *rqptr) { #define STRCAT(string) { \ for (cptr = string; *cptr && sptr < zptr; *sptr++ = *cptr++); \ } #define CONTROL_D 0x04 #define CONTROL_Z 0x1a int idx, bcnt, status, SSLv2len; char *bptr, *cptr, *czptr, *sptr, *zptr; DICT_ENTRY_STRUCT *denptr; /*********/ /* begin */ /*********/ if (WATCHMOD (rqptr, WATCH_MOD_REQUEST)) WatchThis (WATCHITM(rqptr), WATCH_MOD_REQUEST, "RequestGet() !&F !&S !UL !UL !UL", &RequestGet, rqptr->NetIoPtr->ReadStatus, rqptr->NetIoPtr->ReadCount, rqptr->rqNet.ReadBufferSize, rqptr->rqHeader.RequestHeaderLength); if (rqptr->NetIoPtr->ServicePtr->ShareSSH) { if (SAME4 (rqptr->rqNet.ReadBufferPtr, 'SSH-')) { RequestShareBegin (rqptr); return; } if (rqptr->NetIoPtr->ReadStatus == SS$_CANCEL) if (!rqptr->rqNet.PersistentCount) if (!rqptr->BytesRx64) { RequestShareBegin (rqptr); return; } } if (VMSnok (rqptr->NetIoPtr->ReadStatus)) { /***************/ /* read failed */ /***************/ /* disconnected or otherwise failed */ if (!rqptr->BytesRx64) if (rqptr->rqNet.PersistentCount) { InstanceMutexLock (INSTANCE_MUTEX_HTTPD); NetUpdatePersistent (-1); InstanceMutexUnLock (INSTANCE_MUTEX_HTTPD); } RequestEnd (rqptr); return; } rqptr->RequestState = REQUEST_STATE_HEADER; if (!rqptr->rqHeader.RequestHeaderPtr && !rqptr->rqNet.RedactBufferPtr) { /*****************/ /* request begin */ /*****************/ if (NetCurrentProcessing >= NetConcurrentProcessMax) { /************/ /* too busy */ /************/ if (WATCHING (rqptr, WATCH_CONNECT)) WatchThis (WATCHITM(rqptr), WATCH_CONNECT, "PROCESS !UL too-busy !AZ,!UL on !AZ//!AZ,!AZ", NetConcurrentProcessMax, rqptr->ClientPtr->Lookup.HostName, rqptr->ClientPtr->IpPort, rqptr->ServicePtr->RequestSchemeNamePtr, rqptr->ServicePtr->ServerIpAddressString, rqptr->ServicePtr->ServerPortString); InstanceGblSecIncrLong (&acptr->ProcessingTooBusyCount); InstanceGblSecIncrLong (&acptr->ResponseStatusCodeGroup[5]); rqptr->rqResponse.HttpStatus = 503; ErrorGeneral (rqptr, MsgFor(rqptr,MSG_GENERAL_TOO_BUSY), FI_LI); RequestEnd (rqptr); return; } /* these need to be built in RequestGet() due to SesolaNetAccept() */ if (rqptr->ServicePtr->ProxyTunnel == PROXY_TUNNEL_RAW) { /* fake a CONNECT request */ zptr = (sptr = rqptr->rqNet.ReadBufferPtr) + rqptr->rqNet.ReadBufferSize; STRCAT ("CONNECT "); STRCAT (rqptr->ServicePtr->ServerHostName); STRCAT (":0 HTTP/1.1\r\nUser-Agent: "); STRCAT (SoftwareID); STRCAT ("\r\nHost: "); STRCAT (rqptr->ServicePtr->ServerHostPort); STRCAT ("\r\nX-Forwarded-For: "); STRCAT (rqptr->ClientPtr->IpAddressString); STRCAT ("\r\nUpgrade: WASD-tunnel-raw\r\n\r\n"); /* as if it came from the network */ rqptr->NetIoPtr->ReadCount = sptr - rqptr->rqNet.ReadBufferPtr; } else if (rqptr->ServicePtr->ProxyTunnel == PROXY_TUNNEL_FIREWALL) { /* fake the start of a connect request */ memcpy (rqptr->rqNet.ReadBufferPtr, "CONNECT ", 8); /* as if it came from the network */ rqptr->NetIoPtr->ReadCount = 8; } else if (rqptr->ConnectRequest) { /* fake a basic request */ zptr = (sptr = rqptr->rqNet.ReadBufferPtr) + rqptr->rqNet.ReadBufferSize; STRCAT ("GET / HTTP/1.1\r\nHost: "); STRCAT (rqptr->ServicePtr->ServerHostPort); STRCAT ("\r\nUser-Agent: "); STRCAT (SoftwareID); STRCAT ("\r\nConnection: close\r\n\r\n"); /* as if it came from the network */ rqptr->NetIoPtr->ReadCount = sptr - rqptr->rqNet.ReadBufferPtr; /* ONLY the response body in fact */ rqptr->rqResponse.HeaderGenerated = rqptr->rqResponse.HeaderSent = true; rqptr->rqResponse.HttpStatus = 200; } else if (rqptr->RawSocketRequest) { /* fake a CONNECT request */ zptr = (sptr = rqptr->rqNet.ReadBufferPtr) + rqptr->rqNet.ReadBufferSize; STRCAT ("CONNECT "); STRCAT (rqptr->ServicePtr->ServerHostName); STRCAT (":0 HTTP/1.1\r\nUser-Agent: "); STRCAT (SoftwareID); STRCAT ("\r\nHost: "); STRCAT (rqptr->ServicePtr->ServerHostPort); STRCAT ("\r\nUpgrade: WASD-websocket-raw\r\n\r\n"); /* as if it came from the network */ rqptr->NetIoPtr->ReadCount = sptr - rqptr->rqNet.ReadBufferPtr; } } else if (rqptr->ServicePtr->ProxyTunnel == PROXY_TUNNEL_FIREWALL) { /* look for the end-of-line */ sptr = rqptr->rqNet.ReadBufferPtr + 8; while (*sptr && ISLWS(*sptr)) sptr++; cptr = sptr; while (NOTLWS(*sptr) && NOTEOL(*sptr) && *sptr != CONTROL_Z && *sptr != CONTROL_D) sptr++; if (*sptr) { if (cptr == sptr) { RequestEnd (rqptr); return; } /* end-of-line (hostname[:port]), complete the pseudo-header */ zptr = rqptr->rqNet.ReadBufferPtr + rqptr->rqNet.ReadBufferSize; STRCAT (" HTTP/1.1\r\nUser-Agent: "); STRCAT (SoftwareID); STRCAT ("\r\nHost: "); STRCAT (rqptr->ServicePtr->ServerHostPort); STRCAT ("\r\n\r\n"); /* as if it came from the network */ rqptr->NetIoPtr->ReadCount = sptr - rqptr->rqNet.ReadBufferPtr; } } if (rqptr->NetIoPtr->ReadCount >= Http2ClientPrefaceLength && MATCH8 (rqptr->rqNet.ReadBufferPtr, Http2ClientPreface) && MATCH0 (rqptr->rqNet.ReadBufferPtr, Http2ClientPreface, Http2ClientPrefaceLength)) { /******************/ /* HTTP/2 preface */ /******************/ if (Http2Enabled && rqptr->ServicePtr->Http2Enabled) { if (Http2Preface (rqptr)) return; } else if (WATCHING (rqptr, WATCH_REQUEST)) WatchThis (WATCHITM(rqptr), WATCH_REQUEST, "HTTP/2 preface received but HTTP/2 not enabled"); } if (!rqptr->BytesRx64) { /****************/ /* initial read */ /****************/ if (rqptr->ServicePtr->ProxyService) { if (rqptr->NetIoPtr->ReadCount >= 3 && rqptr->rqNet.ReadBufferPtr[0] == 5 && rqptr->rqNet.ReadBufferPtr[1] == rqptr->NetIoPtr->ReadCount - 2) { ProxySocks5 (rqptr); return; } if (rqptr->NetIoPtr->ReadCount >= 9 && rqptr->rqNet.ReadBufferPtr[0] == 4 && rqptr->rqNet.ReadBufferPtr[1] == 1 && *(USHORTPTR)(rqptr->rqNet.ReadBufferPtr+2) <= 65535) { ProxySocks4 (rqptr); return; } } if (rqptr->rqNet.PersistentCount) { InstanceMutexLock (INSTANCE_MUTEX_HTTPD); NetUpdatePersistent (-1); InstanceMutexUnLock (INSTANCE_MUTEX_HTTPD); /* re-timestamp the persistent connection */ sys$gettim (&rqptr->rqTime.BeginTime64); sys$numtim (&rqptr->rqTime.BeginTime7, &rqptr->rqTime.BeginTime64); HttpGmTimeString (rqptr->rqTime.GmDateTime, &rqptr->rqTime.BeginTime64); HttpdTimerSet (rqptr, TIMER_INPUT, 0); /* redo what is done in NetAcceptProcess() */ if (WATCH_NOT_ONE_SHOT(Watch.Category)) if (Watch.FilterSet) WatchFilterClientService (rqptr); else WatchSetWatch (rqptr, WATCH_NEW_ITEM); } } rqptr->BytesRx64 += rqptr->NetIoPtr->ReadCount; cptr = bptr = rqptr->rqNet.ReadBufferPtr; czptr = bptr + (bcnt = rqptr->BytesRx64); if (WATCHMOD (rqptr, WATCH_MOD_REQUEST)) WatchDataFormatted ("!#AZ", bcnt, bptr); if (!rqptr->rqHeader.HttpVersion) { /**************************/ /* parse the request line */ /**************************/ for (;;) { if (bcnt > 64) { /* if it looks suspiciously like an SSLv3 or SSLv2 handshake */ SSLv2len = bptr[0] & 0x80 ? ((bptr[0] & 0x7f) << 8) | bptr[1] : ((bptr[0] & 0x3f) << 8) | bptr[1]; if (SAME2(bptr,0x0316) || SSLv2len == bcnt-2) { InstanceGblSecIncrLong (&acptr->RequestErrorCount); rqptr->rqResponse.HttpStatus = 400; ErrorGeneral (rqptr, MsgFor(rqptr,MSG_GENERAL_NOT_SSL), FI_LI); RequestEnd (rqptr); return; } } /* check for buggy at the end of last request body */ if (bcnt >= 2 && SAME2(bptr,'\r\n')) { if (WATCHING (rqptr, WATCH_REQUEST)) { WatchThis (WATCHITM(rqptr), WATCH_REQUEST, "RFC 2616 section 4.1 bug?"); WatchDataDump (bptr, bcnt); } /* if next request is already in the buffer */ if (bcnt > 2) { /* skip it and adjust counts */ bcnt -= 2; rqptr->BytesRx64 -= 2; *(USHORTPTR)(bptr + bcnt) = '\0\0'; continue; } RequestEnd (rqptr); return; } /* look for end-of-line while checking for errant control chars */ while (cptr < czptr && NOTCTL(*cptr)) cptr++; if (cptr < czptr && (*cptr == '\n' || SAME2(cptr,'\r\n'))) { /* have a full request line (or at least one full newline!) */ denptr = DictInsert (rqptr->rqDictPtr, DICT_TYPE_INTERNAL, "request_line", 12, bptr, cptr - bptr); rqptr->rqHeader.RequestLinePtr = DICT_GET_VALUE(denptr); rqptr->rqHeader.RequestLineLength = DICT_GET_VALUE_LEN(denptr); if (RequestLineParse (rqptr)) { if (rqptr->rqHeader.Method) { InstanceMutexLock (INSTANCE_MUTEX_HTTPD); NetUpdateProcessing (rqptr, +1); InstanceMutexUnLock (INSTANCE_MUTEX_HTTPD); } if (rqptr->rqHeader.HttpVersion) { if (WATCH_NOT_ONE_SHOT(Watch.Category)) WatchFilterHttpProtocol (rqptr); if (WATCHING (rqptr, WATCH_REQUEST)) if (!rqptr->rqHeader.WatchNewRequest) { rqptr->rqHeader.WatchNewRequest = true; WatchDataFormatted ("|!#*+\n", 38 + Watch.ItemWidth); } } break; } RequestEnd (rqptr); return; } else if (cptr < czptr && ISCTL(*cptr)) { /* lurking non-printable */ if (WATCHING (rqptr, WATCH_REQUEST)) { WatchThis (WATCHITM(rqptr), WATCH_REQUEST, "CHAR \\x!2XL at !UL", *cptr, cptr - bptr); WatchDataDump (bptr, bcnt); } if (ConfigNoticeInvalid) { ErrorNoticed (rqptr, 0, "CHAR \\x!2XL at !UL of !UL", FI_LI, *cptr, cptr - bptr, bcnt); RequestNoticeDump (bptr, bcnt); } RequestNBG (rqptr); return; } break; } } if (rqptr->rqHeader.HttpVersion == HTTP_VERSION_1_1 || rqptr->rqHeader.HttpVersion == HTTP_VERSION_1_0 || rqptr->rqHeader.HttpVersion == HTTP_VERSION_0_9) { /*************************/ /* supported HTTP version */ /**************************/ /* look for the end of header blank line */ for (cptr = bptr + rqptr->rqHeader.RequestLineLength; cptr < czptr && !SAME2(cptr,'\n\n') && !SAME4(cptr,'\r\n\r\n'); cptr++); if (cptr < czptr) { if (SAME2(cptr,'\n\n')) cptr += 2; else cptr += 4; rqptr->rqHeader.RequestHeaderPtr = bptr; rqptr->rqHeader.RequestHeaderLength = cptr - bptr; RequestParseHeader (rqptr); return; } } else if (rqptr->rqHeader.HttpVersion) { /****************************/ /* unsupported HTTP version */ /****************************/ rqptr->rqResponse.HttpStatus = 505; ErrorGeneral (rqptr, MsgFor(rqptr,MSG_REQUEST_FORMAT), FI_LI); RequestEnd (rqptr); return; } if (rqptr->rqHeader.RequestHeaderLength >= MAX_REQUEST_HEADER) { /* not likely to be a legitimate HTTP request! */ rqptr->rqResponse.HttpStatus = 414; RequestEnd (rqptr); return; } /*****************************/ /* read more from the client */ /*****************************/ if (rqptr->rqHeader.RequestHeaderLength >= rqptr->rqNet.ReadBufferSize) { /* need more buffer space */ rqptr->rqNet.ReadBufferSize += NetReadBufferSize; rqptr->rqNet.ReadBufferPtr = VmReallocHeap (rqptr, rqptr->rqNet.ReadBufferPtr, rqptr->rqNet.ReadBufferSize, FI_LI); } /* asynchronous read to get more of the header from the client */ NetRead (rqptr, &RequestGet, rqptr->rqNet.ReadBufferPtr + rqptr->BytesRx64, rqptr->rqNet.ReadBufferSize - rqptr->BytesRx64); #undef STRCAT #undef CONTROL_D #undef CONTROL_Z } /****************************************************************************/ /* Parse the rest of the HTTP/1.n header fields and execute the request. This has an equivalent non-HTTP/1.n function, RequestParseDictionary() which performs similar processing for an HTTP/2 request or RequestRedirect() product. */ void RequestParseHeader (REQUEST_STRUCT *rqptr) { int length, retval; char *cptr; /*********/ /* begin */ /*********/ if (WATCHMOD (rqptr, WATCH_MOD_REQUEST)) { WatchThis (WATCHITM(rqptr), WATCH_MOD_REQUEST, "RequestParseHeader() !UL", rqptr->rqHeader.RequestHeaderLength); WatchDataDump (rqptr->rqHeader.RequestHeaderPtr, rqptr->rqHeader.RequestHeaderLength); } if (WATCH_NOT_ONE_SHOT(Watch.Category)) WatchFilterRequestHeader (rqptr); if (WATCHING (rqptr, WATCH_REQUEST_HEADER)) { WatchThis (WATCHITM(rqptr), WATCH_REQUEST_HEADER, "HEADER !UL bytes", rqptr->rqHeader.RequestHeaderLength); WatchData (rqptr->rqHeader.RequestHeaderPtr, rqptr->rqHeader.RequestHeaderLength); } /* if not POST or PUT or zero content length these won't be referenced */ rqptr->rqHeader.RequestBodyPtr = rqptr->rqHeader.RequestHeaderPtr + rqptr->rqHeader.RequestHeaderLength; rqptr->rqHeader.RequestBodyCount = rqptr->BytesRx64 - rqptr->rqHeader.RequestHeaderLength; InstanceMutexLock (INSTANCE_MUTEX_HTTPD); if (rqptr->rqNet.PersistentCount) { acptr->RequestPersistentCount++; if (rqptr->rqNet.PersistentCount > acptr->RequestPersistentMax) acptr->RequestPersistentMax = rqptr->rqNet.PersistentCount; } InstanceMutexUnLock (INSTANCE_MUTEX_HTTPD); cptr = rqptr->rqHeader.RequestHeaderPtr; cptr += rqptr->rqHeader.RequestLineLength; /* step over carriage control at the end of the request line */ if (*cptr == '\r') cptr++; if (*cptr == '\n') cptr++; length = rqptr->rqHeader.RequestHeaderLength - (cptr - rqptr->rqHeader.RequestHeaderPtr); /* -1 indicates RequestNBG(), 0 to end request, +1 to execute request */ retval = RequestFields (rqptr, cptr, length); if (retval > 0) RequestParseExecute (rqptr); else if (retval == 0) RequestEnd (rqptr); else RequestNBG (rqptr); } /****************************************************************************/ /* When the dictionary has already been populated with request fields, as with HTTP/2 or RequestRedirect()ed requests. Has an equivalent HTTP/1.n function RequestParseHeader(). */ void RequestParseDictionary (REQUEST_STRUCT *rqptr) { char *cptr; /*********/ /* begin */ /*********/ if (WATCHMOD (rqptr, WATCH_MOD_REQUEST)) WatchThis (WATCHITM(rqptr), WATCH_MOD_REQUEST, "RequestParseDictionary()"); if (RequestLineParse (rqptr)) if (RequestProcessFields (rqptr)) RequestParseExecute (rqptr); else RequestEnd (rqptr); else RequestEnd (rqptr); } /****************************************************************************/ /* Continue processing and executing the request. */ void RequestParseExecute (REQUEST_STRUCT *rqptr) { BOOL IsAstCall, WebDavHeader; int PerhapsPipeline; char *cptr; /*********/ /* begin */ /*********/ #if WATCH_MOD IsAstCall = HttpdIsAstCall(); #endif if (WATCHMOD (rqptr, WATCH_MOD_REQUEST)) WatchThis (WATCHITM(rqptr), WATCH_MOD_REQUEST, "RequestParseExecute() !&F !&B", &RequestParseExecute, IsAstCall); if (WATCH_NOT_ONE_SHOT(Watch.Category)) WatchFilterPathTrack (rqptr); if (MATCH9 (rqptr->rqHeader.RequestUriPtr, "/rtt?ping")) { AdminPing (rqptr); return; } #if WATCH_MOD if (MATCH3 (rqptr->rqHeader.PathInfoPtr, "/$/")) { /* see each of the test functions for detail */ if (!strcmp (rqptr->rqHeader.PathInfoPtr, "/$/NetIoWriteTest/")) { NetIoWriteTest (rqptr); return; } if (!strcmp (rqptr->rqHeader.PathInfoPtr, "/$/NetIoReadTest/")) { if (rqptr->rqHeader.Method == HTTP_METHOD_POST) { NetIoReadTest (rqptr); return; } } if (!strcmp (rqptr->rqHeader.PathInfoPtr, "/$/HttpdIsAstCall/")) { static int count; if (count & 1) { rqptr->RequestState = REQUEST_STATE_ENDING; SysDclAst (RequestEnd, rqptr); } else { rqptr->rqResponse.NoGzip = true; ResponseHeader200 (rqptr, "text/plain", NULL); SysDclAst (RequestParseExecute, rqptr); } FaoToNet (rqptr, "HttpdIsAstCall() !&B !&B\n", count & 1, IsAstCall); count++; return; } } #endif /* WATCH_MOD */ if (rqptr->rqHeader.UpgradeWebSocket || rqptr->RawSocketRequest) { rqptr->WebSocketRequest = true; if (!WebSockRequest (rqptr)) return; } if (rqptr->rqResponse.HttpVersion == HTTP_VERSION_1_1) { /* HTTP/1.1 */ if (!rqptr->rqHeader.HostPtr) { /* HTTP/1.1 mandates a "Host:" field */ InstanceGblSecIncrLong (&acptr->RequestErrorCount); rqptr->rqResponse.HttpStatus = 400; ErrorGeneral (rqptr, MsgFor(rqptr,MSG_REQUEST_FORMAT), FI_LI); RequestEnd (rqptr); return; } if (rqptr->rqHeader.ExpectUnsupported) { /* HTTP/1.1 mandates a supported "Expect:" field */ InstanceGblSecIncrLong (&acptr->RequestErrorCount); rqptr->rqResponse.HttpStatus = 417; ErrorGeneral (rqptr, MsgFor(rqptr,MSG_REQUEST_FORMAT), FI_LI); RequestEnd (rqptr); return; } /* HTTP/1.1 connections are persistent unless otherwise directed */ if (!rqptr->WebSocketRequest) if (!rqptr->rqHeader.ConnectionClose) rqptr->PersistentRequest = true; if (!HTTP2_REQUEST(rqptr) && !rqptr->RedirectCount) { /* test for possible request pipelining */ PerhapsPipeline = rqptr->BytesRx64 - rqptr->rqHeader.RequestHeaderLength; if (Config.cfMisc.PipelineRequests && !rqptr->rqHeader.ContentLength64 && PerhapsPipeline > 0) { /*********************/ /* perhaps pipelined */ /*********************/ if (WATCHMOD (rqptr, WATCH_MOD_REQUEST)) WatchThis (WATCHITM(rqptr), WATCH_MOD_REQUEST, "PerhapsPipeline !UL", PerhapsPipeline); if (PerhapsPipeline > rqptr->rqNet.ReadBufferSize) { ErrorNoticed (rqptr, SS$_BUGCHECK, "pipeline:!UL buffer:!UL", FI_LI, PerhapsPipeline, rqptr->rqNet.ReadBufferSize); RequestEnd (rqptr); return; } if (rqptr->rqNet.ReadBufferSize > rqptr->rqNet.PipelineBufferSize) { if (rqptr->rqNet.PipelineBufferPtr) VmFree (rqptr->rqNet.PipelineBufferPtr, FI_LI); rqptr->rqNet.PipelineBufferSize = rqptr->rqNet.ReadBufferSize; rqptr->rqNet.PipelineBufferPtr = VmGet (rqptr->rqNet.PipelineBufferSize); } /* buffer the trailing octets */ rqptr->rqNet.PipelineBufferCount = PerhapsPipeline; memcpy (rqptr->rqNet.PipelineBufferPtr, rqptr->rqHeader.RequestHeaderPtr + rqptr->rqHeader.RequestHeaderLength, rqptr->rqNet.PipelineBufferCount); } } } else if (rqptr->rqResponse.HttpVersion == HTTP_VERSION_1_0) { /* HTTP/1.0 */ if (rqptr->rqHeader.KeepAliveConnection || rqptr->rqHeader.ConnectionKeepAlive) rqptr->PersistentRequest = true; } if (rqptr->rqHeader.ContentEncodingUnknown) { rqptr->rqResponse.HttpStatus = 415; ErrorGeneral (rqptr, MsgFor(rqptr,MSG_REQUEST_FORMAT), FI_LI); RequestEnd (rqptr); return; } if (rqptr->rqHeader.CacheControlNoCache || rqptr->rqHeader.CacheControlNoStore || rqptr->rqHeader.CacheControlMaxAgeZero || rqptr->rqHeader.PragmaNoCache) { rqptr->NotFromCache = true; InstanceGblSecIncrLong (&acptr->RequestNotFromCacheCount); } if (LoggingFileError && Config.cfLog.WriteFail503 && !strsame (rqptr->rqHeader.RequestUriPtr, HTTPD_ADMIN, sizeof(HTTPD_ADMIN)-1)) { rqptr->rqResponse.HttpStatus = 503; ErrorGeneral (rqptr, MsgFor(rqptr,MSG_GENERAL_TRY_LATER), FI_LI); RequestEnd (rqptr); return; } if (WebDavEnabled) { WebDavHeader = (rqptr->rqHeader.WebDavDepthPtr || rqptr->rqHeader.WebDavDestinationPtr || rqptr->rqHeader.WebDavIfPtr || rqptr->rqHeader.WebDavLockTokenPtr || rqptr->rqHeader.WebDavTimeoutPtr || rqptr->rqHeader.WebDavTranslatePtr || rqptr->rqHeader.WebDavOverwritePtr); if (WebDavHeader && (rqptr->rqHeader.Method == HTTP_METHOD_GET || rqptr->rqHeader.Method == HTTP_METHOD_HEAD || rqptr->rqHeader.Method == HTTP_METHOD_PUT || rqptr->rqHeader.Method == HTTP_METHOD_DELETE)) rqptr->WebDavMethod = true; if (rqptr->WebDavMethod) rqptr->WebDavRequest = true; else if (WebDavHeader) if (rqptr->rqHeader.UserAgentPtr) if (strstr (rqptr->rqHeader.UserAgentPtr, "WebDAV") || strstr (rqptr->rqHeader.UserAgentPtr, "webdav") || strstr (rqptr->rqHeader.UserAgentPtr, "DAV") || strstr (rqptr->rqHeader.UserAgentPtr, "WebDrive") || strstr (rqptr->rqHeader.UserAgentPtr, "cadaver")) rqptr->WhiffOfWebDav = true; /* little point in polluting the cache with WebDAV requests */ rqptr->rqCache.DoNotCache = rqptr->WebDavRequest || rqptr->WhiffOfWebDav; if (!rqptr->RedirectCount) { if (rqptr->WebDavRequest) InstanceGblSecIncrLong (&acptr->WebDavRequestCount); else if (rqptr->WhiffOfWebDav) InstanceGblSecIncrLong (&acptr->WebDavAromaCount); } } /* avoid the complexity of upgrading requests with a body */ if (!rqptr->rqHeader.ContentLength64) if (rqptr->rqHeader.UpgradeHttp2onHttp && rqptr->rqHeader.ConnectionHttp2Settings && rqptr->rqHeader.ConnectionUpgrade) if (Http2SwitchResponse (rqptr)) return; /********************************************/ /* evaluated in order of probable occurance */ /********************************************/ if (rqptr->rqHeader.Method == HTTP_METHOD_GET) { InstanceMutexLock (INSTANCE_MUTEX_HTTPD); if (!rqptr->RedirectCount) { acptr->MethodGetCount++; if (rqptr->WebDavRequest || rqptr->WhiffOfWebDav) acptr->MethodWebDav_GetCount++; } if (rqptr->WebSocketRequest) { acptr->CurrentWebSockets[InstanceNumber]++; WebSockCurrent++; } InstanceMutexUnLock (INSTANCE_MUTEX_HTTPD); RequestExecute (rqptr); return; } if (rqptr->ServicePtr->ProxyTunnel) { if (rqptr->rqHeader.Method == HTTP_METHOD_CONNECT) { if (!rqptr->RedirectCount) InstanceGblSecIncrLong (&acptr->MethodConnectCount); rqptr->PersistentRequest = false; RequestExecute (rqptr); return; } } if (rqptr->rqHeader.HttpVersion == HTTP_VERSION_0_9) { /*********************************/ /* HTTP/0.9 must be GET requests */ /*********************************/ InstanceGblSecIncrLong (&acptr->RequestErrorCount); rqptr->rqResponse.HttpStatus = 501; ErrorGeneral (rqptr, MsgFor(rqptr,MSG_REQUEST_METHOD), FI_LI); RequestEnd (rqptr); return; } if (rqptr->rqHeader.Method == HTTP_METHOD_HEAD) { if (!rqptr->RedirectCount) InstanceGblSecIncrLong (&acptr->MethodHeadCount); RequestExecute (rqptr); return; } if (rqptr->rqHeader.Method == HTTP_METHOD_POST) { if (!rqptr->RedirectCount) InstanceGblSecIncrLong (&acptr->MethodPostCount); RequestExecute (rqptr); return; } if (rqptr->rqHeader.Method == HTTP_METHOD_PUT) { if (!rqptr->RedirectCount) { InstanceMutexLock (INSTANCE_MUTEX_HTTPD); acptr->MethodPutCount++; if (rqptr->WebDavRequest || rqptr->WhiffOfWebDav) acptr->MethodWebDav_PutCount++; InstanceMutexUnLock (INSTANCE_MUTEX_HTTPD); } RequestExecute (rqptr); return; } if (rqptr->rqHeader.Method == HTTP_METHOD_CONNECT) { if (!rqptr->RedirectCount) InstanceGblSecIncrLong (&acptr->MethodConnectCount); rqptr->PersistentRequest = false; RequestExecute (rqptr); return; } if (rqptr->rqHeader.Method == HTTP_METHOD_DELETE) { if (!rqptr->RedirectCount) { InstanceMutexLock (INSTANCE_MUTEX_HTTPD); acptr->MethodDeleteCount++; if (rqptr->WebDavRequest || rqptr->WhiffOfWebDav) acptr->MethodWebDav_DeleteCount++; InstanceMutexUnLock (INSTANCE_MUTEX_HTTPD); } RequestExecute (rqptr); return; } if (rqptr->rqHeader.Method == HTTP_METHOD_TRACE) { if (!rqptr->RedirectCount) InstanceGblSecIncrLong (&acptr->MethodTraceCount); rqptr->PersistentRequest = false; RequestExecute (rqptr); return; } if (rqptr->rqHeader.Method == HTTP_METHOD_OPTIONS) { if (!rqptr->RedirectCount) { InstanceMutexLock (INSTANCE_MUTEX_HTTPD); acptr->MethodOptionsCount++; if (rqptr->WebDavRequest || rqptr->WhiffOfWebDav) acptr->MethodWebDav_OptionsCount++; InstanceMutexUnLock (INSTANCE_MUTEX_HTTPD); } RequestExecute (rqptr); return; } if (rqptr->rqHeader.Method == HTTP_METHOD_EXTENSION) { if (!rqptr->RedirectCount) InstanceGblSecIncrLong (&acptr->MethodExtensionCount); RequestExecute (rqptr); return; } if (rqptr->rqHeader.Method == HTTP_METHOD_WEBDAV_PROPPATCH) { if (!rqptr->RedirectCount) InstanceGblSecIncrLong (&acptr->MethodWebDavPropPatchCount); RequestExecute (rqptr); return; } if (rqptr->rqHeader.Method == HTTP_METHOD_WEBDAV_PROPFIND) { if (!rqptr->RedirectCount) InstanceGblSecIncrLong (&acptr->MethodWebDavPropFindCount); RequestExecute (rqptr); return; } if (rqptr->rqHeader.Method == HTTP_METHOD_WEBDAV_COPY) { if (!rqptr->RedirectCount) InstanceGblSecIncrLong (&acptr->MethodWebDavCopyCount); RequestExecute (rqptr); return; } if (rqptr->rqHeader.Method == HTTP_METHOD_WEBDAV_MOVE) { if (!rqptr->RedirectCount) InstanceGblSecIncrLong (&acptr->MethodWebDavMoveCount); RequestExecute (rqptr); return; } if (rqptr->rqHeader.Method == HTTP_METHOD_WEBDAV_MKCOL) { if (!rqptr->RedirectCount) InstanceGblSecIncrLong (&acptr->MethodWebDavMkColCount); RequestExecute (rqptr); return; } if (rqptr->rqHeader.Method == HTTP_METHOD_WEBDAV_LOCK) { if (!rqptr->RedirectCount) InstanceGblSecIncrLong (&acptr->MethodWebDavLockCount); RequestExecute (rqptr); return; } if (rqptr->rqHeader.Method == HTTP_METHOD_WEBDAV_UNLOCK) { if (!rqptr->RedirectCount) InstanceGblSecIncrLong (&acptr->MethodWebDavUnLockCount); RequestExecute (rqptr); return; } InstanceGblSecIncrLong (&acptr->RequestErrorCount); rqptr->rqResponse.HttpStatus = 501; ErrorGeneral (rqptr, MsgFor(rqptr,MSG_REQUEST_METHOD), FI_LI); RequestEnd (rqptr); } /****************************************************************************/ /* Parse the first (strict HTTP) or newline (de facto HTTP) terminated string from the client's request packet into HTTP method, path information and query string. Return true to continue processing the request, false to stop immediately. If false this function or any it called must have reported the problem to the client and/or disposed of the thread. */ BOOL RequestLineParse (REQUEST_STRUCT *rqptr) { int length; char *cptr, *sptr, *uptr, *zptr; DICT_ENTRY_STRUCT *denptr; /*********/ /* begin */ /*********/ if (WATCHMOD (rqptr, WATCH_MOD_REQUEST)) { WatchThis (WATCHITM(rqptr), WATCH_MOD_REQUEST, "RequestLineParse()"); WatchDataDump (rqptr->rqHeader.RequestLinePtr, rqptr->rqHeader.RequestLineLength); } cptr = rqptr->rqHeader.RequestLinePtr; zptr = (sptr = rqptr->rqHeader.MethodName) + sizeof(rqptr->rqHeader.MethodName)-1; while (*cptr && NOTLWS(*cptr) && sptr < zptr) *sptr++ = *cptr++; *sptr = '\0'; /* error with method name */ if (*cptr && NOTLWS(*cptr)) goto RequestLineParseFormatError; if (!rqptr->rqHeader.MethodName[0]) goto RequestLineParseFormatError; /* also match the trailing nulls */ if (MATCH4 (rqptr->rqHeader.MethodName, "GET")) rqptr->rqHeader.Method = HTTP_METHOD_GET; else if (MATCH5 (rqptr->rqHeader.MethodName, "HEAD")) rqptr->rqHeader.Method = HTTP_METHOD_HEAD; else if (MATCH5 (rqptr->rqHeader.MethodName, "POST")) rqptr->rqHeader.Method = HTTP_METHOD_POST; else if (MATCH4 (rqptr->rqHeader.MethodName, "PUT")) rqptr->rqHeader.Method = HTTP_METHOD_PUT; else if (MATCH8 (rqptr->rqHeader.MethodName, "CONNECT")) rqptr->rqHeader.Method = HTTP_METHOD_CONNECT; else if (MATCH7 (rqptr->rqHeader.MethodName, "DELETE")) rqptr->rqHeader.Method = HTTP_METHOD_DELETE; else if (MATCH6 (rqptr->rqHeader.MethodName, "TRACE")) rqptr->rqHeader.Method = HTTP_METHOD_TRACE; else if (MATCH8 (rqptr->rqHeader.MethodName, "OPTIONS")) rqptr->rqHeader.Method = HTTP_METHOD_OPTIONS; else if (WebDavEnabled) { if (MATCH5 (rqptr->rqHeader.MethodName, "COPY")) { rqptr->rqHeader.Method = HTTP_METHOD_WEBDAV_COPY; rqptr->WebDavMethod = true; } else if (MATCH6 (rqptr->rqHeader.MethodName, "MKCOL")) { rqptr->rqHeader.Method = HTTP_METHOD_WEBDAV_MKCOL; rqptr->WebDavMethod = true; } else if (MATCH5 (rqptr->rqHeader.MethodName, "MOVE")) { rqptr->rqHeader.Method = HTTP_METHOD_WEBDAV_MOVE; rqptr->WebDavMethod = true; } else if (MATCH10 (rqptr->rqHeader.MethodName, "PROPPATCH")) { rqptr->rqHeader.Method = HTTP_METHOD_WEBDAV_PROPPATCH; rqptr->WebDavMethod = true; } else if (MATCH9 (rqptr->rqHeader.MethodName, "PROPFIND")) { rqptr->rqHeader.Method = HTTP_METHOD_WEBDAV_PROPFIND; rqptr->WebDavMethod = true; } else if (WebDavLockingEnabled) { if (MATCH5 (rqptr->rqHeader.MethodName, "LOCK")) { rqptr->rqHeader.Method = HTTP_METHOD_WEBDAV_LOCK; rqptr->WebDavMethod = true; } else if (MATCH7 (rqptr->rqHeader.MethodName, "UNLOCK")) { rqptr->rqHeader.Method = HTTP_METHOD_WEBDAV_UNLOCK; rqptr->WebDavMethod = true; } else if (MapUrlExtensionMethod) rqptr->rqHeader.Method = HTTP_METHOD_EXTENSION; else rqptr->rqHeader.Method = HTTP_METHOD_UNSUPPORTED; } else if (MapUrlExtensionMethod) rqptr->rqHeader.Method = HTTP_METHOD_EXTENSION; else rqptr->rqHeader.Method = HTTP_METHOD_UNSUPPORTED; } else if (MapUrlExtensionMethod) rqptr->rqHeader.Method = HTTP_METHOD_EXTENSION; else rqptr->rqHeader.Method = HTTP_METHOD_UNSUPPORTED; /* skip across white-space between method and URI */ while (*cptr && ISLWS(*cptr)) cptr++; if (!*cptr) goto RequestLineParseFormatError; /* get the path information, noting the start of the requested URI */ for (uptr = cptr; *cptr && NOTLWS(*cptr) && *cptr != '?'; cptr++); /* error, no path on request line */ if (cptr == uptr) goto RequestLineParseFormatError; denptr = DictInsert (rqptr->rqDictPtr, DICT_TYPE_INTERNAL, "path_info", 9, uptr, cptr - uptr); rqptr->rqHeader.PathInfoPtr = DICT_GET_VALUE(denptr); if ((length = StringUrlDecode (rqptr->rqHeader.PathInfoPtr)) < 0) goto RequestLineParseUrlEncodeError; DictValueLength (denptr, length); rqptr->rqHeader.PathInfoLength = length; /* get any query string, or create an empty one */ if (*cptr == '?') cptr++; for (sptr = cptr; *cptr && NOTLWS(*cptr); cptr++); denptr = DictInsert (rqptr->rqDictPtr, DICT_TYPE_INTERNAL, "query_string", 12, sptr, cptr - sptr); rqptr->rqHeader.QueryStringPtr = DICT_GET_VALUE(denptr); rqptr->rqHeader.QueryStringLength = DICT_GET_VALUE_LEN(denptr); denptr = DictInsert (rqptr->rqDictPtr, DICT_TYPE_INTERNAL, "request_uri", 11, uptr, cptr - uptr); rqptr->rqHeader.RequestUriPtr = DICT_GET_VALUE(denptr); rqptr->rqHeader.RequestUriLength = DICT_GET_VALUE_LEN(denptr); if (WATCHMOD (rqptr, WATCH_MOD_REQUEST)) WatchDataFormatted ("!&Z !&Z !&Z !&Z\n", rqptr->rqHeader.MethodName, rqptr->rqHeader.PathInfoPtr, rqptr->rqHeader.QueryStringPtr, rqptr->rqHeader.RequestUriPtr); /* check the HTTP version */ while (*cptr && ISLWS(*cptr)) cptr++; if (MATCH8 (cptr, "HTTP/1.1")) rqptr->rqHeader.HttpVersion = rqptr->rqResponse.HttpVersion = HTTP_VERSION_1_1; else if (MATCH8 (cptr, "HTTP/1.0")) rqptr->rqHeader.HttpVersion = rqptr->rqResponse.HttpVersion = HTTP_VERSION_1_0; else if (MATCH5 (cptr, "HTTP/")) rqptr->rqHeader.HttpVersion = HTTP_VERSION_UNKNOWN; else rqptr->rqHeader.HttpVersion = rqptr->rqResponse.HttpVersion = HTTP_VERSION_0_9; return (true); /* report a request line format problem and return false */ RequestLineParseFormatError: { InstanceGblSecIncrLong (&acptr->RequestErrorCount); rqptr->rqResponse.HttpStatus = 400; ErrorGeneral (rqptr, MsgFor(rqptr,MSG_REQUEST_FORMAT), FI_LI); return (false); } RequestLineParseUrlEncodeError: { InstanceGblSecIncrLong (&acptr->RequestErrorCount); rqptr->rqResponse.HttpStatus = 400; ErrorGeneral (rqptr, MsgFor(rqptr,MSG_REQUEST_URL_ENC), FI_LI); return (false); } } /*****************************************************************************/ /* RFC7230 section 3.2 is the most recent definition of header fields. Scan the header buffer looking for relevant fields. Return 1 to continue processing the request, 0 to end immediately, -1 to report gross header malformation (for want of a less cumbersome phrase). */ int RequestFields ( REQUEST_STRUCT *rqptr, char *FieldsPtr, int FieldsLength ) { int nlen, vlen; char *cptr, *nptr, *vptr, *zptr; DICT_ENTRY_STRUCT *denptr; /*********/ /* begin */ /*********/ if (WATCHMOD (rqptr, WATCH_MOD_REQUEST)) WatchThis (WATCHITM(rqptr), WATCH_MOD_REQUEST, "RequestFields()"); zptr = (cptr = FieldsPtr) + FieldsLength; nlen = vlen = 0; while (cptr < zptr) { /* (potentially) the blank line terminating the header */ if (cptr < zptr && *cptr == '\r') cptr++; if (cptr < zptr && *cptr == '\n') cptr++; if (cptr >= zptr) break; /* parse the field name */ for (nptr = cptr; cptr < zptr && *cptr != ':'; cptr++) if (ISCTL(*cptr) || ISLWS(*cptr)) break; if ((nlen = cptr - nptr) > MAX_REQUEST_FIELD_NAME_LEN) break; if (*cptr != ':') break; for (cptr++; cptr < zptr && ISLWS(*cptr); cptr++); if (cptr >= zptr) break; /* parse the field value */ for (vptr = cptr; cptr < zptr; cptr++) if (ISCTL(*cptr) && *cptr != '\t') break; if (NOTCRLF(*cptr)) break; if (cptr >= zptr) break; if ((*cptr == '\r' && ISLWS(*(cptr+2) && cptr+2 < zptr) || (*cptr == '\n' && ISLWS(*(cptr+1) && cptr+1 < zptr)))) { /* RFC7230 deprecates folded headers but let's be generous */ if (!(cptr = RequestFoldedHeader (rqptr, nptr, nlen, vptr, zptr))) return (-1); } else { /* trim trailing white-space */ while (cptr-1 > vptr && ISLWS(*(cptr-1))) cptr--; vlen = cptr - vptr; while (NOTCRLF(*cptr) && cptr < zptr) cptr++; if (WATCHMOD (rqptr, WATCH_MOD_REQUEST)) WatchThis (WATCHITM(rqptr), WATCH_MOD_REQUEST, "{!UL}!-!#AZ {!UL}!-!#AZ", nlen, nptr, vlen, vptr); /* enter this request header field into the dictionary */ DictInsert (rqptr->rqDictPtr, DICT_TYPE_REQUEST, nptr, nlen, vptr, vlen); } /* step over the carriage control at the end of the line */ if (cptr < zptr && *cptr == '\r') cptr++; if (cptr < zptr && *cptr == '\n') cptr++; nlen = vlen = 0; } if (cptr >= zptr) if (RequestProcessFields (rqptr)) return (1); else return (0); if (WATCHING (rqptr, WATCH_REQUEST)) { if (cptr < zptr) { if (nlen > MAX_REQUEST_FIELD_NAME_LEN) WatchThis (WATCHITM(rqptr), WATCH_REQUEST, "FIELD \"!32AZ..:\" exceeds !UL bytes", nptr, MAX_REQUEST_FIELD_NAME_LEN); else WatchThis (WATCHITM(rqptr), WATCH_REQUEST, "FIELD \"!#AZ\" char \\x!2XL at !UL", nlen, nptr, *cptr, cptr - FieldsPtr); } WatchDataDump (rqptr->rqHeader.RequestHeaderPtr, rqptr->rqHeader.RequestHeaderLength); } if (cptr < zptr) { if (nlen > MAX_REQUEST_FIELD_NAME_LEN) ErrorNoticed (rqptr, 0, "FIELD \"!32AZ..:\" exceeds !UL bytes", FI_LI, nptr, MAX_REQUEST_FIELD_NAME_LEN); else if (ConfigNoticeInvalid) { ErrorNoticed (rqptr, 0, "FIELD \"!#AZ\" char \\x!2XL at !UL of !UL", FI_LI, nlen, nptr, *cptr, cptr - FieldsPtr, rqptr->rqHeader.RequestHeaderLength); RequestNoticeDump (rqptr->rqHeader.RequestHeaderPtr, rqptr->rqHeader.RequestHeaderLength); } } return (-1); } /*****************************************************************************/ /* RFC7230 deprecates folded headers but let's be generous. Parse the folder header value into a dynamic buffer and insert into the request dictionary. Return a pointer to the new position in the request header, or NULL if there was a fatal error. */ char* RequestFoldedHeader ( REQUEST_STRUCT *rqptr, char *nptr, int nlen, char *hptr, char *hzptr ) { static int vsize; static char *vptr; int vlen; char *sptr, *zptr; /*********/ /* begin */ /*********/ if (WATCHMOD (rqptr, WATCH_MOD_REQUEST)) WatchThis (WATCHITM(rqptr), WATCH_MOD_REQUEST, "RequestFoldedHeader()"); if (WATCHING (rqptr, WATCH_REQUEST)) WatchThis (WATCHITM(rqptr), WATCH_REQUEST, "RFC7230 3.2.4 deprecates folded value of \"!#AZ:\"", nlen, nptr); zptr = (sptr = vptr) + vsize; for (;;) { if (hptr >= hzptr) return (NULL); if (sptr >= zptr) { if (vsize + 2048 >= MAX_REQUEST_HEADER) return (NULL); vsize += 2048; vlen = sptr - vptr; vptr = VmRealloc (vptr, vsize, FI_LI); zptr = (sptr = vptr) + vsize; sptr += vlen; } while (hptr < hzptr && sptr < zptr) { if (ISCTL(*hptr) && *hptr != '\t') break; *sptr++ = *hptr++; } if (sptr >= zptr) continue; if (hptr >= hzptr) return (NULL); if (*hptr == '\r' && *(hptr+1) == '\n' && !ISLWS(*(hptr+2))) break; else if (*hptr == '\n' && !ISLWS(*(hptr+1))) break; if (*hptr == '\r') hptr++; if (*hptr == '\n') hptr++; while (ISLWS(*hptr) && hptr < hzptr) { if (sptr >= zptr) { if (vsize + 2048 >= MAX_REQUEST_HEADER) return (NULL); vsize += 2048; vlen = sptr - vptr; vptr = VmRealloc (vptr, vsize, FI_LI); zptr = (sptr = vptr) + vsize; sptr += vlen; } *sptr++ = ' '; hptr++; } } /* trim trailing white-space */ while (sptr-1 > vptr && ISLWS(*(sptr-1))) sptr--; *sptr = '\0'; vlen = sptr - vptr; if (WATCHMOD (rqptr, WATCH_MOD_REQUEST)) WatchThis (WATCHITM(rqptr), WATCH_MOD_REQUEST, "{!UL}!-!#AZ {!UL}!-!#AZ", nlen, nptr, vlen, vptr); DictInsert (rqptr->rqDictPtr, DICT_TYPE_REQUEST, nptr, nlen, vptr, vlen); return (hptr); } /*****************************************************************************/ /* Process the contents of specific fields stored in the dictionary. */ BOOL RequestProcessFields (REQUEST_STRUCT *rqptr) { int idx, status; char *cptr; RANGE_BYTE *rbptr; DICT_STRUCT *dicptr; DICT_ENTRY_STRUCT *denptr; /*********/ /* begin */ /*********/ if (WATCHMOD (rqptr, WATCH_MOD_REQUEST)) WatchThis (WATCHITM(rqptr), WATCH_MOD_REQUEST, "RequestProcessFields()"); dicptr = rqptr->rqDictPtr; if (WATCHING (rqptr, WATCH_REQUEST_HEADER)) { WatchThis (WATCHITM(rqptr), WATCH_REQUEST_HEADER, "DATA"); DictWatchEntry (NULL); DictWatch (rqptr->rqDictPtr, DICT_TYPE_INTERNAL, "request_line"); DictWatch (rqptr->rqDictPtr, DICT_TYPE_REQUEST, "*"); } if (denptr = DictLookup (dicptr, DICT_TYPE_REQUEST, "accept-charset", 14)) rqptr->rqHeader.AcceptCharsetPtr = DICT_GET_VALUE(denptr); if (denptr = DictLookup (dicptr, DICT_TYPE_REQUEST, "accept-encoding", 15)) { rqptr->rqHeader.AcceptEncodingPtr = cptr = DICT_GET_VALUE(denptr); while (*cptr) { while (*cptr && ISLWS(*cptr)) cptr++; if (MATCH4 (cptr, "gzip") || strsame (cptr, "gzip", 4)) { rqptr->rqHeader.AcceptEncodingGzip = true; cptr += 4; } else if (MATCH7 (cptr, "deflate") || strsame (cptr, "deflate", 7)) { rqptr->rqHeader.AcceptEncodingGzip = true; cptr += 7; } while (*cptr && NOTLWS(*cptr) && *cptr != ',') cptr++; while (*cptr && (ISLWS(*cptr) || *cptr == ',')) cptr++; } } if (denptr = DictLookup (dicptr, DICT_TYPE_REQUEST, "accept", 6)) rqptr->rqHeader.AcceptPtr = DICT_GET_VALUE(denptr); if (denptr = DictLookup (dicptr, DICT_TYPE_REQUEST, "accept-language", 15)) rqptr->rqHeader.AcceptLangPtr = DICT_GET_VALUE(denptr); if (denptr = DictLookup (dicptr, DICT_TYPE_REQUEST, "authorization", 13)) rqptr->rqHeader.AuthorizationPtr = DICT_GET_VALUE(denptr); if (denptr = DictLookup (dicptr, DICT_TYPE_REQUEST, "cache-control", 13)) { cptr = DICT_GET_VALUE(denptr); while (*cptr) { while (*cptr && ISLWS(*cptr)) cptr++; if (MATCH8 (cptr, "no-cache") || strsame (cptr, "no-cache", 8)) { rqptr->rqHeader.CacheControlNoCache = true; cptr += 8; } else if (MATCH8 (cptr, "no-store") || strsame (cptr, "no-store", 8)) { rqptr->rqHeader.CacheControlNoStore = true; cptr += 8; } else if (MATCH12 (cptr, "no-transform") || strsame (cptr, "no-transform", 12)) { rqptr->rqHeader.CacheControlNoTransform = true; cptr += 12; } else if (MATCH8 (cptr, "max-age=") || strsame (cptr, "max-age=", 8)) { cptr += 8; rqptr->rqHeader.CacheControlMaxAge = atoi(cptr); if (!rqptr->rqHeader.CacheControlMaxAge) rqptr->rqHeader.CacheControlMaxAgeZero = true; } else if (MATCH10 (cptr, "max-stale=") || strsame (cptr, "max-stale=", 10)) { cptr += 10; rqptr->rqHeader.CacheControlMaxStale = atoi(cptr); if (!rqptr->rqHeader.CacheControlMaxStale) rqptr->rqHeader.CacheControlMaxStaleZero = true; } else if (MATCH9 (cptr, "max-stale") || strsame (cptr, "max-stale", 9)) { rqptr->rqHeader.CacheControlMaxStaleZero = true; cptr += 9; } else if (MATCH10 (cptr, "min-fresh=") || strsame (cptr, "min-fresh=", 10)) { cptr += 10; rqptr->rqHeader.CacheControlMinFresh = atoi(cptr); if (!rqptr->rqHeader.CacheControlMinFresh) rqptr->rqHeader.CacheControlMinFreshZero = true; } else if (MATCH14 (cptr, "only-if-cached") || strsame (cptr, "only-if-cached", 14)) { rqptr->rqHeader.CacheControlOnlyIfCached = true; cptr += 14; } while (*cptr && NOTLWS(*cptr) && *cptr != ',') cptr++; while (*cptr && (ISLWS(*cptr) || *cptr == ',')) cptr++; } if (WATCHMOD (rqptr, WATCH_MOD_REQUEST)) WatchThis (WATCHITM(rqptr), WATCH_MOD_REQUEST, "cache:!UL/!&B !UL/!&B !UL/!&B !&B !&B !&B !&B", rqptr->rqHeader.CacheControlMaxAge, rqptr->rqHeader.CacheControlMaxAgeZero, rqptr->rqHeader.CacheControlMaxStale, rqptr->rqHeader.CacheControlMaxStaleZero, rqptr->rqHeader.CacheControlMinFresh, rqptr->rqHeader.CacheControlMinFreshZero, rqptr->rqHeader.CacheControlNoCache, rqptr->rqHeader.CacheControlNoTransform, rqptr->rqHeader.CacheControlNoStore, rqptr->rqHeader.CacheControlOnlyIfCached); } if (denptr = DictLookup (dicptr, DICT_TYPE_REQUEST, "connection", 10)) { cptr = DICT_GET_VALUE(denptr); while (*cptr) { while (*cptr && ISLWS(*cptr)) cptr++; if (MATCH5 (cptr, "close") || strsame (cptr, "close", 5)) { rqptr->rqHeader.ConnectionClose = true; cptr += 5; } else if (MATCH14 (cptr, "HTTP2-Settings") || strsame (cptr, "http2-settings", 14)) { rqptr->rqHeader.ConnectionHttp2Settings = true; cptr += 14; } else if (MATCH10 (cptr, "keep-alive") || strsame (cptr, "keep-alive", 10)) { rqptr->rqHeader.ConnectionKeepAlive = true; cptr += 10; } else if (MATCH7 (cptr, "upgrade") || strsame (cptr, "upgrade", 7)) { rqptr->rqHeader.ConnectionUpgrade = true; cptr += 7; } while (*cptr && NOTLWS(*cptr) && *cptr != ',') cptr++; while (*cptr && (ISLWS(*cptr) || *cptr == ',')) cptr++; } if (WATCHMOD (rqptr, WATCH_MOD_REQUEST)) WatchThis (WATCHITM(rqptr), WATCH_MOD_REQUEST, "connection:!&B !&B !&B !&B", rqptr->rqHeader.ConnectionClose, rqptr->rqHeader.ConnectionHttp2Settings, rqptr->rqHeader.ConnectionKeepAlive, rqptr->rqHeader.ConnectionUpgrade); } if (denptr = DictLookup (dicptr, DICT_TYPE_REQUEST, "content-encoding", 16)) { cptr = DICT_GET_VALUE(denptr); while (*cptr) { while (*cptr && ISLWS(*cptr)) cptr++; if (MATCH4 (cptr, "gzip") || strsame (cptr, "gzip", 4)) { rqptr->rqHeader.ContentEncodingGzip = true; cptr += 4; } else if (MATCH7 (cptr, "deflate") || strsame (cptr, "deflate", 7)) { rqptr->rqHeader.ContentEncodingGzip = true; cptr += 7; } else rqptr->rqHeader.ContentEncodingUnknown = true; while (*cptr && NOTLWS(*cptr) && *cptr != ',') cptr++; while (*cptr && (ISLWS(*cptr) || *cptr == ',')) cptr++; } if (WATCHMOD (rqptr, WATCH_MOD_REQUEST)) WatchDataFormatted ("gzip:!&B unknown:!&B\n", rqptr->rqHeader.ContentEncodingGzip, rqptr->rqHeader.ContentEncodingUnknown); } if (denptr = DictLookup (dicptr, DICT_TYPE_REQUEST, "content-length", 14)) rqptr->rqHeader.ContentLength64 = atoq(DICT_GET_VALUE(denptr)); if (denptr = DictLookup (dicptr, DICT_TYPE_REQUEST, "content-type", 12)) rqptr->rqHeader.ContentTypePtr = DICT_GET_VALUE(denptr); if (denptr = DictLookup (dicptr, DICT_TYPE_REQUEST, "cookie", 6)) rqptr->rqHeader.CookiePtr = DICT_GET_VALUE(denptr); if (denptr = DictLookup (dicptr, DICT_TYPE_REQUEST, "forwarded", 9)) rqptr->rqHeader.ForwardedPtr = DICT_GET_VALUE(denptr); if (denptr = DictLookup (dicptr, DICT_TYPE_REQUEST, "http2-settings", 14)) rqptr->rqHeader.Http2SettingsPtr = DICT_GET_VALUE(denptr); if (denptr = DictLookup (dicptr, DICT_TYPE_REQUEST, "if-match", 8)) rqptr->rqHeader.IfMatchPtr = DICT_GET_VALUE(denptr); if (denptr = DictLookup (dicptr, DICT_TYPE_REQUEST, "if-modified-since", 17)) { cptr = DICT_GET_VALUE(denptr); status = HttpGetTime (cptr, &rqptr->rqTime.IfModifiedSinceTime64); if (VMSnok (status)) { rqptr->rqTime.IfModifiedSinceTime64 = 0; InstanceGblSecIncrLong (&acptr->RequestErrorCount); rqptr->rqResponse.HttpStatus = 400; ErrorGeneral (rqptr, MsgFor(rqptr,MSG_REQUEST_FORMAT), FI_LI); return (false); } /* indicate that no "length=" was found (if none is found :^) */ rqptr->rqHeader.IfModifiedSinceLength = -1; /* look for a "length=" parameter to this header field */ while (*cptr) { while (*cptr && *cptr != ';') cptr++; if (!*cptr) break; cptr++; while (*cptr && ISLWS(*cptr)) cptr++; if (!*cptr) break; if (!MATCH6 (cptr, "length")) continue; cptr += 6; while (*cptr && *cptr != '=') cptr++; if (!*cptr) break; cptr++; while (*cptr && ISLWS(*cptr)) cptr++; if (!isdigit (*cptr)) continue; rqptr->rqHeader.IfModifiedSinceLength = atol(cptr); } if (WATCHMOD (rqptr, WATCH_MOD_REQUEST)) WatchDataFormatted ("since:!&S !%D !SL\n", status, &rqptr->rqTime.IfModifiedSinceTime64, rqptr->rqHeader.IfModifiedSinceLength); } if (denptr = DictLookup (dicptr, DICT_TYPE_REQUEST, "if-range", 8)) { cptr = DICT_GET_VALUE(denptr); status = HttpGetTime (cptr, &rqptr->rqTime.IfRangeBeginTime64); if (VMSnok (status)) rqptr->rqTime.IfRangeBeginTime64 = 0; if (WATCHMOD (rqptr, WATCH_MOD_REQUEST)) WatchDataFormatted ("if-range:!&Z !&S !%D \n", cptr, status, &rqptr->rqTime.IfRangeBeginTime64); } if (denptr = DictLookup (dicptr, DICT_TYPE_REQUEST, "if-unmodified-since", 19)) { cptr = DICT_GET_VALUE(denptr); status = HttpGetTime (cptr, &rqptr->rqTime.IfUnModifiedSinceTime64); if (VMSnok (status)) rqptr->rqTime.IfUnModifiedSinceTime64 = 0; if (WATCHMOD (rqptr, WATCH_MOD_REQUEST)) WatchDataFormatted ("unmod:!&Z !&S !%D \n", cptr, status, &rqptr->rqTime.IfUnModifiedSinceTime64); } if (denptr = DictLookup (dicptr, DICT_TYPE_REQUEST, "host", 4)) { rqptr->rqHeader.HostPtr = DICT_GET_VALUE(denptr); rqptr->rqHeader.HostLength = DICT_GET_VALUE_LEN(denptr); } if (denptr = DictLookup (dicptr, DICT_TYPE_REQUEST, "keep-alive", 10)) { rqptr->rqHeader.KeepAliveConnection = true; cptr = DICT_GET_VALUE(denptr); if (isdigit (*cptr)) rqptr->rqHeader.KeepAliveSeconds = atoi(cptr); if (WATCHMOD (rqptr, WATCH_MOD_REQUEST)) WatchDataFormatted ("keep-alive:!&B (!UL)\n", rqptr->rqHeader.KeepAliveConnection, rqptr->rqHeader.KeepAliveSeconds); } if (denptr = DictLookup (dicptr, DICT_TYPE_REQUEST, "origin", 6)) rqptr->rqHeader.OriginPtr = DICT_GET_VALUE(denptr); if (denptr = DictLookup (dicptr, DICT_TYPE_REQUEST, "pragma", 6)) { cptr = DICT_GET_VALUE(denptr); if (MATCH8 (cptr, "no-cache") || strsame (cptr, "no-cache", 8)) rqptr->rqHeader.PragmaNoCache = true; if (WATCHMOD (rqptr, WATCH_MOD_REQUEST)) WatchDataFormatted ("no-cache:!&B\n", rqptr->rqHeader.PragmaNoCache); } if (denptr = DictLookup (dicptr, DICT_TYPE_REQUEST, "range", 5)) { cptr = DICT_GET_VALUE(denptr); rqptr->rqHeader.RangeBytePtr = rbptr = VmGetHeap (rqptr, sizeof(RANGE_BYTE)); if (MATCH6 (cptr, "bytes=")) { /* ok, that's a start */ cptr += 6; idx = 0; while (*cptr) { while (*cptr && ISLWS(*cptr) || *cptr == ',') cptr++; if (!*cptr || *cptr == ';') break; if (idx >= RANGE_BYTE_MAX) { ErrorNoticed (rqptr, 0, "RANGE_BYTE_MAX exceeded", FI_LI); idx = 0; break; } if (isdigit(*cptr)) { /* starting offset */ rbptr->First[idx] = strtoll(cptr,NULL,10); while (*cptr && isdigit(*cptr)) cptr++; if (*cptr == '-') { /* ending offset */ cptr++; rbptr->Last[idx] = strtoll(cptr,NULL,10); while (*cptr && isdigit(*cptr)) cptr++; } idx++; } else if (*cptr == '-') { /* negative offset (from the end-of-file) */ rbptr->Last[idx] = strtoll(cptr,NULL,10); cptr++; while (*cptr && isdigit(*cptr)) cptr++; idx++; } else { /* range format error */ idx = 0; break; } } rbptr->Total = idx; if (WATCHMOD (rqptr, WATCH_MOD_REQUEST)) { WatchDataFormatted ("!UL range!%s\n", idx); for (idx = 0; idx < rbptr->Total; idx++) WatchDataFormatted ("!UL !@UQ - !@UQ\n", idx, &rbptr->First[idx], &rbptr->Last[idx]); } } } if (denptr = DictLookup (dicptr, DICT_TYPE_REQUEST, "referer", 7)) rqptr->rqHeader.RefererPtr = DICT_GET_VALUE(denptr); if (denptr = DictLookup (dicptr, DICT_TYPE_REQUEST, "transfer-encoding", 17)) { cptr = DICT_GET_VALUE(denptr); if (MATCH7 (cptr, "chunked") || strsame (cptr, "chunked", 7)) rqptr->rqHeader.TransferEncodingChunked = true; if (WATCHMOD (rqptr, WATCH_MOD_REQUEST)) WatchDataFormatted ("chunked:!&B\n", rqptr->rqHeader.TransferEncodingChunked); /* If a message is received with both a Transfer-Encoding header field and a Content-Length header field, the latter MUST be ignored. RFC2616; 4.4 Message Length */ DictRemove (dicptr, "content-length", DICT_TYPE_REQUEST, 14); rqptr->rqHeader.ContentLength64 = 0; } if (denptr = DictLookup (dicptr, DICT_TYPE_REQUEST, "upgrade", 7)) { cptr = DICT_GET_VALUE(denptr); if (MATCH3 (cptr, "h2c")) { if (rqptr->rqResponse.HttpVersion == HTTP_VERSION_1_1) rqptr->rqHeader.UpgradeHttp2onHttp = true; } else if (MATCH9 (cptr, "WebSocket") || strsame (cptr, "WebSocket", 9)) rqptr->rqHeader.UpgradeWebSocket = true; else if (MATCH11 (cptr, "WASD-tunnel") || strsame (cptr, "WASD-tunnel", 11)) rqptr->rqHeader.UpgradeWASDtunnel = true; else if (MATCH12 (cptr, "WASD-SOCKS5-")) { /* see ProxySocks5RequestAst() */ rqptr->rqHeader.UpgradeSocks5Ptr = cptr + 12; } } if (denptr = DictLookup (dicptr, DICT_TYPE_REQUEST, "user-agent", 10)) rqptr->rqHeader.UserAgentPtr = DICT_GET_VALUE(denptr); if (denptr = DictLookup (dicptr, DICT_TYPE_REQUEST, "x-forwarded-for", 15)) rqptr->rqHeader.XForwardedForPtr = DICT_GET_VALUE(denptr); if (WebDavEnabled) { if (denptr = DictLookup (dicptr, DICT_TYPE_REQUEST, "depth", 5)) { rqptr->rqHeader.WebDavDepthPtr = cptr = DICT_GET_VALUE(denptr); if (*cptr == '0') rqptr->rqHeader.WebDavDepth = WEBDAV_DEPTH_ZERO; else if (*cptr == '1') rqptr->rqHeader.WebDavDepth = WEBDAV_DEPTH_ONE; else if (MATCH8 (cptr, "infinity") || strsame (cptr, "infinity", 8)) rqptr->rqHeader.WebDavDepth = WEBDAV_DEPTH_INFINITY; else { InstanceGblSecIncrLong (&acptr->RequestErrorCount); rqptr->rqResponse.HttpStatus = 400; ErrorGeneral (rqptr, MsgFor(rqptr,MSG_REQUEST_FORMAT), FI_LI); return (false); } if (WATCHMOD (rqptr, WATCH_MOD_REQUEST)) WatchDataFormatted ("depth:!UL\n", rqptr->rqHeader.WebDavDepth); } if (denptr = DictLookup (dicptr, DICT_TYPE_REQUEST, "destination", 11)) rqptr->rqHeader.WebDavDestinationPtr = DICT_GET_VALUE(denptr); if (denptr = DictLookup (dicptr, DICT_TYPE_REQUEST, "if", 2)) rqptr->rqHeader.WebDavIfPtr = DICT_GET_VALUE(denptr); if (denptr = DictLookup (dicptr, DICT_TYPE_REQUEST, "lock-token", 10)) rqptr->rqHeader.WebDavLockTokenPtr = DICT_GET_VALUE(denptr); if (denptr = DictLookup (dicptr, DICT_TYPE_REQUEST, "overwrite", 9)) { cptr = DICT_GET_VALUE(denptr); if (to_upper(*cptr) == 'T') rqptr->rqHeader.WebDavOverwrite = true; else if (to_upper(*cptr) == 'F') rqptr->rqHeader.WebDavOverwrite = false; else { InstanceGblSecIncrLong (&acptr->RequestErrorCount); rqptr->rqResponse.HttpStatus = 400; ErrorGeneral (rqptr, MsgFor(rqptr,MSG_REQUEST_FORMAT), FI_LI); return (false); } if (WATCHMOD (rqptr, WATCH_MOD_REQUEST)) WatchDataFormatted ("overwrite:!&B\n", rqptr->rqHeader.WebDavOverwrite); } if (denptr = DictLookup (dicptr, DICT_TYPE_REQUEST, "timeout", 7)) rqptr->rqHeader.WebDavTimeoutPtr = DICT_GET_VALUE(denptr); if (denptr = DictLookup (dicptr, DICT_TYPE_REQUEST, "translate", 9)) { /* defacto header */ rqptr->rqHeader.WebDavTranslatePtr = cptr = DICT_GET_VALUE(denptr); if (to_upper(*cptr) == 'T') rqptr->rqHeader.WebDavTranslateFalse = true; else if (to_upper(*cptr) == 'F') rqptr->rqHeader.WebDavTranslateFalse = false; else { InstanceGblSecIncrLong (&acptr->RequestErrorCount); rqptr->rqResponse.HttpStatus = 400; ErrorGeneral (rqptr, MsgFor(rqptr,MSG_REQUEST_FORMAT), FI_LI); return (false); } if (WATCHMOD (rqptr, WATCH_MOD_REQUEST)) WatchDataFormatted ("translate:!&Z\n", rqptr->rqHeader.WebDavTranslatePtr); } /* an OS X (Apple) quirk */ if (denptr = DictLookup (dicptr, DICT_TYPE_REQUEST, "x-expected-entity-length", 24)) { cptr = DICT_GET_VALUE(denptr); if (isdigit(*cptr)) { rqptr->rqHeader.XExpectedEntityLength64 = atoq (cptr); if (WATCHMOD (rqptr, WATCH_MOD_REQUEST)) WatchDataFormatted ("entlen:!@UQ\n", rqptr->rqHeader.XExpectedEntityLength64); } else { InstanceGblSecIncrLong (&acptr->RequestErrorCount); rqptr->rqResponse.HttpStatus = 400; ErrorGeneral (rqptr, MsgFor(rqptr,MSG_REQUEST_FORMAT), FI_LI); return (false); } } } return (true); } /*****************************************************************************/ /* Build a complete request header from dictionary request line and fields and insert into the dictionary. Return a pointer to that entry. */ DICT_ENTRY_STRUCT* RequestDictHeader (REQUEST_STRUCT *rqptr) { int size; char *bptr, *cptr, *sptr, *zptr; DICT_ENTRY_STRUCT *denptr, *renptr, *rldenptr; /*********/ /* begin */ /*********/ if (WATCHMOD (rqptr, WATCH_MOD_REQUEST)) WatchThis (WATCHITM(rqptr), WATCH_MOD_REQUEST, "RequestDictHeader()"); /* only need to do it the once */ if (denptr = DictLookup (rqptr->rqDictPtr, DICT_TYPE_INTERNAL, "request_header", 14)) return (denptr); /* if a request header has not (perhaps yet - e.g. 101) been parsed */ if ((rldenptr = DictLookup (rqptr->rqDictPtr, DICT_TYPE_INTERNAL, "request_line", 12)) == NULL) return (rldenptr); size = 0; size += DICT_GET_VALUE_LEN(rldenptr) + 4; DictIterate (rqptr->rqDictPtr, NULL); while ((denptr = DictIterate (rqptr->rqDictPtr, DICT_TYPE_REQUEST)) != NULL) size += DICT_GET_KEY_LEN(denptr) + DICT_GET_VALUE_LEN(denptr) + 4; /* insert the entry reserving space */ renptr = DictInsert (rqptr->rqDictPtr, DICT_TYPE_INTERNAL, "request_header", 14, NULL, size); bptr = DICT_GET_VALUE(renptr); zptr = (sptr = bptr) + size; /* request line */ for (cptr = DICT_GET_VALUE(rldenptr); *cptr && sptr < zptr; *sptr++ = *cptr++); if (sptr < zptr) *sptr++ = '\r'; if (sptr < zptr) *sptr++ = '\n'; /* request fields */ DictIterate (rqptr->rqDictPtr, NULL); while ((denptr = DictIterate (rqptr->rqDictPtr, DICT_TYPE_REQUEST)) != NULL) { for (cptr = DICT_GET_KEY(denptr); *cptr && sptr < zptr; *sptr++ = *cptr++); if (sptr < zptr) *sptr++ = ':'; if (sptr < zptr) *sptr++ = ' '; for (cptr = DICT_GET_VALUE(denptr); *cptr && sptr < zptr; *sptr++ = *cptr++); if (sptr < zptr) *sptr++ = '\r'; if (sptr < zptr) *sptr++ = '\n'; } if (sptr < zptr) *sptr++ = '\r'; if (sptr < zptr) *sptr++ = '\n'; DictValueLength (renptr, sptr - bptr); if (sptr > zptr) ErrorNoticed (rqptr, SS$_RESULTOVF, NULL, FI_LI); return (renptr); } /*****************************************************************************/ /* If a file specification does not contain a file name the function looks for a home page in the directory. If no home page found it generates a directory listing ("Index of" documents). If the file specification contains a wildcard and there is no query string a directory listing (index) is generated. If there is a query string it must begin with the literal "?httpd=index" for a directory listing (index) to be generated (this allows directives in the query string to be passed to the directory listing functions). If it does not begin with this literal the default query (search) script is invoked. Detects file types that have an associated script. If a script is returned by ConfigContentType(), and the file specification does not include a specific version number (even such as ";0"), an automatic redirection is generated so that the specified document is processed by the script, and the output from that returned to the client. If it does contain a version number the file is always directly returned to the client. */ void RequestExecute (REQUEST_STRUCT *rqptr) { int status; char *cptr, *mpptr; DICT_ENTRY_STRUCT *denptr; /*********/ /* begin */ /*********/ if (WATCHMOD (rqptr, WATCH_MOD_REQUEST)) WatchThis (WATCHITM(rqptr), WATCH_MOD_REQUEST, "RequestExecute()"); WatchServerAlertQuotas (); /**************/ /* meta-agent */ /**************/ if (!(cptr = rqptr->MetaConAgentPtr)) { /* (try to) initiate meta-agent */ cptr = MetaConAgentBegin (rqptr); /* if successful it will AST back to RequestExecute() */ if (MATCH3(cptr,"200") || MATCH3(cptr,"202")) return; } /* 501 means no meta-agent configured */ if (!MATCH3 (cptr, "501")) { if (MATCH3 (cptr, NetRejectStatusString)) { /* used to REJECT a connection (must be before 418) */ rqptr->rqResponse.HttpStatus = NetRejectStatusCode; if (rqptr->rqHeader.UpgradeSocks5Ptr) ProxySocks5ReplyFail (rqptr); NetRejectSetStatus (rqptr); RequestEnd (rqptr); return; } if (MATCH3 (cptr, "418")) { /* 418 can be used to drop a connection */ rqptr->rqResponse.HttpStatus = 418; if (rqptr->rqHeader.UpgradeSocks5Ptr) ProxySocks5ReplyFail (rqptr); RequestEnd (rqptr); return; } if (MATCH3 (cptr, "500")) { /* fatal error */ if (WATCHING (rqptr, WATCH_REQUEST)) WatchThis (WATCHITM(rqptr), WATCH_REQUEST, "AGENT !AZ", cptr); rqptr->rqResponse.HttpStatus = 500; RequestEnd (rqptr); return; } if (!MATCH3 (cptr, "200")) { if (WATCHING (rqptr, WATCH_REQUEST)) WatchThis (WATCHITM(rqptr), WATCH_REQUEST, "AGENT !AZ", cptr); ErrorNoticed (rqptr, SS$_BUGCHECK, cptr, FI_LI); /* non-fatal so drop-through */ } } /***********/ /* request */ /***********/ rqptr->RequestState = REQUEST_STATE_PROCESSING; if (rqptr->rqHeader.PathInfoPtr[0] == '/') { if (!ServiceFindVirtual (rqptr)) { /* virtual service was not found */ if (Config.cfServer.ServiceNotFoundUrl[0]) { /* configuration specifies a redirection URL */ ResponseLocation (rqptr, Config.cfServer.ServiceNotFoundUrl, -1); RequestEnd (rqptr); return; } rqptr->UnknownVirtualService = true; } /* re-set the error reporting mechanism for the different service */ if (rqptr->ServicePtr->ErrorReportPath[0]) rqptr->rqResponse.ErrorReportByRedirect = true; else rqptr->rqResponse.ErrorReportByRedirect = false; } if (WATCHING (rqptr, WATCH_REQUEST)) { WatchThis (WATCHITM(rqptr), WATCH_REQUEST, "!AZ !&P", rqptr->rqHeader.MethodName, rqptr->rqHeader.RequestUriPtr); if (WATCHING (rqptr, WATCH_INTERNAL)) DictWatch (rqptr->rqDictPtr, NULL, "*"); if (rqptr->NotePadPtr) WatchThis (WATCHITM(rqptr), WATCH_REQUEST, "NOTEPAD !AZ", rqptr->NotePadPtr); } if (MATCH9 (rqptr->rqHeader.RequestUriPtr, "socks5://")) { if (rqptr->ServicePtr->ProxyTunnel != PROXY_TUNNEL_CONNECT) { /* only with a proxy CONNECT enabled service (i.e. general proxy) */ ProxySocks5ReplyFail (rqptr); RequestEnd (rqptr); return; } } /* ensure any old values are ignored, in case its a redirection */ rqptr->rqResponse.LocationPtr = NULL; /************************/ /* map the request path */ /************************/ rqptr->RequestMappedFile[0] = rqptr->ScriptName[0] = rqptr->RequestMappedScript[0] = '\0'; /* Map the path, converting it to a VMS file path (or script file name) and returning a pointer to a static buffer containing the mapped path. */ mpptr = MapUrl_Map (rqptr->rqHeader.PathInfoPtr, sizeof(rqptr->rqHeader.PathInfoPtr), rqptr->RequestMappedFile, sizeof(rqptr->RequestMappedFile), rqptr->ScriptName, sizeof(rqptr->ScriptName), rqptr->RequestMappedScript, sizeof(rqptr->RequestMappedScript), rqptr->RequestMappedRunTime, sizeof(rqptr->RequestMappedRunTime), &rqptr->PathOds, rqptr, &rqptr->rqPathSet); /* immediately after mapping in case of SET timeout rules */ HttpdTimerSet (rqptr, TIMER_OUTPUT, 0); if (rqptr->X509ClientCertMeta) { /********************/ /* X509 client cert */ /********************/ status = SesolaClientCert (rqptr, SESOLA_VERIFY_PEER_OPTIONAL, RequestExecute); /* this faux status indicates renegotiation is underway */ if (status == SS$_WAITUSRLBL) return; ErrorExitVmsStatus (SS$_BUGCHECK, ErrorSanityCheck, FI_LI); } if (rqptr->rqPathSet.Http2ToHttp11) { /**********************/ /* HTTP/2 to HTTP/1.1 */ /**********************/ Http2Error (rqptr->Http2Ptr, HTTP2_ERROR_HTTP11, FI_LI); rqptr->rqResponse.HttpStatus = 101; RequestEnd (rqptr); return; } if (rqptr->rqPathSet.Http2SendGoAway) { /******************/ /* HTTP/2 go-away */ /******************/ if (HTTP2_REQUEST(rqptr)) Http2DoError (rqptr->Http2Ptr, 0, rqptr->rqPathSet.Http2SendGoAway - 1, FI_LI); rqptr->rqResponse.HttpStatus = 205; /* reset content */ RequestEnd (rqptr); return; } if (rqptr->rqPathSet.Http2SendReset) { /*********************/ /* HTTP/2 rst_stream */ /*********************/ if (HTTP2_REQUEST(rqptr)) Http2DoError (rqptr->Http2Ptr, 0, rqptr->rqPathSet.Http2SendReset - 1, FI_LI); rqptr->rqResponse.HttpStatus = 205; /* reset content */ RequestEnd (rqptr); return; } if (rqptr->rqPathSet.Http2SendPing) { /***************/ /* HTTP/2 ping */ /***************/ if (HTTP2_REQUEST(rqptr)) Http2Ping (rqptr->Http2Ptr, 0, 0, NULL, 0); } if (rqptr->rqPathSet.ChangeServicePtr) { /******************/ /* change service */ /******************/ if (!ServiceChange (rqptr, rqptr->rqPathSet.ChangeServicePtr)) { RequestEnd (rqptr); return; } } if (mpptr[0] == '\1' && mpptr[1]) { /***********************/ /* mapping redirection */ /***********************/ ResponseLocation (rqptr, mpptr+1, -1); RequestEnd (rqptr); return; } if (rqptr->rqPathSet.Alert == MAPURL_PATH_ALERT_MAP) RequestAlert (rqptr); /* buffer the mapped path (mpptr+1 allows error and non-error mappings) */ rqptr->MappedPathLength = strlen(mpptr+1)+1; rqptr->MappedPathPtr = VmGetHeap (rqptr, rqptr->MappedPathLength+1); memcpy (rqptr->MappedPathPtr, mpptr, rqptr->MappedPathLength); if (!mpptr[0] && mpptr[1]) { /*****************/ /* mapping error */ /*****************/ /* if this path has any request throttling limits */ if (rqptr->rqPathSet.ThrottleSet) { status = ThrottleBegin (rqptr); if (VMSnok (status)) { /* either on the waiting list or waiting list exceeded */ if (status == SS$_ABORT) RequestEnd (rqptr); return; } /* continue to process the request (is on the active list) */ } RequestMappedToStatus (rqptr); return; } for (cptr = rqptr->RequestMappedFile; *cptr && *cptr != ':'; cptr++); if (SAME2(cptr,'::')) { /************************/ /* DECnet not supported */ /************************/ /* if a node is part of the VMS file name then it's not supported */ rqptr->rqResponse.HttpStatus = 501; rqptr->rqResponse.ErrorTextPtr = rqptr->rqHeader.PathInfoPtr; rqptr->rqResponse.ErrorOtherTextPtr = rqptr->RequestMappedFile; ErrorVmsStatus (rqptr, RMS$_SUPPORT, FI_LI); RequestEnd (rqptr); return; } #ifdef ODS_EXTENDED if (OdsExtended) rqptr->PathOdsExtended = (rqptr->PathOds == MAPURL_PATH_ODS_5); else #endif /* ODS_EXTENDED */ rqptr->PathOdsExtended = 0; /* MapUrl_Map() returns CGIplus mappings indicated by a leading '+' */ if (rqptr->ScriptName[0] == '+') { rqptr->IsCgiPlusScript = true; rqptr->ScriptName[0] = '/'; } else rqptr->IsCgiPlusScript = false; if (WATCHMOD (rqptr, WATCH_MOD_REQUEST)) WatchDataFormatted ("!&Z !&Z !&Z !&B !&Z !&Z !&Z\n", rqptr->rqHeader.PathInfoPtr, rqptr->RequestMappedFile, rqptr->ScriptName, rqptr->IsCgiPlusScript, rqptr->RequestMappedScript, rqptr->RequestMappedRunTime, mpptr); /* if it's been SET as an extension method by a mapping rule */ if (rqptr->rqPathSet.MapExtensionMethod) rqptr->rqHeader.Method = HTTP_METHOD_EXTENSION; /******************/ /* proxy request? */ /******************/ if (!rqptr->ScriptName[0] && (rqptr->rqHeader.Method == HTTP_METHOD_CONNECT || MATCH7 (mpptr, "http://") || MATCH8 (mpptr, "https://") || MATCH6 (mpptr, "ftp://") || MATCH6 (mpptr, "raw://") || MATCH9 (mpptr, "socks5://"))) { /* looks awfully like a scheme */ if (rqptr->rqPathSet.Http2ToHttp11) { /* proxy not supported as HTTP/2 ... HTTP/2 to HTTP/1.1 */ if (HTTP2_REQUEST(rqptr)) Http2DoError (rqptr->Http2Ptr, 0, HTTP2_ERROR_HTTP11, FI_LI); rqptr->rqResponse.HttpStatus = 101; RequestEnd (rqptr); return; } rqptr->ProxyRequest = true; /* if it's a trace and there are no more hops available */ if (rqptr->rqHeader.Method == HTTP_METHOD_TRACE || rqptr->rqHeader.MaxForwards) rqptr->ProxyRequest = false; } /*********************************/ /* authorization of request path */ /*********************************/ if (rqptr->ProxyRequest) { if (denptr = DictLookup (rqptr->rqDictPtr, DICT_TYPE_REQUEST, "proxy-authorization", 19)) rqptr->rqHeader.ProxyAuthorizationPtr = DICT_GET_VALUE(denptr); if (rqptr->ServicePtr->LocalAuthRequired) rqptr->rqAuth.RequestAuthorizationPtr = rqptr->rqHeader.AuthorizationPtr; else if (rqptr->ServicePtr->ProxyAuthRequired) rqptr->rqAuth.RequestAuthorizationPtr = rqptr->rqHeader.ProxyAuthorizationPtr; else if (rqptr->rqPathSet.ProxyReverseVerify) rqptr->rqAuth.RequestAuthorizationPtr = rqptr->rqHeader.AuthorizationPtr; else { /* if this proxy has any request throttling limits */ if (rqptr->rqPathSet.ThrottleSet) { status = ThrottleBegin (rqptr); if (VMSnok (status)) { /* either on waiting list or waiting list has been exceeded */ if (status == SS$_ABORT) RequestEnd (rqptr); return; } /* continue to process the request (is on the active list) */ } ProxyRequestBegin (rqptr); return; } } else rqptr->rqAuth.RequestAuthorizationPtr = rqptr->rqHeader.AuthorizationPtr; if (!rqptr->rqPathSet.AuthorizeMapped) Authorize (rqptr, rqptr->rqHeader.PathInfoPtr, rqptr->rqHeader.PathInfoLength, rqptr->rqAuth.Protect1Ptr, rqptr->rqAuth.Protect1Length, &RequestExecutePostAuth1); else if (rqptr->ScriptName[0]) Authorize (rqptr, rqptr->ScriptName, strlen(rqptr->ScriptName), rqptr->rqAuth.Protect1Ptr, rqptr->rqAuth.Protect1Length, &RequestExecutePostAuth1); else Authorize (rqptr, rqptr->MappedPathPtr, rqptr->MappedPathLength, rqptr->rqAuth.Protect1Ptr, rqptr->rqAuth.Protect1Length, &RequestExecutePostAuth1); if (WATCHMOD (rqptr, WATCH_MOD_REQUEST)) WatchDataFormatted ("!&S\n", rqptr->rqAuth.FinalStatus); if (VMSnok (rqptr->rqAuth.FinalStatus)) { /* if asynchronous authentication is not underway */ if (rqptr->rqAuth.FinalStatus != AUTH_PENDING) RequestEnd (rqptr); return; } /* authorization completed, continue to process */ RequestExecutePostAuth1 (rqptr); } /*****************************************************************************/ /* An error message has been returnd by MapUrl_Map(). An independent function so that it can be called directly after initial mapping if no throttling involved or from RequestExecutePostThrottle() if throttle was specified. */ void RequestMappedToStatus (REQUEST_STRUCT *rqptr) { int status; char *mpptr; /*********/ /* begin */ /*********/ if (WATCHMOD (rqptr, WATCH_MOD_REQUEST)) WatchThis (WATCHITM(rqptr), WATCH_MOD_REQUEST, "RequestMappedToStatus() !&Z", rqptr->MappedPathPtr+1); /* MapUrl_Map() returns errors with a leading null character */ mpptr = rqptr->MappedPathPtr + 1; if (isdigit (*mpptr)) { /* an explicitly mapped error (e.g. pass * "403 uh-ahh!") */ rqptr->rqResponse.HttpStatus = atol(mpptr); while (*mpptr && isdigit(*mpptr)) mpptr++; while (*mpptr && ISLWS(*mpptr)) mpptr++; if (rqptr->rqResponse.HttpStatus == 200) { /* mapped to CLI command */ while (*mpptr && isspace(*mpptr)) mpptr++; if (*mpptr == '$') { mpptr++; while (*mpptr && isspace(*mpptr)) mpptr++; ResponseHeader200 (rqptr, "text/plain", NULL); DclBegin (rqptr, &RequestEnd, mpptr, NULL, NULL, NULL, NULL, NULL); return; } } if (rqptr->rqResponse.HttpStatus >= 200 && rqptr->rqResponse.HttpStatus <= 299) { if (rqptr->rqHeader.UpgradeSocks5Ptr) ProxySocks5ReplyFail (rqptr); else if (rqptr->rqResponse.HttpStatus == 204) ResponseHeader (rqptr, 204, NULL, -1, NULL, NULL); else { ResponseHeader (rqptr, rqptr->rqResponse.HttpStatus, NULL, strlen(mpptr)+1, NULL, NULL); FaoToNet (rqptr, "!AZ\n", mpptr); } RequestEnd (rqptr); return; } if (rqptr->rqResponse.HttpStatus >= 300 && rqptr->rqResponse.HttpStatus <= 399) { /* redirection, message "text" should be the location */ ResponseLocation (rqptr, mpptr, -1); RequestEnd (rqptr); return; } if (rqptr->rqResponse.HttpStatus == NetRejectStatusCode) { /* used to REJECT a connection (must be before 418) */ if (rqptr->rqHeader.UpgradeSocks5Ptr) ProxySocks5ReplyFail (rqptr); NetRejectSetStatus (rqptr); RequestEnd (rqptr); return; } if (rqptr->rqResponse.HttpStatus == 418) { /* 418 can be used to drop a connection using a "standard" code */ if (rqptr->rqHeader.UpgradeSocks5Ptr) ProxySocks5ReplyFail (rqptr); RequestEnd (rqptr); return; } if (rqptr->rqResponse.HttpStatus >= 400 && rqptr->rqResponse.HttpStatus <= 599) { if (rqptr->rqHeader.UpgradeSocks5Ptr) ProxySocks5ReplyFail (rqptr); else ErrorGeneral (rqptr, mpptr, FI_LI); RequestEnd (rqptr); return; } /* no point to codes other 2nn/3nn/4nn/5nn, use to abort connection */ if (rqptr->rqHeader.UpgradeSocks5Ptr) ProxySocks5ReplyFail (rqptr); RequestEnd (rqptr); return; } else { /* some implicitly generated message */ if (rqptr->rqHeader.UpgradeSocks5Ptr) { ProxySocks5ReplyFail (rqptr); RequestEnd (rqptr); return; } if (!strcmp (mpptr, MAPURL_USER_RULE_FORBIDDEN_MSG+1)) { /* convert USER mapping rule forbidden into directory not found */ rqptr->rqResponse.HttpStatus = 404; rqptr->rqResponse.ErrorTextPtr = rqptr->rqHeader.PathInfoPtr; ErrorVmsStatus (rqptr, RMS$_DNF, FI_LI); RequestEnd (rqptr); return; } /* just a rule-mapping message */ rqptr->rqResponse.HttpStatus = 403; ErrorGeneral (rqptr, mpptr, FI_LI); RequestEnd (rqptr); return; } } /*****************************************************************************/ /* This function continues to initiate the request processing, either called directly from RequestExecute() or as an AST after asynchronous authentication. */ void RequestExecutePostAuth1 (REQUEST_STRUCT *rqptr) { int length, status; char *cptr, *sptr; DICT_ENTRY_STRUCT *denptr; /*********/ /* begin */ /*********/ if (WATCHMOD (rqptr, WATCH_MOD_REQUEST)) WatchThis (WATCHITM(rqptr), WATCH_MOD_REQUEST, "RequestExecutePostAuth1() !&F !&S", &RequestExecutePostAuth1, rqptr->rqAuth.FinalStatus); /* this may have been delivered asynchronously */ rqptr->rqAuth.AstFunction = NULL; if (VMSnok (rqptr->rqAuth.FinalStatus)) { RequestEnd (rqptr); return; } if (WATCH_NOT_ONE_SHOT(Watch.Category)) WatchFilterRealmUser (rqptr); if (rqptr->rqPathSet.Alert == MAPURL_PATH_ALERT_AUTH) RequestAlert (rqptr); /*****************************/ /* authorized proxy request? */ /*****************************/ if (rqptr->ProxyRequest) { if ((rqptr->ServicePtr->LocalAuthRequired || rqptr->ServicePtr->ProxyAuthRequired) && !rqptr->RemoteUser[0]) { /* authorization never occured! */ rqptr->rqResponse.HttpStatus = 403; ErrorGeneral (rqptr, MsgFor(rqptr,MSG_GENERAL_DISABLED), FI_LI); RequestEnd (rqptr); return; } RequestExecutePostAuth2 (rqptr); return; } if (rqptr->rqHeader.OriginPtr && rqptr->rqPathSet.CorsAllowOriginPtr) { /*********************************/ /* Cross-Origin Resource Sharing */ /*********************************/ ResponseCorsProcess (rqptr); } /************************************/ /* handled internally by the server */ /************************************/ if (rqptr->rqHeader.Method == HTTP_METHOD_OPTIONS) { rqptr->rqCache.DoNotCache = true; ResponseOptions (rqptr); return; } if (rqptr->rqHeader.Method == HTTP_METHOD_TRACE) { if (Config.cfMisc.HttpTraceEnabled) { rqptr->rqCache.DoNotCache = true; ResponseTrace (rqptr); } else { rqptr->rqResponse.HttpStatus = 501; ErrorGeneral (rqptr, MsgFor(rqptr,MSG_REQUEST_METHOD), FI_LI); RequestEnd (rqptr); } return; } if (strsame (rqptr->rqHeader.RequestUriPtr, HTTPD_ADMIN, sizeof(HTTPD_ADMIN)-1)) { rqptr->rqCache.DoNotCache = rqptr->InternalRequest = true; AdminBegin (rqptr); return; } if (strsame (rqptr->rqHeader.RequestUriPtr, HTTPD_VS_ADMIN, sizeof(HTTPD_VS_ADMIN)-1)) { rqptr->rqCache.DoNotCache = rqptr->InternalRequest = true; AdminBegin (rqptr); return; } if (strsame (rqptr->rqHeader.RequestUriPtr, HTTPD_VERIFY, sizeof(HTTPD_VERIFY)-1)) { rqptr->rqCache.DoNotCache = rqptr->InternalRequest = true; ProxyVerifyRequest (rqptr, &RequestEnd); return; } if (strsame (rqptr->rqHeader.RequestUriPtr, INTERNAL_PASSWORD_CHANGE, sizeof(INTERNAL_PASSWORD_CHANGE)-1)) { rqptr->rqCache.DoNotCache = rqptr->InternalRequest = true; HTAdminBegin (rqptr); return; } /******************************/ /* check for script execution */ /******************************/ if (rqptr->ScriptName[0]) { /* get the path derived from the script specification */ sptr = rqptr->ScriptName; /* kludge to allow for 'implied' scripts (e.g. "/path/info") */ if (!SAME2(sptr,'/\0')) { cptr = rqptr->rqHeader.PathInfoPtr; while (*sptr) { if (to_lower(*sptr) != to_lower(*cptr)) break; sptr++; cptr++; } if (!*sptr) { for (sptr = rqptr->rqHeader.PathInfoPtr; *cptr; *sptr++ = *cptr++); *sptr = '\0'; length = sptr - rqptr->rqHeader.PathInfoPtr; rqptr->rqHeader.PathInfoLength = length; denptr = DictLookup (rqptr->rqDictPtr, DICT_TYPE_INTERNAL, "path_info", 9); if (denptr) DictValueLength (denptr, length); } } if (!rqptr->rqPathSet.AuthorizeOnce) { /******************************************/ /* authorization of script path parameter */ /******************************************/ if (!rqptr->rqPathSet.AuthorizeMapped) Authorize (rqptr, rqptr->rqHeader.PathInfoPtr, rqptr->rqHeader.PathInfoLength, rqptr->rqAuth.Protect2Ptr, rqptr->rqAuth.Protect2Length, &RequestExecutePostAuth2); else Authorize (rqptr, rqptr->MappedPathPtr, rqptr->MappedPathLength, rqptr->rqAuth.Protect2Ptr, rqptr->rqAuth.Protect2Length, &RequestExecutePostAuth2); if (VMSnok (rqptr->rqAuth.FinalStatus)) { /* if asynchronous authentication is not underway */ if (rqptr->rqAuth.FinalStatus != AUTH_PENDING) RequestEnd (rqptr); return; } } } /***********************/ /* continue non-script */ /***********************/ RequestExecutePostAuth2 (rqptr); } /*****************************************************************************/ /* This function continues to initiate the request processing, either called directly from RequestExecutePostAuth() or as an AST after asynchronous authentication. */ void RequestExecutePostAuth2 (REQUEST_STRUCT *rqptr) { int status; /*********/ /* begin */ /*********/ if (WATCHMOD (rqptr, WATCH_MOD_REQUEST)) WatchThis (WATCHITM(rqptr), WATCH_MOD_REQUEST, "RequestExecutePostAuth2() !&F !&S", &RequestExecutePostAuth2, rqptr->rqAuth.FinalStatus); /* this may have been delivered asynchronously */ rqptr->rqAuth.AstFunction = NULL; if (VMSnok (rqptr->rqAuth.FinalStatus)) { RequestEnd (rqptr); return; } if (rqptr->WebDavRequest && !rqptr->RemoteUser[0] && rqptr->rqAuth.SourceRealm != AUTH_SOURCE_EXTERNAL && rqptr->rqAuth.SourceRealm != AUTH_SOURCE_OPAQUE) { /* WebDAV requests MUST have authorization applied! */ if (WATCHING (rqptr, WATCH_REQUEST)) WatchThis (WATCHITM(rqptr), WATCH_REQUEST, "WEBDAV paths must have an authorization rule"); DavWebResponse (rqptr, 403, SS$_NOPRIV, "no authorisation", FI_LI); RequestEnd (rqptr); return; } if (WATCH_NOT_ONE_SHOT(Watch.Category)) WatchFilterRealmUser (rqptr); if (rqptr->rqPathSet.Alert == MAPURL_PATH_ALERT_AUTH) RequestAlert (rqptr); /* if this path has any request throttling limits */ if (rqptr->rqPathSet.ThrottleSet) { status = ThrottleBegin (rqptr); if (VMSnok (status)) { /* either on the waiting list or waiting list has been exceeded */ if (status == SS$_ABORT) RequestEnd (rqptr); return; } /* continue to process the request (is on the active list) */ } RequestExecutePostThrottle (rqptr); } /*****************************************************************************/ /* Called by RequestExecutePostAuth2() after throttle is assessed against the path and not initiated, or as an AST initiated by ThrottleRelease() after throttling has been employed against the request. */ void RequestExecutePostThrottle (REQUEST_STRUCT *rqptr) { int status; char *cptr, *sptr, *zptr; char Md5String [8192]; /*********/ /* begin */ /*********/ if (WATCHMOD (rqptr, WATCH_MOD_REQUEST)) WatchThis (WATCHITM(rqptr), WATCH_MOD_REQUEST, "RequestExecutePostThrottle() !&F", &RequestExecutePostThrottle); /* MapUrl_Map() returns errors with a leading null character */ if (!rqptr->MappedPathPtr[0] && rqptr->MappedPathPtr[1]) { /******************************/ /* mapped but throttled error */ /******************************/ RequestMappedToStatus (rqptr); return; } if (rqptr->ProxyRequest) { /*****************/ /* proxy request */ /*****************/ ProxyRequestBegin (rqptr); return; } /***********************/ /* MD5 hash of request */ /***********************/ if (rqptr->ScriptName[0]) { /* non-file hashes are generated using the service plus URI */ zptr = (sptr = Md5String) + sizeof(Md5String); for (cptr = rqptr->ServicePtr->ServerHostPort; *cptr && sptr < zptr; *sptr++ = *cptr++); for (cptr = rqptr->rqHeader.RequestUriPtr; *cptr && *cptr != '?' && sptr < zptr; *sptr++ = *cptr++); if (rqptr->rqPathSet.CacheQuery) while (*cptr && sptr < zptr) *sptr++ = *cptr++; if (sptr >= zptr) { ErrorNoticed (rqptr, SS$_RESULTOVF, "Md5String", FI_LI); ErrorGeneralOverflow (rqptr, FI_LI); RequestEnd (rqptr); return; } *sptr = '\0'; Md5Digest (Md5String, sptr-Md5String, &rqptr->Md5HashPath); } else { /* file hash is generated using the path equivalent of VMS file spec */ Md5Digest (rqptr->MappedPathPtr, rqptr->MappedPathLength, &rqptr->Md5HashPath); } /***************/ /* from cache? */ /***************/ status = CacheSearch (rqptr); if (WATCHMOD (rqptr, WATCH_MOD_REQUEST)) WatchThis (WATCHITM(rqptr), WATCH_MOD_REQUEST, "CacheSearch() !&S", status); /* success status indicates the request is being supplied from cache */ if (VMSok (status)) return; /* not from initial cache search */ RequestExecutePostCache1 (rqptr); } /*****************************************************************************/ /* Called by RequestExecutePostThrottle() after cache search is performed. Called by CacheEnd() if ACP data is collected and unsuitable. */ void RequestExecutePostCache1 (REQUEST_STRUCT *rqptr) { BOOL WildcardName; int len; char *cptr, *sptr, *zptr; /*********/ /* begin */ /*********/ if (WATCHMOD (rqptr, WATCH_MOD_REQUEST)) WatchThis (WATCHITM(rqptr), WATCH_MOD_REQUEST, "RequestExecutePostCache1() !&F", &RequestExecutePostCache1); if (rqptr->ScriptName[0]) { /**********/ /* script */ /**********/ if (rqptr->RawSocketRequest) { /* append the mapped script to the request URI for logging purposes */ len = strlen(rqptr->ScriptName) + rqptr->rqHeader.RequestUriLength + 2; cptr = rqptr->rqHeader.RequestUriPtr; sptr = rqptr->rqHeader.RequestUriPtr = VmGetHeap (rqptr, len); while (*cptr) *sptr++ = *cptr++; *sptr++ = '-'; *sptr++ = '>'; for (cptr = rqptr->ScriptName; *cptr; *sptr++ = *cptr++); } RequestScript (rqptr, rqptr->RequestMappedScript, rqptr->RequestMappedFile, rqptr->RequestMappedRunTime); return; } if (rqptr->WebSocketRequest) { /*********************/ /* WebSocket request */ /*********************/ /* should have been take care of by now! */ rqptr->rqResponse.HttpStatus = 500; RequestEnd (rqptr); return; } /* check for wildcard in file name or type */ for (cptr = rqptr->MappedPathPtr; *cptr; cptr++); sptr = cptr; while (cptr > rqptr->MappedPathPtr && *cptr != ';' && *cptr != '/') cptr--; WildcardName = false; if (*cptr == ';') { /* not interested in caching specific version, only version-less paths */ if (!cptr[1] || isdigit(cptr[1])) rqptr->rqCache.DoNotCache = true; /* check for wildcard characters in name or type */ while (cptr > rqptr->MappedPathPtr && *cptr != '/') { if (*cptr == '*' || *cptr == '%' || *cptr == '?') WildcardName = true; cptr--; } } /* now resolve content-type */ cptr = sptr; while (cptr > rqptr->MappedPathPtr && *cptr != '.' && *cptr != '/') cptr--; if (*cptr != '.') cptr = ""; ConfigContentType (&rqptr->rqContentInfo, cptr); /* if not resolving a language-specific document and GET or HEAD */ if (!(WildcardName || rqptr->rqCache.DoNotCache) && (rqptr->rqHeader.Method == HTTP_METHOD_GET || rqptr->rqHeader.Method == HTTP_METHOD_HEAD)) { /* as there is no file name specified this will just search the cache */ FileBegin (rqptr, &RequestEnd, &RequestExecutePostCache2, &rqptr->Md5HashPath, NULL, NULL); return; } /* not a file */ RequestExecutePostCache2 (rqptr); } /*****************************************************************************/ /* If a file was not found by searching the file cache using the mapped path hash as the key then this function is called as the file-not-found AST of the FileBegin() call in RequestExecutePostThrottle() above. */ void RequestExecutePostCache2 (REQUEST_STRUCT *rqptr) { int status; char *cptr, *sptr, *MappedFilePtr; char MappedFile [ODS_MAX_FILE_NAME_LENGTH+1]; /*********/ /* begin */ /*********/ if (WATCHMOD (rqptr, WATCH_MOD_REQUEST)) WatchThis (WATCHITM(rqptr), WATCH_MOD_REQUEST, "RequestExecutePostCache2() !&F", &RequestExecutePostCache2); MappedFilePtr = rqptr->RequestMappedFile; /* check for device and directory (minimum) */ cptr = MappedFilePtr; if (*cptr == ':') cptr = ""; while (*cptr && !SAME2(cptr,':[')) cptr++; if (*cptr) cptr += 2; if (*cptr == ']') cptr = ""; if (!*cptr) { /* didn't find a device and/or directory - forbidden */ if (WATCHING (rqptr, WATCH_REQUEST)) WatchThis (WATCHITM(rqptr), WATCH_REQUEST, "MAPPING provided NO DEVICE and/or DIRECTORY!!"); rqptr->rqResponse.HttpStatus = 500; rqptr->rqResponse.ErrorOtherTextPtr = rqptr->RequestMappedFile; ErrorVmsStatus (rqptr, RMS$_SYN, FI_LI); RequestEnd (rqptr); return; } if (!rqptr->rqPathSet.OdsName || rqptr->rqPathSet.OdsName == MAPURL_ODS_8BIT) { /* is there a potentially UTF-8 bit pattern here? */ for (cptr = MappedFilePtr; *cptr && ((*cptr & 0xe0) != 0xc0); cptr++); if (*cptr) { /* some UTF-8 encode (e.g. KDE), others (e.g. MS WebFolders) do not */ strzcpy (MappedFile, rqptr->RequestMappedFile, sizeof(MappedFile)); if (ConvertFromUtf8 (MappedFile, -1, '$') > 0) { MappedFilePtr = MappedFile; if (WATCHING (rqptr, WATCH_REQUEST)) WatchThis (WATCHITM(rqptr), WATCH_REQUEST, "UTF-8 to native !AZ", MappedFilePtr); } } } else if (rqptr->rqPathSet.OdsName == MAPURL_ODS_UTF8) { if (ConvertUtf8ToEscape (rqptr->RequestMappedFile, MappedFile, sizeof(MappedFile)) > 0) { MappedFilePtr = MappedFile; if (WATCHING (rqptr, WATCH_REQUEST)) WatchThis (WATCHITM(rqptr), WATCH_REQUEST, "UTF-8 to escaped 8-bit !AZ", MappedFilePtr); } } OdsStructInit (&rqptr->ParseOds, false); /* parse the file specification (must have device and directory!!) */ OdsParse (&rqptr->ParseOds, MappedFilePtr, 0, NULL, 0, NAM$M_SYNCHK, NULL, rqptr); if (VMSnok (status = rqptr->ParseOds.Fab.fab$l_sts)) { if (WebDavEnabled) { if (rqptr->WebDavRequest || rqptr->WhiffOfWebDav) { if (status == RMS$_SYN) { DavWebMicrosoftMunge1 (rqptr); if (rqptr->rqResponse.LocationPtr) { RequestEnd (rqptr); return; } } } } rqptr->rqResponse.ErrorOtherTextPtr = rqptr->RequestMappedFile; ErrorVmsStatus (rqptr, status, FI_LI); RequestEnd (rqptr); return; } if (VMSnok (status = OdsParseTerminate (&rqptr->ParseOds))) { ErrorNoticed (rqptr, status, NULL, FI_LI); RequestEnd (rqptr); return; } if (WebDavEnabled) { if (rqptr->WebDavRequest || rqptr->WhiffOfWebDav || rqptr->rqPathSet.WebDavAll) { /******************/ /* WebDAV request */ /******************/ DavWebRequest (rqptr); return; } } RequestExecutePostCache3 (rqptr); } /*****************************************************************************/ /* Continue with request processing. May by called directly from above or from DavWebRequest2() if a GET or HEAD. */ void RequestExecutePostCache3 (REQUEST_STRUCT *rqptr) { int Count; char *cptr, *sptr; /*********/ /* begin */ /*********/ if (WATCHMOD (rqptr, WATCH_MOD_REQUEST)) WatchThis (WATCHITM(rqptr), WATCH_MOD_REQUEST, "RequestExecutePostCache3() !&F", &RequestExecutePostCache3); /* If the file name component comprises a leading hyphen followed by 20 digits (post v8.4.1, "-yyyymmddhhmmsshhnnnn-") or 16 digits (pre v8.4.2, "-yyyymmddhhmmsshh-") and a trailing hyphen, then consider it temporary and delete it on close. See comments in PUT.C. */ rqptr->DeleteOnClose = false; if (rqptr->ParseOds.NamNameLength == 22 && rqptr->ParseOds.NamNamePtr[0] == '-' && rqptr->ParseOds.NamNamePtr[21] == '-') { for (cptr = rqptr->ParseOds.NamNamePtr+1; isdigit(*cptr); cptr++); if (cptr == rqptr->ParseOds.NamNamePtr+21) { rqptr->DeleteOnClose = true; rqptr->rqResponse.PreExpired = PRE_EXPIRE_DELETE_ON_CLOSE; } } else if (rqptr->ParseOds.NamNameLength == 18 && rqptr->ParseOds.NamNamePtr[0] == '-' && rqptr->ParseOds.NamNamePtr[17] == '-') { for (cptr = rqptr->ParseOds.NamNamePtr+1; isdigit(*cptr); cptr++); if (cptr == rqptr->ParseOds.NamNamePtr+17) { rqptr->DeleteOnClose = true; rqptr->rqResponse.PreExpired = PRE_EXPIRE_DELETE_ON_CLOSE; } } /* all these "file write" methods are handled by the one set of functions */ if ((rqptr->rqHeader.Method == HTTP_METHOD_PUT || rqptr->rqHeader.Method == HTTP_METHOD_POST || rqptr->rqHeader.Method == HTTP_METHOD_DELETE) && !rqptr->RedirectErrorStatusCode) { /* when WebDAV is enabled PUT and DELETE are intercepted above */ PutBegin (rqptr, &RequestEnd); return; } if (rqptr->rqHeader.QueryStringLength && (to_lower(rqptr->rqHeader.QueryStringPtr[0]) == 'h' && strsame (rqptr->rqHeader.QueryStringPtr, "httpd=index", 11)) || (((rqptr->ParseOds.Nam_fnb & NAM$M_WILDCARD) || (rqptr->ParseOds.Nam_fnb & NAM$M_EXP_VER && rqptr->ParseOds.NamNamePtr == rqptr->ParseOds.NamTypePtr)) && !rqptr->rqHeader.QueryStringPtr[0])) { /******************************/ /* generate directory listing */ /******************************/ if ((Config.cfDir.WildcardEnabled || rqptr->rqPathSet.DirWildcard) && !rqptr->rqPathSet.DirNoWildcard) { if (rqptr->rqPathSet.IndexPtr) cptr = rqptr->rqPathSet.IndexPtr; else if (rqptr->rqHeader.QueryStringLength) cptr = rqptr->rqHeader.QueryStringPtr; else cptr = ""; DirBegin (rqptr, &RequestEnd, rqptr->ParseOds.ExpFileName, cptr, "", false); return; } else { rqptr->rqResponse.HttpStatus = 403; ErrorGeneral (rqptr, MsgFor(rqptr,MSG_GENERAL_DISABLED), FI_LI); RequestEnd (rqptr); return; } } if (!rqptr->ParseOds.NamNameLength && !rqptr->ParseOds.NamTypeLength && !rqptr->rqHeader.QueryStringLength) { /*********************************************/ /* no file name supplied, look for home page */ /*********************************************/ RequestHomePage (rqptr); return; } /********************************/ /* get the content-type details */ /********************************/ if (!isdigit(rqptr->ParseOds.NamVersionPtr[1]) && !(rqptr->rqHeader.QueryStringLength && to_lower(rqptr->rqHeader.QueryStringPtr[0]) == 'h' && strsame (rqptr->rqHeader.QueryStringPtr, "httpd=", 6))) { /* If a file specification does not include a specific version number (which indicates send-me-this-file-regardless), and the request does not contain an HTTPd query string, and ConfigContentType() has returned a script name, indicating an automatic script handling for this file type, then redirect. */ if (rqptr->rqContentInfo.AutoScriptNamePtr[0] == '/') { /********************************/ /* automatic script redirection */ /********************************/ InstanceGblSecIncrLong (&acptr->DoAutoScriptCount); if (WATCHING (rqptr, WATCH_REQUEST)) WatchThis (WATCHITM(rqptr), WATCH_REQUEST, "AUTOSCRIPT \'!AZ\'", rqptr->rqContentInfo.AutoScriptNamePtr); Count = strlen(cptr = rqptr->rqContentInfo.AutoScriptNamePtr); Count += rqptr->rqHeader.PathInfoLength; sptr = ResponseLocation (rqptr, NULL, Count+1); while (*cptr) *sptr++ = *cptr++; for (cptr = rqptr->rqHeader.PathInfoPtr; *cptr; *sptr++ = *cptr++); *sptr++ = '?'; RequestEnd (rqptr); return; } } /**************************/ /* implied keyword search */ /**************************/ if (!rqptr->KeywordRedirectCount && rqptr->rqHeader.QueryStringLength && Config.cfScript.DefaultSearchLength && !rqptr->rqPathSet.NoDefaultSearch && !(rqptr->rqHeader.QueryStringLength && to_lower(rqptr->rqHeader.QueryStringPtr[0]) == 'h' && strsame (rqptr->rqHeader.QueryStringPtr, "httpd=", 6))) { if (Config.cfScript.DefaultSearchExcludePtr) { /********************************/ /* exclude specified file types */ /********************************/ if (WATCHMOD (rqptr, WATCH_MOD_REQUEST)) WatchDataFormatted ("!&Z\n", Config.cfScript.DefaultSearchExcludePtr); cptr = ""; sptr = Config.cfScript.DefaultSearchExcludePtr; while (*sptr) { cptr = rqptr->ParseOds.NamTypePtr; if (WATCHMOD (rqptr, WATCH_MOD_REQUEST)) WatchDataFormatted ("!&Z !&Z\n", sptr, cptr); while (*cptr) { if (*sptr == STRING_LIST_CHAR || *cptr == ';') break; if (to_upper(*cptr) != to_upper(*sptr)) break; if (*cptr) cptr++; if (*sptr) sptr++; } if ((!*cptr || *cptr == ';') && (!*sptr || *sptr == STRING_LIST_CHAR)) break; while (*sptr && *sptr != STRING_LIST_CHAR) sptr++; if (*sptr) sptr++; } if ((!*cptr || *cptr == ';') && (!*sptr || *sptr == STRING_LIST_CHAR)) if (WATCHING (rqptr, WATCH_REQUEST)) WatchThis (WATCHITM(rqptr), WATCH_REQUEST, "KEYWORD search \"!AZ\" excluded in \'!AZ\'", rqptr->ParseOds.NamTypePtr, Config.cfScript.DefaultSearchExcludePtr); } if (!Config.cfScript.DefaultSearchExcludePtr || (*cptr && *cptr != ';') || (*sptr && *sptr != STRING_LIST_CHAR)) { /******************/ /* search file(s) */ /******************/ if (WATCHING (rqptr, WATCH_REQUEST)) WatchThis (WATCHITM(rqptr), WATCH_REQUEST, "KEYWORD search \"!AZ\"", rqptr->rqHeader.QueryStringPtr); /* local redirection, so keyword search script gets remapped */ Count = Config.cfScript.DefaultSearchLength; Count += rqptr->rqHeader.PathInfoLength; sptr = ResponseLocation (rqptr, NULL, Count+1); for (cptr = Config.cfScript.DefaultSearch; *cptr; *sptr++ = *cptr++); for (cptr = rqptr->rqHeader.PathInfoPtr; *cptr; *sptr++ = *cptr++); *sptr++ = '?'; rqptr->KeywordRedirectCount++; RequestEnd (rqptr); return; } } if (!rqptr->ParseOds.NamNameLength && !rqptr->ParseOds.NamTypeLength) { /*********************************************/ /* no file name supplied, look for home page */ /*********************************************/ RequestHomePage (rqptr); return; } /****************************************/ /* otherwise ... send the file contents */ /****************************************/ if (!HTTP2_REQUEST(rqptr)) TcpIpSocketSndBuf (rqptr->NetIoPtr); /* allow for directories specified as "/dir1/dir2" (i.e. no trailing '/') */ if (rqptr->ParseOds.NamTypeLength <= 1) RequestFile (rqptr, &RequestEnd, &RequestFileNoType); else RequestFile (rqptr, &RequestEnd, NULL); } /*****************************************************************************/ /* This function can be called one or more times per request. It steps through all possible home page files until either one is found or a default directory listing is generated. 'rqptr->HomePageStatus' attempts to short-circuit searches for conditions other than file-not-found. For example, if the status is directory-not-found then there is little point in continuing to look for home pages in that location! */ void RequestHomePage (REQUEST_STRUCT *rqptr) { int idx, status, Count; char *cptr, *sptr, *FileTypePtr, *HomePageNamePtr; /*********/ /* begin */ /*********/ if (WATCHMOD (rqptr, WATCH_MOD_REQUEST)) WatchThis (WATCHITM(rqptr), WATCH_MOD_REQUEST, "RequestHomePage() !UL", rqptr->RequestHomePageIndex); if (VMSok (rqptr->HomePageStatus)) { RequestEnd (rqptr); return; } if (rqptr->rqResponse.HeaderGenerated || ERROR_REPORTED (rqptr)) { RequestEnd (rqptr); return; } if (rqptr->RequestHomePageIndex && VMSnok (rqptr->HomePageStatus) && rqptr->HomePageStatus != RMS$_FNF) { rqptr->rqResponse.ErrorTextPtr = rqptr->rqHeader.PathInfoPtr; rqptr->rqResponse.ErrorOtherTextPtr = rqptr->ParseOds.NamDevicePtr; ErrorVmsStatus (rqptr, rqptr->HomePageStatus, FI_LI); RequestEnd (rqptr); return; } if (!*(HomePageNamePtr = ConfigHomePage(rqptr->RequestHomePageIndex++))) { /********************************************/ /* no home page, generate directory listing */ /********************************************/ if ((!Config.cfDir.Access && !rqptr->rqPathSet.DirAccess && !rqptr->rqPathSet.DirAccessSelective) || rqptr->rqPathSet.DirNoAccess) { /* directory listing is disabled */ rqptr->rqResponse.ErrorTextPtr = rqptr->rqHeader.PathInfoPtr; rqptr->rqResponse.ErrorOtherTextPtr = rqptr->ParseOds.NamDevicePtr; ErrorVmsStatus (rqptr, RMS$_FNF, FI_LI); RequestEnd (rqptr); return; } rqptr->ParseOds.NamNamePtr[0] = '\0'; if (rqptr->rqPathSet.IndexPtr) cptr = rqptr->rqPathSet.IndexPtr; else if (rqptr->rqHeader.QueryStringLength) cptr = rqptr->rqHeader.QueryStringPtr; else cptr = ""; DirBegin (rqptr, &RequestEnd, rqptr->ParseOds.NamDevicePtr, cptr, "", false); return; } for (cptr = HomePageNamePtr; *cptr && *cptr != '.'; cptr++); FileTypePtr = cptr; /******************************/ /* check for script home page */ /******************************/ for (idx = 0; idx < Config.cfScript.RunTimeCount; idx++) { if (WATCHMOD (rqptr, WATCH_MOD_REQUEST)) WatchDataFormatted ("!&Z !&Z !UL\n", cptr, Config.cfScript.RunTime[idx].StringPtr, Config.cfScript.RunTime[idx].FileTypeLength); if (strsame (cptr, Config.cfScript.RunTime[idx].StringPtr, Config.cfScript.RunTime[idx].FileTypeLength-1)) break; } if (idx < Config.cfScript.RunTimeCount) { /* found an appropriate script file type, does the file exist? */ status = OdsFileExists (rqptr->ParseOds.NamDevicePtr, HomePageNamePtr); if (VMSnok (status)) { /* (probably) file does not exist */ rqptr->HomePageStatus = status; SysDclAst (RequestHomePage, rqptr); return; } /********************/ /* script home page */ /********************/ if (WATCHING (rqptr, WATCH_REQUEST)) WatchThis (WATCHITM(rqptr), WATCH_REQUEST, "SCRIPT welcome !#AZ!AZ", rqptr->ParseOds.NamDeviceLength + rqptr->ParseOds.NamDirectoryLength, rqptr->ParseOds.NamDevicePtr, HomePageNamePtr); /* create a redirection to the original path plus the file name */ Count = rqptr->rqHeader.PathInfoLength; Count += strlen(HomePageNamePtr); sptr = ResponseLocation (rqptr, NULL, Count+1); for (cptr = rqptr->rqHeader.PathInfoPtr; *cptr; *sptr++ = *cptr++); for (cptr = HomePageNamePtr; *cptr; *sptr++ = *cptr++); *sptr++ = '?'; RequestEnd (rqptr); return; } /********************************/ /* generate home page file name */ /********************************/ /* overwrite any existing file name in the NAM block */ strzcpy (rqptr->ParseOds.NamNamePtr, HomePageNamePtr, 255); /* set the mime content type for this file type */ ConfigContentType (&rqptr->rqContentInfo, FileTypePtr); rqptr->HomePageStatus = SS$_NORMAL; RequestFile (rqptr, &RequestEnd, &RequestHomePage); } /*****************************************************************************/ /* Send a file to the client. Special case is pre-processed HTML. */ void RequestFile ( REQUEST_STRUCT *rqptr, REQUEST_AST NextTaskFunction, REQUEST_AST NoSuchFileFunction ) { /*********/ /* begin */ /*********/ if (WATCHMOD (rqptr, WATCH_MOD_REQUEST)) WatchThis (WATCHITM(rqptr), WATCH_MOD_REQUEST, "RequestFile() !&A !&A !&Z !&Z", NextTaskFunction, NoSuchFileFunction, rqptr->ParseOds.ExpFileName, rqptr->rqContentInfo.ContentTypePtr); if (!(rqptr->rqHeader.QueryStringLength && rqptr->rqHeader.QueryStringPtr[0] && to_lower(rqptr->rqHeader.QueryStringPtr[0]) == 'h' && strsame (rqptr->rqHeader.QueryStringPtr, "httpd=content", 13))) { if (ConfigSameContentType (rqptr->rqContentInfo.ContentTypePtr, ConfigContentTypeSsi, -1)) { FileSetAuthorizePath (rqptr, false); FileSetCacheAllowed (rqptr, true); FileSetContentHandler (rqptr, &SsiBegin, SsiSizeMax); FileBegin (rqptr, NextTaskFunction, NoSuchFileFunction, /* A NULL path hash will cause the *file name* to be used. This allows the SSI output to be cached as well if the path indicates that is required. */ rqptr->rqPathSet.CacheSSI ? NULL : &rqptr->Md5HashPath, rqptr->ParseOds.ExpFileName, rqptr->rqContentInfo.ContentTypePtr); return; } } /* JAF */ FileSetAuthorizePath (rqptr, false); FileSetCacheAllowed (rqptr, true); FileBegin (rqptr, NextTaskFunction, NoSuchFileFunction, &rqptr->Md5HashPath, rqptr->ParseOds.ExpFileName, rqptr->rqContentInfo.ContentTypePtr); } /*****************************************************************************/ /* The file requested had no type (extension) and was not found. Check if there is a directory of that name. This allows for directories to be specified as "/dir1/dir2" (i.e. no trailing '/') which is not strictly kosher in WWW syntax but is allowed by some servers and so does occur in some documents. Generate a redirection to the same URL but with a trailing slash on the directory name. */ void RequestFileNoType (REQUEST_STRUCT *rqptr) { char DirName [ODS_MAX_FILE_NAME_LENGTH+1]; char ch; char *cptr, *sptr, *zptr; /*********/ /* begin */ /*********/ if (WATCHMOD (rqptr, WATCH_MOD_REQUEST)) WatchThis (WATCHITM(rqptr), WATCH_MOD_REQUEST, "RequestFileNoType()"); zptr = (sptr = DirName) + sizeof(DirName)-1; cptr = rqptr->ParseOds.NamNamePtr; while (*cptr && sptr < zptr) *sptr++ = *cptr++; cptr = ".DIR;"; while (*cptr && sptr < zptr) *sptr++ = *cptr++; *sptr = '\0'; ch = rqptr->ParseOds.NamNamePtr[0]; rqptr->ParseOds.NamNamePtr[0] = '\0'; if (VMSok (OdsFileExists (rqptr->ParseOds.NamDevicePtr, DirName))) { if (WATCHING (rqptr, WATCH_REQUEST)) WatchThis (WATCHITM(rqptr), WATCH_REQUEST, "DIRECTORY exists \'!AZ!AZ\'", rqptr->ParseOds.NamDevicePtr, DirName); sptr = ResponseLocation (rqptr, NULL, rqptr->rqHeader.PathInfoLength+4); *sptr++ = '/'; *sptr++ = '/'; for (cptr = rqptr->rqHeader.PathInfoPtr; *cptr; *sptr++ = *cptr++); *sptr++ = '/'; *sptr++ = '?'; RequestEnd (rqptr); return; } else { rqptr->ParseOds.NamNamePtr[0] = ch; if (!rqptr->AccountingDone++) InstanceGblSecIncrLong (&acptr->DoFileCount); rqptr->rqResponse.ErrorTextPtr = MapVmsPath (rqptr->ParseOds.NamDevicePtr, rqptr); rqptr->rqResponse.ErrorOtherTextPtr = rqptr->ParseOds.NamDevicePtr; ErrorVmsStatus (rqptr, RMS$_FNF, FI_LI); RequestEnd (rqptr); return; } } /*****************************************************************************/ /* Process request by executing a script. The 'MappedFile' was the non-script-name portion of the path, returned by the original MapUrl(), attempt to parse this as a VMS file specification, but if that fails (and it well might not be a file specification) then do not report it. Provide to the script a "best-guess" as to the file system ODS. */ void RequestScript ( REQUEST_STRUCT *rqptr, char *MappedScript, char *MappedFile, char *MappedRunTime ) { int status; char *cptr; /*********/ /* begin */ /*********/ if (WATCHMOD (rqptr, WATCH_MOD_REQUEST)) WatchThis (WATCHITM(rqptr), WATCH_MOD_REQUEST, "RequestScript() !&Z !&Z !&Z !&Z !&Z", rqptr->ScriptName, rqptr->rqHeader.PathInfoPtr, MappedScript, MappedFile, MappedRunTime); /***************************************/ /* check for internal server "scripts" */ /***************************************/ if (to_lower(rqptr->ScriptName[0]) == to_lower(ADMIN_SCRIPT_ECHO[0]) && strsame (rqptr->ScriptName, ADMIN_SCRIPT_ECHO, sizeof(ADMIN_SCRIPT_ECHO)-1)) { ResponseTrace (rqptr); return; } if (to_lower(rqptr->ScriptName[0]) == to_lower(ADMIN_SCRIPT_HISS[0]) && strsame (rqptr->ScriptName, ADMIN_SCRIPT_HISS, sizeof(ADMIN_SCRIPT_HISS)-1)) { ResponseHiss (rqptr); return; } if (to_lower(rqptr->ScriptName[0]) == to_lower(ADMIN_SCRIPT_STREAM[0]) && strsame (rqptr->ScriptName, ADMIN_SCRIPT_STREAM, sizeof(ADMIN_SCRIPT_STREAM)-1)) { ResponseStream (rqptr); return; } if (to_lower(rqptr->ScriptName[0]) == to_lower(ADMIN_SCRIPT_WHERE[0]) && strsame (rqptr->ScriptName, ADMIN_SCRIPT_WHERE, sizeof(ADMIN_SCRIPT_WHERE)-1)) { ResponseWhere (rqptr, MappedFile); return; } /********************************/ /* parse any file specification */ /********************************/ /* mapping must provide a device and directory */ cptr = MappedFile; if (*cptr == ':') cptr = ""; while (*cptr && !SAME2(cptr,':[')) cptr++; if (*cptr) cptr += 2; if (*cptr == ']') cptr = ""; if (!*cptr) MappedFile[0] = '\0'; if (MappedFile[0]) { OdsParse (&rqptr->ParseOds, MappedFile, 0, NULL, 0, NAM$M_SYNCHK, NULL, rqptr); if (VMSok (status = rqptr->ParseOds.Fab.fab$l_sts)) { if (VMSnok (status = OdsParseTerminate (&rqptr->ParseOds))) { ErrorNoticed (rqptr, status, NULL, FI_LI); RequestEnd (rqptr); return; } if (!(rqptr->ParseOds.Nam_fnb & NAM$M_WILDCARD)) ConfigContentType (&rqptr->rqContentInfo, rqptr->ParseOds.NamTypePtr); } else { /* problem parsing (probably not intended as a file specification) */ char *cptr, *sptr, *zptr; zptr = (sptr = rqptr->ParseOds.ExpFileName) + sizeof(rqptr->ParseOds.ExpFileName); for (cptr = MappedFile; *cptr && sptr < zptr; *sptr++ = *cptr++); if (sptr >= zptr) sptr = rqptr->ParseOds.ExpFileName; *sptr = '\0'; while (sptr > rqptr->ParseOds.ExpFileName && *sptr != '.') sptr--; if (*sptr == '.') ConfigContentType (&rqptr->rqContentInfo, sptr); } } else rqptr->ParseOds.ExpFileName[0] = '\0'; /*********************************************/ /* again check for internal server "scripts" */ /*********************************************/ if (to_lower(rqptr->ScriptName[0]) == to_lower(ADMIN_SCRIPT_UPD[0]) && strsame (rqptr->ScriptName, ADMIN_SCRIPT_UPD, -1)) { UpdBegin (rqptr, &RequestEnd); return; } else if (to_lower(rqptr->ScriptName[0]) == to_lower(ADMIN_SCRIPT_TREE[0]) && strsame (rqptr->ScriptName, ADMIN_SCRIPT_TREE, -1)) { UpdBegin (rqptr, &RequestEnd); return; } else if (to_lower(rqptr->ScriptName[0]) == to_lower(ADMIN_SCRIPT_XRAY[0]) && strsame (rqptr->ScriptName, ADMIN_SCRIPT_XRAY, -1)) { /* create a plain-text response header */ rqptr->PersistentRequest = false; rqptr->rqResponse.PreExpired = rqptr->rqResponse.NoGzip = rqptr->RedirectedXray = true; ResponseHeader200 (rqptr, "text/plain", NULL); /* set up a LOCAL redirection, stripping the '/xray' from the path */ ResponseLocation (rqptr, rqptr->rqHeader.RequestUriPtr + sizeof(ADMIN_SCRIPT_XRAY)-1, -1); /* write that header, returning to RequestEnd() for redirection */ NetWriteFullFlush (rqptr, &RequestEnd); return; } /***************************/ /* "real", external script */ /***************************/ if (rqptr->rqPathSet.ScriptPathFind) { status = OdsFileExists (NULL, rqptr->ParseOds.ExpFileName); if (VMSnok (status)) { ErrorVmsStatus (rqptr, status, FI_LI); RequestEnd (rqptr); return; } } for (cptr = MappedScript; *cptr && *cptr != ':'; cptr++); if (SAME2(cptr,'::')) DECnetBegin (rqptr, &RequestEnd, MappedScript, MappedRunTime); else if (rqptr->IsCgiPlusScript) DclBegin (rqptr, &RequestEnd, NULL, rqptr->ScriptName, NULL, MappedScript, MappedRunTime, NULL); else DclBegin (rqptr, &RequestEnd, NULL, rqptr->ScriptName, MappedScript, NULL, MappedRunTime, NULL); } /*****************************************************************************/ /* This function updates the global section with a formatted representation of the data of the latest request. The acounting data is of course permanently located in the area and so does not require any explicit placement into the section. */ void RequestGblSecUpdate (REQUEST_STRUCT *rqptr) { static char HexDigits [] = "0123456789abcdef"; int status; unsigned long FaoVector [16]; unsigned long *vecptr; char *cptr, *sptr, *zptr; HTTPD_GBLSEC *gsptr; /*********/ /* begin */ /*********/ if (WATCHMOD (rqptr, WATCH_MOD_REQUEST)) WatchThis (WATCHITM(rqptr), WATCH_MOD_REQUEST, "RequestGblSecUpdate()"); gsptr = HttpdGblSecPtr; if (!rqptr) { /* reset request data */ memset (&gsptr->Request, 0, sizeof(gsptr->Request)); return; } /***********************************/ /* update the monitor request data */ /***********************************/ gsptr->Request.HttpStatus = rqptr->rqResponse.HttpStatus; if (HTTP2_REQUEST(rqptr)) gsptr->Request.HttpProtocol = HTTP_VERSION_2; else gsptr->Request.HttpProtocol = rqptr->rqResponse.HttpVersion; gsptr->Request.BytesRawRx64 = rqptr->NetIoPtr->BytesRawRx64; gsptr->Request.BytesRawTx64 = rqptr->NetIoPtr->BytesRawTx64; gsptr->Request.BytesPerSecond = rqptr->BytesPerSecond; gsptr->Request.BytesTxGzipPercent = rqptr->BytesTxGzipPercent; vecptr = FaoVector; *vecptr++ = rqptr->rqTime.BeginTime7[2]; *vecptr++ = rqptr->rqTime.BeginTime7[3]; *vecptr++ = rqptr->rqTime.BeginTime7[4]; *vecptr++ = rqptr->rqTime.BeginTime7[5]; FaoCheck (sizeof(FaoVector), &FaoVector, vecptr, FI_LI); FaolToBuffer (gsptr->Request.Time, sizeof(gsptr->Request.Time), NULL, "!2ZL !2ZL:!2ZL:!2ZL", &FaoVector); vecptr = FaoVector; *vecptr++ = DurationString (rqptr, &rqptr->rqResponse.Duration64); FaolToBuffer (gsptr->Request.Duration, sizeof(gsptr->Request.Duration), NULL, "!AZ", &FaoVector); /* e.g. http://the.server.host.name:port */ zptr = (sptr = gsptr->Request.Service) + sizeof(gsptr->Request.Service)-1; for (cptr = rqptr->ServicePtr->RequestSchemeNamePtr; *cptr && sptr < zptr; *sptr++ = *cptr++); if (sptr < zptr) *sptr++ = '/'; if (sptr < zptr) *sptr++ = '/'; for (cptr = rqptr->ServicePtr->ServerHostName; *cptr && sptr < zptr; *sptr++ = *cptr++); if (sptr < zptr) *sptr++ = ':'; for (cptr = rqptr->ServicePtr->ServerPortString; *cptr && sptr < zptr; *sptr++ = *cptr++); *sptr = '\0'; if (InstanceNodeConfig > 1) { zptr = (sptr = gsptr->Request.PrcNam) + sizeof(gsptr->Request.PrcNam)-1; for (cptr = HttpdProcess.PrcNam; *cptr && sptr < zptr; *sptr++ = *cptr++); *sptr = '\0'; } else gsptr->Request.PrcNam[0] = '\0'; zptr = (sptr = gsptr->Request.ClientHostName) + sizeof(gsptr->Request.ClientHostName)-1; for (cptr = rqptr->ClientPtr->Lookup.HostName; *cptr && sptr < zptr; *sptr++ = *cptr++); *sptr = '\0'; zptr = (sptr = gsptr->Request.ClientIpAddressString) + sizeof(gsptr->Request.ClientIpAddressString)-1; for (cptr = rqptr->ClientPtr->IpAddressString; *cptr && sptr < zptr; *sptr++ = *cptr++); *sptr = '\0'; zptr = (sptr = gsptr->Request.MethodName) + sizeof(gsptr->Request.MethodName)-1; for (cptr = rqptr->rqHeader.MethodName; *cptr && sptr < zptr; cptr++) { if (*cptr >= '\"' && *cptr <= '~') *sptr++ = *cptr; else { *sptr++ = '!'; if (sptr < zptr) *sptr++ = HexDigits[(*cptr & 0xf0) >> 4]; if (sptr < zptr) *sptr++ = HexDigits[(*cptr & 0x0f)]; } } *sptr = '\0'; if (rqptr->rqHeader.RequestUriPtr) { zptr = (sptr = gsptr->Request.Uri) + sizeof(gsptr->Request.Uri)-1; for (cptr = rqptr->rqHeader.RequestUriPtr; *cptr && sptr < zptr; cptr++) { if (*cptr >= '\"' && *cptr <= '~') *sptr++ = *cptr; else { *sptr++ = '!'; if (sptr < zptr) *sptr++ = HexDigits[(*cptr & 0xf0) >> 4]; if (sptr < zptr) *sptr++ = HexDigits[(*cptr & 0x0f)]; } } *sptr = '\0'; } else gsptr->Request.Uri[0] = '\0'; if (rqptr->RemoteUser[0]) { zptr = (sptr = gsptr->Request.AuthUser) + sizeof(gsptr->Request.AuthUser)-1; for (cptr = rqptr->RemoteUser; *cptr; cptr++) if (!isalnum(*cptr) && *cptr != '_' && *cptr != '-') break; if (*cptr) *sptr++ = '\''; for (cptr = rqptr->RemoteUser; *cptr && sptr < zptr; *sptr++ = *cptr++); if (gsptr->Request.AuthUser[0] == '\'' && sptr rqAuth.RealmPtr) { if (sptr < zptr) *sptr++ = '.'; for (cptr = rqptr->rqAuth.RealmPtr; *cptr && sptr < zptr; *sptr++ = *cptr++); } *sptr = '\0'; } else gsptr->Request.AuthUser[0] = '\0'; gsptr->Request.Alert = rqptr->rqPathSet.Alert; if (rqptr->rqNet.ReadErrorCount) status = FaoToBuffer (gsptr->Request.ReadError, sizeof(gsptr->Request.ReadError), NULL, "%X!8XL !-!&m", rqptr->rqNet.ReadErrorStatus); else gsptr->Request.ReadError[0] = '\0'; if (rqptr->rqNet.WriteErrorCount) status = FaoToBuffer (gsptr->Request.WriteError, sizeof(gsptr->Request.WriteError), NULL, "%X!8XL !-!&m", rqptr->rqNet.WriteErrorStatus); else gsptr->Request.WriteError[0] = '\0'; } /*****************************************************************************/ /* Keeps a linked list history from most to least recent that can be reported on by RequestReport(). */ void RequestHistory (REQUEST_STRUCT *rqptr) { int status; char *cptr, *sptr, *zptr; struct RequestHistoryStruct *hlptr; /*********/ /* begin */ /*********/ if (WATCHMOD (rqptr, WATCH_MOD_REQUEST)) WatchThis (WATCHITM(rqptr), WATCH_MOD_REQUEST, "RequestHistory()"); if (RequestHistoryCount < RequestHistoryMax) { /* allocate memory for a new entry */ hlptr = VmGet (sizeof (struct RequestHistoryStruct)); RequestHistoryCount++; } else { /* reuse the tail entry (least recent) */ hlptr = RequestHistoryList.TailPtr; ListRemove (&RequestHistoryList, hlptr); } /* add entry to the head of the history list (most recent) */ ListAddHead (&RequestHistoryList, hlptr, LIST_ENTRY_TYPE_HISTORY); hlptr->Time64 = rqptr->rqTime.BeginTime64; hlptr->ConnectNumber = rqptr->ConnectNumber; hlptr->RequestState = rqptr->RequestState; hlptr->ServicePtr = rqptr->ServicePtr; hlptr->BytesRawRx64 = rqptr->NetIoPtr->BytesRawRx64; hlptr->BytesRawTx64 = rqptr->NetIoPtr->BytesRawTx64; hlptr->BytesPerSecond = rqptr->BytesPerSecond; hlptr->BytesTxGzipPercent = rqptr->BytesTxGzipPercent; hlptr->TimeoutType = rqptr->rqTmr.TimeoutType; hlptr->Duration64 = rqptr->rqResponse.Duration64; hlptr->ResponseStatusCode = rqptr->rqResponse.HttpStatus; if (HTTP2_REQUEST(rqptr)) hlptr->HttpMajorVersion = 2; else if (rqptr->rqResponse.HttpVersion == HTTP_VERSION_1_1 || rqptr->rqResponse.HttpVersion == HTTP_VERSION_1_0) hlptr->HttpMajorVersion = 1; else hlptr->HttpMajorVersion = 0; zptr = (sptr = hlptr->ClientAndRequest) + sizeof(hlptr->ClientAndRequest)-1; if (rqptr->RemoteUser[0]) { cptr = rqptr->RemoteUser; while (*cptr && sptr < zptr) *sptr++ = *cptr++; if (rqptr->rqAuth.RealmPtr) { if (sptr < zptr) *sptr++ = '.'; cptr = rqptr->rqAuth.RealmPtr; while (*cptr && sptr < zptr) *sptr++ = *cptr++; } if (sptr < zptr) *sptr++ = '@'; } cptr = rqptr->ClientPtr->Lookup.HostName; while (*cptr && sptr < zptr) *sptr++ = *cptr++; if (sptr < zptr) *sptr++ = '\0'; hlptr->RequestPtr = sptr; if (rqptr->rqHeader.PathInfoPtr && rqptr->rqHeader.QueryStringPtr) { cptr = rqptr->rqHeader.MethodName; while (*cptr && sptr < zptr) *sptr++ = *cptr++; if (sptr < zptr) *sptr++ = ' '; if (!(cptr = rqptr->rqHeader.RequestUriPtr)) cptr = ""; while (*cptr && sptr < zptr) *sptr++ = *cptr++; } if (sptr >= zptr) hlptr->Truncated = true; else hlptr->Truncated = false; *sptr = '\0'; } /*****************************************************************************/ /* Return a string describing the request current state. */ char* RequestState (int state) { /*********/ /* begin */ /*********/ if (WATCH_MODULE (WATCH_MOD_REQUEST)) { static int click; if (!click) { click = !click; WatchThis (WATCHALL, WATCH_MOD_REQUEST, "RequestState() !UL !AZ", state, RequestState(state)); click = !click; } } switch (state) { case 0 : return ("zero"); case REQUEST_STATE_CONNECTED : return ("connected"); case REQUEST_STATE_PERSIST : return ("persist"); case REQUEST_STATE_HEADER : return ("header"); case REQUEST_STATE_PROCESSING : return ("processing"); case REQUEST_STATE_ENDING : return ("ending"); case REQUEST_STATE_ABORT : return ("abort"); case REQUEST_STATE_SHUTDOWN : return ("shutdown"); case REQUEST_STATE_NOMORE : return ("nomore"); default : return ("undefined"); } } /*****************************************************************************/ /* The request received has a significant header flaw. Advise the client as simply as possible and bail. Only for use by HTTP/1.n protocol requests. */ void RequestNBG (REQUEST_STRUCT *rqptr) { static char ResponseToNBG [] = "HTTP/1.1 400 Request header is significantly broken!\r\n\ Content-Type: text/plain\r\n\ Connection: close\r\n\ \r\n\ The request header is significantly broken!\n"; /*********/ /* begin */ /*********/ /* ensure the count of 400s ticks upwards */ rqptr->rqResponse.HttpStatus = 400; InstanceMutexLock (INSTANCE_MUTEX_HTTPD); RequestHttpStatusCode (rqptr); AccountingPtr->RequestErrorCount++; InstanceMutexUnLock (INSTANCE_MUTEX_HTTPD); NetUpdateProcessing (rqptr, -1); rqptr->RequestState = REQUEST_STATE_ABORT; NetIoWrite (rqptr->NetIoPtr, RequestEnd4, rqptr, ResponseToNBG, sizeof(ResponseToNBG)-1); if (NetRejectStatus400) NetRejectSetStatus (rqptr); } /*****************************************************************************/ /* Ad hoc mechanism to suppress request header dump. Where request headers are malformed a dump of the header is inserted into the process log as an informational (see call code points). In foreseable circumstamces (random or malicious) this might chew a lot of disk storage. */ void RequestNoticeDump ( char *DataPtr, int DataLength ) { #define BYTES_PER_LINE 32 int dlen, lcnt; /*********/ /* begin */ /*********/ if (WATCH_MODULE (WATCH_MOD_REQUEST)) WatchThis (WATCHALL, WATCH_MOD_REQUEST, "RequestNoticeDump()"); if (ConfigNoticeInvalid < 2) return; dlen = DataLength; while (dlen > BYTES_PER_LINE * ConfigNoticeInvalid) { if (dlen % BYTES_PER_LINE) dlen -= dlen % BYTES_PER_LINE; else dlen -= BYTES_PER_LINE; } WatchDataDump (DataPtr, -dlen); if (dlen < DataLength) { lcnt = DataLength / BYTES_PER_LINE; if (DataLength % BYTES_PER_LINE) lcnt++; lcnt -= ConfigNoticeInvalid; fprintf (stdout, "8< %d lines %d bytes 8<\n", lcnt, DataLength - dlen); fflush (stdout); } } /*****************************************************************************/ /* Return a report on current and request history, listed most to least recent. This function blocks while executing. */ void RequestReport (REQUEST_STRUCT *rqptr) { #define REPORT_CURRENT 1 #define REPORT_CONNECT 2 #define REPORT_HISTORY 3 #define REPORT_PLUS 4 #define REPORT_THROTTLE 5 /* the final column just adds a little white-space on the page far right */ static char BeginPageFao [] = "

\n\ \ \ \ \ \ \ \ \ \ \ \ \ \n"; static char CurrentRequestFao [] = "\ \ \ \ \ \ \ \ \ \ \ \ \ \n\ \ \ \ \n"; static char HistoryFao [] = "
Service / ClientTime / RequestHTTPRxTxGzip%Bytes/SecDurationThrottleConnectWATCH
!3ZL!AZ//!AZ!20%D!UL!&,@UQ!&,@UQ!UL!&L!AZ!AZ!&@!&@!&@
!&@!&@
\n\

\n\ \ \ \ \ \ \ \ \ \ \ \ \n"; static char HistoryRequestFao [] = "\ \ \ \ \ \ \ \ \ \ \ \ \n\ \ \ \ \n"; static char RequestEmptyFao [] = "\ \ \n"; static char ConnectHistoryButtonFao [] = "
Service / ClientTime / RequestHTTPRxTxGzip%Bytes/SecDurationStatusConnect
!3ZL!AZ//!AZ!20%D!UL!&,@UQ!&,@UQ!UL!&L!AZ!AZ!UL!UL
!AZ\ !AZ!&?[truncated!]\r\r
000empty
\n\

\n\ \n\ \n\
\n\
\n\ \n\
\n\
\n\
\n\ \n\
\n\
\n\
\n\ \n\
\n\
\n\
\n\ \n\
\n\
\n\
Connect (!UL)\n\
!AZ\n\
History\n\
Request+\n\
\n\ !AZ\ \n\ \n\ \n"; static char EndPageFao [] = "\n\

\n\ \n\
\n\
\n\ \n\
\n\
\n\
\n\ \n\
\n\
\n\
\n\ \n\
\n\
\n\
\n\ \n\
\n\
\n\ !AZ\ \n\ \n\ \n"; int status, DisplayCount, EntryCount, ThrottleCount, ReportType; unsigned long *vecptr; unsigned long FaoVector [48]; int64 Time64, Duration64; char *cptr; char ThrottleButton [256]; struct RequestHistoryStruct *rqhptr; HTTP2_STRUCT *h2ptr; LIST_ENTRY *leptr; REQUEST_STRUCT *rqeptr; /*********/ /* begin */ /*********/ if (WATCH_MODULE(WATCH_MOD_REQUEST)) WatchThis (WATCHALL, WATCH_MOD_REQUEST, "RequestReport()"); cptr = rqptr->rqHeader.PathInfoPtr; if (strsame (cptr, ADMIN_REPORT_REQUEST_CONNECT, -1)) ReportType = REPORT_CONNECT; else if (strsame (cptr, ADMIN_REPORT_REQUEST_CURRENT, -1)) ReportType = REPORT_CURRENT; else if (strsame (cptr, ADMIN_REPORT_REQUEST_HISTORY, -1)) ReportType = REPORT_HISTORY; else if (strsame (cptr, ADMIN_REPORT_REQUEST_PLUS, -1)) ReportType = REPORT_PLUS; else if (strsame (cptr, ADMIN_REPORT_REQUEST_THROTTLE, -1)) ReportType = REPORT_THROTTLE; else if (strsame (cptr, ADMIN_REPORT_REQUEST, -1)) ReportType = REPORT_CURRENT; else { ReportType = REPORT_CURRENT; ErrorNoticed (rqptr, SS$_BUGCHECK, ErrorSanityCheck, FI_LI); } if (ReportType == REPORT_CONNECT) AdminPageTitle (rqptr, "Request & Connection Report"); else if (ReportType == REPORT_HISTORY) AdminPageTitle (rqptr, "Request & History Report"); else if (ReportType == REPORT_THROTTLE) AdminPageTitle (rqptr, "Throttled Request Report"); else if (ReportType == REPORT_PLUS) AdminPageTitle (rqptr, "Request & Connection + History Report"); else AdminPageTitle (rqptr, "Request Report"); status = FaolToNet (rqptr, BeginPageFao, NULL); if (VMSnok (status)) ErrorNoticed (rqptr, status, "FaolToNet()", FI_LI); /********************/ /* current requests */ /********************/ DisplayCount = EntryCount = ThrottleCount = 0; sys$gettim (&Time64); /* process the request list from least to most recent */ for (leptr = RequestList.HeadPtr; leptr; leptr = leptr->NextPtr) { rqeptr = (REQUEST_STRUCT*)leptr; EntryCount++; if (rqeptr->rqPathSet.ThrottleFrom) ThrottleCount++; if (ReportType != REPORT_CONNECT && ReportType != REPORT_PLUS) if (!(rqeptr->rqHeader.PathInfoPtr && rqeptr->rqHeader.QueryStringPtr)) continue; if (ReportType == REPORT_THROTTLE) if (!rqeptr->rqPathSet.ThrottleFrom) continue; vecptr = FaoVector; *vecptr++ = ++DisplayCount; *vecptr++ = rqeptr->ServicePtr->RequestSchemeNamePtr; *vecptr++ = rqeptr->ServicePtr->ServerHostPort; *vecptr++ = &rqeptr->rqTime.BeginTime64; if (rqeptr->Http2Ptr) *vecptr++ = 2; else if (rqeptr->rqResponse.HttpVersion == HTTP_VERSION_1_1 || rqeptr->rqResponse.HttpVersion == HTTP_VERSION_1_0) *vecptr++ = 1; else *vecptr++ = 0; *vecptr++ = &rqeptr->NetIoPtr->BytesRawRx64; *vecptr++ = &rqeptr->NetIoPtr->BytesRawTx64; *vecptr++ = rqeptr->BytesTxGzipPercent; Duration64 = rqeptr->rqTime.BeginTime64 - Time64; *vecptr++ = BytesPerSecond (&rqeptr->NetIoPtr->BytesRawRx64, &rqeptr->NetIoPtr->BytesRawTx64, &Duration64); *vecptr++ = HttpdTimeoutType(rqeptr->rqTmr.TimeoutType); *vecptr++ = DurationString (rqeptr, &Duration64); if (rqeptr->rqPathSet.ThrottleFrom) { if (rqeptr->ThrottleListEntry.DataPtr) { *vecptr++ = "\ release
\ terminate\
"; *vecptr++ = ADMIN_CONTROL_THROTTLE_RELEASE; *vecptr++ = rqeptr->ConnectNumber; *vecptr++ = ADMIN_CONTROL_THROTTLE_TERMINATE; *vecptr++ = rqeptr->ConnectNumber; } else *vecptr++ = "processing"; } else *vecptr++ = ""; if (rqeptr->Http2Ptr) { *vecptr++ = "!UL (!UL)"; *vecptr++ = rqeptr->ConnectNumber; *vecptr++ = rqeptr->Http2Ptr->ConnectNumber; } else { *vecptr++ = "!UL"; *vecptr++ = rqeptr->ConnectNumber; } if (rqeptr->ConnectNumber == rqptr->ConnectNumber) *vecptr++ = "current"; else if (rqeptr == Watch.RequestPtr) *vecptr++ = "WATCHing"; else { *vecptr++ = "P\ +\ W"; *vecptr++ = ADMIN_REPORT_WATCH; *vecptr++ = rqeptr->ConnectNumber; *vecptr++ = ADMIN_REPORT_WATCH; *vecptr++ = rqeptr->ConnectNumber; *vecptr++ = rqeptr->ConnectNumber; *vecptr++ = ADMIN_REPORT_WATCH; *vecptr++ = rqeptr->ConnectNumber; } if (ReportType == REPORT_CONNECT || ReportType == REPORT_PLUS) { *vecptr++ = "!AZ,!UL"; *vecptr++ = rqeptr->ClientPtr->Lookup.HostName; *vecptr++ = rqeptr->ClientPtr->IpPort; } else *vecptr++ = UserAtClient(rqeptr); if (rqeptr->rqHeader.PathInfoPtr && rqeptr->rqHeader.QueryStringPtr) { *vecptr++ = "!AZ !AZ"; *vecptr++ = rqeptr->rqHeader.MethodName; *vecptr++ = rqeptr->rqHeader.RequestUriPtr; } else { if (rqeptr->rqNet.PersistentCount) { *vecptr++ = "[persistent]   !UL"; *vecptr++ = rqeptr->rqNet.PersistentCount; } else /* proactive connection (e.g. what Chrome tends to do) */ *vecptr++ = "[connected]"; } FaoCheck (sizeof(FaoVector), &FaoVector, vecptr, FI_LI); status = FaolToNet (rqptr, CurrentRequestFao, &FaoVector); if (VMSnok (status)) ErrorNoticed (rqptr, status, "FaolToNet()", FI_LI); } if (ReportType == REPORT_CONNECT || ReportType == REPORT_PLUS) { /******************************/ /* current HTTP/2 connections */ /******************************/ /* process the HTTP/2 connection list from least to most recent */ for (leptr = Http2List.HeadPtr; leptr; leptr = leptr->NextPtr) { h2ptr = (HTTP2_STRUCT*)leptr; EntryCount++; vecptr = FaoVector; *vecptr++ = ++DisplayCount; *vecptr++ = h2ptr->ServicePtr->RequestSchemeNamePtr; *vecptr++ = h2ptr->ServicePtr->ServerHostPort; *vecptr++ = &h2ptr->ConnectTime64; *vecptr++ = 2; *vecptr++ = &h2ptr->NetIoPtr->BytesRawRx64; *vecptr++ = &h2ptr->NetIoPtr->BytesRawTx64; *vecptr++ = 0; Duration64 = h2ptr->ConnectTime64 - Time64; *vecptr++ = BytesPerSecond (&h2ptr->NetIoPtr->BytesRawRx64, &h2ptr->NetIoPtr->BytesRawTx64, &Duration64); *vecptr++ = DurationString (rqptr, &Duration64); *vecptr++ = ""; *vecptr++ = ""; *vecptr++ = "!UL"; *vecptr++ = h2ptr->ConnectNumber; if (HTTP2_REQUEST(rqptr) && rqptr->Http2Ptr->ConnectNumber == h2ptr->ConnectNumber) *vecptr++ = "current"; else { *vecptr++ = "P\ +\ W"; *vecptr++ = ADMIN_REPORT_WATCH; *vecptr++ = h2ptr->ConnectNumber; *vecptr++ = ADMIN_REPORT_WATCH; *vecptr++ = h2ptr->ConnectNumber; *vecptr++ = h2ptr->ConnectNumber; *vecptr++ = ADMIN_REPORT_WATCH; *vecptr++ = h2ptr->ConnectNumber; } *vecptr++ = "!AZ,!UL"; *vecptr++ = h2ptr->ClientPtr->Lookup.HostName; *vecptr++ = h2ptr->ClientPtr->IpPort; *vecptr++ = "[HTTP/2]   !UL:!UL:!UL"; *vecptr++ = h2ptr->RequestCurrent; *vecptr++ = h2ptr->RequestPeak; *vecptr++ = h2ptr->RequestCount; FaoCheck (sizeof(FaoVector), &FaoVector, vecptr, FI_LI); status = FaolToNet (rqptr, CurrentRequestFao, &FaoVector); if (VMSnok (status)) ErrorNoticed (rqptr, status, "FaolToNet()", FI_LI); } } else { /* just add in the number of HTTP/2 connections */ for (leptr = Http2List.HeadPtr; leptr; leptr = leptr->NextPtr) EntryCount++; } if (ReportType != REPORT_HISTORY && ReportType != REPORT_PLUS) { /***************/ /* not history */ /***************/ if (ReportType == REPORT_CURRENT || ReportType == REPORT_PLUS) { if (ThrottleCount) sprintf (ThrottleButton, "Throttle (%d)", ADMIN_REPORT_REQUEST_THROTTLE, ThrottleCount); else strzcpy (ThrottleButton, "Throttle", sizeof(ThrottleButton)); status = FaoToNet (rqptr, ConnectHistoryButtonFao, ADMIN_CONTROL_NET_PURGE, ADMIN_CONTROL_NET_PURGE_HTTP1, ADMIN_CONTROL_NET_PURGE_HTTP2, ADMIN_CONTROL_NET_PURGE_ALL, ADMIN_REPORT_REQUEST_CONNECT, EntryCount - DisplayCount, ThrottleButton, ADMIN_REPORT_REQUEST_HISTORY, ADMIN_REPORT_REQUEST_PLUS, AdminRefresh(rqptr)); } else if (ReportType == REPORT_THROTTLE) { if (!DisplayCount) FaolToNet (rqptr, RequestEmptyFao, NULL); status = FaoToNet (rqptr, EndPageFao, ADMIN_CONTROL_NET_PURGE, ADMIN_CONTROL_NET_PURGE_HTTP1, ADMIN_CONTROL_NET_PURGE_HTTP2, ADMIN_CONTROL_NET_PURGE_ALL, AdminRefresh(rqptr)); } else status = FaoToNet (rqptr, EndPageFao, ADMIN_CONTROL_NET_PURGE, ADMIN_CONTROL_NET_PURGE_HTTP1, ADMIN_CONTROL_NET_PURGE_HTTP2, ADMIN_CONTROL_NET_PURGE_ALL, AdminRefresh(rqptr)); if (VMSnok (status)) ErrorNoticed (rqptr, status, "FaoToNet()", FI_LI); rqptr->rqResponse.PreExpired = PRE_EXPIRE_ADMIN; ResponseHeader200 (rqptr, "text/html", &rqptr->NetWriteBufferDsc); AdminEnd (rqptr); return; } /*******************/ /* request history */ /*******************/ vecptr = FaoVector; *vecptr++ = RequestHistoryMax; FaoCheck (sizeof(FaoVector), &FaoVector, vecptr, FI_LI); status = FaolToNet (rqptr, HistoryFao, &FaoVector); if (VMSnok (status)) ErrorNoticed (rqptr, status, "FaolToNet()", FI_LI); DisplayCount = 0; /* process the request list from least to most recent */ for (leptr = RequestHistoryList.HeadPtr; leptr; leptr = leptr->NextPtr) { rqhptr = (struct RequestHistoryStruct*)leptr; vecptr = FaoVector; *vecptr++ = ++DisplayCount; *vecptr++ = rqhptr->ServicePtr->RequestSchemeNamePtr; *vecptr++ = rqhptr->ServicePtr->ServerHostPort; *vecptr++ = &rqhptr->Time64; *vecptr++ = rqhptr->HttpMajorVersion; *vecptr++ = &rqhptr->BytesRawRx64; *vecptr++ = &rqhptr->BytesRawTx64; *vecptr++ = rqhptr->BytesTxGzipPercent; *vecptr++ = rqhptr->BytesPerSecond; *vecptr++ = HttpdTimeoutType(rqhptr->TimeoutType); *vecptr++ = DurationString (rqptr, &rqhptr->Duration64); *vecptr++ = rqhptr->ResponseStatusCode; *vecptr++ = rqhptr->ConnectNumber; *vecptr++ = rqhptr->ClientAndRequest; *vecptr++ = rqhptr->RequestPtr; *vecptr++ = rqhptr->Truncated; FaoCheck (sizeof(FaoVector), &FaoVector, vecptr, FI_LI); status = FaolToNet (rqptr, HistoryRequestFao, &FaoVector); if (VMSnok (status)) ErrorNoticed (rqptr, status, "FaolToNet()", FI_LI); } if (!RequestHistoryList.HeadPtr) { status = FaolToNet (rqptr, RequestEmptyFao, NULL); if (VMSnok (status)) ErrorNoticed (rqptr, status, "FaolToNet()", FI_LI); } status = FaoToNet (rqptr, EndPageFao, ADMIN_CONTROL_NET_PURGE, ADMIN_CONTROL_NET_PURGE_HTTP1, ADMIN_CONTROL_NET_PURGE_HTTP2, ADMIN_CONTROL_NET_PURGE_ALL, AdminRefresh(rqptr)); if (VMSnok (status)) ErrorNoticed (rqptr, status, "FaolToNet()", FI_LI); rqptr->rqResponse.PreExpired = PRE_EXPIRE_ADMIN; ResponseHeader200 (rqptr, "text/html", &rqptr->NetWriteBufferDsc); AdminEnd (rqptr); } /*****************************************************************************/ /* Dump the current and history request lists, for 'crash' analysis. Use vanilla output in case other routines are implicated. */ void RequestDump () { static $DESCRIPTOR (RequestFaoDsc, "C!3ZL !UL !AZ//!AZ !AZ HTTP/!UL !%D !AZ!AZ !@UQ(!UL) !@UQ(!UL) \ !AZ!AZ!AZ!AZ!AZ !AZ!AZ!AZ !3ZL\n\0"); static $DESCRIPTOR (HistoryFaoDsc, "H!3ZL !UL !AZ//!AZ !AZ HTTP/!UL !%D !AZ!AZ !@UQ !@UQ !AZ !AZ!AZ !3ZL\n\0"); int status, Count; unsigned long *vecptr; unsigned long FaoVector [32]; int64 Time64, Duration64; char Buffer [8192]; LIST_ENTRY *leptr; REQUEST_STRUCT *rqeptr; struct RequestHistoryStruct *rqhptr; $DESCRIPTOR (BufferDsc, Buffer); /*********/ /* begin */ /*********/ if (WATCH_MODULE(WATCH_MOD_REQUEST)) WatchThis (WATCHALL, WATCH_MOD_REQUEST, "RequestDump()"); sys$gettim (&Time64); Count = 0; fflush (stdout); for (leptr = RequestList.HeadPtr; leptr; leptr = leptr->NextPtr) { rqeptr = (REQUEST_STRUCT*)leptr; vecptr = FaoVector; *vecptr++ = ++Count; *vecptr++ = rqeptr->ConnectNumber; *vecptr++ = rqeptr->ServicePtr->RequestSchemeNamePtr; *vecptr++ = rqeptr->ServicePtr->ServerHostPort; *vecptr++ = RequestState (rqeptr->RequestState); if (rqeptr->Http2Ptr) *vecptr++ = 2; else if (rqeptr->rqResponse.HttpVersion == HTTP_VERSION_1_1 || rqeptr->rqResponse.HttpVersion == HTTP_VERSION_1_0) *vecptr++ = 1; else *vecptr++ = 0; *vecptr++ = &rqeptr->rqTime.BeginTime64; Duration64 = rqeptr->rqTime.BeginTime64 - Time64; *vecptr++ = HttpdTimeoutType(rqeptr->rqTmr.TimeoutType); *vecptr++ = DurationString (NULL, &Duration64); *vecptr++ = &rqeptr->NetIoPtr->BytesRawRx64; *vecptr++ = rqeptr->rqNet.ReadErrorCount; *vecptr++ = &rqeptr->NetIoPtr->BytesRawTx64; *vecptr++ = rqeptr->rqNet.WriteErrorCount; if (rqeptr->RemoteUser[0]) { if (rqeptr->rqAuth.RealmPtr) { *vecptr++ = rqeptr->RemoteUser; *vecptr++ = "."; *vecptr++ = rqeptr->rqAuth.RealmPtr; } else { *vecptr++ = ""; *vecptr++ = ""; *vecptr++ = rqeptr->RemoteUser; } *vecptr++ = "@"; } else { *vecptr++ = ""; *vecptr++ = ""; *vecptr++ = ""; *vecptr++ = ""; } *vecptr++ = rqeptr->ClientPtr->Lookup.HostName; if (rqeptr->rqHeader.PathInfoPtr && rqeptr->rqHeader.QueryStringPtr) { *vecptr++ = rqeptr->rqHeader.MethodName; *vecptr++ = " "; *vecptr++ = rqeptr->rqHeader.RequestUriPtr; } else if (rqeptr->rqNet.PersistentCount) { static char PersistString [32]; sprintf (PersistString, "[persistent:%u]", rqeptr->rqNet.PersistentCount); *vecptr++ = ""; *vecptr++ = ""; *vecptr++ = PersistString; } else { *vecptr++ = ""; *vecptr++ = ""; *vecptr++ = "[null]"; } *vecptr++ = rqeptr->rqResponse.HttpStatus; status = sys$faol (&RequestFaoDsc, 0, &BufferDsc, &FaoVector); if (VMSnok (status)) FaoErrorNoticed (status, "sys$fao()", FI_LI); /* just in case of an overflow */ Buffer[sizeof(Buffer)-1] = '\0'; fputs (Buffer, stdout); } Count = 0; for (leptr = RequestHistoryList.HeadPtr; leptr; leptr = leptr->NextPtr) { rqhptr = (struct RequestHistoryStruct*)leptr; vecptr = FaoVector; *vecptr++ = ++Count; *vecptr++ = rqhptr->ConnectNumber; *vecptr++ = rqhptr->ServicePtr->RequestSchemeNamePtr; *vecptr++ = rqhptr->ServicePtr->ServerHostPort; *vecptr++ = RequestState (rqhptr->RequestState); *vecptr++ = rqhptr->HttpMajorVersion; *vecptr++ = &rqhptr->Time64; *vecptr++ = HttpdTimeoutType(rqhptr->TimeoutType); *vecptr++ = DurationString (NULL, &rqhptr->Duration64); *vecptr++ = &rqhptr->BytesRawRx64; *vecptr++ = &rqhptr->BytesRawTx64; *vecptr++ = rqhptr->ClientAndRequest; *vecptr++ = rqhptr->RequestPtr; *vecptr++ = rqhptr->Truncated ? "[truncated]" : ""; *vecptr++ = rqhptr->ResponseStatusCode; status = sys$faol (&HistoryFaoDsc, 0, &BufferDsc, &FaoVector); if (VMSnok (status)) FaoErrorNoticed (status, "sys$fao()", FI_LI); /* just in case of an overflow */ Buffer[sizeof(Buffer)-1] = '\0'; fputs (Buffer, stdout); } fflush (stdout); } /*****************************************************************************/