Source code: RadiusFormat.java

index |  560 lines | javadoc ]

package nl.west.aaa.radius;

import nl.west.aaa.*;

/*
 * based of rfc2865 and rfc2866
 */


import java.io.*;
import java.util.*;

public class RadiusFormat
    implements RecordFormat
{

    // Types of RADIUS messages

    public static final int MSG_ACCESS_REQUEST      =  1;
    public static final int MSG_ACCESS_ACCEPT       =  2;
    public static final int MSG_ACCESS_REJECT       =  3;
    public static final int MSG_ACCOUNTING_REQUEST  =  4;
    public static final int MSG_ACCOUNTING_RESPONSE =  5;
    public static final int MSG_ACCESS_CHALLENGE    = 11;
    
    // Types of AVP formats

    public static final int AVP_TYPE_TEXT    = 1;
    public static final int AVP_TYPE_STRING  = 2;
    public static final int AVP_TYPE_ADDRESS = 3;
    public static final int AVP_TYPE_INTEGER = 4;
    public static final int AVP_TYPE_DATA    = 5;

    // Types of AVPs

    public static final int AVP_USER_NAME                =  1;
    public static final int AVP_USER_PASSWORD            =  2;
    public static final int AVP_CHAP_PASSWORD            =  3;
    public static final int AVP_NAS_IP_ADDRESS           =  4;
    public static final int AVP_NAS_PORT                 =  5;
    public static final int AVP_SERVICE_TYPE             =  6;
    public static final int AVP_FRAMED_PROTOCOL          =  7;
    public static final int AVP_FRAMED_IP_ADDRESS        =  8;
    public static final int AVP_FRAMED_IP_NETMASK        =  9;
    public static final int AVP_FRAMED_ROUTING           = 10;
    public static final int AVP_FILTER_ID                = 11;
    public static final int AVP_FRAMED_MTU               = 12;
    public static final int AVP_FRAMED_COMPRESSION       = 13;
    public static final int AVP_LOGIN_IP_HOST            = 14;
    public static final int AVP_LOGIN_SERVICE            = 15;
    public static final int AVP_LOGIN_TCP_PORT           = 16;
    public static final int AVP_REPLY_MESSAGE            = 18;
    public static final int AVP_CALLBACK_NUMBER          = 19;
    public static final int AVP_CALLBACK_ID              = 20;
    public static final int AVP_FRAMED_ROUTE             = 22;
    public static final int AVP_FRAMED_IPX_NETWORK       = 23;
    public static final int AVP_STATE                    = 24;
    public static final int AVP_CLASS                    = 25;
    public static final int AVP_VENDOR_SPECIFIC          = 26;
    public static final int AVP_SESSION_TIMEOUT          = 27;
    public static final int AVP_IDLE_TIMEOUT             = 28;
    public static final int AVP_TERMINATION_ACTION       = 29;
    public static final int AVP_CALLED_STATION_ID        = 30;
    public static final int AVP_CALLING_STATION_ID       = 31;
    public static final int AVP_NAS_IDENTIFIER           = 32;
    public static final int AVP_PROXY_STATE              = 33;
    public static final int AVP_LOGIN_LAT_SERVICE        = 34;
    public static final int AVP_LOGIN_LAT_NODE           = 35;
    public static final int AVP_LOGIN_LAT_GROUP          = 36;
    public static final int AVP_FRAMED_APPLETALK_LINK    = 37;
    public static final int AVP_FRAMED_APPLETALK_NETWORK = 38;
    public static final int AVP_FRAMED_APPLETALK_ZONE    = 39;
    public static final int AVP_ACCT_STATUS_TYPE         = 40;
    public static final int AVP_ACCT_DELAY_TIME          = 41;
    public static final int AVP_ACCT_INPUT_OCTETS        = 42;
    public static final int AVP_ACCT_OUTPUT_OCTETS       = 43;
    public static final int AVP_ACCT_SESSION_ID          = 44;
    public static final int AVP_ACCT_AUTHENTIC           = 45;
    public static final int AVP_ACCT_SESSION_TIME        = 46;
    public static final int AVP_ACCT_INPUT_PACKETS       = 47;
    public static final int AVP_ACCT_OUTPUT_PACKETS      = 48;
    public static final int AVP_ACCT_TERMINATE_CAUSE     = 49;
    public static final int AVP_ACCT_MULTI_SESSION_ID    = 50;
    public static final int AVP_ACCT_LINK_COUNT          = 51;
    public static final int AVP_CHAP_CHALLENGE           = 60;
    public static final int AVP_NAS_PORT_TYPE            = 61;
    public static final int AVP_PORT_LIMIT               = 62;
    public static final int AVP_LOGIN_LAT_PORT           = 63;
    
    private static final String[] avpNames={
        "",                     // 0
        "User-Name",            // 1
        "User-Password",        // 2
        "CHAP-Password",        // 3
        "NAS-IP-Address",       // 4
        "NAS-Port",             // 5
        "Service-Type",         // 6
        "Framed-Protocol",      // 7
        "Framed-IP-Address",    // 8
        "Framed-IP-Netmask",    // 9
        "Framed-Routing",       // 10
        "Filter-Id",            // 11
        "Framed-MTU",           // 12
        "Framed-Compression",   // 13
        "Login-IP-Host",        // 14
        "Login-Service",        // 15
        "Login-TCP-Port",       // 16
        "",                     // 17
        "Reply-Message",        // 18
        "Callback-Number",      // 19
        "Callback-Id",          // 20
        "",                     // 21
        "Framed-Route",         // 22
        "Framed-IPX-Network",   // 23
        "State",                // 24
        "Class",                // 25
        "Vendor-Specific",      // 26
        "Session-Timeout",      // 27
        "Idle-Timeout",         // 28
        "Termination-Action",   // 29
        "Called-Station-Id",    // 30
        "Calling-Station-Id",   // 31
        "NAS-Identifier",       // 32
        "Proxy-State",          // 33
        "Login-LAT-Service",    // 34
        "Login-LAT-Node",       // 35
        "Login-LAT-Group",      // 36
        "Framed-AppleTalk-Link", // 37
        "Framed-AppleTalk-Network", // 38
        "Framed-AppleTalk-Zone", // 39
        "Acct-Status-Type",     // 40
        "Acct-Delay-Time",      // 41
        "Acct-Input-Octets",    // 42
        "Acct-Output-Octets",   // 43
        "Acct-Session-Id",      // 44
        "Acct-Authentic",       // 45
        "Acct-Session-Time",    // 46
        "Acct-Input-Packets",   // 47
        "Acct-Output-Packets",  // 48
        "Acct-Terminate-Cause", // 49
        "Acct-Multi-Session-Id", // 50
        "Acct-Link-Count",      // 51
        "", //52
        "", //53
        "", //54
        "", //55
        "", //56
        "", //57
        "", //58
        "", //59
        "CHAP-Challenge",       // 60
        "NAS-Port-Type",        // 61
        "Port-Limit",           // 62
        "Login-LAT-Port"        // 63
    };
    
    private int nextIdentifier=(new Random()).nextInt(256);
    
    /**
     * Translater the RADIUS message code value
     * to a Message.XXXX value.
     * If an illegal messagecode is given
     * the method throws a RuntimeException.
     */
    public static int getMessageType(int messageCode)
    {
        switch(messageCode)
        {
        case MSG_ACCESS_REQUEST:
            // is allso something of an AUTORISATION_REQUEST
            return Message.AUTHENTICATION_REQUEST;
        case MSG_ACCESS_ACCEPT:
            // is allso something of an AUTORISATION_ACCEPT
            return Message.AUTHENTICATION_ACCEPT;
        case MSG_ACCESS_REJECT:
            // could allso be a AUTORISATION_REJECT
            return Message.AUTHENTICATION_REJECT;
        case MSG_ACCOUNTING_REQUEST:
            return Message.ACCOUNTING_REQUEST;
        case MSG_ACCOUNTING_RESPONSE:
            return Message.ACCOUNTING_REPLY;
        case MSG_ACCESS_CHALLENGE:
            // may allso be AUTHORISATION_CHALLENGE;
            return Message.AUTHENTICATION_CHALLENGE;
        default:
            // throw someting
            throw new RuntimeException("Invalid MessageCode");
        }
    }

    /**
     * Translater the Message.XXX messagetype
     * to a valid RADIUS messagecode.
     * If an unsupported messagecode is given
     * the method return -1.
     */
    public static int getRadiusMessageCode(int type)
    {
        switch(type)
        {
        case Message.AUTHENTICATION_REQUEST:
        case Message.AUTHORISATION_REQUEST:
            return MSG_ACCESS_REQUEST;
        case Message.AUTHENTICATION_ACCEPT:
        case Message.AUTHORISATION_ACCEPT:
            return MSG_ACCESS_ACCEPT;
        case Message.AUTHENTICATION_REJECT:
        case Message.AUTHORISATION_REJECT:
            return MSG_ACCESS_REJECT;
        case Message.AUTHENTICATION_CHALLENGE:
        case Message.AUTHORISATION_CHALLENGE:
            return MSG_ACCESS_CHALLENGE;
        case Message.ACCOUNTING_REQUEST:
            return MSG_ACCOUNTING_REQUEST;
        case Message.ACCOUNTING_REPLY:
            return MSG_ACCOUNTING_RESPONSE;
        case Message.AUTHENTICATION_REDO:    
        case Message.AUTHORISATION_REDO:
        case Message.MESSAGES_REQUEST:
        case Message.MESSAGES_READY:
        case Message.MESSAGE_REJECT:
        case Message.ACCOUNTING_POLL:
        default:
            return -1;
        }
    }

    /**
     * Translates the AVP code to a AVP name.
     */    
    public static String getAVPName(int code)
    {
        if ( (code>=1) && (code<=63) )
            if(avpNames[code].length()>0)
                return avpNames[code];
        return "Radius-Unknown-"+code;
    }
    
    /**
     * Translate a AVP name to a AVP code.
     * Returns -1 for unknown names.
     */
    public static int getAVPCode(String name)
    {
        for (int i=1;i<=63;i++)
            if (avpNames[i].equals(name))
                return i;
        return -1;
    }

    /**
     * Figure out the type according to the code.
     */    
    public static int getAVPType(int code)
    {
        switch(code)
        {
        // TEXT AVP's
        case AVP_FILTER_ID:
        case AVP_REPLY_MESSAGE:
        case AVP_FRAMED_ROUTE:
        case AVP_ACCT_SESSION_ID:
            return AVP_TYPE_TEXT;

        // STRING AVP's
        case AVP_USER_NAME:
        case AVP_USER_PASSWORD:
        case AVP_CALLBACK_NUMBER:
        case AVP_CALLBACK_ID:
        case AVP_STATE:
        case AVP_CLASS:
        case AVP_CALLED_STATION_ID:
        case AVP_CALLING_STATION_ID:
        case AVP_NAS_IDENTIFIER:
        case AVP_PROXY_STATE:
        case AVP_LOGIN_LAT_SERVICE:
        case AVP_LOGIN_LAT_NODE:
        case AVP_LOGIN_LAT_GROUP:
        case AVP_FRAMED_APPLETALK_ZONE:
        case AVP_ACCT_MULTI_SESSION_ID:
        case AVP_CHAP_CHALLENGE:
        case AVP_LOGIN_LAT_PORT:
            return AVP_TYPE_STRING;

        // ADDRESS AVP's
        case AVP_NAS_IP_ADDRESS:
        case AVP_FRAMED_IP_ADDRESS:
        case AVP_FRAMED_IP_NETMASK:
        case AVP_LOGIN_IP_HOST:
            return AVP_TYPE_ADDRESS;
        
        // INTEGER AVP's
        case AVP_NAS_PORT:
        case AVP_SERVICE_TYPE:
        case AVP_FRAMED_PROTOCOL:
        case AVP_FRAMED_ROUTING:
        case AVP_FRAMED_MTU:
        case AVP_FRAMED_COMPRESSION:
        case AVP_LOGIN_SERVICE:
        case AVP_LOGIN_TCP_PORT:
        case AVP_FRAMED_IPX_NETWORK:
        case AVP_SESSION_TIMEOUT:
        case AVP_IDLE_TIMEOUT:
        case AVP_TERMINATION_ACTION:
        case AVP_FRAMED_APPLETALK_LINK:
        case AVP_FRAMED_APPLETALK_NETWORK:
        case AVP_ACCT_STATUS_TYPE:
        case AVP_ACCT_DELAY_TIME:
        case AVP_ACCT_INPUT_OCTETS:
        case AVP_ACCT_OUTPUT_OCTETS:
        case AVP_ACCT_AUTHENTIC:
        case AVP_ACCT_SESSION_TIME:
        case AVP_ACCT_INPUT_PACKETS:
        case AVP_ACCT_OUTPUT_PACKETS:
        case AVP_ACCT_TERMINATE_CAUSE:
        case AVP_ACCT_LINK_COUNT:
        case AVP_NAS_PORT_TYPE:
        case AVP_PORT_LIMIT:
            return AVP_TYPE_INTEGER;
        
        // DATA AVP's (other formats)
        case AVP_CHAP_PASSWORD:
        case AVP_VENDOR_SPECIFIC:
            return AVP_TYPE_DATA;

        default:
            return AVP_TYPE_DATA;
        }    
    
    }
    
    
    /**
     * Check if the message has sufficient attributes
     * for it's type.
     */
    private boolean UNIMPLEMENTED_isValidRadiusMessage(Message msg)
    {
        switch(msg.getMessageType())
        {
        case Message.AUTHENTICATION_REQUEST:
        case Message.AUTHENTICATION_ACCEPT:
        case Message.AUTHENTICATION_REJECT:
        case Message.AUTHENTICATION_CHALLENGE:
        case Message.AUTHENTICATION_REDO:    
        case Message.AUTHORISATION_REQUEST:
        case Message.AUTHORISATION_ACCEPT:
        case Message.AUTHORISATION_REJECT:
        case Message.AUTHORISATION_CHALLENGE:
        case Message.AUTHORISATION_REDO:
        case Message.ACCOUNTING_REQUEST:
        case Message.ACCOUNTING_REPLY:
        case Message.ACCOUNTING_POLL:
        case Message.MESSAGES_REQUEST:
        case Message.MESSAGES_READY:
        case Message.MESSAGE_REJECT:
        default:
            return false;
        }
    }

    public byte[] encodeMessage(Message msg)
    {
        // get message attributes
        Hashtable atts=msg.getAttributes();
    
        // get message attributes
        int messageCode=getRadiusMessageCode(msg.getMessageType());
        if(messageCode<0)
            return null;

        Integer messageIDInteger=(Integer)atts.get("message_id");
        int messageID;
        if(messageIDInteger==null)
            messageID=nextIdentifier();
        else
            messageID=messageIDInteger.intValue();
        byte[] authenticator=(byte[])atts.get("radius_authenticator");
        if (authenticator==null)
            authenticator=newAuthenticator();

        // make the buffer
        RadiusMessageBuffer buf=new RadiusMessageBuffer(
            messageCode,messageID,authenticator);
            
        // store all the other attributes
        Enumeration e=atts.keys();
        while(e.hasMoreElements())
        {
            String key=(String)e.nextElement();
            Object val=atts.get(key);
            if( key.equals("message_id") ||
                key.equals("radius_authenticator") )
            {
                // these attributes are allready stored
            }
            else
            {
                int avpCode=getAVPCode(key);
                if(avpCode<0)
                {
                    // no radius attribute, store as vendor-spec
                    buf.addSpecialAVP(key,val.toString());
                }
                else
                {
                    switch(getAVPType(avpCode))
                    {
                    case AVP_TYPE_TEXT:
                        buf.addTextAVP(avpCode,(String)val);
                        break;
                    case AVP_TYPE_STRING:
                        buf.addStringAVP(avpCode,(String)val);
                        break;
                    case AVP_TYPE_ADDRESS:
                        buf.addAddressAVP(avpCode,(String)val);
                        break;
                    case AVP_TYPE_INTEGER:
                        if(val instanceof Integer)
                            buf.addIntegerAVP(avpCode,((Integer)val).intValue());
                        else
                            buf.addIntegerAVP(avpCode,Integer.parseInt((String)val));
                        break;
                    case AVP_TYPE_DATA:
                        buf.addDataAVP(avpCode,(byte[])val);
                        break;
                    }
                }
            }
        }
        return buf.getBytes();
    }
    
    public Message decodeMessage(byte[] data,Identifier from)
    {
        // TODO:add checks from other decodeMessage to check for
        //      valid format
        // make a buffer out of the data
        RadiusMessageBuffer mbuf=new RadiusMessageBuffer(data);
        // make a message out of it
        Hashtable att=new Hashtable();
        att.put("message_id",new Integer(mbuf.getMessageId()));
        att.put("sid","RADIUS:"+from+":someport:"+mbuf.getMessageId());
        att.put("radius_authenticator",mbuf.getMessageAuthenticator());
        mbuf.setFirstAVP();
        while(mbuf.hasMoreAVPs())
        {
            int code=mbuf.getAVPCode();
            if(code==AVP_VENDOR_SPECIFIC)
            {
                if(mbuf.isSpecialAVP())
                {
                    // special attribute
                    String avpn=mbuf.getAVPSpecialName();
                    String avpo=mbuf.getAVPSpecialValue();
                    att.put(avpn,avpo);
                }
                else
                {
                    // "normal" vendor attribute
                    // TODO: implement
                }
            
            }
            else
            {
                // "normal" attribute
                String avpn=getAVPName(code);
                Object avpo=null;
                switch(getAVPType(code))
                {
                case AVP_TYPE_TEXT:
                    avpo=mbuf.getAVPText();
                    break;
                case AVP_TYPE_STRING:
                    avpo=mbuf.getAVPString();
                    break;
                case AVP_TYPE_ADDRESS:
                    avpo=mbuf.getAVPAddress();
                    break;
                case AVP_TYPE_INTEGER:
                    avpo=new Integer(mbuf.getAVPInteger());
                    break;
                case AVP_TYPE_DATA:
                    avpo=mbuf.getAVPData();
                    break;
                }
                att.put(avpn,avpo);
            }
            mbuf.nextAVP();
        }
        return new Message(getMessageType(mbuf.getMessageCode()),att);
    }
    
    /**
     * Read a RADIUS message from the stream.
     * The algorithm used to check wether it is realy a
     * RADIUS message is <b>NOT</b> very good, since RADIUS
     * has no real identifying header.
     * I suggest you
     * use this RecordFormat last in the list to test
     * for valid recordformats.
     */
    public Message decodeMessage(InputStream in,Identifier from)
        throws IOException
    {
        in.mark(30);    // mark for reset()
        // check for valid codes
        int type=in.read();
        switch(type)
        {
        case MSG_ACCESS_REQUEST:
        case MSG_ACCESS_ACCEPT:
        case MSG_ACCESS_REJECT:
        case MSG_ACCOUNTING_REQUEST:
        case MSG_ACCOUNTING_RESPONSE:
        case MSG_ACCESS_CHALLENGE:
            break;  // ok
        default:
            // other types of requests are not supported and 
            // deemed invalid RADIUS messages
            in.reset();
            return null;
        }
        // skip identifyer
        int ident=in.read();
        // check if length is senseble
        int len=in.read();
        len=(len<<8)|in.read();
        if(len<20||len>1024)
        {
            in.reset();
            return null;
        }
        // read the whole stuff
        in.reset();
        byte[] buf=new byte[len];
        in.read(buf);
        // parse this
        return decodeMessage(buf,from);
    }
    
    private int nextIdentifier()
    {
        nextIdentifier=(nextIdentifier+1)&0xff;
        return nextIdentifier;
    }
    
    /**
     * Generates a new random authenticator.
     */
    public static byte[] newAuthenticator()
    {
        byte[] res=new byte[16];
        (new Random()).nextBytes(res);
        return res;
    }

}


Arthur <arthur@ch.twi.tudelft.nl> http://ch.twi.tudelft.nl/~arthur/
2002-05-27