2020-09-30 16:53:55 +02:00

516 lines
15 KiB
C++

/*++
Copyright (c) 2002 Microsoft Corporation
Module Name:
apsvcc.cpp
Abstract:
Implements the ServiceMain & Service Control Handler for the Auto-Proxy Service.
Author:
Biao Wang (biaow) 10-May-2002
--*/
#include "wininetp.h"
#include <winsvc.h>
#include "apsvcdefs.h"
#include "apsvc.h"
#include "rpcsrv.h"
// global variables
SERVICE_STATUS_HANDLE g_hSvcStatus = NULL;
HANDLE g_hSvcStopEvent = NULL;
HANDLE g_hSvcStopWait = NULL;
AUTOPROXY_RPC_SERVER* g_pRpcSrv = NULL;
#ifdef ENABLE_DEBUG
HKEY g_hKeySvcParams = NULL;
#endif
BOOL g_fShutdownInProgress = FALSE;
CCritSec g_SvcShutdownCritSec;
VOID SvcCleanUp(VOID);
VOID SvcHandlePowerEvents(
WPARAM wParam,
LPARAM lParam
)
{
UNREFERENCED_PARAMETER(lParam);
if (g_pRpcSrv == NULL)
{
LOG_EVENT(AP_ERROR, MSG_WINHTTP_AUTOPROXY_SVC_DATA_CORRUPT);
return;
}
switch (wParam)
{
case PBT_APMQUERYSUSPEND:
g_pRpcSrv->Pause();
break;
case PBT_APMQUERYSUSPENDFAILED:
case PBT_APMRESUMEAUTOMATIC:
case PBT_APMRESUMESUSPEND:
g_pRpcSrv->Resume();
break;
case PBT_APMRESUMECRITICAL:
g_pRpcSrv->Refresh();
break;
case PBT_APMSUSPEND:
break;
default:
;
}
}
DWORD WINAPI SvcControl(
DWORD dwControl, // requested control code
DWORD dwEventType, // event type
LPVOID lpEventData, // event data
LPVOID lpContext // user-defined context data
)
{
UNREFERENCED_PARAMETER(lpContext);
DWORD dwError = NO_ERROR;
LPSERVICE_STATUS pSvcStatus = NULL;
if ((g_pRpcSrv == NULL) ||
((pSvcStatus = g_pRpcSrv->GetServiceStatus()) == NULL) ||
(g_hSvcStatus == NULL))
{
LOG_EVENT(AP_ERROR, MSG_WINHTTP_AUTOPROXY_SVC_DATA_CORRUPT);
return dwError;
}
switch (dwControl)
{
case SERVICE_CONTROL_INTERROGATE:
break;
case SERVICE_CONTROL_STOP:
//#ifdef ENABLE_DEBUG
// LOG_DEBUG_EVENT(AP_WARNING, "[stopping-debug] received Service-Control-Stop from SCM");
//#endif
if (g_hSvcStopEvent)
{
::SetEvent(g_hSvcStopEvent);
//#ifdef ENABLE_DEBUG
// LOG_DEBUG_EVENT(AP_WARNING, "[stopping-debug] signed the Wait-Stop event");
//#endif
// rest of the stop work should be picked up by SvcTimerAndStop() callback
}
pSvcStatus->dwCurrentState = SERVICE_STOP_PENDING;
pSvcStatus->dwWin32ExitCode = NO_ERROR;
pSvcStatus->dwCheckPoint = 0;
pSvcStatus->dwWaitHint = AUTOPROXY_SERVICE_STOP_WAIT_HINT;
break;
case SERVICE_CONTROL_POWEREVENT:
SvcHandlePowerEvents(dwEventType, (LPARAM)lpEventData);
break;
default:
dwError = ERROR_CALL_NOT_IMPLEMENTED;
}
::SetServiceStatus(g_hSvcStatus, pSvcStatus);
return dwError;
}
VOID CALLBACK SvcTimerAndStop(
PVOID lpParameter, // thread data
BOOLEAN TimerOrWaitFired // reason
)
{
UNREFERENCED_PARAMETER(lpParameter);
//#ifdef ENABLE_DEBUG
// LOG_DEBUG_EVENT(AP_WARNING, "[stopping-debug] Stop Callback function called");
//#endif
if (g_fShutdownInProgress)
{
//#ifdef ENABLE_DEBUG
// LOG_DEBUG_EVENT(AP_WARNING, "[stopping-debug] svc is shuting down, return...");
//#endif
return;
}
g_SvcShutdownCritSec.Lock();
if (g_fShutdownInProgress)
{
goto exit;
}
LPSERVICE_STATUS pSvcStatus = NULL;
if ((g_pRpcSrv == NULL) ||
((pSvcStatus = g_pRpcSrv->GetServiceStatus()) == NULL) ||
(g_hSvcStatus == NULL))
{
LOG_EVENT(AP_ERROR, MSG_WINHTTP_AUTOPROXY_SVC_DATA_CORRUPT);
goto exit;
}
if (TimerOrWaitFired == TRUE) // timed-out
{
//#ifdef ENABLE_DEBUG
// LOG_DEBUG_EVENT(AP_WARNING, "[stopping-debug] TimerOrWaitFired = TRUE");
//#endif
DWORD dwIdleTimeout = AUTOPROXY_SVC_IDLE_TIMEOUT * 60 * 1000;
#ifdef ENABLE_DEBUG
DWORD dwRegVal;
DWORD dwValType;
DWORD dwValSize = sizeof(dwRegVal);
if (::RegQueryValueExW(g_hKeySvcParams,
L"IdleTimeout",
NULL,
&dwValType,
(LPBYTE)&dwRegVal,
&dwValSize) == ERROR_SUCCESS)
{
if ((dwValType == REG_DWORD) && (dwRegVal != 0))
{
dwIdleTimeout = dwRegVal; // the value unit from registry is milli-sec.
}
}
#endif
if (!g_pRpcSrv->IsIdle(dwIdleTimeout)) // Has been idle for 3 minutes?
{
//#ifdef ENABLE_DEBUG
// LOG_DEBUG_EVENT(AP_WARNING, "[stopping-debug] idle timeout not yet expired, quit");
//#endif
goto exit;
}
WCHAR wIdleTimeout[16] = {0};
::swprintf(wIdleTimeout, L"%d", AUTOPROXY_SVC_IDLE_TIMEOUT);
LOG_EVENT(AP_INFO, MSG_WINHTTP_AUTOPROXY_SVC_IDLE_TIMEOUT, wIdleTimeout);
// fall thru to kick start shutdown when idle for 3 minutes
}
g_fShutdownInProgress = TRUE;
pSvcStatus->dwCurrentState = SERVICE_STOP_PENDING;
pSvcStatus->dwWin32ExitCode = NO_ERROR;
pSvcStatus->dwCheckPoint = 1;
pSvcStatus->dwWaitHint = AUTOPROXY_SERVICE_STOP_WAIT_HINT;
if (g_pRpcSrv->Close() == TRUE)
{
#ifdef ENABLE_DEBUG
DWORD dwShutdownDelay = 0;
DWORD dwRegVal;
DWORD dwValType;
DWORD dwValSize = sizeof(dwRegVal);
if (::RegQueryValueExW(g_hKeySvcParams,
L"ShutdownDelay",
NULL,
&dwValType,
(LPBYTE)&dwRegVal,
&dwValSize) == ERROR_SUCCESS)
{
if ((dwValType == REG_DWORD) && (dwRegVal != 0))
{
dwShutdownDelay = dwRegVal; // the value unit from registry is milli-sec.
}
}
::Sleep(dwShutdownDelay);
#endif
pSvcStatus->dwCurrentState = SERVICE_STOPPED;
pSvcStatus->dwWaitHint = 0;
::SetServiceStatus(g_hSvcStatus, pSvcStatus);
SvcCleanUp();
}
else
{
// so for some reason we couldn't shut down gracefully, and if the STOP is initiated
// from SCM, then we will be stuck in the STOPPING state (if we are the last service
// in the svchost.exe then we will be forcefully shutdown in 30 seconds). If we are
// idle-stopping, we will revert course and transit back to the RUNNING state. In either
// case we should resume the RPC service.
g_pRpcSrv->Resume();
if (TimerOrWaitFired == TRUE) // we are idle-stopping, transit back to RUNNING
{
pSvcStatus->dwCurrentState = SERVICE_RUNNING;
pSvcStatus->dwWin32ExitCode = NO_ERROR;
pSvcStatus->dwCheckPoint = 0;
pSvcStatus->dwWaitHint = 0;
::SetServiceStatus(g_hSvcStatus, pSvcStatus);
g_fShutdownInProgress = FALSE; // we still allow future shutdown attempts
}
else
{
// we unregister the idle timer since there is no point of doing idle-shutdown
if (g_hSvcStopWait)
{
::UnregisterWaitEx(g_hSvcStopWait, NULL);
g_hSvcStopWait = NULL;
}
}
}
exit:
g_SvcShutdownCritSec.Unlock();
return;
}
VOID AutoProxySvcUnload(VOID)
{
g_SvcShutdownCritSec.FreeLock();
}
EXTERN_C VOID WINAPI WinHttpAutoProxySvcMain(
DWORD dwArgc, // number of arguments
LPWSTR *lpwszArgv // array of arguments
)
{
UNREFERENCED_PARAMETER(dwArgc);
UNREFERENCED_PARAMETER(lpwszArgv);
DWORD dwError;
static SERVICE_STATUS SvcStatus;
SvcStatus.dwServiceType = SERVICE_WIN32_SHARE_PROCESS; // we share svchost.exe w/ other Local Services
SvcStatus.dwControlsAccepted = SERVICE_ACCEPT_STOP | SERVICE_ACCEPT_POWEREVENT;
SvcStatus.dwWin32ExitCode = NO_ERROR;
SvcStatus.dwServiceSpecificExitCode = 0;
SvcStatus.dwCheckPoint = 0;
SvcStatus.dwWaitHint = 0;
InitializeEventLog();
g_hSvcStatus = ::RegisterServiceCtrlHandlerExW(WINHTTP_AUTOPROXY_SERVICE_NAME,
SvcControl,
NULL);
if (g_hSvcStatus == NULL)
{
dwError = ::GetLastError();
LOG_EVENT(AP_ERROR,
MSG_WINHTTP_AUTOPROXY_SVC_WIN32_ERROR,
L"RegisterServiceCtrlHandlerExW()",
dwError);
goto cleanup;
}
if (!g_SvcShutdownCritSec.IsInitialized())
{
if (g_SvcShutdownCritSec.Init() == FALSE)
{
LOG_EVENT(AP_ERROR, MSG_WINHTTP_AUTOPROXY_SVC_FAILED_ALLOCATE_RESOURCE);
goto cleanup;
}
}
g_hSvcStopEvent = ::CreateEvent(NULL,
FALSE, // manual reset
FALSE, // not signalled
NULL);
if (g_hSvcStopEvent == NULL)
{
dwError = ::GetLastError();
LOG_EVENT(AP_ERROR, MSG_WINHTTP_AUTOPROXY_SVC_FAILED_ALLOCATE_RESOURCE);
goto cleanup;
}
g_pRpcSrv = new AUTOPROXY_RPC_SERVER;
if (g_pRpcSrv == NULL)
{
dwError = ERROR_NOT_ENOUGH_MEMORY;
LOG_EVENT(AP_ERROR, MSG_WINHTTP_AUTOPROXY_SVC_FAILED_ALLOCATE_RESOURCE);
goto cleanup;
}
#ifdef ENABLE_DEBUG
long lError;
if ((lError = ::RegOpenKeyExW(HKEY_LOCAL_MACHINE,
L"SYSTEM\\CurrentControlSet\\Services\\WinHttpAutoProxySvc\\Parameters",
0,
KEY_QUERY_VALUE,
&g_hKeySvcParams)) != ERROR_SUCCESS)
{
g_hKeySvcParams = NULL;
LOG_EVENT(AP_WARNING,
MSG_WINHTTP_AUTOPROXY_SVC_WIN32_ERROR,
L"RegOpenKeyExW()",
lError);
}
#endif
SvcStatus.dwCurrentState = SERVICE_START_PENDING;
SvcStatus.dwCheckPoint = 0;
SvcStatus.dwWaitHint = AUTOPROXY_SERVICE_START_WAIT_HINT;
if (::SetServiceStatus(g_hSvcStatus, &SvcStatus) == FALSE)
{
dwError = ::GetLastError();
LOG_EVENT(AP_ERROR,
MSG_WINHTTP_AUTOPROXY_SVC_WIN32_ERROR,
L"SetServiceStatus()",
dwError);
goto cleanup;
}
// ** after this point the HandleEx() control thread can start to call us
if (g_pRpcSrv->Open(&SvcStatus) == FALSE)
{
SvcStatus.dwWin32ExitCode = ERROR_SERVICE_SPECIFIC_ERROR;
SvcStatus.dwServiceSpecificExitCode = g_pRpcSrv->GetLastError();
SvcStatus.dwWaitHint = 0;
//LOG_DEBUG_EVENT(AP_ERROR,
// "The Auto-Proxy service failed to start due to a RPC server error; error = %d",
// SvcStatus.dwServiceSpecificExitCode);
goto cleanup;
}
SvcStatus.dwCurrentState = SERVICE_RUNNING;
SvcStatus.dwWaitHint = 0;
if (::SetServiceStatus(g_hSvcStatus, &SvcStatus) == FALSE)
{
dwError = ::GetLastError();
LOG_EVENT(AP_ERROR,
MSG_WINHTTP_AUTOPROXY_SVC_WIN32_ERROR,
L"SetServiceStatus()",
dwError);
// if for some reason we can't indicate that we are up & running (or stopped), the SCM
// should time-out and stop us; so we don't cleanup here
}
// Ok we are up & running, but before we return to SCM we need to direct a wait thread to
// wait on an service-stop event. We also configure the wait thread to call us periodically
// so that we can detect and shut down after a idle period of time.
DWORD dwTimerInterval = AUTOPROXY_SVC_IDLE_CHECK_INTERVAL * 1000;
#ifdef ENABLE_DEBUG
DWORD dwRegVal;
DWORD dwValType;
DWORD dwValSize = sizeof(dwRegVal);
if (::RegQueryValueExW(g_hKeySvcParams,
L"IdleTimerInterval",
NULL,
&dwValType,
(LPBYTE)&dwRegVal,
&dwValSize) == ERROR_SUCCESS)
{
if ((dwValType == REG_DWORD) && (dwRegVal != 0))
{
dwTimerInterval = dwRegVal; // the value unit from registry is millisec.
}
}
#endif
if (::RegisterWaitForSingleObject(&g_hSvcStopWait,
g_hSvcStopEvent,
SvcTimerAndStop,
NULL,
dwTimerInterval,
0) == FALSE)
{
dwError = ::GetLastError();
LOG_EVENT(AP_WARNING,
MSG_WINHTTP_AUTOPROXY_SVC_WIN32_ERROR,
L"RegisterWaitForSingleObject()",
dwError);
// we fail to direct a wait thread to wait on the service-stop event, so we hang on to the ServiceMain thread
// to wait for the event (this should not happen)
g_hSvcStopWait = NULL;
::WaitForSingleObject(g_hSvcStopEvent, INFINITE);
// ServiceMain would wait until the service-stop event is fired by the SCM
SvcTimerAndStop(NULL,
FALSE // event signaled
);
}
// if we successfully direct a wait thread to wait on the service-stop event, we exit out
// of the ServiceMain thread
return;
cleanup:
if (g_hSvcStatus)
{
SvcStatus.dwCurrentState = SERVICE_STOPPED;
::SetServiceStatus(g_hSvcStatus, &SvcStatus);
}
SvcCleanUp();
}
VOID SvcCleanUp(VOID)
{
if (g_hSvcStopWait)
{
::UnregisterWaitEx(g_hSvcStopWait, NULL);
g_hSvcStopWait = NULL;
}
#ifdef ENABLE_DEBUG
if (g_hKeySvcParams)
{
::RegCloseKey(g_hKeySvcParams);
g_hKeySvcParams = NULL;
}
#endif
if (g_hSvcStopEvent)
{
::CloseHandle(g_hSvcStopEvent);
g_hSvcStopEvent = NULL;
}
if (g_pRpcSrv)
{
// no need to call close here -- close() failed, otherwise we won't be here
delete g_pRpcSrv;
g_pRpcSrv = NULL;
}
// note: according to MSDN, g_hSvcStatus doesn't need to be closed
TerminateEventLog();
}