/*
* radproto.h
*
* RADIUS protocol client classes that offer good performance,
* scalability and multithreading access.
*
* 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.h,v $
* Revision 1.15 2006/04/30 09:22:56 willamowius
* PTimedMutex patch for PWLib >= 1.9.2
*
* Revision 1.14 2006/04/14 13:56:19 willamowius
* call failover code merged
*
* Revision 1.1.1.1 2005/11/21 20:20:00 willamowius
*
*
* Revision 1.4 2005/11/15 19:52:56 jan
* Michal v1 (works, but on in routed, not proxy mode)
*
* Revision 1.13 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.12 2004/07/26 12:19:42 zvision
* New faster Radius implementation, thanks to Pavel Pavlov for ideas!
*
* Revision 1.11.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.11.2.2 2004/07/07 23:11:08 zvision
* Faster and more elegant handling of Cisco VSA
*
* Revision 1.11.2.1 2004/07/07 20:50:14 zvision
* New, faster, Radius client implementation. Thanks to Pavel Pavlov for ideas!
*
* Revision 1.11 2004/04/21 14:15:27 zvision
* Default ports are now set to fixes values (1812,1813), because of problems
* with getservbyname and multiple threads on some systems
*
* Revision 1.10 2004/04/17 11:43:43 zvision
* Auth/acct API changes.
* Header file usage more consistent.
*
* Revision 1.9 2004/03/17 00:00:38 zvision
* Conditional compilation to allow to control RADIUS on Windows just by setting HA_RADIUS macro
*
* Revision 1.8 2003/11/11 11:14:21 zvision
* Fixed invalid signed/unsigned integer conversions for radius attributes.
* Optimized radius attributes handling.
*
* Revision 1.7 2003/10/08 12:40:48 zvision
* Realtime accounting updates added
*
* Revision 1.6 2003/09/29 16:11:44 zvision
* Added cvs Id keyword to header #define macro
*
* 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:19 zvision
* Initially added to 2.2 branch
*
* Revision 1.1.2.5 2003/07/03 15:32:20 zvision
* Fixed comments. Removed md5 param from VerifyResponseAuthenticator.
*
* Revision 1.1.2.4 2003/06/19 10:52:24 zvision
* Added AcctStatusTypes enum.
*
* Revision 1.1.2.3 2003/06/05 10:02:21 zvision
* Bugfixes and small code cleanup.
*
* Revision 1.1.2.2 2003/05/13 17:45:01 zvision
* Added attribute searching functions
*
* Revision 1.1.2.1 2003/04/23 20:14:56 zvision
* Initial revision
*
*/
#if HAS_RADIUS
#ifndef __RADPROTO_H
#define __RADPROTO_H "@(#) $Id: radproto.h,v 1.15 2006/04/30 09:22:56 willamowius Exp $"
#include <vector>
#include <ptlib/sockets.h>
#include "pwlib_compat.h"
class PRandom;
class PMessageDigest5;
/// Encapsulates RADIUS Attribute structure.
class RadiusAttr
{
public:
friend class RadiusPDU;
enum Constants {
/// max length of the attribute raw data
MaxLength = 255,
/// length of the attribute fixed header (Type+Length)
FixedHeaderLength = 2,
/// max length of the Value field
MaxValueLength = (MaxLength - FixedHeaderLength),
/// length of the fixed header for VSA
/// (standard header + VendorId field)
VsaFixedHeaderLength = FixedHeaderLength + 4,
/// max length of the VSA Value field
VsaMaxValueLength = (MaxLength - VsaFixedHeaderLength),
/// length of the fixed header for RFC2865 conformant VSA
/// (standard header + VendorId, VendorType and VendorLength fields)
VsaRfc2865FixedHeaderLength = VsaFixedHeaderLength + 2,
/// max length of the VSA RFC2865 conformant Value field
VsaMaxRfc2865ValueLength = (MaxLength - VsaRfc2865FixedHeaderLength)
};
/// Constants for RADIUS Attribute Type field
enum AttrTypes {
Invalid = 0, UserName, UserPassword, ChapPassword,
NasIpAddress = 4, NasPort, ServiceType, FramedProtocol,
FramedIpAddress = 8, FramedIpNetmask, FramedRouting, FilterId,
FramedMtu = 12, FramedCompression, LoginIpHost, LoginService,
LoginTcpPort = 16, OldPassword, ReplyMessage, CallbackNumber,
CallbackId = 20, Expiration, FramedRoute, FramedIpxNet,
State = 24, AttrTypeClass, VendorSpecific, SessionTimeout,
IdleTimeout = 28, TerminationAction, CalledStationId, CallingStationId,
NasIdentifier = 32, ProxyState, LoginLatService, LoginLatNode,
LoginLatGroup = 36, FramedAppleTalkLink, FramedAppleTalkNetwork, FramedAppleTalkZone,
AcctStatusType = 40, AcctDelayTime, AcctInputOctets, AcctOutputOctets,
AcctSessionId = 44, AcctAuthentic, AcctSessionTime, AcctInputPackets,
AcctOutputPackets = 48, AcctTerminateCause, AcctMultiSessionId, AcctLinkCount,
AcctInputGigawords = 52, AcctOutputGigawords, EventTimestamp = 55,
ChapChallenge = 60, NasPortType, PortLimit, LoginLatPort,
TunnelType = 64, TunnelMediumType, TunnelClientEndpoint, TunnelServerEndpoint,
AcctTunnelConnectionId = 68, TunnelPassword,
PasswordRetry = 75,
Prompt = 76, ConnectInfo, ConfigurationToken,
AcctInterimInterval = 85, AcctTunnelPacketsLost, NasPortId
};
/// Constants for Service-Type attribute values
enum ServiceTypes {
ST_Login = 1, ST_Framed, ST_CallbackLogin,
ST_CallbackFramed = 4, ST_Outbound, ST_Administrative,
ST_NasPrompt = 7,
ST_AuthenticateOnly = 8, ST_CallbackNasPrompt, ST_CallCheck,
ST_CallbackAdministrative = 9
};
/// Constants for Framed-Protocol attribute values
enum FramedProtocols {
FP_Ppp = 1,
FP_Slip
};
/// Constants for Framed-Compression attribute values
enum FramedCompressionTypes {
FC_None = 0,
FC_VJTcpIp,
FC_Ipx,
FC_StacLZS
};
/// Constants for Login-Service attribute values
enum LoginServiceTypes {
LS_Telnet = 0, LS_Rlogin, LS_TcpClear, LS_PortMaster,
LS_Lat, LS_X25_PAD, LS_X25_T3POS, LS_TcpClearQuiet
};
/// Constants for NAS-Port-Type attribute values
enum NASPortTypes {
NasPort_Asynchronous = 0, NasPort_Synchronous, NasPort_IsdnSynchronous,
NasPort_IsdnAsynchronousV120 = 3, NasPort_IsdnAsynchronousV110,
NasPort_Virtual = 5, NasPort_Piafs, NasPort_HdlcClearChannel,
NasPort_X25 = 8, NasPort_X75, NasPort_G3Fax, NasPort_SDSL,
NasPort_AdslCap = 12, NasPort_AdslDmt, NasPort_Idsl, NasPort_Ehternet,
NasPort_xDsl = 16, NasPort_Cable, NasPort_WirelessOther,
NasPort_WirelessIeee8021 = 19
};
/// Constants for Acct-Status-Type atribute values
enum AcctStatusTypes {
AcctStatus_Start = 1,
AcctStatus_Stop = 2,
AcctStatus_InterimUpdate = 3,
AcctStatus_AccountingOn = 7,
AcctStatus_AccountingOff = 8
};
/// Constants for VendorId VSA field
enum VendorIdentifiers {
CiscoVendorId = 9
};
/// Contants for Cisco VSA types
enum CiscoVSA {
CiscoVSA_AV_Pair = 1,
CiscoVSA_h323_remote_address = 23, CiscoVSA_h323_conf_id = 24,
CiscoVSA_h323_setup_time = 25, CiscoVSA_h323_call_origin = 26,
CiscoVSA_h323_call_type = 27, CiscoVSA_h323_connect_time = 28,
CiscoVSA_h323_disconnect_time = 29, CiscoVSA_h323_disconnect_cause = 30,
CiscoVSA_h323_voice_quality = 31, CiscoVSA_h323_gw_id = 33,
CiscoVSA_h323_incoming_conf_id = 35,
CiscoVSA_h323_credit_amount = 101, CiscoVSA_h323_credit_time = 102,
CiscoVSA_h323_return_code = 103, CiscoVSA_h323_redirect_number = 106,
CiscoVSA_h323_preferred_lang = 107,
CiscoVSA_h323_redirect_ip_address = 108,
CiscoVSA_h323_billing_model = 109, CiscoVSA_h323_currency = 110
};
/** Construct uninitialized attribute. It should be initialized
later by other means (operator=, etc.)
*/
RadiusAttr();
/** Create TLV RADIUS attribute of a given type,
initializing #value# field with 'attrLength' bytes
of data pointed to by 'attrValue'. In case of VSA,
#vsaVendorId# is read from the attribute's value data.
*/
RadiusAttr(
unsigned char attrType, /// Attribute Type (see #enum AttrTypes#)
const void* attrValue, /// buffer with attribute Value data
PINDEX valueLength /// length of attribute Value data
);
/** Copy constructor for RADIUS Attribute. It simply does byte copy.
*/
RadiusAttr(
const RadiusAttr& attr /// atrribute to copy from
) { memcpy(m_data, attr.m_data, attr.m_length); }
/** Create TLV RADIUS attribute of a given type,
initializing #value# field with 'stringValue.GetLength()' bytes
from 'stringValue' string. In case of VSA,
#vsaVendorId# is extracted from data contained in the #value# field.
*/
RadiusAttr(
unsigned char attrType, /// Attribute Type (see #enum AttrTypes#)
const PString& stringValue /// string to be stored in the attribute Value data
);
/** Create TLV RADIUS attribute of a given type,
initializing #value# field with an integer value passed
with 'intValue' parameter. This constructor should be also used
for attributes carrying 32-bit timestamps.
*/
RadiusAttr(
unsigned char attrType, /// Attribute Type (see #enum AttrTypes#)
int intValue /// 32 bit integer to be stored in the attribute Value
);
/** Create TLV RADIUS attribute of a given type,
initializing #value# field with 32 bit IPv4 address
value passed with 'addressValue' parameter.
*/
RadiusAttr(
unsigned char attrType, /// Attribute Type (see #enum AttrTypes#)
const PIPSocket::Address& addressValue /// IPv4 address to be stored in the attribute Value
);
/** Create TLV RADIUS vendor-specific (26) attribute of a given
vendor specific type, initializing #value# data with 'vendorId'
32 bit identifier, 'vendorType' vendor attribute type
and 'attrLength' bytes of data pointer to by 'attrValue'.
#vsaVendorId# is set to 'vendorId'.
*/
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
);
/** Create TLV RADIUS vendor-specific attribute of a given type,
initializing #value# data with 'vendorId' 32 bit identifier,
'vendorType' vendor attribute type
and 'stringValue.GetLength()' bytes (characters) of 'stringValue'
parameter. #vsaVendorId# is set to 'vendorId'.
*/
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
);
/** Create TLV RADIUS vendor-specific attribute of a given type,
initializing #value# data with 'vendorId' 32 bit identifier,
'vendorType' vendor-specific attribute type
and 32 bit 'intValue' integer value. #vsaVendorId# is set to 'vendorId'.
*/
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
);
/** Create TLV RADIUS vendor-specific attribute of a given type,
initializing #value# data with 'vendorId' 32 bit identifier,
'vendorType' vendor-specific attribute type
and 32 bit IPv6 address specified by 'addressValue' parameter.
#vsaVendorId# is set to 'vendorId'.
*/
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
);
/** Create TLV RADIUS Cisco VSA attribute of a given type. If #vsaHack#
is false, then the attribute name is also embedded into the attribute
value (like 'h323-currency=USD').
*/
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
);
/** Create TLV RADIUS attribute, reading raw data from the buffer
specified by 'rawData' parameter. Reading stops after full attribute
is reconstructed from the data or 'rawLength' bytes have been read.
Make sure to call #IsValid()# to check if the attribute has been
reconstructed successfully. Call #GetLength()# to obtain number of bytes
that have been actually read.
*/
RadiusAttr(
const void* rawData, /// buffer with the attribute raw data
PINDEX rawLength /// length (bytes) of the buffer
);
/** @return
Type of this attribute (see #enum AttrTypes#).
*/
unsigned char GetType() const { return m_type; }
/** @return
Vendor-specific type for this attribute, assuming this
attribute is a VSA that conforms to RFC 2865 guidelines for VSAs
(has vendorId, vendorType and vendorLength fields).
*/
unsigned char GetVsaType() const
{ return (m_length < VsaRfc2865FixedHeaderLength) ? 0 : m_vendorType; }
/** @return
Total length (bytes) of this attribute.
*/
PINDEX GetLength() const { return m_length; }
/** @return
Length of the Value field for this attribute.
*/
PINDEX GetValueLength() const
{
const PINDEX len = m_length;
const PINDEX headerLen = IsVsa() ? VsaFixedHeaderLength : FixedHeaderLength;
return (len <= headerLen) ? 0 : (len - headerLen);
}
/** @return
Length (bytes) of the Value field for this attribute,
assuming that it conforms to RFC 2865 guidelines for VSAs
(contains vendorId, vendorType and vendorLength fields).
*/
PINDEX GetVsaValueLength() const;
/** Fill the byte array with Value associated with this attribute.
The array is resized, if necessary, to contain the value.
Call #GetValueLength()# to determine number of bytes written
to the array
*/
bool GetValue(
PBYTEArray& val, /// array where data is to be stored
PINDEX offset = 0 /// offset into the array, where the data write starts
) const;
/** Fill the byte array with Value associated with this attribute,
assuming that this attribute is RFC 2865 guidelines conformant VSA.
The array is resized, if necessary, to contain the value.
Call #GetVsaValueLength()# to determine number of bytes written
to the array
*/
bool GetVsaValue(
PBYTEArray& val, /// array where data is to be stored
PINDEX offset = 0 /// offset into the array, where the data write starts
) const;
/** @return
True if this is a vendor-specific attribute (VSA).
*/
bool IsVsa() const { return (m_type == VendorSpecific); }
/** @return
32 bit vendor identifier for VSA. This call is valid only
for VSA attributes (see #IsVSA()#). Also ensure that this attribute
is valid (#IsValid()).
*/
int GetVsaVendorId() const;
/** Get attribute Value as a string. Be aware that the string
may contain embedded 0s. For VSA attributes this call will
build the string from data contained after vendorId field.
If RFC 2865 guidelines conformant VSA value is to be retrieved
use rather #AsVsaString()#.
@return
PString containing attribute Value.
If an error occurs an empty string is returned.
*/
PString AsString() const;
/** Get RFC 2865 guidelines conformant VSA Value as a string.
Be aware that the string may contain embedded 0s. This call will
build the string from data contained after vendorId, vendorType and
vendorLength fields.
@return
PString containing attribute Value.
If an error occurs an empty string is returned.
*/
PString AsVsaString() const;
/** Get Cisco's VSA value as a string, with attribute name removed,
if it is prepended to the string.
@return
PString containing attribute Value.
*/
PString AsCiscoString() const;
/** Get attribute Value as a 32 bit integer.
For VSA attributes this call will build the integer
from 4 bytes of data contained after vendorId field.
If RFC 2865 guidelines conformant VSA value is to be retrieved
use rather #AsVsaInteger()#.
@return
An integer representing attribute Value.
If an error occurs 0 is returned.
*/
int AsInteger() const;
/** Get RFC 2865 guidelines conformant VSA Value as a 32 bit integer.
This call will build the integer from 4 bytes of data contained
after vendorId, vendorType and vendorLength fields.
@return
An integer representing attribute Value.
If an error occurs 0 is returned.
*/
int AsVsaInteger() const;
/** Get attribute Value as a 32 bit timestamp.
For VSA attributes this call will build the timestamp
from 4 bytes of data contained after vendorId field.
If RFC 2865 guidelines conformant VSA value is to be retrieved
use rather #AsVsaTimestamp()#.
@return
PTime representing attribute Value. This timestamp
is interpreted as number of seconds passed since
*/
time_t AsTime() const { return (time_t)AsVsaInteger(); }
/** Get RFC 2865 guidelines conformant VSA Value as a 32 bit timestamp.
This call will build the timestamp from 4 bytes of data contained
after vendorId, vendorType and vendorLength fields.
@return
PTime representing attribute Value. This timestamp
is interpreted as number of seconds passed since
*/
time_t AsVsaTime() const { return (time_t)AsVsaInteger(); }
/** Get attribute Value as a 32 bit IPv4 address.
For VSA attributes this call will build the IPv4 address
from 4 bytes of data contained after vendorId field.
If RFC 2865 guidelines conformant VSA value is to be retrieved
use rather #AsVsaAddress()#.
@return
IPv4 address representing attribute Value.
*/
PIPSocket::Address AsAddress() const;
/** Get RFC 2865 guidelines conformant VSA Value as a 32 bit IPv4 address.
This call will build the IPv4 address from 4 bytes of data contained
after vendorId, vendorType and vendorLength fields.
@return
IPv4 address representing attribute Value.
*/
PIPSocket::Address AsVsaAddress() const;
/** Write this attribute to the buffer. The buffer is resized,
if necessary, to contain this attribute.
@return
True if successfully written (and 'written' receives number
of bytes written to the buffer).
*/
bool Write(
PBYTEArray& buffer, /// buffer the attribute data will be written to
PINDEX& written, /// number of bytes written (if successful return)
PINDEX offset = 0 /// offset into the buffer, where writting starts
) const;
/** Assign contents of the attribute 'attr' to this attribute.
@return
Reference to this attribute
*/
RadiusAttr& operator=(
const RadiusAttr& attr /// the attribute that contents will be assigned from
) { memcpy(m_data, attr.m_data, attr.m_length); return *this; }
/** Check whether this attribute contains valid data.
@return
Trye if this attribute is "valid".
*/
bool IsValid() const
{
return ((PINDEX)m_length) >= ((m_type == VendorSpecific)
? VsaFixedHeaderLength : FixedHeaderLength
);
}
/** Output PDU to the stream. This is
primarily used by the standard #operator<<# function.
*/
void PrintOn(
ostream& strm /// Stream to print the object into.
) const;
friend ostream& operator<<(ostream& s, const RadiusAttr& attr)
{ attr.PrintOn(s); return s; }
protected:
/** Read attribute data from the raw buffer.
@return
TRUE if attribute has been sucessfully read.
Call #GetLength()# to determine how many bytes have been read.
*/
bool Read(
const void* rawData, /// raw buffer with attribute data
PINDEX rawLength /// length of the buffer
);
/** Read attribute data from the byte array.
@return
TRUE if attribute has been sucessfully read.
Call #GetLength()# to determine how many bytes have been read.
*/
bool Read(
const PBYTEArray& array, /// byte array with the attribute data
PINDEX offset = 0 /// offset into the buffer, where data starts
)
{
return (array.GetSize() > offset)
? Read(((const BYTE*)array) + offset, array.GetSize() - offset)
: false;
}
protected:
union {
/** Attribute raw data. The most important fields:
data[0] - attribute Type
data[1] - attribute Length (bytes)
*/
unsigned char m_data[MaxLength];
struct {
unsigned char m_type;
unsigned char m_length;
union {
unsigned char m_value[MaxLength - FixedHeaderLength];
struct {
unsigned char m_vendorId[4];
unsigned char m_vendorType;
unsigned char m_vendorLength;
unsigned char m_vendorValue[MaxLength - VsaRfc2865FixedHeaderLength];
};
};
};
};
};
/// Encapsulates RADIUS packet (PDU).
class RadiusPDU
{
public:
/// Useful constants
enum Constants {
/// This header is always present
FixedHeaderLength = 20,
/// Length of Authenticator vector inside Fixed Header
AuthenticatorLength = 16,
/// Offset of Authenticator field from the beginning of PDU data
AuthenticatorOffset = 4
};
/// Standarized RADIUS packet types
enum Codes {
Invalid = 0, AccessRequest, AccessAccept, AccessReject,
AccountingRequest = 4, AccountingResponse, AccountingStatus,
PasswordRequest = 7,
PasswordAccept = 8, PasswordReject, AccountingMessage,
AccessChallenge = 11,
StatusServer = 12, StatusClient
};
/// Minimum and maximum number of bytes per RADIUS packet.
enum PDUBounds {
MinPduLength = FixedHeaderLength,
MaxPduLength = 4096
};
/** Create RadiusPDU instance initialized with 0
or empty values. This object must be intialized
later with meaningful values.
*/
RadiusPDU();
/// Copy constructor for RADIUS packet.
RadiusPDU(
const RadiusPDU& ref /// source RADIUS packet to be copied
);
/** Create PDU with no attributes, initializing #code#
and #id# fields.
*/
RadiusPDU(
unsigned char packetCode, /// code - see #Codes enum#
unsigned char packetId = 0/// packet id (sequence number)
);
/** Create PDU from the raw data buffer. 'rawLength' defines size
of the buffer. Call #IsValid()# to check if PDU was built successfully
from the raw data.
*/
RadiusPDU(
const void* rawData, /// raw data buffer
PINDEX rawLength /// raw data length
);
/** Checks whether this PDU contains valid data.
@return
TRUE if this PDU is valid.
*/
bool IsValid() const;
/** @return
Code for this RADIUS packet (see #enum Codes#)
*/
unsigned char GetCode() const { return m_code; }
/** Set new type (Code filed) for this PDU.
*/
void SetCode(
unsigned char newCode /// new PDU type
) { m_code = newCode; }
/** @return
Identifier (Id field) of this RADIUS packet
*/
unsigned char GetId() const { return m_id; }
/** Set new identifier for this RADIUS packet.
*/
void SetId(
unsigned char newId /// new packet identifier
) { m_id = newId; }
/** @return
Length of this RADIUS packet (bytes)
*/
PINDEX GetLength() const
{ return (((PINDEX)(m_length[0]) & 0xff) << 8) | ((PINDEX)(m_length[1]) & 0xff); }
/** @return
A pointer to a memory block that holds 16 bytes authenticator.
*/
const unsigned char* GetAuthenticator() const { return m_authenticator; }
/** Fill the array with 16 bytes #authenticator# vector
associated with this RADIUS packet.
*/
void GetAuthenticator(
PBYTEArray& vector, /// buffer where the 16 bytes authenticator will be stored
PINDEX offset = 0 /// offset into the buffer where the data write starts
) const;
/** Fill 16 bytes #authenticator# vector associated with this RADIUS packet
with data passed in 'vector' parameter.
@return
TRUE if authenticator has been set
*/
bool SetAuthenticator(
const PBYTEArray& vector, /// 16 bytes authenticator
PINDEX offset = 0 /// offset into the buffer where the authenticator starts
);
/** Fill 16 bytes #authenticator# vector associated with this RADIUS packet
with data pointed to by 'data' parameter.
@return
TRUE if authenticator has been set
*/
bool SetAuthenticator(
const void* data /// 16 bytes authenticator
);
/// Fill #authenticator# vector with 16 bytes of random data.
void SetAuthenticator(
PRandom& random /// random generator to be used
);
/** Fill Request Authenticator field in this PDU before sending it to
RADIUS server. Default implementation sets RA to random vector for
non-accounting PDUs, or to MD5 checksum of the whole packet
(replacing RA with 0s) + sharedSecret for accounting requests.
*/
void SetAuthenticator(
const PString& secret, /// secret shared between client and server
PMessageDigest5& md5 /// MD5 generator
);
/** @return
Number of attributes associated with this RADIUS PDU.
*/
PINDEX GetNumAttributes() const;
/** Appends a clone of this attribute to the attribute list tail.
@return
True if the attribute has been appended.
*/
bool AppendAttr(
const RadiusAttr& attr /// attribute to be appended
);
RadiusPDU& operator +=(const RadiusAttr& attr)
{ AppendAttr(attr); return (*this); }
/// Append a generic attribute
bool AppendAttr(
unsigned char attrType, /// Attribute Type
const void* attrValue, /// buffer with attribute Value data
PINDEX valueLength /// length of attribute Value data
);
/// Append a string attribute
bool AppendAttr(
unsigned char attrType, /// Attribute Type
const PString& stringValue /// string to be stored in the attribute Value data
);
/// Append an integer attribute
bool AppendAttr(
unsigned char attrType, /// Attribute Type
int intValue /// 32 bit integer to be stored in the attribute Value
);
/// Append an IP address attribute
bool AppendAttr(
unsigned char attrType, /// Attribute Type
const PIPSocket::Address& addressValue /// IPv4 address to be stored in the attribute Value
);
/// Append a generic VSA attribute
bool 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
);
/// Append a string VSA attribute
bool 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
);
/// Append an integer VSA attribute
bool 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
);
/// Append an IP address VSA attribute
bool 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
);
/// Append a string Cisco VSA attribute
bool AppendCiscoAttr(
unsigned char vendorType, /// vendor-specific attribute type
const PString& stringValue, /// string to be stored in the attribute Value
bool vsaHack = false /// true to not prepend attribute name to its value
);
/// Copy contents of the given Radius packet to this packet
RadiusPDU& operator=(
const RadiusPDU& pdu
) { CopyContents(pdu); return *this; }
/** @return
A pointer to a Radius attribute next after #previousAttr#
or to the first one in the packet. #previousAttr# can be used
to iterate over all attributes present in the packet.
NULL is returned if there are no more (or no at all) attributes.
NOTE: All xxxAttr functions return a temporary pointer that is valid
only during this object instance lifetime.
*/
const RadiusAttr* GetAttr(
const RadiusAttr* previousAttr = NULL
) const;
/** Find an attribute of a given type inside the radius packet. The search
starts from the list head or after the specified attribute.
@return
A pointer to the attribute found or NULL if no attribute of this type
has been found.
*/
const RadiusAttr* FindAttr(
unsigned char attrType, /// attribute type to be matched
const RadiusAttr* prevAttr = NULL /// start element for the search operation
) const;
/** Find a VSA attribute of a given VendorId and VendorType fields
inside the radius packet. The search starts from the list head
or after the specified attribute.
@return
A pointer to the attribute found or NULL if no attribute of this type
has been found.
*/
const RadiusAttr* FindVsaAttr(
int vendorId, /// vendor identifier to be matched
unsigned char vendorType, /// vendor attribute type to be matched
const RadiusAttr* prevAttr = NULL /// start element for the search operation
) const;
/** Write this RADIUS packet data into the buffer.
@return
TRUE if packet data has been written
*/
bool Write(
PBYTEArray& buffer, /// buffer where the data will be stored
PINDEX& written, /// number of bytes written (if successful)
PINDEX offset = 0 /// offset into the buffer, where the write operation starts
) const;
/** Scan list of attributes associated with this PDU and encrypts passwords.
This implementation hides password stored in User-Name attributes
using method described in RFC 2865.
@return
False if passwords could not be encrypted for some reason.
*/
bool EncryptPasswords(
const PString& secret, /// secret shared between client and server
PMessageDigest5& md5 /// MD5 generator
);
/** Output PDU to the stream. This is
primarily used by the standard #operator<<# function.
*/
void PrintOn(
ostream& strm /// Stream to print the object into.
) const;
friend ostream& operator<<(ostream& s, const RadiusPDU& pdu)
{ pdu.PrintOn(s); return s; }
protected:
/** Build PDU object from the raw data buffer.
Data previously associated with this object is lost.
@return
TRUE if the object has been successfully built (e.g. raw
buffer contained valid data)
*/
bool Read(
const void* rawData, /// raw buffer with PDU data
PINDEX rawLength /// length (bytes) of the raw buffer
);
/** Build PDU object from the raw data buffer.
Data previously associated with this object is lost.
@return
TRUE if the object has been successfully built (e.g. raw
buffer contained valid data)
*/
bool Read(
const PBYTEArray& buffer, /// buffer with RADIUS packet data
PINDEX offset = 0 /// offset into the buffer, where data starts
);
/// Copy content of RADIUS packet 'pdu' into this object.
void CopyContents(
const RadiusPDU& pdu /// packet to copy data from
);
private:
/// Set Radius packet length to the given value
void SetLength(
PINDEX newLen /// new packet length in bytes
)
{
m_length[0] = (unsigned char)((newLen >> 8) & 0xff);
m_length[1] = (unsigned char)(newLen & 0xff);
}
protected:
union {
/// raw RADIUS packet data
unsigned char m_data[MaxPduLength];
struct {
/// RADIUS packet type
unsigned char m_code;
/// RADIUS packet id (sequence number)
unsigned char m_id;
/// RADIUS packet length (big endian)
unsigned char m_length[2];
/// RADIUS authenticator vector
unsigned char m_authenticator[AuthenticatorLength];
unsigned char m_attributes[MaxPduLength - FixedHeaderLength];
};
};
};
/** RADIUS client socket maintaining IDs
for pending requests and handling requests
from multiple threads.
Call #GenerateNewId()# to create new identifier
that can be used with next RADIUS packet sent
using this socket. If #GenerateNewId()# retruns
P_MAX_INDEX it means that all IDs are being used
and you can either: 1. wait for oldest Id to return
to the pool of free IDs, 2. create new RadiusSocket.
*/
class RadiusSocket : public PUDPSocket
{
PCLASSINFO(RadiusSocket, PUDPSocket)
public:
/** Create new socket at given port. Autoselect local network interface.
Call #IsOpen()# to check if the socket has been created.
*/
RadiusSocket(
/// port number to send requests from (0=autoselect)
WORD port = 0
);
/** Create new socket at given port, bound to the selected
network interface. Call #IsOpen()# to check if the socket
has been created.
*/
RadiusSocket(
/// local network interface address
const PIPSocket::Address& addr,
/// port number to send requests from
WORD port = 0
);
virtual ~RadiusSocket();
virtual void PrintOn(ostream& strm) const;
/** Process RADIUS request/response sequence. It sends Radius packet
to host 'serverAddress:remotePort' and reads the response into 'pdu'.
Use #SetReadTimeout()# to set timeout for this operation.
@return
True if the RADIUS response has been successfully received
and stored in a variable referenced by the 'pdu' param.
*/
virtual bool MakeRequest(
const RadiusPDU* request, /// buffer with RADIUS packet to send
const Address& serverAddress, /// RADIUS server address
WORD remotePort, /// RADIUS server port
RadiusPDU*& pdu /// receives RADIUS Response PDU on success
);
/** Send RADIUS request and return immediatelly. It sends the #request#
to the host 'serverAddress:remotePort' and does not wait for a response.
@return
True if the request has been successfully sent.
*/
virtual bool SendRequest(
const RadiusPDU* request, /// buffer with RADIUS packet to send
const Address& serverAddress, /// RADIUS server address
WORD remotePort /// RADIUS server port
);
/** Generate an unique ID suitable for RadiusPDU identifiers.
This function automatically calls #RefreshIdCache()#.
@return
An identifier in range 0-255 or P_MAX_INDEX if no unique
ID can be generated at this moment.
*/
PINDEX GenerateNewId();
/** Free any RADIUS packet identifiers, that can be reused
at this moment.
*/
void RefreshIdCache(
const time_t now = time(NULL) /// current time
);
/** @return
The time interval for generated Identifier to be unique.
*/
PTimeInterval GetIdCacheTimeout() const { return m_idCacheTimeout; }
/** Set new time interval for generated Identifiers to be unique.
Each generated Identifier can be reused only if the specified
time interval elapses.
*/
void SetIdCacheTimeout(
const PTimeInterval& timeout /// new timeout
) { m_idCacheTimeout = timeout; }
/** @return
TRUE if the socket is not used by any request any more.
Be aware, that the socket (port number) cannot be reused
until all its allocated IDs are timed out.
*/
bool CanDestroy() const { return (m_oldestId == m_nextId); }
/** @return
Timestamp of the most recent request issued.
*/
PTime GetRecentRequestTime() const { return m_recentRequestTime; }
private:
PINDEX AllocReadSyncPoint();
void FreeReadSyncPoint(
PINDEX syncPointIndex
);
private:
struct RadiusRequest
{
RadiusRequest(
const RadiusPDU* request,
RadiusPDU*& response,
const Address* address,
WORD port
) : m_request(request), m_response(response),
m_addr(address), m_port(port) {}
const RadiusPDU* m_request;
RadiusPDU*& m_response;
const Address* m_addr;
WORD m_port;
};
/** Table filled with requests being currently services
by this socket. Indexed by request Id.
*/
RadiusRequest* m_pendingRequests[256];
/** SyncPoint for socket read operations. SyncPoints
are allocated/freed on demand.
*/
PSyncPoint* m_readSyncPoints[256];
/** Index for matching request Id with index into
#readSyncPoints# array.
*/
PINDEX m_readSyncPointIndices[256];
/// Bit map of sync points being used by pending requests
/// (256 bits)
DWORD m_syncPointMap[8];
/// Number of preallocated SyncPoints (for performance reasons)
/// These SyncPoints get freed on socket destruction
PINDEX m_permanentSyncPoints;
/// mutex for mt synchronization
PTimedMutex m_readMutex;
/// mutex for atomic WriteTo operation on the socket
PMutex m_writeMutex;
/// flag signalling that some request thread performs read operation
bool m_isReading;
/// number of pending requests
PINDEX m_nestedCount;
/// oldest Id that should not be used (is still valid)
unsigned char m_oldestId;
/// next free Id
unsigned char m_nextId;
/// timestamps for generated IDs, used to check when
/// Id can be retruned to pool of free IDs
time_t m_idTimestamps[256];
/// Timestamp of the most recent request performed on this socket
PTime m_recentRequestTime;
/// time interval over that generated IDs must be unique
PTimeInterval m_idCacheTimeout;
};
class RadiusClient
{
public:
/** Default UDP ports for RADIUS authorization
and accounting servers.
*/
enum RadiusClientPorts {
DefaultAuthPort = 1812,
DefaultAcctPort = 1813,
Rfc2138AuthPort = 1645, /// obsolete
Rfc2138AcctPort = 1646 /// obsolete
};
/// Some defaults
enum DefaultValues {
/// timeout for packet IDs returned back to the pool of available IDs
DefaultIdCacheTimeout = 9000,
/// timeout for single request operation
DefaultRequestTimeout = 2000,
/// how many times request is send (1==no retransmission)
DefaultRetries = 2,
/// timeout for unused sockets to be deleted
DefaultSocketDeleteTimeout = 60000
};
/** Construct a RADIUS protocol client, building a list of RADIUS servers
from the string. Custom port number for each RADIUS
server can be specified by appending ":auth_port:acct_port" string
to the server name. For example, "radius1.mycompany.com:1812:1813".
Sample lists may look as follows:
"192.168.1.1"
"192.168.1.1:1645:1646"
"192.168.1.1:1645:1646;192.168.2.1:1812:1813"
"radius1.gnugk.org"
"radius1.gnugk.org:1812:1813"
"radius1.gnugk.org;radius2.gnugk.org"
"radius1.gnugk.org:1812:1813;radius2.gnugk.org:1645:1646"
*/
RadiusClient(
/// primary RADIUS server
const PString& servers,
/// local address for RADIUS client
const PString& address = PString(),
/// default secret shared between the client and the server
const PString& sharedSecret = PString()
);
/** Construct a RADIUS protocol client reading its settings
from the config.
*/
RadiusClient(
PConfig& config, /// config that contains RADIUS settings
const PString& sectionName /// config section with the settings
);
/// Destroy this object
virtual ~RadiusClient();
/** @return
The local IP address this RADIUS client is bound to.
*/
PIPSocket::Address GetLocalAddress() const { return m_localAddress; }
/** Get default port number that will be used
during communication with radius server (if server name string does
not specify custom port number).
@return
port number
*/
WORD GetAuthPort() const { return m_authPort; }
/** Get default port number that will be used
during communication with radius server (if server name string does
not specify custom port number).
@return
port number
*/
WORD GetAcctPort() const { return m_acctPort; }
/** Set default port number that will be used
during communication with radius server (if server name string does
not specify custom port number).
*/
void SetAuthPort(WORD port) { m_authPort = port; }
/** Set default port number that will be used
during communication with radius server (if server name string does
not specify custom port number).
*/
void SetAcctPort(WORD port) { m_acctPort = port; }
/** Set UDP port range to be used by the client.
The effective range will be <base;base+range).
@return
True if the new port range has been set.
*/
bool SetClientPortRange(
WORD base, /// base port number
WORD range /// number of ports in the range
);
/** Get time interval for RADIUS packet Identifiers
to be unique.
@return
Id uniquess time interval.
*/
PTimeInterval GetIdCacheInterval() const { return m_idCacheTimeout; }
/** Set new time interval for RADIUS packet Identifiers
to be unique.
Warning: settings this value to
short interval can cause packets to be ignored
by the server, too long value can produce large
number of client UDP sockets opened at once.
@return
True if the new interval has been set.
*/
bool SetIdCacheTimeout(
const PTimeInterval& timeout /// new time interval
);
/** Set timeout values for RADIUS protocol request/response sequence
to complete. If the timeout elapses and RADIUS server did not respond,
then this client switches to the next server on the list.
@return
True if timeout has been set
*/
bool SetRequestTimeout(
const PTimeInterval& timeout /// timeout for RADIUS request/response operation
);
/** Get timeout for RADIUS request operation.
@return
Request timeout
*/
PTimeInterval GetRequestTimeout() const { return m_requestTimeout; }
/// Set timeout for unused RADIUS client sockets to be deleted.
void SetSocketDeleteTimeout(
const PTimeInterval& timeout /// new timeout
);
/** Get timout for unused RADIUS client sockets to be deleted.
@return
Time period after which unused RADIUS sockets are deleted.
*/
PTimeInterval GetSocketDeleteTimeout() const { return m_socketDeleteTimeout; }
/** Set retry count (retransmission count) for a single RADIUS server.
The number includes first request (e.g. settings this value to 1
disables retransmissions).
@return
True if the retry count has been changed.
*/
bool SetRetryCount(
unsigned retries /// retry count (must be at least 1)
);
/** Get retry count (retransmission count) for a single RADIUS server.
@return
Max number of retransmission for a single RADIUS server.
*/
unsigned GetRetryCount() const { return m_numRetries; }
/** Set retransmission method.
Round robin means:
repeat
transmit to server #1
transmit to server #2
...
transmit to server #n
until numRetries
No round robin (default) means:
repeat
transmit to server #1
until numRetries
repeat
transmit to server #2
until numRetries
...
*/
void SetRoundRobinServers(
bool roundRobin /// true for rr behaviour
) { m_roundRobinServers = roundRobin; }
/** Get retransmission method.
@return
TRUE if round robin method is in use.
*/
bool GetRoundRobinServers() const { return m_roundRobinServers; }
/** Send requestPDU to RADIUS server and waits for response.
If the response is received, it is returned in responsePDU.
@return
True if request/response sequence completed successfully.
*/
virtual bool MakeRequest(
const RadiusPDU& requestPDU, /// PDU with request packet
RadiusPDU*& responsePDU /// filled with PDU received from RADIUS server
);
/** Sends a RADIUS request and does not wait for a response.
This can be used to send accounting updates, for example.
@return
True if the RADIUS request has been successfully sent
(this does not mean it has arrived at the radius server).
*/
virtual bool SendRequest(
const RadiusPDU& requestPDU /// PDU with request packet
);
static WORD GetDefaultAuthPort() { return DefaultAuthPort; }
static WORD GetDefaultAcctPort() { return DefaultAcctPort; }
protected:
/** Callback called before a request is sent to the RADIUS server.
It gives the possibility to do something with the request PDU.
The callback is supplied with information whether the packet
is being retransmitted - and can trigger an update of some attributes.
Set requireNewId if attributes with some dynamic contents have been
added, so the request cannot be simply retransmitted with the same ID.
@return
True if proceed with this request
*/
virtual bool OnSendPDU(
/// PDU that is to be sent
RadiusPDU& pdu,
/// true if this is PDU retransmission
bool retransmision,
/// set to true if a new ID and authenticator should be generated
/// for this request upon retransmission
bool& generateNewId
);
/** Callback called after response PDU had been received from RADIUS server
and before it is returned to the calling application.
It gives the possibility to do something with the PDU.
@return
True if proceed with this response
*/
virtual bool OnReceivedPDU(
RadiusPDU& pdu /// PDU that was received
);
/** Determine if the #pdu# should be send to auth port
of RADIUS server (FALSE) or acct port (TRUE).
@return
True to send the PDU to the accounting RADIUS server module,
false to send the PDU to authenticating RADIUS server module.
*/
virtual bool IsAcctPDU(
const RadiusPDU& pdu /// PDU to be checked
) const;
/** Retrieves reference to RADIUS socket and RADIUS packet
identified, that are suitable for a single request.
That means that for each request, GetSocket call should
be made to obtain the socket and the id.
@return:
True if socket and if are filled with valid data
*/
bool GetSocket(
/// pointer that will be filled with RadiusSocket pointer
/// on success
RadiusSocket*& socket,
/// identifier that will be initialized to valid Id on success
unsigned char& id
);
/** Create new instance of RadiusSocket based class. Can be
overriden to provide custom RadiusSocket implementations.
@return
Pointer to the new socket
*/
virtual RadiusSocket* CreateSocket(
const PIPSocket::Address& addr,
WORD port = 0
);
/** Create new instance of RadiusSocket based class. Can be
overriden to provide custom RadiusSocket implementations.
@return
Pointer to the new socket
*/
virtual RadiusSocket* CreateSocket(
WORD port = 0
);
/** Verify Response Authenticator vector from received PDU.
Provided here mainly for RadiusSocket.
@return
True if RA is valid, FALSE otherwise
*/
virtual bool VerifyResponseAuthenticator(
const RadiusPDU* request, /// buffer with RADIUS request PDU
const RadiusPDU* response, /// buffer with RADIUS response PDU
const PString& secret /// shared secret used to create the request PDU
);
/// Parse #servers# string and build a list of RADIUS servers from it.
void GetServersFromString(
const PString& servers
);
protected:
typedef std::list<RadiusSocket*>::iterator socket_iterator;
typedef std::list<RadiusSocket*>::const_iterator socket_const_iterator;
/// An entry describing a signle radius server
typedef struct RadiusServer
{
PString m_serverAddress; /// IP or DNS name
PString m_sharedSecret; /// password shared between the client and the server
WORD m_authPort; /// port number to send Access Requests to
WORD m_acctPort; /// port number to send Accounting Requests to
};
/// NOTE: m_radiusServers and m_sharedSecret are expected to be constant
/// during the object lifetime
/// list of RADIUS servers
std::vector<const RadiusServer*> m_radiusServers;
/// shared password for authorizing this client with RADIUS servers
PString m_sharedSecret;
/// default port for RADIUS authentication (if not overriden)
WORD m_authPort;
/// default port for RADIUS accounting (if not overriden)
WORD m_acctPort;
/// base UDP port for RADIUS client
WORD m_portBase;
/// upper UDP port limit for RADIUS client
WORD m_portMax;
/// timeout value for processing RADIUS client requests
PTimeInterval m_requestTimeout;
/// time interval over which the packet Id has to be unique (for a single client port)
PTimeInterval m_idCacheTimeout;
/// timeout for unused sockets to be deleted from the pool
PTimeInterval m_socketDeleteTimeout;
/// number of packet retransmissions to a single RADIUS server
unsigned m_numRetries;
/// how RADIUS packers are retransmitted
/// 0: numRetries to server #1, numRetries to server #2, ...
/// 1: 1st packet to #1, 1st packet to #2, ..., 2nd packet to #1, ...
bool m_roundRobinServers;
/// local address that the client should bind to when making requests
PIPSocket::Address m_localAddress;
/// array of active RADIUS client sockets
std::list<RadiusSocket*> m_activeSockets;
/// mutex for accessing #activeSockets# and other stuff
mutable PMutex m_socketMutex;
};
#endif /* __RADPROTO_H */
#endif /* HAS_RADIUS */
syntax highlighted by Code2HTML, v. 0.9.1