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

 module to control queue 

  As ip address check by database is time consuming procedure,
  the recently checked addresses are cached and skiped.
  Implemented with HashTable and Queue.
  HashTable has recentry checked dataStress and checked time.
  If ip is included in table and time is new, ignore checking.
  Queue has dataStresses odrered by checked time.
  If an old item is found in table, elder items are removed from table.
  The queue controls the 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 MAXFDCOUNT 10

/* data for select fds */
static fd_set fds; /* fd set */
static int selMax=0; /* max number of fds */
static int fdList[MAXFDCOUNT]; /* fd numbers */
static int fdCount=0; /* count of fds */

static int udpPrepared=FALSE;

/**********************************
 get sockaddr, IPv4 or IPv6:
***********************************/
void *get_in_addr(struct sockaddr *sa)
{
  if (sa->sa_family == AF_INET) {
    return &(((struct sockaddr_in*)sa)->sin_addr);
  }else{
    return &(((struct sockaddr_in6*)sa)->sin6_addr);
  }
}


/***************************************
prepare udp server port
****************************************/
int prepareUdpPort(void (*handler)(int)){
  int sockfd;
  struct addrinfo hints, *servinfo, *p;
  int val=1;
  int ret;
  char* udpServerPort;

  /* prepare socket hint */
  memset(&hints, 0, sizeof hints);
  hints.ai_family = AF_UNSPEC;  /* IPv4/IPv6 dual */
  hints.ai_socktype = SOCK_DGRAM; /* UDP */
  hints.ai_flags = AI_PASSIVE;  /* allow any address of mine */

  /* get server addresses info */
  if(isNull(udpServerPort=GetConfValue("UdpServerPort"))){
    err_msg("ERR at %s#%d: no udp server port in conf", __FILE__,__LINE__);
    return FALSE;
  }
  if ( (ret=getaddrinfo(NULL, udpServerPort, &hints, &servinfo)) != 0 ) {
    err_msg("ERR at %s#%d: getaddrinfo: %s", __FILE__,__LINE__, 
	    gai_strerror(ret));
    return FALSE;
  }
  
  /* loop through all addresses */
  FD_ZERO(&fds);
  for(p = servinfo; p != NULL; p = p->ai_next) {

    /* create socket and bind */
    if ((sockfd = socket(p->ai_family, p->ai_socktype, p->ai_protocol)) == -1){
      continue;
    }
    if (bind(sockfd, p->ai_addr, p->ai_addrlen) == -1) {
      close(sockfd);
      continue;
    }
    
    /* if not overflow max, set to select set */
    if(fdCount<MAXFDCOUNT){
      EnableAsyncIo(sockfd, handler); /* set async mode */
      ioctl(sockfd, FIONBIO,&val); /* set non-blocking mode */
      FD_SET(sockfd, &fds);
      fdList[fdCount]=sockfd;
      fdCount++;
      if(selMax<sockfd) selMax=sockfd;
    }
  }
  
/* if no fd is set, error */
  if (fdCount==0) {
    err_msg("ERR at %s#%d: failed to bind UDP server socket",
	    __FILE__,__LINE__);
    return FALSE;
  }

  /* remove work area */
  freeaddrinfo(servinfo);
  
  udpPrepared=TRUE;
  return TRUE;
}

/***************************************
get a data from udp port
non-blocking read
return=received bytes (0=no data)
use in polling loop 
in: buf=read buffer, bufLen=buffer length
out: buf=acquired string, clientIpAddress=client ip address
***************************************/
int getDataFromUdpPort(char* buf, int bufLen, char* clientIpAddress){

  struct timeval timeout={0,0}; /* no wait */
  int sockfd;
  struct sockaddr_storage client_addr;
  socklen_t addr_len;
  int numbytes;
  int i;

  if(!udpPrepared) return 0;

  /* select in non-blocking mode */
  if(select(selMax+1, &fds, NULL, NULL, &timeout)<=0){

    /* if no select, reset fds and exit */ 
    FD_ZERO(&fds);
    for(i=0;i<fdCount; i++){
      FD_SET(fdList[i],&fds);
    }
    timeout.tv_sec=0;
    timeout.tv_usec=0;
    buf[0]='\0';
    return 0;
  }

  /* search data received fd */
  for(i=0; i<fdCount; i++){
    if(FD_ISSET(fdList[i], &fds))break;
  }
  sockfd=fdList[i];
    
  /* receive data */
  addr_len = sizeof client_addr;
  if((numbytes = recvfrom(sockfd, buf, bufLen-1 , 0,
			  (struct sockaddr *)&client_addr, &addr_len)) > 0) {
    buf[numbytes] = '\0'; /* to terminate string */
    inet_ntop(client_addr.ss_family,
	      get_in_addr((struct sockaddr *)&client_addr), 
	      clientIpAddress, ADDRMAXLN);
  }

  /* if zero bytes received, clear fd set */
  else{
    FD_CLR(sockfd, &fds);
  }

  /* return received bytes */
  return numbytes;
}

