modules/ac/access_control.c

/* [<][>]
[^][v][top][bottom][index][help] */

FUNCTIONS

This source file includes following functions.
  1. AC_to_string
  2. AC_acl_to_string
  3. AC_fetch_acc
  4. AC_check_acl
  5. AC_acc_addup
  6. AC_commit
  7. AC_decay_hook
  8. AC_decay
  9. AC_acc_load
  10. AC_build
  11. AC_rxwalkhook_print
  12. AC_rxwalkhook_print_acl

/***************************************
  $Revision: 1.10 $

  Access control module (ac).

  Status: NOT REVIEWED, 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 600
/* #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)
/* [<][>][^][v][top][bottom][index][help] */
{
  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\tpass %d\tden %d\tqrs %d\tpub %d\tpriv %d\tbonus %d",
            a->connections,
            a->addrpasses,
            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)
/* [<][>][^][v][top][bottom][index][help] */
{
  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\tmaxpublic %d\tdeny %d\ttrustpass %d\t",
            a->maxbonus,
            a->maxdenials,
            a->maxpublic,
            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)
/* [<][>][^][v][top][bottom][index][help] */
{
  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 less specific 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, 
/* [<][>][^][v][top][bottom][index][help] */
                       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;   
  acc_st run_acc;

  AC_fetch_acc( addr, &run_acc );
  
  prefix.ip = *addr;
  prefix.bits = IP_sizebits(addr->space);
  
  /* lock the tree accordingly */
  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;
  
  /* calculate the credit if pointer given */
  if( credit_acc ) {
    memset( credit_acc, 0, sizeof(acc_st));
    credit_acc->public_objects =                       /* -1 == unlimited */
      acl_record->maxpublic - run_acc.public_objects;
    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;
  }

  /* XXX checking tree consistency */
  {
    rx_treecheck_t errorfound;
    er_ret_t err;
    if( (err=RX_treecheck(act_acl, 1, &errorfound)) != RX_OK ) {
      fprintf(stderr, "Nope! %d returned \n", err);
      die;
    }
  }  

  /* release lock */
  TH_release_read_lock( &(act_acl->rwlock) );
  
  g_list_foreach(datlist, rx_free_list_element, NULL);
  g_list_free(datlist);
  /*
    if( ret_err == RX_OK ) { 
    ret_err = AC_OK;
    }
  */
  return ret_err;
}

