//

// Copyright (c) 1997-2001 Microsoft Corporation, All Rights Reserved
//
// ***************************************************************************
//
//	Original Author: Rajesh Rao
//
// 	$Author: rajeshr $
//	$Date: 6/11/98 4:43p $
// 	$Workfile:ldapcach.cpp $
//
//	$Modtime: 6/11/98 11:21a $
//	$Revision: 1 $	
//	$Nokeywords:  $
//
// 
//  Description: Cache for LDAP Schema objects. 
//
//***************************************************************************

#include "precomp.h"

// Initialize the statics
LPCWSTR CLDAPCache :: ROOT_DSE_PATH			= L"LDAP://RootDSE";
LPCWSTR CLDAPCache :: SCHEMA_NAMING_CONTEXT = L"schemaNamingContext";
LPCWSTR CLDAPCache :: LDAP_PREFIX			= L"LDAP://";	
LPCWSTR CLDAPCache :: LDAP_TOP_PREFIX		= L"LDAP://CN=top,";
LPCWSTR CLDAPCache :: RIGHT_BRACKET			= L")";
LPCWSTR CLDAPCache :: OBJECT_CATEGORY_EQUALS_ATTRIBUTE_SCHEMA	= L"(objectCategory=attributeSchema)";

DWORD CLDAPCache::dwLDAPCacheCount = 0;

//***************************************************************************
//
// CLDAPCache::CLDAPCache
//
// Purpose : Constructor. Fills in the cache with all the properties in LDAP.
//
// Parameters: 
//	dsLog : The CDSLog object  onto which logging will be done.
//***************************************************************************

CLDAPCache :: CLDAPCache()
{
	dwLDAPCacheCount++;
	m_isInitialized = FALSE;
	m_pDirectorySearchSchemaContainer = NULL;

	// Initialize the search preferences often used
	m_pSearchInfo[0].dwSearchPref		= ADS_SEARCHPREF_SEARCH_SCOPE;
	m_pSearchInfo[0].vValue.dwType		= ADSTYPE_INTEGER;
	m_pSearchInfo[0].vValue.Integer		= ADS_SCOPE_ONELEVEL;

	m_pSearchInfo[1].dwSearchPref		= ADS_SEARCHPREF_PAGESIZE;
	m_pSearchInfo[1].vValue.dwType		= ADSTYPE_INTEGER;
	m_pSearchInfo[1].vValue.Integer		= 64;

	/*
	m_pSearchInfo[2].dwSearchPref		= ADS_SEARCHPREF_CACHE_RESULTS;
	m_pSearchInfo[2].vValue.dwType		= ADSTYPE_BOOLEAN;
	m_pSearchInfo[2].vValue.Boolean		= 0;
	*/

	m_lpszSchemaContainerSuffix = NULL;
	m_lpszSchemaContainerPath = NULL;
	// Get the ADSI path of the schema container and store it for future use
	//========================================================================
	IADs *pRootDSE = NULL;
	HRESULT result;
	if(SUCCEEDED(result = ADsOpenObject((LPWSTR)ROOT_DSE_PATH, NULL, NULL, ADS_SECURE_AUTHENTICATION, IID_IADs, (LPVOID *) &pRootDSE)))
	{
		// Get the location of the schema container
		BSTR strSchemaPropertyName = SysAllocString((LPWSTR) SCHEMA_NAMING_CONTEXT);


		// Get the schemaNamingContext property. This property contains the ADSI path
		// of the schema container
		VARIANT variant;
		VariantInit(&variant);
		if(SUCCEEDED(result = pRootDSE->Get(strSchemaPropertyName, &variant)))
		{
			// Store the ADSI path to the schema container
			m_lpszSchemaContainerSuffix = NULL;
			if(m_lpszSchemaContainerSuffix = new WCHAR[wcslen(variant.bstrVal) + 1])
			{
				wcscpy(m_lpszSchemaContainerSuffix, variant.bstrVal );
				g_pLogObject->WriteW( L"CLDAPCache :: Got Schema Container as : %s\r\n", m_lpszSchemaContainerSuffix);
			}

			// Form the schema container path
			//==================================
			m_lpszSchemaContainerPath = NULL;
			if(m_lpszSchemaContainerPath = new WCHAR[wcslen(LDAP_PREFIX) + wcslen(m_lpszSchemaContainerSuffix) + 1])
			{
				wcscpy(m_lpszSchemaContainerPath, LDAP_PREFIX);
				wcscat(m_lpszSchemaContainerPath, m_lpszSchemaContainerSuffix);
				
				m_isInitialized = TRUE;
				/*
				if(SUCCEEDED(result = ADsOpenObject(m_lpszSchemaContainerPath, NULL, NULL, ADS_SECURE_AUTHENTICATION, IID_IDirectorySearch, (LPVOID *) &m_pDirectorySearchSchemaContainer)))
				{

					g_pLogObject->WriteW( L"CLDAPCache :: Got IDirectorySearch on Schema Container \r\n");

					if(SUCCEEDED(result = InitializeObjectTree()))
					{
							m_isInitialized = TRUE;
					}
					else
						g_pLogObject->WriteW( L"CLDAPCache :: InitializeObjectTree() FAILED : %x \r\n", result);
				}
				else
					g_pLogObject->WriteW( L"CLDAPCache :: FAILED to get IDirectorySearch on Schema Container : %x\r\n", result);
				*/
			}
		}
		else
			g_pLogObject->WriteW( L"CLDAPCache :: Get on RootDSE FAILED : %x\r\n", result);

		SysFreeString(strSchemaPropertyName);
		VariantClear(&variant);
		pRootDSE->Release();

	}
	else
		g_pLogObject->WriteW( L"CLDAPClassProvider :: InitializeLDAPProvider ADsOpenObject on RootDSE FAILED : %x\r\n", result);

}

