/*
 * sqlauth.cxx
 *
 * SQL authentication/authorization modules for GNU Gatekeeper
 *
 * $Id: sqlauth.cxx,v 1.10 2006/04/14 13:56:19 willamowius Exp $
 *
 * Copyright (c) 2004, 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.
 */
#if defined(_WIN32) && (_MSC_VER <= 1200)
#pragma warning(disable:4786) // warning about too long debug symbol off
#pragma warning(disable:4284)
#endif

#include <ptlib.h>
#include <h235.h>
#include <h323pdu.h>
#include <h235auth.h>
#include <limits>

#include "gk_const.h"
#include "h323util.h"
#include "stl_supp.h"
#include "RasTbl.h"
#include "RasPDU.h"
#include "Routing.h"
#include "Toolkit.h"
#include "RasSrv.h"
#include "gksql.h"
#include "sigmsg.h"
#include "h323util.h"
#include "Neighbor.h"
#include "gkauth.h"

using Routing::Route;

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

protected:
	/** Override from SimplePasswordAuth.
	
	    @return
	    True if the password has been found for the given alias.
	*/
	virtual bool GetPassword(
		/// alias to check the password for
		const PString& alias,
		/// password string, if the match is found
		PString& password
		);

private:
	SQLPasswordAuth();
	SQLPasswordAuth(const SQLPasswordAuth&);
	SQLPasswordAuth& operator=(const SQLPasswordAuth&);
	
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(
		/// name for this authenticator and for the config section to read settings from
		const char* authName
		);
	
	virtual ~SQLAliasAuth();

protected:
	/** Get auth condition string for the given alias. 
	    This implementation searches the SQL database for the string.
	    Override from AliasAuth.
		
	    @return
	    The AliasAuth condition string for the given alias.
	*/
	virtual bool GetAuthConditionString(
		/// an alias the condition string is to be retrieved for
		const PString& alias,
		/// filled with auth condition string that has been found
		PString& authCond
		);

private:
	SQLAliasAuth();
	SQLAliasAuth(const SQLAliasAuth&);
	SQLAliasAuth& operator=(const SQLAliasAuth&);
	
protected:
	/// connection to the SQL database
	GkSQLConnection* m_sqlConn;
	/// parametrized query string for the auth condition string retrieval
	PString m_query;
};

/// Generic SQL authenticator for non-password, SQL based authentication
class SQLAuth : public GkAuthenticator
{
public:
	enum SupportedChecks {
		SQLAuthRasChecks = RasInfo<H225_RegistrationRequest>::flag
			| RasInfo<H225_AdmissionRequest>::flag
			| RasInfo<H225_LocationRequest>::flag,
		SQLAuthMiscChecks = e_Setup | e_SetupUnreg
	};
	
	/// build authenticator reading settings from the config
	SQLAuth(
		/// name for this authenticator and for the config section to read settings from
		const char* authName,
		/// RAS check events supported by this module
		unsigned supportedRasChecks = SQLAuthRasChecks,
		/// Misc check events supported by this module
		unsigned supportedMiscChecks = SQLAuthMiscChecks
		);
	
	virtual ~SQLAuth();

	/** 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
		RasPDU<H225_RegistrationRequest>& rrqPdu, 
		/// authorization data (reject reason, ...)
		RRQAuthData& authData
		);
		
	/** Authenticate using data from ARQ RAS message.
	
		@return:
		#GkAuthenticator::Status enum# with the result of authentication.
	*/
	virtual int Check(
		/// ARQ nessage to be authenticated
		RasPDU<H225_AdmissionRequest> & arqPdu, 
		/// authorization data (call duration limit, reject reason, ...)
		ARQAuthData& authData
		);

	/** Authenticate using data from LRQ RAS message.
	
		@return:
		#GkAuthenticator::Status enum# with the result of authentication.
	*/
	virtual int Check(
		RasPDU<H225_LocationRequest>& req,
		unsigned& rejectReason
		);
		
