|
|
/*++
Copyright (c) 2002 Microsoft Corporation
Module Name:
apsvcc.cpp
Abstract:
Implements the client side L-RPC functions for the Auto-Proxy Service.
Author:
Biao Wang (biaow) 10-May-2002
--*/
#include "wininetp.h"
#include "apsvc.h"
#include "..\apsvc\apsvcdefs.h"
SC_HANDLE g_hSCM = NULL; SC_HANDLE g_hAPSvc = NULL;
BOOL g_fIsAPSvcAvailable = FALSE; DWORD g_dwAPSvcIdleTimeStamp;
#define ESTIMATED_SVC_IDLE_TIMEOUT_IN_SECONDS (((AUTOPROXY_SVC_IDLE_TIMEOUT * 60) * 2) / 3)
BOOL ConnectToAutoProxyService(VOID);
// Return TRUE if the WinHttp Autoproxy Service is available on
// the current platform.
BOOL IsAutoProxyServiceAvailable() { if (!GlobalPlatformDotNet) { return FALSE; }
BOOL fRet = FALSE;
if (g_fIsAPSvcAvailable && ((::GetTickCount() - g_dwAPSvcIdleTimeStamp) < ESTIMATED_SVC_IDLE_TIMEOUT_IN_SECONDS * 1000)) { // the svc is marked "loaded" AND we are within the svc idle timtout period, so
// the svc is likey still up & running
fRet = TRUE; } else { g_fIsAPSvcAvailable = FALSE; // assume the svc is stopped
fRet = ConnectToAutoProxyService(); }
return fRet; }
DWORD OutProcGetProxyForUrl( INTERNET_HANDLE_OBJECT* pSession, LPCWSTR lpcwszUrl, WINHTTP_AUTOPROXY_OPTIONS * pAutoProxyOptions, WINHTTP_PROXY_INFO * pProxyInfo ) { DWORD dwError = ERROR_SUCCESS; RPC_STATUS RpcStatus; RPC_ASYNC_STATE Async;
Async.u.hEvent = NULL;
if (pSession->_hAPBinding == NULL) { GeneralInitCritSec.Lock(); // make sure one thread initialize the per-session L-RPC binding handle at a time
if (pSession->_hAPBinding == NULL) { LPWSTR pwszBindingString; RpcStatus = ::RpcStringBindingComposeW(NULL, AUTOPROXY_L_RPC_PROTOCOL_SEQUENCE, NULL, // this is a L-RPC service
NULL, // end-point (we are using dynamic endpoints, so this is NULL)
NULL, &pwszBindingString); if (RpcStatus != RPC_S_OK) { dwError = ERROR_WINHTTP_AUTO_PROXY_SERVICE_ERROR; GeneralInitCritSec.Unlock(); goto exit; }
INET_ASSERT(pwszBindingString != NULL);
RpcStatus = ::RpcBindingFromStringBindingW(pwszBindingString, &pSession->_hAPBinding);
::RpcStringFreeW(&pwszBindingString); if (RpcStatus != RPC_S_OK) { dwError = ERROR_WINHTTP_AUTO_PROXY_SERVICE_ERROR; GeneralInitCritSec.Unlock(); goto exit; } }
GeneralInitCritSec.Unlock(); }
INET_ASSERT(pSession->_hAPBinding != NULL);
RPC_SECURITY_QOS SecQos; DWORD dwAuthnSvc; DWORD dwAuthnLevel; SecQos.Version = RPC_C_SECURITY_QOS_VERSION; SecQos.Capabilities = RPC_C_QOS_CAPABILITIES_DEFAULT; SecQos.IdentityTracking = RPC_C_QOS_IDENTITY_STATIC; // id don't change per session
if (pAutoProxyOptions->fAutoLogonIfChallenged) { SecQos.ImpersonationType = RPC_C_IMP_LEVEL_IMPERSONATE; dwAuthnLevel = RPC_C_AUTHN_LEVEL_PKT_PRIVACY; dwAuthnSvc = RPC_C_AUTHN_WINNT; } else { SecQos.ImpersonationType = RPC_C_IMP_LEVEL_ANONYMOUS; dwAuthnLevel = RPC_C_AUTHN_LEVEL_NONE; dwAuthnSvc = RPC_C_AUTHN_NONE; }
RpcStatus = ::RpcBindingSetAuthInfoExW(pSession->_hAPBinding, NULL, // only needed by kerberos; but we are L-PRC
dwAuthnLevel, dwAuthnSvc, NULL, 0, &SecQos);
if (RpcStatus != RPC_S_OK) { dwError = ERROR_WINHTTP_AUTO_PROXY_SERVICE_ERROR; goto exit; }
RpcStatus = ::RpcAsyncInitializeHandle(&Async, sizeof(RPC_ASYNC_STATE)); if (RpcStatus != RPC_S_OK) { dwError = ERROR_WINHTTP_AUTO_PROXY_SERVICE_ERROR; goto exit; }
Async.UserInfo = NULL; Async.NotificationType = RpcNotificationTypeEvent; Async.u.hEvent = CreateEvent(NULL, FALSE, // auto reset
FALSE, // not initially set
NULL); if (Async.u.hEvent == NULL) { dwError = ERROR_NOT_ENOUGH_MEMORY; goto exit; }
pAutoProxyOptions->lpvReserved = NULL;
pProxyInfo->dwAccessType = 0; pProxyInfo->lpszProxy = NULL; pProxyInfo->lpszProxyBypass = NULL;
SESSION_OPTIONS Timeouts; DWORD dwTimeout; pSession->GetTimeout(WINHTTP_OPTION_RESOLVE_TIMEOUT, (LPDWORD)&Timeouts.nResolveTimeout); pSession->GetTimeout(WINHTTP_OPTION_CONNECT_TIMEOUT, (LPDWORD)&Timeouts.nConnectTimeout); pSession->GetTimeout(WINHTTP_OPTION_CONNECT_RETRIES, (LPDWORD)&Timeouts.nConnectRetries); pSession->GetTimeout(WINHTTP_OPTION_SEND_TIMEOUT, (LPDWORD)&Timeouts.nSendTimeout); pSession->GetTimeout(WINHTTP_OPTION_RECEIVE_TIMEOUT, (LPDWORD)&Timeouts.nReceiveTimeout);
RpcTryExcept { // ClientGetProxyForUrl is the MIDL-generated client stub function; the server stub
// is called GetProxyForUrl. Since both RPC client & server stub is in the same DLL,
// we used the -prefix MIDL switch to prepend "Client" to the client stub to avoid
// name collisions
ClientGetProxyForUrl(&Async, pSession->_hAPBinding, lpcwszUrl, (P_AUTOPROXY_OPTIONS)pAutoProxyOptions, &Timeouts, (P_AUTOPROXY_RESULT)pProxyInfo, &dwError); } RpcExcept(1) { if (::RpcExceptionCode() == EPT_S_NOT_REGISTERED) { // we thought the svc is available because the idle timeout hasn't expired yet but
// we got an exception here saying the L-RPC endpoint isn't available. So someone
// much have stopped the service manually.
g_fIsAPSvcAvailable = FALSE; }
dwError = ERROR_WINHTTP_AUTO_PROXY_SERVICE_ERROR; goto exit; } RpcEndExcept
if ((Timeouts.nResolveTimeout == INFINITE) || (Timeouts.nConnectTimeout == INFINITE) || (Timeouts.nSendTimeout == INFINITE) || (Timeouts.nReceiveTimeout == INFINITE)) { dwTimeout = INFINITE; } else { dwTimeout = Timeouts.nResolveTimeout + Timeouts.nConnectTimeout + Timeouts.nSendTimeout + Timeouts.nReceiveTimeout; }
DWORD dwWaitResult; DWORD dwWaitTime = 0;
// we are going to wait for the result. But we won't wait for it more than half a sec.
// at a time, so that app can cancel this API call. the minimum wait is half a sec. Also
// we won't wait longer than the app specified time-out.
if (dwTimeout < 500) { dwTimeout = 500; }
do { dwWaitResult = ::WaitForSingleObject(Async.u.hEvent, 500); if (dwWaitResult == WAIT_OBJECT_0) { break; }
if (pSession->IsInvalidated()) { dwError = ERROR_WINHTTP_OPERATION_CANCELLED; break; }
dwWaitTime += 500;
} while (dwWaitTime < dwTimeout);
if (dwWaitResult != WAIT_OBJECT_0) { (void)::RpcAsyncCancelCall(&Async, TRUE); // cancel immediately
dwError = ERROR_WINHTTP_TIMEOUT; goto exit; }
// result has come back
BOOL fRet; RpcStatus = ::RpcAsyncCompleteCall(&Async, &fRet); if (RpcStatus != RPC_S_OK) { dwError = ((RpcStatus == RPC_S_CALL_CANCELLED) ? ERROR_WINHTTP_OPERATION_CANCELLED : ERROR_WINHTTP_AUTO_PROXY_SERVICE_ERROR); goto exit; }
if (fRet == FALSE) { // dwError should be updated by the RPC call itself
goto exit; }
exit:
if (Async.u.hEvent) { ::CloseHandle(Async.u.hEvent); }
return dwError; }
VOID AutoProxySvcDetach(VOID) { if (g_hAPSvc) { ::CloseServiceHandle(g_hAPSvc); g_hAPSvc = NULL; }
if (g_hSCM) { ::CloseServiceHandle(g_hSCM); g_hSCM = NULL; }
}
BOOL ConnectToAutoProxyService(VOID) { // this function is not thread safe, caller must assure only one thread can call
// this function at a time.
if (g_hAPSvc == NULL) { GeneralInitCritSec.Lock(); if (g_hAPSvc == NULL) { if (g_hSCM == NULL) { g_hSCM = ::OpenSCManagerW(NULL, // Local Computer for L-RPC accesss
NULL, // default SCM DB
SC_MANAGER_CONNECT);
if (g_hSCM == NULL) { GeneralInitCritSec.Unlock(); return FALSE; } }
g_hAPSvc = ::OpenServiceW(g_hSCM, WINHTTP_AUTOPROXY_SERVICE_NAME, SERVICE_START | SERVICE_QUERY_STATUS | SERVICE_INTERROGATE);
if (g_hAPSvc == NULL) { GeneralInitCritSec.Unlock(); return FALSE; } }
GeneralInitCritSec.Unlock(); }
INET_ASSERT(g_hAPSvc != NULL);
BOOL fRet = FALSE;
GeneralInitCritSec.Lock(); // one thread to init at a time
if (!g_fIsAPSvcAvailable) { SERVICE_STATUS SvcStatus; if (::QueryServiceStatus(g_hAPSvc, &SvcStatus) == FALSE) { goto exit; }
if (SvcStatus.dwCurrentState == SERVICE_RUNNING || SvcStatus.dwCurrentState == SERVICE_STOP_PENDING // there is a possibility that the service failed to shutdown gracefully
// and it's stucked in the STOP_PENDING state. In that case, however, the
// L-RPC service will continue be available
) { g_dwAPSvcIdleTimeStamp = ::GetTickCount(); g_fIsAPSvcAvailable = TRUE; fRet = TRUE; goto exit; }
if (SvcStatus.dwCurrentState == SERVICE_STOPPED) { if (::StartServiceW(g_hAPSvc, 0, NULL) == FALSE) { DWORD dwError = ::GetLastError(); if (dwError == ERROR_SERVICE_ALREADY_RUNNING) { g_dwAPSvcIdleTimeStamp = ::GetTickCount(); g_fIsAPSvcAvailable = TRUE;
fRet = TRUE; } goto exit; } } else if (SvcStatus.dwCurrentState != SERVICE_START_PENDING) { goto exit; }
// at this point either 1) WinHttp.dll is starting the Svc, or 2
// the SVC is being started otuside WinHttp.dll (e.g. admin starting it using SCM)
// either case we just need to wait for the Svc to run
// the code below is based on an MSDN sample
//wait for service to complete init
if (::QueryServiceStatus(g_hAPSvc, &SvcStatus) == FALSE) { goto exit; } // Save the tick count and initial checkpoint.
DWORD dwStartTickCount = GetTickCount(); DWORD dwOldCheckPoint = SvcStatus.dwCheckPoint;
while (SvcStatus.dwCurrentState == SERVICE_START_PENDING) { // Do not wait longer than the wait hint. A good interval is
// one tenth the wait hint, but no less than 1 second and no
// more than 10 seconds.
//DWORD dwWaitTime = SvcStatus.dwWaitHint / 10;
//if (dwWaitTime < 1000)
// dwWaitTime = 1000;
//else if (dwWaitTime > 10000)
// dwWaitTime = 10000;
Sleep(250);
if (::QueryServiceStatus(g_hAPSvc, &SvcStatus) == FALSE) { goto exit; } if (SvcStatus.dwCheckPoint > dwOldCheckPoint) { // The service is making progress.
dwStartTickCount = GetTickCount(); dwOldCheckPoint = SvcStatus.dwCheckPoint; } else { if(GetTickCount() - dwStartTickCount > SvcStatus.dwWaitHint) { break; } } }
if (SvcStatus.dwCurrentState != SERVICE_RUNNING) { goto exit; } }
g_dwAPSvcIdleTimeStamp = ::GetTickCount(); g_fIsAPSvcAvailable = TRUE;
fRet = TRUE;
exit: GeneralInitCritSec.Unlock(); return fRet; }
|