/**************************************************************************** 
** File: ipcp.c
**
** Author: Mike Borella
**
** Comments: IPCP module.
**
** $Id: ipcp.c,v 1.7 2007/06/25 11:50:26 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 "ipcp.h"

#define IPCP_OPTION_LEN 64

/*
 * IPCP codes
 */

#define IPCP_CODE_CONFREQ        1
#define IPCP_CODE_CONFACK        2
#define IPCP_CODE_CONFNAK        3
#define IPCP_CODE_CONFREJ        4
#define IPCP_CODE_TERMREQ        5
#define IPCP_CODE_TERMACK        6
#define IPCP_CODE_CODEREJ        7

/*
 * IPCP code map
 */

strmap_t ipcp_code_map[] =
{
  { IPCP_CODE_CONFREQ,        "Configure-Request" },
  { IPCP_CODE_CONFACK,        "Configure-Ack" },
  { IPCP_CODE_CONFNAK,        "Configure-Nak" },
  { IPCP_CODE_CONFREJ,        "Configure-Reject" },
  { IPCP_CODE_TERMREQ,        "Terminate-Request" }, 
  { IPCP_CODE_TERMACK,        "Terminate-Ack" }, 
  { IPCP_CODE_CODEREJ,        "Code-Rej" },
  { 0, ""}
};

/*
 * IPCP options
 */

#define IPCP_OPTION_IPADDRS              1
#define IPCP_OPTION_IPCOMPPROTO          2
#define IPCP_OPTION_IPADDR               3
#define IPCP_OPTION_MOBILEIPV4           4
#define IPCP_OPTION_PRIMARYDNS           129
#define IPCP_OPTION_PRIMARYNBNS          130
#define IPCP_OPTION_SECONDARYDNS         131
#define IPCP_OPTION_SECONDARYNBNS        132

/*
 * IPCP option map
 */

strmap_t ipcp_option_map[] = 
{
  { IPCP_OPTION_IPADDRS,               "IP addresses" },
  { IPCP_OPTION_IPCOMPPROTO,           "IP compression protocol" },
  { IPCP_OPTION_IPADDR,                "IP address" },
  { IPCP_OPTION_MOBILEIPV4,            "Mobile IPv4" },
  { IPCP_OPTION_PRIMARYDNS,            "Primary DNS" },
  { IPCP_OPTION_PRIMARYNBNS,           "Primary NBNS" },
  { IPCP_OPTION_SECONDARYDNS,          "Secondary DNS" },
  { IPCP_OPTION_SECONDARYNBNS,         "Secondary NBNS" },  
  { 0, "" } 
};

/*
 * IPCP compression types
 */

#define IPCP_COMP_VJ                    0x002d

/*
 * IPCP compression type map
 */

strmap_t ipcp_comp_map[] = 
{
  { IPCP_COMP_VJ,               "Van Jacobson" },
  { 0, "" } 
};

/*
 * IPCP frame format
 */

typedef struct ipcp
{
  u_int8_t  code;
  u_int8_t  identifier;
  u_int16_t length;
} ipcp_t;

extern struct arg_t * my_args;

/*----------------------------------------------------------------------------
**
** dump_ipcp()
**
** Displays IPCP packets.
**
**----------------------------------------------------------------------------
*/

