Added new param

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

 



According to yr suggestion, I have added one param to gkauthenticator, add same param to radius check proc on RRQ
But now its not going to radius, it simply before says security denial.
it failes in this procedure
 bool GkAuthenticator::CheckRas(

PBYTEArray &rawPDU,

H225_RegistrationRequest& rrq,

unsigned& rejectReason,

const PIPSocket::Address& rxaddr,MemBlock *m

)

{

setLastReceivedRawPDU(rawPDU);

if (checkFlag & RasValue(rrq)) {

int r = Check(rrq, rejectReason,m); (It returns e_fail)

if (r == e_ok) {

PTRACE(4, "GkAuth\t" << name << " check ok");

if (controlFlag != e_Required)

return true;

} else if (r == e_fail) {

PTRACE(2, "GkAuth\t" << name << " check failed");

return false;

}

}

return next == NULL || next->CheckRas(rawPDU,rrq,rejectReason,rxaddr,m);

}

Can anyone plz help me regarding this problem. As it getting hard and hard for me to do this. As u people know much more abt gnugk code other than me .I am just new to it. What I am trying to do that I want to send some extra param from onRRQ event to radius check proc on RRQ.
 
/*
 * radauth.cxx
 *
 * RADIUS protocol authenticator module for GNU Gatekeeper. 
 * Please see docs/radauth.txt for more details.
 *
 * Copyright (c) 2003, Quarcom FHU, Michal Zygmuntowicz
 *
 * This work is published under the GNU Public License (GPL)
 * see file COPYING for details.
 * We also explicitely grant the right to link this code
 * with the OpenH323 library.
 *
 * $Log: radauth.cxx,v $
 * Revision 1.1.2.41  2004/06/22 18:41:16  zvision
 * Username, Calling-Station-Id and Called-Station-Id handling rewritten.
 * Radius modules optimized.
 *
 * Revision 1.1.2.40  2004/06/18 15:42:51  zvision
 * Better User-Name and Calling-Station-Id handling for unregistered endpoints
 *
 * Revision 1.1.2.39  2004/06/17 10:46:13  zvision
 * New RADIUS h323-ivr-out=h323-call-id attribute
 *
 * Revision 1.1.2.38  2004/05/25 19:54:05  zvision
 * Some minor warnings removed
 *
 * Revision 1.1.2.37  2004/05/21 11:40:37  zvision
 * Do not declare H.235 Auth Procedure I support as it is not implemented
 *
 * Revision 1.1.2.36  2004/05/12 14:00:47  zvision
 * Header file usage more consistent. Solaris std::map problems fixed.
 * Compilation warnings removed. VSNET2003 project files added. ANSI.h removed.
 *
 * Revision 1.1.2.35  2004/04/14 21:52:57  zvision
 * Fixed referencing RRQ terminalAlias field without checking for its presence
 *
 * Revision 1.1.2.34  2004/03/31 11:13:36  zvision
 * New CheckSetupUnregisteredOnly option for RadAliasAuth module
 *
 * Revision 1.1.2.33  2004/02/18 20:45:16  zvision
 * Prototype changed for GkAuthenticator::Check ARQ method
 *
 * Revision 1.1.2.32  2004/01/16 13:05:01  zvision
 * Better h323-ivr-in attrbiute handling
 *
 * Revision 1.1.2.31  2004/01/08 11:46:47  zvision
 * Fixed semicolon processing for h323-ivr-in reply attribute
 *
 * Revision 1.1.2.30  2004/01/07 20:37:26  zvision
 * RRQ endpoint alias control (add/remove) through RadAuth/RadAliasAuth modules
 *
 * Revision 1.1.2.29  2003/12/26 13:59:31  zvision
 * Fixed destination call signaling address handling
 *
 * Revision 1.1.2.28  2003/12/21 12:20:35  zvision
 * Fixed conditional compilation
 *
 * Revision 1.1.2.27  2003/12/02 12:47:37  zvision
 * Q.931 Setup authenticator now returns (and documents) Q.931 cause values instead of H225_ReleaseCompleteReason
 *
 * Revision 1.1.2.26  2003/11/22 13:43:48  zvision
 * Q.931 Setup Check now accepts const callptr to prevent it from modifications
 *
 * Revision 1.1.2.25  2003/11/18 23:38:51  zvision
 * Q.931 Setup authentication optimized
 *
 * Revision 1.1.2.24  2003/11/12 13:34:55  zvision
 * Fixed Session-Timeout handling in RadAliasAuth Setup check
 *
 * Revision 1.1.2.23  2003/10/03 00:59:49  zvision
 * Fixed bus errors on some system with dword alignment checking enabled.
 * Small optimizations and RadiusPDU API changes.
 *
 * Revision 1.1.2.22  2003/09/30 11:08:16  zvision
 * Fixed non-working Q.931 authenticator due to previous Check(...) signature
 * changes. Many thanks to Oleg Ustinov!
 *
 * Revision 1.1.2.21  2003/09/29 16:23:45  zvision
 * Cisco h323-xxx response attributes handling improved
 *
 * Revision 1.1.2.20  2003/09/25 11:12:46  zvision
 * Fixed Session-Timeout bug and more flexible h323-credit-time processing
 *
 * Revision 1.1.2.19  2003/09/24 10:19:44  zvision
 * Call duration limit for registered endpoints (through ARQ authenticators)
 *
 * Revision 1.1.2.18  2003/08/25 12:18:35  zvision
 * Added h323-ivr-out attribute with an alias list (thanks Mark Lipscombe)
 *
 * Revision 1.1.2.17  2003/07/31 22:59:24  zvision
 * Fixed IP address retrieval for unregistered endpoints
 *
 * Revision 1.1.2.16  2003/07/31 13:09:15  zvision
 * Added Q.931 Setup message authentication and call duration limit feature
 *
 * Revision 1.1.2.15  2003/07/17 14:40:39  zvision
 * Conditional compilation of features available only when HAS_ACCT is defined.
 *
 * Revision 1.1.2.14  2003/07/16 22:13:21  zvision
 * Fixed Radius attributes for answer call ARQs.
 *
 * Revision 1.1.2.13  2003/07/07 14:28:30  zvision
 * Added missing NAS-Identifier attribute in RadAliasAuth. Thanks Julius Stavaris.
 *
 * Revision 1.1.2.12  2003/07/07 12:02:55  zvision
 * Improved H.235 handling.
 *
 * Revision 1.1.2.11  2003/06/19 15:33:29  zvision
 * Removed static modifier from GetConferenceIDString function.
 *
 * Revision 1.1.2.10  2003/06/11 13:06:57  zvision
 * Added gk_const.h include directive (OPENH323_NEWVERSION macro definition)
 *
 * Revision 1.1.2.9  2003/06/11 12:14:35  zvision
 * Cosmetic changes
 *
 * Revision 1.1.2.8  2003/06/05 10:03:04  zvision
 * Small fix to h323-gw-id attribute.
 *
 * Revision 1.1.2.7  2003/05/29 17:21:22  zvision
 * Fixed compilation errors with OpenH323 versions prior to 1.11.5 (no H235AuthCAT)
 *
 * Revision 1.1.2.6  2003/05/28 13:25:19  zvision
 * Added alias based authentication (RadAliasAuth)
 *
 * Revision 1.1.2.5  2003/05/27 00:13:05  zvision
 * Smart Calling and Called -Station-Id selection (dialedDigits and partyNumber alias types preferred)
 *
 * Revision 1.1.2.4  2003/05/26 23:09:59  zvision
 * Added new OnSend and OnReceive hooks. LocalInterface config parameter introduced.
 *
 * Revision 1.1.2.3  2003/05/13 17:49:49  zvision
 * Removed acctPort. New includeFramedIP feature. Better tracing. Bug-fixes
 *
 * Revision 1.1.2.2  2003/04/29 14:56:27  zvision
 * Added H.235 capability matching
 *
 * Revision 1.1.2.1  2003/04/23 20:15:37  zvision
 * Initial revision
 *
 */
#ifdef HAS_RADIUS

#if (_MSC_VER >= 1200)
#pragma warning( disable : 4786 ) // warning about too long debug symbol off
#endif

#include <ptlib.h>
#include <h225ras.h>
#include <h323pdu.h>
#include "gk_const.h"
#include "gkauth.h"
#include "Toolkit.h"
#include "RasTbl.h"
#include "h323util.h"
#include "ProxyChannel.h"
#include "radauth.h"

namespace {
// Settings for H.235 based module will be stored inside [RadAuth] config section
const char* const RadAuthConfigSectionName = "RadAuth";
// Settings for alias based module will be stored inside [RadAliasAuth] config section
const char* const RadAliasAuthConfigSectionName = "RadAliasAuth";
// append RADIUS H.235 authenticator to the global list of authenticators
GkAuthInit<RadAuth> RAD_A("RadAuth");
// append RADIUS Alias authenticator to the global list of authenticators
GkAuthInit<RadAliasAuth> RAD_A_A("RadAliasAuth");
}

// OID for CAT (Cisco Access Token) algorithm
PString RadAuth::OID_CAT( "1.2.840.113548.10.1.2.1" );

RadAuth::RadAuth( 
	PConfig* cfg, 
	const char* authName 
	)
	:
	GkAuthenticator(cfg, authName),
	radiusServers(cfg->GetString(
		RadAuthConfigSectionName,"Servers",""
		).Tokenise(";, |\t", FALSE)),
	sharedSecret(cfg->GetString(
		RadAuthConfigSectionName, "SharedSecret", ""
		)),
	authPort((WORD)(cfg->GetInteger(
		RadAuthConfigSectionName, "DefaultAuthPort"
		))),
	portBase(1024),
	portMax(65535),
	requestTimeout(cfg->GetInteger(
		RadAuthConfigSectionName, "RequestTimeout"
		)),
	idCacheTimeout(cfg->GetInteger(
		RadAuthConfigSectionName, "IdCacheTimeout"
		)),
	socketDeleteTimeout(cfg->GetInteger(
		RadAuthConfigSectionName, "SocketDeleteTimeout"
		)),
	numRequestRetransmissions(cfg->GetInteger(
		RadAuthConfigSectionName, "RequestRetransmissions"
		)),
	roundRobin(Toolkit::AsBool(cfg->GetString(
		RadAuthConfigSectionName, "RoundRobinServers", "1"
		))),
	appendCiscoAttributes(Toolkit::AsBool(cfg->GetString(
		RadAuthConfigSectionName, "AppendCiscoAttributes", "1"
		))),
	includeTerminalAliases(Toolkit::AsBool(cfg->GetString(
		RadAuthConfigSectionName, "IncludeTerminalAliases", "1"
		))),
	localInterface(cfg->GetString(
		RadAuthConfigSectionName, "LocalInterface", ""
		)),
	radiusClient(NULL),
	attrH323GwId(NULL), attrH323CallType(NULL),
	attrH323CallOriginOriginate(NULL), attrH323CallOriginAnswer(NULL),
	attrNASIdentifier(NULL)
{
	PAssert(radiusServers.GetSize() > 0, "Cannot build RADIUS authenticator "
		" - no RADIUS servers specified in the config"
		);

	if (!localInterface && !PIPSocket::IsLocalHost(localInterface)) {
		PTRACE(1, "RADAUTH\tSpecified local interface - " << localInterface
			<< " - does not belong to this machine"
			);
		localInterface = PString();
	}
	/// build RADIUS client
	radiusClient = new RadiusClient(radiusServers[0], 
		radiusServers.GetSize() > 1 ? radiusServers[1] : PString(), 
		localInterface
		);

	/// if there were specified more than two RADIUS servers, append them
	for (PINDEX i = 2; i < radiusServers.GetSize(); i++)
		radiusClient->AppendServer(radiusServers[i]);	
		
	radiusClient->SetSharedSecret(sharedSecret);
	radiusClient->SetRoundRobinServers(roundRobin);
		
	if (authPort > 0)
		radiusClient->SetAuthPort(authPort);
		
	if (requestTimeout > 0)
		radiusClient->SetRequestTimeout(requestTimeout);
	if (idCacheTimeout > 0)
		radiusClient->SetIdCacheTimeout(idCacheTimeout);
	if (socketDeleteTimeout > 0)
		radiusClient->SetSocketDeleteTimeout(socketDeleteTimeout);
	if (numRequestRetransmissions > 0)
		radiusClient->SetRetryCount(numRequestRetransmissions);
	
	const PStringArray s = cfg->GetString(
		RadAuthConfigSectionName, "RadiusPortRange", ""
		).Tokenise("-");

	// parse port range		
	if (s.GetSize() >= 2) { 
		unsigned p1 = s[0].AsUnsigned();
		unsigned p2 = s[1].AsUnsigned();
	
		// swap if base is greater than max
		if (p2 < p1) {
			const unsigned temp = p1;
			p1 = p2;
			p2 = temp;
		}
		
		if (p1 > 65535)
			p1 = 65535;
		if (p2 > 65535)
			p2 = 65535;
	
		if (p1 > 0 && p2 > 0) {
			portBase = (WORD)p1;
			portMax = (WORD)p2;
		}
	}
	
	radiusClient->SetClientPortRange(portBase, portMax - portBase + 1);

	if (localInterface.IsEmpty())
		localInterfaceAddr = Toolkit::Instance()->GetRouteTable()->GetLocalAddress();
	else
		localInterfaceAddr = PIPSocket::Address(localInterface);

	NASIdentifier = Toolkit::Instance()->GKName();

	if (h235Authenticators == NULL)
		h235Authenticators = new H235Authenticators;
		
	H235AuthCAT* authenticator = new H235AuthCAT;
	authenticator->SetLocalId("dummy");
	authenticator->SetRemoteId("dummy");
	authenticator->SetPassword("dummy");
	h235Authenticators->Append(authenticator);

	attrH323GwId = new RadiusAttr(PString("h323-gw-id=") + NASIdentifier,
		9 /* Cisco */, 33 /* h323-gw-id */
		);
	attrH323CallType = new RadiusAttr(PString("h323-call-type=VoIP"),
		9 /* Cisco */, 27 /* h323-call-type */
		);
	attrH323CallOriginOriginate = new RadiusAttr(
		PString("h323-call-origin=originate"), 
		9 /* Cisco */, 26 /* h323-call-origin */
		);
	attrH323CallOriginAnswer = new RadiusAttr(
		PString("h323-call-origin=answer"),
		9 /* Cisco */, 26 /* h323-call-origin */
		);
	attrNASIdentifier = new RadiusAttr(RadiusAttr::NasIdentifier, NASIdentifier);
}

RadAuth::~RadAuth()
{
	delete radiusClient;
	delete attrH323GwId;
	delete attrH323CallType;
	delete attrH323CallOriginOriginate;
	delete attrH323CallOriginAnswer;
	delete attrNASIdentifier;
}

int RadAuth::Check(
	H225_RegistrationRequest& rrq, 
	unsigned& rejectReason,MemBlock * m
	)
{
	PAssertNULL(radiusClient);

	if (!rrq.HasOptionalField(H225_RegistrationRequest::e_tokens)) {
		PTRACE(4, "RADAUTH\tRRQ Auth not possible - no 'tokens' field");
		rejectReason = H225_RegistrationRejectReason::e_securityDenial;
		return defaultStatus;
	}

	// RRQ has to carry at least one terminalAlias
	if (!rrq.HasOptionalField(H225_RegistrationRequest::e_terminalAlias)) {
		PTRACE(4, "RADAUTH\tRRQ Auth not possible - no 'terminalAlias' field");
		rejectReason = H225_RegistrationRejectReason::e_invalidTerminalAliases;
		return defaultStatus;
	}

	// build RADIUS Access-Request
	RadiusPDU* pdu = radiusClient->BuildPDU();
	if (pdu == NULL) {
		PTRACE(3, "RADAUTH\tRRQ auth failed - could not create Access-Request PDU");
		rejectReason = H225_RegistrationRejectReason::e_undefinedReason;
		return defaultStatus;
	}

	pdu->SetCode(RadiusPDU::AccessRequest);

	// check for ClearTokens (CAT uses ClearTokens)
	const H225_ArrayOf_AliasAddress& aliases = rrq.m_terminalAlias;
	const H225_ArrayOf_ClearToken& tokens = rrq.m_tokens;
	bool foundCAT = false;
		
	// scan ClearTokens and find CATs
	for (PINDEX i = 0; i < tokens.GetSize(); i++) {
		const H235_ClearToken& token = tokens[i];
			
		// is it CAT?
		if (token.m_tokenOID != OID_CAT)
			continue;
			
		// these field are required for CAT
	  	if (!(token.HasOptionalField(H235_ClearToken::e_generalID)
			&& token.HasOptionalField(H235_ClearToken::e_random)
			&& token.HasOptionalField(H235_ClearToken::e_timeStamp)
			&& token.HasOptionalField(H235_ClearToken::e_challenge))) {
			PTRACE(4, "RADAUTH\tRRQ Auth failed - CAT without all required fields");
			rejectReason = H225_RegistrationRejectReason::e_securityDenial;
			delete pdu;
			return e_fail;
		}
				
		// generalID should be present in the list of terminal aliases
		const PString id = token.m_generalID;
		if (FindAlias(aliases, id) == P_MAX_INDEX) {
			PTRACE(4, "RADAUTH\tRRQ Auth failed - CAT generalID is not a valid alias");
			rejectReason = H225_RegistrationRejectReason::e_invalidTerminalAliases;
			delete pdu;
			return e_fail;
		}
					
		// CAT pseudo-random has to be one byte only
		const int randomInt = token.m_random;
		if (randomInt < -127 || randomInt > 255) {
			PTRACE(4,"RADAUTH\tRRQ Auth failed - CAT m_random out of range");
			rejectReason = H225_RegistrationRejectReason::e_securityDenial;
			delete pdu;
			return e_fail;
		}
					
		// CAT challenge has to be 16 bytes
		if (token.m_challenge.GetValue().GetSize() < 16) {
			PTRACE(4,"RADAUTH\tRRQ Auth failed - m_challenge less than 16 bytes");
			rejectReason = H225_RegistrationRejectReason::e_securityDenial;
			delete pdu;
			return e_fail;
		}
				
		// append User-Name
		*pdu += new RadiusAttr(RadiusAttr::UserName, id);

		// build CHAP-Password
		char password[17];
		password[0] = (BYTE)randomInt;
		memcpy(password+1, (const BYTE*)(token.m_challenge), 16);
				
		*pdu += new RadiusAttr( RadiusAttr::ChapPassword, 
			password, sizeof(password)
			);
		*pdu += new RadiusAttr(RadiusAttr::ChapChallenge,
			(int)(DWORD)token.m_timeStamp
			);
			
		foundCAT = true;
		break;
	}				

	if (!foundCAT) {
		PTRACE(4, "RADAUTH\tRRQ Auth not possible - no CAT token found");
		rejectReason = H225_RegistrationRejectReason::e_securityDenial;
		delete pdu;
		return defaultStatus;
	}
	
	// Gk works as NAS point, so append NAS IP
	*pdu += new RadiusAttr(RadiusAttr::NasIpAddress, localInterfaceAddr);
	// NAS-Identifier as Gk name
	*pdu += new RadiusAttr(*attrNASIdentifier);
	// Gk does not have a concept of physical ports,
	// so define port type as NAS-Port-Virtual
	*pdu += new RadiusAttr(RadiusAttr::NasPortType, RadiusAttr::NasPort_Virtual);
	// RRQ service type is Login-User
	*pdu += new RadiusAttr(RadiusAttr::ServiceType,	RadiusAttr::ST_Login);
					
	PIPSocket::Address addr;
				
	bool addrValid = false;
	
	if (rrq.m_callSignalAddress.GetSize() > 0)
		addrValid = GetIPFromTransportAddr(rrq.m_callSignalAddress[0], addr)
			&& addr.IsValid();

	if (!addrValid && rrq.m_rasAddress.GetSize() > 0)
		addrValid = GetIPFromTransportAddr(rrq.m_rasAddress[0], addr) 
			&& addr.IsValid();

	if (!addrValid) {
		PTRACE(3, "RADAUTH\tRRQ check failed - could not determine Framed-IP-Address");
		rejectReason = H225_RegistrationRejectReason::e_invalidCallSignalAddress;
		delete pdu;
		return defaultStatus;
	}

	*pdu += new RadiusAttr(RadiusAttr::FramedIpAddress, addr);

	if (appendCiscoAttributes && includeTerminalAliases) {
		PString aliasList( "terminal-alias:" );
					
		for (PINDEX i = 0; i < aliases.GetSize(); i++) {
			if (i > 0)
				aliasList += ",";
			aliasList += H323GetAliasAddressString(aliases[i]);
		}
		*pdu += new RadiusAttr(
			PString("h323-ivr-out=") + aliasList + PString(";"),
			9 /* Cisco */, 1 /* Cisco-AV-Pair */
			);
	}
				
	// send request and wait for response
	RadiusPDU* response = NULL;
	bool result = OnSendPDU(*pdu, rrq, rejectReason)
		&& radiusClient->MakeRequest(*pdu, response) && (response != NULL);
			
	delete pdu;
			
	if (!result) {
		delete response;
		return defaultStatus;
	}
				
	result = (response->GetCode() == RadiusPDU::AccessAccept);
				
	// process h323-ivr-in=terminal-alias attribute
	if (result) {
		PINDEX index = response->FindVsaAttr(9, 1);
		bool found = false;
		while (index != P_MAX_INDEX && !found) {
			PString h323ivrin;
			const RadiusAttr* attr = response->GetAttrAt(index);
			if (attr && attr->IsValid())
				h323ivrin = attr->AsVsaString();
			if (h323ivrin.Find("h323-ivr-in=") == 0 
				&& ((index = h323ivrin.Find("terminal-alias:")) != P_MAX_INDEX)) {
				found = true;
				index += strlen("terminal-alias:");
				const PINDEX semicolonpos = h323ivrin.Find(';',index);
				h323ivrin = h323ivrin.Mid(
					index, semicolonpos == P_MAX_INDEX
						? P_MAX_INDEX : (semicolonpos-index)
					);
				PStringArray aliases = h323ivrin.Tokenise(",");
				if (aliases.GetSize() > 0 
					&& rrq.HasOptionalField(H225_RegistrationRequest::e_terminalAlias)) {
					PINDEX i = 0;
					while (i < rrq.m_terminalAlias.GetSize()) {
						PINDEX j = aliases.GetStringsIndex(H323GetAliasAddressString(rrq.m_terminalAlias[i]));
						if( j == P_MAX_INDEX )
							rrq.m_terminalAlias.RemoveAt(i);
						else {
							i++;
							aliases.RemoveAt(j);
						}
					}
				}
				for( PINDEX i = 0; i < aliases.GetSize(); i++ ) {
					if( rrq.HasOptionalField(H225_RegistrationRequest::e_terminalAlias) )
						rrq.m_terminalAlias.SetSize(rrq.m_terminalAlias.GetSize()+1);
					else {
						rrq.IncludeOptionalField(H225_RegistrationRequest::e_terminalAlias);
						rrq.m_terminalAlias.SetSize(1);
					}
					H323SetAliasAddress(aliases[i],rrq.m_terminalAlias[rrq.m_terminalAlias.GetSize()-1]);
				}
			}
			if( !found )
				index = response->FindVsaAttr( 9, 1, index + 1 );
		}
	}

	if (result)
	{
		result = OnReceivedPDU(*response, rrq, rejectReason);
		m->BalanceString="12.15";
		m->callDuration =24;
	}
	else
		rejectReason = H225_RegistrationRejectReason::e_securityDenial;
					
	delete response;
	return result ? e_ok : e_fail;
}

