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 |