File Coverage

lib/Mail/Toaster/Logs.pm
Criterion Covered Total %
statement 33 763 4.3
branch 0 518 0.0
condition 0 84 0.0
subroutine 11 46 23.9
pod 24 35 68.5
total 68 1446 4.7


line stmt bran cond sub pod time code
1             package Mail::Toaster::Logs;
2 1     1   928 use strict;
  1         2  
  1         23  
3 1     1   4 use warnings;
  1         1  
  1         35  
4              
5             our $VERSION = 5.50;
6              
7             # the output of warnings and diagnostics should not be enabled in production.
8             # the SNMP daemon depends on the output of maillogs, so we need to return
9             # nothing or valid counters.
10              
11 1     1   3 use Carp;
  1         2  
  1         48  
12 1     1   3 use English '-no_match_vars';
  1         1  
  1         7  
13 1     1   334 use File::Path;
  1         1  
  1         39  
14 1     1   3 use Getopt::Std;
  1         1  
  1         32  
15 1     1   4 use Params::Validate ':all';
  1         1  
  1         134  
16 1     1   3 use Pod::Usage;
  1         1  
  1         57  
17              
18 1     1   3 use vars qw( $spam_ref $count_ref );
  1         1  
  1         36  
19              
20 1     1   3 use lib 'lib';
  1         1  
  1         6  
21 1     1   80 use parent 'Mail::Toaster::Base';
  1         1  
  1         6  
