//
// $Id: ActiveDirectoryProbe.cpp,v 1.11 2005/10/11 19:14:19 bakerj Exp $
//
//****************************************************************************************//
// Copyright (c) 2005, The MITRE Corporation
// All rights reserved.
//
// Redistribution and use in source and binary forms, with or without modification, are
// permitted provided that the following conditions are met:
//
//     * Redistributions of source code must retain the above copyright notice, this list
//       of conditions and the following disclaimer.
//     * Redistributions in binary form must reproduce the above copyright notice, this 
//       list of conditions and the following disclaimer in the documentation and/or other
//       materials provided with the distribution.
//     * Neither the name of The MITRE Corporation nor the names of its contributors may be
//       used to endorse or promote products derived from this software without specific 
//       prior written permission.
//
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY 
// EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
// OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT 
// SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT
// OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
// HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR
// TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
// EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
//
//****************************************************************************************//

#include "ActiveDirectoryProbe.h"

string ActiveDirectoryProbe::ldap_user = "";
string ActiveDirectoryProbe::ldap_password = "";

//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~//
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~  Class ActiveDirectoryProbe  ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~//
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~//
ActiveDirectoryProbe *ActiveDirectoryProbe::instance = NULL;

ActiveDirectoryProbe::ActiveDirectoryProbe()
{
	// Note this is a private constructor
}

ActiveDirectoryProbe::~ActiveDirectoryProbe()
{
	// Do nothing for now
}

//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~//
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~  Public Members  ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~//
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~//
Probe* ActiveDirectoryProbe::Instance()
{

	// -----------------------------------------------------------------------
	//
	//  ABSTRACT
	//
	//	Ensure that the ActiveDirectoryProbe is a singleton.
	// -----------------------------------------------------------------------

	// Use lazy initialization
	if(instance == NULL) 
		instance = new ActiveDirectoryProbe();

	return instance;	
}

