/*
 esp_pcap.c

 Author: Liraz Siri <liraz@bigfoot.com>

 Copyright (c) 1998 Liraz Siri <liraz@bigfoot.com>, Ariel, Israel
                    All rights reserved.

 Created: Thu Sep 24 02:52:10 GMT 1998

 Code for handling raw network packets using the pcap API.
*/

#include "includes.h"
#include "esp_pcap.h"
#include "esp_route.h"
#include "buffer.h"
#include <pcap.h>

#include <net/if.h>

#include <netinet/in.h>
#include <netinet/if_ether.h>

/* DUUUUUUUH */
#define RAW_HDRLEN	0

/* Capricious arbitrary numbers! */
#define PPP_HDRLEN      4
#define NULL_HDRLEN	4
#define ETHER_HDRLEN	14
#define FDDI_HDRLEN	13
#define SLIP_HDRLEN     16

struct device {
 pcap_handler f;
 int type;
};

/* This is a pointer for the input buffer in ssh's packet mechanism. */
static Buffer *packet_incoming = NULL;

/* support devices. To be honest, the only way we really HANDLE these
   functions is by stripping the annoying headers off and passing off
   the raw ip packet to a central processing routine */
static struct device devices[] = {
 { ether_handler,	DLT_EN10MB },
 { ether_handler,	DLT_IEEE802 },
 { ppp_handler,		DLT_PPP },
 { slip_handler,	DLT_SLIP },
 { fddi_handler,	DLT_FDDI },
 { null_handler,	DLT_NULL },
 { raw_handler, 	DLT_RAW },
 { NULL, 0 },
};

struct interface {
 pcap_t *pd;
 pcap_handler handler;
 struct interface *next;
};

static int accepted_protocol = 0, on_loopback = 0;
static struct interface *interface_root = NULL;

/* Initialize another (valid) live pcap interface. We're only interested
   in packets heading for our own interface's address. so we use the
   pcap bpf filter mechanism. */
int esp_pcap_init(pcap_t *pd, unsigned long s_addr, unsigned long mask, 
		      int itype, int local)
{
 char bpf_filter[MAX_FILTER_LEN];
 struct interface **interface_node;
 struct bpf_program fp;
 pcap_handler handler;
 struct in_addr sin_addr;

 if(!(handler = esp_pcap_lookup(itype))) {
  error("Device type not supported.");
  return -1;
 }

/* Zip over to the end of the list */
 for(interface_node = &interface_root; (*interface_node) != NULL;
     interface_node = &((*interface_node) -> next));

 *interface_node = (struct interface *)malloc(sizeof(struct interface));

 (*interface_node)->pd = pd;
 (*interface_node)->handler = handler;
 (*interface_node)->next = NULL;

 if(local) on_loopback = 1;

/* We haven't set promiscious mode ourselves, but that doesn't mean 
   we can rely on the device to give us only packets destined for
   our host. So we set up a simple bpf filter down below just to be on 
   the safe side. */

 if(!local) 
 {
  sin_addr.s_addr = s_addr;
  snprintf(bpf_filter, MAX_FILTER_LEN - 1, BPF_FILTER, inet_ntoa(sin_addr));

  if(pcap_compile(pd, &fp, bpf_filter, 0,  mask) < 0 ||
     pcap_setfilter(pd, &fp) < 0) 
  {
   error("BPF filter initialization failed.");
   return -1;
  }
 
 }
 
 return 0;
}

pcap_handler esp_pcap_lookup(int itype)
{
 struct device *device_node;
 for(device_node = devices; device_node != NULL; device_node++)
  if(device_node -> type == itype) return device_node -> f;
 return NULL;
}

int esp_pcap_interfaces_available(void)
{
 return (interface_root != NULL ? 1 : 0);
}

void esp_pcap_wait_for_packets(fd_set *readfds)
{
 int maxfds;
 struct interface *interface_node;

 maxfds = -1;
 FD_ZERO(readfds);
 for(interface_node = interface_root; interface_node != NULL;
     interface_node = interface_node -> next) {
  FD_SET(pcap_fileno(interface_node->pd), readfds);
  if(pcap_fileno(interface_node->pd) > maxfds) 
   maxfds = pcap_fileno(interface_node->pd);
 }

again:
 if(select(maxfds + 1, readfds, NULL, NULL, NULL) < 0) {
  if(errno == EINTR) goto again;
  return;
 }
 
}

