/*
* 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 <ptlib.h>
#include <ptlib/sockets.h>
#include <ptclib/cypher.h>
#include <ptclib/random.h>
#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 = <<null>>\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<const RadiusAttr*>(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<RadiusAttr*>(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<RadiusAttr*>(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<RadiusAttr*>(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<RadiusAttr*>(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<RadiusAttr*>(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<RadiusAttr*>(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<RadiusAttr*>(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<RadiusAttr*>(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<RadiusAttr*>(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<RadiusAttr*>(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<unsigned long>(prevAttr);
#ifdef _DEBUG
PAssert(ptr >= reinterpret_cast<unsigned long>(m_data + FixedHeaderLength)
&& ptr < reinterpret_cast<unsigned long>(m_data + len), PInvalidParameter
);
#endif
offset = ptr - reinterpret_cast<unsigned long>(m_data)
+ prevAttr->GetLength();
}
return (offset >= len)
? NULL : reinterpret_cast<const RadiusAttr*>(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<RadiusAttr*>(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<RadiusAttr*>(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<DWORD*>(encPwdAttr->m_value);
const DWORD* buf2ptr = reinterpret_cast<const DWORD*>(digest.GetPointer());
// XOR either byte-wise or dword-wise (if the memory block is aligned properly)
if ((reinterpret_cast<unsigned long>(buf2ptr) & 3)
|| (reinterpret_cast<unsigned long>(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<const DWORD*>(digest.GetPointer());
if ((reinterpret_cast<unsigned long>(buf2ptr) & 3)
|| (reinterpret_cast<unsigned long>(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<unsigned char*>(pwdAttr) + pwdAttr->GetLength(),
reinterpret_cast<unsigned long>(encPwdAttr)
- reinterpret_cast<unsigned long>(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<const char*>(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 */
syntax highlighted by Code2HTML, v. 0.9.1