int RadAuth::Check(
	H225_AdmissionRequest& arq, 
	unsigned& rejectReason,
	long& callDurationLimit
	)
{
	PAssertNULL(radiusClient);
	
	// check for ClearTokens
	if (!arq.HasOptionalField(H225_AdmissionRequest::e_tokens)) {
		PTRACE(4, "RADAUTH\tARQ Auth not possible - no 'tokens' field");
		rejectReason = H225_AdmissionRejectReason::e_securityDenial;
		return defaultStatus;
	}
	
	// build RADIUS Access-Request packet
	RadiusPDU* pdu = radiusClient->BuildPDU();
	if (pdu == NULL) {
		PTRACE(3, "RADAUTH\tARQ auth failed - could not to create Access-Request PDU");
		rejectReason = H225_AdmissionRejectReason::e_securityDenial;
		return defaultStatus;
	}

	pdu->SetCode(RadiusPDU::AccessRequest);

	const H225_ArrayOf_ClearToken& tokens = arq.m_tokens;
	bool foundCAT = false;
	PString username;
		
	// scan ClearTokens for CATs
	for (PINDEX i = 0; i < tokens.GetSize(); i++) {
		const H235_ClearToken& token = tokens[i];
				
		// is it CAT?
		if (token.m_tokenOID != OID_CAT)
			continue;
			
			// these field are required for CAT
	  	if (!(token.HasOptionalField(H235_ClearToken::e_generalID)
			&& token.HasOptionalField(H235_ClearToken::e_random)
			&& token.HasOptionalField(H235_ClearToken::e_timeStamp)
			&& token.HasOptionalField(H235_ClearToken::e_challenge)) ) {
			PTRACE(4, "RADAUTH\tARQ Auth failed - CAT without all required fields");
			rejectReason = H225_AdmissionRejectReason::e_securityDenial;
			delete pdu;
			return e_fail;
		}
			
		username = token.m_generalID;
					
		// CAT random has to be one byte only				
		const int randomInt = token.m_random;
		if (randomInt < -127 || randomInt > 255) {
			PTRACE(4, "RADAUTH\tARQ Auth failed - CAT random out of range");
			rejectReason = H225_AdmissionRejectReason::e_securityDenial;
			delete pdu;
			return e_fail;
		}
					
		// CAT challenge has to be 16 bytes
		if (token.m_challenge.GetValue().GetSize() < 16) {
			PTRACE(4, "RADAUTH\tARQ Auth failed - CAT challenge less than 16 bytes");
			rejectReason = H225_AdmissionRejectReason::e_securityDenial;
			delete pdu;
			return e_fail;
		}
					
		*pdu += new RadiusAttr(RadiusAttr::UserName, username);
				
		// build CHAP-Password
		char password[17];
		password[0] = (BYTE)randomInt;
		memcpy(password+1, (const BYTE*)(token.m_challenge), 16);
				
		*pdu += new RadiusAttr(RadiusAttr::ChapPassword, 
			password, sizeof(password)
			);
		// append CHAP-Challenge 
		*pdu += new RadiusAttr(RadiusAttr::ChapChallenge, 
			(int)(DWORD)token.m_timeStamp
			);

		foundCAT = true;
		break;
	}
	
	if (!foundCAT) {
		PTRACE(4, "RADAUTH\tARQ Auth not possible - tokens without CAT");
		rejectReason = H225_AdmissionRejectReason::e_securityDenial;
		delete pdu;
		return defaultStatus;
	}
				
	// Gk acts as NAS, so include NAS IP
	*pdu += new RadiusAttr(RadiusAttr::NasIpAddress, localInterfaceAddr);
	// NAS-Identifier as Gk name
	*pdu += new RadiusAttr(*attrNASIdentifier);
	// NAS-Port-Type as Virtual, since Gk does
	// not care about physical ports concept
	*pdu += new RadiusAttr(RadiusAttr::NasPortType, RadiusAttr::NasPort_Virtual);
	// Service-Type is Login-User if originating the call
	// and Call Check if answering the call
	*pdu += new RadiusAttr(RadiusAttr::ServiceType,
		arq.m_answerCall ? RadiusAttr::ST_CallCheck : RadiusAttr::ST_Login
		);
				
	endptr ep = RegistrationTable::Instance()->FindByEndpointId(
		arq.m_endpointIdentifier
		);
					
	PIPSocket::Address addr;
				
	bool addrValid = false;
					
	if (arq.HasOptionalField(arq.m_answerCall 
		? arq.e_destCallSignalAddress : arq.e_srcCallSignalAddress))
		addrValid = (arq.m_answerCall 
				? GetIPFromTransportAddr(arq.m_destCallSignalAddress, addr)
				: GetIPFromTransportAddr(arq.m_srcCallSignalAddress, addr))
			&& addr.IsValid();
	
	if (!addrValid && ep)
		addrValid = GetIPFromTransportAddr(ep->GetCallSignalAddress(), addr) 
			&& addr.IsValid();

	if (!addrValid) {
		PTRACE(3, "RADAUTH\tARQ check failed - could not determine Framed-IP-Address");
		rejectReason = H225_AdmissionRejectReason::e_securityDenial;
		delete pdu;
		return defaultStatus;
	}

	*pdu += new RadiusAttr(RadiusAttr::FramedIpAddress, addr);
				
	callptr call;
	if (arq.m_answerCall)
		if (arq.HasOptionalField(arq.e_callIdentifier))
			call = CallTable::Instance()->FindCallRec(arq.m_callIdentifier);
		else {					
			H225_CallReferenceValue crv;
			crv.SetValue((WORD)arq.m_callReferenceValue & 0x7fffu);
			call = CallTable::Instance()->FindCallRec(crv);
		}

	// fill Calling-Station-Id and Called-Station-Id
	PString stationId = GetCallingStationId(call, arq, ep);
				
	if (stationId.IsEmpty()) {
		PTRACE(4, "RADAUTH\tARQ Auth failed - could not determine Calling-Station-Id");
		delete pdu;
		return e_fail;
	}
				
	*pdu += new RadiusAttr(RadiusAttr::CallingStationId, stationId);
						
	// Called-Station-Id
	stationId = GetCalledStationId(call, arq, ep);

	if (stationId.IsEmpty()) {
		PTRACE(3, "RADAUTH\tARQ Auth failed - could not determine Called-Station-Id");
		delete pdu;
		return e_fail;
	}
				
	*pdu += new RadiusAttr(RadiusAttr::CalledStationId, stationId);
			
	if (appendCiscoAttributes) {
		*pdu += new RadiusAttr(PString("h323-conf-id=") 
				+ GetGUIDString(arq.m_conferenceID),
			9 /* Cisco */, 24 /* h323-conf-id */
			);
					
		*pdu += new RadiusAttr(arq.m_answerCall
			? *attrH323CallOriginAnswer : *attrH323CallOriginOriginate
			);
		*pdu += new RadiusAttr(*attrH323CallType);
		*pdu += new RadiusAttr(*attrH323GwId);
	}
					
	// send the request and wait for a response
	RadiusPDU* response = NULL;
	bool result = OnSendPDU(*pdu, arq, rejectReason) 
		&& radiusClient->MakeRequest(*pdu, response) && (response != NULL);
			
	delete pdu;
			
	if (!result) {
		delete response;
		return defaultStatus;
	}
				
	// authenticated?
	result = (response->GetCode() == RadiusPDU::AccessAccept);
	if (result) {
		const PINDEX index = response->FindVsaAttr(9, 103);
		if (index != P_MAX_INDEX) {
			const RadiusAttr* attr = response->GetAttrAt(index);
			bool valid = false;

			if (attr && attr->IsValid()) {
				PString s = attr->AsVsaString();
				if (s.Find("h323-return-code=") == 0)
					s = s.Mid(s.FindOneOf("=") + 1);
				if (s.GetLength() > 0
					&& strspn((const char*)s, "0123456789") == (size_t)s.GetLength()) {
					const unsigned retcode = s.AsUnsigned();
					if (retcode != 0) {
						PTRACE(5, "RADAUTH\t" << GetName() << " ARQ check failed "
							"- return code " << retcode
							);
						result = false;
					}
					valid = true;
				}
			}
		
			if (!valid) {
				PTRACE(5, "RADAUTH\t" << GetName() << " check failed - "
					"invalid h323-return-code attribute"
					);
				result = false;
			}
		}
	}
				
	if (result) {
		bool found = false;
			
		const PINDEX index = response->FindVsaAttr(9, 102);
		if (index != P_MAX_INDEX) {
			const RadiusAttr* attr = response->GetAttrAt(index);
			if (attr && attr->IsValid()) {
				PString s = attr->AsVsaString();
				if (s.Find("h323-credit-time=") == 0)
					s = s.Mid(s.FindOneOf("=") + 1);
				if (s.GetLength() > 0
					&& strspn((const char*)s, "0123456789") == (size_t)s.GetLength()) {
					found = true;
					callDurationLimit = s.AsInteger();
					PTRACE(5, "RADAUTH\t" << GetName() << " ARQ check set "
						"duration limit set: " << callDurationLimit
						);
					if (callDurationLimit == 0)
						result = false;
				}
			}
			
			if (!found) {
				PTRACE(5, "RADAUTH\t" << GetName() << " ARQ check failed - "
					"invalid h323-credit-time attribute"
					);
				result = false;
			}
		}
	}
			
	if (result) {
		bool found = false;
		const PINDEX index = response->FindAttr(RadiusAttr::SessionTimeout);
		if (index != P_MAX_INDEX) {
			const RadiusAttr* attr = response->GetAttrAt(index);
			if (attr && attr->IsValid()) {
				found = true;
				const long sessionTimeout = attr->AsInteger();
				if (callDurationLimit < 0 || callDurationLimit > sessionTimeout) {
					callDurationLimit = sessionTimeout;
					PTRACE(5, "RADAUTH\t" << GetName() << " ARQ check set "
						"duration limit set: " << callDurationLimit
						);
				}
				if (callDurationLimit == 0)
					result = false;
			}
		
			if (!found) {
				PTRACE(5, "RADAUTH\t" << GetName() << " ARQ check failed - "
					"invalid Session-Timeout attribute"
					);
				result = false;
			}
		}
	}
	
	if (result)
		result = OnReceivedPDU(*response, arq, rejectReason, callDurationLimit);
	else
		rejectReason = H225_AdmissionRejectReason::e_securityDenial;
				
	delete response;
	return result ? e_ok : e_fail;
}

bool RadAuth::OnSendPDU(
	RadiusPDU& /*pdu*/,
	const H225_RegistrationRequest& /*rrq*/,
	unsigned& /*rejectReason*/
	)
{
	return true;
}

bool RadAuth::OnSendPDU(
	RadiusPDU& /*pdu*/,
	const H225_AdmissionRequest& /*arq*/,
	unsigned& /*rejectReason*/
	)
{
	return true;
}

bool RadAuth::OnReceivedPDU(
	RadiusPDU& /*pdu*/,
	H225_RegistrationRequest& /*rrq*/,
	unsigned& /*rejectReason*/
	)
{
	return true;
}

bool RadAuth::OnReceivedPDU(
	RadiusPDU& /*pdu*/,
	H225_AdmissionRequest& /*arq*/,
	unsigned& /*rejectReason*/,
	long& /*durationLimit*/
	)
{
	return true;
}


RadAliasAuth::RadAliasAuth( 
	PConfig* cfg, 
	const char* authName 
	)
	:
	GkAuthenticator( cfg, authName ),
	radiusServers(cfg->GetString(
		RadAliasAuthConfigSectionName,"Servers",""
		).Tokenise( ";, |\t", FALSE )),
	sharedSecret(cfg->GetString(
		RadAliasAuthConfigSectionName,"SharedSecret",""
		)),
	authPort((WORD)(cfg->GetInteger(
		RadAliasAuthConfigSectionName,"DefaultAuthPort"
		))),
	portBase(1024),
	portMax(65535),
	requestTimeout(cfg->GetInteger(
		RadAliasAuthConfigSectionName,"RequestTimeout"
		)),
	idCacheTimeout(cfg->GetInteger(
		RadAliasAuthConfigSectionName,"IdCacheTimeout"
		)),
	socketDeleteTimeout(cfg->GetInteger(
		RadAliasAuthConfigSectionName,"SocketDeleteTimeout"
		)),
	numRequestRetransmissions(cfg->GetInteger(
		RadAliasAuthConfigSectionName,"RequestRetransmissions"
		)),
	roundRobin(cfg->GetBoolean(
		RadAliasAuthConfigSectionName,"RoundRobinServers", TRUE
		)),
	appendCiscoAttributes(cfg->GetBoolean(
		RadAliasAuthConfigSectionName,"AppendCiscoAttributes", TRUE
		)),
	includeTerminalAliases(cfg->GetBoolean(
		RadAliasAuthConfigSectionName,"IncludeTerminalAliases", TRUE
		)),
	localInterface(cfg->GetString(
		RadAliasAuthConfigSectionName, "LocalInterface", ""
		)),
	fixedUsername(cfg->GetString(
		RadAliasAuthConfigSectionName, "FixedUsername", ""
		)),
	fixedPassword(cfg->GetString(
		RadAliasAuthConfigSectionName, "FixedPassword", ""
		)),
	radiusClient(NULL),
	attrH323GwId(NULL), attrH323CallType(NULL),
	attrH323CallOriginOriginate(NULL), attrH323CallOriginAnswer(NULL),
	attrNASIdentifier(NULL)
{
	PAssert(radiusServers.GetSize() > 0, "Cannot build RADIUS Alias "
		"authenticator - no RADIUS servers specified in the config"
		);

	if (!localInterface && !PIPSocket::IsLocalHost(localInterface)) {
		PTRACE(1, "RADAUTH\tSpecified local interface - " << localInterface
			<<" - does not belong to this machine"
			);
		localInterface = PString();
	}
	/// build RADIUS client
	radiusClient = new RadiusClient(radiusServers[0],
		radiusServers.GetSize() > 1 ? radiusServers[1] : PString(),
		localInterface
		);

	/// if there were specified more than two RADIUS servers, append them
	for (PINDEX i = 2; i < radiusServers.GetSize(); i++)
		radiusClient->AppendServer(radiusServers[i]);	
		
	radiusClient->SetSharedSecret(sharedSecret);
	radiusClient->SetRoundRobinServers(roundRobin);
		
	if (authPort > 0)
		radiusClient->SetAuthPort(authPort);
		
	if (requestTimeout > 0)
		radiusClient->SetRequestTimeout(requestTimeout);
	if (idCacheTimeout > 0)
		radiusClient->SetIdCacheTimeout(idCacheTimeout);
	if (socketDeleteTimeout > 0)
		radiusClient->SetSocketDeleteTimeout(socketDeleteTimeout);
	if (numRequestRetransmissions > 0)
		radiusClient->SetRetryCount(numRequestRetransmissions);
	
	const PStringArray s = cfg->GetString(
		RadAliasAuthConfigSectionName, "RadiusPortRange", ""
		).Tokenise("-");

	// parse port range		
	if (s.GetSize() >= 2) { 
		unsigned p1 = s[0].AsUnsigned();
		unsigned p2 = s[1].AsUnsigned();
	
		// swap if base is greater than max
		if (p2 < p1) {
			const unsigned temp = p1;
			p1 = p2;
			p2 = temp;
		}
		
		if (p1 > 65535)
			p1 = 65535;
		if (p2 > 65535)
			p2 = 65535;
	
		if (p1 > 0 && p2 > 0) {
			portBase = (WORD)p1;
			portMax = (WORD)p2;
		}
	}
	
	if (localInterface.IsEmpty())
		localInterfaceAddr = Toolkit::Instance()->GetRouteTable()->GetLocalAddress();
	else
		localInterfaceAddr = PIPSocket::Address(localInterface);

	NASIdentifier = Toolkit::Instance()->GKName();
	
	radiusClient->SetClientPortRange(portBase, (WORD)(portMax-portBase+1));

	attrH323GwId = new RadiusAttr(PString("h323-gw-id=") + NASIdentifier,
		9 /* Cisco */, 33 /* h323-gw-id */
		);
	attrH323CallType = new RadiusAttr(PString("h323-call-type=VoIP"),
		9 /* Cisco */, 27 /* h323-call-type */
		);
	attrH323CallOriginOriginate = new RadiusAttr(
		PString("h323-call-origin=originate"), 
		9 /* Cisco */, 26 /* h323-call-origin */
		);
	attrH323CallOriginAnswer = new RadiusAttr(
		PString("h323-call-origin=answer"),
		9 /* Cisco */, 26 /* h323-call-origin */
		);
	attrNASIdentifier = new RadiusAttr(RadiusAttr::NasIdentifier, NASIdentifier);
}

RadAliasAuth::~RadAliasAuth()
{
	delete radiusClient;
	delete attrH323GwId;
	delete attrH323CallType;
	delete attrH323CallOriginOriginate;
	delete attrH323CallOriginAnswer;
	delete attrNASIdentifier;
}

int RadAliasAuth::Check(
	H225_RegistrationRequest& rrq, 
	unsigned& rejectReason
	)
{
	PAssertNULL(radiusClient);

	PIPSocket::Address framedIP;
	bool addrValid = false;
	
	if (rrq.m_callSignalAddress.GetSize() > 0)
		addrValid = GetIPFromTransportAddr(rrq.m_callSignalAddress[0], framedIP)
			&& framedIP.IsValid();

	if (!addrValid && rrq.m_rasAddress.GetSize() > 0)
		addrValid = GetIPFromTransportAddr(rrq.m_rasAddress[0], framedIP) 
			&& framedIP.IsValid();

	if (!addrValid) {
		PTRACE(3, "RADAUTH\tRRQ AliasAuth failed - could not determine Framed-IP-Address");
		rejectReason = H225_RegistrationRejectReason::e_invalidCallSignalAddress;
		return defaultStatus;
	}

	const PString username = GetUsername(rrq);

	if (username.IsEmpty() && fixedUsername.IsEmpty()) {
		PTRACE(3, "RADAUTH\tRRQ AliasAuth failed - neither FixedUsername"
			" nor alias inside RRQ were found"
			);
		rejectReason = H225_RegistrationRejectReason::e_securityDenial;
		return defaultStatus;
	}

	// build RADIUS Access-Request
	RadiusPDU* pdu = radiusClient->BuildPDU();
	if (pdu == NULL) {
		PTRACE(3, "RADAUTH\tRRQ AliasAuth failed - could not to create Access-Request PDU");
		rejectReason = H225_RegistrationRejectReason::e_undefinedReason;
		return defaultStatus;
	}

	pdu->SetCode(RadiusPDU::AccessRequest);
	
	// append User-Name
    *pdu += new RadiusAttr(RadiusAttr::UserName, 
		fixedUsername.IsEmpty() ? username : fixedUsername 
		);
	*pdu += new RadiusAttr(RadiusAttr::UserPassword, 
		fixedPassword.IsEmpty() 
			? (fixedUsername.IsEmpty() ? username : fixedUsername)
			: fixedPassword  
			);
			
	// Gk works as NAS point, so append NAS IP
	*pdu += new RadiusAttr(RadiusAttr::NasIpAddress, localInterfaceAddr);
	// NAS-Identifier as Gk name
	*pdu += new RadiusAttr(*attrNASIdentifier);
	*pdu += new RadiusAttr(RadiusAttr::NasPortType, RadiusAttr::NasPort_Virtual);
	*pdu += new RadiusAttr(RadiusAttr::ServiceType, RadiusAttr::ST_Login);
	*pdu += new RadiusAttr(RadiusAttr::FramedIpAddress,	framedIP);

	if (appendCiscoAttributes && includeTerminalAliases
		&& rrq.HasOptionalField(H225_RegistrationRequest::e_terminalAlias)) {
		PString aliasList( "terminal-alias:" );
		for (PINDEX i = 0; i < rrq.m_terminalAlias.GetSize(); i++)	{
			if (i > 0)
				aliasList += ",";
			aliasList += H323GetAliasAddressString(rrq.m_terminalAlias[i]);
		}
		*pdu += new RadiusAttr(
			PString("h323-ivr-out=") + aliasList + PString(";"),
			9 /* Cisco */, 1 /* Cisco-AV-Pair */
			);
	}
					
	// send request and wait for response
	RadiusPDU* response = NULL;
	bool result = OnSendPDU(*pdu, rrq, rejectReason)
		&& radiusClient->MakeRequest(*pdu, response) && (response != NULL);
			
	delete pdu;
			
	if (!result) {
		delete response;
		PTRACE(3, "RADAUTH\tRRQ AliasAuth failed - could not send Access-Request PDU");
		return defaultStatus;
	}
				
	result = (response->GetCode() == RadiusPDU::AccessAccept);
	
	// process h323-ivr-in=terminal-alias attribute
	if (result) {
		PINDEX index = response->FindVsaAttr(9, 1);
		bool found = false;
		while (index != P_MAX_INDEX && !found) {
			PString h323ivrin;
			const RadiusAttr* attr = response->GetAttrAt(index);
			if (attr && attr->IsValid())
				h323ivrin = attr->AsVsaString();
			if (h323ivrin.Find("h323-ivr-in=") == 0 
				&& (index = h323ivrin.Find("terminal-alias:")) != P_MAX_INDEX) {
				found = true;
				index += strlen("terminal-alias:");
				const PINDEX semicolonpos = h323ivrin.Find(';', index);
				h323ivrin = h323ivrin.Mid(
					index, semicolonpos == P_MAX_INDEX
						? P_MAX_INDEX : (semicolonpos-index)
					);
				PStringArray aliases = h323ivrin.Tokenise(",");
				if (aliases.GetSize() > 0 
					&& rrq.HasOptionalField(H225_RegistrationRequest::e_terminalAlias)) {
					PINDEX i = 0;
					while (i < rrq.m_terminalAlias.GetSize()) {
						PINDEX j = aliases.GetStringsIndex(H323GetAliasAddressString(rrq.m_terminalAlias[i]));
						if (j == P_MAX_INDEX)
							rrq.m_terminalAlias.RemoveAt(i);
						else {
							i++;
							aliases.RemoveAt(j);
						}
					}
				}
				for (PINDEX i = 0; i < aliases.GetSize(); i++) {
					if (rrq.HasOptionalField(H225_RegistrationRequest::e_terminalAlias))
						rrq.m_terminalAlias.SetSize(rrq.m_terminalAlias.GetSize()+1);
					else {
						rrq.IncludeOptionalField(H225_RegistrationRequest::e_terminalAlias);
						rrq.m_terminalAlias.SetSize(1);
					}
					H323SetAliasAddress(aliases[i], rrq.m_terminalAlias[rrq.m_terminalAlias.GetSize()-1]);
				}
			}
			if (!found)
				index = response->FindVsaAttr(9, 1, index + 1);
		}
	}
	
	if (result)
		result = OnReceivedPDU(*response, rrq, rejectReason);
	else
		rejectReason = H225_RegistrationRejectReason::e_securityDenial;
					
	delete response;
	return result ? e_ok : e_fail;
}