pdVector ActiveDirectoryProbe::Run(ProbeData* probeDataIn)
{
	//------------------------------------------------------------------------------------//
	//
	//  ABSTRACT
	//
	//  Run the active directory probe. Return a vector of ActiveDirectoryData objects.
	//
	//------------------------------------------------------------------------------------//

	// Gather info from the user.

	GatherAuthentication();

	// Cast the input probe data object as an active directory probe data object

	ActiveDirectoryData* adProbeData = (ActiveDirectoryData*)probeDataIn;
	ActiveDirectoryData* tmpProbeData = NULL;
	pdVector returnVector;

	// If the naming_context, relative_dn, and attribute elements are of a litteral type,
	// we do not need to do any of the pattern matching stuff.

	try
	{
		if((adProbeData->naming_context)->type == literal &&
		   (adProbeData->relative_dn)->type == literal &&
		   (adProbeData->attribute)->type == literal)
		{
			tmpProbeData = GetActiveDirectory(adProbeData->naming_context->object,
				                              adProbeData->relative_dn->object,
											  adProbeData->attribute->object); 
			returnVector.push_back(tmpProbeData);
		}
		else
		{
			// At least one of the elements have a pattern matching type.  Each pattern match must
			// be evaluated and for each possible active directory object that satisfies the pattern
			// match, the associated data must be retreived.

			sVector naming_contexts;
			sVector relative_dns;
			sVector attributes;

			// Reset the matcher and the naming_context vector.

			myMatcher->Reset();
			naming_contexts.clear();

			// Get matching naming context.  If the naming context is a pattern match then
			// find all possible matches. Otherwise, just use the literal value given.

			if(adProbeData->naming_context->type == pattern_match)
			{
				GetMatchingNamingContext(adProbeData->naming_context->object, &naming_contexts);
			}
			else
			{
				naming_contexts.push_back(adProbeData->naming_context->object);
			}

			// Loop through each matching naming context.  We need to look inside each
			// context for information.

			sVector::iterator ncIterator;
			for (ncIterator=naming_contexts.begin(); ncIterator!=naming_contexts.end(); ncIterator++)
			{
				// Reset the relative_dn vector.

				relative_dns.clear();

				// If the relative distinguished name is a pattern match then find all
				// possible matches. Otherwise, just use the literal value given.

				if(adProbeData->relative_dn->type == pattern_match)
				{
					DnPatternMatch((*ncIterator), adProbeData->relative_dn->object, &relative_dns);

				}
				else
				{
					relative_dns.push_back(adProbeData->relative_dn->object);				
				}

				//	For each matching relative distinguished name found

				sVector::iterator rdIterator;
				for (rdIterator=relative_dns.begin(); rdIterator!=relative_dns.end(); rdIterator++)
				{
					// Reset the attribute vector.

					attributes.clear();

					// If the attribute string is a pattern match then ...  Otherwise just use
					// the literal value given.

					if(adProbeData->attribute->type == pattern_match)
					{
						GetMatchingAttributes((*ncIterator), (*rdIterator), adProbeData->attribute->object, &attributes);					
					}
					else
					{
						attributes.push_back(adProbeData->attribute->object);				
					}

					// For each matching attribute found retrieve the associated information
					// from active directory.

					sVector::iterator atIterator;
					for (atIterator=attributes.begin(); atIterator!=attributes.end(); atIterator++)
					{
						tmpProbeData =  GetActiveDirectory((*ncIterator), (*rdIterator), (*atIterator));

						// Only add if the status is not doesNotExists

						if(tmpProbeData->GetStatus() != doesNotExist)
						{
							returnVector.push_back(tmpProbeData);
						}
						else
						{
							delete tmpProbeData;
						}
					}
				} 
			}

			// Now create an object to indicate the results of the pattern match.
			// Need to pass on that the pattern match matched something or did not
			// match any items

			tmpProbeData = new ActiveDirectoryData();
			tmpProbeData->naming_context->object = adProbeData->naming_context->object;
			tmpProbeData->naming_context->type = adProbeData->naming_context->type;
			tmpProbeData->relative_dn->object = adProbeData->relative_dn->object;
			tmpProbeData->relative_dn->type = adProbeData->relative_dn->type;
			tmpProbeData->attribute->object = adProbeData->attribute->object;
			tmpProbeData->attribute->type = adProbeData->attribute->type;
			tmpProbeData->isPatternMatchObject = true;
			if(returnVector.size() == 0)
			{
				tmpProbeData->SetMessage("The specified pattern did not match any items on the system");
				tmpProbeData->SetStatus(doesNotExist);
			} else
			{
				tmpProbeData->SetMessage("The specified pattern matched at least one item on the system.");
				tmpProbeData->SetStatus(exists);
			}
			returnVector.push_back(tmpProbeData);
			tmpProbeData = NULL;
		}

	}
	catch(Exception ex)
	{
		tmpProbeData = new ActiveDirectoryData();
		tmpProbeData->SetMessage(ex.GetErrorMessage());
		tmpProbeData->SetStatus(error);
		tmpProbeData->naming_context->object = adProbeData->naming_context->object;
		tmpProbeData->naming_context->type = adProbeData->naming_context->type;
		tmpProbeData->relative_dn->object = adProbeData->relative_dn->object;
		tmpProbeData->relative_dn->type = adProbeData->relative_dn->type;
		tmpProbeData->attribute->object = adProbeData->attribute->object;
		tmpProbeData->attribute->type = adProbeData->attribute->type;
		tmpProbeData->isPatternMatchObject = adProbeData->isPatternMatchObject;
		returnVector.push_back(tmpProbeData);
		tmpProbeData = NULL;
	}
	catch(...)
	{
		tmpProbeData = new ActiveDirectoryData();
		tmpProbeData->SetMessage("Error: (ActiveDirectoryProbe) Unknown exception occured while running the active directory probe");
		tmpProbeData->SetStatus(error);
		tmpProbeData->naming_context->object = adProbeData->naming_context->object;
		tmpProbeData->naming_context->type = adProbeData->naming_context->type;
		tmpProbeData->relative_dn->object = adProbeData->relative_dn->object;
		tmpProbeData->relative_dn->type = adProbeData->relative_dn->type;
		tmpProbeData->attribute->object = adProbeData->attribute->object;
		tmpProbeData->attribute->type = adProbeData->attribute->type;
		tmpProbeData->isPatternMatchObject = adProbeData->isPatternMatchObject;
		returnVector.push_back(tmpProbeData);
		tmpProbeData = NULL;
	}

	return returnVector;
}

