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  |