/***************************************
  $Revision: 1.5 $

  Access control module (ac).

  Status: NOT REVUED, NOT TESTED

  ******************/ /******************
  Filename            : access_control.c
  Author              : ottrey@ripe.net
  OSs Tested          : Solaris
  ******************/ /******************
  Copyright (c) 1999                              RIPE NCC
 
  All Rights Reserved
  
  Permission to use, copy, modify, and distribute this software and its
  documentation for any purpose and without fee is hereby granted,
  provided that the above copyright notice appear in all copies and that
  both that copyright notice and this permission notice appear in
  supporting documentation, and that the name of the author not be
  used in advertising or publicity pertaining to distribution of the
  software without specific, written prior permission.
  
  THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE, INCLUDING
  ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS; IN NO EVENT SHALL
  AUTHOR BE LIABLE FOR ANY SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR ANY
  DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN
  AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
  OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
  ***************************************/
#include <stdio.h>
#include <glib.h>

#define AC_IMPL
#include "rxroutines.h"
#include "erroutines.h"
#include "access_control.h"
#include "socket.h"
#include "mysql_driver.h"
#include "constants.h"

#define AC_DECAY_TIME 10
/* #define AC_DECAY_TIME 3600 */

/* AC_to_string() */
/*++++++++++++++++++++++++++++++++++++++
  Show an access structure

  More:
  +html+ <PRE>
  Authors:
        marek
  +html+ </PRE><DL COMPACT>
  +html+ <DT>Online References:
  +html+ </UL></DL>

  ++++++++++++++++++++++++++++++++++++++*/
char *AC_to_string(GList *leafptr)
{
  char *result_buf;
  acc_st *a = leafptr->data;

  if( wr_malloc( (void **) &result_buf, 256) != UT_OK ) {
      /* do many bad things...*/
      return NULL;
    }
  
  if( a != NULL ) {
    sprintf(result_buf,
            "conn %d\tden %d\tqrs %d\tpub %d\tpriv %d\tbonus %d",
            a->connections,
            a->denials,
            a->queries,     
            a->public_objects,
            a->private_objects,
            a->private_bonus
            );
  }
  else {
    strcpy(result_buf, "DATA MISSING\n");
  }
  
  return result_buf;
} /* AC_to_string() */

/* AC_acl_to_string() */
/*++++++++++++++++++++++++++++++++++++++
  Show an access control list structure

  More:
  +html+ <PRE>
  Authors:
        marek
  +html+ </PRE><DL COMPACT>
  +html+ <DT>Online References:
  +html+ </UL></DL>

  ++++++++++++++++++++++++++++++++++++++*/
char *AC_acl_to_string(GList *leafptr)
{
  char *result_buf;
  acl_st *a = leafptr->data;

  if( wr_malloc( (void **) &result_buf, 256) != UT_OK ) {
      /* do many bad things...*/
      return NULL;
    }
  
  if( a != NULL ) {
    sprintf(result_buf,
            "maxbonus %d\tmaxdenials %d\tdeny %d\ttrustpass %d",
            a->maxbonus,
            a->maxdenials,
            a->deny,     
            a->trustpass
            );
  }
  else {
    strcpy(result_buf, "DATA MISSING\n");
  }
  
  return result_buf;
} /* AC_acl_to_string() */

/* AC_fetch_acc() */
/*++++++++++++++++++++++++++++++++++++++
  Find the runtime accounting record for this IP, 
  store a copy of it in acc_store.
  
  More:
  +html+ <PRE>
  Authors:
        marek
  +html+ </PRE><DL COMPACT>
  +html+ <DT>Online References:
  +html+ </UL></DL>

  ++++++++++++++++++++++++++++++++++++++*/
