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