/* 
 * Network accounting
 * capture-linux.c - capture raw packets - linux version
 * (C) 1994, 1995 Ulrich Callmeier
 * 27/5/1998 - modified to use /etc/protocols instead of constants -mk
 */

#include <sys/time.h>
#include <sys/wait.h>
#include "netacct.h"

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <malloc.h>
#include <strings.h>
#include <signal.h>
#include <fcntl.h>
#include <utmp.h>
#include <pwd.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <sys/ioctl.h>
#include <sys/stat.h>
#include <asm/types.h>
#include <netdb.h>
#include <netinet/ip.h>
#include <netinet/tcp.h>
#include <netinet/ip_icmp.h>

char *rcs_revision_capture_c = "$Revision: 1.9 $";

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

static int capture_sd = -1;

/*
 * New global constants from /etc/protocols replace #defines from protocols.h
 */
int IP_ICMP = 0;
int IP_TCP = 0;
int IP_UDP = 0;

void init_capture()
/*
 * 1) Open our capture socket
 * 2) Set all the promisc devices to promiscous mode
 * 3) Read essential protocol numbers from /etc/protocols - mk
 */
{
    struct ifreq ifr;
    struct promisc_device *p;
    struct protoent *pr;

    if ((capture_sd = socket (AF_INET, SOCK_PACKET, htons (ETH_P_ALL))) < 0)
	{
	    syslog(LOG_ERR, "can't get socket: %m\n");
	    daemon_stop(0);
	}

    p = cfg -> promisc;
    
    while(p!=NULL)
	{
	    strcpy (p -> oldifr.ifr_name, p -> name);
	    
	    if (ioctl (capture_sd, SIOCGIFFLAGS, &(p -> oldifr)) < 0)
		{
		    syslog(LOG_ERR, "can't get flags: %m\n");
		    daemon_stop(0);
		}
	    
	    p -> reset = 1;
	    
	    ifr = p -> oldifr;
	    ifr.ifr_flags |= IFF_PROMISC;
	    
	    if (ioctl (capture_sd, SIOCSIFFLAGS, &ifr) < 0)
		{
		    syslog(LOG_ERR, "can't set flags: %m\n");
		    daemon_stop(0);
		}

	    DEBUG(DBG_MISC, sprintf(dbg, "%s set to promiscous mode\n", p -> name));
	    
	    p = p -> next;
        }
    if((pr=getprotobyname("tcp")) == NULL) {
        syslog(LOG_ERR, "Can't get TCP protocol entry.\n");
        daemon_stop(0);
    }
    IP_TCP = pr->p_proto;
    if((pr=getprotobyname("udp")) == NULL) {
        syslog(LOG_ERR, "Can't get UDP protocol entry.\n");
        daemon_stop(0);
    }
    IP_UDP = pr->p_proto;
    if((pr=getprotobyname("icmp")) == NULL) {
        syslog(LOG_ERR, "Can't get ICMP protocol entry.\n");
        daemon_stop(0);
    }
    IP_ICMP = pr->p_proto;
}

void exit_capture(void)
{
    struct promisc_device *p;

    /* do we have to check (capture_sd >= 0) ? */

    p = cfg -> promisc;
    
    while(p != NULL)
	{
	    if (ioctl (capture_sd, SIOCSIFFLAGS, &(p -> oldifr)) < 0)
		{
		    syslog(LOG_ERR, "can't reset flags: %m\n");
		}
	    
	    p = p -> next;
	}
    
    close (capture_sd);
}

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;
}

struct dynadat *dynadat = NULL;

