/* Main sniffer routines.

  The source code may be redistributed, as long it remains intact, and
  with this License included. You may modify or use this source code in
  anyway you see fit, but credit must be given where applicable.

  This software comes with NO warranty, and the author is NOT liable for
  any damages which may come from the use of this software.

  You may not use the name of the author to promote this software.

 shadows@whitefang.com (Thamer AL-Herbish)

*/

#include <config.h>
#include <common.h>
#include <log.h>

extern void add_data(struct con data);
static pcap_t *pcap;
int hosts = 0;
extern char *filter;
extern int always_log;

/* Broken libpcaps need this. */
int fddipad;

static void block_signal(void)
{
  sigset_t mask;

  sigemptyset(&mask);
  sigaddset(&mask,SIGALRM);
  sigprocmask(SIG_BLOCK,&mask,NULL);

  return;
}
static void unblock_signal(void)
{
  sigset_t mask;

  sigemptyset(&mask);
  sigaddset(&mask,SIGALRM);
  sigprocmask(SIG_UNBLOCK,&mask,NULL);
  
  return;
}

static struct con data;  

#ifndef SYS_LINUX
/* ALL packets are presumed ETHERTYPE_IP and transport protocol TCP
   since the bpf filter should of taken care of it.*/
#ifdef ETHERTYPE_LINK
static void parse_packet(unsigned char *user,const struct pcap_pkthdr *pk,const unsigned char *packet)
{
  struct ether_header *eth;
  struct ip *iphdr;
  struct tcphdr *tcphdr;
  unsigned int total;
  
  eth = (struct ether_header *)packet;
  iphdr = (struct ip *)(packet + ETHER_HDR_LEN);

  total = (iphdr->ip_hl << 2);
  total += ETHER_HDR_LEN;

  if(total > pk->caplen) 
    goto truncated;

  tcphdr = (struct tcphdr *)(packet+total);

  /* need 14 bytes of data from the tcp header. */

  total += 14;

  if(total > pk->caplen)
    goto truncated;

  data.sport = tcphdr->th_sport;
  data.dport = tcphdr->th_dport;

  data.ack = tcphdr->th_ack;
  data.seq = tcphdr->th_seq;
  
  data.src = iphdr->ip_src;
  data.dst = iphdr->ip_dst;

  bcopy(eth->ether_shost,&data.mac_src,
	(sizeof(unsigned char) * ETHER_ADDR_LEN));
  bcopy(eth->ether_dhost,&data.mac_dest,
	(sizeof(unsigned char) * ETHER_ADDR_LEN));

  data.timestamp = pk->ts;
  data.flags = tcphdr->th_flags;
  block_signal();
  add_data(data);
  unblock_signal();

  
  return;

truncated:
  error_log("WARNING:Got a truncated packet!");
  
}
#elif PPPTYPE_LINK
/* PPP support for FreeBSD 2.1.5 (only used for home debugging) */
#include <net/if_ppp.h>
static void parse_packet(unsigned char *user,const struct pcap_pkthdr *pk,const unsigned char *packet)
{
  struct ip *iphdr;
  struct tcphdr *tcp;
  unsigned int total;

  iphdr = (struct ip *)(packet + PPP_HDRLEN);
  total = (iphdr->ip_hl << 2);
  total += PPP_HDRLEN;

  tcp = (struct tcphdr *)(packet+total);

  total += 14;

  if(total > pk->caplen)
    goto truncated;

  data.sport = tcp->th_sport;
  data.dport = tcp->th_dport;

  data.ack = tcp->th_ack;
  data.seq = tcphdr->th_seq;
  
  data.src = iphdr->ip_src;
  data.dst = iphdr->ip_dst;

  data.timestamp = pk->ts;
  data.flags = tcp->th_flags;
  block_signal();
  add_data(data);
  unblock_signal();

  return;
truncated:
  error_log("Got truncated packet");
  return;
}
#endif
#else
/* Linux support */
static void parse_packet(unsigned char *user,const struct pcap_pkthdr *pk,const unsigned char *packet)
{
  struct ethhdr *eth;
  struct iphdr *iphdr;
  struct tcphdr *tcphdr;
  unsigned int total;
  unsigned char *ptr;
  
  eth = (struct ethhdr *)packet;
  iphdr = (struct iphdr *)(packet + ETHER_HDR_LEN);

  total = (iphdr->ihl << 2);
  total += ETHER_HDR_LEN;

  if(total > pk->caplen) 
    goto truncated;

  tcphdr = (struct tcphdr *)(packet+total);

  /* need 14 bytes of data from the tcp header. */

  total += 14;

  if(total > pk->caplen)
    goto truncated;

  data.sport = tcphdr->source;
  data.dport = tcphdr->dest;

  data.ack = tcphdr->ack_seq;
  data.seq = tcphdr->th_seq;
  
  data.src.s_addr = iphdr->saddr;
  data.dst.s_addr = iphdr->daddr;

  bcopy(eth->h_source,&data.mac_src,
	(sizeof(unsigned char) * ETHER_ADDR_LEN));
  bcopy(eth->h_dest,&data.mac_dest,
	(sizeof(unsigned char) * ETHER_ADDR_LEN));

  data.flags = *((char *)tcphdr+13);
  
  block_signal();
  add_data(data);
  unblock_signal();

  return;

truncated:
  error_log("WARNING:Got a truncated packet!");
  
}
#endif

