|
|
/*--
Copyright (c) 1995-1998 Microsoft Corporation Module Name: ISAPI.CPP Author: Arul Menezes Abstract: ISAPI handling code --*/
#include "pch.h"
#pragma hdrstop
#include "httpd.h"
#define RK_OLE L"SOFTWARE\\Microsoft\\Ole"
#define RK_FREETIMEOUT L"FreeTimeout"
// Amount of time ISAPI cache timeout thread should sleep between firing.
#define CACHE_SLEEP_TIMEOUT 60000
BOOL InitExtensions(CISAPICache **ppISAPICache, DWORD *pdwCacheSleep) { CReg reg(HKEY_LOCAL_MACHINE, RK_OLE);
// We double the amount of time COM tells us it's using to be safe.
*pdwCacheSleep = 3*reg.ValueDW(RK_FREETIMEOUT,600000); // default is 30 minutes, 1800000
*ppISAPICache = new CISAPICache();
return(NULL != *ppISAPICache); }
BOOL CHttpRequest::ExecuteISAPI(void) { DWORD dwRet = HSE_STATUS_ERROR; EXTENSION_CONTROL_BLOCK ECB; CISAPI *pISAPIDLL = NULL;
// wrap all calls to the ISAPI in a _try--_except
__try { if (! g_pVars->m_pISAPICache->Load(m_wszPath,&pISAPIDLL)) return FALSE;
// create an ECB (this allocates no memory)
FillECB(&ECB);
DEBUGCHK(m_hEvent == NULL); m_hEvent = CreateEvent(NULL,FALSE,FALSE,NULL); m_pECB = &ECB;
// call the ISAPI
dwRet = pISAPIDLL->CallExtension(&ECB);
// grab log data if any
if (ECB.lpszLogData[0]) { DEBUGCHK(!m_pszLogParam); m_pszLogParam = MySzDupA(ECB.lpszLogData); } } __except(1) // catch all exceptions
{ TraceTag(ttidWebServer, "ISAPI DLL caused exception 0x%08x and was terminated", GetExceptionCode()); g_pVars->m_pLog->WriteEvent(IDS_HTTPD_EXT_EXCEPTION,m_wszPath,GetExceptionCode(),L"HttpExtensionProc",GetLastError()); }
// set keep-alive status based on return code
m_fKeepAlive = (dwRet==HSE_STATUS_SUCCESS_AND_KEEP_CONN);
TraceTag(ttidWebServer, "ISAPI ExtProc returned %d keep=%d", dwRet, m_fKeepAlive);
if (dwRet == HSE_STATUS_PENDING) { DWORD dwRes = WAIT_ABANDONED;
// Wait up to 5 minutes for the request to complete
if (m_hEvent != NULL) dwRes = WaitForSingleObject (m_hEvent, 300000);
AssertSz(dwRes == WAIT_OBJECT_0, "Pended request was timed out after 5 minutes");
if (dwRes == WAIT_OBJECT_0) dwRet = m_dwStatus; else dwRet = HSE_STATUS_ERROR; }
if (m_hEvent != NULL) { CloseHandle (m_hEvent); m_hEvent = NULL; }
StartRemoveISAPICacheIfNeeded(); g_pVars->m_pISAPICache->Unload(pISAPIDLL);
return(dwRet!=HSE_STATUS_ERROR); }
void CHttpRequest::FillECB(LPEXTENSION_CONTROL_BLOCK pECB) { ZEROMEM(pECB); pECB->cbSize = sizeof(*pECB); pECB->dwVersion = HSE_VERSION; pECB->ConnID = (HCONN)this;
DEBUGCHK(m_pszMethod);
// BUGBUG 13244: IIS examines dwHttpStatusCode if user doesn't send their own headers
// and uses it. However, there's a work around for this on CE (direct use to
// ServerSupportFunction) and using the status code rather than m_rs would
// be a rearchitecting problem. Fix: None for now.
pECB->dwHttpStatusCode = 200; pECB->lpszMethod = m_pszMethod; pECB->lpszQueryString = (PSTR)(m_pszQueryString ? m_pszQueryString : cszEmpty); pECB->lpszContentType = (PSTR)(m_pszContentType ? m_pszContentType : cszEmpty); pECB->lpszPathInfo = (PSTR) (m_pszPathInfo ? m_pszPathInfo : cszEmpty); pECB->lpszPathTranslated = (PSTR) (m_pszPathTranslated ? m_pszPathTranslated : cszEmpty);
pECB->cbTotalBytes = m_dwContentLength; pECB->cbAvailable = m_bufRequest.Count(); pECB->lpbData = m_bufRequest.Data();
pECB->GetServerVariable = ::GetServerVariable; pECB->WriteClient = ::WriteClient; pECB->ReadClient = ::ReadClient; pECB->ServerSupportFunction = ::ServerSupportFunction; }
BOOL WINAPI GetServerVariable(HCONN hConn, PSTR psz, PVOID pv, PDWORD pdw) { CHECKHCONN(hConn); CHECKPTRS2(psz, pdw);
return((CHttpRequest*)hConn)->GetServerVariable(psz, pv, pdw, FALSE); }
int RecvToBuf(SOCKET socket, PVOID pv, DWORD dwReadBufSize, DWORD dwTimeout) { DEBUG_CODE_INIT; int iRecv = SOCKET_ERROR; DWORD dwAvailable;
if (!MySelect(socket,dwTimeout)) { SetLastError(WSAETIMEDOUT); myleave(1400); }
if (ioctlsocket(socket, FIONREAD, &dwAvailable)) { SetLastError(WSAETIMEDOUT); myleave(1401); }
iRecv = recv(socket, (PSTR) pv, (dwAvailable < dwReadBufSize) ? dwAvailable : dwReadBufSize, 0); if (iRecv == 0) { SetLastError(WSAETIMEDOUT); myleave(1402); } if (iRecv == SOCKET_ERROR) { // recv() already has called SetLastError with appropriate message
myleave(1403); }
done: TraceTag(ttidWebServer, "RecvToBuf returns error err = %d, GLE = 0x%08x",err,GetLastError()); return iRecv; }
BOOL WINAPI ReadClient(HCONN hConn, PVOID pv, PDWORD pdw) { CHECKHCONN(hConn); CHECKPTRS2(pv, pdw); if ( *pdw == 0) { SetLastError(ERROR_INVALID_PARAMETER); return FALSE; }
return((CHttpRequest*)hConn)->ReadClient(pv,pdw); }
BOOL CHttpRequest::ReadClient(PVOID pv, PDWORD pdw) { DEBUG_CODE_INIT; BOOL ret = FALSE; PVOID pvFilterModify = pv; DWORD dwBytesReceived; DWORD dwBufferSize = *pdw;
dwBytesReceived = RecvToBuf(m_socket,pv,*pdw,RECVTIMEOUT);
if (dwBytesReceived == 0 || dwBytesReceived == SOCKET_ERROR) myleave(1399);
if (g_pVars->m_fFilters && ! CallFilter(SF_NOTIFY_READ_RAW_DATA,(PSTR*) &pvFilterModify,(int*) &dwBytesReceived, NULL, (int *) &dwBufferSize)) { myleave(1404); }
// Check if filter modified pointer, copy if there's enough room for it.
if (pvFilterModify != pv) { if (*pdw <= dwBufferSize) { memcpy(pv,pvFilterModify,dwBufferSize); } else { SetLastError(ERROR_INSUFFICIENT_BUFFER); myleave(1405); } }
*pdw = dwBytesReceived; ret = TRUE; done: TraceTag(ttidWebServer, "HTTPD:ReadClient failed, GLE = 0x%08x, err = % err",GetLastError(), err); return ret; }
BOOL WINAPI WriteClient(HCONN hConn, PVOID pv, PDWORD pdw, DWORD dwFlags) { CHECKHCONN(hConn); CHECKPTRS2(pv, pdw);
if (dwFlags & HSE_IO_ASYNC) return((CHttpRequest*)hConn)->WriteClientAsync(pv, pdw, FALSE); else return((CHttpRequest*)hConn)->WriteClient(pv, pdw,FALSE); } BOOL WINAPI ServerSupportFunction(HCONN hConn, DWORD dwReq, PVOID pvBuf, PDWORD pdwSize, PDWORD pdwType) { CHECKHCONN(hConn); return((CHttpRequest*)hConn)->ServerSupportFunction(dwReq, pvBuf, pdwSize, pdwType); }
BOOL CHttpRequest::ServerSupportFunction(DWORD dwReq, PVOID pvBuf, PDWORD pdwSize, PDWORD pdwType) { switch (dwReq) { // Can never support these
//case HSE_REQ_ABORTIVE_CLOSE:
//case HSE_REQ_ASYNC_READ_CLIENT:
//case HSE_REQ_GET_CERT_INFO_EX:
//case HSE_REQ_GET_IMPERSONATION_TOKEN:
//case HSE_REQ_GET_SSPI_INFO:
//case HSE_REQ_REFRESH_ISAPI_ACL:
//case HSE_REQ_TRANSMIT_FILE:
default: { SetLastError(ERROR_INVALID_PARAMETER); return FALSE; }
case HSE_REQ_IS_KEEP_CONN: { CHECKPTR(pvBuf); *((BOOL *) pvBuf) = m_fKeepAlive; return TRUE; }
case HSE_REQ_SEND_URL: case HSE_REQ_SEND_URL_REDIRECT_RESP: {
// close connection, because ISAPI won't have a chance to add headers anyway
CHttpResponse resp(m_socket, STATUS_MOVED, CONN_CLOSE,this); // m_rs = STATUS_MOVED;
resp.SendRedirect((PSTR)pvBuf); // send a special redirect body
m_fKeepAlive = FALSE; return TRUE; }
case HSE_REQ_MAP_URL_TO_PATH_EX: case HSE_REQ_MAP_URL_TO_PATH: { CHECKPTRS2(pvBuf,pdwSize);
if (dwReq == HSE_REQ_MAP_URL_TO_PATH_EX) { if (!pdwType) { SetLastError(ERROR_INVALID_PARAMETER); return FALSE; } return MapURLToPath((PSTR)pvBuf,pdwSize,(LPHSE_URL_MAPEX_INFO) pdwType); } else { // IIS docs are misleading here, but even if a valid param is passed in non-EX
// case, ignore it. (Like IIS.)
return MapURLToPath((PSTR)pvBuf,pdwSize); } }
case HSE_REQ_SEND_RESPONSE_HEADER: { // no Connection header...let ISAPI send one if it wants
CHttpResponse resp(m_socket, STATUS_OK, CONN_NONE,this); // no body, default or otherwise (leave that to the ISAPI), but add default headers
m_rs = STATUS_OK; resp.SendResponse((PSTR) pdwType, (PSTR) pvBuf); return TRUE; }
case HSE_REQ_SEND_RESPONSE_HEADER_EX: { // Note: We ignore cchStatus and cchHeader members.
CHECKPTR(pvBuf); HSE_SEND_HEADER_EX_INFO *pHeaderEx = (HSE_SEND_HEADER_EX_INFO *) pvBuf;
// Connection header determined by fKeepConn of passed in struct
CHttpResponse resp(m_socket, STATUS_OK, pHeaderEx->fKeepConn ? CONN_KEEP : CONN_CLOSE, this);
m_fKeepAlive = pHeaderEx->fKeepConn; // no body, default or otherwise (leave that to the ISAPI), but add default headers
m_rs = STATUS_OK; resp.SendResponse(pHeaderEx->pszHeader, pHeaderEx->pszStatus); return TRUE; }
case HSE_APPEND_LOG_PARAMETER: { return MyStrCatA(&m_pszLogParam,(PSTR) pvBuf,","); }
case HSE_REQ_CLOSE_CONNECTION: { // Per ISAPI documentation:
//
// Once you use the HSE_REQ_CLOSE_CONNECTION server
// support function to close a connection, you must
// wait for IIS to call the asynchronous I/O function
// (specified by HSE_REQ_IO_COMPLETION) before you end
// the session with HSE_REQ_DONE_WITH_SESSION.
//
// Since our async I/O calls are all really sync, we
// will call the completion routine at the end of the
// operation and get a HSE_REQ_DONE_WITH_SESSION to do
// the rest of the cleanup.
return TRUE; }
case HSE_REQ_DONE_WITH_SESSION: { AssertSz (m_hEvent != NULL, "Call to end a non-pended session, treating as error");
if (m_hEvent != NULL) { m_dwStatus = *((DWORD *) pvBuf); m_fKeepAlive = FALSE; SetEvent (m_hEvent); return TRUE; } else { SetLastError(ERROR_INVALID_PARAMETER); return FALSE; } }
case HSE_REQ_IO_COMPLETION: { m_pfnCompletion = (PFN_HSE_IO_COMPLETION)pvBuf; m_pvContext = (PVOID) pdwType; return TRUE; } } }
BOOL CHttpRequest::MapURLToPath(PSTR pszBuffer, PDWORD pdwSize, LPHSE_URL_MAPEX_INFO pUrlMapEx) { DWORD dwPermissions; PWSTR wszPath; PSTR pszURL; DWORD dwBufNeeded = 0; BOOL ret;
wszPath = g_pVars->m_pVroots->URLAtoPathW(pszBuffer,&dwPermissions); if (!wszPath) { // Assume failure on matching to virtual root, and not on mem alloc
SetLastError(ERROR_INVALID_PARAMETER); return FALSE; }
dwBufNeeded = (DWORD) WideCharToMultiByte(CP_ACP,0, wszPath, -1, pszBuffer, 0 ,0,0);
// For MAP_EX case, we set these vars from the passed structure, else we use the raw ptrs.
if (pUrlMapEx) { pszURL = pUrlMapEx->lpszPath; *pdwSize = MAX_PATH; } else { pszURL = pszBuffer; }
// To keep this like IIS, we translate "/" to "\". We do the conversion
// only if we're using filters or if there's enough space in the buffer.
if (g_pVars->m_fFilters || *pdwSize >= dwBufNeeded) { for (int i = 0; i < (int) wcslen(wszPath); i++) { if ( wszPath[i] == L'/') wszPath[i] = L'\\'; } }
if (FALSE == g_pVars->m_fFilters) { // We check to make sure buffer is the right size because WideToMultyByte
// will overwrite pieces of pszURL even on failure, leaving pszURL's
// content invalid
if (*pdwSize < dwBufNeeded) { SetLastError(ERROR_INSUFFICIENT_BUFFER); ret = FALSE; } else { MyW2A(wszPath, pszURL, *pdwSize); ret = TRUE; } *pdwSize = dwBufNeeded; } else { // for EX case, put original URL as optional 5th parameter.
ret = FilterMapURL(pszURL, wszPath, pdwSize,dwBufNeeded, pUrlMapEx ? pszBuffer : NULL); }
if (ret && pUrlMapEx) { pUrlMapEx->cchMatchingPath = *pdwSize - 1; // don't count \0
pUrlMapEx->cchMatchingURL = strlen(pszBuffer); pUrlMapEx->dwFlags = dwPermissions; }
MyFree(wszPath); return ret; }
void CISAPI::Unload(PWSTR wszDLLName) { if (m_pfnTerminate) { __try { if (SCRIPT_TYPE_FILTER == m_scriptType) ((PFN_TERMINATEFILTER)m_pfnTerminate)(HSE_TERM_MUST_UNLOAD); else if (SCRIPT_TYPE_EXTENSION == m_scriptType) ((PFN_TERMINATEEXTENSION)m_pfnTerminate)(HSE_TERM_MUST_UNLOAD); else if (SCRIPT_TYPE_ASP == m_scriptType) ((PFN_TERMINATEASP)m_pfnTerminate)();
} __except(1) { DWORD dwExceptionCode = SCRIPT_TYPE_FILTER == m_scriptType ? IDS_HTTPD_FILT_EXCEPTION : IDS_HTTPD_EXT_EXCEPTION; PWSTR wszFunction = SCRIPT_TYPE_FILTER == m_scriptType ? L"TerminateFilter" : L"TerminateExtension";
TraceTag(ttidWebServer, "HTTPD: TerminateExtension faulted"); g_pVars->m_pLog->WriteEvent(dwExceptionCode,wszDLLName ? wszDLLName : m_wszDLLName, GetExceptionCode(),wszFunction,GetLastError()); } } MyFreeLib(m_hinst); m_hinst = NULL; m_pfnTerminate = NULL; }
BOOL CISAPI::Load(PWSTR wszPath) { m_hinst = LoadLibrary(wszPath); if (!m_hinst) return FALSE;
if (SCRIPT_TYPE_ASP == m_scriptType) { if (! (m_pfnHttpProc = GetProcAddress(m_hinst, CE_STRING("ExecuteASP")))) goto error; if (! (m_pfnTerminate = GetProcAddress(m_hinst, CE_STRING("TerminateASP")))) goto error;
return TRUE; }
if (SCRIPT_TYPE_EXTENSION == m_scriptType) { m_pfnGetVersion = GetProcAddress(m_hinst, CE_STRING("GetExtensionVersion")); m_pfnHttpProc = GetProcAddress(m_hinst, CE_STRING("HttpExtensionProc")); m_pfnTerminate = GetProcAddress(m_hinst, CE_STRING("TerminateExtension")); } else if (SCRIPT_TYPE_FILTER == m_scriptType) { m_pfnGetVersion = GetProcAddress(m_hinst, CE_STRING("GetFilterVersion")); m_pfnHttpProc = GetProcAddress(m_hinst, CE_STRING("HttpFilterProc")); m_pfnTerminate = GetProcAddress(m_hinst, CE_STRING("TerminateFilter"));
}
if (!m_pfnHttpProc || !m_pfnGetVersion) goto error;
__try { // call GetVersion immediately after load on extensions and Filters, but not ASP
// if it's a filter we need to do some flags work first
if (SCRIPT_TYPE_FILTER == m_scriptType) { HTTP_FILTER_VERSION vFilt; // IIS ignores ISAPI version info, so do we.
((PFN_GETFILTERVERSION)m_pfnGetVersion)(&vFilt); dwFilterFlags = vFilt.dwFlags;
// client didn't set the prio flags, assign them to default
// If they set more than one prio we use the highest + ignore others
if (0 == (dwFilterFlags & SF_NOTIFY_ORDER_MASK)) { dwFilterFlags |= SF_NOTIFY_ORDER_DEFAULT; } } else if (SCRIPT_TYPE_EXTENSION == m_scriptType) { HSE_VERSION_INFO vExt; // IIS ignores ISAPI version info, so do we.
((PFN_GETEXTENSIONVERSION)m_pfnGetVersion)(&vExt); }
return TRUE; } __except(1) { DWORD dwExceptionCode = SCRIPT_TYPE_FILTER == m_scriptType ? IDS_HTTPD_FILT_EXCEPTION : IDS_HTTPD_EXT_EXCEPTION; PWSTR wszFunction = SCRIPT_TYPE_FILTER == m_scriptType ? L"GetFilterVersion" : L"GetExtensionVersion";
TraceTag(ttidWebServer, "HTTPD: GetExtensionVersion faulted"); g_pVars->m_pLog->WriteEvent(dwExceptionCode,wszPath,GetExceptionCode(),wszFunction,GetLastError()); } // fall through to error
error: m_pfnGetVersion = 0; m_pfnHttpProc = 0; m_pfnTerminate = 0; MyFreeLib(m_hinst); m_hinst = 0; return FALSE; }
//**********************************************************************
// ISAPI Caching functions
//**********************************************************************
HINSTANCE CISAPICache::Load(PWSTR wszDLLName, CISAPI **ppISAPI, SCRIPT_TYPE st) { DEBUG_CODE_INIT; BOOL ret = FALSE; PISAPINODE pTrav = NULL;
EnterCriticalSection(&m_CritSec);
for (pTrav = m_pHead; pTrav != NULL; pTrav = pTrav->m_pNext) { if ( 0 == lstrcmpi(pTrav->m_pISAPI->m_wszDLLName, wszDLLName)) { TraceTag(ttidWebServer, "Found ISAPI dll in cache, name = %s, cur ref count = %d", wszDLLName, pTrav->m_pISAPI->m_cRef); break; } }
if (NULL == pTrav) { TraceTag(ttidWebServer, "ISAPI dll name = %s not found in cache, creating new entry",wszDLLName); if (NULL == (pTrav = MyAllocNZ(ISAPINODE))) myleave(1200);
if (NULL == (pTrav->m_pISAPI = new CISAPI(st))) myleave(1201);
if (NULL == (pTrav->m_pISAPI->m_wszDLLName = MySzDupW(wszDLLName))) myleave(1202);
if (! pTrav->m_pISAPI->Load(wszDLLName)) myleave(1203);
pTrav->m_pNext = m_pHead; m_pHead = pTrav; } pTrav->m_pISAPI->m_cRef++;
*ppISAPI = pTrav->m_pISAPI; ret = TRUE; done: if (!ret) { if (pTrav && pTrav->m_pISAPI) { MyFree(pTrav->m_pISAPI->m_wszDLLName); delete pTrav->m_pISAPI; }
MyFree(pTrav); }
TraceTag(ttidWebServer, "CISAPICache::LoadISAPI failed, err = %d, GLE = 0x%08x",err,GetLastError()); LeaveCriticalSection(&m_CritSec);
if (ret) return pTrav->m_pISAPI->m_hinst;
return NULL; }
void CHttpRequest::StartRemoveISAPICacheIfNeeded() { DEBUGCHK(g_pVars->m_fExtensions); if (InterlockedCompareExchange(&g_pVars->m_fISAPICacheRunning,1,0) == 0) { TraceTag(ttidWebServer, "HTTPD: ExecuteISAPI: Creating RemoveUnusedISAPIs timer"); // We never stop the timer. This is handled on call to Thread Pool Shutdown
g_pVars->m_pThreadPool->StartTimer(RemoveUnusedISAPIs,0,CACHE_SLEEP_TIMEOUT); } }
// lpv = 0 for thread that sleeps forever.
// lpv = 1 to remove all ISAPIs, called on shutdown.
DWORD WINAPI RemoveUnusedISAPIs(LPVOID lpv) { // We set lpv to 1 if we want to unload all ISAPIs during shutdown.
if (lpv) { g_pVars->m_pISAPICache->RemoveUnusedISAPIs(TRUE); return 0; }
if (g_pVars->m_pISAPICache->m_pHead != NULL) g_pVars->m_pISAPICache->RemoveUnusedISAPIs(FALSE);
g_pVars->m_pThreadPool->StartTimer(RemoveUnusedISAPIs,0,CACHE_SLEEP_TIMEOUT); return 0; }
// to milliseconds (10 * 1000)
#define FILETIME_TO_MILLISECONDS ((__int64)10000L)
// fRemoveAll = TRUE ==> remove all ISAPI's, we're shutting down
// = FALSE ==> only remove ones who aren't in use and whose time has expired
void CISAPICache::RemoveUnusedISAPIs(BOOL fRemoveAll) { PISAPINODE pTrav = NULL; PISAPINODE pFollow = NULL; PISAPINODE pDelete = NULL; SYSTEMTIME st; __int64 ft;
EnterCriticalSection(&m_CritSec); GetSystemTime(&st); SystemTimeToFileTime(&st,(FILETIME*) &ft);
// Figure out what time it was g_pVars->m_dwCacheSleep milliseconds ago.
// Elements that haven't been used since then and that have no references
// are deleted.
ft -= FILETIME_TO_MILLISECONDS * g_pVars->m_dwCacheSleep;
for (pTrav = m_pHead; pTrav != NULL; ) { if (fRemoveAll || (pTrav->m_pISAPI->m_cRef == 0 && pTrav->m_pISAPI->m_ftLastUsed < ft)) { TraceTag(ttidWebServer, "Freeing unused ISAPI Dll %s",pTrav->m_pISAPI->m_wszDLLName);
pTrav->m_pISAPI->Unload(); delete pTrav->m_pISAPI;
if (pFollow) pFollow->m_pNext = pTrav->m_pNext; else m_pHead = pTrav->m_pNext;
pDelete = pTrav; pTrav = pTrav->m_pNext; MyFree(pDelete); } else { if (pTrav->m_pISAPI->m_cRef == 0) pTrav->m_pISAPI->CoFreeUnusedLibrariesIfASP();
pFollow = pTrav; pTrav = pTrav->m_pNext; } }
LeaveCriticalSection(&m_CritSec); }
|