er_ret_t AC_fetch_acc( ip_addr_t *addr, acc_st *acc_store, int tmout)
{
  GList    *datlist=NULL;
  rx_datref_t *datref;
  er_ret_t ret_err;
  ip_prefix_t prefix;

  prefix.ip = *addr;
  prefix.bits = IP_sizebits(addr->space);
  TH_acquire_read_lock( &(act_runtime->rwlock) );
  
  if( (ret_err = RX_bin_search(RX_SRCH_EXACT, 0, 0, act_runtime, 
                               &prefix, &datlist, RX_ANS_ALL)) == RX_OK ) {
    switch( g_list_length(datlist) ) {
    case 0:
      memset(acc_store, 0, sizeof(acc_st));
      break;
    case 1:
      datref = (rx_datref_t *) g_list_nth_data(datlist,0);
      memcpy(acc_store, (acc_st *) datref->leafptr, sizeof(acc_st));
      break;
    default: die; 
    }
  }

  TH_release_read_lock( &(act_runtime->rwlock) );
  
return -1;
}/* AC_fetch_acc() */

/* AC_check_acl() */
/*++++++++++++++++++++++++++++++++++++++
  
  AC_check_acl:
  
  search for this ip or other applicable record in the access control tree
  
  if( bonus in combined runtime+connection accountings > max_bonus in acl)
            set denial in the acl for this ip (create if needed)
  if( combined denialcounter > max_denials in acl)
            set the permanent ban in acl; save in SQL too
  calculate credit if pointer provided
  save the access record (ip if created or found/prefix otherwise) 
            at *acl_store if provided

  any of the args except address can be NULL

  More:
  +html+ <PRE>
  Authors:
        marek
  +html+ </PRE><DL COMPACT>
  +html+ <DT>Online References:
  +html+ </UL></DL>

  ++++++++++++++++++++++++++++++++++++++*/
er_ret_t AC_check_acl( ip_addr_t *addr, 
                       acc_st *run_acc, 
                       acc_st *query_acc, 
                       acc_st *credit_acc,
                       acl_st *acl_store
                       )
{
  GList    *datlist=NULL;
  ip_prefix_t prefix;
  er_ret_t ret_err;
  acl_st *acl_record;
  rx_datref_t *datref;
  /* will write to the tree only if run_acc or query_acc are provided */
  int writetoacl = (run_acc != NULL  ||  query_acc != NULL);

  prefix.ip = *addr;
  prefix.bits = IP_sizebits(addr->space);
  
  /* lock the tree accordingly */
  if(writetoacl) {
    TH_acquire_write_lock( &(act_acl->rwlock) );
  } else {
    TH_acquire_read_lock( &(act_acl->rwlock) );
  }
  
  /* find a record */
  if( (ret_err = RX_bin_search(RX_SRCH_EXLESS, 0, 0, act_acl, 
                               &prefix, &datlist, RX_ANS_ALL)
       ) != RX_OK   ||  g_list_length(datlist) == 0 ) {
    /* acl tree is not configured at all ! There always must be a
       catch-all record with defaults */
    die;
  }
  
  
  datref = (rx_datref_t *)g_list_nth_data(datlist,0);
  acl_record = (acl_st *)  datref->leafptr;
  
  if( run_acc && credit_acc ) {
    memset( credit_acc, 0, sizeof(acc_st));
    credit_acc->public_objects = -1; /* unlimited */
    credit_acc->private_objects 
      = acl_record->maxbonus - run_acc->private_bonus;
  }

  /* copy the acl record if asked for it*/
  if( acl_store ) {
    *acl_store =  *acl_record;
  }

  /* release lock */
  if(writetoacl) {
    TH_release_write_lock( &(act_acl->rwlock) );
  } else {
    TH_release_read_lock( &(act_acl->rwlock) );
  }
  
  /*
    if( ret_err == RX_OK ) { 
    ret_err = AC_OK;
    }
  */
  return ret_err;
}

void AC_acc_addup(acc_st *a, acc_st *b, int minus)
{
  int mul = minus ? -1 : 1;
  
  /* add all counters from b to those in a */
  a->connections     +=  mul * b->connections;      
  a->denials         +=  mul * b->denials;     
  a->queries         +=  mul * b->queries;       
  a->public_objects  +=  mul * b->public_objects;
  a->private_objects +=  mul * b->private_objects;
  a->private_bonus   +=  mul * b->private_bonus;
}