//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~//
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~  Private Members  ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~//
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~//

ActiveDirectoryData* ActiveDirectoryProbe::GetActiveDirectory(string namingContextIn,
															  string relativeDnIn,
															  string attributeIn)
{
	//------------------------------------------------------------------------------------//
	//
	//  ABSTRACT
	//
	//  Gather new data and put in a ActiveDirectoryData object.
	//
	//------------------------------------------------------------------------------------//

	LDAP* ld = NULL;

	// Initialize the output structure, an ActiveDirectoryData object.

	ActiveDirectoryData* resultData = new ActiveDirectoryData();
	resultData->naming_context->object = namingContextIn;
	resultData->relative_dn->object = relativeDnIn;
	resultData->attribute->object = attributeIn;

	//	We don't currently collect object_class data

	resultData->object_class->status = notCollected;
	resultData->SetMessage("Notice: object_class data is not currently collected.");
	resultData->adstype->status = notCollected;
	resultData->SetMessage(" Notice: adstype data is not currently collected.");

	// Build the full distinguished name.

	string dn = BuildDistinguishedName(namingContextIn, relativeDnIn);

	try
	{
		// Initializes a session with the local LDAP server.  Use the default port.

		ld = ldap_init(NULL, LDAP_PORT);
		if (ld == NULL)
		{
			string errorMessage = "";
			errorMessage.append("(ActiveDirectoryProbe) Error initializing session - '");
			errorMessage.append(ldap_err2string(LdapGetLastError()));
			errorMessage.append("'");
			throw ActiveDirectoryProbeException(errorMessage, ERROR_FATAL);
		}

		////////////////////////////////////////////////////////////////////////////////////
		//
		// If we ever need to set the version
		//
		// int version = LDAP_VERSION3;
		// if (ldap_set_option(ld,
		//     LDAP_OPT_PROTOCOL_VERSION,
		//     &version) != LDAP_SUCCESS) cout << "could not set version 3" << endl;
		//
		////////////////////////////////////////////////////////////////////////////////////

		// Synchronously authenticates a client to the LDAP server.

		ULONG bind_ret = ldap_simple_bind_s(ld,
			                                (char*)ldap_user.c_str(),
						                    (char*)ldap_password.c_str());
		if (bind_ret != LDAP_SUCCESS)
		{
			string errorMessage = "";
			errorMessage.append("(ActiveDirectoryProbe) Error binding to the specified LDAP server - '");
			errorMessage.append(ldap_err2string(bind_ret));
			errorMessage.append("'");
			throw ActiveDirectoryProbeException(errorMessage, ERROR_FATAL);
		}

		// Set the search variables.
		LDAPMessage* entriesMsg = NULL;

		ULONG search_ret = ldap_search_s(ld,						// session handle
								     	 (char*)dn.c_str(),			// base
									     LDAP_SCOPE_BASE,			// scope
								    	 NULL,						// search filter
									     NULL,						// attributes to return
								    	 0,							// attrsonly
									     &entriesMsg);				// results of the search

		if (search_ret != LDAP_SUCCESS)
		{
			// TODO:
			// Be aware that entriesMsg can contain valid data, even if the
			// call to ldap_search_s returned an error code.  
			// This can be caused by the server returning codes,
			// such as LDAP_RESULTS_TOO_LARGE or other codes,
			// that indicate that the search returned partial
			// results. The user code can handle these cases
			// if required, this example just frees pMsg on any 
			// error code.
			
			if (search_ret == LDAP_RESULTS_TOO_LARGE)
			{
				cout << "error" << endl;
			}

			string errorMessage = "";
			errorMessage.append("(ActiveDirectoryProbe) Error searching for results - '");
			errorMessage.append(ldap_err2string(search_ret));
			errorMessage.append("'");

			resultData->SetMessage(resultData->GetMessage() + errorMessage);

			if(strnicmp(ldap_err2string(search_ret), "No Such Object", 14) == 0) resultData->SetStatus(doesNotExist);
			else resultData->SetStatus(error);
		}
		else
		{
			// Process the search results and obtain a list of attributes.  For each entry that
			// matched the filter in the ldap_search_s() call above, we want to collect attribute
			// information to return via the result structure.

			LDAPMessage* attributesMsg = NULL;

			// ??BUG??: it looks like we are just looping over the set of attributes and resetting 
			// this value each time. This means that only that last attribute is kept.  How should
			// attributes be handled?
			//
			// ARB - 8/29/05 - I think you can only have one entry here as attributes have to be
			// unique for each DN.  It was just implemented as a for loop for some reason.

			for (attributesMsg = ldap_first_entry(ld, entriesMsg);
				 attributesMsg != NULL;
				 attributesMsg = ldap_next_entry(ld, attributesMsg))
			{
				char** value = NULL;

				// Get the value of the specified attribute.

				value = ldap_get_values(ld, attributesMsg, (char*)attributeIn.c_str());
				if (value == NULL)
				{
					char* szStr = "";
					ldap_get_option(ld, LDAP_OPT_ERROR_STRING, (void*)&szStr);

					string errorMessage = "";
					errorMessage.append("(ActiveDirectoryProbe) Error getting value - '");
					errorMessage.append(szStr);
					errorMessage.append("'");

					resultData->SetMessage(resultData->GetMessage() + errorMessage);

					if(strnicmp(szStr, "No Such Attribute", 17) == 0) resultData->SetStatus(doesNotExist);
					else resultData->SetStatus(error);
				}
				else
				{
					// The attribute exists, set the value of the exists flag to true.

					resultData->value->value = value[0];
					resultData->value->dataType = stringType;

					// Free the structure returned by ldap_get_values.

					ldap_value_free(value);
				}
			}

			// Free the search results and connection resource.

			if (entriesMsg != NULL) ldap_msgfree(entriesMsg);
		}

		ldap_unbind(ld);

	}
	catch(ActiveDirectoryProbeException ex)
	{
		resultData->SetMessage(resultData->GetMessage() + " Message: " + ex.GetErrorMessage());
		resultData->SetStatus(error);
	}
	catch(...)
	{	
		string errMsg = "";
		errMsg.append("(ActiveDirectoryProbe) Unknown error attempting to get active ");
		errMsg.append("directory information for the attribute '");
		errMsg.append(attributeIn);
		errMsg.append("' under the relative distinguished name '");
		errMsg.append(relativeDnIn);
		errMsg.append("'\n");

		resultData->SetMessage(resultData->GetMessage() + " Error Message: " + errMsg);
		resultData->SetStatus(error);
	}

	return resultData;
}

