xpmgr/BuildTools/Include/functiondiscoveryproviderhe...

264 lines
9.8 KiB
C++

//
// Copyright (c) Microsoft Corporation. All rights reserved.
//
#pragma once
class CFDProviderHelper
{
public:
CFDProviderHelper() : m_hToken(NULL), m_dwSessionId((DWORD) -1)
{
InitializeSRWLock(&srwTokenLock);
}
~CFDProviderHelper()
{
if ( NULL != m_hToken )
CloseHandle( m_hToken );
}
// Implementation
public:
/////////////////////////////////////////////////////////////////////////////
// CoSetProxyBlanket
//
// Purpose:
// If the provider is running in a process that doesn't have permissions
// to call back into the client process by default then CoSetProxyBlanket
// must be called to enable impersonation.
// This function wraps the necessary calls and provides the correct CoSetProxyBlanket
// parameters in a single function.
//
// Arguments:
// pIUnk: Interface proxy to enable callbacks on
//
// Returns: S_OK on success, appropriate HRESULT otherwise
static HRESULT CoSetProxyBlanket( IUnknown *pIUnk )
{
HRESULT hr = S_OK;
IUnknown* pRealUnknown;
// Try to see if it's a COM proxy. If so, see if it's local to the process or a remote COM proxy.
// If it's remote, set the proxy blanket
ULONG_PTR ulRpcOptions = GetRpcOptions( pIUnk );
if ( SERVER_LOCALITY_PROCESS_LOCAL != ulRpcOptions )
{
hr = CoImpersonateClient();
if ( S_OK == hr )
{
hr = pIUnk->QueryInterface(IID_IUnknown, (LPVOID*)&pRealUnknown );
if ( S_OK == hr )
{
hr = ::CoSetProxyBlanket( pRealUnknown, RPC_C_AUTHN_DEFAULT, RPC_C_AUTHZ_NONE, COLE_DEFAULT_PRINCIPAL, RPC_C_AUTHN_LEVEL_DEFAULT, RPC_C_IMP_LEVEL_IMPERSONATE, NULL, EOAC_STATIC_CLOAKING );
if ( S_OK == hr )
{
hr = ::CoSetProxyBlanket( pIUnk, RPC_C_AUTHN_DEFAULT, RPC_C_AUTHZ_NONE, COLE_DEFAULT_PRINCIPAL,
RPC_C_AUTHN_LEVEL_DEFAULT, RPC_C_IMP_LEVEL_IMPERSONATE, NULL, EOAC_STATIC_CLOAKING );
}
if ( E_NOINTERFACE == hr )
hr = S_OK; // Provider can end up being inproc if we are in the fdPHost process. Ignore error and try to proceed.
pRealUnknown->Release();
}
if ( S_OK == hr )
hr = CoRevertToSelf();
else
CoRevertToSelf(); // Don't want the return code
}
}
return hr;
}
/////////////////////////////////////////////////////////////////////////////
// CoSetProxyBlanketWithThreadToken
//
// Purpose:
// If the provider is running in a process that doesn't have permissions
// to call back into the client process by default then CoSetProxyBlanket
// must be called to enable impersonation.
// This function wraps the necessary calls to CoSetProxyBlanket if the
// provider thread is from a thread pool instead of a thread called directly
// by FD.
// If you want to use this method you must call Initialize on the class instance.
// This is best done in IFunctionDiscoveryProvider::Initialize.
// Initialize must be called from one of the methods called by the client process.
//
// Arguments:
// pIUnk: Interface proxy to enable callbacks on
//
// Returns: S_OK on success, appropriate HRESULT otherwise
HRESULT CoSetProxyBlanketWithThreadToken( IUnknown *pIUnk )
{
if ( NULL == m_hToken )
return S_OK;
IUnknown* pRealUnknown;
HRESULT hr = S_OK;
AcquireSRWLockShared(&srwTokenLock);
if ( !SetThreadToken( NULL, m_hToken ))
hr = HRESULT_FROM_WIN32( GetLastError() );
ReleaseSRWLockShared(&srwTokenLock);
if ( S_OK == hr )
{
hr = pIUnk->QueryInterface(IID_IUnknown, (LPVOID*)&pRealUnknown );
if ( S_OK == hr )
{
hr = ::CoSetProxyBlanket( pRealUnknown, RPC_C_AUTHN_DEFAULT, RPC_C_AUTHZ_NONE, COLE_DEFAULT_PRINCIPAL, RPC_C_AUTHN_LEVEL_DEFAULT, RPC_C_IMP_LEVEL_IMPERSONATE, NULL, EOAC_STATIC_CLOAKING );
if (S_OK == hr)
hr = ::CoSetProxyBlanket( pIUnk, RPC_C_AUTHN_DEFAULT, RPC_C_AUTHZ_NONE, COLE_DEFAULT_PRINCIPAL, RPC_C_AUTHN_LEVEL_DEFAULT, RPC_C_IMP_LEVEL_IMPERSONATE, NULL, EOAC_STATIC_CLOAKING );
if ( E_NOINTERFACE == hr )
hr = S_OK; // Provider can end up being inproc if we are in the fdPHost process. Ignore error and try to proceed.
pRealUnknown->Release();
}
if ( !SetThreadToken( NULL, NULL ))
{
if ( S_OK == hr )
hr = HRESULT_FROM_WIN32( GetLastError() );
}
}
return hr;
}
/////////////////////////////////////////////////////////////////////////////
// Initialize
//
// Purpose:
// If the provider is running in a process that doesn't have permissions
// to call back into the client process by default then CoSetProxyBlanket
// must be called to enable impersonation.
// CoSetProxyBlanketWithThreadToken wraps the calls necessary to do this.
// Before using that function you must call Initialize on the class instance.
// This is best done in IFunctionDiscoveryProvider::Initialize.
// Initialize must be called from one of the methods called by the client process.
//
// Note: There are three cases we we will not impersonate
// 1. If the caller is Local System we will not impersonate and subsequent
// calls to CoSetProxyBlanketWithThreadToken will effectively noop
// 2. Similary if the provider is actually running in-proc no proxy blanket
// is required and CoImpersonateClient will fail with:
// Code=0x80010117 (2147549463): RPC_E_CALL_COMPLETE: "Call context cannot be accessed after call completed."
// Severity=FAILURE; Code=279 (0x117); Facility=1 (0x1) (FACILITY_RPC)
// Handle this case gracefully and effectively noop on the
// CoSetProxyBlanketWithThreadToken as well.
// 3. If we get an inproc proxy to the provider we detect that and subsequent
// calls to CoSetProxyBlanketWithThreadToken will effectively noop.
//
// Returns: S_OK on success, appropriate HRESULT otherwise
HRESULT Initialize( IUnknown *pIUnk )
{
if ( NULL != m_hToken )
return S_FALSE;
const SID LOCAL_SYSTEM_SID = { SID_REVISION, 1, {0,0,0,0,0,5}, SECURITY_LOCAL_SYSTEM_RID };
HRESULT hr = CoImpersonateClient();
if ( S_OK == hr )
{
BOOL isLocalSystem = FALSE;
BOOL isLocalCall = (GetRpcOptions( pIUnk ) == SERVER_LOCALITY_PROCESS_LOCAL);
if (!isLocalCall)
{
if ( !CheckTokenMembership( NULL, (PSID)&LOCAL_SYSTEM_SID, &isLocalSystem ))
{
hr = HRESULT_FROM_WIN32( GetLastError() );
}
}
// Only save the impersonation info if
// this is not a local call, and the caller is not System.
if ( (S_OK == hr) && (!isLocalCall) && (!isLocalSystem) )
{
AcquireSRWLockExclusive(&srwTokenLock);
if ( !OpenThreadToken( GetCurrentThread(), TOKEN_IMPERSONATE | TOKEN_QUERY, FALSE, &m_hToken ))
hr = HRESULT_FROM_WIN32( GetLastError() );
if ( S_OK == hr )
{
DWORD dwSizeReturned;
if ( !GetTokenInformation(m_hToken, TokenSessionId, &m_dwSessionId, sizeof(m_dwSessionId), &dwSizeReturned ))
hr = HRESULT_FROM_WIN32( GetLastError() );
}
ReleaseSRWLockExclusive(&srwTokenLock);
}
if ( S_OK == hr )
hr = CoRevertToSelf();
else
CoRevertToSelf(); // Don't want the return code
}
else if ( RPC_E_CALL_COMPLETE == hr )
{
hr = S_OK; // InProc
}
return hr;
}
BOOL ReleaseToken(DWORD dwSessionId)
{
BOOL bMatch = FALSE;
AcquireSRWLockExclusive( &srwTokenLock );
if ( NULL != m_hToken && dwSessionId == m_dwSessionId )
{
CloseHandle( m_hToken );
m_hToken = NULL;
bMatch = TRUE;
}
ReleaseSRWLockExclusive( &srwTokenLock );
return bMatch;
}
VOID ReleaseToken()
{
AcquireSRWLockExclusive( &srwTokenLock );
if ( NULL != m_hToken)
{
CloseHandle( m_hToken );
m_hToken = NULL;
m_dwSessionId = (DWORD) -1;
}
ReleaseSRWLockExclusive( &srwTokenLock );
}
// Implementation
protected:
/////////////////////////////////////////////////////////////////////////////
// GetRpcOptions
//
// Purpose:
// Get the rpc options if this is a proxy.
//
// Arguments:
// pIUnk: Interface proxy.
//
// Returns: ULONG_PTR rpc options on success, assume it's inproc otherwise (SERVER_LOCALITY_PROCESS_LOCAL)
static ULONG_PTR GetRpcOptions( IUnknown *pIUnk )
{
ULONG_PTR ulOptions = SERVER_LOCALITY_PROCESS_LOCAL;
IRpcOptions* pRpcOptions;
HRESULT hr = pIUnk->QueryInterface( __uuidof( IRpcOptions ), reinterpret_cast<LPVOID*>( &pRpcOptions ) );
if ( S_OK == hr )
{
hr = pRpcOptions->Query( pIUnk, COMBND_SERVER_LOCALITY, &ulOptions );
pRpcOptions->Release();
}
return ulOptions;
}
// Attributes
protected:
DWORD m_dwSessionId;
HANDLE m_hToken;
SRWLOCK srwTokenLock;
};