/*

Copyright (C) 2000 - 2006 Christian Kreibich <christian@whoop.org>.

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to
deal in the Software without restriction, including without limitation the
rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
sell copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in
all copies of the Software and its documentation and acknowledgment shall be
given in the documentation and software packages that this Software was
used.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

*/
#ifdef HAVE_CONFIG_H
#  include <config.h>
#endif

#include <libnd_icmp.h>

static LND_Protocol *icmp;

static LND_Protocol *
icmp_get_ip(void)
{
  static LND_Protocol *ip = NULL;

  if (!ip)
    ip = libnd_proto_registry_find(LND_PROTO_LAYER_NET, 0x0800);
  
  return ip;
}


static struct ip *
icmp_get_last_ip_before_icmp(const LND_Packet *packet, struct icmp **icmphdr)
{
  LND_Protocol *ip;
  GList *l;
  LND_ProtoData *pd;
  struct ip *iphdr = NULL;

  if (!packet || !(ip = icmp_get_ip()))
    return NULL;

  for (l = packet->pd; l; l = g_list_next(l))
    {
      pd = (LND_ProtoData *) l->data;

      if (pd->inst.proto == icmp)
	{
	  if (icmphdr)
	    *icmphdr = (struct icmp*) pd->data;
	  break;
	}

      if (pd->inst.proto == ip)
	iphdr = (struct ip*) pd->data;
    }

  return iphdr;
}


/**
 * icmp_header_complete -
 *
 * checks whether the ICMP message is complete by looking at the
 * IP length field -- easier since ICMP messages can be of varying sizes.
 */
static gboolean
icmp_header_complete(const LND_Packet *packet)
{
  struct ip *iphdr = NULL;

  iphdr = icmp_get_last_ip_before_icmp(packet, NULL);
  if (!iphdr)
    return FALSE;

  return (((guchar*) iphdr) + ntohs(iphdr->ip_len) <= libnd_packet_get_end(packet));
}




/* Plugin hook implementations: ---------------------------------------- */

const char *
name(void)
{
  return ("ICMP Plugin");
}


const char *
description(void)
{
  return ("A plugin providing support for ICMPv4.\n");
}


const char *
author(void)
{
  return ("Christian Kreibich, <christian@whoop.org>");
}


const char *
version(void)
{
  return VERSION;
}


LND_Protocol *
init(void)
{
  icmp = libnd_proto_new("ICMP", LND_PROTO_LAYER_NET | LND_PROTO_LAYER_TRANS, IPPROTO_ICMP);
			     		
  icmp->init_packet     = libnd_icmp_init_packet;
  icmp->header_complete = libnd_icmp_header_complete;
  icmp->fix_packet      = libnd_icmp_fix_packet;

  return icmp;
}


/* Protocol method implementations: ------------------------------------ */

void       
libnd_icmp_init_packet(LND_Packet *packet, guchar *data, guchar *data_end)
{
  LND_Protocol *ip;
  struct icmp *icmphdr;
  struct ip *iphdr;

  /* Cast the data pointer into your protocol's header */
  icmphdr = (struct icmp *) data;

  if (!icmp_header_complete(packet))
    {
      libnd_raw_proto_get()->init_packet(packet, data, data_end);
      return;
    }

  libnd_packet_add_proto_data(packet, icmp, data, data_end);  

  /* We'll need IP data below, make sure we can find the IP protocol.
   */
  if (! (ip = icmp_get_ip()))
    {
      D(("WARNING -- IP not found\n"));
      return;
    }

  /* For some ICMP messages we offer the hex editor to edit the data.
   * Otherwise, we do nothing, as ICMP is the innermost protocol. */

  if (libnd_icmp_header_is_error(icmphdr))
    {      
      ip->init_packet(packet, data + 8, data_end);
      return;
    }
  
  iphdr = icmp_get_last_ip_before_icmp(packet, NULL);
  if (!iphdr)
    return;

  if (((guchar*) icmphdr) + 8 >= ((guchar*) iphdr) + ntohs(iphdr->ip_len))
    return;

  /*
  D(("Pointers: %p %p, difference: %u\n", 
     (((guchar*) icmphdr) + 8), (((guchar*) iphdr) + ntohs(iphdr->ip_len)),
     (((guchar*) iphdr) + ntohs(iphdr->ip_len)) - (((guchar*) icmphdr) + 8)));
  */

  switch (icmphdr->icmp_type)
    {
      /* Echo requests and replies can have optional data that must
	 be echoed. we display that using the raw data plugin:
      */
    case ICMP_ECHO:
    case ICMP_ECHOREPLY:
      libnd_raw_proto_get()->init_packet(packet, data + 8, data_end);
    }
}