string ActiveDirectoryProbe::BuildDistinguishedName(string namingContextIn, string relativeDnIn)
{
	//------------------------------------------------------------------------------------//
	//
	//  ABSTRACT
	//
	//	Build the base of the distinguished name string depending on the naming context.
	//  For example, the configuration naming contex there at MITRE has a base dn of
	//
	//       ...,cn=configuration,dc=mitre,dc=org
	//
	//  Every base dn ends with the domain component so we always append the results of
	//  GetDomainComponents() to the return string 
	//
	//------------------------------------------------------------------------------------//

	string retDnBase = relativeDnIn;

	if (namingContextIn.compare("configuration") == 0)
	{
		retDnBase.append(",CN=Configuration,");
	}
	else if (namingContextIn.compare("domain") == 0)
	{
		// add nothing extra before the domain components
	}
	else if (namingContextIn.compare("schema") == 0)
	{
		retDnBase.append(",CN=Schema,CN=Configuration,");
	}
	else
	{
		string errorMessage = "";
		errorMessage.append("(ActiveDirectoryProbe) Unknown naming context used - '");
		errorMessage.append(namingContextIn);
		errorMessage.append("'");
		throw ActiveDirectoryProbeException(errorMessage);
	}

	retDnBase.append(GetDomainComponents());

	return retDnBase;
}