	/** Authenticate using data from Q.931 Setup message.
	
		@return:
		#GkAuthenticator::Status enum# with the result of authentication.
	*/
	virtual int Check(
		/// Q.931/H.225 Setup message to be authenticated
		SetupMsg &setup,
		/// authorization data (call duration limit, reject reason, ...)
		SetupAuthData& authData
		);

private:
	SQLAuth();
	SQLAuth(const SQLAuth&);
	SQLAuth& operator=(const SQLAuth&);

protected:
	/// connection to the SQL database
	GkSQLConnection* m_sqlConn;
	/// parametrized query string for RRQ auth
	PString m_regQuery;
	/// parametrized query string for LRQ auth
	PString m_nbQuery;
	/// parametrized query string for ARQ/Setup auth
	PString m_callQuery;
};


namespace {

/// a common wrapper for SELECT query execution and result retrieval
bool RunQuery(
	const PString &traceStr,
	GkSQLConnection *conn,
	const PString &query,
	const std::map<PString, PString>& params,
	GkSQLResult::ResultRow& resultRow,
	long timeout
	)
{
	resultRow.clear();
	
	if (conn == NULL) {
		PTRACE(2, traceStr << ": query failed - SQL connection not active");
		return false;
	}
	
	if (query.IsEmpty()) {
		PTRACE(2, traceStr << ": query failed - query string not configured");
		return false;
	}
	
	GkSQLResult* result = conn->ExecuteQuery(query, params, timeout);
	if (result == NULL) {
		PTRACE(2, traceStr << ": query failed - timeout or fatal error");
		return false;
	}

	if (!result->IsValid()) {
		PTRACE(2, traceStr << ": query failed (" << result->GetErrorCode()
			<< ") - " << result->GetErrorMessage()
			);
		delete result;
		return false;
	}
	
	if (result->GetNumRows() < 1)
		PTRACE(3, traceStr << ": query returned no rows");
	else if (result->GetNumFields() < 1)
		PTRACE(2, traceStr << ": bad-formed query - "
			"no columns found in the result set"
			);
	else if (!result->FetchRow(resultRow) || resultRow.empty())
		PTRACE(2, traceStr << ": query failed - could not fetch the result row");
	else {
		delete result;
		return true;
	}

	delete result;
	return false;
}

inline GkSQLResult::ResultRow::iterator FindField(
	GkSQLResult::ResultRow& result,
	const PString& fieldName
	)
{
	GkSQLResult::ResultRow::iterator i = result.begin();
	while (i != result.end() && i->second != fieldName)
		i++;
	return i;
}

} /* namespace */


SQLPasswordAuth::SQLPasswordAuth(
	const char* authName
	)
	: SimplePasswordAuth(authName), m_sqlConn(NULL)
{
	PConfig* cfg = GetConfig();

	const PString driverName = cfg->GetString(authName, "Driver", "");
	if (driverName.IsEmpty()) {
		PTRACE(0, "SQLAUTH\t" << GetName() << " module creation failed: "
			"no SQL driver selected"
			);
		PTRACE(0, "SQLAUTH\tFATAL: Shutting down");
		RasServer::Instance()->Stop();
		return;
	}
	
	m_sqlConn = GkSQLConnection::Create(driverName, authName);
	if (m_sqlConn == NULL) {
		PTRACE(0, "SQLAUTH\t" << GetName() << " module creation failed: "
			"could not find " << driverName << " database driver"
			);
		PTRACE(0, "SQLAUTH\tFATAL: Shutting down");
		RasServer::Instance()->Stop();
		return;
	}

	SetCacheTimeout(cfg->GetInteger(authName, "CacheTimeout", 0));
		
	m_query = cfg->GetString(authName, "Query", "");
	if (m_query.IsEmpty()) {
		PTRACE(0, "SQLAUTH\t" << GetName() << " module creation failed: "
			"no query configured"
			);
		PTRACE(0, "SQLAUTH\tFATAL: Shutting down");
		RasServer::Instance()->Stop();
		return;
	} else
		PTRACE(4, "SQLAUTH\t" << GetName() << " query: " << m_query);
		
	if (!m_sqlConn->Initialize(cfg, authName)) {
		PTRACE(0, "SQLAUTH\t" << GetName() << " module creation failed: "
			"could not connect to the database"
			);
		return;
	}
}

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

bool SQLPasswordAuth::GetPassword(
	const PString& alias,
	PString& password
	)
{
	GkSQLResult::ResultRow result;
	std::map<PString, PString> params;
	params["1"] = alias;
	params["u"] = alias;
	params["2"] = Toolkit::GKName();
	params["g"] = Toolkit::GKName();

	if (!RunQuery("SQLAUTH\t" + GetName() + "('" + alias + "')", m_sqlConn, m_query, params, result, -1))
		return false;
		
	password = result[0].first;
	return true;
}

