//
// $Id:
//
//************************** Property of the MITRE Corporation ***************************//
//
// Copyright (c) 2003 - The MITRE Corporation
//
// This file is part of the OVAL Definition Interpreter.
//
// The OVAL Definition Interpreter is free software; you can redistribute it and/or modify it
// under the terms of the GNU General Public License as published by the Free Software
// Foundation; either version 2 of the License.
//
// The OVAL Definition Interpreter is distributed in the hope that it will be useful, but
// WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
// FOR A PARTICULAR PURPOSE.  See the GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License along with the OVAL
// Definition Interpreter; if not, write to the Free Software Foundation, Inc., 59 Temple
// Place, Suite 330, Boston, MA 02111-1307 USA
//
//****************************************************************************************//

#include "ActiveDirectoryProbe.h"

//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~//
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~  Class ActiveDirectoryProbe  ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~//
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~//

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

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

//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~//
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~  Public Members  ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~//
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~//

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

	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.

	if((adProbeData->naming_context)->type == LITTERAL_TYPE &&
	   (adProbeData->relative_dn)->type == LITTERAL_TYPE &&
	   (adProbeData->attribute)->type == LITTERAL_TYPE)
	{
		try
		{
			tmpProbeData = GetActiveDirectory(adProbeData->naming_context->data,
				                              adProbeData->relative_dn->data,
											  adProbeData->attribute->data); 
			returnVector.push_back(tmpProbeData);
		}
		catch(...)
		{
			string errorMessage = "";
			errorMessage.append("(ActiveDirectoryProbe) Unknown exception occured while ");
			errorMessage.append("running active directory probe with literals.");

			tmpProbeData = new ActiveDirectoryData();
			tmpProbeData->msg = errorMessage;
			returnVector.push_back(tmpProbeData);
			tmpProbeData = NULL;
		}
	}

	// 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.

	else
	{
		sVector naming_contexts;
		sVector relative_dns;
		sVector attributes;

		try
		{
			// 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_TYPE)
			{
				// TODO
				// GetMatchingNamingContext(adProbeData->naming_context->data, &naming_contexts);

				string errorMessage = "";
				errorMessage.append("(ActiveDirectoryProbe) Pattern matching on naming_context");
				errorMessage.append("is not supported yet.");
				throw ActiveDirectoryProbeException(errorMessage);
			}
			else
			{
				naming_contexts.push_back(adProbeData->naming_context->data);
			}

			// 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_TYPE)
				{
					DnPatternMatch((*ncIterator), adProbeData->relative_dn->data, &relative_dns);
				}
				else
				{
					relative_dns.push_back(adProbeData->relative_dn->data);				
				}

				// If no matching relative distinguished names are found add a
				// ActiveDirectoryData with the naming context set and a message indicating
				// that no relative dn matched.

				if(relative_dns.size() == 0)
				{
					tmpProbeData = new ActiveDirectoryData();
					tmpProbeData->naming_context->data = (*ncIterator);
					tmpProbeData->msg = "(ActiveDirectoryProbe) The relative distinguished name '" + adProbeData->relative_dn->data + "' does not exist.";
					returnVector.push_back(tmpProbeData);
					tmpProbeData = NULL;
				}

				//	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_TYPE)
					{
						// TODO
						// GetMatchingAttributes((*ncIterator), (*rdIterator), adProbeData->attribute->data, &attributes);

						string errorMessage = "";
						errorMessage.append("(ActiveDirectoryProbe) Pattern matching on attribute");
						errorMessage.append("is not supported yet.");
						throw ActiveDirectoryProbeException(errorMessage);
					}
					else
					{
						attributes.push_back(adProbeData->attribute->data);				
					}

					// If no matching attributes are found add a ActiveDirectoryData with the 
					// naming_context and relative_dn set and a message indicating that no
					// attributes matched.

					if(attributes.size() == 0)
					{
						tmpProbeData = new ActiveDirectoryData();
						tmpProbeData->naming_context->data = (*ncIterator);
						tmpProbeData->relative_dn->data = (*rdIterator);
						tmpProbeData->msg = "(ActiveDirectoryProbe) The attribute '" + adProbeData->attribute->data + "' does not exist under the specified distinguished name.";
						returnVector.push_back(tmpProbeData);
						tmpProbeData = NULL;
					}

					// 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));
						returnVector.push_back(tmpProbeData);
					}
				}
			}
		}

		// Might be an error with hive key or name... The exception caught should specify
		// the error

		catch(Exception ex)
		{
			tmpProbeData = new ActiveDirectoryData();
			tmpProbeData->msg = ex.GetErrorMessage();
			returnVector.push_back(tmpProbeData);
			tmpProbeData = NULL;
		}

		// An unknown error occured.

		catch(...)
		{
			tmpProbeData = new ActiveDirectoryData();
			tmpProbeData->msg = "Error: (ActiveDirectoryProbe) Unknown exception occured while running the active directory probe";
			returnVector.push_back(tmpProbeData);
			tmpProbeData = NULL;
		}
	}

	// Before returning ensure that all tests have the testId set.

	pdVector::iterator pdIterator;
	for (pdIterator=returnVector.begin(); pdIterator!=returnVector.end(); pdIterator++)
	{
		(*pdIterator)->SetTestId(adProbeData->GetTestId());
	}

	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();

	// 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);
		}

		////////////////////////////////////////////////////////////////////////////////////
		//
		// 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.

		if (ldap_bind_s(ld,
			            NULL,
						NULL,
						LDAP_AUTH_NTLM) != LDAP_SUCCESS)
		{
			string errorMessage = "";
			errorMessage.append("(ActiveDirectoryProbe) Error binding to the LDAP server - '");
			errorMessage.append(ldap_err2string(LdapGetLastError()));
			errorMessage.append("'");
			throw ActiveDirectoryProbeException(errorMessage);
		}

		// Set the search variables.

		LDAPMessage* entriesMsg = NULL;

		DWORD dwErr = 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 (dwErr != 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 (dwErr == LDAP_RESULTS_TOO_LARGE)
			{

			}

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

		// Process the search results and obtain a list of attributes.  For each entry that
		// mathced the filter in the ldap_search_s() call above, we want to collect attribute
		// information to return via the result structure.

		LDAPMessage* attributesMsg = NULL;

		for (attributesMsg = ldap_first_entry(ld, entriesMsg);
		     attributesMsg != NULL;
			 attributesMsg = ldap_next_entry(ld, attributesMsg))
		{
			// Get the value of the specified attribute.
	
			char** value = NULL;

			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 values - '");
				errorMessage.append(szStr);
				errorMessage.append("'");
				throw ActiveDirectoryProbeException(errorMessage);
			}

			// The attribute exists, set the value of the exists flag to true.

			resultData->exists = true;
			resultData->naming_context->data = namingContextIn;
			resultData->relative_dn->data = relativeDnIn;
			resultData->attribute->data = attributeIn;
			resultData->value = value[0];

			// 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->msg.append("Message: " + ex.GetErrorMessage());
	}
	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->msg.append("Error Message: " + errMsg);
	}

	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;
			}
		}		
	}

	// There are no non-escaped regular expression characters so treat this as a normal
	// path after removing the escaping '\' characters

	else if (regexChar < 0)
	{
		relativeDnsIn->push_back(myMatcher->RemoveExtraSlashes(tmpPattern));
	}
}

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::GetMatchingDistinguishedNames(string namingContextIn,
														 string constPatternIn,
														 string patternIn,
														 sVector* relativeDnsIn)
{
	//------------------------------------------------------------------------------------//
	//
	//  ABSTRACT
	//
	//  
	//
	//------------------------------------------------------------------------------------//


	LDAP* ld = NULL;

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

	ld = ldap_init("127.0.0.1", 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.

	if (ldap_bind_s(ld,
			        NULL,
					NULL,
					LDAP_AUTH_NTLM) != LDAP_SUCCESS)
	{
		string errorMessage = "";
		errorMessage.append("(ActiveDirectoryProbe) Error binding to the LDAP server - '");
		errorMessage.append(ldap_err2string(LdapGetLastError()));
		errorMessage.append("'");
		throw ActiveDirectoryProbeException(errorMessage);
	}

	// Set the search variables.

	string baseString = BuildDistinguishedName(namingContextIn, constPatternIn);
	char* attrs[] = { "distinguishedName", NULL };
	LDAPMessage* entriesMsg = NULL;

	DWORD dwErr = 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 (dwErr != 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 (dwErr == LDAP_RESULTS_TOO_LARGE)
		{

		}

		string errorMessage = "";
		errorMessage.append("(ActiveDirectoryProbe) Error searching for results - '");
		errorMessage.append(ldap_err2string(dwErr));
		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);
}

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() : Exception()
{
	// Default constructor simply set the severity to ERROR_FATAL. This is done with the
	// explicit call to the Exception class default constructor.
}

ActiveDirectoryProbeException::ActiveDirectoryProbeException(string errMsgIn) : Exception(errMsgIn)
{
	// Set the error message and then set the severity to ERROR_FATAL. This is done with
	// the explicit call to the Exception class constructor that takes a single string
	// param.
}

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