/* * radproto.cxx * * RADIUS protocol classes. * * 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: radproto.cxx,v $ * Revision 1.29 2006/04/14 13:56:19 willamowius * call failover code merged * * Revision 1.2 2005/12/16 14:16:32 zvision * Removed incorrect C/C++ language constructs * * Revision 1.1.1.1 2005/11/21 20:19:57 willamowius * * * Revision 1.4 2005/11/15 19:52:56 jan * Michal v1 (works, but on in routed, not proxy mode) * * Revision 1.27 2005/05/18 20:56:01 zvision * Better checking and reporting of network errors * * Revision 1.26 2005/05/18 14:23:33 zvision * Better error reporting on socket allocation * * Revision 1.25 2005/04/24 16:39:44 zvision * MSVC6.0 compatibility fixed * * Revision 1.24 2005/04/19 07:48:44 zvision * Previous fix could cause infinite loops, thanks to kubuqi cn * * Revision 1.23 2005/04/18 11:24:51 zvision * Use list instead of vector in GetSocket to prevent from using invalidated * iterators. Thanks to kubuqi cn * * Revision 1.22 2005/03/15 15:24:28 zvision * Removed compiler warning * * Revision 1.21 2005/01/28 11:19:42 zvision * All passwords in the config can be stored in an encrypted form * * Revision 1.20 2004/12/16 17:11:26 zvision * FindVsaAttr ignored vendor identifier during attribute match * * Revision 1.19 2004/08/09 23:32:11 zvision * VC6 compilation errors fixed * * Revision 1.18 2004/08/09 10:08:28 zvision * Fixed missing curly brackets, thanks to Thomas! * * Revision 1.17 2004/07/26 12:19:42 zvision * New faster Radius implementation, thanks to Pavel Pavlov for ideas! * * Revision 1.16.2.3 2004/07/12 22:27:32 zvision * Ability to set a shared secret for each RADIUS server separatelly. * More RADIUS code optimizations. * * Revision 1.16.2.2 2004/07/07 23:11:07 zvision * Faster and more elegant handling of Cisco VSA * * Revision 1.16.2.1 2004/07/07 20:50:14 zvision * New, faster, Radius client implementation. Thanks to Pavel Pavlov for ideas! * * Revision 1.16 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.15 2004/04/21 14:15:26 zvision * Default ports are now set to fixes values (1812,1813), because of problems * with getservbyname and multiple threads on some systems * * Revision 1.14 2004/04/20 15:50:07 zvision * Descendancy check removed to enable compilation with RTTI enabled PWLib * * 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/03/11 13:42:48 zvision * 64-bit fixes from Klaus Kaempf * * Revision 1.10 2003/11/11 11:14:21 zvision * Fixed invalid signed/unsigned integer conversions for radius attributes. * Optimized radius attributes handling. * * Revision 1.9 2003/10/31 00:02:27 zvision * A better tracing/error reporting * * Revision 1.8 2003/10/21 10:47:16 zvision * Fixed minor compilation warnings about precision loss * * Revision 1.7 2003/10/08 12:40:48 zvision * Realtime accounting updates added * * Revision 1.6 2003/09/28 15:47:23 zvision * Better RADIUS client socket handling * * Revision 1.5 2003/09/24 00:22:03 zvision * Removed time_t RadAttr constructors * * Revision 1.4 2003/09/12 16:31:16 zvision * Accounting initially added to the 2.2 branch * * Revision 1.3 2003/08/20 14:46:19 zvision * Avoid PString reference copying. Small code improvements. * * Revision 1.2 2003/08/19 10:44:18 zvision * Initially added to 2.2 branch * * Revision 1.1.2.8 2003/07/20 23:18:51 zvision * Fixed trace output. * * Revision 1.1.2.7 2003/07/03 15:33:35 zvision * Fixed big-endian issues. Fixed small timeout bugs. * Fixed strange bug with segfaults on some machines. * * Revision 1.1.2.6 2003/06/11 12:16:01 zvision * Memory usage optimizations. * Fixed problems with PSocket::Net2Host call crashing on Solaris. * * Revision 1.1.2.5 2003/06/05 10:02:20 zvision * Bugfixes and small code cleanup. * * Revision 1.1.2.4 2003/05/26 20:31:09 zvision * Better local interface handling in RadiusClient constructor * * Revision 1.1.2.3 2003/05/13 17:44:40 zvision * Added attribute searching functions * * Revision 1.1.2.2 2003/04/24 11:26:41 willamowius * compile fixes for VC 6 * * Revision 1.1.2.1 2003/04/23 20:14:16 zvision * Initial revision * */ #if HAS_RADIUS #if defined(_WIN32) && (_MSC_VER <= 1200) #pragma warning(disable:4284) #endif #include #include #include #include #include "Toolkit.h" #include "radproto.h" namespace { #if PTRACING /// Human-readable attribute names const char* const radiusAttributeNames[] = { /* 0*/ "Invalid", "User-Name", "User-Password", "CHAP-Password", /* 4*/ "NAS-IP-Address", "NAS-Port", "Service-Type", "Framed-Protocol", /* 8*/ "Framed-IP-Address", "Framed-IP-Netmask", "Framed-Routing", "Filter-Id", /* 12*/ "Framed-MTU", "Framed-Compression", "Login-IP-Host", "Login-Service", /* 16*/ "Login-TCP-Port", "Unknown", "Reply-Message", "Callback-Number", /* 20*/ "Callback-Id", "Unknown", "Framed-Route", "Framed-IPX-Network", /* 24*/ "State", "Class", "Vendor-Specific", "Session-Timeout", /* 28*/ "Idle-Timeout", "Termination-Action", "Called-Station-Id", "Calling-Station-Id", /* 32*/ "NAS-Identifier", "Proxy-State", "Login-LAT-Service", "Login-LAT-Node", /* 36*/ "Login-LAT-Group", "Framed-AppleTalk-Link", "Framed-AppleTalk-Network", "Framed-AppleTalk-Zone", /* 40*/ "Acct-Status-Type", "Acct-Delay-Time", "Acct-Input-Octets", "Acct-Output-Octets", /* 44*/ "Acct-Session-Id", "Acct-Authentic", "Acct-Session-Time", "Acct-Input-Packets", /* 48*/ "Acct-Output-Packets", "Acct-Terminate-Cause", "Acct-Multi-Session-Id", "Acct-Link-Count", /* 52*/ "Acct-Input-Gigawords", "Acct-Output-Gigawords", "Unknown", "Unknown", /* 56*/ "Unknown", "Unknown", "Unknown", "Unknown", /* 60*/ "CHAP-Challenge", "NAS-Port-Type", "Port-Limit", "Login-LAT-Port", /* 64*/ "Unknown", "Unknown", "Unknown", "Unknown", /* 68*/ "Unknown", "Unknown", "ARAP-Password", "ARAP-Features", /* 72*/ "ARAP-Zone-Access", "ARAP-Security", "ARAP-Security-Data", "Password-Retry", /* 76*/ "Prompt", "Connect-Info", "Configuration-Token", "EAP-Message", /* 80*/ "Message-Authenticator", "Unknown", "Unknown", "Unknown", /* 84*/ "ARAP-Challenge-Response", "Acct-Interim-Interval", "Unknown", "NAS-Port-Id", /* 88*/ "Framed-Pool", /* 89*/ "Unknown" }; /// Human readable RADIUS packet code names const char* const radiusPacketCodeNames[] = { /* 0*/ "Invalid", "Access-Request", "Access-Accept", "Access-Reject", /* 4*/ "Accounting-Request", "Accounting-Response", "Unknown", "Unknown", /* 8*/ "Unknown", "Unknown", "Access-Challenge", "Status-Server", /*12*/ "Status-Client", "Unknown" }; /** Macro that returns name associated with the given attribute type. Returns "Unknown" if the name is not defined. */ inline const char* const PMAP_ATTR_TYPE_TO_NAME(unsigned type) { return type >= sizeof(radiusAttributeNames)/sizeof(radiusAttributeNames[0]) ? radiusAttributeNames[sizeof(radiusAttributeNames)/sizeof(radiusAttributeNames[0])-1] : radiusAttributeNames[type]; } /** Macro that returns name associated with the given RADIUS packet code. Returns "Unknown" if the name is not defined. */ inline const char* const PMAP_CODE_TO_NAME(unsigned code) { return code >= sizeof(radiusPacketCodeNames)/sizeof(radiusPacketCodeNames[0]) ? radiusPacketCodeNames[sizeof(radiusPacketCodeNames)/sizeof(radiusPacketCodeNames[0])-1] : radiusPacketCodeNames[code]; } #endif /* #if PTRACING */ // Cisco VSA attributes together with names and name lengths for fast lookup #define CISCO_ATTR_NAME(namestr) namestr, strlen(namestr) struct CiscoAttrName { CiscoAttrName(const char* n, size_t l, unsigned char t) : m_name(n), m_nameLen(l), m_type(t) {} // workaround for VC6 const char* const m_name; size_t m_nameLen; unsigned char m_type; } CiscoAttrNames[] = { CiscoAttrName(CISCO_ATTR_NAME("h323-remote-address"), RadiusAttr::CiscoVSA_h323_remote_address), CiscoAttrName(CISCO_ATTR_NAME("h323-conf-id"), RadiusAttr::CiscoVSA_h323_conf_id), CiscoAttrName(CISCO_ATTR_NAME("h323-setup-time"), RadiusAttr::CiscoVSA_h323_setup_time), CiscoAttrName(CISCO_ATTR_NAME("h323-connect-time"), RadiusAttr::CiscoVSA_h323_connect_time), CiscoAttrName(CISCO_ATTR_NAME("h323-disconnect-time"), RadiusAttr::CiscoVSA_h323_disconnect_time), CiscoAttrName(CISCO_ATTR_NAME("h323-disconnect-cause"), RadiusAttr::CiscoVSA_h323_disconnect_cause), CiscoAttrName(CISCO_ATTR_NAME("h323-credit-amount"), RadiusAttr::CiscoVSA_h323_credit_amount), CiscoAttrName(CISCO_ATTR_NAME("h323-credit-time"), RadiusAttr::CiscoVSA_h323_credit_time), CiscoAttrName(CISCO_ATTR_NAME("h323-return-code"), RadiusAttr::CiscoVSA_h323_return_code), CiscoAttrName(CISCO_ATTR_NAME("h323-billing-model"), RadiusAttr::CiscoVSA_h323_billing_model), CiscoAttrName(CISCO_ATTR_NAME("h323-currency"), RadiusAttr::CiscoVSA_h323_currency), CiscoAttrName(CISCO_ATTR_NAME("h323-redirect-number"), RadiusAttr::CiscoVSA_h323_redirect_number), CiscoAttrName(CISCO_ATTR_NAME("h323-redirect-ip-address"), RadiusAttr::CiscoVSA_h323_redirect_ip_address), CiscoAttrName(CISCO_ATTR_NAME("h323-gw-id"), RadiusAttr::CiscoVSA_h323_gw_id), CiscoAttrName(CISCO_ATTR_NAME("h323-call-origin"), RadiusAttr::CiscoVSA_h323_call_origin), CiscoAttrName(CISCO_ATTR_NAME("h323-call-type"), RadiusAttr::CiscoVSA_h323_call_type), CiscoAttrName(CISCO_ATTR_NAME("h323-voice-quality"), RadiusAttr::CiscoVSA_h323_voice_quality), CiscoAttrName(CISCO_ATTR_NAME("h323-incoming-conf-id"), RadiusAttr::CiscoVSA_h323_incoming_conf_id), CiscoAttrName(CISCO_ATTR_NAME("h323-preferred-lang"), RadiusAttr::CiscoVSA_h323_preferred_lang), CiscoAttrName(NULL, 0, 0) }; /// macro to put an integer into the buffer using big endian byte order inline void SetRadiusInteger( unsigned char* intBuffer, unsigned intValue ) { intBuffer[0] = (BYTE)((intValue >> 24) & 0xff); intBuffer[1] = (BYTE)((intValue >> 16) & 0xff); intBuffer[2] = (BYTE)((intValue >> 8) & 0xff); intBuffer[3] = (BYTE)(intValue & 0xff); } /// macro to get an integer from the buffer stored in big endian byte order inline unsigned GetRadiusInteger( const unsigned char* intBuffer ) { return (((unsigned)intBuffer[0]) << 24) | (((unsigned)intBuffer[1]) << 16) | (((unsigned)intBuffer[2]) << 8) | ((unsigned)intBuffer[3]); } } // anonymous namespace RadiusAttr::RadiusAttr() : m_type(0), m_length(0) { } RadiusAttr::RadiusAttr( unsigned char attrType, /// type of the attribute const void* attrValue, /// actual attribute value data PINDEX valueLength /// length for the attribute value ) : m_type(attrType), m_length(FixedHeaderLength) { if (valueLength > 0) PAssertNULL(attrValue); #ifdef _DEBUG PAssert(valueLength <= MaxValueLength, PInvalidParameter); #endif if (valueLength > MaxValueLength) valueLength = MaxValueLength; if (valueLength > 0) { m_length = m_length + (unsigned char)valueLength; if (attrValue != NULL) memcpy(m_value, attrValue, valueLength); } } RadiusAttr::RadiusAttr( const void* attrValue, /// buffer with data to be stored in the attribute Value PINDEX valueLength, /// data length (bytes) int vendorId, /// 32 bit vendor identifier unsigned char vendorType /// vendor-specific attribute type ) : m_type(VendorSpecific), m_length(VsaRfc2865FixedHeaderLength), m_vendorType(vendorType), m_vendorLength(2) { SetRadiusInteger(m_vendorId, vendorId); if (valueLength > 0) PAssertNULL(attrValue); #ifdef _DEBUG PAssert(valueLength <= VsaMaxRfc2865ValueLength, PInvalidParameter); #endif if (valueLength > VsaMaxRfc2865ValueLength) valueLength = VsaMaxRfc2865ValueLength; if (valueLength > 0) { m_length = m_length + (unsigned char)valueLength; m_vendorLength = m_vendorLength + (unsigned char)valueLength; if (attrValue != NULL) memcpy(m_vendorValue, attrValue, valueLength); } } RadiusAttr::RadiusAttr( unsigned char attrType, /// Attribute Type (see #enum AttrTypes#) const PString& stringValue /// string to be stored in the attribute Value data ) : m_type(attrType), m_length(FixedHeaderLength) { if (attrType == VendorSpecific) PAssertAlways(PInvalidParameter); PINDEX attrLength = stringValue.GetLength(); if (attrLength > MaxValueLength) attrLength = MaxValueLength; if (attrLength > 0) { m_length = m_length + (unsigned char)attrLength; memcpy(m_value, (const char*)stringValue, attrLength); } } RadiusAttr::RadiusAttr( unsigned char attrType, /// Attribute Type (see #enum AttrTypes#) int intValue /// 32 bit integer to be stored in the attribute Value ) : m_type(attrType), m_length(FixedHeaderLength + 4) { if (attrType == VendorSpecific) PAssertAlways(PInvalidParameter); SetRadiusInteger(m_value, intValue); } RadiusAttr::RadiusAttr( unsigned char attrType, /// Attribute Type (see #enum AttrTypes#) const PIPSocket::Address& addressValue /// IPv4 address to be stored in the attribute Value ) : m_type(attrType), m_length(FixedHeaderLength + 4) { if (attrType == VendorSpecific) PAssertAlways(PInvalidParameter); const DWORD addr = (DWORD)addressValue; m_value[0] = ((const BYTE*)&addr)[0]; m_value[1] = ((const BYTE*)&addr)[1]; m_value[2] = ((const BYTE*)&addr)[2]; m_value[3] = ((const BYTE*)&addr)[3]; } RadiusAttr::RadiusAttr( const PString& stringValue, /// string to be stored in the attribute Value int vendorId, /// 32 bit vendor identifier unsigned char vendorType /// vendor-specific attribute type ) : m_type(VendorSpecific), m_length(VsaRfc2865FixedHeaderLength), m_vendorType(vendorType), m_vendorLength(2) { SetRadiusInteger(m_vendorId, vendorId); PINDEX vsaLength = stringValue.GetLength(); #ifdef _DEBUG PAssert(vsaLength <= VsaMaxRfc2865ValueLength, PInvalidParameter); #endif if (vsaLength > VsaMaxRfc2865ValueLength) vsaLength = VsaMaxRfc2865ValueLength; if (vsaLength > 0) { m_length = m_length + (unsigned char)vsaLength; m_vendorLength = m_vendorLength + (unsigned char)vsaLength; memcpy(m_vendorValue, (const char*)stringValue, vsaLength); } } RadiusAttr::RadiusAttr( int intValue, /// 32 bit integer to be stored in the attribute Value int vendorId, /// 32 bit vendor identifier unsigned char vendorType /// vendor-specific attribute type ) : m_type(VendorSpecific), m_length(VsaRfc2865FixedHeaderLength + 4), m_vendorType(vendorType), m_vendorLength(2 + 4) { SetRadiusInteger(m_vendorId, vendorId); SetRadiusInteger(m_vendorValue, intValue); } RadiusAttr::RadiusAttr( const PIPSocket::Address& addressValue, /// IPv4 address to be stored in the attribute Value int vendorId, /// 32 bit vendor identifier unsigned char vendorType /// vendor-specific attribute type ) : m_type(VendorSpecific), m_length(VsaRfc2865FixedHeaderLength + 4), m_vendorType(vendorType), m_vendorLength(2 + 4) { SetRadiusInteger(m_vendorId, vendorId); const DWORD addr = (DWORD)addressValue; m_vendorValue[0] = ((BYTE*)&addr)[0]; m_vendorValue[1] = ((BYTE*)&addr)[1]; m_vendorValue[2] = ((BYTE*)&addr)[2]; m_vendorValue[3] = ((BYTE*)&addr)[3]; } RadiusAttr::RadiusAttr( unsigned char type, /// Cisco-specific attribute type bool vsaHack, /// true to not prepend attribute name to its value const PString& stringValue /// string to be stored in the attribute Value ) : m_type(VendorSpecific), m_length(VsaRfc2865FixedHeaderLength), m_vendorType(type), m_vendorLength(2) { SetRadiusInteger(m_vendorId, CiscoVendorId); if (!vsaHack) { int i = 0; while (CiscoAttrNames[i].m_name != NULL) if (CiscoAttrNames[i].m_type == type) { memcpy(m_vendorValue, CiscoAttrNames[i].m_name, CiscoAttrNames[i].m_nameLen); m_length = m_length + (unsigned char)CiscoAttrNames[i].m_nameLen; m_vendorLength = m_vendorLength + (unsigned char)CiscoAttrNames[i].m_nameLen; m_data[m_length++] = '='; m_vendorLength++; break; } else i++; } const PINDEX len = stringValue.GetLength(); if (((PINDEX)m_length + len) > MaxLength) return; memcpy(m_data + (PINDEX)m_length, (const char*)stringValue, len); m_length = m_length + (unsigned char)len; m_vendorLength = m_vendorLength + (unsigned char)len; } RadiusAttr::RadiusAttr( const void* rawData, /// buffer with the attribute raw data PINDEX rawLength /// length (bytes) of the buffer ) : m_type(0), m_length(0) { Read(rawData, rawLength); } int RadiusAttr::GetVsaVendorId() const { return GetRadiusInteger(m_vendorId); } bool RadiusAttr::Write( PBYTEArray& buffer, /// buffer the attribute data will be written to PINDEX& written, /// number of bytes written (if successful return) PINDEX offset /// offset into the buffer, where writting starts ) const { if (!IsValid()) return false; if (offset == P_MAX_INDEX) offset = buffer.GetSize(); const PINDEX len = m_length; memcpy(buffer.GetPointer(offset + len) + offset, m_data, len); written = len; return true; } bool RadiusAttr::Read(const void* rawData, PINDEX rawLength) { m_type = m_length = 0; #ifdef _DEBUG PAssertNULL(rawData); PAssert(rawLength >= FixedHeaderLength, PInvalidParameter); #endif if (rawData == NULL || rawLength < FixedHeaderLength) return false; const PINDEX len = ((const unsigned char*)rawData)[1]; if (len < FixedHeaderLength || len > rawLength || (((const unsigned char*)rawData)[0] == VendorSpecific && len < VsaFixedHeaderLength)) return false; memcpy(m_data, rawData, len); return true; } void RadiusAttr::PrintOn( ostream &strm /// Stream to print the object into. ) const { const int indent = strm.precision() + 2; if (!IsValid()) { strm << "(Invalid) {\n"; if (m_length > 0) { const _Ios_Fmtflags flags = strm.flags(); const PBYTEArray value((const BYTE*)m_data, m_length, FALSE); strm << hex << setfill('0') << resetiosflags(ios::floatfield) << setprecision(indent) << setw(16); if (value.GetSize() <= 32 || (flags&ios::floatfield) != ios::fixed) strm << value << '\n'; else { const PBYTEArray truncatedArray((const BYTE*)value, 32, FALSE); strm << truncatedArray << '\n' << setfill(' ') << setw(indent+4) << "...\n"; } strm << dec << setfill(' '); strm.flags(flags); } strm << setw(indent) << "}\n" << setprecision(indent-2); return; } strm << "{\n"; #if PTRACING strm << setw(indent+7) << "type = " << (unsigned)m_type << " (" << PMAP_ATTR_TYPE_TO_NAME(m_type) << ")\n"; #else strm << setw(indent+7) << "type = " << (unsigned)m_type << '\n'; #endif const PINDEX totalLen = m_length; strm << setw(indent+9) << "length = " << totalLen << " octets\n"; if (!IsVsa()) { const _Ios_Fmtflags flags = strm.flags(); const PINDEX valueLen = (totalLen <= FixedHeaderLength) ? 0 : (totalLen - FixedHeaderLength); const PBYTEArray value((const BYTE*)m_value, valueLen, FALSE); strm << setw(indent+8) << "value = " << value.GetSize() << " octets {\n"; strm << hex << setfill('0') << resetiosflags(ios::floatfield) << setprecision(indent+2) << setw(16); if (value.GetSize() > 0) { if (value.GetSize() <= 32 || (flags&ios::floatfield) != ios::fixed) strm << value << '\n'; else { const PBYTEArray truncatedArray((const BYTE*)value, 32, FALSE); strm << truncatedArray << '\n' << setfill(' ') << setw(indent+6) << "...\n"; } } strm << dec << setfill(' ') << setprecision(indent); strm.flags(flags); strm << setw(indent+2) << "}\n"; } else { strm << setw(indent+11) << "vendorId = " << GetVsaVendorId() << '\n'; const _Ios_Fmtflags flags = strm.flags(); PINDEX valueLen = (totalLen <= VsaFixedHeaderLength) ? 0 : (totalLen - VsaFixedHeaderLength); PINDEX headerLen = VsaFixedHeaderLength; if (valueLen > 2) { valueLen -= 2; headerLen += 2; strm << setw(indent+13) << "vendorType = " << (unsigned)m_vendorType << '\n'; strm << setw(indent+15) << "vendorLength = " << (unsigned)m_vendorLength << '\n'; } const PBYTEArray value((const BYTE*)(m_data + headerLen), valueLen, FALSE); strm << setw(indent+14) << "vendorValue = " << value.GetSize() << " octets {\n"; strm << hex << setfill('0') << resetiosflags(ios::floatfield) << setprecision(indent+2) << setw(16); if (value.GetSize() > 0) { if (value.GetSize() <= 32 || (flags&ios::floatfield) != ios::fixed) strm << value << '\n'; else { const PBYTEArray truncatedArray((const BYTE*)value, 32, FALSE); strm << truncatedArray << '\n' << setfill(' ') << setw(indent+6) << "...\n"; } } strm << dec << setfill(' ') << setprecision(indent); strm.flags(flags); strm << setw(indent+2) << "}\n"; } strm << setw(indent) << "}\n" << setprecision(indent-2); } PINDEX RadiusAttr::GetVsaValueLength() const { PINDEX len = m_length; len = (len <= VsaRfc2865FixedHeaderLength) ? 0 : (len - VsaRfc2865FixedHeaderLength); PINDEX len2 = 0; if (len > 0) { len2 = m_vendorLength; len2 = (len2 <= 2) ? 0 : (len2 - 2); } if (len2 < len) len = len2; return len; } bool RadiusAttr::GetValue(PBYTEArray& buffer, PINDEX offset) const { if (!IsValid()) return false; const PINDEX len = GetValueLength(); if (offset == P_MAX_INDEX) offset = buffer.GetSize(); if (len > 0) memcpy(buffer.GetPointer(offset + len) + offset, m_data + (IsVsa() ? VsaFixedHeaderLength : FixedHeaderLength), len ); return true; } bool RadiusAttr::GetVsaValue(PBYTEArray& buffer, PINDEX offset) const { if (!(IsValid() && IsVsa())) return false; const PINDEX len = GetVsaValueLength(); if (offset == P_MAX_INDEX) offset = buffer.GetSize(); if (len > 0) memcpy(buffer.GetPointer(len + offset) + offset, m_vendorValue, len); return true; } PString RadiusAttr::AsString() const { if (!IsValid()) return PString(); const PINDEX len = m_length; const PINDEX headerLen = (m_type == VendorSpecific) ? VsaFixedHeaderLength : FixedHeaderLength; if (len <= headerLen) return PString(); else return PString((const char*)(m_data + headerLen), len - headerLen); } int RadiusAttr::AsInteger() const { if (m_length < (FixedHeaderLength+4) || m_type == VendorSpecific) return 0; return GetRadiusInteger(m_value); } PIPSocket::Address RadiusAttr::AsAddress() const { if (m_length < (FixedHeaderLength+4) || m_type == VendorSpecific) return 0; DWORD addr = 0; ((BYTE*)&addr)[0] = m_value[0]; ((BYTE*)&addr)[1] = m_value[1]; ((BYTE*)&addr)[2] = m_value[2]; ((BYTE*)&addr)[3] = m_value[3]; return addr; } PString RadiusAttr::AsVsaString() const { if (!IsValid() || m_type != VendorSpecific) return PString(); const PINDEX len = m_length; if (len <= VsaRfc2865FixedHeaderLength) return PString(); else return PString((const char*)m_vendorValue, len - VsaRfc2865FixedHeaderLength); } PString RadiusAttr::AsCiscoString() const { if (!IsValid() || m_type != VendorSpecific || GetRadiusInteger(m_vendorId) != CiscoVendorId) return PString(); const PINDEX len = m_length; PINDEX offset = VsaRfc2865FixedHeaderLength; int i = 0; while (CiscoAttrNames[i].m_name != NULL) if (CiscoAttrNames[i].m_type == m_vendorType) { if (CiscoAttrNames[i].m_nameLen < (size_t)(len - offset)) if (memcmp(m_data + offset, CiscoAttrNames[i].m_name, CiscoAttrNames[i].m_nameLen) == 0 && m_data[offset + CiscoAttrNames[i].m_nameLen] == '=') offset += CiscoAttrNames[i].m_nameLen + 1; break; } else i++; if (offset >= len) return PString(); else return PString((const char*)m_data + offset, len - offset); } int RadiusAttr::AsVsaInteger() const { if (m_length < (VsaRfc2865FixedHeaderLength+4) || m_type != VendorSpecific) return 0; return GetRadiusInteger(m_vendorValue); } PIPSocket::Address RadiusAttr::AsVsaAddress() const { if (m_length < (VsaRfc2865FixedHeaderLength+4) || m_type != VendorSpecific) return 0; DWORD addr = 0; ((BYTE*)&addr)[0] = m_vendorValue[0]; ((BYTE*)&addr)[1] = m_vendorValue[1]; ((BYTE*)&addr)[2] = m_vendorValue[2]; ((BYTE*)&addr)[3] = m_vendorValue[3]; return addr; } RadiusPDU::RadiusPDU() : m_code(Invalid), m_id(0) { SetLength(FixedHeaderLength); } RadiusPDU::RadiusPDU( const RadiusPDU& pdu ) { CopyContents(pdu); } RadiusPDU::RadiusPDU( unsigned char packetCode, /// code - see #Codes enum# unsigned char packetId /// packet id (sequence number) ) : m_code(packetCode), m_id(packetId) { SetLength(FixedHeaderLength); } RadiusPDU::RadiusPDU( const void* rawData, /// raw data buffer PINDEX rawLength /// raw data length ) { if (!Read(rawData, rawLength)) { m_code = m_id = Invalid; SetLength(FixedHeaderLength); } } void RadiusPDU::PrintOn( ostream& strm /// Stream to print the object into. ) const { const int indent = strm.precision() + 2; strm << ((!IsValid()) ? "(Invalid) {\n" : "{\n"); #if PTRACING strm << setw(indent+7) << "code = " << (unsigned)m_code << " (" << PMAP_CODE_TO_NAME(m_code) << ")\n"; #else strm << setw(indent+7) << "code = " << (unsigned)m_code << '\n'; #endif strm << setw(indent+5) << "id = " << (unsigned)m_id << '\n'; strm << setw(indent+9) << "length = " << GetLength() << " octets\n"; const _Ios_Fmtflags flags = strm.flags(); const PBYTEArray value((const BYTE*)m_authenticator, AuthenticatorLength, FALSE); strm << setw(indent+28) << "authenticator = 16 octets {\n"; strm << hex << setfill('0') << resetiosflags(ios::floatfield) << setprecision(indent+2) << setw(16); strm << value << '\n'; strm << dec << setfill(' ') << setprecision(indent); strm.flags(flags); strm << setw(indent+2) << "}\n"; const PINDEX numAttributes = GetNumAttributes(); if (numAttributes == 0) strm << setw(indent+22) << "attributes = <>\n"; else { strm << setw(indent+13) << "attributes = " << numAttributes << " elements {\n"; const int aindent = indent + 2; const RadiusAttr* attr = GetAttr(); PINDEX i = 0; while (attr != NULL) { strm << setw(aindent + 1) << "[" << i << "]= " << setprecision(aindent) << *attr << setprecision(indent); attr = GetAttr(attr); i++; } strm << setw(aindent) << "}\n"; } strm << setw(indent-1) << "}\n" << setprecision(indent-2); } bool RadiusPDU::IsValid() const { if (m_code == Invalid) return false; const PINDEX len = GetLength(); if (len < MinPduLength || len > MaxPduLength) return false; PINDEX currLen = FixedHeaderLength; while (currLen < len) { const RadiusAttr* const attr = reinterpret_cast(m_data + currLen); const PINDEX remainingLen = len - currLen; if (remainingLen < RadiusAttr::FixedHeaderLength || remainingLen < attr->GetLength() || !attr->IsValid()) break; currLen += attr->GetLength(); } return currLen == len; } void RadiusPDU::GetAuthenticator(PBYTEArray& vector, PINDEX offset) const { if (offset == P_MAX_INDEX) offset = vector.GetSize(); memcpy(vector.GetPointer(offset + AuthenticatorLength) + offset, m_authenticator, AuthenticatorLength ); } bool RadiusPDU::SetAuthenticator(const PBYTEArray& vector, PINDEX offset) { PINDEX len = vector.GetSize(); if (offset >= len) return false; len -= offset; if (len > 0) memcpy(m_authenticator, ((const BYTE*)vector)+offset, (len < AuthenticatorLength) ? len : AuthenticatorLength ); return true; } bool RadiusPDU::SetAuthenticator(const void* data) { #ifdef _DEBUG PAssertNULL(data); #endif if (data == NULL) return false; memcpy(m_authenticator, data, AuthenticatorLength); return true; } void RadiusPDU::SetAuthenticator(PRandom& random) { DWORD r = (DWORD)random; m_authenticator[0] = ((const BYTE*)&r)[0]; m_authenticator[1] = ((const BYTE*)&r)[1]; m_authenticator[2] = ((const BYTE*)&r)[2]; m_authenticator[3] = ((const BYTE*)&r)[3]; r = (DWORD)random; m_authenticator[4] = ((const BYTE*)&r)[0]; m_authenticator[5] = ((const BYTE*)&r)[1]; m_authenticator[6] = ((const BYTE*)&r)[2]; m_authenticator[7] = ((const BYTE*)&r)[3]; r = (DWORD)random; m_authenticator[8] = ((const BYTE*)&r)[0]; m_authenticator[9] = ((const BYTE*)&r)[1]; m_authenticator[10] = ((const BYTE*)&r)[2]; m_authenticator[11] = ((const BYTE*)&r)[3]; r = (DWORD)random; m_authenticator[12] = ((const BYTE*)&r)[0]; m_authenticator[13] = ((const BYTE*)&r)[1]; m_authenticator[14] = ((const BYTE*)&r)[2]; m_authenticator[15] = ((const BYTE*)&r)[3]; } void RadiusPDU::SetAuthenticator( const PString& secret, PMessageDigest5& md5 ) { if (m_code == AccountingRequest) { const PINDEX pduLength = GetLength(); const PINDEX secretLength = secret.GetLength(); memset(m_authenticator, 0, AuthenticatorLength); md5.Start(); md5.Process(m_data, pduLength); if (secretLength > 0) md5.Process((const char*)secret, secretLength); PMessageDigest::Result digest; md5.CompleteDigest(digest); memcpy(m_authenticator, digest.GetPointer(), AuthenticatorLength); } else { PRandom random; SetAuthenticator(random); } } bool RadiusPDU::AppendAttr( const RadiusAttr& attr /// attribute to be appended ) { const PINDEX len = GetLength(); const PINDEX attrLen = attr.GetLength(); if (!attr.IsValid() || (len + attrLen) > MaxPduLength) return false; *reinterpret_cast(m_data + len) = attr; SetLength(len + attrLen); return true; } bool RadiusPDU::AppendAttr( unsigned char attrType, /// Attribute Type const void* attrValue, /// buffer with attribute Value data PINDEX valueLength /// length of attribute Value data ) { const PINDEX len = GetLength(); const PINDEX attrLen = RadiusAttr::FixedHeaderLength + valueLength; if (attrLen > RadiusAttr::MaxLength || (len + attrLen) > MaxPduLength) return false; RadiusAttr* const attr = reinterpret_cast(m_data + len); attr->m_type = attrType; attr->m_length = attrLen; memcpy(attr->m_value, attrValue, valueLength); SetLength(len + attrLen); return true; } bool RadiusPDU::AppendAttr( unsigned char attrType, /// Attribute Type const PString& stringValue /// string to be stored in the attribute Value data ) { const PINDEX len = GetLength(); const PINDEX attrLen = RadiusAttr::FixedHeaderLength + stringValue.GetLength(); if (attrLen > RadiusAttr::MaxLength || (len + attrLen) > MaxPduLength) return false; RadiusAttr* const attr = reinterpret_cast(m_data + len); attr->m_type = attrType; attr->m_length = attrLen; memcpy(attr->m_value, (const char*)stringValue, stringValue.GetLength()); SetLength(len + attrLen); return true; } bool RadiusPDU::AppendAttr( unsigned char attrType, /// Attribute Type int intValue /// 32 bit integer to be stored in the attribute Value ) { const PINDEX len = GetLength(); const PINDEX attrLen = RadiusAttr::FixedHeaderLength + 4; if (attrLen > RadiusAttr::MaxLength || (len + attrLen) > MaxPduLength) return false; RadiusAttr* const attr = reinterpret_cast(m_data + len); attr->m_type = attrType; attr->m_length = attrLen; SetRadiusInteger(attr->m_value, intValue); SetLength(len + attrLen); return true; } bool RadiusPDU::AppendAttr( unsigned char attrType, /// Attribute Type const PIPSocket::Address& addressValue /// IPv4 address to be stored in the attribute Value ) { const PINDEX len = GetLength(); const PINDEX attrLen = RadiusAttr::FixedHeaderLength + 4; if (attrLen > RadiusAttr::MaxLength || (len + attrLen) > MaxPduLength) return false; const DWORD addr = (DWORD)addressValue; RadiusAttr* const attr = reinterpret_cast(m_data + len); attr->m_type = attrType; attr->m_length = attrLen; attr->m_value[0] = ((const BYTE*)&addr)[0]; attr->m_value[1] = ((const BYTE*)&addr)[1]; attr->m_value[2] = ((const BYTE*)&addr)[2]; attr->m_value[3] = ((const BYTE*)&addr)[3]; SetLength(len + attrLen); return true; } bool RadiusPDU::AppendVsaAttr( const void* attrValue, /// buffer with data to be stored in the attribute Value PINDEX valueLength, /// data length (bytes) int vendorId, /// 32 bit vendor identifier unsigned char vendorType /// vendor-specific attribute type ) { const PINDEX len = GetLength(); const PINDEX attrLen = RadiusAttr::VsaRfc2865FixedHeaderLength + valueLength; if (attrLen > RadiusAttr::MaxLength || (len + attrLen) > MaxPduLength) return false; RadiusAttr* const attr = reinterpret_cast(m_data + len); attr->m_type = RadiusAttr::VendorSpecific; attr->m_length = attrLen; SetRadiusInteger(attr->m_vendorId, vendorId); attr->m_vendorType = vendorType; attr->m_vendorLength = valueLength + 2; memcpy(attr->m_vendorValue, attrValue, valueLength); SetLength(len + attrLen); return true; } bool RadiusPDU::AppendVsaAttr( const PString& stringValue, /// string to be stored in the attribute Value int vendorId, /// 32 bit vendor identifier unsigned char vendorType /// vendor-specific attribute type ) { const PINDEX len = GetLength(); const PINDEX valueLen = stringValue.GetLength(); const PINDEX attrLen = RadiusAttr::VsaRfc2865FixedHeaderLength + valueLen; if (attrLen > RadiusAttr::MaxLength || (len + attrLen) > MaxPduLength) return false; RadiusAttr* const attr = reinterpret_cast(m_data + len); attr->m_type = RadiusAttr::VendorSpecific; attr->m_length = attrLen; SetRadiusInteger(attr->m_vendorId, vendorId); attr->m_vendorType = vendorType; attr->m_vendorLength = valueLen + 2; memcpy(attr->m_vendorValue, (const char*)stringValue, valueLen); SetLength(len + attrLen); return true; } bool RadiusPDU::AppendVsaAttr( int intValue, /// 32 bit integer to be stored in the attribute Value int vendorId, /// 32 bit vendor identifier unsigned char vendorType /// vendor-specific attribute type ) { const PINDEX len = GetLength(); const PINDEX attrLen = RadiusAttr::VsaRfc2865FixedHeaderLength + 4; if (attrLen > RadiusAttr::MaxLength || (len + attrLen) > MaxPduLength) return false; RadiusAttr* const attr = reinterpret_cast(m_data + len); attr->m_type = RadiusAttr::VendorSpecific; attr->m_length = attrLen; SetRadiusInteger(attr->m_vendorId, vendorId); attr->m_vendorType = vendorType; attr->m_vendorLength = 4 + 2; SetRadiusInteger(attr->m_vendorValue, intValue); SetLength(len + attrLen); return true; } bool RadiusPDU::AppendVsaAttr( const PIPSocket::Address& addressValue, /// IPv4 address to be stored in the attribute Value int vendorId, /// 32 bit vendor identifier unsigned char vendorType /// vendor-specific attribute type ) { const PINDEX len = GetLength(); const PINDEX attrLen = RadiusAttr::VsaRfc2865FixedHeaderLength + 4; if (attrLen > RadiusAttr::MaxLength || (len + attrLen) > MaxPduLength) return false; const DWORD addr = (DWORD)addressValue; RadiusAttr* const attr = reinterpret_cast(m_data + len); attr->m_type = RadiusAttr::VendorSpecific; attr->m_length = attrLen; SetRadiusInteger(attr->m_vendorId, vendorId); attr->m_vendorType = vendorType; attr->m_vendorLength = 4 + 2; attr->m_vendorValue[0] = ((const BYTE*)&addr)[0]; attr->m_vendorValue[1] = ((const BYTE*)&addr)[1]; attr->m_vendorValue[2] = ((const BYTE*)&addr)[2]; attr->m_vendorValue[3] = ((const BYTE*)&addr)[3]; SetLength(len + attrLen); return true; } /// Append a string Cisco VSA attribute bool RadiusPDU::AppendCiscoAttr( unsigned char vendorType, /// vendor-specific attribute type const PString& stringValue, /// string to be stored in the attribute Value bool vsaHack /// true to not prepend attribute name to its value ) { const PINDEX len = GetLength(); PINDEX attrLen = RadiusAttr::VsaRfc2865FixedHeaderLength; if ((len + attrLen) > MaxPduLength) return false; RadiusAttr* const attr = reinterpret_cast(m_data + len); attr->m_type = RadiusAttr::VendorSpecific; attr->m_length = attrLen; SetRadiusInteger(attr->m_vendorId, RadiusAttr::CiscoVendorId); attr->m_vendorType = vendorType; attr->m_vendorLength = 2; if (!vsaHack) { int i = 0; while (CiscoAttrNames[i].m_name != NULL) if (CiscoAttrNames[i].m_type == vendorType) { attrLen += CiscoAttrNames[i].m_nameLen + 1; if ((len + attrLen) > MaxPduLength) return false; memcpy(attr->m_vendorValue, CiscoAttrNames[i].m_name, CiscoAttrNames[i].m_nameLen); attr->m_length = attr->m_length + (unsigned char)CiscoAttrNames[i].m_nameLen; attr->m_vendorLength = attr->m_vendorLength + (unsigned char)CiscoAttrNames[i].m_nameLen; attr->m_data[attr->m_length++] = '='; attr->m_vendorLength++; break; } else i++; } const PINDEX strLen = stringValue.GetLength(); attrLen += strLen; if (((PINDEX)attr->m_length + strLen) > RadiusAttr::MaxLength || (len + attrLen) > MaxPduLength) return false; memcpy(attr->m_data + (PINDEX)attr->m_length, (const char*)stringValue, strLen); attr->m_length = attr->m_length + (unsigned char)strLen; attr->m_vendorLength = attr->m_vendorLength + (unsigned char)strLen; SetLength(len + attrLen); return true; } PINDEX RadiusPDU::GetNumAttributes() const { PINDEX count = 0; const RadiusAttr* attr = GetAttr(); while (attr != NULL) { attr = GetAttr(attr); count++; } return count; } const RadiusAttr* RadiusPDU::GetAttr( const RadiusAttr* prevAttr ) const { const PINDEX len = GetLength(); PINDEX offset = FixedHeaderLength; if (prevAttr != NULL) { const unsigned long ptr = reinterpret_cast(prevAttr); #ifdef _DEBUG PAssert(ptr >= reinterpret_cast(m_data + FixedHeaderLength) && ptr < reinterpret_cast(m_data + len), PInvalidParameter ); #endif offset = ptr - reinterpret_cast(m_data) + prevAttr->GetLength(); } return (offset >= len) ? NULL : reinterpret_cast(m_data + offset); } const RadiusAttr* RadiusPDU::FindAttr( unsigned char attrType, /// attribute type to be matched const RadiusAttr* prevAttr /// start element for the search operation ) const { const RadiusAttr* attr = GetAttr(prevAttr); while (attr != NULL && attr->GetType() != attrType) attr = GetAttr(attr); return attr; } const RadiusAttr* RadiusPDU::FindVsaAttr( int vendorId, /// vendor identifier to be matched unsigned char vendorType, /// vendor attribute type to be matched const RadiusAttr* prevAttr /// start element for the search operation ) const { const RadiusAttr* attr = GetAttr(prevAttr); while (attr != NULL && (!attr->IsVsa() || attr->GetVsaVendorId() != vendorId || attr->GetVsaType() != vendorType)) attr = GetAttr(attr); return attr; } bool RadiusPDU::Write(PBYTEArray& buffer, PINDEX& written, PINDEX offset) const { if (!IsValid()) return false; if (offset == P_MAX_INDEX) offset = buffer.GetSize(); const PINDEX len = GetLength(); BYTE* const buffptr = buffer.GetPointer(len + offset) + offset; memcpy(buffptr, m_data, len); written = len; return true; } bool RadiusPDU::Read(const void* rawData, PINDEX rawLength) { #ifdef _DEBUG PAssertNULL(rawData); PAssert(rawLength >= MinPduLength, PInvalidParameter); #endif m_code = m_id = Invalid; SetLength(FixedHeaderLength); if (rawData == NULL || rawLength < MinPduLength) return false; const BYTE* buffptr = (const BYTE*)rawData; memcpy(m_data, buffptr, FixedHeaderLength); buffptr += FixedHeaderLength; const PINDEX length = GetLength(); if (length > rawLength || length < MinPduLength || length > MaxPduLength) { m_code = m_id = Invalid; SetLength(FixedHeaderLength); return false; } if (length > FixedHeaderLength) { memcpy(m_attributes, buffptr, length - FixedHeaderLength); } return true; } bool RadiusPDU::Read( const PBYTEArray& buffer, /// buffer with RADIUS packet data PINDEX offset /// offset into the buffer, where data starts ) { const PINDEX len = buffer.GetSize(); if (len <= offset) return false; return Read(((const BYTE*)buffer) + offset, len - offset); } void RadiusPDU::CopyContents(const RadiusPDU& pdu) { memcpy(m_data, pdu.m_data, FixedHeaderLength); const PINDEX len = GetLength(); if (len < MinPduLength || len > MaxPduLength) { m_code = m_id = Invalid; SetLength(FixedHeaderLength); return; } if (len > FixedHeaderLength) memcpy(m_attributes, pdu.m_attributes, len - FixedHeaderLength); } bool RadiusPDU::EncryptPasswords( const PString& secret, PMessageDigest5& md5 ) { RadiusAttr* const pwdAttr = const_cast(FindAttr(RadiusAttr::UserPassword)); if (pwdAttr == NULL) return true; /// generate 128-bit digest from shared secret and authenticator PMessageDigest::Result digest; const PINDEX secretLength = secret.GetLength(); md5.Start(); if (secretLength > 0) md5.Process((const char*)secret, secretLength); md5.Process(m_authenticator, AuthenticatorLength); md5.CompleteDigest(digest); // calculate length of the new and the old User-Password value const PINDEX origPwdLength = pwdAttr->GetValueLength(); PINDEX encPwdLength = (origPwdLength == 0) ? 16 : ((origPwdLength + 15) & (~((PINDEX)0xf))); const PINDEX len = GetLength(); if ((len + encPwdLength + RadiusAttr::FixedHeaderLength) > MaxPduLength) return false; // the encrypted password attribute will be appended as the last attribute RadiusAttr* const encPwdAttr = reinterpret_cast(m_data + len); encPwdAttr->m_type = RadiusAttr::UserPassword; encPwdAttr->m_length = encPwdLength + RadiusAttr::FixedHeaderLength; memset(encPwdAttr->m_value, 0, encPwdLength); if (origPwdLength > 0) memcpy(encPwdAttr->m_value, pwdAttr->m_value, origPwdLength); // encrypt first 16 bytes of the password DWORD* buf1ptr = reinterpret_cast(encPwdAttr->m_value); const DWORD* buf2ptr = reinterpret_cast(digest.GetPointer()); // XOR either byte-wise or dword-wise (if the memory block is aligned properly) if ((reinterpret_cast(buf2ptr) & 3) || (reinterpret_cast(buf1ptr) & 3)) { for (int _i = 0; _i < 16; _i++) ((BYTE*)buf1ptr)[_i] = ((BYTE*)buf1ptr)[_i] ^ ((const BYTE*)buf2ptr)[_i]; buf1ptr += 4; buf2ptr += 4; } else { // dword aligned data *buf1ptr = *buf1ptr ^ *buf2ptr++; ++buf1ptr; *buf1ptr = *buf1ptr ^ *buf2ptr++; ++buf1ptr; *buf1ptr = *buf1ptr ^ *buf2ptr++; ++buf1ptr; *buf1ptr = *buf1ptr ^ *buf2ptr++; ++buf1ptr; } // encrypt remaining 16 byte blocks of the password while (encPwdLength > 16) { encPwdLength -= 16; // get a new 128-bit digest for encryption md5.Start(); if (secretLength > 0) md5.Process((const char*)secret, secretLength); md5.Process(buf1ptr - 4, 16); md5.CompleteDigest(digest); buf2ptr = reinterpret_cast(digest.GetPointer()); if ((reinterpret_cast(buf2ptr) & 3) || (reinterpret_cast(buf1ptr) & 3)) { for (int _i = 0; _i < 16; _i++) ((BYTE*)buf1ptr)[_i] = ((BYTE*)buf1ptr)[_i] ^ ((const BYTE*)buf2ptr)[_i]; buf1ptr += 4; buf2ptr += 4; } else { // dword aligned data *buf1ptr = *buf1ptr ^ *buf2ptr++; ++buf1ptr; *buf1ptr = *buf1ptr ^ *buf2ptr++; ++buf1ptr; *buf1ptr = *buf1ptr ^ *buf2ptr++; ++buf1ptr; *buf1ptr = *buf1ptr ^ *buf2ptr++; ++buf1ptr; } } // delete the old (clear text) User-Password attribute and append the new // one (encrypted) at the end // this is done by overwritting the old User-Password with attributes // present after it (memory block holding remaining attributes is moved) SetLength(len + encPwdAttr->GetLength() - pwdAttr->GetLength()); memcpy(pwdAttr, reinterpret_cast(pwdAttr) + pwdAttr->GetLength(), reinterpret_cast(encPwdAttr) - reinterpret_cast(pwdAttr) + encPwdAttr->GetLength() - pwdAttr->GetLength() ); // !!! At this point pwdAttr and encPwdAttr are no longer valid! return true; } #ifndef DEFAULT_PERMANENT_SYNCPOINTS #define DEFAULT_PERMANENT_SYNCPOINTS 8 #endif RadiusSocket::RadiusSocket( WORD port ) : m_permanentSyncPoints(DEFAULT_PERMANENT_SYNCPOINTS), m_isReading(false), m_nestedCount(0), m_idCacheTimeout(RadiusClient::DefaultIdCacheTimeout) { if (!Listen(0, port)) { PTRACE(1, "RADIUS\tCould not bind socket to the port " << port << " - error " << GetErrorCode(PSocket::LastGeneralError) << '/' << GetErrorNumber(PSocket::LastGeneralError) << ": " << GetErrorText(PSocket::LastGeneralError) ); Close(); } int i; PRandom random; const unsigned _id_ = random; m_oldestId = m_nextId = (BYTE)(_id_^(_id_>>8)^(_id_>>16)^(_id_>>24)); memset(m_readSyncPoints, 0, sizeof(m_readSyncPoints)); if (IsOpen()) { memset(m_pendingRequests, 0, sizeof(m_pendingRequests)); memset(m_syncPointMap, 0, sizeof(m_syncPointMap)); memset(m_idTimestamps, 0, sizeof(m_idTimestamps)); for (i = 0; i < 256; i++) m_readSyncPointIndices[i] = P_MAX_INDEX; for (i = 0; i < m_permanentSyncPoints; i++) m_readSyncPoints[i] = new PSyncPoint(); } } RadiusSocket::RadiusSocket( const PIPSocket::Address& addr, WORD port ) : m_permanentSyncPoints(DEFAULT_PERMANENT_SYNCPOINTS), m_isReading(false), m_nestedCount(0), m_idCacheTimeout(RadiusClient::DefaultIdCacheTimeout) { if (!Listen(addr, 0, port)) { PTRACE(1, "RADIUS\tCould not bind socket to " << addr << ':' << port << " - error " << GetErrorCode(PSocket::LastGeneralError) << '/' << GetErrorNumber(PSocket::LastGeneralError) << ": " << GetErrorText(PSocket::LastGeneralError) ); Close(); } int i; PRandom random; const unsigned _id_ = random; m_oldestId = m_nextId = (BYTE)(_id_^(_id_>>8)^(_id_>>16)^(_id_>>24)); memset(m_readSyncPoints, 0, sizeof(m_readSyncPoints)); if (IsOpen()) { memset(m_pendingRequests, 0, sizeof(m_pendingRequests)); memset(m_syncPointMap, 0, sizeof(m_syncPointMap)); memset(m_idTimestamps, 0, sizeof(m_idTimestamps)); for (i = 0; i < 256; i++) m_readSyncPointIndices[i] = P_MAX_INDEX; for (i = 0; i < m_permanentSyncPoints; i++) m_readSyncPoints[i] = new PSyncPoint(); } } RadiusSocket::~RadiusSocket() { PWaitAndSignal lock(m_readMutex); for (int i = 0; i < 256; i++) delete m_readSyncPoints[i]; } void RadiusSocket::PrintOn(ostream& strm) const { strm << "port:" << GetPort() << "[active requests: " << m_nestedCount << ", ID space: " << (PINDEX)m_oldestId << '-' << (PINDEX)m_nextId << ']'; } PINDEX RadiusSocket::AllocReadSyncPoint() { PINDEX idx = 0; for (PINDEX k = 0; k < 8; k++) if (m_syncPointMap[k] != 0xffffffff) { for (PINDEX i = 0, j = 1; i < 32; i++, j <<= 1, idx++) if ((m_syncPointMap[k] & ((DWORD)j)) == 0) { m_syncPointMap[k] |= (DWORD)j; if (m_readSyncPoints[idx] == NULL ) m_readSyncPoints[idx] = new PSyncPoint(); return idx; } } else idx += 32; return P_MAX_INDEX; } void RadiusSocket::FreeReadSyncPoint(PINDEX syncPointIndex) { if (syncPointIndex < 256 && syncPointIndex >= 0) { m_syncPointMap[(syncPointIndex >> 5) & 7] &= ~(DWORD)(((DWORD)1)<<(syncPointIndex & 31)); if (syncPointIndex >= m_permanentSyncPoints) { delete m_readSyncPoints[syncPointIndex]; m_readSyncPoints[syncPointIndex] = NULL; } } } bool RadiusSocket::MakeRequest( const RadiusPDU* request, const Address& serverAddress, WORD serverPort, RadiusPDU*& pdu ) { if (!IsOpen() || request == NULL || !request->IsValid()) return false; const PINDEX length = request->GetLength(); const unsigned char id = request->GetId(); const PTimeInterval timeout = GetReadTimeout(); const PTime startTime; bool shouldRead = false; PSyncPoint* syncPoint = NULL; RadiusRequest* requestInfo = NULL; { if (!m_readMutex.Wait(timeout)) { PTRACE(4, "RADIUS\tMutex timed out for the request (id:" << (PINDEX)id << ')' ); return false; } PWaitAndSignal lock(m_readMutex, FALSE); if (m_pendingRequests[id] != NULL) { PTRACE(1, "RADIUS\tDuplicate RADIUS socket request (id:" << (PINDEX)id << ')' ); return false; } if (!m_isReading) m_isReading = shouldRead = true; else { const PINDEX index = AllocReadSyncPoint(); if (index == P_MAX_INDEX) { PTRACE(1, "RADIUS\tFailed to allocate a new mutex for " "the request (id:" << (PINDEX)id << ')' ); return false; } syncPoint = m_readSyncPoints[index]; if (syncPoint == NULL) { PTRACE(1, "RADIUS\tFailed to allocate a new mutex for " "the request (id:" << (PINDEX)id << ')' ); FreeReadSyncPoint(index); return false; } m_readSyncPointIndices[id] = index; m_nestedCount++; } requestInfo = m_pendingRequests[id] = new RadiusRequest(request, pdu, &serverAddress, serverPort); } m_writeMutex.Wait(); BOOL result = WriteTo(request, length, serverAddress, serverPort); if (!result) PTRACE(5, "RADIUS\tError sending UDP packet (" << GetErrorCode(LastWriteError) << '/' << GetErrorNumber(LastWriteError) << ": " << GetErrorText(LastWriteError) << " (id:" << (PINDEX)id << ')' ); m_writeMutex.Signal(); if (result) do { result = FALSE; if (shouldRead) { PIPSocket::Address remoteAddress; WORD remotePort; RadiusPDU* response = new RadiusPDU(); result = ReadFrom(response, sizeof(RadiusPDU), remoteAddress, remotePort ); if (!result) { if (GetErrorCode(LastReadError) == Timeout) PTRACE(6, "RADIUS\tTimed out reading socket " << *this); else PTRACE(5, "RADIUS\tError reading socket " << *this << " (" << GetErrorCode(LastReadError) << '/' << GetErrorNumber(LastReadError) << ": " << GetErrorText(LastReadError) << ')' ); delete response; break; } result = FALSE; PINDEX bytesRead = GetLastReadCount(); if (bytesRead < RadiusPDU::MinPduLength) { PTRACE(5, "RADIUS\tReceived packet is too small (" << bytesRead << ')' ); delete response; continue; } if (!response->IsValid()) { PTRACE(5, "RADIUS\tReceived packet is not a valid Radius PDU"); delete response; continue; } const BYTE newId = response->GetId(); if (!m_readMutex.Wait(timeout)) { PTRACE(5, "RADIUS\tTimed out (mutex) - dropping PDU (id:" << (PINDEX)newId ); delete response; continue; } PWaitAndSignal lock(m_readMutex, FALSE); if (m_pendingRequests[newId] == NULL) { PTRACE(5, "RADIUS\tUnmatched PDU received (code:" << (PINDEX)response->GetCode() << ",id:" << (PINDEX)newId << ')' ); delete response; continue; } if (remoteAddress != *(m_pendingRequests[newId]->m_addr) || remotePort != m_pendingRequests[newId]->m_port) { PTRACE(5, "RADIUS\tReceived PDU from unknown address: " << remoteAddress << ':' << remotePort ); delete response; continue; } m_pendingRequests[newId]->m_response = response; m_pendingRequests[newId] = NULL; response = NULL; if (newId == id) { m_isReading = false; if (m_nestedCount) for (PINDEX i = 0, j = m_oldestId; i < 256; i++, j = (j + 1) & 0xff) if (m_readSyncPointIndices[j] != P_MAX_INDEX && m_readSyncPoints[m_readSyncPointIndices[j] & 0xff] != NULL) { m_readSyncPoints[m_readSyncPointIndices[j] & 0xff]->Signal(); break; } delete requestInfo; return true; } else if(m_readSyncPointIndices[newId] != P_MAX_INDEX && m_readSyncPoints[m_readSyncPointIndices[newId]] != NULL) { m_readSyncPoints[m_readSyncPointIndices[newId]]->Signal(); continue; } } else { result = (syncPoint != NULL && syncPoint->Wait(timeout)); if (!result) break; result = FALSE; PWaitAndSignal lock(m_readMutex); if (m_pendingRequests[id] == NULL) { FreeReadSyncPoint(m_readSyncPointIndices[id]); m_readSyncPointIndices[id] = P_MAX_INDEX; if (m_nestedCount) m_nestedCount--; delete requestInfo; return true; } if (!m_isReading) { m_isReading = shouldRead = true; FreeReadSyncPoint(m_readSyncPointIndices[id]); m_readSyncPointIndices[id] = P_MAX_INDEX; syncPoint = NULL; if (m_nestedCount) m_nestedCount--; continue; } continue; } if (!result) break; } while (PTime() < (startTime + timeout)); { PWaitAndSignal lock(m_readMutex); m_pendingRequests[id] = NULL; if (m_readSyncPointIndices[id] != P_MAX_INDEX) { FreeReadSyncPoint(m_readSyncPointIndices[id]); m_readSyncPointIndices[id] = P_MAX_INDEX; if (m_nestedCount) m_nestedCount--; } if (m_isReading && shouldRead) { m_isReading = false; if (m_nestedCount) for (PINDEX i = m_oldestId, j = 0; j < 256; j++, i = (i + 1) & 0xff) if (m_readSyncPointIndices[i] != P_MAX_INDEX && m_readSyncPoints[m_readSyncPointIndices[i] & 0xff] != NULL) { m_readSyncPoints[m_readSyncPointIndices[i] & 0xff]->Signal(); break; } } } delete requestInfo; return result ? true : false; } bool RadiusSocket::SendRequest( const RadiusPDU* request, const Address& serverAddress, WORD serverPort ) { if (!IsOpen() || request == NULL || !request->IsValid()) return false; PWaitAndSignal lock(m_writeMutex); if (WriteTo(request, request->GetLength(), serverAddress, serverPort)) return true; PTRACE(5, "RADIUS\tError sending UDP packet (" << GetErrorCode(LastWriteError) << '/' << GetErrorNumber(LastWriteError) << ": " << GetErrorText(LastWriteError) << " (id:" << (PINDEX)request->GetId() << ')' ); return false; } void RadiusSocket::RefreshIdCache( const time_t now ) { const PINDEX lastId = ((m_nextId >= m_oldestId) ? m_nextId : ((PINDEX)m_nextId + 256)); const long timeout = m_idCacheTimeout.GetSeconds(); PINDEX i = m_oldestId; while (i++ < lastId && (m_idTimestamps[m_oldestId] + timeout) < now) ++m_oldestId; } PINDEX RadiusSocket::GenerateNewId() { const PTime now; const time_t nowInSeconds = now.GetTimeInSeconds(); RefreshIdCache(nowInSeconds); if (((m_nextId + 1) & 0xff) == m_oldestId) return P_MAX_INDEX; else { m_recentRequestTime = now; m_idTimestamps[m_nextId] = nowInSeconds; return m_nextId++; } } RadiusClient::RadiusClient( /// primary RADIUS server const PString& servers, /// local address for RADIUS client const PString& address, /// default secret shared between the client and the server const PString& sharedSecret ) : m_sharedSecret((const char*)sharedSecret), m_authPort(RadiusClient::GetDefaultAuthPort()), m_acctPort(RadiusClient::GetDefaultAcctPort()), m_portBase(1024), m_portMax(65535), m_requestTimeout(DefaultRequestTimeout), m_idCacheTimeout(DefaultIdCacheTimeout), m_socketDeleteTimeout(DefaultSocketDeleteTimeout), m_numRetries(DefaultRetries), m_roundRobinServers(false), m_localAddress(INADDR_ANY) { GetServersFromString(servers); if (!address) if (!PIPSocket::IsLocalHost(address)) PTRACE(1, "RADIUS\tSpecified local client address " << address << " is not bound to any local interface" ); else PIPSocket::GetHostAddress(address, m_localAddress); #if PTRACING if (PTrace::CanTrace(4)) { ostream& s = PTrace::Begin(4, __FILE__, __LINE__); const int indent = s.precision() + 2; s << "RADIUS\tCreated instance of RADIUS client (local if: " << m_localAddress << ", default ports: " << m_authPort << ',' << m_acctPort << ") for RADIUS servers group:"; for (unsigned i = 0; i < m_radiusServers.size(); i++) s << '\n' << setw(indent + m_radiusServers[i]->m_serverAddress.GetLength()) << m_radiusServers[i]->m_serverAddress << " (auth port: " << (m_radiusServers[i]->m_authPort == 0 ? m_authPort : m_radiusServers[i]->m_authPort) << ", acct port: " << (m_radiusServers[i]->m_acctPort == 0 ? m_acctPort : m_radiusServers[i]->m_acctPort) << ')'; PTrace::End(s); } #endif } RadiusClient::RadiusClient( PConfig& config, /// config that contains RADIUS settings const PString& sectionName /// config section with the settings ) : m_sharedSecret(Toolkit::Instance()->ReadPassword(sectionName, "SharedSecret")), m_authPort((WORD)config.GetInteger(sectionName, "DefaultAuthPort", RadiusClient::GetDefaultAuthPort())), m_acctPort((WORD)config.GetInteger(sectionName, "DefaultAcctPort", RadiusClient::GetDefaultAcctPort())), m_portBase(1024), m_portMax(65535), m_requestTimeout(config.GetInteger(sectionName, "RequestTimeout", DefaultRequestTimeout)), m_idCacheTimeout(config.GetInteger(sectionName, "IdCacheTimeout", DefaultIdCacheTimeout)), m_socketDeleteTimeout(config.GetInteger(sectionName, "SocketDeleteTimeout", DefaultSocketDeleteTimeout)), m_numRetries(config.GetInteger(sectionName, "RequestRetransmissions", DefaultRetries)), m_roundRobinServers(config.GetBoolean( sectionName, "RoundRobinServers", TRUE) ? true : false), m_localAddress(INADDR_ANY) { GetServersFromString(config.GetString(sectionName, "Servers", "")); const PString addr = config.GetString(sectionName, "LocalInterface", ""); if (!addr) if (!PIPSocket::IsLocalHost(addr)) PTRACE(2, "RADIUS\tSpecified local client address '" << addr << "' is not bound to any local interface" ); else PIPSocket::GetHostAddress(addr, m_localAddress); // parse port range (if it does exist) const PStringArray s = config.GetString(sectionName, "RadiusPortRange", "").Tokenise("-"); if (s.GetSize() >= 2) { unsigned p1 = s[0].AsUnsigned(); unsigned p2 = s[1].AsUnsigned(); // swap if base is greater than max if (p2 < p1) { const unsigned temp = p1; p1 = p2; p2 = temp; } if (p1 > 65535) p1 = 65535; if (p2 > 65535) p2 = 65535; if (p1 > 0 && p2 > 0) { m_portBase = (WORD)p1; m_portMax = (WORD)p2; } } #if PTRACING if (PTrace::CanTrace(4)) { ostream& s = PTrace::Begin(4, __FILE__, __LINE__); const int indent = s.precision() + 2; s << "RADIUS\tCreated instance of RADIUS client (local if: " << m_localAddress << ", default ports: " << m_authPort << ',' << m_acctPort << ") for RADIUS servers group:"; for (unsigned i = 0; i < m_radiusServers.size(); i++) s << '\n' << setw(indent + m_radiusServers[i]->m_serverAddress.GetLength()) << m_radiusServers[i]->m_serverAddress << " (auth port: " << (m_radiusServers[i]->m_authPort == 0 ? m_authPort : m_radiusServers[i]->m_authPort) << ", acct port: " << (m_radiusServers[i]->m_acctPort == 0 ? m_acctPort : m_radiusServers[i]->m_acctPort) << ')'; PTrace::End(s); } #endif } RadiusClient::~RadiusClient() { socket_iterator iter = m_activeSockets.begin(); while (iter != m_activeSockets.end()) { RadiusSocket *s = *iter; iter = m_activeSockets.erase(iter); delete s; } for (unsigned i = 0; i < m_radiusServers.size(); i++) delete m_radiusServers[i]; m_radiusServers.clear(); } void RadiusClient::GetServersFromString( const PString& servers ) { const PStringArray tokens = servers.Tokenise(" ;,", FALSE); for (PINDEX i = 0; i < tokens.GetSize(); i++) { const PStringArray serverTokens = tokens[i].Tokenise(":"); if (serverTokens.GetSize() > 0) { const PString serverAddress = serverTokens[0].Trim(); if (!serverAddress) { RadiusServer* const server = new RadiusServer(); server->m_serverAddress = serverAddress; server->m_authPort = 0; server->m_acctPort = 0; if (serverTokens.GetSize() >= 2) server->m_authPort = (WORD)(serverTokens[1].AsInteger()); if (serverTokens.GetSize() >= 3) server->m_acctPort = (WORD)(serverTokens[2].AsInteger()); if (serverTokens.GetSize() >= 4) server->m_sharedSecret = serverTokens[3].Trim(); m_radiusServers.push_back(server); } } } } bool RadiusClient::SetClientPortRange( WORD base, /// base port number WORD range /// number of ports in the range ) { if (range < 1) return false; PWaitAndSignal lock(m_socketMutex); m_portBase = base; m_portMax = m_portBase + (((range-1) < (65535-m_portBase)) ? (range-1) : (65535-m_portBase) ); return true; } bool RadiusClient::SetIdCacheTimeout( const PTimeInterval& timeout /// new time interval ) { PWaitAndSignal lock(m_socketMutex); if (timeout < PTimeInterval(1000)) return false; m_idCacheTimeout = timeout; socket_const_iterator i = m_activeSockets.begin(); while (i != m_activeSockets.end()) { (*i)->SetIdCacheTimeout(timeout); ++i; } return true; } bool RadiusClient::SetRetryCount( unsigned retries /// retry count (must be at least 1) ) { if (retries < 1) return false; PWaitAndSignal lock(m_socketMutex); m_numRetries = retries; return true; } bool RadiusClient::SetRequestTimeout( const PTimeInterval& timeout ) { if (timeout < PTimeInterval(25)) return false; PWaitAndSignal lock(m_socketMutex); m_requestTimeout = timeout; socket_const_iterator i = m_activeSockets.begin(); while (i != m_activeSockets.end()) { (*i)->SetReadTimeout(timeout); (*i)->SetWriteTimeout(timeout); ++i; } return true; } bool RadiusClient::MakeRequest( const RadiusPDU& requestPDU, /// PDU with request packet RadiusPDU*& responsePDU /// filled with PDU received from RADIUS server ) { if (!requestPDU.IsValid()) return false; bool retransmission = false; RadiusSocket* socket = NULL; unsigned char id; const unsigned numServers = m_radiusServers.size(); const PString* secret = NULL; for (unsigned i = 0; i < (m_roundRobinServers ? m_numRetries * numServers : numServers); i++) { const unsigned serverIndex = i % numServers; const PTime now; const RadiusServer* const server = m_radiusServers[serverIndex]; const WORD authPort = server->m_authPort == 0 ? m_authPort : server->m_authPort; const WORD acctPort = server->m_acctPort == 0 ? m_acctPort : server->m_acctPort; const WORD serverPort = IsAcctPDU(requestPDU) ? acctPort : authPort; const PString* const oldSecret = secret; secret = server->m_sharedSecret.IsEmpty() ? &m_sharedSecret : &server->m_sharedSecret; bool secretChanged = secret != oldSecret && oldSecret != NULL && secret->Compare(*oldSecret) != PString::EqualTo; PIPSocket::Address serverAddress; if (!PIPSocket::GetHostAddress(server->m_serverAddress, serverAddress) || !serverAddress.IsValid()) { PTRACE(3, "RADIUS\tCould not get IPv4 address for RADIUS server " "host: " << server->m_serverAddress ); continue; } for (unsigned j = 0; j < (m_roundRobinServers ? 1 : m_numRetries); j++) { RadiusPDU* const clonedRequestPDU = new RadiusPDU(requestPDU); bool requireNewId = false; if (!OnSendPDU(*clonedRequestPDU, retransmission, requireNewId)) { delete clonedRequestPDU; return false; } if (secretChanged || requireNewId || !retransmission) if (!GetSocket(socket, id)) { PTRACE(3, "RADIUS\tSocket allocation failed"); delete clonedRequestPDU; return false; } secretChanged = false; clonedRequestPDU->SetId(id); PMessageDigest5 md5; clonedRequestPDU->SetAuthenticator(*secret, md5); if (!clonedRequestPDU->EncryptPasswords(*secret, md5)) { PTRACE(3, "RADIUS\tCould not encrypt passwords " "(id:" << (PINDEX)(clonedRequestPDU->GetId()) << ')' ); delete clonedRequestPDU; return false; } #if PTRACING if( PTrace::CanTrace(3) ) { ostream& strm = PTrace::Begin(3, __FILE__, __LINE__); strm << "RADIUS\tSending PDU to RADIUS server " << server->m_serverAddress << " (" << serverAddress << ':' << serverPort << ')' << " from " << (*socket) << ", PDU: "; if( PTrace::CanTrace(5) ) strm << *clonedRequestPDU; else strm << PMAP_CODE_TO_NAME(clonedRequestPDU->GetCode()) << ", id " << (PINDEX)(clonedRequestPDU->GetId()); PTrace::End(strm); } #endif RadiusPDU* response = NULL; retransmission = true; if (!socket->MakeRequest(clonedRequestPDU, serverAddress, serverPort, response)) { PTRACE(3, "RADIUS\tReceive response from RADIUS server failed " "(id:" << (PINDEX)(clonedRequestPDU->GetId()) << ')' ); delete clonedRequestPDU; continue; } if (!VerifyResponseAuthenticator( clonedRequestPDU, response, *secret)) { PTRACE(5, "RADIUS\tReceived PDU (id: " << (PINDEX)clonedRequestPDU->GetId() << ") has an invalid response authenticator" ); delete clonedRequestPDU; continue; } delete clonedRequestPDU; #if PTRACING if (PTrace::CanTrace(3)) { ostream& strm = PTrace::Begin(3, __FILE__, __LINE__); strm << "RADIUS\tReceived PDU from RADIUS server " << server->m_serverAddress << " (" << serverAddress << ':' << serverPort << ')' << " by socket " << (*socket) << ", PDU: "; if (PTrace::CanTrace(5)) strm << (*response); else strm << PMAP_CODE_TO_NAME(response->GetCode()) << ", id " << (PINDEX)(response->GetId()); PTrace::End(strm); } #endif if (!OnReceivedPDU(*response)) { delete response; continue; } responsePDU = response; return true; } } return false; } bool RadiusClient::SendRequest( const RadiusPDU& requestPDU /// PDU with request packet ) { if (!requestPDU.IsValid()) return false; RadiusSocket* socket = NULL; unsigned char id; RadiusPDU* clonedRequestPDU = NULL; if (m_radiusServers.empty()) { PTRACE(1, "RADIUS\tNo RADIUS servers configured"); return false; } const RadiusServer* const server = m_radiusServers.front(); const WORD authPort = server->m_authPort == 0 ? m_authPort : server->m_authPort; const WORD acctPort = server->m_acctPort == 0 ? m_acctPort : server->m_acctPort; const WORD serverPort = IsAcctPDU(requestPDU) ? acctPort : authPort; const PString& secret = server->m_sharedSecret.IsEmpty() ? m_sharedSecret : server->m_sharedSecret; PIPSocket::Address serverAddress; if (!PIPSocket::GetHostAddress(server->m_serverAddress, serverAddress) || !serverAddress.IsValid()) { PTRACE(3, "RADIUS\tCould not get IPv4 address for RADIUS server host: " << server->m_serverAddress ); return false; } clonedRequestPDU = new RadiusPDU(requestPDU); bool dummy; if (!OnSendPDU(*clonedRequestPDU, false, dummy)) { delete clonedRequestPDU; return false; } if (!GetSocket(socket, id)) { PTRACE(3, "RADIUS\tSocket allocation failed"); delete clonedRequestPDU; return false; } clonedRequestPDU->SetId(id); PMessageDigest5 md5; clonedRequestPDU->SetAuthenticator(secret, md5); if (!clonedRequestPDU->EncryptPasswords(secret, md5)) { PTRACE(3, "RADIUS\tCould not encrypt passwords " "(id:" << (PINDEX)(clonedRequestPDU->GetId()) << ')' ); delete clonedRequestPDU; return false; } #if PTRACING if (PTrace::CanTrace(3)) { ostream& strm = PTrace::Begin(3, __FILE__, __LINE__); strm << "RADIUS\tSending PDU to RADIUS server " << server->m_serverAddress << " (" << serverAddress << ':' << serverPort << ')' << " from " << (*socket) << ", PDU: "; if (PTrace::CanTrace(5)) strm << *clonedRequestPDU; else strm << PMAP_CODE_TO_NAME(clonedRequestPDU->GetCode()) << ", id " << (PINDEX)(clonedRequestPDU->GetId()); PTrace::End(strm); } #endif if (!socket->SendRequest(clonedRequestPDU, serverAddress, serverPort)) { PTRACE(3, "RADIUS\tError sending RADIUS request (id:" << (PINDEX)id << ')'); delete clonedRequestPDU; return false; } delete clonedRequestPDU; return true; } bool RadiusClient::VerifyResponseAuthenticator( const RadiusPDU* request, const RadiusPDU* response, const PString& secret ) { PMessageDigest5 md5; PMessageDigest::Result digest; const PINDEX len = response->GetLength(); md5.Process(response, RadiusPDU::AuthenticatorOffset); md5.Process(request->GetAuthenticator(), RadiusPDU::AuthenticatorLength); if (len > RadiusPDU::FixedHeaderLength) md5.Process( reinterpret_cast(response) + RadiusPDU::FixedHeaderLength, len - RadiusPDU::FixedHeaderLength ); const PINDEX secretLength = secret.GetLength(); if (secretLength > 0) md5.Process((const char*)secret, secretLength); md5.CompleteDigest(digest); return digest.GetSize() == RadiusPDU::AuthenticatorLength && memcmp(digest.GetPointer(), response->GetAuthenticator(), RadiusPDU::AuthenticatorLength) == 0; } bool RadiusClient::OnSendPDU( RadiusPDU& /*pdu*/, bool /*retransmission*/, bool& /*requireNewId*/ ) { return true; } bool RadiusClient::OnReceivedPDU( RadiusPDU& /*pdu*/ ) { return true; } bool RadiusClient::IsAcctPDU(const RadiusPDU& pdu) const { const unsigned char c = pdu.GetCode(); return (c == RadiusPDU::AccountingRequest) || (c == RadiusPDU::AccountingResponse) || (c == RadiusPDU::AccountingStatus) || (c == RadiusPDU::AccountingMessage); } bool RadiusClient::GetSocket(RadiusSocket*& socket, unsigned char& id) { PWaitAndSignal lock(m_socketMutex); const socket_iterator endIter = m_activeSockets.end(); socket_iterator s = m_activeSockets.begin(); // find a first socket that is not busy (has at least one ID that can // be used for a request) while (s != endIter) { const PINDEX newId = (*s)->GenerateNewId(); if (newId != P_MAX_INDEX) { id = (unsigned char)newId; break; } else ++s; } // refresh state of remaining sockets (reclaim unused request IDs) // and delete sockets that have not been used for a long time const PTime now; socket_iterator i = m_activeSockets.begin(); while (i != endIter) { if (i == s) { ++i; continue; } (*i)->RefreshIdCache(now.GetTimeInSeconds()); if ((*i)->CanDestroy() && ((*i)->GetRecentRequestTime() + m_socketDeleteTimeout) < now) { RadiusSocket *s = *i; i = m_activeSockets.erase(i); delete s; } else ++i; } if (s != endIter) { socket = *s; return true; } // all sockets are busy, create a new one PRandom random; PINDEX randCount = (unsigned)(m_portMax-m_portBase+1) / 3; RadiusSocket* newSocket = NULL; if (randCount > 0) do { PINDEX portIndex = random % (unsigned)(m_portMax-m_portBase+1); delete newSocket; newSocket = NULL; if (m_localAddress == INADDR_ANY) newSocket = CreateSocket((WORD)(m_portBase + portIndex)); else newSocket = CreateSocket(m_localAddress, (WORD)(m_portBase + portIndex)); } while ((newSocket == NULL || !newSocket->IsOpen()) && --randCount); if (newSocket == NULL || !newSocket->IsOpen()) for (WORD p = m_portBase; p < m_portMax; p++) { delete newSocket; newSocket = NULL; if (m_localAddress == INADDR_ANY) newSocket = CreateSocket(p); else newSocket = CreateSocket(m_localAddress, p); if (newSocket->IsOpen()) break; } if (newSocket == NULL || !newSocket->IsOpen()) { delete newSocket; return false; } newSocket->SetReadTimeout(m_requestTimeout); newSocket->SetWriteTimeout(m_requestTimeout); newSocket->SetIdCacheTimeout(m_idCacheTimeout); m_activeSockets.push_back(newSocket); PTRACE(5, "RADIUS\tCreated new RADIUS client socket: " << (*newSocket)); const PINDEX newId = newSocket->GenerateNewId(); if (newId == P_MAX_INDEX) return false; socket = newSocket; id = (unsigned char)newId; return true; } void RadiusClient::SetSocketDeleteTimeout( const PTimeInterval& timeout /// new timeout ) { PWaitAndSignal lock(m_socketMutex); if (timeout > PTimeInterval(20000)) m_socketDeleteTimeout = timeout; } RadiusSocket* RadiusClient::CreateSocket( const PIPSocket::Address& addr, WORD port ) { return new RadiusSocket(addr, port); } RadiusSocket* RadiusClient::CreateSocket( WORD port ) { return new RadiusSocket(port); } #endif /* HAS_RADIUS */