// borfads:  Active Directory routines for borfrras.


#include "stdafx.h"
#include "borfrras.h"

using namespace std;

// Global options.
extern BORF_OPTIONS options;


void BorfADsWarning( LPCTSTR lpwszSource )
{
/**
 *  Generates an event log warning for the given source.
 *
 *    lpwczSource [in]  A desription about where the error happened.
 */

	// The error buffer.
	DWORD dwError;

	// A buffer for the error message.
	TCHAR wszBuffer [1024];

	// A buffer for the provider string.
	TCHAR wszProvider [1024];


	// Get the last error.
	ADsGetLastError(
		&dwError,
		(LPTSTR)&wszBuffer,
		1023,
		(LPTSTR)wszProvider,
		1023 );

	// DEBUG:
	wcerr << lpwszSource << L": " << dwError << L": " << wszBuffer << endl;


	// Log the warning.
	BorfEvent(
		EVENTLOG_WARNING_TYPE,
		IDM_WARNING_ADS,
		sizeof( dwError ),
		&dwError,
		lpwszSource,
		wszBuffer,
		wszProvider,
		NULL );

	
} // BorfADsWarning


HRESULT BorfADsMembers( const CString& sADsPath, CStringList& borfRetVal )
{
/**
 *   Returns the names of members in an Active Directory group.
 * 
 *     sADsPath  [in]      The AD group name in "LDAP://" or "WinNT://" format.
 *     borfRetValue [out]  The names of group members in "WinNT://" format.
 *
 */

	// A result holder for COM methods.
	HRESULT hResult;

	// A pointer for system buffers.
	BSTR bszPath;

	// A shunt that is used to convert bstrPath.
	CString sPath;

	// A pointer for COM indentity interfaces.
	IUnknown* pUnknown;

	// A pointer for COM dispatch interfaces;
	IDispatch* pDispatch;

	// The group handle
	IADsGroup* pGroup;

	// The members handle for pGroup.
	IADsMembers* pMembers;

	// The enumermation handle pMembers.
	IEnumVARIANT* pEnumeration;

	// An element buffer for pEnumeration.
	VARIANT vElement;

	// A pointer for Active Directory members.
	IADs* pADs;

	// A parameter for ADsEnumerateNext.
	DWORD dwCount;


	// Load the Active Directory group.
	hResult = ADsGetObject( (LPCTSTR)sADsPath, IID_IADsGroup, (void**)&pGroup );

	
	if( hResult == MK_E_SYNTAX )
	{
		// The ADSI interfaces return this "bad syntax" error when
		// the requisite COM interfaces have not been initialized.
		//
		// The usual way to inialize these ADSI interfaces is to call
		// CoInitialize() from within each thread that uses them.

		wcerr << L"ADsGetObject: Bad syntax." << endl;
		wcerr << L"Error: COM+ is not initialized." << endl;
		wcerr << L"Check whether this computer has the necessary libraries." << endl;

		BorfADsWarning( L"BorfADsMembers: ADsGetObject" );

		BorfEvent(
			EVENTLOG_ERROR_TYPE,
			IDM_VOID,
			sizeof( hResult ),
			&hResult,
			L"COM+ is not initialized. Check whether this computer has the necessary libraries.",
			NULL );
  
		return hResult;
	}

	if( FAILED( hResult ) )
	{
		BorfADsWarning( L"BorfADsMembers: ADsGetObject" );
		wcerr << L"Error: Unable to load \"" << (LPCTSTR)sADsPath << L"\"" << endl;
		return hResult;
	}


	// Get a pointer to the group members.
	hResult = pGroup->Members( &pMembers );

	if( FAILED( hResult ) )
	{
		BorfADsWarning( L"BorfADsMembers: pGroup->Members" );
		return hResult;
	}


	// Load the group enumeration object. Notice the double-underscore.
	hResult = pMembers->get__NewEnum( &pUnknown );

	if( FAILED( hResult ) )
	{
		BorfADsWarning( L"BorfADsMembers: pMembers->get__NewEnum" );
		return hResult;
	}


	// Load the group enumeration interface.
	hResult = pUnknown->QueryInterface( IID_IEnumVARIANT, (void**)&pEnumeration );

	if( FAILED( hResult ) )
	{
		BorfADsWarning( L"BorfADsMembers: pUknown->QueryInterface" );
		return hResult;
	}


	// Ensure that the borfRetVal object is empty.
	borfRetVal.RemoveAll();


	while( true )
	{
		// Get the next element in the enumeration.
		hResult = ADsEnumerateNext( pEnumeration, 1, &vElement, &dwCount );

		if( FAILED( hResult ) )
		{
			BorfADsWarning( L"BorfADsMembers: ADsEnumerateNext" );
			return hResult;
		}

		// Break when all elements have been enumerated.
		if( hResult == S_FALSE ) {  break; }

		// Get the dispatch interface for the returned group element.
		pDispatch = vElement.pdispVal;

		// Load the Active Directory service interface.
		hResult = pDispatch->QueryInterface( IID_IADs, (void**)&pADs );

		if( FAILED( hResult ) )
		{
			BorfADsWarning( L"BorfADsMembers: pDispatch->QueryInterface" );
			return hResult;
		}

		// Load the member name.
		hResult = pADs->get_ADsPath( &bszPath );
		
		// Convert the member name.
		sPath = bszPath;
		
		// This is what the user wants.
		borfRetVal.AddTail( sPath );
		
		// Release the system buffer.
		SysFreeString( bszPath ); 

		// Release variant members.
		VariantClear( &vElement );

	} // while

	dwCount = pMembers->Release();
	wcout << L"BorfADsMembers: pMembers->Release: " << dwCount << endl;

	dwCount = pGroup->Release();
	wcout << L"BorfADsMembers: pGroup->Release: " << dwCount << endl;

	return S_OK;

} // BorfADsGetMembers



