/**************************************************
OpengateM - a MAC address authentication system
 module for capturing packets with libpcap

Copyright (C) 2011 Opengate Project Team
Written by Yoshiaki Watanabe

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., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.

Email: watanaby@is.saga-u.ac.jp
**************************************************/
#include "opengatemd.h"
#include <pcap.h>

pcap_t *handle;			/* pcap handle */

/*******************************
initialize libpcap
*******************************/
int initPcap(void){
  char myMacAddress[ADDRMAXLN];                      /* mac of pcap device */
  char errbuf[PCAP_ERRBUF_SIZE];	             /* Error string */
  struct bpf_program fp;		             /* The compiled filter */
  char filterExpr[FILTERMAXLN];                      /* filter expression */
  bpf_u_int32 mask;	                             /* my netmask */
  bpf_u_int32 net;	                             /* my IP */
  int snaplen=atoi(GetConfValue("Pcap/SnapLength")); /* packet snap length */ 
  int timeout=atoi(GetConfValue("Pcap/Timeout"));    /* pcap get timeout */
  int promiscuous=atoi(GetConfValue("Pcap/Promiscuous")); /* promiscuous mode */
  char* dev=GetConfValue("Pcap/Device");             /* pcap device */

  /* set filter expression. conf value might be [(not ether src %s) and ...] */
  GetMyMacAddress(myMacAddress);
  snprintf(filterExpr, FILTERMAXLN, GetConfValue("Pcap/Filter"), myMacAddress);

  /* Find the properties for the device */
  if (pcap_lookupnet(dev, &net, &mask, errbuf) == -1) {
    err_msg("ERR at %s#%d: can't get netmask: %s",__FILE__,__LINE__,errbuf);
    net = 0;
    mask = 0;
  }

  /* Open pcap */
  seteuid(0);   /* get root privilege */
  handle = pcap_open_live(dev, snaplen, promiscuous, timeout, errbuf);
  seteuid(getuid());   /* drop root privilege */
  if (handle == NULL) {
    err_msg("ERR at %s#%d: can't open device %s: %s",__FILE__,__LINE__,dev,errbuf);
    terminateProg(0);
  }
  
  /* If the link layer is not Ehternet, exit */
  if(pcap_datalink(handle)!=DLT_EN10MB){
    err_msg("ERR at %s#%d: Link layer is not ethernet",__FILE__,__LINE__);
    terminateProg(0);
  }

  /* Compile and apply the filter */
  if (pcap_compile(handle, &fp, filterExpr, 0, net) == -1) {
    err_msg("ERR at %s#%d: can't compile pcap filter",__FILE__,__LINE__);
    terminateProg(0);
  }
  if (pcap_setfilter(handle, &fp) == -1) {
    err_msg("ERR at %s#%d: can't set pcap filter",__FILE__,__LINE__);
    terminateProg(0);
  }

  pcap_freecode(&fp);
  return 0;
}

/*******************************
get next packet from pcap 
ret=0:no packet,1:ip packet,-1:other packet
*******************************/
int getNextPacketFromPcap(unsigned char* macAndIpAddressRaw, int* pAddrLen, int* pTtl){
  struct pcap_pkthdr header;	/* The header captured by pcap */
  const u_char *packet;		/* The actual packet */
  struct ip *ipv4h; 
  struct ip6_hdr *ipv6h; 
  struct ether_header *ethhdr;
  int ret=0;

  /* initialize to null string */
  bzero(macAndIpAddressRaw,MACADDRLN+IPV6ADDRLN);
  *pTtl=0;
  *pAddrLen=0;

  /* Grab a packet */
  if((packet = pcap_next(handle, &header)) == NULL)return 0; /* 0=no packet*/

  /* Get IpAddress and MacAddress from the packet */
  if (header.caplen < sizeof(struct ip) + sizeof(struct ether_header)) { 
    return -1; /* -1=not ip packet */
  } 
  
  /* get mac address from ether header */
  ethhdr = (struct ether_header *)packet; 
  memcpy(macAndIpAddressRaw, ethhdr->ether_shost, MACADDRLN);

  /* get ip address from ip header */
  switch (ntohs(ethhdr->ether_type)) { 
    case ETHERTYPE_IP: 
      *pAddrLen=IPV4ADDRLN+MACADDRLN;
      ipv4h = (struct ip *)(packet + sizeof(struct ether_header)); 
      memcpy( macAndIpAddressRaw+MACADDRLN, &ipv4h->ip_src, IPV4ADDRLN);
      *pTtl = ipv4h->ip_ttl;
      ret=1; /* 1=success */
      break;

    case ETHERTYPE_IPV6: 
      *pAddrLen=IPV6ADDRLN+MACADDRLN;
      ipv6h = (struct ip6_hdr *)(packet + sizeof(struct ether_header)); 
      memcpy(macAndIpAddressRaw+MACADDRLN, &ipv6h->ip6_src, IPV6ADDRLN);
      *pTtl = ipv6h->ip6_ctlun.ip6_un1.ip6_un1_hlim;
      ret=1; /* 1=success */
      break; 

    default:
      ret=-1; /* -1=not ip packet */
      break;
  } 
  return ret;
}

/**************************
close pcap
**************************/
void closePcap(void){

  /* And close the session */
  pcap_close(handle);

}

