#include	"precomp.h"
#include	"call.h"
#include	"callto.h"
#include	"conf.h"
#include	"confroom.h"
#include	"confpolicies.h"
#include	"confutil.h"
#include	"nmldap.h"


const int	CCallto::s_iMaxCalltoLength		= 255;
const int	CCallto::s_iMaxAddressLength	= 255;

//--------------------------------------------------------------------------//
//	CCallto::CCallto.														//
//--------------------------------------------------------------------------//
CCallto::CCallto(void):
	m_bUnescapedAddressOnly( false ),
	m_ulDestination( INADDR_NONE ),
	m_bGatekeeperEnabled( false ),
	m_pszGatekeeperName( NULL ),
	m_ulGatekeeperAddress( INADDR_NONE ),
	m_bGatewayEnabled( false ),
	m_pszGatewayName( NULL ),
	m_ulGatewayAddress( INADDR_NONE ),
	m_pszDefaultIlsServerName( NULL ),
	m_pszCalltoBuffer( NULL ),
	m_pszDisplayName( NULL )
{
}	//	End of CCallto::CCallto.


//--------------------------------------------------------------------------//
//	CCallto::~CCallto.														//
//--------------------------------------------------------------------------//
CCallto::~CCallto(void)
{
	delete [] m_pszGatekeeperName;
	delete [] m_pszGatewayName;
	delete [] m_pszDefaultIlsServerName;
	delete [] m_pszCalltoBuffer;
	delete [] m_pszDisplayName;

}	//	End of CCallto::~CCallto.


//--------------------------------------------------------------------------//
//	CCallto::Callto.														//
//--------------------------------------------------------------------------//
HRESULT CCallto::Callto
(
	const TCHAR * const	pszCallto,				//	pointer to the callto url to try to place the call with...
	const TCHAR * const	pszDisplayName,			//	pointer to the display name to use...
	const NM_ADDR_TYPE	nmType,					//	callto type to resolve this callto as...
	const bool			bAddressOnly,			//	the pszCallto parameter is to be interpreted as a pre-unescaped addressing component vs a full callto...
	const bool * const	pbSecurityPreference,	//	pointer to security preference, NULL for none. must be "compatible" with secure param if present...
	const bool			bAddToMru,				//	whether or not save in mru...
	const bool			bUIEnabled,				//	whether or not to perform user interaction on errors...
	const HWND			hwndParent,				//	if bUIEnabled is true this is the window to parent error/status windows to...
	INmCall ** const	ppInternalCall			//	out pointer to INmCall * to receive INmCall * generated by placing call...
){
	ASSERT( pszCallto != NULL );
	ASSERT( (hwndParent == NULL) || IsWindow( hwndParent ) );

	HRESULT	hrResult;

	//	These members need to be reset at the beginning of every call...
	m_bUIEnabled			= bUIEnabled;
	m_hwndParent			= hwndParent;
	m_ulDestination			= INADDR_NONE;
	m_bUnescapedAddressOnly	= bAddressOnly;
	m_pszDisplayName		= PszAlloc( pszDisplayName );
	
	m_Parameters.SetParams( NULL );		//	Need to reset all parameter settings too...

	//	Process the callto...
	if( (hrResult = Parse( pszCallto )) == S_OK )
	{
		//	Validate that the security specified (if any) is compatible with current NM state.
		bool	bValidatedSecurity;

		if( (hrResult = GetValidatedSecurity( pbSecurityPreference, bValidatedSecurity )) == S_OK )
		{
			if( (hrResult = Resolve( nmType )) == S_OK )
			{
				hrResult = PlaceCall( pszCallto, bAddToMru, bValidatedSecurity, ppInternalCall );
			}
		}
	}

	if( FAILED( hrResult ) && m_bUIEnabled )
	{
		DisplayCallError( hrResult, (pszDisplayName != NULL)? pszDisplayName: pszCallto );
	}

	delete [] m_pszCalltoBuffer;
	delete [] m_pszDisplayName;
	m_pszCalltoBuffer	= NULL;
	m_pszDisplayName	= NULL;

	return( hrResult );

}	//	End of CCallto::Callto.


//--------------------------------------------------------------------------//
//	CCallto::Parse.															//
//--------------------------------------------------------------------------//
HRESULT CCallto::Parse
(
	const TCHAR * const	pszCallto	//	pointer to the callto url to parse...
){
	ASSERT( pszCallto != NULL );
	ASSERT( m_ulDestination == INADDR_NONE );

	HRESULT	hrResult;

	if( (m_pszCalltoBuffer = PszAlloc( pszCallto )) == NULL )	//	Make a copy that we can modify in place...
	{
		hrResult = E_OUTOFMEMORY;
	}
	else if( TrimSzCallto( m_pszCalltoBuffer ) == 0 )		//	Remove any leading/trailing blanks...
	{
		hrResult = NM_CALLERR_PARAM_ERROR;	//	Entire string was blanks...
	}
	else
	{
		hrResult = S_OK;

		m_pszParsePos = m_pszCalltoBuffer;

		if( !m_bUnescapedAddressOnly )
		{
			//	Skip over "callto:" or "callto://" which mean nothing to us now...
			static const TCHAR	pszCallto[]			= TEXT( "callto:" );
			static const TCHAR	pszDoubleSlash[]	= TEXT( "//" );

			if( StrCmpNI_literal( m_pszCalltoBuffer, pszCallto ) )
			{
				m_pszParsePos = m_pszCalltoBuffer + strlen_literal( pszCallto );

				if( StrCmpNI_literal( m_pszParsePos, pszDoubleSlash ) )
				{
					m_pszParsePos += strlen_literal( pszDoubleSlash );
				}
			}

			int	iLength	= lstrlen( m_pszCalltoBuffer );

			if( m_pszCalltoBuffer[ iLength - 1 ] == '/' )
			{
				m_pszCalltoBuffer[ iLength - 1 ] = '\0';	//	The shell seems to add a trailing slash before calling us...
			}
		}

		//	Break the callto into two pieces at the start of the parameters (if any)...
		m_pszParameters = StrChr( m_pszCalltoBuffer, '+' );

		if( m_pszParameters != NULL )
		{
			if( m_bUnescapedAddressOnly )
			{
				//	Sorry but '+' not allowed in addressing component...
				hrResult = NM_CALLERR_INVALID_ADDRESS;
			}
			else
			{
				*m_pszParameters++ = '\0';
			}
		}

		if( hrResult == S_OK )
		{
			//	Make sure we have something left before going on...
			if( m_pszParsePos[ 0 ] == '\0' )
			{
				hrResult = NM_CALLERR_NO_ADDRESS;
			}
			else
			{
				//	Not really a parsing thing but what we have at this point is conveiniently
				//	exactly what we want to use for a display name if one wasn't specified so
				//	save a copy of it before ParseAddress breaks it up into pieces...
				if( m_pszDisplayName == NULL )
				{
					m_pszDisplayName = PszAlloc( m_pszParsePos );
				}

				if( m_pszParameters != NULL )
				{
					//	Send the parameters off to be parsed...
					hrResult = m_Parameters.SetParams( m_pszParameters );
				}

				if( hrResult == S_OK )
				{
					//	Go parse the addressing component...
					hrResult = ParseAddress();
				}
			}
		}
	}

	return( hrResult );

}	//	End of CCallto::Parse.


