/* Keep track of packets coming in.

  The source code may be redistributed, as long it remains intact, and
  with this copyright 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.
  You may not use the name of the author to promote this.

  shadows@whitefang.com Thamer Al-Herbish
*/

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

#include <log.h>

extern int verbose;

static struct con *look_up(unsigned long key,struct con *ptr);
static void delete(struct con *old);
static void update(struct con *old,struct con *new);
static void add_to_table(struct con data,unsigned long key);
static int check_if_connected(struct con *new,struct con *old);

static struct con hashtable[HASH_TABLE_SIZE];

void initialize_buffer(void)
{
  bzero((char *)hashtable,sizeof(hashtable));
  return;
}

void add_data(struct con data)
{
  struct con *ptr;
  unsigned long key = (data.src.s_addr + data.dst.s_addr + data.sport + data.dport);
#ifdef DEBUG
  info_log("src: %s",inet_ntoa(data.src));
  info_log("dst: %s",inet_ntoa(data.dst));
  info_log("seq: %lu ack: %lu",htonl(data.seq),htonl(data.ack));
#endif
  /* We have to lookup packets regardless of the chance that its a duplicate.
     Chances are duplicate packets will make it into the network. By always
     looking up, we burn up cpu for robustness. */

  if((ptr = look_up(key,&data)) != NULL) {
    if(check_if_connected(&data,ptr))
      delete(ptr);
    else
      update(ptr,&data);
  } else {
    /* If we got here and its a syn packet add it */
    if(((data.flags&TH_SYN) == TH_SYN) && (data.ack == 0))
      add_to_table(data,key);
  }
  return;
}

static void update(struct con *old,struct con *new)
{
  if(((new->flags&(TH_ACK|TH_SYN)) == (TH_ACK|TH_SYN)) && 
     (old->state == SYN_SENT) &&
     ((ntohl(old->seq) + 1) == (ntohl(new->ack))) &&
     (new->src.s_addr == old->dst.s_addr) &&
     (new->dport == old->sport))  {

    old->state = SYN_RECV;
    old->ack = new->seq;
  } else {
    if((new->flags&TH_RST) == TH_RST) {
      if(old->src.s_addr == new->src.s_addr)
	old->state = SYN_SCAN;
      else
	delete(old);
    }
  }
  return;
}

static void delete(struct con *old)
{
  bzero((char *)old,sizeof(struct con));
  return;
}

static void add_to_table(struct con data,unsigned long key)
{
  struct con *last;
  struct con *ptr;
  unsigned long hash_num = (unsigned long)(key%HASH_TABLE_SIZE);
  unsigned char found = 0;

  last = NULL;

  for(ptr = &hashtable[hash_num];ptr;ptr = ptr->next) {
    last = ptr;
    if(!ptr->have) {
      found++;
      break;
    }
  }
  /* optimization on gcc warnings caused me to be more
     conservative here. Probably wont hurt. */

  if(last == NULL || ptr == NULL)
    return;

  if(found) {
    last = ptr->next;
    bcopy(&data,ptr,sizeof(struct con));
    ptr->have++;
    ptr->next = last;
    ptr->state = SYN_SENT;
  } else {
    if((last->next = (struct con *)malloc(sizeof(struct con)))
       == NULL) {
      error_log("out of mem");
      exit(-1);
    }
    bcopy(&data,last->next,sizeof(struct con));
    last->next->have++;
    last->next->state = SYN_SENT;
  }
  return;
}

static int check_if_connected(struct con *new,struct con *old)
{
  if((new->flags == TH_ACK) && (old->state == SYN_RECV) &&
     ((ntohl(old->ack) + 1) == (ntohl(new->ack))) &&
     (old->src.s_addr == new->src.s_addr) &&
     (old->sport == new->sport))  {
    return 1;
  }
  else  {
    return 0;
  }
}

static struct con *look_up(unsigned long key,struct con *data)
{
  unsigned long hash_num = (key%HASH_TABLE_SIZE);
  struct con *ptr;

  for(ptr = &hashtable[hash_num];ptr;ptr = ptr->next) {
    if((ptr->have) && 
       (
	(
	 (ptr->src.s_addr == data->src.s_addr) &&
	 (ptr->dst.s_addr == data->dst.s_addr) &&
	 (ptr->sport == data->sport) &&
	 (ptr->dport == data->dport)
	 )
	||
	(
	 (ptr->dst.s_addr == data->src.s_addr) &&
	 (ptr->src.s_addr == data->dst.s_addr) &&
	 (ptr->sport == data->dport) &&
	 (ptr->dport == data->sport)
	 )
	)
       )
      break;
  }

  return ptr;
}

#ifdef ETHERTYPE_LINK
static void print_ether(char *buff,unsigned char *ether)
{
  int n;
  unsigned char *cp;
  n = ETHER_ADDR_LEN;

  cp = (unsigned char *)ether;

  sprintf(buff,"%x:%x:%x:%x:%x:%x",cp[0],cp[1],cp[2],cp[3],cp[4],cp[5]);
  return;
}
#endif
void delete_old(struct timeval period)
{
  int i;
  struct con *data;
  char *ptr;
  time_t now = time(NULL);
  long tperiod;
  char src[IPSIZE], dst[IPSIZE];
#ifdef ETHERTYPE_LINK
  char mac_src[MAC_SIZE], mac_dst[MAC_SIZE];
#endif
  char *prefix;

  /* Make it faster only check for verbose flag once. */

  if(verbose) {
    for(i = 0;i < HASH_TABLE_SIZE;i++) {
      for(data = &hashtable[i];data;data = data->next) {

	if(data->state == SYN_SCAN)
	  prefix = "ss:";
	else
	  prefix = "hc:";

	tperiod = (now - data->timestamp.tv_sec);
	if((data->have) && (tperiod > period.tv_sec)) {
	  
	  if((ptr = inet_ntoa(data->src)) != NULL)
	    strcpy(src,ptr);
	  else
	    strcpy(src,"unknown");
	  
	  if((ptr = inet_ntoa(data->dst)) != NULL)
	    strcpy(dst,ptr);
	  else
	    strcpy(dst,"unknown");
#ifdef ETHERTYPE_LINK
	  print_ether(mac_src,data->mac_src);
	  print_ether(mac_dst,data->mac_dest);
	  info_log("%s %s %u %s -> %s %u %s",prefix,src,ntohs(data->sport),mac_src,dst,ntohs(data->dport),mac_dst);
#else
	  info_log("%s %s %u -> %s %u",prefix,src,ntohs(data->sport),dst,ntohs(data->dport));
#endif
	  delete(data);
	}
      }
    }
  } else {
    for(i = 0;i < HASH_TABLE_SIZE;i++) {
      for(data = &hashtable[i];data;data = data->next) {
	tperiod = (now - data->timestamp.tv_sec);
	if((data->have) && (tperiod > period.tv_sec)) {

	  if(data->state == SYN_SCAN)
	    prefix = "ss:";
	  else
	    prefix = "hc:";

	  if((ptr = inet_ntoa(data->src)) != NULL)
	    strcpy(src,ptr);
	  else
	    strcpy(src,"unknown");
	  
	  if((ptr = inet_ntoa(data->dst)) != NULL)
	    strcpy(dst,ptr);
	  else
	    strcpy(dst,"unknown");

	  info_log("%s %s -> %s %u",prefix,src,dst,ntohs(data->dport));
	  delete(data);
	}
      }
    }
  }
  return;
}