SQLAliasAuth::SQLAliasAuth(
	const char* authName
	)
	: AliasAuth(authName), m_sqlConn(NULL)
{
	PConfig* cfg = GetConfig();

	const PString driverName = cfg->GetString(authName, "Driver", "");
	if (driverName.IsEmpty()) {
		PTRACE(0, "SQLAUTH\t" << GetName() << " module creation failed: "
			"no SQL driver selected"
			);
		PTRACE(0, "SQLAUTH\tFATAL: Shutting down");
		RasServer::Instance()->Stop();
		return;
	}
	
	m_sqlConn = GkSQLConnection::Create(driverName, authName);
	if (m_sqlConn == NULL) {
		PTRACE(0, "SQLAUTH\t" << GetName() << " module creation failed: "
			"could not find " << driverName << " database driver"
			);
		PTRACE(0, "SQLAUTH\tFATAL: Shutting down");
		RasServer::Instance()->Stop();
		return;
	}

	SetCacheTimeout(cfg->GetInteger(authName, "CacheTimeout", 0));
	
	m_query = cfg->GetString(authName, "Query", "");
	if (m_query.IsEmpty()) {
		PTRACE(1, "SQLAUTH\t" << GetName() << " module creation failed: "
			"no query configured"
			);
		PTRACE(0, "SQLAUTH\tFATAL: Shutting down");
		RasServer::Instance()->Stop();
		return;
	} else
		PTRACE(4, "SQLAUTH\t" << GetName() << " query: " << m_query);
		
	if (!m_sqlConn->Initialize(cfg, authName)) {
		PTRACE(0, "SQLAUTH\t" << GetName() << " module creation failed: "
			"could not connect to the database"
			);
		return;
	}
}

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

bool SQLAliasAuth::GetAuthConditionString(
	const PString& alias,
	PString& authCond
	)
{
	GkSQLResult::ResultRow result;
	std::map<PString, PString> params;
	params["1"] = alias;
	params["u"] = alias;
	params["2"] = Toolkit::GKName();
	params["g"] = Toolkit::GKName();

	if (!RunQuery("SQLAUTH\t" + GetName() + "('" + alias + "')", m_sqlConn, m_query, params, result, -1))
		return false;
		
	authCond = result[0].first;
	return true;
}


SQLAuth::SQLAuth(
	const char* authName,
	unsigned supportedRasChecks,
	unsigned supportedMiscChecks
	) 
	: 
	GkAuthenticator(authName, supportedRasChecks, supportedMiscChecks), 
	m_sqlConn(NULL)
{
	PConfig* cfg = GetConfig();

	const PString driverName = cfg->GetString(authName, "Driver", "");
	if (driverName.IsEmpty()) {
		PTRACE(0, "SQLAUTH\t" << GetName() << " module creation failed: "
			"no SQL driver selected"
			);
		PTRACE(0, "SQLAUTH\tFATAL: Shutting down");
		RasServer::Instance()->Stop();
		return;
	}
	
	m_sqlConn = GkSQLConnection::Create(driverName, authName);
	if (m_sqlConn == NULL) {
		PTRACE(0, "SQLAUTH\t" << GetName() << " module creation failed: "
			"could not find " << driverName << " database driver"
			);
		PTRACE(0, "SQLAUTH\tFATAL: Shutting down");
		RasServer::Instance()->Stop();
		return;
	}

	m_regQuery = cfg->GetString(authName, "RegQuery", "");
	if (m_regQuery.IsEmpty() && IsRasCheckEnabled(RasInfo<H225_RegistrationRequest>::flag)) {
		PTRACE(1, "SQLAUTH\t" << GetName() << " module creation failed: "
			"no RRQ query configured"
			);
		PTRACE(0, "SQLAUTH\tFATAL: Shutting down");
		RasServer::Instance()->Stop();
		return;
	} else if (!m_regQuery)
		PTRACE(4, "SQLAUTH\t" << GetName() << " RRQ query: " << m_regQuery);

	m_nbQuery = cfg->GetString(authName, "NbQuery", "");
	if (m_nbQuery.IsEmpty() && IsRasCheckEnabled(RasInfo<H225_LocationRequest>::flag)) {
		PTRACE(1, "SQLAUTH\t" << GetName() << " module creation failed: "
			"no LRQ query configured"
			);
		PTRACE(0, "SQLAUTH\tFATAL: Shutting down");
		RasServer::Instance()->Stop();
		return;
	} else if (!m_nbQuery)
		PTRACE(4, "SQLAUTH\t" << GetName() << " LRQ query: " << m_nbQuery);

	m_callQuery = cfg->GetString(authName, "CallQuery", "");
	if (m_callQuery.IsEmpty() && (IsRasCheckEnabled(RasInfo<H225_AdmissionRequest>::flag)
			|| IsMiscCheckEnabled(e_Setup) || IsMiscCheckEnabled(e_SetupUnreg))) {
		PTRACE(1, "SQLAUTH\t" << GetName() << " module creation failed: "
			"no ARQ/Setup query configured"
			);
		PTRACE(0, "SQLAUTH\tFATAL: Shutting down");
		RasServer::Instance()->Stop();
		return;
	} else if (!m_callQuery)
		PTRACE(4, "SQLAUTH\t" << GetName() << " ARQ/Setup query: " << m_callQuery);

	if (!m_sqlConn->Initialize(cfg, authName)) {
		PTRACE(0, "SQLAUTH\t" << GetName() << " module creation failed: "
			"could not connect to the database"
			);
		return;
	}
}

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