//--------------------------------------------------------------------------//
//	CCallto::ParseAddress.													//
//--------------------------------------------------------------------------//
HRESULT CCallto::ParseAddress(void)
{
	ASSERT( m_pszCalltoBuffer != NULL );

	HRESULT	hrResult	= NM_CALLERR_INVALID_ADDRESS;

	if( m_Parameters.GetBooleanParam( TEXT( "h323" ), true ) && inGatekeeperMode() )
	{
		m_pszAddress	= m_pszParsePos;
		hrResult		= S_OK;			//	Bless you gatekeeper...
	}
	else if( StrChr( m_pszParsePos, '=' ) == NULL )		//	We're not going to allow the address component to contain an '='...
	{
		//	The address is allowed to be one of these formats...
		//		(1) string
		//		(2) server/email
		//		(3) server:port/email
		//	This means it may contain at most one ':', at most one '/',
		//	and given a ':', there must be a following '/'...

		TCHAR * const	pszColon	= StrChr( m_pszParsePos, ':' );
		TCHAR * const	pszSlash	= StrChr( m_pszParsePos, '/' );

		if( (pszSlash == NULL) && (pszColon == NULL) )
		{
			//	It's a valid simple (1) string format...
			m_pszAddress	= m_pszParsePos;
			m_pszIlsServer	= NULL;
			m_uiIlsPort		= DEFAULT_LDAP_PORT;
			m_pszEmail		= m_pszParsePos;

			if( m_bUnescapedAddressOnly )
			{
				hrResult = S_OK;	//	Done...
			}
			else
			{
				//	Need to unescape m_pszAddress but not m_pszEmail because it points to the same place...
				hrResult = Unescape( m_pszAddress );
			}
		}
		else if( (pszSlash != NULL) && (pszColon == NULL) )
		{
			if( StrChr( pszSlash + 1, '/' ) == NULL )
			{
				//	No colon and only one slash so it's a valid (2) format
				//	as long as each side is non zero length...
				if( (pszSlash > m_pszParsePos) && (lstrlen( pszSlash ) > 1) )
				{
					m_pszAddress	= TEXT( "" );
					m_pszIlsServer	= m_pszParsePos;
					m_uiIlsPort		= DEFAULT_LDAP_PORT;
					m_pszEmail		= pszSlash + 1;
					*pszSlash		= '\0';

					if( m_bUnescapedAddressOnly )
					{
						hrResult = S_OK;	//	Done...
					}
					else
					{
						if( (hrResult = Unescape( m_pszIlsServer )) == S_OK )
						{
							hrResult = Unescape( m_pszEmail );
						}
					}
				}
			}
		}
		else if( (pszSlash != NULL) && (pszColon != NULL) )
		{
			//	Make sure the ':' preceeds the '/' and there's only one of each...
			if( (pszColon < pszSlash) && (StrChr( pszSlash + 1, '/') == NULL) && (StrChr( pszColon + 1, ':' ) == NULL) )
			{
				//	One colon and one slash in the correct order so it's a valid (3) format as long
				//	as all three pieces are non zero length and the port piece is a number...
				if( (pszColon > m_pszParsePos) && (pszSlash > pszColon + 1) && (lstrlen( pszSlash ) > 1) )
				{
					//	We're not in gatekeeper mode so break it up into server, port, and email...
					m_pszAddress	= TEXT( "" );
					m_pszIlsServer	= m_pszParsePos;
					m_pszEmail		= pszSlash + 1;
					*pszColon		= '\0';
					*pszSlash		= '\0';

					if( m_bUnescapedAddressOnly )
					{
						hrResult = DecimalStringToUINT( pszColon + 1, m_uiIlsPort );
					}
					else
					{
						if( (hrResult = Unescape( m_pszIlsServer )) == S_OK )
						{
							if( (hrResult = Unescape( m_pszEmail )) == S_OK )
							{
								if( (hrResult = Unescape( pszColon + 1 )) == S_OK )
								{
									hrResult = DecimalStringToUINT( pszColon + 1, m_uiIlsPort );
								}
							}
						}
					}
				}
			}
		}
	}

	return( hrResult );

}	//	End of CCallto::ParseAddress.


