/**************************************************
opengate Mac addr auth program

 module to control cache of mac and ip address pair.
  The cache is prepared to lower the load examining all packets.
  All detected address pair (allowable or not) are cached.

  As checking packet is time consuming procedure,
  the recently checked addresses are cached and skiped.
  Implemented with HashTable and Queue.
  HashTable:
    Key= comcatenation of MAC and IP Addresses in RAW
    Val= checked time
    If the address pair is included in table and time is new, skip checking.
    If the address pair is elder than the cache limit time, 
     remove the pair and elder pairs from table.
  Queue(FIFO):
    Address pair ordered by checked time.
    The queue is used for the above remove sequence.

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"

#define CACHESIZE 1000000 /* mac&ip cache size (max pairs to save) */

int InitQueueForCache(void);
int EnQueueForCache(unsigned char* addrRaw, int addrLen);
int DeQueueForCache(unsigned char* addrRaw, int* pAddrLen);
int ListQueueForCache(void);
void FreeQueueForCache(void);

/* Queue(FIFO) for stores of MacAndIpAddress */
/* implemented with linked-list */
struct queueNode{
  int addrLen;
  unsigned char addrRaw[MACADDRLN+IPV6ADDRLN];
  struct queueNode *next;
};
static struct queueNode* queueTail=NULL;
static struct queueNode* queueHead=NULL;

/* HashTable for stores of MacAndIpAddress(key) and Time(value) */
static DB* hashDb;

/* Cache Timeout(seconds) */
/* it means the packet checking interval */
static int cacheTimeout;

/* count of address pairs stored in cache */
static int cacheItemCount=0;

/**********************************
This cache is made from HashTable and Queue.
HashTable for quick access, and Queue for ordering.
Same data are stored in the two data structures.
When you add/delete items in cache, treat both structures.
Don't add/delete items from one structure only. 
***********************************/

/****************************************
Is the MAC/IPAddress pair checked recently or not
 input: macAndIpAddressRaw=concatenation of MAC and IP in RAW
        addrLen=address length
 return TRUE if checked recently
****************************************/
int isRecentlyCheckedAddress(unsigned char* macAndIpAddressRaw, int addrLen){

  int timeNow;
  unsigned char storedAddrRaw[MACADDRLN+IPV6ADDRLN];
  int storedAddrLen;
  int ret;
  int* pTime;
  DBT hashKey;
  DBT hashVal;

  /* get present time */
  timeNow=time(NULL);

  /***** get item matched to the mac&ip from hash table */
  hashKey.data = macAndIpAddressRaw;
  hashKey.size = addrLen;
  memset(&hashVal, 0, sizeof(DBT));
  ret=hashDb->get(hashDb, &hashKey, &hashVal, 0);

  /*** if getting from hash is successed */
  if(ret==0){

    /* pick up the pointer to the value */
    pTime=(int*)(hashVal.data);

    /* if data is null, return NO */
    if(pTime==NULL) return FALSE;

    /* if ignoring addresses(exceptional setting), return YES */
    if(*pTime==0) return TRUE;

    /* if recently checked data, return YES */
    if( (timeNow-*pTime) < cacheTimeout ) return TRUE;

    /***** if timeover, elder items are removed from queue and hashTable */
    /* the packet reaching here is over the cache timeout */ 
    /* dequeue data from queue head(oldest) until present address is found. */
    while(DeQueueForCache(storedAddrRaw, &storedAddrLen)){
      hashKey.data=storedAddrRaw;
      hashKey.size = storedAddrLen;
      hashDb->del(hashDb, &hashKey, 0);
      if(memcmp(macAndIpAddressRaw,storedAddrRaw,storedAddrLen)==0)break;
    }

    /* insert updated item to queue and hashTable */
    EnQueueForCache(storedAddrRaw, storedAddrLen);
    hashVal.data = &timeNow;
    hashVal.size = sizeof(int);    
    if(hashDb->put(hashDb, &hashKey, &hashVal, 0) == -1) {
      err_msg("ERR at %s#%d: fail to put into hash table",__FILE__,__LINE__);
      terminateProg(0);
    }

    /* (if timeover) return NO */
    return FALSE;
  }

  /*** if getting from hash is failed, insert address and return NO */
  if(ret==1){

    /*************** begin of adding item to Cache ***/
    /* insert to hash table */
    hashVal.data = &timeNow;
    hashVal.size = sizeof(int);    
    if(hashDb->put(hashDb, &hashKey, &hashVal, 0) == -1) {
      err_msg("ERR at %s#%d: fail to put into hash table",__FILE__,__LINE__);
      terminateProg(0);
    }
    /* insert to queue */
    EnQueueForCache(macAndIpAddressRaw, addrLen);
    /*************** end of adding item to Cache ***/

    /* if cache size is over, remove oldest one */
    if(cacheItemCount>CACHESIZE) DelOldestCacheItem();
    return FALSE;
  }

  /*** if getting from hash is error, exit */
  err_msg("ERR at %s#%d: fail to get hash table item",__FILE__,__LINE__);
  return FALSE;
}

