/**************************************************
OpengateM - a MAC address authentication system
   module for Controling sessions

   each session corresponds to the active client used now 
   (session main key is the mac address of the client)
   a session is created at the start of accessing from a client
   the session is removed at the end of accessing from the client
   the end is determined when no packet is detected for a while

   MAC-IP pair is also kept in work db to recognize IP addresses 
   corresponding to each session

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"

/*******************************
add session for the mac address
add ipfw-rule, add session in work db, and write log(MngDb and syslog) 
*******************************/
int addSession(char* macAddress, char* userId, char* extraId){
  int ruleNumber;
  static int nSerialError=0; /* number of serial errors for addition */
  int failToAdd=FALSE;
  int openTime;
  int checkTime;
  int ruleNumberInDb;

  /* open firewall */
  /* return value */
  /*  normally open: ipfw rule number added for the mac */
  /*  error: between -2 and 2 (see ipfw.c for detail) */
  /*  already opened: ipfw rule number found, but inverted sign (ruleNo = -retVal) */
  ruleNumber=OpenClientGate(macAddress, userId, extraId);

  /* check error */
  if(-2<=ruleNumber && ruleNumber<=2) failToAdd=TRUE;

  /* if the mac is already registered in ipfw rule */
  if(ruleNumber < -2){
    
    /* if the mac is active session in work db, do nothing */
    if(GetSessionFromWorkDb(macAddress, userId, extraId, &openTime,
			    &checkTime, &ruleNumberInDb)){ 
      return FALSE;
    }
    /* if no active session in work db */
    /* invert sign(the inverted value means the ipfw rule found) and make session (below) */
    else{
      ruleNumber=-ruleNumber;
    }
  }

  /* if addtion is failed, retry on next packet */
  /* (as the entry in packet-cache is cleared, next packet is not skipped) */
  /* (as to prevent error loop, retry count is limitted) */ 
  if( failToAdd || !IsMacAddressFoundInIpfw(macAddress) ){
    nSerialError++;
    if(nSerialError<1) DelCacheItem(macAddress,"");
    err_msg("ERR at %s#%d: ipfw rule addition is failed(%d-%d)",
	      __FILE__,__LINE__, failToAdd, nSerialError);
    return FALSE;
  }
  nSerialError=0;

  /* write to session db */
  InsertSessionToWorkDb(macAddress, userId, extraId, ruleNumber);

  /* write open log to syslog */
  WriteOpenToSyslog(userId, extraId, macAddress);

  /* write log to management db */
  PutOpenToMngDb(macAddress);
  return TRUE;
}

/*******************************
delete session for the mac address
del ipfw-rule, del session in work db, and write log(MngDb and syslog)
*******************************/
void delSession(char* macAddress){
  char userId[USERMAXLN]="";
  char extraId[USERMAXLN]="";
  int openTime=0;
  int ruleNumber=0;
  int checkTime=0;
  int success;

  /* get information from session table in work db */
  success=GetSessionFromWorkDb(macAddress, userId, extraId, &openTime, 
				 &checkTime, &ruleNumber);

  /* close firewall and refresh the cache */
  if(success) CloseClientGate(ruleNumber);

  /* del from cache */
  DelCacheItem(macAddress,"");
  
  /* del from session from session table in work db */
  DelSessionFromWorkDb(macAddress);

  /* write close log to syslog */
  WriteCloseToSyslog(userId, extraId, macAddress, openTime);
  if(debug>0) WriteSessionInfoToSyslog(userId, extraId, 
				       macAddress, ruleNumber);

  /* write close log to management db */
  PutCloseToMngDb(macAddress);

  /* reset the memory of MAC IP pair */
  ResetMacIpPairs(macAddress);
}

/*******************************
renew session for the ip address
(renew time in session-table entry) 
*******************************/
void renewSession(char* macAddress){

  /* renew the check time */
  UpdateCheckTimeInWorkDb(macAddress);
}

/*******************************
search db to find ip that is not used for a long time
del ipfw rule, del table entry, write db/log 
*******************************/
void delUselessSessions(void){

  /* scan db to remove old sessions */
  DelUselessSessionsInWorkDb(TRUE);

  /* scan ipfw rule to close unmatched db row */
  CloseUnmatchSessions();
}