char *check_user_dev2line(char *devname)
/*
 * Find username corresponding to devname
 */
{
    struct dev2line *d2l;
    char *line;
    struct dynadat *dd;
    int found;

    d2l = dev2line;
    line = NULL;

    while(d2l!=NULL)
	{
	    if(strcmp(d2l -> netinterface, devname)==0)
		{
		    line = d2l -> line;
		    break;
		}
	    
	    d2l = d2l -> next;
	}
    
    if(line == NULL)
	return NULL;
    
    dd = dynadat;

    found = 0;
    while(dd != NULL)
	{
	    if(strcmp(dd -> netinterface, devname) == 0)
		{
		    found = 1;
		    break;
		}
	    dd = dd -> next;
	}
	    
    if(!found)
	{
	    /* We don't have an entry for this device yet. Add one */

	    dd = malloc(sizeof(struct dynadat));
	    
	    dd -> netinterface = strdup(devname);
	    dd -> last_stat = 0; /* force reading of utmp */
	    dd -> mtime = 0;
	    dd -> user = NULL;
	    dd -> next = dynadat;
	    dynadat = dd;
	}

    /* dd now points to the right dynadat entry */
    /* maybe this is out of date, so we check */
	    
    if ((now - dd->last_stat) > FORCE_STAT_TIME )
	{
	    struct stat statbuf;
	    
	    /* it could be invalid, lets stat utmp */
	    
	    if(stat(_PATH_UTMP, &statbuf)==0)
	      {
		DEBUG(DBG_UTMP, sprintf(dbg, "%d: did a stat of %s\n",(int) now,_PATH_UTMP));
		
		dd -> last_stat = now;
		
		if((statbuf.st_mtime - dd->mtime) > 0)
		  {
		    struct utmp *ut_rec; /* utmp record */
		    
		    /* we have to wade through utmp */
		    DEBUG(DBG_UTMP, sprintf(dbg, "%d: wading through utmp %s\n",(int) now, _PATH_UTMP));
		    
		    dd -> mtime = statbuf.st_mtime;
		    
		    while ((ut_rec = getutent()))
		      {
			if ((ut_rec->ut_type == USER_PROCESS) &&
			    (ut_rec->ut_name[0] != '\000') &&
			    (strcmp(ut_rec->ut_line,line)==0))
			  {
			    if(dd -> user) free(dd -> user);
			    dd -> user = malloc(10);
			    strncpy(dd -> user, ut_rec->ut_user, 8);
			    dd->user[8] = 0;
	
			    DEBUG(DBG_DYNAMIC, sprintf(dbg, "found %s for %s\n",dd->user, line));

			    break;
			  }
		      }
		    endutent();
		  }
	      }
	    else
	      {
		syslog(LOG_ERR,"couldn't stat %s: %m\n",_PATH_UTMP);
		return NULL;
	      }
	  }
    
    return dd -> user;
}

char *check_user_dynamicip(__u32 addr)
/*
 * Find username corresponding to addr
 */
{
    struct dynadat *dd;
    int found;

    DEBUG(DBG_ANNOYING, sprintf(dbg, "check_user_dynamicip(%s)\n", intoa(addr)));

    dd = dynadat;

    found = 0;
    while(dd != NULL)
      {
	if(dd -> addr == addr)
	  {
	    found = 1;
	    break;
	  }
	dd = dd -> next;
      }
	    
    if(!found)
	{
	    /* We don't have an entry for this addr yet. Add one */

	    dd = malloc(sizeof(struct dynadat));
	    
	    dd -> addr = addr;
	    dd -> last_stat = 0; /* force reading of dir */
	    dd -> mtime = 0;
	    dd -> user = NULL;
	    dd -> next = dynadat;
	    dynadat = dd;

	    DEBUG(DBG_DYNAMIC, sprintf(dbg, "added entry for %s to dynadat list\n", intoa(addr)));
	    
	}

    /* dd now points to the right dynadat entry */
    /* maybe this is out of date, so we check */
	    
    if ((now - dd->last_stat) > FORCE_STAT_TIME )
      {
	struct stat statbuf;
	
	/* it could be invalid, lets stat dir */
	    
	if(stat(cfg -> dynamicip, &statbuf)==0)
	  {
	    DEBUG(DBG_DYNAMIC, sprintf(dbg, "%d: did a stat of %s, last_stat was %d, mtime was %d\n",(int) now, cfg->dynamicip, (int) dd->last_stat, (int) dd -> mtime));
	    
	    dd -> last_stat = now;
	    
	    if((statbuf.st_mtime - dd->mtime) > 0)
	      {
		FILE *f;
		char *s;
		/* we have to read dir */
		
		s = malloc(strlen(cfg->dynamicip) + 1 + 15 + 1 );
		
		strcpy(s, cfg->dynamicip);
		strcat(s, "/");
		strcat(s, intoa(addr));
		
		DEBUG(DBG_DYNAMIC, sprintf(dbg, "%d: reading %s\n",(int) now, s));
		
		if(dd -> user) free(dd -> user);
		dd -> user = NULL;
		dd -> mtime = statbuf.st_mtime;
		
		f = fopen(s, "r");
		
		if(f == NULL)
		  {
		    /* syslog(LOG_ERR,"couldn't fopen %s: %m\n",s); */
		    DEBUG(DBG_DYNAMIC, sprintf(dbg, "%d: couldn't fopen %s: %s\n",(int) now, s, strerror(errno)));
		    return NULL;
		  }
		
		dd -> user = malloc(BUFSIZ);
		
		fgets(dd -> user, BUFSIZ, f);
		if(dd->user[strlen(dd->user)-1]=='\n') dd->user[strlen(dd->user)-1]='\0';
		
		fclose(f);
		
		DEBUG(DBG_DYNAMIC, sprintf(dbg, "found %s for %s\n",dd->user, intoa(addr)));
		
	      }
	  }
	else
	  {
	    syslog(LOG_ERR,"couldn't stat %s: %m\n",cfg->dynamicip);
	    return NULL;
	  }
      }
    
    return dd -> user;
}

