/* * 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 #include #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 "< value) associations std::map& 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 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& 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::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::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 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::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 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::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::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 DefaultAcctLoggerCreator("default"); GkAcctLoggerCreator FileAcctLoggerCreator("FileAcct"); }