/**************************************************
OpengateM - MAC address authentication system 

 module for Controling ipfw (firewall) 

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 "opengatemmng.h"

char ruleNumber[WORDMAXLN];  /* ipfw rule number in string form */

static void sigFunc(int signo);

/******************************************************************/
/* open gate for clientAddr                                       */
/*  if forced=TRUE, ignore checking for address overlapping       */
/*      return=ruleNumber                                         */
/*      return=(-1)*ruleNumber: can't open because of overlapping */
/******************************************************************/
int openClientGate(char *clientAddr, int forced, char* userId, char* extraId, char* macAddress)
{
  int fd=0;
  int retNum;
  struct stat st;
  char* lockFile;
  int lockFileExist=TRUE;
  char userIdLong[WORDMAXLN];

  Sigfunc *defaultSigFunc;

  /* prepare userid-long as [userid@extraid] */
  strlcpy(userIdLong, userId, WORDMAXLN);
  if(!isNull(extraId)){
    strlcat(userIdLong, "@", WORDMAXLN);
    strlcat(userIdLong, extraId, WORDMAXLN);
  }

  /* exclusive exec of ipfw to avoid overlapped rule number */
  /**** prepare ****/
  /* search lock file */
  lockFile=GetConfValue("LockFile");
  if(stat(lockFile, &st)!=0) lockFileExist=FALSE;
  else lockFileExist=TRUE;

  /* if lock file exists, exec lock (if not found, lock is ignored) */
  if(lockFileExist){
    
    /* open lockfile */
    fd=open(lockFile, O_RDWR, S_IRUSR|S_IWUSR|S_IRGRP|S_IROTH);
    if(fd==-1){
      err_msg("ERR at %s#%d: lockfile open error",__FILE__,__LINE__);
      return -1;
    } 

    /* set lock timeout (at long time locking, lock is canceled by SIGALRM) */
    if((defaultSigFunc=signalx(SIGALRM, sigFunc))==SIG_ERR){
      err_msg("ERR at %s#%d: set sig alarm error",__FILE__,__LINE__);
      Close(fd);
      return 1;
    }
    alarm(atoi(GetConfValue("LockTimeout")));
    
    /* lock */
    if(Lock(fd)<0){
      err_msg("ERR at %s#%d: lock error",__FILE__,__LINE__);
      Close(fd);
      return -1;
    }

    /* when other process executes here, this process waits in lock() */
    /* returning from lock() means that exclusive execution is possible */
    /* then reset timeout signal and go to main processing */
    signalx(SIGALRM, defaultSigFunc);
    alarm(0);
  }

  /**** read rules ****/
  if((retNum=GetRuleNumber(clientAddr, forced))<0){
    
    /* if fail, then unlock and return */
    if(lockFileExist){
      Unlock(fd);
      Close(fd);
    }
    return retNum; /* perhaps aleady registered addr is -retNum */
  }

  /**** write rules ****/
  /**** if perl script control flag is set in conf file, use perl script */
  /* (not recommended) */
  if(atoi(GetConfValue("IpfwScript/Enable"))){
    if(Systeml(1, GetConfValue("IpfwScript/Path"),GetConfValue("IpfwPath"),
	       ruleNumber,clientAddr,userIdLong,macAddress,"-",
	       GetConfValue("IpfwTagNumber"),(char *)0) != 0){
      err_msg("ERR at %s#%d: exec script error",__FILE__,__LINE__);
      retNum=1;  /* abnormal */
    }
  }

  /**** else, direct control of firewall (default) ***************/
  else{
    
    /********** add outgoing ipfw rule for the client (in IP level) *************/
    /* eg.(ipfw -q add 10000 count tag 123 ip from 192.168.1.10 to any //wata@guest) */
    if(Systeml(1, GetConfValue("IpfwPath"),"-q","add",ruleNumber,
	       "count","tag",GetConfValue("IpfwTagNumber"),
	       "ip","from",clientAddr,"to","any",
	       "//", userIdLong, (char *)0) != 0){
      err_msg("ERR at %s#%d: exec ipfw add error",__FILE__,__LINE__);
      retNum=1;  /* abnormal */
    }

    /********** add incoming ipfw rule for the client *************/
    /* eg.(ipfw -q add 10000 count tag 123 ip from any to 192.168.1.10 //wata@guest) */
    if(Systeml(1, GetConfValue("IpfwPath"),"-q","add",ruleNumber,
	       "count","tag",GetConfValue("IpfwTagNumber"),
	       "ip","from","any","to",clientAddr,
	       "//", userIdLong, (char *)0) != 0){
      err_msg("ERR at %s#%d: exec ipfw add error",__FILE__,__LINE__);
      retNum=1; /* abnormal */
    }
  }

  /* unlock */
  if(lockFileExist){
    Unlock(fd);
    Close(fd);
  }
  
  return retNum;
}