er_ret_t AC_commit(ip_addr_t *addr, acc_st *acc_conn) {
  /* for all accounting trees: XXX runtime only for the moment
     lock tree (no mercy :-)
       find or create entries,
       increase accounting values by the values from connection acc
       reset the connection acc
     unlock accounting trees
  */
  GList    *datlist=NULL;
  acc_st   *recacc;
  er_ret_t ret_err;
  ip_prefix_t prefix;

  prefix.ip = *addr;
  prefix.bits = IP_sizebits(addr->space);
  
  TH_acquire_write_lock( &(act_runtime->rwlock) );
  
  if( (ret_err = RX_bin_search(RX_SRCH_EXACT, 0, 0, act_runtime, 
                               &prefix, &datlist, RX_ANS_ALL)) == RX_OK ) {
    switch( g_list_length(datlist) ) {
    case 0:
      /* need to create a new accounting record */
      if( (ret_err = wr_malloc( (void **)& recacc, sizeof(acc_st))) == UT_OK ) {
        /*  counters = connection counters */
        memcpy( recacc, acc_conn, sizeof(acc_st));
        
        /* attach. The recacc is to be treated as a dataleaf
           (should work on lower levels than RX_asc_*)
        */
        ret_err = RX_bin_node( RX_OPER_CRE, &prefix, 
                               act_runtime, (rx_dataleaf_t *)recacc );
      }
      break;
    case 1:
      {
        rx_datref_t *datref = (rx_datref_t *) g_list_nth_data( datlist,0 );
        
        /* OK, there is a record already, add to it */
        recacc = (acc_st *) datref->leafptr;
        AC_acc_addup(recacc, acc_conn, ACC_PLUS);
      }
      break;
    default: die; /* there shouldnt be more than 1 entry per IP */
    }
  }
  
  TH_release_write_lock( &(act_runtime->rwlock) );
  return ret_err;
}

er_ret_t AC_decay_hook(rx_node_t *node, int level, int nodecounter, void *con) {
  acc_st *a = node->leaves_ptr->data;
  
  a->private_bonus *= 0.95;

  return RX_OK;
} /* AC_decay_hook() */

er_ret_t AC_decay(void) {
  er_ret_t ret_err;

  /* XXX
     This should be run as a detatched thread.
     Yes the while(1) is crappy b/c there's no way of stopping it, 
     but it's Friday night & everyone has either gone off for
     Christmas break or is down at the pub so it's staying as a while(1)!
     And I'm not sure what effect the sleep() will have on the thread.
  */
  while(1) {

    TH_acquire_write_lock( &(act_runtime->rwlock) );

    if( act_runtime->top_ptr != NULL ) {
       rx_walk_tree(act_runtime->top_ptr, AC_decay_hook,
                         RX_WALK_SKPGLU,  /* skip glue nodes */
                         255, 0, 0, NULL, &ret_err);
    }

    /* it should also be as smart as to delete nodes that have reached 
       zero, otherwise the whole of memory will be filled.
       Next release :-)
    */

    TH_release_write_lock( &(act_runtime->rwlock) );

    printf("AC: decaying access tree. (Every %d seconds)\n", AC_DECAY_TIME);

    sleep(AC_DECAY_TIME);
  }

  return ret_err;
} /* AC_decay() */

