/**************************************************************************** 
** File: radius.c
**
** Author: Mike Borella
**
** Comments: Support for decoding RADIUS packets.
**
** $Id: radius.c,v 1.5 2007/06/25 12:47:42 farooq-i-azam Exp $
**
** This program is free software; you can redistribute it and/or modify
** it under the terms of the GNU General Public License as published by
** the Free Software Foundation; either version 2 of the License, or
** (at your option) any later version.
**
** This program 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 Library General Public License for more details.
**
** You should have received a copy of the GNU General Public License
** along with this program; if not, write to the Free Software
** Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
**
*****************************************************************************/

#include "radius.h"
#include "iana.h"
#include "radius_3gpp2.h"

/*
 * Types of RADIUS codes
 */

#define RADIUS_CODE_ACCESSREQUEST      1
#define RADIUS_CODE_ACCESSACCEPT       2
#define RADIUS_CODE_ACCESSREJECT       3
#define RADIUS_CODE_ACCOUNTINGREQUEST  4
#define RADIUS_CODE_ACCOUNTINGRESPONSE 5
#define RADIUS_CODE_ACCESSCHALLENGE    11

/*
 * RADIUS code map
 */

strmap_t radius_code_map[] =
  {
    { RADIUS_CODE_ACCESSREQUEST,       "access request" },
    { RADIUS_CODE_ACCESSACCEPT,        "access accept" },
    { RADIUS_CODE_ACCESSREJECT,        "access reject" },
    { RADIUS_CODE_ACCOUNTINGREQUEST,   "accounting request" },
    { RADIUS_CODE_ACCOUNTINGRESPONSE,  "accounting response" },
    { RADIUS_CODE_ACCESSCHALLENGE,     "access challenge" },
    { 0, ""}
  };

/*
 * RADIUS attributes 
 */

#define RADIUS_ATTR_USERNAME                    1
#define RADIUS_ATTR_USERPASSWORD                2
#define RADIUS_ATTR_CHAPPASSWORD                3
#define RADIUS_ATTR_NASIPADDR                   4
#define RADIUS_ATTR_NASPORT                     5
#define RADIUS_ATTR_SERVICETYPE                 6
#define RADIUS_ATTR_FRAMEDPROTOCOL              7
#define RADIUS_ATTR_FRAMEDIPADDR                8
#define RADIUS_ATTR_FRAMEDIPNETMASK             9

#define RADIUS_ATTR_CLASS                       25
#define RADIUS_ATTR_VENDORSPECIFIC              26

#define RADIUS_ATTR_CALLINGSTATIONID            31

#define RADIUS_ATTR_LOGINLATSERVICE             34

#define RADIUS_ATTR_ACCTSTATUSTYPE              40

#define RADIUS_ATTR_ACCTINPUTOCTETS             42
#define RADIUS_ATTR_ACCTOUTPUTOCTETS            43
#define RADIUS_ATTR_ACCTSESSIONID               44

#define RADIUS_ATTR_ACCTSESSIONTIME             46

#define RADIUS_ATTR_EVENTTIMESTAMP              55

#define RADIUS_ATTR_CHAPCHALLENGE               60
#define RADIUS_ATTR_NASPORTTYPE                 61

/*
 * RADIUS attribute map
 */

strmap_t radius_attr_map[] =
  {
    { RADIUS_ATTR_USERNAME,               "user name" },
    { RADIUS_ATTR_USERPASSWORD,           "user password" },
    { RADIUS_ATTR_CHAPPASSWORD,           "CHAP password" },
    { RADIUS_ATTR_NASIPADDR,              "NAS IP address" },
    { RADIUS_ATTR_NASPORT,                "NAS port" },
    { RADIUS_ATTR_SERVICETYPE,            "service type" },
    { RADIUS_ATTR_FRAMEDPROTOCOL,         "framed protocol" },
    { RADIUS_ATTR_FRAMEDIPADDR,           "framed IP address" },
    { RADIUS_ATTR_FRAMEDIPNETMASK,        "framed IP netmask" },

    { RADIUS_ATTR_CLASS,                  "class" },
    { RADIUS_ATTR_VENDORSPECIFIC,         "vendor specific" },

    { RADIUS_ATTR_CALLINGSTATIONID,       "calling station ID" },

    { RADIUS_ATTR_LOGINLATSERVICE,        "login-LAT-service" },

    { RADIUS_ATTR_ACCTSTATUSTYPE,         "accounting status type" },
    
    { RADIUS_ATTR_ACCTSESSIONID,          "accounting session id" },

    { RADIUS_ATTR_ACCTINPUTOCTETS,        "accounting input octets" },
    { RADIUS_ATTR_ACCTOUTPUTOCTETS,       "accounting output octets" },

    { RADIUS_ATTR_ACCTSESSIONTIME,        "accounting session time" },

    { RADIUS_ATTR_EVENTTIMESTAMP,         "event timestamp" },

    { RADIUS_ATTR_CHAPCHALLENGE,          "CHAP challenge" },
    { RADIUS_ATTR_NASPORTTYPE,            "NAS port type" },
    { 0, ""}
  };