/****************************************
initialize Mac&IpAddress Cache
 Mac&Ip Cache is formed with in HashTable and Queue
 HashTable=to search an item quickly
 Queue    =to list items in FIFO order
****************************************/
void initCache(void) {

  /* prepare hash table */
  if((hashDb = dbopen(NULL, O_CREAT | O_RDWR, 0644, DB_HASH, NULL)) == NULL) {
    err_msg("ERR at %s#%d: fail to open hash table",__FILE__,__LINE__);
    terminateProg(0);
  }

  /* prepare queue */
  InitQueueForCache();

  /* set timeout parameter */
  cacheTimeout=atoi(GetConfValue("CacheTimeout"));
}

/****************************************
memory free for Mac&IpAddress Cache
****************************************/
void freeCache(void) {

  /* free hash table */
  hashDb->close(hashDb);

  /* free queue */
  FreeQueueForCache();
}


/****************************************
delete item from MacAndIp cache matched to the mac or/and ip address
set arguments for mac and/or ip to detele
set argument as "", if the address is not known 
 eg: to delete item for an ipv4 and unknown mac, ("", "192.168.0.100")
****************************************/
int delCacheItem(char* macAddress, char* ipAddress) {
  int found=FALSE;
  DBT hashKey;
  unsigned char addrRaw[MACADDRLN+IPV6ADDRLN];
  unsigned char* pRawMac;
  struct queueNode *temp;
  struct queueNode *prev;
  int inMac=FALSE;
  int inIpv4=FALSE;
  int inIpv6=FALSE;
  int shift=0;
  int length=0;

  /*** set the raw values */
  /* if mac is not null, set the mac raw value */
  if(!isNull(macAddress)) {
    if((pRawMac=(unsigned char*)ether_aton(macAddress)) != NULL){
      memcpy(addrRaw, pRawMac, MACADDRLN);
      inMac=TRUE;
    }
  }

  /* if ip is not null, set the ipv6/ipv4 raw value */
  if(!isNull(ipAddress)) {
    if(strchr(ipAddress, ':') != NULL) {
      if(inet_pton(AF_INET6, ipAddress, addrRaw+MACADDRLN) > 0) inIpv6=TRUE;
    }
    else{
      if(inet_pton(AF_INET, ipAddress, addrRaw+MACADDRLN) > 0) inIpv4=TRUE;
    }
  }
  
  /*** set start point and length for comparing string */
  if(inMac && !inIpv6 && !inIpv4){ /* only mac is indivcated */
    shift=0;
    length=MACADDRLN;
  }
  else if(!inMac && inIpv6){ /* only ipv6 is indicated */
   shift=MACADDRLN;
   length=IPV6ADDRLN;
  }
  else if(!inMac && inIpv4){ /* only ipv4 is indicated */
    shift=MACADDRLN;
    length=IPV4ADDRLN;
  }
  else if(inMac && inIpv6){ /* mac and ipv6 is indicated */
    shift=0;
    length=MACADDRLN+IPV6ADDRLN;
  }
  else if(inMac && inIpv4){ /* mac and ipv4 is indicated */
    shift=0;
    length=MACADDRLN+IPV4ADDRLN;
  }
  else{ /* no one is indicated */
    return FALSE;
  }

  /*** scan queue to find matched address */
  /* set search point to the head of mac-ip cache */
  if(queueHead==NULL) return FALSE;
  prev=queueHead;
  temp=queueHead->next;

  /* scan from queue head to tail */
  while(temp->next!=NULL){

    /* compare indicated value and queue value */
    if(memcmp(addrRaw+shift, (temp->addrRaw)+shift, length)==0){

      /* set found flag */
      found=TRUE;

      /*************** begin of removing item from Cache ***/
      /* delete the item from Hash Table */
      hashKey.data = temp->addrRaw;
      hashKey.size = temp->addrLen;
      hashDb->del(hashDb, &hashKey, 0);

      /* delete the item from Queue */
      prev->next=temp->next;
      free(temp);
      temp=prev;
      /*************** end of removing item from Cache ***/
    }

    /* move to next item */
    prev=temp;
    temp=temp->next;
  }
  return found;
}

