File Coverage

blib/lib/RADIUS/XMLParser.pm
Criterion Covered Total %
statement 191 263 72.6
branch 45 100 45.0
condition 11 16 68.7
subroutine 18 21 85.7
pod 2 3 66.6
total 267 403 66.2


line stmt bran cond sub pod time code
1             package RADIUS::XMLParser;
2              
3 2     2   57872 use strict;
  2         5  
  2         68  
4 2     2   11 use warnings;
  2         5  
  2         51  
5              
6 2     2   12 use File::Basename;
  2         7  
  2         292  
7 2     2   11 use File::Spec;
  2         3  
  2         51  
8 2     2   2061 use Storable qw(lock_store lock_retrieve);
  2         7056  
  2         162  
9 2     2   17 use Carp;
  2         4  
  2         168  
10 2     2   2047 use IO::File;
  2         33946  
  2         272  
11 2     2   5244 use XML::Writer;
  2         22586  
  2         7311  
12              
13             our $VERSION = '2.30';
14              
15             my $interimUpdate;
16             my $writer;
17             my $labelref;
18             my $mapRef;
19             my $startDbm;
20             my $interimDbm;
21             my $daysForOrphan = 1;
22             my $purgeOrphan = 0;
23             my $writeAllEvents = 0;
24             my $outputDir;
25             my $orphanDir;
26             my $xmlencoding = "utf-8";
27              
28             my %map;
29             my @labels;
30             my %tags = ();
31             my %event;
32             my %start;
33             my %stop;
34             my %interim;
35              
36             #--------------------------------------------------
37             # Constructor
38             #--------------------------------------------------
39             sub new {
40              
41 1     1 0 33 my $this = shift;
42 1   33     7 my $class = ref($this) || $this;
43 1         3 my $ref = shift;
44 1         6 my %params = %$ref;
45              
46             #Load parameters if any
47 1 50       7 $mapRef = $params{MAP} if $params{MAP};
48 1 50       5 $purgeOrphan = $params{AUTOPURGE} if $params{AUTOPURGE};
49 1 50       5 $daysForOrphan = $params{DAYSFORORPHAN} if $params{DAYSFORORPHAN};
50 1 50       5 $writeAllEvents = $params{ALLEVENTS} if $params{ALLEVENTS};
51 1 50       6 $xmlencoding = $params{XMLENCODING} if $params{XMLENCODING};
52 1 50       5 $outputDir = $params{OUTPUTDIR} if $params{OUTPUTDIR};
53 1 50       4 $orphanDir = $params{ORPHANDIR} if $params{ORPHANDIR};
54 1 50       6 %map = %$mapRef if $mapRef;
55              
56             #Get current directory
57 1         122 my $curdir = File::Spec->tmpdir();
58 1 50       5 $outputDir = $curdir if ( not defined $outputDir );
59 1 50       5 $orphanDir = $curdir if ( not defined $orphanDir );
60              
61             #Get orphan files
62 1         19 $startDbm = File::Spec->catfile( $orphanDir, "orphan.start" );
63 1         9 $interimDbm = File::Spec->catfile( $orphanDir, "orphan.interim" );
64              
65 1         3 my $self = {};
66 1         3 bless $self => $class;
67              
68             #Load orphan start and interim hash (if any)
69 1         7 _loadHash();
70              
71 1         4 $self;
72             }
73              
74             #--------------------------------------------------
75             # Clean up orphanage on demand
76             # Note that this is done at startup, but might
77             # be required some times to times
78             # (especially for deamons process)
79             #--------------------------------------------------
80             sub flush($) {
81 0     0 1 0 my ($self) = @_;
82 0         0 _loadHash();
83             }
84              
85             #--------------------------------------------------
86             # Open log file and parse each line.
87             # Group then all event based on same session ID
88             #--------------------------------------------------
89             sub convert($$) {
90              
91 1     1 1 8 my ( $self, $log ) = @_;
92              
93             #Initialize counters
94 1         2 my $processedLines = 0;
95              
96             #Open log file to be parsed
97 1 50       4 croak "Log file not supplied" if ( not defined $log );
98              
99             #Get absolute path
100 1         34 $log = File::Spec->rel2abs($log);
101              
102 1 50       50 open( LOG, $log ) or croak "Cannot open file; File=$log; $!";
103              
104             #Boolean that becomes true (1) when the first blank lines have been skipped.
105 1         3 my $begining_skipped = 0;
106              
107             #Get each line
108 1         35 while () {
109              
110 5659         6025 $processedLines++;
111              
112             # Skip the begining of the log file if it only contains blank lines.
113 5659 100 100     18584 if ( /^(\s)*$/ && !$begining_skipped ) {
114 1         12 next;
115             } else {
116 5658         7677 $begining_skipped = 1;
117             }
118              
119             # Analyze line
120 5658         8565 _analyseRadiusLine( $_, $processedLines, $log );
121             }
122              
123             #Store file into XML
124 1         5 my $xmlReturnRef = _event2xml($log);
125 1         6 my %xmlReturn = %$xmlReturnRef;
126              
127             #Log has been parsed
128 1         15 close(LOG);
129              
130             #Reinitializing Stop hash table but keep Start and Interim as orphans
131 1         296 %stop = ();
132              
133 1         12 return ( $xmlReturn{XML_FILE}, $xmlReturn{XML_STOP}, $xmlReturn{XML_START}, $xmlReturn{XML_INTERIM}, $processedLines );
134              
135             }
136              
137             #--------------------------------------------------
138             # Convert Stop event hash reference to XML
139             #--------------------------------------------------
140             sub _event2xml($) {
141              
142 1     1   2 my ($log) = shift;
143              
144             #Initialize counter
145 1         3 my $stopevents = 0;
146 1         3 my $startevents = 0;
147 1         2 my $interimevents = 0;
148              
149             #Create output xml file
150 1         31 my $xml = basename($log);
151              
152             #Replace extension
153 1         5 $xml =~ s/\.[^.]+$//;
154 1         2 $xml .= ".xml";
155              
156             #Create path
157 1         29 $xml = File::Spec->catfile( $outputDir, $xml );
158              
159             #Create a new IO::File
160 1 50       15 my $output = IO::File->new(">$xml")
161             or croak "Cannot open file $xml, $!";
162              
163             #Load XML:Writer
164 1 50       249 $writer = XML::Writer->new(
165             OUTPUT => $output,
166             ENCODING => $xmlencoding,
167             DATA_MODE => 1,
168             DATA_INDENT => 1
169             ) or croak "cannot create XML::Writer: $!";
170              
171             #Start writing
172 1         15221 $writer->xmlDecl( uc($xmlencoding) );
173              
174             #Write a new SESSIONS tag
175 1         57 $writer->startTag("sessions");
176              
177             #For each provided Stop event
178 1         90 foreach my $sessionId ( keys %stop ) {
179              
180             #Open SESSION tag
181 46         1676 $writer->startTag( "session", 'sessionId' => $sessionId );
182 46         3508 my $newRef = $stop{$sessionId};
183 46         817 my %event = %$newRef;
184 46         128 $stopevents++;
185              
186             #Open START tag
187 46         66 my %startevent = ();
188              
189             #And try to retrieve the respective Start session in orphan hash (based on unique session Id)
190 46         79 my $starteventref = _findInStartQueue($sessionId);
191 46         131 $writer->startTag("start");
192 46 100       2288 if ($starteventref) {
193 3         6 $startevents++;
194              
195             #Write content
196 3         7 _writeEvent($starteventref);
197             }
198              
199             #Close START tag
200 46         202 $writer->endTag("start");
201              
202             #Open INTERIMS tag
203 46         1066 my %interimevents = ();
204              
205             #And try to retrieve all the respective Interim sessions in orphan hash (based on unique session Id)
206 46         84 my $interimeventsref = _findInInterimQueue($sessionId);
207 46         121 $writer->startTag("interims");
208 46 50       2295 if ($interimeventsref) {
209 0         0 %interimevents = %$interimeventsref;
210 0         0 for my $event ( sort keys %interimevents ) {
211              
212             #Open INTERIM tag
213 0         0 $writer->startTag( "interim", "id" => $event );
214 0         0 $interimevents++;
215              
216             #Write content
217 0         0 _writeEvent( $interimevents{$event} );
218              
219             #Close INTERIM tag
220 0         0 $writer->endTag("interim");
221             }
222             }
223              
224             #Close INTERIMS tag
225 46         110 $writer->endTag("interims");
226              
227             #Open STOP tag
228 46         1076 $writer->startTag("stop");
229              
230             #Write content
231 46         2173 _writeEvent( \%event );
232              
233             #Close STOP tab
234 46         1340 $writer->endTag("stop");
235              
236             #Close SESSION tag
237 46         1468 $writer->endTag("session");
238             }
239              
240             #[OPTIONAL]
241             #If User wants all events to be reported, let us process start event
242 1 50       50 if ($writeAllEvents) {
243              
244 1         19 for my $sessionId ( keys %start ) {
245              
246             #Open a SESSION Tag
247 43         110 $writer->startTag( "session", 'sessionId' => $sessionId );
248 43         3319 my $newRef = $start{$sessionId};
249              
250             #Open START tag
251 43         113 $writer->startTag("start");
252 43         2156 _writeEvent($newRef);
253 43         1193 $startevents++;
254              
255             #Close START tag
256 43         122 $writer->endTag("start");
257              
258             #Open INTERIMS tag
259 43         1349 my %interimevents = ();
260              
261             #And try to retrieve all the respective Interim sessions in orphan hash (based on unique session Id)
262 43         81 my $interimeventsref = _findInInterimQueue($sessionId);
263 43         115 $writer->startTag("interims");
264 43 50       2223 if ($interimeventsref) {
265 0         0 %interimevents = %$interimeventsref;
266 0         0 for my $event ( sort keys %interimevents ) {
267              
268             #Open INTERIM tag
269 0         0 $writer->startTag( "interim", "id" => $event );
270 0         0 $interimevents++;
271              
272             #Write content
273 0         0 _writeEvent( $interimevents{$event} );
274              
275             #Close INTERIM tag
276 0         0 $writer->endTag("interim");
277             }
278             }
279              
280             #Close INTERIMS tag
281 43         120 $writer->endTag("interims");
282              
283             #Open STOP tag
284 43         1052 $writer->startTag("stop");
285              
286             #Do not write content as all the stop events have been already processed
287              
288             #Close STOP tab
289 43         2163 $writer->endTag("stop");
290              
291             #Close SESSION tag
292 43         958 $writer->endTag("session");
293              
294             #And delete orphan record
295 43         1506 delete $start{$sessionId};
296              
297             }
298              
299             #If User wants all events to be reported, let us process interim event
300 1         71 for my $sessionId ( keys %interim ) {
301              
302             #Open a SESSION Tag
303 129         380 $writer->startTag( "session", 'sessionId' => $sessionId );
304 129         10753 my $newRef = $interim{$sessionId};
305 129         490 my %interimevents = %$newRef;
306              
307             #Open START tag
308 129         367 $writer->startTag("start");
309              
310             #Do not write content as all the start events have been already processed
311             #Close START tag
312 129         7809 $writer->endTag("start");
313              
314 129         3250 for my $event ( sort keys %interimevents ) {
315              
316             #Open INTERIM tag
317 130         388 $writer->startTag( "interim", "id" => $event );
318 130         10167 $interimevents++;
319              
320             #Write content
321 130         310 _writeEvent( $interimevents{$event} );
322              
323             #Close INTERIM tag
324 130         3820 $writer->endTag("interim");
325             }
326              
327             #Open STOP tag
328 129         4538 $writer->startTag("stop");
329              
330             #Do not write content as all the stop events have been already processed
331             #Close STOP tab
332 129         6808 $writer->endTag("stop");
333              
334             #Close SESSION tag
335 129         3264 $writer->endTag("session");
336              
337             #And delete orphan record
338 129         5002 delete $interim{$sessionId};
339             }
340             }
341              
342             #Close SESSIONS tag
343 1         55 $writer->endTag("sessions");
344 1         39 $writer->end();
345 1         40 $output->close();
346              
347 1         132 my %retunedhash = ();
348 1         5 $retunedhash{XML_FILE} = $xml;
349 1         4 $retunedhash{XML_STOP} = $stopevents;
350 1         3 $retunedhash{XML_START} = $startevents;
351 1         4 $retunedhash{XML_INTERIM} = $interimevents;
352 1         5 return \%retunedhash;
353             }
354              
355             #--------------------------------------------------
356             # Remove oldest keys from hash
357             #--------------------------------------------------
358             sub _purgeStartOrphans($) {
359              
360 0     0   0 my $hashref = shift;
361 0         0 my %hash = %$hashref;
362 0         0 my $removed = 0;
363              
364             #Current Epoch
365 0         0 my $time = time;
366              
367             #Compute threshold in seconds
368 0         0 my $threshold = $daysForOrphan * 24 * 3600;
369              
370             #Run through Start hash table
371 0         0 foreach my $sessionId ( keys %hash ) {
372 0         0 my $newHashRef = $hash{$sessionId};
373 0         0 my %newHash = %$newHashRef;
374              
375 0 0       0 if ( !$newHash{"Event-Timestamp"} ) {
376              
377             #Delete records without date
378 0         0 delete $hash{$sessionId};
379 0         0 $removed++;
380 0         0 next;
381             }
382              
383             #Compute max allowed delta time
384 0         0 my $mtime = $newHash{"Event-Timestamp"};
385 0         0 my $delta = $time - $mtime;
386 0 0       0 if ( $delta > $threshold ) {
387              
388             #Delete oldest records
389 0         0 delete $hash{$sessionId};
390 0         0 $removed++;
391 0         0 next;
392             }
393             }
394              
395             #Return reference of purged hash
396 0         0 return \%hash;
397             }
398              
399             #--------------------------------------------------
400             # Remove oldest keys from hash
401             #--------------------------------------------------
402             sub _purgeInterimOrphans($) {
403              
404 0     0   0 my $hashref = shift;
405 0         0 my %hash = %$hashref;
406 0         0 my $removed = 0;
407              
408             #Current Epoch
409 0         0 my $time = time;
410              
411             #Compute threshold in seconds
412 0         0 my $threshold = $daysForOrphan * 24 * 3600;
413              
414             #Run through Interim hash tables
415 0         0 foreach my $sessionId ( keys %hash ) {
416 0         0 my $newHashRef = $hash{$sessionId};
417 0         0 my %newHash = %$newHashRef;
418 0         0 foreach my $occurence ( keys %newHash ) {
419 0         0 my $newNewHashRef = $newHash{$occurence};
420 0         0 my %newNewHash = %$newNewHashRef;
421 0 0       0 if ( !$newNewHash{"Event-Timestamp"} ) {
422              
423             #Delete records without date
424 0         0 delete $newHash{$occurence};
425 0         0 $removed++;
426 0         0 next;
427             }
428              
429             #Compute max allowed delta time
430 0 0       0 my $mtime = ( $newHash{"Event-Timestamp"} ) ? $newHash{"Event-Timestamp"} : 0;
431 0         0 my $delta = $time - $mtime;
432 0 0       0 if ( $delta > $threshold ) {
433              
434             #Delete oldest records
435 0         0 delete $newHash{$occurence};
436 0         0 $removed++;
437 0         0 next;
438             }
439             }
440              
441             #Remove whole interims events if it does not get any interim session
442 0 0       0 delete $hash{$sessionId} if ( !scalar( keys %newHash ) );
443             }
444              
445             #Return reference of purged hash
446 0         0 return \%hash;
447             }
448              
449             #--------------------------------------------------
450             # Retrieve an orphan Start event based on sessionId
451             #--------------------------------------------------
452             sub _findInStartQueue($) {
453              
454 46     46   63 my ($sessionId) = @_;
455 46         67 my $eventref = $start{$sessionId};
456 46 100       129 if ( scalar( keys %$eventref ) ) {
457              
458             #found Start event
459             #Remove start event from orphan hash
460 3         8 delete $start{$sessionId};
461             }
462              
463             #Return hash reference of found Start event, undef otherwise
464 46 100       93 my $return = ( scalar( keys %$eventref ) ) ? $eventref : undef;
465 46         99 return $return;
466              
467             }
468              
469             #--------------------------------------------------
470             # Retrieve an orphan interim event based on sessionId
471             #--------------------------------------------------
472             sub _findInInterimQueue($) {
473              
474 89     89   115 my ($sessionId) = @_;
475 89         182 my $eventref = $interim{$sessionId};
476 89 50       248 if ( scalar( keys %$eventref ) ) {
477              
478             #found Start event
479             #Remove interim event from orphan hash
480 0         0 delete $interim{$sessionId};
481             }
482              
483             #Return hash reference of found Start event, undef otherwise
484 89 50       176 my $return = ( scalar( keys %$eventref ) ) ? $eventref : undef;
485 89         179 return $return;
486              
487             }
488              
489             #--------------------------------------------------
490             # Convert a set of key value from a given hash ref into XML
491             #--------------------------------------------------
492             sub _writeEvent($) {
493              
494 222     222   331 my $ref = shift;
495 222         3181 my %hash = %$ref;
496              
497             #Check if labels have been supplied
498 222 50       847 if ( !scalar( keys %map ) ) {
499              
500             #If not then add any label (tag) found earlier (during parsing)
501 0         0 for my $key ( keys %tags ) {
502 0         0 $map{$key} = $key;
503             }
504             }
505              
506             #convert only the supplied label
507 222         473 for my $key ( keys %map ) {
508              
509             #Get this value
510 222         324 my $value = $hash{$key};
511 222         305 my $tag;
512 222 50       418 if ( $map{$key} ) {
513 222         327 $tag = $map{$key};
514             } else {
515 0         0 $tag = $key;
516             }
517              
518             #Open a new TAG
519 222         610 $writer->startTag($tag);
520 222 50       12578 $writer->characters($value) if $value;
521              
522             #Close TAG
523 222         5152 $writer->endTag($tag);
524              
525             }
526              
527             }
528              
529             #--------------------------------------------------
530             # Read stored hash if file exists
531             #--------------------------------------------------
532             sub _loadHash() {
533              
534             #Load previously stored hashes
535 1     1   2 my $startref;
536             my $interimref;
537              
538             #If file with stored hash exist - START
539 1 50       24 if ( -e $startDbm ) {
540 0 0       0 $startref = lock_retrieve($startDbm)
541             or croak "cannot open file $startDbm: $!";
542 0 0       0 $startref = _purgeStartOrphans($startref) if $purgeOrphan;
543 0         0 %start = %$startref;
544             } else {
545              
546             #Does not exist, so initialize a new one
547 1         4 %start = ();
548             }
549              
550             #If file with stored hash exist - INTERIM
551 1 50       37 if ( -e $interimDbm ) {
552 0 0       0 $interimref = lock_retrieve($interimDbm)
553             or croak "cannot open file $interimDbm: $!";
554 0 0       0 $interimref = _purgeInterimOrphans($interimref) if $purgeOrphan;
555 0         0 %interim = %$interimref;
556             } else {
557              
558             #Does not exist, so initialize a new one
559 1         4 %interim = ();
560             }
561              
562             }
563              
564             #--------------------------------------------------
565             # Retrieve the highest numeric key from a given hash
566             #--------------------------------------------------
567             sub _largestKeyFromHash ($) {
568              
569 130     130   237 my ($hash) = shift;
570 130         313 my ( $key, @keys ) = keys %$hash;
571 130         199 my ( $big, @vals ) = values %$hash;
572              
573 130         302 for ( 0 .. $#keys ) {
574 0 0       0 if ( $vals[$_] > $big ) {
575 0         0 $big = $vals[$_];
576 0         0 $key = $keys[$_];
577             }
578             }
579              
580             #Return highest key value
581 130         463 return $key;
582             }
583              
584             #--------------------------------------------------
585             # Parse each line given as Input buffer
586             #--------------------------------------------------
587             sub _analyseRadiusLine($$$) {
588              
589 5658     5658   8210 my ( $line, $lineNumber, $file ) = @_;
590              
591 5658 100 66     59051 if ( $line =~ /^[A-Za-z]{3}.*[A-Za-z]{3}/
    100 100        
    50          
592             && $line =~ /[0-9]{2}[:][0-9]{2}[:][0-9]{2}/ )
593             {
594              
595             #Radius Date Format (1st line)
596             #Should contain both MON and DAY (letter) And timestamp HH:MI:SS
597             #Start of an event, initialize hash table
598              
599 221         1442 %event = ();
600              
601             } elsif ( $line =~ m/^\n/ || $line =~ m/^[\t\s]+[\n]?$/ ) {
602              
603             #Empty line (end of session - Last line)
604              
605 222   50     557 my $val = $event{"Acct-Status-Type"} || "";
606 222   50     452 my $sessionId = $event{"Acct-Session-Id"} || "";
607 222         11882 my $file = basename($file);
608              
609 222 100       1104 if ( $val =~ /.*[S,s]tart.*/ ) {
    100          
    50          
610              
611             #START event
612 46         204 foreach my $key ( keys %event ) {
613              
614             #Store local start event to global Start events hash
615 968         2146 $start{$sessionId}{$key} = $event{$key};
616             }
617              
618 46         409 $start{$sessionId}{File} = $file;
619              
620             } elsif ( $val =~ /.*[S,s]top.*/ ) {
621              
622             #STOP event
623 46         281 foreach my $key ( keys %event ) {
624              
625             #Store local stop event to global Stop events hash
626 1280         2720 $stop{$sessionId}{$key} = $event{$key};
627             }
628              
629 46         459 $stop{$sessionId}{File} = $file;
630              
631             } elsif ( $val =~ /.*[I,i]nterim/ ) {
632              
633             #INTERIM event
634 130         399 $interimUpdate = _largestKeyFromHash( $interim{$sessionId} );
635 130         206 $interimUpdate++;
636 130         630 foreach my $key ( keys %event ) {
637              
638             #Store local interim event to global Interims events hash
639 2990         7889 $interim{$sessionId}{$interimUpdate}{$key} = $event{$key};
640             }
641              
642 130         1268 $interim{$sessionId}{$interimUpdate}{File} = $file;
643              
644             } else {
645              
646             #If EVENT is populated, this is a unmanaged EVENT or an unexpected empty line
647             #Ignore it
648 0         0 return;
649             }
650              
651             } elsif ( my ( $tag, $val ) = ( $line =~ m/^\t([0-9A-Za-z:-]+)\s+=\s+["]?([A-Za-z0-9=\\\.-\_\s]*)["]?.*\n/ ) ) {
652              
653             #Between first and last line, we store any TAG/VALUE found
654              
655 5215 50       9904 if ($tag) {
656 5215         6476 $tags{$tag}++;
657 5215         19638 $event{$tag} = $val;
658             }
659              
660             }
661              
662             }
663              
664             END {
665              
666             #Store computed Interim hash tables if not empty
667 2 50   2   2874 if ( scalar( keys %interim ) ) {
668 0 0       0 lock_store \%interim, $interimDbm
669             or croak "Cannot store Interim to file $interimDbm: $!";
670             }
671              
672             #Store computed Start hash tables if not empty
673 2 50       19 if ( scalar( keys %start ) ) {
674 0 0       0 lock_store \%start, $startDbm
675             or croak "Cannot store Start to file $startDbm: $!";
676             }
677             }
678              
679             #Keep Perl Happy
680             1;
681              
682             =head1 NAME
683              
684             RADIUS::XMLParser - Radius log file XML convertor
685              
686              
687             =head1 SYNOPSIS
688              
689             =over 5
690              
691             use RADIUS::XMLParser;
692              
693            
694             my %labels = (
695             'Event-Timestamp' => 'Time', # name of tag "Event-Timestamp"
696             'User-Name' => 'User', # name of tag "User-Name"
697             'File' => '' # default name (i.e. File) for tag File
698             );
699            
700             my $radius = RADIUS::XMLParser->new(
701             {
702             VERBOSE => 1,
703             DAYSFORORPHAN => 1,
704             AUTOPURGE => 0,
705             ALLEVENTS => 1,
706             OUTPUTDIR => '/tmp/',
707             MAP => \%labels
708             }
709             );
710            
711             my ($xml, $stop, $start, $interim, $processed) = $radius->convert('radius.log');
712              
713             =back
714              
715             =head1 DESCRIPTION
716              
717             =over
718              
719             =item This module will extract and sort any radius events included into a radius log file.
720              
721             =item Note that your logfile must contain an empty line at its end otherwise the last event will not be analyzed.
722              
723             =item Events will be grouped by their session ID and converted into XML sessions.
724              
725             =item At this time, supported events are the following:
726              
727              
728             START
729             INTERIM-UPDATE
730             STOP
731              
732              
733             =back
734              
735             Any event will be stored on different hash (with SessionID as a unique key).
736             Then, for each STOP event, the respective START and INTERIM event will be retrieved (based on same session ID)
737              
738             =over
739              
740             =item [OPTIONAL] Each found START / INTERIM event will be written, final hash will be empty.
741              
742             =item [OPTIONAL] Only the newest START / INTERIM events will be kept. Oldest ones will be considered as orphan events and will be dropped
743              
744             =back
745              
746             Final XML will get the following structure:
747              
748              
749            
750            
751            
752            
753            
754            
755            
756            
757            
758            
759              
760              
761             =head1 CONSTRUCTOR
762              
763             =head2 Usage:
764              
765             my $parser = RADIUS::XMLParser->new({%params});
766              
767             =head2 Return:
768              
769             A radius parser blessed reference
770              
771             =head2 Parameters:
772              
773             Hash reference including below Options
774              
775             =head2 Options:
776              
777             =head3 [optional] VERBOSE
778              
779             =over
780              
781             =item Integer (0 by default) enabling verbose mode.
782              
783             =item Regarding the amount of lines in a typical Radius log file (hundred MB large is the norm), verbose mode is split into several levels (0,1,2,3).
784              
785             =back
786            
787             =head3 [optional] MAP
788              
789             =over
790              
791             =item Hash reference of labels user would like to see converted into XML.
792              
793             =item Hash Keys are the keys to look for on Radius side
794              
795             =item Hash Values are the name of the XML tags that will be written (XML keys are alias of Radius keys)
796              
797             =item Empty values will result on tag's name = radius keys
798              
799             =item Note that some Radius keys might not be XML compliant (e.g. <3GPP-XYZ-etc...>). This key / value approach will avoid such XML constraint
800              
801             A reference to below Array passed as an input parameter...
802              
803              
804             my %map = (
805             "Acct-Output-Packets" => "Output",
806             "NAS-IP-Address" => "Address",
807             "Event-Timestamp" => ""
808             );
809              
810              
811             ...will result on the following XML structure
812              
813              
814            
815            
816            
817            
818            
819              
820             =item If MAP is not supplied, all the found Key / Values will be written.
821              
822             =item Else, only the supplied keys / values will be written
823              
824             =item FYI, Gettings few MAP is significantly faster... Might save precious time when dealing with large files !
825              
826             =back
827              
828             =head3 [optional] AUTOPURGE
829              
830             =over
831              
832             =item Boolean (0 by default) that will purge stored hash reference (Start + Interim) before being used for Event lookup.
833              
834             =item Newest events will be kept, oldest will be dropped.
835              
836             =item Threshold is defined by below parameter DAYSFORORPHAN
837              
838             =back
839              
840             =head3 [optional] DAYSFORORPHAN
841              
842             =over
843              
844             =item Number of days user would like to keep the orphan Start + Interim events.
845              
846             =item Default is 1 day; any event older than 1 day will be dropped.
847              
848             =item AUTOPURGE must be set to true
849              
850             =back
851              
852             =head3 [optional] OUTPUTDIR
853              
854             =over
855              
856             =item Output directory where XML file will be created
857              
858             =item Default is first temporary directory (returned by Ctmpdir()>)
859              
860             =back
861            
862             =head3 [optional] ALLEVENTS
863              
864             =over
865              
866             =item Boolean (0 by default).
867              
868             =item If 1, all events will be written, including Start, Interim and Stop "orphan" records.
869             Note that Orphan hash should be empty after processing.
870              
871             =item If 0, only the events Stop will be written together with the respective Start / Interims for the same session ID.
872             Note that Orphan hash should not be empty after processing, and therefore should be written on disk (under ORPHANDIR directory)
873              
874             =back
875              
876             =head3 [optional] XMLENCODING
877              
878             =over
879              
880             =item Only C and C are supported
881              
882             =item default is C
883            
884             =back
885              
886             =head3 [optional] ORPHANDIR
887              
888             =over
889              
890             =item Default directory for orphan hash tables stored structure
891              
892             =item Default is first temporary directory (returned by Ctmpdir()>)
893            
894             =back
895              
896             =head1 METHODS
897              
898             =head2 convert
899              
900             =head3 Description:
901              
902             =over
903              
904             =item The C will parse and convert provided file C<$radius_file>.
905              
906             =item All its events will be retrieved, sorted and grouped by their unique sessionId.
907              
908             =item Then, file will be converted into a XML format.
909              
910             =back
911              
912             =head3 Usage:
913            
914             my ($xml, $stop, $start, $interim, $processed) = $parser->convert($radius_file);
915            
916             =head3 Parameter:
917            
918             =over
919              
920             =item C<$radius_file>: Radius log file that will be parsed.
921            
922             =back
923              
924             =head3 Return:
925              
926             =over
927              
928             =item C<$xml>: The name of the XML file that has been created.
929              
930             =item C<$stop>: The number of STOP event written
931              
932             =item C<$start>: The number of START event written
933              
934             =item C<$interim>: The number of INTERIM event written
935              
936             =item C<$processed>: The number of processed lines in the original Radius log file
937              
938             =back
939              
940             =head2 flush
941              
942             =head3 Description:
943              
944             =over
945              
946             =item The C method will cleanup orphanage on demand
947              
948             =item Note that this process is already done at startup but might be required some times to times, especially for deamons processes which might never have to rebuild parser (C method)
949              
950             =item Oldest orphans are dropped
951              
952             =item Need PURGEORPHAN parameter set (optionnally DAYSFORORPHAN)
953              
954             =back
955              
956             =head3 Usage:
957              
958             =over
959            
960             $parser->flush();
961            
962             =back
963              
964             =head1 EXAMPLE:
965              
966              
967             use RADIUS::XMLParser;
968            
969             my $radius_file = 'radius.log';
970             my %map = (
971             "NAS-User-Name" => "User-Name",
972             "Event-Timestamp" => "",
973             "File" => "File"
974             );
975            
976             my $radius = RADIUS::XMLParser->new(
977             {
978             VERBOSE => 1,
979             DAYSFORORPHAN => 1,
980             AUTOPURGE => 0,
981             ALLEVENTS => 1,
982             XMLENCODING => "utf-8",
983             OUTPUTDIR => '/tmp/',
984             MAP => \%map
985             }
986             );
987            
988             my ($xml, $stop, $start, $interim, $processed) = $radius->convert($radius_file);
989            
990              
991             Here is how the generated XML file will look like
992              
993              
994            
995            
996            
997             1334560899
998             User1
999             radius.log
1000            
1001            
1002            
1003             1334561024
1004             User1
1005             radius.log
1006            
1007            
1008             1334561087
1009             User1
1010             radius.log
1011            
1012            
1013            
1014             1334561314
1015             User1
1016             radius.log
1017            
1018            
1019              
1020              
1021             =head1 AUTHOR
1022              
1023             Antoine Amend
1024              
1025             =head1 MODIFICATION HISTORY
1026              
1027             See the Changes file.
1028              
1029             =head1 COPYRIGHT AND LICENSE
1030              
1031             Copyright (c) 2013 Antoine Amend. All rights reserved.
1032              
1033             This program is free software; you can redistribute it and/or
1034             modify it under the same terms as Perl itself.
1035              
1036             =cut
1037