/*
 * RADIUS packet format
 */

typedef struct radius_
{
  u_int8_t   code;
  u_int8_t   identifier;
  u_int16_t  length;
  u_int8_t   authenticator[16];
} radius_header_t;

extern struct arg_t * my_args;
extern strmap_t iana_enterprise_map[];

/*----------------------------------------------------------------------------
**
** dump_radius_vendorspecific()
**
** Parse and dump a vendor specific RADIUS attribute.  
** Returns the number of bytes read or -1 on error.
**
**----------------------------------------------------------------------------
*/

void dump_radius_vendorspecific(packet_t * pkt, u_int8_t type, u_int8_t length)
{
  u_int32_t vendor;

  if (get_packet_bytes((u_int8_t *) &vendor, pkt, 4) == 0)
    return;

  /* Conversions */
  vendor = ntohl(vendor);

  /* Get the vendor specific info */
  switch(vendor)
    {
    case IANA_ENTERPRISE_3GPP2:
      dump_radius_3gpp2(pkt, type, length);
      break;

    default:
      {
	u_int8_t * value;
	
	/* Allocate memory for the value then get it */
        value = my_malloc(length-5);
	if (get_packet_bytes(value, pkt, length-6) == 0)
	  return;
	value[length-5] = '\0';
 
	/* Display */
	if (my_args->m)
	  {
	    display_minimal_string(map2str(radius_attr_map, type));
	    display_minimal_string("[");
	    display_minimal_string(map2str(iana_enterprise_map, vendor));
	    display_minimal_string("]: ");
	    display_minimal(value, length-6, DISP_HEX);
	  }
	else
	  {
	    display_strmap("Attribute type", type, radius_attr_map);
	    display("  Length", &length, 1, DISP_DEC);
	    display_strmap("  Vendor", vendor, iana_enterprise_map);
	    display("  Value", value, length-6, DISP_HEX_MULTILINE);
	  }
	my_free(value);	
      }
      break;

    } /* switch */
}
     
/*----------------------------------------------------------------------------
**
** dump_radius_attribute()
**
** Parse and dump a single RADIUS attribute.  Returns the number of bytes read.
** 
**
**----------------------------------------------------------------------------
*/

