/*
 * radacct.cxx
 *
 * RADIUS protocol accounting logger module for GNU Gatekeeper. 
 *
 * 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: radacct.cxx,v $
 * Revision 1.18  2006/04/14 13:56:19  willamowius
 * call failover code merged
 *
 * Revision 1.1.1.1  2005/11/21 20:19:58  willamowius
 *
 *
 * Revision 1.4  2005/11/15 19:52:56  jan
 * Michal v1 (works, but on in routed, not proxy mode)
 *
 * Revision 1.17  2005/04/24 16:39:44  zvision
 * MSVC6.0 compatibility fixed
 *
 * Revision 1.16  2005/01/05 15:42:41  willamowius
 * new accounting event 'connect', parameter substitution unified in parent class
 *
 * Revision 1.15  2005/01/04 16:47:12  willamowius
 * space in trace msg
 *
 * Revision 1.14  2004/11/15 23:57:42  zvision
 * Ability to choose between the original and the rewritten dialed number
 *
 * Revision 1.13  2004/11/10 18:30:41  zvision
 * Ability to customize timestamp strings
 *
 * Revision 1.12  2004/07/26 12:19:41  zvision
 * New faster Radius implementation, thanks to Pavel Pavlov for ideas!
 *
 * Revision 1.11.2.2  2004/07/07 23:11:07  zvision
 * Faster and more elegant handling of Cisco VSA
 *
 * Revision 1.11.2.1  2004/07/07 20:50:14  zvision
 * New, faster, Radius client implementation. Thanks to Pavel Pavlov for ideas!
 *
 * Revision 1.11  2004/06/25 13:33:18  zvision
 * Better Username, Calling-Station-Id and Called-Station-Id handling.
 * New SetupUnreg option in Gatekeeper::Auth section.
 *
 * Revision 1.10  2004/06/17 10:47:13  zvision
 * New h323-ivr-out=h323-call-id accounting attribute
 *
 * Revision 1.9  2004/04/17 11:43:43  zvision
 * Auth/acct API changes.
 * Header file usage more consistent.
 *
 * Revision 1.8  2004/03/17 00:00:38  zvision
 * Conditional compilation to allow to control RADIUS on Windows just by setting HA_RADIUS macro
 *
 * Revision 1.7  2003/10/31 00:01:24  zvision
 * Improved accounting modules stacking control, optimized radacct/radauth a bit
 *
 * Revision 1.6  2003/10/15 10:16:57  zvision
 * Fixed VC6 compiler warnings. Thanks to Hu Yuxin.
 *
 * Revision 1.5  2003/10/08 12:40:48  zvision
 * Realtime accounting updates added
 *
 * Revision 1.4  2003/09/17 19:23:01  zvision
 * Removed unnecessary setup-time double check.
 * Added h323-connect-time to AcctUpdate packets.
 *
 * Revision 1.3  2003/09/14 21:10:34  zvision
 * Changes due to accounting API redesign.
 *
 * Revision 1.2  2003/09/12 16:31:16  zvision
 * Accounting initially added to the 2.2 branch
 *
 * Revision 1.1.2.5  2003/08/21 15:28:58  zvision
 * Fixed double h323-setup-time sent in Acct-Stop
 *
 * Revision 1.1.2.4  2003/08/17 20:05:39  zvision
 * Added h323-setup-time attribute to Acct-Start packets (Cisco compatibility).
 *
 * Revision 1.1.2.3  2003/07/31 22:58:48  zvision
 * Added Framed-IP-Address attribute and improved h323-disconnect-cause handling
 *
 * Revision 1.1.2.2  2003/07/03 15:30:39  zvision
 * Added cvs Log keyword
 *
 */
#if HAS_RADIUS

#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 <h323pdu.h>
#include "gk_const.h"
#include "h323util.h"
#include "Toolkit.h"
#include "RasTbl.h"
#include "gkacct.h"
#include "radproto.h"
#include "radacct.h"

using std::vector;


