/*
 *  pcap.c - interface to libpcap
 *
 *  Copyright (C) 1999 Robert Cheramy <tibob@via.ecp.fr>
 *  Copyright (C) 1999 Andres Krapf <dae@via.ecp.fr>
 *

 20001107 : tibob : SIGUSR1 support

 */

/*
 *  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 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., 675 Mass Ave, Cambridge, MA 02139, USA.
 */

#include <unistd.h>
#include <netinet/in.h>
#include <netinet/in_systm.h>
#include <netinet/ip.h>
#include <pcap.h>
#include <stdlib.h>
#include <sys/socket.h>
#include <sys/types.h>

#include "config.h"
#include "data.h"
#include "filter.h"
#include "init.h"
#include "pcap.h"
#include "utils.h"

#define SNAPLEN 200

/* dirty code */
extern int promisc;

/* Global variable. This forbids to open 2 pcap descriptors. */
pcap_t  *pcap_global_descriptor = NULL;
struct bpf_program pcap_filter;
int pcap_filter_initialized = 0;

int     offset;

int SigHup = 0;
int SigDump = 0;
extern struct AllLogsType * pAllLogs;

int iphdr_chksum(u_char *iphead, int size)
{
  unsigned long int sum = 0;
  int i;

  for (i = (size * 4) - 2; i > -1; i-=2) {
    sum += iphead[i] + 256 * iphead[i+1];
  }
  sum = (sum & 0xffff) + (sum >> 16);
  /* mmm I'm not sure the "(sum >> 16)" is useful, but better be sure */
  sum += (sum >> 16) + 1;
  return (sum & 0xffff);
}


/*
 * Find a defaultpcap device
 */
char *pcap_default_device() {
  char errbuf[PCAP_ERRBUF_SIZE];
  char *tdev;

  tdev = pcap_lookupdev(errbuf);
  if (!tdev) { 
    fprintf(stderr, "[pcap] pcap_lookupdev error: %s\n", errbuf);
    exit(1);
  }
  return (tdev);
}


/*
 * Opens a Pcap descriptor
 * promisc = 0 for no promisc mode, 1 for this mode.
*/
int openpcap(char *device, int promisc) {
  char errbuf[PCAP_ERRBUF_SIZE];
  int datalink;
  
  if (NULL != pcap_global_descriptor) {
    printf("[pcap] I can't open two pcap descriptors\n");
    return 0 ;
  }

  pcap_global_descriptor = pcap_open_live(device, SNAPLEN, promisc, 1000, errbuf);
  if (NULL == pcap_global_descriptor) {
    fprintf(stderr, "[pcap] error opening pcap: %s\n", errbuf);
    return 0;
  }

  /* compile the IPv4 filter and assign it to the global descriptor */
  if (pcap_compile(pcap_global_descriptor, &pcap_filter,
                   "ip", 1, 0) == 1) {
    fprintf(stderr, "[pcap] error compiling filter \"ip and not ip6\": %s\n",
            pcap_geterr(pcap_global_descriptor));
    return 0;
  }
  pcap_filter_initialized = 1;
  if (pcap_setfilter(pcap_global_descriptor, &pcap_filter) == -1) {
    fprintf(stderr, "[pcap] error assigning filter to descriptor: %s",
            pcap_geterr(pcap_global_descriptor));
    return 0;
  }

  /* set the offset to the beginning of the iphdr */
  if ((datalink = pcap_datalink(pcap_global_descriptor)) < 0) {
    fprintf(stderr, "[pcap] error getting datalink info : %s\n",pcap_geterr(pcap_global_descriptor));
    return 0;
  }

  /*
    these offsets were taken from queso, written by 
    Jordi Murgo <savage@apostols.org>
    see http://apostols.org/projectz/queso/
  */
  switch(datalink) {
  case DLT_EN10MB:
    offset = 14; break;
  case DLT_NULL:
  case DLT_PPP:
    offset =  4; break;
  case DLT_SLIP:
    offset = 16; break;
#ifndef __OS_OPENBSD__
  case DLT_RAW:
    offset =  0; break;
  case DLT_SLIP_BSDOS:
  case DLT_PPP_BSDOS:
    offset = 24; break;
#endif /* __OS_OPENBSD__ */
  case DLT_ATM_RFC1483:
    offset =  8; break;
  case DLT_IEEE802:
    offset = 22; break;
  default:
    fprintf(stderr, "[pcap] Unknown datalink type : %d.", datalink);
    return 0;
  }
  return 1;
}

void closepcap()
{
  if (NULL == pcap_global_descriptor) {
    /* nothing to do */
  } else {
    pcap_close(pcap_global_descriptor);
    pcap_global_descriptor = NULL;
  }
  if (pcap_filter_initialized) {
    pcap_freecode(&pcap_filter);
    pcap_filter_initialized = 0;
  }
}

u_char *getnextippkt()
{
  struct pcap_pkthdr useless;
  u_char * packet;

  if (NULL == pcap_global_descriptor) {
    fprintf(stderr, "[pcap] Can't read non-opened pcap descriptor");
  }

  for (;;) {
    packet = (u_char *) pcap_next(pcap_global_descriptor, &useless);
    if (NULL == packet) 
      continue;
    packet = packet + offset;

    if ( ((struct ip *) packet)->ip_hl < 5 || ((struct ip *) packet)->ip_v != 4 || iphdr_chksum(packet, ((struct ip *) packet)->ip_hl) != 0 ) {
      /* Not an IP packet */
      continue;
    }

    /* handle HUP signal */

    if (SigHup) {
      struct AllLogsType *pTempLog;
      for (pTempLog = pAllLogs; pTempLog; pTempLog = pTempLog->Next) {
	data_dump(pTempLog);
	data_clear(pTempLog);
      }

      Clean();
      ReInit();
      SigHup = 0;
    }
    /* handle USR1 signal */

    if (SigDump) {
      struct AllLogsType * pTempLog;
      for (pTempLog = pAllLogs; pTempLog; pTempLog = pTempLog->Next) {
	data_dump(pTempLog);
      }
      SigDump = 0;
    }
    break;
  }
  return (packet);
}

