// // 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( &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; };