HRESULT BorfADsAddNameToGroup( LPCTSTR lpwszADsName, LPCTSTR lpwszADsGroup )
{
/**
 *   Adds the name given in lpwszADsName to the group given in lpwszADsGroup.
 * 
 *     lpwszADsGroup [in]  An Active Directory group name.
 *     lpwszADsName  [in]  An Active Directory object name.
 *
 *   The parameters may be ADSI paths or ADSI object names.
 *
 */

	// A result holder for COM methods.
	HRESULT hResult;

	// A pointer for system buffers.
	BSTR bszPath;

	// The group handle
	IADsGroup* pGroup;

	// The ADSI moniker that we must use.
	CString sADsMoniker = L"WinNT://";

	// A convenient form of the group name.
	CString sADsGroup = lpwszADsGroup;

	// A convenient form of the object name.
	CString sADsName = lpwszADsName;

	// A shunt for formatting ADSI path names.
	CString sName;


	// Check whether lpwszADsGroup was passed in as an ADSI path name.
	if( sADsGroup.Left( sADsMoniker.GetLength() ) != sADsMoniker )
	{
		// Shunt the group name.
		sName = sADsGroup;

		// Convert lpwszADsGroup into an ADSI path name.
		sADsGroup.Format( L"%s%s/%s,group", sADsMoniker, options.sDomainName, sName );
	}


	// Check whether lpwszADsName was passed in as an ADSI path name.
	if( sADsName.Left( sADsMoniker.GetLength() ) != sADsMoniker )
	{
		// Shunt the object name.
		sName = sADsName;
		
		// Convert lpwszADsName into an ADSI path name, sans type.
		sADsName.Format( L"%s%s/%s", sADsMoniker, options.sDomainName, sName );
	}

	wcout << L"BorfADsGroupAdd: Group: "  << (LPCTSTR)sADsGroup << endl;
	wcout << L"BorfADsGroupAdd: Object: " << (LPCTSTR)sADsName  << endl;

	// Load the group.
	hResult = ADsGetObject( (LPCTSTR)sADsGroup, IID_IADsGroup, (void**)&pGroup );
	
	if( hResult == MK_E_SYNTAX )
	{
		// The ADSI interfaces return this "bad syntax" error when
		// the requisite COM interfaces have not been initialized.
		//
		// The usual way to inialize these ADSI interfaces is to call
		// CoInitialize() from within each thread that uses them.

		wcerr << L"BorfADsGroupAdd: ADsGetObject: Bad syntax." << endl;
		wcerr << L"Error: COM+ is not initialized." << endl;
		wcerr << L"Check whether this computer has the necessary libraries." << endl;

		BorfADsWarning( L"BorfADsGroupAdd: ADsGetObject" );

		BorfEvent(
			EVENTLOG_ERROR_TYPE,
			IDM_VOID,
			sizeof( hResult ),
			&hResult,
			L"COM+ is not initialized. Check whether this computer has the necessary libraries.",
			NULL );

		return hResult;
	}

	if( FAILED( hResult ) )
	{
		wcerr << L"Error: Unable to load \"" << (LPCTSTR)sADsGroup << L"\"" << endl;
		BorfADsWarning( L"BorfADsGroupAdd: ADsGetObject" );
		return hResult;
	}


	// Allocate the path name as a system string.
	bszPath = sADsName.AllocSysString();

	// Add the path name to the group.
	hResult = pGroup->Add( bszPath );

	// Free the system string.
	SysFreeString( bszPath );

	if( hResult == 0x800708BC )
	{
		// FIXME: Find the appropriate symbol for this error code.
		//        The SDK includes do not have anything defined to this value.

		// The object was already part of the group.
		return S_FALSE;
	}

	if( FAILED( hResult ) )
	{
		BorfADsWarning( L"BorfADsGroupAdd: pGroup->Add" );
		return hResult;
	}

	DWORD dwResult = pGroup->Release();
	wcout << L"BorfADsAddNameToGroup: pGroup->Release: " << dwResult << endl;


	// Log that we successfully added the given object to the given group.
	BorfEvent(
		EVENTLOG_INFORMATION_TYPE,
		IDM_ADS_NAMETOGROUP,
		0,
		NULL,
		lpwszADsName,
		lpwszADsGroup,
		NULL );

	return S_OK;

} // BorfADsAddNameToGroup


