/* ====================================================================
* The Vovida Software License, Version 1.0
*
* Copyright (c) 2001 Vovida Networks, Inc. All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
*
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
*
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in
* the documentation and/or other materials provided with the
* distribution.
*
* 3. The names "VOCAL", "Vovida Open Communication Application Library",
* and "Vovida Open Communication Application Library (VOCAL)" must
* not be used to endorse or promote products derived from this
* software without prior written permission. For written
* permission, please contact vocal@vovida.org.
*
* 4. Products derived from this software may not be called "VOCAL", nor
* may "VOCAL" appear in their name, without prior written
* permission of Vovida Networks, Inc.
*
* THIS SOFTWARE IS PROVIDED "AS IS" AND ANY EXPRESSED OR IMPLIED
* WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
* OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE, TITLE AND
* NON-INFRINGEMENT ARE DISCLAIMED. IN NO EVENT SHALL VOVIDA
* NETWORKS, INC. OR ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT DAMAGES
* IN EXCESS OF $1,000, NOR FOR ANY INDIRECT, INCIDENTAL, SPECIAL,
* EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
* PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
* PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
* OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE
* USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH
* DAMAGE.
*
* ====================================================================
*
* This software consists of voluntary contributions made by Vovida
* Networks, Inc. and many individuals on behalf of Vovida Networks,
* Inc. For more information on Vovida Networks, Inc., please see
* <http://www.vovida.org/>.
*
*/
static const char* const RadiusMessage_cxx_Version =
"$Id: RadiusMessage.cxx,v 1.11 2002/12/12 02:34:48 bko Exp $";
#ifdef __FreeBSD__
#include <sys/types.h>
#endif
#include <netinet/in.h>
#include <time.h>
#include "global.h"
#include "cpLog.h"
#include "vmd5.h"
#include "support.hxx"
#include "RadiusMessage.hxx"
/** Create a RADIUS Message with type, no Attriubte yet
Useful for composing a request
*/
RadiusMessage::RadiusMessage( const RadiusPacketType code )
{
cpLog( LOG_DEBUG_STACK, "Create packet with code %d", code );
// These are the requests that we handle now
assert( code == RP_ACCOUNTING_REQUEST ||
code == RP_ACCESS_REQUEST );
memset( &myData, 0, sizeof( struct RadiusHdr ) );
myData.msgHdr.code = code;
myData.msgHdr.length = htons( sizeof( struct RadiusHdr ) );
memset( &myRequestAuthenticator, 0, RadiusAuthenticatorLength );
// Attributes are added later
// Identifier is set and Authenticator is calculated when message is sent
}
/** Create a RADIUS Message with type, identifier and an Authenticator
No Attriubte yet
Use for composing a response
*/
RadiusMessage::RadiusMessage( const RadiusPacketType code,
const u_int8_t requestId,
const u_int8_t *requestAuth )
{
cpLog( LOG_DEBUG_STACK, "Create packet with code %d", code );
assert( requestAuth != 0 );
// These are the responses that we handle now
assert( code == RP_ACCOUNTING_RESPONSE ||
code == RP_ACCESS_REJECT ||
code == RP_ACCESS_CHALLENGE ||
code == RP_ACCESS_ACCEPT );
memset( &myData, 0, sizeof( struct RadiusHdr ) );
myData.msgHdr.code = code;
myData.msgHdr.identifier = requestId;
myData.msgHdr.length = htons( sizeof( struct RadiusHdr ) );
// save original request Authenticator for later use
memcpy( &myRequestAuthenticator, requestAuth, RadiusAuthenticatorLength );
// Attributes are added later
// Authenticator is calculated when message is sent
}
/** Create a RADIUS Message from data
Use when a message is received
*/
RadiusMessage::RadiusMessage( const RadiusData rawMsg, const char* secret )
throw( VRadiusException& )
{
cpLog( LOG_DEBUG_STACK, "Create packet with raw data" );
u_int16_t msgLen = rawMsg.length();
if( msgLen < RadiusMinPacketSize || msgLen > RadiusMaxPacketSize )
{
throw VRadiusException( "Invalid packet size",
__FILE__,
__LINE__ );
}
// Save raw packet
memcpy( myData.buffer, rawMsg.data(), msgLen );
if( msgLen != ntohs(myData.msgHdr.length) )
{
throw VRadiusException( "Packet size and length field don't match",
__FILE__,
__LINE__ );
}
try
{
decodeAttributes( secret );
}
catch( VRadiusException &e )
{
throw e;
}
if( myData.msgHdr.code == RP_ACCOUNTING_REQUEST )
{
// Verify Accounting Request Authenticator
if( verifyAccountingRequestAuthenticator( secret ) == false )
{
throw VRadiusException( "Accounting Request Authentication failed",
__FILE__,
__LINE__ );
}
}
// Access Request Authenticator is not verified
// Response Authenticator should be verified with the original
// request Authenticator later
}
/** Decode Attributes
If an Attribute is received in an Access-Request but an
exception is thrown, an Access-Reject SHOULD be transmitted.
If an Attribute is received in an Access-Accept, Access-Reject
or Access-Challenge packet and an exception is thrown, the
packet must either be treated as an Access-Reject or else
silently discarded.
TODO: Enforce attribute quantity guide Section 5.44 of RFC 2865
*/
void
RadiusMessage::decodeAttributes( const char* secret )
throw( VRadiusException& )
{
u_int16_t totalLen = ntohs( myData.msgHdr.length );
cpLog( LOG_DEBUG_STACK,
"Received code=%d id=%d length=%d",
myData.msgHdr.code, myData.msgHdr.identifier, totalLen);
// Move to the beginning of Attributes
u_int32_t parsed = sizeof( RadiusHdr );
const u_int8_t *msgPtr = myData.buffer + parsed;
const u_int8_t *attrPtr;
RadiusAttributeType attrType;
u_int8_t attrLen;
u_int32_t vendorId; // For VSA
// Build Attribute list
while( parsed < totalLen )
{
attrPtr = msgPtr;
attrType = *attrPtr++;
attrLen = *attrPtr++;
msgPtr += attrLen;
parsed += attrLen;
if( (attrLen < 2) || (parsed > totalLen) )
{
throw VRadiusException( "Invalid Attribute length",
__FILE__,
__LINE__ );
}
if( attrType == RA_VENDOR_SPECIFIC ) // VSA
{
// Vendor-Specific Attribute format
//
// 0 1 2 3
// 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
// | Type | Length | Vendor-Id
// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
// Vendor-Id (cont) | String...
// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-
if( attrLen > 8 )
{
// Extract Vendor-Id
vendorId = ntohl( *(reinterpret_cast< const u_int32_t * >(attrPtr)) );
attrPtr += 4; // Move pointer to Vendor type
// Exclude Type, Length and Vendor-Id
RadiusData rdat( attrPtr, attrLen - 6 );
RadiusAttribute attr( attrType, rdat, vendorId );
myAttributes.push_back( attr );
// Multiple subattributes may be encoded within a single
// Vendor-Specific attribute. They should further be decoded
// by a vendor-specific application.
}
else
{
throw VRadiusException( "VSA has invalid length",
__FILE__,
__LINE__ );
}
}
else
{
// Attribute format
//
// 0 1 2
// 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0
// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-
// | Type | Length | Value ...
// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-
if( attrType == RA_USER_PASSWORD )
{
if( myData.msgHdr.code != RP_ACCESS_REQUEST )
{
throw VRadiusException( "User-Password Attribute in Access-Request",
__FILE__,
__LINE__ );
}
if( ((attrLen-2)%16) != 0 )
{
throw VRadiusException( "Invalid User-Password Attribute Length",
__FILE__,
__LINE__ );
}
// Decode password
RadiusData hiddenPassword( attrPtr, attrLen - 2 );
u_int8_t decodedPassword[ 256 ];
decodeUserPassword( decodedPassword, secret, hiddenPassword );
// Store decoded password in attribute list
RadiusData password( decodedPassword, attrLen - 2 );
RadiusAttribute attr( attrType, password );
myAttributes.push_back( attr );
}
else
{
// Exclude Type and Length
RadiusData rdat( attrPtr, attrLen - 2 );
RadiusAttribute attr( attrType, rdat );
myAttributes.push_back( attr );
}
}
}
}
bool
RadiusMessage::verifyAccountingRequestAuthenticator( const char* secret )
{
cpLog( LOG_DEBUG_STACK, "Verify Accounting Request Authenticator" );
assert( myData.msgHdr.code == RP_ACCOUNTING_REQUEST );
// Copy of Authenticator
u_int8_t auth[ RadiusAuthenticatorLength ];
memcpy( auth, myData.msgHdr.authenticator, RadiusAuthenticatorLength );
// Re-calculate
calcAccountingRequestAuthenticator( secret );
// Compare
return( 0 == memcmp( auth,
myData.msgHdr.authenticator,
RadiusAuthenticatorLength ) );
}
bool
RadiusMessage::verifyResponseAuthenticator( const u_int8_t *reqAuth,
const char* secret )
{
cpLog( LOG_DEBUG_STACK, "Verify Response Authenticator" );
assert( myData.msgHdr.code == RP_ACCESS_ACCEPT ||
myData.msgHdr.code == RP_ACCESS_REJECT ||
myData.msgHdr.code == RP_ACCOUNTING_RESPONSE );
// Copy Request Authenticator
memcpy( myRequestAuthenticator, reqAuth, RadiusAuthenticatorLength );
// Copy Response Authenticator
u_int8_t respAuth[ RadiusAuthenticatorLength ];
memcpy( respAuth, myData.msgHdr.authenticator, RadiusAuthenticatorLength );
// Re-calculate
calcResponseAuthenticator( secret );
return( 0 == memcmp( respAuth,
myData.msgHdr.authenticator,
RadiusAuthenticatorLength ) );
}
bool
RadiusMessage::add( const RadiusAttribute& attr )
{
cpLog( LOG_DEBUG_STACK, "Add Attribute of type %d", attr.type() );
u_int16_t attrLen = attr.length();
u_int16_t currLen = ntohs( myData.msgHdr.length );
if( (currLen + attrLen) > RadiusMaxPacketSize )
{
cpLog( LOG_ERR, "Cannot add %d octets, current length is %d",
attrLen, myData.msgHdr.length );
return false;
}
memcpy( myData.buffer + currLen,
attr.encode().data() ,
attrLen );
myData.msgHdr.length = htons( currLen + attrLen );
myAttributes.push_back( attr );
return true;
}
/** Encode User-Password in Access-Request according to RFC 2865 Section 5.2
Call the shared secret S and the pseudo-random 128-bit Request
Authenticator RA. Break the password into 16-octet chunks p1, p2,
etc. with the last one padded at the end with nulls to a 16-octet
boundary. Call the ciphertext blocks c(1), c(2), etc. We'll need
intermediate values b1, b2, etc.
b1 = MD5(S + RA) c(1) = p1 xor b1
b2 = MD5(S + c(1)) c(2) = p2 xor b2
. .
. .
. .
bi = MD5(S + c(i-1)) c(i) = pi xor bi
The String will contain c(1)+c(2)+...+c(i) where + denotes
concatenation.
*/
void
RadiusMessage::encodeUserPassword( u_int8_t* newPassword,
const char* secret,
const RadiusData& oldPassword )
{
cpLog( LOG_DEBUG_STACK, "Encode User-Password" );
int secretLen = strlen(secret);
assert( secretLen < 256 );
int hashLen = strlen(secret) + RadiusAuthenticatorLength;
u_int8_t hashInput[ 256 + RadiusAuthenticatorLength ];
// Secret + Request Authenticator
memcpy( hashInput, secret, secretLen );
memcpy( hashInput + secretLen,
myData.msgHdr.authenticator,
RadiusAuthenticatorLength );
assert( (oldPassword.length() % 16) == 0 );
const u_int8_t* p = oldPassword.data();
u_int8_t b[ 16 ];
unsigned int j;
int i;
for( i = 0; i < oldPassword.length(); i += sizeof(b) )
{
if( i )
{
memcpy( hashInput + secretLen,
newPassword + (i - sizeof(b)),
sizeof(b) );
}
calcMD5( b, hashInput, hashLen );
for( j = 0; j < sizeof(b); ++j )
{
newPassword[i+j] = *(p+i+j) ^ b[j];
}
}
}
/** Decode User-Password in Access-Request
Reversal of encodeUserPassword above
*/
void
RadiusMessage::decodeUserPassword( u_int8_t* newPassword,
const char* secret,
const RadiusData& oldPassword )
{
cpLog( LOG_DEBUG_STACK, "Decode User-Password" );
int secretLen = strlen(secret);
assert( secretLen < 256 );
int hashLen = strlen(secret) + RadiusAuthenticatorLength;
u_int8_t hashInput[ 256 + RadiusAuthenticatorLength ];
memcpy( hashInput, secret, secretLen );
assert( (oldPassword.length() % 16) == 0 );
const u_int8_t* p = oldPassword.data();
u_int8_t b[ 16 ];
unsigned int j;
int i;
for( i = oldPassword.length() - sizeof(b); i >= 0; i -= sizeof(b) )
{
if( i )
{
memcpy( hashInput + secretLen,
p + (i - sizeof(b)),
sizeof(b) );
}
else
{
memcpy( hashInput + secretLen,
myData.msgHdr.authenticator,
RadiusAuthenticatorLength );
}
calcMD5( b, hashInput, hashLen );
for( j = 0; j < sizeof(b); ++j )
{
newPassword[i+j] = *(p+i+j) ^ b[j];
}
}
}
/** Hide User-Password in Access-Request
*/
void
RadiusMessage::hideUserPassword( const char* secret )
{
cpLog( LOG_DEBUG_STACK, "Hide User-Password" );
assert( myData.msgHdr.code == RP_ACCESS_REQUEST );
RadiusData password;
try
{
password = get( RA_USER_PASSWORD ).value();
}
catch( VRadiusException& e )
{
cpLog( LOG_WARNING, "%s", e.getDescription().c_str() );
return;
}
u_int8_t hiddenPassword[ 256 ];
encodeUserPassword( hiddenPassword, secret, password );
// Replace password in raw message with hidden password
int offset = RadiusPacketHeaderSize;
for ( RadiusAttrIter itr = myAttributes.begin();
itr != myAttributes.end();
itr++ )
{
if( itr->type() == RA_USER_PASSWORD )
{
memcpy( myData.buffer + offset + 2,
hiddenPassword,
password.length() );
}
else
{
offset += itr->length();
}
}
}
/** Calculate and set the Authenticator
Must be called right before the packet is sent
*/
void
RadiusMessage::calcAuthenticator( const char* secret )
{
switch( myData.msgHdr.code )
{
case RP_ACCESS_REQUEST:
{
calcAccessRequestAuthenticator();
hideUserPassword( secret );
break;
}
case RP_ACCESS_ACCEPT:
case RP_ACCESS_REJECT:
case RP_ACCOUNTING_RESPONSE:
case RP_ACCESS_CHALLENGE:
{
calcResponseAuthenticator( secret );
break;
}
case RP_ACCOUNTING_REQUEST:
{
calcAccountingRequestAuthenticator( secret );
break;
}
default:
{
cpLog( LOG_ERR, "Cannot calculate Authenticator for code %d",
myData.msgHdr.code );
assert( 0 );
}
}
}
/** Calculate and set the myRequestAuthenticator for Access-Request packets
*/
void
RadiusMessage::calcAccessRequestAuthenticator()
{
// Generate a global and temporal unique Authenticator
// Request Authenticator = MD5( time + ip + id )
try
{
u_int8_t hashInput[ 256 ];
unsigned int t = time( NULL );
memcpy( hashInput, &t, sizeof(t) );
int hashLen = sizeof(t);
memcpy( hashInput+hashLen,
get( RA_NAS_IP_ADDRESS ).value().data(),
get( RA_NAS_IP_ADDRESS ).value().length() );
hashLen += get( RA_NAS_IP_ADDRESS ).value().length();
memcpy( hashInput+hashLen,
&myData.msgHdr.identifier,
sizeof(myData.msgHdr.identifier) );
hashLen += sizeof(myData.msgHdr.identifier);
// Calculate
calcMD5( myRequestAuthenticator, hashInput, hashLen );
// Set Authenticator in Access Request raw message
memcpy( myData.msgHdr.authenticator,
myRequestAuthenticator,
RadiusAuthenticatorLength );
}
catch( VRadiusException &e )
{
cpLog( LOG_ERR, "Calculate Access Request Authenticator: %s",
e.getDescription().c_str() );
}
}
/** Calculate and set the Authenticator for Response (Access-Accept,
Access-Reject, Access-Challenge or Accounting Response) packets
*/
void
RadiusMessage::calcResponseAuthenticator( const char* secret )
{
// See RFC 2865 Section 3 and RFC 2866 Section 3
// MD5( Code + ID + Length + myRequestAuthenticator + Attributes + Secret )
u_int16_t msgLen = ntohs( myData.msgHdr.length );
// TODO: make buffer bigger to accommodate secret
assert( (msgLen + strlen(secret)) <= RadiusMaxPacketSize );
// Load original request's Authenticator
memcpy( myData.msgHdr.authenticator,
myRequestAuthenticator,
RadiusAuthenticatorLength );
// Load secret behind message
memcpy( myData.buffer + msgLen, secret, strlen(secret) );
u_int8_t auth[ RadiusAuthenticatorLength ];
// Calculate
calcMD5( auth, myData.buffer, msgLen + strlen(secret) );
// Set Authenticator in response packet
memcpy( myData.msgHdr.authenticator,
auth,
RadiusAuthenticatorLength );
}
/** Calculate and set the Authenticator for Accounting request packets
*/
void
RadiusMessage::calcAccountingRequestAuthenticator( const char* secret )
{
// See RFC 2866 Section 3
// MD5( Code + ID + Length + 16 zero octets + Attributes + Secret )
cpLog( LOG_DEBUG_STACK, "Calculate Accounting Request Authenticator" );
u_int32_t msgLen = ntohs( myData.msgHdr.length );
assert( (msgLen + strlen(secret)) <= RadiusMaxPacketSize );
memset( myData.msgHdr.authenticator, 0, RadiusAuthenticatorLength );
// Load secret behind message
memcpy( myData.buffer + msgLen, secret, strlen(secret) );
// Calculate
calcMD5( myRequestAuthenticator, myData.buffer, msgLen + strlen(secret) );
// Set Authenticator in request packet
memcpy( myData.msgHdr.authenticator,
myRequestAuthenticator,
RadiusAuthenticatorLength );
}
void
RadiusMessage::calcMD5( u_int8_t *digest,
const u_int8_t *buf,
const u_int32_t bufLen )
{
cpLog( LOG_DEBUG_STACK, "Calculate MD5" );
MD5Context context;
MD5Init( &context );
MD5Update( &context, buf, bufLen );
MD5Final( digest, &context );
}
RadiusData
RadiusMessage::encodeAttributes() const
{
RadiusData msg;
for( RadiusAttrIter itr = myAttributes.begin();
itr != myAttributes.end();
itr++ )
{
msg += (*itr).encode();
}
return msg;
}
const RadiusAttribute&
RadiusMessage::get( const RadiusAttributeType t ) const
throw( VRadiusException& )
{
for ( RadiusAttrIter itr = myAttributes.begin();
itr != myAttributes.end();
itr++ )
{
if( itr->type() == t )
{
// Found one
return *itr;
}
}
string e( "No attributes of type " + itos( t ) + " found in Attribute list" );
// Not found
throw VRadiusException( e, __FILE__, __LINE__ );
}
list< RadiusAttribute >
RadiusMessage::getAll( const RadiusAttributeType t ) const
{
list< RadiusAttribute > attrList;
for ( RadiusAttrIter itr = myAttributes.begin();
itr != myAttributes.end();
itr++ )
{
if( itr->type() == t )
{
attrList.push_back( *itr );
}
}
return attrList;
}
// Get a human readable representation of the message
string
RadiusMessage::verbose() const
{
return string( hexDump() + headerDump() + attributesVerbose() );
}
// Raw message in hex for debugging
string
RadiusMessage::hexDump() const
{
string s; // Complete output (all lines)
string a; // "Ascii" portion of a line
u_int8_t temp; // One octet
u_int8_t hi; // High hex digit of an octet
u_int8_t low; // Low hex digit of an octet
char buf[4]; // Output buffer for one octet
char cntBuf[6]; // Output buffer for counter at the beginning of a line
int msgLen = ntohs(myData.msgHdr.length);
int i;
for( i = 0; i < msgLen; i++ )
{
if( 0 == (i % 16) ) // 16 in a line
{
snprintf( cntBuf, 6, "%04x ", i );
s += a + "\n" + cntBuf;
a = " ";
}
temp = myData.buffer[i];
hi = (temp & 0xf0) / 16;
low = (temp & 0xf);
snprintf( buf, 4, " %x%x", hi, low );
if( temp < 0x20 || temp > 0x7E)
{
a += ".";
}
else
{
a += reinterpret_cast<u_char>(temp);
}
s += buf;
}
// Last line only:
// Pad with ' ' to make the "ascii" interpreted part line up with
// the previous line
int j = i % 16;
string b; // Blanks
if( j )
{
b = string( (16-j)*3, ' ' );
}
s += b + a + "\n";
return s;
}
// Get a human readable representation of the message header
string
RadiusMessage::headerDump() const
{
string codeStr;
switch( myData.msgHdr.code )
{
case RP_ACCESS_REQUEST:
{
codeStr = "Access-Request (1)";
break;
}
case RP_ACCESS_ACCEPT:
{
codeStr = "Access-Accept (2)";
break;
}
case RP_ACCESS_REJECT:
{
codeStr = "Access-Reject (3)";
break;
}
case RP_ACCOUNTING_REQUEST:
{
codeStr = "Accounting-Request (4)";
break;
}
case RP_ACCOUNTING_RESPONSE:
{
codeStr = "Accounting-Response (5)";
break;
}
case RP_ACCESS_CHALLENGE:
{
codeStr = "Access-Challenge (11)";
break;
}
default:
{
codeStr = "Unknown (" + itos( myData.msgHdr.code ) + ")";
}
}
return string( "\n 1 Code = " + codeStr +
"\n 1 ID = " + itos( myData.msgHdr.identifier ) +
"\n 2 Length = " + itos( ntohs(myData.msgHdr.length) ) +
"\n 16 Authenticator\n" );
}
string
RadiusMessage::attributesVerbose() const
{
string s = "\nAttributes:\n";
for ( RadiusAttrIter itr = myAttributes.begin();
itr != myAttributes.end();
itr++ )
{
s += itr->verbose();
}
return s;
}
// Local Variables:
// mode:c++
// c-file-style:"bsd"
// c-basic-offset:4
// c-file-offsets:((inclass . ++))
// indent-tabs-mode:nil
// End:
syntax highlighted by Code2HTML, v. 0.9.1