//***************************************************************************
//
// CLDAPCache::~CLDAPCache
//
// Purpose : Destructor 
//
//***************************************************************************

CLDAPCache :: ~CLDAPCache()
{
	dwLDAPCacheCount--;
	if(m_pDirectorySearchSchemaContainer)
		m_pDirectorySearchSchemaContainer->Release();

	delete [] m_lpszSchemaContainerSuffix;
	delete [] m_lpszSchemaContainerPath;
}

//***************************************************************************
//
// CLDAPCache::GetProperty
//
// Purpose : Retreives the IDirectory interface of an LDAP property
//
// Parameters: 
//	lpszPropertyName : The name of the LDAP Property to be retreived
//	ppADSIProperty : The address of the pointer where the CADSIProperty object will be placed
//	bWBEMName : True if the lpszPropertyName is the WBEM name. False, if it is the LDAP name
//
//	Return value:
//		The COM value representing the return status. The user should release the object when done.
//		
//***************************************************************************
HRESULT CLDAPCache :: GetProperty(LPCWSTR lpszPropertyName, CADSIProperty **ppADSIProperty, BOOLEAN bWBEMName)
{
	HRESULT result = E_FAIL;

	// Get the LDAP property name from the WBEM class name
	LPWSTR lpszLDAPPropertyName = NULL;
	if(bWBEMName)
		lpszLDAPPropertyName = CLDAPHelper::UnmangleWBEMNameToLDAP(lpszPropertyName);
	else
		lpszLDAPPropertyName = (LPWSTR)lpszPropertyName; // Save a copy by casting, be careful when deleting

	try
	{
		// This is a cached implementation
		// Check the object tree first
		//===================================

		if((*ppADSIProperty) = (CADSIProperty *) m_objectTree.GetElement(lpszLDAPPropertyName))
		{
			// Found it in the tree. Nothing more to be done. It has already been 'addreff'ed
			result = S_OK;
		}
		else // Get it from ADSI 
		{
			if(!m_pDirectorySearchSchemaContainer)
			{
				if(!SUCCEEDED(result = ADsOpenObject(m_lpszSchemaContainerPath, NULL, NULL, ADS_SECURE_AUTHENTICATION, IID_IDirectorySearch, (LPVOID *) &m_pDirectorySearchSchemaContainer)))
					result = E_FAIL;
			}
			else
				result = S_OK;

			if(SUCCEEDED(result))
			{
				// Search for the property
				LPWSTR lpszQuery = NULL;
				if(lpszQuery = new WCHAR[ wcslen(OBJECT_CATEGORY_EQUALS_ATTRIBUTE_SCHEMA) + wcslen(LDAP_DISPLAY_NAME_ATTR) + wcslen(lpszLDAPPropertyName) + 20])
				{
					try
					{
						wcscpy(lpszQuery, LEFT_BRACKET_STR);
						wcscat(lpszQuery, AMPERSAND_STR);
						wcscat(lpszQuery, OBJECT_CATEGORY_EQUALS_ATTRIBUTE_SCHEMA);
						wcscat(lpszQuery, LEFT_BRACKET_STR);
						wcscat(lpszQuery, LDAP_DISPLAY_NAME_ATTR);
						wcscat(lpszQuery, EQUALS_STR);
						wcscat(lpszQuery, lpszLDAPPropertyName);
						wcscat(lpszQuery, RIGHT_BRACKET_STR);
						wcscat(lpszQuery, RIGHT_BRACKET_STR);

						ADS_SEARCH_HANDLE hADSSearchOuter;
						if(SUCCEEDED(result = m_pDirectorySearchSchemaContainer->ExecuteSearch(lpszQuery, NULL, -1, &hADSSearchOuter)))
						{
							try
							{
								if(SUCCEEDED(result = m_pDirectorySearchSchemaContainer->GetNextRow(hADSSearchOuter)) &&
									result != S_ADS_NOMORE_ROWS)
								{
									*ppADSIProperty = NULL;
									if(*ppADSIProperty = new CADSIProperty())
									{
										try
										{
											// Fill in the details of the property
											if(SUCCEEDED(result = FillInAProperty(*ppADSIProperty, hADSSearchOuter)))
											{
												// Add the property to the tree
												m_objectTree.AddElement((*ppADSIProperty)->GetADSIPropertyName(), *ppADSIProperty);
												// No need to release it since we're returning it
											}
											else
											{
												delete *ppADSIProperty;
												*ppADSIProperty = NULL;
											}
										}
										catch ( ... )
										{
											delete *ppADSIProperty;
											*ppADSIProperty = NULL;

											throw;
										}
									}
									else
										result = E_OUTOFMEMORY;
								}
							}
							catch ( ... )
							{
								m_pDirectorySearchSchemaContainer->CloseSearchHandle(hADSSearchOuter);
								throw;
							}

							m_pDirectorySearchSchemaContainer->CloseSearchHandle(hADSSearchOuter);
						}
					}
					catch ( ... )
					{
						delete [] lpszQuery;
						throw;
					}

					delete [] lpszQuery;
				}
				else
					result = E_OUTOFMEMORY;
			}
		}
	}
	catch ( ... )
	{
		if(bWBEMName)
		{
			delete[] lpszLDAPPropertyName;
			lpszLDAPPropertyName = NULL;
		}
		throw;
	}

	// Delete only what was allocated in this function
	//================================================
	if(bWBEMName)
	{
		delete[] lpszLDAPPropertyName;
		lpszLDAPPropertyName = NULL;
	}

	return result;
}