//--------------------------------------------------------------------------//
//	CCallto::Resolve.														//
//--------------------------------------------------------------------------//
HRESULT CCallto::Resolve
(
	const NM_ADDR_TYPE	nmType		//	callto type to resolve this callto as...
){
	HRESULT	hrResult;

	//	First set our address type...

	if( (nmType != NM_ADDR_UNKNOWN) && (nmType != NM_ADDR_CALLTO) )
	{
		m_nmAddressType = nmType;
	}
	else
	{
		const TCHAR * const	pszType	= m_Parameters.GetParam( TEXT( "type" ), NULL );

		if( lstrcmpi( pszType, TEXT( "phone" ) ) == 0 )
		{
			m_nmAddressType = NM_ADDR_ALIAS_E164;
		}
		else if( lstrcmpi( pszType, TEXT( "ip" ) ) == 0 )
		{
			m_nmAddressType = NM_ADDR_IP;
		}
		else if( lstrcmpi( pszType, TEXT( "host" ) ) == 0 )
		{
			m_nmAddressType = NM_ADDR_MACHINENAME;
		}
		else if( lstrcmpi( pszType, TEXT( "directory" ) ) == 0 )
		{
			m_nmAddressType = NM_ADDR_ULS;
		}
		else
		{
			m_nmAddressType = NM_ADDR_UNKNOWN;
		}
	}

	//	Then see what we should do with it...

	if( m_Parameters.GetBooleanParam( TEXT( "h323" ), true ) && inGatekeeperMode() )
	{
		if (!IsGatekeeperLoggedOn() && !IsGatekeeperLoggingOn())
		{
			hrResult = NM_CALLERR_NOT_REGISTERED;
		}
		else
		{
			//	We can always send anything to the gatekeeper for actual resolution...
			if( (hrResult = GetGatekeeperIpAddress( m_ulDestination )) == S_OK )
			{
				if( m_nmAddressType == NM_ADDR_ALIAS_E164 )		//	should this also check for NM_ADDR_H323_GATEWAY???
				{
					CleanupE164StringEx( m_pszAddress );
				}
				else
				{
					m_nmAddressType = NM_ADDR_ALIAS_ID;
				}
			}
		}
	}
	else
	{
		switch( m_nmAddressType )
		{
			case NM_ADDR_ALIAS_ID:
			{
				hrResult = NM_CALLERR_NO_GATEKEEPER;
			}
			break;

			case NM_ADDR_ALIAS_E164:
			case NM_ADDR_H323_GATEWAY:
			{
				if( inGatewayMode() )
				{
					if( (hrResult = GetGatewayIpAddress( m_ulDestination )) == S_OK )
					{
						//	Explicit phone types are also still resolvable in gateway mode...
						CleanupE164StringEx( m_pszAddress );
					}
				}
				else
				{
					hrResult = NM_CALLERR_NO_PHONE_SUPPORT;
				}
			}
			break;

			case NM_ADDR_IP:
			{
				if( (hrResult = GetIpAddress( m_pszAddress, m_ulDestination )) != S_OK )
				{
					hrResult = NM_CALLERR_INVALID_IPADDRESS;
				}
			}
			break;

			case NM_ADDR_MACHINENAME:
			{
				if( (hrResult = GetIpAddressFromHostName( m_pszAddress, m_ulDestination )) != S_OK )
				{
					hrResult = NM_CALLERR_HOST_RESOLUTION_FAILED;
				}
			}
			break;

			case NM_ADDR_ULS:
			{
				//	Ils types need to be resolved against an ils...
				hrResult = GetIpAddressFromIls(	m_ulDestination );
			}
			break;

			default:
			{
				//	If we get here the type was unspecified (Automatic or 2.xx)...
				//	Our order of precedence is ipaddress,hostname,ils,fail...
				//	We will not try phone since it didn't explicitly have a phone type...

				if( (hrResult = GetIpAddress( m_pszAddress, m_ulDestination )) == S_OK )
				{
					m_nmAddressType = NM_ADDR_IP;
				}
				else
				{
					//	It's not a valid ip address so try it next as a host name...
					if( (hrResult = GetIpAddressFromHostName( m_pszAddress, m_ulDestination )) == S_OK )
					{
						m_nmAddressType = NM_ADDR_MACHINENAME;
					}
					else
					{
						//	It's not a valid host name either so try it finally as an ils lookup...
						if ( (hrResult = GetIpAddressFromIls( m_ulDestination )) == S_OK )
						{
							m_nmAddressType = NM_ADDR_ULS;
						}
					}
				}
			}
		}
	}

	return( hrResult );

}	//	End of CCallto::Resolve.


//--------------------------------------------------------------------------//
//	CCallto::PlaceCall.														//
//--------------------------------------------------------------------------//
HRESULT CCallto::PlaceCall
(
	const TCHAR * const	pszCallto,			//	pointer to the original callto...
	const bool			bAddToMru,			//	whether or not save in mru...
	const bool			bSecure,			//	whether or not to place the call securely...
	INmCall ** const	ppInternalCall		//	out pointer to INmCall * to receive INmCall * generated by placing call...
){
	ASSERT( m_ulDestination != INADDR_NONE );

	const TCHAR * const	pszConferenceName	= m_Parameters.GetParam( TEXT( "conference" ), NULL );
	const TCHAR * const	pszPassword			= m_Parameters.GetParam( TEXT( "password" ), NULL );
	const bool			bH323				= m_Parameters.GetBooleanParam( TEXT( "h323" ), true );
	const bool			bAV					= m_Parameters.GetBooleanParam( TEXT( "av" ), true );
	const bool			bData				= m_Parameters.GetBooleanParam( TEXT( "data" ), true );
	const TCHAR * const	pszAlias			= NULL;
	const TCHAR * const	pszE164				= NULL;
	HRESULT				hrResult;

	if( IsLocalIpAddress( m_ulDestination ) )	
	{
		//	We don't want to go any further if we are attempting to call ourselves...
		hrResult = NM_CALLERR_LOOPBACK;
	}
	else
	{
		//	Map to old style call flags...

		DWORD	dwCallFlags	= 0;

		if( pszConferenceName != NULL )
		{
			dwCallFlags |= CRPCF_JOIN;
		}

		if( bH323 )
		{
			dwCallFlags |= CRPCF_H323CC;
		}
		
		if( bSecure )
		{
			dwCallFlags |= CRPCF_SECURE;
		}
		else if( bAV )
		{
			if( g_uMediaCaps & (CAPFLAG_RECV_AUDIO | CAPFLAG_SEND_AUDIO) )
			{
				dwCallFlags |= CRPCF_AUDIO;
			}

			if( g_uMediaCaps & (CAPFLAG_RECV_VIDEO | CAPFLAG_SEND_VIDEO) )
			{
				dwCallFlags |= CRPCF_VIDEO;
			}
		}

		if( bData )
		{
			dwCallFlags |= CRPCF_DATA | CRPCF_T120;
		}

		if (((CRPCF_T120 | CRPCF_DATA) != (dwCallFlags & (CRPCF_T120 | CRPCF_DATA))) &&
			((CRPCF_H323CC | CRPCF_AUDIO) != ( dwCallFlags & (CRPCF_H323CC | CRPCF_AUDIO))) &&
			((CRPCF_H323CC | CRPCF_VIDEO) != ( dwCallFlags & (CRPCF_H323CC | CRPCF_VIDEO))))
			 
		{
			hrResult = NM_CALLERR_UNKNOWN;
		}

		CConfRoom *	pConfRoom	= ::GetConfRoom();
		ASSERT(pConfRoom);

		if (_Module.IsUIActive())
		{
			pConfRoom->BringToFront();
		}

		if( !(pConfRoom->GetMeetingPermissions() & NM_PERMIT_OUTGOINGCALLS) )
		{
			ERROR_OUT( ("CCallto::PlaceCall: meeting setting permissions do not permit outgoing calls...") );
		}
		else
		{
			CCall *	pCall	= new CCall( pszCallto, m_pszDisplayName, m_nmAddressType, bAddToMru, FALSE );

			if( pCall == NULL )
			{
				ERROR_OUT( ("CCallto::PlaceCall: CCall object not created...") );
				hrResult = E_OUTOFMEMORY;
			}
			else
			{
				pCall->AddRef(); // Protect against another thread canceling this call
				
				IncrementBusyOperations();
				{
					const TCHAR *		pszCallAlias	= (m_nmAddressType == NM_ADDR_ULS)? m_pszEmail: m_pszAddress;
					const char * const	pszDestination	= inet_ntoa( *reinterpret_cast<in_addr *>(&m_ulDestination) );

					hrResult = pCall->PlaceCall(	dwCallFlags,		//	call flags bit mask, is there a good reason why they're not named?
													m_nmAddressType,	//	address type.
													pszDestination,		//	setup address.
													pszDestination,		//	destination address.
													pszCallAlias,		//	alias.
													NULL,				//	callto url.
													pszConferenceName,	//	conference name.
													pszPassword,		//	conference password.
													NULL );				//	user data.
				}
				DecrementBusyOperations();
				
				if( FAILED( hrResult ) && (pCall->GetState() == NM_CALL_INVALID) )
				{
					// just release the call to free the data
					// otherwise wait for the call state to be changed
					pCall->Release();
				}

				if( ppInternalCall )
				{
					*ppInternalCall = pCall->GetINmCall();
					(*ppInternalCall)->AddRef();
				}

				pCall->Release();
			}
		}
	}

	return( hrResult );

}	//	End of CCallto::PlaceCall.


