You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
461 lines
14 KiB
461 lines
14 KiB
/*++
|
|
|
|
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;
|
|
}
|