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.
 
 
 
 
 
 

767 lines
24 KiB

// queryreq.cpp - Query request handler
#include "stdafx.h"
#include <process.h>
#include "queryreq.h"
#include "namemap.h"
#include "resource.h"
#include "util.h"
#include "lmaccess.h"
#include <algorithm>
// 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<IDirectorySearch> 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<LPWSTR>((*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<CQueryRequest*>(wParam);
if( !pQueryReq ) return 0;
QUERY_NOTIFY qryn = static_cast<QUERY_NOTIFY>(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<CQueryThread*>(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<CQueryRequest*>(msg.wParam);
if( !pQueryReq ) break;
pQueryReq->Execute();
}
else
{
DispatchMessage(&msg);
}
}
} // WHILE (TRUE)
CoUninitialize();
return 0;
}