/*
* ModSecurity for Apache 2.x, http://www.modsecurity.org/
* Copyright (c) 2004-2013 Trustwave Holdings, Inc. (http://www.trustwave.com/)
*
* You may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* If any of the files related to licensing are missing or if you have any
* other questions related to licensing please contact Trustwave Holdings, Inc.
* directly using the email address security@modsecurity.org.
*/
#define WIN32_LEAN_AND_MEAN
#undef inline
#define inline inline
// IIS7 Server API header file
#include <Windows.h>
#include <sal.h>
#include <strsafe.h>
#include "httpserv.h"
// Project header files
#include "mymodule.h"
#include "mymodulefactory.h"
#include "api.h"
#include "moduleconfig.h"
#include "winsock2.h"
class REQUEST_STORED_CONTEXT : public IHttpStoredContext
{
public:
REQUEST_STORED_CONTEXT()
{
m_pConnRec = NULL;
m_pRequestRec = NULL;
m_pHttpContext = NULL;
m_pProvider = NULL;
m_pResponseBuffer = NULL;
m_pResponseLength = 0;
m_pResponsePosition = 0;
}
~REQUEST_STORED_CONTEXT()
{
FinishRequest();
}
// virtual
VOID
CleanupStoredContext(
VOID
)
{
FinishRequest();
delete this;
}
void FinishRequest()
{
if(m_pRequestRec != NULL)
{
modsecFinishRequest(m_pRequestRec);
m_pRequestRec = NULL;
}
if(m_pConnRec != NULL)
{
modsecFinishConnection(m_pConnRec);
m_pConnRec = NULL;
}
}
conn_rec *m_pConnRec;
request_rec *m_pRequestRec;
IHttpContext *m_pHttpContext;
IHttpEventProvider *m_pProvider;
char *m_pResponseBuffer;
ULONGLONG m_pResponseLength;
ULONGLONG m_pResponsePosition;
};
//----------------------------------------------------------------------------
char *GetIpAddr(apr_pool_t *pool, PSOCKADDR pAddr)
{
const char *format = "%15[0-9.]:%5[0-9]";
char ip[16] = { 0 }; // ip4 addresses have max len 15
char port[6] = { 0 }; // port numbers are 16bit, ie 5 digits max
DWORD len = 50;
char *buf = (char *)apr_palloc(pool, len);
if(buf == NULL)
return "";
buf[0] = 0;
WSAAddressToString(pAddr, sizeof(SOCKADDR), NULL, buf, &len);
// test for IPV4 with port on the end
if (sscanf(buf, format, ip, port) == 2) {
// IPV4 but with port - remove the port
char* input = ":";
char* ipv4 = strtok(buf, input);
return ipv4;
}
return buf;
}
apr_sockaddr_t *CopySockAddr(apr_pool_t *pool, PSOCKADDR pAddr)
{
apr_sockaddr_t *addr = (apr_sockaddr_t *)apr_palloc(pool, sizeof(apr_sockaddr_t));
int adrlen = 16, iplen = 4;
if(pAddr->sa_family == AF_INET6)
{
adrlen = 46;
iplen = 16;
}
addr->addr_str_len = adrlen;
addr->family = pAddr->sa_family;
addr->hostname = "unknown";
#ifdef WIN32
addr->ipaddr_len = sizeof(IN_ADDR);
#else
addr->ipaddr_len = sizeof(struct in_addr);
#endif
addr->ipaddr_ptr = &addr->sa.sin.sin_addr;
addr->pool = pool;
addr->port = 80;
#ifdef WIN32
memcpy(&addr->sa.sin.sin_addr.S_un.S_addr, pAddr->sa_data, iplen);
#else
memcpy(&addr->sa.sin.sin_addr.s_addr, pAddr->sa_data, iplen);
#endif
addr->sa.sin.sin_family = pAddr->sa_family;
addr->sa.sin.sin_port = 80;
addr->salen = sizeof(addr->sa);
addr->servname = addr->hostname;
return addr;
}
//----------------------------------------------------------------------------
char *ZeroTerminate(const char *str, size_t len, apr_pool_t *pool)
{
char *_n = (char *)apr_palloc(pool, len + 1);
memcpy(_n, str, len);
_n[len] = 0;
return _n;
}
//----------------------------------------------------------------------------
// FUNCTION: ConvertUTF16ToUTF8
// DESC: Converts Unicode UTF-16 (Windows default) text to Unicode UTF-8.
//----------------------------------------------------------------------------
char *ConvertUTF16ToUTF8( __in const WCHAR * pszTextUTF16, size_t cchUTF16, apr_pool_t *pool )
{
//
// Special case of NULL or empty input string
//
if ( (pszTextUTF16 == NULL) || (*pszTextUTF16 == L'\0') || cchUTF16 == 0 )
{
// Return empty string
return "";
}
//
// Get size of destination UTF-8 buffer, in CHAR's (= bytes)
//
int cbUTF8 = ::WideCharToMultiByte(
CP_UTF8, // convert to UTF-8
0, // specify conversion behavior
pszTextUTF16, // source UTF-16 string
static_cast<int>( cchUTF16 ), // total source string length, in WCHAR's,
NULL, // unused - no conversion required in this step
0, // request buffer size
NULL, NULL // unused
);
if ( cbUTF8 == 0 )
{
return "";
}
//
// Allocate destination buffer for UTF-8 string
//
int cchUTF8 = cbUTF8; // sizeof(CHAR) = 1 byte
char *pszUTF8 = (char *)apr_palloc(pool, cchUTF8 + 1 );
//
// Do the conversion from UTF-16 to UTF-8
//
int result = ::WideCharToMultiByte(
CP_UTF8, // convert to UTF-8
0, // specify conversion behavior
pszTextUTF16, // source UTF-16 string
static_cast<int>( cchUTF16 ), // total source string length, in WCHAR's,
// including end-of-string \0
pszUTF8, // destination buffer
cbUTF8, // destination buffer size, in bytes
NULL, NULL // unused
);
if ( result == 0 )
{
return "";
}
pszUTF8[cchUTF8] = 0;
return pszUTF8;
}
void Log(void *obj, int level, char *str)
{
CMyHttpModule *mod = (CMyHttpModule *)obj;
WORD logcat = EVENTLOG_INFORMATION_TYPE;
level &= APLOG_LEVELMASK;
if(level <= APLOG_ERR)
logcat = EVENTLOG_ERROR_TYPE;
if(level == APLOG_WARNING || strstr(str, "Warning.") != NULL)
logcat = EVENTLOG_WARNING_TYPE;
mod->WriteEventViewerLog(str, logcat);
}
#define NOTE_IIS "iis-tx-context"
void StoreIISContext(request_rec *r, REQUEST_STORED_CONTEXT *rsc)
{
apr_table_setn(r->notes, NOTE_IIS, (const char *)rsc);
}
REQUEST_STORED_CONTEXT *RetrieveIISContext(request_rec *r)
{
REQUEST_STORED_CONTEXT *msr = NULL;
request_rec *rx = NULL;
/* Look in the current request first. */
msr = (REQUEST_STORED_CONTEXT *)apr_table_get(r->notes, NOTE_IIS);
if (msr != NULL) {
//msr->r = r;
return msr;
}
/* If this is a subrequest then look in the main request. */
if (r->main != NULL) {
msr = (REQUEST_STORED_CONTEXT *)apr_table_get(r->main->notes, NOTE_IIS);
if (msr != NULL) {
//msr->r = r;
return msr;
}
}
/* If the request was redirected then look in the previous requests. */
rx = r->prev;
while(rx != NULL) {
msr = (REQUEST_STORED_CONTEXT *)apr_table_get(rx->notes, NOTE_IIS);
if (msr != NULL) {
//msr->r = r;
return msr;
}
rx = rx->prev;
}
return NULL;
}
HRESULT CMyHttpModule::ReadFileChunk(HTTP_DATA_CHUNK *chunk, char *buf)
{
OVERLAPPED ovl;
DWORD dwDataStartOffset;
ULONGLONG bytesTotal = 0;
BYTE * pIoBuffer = NULL;
HANDLE hIoEvent = INVALID_HANDLE_VALUE;
HRESULT hr = S_OK;
pIoBuffer = (BYTE *)VirtualAlloc(NULL,
1,
MEM_COMMIT | MEM_RESERVE,
PAGE_READWRITE);
if (pIoBuffer == NULL)
{
hr = HRESULT_FROM_WIN32(GetLastError());
goto Done;
}
hIoEvent = CreateEvent(NULL, // security attr
FALSE, // manual reset
FALSE, // initial state
NULL); // name
if (hIoEvent == NULL)
{
hr = HRESULT_FROM_WIN32(GetLastError());
goto Done;
}
while(bytesTotal < chunk->FromFileHandle.ByteRange.Length.QuadPart)
{
DWORD bytesRead = 0;
int was_eof = 0;
ULONGLONG offset = chunk->FromFileHandle.ByteRange.StartingOffset.QuadPart + bytesTotal;
ZeroMemory(&ovl, sizeof ovl);
ovl.hEvent = hIoEvent;
ovl.Offset = (DWORD)offset;
dwDataStartOffset = ovl.Offset & (m_dwPageSize - 1);
ovl.Offset &= ~(m_dwPageSize - 1);
ovl.OffsetHigh = offset >> 32;
if (!ReadFile(chunk->FromFileHandle.FileHandle,
pIoBuffer,
m_dwPageSize,
&bytesRead,
&ovl))
{
DWORD dwErr = GetLastError();
switch (dwErr)
{
case ERROR_IO_PENDING:
//
// GetOverlappedResult can return without waiting for the
// event thus leaving it signalled and causing problems
// with future use of that event handle, so just wait ourselves
//
WaitForSingleObject(ovl.hEvent, INFINITE); // == WAIT_OBJECT_0);
if (!GetOverlappedResult(
chunk->FromFileHandle.FileHandle,
&ovl,
&bytesRead,
TRUE))
{
dwErr = GetLastError();
switch(dwErr)
{
case ERROR_HANDLE_EOF:
was_eof = 1;
break;
default:
hr = HRESULT_FROM_WIN32(dwErr);
goto Done;
}
}
break;
case ERROR_HANDLE_EOF:
was_eof = 1;
break;
default:
hr = HRESULT_FROM_WIN32(dwErr);
goto Done;
}
}
bytesRead -= dwDataStartOffset;
if (bytesRead > chunk->FromFileHandle.ByteRange.Length.QuadPart)
{
bytesRead = (DWORD)chunk->FromFileHandle.ByteRange.Length.QuadPart;
}
if ((bytesTotal + bytesRead) > chunk->FromFileHandle.ByteRange.Length.QuadPart)
{
bytesRead = chunk->FromFileHandle.ByteRange.Length.QuadPart - bytesTotal;
}
memcpy(buf, pIoBuffer + dwDataStartOffset, bytesRead);
buf += bytesRead;
bytesTotal += bytesRead;
if(was_eof != 0)
chunk->FromFileHandle.ByteRange.Length.QuadPart = bytesTotal;
}
Done:
if(NULL != pIoBuffer)
{
VirtualFree(pIoBuffer, 0, MEM_RELEASE);
}
if(INVALID_HANDLE_VALUE != hIoEvent)
{
CloseHandle(hIoEvent);
}
return hr;
}
REQUEST_NOTIFICATION_STATUS
CMyHttpModule::OnSendResponse(
IN IHttpContext * pHttpContext,
IN ISendResponseProvider * pResponseProvider
)
{
REQUEST_STORED_CONTEXT *rsc = NULL;
rsc = (REQUEST_STORED_CONTEXT *)pHttpContext->GetModuleContextContainer()->GetModuleContext(g_pModuleContext);
EnterCriticalSection(&m_csLock);
// here we must check if response body processing is enabled
//
if(rsc == NULL || rsc->m_pRequestRec == NULL || rsc->m_pResponseBuffer != NULL || !modsecIsResponseBodyAccessEnabled(rsc->m_pRequestRec))
{
goto Exit;
}
HRESULT hr = S_OK;
IHttpResponse *pHttpResponse = NULL;
HTTP_RESPONSE *pRawHttpResponse = NULL;
HTTP_BYTE_RANGE *pFileByteRange = NULL;
HTTP_DATA_CHUNK *pSourceDataChunk = NULL;
LARGE_INTEGER lFileSize;
REQUEST_NOTIFICATION_STATUS ret = RQ_NOTIFICATION_CONTINUE;
ULONGLONG ulTotalLength = 0;
DWORD c;
request_rec *r = rsc->m_pRequestRec;
pHttpResponse = pHttpContext->GetResponse();
pRawHttpResponse = pHttpResponse->GetRawHttpResponse();
// here we must add handling of chunked response
// apparently IIS 7 calls this handler once per chunk
// see: http://stackoverflow.com/questions/4385249/how-to-buffer-and-process-chunked-data-before-sending-headers-in-iis7-native-mod
if(pRawHttpResponse->EntityChunkCount == 0)
goto Exit;
// here we must transfer response headers
//
USHORT ctcch = 0;
char *ct = (char *)pHttpResponse->GetHeader(HttpHeaderContentType, &ctcch);
char *ctz = ZeroTerminate(ct, ctcch, r->pool);
// assume HTML if content type not set
// without this output filter would not buffer response and processing would hang
//
if(ctz[0] == 0)
ctz = "text/html";
r->content_type = ctz;
#define _TRANSHEADER(id,str) if(pRawHttpResponse->Headers.KnownHeaders[id].pRawValue != NULL) \
{\
apr_table_setn(r->headers_out, str, \
ZeroTerminate(pRawHttpResponse->Headers.KnownHeaders[id].pRawValue, pRawHttpResponse->Headers.KnownHeaders[id].RawValueLength, r->pool)); \
}
_TRANSHEADER(HttpHeaderCacheControl, "Cache-Control");
_TRANSHEADER(HttpHeaderConnection, "Connection");
_TRANSHEADER(HttpHeaderDate, "Date");
_TRANSHEADER(HttpHeaderKeepAlive, "Keep-Alive");
_TRANSHEADER(HttpHeaderPragma, "Pragma");
_TRANSHEADER(HttpHeaderTrailer, "Trailer");
_TRANSHEADER(HttpHeaderTransferEncoding, "Transfer-Encoding");
_TRANSHEADER(HttpHeaderUpgrade, "Upgrade");
_TRANSHEADER(HttpHeaderVia, "Via");
_TRANSHEADER(HttpHeaderWarning, "Warning");
_TRANSHEADER(HttpHeaderAllow, "Allow");
_TRANSHEADER(HttpHeaderContentLength, "Content-Length");
_TRANSHEADER(HttpHeaderContentType, "Content-Type");
_TRANSHEADER(HttpHeaderContentEncoding, "Content-Encoding");
_TRANSHEADER(HttpHeaderContentLanguage, "Content-Language");
_TRANSHEADER(HttpHeaderContentLocation, "Content-Location");
_TRANSHEADER(HttpHeaderContentMd5, "Content-Md5");
_TRANSHEADER(HttpHeaderContentRange, "Content-Range");
_TRANSHEADER(HttpHeaderExpires, "Expires");
_TRANSHEADER(HttpHeaderLastModified, "Last-Modified");
_TRANSHEADER(HttpHeaderAcceptRanges, "Accept-Ranges");
_TRANSHEADER(HttpHeaderAge, "Age");
_TRANSHEADER(HttpHeaderEtag, "Etag");
_TRANSHEADER(HttpHeaderLocation, "Location");
_TRANSHEADER(HttpHeaderProxyAuthenticate, "Proxy-Authenticate");
_TRANSHEADER(HttpHeaderRetryAfter, "Retry-After");
_TRANSHEADER(HttpHeaderServer, "Server");
_TRANSHEADER(HttpHeaderSetCookie, "Set-Cookie");
_TRANSHEADER(HttpHeaderVary, "Vary");
_TRANSHEADER(HttpHeaderWwwAuthenticate, "Www-Authenticate");
#undef _TRANSHEADER
for(int i = 0; i < pRawHttpResponse->Headers.UnknownHeaderCount; i++)
{
apr_table_setn(r->headers_out,
ZeroTerminate(pRawHttpResponse->Headers.pUnknownHeaders[i].pName, pRawHttpResponse->Headers.pUnknownHeaders[i].NameLength, r->pool),
ZeroTerminate(pRawHttpResponse->Headers.pUnknownHeaders[i].pRawValue, pRawHttpResponse->Headers.pUnknownHeaders[i].RawValueLength, r->pool));
}
r->content_encoding = apr_table_get(r->headers_out, "Content-Encoding");
//r->content_type = apr_table_get(r->headers_out, "Content-Type"); -- already set above
const char *lng = apr_table_get(r->headers_out, "Content-Languages");
if(lng != NULL)
{
r->content_languages = apr_array_make(r->pool, 1, sizeof(const char *));
*(const char **)apr_array_push(r->content_languages) = lng;
}
// Disable kernel caching for this response
// Probably we don't have to do it for ModSecurity
//pHttpContext->GetResponse()->DisableKernelCache(
// IISCacheEvents::HTTPSYS_CACHEABLE::HANDLER_HTTPSYS_UNFRIENDLY);
for(c = 0; c < pRawHttpResponse->EntityChunkCount; c++ )
{
pSourceDataChunk = &pRawHttpResponse->pEntityChunks[ c ];
switch( pSourceDataChunk->DataChunkType )
{
case HttpDataChunkFromMemory:
ulTotalLength += pSourceDataChunk->FromMemory.BufferLength;
break;
case HttpDataChunkFromFileHandle:
pFileByteRange = &pSourceDataChunk->FromFileHandle.ByteRange;
//
// File chunks may contain by ranges with unspecified length
// (HTTP_BYTE_RANGE_TO_EOF). In order to send parts of such a chunk,
// its necessary to know when the chunk is finished, and
// we need to move to the next chunk.
//
if ( pFileByteRange->Length.QuadPart == HTTP_BYTE_RANGE_TO_EOF)
{
if ( GetFileType( pSourceDataChunk->FromFileHandle.FileHandle ) ==
FILE_TYPE_DISK )
{
if ( !GetFileSizeEx( pSourceDataChunk->FromFileHandle.FileHandle,
&lFileSize ) )
{
DWORD dwError = GetLastError();
hr = HRESULT_FROM_WIN32(dwError);
goto Finished;
}
// put the resolved file length in the chunk, replacing
// HTTP_BYTE_RANGE_TO_EOF
pFileByteRange->Length.QuadPart =
lFileSize.QuadPart - pFileByteRange->StartingOffset.QuadPart;
}
else
{
hr = HRESULT_FROM_WIN32(ERROR_NOT_SUPPORTED);
goto Finished;
}
}
ulTotalLength += pFileByteRange->Length.QuadPart;
break;
default:
// TBD: consider implementing HttpDataChunkFromFragmentCache,
// and HttpDataChunkFromFragmentCacheEx
hr = HRESULT_FROM_WIN32(ERROR_NOT_SUPPORTED);
goto Finished;
}
}
rsc->m_pResponseBuffer = (char *)apr_palloc(rsc->m_pRequestRec->pool, ulTotalLength);
ulTotalLength = 0;
for(c = 0; c < pRawHttpResponse->EntityChunkCount; c++ )
{
pSourceDataChunk = &pRawHttpResponse->pEntityChunks[ c ];
switch( pSourceDataChunk->DataChunkType )
{
case HttpDataChunkFromMemory:
memcpy(rsc->m_pResponseBuffer + ulTotalLength, pSourceDataChunk->FromMemory.pBuffer, pSourceDataChunk->FromMemory.BufferLength);
ulTotalLength += pSourceDataChunk->FromMemory.BufferLength;
break;
case HttpDataChunkFromFileHandle:
pFileByteRange = &pSourceDataChunk->FromFileHandle.ByteRange;
if(ReadFileChunk(pSourceDataChunk, rsc->m_pResponseBuffer + ulTotalLength) != S_OK)
{
DWORD dwErr = GetLastError();
hr = HRESULT_FROM_WIN32(dwErr);
goto Finished;
}
ulTotalLength += pFileByteRange->Length.QuadPart;
break;
default:
// TBD: consider implementing HttpDataChunkFromFragmentCache,
// and HttpDataChunkFromFragmentCacheEx
hr = HRESULT_FROM_WIN32(ERROR_NOT_SUPPORTED);
goto Finished;
}
}
rsc->m_pResponseLength = ulTotalLength;
//
// If there's no content-length set, we need to set it to avoid chunked transfer mode
// We can only do it if there is it's the only response to be sent.
//
DWORD dwFlags = pResponseProvider->GetFlags();
if (pResponseProvider->GetHeadersBeingSent() &&
(dwFlags & HTTP_SEND_RESPONSE_FLAG_MORE_DATA) == 0 &&
pHttpContext->GetResponse()->GetHeader(HttpHeaderContentLength) == NULL)
{
CHAR szLength[21]; //Max length for a 64 bit int is 20
ZeroMemory(szLength, sizeof(szLength));
hr = StringCchPrintfA(
szLength,
sizeof(szLength) / sizeof(CHAR) - 1, "%d",
ulTotalLength);
if(FAILED(hr))
{
goto Finished;
}
hr = pHttpContext->GetResponse()->SetHeader(
HttpHeaderContentLength,
szLength,
(USHORT)strlen(szLength),
TRUE);
if(FAILED(hr))
{
goto Finished;
}
}
Finished:
int status = modsecProcessResponse(rsc->m_pRequestRec);
// the logic here is temporary, needs clarification
//
if(status != 0 && status != -1)
{
pHttpContext->GetResponse()->Clear();
pHttpContext->GetResponse()->SetStatus(status, "ModSecurity Action");
pHttpContext->SetRequestHandled();
rsc->FinishRequest();
LeaveCriticalSection(&m_csLock);
return RQ_NOTIFICATION_FINISH_REQUEST;
}
Exit:
// temporary hack, in reality OnSendRequest theoretically could possibly come before OnEndRequest
//
if(rsc != NULL)
rsc->FinishRequest();
LeaveCriticalSection(&m_csLock);
return RQ_NOTIFICATION_CONTINUE;
}
REQUEST_NOTIFICATION_STATUS
CMyHttpModule::OnPostEndRequest(
IN IHttpContext * pHttpContext,
IN IHttpEventProvider * pProvider
)
{
REQUEST_STORED_CONTEXT *rsc = NULL;
rsc = (REQUEST_STORED_CONTEXT *)pHttpContext->GetModuleContextContainer()->GetModuleContext(g_pModuleContext);
// only finish request if OnSendResponse have been called already
//
if(rsc != NULL && rsc->m_pResponseBuffer != NULL)
{
EnterCriticalSection(&m_csLock);
rsc->FinishRequest();
LeaveCriticalSection(&m_csLock);
}
return RQ_NOTIFICATION_CONTINUE;
}
REQUEST_NOTIFICATION_STATUS
CMyHttpModule::OnBeginRequest(
IN IHttpContext * pHttpContext,
IN IHttpEventProvider * pProvider
)
{
HRESULT hr = S_OK;
IHttpRequest* pRequest = NULL;
MODSECURITY_STORED_CONTEXT* pConfig = NULL;
UNREFERENCED_PARAMETER ( pProvider );
EnterCriticalSection(&m_csLock);
if ( pHttpContext == NULL )
{
hr = E_UNEXPECTED;
goto Finished;
}
pRequest = pHttpContext->GetRequest();
if ( pRequest == NULL )
{
hr = E_UNEXPECTED;
goto Finished;
}
hr = MODSECURITY_STORED_CONTEXT::GetConfig(pHttpContext, &pConfig );
if ( FAILED( hr ) )
{
//hr = E_UNEXPECTED;
hr = S_OK;
goto Finished;
}
// If module is disabled, dont go any further
//
if( pConfig->GetIsEnabled() == false )
{
goto Finished;
}
// every 3 seconds we check for changes in config file
//
DWORD ctime = GetTickCount();
if(pConfig->m_Config == NULL || (ctime - pConfig->m_dwLastCheck) > 3000)
{
char *path;
USHORT pathlen;
hr = pConfig->GlobalWideCharToMultiByte(pConfig->GetPath(), wcslen(pConfig->GetPath()), &path, &pathlen);
if ( FAILED( hr ) )
{
hr = E_UNEXPECTED;
goto Finished;
}
WIN32_FILE_ATTRIBUTE_DATA fdata;
BOOL ret;
ret = GetFileAttributesEx(path, GetFileExInfoStandard, &fdata);
pConfig->m_dwLastCheck = ctime;
if(pConfig->m_Config == NULL || (ret != 0 && (pConfig->m_LastChange.dwLowDateTime != fdata.ftLastWriteTime.dwLowDateTime ||
pConfig->m_LastChange.dwHighDateTime != fdata.ftLastWriteTime.dwHighDateTime)))
{
pConfig->m_LastChange.dwLowDateTime = fdata.ftLastWriteTime.dwLowDateTime;
pConfig->m_LastChange.dwHighDateTime = fdata.ftLastWriteTime.dwHighDateTime;
pConfig->m_Config = modsecGetDefaultConfig();
PCWSTR servpath = pHttpContext->GetApplication()->GetApplicationPhysicalPath();
char *apppath;
USHORT apppathlen;
hr = pConfig->GlobalWideCharToMultiByte((WCHAR *)servpath, wcslen(servpath), &apppath, &apppathlen);
if ( FAILED( hr ) )
{
delete path;
hr = E_UNEXPECTED;
goto Finished;
}
if(path[0] != 0)
{
const char * err = modsecProcessConfig((directory_config *)pConfig->m_Config, path, apppath);
if(err != NULL)
{
WriteEventViewerLog(err, EVENTLOG_ERROR_TYPE);
delete apppath;
delete path;
goto Finished;
}
modsecReportRemoteLoadedRules();
if (this->status_call_already_sent == false)
{
this->status_call_already_sent = true;
modsecStatusEngineCall();
}
}
delete apppath;
}
delete path;
}
conn_rec *c;
request_rec *r;
c = modsecNewConnection();
modsecProcessConnection(c);
r = modsecNewRequest(c, (directory_config *)pConfig->m_Config);
// on IIS we force input stream inspection flag, because its absence does not add any performance gain
// it's because on IIS request body must be restored each time it was read
//
modsecSetConfigForIISRequestBody(r);
REQUEST_STORED_CONTEXT *rsc = new REQUEST_STORED_CONTEXT();
rsc->m_pConnRec = c;
rsc->m_pRequestRec = r;
rsc->m_pHttpContext = pHttpContext;
rsc->m_pProvider = pProvider;
pHttpContext->GetModuleContextContainer()->SetModuleContext(rsc, g_pModuleContext);
StoreIISContext(r, rsc);
HTTP_REQUEST *req = pRequest->GetRawHttpRequest();
r->hostname = ConvertUTF16ToUTF8(req->CookedUrl.pHost, req->CookedUrl.HostLength / sizeof(WCHAR), r->pool);
r->path_info = ConvertUTF16ToUTF8(req->CookedUrl.pAbsPath, req->CookedUrl.AbsPathLength / sizeof(WCHAR), r->pool);
if(r->hostname == NULL)
{
if(req->Headers.KnownHeaders[HttpHeaderHost].pRawValue != NULL)
r->hostname = ZeroTerminate(req->Headers.KnownHeaders[HttpHeaderHost].pRawValue,
req->Headers.KnownHeaders[HttpHeaderHost].RawValueLength, r->pool);
}
int port = 0;
char *port_str = NULL;
if(r->hostname != NULL)
{
int k = 0;
char *ptr = (char *)r->hostname;
while(*ptr != 0 && *ptr != ':')
ptr++;
if(*ptr == ':')
{
*ptr = 0;
port_str = ptr + 1;
port = atoi(port_str);
}
}
if(req->CookedUrl.pQueryString != NULL && req->CookedUrl.QueryStringLength > 0)
r->args = ConvertUTF16ToUTF8(req->CookedUrl.pQueryString + 1, (req->CookedUrl.QueryStringLength / sizeof(WCHAR)) - 1, r->pool);
#define _TRANSHEADER(id,str) if(req->Headers.KnownHeaders[id].pRawValue != NULL) \
{\
apr_table_setn(r->headers_in, str, \
ZeroTerminate(req->Headers.KnownHeaders[id].pRawValue, req->Headers.KnownHeaders[id].RawValueLength, r->pool)); \
}
_TRANSHEADER(HttpHeaderCacheControl, "Cache-Control");
_TRANSHEADER(HttpHeaderConnection, "Connection");
_TRANSHEADER(HttpHeaderDate, "Date");
_TRANSHEADER(HttpHeaderKeepAlive, "Keep-Alive");
_TRANSHEADER(HttpHeaderPragma, "Pragma");
_TRANSHEADER(HttpHeaderTrailer, "Trailer");
_TRANSHEADER(HttpHeaderTransferEncoding, "Transfer-Encoding");
_TRANSHEADER(HttpHeaderUpgrade, "Upgrade");
_TRANSHEADER(HttpHeaderVia, "Via");
_TRANSHEADER(HttpHeaderWarning, "Warning");
_TRANSHEADER(HttpHeaderAllow, "Allow");
_TRANSHEADER(HttpHeaderContentLength, "Content-Length");
_TRANSHEADER(HttpHeaderContentType, "Content-Type");
_TRANSHEADER(HttpHeaderContentEncoding, "Content-Encoding");
_TRANSHEADER(HttpHeaderContentLanguage, "Content-Language");
_TRANSHEADER(HttpHeaderContentLocation, "Content-Location");
_TRANSHEADER(HttpHeaderContentMd5, "Content-Md5");
_TRANSHEADER(HttpHeaderContentRange, "Content-Range");
_TRANSHEADER(HttpHeaderExpires, "Expires");
_TRANSHEADER(HttpHeaderLastModified, "Last-Modified");
_TRANSHEADER(HttpHeaderAccept, "Accept");
_TRANSHEADER(HttpHeaderAcceptCharset, "Accept-Charset");
_TRANSHEADER(HttpHeaderAcceptEncoding, "Accept-Encoding");
_TRANSHEADER(HttpHeaderAcceptLanguage, "Accept-Language");
_TRANSHEADER(HttpHeaderAuthorization, "Authorization");
_TRANSHEADER(HttpHeaderCookie, "Cookie");
_TRANSHEADER(HttpHeaderExpect, "Expect");
_TRANSHEADER(HttpHeaderFrom, "From");
_TRANSHEADER(HttpHeaderHost, "Host");
_TRANSHEADER(HttpHeaderIfMatch, "If-Match");
_TRANSHEADER(HttpHeaderIfModifiedSince, "If-Modified-Since");
_TRANSHEADER(HttpHeaderIfNoneMatch, "If-None-Match");
_TRANSHEADER(HttpHeaderIfRange, "If-Range");
_TRANSHEADER(HttpHeaderIfUnmodifiedSince, "If-Unmodified-Since");
_TRANSHEADER(HttpHeaderMaxForwards, "Max-Forwards");
_TRANSHEADER(HttpHeaderProxyAuthorization, "Proxy-Authorization");
_TRANSHEADER(HttpHeaderReferer, "Referer");
_TRANSHEADER(HttpHeaderRange, "Range");
_TRANSHEADER(HttpHeaderTe, "TE");
_TRANSHEADER(HttpHeaderTranslate, "Translate");
_TRANSHEADER(HttpHeaderUserAgent, "User-Agent");
#undef _TRANSHEADER
for(int i = 0; i < req->Headers.UnknownHeaderCount; i++)
{
apr_table_setn(r->headers_in,
ZeroTerminate(req->Headers.pUnknownHeaders[i].pName, req->Headers.pUnknownHeaders[i].NameLength, r->pool),
ZeroTerminate(req->Headers.pUnknownHeaders[i].pRawValue, req->Headers.pUnknownHeaders[i].RawValueLength, r->pool));
}
r->content_encoding = apr_table_get(r->headers_in, "Content-Encoding");
r->content_type = apr_table_get(r->headers_in, "Content-Type");
const char *lng = apr_table_get(r->headers_in, "Content-Languages");
if(lng != NULL)
{
r->content_languages = apr_array_make(r->pool, 1, sizeof(const char *));
*(const char **)apr_array_push(r->content_languages) = lng;
}
switch(req->Verb)
{
case HttpVerbUnparsed:
case HttpVerbUnknown:
case HttpVerbInvalid:
case HttpVerbTRACK: // used by Microsoft Cluster Server for a non-logged trace
case HttpVerbSEARCH:
default:
r->method = "INVALID";
r->method_number = M_INVALID;
break;
case HttpVerbOPTIONS:
r->method = "OPTIONS";
r->method_number = M_OPTIONS;
break;
case HttpVerbGET:
case HttpVerbHEAD:
r->method = "GET";
r->method_number = M_GET;
break;
case HttpVerbPOST:
r->method = "POST";
r->method_number = M_POST;
break;
case HttpVerbPUT:
r->method = "PUT";
r->method_number = M_PUT;
break;
case HttpVerbDELETE:
r->method = "DELETE";
r->method_number = M_DELETE;
break;
case HttpVerbTRACE:
r->method = "TRACE";
r->method_number = M_TRACE;
break;
case HttpVerbCONNECT:
r->method = "CONNECT";
r->method_number = M_CONNECT;
break;
case HttpVerbMOVE:
r->method = "MOVE";
r->method_number = M_MOVE;
break;
case HttpVerbCOPY:
r->method = "COPY";
r->method_number = M_COPY;
break;
case HttpVerbPROPFIND:
r->method = "PROPFIND";
r->method_number = M_PROPFIND;
break;
case HttpVerbPROPPATCH:
r->method = "PROPPATCH";
r->method_number = M_PROPPATCH;
break;
case HttpVerbMKCOL:
r->method = "MKCOL";
r->method_number = M_MKCOL;
break;
case HttpVerbLOCK:
r->method = "LOCK";
r->method_number = M_LOCK;
break;
case HttpVerbUNLOCK:
r->method = "UNLOCK";
r->method_number = M_UNLOCK;
break;
}
if(HTTP_EQUAL_VERSION(req->Version, 0, 9))
r->protocol = "HTTP/0.9";
else if(HTTP_EQUAL_VERSION(req->Version, 1, 0))
r->protocol = "HTTP/1.0";
else
r->protocol = "HTTP/1.1";
r->request_time = apr_time_now();
r->parsed_uri.scheme = "http";
r->parsed_uri.path = r->path_info;
r->parsed_uri.hostname = (char *)r->hostname;
r->parsed_uri.is_initialized = 1;
r->parsed_uri.port = port;
r->parsed_uri.port_str = port_str;
r->parsed_uri.query = r->args;
r->parsed_uri.dns_looked_up = 0;
r->parsed_uri.dns_resolved = 0;
r->parsed_uri.password = NULL;
r->parsed_uri.user = NULL;
r->parsed_uri.fragment = NULL;
r->unparsed_uri = ZeroTerminate(req->pRawUrl, req->RawUrlLength, r->pool);
r->uri = r->unparsed_uri;
r->the_request = (char *)apr_palloc(r->pool, strlen(r->method) + 1 + req->RawUrlLength + 1 + strlen(r->protocol) + 1);
strcpy(r->the_request, r->method);
strcat(r->the_request, " ");
strcat(r->the_request, r->uri);
strcat(r->the_request, " ");
strcat(r->the_request, r->protocol);
HTTP_REQUEST_ID httpRequestID;
char *pszValue = (char *)apr_palloc(r->pool, 24);
httpRequestID = pRequest->GetRawHttpRequest()->RequestId;
_ui64toa(httpRequestID, pszValue, 10);
apr_table_setn(r->subprocess_env, "UNIQUE_ID", pszValue);
PSOCKADDR pAddr = pRequest->GetRemoteAddress();
#if AP_SERVER_MAJORVERSION_NUMBER > 1 && AP_SERVER_MINORVERSION_NUMBER < 3
c->remote_addr = CopySockAddr(r->pool, pAddr);
c->remote_ip = GetIpAddr(r->pool, pAddr);
#else
c->client_addr = CopySockAddr(r->pool, pAddr);
c->client_ip = GetIpAddr(r->pool, pAddr);
#endif
c->remote_host = NULL;
int status = modsecProcessRequest(r);
if(status != DECLINED)
{
pHttpContext->GetResponse()->SetStatus(status, "ModSecurity Action");
pHttpContext->SetRequestHandled();
hr = E_FAIL;
goto Finished;
}
Finished:
LeaveCriticalSection(&m_csLock);
if ( FAILED( hr ) )
{
return RQ_NOTIFICATION_FINISH_REQUEST;
}
return RQ_NOTIFICATION_CONTINUE;
}
apr_status_t ReadBodyCallback(request_rec *r, char *buf, unsigned int length, unsigned int *readcnt, int *is_eos)
{
REQUEST_STORED_CONTEXT *rsc = RetrieveIISContext(r);
*readcnt = 0;
if(rsc == NULL)
{
*is_eos = 1;
return APR_SUCCESS;
}
IHttpContext *pHttpContext = rsc->m_pHttpContext;
IHttpRequest *pRequest = pHttpContext->GetRequest();
if(pRequest->GetRemainingEntityBytes() == 0)
{
*is_eos = 1;
return APR_SUCCESS;
}
HRESULT hr = pRequest->ReadEntityBody(buf, length, false, (DWORD *)readcnt, NULL);
if (FAILED(hr))
{
// End of data is okay.
if (ERROR_HANDLE_EOF != (hr & 0x0000FFFF))
{
// Set the error status.
rsc->m_pProvider->SetErrorStatus( hr );
}
*is_eos = 1;
}
return APR_SUCCESS;
}
apr_status_t WriteBodyCallback(request_rec *r, char *buf, unsigned int length)
{
REQUEST_STORED_CONTEXT *rsc = RetrieveIISContext(r);
if(rsc == NULL || rsc->m_pRequestRec == NULL)
return APR_SUCCESS;
IHttpContext *pHttpContext = rsc->m_pHttpContext;
IHttpRequest *pHttpRequest = pHttpContext->GetRequest();
CHAR szLength[21]; //Max length for a 64 bit int is 20
ZeroMemory(szLength, sizeof(szLength));
HRESULT hr = StringCchPrintfA(
szLength,
sizeof(szLength) / sizeof(CHAR) - 1, "%d",
length);
if(FAILED(hr))
{
// not possible
}
hr = pHttpRequest->SetHeader(
HttpHeaderContentLength,
szLength,
(USHORT)strlen(szLength),
TRUE);
if(FAILED(hr))
{
// possible, but there's nothing we can do
}
// since we clean the APR pool at the end of OnSendRequest, we must get IIS-managed memory chunk
//
void *reqbuf = pHttpContext->AllocateRequestMemory(length);
memcpy(reqbuf, buf, length);
pHttpRequest->InsertEntityBody(reqbuf, length);
return APR_SUCCESS;
}
apr_status_t ReadResponseCallback(request_rec *r, char *buf, unsigned int length, unsigned int *readcnt, int *is_eos)
{
REQUEST_STORED_CONTEXT *rsc = RetrieveIISContext(r);
*readcnt = 0;
if(rsc == NULL || rsc->m_pResponseBuffer == NULL)
{
*is_eos = 1;
return APR_SUCCESS;
}
unsigned int size = length;
if(size > rsc->m_pResponseLength - rsc->m_pResponsePosition)
size = rsc->m_pResponseLength - rsc->m_pResponsePosition;
memcpy(buf, rsc->m_pResponseBuffer + rsc->m_pResponsePosition, size);
*readcnt = size;
rsc->m_pResponsePosition += size;
if(rsc->m_pResponsePosition >= rsc->m_pResponseLength)
*is_eos = 1;
return APR_SUCCESS;
}
apr_status_t WriteResponseCallback(request_rec *r, char *buf, unsigned int length)
{
REQUEST_STORED_CONTEXT *rsc = RetrieveIISContext(r);
if(rsc == NULL || rsc->m_pRequestRec == NULL || rsc->m_pResponseBuffer == NULL)
return APR_SUCCESS;
IHttpContext *pHttpContext = rsc->m_pHttpContext;
IHttpResponse *pHttpResponse = pHttpContext->GetResponse();
HTTP_RESPONSE *pRawHttpResponse = pHttpResponse->GetRawHttpResponse();
HTTP_DATA_CHUNK *pDataChunk = (HTTP_DATA_CHUNK *)apr_palloc(rsc->m_pRequestRec->pool, sizeof(HTTP_DATA_CHUNK));
pRawHttpResponse->EntityChunkCount = 0;
// since we clean the APR pool at the end of OnSendRequest, we must get IIS-managed memory chunk
//
void *reqbuf = pHttpContext->AllocateRequestMemory(length);
memcpy(reqbuf, buf, length);
pDataChunk->DataChunkType = HttpDataChunkFromMemory;
pDataChunk->FromMemory.pBuffer = reqbuf;
pDataChunk->FromMemory.BufferLength = length;
CHAR szLength[21]; //Max length for a 64 bit int is 20
ZeroMemory(szLength, sizeof(szLength));
HRESULT hr = StringCchPrintfA(
szLength,
sizeof(szLength) / sizeof(CHAR) - 1, "%d",
length);
if(FAILED(hr))
{
// not possible
}
hr = pHttpResponse->SetHeader(
HttpHeaderContentLength,
szLength,
(USHORT)strlen(szLength),
TRUE);
if(FAILED(hr))
{
// possible, but there's nothing we can do
}
pHttpResponse->WriteEntityChunkByReference(pDataChunk);
return APR_SUCCESS;
}
CMyHttpModule::CMyHttpModule()
{
// Open a handle to the Event Viewer.
m_hEventLog = RegisterEventSource( NULL, "ModSecurity" );
SYSTEM_INFO sysInfo;
GetSystemInfo(&sysInfo);
m_dwPageSize = sysInfo.dwPageSize;
this->status_call_already_sent = false;
InitializeCriticalSection(&m_csLock);
modsecSetLogHook(this, Log);
modsecSetReadBody(ReadBodyCallback);
modsecSetReadResponse(ReadResponseCallback);
modsecSetWriteBody(WriteBodyCallback);
modsecSetWriteResponse(WriteResponseCallback);
server_rec *s = modsecInit();
char *compname = (char *)malloc(128);
DWORD size = 128;
GetComputerName(compname, &size);
s->server_hostname = compname;
modsecStartConfig();
modsecFinalizeConfig();
modsecInitProcess();
}
CMyHttpModule::~CMyHttpModule()
{
// ModSecurity registers APR pool cleanups, which interfere with APR pool tear down process
// this causes crashes and since we are exiting the process here, so this is a temporary solution
//
//modsecTerminate();
//WriteEventViewerLog("Module deleted.");
// Test whether the handle for the Event Viewer is open.
if (NULL != m_hEventLog)
{
// Close the handle to the Event Viewer.
DeregisterEventSource( m_hEventLog );
m_hEventLog = NULL;
DeleteCriticalSection(&m_csLock);
}
}
void CMyHttpModule::Dispose()
{
}
BOOL CMyHttpModule::WriteEventViewerLog(LPCSTR szNotification, WORD category)
{
// Test whether the handle for the Event Viewer is open.
if (NULL != m_hEventLog)
{
// Write any strings to the Event Viewer and return.
return ReportEvent(
m_hEventLog,
category, 0, 0x1,
NULL, 1, 0, &szNotification, NULL );
}
return FALSE;
}