/**********************************
this is CALLBACK FUNCTION 
(called back from sqlite3_exec in workdb.c/delUselessSessionsInWorkDb)
**********************************/
int closeSession(void* pParam, int argc, char *argv[], char* colName[]){

  int ruleNumber;
  char* userId;
  char* extraId;
  char* macAddress;
  int openTime;

  if(argc < 5) return 1; /* SQLITE_ERROR */
  
  ruleNumber=atoi(argv[0]);
  userId=argv[1];
  extraId=argv[2];
  macAddress=argv[3];
  openTime=atoi(argv[4]);

  /* close firewall */
  CloseClientGate(ruleNumber);
  DelCacheItem(macAddress,"");

  /* write close log to syslog */
  WriteCloseToSyslog(userId, extraId,  macAddress, openTime);
  if(debug>0) WriteSessionInfoToSyslog(userId, extraId, macAddress, ruleNumber);

  /* write close log to management db */
  PutCloseToMngDb(macAddress);

  /* reset the memory of MAC IP pair */
  ResetMacIpPairs(macAddress);

  return 0; /* SQLITE_OK */
}

/*******************************
delete all sessions (terminate the all service)
*******************************/
void delAllSessions(void){

  /* scan db to remove all sessions (argument indicates immediate deletion) */
  DelUselessSessionsInWorkDb(FALSE);

  /* remove ipfw rules having no corresponding db entry  */
  CloseUnmatchSessions();
}

/**********************************************
is the session for the mac found in work db
**********************************************/
int isMatchedSessionFound(char* macAddress){
  char userId[USERMAXLN];
  char extraId[USERMAXLN];
  int openTime;
  int checkTime;
  int ruleNumber;

  /* get info for the macAddress */
  return GetSessionFromWorkDb(macAddress, userId, extraId, 
			 &openTime, &checkTime, &ruleNumber);
}

/************************************
debug dump routine for a hash table
************************************/
void dumpTable(DB* table){
  DBT hashKey;
  DBT hashVal;
  int ruleNumber=0;
  char macAddress[ADDRMAXLN]="?";
  int ret;

  memset(&hashKey, 0, sizeof(DBT));
  memset(&hashVal, 0, sizeof(DBT));
  ret=table->seq(table, &hashKey, &hashVal, R_FIRST);
  while(ret==0){

    ruleNumber=*(int*)(hashVal.data);
    strlcpy(macAddress, (char*)hashKey.data, ADDRMAXLN);
    err_msg("%d:%s", ruleNumber, macAddress);

    /* get next entry */
    ret=table->seq(table, &hashKey, &hashVal, R_NEXT);
  }
}

