1 | /***************************************
2 | $Revision: 1.10 $
3 |
4 | Access control module (ac).
5 |
6 | Status: NOT REVIEWED, NOT TESTED
7 |
8 | ******************/ /******************
9 | Filename : access_control.c
10 | Author : ottrey@ripe.net
11 | OSs Tested : Solaris
12 | ******************/ /******************
13 | Copyright (c) 1999 RIPE NCC
14 |
15 | All Rights Reserved
16 |
17 | Permission to use, copy, modify, and distribute this software and its
18 | documentation for any purpose and without fee is hereby granted,
19 | provided that the above copyright notice appear in all copies and that
20 | both that copyright notice and this permission notice appear in
21 | supporting documentation, and that the name of the author not be
22 | used in advertising or publicity pertaining to distribution of the
23 | software without specific, written prior permission.
24 |
25 | THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE, INCLUDING
26 | ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS; IN NO EVENT SHALL
27 | AUTHOR BE LIABLE FOR ANY SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR ANY
28 | DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN
29 | AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
30 | OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
31 | ***************************************/
32 | #include <stdio.h>
33 | #include <glib.h>
34 |
35 | #define AC_IMPL
36 | #include "rxroutines.h"
37 | #include "erroutines.h"
38 | #include "access_control.h"
39 | #include "socket.h"
40 | #include "mysql_driver.h"
41 | #include "constants.h"
42 |
43 | #define AC_DECAY_TIME 600
44 | /* #define AC_DECAY_TIME 3600 */
45 |
46 | /* AC_to_string() */
47 | /*++++++++++++++++++++++++++++++++++++++
48 | Show an access structure
49 |
50 | More:
51 | +html+ <PRE>
52 | Authors:
53 | marek
54 | +html+ </PRE><DL COMPACT>
55 | +html+ <DT>Online References:
56 | +html+ </UL></DL>
57 |
58 | ++++++++++++++++++++++++++++++++++++++*/
59 | char *AC_to_string(GList *leafptr)
60 | {
61 | char *result_buf;
62 | acc_st *a = leafptr->data;
63 |
64 | if( wr_malloc( (void **) &result_buf, 256) != UT_OK ) {
65 | /* do many bad things...*/
66 | return NULL;
67 | }
68 |
69 | if( a != NULL ) {
70 | sprintf(result_buf,
71 | "conn %d\tpass %d\tden %d\tqrs %d\tpub %d\tpriv %d\tbonus %d",
72 | a->connections,
73 | a->addrpasses,
74 | a->denials,
75 | a->queries,
76 | a->public_objects,
77 | a->private_objects,
78 | a->private_bonus
79 | );
80 | }
81 | else {
82 | strcpy(result_buf, "DATA MISSING\n");
83 | }
84 |
85 | return result_buf;
86 | } /* AC_to_string() */
87 |
88 | /* AC_acl_to_string() */
89 | /*++++++++++++++++++++++++++++++++++++++
90 | Show an access control list structure
91 |
92 | More:
93 | +html+ <PRE>
94 | Authors:
95 | marek
96 | +html+ </PRE><DL COMPACT>
97 | +html+ <DT>Online References:
98 | +html+ </UL></DL>
99 |
100 | ++++++++++++++++++++++++++++++++++++++*/
101 | char *AC_acl_to_string(GList *leafptr)
102 | {
103 | char *result_buf;
104 | acl_st *a = leafptr->data;
105 |
106 | if( wr_malloc( (void **) &result_buf, 256) != UT_OK ) {
107 | /* do many bad things...*/
108 | return NULL;
109 | }
110 |
111 | if( a != NULL ) {
112 | sprintf(result_buf,
113 | "maxbonus %d\tmaxdenials %d\tmaxpublic %d\tdeny %d\ttrustpass %d\t",
114 | a->maxbonus,
115 | a->maxdenials,
116 | a->maxpublic,
117 | a->deny,
118 | a->trustpass
119 | );
120 | }
121 | else {
122 | strcpy(result_buf, "DATA MISSING\n");
123 | }
124 |
125 | return result_buf;
126 | } /* AC_acl_to_string() */
127 |
128 | /* AC_fetch_acc() */
129 | /*++++++++++++++++++++++++++++++++++++++
130 | Find the runtime accounting record for this IP,
131 | store a copy of it in acc_store.
132 |
133 | More:
134 | +html+ <PRE>
135 | Authors:
136 | marek
137 | +html+ </PRE><DL COMPACT>
138 | +html+ <DT>Online References:
139 | +html+ </UL></DL>
140 |
141 | ++++++++++++++++++++++++++++++++++++++*/
142 | er_ret_t AC_fetch_acc( ip_addr_t *addr, acc_st *acc_store)
143 | {
144 | GList *datlist=NULL;
145 | rx_datref_t *datref;
146 | er_ret_t ret_err;
147 | ip_prefix_t prefix;
148 |
149 | prefix.ip = *addr;
150 | prefix.bits = IP_sizebits(addr->space);
151 | TH_acquire_read_lock( &(act_runtime->rwlock) );
152 |
153 | if( (ret_err = RX_bin_search(RX_SRCH_EXACT, 0, 0, act_runtime,
154 | &prefix, &datlist, RX_ANS_ALL)) == RX_OK ) {
155 | switch( g_list_length(datlist) ) {
156 | case 0:
157 | memset(acc_store, 0, sizeof(acc_st));
158 | break;
159 | case 1:
160 | datref = (rx_datref_t *) g_list_nth_data(datlist,0);
161 | memcpy(acc_store, (acc_st *) datref->leafptr, sizeof(acc_st));
162 | break;
163 | default: die;
164 | }
165 | }
166 |
167 | TH_release_read_lock( &(act_runtime->rwlock) );
168 |
169 | return -1;
170 | }/* AC_fetch_acc() */
171 |
172 | /* AC_check_acl() */
173 | /*++++++++++++++++++++++++++++++++++++++
174 |
175 | AC_check_acl:
176 |
177 | search for this ip or less specific record in the access control tree
178 |
179 | if( bonus in combined runtime+connection accountings > max_bonus in acl)
180 | set denial in the acl for this ip (create if needed)
181 | if( combined denialcounter > max_denials in acl)
182 | set the permanent ban in acl; save in SQL too
183 | calculate credit if pointer provided
184 | save the access record (ip if created or found/prefix otherwise)
185 | at *acl_store if provided
186 |
187 | any of the args except address can be NULL
188 |
189 | More:
190 | +html+ <PRE>
191 | Authors:
192 | marek
193 | +html+ </PRE><DL COMPACT>
194 | +html+ <DT>Online References:
195 | +html+ </UL></DL>
196 |
197 | ++++++++++++++++++++++++++++++++++++++*/
198 | er_ret_t AC_check_acl( ip_addr_t *addr,
199 | acc_st *credit_acc,
200 | acl_st *acl_store
201 | )
202 | {
203 | GList *datlist=NULL;
204 | ip_prefix_t prefix;
205 | er_ret_t ret_err;
206 | acl_st *acl_record;
207 | rx_datref_t *datref;
208 | acc_st run_acc;
209 |
210 | AC_fetch_acc( addr, &run_acc );
211 |
212 | prefix.ip = *addr;
213 | prefix.bits = IP_sizebits(addr->space);
214 |
215 | /* lock the tree accordingly */
216 | TH_acquire_read_lock( &(act_acl->rwlock) );
217 | /* find a record */
218 | if( (ret_err = RX_bin_search(RX_SRCH_EXLESS, 0, 0, act_acl,
219 | &prefix, &datlist, RX_ANS_ALL)
220 | ) != RX_OK || g_list_length(datlist) == 0 ) {
221 | /* acl tree is not configured at all ! There always must be a
222 | catch-all record with defaults */
223 | die;
224 | }
225 | datref = (rx_datref_t *)g_list_nth_data(datlist,0);
226 | acl_record = (acl_st *) datref->leafptr;
227 |
228 | /* calculate the credit if pointer given */
229 | if( credit_acc ) {
230 | memset( credit_acc, 0, sizeof(acc_st));
231 | credit_acc->public_objects = /* -1 == unlimited */
232 | acl_record->maxpublic - run_acc.public_objects;
233 | credit_acc->private_objects =
234 | acl_record->maxbonus - run_acc.private_bonus;
235 | }
236 |
237 | /* copy the acl record if asked for it*/
238 | if( acl_store ) {
239 | *acl_store = *acl_record;
240 | }
241 |
242 | /* XXX checking tree consistency */
243 | {
244 | rx_treecheck_t errorfound;
245 | er_ret_t err;
246 | if( (err=RX_treecheck(act_acl, 1, &errorfound)) != RX_OK ) {
247 | fprintf(stderr, "Nope! %d returned \n", err);
248 | die;
249 | }
250 | }
251 |
252 | /* release lock */
253 | TH_release_read_lock( &(act_acl->rwlock) );
254 |
255 | g_list_foreach(datlist, rx_free_list_element, NULL);
256 | g_list_free(datlist);
257 | /*
258 | if( ret_err == RX_OK ) {
259 | ret_err = AC_OK;
260 | }
261 | */
262 | return ret_err;
263 | }
264 |
265 | void AC_acc_addup(acc_st *a, acc_st *b, int minus)
266 | {
267 | int mul = minus ? -1 : 1;
268 |
269 | /* add all counters from b to those in a */
270 | a->connections += mul * b->connections;
271 | a->addrpasses += mul * b->addrpasses;
272 |
273 | a->denials += mul * b->denials;
274 | a->queries += mul * b->queries;
275 | a->public_objects += mul * b->public_objects;
276 | a->private_objects += mul * b->private_objects;
277 | a->private_bonus += mul * b->private_bonus;
278 | }
279 |
280 |
281 | er_ret_t AC_commit(ip_addr_t *addr, acc_st *acc_conn, acl_st *acl_copy) {
282 | /* for all accounting trees: XXX runtime only for the moment
283 | lock tree (no mercy :-)
284 | find or create entries,
285 | increase accounting values by the values from connection acc
286 | reset the connection acc
287 | unlock accounting trees
288 |
289 | THEN
290 |
291 | write lock acl
292 | check maxbonus
293 | set denial
294 | unlock acl
295 | */
296 | GList *datlist=NULL;
297 | acc_st *recacc;
298 | er_ret_t ret_err;
299 | ip_prefix_t prefix;
300 | int permanent_ban=0;
301 |
302 | prefix.ip = *addr;
303 | prefix.bits = IP_sizebits(addr->space);
304 |
305 | acc_conn->private_bonus = acc_conn->private_objects;
306 |
307 | TH_acquire_write_lock( &(act_runtime->rwlock) );
308 |
309 | if( (ret_err = RX_bin_search(RX_SRCH_EXACT, 0, 0, act_runtime,
310 | &prefix, &datlist, RX_ANS_ALL)) == RX_OK ) {
311 | switch( g_list_length(datlist) ) {
312 | case 0:
313 | /* need to create a new accounting record */
314 | if( (ret_err = wr_malloc( (void **)& recacc, sizeof(acc_st))) == UT_OK ) {
315 | /* counters = connection counters */
316 | memcpy( recacc, acc_conn, sizeof(acc_st));
317 |
318 | /* attach. The recacc is to be treated as a dataleaf
319 | (must use lower levels than RX_asc_*)
320 | */
321 | ret_err = RX_bin_node( RX_OPER_CRE, &prefix,
322 | act_runtime, (rx_dataleaf_t *)recacc );
323 | }
324 | break;
325 | case 1:
326 | {
327 | rx_datref_t *datref = (rx_datref_t *) g_list_nth_data( datlist,0 );
328 |
329 | /* OK, there is a record already, add to it */
330 | recacc = (acc_st *) datref->leafptr;
331 | AC_acc_addup(recacc, acc_conn, ACC_PLUS);
332 | }
333 | break;
334 | default: die; /* there shouldn't be more than 1 entry per IP */
335 | }
336 | }
337 | /* free search results */
338 | g_list_foreach(datlist, rx_free_list_element, NULL);
339 | g_list_free(datlist);
340 | datlist=NULL;
341 |
342 | /* set permanent ban if deserved and if not set yet */
343 | if( recacc->denials > acl_copy->maxdenials && acl_copy->deny == 0) {
344 | permanent_ban = 1;
345 | }
346 |
347 | /* XXX checking tree consistency */
348 | {
349 | rx_treecheck_t errorfound;
350 | er_ret_t err;
351 | if( (err=RX_treecheck(act_runtime, 1, &errorfound)) != RX_OK ) {
352 | fprintf(stderr, "Nope! %d returned \n", err);
353 | die;
354 | }
355 | }
356 |
357 | TH_release_write_lock( &(act_runtime->rwlock) );
358 | memset(acc_conn,0, sizeof(acc_st));
359 |
360 | /*++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++*/
361 | /* now check the denials and set the permanent ban */
362 |
363 | if( permanent_ban ) {
364 | acl_st *newacl;
365 |
366 | TH_acquire_write_lock( &(act_acl->rwlock) );
367 | /* find a record in the tree */
368 | dieif( (ret_err = RX_bin_search(RX_SRCH_EXACT, 0, 0, act_acl,
369 | &prefix, &datlist, RX_ANS_ALL)
370 | ) != RX_OK );
371 |
372 | switch( g_list_length(datlist)) {
373 | case 0:
374 | dieif( wr_calloc((void **)&newacl, 1, sizeof(acl_st)) != UT_OK );
375 |
376 | /* make the new one inherit all parameters after the old one
377 | - except the denial :-) */
378 | *newacl = *acl_copy;
379 | newacl->deny = 1;
380 |
381 | /* link in */
382 | RX_bin_node(RX_OPER_CRE, &prefix, act_acl, (rx_dataleaf_t *)newacl);
383 | break;
384 | case 1:
385 | {
386 | /* Uh-oh, the guy is already known ! (or special, in any case) */
387 | rx_datref_t *datref = (rx_datref_t *)g_list_nth_data(datlist,0);
388 | newacl = (acl_st *) datref->leafptr;
389 |
390 | newacl->deny = 1;
391 | }
392 | break;
393 | default:
394 | die;
395 | }
396 |
397 | /* insert/replace a record in the database */
398 | {
399 |
400 | SQ_connection_t *sql_connection = NULL;
401 | SQ_result_set_t *result;
402 | SQ_row_t *row;
403 | char *fulltext;
404 | char newcomment[256];
405 | char *oldcomment;
406 | char *query;
407 | char querybuf[256];
408 |
409 | sprintf(newcomment,"Automatic permanent ban set at %ld", time(NULL));
410 |
411 | sql_connection = SQ_get_connection(CO_get_host(),
412 | CO_get_database_port(),
413 | "RIPADMIN",
414 | CO_get_user(),
415 | CO_get_password() );
416 |
417 | /* get the old entry, extend it */
418 | sprintf(querybuf, "SELECT comment FROM acl WHERE "
419 | "prefix = %u AND prefix_length = %d",
420 | prefix.ip.words[0],
421 | prefix.bits);
422 | result = SQ_execute_query(SQ_STORE, sql_connection, querybuf);
423 | if( SQ_num_rows(result) == 1 ) {
424 | assert( (row = SQ_row_next(result)) != NULL);
425 | oldcomment = SQ_get_column_string(result, row, 0);
426 | }
427 | else {
428 | oldcomment = "";
429 | }
430 |
431 | dieif( wr_malloc((void **)&fulltext,
432 | strlen(oldcomment) + strlen(newcomment) + 2)
433 | != UT_OK );
434 |
435 | sprintf(fulltext,"%s%s%s", oldcomment,
436 | strlen(oldcomment) > 0 ? "\n" : "",
437 | newcomment);
438 |
439 | SQ_free_result(result);
440 |
441 |
442 | /* must hold the thing below (replace..blah blah blah) + fulltext */
443 | dieif( wr_malloc((void **)&query, strlen(fulltext) + 256) != UT_OK );
444 |
445 | /* compose new entry and insert it */
446 | sprintf(query, "REPLACE INTO acl VALUES(%u, %d, %d, %d, %d, %d, %d,"
447 | "\"%s\")",
448 | prefix.ip.words[0],
449 | prefix.bits,
450 | newacl->maxbonus,
451 | newacl->maxpublic,
452 | newacl->maxdenials,
453 | newacl->deny,
454 | newacl->trustpass,
455 | fulltext);
456 |
457 | SQ_execute_query(SQ_NOSTORE, sql_connection, query);
458 | SQ_close_connection(sql_connection);
459 |
460 | wr_free(query);
461 | wr_free(fulltext);
462 | }
463 |
464 | TH_release_write_lock( &(act_acl->rwlock) );
465 | }
466 |
467 | return ret_err;
468 | }
469 |
470 | er_ret_t AC_decay_hook(rx_node_t *node, int level, int nodecounter, void *con) {
471 | acc_st *a = node->leaves_ptr->data;
472 |
473 | a->private_bonus *= 0.95;
474 |
475 | return RX_OK;
476 | } /* AC_decay_hook() */
477 |
478 | er_ret_t AC_decay(void) {
479 | er_ret_t ret_err;
480 |
481 | /* XXX
482 | This should be run as a detatched thread.
483 |
484 | Yes the while(1) is crappy b/c there's no way of stopping it,
485 | but it's Friday night & everyone has either gone off for
486 | Christmas break or is down at the pub so it's staying as a while(1)!
487 | And I'm not sure what effect the sleep() will have on the thread.
488 | */
489 | while(1) {
490 |
491 | TH_acquire_write_lock( &(act_runtime->rwlock) );
492 |
493 | if( act_runtime->top_ptr != NULL ) {
494 | rx_walk_tree(act_runtime->top_ptr, AC_decay_hook,
495 | RX_WALK_SKPGLU, /* skip glue nodes */
496 | 255, 0, 0, NULL, &ret_err);
497 | }
498 |
499 | /* it should also be as smart as to delete nodes that have reached
500 | zero, otherwise the whole of memory will be filled.
501 | Next release :-)
502 | */
503 |
504 | TH_release_write_lock( &(act_runtime->rwlock) );
505 |
506 | printf("AC: decaying access tree. (Every %d seconds)\n", AC_DECAY_TIME);
507 |
508 | sleep(AC_DECAY_TIME);
509 | }
510 |
511 | return ret_err;
512 | } /* AC_decay() */
513 |
514 | er_ret_t AC_acc_load(void)
515 | {
516 | SQ_connection_t *con=NULL;
517 | SQ_result_set_t *result;
518 | SQ_row_t *row;
519 | er_ret_t ret_err = RX_OK;
520 |
521 | if( (con = SQ_get_connection(CO_get_host(), CO_get_database_port(),
522 | "RIPADMIN", CO_get_user(), CO_get_password() )
523 | ) == NULL ) {
524 | fprintf(stderr, "ERROR %d: %s\n", SQ_errno(con), SQ_error(con));
525 | die;
526 | }
527 |
528 | if( (result = SQ_execute_query(SQ_STORE, con, "SELECT * FROM acl"))
529 | == NULL ) {
530 | fprintf(stderr, "ERROR %d: %s\n", SQ_errno(con), SQ_error(con));
531 | die;
532 | }
533 |
534 | TH_acquire_write_lock( &(act_acl->rwlock) );
535 |
536 | while ( (row = SQ_row_next(result)) != NULL && ret_err == RX_OK) {
537 | ip_prefix_t mypref;
538 | acl_st *newacl;
539 | char *col[7];
540 | unsigned myint;
541 | int i;
542 |
543 | memset(&mypref, 0, sizeof(ip_prefix_t));
544 | mypref.ip.space = IP_V4;
545 |
546 | if( (ret_err = wr_malloc( (void **)& newacl, sizeof(acl_st))
547 | ) == UT_OK ) {
548 |
549 | for(i=0; i<7; i++) {
550 | if ( (col[i] = SQ_get_column_string(result, row, i)) == NULL) {
551 | die;
552 | }
553 | }
554 |
555 | /* prefix ip */
556 | if( sscanf(col[0], "%u", &mypref.ip.words[0] ) < 1 ) { die; }
557 |
558 | /* prefix length */
559 | if( sscanf(col[1], "%u", &mypref.bits ) < 1 ) { die; }
560 |
561 | /* acl contents */
562 | if( sscanf(col[2], "%u", & (newacl->maxbonus) ) < 1 ) { die; }
563 | if( sscanf(col[3], "%u", & (newacl->maxpublic) ) < 1 ) { die; }
564 | if( sscanf(col[4], "%hd", & (newacl->maxdenials) ) < 1 ) { die; }
565 |
566 | /* these are chars therefore cannot read directly */
567 | if( sscanf(col[5], "%u", &myint ) < 1 ) { die; }
568 | else {
569 | newacl->deny = myint;
570 | }
571 | if( sscanf(col[6], "%u", &myint ) < 1 ) { die; }
572 | else {
573 | newacl->trustpass = myint;
574 | }
575 |
576 | /* free space */
577 | for(i=0; i<6; i++) free(col[i]);
578 |
579 |
580 | /* now add to the tree */
581 |
582 | ret_err = RX_bin_node( RX_OPER_CRE, &mypref,
583 | act_acl, (rx_dataleaf_t *) newacl );
584 | }
585 | } /* while row */
586 |
587 | TH_release_write_lock( &(act_acl->rwlock) );
588 |
589 | SQ_free_result(result);
590 | /* Close connection */
591 | SQ_close_connection(con);
592 |
593 | /* Start the decay thread. */
594 | TH_run2((void *)AC_decay);
595 |
596 | return ret_err;
597 | }
598 |
599 | er_ret_t AC_build(void)
600 | {
601 | /* create trees */
602 | if ( RX_tree_cre(0, IP_V4, RX_FAM_IP, "0.0.0.0/0", RX_MEM_RAMONLY,
603 | RX_SUB_NONE, &act_runtime) != RX_OK
604 | || RX_tree_cre(0, IP_V4, RX_FAM_IP, "0.0.0.0/0", RX_MEM_RAMONLY,
605 | RX_SUB_NONE, &act_hour) != RX_OK
606 | || RX_tree_cre(0, IP_V4, RX_FAM_IP, "0.0.0.0/0", RX_MEM_RAMONLY,
607 | RX_SUB_NONE, &act_minute) != RX_OK
608 | || RX_tree_cre(0, IP_V4, RX_FAM_IP, "0.0.0.0/0", RX_MEM_RAMONLY,
609 | RX_SUB_NONE, &act_acl) != RX_OK
610 | )
611 | die;
612 | }
613 |
614 | er_ret_t AC_rxwalkhook_print(rx_node_t *node,
615 | int level, int nodecounter,
616 | void *con)
617 | {
618 | char adstr[IP_ADDRSTR_MAX];
619 | char line[1024];
620 | char *dat;
621 |
622 |
623 | if( IP_addr_b2a(&(node->prefix.ip), adstr, IP_ADDRSTR_MAX) != IP_OK ) {
624 | die; /* program error. */
625 | }
626 |
627 | sprintf(line, "%-20s %s\n", adstr,
628 | dat=AC_to_string( node->leaves_ptr ));
629 | wr_free(dat);
630 |
631 | SK_cd_puts((sk_conn_st *)con, line);
632 | return RX_OK;
633 | }
634 |
635 | er_ret_t AC_rxwalkhook_print_acl(rx_node_t *node,
636 | int level, int nodecounter,
637 | void *con)
638 | {
639 | char prefstr[IP_PREFSTR_MAX];
640 | char line[1024];
641 | char *dat;
642 |
643 |
644 | if( IP_pref_b2a(&(node->prefix), prefstr, IP_PREFSTR_MAX) != IP_OK ) {
645 | die; /* program error. */
646 | }
647 |
648 | sprintf(line, "%-20s %s\n", prefstr,
649 | dat=AC_acl_to_string( node->leaves_ptr ));
650 | wr_free(dat);
651 |
652 | SK_cd_puts((sk_conn_st *)con, line);
653 | return RX_OK;
654 | }
655 |