//***************************************************************************
//
// CLDAPCache::GetClass
//
// Purpose : See Header File
//		
//***************************************************************************
HRESULT CLDAPCache :: GetClass(LPCWSTR lpszWBEMClassName, LPCWSTR lpszLDAPClassName, CADSIClass **ppADSIClass)
{
	/************************************************************
	*************************************************************
	***** NO Cache implementation for now. Always fetch everytime
	*************************************************************
	*************************************************************/

	*ppADSIClass = NULL;
	if(!(*ppADSIClass = new CADSIClass(lpszWBEMClassName, lpszLDAPClassName)) )
		return E_OUTOFMEMORY;
	

	HRESULT result = E_FAIL;

	try
	{
		if(!m_pDirectorySearchSchemaContainer)
		{
			if(!SUCCEEDED(result = ADsOpenObject(m_lpszSchemaContainerPath, NULL, NULL, ADS_SECURE_AUTHENTICATION, IID_IDirectorySearch, (LPVOID *) &m_pDirectorySearchSchemaContainer)))
				result = E_FAIL;
		}
		else
			result = S_OK;

		if(SUCCEEDED(result))
		{
			result = CLDAPHelper::GetLDAPClassFromLDAPName(m_pDirectorySearchSchemaContainer,
				m_lpszSchemaContainerSuffix,
				m_pSearchInfo,
				2,
				*ppADSIClass
				);
		}
	}
	catch ( ... )
	{
		// at least GetLDAPClassFromLDAPName throws
		delete *ppADSIClass;
		*ppADSIClass = NULL;

		throw;
	}

	if(!SUCCEEDED(result))
	{
		delete *ppADSIClass;
		*ppADSIClass = NULL;
	}

	return result;
}

