/* 
 * Network accounting
 * capture-pcap.c - capture raw packets - pcap version
 * (C) 1996 Ulrich Callmeier
 */

#include "netacct.h"
#include <pcap.h>
#include <sys/ethernet.h>
#include <netinet/ip_icmp.h>
#include <netinet/tcp.h>
#include <netinet/udp.h>

void handle_ip(unsigned char buf[], char *devname, char *user);

pcap_t *pcap;
char errbuff[PCAP_ERRBUF_SIZE];

#define PCAP_SNAPLEN 128
#define PCAP_TMOUT 1000

void handle_frame (unsigned char[], int);

void init_capture()
{
  struct promisc_device *p;

  pcap = NULL;

  p = cfg -> promisc;
    
  if(p!=NULL)
    {
      pcap = pcap_open_live(p -> name, PCAP_SNAPLEN, 1, PCAP_TMOUT, errbuff);
      
      if(pcap == NULL)
	{
	  syslog(LOG_ERR, "can't pcap_open_live: %s\n", errbuff);
	  daemon_stop(0);
	}
    }
  
  DEBUG(DBG_MISC, sprintf(dbg, "%s set to promiscous mode\n", p -> name));
  
}

void exit_capture(void)
{
  if(pcap != NULL) pcap_close (pcap);
  pcap = NULL;
}

inline int onnet(unsigned long int addr, struct ipnetwork *net)
{
  return ((addr & net -> netmask) == net -> netnumber);
}

int onnetlist(unsigned long int addr, struct ipnetwork *netlist)
{
    while(netlist!=NULL)
	{
	    if(onnet(addr, netlist))
		{
		    return 1;
		}
	    netlist = netlist -> next;
	}
    return 0;
}

void do_packet(u_char *usr, const struct pcap_pkthdr *h, const u_char *p)
{
  handle_frame(p, h->len);
}

void packet_loop()
{
  int res;
  while(running)
    {
      res = pcap_dispatch(pcap, -1, do_packet, NULL); 
      if(res == -1) 
	{
	  syslog(LOG_ERR, "pcap_dispatch: %s\n", pcap_geterr(pcap));
	  DEBUG(DBG_ERR, sprintf(dbg,"pcap_dispatch: %s\n", pcap_geterr(pcap)));
	}
    }
}

FILE *acct_file = stderr;

void
handle_frame (unsigned char buf[], int length)
{
  static struct ip tmp_iphdr;
  unsigned short srcport, dstport;
  struct tcphdr tmp_tcphdr;
  struct udphdr tmp_udphdr;
  struct icmp tmp_icmphdr;
  int found = 0;
  struct mon_host_struct *ptr;

  if(buf[12] * 256 + buf[13] == ETHERTYPE_IP)
    {
      memcpy (&tmp_iphdr, &(buf[14]), sizeof (tmp_iphdr));

      found = 0;
      if(cfg->hostlist) { /* we specified at least one hostlimit tag */
          /* if we don't monitor this IP, bail now - mk */
          for(ptr=cfg->hostlist;ptr && !found;ptr=ptr->next) {
                if(ptr->ipaddr == tmp_iphdr->saddr 
		|| ptr->ipaddr == tmp_iphdr->daddr)
                    found = 1;
          }
          if(!found) {
                packets->ignored++;
                continue;
          }
      }
      
      if((tmp_iphdr.ip_src.s_addr & cfg->ignoremask) ==
	 (tmp_iphdr.ip_dst.s_addr & cfg->ignoremask))
        {
          packets->local++;
          return;
        }
      else
	{
          if(onnetlist(tmp_iphdr.ip_src.s_addr,cfg->ignorenet) ||
	     onnetlist(tmp_iphdr.ip_dst.s_addr, cfg->ignorenet))
            {
              if(!(onnetlist(tmp_iphdr.ip_src.s_addr,cfg->dontignore) ||
		   onnetlist(tmp_iphdr.ip_dst.s_addr, cfg->dontignore)))
                {
                  if(debug_level & DBG_IGNORE)
                    {
                      char tmp[18];
                      strcpy(tmp, intoa(tmp_iphdr.ip_src.s_addr));
                      DEBUG(DBG_IGNORE, sprintf(dbg, "netignored: %s -> %s\n",
                                                tmp,intoa(tmp_iphdr.ip_dst.s_addr)));
                    }
                  packets->netignored++;
                  return;
                }
            }
	  packets->ip++;
	  
	  switch(tmp_iphdr.ip_p)
	    {
	    case IPPROTO_UDP:
	      packets->ip_udp++;
	      memcpy (&tmp_udphdr, &buf[14 + tmp_iphdr.ip_hl * 4], sizeof (tmp_udphdr));
	      srcport = tmp_udphdr.uh_sport;
	      dstport = tmp_udphdr.uh_dport;

	      break;
	    case IPPROTO_TCP:
	      packets->ip_tcp++;
	      memcpy (&tmp_tcphdr, &buf[14 + tmp_iphdr.ip_hl * 4], sizeof (tmp_tcphdr));
	      srcport = tmp_tcphdr.th_sport;
	      dstport = tmp_tcphdr.th_dport;

	      break;
	    case IPPROTO_ICMP:
	      packets->ip_icmp++;
	      memcpy (&tmp_icmphdr, &buf[14 + tmp_iphdr.ip_hl * 4], sizeof (tmp_icmphdr));
	      srcport = tmp_icmphdr.icmp_type;
	      dstport = tmp_icmphdr.icmp_code;

	      break;
	    default:
	      packets->ip_other++;
	      srcport = dstport = 0;
	      break;
	    }
		      
	  register_packet(tmp_iphdr.ip_src.s_addr,tmp_iphdr.ip_dst.s_addr,
			  tmp_iphdr.ip_p, srcport, dstport, 
			  ntohs(tmp_iphdr.ip_len), strdup(cfg->promisc->name), NULL);
	}
    }
  else
    {
      /* ETH_P_ARP, ETH_P_RARP, ETH_P_IPX, etc. */
      packets -> ignored ++;
    }
}
