// queryreq.cpp - Query request handler #include "stdafx.h" #include #include "queryreq.h" #include "namemap.h" #include "resource.h" #include "util.h" #include "lmaccess.h" #include // singleton query thread object CQueryThread g_QueryThread; //////////////////////////////////////////////////////////////////////////////////////////// // class CQueryRequest // #define MSG_QUERY_START (WM_USER + 1) #define MSG_QUERY_REPLY (WM_USER + 2) // static members HWND CQueryRequest::m_hWndCB = NULL; // Forward ref LRESULT CALLBACK QueryRequestWndProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam); HANDLE CQueryRequest::m_hMutex = NULL; // query window class object CMsgWindowClass QueryWndClass(L"BOMQueryHandler", QueryRequestWndProc); CQueryRequest::CQueryRequest() { m_cRef = 0; m_eState = QRST_INACTIVE; m_cPrefs = 0; m_paSrchPrefs = NULL; m_hrStatus = S_OK; m_pvstrAttr = NULL; m_pQueryCallback = NULL; } CQueryRequest::~CQueryRequest() { if (m_paSrchPrefs != NULL) delete m_paSrchPrefs; } HRESULT CQueryRequest::SetQueryParameters(LPCWSTR pszScope, LPCWSTR pszFilter, string_vector* pvstrClasses, string_vector* pvstrAttr) { if( !pszScope || !pszScope[0] || !pszFilter || !pvstrClasses || pvstrClasses->empty() ) return E_INVALIDARG; if( m_eState != QRST_INACTIVE ) return E_FAIL; m_strScope = pszScope; m_strFilter = pszFilter; m_vstrClasses = *pvstrClasses; m_pvstrAttr = pvstrAttr; return S_OK; } HRESULT CQueryRequest::SetSearchPreferences(ADS_SEARCHPREF_INFO* paSrchPrefs, int cPrefs) { m_cPrefs = cPrefs; if( cPrefs == 0 ) return S_OK; // Special case if( !paSrchPrefs ) return E_POINTER; if( m_eState != QRST_INACTIVE ) return E_FAIL; m_paSrchPrefs = new ADS_SEARCHPREF_INFO[cPrefs]; if (m_paSrchPrefs == NULL) return E_OUTOFMEMORY; memcpy(m_paSrchPrefs, paSrchPrefs, cPrefs * sizeof(ADS_SEARCHPREF_INFO)); return S_OK; } HRESULT CQueryRequest::SetCallback(CQueryCallback* pCallback, LPARAM lUserParam) { if( m_eState != QRST_INACTIVE ) return E_FAIL; m_pQueryCallback = pCallback; m_lUserParam = lUserParam; return S_OK; } HRESULT CQueryRequest::Start() { if( m_strScope.empty() || !m_pQueryCallback ) return E_FAIL; if( m_eState != QRST_INACTIVE ) return E_FAIL; // Create callback window the first time (m_hwndCB is static) if (m_hWndCB == NULL) m_hWndCB = QueryWndClass.Window(); if (m_hWndCB == NULL) return E_FAIL; // Create mutex the first time (m_hMutex is static) if (m_hMutex == NULL) m_hMutex = CreateMutex(NULL, FALSE, NULL); if (m_hMutex == NULL) return E_FAIL; // Post request to query thread Lock(); BOOL bStat = g_QueryThread.PostRequest(this); if (bStat) { m_eState = QRST_QUEUED; m_cRef++; } Unlock(); return bStat ? S_OK : E_FAIL; } HRESULT CQueryRequest::Stop(BOOL bNotify) { HRESULT hr = S_OK; Lock(); if (m_eState == QRST_QUEUED || m_eState == QRST_ACTIVE) { // Change state to stopped and notify the user if requested. // Don't release the query request here because the query thread needs // to see the new state. When the thread sees the stopped state it will // send a message to this thread's window proc, which will release the request. m_eState = QRST_STOPPED; if (bNotify) { ASSERT(m_pQueryCallback != NULL); m_pQueryCallback->QueryCallback(QRYN_STOPPED, this, m_lUserParam); } } else { hr = S_FALSE; } Unlock(); return hr; } void CQueryRequest::Release() { ASSERT(m_cRef > 0); if (--m_cRef == 0) delete this; } void CQueryRequest::Execute() { // Move query to active state (if still in queued state) Lock(); ASSERT(m_eState == QRST_QUEUED || m_eState == QRST_STOPPED); if (m_eState == QRST_STOPPED) { PostMessage(m_hWndCB, MSG_QUERY_REPLY, (WPARAM)this, (LPARAM)QRYN_STOPPED); Unlock(); return; } m_eState = QRST_ACTIVE; Unlock(); // Intiate the query CComPtr spDirSrch; ADS_SEARCH_HANDLE hSearch; LPCWSTR* paszAttr = NULL; LPCWSTR* paszNameAttr = NULL; do { // Create a directory search object m_hrStatus = ADsOpenObject(m_strScope.c_str(), NULL, NULL, ADS_SECURE_AUTHENTICATION, IID_IDirectorySearch, (LPVOID*)&spDirSrch); BREAK_ON_FAILURE(m_hrStatus) if (m_cPrefs != 0) { m_hrStatus = spDirSrch->SetSearchPreference(m_paSrchPrefs, m_cPrefs); BREAK_ON_FAILURE(m_hrStatus) } // Get naming attribute for each query class // This will be the attribute placed in column [0] of each RowItem paszNameAttr = new LPCWSTR[m_vstrClasses.size()]; if (paszNameAttr == NULL) { m_hrStatus = E_OUTOFMEMORY; break; } for (int i = 0; i < m_vstrClasses.size(); i++) { // get display name map for this class DisplayNameMap* pNameMap = DisplayNames::GetMap(m_vstrClasses[i].c_str()); ASSERT(pNameMap != NULL); // Save pointer to naming attribute paszNameAttr[i] = pNameMap->GetNameAttribute(); } // Create array of attribute name ptrs for ExecuteSearch // Include space for user selected attribs, naming attribs, class and object path (distinguised name) paszAttr = new LPCWSTR[m_pvstrAttr->size() + m_vstrClasses.size() + 3]; if (paszAttr == NULL) { m_hrStatus = E_OUTOFMEMORY; break; } int cAttr = 0; // add user selected attributes // These must be first because the column loop in the query code below indexes through them for (i=0; i < m_pvstrAttr->size(); i++) paszAttr[cAttr++] = const_cast((*m_pvstrAttr)[i].c_str()); // add class naming attributes for (i = 0; i < m_vstrClasses.size(); i++) { // Multiple classes can use the same name so check for dup before adding int j = 0; while (j < i && wcscmp(paszNameAttr[i], paszNameAttr[j]) != 0) j++; if (j == i) paszAttr[cAttr++] = paszNameAttr[i]; } // add path attribute paszAttr[cAttr++] = L"distinguishedName"; // add class attribute paszAttr[cAttr++] = L"objectClass"; // add user state attribute paszAttr[cAttr++] = L"userAccountControl"; // Add (& ... ) around the query because DSQuery leaves it off // and GetNextRow causes heap error or endless query without it Lock(); m_strFilter.insert(0, L"(&"), m_strFilter.append(L")"); // Initiate search m_hrStatus = spDirSrch->ExecuteSearch((LPWSTR)m_strFilter.c_str(), (LPWSTR*)paszAttr, cAttr, &hSearch); Unlock(); BREAK_ON_FAILURE(m_hrStatus) } while (FALSE); // If search failed, change query state and send failure message if (FAILED(m_hrStatus)) { // Don't do anything if query has already been stopped Lock(); if (m_eState == QRST_ACTIVE) { m_eState = QRST_FAILED; PostMessage(m_hWndCB, MSG_QUERY_REPLY, (WPARAM)this, (LPARAM)QRYN_FAILED); } Unlock(); delete [] paszAttr; paszAttr = NULL; delete [] paszNameAttr; paszNameAttr = NULL; return; } // Get class map for translating class names DisplayNameMap* pNameMap = DisplayNames::GetClassMap(); if( !pNameMap ) return; // Get Results int nItems = 0; while (nItems < MAX_RESULT_ITEMS && spDirSrch->GetNextRow(hSearch) == S_OK) { ADS_SEARCH_COLUMN col; // Allocate row item for user attributes plus fixed attributes (name & class) CRowItem* pRowItem = new CRowItem(m_pvstrAttr->size() + ROWITEM_USER_INDEX); if (pRowItem == NULL) { m_hrStatus = E_OUTOFMEMORY; break; } // Get path attribute if (spDirSrch->GetColumn(hSearch, L"distinguishedName", &col) == S_OK) { pRowItem->SetObjPath(col.pADsValues->CaseIgnoreString); spDirSrch->FreeColumn(&col); } // Get class attribute if (spDirSrch->GetColumn(hSearch, L"objectClass", &col) == S_OK) { // Class name is last element of multivalued objectClass attribute ASSERT(col.dwADsType == ADSTYPE_CASE_IGNORE_STRING); LPWSTR pszClass = col.pADsValues[col.dwNumValues-1].CaseIgnoreString; // Put class display name in row item pRowItem->SetAttribute(ROWITEM_CLASS_INDEX, pNameMap->GetAttributeDisplayName(pszClass)); // Find class name in query classes vector string_vector::iterator itClass = std::find(m_vstrClasses.begin(), m_vstrClasses.end(), pszClass); // if found, look up name attribute for this class and put it in the rowitem if (itClass != m_vstrClasses.end()) { ADS_SEARCH_COLUMN colName; if (spDirSrch->GetColumn(hSearch, (LPWSTR)paszNameAttr[itClass - m_vstrClasses.begin()], &colName) == S_OK) { pRowItem->SetAttribute(ROWITEM_NAME_INDEX, colName.pADsValues->CaseIgnoreString); spDirSrch->FreeColumn(&colName); } } else { // Use CN from path for the name LPCWSTR pszPath = pRowItem->GetObjPath(); if( pszPath == NULL ) { m_hrStatus = E_OUTOFMEMORY; break; } LPCWSTR pszSep; if (_tcsnicmp(pszPath, L"CN=", 3) == 0 && (pszSep = _tcschr(pszPath + 3, L',')) != NULL) { // Limit name to MAX_PATH chars int cch = pszSep - (pszPath + 3); if (cch >= MAX_PATH) cch = MAX_PATH - 1; // Create null-terminated CN string WCHAR szTemp[MAX_PATH]; memcpy(szTemp, pszPath + 3, cch * sizeof(WCHAR)); szTemp[cch] = 0; pRowItem->SetAttribute(ROWITEM_NAME_INDEX , szTemp); } else { ASSERT(0); } } spDirSrch->FreeColumn(&col); } // Set disabled status based on the value returned by AD if (SUCCEEDED(spDirSrch->GetColumn(hSearch, L"userAccountControl", &col))) { pRowItem->SetDisabled((col.pADsValues->Integer & UF_ACCOUNTDISABLE) != 0); spDirSrch->FreeColumn(&col); } // loop through all user attributes for (int iAttr = 0; iAttr < m_pvstrAttr->size(); ++iAttr) { HRESULT hr = spDirSrch->GetColumn(hSearch, (LPWSTR)paszAttr[iAttr], &col); if (SUCCEEDED(hr) && col.dwNumValues > 0) { WCHAR szBuf[MAX_PATH] = {0}; LPWSTR psz = NULL; switch (col.dwADsType) { case ADSTYPE_DN_STRING: case ADSTYPE_CASE_EXACT_STRING: case ADSTYPE_PRINTABLE_STRING: case ADSTYPE_NUMERIC_STRING: case ADSTYPE_TYPEDNAME: case ADSTYPE_FAXNUMBER: case ADSTYPE_PATH: case ADSTYPE_OBJECT_CLASS: case ADSTYPE_CASE_IGNORE_STRING: psz = col.pADsValues->CaseIgnoreString; break; case ADSTYPE_BOOLEAN: if (col.pADsValues->Boolean) { static WCHAR szYes[16] = L""; if (szYes[0] == 0) { int nLen = ::LoadString(_Module.GetResourceInstance(), IDS_YES, szYes, lengthof(szYes)); ASSERT(nLen != 0); } psz = szYes; } else { static WCHAR szNo[16] = L""; if (szNo[0] == 0) { int nLen = ::LoadString(_Module.GetResourceInstance(), IDS_NO, szNo, lengthof(szNo)); ASSERT(nLen != 0); } psz = szNo; } break; case ADSTYPE_INTEGER: _snwprintf( szBuf, MAX_PATH-1, L"%d",col.pADsValues->Integer ); psz = szBuf; break; case ADSTYPE_OCTET_STRING: if ( (_wcsicmp(col.pszAttrName, L"objectGUID") == 0) ) { //Cast to LPGUID GUID* pObjectGUID = (LPGUID)(col.pADsValues->OctetString.lpValue); //Convert GUID to string. ::StringFromGUID2(*pObjectGUID, szBuf, 39); psz = szBuf; } break; case ADSTYPE_UTC_TIME: { SYSTEMTIME systemtime = col.pADsValues->UTCTime; DATE date; VARIANT varDate; if (SystemTimeToVariantTime(&systemtime, &date) != 0) { //Pack in variant.vt. varDate.vt = VT_DATE; varDate.date = date; if( SUCCEEDED(VariantChangeType(&varDate,&varDate, VARIANT_NOVALUEPROP, VT_BSTR)) ) { wcsncpy(szBuf, varDate.bstrVal, MAX_PATH-1); } VariantClear(&varDate); } } break; case ADSTYPE_LARGE_INTEGER: { LARGE_INTEGER liValue; FILETIME filetime; DATE date; SYSTEMTIME systemtime; VARIANT varDate; liValue = col.pADsValues->LargeInteger; filetime.dwLowDateTime = liValue.LowPart; filetime.dwHighDateTime = liValue.HighPart; if((filetime.dwHighDateTime!=0) || (filetime.dwLowDateTime!=0)) { //Check for properties of type LargeInteger that represent time. //If TRUE, then convert to variant time. if ((0==wcscmp(L"accountExpires", col.pszAttrName)) || (0==wcscmp(L"badPasswordTime", col.pszAttrName))|| (0==wcscmp(L"lastLogon", col.pszAttrName)) || (0==wcscmp(L"lastLogoff", col.pszAttrName)) || (0==wcscmp(L"lockoutTime", col.pszAttrName)) || (0==wcscmp(L"pwdLastSet", col.pszAttrName)) ) { //Handle special case for Never Expires where low part is -1 if (filetime.dwLowDateTime==-1) { psz = L"Never Expires"; } else { if ( (FileTimeToLocalFileTime(&filetime, &filetime) != 0) && (FileTimeToSystemTime(&filetime, &systemtime) != 0) && (SystemTimeToVariantTime(&systemtime, &date) != 0) ) { //Pack in variant.vt. varDate.vt = VT_DATE; varDate.date = date; if( SUCCEEDED(VariantChangeType(&varDate, &varDate, VARIANT_NOVALUEPROP,VT_BSTR)) ) { wcsncpy( szBuf, varDate.bstrVal, lengthof(szBuf) ); psz = szBuf; } VariantClear(&varDate); } } } else { //Print the LargeInteger. _snwprintf(szBuf, MAX_PATH-1, L"%d,%d",filetime.dwHighDateTime, filetime.dwLowDateTime); } } } break; case ADSTYPE_NT_SECURITY_DESCRIPTOR: break; } if (psz != NULL) hr = pRowItem->SetAttribute(iAttr + ROWITEM_USER_INDEX, psz); spDirSrch->FreeColumn(&col); } } // for user attributes // Add row to new rows vector and notify client Lock(); // if query is still active if (m_eState == QRST_ACTIVE) { m_vRowsNew.push_back(*pRowItem); delete pRowItem; // notify if first new row if (m_vRowsNew.size() == 1) PostMessage(m_hWndCB, MSG_QUERY_REPLY, (WPARAM)this, (LPARAM)QRYN_NEWROWITEMS); Unlock(); } else { delete pRowItem; Unlock(); break; } } Lock(); // If query wasn't stopped, then change state to completed and notify main thread if (m_eState == QRST_ACTIVE) { m_eState = QRST_COMPLETE; PostMessage(m_hWndCB, MSG_QUERY_REPLY, (WPARAM)this, (LPARAM)QRYN_COMPLETED); } else if (m_eState == QRST_STOPPED) { // if query was stopped, then acknowledge with notify so main thread can release the query req PostMessage(m_hWndCB, MSG_QUERY_REPLY, (WPARAM)this, (LPARAM)QRYN_STOPPED); } Unlock(); spDirSrch->CloseSearchHandle(hSearch); delete [] paszAttr; delete [] paszNameAttr; } LRESULT CALLBACK QueryRequestWndProc(HWND hWnd, UINT nMsg, WPARAM wParam, LPARAM lParam) { if (nMsg == MSG_QUERY_REPLY) { CQueryRequest* pQueryReq = reinterpret_cast(wParam); if( !pQueryReq ) return 0; QUERY_NOTIFY qryn = static_cast(lParam); // Don't do any callbacks for a stopped query. Also, don't forward a stop notification. // The client receives a QRYN_STOPPED directly from the CQueryRequest::Stop() method. if (pQueryReq->m_eState != QRST_STOPPED && qryn != QRYN_STOPPED) pQueryReq->m_pQueryCallback->QueryCallback(qryn, pQueryReq, pQueryReq->m_lUserParam); // any notify but new row items indicates query is completed, so it can be released if (qryn != QRYN_NEWROWITEMS) pQueryReq->Release(); return 0; } return DefWindowProc(hWnd, nMsg, wParam, lParam); } //////////////////////////////////////////////////////////////////////////////////////////// // class QueryThread // LRESULT CALLBACK QueryHandlerWndProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam); //----------------------------------------------------------------------------- // CQueryThread::StartThread // // Start the thread //----------------------------------------------------------------------------- BOOL CQueryThread::Start() { // If thread exists, just return if (m_hThread != NULL) return TRUE; BOOL bRet = FALSE; do // False loop { // Create start event m_hEvent = CreateEvent(NULL, FALSE, FALSE, NULL); if (m_hEvent == NULL) break; // Start the thread m_hThread = (HANDLE)_beginthreadex(NULL, 0, ThreadProc, this, 0, &m_uThreadID); if (m_hThread == NULL) break; // Wait for start event DWORD dwEvStat = WaitForSingleObject(m_hEvent, 10000); if (dwEvStat != WAIT_OBJECT_0) break; bRet = TRUE; } while (0); ASSERT(bRet); // Clean up on failure if (!bRet) { if (m_hEvent) { CloseHandle(m_hEvent); m_hEvent = NULL; } if (m_hThread) { CloseHandle(m_hThread); m_hThread = NULL; } } return bRet; } void CQueryThread::Kill() { if (m_hThread != NULL) { PostThreadMessage(m_uThreadID, WM_QUIT, 0, 0); MSG msg; while (TRUE) { // Wait either for the thread to be signaled or any input event. DWORD dwStat = MsgWaitForMultipleObjects(1, &m_hThread, FALSE, INFINITE, QS_ALLINPUT); if (WAIT_OBJECT_0 == dwStat) break; // The thread is signaled. // There is one or more window message available. // Dispatch them and wait. if (PeekMessage(&msg,NULL,NULL,NULL,PM_REMOVE)) { TranslateMessage(&msg); DispatchMessage(&msg); } } CloseHandle(m_hThread); CloseHandle(m_hEvent); m_hThread = NULL; m_hEvent = NULL; } } BOOL CQueryThread::PostRequest(CQueryRequest* pQueryReq) { // make sure thread is active BOOL bStat = Start(); if (bStat) bStat = PostThreadMessage(m_uThreadID, MSG_QUERY_START, (WPARAM)pQueryReq, (LPARAM)0); return bStat; } unsigned _stdcall CQueryThread::ThreadProc(void* pVoid ) { ASSERT(pVoid != NULL); // Do a PeekMessage to create the message queue MSG msg; PeekMessage(&msg, NULL, WM_USER, WM_USER, PM_NOREMOVE); // Then signal that thread is started CQueryThread* pThread = reinterpret_cast(pVoid); if( !pThread ) return 0; ASSERT(pThread->m_hEvent != NULL); SetEvent(pThread->m_hEvent); HRESULT hr = CoInitialize(NULL); RETURN_ON_FAILURE(hr); // Mesage loop while (TRUE) { long lStat = GetMessage(&msg, NULL, 0, 0); // zero => WM_QUIT received, so exit thread function if (lStat == 0) break; if (lStat > 0) { // Only process thread message of the expected type if (msg.hwnd == NULL && msg.message == MSG_QUERY_START) { CQueryRequest* pQueryReq = reinterpret_cast(msg.wParam); if( !pQueryReq ) break; pQueryReq->Execute(); } else { DispatchMessage(&msg); } } } // WHILE (TRUE) CoUninitialize(); return 0; }