//***************************************************************************
//
// CLDAPCache::GetSchemaContainerSearch
//
// Purpose : To return the IDirectorySearch interface on the schema container
//
// Parameters:
//	ppDirectorySearch : The address where the pointer to the required interface will
//		be stored.
//
// 
//	Return Value: The COM result representing the status. The user should release
//	the interface pointer when done with it.
//***************************************************************************
HRESULT CLDAPCache :: GetSchemaContainerSearch(IDirectorySearch ** ppDirectorySearch)
{
	if(m_pDirectorySearchSchemaContainer)
	{
		*ppDirectorySearch = m_pDirectorySearchSchemaContainer;
		(*ppDirectorySearch)->AddRef();
		return S_OK;
	}
	else
		return E_FAIL;

}

//***************************************************************************
//
// CLDAPCache::EnumerateClasses
//
// Purpose : See Header
//		
//***************************************************************************
HRESULT CLDAPCache::EnumerateClasses(LPCWSTR lpszWBEMSuperclass,
	BOOLEAN bDeep,
	LPWSTR **pppADSIClasses,
	DWORD *pdwNumRows,
	BOOLEAN bArtificialClass)
{
	// Get the LDAP name of the super class
	// Do not mangle if it one of the classes that we know
	//=====================================================
	LPWSTR lpszLDAPSuperClassName = NULL;
	if(_wcsicmp(lpszWBEMSuperclass, LDAP_BASE_CLASS) != 0)
	{
		lpszLDAPSuperClassName = CLDAPHelper::UnmangleWBEMNameToLDAP(lpszWBEMSuperclass);
		if(!lpszLDAPSuperClassName) // We were returned a NULL by the Unmangler, so not a DS class
		{
			*pppADSIClasses = NULL;
			*pdwNumRows = 0;
			return S_OK;
		}
	}

	HRESULT result = E_FAIL;
	if(!m_pDirectorySearchSchemaContainer)
	{
		if(!SUCCEEDED(result = ADsOpenObject(m_lpszSchemaContainerPath, NULL, NULL, ADS_SECURE_AUTHENTICATION, IID_IDirectorySearch, (LPVOID *) &m_pDirectorySearchSchemaContainer)))
			result = E_FAIL;
	}
	else
		result = S_OK;

	if(SUCCEEDED(result))
	{
		result = CLDAPHelper::EnumerateClasses(m_pDirectorySearchSchemaContainer, 
							m_lpszSchemaContainerSuffix, 
							m_pSearchInfo,
							2,
							lpszLDAPSuperClassName, 
							bDeep, 
							pppADSIClasses, 
							pdwNumRows,
							bArtificialClass);
	}

	// If the superclass is an artificial class like "ADS_User", then a concrete sub-class "DS_User" exists.
	// This is added manually here, to both the EnumInfoList as well as the structure being returned
	// The above call to EnumerateClasses would have helpfully left an extra element unfilled at the beginning
	// of the array
	if(SUCCEEDED(result) && bArtificialClass)
	{
		(*pppADSIClasses)[0] = NULL;
		if((*pppADSIClasses)[0] = new WCHAR[wcslen(lpszWBEMSuperclass+1) + 1])
			wcscpy((*pppADSIClasses)[0], lpszWBEMSuperclass+1); 
		else
			result = E_OUTOFMEMORY;
	}

	delete[] lpszLDAPSuperClassName;
	return result;
}

//***************************************************************************
//
// CLDAPCache::IsInitialized
//
// Purpose : Indicates whether the cache was created and initialized succeddfully
//
// Parameters: 
//	None
//
//	Return value:
//		A boolean value indicating the status
//		
//***************************************************************************

BOOLEAN CLDAPCache :: IsInitialized()
{
	return m_isInitialized;
}




//***************************************************************************
//
// CLDAPCache :: InitializeObjectTree
//
// Purpose : Initialize the lexically ordered binary tree with all the properties 
//	LDAP
//
// Parameters:
//	None
// 
//	Return Value: The COM status representing the return value
//***************************************************************************