/*********************************
is the udp client trusted 
(the address is found in the trusted client list in conf file)
*********************************/
int isUdpClientTrusted(char* clientIpAddress){

  char* ipAddressInConf=NULL;
  int found=FALSE;
  
  /* if the address is my local address, return true */
  if(IsMyIpAddress(clientIpAddress)){
    found=TRUE;
  }

  /* if the udp client address is found in conf file, return true */
  else{
    ipAddressInConf=GetFirstConfValue("UdpClient");
    while(!isNull(ipAddressInConf)){
      if(strcmp(clientIpAddress, ipAddressInConf)==0){
	found=TRUE;
	break;
      }
      ipAddressInConf=GetNextConfValue();
    }
  }
  if(!found){
    err_msg("ERR at %s#%d: UDP client [%s] is not found on trusted list in conf file",
	    __FILE__,__LINE__, clientIpAddress);
  }
  return found;
}

/**************************************************
is the address equal to the one of localhost  
**************************************************/
int isMyIpAddress(char* ipAddress){
  struct ifaddrs * ifAddrStruct=NULL;
  struct ifaddrs * ifa=NULL;
  void * tmpAddrPtr=NULL;
  char addressBuffer[INET6_ADDRSTRLEN];
  int result=FALSE;
  
  /* get interface addresses */
  getifaddrs(&ifAddrStruct);
  
  /* loop for interface addresses */
  for (ifa = ifAddrStruct; ifa != NULL; ifa = ifa->ifa_next) {
    
    /* if a valid IP4 Address */
    if (ifa ->ifa_addr->sa_family==AF_INET) {
      
      /* convert address to display form */
      tmpAddrPtr=&((struct sockaddr_in *)ifa->ifa_addr)->sin_addr;
      inet_ntop(AF_INET, tmpAddrPtr, addressBuffer, INET_ADDRSTRLEN);
    } 
    
    /* if a valid IP6 Address */
    else if (ifa->ifa_addr->sa_family==AF_INET6) {
      
      /* if link local, remove scope-id in address */
      struct sockaddr_in6 *addr = (struct sockaddr_in6 *)ifa->ifa_addr;
      if (IN6_IS_ADDR_LINKLOCAL(&addr->sin6_addr)) {
	addr->sin6_addr.s6_addr[2] = addr->sin6_addr.s6_addr[3] = 0;
      }
      
      /* convert address to display form */
      tmpAddrPtr=&((struct sockaddr_in6 *)ifa->ifa_addr)->sin6_addr;
      inet_ntop(AF_INET6, tmpAddrPtr, addressBuffer, INET6_ADDRSTRLEN);
    }
      
    /* compare with arg */
    if(strcmp(addressBuffer, ipAddress)==0){
      result=TRUE;
      break;
    }
  }
  
  /* clean */
  if (ifAddrStruct!=NULL) freeifaddrs(ifAddrStruct);
  
  return result;
}

/***************************************
Set the socket to the asynchronous IO mode
***************************************/
int enableAsyncIo(int sockfd, void (*handler)(int))
{
  int flags;
  struct sigaction sa;

  if(handler==NULL){
    err_msg("ERR at %s#%d: handler is not set",__FILE__,__LINE__);
    return FALSE;
  }

  flags = fcntl(sockfd, F_GETFL);
  fcntl(sockfd, F_SETFL, flags | O_ASYNC); 

  sa.sa_flags = 0;
  sa.sa_handler = handler;
  sigemptyset(&sa.sa_mask);

  if (sigaction(SIGIO, &sa, NULL)){
    err_msg("ERR at %s#%d: sigaction: %s",__FILE__,__LINE__, strerror(errno));
    exit(0);
  }
  if (fcntl(sockfd, F_SETOWN, getpid()) < 0){
    err_msg("ERR at %s#%d: fcntl: %s",__FILE__,__LINE__, strerror(errno));
    exit(0);
  }

  return TRUE;
}

/**************************
 *************************/

int PrepareUdpPort(void (*handler)(int)){
  int ret;
  if(debug>1) err_msg("DEBUG:=>prepareUdpPort( )");
  ret = prepareUdpPort(handler);
  if(debug>1) err_msg("DEBUG:(%d)<=prepareUdpPort( )",ret);
  return ret;
}

int GetDataFromUdpPort(char* buf, int bufLen, char* clientIpAddress){
  int ret;
  if(debug>2) err_msg("DEBUG:=>getDataFromUdpPort(%d)",bufLen);
  ret = getDataFromUdpPort(buf, bufLen, clientIpAddress);
  if(debug>2) err_msg("DEBUG:(%d)<=getDataFromUdpPort(%s,%s)",
		      ret,buf,clientIpAddress);
  return ret;
}

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

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

int EnableAsyncIo(int sockfd, void (*handler)(int)){
  int ret;
  if(debug>1) err_msg("DEBUG:=>enableAsyncIo(%d)",sockfd);
  ret = enableAsyncIo(sockfd, handler);
  if(debug>1) err_msg("DEBUG:(%d)<=enableAsyncIo()", ret);
  return ret;
}