/****************************************
delete oldest item from MacAndIp cache 
****************************************/
int delOldestCacheItem(void) {
  DBT hashKey;
  unsigned char addrRaw[MACADDRLN+IPV6ADDRLN];
  int addrLen=0;

  /* delete oldest item(=head) from queue */
  if(DeQueueForCache(addrRaw, &addrLen)){

    /* delete the item from Hash Table */
    hashKey.data = addrRaw;
    hashKey.size = addrLen;
    hashDb->del(hashDb, &hashKey, 0);
    return TRUE;
  }

  return FALSE;
}

/*********************************************
initialize MacAndIpAddress Queue(FIFO)
 Queue
   HeadNode - DataNode - DataNode - TailNode
 (dummy node)                     (dummy node)
   ^queueHead                       ^queueTail
*********************************************/
int initQueueForCache(void){

  unsigned char addrRaw[MACADDRLN+IPV6ADDRLN];
  int addrLen;

  /* if not exist, prepare head and tail */
  if(queueHead==NULL){
    queueHead=(struct queueNode*)malloc(sizeof(struct queueNode));
    if(queueHead==NULL){
      err_msg("ERR at %s#%d: fail to malloc",__FILE__,__LINE__);
      terminateProg(0);
    }
    queueTail=(struct queueNode*)malloc(sizeof(struct queueNode));
    if(queueTail==NULL){
      err_msg("ERR at %s#%d: fail to malloc",__FILE__,__LINE__);
      terminateProg(0);
    }
    bzero(queueHead->addrRaw, MACADDRLN+IPV6ADDRLN);
    queueHead->addrLen=0;
    bzero(queueTail->addrRaw, MACADDRLN+IPV6ADDRLN);
    queueTail->addrLen=0;
    queueHead->next=queueTail;
    queueTail->next=NULL;
  }
  
  /* if exist, reset all */
  else{
    while(DeQueueForCache(addrRaw,&addrLen))
      ;
  }

  /* reset item count */
  cacheItemCount=0;

  return TRUE;
}

/****************************************
Add data to the tail of MacAndIP Queue
 input=addr
****************************************/
int enQueueForCache(unsigned char* addrRaw, int addrLen){
  struct queueNode *newNode;

  /* if not prepared, error */
  if(queueHead==NULL){
    err_msg("ERR at %s#%d: queue not init",__FILE__,__LINE__);
    return FALSE;
  }

  /*** add item after the tail and set it as new tail */
  /* memory area for new node is prepared */
  newNode=(struct queueNode*)malloc(sizeof(struct queueNode));
  if(newNode==NULL){
    err_msg("ERR at %s#%d: fail to malloc",__FILE__,__LINE__);
    terminateProg(0);
  }

  /* data is set into the tail-node(old) */
  memcpy(queueTail->addrRaw, addrRaw, addrLen);
  queueTail->addrLen=addrLen;

  /* the new node is linked after the tail-node(old) */
  /* and is used as the new tail-node */
  queueTail->next=newNode;
  queueTail=newNode;

  /* the tail-node(new) is initialized */
  bzero(queueTail->addrRaw,MACADDRLN+IPV6ADDRLN);
  queueTail->addrLen=0;
  queueTail->next=NULL;

  /* increment item count */
  cacheItemCount++;

  return TRUE;
}

/****************************************
Get and remove address data from the head of MacAndIP Queue
output
 addrRaw:binary string of Mac&Ip (length=MACADDRLN+IPV6ADDRLN)
 pAddrLen: pointer to the aquired string length 
****************************************/
int deQueueForCache(unsigned char* addrRaw, int* pAddrLen){

  /* set null string as default */
  bzero(addrRaw, MACADDRLN+IPV6ADDRLN);

  /* if not prepared, error */
  if(queueHead==NULL){
    err_msg("ERR at %s#%d: queue not init",__FILE__,__LINE__);
    return FALSE;
  }
  else if(queueHead->next==NULL){
    err_msg("ERR at %s#%d: queue not init",__FILE__,__LINE__);
    return FALSE;  
  }

  /* if no data, return false */
  else if(queueHead->next==queueTail){
    return FALSE;
  }

  /* get item from the head and remove the item from the queue */
  else {
    struct queueNode *temp;

    /* head-node is a dummy node, second-node has the 1st data */
    /* 'temp' points the 1st data. 'temp->next' points the 2nd data */
    
    /* link the head-node to 2nd data, get 1st data, and remove 1st data */
    temp=queueHead->next;
    queueHead->next=temp->next;
    memcpy(addrRaw, temp->addrRaw, temp->addrLen);
    *pAddrLen=temp->addrLen;
    free(temp);

    /* decrement item count */
    cacheItemCount--;
  }

  return TRUE;
}

