/*
* gkacct.cxx
*
* Accounting modules for GNU Gatekeeper. Provides generic
* support for accounting to the 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: gkacct.cxx,v $
* Revision 1.30 2006/04/14 13:56:19 willamowius
* call failover code merged
*
* Revision 1.3 2006/01/19 11:48:45 zvision
* New media-ip and codec accounting variables for fast start calls
*
* Revision 1.2 2005/12/15 12:40:39 willamowius
* new accounting variables
*
* Revision 1.1.1.1 2005/11/21 20:20:00 willamowius
*
*
* Revision 1.4 2005/11/15 19:52:56 jan
* Michal v1 (works, but on in routed, not proxy mode)
*
* Revision 1.29 2005/05/19 16:41:16 zvision
* Solaris need explicit std::map
*
* Revision 1.28 2005/04/24 16:39:44 zvision
* MSVC6.0 compatibility fixed
*
* Revision 1.27 2005/03/18 14:54:47 zvision
* Various accounting variables ported from 2.0 branch
*
* Revision 1.26 2005/02/02 22:16:46 zvision
* Different Username was presented during auth and acct steps in some case
*
* Revision 1.25 2005/01/17 08:42:24 zvision
* Compilation error fixed (missing std:: prefix before map)
*
* Revision 1.24 2005/01/16 22:37:15 zvision
* Redundant config reload mutex removed
*
* Revision 1.23 2005/01/12 18:01:34 willamowius
* small cleanup
*
* Revision 1.22 2005/01/12 17:55:00 willamowius
* fix gkip accounting parameter
*
* Revision 1.21 2005/01/10 23:49:06 willamowius
* provide mechanism for accounting modules to escape the parameters
*
* Revision 1.20 2005/01/05 15:42:31 willamowius
* new accounting event 'connect', parameter substitution unified in parent class
*
* Revision 1.19 2005/01/04 17:50:51 willamowius
* space in trace msg
*
* Revision 1.18 2004/11/15 23:57:41 zvision
* Ability to choose between the original and the rewritten dialed number
*
* Revision 1.17 2004/11/10 18:30:41 zvision
* Ability to customize timestamp strings
*
* Revision 1.16 2004/06/28 19:09:33 zvision
* Monthly CDR files rotation fixes.
*
* Revision 1.15 2004/06/28 18:47:58 zvision
* Fixed a bug in Username determination for unregistered endpoints
*
* Revision 1.14 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.13 2004/05/31 22:45:52 zvision
* Fixed CDR rotation per number of lines
*
* Revision 1.12 2004/05/22 12:17:12 zvision
* Parametrized FileAcct CDR format
*
* Revision 1.11 2004/05/12 11:49:08 zvision
* New flexible CDR file rotation
*
* Revision 1.10 2004/04/17 11:43:42 zvision
* Auth/acct API changes.
* Header file usage more consistent.
*
* Revision 1.9 2003/12/21 00:58:17 zvision
* FileAcct logger should work fine with the reload command now
*
* Revision 1.8 2003/11/01 10:36:34 zvision
* Fixed missing semicolon. Thanks to Hu Yuxin
*
* Revision 1.7 2003/10/31 00:01:23 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/28 15:45:49 zvision
* Microsecond field added back to h323-xxx-time attributes
*
* Revision 1.3 2003/09/14 21:09:29 zvision
* Added new FileAcct logger from Tamas Jalsovszky. Thanks!
* Fixed module stacking. Redesigned API.
*
* Revision 1.2 2003/09/12 16:31:16 zvision
* Accounting initially added to the 2.2 branch
*
* Revision 1.1.2.1 2003/06/19 15:36:04 zvision
* Initial generic accounting support for GNU GK.
*
*/
#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 <h225.h>
#include "gk_const.h"
#include "h323util.h"
#include "stl_supp.h"
#include "Toolkit.h"
#include "RasTbl.h"
#include "gktimer.h"
#include "gkacct.h"
using std::find;
using std::vector;
/// Name of the config file section for accounting configuration
namespace {
const char* GkAcctSectionName = "Gatekeeper::Acct";
}
extern const char* CallTableSection;
GkAcctLogger::GkAcctLogger(
const char* moduleName,
const char* cfgSecName
)
: NamedObject(moduleName), m_controlFlag(Required), m_defaultStatus(Fail),
m_enabledEvents(AcctAll), m_supportedEvents(AcctNone), m_config(GkConfig()),
m_configSectionName(cfgSecName)
{
if (m_configSectionName.IsEmpty())
m_configSectionName = moduleName;
const PStringArray control(
m_config->GetString(GkAcctSectionName, moduleName, "").Tokenise(";,")
);
if (control.GetSize() < 1)
PTRACE(1, "GKACCT\tEmpty config entry for module " << moduleName);
else if (strcasecmp(moduleName, "default") == 0) {
m_controlFlag = Required;
m_defaultStatus = Toolkit::AsBool(control[0]) ? Ok : Fail;
m_supportedEvents = AcctAll;
} else if (control[0] *= "optional")
m_controlFlag = Optional;
else if (control[0] *= "sufficient")
m_controlFlag = Sufficient;
else if (control[0] *= "alternative")
m_controlFlag = Alternative;
if (control.GetSize() > 1)
m_enabledEvents = GetEvents(control);
PTRACE(1, "GKACCT\tCreated module " << moduleName << " with event mask "
<< PString(PString::Unsigned,(long)m_enabledEvents,16)
);
}
GkAcctLogger::~GkAcctLogger()
{
PTRACE(1,"GKACCT\tDestroyed module "<<GetName());
}
int GkAcctLogger::GetEvents(
const PStringArray& tokens
) const
{
int mask = 0;
for( PINDEX i = 1; i < tokens.GetSize(); i++ ) {
const PString& token = tokens[i];
if( token *= "start" )
mask |= AcctStart;
else if( token *= "stop" )
mask |= AcctStop;
else if( token *= "update" )
mask |= AcctUpdate;
else if( token *= "connect" )
mask |= AcctConnect;
else if( token *= "on" )
mask |= AcctOn;
else if( token *= "off" )
mask |= AcctOff;
}
return mask;
}
GkAcctLogger::Status GkAcctLogger::Log(
AcctEvent evt, /// accounting event to log
const callptr& /*call*/ /// a call associated with the event (if any)
)
{
return (evt & m_enabledEvents & m_supportedEvents) ? m_defaultStatus : Next;
}
void GkAcctLogger::SetupAcctParams(
/// CDR parameters (name => value) associations
std::map<PString, PString>& params,
/// call (if any) associated with an accounting event being logged
const callptr& call,
/// timestamp formatting string
const PString& timestampFormat
) const
{
PIPSocket::Address addr;
WORD port = 0;
time_t t;
vector<PIPSocket::Address> interfaces;
Toolkit* const toolkit = Toolkit::Instance();
toolkit->GetGKHome(interfaces);
params["g"] = toolkit->GKName();
params["n"] = PString(call->GetCallNumber());
params["u"] = GetUsername(call);
params["d"] = call->GetDuration();
params["c"] = call->GetDisconnectCause();
params["s"] = call->GetAcctSessionId();
params["p"] = call->GetPostDialDelay();
params["r"] = call->GetReleaseSource();
params["t"] = call->GetTotalCallDuration();
if (interfaces.empty())
params["gkip"] = "";
else
params["gkip"] = interfaces.front().AsString();
params["CallId"] = ::AsString(call->GetCallIdentifier().m_guid);
params["ConfId"] = ::AsString(call->GetConferenceIdentifier());
t = call->GetSetupTime();
if (t)
params["setup-time"] = toolkit->AsString(PTime(t), timestampFormat);
t = call->GetAlertingTime();
if (t)
params["alerting-time"] = toolkit->AsString(PTime(t), timestampFormat);
t = call->GetConnectTime();
if (t)
params["connect-time"] = toolkit->AsString(PTime(t), timestampFormat);
t = call->GetDisconnectTime();
if (t)
params["disconnect-time"] = toolkit->AsString(PTime(t), timestampFormat);
params["ring-time"] = call->GetRingTime();
if (call->GetSrcSignalAddr(addr, port)) {
params["caller-ip"] = addr.AsString();
params["caller-port"] = port;
}
params["src-info"] = call->GetSrcInfo();
params["Calling-Station-Id"] = GetCallingStationId(call);
addr = (DWORD)0;
port = 0;
if (call->GetDestSignalAddr(addr, port)) {
params["callee-ip"] = addr.AsString();
params["callee-port"] = port;
}
params["dest-info"] = call->GetDestInfo();
params["Called-Station-Id"] = GetCalledStationId(call);
params["Dialed-Number"] = GetDialedNumber(call);
endptr caller;
if (caller = call->GetCallingParty()) {
params["caller-epid"] = caller->GetEndpointIdentifier().GetValue();
}
endptr callee;
if (callee = call->GetCalledParty()) {
params["callee-epid"] = callee->GetEndpointIdentifier().GetValue();
}
params["call-attempts"] = PString(call->GetNoCallAttempts());
params["last-cdr"] = call->GetNoRemainingRoutes() > 0 ? "0" : "1";
if (call->GetMediaOriginatingIp(addr))
params["media-oip"] = addr.AsString();
params["codec"] = call->GetCodec();
}
PString GkAcctLogger::ReplaceAcctParams(
/// parametrized CDR string
const PString& cdrStr,
/// parameter values
const std::map<PString, PString>& params
) const
{
PString finalCDR((const char*)cdrStr);
PINDEX len = finalCDR.GetLength();
PINDEX pos = 0;
while (pos != P_MAX_INDEX && pos < len) {
pos = finalCDR.Find('%', pos);
if (pos++ == P_MAX_INDEX)
break;
if (pos >= len) // strings ending with '%' - special case
break;
const char c = finalCDR[pos]; // char next after '%'
if (c == '%') { // replace %% with %
finalCDR.Delete(pos, 1);
len--;
} else if (c == '{') { // escaped syntax (%{Name})
const PINDEX closingBrace = finalCDR.Find('}', ++pos);
if (closingBrace != P_MAX_INDEX) {
const PINDEX paramLen = closingBrace - pos;
std::map<PString, PString>::const_iterator i = params.find(
finalCDR.Mid(pos, paramLen)
);
if (i != params.end()) {
const PINDEX escapedLen = EscapeAcctParam(i->second).GetLength();
finalCDR.Splice(EscapeAcctParam(i->second), pos - 2, paramLen + 3);
len = len + escapedLen - paramLen - 3;
pos = pos - 2 + escapedLen;
} else {
// replace out of range parameter with an empty string
finalCDR.Delete(pos - 2, paramLen + 3);
len -= paramLen + 3;
pos -= 2;
}
}
} else { // simple syntax (%c)
std::map<PString, PString>::const_iterator i = params.find(c);
if (i != params.end()) {
const PINDEX escapedLen = EscapeAcctParam(i->second).GetLength();
finalCDR.Splice(EscapeAcctParam(i->second), pos - 1, 2);
len = len + escapedLen - 2;
pos = pos - 1 + escapedLen;
} else {
// replace out of range parameter with an empty string
finalCDR.Delete(pos - 1, 2);
len -= 2;
pos--;
}
}
}
return finalCDR;
}
PString GkAcctLogger::EscapeAcctParam(const PString& param) const
{
return param; // default implementation: don't escape anything
}
PString GkAcctLogger::GetUsername(
/// call (if any) associated with the RAS message
const callptr& call
) const
{
if (!call)
return PString();
const endptr callingEP = call->GetCallingParty();
PString username;
username = GetBestAliasAddressString(call->GetSourceAddress(), true,
AliasAddressTagMask(H225_AliasAddress::e_h323_ID),
AliasAddressTagMask(H225_AliasAddress::e_email_ID)
| AliasAddressTagMask(H225_AliasAddress::e_url_ID)
);
if (callingEP && (username.IsEmpty()
|| FindAlias(callingEP->GetAliases(), username) == P_MAX_INDEX))
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())
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())
username = call->GetCallingStationId();
if (username.IsEmpty()) {
PIPSocket::Address callingSigAddr;
WORD callingSigPort;
if (call->GetSrcSignalAddr(callingSigAddr, callingSigPort)
&& callingSigAddr.IsValid())
username = callingSigAddr.AsString();
}
return username;
}
PString GkAcctLogger::GetCallingStationId(
/// call associated with the accounting event
const callptr& call
) const
{
if (!call)
return PString();
PString id = call->GetCallingStationId();
if (!id)
return id;
if (id.IsEmpty())
id = GetBestAliasAddressString(call->GetSourceAddress(), false,
AliasAddressTagMask(H225_AliasAddress::e_dialedDigits)
| AliasAddressTagMask(H225_AliasAddress::e_partyNumber)
);
if (id.IsEmpty()) {
const endptr callingEP = call->GetCallingParty();
if (callingEP)
id = GetBestAliasAddressString(callingEP->GetAliases(), false,
AliasAddressTagMask(H225_AliasAddress::e_dialedDigits)
| AliasAddressTagMask(H225_AliasAddress::e_partyNumber)
);
}
if (id.IsEmpty()) {
PIPSocket::Address callingSigAddr;
WORD callingSigPort = 0;
if (call->GetSrcSignalAddr(callingSigAddr, callingSigPort)
&& callingSigAddr.IsValid())
id = ::AsString(callingSigAddr, callingSigPort);
}
return id;
}
PString GkAcctLogger::GetCalledStationId(
/// call associated with the accounting event
const callptr& call
) const
{
if (!call)
return PString();
PString id = call->GetCalledStationId();
if (!id)
return id;
if (id.IsEmpty())
id = GetBestAliasAddressString(call->GetDestinationAddress(), false,
AliasAddressTagMask(H225_AliasAddress::e_dialedDigits)
| AliasAddressTagMask(H225_AliasAddress::e_partyNumber)
);
if (id.IsEmpty()) {
const endptr calledEP = call->GetCalledParty();
if (calledEP)
id = GetBestAliasAddressString(calledEP->GetAliases(), false,
AliasAddressTagMask(H225_AliasAddress::e_dialedDigits)
| AliasAddressTagMask(H225_AliasAddress::e_partyNumber)
);
}
if (id.IsEmpty()) {
PIPSocket::Address calledSigAddr;
WORD calledSigPort = 0;
if (call->GetDestSignalAddr(calledSigAddr, calledSigPort)
&& calledSigAddr.IsValid())
id = ::AsString(calledSigAddr, calledSigPort);
}
return id;
}
PString GkAcctLogger::GetDialedNumber(
/// call associated with the accounting event
const callptr& call
) const
{
if (!call)
return PString();
PString id = call->GetDialedNumber();
if (!id)
return id;
if (id.IsEmpty())
id = GetBestAliasAddressString(call->GetDestinationAddress(), false,
AliasAddressTagMask(H225_AliasAddress::e_dialedDigits)
| AliasAddressTagMask(H225_AliasAddress::e_partyNumber)
);
if (id.IsEmpty()) {
const endptr calledEP = call->GetCalledParty();
if (calledEP)
id = GetBestAliasAddressString(calledEP->GetAliases(), false,
AliasAddressTagMask(H225_AliasAddress::e_dialedDigits)
| AliasAddressTagMask(H225_AliasAddress::e_partyNumber)
);
}
if (id.IsEmpty()) {
PIPSocket::Address calledSigAddr;
WORD calledSigPort = 0;
if (call->GetDestSignalAddr(calledSigAddr, calledSigPort)
&& calledSigAddr.IsValid())
id = ::AsString(calledSigAddr, calledSigPort);
}
return id;
}
const char* const FileAcct::m_intervalNames[] =
{
"Hourly", "Daily", "Weekly", "Monthly"
};
FileAcct::FileAcct(
const char* moduleName,
const char* cfgSecName
)
:
GkAcctLogger(moduleName, cfgSecName),
m_cdrFile(NULL), m_rotateLines(-1), m_rotateSize(-1), m_rotateInterval(-1),
m_rotateMinute(-1), m_rotateHour(-1), m_rotateDay(-1),
m_rotateTimer(GkTimerManager::INVALID_HANDLE), m_cdrLines(0),
m_standardCDRFormat(true)
{
SetSupportedEvents(FileAcctEvents);
m_cdrString = GetConfig()->GetString(GetConfigSectionName(), "CDRString", "");
m_standardCDRFormat = Toolkit::AsBool(
GetConfig()->GetString(GetConfigSectionName(), "StandardCDRFormat",
m_cdrString.IsEmpty() ? "1" : "0"
));
m_timestampFormat = GetConfig()->GetString(GetConfigSectionName(),
"TimestampFormat", ""
);
// determine rotation type (by lines, by size, by time)
const PString rotateCondition = GetConfig()->GetString(
GetConfigSectionName(), "Rotate", ""
).Trim();
if (!rotateCondition) {
const char suffix = rotateCondition[rotateCondition.GetLength()-1];
if (rotateCondition[0] == 'L' || rotateCondition[0] == 'l') {
// rotate per number of lines
m_rotateLines = rotateCondition.Mid(1).AsInteger();
if (suffix == 'k' || suffix == 'K')
m_rotateLines *= 1000;
else if (suffix == 'm' || suffix == 'M')
m_rotateLines *= 1000*1000;
} else if (rotateCondition[0] == 'S' || rotateCondition[0] == 's') {
// rotate per CDR file size
m_rotateSize = rotateCondition.Mid(1).AsInteger();
if (suffix == 'k' || suffix == 'K')
m_rotateSize *= 1024;
else if (suffix == 'm' || suffix == 'M')
m_rotateSize *= 1024*1024;
} else {
for (int i = 0; i < RotationIntervalMax; i++)
if (strcasecmp(rotateCondition, m_intervalNames[i]) == 0)
m_rotateInterval = i;
if (m_rotateInterval < 0 || m_rotateInterval >= RotationIntervalMax)
PTRACE(1, "GKACCT\t" << GetName() << " unsupported rotation "
"method: " << rotateCondition << " - rotation disabled"
);
else {
// time based rotation
GetRotateInterval(*GetConfig(), GetConfigSectionName());
}
}
}
m_cdrFilename = GetConfig()->GetString(GetConfigSectionName(), "DetailFile", "");
m_cdrFile = OpenCDRFile(m_cdrFilename);
if (m_cdrFile && m_cdrFile->IsOpen()) {
PTRACE(2, "GKACCT\t" << GetName() << " CDR file: "
<< m_cdrFile->GetFilePath()
);
// count an initial number of CDR lines
if (m_rotateLines > 0) {
PString s;
m_cdrFile->SetPosition(0);
while (m_cdrFile->ReadLine(s))
m_cdrLines++;
m_cdrFile->SetPosition(m_cdrFile->GetLength());
}
}
// setup rotation timer in case of time based rotation
PTime now, rotateTime;
switch (m_rotateInterval)
{
case Hourly:
rotateTime = PTime(0, m_rotateMinute, now.GetHour(), now.GetDay(),
now.GetMonth(), now.GetYear(), now.GetTimeZone()
);
if (rotateTime <= now)
rotateTime += PTimeInterval(0, 0, 0, 1); // 1 hour
m_rotateTimer = Toolkit::Instance()->GetTimerManager()->RegisterTimer(
this, &FileAcct::RotateOnTimer, rotateTime, 60*60
);
PTRACE(5, "GKACCT\t" << GetName() << " hourly rotation enabled (first "
"rotation sheduled at " << rotateTime
);
break;
case Daily:
rotateTime = PTime(0, m_rotateMinute, m_rotateHour, now.GetDay(),
now.GetMonth(), now.GetYear(), now.GetTimeZone()
);
if (rotateTime <= now)
rotateTime += PTimeInterval(0, 0, 0, 0, 1); // 1 day
m_rotateTimer = Toolkit::Instance()->GetTimerManager()->RegisterTimer(
this, &FileAcct::RotateOnTimer, rotateTime, 60*60*24
);
PTRACE(5, "GKACCT\t" << GetName() << " daily rotation enabled (first "
"rotation sheduled at " << rotateTime
);
break;
case Weekly:
rotateTime = PTime(0, m_rotateMinute, m_rotateHour, now.GetDay(),
now.GetMonth(), now.GetYear(), now.GetTimeZone()
);
if (rotateTime.GetDayOfWeek() < m_rotateDay)
rotateTime += PTimeInterval(0, 0, 0, 0,
m_rotateDay - rotateTime.GetDayOfWeek()
);
else if (rotateTime.GetDayOfWeek() > m_rotateDay)
rotateTime -= PTimeInterval(0, 0, 0, 0,
rotateTime.GetDayOfWeek() - m_rotateDay
);
if (rotateTime <= now)
rotateTime += PTimeInterval(0, 0, 0, 0, 7); // 1 week
m_rotateTimer = Toolkit::Instance()->GetTimerManager()->RegisterTimer(
this, &FileAcct::RotateOnTimer, rotateTime, 60*60*24*7
);
PTRACE(5, "GKACCT\t" << GetName() << " weekly rotation enabled (first "
"rotation sheduled at " << rotateTime
);
break;
case Monthly:
rotateTime = PTime(0, m_rotateMinute, m_rotateHour, 1,
now.GetMonth(), now.GetYear(), now.GetTimeZone()
);
rotateTime += PTimeInterval(0, 0, 0, 0, m_rotateDay - 1);
while (rotateTime.GetMonth() != now.GetMonth())
rotateTime -= PTimeInterval(0, 0, 0, 0, 1); // 1 day
if (rotateTime <= now) {
rotateTime = PTime(0, m_rotateMinute, m_rotateHour, 1,
now.GetMonth() + (now.GetMonth() == 12 ? -11 : 1),
now.GetYear() + (now.GetMonth() == 12 ? 1 : 0),
now.GetTimeZone()
);
const int month = rotateTime.GetMonth();
rotateTime += PTimeInterval(0, 0, 0, 0, m_rotateDay - 1);
while (rotateTime.GetMonth() != month)
rotateTime -= PTimeInterval(0, 0, 0, 0, 1); // 1 day
}
m_rotateTimer = Toolkit::Instance()->GetTimerManager()->RegisterTimer(
this, &FileAcct::RotateOnTimer, rotateTime
);
PTRACE(5, "GKACCT\t" << GetName() << " monthly rotation enabled (first "
"rotation sheduled at " << rotateTime
);
break;
}
}
FileAcct::~FileAcct()
{
if (m_rotateTimer != GkTimerManager::INVALID_HANDLE)
Toolkit::Instance()->GetTimerManager()->UnregisterTimer(m_rotateTimer);
PWaitAndSignal lock(m_cdrFileMutex);
if (m_cdrFile) {
m_cdrFile->Close();
delete m_cdrFile;
}
}
void FileAcct::GetRotateInterval(
PConfig& cfg,
const PString& section
)
{
PString s;
if (m_rotateInterval == Hourly)
m_rotateMinute = cfg.GetInteger(section, "RotateTime", 59);
else {
s = cfg.GetString(section, "RotateTime", "00:59");
m_rotateHour = s.AsInteger();
m_rotateMinute = 0;
if (s.Find(':') != P_MAX_INDEX)
m_rotateMinute = s.Mid(s.Find(':') + 1).AsInteger();
if (m_rotateHour < 0 || m_rotateHour > 23 || m_rotateMinute < 0
|| m_rotateMinute > 59) {
PTRACE(1, "GKACCT\t" << GetName() << " invalid "
"RotateTime specified: " << s
);
m_rotateMinute = 59;
m_rotateHour = 0;
}
}
if (m_rotateInterval == Weekly) {
s = cfg.GetString(section, "RotateDay", "Sun");
if (strspn(s, "0123456") == (size_t)s.GetLength()) {
m_rotateDay = s.AsInteger();
} else {
std::map<PCaselessString, int> dayNames;
dayNames["sun"] = 0; dayNames["sunday"] = 0;
dayNames["mon"] = 1; dayNames["monday"] = 1;
dayNames["tue"] = 2; dayNames["tuesday"] = 2;
dayNames["wed"] = 3; dayNames["wednesday"] = 3;
dayNames["thu"] = 4; dayNames["thursday"] = 4;
dayNames["fri"] = 5; dayNames["friday"] = 5;
dayNames["sat"] = 6; dayNames["saturday"] = 6;
std::map<PCaselessString, int>::const_iterator i = dayNames.find(s);
m_rotateDay = (i != dayNames.end()) ? i->second : -1;
}
if (m_rotateDay < 0 || m_rotateDay > 6) {
PTRACE(1, "GKACCT\t" << GetName() << " invalid "
"RotateDay specified: " << s
);
m_rotateDay = 0;
}
} else if (m_rotateInterval == Monthly) {
m_rotateDay = cfg.GetInteger(section, "RotateDay", 1);
if (m_rotateDay < 1 || m_rotateDay > 31) {
PTRACE(1, "GKACCT\t" << GetName() << " invalid "
"RotateDay specified: " << cfg.GetString(section, "RotateDay", "")
);
m_rotateDay = 1;
}
}
}
GkAcctLogger::Status FileAcct::Log(
GkAcctLogger::AcctEvent evt,
const callptr& call
)
{
if ((evt & GetEnabledEvents() & GetSupportedEvents()) == 0)
return Next;
if (!call) {
PTRACE(1, "GKACCT\t" << GetName() << " - missing call info for event " << evt);
return Fail;
}
PString cdrString;
if (!GetCDRText(cdrString, evt, call)) {
PTRACE(2, "GKACCT\t" << GetName() << " - unable to get CDR text for "
"event " << evt << ", call no. " << call->GetCallNumber()
);
return Fail;
}
PWaitAndSignal lock(m_cdrFileMutex);
if (m_cdrFile && m_cdrFile->IsOpen()) {
if (m_cdrFile->WriteLine(PString(cdrString))) {
PTRACE(5, "GKACCT\t" << GetName() << " - CDR string for event "
<< evt << ", call no. " << call->GetCallNumber()
<< ": " << cdrString
);
m_cdrLines++;
if (IsRotationNeeded())
Rotate();
return Ok;
} else
PTRACE(1, "GKACCT\t" << GetName() << " - write CDR text for event "
<< evt << ", call no. " << call->GetCallNumber()
<< " failed: " << m_cdrFile->GetErrorText()
);
} else
PTRACE(1, "GKACCT\t" << GetName() << " - write CDR text for event "
<< evt << ", for call no. " << call->GetCallNumber()
<< " failed: CDR file is closed"
);
return Fail;
}
bool FileAcct::GetCDRText(
PString& cdrString,
AcctEvent evt,
const callptr& call
)
{
if ((evt & AcctStop) != AcctStop || !call)
return false;
if (m_standardCDRFormat)
cdrString = call->GenerateCDR(m_timestampFormat);
else {
std::map<PString, PString> params;
SetupAcctParams(params, call, m_timestampFormat);
cdrString = ReplaceAcctParams(m_cdrString, params);
}
return !cdrString;
}
bool FileAcct::IsRotationNeeded()
{
if (m_rotateLines > 0 && m_cdrLines >= m_rotateLines)
return true;
if (m_rotateSize > 0 && m_cdrFile && m_cdrFile->GetLength() >= m_rotateSize)
return true;
return false;
}
void FileAcct::RotateOnTimer(
GkTimer* timer
)
{
if (m_rotateInterval == Monthly) {
// setup next time for one-shot timer
const PTime& rotateTime = timer->GetExpirationTime();
PTime newRotateTime(rotateTime.GetSecond(), rotateTime.GetMinute(),
rotateTime.GetHour(), 1,
rotateTime.GetMonth() < 12 ? rotateTime.GetMonth() + 1 : 1,
rotateTime.GetMonth() < 12 ? rotateTime.GetYear() : rotateTime.GetYear() + 1,
rotateTime.GetTimeZone()
);
const int month = newRotateTime.GetMonth();
newRotateTime += PTimeInterval(0, 0, 0, 0, m_rotateDay - 1);
while (newRotateTime.GetMonth() != month)
newRotateTime -= PTimeInterval(0, 0, 0, 0, 1);
timer->SetExpirationTime(newRotateTime);
timer->SetFired(false);
}
PWaitAndSignal lock(m_cdrFileMutex);
Rotate();
}
void FileAcct::Rotate()
{
if (m_cdrFile) {
if (m_cdrFile->IsOpen())
m_cdrFile->Close();
delete m_cdrFile;
m_cdrFile = NULL;
}
const PFilePath fn = m_cdrFilename;
if (PFile::Exists(fn))
if (!PFile::Rename(fn, fn.GetFileName() + PTime().AsString(".yyyyMMdd-hhmmss")))
PTRACE(1, "GKACCT\t" << GetName() << " rotate failed - could not "
"rename the log file"
);
m_cdrFile = OpenCDRFile(fn);
m_cdrLines = 0;
}
PTextFile* FileAcct::OpenCDRFile(
const PFilePath& fn
)
{
PTextFile* cdrFile = new PTextFile(fn, PFile::ReadWrite,
PFile::Create | PFile::DenySharedWrite
);
if (!cdrFile->IsOpen()) {
PTRACE(1, "GKACCT\t" << GetName() << " could not open file"
" required for plain text accounting \""
<< fn << "\" :" << cdrFile->GetErrorText()
);
delete cdrFile;
return NULL;
}
cdrFile->SetPermissions(PFileInfo::UserRead | PFileInfo::UserWrite);
cdrFile->SetPosition(cdrFile->GetLength());
return cdrFile;
}
GkAcctLoggerList::GkAcctLoggerList()
: m_acctUpdateInterval(
GkConfig()->GetInteger(CallTableSection, "AcctUpdateInterval", 0)
)
{
// should not be less than 10 seconds
if (m_acctUpdateInterval)
m_acctUpdateInterval = PMAX(10, m_acctUpdateInterval);
}
GkAcctLoggerList::~GkAcctLoggerList()
{
DeleteObjectsInContainer(m_loggers);
m_loggers.clear();
}
void GkAcctLoggerList::OnReload()
{
m_acctUpdateInterval = GkConfig()->GetInteger(CallTableSection,
"AcctUpdateInterval", 0
);
// should not be less than 10 seconds
if (m_acctUpdateInterval)
m_acctUpdateInterval = PMAX(10, m_acctUpdateInterval);
DeleteObjectsInContainer(m_loggers);
m_loggers.clear();
const PStringArray modules = GkConfig()->GetKeys(GkAcctSectionName);
for (PINDEX i = 0; i < modules.GetSize(); i++) {
GkAcctLogger* logger = Factory<GkAcctLogger>::Create(modules[i]);
if (logger)
m_loggers.push_back(logger);
}
}
bool GkAcctLoggerList::LogAcctEvent(
GkAcctLogger::AcctEvent evt, /// the accounting event to be logged
const callptr& call, /// a call associated with the event (if any)
time_t now /// "now" timestamp for accounting update events
)
{
// if this is an accounting update, check the interval
if (evt & GkAcctLogger::AcctUpdate)
if ((!call) || m_acctUpdateInterval == 0
|| (now - call->GetLastAcctUpdateTime()) < m_acctUpdateInterval)
return true;
else
call->SetLastAcctUpdateTime(now);
bool finalResult = true;
GkAcctLogger::Status status = GkAcctLogger::Ok;
std::list<GkAcctLogger*>::const_iterator iter = m_loggers.begin();
while (iter != m_loggers.end()) {
GkAcctLogger* logger = *iter++;
if ((evt & logger->GetEnabledEvents() & logger->GetSupportedEvents()) == 0)
continue;
status = logger->Log(evt, call);
switch (status)
{
case GkAcctLogger::Ok:
#if PTRACING
if (PTrace::CanTrace(3)) {
ostream& strm = PTrace::Begin(3,__FILE__,__LINE__);
strm << "GKACCT\t" << logger->GetName() << " logged event " << evt;
if (call)
strm << " for call no. " << call->GetCallNumber();
PTrace::End(strm);
}
#endif
break;
default:
#if PTRACING
if (PTrace::CanTrace(3)) {
ostream& strm = PTrace::Begin(3, __FILE__, __LINE__);
strm << "GKACCT\t" << logger->GetName() << " failed to log event "
<< evt;
if (call)
strm << " for call no. " << call->GetCallNumber();
PTrace::End(strm);
}
#endif
// required and sufficient rules always determine
// status of the request
if (logger->GetControlFlag() == GkAcctLogger::Required
|| logger->GetControlFlag() == GkAcctLogger::Sufficient)
finalResult = false;
}
// sufficient and alternative are terminal rules (on log success)
if (status == GkAcctLogger::Ok
&& (logger->GetControlFlag() == GkAcctLogger::Sufficient
|| logger->GetControlFlag() == GkAcctLogger::Alternative))
break;
}
// a last rule determine status of the the request
if (finalResult && status != GkAcctLogger::Ok)
finalResult = false;
#if PTRACING
if (PTrace::CanTrace(2)) {
ostream& strm = PTrace::Begin(2, __FILE__, __LINE__);
strm << "GKACCT\t" << (finalResult ? "Successfully logged event "
: "Failed to log event ") << evt;
if (call)
strm << " for call no. " << call->GetCallNumber();
PTrace::End(strm);
}
#endif
return finalResult;
}
namespace {
GkAcctLoggerCreator<GkAcctLogger> DefaultAcctLoggerCreator("default");
GkAcctLoggerCreator<FileAcct> FileAcctLoggerCreator("FileAcct");
}
syntax highlighted by Code2HTML, v. 0.9.1