int RadAliasAuth::Check(
	H225_AdmissionRequest& arq, 
	unsigned& rejectReason,
	long& callDurationLimit
	)
{
	PAssertNULL(radiusClient);

	endptr ep = RegistrationTable::Instance()->FindByEndpointId(
		arq.m_endpointIdentifier
		);
	callptr call;
	if (arq.m_answerCall)
		if (arq.HasOptionalField(arq.e_callIdentifier))
			call = CallTable::Instance()->FindCallRec(arq.m_callIdentifier);
		else {
			H225_CallReferenceValue crv;
			crv.SetValue((WORD)arq.m_callReferenceValue & 0x7fffu);
			call = CallTable::Instance()->FindCallRec(crv);
		}
	
	const PString username = GetUsername(call, arq, ep);

	if (username.IsEmpty() && fixedUsername.IsEmpty()) {
		PTRACE(3, "RADAUTH\tARQ AliasAuth failed - neither FixedUsername"
			" nor alias inside ARQ were found"
			);
		rejectReason = H225_AdmissionRejectReason::e_securityDenial;
		return defaultStatus;
	}

	// build RADIUS Access-Request packet
	RadiusPDU* pdu = radiusClient->BuildPDU();
	if (pdu == NULL) {
		PTRACE(3, "RADAUTH\tARQ AliasAuth failed - could not to create Access-Request PDU");
		return defaultStatus;
	}

	pdu->SetCode(RadiusPDU::AccessRequest);
	
	// append User-Name
    *pdu += new RadiusAttr(RadiusAttr::UserName, 
		fixedUsername.IsEmpty() ? username : fixedUsername 
		);
		
	*pdu += new RadiusAttr(RadiusAttr::UserPassword, 
		fixedPassword.IsEmpty() 
			? (fixedUsername.IsEmpty() ? username : fixedUsername)
			: fixedPassword  
			);
			
	// Gk works as NAS point, so append NAS IP
	*pdu += new RadiusAttr(RadiusAttr::NasIpAddress, localInterfaceAddr);
	// NAS-Identifier as Gk name
	*pdu += new RadiusAttr(*attrNASIdentifier);
	*pdu += new RadiusAttr(RadiusAttr::NasPortType, RadiusAttr::NasPort_Virtual);
	// Service-Type is Login-User if originating the call
	// and Call Check if answering the call
	*pdu += new RadiusAttr(RadiusAttr::ServiceType,
		arq.m_answerCall ? RadiusAttr::ST_CallCheck : RadiusAttr::ST_Login
		);

	PIPSocket::Address framedIP;
	bool addrValid = false;
					
	if (arq.HasOptionalField(arq.m_answerCall 
		? arq.e_destCallSignalAddress : arq.e_srcCallSignalAddress))
		addrValid = (arq.m_answerCall 
				? GetIPFromTransportAddr(arq.m_destCallSignalAddress, framedIP)
				: GetIPFromTransportAddr(arq.m_srcCallSignalAddress, framedIP))
			&& framedIP.IsValid();
	
	if (!addrValid && ep)
		addrValid = GetIPFromTransportAddr(ep->GetCallSignalAddress(), framedIP) 
			&& framedIP.IsValid();

	if (!addrValid) {
		PTRACE(3, "RADAUTH\tARQ check failed - could not determine Framed-IP-Address");
		rejectReason = H225_AdmissionRejectReason::e_securityDenial;
		delete pdu;
		return defaultStatus;
	}
					
	*pdu += new RadiusAttr(RadiusAttr::FramedIpAddress, framedIP);
	
	// build Calling-Station-Id and Called-Station-Id 
	PString stationId = GetCallingStationId(call, arq, ep);

	if (stationId.IsEmpty()) {
		PTRACE(3, "RADAUTH\t" << GetName() << " ARQ check failed - "
			"could not determine Calling-Station-Id"
			);
		delete pdu;
		return e_fail;
	}
	
	*pdu += new RadiusAttr(RadiusAttr::CallingStationId, stationId);
						
	// Called-Station-Id
	stationId = GetCalledStationId(call, arq, ep);

	if (stationId.IsEmpty()) {
		delete pdu;
		PTRACE(3, "RADAUTH\t" << GetName() << " ARQ check failed - "
			"could not determine Called-Station-Id"
			);
		return e_fail;
	}
				
	*pdu += new RadiusAttr(RadiusAttr::CalledStationId, stationId);
			
	if (appendCiscoAttributes) {
		*pdu += new RadiusAttr(
			PString("h323-conf-id=") + GetGUIDString(arq.m_conferenceID),
			9 /* Cisco */, 24 /* h323-conf-id */
			);
		*pdu += new RadiusAttr(arq.m_answerCall 
			? *attrH323CallOriginAnswer : *attrH323CallOriginOriginate 
			);
		*pdu += new RadiusAttr(*attrH323CallType);
		*pdu += new RadiusAttr(*attrH323GwId);
	}
					
	// send the request and wait for a response
	RadiusPDU* response = NULL;
	bool result = OnSendPDU(*pdu, arq, rejectReason) 
		&& radiusClient->MakeRequest(*pdu, response) 
		&& (response != NULL);
			
	delete pdu;
			
	if (!result) {
		delete response;
		return defaultStatus;
	}
				
	// authenticated?
	result = (response->GetCode() == RadiusPDU::AccessAccept);
	if (result) {
		const PINDEX index = response->FindVsaAttr( 9, 103 );
		if( index != P_MAX_INDEX ) {
			const RadiusAttr* attr = response->GetAttrAt(index);
			BOOL valid = FALSE;
		
			if( attr && attr->IsValid() ) {
				PString s = attr->AsVsaString();
				if( s.Find("h323-return-code=") == 0 )
					s = s.Mid( s.FindOneOf("=") + 1 );
				if( s.GetLength() > 0
					&& strspn((const char*)s,"0123456789") == (size_t)s.GetLength() ) {
					unsigned retcode = s.AsUnsigned();
					if( retcode != 0 ) {
						PTRACE(5,"RADAUTH\t"<<GetName()<<" ARQ check failed - return code "<<retcode);
						result = FALSE;
					}
					valid = TRUE;
				}
			}
		
			if( !valid ) {
				PTRACE(5,"RADAUTH\t"<<GetName()<<" check failed - invalid h323-return-code attribute");
				result = FALSE;
			}
		}
	}
	
	if( result ) {
		BOOL found = FALSE;
		
		const PINDEX index = response->FindVsaAttr( 9, 102 );
		if( index != P_MAX_INDEX ) {
			const RadiusAttr* attr = response->GetAttrAt(index);
			if( attr && attr->IsValid() ) {
				PString s = attr->AsVsaString();
				if( s.Find("h323-credit-time=") == 0 )
					s = s.Mid( s.FindOneOf("=") + 1 );
				if( s.GetLength() > 0
					&& strspn((const char*)s,"0123456789") == (size_t)s.GetLength() ) {
					found = TRUE;
					callDurationLimit = s.AsInteger();
					PTRACE(5,"RADAUTH\t"<<GetName()<<" ARQ check set duration limit set: "<<callDurationLimit);
					if( callDurationLimit == 0 )
						result = FALSE;
				}
			}
			
			if( !found ) {
				PTRACE(5,"RADAUTH\t"<<GetName()<<" ARQ check failed - invalid h323-credit-time attribute: ");
				result = FALSE;
			}
		}
	}
	
	if( result ) {
		BOOL found = FALSE;
		
		const PINDEX index = response->FindAttr( RadiusAttr::SessionTimeout );
		if( index != P_MAX_INDEX ) {
			const RadiusAttr* attr = response->GetAttrAt(index);
			if( attr && attr->IsValid() ) {
				found = TRUE;
				const long sessionTimeout = attr->AsInteger();
				if( (callDurationLimit < 0) || (callDurationLimit > sessionTimeout) )
				{
					callDurationLimit = sessionTimeout;
					PTRACE(5,"RADAUTH\t"<<GetName()<<" ARQ check set duration limit set: "<<callDurationLimit);
				}
				if( callDurationLimit == 0 )
					result = FALSE;
			}
			
			if( !found ) {
				PTRACE(5,"RADAUTH\t"<<GetName()<<" ARQ check failed - invalid Session-Timeout attribute");
				result = FALSE;
			}
		}
	}
	
	if (result)
		result = OnReceivedPDU(*response, arq, rejectReason, callDurationLimit);
	else
		rejectReason = H225_AdmissionRejectReason::e_securityDenial;
					
	delete response;
	return result ? e_ok : e_fail;
}

int RadAliasAuth::Check(
	Q931& q931pdu,
	H225_Setup_UUIE& setup, 
	callptr& call,
	unsigned& releaseCompleteCause,
	long& durationLimit
	)
{
	PAssertNULL(radiusClient);

	CallRec* callrec = call.operator->();
	callptr tempcall;
	
	if (!callrec && setup.HasOptionalField(H225_Setup_UUIE::e_callIdentifier)) {
		tempcall = CallTable::Instance()->FindCallRec(setup.m_callIdentifier);
		callrec = tempcall.operator->();
	}
	
	endptr callingEP;

	if (callrec)
		callingEP = callrec->GetCallingParty();

	if (!callingEP && setup.HasOptionalField(H225_Setup_UUIE::e_endpointIdentifier))
		callingEP = RegistrationTable::Instance()->FindByEndpointId( 
				setup.m_endpointIdentifier
				);

	// get the IP address for Framed-IP-Address attribute
	PIPSocket::Address framedIP(0);
	WORD framedPort = 0;
	bool addrValid = false;
	
	if (callrec && callrec->HasSrcSignalAddr())
		addrValid = callrec->GetSrcSignalAddr(framedIP, framedPort)
			&& framedIP.IsValid();
			
	if (!addrValid && setup.HasOptionalField(setup.e_sourceCallSignalAddress))
		addrValid = GetIPFromTransportAddr(setup.m_sourceCallSignalAddress, framedIP)
			&& framedIP.IsValid();

	if (!addrValid && callingEP)
		addrValid = GetIPFromTransportAddr(callingEP->GetCallSignalAddress(), framedIP)
			&& framedIP.IsValid();

	if (!addrValid) {		 
		PTRACE(2, "RADAUTH\t" << GetName() << " Setup check could not "
			"determine Framed-IP-Address"
			);
		releaseCompleteCause = Q931::CallRejected;
		return defaultStatus;
	}
	
	// get the username for User-Name attribute		
	const PString username = GetUsername(call, q931pdu, setup);

	if (username.IsEmpty() && fixedUsername.IsEmpty()) {
		PTRACE(2,"RADAUTH\t"<<GetName()<<" Setup check failed - neither FixedUsername"
			" nor alias and IP address inside Setup were found"
			);
		releaseCompleteCause = Q931::CallRejected;
		return defaultStatus;
	}

	// build RADIUS Access-Request
	RadiusPDU* pdu = radiusClient->BuildPDU();
	if (pdu == NULL) {
		PTRACE(3, "RADAUTH\t" << GetName() << " Setup check failed - "
			"could not to create Access-Request PDU"
			);
		releaseCompleteCause = Q931::TemporaryFailure;
		return defaultStatus;
	}

	pdu->SetCode(RadiusPDU::AccessRequest);
	
	// append User-Name
    *pdu += new RadiusAttr(RadiusAttr::UserName, 
		fixedUsername.IsEmpty() ? username : fixedUsername 
		);
		
	*pdu += new RadiusAttr(RadiusAttr::UserPassword, 
		fixedPassword.IsEmpty() 
			? (fixedUsername.IsEmpty() ? username : fixedUsername)
			: fixedPassword  
			);
			
	// Gk works as NAS point, so append NAS IP
	*pdu += new RadiusAttr(RadiusAttr::NasIpAddress, localInterfaceAddr);
	// NAS-Identifier as Gk name
	*pdu += new RadiusAttr(*attrNASIdentifier);
	*pdu += new RadiusAttr(RadiusAttr::NasPortType, RadiusAttr::NasPort_Virtual);
	// Service-Type is Login-User if originating the call
	*pdu += new RadiusAttr(RadiusAttr::ServiceType, RadiusAttr::ST_Login);
	*pdu += new RadiusAttr(RadiusAttr::FramedIpAddress, framedIP);
	
	PString stationId = GetCallingStationId(call, q931pdu, setup);
	
	if (stationId.IsEmpty()) {
		PTRACE(3,"RADAUTH\t"<<GetName()<<" Setup check failed - no calling station id found");
		releaseCompleteCause = Q931::CallRejected;
		delete pdu;
		return e_fail;
	}
	
#ifdef HAS_ACCT		
	if (callrec && callrec->GetCallingStationId().IsEmpty())
		callrec->SetCallingStationId(stationId);
#endif

	*pdu += new RadiusAttr(RadiusAttr::CallingStationId, stationId);

	stationId = GetCalledStationId(call, q931pdu, setup);
	
	if (stationId.IsEmpty()) {
		PTRACE(3,"RADAUTH\t"<<GetName()<<" Setup check failed - no called station id found");
		releaseCompleteCause = Q931::CallRejected;
		delete pdu;
		return e_fail;
	}

#ifdef HAS_ACCT	
	if (callrec && callrec->GetCalledStationId().IsEmpty())
		callrec->SetCalledStationId(stationId);
#endif

	*pdu += new RadiusAttr(RadiusAttr::CalledStationId, stationId);
	
	if (appendCiscoAttributes) {
		*pdu += new RadiusAttr(
			PString("h323-conf-id=") + GetGUIDString(setup.m_conferenceID),
			9 /* Cisco */, 24 /* h323-conf-id */
			);
		*pdu += new RadiusAttr(*attrH323CallOriginOriginate);
		*pdu += new RadiusAttr(*attrH323CallType);
		*pdu += new RadiusAttr(*attrH323GwId);
	}
					
	// send the request and wait for a response
	RadiusPDU* response = NULL;
	bool result = OnSendPDU(*pdu, q931pdu, setup, releaseCompleteCause, durationLimit) 
		&& radiusClient->MakeRequest(*pdu, response) && (response != NULL);
			
	delete pdu;
			
	if( !result ) {
		delete response;
		PTRACE(3, "RADAUTH\t" << GetName() << " Setup check failed - "
			"could not send Access-Request PDU"
			);
		releaseCompleteCause = Q931::TemporaryFailure;
		return defaultStatus;
	}
				
	// authenticated?
	result = (response->GetCode() == RadiusPDU::AccessAccept);
	
	// process h323-return-code reply attribute
	if( result ) {
		const PINDEX index = response->FindVsaAttr( 9, 103 );
		if( index != P_MAX_INDEX ) {
			const RadiusAttr* attr = response->GetAttrAt(index);
			BOOL valid = FALSE;
		
			if( attr && attr->IsValid() ) {
				PString s = attr->AsVsaString();
				if( s.Find("h323-return-code=") == 0 )
					s = s.Mid( s.FindOneOf("=") + 1 );
				if( s.GetLength() > 0
					&& strspn((const char*)s,"0123456789") == (size_t)s.GetLength() ) {
					unsigned retcode = s.AsUnsigned();
					if( retcode != 0 ) {
						PTRACE(5,"RADAUTH\t"<<GetName()<<" Setup check failed - return code "<<retcode);
						result = FALSE;
					}
					valid = TRUE;
				}
			}
		
			if( !valid ) {
				PTRACE(5,"RADAUTH\t"<<GetName()<<" check failed - invalid h323-return-code attribute");
				result = FALSE;
			}
		}
	}

	// process h323-credit-time attribute	
	if( result ) {
		BOOL found = FALSE;
		
		const PINDEX index = response->FindVsaAttr( 9, 102 );
		if( index != P_MAX_INDEX ) {
			const RadiusAttr* attr = response->GetAttrAt(index);
			if( attr && attr->IsValid() ) {
				PString s = attr->AsVsaString();
				if( s.Find("h323-credit-time=") == 0 ) 
					s = s.Mid( s.FindOneOf("=") + 1 );
				if( s.GetLength() > 0
					&& strspn((const char*)s,"0123456789") == (size_t)s.GetLength() ) {
					found = TRUE;
					durationLimit = s.AsInteger();
					PTRACE(5,"RADAUTH\t"<<GetName()<<" Setup check set duration limit set: "<<durationLimit);
					if( durationLimit == 0 )
						result = FALSE;
				}
			}
			
			if( !found ) {
				PTRACE(5,"RADAUTH\t"<<GetName()<<" Setup check failed - invalid h323-credit-time attribute: ");
				result = FALSE;
			}
		}
	}

	// process Session-Timeout attribute	
	if( result ) {
		BOOL found = FALSE;
		
		const PINDEX index = response->FindAttr( RadiusAttr::SessionTimeout );
		if( index != P_MAX_INDEX ) {
			const RadiusAttr* attr = response->GetAttrAt(index);
			if( attr && attr->IsValid() ) {
				found = TRUE;
				const long sessionTimeout = attr->AsInteger();
				if( (durationLimit < 0) || (durationLimit > sessionTimeout) ) {
					durationLimit = sessionTimeout;
					PTRACE(5,"RADAUTH\t"<<GetName()<<" Setup check set duration limit set: "<<durationLimit);
				}
				if( durationLimit == 0 )
					result = FALSE;
			}
			
			if( !found ) {
				PTRACE(5,"RADAUTH\t"<<GetName()<<" Setup check failed - invalid Session-Timeout attribute");
				result = FALSE;
			}
		}
	}
	
	if (result)
		result = OnReceivedPDU(*response, q931pdu, setup, releaseCompleteCause, durationLimit);
	else
		releaseCompleteCause = Q931::CallRejected;
	
	delete response;
	return result ? e_ok : e_fail;
}

bool RadAliasAuth::OnSendPDU(
	RadiusPDU& /*pdu*/,
	const H225_RegistrationRequest& /*rrq*/,
	unsigned& /*rejectReason*/
	)
{
	return true;
}

bool RadAliasAuth::OnSendPDU(
	RadiusPDU& /*pdu*/,
	const H225_AdmissionRequest& /*rrq*/,
	unsigned& /*rejectReason*/
	)
{
	return true;
}

bool RadAliasAuth::OnSendPDU(
	RadiusPDU& /*pdu*/,
	const Q931& /*q931pdu*/,
	const H225_Setup_UUIE& /*setup*/,
	unsigned& /*releaseCompleteCause*/,
	long& /*durationLimit*/
	)
{
	return true;
}

bool RadAliasAuth::OnReceivedPDU(
	RadiusPDU& /*pdu*/,
	H225_RegistrationRequest& /*rrq*/,
	unsigned& /*rejectReason*/
	)
{
	return true;
}

bool RadAliasAuth::OnReceivedPDU(
	RadiusPDU& /*pdu*/,
	H225_AdmissionRequest& /*arq*/,
	unsigned& /*rejectReason*/,
	long& /*durationLimit*/
	)
{
	return true;
}

bool RadAliasAuth::OnReceivedPDU(
	RadiusPDU& /*pdu*/,
	Q931& /*q931pdu*/,
	H225_Setup_UUIE& /*setup*/,
	unsigned& /*releaseCompleteCause*/,
	long& /*durationLimit*/
	)
{
	return true;
}

#endif /* HAS_RADIUS */
/*
 * radauth.h
 *
 * RADIUS protocol authenticator modules for GNU Gatekeeper. 
 * H.235 based and alias based authentication schemes are supported.
 * Please see docs/radauth.txt for more details.
 *
 * Copyright (c) 2003, Quarcom FHU, Michal Zygmuntowicz
 *
 * This work is published under the GNU Public License (GPL)
 * see file COPYING for details.
 * We also explicitely grant the right to link this code
 * with the OpenH323 library.
 *
 * $Log: radauth.h,v $
 * Revision 1.1.2.21  2004/06/22 18:41:16  zvision
 * Username, Calling-Station-Id and Called-Station-Id handling rewritten.
 * Radius modules optimized.
 *
 * Revision 1.1.2.20  2004/06/18 15:42:51  zvision
 * Better User-Name and Calling-Station-Id handling for unregistered endpoints
 *
 * Revision 1.1.2.19  2004/05/12 17:46:40  zvision
 * Fixes CVS keyword substitution. New ParseEmailAliases config option.
 *
 * Revision 1.1.2.18  2004/05/12 14:00:47  zvision
 * Header file usage more consistent. Solaris std::map problems fixed.
 * Compilation warnings removed. VSNET2003 project files added. ANSI.h removed.
 *
 * Revision 1.1.2.17  2004/03/31 11:13:36  zvision
 * New CheckSetupUnregisteredOnly option for RadAliasAuth module
 *
 * Revision 1.1.2.16  2004/02/18 20:45:16  zvision
 * Prototype changed for GkAuthenticator::Check ARQ method
 *
 * Revision 1.1.2.15  2004/01/07 20:37:26  zvision
 * RRQ endpoint alias control (add/remove) through RadAuth/RadAliasAuth modules
 *
 * Revision 1.1.2.14  2003/12/21 12:20:38  zvision
 * Fixed conditional compilation
 *
 * Revision 1.1.2.13  2003/12/02 12:47:37  zvision
 * Q.931 Setup authenticator now returns (and documents) Q.931 cause values instead of H225_ReleaseCompleteReason
 *
 * Revision 1.1.2.12  2003/11/22 13:43:48  zvision
 * Q.931 Setup Check now accepts const callptr to prevent it from modifications
 *
 * Revision 1.1.2.11  2003/11/18 23:38:51  zvision
 * Q.931 Setup authentication optimized
 *
 * Revision 1.1.2.10  2003/09/30 11:08:16  zvision
 * Fixed non-working Q.931 authenticator due to previous Check(...) signature
 * changes. Many thanks to Oleg Ustinov!
 *
 * Revision 1.1.2.9  2003/09/24 10:19:44  zvision
 * Call duration limit for registered endpoints (through ARQ authenticators)
 * VS: ----------------------------------------------------------------------
 *
 * Revision 1.1.2.8  2003/08/25 12:18:35  zvision
 * Added h323-ivr-out attribute with an alias list (thanks Mark Lipscombe)
 *
 * Revision 1.1.2.7  2003/07/31 13:09:15  zvision
 * Added Q.931 Setup message authentication and call duration limit feature
 *
 * Revision 1.1.2.6  2003/07/07 12:02:55  zvision
 * Improved H.235 handling.
 *
 * Revision 1.1.2.5  2003/05/28 13:25:19  zvision
 * Added alias based authentication (RadAliasAuth)
 *
 * Revision 1.1.2.4  2003/05/26 23:08:18  zvision
 * New OnSend and OnReceive hooks.
 * LocalInterface config parameter Introduced.
 *
 * Revision 1.1.2.3  2003/05/13 17:48:43  zvision
 * Removed acctPort. New includeFramedIP feature.
 *
 * Revision 1.1.2.2  2003/04/29 14:56:26  zvision
 * Added H.235 capability matching
 *
 * Revision 1.1.2.1  2003/04/23 20:16:25  zvision
 * Initial revision
 *
 */
#ifndef RADAUTH_H
#define RADAUTH_H "#(@) $Id: radauth.h,v 1.1.2.21 2004/06/22 18:41:16 zvision Exp $"
#ifdef HAS_RADIUS

#include "gkauth.h"
#include "radproto.h"

/**
 * Gatekeeper authenticator module for RADIUS protocol.
 * Currently it supports user authentication through
 * CATs (Cisco Access Tokens) carried inside RRQ and ARQ
 * RAS messages. If your software does not support CATs,
 * please take a look at OpenH323 H235AuthCAT authenticator class
 * - it provides an implementation for CATs.
 */
class RadAuth : public GkAuthenticator 
{
public:
	/** Create GkAuthenticator for RADIUS protocol
	*/
	RadAuth( 
		/// authenticator settings
		PConfig* cfg, 
		/// authenticator name from Gatekeeper::Auth section
		const char* authName 
		);
		
	/// Destroy the authenticator
	virtual ~RadAuth();
	struct MemBlock {
	long callDuration;
	PString BalanceString;
};
	
protected:		
	/** Authenticate using data from RRQ RAS message.
	
		@return:
		#GkAuthenticator::Status enum# with the result of authentication.
	*/
	virtual int Check(
		/// RRQ RAS message to be authenticated
		H225_RegistrationRequest & rrq, 
		/// reference to the variable, that can be set 
		/// to custom H225_RegistrationRejectReason
		/// if the check fails
		unsigned& rejectReason,MemBlock *
		);
		
	/** Authenticate using data from ARQ RAS message.
	
		@return:
		#GkAuthenticator::Status enum# with the result of authentication.
	*/
	virtual int Check(
		/// ARQ nessage to be authenticated
		H225_AdmissionRequest &, 
		/// reference to the variable, that can be set 
		/// to custom H225_AdmissionRejectReason
		/// if the check fails
		unsigned& rejectReason,
		/// call duration limit to be set (-1 for no duration limit)
		long& callDurationLimit
		); 

	/** Hook for adding/modifying pdu before it is sent.
		It can be used to add custom attributes, for example.
		
		@return
		TRUE if PDU should be sent, FALSE to reject RRQ
		(rejectReason can be set to indicate a particular reason).
	*/
	virtual bool OnSendPDU(
		RadiusPDU& pdu, /// PDU to be sent
		const H225_RegistrationRequest& rrq, /// RRQ being processed
		unsigned& rejectReason /// reject reason on return FALSE
		);

	/** Hook for adding/modifying pdu before it is sent.
		It can be used to add custom attributes, for example.
		
		@return
		TRUE if PDU should be sent, FALSE to reject ARQ
		(rejectReason can be set to indicate a particular reason).
	*/
	virtual bool OnSendPDU(
		RadiusPDU& pdu, /// PDU to be sent
		const H225_AdmissionRequest& rrq, /// ARQ being processed
		unsigned& rejectReason /// reject reason on return FALSE
		);

	/** Hook for processing pdu after it is received.
		It can be used to process custom attributes, for example.
		
		@return
		TRUE if PDU should be accepted, FALSE to reject RRQ
		(rejectReason can be set to indicate a particular reason).
	*/
	virtual bool OnReceivedPDU(
		RadiusPDU& pdu, /// received PDU 
		H225_RegistrationRequest& rrq, /// RRQ being processed
		unsigned& rejectReason /// reject reason on return FALSE
		);

	/** Hook for processing pdu after it is received.
		It can be used to process custom attributes, for example.
		
		@return
		TRUE if PDU should be accepted, FALSE to reject ARQ
		(rejectReason can be set to indicate a particular reason).
	*/
	virtual bool OnReceivedPDU(
		RadiusPDU& pdu, /// PDU received
		H225_AdmissionRequest& arq, /// ARQ being processed
		unsigned& rejectReason, /// reject reason on return FALSE
		long& durationLimit /// call duration limit to set (-1 means no limit)
		);
		
private:
	/* No copy constructor allowed */
	RadAuth(const RadAuth&);
	/* No operator= allowed */
	RadAuth& operator=(const RadAuth&);
	
private:
	/// array of configured RADIUS server names
	PStringArray radiusServers;
	/// shared secret for gk client<->RADIUS server authorization
	PString sharedSecret;
	/// default port that will be used for sending RADIUS auth
	/// requests
	WORD authPort;
	/// base port number for UDP client socket allocation
	WORD portBase;
	/// max port number for UDP client socket allocation
	WORD portMax;
	/// timeout (ms) for a single RADIUS request
	unsigned requestTimeout;
	/// timeout (ms) for RADIUS requests IDs to be unique
	unsigned idCacheTimeout;
	/// timeout (ms) for unused sockets to be deleted
	unsigned socketDeleteTimeout;
	/// how many times to transmit a single request (1==no retransmission)
	/// to a single RADIUS server
	unsigned numRequestRetransmissions;
	/// retransmission fashion: 
	/// 	FALSE - do #numRequestRetransmissions# for server A,
	///				then do #numRequestRetransmissions# for server B, etc.
	///		TRUE - transmit request to server A, then to server B, etc.
	///				the whole procedure repeat #numRequestRetransmissions# times
	bool roundRobin;
	/// if TRUE Cisco VSAs are appended to the RADIUS packets
	bool appendCiscoAttributes;
	/// if TRUE an h323-ivr-out attribute will be sent with every alias
	/// found inside RRQ.m_terminalAlias
	bool includeTerminalAliases;
	/// Local interface RADIUS client should be bound to (multihomed hosts)
	PString localInterface;	
	/// IP address of the local interface
	PIPSocket::Address localInterfaceAddr;
	/// NAS identifier (GK name)
	PString NASIdentifier;
	/// RADIUS protocol client class associated with this authenticator
	RadiusClient* radiusClient;
	/// radius attributes that do not change - x4 performance boost
	RadiusAttr* attrH323GwId;
	RadiusAttr* attrH323CallType;
	RadiusAttr* attrH323CallOriginOriginate;
	RadiusAttr* attrH323CallOriginAnswer;
	RadiusAttr* attrNASIdentifier;
	/// OID (Object Identifier) for CAT alghoritm
	static PString OID_CAT;
};

