RADIUS Client Programming Guide


Constructor

Authentication and Accounting

ClientReceiveException and Retries

Attributes

Setting Attributes

Retrieving Attributes

VendorSpecific Class

Message-Authenticator

Miscellaneous Methods

Other Classes and Methods

PAP Authentication

CHAP Authentication

MS-CHAP Authentication

MS-CHAP V2 Authentication

EAP & EAP-MD5

LEAP (Lightweight EAP)

Digest Authentication

Disconnect and Change of Authorization Messages

(JAAS) LoginModule

Radtest.java

Testing Problems

RFC's & Other documents


RADIUS Client

The RADIUS client is a relatively new type of software.  Most RADIUS server provide a test client that performs a simple authentication to test a server.  General purpose clients in PERL, C, and Java have been making their appearance lately.

The RADIUS client request has four operational parts, the constructor, building the attribute list, sending/receiving the packet, and interpreting the results.

 


Constructor

There are several constructors but three are really just convenience versions of

RADIUSClient(InetAddress rHost,
                    int port,
                    java.lang.String secret,
                    int timeout)

The rHost is the RADIUS server host address.

The port depends on the particular operation and lineage of the server. RADIUS servers conforming to the RFC's use the ports 1812 and 1813 for authentication and accounting respectively. Servers based on old code will use 1645 and 1646 respectively.  If there are multiple servers on the same machine they will have different ports.  Picking the wrong port is one source of connectivity problems.

The secret is a string shared between the client and server.  This is used to encode passwords and validate responses. If the secret is different one each machine the main problem is that valid passwords will not work on the server.  Since the secret's are different they'll encode and decode differently.

The time out value should be set by experimentation. It's possible that a packet may take several seconds to make a round trip across a number of proxy servers and authentication to take place.  It's also possible on a local network it can take only tens of milliseconds for the trip and full authentication. One second is probably a good minimum, but if debugging the timeout should be longer to reduce false negatives in tests.

The RADIUS protocol requires that a fresh request use a unique packet message identifier and unique port number. Retries must keep the same message identifier and port number. This aids in detecting duplicate packets. These details are handled by the client but are controlled by your code. 

After the constructor is called the first packet behaves properly. If a process is performing a number of sequential authentications it may use the same constructor but must use the reset()method before the next authentication or accounting packet is sent.  If, however, a request has timed out, the retry loop must not use the reset()method.

Top


Authentication and Accounting

There are three authentication methods.

Common properties of authentication and accounting: All methods return an int indicating the response packet type.  For the first two methods below this will be one of Access_Accept, Access_Reject, Access_Challenge or Access_BadPacket. The last, Access_BadPacket is returned if the returned authenticator is incorrect or the attributes could not be extracted.

The client may also throw an IOException.  This occurs if there was a problem sending the packet or a timeout waiting for a response packet.  For those who need to know which failed catch both ClientSendException and ClientReceiveException.

RADIUS servers generally expect either an NAS-IP-Address or NAS-Identifier attribute identifying the client.

public int authenticate(String name,String password, AttributeList alist)

Authenticate with a given name, password and list of attributes.  This is probably the most common authentication method used.  The AttributeList must not contain the User-Name or User-Password as they are generated automatically.

public int authenticate(AttributeList alist)

Authenticate with a list of Attributes. This authenticates with whatever attributes are contained in the AttributeList. This method would be used when sending a CHAP-Password attribute, for example. To encode a password see the RADIUSEncrypt.encrypt() method.

public int authenticate(int packetType, AttributeList alist)

Authenticate with a given packet type and a list of attributes.  The packet type may be Access-Request, but would usually be one of the extended packet types like Disconnect_Request,  Event_Request, and so on from RFC 2882 (Extended RADIUS Practices).

public int accounting(AttributeList alist)

Perform an accounting action given the attributes in the attribute list.

public int accounting(int packetType, AttributeList alist)

Send a packet to the accounting server with the given packet type and attributes. The packet type may be Accounting-Request, but would usually be one of the extended packet types  from RFC 2882 (Extended RADIUS Practices).

Top


ClientReceiveException and Retries

Packets sent by the RADIUS client may not be received by the server, or the server may not answer the client, or the server be busy and answer the client much too late. The RADIUS protocol uses an unreliable transport, UDP (User Datagram Protocol) which is not guaranteed to make it to it's destination. A time out is used by the client to determine if a packet is late in arriving. This timeout is either the default time out or once specified in the constructor.

