2025-04-27 07:49:33 -04:00

773 lines
24 KiB
C++

/*++
Copyright (c) 1999 Microsoft Corporation
Module Name :
ISAPINative.cxx
Abstract :
The code in this file does all the work for the ISAPI handler,
using methods of ISAPINativeCallBack as needed.
Author:
Anil Ruia (anilr) 31-Aug-1999
Project:
Web Server
--*/
/************************************************************
* Include Headers
************************************************************/
#include "precomp.hxx"
#include <httpext.h>
#include "ISAPINative.hxx"
/********************************************************************++
Global Entry Points for callback from DT
++********************************************************************/
# if !defined( dllexp)
# define dllexp __declspec( dllexport)
# endif // !defined( dllexp)
/********************************************************************++
++********************************************************************/
// this function is called by the static initializer of the ISAPIHandler
// class to initialize all the data structures
dllexp void NativeIsapiInitialize(void)
{
if (!IsapiInitialized)
{
IsapiInitialized = TRUE;
// Initialize the lookup table
for (int i=0;i<TABLE_SIZE;i++)
dllLookupTable[i] = NULL;
// Initialize the critical section
InitializeCriticalSection(&dllTableLock);
OpenProcessToken(GetCurrentProcess(), TOKEN_ALL_ACCESS, &PROC_TOKEN);
}
}
// This method is the one called by the ISAPI handler to do the real work
dllexp int NativeIsapiProcessRequest(int NativeContext, LPTSTR dllName)
{
ULONG rc = NO_ERROR;
xspmrt::_ISAPINativeCallback *m_pISAPI;
// Create the Managed Callback Object used to get stuff from managed
// code
rc = CoCreateInstance(__uuidof(xspmrt::ISAPINativeCallback),
NULL, CLSCTX_INPROC_SERVER,
__uuidof(xspmrt::_ISAPINativeCallback),
(LPVOID *)&m_pISAPI);
if (FAILED(rc))
{
DBGPRINTF((DBG_CONTEXT, "CoCreateInstance of ISAPINativeCallback failed, hr %08x\n", rc));
return InternalServerError;
}
DBG_ASSERT(m_pISAPI != NULL);
// Initialize the Managed Callback
m_pISAPI->Init(NativeContext);
// try to locate the dll in the lookup table, if not make a new entry
// BUGBUG: not always InternalServerError, sometimes BadGateway
dllData *dll = FindOrInsertDllInLookupTable(dllName);
if (dll == NULL)
return GetLastError();
// Setup the EXTENSION_CONTROL_BLOCK
LPEXTENSION_CONTROL_BLOCK lpECB;
lpECB = new EXTENSION_CONTROL_BLOCK;
if (lpECB == NULL)
{
DBGPRINTF((DBG_CONTEXT, "Out of memory\n"));
return InternalServerError;
}
// setup the request specific structure
requestData *req;
req = new requestData;
if (req == NULL)
{
DBGPRINTF((DBG_CONTEXT, "Out of memory\n"));
return InternalServerError;
}
req->m_pISAPI = m_pISAPI;
req->lpECB = lpECB;
req->PFN_HSE_IO_COMPLETION_CALLBACK = NULL;
req->pContext = NULL;
req->isPending = FALSE;
req->writeLock = CreateSemaphore(NULL, 1, 1, NULL);
if (req->writeLock == NULL)
{
DBGPRINTF((DBG_CONTEXT, "Cannot create semaphore\n"));
delete req;
return InternalServerError;
}
req->reqDll = dll;
// initialize ECB to pass to HttpExtensionProc
lpECB->cbSize = sizeof(EXTENSION_CONTROL_BLOCK);
lpECB->dwVersion = 0x00050000; // BUGBUG: what version?
lpECB->ConnID = (HCONN)req;
lpECB->dwHttpStatusCode = 200;
LPSTR LogData;
m_pISAPI->GetLogData(&LogData);
if (LogData == NULL)
LogData = "";
if (strlen(LogData) < HSE_LOG_BUFFER_LEN)
strcpy(lpECB->lpszLogData, LogData);
else
memcpy(lpECB->lpszLogData, LogData, HSE_LOG_BUFFER_LEN);
m_pISAPI->GetServerVariable("REQUEST_METHOD", &lpECB->lpszMethod);
m_pISAPI->GetServerVariable("QUERY_STRING", &lpECB->lpszQueryString);
m_pISAPI->GetServerVariable("PATH_INFO", &lpECB->lpszPathInfo);
m_pISAPI->GetServerVariable("PATH_TRANSLATED", &lpECB->lpszPathTranslated);
int buflen;
m_pISAPI->ReadRequestLength(&buflen);
LPBYTE bufptr;
m_pISAPI->ReadRequestBytes((LPSTR *)&bufptr);
DBGPRINTF((DBG_CONTEXT, "Data read: %s\n", bufptr));
lpECB->cbTotalBytes = buflen;
lpECB->cbAvailable = buflen;
lpECB->lpbData = bufptr;
m_pISAPI->GetServerVariable("CONTENT_TYPE", &lpECB->lpszContentType);
lpECB->GetServerVariable = InternalGetServerVariable;
lpECB->WriteClient = InternalWriteClient;
lpECB->ReadClient = InternalReadClient;
lpECB->ServerSupportFunction = InternalServerSupportFunction;
int returnCode;
// Impersonate the logged on user
HANDLE token;
m_pISAPI->UserToken((int *)&token);
if (token == NULL)
token = PROC_TOKEN;
if (!ImpersonateLoggedOnUser(token))
DBGPRINTF((DBG_CONTEXT, "Impersonation failed\n"));
switch(dll->HttpExtensionProc(lpECB))
{
case HSE_STATUS_SUCCESS:
case HSE_STATUS_SUCCESS_AND_KEEP_CONN:
case HSE_STATUS_ERROR:
returnCode = lpECB->dwHttpStatusCode;
break;
case HSE_STATUS_PENDING:
req->isPending = TRUE;
returnCode = Pending;
break;
default:
DBGPRINTF((DBG_CONTEXT, "Extension %S returned unknown status\n", dllName));
returnCode = BadGateway;
}
// stop impersonating
if (!SetThreadToken(NULL, NULL))
DBGPRINTF((DBG_CONTEXT, "Deimpersonation failed\n"));
if(returnCode != Pending)
CleanupReqStrs(req);
return returnCode;
}
void CleanupReqStrs(requestData *req)
{
xspmrt::_ISAPINativeCallback *m_pISAPI = req->m_pISAPI;
LPEXTENSION_CONTROL_BLOCK lpECB = req->lpECB;
// cleanup all request specific structures. dll specific structures
// will be cleaned up when the process terminates
m_pISAPI->Release();
CloseHandle(req->writeLock);
delete req;
delete lpECB;
}
int GetHashCode(LPWSTR str)
{
// BUGBUG: TODO
return 0;
}
dllData *FindOrInsertDllInLookupTable(LPWSTR dllName)
{
int position = GetHashCode(dllName) % TABLE_SIZE;
EnterCriticalSection(&dllTableLock);
// look for the dll first
dllData *dll = dllLookupTable[position];
while (dll != NULL)
{
if(!_wcsicmp(dllName, dll->lpLibFileName))
goto End;
dll = dll->nextDll;
}
// not found, create a new entry
dll = new dllData;
if (dll == NULL)
{
DBGPRINTF((DBG_CONTEXT, "Out of memory\n"));
SetLastError(InternalServerError);
goto End;
}
dll->lpLibFileName = new TCHAR[wcslen(dllName) + 1];
if (dll->lpLibFileName == NULL)
{
DBGPRINTF((DBG_CONTEXT, "Out of memory\n"));
delete dll;
dll = NULL;
SetLastError(InternalServerError);
goto End;
}
wcscpy(dll->lpLibFileName, dllName);
// Load the dll
dll->hModule = LoadLibrary(dllName);
if (dll->hModule == NULL)
{
DBGPRINTF((DBG_CONTEXT, "Cannot load dll %S\n", dllName));
delete dll->lpLibFileName;
delete dll;
dll = NULL;
SetLastError(BadGateway);
goto End;
}
// Get pointers to the entrypoint functions
dll->GetExtensionVersion = (BOOL (WINAPI *)(HSE_VERSION_INFO *))GetProcAddress(dll->hModule, "GetExtensionVersion");
dll->HttpExtensionProc = (DWORD (WINAPI *)(LPEXTENSION_CONTROL_BLOCK))GetProcAddress(dll->hModule, "HttpExtensionProc");
dll->TerminateExtension = (BOOL (WINAPI *)(DWORD))GetProcAddress(dll->hModule, "TerminateExtension");
// The GetExtensionVersion and HttpExtensionProc functions are not
//optional
if ((dll->GetExtensionVersion == NULL) ||
(dll->HttpExtensionProc == NULL))
{
DBGPRINTF((DBG_CONTEXT, "%S not an ISAPI dll: Entry-point function missing\n", dllName));
FreeLibrary(dll->hModule);
delete dll->lpLibFileName;
delete dll;
dll = NULL;
SetLastError(BadGateway);
goto End;
}
// Call GetExtensionVersion, BUGBUG: Dont know what to do with it
// Maybe add it to some log
if (!dll->GetExtensionVersion(&dll->pVer))
{
DBGPRINTF((DBG_CONTEXT, "GetExtensionVersion returned false, cannot use the dll %S\n", dllName));
FreeLibrary(dll->hModule);
delete dll->lpLibFileName;
delete dll;
dll = NULL;
SetLastError(BadGateway);
goto End;
}
// Insert into lookup table so that future requests to the same dll
// do not have to reload it
dll->nextDll = dllLookupTable[position];
dllLookupTable[position] = dll;
End:
LeaveCriticalSection(&dllTableLock);
return dll;
}
// All the cleanup to be done when iiswp is terminating.
void IsapiNativeCleanup()
{
if (IsapiInitialized)
{
CleanupDllLookupTable();
DeleteCriticalSection(&dllTableLock);
CloseHandle(PROC_TOKEN);
IsapiInitialized = FALSE;
}
}
// cleanup of dll lookup table. Go through the table, looking for valid
// entries, tell those dll's to cleanup, unload them and free corresponding
// data structure
void CleanupDllLookupTable()
{
EnterCriticalSection(&dllTableLock);
for (int i=0; i<TABLE_SIZE; i++)
{
dllData *dll = dllLookupTable[i];
while (dll != NULL)
{
if (dll->TerminateExtension != NULL)
dll->TerminateExtension(HSE_TERM_MUST_UNLOAD);
FreeLibrary(dll->hModule);
delete dll->lpLibFileName;
dllData *nextDll = dll->nextDll;
delete dll;
dll = nextDll;
}
dllLookupTable[i] = NULL;
}
LeaveCriticalSection(&dllTableLock);
}
// The GetServerVariable function passed to ISAPI extensions
BOOL WINAPI InternalGetServerVariable(HCONN hConn,
LPSTR lpszVariableName,
LPVOID lpvBuffer,
LPDWORD lpdwSizeofBuffer)
{
// check if valid identifier
if ((hConn == NULL) ||
(lpszVariableName == NULL) ||
(lpvBuffer == NULL) ||
(lpdwSizeofBuffer == NULL))
{
SetLastError(ERROR_INVALID_PARAMETER);
return FALSE;
}
xspmrt::_ISAPINativeCallback *m_pISAPI = ((requestData *)hConn)->m_pISAPI;
// Metabase related variables, not supported
if (!strcmp(lpszVariableName, "APPL_MD_PATH") ||
!strcmp(lpszVariableName, "INSTANCE_ID") ||
!strcmp(lpszVariableName, "INSTANCE_META_PATH"))
{
SetLastError(ERROR_NO_DATA);
return FALSE;
}
LPSTR lpszValue;
// BUGBUG: added for FP LWS
if (!strcmp(lpszVariableName, "HTTP_FRONTPAGE_LWS_PATH"))
m_pISAPI->MapPath("/", &lpszValue);
else if (!strcmp(lpszVariableName, "HTTP_FRONTPAGE_LWS_SCRIPT_NAME"))
m_pISAPI->GetServerVariable("SCRIPT_NAME", &lpszValue);
else if (!strcmp(lpszVariableName, "CONTENT_ENCODING"))
m_pISAPI->GetServerVariable("CONTENT_TYPE", &lpszValue);
else
m_pISAPI->GetServerVariable(lpszVariableName, &lpszValue);
DBGPRINTF((DBG_CONTEXT, "Var %s=%s\n", lpszVariableName, lpszValue));
if (lpszValue != NULL)
if (*lpdwSizeofBuffer > strlen(lpszValue))
{
strcpy((LPSTR)lpvBuffer, lpszValue);
*lpdwSizeofBuffer = strlen(lpszValue) + 1;
}
else
{
*lpdwSizeofBuffer = strlen(lpszValue) + 1;
SetLastError(ERROR_INSUFFICIENT_BUFFER);
return FALSE;
}
else
{
SetLastError(ERROR_NO_DATA);
return FALSE;
}
return TRUE;
}
// The ReadClient function passed to ISAPI extensions. Doesn't do
// anything. All the request body is supplied to the client at the start.
BOOL WINAPI InternalReadClient(HCONN hConn, LPVOID lpvBuffer, LPDWORD lpdwSize)
{
if ((hConn == NULL) ||
(lpvBuffer == NULL) ||
(lpdwSize == NULL))
{
SetLastError(ERROR_INVALID_PARAMETER);
return FALSE;
}
*lpdwSize = 0;
return TRUE;
// We are not doing async reads. Providing all data as part of
// initial call to HttpExtensionProc. So, no more data now.
}
// The WriteClient function passed to ISAPI extensions. Does both synchronous
// and asynchronous I/O
BOOL WINAPI InternalWriteClient(HCONN hConn, LPVOID lpvBuffer, LPDWORD lpdwSize, DWORD dwSync)
{
if ((hConn == NULL) ||
(lpvBuffer == NULL) ||
(lpdwSize == NULL))
{
SetLastError(ERROR_INVALID_PARAMETER);
return FALSE;
}
requestData *req = (requestData *)hConn;
xspmrt::_ISAPINativeCallback *m_pISAPI = req->m_pISAPI;
if (dwSync != HSE_IO_ASYNC)
{
WaitForSingleObject(req->writeLock, INFINITE);
m_pISAPI->Write((int)lpvBuffer, *lpdwSize);
if (*lpdwSize <= 0x1000) // BUGBUG: DBGPRINTF crashes on large prints
DBGPRINTF((DBG_CONTEXT, "Data written: %s\n", lpvBuffer));
else
DBGPRINTF((DBG_CONTEXT, "%d bytes written.\n", *lpdwSize));
ReleaseSemaphore(req->writeLock, 1, NULL);
}
else // if (dwSync == HSE_IO_ASYNC)
{
if (req->PFN_HSE_IO_COMPLETION_CALLBACK == NULL)
{
DBGPRINTF((DBG_CONTEXT, "No callback registered\n"));
SetLastError(ERROR_INVALID_PARAMETER);
return FALSE;
}
asyncWriteStr *asyncWriteData = new asyncWriteStr;
if (asyncWriteData == NULL)
{
DBGPRINTF((DBG_CONTEXT, "Out of memory\n"));
SetLastError(ERROR_INVALID_PARAMETER);
return FALSE;
}
asyncWriteData->req = req;
asyncWriteData->bufptr = lpvBuffer;
asyncWriteData->buflen = *lpdwSize;
HANDLE asyncWriteThread = CreateThread(NULL,
0,
asyncWriteFunc,
(LPVOID)asyncWriteData,
0,
NULL);
CloseHandle(asyncWriteThread);
}
return TRUE;
}
// The function which does the async Write (in a separate thread).
DWORD WINAPI asyncWriteFunc(LPVOID lpParameter)
{
asyncWriteStr *asyncWriteData = (asyncWriteStr *)lpParameter;
requestData *req = asyncWriteData->req;
xspmrt::_ISAPINativeCallback *m_pISAPI = req->m_pISAPI;
WaitForSingleObject(req->writeLock, INFINITE);
m_pISAPI->Write((int)asyncWriteData->bufptr, asyncWriteData->buflen);
ReleaseSemaphore(req->writeLock, 1, NULL);
req->PFN_HSE_IO_COMPLETION_CALLBACK(req->lpECB, req->pContext, asyncWriteData->buflen, 0);
delete asyncWriteData;
return 0;
}
// The ServerSupportFunction passed to ISAPI extensions
// It looks at the particular task requested and performs it
BOOL WINAPI InternalServerSupportFunction(HCONN ConnID,
DWORD dwHSERRequest,
LPVOID lpvBuffer,
LPDWORD lpdwSize,
LPDWORD lpdwDataType)
{
if (ConnID == NULL)
{
SetLastError(ERROR_INVALID_PARAMETER);
return FALSE;
}
requestData *req = (requestData *)ConnID;
xspmrt::_ISAPINativeCallback *m_pISAPI = req->m_pISAPI;
switch(dwHSERRequest)
{
case HSE_APPEND_LOG_PARAMETER:
m_pISAPI->AppendToLog((LPSTR)lpvBuffer);
return TRUE;
case HSE_REQ_ABORTIVE_CLOSE:
return TRUE;
case HSE_REQ_ASYNC_READ_CLIENT:
// No async reads supported right now
SetLastError(ERROR_INVALID_PARAMETER);
return FALSE;
case HSE_REQ_CLOSE_CONNECTION:
m_pISAPI->Close();
return TRUE;
case HSE_REQ_DONE_WITH_SESSION:
if (req->isPending)
{
m_pISAPI->Complete();
CleanupReqStrs(req);
}
else
{
SetLastError(ERROR_INVALID_PARAMETER);
return FALSE;
}
return TRUE;
case HSE_REQ_GET_CERT_INFO_EX:
// BUGBUG: TODO
SetLastError(ERROR_INVALID_PARAMETER);
return FALSE;
case HSE_REQ_GET_IMPERSONATION_TOKEN:
if (lpvBuffer == NULL)
{
SetLastError(ERROR_INVALID_PARAMETER);
return FALSE;
}
HANDLE token;
m_pISAPI->UserToken((int *)&token);
if (token != NULL)
*(PHANDLE)lpvBuffer = token;
else
*(PHANDLE)lpvBuffer = PROC_TOKEN;
return TRUE;
case HSE_REQ_GET_SSPI_INFO:
// BUGBUG: TODO
SetLastError(ERROR_INVALID_PARAMETER);
return FALSE;
case HSE_REQ_IO_COMPLETION:
if (lpvBuffer == NULL)
{
SetLastError(ERROR_INVALID_PARAMETER);
return FALSE;
}
req->PFN_HSE_IO_COMPLETION_CALLBACK =
(void (WINAPI *)(LPEXTENSION_CONTROL_BLOCK, PVOID, DWORD, DWORD))
lpvBuffer;
req->pContext = lpdwDataType;
return TRUE;
case HSE_REQ_IS_KEEP_CONN:
if (lpvBuffer == NULL)
{
SetLastError(ERROR_INVALID_PARAMETER);
return FALSE;
}
m_pISAPI->KeepAlive((BOOL *)lpvBuffer);
return TRUE;
case HSE_REQ_MAP_URL_TO_PATH:
case HSE_REQ_MAP_URL_TO_PATH_EX:
{
if ((lpvBuffer == NULL) ||
(lpdwSize == NULL) ||
(lpdwDataType == NULL))
{
SetLastError(ERROR_INVALID_PARAMETER);
return FALSE;
}
LPSTR lpszValue;
m_pISAPI->MapPath((LPSTR)lpvBuffer, &lpszValue);
if (*lpdwSize > strlen(lpszValue))
{
strcpy((LPSTR)lpvBuffer, lpszValue);
*lpdwSize = strlen(lpszValue) + 1;
LPHSE_URL_MAPEX_INFO mapInfo = (LPHSE_URL_MAPEX_INFO) lpdwDataType;
if (strlen(lpszValue) < MAX_PATH)
strcpy(mapInfo->lpszPath, lpszValue);
else
{
SetLastError(ERROR_INSUFFICIENT_BUFFER);
return FALSE;
}
// BUGBUG: Need to populate dwFlags by actually finding
// permissions on this URL
mapInfo->dwFlags = 0;
mapInfo->cchMatchingPath = 0;
mapInfo->cchMatchingURL = 0;
mapInfo->dwReserved1 = 0;
mapInfo->dwReserved2 = 0;
return TRUE;
}
else
{
*lpdwSize = strlen(lpszValue) + 1;
SetLastError(ERROR_INSUFFICIENT_BUFFER);
return FALSE;
}
}
case HSE_REQ_REFRESH_ISAPI_ACL:
// BUGBUG: TODO
SetLastError(ERROR_INVALID_PARAMETER);
return FALSE;
case HSE_REQ_SEND_URL_REDIRECT_RESP:
case HSE_REQ_SEND_URL:
if (lpvBuffer == NULL)
{
SetLastError(ERROR_INVALID_PARAMETER);
return FALSE;
}
m_pISAPI->Redirect((LPSTR)lpvBuffer);
return TRUE;
case HSE_REQ_SEND_RESPONSE_HEADER:
if ((lpvBuffer == NULL) ||
(lpdwDataType == NULL))
{
SetLastError(ERROR_INVALID_PARAMETER);
return FALSE;
}
// BUGBUG: deprecated in IIS 5.0, but added on because FP uses it
WaitForSingleObject(req->writeLock, INFINITE);
m_pISAPI->SendStatus((LPSTR)lpvBuffer);
m_pISAPI->SendHeaders((LPSTR)lpdwDataType);
ReleaseSemaphore(req->writeLock, 1, NULL);
return TRUE;
case HSE_REQ_SEND_RESPONSE_HEADER_EX:
{
if (lpvBuffer == NULL)
{
SetLastError(ERROR_INVALID_PARAMETER);
return FALSE;
}
LPHSE_SEND_HEADER_EX_INFO headers = (LPHSE_SEND_HEADER_EX_INFO) lpvBuffer;
WaitForSingleObject(req->writeLock, INFINITE);
m_pISAPI->SendStatus((LPSTR)headers->pszStatus);
m_pISAPI->SendHeaders((LPSTR)headers->pszHeader);
// BUGBUG: below closes the connection immediately, we want to
// be able to close connection at the end of the request
// if (!headers->fKeepConn)
// m_pISAPI->Close();
ReleaseSemaphore(req->writeLock, 1, NULL);
return TRUE;
}
case HSE_REQ_TRANSMIT_FILE:
if (lpvBuffer == NULL)
{
SetLastError(ERROR_INVALID_PARAMETER);
return FALSE;
}
HSE_TF_INFO *fileInfo = (HSE_TF_INFO *)lpvBuffer;
if ((req->PFN_HSE_IO_COMPLETION_CALLBACK == NULL) &&
(fileInfo->pfnHseIO == NULL))
{
DBGPRINTF((DBG_CONTEXT, "No callback registered\n"));
SetLastError(ERROR_INVALID_PARAMETER);
return FALSE;
}
xFileStr *xFileData = new xFileStr;
if (xFileData == NULL)
{
DBGPRINTF((DBG_CONTEXT, "Out of memory\n"));
return FALSE;
}
xFileData->req = req;
xFileData->fileInfo = fileInfo;
HANDLE xFileThread = CreateThread(NULL,
0,
xFileFunc,
(LPVOID)xFileData,
0,
NULL);
CloseHandle(xFileThread);
return TRUE;
}
DBGPRINTF((DBG_CONTEXT, "Unknown Server Request\n"));
SetLastError(ERROR_INVALID_PARAMETER);
return FALSE;
}
DWORD WINAPI xFileFunc(LPVOID lpParameter)
{
xFileStr *xFileData = (xFileStr *) lpParameter;
HSE_TF_INFO *fileInfo = xFileData->fileInfo;
requestData *req = xFileData->req;
xspmrt::_ISAPINativeCallback *m_pISAPI = req->m_pISAPI;
WaitForSingleObject(req->writeLock, INFINITE);
m_pISAPI->SendStatus((LPSTR)fileInfo->pszStatusCode);
m_pISAPI->SendHeaders((LPSTR)fileInfo->pHead);
// a value of 0 means send the entire file. I don't know if putting
// infinite will work.
// if (fileInfo->BytesToWrite == 0)
// fileInfo->BytesToWrite = INFINITE;
m_pISAPI->WriteFile(fileInfo->hFile,
fileInfo->Offset,
fileInfo->BytesToWrite);
m_pISAPI->Write((int)fileInfo->pTail, fileInfo->TailLength);
// BUGBUG: this will disconnect right now before sending the data
//if (fileInfo->dwFlags & HSE_IO_DISCONNECT_AFTER_SEND)
// m_pISAPI->Close();
ReleaseSemaphore(req->writeLock, 1, NULL);
// BUGBUG: No way to tell currently how many bytes were written
DWORD cbIO = 0;
if (fileInfo->pfnHseIO != NULL)
fileInfo->pfnHseIO(req->lpECB, fileInfo->pContext, cbIO, 0);
else
req->PFN_HSE_IO_COMPLETION_CALLBACK(req->lpECB, req->pContext, cbIO, 0);
return 0;
}
// Entrypoint to enable FrontPage Light Weight Server
BOOL __cdecl FPIsLWSEnabled(LPEXTENSION_CONTROL_BLOCK *lpECB)
{
return TRUE;
}