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.
 
 
 
 
 
 

976 lines
27 KiB

/*++
Copyright (C) 1996-2001 Microsoft Corporation
Module Name:
REGEPROV.CPP
Abstract:
History:
--*/
#include "precomp.h"
#include <wbemidl.h>
#include <stdio.h>
#include "cfdyn.h"
#include "stdprov.h"
#include "regeprov.h"
#include <sync.h>
#include <tss.h>
#include <genutils.h>
#include <analyser.h>
#include <cominit.h>
#include <GroupsForUser.h>
template <class T>
class CLockUnlock
{
private:
T *m_pObj;
public:
CLockUnlock(T *pObj) : m_pObj(pObj) { if(pObj) pObj->Lock(); }
~CLockUnlock() { if(m_pObj) m_pObj->Unlock(); }
};
CRegEventProvider::CRegEventProvider()
: m_lRef(0), m_hThread(NULL), m_dwId(NULL), m_hQueueSemaphore(NULL),
m_pKeyClass(NULL), m_pValueClass(NULL), m_pTreeClass(NULL), m_pSink(NULL)
{
}
CRegEventProvider::~CRegEventProvider()
{
if(m_pSink)
m_pSink->Release();
if(m_pKeyClass)
m_pKeyClass->Release();
if(m_pValueClass)
m_pValueClass->Release();
if(m_pTreeClass)
m_pTreeClass->Release();
InterlockedDecrement(&lObj);
if (m_hThread) CloseHandle(m_hThread);
if (m_hQueueSemaphore) CloseHandle(m_hQueueSemaphore);
}
STDMETHODIMP CRegEventProvider::QueryInterface(REFIID riid, void** ppv)
{
*ppv = NULL;
if(riid == IID_IWbemEventProvider || riid == IID_IUnknown)
{
*ppv = (IWbemEventProvider*)this;
AddRef();
return S_OK;
}
else if(riid == IID_IWbemEventProviderQuerySink)
{
*ppv = (IWbemEventProviderQuerySink*)this;
AddRef();
return S_OK;
}
else if(riid == IID_IWbemEventProviderSecurity)
{
*ppv = (IWbemEventProviderSecurity*)this;
AddRef();
return S_OK;
}
else if(riid == IID_IWbemProviderInit)
{
*ppv = (IWbemProviderInit*)this;
AddRef();
return S_OK;
}
else return E_NOINTERFACE;
}
ULONG STDMETHODCALLTYPE CRegEventProvider::AddRef()
{
return InterlockedIncrement(&m_lRef);
}
ULONG STDMETHODCALLTYPE CRegEventProvider::Release()
{
long lRef = InterlockedDecrement(&m_lRef);
if(lRef == 0)
{
{
CInCritSec ics(&m_cs);
// deactivate all event requests
for(int i = 0; i < m_apRequests.GetSize(); i++)
{
CRegistryEventRequest* pReq = m_apRequests[i];
if(pReq) pReq->ForceDeactivate();
}
m_apRequests.RemoveAll();
if(m_hThread) KillWorker();
}
delete this;
}
return lRef;
}
STDMETHODIMP CRegEventProvider::Initialize(LPWSTR wszUser,
long lFlags,
LPWSTR wszNamespace,
LPWSTR wszLocale,
IWbemServices* pNamespace,
IWbemContext* pCtx,
IWbemProviderInitSink* pSink)
{
HRESULT hres;
hres = pNamespace->GetObject(REG_KEY_EVENT_CLASS,// strObjectPath
0, // lFlags
pCtx, // pCtx
&m_pKeyClass, // ppObject
NULL); // ppCallResult
if ( SUCCEEDED(hres) )
{
hres = pNamespace->GetObject(REG_VALUE_EVENT_CLASS,
0,
pCtx,
&m_pValueClass,
NULL);
}
if ( SUCCEEDED(hres) )
{
hres = pNamespace->GetObject( REG_TREE_EVENT_CLASS,
0,
pCtx,
&m_pTreeClass,
NULL );
}
if ( SUCCEEDED(hres) )
{
m_hQueueSemaphore = CreateSemaphore( NULL, // lpSemaphoreAttributes
0, //lInitialCount
0x7fffffff, // lMaximumCount
NULL); // lpName
if ( m_hQueueSemaphore != NULL )
hres = WBEM_S_NO_ERROR;
else
hres = WBEM_E_OUT_OF_MEMORY;
}
pSink->SetStatus(hres, 0);
return hres;
}
STDMETHODIMP CRegEventProvider::ProvideEvents(IWbemObjectSink* pSink,
long lFlags)
{
m_pSink = pSink;
pSink->AddRef();
return S_OK;
}
HRESULT CRegEventProvider::AddRequest(CRegistryEventRequest* pNewReq)
{
// This is only called after entering the critical section m_cs
int nActiveRequests = m_apRequests.GetSize();
// Search for a similar request
// ============================
// This will not change the number of active requests.
// It will cause the request Id to be served by an existing
// CRegistryEventRequest.
for(int i = 0; i < nActiveRequests; i++)
{
CRegistryEventRequest* pReq = m_apRequests[i];
// Only active requests are in the array.
if(pReq->IsSameAs(pNewReq))
{
// Found it!
// =========
HRESULT hres = pReq->Reactivate(pNewReq->GetPrimaryId(),
pNewReq->GetMsWait());
delete pNewReq;
return hres;
}
}
// Not found. Add it
// =================
HRESULT hres = pNewReq->Activate();
if(SUCCEEDED(hres))
{
m_apRequests.Add(pNewReq);
// If there were no active requests before this one was added
// then we have to start up the worker thread.
if ( nActiveRequests == 0 )
{
CreateWorker();
}
}
return hres;
}
STDMETHODIMP CRegEventProvider::CancelQuery(DWORD dwId)
{
CInCritSec ics(&m_cs);
int nOriginalSize = m_apRequests.GetSize();
// Remove all requests with this Id
// ================================
for(int i = 0; i < m_apRequests.GetSize(); i++)
{
CRegistryEventRequest* pReq = m_apRequests[i];
// If Deactivate returns WBEM_S_FALSE then the request was not serving
// this id or it is still serving other ids so we leave it in the array.
// If S_OK is returned then the request is no longer serving any ids
// and it is marked as inactive and its resources are released. There
// may still be references to it in the worker thread queue, but the
// worker thread will see that it is inactive and not fire the events.
if (pReq->Deactivate(dwId) == S_OK)
{
m_apRequests.RemoveAt(i);
--i;
}
}
// If we have cancelled the last subscription then kill the worker thread.
if (nOriginalSize > 0 && m_apRequests.GetSize() == 0)
{
if(m_hThread) KillWorker();
}
return WBEM_S_NO_ERROR;
}
void CRegEventProvider::CreateWorker()
{
// This is only called while in m_cs
m_hThread = CreateThread(NULL, // lpThreadAttributes
0, // dwStackSize
(LPTHREAD_START_ROUTINE)&CRegEventProvider::Worker, // lpStartAddress
this, // lpParameter
0, // dwCreationFlags
&m_dwId); // lpThreadId
}
void CRegEventProvider::KillWorker()
{
// When this is called the following is true:
// All waits have been unregistered .
// All thread pool requests have been processed.
// All CRegistryEventRequests in the queue are inactive.
// m_cs has been entered.
// Therefore no other threads will:
// Place events in the queue.
// Modify CRegistryEventRequests in the queue.
// Create or destroy a worker thread.
// So the worker thread will empty the queue of the remaining
// inactive CRegistryEventRequests and then retrieve the null
// event and return.
EnqueueEvent((CRegistryEventRequest*)0);
WaitForSingleObject(m_hThread, // hHandle
INFINITE); // dwMilliseconds
CloseHandle(m_hThread);
m_hThread = 0;
m_dwId = 0;
}
HRESULT CRegEventProvider::GetValuesForProp(QL_LEVEL_1_RPN_EXPRESSION* pExpr,
CPropertyName& PropName,
CWStringArray& awsVals)
{
awsVals.Empty();
// Get the necessary query
// =======================
QL_LEVEL_1_RPN_EXPRESSION* pPropExpr = NULL;
HRESULT hres = CQueryAnalyser::GetNecessaryQueryForProperty(pExpr,
PropName, pPropExpr);
if(FAILED(hres))
{
return hres;
}
if(pPropExpr == NULL)
return WBEM_E_FAILED;
// See if there are any tokens
// ===========================
if(pPropExpr->nNumTokens == 0)
{
delete pPropExpr;
return WBEMESS_E_REGISTRATION_TOO_BROAD;
}
// Combine them all
// ================
for(int i = 0; i < pPropExpr->nNumTokens; i++)
{
QL_LEVEL_1_TOKEN& Token = pPropExpr->pArrayOfTokens[i];
if(Token.nTokenType == QL1_NOT)
{
delete pPropExpr;
return WBEMESS_E_REGISTRATION_TOO_BROAD;
}
else if(Token.nTokenType == QL1_AND || Token.nTokenType == QL1_OR)
{
// We treat them all as ORs
// ========================
}
else
{
// This is a token
// ===============
if(Token.nOperator != QL1_OPERATOR_EQUALS)
{
delete pPropExpr;
return WBEMESS_E_REGISTRATION_TOO_BROAD;
}
if(V_VT(&Token.vConstValue) != VT_BSTR)
{
delete pPropExpr;
return WBEM_E_INVALID_QUERY;
}
// This token is a string equality. Add the string to the list
// ===========================================================
if ( awsVals.Add(V_BSTR(&Token.vConstValue)) != CWStringArray::no_error)
{
delete pPropExpr;
return WBEM_E_OUT_OF_MEMORY;
}
}
}
delete pPropExpr;
return WBEM_S_NO_ERROR;
}
HRESULT CRegEventProvider::RaiseEvent(IWbemClassObject* pEvent)
{
if(m_pSink)
return m_pSink->Indicate(1, &pEvent);
else
return WBEM_S_NO_ERROR;
}
HKEY CRegEventProvider::TranslateHiveName(LPCWSTR wszName)
{
if(!wbem_wcsicmp(wszName, L"HKEY_CLASSES_ROOT"))
return HKEY_CLASSES_ROOT;
/* Disallowed: different semantics for client and server
else if(!wbem_wcsicmp(wszName, L"HKEY_CURRENT_USER"))
return HKEY_CURRENT_USER;
*/
else if(!wbem_wcsicmp(wszName, L"HKEY_LOCAL_MACHINE"))
return HKEY_LOCAL_MACHINE;
else if(!wbem_wcsicmp(wszName, L"HKEY_USERS"))
return HKEY_USERS;
else if(!wbem_wcsicmp(wszName, L"HKEY_PERFORMANCE_DATA"))
return HKEY_PERFORMANCE_DATA;
else if(!wbem_wcsicmp(wszName, L"HKEY_CURRENT_CONFIG"))
return HKEY_CURRENT_CONFIG;
else if(!wbem_wcsicmp(wszName, L"HKEY_DYN_DATA"))
return HKEY_DYN_DATA;
else
return NULL;
}
DWORD CRegEventProvider::Worker(void* p)
{
CoInitializeEx(0,COINIT_MULTITHREADED);
CRegEventProvider* pThis = (CRegEventProvider*)p;
while(true)
{
DWORD dwRes = WaitForSingleObject(
pThis->m_hQueueSemaphore, // hHandle
INFINITE ); // dwMilliseconds
if ( dwRes != WAIT_OBJECT_0 )
{
CoUninitialize();
return dwRes;
}
CRegistryEventRequest *pReq = 0;
try
{
{
CInCritSec ics(&(pThis->m_csQueueLock));
pReq = pThis->m_qEventQueue.Dequeue();
}
// If pReq is null then it is a signal for the thread to terminate.
if (pReq)
{
pReq->ProcessEvent();
// Dequeueing the request doesn't release it.
// If it did then it might be deleted before we had a chance to use it.
// Now we are done with it.
pReq->Release();
}
else
{
break;
}
}
catch( CX_MemoryException& )
{
CoUninitialize();
return ERROR_NOT_ENOUGH_MEMORY;
}
}
CoUninitialize();
return 0;
}
void CRegEventProvider::EnqueueEvent(CRegistryEventRequest *pReq)
{
{
CInCritSec ics(&m_csQueueLock);
// Placing the request in the queue AddRefs it.
if ( !m_qEventQueue.Enqueue(pReq) )
throw CX_MemoryException();
}
// Tell the worker thread that there is an item to process in the queue.
ReleaseSemaphore(m_hQueueSemaphore, // hSemaphore
1, // lReleaseCount
NULL); // lpPreviousCount
}
VOID CALLBACK CRegEventProvider::EnqueueEvent(PVOID lpParameter,
BOOLEAN TimerOrWaitFired)
{
CRegistryEventRequest *pReq = (CRegistryEventRequest*) lpParameter;
CRegEventProvider *pProv = pReq->GetProvider();
pProv->EnqueueEvent(pReq);
}
const CLSID CLSID_RegistryEventProvider =
{0xfa77a74e,0xe109,0x11d0,{0xad,0x6e,0x00,0xc0,0x4f,0xd8,0xfd,0xff}};
IUnknown* CRegEventProviderFactory::CreateImpObj()
{
return (IWbemEventProvider*) new CRegEventProvider;
}
STDMETHODIMP CRegEventProvider::NewQuery(DWORD dwId,
WBEM_WSTR wszLanguage,
WBEM_WSTR wszQuery)
{
HRESULT hres;
CancelQuery(dwId);
// Parse the query
// ===============
CTextLexSource Source(wszQuery);
QL1_Parser Parser(&Source);
QL_LEVEL_1_RPN_EXPRESSION* pExpr;
if(Parser.Parse(&pExpr))
{
return WBEM_E_INVALID_QUERY;
}
CDeleteMe<QL_LEVEL_1_RPN_EXPRESSION> dm(pExpr);
// Check the class
// ===============
int nEventType;
if(!wbem_wcsicmp(pExpr->bsClassName, REG_VALUE_EVENT_CLASS))
{
nEventType = e_RegValueChange;
}
else if(!wbem_wcsicmp(pExpr->bsClassName, REG_KEY_EVENT_CLASS))
{
nEventType = e_RegKeyChange;
}
else if(!wbem_wcsicmp(pExpr->bsClassName, REG_TREE_EVENT_CLASS))
{
nEventType = e_RegTreeChange;
}
else
{
// No such class
// =============
return WBEM_E_INVALID_QUERY;
}
// Check tolerance on Win95
// ========================
if(!IsNT() && pExpr->Tolerance.m_bExact)
{
return WBEMESS_E_REGISTRATION_TOO_PRECISE;
}
// Extract the values of hive from the query
// =========================================
CPropertyName Name;
Name.AddElement(REG_HIVE_PROPERTY_NAME);
CWStringArray awsHiveVals;
hres = GetValuesForProp(pExpr, Name, awsHiveVals);
if(FAILED(hres)) return hres;
// Translate them to real hives
// ============================
CUniquePointerArray<HKEY> aHives;
for(int i = 0; i < awsHiveVals.Size(); i++)
{
HKEY hHive = TranslateHiveName(awsHiveVals[i]);
if(hHive == NULL)
{
return WBEM_E_INVALID_QUERY;
}
HKEY* phNew = new HKEY(hHive);
if ( phNew == NULL )
return WBEM_E_OUT_OF_MEMORY;
if ( aHives.Add(phNew) < 0 )
return WBEM_E_OUT_OF_MEMORY;
}
// Extract the values of key from the query
// ========================================
Name.Empty();
if(nEventType == e_RegTreeChange)
{
Name.AddElement(REG_ROOT_PROPERTY_NAME);
}
else
{
Name.AddElement(REG_KEY_PROPERTY_NAME);
}
CWStringArray awsKeyVals;
hres = GetValuesForProp(pExpr, Name, awsKeyVals);
if(FAILED(hres))
{
return hres;
}
CWStringArray awsValueVals;
if(nEventType == e_RegValueChange)
{
// Extract the values for the value
// ================================
Name.Empty();
Name.AddElement(REG_VALUE_PROPERTY_NAME);
hres = GetValuesForProp(pExpr, Name, awsValueVals);
if(FAILED(hres))
{
return hres;
}
}
HRESULT hresGlobal = WBEM_E_INVALID_QUERY;
{
CInCritSec ics(&m_cs); // do this in a critical section
// Go through every combination of the above and create requests
// =============================================================
for(int nHiveIndex = 0; nHiveIndex < aHives.GetSize(); nHiveIndex++)
{
HKEY hHive = *aHives[nHiveIndex];
LPWSTR wszHive = awsHiveVals[nHiveIndex];
for(int nKeyIndex = 0; nKeyIndex < awsKeyVals.Size(); nKeyIndex++)
{
LPWSTR wszKey = awsKeyVals[nKeyIndex];
if(nEventType == e_RegValueChange)
{
for(int nValueIndex = 0; nValueIndex < awsValueVals.Size();
nValueIndex++)
{
LPWSTR wszValue = awsValueVals[nValueIndex];
CRegistryEventRequest* pReq =
new CRegistryValueEventRequest(this,
pExpr->Tolerance,
dwId, hHive, wszHive, wszKey, wszValue);
if(pReq->IsOK())
{
HRESULT hres = AddRequest(pReq);
if(SUCCEEDED(hres))
hresGlobal = hres;
}
else
{
DEBUGTRACE((LOG_ESS, "Invalid registration: key "
"%S, value %S\n", wszKey, wszValue));
delete pReq;
}
}
}
else
{
// Value-less request
// ==================
CRegistryEventRequest* pReq;
if(nEventType == e_RegKeyChange)
{
pReq = new CRegistryKeyEventRequest(this,
pExpr->Tolerance,
dwId, hHive, wszHive, wszKey);
}
else
{
pReq = new CRegistryTreeEventRequest(this,
pExpr->Tolerance,
dwId, hHive, wszHive, wszKey);
}
if(pReq->IsOK())
{
hres = AddRequest(pReq);
if(SUCCEEDED(hres))
hresGlobal = hres;
}
else
{
DEBUGTRACE((LOG_ESS, "Invalid registration: key %S\n",
wszKey));
delete pReq;
}
}
}
}
} // out of critical section
return hresGlobal;
}
STDMETHODIMP CRegEventProvider::AccessCheck(WBEM_CWSTR wszLanguage,
WBEM_CWSTR wszQuery,
long lSidLength,
const BYTE* aSid)
{
HRESULT hres;
PSID pSid = (PSID)aSid;
HANDLE hToken = NULL;
if(pSid == NULL)
{
//
// Access check based on the thread
//
hres = WbemCoImpersonateClient();
if(FAILED(hres))
return hres;
BOOL bRes = OpenThreadToken(GetCurrentThread(), // ThreadHandle
TOKEN_READ, // DesiredAccess
TRUE, // OpenAsSelf
&hToken); // TokenHandle
WbemCoRevertToSelf();
if(!bRes)
{
return WBEM_E_ACCESS_DENIED;
}
}
CCloseMe cm1(hToken);
// Parse the query
// ===============
CTextLexSource Source(wszQuery);
QL1_Parser Parser(&Source);
QL_LEVEL_1_RPN_EXPRESSION* pExpr;
if(Parser.Parse(&pExpr))
{
return WBEM_E_INVALID_QUERY;
}
CDeleteMe<QL_LEVEL_1_RPN_EXPRESSION> dm(pExpr);
// Check the class
// ===============
int nEventType;
if(!wbem_wcsicmp(pExpr->bsClassName, REG_VALUE_EVENT_CLASS))
{
nEventType = e_RegValueChange;
}
else if(!wbem_wcsicmp(pExpr->bsClassName, REG_KEY_EVENT_CLASS))
{
nEventType = e_RegKeyChange;
}
else if(!wbem_wcsicmp(pExpr->bsClassName, REG_TREE_EVENT_CLASS))
{
nEventType = e_RegTreeChange;
}
else
{
// No such class
// =============
return WBEM_E_INVALID_QUERY;
}
// Extract the values of hive from the query
// =========================================
CPropertyName Name;
Name.AddElement(REG_HIVE_PROPERTY_NAME);
CWStringArray awsHiveVals;
hres = GetValuesForProp(pExpr, Name, awsHiveVals);
if(FAILED(hres)) return hres;
// Translate them to real hives
// ============================
CUniquePointerArray<HKEY> aHives;
for(int i = 0; i < awsHiveVals.Size(); i++)
{
HKEY hHive = TranslateHiveName(awsHiveVals[i]);
if(hHive == NULL)
{
return WBEM_E_INVALID_QUERY;
}
HKEY* phNew = new HKEY(hHive);
if ( phNew == NULL )
return WBEM_E_OUT_OF_MEMORY;
if ( aHives.Add(phNew) < 0 )
return WBEM_E_OUT_OF_MEMORY;
}
// Extract the values of key from the query
// ========================================
Name.Empty();
if(nEventType == e_RegTreeChange)
{
Name.AddElement(REG_ROOT_PROPERTY_NAME);
}
else
{
Name.AddElement(REG_KEY_PROPERTY_NAME);
}
CWStringArray awsKeyVals;
hres = GetValuesForProp(pExpr, Name, awsKeyVals);
if(FAILED(hres))
{
return hres;
}
HRESULT hresGlobal = WBEM_E_INVALID_QUERY;
// Go through every combination of the above and create requests
// =============================================================
for(int nHiveIndex = 0; nHiveIndex < aHives.GetSize(); nHiveIndex++)
{
HKEY hHive = *aHives[nHiveIndex];
LPWSTR wszHive = awsHiveVals[nHiveIndex];
for(int nKeyIndex = 0; nKeyIndex < awsKeyVals.Size(); nKeyIndex++)
{
LPWSTR wszKey = awsKeyVals[nKeyIndex];
// Get that key's security
// =======================
HKEY hKey;
long lRes = RegOpenKeyExW(hHive, // hKey
wszKey, // lpSubKey
0, // ulOptions
READ_CONTROL, // samDesired
&hKey); // phkResult
if(lRes)
return WBEM_E_NOT_FOUND;
CRegCloseMe cm2(hKey);
DWORD dwLen = 0;
lRes = RegGetKeySecurity(hKey, // hKey
OWNER_SECURITY_INFORMATION |
GROUP_SECURITY_INFORMATION |
DACL_SECURITY_INFORMATION, // SecurityInformation
NULL, // pSecurityDescriptor
&dwLen); // lpcbSecurityDescriptor
if(lRes != ERROR_INSUFFICIENT_BUFFER)
return WBEM_E_FAILED;
PSECURITY_DESCRIPTOR pDesc = (PSECURITY_DESCRIPTOR)new BYTE[dwLen];
if(pDesc == NULL)
return WBEM_E_OUT_OF_MEMORY;
CVectorDeleteMe<BYTE> vdm((BYTE*)pDesc);
lRes = RegGetKeySecurity(hKey,
OWNER_SECURITY_INFORMATION |
GROUP_SECURITY_INFORMATION |
DACL_SECURITY_INFORMATION,
pDesc,
&dwLen);
if(lRes)
return WBEM_E_FAILED;
//
// Check permissions differently depending on whether we have a SID
// or an actual token
//
if(pSid)
{
//
// We have a SID --- walk the ACL
//
//
// Extract the ACL
//
PACL pAcl = NULL;
BOOL bAclPresent, bAclDefaulted;
if(!GetSecurityDescriptorDacl(pDesc, // pSecurityDescriptor
&bAclPresent, // lpbDaclPresent
&pAcl, // pDacl
&bAclDefaulted))// lpbDaclDefaulted
{
return WBEM_E_FAILED;
}
if(bAclPresent)
{
//
// This is our own ACL walker
//
DWORD dwAccessMask;
NTSTATUS st = GetAccessMask((PSID)pSid, pAcl,
&dwAccessMask);
if(st)
{
ERRORTRACE((LOG_ESS, "Registry event provider unable "
"to retrieve access mask for the creator of "
"registration %S: NT status %d.\n"
"Registration disabled\n", wszQuery, st));
return WBEM_E_FAILED;
}
if((dwAccessMask & KEY_NOTIFY) == 0)
return WBEM_E_ACCESS_DENIED;
}
}
else
{
//
// We have a token --- use AccessCheck
//
//
// Construct generic mapping for registry keys
//
GENERIC_MAPPING map;
map.GenericRead = KEY_READ;
map.GenericWrite = KEY_WRITE;
map.GenericExecute = KEY_EXECUTE;
map.GenericAll = KEY_ALL_ACCESS;
//
// Construct privilege array receptacle
//
PRIVILEGE_SET ps[10];
DWORD dwSize = 10 * sizeof(PRIVILEGE_SET);
DWORD dwGranted;
BOOL bResult;
BOOL bOK = ::AccessCheck(pDesc, // pSecurityDescriptor
hToken, // ClientToken
KEY_NOTIFY, // DesiredAccess
&map, // GenericMapping
ps, // PrivilegeSet
&dwSize, // PrivilegeSetLength
&dwGranted, // GrantedAccess
&bResult); // AccessStatus
if(!bOK || !bResult)
return WBEM_E_ACCESS_DENIED;
}
}
}
return WBEM_S_NO_ERROR;
}