/*****************************************************************/
/* close gate for clientAddr for the rule number                 */
/*****************************************************************/
void closeClientGate(int ruleNumber)
{
  int count;
  char ruleNumberStr[WORDMAXLN];

  snprintf(ruleNumberStr, WORDMAXLN, "%d", ruleNumber); /* to string */

  /* count rule */
  count=CountRuleNumber(ruleNumber);

  if(count>0){
    /* exec ipfw del */
    /* [ipfw del rule] deletes all rule of the rule number at one call */
    if(Systeml(1, GetConfValue("IpfwPath"),"delete",ruleNumberStr,(char *)0) != 0){
      err_msg("ERR at %s#%d: exec ipfw del error",__FILE__,__LINE__);
    }
  }
}

/**************************************
 get unused ipfw rule number       
 error if addr is already in rules  
 if forced=TRUE, ignore checking for address overlapping
 return value ret>0: acquired rule number that can be used 
              ret=-1: no rule number available 
              ret=-2: some system error occured 
              ret=-num: the ip address is already registered in rule 'num' 
**************************************/
int getRuleNumber(char *clientAddr, int forced)
{
  FILE *fpipe;
  char buf[BUFFMAXLN];
  int num,newNum,readinNum;
  int ipfwmin;
  int ipfwmax;
  int ipfwinterval;
  int portStatus;
  int fileStatus;
  char* p;

  enum status {NORMAL, ABNORMAL, FOUND, NOTFOUND, DUP};

  /* exec ipfw list and open pipe */
  if((fpipe=Popenl(1, "r", GetConfValue("IpfwPath"),"list",(char *)0)) == NULL){ 
      err_msg("ERR at %s#%d: exec ipfw list error",__FILE__,__LINE__);
  }
  
  /* search unused rule number in the list read from pipe */
  /* check duplication of clientAddr to existing rules */

  newNum=-1;
  readinNum=0;
  portStatus=NOTFOUND;
  fileStatus=NORMAL;

  /* get rule range from config */
  ipfwmin=atoi(GetConfValue("IpfwRule/Min"));
  ipfwmax=atoi(GetConfValue("IpfwRule/Max"));
  ipfwinterval=atoi(GetConfValue("IpfwRule/Interval"));

  /* each port is checked whether it can be used for new rule or not */
  for(num=ipfwmin;num<=ipfwmax;num+=ipfwinterval){

    /* skip rules smaller than num */
    while(readinNum<num){
      if(fgets(buf, BUFFMAXLN, fpipe)==NULL){
	if(feof(fpipe)==1) fileStatus=EOF; 
	else fileStatus=ABNORMAL;
	break;
      }
      if( sscanf(buf, "%d", &readinNum) !=1 ){
	err_msg("ERR at %s#%d: abnormal ipfw response[ %s ]",
		__FILE__,__LINE__, buf);
	fileStatus=ABNORMAL; /* abnormal responsem exit internal loop */
	break;
      }
    }

    if(fileStatus==ABNORMAL){
      /* abnormal file proc, exit external loop */ 
      break;
    }

    if(fileStatus==EOF){
      /* EOF before reading a rule that is larger or equal to num */
      /* it means that num can be used for new client */
      portStatus=FOUND;
      newNum=num;
      break;
    }

    /* at this point, readinNum is larger or equal to num */
    /* check number duplication */
    if(readinNum==num){

      /* if clientAddr found in the existing rule, then duplicate err exit */
      /* if in forced mode, ignore the checking */
      if(!forced){
	if(((p=(char*)strstr(buf+1,clientAddr))!=NULL)
	   && isspace(*(p-1))
	   && !isalnum(*(p+strlen(clientAddr)))){
	  newNum=num;
	  portStatus=DUP;
	  break;
	} else{
	  continue;
	}
      }
      continue;
    }
 
    /* at this point, readNum is larger than num */
    /* it means that num can be used for new client */
    newNum=num;
    portStatus=FOUND;

    break;
  }
  
  /* close pipe */
  Pclose(fpipe);
    
  if(fileStatus==ABNORMAL){
    err_msg("ERR at %s#%d: abnormal ipfw response ",__FILE__,__LINE__);
    return -2;
  }
  if(portStatus==NOTFOUND){
    err_msg("ERR at %s#%d: cannot get unused ipfw number",__FILE__,__LINE__);
    return -1;
  }
  if(portStatus==DUP){
    snprintf(ruleNumber, WORDMAXLN, "%d", newNum); /* to string */
    return -newNum;
  }

  snprintf(ruleNumber, WORDMAXLN, "%d", newNum); /* to string */
  return newNum;
}

