/**
* JRadius - A RADIUS Server Java Adapter
* Copyright (c) 2006-2009 Coova Technologies, LLC <support@coova.com>
* Copyright (C) 2004-2005 PicoPoint, B.V.
*
* This library is free software; you can redistribute it and/or modify it
* under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation; either version 2.1 of the License, or (at
* your option) any later version.
*
* This library is distributed in the hope that it will be useful, but
* WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
* or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public
* License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this library; if not, write to the Free Software Foundation,
* Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*
*/
package net.jradius.client;
import java.beans.Introspector;
import java.beans.PropertyDescriptor;
import java.io.IOException;
import java.lang.reflect.Method;
import java.net.DatagramSocket;
import java.net.InetAddress;
import java.net.SocketException;
import java.security.NoSuchAlgorithmException;
import java.util.HashMap;
import java.util.LinkedHashMap;
import net.jradius.client.auth.CHAPAuthenticator;
import net.jradius.client.auth.EAPMD5Authenticator;
import net.jradius.client.auth.EAPMSCHAPv2Authenticator;
import net.jradius.client.auth.MSCHAPv1Authenticator;
import net.jradius.client.auth.MSCHAPv2Authenticator;
import net.jradius.client.auth.PAPAuthenticator;
import net.jradius.client.auth.RadiusAuthenticator;
import net.jradius.exception.RadiusException;
import net.jradius.exception.UnknownAttributeException;
import net.jradius.log.RadiusLog;
import net.jradius.packet.AccessChallenge;
import net.jradius.packet.AccessRequest;
import net.jradius.packet.AccountingRequest;
import net.jradius.packet.AccountingResponse;
import net.jradius.packet.CoARequest;
import net.jradius.packet.CoAResponse;
import net.jradius.packet.DisconnectRequest;
import net.jradius.packet.DisconnectResponse;
import net.jradius.packet.RadiusRequest;
import net.jradius.packet.RadiusResponse;
import net.jradius.session.JRadiusSession;
/**
* A Radius Client Context
*
* @author David Bird
*/
public class RadiusClient
{
protected RadiusClientTransport transport;
protected static final LinkedHashMap<String, Class<?>> authenticators = new LinkedHashMap<String, Class<?>>();
protected JRadiusSession session;
static
{
// Supported Authentication Protocols
registerAuthenticator("pap", PAPAuthenticator.class);
registerAuthenticator("chap", CHAPAuthenticator.class);
registerAuthenticator("mschapv1", MSCHAPv1Authenticator.class);
registerAuthenticator("mschapv2", MSCHAPv2Authenticator.class);
registerAuthenticator("mschap", MSCHAPv2Authenticator.class);
registerAuthenticator("eap-md5", EAPMD5Authenticator.class);
registerAuthenticator("eap-mschapv2", EAPMSCHAPv2Authenticator.class);
try
{
registerAuthenticator("eap-tls", "net.jradius.client.auth.EAPTLSAuthenticator");
registerAuthenticator("eap-ttls", "net.jradius.client.auth.EAPTTLSAuthenticator");
registerAuthenticator("peap", "net.jradius.client.auth.PEAPAuthenticator");
//registerAuthenticator("eap-aka", "net.jradius.client.auth.EAPAKAAuthenticator");
}
catch (ClassNotFoundException e)
{
RadiusLog.warn("EAP-TLS and EAP-TTLS are only available with Java 1.5");
}
// Lets use the Gnu-Crypto Provider
//if (java.security.Security.getProvider("GNU-CRYPTO") == null)
//java.security.Security.addProvider(new gnu.crypto.jce.GnuCrypto());
}
/**
* Default constructor
* @throws IOException
*/
public RadiusClient() throws IOException
{
this.transport = new UDPClientTransport();
this.transport.setRadiusClient(this);
}
public RadiusClient(DatagramSocket socket)
{
this.transport = new UDPClientTransport(socket);
this.transport.setRadiusClient(this);
}
public RadiusClient(RadiusClientTransport transport)
{
this.transport = transport;
this.transport.setRadiusClient(this);
}
/**
* RadiusClient constructor
* @param address The Internet address to send to
* @param secret Our shared secret
* @throws IOException
* @throws RadiusException
*/
public RadiusClient(InetAddress address, String secret) throws IOException
{
this.transport = new UDPClientTransport();
this.transport.setRadiusClient(this);
setRemoteInetAddress(address);
setSharedSecret(secret);
}
public RadiusClient(DatagramSocket socket, InetAddress address, String secret)
{
this(socket);
setRemoteInetAddress(address);
setSharedSecret(secret);
}
/**
* RadiusClient constructor
* @param address The Internet address to send to
* @param secret Our shared secret
* @param authPort The authentication port
* @param acctPort The accounting port
* @param timeout Timeout (time to wait for a reply)
* @throws IOException
* @throws RadiusException
*/
public RadiusClient(InetAddress address, String secret, int authPort, int acctPort, int timeout) throws IOException
{
this.transport = new UDPClientTransport();
this.transport.setRadiusClient(this);
setRemoteInetAddress(address);
setSharedSecret(secret);
setAuthPort(authPort);
setAcctPort(acctPort);
setSocketTimeout(timeout);
}
public RadiusClient(DatagramSocket socket, InetAddress address, String secret, int authPort, int acctPort, int timeout) throws SocketException
{
this(socket);
setRemoteInetAddress(address);
setSharedSecret(secret);
setAuthPort(authPort);
setAcctPort(acctPort);
setSocketTimeout(timeout);
}
public void close()
{
if (transport != null)
transport.close();
}
/**
* Registration of supported RadiusAuthenticator protocols
* @param name The authentication protocol name
* @param c The RadiusAuthenticator class that implements the protocol
*/
public static void registerAuthenticator(String name, Class<?> c)
{
authenticators.put(name, c);
}
public static void registerAuthenticator(String name, String className) throws ClassNotFoundException
{
Class<?> c = Class.forName(className);
authenticators.put(name, c);
}
/**
* Get a supported RadiusAuthenticator based on the protocol name. If no
* protocol with that name is supported, null is returned. If the authenticator
* class for the named protocol has writable bean properties, these can be set by
* appending a colon separated list of property=value pairs to the protocolName.
* For instance, the EAP-TLS (and EAP-TTLS since it derives from EAP-TLS) authenticator
* class has numerous configurable properties (including keyFile, keyFileType, keyPassword, etc).
* <p>
* Examples:
* <ul>
* <li>getAuthProtocol("pap") returns PAPAuthenticator</li>
* <li>getAuthProtocol("chap") returns CHAPAuthenticator</li>
* <li>getAuthProtocol("eap-md5") returns EAPMD5Authenticator</li>
* <li>getAuthProtocol("eap-ttls") returns default EALTTLSAuthenticator</li>
* <li>getAuthProtocol("eap-tls:keyFile=keystore:keyPassword=mypass") returns EALTLSAuthenticator with setKeyFile("keystore") and setKeyPassword("mypass")</li>
* <li>getAuthProtocol("eap-ttls:trustAll=true") returns EALTTLSAuthenticator with setTrustAll(true)</li>
* </ul>
* Keep in mind that Java 1.5 is required for EAP-TLS/TTLS and only PAP is usable as the inner protocol
* because of limitations of Java 1.5.
* <p>
* @param protocolName The requested authentication protocol
* @return Returns an instance of RadiusAuthenticator or null
*/
public static RadiusAuthenticator getAuthProtocol(String protocolName)
{
RadiusAuthenticator auth = null;
String[] args = null;
int i;
if ((i = protocolName.indexOf(':')) > 0)
{
if (i < protocolName.length())
{
args = protocolName.substring(i + 1).split(":");
}
protocolName = protocolName.substring(0, i);
}
protocolName = protocolName.toLowerCase();
Class<?> c = (Class<?>) authenticators.get(protocolName);
if (c == null) return null;
try
{
auth = (RadiusAuthenticator)c.newInstance();
}
catch(Exception e)
{
RadiusLog.error("Invalid auth protocol", e);
return null;
}
if (args != null)
{
HashMap<String, PropertyDescriptor> elements = new HashMap<String, PropertyDescriptor>();
Class<?> clazz = auth.getClass();
PropertyDescriptor[] props = null;
try
{
props = Introspector.getBeanInfo(clazz).getPropertyDescriptors();
}
catch (Exception e)
{
RadiusLog.error("Could not instanciate authenticator " + protocolName, e);
return auth;
}
for (int p = 0; p < props.length; p++)
{
PropertyDescriptor pd = props[p];
Method m = pd.getWriteMethod();
if (m != null)
{
elements.put(pd.getName(), pd);
}
}
for (int a = 0; a < args.length; a++)
{
int eq = args[a].indexOf("=");
if (eq > 0)
{
String name = args[a].substring(0, eq);
String value = args[a].substring(eq + 1);
PropertyDescriptor pd = (PropertyDescriptor)elements.get(name);
Method m = pd.getWriteMethod();
if (m == null)
{
RadiusLog.error("Authenticator " + protocolName + " does not have a writable attribute " + name);
}
else
{
Object valueObject = value;
Class<?> cType = pd.getPropertyType();
if (cType == Boolean.class)
{
valueObject = new Boolean(value);
}
else if (cType == Integer.class)
{
valueObject = new Integer(value);
}
try
{
m.invoke(auth, new Object[]{ valueObject });
}
catch (Exception e)
{
RadiusLog.error("Error setting attribute " + name + " for authenticator " + protocolName, e);
}
}
}
}
}
return auth;
}
public RadiusResponse sendReceive(RadiusRequest p, int retries) throws RadiusException
{
return transport.sendReceive(p, retries);
}
public void send(RadiusRequest p) throws Exception
{
transport.send(p, 0);
}
/**
* Authenicates using the specified method. For all methods, it is assumed
* that the user's password can be found in the User-Password attribute. All
* authentiation requests automatically contain the Message-Authenticator attribute.
* @param p RadiusPacket to be send (should be AccessRequest)
* @param auth The RadiusAuthenticator instance (if null, PAPAuthenticator is used)
* @param retries Number of times to retry (without response)
* @return Returns the reply RadiusPacket
* @throws RadiusException
* @throws UnknownAttributeException
* @throws NoSuchAlgorithmException
*/
public RadiusResponse authenticate(AccessRequest p, RadiusAuthenticator auth, int retries)
throws RadiusException, UnknownAttributeException, NoSuchAlgorithmException
{
if (auth == null) auth = new PAPAuthenticator();
auth.setupRequest(this, p);
auth.processRequest(p);
while (true)
{
RadiusResponse reply = transport.sendReceive(p, retries);
if (reply instanceof AccessChallenge)
{
auth.processChallenge(p, reply);
}
else
{
return reply;
}
}
}
/**
* Send an accounting request
* @param p The RadiusPacket to be sent (should be AccountingRequest)
* @param retries Number of times to retry (without a response)
* @return Returns the reply RadiusPacket
* @throws RadiusException
* @throws UnknownAttributeException
*/
public AccountingResponse accounting(AccountingRequest p, int retries)
throws RadiusException
{
RadiusResponse response = transport.sendReceive(p, retries);
if (!(response instanceof AccountingResponse))
throw new RadiusException("Received something other than AccountingResponse to a AccountingRequest");
return (AccountingResponse)response;
}
public DisconnectResponse disconnect(DisconnectRequest p, int retries)
throws RadiusException
{
RadiusResponse response = transport.sendReceive(p, retries);
if (!(response instanceof DisconnectResponse))
throw new RadiusException("Received something other than DisconnectResponse to a DisconnectRequest");
return (DisconnectResponse)response;
}
public CoAResponse changeOfAuth(CoARequest p, int retries)
throws RadiusException
{
RadiusResponse response = transport.sendReceive(p, retries);
if (!(response instanceof CoAResponse))
throw new RadiusException("Received something other than CoAResponse to a CoARequest");
return (CoAResponse)response;
}
/**
* @return Returns the RADIUS accounting port
*/
public int getAcctPort()
{
return transport.getAcctPort();
}
/**
* @param acctPort The RADIUS accounting port
*/
public void setAcctPort(int acctPort)
{
transport.setAcctPort(acctPort);
}
/**
* @return Returns the RADIUS authentication port
*/
public int getAuthPort()
{
return transport.getAuthPort();
}
/**
* @param authPort The RADIUS authentication port
*/
public void setAuthPort(int authPort)
{
transport.setAuthPort(authPort);
}
/**
* @return Returns the socket timeout (in seconds)
*/
public int getSocketTimeout()
{
return transport.getSocketTimeout();
}
/**
* @param socketTimeout The socket timeout (in seconds)
*/
public void setSocketTimeout(int socketTimeout)
{
transport.setSocketTimeout(socketTimeout);
}
/**
* @return Returns the remote server IP Address
*/
public InetAddress getRemoteInetAddress()
{
return transport.getRemoteInetAddress();
}
/**
* @param remoteInetAddress The remote server IP Address
*/
public void setRemoteInetAddress(InetAddress remoteInetAddress)
{
transport.setRemoteInetAddress(remoteInetAddress);
}
/**
* @return Returns the local IP Address (bind address)
*/
public InetAddress getLocalInetAddress()
{
return transport.getLocalInetAddress();
}
/**
* @param localInetAddress The local IP Address to bind to
*/
public void setLocalInetAddress(InetAddress localInetAddress)
{
// TODO: create a socket bound to the localInetAddress
}
/**
* @return Returns the shared secret
*/
public String getSharedSecret()
{
return transport.getSharedSecret();
}
/**
* @param sharedSecret The shared secret to set
*/
public void setSharedSecret(String sharedSecret)
{
transport.setSharedSecret(sharedSecret);
}
}