void ActiveDirectoryProbe::DnPatternMatch(string namingContextIn,
										  string pattern,
										  sVector* relativeDnsIn)
{
	//------------------------------------------------------------------------------------//
	//
	//  ABSTRACT
	//
	//  Given a naming context and a pattern, find all matching distinguished names in the
	//  active directory.  Populate the relativeDnsIn vector with these names.
	//
	//------------------------------------------------------------------------------------//

	string constPortion = "";
	string tmpPattern = pattern;

	// Find the last non-escaped regular expression char. (excluding the trailing dollar
	// sign)  We will use this constant portion to speed up the search.

	int regexChar = myMatcher->FindLastRegexChar(tmpPattern);

	// If a '$' the last regex char and the last char in the string, then remove it and
	// find the next to last regex character.

	int dollar = tmpPattern.find("$");
	if (dollar == tmpPattern.length() - 1)
	{
		tmpPattern = tmpPattern.substr(0, pattern.length() - 1);
		regexChar = myMatcher->FindLastRegexChar(tmpPattern);
	}

	// A regular expression character was found in the pattern.  Get the constant portion
	// of this pattern and remove double '\'s.

	if(regexChar >= 0)
	{
		constPortion = tmpPattern.substr(regexChar + 1, tmpPattern.length() - regexChar);
		constPortion = myMatcher->RemoveExtraSlashes(constPortion);

		// Only concerned with entire names that are constant.  Remember that in Active
		// Directory, the distinguished name is broken up by a commas.  So, unless the string
		// starts with 'cn=', 'ou=' or 'dc=', find the first comma in the constant portion
		// and look at everything after that.

		if ((constPortion.compare(0, 3, "cn=") != 0) &&
			(constPortion.compare(0, 3, "ou=") != 0) &&
			(constPortion.compare(0, 3, "dc=") != 0))
		{
			int firstComma = constPortion.find_first_of(",");
			constPortion = constPortion.substr(firstComma + 1, constPortion.length() - firstComma);
		}

		// The constant portion has been spererated.  Feed this along with the naming context
		// and the origianal pattern to the GetMatchingDistinguishedNames() function.

		try
		{
			myMatcher->Reset();
			GetMatchingDistinguishedNames(namingContextIn, constPortion, pattern, relativeDnsIn);

		}
		catch(REGEXException ex)
		{
			if(ex.GetSeverity() == ERROR_WARN)
			{
				string pcreMsg = "";
				pcreMsg.append("Active Directory Probe Warning - while searching for matching dn's:\n");
				pcreMsg.append("-----------------------------------------------------------------------\n");
				pcreMsg.append(ex.GetErrorMessage());
				if(Log::verboseMode)
				{
					cout << pcreMsg << "\n"<< endl;
					Log::WriteLog(pcreMsg + "\n\n");
				}
			}
			else
			{
				throw;
			}
		}
		catch(...)
		{
			throw;
		}
	}
	else if (regexChar < 0)
	{
		// There are no non-escaped regular expression characters so treat this as a normal
		// path after removing the escaping '\' characters

		relativeDnsIn->push_back(myMatcher->RemoveExtraSlashes(tmpPattern));
	}
}

void ActiveDirectoryProbe::GatherAuthentication()
{
	//------------------------------------------------------------------------------------//
	//
	//  ABSTRACT
	//
	//
	//
	//------------------------------------------------------------------------------------//

	if(ldap_user.empty() == true)
	{
		char tmp_str[256];

		cout << "\n";
		cout << "Please enter the distinguished name of the user to bind as.\n";
		cin.getline (tmp_str,256);
		ldap_user = tmp_str;
	//ldap_user = "cn=andrew buttner,cn=users,dc=exchange,dc=local"

		cout << "\n";
		cout << "Please enter the password for this user.\n";
		cin.getline (tmp_str,256);
		ldap_password = tmp_str;
	//ldap_password = "Ov@lrules";

		cout << "\n";
	}
}