RadAcct::RadAcct( 
	const char* moduleName,
	const char* cfgSecName
	)
	:
	GkAcctLogger(moduleName, cfgSecName),
	m_nasIdentifier(Toolkit::Instance()->GKName()),
	m_radiusClient(NULL),
	m_attrH323CallOrigin(RadiusAttr::CiscoVSA_h323_call_origin, false,
		PString("proxy")),
	m_attrH323CallType(RadiusAttr::CiscoVSA_h323_call_type, false, 
		PString("VoIP"))
{
	// it is very important to set what type of accounting events
	// are supported for each accounting module, otherwise Log method
	// will no get called
	SetSupportedEvents(RadAcctEvents);
	
	PConfig* cfg = GetConfig();
	const PString& cfgSec = GetConfigSectionName();

	m_radiusClient = new RadiusClient(*cfg, cfgSec);

	m_nasIpAddress = m_radiusClient->GetLocalAddress();
	if (m_nasIpAddress == INADDR_ANY) {
		vector<PIPSocket::Address> interfaces;
		Toolkit::Instance()->GetGKHome(interfaces);
		if (!interfaces.empty())
			m_nasIpAddress = interfaces.front();
		else
			PTRACE(1, "RADACCT\t" << GetName() << " cannot determine "
				" NAS IP address"
				);
	}

	m_appendCiscoAttributes = Toolkit::AsBool(cfg->GetString(
		cfgSec, "AppendCiscoAttributes", "1"
		));
	m_fixedUsername = cfg->GetString(cfgSec, "FixedUsername", "");

	m_timestampFormat = cfg->GetString(cfgSec, "TimestampFormat", "");

	m_useDialedNumber = Toolkit::AsBool(cfg->GetString(
		cfgSec, "UseDialedNumber", "0"
		));
	
	m_attrNasIdentifier = RadiusAttr(RadiusAttr::NasIdentifier, m_nasIdentifier);
	m_attrH323GwId = RadiusAttr(RadiusAttr::CiscoVSA_h323_gw_id, false, m_nasIdentifier);
}

RadAcct::~RadAcct()
{
	delete m_radiusClient;
}