/*******************************
 get packet count from ipfw  
*******************************/
int getPacketCount(int ruleNumber)
{
  FILE *fpipe;
  char buf[BUFFMAXLN];
  int rule;
  int packets,packetsSum;
  char ruleNumberStr[WORDMAXLN];

  snprintf(ruleNumberStr, WORDMAXLN, "%d", ruleNumber); /* to string */

  /* exec proc */
  if((fpipe=Popenl(1, "r", GetConfValue("IpfwPath"),"-a","list",ruleNumberStr,(char *)0)) == NULL){ 
    err_msg("ERR at %s#%d: exec ipfw -a list error",__FILE__,__LINE__);
  }

  /* search unused number in the list read from pipe */
  packetsSum=0;
    
  while(fgets(buf, BUFFMAXLN, fpipe)!=NULL){
    sscanf(buf, "%d %d", &rule, &packets);   /* get packet count */
    packetsSum+=packets;
  }

  /* close pipe */
  Pclose(fpipe);

  return packetsSum;
}

/**********************************************
 get rule count registed to a rule number   
**********************************************/
int countRuleNumber(int ruleNumber)
{
  FILE *fpipe;
  char buf[BUFFMAXLN];
  int ruleCount;
  char ruleNumberStr[WORDMAXLN];

  snprintf(ruleNumberStr, WORDMAXLN, "%d", ruleNumber); /* to string */

  /* exec proc */
  if((fpipe=Popenl(1, "r", GetConfValue("IpfwPath"),"list",ruleNumberStr,(char *)0)) == NULL){ 
    err_msg("ERR at %s#%d: exec ipfw list error",__FILE__,__LINE__);
  }
  
  /* count line read from pipe */
  ruleCount = 0;
  while(fgets(buf, BUFFMAXLN, fpipe)!=0) ruleCount++;

  /* close pipe */
  Pclose(fpipe);

  return ruleCount;
}

/**********************************************
 function called by signal int              
**********************************************/
static void sigFunc(int signo)
{
  return;
}