string ActiveDirectoryProbe::GetDomainComponents()
{
	//------------------------------------------------------------------------------------//
	//
	//  ABSTRACT
	//
	//  Retrieve the domain components part of the active directory.  This represents the
	//  top of the Active Directory LDAP tree.
	//
	//  NOTE:  I assume that the dc is always related to the domain name.  For exmaple, if
	//  your domain is 'mitre.org' then the domain components would be 'dc=mitre,dc=org'.
	//
	//------------------------------------------------------------------------------------//

	DOMAIN_CONTROLLER_INFO* DomainControllerInfo = NULL;
	DWORD dReturn = 0L;
	ULONG dcFlags;

	dcFlags = DS_WRITABLE_REQUIRED | DS_DIRECTORY_SERVICE_REQUIRED | DS_RETURN_DNS_NAME;

	dReturn = DsGetDcName(NULL,						// ComputerName
                          NULL,						// DomainName
                          NULL,						// DomainGuid
                          NULL,						// SiteName
                          dcFlags,					// Flags
                          &DomainControllerInfo );	// DomainControllerInfo buffer

	LPTSTR domain = DomainControllerInfo->DomainName;

	// Turn the domain into the domain components part of the distinguished name.  The dc
	// string is formed by breaking up the domain name at each period. 

	string retDomain = "DC=";
	retDomain.append(domain);

	int pos = retDomain.find(".");
	while ( pos != string::npos )
	{
		retDomain.replace(pos, 1, ",DC=");
		pos = retDomain.find(".", pos);
	}

	// Free the DomainControllerInfo buffer.

	NetApiBufferFree(DomainControllerInfo);

	return retDomain;
}

void ActiveDirectoryProbe::GetMatchingAttributes(string namingContext, string relativeDN, string attribute, sVector *attributes)
{
	//------------------------------------------------------------------------------------//
	//
	//  ABSTRACT
	//
	//  
	//
	//------------------------------------------------------------------------------------//

	string errorMessage = "";
	errorMessage.append("(ActiveDirectoryProbe) Pattern matching on attribute");
	errorMessage.append("is not supported yet.");
	throw ActiveDirectoryProbeException(errorMessage);
}

void ActiveDirectoryProbe::GetMatchingDistinguishedNames(string namingContextIn,
														 string constPatternIn,
														 string patternIn,
														 sVector* relativeDnsIn)
{
	//------------------------------------------------------------------------------------//
	//
	//  ABSTRACT
	//
	//  
	//
	//------------------------------------------------------------------------------------//

	LDAP* ld = NULL;

	//

	string baseString = BuildDistinguishedName(namingContextIn, constPatternIn);


	// Initializes a session with the local LDAP server.  Use the default port.  Note that
	// this just creates a pointer to a new LDAP structure, so this will succeed even if
	// the server doesn't exist.

	ld = ldap_init(NULL, LDAP_PORT);
	if (ld == NULL)
	{
		string errorMessage = "";
		errorMessage.append("(ActiveDirectoryProbe) Error initializing session - '");
		errorMessage.append(ldap_err2string(LdapGetLastError()));
		errorMessage.append("'");
		throw ActiveDirectoryProbeException(errorMessage);
	}

	// Synchronously authenticates a client to the LDAP server.

	ULONG bind_ret = ldap_simple_bind_s(ld,
		                                (char*)ldap_user.c_str(),
						                (char*)ldap_password.c_str());
	if(bind_ret != LDAP_SUCCESS)
	{
		string errorMessage = "";
		errorMessage.append("(ActiveDirectoryProbe) Error binding to the LDAP server - '");
		errorMessage.append(ldap_err2string(bind_ret));
		errorMessage.append("'");
		throw ActiveDirectoryProbeException(errorMessage, ERROR_FATAL);
	}

	// Set the search variables.

	char* attrs[] = { "distinguishedName", NULL };
	LDAPMessage* entriesMsg = NULL;

	ULONG search_ret = ldap_search_s(ld,							// session handle
								     (char*)baseString.c_str(),		// base
							      	 LDAP_SCOPE_SUBTREE,			// scope
								     NULL,							// search filter
								     attrs,							// attributes to return
								     0,								// attrsonly
								     &entriesMsg);					// results of the search
	if (search_ret != LDAP_SUCCESS)
	{
		// TODO:
		// Be aware that entriesMsg can contain valid data, even if the
		// call to ldap_search_s returned an error code.  
		// This can be caused by the server returning codes,
		// such as LDAP_RESULTS_TOO_LARGE or other codes,
		// that indicate that the search returned partial
		// results. The user code can handle these cases
		// if required, this example just frees pMsg on any 
		// error code.
		
		if (search_ret == LDAP_RESULTS_TOO_LARGE)
		{

		}

		string errorMessage = "";
		errorMessage.append("(ActiveDirectoryProbe) Error searching for results - '");
		errorMessage.append(ldap_err2string(search_ret));
		errorMessage.append("' - with DN = '");
		errorMessage.append(baseString);
		errorMessage.append("'");
		throw ActiveDirectoryProbeException(errorMessage);
	}

	// Process the search results and obtain a list of distinguished names.  For each entry that
	// matches the pattern, add it to the vector.

	LDAPMessage* attributesMsg = ldap_first_entry(ld, entriesMsg);
	while (attributesMsg != NULL)
	{
		string tmpDN = ldap_get_dn(ld, attributesMsg);
		string tmpRelDn = RemoveDnBase(namingContextIn, tmpDN);

		if(myMatcher->IsMatch(patternIn.c_str(), tmpRelDn.c_str()))
		{
			relativeDnsIn->push_back(tmpRelDn);
		}

		attributesMsg = ldap_next_entry(ld, attributesMsg);

	}

	// Free the search results and connection resource

	if (entriesMsg != NULL) ldap_msgfree(entriesMsg);
	ldap_unbind(ld);
}


