// ProvInfo.cpp #include "precomp.h" #include "ProvInfo.h" #include "NCDefs.h" #include "dutils.h" #include "NCProv.h" #include "NCProvider.h" #include "QueryHelp.h" // For parsing stuff. #include #define COUNTOF(x) (sizeof(x)/sizeof(x[0])) extern BOOL bIgnore; CClientInfo::~CClientInfo() { } CSinkInfo *CClientInfo::GetSinkInfo(DWORD dwID) { if (dwID == 0) return m_pProvider->m_pProv; return NULL; } HRESULT CClientInfo::PostBuffer(LPBYTE pData, DWORD dwDataSize) { CBuffer buffer(pData, dwDataSize); while (!buffer.IsEOF()) { DWORD dwMsg = buffer.ReadDWORD(); switch(dwMsg) { case NC_SRVMSG_CLIENT_INFO: DEBUGTRACE( (LOG_ESS, "NCProv: Got NC_SRVMSG_CLIENT_INFO\n")); if (ProcessClientInfo(&buffer)) m_pProvider->m_pProv->AddClient(this); else m_pProvider->DisconnectAndClose(this); // Ignore the rest of this client's messages since the client // info message can't be accompanied by any other messages. buffer.SetEOF(); break; case NC_SRVMSG_EVENT_LAYOUT: { LPBYTE pTop = buffer.m_pCurrent - sizeof(DWORD); DWORD dwSize = buffer.ReadDWORD(), dwFuncIndex = buffer.ReadDWORD(), dwSinkIndex = buffer.ReadDWORD(); DEBUGTRACE( (LOG_ESS, "NCProv: Got event layout: index = %d, sink = %d\n", dwFuncIndex, dwSinkIndex)); CEventInfo *pEvent = new CEventInfo; if (pEvent) { CSinkInfo *pSinkInfo = GetSinkInfo(dwSinkIndex); if (pSinkInfo && pEvent->InitFromBuffer(this, &buffer)) { pEvent->SetSink(pSinkInfo->GetSink()); m_mapEvents.AddNormalEventInfo(dwFuncIndex, pEvent); } else { delete pEvent; DEBUGTRACE( (LOG_ESS, "NCProv: Failed to init event layout: index = %d, sink = %d\n", dwFuncIndex, dwSinkIndex)); } } // Move past the current message. buffer.m_pCurrent = pTop + dwSize; // // validation of dwSize will be done on next iteration when // retrieving dword. // break; } case NC_SRVMSG_PREPPED_EVENT: { LPBYTE pTop = buffer.m_pCurrent - sizeof(DWORD); DWORD dwSize = buffer.ReadDWORD(), dwEventIndex = buffer.ReadDWORD(); CEventInfo *pEvent; pEvent = m_mapEvents.GetNormalEventInfo(dwEventIndex); DEBUGTRACE( (LOG_ESS, "NCProv: NCMSG_PREPPED_EVENT index %d\n", dwEventIndex)); if (pEvent) { if (pEvent->SetPropsWithBuffer(&buffer)) { #ifndef NO_INDICATE pEvent->Indicate(); #else m_dwEvents++; #endif } else ERRORTRACE( (LOG_ESS, "NCProv: SetPropsWithBuffer failed, index %d", dwEventIndex)); } else ERRORTRACE( (LOG_ESS, "NCProv: Didn't find function info for index %d", dwEventIndex)); // Move past the current message. buffer.m_pCurrent = pTop + dwSize; // // validation of dwSize will be done on next iteration when // retrieving dword. // break; } case NC_SRVMSG_ACCESS_CHECK_REPLY: { try { NC_SRVMSG_REPLY *pReply = (NC_SRVMSG_REPLY*) buffer.m_pBuffer; CPipeClient *pClient = (CPipeClient*) pReply->dwMsgCookie; pClient->m_hrClientReply = pReply->hrRet; SetEvent(pClient->m_heventMsgReceived); } catch(...) { } buffer.SetEOF(); break; } default: // Unknown message! _ASSERT(FALSE, L"NCProv: Received unknown message"); // Ignore the rest of this message. buffer.SetEOF(); } } return S_OK; } ///////////////////////////////////////////////////////////////////////////// // CPipeClient CPipeClient::CPipeClient(CNCProvider *pProvider, HANDLE hPipe) : m_hPipe(hPipe), #ifndef NO_DECODE m_bufferRecv(MAX_MSG_SIZE) #else m_bufferRecv(500000) // We need to make this really big since we won't know // how big to make it. #endif { m_pProvider = pProvider; memset(&m_info.overlap, 0, sizeof(m_info.overlap)); m_info.pInfo = this; // We'll set this to indicate we've received a message from our client. m_heventMsgReceived = CreateEvent(NULL, FALSE, FALSE, NULL); } CPipeClient::~CPipeClient() { if (m_hPipe) { DisconnectNamedPipe(m_hPipe); // Close the handle to the pipe instance. CloseHandle(m_hPipe); } if (m_heventMsgReceived) CloseHandle(m_heventMsgReceived); } BOOL CPipeClient::ProcessClientInfo(CBuffer *pBuffer) { DWORD dwBufferSize = pBuffer->ReadDWORD(); // // ignore what the client says here. we've already determined our msg size. // m_bufferRecv.Reset(MAX_MSG_SIZE); return TRUE; } ///////////////////////////////////////////////////////////////////////////// // CProvInfo #ifdef _UNICODE #define USTR_INSERT _T("%s") #else #define USTR_INSERT _T("%S") #endif CProvInfo::CProvInfo() : m_heventProviderReady(NULL), CSinkInfo(0) { } void GetBaseName(LPCWSTR szName, LPWSTR szBase) { // Normalize this by making sure it doesn't start with "\\.\" if (wcsstr(szName, L"\\\\.\\") == szName) StringCchCopyW( szBase, MAX_PATH*2, szName + 4); else StringCchCopyW( szBase, MAX_PATH*2, szName ); _wcsupr(szBase); // Get rid of the '\' chars since we can't use it in OS object names. for (WCHAR *szCurrent = szBase; *szCurrent; szCurrent++) { if (*szCurrent == '\\') *szCurrent = '/'; } } // SDDL string description: // D: Security Descriptor // A: Access allowed // 0x1f0003: EVENT_ALL_ACCESS // BA: Built-in administrators // 0x100000: SYNCHRONIZE // WD: Everyone #define ESS_EVENT_SDDL L"D:(A;;0x1f0003;;;BA)(A;;0x100000;;;WD)" BOOL CProvInfo::Init(LPCWSTR szNamespace, LPCWSTR szProvider) { WCHAR szReadyEventName[MAX_PATH * 2], szBaseNamespace[MAX_PATH * 2] = L"", szBaseProvider[MAX_PATH * 2] = L""; if (!szNamespace || !szProvider) return FALSE; DEBUGTRACE( (LOG_ESS, "NCProv: CProvInfo::Init: %S, %S\n", szNamespace, szProvider)); GetBaseName(szNamespace, szBaseNamespace); GetBaseName(szProvider, szBaseProvider); // Get the ready event. StringCchPrintfW( szReadyEventName, MAX_PATH*2, OBJNAME_EVENT_READY L"%s%s", szBaseNamespace, szBaseProvider); _wcsupr(szReadyEventName); // Save these for later. m_strName = szProvider; m_strBaseName = szBaseProvider; m_strBaseNamespace = szBaseNamespace; // Create the provider ready event. m_heventProviderReady = OpenEventW( EVENT_ALL_ACCESS, FALSE, szReadyEventName); if (!m_heventProviderReady) { PSECURITY_DESCRIPTOR pSD = NULL; DWORD dwSize; if ( !ConvertStringSecurityDescriptorToSecurityDescriptorW( ESS_EVENT_SDDL, // security descriptor string SDDL_REVISION_1, // revision level &pSD, // SD &dwSize) ) return FALSE; SECURITY_ATTRIBUTES sa = { sizeof(sa), pSD, FALSE }; m_heventProviderReady = CreateEventW( &sa, TRUE, FALSE, szReadyEventName); if (!m_heventProviderReady) { ERRORTRACE( (LOG_ESS, "NCProv: Couldn't init provider event: err = %d", GetLastError())); } if (pSD) LocalFree((HLOCAL) pSD); } BOOL bRet; if (m_heventProviderReady) { SetEvent(m_heventProviderReady); bRet = TRUE; } else bRet = FALSE; return bRet; } CProvInfo::~CProvInfo() { if (m_heventProviderReady) { DEBUGTRACE( (LOG_ESS, "NCProv: In ~CProvInfo, resetting ready event.\n")); ResetEvent(m_heventProviderReady); CloseHandle(m_heventProviderReady); DWORD dwMsg = NC_CLIMSG_PROVIDER_UNLOADING; DEBUGTRACE( (LOG_ESS, "NCProv: Sending the NC_CLIMSG_PROVIDER_UNLOADING message.\n")); // Tell our clients that we're going away. SendMessageToClients((LPBYTE) &dwMsg, sizeof(dwMsg), FALSE); } CClientInfoListIterator info; Lock(); for ( info = m_listClients.begin( ); info != m_listClients.end( ); ++info ) { (*info)->Release(); } Unlock(); } DWORD WINAPI TempThreadProc(IWbemEventSink *pSink) { HRESULT hr; hr = CoInitializeEx( 0, COINIT_MULTITHREADED ); if ( FAILED(hr) ) { return hr; } DWORD dwRet = ERROR_SUCCESS; try { // Since we got a new client, we'll have to recheck our subscriptions pSink->SetStatus( WBEM_STATUS_REQUIREMENTS, WBEM_REQUIREMENTS_RECHECK_SUBSCRIPTIONS, NULL, NULL); } catch( CX_MemoryException ) { dwRet = ERROR_OUTOFMEMORY; } pSink->Release(); CoUninitialize(); return dwRet; } // Functions called as clients connect/disconnect with pipe. void CProvInfo::AddClient(CClientInfo *pInfo) { DEBUGTRACE( (LOG_ESS, "NCProv: In AddClient...\n")); Lock(); m_listClients.push_back(pInfo); Unlock(); DWORD dwID; IWbemEventSink *pSink = pInfo->m_pProvider->m_pProv->GetSink(); // Will get released by TempThreadProc. pSink->AddRef(); // We have to do this stuff off a thread, because AddClient is // called from the completed read routine (which means the routine // isn't free to receive a response to a client AccessCheck query). CloseHandle( CreateThread( NULL, 0, (LPTHREAD_START_ROUTINE) TempThreadProc, pSink, 0, &dwID)); } void CProvInfo::RemoveClient(CClientInfo *pInfo) { DEBUGTRACE( (LOG_ESS, "NCProv: Client removed...\n")); CClientInfoListIterator info; Lock(); for (info = m_listClients.begin(); info != m_listClients.end(); info++) { if (*info == pInfo) { m_listClients.erase(info); //delete pInfo; pInfo->Release(); break; } } Unlock(); } BOOL CSinkInfo::BuildClassDescendentList( LPCWSTR szClass, CBstrList &listClasses) { if ( szClass == NULL ) return FALSE; IEnumWbemClassObject *pClassEnum = NULL; // Add the class name itself to the list. listClasses.push_front(szClass); if (SUCCEEDED(m_pNamespace->CreateClassEnum( (const BSTR) szClass, WBEM_FLAG_DEEP, NULL, &pClassEnum))) { IWbemClassObject *pClass = NULL; DWORD nCount; while(SUCCEEDED(pClassEnum->Next( WBEM_INFINITE, 1, &pClass, &nCount)) && nCount == 1) { _variant_t vClass; if (SUCCEEDED(pClass->Get( L"__CLASS", 0, &vClass, NULL, NULL) && vClass.vt == VT_BSTR)) { // Upper it to simplify our comparisons later. _wcsupr(V_BSTR(&vClass)); listClasses.push_back(V_BSTR(&vClass)); } pClass->Release(); } pClassEnum->Release(); } return TRUE; } HRESULT CProvInfo::SendMessageToClients(LPBYTE pData, DWORD dwSize, BOOL bGetReply) { HRESULT hr = S_OK; Lock(); for (CClientInfoListIterator client = m_listClients.begin(); client != m_listClients.end(); client++) { hr = (*client)->SendClientMessage(pData, dwSize, bGetReply); if (bGetReply && FAILED(hr)) break; } Unlock(); return hr; } // Functions called as CNCProvider:: functions are called by WMI. HRESULT STDMETHODCALLTYPE CSinkInfo::NewQuery( DWORD dwID, WBEM_WSTR szLang, WBEM_WSTR szQuery) { CQueryParser parser; HRESULT hr; _bstr_t strClass; DEBUGTRACE( (LOG_ESS, "NCProv: CSinkInfo::NewQuery: %d, %S, %S\n", dwID, szLang, szQuery)); if (SUCCEEDED(hr = parser.Init(szQuery)) && SUCCEEDED(hr = parser.GetClassName(strClass))) { CBstrList listClasses; // Make sure this is upper cased (optimizes compares). _wcsupr(strClass); BuildClassDescendentList(strClass, listClasses); Lock(); BOOL bAlreadyInMap = m_mapQueries.find(dwID) != m_mapQueries.end(); // Keep this in our map. if (!bAlreadyInMap) { m_mapQueries[dwID] = listClasses; //pList->assign(listClasses.begin(), listClasses.end()); } Unlock(); if (GetClientCount() != 0) { char buff[256]; CBuffer buffer( buff, 256, CBuffer::ALIGN_DWORD_PTR); // Header stuff buffer.Write((DWORD) NC_CLIMSG_NEW_QUERY_REQ); buffer.Write(m_dwID); // Write the sink ID. buffer.Write((DWORD_PTR) 0); // No cookie needed. // New Query data buffer.Write(dwID); buffer.WriteAlignedLenString(szLang); buffer.WriteAlignedLenString(szQuery); buffer.Write( (DWORD)listClasses.size() ); // Write the newly activated classes. for (CBstrListIterator i = listClasses.begin(); i != listClasses.end(); i++) { if (!bAlreadyInMap) AddClassRef(*i); buffer.WriteAlignedLenString((LPCWSTR) *i); } DWORD dwSize = buffer.GetUsedSize(); SendMessageToClients(buffer.m_pBuffer, dwSize, FALSE); } else { // Add a ref to each class if the query wasn't already in our map. if (!bAlreadyInMap) { for (CBstrListIterator i = listClasses.begin(); i != listClasses.end(); i++) { AddClassRef(*i); } } } } return hr; } HRESULT STDMETHODCALLTYPE CSinkInfo::CancelQuery(DWORD dwID) { DEBUGTRACE( (LOG_ESS, "NCProv: CSinkInfo::CancelQuery: %d\n", dwID)); Lock(); //BOOL bProvGoingAway; CQueryToClassMapIterator query = m_mapQueries.find(dwID); // If this isn't in our map, winmgmt is doing something strange. if (query == m_mapQueries.end()) { Unlock(); return S_OK; } CBstrList &listClasses = (*query).second; // Remove this query's ref on its classes, and remove the classes from the // list that still have a positive ref. The classes left in the list are // the ones that we need to tell our clients to deactivate. for (CBstrListIterator i = listClasses.begin(); i != listClasses.end(); ) { if (RemoveClassRef(*i) > 0) { i = listClasses.erase(i); if (i == listClasses.end()) break; } else // We can't have this in the for loop because listClasses.erase // already moves us ahead. i++; } if (GetClientCount() != 0) { char buff[256]; CBuffer buffer( buff, 256, CBuffer::ALIGN_DWORD_PTR); // Header stuff buffer.Write((DWORD) NC_CLIMSG_CANCEL_QUERY_REQ); buffer.Write(m_dwID); // Write the sink ID. buffer.Write((DWORD_PTR) 0); // No cookie needed. // Cancel Query data buffer.Write(dwID); buffer.Write( (DWORD)listClasses.size() ); // Write the newly deactivated classes. for (CBstrListIterator i = listClasses.begin(); i != listClasses.end(); i++) { buffer.WriteAlignedLenString((LPCWSTR) *i); } DWORD dwSize = buffer.GetUsedSize(); SendMessageToClients(buffer.m_pBuffer, dwSize, FALSE); } // Erase this query ID from our map. m_mapQueries.erase(query); Unlock(); return S_OK; } HRESULT STDMETHODCALLTYPE CProvInfo::AccessCheck( LPCWSTR szLang, LPCWSTR szQuery, DWORD dwSidLen, LPBYTE pSid) { DEBUGTRACE( (LOG_ESS, "NCProv: CProvInfo::AccessCheck: %S, %S\n", szLang, szQuery)); HRESULT hr; char szBuffer[256]; CBuffer buffer(szBuffer, sizeof(szBuffer), CBuffer::ALIGN_DWORD_PTR); // Header stuff buffer.Write((DWORD) NC_CLIMSG_ACCESS_CHECK_REQ); buffer.Write((DWORD) 0); // We only send this to the main sink (for now). buffer.Write((DWORD_PTR) 0); // We'll fill this in later with the real cookie. // Access Check data buffer.WriteAlignedLenString(szLang); buffer.WriteAlignedLenString(szQuery); buffer.Write(dwSidLen); buffer.Write(pSid, dwSidLen); DWORD dwSize = buffer.GetUsedSize(); hr = SendMessageToClients(buffer.m_pBuffer, dwSize, TRUE); return hr; } int CSinkInfo::AddClassRef(LPCWSTR szClass) { CBstrToIntIterator i = m_mapEnabledClasses.find(szClass); int iRet = 1; if (i == m_mapEnabledClasses.end()) { iRet = 1; m_mapEnabledClasses[szClass] = 1; } else iRet = ++(*i).second; return iRet; } int CSinkInfo::RemoveClassRef(LPCWSTR szClass) { CBstrToIntIterator i = m_mapEnabledClasses.find(szClass); int iRet = 0; if (i != m_mapEnabledClasses.end()) { iRet = --(*i).second; if (iRet <= 0) m_mapEnabledClasses.erase(i); } return iRet; } _COM_SMARTPTR_TYPEDEF(IWbemClassObject, __uuidof(IWbemClassObject)); // retrieve the acl from the provider registration // upon success, *pDacl points to a byte array containing the dacl // will be NULL if dacl is NULL // caller's responsibility to delete memory HRESULT CProvInfo::GetProviderDacl(IWbemServices *pNamespace, BYTE** pDacl) { HRESULT hr = WBEM_E_INVALID_PROVIDER_REGISTRATION; DEBUGTRACE((LOG_ESS, "NCProv: GetProviderDacl\n")); WCHAR szObjPath[MAX_PATH * 2]; StringCchPrintfW( szObjPath, MAX_PATH*2, L"__Win32Provider.Name=\"%s\"", (LPCWSTR) m_strName); IWbemClassObjectPtr pRegistration; if (SUCCEEDED(hr = pNamespace->GetObject(CWbemBSTR( szObjPath ), 0, NULL, &pRegistration, NULL))) { _variant_t vSD; if (SUCCEEDED(hr = pRegistration->Get(L"SecurityDescriptor", 0, &vSD, NULL, NULL))) { if (vSD.vt == VT_NULL) { hr = WBEM_S_NO_ERROR; *pDacl = NULL; DEBUGTRACE((LOG_ESS, "NCProv: GetProviderDacl - NULL SD\n")); } else { _ASSERT(vSD.vt == VT_BSTR, L""); PSECURITY_DESCRIPTOR pSD; if (ConvertStringSecurityDescriptorToSecurityDescriptorW( vSD.bstrVal, SDDL_REVISION_1, &pSD, NULL)) { PACL pAcl; BOOL bDaclPresent, bDaclDefaulted; if (GetSecurityDescriptorDacl(pSD, &bDaclPresent, &pAcl, &bDaclDefaulted)) { if (bDaclPresent) { ACL_SIZE_INFORMATION sizeInfo; if ( GetAclInformation(pAcl, &sizeInfo, sizeof(ACL_SIZE_INFORMATION), AclSizeInformation) ) { if (*pDacl = new BYTE[sizeInfo.AclBytesInUse + sizeInfo.AclBytesFree]) memcpy(*pDacl, pAcl, sizeInfo.AclBytesInUse); else hr = WBEM_E_OUT_OF_MEMORY; } else { ERRORTRACE((LOG_ESS, "NCProv: Failed to retrieve ACL Information\n")); hr = WBEM_E_FAILED; } } else { pDacl = NULL; hr = WBEM_S_NO_ERROR; } } else { ERRORTRACE((LOG_ESS, "NCProv: Failed to retrieve DACL\n")); hr = WBEM_E_FAILED; } LocalFree(pSD); } else { ERRORTRACE((LOG_ESS, "NCProv: Failed to convert SecurityDescriptor property\n")); hr = WBEM_E_INVALID_PARAMETER; } } } else ERRORTRACE((LOG_ESS, "NCProv: Failed to retrieve SecurityDescriptor property, 0x%08X\n", hr)); } DEBUGTRACE((LOG_ESS, "NCProv: GetProviderDacl returning 0x%08X\n", hr)); return hr; } #define REPLY_WAIT_TIMEOUT 5000 HRESULT CPipeClient::SendClientMessage(LPVOID pData, DWORD dwSize, BOOL bGetReply) { CPipeClient **pCookie = (CPipeClient**) ((LPBYTE) pData + sizeof(DWORD) * 2); DWORD dwWritten; if (bGetReply) *pCookie = this; // // Allocate a bigger buffer to put the length in // BYTE* pBuffer = new BYTE[dwSize + sizeof(DWORD)]; if(pBuffer == NULL) return WBEM_E_OUT_OF_MEMORY; *(DWORD*)pBuffer = dwSize + sizeof(DWORD); memcpy(pBuffer + sizeof(DWORD), pData, dwSize); BOOL bRes = WriteFile(m_hPipe, pBuffer, dwSize + sizeof(DWORD), &dwWritten, NULL); delete [] pBuffer; if(!bRes) return WBEM_E_FAILED; if(dwWritten != dwSize + sizeof(DWORD)) return WBEM_E_FAILED; if (bGetReply) { HRESULT hr; if (WaitForSingleObject(m_heventMsgReceived, REPLY_WAIT_TIMEOUT) == 0) hr = m_hrClientReply; else hr = WBEM_E_FAILED; return hr; } else return S_OK; }