1 | /*************************************** 2 | $Revision: 1.7 $ 3 | 4 | Access control (AC). ac_persistence.c - functions to make the access 5 | control tree persistent. 6 | 7 | Status: NOT REVIEWED, NOT TESTED, COMPLETE 8 | 9 | Implementation by: Tiago Antao 10 | 11 | ******************/ /****************** 12 | Copyright (c) 2002 RIPE NCC 13 | 14 | All Rights Reserved 15 | 16 | Permission to use, copy, modify, and distribute this software and its 17 | documentation for any purpose and without fee is hereby granted, 18 | provided that the above copyright notice appear in all copies and that 19 | both that copyright notice and this permission notice appear in 20 | supporting documentation, and that the name of the author not be 21 | used in advertising or publicity pertaining to distribution of the 22 | software without specific, written prior permission. 23 | 24 | THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE, INCLUDING 25 | ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS; IN NO EVENT SHALL 26 | AUTHOR BE LIABLE FOR ANY SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR ANY 27 | DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN 28 | AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF 29 | OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. 30 | ***************************************/ 31 | 32 | /* 33 | ac_persistence - Does access control persistence 34 | 35 | External entry points are: 36 | 37 | AC_persistence_daemon - Persistence thread 38 | AC_persistence_save - Saves the current access control tree 39 | */ 40 | 41 | #include "rip.h" 42 | #include <math.h> 43 | 44 | /* 45 | Global variables. 46 | */ 47 | 48 | int ac_auto_save; /* This should be encapsulated. */ 49 | 50 | /* 51 | Local typedefs. 52 | */ 53 | 54 | 55 | /* acc_ip 56 | It is needed because we need to know the IP of a acc on a GList context. 57 | */ 58 | typedef struct { 59 | acc_st acc; 60 | ip_prefix_internal_t ip; /* internal?????*/ 61 | } acc_ip; 62 | 63 | 64 | /* 65 | Local variables. 66 | */ 67 | 68 | static rw_lock_t save_lock; /* lock for save operation*/ 69 | static char on_save = 0; /* lack of trylock in TH requires this */ 70 | 71 | /* 72 | From access_control.c 73 | */ 74 | void AC_decay_leaf_l(acc_st *leaf); 75 | char AC_prunable(acc_st *leaf); 76 | extern ut_timer_t oldest_timestamp; 77 | 78 | /* 79 | AC_acc_copy_l: 80 | 81 | Copies the contents of a acc_st variable. 82 | 83 | maybe it should go to access_control.c 84 | */ 85 | void AC_acc_copy_l(acc_st *dest, acc_st *src) { 86 | memcpy(dest, src, sizeof(acc_st)); /* This is OK for the current struct data */ 87 | } 88 | 89 | /* 90 | AC_persistence_get_leaves_hook_l: 91 | 92 | Hook for the return of a Glist of leaves. Constructs an acc_ip. 93 | */ 94 | /* static */ 95 | er_ret_t AC_persistence_get_leaves_hook_l(rx_node_t* node, int level, 96 | int node_counter, void *con) { 97 | GList** list; 98 | acc_ip* ip_acc; 99 | acc_st* acc; 100 | 101 | 102 | 103 | list = (GList**) con; 104 | acc = node->leaves_ptr->data; 105 | if (acc->changed != AC_ACC_NOT_CHANGED) { 106 | ip_acc = UT_malloc(sizeof(acc_ip)); 107 | AC_acc_copy_l(&(ip_acc->acc), acc); 108 | 109 | acc->changed = AC_ACC_NOT_CHANGED; 110 | 111 | ip_acc->ip = node->prefix; 112 | 113 | *list = g_list_prepend(*list, ip_acc); 114 | } 115 | return RX_OK; 116 | } /* AC_persistence_get_leaves_hook_l */ 117 | 118 | /* 119 | ac_persistence_get_leaves: 120 | 121 | Returns a GList of leaves (clone, ie don't forget to deallocate elements) 122 | 123 | Write locks act_runtime. 124 | */ 125 | GList* ac_persistence_get_leaves() { 126 | GList *list; 127 | er_ret_t ret_err; 128 | 129 | list = NULL; 130 | 131 | TH_acquire_write_lock(&(act_runtime->rwlock)); 132 | rx_walk_tree(act_runtime->top_ptr, AC_persistence_get_leaves_hook_l, 133 | RX_WALK_SKPGLU, 255, 0, 0, &list, &ret_err); 134 | /* dieif(ret_err != RX_OK); -- can be safely ignored as result is always OK*/ 135 | TH_release_write_lock(&(act_runtime->rwlock)); 136 | 137 | return list; 138 | } /* ac_persistence_get_leaves */ 139 | 140 | 141 | /* 142 | AC_delete_timestamp_l 143 | */ 144 | void AC_delete_timestamp_l(SQ_connection_t* conn) { 145 | char delete_query[120]; 146 | 147 | sprintf(delete_query, 148 | "DELETE FROM access " 149 | " WHERE timestamp<%11.0f AND addr_passes=0 AND denials=0 ", 150 | ceil(UT_time_getvalue(&oldest_timestamp)) - 10); 151 | //printf("%s\n", delete_query); 152 | SQ_execute_query(conn, delete_query, NULL); //ignoring ?!? 153 | } 154 | 155 | /* 156 | AC_persistence_load: 157 | 158 | Loads the SQL access control data into the act_runtime tree. 159 | Also, clears old leaves from SQL. 160 | 161 | This is different from discussed design!!! Explain: 162 | 163 | This operation blocks the server. It is all in-memory so it should not take too 164 | long. 165 | 166 | Pseudo-code: 167 | 168 | connect to admin DB 169 | SELECT * FROM access 170 | write lock act_runtime 171 | for each row 172 | creates acc_st and ip_prefix 173 | calculates new decay 174 | if node still active then 175 | adds acc_st/ip_prefix to act_runtime 176 | if timestamp<oldest_timestamp 177 | oldest_timestamp = timestamp; 178 | release lock 179 | DELETE FROM access WHERE timestamp<oldest_timestamp 180 | close DB 181 | 182 | */ 183 | void AC_persistence_load(void) { 184 | SQ_connection_t* sql_conn; 185 | SQ_result_set_t* rs; 186 | SQ_row_t *row; 187 | er_ret_t ret_err = RX_OK; 188 | unsigned long timestamp; 189 | 190 | 191 | sql_conn = AC_dbopen_admin(); 192 | 193 | if (SQ_execute_query(sql_conn, "SELECT prefix, prefix_length, connections, " 194 | "addr_passes, denials, queries, " 195 | "referrals, public_objects, " 196 | "private_objects, public_bonus, " 197 | "private_bonus, timestamp " 198 | "FROM access", &rs) == 0) { 199 | TH_acquire_write_lock(&(act_runtime->rwlock)); 200 | 201 | while ((row = SQ_row_next(rs)) != NULL && ret_err == RX_OK) { 202 | acc_st *acc; 203 | ip_prefix_t ip; 204 | 205 | /* Repeat with me: With pre-historic languages you have to do basic 206 | memory management. 8-) */ 207 | acc = UT_malloc(sizeof(acc_st)); 208 | 209 | IP_pref_f2b_v4(&ip, 210 | SQ_get_column_string_nocopy(rs, row, 0), 211 | SQ_get_column_string_nocopy(rs, row, 1)); 212 | 213 | /* 214 | SQ_get_column_int errors are not detected. 215 | In theory it should not be a problem 216 | */ 217 | SQ_get_column_int(rs, row, 2, (long *)&acc->connections); 218 | SQ_get_column_int(rs, row, 3, (long *)&acc->addrpasses); 219 | SQ_get_column_int(rs, row, 4, (long *)&acc->denials); 220 | SQ_get_column_int(rs, row, 5, (long *)&acc->queries); 221 | SQ_get_column_int(rs, row, 6, (long *)&acc->referrals); 222 | SQ_get_column_int(rs, row, 7, (long *)&acc->public_objects); 223 | SQ_get_column_int(rs, row, 8, (long *)&acc->private_objects); 224 | acc->public_bonus = atof(SQ_get_column_string_nocopy(rs, row, 9)); 225 | acc->private_bonus = atof(SQ_get_column_string_nocopy(rs, row, 10)); 226 | SQ_get_column_int(rs, row, 11, (long*)×tamp); 227 | 228 | UT_time_set(&acc->timestamp, timestamp, 0); 229 | 230 | /* Mark it as not changed */ 231 | acc->changed = AC_ACC_NOT_CHANGED; 232 | 233 | AC_decay_leaf_l(acc); 234 | /* lets be memory efficient and not create if it is decayed */ 235 | if (AC_prunable(acc)) { 236 | UT_free(acc); 237 | } 238 | else { 239 | if (UT_timediff(&acc->timestamp, &oldest_timestamp) > 0 && 240 | acc->denials == 0 && acc->addrpasses == 0) { 241 | oldest_timestamp = acc->timestamp; 242 | } 243 | // what about if it already exists? 244 | ret_err = rx_bin_node(RX_OPER_CRE, &ip, act_runtime, (rx_dataleaf_t*) acc); 245 | } 246 | } 247 | // if ret_err... 248 | 249 | SQ_free_result(rs); 250 | TH_release_write_lock(&(act_runtime->rwlock)); 251 | } 252 | else { 253 | ER_perror(FAC_AC, AC_INTR_ERR, "Couldn't load access table"); 254 | } 255 | AC_delete_timestamp_l(sql_conn); 256 | SQ_close_connection(sql_conn); 257 | 258 | } /* AC_persistence_load */ 259 | 260 | /* 261 | AC_persistence_walk_l: 262 | 263 | Writes each leaf (or not, depending on timestamp & changed). 264 | 265 | Pseudo-code: 266 | 267 | for each element 268 | if acc.changed = not changed 269 | continue 270 | if acc.changed == new 271 | insert 272 | if insert ok 273 | return 274 | update (also on special acc.changed cases) 275 | if fail log 276 | 277 | */ 278 | static void AC_persistence_walk_l(GList *list) { 279 | acc_ip *element; 280 | SQ_connection_t *sql_conn; 281 | char sql[500]; 282 | GList *curr_list; 283 | 284 | sql_conn = AC_dbopen_admin(); 285 | curr_list = list; 286 | 287 | while (curr_list) { 288 | element = curr_list->data; 289 | curr_list = g_list_next(curr_list); 290 | if (element->acc.changed == AC_ACC_NOT_CHANGED || 291 | IP_pref_b2v4_addr(&element->ip) == 0) continue; 292 | if (element->acc.changed == AC_ACC_NEW) { 293 | sprintf(sql, "INSERT INTO access(prefix, prefix_length, connections, addr_passes, denials, queries, referrals, public_objects, private_objects, public_bonus, private_bonus, timestamp) VALUES(%u, %d, %d, %d, %d, %d, %d, %d, %d, %f, %f, %10.0f)", 294 | IP_pref_b2v4_addr(&element->ip), 295 | IP_pref_b2_len(&element->ip), 296 | element->acc.connections, 297 | element->acc.addrpasses, 298 | element->acc.denials, 299 | element->acc.queries, 300 | element->acc.referrals, 301 | element->acc.public_objects, 302 | element->acc.private_objects, 303 | element->acc.public_bonus, 304 | element->acc.private_bonus, 305 | ceil(UT_time_getvalue(&element->acc.timestamp)) 306 | ); 307 | //printf("%s\n", sql); 308 | if (SQ_execute_query(sql_conn, sql, NULL) == 0 ) { 309 | continue; 310 | } 311 | /* It is OK to fail, let's wait for UPDATE */ 312 | } 313 | sprintf(sql, "UPDATE access " 314 | "SET connections = %d, addr_passes = %d, denials = %d, " 315 | "queries = %d, referrals = %d, public_objects = %d, " 316 | "private_objects = %d , public_bonus = %f, " 317 | "private_bonus = %f, timestamp = %10.0f " 318 | "WHERE prefix = %u AND prefix_length = %d", 319 | element->acc.connections, 320 | element->acc.addrpasses, 321 | element->acc.denials, 322 | element->acc.queries, 323 | element->acc.referrals, 324 | element->acc.public_objects, 325 | element->acc.private_objects, 326 | element->acc.public_bonus, 327 | element->acc.private_bonus, 328 | ceil(UT_time_getvalue(&element->acc.timestamp)), 329 | IP_pref_b2v4_addr(&element->ip), 330 | IP_pref_b2_len(&element->ip) 331 | ); 332 | //printf("%s\n", sql); 333 | if (SQ_execute_query(sql_conn, sql, NULL) != 0 ) { 334 | ER_perror(FAC_AC, AC_INTR_ERR, "Problems with database updates on " 335 | "admin database"); 336 | SQ_close_connection(sql_conn); 337 | return; 338 | } 339 | } 340 | AC_delete_timestamp_l(sql_conn); 341 | SQ_close_connection(sql_conn); 342 | } 343 | 344 | 345 | 346 | /* 347 | AC_persistence_save_l: 348 | 349 | Saves the act_runtime tree into the SQL access control data table: 350 | This is the function that realy does it (not the public 351 | AC_persistence_save). 352 | 353 | Pseudo-code: 354 | 355 | gets a list of relevant leaves 356 | writes the leaves to SQL 357 | destroy the list of leaves 358 | 359 | */ 360 | static void AC_persistence_save_l(void) { 361 | GList *leaves; 362 | int length; 363 | int cont; 364 | 365 | leaves = ac_persistence_get_leaves(); 366 | 367 | if (leaves) { 368 | AC_persistence_walk_l(leaves); 369 | 370 | /* free GList (&contents!!) */ 371 | length = g_list_length(leaves); 372 | for(cont=0; cont<length; cont++) { 373 | UT_free(g_list_nth_data(leaves, cont)); 374 | } 375 | g_list_free(leaves); 376 | } 377 | } /* AC_persistence_save_l */ 378 | 379 | 380 | /* 381 | AC_persistence_save: 382 | 383 | Saves the act_runtime tree into the SQL access control data table: 384 | Checks if another thread is saving, if so doesn't save. 385 | Saving is done by a call to ac_persistence_save which does the real job. 386 | 387 | Returns AC_OK if done or AC_SAVING in the case of another save process 388 | is running. 389 | 390 | Pseudo-code: 391 | 392 | if !(acquire saving rights) 393 | return AC_SAVING 394 | save 395 | release saving rights 396 | return AC_OK 397 | */ 398 | er_ret_t AC_persistence_save(void) { 399 | 400 | 401 | /* Let's see if a save is already running... */ 402 | TH_acquire_write_lock(&save_lock); 403 | /* Save already running*/ 404 | if (on_save) { 405 | TH_release_write_lock(&save_lock); 406 | return AC_SAVING; 407 | } 408 | /* else ... 409 | We have a go! 410 | */ 411 | on_save = 1; 412 | TH_release_write_lock(&save_lock); 413 | 414 | AC_persistence_save_l(); 415 | 416 | /* Signal end of save operation */ 417 | TH_acquire_write_lock(&save_lock); 418 | on_save = 0; 419 | TH_release_write_lock(&save_lock); 420 | 421 | return AC_OK; 422 | 423 | } /* AC_persistence save */ 424 | 425 | 426 | /* 427 | AC_persistence_init: 428 | 429 | Initializes the persistence system: 430 | 431 | 1. Initializes the dump control mutex. 432 | 2. Initializes the oldest_timestamp. 433 | 3. If required by user loads SQL accounting data. 434 | 4. Sets auto save. 435 | */ 436 | er_ret_t AC_persistence_init(void) { 437 | TH_init_read_write_lock(&save_lock); 438 | 439 | UT_timeget(&oldest_timestamp); 440 | 441 | if (ca_get_ac_load) { 442 | AC_persistence_load(); 443 | } 444 | 445 | ac_auto_save = ca_get_ac_auto_save; 446 | return AC_OK; 447 | } /* AC_persistence_init */ 448 | 449 | 450 | /* 451 | AC_persistence_daemon: 452 | 453 | Implements the top-level thread code: 454 | 455 | Pseudo-code: 456 | 457 | while (CO_get_do_server) 458 | Sleep. 459 | If auto save 460 | Dumps the current version of the act_runtime tree to the persistence 461 | store. 462 | */ 463 | er_ret_t AC_persistence_daemon(void) { 464 | 465 | TA_add(0, "persistence"); 466 | 467 | while(CO_get_do_server()) { 468 | SV_sleep(ca_get_ac_save_interval); 469 | if (ac_auto_save) { 470 | AC_persistence_save(); 471 | } 472 | } 473 | 474 | TA_delete(); 475 | 476 | return AC_OK; 477 | } /* AC_persistence_daemon */