int SQLAuth::Check(
	/// RRQ RAS message to be authenticated
	RasPDU<H225_RegistrationRequest>& rrqPdu, 
	/// authorization data (reject reason, ...)
	RRQAuthData& authData
	)
{
	H225_RegistrationRequest &rrq = rrqPdu;
	std::map<PString, PString> params;
	
	// get the username for User-Name attribute		
	params["u"] = GetUsername(rrqPdu);
	params["g"] = Toolkit::GKName();
	
	PIPSocket::Address addr = (rrqPdu.operator->())->m_peerAddr;

	const PString traceStr = "SQLAUTH\t" + GetName() + "(RRQ from "
		+ addr.AsString() + " Username=" + params["u"]
		+ ")";
	params["callerip"] = addr.AsString();
	
	addr = (rrqPdu.operator->())->m_localAddr;
	params["gkip"] = addr.AsString();

	if (rrq.HasOptionalField(H225_RegistrationRequest::e_terminalAlias)) {
		PString aliasList;
		for (PINDEX i = 0; i < rrq.m_terminalAlias.GetSize(); i++) {
			if(i > 0)
				aliasList += ",";
			aliasList += AsString(rrq.m_terminalAlias[i], FALSE);
		}
		params["aliases"] = aliasList;
	}

	GkSQLResult::ResultRow result;	
	if (!RunQuery(traceStr, m_sqlConn, m_regQuery, params, result, -1)) {
		authData.m_rejectReason = H225_RegistrationRejectReason::e_resourceUnavailable;
		return GetDefaultStatus();
	}

	if (!Toolkit::AsBool(result[0].first)) {
		authData.m_rejectReason = H225_RegistrationRejectReason::e_securityDenial;
		return e_fail;
	}

	GkSQLResult::ResultRow::const_iterator iter = FindField(result, "billingmode");
	if (iter != result.end()) {
		const PString &s = iter->first;
		if (!s) {
			if (strspn((const char*)s,"0123456789.") == (size_t)s.GetLength()) {
				const int intVal = s.AsInteger();
				if (intVal == 0)
					authData.m_billingMode = H225_CallCreditServiceControl_billingMode::e_credit;
				else if (intVal == 1 || intVal == 2)
					authData.m_billingMode = H225_CallCreditServiceControl_billingMode::e_debit;
			} else {
				PTRACE(3, traceStr << " - invalid billingmode attribute '"
					<< s << '\''
					);
			}
		}
	}

	iter = FindField(result, "creditamount");
	if (iter != result.end()) {
		if (!iter->first)
			authData.m_amountString = iter->first;
	}

	iter = FindField(result, "aliases");
	if (iter != result.end()) {
		PStringArray aliases = iter->first.Tokenise(",");
		if (aliases.GetSize() > 0 
				&& rrq.HasOptionalField(H225_RegistrationRequest::e_terminalAlias)) {
			PINDEX i = 0;
			while (i < rrq.m_terminalAlias.GetSize()) {
				PINDEX j = aliases.GetStringsIndex(AsString(rrq.m_terminalAlias[i], FALSE));
				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]);
		}
	}
			
	return e_ok;
}
		
