/*****************************************************************************/ /* ProxyRework.c The proxy rework facility will modify a target string to a replacement string in the request header (e.g. Host:), the response header (e.g. set-cookie:), and in the response body. Rework will be applied to HTML and CSS responses. These are simple string matches. CONFIGURATION ------------- The proxy rework facility must be enabled for a service by setting a maximum size for the HTML response body to be reworked, in kB. # WASD_CONFIG_SERVICE [[*.1924]] [ServiceReworkMax] 128 Specific paths must then be SET in WASD_CONFIG_MAP to have proxy requests reworked. # WASD_CONFIG_MAP [[*:1924]] set * proxy=rework=192.168.1.3=192.168.1.2 VERSION HISTORY --------------- 26-FEB-2021 MGD initial */ /*****************************************************************************/ #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 /* VMS related header files */ #include #include #include #include /* application related header files */ #include "wasd.h" #define WASD_MODULE "PROXYREWORK" /******************/ /* global storage */ /******************/ int ProxyReworkMax; /********************/ /* external storage */ /********************/ extern int ProxyReadBufferSize; extern int ToLowerCase[], ToUpperCase[]; extern PROXY_ACCOUNTING_STRUCT *ProxyAccountingPtr; extern WATCH_STRUCT Watch; /*****************************************************************************/ /* Initialise the buffering of a response to be reworked. Then manage that buffering as required during the response. Return |true| to continue, |false| not to. */ BOOL ProxyReworkResponse (PROXY_TASK *tkptr) { int count, idx, size; char *cptr, *ctptr; REQUEST_STRUCT *rqptr; /*********/ /* begin */ /*********/ rqptr = tkptr->RequestPtr; if (WATCHMOD (tkptr, WATCH_MOD_PROXY)) WatchThis (WATCHITM(tkptr), WATCH_MOD_PROXY, "ProxyResponseRework() !UL !UL", tkptr->ResponseBodyLength, tkptr->ResponseContentLength); if (!tkptr->ResponseReworkInit) { /*********************/ /* initialise rework */ /*********************/ tkptr->ResponseReworkInit = true; ProxyAccountingPtr->ReworkCount++; if (!tkptr->ResponseContentTypePtr) { /* no content-type in proxied HTTP response - look for a file type */ for (cptr = rqptr->rqHeader.PathInfoPtr; *cptr; cptr++); while (cptr > rqptr->rqHeader.PathInfoPtr && *cptr != '/' && *cptr != '.') cptr--; if (*cptr == '.' && *(cptr+1)) { /* a file extension (perhaps) - try to map to content type */ ctptr = ConfigContentType (NULL, cptr); if (WATCHING (tkptr, WATCH_PROXY_REWORK)) WatchThis (WATCHITM(tkptr), WATCH_PROXY_REWORK, "TYPE !AZ !AZ", cptr, ctptr); tkptr->ResponseContentTypePtr = ctptr; } } if (!tkptr->ResponseContentTypePtr || MATCH9(tkptr->ResponseContentTypePtr, "text/html") || MATCH8(tkptr->ResponseContentTypePtr, "text/css")) { /* unknown (we'll sniff) or reworkable content */ tkptr->ResponseReworkMax = rqptr->ServicePtr->ProxyReworkMax; ProxyReworkBuffer (tkptr); } else return (true); /* initialise the host1=host2 data */ size = strlen(rqptr->rqPathSet.ProxyReworkPtr); cptr = VmGetHeap (rqptr, size); strcpy (cptr, rqptr->rqPathSet.ProxyReworkPtr); for (idx = 0; idx < 4 && *cptr; idx++) { tkptr->ReworkPtr1[idx] = cptr; while (*cptr && *cptr != '=') cptr++; tkptr->ReworkLen1[idx] = cptr - tkptr->ReworkPtr1[idx]; if (*cptr) *cptr++ = '\0'; for (tkptr->ReworkPtr2[idx] = cptr; *cptr && *cptr != '$'; cptr++); tkptr->ReworkLen2[idx] = cptr - tkptr->ReworkPtr2[idx]; if (*cptr) *cptr++ = '\0'; if (WATCHING (tkptr, WATCH_PROXY_REWORK)) WatchThis (WATCHITM(tkptr), WATCH_PROXY_REWORK, "REPLACE !&Z with !&Z", tkptr->ReworkPtr1[idx], tkptr->ReworkPtr2[idx]); } } if (tkptr->ResponseBufferSize > tkptr->ResponseReworkMax * 1024) { /***********/ /* too big */ /***********/ if (WATCHING (tkptr, WATCH_PROXY_REWORK)) WatchThis (WATCHITM(tkptr), WATCH_PROXY_REWORK, "REWORK abort !UL (!UL) exceeds !UL kB", tkptr->ResponseBufferSize / 1024, tkptr->ResponseBufferSize, tkptr->ResponseReworkMax); ProxyAccountingPtr->ReworkTooBig++; tkptr->ResponseStatusCode = 500; ErrorGeneralOverflow (rqptr, FI_LI); return (false); } if (!tkptr->ResponseContentTypePtr) { /************************/ /* content type unknown */ /************************/ if (tkptr->ResponseBodyLength) { ProxyAccountingPtr->ReworkNoType++; /* we (perhaps now) have some body to sniff */ ProxyReworkSniffHtml (tkptr); if (!tkptr->ResponseContentTypePtr) { /***************/ /* quit rework */ /***************/ if (WATCHING (tkptr, WATCH_PROXY_REWORK)) WatchThis (WATCHITM(tkptr), WATCH_PROXY_REWORK, "NO distinctive ODOUR"); /* not reworkable so go back to just proxying the content */ tkptr->ResponseReworkMax = 0; tkptr->ResponseBufferNetPtr = tkptr->ResponseBufferPtr; tkptr->ResponseBufferNetCount = tkptr->ResponseBodyLength; ProxyResponseNetWrite (tkptr); return (true); } } } count = tkptr->ResponseBufferCurrentPtr - tkptr->ResponseBufferPtr; if (tkptr->ResponseBufferSize - count < ProxyReadBufferSize / 2) { /*****************/ /* expand buffer */ /*****************/ tkptr->ResponseBufferSize += ProxyReadBufferSize; tkptr->ResponseBufferPtr = VmReallocHeap (rqptr, tkptr->ResponseBufferPtr, tkptr->ResponseBufferSize, FI_LI); tkptr->ResponseBufferCurrentPtr = tkptr->ResponseBufferPtr + count; tkptr->ResponseBufferRemaining = tkptr->ResponseBufferSize - count; } return (true); } /*****************************************************************************/ /* Adjust the network buffer as required. */ void ProxyReworkBuffer (PROXY_TASK *tkptr) { int len; char *cbptr, *cptr, *ctptr; REQUEST_STRUCT *rqptr; /*********/ /* begin */ /*********/ rqptr = tkptr->RequestPtr; if (WATCHMOD (rqptr, WATCH_MOD_PROXY)) WatchThis (WATCHITM(rqptr), WATCH_MOD_PROXY, "ProxyReworkBuffer()"); if (tkptr->ResponseContentLength) { /* response has specified a content length */ if (tkptr->ResponseContentLength / 1024 > tkptr->ResponseReworkMax) { /* exceeds maximum size - will be reported by caller */ tkptr->ResponseBufferSize = tkptr->ResponseContentLength; return; } } if (tkptr->ResponseContentLength > tkptr->ResponseBufferSize) { /* allocate buffer based on specified content-length */ cbptr = tkptr->ResponseBufferPtr; tkptr->ResponseBufferSize = tkptr->ResponseContentLength; tkptr->ResponseBufferPtr = VmGetHeap (rqptr, tkptr->ResponseBufferSize); tkptr->ResponseBufferCurrentPtr = tkptr->ResponseBufferPtr; tkptr->ResponseBufferRemaining = tkptr->ResponseBufferSize; } else { /* continue to use the existing buffer and grow as required */ cbptr = NULL; tkptr->ResponseBufferCurrentPtr = tkptr->ResponseBufferPtr; tkptr->ResponseBufferRemaining = tkptr->ResponseBufferSize; } if (WATCHING (tkptr, WATCH_PROXY_REWORK)) WatchThis (WATCHITM(tkptr), WATCH_PROXY_REWORK, "REWORK !UL kB", tkptr->ResponseReworkMax); if (tkptr->ResponseBufferNetCount) { /* response body exists beyond the response header */ memcpy (tkptr->ResponseBufferPtr, tkptr->ResponseBufferNetPtr, tkptr->ResponseBufferNetCount); tkptr->ResponseBufferCurrentPtr = tkptr->ResponseBufferPtr + tkptr->ResponseBufferNetCount; tkptr->ResponseBufferRemaining = tkptr->ResponseBufferSize - tkptr->ResponseBufferNetCount; tkptr->ResponseBodyLength = tkptr->ResponseBufferNetCount; } else { /* use the entire buffer */ tkptr->ResponseBufferCurrentPtr = tkptr->ResponseBufferPtr; tkptr->ResponseBufferRemaining = tkptr->ResponseBufferSize; tkptr->ResponseBodyLength = 0; } if (cbptr) { /* dispose of the previous buffer */ VmFreeFromHeap (rqptr, cbptr, FI_LI); } } /*****************************************************************************/ /* The response header and body have been read and buffered. If the content-type is unknown (no header field) then first check if the request path had a file type (e.g. ".html") and try to determine the content-type from that. If it didn't then sniff the body for indications of HTML or CSS. If the body is suitable to rework peform that and return the reworked content. If not just return the body as-is. */ void ProxyReworkProcess (PROXY_TASK *tkptr) { int cnt, delta, idx, len, rblen; char *cptr, *ctptr; REQUEST_STRUCT *rqptr; /*********/ /* begin */ /*********/ rqptr = tkptr->RequestPtr; if (WATCHMOD (rqptr, WATCH_MOD_PROXY)) WatchThis (WATCHITM(rqptr), WATCH_MOD_PROXY, "ProxyReworkProcess()"); ProxyReworkHeader (tkptr); for (idx = 0; idx < 4; idx++) { if (!tkptr->ReworkPtr1[idx]) break; ProxyAccountingPtr->ReworkReplaceSearch++; if (WATCHING (tkptr, WATCH_PROXY_REWORK)) WatchThis (WATCHITM(tkptr), WATCH_PROXY_REWORK, "BODY replacing !&Z with !&Z", tkptr->ReworkPtr1[idx], tkptr->ReworkPtr2[idx]); rblen = tkptr->ResponseBodyLength; len = StringReplace (rqptr, &tkptr->ResponseBufferPtr, tkptr->ReworkPtr1[idx], tkptr->ReworkPtr2[idx]); if (len) { tkptr->ResponseBodyLength = len; delta = tkptr->ReworkLen1[idx] - tkptr->ReworkLen2[idx]; cnt = (len - rblen) / delta; if (cnt < 0) cnt = -cnt; if (WATCHMOD (rqptr, WATCH_MOD_PROXY)) WatchThis (WATCHITM(rqptr), WATCH_MOD_PROXY, "!SL !UL-!UL=!SL !UL", delta, len, rblen, len-rblen, cnt); if (WATCHING (tkptr, WATCH_PROXY_REWORK)) WatchThis (WATCHITM(tkptr), WATCH_PROXY_REWORK, "BODY !UL replaced", cnt); ProxyAccountingPtr->ReworkReplaceCount += cnt; } else if (WATCHING (tkptr, WATCH_PROXY_REWORK)) WatchThis (WATCHITM(tkptr), WATCH_PROXY_REWORK, "BODY 0 replaced"); } if (WATCHMOD (tkptr, WATCH_MOD_PROXY)) { if ((len = strlen(tkptr->ResponseContentTypePtr)) > 15) len = 15; WatchThis (WATCHITM(tkptr), WATCH_MOD_PROXY, "!#AZ !UL !UL !UL", len, tkptr->ResponseContentTypePtr, tkptr->ResponseBufferSize, tkptr->ResponseBodyLength, tkptr->ResponseContentLength); WatchDataDump (tkptr->ResponseBufferPtr, tkptr->ResponseBodyLength); } tkptr->ResponseReworkMax = 0; /* ..ResponseBufferNet.. specify what is sent to the client */ tkptr->ResponseBufferNetPtr = tkptr->ResponseBufferPtr; tkptr->ResponseBufferNetCount = tkptr->ResponseBodyLength; } /*****************************************************************************/ /* ************ *** NOTE *** This function takes a pointer to a request!!! ************ */ void ProxyReworkWriteBody (REQUEST_STRUCT *rqptr) { PROXY_TASK *tkptr; /*********/ /* begin */ /*********/ if (WATCHMOD (rqptr, WATCH_MOD_PROXY)) WatchThis (WATCHITM(rqptr), WATCH_MOD_PROXY, "ProxyReworkWriteBody()"); tkptr = rqptr->ProxyTaskPtr; NetWrite (rqptr, &ProxyReworkEnd, tkptr->ResponseBufferPtr, tkptr->ResponseBodyLength); } /*****************************************************************************/ /* ************ *** NOTE *** This function takes a pointer to a request!!! ************ */ void ProxyReworkEnd (REQUEST_STRUCT *rqptr) { PROXY_TASK *tkptr; /*********/ /* begin */ /*********/ if (WATCHMOD (rqptr, WATCH_MOD_PROXY)) WatchThis (WATCHITM(rqptr), WATCH_MOD_PROXY, "ProxyReworkWriteBody()"); tkptr = rqptr->ProxyTaskPtr; ProxyEnd (tkptr); } /*****************************************************************************/ /* The response did not specify a content-type so sniffing the content is there any odour of HTML. Every response should have a content-type! But... fortunately HTML is fairly easily identified. Or should be. */ BOOL ProxyReworkSniffHtml (PROXY_TASK *tkptr) { BOOL yep = false; char ch = 0; char *cptr; /*********/ /* begin */ /*********/ if (WATCHING (tkptr, WATCH_PROXY_REWORK)) WatchThis (WATCHITM(tkptr), WATCH_PROXY_REWORK, "SNIFF for HTML"); for (cptr = tkptr->ResponseBufferPtr; isspace(*cptr); cptr++); if (!memcmp (cptr, "ResponseBodyLength > 256) if (tkptr->ResponseBufferPtr[255]) ch = tkptr->ResponseBufferPtr[255]; if (stristr (cptr, "ResponseBufferPtr[255] = '\0'; } if (yep) { tkptr->ResponseContentTypePtr = "text/html"; if (WATCHING (tkptr, WATCH_PROXY_REWORK)) WatchThis (WATCHITM(tkptr), WATCH_PROXY_REWORK, "SMELLS of HTML"); } return (yep); } /*****************************************************************************/ /* Rework strings in the (rebuilt) response header. */ void ProxyReworkHeader (PROXY_TASK *tkptr) { int cnt, delta, idx, len, rblen; REQUEST_STRUCT *rqptr; /*********/ /* begin */ /*********/ rqptr = tkptr->RequestPtr; if (WATCHMOD (rqptr, WATCH_MOD_PROXY)) WatchThis (WATCHITM(rqptr), WATCH_MOD_PROXY, "ProxyReworkHeader()"); for (idx = 0; idx < 4; idx++) { if (!tkptr->ReworkPtr1[idx]) break; if (WATCHING (tkptr, WATCH_PROXY_REWORK)) WatchThis (WATCHITM(tkptr), WATCH_PROXY_REWORK, "HEADER replacing !&Z with !&Z", tkptr->ReworkPtr1[idx], tkptr->ReworkPtr2[idx]); rblen = tkptr->RebuiltHeaderLength; len = StringReplace (rqptr, &tkptr->RebuiltHeaderPtr, tkptr->ReworkPtr1[idx], tkptr->ReworkPtr2[idx]); if (len) { /* see ProxyReworkProcess() for detail */ tkptr->RebuiltHeaderLength = len; delta = tkptr->ReworkLen1[idx] - tkptr->ReworkLen2[idx]; cnt = (len - rblen) / delta; if (cnt < 0) cnt = -cnt; if (WATCHING (tkptr, WATCH_PROXY_REWORK)) WatchThis (WATCHITM(tkptr), WATCH_PROXY_REWORK, "HEADER !UL replaced", cnt); ProxyAccountingPtr->ReworkReplaceCount += cnt; } else if (WATCHING (tkptr, WATCH_PROXY_REWORK)) WatchThis (WATCHITM(tkptr), WATCH_PROXY_REWORK, "HEADER 0 replaced"); } } /*****************************************************************************/