HRESULT BorfStripWinntPath( CString& sADsPath )
{
/**
 *  Strips the moniker, parent, and type from an WinNT-style ADSI path.
 *
 *    sADsPath [in,out]  Returns the object name.
 *
 *  The sADsPath argument must be of the form:
 *
 *    WinNT://[parent/]object[$][,type]
 */

	// An index into the string.
	int nIndex;

	// A buffer for processing the string.
	TCHAR tcBuffer;

	// The moniker that we are expecting.
	CString sWinntMoniker = L"WinNT://";


	// Check the moniker.
	if( sADsPath.Left( sWinntMoniker.GetLength() ) != sWinntMoniker )
	{
		wcerr << L"BorfStripWinntPath: Unepected moniker." << endl;
		return ERROR_BAD_ARGUMENTS;
	}

	// Parse from the end of the string.
	nIndex = sADsPath.GetLength() -1;

	// Strip the suffix.
	while( nIndex >= 0 && sADsPath.GetAt( nIndex ) != '/' )
	{
		// Get the next character.
		tcBuffer = sADsPath.GetAt( nIndex );

		if( tcBuffer == ',' || tcBuffer == '$' )
		{
			// Strip the type or down-level suffix.
			sADsPath.Delete( nIndex, sADsPath.GetLength() - nIndex );
		}

		// Decrement the index.
		nIndex -= 1;
	}

	// Strip the prefix.
	sADsPath.Delete( 0, nIndex +1 );

   	return S_OK;

} // BorfStripWinntPath



HRESULT BorfStripWinntPathList( CStringList& sADsPathList )
{

/**
 *  Calls BorfStripWinntPath against all elements of the list.
 *
 *    sADsPathList [in,out]  Strips all path names in the list.
 *
 *  Elements in the sADsPathList argument must be of the form:
 *
 *    WinNT://[parent/]object[$][,type]
 */

	// A position that is used to iterate through the list.
	POSITION borfPosition;

	// Required because COblist lacks GetNextPosition method
	POSITION borfPositionLast;

	// Holds the result of borfStripWinntPath.
	HRESULT hResult;

	// This is the result that we will return.
	HRESULT hError = S_OK;

	// An intermediary buffer.
	CString sBuffer;


	// Get a pointer to the head of the list.
	borfPosition = sADsPathList.GetHeadPosition();

	while( borfPosition )
	{
		// Save the position of the working element.
		borfPositionLast = borfPosition;

		// Get the the working list element and iterate the position.
		sBuffer = sADsPathList.GetNext( borfPosition );

		// Strip this element.
		hResult = BorfStripWinntPath( sBuffer );

		if( FAILED( hResult ) )
		{
			// Save this error result.
			hError = hResult;
		}

		else
		{
			// Put the stripped element back into the list.
			sADsPathList.SetAt( borfPositionLast, (LPCTSTR)sBuffer );

			// DEBUG:
			// wcout << L"BorfStripWinntPathList: " << (LPCTSTR)borfBuffer << endl;
		}

	} // while

	// Return the last result.
	return hError;

} // BorfStripWinntPathList

// eof
