/**************************************************************************** 
** File: dhcp.c
**
** Author: Mike Borella
**
** Comments: Dump DHCP header information.  See RFCs 2131 and 2132.
**
** $Id: dhcp.c,v 1.22 2007/06/25 12:57:52 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 "arp.h"
#include "dhcp.h"

#define HOLDER_SIZE 256

/*
 * DHCP operation type map
 */

strmap_t dhcp_op_map[] =
{
  { DHCP_OP_BOOTREQUEST, "boot request" },
  { DHCP_OP_BOOTREPLY,   "boot reply" },
  { 0, "" }
};

/*
 * DHCP option map
 */

strmap_t dhcp_option_map[] =
{
  { DHCP_OPTION_PAD,             "pad" },
  { DHCP_OPTION_NETMASK,         "netmask" },
  { DHCP_OPTION_TIMEOFFSET,      "time offset" },
  { DHCP_OPTION_ROUTER,          "router" },
  { DHCP_OPTION_TIMESERVER,      "time server" },
  { DHCP_OPTION_NAMESERVER,      "name server" },
  { DHCP_OPTION_DNS,             "DNS server" },
  { DHCP_OPTION_LOGSERVER,       "log server" },
  { DHCP_OPTION_COOKIESERVER,    "cookie server" },
  { DHCP_OPTION_LPRSERVER,       "LPR server" },
  { DHCP_OPTION_IMPRESSSERVER,   "impress server" },
  { DHCP_OPTION_RESLOCSERVER,    "RLP server" },
  { DHCP_OPTION_HOSTNAME,        "hostname" },
  { DHCP_OPTION_BOOTFILESIZE,    "boot file size" },
  { DHCP_OPTION_MERITDUMP,       "Merit dump" },
  { DHCP_OPTION_DOMAINNAME,      "domain name" },
  { DHCP_OPTION_SWAPSERVER,      "swap server" },
  { DHCP_OPTION_ROOTPATH,        "root path" },
  { DHCP_OPTION_EXTSPATH,        "extension path" },
  { DHCP_OPTION_IPFORWARD,       "IP forward" },
  { DHCP_OPTION_NONLOCALSR,      "source routing" },
  { DHCP_OPTION_POLICYFILTER,    "policy filter" },

  { DHCP_OPTION_VENDORSPECIFIC,  "vendor specific" },
  { DHCP_OPTION_NETBIOSNAMESERV, "NETBIOS name server" },
  { DHCP_OPTION_NETBIOSDGDIST,   "NETBIOS datagram distribution server" },
  { DHCP_OPTION_NETBIOSNODETYPE, "NETBIOS node type" },
  { DHCP_OPTION_NETBIOSSCOPE,    "NETBIOS scope" },

  { DHCP_OPTION_REQUESTEDIPADDR, "requested IP address" },
  { DHCP_OPTION_IPADDRLEASE,     "IP address lease" },
  { DHCP_OPTION_OVERLOAD,        "overload" },
  { DHCP_OPTION_MESSAGETYPE,     "message type" },
  { DHCP_OPTION_SERVERID,        "server ID" },
  { DHCP_OPTION_PARAMREQLIST,    "parameter request list" },

  { DHCP_OPTION_RENEWALTIME,     "renewal time" },
  { DHCP_OPTION_REBINDINGTIME,   "rebinding time" },

  { DHCP_OPTION_VENDORCLASSID,   "vendor class ID" },
  { DHCP_OPTION_CLIENTID,        "client ID" },

  { DHCP_OPTION_USERCLASS,       "user class" },

  { DHCP_OPTION_END,             "end of options" },
  { 0, "" }
};

/*
 * DHCP message type map
 */

strmap_t dhcp_msgtype_map[] =
{
  { DHCP_MSGTYPE_DISCOVER,    "DHCPDISCOVER" },
  { DHCP_MSGTYPE_OFFER,       "DHCPOFFER" },
  { DHCP_MSGTYPE_REQUEST,     "DHCPREQUEST" },
  { DHCP_MSGTYPE_DECLINE,     "DHCPDECLINE" },
  { DHCP_MSGTYPE_ACK,         "DHCPACK" },
  { DHCP_MSGTYPE_NAK,         "DHCPNAK" },
  { DHCP_MSGTYPE_RELEASE,     "DHCPRELASE" },
  { DHCP_MSGTYPE_INFORM,      "DHCPINFORM" },
  { 0, "" }
};

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

