/* * 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.35 2006/06/08 08:58:48 willamowius * gcc 4.1 compile fixes * * Revision 1.34 2006/04/14 13:56:19 willamowius * call failover code merged * * Revision 1.2 2005/12/05 13:29:02 zvision * Accept multiple routes from RADIUS/SQL auth modules * * 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.33 2005/04/24 16:39:44 zvision * MSVC6.0 compatibility fixed * * Revision 1.32 2005/02/10 23:26:39 zvision * Accounting updates/call disconnect handling does not lock the whole call table * * Revision 1.31 2005/02/01 14:28:10 zvision * Parts of signaling code rewritten * * Revision 1.30 2005/01/28 11:19:42 zvision * All passwords in the config can be stored in an encrypted form * * Revision 1.29 2005/01/26 23:50:26 zvision * Framed-IP-Address could not be determined to unregistered calls without * Setup-UUIE.sourceCallSignalAddress field * * Revision 1.28 2005/01/25 18:59:08 zvision * Aliases handling fixed, alias type is not appended anymore * * Revision 1.27 2005/01/25 00:37:35 zvision * Handle aliases of type partyNumber properly * * Revision 1.26 2005/01/10 22:49:29 willamowius * typo * * Revision 1.25 2004/12/08 13:02:56 zvision * Better Calling/Called-Station-Id handling * * Revision 1.24 2004/11/15 23:57:42 zvision * Ability to choose between the original and the rewritten dialed number * * Revision 1.23 2004/11/03 10:40:17 zvision * Add/remove RRQ aliases using h323-ivr-in=terminal-alias Cisco AV-Pair attr * * Revision 1.22 2004/08/09 21:52:23 zvision * RADIUS based call routing * * Revision 1.21 2004/07/26 12:19:41 zvision * New faster Radius implementation, thanks to Pavel Pavlov for ideas! * * Revision 1.20.2.2 2004/07/07 23:11:07 zvision * Faster and more elegant handling of Cisco VSA * * Revision 1.20.2.1 2004/07/07 20:50:14 zvision * New, faster, Radius client implementation. Thanks to Pavel Pavlov for ideas! * * Revision 1.20 2004/07/06 23:46:20 zvision * gcc 2.95.x compilation errors fixed * * Revision 1.19 2004/07/05 16:39:45 zvision * Support for CallCreditServiceControl * * Revision 1.18 2004/06/25 13:33:19 zvision * Better Username, Calling-Station-Id and Called-Station-Id handling. * New SetupUnreg option in Gatekeeper::Auth section. * * Revision 1.17 2004/06/17 10:47:13 zvision * New h323-ivr-out=h323-call-id accounting attribute * * Revision 1.16 2004/06/17 10:03:17 zvision * Better Framed-IP-Address handling in RadAliasAuth Setup check * * Revision 1.15 2004/06/16 23:46:47 zvision * RadAliasAuth will work even when Setup-UUIE does not contain sourceAddress * * Revision 1.14 2004/05/22 12:25:17 zvision * Check aliases only when authenticating RRQ message * * Revision 1.13 2004/04/17 11:43:43 zvision * Auth/acct API changes. * Header file usage more consistent. * * Revision 1.12 2004/03/17 00:00:38 zvision * Conditional compilation to allow to control RADIUS on Windows just by setting HA_RADIUS macro * * Revision 1.11 2004/02/20 14:44:11 zvision * Changed API for GkAuthenticator class. Modified RadAuth/RadAliasAuth classes. * Added Q.931 Setup authentication for RadAuth module. * * Revision 1.10 2003/11/14 00:27:30 zvision * Q.931/H.225 Setup authentication added * * Revision 1.9 2003/10/31 00:01:28 zvision * Improved accounting modules stacking control, optimized radacct/radauth a bit * * Revision 1.8 2003/10/21 15:55:27 zvision * Fixed compiler warnings for gcc < 3 * * Revision 1.7 2003/10/15 10:16:57 zvision * Fixed VC6 compiler warnings. Thanks to Hu Yuxin. * * Revision 1.6 2003/10/08 12:40:48 zvision * Realtime accounting updates added * * Revision 1.5 2003/09/28 16:24:31 zvision * Introduced call duration limit feature for registered endpoints (ARQ) * * Revision 1.4 2003/08/25 12:53:38 zvision * Introduced includeTerminalAliases config option. Changed visibility * of some member variables to private. * * Revision 1.3 2003/08/20 14:46:19 zvision * Avoid PString reference copying. Small code improvements. * * Revision 1.2 2003/08/19 10:47:37 zvision * Initially added to 2.2 brach. Completely redesigned. * Redundant code removed. Added h323-return-code, h323-credit-time * and Session-Timeout respone attributes processing. * * 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 * */ #if HAS_RADIUS #if defined(_WIN32) && (_MSC_VER <= 1200) #pragma warning(disable:4786) // warning about too long debug symbol off #endif #include #include #include #include #include #include #include "gk_const.h" #include "h323util.h" #include "stl_supp.h" #include "Toolkit.h" #include "RasTbl.h" #include "RasPDU.h" #include "Routing.h" #include "sigmsg.h" #include "radproto.h" #include "gkauth.h" #include "radauth.h" using std::vector; using Routing::Route; 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"; } // OID for CAT (Cisco Access Token) algorithm PString RadAuth::OID_CAT("1.2.840.113548.10.1.2.1"); RadAuthBase::RadAuthBase( const char* authName, const char* configSectionName, unsigned supportedRasChecks, unsigned supportedMiscChecks ) : GkAuthenticator(authName, supportedRasChecks, supportedMiscChecks), m_radiusClient(NULL), m_attrH323CallType(RadiusAttr::CiscoVSA_h323_call_type, false, PString("VoIP")), m_attrH323CallOriginOriginate(RadiusAttr::CiscoVSA_h323_call_origin, false, PString("originate")), m_attrH323CallOriginAnswer(RadiusAttr::CiscoVSA_h323_call_origin, false, PString("answer")) { // read settings from the config m_appendCiscoAttributes = Toolkit::AsBool(GetConfig()->GetString( configSectionName,"AppendCiscoAttributes", "1" )); m_includeTerminalAliases = Toolkit::AsBool(GetConfig()->GetString( configSectionName, "IncludeTerminalAliases", "1" )); m_nasIdentifier = Toolkit::Instance()->GKName(); /// build RADIUS client m_radiusClient = new RadiusClient(*GetConfig(), configSectionName); m_nasIpAddress = m_radiusClient->GetLocalAddress(); if (m_nasIpAddress == INADDR_ANY) { vector interfaces; Toolkit::Instance()->GetGKHome(interfaces); if (!interfaces.empty()) m_nasIpAddress = interfaces.front(); else PTRACE(1, "RADAUTH\t" << GetName() << " cannot determine " " NAS IP address" ); } m_useDialedNumber = Toolkit::AsBool(GetConfig()->GetString( configSectionName, "UseDialedNumber", "0" )); m_attrH323GwId = RadiusAttr(RadiusAttr::CiscoVSA_h323_gw_id, false, m_nasIdentifier); m_attrNasIdentifier = RadiusAttr(RadiusAttr::NasIdentifier, m_nasIdentifier); } RadAuthBase::~RadAuthBase() { delete m_radiusClient; } int RadAuthBase::Check( /// RRQ RAS message to be authenticated RasPDU& rrqPdu, /// authorization data (reject reason, ...) RRQAuthData& authData ) { H225_RegistrationRequest& rrq = (H225_RegistrationRequest&)rrqPdu; // build RADIUS Access-Request RadiusPDU* const pdu = new RadiusPDU(RadiusPDU::AccessRequest); // Append User-Name and a password related attributes // (User-Password or Chap-Password and Chap-Timestamp) const int status = AppendUsernameAndPassword(*pdu, rrqPdu, authData); if (status != e_ok) { delete pdu; return status; } // Gk works as NAS point, so append NAS IP pdu->AppendAttr(RadiusAttr::NasIpAddress, m_nasIpAddress); // NAS-Identifier as Gk name pdu->AppendAttr(m_attrNasIdentifier); // Gk does not have a concept of physical ports, // so define port type as NAS-Port-Virtual pdu->AppendAttr(RadiusAttr::NasPortType, RadiusAttr::NasPort_Virtual); // RRQ service type is Login-User pdu->AppendAttr(RadiusAttr::ServiceType, RadiusAttr::ST_Login); // append Framed-IP-Address PIPSocket::Address addr; bool ipFound = false; if (rrq.m_callSignalAddress.GetSize() > 0) { if (GetIPFromTransportAddr(rrq.m_callSignalAddress[0], addr) && addr.IsValid()) ipFound = true; } else if (rrq.m_rasAddress.GetSize() > 0) { if (GetIPFromTransportAddr(rrq.m_rasAddress[0], addr) && addr.IsValid()) ipFound = true; } if (!ipFound) { PTRACE(2, "RADAUTH\t" << GetName() << " RRQ auth failed: " "could not determine Framed-IP-Address" ); authData.m_rejectReason = H225_RegistrationRejectReason::e_invalidCallSignalAddress; delete pdu; return e_fail; } else pdu->AppendAttr(RadiusAttr::FramedIpAddress, addr); if (m_appendCiscoAttributes && m_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 += AsString(rrq.m_terminalAlias[i], FALSE); } // Cisco-AV-Pair pdu->AppendCiscoAttr(RadiusAttr::CiscoVSA_AV_Pair, PString("h323-ivr-out=") + aliasList + ";", true ); } if (!OnSendPDU(*pdu, rrqPdu, authData)) { delete pdu; return e_fail; } // send request and wait for response RadiusPDU* response = NULL; bool result = m_radiusClient->MakeRequest(*pdu, response) && response; delete pdu; if (!result) { PTRACE(2, "RADAUTH\t" << GetName() << " RRQ auth failed: " " could not receive or decode response from RADIUS" ); delete response; authData.m_rejectReason = H225_RegistrationRejectReason::e_undefinedReason; return GetDefaultStatus(); } result = (response->GetCode() == RadiusPDU::AccessAccept); PString value; const RadiusAttr* attr; // test for h323-return-code attribute (has to be 0 if accept) if (result) { attr = response->FindVsaAttr(RadiusAttr::CiscoVendorId, RadiusAttr::CiscoVSA_h323_return_code ); if (attr != NULL) { value = attr->AsCiscoString(); if (value.GetLength() > 0 && strspn((const char*)value, "0123456789") == (size_t)value.GetLength()) { const unsigned retcode = value.AsUnsigned(); if (retcode != 0) { PTRACE(3, "RADAUTH\t" << GetName() << " RRQ check failed: " "return code " << retcode ); result = false; } } else { PTRACE(2, "RADAUTH\t" << GetName() << " RRQ check failed: " "invalid h323-return-code attribute '" << value << '\'' ); result = false; } } } // check for h323-billing-model if (result) { attr = response->FindVsaAttr(RadiusAttr::CiscoVendorId, RadiusAttr::CiscoVSA_h323_billing_model ); if (attr != NULL) { value = attr->AsCiscoString(); if (value.GetLength() > 0 && strspn((const char*)value,"0123456789") == (size_t)value.GetLength()) { const int intVal = value.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, "RADAUTH\t" << GetName() << " invalid h323-billing-model " "attribute '" << value << '\'' ); } } } // check for h323-credit-amount if (result) { attr = response->FindVsaAttr(RadiusAttr::CiscoVendorId, RadiusAttr::CiscoVSA_h323_credit_amount ); if (attr != NULL) { value = attr->AsCiscoString(); if (value.GetLength() > 0 && strspn((const char*)value,"0123456789.") == (size_t)value.GetLength()) { if (value.Find('.') == P_MAX_INDEX) { PTRACE(3, "RADAUTH\t" << GetName() << " h323-credit-amount " "without a decimal dot is ambiguous '" << value << '\'' ); authData.m_amountString = psprintf(PString("%d.%d"), value.AsInteger() / 100, value.AsInteger() % 100 ); } else authData.m_amountString = value; attr = response->FindVsaAttr(RadiusAttr::CiscoVendorId, RadiusAttr::CiscoVSA_h323_currency ); if (attr != NULL) authData.m_amountString += attr->AsCiscoString(); } else { PTRACE(3, "RADAUTH\t" << GetName() << " invalid h323-credit-amount " "attribute '" << value << '\'' ); } } } // process h323-ivr-in=terminal-alias attribute if (result) { attr = response->FindVsaAttr(RadiusAttr::CiscoVendorId, RadiusAttr::CiscoVSA_AV_Pair ); while (attr != NULL) { PINDEX index; value = attr->AsCiscoString(); if (value.Find("h323-ivr-in=") == 0 && ((index = value.Find("terminal-alias:")) != P_MAX_INDEX)) { index += strlen("terminal-alias:"); const PINDEX semicolonpos = value.Find(';', index); value = value.Mid(index, semicolonpos == P_MAX_INDEX ? P_MAX_INDEX : (semicolonpos-index) ); PStringArray aliases = value.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]); } break; } attr = response->FindVsaAttr(RadiusAttr::CiscoVendorId, RadiusAttr::CiscoVSA_AV_Pair, attr ); } } if (result) result = OnReceivedPDU(*response, rrqPdu, authData); else authData.m_rejectReason = H225_RegistrationRejectReason::e_securityDenial; delete response; return result ? e_ok : e_fail; } int RadAuthBase::Check( /// ARQ nessage to be authenticated RasPDU & arqPdu, /// authorization data (call duration limit, reject reason, ...) ARQAuthData& authData ) { H225_AdmissionRequest& arq = (H225_AdmissionRequest&)arqPdu; // build RADIUS Access-Request packet RadiusPDU* const pdu = new RadiusPDU(RadiusPDU::AccessRequest); const bool hasCall = authData.m_call.operator->() != NULL; PIPSocket::Address addr; endptr callingEP, calledEP; // try to extract calling/called endpoints from RegistrationTable // (unregistered endpoints will not be present there) if (arq.m_answerCall) { calledEP = authData.m_requestingEP; if (hasCall) callingEP = authData.m_call->GetCallingParty(); } else { callingEP = authData.m_requestingEP; if (hasCall) calledEP = authData.m_call->GetCalledParty(); if (!calledEP && arq.HasOptionalField(H225_AdmissionRequest::e_destCallSignalAddress)) calledEP = RegistrationTable::Instance()->FindBySignalAdr(arq.m_destCallSignalAddress); } // at least requesting endpoint (the one that is sending ARQ) // has to be present in the RegistrationTable if (arq.m_answerCall ? !calledEP : !callingEP) { delete pdu; PTRACE(3, "RADAUTH\t" << GetName() << " ARQ auth failed: " "requesting endpoint " << arq.m_endpointIdentifier << " not registered" ); authData.m_rejectReason = arq.m_answerCall ? H225_AdmissionRejectReason::e_calledPartyNotRegistered : H225_AdmissionRejectReason::e_callerNotRegistered; return e_fail; } // Append User-Name and a password related attributes // (User-Password or Chap-Password and Chap-Timestamp) PString username; const int status = AppendUsernameAndPassword(*pdu, arqPdu, authData, &username); if (status != e_ok) { delete pdu; return status; } // Gk acts as NAS, so include NAS IP pdu->AppendAttr(RadiusAttr::NasIpAddress, m_nasIpAddress); // NAS-Identifier as Gk name pdu->AppendAttr(m_attrNasIdentifier); // NAS-Port-Type as Virtual, since Gk does // not care about physical ports concept pdu->AppendAttr(RadiusAttr::NasPortType, RadiusAttr::NasPort_Virtual); // Service-Type is Login-User if originating the call // and Call Check if answering the call pdu->AppendAttr(RadiusAttr::ServiceType, arq.m_answerCall ? RadiusAttr::ST_CallCheck : RadiusAttr::ST_Login ); // append Frame-IP-Address bool ipFound = false; if (arq.m_answerCall) { if (calledEP && GetIPFromTransportAddr(calledEP->GetCallSignalAddress(), addr) && addr.IsValid()) ipFound = true; else if (arq.HasOptionalField(arq.e_destCallSignalAddress) && GetIPFromTransportAddr(arq.m_destCallSignalAddress, addr) && addr.IsValid()) ipFound = true; } else { if (callingEP && GetIPFromTransportAddr(callingEP->GetCallSignalAddress(), addr) && addr.IsValid()) ipFound = true; else if(arq.HasOptionalField(arq.e_srcCallSignalAddress) && GetIPFromTransportAddr(arq.m_srcCallSignalAddress, addr) && addr.IsValid()) ipFound = true; } if (!ipFound) { PTRACE(2, "RADAUTH\t" << GetName() << " ARQ auth failed: " "could not setup Framed-IP-Address" ); authData.m_rejectReason = H225_AdmissionRejectReason::e_securityDenial; delete pdu; return e_fail; } else pdu->AppendAttr(RadiusAttr::FramedIpAddress, addr); // fill Calling-Station-Id and Called-Station-Id fields PString stationId = GetCallingStationId(arqPdu, authData); if (!stationId) { pdu->AppendAttr(RadiusAttr::CallingStationId, stationId); } const PString dialedNumber = GetDialedNumber(arqPdu, authData); const PString calledStationId = GetCalledStationId(arqPdu, authData); stationId = m_useDialedNumber ? dialedNumber : calledStationId; if (stationId.IsEmpty()) { delete pdu; PTRACE(2, "RADAUTH\t" << GetName() << " ARQ auth failed: " "no suitable alias for Calling-Station-Id has been found" ); authData.m_rejectReason = H225_AdmissionRejectReason::e_securityDenial; return e_fail; } else pdu->AppendAttr(RadiusAttr::CalledStationId, stationId); if (m_appendCiscoAttributes) { pdu->AppendCiscoAttr(RadiusAttr::CiscoVSA_h323_conf_id, GetGUIDString(arq.m_conferenceID) ); if (arq.m_answerCall) pdu->AppendAttr(m_attrH323CallOriginAnswer); else pdu->AppendAttr(m_attrH323CallOriginOriginate); pdu->AppendAttr(m_attrH323CallType); pdu->AppendAttr(m_attrH323GwId); } if (!OnSendPDU(*pdu, arqPdu, authData)) { delete pdu; return e_fail; } // send the request and wait for a response RadiusPDU* response = NULL; bool result = m_radiusClient->MakeRequest(*pdu, response) && response; delete pdu; if (!result) { PTRACE(2, "RADAUTH\t" << GetName() << " ARQ auth failed: " " could not receive or decode response from RADIUS" ); delete response; authData.m_rejectReason = H225_AdmissionRejectReason::e_undefinedReason; return GetDefaultStatus(); } // authenticated? result = (response->GetCode() == RadiusPDU::AccessAccept); PString value; const RadiusAttr* attr; // test for h323-return-code attribute (has to be 0 if accept) if (result) { attr = response->FindVsaAttr(RadiusAttr::CiscoVendorId, RadiusAttr::CiscoVSA_h323_return_code ); if (attr != NULL) { value = attr->AsCiscoString(); if (value.GetLength() > 0 && strspn((const char*)value, "0123456789") == (size_t)value.GetLength()) { const unsigned retcode = value.AsUnsigned(); if (retcode != 0) { PTRACE(3, "RADAUTH\t" << GetName() << " ARQ check failed: " "return code " << retcode ); result = false; } } else { PTRACE(2, "RADAUTH\t" << GetName() << " ARQ check failed: " "invalid h323-return-code attribute '" << value << '\'' ); result = false; } } } // check for h323-credit-time attribute (call duration limit) if (result) { attr = response->FindVsaAttr(RadiusAttr::CiscoVendorId, RadiusAttr::CiscoVSA_h323_credit_time ); if (attr != NULL) { value = attr->AsCiscoString(); if (value.GetLength() > 0 && strspn((const char*)value,"0123456789") == (size_t)value.GetLength()) { authData.m_callDurationLimit = value.AsInteger(); PTRACE(5, "RADAUTH\t" << GetName() << " ARQ check set duration " "limit: " << authData.m_callDurationLimit ); if (authData.m_callDurationLimit == 0) result = false; } else { PTRACE(2, "RADAUTH\t" << GetName() << " ARQ check failed: " "invalid h323-credit-time attribute '" << value << '\'' ); result = false; } } } // check for Session-Timeout attribute (alternate call duration limit) if (result) { const RadiusAttr* const attr = response->FindAttr(RadiusAttr::SessionTimeout); if (attr != NULL) { const long sessionTimeout = attr->AsInteger(); if (authData.m_callDurationLimit < 0 || authData.m_callDurationLimit > sessionTimeout) { authData.m_callDurationLimit = sessionTimeout; PTRACE(5, "RADAUTH\t" << GetName() << " ARQ check set " "duration limit set " << authData.m_callDurationLimit ); } if (authData.m_callDurationLimit == 0) result = false; } } // check for h323-billing-model if (result) { attr = response->FindVsaAttr(RadiusAttr::CiscoVendorId, RadiusAttr::CiscoVSA_h323_billing_model ); if (attr != NULL) { value = attr->AsCiscoString(); if (value.GetLength() > 0 && strspn((const char*)value,"0123456789") == (size_t)value.GetLength()) { const int intVal = value.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, "RADAUTH\t" << GetName() << " invalid h323-billing-model " "attribute '" << value << '\'' ); } } } // check for h323-credit-amount if (result) { attr = response->FindVsaAttr(RadiusAttr::CiscoVendorId, RadiusAttr::CiscoVSA_h323_credit_amount ); if (attr != NULL) { value = attr->AsCiscoString(); if (value.GetLength() > 0 && strspn((const char*)value,"0123456789.") == (size_t)value.GetLength()) { if (value.Find('.') == P_MAX_INDEX) { PTRACE(3, "RADAUTH\t" << GetName() << " h323-credit-amount " "without a decimal dot is ambiguous '" << value << '\'' ); authData.m_amountString = psprintf(PString("%d.%d"), value.AsInteger() / 100, value.AsInteger() % 100 ); } else authData.m_amountString = value; attr = response->FindVsaAttr(RadiusAttr::CiscoVendorId, RadiusAttr::CiscoVSA_h323_currency ); if (attr != NULL) authData.m_amountString += attr->AsCiscoString(); } else { PTRACE(3, "RADAUTH\t" << GetName() << " invalid h323-credit-amount " "attribute '" << value << '\'' ); } } } // check for h323-redirect-number if (result) { attr = response->FindVsaAttr(RadiusAttr::CiscoVendorId, RadiusAttr::CiscoVSA_h323_redirect_number ); if (attr != NULL) { value = attr->AsCiscoString(); if (!value) { authData.SetRouteToAlias(value); PTRACE(5, "RADAUTH\t" << GetName() << " ARQ check redirect " "to the number " << value ); } } } // check for h323-redirect-ip-address if (result) { attr = response->FindVsaAttr(RadiusAttr::CiscoVendorId, RadiusAttr::CiscoVSA_h323_redirect_ip_address ); if (attr != NULL) { value = attr->AsCiscoString(); if (!value) { PStringArray tokens(value.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("RADIUS", addr, port); route.m_destEndpoint = RegistrationTable::Instance()->FindBySignalAdr( SocketToH225TransportAddr(addr, port) ); authData.m_destinationRoutes.push_back(route); PTRACE(5, "RADAUTH\t" << GetName() << " ARQ check redirect " "to the address " << route.AsString() ); } } } } } if (result) result = OnReceivedPDU(*response, arqPdu, authData); else authData.m_rejectReason = H225_AdmissionRejectReason::e_securityDenial; delete response; return result ? e_ok : e_fail; } int RadAuthBase::Check( SetupMsg &setup, SetupAuthData &authData ) { // build RADIUS Access-Request packet RadiusPDU* const pdu = new RadiusPDU(RadiusPDU::AccessRequest); H225_Setup_UUIE& setupBody = setup.GetUUIEBody(); const bool hasCall = authData.m_call.operator->() != NULL; PIPSocket::Address addr; endptr callingEP, calledEP; if (hasCall) callingEP = authData.m_call->GetCallingParty(); if (!callingEP && setupBody.HasOptionalField(H225_Setup_UUIE::e_endpointIdentifier)) callingEP = RegistrationTable::Instance()->FindByEndpointId( setupBody.m_endpointIdentifier ); // Append User-Name and a password related attributes // (User-Password or Chap-Password and Chap-Timestamp) PString username; const int status = AppendUsernameAndPassword(*pdu, setup, callingEP, authData, &username ); if (status != e_ok) { delete pdu; return status; } // Gk acts as NAS, so include NAS IP pdu->AppendAttr(RadiusAttr::NasIpAddress, m_nasIpAddress); // NAS-Identifier as Gk name pdu->AppendAttr(m_attrNasIdentifier); // NAS-Port-Type as Virtual, since Gk does // not care about physical ports concept pdu->AppendAttr(RadiusAttr::NasPortType, RadiusAttr::NasPort_Virtual); // Service-Type is Login-User if originating the call // and Call Check if answering the call pdu->AppendAttr(RadiusAttr::ServiceType, RadiusAttr::ST_Login); // append Frame-IP-Address bool ipFound = false; WORD dummyPort; if (hasCall && authData.m_call->GetSrcSignalAddr(addr, dummyPort) && addr.IsValid()) ipFound = true; else if (callingEP && GetIPFromTransportAddr(callingEP->GetCallSignalAddress(), addr) && addr.IsValid()) ipFound = true; else if (setupBody.HasOptionalField(H225_Setup_UUIE::e_sourceCallSignalAddress) && GetIPFromTransportAddr(setupBody.m_sourceCallSignalAddress, addr) && addr.IsValid()) ipFound = true; else { setup.GetPeerAddr(addr); ipFound = addr.IsValid(); } if (!ipFound) { PTRACE(2, "RADAUTH\t" << GetName() << " Setup auth failed: " "could not setup Framed-IP-Address" ); delete pdu; authData.m_rejectCause = Q931::CallRejected; return e_fail; } else pdu->AppendAttr(RadiusAttr::FramedIpAddress, addr); // fill Calling-Station-Id and Called-Station-Id fields PString stationId = GetCallingStationId(setup, authData); if (!stationId) { pdu->AppendAttr(RadiusAttr::CallingStationId, stationId); } const PString calledStationId = GetCalledStationId(setup, authData); const PString dialedNumber = GetDialedNumber(setup, authData); stationId = m_useDialedNumber ? dialedNumber : calledStationId; if (stationId.IsEmpty()) { delete pdu; PTRACE(2, "RADAUTH\t" << GetName() << " Setup check failed: " "no called station id found" ); authData.m_rejectReason = H225_ReleaseCompleteReason::e_badFormatAddress; return e_fail; } else pdu->AppendAttr(RadiusAttr::CalledStationId, stationId); if (m_appendCiscoAttributes) { pdu->AppendCiscoAttr(RadiusAttr::CiscoVSA_h323_conf_id, GetGUIDString(setupBody.m_conferenceID) ); pdu->AppendAttr(m_attrH323CallOriginOriginate); pdu->AppendAttr(m_attrH323CallType); pdu->AppendAttr(m_attrH323GwId); } if (!OnSendPDU(*pdu, setup, authData)) { delete pdu; return e_fail; } // send the request and wait for a response RadiusPDU* response = NULL; bool result = m_radiusClient->MakeRequest(*pdu, response) && response; delete pdu; if (!result) { PTRACE(2, "RADAUTH\t" << GetName() << " Setup auth failed: " " could not receive or decode response from RADIUS" ); delete response; authData.m_rejectCause = Q931::TemporaryFailure; return GetDefaultStatus(); } // authenticated? result = (response->GetCode() == RadiusPDU::AccessAccept); PString value; const RadiusAttr* attr; // test for h323-return-code attribute (has to be 0 if accept) if (result) { attr = response->FindVsaAttr(RadiusAttr::CiscoVendorId, RadiusAttr::CiscoVSA_h323_return_code ); if (attr != NULL) { value = attr->AsCiscoString(); if (value.GetLength() > 0 && strspn((const char*)value, "0123456789") == (size_t)value.GetLength()) { const unsigned retcode = value.AsUnsigned(); if (retcode != 0) { PTRACE(5, "RADAUTH\t" << GetName() << " Setup check failed: " "return code " << retcode ); result = false; } } else { PTRACE(2, "RADAUTH\t" << GetName() << " Setup check failed: " "invalid h323-return-code attribute '" << value << '\'' ); result = false; } } } // check for h323-credit-time attribute (call duration limit) if (result) { attr = response->FindVsaAttr(RadiusAttr::CiscoVendorId, RadiusAttr::CiscoVSA_h323_credit_time ); if (attr != NULL) { value = attr->AsCiscoString(); if (value.GetLength() > 0 && strspn((const char*)value,"0123456789") == (size_t)value.GetLength() ) { authData.m_callDurationLimit = value.AsInteger(); PTRACE(5, "RADAUTH\t" << GetName() << " Setup check set duration " "limit: " << authData.m_callDurationLimit ); if (authData.m_callDurationLimit == 0) result = false; } else { PTRACE(2, "RADAUTH\t" << GetName() << " Setup check failed: " "invalid h323-credit-time attribute '" << value << '\'' ); result = false; } } } // check for Session-Timeout attribute (alternate call duration limit) if (result) { const RadiusAttr* const attr = response->FindAttr(RadiusAttr::SessionTimeout); if (attr != NULL) { const long sessionTimeout = attr->AsInteger(); if (authData.m_callDurationLimit < 0 || authData.m_callDurationLimit > sessionTimeout) { authData.m_callDurationLimit = sessionTimeout; PTRACE(5, "RADAUTH\t" << GetName() << " Setup check " "set duration limit: " << authData.m_callDurationLimit ); } if (authData.m_callDurationLimit == 0) result = false; } } // check for h323-redirect-number if (result) { attr = response->FindVsaAttr(RadiusAttr::CiscoVendorId, RadiusAttr::CiscoVSA_h323_redirect_number ); if (attr != NULL) { value = attr->AsCiscoString(); if (!value) { authData.SetRouteToAlias(value); PTRACE(5, "RADAUTH\t" << GetName() << " ARQ check redirect " "to the number " << value ); } } } // check for h323-redirect-ip-address if (result) { attr = response->FindVsaAttr(RadiusAttr::CiscoVendorId, RadiusAttr::CiscoVSA_h323_redirect_ip_address ); if (attr != NULL) { value = attr->AsCiscoString(); if (!value) { PStringArray tokens(value.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("RADIUS", addr, port); route.m_destEndpoint = RegistrationTable::Instance()->FindBySignalAdr( SocketToH225TransportAddr(addr, port) ); authData.m_destinationRoutes.push_back(route); PTRACE(5, "RADAUTH\t" << GetName() << " Setup check redirect " "to the address " << route.AsString() ); } } } } } if (result) result = OnReceivedPDU(*response, setup, authData); else authData.m_rejectCause = Q931::CallRejected; delete response; return result ? e_ok : e_fail; } bool RadAuthBase::OnSendPDU( RadiusPDU& /*pdu*/, RasPDU& /*rrqPdu*/, RRQAuthData& /*authData*/ ) { return true; } bool RadAuthBase::OnSendPDU( RadiusPDU& /*pdu*/, RasPDU& /*arqPdu*/, ARQAuthData& /*authData*/ ) { return true; } bool RadAuthBase::OnSendPDU( RadiusPDU& /*pdu*/, SetupMsg &/*setup*/, SetupAuthData& /*authData*/ ) { return true; } bool RadAuthBase::OnReceivedPDU( RadiusPDU& /*pdu*/, RasPDU& /*rrqPdu*/, RRQAuthData& /*authData*/ ) { return true; } bool RadAuthBase::OnReceivedPDU( RadiusPDU& /*pdu*/, RasPDU& /*arqPdu*/, ARQAuthData& /*authData*/ ) { return true; } bool RadAuthBase::OnReceivedPDU( RadiusPDU& /*pdu*/, SetupMsg &/*setup*/, SetupAuthData& /*authData*/ ) { return true; } int RadAuthBase::AppendUsernameAndPassword( RadiusPDU& /*pdu*/, RasPDU& /*rrqPdu*/, RRQAuthData& /*authData*/, PString* /*username*/ ) const { return GetDefaultStatus(); } int RadAuthBase::AppendUsernameAndPassword( RadiusPDU& /*pdu*/, RasPDU& /*arqPdu*/, ARQAuthData& /*authData*/, PString* /*username*/ ) const { return GetDefaultStatus(); } int RadAuthBase::AppendUsernameAndPassword( RadiusPDU &/*pdu*/, SetupMsg &/*setup*/, endptr &/*callingEP*/, SetupAuthData &/*authData*/, PString * /*username*/ ) const { return GetDefaultStatus(); } RadAuth::RadAuth( const char* authName ) : RadAuthBase(authName, RadAuthConfigSectionName) { // setup H.235 algorithm and method types used // by this authenticator - this will make sure // GCF H.235 alogirthm selection will not skip // information required by this authenticator H235AuthCAT* authenticator = new H235AuthCAT; authenticator->SetLocalId("dummy"); authenticator->SetRemoteId("dummy"); authenticator->SetPassword("dummy"); AppendH235Authenticator(authenticator); } RadAuth::~RadAuth() { } int RadAuth::CheckTokens( RadiusPDU& pdu, const H225_ArrayOf_ClearToken& tokens, const H225_ArrayOf_AliasAddress* aliases, PString* username ) const { // scan ClearTokens and find CATs for (PINDEX i = 0; i < tokens.GetSize(); i++) { const H235_ClearToken& token = tokens[i]; // is this 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(3, "RADAUTH\t" << GetName() << " auth failed: " "CAT without all required fields" ); return e_fail; } // generalID should be present in the list of terminal aliases const PString id = token.m_generalID; if (aliases && FindAlias(*aliases, id) == P_MAX_INDEX) { PTRACE(3, "RADAUTH\t" << GetName() << " auth failed: " "CAT m_generalID is not a valid alias" ); return e_fail; } // CAT pseudo-random has to be one byte only const int randomInt = token.m_random; if (randomInt < -127 || randomInt > 255) { PTRACE(3, "RADAUTH\t" << GetName() << " auth failed: " "CAT m_random out of range" ); return e_fail; } // CAT challenge has to be 16 bytes if (token.m_challenge.GetValue().GetSize() < 16) { PTRACE(3, "RADAUTH\t" << GetName() << " auth failed: " "m_challenge less than 16 bytes" ); return e_fail; } // append User-Name pdu.AppendAttr(RadiusAttr::UserName, id); if (username != NULL) *username = (const char*)id; // build CHAP-Password char password[17] = { (BYTE)randomInt }; memcpy(password + 1, (const BYTE*)(token.m_challenge), 16); pdu.AppendAttr(RadiusAttr::ChapPassword, password, sizeof(password)); pdu.AppendAttr(RadiusAttr::ChapChallenge, (int)(DWORD)token.m_timeStamp); return e_ok; } PTRACE(3, "RADAUTH\t" << GetName() << " auth failed: no CAT token found"); return GetDefaultStatus(); } int RadAuth::AppendUsernameAndPassword( RadiusPDU& pdu, RasPDU& rrqPdu, RRQAuthData& authData, PString* username ) const { H225_RegistrationRequest& rrq = (H225_RegistrationRequest&)rrqPdu; // RRQ has to carry at least one terminalAlias if (!rrq.HasOptionalField(H225_RegistrationRequest::e_terminalAlias)) { PTRACE(3, "RADAUTH\t" << GetName() << " RRQ auth failed: " "no m_terminalAlias field" ); authData.m_rejectReason = H225_RegistrationRejectReason::e_securityDenial; return GetDefaultStatus(); } // check for ClearTokens (CAT uses ClearTokens) if (!rrq.HasOptionalField(H225_RegistrationRequest::e_tokens)) { PTRACE(3, "RADAUTH\t" << GetName() << " RRQ auth failed: " "tokens not found" ); authData.m_rejectReason = H225_RegistrationRejectReason::e_securityDenial; return GetDefaultStatus(); } const int result = CheckTokens(pdu, rrq.m_tokens, &rrq.m_terminalAlias, username); if (result != e_ok) authData.m_rejectReason = H225_RegistrationRejectReason::e_securityDenial; return result; } int RadAuth::AppendUsernameAndPassword( RadiusPDU& pdu, RasPDU& arqPdu, ARQAuthData& authData, PString* username ) const { H225_AdmissionRequest& arq = (H225_AdmissionRequest&)arqPdu; // check for ClearTokens if (!arq.HasOptionalField(H225_AdmissionRequest::e_tokens)) { PTRACE(3, "RADAUTH\t" << GetName() << " ARQ auth failed: " "tokens not found" ); authData.m_rejectReason = H225_AdmissionRejectReason::e_securityDenial; return GetDefaultStatus(); } const int result = CheckTokens(pdu, arq.m_tokens, NULL, username); if (result != e_ok) authData.m_rejectReason = H225_AdmissionRejectReason::e_securityDenial; return result; } int RadAuth::AppendUsernameAndPassword( RadiusPDU& pdu, SetupMsg &setup, endptr& /*callingEP*/, SetupAuthData& authData, PString* username ) const { H225_Setup_UUIE &setupBody = setup.GetUUIEBody(); // check for ClearTokens (CAT uses ClearTokens) if (!setupBody.HasOptionalField(H225_Setup_UUIE::e_tokens)) { PTRACE(3, "RADAUTH\t" << GetName() << " Setup auth failed: no tokens"); authData.m_rejectReason = H225_ReleaseCompleteReason::e_securityDenied; return GetDefaultStatus(); } const int result = CheckTokens(pdu, setupBody.m_tokens, NULL, username); if (result != e_ok) authData.m_rejectCause = Q931::CallRejected; return result; } RadAliasAuth::RadAliasAuth( const char* authName ) : RadAuthBase(authName, RadAliasAuthConfigSectionName) { m_fixedUsername = GetConfig()->GetString( RadAliasAuthConfigSectionName, "FixedUsername", "" ); m_fixedPassword = Toolkit::Instance()->ReadPassword( RadAliasAuthConfigSectionName, "FixedPassword" ); } RadAliasAuth::~RadAliasAuth() { } int RadAliasAuth::AppendUsernameAndPassword( RadiusPDU& pdu, RasPDU& rrqPdu, RRQAuthData& authData, PString* username ) const { const PString id = GetUsername(rrqPdu); if (id.IsEmpty() && m_fixedUsername.IsEmpty()) { PTRACE(3, "RADAUTH\t" << GetName() << " RRQ check failed: " "neither FixedUsername nor alias inside RRQ were found" ); authData.m_rejectReason = H225_RegistrationRejectReason::e_securityDenial; return GetDefaultStatus(); } // append User-Name pdu.AppendAttr(RadiusAttr::UserName, m_fixedUsername.IsEmpty() ? id : m_fixedUsername ); if (username != NULL) *username = (const char*)id; // append User-Password if (!m_fixedPassword) pdu.AppendAttr(RadiusAttr::UserPassword, m_fixedPassword); else pdu.AppendAttr(RadiusAttr::UserPassword, m_fixedUsername.IsEmpty() ? id : m_fixedUsername ); return e_ok; } int RadAliasAuth::AppendUsernameAndPassword( RadiusPDU& pdu, RasPDU& arqPdu, ARQAuthData& authData, PString* username ) const { const PString id = GetUsername(arqPdu, authData); if (id.IsEmpty() && m_fixedUsername.IsEmpty()) { PTRACE(3, "RADAUTH\t" << GetName() << " ARQ check failed: " "neither FixedUsername nor alias inside ARQ were found" ); authData.m_rejectReason = H225_AdmissionRejectReason::e_securityDenial; return GetDefaultStatus(); } // append User-Name pdu.AppendAttr(RadiusAttr::UserName, m_fixedUsername.IsEmpty() ? id : m_fixedUsername ); if (username != NULL) *username = (const char*)id; if (!m_fixedPassword) pdu.AppendAttr(RadiusAttr::UserPassword, m_fixedPassword); else pdu.AppendAttr(RadiusAttr::UserPassword, m_fixedUsername.IsEmpty() ? id : m_fixedUsername ); return e_ok; } int RadAliasAuth::AppendUsernameAndPassword( RadiusPDU& pdu, SetupMsg &setup, endptr& /*callingEP*/, SetupAuthData& authData, PString* username ) const { const PString id = GetUsername(setup, authData); if (id.IsEmpty() && m_fixedUsername.IsEmpty()) { PTRACE(3, "RADAUTH\t" << GetName() << " Setup check failed: " "neither FixedUsername nor alias inside Setup were found" ); authData.m_rejectReason = H225_ReleaseCompleteReason::e_badFormatAddress; return GetDefaultStatus(); } // append User-Name pdu.AppendAttr(RadiusAttr::UserName, m_fixedUsername.IsEmpty() ? id : m_fixedUsername ); if (username != NULL) *username = (const char*)id; if (!m_fixedPassword) pdu.AppendAttr(RadiusAttr::UserPassword, m_fixedPassword); else pdu.AppendAttr(RadiusAttr::UserPassword, m_fixedUsername.IsEmpty() ? id : m_fixedUsername ); return e_ok; } namespace { GkAuthCreator RadAuthCreator("RadAuth"); GkAuthCreator RadAliasAuthCreator("RadAliasAuth"); } #endif /* HAS_RADIUS */