/* Change of plans. This should now be called from packet.c by 
   packet_esp_pcap_handlepacket */
int esp_pcap_handlepacket(fd_set *readfds, Buffer *incoming)
{
 struct interface *interface_node;

 accepted_protocol = 0;
 packet_incoming = incoming;

 for(interface_node = interface_root; interface_node != NULL;
     interface_node = interface_node -> next) 
  if(FD_ISSET(pcap_fileno(interface_node->pd), readfds)) {
   pcap_dispatch(interface_node->pd, 1, interface_node->handler, NULL);
   return 1;
  }

 return -1;
}

/* Returns true if we received, handled, filtered and finally ACCEPTED
   a packet. (breaking sweat) */
int esp_pcap_accepted_protocol(void)
{
 return accepted_protocol;
}

/* strip off ethernet headers */
void 
ether_handler(u_char *user, const struct pcap_pkthdr *h, const u_char *p)
{
 u_int length = h->len;
 struct ether_header *eth;

 if(h->caplen < sizeof(struct ether_header)) 
  return;

 eth = (struct ether_header *)p;
 if( ntohs(eth -> ether_type) < 1500 ) {
  p += 8;
  length -= 8;
  eth = (struct ether_header *)p;
 }

 p += sizeof(struct ether_header);
 length -= sizeof(struct ether_header);

 if( ntohs(eth->ether_type) == ETHERTYPE_IP ) 
  esp_pcap_handle_ippkt(p, length);
}

/* Handle packets zipping off a DLT_PPP interface. By handle I mean
   decease non-ip related headers */
void
ppp_handler(u_char *user, const struct pcap_pkthdr *h, const u_char *p)
{
 if(h->caplen >= PPP_HDRLEN)
  esp_pcap_handle_ippkt((p + PPP_HDRLEN), h->len - PPP_HDRLEN);
}

/* Handle packets zipping off a DLT_SLIP interface. By handle I mean
   decease non-ip related headers */
void
slip_handler(u_char *user, const struct pcap_pkthdr *h, const u_char *p)
{
 if(h->caplen >= SLIP_HDRLEN)
  esp_pcap_handle_ippkt((p + SLIP_HDRLEN), h->len - SLIP_HDRLEN);
}

/* Handle packets zipping off a DLT_FDDI interface. By handle I mean
   decease non-ip related headers */
void
fddi_handler(u_char *user, const struct pcap_pkthdr *h, const u_char *p)
{
 if(h->caplen >= FDDI_HDRLEN)
  esp_pcap_handle_ippkt((p + FDDI_HDRLEN), h->len - FDDI_HDRLEN);
}


/* Handle packets zipping off a DLT_NULL interface (loopback?). By handle
   I mean decease non-ip related headers */
void
null_handler(u_char *user, const struct pcap_pkthdr *h, const u_char *p)
{
 if(h->caplen >= NULL_HDRLEN)
  esp_pcap_handle_ippkt((p + NULL_HDRLEN), h->len - NULL_HDRLEN);
}

void
raw_handler(u_char *user, const struct pcap_pkthdr *h, const u_char *p)
{
 esp_pcap_handle_ippkt(p, h->len);
}

void esp_pcap_handle_ippkt(const u_char *packet, u_int length)
{
 static unsigned drop_packet = 0;

 /*-- On loopback, when reading directly from a raw loopback interface
      we expieriance an 'echo' phononema. We receive every packet
      twice. the drop_packet mechanism should correct this. --*/
 if(on_loopback)
 {
  drop_packet = !drop_packet;
  if(!drop_packet) return;
 }

 if(!fragmented(packet, length) &&
    esp_route_handle_packet(packet, length) < 0)
  accepted_protocol = esp_receive_raw(packet_incoming, packet, length);
}

int fragmented(const u_char *packet, u_int length)
{
 int len;
 struct iprec *ip_rec;

 ip_rec = (struct iprec *)packet;
 if(length < sizeof(struct iprec) ||
    length < ntohs(ip_rec->ip_len) ||
    (ntohs(ip_rec->ip_off) & 0x1fff) != 0)
  return 1;

 return 0;
}