//--------------------------------------------------------------------------//
//	CCallto::GetValidatedSecurity.											//
//--------------------------------------------------------------------------//
HRESULT CCallto::GetValidatedSecurity
(
	const bool * const	pbSecurityPreference,	//	pointer to security preference, NULL for none. must be "compatible" with secure param if present...
	bool &				bValidatedSecurity		//	out bool reference to recieve validated security setting
){
	HRESULT	hrResult;

	//	First figure out what security setting is desired...

	if( pbSecurityPreference != NULL )
	{
		//	A preference was specified so use it.
		bValidatedSecurity = *pbSecurityPreference;
	}
	else
	{
		//	No preference was specified either so check for secure param
		//	passing the current system settings to use as the default.

		bool	bUserAlterable;
		bool	bDefaultSecurity;

		CConfRoom::get_securitySettings( bUserAlterable, bDefaultSecurity );
		
		bValidatedSecurity = m_Parameters.GetBooleanParam( TEXT( "secure" ), bDefaultSecurity );
	}

	//	And then validate that the desired setting is allowed...

    int	iSecurityPolicy	= ConfPolicies::GetSecurityLevel();

	if( (bValidatedSecurity && (iSecurityPolicy == DISABLED_POL_SECURITY)) ||
		((!bValidatedSecurity) && (iSecurityPolicy == REQUIRED_POL_SECURITY)) )
	{
		//	There was a mismatch between what they want and what they can have...
		//	Set security to what they can have and return mismatch error...

		bValidatedSecurity	= (iSecurityPolicy == REQUIRED_POL_SECURITY);
		hrResult			= NM_CALLERR_SECURITY_MISMATCH;
	}
	else
	{
		hrResult = S_OK;
    }

	return( hrResult );

}	//	End of CCallto::GetValidatedSecurity.


//--------------------------------------------------------------------------//
//	CCallto::inGatekeeperMode.												//
//--------------------------------------------------------------------------//
bool CCallto::inGatekeeperMode(void)
{
	return(ConfPolicies::CallingMode_GateKeeper == ConfPolicies::GetCallingMode() );

}	//	End of CCallto::inGatekeeperMode.


//--------------------------------------------------------------------------//
//	CCallto::SetGatekeeperEnabled.											//
//--------------------------------------------------------------------------//
void CCallto::SetGatekeeperEnabled
(
	const bool	bEnabled	//	new Gatekeeper state
){

	m_bGatekeeperEnabled = bEnabled;

}	//	End of CCallto::SetGatekeeperEnabled.


//--------------------------------------------------------------------------//
//	CCallto::SetGatekeeperName.												//
//--------------------------------------------------------------------------//
HRESULT CCallto::SetGatekeeperName
(
	const TCHAR * const	pszGatekeeperName	//	pointer to new Gatekeeper name
){

	if( lstrcmpi( pszGatekeeperName, m_pszGatekeeperName ) != 0 )
	{
		delete [] m_pszGatekeeperName;

		m_pszGatekeeperName		= PszAlloc( pszGatekeeperName );
		m_ulGatekeeperAddress	= INADDR_NONE;	//	We reset this cached value when the name changes...
	}

	ASSERT( (m_pszGatekeeperName != NULL) || (pszGatekeeperName == NULL) );

	return( ((pszGatekeeperName != NULL) && (m_pszGatekeeperName == NULL))? E_OUTOFMEMORY: S_OK );

}	//	End of CCallto::SetGatekeeperName.


//--------------------------------------------------------------------------//
//	CCallto::GetGatekeeperIpAddress.										//
//--------------------------------------------------------------------------//
HRESULT CCallto::GetGatekeeperIpAddress
(
	unsigned long &	ulIpAddress		//	out unsigned long reference to receive gatekeeper IP address
){
	ASSERT( m_pszGatekeeperName != NULL );

	if( m_ulGatekeeperAddress == INADDR_NONE )
	{
		GetIpAddressFromHostName( m_pszGatekeeperName, m_ulGatekeeperAddress );
	}

	ulIpAddress = m_ulGatekeeperAddress;

	return( (m_ulGatekeeperAddress != INADDR_NONE)? S_OK: NM_CALLERR_NO_GATEKEEPER );

}	//	End of CCallto::GetGatekeeperIpAddress.


//--------------------------------------------------------------------------//
//	CCallto::inGatewayMode.													//
//--------------------------------------------------------------------------//
bool CCallto::inGatewayMode(void)
{

	return( (m_bGatewayEnabled && (m_pszGatewayName != NULL)) || (m_Parameters.GetParam( TEXT( "gateway" ), NULL ) != NULL) );

}	//	End of CCallto::inGatewayMode.


//--------------------------------------------------------------------------//
//	CCallto::SetGatewayEnabled.												//
//--------------------------------------------------------------------------//
void CCallto::SetGatewayEnabled
(
	const bool	bEnabled	//	new Gateway state
){

	m_bGatewayEnabled = bEnabled;

}	//	End of CCallto::SetGatewayEnabled.