gboolean
libnd_icmp_header_complete(const LND_Packet *packet, guint nesting)
{
  if (!packet)
    return FALSE;

  return icmp_header_complete(packet);
  TOUCH(nesting);
}


gboolean
libnd_icmp_message_complete(const LND_Packet *packet)
{
  struct icmp *icmphdr;
  struct ip *iphdr;

  if (!packet)
    return FALSE;

  icmphdr = (struct icmp *) libnd_packet_get_data(packet, icmp, 0);

  if (!icmphdr)
    return FALSE;

  /* If it's an error message, check whether we have the full
   * datagram that contains the ICMP message.
   */
  if (libnd_icmp_header_is_error(icmphdr))
    {
      iphdr = (struct ip *) ((guchar *) icmphdr + 8);
      
      return ((guchar *) iphdr + (iphdr->ip_hl << 2) + 8 <= libnd_packet_get_end(packet));
    }

  /* For all other cases, check individually.
   */
  switch (icmphdr->icmp_type)
    {
    case ICMP_ECHO:
    case ICMP_ECHOREPLY:

      iphdr = icmp_get_last_ip_before_icmp(packet, NULL);
      if (!iphdr)
	return FALSE;

      return ((guchar *) iphdr + ntohs(iphdr->ip_len) <= libnd_packet_get_end(packet));

    case ICMP_TIMESTAMP:
    case ICMP_TIMESTAMPREPLY:
      return ((guchar *) icmphdr + 20 <= libnd_packet_get_end(packet));

    case ICMP_INFO_REQUEST:
    case ICMP_INFO_REPLY:
    case 10:
      return ((guchar *) icmphdr + 8 <= libnd_packet_get_end(packet));

    case ICMP_ADDRESS:
    case ICMP_ADDRESSREPLY:
      return ((guchar *) icmphdr + 12 <= libnd_packet_get_end(packet));

    case 9:
      {
	struct icmp *icmphdr_adv = (struct icmp *) icmphdr;

	return ((guchar *) icmphdr + 8 + icmphdr_adv->icmp_hun.ih_rtradv.irt_num_addrs * 8 <= libnd_packet_get_end(packet));
      }
      
    default:
      break;
    }

  return FALSE;
}


guint16    
libnd_icmp_checksum(const LND_Packet *packet)
{
  guint16         old_sum, sum;
  struct icmp *icmphdr;
  struct ip      *iphdr;

  
  iphdr = icmp_get_last_ip_before_icmp(packet, &icmphdr);
  
  old_sum = icmphdr->icmp_cksum;
  icmphdr->icmp_cksum = 0;
  sum = libnd_misc_in_cksum((u_short *)icmphdr, ntohs(iphdr->ip_len) - (iphdr->ip_hl << 2), 0);
  icmphdr->icmp_cksum = old_sum;
  
  return sum;
}


gboolean   
libnd_icmp_csum_correct(const LND_Packet *packet, guint16 *correct_sum)
{
  struct icmp *icmphdr;
  guint16 sum;

  if (!packet)
    return FALSE;

  icmphdr = (struct icmp *) libnd_packet_get_data(packet, icmp, 0);
  
  sum = libnd_icmp_checksum(packet);
  
  if (correct_sum)
    *correct_sum = sum;
  
  return (icmphdr->icmp_cksum == sum);
}


gboolean
libnd_icmp_fix_packet(LND_Packet *packet)
{
  struct icmp *icmphdr;
  guint16      correct_sum;
  
  if (!packet)
    return FALSE;
  
  if (!libnd_icmp_csum_correct(packet, &correct_sum))
    {
      icmphdr = (struct icmp*) libnd_packet_get_data(packet, icmp, 0);
      icmphdr->icmp_cksum = correct_sum;
      libnd_packet_modified(packet);
      return TRUE;
    }
  
  return FALSE;
}


LND_Protocol *
libnd_icmp_get(void)
{
  return icmp;
}


gboolean
libnd_icmp_header_is_error(struct icmp *icmphdr)
{
  if (!icmphdr)
    return FALSE;

  switch (icmphdr->icmp_type)
    {
    case ICMP_DEST_UNREACH:
    case ICMP_SOURCE_QUENCH:
    case ICMP_REDIRECT:
    case ICMP_TIME_EXCEEDED:
    case ICMP_PARAMETERPROB:
      return TRUE;
    }

  return FALSE;
}














syntax highlighted by Code2HTML, v. 0.9.1