/*----------------------------------------------------------------------------
**
** dump_dhcp()
**
** Parse DHCP packet and dump fields
**
**----------------------------------------------------------------------------
*/

void dump_dhcp(packet_t *pkt)
{
  dhcp_header_t dhcp;
  int j;
  u_int32_t i;
  u_int32_t cookie_holder;
  u_int8_t opt;
  char holder[HOLDER_SIZE]; /* this needs to be as big as the biggest option */

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

  /*
   * Get the DHCP fixed header
   */

  if (get_packet_bytes((u_int8_t *) &dhcp, pkt, sizeof(dhcp_header_t)) == 0)
    return;

  /*
   * Conversions 
   */

  dhcp.xid = ntohl(dhcp.xid);
  dhcp.secs = ntohs(dhcp.secs);
  dhcp.flags = ntohs(dhcp.flags);

  /* announcement */
  if (!my_args->m)
    display_header_banner("DHCP Header");

  /*
   * Display operation, other info
   */

  snprintf(holder, HOLDER_SIZE, "%d (%s)", dhcp.op, 
	  map2str(dhcp_op_map, dhcp.op));
  if (my_args->m)
    {
      display_minimal_string(map2str(dhcp_op_map, dhcp.op));
      display_minimal_string(" ");
    }
  else
    {
      display("Operation", (u_int8_t *) holder, strlen(holder), DISP_STRING);
      display("Hardware addr type", (u_int8_t *) &dhcp.htype, 1, 
	      DISP_DEC);
      display("Hardware addr length", (u_int8_t *) &dhcp.hlen, 1, 
	      DISP_DEC);
      display("Hops", (u_int8_t *) &dhcp.hops, 1, DISP_DEC); 
      display("Transaction ID", (u_int8_t *) &dhcp.xid, 4, DISP_HEX);
      display("Seconds", (u_int8_t *) &dhcp.secs, 2, DISP_DEC);
      display("Flags", (u_int8_t *) &dhcp.flags, 2, DISP_HEX);
    }

  /* 
   * Display IPs 
   */

  if (!my_args->m)
    {
      display_ipv4("Client addr", (u_int8_t *) &dhcp.ciaddr);
      display_ipv4("Your addr", (u_int8_t *) &dhcp.yiaddr);
      display_ipv4("Next server addr", (u_int8_t *) &dhcp.siaddr);
      display_ipv4("Relay agent addr", (u_int8_t *) &dhcp.giaddr);
      display ("Client hardware addr:", (u_int8_t *) dhcp.chaddr, dhcp.hlen,
			DISP_HEXCOLONS );
    }

  /*
   * Display names
   */
  
  if (!my_args->m)
    {
      display("Server host name", (u_int8_t *) dhcp.sname, strlen(dhcp.sname),
	      DISP_STRING);
      display("Boot file name", (u_int8_t *) dhcp.file, strlen(dhcp.file),
	      DISP_STRING);
    }
  
  /*
   * Look for BOOTP cookie
   */

  i = htonl(BOOTP_COOKIE);
  if (look_packet_bytes((u_int8_t *) &cookie_holder, pkt, 4) == 0)
    return;
  if (i == cookie_holder && !my_args->m)
    {
      if (skip_packet_bytes(pkt, 4) == 0)
	return;
      display("BOOTP cookie", (u_int8_t *) &cookie_holder, 4, DISP_HEX);
    }

  /*
   * Parse the options list
   */

  while (get_packet_apparentbytesleft(pkt))
    {     
      u_int8_t len=0;
      
      /*
       * Get the option number and dump it
       */
      
      if (get_packet_bytes((u_int8_t *) &opt, pkt, 1) == 0)
	return;
      snprintf(holder, HOLDER_SIZE, "%d (%s)", opt, 
	      map2str(dhcp_option_map, opt));
      if (!my_args->m)
	display("Option", (u_int8_t *) holder, strlen(holder), DISP_STRING);
      
      /*
       * If its a pad, go back and get another
       */
      
      if (opt == DHCP_OPTION_PAD)
	continue;

      /*
       * If its an end-of-options indicator, bail out
       */

      if (opt == DHCP_OPTION_END)
	break;

      /*
       * Otherwise, grab and dump the length
       */

      if (get_packet_bytes((u_int8_t *) &len, pkt, 1) == 0)
	return;
      if (!my_args->m)
	display("  Length", (u_int8_t *) &len, 1, DISP_DEC);
      
      switch(opt)
	{
	case DHCP_OPTION_NETMASK: /* 1 */
	  if (get_packet_bytes((u_int8_t *) holder, pkt, len) == 0)
	    return;
	  if (!my_args->m)
	    display_ipv4("  Mask", (u_int8_t *) holder);
	  break;

	case DHCP_OPTION_TIMEOFFSET: /* 2 */
	  if (get_packet_bytes((u_int8_t *) holder, pkt, len) == 0)
	    return;
	  if (!my_args->m)
	    display("  Offset", (u_int8_t *) holder, len, DISP_DEC);
	  break;

	case DHCP_OPTION_ROUTER:	/* 3 */
	case DHCP_OPTION_TIMESERVER: 	/* 4 */
	case DHCP_OPTION_NAMESERVER: 	/* 5 */
	case DHCP_OPTION_DNS: 		/* 6 */
	case DHCP_OPTION_LOGSERVER: 	/* 7 */
	case DHCP_OPTION_COOKIESERVER:	/* 8 */
	case DHCP_OPTION_LPRSERVER:	/* 9 */
	case_DHCP_OPTION_IMPRESSSERVER:	/* 10 */
	case_DHCP_OPTION_RESLOCSERVER:	/* 11 */
	case_DHCP_OPTION_SWAPSERVER:	/* 16 */
	case DHCP_OPTION_NETBIOSNAMESERV: /* 44 */
	  j = 0;
	  while (j < len / 4)
	    {
	      if (get_packet_bytes((u_int8_t *) holder, pkt, 4) == 0)
		return;
	      if (!my_args->m)
		display_ipv4("  Address", (u_int8_t *) holder);
	      j++;
	    }
	  break;

	case DHCP_OPTION_HOSTNAME: /* 12 */
	  if (get_packet_bytes((u_int8_t *) holder, pkt, len) == 0)
	    return;
	  holder[len] = '\0';
	  if (!my_args->m)
	    display("  Host name", (u_int8_t *) holder, strlen(holder), 
		    DISP_STRING);
	  break;

	case DHCP_OPTION_DOMAINNAME: /* 15 */
	  if (get_packet_bytes((u_int8_t *) holder, pkt, len) == 0)
	    return;
	  holder[len] = '\0';
	  if (!my_args->m)
	    display("  Domain name", (u_int8_t *) holder, strlen(holder),
		    DISP_STRING);
	  break;

	case DHCP_OPTION_VENDORSPECIFIC: /* 43 */
	  if (get_packet_bytes((u_int8_t *) holder, pkt, len) == 0)
	    return;
	  if (!my_args->m)
	    display("  Parameters", (u_int8_t *) holder, len, DISP_HEX);
	  break;

	case DHCP_OPTION_NETBIOSNODETYPE: /* 46 */
	  if (get_packet_bytes((u_int8_t *) holder, pkt, len) == 0)
	    return;
	  switch(holder[0])
	    {
	    case 0x1: 
	      snprintf(holder, HOLDER_SIZE, "0x%x (B-node / broadcast)", 
		       holder[0]);
	      break;
	    case 0x2:
	      snprintf(holder, HOLDER_SIZE, "0x%x (P-node / point to point)", 
		      holder[0]);
	      break;
	    case 0x4:
	      snprintf(holder, HOLDER_SIZE, "0x%x (M-node / mixed)", 
		       holder[0]);
	      break;
	    case 0x8:
	      snprintf(holder, HOLDER_SIZE, "0x%x (H-node)", holder[0]);
	      break;
	    }
	  if (!my_args->m)
	    display_string("Node type", holder);
	  break;

	case DHCP_OPTION_REQUESTEDIPADDR: /* 50 */
	  if (get_packet_bytes((u_int8_t *) holder, pkt, len) == 0)
	    return;
	  if (!my_args->m)
	    display_ipv4("  Address", (u_int8_t *) holder);
	  else
	    {
	      display_minimal_ipv4((u_int8_t *) holder);
	      display_minimal_string(" ");
	    }
	  break;

	case DHCP_OPTION_IPADDRLEASE: /* 51 */
	  if (get_packet_bytes((u_int8_t *) holder, pkt, 4) == 0)
	    return;
	  if (!my_args->m)
	    display("  Lease time", (u_int8_t *) holder, 4, DISP_DEC);
	  break;

	case DHCP_OPTION_MESSAGETYPE: /* 53 */
	  {
	    u_int8_t msg_type;

	    if (get_packet_bytes(&msg_type, pkt, 1) == 0)
	      return;
	    snprintf(holder, HOLDER_SIZE, "%d (%s)", msg_type, 
		    map2str(dhcp_msgtype_map, msg_type));
	    if (!my_args->m)
	      display_string("  Message type", holder);
	    else
	      {
		display_minimal_string(map2str(dhcp_msgtype_map, msg_type));
		display_minimal_string(" ");
	      }
	  }
	  break;

	case DHCP_OPTION_SERVERID: /* 54 */
	  if (get_packet_bytes((u_int8_t *) holder, pkt, len) == 0)
	    return;
	  if (!my_args->m)
	    display_ipv4("  Address", (u_int8_t *) holder);
	  break;

	case DHCP_OPTION_PARAMREQLIST: /* 55 */
	  for (i=0; i < len; i++)
	    {
	      u_int8_t opt;

	      if (get_packet_bytes((u_int8_t *) &opt, pkt, 1) == 0)
		return;
	      if (!my_args->m)
		{
		  snprintf(holder, HOLDER_SIZE, "%d (%s)", opt, 
			  map2str(dhcp_option_map, opt));
		  display_string("  Option", holder);
		}
	    }
	  break;

	case DHCP_OPTION_MAXDHCPMSGSIZE: /* 57 */
	  if (get_packet_bytes((u_int8_t *) holder, pkt, len) == 0)
	    return;

	/*
	 * Conversion
	 */

	*holder = ntohs ( *holder );

	  if (!my_args->m)
	    display("  Size", (u_int8_t *) holder, 2, DISP_DEC);
	  break;

	case DHCP_OPTION_RENEWALTIME: /* 58 */
	  {
	    u_int32_t renewal_time;

	    if (get_packet_bytes((u_int8_t *) &renewal_time, pkt, len) == 0)
	      return;
	    renewal_time = ntohl(renewal_time);
	    if (!my_args->m)
	      display("  Renewal time", (u_int8_t *) &renewal_time, 4, 
		      DISP_DEC);
	  }
	  break;

	case DHCP_OPTION_REBINDINGTIME: /* 59 */
	  {
	    u_int32_t rebinding_time;

	    if (get_packet_bytes((u_int8_t *) &rebinding_time, pkt, len) == 0)
	      return;
	    rebinding_time = ntohl(rebinding_time);
	    if (!my_args->m)
	      display("  Rebinding time", (u_int8_t *) &rebinding_time, 4, 
		      DISP_DEC);
	  }
	  break;

	case DHCP_OPTION_VENDORCLASSID: /* 60 */
	  if (get_packet_bytes((u_int8_t *) holder, pkt, len) == 0)
	    return;
	  if (!my_args->m)
	    display("  Parameters", (u_int8_t *) holder, len, DISP_HEX);
	  break;

	case DHCP_OPTION_CLIENTID: /* 61 */
	  if (!my_args->m)
	    {
	      u_int8_t hw_type;

	      /* The first byte should be a hardware type */
	      if (get_packet_bytes((u_int8_t *) &hw_type, pkt, 1) == 0)
		return;
	      snprintf(holder, HOLDER_SIZE, "%d (%s)", hw_type, 
		      map2str(arp_hardware_map, hw_type));
	      display_string("  Hardware type", (u_int8_t *) holder);

	      /* If its ethernet, display it as such, otherwise punt */
	      if (hw_type == ARP_HWTYPE_ETHERNET)
		{
		  if (get_packet_bytes((u_int8_t *) holder, pkt, len-1) == 0)
		    return;
		   display("  Hardware address", (u_int8_t *) holder, len-1, 
			   DISP_HEXCOLONS);
		}
	      else
		{
		  if (get_packet_bytes((u_int8_t *) holder, pkt, len-1) == 0)
		    return;
		   display("  Hardware address", (u_int8_t *) holder, len-1, 
			   DISP_HEX);
		}
	    }
	  else
	    {
	      /* In minimal mode, we'll just grab the parameter and continue */
	      if (get_packet_bytes((u_int8_t *) holder, pkt, len) == 0)
		return;
	    }
	  break;

	default:
	  break;
	  /* do nothing for now */
	} /* switch */

    } /* while */

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

}




