File Coverage

blib/lib/Log/Saftpresse/CountersOutput/Pflogsumm.pm
Criterion Covered Total %
statement 12 352 3.4
branch 0 130 0.0
condition 0 18 0.0
subroutine 4 29 13.7
pod 0 25 0.0
total 16 554 2.8


line stmt bran cond sub pod time code
1             package Log::Saftpresse::CountersOutput::Pflogsumm;
2              
3 1     1   1026 use Moose;
  1         1  
  1         5  
4              
5             # ABSTRACT: plugin to output counters in pflogsumm style output
6             our $VERSION = '1.4'; # VERSION
7              
8             extends 'Log::Saftpresse::CountersOutput';
9              
10 1     1   4342 use Log::Saftpresse::Utils qw( adj_int_units get_smh);
  1         1  
  1         57  
11              
12 1     1   4 use Time::Piece;
  1         2  
  1         7  
13              
14             has 'problems_first' => ( is => 'rw', isa => 'Bool', default => 0 );
15              
16             has 'reject_detail' => ( is => 'rw', isa => 'Int', default => 0 );
17             has 'bounce_detail' => ( is => 'rw', isa => 'Int', default => 0 );
18             has 'deferral_detail' => ( is => 'rw', isa => 'Int', default => 0 );
19              
20             has 'smtp_detail' => ( is => 'rw', isa => 'Int', default => 0 );
21             has 'smtpd_warn_detail' => ( is => 'rw', isa => 'Int', default => 0 );
22              
23             has 'quiet' => ( is => 'rw', isa => 'Bool', default => 0 );
24              
25             has 'top_domains_cnt' => ( is => 'rw', isa => 'Int', default => 20 );
26             has 'top_users_cnt' => ( is => 'rw', isa => 'Int', default => 20 );
27              
28             sub output {
29 0     0 0   my ( $self, $cnt ) = @_;
30 0           my $pf_cnt = $cnt->{'Postfix'};
31              
32 0           $self->print_totals( $cnt );
33              
34 0 0         if( defined $pf_cnt->get_node('conn') ) {
35 0           $self->print_smtpd_stats( $pf_cnt );
36             }
37 0 0         if( $self->problems_first ) {
38 0           $self->print_problems_reports( $pf_cnt );
39             }
40              
41 0           $self->print_traffic_summaries( $cnt );
42              
43 0 0         if( $self->top_domains_cnt != 0 ) {
44 0           $self->print_domain_summaries( $cnt );
45             }
46              
47 0 0         if( defined $pf_cnt->get_node('conn') ) {
48 0           $self->print_smtpd_summaries( $pf_cnt );
49             }
50              
51 0           $self->print_user_summaries( $cnt );
52              
53             # print_hash_by_key(\%noMsgSize, "Messages with no size data", 0, 1);
54              
55 0 0         if( ! $self->problems_first ) {
56 0           $self->print_problems_reports( $pf_cnt );
57             }
58              
59             # print_detailed_msg_data(\%msgDetail, "Message detail", $opts{'q'}) if($opts{'e'});
60              
61 0 0         if( defined $pf_cnt->{'tls_conn'} ) {
62 0           $self->print_tls_stats( $pf_cnt );
63             }
64 0 0         if( defined $cnt->{'PostfixGeoStats'} ) {
65 0           $self->print_geo_stats( $cnt->{'PostfixGeoStats'} );
66             }
67              
68 0           return;
69             }
70              
71             sub print_user_summaries {
72 0     0 0   my ( $self, $cnt ) = @_;
73 0           my $pf_cnt = $cnt->{'Postfix'};
74 0           my $quiet = $self->quiet;
75              
76 0           $self->print_hash_by_cnt_vals(
77             $pf_cnt->get_node('recieved', 'by_sender'),
78             "Senders by message count", 0, $quiet );
79              
80 0           $self->print_hash_by_cnt_vals(
81             $pf_cnt->get_node('sent', 'by_rcpt'),
82             "Recipients by message count", 0, $quiet );
83              
84 0           $self->print_hash_by_cnt_vals(
85             $pf_cnt->get_node('recieved', 'size', 'by_sender'),
86             "Senders by message size", 0, $quiet );
87              
88 0           $self->print_hash_by_cnt_vals(
89             $pf_cnt->get_node('sent', 'size', 'by_rcpt'),
90             "Recipients by message size", 0, $quiet );
91              
92 0           return;
93             }
94              
95             sub hash_calc_avg {
96 0     0 0   my ( $self, $precision, $total, $count ) = @_;
97 0           my %avg;
98 0           my %uniq = map { $_ => 1 } ( keys %$total, keys %$count );
  0            
99 0           my @keys = keys %uniq;
100 0           foreach my $key ( @keys ) {
101 0           my $value;
102 0 0 0       if( defined $total->{$key} && $total->{$key} > 0
      0        
      0        
103             && defined $count->{$key} && $count->{$key} > 0 ) {
104 0           $value = $total->{$key} / $count->{$key};
105             }
106 0 0 0       if( defined $total->{$key} && $total->{$key} eq 0 ) {
107 0           $value = 0;
108             }
109 0 0         if( defined $value ) {
110 0           $avg{$key} = sprintf('%.'.$precision.'f', $value);
111             } else {
112 0           $avg{$key} = undef;
113             }
114             }
115 0           return \%avg;
116             }
117              
118             sub print_table_from_hashes {
119 0     0 0   my ( $self, $legend, $sort, $lw, $cw, @rows ) = @_;
120 0           my @headers = map { $_->[0] } @rows;
  0            
121 0           my @hashes = map { $_->[1] } @rows;
  0            
122 0           my @yaxis;
123              
124 0           $self->print_table_header( $legend, $lw, $cw, @headers );
125              
126 0 0         if( ref($sort) eq 'ARRAY' ) { # sort by a column value
127 0           my ( $sortby, $alg, $limit ) = @$sort;
128 0           my ( $row ) = grep { $_->[0] eq $sortby } @rows;
  0            
129 0           $row = $row->[1];
130 0 0         if( ! defined $row ) { confess('cant find row '.$sortby.' for sorting'); }
  0            
131 0 0         if( $alg eq 'decimal' ) {
132 0           @yaxis = sort { $row->{$b} <=> $row->{$a} } keys %$row;
  0            
133             } else { # string
134 0           @yaxis = sort { $row->{$b} cmp $row->{$a} } keys %$row;
  0            
135             }
136 0 0 0       if( $limit > 0 && scalar @yaxis > $limit ) { @yaxis = @yaxis[0 .. ($limit-1) ] };
  0            
137             } else { # simple sort by key
138 0           my @all_keys = map { keys %$_ } @hashes;
  0            
139 0           my %uniq = map { $_ => 1 } @all_keys;
  0            
140 0 0         if( $sort eq 'decimal' ) {
141 0           @yaxis = sort { $a <=> $b } keys %uniq;
  0            
142             } else { # string
143 0           @yaxis = sort { $a cmp $b } keys %uniq;
  0            
144             }
145             }
146              
147 0           foreach my $row ( @yaxis ) {
148             $self->print_table_row( $row, $lw, $cw,
149 0           map { $_->{$row} } @hashes );
  0            
150             }
151 0           print "\n";
152              
153 0           return;
154             }
155              
156             sub print_table_row {
157 0     0 0   my ( $self, $ylabel, $lw, $cw, @values ) = @_;
158              
159 0           printf("%".$lw."s", $ylabel);
160 0           foreach my $value ( @values ) {
161 0 0         if( ! defined $value ) { $value = '-'; }
  0            
162 0           printf(" %".$cw."s", $value);
163             }
164 0           print "\n";
165              
166 0           return;
167             }
168              
169             sub print_table_header {
170 0     0 0   my ( $self, $legend, $lw, $cw, @headers ) = @_;
171              
172 0           $self->print_table_row( $legend, $lw, $cw, @headers);
173              
174 0           my $width = $lw + (( $cw + 1 ) * scalar @headers);
175 0           print( ("-" x $width)."\n");
176              
177 0           return;
178             }
179              
180             sub print_domain_summaries {
181 0     0 0   my ( $self, $cnt ) = @_;
182 0           my $top_cnt = $self->top_domains_cnt;
183 0           my $pf_cnt = $cnt->{'Postfix'};
184              
185 0           foreach my $table ( 'sent', 'recieved' ) {
186 0           print_subsect_title("Host/Domain Summary: Message Delivery (top $top_cnt $table)");
187 0 0         $self->print_table_from_hashes( 'host/domain',
188             [ 'sent cnt', 'decimal', $top_cnt ], 25, 10,
189             [ 'sent cnt', $pf_cnt->get_node($table, 'by_domain') ],
190             [ 'bytes', $pf_cnt->get_node($table, 'size', 'by_domain') ],
191             $table eq 'sent' ? (
192             # TODO
193             #[ 'defers', $delivered->get_node('busy', 'per_day') ],
194             [ 'avg delay', $self->hash_calc_avg( 2,
195             $pf_cnt->get_node('sent', $table, 'delay', 'by_domain'),
196             $pf_cnt->get_node('sent', $table, 'by_domain'),
197             ), ],
198             [ 'max. delay', $pf_cnt->get_node('sent', $table, 'max_delay', 'by_domain'), ],
199             ) : (),
200             );
201             }
202              
203 0           return;
204             }
205              
206             sub print_smtpd_summaries {
207 0     0 0   my ( $self, $cnt ) = @_;
208 0           my $params = {
209             'day' => [ 'Per-Day', 'per_day', 'string', 15 ],
210             'hour' => [ 'Per-Hour', 'per_hr', 'decimal', 15 ],
211             'domain' => [ 'Per-Hour', 'per_domain', [ 'connections', 'decimal', 20 ], 25 ],
212             };
213              
214 0           foreach my $table ( 'day', 'hour', 'domain' ) {
215 0           my ( $title, $key, $sort, $len ) = @{$params->{ $table }};
  0            
216 0           print_subsect_title("$title SMTPD Connection Summary");
217 0           $self->print_table_from_hashes( $table, $sort, $len, 10,
218             [ 'connections', $cnt->get_node('conn', $key) ],
219             [ 'time conn.', $cnt->get_node('conn', 'busy', $key) ],
220             [ 'avg./conn.', $self->hash_calc_avg( 2,
221             $cnt->get_node('conn', 'busy', $key),
222             $cnt->get_node('conn', $key),
223             ), ],
224             [ 'max. time', $cnt->get_node('conn', 'busy', 'max_'.$key ), ],
225             );
226             }
227 0           return;
228             }
229              
230             sub print_traffic_summaries {
231 0     0 0   my ( $self, $cnt ) = @_;
232 0           my $pf_cnt = $cnt->{'Postfix'};
233 0           my $params = {
234             'day' => [ 'Per-Day', 'per_day', 'string' ],
235             'hour' => [ 'Per-Hour', 'per_hr', 'decimal' ],
236             };
237              
238 0           foreach my $table ('day', 'hour') {
239 0           my ( $title, $key, $sort ) = @{$params->{ $table }};
  0            
240 0           print_subsect_title( "$title Traffic Summary" );
241 0           $self->print_table_from_hashes( $table, $sort, 15, 10,
242             [ 'recieved', $pf_cnt->get_node('incoming', $key) ],
243             [ 'delivered', $pf_cnt->get_node('sent', $key) ],
244             [ 'deffered', $pf_cnt->get_node('deferred', $key), ],
245             [ 'bounced', $pf_cnt->get_node('bounced', $key), ],
246             [ 'rejected', $pf_cnt->get_node('reject', $key) ],
247             );
248             }
249              
250 0           return;
251             }
252              
253             sub print_totals {
254 0     0 0   my ( $self, $cnt ) = @_;
255 0           my $pf_cnt = $cnt->{'Postfix'};
256 0           my $smtpdConnCnt = 0;
257              
258             # PostfixRejects
259 0           my $msgsRjctd = $pf_cnt->get_value_or_zero('reject', 'total', 'reject');
260 0           my $msgsDscrdd = $pf_cnt->get_value_or_zero('reject', 'total', 'discard');
261 0           my $msgsWrnd = $pf_cnt->get_value_or_zero('reject', 'total', 'warning');
262 0           my $msgsHld = $pf_cnt->get_value_or_zero('reject', 'total', 'hold');
263              
264             # PostfixRecieved
265 0           my $msgsRcvd = $pf_cnt->get_value_or_zero('incoming', 'total');
266              
267 0           my $msgsDlvrd = $pf_cnt->get_value_or_zero('sent', 'total');
268 0           my $msgsDfrd = $pf_cnt->get_value_or_zero('deferred', 'total');
269 0           my $msgsFwdd = $pf_cnt->get_value_or_zero('forwarded');
270 0           my $msgsBncd = $pf_cnt->get_value_or_zero('bounced', 'total');
271              
272 0           my $sizeRcvd = $pf_cnt->get_value_or_zero('recieved', 'size', 'total');
273 0           my $sizeDlvrd = $pf_cnt->get_value_or_zero('sent', 'size', 'total');
274              
275 0           my $sendgUserCnt = $pf_cnt->get_key_count('recieved', 'by_sender');
276 0           my $sendgDomCnt = $pf_cnt->get_key_count('recieved', 'by_domain');
277 0           my $recipUserCnt =$pf_cnt->get_key_count('sent', 'by_rcpt');
278 0           my $recipDomCnt = $pf_cnt->get_key_count('sent', 'by_domain');
279              
280             # Calculate percentage of messages rejected and discarded
281 0           my $msgsRjctdPct = 0;
282 0           my $msgsDscrddPct = 0;
283 0 0         if(my $msgsTotal = $msgsDlvrd + $msgsRjctd + $msgsDscrdd) {
284 0           $msgsRjctdPct = int(($msgsRjctd/$msgsTotal) * 100);
285 0           $msgsDscrddPct = int(($msgsDscrdd/$msgsTotal) * 100);
286             }
287              
288 0           print "Postfix log summaries generated on ".Time::Piece->new->ymd."\n";
289              
290 0           print_subsect_title("Grand Totals");
291 0           print "messages\n\n";
292 0           printf " %6d%s received\n", adj_int_units($msgsRcvd);
293 0           printf " %6d%s delivered\n", adj_int_units($msgsDlvrd);
294 0           printf " %6d%s forwarded\n", adj_int_units($msgsFwdd);
295 0           printf " %6d%s deferred", adj_int_units($msgsDfrd);
296             #printf " (%d%s deferrals)", adj_int_units($msgsDfrdCnt) if($msgsDfrdCnt);
297 0           print "\n";
298 0           printf " %6d%s bounced\n", adj_int_units($msgsBncd);
299 0           printf " %6d%s rejected (%d%%)\n", adj_int_units($msgsRjctd), $msgsRjctdPct;
300 0           printf " %6d%s reject warnings\n", adj_int_units($msgsWrnd);
301 0           printf " %6d%s held\n", adj_int_units($msgsHld);
302 0           printf " %6d%s discarded (%d%%)\n", adj_int_units($msgsDscrdd), $msgsDscrddPct;
303 0           print "\n";
304 0           printf " %6d%s bytes received\n", adj_int_units($sizeRcvd);
305 0           printf " %6d%s bytes delivered\n", adj_int_units($sizeDlvrd);
306 0           printf " %6d%s senders\n", adj_int_units($sendgUserCnt);
307 0           printf " %6d%s sending hosts/domains\n", adj_int_units($sendgDomCnt);
308 0           printf " %6d%s recipients\n", adj_int_units($recipUserCnt);
309 0           printf " %6d%s recipient hosts/domains\n", adj_int_units($recipDomCnt);
310 0           print "\n";
311              
312 0           return;
313             }
314              
315             sub print_smtpd_stats {
316 0     0 0   my ( $self, $cnt ) = @_;
317 0           my $smtpdConnCnt = $cnt->get_value_or_zero('conn', 'total');
318 0           print "\nsmtpd\n\n";
319 0           printf " %6d%s connections\n",
320             adj_int_units($smtpdConnCnt);
321             printf " %6d%s hosts/domains\n",
322 0           adj_int_units(int(keys %{$cnt->get_node('conn', 'per_domain')}));
  0            
323 0 0         printf " %6d avg. connect time (seconds)\n",
324             $smtpdConnCnt > 0 ?
325             ($cnt->get_value_or_zero('conn', 'busy', 'total')
326             / $smtpdConnCnt ) + .5
327             : 0;
328             {
329 0           my ($sec, $min, $hr) = get_smh($cnt->get_value_or_zero('conn', 'busy', 'total'));
  0            
330 0           printf " %2d:%02d:%02d total connect time\n",
331             $hr, $min, $sec;
332             }
333 0           return;
334             }
335              
336             sub print_problems_reports {
337 0     0 0   my ( $self, $cnt ) = @_;
338              
339 0 0         if($self->deferral_detail != 0) {
340 0           $self->print_nested_hash( $cnt->get_node('deferred'),
341             "message deferral detail",
342             $self->deferral_detail );
343             }
344 0 0         if($self->bounce_detail != 0) {
345 0           $self->print_nested_hash( $cnt->get_node('bounced'),
346             "message bounce detail (by relay)",
347             $self->bounce_detail );
348             }
349 0 0         if($self->reject_detail != 0) {
350 0           foreach my $key ( 'reject', 'warning', 'hold', 'discard') {
351 0           $self->print_nested_hash($cnt->get_node('reject', $key),
352             "message $key detail",
353             $self->reject_detail );
354             }
355             }
356              
357 0 0         if( my $smtp_cnt = $cnt->{'PostfixSmtp'} ) {
358 0           my $messages = $smtp_cnt->get_node('messages');
359 0 0         if( defined $messages ) {
360 0           $self->print_nested_hash($messages, "smtp delivery failures",
361             $self->smtp_detail );
362             }
363             }
364 0 0         if( my $msg_cnt = $cnt->{'PostfixMessages'} ) {
365 0 0         if($self->smtpd_warn_detail != 0) {
366 0           $self->print_nested_hash($msg_cnt->get_node('warning'),
367             "Warnings",
368             $self->smtpd_warn_detail );
369             }
370 0           $self->print_nested_hash($msg_cnt->get_node('fatal'),
371             "Fatal Errors", 0 );
372 0           $self->print_nested_hash($msg_cnt->get_node('panic'),
373             "Panics", 0 );
374 0           $self->print_hash_by_cnt_vals($msg_cnt->get_node('master'),
375             "Master daemon messages", 0 );
376             }
377             }
378              
379             sub print_tls_stats {
380 0     0 0   my ( $self, $cnt ) = @_;
381 0           my $smtpdConnCnt;
382              
383 0 0         if( defined $cnt->get_node('conn') ) {
384 0           $smtpdConnCnt = $cnt->get_value_or_zero('conn', 'total');
385             }
386 0           my $msgs_rcvd = $cnt->get_value_or_zero('incoming', 'total');
387 0           my $msgs_sent = $cnt->get_value_or_zero('sent', 'total');
388              
389 0           print_subsect_title("TLS Statistics");
390              
391 0           my @total_stats = (
392             [ 'incoming tls connections' => $smtpdConnCnt,
393             'tls_conn', 'smtpd', 'total' ],
394             [ 'incoming tls messages' => $msgs_rcvd,
395             'tls_msg', 'smtpd', 'total' ],
396             [ 'outgoing tls connections' => $smtpdConnCnt,
397             'tls_conn', 'smtp', 'total' ],
398             [ 'outgoing tls messages' => $msgs_sent,
399             'tls_msg', 'smtp', 'total' ],
400             );
401              
402 0           foreach my $stat ( @total_stats ) {
403 0           my ( $name, $total, @node ) = @$stat;
404 0           my $value = $cnt->get( @node );
405 0 0         if( ! defined $value ) { next; }
  0            
406 0           printf " %6d%s $name",
407             adj_int_units($value);
408 0 0         if( $total ) {
409 0           print_in_percent($value, $total);
410 0           } else { print "\n"; }
411             }
412              
413 0           my @tls_statistics = (
414             [ "Incoming TLS trust-level" =>
415             $smtpdConnCnt, 'tls_conn', 'smtpd', 'level' ],
416             [ "Outgoing TLS trust-level" =>
417             0, 'tls_conn', 'smtp', 'level' ],
418             [ "Incoming TLS Protocol Version" =>
419             $smtpdConnCnt, 'tls_conn', 'smtpd', 'protocol' ],
420             [ "Outgoing TLS Protocol Version" =>
421             0, 'tls_conn', 'smtp', 'protocol' ],
422             [ "Incoming TLS key length" =>
423             $smtpdConnCnt, 'tls_conn', 'smtpd', 'keylen' ],
424             [ "Outgoing TLS key length" =>
425             0, 'tls_conn', 'smtp', 'keylen' ],
426             [ "Incoming TLS Ciphers" =>
427             $smtpdConnCnt, 'tls_conn', 'smtpd', 'cipher' ],
428             [ "Outgoing TLS Ciphers" =>
429             0, 'tls_conn', 'smtp', 'cipher' ],
430             );
431              
432 0           foreach my $tls_stat ( @tls_statistics ) {
433 0           my ( $title, $total, @node ) = @$tls_stat;
434 0           my $values = $cnt->get_node(@node);
435 0 0         if( ! defined $values ) { next; }
  0            
436 0           $values = hash_key_add_percent( $values, $total );
437 0           $self->print_hash_by_cnt_vals( $values, $title, 0, 1 );
438             }
439             }
440              
441             sub print_geo_stats {
442 0     0 0   my ( $self, $cnt ) = @_;
443 0           my $client = $cnt->get_node('client');
444 0 0         if( defined $client ) {
445 0           $self->print_hash_by_cnt_vals( $client, 'Client Countries', 0, 1 );
446             }
447             }
448              
449             sub print_in_percent {
450 0     0 0   my ( $value, $total ) = @_;
451 0           my $percent = $value / $total * 100;
452 0           printf(" (%3.2f%% of %d)\n", $percent, $total );
453 0           return;
454             }
455              
456             sub hash_key_add_percent {
457 0     0 0   my ( $hash, $base ) = @_;
458 0 0 0       if( ! defined $base || $base == 0 ) {
459 0           return( $hash );
460             }
461             my $out = {
462             map {
463 0           my $percent = sprintf("%.2f%%", $hash->{$_} / $base * 100 );
  0            
464 0           $_.' ('.$percent.')' => $hash->{$_};
465             } keys %$hash
466             };
467 0           return( $out );
468             }
469              
470             # print hash contents sorted by numeric values in descending
471             # order (i.e.: highest first)
472             sub print_hash_by_cnt_vals {
473 0     0 0   my($self, $hashRef, $title, $cnt, $quiet) = @_;
474 0           my $dottedLine;
475 0 0         if( ! defined $hashRef) { return; }
  0            
476 0 0         $title = sprintf "%s%s", $cnt? "top $cnt " : "", $title;
477 0 0         unless(%$hashRef) {
478 0 0         return if($quiet);
479 0           $dottedLine = ": none";
480             } else {
481 0           $dottedLine = "\n" . "-" x length($title);
482             }
483 0           printf "\n$title$dottedLine\n";
484 0           really_print_hash_by_cnt_vals($hashRef, $cnt, ' ');
485             }
486              
487             # print hash contents sorted by key in ascending order
488             sub print_hash_by_key {
489 0     0 0   my($hashRef, $title, $cnt, $quiet) = @_;
490 0           my $dottedLine;
491 0 0         $title = sprintf "%s%s", $cnt? "first $cnt " : "", $title;
492 0 0         unless(%$hashRef) {
493 0 0         return if($quiet);
494 0           $dottedLine = ": none";
495             } else {
496 0           $dottedLine = "\n" . "-" x length($title);
497             }
498 0           printf "\n$title$dottedLine\n";
499 0           foreach (sort keys(%$hashRef))
500             {
501 0           printf " %s %s\n", $_, $hashRef->{$_};
502 0 0         last if --$cnt == 0;
503             }
504             }
505              
506             # print "nested" hashes
507             sub print_nested_hash {
508 0     0 0   my( $self, $hashRef, $title, $cnt ) = @_;
509 0           my $quiet = $self->quiet;
510 0           my $dottedLine;
511 0 0         if( ! defined $hashRef ) { return; }
  0            
512 0 0         unless(%$hashRef) {
513 0 0         return if($quiet);
514 0           $dottedLine = ": none";
515             } else {
516 0           $dottedLine = "\n" . "-" x length($title);
517             }
518 0           printf "\n$title$dottedLine\n";
519 0           walk_nested_hash($hashRef, $cnt, 0);
520             }
521              
522             # "walk" a "nested" hash
523             sub walk_nested_hash {
524 0     0 0   my ($hashRef, $cnt, $level) = @_;
525 0           $level += 2;
526 0           my $indents = ' ' x $level;
527 0           my ($keyName, $hashVal) = each(%$hashRef);
528              
529 0 0         if( ref($hashRef) ne 'HASH' ) { return; }
  0            
530              
531 0 0         if(ref($hashVal) ne 'HASH') {
532 0           really_print_hash_by_cnt_vals($hashRef, $cnt, $indents);
533 0           return;
534             }
535 0           foreach (sort keys %$hashRef) {
536 0 0         if( ref $hashRef->{$_} ne 'HASH' ) { next; }
  0            
537 0           print "$indents$_";
538             # If the next hash is finally the data, total the
539             # counts for the report and print
540 0           my $hashVal2 = (each(%{$hashRef->{$_}}))[1];
  0            
541 0           keys(%{$hashRef->{$_}}); # "reset" hash iterator
  0            
542 0 0         unless(ref($hashVal2) eq 'HASH') {
543 0 0         print " (top $cnt)" if($cnt > 0);
544 0           my $rptCnt = 0;
545 0           $rptCnt += $_ foreach (values %{$hashRef->{$_}});
  0            
546 0           print " (total: $rptCnt)";
547             }
548 0           print "\n";
549 0           walk_nested_hash($hashRef->{$_}, $cnt, $level);
550             }
551             }
552              
553             # print per-message info in excruciating detail :-)
554             sub print_detailed_msg_data {
555 1     1   2488 use vars '$hashRef';
  1         2  
  1         678  
556 0     0 0   local($hashRef) = $_[0];
557 0           my($title, $quiet) = @_[1,2];
558 0           my $dottedLine;
559 0 0         unless(%$hashRef) {
560 0 0         return if($quiet);
561 0           $dottedLine = ": none";
562             } else {
563 0           $dottedLine = "\n" . "-" x length($title);
564             }
565 0           printf "\n$title$dottedLine\n";
566 0           foreach (sort by_domain_then_user keys(%$hashRef))
567             {
568 0           printf " %s %s\n", $_, shift(@{$hashRef->{$_}});
  0            
569 0           foreach (@{$hashRef->{$_}}) {
  0            
570 0           print " $_\n";
571             }
572 0           print "\n";
573             }
574             }
575              
576             # *really* print hash contents sorted by numeric values in descending
577             # order (i.e.: highest first), then by IP/addr, in ascending order.
578             sub really_print_hash_by_cnt_vals {
579 0     0 0   my($hashRef, $cnt, $indents) = @_;
580              
581 0           foreach (map { $_->[0] }
  0            
582 0 0         sort { $b->[1] <=> $a->[1] || $a->[2] cmp $b->[2] }
583 0           map { [ $_, $hashRef->{$_}, normalize_host($_) ] }
584             (keys(%$hashRef)))
585             {
586 0           printf "$indents%6d%s %s\n", adj_int_units($hashRef->{$_}), $_;
587 0 0         last if --$cnt == 0;
588             }
589             }
590              
591             # Print a sub-section title with properly-sized underline
592             sub print_subsect_title {
593 0     0 0   my $title = $_[0];
594 0           print "\n$title\n" . "-" x length($title) . "\n";
595             }
596              
597             # Normalize IP addr or hostname
598             # (Note: Makes no effort to normalize IPv6 addrs. Just returns them
599             # as they're passed-in.)
600             sub normalize_host {
601             # For IP addrs and hostnames: lop off possible " (user@dom.ain)" bit
602 0     0 0   my $norm1 = (split(/\s/, $_[0]))[0];
603              
604 0 0         if((my @octets = ($norm1 =~ /^(\d{1,3})\.(\d{1,3})\.(\d{1,3})\.(\d{1,3})$/)) == 4) {
605             # Dotted-quad IP address
606 0           return(pack('U4', @octets));
607             } else {
608             # Possibly hostname or user@dom.ain
609 0           return(join( '', map { lc $_ } reverse split /[.@]/, $norm1 ));
  0            
610             }
611             }
612              
613             # subroutine to sort by domain, then user in domain, then by queue i.d.
614             # Note: mixing Internet-style domain names and UUCP-style bang-paths
615             # may confuse this thing. An attempt is made to use the first host
616             # preceding the username in the bang-path as the "domain" if none is
617             # found otherwise.
618             sub by_domain_then_user {
619             # first see if we can get "user@somedomain"
620 0     0 0   my($userNameA, $domainA) = split(/\@/, ${$hashRef->{$a}}[0]);
  0            
621 0           my($userNameB, $domainB) = split(/\@/, ${$hashRef->{$b}}[0]);
  0            
622              
623             # try "somedomain!user"?
624 0 0         ($userNameA, $domainA) = (split(/!/, ${$hashRef->{$a}}[0]))[-1,-2]
  0            
625             unless($domainA);
626 0 0         ($userNameB, $domainB) = (split(/!/, ${$hashRef->{$b}}[0]))[-1,-2]
  0            
627             unless($domainB);
628              
629             # now re-order "mach.host.dom"/"mach.host.do.co" to
630             # "host.dom.mach"/"host.do.co.mach"
631 0 0         $domainA =~ s/^(.*)\.([^\.]+)\.([^\.]{3}|[^\.]{2,3}\.[^\.]{2})$/$2.$3.$1/
632             if($domainA);
633 0 0         $domainB =~ s/^(.*)\.([^\.]+)\.([^\.]{3}|[^\.]{2,3}\.[^\.]{2})$/$2.$3.$1/
634             if($domainB);
635              
636             # oddly enough, doing this here is marginally faster than doing
637             # an "if-else", above. go figure.
638 0 0         $domainA = "" unless($domainA);
639 0 0         $domainB = "" unless($domainB);
640              
641 0 0         if($domainA lt $domainB) {
    0          
642 0           return -1;
643             } elsif($domainA gt $domainB) {
644 0           return 1;
645             } else {
646             # disregard leading bang-path
647 0           $userNameA =~ s/^.*!//;
648 0           $userNameB =~ s/^.*!//;
649 0 0         if($userNameA lt $userNameB) {
    0          
650 0           return -1;
651             } elsif($userNameA gt $userNameB) {
652 0           return 1;
653             } else {
654 0 0         if($a lt $b) {
    0          
655 0           return -1;
656             } elsif($a gt $b) {
657 0           return 1;
658             }
659             }
660             }
661 0           return 0;
662             }
663              
664             1;
665              
666             __END__
667              
668             =pod
669              
670             =encoding UTF-8
671              
672             =head1 NAME
673              
674             Log::Saftpresse::CountersOutput::Pflogsumm - plugin to output counters in pflogsumm style output
675              
676             =head1 VERSION
677              
678             version 1.4
679              
680             =head1 AUTHOR
681              
682             Markus Benning <ich@markusbenning.de>
683              
684             =head1 COPYRIGHT AND LICENSE
685              
686             This software is Copyright (c) 1998 by James S. Seymour, 2015 by Markus Benning.
687              
688             This is free software, licensed under:
689              
690             The GNU General Public License, Version 2, June 1991
691              
692             =cut