void packet_loop()
{
  struct sockaddr saddr;
  int sizeaddr;
  unsigned char buff[1600];
  unsigned char *buf;
  int hardheader;
  int length;
  static struct iphdr *tmp_iphdr;
  int type;
  int dynamicstyle;
  int do_user;
  __u32 dynamicaddr, otheraddr;
  char *user;
  struct promisc_device *p;
  struct headerdat *h;
  int found = 0;
  struct mon_host_struct *ptr;    

  dynamicstyle = (dev2line != NULL) ? 1 : ((cfg->dynamicip != NULL) ? 2 : 0);

  buf = &buff[20];

  while (running)
    {
      sizeaddr = 14;
      length = recvfrom (capture_sd, buf, 127, 0, &saddr, &sizeaddr);
      if (length == -1)
	{
	  if(errno != EINTR)
	    DEBUG(DBG_SYSCALL, sprintf(dbg, "recvfrom: %s\n", strerror(errno)));
	  continue;
	}
      
      do_user = 0;
      
      p = cfg -> notdev;
      
      while(p!=NULL)
	{
	  if (!strcmp(p -> name, saddr.sa_data)) {
	    packets->notdev++;
	    break;
	  }
	  p = p -> next;
	}
      if (p)
	continue;

      found = 0;
      if(cfg->iflist) { /* we specified at least one iflimit tag */
          /* if we don't monitor this interface, bail now - mk */
          for(p=cfg->iflist;p && !found;p=p->next) {
                if(!strcmp(p->name, saddr.sa_data))
                    found = 1;
          }
          if(!found) {
                packets->ignored++;
	        continue;
          }
      }                                                                    

      h = cfg -> headers;
      
      hardheader = -1;
      while (h)
	{
	  if(strncmp(saddr.sa_data,h->name,h->l) == 0)
	    {
	      hardheader = h -> offset;
	      if (h -> type != 0)
		type = buf[h -> type] * 256 + buf[h -> type + 1];
	      else
		type = ETH_P_IP;
	      break;
	    }
	  h = h -> next;
	}

      if (hardheader == -1)
	{
	  /* ASSUMES: interface - line just with ppp and slip etc. */
	  if(dynamicstyle == 1) do_user = 1;
	  
	  hardheader = 0;
	  type = 0;
	  packets->unenc++;
#ifdef IGNORE_UNENC
	  continue; /* ignore ppp/slip */
#endif
	}
      else
	{
	  if(type != ETH_P_IP)
	    {
	      /* ETH_P_ARP, ETH_P_RARP, ETH_P_IPX, etc. */
	      packets->ignored++;
	      continue;
	    }
	}

      tmp_iphdr = (void *) (buf+hardheader);

      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->saddr & cfg->ignoremask) == (tmp_iphdr->daddr & cfg->ignoremask))
	{
	  packets->local++;
	  continue;
	}
      else
	{
	  if(onnetlist(tmp_iphdr->saddr,cfg->ignorenet) || onnetlist(tmp_iphdr->daddr, cfg->ignorenet))
	    {
	      if(!(onnetlist(tmp_iphdr->saddr,cfg->dontignore) || onnetlist(tmp_iphdr->daddr, cfg->dontignore)))
		{
		  if(debug_level & DBG_IGNORE)
		    {
		      char tmp[18];
		      strcpy(tmp, intoa(tmp_iphdr->saddr));
		      DEBUG(DBG_IGNORE, sprintf(dbg, "netignored: %s -> %s\n",
						tmp,intoa(tmp_iphdr->daddr)));
		    }
		  packets->netignored++;
		  continue;
		}
	    }
	  packets->ip++;
	  user = NULL;
	  
	  switch(dynamicstyle)
	    {
	    case 1:
	      if(do_user) user = check_user_dev2line(saddr.sa_data);
	      break;
	    case 2:
	      dynamicaddr = otheraddr = 0;
	      if(onnet(tmp_iphdr->saddr, &cfg->dynamicnet)) 
		{
		  dynamicaddr = tmp_iphdr->saddr;
		  if (onnet(tmp_iphdr->daddr, &cfg->dynamicnet)) otheraddr = tmp_iphdr->daddr;
		  DEBUG(DBG_ANNOYING, sprintf(dbg, "source %s is on dynamicnet\n", intoa(dynamicaddr)));
		  
		}
	      else if (onnet(tmp_iphdr->daddr, &cfg->dynamicnet))
		{
		  dynamicaddr = tmp_iphdr->daddr;
		  if(onnet(tmp_iphdr->saddr, &cfg->dynamicnet)) otheraddr = tmp_iphdr->saddr;
		  DEBUG(DBG_ANNOYING, sprintf(dbg, "destination %s is on dynamicnet\n", intoa(dynamicaddr)));
		}
	      
	      if(dynamicaddr != 0)
		{
		  if(onnetlist(dynamicaddr, cfg->excludenamelookup))
		    {
		      DEBUG(DBG_ANNOYING, sprintf(dbg, "BUT: %s is excluded from name lookup\n", intoa(dynamicaddr)));
		      
		      dynamicaddr = otheraddr;
		      otheraddr = 0;
		      if(onnetlist(dynamicaddr, cfg->excludenamelookup))
			{
			  DEBUG(DBG_ANNOYING, sprintf(dbg, "prev. bug: %s is excluded from name lookup, too\n", intoa(dynamicaddr)));
			  dynamicaddr = 0;
			}
		    }
		  if(dynamicaddr != 0)
		    {
		      user = check_user_dynamicip(dynamicaddr); 
		      if((user == NULL) && (otheraddr != 0))
			{
			  user = check_user_dynamicip(otheraddr); 
			}
		    }
		}
	      break;
	    }
	  handle_ip(buf+hardheader, saddr.sa_data, user);
	}
    }
}

