modules/ac/access_control.c

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

FUNCTIONS

This source file includes following functions.
  1. AC_to_string_header
  2. AC_to_string
  3. AC_credit_to_string
  4. AC_acl_to_string_header
  5. AC_acl_to_string
  6. AC_findexless_acl_l
  7. AC_findcreate_acl_l
  8. AC_findcreate_account_l
  9. AC_fetch_acc
  10. AC_check_acl
  11. AC_acc_addup
  12. AC_commit_credit
  13. AC_acl_sql
  14. AC_ban_set
  15. AC_asc_ban_set
  16. AC_commit
  17. AC_decay_hook
  18. AC_decay
  19. AC_acc_load
  20. AC_build
  21. AC_rxwalkhook_print
  22. AC_rxwalkhook_print_acl

/***************************************
  $Revision: 1.18 $

  Access control module (ac) - access control for the query part

  Status: NOT REVIEWED, TESTED
  
  Design and implementation by: Marek Bukowy
  
  ******************/ /******************
  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_OK RX_OK
#define AC_INVARG IP_INVARG

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

#define AC_DECAY_TIME 600
/* #define AC_DECAY_TIME 3600 */


#define ACL_FORMAT        "%10d %10d %10d %10d %10d"
#define ACL_HEADER  "%-20s %10s %10s %10s %10s %10s\n"


#define ACC_FORMAT       "%4d    %4d    %4d    %4d    %6d    %6d    %6d"
#define ACC_HEADER "%-20s %4s    %4s    %4s    %4s    %6s    %6s    %6s\n"

/* AC_to_string_header() */
char *AC_to_string_header(void) 
/* [<][>][^][v][top][bottom][index][help] */
{
  char *result_buf;

  dieif( wr_malloc( (void **) &result_buf, 256) != UT_OK );
  
  sprintf(result_buf, ACC_HEADER, "ip",
          "conn", "pass", "deny", "qry", "pub", "priv", "bonus" );

  return result_buf;
}

/* 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 ) {
    strcpy(result_buf, "DATA MISSING!");
  }
  else {
    sprintf(result_buf,
            
            /*            "conn %4d  pass %4d  den %4d  qrs %4d  pub %5d  priv %5d  bonus %5d",*/
            ACC_FORMAT,
            a->connections,
            a->addrpasses,
            a->denials,
            a->queries,     
            a->public_objects,
            a->private_objects,
            a->private_bonus
            );
  }
  
  return result_buf;
} /* AC_to_string() */


/*++++++++++++++++++++++++++++++++++++++
  Show credit (for logging of queries)

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

  ++++++++++++++++++++++++++++++++++++++*/
char *AC_credit_to_string(acc_st *a)
/* [<][>][^][v][top][bottom][index][help] */
{
  char *result_buf;
  
  if( wr_malloc( (void **) &result_buf, 64) != UT_OK ) {
      /* do many bad things...*/
      return NULL;
    }
  
  dieif( a == NULL );

  sprintf(result_buf,"%d+%d%s",
          a->private_objects,
          a->public_objects,
          a->denials ? " **DENIED**" : ""
          );

  return result_buf;
} /* AC_credit_to_string */ 


char *
AC_acl_to_string_header(void)
/* [<][>][^][v][top][bottom][index][help] */
{
  char *result_buf;
  dieif( wr_malloc( (void **) &result_buf, 256) != UT_OK );

  sprintf(result_buf, ACL_HEADER, "ip",
          "maxbonus", "maxdenials", "maxpublic", "ban", "trustpass" );

  return result_buf;
}


/* 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, ACL_FORMAT,
            a->maxbonus,
            a->maxdenials,
            a->maxpublic,
            a->deny,     
            a->trustpass
            );
  }
  else {
    strcpy(result_buf, "DATA MISSING\n");
  }
  
  return result_buf;
} /* AC_acl_to_string() */

er_ret_t
AC_findexless_acl_l(ip_prefix_t *prefix, acl_st *store_acl)
/* [<][>][^][v][top][bottom][index][help] */
{
  GList       *datlist=NULL;
  er_ret_t    ret_err;
  rx_datref_t *datref;  

  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);

  *store_acl = * ((acl_st *)  datref->leafptr);

  wr_clear_list( &datlist );

  /* 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;
    }
  }  

  return ret_err;
}

er_ret_t
AC_findcreate_acl_l(ip_prefix_t *prefix, acl_st **store_acl)
/* [<][>][^][v][top][bottom][index][help] */
{
  GList       *datlist=NULL;
  er_ret_t    ret_err;
  acl_st      *newacl;
  acl_st acl_copy;    

  if( NOERR(ret_err = RX_bin_search(RX_SRCH_EXACT, 0, 0, act_acl, 
                                    prefix, &datlist, RX_ANS_ALL)
            )) {
    
    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 */
      
      AC_findexless_acl_l(prefix, &acl_copy);

      *newacl = acl_copy;
      
      /* link in */
      RX_rt_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;
      }
      break;
    default:
      die;
    }
  } 

  /* free search results */
  wr_clear_list( &datlist );
  
  /* store */
  *store_acl = newacl;
  return ret_err;
}