/** RADIUS Alias Authentication module.
	It authenticates endpoints/calls using non-H.235 
	attributes (alias,address,etc).
*/
class RadAliasAuth : public GkAuthenticator 
{
public:
	/** Create GkAuthenticator for RADIUS Alias authenticator
	*/
	RadAliasAuth( 
		/// authenticator settings
		PConfig* cfg, 
		/// authenticator name from Gatekeeper::Auth section
		const char* authName 
		);
		
	/// Destroy the authenticator
	virtual ~RadAliasAuth();
	
protected:		
	/** Authenticate using data from RRQ RAS message.
	
		@return:
		#GkAuthenticator::Status enum# with the result of authentication.
	*/

	virtual int Check(
		/// RRQ RAS message to be authenticated
		H225_RegistrationRequest & rrq, 
		/// reference to the variable, that can be set 
		/// to custom H225_RegistrationRejectReason
		/// if the check fails
		unsigned& rejectReason
		);
		
	/** Authenticate using data from ARQ RAS message.
	
		@return:
		#GkAuthenticator::Status enum# with the result of authentication.
	*/
	virtual int Check(
		/// ARQ message to be authenticated
		H225_AdmissionRequest &, 
		/// reference to the variable, that can be set 
		/// to custom H225_AdmissionRejectReason
		/// if the check fails
		unsigned& rejectReason,
		/// call duration limit to be set (-1 for no duration limit)
		long& callDurationLimit
		); 

	/** Authenticate/Authorize Setup signalling message.
	
		@return
		e_fail - authentication failed
		e_ok - authenticated with this authenticator
		e_next - authentication could not be determined
	*/
	virtual int Check(
		/// received Q.931 Setup message
		Q931& q931pdu, 
		/// received H.225 Setup message
		H225_Setup_UUIE& setup, 
		/// CallRec for the call being authenticated
		callptr& call,
		/// Q.931 cause to set, if authentication failed
		unsigned& releaseCompleteCause, 
		/// call duration limit to set (-1 for no duration limit)
		long& durationLimit
		);

	/** Hook for adding/modifying pdu before it is sent.
		It can be used to add custom attributes, for example.
		
		@return
		TRUE if PDU should be sent, FALSE to reject RRQ
		(rejectReason can be set to indicate a particular reason).
	*/
	virtual bool OnSendPDU(
		RadiusPDU& pdu, /// PDU to be sent
		const H225_RegistrationRequest& rrq, /// RRQ being processed
		unsigned& rejectReason /// reject reason on return FALSE
		);

	/** Hook for adding/modifying pdu before it is sent.
		It can be used to add custom attributes, for example.
		
		@return
		TRUE if PDU should be sent, FALSE to reject ARQ
		(rejectReason can be set to indicate a particular reason).
	*/
	virtual bool OnSendPDU(
		RadiusPDU& pdu, /// PDU to be sent
		const H225_AdmissionRequest& rrq, /// ARQ being processed
		unsigned& rejectReason /// reject reason on return FALSE
		);

	/** Hook for adding/modifying pdu before it is sent.
		It can be used to add custom attributes, for example.
		
		@return
		TRUE if PDU should be sent, FALSE to reject Setup
		(releaseCompleteCode can be set to indicate a particular reason).
	*/
	virtual bool OnSendPDU(
		RadiusPDU& pdu, /// PDU to be sent
		const Q931& q931pdu, /// Q.931 Setup message being processed
		const H225_Setup_UUIE& setup, /// H.225 Setup message being processed
		unsigned& releaseCompleteCause, /// Q.931 cause on return FALSE
		long& durationLimit /// call duration limit to set (-1 means no limit)
		);
		
	/** Hook for processing pdu after it is received.
		It can be used to process custom attributes, for example.
		
		@return
		TRUE if PDU should be accepted, FALSE to reject RRQ
		(rejectReason can be set to indicate a particular reason).
	*/
	virtual bool OnReceivedPDU(
		RadiusPDU& pdu, /// received PDU 
		H225_RegistrationRequest& rrq, /// RRQ being processed
		unsigned& rejectReason /// reject reason on return FALSE
		);

	/** Hook for processing pdu after it is received.
		It can be used to process custom attributes, for example.
		
		@return
		TRUE if PDU should be accepted, FALSE to reject ARQ
		(rejectReason can be set to indicate a particular reason).
	*/
	virtual bool OnReceivedPDU(
		RadiusPDU& pdu, /// PDU received
		H225_AdmissionRequest& arq, /// ARQ being processed
		unsigned& rejectReason, /// reject reason on return FALSE
		long& durationLimit /// call duration limit to set (-1 means no limit)
		);
		
	/** Hook for processing pdu after it is received.
		It can be used to process custom attributes, for example.
		
		@return
		TRUE if PDU should be accepted, FALSE to reject Setup
		(releaseCompleteCode can be set to indicate a particular reason).
	*/
	virtual bool OnReceivedPDU(
		RadiusPDU& pdu, /// PDU received
		Q931& q931pdu, /// Q.931 Setup message being processed
		H225_Setup_UUIE& setup, /// H.225 Setup message being processed
		unsigned& releaseCompleteCause, /// Q.931 cause on return FALSE
		long& durationLimit /// call duration limit to set (-1 means no limit)
		);
		
private:
	/* No copy constructor allowed */
	RadAliasAuth(const RadAliasAuth&);
	/* No operator= allowed */
	RadAliasAuth& operator=(const RadAliasAuth&);
	
private:
	/// array of configured RADIUS server names
	PStringArray radiusServers;
	/// shared secret for gk client<->RADIUS server authorization
	PString sharedSecret;
	/// default port that will be used for sending RADIUS auth
	/// requests
	WORD authPort;
	/// base port number for UDP client socket allocation
	WORD portBase;
	/// max port number for UDP client socket allocation
	WORD portMax;
	/// timeout (ms) for a single RADIUS request
	unsigned requestTimeout;
	/// timeout (ms) for RADIUS requests IDs to be unique
	unsigned idCacheTimeout;
	/// timeout (ms) for unused sockets to be deleted
	unsigned socketDeleteTimeout;
	/// how many times to transmit a single request (1==no retransmission)
	/// 2to a single RADIUS server
	unsigned numRequestRetransmissions;
	/// retransmission fashion: 
	/// 	FALSE - do #numRequestRetransmissions# for server A,
	///				then do #numRequestRetransmissions# for server B, etc.
	///		TRUE - transmit request to server A, then to server B, etc.
	///				the whole procedure repeat #numRequestRetransmissions# times
	bool roundRobin;
	/// if TRUE Cisco VSAs are appended to the RADIUS packets
	bool appendCiscoAttributes;
	/// if TRUE an h323-ivr-out attribute will be sent with every alias
	/// found inside RRQ.m_terminalAlias
	bool includeTerminalAliases;
	/// check Q.931 Setup messages coming from unregistered endpoints only
	/// local interface RADIUS client should be bound to (multihomed hosts)
	PString localInterface;	
	/// IP address of the local interface
	PIPSocket::Address localInterfaceAddr;
	/// NAS identifier (GK name)
	PString NASIdentifier;
	/// fixed username to be send in RADIUS requests instead of alias
	PString fixedUsername;
	/// fixed password to be send in RADIUS requests instead of duplicating User-Name
	PString fixedPassword;
	/// RADIUS protocol client class associated with this authenticator
	RadiusClient* radiusClient;
	/// radius attributes that do not change - x4 performance boost
	RadiusAttr* attrH323GwId;
	RadiusAttr* attrH323CallType;
	RadiusAttr* attrH323CallOriginOriginate;
	RadiusAttr* attrH323CallOriginAnswer;
	RadiusAttr* attrNASIdentifier;
};

#endif /* HAS_RADIUS */
#endif /* RADAUTH_H */
//////////////////////////////////////////////////////////////////
//
// gkauth.cxx
//
// $Id
//
// This work is published under the GNU Public License (GPL)
// see file COPYING for details.
// We also explicitely grant the right to link this code
// with the OpenH323 library.
//
// History:
//      2003/10/23      LDAP support for Window (HAS_WLDAP) added (Franz J Ehrengruber)
//      2001/09/19      initial version (Chih-Wei Huang)
//
//////////////////////////////////////////////////////////////////

#if (_MSC_VER >= 1200)
#pragma warning( disable : 4786 ) // warning about too long debug symbol off
#pragma warning( disable : 4800 ) // warning about forcing value to bool
#endif

#include <ptlib.h>
#include <h323pdu.h>
#include <h225.h>
#include <h235.h>
#include <h235auth.h>
#include "gk_const.h"
#include "h323util.h"
#include "stl_supp.h"
#include "Toolkit.h"
#include "RasTbl.h"
#include "gksql.h"
#include "gkauth.h"

#if defined(HAS_LDAP)		// shall use LDAP (UNIX)
  #include "gkldap.h"
#endif
#if defined(HAS_WLDAP)		// shall use WLDAP (Windows)
  #include "gk_wldap.h"
#endif

namespace {
const char* const GkAuthSectionName = "Gatekeeper::Auth";
}

GkAuthenticator *GkAuthenticator::head = 0;
//////////////////////////////////////////////////////////////////////
// Definition of authentication rules

class AliasAuth : public GkAuthenticator {
public:
	AliasAuth(PConfig *, const char *);

protected:
	virtual int Check(const H225_GatekeeperRequest &, unsigned &);
	virtual int Check(H225_RegistrationRequest &, unsigned &);
	virtual int Check(const H225_UnregistrationRequest &, unsigned &);
	virtual int Check(H225_AdmissionRequest &, unsigned &);
	virtual int Check(const H225_BandwidthRequest &, unsigned &);
	virtual int Check(const H225_DisengageRequest &, unsigned &);
	virtual int Check(const H225_LocationRequest &, unsigned &);
	virtual int Check(const H225_InfoRequest &, unsigned &);

	virtual bool doCheck(const H225_ArrayOf_TransportAddress &, const PString &);
	virtual bool AuthCondition(const H225_TransportAddress & SignalAdr, const PString &);

private:
	/** @returns the value for a given alias from section #RasSrv::RRQAuth# 
	    in ini-file
	 */
	virtual PString GetConfigString(const PString & alias);
};

#if HAS_MYSQL

#include <mysql.h>

class MySQLPasswordAuth : public SimplePasswordAuth
{
public:
	MySQLPasswordAuth(
		PConfig* cfg, 
		const char* authName
		);
	virtual ~MySQLPasswordAuth();

protected:
	virtual PString GetPassword(
		const PString& alias
		);
		
protected:
	GkSQLConnection* m_sqlConn;
	PString m_query;
};

class MySQLAliasAuth : public AliasAuth
{
public:
	MySQLAliasAuth(
		PConfig* cfg, 
		const char* authName
		);
	
	virtual ~MySQLAliasAuth();

protected:
	virtual PString GetConfigString(
		const PString& alias
		);

protected:
	GkSQLConnection* m_sqlConn;
	CacheManager* m_cache;
	PString m_query;
};


#endif // HAS_MYSQL

/// Generic SQL authenticator for H.235 enabled endpoints
class SQLPasswordAuth : public SimplePasswordAuth
{
public:
	/// build authenticator reading settings from the config
	SQLPasswordAuth(
		/// config to read settings from
		PConfig* cfg,
		/// name for this authenticator and for the config section to read settings from
		const char* authName
		);
	
	virtual ~SQLPasswordAuth();

protected:
	/** Override from SimplePasswordAuth.
	
	    @return
	    Clear text password associated with the given alias
	    or an empty string if no password is found.
	*/
	virtual PString GetPassword(
		/// alias to check the password for
		const PString& alias
		);
		
protected:
	/// connection to the SQL database
	GkSQLConnection* m_sqlConn;
	/// parametrized query string for password retrieval
	PString m_query;
};

/// Generic SQL authenticator for alias/IP based authentication
class SQLAliasAuth : public AliasAuth
{
public:
	/// build authenticator reading settings from the config
	SQLAliasAuth(
		/// config to read settings from
		PConfig* cfg, 
		/// name for this authenticator and for the config section to read settings from
		const char* authName
		);
	
	virtual ~SQLAliasAuth();

protected:
	/** Override from AliasAuth.
	
	    @return
	    Auth condition string associated with the given alias
	    or an empty string if no match is found for the given alias.
	*/
	virtual PString GetConfigString(
		/// alias to retrieve the auth condition string for
		const PString& alias
		);

protected:
	/// connection to the SQL database
	GkSQLConnection* m_sqlConn;
	/// auth condition string cache
	CacheManager* m_cache;
	/// parametrized query string for the auth condition string retrieval
	PString m_query;
};


#if ((defined(__GNUC__) && __GNUC__ <= 2) && !defined(WIN32))
#include <unistd.h>
#include <procbuf.h>

class ExternalPasswordAuth : public SimplePasswordAuth {
public:
	ExternalPasswordAuth(PConfig *, const char *);

	virtual PString GetPassword(const PString &);
private:
	bool ExternalInit();

	PString Program;
};

namespace {
GkAuthInit<ExternalPasswordAuth> ExternalPasswordAuthFactory("ExternalPasswordAuth");
}

#endif

// LDAP authentification
#if defined(HAS_LDAP)		// shall use LDAP

class LDAPPasswordAuth : public SimplePasswordAuth {
public:
  LDAPPasswordAuth(PConfig *, const char *);
  virtual ~LDAPPasswordAuth();

  virtual PString GetPassword(const PString &alias);
  
  virtual int Check(H225_RegistrationRequest & rrq, unsigned & reason);
};

// ISO 14882:1998 (C++), ISO9899:1999 (C), ISO9945-1:1996 (POSIX) have a
// very clear oppinion regarding user symbols starting or ending with '_'

class LDAPAliasAuth : public AliasAuth {
public:
	LDAPAliasAuth(PConfig *, const char *);
	virtual ~LDAPAliasAuth();
	
	virtual int Check(H225_RegistrationRequest & rrq, unsigned &);

private:
	/** Searchs for an alias in LDAP and converts it to a valid config
	    string (the expected return value from LDAP is only an IP-address!).
	    @returns config-string (format: see description in ini-file)
	 */
	virtual PString GetConfigString(const PString &alias);
};


#endif // HAS_LDAP

#if defined HAS_WLDAP

class LDAPPasswordAuth : public SimplePasswordAuth {
public:
  LDAPPasswordAuth(PConfig *, const char *);
  virtual ~LDAPPasswordAuth();

  /** Searchs for an h323id/e164 alias in LDAP then:
	    1) mandatory: retrieves the H235Password attribute 
	    2) optional: retrieves the AccountStatus attribute and converts 
		   it to a valid config string. (format: see description in ini-file)
		3) optionally: retrieves the h323id/e164 alias attributes
	    @returns the H323Password attribute or an empty string;
  */
  virtual PString GetPassword(const PString &alias);
  
  /** calls SimplePasswordAuth::Check(rrq, reason) and then:
	    1) optionally converts AccountStatus attribute to a valid 
		   config string. (format: see description in ini-file) and 
		   calls doCheckAccountStatus(rrq.m_callSignalAddress, cfgString)
		   to verify the account status
		2) optionally: checks the supplied h323id/e164 alias are valid.
	    @returns (result) ? e_ok : e_fail;
  */
  virtual int Check(H225_RegistrationRequest & rrq, unsigned & reason);


private:

	// see above
	virtual bool doCheckAccountStatus(const H225_ArrayOf_TransportAddress & addrs, const PString & cfgString);
	
	// see above
	virtual bool AuthCondition(const H225_TransportAddress & SignalAdr, const PString & Condition);
	
	int attribute_count;
	PString epAccountStatus_value;
	PString epH323ID_value;
	PStringList epTelephoneNo_value;
	PString epH235Password_value;
};

// ISO 14882:1998 (C++), ISO9899:1999 (C), ISO9945-1:1996 (POSIX) have a
// very clear oppinion regarding user symbols starting or ending with '_'

class LDAPAliasAuth : public AliasAuth {
public:
	LDAPAliasAuth(PConfig *, const char *);
	virtual ~LDAPAliasAuth();
	
	/** Searchs for an h323id/e164 alias in LDAP then:
	    1) mandatory: retrieves the AccountStatus attribute and converts 
		   it to a valid config string. (format: see description in ini-file)
		2) optionally: retrieves the h323id/e164 alias attributes and checks
		   if the aliases are valid.
	    @returns (AliasFoundInLDAP) ? e_ok : defaultStatus;
	 */
	virtual int Check(H225_RegistrationRequest & rrq, unsigned &);

};


#endif // HAS_WLDAP


// Initial author: Michael Rubashenkkov  2002/01/14 (GkAuthorize)
// Completely rewrite by Chih-Wei Huang  2002/05/01
class AuthObj;
class AuthRule;
class PrefixAuth : public GkAuthenticator {
public:
	PrefixAuth(PConfig *, const char *);
	~PrefixAuth();

	typedef std::map< PString, AuthRule *, greater<PString> > Rules;

private:
	virtual int Check(const H225_GatekeeperRequest &, unsigned &);
	virtual int Check(H225_RegistrationRequest &, unsigned &);
	virtual int Check(const H225_UnregistrationRequest &, unsigned &);
	virtual int Check(H225_AdmissionRequest &, unsigned &); 
	virtual int Check(const H225_BandwidthRequest &, unsigned &);
	virtual int Check(const H225_DisengageRequest &, unsigned &);
	virtual int Check(const H225_LocationRequest &, unsigned &);
	virtual int Check(const H225_InfoRequest &, unsigned &);

	virtual int doCheck(const AuthObj &);
	
	Rules prefrules;
};



class CacheManager 
{
public:
	CacheManager(
		long ttl /// cache expiration timeout (seconds)
		) : m_ttl(ttl) {}

	bool Retrieve(const PString & key, PString & value);
	void Save(const PString & key, const PString & value);
	
	void SetTimeout(
		long newTimeout /// new cache expiration timeout
		)
	{
		m_ttl = newTimeout;
	}

private:
	std::map<PString, PString> m_cache;
	std::map<PString, long> m_ctime;
	// 0 means don't cache, -1 means never expires
	long m_ttl; // seconds

	CacheManager(const CacheManager &);
	CacheManager & operator=(const CacheManager &);
};      

bool CacheManager::Retrieve(const PString & key, PString & value)
{
	std::map<PString, PString>::iterator iter = m_cache.find(key);
	if (iter == m_cache.end())
		return false;
	if (m_ttl > 0) {
		std::map<PString, long>::iterator i = m_ctime.find(key);
		if ((time(NULL) - i->second) > m_ttl)
			return false; // cache expired
	}
	value = iter->second;
	PTRACE(5, "GkAuth\tCache found for " << key);
	return true;
}

void CacheManager::Save(const PString & key, const PString & value)
{
	if (m_ttl != 0) {
		m_cache[key] = value;
		m_ctime[key] = time(NULL);
	}
}

//////////////////////////////////////////////////////////////////////

GkAuthenticator::GkAuthenticator(PConfig *cfg, const char *authName) 
	: config(cfg), h235Authenticators(NULL), name(authName), checkFlag(e_ALL)
{
	PStringArray control(config->GetString(GkAuthSectionName, name, "").Tokenise(";,"));
	if (PString(name) == "default")
		controlFlag = e_Sufficient,
		defaultStatus = Toolkit::AsBool(control[0]) ? e_ok : e_fail;
	else if (control[0] *= "optional")
		controlFlag = e_Optional, defaultStatus = e_next;
	else if (control[0] *= "required")
		controlFlag = e_Required, defaultStatus = e_fail;
	else
		controlFlag = e_Sufficient, defaultStatus = e_fail;

	if (control.GetSize() > 1) {
		checkFlag = 0;
		std::map<PString, int> rasmap;
		rasmap["GRQ"] = e_GRQ, rasmap["RRQ"] = e_RRQ,
		rasmap["URQ"] = e_URQ, rasmap["ARQ"] = e_ARQ,
		rasmap["BRQ"] = e_BRQ, rasmap["DRQ"] = e_DRQ,
		rasmap["LRQ"] = e_LRQ, rasmap["IRQ"] = e_IRQ,
		rasmap["Setup"] = e_Setup;
		rasmap["SetupUnreg"] = e_SetupUnreg;
		for (PINDEX i=1; i < control.GetSize(); ++i) {
			if (rasmap.find(control[i]) != rasmap.end())
				checkFlag |= rasmap[control[i]];
		}
	}
	
	next = head;
	head = this;

	PTRACE(1, "GkAuth\tAdd " << name << " rule with flag " << hex << checkFlag << dec);
}

GkAuthenticator::~GkAuthenticator()
{
	PTRACE(1, "GkAuth\tRemove " << name << " rule");
	delete next;  // delete whole list recursively
	delete h235Authenticators;
}

bool GkAuthenticator::CheckRas(
	PBYTEArray &rawPDU, 
	H225_RegistrationRequest& rrq, 
	unsigned& rejectReason,
	const PIPSocket::Address& rxaddr,MemBlock *m
	)
{
	setLastReceivedRawPDU(rawPDU);
	if (checkFlag & RasValue(rrq)) {
		int r = Check(rrq, rejectReason,m);
		if (r == e_ok) {
			PTRACE(4, "GkAuth\t" << name << " check ok");
			if (controlFlag != e_Required)
				return true;
		} else if (r == e_fail) {
			PTRACE(2, "GkAuth\t" << name << " check failed");
			return false;
		}
	}
	// try next rule
	return next == NULL || next->CheckRas(rawPDU,rrq,rejectReason,rxaddr,m);
}
bool GkAuthenticator::CheckRas(
	PBYTEArray &rawPDU, 
	H225_RegistrationRequest& rrq, 
	unsigned& rejectReason,
	const PIPSocket::Address& rxaddr
	)
{
	setLastReceivedRawPDU(rawPDU);
	if (checkFlag & RasValue(rrq)) {
		int r = Check(rrq, rejectReason);
		if (r == e_ok) {
			PTRACE(4, "GkAuth\t" << name << " check ok");
			if (controlFlag != e_Required)
				return true;
		} else if (r == e_fail) {
			PTRACE(2, "GkAuth\t" << name << " check failed");
			return false;
		}
	}
	// try next rule
	return next == NULL || next->CheckRas(rawPDU,rrq,rejectReason,rxaddr);
}
bool GkAuthenticator::CheckRas(
	PBYTEArray &rawPDU, 
	H225_AdmissionRequest& req, 
	unsigned& rejectReason,
	long& callDurationLimit
	)
{
	callDurationLimit = -1;
	setLastReceivedRawPDU(rawPDU);
	if (checkFlag & RasValue(req)) {
		int r = Check(req, rejectReason, callDurationLimit);
		if( callDurationLimit == 0 ) {
			PTRACE(2,"GkAuth\t" << name << " - call duration limit is 0");
			return false;
		}
		if (r == e_ok) {
			PTRACE(4, "GkAuth\t" << name << " check ok");
			if (controlFlag != e_Required)
				return true;
		} else if (r == e_fail) {
			PTRACE(2, "GkAuth\t" << name << " check failed");
			return false;
		}
	}
	// try next rule
	if( next ) {
		long limit = -1;
		if( !next->CheckRas(rawPDU,req,rejectReason,limit) )
			return false;
		if( callDurationLimit >= 0 && limit >= 0 )
			callDurationLimit = PMIN(limit,callDurationLimit);
		else
			callDurationLimit = PMAX(limit,callDurationLimit);
	}
	if( callDurationLimit == 0 ) {
		PTRACE(2,"GkAuth\t" << name << " - call duration limit is 0");
		return false;
	}
	return true;
}
		
bool GkAuthenticator::CheckSig( 
	/// received Q.931 Setup message
	Q931& q931pdu, 
	/// received H.225 Setup message
	H225_Setup_UUIE& setup, 
	/// CallRec for the call being authenticated
	callptr& call,
	/// Q.931 cause to set, if authentication fails
	unsigned& releaseCompleteCause, 
	/// call duration limit (should be set initially to -1,
	/// what means no limit)
	long& callDurationLimit,
	/// is the call from a registered endpoint
	bool registered
	)
{
	callDurationLimit = -1;
	if (checkFlag & (registered ? e_Setup : (e_Setup | e_SetupUnreg))) {
		const int r = Check( q931pdu, setup, call, releaseCompleteCause, callDurationLimit );
		if( callDurationLimit == 0 ) {
			PTRACE(2,"GkAuth\t"<<name<<" - call duration limit is 0");
			return false;
		}
		if( r == e_ok ) {
			PTRACE(4,"GkAuth\t"<<name<<" Q.931 Setup check ok");
			if( controlFlag != e_Required )
				return true;
		} else if( r == e_fail ) {
			PTRACE(2,"GkAuth\t"<<name<<" Q.931 Setup check failed");
			return false;
		}
	}
	
	// try next rule
	if( next ) {
		long limit = -1;
		if (!next->CheckSig(q931pdu, setup, call, releaseCompleteCause, limit, registered))
			return false;
		if( callDurationLimit >= 0 && limit >= 0 )
			callDurationLimit = PMIN(limit,callDurationLimit);
		else
			callDurationLimit = PMAX(limit,callDurationLimit);
	}
	if( callDurationLimit == 0 ) {
		PTRACE(2,"GkAuth\t"<<name<<" - call duration limit is 0");
		return false;
	}
	return true;
}