When the time has expired waiting for a response the client throws a ClientReceiveException. While this exception may be caused by other problems all merit a retry attempt. The AXL RADIUS client does not automatically retry a packet. It does provide a retry() method. The length of the timeout and the number of reties is left to the programmer. Some systems use a six second timeout and as many as four retries.

Here are the RADIUS RFC rules for handling retries:

The RADIUS client simplifies this by sending the original packet to the server using the retry() method. An example of retry code is found in the source com/theorem/radius3/examples/code/RetryTest.java.

Note that the RADIUSClient constructor can be re-used for multiple authentication and accounting requests. The method reset() must be used between each reuse of the RADIUSClient to change the authenticator, packet identifier, socket and port number. If you're reusing the AttributeList it must be cleared and rebuilt or you'll find duplicate attributes being sent to the server.


Attributes

RADIUS clients and servers exchange information through TLV (Type-Length-Value) attributes.  TLV means the attribute has a tag indicating it's type (NAS-IP-Address), the length of the entire attribute, and the attribute's value.   In general the shortest attribute is six bytes with the data being a number four bytes long.  Longer attributes will hold string or binary information.  Each attribute is limited to a length of 255 bytes.

The default maximum packet length is 4096 bytes including a 20 byte header leaving 4076 bytes for the attribute data. The AXL Software client is able to enlarge the packet size to accommodate more large attributes but the server must also be able to handle larger packets.

There are some basic types.  The length refers to the attribute value's length

In addition there is the Vendor-Specific attribute which deserves it's own description. Just as the RADIUS packet contains attributes, the Vendor-Specific attribute also contains attributes within it's data area.  This has not always been the case as the data value in the past was considered an opaque value whose formatting was known to the vendor. Better ideas have prevailed and this attribute now has structure anyone can create or disassemble.  There is a special VendorSpecific class that handles these attributes.

Collections of attributes are held in an AttributeList class, which holds a collection of Attribute classes. The AttributeList has methods for finding, counting, accessing, and creating all the types of attributes listed above.

Top


Setting Attributes

Attributes are generally handled by the AttributeList class.  It can create all the types of attributes mentioned above, IP address, integer, strings, and so on. Using the constructor:

AttributeList alist = new AttributeList();

To add an IP address as an NAS-IP-Address attribute:
InetAddress ourIP = InetAddress.getByName("192.168.1.1");
alist.addAttribute(Attribute.NAS_IP_Address, ourIP);

To add an integer as a NAS-Port:
alist.addAttribute(Attribute.NAS_Port, 3);
or as a Service-Type of
alist.addAttribute(Attribute.Service_Type, AV.Login);

To add a string as a User-Name:
alist.addAttribute(Attribute.User_Name, "michael");

To add a binary value: version of the User-Name:
byte [] un = "michael".getBytes();
alist.addAttribute(Attribute.User_Name, un);

Create tunneling attributes using:
alist.addAttribute(int tag, int tunnelTag, <data type>) methods.
final int L2TP = 3;
alist.addAttribute(Attribute.Tunnel_Type, 0, L2TP);

Top


Retrieving Attributes

Once authenticated (or accounted) attributes will be returned from the server.   Here's how to access them:

RADIUSClient rc = new RADIUSClient(...);
...
int result = rc.authenticate(...);
AttributeList responseList = rc.getAttributes();