void start_sniffing(char *device)
{
  struct bpf_program bp;
  unsigned long ip, mask;
  char error_buff[PCAP_ERRBUF_SIZE];
  struct in_addr interface;

  bzero((char *)&data,sizeof(data));

  /* Open the specified device */
#ifdef ETHERTYPE_LINK
  if((pcap = pcap_open_live(device,ETHERBUFF,1,0,error_buff)) == NULL) {
    error_log("Could'nt open device %s: %s",device,error_buff);
    exit(-1);
  }
#elif PPPTYPE_LINK
  if((pcap = pcap_open_live(device,PPPBUFF,1,0,error_buff)) == NULL) {
    error_log("Could'nt open device %s: %s",device,error_buff);
    exit(-1);
  }
#endif
  if(pcap_lookupnet(device,&ip,&mask,error_buff) < 0) {
    error_log("Could'nt lookupnet: %s",error_buff);
    exit(-1);
  }

  interface.s_addr = ip;
  info_log("Sniffing on interface: %s",inet_ntoa(interface));

  /* This doesnt stop normal acks from coming, we have to deal with it
     anyway */
    
  if(filter == NULL) {
    if(pcap_compile(pcap,&bp,DEFAULT_FILTER,0,mask) < 0) {
      error_log("Could'nt compile bpf instructions: %s",pcap_geterr(pcap));
      exit(-1);
    }
  }
  else {
      if(pcap_compile(pcap,&bp,filter,0,mask) < 0) {
      error_log("Could'nt compile bpf instructions: %s",pcap_geterr(pcap));
      exit(-1);
    }
  }
  if(pcap_setfilter(pcap,&bp) < 0) {
    error_log("Could'nt set filter: %s",pcap_geterr(pcap));
    fprintf(stderr,"Error in filter: %s\n",pcap_geterr(pcap));
    exit(-1);
  }
  
  while(1) {
    if(pcap_loop(pcap,-1,parse_packet,(char *)0) < 0) {
     error_log("pcap_loop: %s",pcap_geterr(pcap));
     exit(-1);
    }
  }
  /* We should never ever reach here. */
  return;
}

void log_stats(void)
{
  struct pcap_stat statp;
  static unsigned long dropped = 0;

  if(!(pcap_stats(pcap,&statp))) {
  if((statp.ps_drop > dropped)|| always_log) {
    info_log("Dropped by kernel: %lu",(statp.ps_drop - dropped));
    dropped  = (unsigned long)statp.ps_drop;
  }
  return;
  }
}