int GkAuthenticator::Check(const H225_GatekeeperRequest &, unsigned &)
{
	return defaultStatus;
}

int GkAuthenticator::Check(H225_RegistrationRequest &, unsigned &)
{
	return defaultStatus;
}
int GkAuthenticator::Check(H225_RegistrationRequest & request, unsigned & rejectReason,MemBlock *)
{
	return Check(request,rejectReason);
}
int GkAuthenticator::Check(const H225_UnregistrationRequest &, unsigned &)
{
	return defaultStatus;
}

int GkAuthenticator::Check(H225_AdmissionRequest &, unsigned &)
{
	return defaultStatus;
}

int GkAuthenticator::Check(
	H225_AdmissionRequest& request, 
	unsigned& rejectReason,
	long& /*callDurationLimit*/
	)
{
	// for backward compatibility with derived authenticators
	// that do not understand call duration limit feature
	return Check(request,rejectReason);
}

int GkAuthenticator::Check(const H225_BandwidthRequest &, unsigned &)
{
	return defaultStatus;
}

int GkAuthenticator::Check(const H225_DisengageRequest &, unsigned &)
{
	return defaultStatus;
}

int GkAuthenticator::Check(const H225_LocationRequest &, unsigned &)
{
	return defaultStatus;
}

int GkAuthenticator::Check(const H225_InfoRequest &, unsigned &)
{
	return defaultStatus;
}

int GkAuthenticator::Check( Q931&, H225_Setup_UUIE&, callptr&, unsigned&, long& )
{
	return defaultStatus;
}

BOOL GkAuthenticator::GetH235Capability(
	H225_ArrayOf_AuthenticationMechanism& mechanisms,
	H225_ArrayOf_PASN_ObjectId& algorithmOIDs
	) const
{
	if( h235Authenticators == NULL )
		return FALSE;
		
	for( int i = 0; i < h235Authenticators->GetSize(); i++ )
		(*h235Authenticators)[i].SetCapability(mechanisms,algorithmOIDs);
		
	return TRUE;
}		

BOOL GkAuthenticator::IsH235Capability(
	const H235_AuthenticationMechanism& mechanism,
	const PASN_ObjectId& algorithmOID
	) const
{
	if( h235Authenticators == NULL )
		return FALSE;
		
	for( int i = 0; i < h235Authenticators->GetSize(); i++ )
		if( (*h235Authenticators)[i].IsCapability(mechanism,algorithmOID) )
			return TRUE;
		
	return FALSE;
}

BOOL GkAuthenticator::IsH235Capable() const
{
	return (h235Authenticators != NULL) && (h235Authenticators->GetSize() > 0);
}

PString GkAuthenticator::GetUsername(
	/// RRQ message with additional data
	const H225_RegistrationRequest& rrq
	) const
{
	PString username;
	
	if (rrq.HasOptionalField(H225_RegistrationRequest::e_terminalAlias))
		username = GetBestAliasAddressString(rrq.m_terminalAlias, false,
			AliasAddressTagMask(H225_AliasAddress::e_h323_ID),
			AliasAddressTagMask(H225_AliasAddress::e_email_ID)
				| AliasAddressTagMask(H225_AliasAddress::e_url_ID)
			);

	if (username.IsEmpty()) {
		PIPSocket::Address addr;
		if (rrq.m_callSignalAddress.GetSize() > 0
			&& GetIPFromTransportAddr(rrq.m_callSignalAddress[0], addr)
			&& addr.IsValid())
			username = addr.AsString();
		else if (rrq.m_rasAddress.GetSize() > 0
			&& GetIPFromTransportAddr(rrq.m_rasAddress[0], addr) 
			&& addr.IsValid())
			username = addr.AsString();
	}
	
	return username;
}

PString GkAuthenticator::GetUsername(
	/// call (if any) associated with the RAS message
	const callptr& call,
	/// ARQ message with additional data
	const H225_AdmissionRequest& arq,
	/// endpoint sending the ARQ request
	const endptr& ep
	) const
{
	PString username;
	
	/// try to find h323_ID, email_ID or url_ID to use for User-Name
	if (!arq.m_answerCall)
		username = GetBestAliasAddressString(arq.m_srcInfo, true, 
			AliasAddressTagMask(H225_AliasAddress::e_h323_ID),
			AliasAddressTagMask(H225_AliasAddress::e_email_ID)
				| AliasAddressTagMask(H225_AliasAddress::e_url_ID)
			);
	else if (call)
		username = GetBestAliasAddressString(call->GetSourceAddress(), true,
			AliasAddressTagMask(H225_AliasAddress::e_h323_ID),
			AliasAddressTagMask(H225_AliasAddress::e_email_ID)
				| AliasAddressTagMask(H225_AliasAddress::e_url_ID)
			);
				
	if (ep && (username.IsEmpty() 
			|| FindAlias(ep->GetAliases(), username) == P_MAX_INDEX))
		username = GetBestAliasAddressString(ep->GetAliases(), false,
			AliasAddressTagMask(H225_AliasAddress::e_h323_ID),
			AliasAddressTagMask(H225_AliasAddress::e_email_ID)
				| AliasAddressTagMask(H225_AliasAddress::e_url_ID)
			);

	/// if no h323_ID, email_ID or url_ID has been found, try to find any alias
	if (username.IsEmpty())
		if (!arq.m_answerCall)
			username = GetBestAliasAddressString(arq.m_srcInfo, false, 
				AliasAddressTagMask(H225_AliasAddress::e_h323_ID),
				AliasAddressTagMask(H225_AliasAddress::e_email_ID)
					| AliasAddressTagMask(H225_AliasAddress::e_url_ID)
				);
		else if (call)
			username = GetBestAliasAddressString(call->GetSourceAddress(), true,
				AliasAddressTagMask(H225_AliasAddress::e_h323_ID),
				AliasAddressTagMask(H225_AliasAddress::e_email_ID)
					| AliasAddressTagMask(H225_AliasAddress::e_url_ID)
				);

		
	if (username.IsEmpty()) {
		PIPSocket::Address addr;
		if (arq.HasOptionalField(H225_AdmissionRequest::e_srcCallSignalAddress)
			&& GetIPFromTransportAddr(arq.m_srcCallSignalAddress, addr)
			&& addr.IsValid())
			username = addr.AsString();
		else if (ep && GetIPFromTransportAddr(ep->GetCallSignalAddress(), addr) 
			&& addr.IsValid())
			username = addr.AsString();
	}
	
	return username;
}

PString GkAuthenticator::GetUsername(
	/// call (if any) associated with the Setup message
	const callptr& call,
	/// Q.931 Setup message with additional data
	const Q931& q931pdu,
	/// Setup-UUIE element extracted from the Q.931 Setup message
	const H225_Setup_UUIE& setup
	) const
{
	PString username;				
	endptr callingEP;
	
	if (call)
		callingEP = call->GetCallingParty();
	
	if (setup.HasOptionalField(setup.e_sourceAddress)) {
		username = GetBestAliasAddressString(setup.m_sourceAddress, true,
			AliasAddressTagMask(H225_AliasAddress::e_h323_ID),
			AliasAddressTagMask(H225_AliasAddress::e_email_ID)
				| AliasAddressTagMask(H225_AliasAddress::e_url_ID)
			);
		if (!username && callingEP 
				&& FindAlias(callingEP->GetAliases(), username) == P_MAX_INDEX)
			username = PString();
	}

	if (username.IsEmpty() && call) {
		username = GetBestAliasAddressString(call->GetSourceAddress(), true,
			AliasAddressTagMask(H225_AliasAddress::e_h323_ID),
			AliasAddressTagMask(H225_AliasAddress::e_email_ID)
				| AliasAddressTagMask(H225_AliasAddress::e_url_ID)
			);
		if (!username && callingEP 
				&& FindAlias(callingEP->GetAliases(), username) == P_MAX_INDEX)
			username = PString();
	}
	
	if (username.IsEmpty() && callingEP)
		username = GetBestAliasAddressString(callingEP->GetAliases(), false,
			AliasAddressTagMask(H225_AliasAddress::e_h323_ID),
			AliasAddressTagMask(H225_AliasAddress::e_email_ID)
				| AliasAddressTagMask(H225_AliasAddress::e_url_ID)
			);
	
	if (username.IsEmpty() && call)
		username = GetBestAliasAddressString(call->GetSourceAddress(), false,
			AliasAddressTagMask(H225_AliasAddress::e_h323_ID),
			AliasAddressTagMask(H225_AliasAddress::e_email_ID)
				| AliasAddressTagMask(H225_AliasAddress::e_url_ID)
			);

	if (username.IsEmpty() && setup.HasOptionalField(setup.e_sourceAddress))
		username = GetBestAliasAddressString(setup.m_sourceAddress, false,
			AliasAddressTagMask(H225_AliasAddress::e_h323_ID),
			AliasAddressTagMask(H225_AliasAddress::e_email_ID)
				| AliasAddressTagMask(H225_AliasAddress::e_url_ID)
			);

	if (username.IsEmpty())
		q931pdu.GetCallingPartyNumber(username);
		
	if (username.IsEmpty()) {
		PIPSocket::Address addr(0);
		WORD port;
		bool addrValid = false;
	
		if (call && call->HasSrcSignalAddr())
			addrValid = call->GetSrcSignalAddr(addr, port) && addr.IsValid();
			
		if (!addrValid && setup.HasOptionalField(setup.e_sourceCallSignalAddress))
			addrValid = GetIPFromTransportAddr(setup.m_sourceCallSignalAddress, addr)
				&& addr.IsValid();

		if (!addrValid && callingEP)
			addrValid = GetIPFromTransportAddr(callingEP->GetCallSignalAddress(), addr)
				&& addr.IsValid();

		if (addrValid)
			username = addr.AsString();
	}
	
	return username;
}

PString GkAuthenticator::GetCallingStationId(
	/// call (if any) associated with the RAS message
	const callptr& call,
	/// ARQ message with additional data
	const H225_AdmissionRequest& arq,
	/// endpoint sending the ARQ request
	const endptr& ep
	) const
{
	PString id;

	// Calling-Station-Id
	if (!arq.m_answerCall) // srcInfo is meaningful only in an originating ARQ
		id = GetBestAliasAddressString(arq.m_srcInfo, false,
			AliasAddressTagMask(H225_AliasAddress::e_dialedDigits)
				| AliasAddressTagMask(H225_AliasAddress::e_partyNumber)
			);
#ifdef HAS_ACCT
	else if (call)
		id = call->GetCallingStationId();
#endif

	if (!id)
		return id;
		
	if (id.IsEmpty() && call)
		id = GetBestAliasAddressString(call->GetSourceAddress(), false,
			AliasAddressTagMask(H225_AliasAddress::e_dialedDigits)
				| AliasAddressTagMask(H225_AliasAddress::e_partyNumber)
			);

	if (id.IsEmpty() && ep && !arq.m_answerCall)
		id = GetBestAliasAddressString(ep->GetAliases(), false,
			AliasAddressTagMask(H225_AliasAddress::e_dialedDigits)
				| AliasAddressTagMask(H225_AliasAddress::e_partyNumber)
			);
			
	if (id.IsEmpty() && arq.m_answerCall && call) {
		endptr callingEP = call->GetCallingParty();
		if (callingEP)
			id = GetBestAliasAddressString(
				callingEP->GetAliases(), false,
				AliasAddressTagMask(H225_AliasAddress::e_dialedDigits)
					| AliasAddressTagMask(H225_AliasAddress::e_partyNumber)
				);
	}
				
	if (id.IsEmpty() && call && call->HasSrcSignalAddr()) {
		PIPSocket::Address addr(0);
		WORD port = 0;
		if (call->GetSrcSignalAddr(addr, port) && addr.IsValid())
			id = AsString(addr, port);
	}
	
	return id;
}

PString GkAuthenticator::GetCallingStationId(
	/// call (if any) associated with the Setup message
	const callptr& call,
	/// Q.931 Setup message with additional data
	const Q931& q931pdu,
	/// Setup-UUIE element extracted from the Q.931 Setup message
	const H225_Setup_UUIE& setup
	) const
{
	PString id;
	
#ifdef HAS_ACCT
	if (call)
		id = call->GetCallingStationId();
#endif

	if (id.IsEmpty())
		q931pdu.GetCallingPartyNumber(id);
	
	if (!id)
		return id;

	if (id.IsEmpty() && setup.HasOptionalField(setup.e_sourceAddress)) 
		id = GetBestAliasAddressString(setup.m_sourceAddress, false,
			AliasAddressTagMask(H225_AliasAddress::e_dialedDigits)
				| AliasAddressTagMask(H225_AliasAddress::e_partyNumber)
			);

	if (id.IsEmpty() && call)
		id = GetBestAliasAddressString(call->GetSourceAddress(), false,
			AliasAddressTagMask(H225_AliasAddress::e_dialedDigits)
				| AliasAddressTagMask(H225_AliasAddress::e_partyNumber)
			);

	if (id.IsEmpty() && call) {
		endptr callingEP = call->GetCallingParty();
		if (callingEP)
			id = GetBestAliasAddressString(callingEP->GetAliases(), false,
				AliasAddressTagMask(H225_AliasAddress::e_dialedDigits)
					| AliasAddressTagMask(H225_AliasAddress::e_partyNumber)
				);
	}
	
	return id;
}

PString GkAuthenticator::GetCalledStationId(
	/// call (if any) associated with the RAS message
	const callptr& call,
	/// ARQ message with additional data
	const H225_AdmissionRequest& arq,
	/// endpoint sending the ARQ request
	const endptr& ep
	) const
{
	PString id;
				
	if (!arq.m_answerCall) {
		if (arq.HasOptionalField(H225_AdmissionRequest::e_destinationInfo))
			id = GetBestAliasAddressString(arq.m_destinationInfo, false,
				AliasAddressTagMask(H225_AliasAddress::e_dialedDigits)
					| AliasAddressTagMask(H225_AliasAddress::e_partyNumber)
				);
	}
#ifdef HAS_ACCT
	else if (call)
		id = call->GetCalledStationId();
#endif

	if (!id)
		return id;

	if (id.IsEmpty() && call)
		id = GetBestAliasAddressString(call->GetDestinationAddress(), false,
			AliasAddressTagMask(H225_AliasAddress::e_dialedDigits)
				| AliasAddressTagMask(H225_AliasAddress::e_partyNumber)
			);

	PIPSocket::Address addr;
	
	if (id.IsEmpty() && arq.m_answerCall) {
		if (arq.HasOptionalField(H225_AdmissionRequest::e_destinationInfo))
			id = GetBestAliasAddressString(arq.m_destinationInfo, false,
				AliasAddressTagMask(H225_AliasAddress::e_dialedDigits)
					| AliasAddressTagMask(H225_AliasAddress::e_partyNumber)
				);

		if (id.IsEmpty() && ep)
			id = GetBestAliasAddressString(ep->GetAliases(), false,
				AliasAddressTagMask(H225_AliasAddress::e_dialedDigits)
					| AliasAddressTagMask(H225_AliasAddress::e_partyNumber)
				);

		if (id.IsEmpty() && ep 
			&& GetIPFromTransportAddr(ep->GetCallSignalAddress(), addr) 
			&& addr.IsValid())
			id = addr.AsString();
	}
		
	// this does not work well in routed mode, when destCallSignalAddress
	// is usually the gatekeeper address
	if (id.IsEmpty() 
		&& arq.HasOptionalField(H225_AdmissionRequest::e_destCallSignalAddress)) {
		const H225_TransportAddress& tsap = arq.m_destCallSignalAddress;
		id = AsDotString(tsap);
	}
	
	return id;
}

PString GkAuthenticator::GetCalledStationId(
	/// call (if any) associated with the Setup message
	const callptr& call,
	/// Q.931 Setup message with additional data
	const Q931& q931pdu,
	/// Setup-UUIE element extracted from the Q.931 Setup message
	const H225_Setup_UUIE& setup
	) const
{
	PString id;
	
#ifdef HAS_ACCT
	if (call)
		id = call->GetCalledStationId();
#endif

	if (id.IsEmpty())
		q931pdu.GetCalledPartyNumber(id);
	
	if (!id)
		return id;
		
	if (id.IsEmpty() && setup.HasOptionalField(setup.e_destinationAddress))
		id = GetBestAliasAddressString(setup.m_destinationAddress, false,
			AliasAddressTagMask(H225_AliasAddress::e_dialedDigits)
				| AliasAddressTagMask(H225_AliasAddress::e_partyNumber)
			);

	if (id.IsEmpty() && call)
		id = GetBestAliasAddressString(call->GetDestinationAddress(), false,
			AliasAddressTagMask(H225_AliasAddress::e_dialedDigits)
				| AliasAddressTagMask(H225_AliasAddress::e_partyNumber)
			);

	if (id.IsEmpty()) {
		PIPSocket::Address addr;
		WORD port;
		if (call && call->HasDestSignalAddr() 
			&& call->GetDestSignalAddr(addr, port))
			id = AsString(addr, port);
		else if (setup.HasOptionalField(setup.e_destCallSignalAddress) 
			&& GetIPAndPortFromTransportAddr(setup.m_destCallSignalAddress, addr, port) 
			&& addr.IsValid())
			id = AsString(addr, port);
	}
	
	return id;
}



const char *passwdsec = "Password";

// SimplePasswordAuth
SimplePasswordAuth::SimplePasswordAuth(PConfig *cfg, const char *authName)
      : GkAuthenticator(cfg, authName), aliases(0)
{
	filled = config->GetInteger(passwdsec, "KeyFilled", 0);
	checkid = Toolkit::AsBool(config->GetString(passwdsec, "CheckID", "0"));
	m_cache = new CacheManager(config->GetInteger(passwdsec, "PasswordTimeout", -1));
	
	if( h235Authenticators == NULL )
		h235Authenticators = new H235Authenticators;
		
	H235Authenticator* authenticator;
	
	authenticator = new H235AuthSimpleMD5;
	authenticator->SetLocalId("dummy");
	authenticator->SetRemoteId("dummy");
	authenticator->SetPassword("dummy");
	h235Authenticators->Append(authenticator);
	authenticator = new H235AuthCAT;
	authenticator->SetLocalId("dummy");
	authenticator->SetRemoteId("dummy");
	authenticator->SetPassword("dummy");
	h235Authenticators->Append(authenticator);
/*
#if P_SSL
	authenticator = new H235AuthProcedure1;
	authenticator->SetLocalId("dummy");
	authenticator->SetRemoteId("dummy");
	authenticator->SetPassword("dummy");
	h235Authenticators->Append(authenticator);
#endif
*/
}

SimplePasswordAuth::~SimplePasswordAuth()
{
	delete m_cache;
}

int SimplePasswordAuth::Check(const H225_GatekeeperRequest & grq, unsigned &)
{
	return doCheck(grq);
}

int SimplePasswordAuth::Check(H225_RegistrationRequest & rrq, unsigned &)
{
	if (checkid) {
		if (!rrq.HasOptionalField(H225_RegistrationRequest::e_terminalAlias))
			return e_fail;
		aliases = &rrq.m_terminalAlias;
	}
	return doCheck(rrq);
}

int SimplePasswordAuth::Check(const H225_UnregistrationRequest & urq, unsigned &)
{
	return doCheck(urq);
}

int SimplePasswordAuth::Check(H225_AdmissionRequest & arq, unsigned &)
{
	return doCheck(arq);
}

int SimplePasswordAuth::Check(const H225_BandwidthRequest & brq, unsigned &)
{
	return doCheck(brq);
}

int SimplePasswordAuth::Check(const H225_DisengageRequest & drq, unsigned &)
{
	return doCheck(drq);
}

int SimplePasswordAuth::Check(const H225_LocationRequest & lrq, unsigned &)
{
	return doCheck(lrq);
}

int SimplePasswordAuth::Check(const H225_InfoRequest & drq, unsigned &)
{
	return doCheck(drq);
}

PString SimplePasswordAuth::GetPassword(const PString & id)
{
	return Toolkit::CypherDecode(id, config->GetString(passwdsec, id, ""), filled);
}

#if defined HAS_WLDAP

// ini file section name
extern const char *ldap_attr_name_sec; // [GkLDAP::LDAPAttributeNames]
extern const char *ldap_auth_sec;      // [GkLDAP::Settings]

bool SimplePasswordAuth::InternalGetPassword(PString & passwd)
{
	PStringList AliasList;
	// alias search presedence e164/h323id
	if (Toolkit::AsBool(GkConfig()->GetString(ldap_auth_sec, "LdapPasswAuthSearchPrecedenceE164", "0"))) {
		PString h323id;
		for (PINDEX j = 0; j < aliases->GetSize(); ++j) {
			if ((*aliases)[j].GetTag() == H225_AliasAddress::e_dialedDigits) {
				const PString & e164 = H323GetAliasAddressString((*aliases)[j]);
				PTRACE(5, "GkAuth\tSimplePasswordAuth::InternalGetPassword() e164 '" << e164 <<"'");
				AliasList.AppendString(e164);
			}
			else if ((*aliases)[j].GetTag() == H225_AliasAddress::e_h323_ID) {
				h323id = H323GetAliasAddressString((*aliases)[j]);
				PTRACE(5, "GkAuth\tSimplePasswordAuth::InternalGetPassword() h323id '" << h323id <<"'");
			}
		}
		AliasList.AppendString(h323id);
	}
	else {
		for (PINDEX j = 0; j < aliases->GetSize(); ++j) {
			PTRACE(5, "GkAuth\tSimplePasswordAuth::InternalGetPassword() H323GetAliasAddressString((*aliases)[j]) '" << H323GetAliasAddressString((*aliases)[j]) <<"'");
			AliasList.AppendString(H323GetAliasAddressString((*aliases)[j]));
		}
	}

	// step through the list of aliases and search
	// LDAP for a password. If the returned string
	// 'passwd' is not empty we return result.
	bool result = false;
	for (PINDEX i = 0; i < AliasList.GetSize(); i++) {
		passwd = "";
		result = m_cache->Retrieve(AliasList[i], passwd);
		if (!result) {
			passwd = GetPassword(AliasList[i]);
			PTRACE(5, "GkAuth\tSimplePasswordAuth::InternalGetPassword() passwd '" << passwd <<"'");
			if(!passwd.IsEmpty()) {
				aliases = 0;
				return result;
			}
		}
	}
	aliases = 0;
	return result;
}

#else // any authenticator

bool SimplePasswordAuth::InternalGetPassword(const PString & id, PString & passwd)
{
	bool result = m_cache->Retrieve(id, passwd);
	if (!result)
		passwd = GetPassword(id);
	return result;
}

#endif // HAS_WLDAP

bool SimplePasswordAuth::CheckAliases(const PString & id)
{
	bool r = false;
	for (PINDEX i = 0; i < aliases->GetSize(); i++)
		if (H323GetAliasAddressString((*aliases)[i]) == id) {
			r = true;
			break;
		}
#if !defined HAS_WLDAP
	aliases = 0;
#endif
	return r;
}

static const char OID_CAT[] = "1.2.840.113548.10.1.2.1";

bool SimplePasswordAuth::CheckTokens(const H225_ArrayOf_ClearToken & tokens)
{
	for (PINDEX i=0; i < tokens.GetSize(); ++i) {
		H235_ClearToken & token = tokens[i];
		// check for Cisco Access Token
		if( token.m_tokenOID == OID_CAT )
		{
			if( !token.HasOptionalField(H235_ClearToken::e_generalID) )
				return false;
				
			PString id = token.m_generalID;
			if (aliases && !CheckAliases(id))
				return false;
				
			PString passwd;
			bool cached = false;
#if defined HAS_WLDAP
			cached = InternalGetPassword(passwd);
#else
			cached = InternalGetPassword(id, passwd);
#endif // HAS_WLDAP

			H235AuthCAT authCAT;
			authCAT.SetLocalId(id);
			authCAT.SetPassword(passwd);
			if (authCAT.ValidateClearToken(token) == H235Authenticator::e_OK) {
				PTRACE(4, "GkAuth\t" << id << " password match (CAT)");
				if (!cached)
					m_cache->Save(id, passwd);
				return true;
			}
			return false;
		}
		if (token.HasOptionalField(H235_ClearToken::e_generalID) &&
		    token.HasOptionalField(H235_ClearToken::e_password)) {
			PString id = token.m_generalID;
			if (aliases && !CheckAliases(id))
				return false;
			PString passwd, tokenpasswd = token.m_password;
			bool cached = false;
#if defined HAS_WLDAP
			cached = InternalGetPassword(passwd);
#else
			cached = InternalGetPassword(id, passwd);
#endif
			if (passwd == tokenpasswd) {
				PTRACE(4, "GkAuth\t" << id << " password match");
				if (!cached)
					m_cache->Save(id, passwd);
				return true;
			}
		}
	}
	return false;
}