int SQLAuth::Check(
	/// ARQ nessage to be authenticated
	RasPDU<H225_AdmissionRequest> & arqPdu, 
	/// authorization data (call duration limit, reject reason, ...)
	ARQAuthData& authData
	)
{
	const H225_AdmissionRequest &arq = arqPdu;
	std::map<PString, PString> params;
	
	PIPSocket::Address addr = (arqPdu.operator->())->m_peerAddr;

	const PString traceStr = "SQLAUTH\t" + GetName() + "(ARQ from "
		+ addr.AsString() + " CRV=" + PString(arq.m_callReferenceValue.GetValue() & 0x7fff)
		+ ")";
	params["callerip"] = addr.AsString();
		
	// get the username for User-Name attribute		
	params["u"] = GetUsername(arqPdu, authData);
	params["g"] = Toolkit::GKName();
	
	addr = (arqPdu.operator->())->m_localAddr;
	params["gkip"] = addr.AsString();

	params["Calling-Station-Id"] = GetCallingStationId(arqPdu, authData);
	params["Called-Station-Id"] = GetCalledStationId(arqPdu, authData);
	params["Dialed-Number"] = GetDialedNumber(arqPdu, authData);
	params["bandwidth"] = PString(arq.m_bandWidth.GetValue());
	params["answer"] = arq.m_answerCall ? "1" : "0";
	params["arq"] = "1";
	
	GkSQLResult::ResultRow result;	
	if (!RunQuery(traceStr, m_sqlConn, m_callQuery, params, result, -1)) {
		authData.m_rejectReason = H225_AdmissionRejectReason::e_resourceUnavailable;
		return GetDefaultStatus();
	}

	if (!Toolkit::AsBool(result[0].first)) {
		authData.m_rejectReason = H225_AdmissionRejectReason::e_securityDenial;
		return e_fail;
	}

	GkSQLResult::ResultRow::const_iterator iter = FindField(result, "billingmode");
	if (iter != result.end()) {
		const PString &s = iter->first;
		if (!s) {
			if (strspn((const char*)s,"0123456789.") == (size_t)s.GetLength()) {
				const int intVal = s.AsInteger();
				if (intVal == 0)
					authData.m_billingMode = H225_CallCreditServiceControl_billingMode::e_credit;
				else if (intVal == 1 || intVal == 2)
					authData.m_billingMode = H225_CallCreditServiceControl_billingMode::e_debit;
			} else {
				PTRACE(3, traceStr << " - invalid billingmode attribute '"
					<< s << '\''
					);
			}
		}
	}

	iter = FindField(result, "creditamount");
	if (iter != result.end()) {
		if (!iter->first)
			authData.m_amountString = iter->first;
	}

	iter = FindField(result, "credittime");
	if (iter != result.end()) {
		const PString &s = iter->first;
		if (s.GetLength() > 0
			&& strspn((const char*)s, "0123456789") == (size_t)s.GetLength()) {
			PUInt64 limit = s.AsUnsigned64();
			if (limit > PUInt64(std::numeric_limits<long>::max()))
				authData.m_callDurationLimit = std::numeric_limits<long>::max();
			else
				authData.m_callDurationLimit = static_cast<long>(limit);
			PTRACE(5, traceStr << " - duration limit set to "
				<< authData.m_callDurationLimit
				);
			if (authData.m_callDurationLimit == 0) {
				authData.m_rejectReason = H225_AdmissionRejectReason::e_securityDenial;
				return e_fail;
			}
		}
	}

	iter = FindField(result, "redirectnumber");
	if (iter != result.end()) {
		const PString &s = iter->first;
		if (!s) {
			authData.SetRouteToAlias(s);
			PTRACE(5, traceStr << " - call redirected to the number " << s);
		}
	}
			
	iter = FindField(result, "redirectip");
	if (iter != result.end()) {
		const PString &s = iter->first;
		if (!s) {
			PStringArray tokens(s.Tokenise("; \t", FALSE));
			for (PINDEX i = 0; i < tokens.GetSize(); ++i) {
				PIPSocket::Address addr;
				WORD port = 0;
					
				if (GetTransportAddress(tokens[i], GK_DEF_ENDPOINT_SIGNAL_PORT, addr, port)
						&& addr.IsValid() && port != 0) {
					Route route("SQL", addr, port);
					route.m_destEndpoint = RegistrationTable::Instance()->FindBySignalAdr(
						SocketToH225TransportAddr(addr, port)
						);
					authData.m_destinationRoutes.push_back(route);
					PTRACE(5, traceStr << " - call redirected to the address " <<
						route.AsString()
						);
				}
			}
		}
	}

	iter = FindField(result, "proxy");
	if (iter != result.end()) {
		const PString &s = iter->first;
		if (!s) {
			authData.m_proxyMode = Toolkit::AsBool(s)
				? CallRec::ProxyEnabled : CallRec::ProxyDisabled;
			PTRACE(5, traceStr << " - proxy mode "
				<< (authData.m_proxyMode == CallRec::ProxyEnabled ? "enabled" : "disabled")
				);
		}
	}
			
	return e_ok;
}

