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