//--------------------------------------------------------------------------//
//	CCallto::SetGatewayName.												//
//--------------------------------------------------------------------------//
HRESULT CCallto::SetGatewayName
(
	const TCHAR * const	pszGatewayName	//	pointer to new Gateway name
){

	if( lstrcmpi( pszGatewayName, m_pszGatewayName ) != 0 )
	{
		delete [] m_pszGatewayName;

		m_pszGatewayName	= PszAlloc( pszGatewayName );
		m_ulGatewayAddress	= INADDR_NONE;	//	We reset this cached value when the name changes...
	}

	ASSERT( (m_pszGatewayName != NULL) || (pszGatewayName == NULL) );

	return( ((pszGatewayName != NULL) && (m_pszGatewayName == NULL))? E_OUTOFMEMORY: S_OK );

}	//	End of CCallto::SetGatewayName.


//--------------------------------------------------------------------------//
//	CCallto::GetGatewayIpAddress.											//
//--------------------------------------------------------------------------//
HRESULT CCallto::GetGatewayIpAddress
(
	unsigned long &	ulIpAddress		//	out unsigned long reference to receive gateway IP address
){

	const TCHAR * const	pszGateway	= m_Parameters.GetParam( TEXT( "gateway" ), NULL );

	if( pszGateway != NULL )
	{
		//	A non-default gateway was specified with this callto...
		GetIpAddressFromHostName( pszGateway, ulIpAddress );
	}
	else
	{
		if( m_ulGatewayAddress == INADDR_NONE )
		{
			GetIpAddressFromHostName( m_pszGatewayName, m_ulGatewayAddress );
		}

		ulIpAddress = m_ulGatewayAddress;
	}

	return( (ulIpAddress != INADDR_NONE)? S_OK: NM_CALLERR_NO_GATEWAY );

}	//	End of CCallto::GetGatewayIpAddress.


//--------------------------------------------------------------------------//
//	CCallto::SetIlsServerName.												//
//--------------------------------------------------------------------------//
HRESULT CCallto::SetIlsServerName
(
	const TCHAR * const	pszServerName	//	pointer to new default Ils server name
){

	delete [] m_pszDefaultIlsServerName;
	
	m_pszDefaultIlsServerName = PszAlloc( pszServerName );

	ASSERT( (m_pszDefaultIlsServerName != NULL) || (pszServerName == NULL) );

	return( ((pszServerName != NULL) && (m_pszDefaultIlsServerName == NULL))? E_OUTOFMEMORY: S_OK );

}	//	End of CCallto::SetIlsServerName.


//--------------------------------------------------------------------------//
//	CCallto::GetIpAddressFromIls.											//
//--------------------------------------------------------------------------//
HRESULT CCallto::GetIpAddressFromIls
(
	unsigned long &	ulIpAddress		//	out unsigned long reference to receive IP address
){
	ASSERT( m_pszEmail != NULL );

	HRESULT	hrResult;

	const TCHAR * const	pszActiveIlsServer	= (m_pszIlsServer != NULL)? m_pszIlsServer: m_pszDefaultIlsServerName;

	if( pszActiveIlsServer == NULL )
	{
		hrResult = NM_CALLERR_NO_ILS;
	}

	if( g_pLDAP == NULL )
	{
		g_pLDAP = new CNmLDAP;
	}

	ASSERT( g_pLDAP != NULL );

	if( g_pLDAP == NULL )
	{
		hrResult = E_OUTOFMEMORY;
	}
	else
	{
		TCHAR	szIpAddress[ 64 ];

		hrResult = g_pLDAP->ResolveUser( m_pszEmail, pszActiveIlsServer, szIpAddress, ARRAY_ELEMENTS( szIpAddress ), m_uiIlsPort );

		if( hrResult == S_OK )
		{
			//	Verify that it gave back a good IP address...
			hrResult = GetIpAddress( szIpAddress, ulIpAddress );
		}

		if( hrResult != S_OK )
		{
			hrResult = NM_CALLERR_ILS_RESOLUTION_FAILED;
		}
	}

	return( hrResult );

}	//	End of CCallto::GetIpAddressFromIls.


//--------------------------------------------------------------------------//
//	CCallto::DoUserValidation    											//
//--------------------------------------------------------------------------//
bool CCallto::DoUserValidation(const TCHAR * const pszCallto)
{
    bool    bRet        = false;
    CCallto callto;
    TCHAR   szCaption[MAX_PATH];
    TCHAR   *pszText    = NULL;

    // Parse input string to retrieve display name
    if(FAILED(callto.Parse(pszCallto)))
        goto Exit;

    // Verify we have a valid display name
    if(NULL == callto.m_pszDisplayName)
        goto Exit;

    // Allocate message buffer. MAX_PATH represents max format string size
    pszText = new TCHAR[_tcslen(callto.m_pszDisplayName) + MAX_PATH];
    if(NULL == pszText)
        goto Exit;

    // Compose message string
    if(!FLoadString1(IDS_JOIN_PERMISSION, pszText, callto.m_pszDisplayName))
        goto Exit;

    if(!FLoadString(IDS_MSGBOX_TITLE, szCaption, CCHMAX(szCaption)))
        goto Exit;

    // Display message box
    if(IDOK != MessageBox(NULL, pszText, szCaption, MB_ICONWARNING | MB_OKCANCEL | MB_TOPMOST))
        goto Exit;

    bRet = true;
Exit:
    // Free allocated buffer
    if(NULL != pszText)
    {
        delete [] pszText;
    }
    
    return bRet;
}

//--------------------------------------------------------------------------//
//	CCalltoParams::CCalltoParams.											//
//--------------------------------------------------------------------------//
CCalltoParams::CCalltoParams(void):
	m_chNameDelimiter( '+' ),
	m_chValueDelimiter( '=' ),
	m_pszParams( NULL ),
	m_iCount( 0 )
{
}	//	End of CCalltoParams::CCalltoParams.


//--------------------------------------------------------------------------//
//	CCalltoParams::CCalltoParams.											//
//--------------------------------------------------------------------------//
CCalltoParams::~CCalltoParams()
{

	delete [] m_pszParams;

}	//	End of CCalltoParams::~CCalltoParams.