/* 
   finds exact prefix or creates area initialised to zeros + sets ptr to it.

   returns error code

   operates on the given tree
   assumes tree locked 
 */
er_ret_t 
AC_findcreate_account_l(rx_tree_t *tree, ip_prefix_t *prefix, 
/* [<][>][^][v][top][bottom][index][help] */
                        acc_st **acc_store)
{
  GList       *datlist=NULL;
  er_ret_t    ret_err;
  acc_st      *recacc;

  if( (ret_err = RX_bin_search(RX_SRCH_EXACT, 0, 0, tree, 
                               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 = init to zeros */
        memset( recacc, 0, sizeof(acc_st));
        
        /* attach. The recacc is to be treated as a dataleaf
           (must use lower levels than RX_asc_*)
        */
        ret_err = RX_rt_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 */
        recacc = (acc_st *) datref->leafptr;
        
      }
      break;
    default: die; /* there shouldn't be more than 1 entry per IP */
    }
  }
    
  wr_clear_list( &datlist );
  
  *acc_store = recacc;
  
  return ret_err;
}


/* AC_fetch_acc() */
/*++++++++++++++++++++++++++++++++++++++
  Finds the runtime accounting record for this IP, 
  stores a copy of it in acc_store. 

  If not found, then it is created and initialised to zeros in findcreate()

  ++++++++++++++++++++++++++++++++++++++*/
er_ret_t AC_fetch_acc( ip_addr_t *addr, acc_st *acc_store)
/* [<][>][^][v][top][bottom][index][help] */
{
  er_ret_t ret_err;
  ip_prefix_t prefix;
  acc_st *ac_ptr;

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

  TH_acquire_read_lock( &(act_runtime->rwlock) );
  
  ret_err = AC_findcreate_account_l(act_runtime, &prefix, &ac_ptr);
  *acc_store = *ac_ptr;

  TH_release_read_lock( &(act_runtime->rwlock) );

  return ret_err;
}/* 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

  ++++++++++++++++++++++++++++++++++++++*/
er_ret_t AC_check_acl( ip_addr_t *addr, 
/* [<][>][^][v][top][bottom][index][help] */
                       acc_st *credit_acc,
                       acl_st *acl_store
                       )
{
  ip_prefix_t prefix;
  er_ret_t    ret_err;
  acl_st      acl_record;
  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 an applicable record */
  AC_findexless_acl_l(&prefix, &acl_record);
  
  /* 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;
  }

  /* release lock */
  TH_release_read_lock( &(act_acl->rwlock) );
  
 
  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;
}


/* 
   performs the commit on an accounting tree (locks them first)
   stores a copy of the accounting record at rec_store
*/
er_ret_t 
AC_commit_credit(rx_tree_t *tree, ip_prefix_t *prefix, 
/* [<][>][^][v][top][bottom][index][help] */
                 acc_st *acc_conn, acc_st *rec_store )
{
  acc_st      *accountrec;
  er_ret_t    ret_err;


  acc_conn->private_bonus = acc_conn->private_objects;

  TH_acquire_write_lock( &(tree->rwlock) );

  AC_findcreate_account_l(act_runtime, prefix, &accountrec);
  
  AC_acc_addup(accountrec, acc_conn, ACC_PLUS);
  /* XXX checking tree consistency */

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

  TH_release_write_lock( &(tree->rwlock) );
 
  *rec_store = *accountrec;
  
  return ret_err;
}

/* insert/replace a record in the database */
er_ret_t 
AC_acl_sql(ip_prefix_t *prefix, acl_st *newacl, char *newcomment )
/* [<][>][^][v][top][bottom][index][help] */
{  
  SQ_connection_t *sql_connection = NULL;
  SQ_result_set_t *result;
  SQ_row_t *row;
  char *oldcomment;
  char *query;
  char querybuf[256];
  
  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);
  dieif( SQ_execute_query(sql_connection, querybuf, &result) == -1 );
  
  if( SQ_num_rows(result) == 1 ) {
    dieif( (row = SQ_row_next(result)) == NULL);
    oldcomment = SQ_get_column_string(result, row, 0);
  }
  else {
    oldcomment = "";
  }

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

}