void AC_acc_addup(acc_st *a, acc_st *b, int minus)
/* [<][>][^][v][top][bottom][index][help] */
{
  int mul = minus ? -1 : 1;
  
  /* add all counters from b to those in a */
  a->connections     +=  mul * b->connections;   
  a->addrpasses      +=  mul * b->addrpasses;  
 
  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, acl_st *acl_copy) {
/* [<][>][^][v][top][bottom][index][help] */
  /* 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

     THEN

     write lock acl 
       check maxbonus
       set denial
     unlock acl
  */
  GList    *datlist=NULL;
  acc_st   *recacc;
  er_ret_t ret_err;
  ip_prefix_t prefix;
  int permanent_ban=0;

  prefix.ip = *addr;
  prefix.bits = IP_sizebits(addr->space);

  acc_conn->private_bonus = acc_conn->private_objects;

  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
           (must use 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 shouldn't be more than 1 entry per IP */
    }
  }
  /* free search results */
  g_list_foreach(datlist, rx_free_list_element, NULL);
  g_list_free(datlist);
  datlist=NULL;

  /* set permanent ban if deserved  and if not set yet */
  if( recacc->denials > acl_copy->maxdenials && acl_copy->deny == 0) {
    permanent_ban = 1;
  }

  /* XXX checking tree consistency */
  {
    rx_treecheck_t errorfound;
    er_ret_t err;
    if( (err=RX_treecheck(act_runtime, 1, &errorfound)) != RX_OK ) {
      fprintf(stderr, "Nope! %d returned \n", err);
      die;
    }
  }  

  TH_release_write_lock( &(act_runtime->rwlock) );
  memset(acc_conn,0, sizeof(acc_st));

  /*++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++*/
  /* now check the denials and set the permanent ban */
  
  if( permanent_ban ) {
    acl_st *newacl;

    TH_acquire_write_lock( &(act_acl->rwlock) );  
    /* find a record in the tree */
    dieif( (ret_err = RX_bin_search(RX_SRCH_EXACT, 0, 0, act_acl, 
                                    &prefix, &datlist, RX_ANS_ALL)
            ) != RX_OK );
    
    switch( g_list_length(datlist)) {
    case 0:
      dieif( wr_calloc((void **)&newacl, 1, sizeof(acl_st)) != UT_OK );

      /* make the new one inherit all parameters after the old one
         - except the denial :-) */
      *newacl = *acl_copy;
      newacl->deny = 1;
      
      /* link in */
      RX_bin_node(RX_OPER_CRE, &prefix, act_acl, (rx_dataleaf_t *)newacl);
      break;
    case 1:
      {
        /* Uh-oh, the guy is already known ! (or special, in any case) */ 
        rx_datref_t *datref = (rx_datref_t *)g_list_nth_data(datlist,0);
        newacl = (acl_st *) datref->leafptr;

        newacl->deny = 1;
      }
      break;
    default:
      die;
    } 
    
    /* insert/replace a record in the database */
    {
      
      SQ_connection_t *sql_connection = NULL;
      SQ_result_set_t *result;
      SQ_row_t *row;
      char *fulltext;
      char newcomment[256];
      char *oldcomment;
      char *query;
      char querybuf[256];

      sprintf(newcomment,"Automatic permanent ban set at %ld", time(NULL));
      
      sql_connection = SQ_get_connection(CO_get_host(),
                                         CO_get_database_port(),
                                         "RIPADMIN",
                                         CO_get_user(), 
                                         CO_get_password() );

      /* get the old entry, extend it */
      sprintf(querybuf, "SELECT comment FROM acl WHERE "
                     "prefix = %u AND prefix_length = %d", 
              prefix.ip.words[0],
              prefix.bits);
      result = SQ_execute_query(SQ_STORE, sql_connection, querybuf);
      if( SQ_num_rows(result) == 1 ) {
        assert( (row = SQ_row_next(result)) != NULL);
        oldcomment = SQ_get_column_string(result, row, 0);
      }
      else {
        oldcomment = "";
      }
      
      dieif( wr_malloc((void **)&fulltext, 
                       strlen(oldcomment) + strlen(newcomment) + 2) 
             != UT_OK );
      
      sprintf(fulltext,"%s%s%s", oldcomment, 
              strlen(oldcomment) > 0 ? "\n" : "",
              newcomment);

      SQ_free_result(result);
      
      
      /* must hold the thing below (replace..blah blah blah) + fulltext */
      dieif( wr_malloc((void **)&query, strlen(fulltext) + 256) != UT_OK );
      
      /* compose new entry and insert it */
      sprintf(query, "REPLACE INTO acl VALUES(%u, %d, %d, %d, %d, %d, %d,"
              "\"%s\")",
              prefix.ip.words[0],
              prefix.bits,
              newacl->maxbonus,
              newacl->maxpublic,
              newacl->maxdenials,
              newacl->deny,
              newacl->trustpass,
              fulltext);
            
      SQ_execute_query(SQ_NOSTORE, sql_connection, query);
      SQ_close_connection(sql_connection);
      
      wr_free(query);
      wr_free(fulltext);
    }
      
    TH_release_write_lock( &(act_acl->rwlock) );
  }

  return ret_err;
}

er_ret_t AC_decay_hook(rx_node_t *node, int level, int nodecounter, void *con) {
/* [<][>][^][v][top][bottom][index][help] */
  acc_st *a = node->leaves_ptr->data;
  
  a->private_bonus *= 0.95;

  return RX_OK;
} /* AC_decay_hook() */

er_ret_t AC_decay(void) {
/* [<][>][^][v][top][bottom][index][help] */
  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)
/* [<][>][^][v][top][bottom][index][help] */
{
  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[7];
    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<7; 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], "%u",  & (newacl->maxpublic)   ) < 1 ) { die; }
      if( sscanf(col[4], "%hd", & (newacl->maxdenials) ) < 1 ) { die; }
      
      /* these are chars therefore cannot read directly */
      if( sscanf(col[5], "%u", &myint              ) < 1 ) { die; }
      else {
        newacl->deny = myint;
      }
      if( sscanf(col[6], "%u", &myint  ) < 1 ) { die; }
      else {
        newacl->trustpass = myint;
      }
      
      /* free space */
      for(i=0; i<6; i++) free(col[i]);
      
      
      /* 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) 
/* [<][>][^][v][top][bottom][index][help] */
{
  /* 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, 
/* [<][>][^][v][top][bottom][index][help] */
                             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, 
/* [<][>][^][v][top][bottom][index][help] */
                             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;
}


/* [<][>][^][v][top][bottom][index][help] */