bool SimplePasswordAuth::CheckCryptoTokens(const H225_ArrayOf_CryptoH323Token & tokens)
{
	for (PINDEX i = 0; i < tokens.GetSize(); ++i) {
		if (tokens[i].GetTag() == H225_CryptoH323Token::e_cryptoEPPwdHash) {
			H225_CryptoH323Token_cryptoEPPwdHash & pwdhash = tokens[i];
			PString id = AsString(pwdhash.m_alias, FALSE);
			if (aliases && !CheckAliases(id))
				return false;
			PString passwd;
			bool cached = false;
#if defined HAS_WLDAP
			cached = InternalGetPassword(passwd);
#else
			cached = InternalGetPassword(id, passwd);
#endif // HAS_WLDAP

			H235AuthSimpleMD5 authMD5;
			authMD5.SetLocalId(id);
			authMD5.SetPassword(passwd);
			PBYTEArray nullPDU;
			if (authMD5.ValidateCryptoToken(tokens[i], nullPDU) == H235Authenticator::e_OK) {
				PTRACE(4, "GkAuth\t" << id << " password match (MD5)");
				if (!cached)
					m_cache->Save(id, passwd);
				return true;
			}
#if P_SSL
		} else if (tokens[i].GetTag() == H225_CryptoH323Token::e_nestedcryptoToken){
			H235_CryptoToken & nestedCryptoToken = tokens[i];
			H235_CryptoToken_cryptoHashedToken & cryptoHashedToken = nestedCryptoToken;
			H235_ClearToken & clearToken = cryptoHashedToken.m_hashedVals;
			PString gk_id = clearToken.m_generalID;
			//assumption: sendersID == endpoint alias (RRQ)
			PString ep_alias = clearToken.m_sendersID; 
			if (aliases && !CheckAliases(ep_alias))
				return false;
			PString passwd;
			bool cached = false;
#if defined HAS_WLDAP
			cached = InternalGetPassword(passwd);
#else
			cached = InternalGetPassword(ep_alias, passwd);
#endif // HAS_WLDAP
			//if a password is not found: senderID == endpointIdentifier?
			if (passwd.IsEmpty()){
			 	//get endpoint by endpointIdentifier
				H225_EndpointIdentifier ep_id;
				ep_id = clearToken.m_sendersID;
				endptr ep = RegistrationTable::Instance()->FindByEndpointId(ep_id);
				if(!ep){
					return false;
				}
				//check all endpoint aliases for a password
				H225_ArrayOf_AliasAddress ep_aliases = ep->GetAliases();
				for (PINDEX i = 0; i < ep_aliases.GetSize(); i++){
					ep_alias = H323GetAliasAddressString(ep_aliases[i]);
					cached = InternalGetPassword(ep_alias, passwd);
					if (!passwd)
						break;
				}
			}
			H235AuthProcedure1 authProcedure1;
			authProcedure1.SetLocalId(gk_id);
			authProcedure1.SetPassword(passwd);
			if (authProcedure1.ValidateCryptoToken(tokens[i], getLastReceivedRawPDU()) == H235Authenticator::e_OK) {
				PTRACE(4, "GkAuth\t" << ep_alias << " password match (SHA-1)");
				if (!cached)
					m_cache->Save(ep_alias, passwd);
				return true;
			}
#endif

		}
	}
	return false;
}


#if HAS_MYSQL

// MySQLPasswordAuth
MySQLPasswordAuth::MySQLPasswordAuth(
	PConfig* cfg, 
	const char* authName
	)
	: SimplePasswordAuth(cfg, authName), m_sqlConn(NULL)
{
	GkSQLCreator* driverCreator = GkSQLCreator::FindDriver("MySQL");
	if (driverCreator == NULL) {
		PTRACE(1, GetName() << "\tModule creation failed: could not find driver for MySQL database");
		return;
	}
	
	if (m_cache)
		m_cache->SetTimeout(cfg->GetInteger("MySQLAuth", "CacheTimeout", -1));
		
	const PString password = cfg->GetString("MySQLAuth", "Password", "");
	
	PString host = cfg->GetString("MySQLAuth", "Host", "localhost");
	if (cfg->GetInteger("MySQLAuth", "Port", -1) > 0)
		host += ":" + PString(cfg->GetInteger("MySQLAuth", "Port", MYSQL_PORT));
		
	m_sqlConn = driverCreator->CreateConnection(authName);
	if (!m_sqlConn->Initialize(
			host,
			cfg->GetString("MySQLAuth", "Database", "mysql"),
			cfg->GetString("MySQLAuth", "User", "mysql"),
			password.IsEmpty() ? (const char*)NULL : (const char*)password,
			1, 1)) {
		delete m_sqlConn;
		m_sqlConn = NULL;
		PTRACE(2, GetName() << "\tModule creation failed: could not connect to the database");
		return;
	}
	
	const PString table = cfg->GetString("MySQLAuth", "Table", "");
	const PString passwordField = cfg->GetString("MySQLAuth", "PasswordField", "");
	const PString aliasField = cfg->GetString("MySQLAuth", "IDField", "");
	
	if (table.IsEmpty() || passwordField.IsEmpty() || aliasField.IsEmpty()) {
		PTRACE(1, GetName() << "\tCannot build query: Table, PasswordField or IDField not specified");
		return;
	}
	
	m_query = "SELECT " + passwordField + " FROM " + table + " WHERE " 
		+ aliasField + " = '%1'";
		
	const PString extraCrit = cfg->GetString("MySQLAuth", "ExtraCriterion", "");
	if (!extraCrit.IsEmpty())
		m_query += " AND " + extraCrit;
		
	PTRACE(4, GetName() << "\tConfigured query: " << m_query);
}

MySQLPasswordAuth::~MySQLPasswordAuth()
{
	delete m_sqlConn;
}

PString MySQLPasswordAuth::GetPassword(
	const PString& alias
	)
{
	PString password;
	
	if (m_sqlConn == NULL) {
		PTRACE(2, GetName() << "\tPassword query for alias '" << alias 
			<< "' failed: SQL connection not active"
			);
		return password;
	}
	
	if (m_query.IsEmpty()) {
		PTRACE(2, GetName() << "\tPassword query for alias '" << alias 
			<< "' failed: Query string not configured"
			);
		return password;
	}
	
	PStringArray params;
	params += alias;
	GkSQLResult* result = m_sqlConn->ExecuteQuery(m_query, &params);
	if (result == NULL)
		PTRACE(2, GetName() << "\tPassword query for alias '" << alias 
			<< "' failed: Timeout or fatal error"
			);
	else {
		if (result->IsValid()) {
			PStringArray fields;
			if (result->GetNumRows() < 1)
				PTRACE(4, GetName() << "\tPassword not found for alias '" << alias << '\'');
			else if (result->GetNumFields() < 1)
				PTRACE(4, GetName() << "\tBad-formed query: no columns found in the result set");
			else if ((!result->FetchRow(fields)) || fields.GetSize() < 1)
				PTRACE(4, GetName() << "\tPassword query for alias '" << alias 
					<< "' failed: could not fetch the result row"
					);
			else
				password = fields[0];
		} else
			PTRACE(2, GetName() << "\tPassword query for alias '" << alias 
				<< "' failed (" << result->GetErrorCode() << "): "
				<< result->GetErrorMessage()
				);
		delete result;
	}
	
	return password;
}

// MySQLAliasAuth
MySQLAliasAuth::MySQLAliasAuth(
	PConfig* cfg, 
	const char* authName
	)
	: AliasAuth(cfg, authName), m_sqlConn(NULL), m_cache(NULL)
{
	GkSQLCreator* driverCreator = GkSQLCreator::FindDriver("MySQL");
	if (driverCreator == NULL) {
		PTRACE(1, GetName() << "\tModule creation failed: could not find driver for MySQL database");
		return;
	}
	
	m_cache = new CacheManager(cfg->GetInteger(authName, "CacheTimeout", -1));
	
	const PString password = cfg->GetString(authName, "Password", "");
	
	PString host = cfg->GetString(authName, "Host", "localhost");
	if (cfg->GetInteger(authName, "Port", -1) > 0)
		host += ":" + PString(cfg->GetInteger(authName, "Port", MYSQL_PORT));
		
	m_sqlConn = driverCreator->CreateConnection(authName);
	if (!m_sqlConn->Initialize(
			host,
			cfg->GetString(authName, "Database", "mysql"),
			cfg->GetString(authName, "User", "mysql"),
			password.IsEmpty() ? (const char*)NULL : (const char*)password,
			1, 1)) {
		delete m_sqlConn;
		m_sqlConn = NULL;
		PTRACE(2, GetName() << "\tModule creation failed: could not connect to the database");
		return;
	}
	
	const PString table = cfg->GetString(authName, "Table", "");
	const PString ipField = cfg->GetString(authName, "IPField", "");
	const PString aliasField = cfg->GetString(authName, "IDField", "");
	
	if (table.IsEmpty() || ipField.IsEmpty() || aliasField.IsEmpty()) {
		PTRACE(1, GetName() << "\tCannot build query: Table, IPField or IDField not specified");
		return;
	}
	
	m_query = "SELECT " + ipField + " FROM " + table + " WHERE " 
		+ aliasField + " = '%1'";
		
	const PString extraCrit = cfg->GetString(authName, "ExtraCriterion", "");
	if (!extraCrit.IsEmpty())
		m_query += " AND " + extraCrit;
		
	PTRACE(4, GetName() << "\tConfigured query: " << m_query);
}

MySQLAliasAuth::~MySQLAliasAuth()
{
	delete m_cache;
	delete m_sqlConn;
}

PString MySQLAliasAuth::GetConfigString(
	const PString& alias
	)
{
	PString authCondition;
	
	if (m_sqlConn == NULL) {
		PTRACE(2, GetName() << "\tQuery for alias '" << alias 
			<< "' failed: SQL connection not active"
			);
		return authCondition;
	}
	
	if (m_query.IsEmpty()) {
		PTRACE(2, GetName() << "\tQuery for alias '" << alias 
			<< "' failed: Query string not configured"
			);
		return authCondition;
	}
	
	if (m_cache == NULL || m_cache->Retrieve(alias, authCondition))
		return authCondition;
		
	PStringArray params;
	params += alias;
	GkSQLResult* result = m_sqlConn->ExecuteQuery(m_query, &params);
	if (result == NULL)
		PTRACE(2, GetName() << "\tQuery for alias '" << alias 
			<< "' failed: Timeout or fatal error"
			);
	else {
		if (result->IsValid()) {
			PStringArray fields;
			if (result->GetNumRows() < 1)
				PTRACE(4, GetName() << "\tEntry not found for alias '" << alias << '\'');
			else if (result->GetNumFields() < 1)
				PTRACE(4, GetName() << "\tBad-formed query: no columns found in the result set");
			else if ((!result->FetchRow(fields)) || fields.GetSize() < 1)
				PTRACE(4, GetName() << "\tQuery for alias '" << alias 
					<< "' failed: could not fetch the result row"
					);
			else {
				authCondition = fields[0];
				m_cache->Save(alias, authCondition);
			}
		} else
			PTRACE(2, GetName() << "\tQuery for alias '" << alias 
				<< "' failed (" << result->GetErrorCode() << "): "
				<< result->GetErrorMessage()
				);
		delete result;
	}
	return authCondition;
}

#endif // HAS_MYSQL

SQLPasswordAuth::SQLPasswordAuth(
	PConfig* cfg, 
	const char* authName
	)
	: SimplePasswordAuth(cfg, authName), m_sqlConn(NULL)
{
	const PString driverName = cfg->GetString(authName, "Driver", "");
	if (driverName.IsEmpty()) {
		PTRACE(1, GetName() << "\tModule creation failed: no SQL driver selected");
		return;
	}
	
	GkSQLCreator* driverCreator = GkSQLCreator::FindDriver(driverName);
	if (driverCreator == NULL) {
		PTRACE(1, GetName() << "\tModule creation failed: could not find " 
			<< driverName << " database driver"
			);
		return;
	}

	if (m_cache)
		m_cache->SetTimeout(cfg->GetInteger(authName, "CacheTimeout", 0));
		
	m_sqlConn = driverCreator->CreateConnection(authName);
	if (!m_sqlConn->Initialize(cfg, authName)) {
		delete m_sqlConn;
		m_sqlConn = NULL;
		PTRACE(2, GetName() << "\tModule creation failed: could not connect to the database");
		return;
	}
	
	m_query = cfg->GetString(authName, "Query", "");
	if (m_query.IsEmpty())
		PTRACE(1, GetName() << "\tModule creation failed: no query configured");
	else
		PTRACE(4, GetName() << "\tConfigured query: " << m_query);
}

SQLPasswordAuth::~SQLPasswordAuth()
{
	delete m_sqlConn;
}

PString SQLPasswordAuth::GetPassword(
	const PString& alias
	)
{
	PString password;
	
	if (m_sqlConn == NULL) {
		PTRACE(2, GetName() << "\tPassword query for alias '" << alias 
			<< "' failed: SQL connection not active"
			);
		return password;
	}
	
	if (m_query.IsEmpty()) {
		PTRACE(2, GetName() << "\tPassword query for alias '" << alias 
			<< "' failed: Query string not configured"
			);
		return password;
	}
	
	PStringArray params;
	params += alias;
	params += Toolkit::GKName();
	GkSQLResult* result = m_sqlConn->ExecuteQuery(m_query, &params);
	if (result == NULL)
		PTRACE(2, GetName() << "\tPassword query for alias '" << alias 
			<< "' failed: Timeout or fatal error"
			);
	else {
		if (result->IsValid()) {
			PStringArray fields;
			if (result->GetNumRows() < 1)
				PTRACE(4, GetName() << "\tPassword not found for alias '" << alias << '\'');
			else if (result->GetNumFields() < 1)
				PTRACE(4, GetName() << "\tBad-formed query: no columns found in the result set");
			else if ((!result->FetchRow(fields)) || fields.GetSize() < 1)
				PTRACE(4, GetName() << "\tPassword query for alias '" << alias 
					<< "' failed: could not fetch the result row"
					);
			else
				password = fields[0];
		} else
			PTRACE(2, GetName() << "\tPassword query for alias '" << alias 
				<< "' failed (" << result->GetErrorCode() << "): "
				<< result->GetErrorMessage()
				);
		delete result;
	}
	
	return password;
}

SQLAliasAuth::SQLAliasAuth(
	PConfig* cfg, 
	const char* authName
	)
	: AliasAuth(cfg, authName), m_sqlConn(NULL), m_cache(NULL)
{
	const PString driverName = cfg->GetString(authName, "Driver", "");
	if (driverName.IsEmpty()) {
		PTRACE(1, GetName() << "\tModule creation failed: no SQL driver selected");
		return;
	}
	
	GkSQLCreator* driverCreator = GkSQLCreator::FindDriver(driverName);
	if (driverCreator == NULL) {
		PTRACE(1, GetName() << "\tModule creation failed: could not find " 
			<< driverName << " database driver"
			);
		return;
	}

	m_cache = new CacheManager(config->GetInteger(authName, "CacheTimeout", 0));
	
	m_sqlConn = driverCreator->CreateConnection(authName);
	if (!m_sqlConn->Initialize(cfg, authName)) {
		delete m_sqlConn;
		m_sqlConn = NULL;
		PTRACE(2, GetName() << "\tModule creation failed: could not connect to the database");
		return;
	}
	
	m_query = cfg->GetString(authName, "Query", "");
	if (m_query.IsEmpty())
		PTRACE(1, GetName() << "\tModule creation failed: no query configured");
	else
		PTRACE(4, GetName() << "\tConfigured query: " << m_query);
}

SQLAliasAuth::~SQLAliasAuth()
{
	delete m_sqlConn;
	delete m_cache;
}

PString SQLAliasAuth::GetConfigString(
	const PString& alias
	)
{
	PString authCondition;
	
	if (m_sqlConn == NULL) {
		PTRACE(2, GetName() << "\tQuery for alias '" << alias 
			<< "' failed: SQL connection not active"
			);
		return authCondition;
	}
	
	if (m_query.IsEmpty()) {
		PTRACE(2, GetName() << "\tQuery for alias '" << alias 
			<< "' failed: Query string not configured"
			);
		return authCondition;
	}

	if (m_cache == NULL || m_cache->Retrieve(alias, authCondition))
		return authCondition;
	
	PStringArray params;
	params += alias;
	params += Toolkit::GKName();
	GkSQLResult* result = m_sqlConn->ExecuteQuery(m_query, &params);
	if (result == NULL)
		PTRACE(2, GetName() << "\tQuery for alias '" << alias 
			<< "' failed: Timeout or fatal error"
			);
	else {
		if (result->IsValid()) {
			PStringArray fields;
			if (result->GetNumRows() < 1)
				PTRACE(4, GetName() << "\tEntry not found for alias '" << alias << '\'');
			else if (result->GetNumFields() < 1)
				PTRACE(4, GetName() << "\tBad-formed query: no columns found in the result set");
			else if ((!result->FetchRow(fields)) || fields.GetSize() < 1)
				PTRACE(4, GetName() << "\tQuery for alias '" << alias 
					<< "' failed: could not fetch the result row"
					);
			else {
				authCondition = fields[0];
				m_cache->Save(alias, authCondition);
			}
		} else
			PTRACE(2, GetName() << "\tQuery for alias '" << alias 
				<< "' failed (" << result->GetErrorCode() << "): "
				<< result->GetErrorMessage()
				);
		delete result;
	}
	return authCondition;
}

#if ((defined(__GNUC__) && __GNUC__ <= 2) && !defined(WIN32))
// ExternalPasswordAuth

ExternalPasswordAuth::ExternalPasswordAuth(PConfig * cfg, const char * authName)
      : SimplePasswordAuth(cfg, authName)
{
	ExternalInit();
}

bool ExternalPasswordAuth::ExternalInit()
{
	const char *ExternalSec = "ExternalAuth";

	// Read the configuration
	Program = config->GetString(ExternalSec, "PasswordProgram", "");
	
	return true;
}

PString ExternalPasswordAuth::GetPassword(const PString & id)
{
	const int BUFFSIZE = 256;
	char buff[BUFFSIZE] = "";
	if (!Program) {
		procbuf proc(Program + " " + id, ios::in);
		istream istr(&proc);
		istr.getline(buff, BUFFSIZE);
	} else {
		PTRACE(2, "GkAuth\tProgram is not defined");
	}
	return PString(buff);
}

#endif // class ExternalPasswordAuth

// LDAP authentification
#if defined(HAS_LDAP)

LDAPPasswordAuth::LDAPPasswordAuth(PConfig * cfg, const char * authName)
  : SimplePasswordAuth(cfg, authName)
{
}

LDAPPasswordAuth::~LDAPPasswordAuth()
{
}

PString LDAPPasswordAuth::GetPassword(const PString & alias)
{
	PStringList attr_values;
	using namespace lctn; // LDAP config tags and names
	// get pointer to new answer object
	if(GkLDAP::Instance()->getAttribute(alias, H235PassWord, attr_values) && 
	   !attr_values.IsEmpty()){
		return attr_values[0];
	}
	return "";
}  

int LDAPPasswordAuth::Check(H225_RegistrationRequest & rrq, unsigned & reason)
{
	int result = SimplePasswordAuth::Check(rrq, reason);
	if(result == e_ok) {
		// check if all aliases in RRQ exists in LDAP entry
		const H225_ArrayOf_AliasAddress & aliases = rrq.m_terminalAlias;
		if(!GkLDAP::Instance()->validAliases(aliases)) {
			result = e_fail;
		}
	}
	return result;
}


LDAPAliasAuth::LDAPAliasAuth(PConfig *cfg, const char *authName) : AliasAuth(cfg, authName)
{
}

LDAPAliasAuth::~LDAPAliasAuth()
{
}

PString LDAPAliasAuth::GetConfigString(const PString &alias)
{
	PStringList attr_values;
	using namespace lctn; // LDAP config tags and names
	// get pointer to new answer object
	if (GkLDAP::Instance()->getAttribute(alias, IPAddress, attr_values) && (!attr_values.IsEmpty())) {
		PString ip = attr_values[0];
    		if(!ip.IsEmpty()){
      			PString port = GK_DEF_ENDPOINT_SIGNAL_PORT;    
			return "sigip:" + ip + ":" + port;
		}
	}
	return "";
}

int LDAPAliasAuth::Check(H225_RegistrationRequest & rrq, unsigned & reason)
{
	int result = AliasAuth::Check(rrq, reason);
	if(result == e_ok) {
		// check if all aliases in RRQ exists in LDAP entry
		const H225_ArrayOf_AliasAddress & aliases = rrq.m_terminalAlias;
		if(!GkLDAP::Instance()->validAliases(aliases)) {
      			result = e_fail;
    		}
	}
  	return result;
}

#endif // HAS_LDAP

#if defined HAS_WLDAP

LDAPPasswordAuth::LDAPPasswordAuth(PConfig * cfg, const char * authName) : SimplePasswordAuth(cfg, authName)
{
}

LDAPPasswordAuth::~LDAPPasswordAuth()
{
}

PString LDAPPasswordAuth::GetPassword(const PString & alias) // called by SimplePasswordAuth::InternalGetPassword()
{ 
	LDAPAnswer * answer = new LDAPAnswer;
	bool CheckAliases = false;
	bool CheckStatus = false;
	PStringArray attributes;

	// attributes to retrieve
	using namespace lctn;
	attributes += LDAPAttrTags[H235PassWord];
	if (CheckStatus = (Toolkit::AsBool(GkConfig()->GetString(ldap_auth_sec, "LdapPasswAuthCheckAccountStatus", "0"))))
		attributes += LDAPAttrTags[AccountStatus];
	if (CheckAliases = (Toolkit::AsBool(GkConfig()->GetString(ldap_auth_sec, "LdapAliasAuthCheckAliases", "0")))) {
		attributes += LDAPAttrTags[H323ID];
		attributes += LDAPAttrTags[TelephonNo];
	}
	attribute_count = GkLDAP::Instance()->getAttributes(alias, attributes, answer);

	// don't bother to get attr values
	// if attribute_count is zero
	if(attribute_count) {
		// extract attribute values
		PStringList attrib_values;
		int attrvalues_num = (GkLDAP::Instance()->getAttributeValues(answer, H235PassWord, attrib_values));
		if(!attrib_values.IsEmpty())
			epH235Password_value = attrib_values[0];

		if(CheckStatus) {
			attrib_values.RemoveAll();
			attrvalues_num = (GkLDAP::Instance()->getAttributeValues(answer, AccountStatus, attrib_values));
			if(!attrib_values.IsEmpty())
				epAccountStatus_value = attrib_values[0];
		}

		if(CheckAliases) {
			attrib_values.RemoveAll();
			attrvalues_num = (GkLDAP::Instance()->getAttributeValues(answer, H323ID, attrib_values));
			if(!attrib_values.IsEmpty())
				epH323ID_value = attrib_values[0];

			attrvalues_num = (GkLDAP::Instance()->getAttributeValues(answer, TelephonNo, epTelephoneNo_value));
		}
	}
	delete answer;

	// success
	if(attribute_count && !epH235Password_value.IsEmpty())
		return epH235Password_value;

	// failed
	return "";
}  