22              
23             sub report_yesterdays_activity {
24 0     0 1   my $self = shift;
25 0           my %p = validate(@_, { $self->get_std_opts } );
26              
27 0 0         return $p{test_ok} if defined $p{test_ok};
28              
29 0   0       my $email = $self->conf->{toaster_admin_email} || "postmaster";
30 0 0         my $qma_dir = $self->find_qmailanalog or return;
31              
32             # if ( ! -s $self->get_yesterdays_smtp_log ) {
33             # carp "no smtp log file for yesterday found!\n";
34             # return;
35             # };
36              
37 0           my $send_log = $self->get_yesterdays_send_log;
38 0 0         if ( ! -s $send_log ) {
39 0           carp "no send log file for yesterday found!\n";
40 0           return;
41             };
42              
43 0           $self->audit( "processing log: $send_log" );
44              
45 0 0         my $cat = $send_log =~ m/\.bz2$/ ? $self->util->find_bin( "bzcat" )
    0          
46             : $send_log =~ m/\.gz$/ ? $self->util->find_bin( "gzcat" )
47             : $self->util->find_bin( "cat" );
48              
49 0           my %cmds = (
50             overall => { cmd => "$qma_dir/zoverall" },
51             failure => { cmd => "$qma_dir/zfailures" },
52             deferral => { cmd => "$qma_dir/zdeferrals" },
53             );
54              
55 0           foreach ( keys %cmds ) {
56 0           my $cmd = "$cat $send_log | $qma_dir/matchup 5>/dev/null | " . $cmds{$_}{cmd};
57 0           $self->audit( "calculating $_ stats with: $cmd");
58 0           $cmds{$_}{out} = `$cmd`;
59             };
60              
61 0           my ( $dd, $mm, $yy ) = $self->util->get_the_date(bump=>0);
62 0           my $date = "$yy.$mm.$dd";
63 0           $self->audit( "date: $yy.$mm.$dd" );
64              
65 0           open my $EMAIL, "| /var/qmail/bin/qmail-inject"; ## no critic (Open)
66 0           print $EMAIL <<"EO_EMAIL";
67             To: $email
68             From: postmaster
69             Subject: Daily Mail Toaster Report for $date
70              
71             ====================================================================
72             OVERALL MESSAGE DELIVERY STATISTICS
73             ____________________________________________________________________
74             \n$cmds{overall}{out}\n\n
75             ====================================================================
76             MESSAGE FAILURE REPORT
77             ____________________________________________________________________
78             $cmds{failure}{out}\n\n
79             ====================================================================
80             MESSAGE DEFERRAL REPORT
81             ____________________________________________________________________
82             $cmds{deferral}{out}
83             EO_EMAIL
84              
85 0           close $EMAIL;
86              
87 0           return 0; # because periodic expects 0 for non-error exit code
88             }
89              
90             sub find_qmailanalog {
91 0     0 0   my $self = shift;
92              
93 0   0       my $qmailanalog_dir = $self->conf->{qmailanalog_bin} || "/var/qmail/qmailanalog/bin";
94              
95             # the port location changed, if toaster.conf hasn't been updated, this
96             # will catch it.
97 0 0         if ( ! -d $qmailanalog_dir ) {
98 0           carp <<"EO_QMADIR_MISSING";
99             ERROR: the location of qmailanalog programs is missing! Make sure you have
100             qmailanalog installed and the path to the binaries is set correctly in
101             toaster.conf. The current setting is $qmailanalog_dir
102             EO_QMADIR_MISSING
103              
104              
105 0 0         if ( -d "/usr/local/qmailanalog/bin" ) {
106 0           $qmailanalog_dir = "/usr/local/qmailanalog/bin";
107              
108 0           carp <<"EO_QMADIR_FOUND";
109              
110             YAY! I found your qmailanalog programs in /usr/local/qmailanalog/bin. You
111             should update toaster.conf so you stop getting this error message.
112             EO_QMADIR_FOUND
113             };
114             };
115              
116             # make sure that the matchup program is in there
117 0 0         unless ( -x "$qmailanalog_dir/matchup" ) {
118 0           carp <<"EO_NO_MATCHUP";
119              
120             report_yesterdays_activity: ERROR! The 'maillogs yesterday' feature only
121             works if qmailanalog is installed. I am unable to find the binaries for
122             it. Please make sure it is installed and the qmailanalog_bin setting in
123             toaster.conf is configured correctly.
124             EO_NO_MATCHUP
125              
126 0           return;
127             }
128              
129 0           return $qmailanalog_dir;
130             };
131              
132             sub get_yesterdays_send_log {
133 0     0 0   my $self = shift;
134              
135 0 0 0       if ( $self->conf->{send_log_method} && $self->conf->{send_log_method} eq "syslog" ) {
136 0           return $self->get_yesterdays_send_log_syslog;
137             };
138              
139             # some form of multilog logging
140 0           my $logbase = $self->toaster->get_log_dir;
141              
142 0           my ( $dd, $mm, $yy ) = $self->util->get_the_date(bump=>0);
143              
144             # where todays logs are archived
145 0           my $today = "$logbase/$yy/$mm/$dd/sendlog";
146 0           $self->audit( "updating todays symlink for sendlogs to $today");
147 0 0         unlink "$logbase/sendlog" if -l "$logbase/sendlog";
148 0           symlink( $today, "$logbase/sendlog" );
149              
150             # where yesterdays logs are archived
151 0           ( $dd, $mm, $yy ) = $self->util->get_the_date(bump=>1);
152 0           my $yester = "$logbase/$yy/$mm/$dd/sendlog.gz";
153 0           $self->audit( "updating yesterdays symlink for sendlogs to $yester" );
154 0 0         unlink "$logbase/sendlog.gz" if -l "$logbase/sendlog.gz";
155 0           symlink( $yester, "$logbase/sendlog.gz" );
156              
157 0           return $yester;
158             };
159              
160             sub get_yesterdays_send_log_syslog {
161 0     0 0   my $self = shift;
162              
163             # freebsd's maillog is rotated daily
164 0 0         if ( $OSNAME eq "freebsd" ) {
165 0           my $file = "/var/log/maillog.0";
166              
167 0 0         return -e "$file.bz2" ? "$file.bz2"
    0          
168             : -e "$file.gz" ? "$file.gz"
169             : croak "could not find yesterdays qmail-send logs! ";
170             }
171              
172 0 0         if ( $OSNAME eq "darwin" ) {
173 0           return "/var/log/mail.log"; # logs are rotated weekly.
174             }
175              
176 0           my $file = "/var/log/mail.log.0";
177              
178 0 0         return -e "$file.gz" ? "$file.gz"
    0          
179             : -e "$file.bz2" ? "$file.bz2"
180             : croak "could not find your mail logs from yesterday!\n";
181             };
182              
183             sub get_yesterdays_smtp_log {
184 0     0 0   my $self = shift;
185              
186 0           my $logbase = $self->toaster->get_log_dir;
187              
188             # set up our date variables for today
189 0           my ( $dd, $mm, $yy ) = $self->util->get_the_date(bump=>0);
190              
191             # where todays logs are being archived
192 0           my $today = "$logbase/$yy/$mm/$dd/smtplog";
193 0           $self->audit( "updating todays symlink for smtplogs to $today" );
194 0 0         unlink("$logbase/smtplog") if -l "$logbase/smtplog";
195 0           symlink( $today, "$logbase/smtplog" );
196              
197 0           ( $dd, $mm, $yy ) = $self->util->get_the_date(bump=>1);
198              
199             # where yesterdays logs are being archived
200 0           my $yester = "$logbase/$yy/$mm/$dd/smtplog.gz";
201 0           $self->audit( "updating yesterdays symlink for smtplogs" );
202 0 0         unlink("$logbase/smtplog.gz") if -l "$logbase/smtplog.gz";
203 0           symlink( $yester, "$logbase/smtplog.gz" );
204              
205 0           return $yester;
206             };
207              
208             sub verify_settings {
209 0     0 1   my $self = shift;
210 0           my %p = validate(@_, { $self->get_std_opts } );
211 0 0         return $p{test_ok} if defined $p{test_ok};
212              
213 0           my $logbase = $self->toaster->get_log_dir;
214 0   0       my $counters = $self->conf->{logs_counters} || "counters";
215              
216 0   0       my $user = $self->conf->{logs_user} || 'qmaill';
217 0   0       my $group = $self->conf->{logs_group} || 'qnofiles';
218              
219 0 0         if ( !-e $logbase ) {
220 0 0         mkpath( $logbase, 0, oct('0755') )
221             or return $self->error( "Couldn't create $logbase: $!", %p );
222 0 0         $self->util->chown($logbase, uid=>$user, gid=>$group) or return;
223             };
224              
225 0 0         if ( -w $logbase ) {
226 0 0         $self->util->chown($logbase, uid=>$user, gid=>$group) or return;
227             }
228              
229 0           my $dir = "$logbase/$counters";
230              
231 0 0         if ( ! -e $dir ) {
232 0           eval { mkpath( $dir, 0, oct('0755') ); };
  0            
233 0 0         return $self->error( "Couldn't create $dir: $!",fatal=>0) if $EVAL_ERROR;
234 0 0         $self->util->chown($dir, uid=>$user, gid=>$group) or return;
235             }
236 0 0         $self->error( "$dir is not a directory!",fatal=>0) if ! -d $dir;
237              
238 0           my $script = "/usr/local/bin/maillogs";
239 0 0         $script = '/usr/local/sbin/maillogs' if ! -x $script;
240              
241 0 0         return $self->error( "$script must be installed!",fatal=>0) if ! -e $script;
242 0 0         return $self->error( "$script must be executable!",fatal=>0) if ! -x $script;
243 0           return 1;
244             }
245              
246             sub parse_cmdline_flags {
247 0     0 1   my $self = shift;
248              
249 0           my %p = validate(@_, {
250             'prot' => { type=>SCALAR|UNDEF, optional=>1, },
251             $self->get_std_opts,
252             } );
253              
254 0 0         my $prot = $p{prot} or pod2usage;
255              
256 0           my @prots = qw/ smtp send imap pop3 rbl yesterday qmailscanner
257             spamassassin webmail test /;
258 0           my %valid_prots = map { $_ => 1 } @prots;
  0            
259              
260 0 0         pod2usage if !$valid_prots{$prot};
261              
262 0 0         return 1 if $prot eq "test";
263              
264 0           $self->audit( "parse_cmdline_flags: prot is $prot" );
265              
266 0 0         return $self->smtp_auth_count if $prot eq "smtp";
267 0 0         return $self->rbl_count if $prot eq "rbl";
268 0 0         return $self->send_count if $prot eq "send";
269 0 0         return $self->pop3_count if $prot eq "pop3";
270 0 0         return $self->imap_count if $prot eq "imap";
271 0 0         return $self->spama_count if $prot eq "spamassassin";
272 0 0         return $self->qms_count if $prot eq "qmailscanner";
273 0 0         return $self->webmail_count if $prot eq "webmail";
274 0 0         return $self->report_yesterdays_activity if $prot eq "yesterday";
275 0           pod2usage;
276             }
277              
278             sub what_am_i {
279 0     0 0   my $self = shift;
280 0           $self->audit( "what_am_i: $0");
281 0           $0 =~ /([a-zA-Z0-9\.]*)$/;
282 0           $self->audit( " returning $1" );
283 0           return $1;
284             }
285              
286             sub rbl_count {
287 0     0 1   my $self = shift;
288 0           my $verbose = $self->{verbose};
289              
290 0           my $countfile = $self->set_countfile(prot=>"rbl");
291 0           $spam_ref = $self->counter_read( file=>$countfile );
292 0           my $logbase = $self->toaster->get_log_dir;
293              
294 0           $self->process_rbl_logs(
295             files => $self->check_log_files( "$logbase/smtp/current" ),
296             );
297              
298 0 0         print " Spam Counts\n\n" if $verbose;
299              
300 0           my $i = 0;
301 0           while ( my ($description,$count) = each %$spam_ref ) {
302 0 0         print ":" if $i > 0;
303 0           print "$description:$count";
304 0           $i++;
305             }
306 0 0         print "\n" if $i > 0;
307 0           return 1;
308             }
309              
310             sub smtp_auth_count {
311 0     0 1   my $self = shift;
312 0           my $verbose = $self->{verbose};
313              
314 0           my $countfile = $self->set_countfile(prot=>"smtp");
315 0           my $count_ref = $self->counter_read( file=>$countfile );
316              
317 0 0         print " SMTP Counts\n\n" if $verbose;
318              
319 0           my $logfiles = $self->check_log_files( $self->syslog_locate );
320 0 0         if ( $logfiles->[0] eq "" ) {
321 0           carp "\nsmtp_auth_count: Ack, no logfiles! You may want to see why?";
322 0           return 1;
323             }
324              
325 0           my ($lines, %new_entries);
326              
327             # we could have one log file, or dozens (multilog)
328             # go through each, adding their entries to the new_entries hash.
329 0           foreach (@$logfiles) {
330 0           open my $LOGF, "<", $_;
331              
332 0           while ( my $log_line = <$LOGF> ) {
333 0 0         next unless ( $log_line =~ /vchkpw-(smtp|submission)/ );
334              
335 0           $lines++;
336 0           $new_entries{connect}++;
337 0 0         $new_entries{success}++ if ( $log_line =~ /success/i );
338             }
339             }
340              
341 0 0         if ( $new_entries{success} ) {
342              
343             # because rrdtool expects ever increasing counters (ie, not starting new
344             # each day), we keep track of when the counts suddenly reset (ie, after a
345             # syslog gets rotated). To reliably know when this happens, we save the
346             # last counter in a _last count. If the new count is greater the last
347             # count, add the difference, which is how many authentications
348             # happened since we last checked.
349              
350 0 0         if ( $new_entries{success} >= $count_ref->{success_last} ) {
351             $count_ref->{success} +=
352 0           ( $new_entries{success} - $count_ref->{success_last} );
353             }
354             else {
355             # If the counters are lower, then the logs were just rolled and we
356             # need only to add them to the new count.
357 0           $count_ref->{success} += $new_entries{success};
358             };
359              
360 0           $count_ref->{success_last} = $new_entries{success};
361             }
362              
363 0 0         if ( $new_entries{connect} ) {
364 0 0         if ( $new_entries{connect} >= $count_ref->{connect_last} ) {
365             $count_ref->{connect} +=
366 0           ( $new_entries{connect} - $count_ref->{connect_last} );
367             }
368             else {
369             $count_ref->{connect} += $new_entries{connect}
370 0           };
371              
372 0           $count_ref->{connect_last} = $new_entries{connect};
373             };
374              
375 0           foreach ( qw/ connect success / ) {
376 0 0         $count_ref->{$_} = 0 if ! defined $count_ref->{$_};
377             };
378              
379 0           print "smtp_auth_connect:$count_ref->{connect}:"
380             ."smtp_auth_success:$count_ref->{success}\n";
381              
382 0           return $self->counter_write( log=>$countfile, values=>$count_ref, fatal=>0, verbose=>$verbose );
383             }
384              
385             sub send_count {
386 0     0 1   my $self = shift;
387 0           my $verbose = $self->{verbose};
388              
389 0           my $logbase = $self->toaster->get_log_dir;
390 0           my $countfile = $self->set_countfile(prot=>"send");
391 0           $count_ref = $self->counter_read( file=>$countfile );
392              
393 0 0         print "processing send logs\n" if $verbose;
394              
395 0           $self->process_send_logs(
396             roll => 0,
397             files => $self->check_log_files( "$logbase/send/current" ),
398             );
399              
400 0 0 0       if ( $count_ref->{status_remotep} && $count_ref->{status} ) {
401             $count_ref->{concurrencyremote} =
402 0           ( $count_ref->{status_remotep} / $count_ref->{status} ) * 100;
403             }
404              
405 0 0         print " Counts\n\n" if $verbose;
406              
407 0           my $i = 0;
408 0           while ( my ($description, $count) = each %$count_ref ) {
409 0 0         print ":" if ( $i > 0 );
410 0           print "$description:$count";
411 0           $i++;
412             }
413 0           print "\n";
414 0           return 1;
415             }
416              
417             sub imap_count {
418 0     0 1   my $self = shift;
419 0           my $verbose = $self->{verbose};
420              
421 0           my ( $imap_success, $imap_connect, $imap_ssl_success, $imap_ssl_connect );
422              
423 0           my $countfile = $self->set_countfile(prot=>"imap");
424 0           $count_ref = $self->counter_read( file=>$countfile );
425              
426 0           my $logfiles = $self->check_log_files( $self->syslog_locate );
427 0 0         if ( @$logfiles[0] eq "" ) {
428 0           carp "\n imap_count ERROR: no logfiles!";
429 0           return;
430             }
431              
432 0           my $lines;
433 0           foreach (@$logfiles) {
434 0           open my $LOGF, "<", $_;
435              
436 0           while ( my $line = <$LOGF> ) {
437 0 0         next if $line !~ /imap/;
438              
439 0           $lines++;
440              
441 0 0         if ( $line =~ /imap-login/ ) { # dovecot
442 0 0         if ( $line =~ /secured/ ) { $imap_ssl_success++; }
  0            
443 0           else { $imap_success++; };
444 0           next;
445             };
446              
447 0 0         if ( $line =~ /ssl: LOGIN/ ) { # courier
448 0           $imap_ssl_success++;
449 0           next;
450             }
451              
452 0 0         if ( $line =~ /LOGIN/ ) { # courier
453 0           $imap_success++;
454 0           next;
455             }
456            
457             # elsif ( $line =~ /ssl: Connection/ ) { $imap_ssl_connect++; }
458             # elsif ( $line =~ /Connection/ ) { $imap_connect++; }
459             }
460 0           close $LOGF;
461             }
462              
463 0 0         unless ( $lines ) {
464 0   0       $count_ref->{imap_success} ||= 0; # hush those "uninitialized value" errors
465 0   0       $count_ref->{imap_ssl_success} ||= 0;
466              
467 0           print "imap_success:$count_ref->{imap_success}"
468             . ":imap_ssl_success:$count_ref->{imap_ssl_success}\n";
469 0 0         carp "imap_count: no log entries to process. I'm done!" if $verbose;
470 0           return 1;
471             };
472              
473 0 0         if ( $imap_success ) {
474 0 0         if ( $imap_success >= $count_ref->{imap_success_last} ) {
475             $count_ref->{imap_success}
476 0           += ( $imap_success - $count_ref->{imap_success_last} );
477             }
478             else {
479 0           $count_ref->{imap_success} += $imap_success;
480             }
481              
482 0           $count_ref->{imap_success_last} = $imap_success;
483             };
484              
485 0 0         if ( $imap_ssl_success ) {
486 0 0         if ( $imap_ssl_success >= $count_ref->{imap_ssl_success_last} ) {
487             $count_ref->{imap_ssl_success} +=
488 0           ( $imap_ssl_success - $count_ref->{imap_ssl_success_last} );
489             }
490             else {
491 0           $count_ref->{imap_ssl_success} += $imap_ssl_success;
492             }
493              
494 0           $count_ref->{imap_ssl_success_last} = $imap_ssl_success;
495             };
496              
497             print "imap_success:".$count_ref->{imap_success}
498 0           . ":imap_ssl_success:".$count_ref->{imap_ssl_success}."\n";
499              
500 0           return $self->counter_write( log=>$countfile, values=>$count_ref, fatal=>0 );
501             }
502              
503             sub pop3_count {
504 0     0 1   my $self = shift;
505 0           my $verbose = $self->{verbose};
506              
507             # read our counters from disk
508 0           my $countfile = $self->set_countfile(prot=>"pop3");
509              
510 0 0         print "pop3_count: reading counters from $countfile.\n" if $verbose;
511 0           $count_ref = $self->counter_read( file=>$countfile );
512              
513             # get the location of log files to process
514 0 0         print "finding the log files to process.\n" if $verbose;
515 0           my $logfiles = $self->check_log_files( $self->syslog_locate );
516 0 0         if ( $logfiles->[0] eq "" ) {
517 0           carp " pop3_count: ERROR: no logfiles to process!";
518 0           return;
519             }
520              
521 0 0         print "pop3_count: processing files @$logfiles.\n" if $verbose;
522              
523 0           my $lines;
524 0           my %new_entries = (
525             'connect' => 0,
526             'success' => 0,
527             'ssl_connect' => 0,
528             'ssl_success' => 0,
529             );
530              
531 0           my %valid_counters = (
532             'pop3_success' => 1, # successful authentication
533             'pop3_success_last' => 1, # last success count
534             'pop3_connect' => 1, # total connections
535             'pop3_connect_last' => 1, # last total connections
536             'pop3_ssl_success' => 1, # ssl successful auth
537             'pop3_ssl_success_last' => 1, # last ssl success auths
538             'pop3_ssl_connect' => 1, # ssl connections
539             'pop3_ssl_connect_last' => 1, # last ssl connects
540             );
541              
542 0           foreach my $key ( keys %valid_counters ) {
543 0 0         if ( ! defined $count_ref->{$key} ) {
544 0 0         carp "pop3_count: missing key $key in count_ref!" if $verbose;
545 0           $count_ref->{$key} = 0;
546             };
547             };
548              
549 0 0         print "processing...\n" if $verbose;
550 0           foreach (@$logfiles) {
551 0           open my $LOGF, "<", $_;
552              
553             LINE:
554 0           while ( my $line = <$LOGF> ) {
555 0 0         next unless ( $line =~ /pop3/ ); # discard everything not pop3
556 0           $lines++;
557              
558 0 0         if ( $line =~ /vchkpw-pop3:/ ) { # qmail-pop3d
    0          
    0          
    0          
559 0           $new_entries{connect}++;
560 0 0         $new_entries{success}++ if ( $line =~ /success/ );
561             }
562             elsif ( $line =~ /pop3d: / ) { # courier pop3d
563 0 0         $new_entries{connect}++ if ( $line =~ /Connection/ );
564 0 0         $new_entries{success}++ if ( $line =~ /LOGIN/ );
565             }
566             elsif ( $line =~ /pop3d-ssl: / ) { # courier pop3d-ssl
567 0 0         if ( $line =~ /LOGIN/ ) {
568 0           $new_entries{ssl_success}++;
569 0           next LINE;
570             };
571 0 0         $new_entries{ssl_connect}++ if ( $line =~ /Connection/ );
572             }
573             elsif ( $line =~ /pop3-login: / ) { # dovecot pop3
574 0 0         if ( $line =~ /secured/ ) {
575 0           $new_entries{ssl_success}++;
576             } else {
577 0           $new_entries{success}++;
578             }
579             }
580             }
581 0           close $LOGF;
582             }
583              
584 0 0         if ( ! $lines ) {
585 0           pop3_report();
586 0 0         carp "pop3_count: no log entries, I'm done!" if $verbose;
587 0           return 1;
588             };
589              
590 0 0         if ( $new_entries{success} ) {
591 0 0         if ( $new_entries{success} >= $count_ref->{pop3_success_last} ) {
592             $count_ref->{pop3_success} +=
593 0           ( $new_entries{success} - $count_ref->{pop3_success_last} );
594             }
595             else {
596 0           $count_ref->{pop3_success} += $new_entries{success};
597             }
598              
599 0           $count_ref->{pop3_success_last} = $new_entries{success};
600             };
601              
602 0 0         if ( $new_entries{connect} ) {
603 0 0         if ( $new_entries{connect} >= $count_ref->{pop3_connect_last} ) {
604             $count_ref->{pop3_connect} +=
605 0           ( $new_entries{connect} - $count_ref->{pop3_connect_last} );
606             }
607 0           else { $count_ref->{pop3_connect} += $new_entries{connect} }
608              
609 0           $count_ref->{pop3_connect_last} = $new_entries{connect};
610             };
611              
612 0 0         if ( $new_entries{ssl_success} ) {
613 0 0         if ( $new_entries{ssl_success} >= $count_ref->{pop3_ssl_success_last} ) {
614             $count_ref->{pop3_ssl_success} +=
615 0           ( $new_entries{ssl_success} - $count_ref->{pop3_ssl_success_last} );
616             }
617             else {
618 0           $count_ref->{pop3_ssl_success} += $new_entries{ssl_success};
619             }
620              
621 0           $count_ref->{pop3_ssl_success_last} = $new_entries{ssl_success};
622             };
623              
624 0 0         if ( $new_entries{ssl_connect} ) {
625 0 0         if ( $new_entries{ssl_connect} >= $count_ref->{pop3_ssl_connect_last} ) {
626             $count_ref->{pop3_ssl_connect}
627 0           += ( $new_entries{ssl_connect} - $count_ref->{pop3_ssl_connect_last} );
628             }
629             else {
630 0           $count_ref->{pop3_ssl_connect} += $new_entries{ssl_connect};
631             }
632              
633 0           $count_ref->{pop3_ssl_connect_last} = $new_entries{ssl_connect};
634             };
635              
636 0           pop3_report();
637              
638 0           return $self->counter_write( log=>$countfile, values=>$count_ref, fatal=>0 );
639             }
640              
641             sub pop3_report {
642              
643 0 0   0 0   $count_ref->{pop3_connect} || 0;
644 0 0         $count_ref->{pop3_ssl_connect} || 0;
645 0 0         $count_ref->{pop3_success} || 0;
646 0 0         $count_ref->{pop3_ssl_success} || 0;
647              
648             print "pop3_connect:" . $count_ref->{pop3_connect}
649             . ":pop3_ssl_connect:" . $count_ref->{pop3_ssl_connect}
650             . ":pop3_success:" . $count_ref->{pop3_success}
651             . ":pop3_ssl_success:" . $count_ref->{pop3_ssl_success}
652 0           . "\n";
653             };
654              
655             sub webmail_count {
656 0     0 1   my $self = shift;
657 0           my $verbose = $self->{verbose};
658              
659 0           my $countfile = $self->set_countfile(prot=>"web");
660 0           $count_ref = $self->counter_read( file=>$countfile );
661              
662 0           my $logfiles = $self->check_log_files( $self->syslog_locate );
663 0 0         if ( @$logfiles[0] eq "" ) {
664 0           carp "\n ERROR: no logfiles!";
665 0           return 0;
666             }
667              
668             # sample log entries
669             # Feb 21 10:24:41 cadillac sqwebmaild: LOGIN, user=matt@cadillac.net, ip=[66.227.213.209]
670             # Feb 21 10:27:00 cadillac sqwebmaild: LOGIN FAILED, user=matt@cadillac.net, ip=[66.227.213.209]
671              
672 0           my %temp;
673              
674 0           foreach (@$logfiles) {
675 0           open my $LOGF, "<", $_;
676              
677 0           while ( my $line = <$LOGF> ) {
678 0 0         next if $line =~ /spamd/; # typically half the syslog file
679 0 0         next if $line =~ /pop3/; # another 1/3 to 1/2
680              
681 0 0 0       if ( $line =~ /Successful webmail login/ ) { # squirrelmail w/plugin
    0          
    0          
682 0           $temp{success}++;
683 0           $temp{connect}++;
684             }
685             elsif ( $line =~ /sqwebmaild/ ) { # sqwebmail
686 0           $temp{connect}++;
687 0 0         $temp{success}++ if ( $line !~ /FAILED/ );
688             }
689             elsif ( $line =~ /imapd: LOGIN/ && $line =~ /127\.0\.0\./ )
690             { # IMAP connections on loopback interface are webmail
691 0           $temp{success}++;
692             }
693             }
694 0           close $LOGF;
695             }
696              
697 0 0         if ( !$temp{connect} ) {
698 0 0         carp "webmail_count: No webmail logins! I'm all done." if $verbose;
699 0           return 1;
700             };
701              
702 0 0         if ( $temp{success} ) {
703 0 0         if ( $temp{success} >= $count_ref->{success_last} ) {
704             $count_ref->{success} =
705 0           $count_ref->{success} + ( $temp{success} - $count_ref->{success_last} );
706             }
707 0           else { $count_ref->{success} = $count_ref->{success} + $temp{success} }
708              
709 0           $count_ref->{success_last} = $temp{success};
710             };
711              
712 0 0         if ( $temp{connect} ) {
713 0 0         if ( $temp{connect} >= $count_ref->{connect_last} ) {
714             $count_ref->{connect} =
715 0           $count_ref->{connect} + ( $temp{connect} - $count_ref->{connect_last} );
716             }
717 0           else { $count_ref->{connect} = $count_ref->{connect} + $temp{connect} }
718              
719 0           $count_ref->{connect_last} = $temp{connect};
720             };
721              
722 0 0         if ( ! $count_ref->{connect} ) {
723 0           $count_ref->{connect} = 0;
724             };
725              
726 0 0         if ( ! $count_ref->{success} ) {
727 0           $count_ref->{success} = 0;
728             };
729              
730 0           print "webmail_connect:$count_ref->{connect}"
731             . ":webmail_success:$count_ref->{success}"
732             . "\n";
733              
734 0           return $self->counter_write(
735             log => $countfile,
736             values => $count_ref,
737             fatal => 0,
738             );
739             }
740              
741             sub spama_count {
742 0     0 1   my $self = shift;
743 0           my $verbose = $self->{verbose};
744              
745 0           my $countfile = $self->set_countfile(prot=>"spam");
746 0           $count_ref = $self->counter_read( file=>$countfile );
747              
748 0           my $logfiles = $self->check_log_files( $self->syslog_locate );
749 0 0         if ( @$logfiles[0] eq "" ) {
750 0           carp "\n spamassassin_count ERROR: no logfiles!";
751 0           return;
752             }
753              
754 0           my %temp = ( spam => 1, ham => 1 );
755              
756 0           foreach (@$logfiles) {
757 0 0         open my $LOGF, "<", $_ or do {
758 0           carp "unable to open $_: $!";
759 0           next;
760             };
761              
762 0           while ( my $line = <$LOGF> ) {
763 0 0         next unless $line =~ /spamd/;
764 0           $temp{spamd_lines}++;
765              
766 0 0         if ( $line =~
    0          
767             /clean message \(([0-9-\.]+)\/([0-9\.]+)\) for .* in ([0-9\.]+) seconds, ([0-9]+) bytes/
768             )
769             {
770 0           $temp{ham}++;
771 0           $temp{ham_scores} += $1;
772 0           $temp{threshhold} += $2;
773 0           $temp{ham_seconds} += $3;
774 0           $temp{ham_bytes} += $4;
775             }
776             elsif ( $line =~
777             /identified spam \(([0-9-\.]+)\/([0-9\.]+)\) for .* in ([0-9\.]+) seconds, ([0-9]+) bytes/
778             )
779             {
780 0           $temp{spam}++;
781 0           $temp{spam_scores} += $1;
782 0           $temp{threshhold} += $2;
783 0           $temp{spam_seconds} += $3;
784 0           $temp{spam_bytes} += $4;
785             }
786             else {
787 0           $temp{other}++;
788             }
789             }
790              
791 0           close $LOGF;
792             }
793              
794 0 0         unless ( $temp{spamd_lines} ) {
795 0 0         carp "spamassassin_count: no log file entries for spamd!" if $verbose;
796 0           return 1;
797             };
798              
799 0   0       my $ham_count = $temp{ham} || 0;
800 0   0       my $spam_count = $temp{spam} || 0;
801              
802 0 0         if ( $ham_count ) {
803 0 0         if ( $ham_count >= $count_ref->{sa_ham_last} ) {
804             $count_ref->{sa_ham} =
805 0           $count_ref->{sa_ham} + ( $ham_count - $count_ref->{sa_ham_last} );
806             }
807             else {
808 0           $count_ref->{sa_ham} = $count_ref->{sa_ham} + $ham_count;
809             }
810             };
811              
812 0 0         if ( $spam_count ) {
813 0 0         if ( $spam_count >= $count_ref->{sa_spam_last} ) {
814             $count_ref->{sa_spam} =
815 0           $count_ref->{sa_spam} + ( $spam_count - $count_ref->{sa_spam_last} );
816             }
817             else {
818 0           $count_ref->{sa_spam} = $count_ref->{sa_spam} + $spam_count;
819             }
820             };
821              
822 0           require POSIX; # needed for floor
823             $count_ref->{avg_spam_score} = (defined $temp{spam_scores} && $spam_count )
824 0 0 0       ? POSIX::floor( $temp{spam_scores} / $spam_count * 100 ) : 0;
825              
826             $count_ref->{avg_ham_score} = (defined $temp{ham_scores} && $ham_count )
827 0 0 0       ? POSIX::floor( $temp{ham_scores} / $ham_count * 100 ) : 0;
828              
829             $count_ref->{threshhold} = ( $temp{threshhold} && ($ham_count || $spam_count) )
830 0 0 0       ? POSIX::floor( $temp{threshhold} / ( $ham_count + $spam_count ) * 100 ) : 0;
831              
832 0           $count_ref->{sa_ham_last} = $ham_count;
833 0           $count_ref->{sa_spam_last} = $spam_count;
834              
835             $count_ref->{sa_ham_seconds} = (defined $temp{ham_seconds} && $ham_count )
836 0 0 0       ? POSIX::floor( $temp{ham_seconds} / $ham_count * 100 ) : 0;
837              
838             $count_ref->{sa_spam_seconds} = (defined $temp{spam_seconds} && $spam_count)
839 0 0 0       ? POSIX::floor( $temp{spam_seconds} / $spam_count * 100 ) : 0;
840              
841             $count_ref->{sa_ham_bytes} = (defined $temp{ham_bytes} && $ham_count )
842 0 0 0       ? POSIX::floor( $temp{ham_bytes} / $ham_count * 100 ) : 0;
843              
844             $count_ref->{sa_spam_bytes} = (defined $temp{spam_bytes} && $spam_count )
845 0 0 0       ? POSIX::floor( $temp{spam_bytes} / $spam_count * 100 ) : 0;
846              
847 0           print "sa_spam:$count_ref->{sa_spam}"
848             . ":sa_ham:$count_ref->{sa_ham}"
849             . ":spam_score:$count_ref->{avg_spam_score}"
850             . ":ham_score:$count_ref->{avg_ham_score}"
851             . ":threshhold:$count_ref->{threshhold}"
852             . ":ham_seconds:$count_ref->{sa_ham_seconds}"
853             . ":spam_seconds:$count_ref->{sa_spam_seconds}"
854             . ":ham_bytes:$count_ref->{sa_ham_bytes}"
855             . ":spam_bytes:$count_ref->{sa_spam_bytes}"
856             . "\n";
857              
858 0           return $self->counter_write( log=>$countfile, values=>$count_ref, fatal=>0 );
859             }
860              
861             sub qms_count {
862 0     0 1   my $self = shift;
863 0           my $verbose = $self->{verbose};
864              
865 0           my ( $qs_clean, $qs_virus, $qs_all );
866              
867 0           my $countfile = $self->set_countfile(prot=>"virus");
868 0           my $count_ref = $self->counter_read( file=>$countfile );
869              
870 0           my $logfiles = $self->check_log_files( $self->syslog_locate );
871 0 0 0       if ( ! defined @$logfiles[0] || @$logfiles[0] eq "" ) {
872 0           carp " qms_count: ERROR: no logfiles!";
873 0           return 1;
874             }
875              
876 0           my $grep = $self->util->find_bin("grep", verbose=>0);
877 0           my $wc = $self->util->find_bin("wc", verbose=>0);
878              
879 0           $qs_clean = `$grep " qmail-scanner" @$logfiles | $grep "Clear:" | $wc -l`;
880 0           $qs_clean = $qs_clean * 1;
881 0           $qs_all = `$grep " qmail-scanner" @$logfiles | $wc -l`;
882 0           $qs_all = $qs_all * 1;
883 0           $qs_virus = $qs_all - $qs_clean;
884              
885 0 0         if ( $qs_all == 0 ) {
886 0 0         carp "qms_count: no log files for qmail-scanner found!" if $verbose;
887 0           return 1;
888             };
889              
890 0 0         if ( $qs_clean ) {
891 0 0         if ( $qs_clean >= $count_ref->{qs_clean_last} ) {
892             $count_ref->{qs_clean} =
893 0           $count_ref->{qs_clean} + ( $qs_clean - $count_ref->{qs_clean_last} );
894             }
895 0           else { $count_ref->{qs_clean} = $count_ref->{qs_clean} + $qs_clean }
896              
897 0           $count_ref->{qs_clean_last} = $qs_clean;
898             };
899              
900 0 0         if ( $qs_virus ) {
901 0 0         if ( $qs_virus >= $count_ref->{qs_virus_last} ) {
902             $count_ref->{qs_virus} =
903 0           $count_ref->{qs_virus} + ( $qs_virus - $count_ref->{qs_virus_last} );
904             }
905 0           else { $count_ref->{qs_virus} = $count_ref->{qs_virus} + $qs_virus }
906              
907 0           $count_ref->{qs_virus_last} = $qs_virus;
908             };
909              
910 0           print "qs_clean:$qs_clean:qs_virii:$qs_virus\n";
911              
912 0 0         if ( !$count_ref ) {
913 0           $count_ref = { qs_clean=>0, qs_virii=>0 };
914             };
915              
916 0           return $self->counter_write( log=>$countfile, values=>$count_ref, fatal=>0 );
917             }
918              
919             sub roll_send_logs {
920 0     0 1   my $self = shift;
921 0           my $verbose = $self->{verbose};
922              
923 0           my $logbase = $self->toaster->get_log_dir;
924 0 0         print "roll_send_logs: logging base is $logbase.\n" if $verbose;
925              
926 0           my $countfile = $self->set_countfile(prot=>"send");
927 0           $count_ref = $self->counter_read( file=>$countfile );
928              
929 0           $self->process_send_logs(
930             roll => 1,
931             files => $self->check_log_files( "$logbase/send/current" ),
932             );
933              
934 0           $self->counter_write( log=>$countfile, values=>$count_ref, fatal=>0 );
935             }
936              
937             sub roll_rbl_logs {
938 0     0 1   my $self = shift;
939              
940 0           my $logbase = $self->toaster->get_log_dir;
941 0           my $countfile = $self->set_countfile(prot=>'rbl');
942              
943 0 0         if ( -r $countfile ) {
944 0           $spam_ref = $self->counter_read( file=>$countfile );
945             }
946              
947             $self->process_rbl_logs(
948 0           roll => 1,
949             files => $self->check_log_files( "$logbase/smtp/current" ),
950             );
951              
952 0           $self->counter_write( log=>$countfile, values=>$spam_ref, fatal=>0 );
953 0           exit 0;
954             }
955              
956             sub roll_pop3_logs {
957 0     0 0   my $self = shift;
958 0           my $verbose = $self->{verbose};
959 0           my $logbase = $self->toaster->get_log_dir;
960              
961 0           $self->process_pop3_logs(
962             roll => 1,
963             files => $self->check_log_files( "$logbase/pop3/current" ),
964             );
965              
966 0           $self->compress_yesterdays_logs( "pop3log" );
967             }
968              
969             sub compress_yesterdays_logs {
970 0     0 1   my $self = shift;
971 0 0         my $file = shift or croak "missing log file!";
972              
973 0           my ( $dd, $mm, $yy ) = $self->util->get_the_date(bump=>1 );
974              
975 0           my $logbase = $self->toaster->get_log_dir;
976 0           $file = "$logbase/$yy/$mm/$dd/$file";
977              
978 0 0         return $self->audit( " $file is already compressed") if -e "$file.gz";
979 0 0         return $self->audit( " $file does not exist.") if ! -e $file;
980 0 0         return $self->error( "insufficient permissions to compress $file",fatal=>0)
981             if ! $self->util->is_writable( "$file.gz",fatal=>0 );
982              
983 0 0         my $gzip = $self->util->find_bin('gzip',fatal=>0) or return;
984 0 0         $self->util->syscmd( "$gzip $file", fatal=>0 )
985             or return $self->error( "compressing the logfile $file: $!", fatal=>0);
986              
987 0           $self->audit("compressed $file");
988 0           return 1;
989             }
990              
991             sub purge_last_months_logs {
992 0     0 1   my $self = shift;
993 0           my $verbose = $self->{verbose};
994              
995 0 0         if ( ! $self->conf->{logs_archive_purge} ) {
996 0           $self->audit( "logs_archive_purge is disabled in toaster.conf, skipping.\n");
997 0           return 1;
998             };
999              
1000 0 0         my ( $dd, $mm, $yy ) = $self->util->get_the_date(bump=>31 ) or return;
1001              
1002 0           my $logbase = $self->toaster->get_log_dir;
1003              
1004 0 0 0       unless ( $logbase && -d $logbase ) {
1005 0           carp "purge_last_months_logs: no log directory $logbase. I'm done!";
1006 0           return 1;
1007             };
1008              
1009 0           my $last_m_log = "$logbase/$yy/$mm";
1010              
1011 0 0         if ( ! -d $last_m_log ) {
1012 0 0         print "purge_last_months_logs: log dir $last_m_log doesn't exist. I'm done.\n" if $verbose;
1013 0           return 1;
1014             };
1015              
1016 0 0         print "\nI'm about to delete $last_m_log...." if $verbose;
1017 0 0         if ( rmtree($last_m_log) ) {
1018 0 0         print "done.\n\n" if $verbose;
1019 0           return 1;
1020             };
1021              
1022 0           return;
1023             }
1024              
1025             sub check_log_files {
1026 0     0 1   my $self = shift;
1027 0           my @exists;
1028 0           foreach my $file ( @_ ) {
1029 0 0 0       next if !$file || ! -e $file;
1030 0           push @exists, $file;
1031             };
1032 0           return \@exists;
1033             }
1034              
1035             sub check_log_files_2 {
1036             # this will be for logcheck based counters - someday
1037 0     0 0   my $self = shift;
1038 0           my @exists;
1039 0           foreach my $file ( @_ ) { };
1040 0           return \@exists;
1041             };
1042              
1043             sub process_pop3_logs {
1044 0     0 1   my $self = shift;
1045 0           my $verbose = $self->{verbose};
1046              
1047 0           my %p = validate(@_, {
1048             'roll' => { type=>BOOLEAN, optional=>1, default=>0 },
1049             'files' => { type=>ARRAYREF, optional=>1 }
1050             }
1051             );
1052              
1053 0           my $files_ref = $p{files};
1054              
1055 0           my $skip_archive = 0;
1056 0 0 0       $skip_archive++ if !$files_ref || !$files_ref->[0]; # no log file(s)!
1057              
1058 0 0         if ( $p{roll} ) {
1059              
1060 0           my $PIPE_TO_CRONOLOG;
1061 0 0         if ( ! $skip_archive ) {
1062 0 0         $PIPE_TO_CRONOLOG = $self->get_cronolog_handle("pop3log")
1063             or $skip_archive++;
1064             };
1065              
1066 0           while (<STDIN>) {
1067 0 0         print $_ if $self->conf->{logs_taifiles};
1068 0 0         print $PIPE_TO_CRONOLOG $_ if ! $skip_archive;
1069             }
1070 0 0         close $PIPE_TO_CRONOLOG if ! $skip_archive;
1071 0           return $skip_archive;
1072             }
1073              
1074             # these logfiles are empty unless verbose is enabled
1075 0           foreach my $file ( @$files_ref ) {
1076 0           $self->audit( " reading file $file...");
1077              
1078 0           my $MULTILOG_FILE;
1079 0 0         open ($MULTILOG_FILE, '<', $file ) or do {
1080 0           carp "couldn't read $file: $!";
1081 0           $skip_archive++;
1082 0           next;
1083             };
1084              
1085 0           while (<$MULTILOG_FILE>) {
1086 0           chomp;
1087             #count_pop3_line( $_ );
1088             }
1089 0           close $MULTILOG_FILE;
1090 0 0         $self->audit( "done.") if $verbose;
1091             }
1092              
1093 0           return $skip_archive;
1094             }
1095              
1096             sub process_rbl_logs {
1097 0     0 1   my $self = shift;
1098 0           my $verbose = $self->{verbose};
1099              
1100 0           my %p = validate( @_, {
1101             'roll' => { type=>BOOLEAN, optional=>1, default=>0 },
1102             'files' => { type=>ARRAYREF,optional=>1, },
1103             },
1104             );
1105              
1106 0           my $files_ref = $p{files};
1107              
1108 0           my $skip_archive = 0;
1109 0 0 0       $skip_archive++ if ! $files_ref || !$files_ref->[0]; # no log file(s)!
1110              
1111 0 0         if ( $p{roll} ) {
1112 0           my $PIPE_TO_CRONOLOG;
1113 0 0         if ( ! $skip_archive ) {
1114 0 0         $PIPE_TO_CRONOLOG = $self->get_cronolog_handle('smtplog')
1115             or $skip_archive++;
1116             };
1117              
1118 0           while (<STDIN>) {
1119 0           $self->count_rbl_line ( $_ );
1120 0 0         print $_ if $self->conf->{logs_taifiles};
1121 0 0         print $PIPE_TO_CRONOLOG $_ if ! $skip_archive;
1122             }
1123 0 0         close $PIPE_TO_CRONOLOG if ! $skip_archive;
1124 0           return $skip_archive;
1125             }
1126              
1127 0           foreach my $file ( @$files_ref ) {
1128 0 0         print "process_rbl_logs: reading file $file..." if $verbose;
1129              
1130 0           my $MULTILOG_FILE;
1131 0 0         open ($MULTILOG_FILE, "<", $file ) or do {
1132 0           carp "couldn't read $file: $!";
1133 0           $skip_archive++;
1134 0           next;
1135             };
1136              
1137 0           while (<$MULTILOG_FILE>) { $self->count_rbl_line( $_ ); }
  0            
1138 0           close $MULTILOG_FILE ;
1139 0 0         print "done.\n" if $verbose;
1140             }
1141              
1142 0           return $skip_archive;
1143             }
1144              
1145             sub count_rbl_line {
1146 0     0 1   my $self = shift;
1147 0 0         my $line = shift or return;
1148              
1149             # comment out print lines
1150 0           chomp $line;
1151              
1152 0 0         if ( $line =~ /rblsmtpd/ ) {
    0          
    0          
1153             # match the most common entries earliest
1154 0 0         if ( $line =~ /spamhaus/ ) { $spam_ref->{spamhaus}++ }
  0 0          
    0          
    0          
    0          
    0          
    0          
    0          
    0          
    0          
    0          
1155 0           elsif ( $line =~ /spamcop/ ) { $spam_ref->{spamcop}++ }
1156 0           elsif ( $line =~ /dsbl\.org/ ) { $spam_ref->{dsbl}++ }
1157 0           elsif ( $line =~ /services/ ) { $spam_ref->{services}++ }
1158 0           elsif ( $line =~ /rfc-ignorant/ ) { $spam_ref->{ignorant}++ }
1159 0           elsif ( $line =~ /sorbs/ ) { $spam_ref->{sorbs}++ }
1160 0           elsif ( $line =~ /njabl/ ) { $spam_ref->{njabl}++ }
1161 0           elsif ( $line =~ /ORDB/ ) { $spam_ref->{ordb}++ }
1162 0           elsif ( $line =~ /mail-abuse/ ) { $spam_ref->{maps}++ }
1163 0           elsif ( $line =~ /monkeys/ ) { $spam_ref->{monkeys}++ }
1164 0           elsif ( $line =~ /visi/ ) { $spam_ref->{visi}++ }
1165             else {
1166             #print $line;
1167 0           $spam_ref->{other}++;
1168             }
1169             }
1170             elsif ( $line =~ /CHKUSER/ ) {
1171 0 0         if ( $line =~ /CHKUSER acce/ ) { $spam_ref->{ham}++ }
  0 0          
1172 0           elsif ( $line =~ /CHKUSER reje/ ) { $spam_ref->{chkuser}++ }
1173             else {
1174             #print $line;
1175 0           $spam_ref->{other}++;
1176             }
1177             }
1178             elsif ( $line =~ /simscan/i ) {
1179 0 0         if ( $line =~ /clean/i ) { $spam_ref->{ham}++ }
  0 0          
    0          
1180 0           elsif ( $line =~ /virus:/i ) { $spam_ref->{virus}++ }
1181 0           elsif ( $line =~ /spam rej/i ) { $spam_ref->{spamassassin}++ }
1182             else {
1183             #print $line;
1184 0           $spam_ref->{other}++;
1185             };
1186             }
1187             else {
1188 0 0         if ( $line =~ /badhelo:/ ) { $spam_ref->{badhelo}++ }
  0 0          
    0          
    0          
1189 0           elsif ( $line =~ /badmailfrom:/ ) { $spam_ref->{badmailfrom}++ }
1190 0           elsif ( $line =~ /badmailto:/ ) { $spam_ref->{badmailto}++ }
1191 0           elsif ( $line =~ /Reverse/ ) { $spam_ref->{dns}++ }
1192             else {
1193             #print $line;
1194 0           $spam_ref->{other}++;
1195             };
1196             }
1197              
1198 0           $spam_ref->{count}++;
1199 0           return 1;
1200             }
1201              
1202             sub process_send_logs {
1203 0     0 1   my $self = shift;
1204 0           my $verbose = $self->{verbose};
1205              
1206 0           my %p = validate( @_, {
1207             'roll' => { type=>SCALAR, optional=>1, default=>0 },
1208             'files' => { type=>ARRAYREF,optional=>1, },
1209             },
1210             );
1211              
1212 0           my $files_ref = $p{files};
1213              
1214 0           my $skip_archive = 0;
1215 0 0 0       $skip_archive++ if ! $files_ref || !$files_ref->[0]; # no log files
1216              
1217 0 0         if ( $p{roll} ) {
1218              
1219 0 0         print "process_send_logs: log rolling is enabled.\n" if $verbose;
1220              
1221 0           my $PIPE_TO_CRONOLOG;
1222 0 0         if ( ! $skip_archive ) {
1223 0 0         $PIPE_TO_CRONOLOG = $self->get_cronolog_handle("sendlog")
1224             or $skip_archive++;
1225             };
1226              
1227 0           while (<STDIN>) {
1228 0           $self->count_send_line( $_ );
1229 0 0         print $_ if $self->conf->{logs_taifiles};
1230 0 0         print $PIPE_TO_CRONOLOG $_ if ! $skip_archive;
1231             }
1232 0 0         close $PIPE_TO_CRONOLOG if ! $skip_archive;
1233 0           return $skip_archive;
1234             }
1235              
1236 0 0         print "process_send_logs: log rolling is disabled.\n" if $verbose;
1237              
1238 0           foreach my $file ( @$files_ref ) {
1239              
1240 0 0         print "process_send_logs: reading file $file.\n" if $verbose;
1241              
1242 0           my $INFILE;
1243 0 0         open( $INFILE, "<", $file ) or do {
1244 0           carp "process_send_logs couldn't read $file: $!";
1245 0           $skip_archive++;
1246 0           next;
1247             };
1248              
1249 0           while (<$INFILE>) {
1250 0           chomp;
1251 0           $self->count_send_line( $_ );
1252             }
1253 0           close $INFILE;
1254             }
1255              
1256 0           return $skip_archive;
1257             }
1258              
1259             sub count_send_line {
1260 0     0 1   my $self = shift;
1261 0 0         my $line = shift or do {
1262 0           $count_ref->{message_other}++;
1263 0           return;
1264             };
1265              
1266             ########## $line will have a log entry in this format ########
1267             # @40000000450c020b32315f74 new msg 71198
1268             # @40000000450c020b32356e84 info msg 71198: bytes 3042 from <doc-committers@FreeBSD.org> qp 44209 uid 89
1269             # @40000000450c020b357ed10c starting delivery 196548: msg 71198 to localexample.org-user@example.org
1270             # @40000000450c020b357f463c status: local 1/10 remote 0/100
1271             # @40000000450c020c06ac5dcc delivery 196548: success: did_0+0+1/
1272             # @40000000450c020c06b6122c status: local 0/10 remote 0/100
1273             # @40000000450c020c06be6ae4 end msg 71198
1274             ################################################
1275              
1276 0           chomp $line;
1277             #carp "$line";
1278              
1279             # split the line into date and activity
1280 0           my ( $tai_date, $activity ) = $line =~ /\A@([a-z0-9]*)\s(.*)\z/xms;
1281              
1282 0 0         unless ($activity) {
1283 0           $count_ref->{message_other}++;
1284 0           return;
1285             };
1286              
1287 0 0         if ( $activity =~ /^new msg/ ) {
    0          
    0          
    0          
    0          
    0          
    0          
1288             # new msg 71512
1289             # the complete line match: /^new msg ([0-9]*)/
1290 0           $count_ref->{message_new}++;
1291             }
1292             elsif ( $activity =~ /^info msg / ) {
1293             # info msg 71766: bytes 28420 from <elfer@club-internet.fr> qp 5419 uid 89
1294             # a complete line match
1295             # /^info msg ([0-9]*): bytes ([0-9]*) from \<(.*)\> qp ([0-9]*)/
1296              
1297 0           $activity =~ /^info msg ([0-9]*): bytes ([0-9]*) from/;
1298              
1299 0           $count_ref->{message_bytes} += $2;
1300 0           $count_ref->{message_info}++;
1301             }
1302             elsif ( $activity =~ /^starting delivery/ ) {
1303              
1304             # starting delivery 136986: msg 71766 to remote bbarnes@example.com
1305              
1306             # a more complete line match
1307             # /^starting delivery ([0-9]*): msg ([0-9]*) to ([a-z]*) ([a-zA-Z\@\._-])$/
1308              
1309 0           $activity =~ /^starting delivery ([0-9]*): msg ([0-9]*) to ([a-z]*) /;
1310              
1311 0 0         if ( $3 eq "remote" ) { $count_ref->{start_delivery_remote}++ }
  0 0          
1312 0           elsif ( $3 eq "local" ) { $count_ref->{start_delivery_local}++ }
1313 0           else { print "count_send_line: unknown delivery line format\n"; };
1314              
1315 0           $count_ref->{start_delivery}++;
1316             }
1317             elsif ( $activity =~ /^status: local/ ) {
1318             # status: local 0/10 remote 3/100
1319 0           $activity =~ /^status: local ([0-9]*)\/([0-9]*) remote ([0-9]*)\/([0-9]*)/;
1320              
1321 0           $count_ref->{status_localp} += ( $1 / $2 );
1322 0           $count_ref->{status_remotep} += ( $3 / $4 );
1323              
1324 0           $count_ref->{status}++;
1325             }
1326             elsif ( $activity =~ /^end msg/ ) {
1327             # end msg 71766
1328             # /^end msg ([0-9]*)$/
1329              
1330             # this line is useless, why was it here?
1331             #$count_ref->{local}++ if ( $3 && $3 eq "local" );
1332              
1333 0           $count_ref->{message_end}++;
1334             }
1335             elsif ( $activity =~ /^delivery/ ) {
1336             # delivery 136986: success: 67.109.54.82_accepted_message./Remote_host_said:
1337             # _250_2.6.0__<000c01c6c92a$97f4a580$8a46c3d4@p3>_Queued_mail_for_delivery/
1338              
1339 0           $activity =~ /^delivery ([0-9]*): ([a-z]*): /;
1340              
1341 0 0         if ( $2 eq "success" ) { $count_ref->{delivery_success}++ }
  0 0          
    0          
1342 0           elsif ( $2 eq "deferral" ) { $count_ref->{delivery_deferral}++ }
1343 0           elsif ( $2 eq "failure" ) { $count_ref->{delivery_failure}++ }
1344 0           else { print "unknown " . $activity . "\n"; };
1345              
1346 0           $count_ref->{delivery}++;
1347             }
1348             elsif ( $activity =~ /^bounce/ ) {
1349             # /^bounce msg ([0-9]*) [a-z]* ([0-9]*)/
1350 0           $count_ref->{message_bounce}++;
1351             }
1352             else {
1353             #warn "other: $activity";
1354 0           $count_ref->{other}++;
1355             }
1356              
1357 0           return 1;
1358             }
1359              
1360              
1361             sub counter_create {
1362 0     0 0   my $self = shift;
1363 0           my $file = shift;
1364              
1365 0           my $verbose = $self->{verbose};
1366 0 0         carp "\nWARN: the file $file is missing! I will try to create it." if $verbose;
1367              
1368 0 0         if ( ! $self->util->is_writable( $file,verbose=>0,fatal=>0) ) {
1369 0 0         carp "FAILED.\n $file does not exist and the user $UID has "
1370             . "insufficent privileges to create it!" if $verbose;
1371 0           return;
1372             };
1373              
1374 0           $self->counter_write( log => $file, values => { created => time, },);
1375              
1376 0   0       my $user = $self->{conf}{logs_user} || "qmaill";
1377 0   0       my $group = $self->{conf}{logs_group} || "qnofiles";
1378              
1379 0           $self->util->chown( $file, uid=>$user, gid=>$group, verbose=>0);
1380              
1381 0           print "done.\n";
1382 0           return 1;
1383             };
1384              
1385             sub counter_read {
1386 0     0 1   my $self = shift;
1387 0           my %p = validate(@_, { 'file' => SCALAR, $self->get_std_opts } );
1388 0           my %args = $self->get_std_args( %p );
1389              
1390 0 0         my $file = $p{file} or croak "you must pass a filename!\n";
1391 0           my $verbose = $p{verbose};
1392              
1393 0 0         if ( ! -e $file ) {
1394 0 0         $self->counter_create( $file ) or return;
1395             }
1396              
1397 0           my %hash = (
1398             connect_last => 0,
1399             success_last => 0
1400             );
1401              
1402 0           foreach ( $self->util->file_read( $file, verbose=>$verbose ) ) {
1403 0           my ($description, $count) = split( /:/, $_ );
1404 0           $hash{ $description } = $count;
1405             }
1406              
1407 0           $self->audit( "counter_read: read counters from $file", %args );
1408              
1409 0           return \%hash;
1410             }
1411              
1412             sub counter_write {
1413 0     0 1   my $self = shift;
1414 0           my %p = validate( @_, {
1415             'values' => HASHREF,
1416             'log' => SCALAR,
1417             $self->get_std_opts,
1418             },
1419             );
1420 0           my %args = $self->get_std_args( %p );
1421              
1422 0           my ( $logfile, $values_ref ) = ( $p{log}, $p{values} );
1423              
1424 0 0         if ( -d $logfile ) {
1425 0           print "FAILURE: counter_write $logfile is a directory!\n";
1426             }
1427              
1428 0 0         return $self->error( "counter_write: $logfile is not writable",fatal=>0 )
1429             unless $self->util->is_writable( $logfile, %args );
1430              
1431 0 0         unless ( -e $logfile ) {
1432 0           print "NOTICE: counter_write is creating $logfile";
1433             }
1434              
1435             # it might be necessary to wrap the counters
1436             #
1437             # if so, the 32 and 64 bit limits are listed below. Just
1438             # check the number, and subtract the maximum value for it.
1439             # rrdtool will continue to Do The Right Thing. :)
1440              
1441 0           my @lines;
1442 0           while ( my ($key, $value) = each %$values_ref ) {
1443 0           $self->audit( "key: $key \t val: $value", %args);
1444 0 0 0       if ( $key && defined $value ) {
1445             # 32 bit - 4294967295
1446             # 64 bit - 18446744073709551615
1447 0 0         if ( $value > 4294967295 ) { $value = $value - 4294967295; };
  0            
1448 0           push @lines, "$key:$value";
1449             }
1450             }
1451              
1452 0           return $self->util->file_write( $logfile, lines => \@lines, %args );
1453             }
1454              
1455             sub get_cronolog_handle {
1456 0     0 0   my $self = shift;
1457 0 0         my $file = shift or croak "missing file!";
1458 0           my $verbose = $self->{verbose};
1459              
1460 0           my $logbase = $self->toaster->get_log_dir;
1461              
1462             # archives disabled in toaster.conf
1463 0 0         if ( ! $self->conf->{logs_archive} ) {
1464 0 0         warn "get_cronolog_handle: archives disabled, skipping cronolog handle.\n" if $verbose;
1465 0           return;
1466             };
1467              
1468             # $logbase is missing and we haven't permission to create it
1469 0 0         unless ( -w $logbase ) {
1470 0           warn "WARN: could not write to $logbase. FAILURE!";
1471 0           return;
1472             };
1473              
1474 0           my $cronolog = $self->util->find_bin( "cronolog", verbose=>0, fatal=>0 );
1475 0 0 0       if ( ! $cronolog || !-x $cronolog) {
1476 0           warn "cronolog could not be found. Please install it!";
1477 0           return;
1478             }
1479              
1480 0           my $tai64nlocal;
1481              
1482 0 0         if ( $self->conf->{logs_archive_untai} ) {
1483 0           my $taibin = $self->util->find_bin( "tai64nlocal",verbose=>0, fatal=>0 );
1484              
1485 0 0         if ( ! $taibin ) {
1486 0           carp "tai64nlocal is selected in toaster.conf but cannot be found!";
1487             };
1488              
1489 0 0 0       if ( $taibin && ! -x $taibin ) {
1490 0           carp "tai64nlocal is not executable by you! ERROR!";
1491             }
1492              
1493 0           $tai64nlocal = $taibin;
1494             }
1495              
1496 0           my $cronolog_invocation = "| ";
1497 0 0         $cronolog_invocation .= "$tai64nlocal | " if $tai64nlocal;
1498 0           $cronolog_invocation .= "$cronolog $logbase/\%Y/\%m/\%d/$file";
1499              
1500             ## no critic
1501 0 0         open my $PIPE_TO_CRONOLOG, $cronolog_invocation or return;
1502             ## use critic
1503              
1504 0           return $PIPE_TO_CRONOLOG;
1505             };
1506              
1507             sub syslog_locate {
1508 0     0 1   my ( $self, $verbose ) = @_;
1509              
1510 0           my $log = "/var/log/maillog";
1511              
1512 0 0         if ( -e $log ) {
1513 0 0         print "syslog_locate: using $log\n" if $verbose;
1514 0           return "$log";
1515             }
1516              
1517 0           $log = "/var/log/mail.log";
1518 0 0         if ( $OSNAME eq "darwin" ) {
1519 0 0         print "syslog_locate: Darwin detected...using $log\n" if $verbose;
1520 0           return $log;
1521             }
1522              
1523 0 0         if ( -e $log ) {
1524 0 0         print "syslog_locate: using $log\n" if $verbose;
1525 0           return $log;
1526             };
1527              
1528 0           $log = "/var/log/messages";
1529 0 0         return $log if -e $log;
1530              
1531 0           $log = "/var/log/system.log";
1532 0 0         return $log if -e $log;
1533              
1534 0           croak "syslog_locate: can't find your syslog mail log\n";
1535             }
1536              
1537             sub set_countfile {
1538 0     0 0   my $self = shift;
1539              
1540 0           my %p = validate(@_, { prot=>SCALAR } );
1541 0           my $prot = $p{prot};
1542              
1543 0           my $logbase = $self->toaster->get_log_dir;
1544 0   0       my $counters = $self->conf->{logs_counters} || "counters";
1545 0   0       my $prot_file = $self->conf->{'logs_'.$prot.'_count'} || "$prot.txt";
1546              
1547             # $self->audit( "countfile: $logbase/$counters/$prot_file");
1548              
1549 0           return "$logbase/$counters/$prot_file";
1550             }
1551              
1552             1;
1553             __END__
1554             sub {}
1555              
1556             =head1 NAME
1557              
1558             Mail::Toaster::Logs - objects and functions for interacting with email logs
1559              
1560             This module contains functions related to mail logging and are used primarily in maillogs. Some functions are also used in toaster-watcher.pl and toaster_setup.pl.
1561              
1562              
1563             =head1 METHODS
1564              
1565             =over 8
1566              
1567             =item new
1568              
1569             Create a new Mail::Toaster::Logs object.
1570              
1571             use Mail::Toaster::Logs;
1572             $logs = Mail::Toaster::Logs->new;
1573              
1574              
1575             =item report_yesterdays_activity
1576              
1577             email a report of yesterdays email traffic.
1578              
1579              
1580             =item verify_settings
1581              
1582             Does some checks to make sure things are set up correctly.
1583              
1584             $logs->verify_settings;
1585              
1586             tests:
1587              
1588             logs base directory exists
1589             logs based owned by qmaill
1590             counters directory exists
1591             maillogs is installed
1592              
1593              
1594             =item parse_cmdline_flags
1595              
1596             Do the appropriate things based on what argument is passed on the command line.
1597              
1598             $logs->parse_cmdline_flags(prot=>$prot, verbose=>0);
1599              
1600             $prot is the protocol we're supposed to work on.
1601              
1602              
1603             =item check_log_files
1604              
1605             $logs->check_log_files( $check );
1606              
1607              
1608             =item compress_yesterdays_logs
1609              
1610             $logs->compress_yesterdays_logs( $file );
1611              
1612              
1613             =item count_rbl_line
1614              
1615             $logs->count_rbl_line($line);
1616              
1617              
1618             =item count_send_line
1619              
1620             usage:
1621             $logs->count_send_line( $count, $line );
1622              
1623             arguments required:
1624             count - a hashref of counter values
1625             line - an entry from qmail's send logs
1626              
1627             results:
1628             a hashref will be returned with updated counters
1629              
1630              
1631             =item counter_read
1632              
1633             $logs->counter_read( file=>$file );
1634              
1635             $file is the file to read from. The sub returns a hashref full of key value pairs.
1636              
1637              
1638             =item counter_write
1639              
1640             $logs->counter_write(log=>$file, values=>$values);
1641              
1642             arguments required:
1643             file - the logfile to write.
1644             values - a hashref of value=count style pairs.
1645              
1646             result:
1647             1 if written
1648             0 if not.
1649              
1650             =cut
1651              
1652             =item imap_count
1653              
1654             $logs->imap_count;
1655              
1656             Count the number of connections and successful authentications via IMAP and IMAP-SSL.
1657              
1658              
1659             =item pop3_count
1660              
1661             $logs->pop3_count;
1662              
1663             Count the number of connections and successful authentications via POP3 and POP3-SSL.
1664              
1665              
1666             =item process_pop3_logs
1667              
1668              
1669             =item process_rbl_logs
1670              
1671             process_rbl_logs(
1672             roll => 0,
1673             files => $self->check_log_files( "$logbase/smtp/current" ),
1674             );
1675              
1676              
1677              
1678             =item process_send_logs
1679              
1680              
1681              
1682             =item qms_count
1683              
1684             $logs->qms_count;
1685              
1686             Count statistics logged by qmail scanner.
1687              
1688              
1689             =item purge_last_months_logs
1690              
1691             $logs->purge_last_months_logs(
1692             fatal => 0,
1693             );
1694              
1695             For a supplied protocol, cleans out last months email logs.
1696              
1697              
1698             =item rbl_count
1699              
1700             Count the number of connections we've blocked (via rblsmtpd) for each RBL that we use.
1701              
1702             $logs->rbl_count(
1703              
1704             =item roll_rbl_logs
1705              
1706             $logs->roll_rbl_logs;
1707              
1708             Roll the qmail-smtpd logs (without 2>&1 output generated by rblsmtpd).
1709              
1710             =item RollPOP3Logs
1711              
1712             $logs->RollPOP3Logs;
1713              
1714             These logs will only exist if tcpserver verbose is enabled. Rolling them is not likely to be necessary but the code is here should it ever prove necessary.
1715              
1716              
1717             =item roll_send_logs
1718              
1719             $logs->roll_send_logs;
1720              
1721             Roll the qmail-send multilog logs. Update the maillogs counter.
1722              
1723              
1724             =item send_count
1725              
1726             $logs->send_count;
1727              
1728             Count the number of messages we deliver, and a whole mess of stats from qmail-send.
1729              
1730              
1731             =item smtp_auth_count
1732              
1733             $logs->smtp_auth_count;
1734              
1735             Count the number of times users authenticate via SMTP-AUTH to our qmail-smtpd daemon.
1736              
1737              
1738             =item spama_count
1739              
1740             $logs->spama_count;
1741              
1742             Count statistics logged by SpamAssassin.
1743              
1744              
1745             =item syslog_locate
1746              
1747             $logs->syslog_locate;
1748              
1749             Determine where syslog.mail is logged to. Right now we just test based on the OS you're running on and assume you've left it in the default location. This is easy to expand later.
1750              
1751             =cut
1752              
1753             =item webmail_count
1754              
1755             $logs->webmail_count;
1756              
1757             Count the number of webmail authentications.
1758              
1759             =back
1760              
1761             =head1 AUTHOR
1762              
1763             Matt Simerson <matt@tnpi.net>
1764              
1765              
1766             =head1 BUGS
1767              
1768             None known. Report any to author.
1769             Patches welcome.
1770              
1771              
1772             =head1 SEE ALSO
1773              
1774             The following are relevant man/perldoc pages:
1775              
1776             maillogs
1777             Mail::Toaster
1778             toaster.conf
1779              
1780             http://mail-toaster.org/
1781              
1782              
1783             =head1 COPYRIGHT
1784              
1785             Copyright (c) 2004-2008, The Network People, Inc. All rights reserved.
1786              
1787             Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met:
1788              
1789             Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer.
1790              
1791             Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution.
1792              
1793             Neither the name of the The Network People, Inc. nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission.
1794              
1795             THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
1796              
1797             =cut