/* re/sets the permanent ban flag both in the acl tree in memory
   and the sql table. The "text" is appended to the comment 
   in the sql record (the expected cases are
   - "automatic" in case the limit is exceeded and ban is set by s/w
   - "manual"    in case it is (un)set from the config iface
*/
er_ret_t
AC_ban_set(ip_prefix_t *prefix, char *text, int denyflag)
/* [<][>][^][v][top][bottom][index][help] */
{
  acl_st *treeacl;
  char newcomment[256];
  er_ret_t ret_err;
  time_t  clock;
  char timebuf[26];
  
  time(&clock);
  ctime_r(&clock, timebuf);

  sprintf(newcomment,"%s permanent ban set to %d at %s", text, 
          denyflag, timebuf);
    
  TH_acquire_write_lock( &(act_acl->rwlock) );  

  /* find a record in the tree */  
  if( NOERR(ret_err = AC_findcreate_acl_l( prefix, &treeacl )) ) {
    treeacl->deny = denyflag;
    ret_err = AC_acl_sql( prefix, treeacl, newcomment );
  }
  TH_release_write_lock( &(act_acl->rwlock) );

  return ret_err;
}

er_ret_t
AC_asc_ban_set(char *addrstr, char *text, int denyflag)
/* [<][>][^][v][top][bottom][index][help] */
{
  er_ret_t ret_err;
  GList *preflist = NULL;
  ip_keytype_t key_type;

  if( (ret_err = IP_smart_conv(addrstr, 0, 0,
                               &preflist, IP_PLAIN, &key_type)) != IP_OK ) {
    return ret_err;
  }
  
  /* allow only one prefix */
  /* The argument can be even a range, but must decompose into one prefix */
  if(  NOERR(ret_err) && g_list_length( preflist ) != 1 ) {
    ret_err = AC_INVARG;
  }
  
  if( NOERR(ret_err) ) {
    ret_err = AC_ban_set( (g_list_first(preflist)->data), text, denyflag);
  }

  wr_clear_list( &preflist );
  
  return ret_err;
}

er_ret_t AC_commit(ip_addr_t *addr, acc_st *acc_conn, acl_st *acl_copy) {
/* [<][>][^][v][top][bottom][index][help] */
  /* 
        lock runtime + minute accounting trees 
        -----------------------  XXX runtime only for the moment
           find or create entries, 
           increase accounting values by the values from passed acc
           check values against acl, see if permanent ban applies

           reset the connection acc
        unlock accounting trees

        if permanent ban - set it! :
            lock acl
            find/create IP in memory
            set ban
            find/create IP in SQL
            copy old values (if any), set ban, append comment
            unlock acl
  */
  
  acc_st   account;
  er_ret_t ret_err;
  ip_prefix_t prefix;

  prefix.ip = *addr;
  prefix.bits = IP_sizebits(addr->space);
  
  ret_err = AC_commit_credit(act_runtime, &prefix, acc_conn, &account);
  /* XXX add more trees here */
  
  memset(acc_conn,0, sizeof(acc_st));

  /* set permanent ban if deserved  and if not set yet */
  if( account.denials > acl_copy->maxdenials 
      && acl_copy->deny == 0 
      && NOERR(ret_err) ) {
    
    ret_err = AC_ban_set(&prefix, "Automatic", 1);
  }

  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() */



/* 
     This should be run as a detached thread.
*/
er_ret_t AC_decay(void) {
/* [<][>][^][v][top][bottom][index][help] */
  er_ret_t ret_err;

  
  while(CO_get_do_server()) {

    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);

    SV_sleep(LOCK_SHTDOWN, 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( SQ_execute_query(con, "SELECT * FROM acl", &result) == -1 ) {
      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++) {
          wr_free(col[i]);
      }
      
      
      /* now add to the tree */
      
      ret_err = RX_rt_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);

  

  return ret_err;
}

er_ret_t AC_build(void) 
/* [<][>][^][v][top][bottom][index][help] */
{
  /* create trees */
  if (      RX_tree_cre("0.0.0.0/0", RX_FAM_IP, RX_MEM_RAMONLY, 
                        RX_SUB_NONE, &act_runtime) != RX_OK
         || RX_tree_cre("0.0.0.0/0", RX_FAM_IP, RX_MEM_RAMONLY, 
                        RX_SUB_NONE, &act_hour) != RX_OK
         || RX_tree_cre("0.0.0.0/0", RX_FAM_IP, RX_MEM_RAMONLY, 
                        RX_SUB_NONE, &act_minute) != RX_OK
         || RX_tree_cre("0.0.0.0/0", RX_FAM_IP, RX_MEM_RAMONLY, 
                        RX_SUB_NONE, &act_acl) != RX_OK
         )
    die; /*can be changed to an error and handled ... some day */

  return RX_OK;
}

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] */