/********************************************
 get mac address for the pcap device
********************************************/
int getMyMacAddress(char* macAddress){
    struct ifaddrs* ifapHead;
    int          found = FALSE;
    struct ifaddrs* ifap;
    struct sockaddr_dl* sdl = NULL;
    char*       pcapDev;

    /* set default value */
    strlcpy(macAddress, "00:00:00:00:00:00", ADDRMAXLN);

    /* get pcap device name from conf file */
    pcapDev = GetConfValue("Pcap/Device");
    if(isNull(pcapDev)) return FALSE;

    /* get ifaddrs */
    if (getifaddrs(&ifapHead) != 0){
      err_msg("ERR at %s#%d: getifaddrs failed",__FILE__,__LINE__);
      return FALSE;
    }

    /* search pcap device */
    for (ifap = ifapHead; ifap!=NULL; ifap = ifap->ifa_next) {
      if ((ifap->ifa_addr->sa_family == AF_LINK) 
	  && (strcmp(ifap->ifa_name, pcapDev) == 0)) {
	found=TRUE;
	break;
      }
    }

    /* if not found, error return */
    if(!found){
      err_msg("ERR at %s#%d: cannot find interface %s",
	      __FILE__,__LINE__, pcapDev);
      if(ifapHead) freeifaddrs(ifapHead);
      return FALSE;
    }

    /* if found, convert to [12:34:56:78:9a:bc] */
    sdl = (struct sockaddr_dl *)ifap->ifa_addr;
    if(sdl != NULL){
      ConvertMacFromRawToDisplay((unsigned char*)LLADDR(sdl), macAddress);
    }
    if(ifapHead) freeifaddrs(ifapHead);

    return TRUE;
}

/*****************************
convert IP address from network raw form to display form 
*****************************/
void convertIpFromRawToDisplay(unsigned char* ipAddressRaw, int ipAddrLen, char* ipAddress){
  
  /* convert ip address from network form to presentation form */
  switch (ipAddrLen) { 
  case IPV4ADDRLN: 
    inet_ntop(AF_INET, ipAddressRaw, ipAddress, ADDRMAXLN);
    break;
    
  case IPV6ADDRLN: 
    inet_ntop(AF_INET6, ipAddressRaw, ipAddress, ADDRMAXLN);
    break;
    
  default:
    ipAddress[0]='\0';
    break;
  }
}

/*****************************
convert IP address from network raw form to display form 
*****************************/
void convertMacFromRawToDisplay(unsigned char* macAddressRaw, char* macAddress){
  strlcpy(macAddress, ether_ntoa((struct ether_addr*)macAddressRaw), ADDRMAXLN);
}

/***********************************************
 routines for debugging output 
**********************************************/
int InitPcap(void){
  int ret;
  if(debug>1) err_msg("DEBUG:=>initPcap( )");
  ret = initPcap();
  if(debug>1) err_msg("DEBUG:(%d)<=initPcap( )",ret);
  return ret;
}

int GetNextPacketFromPcap(unsigned char* macAndIpAddressRaw, int* pAddrLen, int* pTtl){
  int ret;
  if(debug>2) err_msg("DEBUG:=>getNextPacketFromPcap( )");
  ret = getNextPacketFromPcap(macAndIpAddressRaw, pAddrLen, pTtl);
  if(debug>2) err_msg("DEBUG:(%d)<=getNextPacketFromPcap(%x,%d,%d)",ret,macAndIpAddressRaw[0],*pAddrLen,*pTtl);
  return ret;
}

void ClosePcap(void){
  if(debug>1) err_msg("DEBUG:=>closePcap( )");
  closePcap();
  if(debug>1) err_msg("DEBUG:<=closePcap( )");
}


int GetMyMacAddress(char* macAddress){
  int ret;
  if(debug>1) err_msg("DEBUG:=>getMyMacAddress( )");
  ret = getMyMacAddress(macAddress);
  if(debug>1) err_msg("DEBUG:(%d)<=getMyMacAddress(%s)",ret,macAddress);
  return ret;
}

void ConvertIpFromRawToDisplay(unsigned char* ipAddressRaw, int ipAddrLen, char* ipAddress){
  if(debug>1) err_msg("DEBUG:=>convertIpFromRawtoDisplay(%x,%d,)",ipAddressRaw[0],ipAddrLen);
  convertIpFromRawToDisplay(ipAddressRaw, ipAddrLen, ipAddress);
  if(debug>1) err_msg("DEBUG:<=convertIpFromRawToDisplay(,,%s)",ipAddress);
}

void ConvertMacFromRawToDisplay(unsigned char* macAddressRaw, char* macAddress){
  if(debug>1) err_msg("DEBUG:=>closePcap(%x)",macAddressRaw[0]);
  convertMacFromRawToDisplay(macAddressRaw, macAddress);
  if(debug>1) err_msg("DEBUG:<=closePcap(%s)",macAddress);
}

/**************test main********************
int testmain(){

  char ipAddress[ADDRMAXLN];
  char macAddress[ADDRMAXLN];
  int count=0;
  int len,ttl;

  initPcap();
  
  while(count<50){
    if(getNextPacketFromPcap(macAddress, ipAddress, &len, &ttl)){
      count++;
      printf("MAC[%s],IP[%s]\n", macAddress, ipAddress);
    }
  }

  closePcap();
  return 0;
}
******************************************/
