/*****************************************************************************/ /* Proxy.c ************* ** CAUTION ** ************* THIS MODULE IS TASK-ORIENTED, NOT REQUEST-ORIENTED. That is, most of the functions take a pointer to proxy task rather than a pointer to request as do other modules. The reason is simple. Many of these functions are designed for use independently of a request. Implements a basic HTTP proxy service in conjunction with ProxyCache.c module. This module provides all the request parsing, networking, response parsing and writing to the client. The ProxyCache.c module provides the caching in files of candidate requests. All errors related to connecting to, requesting from, and processing the response from the proxied server are reported as 502s (gateway failures). Any errors such as buffer overflows, etc., are reported as 500 (internal problems). NON-PROXY REDIRECTION --------------------- WASD provides for redirection from non-proxy to proxy request, allowing a type of gatewaying between http: and https:, ftp: and ftp:, https: and http:, etc. Warning: THIS REEKS OF BEING KLUDGY. Although the redirection is implemented in RequestRedirect() in REQUEST.C module RequestRedirect() function it is described in greater detail here because it is wholly dependent on proxying functionality. Note that there are many combinations of mappings and services that could be used to provide gatewaying between protocols. What follows here are the special case and some suggestions. There are many other ways to organise these capabilities. Note that the proxy services must support the type of proxy request being demanded of it (i.e. http:, https: and/or ftp:). 1. Simple in-line URL ("one-shot" proxy) ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ This is when a request to a proxy service contains, *not* a proxy format GET http://the.host.name:port/path HTTP/1.0 but a standard format request, with the URL containing another "full" URL, GET /http://the.host.name:port/path HTTP/1.0 because the 'user' entered http://the.proxy.service:8080/http://the.host.name:port/path into the browser location field. This known with WASD as a "one-shot" proxy because the server will retrieve it but the anything other than a self-relative link within any HTML document will not be correct. The configuration requirements for this type of proxy are fairly simple. [[the.proxy.service:port]] pass /http://* http://* pass /https://* https://* pass /ftp://* ftp://* 2. Wildcard DNS (CNAME) record ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ This relies on being able to manipulate host records in the DNS or local name resolution database. If a "*.the.proxy.host" DNS (CNAME) record is resolved it allows any host name ending in ".the.proxy.host" to resolve to the corresponding IP address. Similarly (at least the Compaq TCP/IP Services) local host database allows an alias like "another.host.name.proxy.host.name" for the proxy host name. Both of these would allow a browser to access "another.host.name.proxy.host.name" with it resolved to the proxy service. The request "Host:" field would contain "another.host.name.proxy.host.name". Using this approach a complete proxy may be implemented, where returned HTML documents contain links that are always correct with reference to the host used to request them. For a wildcard DNS (CNAME) record the browser user may enter any host name appended by the proxy service host name and port and have the request proxied to the host name. Entering the following URL into the browser location field, http://the.host.name.the.proxy.service:8080/path would result in a standard HTTP proxy request for "/path" being made to "the.host.name:80". The URL, https://the.host.name.the.proxy.service:8080/path in an SSL proxy request. Note that normally the well-known port would be used to connect to (80 for http: and 443 for https:). If the final, period-separated component of the wildcard host name is all digits it is interpreted as a specific port to connect to. The example http://the.host.name.8001.the.proxy.service:8080/path would connect to "the.host.name:8001", and https://the.host.name.8443.the.proxy.service:8080/path to "the.host.name:8443". A further special case may be developed where part of that host name may be used to indicate the proxy gateway desired. The REDIRECT mapping rule allows for the following configuration being specially processed. The following example shows a complex but powerful arrangement where the host name used provides an indication of the type of gatewaying required to be performed. [[the.proxy.service:port]] if (host:*.ftp.the.proxy.service:*) redirect * /ftp://ftp.the.proxy.service/*? elif (host:*.https.the.proxy.service:*) redirect * /https://https.the.proxy.service/*? elif (host:*.http.the.proxy.service:*) redirect * /http://http.the.proxy.service/*? elif (host:*.the.proxy.service:*) # fallback to HTTP if no other specified redirect * /http://the.proxy.service/*? else pass https://* https://* pass http://* http://* pass ftp://* ftp://* endif In this case a user may specify to connect via HTTP but gateway to an HTTP-over-SSL service using a request such as http://the.host.name.https.the.proxy.service:8080/path where a proxy request will effectively be made to https://the.host.name:443/path All digit components may still be used to indicate specific ports to be connected to, as in this example http://the.host.name.7443.https.the.proxy.service:8080/path where the proxy request would effectively be made to https://the.host.name:7443/path Notice that the example configuration allows for gatewayed HTTP, HTTP-over-SSL and FTP based on the original host name used for the request and the consequent contents of the "Host:" request field (which is adjusted to reflect the changed host contents during the gatewaying). Note also that is a script author is going to use the mechansim for 'protocol conversion' then the script needs to provide an appropriate "Host:" request header field. CLIENT TO ORIGIN SERVER AFFINITY -------------------------------- Courtesy Jean-Pierre Petit (jpp@esme.fr) High performance/highly available proxy server configurations require more than one instance configured and running. Whether this is done by running multiple instances on the same host or one instance on multiple hosts, it leads to situations where successive requests will be processed by different instances. As those instances don't share a common name to IP address cache, they will eventually use different IP addresses when trying to connect to an origin server running on multiple hosts. This may results in the following, user visible, issues: * multiple requests for authentication (one from each origin host) * loss of icons, images, javascripts, CSS because requests for these files, although they return a 401 status, will not trigger a browser authentication dialog * loss of context and performance issues where scripts/environments need to be started on a new host (php, python, webware,...) For these reasons, the proxy server will make every effort to relay successive requests from a given client to the same origin host as long as this one is available (built-in failover capability will ultimately trigger the choice of a new host). This is known as client to origin affinity or proxy affinity capability. After the first successful connection to an origin host, the proxy server will send a cookie indicating the IP address used to the client browser. Upon subsequent requests, this cookie will be used to select the same host. The cookie is named WasdProxyAffinity_the.origin.server and his value is simply the IP address. This behaviour can be WATCHed by checking the "Proxy Processing" box. WEBSOCKET PROXY --------------- This is a second guess because the RFC specifies that CONNECT should be used to establish a connection to the WebSocket host. However the WASD proxy facility does recognise a WebSocket upgrade request in a standard proxied GET and will recognise an upgrade response from the proxied server and set up a WASD tunnel between client and proxied server. VERSION HISTORY --------------- 14-AUG-2020 MGD bugfix; ProxyEnd() fix NetIoEnd() fix 20-JUL-2020 MGD bugfix; ProxyEnd() free ioptr using NetIoEnd() 19-JAN-2020 MGD more proxy persistent connection (per JPP) 23-APR-2018 MGD ProxyRequestRebuild() proxy-authorization opaque: 12-DEC-2017 MGD ProxyRequestRebuild() refine rebuild memory allocation 18-MAR-2017 MGD ProxyRequestRebuild() replace the "Host:" header 07-MAR-2017 MGD ProxyRequestRebuild() implement proxy=header=[=] 15-APR-2016 JPP bugfix; ProxyRequestBegin() task connection persistence 10-JAN-2016 MGD rework request processing due to dictionary 11-AUG-2015 MGD restructure of network I/O abstractions 19-JAN-2015 JPP ProxyResponseRebuild() and ProxyRequestRebuild() provide timeout=n parameter with Keep-Alive: header field (some origin servers hang when no parameters supplied) 06-NOV-2014 MGD ProxyInit() unescaped "!" in FaoToStdout() (thanks Tony) 09-JUN-2014 MGD accounting for network blocks 18-NOV-2013 JMB bugfix; no $QIO buffer should exceed 65535! 20-APR-2013 MGD bugfix; ProxyResponseRebuild() call ProxyRebuildLocation() can return a pointer to the original location! 18-MAR-2013 MGD ProxyResponseRebuild() additional header length bumped from an ambit 256 to an ambit 1024 (Uni Malaga :-) 08-JUN-2012 JPP set ProxyReadBufferSize to 64k 05-FEB-2012 MGD proxy WebSocket upgrade requests as raw tunnels 31-AUG-2010 MGD ProxyRequestParse() allow for IPv6 addressed hosts e.g. http://[fe80::200:f8ff:fe75:c062%eth0]:6680/ 24-AUG-2010 MGD ProxyResponseRebuild() generate "Proxy-Authorization:" header for [ServiceProxyChainCred] and proxy=chain=cred= 20-JAN-2010 JPP ProxyResponseRebuild() "accept" && !"accept-encoding" 20-SEP-2008 MGD bugfix; ProxyResponseRebuild() proxy->client compression chunk only for HTTP/1.1 responses and connection persistence header fields reflect non-chunked GZIP stream 23-OCT-2007 MGD ProxyRequestRebuild() allow "Proxy-Authorization:" header only if configured for REMOTE proxy authentication 02-JUN-2007 MGD WebDAV requirements 04-MAY-2007 MGD ProxyRequestBegin() restrict HTTP methods for FTP scheme ProxyResponseRebuild() make request HTTP version a consideration before chunking proxy->client (with JPP) 10-JAN-2007 MGD bugfix; ProxyRequestRebuild() proxy verify "Authorization:" request header field carriage-control 21-APR-2006 JPP add support for sending proxy generated cookies in ProxyResponseRebuild() (to support proxy affinity) 12-APR-2006 MGD ProxyEnd() minor rework 10-SEP-2005 JPP implement proxy=reverse=[no]auth 14-JUL-2005 MGD detect and absorb a "Public:" response header field (present in RFC2068 but obsoleted in RFC2116) 03-MAY-2005 MGD "Cache-Control: max-age=0" applied only to proxy cache 16-MAR-2005 JPP allow client-side GZIPing of proxied responses 20-JAN-2005 MGD bugfix; ProxyReadResponseAst() if required, chunking needs to be performed after header as well as body processing 05-JAN-2005 MGD ProxyRequestBegin() remove explicit disable of POST & PUT connection persistence 18-NOV-2004 MGD ensure (persistent) HEAD responses do not wait for a body, allow for a server sending more body than in content-length 14-NOV-2004 MGD remove suppression of "Accept-Encoding:" request header, detect "Content-Encoding:" responses (to suppress caching) 26-OCT-2004 MGD bugfix; ProxyEnd() check for outstanding client SSL I/O 30-AUG-2004 MGD increased HTTP/1.1 compliance, now supports independent client->proxy and proxy-origin connection persistence, significant revision to cache file management 10-AUG-2004 MGD some functionality moved to ProxyTunnel.c 30-APR-2004 MGD significant changes to eliminate RMS from cache file read by using ACP/QIOs (saves a few cycles and a little latency, continue to use the RMS abstraction for handling the greater complexity of file creation and population). 10-APR-2004 MGD significant modifications to support IPv6, use generic name-to-address resolution function 20-NOV-2003 MGD reverse proxy 302 "Location:" rewrite, reverse proxy authorization verification 28-OCT-2003 MGD bugfix; chained proxy CONNECT processing bugfix; keep track of outstanding body reads 19-SEP-2003 MGD bugfix; ProxyRequestRebuild() rebuild buffer space 21-AUG-2003 MGD "Range:" header field 25-MAY-2003 MGD improve efficiency of ProxyRequestRebuild(), un-recognised/known request fields, remove explicit setting 'rqBody.DataStatus = SS$_ENDOFFILE' 02-APR-2003 MGD "X-Forwarded-For:" header field, modify to "Forwarded: by" processing 15-MAY-2002 MGD proxy gateway statistics 30-APR-2002 MGD "Cache-Control:" field for Mozilla compatibility 17-MAR-2002 MGD ParseRequest() allow for incomplete FTP URLs 02-FEB-2002 MGD rework for request body processing changes 22-JAN-2002 MGD ProxyNetHostConnectAst() invalidate host cache entry if connection fails (jpp@esme.fr), ProxyRequestParse() ignore leading '/' allowing "one-shot" proxying using a standard request 04-JAN-2002 MGD proxy HTTP-to-SSL gateway 14-OCT-2001 MGD outgoing sockets may be bound to a specific IP address 04-AUG-2001 MGD support module WATCHing 27-APR-2001 MGD requirement for ProxyResolveHostCacheTimer() has been eliminated with HttpTick() and 'HttpdTickSecond' 03-SEP-2000 MGD bugfix; ProxyResolveHostLookup() can be called multiple during host name resolution - only allocate channel once!! bugfix; ProxyResolveHostLookup() hash collision list 04-JUL-2000 MGD bugfix; ProxyEnd() for CONNECT (proxy SSL) 11-JUN-2000 MGD refine numeric host detection, ProxyRequestRebuild() now dynamically allocates a rebuild buffer based on 'NetReadBufferSize' 01-JUN-2000 MGD bugfix; use 'rqHeader.RequestUriPtr' as '->RequestUriPtr' (non-URL-decoded path plus query string) 29-APR-2000 MGD proxy authorization 04-MAR-2000 MGD use FaolToNet(), et.al. 04-DEC-1999 MGD default host name cache purge now 24 hours 11-NOV-1999 MGD provide "ETag:" propagation 25-OCT-1999 MGD remove NETLIB support 10-OCT-1999 MGD just sys$dassn() the socket 26-JUN-1999 MGD bugfix; ProxyEnd() call in ProxyReadResponseAst(), bugfix; header pointer in ProxyHttp09ResponseHeader(), always initialize cache parameters (even if not enabled), revised request run down ProxyShutdownSocket() 19-AUG-1998 MGD initial development (recommenced DEC 1998) */ /*****************************************************************************/ #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-related header files */ #include "wasd.h" #define WASD_MODULE "PROXY" /******************/ /* global storage */ /******************/ int ProxyReadBufferSize = 65535; BOOL ProxyServingEnabled, ProxyUnknownRequestFields; int ProxyConnectPersistMax, ProxyConnectPersistSeconds, ProxyConnectTimeoutSeconds, ProxyForwardedBy, ProxyHostLookupRetryCount, ProxyXForwardedFor; PROXY_ACCOUNTING_STRUCT *ProxyAccountingPtr; /********************/ /* external storage */ /********************/ extern int ToLowerCase[], ToUpperCase[]; extern unsigned long HttpdTime64[]; extern BOOL GzipResponse; extern char ErrorSanityCheck[], ServerHostPort[], SoftwareID[]; extern ACCOUNTING_STRUCT *AccountingPtr; extern CONFIG_STRUCT Config; extern IPADDRESS TcpIpEmptyAddress; extern MSG_STRUCT Msgs; extern LIST_HEAD ServiceList; extern WATCH_STRUCT Watch; /****************************************************************************/ /* */ ProxyInit () { BOOL CacheEnabled; char *cptr; SERVICE_STRUCT *svptr, *tsvptr; /*********/ /* begin */ /*********/ if (WATCH_MODULE(WATCH_MOD_PROXY)) WatchThis (WATCHALL, WATCH_MOD_PROXY, "ProxyInit()"); ProxyForwardedBy = ProxyXForwardedFor = 0; ProxyServingEnabled = CacheEnabled = false; /* copy the configuration parameters, ensuring they're reasonable */ ProxyHostLookupRetryCount = Config.cfProxy.HostLookupRetryCount; if (!ProxyHostLookupRetryCount) ProxyHostLookupRetryCount = Config.cfMisc.DnsLookupRetryCount; if (!ProxyHostLookupRetryCount) ProxyHostLookupRetryCount = TCPIP_LOOKUP_RETRY_COUNT; if (ProxyHostLookupRetryCount < 0 || ProxyHostLookupRetryCount > 100) ProxyHostLookupRetryCount = 0; /* just some information about the proxy services */ LIST_ITERATE (svptr, &ServiceList) { if (!(svptr->ProxyService || svptr->ConnectService)) continue; if (svptr->ProxyChainHostName[0]) FaoToStdout ("%HTTPD-I-PROXY, !AZ chains to !AZ\n", svptr->ServerHostPort, svptr->ProxyChainHostPort); if (svptr->ProxyService) { FaoToStdout ("%HTTPD-I-PROXY, HTTP service !AZ", svptr->ServerHostPort); if (svptr->ProxyAuthRequired) fprintf (stdout, " (auth)"); if (svptr->ProxyFileCacheEnabled) { CacheEnabled = true; fprintf (stdout, " (cached)"); } if (svptr->ConnectService) fprintf (stdout, " (connect)"); fputs ("\n", stdout); } else if (svptr->ConnectService) { FaoToStdout ("%HTTPD-I-PROXY, connect service !AZ", svptr->ServerHostPort); if (svptr->ProxyAuthRequired) fprintf (stdout, " (auth)"); fputs ("\n", stdout); } } if (!Config.cfProxy.CacheEnabled) CacheEnabled = false; if (ProxyServiceCount()) { /* initialize the proxy file cache (at least parameters) */ ProxyCacheInit (CacheEnabled); if (Config.cfProxy.ServingEnabled) { ProxyServingEnabled = true; FaoToStdout ("%HTTPD-I-PROXY, processing enabled\n"); } else FaoToStdout ("%HTTPD-W-PROXY, \ disabled in configuration, services not enabled!!\n"); } ProxyForwardedBy = Config.cfProxy.ForwardedBy; ProxyXForwardedFor = Config.cfProxy.XForwardedFor; ProxyUnknownRequestFields = Config.cfProxy.UnknownRequestFields; ProxyConnectPersistMax = Config.cfProxy.ConnectPersistMax; ProxyConnectPersistSeconds = Config.cfProxy.ConnectPersistSeconds; ProxyConnectTimeoutSeconds = Config.cfProxy.ConnectTimeoutSeconds; InstanceMutexLock (INSTANCE_MUTEX_HTTPD); ProxyAccountingPtr->ServingEnabled = ProxyServingEnabled; ProxyAccountingPtr->TunnelCurrent = 0; InstanceMutexUnLock (INSTANCE_MUTEX_HTTPD); ProxyNetInit (); ProxyVerifyInit (); } /*****************************************************************************/ /* Return the number of proxy-capable services in the global list. */ int ProxyServiceCount () { int count; SERVICE_STRUCT *svptr; /*********/ /* begin */ /*********/ count = 0; LIST_ITERATE (svptr, &ServiceList) if (svptr->ProxyService || svptr->ConnectService) count++; return (count); } /****************************************************************************/ /* ************ *** NOTE *** This function takes a pointer to a request!!! ************ */ ProxyRequestBegin (REQUEST_STRUCT *rqptr) { int status; char *cptr, *sptr, *zptr; PROXY_TASK *tkptr; DICT_ENTRY_STRUCT *denptr; /*********/ /* begin */ /*********/ if (WATCHMOD (rqptr, WATCH_MOD_PROXY)) WatchThis (WATCHITM(rqptr), WATCH_MOD_PROXY, "ProxyRequestBegin()"); if (!ProxyServingEnabled) { rqptr->rqResponse.HttpStatus = 503; ErrorGeneral (rqptr, MsgFor(rqptr,MSG_PROXY_DISABLED), FI_LI); RequestEnd (rqptr); return; } if (HTTP2_REQUEST(rqptr)) { rqptr->rqResponse.HttpStatus = 400; ErrorGeneral (rqptr, MsgFor(rqptr,MSG_PROXY_NONE_CONFIGURED), FI_LI); RequestEnd (rqptr); return; } if (rqptr->rqHeader.Method == HTTP_METHOD_CONNECT || rqptr->rqHeader.Method == HTTP_METHOD_SHARE_SSH) { if (!rqptr->ServicePtr->ProxyTunnel) { rqptr->rqResponse.HttpStatus = 403; ErrorGeneral (rqptr, MsgFor(rqptr,MSG_PROXY_NOT_CONNECT), FI_LI); RequestEnd (rqptr); return; } } else { if (!rqptr->ServicePtr->ProxyService) { rqptr->rqResponse.HttpStatus = 403; ErrorGeneral (rqptr, MsgFor(rqptr,MSG_PROXY_NOT_SERVICE), FI_LI); RequestEnd (rqptr); return; } } if (!rqptr->AccountingDone++) InstanceGblSecIncrLong (&AccountingPtr->DoProxyCount); if (denptr = DictLookup (rqptr->rqDictPtr, DICT_TYPE_REQUEST, "proxy-connection", 16)) { cptr = DICT_GET_VALUE(denptr); while (*cptr) { while (*cptr && ISLWS(*cptr)) cptr++; if (strsame (cptr, "close", 5)) { rqptr->rqHeader.ProxyConnectionClose = true; cptr += 5; } else if (strsame (cptr, "keep-alive", 10)) { rqptr->rqHeader.ProxyConnectionKeepAlive = true; cptr += 10; } while (*cptr && !ISLWS(*cptr) && *cptr != ',') cptr++; while (*cptr && (ISLWS(*cptr) || *cptr == ',')) cptr++; } if (WATCHMOD (rqptr, WATCH_MOD_REQUEST)) WatchDataFormatted ("close:!&B keep-alive:!&B\n", rqptr->rqHeader.ProxyConnectionClose, rqptr->rqHeader.ProxyConnectionKeepAlive); } /* set up the task structure (only ever one per request!) */ rqptr->ProxyTaskPtr = tkptr = VmGetHeap (rqptr, sizeof(PROXY_TASK)); /* populate fundamental task fields */ tkptr->RequestPtr = rqptr; tkptr->ServicePtr = rqptr->ServicePtr; tkptr->WatchItem = rqptr->WatchItem; tkptr->NetIoPtr = NetIoProxyBegin(); tkptr->NetIoPtr->WatchItem = tkptr->WatchItem; NetIoQioMaxSeg (tkptr->NetIoPtr); /* copy the request method number and HTTP version number */ tkptr->HttpMethod = rqptr->rqHeader.Method; tkptr->RequestHttpVersion = rqptr->rqHeader.HttpVersion; tkptr->PersistentRequest = true; if (rqptr->rqHeader.Method != HTTP_METHOD_GET && rqptr->rqHeader.Method != HTTP_METHOD_HEAD && rqptr->rqHeader.Method != HTTP_METHOD_POST && rqptr->rqHeader.Method != HTTP_METHOD_PUT) rqptr->PersistentRequest = tkptr->PersistentRequest = false; else if (rqptr->PersistentRequest || (rqptr->rqHeader.ProxyConnectionKeepAlive && !rqptr->rqHeader.ProxyConnectionClose)) tkptr->PersistentRequest = true; else tkptr->PersistentRequest = false; /* if there is a specific proxy IP address to bind to */ if (IPADDRESS_IS_SET (&rqptr->rqPathSet.ProxyBindIpAddress)) IPADDRESS_COPY (&tkptr->BindIpAddress, &rqptr->rqPathSet.ProxyBindIpAddress) if (IPADDRESS_IS_SET (&rqptr->rqPathSet.ProxyChainIpAddress)) { /* a chain has been set by mapping rule, overrides any service */ IPADDRESS_COPY (&tkptr->ChainIpAddress, &rqptr->rqPathSet.ProxyChainIpAddress) tkptr->ChainIpPort = rqptr->rqPathSet.ProxyChainPort; tkptr->ChainHostPortPtr = tkptr->ChainHostPort; /* a local copy is needed because of the volatility of request heap */ zptr = (sptr = tkptr->ChainHostPort) + sizeof(tkptr->ChainHostPort)-1; for (cptr = rqptr->rqPathSet.ProxyChainHostPortPtr; *cptr && sptr < zptr; *sptr++ = *cptr++); *sptr = '\0'; } else if (IPADDRESS_IS_SET (&tkptr->ServicePtr->ProxyChainIpAddress)) { /* chaining to the next proxy server in a cascade */ IPADDRESS_COPY (&tkptr->ChainIpAddress, &tkptr->ServicePtr->ProxyChainIpAddress) tkptr->ChainIpPort = tkptr->ServicePtr->ProxyChainPort; tkptr->ChainHostPortPtr = tkptr->ServicePtr->ProxyChainHostPort; } /* allocate a buffer used for creating the MD5 digest, network I/O, etc. */ if (!tkptr->ResponseBufferPtr) { tkptr->ResponseBufferPtr = tkptr->ResponseBufferCurrentPtr = VmGetHeap (rqptr, ProxyReadBufferSize); tkptr->ResponseBufferSize = tkptr->ResponseBufferRemaining = ProxyReadBufferSize; } /* set the lookup retry count to default if necessary */ if (!tkptr->ProxyLookupRetryCount) tkptr->ProxyLookupRetryCount = ProxyHostLookupRetryCount; /* a little accounting */ switch (tkptr->HttpMethod) { case HTTP_METHOD_CONNECT : InstanceGblSecIncrLong (&ProxyAccountingPtr->MethodConnectCount); break; case HTTP_METHOD_DELETE : InstanceGblSecIncrLong (&ProxyAccountingPtr->MethodDeleteCount); break; case HTTP_METHOD_EXTENSION : InstanceGblSecIncrLong (&ProxyAccountingPtr->MethodExtensionCount); break; case HTTP_METHOD_GET : InstanceGblSecIncrLong (&ProxyAccountingPtr->MethodGetCount); break; case HTTP_METHOD_HEAD : InstanceGblSecIncrLong (&ProxyAccountingPtr->MethodHeadCount); break; case HTTP_METHOD_OPTIONS : InstanceGblSecIncrLong (&ProxyAccountingPtr->MethodOptionsCount); break; case HTTP_METHOD_POST : InstanceGblSecIncrLong (&ProxyAccountingPtr->MethodPostCount); break; case HTTP_METHOD_PUT : InstanceGblSecIncrLong (&ProxyAccountingPtr->MethodPutCount); break; case HTTP_METHOD_TRACE : InstanceGblSecIncrLong (&ProxyAccountingPtr->MethodTraceCount); break; case HTTP_METHOD_WEBDAV_COPY: InstanceGblSecIncrLong (&ProxyAccountingPtr->MethodWebDavCopyCount); break; case HTTP_METHOD_WEBDAV_LOCK : InstanceGblSecIncrLong (&ProxyAccountingPtr->MethodWebDavLockCount); break; case HTTP_METHOD_WEBDAV_MKCOL : InstanceGblSecIncrLong (&ProxyAccountingPtr->MethodWebDavMkColCount); break; case HTTP_METHOD_WEBDAV_MOVE : InstanceGblSecIncrLong (&ProxyAccountingPtr->MethodWebDavMoveCount); break; case HTTP_METHOD_WEBDAV_PROPFIND : InstanceGblSecIncrLong (&ProxyAccountingPtr->MethodWebDavPropFindCount); break; case HTTP_METHOD_WEBDAV_PROPPATCH : InstanceGblSecIncrLong (&ProxyAccountingPtr->MethodWebDavPropPatchCount); break; case HTTP_METHOD_WEBDAV_UNLOCK : InstanceGblSecIncrLong (&ProxyAccountingPtr->MethodWebDavUnLockCount); break; case HTTP_METHOD_SHARE_SSH : InstanceGblSecIncrLong (&ProxyAccountingPtr->MethodSshCount); break; default : ErrorExitVmsStatus (SS$_BUGCHECK, ErrorSanityCheck, FI_LI); } if (rqptr->rqHeader.Method == HTTP_METHOD_CONNECT || rqptr->rqHeader.Method == HTTP_METHOD_SHARE_SSH) { /**************/ /* tunnelling */ /**************/ /* these definitely do not persist! */ rqptr->PersistentRequest = false; if (tkptr->ProxyTunnel == PROXY_TUNNEL_HTTPS) { if (!tkptr->ServicePtr->SSLclientEnabled) { if (WATCHING (rqptr, WATCH_PROXY)) WatchThis (WATCHITM(rqptr), WATCH_PROXY, "CLIENT SSL not configured"); rqptr->rqResponse.HttpStatus = 503; ErrorGeneral (rqptr, MsgFor(rqptr,MSG_PROXY_GATEWAY), FI_LI); ProxyEnd (tkptr); return; } } if (VMSnok (status = ProxyTunnelRequestParse (rqptr))) { RequestEnd (rqptr); return; } ProxyNetResolveHost (tkptr); return; } /***************************/ /* GET, POST, etc. methods */ /***************************/ /* get the method, host (and port), path and query string */ if (VMSnok (status = ProxyRequestParse (rqptr))) { RequestEnd (rqptr); return; } if (WATCHING (tkptr, WATCH_PROXY)) WatchThis (WATCHITM(rqptr), WATCH_PROXY, "!AZ !AZ!AZ", tkptr->RequestHttpMethodNamePtr, tkptr->RequestHostPort, tkptr->RequestUriPtr); if (tkptr->RequestScheme == PROXY_SCHEME_FTP) { /*************/ /* proxy FTP */ /*************/ /* ensure only meaningful methods are used against an FTP service */ switch (tkptr->HttpMethod) { case HTTP_METHOD_GET : case HTTP_METHOD_HEAD : case HTTP_METHOD_POST : case HTTP_METHOD_PUT : break; default: rqptr->rqResponse.HttpStatus = 405; ErrorGeneral (rqptr, MsgFor(rqptr,MSG_HTTP_405), FI_LI); RequestEnd (rqptr); return; } /* get (any requested) authentication before we do too much else */ if (*(cptr = rqptr->rqHeader.QueryStringPtr)) { if (ProxyFtpInQueryString (cptr, "login")) { if (rqptr->rqHeader.AuthorizationPtr) { /* our authorization source is "local" not proxy */ rqptr->rqAuth.RequestAuthorizationPtr = rqptr->rqHeader.AuthorizationPtr; BasicAuthorization (rqptr); } if (!rqptr->RemoteUser[0] || !rqptr->RemoteUserPassword[0]) { rqptr->rqResponse.HttpStatus = 401; rqptr->rqAuth.RealmDescrPtr = MsgFor(rqptr,MSG_PROXY_FTP_SERVER); ErrorGeneral (rqptr, MsgFor(rqptr,MSG_PROXY_FTP_USERNAME_PWD), FI_LI); RequestEnd (rqptr); return; } } if (ProxyFtpInQueryString (cptr, "upload")) { ProxyFtpStoreForm (rqptr); RequestEnd (rqptr); return; } } } else if (tkptr->RequestScheme == PROXY_SCHEME_HTTP) { /**************/ /* proxy HTTP */ /**************/ /* if caching is enabled for this service then see if it can be used */ if (rqptr->ServicePtr->ProxyFileCacheEnabled) if (VMSok (ProxyCacheReadBegin (tkptr))) return; } ProxyNetResolveHost (tkptr); } /*****************************************************************************/ /* Only called when a cache check has failed to be able to supply the request. Merely call ProxyNetResolveHost() to begin an attempt to supply the request from the remote server. */ ProxyReadCacheFailed (PROXY_TASK *tkptr) { /*********/ /* begin */ /*********/ if (WATCH_MOD && (!tkptr->RequestPtr || tkptr->RequestPtr->WatchItem) && WATCH_MODULE(WATCH_MOD_PROXY)) WatchThis (WATCHITM(tkptr), WATCH_MOD_PROXY, "ProxyReadCacheFailed()"); ProxyNetResolveHost (tkptr); } /****************************************************************************/ /* ************ *** NOTE *** This function takes a pointer to a request!!! ************ Parse the various components from the proxy request. */ int ProxyRequestParse (REQUEST_STRUCT *rqptr) { #define paptr ProxyAccountingPtr int Length; char *cptr, *sptr, *zptr; PROXY_TASK *tkptr; /*********/ /* begin */ /*********/ if (WATCHMOD (rqptr, WATCH_MOD_PROXY)) WatchThis (WATCHITM(rqptr), WATCH_MOD_PROXY, "ProxyRequestParse() !&Z", rqptr->MappedPathPtr); /* get a local pointer to the proxy task structure */ tkptr = rqptr->ProxyTaskPtr; cptr = rqptr->rqHeader.RequestUriPtr; zptr = (sptr = tkptr->RequestSchemeName) + sizeof(tkptr->RequestSchemeName)-1; /* this allows a proxy service to be used non-proxy (if mapped) */ if (*cptr == '/') cptr++; while (*cptr && *cptr != ':' && sptr < zptr) *sptr++ = *cptr++; if (*cptr && sptr < zptr) *sptr++ = *cptr++; if (sptr >= zptr) { InstanceGblSecIncrLong (&AccountingPtr->RequestErrorCount); rqptr->rqResponse.HttpStatus = 502; ErrorGeneral (rqptr, MsgFor(rqptr,MSG_REQUEST_FORMAT), FI_LI); return (STS$K_ERROR); } *sptr = '\0'; if (WATCHMOD (rqptr, WATCH_MOD_PROXY)) WatchThis (WATCHITM(rqptr), WATCH_MOD_PROXY, "!&Z", tkptr->RequestSchemeName); if (tkptr->HttpMethod == HTTP_METHOD_CONNECT) tkptr->RequestScheme = PROXY_SCHEME_CONNECT; else if (strsame (tkptr->RequestSchemeName, "http:", -1)) { tkptr->RequestScheme = PROXY_SCHEME_HTTP; /* [http=0,https=1][http=0,https=1,ftp=2] */ if (rqptr->ServicePtr->RequestScheme == SCHEME_HTTP) InstanceGblSecIncrLong (&paptr->GatewaySchemeCount[0][0]); else InstanceGblSecIncrLong (&paptr->GatewaySchemeCount[1][0]); } else if (strsame (tkptr->RequestSchemeName, "ftp:", -1)) { tkptr->RequestScheme = PROXY_SCHEME_FTP; /* [http=0,https=1][http=0,https=1,ftp=2] */ if (rqptr->ServicePtr->RequestScheme == SCHEME_HTTP) InstanceGblSecIncrLong (&paptr->GatewaySchemeCount[0][2]); else InstanceGblSecIncrLong (&paptr->GatewaySchemeCount[1][2]); } else if (strsame (tkptr->RequestSchemeName, "https:", -1)) { if (tkptr->ServicePtr->SSLclientEnabled) { /* proxy HTTP-to-SSL gateway "one-shot" request */ tkptr->RequestScheme = PROXY_SCHEME_HTTPSSL; /* [http=0,https=1][http=0,https=1,ftp=2] */ if (rqptr->ServicePtr->RequestScheme == SCHEME_HTTP) InstanceGblSecIncrLong (&paptr->GatewaySchemeCount[0][1]); else InstanceGblSecIncrLong (&paptr->GatewaySchemeCount[1][1]); } else { if (WATCHING (rqptr, WATCH_PROXY)) WatchThis (WATCHITM(rqptr), WATCH_PROXY, "CLIENT SSL not configured"); rqptr->rqResponse.HttpStatus = 503; ErrorGeneral (rqptr, MsgFor(rqptr,MSG_PROXY_GATEWAY), FI_LI); return (STS$K_ERROR); } } else tkptr->RequestScheme = 0; if (!tkptr->RequestScheme) { rqptr->rqResponse.HttpStatus = 502; ErrorGeneral (rqptr, MsgFor(rqptr,MSG_PROXY_REQUEST_SCHEME), FI_LI); return (STS$K_ERROR); } if (*((USHORTPTR)cptr) != '//') { InstanceGblSecIncrLong (&AccountingPtr->RequestErrorCount); rqptr->rqResponse.HttpStatus = 502; ErrorGeneral (rqptr, MsgFor(rqptr,MSG_REQUEST_FORMAT), FI_LI); return (STS$K_ERROR); } cptr += 2; /*******************************/ /* check for username:password */ /*******************************/ for (sptr = cptr; *sptr && *sptr != '@' && *sptr != '/' && *sptr != '?'; sptr++); if (*sptr == '@') { /* looks like we found one */ zptr = (sptr = tkptr->UrlUserName) + sizeof(tkptr->UrlUserName)-1; while (*cptr && *cptr != ':' && *cptr != '@' && *cptr != '/' && *cptr != '?' && sptr < zptr) *sptr++ = *cptr++; *sptr = '\0'; if (sptr >= zptr) cptr = ""; if (*cptr == ':') { cptr++; zptr = (sptr = tkptr->UrlPassword) + sizeof(tkptr->UrlPassword)-1; while (*cptr && *cptr != '@' && *cptr != '/' && *cptr != '?' && sptr < zptr) *sptr++ = *cptr++; *sptr = '\0'; if (sptr >= zptr) cptr = ""; } if (*cptr != '@') { InstanceGblSecIncrLong (&AccountingPtr->RequestErrorCount); rqptr->rqResponse.HttpStatus = 502; ErrorGeneral (rqptr, MsgFor(rqptr,MSG_REQUEST_FORMAT), FI_LI); return (STS$K_ERROR); } cptr++; } /*************************/ /* get server host name */ /*************************/ zptr = (sptr = tkptr->RequestHostName) + sizeof(tkptr->RequestHostName)-1; if (*cptr == '[') { /* square bracket delimited IPv6 address (de facto?) */ cptr++; while (*cptr && *cptr != ']' && *cptr != '%' && sptr < zptr) *sptr++ = *cptr++; if (*cptr == '%') { /* e.g. http://[fe80::200:f8ff:fe75:c062%eth0]:6680 */ while (*cptr && *cptr != ']') cptr++; } if (*cptr) cptr++; } else while (*cptr && *cptr != ':' && *cptr != '/' && *cptr != '?' && sptr < zptr) *sptr++ = *cptr++; *sptr = '\0'; if (sptr >= zptr) cptr = ""; if (*cptr && *cptr != ':' && *cptr != '/' && *cptr != '?') { InstanceGblSecIncrLong (&AccountingPtr->RequestErrorCount); rqptr->rqResponse.HttpStatus = 502; ErrorGeneral (rqptr, MsgFor(rqptr,MSG_REQUEST_FORMAT), FI_LI); return (STS$K_ERROR); } if (WATCHMOD (rqptr, WATCH_MOD_PROXY)) WatchThis (WATCHITM(rqptr), WATCH_MOD_PROXY, "!&Z", tkptr->RequestHostName); /******************************/ /* get (optional) server port */ /******************************/ if (*cptr == ':') { cptr++; if (isdigit(*cptr)) { zptr = (sptr = tkptr->RequestPortString) + sizeof(tkptr->RequestPortString)-1; while (*cptr && isdigit(*cptr) && sptr < zptr) *sptr++ = *cptr++; if (sptr >= zptr) { InstanceGblSecIncrLong (&AccountingPtr->RequestErrorCount); rqptr->rqResponse.HttpStatus = 502; ErrorGeneral (rqptr, MsgFor(rqptr,MSG_REQUEST_FORMAT), FI_LI); return (STS$K_ERROR); } *sptr = '\0'; tkptr->RequestPort = atol (tkptr->RequestPortString); } } if (!tkptr->RequestPort) { if (tkptr->RequestScheme == PROXY_SCHEME_HTTP) { tkptr->RequestPort = 80; SET4(tkptr->RequestPortString,'80\0\0'); } else if (tkptr->RequestScheme == PROXY_SCHEME_FTP) { tkptr->RequestPort = 21; SET4(tkptr->RequestPortString,'21\0\0'); } else if (tkptr->RequestScheme == PROXY_SCHEME_HTTPSSL) { tkptr->RequestPort = 443; SET4(tkptr->RequestPortString,'443\0'); } } if (WATCHMOD (rqptr, WATCH_MOD_PROXY)) WatchThis (WATCHITM(rqptr), WATCH_MOD_PROXY, "!UL !&Z", tkptr->RequestPort, tkptr->RequestPortString); /***************************************/ /* get request URI and if query string */ /***************************************/ if (*cptr == '/') tkptr->RequestUriPtr = cptr; else if (tkptr->RequestScheme == PROXY_SCHEME_FTP) { /* no trailing slash after host[:port], redirect to add one */ sptr = ResponseLocation (rqptr, rqptr->rqHeader.RequestUriPtr, rqptr->rqHeader.RequestUriLength+1); sptr[rqptr->rqHeader.RequestUriLength] = '/'; return (STS$K_ERROR); } else tkptr->RequestUriPtr = "/"; if (rqptr->rqHeader.QueryStringPtr[0]) { while (*cptr && *cptr != '?') cptr++; tkptr->RequestUriQueryStringPtr = cptr; } else tkptr->RequestUriQueryStringPtr = ""; /*******************************/ /* point to any request cookie */ /*******************************/ tkptr->RequestHttpCookiePtr = rqptr->rqHeader.CookiePtr; /***********************/ /* composite name:port */ /***********************/ /* store host name/port as FIRST ITEM in cache data block */ zptr = (sptr = tkptr->RequestHostPort) + sizeof(tkptr->RequestHostPort); for (cptr = tkptr->RequestHostName; *cptr && sptr < zptr; *sptr++ = *cptr++); if (sptr < zptr) *sptr++ = ':'; for (cptr = tkptr->RequestPortString; *cptr && sptr < zptr; *sptr++ = *cptr++); if (sptr >= zptr) { rqptr->rqResponse.HttpStatus = 500; ErrorGeneralOverflow (rqptr, FI_LI); return (STS$K_ERROR); } *sptr = '\0'; tkptr->RequestHostPortLength = sptr - tkptr->RequestHostPort; if (WATCHMOD (rqptr, WATCH_MOD_PROXY)) WatchThis (WATCHITM(rqptr), WATCH_MOD_PROXY, "!&Z", tkptr->RequestHostPort); tkptr->RequestHttpMethod = rqptr->rqHeader.Method; tkptr->RequestHttpMethodNamePtr = rqptr->rqHeader.MethodName; /* max-age=0 applies to intermediate caches but not origin servers */ tkptr->RequestNotFromCache = rqptr->NotFromCache || rqptr->rqHeader.CacheControlMaxAgeZero; return (SS$_NORMAL); #undef paptr } /*****************************************************************************/ /* If a proxied request is being supplied from cache then end that (basically close the associated cache file). If supplied via a network transaction and the socket was created then shut and close the socket. If concurrently writing a request body to the proxied server then just return after closing the socket. The body write will receive an error and call this function to close the request down. Finally deallocate proxy memory and if an associated request structure end that request. */ ProxyEnd (PROXY_TASK *tkptr) { #define paptr ProxyAccountingPtr int status, StatusCodeGroup; NETIO_STRUCT *ioptr; REQUEST_STRUCT *rqptr; /*********/ /* begin */ /*********/ if (WATCHMOD (tkptr, WATCH_MOD_PROXY)) WatchThis (WATCHITM(tkptr), WATCH_MOD_PROXY, "ProxyEnd()"); ioptr = tkptr->NetIoPtr; rqptr = tkptr->RequestPtr; if (tkptr->ParseInUse || tkptr->CacheFileQio.AcpChannel || tkptr->CacheFileQio.QioChannel || tkptr->LoadFab.fab$w_ifi) { /* finalize cache file processing */ ProxyCacheEnd (tkptr); return; } if (ioptr->SesolaPtr && !(tkptr->PersistentRequest && tkptr->PersistentResponse && NETIO_CONNECTED (tkptr->NetIoPtr))) { /* proxy Secure Sockets Layer request */ SesolaNetClientShutdown (ioptr->SesolaPtr); return; } if (tkptr->ProxyTunnel) { if (NETIO_CONNECTED (rqptr->NetIoPtr)) NetCloseSocket (rqptr); if (NETIO_CONNECTED (tkptr->NetIoPtr)) ProxyNetCloseSocket (tkptr); } /* if outstanding I/O then wait until it concludes */ if (NETIO_IN_PROGRESS (ioptr)) return; if (NETIO_IN_PROGRESS (rqptr->NetIoPtr)) return; /********************/ /* proxy accounting */ /********************/ InstanceMutexLock (INSTANCE_MUTEX_HTTPD); StatusCodeGroup = tkptr->ResponseStatusCode / 100; if (StatusCodeGroup < 0 || StatusCodeGroup > 5) StatusCodeGroup = 0; if (tkptr->ProxyCacheReadBytes) paptr->CacheStatusCodeCount[StatusCodeGroup]++; else paptr->NetStatusCodeCount[StatusCodeGroup]++; /* update the stats using the underlying network I/O structure */ ADD_QUAD_QUAD (ioptr->BytesTallyRx, paptr->BytesRawRx) ADD_QUAD_QUAD (ioptr->BlocksTallyRx, paptr->BlocksRawRx) if (tkptr->CannotCache) ADD_QUAD_QUAD (ioptr->BytesTallyRx, paptr->BytesCannotCacheRx); ADD_QUAD_QUAD (ioptr->BytesTallyTx, paptr->BytesRawTx) ADD_QUAD_QUAD (ioptr->BlocksTallyTx, paptr->BlocksRawTx) if (tkptr->CannotCache) ADD_QUAD_QUAD (ioptr->BytesTallyTx, paptr->BytesCannotCacheTx); if (tkptr->ProxyCacheReadBytes) { ADD_LONG_QUAD (tkptr->ProxyCacheReadBytes, paptr->BytesCacheTx); ADD_LONG_QUAD (rqptr->rqHeader.RequestHeaderLength, paptr->BytesCacheRx); } /* reset the intermediate accumulators used to update the stats */ PUT_ZERO_QUAD (ioptr->BlocksTallyRx); PUT_ZERO_QUAD (ioptr->BlocksTallyTx); PUT_ZERO_QUAD (ioptr->BytesTallyRx); PUT_ZERO_QUAD (ioptr->BytesTallyTx); if (tkptr->ProxyTunnel) if (paptr->TunnelCurrent > 0) paptr->TunnelCurrent--; InstanceMutexUnLock (INSTANCE_MUTEX_HTTPD); /****************/ /* task rundown */ /****************/ if (tkptr->VerifyRecordPtr) ProxyVerifyRecordReset (tkptr); if (tkptr->ResponseStatusCode) rqptr->rqResponse.HttpStatus = tkptr->ResponseStatusCode; if (tkptr->ProxyTunnel) { rqptr->ProxyTunnelRequest = true; if (WATCHING (tkptr, WATCH_PROXY)) { char *cptr; switch (tkptr->ProxyTunnel) { case PROXY_TUNNEL_CONNECT : cptr = "CONNECT"; break; case PROXY_TUNNEL_HTTP : cptr = "HTTP"; break; case PROXY_TUNNEL_HTTPS : cptr = "HTTPS"; break; case PROXY_TUNNEL_RAW : cptr = "RAW"; break; default : cptr = "?"; } WatchThis (WATCHITM(tkptr), WATCH_PROXY, "TUNNEL (!AZ) removed", cptr); } } /* use |tkptr->NetIoPtr| not |ioptr| */ if (tkptr->PersistentRequest && tkptr->PersistentResponse && NETIO_CONNECTED (tkptr->NetIoPtr)) { /* try to make the connection persist */ ProxyNetConnectPersist (tkptr); } /* use |tkptr->NetIoPtr| not |ioptr| */ if (NETIO_CONNECTED (tkptr->NetIoPtr)) { /* still have the socket open (obviously couldn't persist) */ if (ioptr->SesolaPtr) { SesolaNetClientShutdown (ioptr->SesolaPtr); return; } ProxyNetCloseSocket (tkptr); NetIoEnd (ioptr); tkptr->NetIoPtr = NULL; } else if (tkptr->ProxyTunnel) { NetIoEnd (ioptr); tkptr->NetIoPtr = NULL; } /* indicate this no longer has an associated proxy task */ rqptr->ProxyTaskPtr = NULL; /* request heap memory will be freed by request rundown */ RequestEnd (rqptr); #undef paptr } /****************************************************************************/ /* Called from ProxyNetHostConnectAst() or SesolaNetClientConnect() to rebuild and then write the request to the proxied server. */ ProxyWriteRequest (PROXY_TASK *tkptr) { int status; REQUEST_STRUCT *rqptr; /*********/ /* begin */ /*********/ if (WATCHMOD (tkptr, WATCH_MOD_PROXY)) WatchThis (WATCHITM(tkptr), WATCH_MOD_PROXY, "ProxyWriteRequest()"); rqptr = tkptr->RequestPtr; ProxyRequestRebuild (tkptr); if (WATCHING (tkptr, WATCH_PROXY_REQU_HDR)) { WatchThis (WATCHITM(tkptr), WATCH_PROXY_REQU_HDR, "REQUEST HEADER !UL bytes", tkptr->RebuiltRequestLength); WatchData (tkptr->RebuiltRequestPtr, tkptr->RebuiltRequestLength); } ProxyNetWrite (tkptr, &ProxyWriteRequestAst, tkptr->RebuiltRequestPtr, tkptr->RebuiltRequestLength); } /****************************************************************************/ /* Called as an AST from ProxyWriteRequest(). The write of the request header to the remote server has completed. If the is a body to the request (i.e. a POST or PUT method) then call the function to BEGIN CONCURRENTLY writing that. If no body check the status of the proxy write. If not successful then report the error and end the proxy processing. If OK then queue a read from the remote server to begin to get the response. */ ProxyWriteRequestAst (PROXY_TASK *tkptr) { int DataLength; char *cptr, *sptr, *zptr, *DataPtr; REQUEST_STRUCT *rqptr; /*********/ /* begin */ /*********/ if (WATCHMOD (tkptr, WATCH_MOD_PROXY)) WatchThis (WATCHITM(tkptr), WATCH_MOD_PROXY, "ProxyWriteRequestAst() !&F !&S", &ProxyWriteRequestAst, tkptr->NetIoPtr->WriteStatus); rqptr = tkptr->RequestPtr; if (VMSnok (tkptr->NetIoPtr->WriteStatus)) { /***********************/ /* write request error */ /***********************/ if (tkptr->NetIoPtr->Channel) { /* the socket has not been explicitly closed, so ... */ tkptr->ResponseStatusCode = 502; if (rqptr) { /* a request associated with this proxy task */ rqptr->rqResponse.HttpStatus = 502; rqptr->rqResponse.ErrorTextPtr = tkptr->RequestHostPort; ErrorVmsStatus (rqptr, tkptr->NetIoPtr->WriteStatus, FI_LI); } } /* toodleoo */ ProxyEnd (tkptr); return; } /**********************************************************/ /* concurrently send body as well as waiting for response */ /**********************************************************/ if (rqptr->rqHeader.ContentLength) { BodyReadBegin (rqptr, &ProxyWriteRequestBody, NULL); tkptr->QueuedBodyRead++; } /*************************************/ /* begin (wait for) reading response */ /*************************************/ tkptr->ResponseHeaderPtr = tkptr->ResponseBufferCurrentPtr; tkptr->ResponseBodyLength = 0; tkptr->ResponseHeaderLength = 0; tkptr->ResponseConsecutiveNewLineCount = 0; /* responses can quite legitimately have a content-length of zero */ tkptr->ResponseContentLength = -1; ProxyNetRead (tkptr, &ProxyReadResponseAst, tkptr->ResponseBufferCurrentPtr, tkptr->ResponseBufferRemaining); } /*****************************************************************************/ /* A network read of the request body has completed and BodyReadAst() has called this function as an AST. Write it to the remote server with a completion AST to ProxyWriteRequestBodyAst(). */ ProxyWriteRequestBody (REQUEST_STRUCT *rqptr) { int status; PROXY_TASK *tkptr; /*********/ /* begin */ /*********/ tkptr = rqptr->ProxyTaskPtr; if (WATCHMOD (rqptr, WATCH_MOD_PROXY)) WatchThis (WATCHITM(rqptr), WATCH_MOD_PROXY, "ProxyWriteRequestBody() !&F !&S !&X !UL", &ProxyWriteRequestBody, rqptr->rqBody.DataStatus, rqptr->rqBody.DataPtr, rqptr->rqBody.DataCount); if (tkptr->QueuedBodyRead) tkptr->QueuedBodyRead--; if (rqptr->rqBody.DataStatus == SS$_ENDOFFILE) { /****************************/ /* request body is finished */ /****************************/ /* just finish up this concurrent activity */ return; } if (VMSnok (rqptr->rqBody.DataStatus)) { ProxyEnd (tkptr); return; } ProxyNetWrite (tkptr, &ProxyWriteRequestBodyAst, rqptr->rqBody.DataPtr, rqptr->rqBody.DataCount); } /*****************************************************************************/ /* A queued write to the remote server has completed. Get more of the request body. */ ProxyWriteRequestBodyAst (PROXY_TASK *tkptr) { int status; REQUEST_STRUCT *rqptr; /*********/ /* begin */ /*********/ if (WATCHMOD (tkptr, WATCH_MOD_PROXY)) WatchThis (WATCHITM(tkptr), WATCH_MOD_PROXY, "ProxyWriteRequestBodyAst() !&F !&S", &ProxyWriteRequestBodyAst, tkptr->NetIoPtr->WriteStatus); rqptr = tkptr->RequestPtr; if (VMSnok (tkptr->NetIoPtr->WriteStatus)) { /* write body error */ if (tkptr->NetIoPtr->Channel) { /* only if the remote server has not responded do we report this */ if (!tkptr->ResponseStatusCode) { /* the socket has not been deliberately closed */ tkptr->ResponseStatusCode = 502; if (rqptr) { /* a request associated with this proxy task */ rqptr->rqResponse.HttpStatus = 502; rqptr->rqResponse.ErrorTextPtr = tkptr->RequestHostPort; ErrorVmsStatus (rqptr, tkptr->NetIoPtr->WriteStatus, FI_LI); } } } /* finale */ ProxyEnd (tkptr); return; } /* get more from the client */ BodyRead (rqptr); tkptr->QueuedBodyRead++; } /****************************************************************************/ /* Called as an AST when the read of a buffer from the proxied server completes. It checks for, and analyzes, the response header. When the response header has been analyzed a decision is made as to whether the response is cachable. If cacheable this function stops executing while the cache file is created. When that is complete ProxyReadResponseCacheWrite() is called as an AST to write the first buffer of data into the file. If not cacheable then if associated with a request the data is written to the client via the network. */ ProxyReadResponseAst (PROXY_TASK *tkptr) { int status; char *cptr, *sptr, *zptr; REQUEST_STRUCT *rqptr; /*********/ /* begin */ /*********/ if (WATCHMOD (tkptr, WATCH_MOD_PROXY)) WatchThis (WATCHITM(tkptr), WATCH_MOD_PROXY, "ProxyReadResponseAst() !&F !&X !UL", &ProxyReadResponseAst, tkptr->NetIoPtr->ReadStatus, tkptr->NetIoPtr->ReadCount); rqptr = tkptr->RequestPtr; if (VMSnok (tkptr->NetIoPtr->ReadStatus)) { /**************/ /* read error */ /**************/ if (tkptr->NetIoPtr->Channel) { /* the socket has not been explicitlyly shut down */ if (tkptr->ResponseBytes) { /* we got some bytes back from the remote server */ if (tkptr->ResponseHttpVersion != HTTP_VERSION_0_9 && !tkptr->ResponseHeaderLength) { /* header was not completely received */ tkptr->ResponseStatusCode = 502; if (rqptr) { /* request associated with task */ rqptr->rqResponse.HttpStatus = 502; ErrorGeneral (rqptr, MsgFor(rqptr,MSG_PROXY_HEADER), FI_LI); } } } else { /* absolutely no bytes at all from the remote server */ tkptr->ResponseStatusCode = 502; if (rqptr) { /* request associated with task */ rqptr->rqResponse.HttpStatus = 502; rqptr->rqResponse.ErrorTextPtr = tkptr->RequestHostPort; switch (tkptr->NetIoPtr->ReadStatus) { case PROXY_ERROR_HOST_DISCONNECTED : /* disconnected by third party */ ErrorGeneral (rqptr, MsgFor(rqptr, MSG_PROXY_DISCON), FI_LI); break; default : ErrorVmsStatus (rqptr, tkptr->NetIoPtr->ReadStatus, FI_LI); } } } } /* finis */ ProxyEnd (tkptr); return; } tkptr->ResponseBytes += tkptr->NetIoPtr->ReadCount; if (tkptr->ResponseConsecutiveNewLineCount < 2) { tkptr->ResponseBufferCurrentPtr += tkptr->NetIoPtr->ReadCount; tkptr->ResponseBufferRemaining -= tkptr->NetIoPtr->ReadCount; /* careful of this loop - it's only here to help with 100 continues */ while (tkptr->ResponseConsecutiveNewLineCount < 2) { /***************************/ /* examine response header */ /***************************/ status = ProxyResponseHeader (tkptr); if (VMSnok (status)) { ProxyEnd (tkptr); return; } /* if the full header has not yet been received */ if (tkptr->ResponseConsecutiveNewLineCount < 2) break; if (tkptr->ResponseStatusCode < 100 || tkptr->ResponseStatusCode > 599 || tkptr->ResponseTransferEncodingUnsupported) { tkptr->ResponseStatusCode = 502; ErrorGeneral (rqptr, MsgFor(rqptr,MSG_PROXY_HEADER), FI_LI); ProxyEnd (tkptr); return; } if (tkptr->ResponseHttpVersion == HTTP_VERSION_1_1 && tkptr->ResponseStatusCode == 100 && tkptr->ResponseConsecutiveNewLineCount >= 2) { /*******************************/ /* special case - 100 continue */ /*******************************/ /* synchronous write, convenient and won't happen very often */ NetWrite (tkptr->RequestPtr, 0, tkptr->RebuiltResponsePtr, tkptr->RebuiltResponseLength); /* reset the response header processing */ ProxyResponseRebuild (tkptr, NULL, 0); /* start again, at the point where the 100 header finished */ tkptr->ResponseHeaderPtr += tkptr->ResponseHeaderLength; tkptr->ResponseHeaderLength = 0; tkptr->ResponseConsecutiveNewLineCount = 0; /* if more still in the read buffer - use that while loop now! */ if (tkptr->ResponseBufferCurrentPtr > tkptr->ResponseHeaderPtr) continue; } /* make sure we're out of that while loop */ break; } if (tkptr->ResponseConsecutiveNewLineCount < 2) { /*******************************************/ /* entire header has not yet been received */ /*******************************************/ ProxyNetRead (tkptr, &ProxyReadResponseAst, tkptr->ResponseBufferCurrentPtr, tkptr->ResponseBufferRemaining); return; } /****************************/ /* header has been received */ /****************************/ rqptr->rqResponse.HttpStatus = tkptr->ResponseStatusCode; /* only the body portion goes to the client (header has been rebuilt) */ tkptr->ResponseBufferNetPtr = tkptr->ResponseHeaderPtr + tkptr->ResponseHeaderLength; tkptr->ResponseBufferNetCount = tkptr->ResponseBufferCurrentPtr - tkptr->ResponseBufferNetPtr; /* everything in the buffer goes to the cache file */ tkptr->ResponseBufferCachePtr = tkptr->ResponseBufferPtr; tkptr->ResponseBufferCacheCount = tkptr->ResponseBufferCurrentPtr - tkptr->ResponseBufferCachePtr; if (tkptr->ResponseHttpVersion == HTTP_VERSION_1_1 && tkptr->ResponseStatusCode == 101 && tkptr->ResponseUpgradeWebSocket && rqptr->WebSocketRequest) { /***************************************/ /* special case - 101 change protocols */ /***************************************/ if (WATCHING (rqptr, WATCH_PROXY)) WatchThis (WATCHITM(rqptr), WATCH_PROXY, "UPGRADE WebSocket"); /* the proxied WebSocket will be handled by a raw tunnel */ tkptr->ProxyTunnel = PROXY_TUNNEL_RAW; rqptr->rqResponse.HeaderSent = true; NetWriteRaw (rqptr, &ProxyTunnelConnectResponseAst, tkptr->ResponseHeaderPtr, tkptr->ResponseHeaderLength); return; } if (WATCHING (tkptr, WATCH_PROXY_RESP_BDY)) { if (tkptr->ResponseBufferNetCount) { WatchThis (WATCHITM(rqptr), WATCH_PROXY_RESP_BDY, "RESPONSE BODY !UL bytes", tkptr->ResponseBufferNetCount); WatchDataDump (tkptr->ResponseBufferNetPtr, tkptr->ResponseBufferNetCount); } } if (rqptr->rqHeader.Method == HTTP_METHOD_HEAD) { /* no possibility of a body */ tkptr->ResponseContentLength = tkptr->ResponseBufferNetCount = 0; } else { /* whatever is left in the buffer */ tkptr->ResponseBodyLength = tkptr->ResponseBufferNetCount; } if (tkptr->ResponseTransferEncodingChunked) { status = ProxyResponseChunked (tkptr); if (VMSnok (status)) { tkptr->ResponseStatusCode = 502; ErrorGeneral (rqptr, MsgFor(rqptr,MSG_PROXY_HEADER), FI_LI); ProxyEnd (tkptr); return; } } if (tkptr->ServicePtr->ProxyFileCacheEnabled && tkptr->ProxyCacheSuitable) { /***********************/ /* begin proxy caching */ /***********************/ status = ProxyCacheLoadBegin (tkptr); if (VMSok (status)) return; /* error status means it was not a candidate for caching */ } } else { /*******************************/ /* just receiving the body now */ /*******************************/ tkptr->ResponseBodyLength += tkptr->NetIoPtr->ReadCount; /* both cache file and network contents are identical from here */ tkptr->ResponseBufferCachePtr = tkptr->ResponseBufferNetPtr = tkptr->ResponseBufferPtr; tkptr->ResponseBufferCacheCount = tkptr->ResponseBufferNetCount = tkptr->NetIoPtr->ReadCount; if (WATCHING (tkptr, WATCH_PROXY_RESP_BDY)) { WatchThis (WATCHITM(rqptr), WATCH_PROXY_RESP_BDY, "RESPONSE BODY !UL bytes", tkptr->NetIoPtr->ReadCount); WatchDataDump (tkptr->ResponseBufferPtr, tkptr->NetIoPtr->ReadCount); #if WATCH_MOD if (tkptr->ResponseContentTypeText) { if (tkptr->NetIoPtr->ReadCount && tkptr->ResponseBufferPtr[tkptr->NetIoPtr->ReadCount-1] == '\n') WatchDataFormatted ("!#AZ", tkptr->NetIoPtr->ReadCount, tkptr->ResponseBufferPtr); else WatchDataFormatted ("!#AZ\n", tkptr->NetIoPtr->ReadCount, tkptr->ResponseBufferPtr); } #endif /* WATCH_MOD */ } if (tkptr->ResponseTransferEncodingChunked) { status = ProxyResponseChunked (tkptr); if (VMSnok (status)) { tkptr->ResponseStatusCode = 502; ErrorGeneral (rqptr, MsgFor(rqptr,MSG_PROXY_HEADER), FI_LI); ProxyEnd (tkptr); return; } } } /*******************************************************/ /* to cache file (then any client) or direct to client */ /*******************************************************/ if (tkptr->LoadFab.fab$w_ifi) ProxyCacheLoadWrite (tkptr); else ProxyResponseNetWrite (tkptr); } /****************************************************************************/ /* The body is being "Transfer-Encoding: chunked" so the internal structure of this data stream needs to be parsed to detect end-of-body. */ int ProxyResponseChunked (PROXY_TASK *tkptr) { #define WATCH_CHUNK \ if (WATCHMOD (tkptr, WATCH_MOD_PROXY)) \ WatchThis (WATCHITM(tkptr), WATCH_MOD_PROXY, \ "CHUNKED !SL !UL !UL !UL !&B !&B !&B !&Z", \ BufferRemaining, \ tkptr->ResponseChunkedNewlineCount, \ tkptr->ResponseChunkedSize, \ tkptr->ResponseChunkedCount, \ tkptr->ResponseChunkedEol, \ tkptr->ResponseChunkedEot, \ tkptr->ResponseChunkedEnd, \ tkptr->ResponseChunkedString); int BufferRemaining; char *cptr, *czptr, *sptr, *zptr; REQUEST_STRUCT *rqptr; /*********/ /* begin */ /*********/ if (WATCHMOD (tkptr, WATCH_MOD_PROXY)) WatchThis (WATCHITM(tkptr), WATCH_MOD_PROXY, "ProxyResponseChunked()"); rqptr = tkptr->RequestPtr; if (!tkptr->ResponseChunkedInit) { tkptr->ResponseChunkedInit = true; tkptr->ResponseChunkedEnd = tkptr->ResponseChunkedEol = tkptr->ResponseChunkedEot = false; tkptr->ResponseChunkedCount = tkptr->ResponseChunkedSize = tkptr->ResponseChunkedNewlineCount = 0; tkptr->ResponseChunkedString[0] = '\0'; } czptr = (cptr = tkptr->ResponseBufferNetPtr) + tkptr->ResponseBufferNetCount; while (cptr < czptr) { BufferRemaining = czptr - cptr; WATCH_CHUNK if (tkptr->ResponseChunkedSize && !tkptr->ResponseChunkedEot) { /* currently transfering a chunk */ if (BufferRemaining >= tkptr->ResponseChunkedSize - tkptr->ResponseChunkedCount) { /* have enough for this chunk (at least) */ cptr += tkptr->ResponseChunkedSize - tkptr->ResponseChunkedCount; tkptr->ResponseChunkedCount = tkptr->ResponseChunkedSize = 0; } else { /* have not reached the end of this chunk yet */ tkptr->ResponseChunkedCount += BufferRemaining; break; } } if (cptr >= czptr) break; BufferRemaining = czptr - cptr; WATCH_CHUNK while (cptr < czptr && !tkptr->ResponseChunkedSize && !tkptr->ResponseChunkedEot) { /* getting the size of the next chunk */ if (tkptr->ResponseChunkedEol) { /* looking for the end of the chunk size line */ while (cptr < czptr && *cptr != '\n') cptr++; if (cptr >= czptr) break; cptr++; /* hit the end-of-line */ tkptr->ResponseChunkedSize = strtol (tkptr->ResponseChunkedString, NULL, 16); if (WATCHING (tkptr, WATCH_PROXY_RESP_BDY)) WatchThis (WATCHITM(rqptr), WATCH_PROXY_RESP_BDY, "RESPONSE BODY CHUNK \"!AZ\" !UL bytes", tkptr->ResponseChunkedString, tkptr->ResponseChunkedSize); tkptr->ResponseChunkedEol = false; tkptr->ResponseChunkedString[0] = '\0'; tkptr->ResponseChunkedCount = 0; if (!tkptr->ResponseChunkedSize) { tkptr->ResponseChunkedEot = true; tkptr->ResponseChunkedNewlineCount = 1; } } else { if (!tkptr->ResponseChunkedString[0]) { /* step over any trailing CR-LF of any preceding chunk */ if (*cptr == '\r' && cptr < czptr) cptr++; if (*cptr == '\n' && cptr < czptr) cptr++; } zptr = (sptr = tkptr->ResponseChunkedString) + sizeof(tkptr->ResponseChunkedString); /* find the end of the currently buffered string */ for (sptr = tkptr->ResponseChunkedString; *sptr && sptr < zptr; sptr++); while (isxdigit(*cptr) && cptr < czptr && sptr < zptr) *sptr++ = *cptr++; /* this shouldn't happen! */ if (sptr >= zptr) return (STS$K_ERROR); *sptr = '\0'; /* if reached the end of the hex number look for the end-of-line */ if (cptr < czptr && !isxdigit(*cptr)) tkptr->ResponseChunkedEol = true; } } if (cptr >= czptr) break; BufferRemaining = czptr - cptr; WATCH_CHUNK if (tkptr->ResponseChunkedEot) { /* looking for the end-of-trailer consecutive newlines */ while (cptr < czptr && tkptr->ResponseChunkedNewlineCount < 2) { if (*cptr == '\n') tkptr->ResponseChunkedNewlineCount++; else if (*cptr != '\r') tkptr->ResponseChunkedNewlineCount = 0; cptr++; } if (tkptr->ResponseChunkedNewlineCount >= 2) break; } if (cptr >= czptr) break; BufferRemaining = czptr - cptr; } if (tkptr->ResponseChunkedNewlineCount >= 2) tkptr->ResponseChunkedEnd = true; if (cptr >= czptr) BufferRemaining = 0; WATCH_CHUNK return (SS$_NORMAL); } /****************************************************************************/ /* If a cache load is in progress this function is called when the file write is complete. If not in progress then this is called directly from ProxyReadResponseAst(). If a request is associated with the task the current buffer is written out to the client via the network. If no request then the next buffer is read from the proxied server. */ ProxyResponseNetWrite (PROXY_TASK *tkptr) { int dcnt; char *dptr; /*********/ /* begin */ /*********/ if (WATCHMOD (tkptr, WATCH_MOD_PROXY)) WatchThis (WATCHITM(tkptr), WATCH_MOD_PROXY, "ProxyResponseNetWrite()"); if (!tkptr->ResponseHeaderSent) { dptr = tkptr->RebuiltResponsePtr, dcnt = tkptr->RebuiltResponseLength; } else { dptr = tkptr->ResponseBufferNetPtr; dcnt = tkptr->ResponseBufferNetCount; } NetWrite (tkptr->RequestPtr, &ProxyResponseNetWriteAst, dptr, dcnt); } /****************************************************************************/ /* ************ *** NOTE *** This function takes a pointer to a request!!! ************ Just a wrapper for when NetWrite() is used to AST queue a read of the next part of the response. Check the network write status and if OK then queue another read of data from the proxied server. */ ProxyResponseNetWriteAst (REQUEST_STRUCT *rqptr) { int BufferSize; PROXY_TASK *tkptr; /*********/ /* begin */ /*********/ tkptr = rqptr->ProxyTaskPtr; if (WATCHMOD (rqptr, WATCH_MOD_PROXY)) WatchThis (WATCHITM(rqptr), WATCH_MOD_PROXY, "ProxyResponseNetWriteAst() !&F !&X !&B !UL !UL", &ProxyResponseNetWriteAst, rqptr->NetIoPtr->WriteStatus, tkptr->PersistentResponse, tkptr->ResponseBodyLength, tkptr->ResponseContentLength); if (VMSnok (rqptr->NetIoPtr->WriteStatus)) { /* write to client failed, just abandon the request */ ProxyEnd (tkptr); return; } if (!tkptr->ResponseHeaderSent) { /* response header has now been sent */ tkptr->ResponseHeaderSent = true; if (tkptr->ResponseBufferNetCount) { /* there is response body available to be sent */ ProxyResponseNetWrite (tkptr); return; } } if (tkptr->ResponseTransferEncodingChunked) { if (tkptr->ResponseChunkedEnd) { /* end of chunked response body */ ProxyEnd (tkptr); return; } BufferSize = tkptr->ResponseBufferSize; } else if (tkptr->ResponseContentLength >= 0) { /* a content-length was specified, read only enough to satisfy that */ BufferSize = tkptr->ResponseContentLength - tkptr->ResponseBodyLength; if (BufferSize > tkptr->ResponseBufferSize) BufferSize = tkptr->ResponseBufferSize; /* if more was initially sent than content-length this can be negative */ if (BufferSize <= 0) { /* have read enough to satisfy this (perhaps persistent) response */ ProxyEnd (tkptr); return; } } else BufferSize = tkptr->ResponseBufferSize; ProxyNetRead (tkptr, &ProxyReadResponseAst, tkptr->ResponseBufferPtr, BufferSize); } /****************************************************************************/ /* Check the HTTP version. If it doesn't look like an HTTP/1.n then assume its an HTTP/0.9 (stream of HTML) and don't look for header fields. Look through the data received from the remote server so far for two consecutive blank lines (two newlines or two carriage-return/newline combinations). This delimits the response header. When found parse the response header. */ int ProxyResponseHeader (PROXY_TASK *tkptr) { int status, cnlcnt; char *cptr, *zptr; /*********/ /* begin */ /*********/ if (WATCHMOD (tkptr, WATCH_MOD_PROXY)) WatchThis (WATCHITM(tkptr), WATCH_MOD_PROXY, "ProxyResponseHeader()"); cnlcnt = tkptr->ResponseConsecutiveNewLineCount; cptr = tkptr->ResponseHeaderPtr; zptr = tkptr->ResponseBufferCurrentPtr; if (!tkptr->ResponseHttpVersion) { /******************************/ /* establish response version */ /******************************/ if (MATCH8 (cptr, "HTTP/1.1") && ISLWS(cptr[8])) tkptr->ResponseHttpVersion = HTTP_VERSION_1_1; else if (MATCH8 (cptr, "HTTP/1.0") && ISLWS(cptr[8])) tkptr->ResponseHttpVersion = HTTP_VERSION_1_0; else if (MATCH5 (cptr, "HTTP/")) tkptr->ResponseHttpVersion = HTTP_VERSION_UNKNOWN; else { ProxyHttp09ResponseHeader (tkptr); return (SS$_NORMAL); } } /******************************/ /* look for the end-of-header */ /******************************/ while (*cptr && cnlcnt < 2 && cptr < zptr) { if (*cptr == '\r') cptr++; if (!*cptr) break; if (*cptr != '\n') { cptr++; cnlcnt = 0; continue; } cptr++; cnlcnt++; } if ((tkptr->ResponseConsecutiveNewLineCount = cnlcnt) >= 2) { /* found the end of the response header */ tkptr->ResponseHeaderLength = cptr - (char*)tkptr->ResponseHeaderPtr; status = ProxyResponseRebuild (tkptr, tkptr->ResponseHeaderPtr, tkptr->ResponseHeaderLength); return (status); } return (SS$_NORMAL); } /****************************************************************************/ /* Fudge status response components for HTTP/0.9. Leave the 'tkptr->ResponseHeaderPtr' pointing where-ever but set the 'tkptr->ResponseHeaderLength' to zero. */ ProxyHttp09ResponseHeader (PROXY_TASK *tkptr) { /*********/ /* begin */ /*********/ if (WATCHMOD (tkptr, WATCH_MOD_PROXY)) WatchThis (WATCHITM(tkptr), WATCH_MOD_PROXY, "ProxyHttp09ResponseHeader()"); tkptr->ProxyCacheSuitable = false; tkptr->ResponseConsecutiveNewLineCount = 2; tkptr->ResponseHeaderLength = 0; /* do not touch 'tkptr->ResponseHeaderPtr' upon penalty of bug! */ tkptr->ResponseHttpVersion = HTTP_VERSION_0_9; tkptr->ResponseStatusCode = 200; } /****************************************************************************/ /* Rebuild the response header pointed to by the supplied paramaters into a new dynamically allocated buffer. This function is intended to process the response header either direct from the network or as read from a cache file. The rebuilt header has been modified to remove hop-by-hop fields and to add persistent connection fields as required. Other fields, perhaps unknown ones, are just propagated. */ int ProxyResponseRebuild ( PROXY_TASK *tkptr, char *HeaderPtr, int HeaderLength ) { static char AgeField [32]; static $DESCRIPTOR (AgeFieldDsc, AgeField); static $DESCRIPTOR (AgeFieldFaoDsc, "Age: !UL\r\n\0"); static char KeepAliveField [32]; static $DESCRIPTOR (KeepAliveFieldDsc, KeepAliveField); static $DESCRIPTOR (KeepAliveFieldFaoDsc, "Keep-Alive: timeout=!UL\r\n\0"); int idx, status, AgeSeconds, KeepAliveSeconds, RebuiltHeaderLength; char *cptr, *hzptr, *lzptr, *sptr, *tcptr, *tsptr, *tzptr, *zptr, *RebuiltHeaderPtr,*ContentLengthPtr=NULL,*ContentLengthEndPtr, *ContentTypePtr=NULL; unsigned long *Time64Ptr; PROXY_CACHE_FILE_QIO *cfqptr; REQUEST_STRUCT *rqptr; /*********/ /* begin */ /*********/ if (WATCHMOD (tkptr, WATCH_MOD_PROXY)) { WatchThis (WATCHITM(tkptr), WATCH_MOD_PROXY, "ProxyResponseRebuild() !&X !UL", HeaderPtr, HeaderLength); if (HeaderPtr) WatchDataDump (HeaderPtr, HeaderLength); } /* initialize header storage/flags potentially modified by this function */ tkptr->ResponseContentLength = -1; tkptr->PersistentResponse = tkptr->ResponseConnectionClose = tkptr->ResponseConnectionKeepAlive = tkptr->ResponseContentEncodingGzip = tkptr->ResponseContentEncodingUnknown = tkptr->ResponseContentTypeMultipart = tkptr->ResponseNoCache = tkptr->ResponseTransferEncodingChunked = tkptr->ResponseTransferEncodingUnsupported = tkptr->ResponseVaryUnsupported = false; tkptr->ResponseExpires[0] = tkptr->ResponseLastModified[0] = '\0'; /* can be called with the 100-Continue response header to reset the above */ if (!HeaderPtr) return (SS$_NORMAL); rqptr = tkptr->RequestPtr; /* non-zero QIO channel to check if it's from a cache file */ cfqptr = &tkptr->CacheFileQio; /* so we can detect whether the original header contained an age field */ AgeSeconds = -1; if (WATCHING (tkptr, WATCH_PROXY_RESP_HDR)) { WatchThis (WATCHITM(rqptr), WATCH_PROXY_RESP_HDR, "RESPONSE HEADER server->proxy !UL bytes", HeaderLength); WatchData (HeaderPtr, HeaderLength); } RebuiltHeaderLength = HeaderLength + 1024; if (rqptr->ProxyReverseLocationPtr && (tkptr->ResponseStatusCode == 301 || tkptr->ResponseStatusCode == 302)) RebuiltHeaderLength += PROXY_LOCATION_REBUILD_BUFFER; RebuiltHeaderPtr = VmGetHeap (rqptr, RebuiltHeaderLength); zptr = (sptr = RebuiltHeaderPtr) + RebuiltHeaderLength; cptr = HeaderPtr; hzptr = cptr + HeaderLength; /************************/ /* response status line */ /************************/ /* HTTP protocol version (redundant for network, needed for cache file) */ if (MATCH8 (cptr, "HTTP/1.1")) tkptr->ResponseHttpVersion = HTTP_VERSION_1_1; else if (MATCH8 (cptr, "HTTP/1.0")) tkptr->ResponseHttpVersion = HTTP_VERSION_1_0; else if (MATCH5 (cptr, "HTTP/")) tkptr->ResponseHttpVersion = HTTP_VERSION_UNKNOWN; while (!ISLWS(*cptr) && NOTEOL(*cptr) && cptr < hzptr && sptr < zptr) *sptr++ = *cptr++; while (ISLWS(*cptr) && cptr < hzptr && sptr < zptr) *sptr++ = *cptr++; /* get the HTTP status code */ tkptr->ResponseStatusCode = atoi(cptr); /* copy the rest of the response status line */ while (NOTEOL(*cptr) && cptr < hzptr && sptr < zptr) *sptr++ = *cptr++; if (*cptr == '\r' && cptr < hzptr && sptr < zptr) *sptr++ = *cptr++; if (*cptr == '\n' && cptr < hzptr && sptr < zptr) *sptr++ = *cptr++; if (cptr == HeaderPtr || cptr == hzptr || !tkptr->ResponseStatusCode) { ErrorNoticed (rqptr, SS$_BUGCHECK, ErrorSanityCheck, FI_LI); rqptr->rqResponse.HttpStatus = 500; ErrorGeneral (rqptr, ErrorSanityCheck, FI_LI); return (STS$K_ERROR); } /******************/ /* rest of header */ /******************/ while (cptr < hzptr && sptr < zptr) { /* get a pointer to the end-of-line character */ for (lzptr = cptr; lzptr < hzptr && NOTEOL(*lzptr); lzptr++); /* if reached the end-of-header empty line */ if (cptr == lzptr) break; if (WATCHMOD (tkptr, WATCH_MOD_PROXY)) WatchDataFormatted ("{!UL}!-!#AZ\n", lzptr-cptr, cptr); if (TOUP(*cptr) == 'A' && strsame (cptr, "Age:", 4)) { /*******/ /* age */ /*******/ tcptr = cptr + 4; while (ISLWS(*tcptr) && tcptr < lzptr) tcptr++; AgeSeconds = atoi(tcptr); if (WATCHMOD (tkptr, WATCH_MOD_PROXY)) WatchThis (WATCHITM(tkptr), WATCH_MOD_PROXY, "!UL", AgeSeconds); /* we'll add our own later, the original header is not propagated */ cptr += 4; while (cptr < lzptr) cptr++; if (*cptr == '\r' && cptr < hzptr) cptr++; if (*cptr == '\n' && cptr < hzptr) cptr++; continue; } else if (TOUP(*cptr) == 'C') { if (strsame (cptr, "Cache-Control:", 14)) { /*****************/ /* cache control */ /*****************/ tcptr = cptr + 14; while (ISLWS(*tcptr) && tcptr < lzptr) tcptr++; while (tcptr < lzptr) { if (strsame (tcptr, "must-revalidate", 15)) { tkptr->ResponseCacheControlMustReval = true; tcptr += 15; } else if (strsame (tcptr, "max-age=", 8)) { tcptr += 8; tkptr->ResponseCacheControlMaxAge = atoi(tcptr); if (!tkptr->ResponseCacheControlMaxAge) tkptr->ResponseCacheControlMaxAgeZero = true; } else if (strsame (tcptr, "no-cache", 8)) { tkptr->ResponseCacheControlNoCache = true; tcptr += 8; } else if (strsame (tcptr, "no-store", 8)) { tkptr->ResponseCacheControlNoStore = true; tcptr += 8; } else if (strsame (tcptr, "no-transform", 12)) { tkptr->ResponseCacheControlNoTransform = true; tcptr += 12; } else if (strsame (tcptr, "private", 7)) { tkptr->ResponseCacheControlPrivate = true; tcptr += 7; } else if (strsame (tcptr, "proxy-revalidate", 16)) { tkptr->ResponseCacheControlProxyReval = true; tcptr += 16; } else if (strsame (tcptr, "public", 6)) { tkptr->ResponseCacheControlPublic = true; tcptr += 6; } else if (strsame (tcptr, "s-maxage=", 9)) { tcptr += 9; tkptr->ResponseCacheControlSMaxAge = atoi(tcptr); if (!tkptr->ResponseCacheControlSMaxAge) tkptr->ResponseCacheControlMaxAgeZero = true; } while (!ISLWS(*tcptr) && *tcptr != ',' && tcptr < lzptr) tcptr++; while ((ISLWS(*tcptr) || *tcptr == ',') && tcptr < lzptr) tcptr++; } if (tkptr->ResponseCacheControlMaxAgeZero || tkptr->ResponseCacheControlMustReval || tkptr->ResponseCacheControlNoCache || tkptr->ResponseCacheControlNoStore || tkptr->ResponseCacheControlPrivate || tkptr->ResponseCacheControlProxyReval) tkptr->ResponseNoCache = true; if (WATCHMOD (tkptr, WATCH_MOD_PROXY)) WatchThis (WATCHITM(tkptr), WATCH_MOD_PROXY, "!&B - !&B !UL/!&B !&B !&B !&B !&B !&B !&B !UL", tkptr->ResponseNoCache, tkptr->ResponseCacheControlMustReval, tkptr->ResponseCacheControlMaxAge, tkptr->ResponseCacheControlMaxAgeZero, tkptr->ResponseCacheControlNoCache, tkptr->ResponseCacheControlNoStore, tkptr->ResponseCacheControlNoTransform, tkptr->ResponseCacheControlPrivate, tkptr->ResponseCacheControlProxyReval, tkptr->ResponseCacheControlPublic, tkptr->ResponseCacheControlSMaxAge); /* drop through to copy this header */ } else if (strsame (cptr, "Content-Length:", 15)) { /******************/ /* content-length */ /******************/ tcptr = cptr + 15; while (ISLWS(*tcptr) && tcptr < lzptr) tcptr++; tkptr->ResponseContentLength = atoi(tcptr); if (WATCHMOD (tkptr, WATCH_MOD_PROXY)) WatchThis (WATCHITM(tkptr), WATCH_MOD_PROXY, "!UL", tkptr->ResponseContentLength); /* do not copy this header until we are sure we don't deflate */ /* save pointers to Content-Length header for subsequent use */ ContentLengthPtr = cptr; while (cptr < lzptr) cptr++; if (*cptr == '\r' && cptr < hzptr) cptr++; if (*cptr == '\n' && cptr < hzptr) cptr++; ContentLengthEndPtr = cptr; continue; } else if (strsame (cptr, "Content-Encoding:", 17)) { /********************/ /* content-encoding */ /********************/ tcptr = cptr + 17; while (ISLWS(*tcptr) && tcptr < lzptr) tcptr++; if (strsame (tcptr, "gzip", 4)) tkptr->ResponseContentEncodingGzip = true; else tkptr->ResponseContentEncodingUnknown = true; if (WATCHMOD (tkptr, WATCH_MOD_PROXY)) WatchThis (WATCHITM(tkptr), WATCH_MOD_PROXY, "!&B", tkptr->ResponseContentEncodingGzip); /* drop through to copy this header */ } else if (strsame (cptr, "Content-Type:", 13)) { /****************/ /* content-type */ /****************/ tcptr = cptr + 13; while (ISLWS(*tcptr) && tcptr < lzptr) tcptr++; ContentTypePtr = tcptr; if (*tcptr == 'm' && strsame (tcptr, "multipart/", 10)) tkptr->ResponseContentTypeMultipart = true; #if WATCH_MOD else if (*tcptr == 't' && strsame (tcptr, "text/", 5)) tkptr->ResponseContentTypeText = true; #endif /* WATCH_MOD */ if (WATCHMOD (tkptr, WATCH_MOD_PROXY)) WatchThis (WATCHITM(tkptr), WATCH_MOD_PROXY, "!&B", tkptr->ResponseContentTypeMultipart); /* drop through to copy this header */ } else if (strsame (cptr, "Connection:", 11)) { /**************/ /* connection */ /**************/ cptr += 11; while (ISLWS(*cptr) && cptr < lzptr) cptr++; while (cptr < lzptr) { if (strsame (cptr, "close", 5)) { tkptr->ResponseConnectionClose = true; cptr += 5; } else if (strsame (cptr, "keep-alive", 10)) { tkptr->ResponseConnectionKeepAlive = true; cptr += 10; } while (!ISLWS(*cptr) && *cptr != ',' && cptr < lzptr) cptr++; while ((ISLWS(*cptr) || *cptr == ',') && cptr < lzptr) cptr++; } if (WATCHMOD (tkptr, WATCH_MOD_PROXY)) WatchThis (WATCHITM(tkptr), WATCH_MOD_PROXY, "!&B !&B", tkptr->ResponseConnectionClose, tkptr->ResponseConnectionKeepAlive); /* do not drop through, this header is not propagated */ while (cptr < lzptr) cptr++; if (*cptr == '\r' && cptr < hzptr) cptr++; if (*cptr == '\n' && cptr < hzptr) cptr++; continue; } } else if (TOUP(*cptr) == 'E' && strsame (cptr, "Expires:", 8)) { /***********/ /* expires */ /***********/ tcptr = cptr + 8; while (ISLWS(*tcptr) && tcptr < lzptr) tcptr++; tzptr = (tsptr = tkptr->ResponseExpires) + sizeof(tkptr->ResponseExpires); while (tcptr < lzptr && tsptr < tzptr) *tsptr++ = *tcptr++; if (tsptr >= tzptr) tsptr = tkptr->ResponseExpires; *tsptr = '\0'; status = HttpGmTime (tkptr->ResponseExpires, &tkptr->ResponseExpiresTime64); if (VMSnok (status)) { PUT_ZERO_QUAD (tkptr->ResponseExpiresTime64); tkptr->ResponseExpires[0] = '\0'; } if (WATCHMOD (tkptr, WATCH_MOD_PROXY)) WatchThis (WATCHITM(tkptr), WATCH_MOD_PROXY, "!&Z !&S !%D", tkptr->ResponseExpires, status, &tkptr->ResponseExpiresTime64); /* drop through to copy this header */ } else if (TOUP(*cptr) == 'K' && strsame (cptr, "Keep-Alive:", 11)) { /**************/ /* keep-alive */ /**************/ /* just note the presence of the field */ tkptr->ResponseKeepAlive = true; /* do not drop through, this header is not propagated */ cptr += 11; while (cptr < lzptr) cptr++; if (*cptr == '\r' && cptr < hzptr) cptr++; if (*cptr == '\n' && cptr < hzptr) cptr++; continue; } else if (TOUP(*cptr) == 'L') { if (strsame (cptr, "Last-Modified:", 14)) { /*****************/ /* last-modified */ /*****************/ tcptr = cptr + 14; while (ISLWS(*tcptr) && tcptr < lzptr) tcptr++; tzptr = (tsptr = tkptr->ResponseLastModified) + sizeof(tkptr->ResponseLastModified); while (tcptr < lzptr && tsptr < tzptr) *tsptr++ = *tcptr++; if (tsptr >= tzptr) tsptr = tkptr->ResponseLastModified; *tsptr = '\0'; status = HttpGmTime (tkptr->ResponseLastModified, &tkptr->ResponseLastModifiedTime64); if (VMSnok (status)) { PUT_ZERO_QUAD (tkptr->ResponseLastModifiedTime64); tkptr->ResponseLastModified[0] = '\0'; } if (WATCHMOD (tkptr, WATCH_MOD_PROXY)) WatchThis (WATCHITM(tkptr), WATCH_MOD_PROXY, "!&Z !&S !%D", tkptr->ResponseLastModified, status, &tkptr->ResponseLastModifiedTime64); /* drop through to copy this header */ } else /* rewrite only if necessary, otherwise fall through to copy */ if (strsame (cptr, "Location:", 9) && rqptr->ProxyReverseLocationPtr && (tkptr->ResponseStatusCode == 301 || tkptr->ResponseStatusCode == 302)) { /************/ /* location */ /************/ cptr += 9; while (ISLWS(*cptr) && cptr < lzptr) cptr++; tcptr = "Location: "; while (*tcptr && sptr < zptr) *sptr++ = *tcptr++; if (tcptr = ProxyRebuildLocation (tkptr, cptr)) { /* the location was rebuilt */ while (*tcptr && sptr < zptr) *sptr++ = *tcptr++; if (sptr < zptr) *sptr++ = '\r'; if (sptr < zptr) *sptr++ = '\n'; /* do not drop through, this header is not propagated as-is */ while (cptr < lzptr) cptr++; if (*cptr == '\r' && cptr < hzptr) cptr++; if (*cptr == '\n' && cptr < hzptr) cptr++; continue; } /* else drop through to copy this header */ } } else if (TOUP(*cptr) == 'P') { if (strsame (cptr, "Pragma:", 7)) { /**********/ /* pragma */ /**********/ cptr += 7; while (ISLWS(*cptr) && cptr < lzptr) cptr++; if (strsame (cptr, "no-cache", 8)) tkptr->ResponseNoCache = true; if (WATCHMOD (tkptr, WATCH_MOD_PROXY)) WatchThis (WATCHITM(tkptr), WATCH_MOD_PROXY, "!&B", tkptr->ResponseNoCache); /* do not drop through, this header is not propagated */ while (cptr < lzptr) cptr++; if (*cptr == '\r' && cptr < hzptr) cptr++; if (*cptr == '\n' && cptr < hzptr) cptr++; continue; } else if (strsame (cptr, "Public:", 7)) { /**********/ /* public */ /**********/ cptr += 7; while (ISLWS(*cptr) && cptr < lzptr) cptr++; /* do not drop through, this header is not propagated */ while (cptr < lzptr) cptr++; if (*cptr == '\r' && cptr < hzptr) cptr++; if (*cptr == '\n' && cptr < hzptr) cptr++; continue; } } else if (TOUP(*cptr) == 'T' && strsame (cptr, "Transfer-Encoding:", 18)) { /*********************/ /* transfer-encoding */ /*********************/ tcptr = cptr + 18; while (ISLWS(*tcptr) && tcptr < lzptr) tcptr++; if (strsame (tcptr, "chunked", 7)) tkptr->ResponseTransferEncodingChunked = true; else tkptr->ResponseTransferEncodingUnsupported = true; if (WATCHMOD (tkptr, WATCH_MOD_PROXY)) WatchThis (WATCHITM(tkptr), WATCH_MOD_PROXY, "!&B !&B", tkptr->ResponseTransferEncodingChunked, tkptr->ResponseTransferEncodingUnsupported); /* drop through to copy this header */ } else if (TOUP(*cptr) == 'U' && strsame (cptr, "Upgrade:", 8)) { /**********************/ /* upgrade: websocket */ /**********************/ tcptr = cptr + 8; while (ISLWS(*tcptr) && tcptr < lzptr) tcptr++; if (strsame (tcptr, "websocket", 9)) tkptr->ResponseUpgradeWebSocket = true; /* drop through to copy this header */ } else if (TOUP(*cptr) == 'V' && strsame (cptr, "Vary:", 5)) { /********/ /* vary */ /********/ tcptr = cptr + 5; while (ISLWS(*tcptr) && tcptr < lzptr) tcptr++; while (tcptr < lzptr) { if (*tcptr == '*') { /* something not related to header fields */ tkptr->ResponseVaryUnsupported = true; tcptr++; } else if (strsame (tcptr, "accept", 6) && !strsame (tcptr, "accept-encoding", 15)) { /* "Accept:", "Accept-Charset:, all-but, etc. */ tkptr->ResponseVaryUnsupported = true; tcptr += 6; } while (!ISLWS(*tcptr) && *tcptr != ',' && tcptr < lzptr) tcptr++; while ((ISLWS(*tcptr) || *tcptr == ',') && tcptr < lzptr) tcptr++; } if (WATCHMOD (tkptr, WATCH_MOD_PROXY)) WatchThis (WATCHITM(tkptr), WATCH_MOD_PROXY, "!&B", tkptr->ResponseVaryUnsupported); /* drop through to copy this header */ } /* got all the way down here, copy the header into the rebuilt buffer */ while (cptr < lzptr && sptr < zptr) *sptr++ = *cptr++; if (*cptr == '\r' && cptr < hzptr && sptr < zptr) *sptr++ = *cptr++; if (*cptr == '\n' && cptr < hzptr && sptr < zptr) *sptr++ = *cptr++; } /*********************/ /* add proxy headers */ /*********************/ if (tkptr->ResponseStatusCode != 100) { /*******/ /* age */ /*******/ /* if this header is coming from a cache file, use last loaded time */ if (cfqptr->QioChannel && /* interim v6.0 backward compatibility (didn't use BDT) */ QUAD_NOT_ZERO(cfqptr->BdtTime64)) Time64Ptr = &cfqptr->BdtTime64; else /* if an age field was supplied with the original header */ if (AgeSeconds >= 0) Time64Ptr = NULL; else /* if there's a last modified supplied with the response */ if (QUAD_NOT_ZERO(tkptr->ResponseLastModifiedTime64)) Time64Ptr = &tkptr->ResponseLastModifiedTime64; else /* otherwise just use the current time */ { Time64Ptr = NULL; AgeSeconds = 0; } if (Time64Ptr) AgeSeconds = ProxyCacheDeltaSeconds (&HttpdTime64, Time64Ptr); if (AgeSeconds < 0) AgeSeconds = 0; sys$fao (&AgeFieldFaoDsc, 0, &AgeFieldDsc, AgeSeconds); for (cptr = AgeField; *cptr && sptr < zptr; *sptr++ = *cptr++); /*************/ /* compress? */ /*************/ if (!tkptr->ResponseContentEncodingGzip && !tkptr->ResponseContentEncodingUnknown && !tkptr->ResponseTransferEncodingChunked) { if (GzipResponse) rqptr->rqResponse.ContentEncodeAsGzip = GzipShouldDeflate (rqptr, ContentTypePtr, tkptr->ResponseContentLength); if (rqptr->rqResponse.ContentEncodeAsGzip) { for (cptr = "Content-Encoding: gzip\r\n"; *cptr && sptr < zptr; *sptr++ = *cptr++); if (tkptr->ResponseHttpVersion == HTTP_VERSION_1_1 && tkptr->RequestHttpVersion == HTTP_VERSION_1_1) { rqptr->rqResponse.TransferEncodingChunked = true; for (cptr = "Transfer-Encoding: chunked\r\n"; *cptr && sptr < zptr; *sptr++ = *cptr++); } } else if (ContentLengthPtr) { /* we don't deflate, so output any Content-Length header */ for (cptr = ContentLengthPtr; cptr < ContentLengthEndPtr && sptr < zptr; *sptr++ = *cptr++); } } else if (ContentLengthPtr) { /* we don't deflate, so output Content-Length header */ for (cptr = ContentLengthPtr; cptr < ContentLengthEndPtr && sptr < zptr; *sptr++ = *cptr++); } /**************/ /* connection */ /**************/ /* MUST follow the COMPRESS? decision immediately above */ /* special case, 304's SHALL NOT contain a body (the only one AFICT) */ if (tkptr->ResponseStatusCode == 304 && tkptr->ResponseContentLength < 0) { /* to make persistence work just assume a content-length of 0 */ tkptr->ResponseContentLength = 0; } if (tkptr->RequestHttpVersion == HTTP_VERSION_1_1) { /* proxy->origin persistence */ if (tkptr->PersistentRequest && (tkptr->ResponseContentLength >= 0 || tkptr->ResponseTransferEncodingChunked) && !tkptr->ResponseConnectionClose && !rqptr->rqResponse.ContentEncodeAsGzip) tkptr->PersistentResponse = true; else tkptr->PersistentResponse = false; /* client->proxy persistence */ if (tkptr->PersistentRequest && tkptr->PersistentResponse) { rqptr->PersistentResponse = true; /* not strictly required but many HTTP/1.1 servers supply it */ for (cptr = "Connection: keep-alive\r\n"; *cptr && sptr < zptr; *sptr++ = *cptr++); } else { rqptr->PersistentResponse = false; for (cptr = "Connection: close\r\n"; *cptr && sptr < zptr; *sptr++ = *cptr++); } } else if (tkptr->RequestHttpVersion == HTTP_VERSION_1_0) { /* proxy->origin persistence */ if (tkptr->PersistentRequest && (tkptr->ResponseContentLength >= 0 || tkptr->ResponseTransferEncodingChunked) && (tkptr->ResponseKeepAlive || tkptr->ResponseConnectionKeepAlive) && !tkptr->ResponseConnectionClose && !rqptr->rqResponse.ContentEncodeAsGzip) tkptr->PersistentResponse = true; else tkptr->PersistentResponse = false; /* client->proxy persistence */ if (tkptr->PersistentRequest && tkptr->PersistentResponse) { rqptr->PersistentResponse = true; for (cptr = "Connection: keep-alive\r\n"; *cptr && sptr < zptr; *sptr++ = *cptr++); if (rqptr->rqPathSet.TimeoutPersistent) KeepAliveSeconds = rqptr->rqPathSet.TimeoutPersistent; else KeepAliveSeconds = Config.cfTimeout.Persistent; sys$fao (&KeepAliveFieldFaoDsc, 0, &KeepAliveFieldDsc, KeepAliveSeconds); for (cptr = KeepAliveField; *cptr && sptr < zptr; *sptr++ = *cptr++); } else rqptr->PersistentResponse = false; } /***************************/ /* proxy generated cookies */ /***************************/ for (idx = 0; idx < RESPONSE_COOKIE_MAX; idx++) { if (rqptr->rqResponse.CookiePtr[idx]) { for (cptr = "Set-Cookie: "; *cptr && sptr < zptr; *sptr++ = *cptr++); for (cptr = rqptr->rqResponse.CookiePtr[idx]; *cptr && sptr < zptr; *sptr++ = *cptr++); if (sptr < zptr) *sptr++ = '\r'; if (sptr < zptr) *sptr++ = '\n'; } } } /* header terminating empty line */ if (sptr < zptr) *sptr++ = '\r'; if (sptr < zptr) *sptr++ = '\n'; if (sptr >= zptr) { ErrorNoticed (rqptr, SS$_BUGCHECK, ErrorSanityCheck, FI_LI); rqptr->rqResponse.HttpStatus = 500; ErrorGeneralOverflow (rqptr, FI_LI); return (STS$K_ERROR); } *sptr = '\0'; RebuiltHeaderLength = sptr - RebuiltHeaderPtr; if (WATCHING (tkptr, WATCH_PROXY_RESP_HDR)) { WatchThis (WATCHITM(rqptr), WATCH_PROXY_RESP_HDR, "RESPONSE HEADER proxy->client !UL bytes", RebuiltHeaderLength); WatchData (RebuiltHeaderPtr, RebuiltHeaderLength); } tkptr->RebuiltResponsePtr = RebuiltHeaderPtr; tkptr->RebuiltResponseLength = RebuiltHeaderLength; return (SS$_NORMAL); } /****************************************************************************/ #ifdef COMMENTS_WITH_COMMENTS /* The reverse-proxy response header "Location:" field needs to be rewritten. The original mapping rule would have been something like redirect /foo/* /http://foo.bar/* proxy=reverse=location=/foo/ Check the "Location:" field for the proxied-to host name and if so rewrite it to use the proxy server and the path indicated. If the location field does not contain the proxied-to host name then assume it's not being redirected back to the same service. If the 'proxy=reverse=location=' ends in an asterisk the entire 302 "Location:" URL is appended (rather than just the path) resulting in something along the lines of Location: http://original.host/foo/http://foo.bar/example/ which once redirected by the client can be subsequently tested for and some action made according to the content (just a bell or whistle ;^). The original scheme, host and 'proxy=reverse=location=' information is conveyed across the reverse-proxy redirect using redirect-persistent storage. Returns a pointer to a dynamic buffer containing the new location string or NULL if no rebuild was necesary. */ #endif /* COMMENTS_WITH_COMMENTS */ char* ProxyRebuildLocation ( PROXY_TASK *tkptr, char *LocationPtr ) { boolean RebuildLocation; int len; char *cptr, *lptr, *sptr, *zptr; REQUEST_STRUCT *rqptr; /*********/ /* begin */ /*********/ if (WATCHMOD (tkptr, WATCH_MOD_PROXY)) WatchThis (WATCHITM(tkptr), WATCH_MOD_PROXY, "ProxyRebuildLocation() !&Z", tkptr->RequestPtr->ProxyReverseLocationPtr); rqptr = tkptr->RequestPtr; for (cptr = rqptr->ProxyReverseLocationPtr; *cptr; *cptr++); if (cptr > rqptr->ProxyReverseLocationPtr) cptr--; if (*cptr == '*') { /* ends in a wildcard, use whatever is in the location field */ RebuildLocation = true; } else { RebuildLocation = false; lptr = LocationPtr; while (*lptr && *lptr != ':' && NOTEOL(*lptr)) lptr++; if (*lptr == ':') lptr++; if (*lptr == '/') lptr++; if (*lptr == '/') lptr++; /* this will contain the redirect-to/proxied-to host */ sptr = rqptr->rqHeader.HostPtr; while (*sptr && *lptr && TOLO(*sptr) == TOLO(*lptr)) { sptr++; lptr++; } if (!*sptr && (!*lptr || *lptr == '/')) RebuildLocation = true; } if (WATCHMOD (tkptr, WATCH_MOD_PROXY)) WatchThis (WATCHITM(tkptr), WATCH_MOD_PROXY, "!&?REBUILD\rNO REBUILD\r", RebuildLocation); if (!RebuildLocation) return (NULL); LocationPtr = sptr = VmGetHeap (rqptr, PROXY_LOCATION_REBUILD_BUFFER); zptr = sptr + PROXY_LOCATION_REBUILD_BUFFER; for (cptr = rqptr->ProxyReverseLocationPtr; *cptr && sptr < zptr; *sptr++ = *cptr++); /* finished with this now */ rqptr->ProxyReverseLocationPtr = NULL; /* prevent consecutive slashes */ if (*(sptr-1) == '/' && *lptr == '/') lptr++; while (*lptr && !ISLWS(*lptr) && NOTEOL(*lptr) && sptr < zptr) *sptr++ = *lptr++; if (sptr >= zptr) { ErrorNoticed (rqptr, SS$_RESULTOVF, NULL, FI_LI); strcpy (LocationPtr, "error-buffer-overflow!"); return (LocationPtr); } *sptr = '\0'; if (WATCHMOD (tkptr, WATCH_MOD_PROXY)) WatchDataFormatted ("!&Z", LocationPtr); return (LocationPtr); } /****************************************************************************/ /* Using the request header fields rebuild a request header suitable for use by the proxy server. */ int ProxyRequestRebuild (PROXY_TASK *tkptr) { #define STRCAT(str) for (cptr = str; *cptr && sptr < zptr; *sptr++ = *cptr++); #define CHRCAT(ch) { if (sptr < zptr) *sptr++ = ch; } static char KeepAliveField [32]; static $DESCRIPTOR (KeepAliveFieldDsc, KeepAliveField); static $DESCRIPTOR (KeepAliveFieldFaoDsc, "Keep-Alive: timeout=!UL\r\n\0"); BOOL ForwardedBy, XForwardedFor; int idx, klen, HeaderSize, KeepAliveSeconds; char *aptr, *cptr, *kptr, *sptr, *zptr; char EncodedString [256]; DICT_ENTRY_STRUCT *denptr; REQUEST_STRUCT *rqptr; /*********/ /* begin */ /*********/ if (WATCHMOD (tkptr, WATCH_MOD_PROXY)) WatchThis (WATCHITM(tkptr), WATCH_MOD_PROXY, "ProxyRequestRebuild()"); rqptr = tkptr->RequestPtr; tkptr->RebuiltRequestPtr = NULL; for (HeaderSize = 1024; HeaderSize < MAX_REQUEST_HEADER; HeaderSize *= 2) { if (tkptr->RebuiltRequestPtr) VmFreeFromHeap (rqptr, tkptr->RebuiltRequestPtr, FI_LI); tkptr->RebuiltRequestPtr = sptr = VmGetHeap (rqptr, HeaderSize); zptr = sptr + HeaderSize; STRCAT (rqptr->rqHeader.MethodName) CHRCAT (' ') if (IPADDRESS_IS_SET (&tkptr->ChainIpAddress)) { STRCAT ("http://") STRCAT (tkptr->RequestHostPort) } STRCAT (tkptr->RequestUriPtr) if (tkptr->RequestHttpVersion == HTTP_VERSION_0_9) { /*******************/ /* HTTP/0.9 header */ /*******************/ /* just end the line (and header) without an HTTP protocol version */ STRCAT ("\r\n") } else { /*******************/ /* HTTP/1.n header */ /*******************/ if (rqptr->rqHeader.HttpVersion == HTTP_VERSION_1_1) STRCAT (" HTTP/1.1\r\n") else STRCAT (" HTTP/1.0\r\n") /******************/ /* request fields */ /******************/ /* add (with some massage) the original request's header fields */ DictIterate (rqptr->rqDictPtr, NULL); while (denptr = DictIterate (rqptr->rqDictPtr, DICT_TYPE_REQUEST)) { cptr = kptr = DICT_GET_KEY(denptr); if (TOUP(*cptr) == 'A') { /* match including the terminating null */ if (MATCH14 (cptr, "authorization") || strsame (cptr, "Authorization", -1)) { if (rqptr->rqPathSet.ProxyReverseNoAuthHeader) continue; if (rqptr->rqPathSet.ProxyReverseVerify) { /* (very) special case */ ProxyVerifyRecordSet (tkptr); if (tkptr->VerifyRecordPtr) { STRCAT ("Authorization: Basic ") STRCAT (tkptr->VerifyRecordPtr->AuthorizationString) STRCAT ("\r\n") } else { /* busy indicates increase [ProxyVerifyRecordMax] */ rqptr->rqResponse.HttpStatus = 503; ErrorGeneral (rqptr, MsgFor(rqptr,MSG_GENERAL_TOO_BUSY), FI_LI); return (STS$K_ERROR); } continue; } /* inhibits caching (unless "cache-control: public") */ tkptr->RequestAuthorization = true; } } /* Hop-by-hop header fields ... For strict HTTP/1.1 compliance the following header fields are also hop-by-hop; "TE:", "Trailers:", "Transfer-Encoding:". However WASD transfers chunked request bodies directly to the proxied server and does not perform any re-encoding. For the upgrade header field it is just suppressed as WASD does not support changing protocols let alone HTTP-incompatible ones. */ if (TOUP(*cptr) == 'C' && (MATCH11 (cptr, "connection") || strsame (cptr, "Connection", -1))) { /* proxied WebSocket requests use connection upgrade */ if (!rqptr->WebSocketRequest) continue; } if (TOUP(*cptr) == 'K' && (MATCH11 (cptr, "keep-alive") || strsame (cptr, "Keep-Alive", -1))) continue; if (TOUP(*cptr) == 'H') { /* replace "Host:" field */ if (MATCH5 (cptr, "host") && strsame (cptr, "host", -1)) { STRCAT ("Host: ") STRCAT (tkptr->RequestHostPort) STRCAT ("\r\n") continue; } } if (TOUP(*cptr) == 'P') { /* absorb proxy authentication credentials unless CHAIN */ if (MATCH16 (cptr, "proxy-authorization") && strsame (cptr, "Proxy-Authorization", -1)) { if (!rqptr->ServicePtr->ProxyChainAuthRequired || rqptr->rqPathSet.ProxyChainCredPtr) continue; } if (MATCH16 (cptr, "proxy-connection") && strsame (cptr, "Proxy-Connection", -1)) continue; } if (TOUP(*cptr) == 'U' && (MATCH8 (cptr, "upgrade") || strsame (cptr, "Upgrade", -1))) { /* proxied WebSocket requests use connection upgrade */ if (!rqptr->WebSocketRequest) continue; } if (rqptr->rqPathSet.ProxyHeaderCount) { klen = strlen(kptr); for (idx = 0; idx < rqptr->rqPathSet.ProxyHeaderCount; idx++) { for (aptr = rqptr->rqPathSet.ProxyHeader[idx]; *aptr && *aptr != '='; aptr++); /* if not this header then just on to the next */ if (aptr - rqptr->rqPathSet.ProxyHeader[idx] != klen) continue; if (!strsame (rqptr->rqPathSet.ProxyHeader[idx], kptr, klen)) continue; break; } if (idx < rqptr->rqPathSet.ProxyHeaderCount) { /* if no parameter then exclude the header */ if (!*aptr) continue; /* else set header value to the parameter (even if empty) */ aptr++; } else aptr = NULL; } else aptr = NULL; /* otherwise, include this field */ while (*cptr && sptr < zptr) { /* make it look more like an HTTP/1.1 request by capitalising */ if (cptr == kptr || *(cptr-1) == '-') *sptr++ = TOUP(*cptr++); else *sptr++ = *cptr++; } STRCAT (": ") if ((cptr = aptr) == NULL) cptr = DICT_GET_VALUE(denptr); STRCAT (cptr) STRCAT ("\r\n") } if ((cptr = rqptr->ServicePtr->ProxyChainCred)[0] || (cptr = rqptr->rqPathSet.ProxyChainCredPtr)) { /* upstream (chained) proxy require authorization */ if (strsame (cptr, "basic:", 6)) { /* format is "basic::" */ cptr = BasicPrintableEncode (cptr+6, EncodedString, sizeof(EncodedString)); if (cptr[0]) { /* error report returned by the encode function */ rqptr->rqResponse.HttpStatus = 401; ErrorGeneral (rqptr, cptr, FI_LI); return (STS$K_ERROR); } STRCAT ("Proxy-Authorization: basic "); STRCAT (EncodedString); STRCAT ("\r\n") } else if (strsame (cptr, "opaque:", 7)) { STRCAT ("Proxy-Authorization: "); STRCAT (cptr + 7); STRCAT ("\r\n") } else { rqptr->rqResponse.HttpStatus = 500; ErrorGeneral (rqptr, MsgFor(rqptr,MSG_AUTH_SCHEME), FI_LI); return (STS$K_ERROR); } } /* proxy to remote server connection persistence */ if (tkptr->PersistentRequest) { STRCAT ("Connection: keep-alive\r\n") if (rqptr->rqPathSet.TimeoutPersistent) KeepAliveSeconds = rqptr->rqPathSet.TimeoutPersistent; else KeepAliveSeconds = Config.cfTimeout.Persistent; sys$fao (&KeepAliveFieldFaoDsc, 0, &KeepAliveFieldDsc, KeepAliveSeconds); STRCAT (KeepAliveField) } else STRCAT ("Connection: close\r\n") /*******************************/ /* this proxy's proxied fields */ /*******************************/ ForwardedBy = ProxyForwardedBy; if (rqptr->rqPathSet.ProxyForwardedBy) { if (rqptr->rqPathSet.ProxyForwardedBy == PROXY_FORWARDED_NONE) ForwardedBy = PROXY_FORWARDED_DISABLED; else ForwardedBy = rqptr->rqPathSet.ProxyForwardedBy; } if (ForwardedBy) { STRCAT ("Forwarded: ") if (ForwardedBy == PROXY_FORWARDED_BY || ForwardedBy == PROXY_FORWARDED_FOR || ForwardedBy == PROXY_FORWARDED_ADDRESS) { STRCAT ("by http://") if (rqptr->ServicePtr->ServerPort == 80) STRCAT (rqptr->ServicePtr->ServerHostName) else STRCAT (rqptr->ServicePtr->ServerHostPort) STRCAT (" (") STRCAT (SoftwareID) CHRCAT (')') } if (ForwardedBy == PROXY_FORWARDED_FOR) { STRCAT (" for ") STRCAT (rqptr->ClientPtr->Lookup.HostName) } else if (ForwardedBy == PROXY_FORWARDED_ADDRESS) { STRCAT (" for ") STRCAT (&rqptr->ClientPtr->IpAddressString) } if (rqptr->rqHeader.ForwardedPtr) { if (ForwardedBy == PROXY_FORWARDED_BY || ForwardedBy == PROXY_FORWARDED_FOR || ForwardedBy == PROXY_FORWARDED_ADDRESS) STRCAT (", ") STRCAT (rqptr->rqHeader.ForwardedPtr) } STRCAT ("\r\n") } XForwardedFor = ProxyXForwardedFor; if (rqptr->rqPathSet.ProxyXForwardedFor) { if (rqptr->rqPathSet.ProxyXForwardedFor == PROXY_XFORWARDEDFOR_NONE) XForwardedFor = PROXY_XFORWARDEDFOR_DISABLED; else XForwardedFor = rqptr->rqPathSet.ProxyXForwardedFor; } if (XForwardedFor) { STRCAT ("X-Forwarded-For: ") if (rqptr->rqHeader.XForwardedForPtr) { STRCAT (rqptr->rqHeader.XForwardedForPtr) STRCAT (", ") } if (XForwardedFor == PROXY_XFORWARDEDFOR_ENABLED) STRCAT (rqptr->ClientPtr->Lookup.HostName) else if (XForwardedFor == PROXY_XFORWARDEDFOR_ADDRESS) STRCAT (&rqptr->ClientPtr->IpAddressString) else STRCAT ("unknown") STRCAT ("\r\n") } /**************************/ /* end of HTTP/1.n header */ /**************************/ /* terminate last line, end-of-header empty line */ STRCAT ("\r\n") } if (sptr < zptr) break; } if (sptr >= zptr) { rqptr->rqResponse.HttpStatus = 500; ErrorGeneralOverflow (rqptr, FI_LI); return (STS$K_ERROR); } *sptr = '\0'; tkptr->RebuiltRequestLength = sptr - tkptr->RebuiltRequestPtr; if (WATCHMOD (tkptr, WATCH_MOD_PROXY)) WatchDataFormatted ("!&Z\n", tkptr->RebuiltRequestPtr); return (SS$_NORMAL); #undef STRCAT #undef CHRCAT } /****************************************************************************/