//--------------------------------------------------------------------------//
//	CCalltoParams::SetParams.												//
//--------------------------------------------------------------------------//
HRESULT
CCalltoParams::SetParams
(
	const TCHAR * const	pszParams
){

	HRESULT	hrResult;

	delete [] m_pszParams;

	m_pszParams	= NULL;
	m_iCount	= 0;

	if( pszParams == NULL )
	{
		hrResult = S_OK;
	}
	else if( (m_pszParams = new TCHAR [ lstrlen( pszParams ) + 1 ]) == NULL )
	{
		hrResult = E_OUTOFMEMORY;
	}
	else
	{
		hrResult = S_OK;

		lstrcpy( m_pszParams, pszParams );

		TCHAR *	pszPos	= m_pszParams;
		TCHAR *	pszEnd;

		while( (*pszPos != '\0') && (m_iCount < ARRAY_ELEMENTS( m_pszNames )) )
		{
			m_pszNames[ m_iCount ]	= pszPos;
			m_pszValues[ m_iCount ]	= NULL;

			while( *pszPos != '\0' )
			{
				if( (*pszPos == '+') || (*pszPos == '=') )
				{
					break;
				}

				pszPos = CharNext( pszPos );
			}

			if( *pszPos != '=' )
			{
				//	Valueless param...

				if( m_pszNames[ m_iCount ] == pszPos )
				{
					hrResult = NM_CALLERR_PARAM_ERROR;	//	Can't have zero length param names...
					break;
				}

				pszEnd = pszPos;
				pszPos = CharNext( pszPos );

				if( (pszPos != pszEnd) && (*pszPos == '\0') )
				{
					hrResult = NM_CALLERR_PARAM_ERROR;	//	Can't have trailing + or =...
					break;
				}

				*pszEnd = '\0';
				m_iCount++;
			}
			else
			{
				//	Value follows...
				pszEnd = pszPos;
				pszPos = CharNext( pszPos );
				*pszEnd = '\0';

				m_pszValues[ m_iCount ] = pszPos;

				while( *pszPos != '\0' )
				{
					if( (*pszPos == '+') || (*pszPos == '=') )
					{
						break;
					}

					pszPos = CharNext( pszPos );
				}

				if( (*pszPos == '=') || (m_pszValues[ m_iCount ] == pszPos) )
				{
					hrResult = NM_CALLERR_PARAM_ERROR;	//	Can't have '=' or zero length param names...
					break;
				}

				pszEnd = pszPos;
				pszPos = CharNext( pszPos );

				if( (pszPos != pszEnd) && (*pszPos == '\0') )
				{
					hrResult = NM_CALLERR_PARAM_ERROR;	//	Can't have trailing + or =...
					break;
				}

				*pszEnd = '\0';
				m_iCount++;
			}
		}

		if( hrResult == S_OK )
		{
			if( m_iCount == ARRAY_ELEMENTS( m_pszNames ) )
			{
				hrResult = NM_CALLERR_PARAM_ERROR;	//	Too many params...
			}
			else
			{
				for( int nn = 0; nn < m_iCount; nn++ )
				{
					if( (hrResult = Unescape( m_pszNames[ nn ] )) != S_OK )
					{
						break;
					}

					if( m_pszValues[ nn ] != NULL )
					{
						if( (hrResult = Unescape( m_pszValues[ nn ] )) != S_OK )
						{
							break;
						}
					}
				}
			}
		}
	}

	return( hrResult );

}	//	End of CCalltoParams::SetParams.


//--------------------------------------------------------------------------//
//	CCalltoParams::GetParam.												//
//--------------------------------------------------------------------------//
const TCHAR * const
CCalltoParams::GetParam
(
	const TCHAR * const	pszName,
	const TCHAR * const	pszDefaultValue
) const
{
	ASSERT( pszName != NULL );

	TCHAR *	pszValue	= NULL;

	if( m_pszParams != NULL )
	{
		for( int nn = 0; nn < m_iCount; nn++ )
		{
			if( lstrcmpi( pszName, m_pszNames[ nn ] ) == 0 )
			{
				pszValue = m_pszValues[ nn ];
				break;
			}
		}
	}

	return( (pszValue == NULL)? pszDefaultValue: pszValue );

}	//	End of CCalltoParams::GetParam.


//--------------------------------------------------------------------------//
//	CCalltoParams::GetBooleanParam.											//
//--------------------------------------------------------------------------//
bool
CCalltoParams::GetBooleanParam
(
	const TCHAR * const	pszParamName,
	const bool			bDefaultValue
) const
{
	ASSERT( pszParamName != NULL );

	const TCHAR * const	pszValue	= GetParam( pszParamName, NULL );
	bool				bResult;

	if( pszValue == NULL )		//	this parameter wasn't specified...
	{
		bResult = bDefaultValue;
	}
	else if( *pszValue == 0 )	//	this parameter was specified but with no value...
	{
		bResult = true;
	}
	else if(	(lstrcmpi( pszValue, TEXT( "1" ) ) == 0)	||
				(lstrcmpi( pszValue, TEXT( "true" ) ) == 0)	||
				(lstrcmpi( pszValue, TEXT( "y" ) ) == 0)	||
				(lstrcmpi( pszValue, TEXT( "yes" ) ) == 0)	||
				(lstrcmpi( pszValue, TEXT( "on" ) ) == 0) )
	{
		bResult = true;
	}
	else if(	(lstrcmpi( pszValue, TEXT( "0" ) ) == 0)		||
				(lstrcmpi( pszValue, TEXT( "false" ) ) == 0)	||
				(lstrcmpi( pszValue, TEXT( "n" ) ) == 0)		||
				(lstrcmpi( pszValue, TEXT( "no" ) ) == 0)		||
				(lstrcmpi( pszValue, TEXT( "off" ) ) == 0) )
	{
		bResult = false;
	}
	else
	{
		bResult = bDefaultValue;
	}

	return( bResult );

}	//	End of CCalltoParams::GetBooleanParam.


//--------------------------------------------------------------------------//
//	IsLocalIpAddress.														//
//--------------------------------------------------------------------------//
bool IsLocalIpAddress
(
	const unsigned long	ulIpAddress		// IP Address to verify is not local
){
	bool	bResult	= (ulIpAddress == INADDR_LOOPBACK);	//	First check right away if it's the prefined loop back ip address...

	if( !bResult )
    {
	    char	szHostName[ MAX_PATH ];

	    //	Get our own local hostname...
		if( gethostname( szHostName, ARRAY_ELEMENTS( szHostName ) ) == SOCKET_ERROR )
		{
			WARNING_OUT( ("IsLocalIpAddress: gethostname() failed with error=%s", PszWSALastError()) );
		}
		else
		{
			//	Now find out which IP addresses are associated with it...
			HOSTENT *	pHostEnt	= gethostbyname( szHostName );

			if( pHostEnt == NULL )
			{
				WARNING_OUT( ("IsLocalIpAddress: gethostbyname() failed with error=%s", PszWSALastError()) );
			}
			else if( (pHostEnt->h_addrtype != AF_INET) || (pHostEnt->h_length != sizeof( ulIpAddress )) )
			{
				WARNING_OUT( ("IsLocalIpAddress: gethostbyname() returned unexpected address type: 0x%08X (%d)", pHostEnt->h_addrtype, pHostEnt->h_addrtype) );
			}
			else
			{
				ASSERT( reinterpret_cast<unsigned long **>(pHostEnt->h_addr_list) != NULL );

				//	Compare all the IP addresses associated with this machine to see if any of them match the one specified...
				for( unsigned long ** ppIpAddress = reinterpret_cast<unsigned long **>(pHostEnt->h_addr_list); *ppIpAddress != NULL; ppIpAddress++ )
				{
					if( **ppIpAddress == ulIpAddress )
					{
						bResult = true;
						break;
					}
				}
			}
		}
	}

	return( bResult );

}	//	End of IsLocalIpAddress.