/************************************************
 close sessions that lost ipfw rule or database entry
 make HashTables of sessions and ipfw-rules, then compare
  sessionTable        ruleTable
  from work-db        from ipfw
   key=macAddr       key=macAddr
   val=0             val=ipfwRule
      ^                 ^  
      |-----------------|  
entry in ruleTable and sessionTable should be matched.
if exists in ruleTable and not in sessionTable, remove rule
if exists in sessionTable and not in ruleTable, remove session
*************************************************/
void closeUnmatchSessions(void){
  DB* ruleTable;
  DB* sessionTable;

  /* prepare hash table for active sessions */
  if((sessionTable = 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 hash table for rule numbers */
  if((ruleTable = 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);
  }

  /* get rule numbers from ipfw */
  GetRuleTableFromIpfw(ruleTable);

  /* get active sessions from work db */
  GetSessionTableFromWorkDb(sessionTable);

  /****debug print
  err_msg("Rule");
  dumpTable(ruleTable);
  err_msg("Session");
  dumpTable(sessionTable);
  ****/

  /* remove unmatched entry */
  RemoveSessionUnmatchedToIpfwRule(ruleTable, sessionTable);
  RemoveIpfwRuleUnmatchedToSession(ruleTable, sessionTable);

  /* erase table area */
  sessionTable->close(sessionTable);
  ruleTable->close(ruleTable);
}

/*******************************
remove session unmatched to ipfw active rule
 (session exists in session-table but no ipfw rule exists in rule-table)
*******************************/
void removeSessionUnmatchedToIpfwRule(DB* ruleTable, DB* sessionTable){
  DBT hashKey;
  DBT hashVal;
  int retRuleTbl;
  int retSesTbl;
  int ruleNumber;
  char userId[USERMAXLN];
  char extraId[USERMAXLN];
  char macAddress[ADDRMAXLN];
  int openTime;
  int checkTime;

  /*** scan session table to find entry unmatched to rule table */
  /* get first entry of session table */
  memset(&hashKey, 0, sizeof(DBT));
  memset(&hashVal, 0, sizeof(DBT));
  retSesTbl=sessionTable->seq(sessionTable, &hashKey, &hashVal, R_FIRST);
  while(retSesTbl==0){

    /* if found a session, save the data */
    strlcpy(macAddress, (char*)hashKey.data, ADDRMAXLN);

    /* get entry having same key in rule table */
    hashKey.data = macAddress;
    hashKey.size = strlen(macAddress)+1;
    memset(&hashVal, 0, sizeof(DBT));
    retRuleTbl=ruleTable->get(ruleTable, &hashKey, &hashVal, 0);

    /*** if rule exists (the session matchs to a rule), do nothing */
    if(retRuleTbl==0){    /* (get-function returns 0 on success) */
      ; 
    }

    /*** else(no rule for the session), remove the session */
    else{
      
      /* write log and close session */
      /* (as Hash(sessionTable) is cleared after scanning, it is untouched) */
      if(!GetSessionFromWorkDb(macAddress, userId, extraId, 
			      &openTime, &checkTime, &ruleNumber)){
	err_msg("ERR at %s#%d: fail to get session info",__FILE__,__LINE__);
      }else{
	WriteCloseToSyslog(userId, extraId, macAddress, openTime);
	PutCloseToMngDb(macAddress);
	DelSessionFromWorkDb(macAddress);
      }

      /* write session info to syslog */
      if(debug>0) WriteSessionInfoToSyslog(userId, extraId, 
					   macAddress, ruleNumber);
    }

    /* get next rule entry */
    retSesTbl=sessionTable->seq(sessionTable, &hashKey, &hashVal, R_NEXT);
  }
}

/***********************************
remove active ipfw rule unmatched to session table
 (ipfw rule exists in rule-table but no corresponding session exists in session-table)
***********************************/
void  removeIpfwRuleUnmatchedToSession(DB* ruleTable, DB* sessionTable){
  DBT hashKey;
  DBT hashVal;
  int retRuleTbl;
  int retSesTbl;
  int ruleNumber;
  char macAddress[ADDRMAXLN];

  /*** scan ipfw rule table to find entry unmatched to session table */
  /* get first entry of ipfw rule table */
  memset(&hashKey, 0, sizeof(DBT));
  memset(&hashVal, 0, sizeof(DBT));
  retRuleTbl=ruleTable->seq(ruleTable, &hashKey, &hashVal, R_FIRST);
  while(retRuleTbl==0){

    /* if found in rule table, save data */
    ruleNumber=*(int*)(hashVal.data);
    strlcpy(macAddress, (char*)hashKey.data, ADDRMAXLN);

    /* get entry having same key in session table */
    hashKey.data = macAddress;
    hashKey.size = strlen(macAddress)+1;
    memset(&hashVal, 0, sizeof(DBT));
    retSesTbl=sessionTable->get(sessionTable, &hashKey, &hashVal, 0);

    /*** if session exists(the rule matchs to a session), do nothing */
    if(retSesTbl==0){    /* (get-function returns 0 on success) */
      ; 
    }

    /*** else(no session for the rule), remove the rule */
    else{

      /* remove entry in ipfw active rules */
      /* (as Hash(ruleTable) is cleared after scanning, it is untouched) */
      CloseClientGate(ruleNumber);
      DelCacheItem(macAddress,"");

      /* write log */
      WriteCloseToSyslog("?", "", macAddress, time(NULL));
      PutCloseToMngDb(macAddress);
      
      /* write session info to syslog (no user info)*/
      if(debug>0) WriteSessionInfoToSyslog("?","", macAddress, ruleNumber);
    }

    /* get next rule entry */
    retRuleTbl=ruleTable->seq(ruleTable, &hashKey, &hashVal, R_NEXT);
  }
}


/******************************************
write open message to syslog
******************************************/
void writeOpenToSyslog(char* userId, char* extraId, char* macAddress){

  if(extraId[0]=='\0'){
    err_msg("OPEN: user %s at %s", 
	    userId, macAddress);
  }else{
    err_msg("OPEN: user %s%s%s at %s", 
	    userId, GetConfValue("UserIdSeparator"), extraId, macAddress);
  }
}

/******************************************
write close message to syslog
******************************************/
void writeCloseToSyslog(char* userId, char* extraId, char* macAddress, int openTime){

  double time_l;
  int hour, min, sec;

  /* time_l is the time length from open-net to close-net */
  time_l=difftime((int)time(NULL), openTime);
  hour=time_l/60/60;
  min=(time_l-hour*60*60)/60;
  sec=(time_l-hour*60*60-min*60);

  if(extraId[0]=='\0'){
    err_msg("CLOS: user %s at %s ( %02d:%02d:%02d )", 
	    userId, macAddress, hour,min,sec);
  }else{
    err_msg("CLOS: user %s%s%s at %s ( %02d:%02d:%02d )", 
	    userId, GetConfValue("UserIdSeparator"), extraId, 
	    macAddress, hour,min,sec);
  }
}

/******************************************
write session info message to syslog
******************************************/
void writeSessionInfoToSyslog(char* userId, char* extraId, char* macAddress, int ruleNumber){

  char detectTimeStr[WORDMAXLN];
  int ttl;

  /* get client info from macinfo table in workdb */
  GetMacInfoFromWorkDb(macAddress, detectTimeStr, &ttl);

  if(extraId[0]=='\0'){
    err_msg("INFO: user=%s macaddr=%s "
	    "ipfwrule=%d ttl=%d lastcheck=%s", 
	    userId, macAddress, ruleNumber, ttl, detectTimeStr);
  }else{
    err_msg("INFO: user=%s%s%s macaddr=%s "
	    "ipfwrule=%d ttl=%d lastcheck=%s", 
	    userId, GetConfValue("UserIdSeparator"), extraId, 
	    macAddress, ruleNumber, ttl, detectTimeStr);
  }
}

/**********************************************
Memorize the MAC&IP address pair to DB and log
**********************************************/
void setMacIpPair(char* macAddress, char* ipAddress, char* userId, char* extraId){
  
  /* if the pair is not found in work db, record it */
  /* the pair in work db is used for saving session detail (remove at the end of session)*/
  /* one session includes plural pairs (one MAC corresponds to plural IPs) */
  if(!IsFoundMacIpPairInWorkDb(macAddress, ipAddress)){
    PutMacIpPairToWorkDb(macAddress, ipAddress);

    /* record the pair to management db */
    /* the pair in management db is the sccess log (don't remove at the end of session)*/
    PutMacIpPairToMngDb(macAddress, ipAddress);

    /* write to syslog */
    if(isNull(extraId)){
      err_msg("FIND: user %s from %s at %s", userId, ipAddress, macAddress);
    }else{
      err_msg("FIND: user %s%s%s from %s at %s", 
	      userId, GetConfValue("UserIdSeparator"), extraId, 
	      ipAddress, macAddress);
    }
  }
}

/**********************************
Reset the MAC&IP address pair in DB
***********************************/
void resetMacIpPairs(char* macAddress){

  /* remove the all-pairs having the MAC from work db */
  DelMacIpPairsInWorkDb(macAddress);
}


/**************************************************
 routines for debugging output
 *************************************************/

int AddSession(char* macAddress, char* userId, char* extraId){
  int ret;
  if(debug>1) err_msg("DEBUG:=>addSession(%s,%s,%s)",
		      macAddress, userId, extraId);
  ret = addSession(macAddress, userId, extraId);
  if(debug>1) err_msg("DEBUG:(%d)<=addSession( )",ret);
  return ret;
}
void DelSession(char* macAddress){
  if(debug>1) err_msg("DEBUG:=>delSession(%s)",macAddress);
  delSession(macAddress);
  if(debug>1) err_msg("DEBUG:<=delSession( )");
}
void RenewSession(char* macAddress){
  if(debug>1) err_msg("DEBUG:=>renewSession(%s)",macAddress);
  renewSession(macAddress);
  if(debug>1) err_msg("DEBUG:<=renewSession( )");
}
void DelUselessSessions(void){
  if(debug>1) err_msg("DEBUG:=>delUselessSessions()");
  delUselessSessions();
  if(debug>1) err_msg("DEBUG:<=delUselessSessions( )");
}
void DelAllSessions(void){
  if(debug>1) err_msg("DEBUG:=>delAllSessions()");
  delAllSessions();
  if(debug>1) err_msg("DEBUG:<=delAllSessions( )");
}
int CloseSession(void* pParam, int argc, char *argv[], char* colName[]){
  int ret;
  if(debug>1) err_msg("DEBUG:=>closeSession()");
  ret = closeSession(pParam, argc, argv, colName);
  if(debug>1) err_msg("DEBUG:(%d)<=closeSession( )",ret);
  return ret;
}
int IsMatchedSessionFound(char* macAddress){
  int ret;
  if(debug>1) err_msg("DEBUG:=>isMatchedSessionFound(%s)",
		      macAddress);
  ret = isMatchedSessionFound(macAddress);
  if(debug>1) err_msg("DEBUG:(%d)<=isMatchedSessionFound( )",ret);
  return ret;
}

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

void WriteOpenToSyslog(char* userId, char* extraId, char* macAddress){
  if(debug>1) err_msg("DEBUG:=>writeOpenToSyslog(%s,%s,%s)", userId, extraId, macAddress);
  writeOpenToSyslog(userId, extraId, macAddress);
  if(debug>1) err_msg("DEBUG:<=writeOpenToSyslog( )");
}

void WriteCloseToSyslog(char* userId, char* extraId, char* macAddress, int openTime){
  if(debug>1) err_msg("DEBUG:=>writeCloseToSyslog(%s,%s,%s,%d)", userId, extraId, macAddress, openTime);
  writeCloseToSyslog(userId, extraId, macAddress, openTime);
  if(debug>1) err_msg("DEBUG:<=writeCloseToSyslog( )");
}

void WriteSessionInfoToSyslog(char* userId, char* extraId, char* macAddress, int ruleNumber){
  if(debug>1) err_msg("DEBUG:=>writeSessionInfoToSyslog(%s,%s,%s,%d)", userId, extraId, macAddress, ruleNumber);
  writeSessionInfoToSyslog(userId, extraId, macAddress, ruleNumber);
  if(debug>1) err_msg("DEBUG:<=writeSessionInfoToSyslog( )");
}


void RemoveSessionUnmatchedToIpfwRule(DB* ruleTable, DB* sessionTable){
  if(debug>1) err_msg("DEBUG:=>removeSessionUnmatchedToIpfwRule()");
  removeSessionUnmatchedToIpfwRule(ruleTable, sessionTable);
  if(debug>1) err_msg("DEBUG:<=removeSessionUnmatchedToIpfwRule()");
}

void RemoveIpfwRuleUnmatchedToSession(DB* ruleTable, DB* sessionTable){
  if(debug>1) err_msg("DEBUG:=>removeIpfwRuleUnmatchedToSession()");
  removeIpfwRuleUnmatchedToSession(ruleTable, sessionTable);
  if(debug>1) err_msg("DEBUG:<=removeIpfwRuleUnmatchedToSession()");

}

void SetMacIpPair(char* macAddress, char* ipAddress, char* userId, char* extraId){
  if(debug>1) err_msg("DEBUG:=>setMacIpPair(%s,%s,%s,%s)",
		      macAddress, ipAddress, userId, extraId);
  setMacIpPair(macAddress, ipAddress, userId, extraId);
  if(debug>1) err_msg("DEBUG:<=setMacIpPair( )");
}

void ResetMacIpPairs(char* macAddress){
  if(debug>1) err_msg("DEBUG:=>resetMacIpPairs(%s)", macAddress);
  resetMacIpPairs(macAddress);
  if(debug>1) err_msg("DEBUG:<=resetMacIpPairs( )");
}

/********************testmain********************
void testmain(){

  char* ipAddress="192.168.0.120";
  char* macAddress="00:01:02:03:04:05";
  char* userId="watanaby";
  char* extraId="";

  addSession(ipAddress, macAddress, userId, extraId);
  sleep(10);
  renewSession(ipAddress);
  sleep(10);
  delUselessSessions();
  sleep(10);
  delUselessSessions();
  delSession(ipAddress);
}
************************************************/