er_ret_t AC_acc_load(void)
{
  SQ_connection_t *con=NULL;
  SQ_result_set_t *result;
  SQ_row_t *row;
  er_ret_t ret_err = RX_OK;

  if( (con = SQ_get_connection(CO_get_host(), CO_get_database_port(), 
                        "RIPADMIN", CO_get_user(), CO_get_password() )
       ) == NULL ) {
    fprintf(stderr, "ERROR %d: %s\n", SQ_errno(con), SQ_error(con));
    die;
  }
  
  if( (result = SQ_execute_query(SQ_STORE, con, "SELECT * FROM acl"))
      == NULL ) {
    fprintf(stderr, "ERROR %d: %s\n", SQ_errno(con), SQ_error(con));
    die;
  }
  
  TH_acquire_write_lock( &(act_acl->rwlock) );

  while ( (row = SQ_row_next(result)) != NULL && ret_err == RX_OK) {
    ip_prefix_t mypref;
    acl_st *newacl;
    char *col[6];
    unsigned myint;
    int i;

    memset(&mypref, 0, sizeof(ip_prefix_t));
    mypref.ip.space = IP_V4;
    
    if( (ret_err = wr_malloc( (void **)& newacl, sizeof(acl_st))
         ) == UT_OK ) {

      for(i=0; i<6; i++) {
        if ( (col[i] = SQ_get_column_string(result, row, i)) == NULL) {
          die;
        }
      }
      
      /* prefix ip */
      if( sscanf(col[0], "%u", &mypref.ip.words[0] ) < 1 ) { die; }
      
      /* prefix length */
      if( sscanf(col[1], "%u", &mypref.bits ) < 1 ) { die; }
      
      /* acl contents */
      if( sscanf(col[2], "%u",  & (newacl->maxbonus)   ) < 1 ) { die; }
      if( sscanf(col[3], "%hd", & (newacl->maxdenials) ) < 1 ) { die; }
      
      /* these are chars therefore cannot read directly */
      if( sscanf(col[4], "%u", &myint              ) < 1 ) { die; }
      else {
        newacl->deny = myint;
      }
      if( sscanf(col[5], "%u", &myint  ) < 1 ) { die; }
      else {
        newacl->trustpass = myint;
      }
      
      /* now add to the tree */
      
      ret_err = RX_bin_node( RX_OPER_CRE, &mypref, 
                             act_acl, (rx_dataleaf_t *) newacl );
    }
  } /* while row */

  TH_release_write_lock( &(act_acl->rwlock) );

  SQ_free_result(result);
  /* Close connection */
  SQ_close_connection(con);

  /* Start the decay thread. */
  TH_run2((void *)AC_decay);

  return ret_err;
}

er_ret_t AC_build(void) 
{
  /* create trees */
  if (   RX_tree_cre(0, IP_V4, RX_FAM_IP, "0.0.0.0/0", RX_MEM_RAMONLY, 
                  RX_SUB_NONE, &act_runtime) != RX_OK
      || RX_tree_cre(0, IP_V4, RX_FAM_IP, "0.0.0.0/0", RX_MEM_RAMONLY, 
                  RX_SUB_NONE, &act_hour) != RX_OK
      || RX_tree_cre(0, IP_V4, RX_FAM_IP, "0.0.0.0/0", RX_MEM_RAMONLY, 
                  RX_SUB_NONE, &act_minute) != RX_OK
      || RX_tree_cre(0, IP_V4, RX_FAM_IP, "0.0.0.0/0", RX_MEM_RAMONLY, 
                  RX_SUB_NONE, &act_acl) != RX_OK
         )
    die;
}

er_ret_t AC_rxwalkhook_print(rx_node_t *node, 
                             int level, int nodecounter, 
                             void *con)
{
  char adstr[IP_ADDRSTR_MAX];
  char line[1024];
  char *dat;
  
  
    if( IP_addr_b2a(&(node->prefix.ip), adstr, IP_ADDRSTR_MAX) != IP_OK ) {
      die; /* program error. */
    }
    
    sprintf(line, "%-20s %s\n", adstr, 
            dat=AC_to_string( node->leaves_ptr ));
    wr_free(dat);
    
    SK_cd_puts((sk_conn_st *)con, line);
    return RX_OK;
}

er_ret_t AC_rxwalkhook_print_acl(rx_node_t *node, 
                             int level, int nodecounter, 
                             void *con)
{
  char prefstr[IP_PREFSTR_MAX];
  char line[1024];
  char *dat;
  
  
    if( IP_pref_b2a(&(node->prefix), prefstr, IP_PREFSTR_MAX) != IP_OK ) {
      die; /* program error. */
    }
    
    sprintf(line, "%-20s %s\n", prefstr, 
            dat=AC_acl_to_string( node->leaves_ptr ));
    wr_free(dat);
    
    SK_cd_puts((sk_conn_st *)con, line);
    return RX_OK;
}