HRESULT CLDAPCache :: InitializeObjectTree()
{
	// Get the attributes of all the instances of the
	// class "AttributeSchema"
	//=================================================
	HRESULT result = E_FAIL;

/*
	// Now perform a search for all the attributes
	//============================================
	if(SUCCEEDED(result = m_pDirectorySearchSchemaContainer->SetSearchPreference(m_pSearchInfo, 2)))
	{
		ADS_SEARCH_HANDLE hADSSearchOuter;
		
		// Count of attributes
		DWORD dwCount = 0;

		if(SUCCEEDED(result = m_pDirectorySearchSchemaContainer->ExecuteSearch((LPWSTR)OBJECT_CATEGORY_EQUALS_ATTRIBUTE_SCHEMA, NULL, -1, &hADSSearchOuter)))
		{
			CADSIProperty *pNextProperty;
			while(SUCCEEDED(result = m_pDirectorySearchSchemaContainer->GetNextRow(hADSSearchOuter)) &&
				result != S_ADS_NOMORE_ROWS)
			{
				pNextProperty = new CADSIProperty();
				dwCount ++;

				// Fill in the details of the property
				FillInAProperty(pNextProperty, hADSSearchOuter);

				// Add the property to the tree
				m_objectTree.AddElement(pNextProperty->GetADSIPropertyName(), pNextProperty);
				pNextProperty->Release();
			}
			m_pDirectorySearchSchemaContainer->CloseSearchHandle(hADSSearchOuter);
		}

		g_pLogObject->WriteW( L"CLDAPCache :: InitializeObjectTree() Initialized with %d attributes\r\n", dwCount);
	}
	else
		g_pLogObject->WriteW( L"CLDAPCache :: InitializeObjectTree() SetSearchPreference() FAILED with %x\r\n", result);

*/
	return result;
}