//--------------------------------------------------------------------------//
//	DecimalStringToUINT.													//
//--------------------------------------------------------------------------//
HRESULT DecimalStringToUINT
(
	const TCHAR * const	pszDecimalString,		//	Pointer to string to convert...
	unsigned int &		uiValue					//	out unsigned int reference to receive converted value...
){
	ASSERT( pszDecimalString != NULL );

	HRESULT	hrResult;

	if( lstrlen( pszDecimalString ) > 10 )
	{
		hrResult = E_INVALIDARG;		//	Limit it to billions....
	}
	else
	{
		hrResult = S_OK;

		const TCHAR * pszDigit;

		for( pszDigit = pszDecimalString, uiValue = 0; *pszDigit != '\0'; pszDigit = CharNext( pszDigit ) )
		{
			if( (*pszDigit < '0') || (*pszDigit > '9') )
			{
				//	There's a non digit character in the string so fail...
				hrResult = E_INVALIDARG;
				break;
			}

			uiValue = (uiValue * 10) + *pszDigit - '0';
		}
	}

	return( hrResult );

}	//	End of DecimalStringToUINT.


//--------------------------------------------------------------------------//
//	TrimSzCallto.															//
//--------------------------------------------------------------------------//
int TrimSzCallto
(
	TCHAR * const	pszSrc		//	Pointer to string to trim blanks from in place...
){
	ASSERT( pszSrc != NULL );

	TCHAR *	pszFirst;
	int		iResult;

	for( pszFirst = pszSrc; *pszFirst == ' '; pszFirst = CharNext( pszFirst ) ){};

	if( *pszFirst == '\0' )
	{
		*pszSrc = '\0';
		iResult	= 0;
	}
	else
	{
		TCHAR *	pszLast;
		TCHAR *	psz;

		for( pszLast = pszFirst, psz = pszFirst; *psz != '\0'; psz = CharNext( psz ) )
		{
			if( *psz != ' ' )
			{
				pszLast = psz;
			}
		}

		pszLast		= CharNext( pszLast );
		*pszLast	= '\0';

		lstrcpy( pszSrc, pszFirst );
		iResult = lstrlen( pszSrc );
	}

	return( iResult );

}	//	End of TrimSzCallto.


//--------------------------------------------------------------------------//
//	GetIpAddress.															//
//--------------------------------------------------------------------------//
HRESULT GetIpAddress
(
	const TCHAR * const	pszIpAddress,	//	pointer to dotted IP address string
	unsigned long &		ulIpAddress		//	out unsigned long reference to receive IP address
){
	ASSERT( pszIpAddress != NULL );

	ulIpAddress = INADDR_NONE;

	int				ipByte	= 0;
	int				parts	= 0;
	const TCHAR *	ptr		= pszIpAddress;
	bool			newPart	= true;
	bool			result	= true;

	while( result && (*ptr != NULL) && (parts <= 4) )
	{
		if( (*ptr >= '0') && (*ptr <= '9') )
		{
			if( newPart )
			{
				parts++;
				newPart = false;
			}

			ipByte = (ipByte * 10) + (*ptr - '0');

			if( ipByte > 255 )
			{
				result = false;
			}
		}
		else if( *ptr == '.' )
		{
			newPart	= true;
			ipByte	= 0;
		}
		else
		{
			result = false;
		}

		ptr++;
	}

	if( result && (parts == 4) )
	{
#if !defined( UNICODE )
		ulIpAddress = inet_addr( pszIpAddress );
#else
		char *	ansiIPAddress;
		int		size;

		size = WideCharToMultiByte(	CP_ACP,		// code page
									0,			// performance and mapping flags
									ipAddress,	// address of wide-character string
									-1,			// number of characters in string
									NULL,		// address of buffer for new string
									0,			// size of buffer
									NULL,		// address of default for unmappable characters
									NULL );		// address of flag set when default char. used

		if( (ansiIPAddress = new char [ size ]) != NULL )
		{
			size = WideCharToMultiByte(	CP_ACP,			// code page
										0,				// performance and mapping flags
										pszIpAddress,	// address of wide-character string
										-1,				// number of characters in string
										ansiIPAddress,	// address of buffer for new string
										size,			// size of buffer
										NULL,			// address of default for unmappable characters
										NULL );			// address of flag set when default char. used

			if( size != 0 )
			{
				ulIpAddress = inet_addr( ansiIPAddress );
			}

			delete [] ansiIPAddress;
		}
#endif	//	!defined( UNICODE )
	}

	return( (ulIpAddress != INADDR_NONE)? S_OK: E_FAIL );

}	//	End of GetIpAddress.