int LDAPPasswordAuth::Check(H225_RegistrationRequest & rrq, unsigned & reason)
{
	// reset data class members
	attribute_count = 0;
	epAccountStatus_value = "";
	epH323ID_value = "";
	epTelephoneNo_value.RemoveAll();
	epH235Password_value = "";

	// reqired, sets global aliases
	checkid = true;

	// get the password for alias
	int result = SimplePasswordAuth::Check(rrq, reason);

	// success - do further checks
	if(result == e_ok && attribute_count > 0) {
		// check account status
		// Note: empty authentication attribute values indicate the
		//       password was cached, so the check below would fail!
		if (Toolkit::AsBool(GkConfig()->GetString(ldap_auth_sec, "LdapPasswAuthCheckAccountStatus", "0"))) {
			if(!epAccountStatus_value.IsEmpty()) {
				const PString cfgString = epAccountStatus_value;
				PTRACE(4, "Gk\tLDAPPasswordAuth::Check() '" << cfgString << "' " << epH323ID_value);
				if (!cfgString) {
					if (doCheckAccountStatus(rrq.m_callSignalAddress, cfgString)) {
						result = e_ok;
					} 
					else {
						PTRACE(4, "Gk\tLDAPAliasAuth::Check() condition '" << cfgString << "' rejected endpoint " << epH323ID_value);
						return e_fail;
					}
				}
			}
		} // check account status
	
		if (Toolkit::AsBool(GkConfig()->GetString(ldap_auth_sec, "LdapPasswAuthCheckAliases", "0"))) {
			// check if all aliases in RRQ exists in LDAP entry only if
			// autentication attribute values are valid. see note below
			// Note: empty authentication attribute values indicate the
			//       password was cached, so the check below would fail!
			if(!epH323ID_value.IsEmpty() &&
			   !epTelephoneNo_value.IsEmpty()) {
				const H225_ArrayOf_AliasAddress & aliases = rrq.m_terminalAlias;
				if(!GkLDAP::Instance()->validateAliases(aliases, epH323ID_value, epTelephoneNo_value)) {
					PTRACE(4, "Gk\tLDAPPasswordAuth::Check() validateAliases() failed");
					return e_fail;
				}
				PTRACE(4, "Gk\tLDAPPasswordAuth::Check() validateAliases() success");
			}
		}
	} // result == e_ok & attribute_count
	
	return result;
}

bool LDAPPasswordAuth::doCheckAccountStatus(const H225_ArrayOf_TransportAddress & addrs, const PString & cfgString)
{
	const PStringArray conditions(cfgString.Tokenise("&|", FALSE));
	for (PINDEX i = 0; i < conditions.GetSize(); ++i) {
		for (PINDEX j = 0; j < addrs.GetSize(); ++j) {
			if (AuthCondition(addrs[j], conditions[i])) {
				PTRACE(4, "Gk\tLDAPPasswordAuth::doCheckAccountStatus() '" << conditions[i] << "' applied successfully for endpoint " << AsDotString(addrs[j]));
				return true;
			}
		}
	}
	return false;
}

bool LDAPPasswordAuth::AuthCondition(const H225_TransportAddress & SignalAdr, const PString & Condition)
{
	const bool ON_ERROR = false; // return value on parse error in condition

	const PStringArray rule = Condition.Tokenise(":", FALSE);
	if (rule.GetSize() < 1) {
		PTRACE(1, "LDAPPasswordAuth::AuthCondition() Errornous RRQAuth rule: " << Condition);
		return ON_ERROR;
	}
	
	// 
	// condition = rule[0]:rule[1]... = rName:params...
	//
	
	const PString &rName = rule[0];

 	if (rName=="confirm" || rName=="allow") {
 		return true;
 	}
 	else if (rName=="reject" || rName=="deny" || rName=="forbid") {
 		return false;
 	}
	//
	// condition 'sigaddr' example:
	//   sigaddr:.*ipAddress .* ip = .* c3 47 e2 a2 .*port = 1720.*
	//
	else if (rName=="sigaddr") {
		if(rule.GetSize() < 2)
			return false;
		return Toolkit::MatchRegex(AsString(SignalAdr), rule[1]) != 0;
	}
	//
	// condition 'sigip' example:
	//   sigip:195.71.129.69:1720
	//
	else if (rName=="sigip") {
		if (rule.GetSize() < 2)
			return false;
		PIPSocket::Address ip;
		PIPSocket::GetHostAddress(rule[1], ip);
		WORD port = (rule.GetSize() < 3) ? GK_DEF_ENDPOINT_SIGNAL_PORT : (WORD)rule[2].AsInteger();
		return (SignalAdr == SocketToH225TransportAddr(ip, port));
	} else {
		PTRACE(4, "Gk\tLDAPPasswordAuth::AuthCondition() Unknown RRQAuth condition: " << Condition);
		return ON_ERROR;
	}

	// not reached...
	return false;
}

LDAPAliasAuth::LDAPAliasAuth(PConfig *cfg, const char *authName) : AliasAuth(cfg, authName)
{
}

LDAPAliasAuth::~LDAPAliasAuth()
{
}

int LDAPAliasAuth::Check(H225_RegistrationRequest & rrq, unsigned & reason)
{
	int result;
	bool AliasFoundInLDAP = false;

	if (!rrq.HasOptionalField(H225_RegistrationRequest::e_terminalAlias))
		return defaultStatus;

	const H225_ArrayOf_AliasAddress & NewAliases = rrq.m_terminalAlias;
	PStringList AliasList;

	// alias search presedence e164/h323id
	if (Toolkit::AsBool(GkConfig()->GetString(ldap_auth_sec, "LdapAliasAuthSearchPrecedenceE164", "0"))) {
		PString h323id;
		for (PINDEX j = 0; j < NewAliases.GetSize(); ++j) {
			if (NewAliases[j].GetTag() == H225_AliasAddress::e_dialedDigits)
				AliasList.AppendString(H323GetAliasAddressString(NewAliases[j]));
			else if (NewAliases[j].GetTag() == H225_AliasAddress::e_h323_ID)
				h323id = H323GetAliasAddressString(NewAliases[j]);
		}
		AliasList.AppendString(h323id);
	}
	else {
		for (PINDEX j = 0; j < NewAliases.GetSize(); ++j) {
			AliasList.AppendString(H323GetAliasAddressString(NewAliases[j]));
		}
	}

	// step through alias list and search LDAP
	for (PINDEX i = 0; i < AliasList.GetSize(); ++i) {
		// attributes values
		int attribute_count;
		PString epAccountStatus_value;
		PString epH323ID_value;
		PStringList epTelephoneNo_value;

		LDAPAnswer * answer = new LDAPAnswer;
		bool CheckAliases = false;
		PStringArray attributes;

		// attributes to return
		using namespace lctn;
		attributes += LDAPAttrTags[AccountStatus];
		if (CheckAliases = (Toolkit::AsBool(GkConfig()->GetString(ldap_auth_sec, "LdapAliasAuthCheckAliases", "0")))) {
			attributes += LDAPAttrTags[H323ID];
			attributes += LDAPAttrTags[TelephonNo];
		}
		attribute_count = GkLDAP::Instance()->getAttributes(AliasList[i], attributes, answer);

		// don't bother to get attr values
		// if attribute_count is zero
		if(attribute_count) {
			// extract attribute values
			PStringList attrib_values;
			int attrvalues_num = (GkLDAP::Instance()->getAttributeValues(answer, AccountStatus, attrib_values));
			if(!attrib_values.IsEmpty())
				epAccountStatus_value = attrib_values[0];

			if(CheckAliases) {
				attrib_values.RemoveAll();
				attrvalues_num = (GkLDAP::Instance()->getAttributeValues(answer, H323ID, attrib_values));
				if(!attrib_values.IsEmpty())
					epH323ID_value = attrib_values[0];

				attrvalues_num = (GkLDAP::Instance()->getAttributeValues(answer, TelephonNo, epTelephoneNo_value));
			}
		}
		delete answer;

		// LDAP success
		if(attribute_count > 0 && !epAccountStatus_value.IsEmpty()) {
			const PString cfgString = epAccountStatus_value;
			PTRACE(5, "Gk\tLDAPAliasAuth::Check() '" << cfgString << "' " << AliasList[i]);
			if (!cfgString) {
				if (doCheck(rrq.m_callSignalAddress, cfgString)) {
					AliasFoundInLDAP = true;
					result = e_ok;

					// check if all aliases in RRQ exists in LDAP entry = second step
					if(CheckAliases) {
						if(!epH323ID_value.IsEmpty() &&
						   !epTelephoneNo_value.IsEmpty()) {
							const H225_ArrayOf_AliasAddress & aliases = rrq.m_terminalAlias;
							if(!GkLDAP::Instance()->validateAliases(aliases, epH323ID_value, epTelephoneNo_value)) {
								PTRACE(4, "Gk\tLDAPAliasAuth::Check() validateAliases() failed");
								return e_fail;
							}
							PTRACE(4, "Gk\tLDAPAliasAuth::Check() validateAliases() success");
						}
						else {
							PTRACE(4, "Gk\tLDAPAliasAuth::Check() one or more aliases missing");
							return e_fail;
						}
					}
					break;
				} 
				else {
					PTRACE(4, "Gk\tLDAPAliasAuth::Check() condition '" << cfgString << "' rejected endpoint " << AliasList[i]);
					return e_fail;
				}
			}
		} // if 
	} // for
	
	// return success/failure 
	result = (AliasFoundInLDAP) ? e_ok : defaultStatus;
	return result;
}
#endif // HAS_WLDAP

// AliasAuth
AliasAuth::AliasAuth(PConfig *cfg, const char *authName) : GkAuthenticator(cfg, authName)
{
}

int AliasAuth::Check(const H225_GatekeeperRequest &, unsigned &)
{
	return e_next;
}

int AliasAuth::Check(H225_RegistrationRequest & rrq, unsigned &)
{
	bool AliasFoundInConfig = false;

	if (!rrq.HasOptionalField(H225_RegistrationRequest::e_terminalAlias))
		return defaultStatus;

	const H225_ArrayOf_AliasAddress & NewAliases = rrq.m_terminalAlias;

	// alias is the config file entry of this endpoint
	for (PINDEX i = 0; i <= NewAliases.GetSize(); ++i) {
		const PString alias = (i < NewAliases.GetSize()) ? AsString(NewAliases[i], FALSE) : PString("default");
		const PString cfgString = GetConfigString(alias);
		if (!cfgString) {
			if (doCheck(rrq.m_callSignalAddress, cfgString)) {
				AliasFoundInConfig = true;
				break;
			} else {
				PTRACE(4, "Gk\tRRQAuth condition '" << cfgString << "' rejected endpoint " << alias);
				return e_fail;
			}
		}
	}
	return (AliasFoundInConfig) ? e_ok : defaultStatus;
}

int AliasAuth::Check(const H225_UnregistrationRequest &, unsigned &)
{
	return e_next;
}

int AliasAuth::Check(H225_AdmissionRequest &, unsigned &)
{
	return e_next;
}

int AliasAuth::Check(const H225_BandwidthRequest &, unsigned &)
{
	return e_next;
}

int AliasAuth::Check(const H225_DisengageRequest &, unsigned &)
{
	return e_next;
}

int AliasAuth::Check(const H225_LocationRequest &, unsigned &)
{
	return e_next;
}

int AliasAuth::Check(const H225_InfoRequest &, unsigned &)
{
	return e_next;
}

PString AliasAuth::GetConfigString(const PString & alias)
{
	return config->GetString("RasSrv::RRQAuth", alias, "");
}

bool AliasAuth::doCheck(const H225_ArrayOf_TransportAddress & addrs, const PString & cfgString)
{
	const PStringArray conditions(cfgString.Tokenise("&|", FALSE));
	for (PINDEX i = 0; i < conditions.GetSize(); ++i) {
		for (PINDEX j = 0; j < addrs.GetSize(); ++j) {
			if (AuthCondition(addrs[j], conditions[i])) {
				PTRACE(4, "Gk\tRRQAuth condition '" << conditions[i] << "' applied successfully for endpoint " << AsDotString(addrs[j]));
				return true;
			}
		}
	}
	return false;
}

bool AliasAuth::AuthCondition(const H225_TransportAddress & SignalAdr, const PString & Condition)
{
	const bool ON_ERROR = false; // return value on parse error in condition

	const PStringArray rule = Condition.Tokenise(":", FALSE);
	if (rule.GetSize() < 1) {
		PTRACE(1, "Errornous RRQAuth rule: " << Condition);
		return ON_ERROR;
	}
	
	// 
	// condition = rule[0]:rule[1]... = rName:params...
	//
	
	const PString &rName = rule[0];

 	if (rName=="confirm" || rName=="allow") {
 		return true;
 	}
 	else if (rName=="reject" || rName=="deny" || rName=="forbid") {
 		return false;
 	}
	//
	// condition 'sigaddr' example:
	//   sigaddr:.*ipAddress .* ip = .* c3 47 e2 a2 .*port = 1720.*
	//
	else if (rName=="sigaddr") {
		if(rule.GetSize() < 2)
			return false;
		return Toolkit::MatchRegex(AsString(SignalAdr), rule[1]) != 0;
	}
	//
	// condition 'sigip' example:
	//   sigip:195.71.129.69:1720
	//
	else if (rName=="sigip") {
		if (rule.GetSize() < 2)
			return false;
		PIPSocket::Address ip;
		PIPSocket::GetHostAddress(rule[1], ip);
		WORD port = (rule.GetSize() < 3) ? GK_DEF_ENDPOINT_SIGNAL_PORT : (WORD)rule[2].AsInteger();
		return (SignalAdr == SocketToH225TransportAddr(ip, port));
	} else {
		PTRACE(4, "Unknown RRQAuth condition: " << Condition);
		return ON_ERROR;
	}

	// not reached...
	return false;
}

// Help classes for PrefixAuth
static const char* const prfflag="prf:";
static const char* const allowflag="allow";
static const char* const denyflag="deny";
static const char* const ipflag="ipv4:";
static const char* const aliasflag="alias:";

class AuthObj { // abstract class
public:
	virtual ~AuthObj() {}

	virtual bool IsValid() const { return true; }

	virtual PStringArray GetPrefixes() const = 0;

	virtual PIPSocket::Address GetIP() const = 0;
	virtual PString GetAliases() const = 0;
};

class RRQAuthObj : public AuthObj {
public:
	RRQAuthObj(const H225_RegistrationRequest & ras) : rrq(ras) {}

	virtual PStringArray GetPrefixes() const;

	virtual PIPSocket::Address GetIP() const;
	virtual PString GetAliases() const;

private:
	const H225_RegistrationRequest & rrq;
};

class ARQAuthObj : public AuthObj {
public:
	ARQAuthObj(const H225_AdmissionRequest & ras);

	virtual bool IsValid() const { return ep; }

	virtual PStringArray GetPrefixes() const;

	virtual PIPSocket::Address GetIP() const;
	virtual PString GetAliases() const;

private:
	const H225_AdmissionRequest & arq;
	endptr ep;
};

ARQAuthObj::ARQAuthObj(const H225_AdmissionRequest & ras) : arq(ras)
{
	ep = RegistrationTable::Instance()->FindByEndpointId(arq.m_endpointIdentifier);
}

PStringArray ARQAuthObj::GetPrefixes() const
{
	PStringArray array;
	if (arq.HasOptionalField(H225_AdmissionRequest::e_destinationInfo))
		if (PINDEX ss = arq.m_destinationInfo.GetSize() > 0) {
			array.SetSize(ss);
			for (PINDEX i = 0; i < ss; ++i)
				array[i] = AsString(arq.m_destinationInfo[i], FALSE);
		}
	if (array.GetSize() == 0)
		// let empty destinationInfo match the ALL rule
		array.SetSize(1);

	return array;
}

PIPSocket::Address ARQAuthObj::GetIP() const
{
	PIPSocket::Address result;
	const H225_TransportAddress & addr = (arq.HasOptionalField(H225_AdmissionRequest::e_srcCallSignalAddress)) ?
		arq.m_srcCallSignalAddress : ep->GetCallSignalAddress();
	GetIPFromTransportAddr(addr, result);
	return result;
}

PString ARQAuthObj::GetAliases() const
{
	return AsString(ep->GetAliases());
}

class LRQAuthObj : public AuthObj {
public:
	LRQAuthObj(const H225_LocationRequest & ras);

	virtual PStringArray GetPrefixes() const;

	virtual PIPSocket::Address GetIP() const;
	virtual PString GetAliases() const;

private:
	const H225_LocationRequest & lrq;
	PIPSocket::Address ipaddress;
};

LRQAuthObj::LRQAuthObj(const H225_LocationRequest & ras) : lrq(ras)
{
	GetIPFromTransportAddr(lrq.m_replyAddress, ipaddress);
}

PStringArray LRQAuthObj::GetPrefixes() const
{
	PStringArray array;
	if (PINDEX ss = lrq.m_destinationInfo.GetSize() > 0) {
		array.SetSize(ss);
		for (PINDEX i = 0; i < ss; ++i)
			array[i] = AsString(lrq.m_destinationInfo[i], FALSE);
	}
	return array;
}

PIPSocket::Address LRQAuthObj::GetIP() const
{
	return ipaddress;
}

PString LRQAuthObj::GetAliases() const
{
	return (lrq.HasOptionalField(H225_LocationRequest::e_sourceInfo)) ? AsString(lrq.m_sourceInfo) : PString();
}


class AuthRule {
public:
	enum Result {
		e_nomatch,
		e_allow,
		e_deny
	};

	AuthRule(Result f, bool r) : priority(1000), fate(f), inverted(r), next(0) {}
	virtual ~AuthRule() { delete next; }

	virtual bool Match(const AuthObj &) = 0;
	int Check(const AuthObj &);
	
	bool operator<(const AuthRule & o) const { return priority < o.priority; }
	void SetNext(AuthRule *n) { next = n; }

//	virtual PString GetName() const { return PString(); }

protected:
	int priority; // the lesser the value, the higher the priority

private:
	Result fate;
	bool inverted;
	AuthRule *next;
};

int AuthRule::Check(const AuthObj & aobj)
{
//	PTRACE(3, "auth\t" << GetName());
	return (Match(aobj) ^ inverted) ? fate : (next) ? next->Check(aobj) : e_nomatch;
}

inline void delete_rule(PrefixAuth::Rules::value_type r)
{
	delete r.second;
}

class NullRule : public AuthRule {
public:
	NullRule() : AuthRule(e_nomatch, false) {}
	virtual bool Match(const AuthObj &) { return false; }
};

class IPv4AuthRule : public AuthRule {
public:
	IPv4AuthRule(Result, const PString &, bool);

private:
	virtual bool Match(const AuthObj &);
//	virtual PString GetName() const { return network.AsString() + "/" + PString(PString::Unsigned, 32-priority); }

	PIPSocket::Address network, netmask;
};

IPv4AuthRule::IPv4AuthRule(Result f, const PString & cfg, bool r) : AuthRule(f, r)
{
	Toolkit::GetNetworkFromString(cfg, network, netmask);
	DWORD n = ~PIPSocket::Net2Host(DWORD(netmask));
	for (priority = 0; n; n >>= 1)
		++priority;
}

bool IPv4AuthRule::Match(const AuthObj & aobj)
{
	return ((aobj.GetIP() & netmask) == network);
}

class AliasAuthRule : public AuthRule {
public:
	AliasAuthRule(Result f, const PString & cfg, bool r) : AuthRule(f, r), pattern(cfg) { priority = -1; }

private:
	virtual bool Match(const AuthObj &);
//	virtual PString GetName() const { return pattern; }

	PString pattern;
};

bool AliasAuthRule::Match(const AuthObj & aobj)
{
	return (aobj.GetAliases().FindRegEx(pattern) != P_MAX_INDEX);
}

inline bool is_inverted(const PString & cfg, PINDEX p)
{
	return (p > 1) ? cfg[p-1] == '!' : false;
}

inline bool comp_authrule_priority(AuthRule *a1, AuthRule *a2)
{
	return *a1 < *a2;
}

// PrefixAuth
PrefixAuth::PrefixAuth(PConfig *cfg, const char *authName)
      : GkAuthenticator(cfg, authName)
{
	int ipfl = strlen(ipflag), aliasfl = strlen(aliasflag);
	PStringToString cfgs=cfg->GetAllKeyValues("PrefixAuth");
	for (PINDEX i = 0; i < cfgs.GetSize(); ++i) {
		PString key = cfgs.GetKeyAt(i);
		if (key *= "default") {
			defaultStatus = Toolkit::AsBool(cfgs.GetDataAt(i)) ? e_ok : e_fail;
			continue;
		} else if (key *= "ALL") {
			// use space (0x20) as the key so it will be the last resort
			key = " ";
		}
		if (prefrules.find(key) != prefrules.end())
			continue; //rule already exists? ignore

		PStringArray rules = cfgs.GetDataAt(i).Tokenise("|", FALSE);
		PINDEX sz = rules.GetSize();
		if (sz < 1)
			continue;
		//AuthRule *rls[sz];
		AuthRule **rls = new AuthRule *[sz];
		for (PINDEX j = 0; j < sz; ++j) {
			PINDEX pp;
			// if not allowed, assume denial
			AuthRule::Result ft = (rules[j].Find(allowflag) != P_MAX_INDEX) ? AuthRule::e_allow : AuthRule::e_deny;
			if ((pp=rules[j].Find(ipflag)) != P_MAX_INDEX)
				rls[j] = new IPv4AuthRule(ft, rules[j].Mid(pp+ipfl), is_inverted(rules[j], pp));
			else if ((pp=rules[j].Find(aliasflag)) != P_MAX_INDEX)
				rls[j] = new AliasAuthRule(ft, rules[j].Mid(pp+aliasfl), is_inverted(rules[j], pp));
			else
				rls[j] = new NullRule;
		}

		// sort the rules by priority
		stable_sort(rls, rls + sz, comp_authrule_priority);
		for (PINDEX k = 1; k < sz; ++k)
			rls[k-1]->SetNext(rls[k]);
		prefrules[key] = rls[0];
		delete [] rls;
	}
}

PrefixAuth::~PrefixAuth()
{
	for_each(prefrules.begin(), prefrules.end(), delete_rule);
}

int PrefixAuth::Check(const H225_GatekeeperRequest &, unsigned &)
{
	return e_next;
}

int PrefixAuth::Check(H225_RegistrationRequest & /*rrq*/, unsigned &)
{
	return e_next;
}

int PrefixAuth::Check(const H225_UnregistrationRequest &, unsigned &)
{
	return e_next;
}

int PrefixAuth::Check(H225_AdmissionRequest & arq, unsigned &)
{
	return CallTable::Instance()->FindCallRec(arq.m_callIdentifier) ? e_ok : doCheck(ARQAuthObj(arq));
}

int PrefixAuth::Check(const H225_BandwidthRequest &, unsigned &)
{
	return e_next;
}

int PrefixAuth::Check(const H225_DisengageRequest &, unsigned &)
{
	return e_next;
}

int PrefixAuth::Check(const H225_LocationRequest & lrq, unsigned &)
{
	return doCheck(LRQAuthObj(lrq));
}

int PrefixAuth::Check(const H225_InfoRequest &, unsigned &)
{
	return e_next;
}

struct comp_pref { // function object
	comp_pref(const PString & s) : value(s) {}
	bool operator()(const PrefixAuth::Rules::value_type & v) const;
	const PString & value;
};

inline bool comp_pref::operator()(const PrefixAuth::Rules::value_type & v) const
{
	return (value.Find(v.first) == 0) || (v.first *= " ");
}

int PrefixAuth::doCheck(const AuthObj & aobj)
{
	if (!aobj.IsValid())
		return e_fail;
	PStringArray ary(aobj.GetPrefixes());
	for (PINDEX i = 0; i < ary.GetSize(); ++i) {
		// find the first match rule
		// since prefrules is descendently sorted
		// it must be the most specific prefix
		for (Rules::iterator j = prefrules.begin(); j != prefrules.end(); ++j) {
			Rules::iterator iter = find_if(j, prefrules.end(), comp_pref(ary[i]));
			if (iter == prefrules.end())
				break;
			switch (iter->second->Check(aobj))
			{
				case AuthRule::e_allow:
					return e_ok;
				case AuthRule::e_deny:
					return e_fail;
				default: // try next prefix...
					j = iter;
			}
		}
	}
	return defaultStatus;
}

namespace {
std::list<GkAuthInitializer *> *AuthNameList;
}

GkAuthInitializer::GkAuthInitializer(const char *n) : name(n)
{
	static std::list<GkAuthInitializer *> aList;
	AuthNameList = &aList;

	AuthNameList->push_back(this);
}

GkAuthInitializer::~GkAuthInitializer()
{
}

bool GkAuthInitializer::Compare(PString n) const
{
	return n == name;
}

