1 | /*************************************** 2 | $Revision: 1.32 $ 3 | 4 | Access control module (ac) - access control for the query part 5 | 6 | Status: NOT REVIEWED, TESTED 7 | 8 | Design and implementation by: Marek Bukowy 9 | 10 | ******************/ /****************** 11 | Copyright (c) 1999 RIPE NCC 12 | 13 | All Rights Reserved 14 | 15 | Permission to use, copy, modify, and distribute this software and its 16 | documentation for any purpose and without fee is hereby granted, 17 | provided that the above copyright notice appear in all copies and that 18 | both that copyright notice and this permission notice appear in 19 | supporting documentation, and that the name of the author not be 20 | used in advertising or publicity pertaining to distribution of the 21 | software without specific, written prior permission. 22 | 23 | THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE, INCLUDING 24 | ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS; IN NO EVENT SHALL 25 | AUTHOR BE LIABLE FOR ANY SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR ANY 26 | DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN 27 | AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF 28 | OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. 29 | ***************************************/ 30 | #include <stdio.h> 31 | #include <glib.h> 32 | #include <string.h> 33 | 34 | #define AC_IMPL 35 | #include <rxroutines.h> 36 | #include <erroutines.h> 37 | #include <access_control.h> 38 | #include "sk.h" 39 | #include "mysql_driver.h" 40 | #include <constants.h> 41 | #include <server.h> 42 | 43 | #include "ca_configFns.h" 44 | #include "ca_dictSyms.h" 45 | #include "ca_macros.h" 46 | #include "ca_srcAttribs.h" 47 | 48 | #define AC_DECAY_TIME 600 /* YYY configurable constant */ 49 | 50 | /* formats for printing the access control list entries */ 51 | #define ACL_FORMAT "%10d %10d %10d %10d %10d" 52 | #define ACL_HEADER "%-20s %10s %10s %10s %10s %10s\n" 53 | 54 | /* formats for printing the accounting entries */ 55 | #define ACC_FORMAT "%4d %4d %4d %4d %7d %7d %7d %7d %7d" 56 | #define ACC_HEADER "%-20s %4s %4s %4s %4s %7s %7s %7s %7s %7s\n" 57 | 58 | 59 | /*++++++++++++++++++++++++++++++++++++++ 60 | AC_to_string_header: 61 | 62 | produce a header for the access stats printout 63 | 64 | returns an allocated string 65 | ++++++++++++++++++++++++++++++++++++++*/ 66 | char *AC_to_string_header(void) 67 | { 68 | char *result_buf; 69 | 70 | dieif( wr_malloc( (void **) &result_buf, 256) != UT_OK ); 71 | 72 | sprintf(result_buf, ACC_HEADER, 73 | "ip", "conn", "pass", "deny", "qry", "refs", "priv_o", "pub_o", "priv_b","pub_b"); 74 | 75 | return result_buf; 76 | } 77 | 78 | /*++++++++++++++++++++++++++++++++++++++ 79 | AC_to_string: 80 | 81 | Show an access structure 82 | 83 | returns an allocated string 84 | ++++++++++++++++++++++++++++++++++++++*/ 85 | char *AC_to_string(GList *leafptr) 86 | { 87 | char *result_buf; 88 | acc_st *a = leafptr->data; 89 | 90 | if( wr_malloc( (void **) &result_buf, 256) != UT_OK ) { 91 | /* XXX generic malloc handler pending ...*/ 92 | return NULL; 93 | } 94 | 95 | if( a == NULL ) { 96 | strcpy(result_buf, "DATA MISSING!"); 97 | } 98 | else { 99 | sprintf(result_buf, ACC_FORMAT, 100 | a->connections, 101 | a->addrpasses, 102 | a->denials, 103 | a->queries, 104 | a->referrals, 105 | a->private_objects, 106 | a->public_objects, 107 | a->private_bonus, 108 | a->public_bonus 109 | ); 110 | } 111 | 112 | return result_buf; 113 | } /* AC_to_string() */ 114 | 115 | 116 | /*++++++++++++++++++++++++++++++++++++++ 117 | AC_credit_to_string: 118 | 119 | Show credit used (for logging of queries) 120 | 121 | acc_st *a - the credit structure 122 | 123 | returns an allocated string 124 | ++++++++++++++++++++++++++++++++++++++*/ 125 | char *AC_credit_to_string(acc_st *a) 126 | { 127 | char *result_buf; 128 | 129 | if( wr_malloc( (void **) &result_buf, 64) != UT_OK ) { 130 | /* XXX generic malloc handler pending ...*/ 131 | return NULL; 132 | } 133 | 134 | dieif( a == NULL ); 135 | 136 | sprintf(result_buf,"%d+%d+%d%s", 137 | a->private_objects, 138 | a->public_objects, 139 | a->referrals, 140 | a->denials ? " **DENIED**" : "" 141 | ); 142 | 143 | return result_buf; 144 | } /* AC_credit_to_string */ 145 | 146 | 147 | /*+++++++++++++++++++++++++++++++++++++++ 148 | AC_acl_to_string_header: 149 | 150 | produce a header for the acl printout 151 | 152 | returns an allocated string 153 | ++++++++++++++++++++++++++++++++++++++*/ 154 | char * 155 | AC_acl_to_string_header(void) 156 | { 157 | char *result_buf; 158 | dieif( wr_malloc( (void **) &result_buf, 256) != UT_OK ); 159 | 160 | sprintf(result_buf, ACL_HEADER, "ip", 161 | /* the names must match those in AC_ar_acl, so just take 162 | them from there */ 163 | AC_ar_acl[AC_AR_MAXPRIVATE], 164 | AC_ar_acl[AC_AR_MAXPUBLIC], 165 | AC_ar_acl[AC_AR_MAXDENIALS], 166 | AC_ar_acl[AC_AR_DENY], 167 | AC_ar_acl[AC_AR_TRUSTPASS] 168 | ); 169 | 170 | 171 | return result_buf; 172 | } 173 | 174 | 175 | 176 | /*++++++++++++++++++++++++++++++++++++++ 177 | AC_acl_to_string: 178 | 179 | Show an access control list structure 180 | 181 | returns an allocated string 182 | ++++++++++++++++++++++++++++++++++++++*/ 183 | char *AC_acl_to_string(GList *leafptr) 184 | { 185 | char *result_buf; 186 | acl_st *a = leafptr->data; 187 | 188 | if( wr_malloc( (void **) &result_buf, 256) != UT_OK ) { 189 | /* XXX generic malloc handler pending ...*/ 190 | return NULL; 191 | } 192 | 193 | if( a != NULL ) { 194 | sprintf(result_buf, ACL_FORMAT, 195 | a->maxprivate, 196 | a->maxpublic, 197 | a->maxdenials, 198 | a->deny, 199 | a->trustpass 200 | ); 201 | } 202 | else { 203 | strcpy(result_buf, "DATA MISSING\n"); 204 | } 205 | 206 | return result_buf; 207 | } /* AC_acl_to_string() */ 208 | 209 | 210 | /*+++++++++++++++++++++++++++++++++++++++ 211 | AC_findexless_acl_l: 212 | 213 | find the exact or less specific match for the given prefix in the acl tree. 214 | 215 | ip_prefix_t *prefix - prefix to look for 216 | 217 | acl_st *store_acl - pointer to store the output 218 | 219 | returns error code from RX or OK 220 | 221 | MT-Note: assumes locked acl tree 222 | ++++++++++++++++++++++++++++++++++++++*/ 223 | er_ret_t 224 | AC_findexless_acl_l(ip_prefix_t *prefix, acl_st *store_acl) 225 | { 226 | GList *datlist=NULL; 227 | er_ret_t ret_err; 228 | rx_datref_t *datref; 229 | 230 | if( (ret_err = RX_bin_search(RX_SRCH_EXLESS, 0, 0, act_acl, 231 | prefix, &datlist, RX_ANS_ALL) 232 | ) != RX_OK || g_list_length(datlist) == 0 ) { 233 | /* acl tree is not configured at all ! There always must be a 234 | catch-all record with defaults */ 235 | die; 236 | } 237 | 238 | datref = (rx_datref_t *)g_list_nth_data(datlist,0); 239 | 240 | *store_acl = * ((acl_st *) datref->leafptr); 241 | 242 | wr_clear_list( &datlist ); 243 | 244 | /* XXX dbg checking tree consistency */ 245 | { 246 | rx_treecheck_t errorfound; 247 | er_ret_t err; 248 | if( (err=RX_treecheck(act_acl, 1, &errorfound)) != RX_OK ) { 249 | fprintf(stderr, "Nope! %d returned \n", err); 250 | die; 251 | } 252 | } 253 | 254 | return ret_err; 255 | } 256 | /* AC_findexless_acl_l */ 257 | 258 | 259 | /*+++++++++++++++++++++++++++++++++++++++ 260 | AC_findcreate_acl_l: 261 | 262 | find or create an entry for the given prefix in the acl tree. 263 | 264 | ip_prefix_t *prefix - prefix to look for 265 | 266 | acl_st **store_acl - pointer to store the ptr to the acl struct 267 | (initialised to the values of the parent entry 268 | if just created) 269 | 270 | returns error code from RX or OK 271 | 272 | MT-Note: assumes locked acl tree 273 | ++++++++++++++++++++++++++++++++++++++*/ 274 | er_ret_t 275 | AC_findcreate_acl_l(ip_prefix_t *prefix, acl_st **store_acl) 276 | { 277 | GList *datlist=NULL; 278 | er_ret_t ret_err; 279 | acl_st *newacl; 280 | acl_st acl_copy; 281 | 282 | if( NOERR(ret_err = RX_bin_search(RX_SRCH_EXACT, 0, 0, act_acl, 283 | prefix, &datlist, RX_ANS_ALL) 284 | )) { 285 | 286 | switch( g_list_length(datlist)) { 287 | case 0: 288 | dieif( wr_calloc((void **)&newacl, 1, sizeof(acl_st)) != UT_OK ); 289 | 290 | /* make the new one inherit all parameters after the old one */ 291 | 292 | AC_findexless_acl_l(prefix, &acl_copy); 293 | 294 | *newacl = acl_copy; 295 | 296 | /* link in */ 297 | rx_bin_node(RX_OPER_CRE, prefix, act_acl, (rx_dataleaf_t *)newacl); 298 | break; 299 | case 1: 300 | { 301 | /* Uh-oh, the guy is already known ! (or special, in any case) */ 302 | rx_datref_t *datref = (rx_datref_t *)g_list_nth_data(datlist,0); 303 | newacl = (acl_st *) datref->leafptr; 304 | } 305 | break; 306 | default: 307 | die; 308 | } 309 | } 310 | 311 | /* free search results */ 312 | wr_clear_list( &datlist ); 313 | 314 | /* store */ 315 | *store_acl = newacl; 316 | return ret_err; 317 | } 318 | /* AC_findcreate_acl_l */ 319 | 320 | 321 | /*+++++++++++++++++++++++++++++++++++++++ 322 | AC_findcreate_account_l: 323 | 324 | finds exact prefix in the accounting tree 325 | or creates area initialised to zeros + sets ptr to it. 326 | 327 | rx_tree_t *tree - the tree 328 | 329 | ip_prefix_t *prefix - prefix to look for 330 | 331 | acc_st **store_acl - pointer to store the ptr to the account struct 332 | 333 | returns error code from RX or OK 334 | 335 | MT-Note: assumes locked accounting tree 336 | ++++++++++++++++++++++++++++++++++++++*/ 337 | er_ret_t 338 | AC_findcreate_account_l(rx_tree_t *tree, ip_prefix_t *prefix, 339 | acc_st **acc_store) 340 | { 341 | GList *datlist=NULL; 342 | er_ret_t ret_err; 343 | acc_st *recacc; 344 | 345 | if( (ret_err = RX_bin_search(RX_SRCH_EXACT, 0, 0, tree, 346 | prefix, &datlist, RX_ANS_ALL)) == RX_OK ) { 347 | switch( g_list_length(datlist) ) { 348 | case 0: 349 | /* need to create a new accounting record */ 350 | if( (ret_err = wr_malloc( (void **)& recacc, sizeof(acc_st))) == UT_OK ) { 351 | /* counters = init to zeros */ 352 | memset( recacc, 0, sizeof(acc_st)); 353 | 354 | /* attach. The recacc is to be treated as a dataleaf 355 | (must use lower levels than RX_asc_*) 356 | */ 357 | ret_err = rx_bin_node( RX_OPER_CRE, prefix, 358 | act_runtime, (rx_dataleaf_t *)recacc ); 359 | } 360 | break; 361 | case 1: 362 | { 363 | rx_datref_t *datref = (rx_datref_t *) g_list_nth_data( datlist,0 ); 364 | 365 | /* OK, there is a record already */ 366 | recacc = (acc_st *) datref->leafptr; 367 | 368 | } 369 | break; 370 | default: die; /* there shouldn't be more than 1 entry per IP */ 371 | } 372 | } 373 | 374 | wr_clear_list( &datlist ); 375 | 376 | *acc_store = recacc; 377 | 378 | return ret_err; 379 | } 380 | 381 | 382 | /*++++++++++++++++++++++++++++++++++++++ 383 | AC_fetch_acc: 384 | 385 | Finds the runtime accounting record for this IP, 386 | stores a copy of it in acc_store. 387 | If not found, then it is created and initialised to zeros in findcreate() 388 | 389 | ip_addr_t *addr - address 390 | 391 | acc_st *acc_store - pointer to store the account struct 392 | 393 | MT-Note: locks/unlocks the accounting tree 394 | ++++++++++++++++++++++++++++++++++++++*/ 395 | er_ret_t AC_fetch_acc( ip_addr_t *addr, acc_st *acc_store) 396 | { 397 | er_ret_t ret_err; 398 | ip_prefix_t prefix; 399 | acc_st *ac_ptr; 400 | 401 | prefix.ip = *addr; 402 | prefix.bits = IP_sizebits(addr->space); 403 | 404 | TH_acquire_read_lock( &(act_runtime->rwlock) ); 405 | 406 | ret_err = AC_findcreate_account_l(act_runtime, &prefix, &ac_ptr); 407 | *acc_store = *ac_ptr; 408 | 409 | TH_release_read_lock( &(act_runtime->rwlock) ); 410 | 411 | return ret_err; 412 | }/* AC_fetch_acc() */ 413 | 414 | 415 | /*++++++++++++++++++++++++++++++++++++++ 416 | AC_check_acl: 417 | 418 | search for this ip or less specific record in the access control tree 419 | 420 | if( bonus in combined runtime+connection accountings > max_bonus in acl) 421 | set denial in the acl for this ip (create if needed) 422 | if( combined denialcounter > max_denials in acl) 423 | set the permanent ban in acl; save in SQL too 424 | calculate credit if pointer provided 425 | save the access record (ip if created or found/prefix otherwise) 426 | at *acl_store if provided 427 | 428 | ip_addr_t *addr - address 429 | 430 | acc_st *acc_store - pointer to store the *credit* account struct 431 | 432 | acl_st *acl_store - pointer to store the acl struct 433 | 434 | any of the args except address can be NULL 435 | 436 | returns error code from RX or OK 437 | 438 | MT-Note: locks/unlocks the accounting tree 439 | ++++++++++++++++++++++++++++++++++++++*/ 440 | er_ret_t AC_check_acl( ip_addr_t *addr, 441 | acc_st *credit_acc, 442 | acl_st *acl_store 443 | ) 444 | { 445 | ip_prefix_t prefix; 446 | er_ret_t ret_err = AC_OK; 447 | acl_st acl_record; 448 | acc_st run_acc; 449 | 450 | AC_fetch_acc( addr, &run_acc ); 451 | 452 | prefix.ip = *addr; 453 | prefix.bits = IP_sizebits(addr->space); 454 | 455 | /* lock the tree accordingly */ 456 | TH_acquire_read_lock( &(act_acl->rwlock) ); 457 | 458 | /* find an applicable record */ 459 | AC_findexless_acl_l(&prefix, &acl_record); 460 | 461 | /* calculate the credit if pointer given */ 462 | if( credit_acc ) { 463 | memset( credit_acc, 0, sizeof(acc_st)); 464 | 465 | /* credit = -1 if unlimited, otherwise credit = limit - bonus */ 466 | credit_acc->public_objects = 467 | ( acl_record.maxpublic == -1 ) 468 | ? -1 /* -1 == unlimited */ 469 | : (acl_record.maxpublic - run_acc.public_bonus); 470 | 471 | credit_acc->private_objects = 472 | ( acl_record.maxprivate == -1 ) 473 | ? -1 /* -1 == unlimited */ 474 | : (acl_record.maxprivate - run_acc.private_bonus); 475 | } 476 | 477 | /* copy the acl record if asked for it*/ 478 | if( acl_store ) { 479 | *acl_store = acl_record; 480 | } 481 | 482 | /* release lock */ 483 | TH_release_read_lock( &(act_acl->rwlock) ); 484 | 485 | 486 | return ret_err; 487 | } 488 | 489 | 490 | 491 | /*++++++++++++++++++++++++++++++++++++++ 492 | AC_acc_addup: 493 | 494 | Add/subtract the values from one accounting structure to another 495 | 496 | acc_st *a this one gets changed 497 | 498 | acc_st *b this one provides the values to change a 499 | 500 | int minus triggers subtraction if non-zero 501 | 502 | +++++++++++++++++++++++++++++++++++++++*/ 503 | void AC_acc_addup(acc_st *a, acc_st *b, int minus) 504 | { 505 | int mul = minus ? -1 : 1; 506 | 507 | /* add all counters from b to those in a */ 508 | a->connections += mul * b->connections; 509 | a->addrpasses += mul * b->addrpasses; 510 | 511 | a->denials += mul * b->denials; 512 | a->queries += mul * b->queries; 513 | a->referrals += mul * b->referrals; 514 | a->public_objects += mul * b->public_objects; 515 | a->private_objects += mul * b->private_objects; 516 | a->private_bonus += mul * b->private_bonus; 517 | a->public_bonus += mul * b->public_bonus; 518 | }/* AC_acc_addup */ 519 | 520 | /*++++++++++++++++++++++++++++++++++++++ 521 | AC_commit_credit: 522 | 523 | performs the commit on an accounting tree (locks them first) 524 | stores a copy of the accounting record at rec_store 525 | 526 | rx_tree_t *tree - the tree 527 | 528 | ip_prefix_t *prefix - prefix (usually a /32) 529 | 530 | acc_st *acc_conn - credit used 531 | 532 | acc_st *rec_store - pointer to store the account struct 533 | 534 | returns error code from AC_findcreate_account_l or OK 535 | 536 | MT-Note: locks/unlocks the accounting tree 537 | +++++++++++++++++++++++++++++++++++++++*/ 538 | er_ret_t 539 | AC_commit_credit(rx_tree_t *tree, ip_prefix_t *prefix, 540 | acc_st *acc_conn, acc_st *rec_store ) 541 | { 542 | acc_st *accountrec; 543 | er_ret_t ret_err; 544 | 545 | 546 | acc_conn->private_bonus = acc_conn->private_objects; 547 | acc_conn->public_bonus = acc_conn->public_objects; 548 | 549 | TH_acquire_write_lock( &(tree->rwlock) ); 550 | 551 | ret_err = AC_findcreate_account_l(act_runtime, prefix, &accountrec); 552 | 553 | if( NOERR(ret_err)) { 554 | AC_acc_addup(accountrec, acc_conn, ACC_PLUS); 555 | } 556 | 557 | TH_release_write_lock( &(tree->rwlock) ); 558 | 559 | *rec_store = *accountrec; 560 | 561 | return ret_err; 562 | }/* AC_commit_credit */ 563 | 564 | /*++++++++++++++++++++++++++++++++++++++ 565 | AC_dbopen_admin: 566 | 567 | opens the ADMIN database and returns a pointer to the connection structure 568 | (rationale: the opening process became a bit bloated and is done twice, 569 | so I put it into a separate function) 570 | ++++++++++++++++++++++++++++++++++++++*/ 571 | SQ_connection_t * 572 | AC_dbopen_admin(void) 573 | { 574 | SQ_connection_t *con=NULL; 575 | char *dbhost = ca_get_ripadminhost; 576 | char *dbname = ca_get_ripadmintable; 577 | char *dbuser = ca_get_ripadminuser; 578 | char *dbpass = ca_get_ripadminpassword; 579 | int dbport = ca_get_ripadminport; 580 | 581 | if( (con = SQ_get_connection(dbhost, dbport, dbname, dbuser, dbpass) 582 | ) == NULL ) { 583 | fprintf(stderr, "ERROR %d: %s\n", SQ_errno(con), SQ_error(con)); 584 | die; 585 | } 586 | 587 | free(dbhost); 588 | free(dbname); 589 | free(dbuser); 590 | free(dbpass); 591 | 592 | return con; 593 | } 594 | 595 | /*++++++++++++++++++++++++++++++++++++++ 596 | AC_acl_sql: 597 | 598 | updates/creates a record for the given prefix in the acl table of 599 | the RIPADMIN database. Adds a comment. 600 | 601 | ip_prefix_t *prefix - prefix 602 | 603 | acl_st *newacl - new values to store in the database 604 | 605 | char *newcomment - comment to be added (must not be NULL) 606 | 607 | placeholder: it may return an error code from SQ - as soon as sq 608 | implements common error scheme 609 | 610 | ++++++++++++++++++++++++++++++++++++++*/ 611 | er_ret_t 612 | AC_acl_sql(ip_prefix_t *prefix, acl_st *newacl, char *newcomment ) 613 | { 614 | SQ_connection_t *sql_connection = NULL; 615 | SQ_result_set_t *result; 616 | SQ_row_t *row; 617 | char *oldcomment; 618 | char *query; 619 | char querybuf[256]; 620 | 621 | sql_connection = AC_dbopen_admin(); 622 | 623 | /* get the old entry, extend it */ 624 | sprintf(querybuf, "SELECT comment FROM acl WHERE " 625 | "prefix = %u AND prefix_length = %d", 626 | prefix->ip.words[0], 627 | prefix->bits); 628 | dieif( SQ_execute_query(sql_connection, querybuf, &result) == -1 ); 629 | 630 | if( SQ_num_rows(result) == 1 ) { 631 | dieif( (row = SQ_row_next(result)) == NULL); 632 | oldcomment = SQ_get_column_string(result, row, 0); 633 | } 634 | else { 635 | oldcomment = ""; 636 | } 637 | 638 | SQ_free_result(result); 639 | 640 | /* must hold the thing below (REPLACE..blah blah blah) + text */ 641 | dieif( wr_malloc((void **)&query, 642 | strlen(oldcomment) + strlen(newcomment) + 256) != UT_OK ); 643 | 644 | /* compose new entry and insert it */ 645 | sprintf(query, "REPLACE INTO acl VALUES(%u, %d, %d, %d, %d, %d, %d," 646 | "\"%s%s%s\")", 647 | prefix->ip.words[0], 648 | prefix->bits, 649 | newacl->maxprivate, 650 | newacl->maxpublic, 651 | newacl->maxdenials, 652 | newacl->deny, 653 | newacl->trustpass, 654 | oldcomment, 655 | strlen(oldcomment) > 0 ? "\n" : "", 656 | newcomment 657 | ); 658 | 659 | SQ_execute_query(sql_connection, query, NULL); 660 | SQ_close_connection(sql_connection); 661 | 662 | wr_free(query); 663 | 664 | return AC_OK; 665 | 666 | }/* AC_acl_sql */ 667 | 668 | /*++++++++++++++++++++++++++++++++++++++ 669 | AC_ban_set: 670 | 671 | re/sets the permanent ban flag both in the acl tree in memory 672 | and the sql table. The "text" is appended to the comment 673 | in the sql record (the expected cases are 674 | - "automatic" in case the limit is exceeded and ban is set by s/w 675 | - "manual" in case it is (un)set from the config iface 676 | 677 | ip_prefix_t *prefix - prefix 678 | 679 | char *text - usually "automatic" or "manual" 680 | 681 | int denyflag - new value of the denyflag (ban) 682 | 683 | returns error code from AC_acl_sql or OK 684 | +++++++++++++++++++++++++++++++++++++++*/ 685 | er_ret_t 686 | AC_ban_set(ip_prefix_t *prefix, char *text, int denyflag) 687 | { 688 | acl_st *treeacl; 689 | char newcomment[256]; 690 | er_ret_t ret_err; 691 | time_t clock; 692 | char timebuf[26]; 693 | char prefstr[IP_PREFSTR_MAX]; 694 | 695 | time(&clock); 696 | ctime_r(&clock, timebuf); 697 | 698 | sprintf(newcomment,"%s permanent ban set to %d at %s", text, 699 | denyflag, timebuf); 700 | 701 | if( IP_pref_b2a(prefix, prefstr, IP_PREFSTR_MAX) != IP_OK ) { 702 | die; /* program error - this is already converted so must be OK */ 703 | } 704 | 705 | ER_inf_va( FAC_AC, ASP_AC_I_PERMBAN, 706 | "%s permanent ban set to %d for %s", text, denyflag, prefstr ); 707 | 708 | TH_acquire_write_lock( &(act_acl->rwlock) ); 709 | 710 | /* find a record in the tree */ 711 | if( NOERR(ret_err = AC_findcreate_acl_l( prefix, &treeacl )) ) { 712 | treeacl->deny = denyflag; 713 | ret_err = AC_acl_sql( prefix, treeacl, newcomment ); 714 | } 715 | TH_release_write_lock( &(act_acl->rwlock) ); 716 | 717 | return ret_err; 718 | }/* AC_ban_set */ 719 | 720 | 721 | /*++++++++++++++++++++++++++++++++++++++ 722 | AC_asc_ban_set: 723 | 724 | sets ban on text address/range. Parses the text address/range/prefix 725 | and then calls AC_ban_set on that prefix. 726 | 727 | Precondition: if the key is a range, it must decompose into one prefix 728 | 729 | returns error code from IP_smart_conv, AC_ban_set or 730 | AC_INVARG if range composed 731 | +++++++++++++++++++++++++++++++++++++++*/ 732 | er_ret_t 733 | AC_asc_ban_set(char *addrstr, char *text, int denyflag) 734 | { 735 | er_ret_t ret_err; 736 | GList *preflist = NULL; 737 | ip_keytype_t key_type; 738 | 739 | if( (ret_err = IP_smart_conv(addrstr, 0, 0, 740 | &preflist, IP_PLAIN, &key_type)) != IP_OK ) { 741 | return ret_err; 742 | } 743 | 744 | /* allow only one prefix */ 745 | /* The argument can be even a range, but must decompose into one prefix */ 746 | if( NOERR(ret_err) && g_list_length( preflist ) != 1 ) { 747 | ret_err = AC_INVARG; 748 | } 749 | 750 | if( NOERR(ret_err) ) { 751 | ret_err = AC_ban_set( (g_list_first(preflist)->data), text, denyflag); 752 | } 753 | 754 | wr_clear_list( &preflist ); 755 | 756 | return ret_err; 757 | }/* AC_asc_ban_set */ 758 | 759 | /*++++++++++++++++++++++++++++++++++++++ 760 | AC_asc_all_set: 761 | 762 | take ascii prefix and find/create a new entry, inheriting all parameters 763 | and then set them according to the array of args. 764 | 765 | +*/ 766 | er_ret_t 767 | AC_asc_all_set(ip_prefix_t *prefix, char *comment, char * array[]) 768 | { 769 | er_ret_t ret_err; 770 | acl_st *treeacl; 771 | int i; 772 | 773 | TH_acquire_write_lock( &(act_acl->rwlock) ); 774 | 775 | /* find/create a record in the tree */ 776 | if( NOERR(ret_err = AC_findcreate_acl_l( prefix, &treeacl )) ) { 777 | 778 | /* update it from the array */ 779 | for(i=0; i<AC_AR_SIZE; i++) { 780 | if(array[i] != NULL) { /* set only those that have been specified */ 781 | int val,k; 782 | 783 | if( (k=sscanf( array[i], "%d", &val)) < 1 ) { 784 | ret_err = AC_INVARG; 785 | break; /* quit the for */ 786 | } 787 | 788 | /* otherwise, the value makes sense. Put it in the structure. */ 789 | switch(i) { 790 | case AC_AR_MAXPRIVATE: treeacl->maxprivate = val; break; 791 | case AC_AR_MAXPUBLIC: treeacl->maxpublic = val; break; 792 | case AC_AR_MAXDENIALS: treeacl->maxdenials = val; break; 793 | case AC_AR_DENY: treeacl->deny = val; break; 794 | case AC_AR_TRUSTPASS: treeacl->trustpass = val; break; 795 | } /* switch */ 796 | } /* if array[i] not null */ 797 | } /* for each array element */ 798 | 799 | if( NOERR(ret_err) ) { /* protect against AC_INVARG */ 800 | ret_err = AC_acl_sql( prefix, treeacl, comment ); 801 | } 802 | } /* if find/create OK */ 803 | 804 | TH_release_write_lock( &(act_acl->rwlock) ); 805 | 806 | return ret_err; 807 | } 808 | 809 | 810 | /*++++++++++++++++++++++++++++++++++++++ 811 | AC_asc_acl_command_set: 812 | 813 | parse a command and set acl options for an entry. 814 | command syntax: 815 | 816 | <prefix> option=value,option=value,option=value... 817 | 818 | where <option> is defined in AC_ar_acl[] array, value is an integer 819 | 820 | char *command text of the command. 821 | Syntax: ip[/prefixlength] column=value,column=value... 822 | Column names as in acl display. Unset columns are inherited. 823 | 824 | char *comment text to be added to the acl record's comment column. 825 | ++++++++++++++++++++++++++++++++++++++*/ 826 | 827 | er_ret_t 828 | AC_asc_acl_command_set( char *command, char *comment ) 829 | { 830 | ip_prefix_t *prefix; 831 | char *eop, *eoc, *value; 832 | char *array[AC_AR_SIZE]; 833 | er_ret_t ret_err = AC_OK; 834 | GList *preflist = NULL; 835 | ip_keytype_t key_type; 836 | 837 | char *copy = strdup(command); 838 | char *addrstr = copy; 839 | eoc = strchr(copy, '\0'); /* points to the end of it */ 840 | 841 | memset(array, 0 ,sizeof(array)); 842 | 843 | /* first comes the prefix. Find the space after it 844 | and break the string there. 845 | */ 846 | if( (eop = strchr(copy,' ')) == NULL) { 847 | ret_err = AC_INVARG; 848 | } 849 | 850 | if( NOERR(ret_err) ) { 851 | *eop++ = 0; 852 | 853 | /* now eop points to the rest of the string (if any). Take options. 854 | */ 855 | while( eop != eoc && ret_err == AC_OK) { 856 | char *sp; 857 | 858 | /* give getsubopt chunks with no spaces */ 859 | if( (sp = strchr(eop, ' ')) != NULL ) { 860 | *sp=0; 861 | } 862 | 863 | while( *eop != '\0' ) { 864 | int k = getsubopt(&eop, AC_ar_acl, &value); 865 | if( k < 0 ) { 866 | ret_err = AC_INVARG; 867 | break; 868 | } 869 | 870 | array[k] = value; 871 | } 872 | 873 | if( eop != eoc ) { /*getsubopt finished but did not consume all string*/ 874 | eop ++; /* must have been a space. advance one */ 875 | } 876 | } 877 | } 878 | 879 | /* convert the prefix */ 880 | if( NOERR(ret_err) ) { 881 | ret_err = IP_smart_conv(addrstr, 0, 0, &preflist, IP_PLAIN, &key_type); 882 | 883 | /* allow only one prefix */ 884 | /* The argument can be even a range, but must decompose into one prefix */ 885 | if( NOERR(ret_err) && g_list_length( preflist ) == 1 ) { 886 | prefix = (g_list_first(preflist)->data); 887 | } 888 | else { 889 | ret_err = AC_INVARG; 890 | } 891 | } 892 | 893 | /* perform changes */ 894 | if( NOERR(ret_err) ) { 895 | ret_err = AC_asc_all_set(prefix, comment, array); 896 | } 897 | 898 | wr_clear_list( &preflist ); 899 | free(copy); 900 | 901 | return ret_err; 902 | } 903 | 904 | /*++++++++++++++++++++++++++++++++++++++ 905 | AC_asc_set_nodeny: 906 | 907 | reset the deny counter in the access tree to 0 (after reenabling). 908 | 909 | Operates on the runtime access tree. 910 | 911 | char *ip text IP (ip only, not prefix or range). 912 | +++++++++++++++++++++++++++++++++++++++*/ 913 | er_ret_t AC_asc_set_nodeny(char *ip) 914 | { 915 | ip_prefix_t prefix; 916 | er_ret_t ret_err; 917 | acc_st *ac_ptr; 918 | 919 | ret_err = IP_addr_e2b( &(prefix.ip), ip ); 920 | prefix.bits = IP_sizebits(prefix.ip.space); 921 | 922 | if( NOERR(ret_err)) { 923 | TH_acquire_write_lock( &(act_runtime->rwlock) ); 924 | 925 | ret_err = AC_findcreate_account_l(act_runtime, &prefix, &ac_ptr); 926 | if( NOERR(ret_err)) { 927 | ac_ptr->denials = 0; 928 | } 929 | 930 | TH_release_write_lock( &(act_runtime->rwlock) ); 931 | } 932 | 933 | return ret_err; 934 | } 935 | 936 | /*++++++++++++++++++++++++++++++++++++++ 937 | AC_commit: 938 | 939 | commits the credit into all accounting trees, (XXX: only one at the moment) 940 | checks the limits and sets automatic ban if limit exceeded. 941 | 942 | ip_addr_t *addr - user's address 943 | acc_st *acc_conn - credit used 944 | acl_st *acl_copy - pointer to store a copy of the acl 945 | 946 | returns error code from AC_commit_credit or AC_ban_set or OK. 947 | 948 | outline: 949 | lock runtime + minute accounting trees 950 | ----------------------- XXX runtime only for the moment 951 | find or create entries, 952 | increase accounting values by the values from passed acc 953 | check values against acl, see if permanent ban applies 954 | 955 | reset the connection acc 956 | unlock accounting trees 957 | 958 | if permanent ban - set it! : 959 | lock acl 960 | find/create IP in memory 961 | set ban 962 | find/create IP in SQL 963 | copy old values (if any), set ban, append comment 964 | unlock acl 965 | 966 | +++++++++++++++++++++++++++++++++++++++*/ 967 | er_ret_t AC_commit(ip_addr_t *addr, acc_st *acc_conn, acl_st *acl_copy) { 968 | acc_st account; 969 | er_ret_t ret_err; 970 | ip_prefix_t prefix; 971 | 972 | prefix.ip = *addr; 973 | prefix.bits = IP_sizebits(addr->space); 974 | 975 | ret_err = AC_commit_credit(act_runtime, &prefix, acc_conn, &account); 976 | /* XXX add more trees here */ 977 | 978 | memset(acc_conn,0, sizeof(acc_st)); 979 | 980 | /* set permanent ban if deserved and if not set yet */ 981 | if( account.denials > acl_copy->maxdenials 982 | && acl_copy->deny == 0 983 | && NOERR(ret_err) ) { 984 | 985 | ret_err = AC_ban_set(&prefix, "Automatic", 1); 986 | } 987 | 988 | return ret_err; 989 | } /* AC_commit */ 990 | 991 | 992 | /*++++++++++++++++++++++++++++++++++++++ 993 | AC_decay_hook: 994 | 995 | action performed on a single account node during decay (diminishing the 996 | bonus). Conforms to rx_walk_tree interface, therefore some of the 997 | arguments do not apply and are not used. 998 | 999 | rx_node_t *node - pointer to the node of the radix tree 1000 | 1001 | int level - not used 1002 | 1003 | int nodecounter - not used 1004 | 1005 | void *con - in real life: (float *) - points to the decay factor. 1006 | 1007 | returns always OK 1008 | +++++++++++++++++++++++++++++++++++++++*/ 1009 | er_ret_t AC_decay_hook(rx_node_t *node, int level, 1010 | int nodecounter, void *con) 1011 | { 1012 | acc_st *a = node->leaves_ptr->data; 1013 | float factor = *( float *) con; 1014 | 1015 | a->private_bonus *= factor; 1016 | a->public_bonus *= factor; 1017 | 1018 | return RX_OK; 1019 | } /* AC_decay_hook() */ 1020 | 1021 | 1022 | 1023 | /*++++++++++++++++++++++++++++++++++++++ 1024 | AC_decay: 1025 | 1026 | Every AC_DECAY_TIME goes through the accounting tree(s) and decays the 1027 | bonus values. 1028 | 1029 | returns always OK 1030 | 1031 | MT-Note This should be run as a detached thread. 1032 | +++++++++++++++++++++++++++++++++++++++*/ 1033 | er_ret_t AC_decay(void) { 1034 | er_ret_t ret_err = AC_OK; 1035 | float decay_factor; 1036 | 1037 | /* the decay factor of 1038 | f(t) = exp(-a*t) 1039 | a = -ln(0.5) / t 1040 | so for T being the half-life period and v being the sampling interval 1041 | used as the unit of time 1042 | a/v = -ln(0.5) / T 1043 | hence a = -ln(0.5) * v / T; 1044 | */ 1045 | 1046 | 1047 | 1048 | /* XXX uses CO_get_do_server() to see when to exit the program. 1049 | this will change */ 1050 | while(CO_get_do_server()) { 1051 | 1052 | /* those values can be changed in runtime - so recalculate 1053 | the decay factor vefore each pass */ 1054 | dieif( ca_get_ac_decay_halflife == 0 ); 1055 | decay_factor = .693147180559945 * ca_get_ac_decay_interval / ca_get_ac_decay_halflife ; 1056 | 1057 | TH_acquire_write_lock( &(act_runtime->rwlock) ); 1058 | 1059 | if( act_runtime->top_ptr != NULL ) { 1060 | rx_walk_tree(act_runtime->top_ptr, AC_decay_hook, 1061 | RX_WALK_SKPGLU, /* skip glue nodes */ 1062 | 255, 0, 0, &decay_factor, &ret_err); 1063 | } 1064 | 1065 | /* it should also be as smart as to delete nodes that have reached 1066 | zero, otherwise the whole of memory will be filled. 1067 | Next release :-) 1068 | */ 1069 | 1070 | TH_release_write_lock( &(act_runtime->rwlock) ); 1071 | 1072 | ER_dbg_va( FAC_AC, ASP_AC_DECAY, 1073 | "AC: decaying access tree. (Every %d seconds)\n", AC_DECAY_TIME); /* YYY configurable constant: text */ 1074 | 1075 | SV_sleep(AC_DECAY_TIME); 1076 | } 1077 | 1078 | return ret_err; 1079 | } /* AC_decay() */ 1080 | 1081 | 1082 | /*++++++++++++++++++++++++++++++++++++++ 1083 | AC_acc_load: 1084 | 1085 | loads the acl access tree from the acl table of the RIPADMIN database. 1086 | (takes port/host/user/password from the config module). 1087 | 1088 | bails out if encounters problems with the database (logs to stderr). 1089 | 1090 | returns error code from RX_bin_node or wr_malloc. 1091 | ++++++++++++++++++++++++++++++++++++++*/ 1092 | er_ret_t AC_acc_load(void) 1093 | { 1094 | SQ_connection_t *con=NULL; 1095 | SQ_result_set_t *result; 1096 | SQ_row_t *row; 1097 | er_ret_t ret_err = RX_OK; 1098 | 1099 | con = AC_dbopen_admin(); 1100 | 1101 | if( SQ_execute_query(con, "SELECT * FROM acl", &result) == -1 ) { 1102 | fprintf(stderr, "ERROR %d: %s\n", SQ_errno(con), SQ_error(con)); 1103 | die; 1104 | } 1105 | 1106 | TH_acquire_write_lock( &(act_acl->rwlock) ); 1107 | 1108 | while ( (row = SQ_row_next(result)) != NULL && ret_err == RX_OK) { 1109 | ip_prefix_t mypref; 1110 | acl_st *newacl; 1111 | #define NUMELEM (7) 1112 | char *col[NUMELEM]; 1113 | unsigned myint; 1114 | int i; 1115 | 1116 | memset(&mypref, 0, sizeof(ip_prefix_t)); 1117 | mypref.ip.space = IP_V4; 1118 | 1119 | if( (ret_err = wr_malloc( (void **)& newacl, sizeof(acl_st)) 1120 | ) == UT_OK ) { 1121 | 1122 | for(i=0; i<NUMELEM; i++) { 1123 | if ( (col[i] = SQ_get_column_string(result, row, i)) == NULL) { 1124 | die; 1125 | } 1126 | } 1127 | 1128 | /* prefix ip */ 1129 | if( sscanf(col[0], "%u", &mypref.ip.words[0] ) < 1 ) { die; } 1130 | 1131 | /* prefix length */ 1132 | if( sscanf(col[1], "%u", &mypref.bits ) < 1 ) { die; } 1133 | 1134 | /* acl contents */ 1135 | if( sscanf(col[2], "%u", & (newacl->maxprivate) ) < 1 ) { die; } 1136 | if( sscanf(col[3], "%u", & (newacl->maxpublic) ) < 1 ) { die; } 1137 | if( sscanf(col[4], "%hd", & (newacl->maxdenials) ) < 1 ) { die; } 1138 | 1139 | /* these are chars therefore cannot read directly */ 1140 | if( sscanf(col[5], "%u", &myint ) < 1 ) { die; } 1141 | else { 1142 | newacl->deny = myint; 1143 | } 1144 | if( sscanf(col[6], "%u", &myint ) < 1 ) { die; } 1145 | else { 1146 | newacl->trustpass = myint; 1147 | } 1148 | 1149 | /* free space */ 1150 | for(i=0; i<NUMELEM; i++) { 1151 | wr_free(col[i]); 1152 | } 1153 | 1154 | /* now add to the tree */ 1155 | ret_err = rx_bin_node( RX_OPER_CRE, &mypref, 1156 | act_acl, (rx_dataleaf_t *) newacl ); 1157 | } 1158 | } /* while row */ 1159 | 1160 | TH_release_write_lock( &(act_acl->rwlock) ); 1161 | 1162 | SQ_free_result(result); 1163 | /* Close connection */ 1164 | SQ_close_connection(con); 1165 | 1166 | return ret_err; 1167 | } /* AC_acc_load */ 1168 | 1169 | 1170 | 1171 | /*++++++++++++++++++++++++++++++++++++++ 1172 | AC_build: 1173 | 1174 | creates empty trees for accounting/acl. 1175 | 1176 | returns error code from RX_tree_cre or OK. 1177 | (XXX): just now only bails out when encounters problems. 1178 | ++++++++++++++++++++++++++++++++++++++*/ 1179 | er_ret_t AC_build(void) 1180 | { 1181 | /* create trees */ 1182 | if ( RX_tree_cre("0.0.0.0/0", RX_FAM_IP, RX_MEM_RAMONLY, 1183 | RX_SUB_NONE, &act_runtime) != RX_OK 1184 | || RX_tree_cre("0.0.0.0/0", RX_FAM_IP, RX_MEM_RAMONLY, 1185 | RX_SUB_NONE, &act_hour) != RX_OK 1186 | || RX_tree_cre("0.0.0.0/0", RX_FAM_IP, RX_MEM_RAMONLY, 1187 | RX_SUB_NONE, &act_minute) != RX_OK 1188 | || RX_tree_cre("0.0.0.0/0", RX_FAM_IP, RX_MEM_RAMONLY, 1189 | RX_SUB_NONE, &act_acl) != RX_OK 1190 | ) 1191 | die; /*can be changed to an error and handled ... some day */ 1192 | 1193 | return RX_OK; 1194 | } 1195 | 1196 | /*++++++++++++++++++++++++++++++++++++++ 1197 | AC_rxwalkhook_print: 1198 | 1199 | action performed on a single account node 1200 | when listing the contents of the access tree: format and print the 1201 | data from this node. 1202 | 1203 | Conforms to rx_walk_tree interface, therefore some of the 1204 | arguments do not apply and are not used. 1205 | 1206 | rx_node_t *node - pointer to the node of the radix tree 1207 | 1208 | int level - not used 1209 | 1210 | int nodecounter - not used 1211 | 1212 | void *con - pointer to the connection structure (prints to it) 1213 | 1214 | returns always OK 1215 | +++++++++++++++++++++++++++++++++++++++*/ 1216 | er_ret_t AC_rxwalkhook_print(rx_node_t *node, 1217 | int level, int nodecounter, 1218 | void *con) 1219 | { 1220 | char adstr[IP_ADDRSTR_MAX]; 1221 | char line[1024]; 1222 | char *dat; 1223 | 1224 | 1225 | if( IP_addr_b2a(&(node->prefix.ip), adstr, IP_ADDRSTR_MAX) != IP_OK ) { 1226 | die; /* program error. */ 1227 | } 1228 | 1229 | sprintf(line, "%-20s %s\n", adstr, 1230 | dat=AC_to_string( node->leaves_ptr )); 1231 | wr_free(dat); 1232 | 1233 | SK_cd_puts((sk_conn_st *)con, line); 1234 | return RX_OK; 1235 | } /* AC_rxwalkhook_print */ 1236 | 1237 | 1238 | /*++++++++++++++++++++++++++++++++++++++ 1239 | AC_rxwalkhook_print_acl: 1240 | 1241 | action performed on a single account node 1242 | when listing the contents of the acl tree: format and print the 1243 | data from this node. 1244 | 1245 | Conforms to rx_walk_tree interface, therefore some of the 1246 | arguments do not apply and are not used. 1247 | 1248 | rx_node_t *node - pointer to the node of the radix tree 1249 | 1250 | int level - not used 1251 | 1252 | int nodecounter - not used 1253 | 1254 | void *con - pointer to the connection structure (prints to it) 1255 | 1256 | returns always OK 1257 | +++++++++++++++++++++++++++++++++++++++*/ 1258 | er_ret_t AC_rxwalkhook_print_acl(rx_node_t *node, 1259 | int level, int nodecounter, 1260 | void *con) 1261 | { 1262 | char prefstr[IP_PREFSTR_MAX]; 1263 | char line[1024]; 1264 | char *dat; 1265 | 1266 | 1267 | if( IP_pref_b2a(&(node->prefix), prefstr, IP_PREFSTR_MAX) != IP_OK ) { 1268 | die; /* program error. */ 1269 | } 1270 | 1271 | sprintf(line, "%-20s %s\n", prefstr, 1272 | dat=AC_acl_to_string( node->leaves_ptr )); 1273 | wr_free(dat); 1274 | 1275 | SK_cd_puts((sk_conn_st *)con, line); 1276 | return RX_OK; 1277 | }/* AC_rxwalkhook_print_acl */ 1278 | 1279 | /*++++++++++++++++++++++++++++++++++++++ 1280 | AC_count_object: 1281 | 1282 | accounts an objects in the credit accordingly to its type, 1283 | or sets denial if the limit is defined and the credit is exceeded. 1284 | 1285 | acc_st *acc_credit pointer to the credit structure (gets modified) 1286 | 1287 | acl_st *acl acl, contains the limits for private/public objects 1288 | 1289 | int private indicates if the object type is private 1290 | ++++++++++++++++++++++++++++++++++++++*/ 1291 | void 1292 | AC_count_object( acc_st *acc_credit, 1293 | acl_st *acl, 1294 | int private ) 1295 | { 1296 | if( private ) { 1297 | if( acc_credit->private_objects <= 0 && acl->maxprivate != -1 ) { 1298 | /* must be negative - will be subtracted */ 1299 | acc_credit->denials = -1; 1300 | } else { 1301 | acc_credit->private_objects --; 1302 | } 1303 | } 1304 | else { 1305 | if( acc_credit->public_objects <= 0 && acl->maxpublic != -1 ) { 1306 | acc_credit->denials = -1; 1307 | } else { 1308 | acc_credit->public_objects --; 1309 | } 1310 | } 1311 | } /* AC_count_object */ 1312 | 1313 | 1314 | /*++++++++++++++++++++++++++++++++++++++ 1315 | AC_credit_isdenied: 1316 | checks the denied flag in credit (-1 or 1 => denied) 1317 | 1318 | acc_st *acc_credit pointer to the credit structure 1319 | +*/ 1320 | int 1321 | AC_credit_isdenied(acc_st *acc_credit) 1322 | { 1323 | return (acc_credit->denials != 0); 1324 | } /* AC_credit_isdenied */ 1325 | 1326 | 1327 | /*++++++++++++++++++++++++++++++++++++++ 1328 | AC_get_higher_limit: 1329 | 1330 | returns the higher number of the two acl limits: maxprivate & maxpublic 1331 | corrected w.r.t the current credit left, 1332 | or unlimited if any of them is 'unlimited'. 1333 | 1334 | acc_st *acc_credit current credit left 1335 | 1336 | acl_st *acl acl for that user 1337 | ++++++++++++++++++++++++++++++++++++++*/ 1338 | 1339 | int 1340 | AC_get_higher_limit(acc_st *acc_credit, 1341 | acl_st *acl) 1342 | { 1343 | if( acl->maxprivate == -1 || acl->maxpublic == -1 ) { 1344 | return -1; 1345 | } 1346 | else { 1347 | int a = acc_credit->private_objects; 1348 | int b = acc_credit->public_objects; 1349 | 1350 | return (a > b ? a : b); 1351 | } 1352 | }