//--------------------------------------------------------------------------//
//	GetIpAddressFromHostName.												//
//--------------------------------------------------------------------------//
HRESULT GetIpAddressFromHostName
(
	const TCHAR * const	pszName,		//	pointer to host name to get IP address of
	unsigned long &		ulIpAddress		//	out unsigned long reference to receive IP address
){
	ASSERT( pszName != NULL );

	HRESULT	hrResult	= E_FAIL;

	if( pszName[ 0 ] == '\0' )
	{
		ulIpAddress = INADDR_NONE;
	}
	else if( (hrResult = GetIpAddress( pszName, ulIpAddress )) != S_OK )
	{
		//	Wasn't already in dotted IP address form...

		HOSTENT *	pHostEnt;
#if !defined( UNICODE )
		TCHAR *		pszOemName = new TCHAR [ lstrlen( pszName ) + 1 ];

		if( pszOemName == NULL )
		{
			hrResult = E_OUTOFMEMORY;
		}
		else
		{
			lstrcpy( pszOemName, pszName );
			CharUpper ( pszOemName );
			CharToOem( pszOemName, pszOemName );

			pHostEnt = gethostbyname( pszOemName );

			if( pHostEnt == NULL )
			{
				WARNING_OUT( ("GetIpAddressFromHostName: gethostbyname() failed with error=%s", PszWSALastError()) );
			}
			else if( (pHostEnt->h_addrtype != AF_INET) || (pHostEnt->h_length != sizeof( ulIpAddress )) )
			{
				WARNING_OUT( ("GetIpAddressFromHostName: gethostbyname() returned unexpected address type: 0x%08X (%d)", pHostEnt->h_addrtype, pHostEnt->h_addrtype) );
			}
			else
			{
				if( pHostEnt->h_addr_list[ 0 ] != NULL )
				{
					ulIpAddress	= *reinterpret_cast<unsigned long *>(pHostEnt->h_addr_list[ 0 ]);	//	Just use the first IP address
					hrResult	= S_OK;
				}
			}

			delete [] pszOemName;
		}
#else
		//	Need to figure out OEM'ing the name...

		char *	pszMultiByteName;
		int		iSize;

		iSize = WideCharToMultiByte(	CP_ACP,		// code page
										0,			// performance and mapping flags
										pszName,	// address of wide-character string
										-1,			// number of characters in string
										NULL,		// address of buffer for new string
										0,			// size of buffer
										NULL,		// address of default for unmappable characters
										NULL );		// address of flag set when default char. used

		if( (pszMultiByteName = new char [ iSize ]) == NULL )
		{
			hrResult = E_OUTOFMEMORY;
		}
		else
		{
			iSize = WideCharToMultiByte(	CP_ACP,				// code page
											0,					// performance and mapping flags
											pszName,			// address of wide-character string
											-1,					// number of characters in string
											pszMultiByteName,	// address of buffer for new string
											iSize,				// size of buffer
											NULL,				// address of default for unmappable characters
											NULL );				// address of flag set when default char. used

			if( iSize != 0 )
			{
				pHostEnt = gethostbyname( ansiHost );

				if( pHostEnt == NULL )
				{
					WARNING_OUT( ("GetIpAddressFromHostName: gethostbyname() failed with error=%s", PszWSALastError()) );
				}
				else if( (pHostEnt->h_addrtype != AF_INET) || (pHostEnt->h_length != sizeof( ulIpAddress )) )
				{
					WARNING_OUT( ("GetIpAddressFromHostName: gethostbyname() returned unexpected address type: 0x%08X (%d)", pHostEnt->h_addrtype, pHostEnt->h_addrtype) );
				}
				else
				{
					if( pHostEnt->h_addr_list[ 0 ] != NULL )
					{
						ulIpAddress	= *reinterpret_cast<unsigned long *>(pHostEnt->h_addr_list[ 0 ]);	//	Just use the first IP address
						hrResult	= S_OK;
					}
				}
			}

			delete [] pszMultiByteName;
		}
#endif	// !defined( UNICODE )
	}

	return( hrResult );

}	//	End of GetIpAddressFromHostName.


//--------------------------------------------------------------------------//
//	IsPhoneNumber.															//
//--------------------------------------------------------------------------//
bool IsPhoneNumber
(
	const TCHAR *	pszPhone	//	string to check for invalid phone number characters
){
	ASSERT( pszPhone != NULL );
	ASSERT( pszPhone[ 0 ] != '\0' );

	bool	bResult	= true;

	while( pszPhone[ 0 ] != '\0' )
	{
		switch( pszPhone[ 0 ] )
		{
			case '0':
			case '1':
			case '2':
			case '3':
			case '4':
			case '5':
			case '6':
			case '7':
			case '8':
			case '9':
			case '(':
			case ')':
			case '#':
			case '*':
			case '-':
			case ',':
			case ' ':
				break;

			default:
				bResult = false;
				break;
		}
		
		pszPhone++;
	}

	return( bResult );

}	//	End of IsPhoneNumber.


//--------------------------------------------------------------------------//
//	bCanCallAsPhoneNumber.													//
//--------------------------------------------------------------------------//
bool bCanCallAsPhoneNumber
(
	const TCHAR * const	pszPhone
){
	ASSERT( pszPhone != NULL );
	ASSERT( pszPhone[ 0 ] != '\0' );

	bool	bResult	= FALSE;

	if( IsPhoneNumber( pszPhone ) )
	{
		if( ConfPolicies::CallingMode_GateKeeper == ConfPolicies::GetCallingMode() )
		{
			bResult = true;
		}
		else
		{
			RegEntry	reConf( CONFERENCING_KEY, HKEY_CURRENT_USER );

			bResult = (reConf.GetNumber( REGVAL_USE_H323_GATEWAY ) != 0);
		}
	}

	return( bResult );

}	//	End of bCanCallAsPhoneNumber.


//--------------------------------------------------------------------------//
//	unescape.																//
//--------------------------------------------------------------------------//
HRESULT Unescape
(
	TCHAR * const	pszSrc		//	pointer to string to unescape in place
){
	ASSERT( pszSrc != NULL );
	
	TCHAR *	pszPercentSign;
	HRESULT	hrResult;

	for( hrResult = S_OK, pszPercentSign = pszSrc; pszPercentSign != NULL; )
	{
		if( (pszPercentSign = StrChr( pszPercentSign, '%' )) != NULL )
		{
			TCHAR	chHighNibble	= pszPercentSign[ 1 ];

			if( ((chHighNibble >= '0') && (chHighNibble <= '9'))	||
				((chHighNibble >= 'a') && (chHighNibble <= 'f'))	||
				((chHighNibble >= 'A') && (chHighNibble <= 'F')) )
			{
				TCHAR	chLowNibble	= pszPercentSign[ 2 ];

				if( ((chLowNibble >= '0') && (chLowNibble <= '9'))	||
					((chLowNibble >= 'a') && (chLowNibble <= 'f'))	||
					((chLowNibble >= 'A') && (chLowNibble <= 'F')) )
				{
					chHighNibble	= ((chHighNibble >= '0') && (chHighNibble <= '9'))? chHighNibble - '0':
										((chHighNibble >= 'a') && (chHighNibble <= 'f'))? chHighNibble - 'a' + 10: chHighNibble - 'A' + 10;
					chLowNibble		= ((chLowNibble >= '0') && (chLowNibble <= '9'))? chLowNibble - '0':
										((chLowNibble >= 'a') && (chLowNibble <= 'f'))? chLowNibble - 'a' + 10: chLowNibble - 'A' + 10;

					*pszPercentSign++ = (chHighNibble << 4) | chLowNibble;
					lstrcpy( pszPercentSign, &pszPercentSign[ 2 ] );
				}
				else
				{
					hrResult = NM_CALLERR_UNESCAPE_ERROR;
					break;
				}
			}
			else
			{
				hrResult = NM_CALLERR_UNESCAPE_ERROR;
				break;
			}
		}
	}

	return( hrResult );

}	//	End of Unescape.