GkAcctLogger::Status RadAcct::Log(
	GkAcctLogger::AcctEvent evt, 
	const callptr& call
	)
{
	// a workaround to prevent processing end on "sufficient" module
	// if it is not interested in this event type
	if ((evt & GetEnabledEvents() & GetSupportedEvents()) == 0)
		return Next;
		
	if (m_radiusClient == NULL) {
		PTRACE(1,"RADACCT\t"<<GetName()<<" - null RADIUS client instance");
		return Fail;
	}

	if ((evt & (AcctStart | AcctStop | AcctUpdate)) && (!call)) {
		PTRACE(1,"RADACCT\t"<<GetName()<<" - missing call info for event "<<evt);
		return Fail;
	}
	
	// build RADIUS Accounting-Request
	RadiusPDU* const pdu = new RadiusPDU(RadiusPDU::AccountingRequest);

	pdu->AppendAttr(RadiusAttr::AcctStatusType, 
		(evt & AcctStart) ? RadiusAttr::AcctStatus_Start
		: ((evt & AcctStop) ? RadiusAttr::AcctStatus_Stop
		: ((evt & AcctUpdate) ? RadiusAttr::AcctStatus_InterimUpdate
		: ((evt & AcctOn) ? RadiusAttr::AcctStatus_AccountingOn
		: ((evt & AcctOff) ? RadiusAttr::AcctStatus_AccountingOff : 0)
		))));

	PIPSocket::Address addr;
	WORD port;
					
	// Gk works as NAS point, so append NAS IP
	pdu->AppendAttr(RadiusAttr::NasIpAddress, m_nasIpAddress);
	pdu->AppendAttr(m_attrNasIdentifier);
	pdu->AppendAttr(RadiusAttr::NasPortType, RadiusAttr::NasPort_Virtual);
		
	if (evt & (AcctStart | AcctStop | AcctUpdate)) {
		pdu->AppendAttr(RadiusAttr::ServiceType, RadiusAttr::ST_Login);
		pdu->AppendAttr(RadiusAttr::AcctSessionId, call->GetAcctSessionId());

		endptr callingEP = call->GetCallingParty();
		PIPSocket::Address callerIP(0);
		WORD callerPort = 0;		
		
		call->GetSrcSignalAddr(callerIP, callerPort);

		const PString username = GetUsername(call);
		if (username.IsEmpty() && m_fixedUsername.IsEmpty())
			PTRACE(3,"RADACCT\t"<<GetName()<<" could not determine User-Name"
				<<" for the call no. "<<call->GetCallNumber()
				);
		else
			pdu->AppendAttr(RadiusAttr::UserName, 
				m_fixedUsername.IsEmpty() ? username : m_fixedUsername
				);
		
		if (callerIP.IsValid())
			pdu->AppendAttr(RadiusAttr::FramedIpAddress, callerIP);
		
		if ((evt & AcctStart) == 0)
			pdu->AppendAttr(RadiusAttr::AcctSessionTime, call->GetDuration());
	
		PString stationId = GetCallingStationId(call);
		if (!stationId)
			pdu->AppendAttr(RadiusAttr::CallingStationId, stationId);
		else
			PTRACE(3,"RADACCT\t"<<GetName()<<" could not determine"
				<<" Calling-Station-Id for the call "<<call->GetCallNumber()
				);

		stationId = m_useDialedNumber ? GetDialedNumber(call) : GetCalledStationId(call);
		if (!stationId)
			pdu->AppendAttr(RadiusAttr::CalledStationId, stationId);
		else
			PTRACE(3,"RADACCT\t"<<GetName()<<" could not determine"
				<<" Called-Station-Id for the call no. "<<call->GetCallNumber()
				);
		
		if (m_appendCiscoAttributes) {
			pdu->AppendCiscoAttr(RadiusAttr::CiscoVSA_h323_conf_id,
				GetGUIDString(call->GetConferenceIdentifier())
				);
						
			pdu->AppendAttr(m_attrH323GwId);
			pdu->AppendAttr(m_attrH323CallOrigin);
			pdu->AppendAttr(m_attrH323CallType);

			Toolkit* const toolkit = Toolkit::Instance();
				
			time_t tm = call->GetSetupTime();
			if (tm != 0)
				pdu->AppendCiscoAttr(RadiusAttr::CiscoVSA_h323_setup_time,
					toolkit->AsString(PTime(tm), m_timestampFormat)
					);
			
			if (evt & (AcctStop | AcctUpdate)) {
				tm = call->GetConnectTime();
				if (tm != 0)
					pdu->AppendCiscoAttr(RadiusAttr::CiscoVSA_h323_connect_time,
						toolkit->AsString(PTime(tm), m_timestampFormat)
						);
			}
			
			if (evt & AcctStop) {
				tm = call->GetDisconnectTime();
				if (tm != 0)
					pdu->AppendCiscoAttr(RadiusAttr::CiscoVSA_h323_disconnect_time,
						toolkit->AsString(PTime(tm), m_timestampFormat)
						);
				
				pdu->AppendCiscoAttr(RadiusAttr::CiscoVSA_h323_disconnect_cause,
					PString(PString::Unsigned, (long)(call->GetDisconnectCause()), 16)
					);
			}					
			
			if (call->GetDestSignalAddr(addr,port))
				pdu->AppendCiscoAttr(RadiusAttr::CiscoVSA_h323_remote_address,
					addr.AsString()
					);

			pdu->AppendCiscoAttr(RadiusAttr::CiscoVSA_AV_Pair,
				PString("h323-ivr-out=h323-call-id:") 
					+ GetGUIDString(call->GetCallIdentifier().m_guid),
				true
				);
		}
	
		pdu->AppendAttr(RadiusAttr::AcctDelayTime, 0);
	}
		
	// send request and wait for response
	RadiusPDU* response = NULL;
	bool result = OnSendPDU(*pdu, evt, call);
	
	// accounting updates must be fast, so we are just sending
	// the request to the server and are not waiting for a response
	if (result)
		if (evt & AcctUpdate)
			result = m_radiusClient->SendRequest(*pdu);
		else
			result = m_radiusClient->MakeRequest(*pdu, response) && (response != NULL);
			
	delete pdu;
			
	if (!result) {
		delete response;
		return Fail;
	}
				
	if (response) {
		// check if Access-Request has been accepted
		result = (response->GetCode() == RadiusPDU::AccountingResponse);
		if (result)
			result = OnReceivedPDU(*response, evt, call);
		else
			PTRACE(4, "RADACCT\t" << GetName() << " - received response is not "
				" an AccountingResponse, event " << evt << ", call no. "
				<< (call ? call->GetCallNumber() : 0)
				);
		delete response;
	}
	return result ? Ok : Fail;
}

bool RadAcct::OnSendPDU(
	RadiusPDU& /*pdu*/,
	GkAcctLogger::AcctEvent /*evt*/,
	const callptr& /*call*/
	)
{
	return true;
}

bool RadAcct::OnReceivedPDU(
	RadiusPDU& /*pdu*/,
	GkAcctLogger::AcctEvent /*evt*/,
	const callptr& /*call*/
	)
{
	return true;
}

namespace {
	// append RADIUS based accounting logger to the global list of loggers
	GkAcctLoggerCreator<RadAcct> RadAcctCreator("RadAcct");
}

#endif /* HAS_RADIUS */


syntax highlighted by Code2HTML, v. 0.9.1