int dump_radius_attribute(packet_t * pkt)
{
  u_int8_t   type;
  u_int8_t   length;
  u_int8_t * generic_value;

  /* Get the code */
  if (get_packet_bytes(&type, pkt, 1) == 0)
    return 0;
  
  /* Get the length */
  if (get_packet_bytes(&length, pkt, 1) == 0)
    return 0;
  
  /* Decide how to proceed based on the code */
  switch(type)
    {
      /* These cases are for all attributes to be printed as text strings */
    case RADIUS_ATTR_USERNAME:
    case RADIUS_ATTR_CLASS:
    case RADIUS_ATTR_CALLINGSTATIONID:
    case RADIUS_ATTR_LOGINLATSERVICE:
      {
	u_int8_t * username;
	
	/* Allocate memory for the username then get it */
	username = my_malloc(length-1);
	if (get_packet_bytes(username, pkt, length-2) == 0)
	  return 0;
	username[length-1] = '\0';

	/* Display */
	if (my_args->m)
	  {
	    display_minimal_string(map2str(radius_attr_map, type));
	    display_minimal_string(": ");
	    display_minimal_string(username);
	  }
	else
	  {
	    display_strmap("Attribute type", type, radius_attr_map);
	    display("  Length", &length, 1, DISP_DEC);
	    display_string("  Value", username);	    
	  }

	my_free(username);
      }
      break;

      /* These cases are for all attributes to be printed as 4 byte ints */
    case RADIUS_ATTR_NASPORT:
    case RADIUS_ATTR_SERVICETYPE:      /* should have its own parser */
    case RADIUS_ATTR_FRAMEDPROTOCOL:   /* should have its own parser */
    case RADIUS_ATTR_NASPORTTYPE:      /* should have its own parser */
    case RADIUS_ATTR_EVENTTIMESTAMP:
    case RADIUS_ATTR_ACCTSESSIONTIME:
    case RADIUS_ATTR_ACCTINPUTOCTETS:
    case RADIUS_ATTR_ACCTOUTPUTOCTETS:
      {
	u_int32_t byte4;

	if (get_packet_bytes((u_int8_t *) &byte4, pkt, 4) == 0)
	  return 0;

	/* Conversions */
	byte4 = ntohl(byte4);

	/* Display */
	if (my_args->m)
	  {
	    display_minimal_string(map2str(radius_attr_map, type));
	    display_minimal_string(": ");
	    display_minimal((u_int8_t *) &byte4, 4, DISP_DEC);
	  }
	else
	  {
	    display_strmap("Attribute type", type, radius_attr_map);
	    display("  Length", &length, 1, DISP_DEC);
	    display("  Value", (u_int8_t *) &byte4, 4, DISP_DEC);
	  }	
      }
      break;

 
      /* These cases are for all attributes to be printed as IP addresses */
    case RADIUS_ATTR_NASIPADDR:
    case RADIUS_ATTR_FRAMEDIPADDR:
    case RADIUS_ATTR_FRAMEDIPNETMASK:
      {
	u_int32_t addr;

	if (get_packet_bytes((u_int8_t *) &addr, pkt, 4) == 0)
	  return 0;

	/* Display */
	if (my_args->m)
	  {
	    display_minimal_string(map2str(radius_attr_map, type));
	    display_minimal_string(": ");
	    display_minimal((u_int8_t *) &addr, 4, DISP_DOTTEDDEC);
	  }
	else
	  {
	    display_strmap("Attribute type", type, radius_attr_map);
	    display("  Length", &length, 1, DISP_DEC);
	    display("  Address", (u_int8_t *) &addr, 4, DISP_DOTTEDDEC);
	  }	
      }
      break;


    case RADIUS_ATTR_VENDORSPECIFIC:
      dump_radius_vendorspecific(pkt, type, length);
      break;

      /* These cases are for all attributes to be printed as hex */
    case RADIUS_ATTR_CHAPCHALLENGE:
    case RADIUS_ATTR_USERPASSWORD:
    case RADIUS_ATTR_CHAPPASSWORD:
    default:
      {
	/* Allocate memory for the value then get it */
	generic_value = my_malloc(length-2);
	if (get_packet_bytes(generic_value, pkt, length-2) == 0)
	  return 0;
	
	/* Display */
	if (my_args->m)
	  {
	    display_minimal_string(map2str(radius_attr_map, type));
	    display_minimal_string(": ");
	    display_minimal(generic_value, length-2, DISP_HEX);
	  }
	else
	  {
	    display_strmap("Attribute type", type, radius_attr_map);
	    display("  Length", &length, 1, DISP_DEC);
	    display("  Value", generic_value, length-2, DISP_HEX_MULTILINE);
	  }

	/* Free the memory of the value */
	my_free(generic_value);
      }
      break;
    }

  return length;
}

/*----------------------------------------------------------------------------
**
** dump_radius()
**
** Parse and dump RADIUS packets
**
**----------------------------------------------------------------------------
*/

void dump_radius(packet_t *pkt)
{
  radius_header_t radius;
  int             len;
  int             first;

  /* Set the layer */
  set_layer(LAYER_TRANSPORT);

  /* Get the RADIUS header */
  if (get_packet_bytes((u_int8_t *) &radius, pkt, sizeof(radius_header_t)) 
      == 0)
    return;

  /* Conversions */
  radius.length = ntohs(radius.length);

  /* Display */
  if (my_args->m)
    {
      display_minimal_string("| RADIUS ");
      display_minimal_string(map2str(radius_code_map, radius.code));
      display_minimal_string(" ");
      display_minimal(&radius.identifier, 1, DISP_DEC);
      display_minimal_string(" (");
    }
  else
    {
      display_header_banner("RADIUS");
      display_strmap("Code", radius.code, radius_code_map);
      display("Identifier", &radius.identifier, 1, DISP_DEC);
      display("Length", (u_int8_t *) &radius.length, 2, DISP_DEC);
      display("Authenticator", (u_int8_t *) &radius.authenticator, 16, 
	      DISP_HEX);
    }
  
  /* Read all of the attributes */

  /* Get length of attributes in bytes */
  len = radius.length - sizeof(radius_header_t);

  first = 1;
  while(1)
    {
      if (len <= 0)
	break;
      if (first != 1 && my_args->m)
	display_minimal_string(", ");
      len = len - dump_radius_attribute(pkt);
      first = 0;
    }

  /* Final close paren for minimal mode */
  if (my_args->m)
    display_minimal_string(")");

  /* Dump the hex buffer */
  hexbuffer_flush();

  return;
}