void ActiveDirectoryProbe::GetMatchingNamingContext(string namingContextIn, sVector *namingContexts) {
	//------------------------------------------------------------------------------------//
	//
	//  ABSTRACT
	//
	//  
	//
	//------------------------------------------------------------------------------------//

	string errorMessage = "";
	errorMessage.append("(ActiveDirectoryProbe) Pattern matching on naming_context");
	errorMessage.append("is not supported yet.");
	throw ActiveDirectoryProbeException(errorMessage, ERROR_FATAL);
}

string ActiveDirectoryProbe::RemoveDnBase(string namingContextIn, string dnIn)
{
	//------------------------------------------------------------------------------------//
	//
	//  ABSTRACT
	//
	//	Remove the base of the distinguished name depending on the naming context.
	//
	//  NOTE: Are the dn names in active directory case insensitive?  In other words, if
	//  the dn passed into this function looks like ...,cn=configuration,dc=mitre,dc=org
	//  then the std::string find function will return -1 since it is looking for
	//  ...,CN=Configuration,DC=....
	//
	//  I did add a kludge for now checking the lower case version just incase, but this
	//  should be permenantly added or removed once an answer is known for sure.
	//
	//------------------------------------------------------------------------------------//

	string returnDnBase = "";
	int pos = 0;
	
	if (namingContextIn.compare("configuration") == 0)
	{
		pos = dnIn.find("CN=Configuration");
		if (pos == -1) pos = dnIn.find("cn=configuration");
	}
	else if (namingContextIn.compare("domain") == 0)
	{
		pos = dnIn.find("DC=");
		if (pos == -1) pos = dnIn.find("dc=");
	}
	else if (namingContextIn.compare("schema") == 0)
	{
		pos = dnIn.find("CN=Schema");
		if (pos == -1) pos = dnIn.find("cn=schema");
	}
	else
	{
		string errorMessage = "";
		errorMessage.append("(ActiveDirectoryProbe) Can not remove unknown naming context - '");
		errorMessage.append(namingContextIn);
		errorMessage.append("'");
		throw ActiveDirectoryProbeException(errorMessage);
	}

	returnDnBase = dnIn.substr(0, pos-1);

	return returnDnBase;
}

//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~//
//~~~~~~~~~~~~~~~~~~~~~~~~  Class ActiveDirectoryProbeException  ~~~~~~~~~~~~~~~~~~~~~~~~~//
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~//

ActiveDirectoryProbeException::ActiveDirectoryProbeException(string errMsgIn, int severity) : Exception(errMsgIn, severity)
{
}

ActiveDirectoryProbeException::~ActiveDirectoryProbeException()
{
}