HRESULT CLDAPCache :: FillInAProperty(CADSIProperty *pNextProperty, ADS_SEARCH_HANDLE hADSSearchOuter)
{
	ADS_SEARCH_COLUMN adsNextColumn;
	HRESULT result = E_FAIL;
	LPWSTR lpszWBEMName = NULL;
	BOOLEAN bNeedToCheckForORName = FALSE;
	if(SUCCEEDED(result = m_pDirectorySearchSchemaContainer->GetColumn( hADSSearchOuter, (LPWSTR)ATTRIBUTE_SYNTAX_ATTR, &adsNextColumn )))
	{
		if(adsNextColumn.dwADsType == ADSTYPE_PROV_SPECIFIC)
			result = E_FAIL;
		else
		{
			pNextProperty->SetSyntaxOID(adsNextColumn.pADsValues->CaseIgnoreString);
			if(_wcsicmp(adsNextColumn.pADsValues->CaseIgnoreString, DN_WITH_BINARY_OID) == 0)
				bNeedToCheckForORName = TRUE;
		}
		m_pDirectorySearchSchemaContainer->FreeColumn( &adsNextColumn );
	}

	if(SUCCEEDED(result) && SUCCEEDED(result = m_pDirectorySearchSchemaContainer->GetColumn( hADSSearchOuter, (LPWSTR)IS_SINGLE_VALUED_ATTR, &adsNextColumn )))
	{
		if(adsNextColumn.dwADsType == ADSTYPE_PROV_SPECIFIC)
			result = E_FAIL;
		else
			pNextProperty->SetMultiValued( (adsNextColumn.pADsValues->Boolean)? FALSE : TRUE);
		m_pDirectorySearchSchemaContainer->FreeColumn( &adsNextColumn );
	}

	if(SUCCEEDED(result) && SUCCEEDED(result = m_pDirectorySearchSchemaContainer->GetColumn( hADSSearchOuter, (LPWSTR)ATTRIBUTE_ID_ATTR, &adsNextColumn )))
	{
		if(adsNextColumn.dwADsType == ADSTYPE_PROV_SPECIFIC)
			result = E_FAIL;
		else
			pNextProperty->SetAttributeID(adsNextColumn.pADsValues->CaseIgnoreString);
		m_pDirectorySearchSchemaContainer->FreeColumn( &adsNextColumn );
	}

	if(SUCCEEDED(result) && SUCCEEDED(result = m_pDirectorySearchSchemaContainer->GetColumn( hADSSearchOuter, (LPWSTR)COMMON_NAME_ATTR, &adsNextColumn )))
	{
		if(adsNextColumn.dwADsType == ADSTYPE_PROV_SPECIFIC)
			result = E_FAIL;
		else
			pNextProperty->SetCommonName(adsNextColumn.pADsValues->CaseIgnoreString);
		m_pDirectorySearchSchemaContainer->FreeColumn( &adsNextColumn );
	}

	if(SUCCEEDED(result) && SUCCEEDED(result = m_pDirectorySearchSchemaContainer->GetColumn( hADSSearchOuter, (LPWSTR)LDAP_DISPLAY_NAME_ATTR, &adsNextColumn )))
	{
		if(adsNextColumn.dwADsType == ADSTYPE_PROV_SPECIFIC)
			result = E_FAIL;
		else
		{
			pNextProperty->SetADSIPropertyName(adsNextColumn.pADsValues->CaseIgnoreString);
			lpszWBEMName = CLDAPHelper::MangleLDAPNameToWBEM(adsNextColumn.pADsValues->CaseIgnoreString);
			pNextProperty->SetWBEMPropertyName(lpszWBEMName);
			delete []lpszWBEMName;
		}
		m_pDirectorySearchSchemaContainer->FreeColumn( &adsNextColumn );
	}

	if(SUCCEEDED(result) && SUCCEEDED(m_pDirectorySearchSchemaContainer->GetColumn( hADSSearchOuter, (LPWSTR)MAPI_ID_ATTR, &adsNextColumn )))
	{
		if(adsNextColumn.dwADsType == ADSTYPE_PROV_SPECIFIC)
			result = E_FAIL;
		else
			pNextProperty->SetMAPI_ID(adsNextColumn.pADsValues->Integer);
		m_pDirectorySearchSchemaContainer->FreeColumn( &adsNextColumn );
	}

	if(SUCCEEDED(result) && SUCCEEDED(result = m_pDirectorySearchSchemaContainer->GetColumn( hADSSearchOuter, (LPWSTR)OM_SYNTAX_ATTR, &adsNextColumn )))
	{
		if(adsNextColumn.dwADsType == ADSTYPE_PROV_SPECIFIC)
			result = E_FAIL;
		else
			pNextProperty->SetOMSyntax(adsNextColumn.pADsValues->Integer);
		m_pDirectorySearchSchemaContainer->FreeColumn( &adsNextColumn );
	}

	if(bNeedToCheckForORName && SUCCEEDED(result) && SUCCEEDED(m_pDirectorySearchSchemaContainer->GetColumn( hADSSearchOuter, (LPWSTR)OM_OBJECT_CLASS_ATTR, &adsNextColumn )))
	{
		if(adsNextColumn.dwADsType == ADSTYPE_PROV_SPECIFIC)
			result = E_FAIL;
		else
		{
			// Just the first octet in the LPBYTE array is enough for differntiating between ORName and DNWithBinary
			if((adsNextColumn.pADsValues->OctetString).lpValue[0] == 0x56)
				pNextProperty->SetORName(TRUE);
		}
		m_pDirectorySearchSchemaContainer->FreeColumn( &adsNextColumn );
	}

	if(SUCCEEDED(result) && SUCCEEDED(m_pDirectorySearchSchemaContainer->GetColumn( hADSSearchOuter, (LPWSTR)SEARCH_FLAGS_ATTR, &adsNextColumn )))
	{
		if(adsNextColumn.dwADsType == ADSTYPE_PROV_SPECIFIC)
			result = E_FAIL;
		else
			pNextProperty->SetSearchFlags(adsNextColumn.pADsValues->Integer);
		m_pDirectorySearchSchemaContainer->FreeColumn( &adsNextColumn );
	}

	if(SUCCEEDED(result) && SUCCEEDED(m_pDirectorySearchSchemaContainer->GetColumn( hADSSearchOuter, (LPWSTR)SYSTEM_ONLY_ATTR, &adsNextColumn )))
	{
		if(adsNextColumn.dwADsType == ADSTYPE_PROV_SPECIFIC)
			result = E_FAIL;
		else
			pNextProperty->SetSystemOnly(TRUE);
		m_pDirectorySearchSchemaContainer->FreeColumn( &adsNextColumn );
	}

	return result;
}