int SQLAuth::Check(
	RasPDU<H225_LocationRequest>& lrqPdu,
	unsigned& rejectReason
	)
{
	H225_LocationRequest &lrq = lrqPdu;
	std::map<PString, PString> params;
	
	PIPSocket::Address addr = (lrqPdu.operator->())->m_peerAddr;

	const PString traceStr = "SQLAUTH\t" + GetName() + "(LRQ from "
		+ addr.AsString() + ")";
	params["nbip"] = addr.AsString();
	params["nbid"] = RasServer::Instance()->GetNeighbors()->GetNeighborIdBySigAdr(addr);
	params["g"] = Toolkit::GKName();
	
	addr = (lrqPdu.operator->())->m_localAddr;
	params["gkip"] = addr.AsString();

	if (lrq.HasOptionalField(H225_LocationRequest::e_sourceInfo)) {
		params["Calling-Station-Id"] = GetBestAliasAddressString(lrq.m_sourceInfo,
			false, AliasAddressTagMask(H225_AliasAddress::e_dialedDigits)
				| AliasAddressTagMask(H225_AliasAddress::e_partyNumber)
			);
		params["src-info"] = AsString(lrq.m_sourceInfo);
	}
	params["Called-Station-Id"] = GetBestAliasAddressString(lrq.m_destinationInfo,
		false, AliasAddressTagMask(H225_AliasAddress::e_dialedDigits)
			| AliasAddressTagMask(H225_AliasAddress::e_partyNumber)
		);
	params["dest-info"] = AsString(lrq.m_destinationInfo);

	if (lrq.HasOptionalField(H225_LocationRequest::e_bandWidth))
		params["bandwidth"] = PString(lrq.m_bandWidth.GetValue());

	GkSQLResult::ResultRow result;	
	if (!RunQuery(traceStr, m_sqlConn, m_nbQuery, params, result, -1)) {
		rejectReason = H225_LocationRejectReason::e_resourceUnavailable;
		return GetDefaultStatus();
	}

	if (!Toolkit::AsBool(result[0].first)) {
		rejectReason = H225_LocationRejectReason::e_securityDenial;
		return e_fail;
	}

	GkSQLResult::ResultRow::const_iterator iter = FindField(result, "destination");
	if (iter != result.end()) {
		PStringArray aliases = iter->first.Tokenise(",");
		if (aliases.GetSize() > 0) {
			PINDEX i = 0;
			while (i < lrq.m_destinationInfo.GetSize()) {
				PINDEX j = aliases.GetStringsIndex(AsString(lrq.m_destinationInfo[i], FALSE));
				if( j == P_MAX_INDEX )
					lrq.m_destinationInfo.RemoveAt(i);
				else {
					i++;
					aliases.RemoveAt(j);
				}
			}
		}
		for (PINDEX i = 0; i < aliases.GetSize(); i++) {
			const PINDEX sz = lrq.m_destinationInfo.GetSize();
			lrq.m_destinationInfo.SetSize(sz + 1);
			H323SetAliasAddress(aliases[i], lrq.m_destinationInfo[sz]);
		}
	}
			
	return e_ok;
}
	