To see if an attribute type exists:
if (responseList.exists(Attribute.Session_Timeout) { ...

Get all attributes of certain type.  In this case we must return all State attributes, so collect them:
AttributeList stateList = responseList.getAttributeList(Attribute.State);

You can get the values of attributes, either the first (and perhaps the only one) or a group of attribute values:
if (responseList.size(Attribute.State) > 1) {
byte stateValues[][] = responseList.getAllBinaryAttributes(Attribute.State);
...
} else if (responseList.size(Attribute.State) == 1) {
byte stateValue[] = responseList.getBinaryAttribute(Attribute.State);
...
}

Get a string attribute:
String name = rList.getStringAttribute(Attribute.User_Name);

Get an IP address:
InetAddress ip = rList.getIPAttribute(Attribute.NAS_IP_Address);

Attribute lists may be merged.  In the example above where the State attributes are collected they may be merged with the new request attributes (requestList):
requestList.clearAttributes();  // Clear previous request attributes.
requestList.mergeAttributes(stateList);

Attribute lists can also display themselves in a human readable form using the AttributeList.toString() method.  Individual attributes (Attribute) can also display themselves.

Tunnel attributes are retrieved this way:
Attribute tt = new Attribute(responseList.getBinaryAttribute(Attribute.Tunnel_Type);
// Force a conversion to a tunnel attribute.
tt.convertToTunnel();

Top


VendorSpecific Class

The VendorSpecific class aids in creating and interpreting Vendor-Specific attributes (VSA).   Some contain raw data (like a string or integer value).  Others vendors conserve how many Vendor-Specific attributes they must use by placing their attributes within the data portion of the attribute.  This requires some extra work in extracting these attributes, which is performed by this class.  Don't confuse a Vendor-Specific attribute with Vendor Proprietary attributes, such as #21, Password-Expiration.  The latter are regular RADIUS attributes.

Although you can place multiple vendor sub-attributes in a VSA it is not recommended unless you have tested this against your server(s). Some servers accept multiple sub-attributes, some do not. To be safe place only one sub-attribute in each VSA and use multiple VSA's to pass multiple sub-attributes.

Some vendors are using longer attribute tags. Normally tags are 8 bits long giving 255 legal values. The new tags are 16 bits long giving 65535 legal values. Tags with a value of 0 are considered invalid. The VendorSpecific class can handle these by informing the

VendorSpecific extends the AttributeList class which allows all getX() and add() methods in AttributeList to be used.

// Example of using the VendorSpecific class.
// This example assumes there is only
// one possible Vendor-Specific attribute in the response.

Attribute a = responseList.getBinaryAttribute(Radius.Vendor_Specific);
VendorSpecific vs = new VendorSpecific(a);

timeoutX = vs.getInt(1);    // Get vendor attribute tag #1 as an int.
prompt = vs.getString(2);   // Get vendor attribute tag #2 as a string.


To extract all VSA's of a certain type use this method:

// Extract all Microsoft MS-CHAP-Challenge attributes from all Microsoft VSA's.
// Of course there must only be one, but there may be multiple MS VSA's.
Attribute a[] = responseList.getVendorSpecific(Microsoft.VENDORID,
	Microsoft.MS_CHAP_Challenge);
if (a.length != 1)
	throw new Exception("Missing MS-CHAP-Challenge");
byte chapChallenge[] = a[0].getAttributeData();

Vendor-Specific attributes can be created as well.

    RADIUSClient r = new RADIUSClient(...);
    AttributeList alist = new AttributeList(r);

    // Create sub-attributes within a vendor specific attribute.
    // the attribute types will probably not follow the standard
    // RADIUS types (e.g. #1, User-Name, may be something different
    // to a vendor.
    VendorSpecific vs = new VendorSpecific(12345);
    vs.add(1, "Attribute 1");
    vs.add(2, "Attribute 2");
    vs.add(34, 512);
    vs.add(21, Inetaddress.getByName("127.0.0.1"));

    alist.add(vs.getAttribute());

    // Create an single valued Vendor-Specific attribute. The
    // number 34567 is the Vendor Identifier.
    vs = new VendorSpecific(34567);
    vs.setData("Hello World".getBytes());

    alist.add(vs.getAttribute());

    r.authenticate(..., alist);

Response attributes can be converted to Vendor-Specific this way:

      r.authenticate(...);
    AttributeList alist = r.getAttributeList();
    Attribute a =
alist.getBinaryAttribute(A.Vendor_Specific);
    VendorSpecific vs = new VendorSpecific(a);

Special note for the Cisco Vendor-Specific attribute:  The VSA's are set in the following way:  Set the attribute type to the h323 type, e.g. CISCO.hH323_Credit_Amount.  Set the value of the attribute to be the string "H323-Credit-Amount=1000". The string is referred to by CISCO as an av-pair or cisco-avpair in the following general RADIUS CISCO documentation.. This format is a remanent of the TACACS protocol that used strings rather than the more compact attributes used by RADIUS. More extensive information on the H323 extensions can be found here or by searching for H323 on Cisco's search page.  Here's an example:

VendorSpecific vs = new VendorSpecific(Cisco.VENDORID);
vs.addAttribute(Cisco.h323_credit_time, "h323-credit-amount=32");

or always use the Cisco.ciscoav instead of specific attribute type:

vs.addAttribute(Cisco.ciscoav, "h323-credit-amount=32");

Encryption of VSA sub-attributes: Cisco allows several forms of sub-attribute encryption. The only method supported is the Cisco Encrypted String VSA Format also supported by an IETF draft for Salt-Encryption of RADIUS Attributes.

Long attribute tags: One vendor (Ascend) has exhausted their VSA attribute space and has started using 16 bit tag values instead of the standard 8 bit tags. These are supported using the constructors VendorSpecific(Attribute vs, boolean longTags) and

Top


Message-Authenticator Attribute

The Message-Authenticator is calculated within the RADIUS client core functions.   It can only be calculated for authentication packets, not for accounting packets.   There are two ways to include a Message-Authenticator attribute: either as an empty sixteen byte value or as an empty attribute.

requestList.addAttribute(Attribute.Message_Authenticator, new byte[16]);
or
requestList.addAttribute(Attribute.Message_Authenticator);

The Message-Authenticator is returned with the response packet.  It is internally checked for correctness.  If it is not correct the authenticate() method will return Access_BadPacket.

Top

Miscellaneous RADIUSClient methods

void bind(InetAddress localAddr)

Binds the client to a particular address.  If not used the client will bind itself to the first available address on a multi-homed machine.  This can be a problem on a multi-homed machine since the RADIUS server may not recognize the client's source address or the source address may differ from the NAS-IP-Address or NAS-Identifier attribute.

void close()

Close the client.  This closes the socket.

AttributeList getAttributes()

Get the attribute list returned from a response.

void reset()

If a client construct is to be used to send a new authentication or accounting request this method must be called before either the authenticate() or account() methods are called.  Do not use this if performing reties for a packet that has timed out (IOException).

void setMaximumPacketSize(int maxPacketSize)

The default packet size for a RADIUS packet is 4096 bytes.  This value can be expanded or contracted using this method.  The RADIUS server must also accept the larger packet size or it will truncate the request packets to it's packet size value.

String toString()

Displays the client and it's connection.

byte [] getBytes(String str)

Get the UTF8 encoding of a string.  Using the String.getBytes() method returns the ASCII encoding which can render some bytes as question marks ('?').

byte [] createChallenge16()

Create a sixteen byte random challenge value for some authentication methods.

byte [] createChallenge8()

Create a eight byte random challenge value for some authentication methods.

byte [] getSecret()

Retrieve the secret RADIUS shared between the client and server.


Other Classes and Methods

PacketType.getName(int packetType)

Return the string name for the packet type result returned from authenticate() or account().  For example the type Accounting-Response would be returned as the string Accounting-Response(5).
System.out.println("Packet type for 5: " + new PacketType().getName(5));

AV Class

AV holds a number of attribute value constants such as AV.Start, AV.Stop for accounting, e.g.
alist.addAttribute(Attribute.Acct_Status_Type, AV.Start);

AV.lookup()

Returns a string representing an attribute's value. For example:
int statusType = requestList.getInt(Attribute.Acct_Status_Type);
System.out.println("Accounting status type is:" + AV.lookup(statusType));

Cisco Class

The Cisco class holds the Vendor ID for the Cisco Vendor-Specific attribute as well as values.  Cisco VS attributes are built like this:
VendorSpecific vs = new VendorSpecific(Cisco.VENDORID);
vs.addAttribute(Cisco.h323_credit_time, "h323-credit-amount=32");

Microsoft Class

Microsoft Vendor-Specific class holding the MS Vendor ID and it's attribute types.

Top


PAP

PAP authentication is the simplest:

	AttributeList alist = new AttributeList();
	// Create necessary attributes.
	...

	// Authenticate.
	rclient.authenticate(name, password, alist);

Top


CHAP

CHAP authentication is performed by the client in the following way using the RADIUSClient.createCHAP() method.

	AttributeList alist = new AttributeList();
	// Create other necessary attributes.
	...
	// To send a CHAP challenge we need to explicitly set
	// the User-Name and create the CHAP challenge. 
	alist.addAttribute(Attribute.User_Name, "michael");

	// Get the UTF8 encoding of the password.
	byte password[]= rauth.getBytes("test");

	// Create the appropriate CHAP attributes.
	rauth.createCHAP(password, alist);

	// Authenticate.
	rclient.authenticate(alist);

Top


MS-CHAP

MS-CHAP authentication is similar to CHAP but Microsoft does it a little differently.

	AttributeList alist = new AttributeList();
	// Create other necessary attributes.
	...
	// To send an MS-CHAP challenge we need to explicitly set
	// the User-Name.
	alist.addAttribute(Attribute.User_Name, "michael");
	// Get the UTF8 encoding of the password.
	byte password[]= rauth.getBytes("test");

	// Create the appropriate MS-CHAP attributes.
	rauth.createMSCHAP(password, alist);

	// Authenticate.  The result we're looking for is
	// PacketType.Access_Accept.
	int result = rclient.authenticate(alist);

Top


MS-CHAP V2

MS-CHAP V2 is a little more involved than MS-CHAP and there is a future check on the response from the server. We need to provide more information, like the User-Name and password value.

	AttributeList alist = new AttributeList();
	// Create other necessary attributes.
	...

	// Get the UTF8 encoding of the password.
	byte password[]= rauth.getBytes("test");
	// Get the UTF8 encoding of the name.
	byte name[]= rauth.getBytes("michael");

	// To send an MS-CHAP challenge we need to explicitly set
	// the User-Name. 
	alist.addAttribute(Attribute.User_Name, name);
	

	// Create the appropriate MS-CHAP attributes.
	rauth.createMSCHAP2(name, password, alist);

	// Authenticate.
	int result = rclient.authenticate(alist);

	// Authenticate.  The result we're looking for is
	// PacketType.Access_Accept.
	if (result == PacketType.Access_Accept) {

		// See if the response is correct.
		boolean cmp = rauth.cmpMSCHAP2(name.getBytes(),
			 password.getBytes(), alist);
		if (cmp == false)
			// Failed to verify the server's response.
		else
			// All is well.
	}

Top

 


EAP Client & EAPMD5 Client

The EAPMD5 client is a complete EAP MD5 authentication implementation.  It acts as a wrapper around the RADIUSClient class.

The EAPClient class holds base methods for handling EAP transactions and can be extended for other EAP authentication methods.

The EAPClient's constructor sets up the basic information to contact the server and provide standard attributes to accompany the EAP transaction - User-Name, NAS-IP-Address / NAS-Identifier, Message-Authenticator, and all EAP-Message attributes generated.

Methods:

addAttributes()

Add additional attributes to the RADIUS client EAP response.  These would be hints to the server about the kind of service requested - it must not include any of the attributes listed above in the EAPClient constructor description.

createPacketId()

Create a randomly generated initial packet identifier.  This is used on the first packet that's sent to the RADIUS server.

debug(boolean enable)

Enable or disable debugging - this enables the debugging output of the RADIUS client.

getAttributes()

After a packet is sent to the server any response attributes can be retrieved using this method.

getEAPPacket()

Retrieve the EAPPacket object containing the EAP data from the server.  The code, type, and data fields are available using the EAPPacket class.

reset(byte[] name)

Reset the EAPClient with a new identity (User-Name attribute).  This resets the RADIUS client and initializes the EAPClient with a new name for the Identity packet.

send(AttributeList requestList)

Send the completed EAP packet through the RADIUS client.  The requestList is created by an  EAPClient method like createIdentityResponse() or createMD5Response().

The EAPMD5Client is an example of a simple EAP transaction.  It first sends the Identity response to identify the entity. The server responds with an EAP MD5 challenge packet.  The password encoded using the challenge and packet id is sent to the server.  The server responds with success or failure. The source is included to aid in producing different EAP clients.

The EAPMD5Client constructor is the same as the EAPClient constructor.

authenticate(byte[] password)

This method is used to authenticate using the name passed in the constructor.    If you're creating an instance of the EAPMD5Client for each authentication this is the method to use.  This is safe to use for retries using the same name.

authenticate(byte[] name, byte[] password)

If the class instance is to be used more than once for different entities use this method for subsequent authentications.

setAttributes(AttributeList local)

Add additional attributes to the RADIUS client EAP response.  These would be hints to the server about the kind of service requested - it must not include any of the attributes listed above in the EAPClient constructor description.


LEAP (Cisco's Lightweight EAP)

Cisco™ introduced a mutual authentication method called LEAP. This is used to generate a session key to be used on wireless networks.

This is available as a RADIUS client module. To access this module import com.theorem.radserver3.module.* if using the AXL RADIUS Server or com.theorem.radius3.module.* for the AXL RADIUS Client distribution.

LEAPCLIENT leap = (LEAPCLIENT) rc.getModuleInstance("LEAPCLIENT", RADIUSClient rc)

The first parameter is the name of the module and the second is an existing RADIUSClient object. Do NOT use the LEAPCLIENT() constructor directly.

setUserName(String userName)

Set the User-Name attribute value. This may contain a realm. If it does contain a realm and the LEAP server doesn't accept realms use the next method.

setLEAPIdentity(String leapIdentity)

Set the EAP-LEAP identity. This is not necessary if this is the same as the User-Name attribute. This should only be used if the User-Name attribute contains a realm but the identity must not do so.

setCommonAttributes(AttributeList commonAttributes)

Set the attributes that should accompany each RADIUS packet used in the LEAP protocol. These attributes might be NAS-IP-Address or NAS-Identifier. The list must not contain a User-Name which will be created by the LEAPCLIENT module.

setPassword(byte[] password)

Set the password to be used by the LEAP protocol.

authenticate()

After all the necessary set up methods above have been invoked the authenticate() method attempts to perform the LEAP authentication. It will return TRUE if it succeeded. On success the next method can be called to recover the session key.

getSessionKey()

Recover the session key to be used to encode network transmissions.


Digest authentication

This form of authentication is defined in draft-sterman-aaa-sip-00.txt' and is used for SIP (Session Initiation Protocol) and HTTP authentication. Note that not all HTTP authentication is done through this mechanism. Many times the HTTP server will use something like PAP or CHAP.

First we have to say that this draft RFC uses a very strange format for the digest attributes. It uses a method simlilar to the Vendor-Specific attribute - there are multiple sub-attributes concatentated into possibly several Digest-Attributes (#207) attributes. Fortunately the ClientDigest performs this job for you.

The class com.theorem.radius3.auth.digest.client.ClientDigest is used to perform the authentication. It uses and existing RADIUSClient class. The ClientDigest class uses two methods of setting the configuration parameters. You can either use the various .set????() methods (like setRealm(String digestRealm)) or the set(int tag, String value) method.

For example:

	RADIUSClient rc = new RADIUSClient(...);
AttributeList common = new AttributeList();
common.addAttribute(Attribute.User_Name, "digest");
common.addAttribute(Attribute.NAS_Identifier, "DigestClient");
ClientDigest cda = new ClientDigest(rc, common);
cda.setRealm("deltathree");
cda.setNonce("3bada1a0");
cda.setMethod("INVITE");
cda.setURI("sip:97226491335@213.137.69.38");
cda.setAlgorithm("md5");
cda.setUserName("12345678");
cda.setResponse("939044714f373240de9fdc67fc61ab68");
int result = cda.authenticate();
System.out.println("Returned result " + new PacketType().getName(result));
System.out.println("Attributes:\n" + rc.getAttributes());

Disconnect and Change of Authorization Messages

These are messages sent from the server to a client. This requires that request messages travel in the reverse direction to normal traffic. The client must be able to receive asynchonous Disconnect-Requests and CoA-Requests at any time.

A RADIUS client may allow these messages by starting a DMCOA receiver.

RADIUSClient rc = new RADIUSClient(server, port, secret);
// Start the receiver using the same client as the sender is using.
DMCOAReceiver dr = new DMCOAReceiver(rc, 0, this);
dr.start();

The DMCOAReceiver class will issue a call back to the class implementing the DMCOACallback interface. In the above instance the class containing the above code implemented the DMCOACallback interface. The callback method DMCOACallBac.,dmcoaCallback(int packetType, AttributeList requestAttributes) is passed the message type (either Disconnect-Request or CoA-Request) and the request packet's attribute list. The response is created by populating the DMCOAResponseclass and returning it to the DMCOAReciever.

An example of this is found in the file com/theorem/radius3/examples/dmcoaclient/ClientDMCOA.java.


(JAAS) LoginModule

The RADIUS client can be used to perform logins using the class com.theorem.radius3.login.RADIUSLogin as an implementation of the javax.security.auth.spi.LoginModule. This requires JDK 1.4+ or JAAS to compile and run. There is an example of use in the directory com/theorem/radius3/examples/login.  The security configuration file is found in com/theorem/radius3/login/RADIUSLogin.config and LoginContext is RADIUSLogin.  The options include configuring the client as well as including any request attributes in the Access-Request packet.  The RADIUSLogin class requires the support of a CallBackHandler. There is an example of a CallbackHandler in the directory com/theorem/radserver3/examples/login.

There are a number of handlers for this class. A number are similarly to the defined callbacks:

The remainder facilitate fine grain control over the RADIUS session:

Unlike many CallbackHandler implementations the RADIUS CallBackHandler will be called more than once. All callbacks from the RADIUSLogin module use the same method to determine if the callback is active - isReady().  It this returns true the callback is active and the data for the specific callback may be read or written.  This mechanism allows the handler to be re-entrant.

Some Callback entries in the call back table will be null and should be ignored.   These handlers (RADIUSTextInputCallback, RADIUSTextOutputCallback, and RADIUSConfirmationCallback) will not necessarily have values until they're actually needed.

Here's an example of an AccessAccept callback and it's check for readiness.

public void handle(Callback[] callbacks) throws IOException, UnsupportedCallbackException
...
} else if (cb instanceof AccessAccept){
  // Just pick up the response attributes object for now.
  // It'll be populated after authentication.
  AccessAccept accessAccept = ((AccessAccept) cb);
  if (! accessAccept.isReady())
     continue;

  // Save the latest attributes.
  lastAttributes = accessAccept.getResponseAttributes();
}

The AccessChallenge class provides a method for introducing attributes suitable for a challenge response. The attributes provided by the RADIUSLogin.config are included in the Access-Challenge packet but not those provided by the SetRequestAttributes handler.

The ClientException handler is called whenever a client related exception is called.With two exceptions this is merely informative as the a LoginException will be thrown anyway.  If the ClientException's .getException() method returns an instance of ClientReceiveException or ClientSendException the CallbackHandler may request that the packet be resent. After some number of retries (maybe 3 to 10) the ClientException handler should stop attempting retries. Some ClientReceiveException's may be spurious if the server has received the packet but hasn't yet responded.  In this case the server may be dropping duplicate packets.

The file RADIUSLogin.config is the security configuration for the RADIUSLogin class.   It can either be used on the command line like this:  -Djava.security.auth.login.config==<path to the file>/login/RADIUSLogin.config

Or it can be assigned as a system property in a program.  This is an example.

For an example of a CallbackHandler implementation look at com/theorem/radius3/examples/login/RADIUSHandler.java.

If your system is unable to process the Login Module because the JDK/JVM is prior to version 1.4 you may remove it from the jar file using any ZIP utility.  If you are unable to compile it remove the contents of the login directory.

Top


Testing Problems

Here are some aspects to programming a RADIUS client that tend to have the same result - no reply from the server.

  1. The RADIUS client / server system uses the UDP protocol to communicate.  UDP is called a connectionless protocol.

    Connectionless because, unlike TCP, a solid connection is established before the packet is sent.  Using UDP the packet is simply sent to the server whether the server's listening or not.  Under TCP a firm connection is established with the server before data is sent.  If the connection fails the TCP protocol will let you know.  UDP is almost a shot in the dark.  All that's required is that the UDP packet can reach the server.
  2. UDP is also called an unreliable protocol, unreliable because the packet may be damaged or discarded by any system on route.   There is no way to know this has happened since unlike TCP, there are no checks to make sure packets arrived intact.  If the packet is discarded it will never reach the server. Conversely, a packet from the server may never reach the client.
  3. The server may not reply to an incorrectly formed packet, or a packet that contains incorrect information. The server is not required to let you know that there's something wrong.
  4. A missing or incorrect NAS-IP-Address / NAS-Identifier attribute will sometimes cause dropped packets.

Some hints for specific servers:

Top


Radtest.java

The example file, radtest.java, illustrates authentication and accounting.  The attributes used are not typical but are present to show a variety of attribute types.  It does not perform retries.

To use the radtest.class on *NIX  use
java -cp .:radclient3.jar radtest
On Windows use:
java -cp .;radclient3.jar radtest

The radtest.java file uses the radtest.properties file for configuration.


Requests For Comment (RFC's) & Other documentation

Other documentation:

Top