Leaked source code of windows server 2003
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.
 
 
 
 
 
 

1909 lines
50 KiB

//******************************************************************************
//
// BINDING.CPP
//
// Copyright (C) 1996-1999 Microsoft Corporation
//
//******************************************************************************
#include "precomp.h"
#include <stdio.h>
#include <pragmas.h>
#include <ess.h>
#include <permbind.h>
#include <cominit.h>
#include <callsec.h>
#include <wmimsg.h>
#include <vector>
#include "Quota.h"
#define MIN_TIMEOUT_BETWEEN_TOKEN_ATTEMPTS 60000
//*****************************************************************************
//
// Syncronization model:
//
// 1. Bindings themselves are immutable and do not require protection.
// 2. Releasing a binding (removing from table) can release the other end-point
// and generally cannot be done in a CS.
//
//*************************** Event Consumer **********************************
long g_lNumConsumers = 0;
long g_lNumBindings = 0;
long g_lNumFilters = 0;
CEventConsumer::CEventConsumer( CEssNamespace* pNamespace )
: CQueueingEventSink(pNamespace), m_pOwnerSid(NULL)
{
InterlockedIncrement( &g_lNumConsumers );
}
CEventConsumer::~CEventConsumer()
{
InterlockedDecrement( &g_lNumConsumers );
delete [] m_pOwnerSid;
}
HRESULT CEventConsumer::EnsureReferences(CEventFilter* pFilter,
CBinding* pBinding)
{
CBinding* pOldBinding = NULL;
{
CInCritSec ics(&m_cs);
for(int i = 0; i < m_apBindings.GetSize(); i++)
{
if(m_apBindings[i]->GetFilter() == pFilter)
{
// Replace the binding
// ===================
m_apBindings.SetAt(i, pBinding, &pOldBinding);
break;
}
}
if(pOldBinding == NULL)
{
// Add it to the list
// ==================
if(m_apBindings.Add(pBinding) < 0)
return WBEM_E_OUT_OF_MEMORY;
}
}
if(pOldBinding)
{
// Found
// =====
pOldBinding->Release();
return S_FALSE;
}
else
{
return S_OK;
}
}
HRESULT CEventConsumer::EnsureNotReferences(CEventFilter* pFilter)
{
CBinding* pOldBinding = NULL;
{
CInCritSec ics(&m_cs);
for(int i = 0; i < m_apBindings.GetSize(); i++)
{
if(m_apBindings[i]->GetFilter() == pFilter)
{
// Remove the binding
// ==================
m_apBindings.RemoveAt(i, &pOldBinding);
break;
}
}
}
if(pOldBinding)
{
pOldBinding->Release();
return S_OK;
}
else
{
// Not found
// =========
return S_FALSE;
}
}
HRESULT CEventConsumer::Unbind()
{
// Unbind the binding array from the consumer
// ==========================================
CBinding** apBindings = NULL;
int nNumBindings = 0;
{
CInCritSec ics(&m_cs);
nNumBindings = m_apBindings.GetSize();
apBindings = m_apBindings.UnbindPtr();
if ( NULL == apBindings )
{
return WBEM_S_FALSE;
}
}
// Instruct all the filters that are bound to us to unbind
// =======================================================
HRESULT hres = S_OK;
for(int i = 0; i < nNumBindings; i++)
{
HRESULT hr = apBindings[i]->GetFilter()->EnsureNotReferences(this);
if( FAILED( hr ) )
{
hres = hr;
}
apBindings[i]->Release();
}
delete [] apBindings;
return hres;
}
HRESULT CEventConsumer::ConsumeFromBinding(CBinding* pBinding,
long lNumEvents, IWbemEvent** apEvents,
CEventContext* pContext)
{
DWORD dwQoS = pBinding->GetQoS();
if( dwQoS == WMIMSG_FLAG_QOS_SYNCHRONOUS )
{
// Synchronous delivery --- call the ultimate client
// =================================================
IUnknown* pOldSec = NULL;
HRESULT hr;
if(!pBinding->IsSecure())
{
hr = CoSwitchCallContext(NULL, &pOldSec);
if ( FAILED( hr ) )
{
return hr;
}
}
HRESULT hres = ActuallyDeliver( lNumEvents,
apEvents,
pBinding->IsSecure(),
pContext );
if(!pBinding->IsSecure())
{
IUnknown* pGarb = NULL;
hr = CoSwitchCallContext(pOldSec, &pGarb);
if ( FAILED( hr ) && SUCCEEDED ( hres ) )
{
return hr;
}
}
return hres;
}
// Asynchronous delivery --- delegate to queueing sink
// ===================================================
return CQueueingEventSink::SecureIndicate( lNumEvents,
apEvents,
pBinding->IsSecure(),
pBinding->ShouldSlowDown(),
dwQoS,
pContext );
}
HRESULT CEventConsumer::GetAssociatedFilters(
CRefedPointerSmallArray<CEventFilter>& apFilters)
{
CInCritSec ics(&m_cs);
for(int i = 0; i < m_apBindings.GetSize(); i++)
{
if(apFilters.Add(m_apBindings[i]->GetFilter()) < 0)
return WBEM_E_OUT_OF_MEMORY;
}
return WBEM_S_NO_ERROR;
}
HRESULT CEventConsumer::ReportEventDrop(IWbemEvent* pEvent)
{
// Log a message
// =============
ERRORTRACE((LOG_ESS, "Dropping event destined for event consumer %S in "
"namespace %S\n", (LPCWSTR)(WString)GetKey(),
m_pNamespace->GetName()));
if(pEvent->InheritsFrom(EVENT_DROP_CLASS) == S_OK)
{
ERRORTRACE((LOG_ESS, "Unable to deliver an event indicating inability "
"to deliver another event to event consumer %S in namespace %S.\n"
"Not raising an error event to avoid an infinite loop!\n",
(LPCWSTR)(WString)GetKey(), m_pNamespace->GetName()));
return S_FALSE;
}
return S_OK;
}
//*************************** Event Filter **********************************
CEventFilter::CEventFilter(CEssNamespace* pNamespace)
: m_pNamespace(pNamespace), m_eState(e_Inactive),
m_ForwardingSink(this), m_ClassChangeSink(this),
m_eValidity(e_TemporarilyInvalid), m_pOwnerSid(NULL),
m_bSingleAsync(false), m_lSecurityChecksRemaining(0),
m_lSubjectToSDSCount(0), m_hresPollingError(S_OK),
m_hresFilterError(WBEM_E_CRITICAL_ERROR), m_bCheckSDs(true),
m_bHasBeenValid(false), m_dwLastTokenAttempt(0), m_pToken(NULL),
m_bReconstructOnHit(false), m_hresTokenError(WBEM_E_CRITICAL_ERROR)
{
InterlockedIncrement( &g_lNumFilters );
m_pNamespace->AddRef();
}
CEventFilter::~CEventFilter()
{
InterlockedDecrement( &g_lNumFilters );
delete [] m_pOwnerSid;
if(m_pNamespace)
m_pNamespace->Release();
if(m_pToken)
m_pToken->Release();
}
HRESULT CEventFilter::EnsureReferences(CEventConsumer* pConsumer,
CBinding* pBinding)
{
CBinding* pOldBinding = NULL;
{
CInUpdate iu(this);
// Actually change the bindings
// ============================
{
CInCritSec ics(&m_cs);
for(int i = 0; i < m_apBindings.GetSize(); i++)
{
if(m_apBindings[i]->GetConsumer() == pConsumer)
{
// Replace the binding
// ===================
// binding cannot change synchronicity --- in such cases,
// it is first removed, and then re-added. Therefore,
// no m_bSingleAsync adjustment is needed
m_apBindings.SetAt(i, pBinding, &pOldBinding);
break;
}
}
if(pOldBinding == NULL)
{
// Add it to the list
// ==================
if(m_apBindings.Add(pBinding) < 0)
return WBEM_E_OUT_OF_MEMORY;
AdjustSingleAsync();
}
}
// Activate if needed
// ==================
AdjustActivation();
}
if(pOldBinding)
{
// Found
// =====
pOldBinding->Release();
return S_FALSE;
}
else
{
return S_OK;
}
}
HRESULT CEventFilter::EnsureNotReferences(CEventConsumer* pConsumer)
{
CBinding* pOldBinding = NULL;
{
CInUpdate iu(this);
// Make the actual change
// ======================
{
CInCritSec ics(&m_cs);
for(int i = 0; i < m_apBindings.GetSize(); i++)
{
if(m_apBindings[i]->GetConsumer() == pConsumer)
{
// Remove the binding
// ==================
m_apBindings.RemoveAt(i, &pOldBinding);
AdjustSingleAsync();
break;
}
} // for
} // m_cs
// Deactivate the filter if necessary
// ==================================
AdjustActivation();
} // update
if(pOldBinding)
{
pOldBinding->Release();
return S_OK;
}
else
{
// Not found
// =========
return S_FALSE;
}
}
HRESULT CEventFilter::Unbind(bool bShuttingDown)
{
// Unbind the binding array from the filter
// ========================================
std::vector< CWbemPtr < CBinding >,wbem_allocator< CWbemPtr<CBinding> > > apBindings;
int nNumBindings = 0;
{
CInUpdate iu(this);
{
CInCritSec ics(&m_cs);
nNumBindings = m_apBindings.GetSize();
apBindings.insert(apBindings.begin(),nNumBindings,CWbemPtr<CBinding>()); // may throw
CBinding ** ppBindTmp = m_apBindings.UnbindPtr(); //needed to set size to zero
CVectorDeleteMe<CBinding *> dm(ppBindTmp);
if ( NULL == ppBindTmp)
{
return WBEM_S_FALSE;
}
for (int Idx=0;Idx<nNumBindings;Idx++)
{
apBindings[Idx].Attach(ppBindTmp[Idx]); // does not AddRef
}
m_bSingleAsync = false;
}
if(!bShuttingDown)
AdjustActivation(); // throws
}
// Instruct all the consumers that are bound to us to unbind
// =========================================================
HRESULT hres = S_OK;
for(int i = 0; i < nNumBindings; i++)
{
HRESULT hr = apBindings[i]->GetConsumer()->EnsureNotReferences(this);
if ( FAILED( hr ) )
{
hres = hr;
}
apBindings[i].Release(); // performs the "final" release
}
return hres;
}
void CEventFilter::SetInactive()
{
m_eState = e_Inactive;
}
BOOL CEventFilter::IsActive()
{
return (m_eState == e_Active);
}
HRESULT CEventFilter::GetFilterError()
{
return m_hresFilterError;
}
HRESULT CEventFilter::GetEventNamespace(LPWSTR* pwszNamespace)
{
*pwszNamespace = NULL;
return S_OK;
}
// assumes in m_cs
void CEventFilter::AdjustSingleAsync()
{
if(m_apBindings.GetSize() > 1)
m_bSingleAsync = false;
else if(m_apBindings.GetSize() == 0)
m_bSingleAsync = false;
else if(m_apBindings[0]->IsSynch())
m_bSingleAsync = false;
else
m_bSingleAsync = true;
}
bool CEventFilter::IsBound()
{
return (m_apBindings.GetSize() != 0);
}
// Requires: in m_csChangeBindings
HRESULT CEventFilter::AdjustActivation()
{
// Invalid filters cannot be activated or deactivated
// ==================================================
if(m_eValidity == e_PermanentlyInvalid)
return S_FALSE;
HRESULT hres = S_FALSE;
if(!IsBound() )
{
//
// Even if this filter is not active, it may be subscribed for
// activation events if it is temporarily invalid (and that's the only
// reason it is not active).
//
m_pNamespace->UnregisterFilterFromAllClassChanges(this);
if(m_eState == e_Active)
{
hres = m_pNamespace->DeactivateFilter(this);
if(FAILED(hres)) return hres;
m_eState = e_Inactive;
}
return WBEM_S_NO_ERROR;
}
else if(m_eState == e_Inactive && IsBound() )
{
//
// Even though this filter is not active, it may be subscribed for
// activation events if it is temporarily invalid (and that's the only
// reason it is not active).
//
m_pNamespace->UnregisterFilterFromAllClassChanges(this);
hres = m_pNamespace->ActivateFilter(this);
if(FAILED(hres)) return hres;
m_eState = e_Active;
return WBEM_S_NO_ERROR;
}
else
{
return S_FALSE;
}
}
void CEventFilter::MarkAsPermanentlyInvalid(HRESULT hres)
{
m_eValidity = e_PermanentlyInvalid;
m_hresFilterError = hres;
}
void CEventFilter::MarkAsTemporarilyInvalid(HRESULT hres)
{
m_eValidity = e_TemporarilyInvalid;
m_hresFilterError = hres;
}
void CEventFilter::MarkAsValid()
{
m_eValidity = e_Valid;
m_bHasBeenValid = true;
m_hresFilterError = WBEM_S_NO_ERROR;
}
void CEventFilter::MarkReconstructOnHit(bool bReconstruct)
{
//
// Reconstruction is not really needed, since dummer nodes are used for
// this
//
m_bReconstructOnHit = bReconstruct;
}
HRESULT CEventFilter::CheckEventAccessToFilter( IServerSecurity* pProvCtx )
{
HRESULT hr = WBEM_S_NO_ERROR;
const PSECURITY_DESCRIPTOR pEventAccessSD = GetEventAccessSD();
if ( pEventAccessSD == NULL )
{
//
// filter allows all events
//
return WBEM_S_NO_ERROR;
}
//
// check that the event provider's calling context has access to filter
//
if ( pProvCtx != NULL )
{
hr = pProvCtx->ImpersonateClient();
if ( SUCCEEDED(hr) )
{
HANDLE hToken;
if ( OpenThreadToken( GetCurrentThread(),
TOKEN_QUERY,
TRUE,
&hToken ) )
{
GENERIC_MAPPING map;
ZeroMemory( &map, sizeof(GENERIC_MAPPING) );
PRIVILEGE_SET ps;
DWORD dwPrivLength = sizeof(ps);
BOOL bStatus;
DWORD dwGranted;
if ( ::AccessCheck( PSECURITY_DESCRIPTOR(pEventAccessSD),
hToken,
WBEM_RIGHT_PUBLISH,
&map,
&ps,
&dwPrivLength,
&dwGranted,
&bStatus ) )
{
hr = bStatus ? WBEM_S_NO_ERROR : WBEM_E_ACCESS_DENIED;
}
else
{
hr = HRESULT_FROM_WIN32( GetLastError() );
}
CloseHandle( hToken );
}
else
{
hr = HRESULT_FROM_WIN32( GetLastError() );
}
pProvCtx->RevertToSelf();
}
}
return hr;
}
HRESULT CEventFilter::CheckFilterAccessToEvent( PSECURITY_DESCRIPTOR pEventSD )
{
HRESULT hr;
if ( pEventSD == NULL )
{
//
// event provider allows all filters access
//
return WBEM_S_NO_ERROR;
}
if( !m_bCheckSDs )
{
//
// This filter was unconditionally allowed by all its event providers!
//
return WBEM_S_NO_ERROR;
}
//
// Get the token for this filter
//
if( m_pToken == NULL && FAILED(m_hresTokenError) )
{
//
// Check how long it's been since we last attempted to get the token --
// don't want to do that too often.
//
if(m_dwLastTokenAttempt == 0 ||
m_dwLastTokenAttempt <
GetTickCount() - MIN_TIMEOUT_BETWEEN_TOKEN_ATTEMPTS )
{
//
// Get the filter to find a token, however it does that
//
m_hresTokenError = ObtainToken( &m_pToken );
if( FAILED(m_hresTokenError) )
{
m_dwLastTokenAttempt = GetTickCount();
}
}
}
if ( m_hresTokenError == WBEM_S_NO_ERROR )
{
_DBG_ASSERT( m_pToken != NULL );
//
// Check security for real
//
DWORD dwGranted;
hr = m_pToken->AccessCheck( WBEM_RIGHT_SUBSCRIBE,
(const BYTE*)pEventSD,
&dwGranted );
if( SUCCEEDED(hr) )
{
if(dwGranted & WBEM_RIGHT_SUBSCRIBE)
{
hr = WBEM_S_NO_ERROR;
}
else
{
hr = WBEM_E_ACCESS_DENIED;
}
}
}
else
{
hr = m_hresTokenError;
}
return hr;
}
HRESULT CEventFilter::AccessCheck( CEventContext* pContext, IWbemEvent* pEvent)
{
HRESULT hr;
//
// With polling, there will be a null context. we don't do an access
// check in that case.
//
if ( pContext == NULL )
{
return WBEM_S_NO_ERROR;
}
PSECURITY_DESCRIPTOR pEventSD = (PSECURITY_DESCRIPTOR)pContext->GetSD();
//
// check that the filter allows access to the event provider and owner.
// owner and provider can be different when the provider is signaling
// events on behalf of some other identity.
//
CWbemPtr<IServerSecurity> pProvCtx = NULL;
CoGetCallContext( IID_IServerSecurity, (void**)&pProvCtx );
//
// NOTE: With cross namespace events, the two parts of the access check
// are split up between the namespaces. The FilterAccessToEvent is
// performed in the event's namespace with the temp subscription's
// AccessCheck. This is possible because the owner sid is propagated
// over with the temp subscription. The EventAccessToFilter is performed
// in the subscription namespace. This is possible because the call
// context and the SD of the event ( containing the event owner sid )
// is propagated with the event. Both functions are called in
// both namespaces, but the unnecessary calls turn out to be no-ops.
//
hr = CheckEventAccessToFilter( pProvCtx );
if ( SUCCEEDED(hr) )
{
//
// check that the event provider allows access to the filter.
//
hr = CheckFilterAccessToEvent( pEventSD );
}
return hr;
}
HRESULT CEventFilter::Deliver( long lNumEvents,
IWbemEvent** apEvents,
CEventContext* pContext )
{
int i;
if( m_lSecurityChecksRemaining > 0 )
{
return WBEM_S_FALSE;
}
CBinding* pBinding = NULL;
{
CInCritSec ics(&m_cs);
if(m_bSingleAsync)
{
//
// Thought we could deliver (call Indicate on the binding) right
// here, since single async ensures that no delivery will occur on
// this thread. But no --- an error may be raised, and that event
// will be delivered on this thread, so we must exit the critsec
// before calling
//
pBinding = m_apBindings[0];
pBinding->AddRef();
}
}
if( pBinding )
{
CReleaseMe rm1(pBinding);
return pBinding->Indicate( lNumEvents, apEvents, pContext );
}
// Make referenced copies of all the bindings to deliver over
// ==========================================================
// CANNOT USE SCOPING DUE TO CTempArray --- it uses _alloca
m_cs.Enter();
CTempArray<CBinding*> apBindings;
int nSize = m_apBindings.GetSize();
if(!INIT_TEMP_ARRAY(apBindings, nSize))
{
m_cs.Leave();
return WBEM_E_OUT_OF_MEMORY;
}
{
for(i = 0; i < nSize; i++)
{
CBinding* pBindingInner = m_apBindings[i];
pBindingInner->AddRef();
apBindings[i] = pBindingInner;
}
}
m_cs.Leave();
// Deliver and release the bindings
// ================================
HRESULT hresGlobal = S_OK;
for(i = 0; i < nSize; i++)
{
CBinding* pBindingInner = apBindings[i];
HRESULT hres = pBindingInner->Indicate( lNumEvents, apEvents, pContext );
pBindingInner->Release();
if(FAILED(hres))
hresGlobal = hres;
}
return hresGlobal;
}
HRESULT CEventFilter::LockForUpdate()
{
// Don't need to do anything since the namespace is locked!
/*
m_csChangeBindings.Enter();
AddRef();
*/
return S_OK;
}
HRESULT CEventFilter::UnlockForUpdate()
{
/*
m_csChangeBindings.Leave();
Release();
*/
return S_OK;
}
HRESULT CEventFilter::CEventForwardingSink::Indicate(long lNumEvents,
IWbemEvent** apEvents,
CEventContext* pContext)
{
return m_pOwner->Deliver(lNumEvents, apEvents, pContext);
}
void CEventFilter::IncrementRemainingSecurityChecks()
{
InterlockedIncrement(&m_lSecurityChecksRemaining);
InterlockedIncrement(&m_lSubjectToSDSCount);
}
void CEventFilter::DecrementRemainingSecurityChecks(HRESULT hresProvider)
{
//
// The provider could have said;
// S_OK: this subscription is fine, send all events through or
// S_SUBJECT_TO_SDS: check event security descriptors before sending
// So, if all the providers gave us a blank check, we won't check security
// descriptors, but if any did, we will check them all.
//
if(hresProvider != WBEM_S_SUBJECT_TO_SDS)
{
if(hresProvider != WBEM_S_NO_ERROR)
{
ERRORTRACE((LOG_ESS, "Invalid return code from provider security test: "
"0x%X\n", hresProvider));
return;
}
InterlockedDecrement(&m_lSubjectToSDSCount);
}
InterlockedDecrement(&m_lSecurityChecksRemaining);
if ( 0 == m_lSubjectToSDSCount && 0 == m_lSecurityChecksRemaining )
{
m_bCheckSDs = false;
}
else
{
InterlockedExchange( &m_lSubjectToSDSCount, 0 );
}
}
HRESULT CEventFilter::SetActualClassChangeSink( IWbemObjectSink* pSink,
IWbemObjectSink** ppOldSink )
{
HRESULT hr;
if ( m_pActualClassChangeSink != NULL )
{
m_pActualClassChangeSink->AddRef();
*ppOldSink = m_pActualClassChangeSink;
hr = WBEM_S_NO_ERROR;
}
else
{
*ppOldSink = NULL;
hr = WBEM_S_FALSE;
}
m_pActualClassChangeSink = pSink;
return hr;
}
HRESULT CEventFilter::Reactivate()
{
HRESULT hres;
//
// This is called when a class or something like that changes from
// from underneath us.
// What we need to do is lock the namespace, deactivate this filter, then
// activate it again
//
CInUpdate iu(m_pNamespace);
DEBUGTRACE((LOG_ESS, "Attempting to reactivate filter '%S' in namespace "
"'%S'\n", (LPCWSTR)(WString)GetKey(),
m_pNamespace->GetName()));
// Invalid filters cannot be activated or deactivated
// ==================================================
if(m_eValidity == e_PermanentlyInvalid)
{
DEBUGTRACE((LOG_ESS, "Not reactivate filter '%S' in namespace "
"'%S': permanently invalid\n",
(LPCWSTR)(WString)GetKey(), m_pNamespace->GetName()));
return S_FALSE;
}
if(m_eState == e_Active)
{
DEBUGTRACE((LOG_ESS, "Deactivating filter '%S' in namespace "
"'%S' prior to reactivation\n",
(LPCWSTR)(WString)GetKey(), m_pNamespace->GetName()));
hres = m_pNamespace->DeactivateFilter(this);
if(FAILED(hres))
{
ERRORTRACE((LOG_ESS, "Deactivating filter '%S' in namespace "
"'%S' prior to reactivation failed: 0x%X\n",
(LPCWSTR)(WString)GetKey(), m_pNamespace->GetName(),
hres));
return hres;
}
m_eState = e_Inactive;
}
hres = AdjustActivation();
DEBUGTRACE((LOG_ESS, "Reactivating filter '%S' in namespace "
"'%S' returned 0x%X\n",
(LPCWSTR)(WString)GetKey(), m_pNamespace->GetName(),
hres));
return hres;
}
STDMETHODIMP CEventFilter::CClassChangeSink::Indicate( long lNumEvents,
IWbemEvent** apEvents )
{
HRESULT hr;
hr = m_pOuter->Reactivate();
if ( SUCCEEDED(hr) )
{
hr = m_pOuter->m_pNamespace->FirePostponedOperations();
}
else
{
m_pOuter->m_pNamespace->FirePostponedOperations();
}
if ( FAILED(hr) )
{
ERRORTRACE((LOG_ESS, "Error encountered when reactivating filter '%S' "
"due to a class change. Namespace is '%S', HRES=0x%x\n",
(LPCWSTR)(WString)m_pOuter->GetKey(),
m_pOuter->m_pNamespace->GetName(),
hr ));
}
return hr;
}
//***************************** Binding ***************************************
CBinding::CBinding()
: m_pConsumer(NULL), m_pFilter(NULL), m_dwQoS( WMIMSG_FLAG_QOS_EXPRESS ),
m_bSlowDown(false), m_bSecure(false), m_bDisabledForSecurity(false)
{
InterlockedIncrement( &g_lNumBindings );
}
CBinding::CBinding(ADDREF CEventConsumer* pConsumer,
ADDREF CEventFilter* pFilter)
: m_pConsumer(NULL), m_pFilter(NULL), m_dwQoS( WMIMSG_FLAG_QOS_EXPRESS ),
m_bSlowDown(false), m_bSecure(false)
{
InterlockedIncrement( &g_lNumBindings );
SetEndpoints(pConsumer, pFilter, NULL);
}
HRESULT CBinding::SetEndpoints(ADDREF CEventConsumer* pConsumer,
ADDREF CEventFilter* pFilter,
READONLY PSID pBinderSid)
{
m_pConsumer = pConsumer;
m_pConsumer->AddRef();
m_pFilter = pFilter;
m_pFilter->AddRef();
// Make sure that the owner of this binding is the same as the
// owners of the endpoints
// ==================================================================
if(pBinderSid && (!EqualSid(pBinderSid, pConsumer->GetOwner()) ||
!EqualSid(pBinderSid, pFilter->GetOwner())))
{
DisableForSecurity();
}
return WBEM_S_NO_ERROR;
}
void CBinding::DisableForSecurity()
{
ERRORTRACE((LOG_ESS, "An event binding is disabled because its creator is "
"not the same security principal as the creators of the endpoints. "
"The binding and the endpoints must be created by the same user!\n"));
m_bDisabledForSecurity = true;
}
CBinding::~CBinding()
{
InterlockedDecrement( &g_lNumBindings );
if(m_pConsumer)
m_pConsumer->Release();
if(m_pFilter)
m_pFilter->Release();
}
DWORD CBinding::GetQoS() NOCS
{
return m_dwQoS;
}
bool CBinding::IsSynch() NOCS
{
return m_dwQoS == WMIMSG_FLAG_QOS_SYNCHRONOUS;
}
bool CBinding::IsSecure() NOCS
{
return m_bSecure;
}
bool CBinding::ShouldSlowDown() NOCS
{
return m_bSlowDown;
}
HRESULT CBinding::Indicate(long lNumEvents, IWbemEvent** apEvents,
CEventContext* pContext)
{
// Check if this binding is active
// ===============================
if(m_bDisabledForSecurity)
return WBEM_S_FALSE;
// It is: deliver
// ==============
return m_pConsumer->ConsumeFromBinding(this, lNumEvents, apEvents,
pContext);
}
//************************* Consumer watch instruction ************************
CWbemInterval CConsumerWatchInstruction::mstatic_Interval;
CConsumerWatchInstruction::CConsumerWatchInstruction(CBindingTable* pTable)
: CBasicUnloadInstruction(mstatic_Interval),
m_pTableRef(pTable->m_pTableRef)
{
if(m_pTableRef)
m_pTableRef->AddRef();
}
CConsumerWatchInstruction::~CConsumerWatchInstruction()
{
if(m_pTableRef)
m_pTableRef->Release();
}
void CConsumerWatchInstruction::staticInitialize(IWbemServices* pRoot)
{
mstatic_Interval = CBasicUnloadInstruction::staticRead(pRoot, GetCurrentEssContext(),
L"__EventSinkCacheControl=@");
}
HRESULT CConsumerWatchInstruction::Fire(long, CWbemTime)
{
if(!m_bTerminate)
{
CEssThreadObject Obj(NULL);
SetConstructedEssThreadObject(&Obj);
CEssNamespace* pNamespace = NULL;
if(m_pTableRef)
{
m_pTableRef->GetNamespace(&pNamespace);
m_pTableRef->UnloadUnusedConsumers(m_Interval);
}
Terminate();
if( pNamespace )
{
pNamespace->FirePostponedOperations();
pNamespace->Release();
}
ClearCurrentEssThreadObject();
}
return WBEM_S_NO_ERROR; // no point worrying the timer
}
//*************************** Binding Table ************************************
class CConsumersToRelease
{
CEventConsumer** m_apConsumers;
int m_nNumConsumers;
public:
CConsumersToRelease(CEventConsumer** apConsumers, int nNumConsumers)
: m_apConsumers(apConsumers), m_nNumConsumers(nNumConsumers)
{
}
~CConsumersToRelease()
{
for(int i = 0; i < m_nNumConsumers; i++)
{
m_apConsumers[i]->Shutdown();
m_apConsumers[i]->Release();
}
delete [] m_apConsumers;
}
static DWORD Delete(void* p)
{
delete (CConsumersToRelease*)p;
return 0;
}
};
CBindingTable::CBindingTable(CEssNamespace* pNamespace)
: m_pNamespace(pNamespace), m_pInstruction(NULL),
m_bUnloadInstruction(FALSE), m_lNumPermConsumers(0),
m_pTableRef(NULL)
{
m_pTableRef = new CBindingTableRef(this);
if(m_pTableRef)
m_pTableRef->AddRef();
}
void CBindingTable::Clear( bool bSkipClean )
{
//
// Ensure that no more unloading instructions can make it in
//
if(m_pTableRef)
{
m_pTableRef->Disconnect();
m_pTableRef->Release();
m_pTableRef = NULL;
}
// Unbind filter and consumer arrays from the table
// ================================================
CEventFilter** apFilters;
int nNumFilters;
CEventConsumer** apConsumers;
int nNumConsumers;
{
CInCritSec ics(&m_cs);
nNumFilters = m_apFilters.GetSize();
apFilters = m_apFilters.UnbindPtr();
nNumConsumers = m_apConsumers.GetSize();
apConsumers = m_apConsumers.UnbindPtr();
}
int i;
// Unbind and release all filters
// ==============================
if ( apFilters )
{
for(i = 0; i < nNumFilters; i++)
{
if (!apFilters[i]->IsInternal())
{
g_quotas.DecrementQuotaIndex(
apFilters[i]->GetOwner() ? ESSQ_PERM_SUBSCRIPTIONS :
ESSQ_TEMP_SUBSCRIPTIONS,
apFilters[i],
1 );
}
apFilters[i]->Unbind(bSkipClean); // shutting down
apFilters[i]->Release();
}
delete [] apFilters;
}
//
// unbind all consumers, but postpone their release.
//
if ( apConsumers )
{
for(i = 0; i < nNumConsumers; i++)
{
apConsumers[i]->Unbind(); // shutting down
}
//
// Release all consumers (unbound by virtue of filter unbinding), but do
// so on a separate thread
//
CConsumersToRelease* pToRelease =
new CConsumersToRelease(apConsumers, nNumConsumers);
DWORD dwId;
HANDLE hThread = CreateThread(NULL, 0,
(LPTHREAD_START_ROUTINE)CConsumersToRelease::Delete, pToRelease, 0,
&dwId);
if(hThread == NULL)
{
ERRORTRACE((LOG_ESS, "Unable to launch consumer deleting thread: %d\n",
GetLastError()));
}
else
{
DWORD dwRes = WaitForSingleObject(hThread, INFINITE );
_DBG_ASSERT( WAIT_OBJECT_0 == dwRes );
CloseHandle(hThread);
}
}
}
HRESULT CBindingTable::AddEventFilter(CEventFilter* pFilter)
{
HRESULT hr;
if (pFilter->IsInternal() ||
SUCCEEDED(hr = g_quotas.IncrementQuotaIndex(
pFilter->GetOwner() ? ESSQ_PERM_SUBSCRIPTIONS : ESSQ_TEMP_SUBSCRIPTIONS,
pFilter,
1)))
{
CInCritSec ics(&m_cs);
if (m_apFilters.Add(pFilter) >= 0)
hr = S_OK;
else
hr = WBEM_E_OUT_OF_MEMORY;
}
return hr;
}
HRESULT CBindingTable::AddEventConsumer(CEventConsumer* pConsumer)
{
CInCritSec ics(&m_cs);
if(m_apConsumers.Add(pConsumer) < 0)
return WBEM_E_OUT_OF_MEMORY;
if(pConsumer->IsPermanent())
{
if(m_lNumPermConsumers++ == 0)
m_pNamespace->SetActive();
}
return S_OK;
}
HRESULT CBindingTable::FindEventFilter(LPCWSTR wszKey,
RELEASE_ME CEventFilter** ppFilter)
{
CInCritSec ics(&m_cs);
if(m_apFilters.Find(wszKey, ppFilter))
return S_OK;
else
return WBEM_E_NOT_FOUND;
}
HRESULT CBindingTable::FindEventConsumer(LPCWSTR wszKey,
RELEASE_ME CEventConsumer** ppConsumer)
{
CInCritSec ics(&m_cs);
if(m_apConsumers.Find(wszKey, ppConsumer))
return S_OK;
else
return WBEM_E_NOT_FOUND;
}
HRESULT CBindingTable::RemoveEventFilter(LPCWSTR wszKey)
{
// Find it and remove it from the table
// ====================================
CEventFilter* pFilter = NULL;
HRESULT hres;
{
CInCritSec ics(&m_cs);
if(!m_apFilters.Remove(wszKey, &pFilter))
return WBEM_E_NOT_FOUND;
}
if(pFilter == NULL)
return WBEM_E_CRITICAL_ERROR;
// Remove 1 from our quota count.
if (!pFilter->IsInternal())
{
g_quotas.DecrementQuotaIndex(
pFilter->GetOwner() ? ESSQ_PERM_SUBSCRIPTIONS : ESSQ_TEMP_SUBSCRIPTIONS,
pFilter,
1);
}
// Unbind it, thus deactivating
// ============================
hres = pFilter->Unbind();
pFilter->Release();
return hres;
}
void CBindingTable::MarkRemoval(CEventConsumer* pConsumer)
{
if(pConsumer && pConsumer->IsPermanent())
{
if(--m_lNumPermConsumers == 0)
m_pNamespace->SetInactive();
}
}
HRESULT CBindingTable::RemoveEventConsumer(LPCWSTR wszKey)
{
// Find it and remove it from the table
// ====================================
CEventConsumer* pConsumer = NULL;
HRESULT hres;
{
CInCritSec ics(&m_cs);
if(!m_apConsumers.Remove(wszKey, &pConsumer))
return WBEM_E_NOT_FOUND;
MarkRemoval(pConsumer);
}
if(pConsumer == NULL)
return WBEM_E_CRITICAL_ERROR;
hres = pConsumer->Unbind();
pConsumer->Release();
return hres;
}
HRESULT CBindingTable::Bind(LPCWSTR wszFilterKey, LPCWSTR wszConsumerKey,
CBinding* pBinding, PSID pBinderSid)
{
// Find them both and get ref-counted pointers
// ===========================================
CEventFilter* pFilter;
CEventConsumer* pConsumer;
HRESULT hres;
{
CInCritSec ics(&m_cs);
hres = FindEventFilter(wszFilterKey, &pFilter);
if(FAILED(hres)) return hres;
hres = FindEventConsumer(wszConsumerKey, &pConsumer);
if(FAILED(hres))
{
pFilter->Release();
return hres;
}
}
// Fully construct the binding --- will check security
// ===================================================
hres = pBinding->SetEndpoints(pConsumer, pFilter, pBinderSid);
if(FAILED(hres))
return hres;
// Make them reference each other
// ==============================
HRESULT hresGlobal = S_OK;
hres = pFilter->EnsureReferences(pConsumer, pBinding);
if(FAILED(hres))
hresGlobal = hres;
hres = pConsumer->EnsureReferences(pFilter, pBinding);
if(FAILED(hres))
hresGlobal = hres;
// Cleanup
// =======
pConsumer->Release();
pFilter->Release();
return hresGlobal;
}
HRESULT CBindingTable::Unbind(LPCWSTR wszFilterKey, LPCWSTR wszConsumerKey)
{
// Find them both and get ref-counted pointers
// ===========================================
CEventFilter* pFilter;
CEventConsumer* pConsumer;
HRESULT hres;
{
CInCritSec ics(&m_cs);
hres = FindEventFilter(wszFilterKey, &pFilter);
if(FAILED(hres)) return hres;
hres = FindEventConsumer(wszConsumerKey, &pConsumer);
if(FAILED(hres))
{
pFilter->Release();
return hres;
}
}
// Remove respective references
// ============================
HRESULT hresGlobal = S_OK;
hres = pFilter->EnsureNotReferences(pConsumer);
if(FAILED(hres))
hresGlobal = hres;
pConsumer->EnsureNotReferences(pFilter);
if(FAILED(hres))
hresGlobal = hres;
pFilter->Release();
pConsumer->Release();
return hresGlobal;
}
BOOL CBindingTable::DoesHavePermanentConsumers()
{
return (m_lNumPermConsumers != 0);
}
HRESULT CBindingTable::ResetProviderRecords(LPCWSTR wszProviderRef)
{
// Make a copy of the list of consumers, AddRefed
// ==============================================
CRefedPointerArray<CEventConsumer> apConsumers;
if(!GetConsumers(apConsumers))
return WBEM_E_OUT_OF_MEMORY;
// Go through all the consumers and see if they reference this record
// ==================================================================
for(int i = 0; i < apConsumers.GetSize(); i++)
{
apConsumers[i]->ResetProviderRecord(wszProviderRef);
}
return S_OK;
}
//*******************************************************************************
//
// EnsureConsumerWatchInstruction / UnloadUnusedConsumers synchronization
//
// Usage:
//
// ECWI is called when a consumer is loaded. It is called after the consumer
// record has been updated. Post-condition: UnloadUnusedConsumers must be
// called at least once after this function starts executing.
//
// UUC is called by the CConsumerWatchTimerInstruction::Fire on timer. The
// instruction then self-destructs. Post-condition: idle consumers
// unloaded; if any are still active, another UUC will occur in the future;
// If none are active for a while, no UUC will occur in the future,
// until ECWI is called.
//
// Primitives:
//
// CS m_cs: atomic, data access
//
// BOOL m_bUnloadInstruction: Can only be accessed in m_cs. Semantics:
// TRUE if an instruction is either scheduled or will be scheduled
// shortly; this putative instruction, when fired, is guaranteed to
// examine any consumer in the table at the time of he check.
//
// Algorithm:
//
// ECWI checks m_bUnloadInstructiion (in m_cs) and if TRUE does nothing, as the
// m_bUnloadInstruction == TRUE guarantee above assures that UUC will be
// called. If it is FALSE, ECWI sets it to TRUE, then schedules an
// instruction (outside of m_cs). The setting of m_bUnloadInstruction to
// TRUE is correct, since an instruction will be scheduled shortly. Thus,
// ECWI post-condition is satisfied, assuming primitive semantics above.
//
// UUC, in m_cs, sets m_bUnloadInstriction to FALSE and makes a copy of the
// consumer list. Outside of m_cs, it iterates over the copy and unloads
// consumers as required. Then, if any are active, it calls ECWI. This
// guarantees that another UUC will be called. If a consumer was active
// before the entry into m_cs, we call ECWI. If a consumer became active
// after we entered into m_cs, it will call ECWI after we have reset
// m_bUnloadInstruction, causing another instruction to be scheduled. This
// proves our post-condition assuming primitive semantics above.
//
// Proof of primitives:
//
// m_bUnloadInstruction becomes TRUE only in ECWI. When it does, ECWI is
// guaranteed to schedule a new instruction, causing a call to UUC. So, the
// semantics holds in the beginning. It can become invalid if UUC fires and is
// not rescheduled. But UUC resets m_bUnloadInstruction to FALSE, thus making
// semantics valid vacuously.
//
// Now, we need to show that any consumer in the table at the time when
// m_bUnloadInstruction == TRUE will be examined by the scheduled UUC. Well,
// the latest scheduled (or about to be scheduled) UUC, cannot have exited
// its m_cs stint yet, for otherwise m_bUnloadInstruction would be FALSE.
// Therefore, it hasn't entered it yet, and therefore hasn't made a copy yet.
//
//******************************************************************************
HRESULT CBindingTable::EnsureConsumerWatchInstruction()
{
// Check if it is already there
// ============================
BOOL bMustSchedule = FALSE;
{
CInCritSec ics(&m_cs);
if(!m_bUnloadInstruction)
{
// Not there. Mark as there, preventing others from scheduling
// more.
// ============================================================
m_bUnloadInstruction = TRUE;
bMustSchedule = TRUE;
}
}
if(bMustSchedule)
{
CConsumerWatchInstruction* pInst = new CConsumerWatchInstruction(this);
if(pInst == NULL)
{
CInCritSec ics(&m_cs);
m_bUnloadInstruction = FALSE;
return WBEM_E_OUT_OF_MEMORY;
}
pInst->AddRef();
// Set it in the generator
// =======================
HRESULT hres = m_pNamespace->GetTimerGenerator().Set(pInst);
if(FAILED(hres))
{
CInCritSec ics(&m_cs);
m_bUnloadInstruction = FALSE;
return hres;
}
pInst->Release();
return S_OK;
}
else
{
return S_FALSE;
}
}
HRESULT CBindingTable::UnloadUnusedConsumers(CWbemInterval Interval)
{
// Mark unload instruction as empty and copy consumer records
// ==========================================================
CRefedPointerArray<CEventConsumer> apConsumers;
{
CInCritSec ics(&m_cs);
m_bUnloadInstruction = FALSE;
if(!GetConsumers(apConsumers))
return WBEM_E_OUT_OF_MEMORY;
}
// Go through the consumers and unload them if needed
// ==================================================
BOOL bUnloaded = FALSE;
BOOL bActive = FALSE;
for(int i = 0; i < apConsumers.GetSize(); i++)
{
if(apConsumers[i]->UnloadIfUnusedFor(Interval))
bUnloaded = TRUE;
else if(!apConsumers[i]->IsFullyUnloaded())
bActive = TRUE;
}
// Schedule DLL unloading if any COM objects were unloaded
// =======================================================
if(bUnloaded)
m_pNamespace->GetTimerGenerator().ScheduleFreeUnusedLibraries();
// Schedule the new instruction if needed
// ======================================
if(bActive)
return EnsureConsumerWatchInstruction();
return S_OK;
}
BOOL CBindingTable::GetConsumers(
CRefedPointerArray<CEventConsumer>& apConsumers)
{
CInCritSec ics(&m_cs);
TConsumerIterator it;
for(it = m_apConsumers.Begin(); it != m_apConsumers.End(); it++)
{
if(apConsumers.Add(*it) < 0)
return FALSE;
}
return TRUE;
}
BOOL CBindingTable::GetEventFilters( CRefedPointerArray< CEventFilter > & apEventFilters )
{
CInCritSec ics( &m_cs );
TFilterIterator it;
for( it = m_apFilters.Begin( ); it != m_apFilters.End( ); ++it )
{
if( apEventFilters.Add( *it ) < 0 )
{
return FALSE;
}
}
return TRUE;
}
HRESULT CBindingTable::RemoveConsumersStartingWith(LPCWSTR wszPrefix)
{
CRefedPointerArray<CEventConsumer> apToRelease;
int nLen = wcslen(wszPrefix);
{
CInCritSec ics(&m_cs);
TConsumerIterator it = m_apConsumers.Begin();
while(it != m_apConsumers.End())
{
if(!wcsncmp((WString)(*it)->GetKey(), wszPrefix, nLen))
{
// Found it --- move to the "to be released" list
// ==============================================
CEventConsumer* pConsumer;
it = m_apConsumers.Remove(it, &pConsumer);
MarkRemoval(pConsumer);
apToRelease.Add(pConsumer);
pConsumer->Release();
}
else
{
it++;
}
}
}
// Unbind all the consumers we have left. Release will happen on destruct
// =======================================================================
for(int i = 0; i < apToRelease.GetSize(); i++)
{
apToRelease[i]->Unbind();
}
return WBEM_S_NO_ERROR;
}
HRESULT CBindingTable::RemoveConsumerWithFilters(LPCWSTR wszConsumerKey)
{
HRESULT hres;
CRefedPointerSmallArray<CEventFilter> apFilters;
{
CInCritSec ics(&m_cs);
// Find the consumer in question
// =============================
CEventConsumer* pConsumer = NULL;
hres = FindEventConsumer(wszConsumerKey, &pConsumer);
if(FAILED(hres))
return hres;
CReleaseMe rm1(pConsumer);
// Make addrefed copies of all its associated filters
// ==================================================
hres = pConsumer->GetAssociatedFilters(apFilters);
if(FAILED(hres))
return hres;
}
// Remove the consumer
// ===================
RemoveEventConsumer(wszConsumerKey);
// Remove every one of its filters
// ===============================
for(int i = 0; i < apFilters.GetSize(); i++)
{
RemoveEventFilter((WString)apFilters[i]->GetKey());
}
return S_OK;
}
HRESULT CBindingTable::ReactivateAllFilters()
{
// Retrieve a copy of all the filters
// ==================================
CRefedPointerArray<CEventFilter> apFilters;
{
CInCritSec ics(&m_cs);
TFilterIterator it;
for(it = m_apFilters.Begin(); it != m_apFilters.End(); it++)
{
if(apFilters.Add(*it) < 0)
return WBEM_E_OUT_OF_MEMORY;
}
}
// Reactivate them all
// ===================
for(int i = 0; i < apFilters.GetSize(); i++)
{
CEventFilter* pFilter = apFilters[i];
pFilter->SetInactive();
pFilter->AdjustActivation();
}
return WBEM_S_NO_ERROR;
}
void CBindingTable::Park()
{
//
// Tell each filter to "park" itself
//
CInCritSec ics(&m_cs);
TFilterIterator it;
for(it = m_apFilters.Begin(); it != m_apFilters.End(); it++)
{
(*it)->Park();
}
}
void CBindingTable::DumpStatistics(FILE* f, long lFlags)
{
fprintf(f, "%d consumers (%d permanent), %d filters\n",
m_apConsumers.GetSize(), m_lNumPermConsumers,
m_apFilters.GetSize());
}
CBindingTableRef::~CBindingTableRef()
{
}
CBindingTableRef::CBindingTableRef(CBindingTable* pTable)
: m_pTable(pTable), m_lRef(0)
{
}
void CBindingTableRef::AddRef()
{
InterlockedIncrement(&m_lRef);
}
void CBindingTableRef::Release()
{
if(InterlockedDecrement(&m_lRef) == 0)
delete this;
}
void CBindingTableRef::Disconnect()
{
CInCritSec ics(&m_cs);
m_pTable = NULL;
}
HRESULT CBindingTableRef::UnloadUnusedConsumers(CWbemInterval Interval)
{
CInCritSec ics(&m_cs);
if(m_pTable)
return m_pTable->UnloadUnusedConsumers(Interval);
else
return WBEM_S_FALSE;
}
HRESULT CBindingTableRef::GetNamespace(RELEASE_ME CEssNamespace** ppNamespace)
{
CInCritSec ics(&m_cs);
if(m_pTable)
{
*ppNamespace = m_pTable->m_pNamespace;
if(*ppNamespace)
(*ppNamespace)->AddRef();
}
else
{
*ppNamespace = NULL;
}
return WBEM_S_NO_ERROR;
}