/**********************************************
get rule numbers table from ipfw rule set
**********************************************/
int getRuleTableFromIpfw(DB* ruleTable){

  DBT hashKey;
  DBT hashVal;
  FILE *fpipe;
  char buf[BUFFMAXLN];
  int ruleNumber;
  char clientAddr[ADDRMAXLN];
  char *p;
  int ipfwmin;
  int ipfwmax;
  int ipfwinterval;
  int resultFlag=FALSE;
  
  /* exec ipfw list and open pipe */
  if((fpipe=Popenl(1, "r", GetConfValue("IpfwPath"),"list",(char *)0)) == NULL){ 
    err_msg("ERR at %s#%d: exec ipfw list error",__FILE__,__LINE__);
  }
  
  /* get rule range from config */
  ipfwmin=atoi(GetConfValue("IpfwRule/Min"));
  ipfwmax=atoi(GetConfValue("IpfwRule/Max"));
  ipfwinterval=atoi(GetConfValue("IpfwRule/Interval"));
  
  /* get ipfw rule line from pipe */
  while(fgets(buf, BUFFMAXLN, fpipe)!=NULL){

    /* get ruleNumber(=leftmost number) */
    /* 10000 count tag 123 ip from 192.168.0.2 to any .... */
    /* 10000 count tag 123 ip from any to 192.168.0.2 .... */
    if( sscanf(buf, "%d", &ruleNumber) !=1 ) continue;

    /* check the rule number range */
    if(ruleNumber < ipfwmin)continue;
    if(ruleNumber > ipfwmax)break;

    /* get clientAddr(=after [from]) in the line */
    if((p=(char*)strstr(buf, "from"))==NULL) continue;
    if( sscanf((p+4), "%s", clientAddr) != 1 ) continue;
    if( (strchr(clientAddr,'.')==NULL) && (strchr(clientAddr,':')==NULL) )continue;
      
    /* put to the hash table */
    resultFlag=TRUE;
    hashVal.data = &ruleNumber;
    hashVal.size = sizeof(int);    
    hashKey.data = clientAddr;
    hashKey.size = strlen(clientAddr)+1;
    if(ruleTable->put(ruleTable, &hashKey, &hashVal, 0) == -1) {
      err_msg("ERR at %s#%d: fail to put into hash table",__FILE__,__LINE__);
    }
  }

  /* close pipe */
  Pclose(fpipe);

  return resultFlag;
}


/*********************************************
 routines for debugging output
**********************************************/
int GetRuleNumber(char *clientAddr, int forced)
{
  int ret;

  if(debug>1) err_msg("DEBUG:=>getRuleNumber(%s,%d)",clientAddr,forced);
  ret=getRuleNumber(clientAddr, forced);
  if(debug>1) err_msg("DEBUG:(%d)<=getRuleNumber( )",ret);

  return ret;
}

int OpenClientGate(char *clientAddr, int forced, char* userId, char* extraId,char* macAddress)
{
  int ret;

  if(debug>1) err_msg("DEBUG:=>openClientGate(%s,%d,%s,%s)",clientAddr,forced,userId,extraId,macAddress);
  ret=openClientGate(clientAddr,forced,userId,extraId,macAddress);
  if(debug>1) err_msg("DEBUG:(%d)<=openClientGate( )",ret);

  return ret;
}

void CloseClientGate(int ruleNumber)
{
  if(debug>1) err_msg("DEBUG:=>closeClientGate(%d)",ruleNumber);
  closeClientGate(ruleNumber);
  if(debug>1) err_msg("DEBUG:<=closeClientGate( )");
}


int GetPacketCount(int ruleNumber)
{
  int ret;

  if(debug>1) err_msg("DEBUG:=>getPacketCount(%d)",ruleNumber);
  ret=getPacketCount(ruleNumber);
  if(debug>1) err_msg("DEBUG:(%d)<=getPacketCount( )",ret);

  return ret;
}

int CountRuleNumber(int ruleNumber)
{
  int ret;
  
  if(debug>1) err_msg("DEBUG:=>countRuleNumber(%d)", ruleNumber);
  ret=countRuleNumber(ruleNumber);
  if(debug>1) err_msg("DEBUG:(%d)<=countRuleNumber( )",ret);
  
  return ret;
}

int GetRuleTableFromIpfw(DB* ruleTable){
  int ret;
  if(debug>1) err_msg("DEBUG:=>getRuleTableFromIpfw()");
  ret=getRuleTableFromIpfw(ruleTable);
  if(debug>1) err_msg("DEBUG:<=getRuleTableFromIpfw( )", ret);
  return ret;
}