void handle_ip(unsigned char buf[], char *devname, char *user)
{
    static struct iphdr *tmp_iphdr;
    static struct tcphdr *tmp_tcphdr;
    static struct icmphdr *tmp_icmphdr;
    unsigned short srcport, dstport;
    tmp_iphdr = (void *) &buf[0];
    tmp_tcphdr = (void *) &buf[tmp_iphdr->ihl*4];
    /* relevant headers of udphdr and tcphdr are identical */
    tmp_icmphdr = (void *) &buf[tmp_iphdr->ihl*4];

    if(tmp_iphdr->protocol == IP_UDP)
	{
	    packets->ip_udp++;
	    
	    srcport = ntohs(tmp_tcphdr->source);
	    dstport = ntohs(tmp_tcphdr->dest);
	}
    else if(tmp_iphdr->protocol == IP_TCP)
	{
	    packets->ip_tcp++;

	    srcport = ntohs(tmp_tcphdr->source);
	    dstport = ntohs(tmp_tcphdr->dest);
	}
    else if(tmp_iphdr->protocol == IP_ICMP)
	{
	    packets->ip_icmp++;
	    srcport = tmp_icmphdr->type; 
	    dstport = tmp_icmphdr->code;
	}
    else
	{
	    packets->ip_other++;
	    srcport = dstport = 0;
	}
    
    register_packet(tmp_iphdr->saddr,tmp_iphdr->daddr,tmp_iphdr->protocol, srcport, dstport, ntohs(tmp_iphdr->tot_len), devname, user);
}

#ifdef TCP_USER_INFO

/* Originally by Vlad Seriakov (vlad@torn.ktts.kharkov.ua) */
/* Adapted by Ulrich Callmeier */

int get_tcp_info(struct ipdata *ip, uid_t *uid)
{
  FILE *procinfo;
  char buffer[1024];
  int num, local_port, rem_port, d, state, tmout;
  long localaddr, remaddr;
  unsigned long rxq, txq, time_len, retr, inode;
  int timer_run;
  
  if ((procinfo = fopen(PATH_PROCNET_TCP, "r")) == NULL)
    {
      syslog(LOG_ERR, "error opening %s: %s", PATH_PROCNET_TCP,strerror(errno));
      DEBUG(DBG_ERR, sprintf(dbg,"error opening %s: %s", PATH_PROCNET_TCP,strerror(errno)));
      return(-1);
    }
  
  fgets(buffer, sizeof(buffer), procinfo); /* skip header line */
  
  while (!feof(procinfo))
    {
      if(fgets(buffer, sizeof(buffer), procinfo)!=NULL)
	{
	  num = sscanf(buffer,
		       "%d: %lX:%X %lX:%X %X %lX:%lX %X:%lX %lX %d %d %ld\n",
		       &d, &localaddr, &local_port,
		       &remaddr, &rem_port, &state,
		       &txq, &rxq, &timer_run, &time_len, &retr, (int*) uid, &tmout, &inode);
	  if (num < 11) continue;		/* 13 ? */
	  if(localaddr == ip->src && remaddr == ip->dst &&
	     local_port == ip->srcport && rem_port == ip->dstport)
	    {
	      fclose(procinfo);
	      return 1;
	    }
	}
    }
  
  fclose(procinfo);
  
  return(0);
}

#endif