void dump_ipcp(packet_t *pkt)
{
  ipcp_t ipcp;

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

  /*
   * Get the header
   */

  if (get_packet_bytes((u_int8_t *) &ipcp, pkt, sizeof(ipcp_t)) == 0)
    return;

  /*
   * Conversion
   */

  ipcp.length = ntohs(ipcp.length);

  /*
   * Dump the header
   */

  if (my_args->m)
    {
      display_minimal_string("| IPCP ");
      display_minimal_string(map2str(ipcp_code_map, ipcp.code));
      display_minimal_string(" ");
    }
  else
    {
      display_header_banner("IPCP Header");
      display_strmap("Code", ipcp.code, ipcp_code_map);
      display("Identifier", &ipcp.identifier, 1, DISP_DEC);
      display("Length", (u_int8_t *) &ipcp.length, 2, DISP_DEC);
    }

  /*
   * Grab the payload data
   */

  if (ipcp.length > sizeof(ipcp_t))
    {
      switch(ipcp.code)
	{
	case IPCP_CODE_CONFREQ:
	case IPCP_CODE_CONFACK:
	case IPCP_CODE_CONFNAK:
	case IPCP_CODE_CONFREJ:
	  {
	    u_int8_t bytes_read = 0;
	    u_int8_t type;
	    u_int8_t length;
	    u_int8_t value [IPCP_OPTION_LEN];
	    int comma = 0;
	    
	    /*
	     * IPCP options appear in Type-Length-Value format 
	     */
	    
	    while (bytes_read < ipcp.length - sizeof(ipcp_t))
	      {
		/*
		 * Get type
		 */
		
		if (get_packet_bytes((u_int8_t *) &type, pkt, 1)  == 0)
		  break;
		bytes_read ++;


		/*
		 * Get length
		 */
		
		if (get_packet_bytes((u_int8_t *) &length, pkt, 1)  == 0)
		  break;
		bytes_read ++;

                /* 
                 * In minimal mode we start all IPCP options with an open paren
                 * and put a comma before all but the first
                 */

                if (my_args->m)
                  {
                    if (comma)
                      display_minimal_string(", ");
                    else
                      display_minimal_string("(");
		    comma = 1;
                  }

		/*
		 * Display the type and length
		 */
		
		if (my_args->m)
		  {
		    display_minimal_string(map2str(ipcp_option_map, type));
		  }
		else
		  {
		    display_strmap("Option", type, ipcp_option_map);
		    display("  Length", &length, 1, DISP_DEC);
		  }
		
		/*
		 * Figure out if we need to get a value
		 */

		if (length > 2)
		  {
		    if (get_packet_bytes((u_int8_t *) value, pkt, length-2)  
			== 0)
		      break;
		    bytes_read = bytes_read + length - 2;
		    
		    /*
		     * Display the value
		     */

		    switch(type)
		      {
		      case IPCP_OPTION_IPADDRS:
			break;
			
		      case IPCP_OPTION_IPCOMPPROTO:
			{
			  u_int16_t comp_type;
			  
			  memcpy((void *) &comp_type, (void *) &value, 2);
			  comp_type = ntohs(comp_type);
			  
			  if (my_args->m)
			    {
			      display_minimal_string(" ");
			      display_minimal_string(map2str
						     (ipcp_comp_map, 
						      comp_type));
			    }
			  else
			    {
			      u_int8_t max_slot_id = value[2];
			      u_int8_t comp_slot_id = value[3];
			      
			      display_strmap("IP compression", comp_type, 
					     ipcp_comp_map);
			      display("Max slot ID", 
				      (u_int8_t *) &max_slot_id, 1, 
				      DISP_DEC);
			      display("Comp slot ID", 
				      (u_int8_t *) &comp_slot_id, 1, 
				      DISP_DEC);
			    }
			  break;
			}

			case IPCP_OPTION_IPADDR:
			case IPCP_OPTION_MOBILEIPV4:
			case IPCP_OPTION_PRIMARYDNS:
			case IPCP_OPTION_PRIMARYNBNS:
			case IPCP_OPTION_SECONDARYDNS:
			case IPCP_OPTION_SECONDARYNBNS:
			  if (my_args->m)
			    {
			      display_minimal_string(" ");
			      display_minimal_ipv4((u_int8_t *) &value);
			    }
			  else
			    display_ipv4("IP address", (u_int8_t *) &value);
			  break;
			  
			}

		  }
	      } /* while */
            
	    /* 
             * Insert end paren in minimal mode
             */
	    
            if (my_args->m)
              display_minimal_string(")");
	  }
	  break;
	  
	case IPCP_CODE_TERMREQ:
	case IPCP_CODE_TERMACK:
	  {
	    u_int8_t * data;
	    u_int8_t data_len;

	    /*
	     * Read the data, if any
	     */

	    data_len = ipcp.length - sizeof(ipcp_t);
	    if (data_len > 0)
	      {
		/* allocate memory for the data */
		data = (u_int8_t *) my_malloc(data_len);

		/* grab the data field */
		if (get_packet_bytes(data, pkt, data_len) == 0)
		  {
		    my_free(data);
		    return;
		  }
	
		/* dump it out as hex, but don't dump in minimal mode */
		if (!my_args->m)
		  {
		    display("Data", data, data_len, DISP_HEX);
		  }
		
		/* free that darn mem! */
		my_free(data);
	      }
	  }
	  break;

	case IPCP_CODE_CODEREJ:
	  dump_ipcp(pkt);
	  break;

	default:
	  break;
	}
    }

  /* dump the hex buffer */
  if (!my_args->l)
    hexbuffer_flush();
}
