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 */