modules/ac/access_control.c
/* [<][>][^][v][top][bottom][index][help] */
FUNCTIONS
This source file includes following functions.
- AC_to_string
- AC_acl_to_string
- AC_fetch_acc
- AC_check_acl
- AC_acc_addup
- AC_commit
- AC_decay_hook
- AC_decay
- AC_acc_load
- AC_build
- AC_rxwalkhook_print
- 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;
}