/****************************************
Listing MacAndIpAddress Queue (for debugging)
****************************************/
int listQueueForCache(void){

  struct queueNode *temp;
  int i;

  printf("Queue items\n");
  if(queueHead==NULL) return FALSE;
  temp=queueHead->next;
  while(temp->next!=NULL){
    for(i=0;i<temp->addrLen;i++){
      printf("%x ", temp->addrRaw[i]);
    }
    printf("\n");
    temp=temp->next;
  }
  printf("---\n");
  return TRUE;
}

/****************************************
memory free for MacAndIpAddress Queue
****************************************/
void freeQueueForCache(void){
  unsigned char addrRaw[MACADDRLN+IPV6ADDRLN];
  int addrLen;
  while(DeQueueForCache(addrRaw,&addrLen));
  free(queueHead);
  free(queueTail);
}

/************************************/
/* arp form is reformed to ndp form */ 
/* format macAddr for ndp or arp    */
/* match the form of two program    */
/* mac addr by arp 00:01:12:0b:..   */
/* mac addr by ndp 0:1:12:b:..      */
/* DO NOT CALL IT WITH CONST STRING */
/************************************/
int reFormatMacAddr(char* macAddr)
{
  int m1,m2,m3,m4,m5,m6;
  if(sscanf(macAddr, "%x:%x:%x:%x:%x:%x", &m1,&m2,&m3,&m4,&m5,&m6)!=6) return FALSE;
  snprintf(macAddr, ADDRMAXLN,"%02x:%02x:%02x:%02x:%02x:%02x", m1,m2,m3,m4,m5,m6);
  return TRUE;
}


/****************************************************
 routines for debugging putput
 ***************************************************/

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

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

int IsRecentlyCheckedAddress(unsigned char* macAndIpAddressRaw, int addrLen){
  int ret;
  if(debug>1) err_msg("DEBUG:=>isRecentlyCheckedAddress(%x,%d)", macAndIpAddressRaw[0],addrLen);
  ret = isRecentlyCheckedAddress(macAndIpAddressRaw, addrLen);
  if(debug>1) err_msg("DEBUG:(%d)<=isRecentlyCheckedAddress( )",ret);
  return ret;
}

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

int DelOldestCacheItem(void) {
  int ret;
  if(debug>1) err_msg("DEBUG:=>delOldestCacheItem( )");
  ret = delOldestCacheItem();
  if(debug>1) err_msg("DEBUG:(%d)<=delOldestCacheItem( )",ret);
  return ret;
}

int InitQueueForCache(void){
  int ret;
  if(debug>1) err_msg("DEBUG:=>initQueueForCache( )");
  ret = initQueueForCache();
  if(debug>1) err_msg("DEBUG:(%d)<=initQueueForCache( )",ret);
  return ret;
}

int EnQueueForCache(unsigned char* addrRaw, int addrLen){
  int ret;
  if(debug>1) err_msg("DEBUG:=>enQueueForCache(%x,%d)", addrRaw[0],addrLen);
  ret = enQueueForCache(addrRaw,addrLen);
  if(debug>1) err_msg("DEBUG:(%d)<=enQueueForCache( )",ret);
  return ret;
}

int DeQueueForCache(unsigned char* addrRaw, int* pAddrLen){
  int ret;
  if(debug>1) err_msg("DEBUG:=>deQueueForCache( )");
  ret = deQueueForCache(addrRaw,pAddrLen);
  if(debug>1) err_msg("DEBUG:(%d)<=deQuqueForCache(%x,%d)",ret, addrRaw[0],*pAddrLen);
  return ret;

}
int ListQueueForCache(void){
  int ret;
  if(debug>1) err_msg("DEBUG:=>listQueueForCache( )");
  ret = listQueueForCache();
  if(debug>1) err_msg("DEBUG:(%d)<=listQueueForCache( )",ret);
  return ret;

}

void FreeQueueForCache(void){
  if(debug>1) err_msg("DEBUG:=>freeQueueForCache()");
  freeQueueForCache();
  if(debug>1) err_msg("DEBUG:<=freequeueForCache()");

}

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