GkAuthenticatorList::GkAuthenticatorList(PConfig *cfg)
{
	PStringList authList(cfg->GetKeys(GkAuthSectionName));

	for (PINDEX i=authList.GetSize(); i-- > 0; ) {
		PString authName(authList[i]);
		std::list<GkAuthInitializer *>::iterator Iter =
			find_if(AuthNameList->begin(), AuthNameList->end(),
				bind2nd(mem_fun(&GkAuthInitializer::Compare), authName));
		if (Iter != AuthNameList->end())
			(*Iter)->CreateAuthenticator(cfg);
#if PTRACING
		else
			PTRACE(1, "GkAuth\tUnknown auth " << authName << ", ignore!");
#endif
	}
	
	m_mechanisms = new H225_ArrayOf_AuthenticationMechanism;
	m_algorithmOIDs = new H225_ArrayOf_PASN_ObjectId;
	GkAuthenticator* authenticator = GetHead();
	BOOL found = FALSE;
			
	// scan all authenticators that are either "required" or "sufficient"
	// (skip "optional") and fill #mechanisms# and #algorithmOIDs# arrays
	// with H.235 capabilities that are supported by all these authenticators
	while( authenticator )
	{
		if( authenticator->IsH235Capable() 
			&& ((authenticator->GetControlFlag() == GkAuthenticator::e_Required)
				|| (authenticator->GetControlFlag() == GkAuthenticator::e_Sufficient)) )
		{
			if( m_mechanisms->GetSize() == 0 )
			{
				// append H.235 capability to empty arrays
				authenticator->GetH235Capability(
					*m_mechanisms, *m_algorithmOIDs
					);
				// should never happen, but we should check just for a case				
				if( m_algorithmOIDs->GetSize() == 0 )
					m_mechanisms->RemoveAll();
				else
					found = TRUE;
				authenticator = authenticator->GetNext();
				continue;
			}
			
			// Already have H.235 capabilities - check the current
			// authenticator if it supports any of the capabilities.
			// Remove capabilities that are not supported
			
			H225_ArrayOf_AuthenticationMechanism matchedMechanisms;
			
			int i, j, k;
			
			for( i = 0; i < m_algorithmOIDs->GetSize(); i++ )
			{
				BOOL matched = FALSE;
			
				for( j = 0; j < m_mechanisms->GetSize(); j++ )
					if( authenticator->IsH235Capability(
							(*m_mechanisms)[j], (*m_algorithmOIDs)[i]
							) )
					{
						for( k = 0; k < matchedMechanisms.GetSize(); k++ )
							if( matchedMechanisms[k].GetTag() == (*m_mechanisms)[j].GetTag() )
								break;
						if( k == matchedMechanisms.GetSize() )
						{
							matchedMechanisms.SetSize(k+1);
							matchedMechanisms[k].SetTag((*m_mechanisms)[j].GetTag());
						}
						matched = TRUE;
					}
				
				if( !matched )
				{
					PTRACE(5,"GkAuth\tRemoved from GCF list algorithm OID: "<<(*m_algorithmOIDs)[i]);
					m_algorithmOIDs->RemoveAt(i);
					i--;
				}
			}
			
			for( i = 0; i < m_mechanisms->GetSize(); i++ )
			{
				for( j = 0; j < matchedMechanisms.GetSize(); j++ )
					if( (*m_mechanisms)[i].GetTag() == matchedMechanisms[j].GetTag() )
						break;
				if( j == matchedMechanisms.GetSize() )
				{
					PTRACE(5,"GkAuth\tRemoved from GCF list mechanism: "<<(*m_mechanisms)[i]);
					m_mechanisms->RemoveAt(i);
					i--;
				}
			}
			
			if( (m_mechanisms->GetSize() == 0) || (m_algorithmOIDs->GetSize() == 0) )
			{
				PTRACE(4,"GkAuth\tConflicting H.235 capabilities are active"
					<<" - GCF will not select any particular capability"
					);
				m_mechanisms->RemoveAll();
				m_algorithmOIDs->RemoveAll();
				break;
			}
		}
		authenticator = authenticator->GetNext();
	}

	// Scan "optional" authenticators if the above procedure has not found
	// any H.235 capabilities or has found more than one
	if( (!found) || (m_mechanisms->GetSize() > 1) || (m_algorithmOIDs->GetSize() > 1) )
	{
		authenticator = GetHead();
		while( authenticator )
		{
			if( authenticator->IsH235Capable() 
				&& (authenticator->GetControlFlag() == GkAuthenticator::e_Optional) )
			{
				if( m_mechanisms->GetSize() == 0 )
				{
					authenticator->GetH235Capability(
						(*m_mechanisms), (*m_algorithmOIDs)
						);
					if( m_algorithmOIDs->GetSize() == 0 )
						m_mechanisms->RemoveAll();
					else
						found = TRUE;
					authenticator = authenticator->GetNext();
					continue;
				}
			
				H225_ArrayOf_AuthenticationMechanism matchedMechanisms;
			
				int i, j, k;
			
				for( i = 0; i < m_algorithmOIDs->GetSize(); i++ )
				{
					BOOL matched = FALSE;
			
					for( j = 0; j < m_mechanisms->GetSize(); j++ )
						if( authenticator->IsH235Capability(
								(*m_mechanisms)[j], (*m_algorithmOIDs)[i]
								) )
						{
							for( k = 0; k < matchedMechanisms.GetSize(); k++ )
								if( matchedMechanisms[k].GetTag() == (*m_mechanisms)[j].GetTag() )
									break;
							if( k == matchedMechanisms.GetSize() )
							{
								matchedMechanisms.SetSize(k+1);
								matchedMechanisms[k].SetTag((*m_mechanisms)[j].GetTag());
							}
							matched = TRUE;
						}
				
					if( !matched )
					{
						PTRACE(5,"GkAuth\tRemoved from GCF list algorithm OID: "<<(*m_algorithmOIDs)[i]);
						m_algorithmOIDs->RemoveAt(i);
						i--;
					}
				}
			
				for( i = 0; i < m_mechanisms->GetSize(); i++ )
				{
					for( j = 0; j < matchedMechanisms.GetSize(); j++ )
						if( (*m_mechanisms)[i].GetTag() == matchedMechanisms[j].GetTag() )
							break;
					if( j == matchedMechanisms.GetSize() )
					{
						PTRACE(5,"GkAuth\tRemoved from GCF list mechanism: "<<(*m_mechanisms)[i]);
						m_mechanisms->RemoveAt(i);
						i--;
					}
				}
			
				if( (m_mechanisms->GetSize() == 0) || (m_algorithmOIDs->GetSize() == 0) )
				{
					PTRACE(4,"GkAuth\tConflicting H.235 capabilities are active"
						<<" - GCF will not select any particular capability"
						);
					m_mechanisms->RemoveAll();
					m_algorithmOIDs->RemoveAll();
					break;
				}
			}
			authenticator = authenticator->GetNext();
		}
	}
	
	if( PTrace::CanTrace(5) )
		if( (m_mechanisms->GetSize() > 0) && (m_algorithmOIDs->GetSize() > 0) )
		{
			ostream& strm = PTrace::Begin(5,__FILE__,__LINE__);
			strm <<"GkAuth\tH.235 capabilities selected for GCF:\n";
			strm <<"\tAuthentication mechanisms: \n";
			int i;
			for( i = 0; i < m_mechanisms->GetSize(); i++ )
				strm << "\t\t" << (*m_mechanisms)[i] << '\n';
			strm <<"\tAuthentication algorithm OIDs: \n";
			for( i = 0; i < m_algorithmOIDs->GetSize(); i++ )
				strm << "\t\t" << (*m_algorithmOIDs)[i] << '\n';
			PTrace::End(strm);
		}
}

GkAuthenticatorList::~GkAuthenticatorList()
{	
	delete GkAuthenticator::head;
	GkAuthenticator::head = 0;
	
	delete m_mechanisms;
	delete m_algorithmOIDs;
}

void GkAuthenticatorList::GetH235Capabilities(
	H225_ArrayOf_AuthenticationMechanism& mechanisms,
	H225_ArrayOf_PASN_ObjectId& algorithmOIDs
	) const
{
	mechanisms = *m_mechanisms;
	algorithmOIDs = *m_algorithmOIDs;
}

namespace {
GkAuthInit<GkAuthenticator> DefaultAuthFactory("default");
GkAuthInit<AliasAuth> AliasAuthFactory("AliasAuth");
GkAuthInit<PrefixAuth> PrefixAuthFactory("PrefixAuth");
GkAuthInit<SimplePasswordAuth> SimplePasswordAuthFactory("SimplePasswordAuth");
GkAuthInit<SQLPasswordAuth> SQLPasswordAuthFactory("SQLPasswordAuth");
GkAuthInit<SQLAliasAuth> SQLAliasAuthFactory("SQLAliasAuth");
#if HAS_MYSQL
GkAuthInit<MySQLPasswordAuth> MySQLPasswordAuthFactory("MySQLPasswordAuth");
GkAuthInit<MySQLAliasAuth> MySQLAliasAuthFactory("MySQLAliasAuth");
#endif
#if defined(HAS_LDAP) || defined(HAS_WLDAP)
GkAuthInit<LDAPPasswordAuth> LDAPPasswordAuthFactory("LDAPPasswordAuth");
GkAuthInit<LDAPAliasAuth> LDAPAliasAuthFactory("LDAPAliasAuth");
#endif
}
//////////////////////////////////////////////////////////////////
//
// gkauth.h
//
// Gatekeeper authentication modules
//
// This work is published under the GNU Public License (GPL)
// see file COPYING for details.
// We also explicitely grant the right to link this code
// with the OpenH323 library.
//
//
// History:
//      2001/09/19      initial version (Chih-Wei Huang)
//
//////////////////////////////////////////////////////////////////

#ifndef GKAUTH_H
#define GKAUTH_H "#(@) $Id: gkauth.h,v 1.6.2.16 2004/06/22 18:41:15 zvision Exp $"

#include <map>
#include <list>
#include "RasTbl.h"

class H225_GatekeeperRequest;
class H225_RegistrationRequest;
class H225_UnregistrationRequest;
class H225_AdmissionRequest;
class H225_BandwidthRequest;
class H225_DisengageRequest;
class H225_LocationRequest;
class H225_InfoRequest;
class H225_ArrayOf_ClearToken;
class H225_ArrayOf_CryptoH323Token;
class H225_ArrayOf_AliasAddress;
class H225_ArrayOf_AuthenticationMechanism;
class H225_ArrayOf_PASN_ObjectId;
class H235_AuthenticationMechanism;
class PASN_ObjectId;
class H235Authenticators;
class Q931;
class H225_Setup_UUIE;

class CacheManager;
struct MemBlock {
	long callDuration;
	PString BalanceString;
};

class GkAuthenticator {
public:
	enum Control {
		e_Optional,
		e_Required,
		e_Sufficient
	};

	enum Status {
		e_ok = 1,	// the request is authenticated
		e_fail = -1,	// the request should be rejected
		e_next = 0	// the request is undetermined
	};

	enum {
		e_GRQ = 0x0001,
		e_RRQ = 0x0002,
		e_URQ = 0x0004,
		e_ARQ = 0x0008,
		e_BRQ = 0x0010,
		e_DRQ = 0x0020,
		e_LRQ = 0x0040,
		e_IRQ = 0x0080,
		e_ALL = 0x00FF,
		e_Setup = 0x1000,
		e_SetupUnreg = 0x2000
	};

	GkAuthenticator(PConfig *, const char *authName = "default");
	virtual ~GkAuthenticator();

	template<class RasType> bool CheckRas(PBYTEArray &rawPDU, const RasType & req, unsigned & reason)
	{
	        setLastReceivedRawPDU(rawPDU);
		if (checkFlag & RasValue(req)) {
			int r = Check(req, reason);
			if (r == e_ok) {
				PTRACE(4, "GkAuth\t" << name << " check ok");
				if (controlFlag != e_Required)
					return true;
			} else if (r == e_fail) {
				PTRACE(2, "GkAuth\t" << name << " check failed");
				return false;
			}
		}
		// try next rule
		return (next) ? next->CheckRas(rawPDU, req, reason) : true;
	}

	bool CheckRas(
		/// received raw H.225 ARQ message
		PBYTEArray &rawPDU, 
		/// decoded H.225 ARQ message
		H225_RegistrationRequest& req, 
		/// RRJ code if the authentication fails
		unsigned& rejectReason,
		/// the address which the RRQ has been received from
		const PIPSocket::Address& rxaddr,MemBlock *
		);
		bool CheckRas(
		/// received raw H.225 ARQ message
		PBYTEArray &rawPDU, 
		/// decoded H.225 ARQ message
		H225_RegistrationRequest& req, 
		/// RRJ code if the authentication fails
		unsigned& rejectReason,
		/// the address which the RRQ has been received from
		const PIPSocket::Address& rxaddr
		);
	bool CheckRas(
		/// received raw H.225 ARQ message
		PBYTEArray &rawPDU, 
		/// decoded H.225 ARQ message
		H225_AdmissionRequest& req, 
		/// ARJ code if authentication fails
		unsigned& rejectReason,
		/// call duration limit to be set (-1 for no limit)
		long& callDurationLimit
		);
		
	/** Authenticate/Authorize Setup signalling message.
		
		@return
		true if the message has been authenticated, false otherwise
		(if authentication failed or call duration limit is 0)
	*/
	bool CheckSig( 
		/// received Q.931 Setup message
		Q931& q931pdu, 
		/// received H.225 Setup message
		H225_Setup_UUIE& setup, 
		/// CallRec for the call being authenticated
		callptr& call,
		/// Q.931 cause to set, if authentication fails
		unsigned& releaseCompleteCause, 
		/// call duration limit to be set (-1 for no limit)
		long& callDurationLimit,
		/// is the call from a registered endpoint
		bool registered
		);
	
	const char *GetName() { return name; }

	/** @return
		TRUE if this authenticator provides H.235 compatible security.
		It simply checks if h235Authenticators list is not empty.
	*/
	virtual BOOL IsH235Capable() const;
	
	/** If the authenticator supports H.235 security,
		this call returns H.235 security capabilities
		associated with it. It scans list pointed by h235Authenticators.
		
		@return
		TRUE is H.235 security is supported and capabilities
		has been set.
	*/
	virtual BOOL GetH235Capability(
		/// append supported authentication mechanism to this array
		H225_ArrayOf_AuthenticationMechanism& mechanisms,
		/// append supported algorithm OIDs for the given authentication
		/// mechanism
		H225_ArrayOf_PASN_ObjectId& algorithmOIDs
		) const;

	/** Check if this authenticator supports the given
		H.235 capability (mechanism+algorithmOID) by scanning
		list pointed by h235Authenticators.
		
		@return
		TRUE if the capability is supported.
	*/
	virtual BOOL IsH235Capability(
		/// authentication mechanism
		const H235_AuthenticationMechanism& mechanism,
		/// algorithm OID for the given authentication mechanism
		const PASN_ObjectId& algorithmOID
		) const;

	/** @return
		Control flag determining authenticator behaviour
		(optional,sufficient,required).
	*/
	Control GetControlFlag() const { return controlFlag; }

	/** @return
		Next authenticator on the list.
	*/
	GkAuthenticator* GetNext() const { return next; }
	
protected:
	// the second argument is the reject reason, if any
	virtual int Check(const H225_GatekeeperRequest &, unsigned &);
	virtual int Check(H225_RegistrationRequest &, unsigned &);
	virtual int Check(H225_RegistrationRequest &, unsigned &,MemBlock *);
	virtual int Check(const H225_UnregistrationRequest &, unsigned &);
	virtual int Check(H225_AdmissionRequest &, unsigned &);
	virtual int Check(
		/// received H.225 ARJ message
		H225_AdmissionRequest& request, 
		/// ARJ reason to set, if authentication failed
		unsigned& rejectReason, 
		/// call duration limit to set (-1 for no duration limit)
		long& callDurationLimit
		);
	virtual int Check(const H225_BandwidthRequest &, unsigned &);
	virtual int Check(const H225_DisengageRequest &, unsigned &);
	virtual int Check(const H225_LocationRequest &, unsigned &);
	virtual int Check(const H225_InfoRequest &, unsigned &);
	/** Authenticate/Authorize Setup signalling message.
	
		@return
		e_fail - authentication failed
		e_ok - authenticated with this authenticator
		e_next - authentication could not be determined
	*/
	virtual int Check(
		/// received Q.931 Setup message
		Q931& q931pdu, 
		/// received H.225 Setup message
		H225_Setup_UUIE& setup,
		/// CallRec for the call being authenticated
		callptr& call,
		/// Q.931 cause to set, if authentication failed
		unsigned& releaseCompleteCause, 
		/// call duration limit to set (-1 for no duration limit)
		long& callDurationLimit
		);

	int RasValue(const H225_GatekeeperRequest &)     { return e_GRQ; }
	int RasValue(const H225_RegistrationRequest &)   { return e_RRQ; }
	int RasValue(const H225_UnregistrationRequest &) { return e_URQ; }
	int RasValue(const H225_AdmissionRequest &)      { return e_ARQ; }
	int RasValue(const H225_BandwidthRequest &)      { return e_BRQ; }
	int RasValue(const H225_DisengageRequest &)      { return e_DRQ; }
	int RasValue(const H225_LocationRequest &)       { return e_LRQ; }
	int RasValue(const H225_InfoRequest &)           { return e_IRQ; }

	PBYTEArray& getLastReceivedRawPDU(){ return m_lastReceivedRawPDU; }

	/** @return
	    A string that can be used to identify an account name
	    associated with the call.
	*/
	virtual PString GetUsername(
		/// RRQ message with additional data
		const H225_RegistrationRequest& rrq
		) const;
	virtual PString GetUsername(
		/// call (if any) associated with the RAS message
		const callptr& call,
		/// ARQ message with additional data
		const H225_AdmissionRequest& arq,
		/// endpoint sending the ARQ request
		const endptr& ep
		) const;
	virtual PString GetUsername(
		/// call (if any) associated with the Setup message
		const callptr& call,
		/// Q.931 Setup message with additional data
		const Q931& q931pdu,
		/// Setup-UUIE element extracted from the Q.931 Setup message
		const H225_Setup_UUIE& setup
		) const;

	/** @return
	    A string that can be used to identify a calling number.
	*/
	virtual PString GetCallingStationId(
		/// call (if any) associated with the RAS message
		const callptr& call,
		/// ARQ message with additional data
		const H225_AdmissionRequest& arq,
		/// endpoint sending the ARQ request
		const endptr& ep
		) const;
	virtual PString GetCallingStationId(
		/// call (if any) associated with the Setup message
		const callptr& call,
		/// Q.931 Setup message with additional data
		const Q931& q931pdu,
		/// Setup-UUIE element extracted from the Q.931 Setup message
		const H225_Setup_UUIE& setup
		) const;

	/** @return
	    A string that can be used to identify a calling number.
	*/
	virtual PString GetCalledStationId(
		/// call (if any) associated with the RAS message
		const callptr& call,
		/// ARQ message with additional data
		const H225_AdmissionRequest& arq,
		/// endpoint sending the ARQ message
		const endptr& ep
		) const;
	virtual PString GetCalledStationId(
		/// call (if any) associated with the Setup message
		const callptr& call,
		/// Q.931 Setup message with additional data
		const Q931& q931pdu,
		/// Setup-UUIE element extracted from the Q.931 Setup message
		const H225_Setup_UUIE& setup
		) const;
		
protected:
	Control controlFlag;
	Status defaultStatus;
	PConfig *config;

	H235Authenticators* h235Authenticators;
	
private:
	const char *name;
	int checkFlag;

	GkAuthenticator *next;
	static GkAuthenticator *head;

	GkAuthenticator(const GkAuthenticator &);
	GkAuthenticator & operator=(const GkAuthenticator &);
	
	void setLastReceivedRawPDU(PBYTEArray &rawPDU){ m_lastReceivedRawPDU = rawPDU; }
	
	PBYTEArray m_lastReceivedRawPDU;

	friend class GkAuthenticatorList;
};

class SimplePasswordAuth : public GkAuthenticator {
public:
	SimplePasswordAuth(PConfig *, const char *);
	~SimplePasswordAuth();

protected:
	virtual int Check(const H225_GatekeeperRequest &, unsigned &);
	virtual int Check(H225_RegistrationRequest &, unsigned &);
	virtual int Check(const H225_UnregistrationRequest &, unsigned &);
	virtual int Check(H225_AdmissionRequest &, unsigned &);
	virtual int Check(const H225_BandwidthRequest &, unsigned &);
	virtual int Check(const H225_DisengageRequest &, unsigned &);
	virtual int Check(const H225_LocationRequest &, unsigned &);
	virtual int Check(const H225_InfoRequest &, unsigned &);

	virtual PString GetPassword(const PString & id);

	virtual bool CheckAliases(const PString &);
	virtual bool CheckTokens(const H225_ArrayOf_ClearToken &);
	virtual bool CheckCryptoTokens(const H225_ArrayOf_CryptoH323Token &);

	template<class RasType> int doCheck(const RasType & req)
	{
		if (req.HasOptionalField(RasType::e_cryptoTokens))
			return CheckCryptoTokens(req.m_cryptoTokens) ? e_ok : e_fail;
	 	else if (req.HasOptionalField(RasType::e_tokens))
			return CheckTokens(req.m_tokens) ? e_ok : e_fail;
		return (controlFlag == e_Optional) ? e_next : e_fail;
	}

#if defined HAS_WLDAP
	bool InternalGetPassword(PString & passwd);
#else
	bool InternalGetPassword(const PString & id, PString & passwd);
#endif // HAS_WLDAP

protected:
	int filled;
	bool checkid;
	const H225_ArrayOf_AliasAddress *aliases;
	CacheManager* m_cache;
};

class GkAuthInitializer {
public:
	GkAuthInitializer(const char *);
	virtual ~GkAuthInitializer();
	// virtual constructor
	virtual GkAuthenticator *CreateAuthenticator(PConfig *) = 0;
	bool Compare(PString n) const;

protected:
	const char *name;
};

template<class GkAuth> class GkAuthInit : public GkAuthInitializer {
public:
	GkAuthInit(const char *n) : GkAuthInitializer(n) {}
	virtual GkAuthenticator *CreateAuthenticator(PConfig *config)
	{ return new GkAuth(config, name); }
};

class GkAuthenticatorList {
public:
	GkAuthenticatorList(PConfig *);
	virtual ~GkAuthenticatorList();

	template<class RasType> bool Check(const RasType & req, unsigned & reason)
	{
		return (GkAuthenticator::head) ? GkAuthenticator::head->CheckRas(getLastReceivedRawPDU(), req, reason) : true;
	}
	
	bool Check(
		/// received H.225 RRQ message
		H225_RegistrationRequest& request, 
		/// RRJ reason to set, if the authentication fails
		unsigned& rejectReason,
		/// the address which the RRQ has been received from
		const PIPSocket::Address& rxaddr
		)
	{
		return (GkAuthenticator::head) ? GkAuthenticator::head->CheckRas(
			getLastReceivedRawPDU(), request, rejectReason, rxaddr
			) : true;
	}
	bool Check(
		/// received H.225 RRQ message
		H225_RegistrationRequest& request, 
		/// RRJ reason to set, if the authentication fails
		unsigned& rejectReason,
		/// the address which the RRQ has been received from
		const PIPSocket::Address& rxaddr,MemBlock *m
		)
	{
		return (GkAuthenticator::head) ? GkAuthenticator::head->CheckRas(
			getLastReceivedRawPDU(), request, rejectReason, rxaddr,m
			) : true;
	}
	bool Check(
		/// received H.225 ARQ message
		H225_AdmissionRequest& request, 
		/// ARJ reason to set, if authentication fails
		unsigned& rejectReason, 
		/// call duration limit to set (-1 for no duration limit)
		long& callDurationLimit
		)
	{
		callDurationLimit = -1;
		return (GkAuthenticator::head) ? GkAuthenticator::head->CheckRas(
			getLastReceivedRawPDU(), request, rejectReason, callDurationLimit
			) : true;
	}

	/** Authenticate/Authorize Setup signalling message. 
		Iterate through all authenticators.
		
		@return
		true if the message has been authenticated, false if not
		or if call duration limit is 0
	*/
	bool CheckSig(
		/// received Q.931 Setup message
		Q931& q931pdu,
		/// received H.225 Setup message
		H225_Setup_UUIE& setup, 
		/// CallRec for the call being authenticated
		callptr& call,
		/// Q.931 cause value to set, if authentication fails
		unsigned& releaseCompleteCause,
		/// call duration limit to set (-1 for no duration limit)
		long& callDurationLimit,
		/// is the call from a registered endpoint
		bool registered = true
		)
	{
		callDurationLimit = -1;
		return (GkAuthenticator::head != NULL) ? GkAuthenticator::head->CheckSig( 
			q931pdu, setup, call, releaseCompleteCause, callDurationLimit, registered
			) : true;
	}
	
	virtual void setLastReceivedRawPDU(PBYTEArray &rawPDU){ m_lastReceivedRawPDU = rawPDU; }

	/** @return
		Head of the authenticators list. Use GkAuthenticator::GetNext()
		to traverse the list.
	*/
	GkAuthenticator* GetHead() const { return GkAuthenticator::head; }

	void GetH235Capabilities(
		H225_ArrayOf_AuthenticationMechanism& mechanisms,
		H225_ArrayOf_PASN_ObjectId& algorithmOIDs
		) const;
	
private:
	GkAuthenticatorList(const GkAuthenticatorList &);
	GkAuthenticatorList & operator=(const GkAuthenticatorList &);

	virtual PBYTEArray& getLastReceivedRawPDU(){ return m_lastReceivedRawPDU; }
	
	PBYTEArray m_lastReceivedRawPDU;
	
	/// the most common authentication capabilities 
	/// shared by all authenticators on the list
	H225_ArrayOf_AuthenticationMechanism* m_mechanisms;
	H225_ArrayOf_PASN_ObjectId* m_algorithmOIDs;
};


#endif  // GKAUTH_H


[Index of Archives]     [SIP]     [Open H.323]     [Gnu Gatekeeper]     [Asterisk PBX]     [ISDN Cause Codes]     [Yosemite News]

  Powered by Linux