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*)&timestamp);
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 */