int SQLAuth::Check(
	/// Q.931/H.225 Setup message to be authenticated
	SetupMsg &setup,
	/// authorization data (call duration limit, reject reason, ...)
	SetupAuthData& authData
	)
{
	std::map<PString, PString> params;
	
	PIPSocket::Address addr;
	setup.GetPeerAddr(addr);

	const PString traceStr = "SQLAUTH\t" + GetName() + "(Setup from "
		+ addr.AsString() + " CRV=" + PString(setup.GetQ931().GetCallReference())
		+ ")";
	params["callerip"] = addr.AsString();
		
	// get the username for User-Name attribute		
	params["u"] = GetUsername(setup, authData);
	params["g"] = Toolkit::GKName();
	
	setup.GetLocalAddr(addr);
	params["gkip"] = addr.AsString();

	params["Calling-Station-Id"] = GetCallingStationId(setup, authData);
	params["Called-Station-Id"] = GetCalledStationId(setup, authData);
	params["Dialed-Number"] = GetDialedNumber(setup, authData);
	params["answer"] = "0";
	params["arq"] = "0";

	if (authData.m_call)
		params["bandwidth"] = PString(authData.m_call->GetBandwidth());

	GkSQLResult::ResultRow result;	
	if (!RunQuery(traceStr, m_sqlConn, m_callQuery, params, result, -1)) {
		authData.m_rejectCause = Q931::TemporaryFailure;
		return GetDefaultStatus();
	}

	if (!Toolkit::AsBool(result[0].first)) {
		authData.m_rejectCause = Q931::CallRejected;
		return e_fail;
	}

	GkSQLResult::ResultRow::const_iterator iter = FindField(result, "credittime");
	if (iter != result.end()) {
		const PString &s = iter->first;
		if (s.GetLength() > 0
			&& strspn((const char*)s, "0123456789") == (size_t)s.GetLength()) {
			PUInt64 limit = s.AsUnsigned64();
			if (limit > PUInt64(std::numeric_limits<long>::max()))
				authData.m_callDurationLimit = std::numeric_limits<long>::max();
			else
				authData.m_callDurationLimit = static_cast<long>(limit);
			PTRACE(5, traceStr << " - duration limit set to "
				<< authData.m_callDurationLimit
				);
			if (authData.m_callDurationLimit == 0) {
				authData.m_rejectCause = Q931::CallRejected;
				return e_fail;
			}
		}
	}

	iter = FindField(result, "redirectnumber");
	if (iter != result.end()) {
		const PString &s = iter->first;
		if (!s) {
			authData.SetRouteToAlias(s);
			PTRACE(5, traceStr << " - call redirected to the number " << s);
		}
	}
			
	iter = FindField(result, "redirectip");
	if (iter != result.end()) {
		const PString &s = iter->first;
		if (!s) {
			PStringArray tokens(s.Tokenise("; \t", FALSE));
			for (PINDEX i = 0; i < tokens.GetSize(); ++i) {
				PIPSocket::Address addr;
				WORD port = 0;
					
				if (GetTransportAddress(tokens[i], GK_DEF_ENDPOINT_SIGNAL_PORT, addr, port)
						&& addr.IsValid() && port != 0) {
					Route route("SQL", addr, port);
					route.m_destEndpoint = RegistrationTable::Instance()->FindBySignalAdr(
						SocketToH225TransportAddr(addr, port)
						);
					authData.m_destinationRoutes.push_back(route);
					PTRACE(5, traceStr << " - call redirected to the address " <<
						route.AsString()
						);
				}
			}
		}
	}

	iter = FindField(result, "proxy");
	if (iter != result.end()) {
		const PString &s = iter->first;
		if (!s) {
			authData.m_proxyMode = Toolkit::AsBool(s)
				? CallRec::ProxyEnabled : CallRec::ProxyDisabled;
			PTRACE(5, traceStr << " - proxy mode "
				<< (authData.m_proxyMode == CallRec::ProxyEnabled ? "enabled" : "disabled")
				);
		}
	}
			
	return e_ok;
}


namespace { // anonymous namespace
	GkAuthCreator<SQLPasswordAuth> SQLPasswordAuthCreator("SQLPasswordAuth");
	GkAuthCreator<SQLAliasAuth> SQLAliasAuthCreator("SQLAliasAuth");
	GkAuthCreator<SQLAuth> SQLAuthCreator("SQLAuth");
} // end of anonymous namespace


syntax highlighted by Code2HTML, v. 0.9.1